127 lines
5.1 KiB
TypeScript
127 lines
5.1 KiB
TypeScript
import { WeeklyAvailability } from "@ui/hooks/availabilities";
|
|
import { Text } from "@ui/ui-library/text";
|
|
|
|
// Check if a time slot is available for a given day
|
|
const isTimeSlotAvailable = (
|
|
day: number,
|
|
timeSlot: string,
|
|
draftAvailabilities: WeeklyAvailability
|
|
): boolean => {
|
|
const dayAvailability = draftAvailabilities[day];
|
|
if (!dayAvailability.enabled) return false;
|
|
|
|
const slotMinutes = timeToMinutes(timeSlot);
|
|
return dayAvailability.timeRanges.some((range) => {
|
|
const startMinutes = timeToMinutes(range.start);
|
|
const endMinutes = timeToMinutes(range.end);
|
|
return slotMinutes >= startMinutes && slotMinutes < endMinutes;
|
|
});
|
|
};
|
|
|
|
// Helper function to convert time string to minutes
|
|
const timeToMinutes = (time: string): number => {
|
|
const [hours, minutes] = time.split(":").map(Number);
|
|
return hours * 60 + minutes;
|
|
};
|
|
|
|
// Helper function to convert minutes to time string
|
|
const minutesToTime = (minutes: number): string => {
|
|
const hours = Math.floor(minutes / 60);
|
|
const mins = minutes % 60;
|
|
return `${hours.toString().padStart(2, "0")}:${mins
|
|
.toString()
|
|
.padStart(2, "0")}`;
|
|
};
|
|
|
|
// Generate time slots for visualization based on duration
|
|
const generateTimeSlots = (intervalMinutes: number = 30) => {
|
|
const slots = [];
|
|
for (let hour = 0; hour < 24; hour++) {
|
|
for (let minute = 0; minute < 60; minute += intervalMinutes) {
|
|
slots.push(minutesToTime(hour * 60 + minute));
|
|
}
|
|
}
|
|
return slots;
|
|
};
|
|
|
|
const DAYS_OF_WEEK = [0, 1, 2, 3, 4, 5, 6];
|
|
const DAYS_OF_WEEK_DISPLAY = [
|
|
"Lundi",
|
|
"Mardi",
|
|
"Mercredi",
|
|
"Jeudi",
|
|
"Vendredi",
|
|
"Samedi",
|
|
"Dimanche",
|
|
];
|
|
|
|
export const AvailabilityVisualization = ({
|
|
draftAvailabilities,
|
|
slotDurationMinutes = 30,
|
|
}: {
|
|
draftAvailabilities: WeeklyAvailability;
|
|
slotDurationMinutes?: number;
|
|
}) => {
|
|
const timeSlots = generateTimeSlots(slotDurationMinutes);
|
|
return (
|
|
<div className="bg-white dark:bg-gray-700/40 rounded-xl shadow-sm dark:shadow-gray-900/20 border border-gray-200 dark:border-gray-600/50 overflow-hidden">
|
|
{/* Weekly Calendar Header */}
|
|
<div className="grid grid-cols-8 border-b-2 border-gray-200 dark:border-gray-600">
|
|
<div className="p-4 bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-700/60 dark:to-slate-800/60 border-r border-gray-200 dark:border-gray-600">
|
|
<Text className="font-bold text-sm text-slate-700 dark:text-slate-300">
|
|
Heure
|
|
</Text>
|
|
</div>
|
|
{DAYS_OF_WEEK.map((day) => (
|
|
<div
|
|
key={day}
|
|
className="p-4 bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-700/60 dark:to-slate-800/60 border-r border-gray-200 dark:border-gray-600 last:border-r-0 text-center"
|
|
>
|
|
<Text className="font-bold text-sm text-slate-700 dark:text-slate-300">
|
|
{DAYS_OF_WEEK_DISPLAY[day]}
|
|
</Text>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Time Slots */}
|
|
<div className="max-h-140 overflow-y-auto">
|
|
{timeSlots.map((timeSlot) => {
|
|
const timeMinutes = timeToMinutes(timeSlot);
|
|
const hour = Math.floor(timeMinutes / 60);
|
|
// Only show hours from 6 AM to 11 PM
|
|
if (hour < 6 || hour > 23) return null;
|
|
|
|
return (
|
|
<div
|
|
key={timeSlot}
|
|
className="grid grid-cols-8 border-b border-gray-100 dark:border-gray-700 hover:bg-slate-50/50 dark:hover:bg-slate-800/50 transition-colors duration-150"
|
|
>
|
|
<div className="p-3 border-r border-gray-200 dark:border-gray-600 bg-gradient-to-r from-slate-50/80 to-slate-100/80 dark:from-slate-800/80 dark:to-slate-900/80">
|
|
<Text className="text-xs font-semibold text-slate-600 dark:text-slate-400">
|
|
{timeSlot}
|
|
</Text>
|
|
</div>
|
|
{DAYS_OF_WEEK.map((day) => (
|
|
<div
|
|
key={`${day}-${timeSlot}`}
|
|
className="p-3 border-r border-gray-200 dark:border-gray-600 last:border-r-0 min-h-[3rem] flex items-center justify-center bg-gradient-to-br from-white to-slate-50/30 dark:from-gray-700/40 dark:to-slate-800/40"
|
|
>
|
|
{isTimeSlotAvailable(day, timeSlot, draftAvailabilities) ? (
|
|
<div className="w-full h-8 bg-gradient-to-r from-emerald-400 via-emerald-500 to-emerald-600 dark:from-emerald-500 dark:via-emerald-600 dark:to-emerald-700 rounded-lg shadow-sm border border-emerald-300 dark:border-emerald-600 flex items-center justify-center group hover:shadow-md transition-all duration-200 hover:scale-105">
|
|
<div className="w-3 h-3 bg-white/90 rounded-full shadow-sm group-hover:bg-white transition-colors duration-200"></div>
|
|
</div>
|
|
) : (
|
|
<div className="w-full h-8 bg-gray-100 dark:bg-gray-700 rounded-lg border border-gray-200 dark:border-gray-600 flex items-center justify-center opacity-50">
|
|
<div className="w-2 h-2 bg-gray-300 dark:bg-gray-500 rounded-full"></div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|