Keep a note on the overview until somebody ticks it off
A note with a follow-up date is a task, and the overview is where tasks are looked for. Until now it lived only in the employee file, which is the one place you go when you already know who you are looking for. Follow-ups behave differently from everything else on that card, and the difference is the point: an entry on Monday is over on Tuesday, an unfinished task is not. So there is no lower bound on the date — what was due and never ticked off stays, marked overdue in red, sorted to the top because it is sorted by date. A task that drops out of the list by itself is a forgotten task. Only "Erledigt" removes it. A note without a follow-up date never appears: it is a record, not a task. Checked against the live database — an overdue one and an upcoming one appear, one without a date and one beyond the chosen period do not, and ticking the overdue one off removes exactly it. The probe notes were deleted again. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -38,7 +38,12 @@ const DOT_STYLES: Record<string, string> = {
|
||||
Gehaltsanpassung: "bg-warning-text",
|
||||
};
|
||||
|
||||
const KIND_LABEL = { hire: "Eintritt", exit: "Austritt", return: "Rückkehr aus Abwesenheit" } as const;
|
||||
const KIND_LABEL = {
|
||||
hire: "Eintritt",
|
||||
exit: "Austritt",
|
||||
return: "Rückkehr aus Abwesenheit",
|
||||
note: "Wiedervorlage",
|
||||
} as const;
|
||||
|
||||
export default async function DashboardPage({
|
||||
searchParams,
|
||||
@@ -83,6 +88,7 @@ export default async function DashboardPage({
|
||||
upcomingHires,
|
||||
upcomingExits,
|
||||
upcomingReturns,
|
||||
upcomingNotes,
|
||||
history,
|
||||
} = await withUser(userId, (tx) => loadDashboardData(tx, { userId, today, yearStart, yearEnd, bisIso, arten }));
|
||||
// "Aktiv" means status Aktiv — somebody on Karenz is employed but not
|
||||
@@ -115,26 +121,46 @@ export default async function DashboardPage({
|
||||
.sort((a, b) => b.count - a.count);
|
||||
const maxDivisionCount = Math.max(1, ...divisionBars.map((d) => d.count));
|
||||
|
||||
type UpcomingItem = { id: string; label: string; date: string; kind: keyof typeof KIND_LABEL };
|
||||
type UpcomingItem = {
|
||||
id: string;
|
||||
/** Ziel des Klicks — bei einer Wiedervorlage die Akte, nicht die Notiz. */
|
||||
employeeId: string;
|
||||
label: string;
|
||||
/** Zweite Zeile: bei einer Wiedervorlage der Notiztext statt der Art. */
|
||||
hinweis?: string;
|
||||
date: string;
|
||||
kind: keyof typeof KIND_LABEL;
|
||||
};
|
||||
const upcomingAlle: UpcomingItem[] = [
|
||||
...(upcomingHires).map((e) => ({
|
||||
id: e.id,
|
||||
employeeId: e.id,
|
||||
label: fmtName(e.first_name, e.last_name),
|
||||
date: e.entry_date,
|
||||
kind: "hire" as const,
|
||||
})),
|
||||
...(upcomingExits).map((e) => ({
|
||||
id: e.id,
|
||||
employeeId: e.id,
|
||||
label: fmtName(e.first_name, e.last_name),
|
||||
date: e.exit_date!,
|
||||
kind: "exit" as const,
|
||||
})),
|
||||
...(upcomingReturns).map((e) => ({
|
||||
id: e.id,
|
||||
employeeId: e.id,
|
||||
label: fmtName(e.first_name, e.last_name),
|
||||
date: e.karenz_return_date!,
|
||||
kind: "return" as const,
|
||||
})),
|
||||
...(upcomingNotes).map((n) => ({
|
||||
id: n.id,
|
||||
employeeId: n.employee_id!,
|
||||
label: fmtName(n.first_name, n.last_name),
|
||||
hinweis: n.note_text,
|
||||
date: n.due_date!,
|
||||
kind: "note" as const,
|
||||
})),
|
||||
].sort((a, b) => a.date.localeCompare(b.date));
|
||||
|
||||
// Die Karte bleibt eine Übersicht, keine Liste: acht Zeilen, und darunter
|
||||
@@ -233,20 +259,35 @@ export default async function DashboardPage({
|
||||
<AnstehendFilter zeitraum={zeitraum} arten={arten} />
|
||||
</Suspense>
|
||||
<ul className="flex flex-col divide-y divide-border-subtle">
|
||||
{upcoming.map((item) => (
|
||||
<li key={`${item.kind}-${item.id}`}>
|
||||
<Link
|
||||
href={`/employees/${item.id}`}
|
||||
className="-mx-2 flex items-center justify-between gap-2 rounded px-2 py-2.5 text-sm hover:bg-surface"
|
||||
>
|
||||
<span className="min-w-0">
|
||||
<span className="block truncate font-semibold text-ink">{item.label}</span>
|
||||
<span className="text-xs text-ink-muted">{KIND_LABEL[item.kind]}</span>
|
||||
</span>
|
||||
<span className="shrink-0 text-xs font-semibold tabular-nums text-ink-muted">{fmtDate(item.date)}</span>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
{upcoming.map((item) => {
|
||||
// Überfällig gibt es nur bei Wiedervorlagen: die anderen Arten
|
||||
// haben eine untere Grenze, eine offene Aufgabe nicht.
|
||||
const ueberfaellig = item.date < today;
|
||||
return (
|
||||
<li key={`${item.kind}-${item.id}`}>
|
||||
<Link
|
||||
href={`/employees/${item.employeeId}`}
|
||||
className="-mx-2 flex items-center justify-between gap-2 rounded px-2 py-2.5 text-sm hover:bg-surface"
|
||||
>
|
||||
<span className="min-w-0">
|
||||
<span className="block truncate font-semibold text-ink">{item.label}</span>
|
||||
<span className="block truncate text-xs text-ink-muted">
|
||||
{KIND_LABEL[item.kind]}
|
||||
{item.hinweis ? ` — ${item.hinweis}` : ""}
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
className={`shrink-0 text-xs font-semibold tabular-nums ${
|
||||
ueberfaellig ? "text-danger-text" : "text-ink-muted"
|
||||
}`}
|
||||
>
|
||||
{ueberfaellig ? "überfällig " : ""}
|
||||
{fmtDate(item.date)}
|
||||
</span>
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
{upcoming.length === 0 && (
|
||||
<p className="py-2 text-sm text-ink-muted">
|
||||
{istEingeschraenkt(zeitraum, arten)
|
||||
|
||||
Reference in New Issue
Block a user