Projekte: Projektzeile am Ticket und beim Kunden ohne Kasten, Aktionen als Textlinks
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8807af3f42
commit
47cf82eb91
2 files changed
+36
-82
No files matched your search
@@ -1,37 +1,18 @@
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { FaCodeBranch } from 'react-icons/fa'
|
import { Badge, Button } from './ui'
|
||||||
import { FaLinkSlash } from 'react-icons/fa6'
|
|
||||||
import PreviewBlock from './PreviewBlock'
|
import PreviewBlock from './PreviewBlock'
|
||||||
import AnalyseRow from './AnalyseRow'
|
import AnalyseRow from './AnalyseRow'
|
||||||
import GiteaLinkButton from './GiteaLinkButton'
|
import GiteaLinkButton from './GiteaLinkButton'
|
||||||
import PreviewLinkButton from './PreviewLinkButton'
|
import PreviewLinkButton from './PreviewLinkButton'
|
||||||
|
import { statusValueTone } from './projectTones'
|
||||||
import { suggestSubdomain } from '../lib/subdomain'
|
import { suggestSubdomain } from '../lib/subdomain'
|
||||||
import { needsPreviewButton, assignSummary } from '../lib/projectAssign'
|
import { needsPreviewButton, assignSummary } from '../lib/projectAssign'
|
||||||
import { updateWebsiteProject, backfillProjectPushes } from '../lib/projectAdminApi'
|
import { updateWebsiteProject, backfillProjectPushes } from '../lib/projectAdminApi'
|
||||||
|
|
||||||
const STATUS_COLORS = {
|
// Status und Provisioning: Wert als Badge, ohne Wert nichts
|
||||||
ready: 'var(--green-fg)',
|
function StatusValue({ label, value }) {
|
||||||
live: 'var(--green-fg)',
|
|
||||||
online: 'var(--green-fg)',
|
|
||||||
provisioning: 'var(--orange-fg)',
|
|
||||||
pending: 'var(--orange-fg)',
|
|
||||||
building: 'var(--blue-fg)',
|
|
||||||
error: 'var(--red-fg)',
|
|
||||||
failed: 'var(--red-fg)',
|
|
||||||
}
|
|
||||||
|
|
||||||
function StatusBadge({ label, value }) {
|
|
||||||
if (!value) return null
|
if (!value) return null
|
||||||
const color = STATUS_COLORS[String(value).toLowerCase()] || 'var(--text-2)'
|
return <span>{label}: <Badge tone={statusValueTone(value)}>{value}</Badge></span>
|
||||||
return (
|
|
||||||
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 12, color: 'var(--text-2)', marginRight: 12 }}>
|
|
||||||
{label}:
|
|
||||||
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 4, color }}>
|
|
||||||
<span style={{ width: 8, height: 8, borderRadius: '50%', background: color, display: 'inline-block' }} />
|
|
||||||
{value}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function AssignedProjectCard({ project, onUnassign, showActions = false, customerName = '', onChanged = () => {} }) {
|
export default function AssignedProjectCard({ project, onUnassign, showActions = false, customerName = '', onChanged = () => {} }) {
|
||||||
@@ -79,65 +60,47 @@ export default function AssignedProjectCard({ project, onUnassign, showActions =
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ background: 'var(--bg-subtle)', border: '1px solid var(--line)', borderRadius: 10, padding: '12px 14px', marginBottom: 12 }}>
|
<div className="proj-item">
|
||||||
<div style={{ display: 'flex', gap: 8, alignItems: 'flex-start', justifyContent: 'space-between' }}>
|
<div className="proj-head">
|
||||||
<div style={{ minWidth: 0 }}>
|
<div className="proj-main">
|
||||||
<strong>{project.projectName}</strong>
|
<strong>{project.projectName}</strong>
|
||||||
{project.subdomain && <span className="text-grey" style={{ marginLeft: 8, fontSize: 13 }}>({project.subdomain})</span>}
|
{project.subdomain && <>{' '}<span className="muted small">({project.subdomain})</span></>}
|
||||||
{project.repoFullName && (
|
{project.repoFullName && <div className="cell-sub">{project.repoFullName}</div>}
|
||||||
<div className="text-grey" style={{ fontSize: 12, marginTop: 4 }}>
|
|
||||||
<FaCodeBranch style={{ marginRight: 4 }} />{project.repoFullName}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', gap: 8, alignItems: 'center', flexShrink: 0 }}>
|
<div className="row-actions">
|
||||||
<PreviewLinkButton href={project.previewUrl} subdomain={project.subdomain} />
|
<PreviewLinkButton href={project.previewUrl} subdomain={project.subdomain} />
|
||||||
<GiteaLinkButton href={project.giteaRepoUrl} />
|
<GiteaLinkButton href={project.giteaRepoUrl} />
|
||||||
{showActions && onUnassign && (
|
{showActions && onUnassign && (
|
||||||
<button
|
<Button variant="danger" type="button" title="Vom Ticket lösen" aria-label="Vom Ticket lösen" onClick={() => onUnassign(project.$id)}>
|
||||||
type="button"
|
Lösen
|
||||||
title="Vom Ticket lösen"
|
</Button>
|
||||||
aria-label="Vom Ticket lösen"
|
|
||||||
onClick={() => onUnassign(project.$id)}
|
|
||||||
style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 32, height: 32, borderRadius: 6, background: 'var(--red-bg)', border: '1px solid var(--line)', color: 'var(--red-fg)', cursor: 'pointer' }}
|
|
||||||
>
|
|
||||||
<FaLinkSlash size={16} />
|
|
||||||
</button>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Status- und Provisioning-Zeile */}
|
{(project.status || project.provisioningStatus) && (
|
||||||
<div style={{ marginTop: 10, display: 'flex', flexWrap: 'wrap', alignItems: 'center' }}>
|
<div className="proj-meta">
|
||||||
<StatusBadge label="Status" value={project.status} />
|
<StatusValue label="Status" value={project.status} />
|
||||||
<StatusBadge label="Provisioning" value={project.provisioningStatus} />
|
<StatusValue label="Provisioning" value={project.provisioningStatus} />
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{project.subdomain && <AnalyseRow key={project.$id} projectId={project.$id} />}
|
{project.subdomain && <AnalyseRow key={project.$id} projectId={project.$id} />}
|
||||||
|
|
||||||
{showActions && project.subdomain && (
|
{showActions && project.subdomain && (
|
||||||
<div style={{ marginTop: 10 }}>
|
<>
|
||||||
<button
|
<button type="button" className="link" aria-expanded={showPreviewBlock} onClick={() => setShowPreviewBlock((v) => !v)}>
|
||||||
type="button"
|
|
||||||
className="btn btn-sm"
|
|
||||||
aria-expanded={showPreviewBlock}
|
|
||||||
onClick={() => setShowPreviewBlock((v) => !v)}
|
|
||||||
>
|
|
||||||
Preview {showPreviewBlock ? '▴' : '▾'}
|
Preview {showPreviewBlock ? '▴' : '▾'}
|
||||||
</button>
|
</button>
|
||||||
{showPreviewBlock && (
|
{showPreviewBlock && <PreviewBlock key={project.$id} projectId={project.$id} />}
|
||||||
<div style={{ marginTop: 8 }}>
|
</>
|
||||||
<PreviewBlock key={project.$id} projectId={project.$id} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{showActions && (
|
{showActions && (
|
||||||
<div style={{ marginTop: 10 }}>
|
<>
|
||||||
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'center' }}>
|
<div className="row-actions">
|
||||||
{needsPreviewButton(project) && !showPreviewInput && (
|
{needsPreviewButton(project) && !showPreviewInput && (
|
||||||
<button type="button" className="btn btn-sm" onClick={openPreviewInput}>
|
<button type="button" className="link" onClick={openPreviewInput}>
|
||||||
Preview einschalten
|
Preview einschalten
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
@@ -145,32 +108,23 @@ export default function AssignedProjectCard({ project, onUnassign, showActions =
|
|||||||
<>
|
<>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="form-control"
|
className="input"
|
||||||
style={{ maxWidth: 200 }}
|
aria-label="Subdomain für die Preview"
|
||||||
value={subdomain}
|
value={subdomain}
|
||||||
onChange={(e) => setSubdomain(e.target.value)}
|
onChange={(e) => setSubdomain(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<button
|
<Button type="button" disabled={!subdomain || previewBusy} onClick={handleEnablePreview}>
|
||||||
type="button"
|
|
||||||
className="btn btn-sm"
|
|
||||||
disabled={!subdomain || previewBusy}
|
|
||||||
onClick={handleEnablePreview}
|
|
||||||
>
|
|
||||||
Einschalten
|
Einschalten
|
||||||
</button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<button type="button" className="btn btn-sm" disabled={pushBusy} onClick={handleBackfill}>
|
<button type="button" className="link" disabled={pushBusy} onClick={handleBackfill}>
|
||||||
Pushes nachtragen
|
Pushes nachtragen
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{(previewMessage || pushMessage) && (
|
{previewMessage && <p className="msg muted">{previewMessage}</p>}
|
||||||
<div className="text-grey" style={{ marginTop: 6, fontSize: 12 }}>
|
{pushMessage && <p className="msg muted">{pushMessage}</p>}
|
||||||
{previewMessage && <div>{previewMessage}</div>}
|
</>
|
||||||
{pushMessage && <div>{pushMessage}</div>}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1121,7 +1121,7 @@ button.link { text-align: left; } /* Reset und Gewicht von button.link stehen im
|
|||||||
.projects-table td.cell-address code { display: block; max-width: 84px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; }
|
.projects-table td.cell-address code { display: block; max-width: 84px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; }
|
||||||
.projects-table td.cell-updated { min-width: 70px; }
|
.projects-table td.cell-updated { min-width: 70px; }
|
||||||
.projects-table tr.is-archived td { opacity: .72; }
|
.projects-table tr.is-archived td { opacity: .72; }
|
||||||
.row-actions { display: flex; flex-wrap: wrap; gap: 4px 14px; font-size: 13px; }
|
.row-actions { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 14px; font-size: 13px; } /* Links (a) und Textknoepfe (button) auf einer Zeile; ohne baseline sitzt ein button.link-danger in einer hoeheren Zeile mittig statt oben */
|
||||||
.row-actions .link { white-space: nowrap; }
|
.row-actions .link { white-space: nowrap; }
|
||||||
.projects-table .row-actions { justify-content: flex-end; }
|
.projects-table .row-actions { justify-content: flex-end; }
|
||||||
/* Unter 860 px steht jede Zeile als zweispaltiges Raster: Projekt ueber beide Spalten, Kunde und Status nebeneinander, Aktionen darunter. */
|
/* Unter 860 px steht jede Zeile als zweispaltiges Raster: Projekt ueber beide Spalten, Kunde und Status nebeneinander, Aktionen darunter. */
|
||||||
|
|||||||
Reference in new issue
Block a user