From ed8bdba015360316df512b919ba011eaa066e474 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 12:03:47 +0000 Subject: [PATCH] Tickets: Abschlussfixes P2 - Termin ohne Umbruch in der Zeitspanne, Art in der Liste, Laden statt Leerzustand, Unterstuetzung entsperrt bei Fehler, Aufraeumen Co-Authored-By: Claude Sonnet 5.5 --- src/components/OwnSupportModal.jsx | 17 +++++++++++------ src/components/WorksheetTimesModal.jsx | 1 - src/lib/ticketRoutes.js | 2 +- src/lib/worksheetFlow.js | 2 +- src/pages/TicketPage.jsx | 16 ++++++++++++++-- src/pages/TicketsPage.jsx | 17 ++++++++--------- src/styles/global.css | 5 ++--- tests/unit/ticketRoutes.test.mjs | 1 + 8 files changed, 38 insertions(+), 23 deletions(-) diff --git a/src/components/OwnSupportModal.jsx b/src/components/OwnSupportModal.jsx index b55230e..8c10bdd 100644 --- a/src/components/OwnSupportModal.jsx +++ b/src/components/OwnSupportModal.jsx @@ -23,13 +23,18 @@ export default function OwnSupportModal({ worksheet, currentUserId, onClose, onS const save = async (value) => { setSaving(true) setError('') - const result = await onSave(worksheet, value) - if (result?.success) { - onClose() - return + try { + const result = await onSave(worksheet, value) + if (result?.success) { + onClose() + return + } + setError(result?.error || 'Speichern fehlgeschlagen') + } catch (err) { + setError(err.message || 'Speichern fehlgeschlagen') + } finally { + setSaving(false) } - setError(result?.error || 'Speichern fehlgeschlagen') - setSaving(false) } const handleSubmit = (e) => { diff --git a/src/components/WorksheetTimesModal.jsx b/src/components/WorksheetTimesModal.jsx index 0f7251f..a6a4415 100644 --- a/src/components/WorksheetTimesModal.jsx +++ b/src/components/WorksheetTimesModal.jsx @@ -90,7 +90,6 @@ export default function WorksheetTimesModal({ worksheet, currentUserId, onClose, setSaving(false) } - // Kurzer Titel: auf dem Handy laeuft ein langer unter das Schliessen-X return ( -

Fehler: {error || 'Das Ticket konnte nicht geladen werden.'}

+

Fehler: {error}

{back}
) @@ -63,6 +63,18 @@ export default function TicketPage() { return } +// Datum und Uhrzeitspanne bleiben je in einer Zeile: umbrechen darf nur das Komma dazwischen. +// Rohtext ohne ", " (unlesbare Altwerte) bricht weiter frei um. +function AppointmentText({ text }) { + const at = text.indexOf(', ') + if (at === -1) return text + return ( + <> + {text.slice(0, at)}, {text.slice(at + 2)} + + ) +} + function TicketView({ ticket, updateWorkorder, tab }) { const location = useLocation() const navigate = useNavigate() @@ -204,7 +216,7 @@ function TicketView({ ticket, updateWorkorder, tab }) {
Termin
-
{appointment || Kein Termin}
+
{appointment ? : Kein Termin}
Fällig
diff --git a/src/pages/TicketsPage.jsx b/src/pages/TicketsPage.jsx index 531b5c7..60bd8f8 100644 --- a/src/pages/TicketsPage.jsx +++ b/src/pages/TicketsPage.jsx @@ -19,7 +19,7 @@ import { } from '../lib/ticketListUrl' import { isRowActivation } from '../lib/rowClick' import { ticketPath, ticketRef } from '../lib/ticketRoutes' -import { TYPE_FILTERS } from '../lib/ticketStatus' +import { TYPE_FILTERS, ticketTypeLabel } from '../lib/ticketStatus' import { TIME_MAX_ROWS, minutesCellText } from '../lib/ticketTimes' import { appointmentText, assigneeLabel, ticketNumber } from '../lib/ticketView' @@ -91,12 +91,6 @@ function TicketList() { const { employees } = useEmployees() const { byWoid, capped, version } = useTicketMinutes(workorders) - // Der Ladehinweis ohne Zeilen gilt nur dem allerersten Laden; danach bleiben die alten Zeilen stehen - const [loaded, setLoaded] = useState(false) - useEffect(() => { - if (!loading) setLoaded(true) - }, [loading]) - // Mit welchem Limit wurden die gezeigten Zeilen geladen? Nach "Mehr laden" ist state.limit schon größer, // die Zeilen sind es erst mit der Antwort: der Knopf bleibt bis dahin stehen (und ist gesperrt). // Im Ruhezustand heißt das: Zeilen >= state.limit und state.limit unter der Obergrenze von nextLimit. @@ -122,6 +116,9 @@ function TicketList() { return { id: tab.id, label: tab.label, count } }) + // Die Art steht in der Zeile, solange der Filter keine einzelne Art festlegt + const showType = state.typeId === 'orders' || state.typeId === 'all' + // byWoid ist eine an Ort und Stelle ergänzte Map: erst `version` zeigt eine Änderung an const rows = useMemo( () => @@ -147,6 +144,7 @@ function TicketList() {
{ticket.customerName || 'Unbekannt'} {ticket.customerLocation ? ` · ${ticket.customerLocation}` : ''} + {showType && ticket.type ? ` · ${ticketTypeLabel(ticket.type)}` : ''}
@@ -157,11 +155,12 @@ function TicketList() { ) }), - [workorders, employees, byWoid, version, listSearch, navigate] + [workorders, employees, byWoid, version, listSearch, navigate, showType] ) let content - if (!loaded && loading) { + // Ladezustand, solange keine Zeilen da sind (erstes Laden und Filterwechsel auf eine leere Liste); mit Zeilen bleiben die alten stehen + if (loading && workorders.length === 0) { content =
Wird geladen …
} else if (error) { content =

Fehler: {error}

diff --git a/src/styles/global.css b/src/styles/global.css index 54294bc..734708c 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -967,7 +967,6 @@ a.list-row:hover .cell-title { color: var(--accent-ink); } /* oberhalb durch die Reihenfolge, nie durch eine Erzwingung. */ /* ---------- Arbeitsblaetter ---------- */ -.ws-list { margin-bottom: 8px; } .ws { padding: 16px 0; border-bottom: 1px solid var(--line); display: grid; grid-template-columns: 148px minmax(0, 1fr) auto; gap: 4px 24px; } .ws:first-of-type { border-top: 1px solid var(--line); } .ws-when { color: var(--text-2); font-size: 13px; line-height: 20px; } @@ -976,11 +975,9 @@ a.list-row:hover .cell-title { color: var(--accent-ink); } .ws-head .who { font-weight: 600; overflow-wrap: anywhere; } .ws-text { color: var(--text); margin-top: 4px; max-width: 640px; white-space: pre-wrap; overflow-wrap: anywhere; } .ws-support { margin-top: 8px; font-size: 13px; color: var(--text-2); overflow-wrap: anywhere; } -.ws-support b { color: var(--text); font-weight: 500; } .ws-acts { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 8px; font-size: 13px; } .ws-time { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; } .ws-time .t { font-weight: 600; } -.ws-time .r { color: var(--text-2); font-size: 13px; } .ws.comment .ws-text { color: var(--text-2); } button.link, .abo-toggle { background: none; border: 0; padding: 0; font: inherit; cursor: pointer; } button.link { font-weight: 500; } /* font: inherit setzt sonst das Gewicht von .link zurueck */ @@ -996,6 +993,8 @@ button.link { font-weight: 500; } /* font: inherit setzt sonst das Gewicht von . /* Auf der markierten Zeile (--bg-subtle) reicht das helle Grau von .id und .faint nicht fuer den Kontrast */ .table tr.row:hover .id, .table tr.row:hover .faint { color: var(--text-2); } .list-more { display: flex; justify-content: center; padding: 8px 0 24px; } +/* Der lange Platzhalter wird am Handy mit "…" gekuerzt statt hart abgeschnitten */ +.toolbar .search { text-overflow: ellipsis; } @media (min-width: 861px) { .toolbar .search { width: 400px; } } diff --git a/tests/unit/ticketRoutes.test.mjs b/tests/unit/ticketRoutes.test.mjs index bfa709b..8cec430 100644 --- a/tests/unit/ticketRoutes.test.mjs +++ b/tests/unit/ticketRoutes.test.mjs @@ -24,6 +24,7 @@ test('ticketPath: Sonderzeichen kodiert, leeres ref ergibt /tickets', () => { test('ticketRef: woid vor $id, nie die Kurznummer', () => { assert.equal(ticketRef({ woid: '100131', $id: 'abc' }), '100131') assert.equal(ticketRef({ woid: '', $id: 'abcdefghij' }), 'abcdefghij') + assert.equal(ticketRef({ woid: ' ', $id: 'abcdefghij' }), 'abcdefghij') assert.equal(ticketRef(null), '') })