- Updated Mini App design guidelines to include detailed instructions on UI changes, accessibility rules, and verification processes. - Refactored multiple components to utilize `MiniAppScreen` and `MiniAppScreenContent` for consistent layout structure across the application. - Improved error handling in `GlobalError` and `NotFound` components by integrating new layout components for better user experience. - Introduced new hooks for admin functionality, streamlining access checks and data loading processes. - Enhanced documentation to reflect changes in design policies and component usage, ensuring clarity for future development.
96 lines
2.7 KiB
TypeScript
96 lines
2.7 KiB
TypeScript
/**
|
|
* 6-week (42-cell) calendar grid starting from Monday. Composes CalendarDay cells.
|
|
* Ported from webapp/js/calendar.js renderCalendar.
|
|
*/
|
|
|
|
"use client";
|
|
|
|
import { useMemo } from "react";
|
|
import {
|
|
firstDayOfMonth,
|
|
getMonday,
|
|
localDateString,
|
|
} from "@/lib/date-utils";
|
|
import type { CalendarEvent, DutyWithUser } from "@/types";
|
|
import { dutiesByDate, calendarEventsByDate } from "@/lib/calendar-data";
|
|
import { cn } from "@/lib/utils";
|
|
import { CalendarDay } from "./CalendarDay";
|
|
import { useTranslation } from "@/i18n/use-translation";
|
|
|
|
export interface CalendarGridProps {
|
|
/** Currently displayed month. */
|
|
currentMonth: Date;
|
|
/** All duties for the visible range (will be grouped by date). */
|
|
duties: DutyWithUser[];
|
|
/** All calendar events for the visible range. */
|
|
calendarEvents: CalendarEvent[];
|
|
/** Called when a day cell is clicked (opens day detail). Receives date key and cell rect for popover. */
|
|
onDayClick: (dateKey: string, anchorRect: DOMRect) => void;
|
|
className?: string;
|
|
}
|
|
|
|
const CELLS = 42;
|
|
|
|
export function CalendarGrid({
|
|
currentMonth,
|
|
duties,
|
|
calendarEvents,
|
|
onDayClick,
|
|
className,
|
|
}: CalendarGridProps) {
|
|
const { t } = useTranslation();
|
|
const dutiesByDateMap = useMemo(
|
|
() => dutiesByDate(duties),
|
|
[duties]
|
|
);
|
|
const calendarEventsByDateMap = useMemo(
|
|
() => calendarEventsByDate(calendarEvents),
|
|
[calendarEvents]
|
|
);
|
|
const todayKey = localDateString(new Date());
|
|
|
|
const cells = useMemo(() => {
|
|
const first = firstDayOfMonth(currentMonth);
|
|
const start = getMonday(first);
|
|
const result: { date: Date; key: string; month: number }[] = [];
|
|
const d = new Date(start);
|
|
for (let i = 0; i < CELLS; i++) {
|
|
const key = localDateString(d);
|
|
result.push({ date: new Date(d), key, month: d.getMonth() });
|
|
d.setDate(d.getDate() + 1);
|
|
}
|
|
return result;
|
|
}, [currentMonth]);
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
"calendar-grid grid grid-cols-7 gap-1 mb-4 min-h-[var(--calendar-grid-min-height)]",
|
|
className
|
|
)}
|
|
role="grid"
|
|
aria-label={t("aria.calendar")}
|
|
>
|
|
{cells.map(({ date, key, month }, i) => {
|
|
const isOtherMonth = month !== currentMonth.getMonth();
|
|
const dayDuties = dutiesByDateMap[key] ?? [];
|
|
const eventSummaries = calendarEventsByDateMap[key] ?? [];
|
|
|
|
return (
|
|
<div key={`cell-${i}`} role="gridcell" className="min-h-0">
|
|
<CalendarDay
|
|
dateKey={key}
|
|
dayOfMonth={date.getDate()}
|
|
isToday={key === todayKey}
|
|
isOtherMonth={isOtherMonth}
|
|
duties={dayDuties}
|
|
eventSummaries={eventSummaries}
|
|
onDayClick={onDayClick}
|
|
/>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|