import { useState, useMemo } from 'react'; import { cn, formatCurrency, formatPercent, formatDate } from '@/lib/utils'; import { PLATFORM_COLORS } from '@/lib/constants'; import { CalendarDays, ArrowUpDown, Filter, ChevronDown, } from 'lucide-react'; import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, } from 'recharts'; // ── Types ────────────────────────────────────────────────────────────── type ReservationStatus = 'confirmed' | 'completed' | 'cancelled'; type Platform = 'airbnb' | 'vrbo'; type SortField = 'guest' | 'checkin' | 'checkout' | 'nights' | 'rate' | 'total' | 'status'; type SortDir = 'asc' | 'desc'; interface Reservation { id: string; guest: string; platform: Platform; checkin: string; checkout: string; nights: number; nightlyRate: number; totalPayout: number; status: ReservationStatus; } // ── Mock Data ────────────────────────────────────────────────────────── const MOCK_RESERVATIONS: Reservation[] = [ { id: 'r-01', guest: 'Sarah Mitchell', platform: 'airbnb', checkin: '2025-10-04', checkout: '2025-10-08', nights: 4, nightlyRate: 195, totalPayout: 741, status: 'completed' }, { id: 'r-02', guest: 'James Park', platform: 'vrbo', checkin: '2025-10-12', checkout: '2025-10-15', nights: 3, nightlyRate: 185, totalPayout: 527, status: 'completed' }, { id: 'r-03', guest: 'Emily Rodriguez', platform: 'airbnb', checkin: '2025-10-22', checkout: '2025-10-27', nights: 5, nightlyRate: 210, totalPayout: 999, status: 'completed' }, { id: 'r-04', guest: 'Michael Chen', platform: 'airbnb', checkin: '2025-11-01', checkout: '2025-11-04', nights: 3, nightlyRate: 175, totalPayout: 499, status: 'completed' }, { id: 'r-05', guest: 'Lisa Thompson', platform: 'vrbo', checkin: '2025-11-10', checkout: '2025-11-17', nights: 7, nightlyRate: 165, totalPayout: 1098, status: 'completed' }, { id: 'r-06', guest: 'David Kim', platform: 'airbnb', checkin: '2025-11-22', checkout: '2025-11-24', nights: 2, nightlyRate: 220, totalPayout: 418, status: 'cancelled' }, { id: 'r-07', guest: 'Amanda Foster', platform: 'vrbo', checkin: '2025-12-05', checkout: '2025-12-09', nights: 4, nightlyRate: 230, totalPayout: 874, status: 'completed' }, { id: 'r-08', guest: 'Robert Johnson', platform: 'airbnb', checkin: '2025-12-18', checkout: '2025-12-25', nights: 7, nightlyRate: 250, totalPayout: 1663, status: 'completed' }, { id: 'r-09', guest: 'Jennifer Lee', platform: 'airbnb', checkin: '2025-12-28', checkout: '2025-12-31', nights: 3, nightlyRate: 245, totalPayout: 698, status: 'completed' }, { id: 'r-10', guest: 'Chris Martinez', platform: 'vrbo', checkin: '2026-01-03', checkout: '2026-01-06', nights: 3, nightlyRate: 180, totalPayout: 513, status: 'completed' }, { id: 'r-11', guest: 'Natalie Wright', platform: 'airbnb', checkin: '2026-01-15', checkout: '2026-01-20', nights: 5, nightlyRate: 190, totalPayout: 903, status: 'completed' }, { id: 'r-12', guest: 'Kevin Brown', platform: 'vrbo', checkin: '2026-01-28', checkout: '2026-02-01', nights: 4, nightlyRate: 175, totalPayout: 665, status: 'completed' }, { id: 'r-13', guest: 'Patricia Davis', platform: 'airbnb', checkin: '2026-02-07', checkout: '2026-02-12', nights: 5, nightlyRate: 200, totalPayout: 950, status: 'completed' }, { id: 'r-14', guest: 'Thomas Wilson', platform: 'airbnb', checkin: '2026-02-20', checkout: '2026-02-22', nights: 2, nightlyRate: 215, totalPayout: 409, status: 'cancelled' }, { id: 'r-15', guest: 'Rachel Garcia', platform: 'vrbo', checkin: '2026-03-01', checkout: '2026-03-05', nights: 4, nightlyRate: 205, totalPayout: 779, status: 'confirmed' }, { id: 'r-16', guest: 'Daniel Taylor', platform: 'airbnb', checkin: '2026-03-10', checkout: '2026-03-14', nights: 4, nightlyRate: 210, totalPayout: 798, status: 'confirmed' }, { id: 'r-17', guest: 'Stephanie Moore', platform: 'vrbo', checkin: '2026-03-20', checkout: '2026-03-26', nights: 6, nightlyRate: 195, totalPayout: 1112, status: 'confirmed' }, { id: 'r-18', guest: 'Brian Anderson', platform: 'airbnb', checkin: '2026-03-28', checkout: '2026-03-31', nights: 3, nightlyRate: 225, totalPayout: 641, status: 'confirmed' }, ]; const STATUS_STYLES: Record = { confirmed: 'bg-green-500/10 text-green-400', completed: 'bg-blue-500/10 text-blue-400', cancelled: 'bg-red-500/10 text-red-400', }; const PLATFORM_DOT: Record = { airbnb: PLATFORM_COLORS.airbnb, vrbo: PLATFORM_COLORS.vrbo, }; // ── Component ────────────────────────────────────────────────────────── export default function Reservations() { const [platformFilter, setPlatformFilter] = useState('all'); const [statusFilter, setStatusFilter] = useState('all'); const [sortField, setSortField] = useState('checkin'); const [sortDir, setSortDir] = useState('desc'); function toggleSort(field: SortField) { if (sortField === field) { setSortDir((d) => (d === 'asc' ? 'desc' : 'asc')); } else { setSortField(field); setSortDir('asc'); } } const filtered = useMemo(() => { let list = [...MOCK_RESERVATIONS]; if (platformFilter !== 'all') list = list.filter((r) => r.platform === platformFilter); if (statusFilter !== 'all') list = list.filter((r) => r.status === statusFilter); list.sort((a, b) => { const dir = sortDir === 'asc' ? 1 : -1; switch (sortField) { case 'guest': return a.guest.localeCompare(b.guest) * dir; case 'checkin': return (new Date(a.checkin).getTime() - new Date(b.checkin).getTime()) * dir; case 'checkout': return (new Date(a.checkout).getTime() - new Date(b.checkout).getTime()) * dir; case 'nights': return (a.nights - b.nights) * dir; case 'rate': return (a.nightlyRate - b.nightlyRate) * dir; case 'total': return (a.totalPayout - b.totalPayout) * dir; case 'status': return a.status.localeCompare(b.status) * dir; default: return 0; } }); return list; }, [platformFilter, statusFilter, sortField, sortDir]); // Monthly revenue chart data const monthlyRevenue = useMemo(() => { const map = new Map(); MOCK_RESERVATIONS.filter((r) => r.status !== 'cancelled').forEach((r) => { const d = new Date(r.checkin); const key = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`; map.set(key, (map.get(key) || 0) + r.totalPayout); }); return Array.from(map.entries()) .sort(([a], [b]) => a.localeCompare(b)) .map(([month, revenue]) => { const [y, m] = month.split('-'); const label = new Date(Number(y), Number(m) - 1).toLocaleDateString('en-US', { month: 'short', year: '2-digit', }); return { month: label, revenue }; }); }, []); // Monthly occupancy summary const monthlyOccupancy = useMemo(() => { const map = new Map(); MOCK_RESERVATIONS.filter((r) => r.status !== 'cancelled').forEach((r) => { const d = new Date(r.checkin); const key = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`; map.set(key, (map.get(key) || 0) + r.nights); }); return Array.from(map.entries()) .sort(([a], [b]) => a.localeCompare(b)) .map(([month, nights]) => { const [y, m] = month.split('-'); const daysInMonth = new Date(Number(y), Number(m), 0).getDate(); const label = new Date(Number(y), Number(m) - 1).toLocaleDateString('en-US', { month: 'short', year: '2-digit', }); return { month: label, nights, daysInMonth, occupancy: nights / daysInMonth }; }); }, []); const totalRevenue = filtered .filter((r) => r.status !== 'cancelled') .reduce((sum, r) => sum + r.totalPayout, 0); const SortHeader = ({ field, label, align }: { field: SortField; label: string; align?: string }) => ( toggleSort(field)} > {label} {sortField === field && ( )} ); return (
{/* Header */}

Reservations

{filtered.length} reservations
{/* Revenue chart + occupancy summary */}
{/* Bar chart */}

Revenue by Month

`$${(v / 1000).toFixed(1)}k`} /> [formatCurrency(value), 'Revenue']} />
{/* Occupancy cards */}

Monthly Occupancy

{monthlyOccupancy.map((m) => (
{m.month}
{formatPercent(m.occupancy)}
))}
Total Revenue {formatCurrency(totalRevenue)}
{/* Filters */}
Filters
{/* Platform filter */}
{/* Status filter */}
{/* Reservations table */}
{filtered.map((r) => ( ))} {filtered.length === 0 && ( )}
Platform
{r.guest} {r.platform === 'vrbo' ? 'VRBO' : 'Airbnb'} {formatDate(r.checkin)} {formatDate(r.checkout)} {r.nights} {formatCurrency(r.nightlyRate)} {formatCurrency(r.totalPayout)} {r.status}
No reservations match the selected filters.
); }