feat: default to Month view and implement household services filter toggle for parents
This commit is contained in:
@@ -61,7 +61,8 @@ const categories = [
|
||||
{ label: "Elternabend", type: TerminType.ELTERNABEND, color: "oklch(0.50 0.10 280)" },
|
||||
{ label: "Fest", type: TerminType.KITA_FEST, color: "var(--good)" },
|
||||
{ label: "Geburtstag", type: TerminType.GEBURTSTAG_INTERN, color: "oklch(0.50 0.10 320)" },
|
||||
{ label: "Elterndienst", type: "elterndienst", color: "oklch(0.60 0.15 160)" },
|
||||
{ label: "Mein Elterndienst", type: "mein-elterndienst", color: "oklch(0.60 0.16 160)" },
|
||||
{ label: "Elterndienst", type: "elterndienst", color: "oklch(0.75 0.08 160)" },
|
||||
{ label: "Mein Notdienst", type: "mein-notdienst", color: "oklch(0.60 0.18 45)" },
|
||||
{ label: "Notdienst", type: "notdienst", color: "oklch(0.75 0.10 50)" },
|
||||
{ label: "Sonstiges", type: TerminType.SONSTIGES, color: "var(--ink-muted)" },
|
||||
@@ -76,9 +77,10 @@ export function CalendarOverviewClient({
|
||||
userId: string;
|
||||
isAdmin: boolean;
|
||||
}) {
|
||||
const [view, setView] = useState<"liste" | "monat" | "agenda">("liste");
|
||||
const [view, setView] = useState<"liste" | "monat" | "agenda">("monat");
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [activeTypes, setActiveTypes] = useState<Set<string>>(new Set());
|
||||
const [filterScope, setFilterScope] = useState<"own" | "all">(isAdmin ? "all" : "own");
|
||||
|
||||
// Month navigation state
|
||||
const [currentMonthDate, setCurrentMonthDate] = useState(new Date());
|
||||
@@ -106,6 +108,12 @@ export function CalendarOverviewClient({
|
||||
// Filter logic
|
||||
const filteredEvents = useMemo(() => {
|
||||
return termine.filter((item) => {
|
||||
// 0. Filter by scope (own vs all)
|
||||
if (filterScope === "own") {
|
||||
if (item.kind === "duty" && !item.isOwn) return false;
|
||||
if (item.kind === "notdienst" && !item.isOwn) return false;
|
||||
}
|
||||
|
||||
// 1. Search Query filter
|
||||
const query = searchQuery.trim().toLowerCase();
|
||||
if (query) {
|
||||
@@ -129,7 +137,9 @@ export function CalendarOverviewClient({
|
||||
if (activeTypes.size > 0) {
|
||||
const itemType =
|
||||
item.kind === "duty"
|
||||
? "elterndienst"
|
||||
? item.isOwn
|
||||
? "mein-elterndienst"
|
||||
: "elterndienst"
|
||||
: item.kind === "notdienst"
|
||||
? item.isOwn
|
||||
? "mein-notdienst"
|
||||
@@ -140,7 +150,7 @@ export function CalendarOverviewClient({
|
||||
|
||||
return true;
|
||||
});
|
||||
}, [termine, searchQuery, activeTypes]);
|
||||
}, [termine, searchQuery, activeTypes, filterScope]);
|
||||
|
||||
// Check if an event falls on a specific day
|
||||
const isEventOnDay = (item: CalendarListItemDto, day: Date) => {
|
||||
@@ -177,23 +187,54 @@ export function CalendarOverviewClient({
|
||||
<div className="space-y-5">
|
||||
{/* Search and Filters Header */}
|
||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||
<div className="inline-flex overflow-hidden rounded-full border border-[var(--hairline)] bg-[var(--surface)]">
|
||||
{(["liste", "monat", "agenda"] as const).map((v) => (
|
||||
<button
|
||||
key={v}
|
||||
type="button"
|
||||
onClick={() => setView(v)}
|
||||
className={[
|
||||
"h-9 px-4 text-[13px] font-semibold transition",
|
||||
v !== "liste" ? "border-l border-[var(--hairline-soft)]" : "",
|
||||
view === v
|
||||
? "bg-[var(--accent-soft)] text-[var(--accent-deep)]"
|
||||
: "text-[var(--ink-soft)] hover:bg-[var(--surface-2)] hover:text-[var(--ink)]",
|
||||
].join(" ")}
|
||||
>
|
||||
{v.charAt(0).toUpperCase() + v.slice(1)}
|
||||
</button>
|
||||
))}
|
||||
<div className="flex items-center flex-wrap gap-3">
|
||||
<div className="inline-flex overflow-hidden rounded-full border border-[var(--hairline)] bg-[var(--surface)]">
|
||||
{(["liste", "monat", "agenda"] as const).map((v) => (
|
||||
<button
|
||||
key={v}
|
||||
type="button"
|
||||
onClick={() => setView(v)}
|
||||
className={[
|
||||
"h-9 px-4 text-[13px] font-semibold transition",
|
||||
v !== "liste" ? "border-l border-[var(--hairline-soft)]" : "",
|
||||
view === v
|
||||
? "bg-[var(--accent-soft)] text-[var(--accent-deep)]"
|
||||
: "text-[var(--ink-soft)] hover:bg-[var(--surface-2)] hover:text-[var(--ink)]",
|
||||
].join(" ")}
|
||||
>
|
||||
{v.charAt(0).toUpperCase() + v.slice(1)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{!isAdmin && (
|
||||
<div className="inline-flex overflow-hidden rounded-full border border-[var(--hairline)] bg-[var(--surface)] p-0.5">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setFilterScope("own")}
|
||||
className={[
|
||||
"h-8 px-4 rounded-full text-[12px] font-semibold transition-all duration-200",
|
||||
filterScope === "own"
|
||||
? "bg-[var(--accent-deep)] text-[var(--surface)] shadow-sm"
|
||||
: "text-[var(--ink-soft)] hover:text-[var(--ink)] hover:bg-[var(--surface-2)]",
|
||||
].join(" ")}
|
||||
>
|
||||
Meine Dienste
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setFilterScope("all")}
|
||||
className={[
|
||||
"h-8 px-4 rounded-full text-[12px] font-semibold transition-all duration-200",
|
||||
filterScope === "all"
|
||||
? "bg-[var(--accent-deep)] text-[var(--surface)] shadow-sm"
|
||||
: "text-[var(--ink-soft)] hover:text-[var(--ink)] hover:bg-[var(--surface-2)]",
|
||||
].join(" ")}
|
||||
>
|
||||
Alle Dienste
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-1 flex-wrap items-center justify-end gap-2">
|
||||
@@ -291,16 +332,40 @@ export function CalendarOverviewClient({
|
||||
>
|
||||
{item.kind === "duty" ? (
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-[var(--good-soft)] text-[var(--good)]">
|
||||
<WashingMachine className="h-5 w-5 [stroke-width:1.7]" />
|
||||
<div
|
||||
className="absolute inset-y-3 left-0 w-[4px] rounded-r-full"
|
||||
style={{
|
||||
backgroundColor: item.isOwn
|
||||
? "oklch(0.60 0.16 160)"
|
||||
: "oklch(0.75 0.08 160)",
|
||||
}}
|
||||
/>
|
||||
<div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-[var(--surface-2)] text-[var(--ink-soft)] ml-2">
|
||||
<WashingMachine className="h-5 w-5 [stroke-width:1.7]" style={{ color: item.isOwn ? "oklch(0.60 0.16 160)" : "oklch(0.75 0.08 160)" }} />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<h4 className="font-semibold text-[15px] text-[var(--ink)] truncate">
|
||||
{item.title}
|
||||
</h4>
|
||||
<Badge variant="secondary" className="bg-[oklch(0.95_0.02_160)] text-[oklch(0.40_0.10_160)]">
|
||||
Elterndienst
|
||||
<Badge
|
||||
variant="secondary"
|
||||
style={{
|
||||
color: item.isOwn
|
||||
? "oklch(0.60 0.16 160)"
|
||||
: "oklch(0.75 0.08 160)",
|
||||
borderColor:
|
||||
(item.isOwn
|
||||
? "oklch(0.60 0.16 160)"
|
||||
: "oklch(0.75 0.08 160)") + "44",
|
||||
backgroundColor:
|
||||
(item.isOwn
|
||||
? "oklch(0.60 0.16 160)"
|
||||
: "oklch(0.75 0.08 160)") + "11",
|
||||
}}
|
||||
className="border text-[10px]"
|
||||
>
|
||||
{item.isOwn ? "Mein Elterndienst" : "Elterndienst"}
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="mt-1 text-sm text-[var(--ink-soft)]">
|
||||
@@ -604,7 +669,9 @@ function MonthView({
|
||||
{dayEvents.slice(0, 3).map((item) => {
|
||||
const color =
|
||||
item.kind === "duty"
|
||||
? "oklch(0.60 0.15 160)"
|
||||
? item.isOwn
|
||||
? "oklch(0.60 0.16 160)"
|
||||
: "oklch(0.75 0.08 160)"
|
||||
: item.kind === "notdienst"
|
||||
? item.isOwn
|
||||
? "oklch(0.60 0.18 45)"
|
||||
@@ -709,6 +776,10 @@ function AgendaView({
|
||||
? item.isOwn
|
||||
? "oklch(0.60 0.18 45)"
|
||||
: "oklch(0.75 0.10 50)"
|
||||
: item.kind === "duty"
|
||||
? item.isOwn
|
||||
? "oklch(0.60 0.16 160)"
|
||||
: "oklch(0.75 0.08 160)"
|
||||
: "oklch(0.60 0.15 160)";
|
||||
|
||||
return (
|
||||
|
||||
@@ -63,6 +63,7 @@ export type DutyCalendarItemDto = {
|
||||
status: DutyAssignmentStatus;
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
isOwn: boolean;
|
||||
};
|
||||
|
||||
export type NotdienstCalendarItemDto = {
|
||||
@@ -150,7 +151,7 @@ export function TerminList({
|
||||
}
|
||||
|
||||
function DutyRow({ duty, isAdmin }: { duty: DutyCalendarItemDto; isAdmin: boolean }) {
|
||||
const color = "var(--good)";
|
||||
const color = duty.isOwn ? "oklch(0.60 0.16 160)" : "oklch(0.75 0.08 160)";
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
|
||||
return (
|
||||
@@ -170,7 +171,9 @@ function DutyRow({ duty, isAdmin }: { duty: DutyCalendarItemDto; isAdmin: boolea
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 relative">
|
||||
<CategoryPill color={color}>Elterndienst</CategoryPill>
|
||||
<CategoryPill color={color}>
|
||||
{duty.isOwn ? "Mein Elterndienst" : "Elterndienst"}
|
||||
</CategoryPill>
|
||||
{isAdmin ? (
|
||||
<div className="relative">
|
||||
<Button
|
||||
|
||||
@@ -134,6 +134,7 @@ export default async function KalenderPage({
|
||||
startDate: true,
|
||||
endDate: true,
|
||||
status: true,
|
||||
familyId: true,
|
||||
dutyType: {
|
||||
select: {
|
||||
name: true,
|
||||
@@ -195,15 +196,21 @@ export default async function KalenderPage({
|
||||
participation.status === EventParticipationStatus.ATTENDING,
|
||||
),
|
||||
})),
|
||||
...dutyAssignmentRows.map((assignment) => ({
|
||||
kind: "duty" as const,
|
||||
id: assignment.id,
|
||||
title: assignment.dutyType.name,
|
||||
familyName: assignment.family.name,
|
||||
status: assignment.status,
|
||||
startDate: assignment.startDate,
|
||||
endDate: assignment.endDate,
|
||||
})),
|
||||
...dutyAssignmentRows.map((assignment) => {
|
||||
const isOwn = assignment.familyId === familyIdForParticipation;
|
||||
return {
|
||||
kind: "duty" as const,
|
||||
id: assignment.id,
|
||||
title: isOwn
|
||||
? `Mein Elterndienst (${assignment.dutyType.name})`
|
||||
: `${assignment.dutyType.name} (${assignment.family.name})`,
|
||||
familyName: assignment.family.name,
|
||||
status: assignment.status,
|
||||
startDate: assignment.startDate,
|
||||
endDate: assignment.endDate,
|
||||
isOwn,
|
||||
};
|
||||
}),
|
||||
...notdienstAssignmentRows.map((assignment) => {
|
||||
const isOwn = assignment.child.familyId === familyIdForParticipation;
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user