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:
1 parent
f911dbcb03
commit
8d75f7346e
6 files changed
+98
-91
No files matched your search
@@ -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
@@ -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
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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}
|
||||
>
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user