Enhance AvailabilityCard and AvailabilitiesPage components by adding a copy functionality for time ranges across days. Introduce a modal for selecting target days and confirm copying of availability settings, improving user experience and flexibility.
This commit is contained in:
parent
fab34e70cb
commit
d03022c21b
2 changed files with 180 additions and 1 deletions
|
|
@ -2,7 +2,7 @@ import { useState } from "react";
|
|||
import { Switch } from "@ui/ui-library/switch";
|
||||
import { Text } from "@ui/ui-library/text";
|
||||
import { Button } from "@ui/ui-library/button";
|
||||
import { MinusIcon, PlusIcon } from "@ui/ui-library/icons";
|
||||
import { MinusIcon, PlusIcon, CopyIcon } from "@ui/ui-library/icons";
|
||||
import {
|
||||
Select,
|
||||
SelectButton,
|
||||
|
|
@ -22,6 +22,11 @@ interface AvailabilityCardProps {
|
|||
onEnabledChange: (enabled: boolean) => void;
|
||||
timeRanges: TimeRange[];
|
||||
onTimeRangesChange: (ranges: TimeRange[]) => void;
|
||||
onCopyToOtherDays?: (
|
||||
sourceDay: number,
|
||||
enabled: boolean,
|
||||
timeRanges: TimeRange[]
|
||||
) => void;
|
||||
}
|
||||
|
||||
const MINUTES_IN_DAY = 24 * 60;
|
||||
|
|
@ -146,6 +151,7 @@ export function AvailabilityCard({
|
|||
onEnabledChange,
|
||||
timeRanges,
|
||||
onTimeRangesChange,
|
||||
onCopyToOtherDays,
|
||||
}: AvailabilityCardProps) {
|
||||
const dayDisplay = DAYS_OF_WEEK_DISPLAY[day];
|
||||
|
||||
|
|
@ -239,6 +245,17 @@ export function AvailabilityCard({
|
|||
<div className="flex flex-col gap-2 w-full">
|
||||
<div className="flex items-center justify-between">
|
||||
<Text className="text-lg font-semibold">{dayDisplay}</Text>
|
||||
{onCopyToOtherDays && enabled && timeRanges.length > 0 && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onPress={() => onCopyToOtherDays(day, enabled, timeRanges)}
|
||||
className="h-6 px-2 text-xs border-gray-300 hover:border-primary hover:bg-primary/5 text-gray-600 hover:text-primary"
|
||||
>
|
||||
<CopyIcon className="size-3 mr-1" />
|
||||
Copier
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch
|
||||
|
|
|
|||
|
|
@ -8,8 +8,77 @@ import {
|
|||
WeeklyAvailability,
|
||||
} from "@ui/hooks/availabilities";
|
||||
import { toast } from "@ui/ui-library/toast/toast-queue";
|
||||
import { useState } from "react";
|
||||
import { Checkbox } from "@ui/ui-library/checkbox";
|
||||
|
||||
// Custom Modal Component
|
||||
interface CustomModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
title: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
function CustomModal({ isOpen, onClose, title, children }: CustomModalProps) {
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||||
{/* Backdrop */}
|
||||
<div
|
||||
className="absolute inset-0 bg-black/50 backdrop-blur-sm"
|
||||
onClick={onClose}
|
||||
/>
|
||||
|
||||
{/* Modal */}
|
||||
<div className="relative bg-white dark:bg-gray-800 rounded-xl shadow-2xl border border-gray-200 dark:border-gray-700 w-full max-w-md mx-4 max-h-[90vh] overflow-hidden">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between p-6 border-b border-gray-200 dark:border-gray-700">
|
||||
<h2 className="text-xl font-semibold text-gray-900 dark:text-gray-100">
|
||||
{title}
|
||||
</h2>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="p-1 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
|
||||
>
|
||||
<svg
|
||||
className="w-5 h-5 text-gray-500"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="p-6">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const DAYS_OF_WEEK = [0, 1, 2, 3, 4, 5, 6];
|
||||
const DAYS_OF_WEEK_DISPLAY = [
|
||||
"Lundi",
|
||||
"Mardi",
|
||||
"Mercredi",
|
||||
"Jeudi",
|
||||
"Vendredi",
|
||||
"Samedi",
|
||||
"Dimanche",
|
||||
];
|
||||
|
||||
interface TimeRange {
|
||||
start: string;
|
||||
end: string;
|
||||
}
|
||||
|
||||
export function AvailabilitiesPage() {
|
||||
const {
|
||||
|
|
@ -19,6 +88,47 @@ export function AvailabilitiesPage() {
|
|||
setDraftAvailabilities,
|
||||
} = useAvailabilities();
|
||||
|
||||
const [copyModalOpen, setCopyModalOpen] = useState(false);
|
||||
const [sourceDayData, setSourceDayData] = useState<{
|
||||
day: number;
|
||||
enabled: boolean;
|
||||
timeRanges: TimeRange[];
|
||||
} | null>(null);
|
||||
const [selectedDays, setSelectedDays] = useState<number[]>([]);
|
||||
|
||||
const handleCopyToOtherDays = (
|
||||
sourceDay: number,
|
||||
enabled: boolean,
|
||||
timeRanges: TimeRange[]
|
||||
) => {
|
||||
setSourceDayData({ day: sourceDay, enabled, timeRanges });
|
||||
setSelectedDays([]);
|
||||
setCopyModalOpen(true);
|
||||
};
|
||||
|
||||
const applyCopyToSelectedDays = () => {
|
||||
if (!sourceDayData) return;
|
||||
|
||||
const updatedAvailabilities = { ...draftAvailabilities };
|
||||
selectedDays.forEach((day) => {
|
||||
updatedAvailabilities[day] = {
|
||||
enabled: sourceDayData.enabled,
|
||||
timeRanges: [...sourceDayData.timeRanges],
|
||||
};
|
||||
});
|
||||
|
||||
setDraftAvailabilities(updatedAvailabilities);
|
||||
setCopyModalOpen(false);
|
||||
setSourceDayData(null);
|
||||
setSelectedDays([]);
|
||||
|
||||
toast.add({
|
||||
title: "Succès",
|
||||
description: `Horaires copiés vers ${selectedDays.length} jour(s)`,
|
||||
type: "success",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col p-4">
|
||||
<div className="flex justify-between items-start mb-3">
|
||||
|
|
@ -91,6 +201,7 @@ export function AvailabilitiesPage() {
|
|||
},
|
||||
});
|
||||
}}
|
||||
onCopyToOtherDays={handleCopyToOtherDays}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -164,6 +275,57 @@ export function AvailabilitiesPage() {
|
|||
{isUpdating ? "Enregistrement..." : "Enregistrer les disponibilités"}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Copy Modal */}
|
||||
<CustomModal
|
||||
isOpen={copyModalOpen}
|
||||
onClose={() => setCopyModalOpen(false)}
|
||||
title={`Copier les horaires de ${
|
||||
sourceDayData ? DAYS_OF_WEEK_DISPLAY[sourceDayData.day] : ""
|
||||
}`}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<Text className="text-gray-600 dark:text-gray-400">
|
||||
Sélectionnez les jours vers lesquels vous souhaitez copier ces
|
||||
horaires :
|
||||
</Text>
|
||||
|
||||
<div className="space-y-3 max-h-60 overflow-y-auto">
|
||||
{DAYS_OF_WEEK.filter((day) => day !== sourceDayData?.day).map(
|
||||
(day) => (
|
||||
<Checkbox
|
||||
key={day}
|
||||
isSelected={selectedDays.includes(day)}
|
||||
onChange={(isSelected) => {
|
||||
if (isSelected) {
|
||||
setSelectedDays([...selectedDays, day]);
|
||||
} else {
|
||||
setSelectedDays(selectedDays.filter((d) => d !== day));
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Text className="font-medium">
|
||||
{DAYS_OF_WEEK_DISPLAY[day]}
|
||||
</Text>
|
||||
</Checkbox>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-3 pt-4 border-t border-gray-200 dark:border-gray-700">
|
||||
<Button variant="outline" onPress={() => setCopyModalOpen(false)}>
|
||||
Annuler
|
||||
</Button>
|
||||
<Button
|
||||
variant="solid"
|
||||
isDisabled={selectedDays.length === 0}
|
||||
onPress={applyCopyToSelectedDays}
|
||||
>
|
||||
Copier vers {selectedDays.length} jour(s)
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CustomModal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue