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 { 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}
|
||||
</div>
|
||||
)}
|
||||
{project.subdomain && <>{' '}<span className="muted small">({project.subdomain})</span></>}
|
||||
{project.repoFullName && <div className="cell-sub">{project.repoFullName}</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} />
|
||||
</div>
|
||||
{(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>
|
||||
)
|
||||
|
||||
@@ -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. */
|
||||
|
||||
Reference in new issue
Block a user