115 lines
5.8 KiB
TypeScript
115 lines
5.8 KiB
TypeScript
import { format, eachDayOfInterval, endOfMonth, startOfMonth, startOfYear, endOfYear, eachMonthOfInterval, getDay } from 'date-fns';
|
|
import { es } from 'date-fns/locale';
|
|
import type { Reservation } from '../types';
|
|
|
|
interface Props {
|
|
reservations: Reservation[];
|
|
year: number;
|
|
}
|
|
|
|
export function YearlyCalendar({ reservations = [], year }: Props) {
|
|
const months = eachMonthOfInterval({
|
|
start: startOfYear(new Date(year, 0, 1)),
|
|
end: endOfYear(new Date(year, 0, 1))
|
|
});
|
|
|
|
const isDayOccupied = (date: Date) => {
|
|
if (!reservations || !Array.isArray(reservations)) return false;
|
|
|
|
return reservations.some(res => {
|
|
if (!res.start_date || !res.end_date) return false;
|
|
|
|
const start = new Date(res.start_date);
|
|
const end = new Date(res.end_date);
|
|
|
|
if (isNaN(start.getTime()) || isNaN(end.getTime())) return false;
|
|
|
|
// Ajuste de zona horaria simple
|
|
const checkDate = new Date(date);
|
|
checkDate.setHours(12, 0, 0, 0);
|
|
start.setHours(0, 0, 0, 0);
|
|
end.setHours(23, 59, 59, 999);
|
|
|
|
return checkDate >= start && checkDate <= end;
|
|
});
|
|
};
|
|
|
|
return (
|
|
<div className="p-8 max-w-[1600px] mx-auto animate-in fade-in zoom-in duration-500">
|
|
<header className="mb-8 flex justify-between items-end">
|
|
<div>
|
|
<h1 className="text-4xl font-black text-white tracking-tighter mb-2">Vista Anual {year}</h1>
|
|
<p className="text-slate-400 text-lg">Panorama global de ocupación.</p>
|
|
</div>
|
|
<div className="flex gap-4 items-center bg-slate-800/50 px-4 py-2 rounded-full border border-slate-700">
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-3 h-3 rounded-full bg-emerald-500"></div>
|
|
<span className="text-slate-300 text-xs font-bold uppercase tracking-wider">Libre</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-3 h-3 rounded-full bg-red-500 shadow-[0_0_10px_rgba(239,68,68,0.5)]"></div>
|
|
<span className="text-slate-300 text-xs font-bold uppercase tracking-wider">Ocupado</span>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
|
{months.map((month) => {
|
|
const monthStart = startOfMonth(month);
|
|
const monthEnd = endOfMonth(month);
|
|
const days = eachDayOfInterval({ start: monthStart, end: monthEnd });
|
|
const startDayOfWeek = getDay(monthStart); // 0 = Sunday
|
|
|
|
// Create empty slots for days before the 1st of the month
|
|
const emptySlots = Array(startDayOfWeek).fill(null);
|
|
|
|
return (
|
|
<div key={month.toString()} className="bg-slate-800/40 backdrop-blur-md border border-slate-700/50 rounded-2xl p-4 hover:border-slate-600 transition-colors shadow-lg">
|
|
<h3 className="text-lg font-bold text-white mb-4 capitalize text-center border-b border-slate-700/50 pb-2">
|
|
{format(month, 'MMMM', { locale: es })}
|
|
</h3>
|
|
|
|
<div className="grid grid-cols-7 gap-1 mb-2">
|
|
{['D', 'L', 'M', 'X', 'J', 'V', 'S'].map(d => (
|
|
<div key={d} className="text-center text-[10px] font-bold text-slate-500">{d}</div>
|
|
))}
|
|
</div>
|
|
|
|
<div className="grid grid-cols-7 gap-1">
|
|
{emptySlots.map((_, i) => (
|
|
<div key={`empty-${i}`} />
|
|
))}
|
|
{days.map((day) => {
|
|
const occupied = isDayOccupied(day);
|
|
return (
|
|
<div
|
|
key={day.toString()}
|
|
className={`
|
|
aspect-square rounded-md flex items-center justify-center text-xs font-medium relative group transition-all duration-300
|
|
${occupied
|
|
? 'bg-red-500/30 text-red-100 shadow-[0_0_10px_rgba(239,68,68,0.2)] hover:bg-red-500/40'
|
|
: 'bg-emerald-500/10 text-emerald-500/60 hover:bg-emerald-500/20 hover:text-emerald-400'
|
|
}
|
|
`}
|
|
title={format(day, 'dd/MM/yyyy')}
|
|
>
|
|
{format(day, 'd')}
|
|
|
|
{/* Tooltip on hover if occupied (simplified) */}
|
|
{occupied && (
|
|
<div className="absolute bottom-full mb-2 left-1/2 -translate-x-1/2 hidden group-hover:block whitespace-nowrap z-10 bg-black/80 backdrop-blur text-white text-[10px] px-2 py-1 rounded shadow-lg border border-white/10">
|
|
Ocupado
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|