xtablo-source/apps/main/src/components/AvailabilityVisualization.tsx
2025-10-23 11:54:45 +02:00

123 lines
5.1 KiB
TypeScript

import { Text } from "@xtablo/ui/components/typography";
import { WeeklyAvailability } from "../hooks/availabilities";
// 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-linear-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-linear-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-linear-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-linear-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-linear-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>
);
};