diff --git a/src/frontend/apps/calendars/src/features/calendar/components/calendar-list/constants.ts b/src/frontend/apps/calendars/src/features/calendar/components/calendar-list/constants.ts new file mode 100644 index 0000000..208351b --- /dev/null +++ b/src/frontend/apps/calendars/src/features/calendar/components/calendar-list/constants.ts @@ -0,0 +1,17 @@ +/** + * Constants for the CalendarList components. + */ + +/** + * Default color palette for calendar creation. + */ +export const DEFAULT_COLORS = [ + "#3788d8", // Blue + "#28a745", // Green + "#dc3545", // Red + "#ffc107", // Yellow + "#6f42c1", // Purple + "#fd7e14", // Orange + "#20c997", // Teal + "#e83e8c", // Pink +]; diff --git a/src/frontend/apps/calendars/src/features/calendar/components/calendar-list/hooks/useCalendarListState.ts b/src/frontend/apps/calendars/src/features/calendar/components/calendar-list/hooks/useCalendarListState.ts new file mode 100644 index 0000000..c58a7ae --- /dev/null +++ b/src/frontend/apps/calendars/src/features/calendar/components/calendar-list/hooks/useCalendarListState.ts @@ -0,0 +1,200 @@ +/** + * useCalendarListState hook. + * Manages state and handlers for the CalendarList component. + */ + +import { useState, useCallback } from "react"; + +import type { + CalDavCalendar, + CalDavCalendarCreate, + CalDavCalendarUpdate, +} from "../../../services/dav/types/caldav-service"; +import type { CalendarModalState, DeleteState } from "../types"; + +interface UseCalendarListStateProps { + createCalendar: ( + params: CalDavCalendarCreate + ) => Promise<{ success: boolean; error?: string }>; + updateCalendar: ( + url: string, + options: CalDavCalendarUpdate + ) => Promise<{ success: boolean; error?: string }>; + deleteCalendar: (url: string) => Promise<{ success: boolean; error?: string }>; + shareCalendar: ( + url: string, + email: string + ) => Promise<{ success: boolean; error?: string }>; +} + +export const useCalendarListState = ({ + createCalendar, + updateCalendar, + deleteCalendar, + shareCalendar, +}: UseCalendarListStateProps) => { + // Modal states + const [modalState, setModalState] = useState({ + isOpen: false, + mode: "create", + calendar: null, + }); + + const [deleteState, setDeleteState] = useState({ + isOpen: false, + calendar: null, + isLoading: false, + }); + + const [isMyCalendarsExpanded, setIsMyCalendarsExpanded] = useState(true); + const [isSharedCalendarsExpanded, setIsSharedCalendarsExpanded] = + useState(true); + const [openMenuUrl, setOpenMenuUrl] = useState(null); + + // Modal handlers + const handleOpenCreateModal = useCallback(() => { + setModalState({ + isOpen: true, + mode: "create", + calendar: null, + }); + }, []); + + const handleOpenEditModal = useCallback((calendar: CalDavCalendar) => { + setModalState({ + isOpen: true, + mode: "edit", + calendar, + }); + }, []); + + const handleCloseModal = useCallback(() => { + setModalState({ + isOpen: false, + mode: "create", + calendar: null, + }); + }, []); + + const handleSaveCalendar = useCallback( + async (name: string, color: string, description?: string) => { + if (modalState.mode === "create") { + const result = await createCalendar({ + displayName: name, + color, + description, + components: ['VEVENT'], + }); + if (!result.success) { + throw new Error(result.error); + } + } else if (modalState.calendar) { + const result = await updateCalendar(modalState.calendar.url, { + displayName: name, + color, + description, + }); + if (!result.success) { + throw new Error(result.error); + } + } + }, + [modalState, createCalendar, updateCalendar] + ); + + const handleShareCalendar = useCallback( + async (email: string): Promise<{ success: boolean; error?: string }> => { + if (!modalState.calendar) { + return { success: false, error: 'No calendar selected' }; + } + return shareCalendar(modalState.calendar.url, email); + }, + [modalState.calendar, shareCalendar] + ); + + // Delete handlers + const handleOpenDeleteModal = useCallback((calendar: CalDavCalendar) => { + setDeleteState({ + isOpen: true, + calendar, + isLoading: false, + }); + }, []); + + const handleCloseDeleteModal = useCallback(() => { + setDeleteState({ + isOpen: false, + calendar: null, + isLoading: false, + }); + }, []); + + const handleConfirmDelete = useCallback(async () => { + if (!deleteState.calendar) return; + + setDeleteState((prev) => ({ ...prev, isLoading: true })); + try { + const result = await deleteCalendar(deleteState.calendar.url); + if (!result.success) { + console.error("Failed to delete calendar:", result.error); + } + handleCloseDeleteModal(); + } catch (error) { + console.error("Error deleting calendar:", error); + setDeleteState((prev) => ({ ...prev, isLoading: false })); + } + }, [deleteState.calendar, deleteCalendar, handleCloseDeleteModal]); + + // Menu handlers + const handleMenuToggle = useCallback( + (calendarUrl: string, e: React.MouseEvent) => { + e.stopPropagation(); + setOpenMenuUrl(openMenuUrl === calendarUrl ? null : calendarUrl); + }, + [openMenuUrl] + ); + + const handleCloseMenu = useCallback(() => { + setOpenMenuUrl(null); + }, []); + + // Expansion handlers + const handleToggleMyCalendars = useCallback(() => { + setIsMyCalendarsExpanded((prev) => !prev); + }, []); + + const handleToggleSharedCalendars = useCallback(() => { + setIsSharedCalendarsExpanded((prev) => !prev); + }, []); + + return { + // Modal state + modalState, + deleteState, + + // Expansion state + isMyCalendarsExpanded, + isSharedCalendarsExpanded, + openMenuUrl, + + // Modal handlers + handleOpenCreateModal, + handleOpenEditModal, + handleCloseModal, + handleSaveCalendar, + handleShareCalendar, + + // Delete handlers + handleOpenDeleteModal, + handleCloseDeleteModal, + handleConfirmDelete, + + // Menu handlers + handleMenuToggle, + handleCloseMenu, + + // Expansion handlers + handleToggleMyCalendars, + handleToggleSharedCalendars, + }; +}; diff --git a/src/frontend/apps/calendars/src/features/calendar/components/calendar-list/types.ts b/src/frontend/apps/calendars/src/features/calendar/components/calendar-list/types.ts new file mode 100644 index 0000000..98934ef --- /dev/null +++ b/src/frontend/apps/calendars/src/features/calendar/components/calendar-list/types.ts @@ -0,0 +1,88 @@ +/** + * Type definitions for CalendarList components. + */ + +import type { CalDavCalendar } from "../../services/dav/types/caldav-service"; +import type { Calendar } from "../../types"; + +/** + * Props for the CalendarModal component. + */ +export interface CalendarModalProps { + isOpen: boolean; + mode: "create" | "edit"; + calendar?: CalDavCalendar | null; + onClose: () => void; + onSave: (name: string, color: string, description?: string) => Promise; + onShare?: (email: string) => Promise<{ success: boolean; error?: string }>; +} + +/** + * Props for the CalendarItemMenu component. + */ +export interface CalendarItemMenuProps { + onEdit: () => void; + onDelete: () => void; + onSubscription?: () => void; + onClose: () => void; +} + +/** + * Props for the DeleteConfirmModal component. + */ +export interface DeleteConfirmModalProps { + isOpen: boolean; + calendarName: string; + onConfirm: () => void; + onCancel: () => void; + isLoading: boolean; +} + +/** + * Props for the CalendarListItem component. + */ +export interface CalendarListItemProps { + calendar: CalDavCalendar; + isVisible: boolean; + isMenuOpen: boolean; + onToggleVisibility: (url: string) => void; + onMenuToggle: (url: string, e: React.MouseEvent) => void; + onEdit: (calendar: CalDavCalendar) => void; + onDelete: (calendar: CalDavCalendar) => void; + onSubscription?: (calendar: CalDavCalendar) => void; + onCloseMenu: () => void; +} + +/** + * Props for the SharedCalendarListItem component. + */ +export interface SharedCalendarListItemProps { + calendar: Calendar; + isVisible: boolean; + onToggleVisibility: (id: string) => void; +} + +/** + * Props for the main CalendarList component. + */ +export interface CalendarListProps { + calendars: Calendar[]; +} + +/** + * State for the calendar modal. + */ +export interface CalendarModalState { + isOpen: boolean; + mode: "create" | "edit"; + calendar: CalDavCalendar | null; +} + +/** + * State for the delete confirmation. + */ +export interface DeleteState { + isOpen: boolean; + calendar: CalDavCalendar | null; + isLoading: boolean; +}