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:
ClaudeandClaude Sonnet 5.5 committed 2026-10-06 14:34:29 +00:00
1 parent 8807af3f42
commit 47cf82eb91
2 files changed
+36 -82

No files matched your search

+35 -81
View File
@@ -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>
) )
+1 -1
View File
@@ -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. */