Design: Rest aus P1 - Archivieren als roter Link, Planboard-Tokens und Scroll-Container, !important entfernt, Invalid-Ring im Fokus, Schublade nicht tabbar, Dialog-Fokus, Protokoll ohne Ueberlauf

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-06 06:02:54 +00:00
1 parent f911dbcb03
commit 8d75f7346e
6 files changed
+98 -91

No files matched your search

+3 -3
View File
@@ -42,9 +42,9 @@ export default function CustomerViewLog({ customerId }) {
<div className="list"> <div className="list">
{entries.map((e) => ( {entries.map((e) => (
<div key={e.id} className="list-row"> <div key={e.id} className="list-row">
<span className="mono muted" style={{ minWidth: 130 }}>{formatViewLogTime(e.at)}</span> <span className="mono muted list-row-text" style={{ minWidth: 130 }}>{formatViewLogTime(e.at)}</span>
<span style={{ minWidth: 120, fontWeight: 600 }}>{e.staffName || '–'}</span> <span className="list-row-text" style={{ minWidth: 120, fontWeight: 600 }}>{e.staffName || '–'}</span>
<div className="grow"> <div className="grow list-row-text">
<div>{viewLogLabel(e.action)}</div> <div>{viewLogLabel(e.action)}</div>
{e.detail && <div className="muted">{e.detail}</div>} {e.detail && <div className="muted">{e.detail}</div>}
</div> </div>
+13 -12
View File
@@ -277,29 +277,30 @@
gap: 6px; gap: 6px;
} }
.admin-status, /* Tabellenzellen: .admin-table td > span (0,1,2) setzt display/Farbe/Schrift; die Selektoren hier sind spezifischer (0,2,1) */
.admin-role { .admin-table td > .admin-status,
display: inline-flex !important; .admin-table td > .admin-role {
display: inline-flex;
width: fit-content; width: fit-content;
align-items: center; align-items: center;
padding: 3px 7px; padding: 3px 7px;
border: 1px solid var(--admin-line-strong); border: 1px solid var(--admin-line-strong);
border-radius: 4px; border-radius: 4px;
color: var(--text-muted) !important; color: var(--text-muted);
font-size: 11px !important; font-size: 11px;
font-weight: 700; font-weight: 700;
} }
.admin-status.active, .admin-table td > .admin-status.active,
.admin-role.admin { .admin-table td > .admin-role.admin {
border-color: rgba(16, 185, 129, 0.45); border-color: var(--green-fg);
color: #6ee7b7 !important; color: var(--green-fg);
} }
.admin-shortcode { .admin-table td > .admin-shortcode {
color: var(--text) !important; color: var(--text);
font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 12px !important; font-size: 12px;
letter-spacing: 0.04em; letter-spacing: 0.04em;
} }
+70 -67
View File
@@ -11,10 +11,11 @@ import { groupAppointmentsByDay, countInRange, dayKey } from '../lib/planboard'
/* Termine haengen an den Tickets (startDate/startTime/endTime/assignedTo); /* Termine haengen an den Tickets (startDate/startTime/endTime/assignedTo);
die Aufbereitung steckt in src/lib/planboard.js. */ die Aufbereitung steckt in src/lib/planboard.js. */
// Farben nur ueber Tokens (P3 baut die Seite um)
const TYPE_COLORS = { const TYPE_COLORS = {
akquise: { bg: 'rgba(198,113,57,0.16)', fg: '#c67139' }, akquise: { bg: 'var(--appt-akquise)', fg: 'var(--orange-fg)' },
webpage: { bg: 'rgba(122,138,94,0.18)', fg: '#7a8a5e' }, webpage: { bg: 'var(--bg-hover)', fg: 'var(--text-2)' },
project: { bg: 'rgba(122,138,94,0.18)', fg: '#7a8a5e' }, project: { bg: 'var(--bg-hover)', fg: 'var(--text-2)' },
} }
export default function PlanboardPage() { export default function PlanboardPage() {
@@ -47,72 +48,74 @@ export default function PlanboardPage() {
} }
/> />
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: 12 }}> <div className="planboard-scroll">
{weekDays.map((day) => { <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: 12 }}>
const entries = byDay[dayKey(day)] || [] {weekDays.map((day) => {
const today = isSameDay(day, new Date()) const entries = byDay[dayKey(day)] || []
return ( const today = isSameDay(day, new Date())
<Card key={day.toISOString()} pad={false}> return (
<div style={{ <Card key={day.toISOString()} pad={false}>
padding: '10px 12px', <div style={{
borderBottom: '1px solid var(--border)', padding: '10px 12px',
display: 'flex', borderBottom: '1px solid var(--border)',
flexDirection: 'column', display: 'flex',
background: today ? 'rgba(198,113,57,0.10)' : undefined, flexDirection: 'column',
}}> background: today ? 'var(--appt-akquise)' : undefined,
<strong style={{ fontSize: 13 }}>{format(day, 'EEEE', { locale: de })}</strong> }}>
<span className="faint" style={{ fontSize: 12 }}>{format(day, 'dd.MM')}</span> <strong style={{ fontSize: 13 }}>{format(day, 'EEEE', { locale: de })}</strong>
</div> <span className="muted" style={{ fontSize: 12 }}>{format(day, 'dd.MM')}</span>
</div>
<div style={{ padding: 8, minHeight: 120, display: 'flex', flexDirection: 'column', gap: 6 }}> <div style={{ padding: 8, minHeight: 120, display: 'flex', flexDirection: 'column', gap: 6 }}>
{entries.length === 0 && ( {entries.length === 0 && (
<p className="faint" style={{ fontSize: 12, padding: '4px 4px 0' }}>Keine Termine</p> <p className="faint" style={{ fontSize: 12, padding: '4px 4px 0' }}>Keine Termine</p>
)} )}
{entries.map((entry) => { {entries.map((entry) => {
const color = TYPE_COLORS[String(entry.type).toLowerCase()] const color = TYPE_COLORS[String(entry.type).toLowerCase()]
|| { bg: 'rgba(127,127,127,0.10)', fg: 'var(--border)' } || { bg: 'var(--bg-subtle)', fg: 'var(--line-strong)' }
return ( return (
<button <button
key={entry.id} key={entry.id}
type="button" type="button"
title={`Ticket #${entry.woid} öffnen`} title={`Ticket #${entry.woid} öffnen`}
onClick={() => navigate(`/tickets?woid=${encodeURIComponent(entry.woid)}`)} onClick={() => navigate(`/tickets?woid=${encodeURIComponent(entry.woid)}`)}
style={{ style={{
textAlign: 'left', textAlign: 'left',
border: '1px solid var(--border)', border: '1px solid var(--border)',
borderLeft: `3px solid ${color.fg}`, borderLeft: `3px solid ${color.fg}`,
borderRadius: 8, borderRadius: 8,
background: color.bg, background: color.bg,
color: 'inherit', color: 'inherit',
padding: '8px 10px', padding: '8px 10px',
cursor: 'pointer', cursor: 'pointer',
font: 'inherit', font: 'inherit',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
gap: 2, gap: 2,
}} }}
> >
<span style={{ fontSize: 12, fontWeight: 700 }}> <span style={{ fontSize: 12, fontWeight: 700 }}>
{entry.time ? `${entry.time}${entry.endTime ? `–${entry.endTime}` : ''}` : 'ohne Uhrzeit'} {entry.time ? `${entry.time}${entry.endTime ? `–${entry.endTime}` : ''}` : 'ohne Uhrzeit'}
</span> </span>
<span style={{ fontSize: 12.5, fontWeight: 600, lineHeight: 1.25 }}> <span style={{ fontSize: 12.5, fontWeight: 600, lineHeight: 1.25 }}>
{entry.customerName || entry.topic} {entry.customerName || entry.topic}
</span> </span>
{entry.customerName && ( {entry.customerName && (
<span className="faint" style={{ fontSize: 11.5, lineHeight: 1.25 }}>{entry.topic}</span> <span className="muted" style={{ fontSize: 11.5, lineHeight: 1.25 }}>{entry.topic}</span>
)} )}
<span className="faint" style={{ fontSize: 11 }}> <span className="muted" style={{ fontSize: 11 }}>
#{entry.woid} #{entry.woid}
{entry.employeeName ? ` · ${entry.employeeShort || entry.employeeName}` : ' · niemand zugewiesen'} {entry.employeeName ? ` · ${entry.employeeShort || entry.employeeName}` : ' · niemand zugewiesen'}
</span> </span>
</button> </button>
) )
})} })}
</div> </div>
</Card> </Card>
) )
})} })}
</div>
</div> </div>
</div> </div>
) )
+1 -2
View File
@@ -743,8 +743,7 @@ export default function ProjectsPage() {
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: '8px', marginTop: '16px', flexWrap: 'wrap' }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: '8px', marginTop: '16px', flexWrap: 'wrap' }}>
<button <button
type="button" type="button"
className="btn" className={editProject.archived ? 'btn' : 'link-danger'}
style={{ background: editProject.archived ? 'var(--accent)' : 'var(--danger)', color: '#fff' }}
onClick={() => handleArchiveToggle(editProject)} onClick={() => handleArchiveToggle(editProject)}
disabled={archiveBusy || editLoading} disabled={archiveBusy || editLoading}
> >
+2 -2
View File
@@ -42,7 +42,7 @@
white-space: pre-wrap; overflow-wrap: anywhere; user-select: text; white-space: pre-wrap; overflow-wrap: anywhere; user-select: text;
} }
@media (max-width: 720px) { @media (max-width: 720px) {
.area-row { grid-template-columns: minmax(0, 1fr) !important; } .area-rows .area-row { grid-template-columns: minmax(0, 1fr); }
.area-row.head { display: none; } .area-row.head { display: none; }
.funktionen-leer { display: none; } .funktionen-leer { display: none; }
.area-kv { grid-template-columns: minmax(0, 1fr); } .area-kv { grid-template-columns: minmax(0, 1fr); }
@@ -66,7 +66,7 @@
/* Nach verfuegbarer Breite stapeln (nicht nach Fensterbreite): mit ausgeklappter Sidebar sind Tablet quer oder ein /* Nach verfuegbarer Breite stapeln (nicht nach Fensterbreite): mit ausgeklappter Sidebar sind Tablet quer oder ein
halbes Laptop-Fenster sonst zu schmal fuer die Spalten */ halbes Laptop-Fenster sonst zu schmal fuer die Spalten */
@container (max-width: 960px) { @container (max-width: 960px) {
.area-row { grid-template-columns: minmax(0, 1fr) !important; gap: 4px; } .area-rows .area-row { grid-template-columns: minmax(0, 1fr); gap: 4px; }
.area-row.head { display: none; } .area-row.head { display: none; }
.funktionen-leer { display: none; } .funktionen-leer { display: none; }
} }
+9 -5
View File
@@ -362,6 +362,7 @@ h2.section .link { font-size: 14px; font-weight: 500; }
.list-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 12px 0; border-bottom: 1px solid var(--line); } .list-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 12px 0; border-bottom: 1px solid var(--line); }
.list-row:first-child { border-top: 1px solid var(--line); } .list-row:first-child { border-top: 1px solid var(--line); }
.list-row-text { min-width: 0; overflow-wrap: anywhere; }
a.list-row:hover .cell-title { color: var(--accent-ink); } a.list-row:hover .cell-title { color: var(--accent-ink); }
/* ---------- Dialog ---------- */ /* ---------- Dialog ---------- */
@@ -388,6 +389,7 @@ a.list-row:hover .cell-title { color: var(--accent-ink); }
flex-direction: column; flex-direction: column;
animation: pop 0.14s ease-out; animation: pop 0.14s ease-out;
} }
.dialog:focus { outline: none; }
.dialog-wide { max-width: 760px; } .dialog-wide { max-width: 760px; }
.dialog-head { display: flex; align-items: center; justify-content: space-between; padding: 20px 24px 4px; } .dialog-head { display: flex; align-items: center; justify-content: space-between; padding: 20px 24px 4px; }
.dialog-head h3 { font-size: 18px; line-height: 26px; font-weight: 600; } .dialog-head h3 { font-size: 18px; line-height: 26px; font-weight: 600; }
@@ -448,8 +450,8 @@ a.list-row:hover .cell-title { color: var(--accent-ink); }
} }
@media (max-width: 860px) { @media (max-width: 860px) {
.sidebar { transform: translateX(-100%); transition: transform 0.18s ease-out; width: min(300px, 86vw); box-shadow: none; } .sidebar { transform: translateX(-100%); visibility: hidden; transition: transform 0.18s ease-out, visibility 0s 0.18s; width: min(300px, 86vw); box-shadow: none; }
.shell.menu-open .sidebar { transform: none; box-shadow: var(--shadow-pop); } .shell.menu-open .sidebar { transform: none; visibility: visible; transition-delay: 0s; box-shadow: var(--shadow-pop); }
.shell.menu-open .scrim { position: fixed; inset: 0; background: var(--scrim); z-index: 15; } .shell.menu-open .scrim { position: fixed; inset: 0; background: var(--scrim); z-index: 15; }
.main { margin-left: 0; } .main { margin-left: 0; }
.mobile-bar { .mobile-bar {
@@ -593,6 +595,8 @@ a.list-row:hover .cell-title { color: var(--accent-ink); }
.wrap { flex-wrap: wrap; } .wrap { flex-wrap: wrap; }
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; } .mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; }
.text-ellipsis { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .text-ellipsis { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Planboard (bis P3): die 7-Spalten-Woche scrollt waagerecht im Container, nicht die Seite */
.planboard-scroll { overflow-x: auto; }
.automatic-assignment-note { .automatic-assignment-note {
padding: 12px 14px; padding: 12px 14px;
@@ -900,11 +904,10 @@ a.list-row:hover .cell-title { color: var(--accent-ink); }
.fin-tx-action { grid-area: action; align-self: start; } .fin-tx-action { grid-area: action; align-self: start; }
} }
/* Dialoge: Rechnung schreiben, Buchung zuordnen (auf dem dunklen Overlay) */ /* Dialoge: Rechnung schreiben, Buchung zuordnen. Overlay, Box und Schliessknopf kommen aus KOMPAT (.overlay:has(> .overlay-content), */
.fin-close { background: none; border: 0; padding: 0; line-height: 1; font-size: 32px; top: 16px; right: 24px; z-index: 1; } /* .overlay-content, .overlay-close); hier nur, was dort fehlt. */
.fin-close:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; border-radius: 4px; } .fin-close:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; border-radius: 4px; }
.fin-close:disabled { opacity: 0.4; cursor: not-allowed; } .fin-close:disabled { opacity: 0.4; cursor: not-allowed; }
.fin-overlay { background: rgba(6, 9, 14, 0.98); }
.fin-dialog { max-width: 760px; } .fin-dialog { max-width: 760px; }
.fin-dialog:focus { outline: none; } .fin-dialog:focus { outline: none; }
.fin-dialog-error { .fin-dialog-error {
@@ -1046,6 +1049,7 @@ a.list-row:hover .cell-title { color: var(--accent-ink); }
} }
.form-control::placeholder { color: var(--text-3); } .form-control::placeholder { color: var(--text-3); }
.form-control:focus { box-shadow: 0 0 0 1px var(--accent), 0 0 0 4px var(--accent-ring); } .form-control:focus { box-shadow: 0 0 0 1px var(--accent), 0 0 0 4px var(--accent-ring); }
.form-control[aria-invalid="true"]:focus, .form-control.is-invalid:focus { box-shadow: 0 0 0 1px var(--danger), 0 0 0 4px var(--danger-ring); }
.form-control[readonly] { background: var(--bg-subtle); } .form-control[readonly] { background: var(--bg-subtle); }
textarea.form-control { height: auto; min-height: 96px; padding: 8px 10px; resize: vertical; line-height: 20px; } textarea.form-control { height: auto; min-height: 96px; padding: 8px 10px; resize: vertical; line-height: 20px; }
select.form-control { select.form-control {