import React, { useEffect, useState, useCallback } from 'react' import ReactDOM from 'react-dom' import { useTranslation } from '../i18n' import { useVacayStore } from '../store/vacayStore' import { addListener, removeListener } from '../api/websocket' import Navbar from '../components/Layout/Navbar' import VacayCalendar from '../components/Vacay/VacayCalendar' import VacayPersons from '../components/Vacay/VacayPersons' import VacayStats from '../components/Vacay/VacayStats' import VacaySettings from '../components/Vacay/VacaySettings' import { Plus, Minus, ChevronLeft, ChevronRight, Settings, CalendarDays, AlertTriangle, Users, Eye, Pencil, Trash2, Unlink, ShieldCheck, SlidersHorizontal } from 'lucide-react' import Modal from '../components/shared/Modal' export default function VacayPage(): React.ReactElement { const { t } = useTranslation() const { years, selectedYear, setSelectedYear, addYear, removeYear, loadAll, loadPlan, loadEntries, loadStats, loadHolidays, loading, incomingInvites, acceptInvite, declineInvite, plan } = useVacayStore() const [showSettings, setShowSettings] = useState(false) const [deleteYear, setDeleteYear] = useState(null) const [showMobileSidebar, setShowMobileSidebar] = useState(false) useEffect(() => { loadAll() }, []) // Live sync via WebSocket const handleWsMessage = useCallback((msg: { type: string }) => { if (msg.type === 'vacay:update' || msg.type === 'vacay:settings') { loadPlan() loadEntries(selectedYear) loadStats(selectedYear) if (msg.type === 'vacay:settings') loadAll() } if (msg.type === 'vacay:invite' || msg.type === 'vacay:accepted' || msg.type === 'vacay:declined' || msg.type === 'vacay:cancelled' || msg.type === 'vacay:dissolved') { loadAll() } }, [selectedYear]) useEffect(() => { addListener(handleWsMessage) return () => removeListener(handleWsMessage) }, [handleWsMessage]) useEffect(() => { if (selectedYear) { loadEntries(selectedYear); loadStats(selectedYear); loadHolidays(selectedYear) } }, [selectedYear]) const handleAddNextYear = () => { const nextYear = years.length > 0 ? Math.max(...years) + 1 : new Date().getFullYear() addYear(nextYear) } const handleAddPrevYear = () => { const prevYear = years.length > 0 ? Math.min(...years) - 1 : new Date().getFullYear() addYear(prevYear) } if (loading) { return (
) } // Sidebar content (shared between desktop sidebar and mobile drawer) const sidebarContent = ( <> {/* Year Selector */}
{t('vacay.year')}
{selectedYear}
{years.map(y => (
setSelectedYear(y)} className="group relative py-1.5 rounded-lg text-xs font-medium transition-[background-color,color] duration-150 ease-[cubic-bezier(0.23,1,0.32,1)] text-center cursor-pointer" style={{ background: y === selectedYear ? 'var(--text-primary)' : 'var(--bg-secondary)', color: y === selectedYear ? 'var(--bg-card)' : 'var(--text-muted)', }}> {y} {years.length > 1 && ( { e.stopPropagation(); setDeleteYear(y); setShowMobileSidebar(false) }} className="absolute -top-1 -right-1 w-3.5 h-3.5 rounded-full bg-red-500 text-white text-[7px] flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity cursor-pointer"> )}
))}
{/* Legend */} {(plan?.holidays_enabled || plan?.company_holidays_enabled || plan?.block_weekends) && (
{t('vacay.legend')}
{plan?.holidays_enabled && (plan?.holiday_calendars ?? []).length === 0 && ( )} {plan?.holidays_enabled && (plan?.holiday_calendars ?? []).map(cal => ( ))} {plan?.company_holidays_enabled && } {plan?.block_weekends && }
)} ) return (
{/* Mobile + tablet header (filter toggle lives here) */}

{t('admin.addons.catalog.vacay.name')}

{/* Desktop header — unified toolbar (sidebar is always visible at this width) */}

{t('admin.addons.catalog.vacay.name')}

{t('vacay.subtitle')}
{/* Main layout */}
{/* Desktop Sidebar */}
{sidebarContent}
{/* Calendar */}
{/* Mobile Sidebar Drawer */} {showMobileSidebar && ReactDOM.createPortal(
setShowMobileSidebar(false)} />
{sidebarContent}
, document.body )} {/* Settings Modal */} setShowSettings(false)} title={t('vacay.settings')} size="md"> setShowSettings(false)} /> {/* Delete Year Modal */} setDeleteYear(null)} title={t('vacay.removeYear')} size="sm">

{t('vacay.removeYearConfirm', { year: deleteYear })}

{t('vacay.removeYearHint')}

{/* Incoming invite — forced fullscreen modal */} {incomingInvites.length > 0 && ReactDOM.createPortal(
{incomingInvites.map(inv => (
{inv.username?.[0]?.toUpperCase()}

{t('vacay.inviteTitle')}

{inv.username} {t('vacay.inviteWantsToFuse')}

))}
, document.body )}
) } function InfoItem({ icon: Icon, text }: { icon: React.ComponentType<{ size?: number; className?: string; style?: React.CSSProperties }>; text: string }): React.ReactElement { return (
{text}
) } function LegendItem({ color, label }: { color: string; label: string }): React.ReactElement { return (
{label}
) }