🔥(front) remove deprecated calendar components

Remove old CalendarView, CalendarList, EventModal and
CreateCalendarModal replaced by new Scheduler and
CalendarList implementations.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Nathan Panchout
2026-01-25 20:35:20 +01:00
parent 4faa131878
commit 85465628e1
6 changed files with 0 additions and 1136 deletions

View File

@@ -1,88 +0,0 @@
/**
* CalendarList component - List of calendars with visibility toggles.
*/
import { Button, Checkbox } from "@openfun/cunningham-react";
import { Calendar } from "../api";
import { useToggleCalendarVisibility } from "../hooks/useCalendars";
interface CalendarListProps {
calendars: Calendar[];
onCreateCalendar: () => void;
}
export const CalendarList = ({
calendars,
onCreateCalendar,
}: CalendarListProps) => {
const toggleVisibility = useToggleCalendarVisibility();
// Ensure calendars is an array
const calendarsArray = Array.isArray(calendars) ? calendars : [];
const ownedCalendars = calendarsArray.filter(
(cal) => !cal.name.includes("(partagé)")
);
const sharedCalendars = calendarsArray.filter((cal) =>
cal.name.includes("(partagé)")
);
const handleToggle = (calendarId: string) => {
toggleVisibility.mutate(calendarId);
};
const renderCalendarItem = (calendar: Calendar) => (
<div key={calendar.id} className="calendar-list__item">
<Checkbox
checked={calendar.is_visible}
onChange={() => handleToggle(calendar.id)}
label=""
aria-label={`Afficher ${calendar.name}`}
/>
<span
className="calendar-list__color"
style={{ backgroundColor: calendar.color }}
/>
<span className="calendar-list__name" title={calendar.name}>
{calendar.name}
</span>
{calendar.is_default && (
<span className="calendar-list__badge">Par défaut</span>
)}
</div>
);
return (
<div className="calendar-list">
<div className="calendar-list__section">
<div className="calendar-list__section-header">
<span className="calendar-list__section-title">Mes calendriers</span>
<button
className="calendar-list__add-btn"
onClick={onCreateCalendar}
title="Créer un calendrier"
>
<span className="material-icons">add</span>
</button>
</div>
<div className="calendar-list__items">
{ownedCalendars.map(renderCalendarItem)}
</div>
</div>
{sharedCalendars.length > 0 && (
<div className="calendar-list__section">
<div className="calendar-list__section-header">
<span className="calendar-list__section-title">
Calendriers partagés
</span>
</div>
<div className="calendar-list__items">
{sharedCalendars.map(renderCalendarItem)}
</div>
</div>
)}
</div>
);
};

View File

@@ -1,171 +0,0 @@
.calendar-view {
display: flex;
flex-direction: column;
flex: 1;
height: 100%;
min-height: 0;
position: relative;
&__loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 10;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem 2rem;
background: var(--c--theme--colors--greyscale-000);
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
p {
margin: 0;
color: var(--c--theme--colors--greyscale-600);
}
}
&__container {
flex: 1;
min-height: 0;
overflow: hidden;
transition: opacity 0.3s ease;
}
&--loading,
&--error {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
gap: 1rem;
padding: 2rem;
text-align: center;
p {
color: var(--c--theme--colors--greyscale-600);
margin: 0;
}
button {
padding: 0.5rem 1rem;
background: var(--c--theme--colors--primary-500);
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
&:hover {
background: var(--c--theme--colors--primary-600);
}
}
}
}
// Open-calendar overrides to match La Suite theme
.calendar-view__container {
// Calendar toolbar
.ec-toolbar {
padding: 0.75rem 1rem;
border-bottom: 1px solid var(--c--theme--colors--greyscale-200);
background: var(--c--theme--colors--greyscale-000);
}
// Today button
.ec-button {
padding: 0.375rem 0.75rem;
border-radius: 4px;
font-size: 0.875rem;
font-weight: 500;
border: 1px solid var(--c--theme--colors--greyscale-300);
background: var(--c--theme--colors--greyscale-000);
color: var(--c--theme--colors--greyscale-800);
cursor: pointer;
transition: all 0.2s ease;
&:hover {
background: var(--c--theme--colors--greyscale-100);
}
&.ec-active {
background: var(--c--theme--colors--primary-500);
border-color: var(--c--theme--colors--primary-500);
color: white;
}
}
// Navigation buttons
.ec-prev,
.ec-next {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
}
// Title
.ec-title {
font-size: 1.125rem;
font-weight: 600;
color: var(--c--theme--colors--greyscale-900);
text-transform: capitalize;
}
// Days header
.ec-days,
.ec-day {
border-color: var(--c--theme--colors--greyscale-200);
}
.ec-day-head {
padding: 0.5rem;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
color: var(--c--theme--colors--greyscale-600);
}
// Time slots
.ec-time {
font-size: 0.75rem;
color: var(--c--theme--colors--greyscale-500);
}
// Events
.ec-event {
border-radius: 4px;
font-size: 0.75rem;
padding: 2px 4px;
border: none;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
&:hover {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
}
}
// Today highlight
.ec-today {
background: rgba(var(--c--theme--colors--primary-rgb, 49, 116, 173), 0.1);
}
// Now indicator
.ec-now-indicator {
border-color: var(--c--theme--colors--danger-500);
&::before {
background: var(--c--theme--colors--danger-500);
}
}
// Popup styles
.ec-popup {
border-radius: 8px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
border: 1px solid var(--c--theme--colors--greyscale-200);
}
}

View File

@@ -1,209 +0,0 @@
/**
* CalendarView component using open-calendar (Algoo).
* Renders a CalDAV-connected calendar view.
*/
import { useEffect, useRef, useState } from "react";
import { useAuth } from "@/features/auth/Auth";
import { createEventModalHandlers, type ModalState } from "./EventModalAdapter";
import { useEventModal } from "../hooks/useEventModal";
import type { IcsEvent } from 'ts-ics';
interface CalendarViewProps {
selectedDate?: Date;
onSelectDate?: (date: Date) => void;
}
export const CalendarView = ({
selectedDate = new Date(),
onSelectDate,
}: CalendarViewProps) => {
const containerRef = useRef<HTMLDivElement>(null);
const calendarRef = useRef<unknown>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [modalState, setModalState] = useState<ModalState>({
isOpen: false,
mode: 'create',
event: null,
calendarUrl: '',
calendars: [],
handleSave: null,
handleDelete: null,
});
const { user } = useAuth();
// Use the modal hook with state from adapter
const modal = useEventModal({
calendars: modalState.calendars,
initialEvent: modalState.event,
initialCalendarUrl: modalState.calendarUrl,
onSubmit: async (event: IcsEvent, calendarUrl: string) => {
if (modalState.handleSave) {
await modalState.handleSave({ calendarUrl, event });
setModalState(prev => ({ ...prev, isOpen: false }));
}
},
onDelete: async (event: IcsEvent, calendarUrl: string) => {
if (modalState.handleDelete) {
await modalState.handleDelete({ calendarUrl, event });
setModalState(prev => ({ ...prev, isOpen: false }));
}
},
});
// Sync modal state with adapter state
useEffect(() => {
if (modalState.isOpen) {
// Always call open to update all state (event, mode, calendarUrl, calendars)
modal.open(modalState.event, modalState.mode, modalState.calendarUrl, modalState.calendars);
} else if (modal.isOpen) {
modal.close();
}
}, [modalState.isOpen, modalState.event, modalState.mode, modalState.calendarUrl, modalState.calendars]);
useEffect(() => {
const initCalendar = async () => {
if (!containerRef.current || !user) return;
try {
setIsLoading(true);
setError(null);
// Dynamically import open-calendar to avoid SSR issues (uses browser-only globals)
const { createCalendar } = await import("open-dav-calendar");
// Clear previous calendar instance
if (containerRef.current) {
containerRef.current.innerHTML = "";
}
// CalDAV server URL - proxied through Django backend
// The proxy handles authentication via session cookies
// open-calendar will discover calendars from this URL
const caldavServerUrl = `${process.env.NEXT_PUBLIC_API_ORIGIN}/api/v1.0/caldav/`;
// Create calendar with CalDAV source
// Use fetchOptions with credentials to include cookies
const calendar = await createCalendar(
[
{
serverUrl: caldavServerUrl,
fetchOptions: {
credentials: "include" as RequestCredentials,
headers: {
"Content-Type": "application/xml",
},
},
},
],
[], // No address books for now
containerRef.current,
{
view: "timeGridWeek",
views: ["dayGridMonth", "timeGridWeek", "timeGridDay", "listWeek"],
locale: "fr",
date: selectedDate,
editable: true,
// Use custom EventEditHandlers that update React state
...createEventModalHandlers(setModalState, []),
onEventCreated: (info) => {
console.log("Event created:", info);
},
onEventUpdated: (info) => {
console.log("Event updated:", info);
},
onEventDeleted: (info) => {
console.log("Event deleted:", info);
},
},
{
// French translations
calendar: {
today: "Aujourd'hui",
month: "Mois",
week: "Semaine",
day: "Jour",
list: "Liste",
},
event: {
edit: "Modifier",
delete: "Supprimer",
save: "Enregistrer",
cancel: "Annuler",
title: "Titre",
description: "Description",
location: "Lieu",
startDate: "Date de début",
endDate: "Date de fin",
allDay: "Journée entière",
},
}
);
calendarRef.current = calendar;
setIsLoading(false);
} catch (err) {
console.error("Failed to initialize calendar:", err);
setError(
err instanceof Error
? err.message
: "Erreur lors du chargement du calendrier"
);
setIsLoading(false);
}
};
initCalendar();
return () => {
// Cleanup
if (containerRef.current) {
containerRef.current.innerHTML = "";
}
calendarRef.current = null;
};
}, [user]);
// Update calendar date when selectedDate changes
useEffect(() => {
if (calendarRef.current && selectedDate) {
// open-calendar may have a method to set date
// This depends on the CalendarElement API
}
}, [selectedDate]);
if (!user) {
return (
<div className="calendar-view calendar-view--loading">
<p>Connexion requise pour afficher le calendrier</p>
</div>
);
}
if (error) {
return (
<div className="calendar-view calendar-view--error">
<p>{error}</p>
<button onClick={() => window.location.reload()}>Réessayer</button>
</div>
);
}
return (
<div className="calendar-view">
{isLoading && (
<div className="calendar-view__loading">
<p>Chargement du calendrier...</p>
</div>
)}
<div
ref={containerRef}
className="calendar-view__container"
style={{ opacity: isLoading ? 0 : 1 }}
/>
{modal.Modal}
</div>
);
};

View File

@@ -1,112 +0,0 @@
/**
* Modal component for creating a new calendar.
*/
import { useState, useEffect } from "react";
import {
Button,
Input,
Modal,
ModalSize,
useModal,
} from "@openfun/cunningham-react";
import { useTranslation } from "next-i18next";
import { useCreateCalendar } from "../hooks/useCalendars";
import { addToast, ToasterItem } from "@/features/ui/components/toaster/Toaster";
import { errorToString } from "@/features/api/APIError";
export const useCreateCalendarModal = () => {
const { t } = useTranslation();
const modal = useModal();
const createCalendar = useCreateCalendar();
const [name, setName] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false);
// Reset form when modal opens
useEffect(() => {
if (modal.isOpen) {
setName("");
setIsSubmitting(false);
}
}, [modal.isOpen]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!name.trim()) {
return;
}
setIsSubmitting(true);
try {
await createCalendar.mutateAsync({
name: name.trim(),
});
addToast(
<ToasterItem>
<span>{t("calendar.created_success", { name: name.trim(), defaultValue: `Calendrier "${name.trim()}" créé avec succès` })}</span>
</ToasterItem>
);
setName("");
modal.close();
} catch (error) {
console.error("Failed to create calendar:", error);
const errorMessage = errorToString(error);
addToast(
<ToasterItem type="error">
<span>{errorMessage || t("calendar.created_error", { defaultValue: "Erreur lors de la création du calendrier" })}</span>
</ToasterItem>
);
} finally {
setIsSubmitting(false);
}
};
const handleClose = () => {
setName("");
modal.close();
};
return {
...modal,
Modal: (
<Modal
{...modal}
title={t("calendar.create_modal.title", { defaultValue: "Créer un nouveau calendrier" })}
size={ModalSize.SMALL}
onClose={handleClose}
>
<form onSubmit={handleSubmit}>
<Input
label={t("calendar.create_modal.name_label", { defaultValue: "Nom du calendrier" })}
value={name}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setName(e.target.value)}
required
disabled={isSubmitting}
autoFocus
placeholder={t("calendar.create_modal.name_placeholder", { defaultValue: "Mon calendrier" })}
/>
<div style={{ display: "flex", gap: "1rem", justifyContent: "flex-end", marginTop: "1.5rem" }}>
<Button
type="button"
color="secondary"
onClick={handleClose}
disabled={isSubmitting}
>
{t("common.cancel", { defaultValue: "Annuler" })}
</Button>
<Button
type="submit"
color="primary"
disabled={!name.trim() || isSubmitting}
>
{isSubmitting
? t("common.creating", { defaultValue: "Création..." })
: t("common.create", { defaultValue: "Créer" })}
</Button>
</div>
</form>
</Modal>
),
};
};

View File

@@ -1,441 +0,0 @@
import { Modal, Button, Input, TextArea, Select } from '@openfun/cunningham-react';
import type { IcsEvent, IcsRecurrenceRule } from 'ts-ics';
import { useState, useEffect, useRef } from "react";
import { RecurrenceEditor } from './RecurrenceEditor';
// Helper function to check if event is all-day (same logic as open-dav-calendar)
function isEventAllDay(event: IcsEvent): boolean {
return event.start.type === 'DATE' || (event.end?.type === 'DATE');
}
// Calendar type from open-calendar (exported for use in other components)
export interface Calendar {
url: string;
uid?: unknown;
displayName?: string;
calendarColor?: string;
description?: string;
}
interface EventModalProps {
isOpen: boolean;
mode: 'create' | 'edit';
calendars: Calendar[];
selectedEvent?: IcsEvent | null;
calendarUrl: string;
onSubmit: (event: IcsEvent, calendarUrl: string) => Promise<void>;
onAllDayChange: (isAllDay: boolean) => void;
onClose: () => void;
onDelete?: (event: IcsEvent, calendarUrl: string) => Promise<void>;
}
const VISIBILITY_OPTIONS = [
{ value: 'default', label: 'Par défaut' },
{ value: 'public', label: 'Public' },
{ value: 'private', label: 'Privé' },
] as const;
export function EventModal({
isOpen,
mode,
calendars,
selectedEvent,
calendarUrl,
onSubmit,
onAllDayChange,
onClose,
onDelete,
}: EventModalProps) {
const titleInputRef = useRef<HTMLInputElement>(null);
const [formData, setFormData] = useState<Partial<IcsEvent & { startTime?: string; endTime?: string }>>({});
const [activeFeatures, setActiveFeatures] = useState<Set<string>>(new Set());
const [selectedCalendarUrl, setSelectedCalendarUrl] = useState<string>(calendarUrl);
const [allDay, setAllDay] = useState<boolean>(false);
// Helper to get local date from IcsDateObject
const getLocalDate = (dateObj: { date: Date; local?: { date: Date; timezone: string } }): Date => {
return dateObj.local?.date || dateObj.date;
};
// Format date for input
const formatDateForInput = (date: Date): string => {
return date.toISOString().split('T')[0];
};
// Format time for input
const formatTimeForInput = (date: Date): string => {
return date.toTimeString().slice(0, 5);
};
useEffect(() => {
if (selectedEvent) {
const eventAllDay = isEventAllDay(selectedEvent);
const startDate = getLocalDate(selectedEvent.start);
const endDate = selectedEvent.end ? getLocalDate(selectedEvent.end) : startDate;
setAllDay(eventAllDay);
setFormData({
...selectedEvent,
summary: selectedEvent.summary || '',
startTime: eventAllDay ? undefined : formatTimeForInput(startDate),
endTime: eventAllDay ? undefined : formatTimeForInput(endDate),
});
setSelectedCalendarUrl(calendarUrl);
} else {
const now = new Date();
const endTime = new Date(now.getTime() + 30 * 60 * 1000);
setAllDay(false);
setFormData({
summary: '',
start: {
type: 'DATE-TIME',
date: now,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
},
end: {
type: 'DATE-TIME',
date: endTime,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
},
startTime: formatTimeForInput(now),
endTime: formatTimeForInput(endTime),
});
setSelectedCalendarUrl(calendars[0]?.url || '');
}
}, [selectedEvent, calendars, calendarUrl]);
useEffect(() => {
if (isOpen) {
requestAnimationFrame(() => {
titleInputRef.current?.focus();
});
}
}, [isOpen]);
const startDate = formData.start ? getLocalDate(formData.start) : new Date();
const endDate = formData.end ? getLocalDate(formData.end) : startDate;
const toggleFeature = (feature: string) => {
if (feature === 'allDay') {
const newAllDay = !allDay;
setAllDay(newAllDay);
onAllDayChange(newAllDay);
return;
}
setActiveFeatures(prev => {
const next = new Set(prev);
if (next.has(feature)) {
next.delete(feature);
} else {
next.add(feature);
}
return next;
});
};
const handleSubmit = async () => {
if (!formData.summary) return;
const startDateValue = new Date(`${formatDateForInput(startDate)}T${allDay ? '00:00:00' : formData.startTime || '00:00:00'}`);
const endDateValue = new Date(`${formatDateForInput(endDate)}T${allDay ? '00:00:00' : formData.endTime || '00:00:00'}`);
const updatedEvent: IcsEvent = {
...(selectedEvent || {}),
uid: selectedEvent?.uid || `event-${Date.now()}`,
summary: formData.summary || '',
location: formData.location || undefined,
description: formData.description || formData.notes || undefined,
start: {
type: allDay ? 'DATE' : 'DATE-TIME',
date: startDateValue,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
},
end: {
type: allDay ? 'DATE' : 'DATE-TIME',
date: endDateValue,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
},
recurrenceRule: formData.recurrenceRule,
};
await onSubmit(updatedEvent, selectedCalendarUrl);
};
const renderFeatureContent = () => {
return (
<>
{activeFeatures.has('calendar') && (
<div className="event-modal-layout event-modal-layout--margin-bottom-1rem">
<Select
label="Calendrier"
name="calendar-select"
value={selectedCalendarUrl}
onChange={(e) => setSelectedCalendarUrl(e.target.value)}
options={calendars.map(cal => ({
value: cal.url,
label: cal.displayName || cal.url
}))}
required
/>
</div>
)}
{activeFeatures.has('repeat') && (
<div className="event-modal-layout event-modal-layout--margin-bottom-1rem">
<RecurrenceEditor
value={formData.recurrenceRule}
onChange={(recurrenceRule) => {
setFormData(prev => ({ ...prev, recurrenceRule }));
}}
/>
</div>
)}
{activeFeatures.has('location') && (
<div className="event-modal-layout event-modal-layout--margin-bottom-1rem">
<Input
label="Lieu"
placeholder="Ajouter un lieu"
value={formData.location || ''}
onChange={(e) => setFormData(prev => ({ ...prev, location: e.target.value }))}
icon={<span className="material-icons">location_on</span>}
/>
</div>
)}
{activeFeatures.has('notes') && (
<div className="event-modal-layout event-modal-layout--margin-bottom-1rem">
<TextArea
label="Notes"
placeholder="Ajouter des notes"
value={formData.description || formData.notes || ''}
onChange={(e) => setFormData(prev => ({ ...prev, description: e.target.value, notes: e.target.value }))}
rows={4}
/>
</div>
)}
</>
);
};
const selectedCalendar = calendars.find(cal => cal.url === selectedCalendarUrl);
return (
<Modal
isOpen={isOpen}
onClose={onClose}
title={mode === 'create' ? 'Créer un événement' : 'Modifier l\'événement'}
>
<div className="event-modal-layout event-modal-layout--gap-2rem">
<Input
label="Titre"
value={formData.summary || ''}
onChange={(e) => setFormData(prev => ({ ...prev, summary: e.target.value }))}
ref={titleInputRef}
/>
<div className="event-modal-layout event-modal-layout--row event-modal-layout--gap-1rem">
<div className="event-modal-layout event-modal-layout--flex-1">
<Input
type="date"
label="Date de début"
name="event-start-date"
value={formatDateForInput(startDate)}
onChange={(e) => {
const newDate = new Date(e.target.value);
const currentTime = allDay ? '00:00:00' : (formData.startTime || formatTimeForInput(startDate));
const [hours, minutes] = currentTime.split(':');
newDate.setHours(parseInt(hours || '0'), parseInt(minutes || '0'));
setFormData(prev => ({
...prev,
start: {
type: allDay ? 'DATE' : 'DATE-TIME',
date: newDate,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
},
startTime: allDay ? undefined : currentTime,
}));
}}
required
/>
</div>
{!allDay && (
<div className="event-modal-layout event-modal-layout--flex-1">
<Input
type="time"
label="Heure de début"
name="event-start-time"
value={formData.startTime || formatTimeForInput(startDate)}
onChange={(e) => {
const [hours, minutes] = e.target.value.split(':');
const newDate = new Date(startDate);
newDate.setHours(parseInt(hours || '0'), parseInt(minutes || '0'));
setFormData(prev => ({
...prev,
start: {
type: 'DATE-TIME',
date: newDate,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
},
startTime: e.target.value,
}));
}}
required
/>
</div>
)}
</div>
<div className="event-modal-layout event-modal-layout--row event-modal-layout--gap-1rem">
<div className="event-modal-layout event-modal-layout--flex-1">
<Input
type="date"
label="Date de fin"
name="event-end-date"
value={formatDateForInput(endDate)}
onChange={(e) => {
const newDate = new Date(e.target.value);
const currentTime = allDay ? '00:00:00' : (formData.endTime || formatTimeForInput(endDate));
const [hours, minutes] = currentTime.split(':');
newDate.setHours(parseInt(hours || '0'), parseInt(minutes || '0'));
setFormData(prev => ({
...prev,
end: {
type: allDay ? 'DATE' : 'DATE-TIME',
date: newDate,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
},
endTime: allDay ? undefined : currentTime,
}));
}}
required
/>
</div>
{!allDay && (
<div className="event-modal-layout event-modal-layout--flex-1">
<Input
type="time"
label="Heure de fin"
name="event-end-time"
value={formData.endTime || formatTimeForInput(endDate)}
onChange={(e) => {
const [hours, minutes] = e.target.value.split(':');
const newDate = new Date(endDate);
newDate.setHours(parseInt(hours || '0'), parseInt(minutes || '0'));
setFormData(prev => ({
...prev,
end: {
type: 'DATE-TIME',
date: newDate,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
},
endTime: e.target.value,
}));
}}
required
/>
</div>
)}
</div>
{renderFeatureContent()}
<div className="event-modal-layout event-modal-layout--row event-modal-layout--gap-0-5rem event-modal-layout--wrap">
<button
className={`event-modal__feature-tag ${activeFeatures.has('calendar') ? 'event-modal__feature-tag--active' : ''}`}
onClick={() => toggleFeature('calendar')}
style={selectedCalendar?.calendarColor ? {
backgroundColor: selectedCalendar.calendarColor,
color: 'white',
} : undefined}
>
<span className="material-icons">event_note</span>
{selectedCalendar?.displayName || selectedCalendar?.url || 'Calendrier'}
</button>
<button
className={`event-modal__feature-tag ${activeFeatures.has('visibility') ? 'event-modal__feature-tag--active' : ''}`}
onClick={() => toggleFeature('visibility')}
>
<span className="material-icons">visibility</span>
Visibilité
</button>
<button
className={`event-modal__feature-tag ${activeFeatures.has('repeat') ? 'event-modal__feature-tag--active' : ''}`}
onClick={() => toggleFeature('repeat')}
>
<span className="material-icons">repeat</span>
Répéter
</button>
<button
className={`event-modal__feature-tag ${allDay ? 'event-modal__feature-tag--active' : ''}`}
onClick={() => toggleFeature('allDay')}
>
<span className="material-icons">today</span>
Toute la journée
</button>
<button
className={`event-modal__feature-tag ${activeFeatures.has('invites') ? 'event-modal__feature-tag--active' : ''}`}
onClick={() => toggleFeature('invites')}
>
<span className="material-icons">people</span>
Invités
</button>
<button
className={`event-modal__feature-tag ${activeFeatures.has('location') ? 'event-modal__feature-tag--active' : ''}`}
onClick={() => toggleFeature('location')}
>
<span className="material-icons">location_on</span>
Lieu
</button>
<button
className={`event-modal__feature-tag ${activeFeatures.has('visio') ? 'event-modal__feature-tag--active' : ''}`}
onClick={() => toggleFeature('visio')}
>
<span className="material-icons">videocam</span>
Visio
</button>
<button
className={`event-modal__feature-tag ${activeFeatures.has('notification') ? 'event-modal__feature-tag--active' : ''}`}
onClick={() => toggleFeature('notification')}
>
<span className="material-icons">notifications</span>
Rappel
</button>
<button
className={`event-modal__feature-tag ${activeFeatures.has('notes') ? 'event-modal__feature-tag--active' : ''}`}
onClick={() => toggleFeature('notes')}
>
<span className="material-icons">notes</span>
Notes
</button>
</div>
<div className="event-modal-layout event-modal-layout--row event-modal-layout--justify-space-between event-modal-layout--margin-top-2rem">
{mode === 'edit' && selectedEvent && (
<Button
type="button"
color="danger"
onClick={() => onDelete?.(selectedEvent, selectedCalendarUrl)}
icon={<span className="material-icons">delete</span>}
>
Supprimer
</Button>
)}
<div className="event-modal-layout event-modal-layout--row event-modal-layout--gap-1rem event-modal-layout--margin-left-auto">
<Button
type="button"
color="secondary"
onClick={onClose}
>
Annuler
</Button>
<Button onClick={handleSubmit}>
{mode === 'create' ? 'Créer' : 'Enregistrer'}
</Button>
</div>
</div>
</div>
</Modal>
);
}

View File

@@ -1,115 +0,0 @@
/**
* Adapter that bridges open-calendar's EventEditHandlers with React state.
* The handlers update state, and the modal is rendered normally in React tree.
*/
import type { IcsEvent } from 'ts-ics';
import type { OpenCalendar } from '../hooks/useEventModal';
interface EventEditHandlers {
onCreateEvent: (info: EventEditCreateInfo) => void;
onSelectEvent: (info: EventEditSelectInfo) => void;
onMoveResizeEvent: (info: EventEditMoveResizeInfo) => void;
onDeleteEvent: (info: EventEditDeleteInfo) => void;
}
interface EventEditCreateInfo {
jsEvent: Event;
userContact?: any;
event: IcsEvent;
calendars: OpenCalendar[];
vCards: any[];
handleCreate: (event: { calendarUrl: string; event: IcsEvent }) => Promise<Response>;
}
interface EventEditSelectInfo {
jsEvent: Event;
userContact?: any;
calendarUrl: string;
event: IcsEvent;
recurringEvent?: IcsEvent;
calendars: OpenCalendar[];
vCards: any[];
handleUpdate: (event: { calendarUrl: string; event: IcsEvent }) => Promise<Response>;
handleDelete: (event: { calendarUrl: string; event: IcsEvent }) => Promise<Response>;
}
interface EventEditMoveResizeInfo {
jsEvent: Event;
calendarUrl: string;
userContact?: any;
event: IcsEvent;
recurringEvent?: IcsEvent;
start: Date;
end: Date;
handleUpdate: (event: { calendarUrl: string; event: IcsEvent }) => Promise<Response>;
}
interface EventEditDeleteInfo {
jsEvent: Event;
calendarUrl: string;
userContact?: any;
event: IcsEvent;
recurringEvent?: IcsEvent;
handleDelete: (event: { calendarUrl: string; event: IcsEvent }) => Promise<Response>;
}
export interface ModalState {
isOpen: boolean;
mode: 'create' | 'edit';
event: IcsEvent | null;
calendarUrl: string;
calendars: OpenCalendar[];
handleSave: ((event: { calendarUrl: string; event: IcsEvent }) => Promise<Response>) | null;
handleDelete: ((event: { calendarUrl: string; event: IcsEvent }) => Promise<Response>) | null;
}
/**
* Creates EventEditHandlers that update the provided state setter.
*/
export function createEventModalHandlers(
setState: (state: ModalState) => void,
calendars: OpenCalendar[] | (() => OpenCalendar[])
): EventEditHandlers {
const getCalendars = (): OpenCalendar[] => {
return typeof calendars === 'function' ? calendars() : calendars;
};
return {
onCreateEvent: ({ event, calendars: calList, handleCreate }: EventEditCreateInfo) => {
setState({
isOpen: true,
mode: 'create',
event,
calendarUrl: calList[0]?.url || '',
calendars: calList,
handleSave: handleCreate,
handleDelete: null,
});
},
onSelectEvent: ({ calendarUrl, event, calendars: calList, handleUpdate, handleDelete }: EventEditSelectInfo) => {
setState({
isOpen: true,
mode: 'edit',
event,
calendarUrl,
calendars: calList,
handleSave: handleUpdate,
handleDelete,
});
},
onMoveResizeEvent: ({ calendarUrl, event, start, end, handleUpdate }: EventEditMoveResizeInfo) => {
const newEvent = { ...event };
const startDelta = start.getTime() - event.start.date.getTime();
newEvent.start = { ...newEvent.start, date: new Date(event.start.date.getTime() + startDelta) };
if (event.end) {
const endDelta = end.getTime() - event.end.date.getTime();
newEvent.end = { ...newEvent.end, date: new Date(event.end.date.getTime() + endDelta) };
}
handleUpdate({ calendarUrl, event: newEvent });
},
onDeleteEvent: ({ calendarUrl, event, handleDelete }: EventEditDeleteInfo) => {
handleDelete({ calendarUrl, event });
},
};
}