feat: complete notdienst planning, quick wins and notifications

This commit is contained in:
t.indorf
2026-10-12 15:34:09 +02:00
parent b915857951
commit 00f602a7aa
50 changed files with 2443 additions and 513 deletions
+14 -37
View File
@@ -26,7 +26,8 @@ import { de } from "date-fns/locale";
import { toast } from "sonner";
import { cn } from "@/lib/utils";
import { formatDateKey, getWorkingDaysOfMonth } from "@/lib/date-utils";
import { formatDateKey, formatUtcDateKey } from "@/lib/date-utils";
import { getNotdienstDays } from "@/lib/notdienst-days";
import { saveNotdienstAvailabilities } from "./actions";
export const notdienstEventCategories = {
@@ -100,23 +101,21 @@ export function NotdienstForm({
}: NotdienstFormProps) {
const router = useRouter();
// Lokaler State für die Auswahl. Ein Datum liegt als stabiler yyyy-MM-dd-Schlüssel vor.
const [selectedDates, setSelectedDates] = useState<Set<string>>(
const [selection, setSelectedDates] = useState<Set<string>>(
new Set(initialSelectedDates),
);
const [isPending, startTransition] = useTransition();
const closureDateKeys = useMemo(
() => getClosureDateKeys(calendarItems),
[calendarItems],
const selectableDateKeys = useMemo(
() => new Set(getNotdienstDays(targetYear, targetMonth,
calendarItems.filter((item) => item.category === "closure" || item.category === "team"),
).map(formatUtcDateKey)),
[calendarItems, targetMonth, targetYear],
);
const selectedDates = useMemo(
() => new Set(Array.from(selection).filter((key) => selectableDateKeys.has(key))),
[selection, selectableDateKeys],
);
const selectableDateKeys = useMemo(() => {
const workingDays = getWorkingDaysOfMonth(targetYear, targetMonth);
return new Set(
workingDays
.map(formatDateKey)
.filter((dateKey) => !closureDateKeys.has(dateKey)),
);
}, [closureDateKeys, targetMonth, targetYear]);
const monthStart = startOfMonth(new Date(targetYear, targetMonth - 1));
const selectedCount = selectedDates.size;
const hasMinimumRequirement = requiredDaysTotal > 0;
@@ -375,12 +374,9 @@ export function NotdienstCalendar({
const isInTargetMonth = isSameMonth(day, visibleMonthStart);
const isSelected = selected.has(dateKey);
const dayEvents = getCalendarItemsForDay(events, day);
const hasClosure = dayEvents.some(
(event) => event.category === "closure",
);
const isSelectable =
isInTargetMonth && selectableDateKeys.has(dateKey) && !hasClosure;
const isWeekend = day.getDay() === 0 || day.getDay() === 6;
const hasClosure = isInTargetMonth && !isWeekend && !selectableDateKeys.has(dateKey);
const isSelectable = isInTargetMonth && selectableDateKeys.has(dateKey);
return (
<CalendarTile
@@ -603,25 +599,6 @@ function getCalendarItemsForDay(
});
}
function getClosureDateKeys(items: NotdienstCalendarItem[]) {
const closureKeys = new Set<string>();
for (const item of items) {
if (item.category !== "closure") continue;
const days = eachDayOfInterval({
start: startOfDay(item.startDate),
end: startOfDay(item.endDate),
});
for (const day of days) {
closureKeys.add(formatDateKey(day));
}
}
return closureKeys;
}
function formatEventDate(event: NotdienstCalendarItem) {
const date = format(event.startDate, "EEEE, d. MMMM yyyy", { locale: de });
return event.allDay