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">
{entries.map((e) => (
<div key={e.id} className="list-row">
<span className="mono muted" style={{ minWidth: 130 }}>{formatViewLogTime(e.at)}</span>
<span style={{ minWidth: 120, fontWeight: 600 }}>{e.staffName || '–'}</span>
<div className="grow">
<span className="mono muted list-row-text" style={{ minWidth: 130 }}>{formatViewLogTime(e.at)}</span>
<span className="list-row-text" style={{ minWidth: 120, fontWeight: 600 }}>{e.staffName || '–'}</span>
<div className="grow list-row-text">
<div>{viewLogLabel(e.action)}</div>
{e.detail && <div className="muted">{e.detail}</div>}
</div>
+13 -12
View File
@@ -277,29 +277,30 @@
gap: 6px;
}
.admin-status,
.admin-role {
display: inline-flex !important;
/* Tabellenzellen: .admin-table td > span (0,1,2) setzt display/Farbe/Schrift; die Selektoren hier sind spezifischer (0,2,1) */
.admin-table td > .admin-status,
.admin-table td > .admin-role {
display: inline-flex;
width: fit-content;
align-items: center;
padding: 3px 7px;
border: 1px solid var(--admin-line-strong);
border-radius: 4px;
color: var(--text-muted) !important;
font-size: 11px !important;
color: var(--text-muted);
font-size: 11px;
font-weight: 700;
}
.admin-status.active,
.admin-role.admin {
border-color: rgba(16, 185, 129, 0.45);
color: #6ee7b7 !important;
.admin-table td > .admin-status.active,
.admin-table td > .admin-role.admin {
border-color: var(--green-fg);
color: var(--green-fg);
}
.admin-shortcode {
color: var(--text) !important;
.admin-table td > .admin-shortcode {
color: var(--text);
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 12px !important;
font-size: 12px;
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);
die Aufbereitung steckt in src/lib/planboard.js. */
// Farben nur ueber Tokens (P3 baut die Seite um)
const TYPE_COLORS = {
akquise: { bg: 'rgba(198,113,57,0.16)', fg: '#c67139' },
webpage: { bg: 'rgba(122,138,94,0.18)', fg: '#7a8a5e' },
project: { bg: 'rgba(122,138,94,0.18)', fg: '#7a8a5e' },
akquise: { bg: 'var(--appt-akquise)', fg: 'var(--orange-fg)' },
webpage: { bg: 'var(--bg-hover)', fg: 'var(--text-2)' },
project: { bg: 'var(--bg-hover)', fg: 'var(--text-2)' },
}
export default function PlanboardPage() {
@@ -47,72 +48,74 @@ export default function PlanboardPage() {
}
/>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: 12 }}>
{weekDays.map((day) => {
const entries = byDay[dayKey(day)] || []
const today = isSameDay(day, new Date())
return (
<Card key={day.toISOString()} pad={false}>
<div style={{
padding: '10px 12px',
borderBottom: '1px solid var(--border)',
display: 'flex',
flexDirection: 'column',
background: today ? 'rgba(198,113,57,0.10)' : undefined,
}}>
<strong style={{ fontSize: 13 }}>{format(day, 'EEEE', { locale: de })}</strong>
<span className="faint" style={{ fontSize: 12 }}>{format(day, 'dd.MM')}</span>
</div>
<div className="planboard-scroll">
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: 12 }}>
{weekDays.map((day) => {
const entries = byDay[dayKey(day)] || []
const today = isSameDay(day, new Date())
return (
<Card key={day.toISOString()} pad={false}>
<div style={{
padding: '10px 12px',
borderBottom: '1px solid var(--border)',
display: 'flex',
flexDirection: 'column',
background: today ? 'var(--appt-akquise)' : undefined,
}}>
<strong style={{ fontSize: 13 }}>{format(day, 'EEEE', { locale: de })}</strong>
<span className="muted" style={{ fontSize: 12 }}>{format(day, 'dd.MM')}</span>
</div>
<div style={{ padding: 8, minHeight: 120, display: 'flex', flexDirection: 'column', gap: 6 }}>
{entries.length === 0 && (
<p className="faint" style={{ fontSize: 12, padding: '4px 4px 0' }}>Keine Termine</p>
)}
<div style={{ padding: 8, minHeight: 120, display: 'flex', flexDirection: 'column', gap: 6 }}>
{entries.length === 0 && (
<p className="faint" style={{ fontSize: 12, padding: '4px 4px 0' }}>Keine Termine</p>
)}
{entries.map((entry) => {
const color = TYPE_COLORS[String(entry.type).toLowerCase()]
|| { bg: 'rgba(127,127,127,0.10)', fg: 'var(--border)' }
return (
<button
key={entry.id}
type="button"
title={`Ticket #${entry.woid} öffnen`}
onClick={() => navigate(`/tickets?woid=${encodeURIComponent(entry.woid)}`)}
style={{
textAlign: 'left',
border: '1px solid var(--border)',
borderLeft: `3px solid ${color.fg}`,
borderRadius: 8,
background: color.bg,
color: 'inherit',
padding: '8px 10px',
cursor: 'pointer',
font: 'inherit',
display: 'flex',
flexDirection: 'column',
gap: 2,
}}
>
<span style={{ fontSize: 12, fontWeight: 700 }}>
{entry.time ? `${entry.time}${entry.endTime ? `–${entry.endTime}` : ''}` : 'ohne Uhrzeit'}
</span>
<span style={{ fontSize: 12.5, fontWeight: 600, lineHeight: 1.25 }}>
{entry.customerName || entry.topic}
</span>
{entry.customerName && (
<span className="faint" style={{ fontSize: 11.5, lineHeight: 1.25 }}>{entry.topic}</span>
)}
<span className="faint" style={{ fontSize: 11 }}>
#{entry.woid}
{entry.employeeName ? ` · ${entry.employeeShort || entry.employeeName}` : ' · niemand zugewiesen'}
</span>
</button>
)
})}
</div>
</Card>
)
})}
{entries.map((entry) => {
const color = TYPE_COLORS[String(entry.type).toLowerCase()]
|| { bg: 'var(--bg-subtle)', fg: 'var(--line-strong)' }
return (
<button
key={entry.id}
type="button"
title={`Ticket #${entry.woid} öffnen`}
onClick={() => navigate(`/tickets?woid=${encodeURIComponent(entry.woid)}`)}
style={{
textAlign: 'left',
border: '1px solid var(--border)',
borderLeft: `3px solid ${color.fg}`,
borderRadius: 8,
background: color.bg,
color: 'inherit',
padding: '8px 10px',
cursor: 'pointer',
font: 'inherit',
display: 'flex',
flexDirection: 'column',
gap: 2,
}}
>
<span style={{ fontSize: 12, fontWeight: 700 }}>
{entry.time ? `${entry.time}${entry.endTime ? `–${entry.endTime}` : ''}` : 'ohne Uhrzeit'}
</span>
<span style={{ fontSize: 12.5, fontWeight: 600, lineHeight: 1.25 }}>
{entry.customerName || entry.topic}
</span>
{entry.customerName && (
<span className="muted" style={{ fontSize: 11.5, lineHeight: 1.25 }}>{entry.topic}</span>
)}
<span className="muted" style={{ fontSize: 11 }}>
#{entry.woid}
{entry.employeeName ? ` · ${entry.employeeShort || entry.employeeName}` : ' · niemand zugewiesen'}
</span>
</button>
)
})}
</div>
</Card>
)
})}
</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' }}>
<button
type="button"
className="btn"
style={{ background: editProject.archived ? 'var(--accent)' : 'var(--danger)', color: '#fff' }}
className={editProject.archived ? 'btn' : 'link-danger'}
onClick={() => handleArchiveToggle(editProject)}
disabled={archiveBusy || editLoading}
>
+2 -2
View File
@@ -42,7 +42,7 @@
white-space: pre-wrap; overflow-wrap: anywhere; user-select: text;
}
@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; }
.funktionen-leer { display: none; }
.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
halbes Laptop-Fenster sonst zu schmal fuer die Spalten */
@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; }
.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: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); }
/* ---------- Dialog ---------- */
@@ -388,6 +389,7 @@ a.list-row:hover .cell-title { color: var(--accent-ink); }
flex-direction: column;
animation: pop 0.14s ease-out;
}
.dialog:focus { outline: none; }
.dialog-wide { max-width: 760px; }
.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; }
@@ -448,8 +450,8 @@ a.list-row:hover .cell-title { color: var(--accent-ink); }
}
@media (max-width: 860px) {
.sidebar { transform: translateX(-100%); transition: transform 0.18s ease-out; width: min(300px, 86vw); box-shadow: none; }
.shell.menu-open .sidebar { transform: none; box-shadow: var(--shadow-pop); }
.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; visibility: visible; transition-delay: 0s; box-shadow: var(--shadow-pop); }
.shell.menu-open .scrim { position: fixed; inset: 0; background: var(--scrim); z-index: 15; }
.main { margin-left: 0; }
.mobile-bar {
@@ -593,6 +595,8 @@ a.list-row:hover .cell-title { color: var(--accent-ink); }
.wrap { flex-wrap: wrap; }
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; }
.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 {
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; }
}
/* Dialoge: Rechnung schreiben, Buchung zuordnen (auf dem dunklen Overlay) */
.fin-close { background: none; border: 0; padding: 0; line-height: 1; font-size: 32px; top: 16px; right: 24px; z-index: 1; }
/* Dialoge: Rechnung schreiben, Buchung zuordnen. Overlay, Box und Schliessknopf kommen aus KOMPAT (.overlay:has(> .overlay-content), */
/* .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:disabled { opacity: 0.4; cursor: not-allowed; }
.fin-overlay { background: rgba(6, 9, 14, 0.98); }
.fin-dialog { max-width: 760px; }
.fin-dialog:focus { outline: none; }
.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: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); }
textarea.form-control { height: auto; min-height: 96px; padding: 8px 10px; resize: vertical; line-height: 20px; }
select.form-control {