Projektdetails und Fortschritt kundenverständlich

Der Server bereitet Commits, Tickets und Arbeitseinträge für Kunden auf
(server/services/customerProjectView.js), die Rohdaten bleiben intern:
- keine Co-Authored-By-/Signed-off-by-Zeilen und KI-Hinweise, keine
  Commit-Kürzel, Kontonamen, Git-Links oder Branch
- technische Commits (Preview-Config, Merges) ausgeblendet, bekannte
  Automatik-Texte übersetzt, "fix"/"upload" -> "Website aktualisiert"
- Ticketstatus und Arbeitsarten auf Deutsch (Open -> Eingegangen,
  Remote -> Online erledigt, ...), Fortschritt in 4 Schritten
- Akquise-Tickets auch in den Projektdetails verborgen
- /api/projects ohne Gitea-URL, Repo- und Vorlagennamen

Darstellung: Auftragskarte mit Status und Fortschrittsleiste, Zeitleiste
"Letzte Änderungen" nach Tagen, Arbeiten mit Farbe/Symbol je Art und
Arbeitszeit; der Tab "Fortschritt" nutzt dieselben Bausteine. In der
App-Ansicht öffnen sich die Projektdetails als eigene Seite mit Zurück
zu "Projekte".

Tests: npm test (node --test, 22 Fälle).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-09-26 18:46:22 +00:00
1 parent e54f97144c
commit ba9916cfe6
9 files changed
+932 -138

No files matched your search

+128 -27
View File
@@ -235,15 +235,118 @@
background: rgba(157, 61, 254, 0.14); border: 1px solid rgba(157, 61, 254, 0.4); cursor: pointer;
}
.kb-chip:hover { background: rgba(157, 61, 254, 0.24); }
.kb-details { margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--kb-border); }
.kb-details h3 { margin: 16px 0 8px; font-size: 14px; letter-spacing: 0.02em; }
.kb-details h3:first-child { margin-top: 0; }
.commit-item { padding: 7px 0; border-bottom: 1px solid rgba(255, 255, 255, 0.06); font-size: 14px; }
.commit-item:last-child { border-bottom: none; }
.commit-meta { font-size: 12px; color: var(--kb-faint); }
.commit-body { margin: 2px 0 0 8px; font-size: 13px; color: var(--kb-muted); white-space: pre-wrap; }
.kb-details-chev {
width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round;
transform: rotate(90deg); transition: transform 0.2s;
}
.kb-details-btn[aria-expanded='true'] .kb-details-chev { transform: rotate(-90deg); }
.kb-details { display: grid; gap: 24px; margin-top: 18px; padding-top: 20px; border-top: 1px solid var(--kb-border); }
/* ------------------------------------------ Tabellen, Pills, Aktivitaet --- */
/* --------------------------------------------------------- Projektdetails --- */
/* Auftrag mit Fortschrittsleiste, Zeitleiste der Aenderungen, Arbeiten mit Symbol je Art.
Dieselben Bausteine nutzt der Tab "Fortschritt" (app.js: renderOrder, renderWorkEntries). */
.pd-tone-new { --pd-c: #d8b4fe; --pd-bg: rgba(157, 61, 254, 0.1); --pd-line: rgba(157, 61, 254, 0.4); --pd-bar: #b77bff; }
.pd-tone-active { --pd-c: #e9d5ff; --pd-bg: rgba(157, 61, 254, 0.2); --pd-line: rgba(157, 61, 254, 0.58); --pd-bar: var(--kb-purple); }
.pd-tone-waiting { --pd-c: #fcd34d; --pd-bg: rgba(251, 191, 36, 0.12); --pd-line: rgba(251, 191, 36, 0.45); --pd-bar: #f5b82e; }
.pd-tone-review { --pd-c: #93c5fd; --pd-bg: rgba(59, 130, 246, 0.14); --pd-line: rgba(96, 165, 250, 0.45); --pd-bar: #4f8ff7; }
.pd-tone-done { --pd-c: #86efac; --pd-bg: rgba(74, 222, 128, 0.09); --pd-line: rgba(74, 222, 128, 0.38); --pd-bar: #34c46a; }
.pd-tone-paused, .pd-tone-stopped { --pd-c: var(--kb-muted); --pd-bg: rgba(255, 255, 255, 0.05); --pd-line: var(--kb-border-strong); --pd-bar: rgba(255, 255, 255, 0.3); }
.pd-order {
padding: 18px 20px; border-radius: 16px; border: 1px solid var(--pd-line);
background: linear-gradient(135deg, var(--pd-bg), rgba(255, 255, 255, 0.015) 72%), rgba(12, 12, 14, 0.6);
}
.pd-order-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 10px 14px; flex-wrap: wrap; }
.pd-order-name { min-width: 0; }
.pd-order-no { display: block; margin-bottom: 3px; font-size: 12px; font-weight: 600; letter-spacing: 0.04em; color: var(--kb-muted); }
.pd-order-name h3 { margin: 0; font-size: 18px; line-height: 1.3; overflow-wrap: anywhere; }
.pd-status {
flex: none; display: inline-flex; align-items: center; gap: 7px; padding: 4px 11px; border-radius: 999px;
font-size: 12.5px; font-weight: 700; color: var(--pd-c); background: var(--pd-bg); border: 1px solid var(--pd-line);
}
.pd-status::before { content: ''; flex: none; width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
.pd-tone-active .pd-status::before, .pd-tone-waiting .pd-status::before, .pd-tone-review .pd-status::before { animation: pd-pulse 1.8s ease-in-out infinite; }
@keyframes pd-pulse { 50% { opacity: 0.3; } }
.pd-steps { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 6px; margin: 18px 0 0; padding: 0; list-style: none; }
.pd-steps li { position: relative; min-width: 0; padding-top: 13px; font-size: 12px; color: var(--kb-faint); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pd-steps li::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 6px; border-radius: 3px; background: rgba(255, 255, 255, 0.1); }
.pd-steps li.is-done { color: var(--kb-muted); }
.pd-steps li.is-done::before, .pd-steps li.is-current::before { background: var(--pd-bar); }
.pd-steps li.is-current { color: #fff; font-weight: 700; }
.pd-order-hint { margin: 14px 0 0; font-size: 13.5px; color: var(--pd-c); }
.pd-section { min-width: 0; }
.pd-title {
display: flex; align-items: center; gap: 9px; margin: 0 0 14px; font-size: 12px; font-weight: 700;
letter-spacing: 0.12em; text-transform: uppercase; color: var(--kb-muted);
}
.pd-ico { flex: none; width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }
.pd-title .pd-ico { color: var(--kb-lilac); }
.pd-empty { margin: 0; font-size: 14px; color: var(--kb-muted); }
.pd-note { margin: 0 0 8px; font-size: 13px; color: var(--kb-muted); }
/* Zeitleiste "Letzte Änderungen": Tage als Ueberschrift, jede Aenderung ein Punkt auf der Linie */
.pd-timeline { position: relative; padding-left: 22px; }
.pd-timeline::before {
content: ''; position: absolute; left: 5px; top: 8px; bottom: 8px; width: 2px; border-radius: 1px;
background: linear-gradient(var(--kb-purple-line), rgba(157, 61, 254, 0.06));
}
.pd-day + .pd-day { margin-top: 16px; }
.pd-day-label { margin: 0 0 8px; font-size: 12px; font-weight: 700; letter-spacing: 0.02em; color: var(--kb-lilac); }
.pd-changes { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; }
.pd-change { position: relative; }
.pd-change::before {
content: ''; position: absolute; left: -21px; top: 6px; width: 8px; height: 8px; border-radius: 50%;
background: #0c0c0e; border: 2px solid var(--kb-purple);
}
.pd-day:first-child .pd-change:first-child::before { background: var(--kb-purple); box-shadow: 0 0 0 4px rgba(157, 61, 254, 0.22); }
.pd-change-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.pd-change-head strong { font-size: 14.5px; line-height: 1.4; overflow-wrap: anywhere; }
.pd-change-head time { flex: none; font-size: 12px; color: var(--kb-faint); font-variant-numeric: tabular-nums; }
.pd-change-details { margin: 4px 0 0; padding: 0 0 0 17px; font-size: 13px; line-height: 1.5; color: var(--kb-muted); }
.pd-change-details li + li { margin-top: 3px; }
.pd-change-details li::marker { color: var(--kb-faint); }
/* Lange Listen gekuerzt; "Weitere … anzeigen" klappt auf */
.pd-collapsible:not(.is-open) [data-extra] { display: none; }
.pd-more {
margin-top: 14px; padding: 8px 15px; border-radius: 999px; border: 1px solid var(--kb-border-strong); background: transparent;
color: #d4b4ff; font: inherit; font-size: 13px; font-weight: 700; cursor: pointer;
}
.pd-more:hover { background: rgba(157, 61, 254, 0.12); }
/* Arbeiten: Farbe und Symbol je Art (Website-Update, online, vor Ort, Buero) */
.pd-kind-update { --pd-k: #d8b4fe; --pd-kbg: rgba(157, 61, 254, 0.18); }
.pd-kind-online { --pd-k: #93c5fd; --pd-kbg: rgba(59, 130, 246, 0.17); }
.pd-kind-onsite { --pd-k: #5eead4; --pd-kbg: rgba(45, 212, 191, 0.15); }
.pd-kind-office { --pd-k: #fcd34d; --pd-kbg: rgba(251, 191, 36, 0.15); }
.pd-kind-work { --pd-k: rgba(255, 255, 255, 0.78); --pd-kbg: rgba(255, 255, 255, 0.08); }
.pd-work { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
.pd-entry {
display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 12px; align-items: start;
padding: 12px 14px; border-radius: 14px; background: rgba(255, 255, 255, 0.025); border: 1px solid var(--kb-border);
}
.pd-entry-ico { width: 36px; height: 36px; border-radius: 10px; display: grid; place-items: center; color: var(--pd-k); background: var(--pd-kbg); }
.pd-entry-ico .pd-ico { width: 19px; height: 19px; }
.pd-entry-main { min-width: 0; }
.pd-entry-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 8px; min-height: 22px; }
.pd-entry-head strong { font-size: 14.5px; color: var(--pd-k); }
.pd-entry-meta { font-size: 12.5px; color: var(--kb-faint); }
.pd-entry-text { margin: 4px 0 0; font-size: 13.5px; line-height: 1.5; color: rgba(255, 255, 255, 0.82); white-space: pre-wrap; overflow-wrap: anywhere; }
.pd-entry-changes { margin: 4px 0 0; padding-left: 17px; font-size: 13.5px; line-height: 1.5; color: rgba(255, 255, 255, 0.82); }
.pd-entry-changes li::marker { color: var(--pd-k); }
.pd-time {
display: inline-flex; align-items: center; gap: 5px; padding: 3px 9px; border-radius: 999px; white-space: nowrap;
font-size: 12px; font-weight: 700; background: rgba(255, 255, 255, 0.07); border: 1px solid var(--kb-border-strong);
}
.pd-time-ico { width: 13px; height: 13px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; opacity: 0.75; }
/* Tab "Fortschritt": je Auftrag eine Karte in der Farbe seines Status */
#activity-body:has(.pd-order-card) { display: grid; gap: 16px; padding: 0; background: none; border: none; }
.pd-order-card > .pd-collapsible, .pd-order-card > .pd-empty { margin-top: 18px; }
.pd-order-card .pd-entry { background: rgba(0, 0, 0, 0.28); }
/* ------------------------------------------------- Tabellen und Pills --- */
.simple-table { width: 100%; border-collapse: collapse; font-size: 14px; }
.simple-table th, .simple-table td { padding: 10px 10px; text-align: left; border-bottom: 1px solid var(--kb-border); }
.simple-table th { font-size: 10.5px; font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase; color: var(--kb-faint); }
@@ -256,13 +359,7 @@
.pill-warn { color: #d8b4fe; background: rgba(157, 61, 254, 0.16); border-color: rgba(157, 61, 254, 0.5); }
.pill-red { color: #fca5a5; background: rgba(248, 113, 113, 0.12); border-color: rgba(248, 113, 113, 0.45); }
.pill-muted { color: var(--kb-muted); background: rgba(255, 255, 255, 0.06); border-color: var(--kb-border-strong); }
.pill-git { color: #fdba74; background: rgba(251, 146, 60, 0.12); border-color: rgba(251, 146, 60, 0.45); }
.pill-amber { color: #fcd34d; background: rgba(251, 191, 36, 0.12); border-color: rgba(251, 191, 36, 0.45); }
.activity-item { padding: 14px 0; border-bottom: 1px solid var(--kb-border); }
.activity-item:first-child { padding-top: 0; }
.activity-item:last-child { border-bottom: none; padding-bottom: 0; }
.activity-status { display: inline-block; margin-left: 6px; padding: 1px 9px; border-radius: 999px; font-size: 12px; background: rgba(255, 255, 255, 0.06); border: 1px solid var(--kb-border-strong); }
.activity-ws { margin: 4px 0 4px 14px; font-size: 13px; color: rgba(255, 255, 255, 0.78); white-space: pre-wrap; }
/* ------------------------------------------------------------------- Chat --- */
.chat-box { display: flex; flex-direction: column; height: 520px; }
@@ -554,18 +651,22 @@ button.kb-avatar:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
.kb-project-top h2 { font-size: 19px; }
.kb-open { right: 10px; bottom: 10px; padding: 7px 12px; font-size: 12.5px; }
.kb-chip { padding: 7px 12px; font-size: 13px; }
.kb-project [data-details-btn] { width: 100%; justify-content: center; padding: 12px 16px; font-size: 14px; }
/* Projektdetails: der Knopf oeffnet eine eigene Seite (Pfeil nach rechts wie eine Listenzeile) */
.kb-project [data-details-btn] { width: 100%; justify-content: space-between; padding: 14px 16px 14px 18px; font-size: 15px; }
.kb-project .kb-details-chev { transform: none; width: 18px; height: 18px; opacity: 0.7; }
.kb-details .simple-table { font-size: 13px; }
/* Fortschritt als Zeitleiste */
#activity-body .activity-item { position: relative; padding: 0 0 18px 22px; border-bottom: none; }
#activity-body .activity-item::before {
content: ''; position: absolute; left: 0; top: 7px; width: 9px; height: 9px; border-radius: 50%;
background: var(--kb-purple); box-shadow: 0 0 0 3px rgba(157, 61, 254, 0.22);
}
#activity-body .activity-item::after { content: ''; position: absolute; left: 4px; top: 22px; bottom: 2px; width: 1px; background: var(--kb-border-strong); }
#activity-body .activity-item:last-child { padding-bottom: 0; }
#activity-body .activity-item:last-child::after { display: none; }
.kb-details.pd-page { margin: 0; padding: 0; border-top: none; gap: 26px; }
.pd-order { padding: 16px; border-radius: 18px; }
.pd-order-name h3 { font-size: 17px; }
.pd-steps { gap: 5px; }
.pd-steps li { font-size: 11.5px; letter-spacing: -0.01em; }
.pd-change-head strong { font-size: 15px; }
.pd-change-details { font-size: 13.5px; }
.pd-entry { grid-template-columns: auto minmax(0, 1fr); gap: 4px 12px; padding: 12px; border-radius: 16px; }
.pd-entry .pd-time { grid-column: 2; justify-self: start; margin-top: 6px; }
.pd-entry-text, .pd-entry-changes { font-size: 14px; }
.pd-more { width: 100%; padding: 12px 14px; font-size: 14px; }
.pd-order-hint .kb-link { display: inline-block; padding: 6px 0; }
/* Rechnungen: jede Zeile als Karte */
#invoices-body:has(.kb-invoices) { padding: 0; background: none; border: none; }
@@ -689,6 +790,6 @@ button.kb-avatar:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
}
@media (prefers-reduced-motion: reduce) {
.kb-shell.dashboard-content-reveal, .kb-view.active, .kb-sheet[open] .kb-sheet-panel, #loading:not(.hidden) { animation: none !important; }
.kb-header, .kb-nav, .kb-logo, .kb-app-title, .kb-sheet-panel { transition: none !important; }
.kb-shell.dashboard-content-reveal, .kb-view.active, .kb-sheet[open] .kb-sheet-panel, #loading:not(.hidden), .pd-status::before { animation: none !important; }
.kb-header, .kb-nav, .kb-logo, .kb-app-title, .kb-sheet-panel, .kb-details-chev { transition: none !important; }
}