From 59e196fcb24ff0015cf77534f83b8af5bcc8dbfa Mon Sep 17 00:00:00 2001 From: Arthur Belleville Date: Sun, 19 Oct 2025 11:34:51 +0200 Subject: [PATCH] Improve flow for event types --- ui/src/components/NavigationBar.tsx | 41 ++------- ui/src/components/TabloModal.tsx | 2 +- ui/src/components/ui/checkbox.tsx | 48 ++++++----- ui/src/components/ui/clipboard.tsx | 2 +- ui/src/components/ui/hooks/use-clipboard.ts | 34 ++++++++ ui/src/hooks/availabilities.ts | 58 ++++++++----- ui/src/pages/availabilities.tsx | 95 +++++++++++---------- ui/src/ui-library/hooks/use-clipboard.ts | 32 ------- 8 files changed, 157 insertions(+), 155 deletions(-) create mode 100644 ui/src/components/ui/hooks/use-clipboard.ts diff --git a/ui/src/components/NavigationBar.tsx b/ui/src/components/NavigationBar.tsx index 8dc876a..3f93dac 100644 --- a/ui/src/components/NavigationBar.tsx +++ b/ui/src/components/NavigationBar.tsx @@ -9,15 +9,11 @@ import { DropdownMenuTrigger, } from "@ui/components/ui/dropdown-menu"; import { useUser } from "@ui/providers/UserStoreProvider"; -// react-aria components (still used) -import { Disclosure, DisclosureControl, DisclosurePanel } from "@ui/ui-library/disclosure"; -import { Link } from "@ui/ui-library/link"; import { isProd, isStaging } from "@ui/utils/helpers"; import { getXtabloIcon } from "@ui/utils/iconHelpers"; import { CalendarCheckIcon, CalendarIcon, - ChevronRightIcon, Circle, ConstructionIcon, Kanban, @@ -32,7 +28,7 @@ import { SquareKanban, } from "lucide-react"; import { useState } from "react"; -import { LinkProps, Separator } from "react-aria-components"; +import { Separator } from "react-aria-components"; import { Link as RouterLink, useLocation } from "react-router-dom"; import { twMerge } from "tailwind-merge"; import { ThemeSwitcher } from "./ThemeSwitcher"; @@ -43,35 +39,14 @@ import { useLogout } from "src/hooks/auth"; type NavLinkItem = { isActive?: boolean; -} & LinkProps; + children: React.ReactNode; +}; -type NavLinkProps = NavLinkItem | { title: string; items: NavLinkItem[] }; +type NavLinkProps = NavLinkItem; -function NavLink(props: NavLinkProps) { - if ("items" in props) { - return ( - - - {props.title}{" "} - - - -
    - {props.items.map((item) => ( -
  • - -
  • - ))} -
-
-
- ); - } - - const { isActive, ...rest } = props; +function NavLink({ isActive, children }: NavLinkProps) { return ( - [data-ui=icon]:not([class*=size-])]:size-4.5", "[&>[data-ui=notification-badge]]:bg-navbar-darker", @@ -88,8 +63,8 @@ function NavLink(props: NavLinkProps) { : ["font-medium", "text-gray-300/90 [&:not(:hover)>[data-ui=icon]]:bg-navbar-darker"] )} > - {props.children} - + {children} + ); } diff --git a/ui/src/components/TabloModal.tsx b/ui/src/components/TabloModal.tsx index 04f6f37..55ebb0a 100644 --- a/ui/src/components/TabloModal.tsx +++ b/ui/src/components/TabloModal.tsx @@ -10,7 +10,7 @@ import { useTabloMembers } from "@ui/hooks/tablos"; import { toast } from "@ui/lib/toast"; import { useUser } from "@ui/providers/UserStoreProvider"; import { TabloUpdate, UserTablo } from "@ui/types/tablos.types"; -import { FileTrigger } from "@ui/ui-library/file-trigger"; +import { FileTrigger } from "react-aria-components"; import { DownloadIcon, Trash2Icon } from "lucide-react"; import { useEffect, useRef, useState } from "react"; import { ClickOutside } from "./ClickOutside"; diff --git a/ui/src/components/ui/checkbox.tsx b/ui/src/components/ui/checkbox.tsx index 1044025..651800b 100644 --- a/ui/src/components/ui/checkbox.tsx +++ b/ui/src/components/ui/checkbox.tsx @@ -1,25 +1,29 @@ -import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; -import { cn } from "@ui/lib/utils"; -import { Check } from "lucide-react"; -import * as React from "react"; +"use client"; -const Checkbox = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - - - - - -)); -Checkbox.displayName = CheckboxPrimitive.Root.displayName; +import * as React from "react"; +import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; +import { CheckIcon } from "lucide-react"; + +import { cn } from "@ui/lib/utils"; + +function Checkbox({ className, ...props }: React.ComponentProps) { + return ( + + + + + + ); +} export { Checkbox }; diff --git a/ui/src/components/ui/clipboard.tsx b/ui/src/components/ui/clipboard.tsx index a3663ed..f6c80d2 100644 --- a/ui/src/components/ui/clipboard.tsx +++ b/ui/src/components/ui/clipboard.tsx @@ -1,5 +1,5 @@ import { cn } from "@ui/lib/utils"; -import { useCopyToClipboard } from "@ui/ui-library/hooks/use-clipboard"; +import { useCopyToClipboard } from "@ui/components/ui/hooks/use-clipboard"; import { Check, Copy } from "lucide-react"; import React from "react"; import { Button, ButtonProps } from "./button"; diff --git a/ui/src/components/ui/hooks/use-clipboard.ts b/ui/src/components/ui/hooks/use-clipboard.ts new file mode 100644 index 0000000..ab0136c --- /dev/null +++ b/ui/src/components/ui/hooks/use-clipboard.ts @@ -0,0 +1,34 @@ +import React from "react"; + +export function useCopyToClipboard({ timeout = 2000 } = {}) { + const [error, setError] = React.useState(null); + const [copied, setCopied] = React.useState(false); + const [copyTimeout, setCopyTimeout] = React.useState(null); + + const handleCopyResult = (value: boolean) => { + window.clearTimeout(copyTimeout!); + setCopyTimeout(window.setTimeout(() => setCopied(false), timeout)); + setCopied(value); + }; + + const copy = (valueToCopy: string) => { + if ("clipboard" in navigator) { + navigator.clipboard + .writeText(valueToCopy) + .then(() => handleCopyResult(true)) + .catch((err) => setError(err)); + } else { + setError( + new Error("useCopyToClipboard: navigator.clipboard is not supported") + ); + } + }; + + const reset = () => { + setCopied(false); + setError(null); + window.clearTimeout(copyTimeout!); + }; + + return { copy, reset, error, copied }; +} diff --git a/ui/src/hooks/availabilities.ts b/ui/src/hooks/availabilities.ts index 73c8359..fef1bac 100644 --- a/ui/src/hooks/availabilities.ts +++ b/ui/src/hooks/availabilities.ts @@ -33,20 +33,23 @@ export type Exception = { const DAYS_OF_WEEK = [0, 1, 2, 3, 4, 5, 6]; -export const DEFAULT_AVAILABILITIES: WeeklyAvailability = DAYS_OF_WEEK.reduce((acc, day) => { - if (day === 5 || day === 6) { - acc[day] = { - enabled: false, - timeRanges: [{ start: "09:00", end: "17:00" }], - }; - } else { - acc[day] = { - enabled: true, - timeRanges: [{ start: "09:00", end: "17:00" }], - }; - } - return acc; -}, {} as WeeklyAvailability); +export const DEFAULT_AVAILABILITIES: WeeklyAvailability = DAYS_OF_WEEK.reduce( + (acc, day) => { + if (day === 5 || day === 6) { + acc[day] = { + enabled: false, + timeRanges: [{ start: "09:00", end: "17:00" }], + }; + } else { + acc[day] = { + enabled: true, + timeRanges: [{ start: "09:00", end: "17:00" }], + }; + } + return acc; + }, + {} as WeeklyAvailability +); export function useAvailabilities() { const { session } = useSession(); @@ -83,7 +86,8 @@ export function useAvailabilities() { newException?: Exception | null; }) => { const newAvailabilities = updatedAvailabilities; - const newExceptions = (availabilities?.exceptions as Exception[] | null) || []; + const newExceptions = + (availabilities?.exceptions as Exception[] | null) || []; if (newException) { newExceptions.push(newException); } @@ -104,14 +108,22 @@ export function useAvailabilities() { }, }); - const { mutate: deleteException } = useMutation({ + const { mutate: deleteException } = useMutation< + void, + Error, + { exceptionIndex: number } + >({ mutationFn: async ({ exceptionIndex }: { exceptionIndex: number }) => { - const currentExceptions = (availabilities?.exceptions as Exception[] | null) || []; - const updatedExceptions = currentExceptions.filter((_, index) => index !== exceptionIndex); + const currentExceptions = + (availabilities?.exceptions as Exception[] | null) || []; + const updatedExceptions = currentExceptions.filter( + (_, index) => index !== exceptionIndex + ); const { error } = await supabase.from("availabilities").upsert( { - availability_data: availabilities?.availability_data || DEFAULT_AVAILABILITIES, + availability_data: + availabilities?.availability_data || DEFAULT_AVAILABILITIES, exceptions: updatedExceptions, user_id: session?.user.id, }, @@ -126,11 +138,14 @@ export function useAvailabilities() { }, }); - const [draftAvailabilities, setDraftAvailabilities] = useState(null); + const [draftAvailabilities, setDraftAvailabilities] = + useState(null); useEffect(() => { if (availabilities?.availability_data) { - setDraftAvailabilities(availabilities.availability_data as WeeklyAvailability); + setDraftAvailabilities( + availabilities.availability_data as WeeklyAvailability + ); } }, [availabilities?.availability_data]); @@ -141,5 +156,6 @@ export function useAvailabilities() { setDraftAvailabilities, exceptions: (availabilities?.exceptions as Exception[] | null) || [], deleteException, + isModified: draftAvailabilities !== availabilities?.availability_data, }; } diff --git a/ui/src/pages/availabilities.tsx b/ui/src/pages/availabilities.tsx index 624f789..b728a44 100644 --- a/ui/src/pages/availabilities.tsx +++ b/ui/src/pages/availabilities.tsx @@ -26,11 +26,12 @@ import { WeeklyAvailability, } from "@ui/hooks/availabilities"; import { toast } from "@ui/lib/toast"; -import { Checkbox } from "@ui/ui-library/checkbox"; +import { Checkbox } from "@ui/components/ui/checkbox"; import { Plus as PlusIcon, SaveIcon } from "lucide-react"; import { useState } from "react"; import { ExceptionModal } from "src/components/ExceptionModal"; import { CardContent } from "src/components/ui/card"; +import { Label } from "src/components/ui/label"; const DAYS_OF_WEEK = [0, 1, 2, 3, 4, 5, 6]; const DAYS_OF_WEEK_DISPLAY = [ @@ -55,6 +56,7 @@ export function AvailabilitiesPage() { setDraftAvailabilities, exceptions, deleteException, + isModified, } = useAvailabilities(); const [copyModalOpen, setCopyModalOpen] = useState(false); @@ -118,38 +120,40 @@ export function AvailabilitiesPage() {
- + { + onSuccess: () => { + toast.add({ + title: "Succès", + description: "Disponibilités enregistrées avec succès", + type: "success", + }); + }, + onError: (err) => { + console.error(err); + toast.add({ + title: "Erreur", + description: "Erreur lors de l'enregistrement des disponibilités", + type: "error", + }); + }, + } + ); + }} + > + Enregistrer + + )}