From 23840319c25a7b33e2f8c0fb1343b06ec55eb9ea Mon Sep 17 00:00:00 2001 From: Arthur Belleville Date: Thu, 16 Oct 2025 18:56:49 +0200 Subject: [PATCH] Improve UI --- ui/src/components/AvailabilityCard.tsx | 146 ++++++------------- ui/src/components/EventModal.tsx | 188 +++++++++--------------- ui/src/components/EventTypeCard.tsx | 6 +- ui/src/components/EventTypeModal.tsx | 4 +- ui/src/components/ThemeSwitcher.tsx | 93 ++++++++---- ui/src/components/ui/button.tsx | 3 +- ui/src/components/ui/dialog.tsx | 2 +- ui/src/components/ui/time-input.tsx | 5 + ui/src/contexts/ThemeContext.tsx | 66 ++++++--- ui/src/main.css | 194 ++++++++++++------------- ui/src/pages/tablo.tsx | 27 ++-- 11 files changed, 338 insertions(+), 396 deletions(-) diff --git a/ui/src/components/AvailabilityCard.tsx b/ui/src/components/AvailabilityCard.tsx index 7d389b9..675788c 100644 --- a/ui/src/components/AvailabilityCard.tsx +++ b/ui/src/components/AvailabilityCard.tsx @@ -1,16 +1,8 @@ import { Button } from "@ui/components/ui/button"; import { Card, CardAction, CardContent, CardHeader, CardTitle } from "@ui/components/ui/card"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@ui/components/ui/select"; import { Switch } from "@ui/components/ui/switch"; -import { useTimePicker } from "@ui/ui-library/time-picker"; +import { TimeInput } from "@ui/components/ui/time-input"; import { Copy as CopyIcon, Minus as MinusIcon, Plus as PlusIcon } from "lucide-react"; -import { useState } from "react"; interface TimeRange { start: string; @@ -59,8 +51,6 @@ export function AvailabilityCard({ }: AvailabilityCardProps) { const dayDisplay = DAYS_OF_WEEK_DISPLAY[day]; - const [selectedRangeIndex, setSelectedRangeIndex] = useState(0); - const handleAddRange = () => { // Find a free slot for the new range const sortedRanges = [...timeRanges].sort( @@ -100,17 +90,13 @@ export function AvailabilityCard({ const newRanges = [...timeRanges, { start: newStart, end: newEnd }]; onTimeRangesChange(newRanges); - setSelectedRangeIndex(newRanges.length - 1); }; const handleDeleteRange = (index: number) => { const newRanges = timeRanges.filter((_, i) => i !== index); onTimeRangesChange(newRanges); - setSelectedRangeIndex(Math.min(selectedRangeIndex, newRanges.length - 1)); }; - const timeOptions = useTimePicker({ intervalInMinute: 30 }); - const validateTimeRange = (ranges: TimeRange[], index: number): boolean => { const currentRange = ranges[index]; const currentStart = timeToMinutes(currentRange.start); @@ -171,93 +157,54 @@ export function AvailabilityCard({ {/* Time Ranges */} -
+
{timeRanges.map((range, index) => ( -
setSelectedRangeIndex(index)} - className={`flex items-center gap-1 rounded-md px-1.5 py-1 cursor-pointer transition-all duration-200 ${ - selectedRangeIndex === index ? "bg-primary/10" : "bg-muted/80 hover:bg-muted" - }`} - > -
- {selectedRangeIndex === index ? ( - <> - - - - - - ) : ( - <> - {range.start} - - {range.end} - - )} -
+
+ { + const newRanges = [...timeRanges]; + newRanges[index] = { + ...range, + start: value, + }; + if (validateTimeRange(newRanges, index)) { + onTimeRangesChange(newRanges); + } + }} + isDisabled={!enabled} + className="h-8 text-sm max-w-32" + id={`start-${day}-${index}`} + /> + - + { + const newRanges = [...timeRanges]; + newRanges[index] = { + ...range, + end: value, + }; + if (validateTimeRange(newRanges, index)) { + onTimeRangesChange(newRanges); + } + }} + isDisabled={!enabled} + className="h-8 text-sm max-w-32" + id={`end-${day}-${index}`} + /> {timeRanges.length > 1 && ( )}
@@ -268,9 +215,10 @@ export function AvailabilityCard({ disabled={!enabled} variant="outline" size="sm" - className="h-5 px-1.5 flex items-center text-xs border-0 bg-muted/50 hover:bg-muted" + className="h-8 px-3 flex items-center text-sm" > - + + Ajouter une plage horaire )}
diff --git a/ui/src/components/EventModal.tsx b/ui/src/components/EventModal.tsx index 07302f7..40ab50b 100644 --- a/ui/src/components/EventModal.tsx +++ b/ui/src/components/EventModal.tsx @@ -1,5 +1,16 @@ import { getLocalTimeZone, parseDate, today } from "@internationalized/date"; +import { Button } from "@ui/components/ui/button"; import { DatePicker } from "@ui/components/ui/date-picker"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@ui/components/ui/dialog"; +import { Input } from "@ui/components/ui/input"; +import { Label } from "@ui/components/ui/label"; import { Select, SelectContent, @@ -7,13 +18,13 @@ import { SelectTrigger, SelectValue, } from "@ui/components/ui/select"; +import { Textarea } from "@ui/components/ui/textarea"; +import { TimeInput } from "@ui/components/ui/time-input"; import { useCreateEvents, useEvent, useUpdateEvent } from "@ui/hooks/events"; import { useTablosList } from "@ui/hooks/tablos"; import { useUser } from "@ui/providers/UserStoreProvider"; import { Event, EventInsert } from "@ui/types/events.types"; -import { useTimePicker } from "@ui/ui-library/time-picker"; import { useEffect, useState } from "react"; -import { Group } from "react-aria-components"; import { useNavigate, useParams, useSearchParams } from "react-router-dom"; export const EventModal = ({ mode }: { mode: "create" | "edit" }) => { @@ -29,7 +40,6 @@ export const EventModal = ({ mode }: { mode: "create" | "edit" }) => { const { data: tablos, isLoading: tablosLoading } = useTablosList(); const createEvents = useCreateEvents(); const updateEvent = useUpdateEvent(); - const timeOptions = useTimePicker({ intervalInMinute: 15 }); const navigate = useNavigate(); const onClose = () => { @@ -44,31 +54,20 @@ export const EventModal = ({ mode }: { mode: "create" | "edit" }) => { return `${year}-${month}-${day}`; }; - // Find the nearest time option to the selected date - const getNearestTimeOption = (date: Date, type: "start" | "end") => { - const dateMinutes = date.getHours() * 60 + date.getMinutes(); - - let nearestOption = timeOptions[0]; - let smallestDiff = Infinity; - - for (const option of timeOptions) { - const optionMinutes = option.hour * 60 + option.minute; - const diff = - type === "start" ? Math.abs(dateMinutes - optionMinutes) : dateMinutes + 30 - optionMinutes; - - if (0 <= diff && diff < smallestDiff) { - smallestDiff = diff; - nearestOption = option; - } - } - - return nearestOption?.id || ""; + // Format time from Date to HH:MM string + const formatTimeFromDate = (date: Date, addMinutes: number = 0): string => { + const hours = date.getHours(); + const minutes = date.getMinutes() + addMinutes; + const totalMinutes = hours * 60 + minutes; + const finalHours = Math.floor(totalMinutes / 60) % 24; + const finalMinutes = totalMinutes % 60; + return `${finalHours.toString().padStart(2, "0")}:${finalMinutes.toString().padStart(2, "0")}`; }; const [formEvent, setFormEvent] = useState({ start_date: date ? getLocalDateString(date) : "", - start_time: date ? getNearestTimeOption(date, "start") : "", - end_time: date ? getNearestTimeOption(date, "end") : "", + start_time: date ? formatTimeFromDate(date) : "", + end_time: date ? formatTimeFromDate(date, 30) : "", tablo_id: tablo_id || "", title: "", created_by: user.id, @@ -90,30 +89,11 @@ export const EventModal = ({ mode }: { mode: "create" | "edit" }) => { }, [mode, event]); return ( -
-
- {/* Header with colored accent */} -
-
-

- {mode === "edit" ? "Modifier l'événement" : "Nouvel événement"} -

- -
-
+ + + + {mode === "edit" ? "Modifier l'événement" : "Nouvel événement"} + {mode === "edit" && event ? new Date(event.start_date).toLocaleDateString("fr-FR", { weekday: "long", @@ -127,14 +107,15 @@ export const EventModal = ({ mode }: { mode: "create" | "edit" }) => { month: "long", day: "numeric", })} -
-
+ + - {/* Form Content */} -
+
{/* Title Input */}
- Titre * + @@ -143,19 +124,14 @@ export const EventModal = ({ mode }: { mode: "create" | "edit" }) => { title: e.target.value, } as Event) } - className="w-full text-lg font-medium border-none outline-none bg-transparent text-gray-900 dark:text-white placeholder-gray-400 focus:ring-0 px-0" placeholder="Ajouter un titre" - aria-label="Titre de l'événement" autoFocus /> -
{/* Tablo Selection */}
- +
- -
- +
+
+ { }); } }} - buttonClassName="h-[36px]" + buttonClassName="h-10 w-full" />
-
- - + className="w-full" + id="event-start-time" + />
-
- - + className="w-full" + id="event-end-time" + />
- +
{/* Description */}
- -