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
+35 -81

No files matched your search

+34 -80
View File
@@ -1,37 +1,18 @@
import { useState } from 'react'
import { FaCodeBranch } from 'react-icons/fa'
import { FaLinkSlash } from 'react-icons/fa6'
import { Badge, Button } from './ui'
import PreviewBlock from './PreviewBlock'
import AnalyseRow from './AnalyseRow'
import GiteaLinkButton from './GiteaLinkButton'
import PreviewLinkButton from './PreviewLinkButton'
import { statusValueTone } from './projectTones'
import { suggestSubdomain } from '../lib/subdomain'
import { needsPreviewButton, assignSummary } from '../lib/projectAssign'
import { updateWebsiteProject, backfillProjectPushes } from '../lib/projectAdminApi'
const STATUS_COLORS = {
ready: 'var(--green-fg)',
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 }) {
// Status und Provisioning: Wert als Badge, ohne Wert nichts
function StatusValue({ label, value }) {
if (!value) return null
const color = STATUS_COLORS[String(value).toLowerCase()] || 'var(--text-2)'
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>
)
return <span>{label}: <Badge tone={statusValueTone(value)}>{value}</Badge></span>
}
export default function AssignedProjectCard({ project, onUnassign, showActions = false, customerName = '', onChanged = () => {} }) {
@@ -79,65 +60,47 @@ export default function AssignedProjectCard({ project, onUnassign, showActions =
}
return (
<div style={{ background: 'var(--bg-subtle)', border: '1px solid var(--line)', borderRadius: 10, padding: '12px 14px', marginBottom: 12 }}>
<div style={{ display: 'flex', gap: 8, alignItems: 'flex-start', justifyContent: 'space-between' }}>
<div style={{ minWidth: 0 }}>
<div className="proj-item">
<div className="proj-head">
<div className="proj-main">
<strong>{project.projectName}</strong>
{project.subdomain && <span className="text-grey" style={{ marginLeft: 8, fontSize: 13 }}>({project.subdomain})</span>}
{project.repoFullName && (
<div className="text-grey" style={{ fontSize: 12, marginTop: 4 }}>
<FaCodeBranch style={{ marginRight: 4 }} />{project.repoFullName}
{project.subdomain && <>{' '}<span className="muted small">({project.subdomain})</span></>}
{project.repoFullName && <div className="cell-sub">{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} />
<GiteaLinkButton href={project.giteaRepoUrl} />
{showActions && onUnassign && (
<button
type="button"
title="Vom Ticket lösen"
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>
<Button variant="danger" type="button" title="Vom Ticket lösen" aria-label="Vom Ticket lösen" onClick={() => onUnassign(project.$id)}>
Lösen
</Button>
)}
</div>
</div>
{/* Status- und Provisioning-Zeile */}
<div style={{ marginTop: 10, display: 'flex', flexWrap: 'wrap', alignItems: 'center' }}>
<StatusBadge label="Status" value={project.status} />
<StatusBadge label="Provisioning" value={project.provisioningStatus} />
{(project.status || project.provisioningStatus) && (
<div className="proj-meta">
<StatusValue label="Status" value={project.status} />
<StatusValue label="Provisioning" value={project.provisioningStatus} />
</div>
)}
{project.subdomain && <AnalyseRow key={project.$id} projectId={project.$id} />}
{showActions && project.subdomain && (
<div style={{ marginTop: 10 }}>
<button
type="button"
className="btn btn-sm"
aria-expanded={showPreviewBlock}
onClick={() => setShowPreviewBlock((v) => !v)}
>
<>
<button type="button" className="link" aria-expanded={showPreviewBlock} onClick={() => setShowPreviewBlock((v) => !v)}>
Preview {showPreviewBlock ? '▴' : '▾'}
</button>
{showPreviewBlock && (
<div style={{ marginTop: 8 }}>
<PreviewBlock key={project.$id} projectId={project.$id} />
</div>
)}
</div>
{showPreviewBlock && <PreviewBlock key={project.$id} projectId={project.$id} />}
</>
)}
{showActions && (
<div style={{ marginTop: 10 }}>
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'center' }}>
<>
<div className="row-actions">
{needsPreviewButton(project) && !showPreviewInput && (
<button type="button" className="btn btn-sm" onClick={openPreviewInput}>
<button type="button" className="link" onClick={openPreviewInput}>
Preview einschalten
</button>
)}
@@ -145,32 +108,23 @@ export default function AssignedProjectCard({ project, onUnassign, showActions =
<>
<input
type="text"
className="form-control"
style={{ maxWidth: 200 }}
className="input"
aria-label="Subdomain für die Preview"
value={subdomain}
onChange={(e) => setSubdomain(e.target.value)}
/>
<button
type="button"
className="btn btn-sm"
disabled={!subdomain || previewBusy}
onClick={handleEnablePreview}
>
<Button type="button" disabled={!subdomain || previewBusy} onClick={handleEnablePreview}>
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
</button>
</div>
{(previewMessage || pushMessage) && (
<div className="text-grey" style={{ marginTop: 6, fontSize: 12 }}>
{previewMessage && <div>{previewMessage}</div>}
{pushMessage && <div>{pushMessage}</div>}
</div>
)}
</div>
{previewMessage && <p className="msg muted">{previewMessage}</p>}
{pushMessage && <p className="msg muted">{pushMessage}</p>}
</>
)}
</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-updated { min-width: 70px; }
.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; }
.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. */