- Custom toolbar replacing FullCalendar defaults (nav, today, view switcher) - Calendar sidebar with visibility toggles, color dots, add/edit support - CalendarForm dialog for creating/editing calendars with color swatches - EventForm updated to use calendar dropdown instead of color picker - CSS overrides: accent-tinted today highlight, now indicator, rounded event pills - Types updated for Calendar interface and mixed id types Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
100 lines
3.4 KiB
TypeScript
100 lines
3.4 KiB
TypeScript
import { useState } from 'react';
|
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
import { Plus, Pencil } from 'lucide-react';
|
|
import { toast } from 'sonner';
|
|
import api, { getErrorMessage } from '@/lib/api';
|
|
import type { Calendar } from '@/types';
|
|
import { useCalendars } from '@/hooks/useCalendars';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Checkbox } from '@/components/ui/checkbox';
|
|
import CalendarForm from './CalendarForm';
|
|
|
|
export default function CalendarSidebar() {
|
|
const queryClient = useQueryClient();
|
|
const { data: calendars = [] } = useCalendars();
|
|
const [showForm, setShowForm] = useState(false);
|
|
const [editingCalendar, setEditingCalendar] = useState<Calendar | null>(null);
|
|
|
|
const toggleMutation = useMutation({
|
|
mutationFn: async ({ id, is_visible }: { id: number; is_visible: boolean }) => {
|
|
await api.put(`/calendars/${id}`, { is_visible });
|
|
},
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['calendars'] });
|
|
queryClient.invalidateQueries({ queryKey: ['calendar-events'] });
|
|
},
|
|
onError: (error) => {
|
|
toast.error(getErrorMessage(error, 'Failed to update calendar'));
|
|
},
|
|
});
|
|
|
|
const handleToggle = (calendar: Calendar) => {
|
|
toggleMutation.mutate({ id: calendar.id, is_visible: !calendar.is_visible });
|
|
};
|
|
|
|
const handleEdit = (calendar: Calendar) => {
|
|
setEditingCalendar(calendar);
|
|
setShowForm(true);
|
|
};
|
|
|
|
const handleCloseForm = () => {
|
|
setShowForm(false);
|
|
setEditingCalendar(null);
|
|
};
|
|
|
|
return (
|
|
<div className="w-56 shrink-0 border-r bg-card flex flex-col">
|
|
<div className="p-4 border-b flex items-center justify-between">
|
|
<span className="text-sm font-semibold font-heading text-foreground">Calendars</span>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className="h-7 w-7"
|
|
onClick={() => { setEditingCalendar(null); setShowForm(true); }}
|
|
>
|
|
<Plus className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
<div className="flex-1 overflow-y-auto p-3 space-y-0.5">
|
|
{calendars.map((cal) => (
|
|
<div
|
|
key={cal.id}
|
|
className="group flex items-center gap-2.5 rounded-md px-2 py-1.5 hover:bg-card-elevated transition-colors duration-150"
|
|
>
|
|
<Checkbox
|
|
checked={cal.is_visible}
|
|
onChange={() => handleToggle(cal)}
|
|
className="shrink-0"
|
|
style={{
|
|
accentColor: cal.color,
|
|
borderColor: cal.is_visible ? cal.color : undefined,
|
|
backgroundColor: cal.is_visible ? cal.color : undefined,
|
|
}}
|
|
/>
|
|
<span
|
|
className="h-2.5 w-2.5 rounded-full shrink-0"
|
|
style={{ backgroundColor: cal.color }}
|
|
/>
|
|
<span className="text-sm text-foreground truncate flex-1">{cal.name}</span>
|
|
{!cal.is_system && (
|
|
<button
|
|
onClick={() => handleEdit(cal)}
|
|
className="opacity-0 group-hover:opacity-100 transition-opacity duration-150 text-muted-foreground hover:text-foreground"
|
|
>
|
|
<Pencil className="h-3.5 w-3.5" />
|
|
</button>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{showForm && (
|
|
<CalendarForm
|
|
calendar={editingCalendar}
|
|
onClose={handleCloseForm}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|