Initial commit: monorepo Naturcalabacera reservas (apps/api + apps/web + packages/shared)
This commit is contained in:
114
src/components/YearlyCalendar.tsx
Normal file
114
src/components/YearlyCalendar.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user