feat(calendar): month-view calendar bound to calendar_events

Replaces the template's static-data calendar with a multi-tenant calendar
backed by Appwrite calendar_events.

Schema/validation:
- lib/validation/calendar.ts (calendarEventSchema with cross-field check
  end >= start)
- lib/appwrite/calendar-actions.ts: createCalendarEventAction,
  updateCalendarEventAction, deleteCalendarEventAction. Date inputs
  (HTML datetime-local 'YYYY-MM-DDTHH:mm', date 'YYYY-MM-DD') are
  normalized to ISO 8601 before write.
- lib/appwrite/calendar-queries.ts: listCalendarEvents with optional
  start/end range queries.

UI:
- /calendar server page: pulls events + customers, hands to CalendarClient.
- CalendarClient: month grid (6 rows × 7 cols), Monday-first, today badge,
  prev/next/Bugün nav. Multi-day events show on every day in their range.
  Each day cell shows up to 3 event chips with start time prefix; '+N
  daha' for overflow. Hover reveals a + button to add an event on that day.
- EventFormSheet: title, all-day switch (toggles input type between
  date and datetime-local), start/end with validation, customer FK,
  color preset (blue/green/amber/red/violet/slate). Sentinel '__none__'
  for nullable Selects. When editing, footer shows a destructive 'Sil'
  ghost button on the left that triggers the parent's confirm dialog.

Color tokens centralized in COLOR_BG map; falls back to primary tint.

Removed all template calendar files (calendars.tsx, calendar-main, etc.)
since the data model didn't match.
This commit is contained in:
kovakmedya
2026-04-30 06:01:42 +03:00
parent 671195fb7d
commit b4c1073d91
22 changed files with 885 additions and 1925 deletions
+26
View File
@@ -0,0 +1,26 @@
import { z } from "zod";
export const calendarEventSchema = z
.object({
title: z.string().trim().min(1, "Başlık zorunlu.").max(255),
description: z
.string()
.trim()
.max(2000)
.optional()
.transform((v) => (v ? v : undefined)),
start: z.string().min(1, "Başlangıç zorunlu."),
end: z.string().min(1, "Bitiş zorunlu."),
allDay: z
.union([z.boolean(), z.literal("on"), z.literal(""), z.undefined()])
.transform((v) => v === true || v === "on")
.optional(),
customerId: z.string().optional().transform((v) => (v ? v : undefined)),
color: z.string().optional().transform((v) => (v ? v : undefined)),
})
.refine((d) => new Date(d.end).getTime() >= new Date(d.start).getTime(), {
message: "Bitiş tarihi başlangıçtan önce olamaz.",
path: ["end"],
});
export type CalendarEventInput = z.infer<typeof calendarEventSchema>;