feat: Rechnungen, Dokumente und Preview im Ticket buendeln
- InvoiceNinja-Rechnungen pro Kunde/Ticket anzeigen, verknuepfen und aus Ticket heraus erstellen (integrationsApi, useInvoices, InvoicePanel) - Paperless-ngx: Ticket-Uploads automatisch synchronisieren und Kunden-/ Ticket-Dokumente durchsuchen/anzeigen (DocumentsPanel, FileUploadModal) - Projekt-/Preview-Karte mit Status, Provisioning und letztem Commit - Einheitliches Ticket-Detail mit Tabs (Details/Arbeitsblaetter/Rechnungen/ Dokumente/Projekt) + zugehoerige Styles Aufruf des Integrations-Backends same-origin per Appwrite-JWT. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
8abf11ad18
commit
8f6927fbb0
8 files changed
+862
-186
No files matched your search
@@ -1,13 +1,40 @@
|
||||
import { FaTrash, FaCodeBranch } from 'react-icons/fa'
|
||||
import { FaGlobe, FaCodeCommit, FaClock } from 'react-icons/fa6'
|
||||
import ProjectReadme from './ProjectReadme'
|
||||
import GiteaLinkButton from './GiteaLinkButton'
|
||||
import PreviewLinkButton from './PreviewLinkButton'
|
||||
|
||||
const STATUS_COLORS = {
|
||||
ready: '#10b981',
|
||||
live: '#10b981',
|
||||
online: '#10b981',
|
||||
provisioning: '#f59e0b',
|
||||
pending: '#f59e0b',
|
||||
building: '#3b82f6',
|
||||
error: '#ef4444',
|
||||
failed: '#ef4444',
|
||||
}
|
||||
|
||||
function StatusBadge({ label, value }) {
|
||||
if (!value) return null
|
||||
const color = STATUS_COLORS[String(value).toLowerCase()] || '#a0aec0'
|
||||
return (
|
||||
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 12, color: '#cbd5e0', 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 }) {
|
||||
const lastPush = project.lastPushAt || project.giteaSyncedAt
|
||||
return (
|
||||
<div style={{ background: 'rgba(30,41,59,0.5)', border: '1px solid rgba(59,130,246,0.25)', borderRadius: 10, padding: '12px 14px', marginBottom: 12 }}>
|
||||
<div style={{ display: 'flex', gap: 8, alignItems: 'flex-start', justifyContent: 'space-between' }}>
|
||||
<div>
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<strong>{project.projectName}</strong>
|
||||
{project.subdomain && <span className="text-grey" style={{ marginLeft: 8, fontSize: 13 }}>({project.subdomain})</span>}
|
||||
{project.repoFullName && (
|
||||
@@ -15,6 +42,14 @@ export default function AssignedProjectCard({ project, onUnassign, showActions =
|
||||
<FaCodeBranch style={{ marginRight: 4 }} />{project.repoFullName}
|
||||
</div>
|
||||
)}
|
||||
{project.liveDomain && (
|
||||
<div style={{ fontSize: 12, marginTop: 4 }}>
|
||||
<FaGlobe style={{ marginRight: 4, color: '#34d399' }} />
|
||||
<a href={`https://${project.liveDomain}`} target="_blank" rel="noopener noreferrer" style={{ color: '#34d399', textDecoration: 'none' }}>
|
||||
{project.liveDomain}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 8, alignItems: 'center', flexShrink: 0 }}>
|
||||
<PreviewLinkButton href={project.previewUrl} />
|
||||
@@ -24,6 +59,38 @@ export default function AssignedProjectCard({ project, onUnassign, showActions =
|
||||
)}
|
||||
</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.hasPreview && <StatusBadge label="Preview" value="aktiv" />}
|
||||
</div>
|
||||
|
||||
{/* Letzter Commit */}
|
||||
{(project.lastCommitMessage || project.lastCommitSha) && (
|
||||
<div style={{ marginTop: 8, fontSize: 12, color: '#94a3b8', background: 'rgba(0,0,0,0.2)', borderRadius: 8, padding: '8px 10px' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<FaCodeCommit style={{ color: '#3b82f6' }} />
|
||||
{project.lastCommitSha && <code style={{ color: '#cbd5e0' }}>{String(project.lastCommitSha).slice(0, 7)}</code>}
|
||||
{project.lastCommitBranch || project.lastPushBranch ? (
|
||||
<span className="text-grey">({project.lastPushBranch || project.lastCommitBranch})</span>
|
||||
) : null}
|
||||
</div>
|
||||
{project.lastCommitMessage && (
|
||||
<div style={{ marginTop: 4, color: '#cbd5e0', whiteSpace: 'pre-wrap' }}>{project.lastCommitMessage}</div>
|
||||
)}
|
||||
<div style={{ marginTop: 4, display: 'flex', gap: 12, flexWrap: 'wrap' }}>
|
||||
{project.lastCommitAuthor && <span>von {project.lastCommitAuthor}</span>}
|
||||
{lastPush && (
|
||||
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 4 }}>
|
||||
<FaClock /> {new Date(lastPush).toLocaleString('de-DE')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{project.repoFullName && <ProjectReadme repoFullName={project.repoFullName} />}
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user