From b4c1073d911a081e88883e501cd90fd8e3448fb1 Mon Sep 17 00:00:00 2001 From: kovakmedya Date: Thu, 30 Apr 2026 06:01:42 +0300 Subject: [PATCH] feat(calendar): month-view calendar bound to calendar_events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../calendar/components/calendar-client.tsx | 259 ++++++++++++ .../calendar/components/calendar-main.tsx | 347 ---------------- .../calendar/components/calendar-sidebar.tsx | 78 ---- .../calendar/components/calendar-unified.tsx | 381 ------------------ .../calendar/components/calendar.tsx | 77 ---- .../calendar/components/calendars.tsx | 203 ---------- .../calendar/components/date-picker.tsx | 48 --- .../calendar/components/event-form-sheet.tsx | 274 +++++++++++++ .../calendar/components/event-form.tsx | 339 ---------------- .../calendar/components/quick-actions.tsx | 152 ------- .../(dashboard)/calendar/components/types.ts | 32 ++ src/app/(dashboard)/calendar/data.ts | 55 --- .../(dashboard)/calendar/data/calendars.json | 37 -- .../calendar/data/event-dates.json | 30 -- src/app/(dashboard)/calendar/data/events.json | 62 --- src/app/(dashboard)/calendar/page.tsx | 56 ++- src/app/(dashboard)/calendar/types.ts | 20 - src/app/(dashboard)/calendar/use-calendar.ts | 90 ----- src/lib/appwrite/calendar-actions.ts | 209 ++++++++++ src/lib/appwrite/calendar-queries.ts | 28 ++ src/lib/appwrite/calendar-types.ts | 7 + src/lib/validation/calendar.ts | 26 ++ 22 files changed, 885 insertions(+), 1925 deletions(-) create mode 100644 src/app/(dashboard)/calendar/components/calendar-client.tsx delete mode 100644 src/app/(dashboard)/calendar/components/calendar-main.tsx delete mode 100644 src/app/(dashboard)/calendar/components/calendar-sidebar.tsx delete mode 100644 src/app/(dashboard)/calendar/components/calendar-unified.tsx delete mode 100644 src/app/(dashboard)/calendar/components/calendar.tsx delete mode 100644 src/app/(dashboard)/calendar/components/calendars.tsx delete mode 100644 src/app/(dashboard)/calendar/components/date-picker.tsx create mode 100644 src/app/(dashboard)/calendar/components/event-form-sheet.tsx delete mode 100644 src/app/(dashboard)/calendar/components/event-form.tsx delete mode 100644 src/app/(dashboard)/calendar/components/quick-actions.tsx create mode 100644 src/app/(dashboard)/calendar/components/types.ts delete mode 100644 src/app/(dashboard)/calendar/data.ts delete mode 100644 src/app/(dashboard)/calendar/data/calendars.json delete mode 100644 src/app/(dashboard)/calendar/data/event-dates.json delete mode 100644 src/app/(dashboard)/calendar/data/events.json delete mode 100644 src/app/(dashboard)/calendar/types.ts delete mode 100644 src/app/(dashboard)/calendar/use-calendar.ts create mode 100644 src/lib/appwrite/calendar-actions.ts create mode 100644 src/lib/appwrite/calendar-queries.ts create mode 100644 src/lib/appwrite/calendar-types.ts create mode 100644 src/lib/validation/calendar.ts diff --git a/src/app/(dashboard)/calendar/components/calendar-client.tsx b/src/app/(dashboard)/calendar/components/calendar-client.tsx new file mode 100644 index 0000000..0fb01ab --- /dev/null +++ b/src/app/(dashboard)/calendar/components/calendar-client.tsx @@ -0,0 +1,259 @@ +"use client"; + +import { useMemo, useState, useTransition } from "react"; +import { ChevronLeft, ChevronRight, Loader2, Plus, Trash2 } from "lucide-react"; +import { toast } from "sonner"; + +import { Button } from "@/components/ui/button"; +import { Card, CardContent } from "@/components/ui/card"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { deleteCalendarEventAction } from "@/lib/appwrite/calendar-actions"; +import { cn } from "@/lib/utils"; + +import { EventFormSheet } from "./event-form-sheet"; +import { COLOR_BG, type Customer, type EventRow } from "./types"; + +type Props = { + events: EventRow[]; + customers: Customer[]; +}; + +const WEEKDAYS = ["Pzt", "Sal", "Çar", "Per", "Cum", "Cmt", "Paz"]; +const MONTH_NAMES = [ + "Ocak", "Şubat", "Mart", "Nisan", "Mayıs", "Haziran", + "Temmuz", "Ağustos", "Eylül", "Ekim", "Kasım", "Aralık", +]; + +function startOfMonthGrid(year: number, month: number): Date { + // Monday-first grid; first cell is the Monday on/before the 1st + const first = new Date(year, month, 1); + const dayIdx = (first.getDay() + 6) % 7; // 0 = Mon + return new Date(year, month, 1 - dayIdx); +} + +function ymd(d: Date): string { + const pad = (n: number) => String(n).padStart(2, "0"); + return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`; +} + +export function CalendarClient({ events, customers }: Props) { + const today = new Date(); + const [cursor, setCursor] = useState(new Date(today.getFullYear(), today.getMonth(), 1)); + const [formOpen, setFormOpen] = useState(false); + const [editing, setEditing] = useState(null); + const [defaultDate, setDefaultDate] = useState(); + const [deleting, setDeleting] = useState(null); + const [busy, startTransition] = useTransition(); + + const eventsByDay = useMemo(() => { + const map = new Map(); + for (const e of events) { + const start = new Date(e.start); + const end = new Date(e.end); + const cur = new Date(start.getFullYear(), start.getMonth(), start.getDate()); + const last = new Date(end.getFullYear(), end.getMonth(), end.getDate()); + while (cur.getTime() <= last.getTime()) { + const key = ymd(cur); + const arr = map.get(key) ?? []; + arr.push(e); + map.set(key, arr); + cur.setDate(cur.getDate() + 1); + } + } + return map; + }, [events]); + + const grid = useMemo(() => { + const start = startOfMonthGrid(cursor.getFullYear(), cursor.getMonth()); + const days: Date[] = []; + for (let i = 0; i < 42; i++) { + const d = new Date(start); + d.setDate(start.getDate() + i); + days.push(d); + } + return days; + }, [cursor]); + + const handlePrev = () => setCursor(new Date(cursor.getFullYear(), cursor.getMonth() - 1, 1)); + const handleNext = () => setCursor(new Date(cursor.getFullYear(), cursor.getMonth() + 1, 1)); + const handleToday = () => setCursor(new Date(today.getFullYear(), today.getMonth(), 1)); + + const handleAddOnDay = (date: Date) => { + setEditing(null); + setDefaultDate(ymd(date)); + setFormOpen(true); + }; + + const handleAddNew = () => { + setEditing(null); + setDefaultDate(ymd(today)); + setFormOpen(true); + }; + + const handleEdit = (event: EventRow) => { + setEditing(event); + setDefaultDate(undefined); + setFormOpen(true); + }; + + const handleDelete = () => { + if (!deleting) return; + startTransition(async () => { + const fd = new FormData(); + fd.set("id", deleting.id); + const result = await deleteCalendarEventAction(fd); + if (result.ok) { + toast.success("Etkinlik silindi."); + setDeleting(null); + } else { + toast.error(result.error ?? "Silme başarısız."); + } + }); + }; + + const todayKey = ymd(today); + + return ( + + +
+
+ +

+ {MONTH_NAMES[cursor.getMonth()]} {cursor.getFullYear()} +

+ + +
+ +
+ +
+ {WEEKDAYS.map((wd) => ( +
+ {wd} +
+ ))} + {grid.map((d) => { + const inMonth = d.getMonth() === cursor.getMonth(); + const key = ymd(d); + const isToday = key === todayKey; + const dayEvents = eventsByDay.get(key) ?? []; + return ( +
+
+ + {d.getDate()} + + +
+
+ {dayEvents.slice(0, 3).map((e) => ( + + ))} + {dayEvents.length > 3 && ( + + +{dayEvents.length - 3} daha + + )} +
+
+ ); + })} +
+
+ + { + setFormOpen(v); + if (!v) setEditing(null); + }} + event={editing} + defaultDate={defaultDate} + customers={customers} + onRequestDelete={(e) => { + setFormOpen(false); + setDeleting(e); + }} + /> + + !v && setDeleting(null)}> + + + Etkinliği sil + + {deleting?.title} kalıcı olarak silinecek. + + + + + + + + +
+ ); +} diff --git a/src/app/(dashboard)/calendar/components/calendar-main.tsx b/src/app/(dashboard)/calendar/components/calendar-main.tsx deleted file mode 100644 index d8821d6..0000000 --- a/src/app/(dashboard)/calendar/components/calendar-main.tsx +++ /dev/null @@ -1,347 +0,0 @@ -"use client" - -import { useState } from "react" -import { - ChevronLeft, - ChevronRight, - Calendar as CalendarIcon, - Clock, - MapPin, - Users, - MoreHorizontal, - Search, - Grid3X3, - List, - ChevronDown, - Menu -} from "lucide-react" -import { format, addMonths, subMonths, startOfMonth, endOfMonth, eachDayOfInterval, isSameMonth, isToday, isSameDay } from "date-fns" - -import { Button } from "@/components/ui/button" -import { Card, CardContent } from "@/components/ui/card" -import { Badge } from "@/components/ui/badge" -import { Avatar, AvatarFallback } from "@/components/ui/avatar" -import { Input } from "@/components/ui/input" -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger -} from "@/components/ui/dropdown-menu" -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle -} from "@/components/ui/dialog" -import { cn } from "@/lib/utils" -import { type CalendarEvent } from "../types" - -// Import data -import eventsData from "../data/events.json" - -interface CalendarMainProps { - selectedDate?: Date - onDateSelect?: (date: Date) => void - onMenuClick?: () => void - events?: CalendarEvent[] - onEventClick?: (event: CalendarEvent) => void -} - -export function CalendarMain({ selectedDate, onDateSelect, onMenuClick, events, onEventClick }: CalendarMainProps) { - // Convert JSON events to CalendarEvent objects with proper Date objects, fallback to imported data - const sampleEvents: CalendarEvent[] = events || eventsData.map(event => ({ - ...event, - date: new Date(event.date), - type: event.type as "meeting" | "event" | "personal" | "task" | "reminder" - })) - - const [currentDate, setCurrentDate] = useState(selectedDate || new Date()) - const [viewMode, setViewMode] = useState<"month" | "week" | "day" | "list">("month") - const [showEventDialog, setShowEventDialog] = useState(false) - const [selectedEvent, setSelectedEvent] = useState(null) - - const monthStart = startOfMonth(currentDate) - const monthEnd = endOfMonth(currentDate) - - // Extend to show full weeks (including previous/next month days) - const calendarStart = new Date(monthStart) - calendarStart.setDate(calendarStart.getDate() - monthStart.getDay()) - - const calendarEnd = new Date(monthEnd) - calendarEnd.setDate(calendarEnd.getDate() + (6 - monthEnd.getDay())) - - const calendarDays = eachDayOfInterval({ start: calendarStart, end: calendarEnd }) - - const getEventsForDay = (date: Date) => { - return sampleEvents.filter(event => isSameDay(event.date, date)) - } - - const navigateMonth = (direction: "prev" | "next") => { - setCurrentDate(direction === "prev" ? subMonths(currentDate, 1) : addMonths(currentDate, 1)) - } - - const goToToday = () => { - setCurrentDate(new Date()) - } - - const handleEventClick = (event: CalendarEvent) => { - if (onEventClick) { - onEventClick(event) - } else { - setSelectedEvent(event) - setShowEventDialog(true) - } - } - - const renderCalendarGrid = () => { - const weekDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] - - return ( -
- {/* Calendar Header */} -
- {weekDays.map(day => ( -
- {day} -
- ))} -
- - {/* Calendar Body */} -
- {calendarDays.map(day => { - const dayEvents = getEventsForDay(day) - const isCurrentMonth = isSameMonth(day, currentDate) - const isDayToday = isToday(day) - const isSelected = selectedDate && isSameDay(day, selectedDate) - - return ( -
onDateSelect?.(day)} - > -
- - {format(day, 'd')} - - {dayEvents.length > 2 && ( - - +{dayEvents.length - 2} - - )} -
- -
- {dayEvents.slice(0, 2).map(event => ( -
{ - e.stopPropagation() - handleEventClick(event) - }} - > -
- - {event.title} -
-
- ))} -
-
- ) - })} -
-
- ) - } - - const renderListView = () => { - const upcomingEvents = sampleEvents - .filter(event => event.date >= new Date()) - .sort((a, b) => a.date.getTime() - b.date.getTime()) - - return ( -
-
- {upcomingEvents.map(event => ( - handleEventClick(event)}> - -
-
-
-
-

{event.title}

-
-
- - {format(event.date, 'MMM d, yyyy')} -
-
- - {event.time} -
-
- - {event.location} -
-
-
-
-
-
- {event.attendees.slice(0, 3).map((attendee, index) => ( - - {attendee} - - ))} -
- -
-
- - - ))} -
-
- ) - } - - return ( -
- {/* Header */} -
-
- {/* Mobile Menu Button */} - - -
- - - -
- -

- {format(currentDate, 'MMMM yyyy')} -

-
- -
- {/* Search */} -
- - -
- - {/* View Mode Toggle */} - - - - - - setViewMode("month")} className="cursor-pointer"> - - Month - - setViewMode("list")} className="cursor-pointer"> - - List - - - -
-
- - {/* Calendar Content */} - {viewMode === "month" ? renderCalendarGrid() : renderListView()} - - {/* Event Detail Dialog */} - - - - {selectedEvent?.title || "Event Details"} - - View and manage this calendar event - - - {selectedEvent && ( -
-
- - {format(selectedEvent.date, 'EEEE, MMMM d, yyyy')} -
-
- - {selectedEvent.time} ({selectedEvent.duration}) -
-
- - {selectedEvent.location} -
-
- -
- Attendees: -
- {selectedEvent.attendees.map((attendee: string, index: number) => ( - - {attendee} - - ))} -
-
-
-
- - {selectedEvent.type} - -
-
- - -
-
- )} -
-
-
- ) -} diff --git a/src/app/(dashboard)/calendar/components/calendar-sidebar.tsx b/src/app/(dashboard)/calendar/components/calendar-sidebar.tsx deleted file mode 100644 index 8452c90..0000000 --- a/src/app/(dashboard)/calendar/components/calendar-sidebar.tsx +++ /dev/null @@ -1,78 +0,0 @@ -"use client" - -import { Plus } from "lucide-react" - -import { Calendars } from "./calendars" -import { DatePicker } from "./date-picker" -import { Button } from "@/components/ui/button" -import { Separator } from "@/components/ui/separator" - -interface CalendarSidebarProps { - selectedDate?: Date - onDateSelect?: (date: Date) => void - onNewCalendar?: () => void - onNewEvent?: () => void - events?: Array<{ date: Date; count: number }> - className?: string -} - -export function CalendarSidebar({ - selectedDate, - onDateSelect, - onNewCalendar, - onNewEvent, - events = [], - className -}: CalendarSidebarProps) { - return ( -
- {/* Add New Event Button */} -
- -
- - {/* Date Picker */} - - - - - {/* Calendars */} -
- { - console.log(`Calendar ${calendarId} visibility: ${visible}`) - }} - onCalendarEdit={(calendarId) => { - console.log(`Edit calendar: ${calendarId}`) - }} - onCalendarDelete={(calendarId) => { - console.log(`Delete calendar: ${calendarId}`) - }} - /> -
- - {/* Footer */} -
- -
-
- ) -} diff --git a/src/app/(dashboard)/calendar/components/calendar-unified.tsx b/src/app/(dashboard)/calendar/components/calendar-unified.tsx deleted file mode 100644 index fdc54fd..0000000 --- a/src/app/(dashboard)/calendar/components/calendar-unified.tsx +++ /dev/null @@ -1,381 +0,0 @@ -"use client" - -import { useState } from "react" -import { - ChevronLeft, - ChevronRight, - Calendar as CalendarIcon, - Clock, - MapPin, - Users, - Search, - Grid3X3, - List, - ChevronDown, - Menu, - Plus -} from "lucide-react" -import { format, addMonths, subMonths, startOfMonth, endOfMonth, eachDayOfInterval, isSameMonth, isToday, isSameDay } from "date-fns" - -import { Button } from "@/components/ui/button" -import { Badge } from "@/components/ui/badge" -import { Avatar, AvatarFallback } from "@/components/ui/avatar" -import { Calendar } from "@/components/ui/calendar" -import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@/components/ui/sheet" -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger -} from "@/components/ui/dropdown-menu" -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle -} from "@/components/ui/dialog" -import { cn } from "@/lib/utils" -import { type CalendarEvent } from "../types" - -// Import data -import eventsData from "../data/events.json" -import calendarsData from "../data/calendars.json" - -interface CalendarMainProps { - eventDates?: Array<{ date: Date; count: number }> -} - -export function CalendarMain({ eventDates = [] }: CalendarMainProps) { - const [selectedDate, setSelectedDate] = useState(new Date()) - const [currentDate, setCurrentDate] = useState(new Date()) - const [viewMode, setViewMode] = useState<"month" | "week" | "day" | "list">("month") - const [showEventDialog, setShowEventDialog] = useState(false) - const [selectedEvent, setSelectedEvent] = useState(null) - const [showCalendarSheet, setShowCalendarSheet] = useState(false) - - // Convert JSON events to CalendarEvent objects with proper Date objects - const sampleEvents: CalendarEvent[] = eventsData.map(event => ({ - ...event, - date: new Date(event.date), - type: event.type as "meeting" | "event" | "personal" | "task" | "reminder" - })) - - const monthStart = startOfMonth(currentDate) - const monthEnd = endOfMonth(currentDate) - - // Extend to show full weeks (including previous/next month days) - const calendarStart = new Date(monthStart) - calendarStart.setDate(calendarStart.getDate() - monthStart.getDay()) - - const calendarEnd = new Date(monthEnd) - calendarEnd.setDate(calendarEnd.getDate() + (6 - monthEnd.getDay())) - - const calendarDays = eachDayOfInterval({ start: calendarStart, end: calendarEnd }) - - const getEventsForDay = (date: Date) => { - return sampleEvents.filter(event => isSameDay(event.date, date)) - } - - const navigateMonth = (direction: "prev" | "next") => { - setCurrentDate(direction === "prev" ? subMonths(currentDate, 1) : addMonths(currentDate, 1)) - } - - const goToToday = () => { - setCurrentDate(new Date()) - } - - const handleEventClick = (event: CalendarEvent) => { - setSelectedEvent(event) - setShowEventDialog(true) - } - - const handleDateSelect = (date: Date) => { - setSelectedDate(date) - } - - const handleNewCalendar = () => { - console.log("Creating new calendar") - // In a real app, this would open a new calendar form - } - - const handleNewEvent = () => { - console.log("Creating new event") - // In a real app, this would open event form - } - - const renderCalendarGrid = () => { - const weekDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] - - return ( -
- {/* Calendar Header */} -
- {weekDays.map(day => ( -
- {day} -
- ))} -
- - {/* Calendar Grid */} -
- {calendarDays.map((day) => { - const dayEvents = getEventsForDay(day) - const isCurrentMonth = isSameMonth(day, currentDate) - const isDayToday = isToday(day) - const isSelected = isSameDay(day, selectedDate) - - return ( -
handleDateSelect(day)} - > - {/* Date Number */} -
- {format(day, 'd')} -
- - {/* Events */} -
- {dayEvents.slice(0, 3).map((event) => ( -
{ - e.stopPropagation() - handleEventClick(event) - }} - > - {event.time} {event.title} -
- ))} - {dayEvents.length > 3 && ( -
- +{dayEvents.length - 3} more -
- )} -
-
- ) - })} -
-
- ) - } - - const renderSidebar = () => ( -
-
-
-

Calendar

- -
- - {/* Date Picker */} - date && handleDateSelect(date)} - className="rounded-md border" - modifiers={{ - eventDay: eventDates.map(ed => ed.date) - }} - modifiersStyles={{ - eventDay: { fontWeight: 'bold' } - }} - /> -
- - {/* Mini Calendars List */} -
-
-

My Calendars

- -
- -
- {calendarsData.map((calendar) => ( -
-
- {calendar.name} -
- ))} -
-
-
- ) - - return ( -
-
- {/* Desktop Sidebar */} -
- {renderSidebar()} -
- - {/* Main Calendar Panel */} -
- {/* Calendar Toolbar */} -
-
-
- {/* Mobile Menu Button */} - - - {/* Month Navigation */} -
- -

- {format(currentDate, 'MMMM yyyy')} -

- -
- - -
- -
-
- -
- - {/* View Mode Toggle */} - - - - - - setViewMode("month")}> - - Month - - setViewMode("week")}> - - Week - - setViewMode("day")}> - - Day - - setViewMode("list")}> - - List - - - -
-
-
- - {/* Calendar Content */} - {renderCalendarGrid()} -
-
- - {/* Mobile/Tablet Sheet */} - - - - Calendar - - Browse dates and manage your calendar events - - - {renderSidebar()} - - - - {/* Event Details Dialog */} - - - - {selectedEvent?.title} - - Event details and information - - - {selectedEvent && ( -
-
- - {selectedEvent.time} • {selectedEvent.duration} -
- - {selectedEvent.location && ( -
- - {selectedEvent.location} -
- )} - - {selectedEvent.attendees.length > 0 && ( -
- -
- {selectedEvent.attendees.map((attendee, index) => ( - - - {attendee} - - - ))} -
-
- )} - - {selectedEvent.description && ( -
- {selectedEvent.description} -
- )} - -
- - {selectedEvent.type} - -
-
- )} -
-
-
- ) -} diff --git a/src/app/(dashboard)/calendar/components/calendar.tsx b/src/app/(dashboard)/calendar/components/calendar.tsx deleted file mode 100644 index ce5a320..0000000 --- a/src/app/(dashboard)/calendar/components/calendar.tsx +++ /dev/null @@ -1,77 +0,0 @@ -"use client" - -import { CalendarSidebar } from "./calendar-sidebar" -import { CalendarMain } from "./calendar-main" -import { EventForm } from "./event-form" -import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@/components/ui/sheet" -import { type CalendarEvent } from "../types" -import { useCalendar } from "../use-calendar" - -interface CalendarProps { - events: CalendarEvent[] - eventDates: Array<{ date: Date; count: number }> -} - -export function Calendar({ events, eventDates }: CalendarProps) { - const calendar = useCalendar(events) - - return ( - <> -
-
- {/* Desktop Sidebar - Hidden on mobile/tablet, shown on extra large screens */} -
- -
- - {/* Main Calendar Panel */} -
- calendar.setShowCalendarSheet(true)} - events={calendar.events} - onEventClick={calendar.handleEditEvent} - /> -
-
- - {/* Mobile/Tablet Sheet - Positioned relative to calendar container */} - - - - Calendar - - Browse dates and manage your calendar events - - - - - -
- - {/* Event Form Dialog */} - - - ) -} diff --git a/src/app/(dashboard)/calendar/components/calendars.tsx b/src/app/(dashboard)/calendar/components/calendars.tsx deleted file mode 100644 index 1cd9798..0000000 --- a/src/app/(dashboard)/calendar/components/calendars.tsx +++ /dev/null @@ -1,203 +0,0 @@ -"use client" - -import { useState } from "react" -import { Check, ChevronRight, Plus, Eye, EyeOff, MoreHorizontal } from "lucide-react" - -import { - Collapsible, - CollapsibleContent, - CollapsibleTrigger, -} from "@/components/ui/collapsible" -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuSeparator, - DropdownMenuTrigger -} from "@/components/ui/dropdown-menu" -import { cn } from "@/lib/utils" - -interface CalendarItem { - id: string - name: string - color: string - visible: boolean - type: "personal" | "work" | "shared" -} - -interface CalendarGroup { - name: string - items: CalendarItem[] -} - -interface CalendarsProps { - calendars?: { - name: string - items: string[] - }[] - onCalendarToggle?: (calendarId: string, visible: boolean) => void - onCalendarEdit?: (calendarId: string) => void - onCalendarDelete?: (calendarId: string) => void - onNewCalendar?: () => void -} - -// Enhanced calendar data with colors and visibility -const enhancedCalendars: CalendarGroup[] = [ - { - name: "My Calendars", - items: [ - { id: "personal", name: "Personal", color: "bg-blue-500", visible: true, type: "personal" }, - { id: "work", name: "Work", color: "bg-green-500", visible: true, type: "work" }, - { id: "family", name: "Family", color: "bg-pink-500", visible: true, type: "personal" } - ] - }, - { - name: "Favorites", - items: [ - { id: "holidays", name: "Holidays", color: "bg-red-500", visible: true, type: "shared" }, - { id: "birthdays", name: "Birthdays", color: "bg-purple-500", visible: true, type: "personal" } - ] - }, - { - name: "Other", - items: [ - { id: "travel", name: "Travel", color: "bg-orange-500", visible: false, type: "personal" }, - { id: "reminders", name: "Reminders", color: "bg-yellow-500", visible: true, type: "personal" }, - { id: "deadlines", name: "Deadlines", color: "bg-red-600", visible: true, type: "work" } - ] - } -] - -export function Calendars({ - onCalendarToggle, - onCalendarEdit, - onCalendarDelete, - onNewCalendar -}: CalendarsProps) { - const [calendarData, setCalendarData] = useState(enhancedCalendars) - - const handleToggleVisibility = (calendarId: string) => { - setCalendarData(prev => prev.map(group => ({ - ...group, - items: group.items.map(item => - item.id === calendarId - ? { ...item, visible: !item.visible } - : item - ) - }))) - - const calendar = calendarData.flatMap(g => g.items).find(c => c.id === calendarId) - if (calendar) { - onCalendarToggle?.(calendarId, !calendar.visible) - } - } - - return ( -
- {calendarData.map((calendar, index) => ( -
- - - {calendar.name} -
- {index === 0 && ( -
{ - e.stopPropagation() - onNewCalendar?.() - }} - > - -
- )} - -
-
- - -
- {calendar.items.map((item) => ( -
-
-
- {/* Calendar Color & Visibility Toggle */} - - - {/* Calendar Name */} - handleToggleVisibility(item.id)} - > - {item.name} - - - {/* Visibility Icon */} -
- {item.visible ? ( - - ) : ( - - )} -
- - {/* More Options */} - - -
e.stopPropagation()} - > - -
-
- - onCalendarEdit?.(item.id)} - className="cursor-pointer" - > - Edit calendar - - handleToggleVisibility(item.id)} - className="cursor-pointer" - > - {item.visible ? "Hide" : "Show"} calendar - - - onCalendarDelete?.(item.id)} - className="cursor-pointer text-destructive" - > - Delete calendar - - -
-
-
-
- ))} -
-
-
-
- ))} -
- ) -} diff --git a/src/app/(dashboard)/calendar/components/date-picker.tsx b/src/app/(dashboard)/calendar/components/date-picker.tsx deleted file mode 100644 index 2bdc7e5..0000000 --- a/src/app/(dashboard)/calendar/components/date-picker.tsx +++ /dev/null @@ -1,48 +0,0 @@ -"use client" - -import { useState } from "react" -import { Calendar } from "@/components/ui/calendar" - -interface DatePickerProps { - selectedDate?: Date - onDateSelect?: (date: Date) => void - events?: Array<{ date: Date; count: number }> -} - -export function DatePicker({ selectedDate, onDateSelect, events = [] }: DatePickerProps) { - const [date, setDate] = useState(selectedDate || new Date()) - - const handleDateSelect = (selectedDate: Date | undefined) => { - if (selectedDate) { - setDate(selectedDate) - onDateSelect?.(selectedDate) - } - } - - // Create a map of dates with events for styling - const eventDates = events.reduce((acc, event) => { - const dateKey = event.date.toDateString() - acc[dateKey] = event.count - return acc - }, {} as Record) - - return ( -
- { - const eventCount = eventDates[date.toDateString()] - return Boolean(eventCount && eventCount > 0) - } - }} - modifiersClassNames={{ - hasEvents: "relative after:absolute after:bottom-1 after:right-1 after:w-1.5 after:h-1.5 after:bg-primary after:rounded-full" - }} - /> -
- ) -} diff --git a/src/app/(dashboard)/calendar/components/event-form-sheet.tsx b/src/app/(dashboard)/calendar/components/event-form-sheet.tsx new file mode 100644 index 0000000..b33cbc2 --- /dev/null +++ b/src/app/(dashboard)/calendar/components/event-form-sheet.tsx @@ -0,0 +1,274 @@ +"use client"; + +import { useActionState, useEffect, useState } from "react"; +import { Loader2, Save, Trash2 } from "lucide-react"; +import { toast } from "sonner"; + +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { + Sheet, + SheetContent, + SheetDescription, + SheetFooter, + SheetHeader, + SheetTitle, +} from "@/components/ui/sheet"; +import { Switch } from "@/components/ui/switch"; +import { Textarea } from "@/components/ui/textarea"; +import { + createCalendarEventAction, + updateCalendarEventAction, +} from "@/lib/appwrite/calendar-actions"; +import { initialCalendarState } from "@/lib/appwrite/calendar-types"; +import { cn } from "@/lib/utils"; + +import { COLOR_PRESETS, type Customer, type EventRow } from "./types"; + +const NONE = "__none__"; + +type Props = { + open: boolean; + onOpenChange: (v: boolean) => void; + event?: EventRow | null; + defaultDate?: string; // YYYY-MM-DD for new events + customers: Customer[]; + onRequestDelete?: (event: EventRow) => void; +}; + +function isoToInput(iso: string, allDay: boolean): string { + if (!iso) return ""; + if (allDay) return iso.slice(0, 10); + const d = new Date(iso); + const pad = (n: number) => String(n).padStart(2, "0"); + return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`; +} + +export function EventFormSheet({ + open, + onOpenChange, + event, + defaultDate, + customers, + onRequestDelete, +}: Props) { + const isEdit = Boolean(event); + const action = isEdit ? updateCalendarEventAction : createCalendarEventAction; + const [state, formAction, isPending] = useActionState(action, initialCalendarState); + const [allDay, setAllDay] = useState(event?.allDay ?? false); + + useEffect(() => { + setAllDay(event?.allDay ?? false); + }, [event]); + + useEffect(() => { + if (state.ok) { + toast.success(isEdit ? "Etkinlik güncellendi." : "Etkinlik eklendi."); + onOpenChange(false); + } else if (state.error) { + toast.error(state.error); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [state]); + + const startDefault = + event?.start + ? isoToInput(event.start, allDay) + : defaultDate + ? allDay + ? defaultDate + : `${defaultDate}T09:00` + : ""; + const endDefault = + event?.end + ? isoToInput(event.end, allDay) + : defaultDate + ? allDay + ? defaultDate + : `${defaultDate}T10:00` + : ""; + + return ( + + + + {isEdit ? "Etkinliği düzenle" : "Yeni etkinlik"} + + Tarih, saat ve müşteri bilgileri ile bir takvim girdisi oluşturun. + + + +
{ + ["customerId", "color"].forEach((k) => { + if (fd.get(k) === NONE) fd.set(k, ""); + }); + formAction(fd); + }} + className="flex flex-1 flex-col" + > + {isEdit && event && } + +
+
+ + + {state.fieldErrors?.title && ( +

{state.fieldErrors.title}

+ )} +
+ +
+
+ +

Saat girmeden gün boyu sürecek.

+
+ +
+ +
+
+ + + {state.fieldErrors?.start && ( +

{state.fieldErrors.start}

+ )} +
+
+ + + {state.fieldErrors?.end && ( +

{state.fieldErrors.end}

+ )} +
+
+ +
+ + +
+ +
+ + +
+ +
+ +