Dashboard.tsx
Golden-Hour-backend-layer-2/pages/Dashboard.tsx
import React, { useState, useMemo } from 'react';
type ViewMode = 'Day' | 'Week' | 'Month';
interface CalendarEvent {
id: string;
title: string;
time: string;
date: Date;
category: 'Maisons' | 'Experiences' | 'Services' | 'Products';
client: string;
description?: string;
}
const Dashboard: React.FC = () => {
const [viewMode, setViewMode] = useState<ViewMode>('Month');
const [currentDate, setCurrentDate] = useState(new Date());
const events: CalendarEvent[] = useMemo(() => [
{ id: '1', title: 'Villa Maintenance', time: '09:00', date: new Date(2025, 4, 15), category: 'Maisons', client: 'Alice Smith', description: 'Routine check of the HVAC and pool systems.' },
{ id: '2', title: 'Mountain Hike', time: '10:30', date: new Date(2025, 4, 16), category: 'Experiences', client: 'Bob Chen', description: 'Guided tour through the alpine trail.' },
{ id: '3', title: 'Private Chef', time: '19:00', date: new Date(2025, 4, 15), category: 'Services', client: 'Sarah Miller', description: 'Mediterranean 5-course dinner preparation.' },
{ id: '4', title: 'Cleaning Service', time: '14:00', date: new Date(2025, 4, 18), category: 'Maisons', client: 'John Doe', description: 'Full property turnover.' },
{ id: '5', title: 'City Tour', time: '11:00', date: new Date(2025, 4, 20), category: 'Experiences', client: 'Emma Watson', description: 'Art-deco architecture walking tour.' },
{ id: '6', title: 'Product Delivery', time: '16:00', date: new Date(2025, 4, 15), category: 'Products', client: 'Mark Ross', description: 'Luxury hamper delivery.' },
], []);
const getDaysInMonth = (date: Date) => {
const year = date.getFullYear();
const month = date.getMonth();
const firstDay = new Date(year, month, 1).getDay();
const lastDate = new Date(year, month + 1, 0).getDate();
let days: (Date | null)[] = [];
for (let i = 0; i < firstDay; i++) days.push(null);
for (let i = 1; i <= lastDate; i++) days.push(new Date(year, month, i));
const totalCells = 35;
while (days.length < totalCells) days.push(null);
return days;
};
const getDaysInWeek = (date: Date) => {
const days = [];
const current = new Date(date);
const day = current.getDay();
const diff = current.getDate() - day;
const startOfWeek = new Date(current.setDate(diff));
for (let i = 0; i < 7; i++) {
days.push(new Date(startOfWeek));
startOfWeek.setDate(startOfWeek.getDate() + 1);
}
return days;
};
const navigateDate = (direction: number) => {
const newDate = new Date(currentDate);
if (viewMode === 'Month') newDate.setMonth(currentDate.getMonth() + direction);
else if (viewMode === 'Week') newDate.setDate(currentDate.getDate() + (direction * 7));
else newDate.setDate(currentDate.getDate() + direction);
setCurrentDate(newDate);
};
const isToday = (date: Date | null) => {
if (!date) return false;
const today = new Date();
return date.getDate() === today.getDate() && date.getMonth() === today.getMonth() && date.getFullYear() === today.getFullYear();
};
const getEventsForDate = (date: Date | null) => {
if (!date) return [];
return events.filter(e => e.date.getDate() === date.getDate() && e.date.getMonth() === date.getMonth());
};
const categoryColors: Record<string, string> = {
Maisons: 'bg-[#c5a059]',
Experiences: 'bg-stone-300',
Services: 'bg-stone-200',
Products: 'bg-stone-800'
};
const renderMonthView = () => (
<div className="flex-1 bg-white gallery-shadow rounded-xl overflow-hidden border border-[#c5a059]/10 flex flex-col fade-in-section">
<div className="grid grid-cols-7 border-b border-[#c5a059]/5 bg-[#fdfcf9]/50">
{['S', 'M', 'T', 'W', 'T', 'F', 'S'].map(day => (
<div key={day} className="py-2.5 text-center text-[7px] font-bold uppercase tracking-[0.3em] text-slate-300 font-serif italic">
{day}
</div>
))}
</div>
<div className="grid grid-cols-7 grid-rows-5 flex-1 divide-x divide-y divide-[#c5a059]/5">
{getDaysInMonth(currentDate).map((date, idx) => {
const dayEvents = getEventsForDate(date);
return (
<div
key={idx}
className={`min-h-[85px] p-3 relative group transition-all duration-700 hover:bg-[#fdfcf9] ${!date ? 'bg-[#f4f2ee]/10' : ''}`}
>
{date ? (
<>
<div className="flex justify-between items-start mb-1.5">
<span className={`serif-numeral text-sm leading-none transition-all duration-700 ${
isToday(date) ? 'text-[#c5a059] font-medium' : 'text-stone-200 group-hover:text-slate-800'
}`}>
{date.getDate()}
</span>
</div>
<div className="space-y-1">
{dayEvents.slice(0, 2).map(event => (
<div key={event.id} className="flex items-center space-x-1.5">
<div className={`w-0.5 h-0.5 rounded-full ${categoryColors[event.category]} opacity-40`}></div>
<p className="text-[7px] font-bold text-slate-600 tracking-tight truncate uppercase leading-none">{event.title}</p>
</div>
))}
</div>
</>
) : null}
</div>
);
})}
</div>
</div>
);
const renderWeekView = () => (
<div className="flex-1 grid grid-cols-7 gap-4 fade-in-section">
{getDaysInWeek(currentDate).map((date, idx) => {
const dayEvents = getEventsForDate(date);
return (
<div key={idx} className={`bg-white gallery-shadow rounded-lg border border-[#c5a059]/10 p-5 flex flex-col transition-all duration-700 ${isToday(date) ? 'ring-1 ring-[#c5a059]/10 shadow-sm shadow-[#c5a059]/5' : ''}`}>
<div className="text-center mb-4 pb-3 border-b border-[#c5a059]/5">
<p className="text-[7px] font-bold text-[#c5a059]/50 uppercase tracking-[0.2em] mb-1 font-serif italic">
{date.toLocaleDateString('en-US', { weekday: 'short' })}
</p>
<h4 className={`serif-numeral text-2xl ${isToday(date) ? 'text-slate-900' : 'text-stone-100'}`}>
{date.getDate()}
</h4>
</div>
<div className="flex-1 space-y-4 overflow-y-auto no-scrollbar">
{dayEvents.map(event => (
<div key={event.id} className="group cursor-default border-b border-transparent hover:border-[#c5a059]/10 pb-2 transition-all">
<p className="text-[6.5px] font-bold uppercase tracking-[0.1em] text-[#c5a059]/30 font-serif italic">{event.time}</p>
<h5 className="text-[8px] font-bold text-slate-800 uppercase tracking-widest mt-0.5 leading-tight group-hover:text-[#c5a059] transition-all">{event.title}</h5>
<p className="text-[7.5px] font-serif italic text-slate-400">{event.client}</p>
</div>
))}
</div>
</div>
);
})}
</div>
);
const renderDayView = () => {
const dayEvents = getEventsForDate(currentDate);
return (
<div className="flex-1 flex gap-8 fade-in-section">
<div className="flex-1 bg-white gallery-shadow rounded-2xl p-10 border border-[#c5a059]/10 flex flex-col">
<div className="flex items-center justify-between mb-10 border-b border-[#c5a059]/5 pb-8">
<div>
<p className="text-[7px] font-bold uppercase tracking-[0.3em] text-[#c5a059] mb-1 font-serif italic">Edition Quotidienne</p>
<h3 className="font-serif italic text-4xl text-slate-900 leading-none">
{currentDate.toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric' })}
</h3>
</div>
<div className="flex -space-x-3">
{[1, 2].map(i => (
<div key={i} className="w-9 h-9 rounded-full border border-white grayscale bg-stone-50 overflow-hidden shadow-sm">
<img src={`https://picsum.photos/seed/day-${i}/80/80`} alt="" />
</div>
))}
</div>
</div>
<div className="flex-1 overflow-y-auto pr-4 space-y-8 no-scrollbar">
{dayEvents.length > 0 ? dayEvents.map(event => (
<div key={event.id} className="flex group">
<div className="w-20 flex-shrink-0 pt-0.5">
<p className="serif-numeral text-lg text-stone-200 group-hover:text-[#c5a059] transition-colors">{event.time}</p>
<div className="w-[0.5px] h-full bg-[#c5a059]/5 mx-auto mt-3"></div>
</div>
<div className="flex-1 bg-[#fdfcf9]/30 rounded-xl p-5 border border-[#c5a059]/5 group-hover:border-[#c5a059]/10 transition-all duration-500">
<span className="text-[7px] font-bold uppercase tracking-[0.2em] text-[#c5a059]/60 italic font-serif leading-none">{event.category}</span>
<h4 className="font-serif italic text-2xl text-slate-900 mt-1.5 mb-1">{event.title}</h4>
<p className="text-[11px] text-slate-500 font-serif italic leading-snug">{event.description}</p>
</div>
</div>
)) : (
<div className="flex-1 flex items-center justify-center text-slate-200 font-serif italic text-2xl opacity-40">
Aucun événement prévu pour aujourd'hui
</div>
)}
</div>
</div>
<div className="w-72 space-y-8">
<div className="bg-[#1a1a1a] rounded-2xl p-8 gallery-shadow text-white relative overflow-hidden">
<div className="absolute top-0 right-0 w-24 h-24 bg-[#c5a059]/5 rounded-full blur-3xl -mr-12 -mt-12"></div>
<p className="text-[7px] font-bold uppercase tracking-[0.3em] text-[#c5a059] mb-8 font-serif italic relative z-10">Sommaire</p>
<div className="space-y-6 relative z-10">
<div className="flex justify-between items-end border-b border-white/5 pb-3">
<span className="text-stone-400 font-serif italic text-[12px]">Registry</span>
<span className="serif-numeral text-2xl">{dayEvents.length}</span>
</div>
<div className="flex justify-between items-end border-b border-white/5 pb-3">
<span className="text-stone-400 font-serif italic text-[12px]">Occupation</span>
<span className="serif-numeral text-2xl">84%</span>
</div>
</div>
</div>
<div className="bg-white rounded-2xl p-8 border border-[#c5a059]/10">
<h4 className="font-serif italic text-xl text-slate-900 mb-6">Météo</h4>
<div className="flex items-center space-x-4">
<div className="w-9 h-9 rounded-full bg-[#fdfcf9] flex items-center justify-center text-[#c5a059]">
<i className="fa-solid fa-sun text-sm"></i>
</div>
<div>
<p className="text-[6.5px] font-bold uppercase tracking-[0.1em] text-slate-300 font-serif italic">Ciel Dégagé</p>
<p className="text-[15px] font-serif italic text-slate-900 leading-none">Antibes, FR</p>
</div>
</div>
</div>
</div>
</div>
);
};
return (
<div className="fade-in-section flex flex-col h-full space-y-8 pb-10">
<div className="flex flex-col md:flex-row md:items-end justify-between gap-6 border-b border-[#c5a059]/10 pb-8">
<div className="flex items-baseline space-x-4">
<span className="font-serif italic text-5xl text-stone-100 select-none">Nº</span>
<div>
<h2 className={`font-serif italic text-slate-900 tracking-tighter transition-all duration-700 leading-none ${viewMode === 'Day' ? 'text-6xl' : 'text-4xl'}`}>
{viewMode === 'Day'
? currentDate.getDate()
: currentDate.toLocaleString('default', { month: 'long' })}
</h2>
<p className="text-[7px] font-bold uppercase tracking-[0.4em] text-[#c5a059] mt-2 leading-none font-serif italic">
Catalogue Index — {currentDate.getFullYear()}
</p>
</div>
</div>
<div className="flex items-center space-x-10">
<div className="flex items-center space-x-4">
<button onClick={() => navigateDate(-1)} className="text-[#c5a059]/60 hover:text-[#c5a059] transition-all p-1">
<i className="fa-solid fa-arrow-left-long text-[9px]"></i>
</button>
<button onClick={() => setCurrentDate(new Date())} className="text-[7.5px] font-bold uppercase tracking-[0.2em] text-stone-300 hover:text-slate-900 font-serif italic">Aujourd'hui</button>
<button onClick={() => navigateDate(1)} className="text-[#c5a059]/60 hover:text-[#c5a059] transition-all p-1">
<i className="fa-solid fa-arrow-right-long text-[9px]"></i>
</button>
</div>
<div className="h-6 w-[0.5px] bg-[#c5a059]/10"></div>
<div className="flex space-x-6">
{(['Day', 'Week', 'Month'] as ViewMode[]).map((mode) => (
<button
key={mode}
onClick={() => setViewMode(mode)}
className={`text-[7.5px] font-bold uppercase tracking-[0.2em] transition-all relative pb-1.5 font-serif italic ${
viewMode === mode ? 'text-slate-900' : 'text-stone-300 hover:text-slate-500'
}`}
>
{mode}
{viewMode === mode && <div className="absolute bottom-0 left-0 right-0 h-[1px] bg-[#c5a059]"></div>}
</button>
))}
</div>
</div>
</div>
{viewMode === 'Month' && renderMonthView()}
{viewMode === 'Week' && renderWeekView()}
{viewMode === 'Day' && renderDayView()}
</div>
);
};
export default Dashboard;
Artículos relacionados
App.tsx
App.tsx — typescript source code from the Golden-Hour-backend-layer-2 learning materials (Golden-Hour-backend-layer-2/App.tsx).
Leer artículo →Header.tsx
Header.tsx — typescript source code from the Golden-Hour-backend-layer-2 learning materials (Golden-Hour-backend-layer-2/components/Header.tsx).
Leer artículo →Sidebar.tsx
Sidebar.tsx — typescript source code from the Golden-Hour-backend-layer-2 learning materials (Golden-Hour-backend-layer-2/components/Sidebar.tsx).
Leer artículo →index.tsx
index.tsx — typescript source code from the Golden-Hour-backend-layer-2 learning materials (Golden-Hour-backend-layer-2/index.tsx).
Leer artículo →ContentManagement.tsx
ContentManagement.tsx — typescript source code from the Golden-Hour-backend-layer-2 learning materials (Golden-Hour-backend-layer-2/pages/ContentManagement.tsx).
Leer artículo →CustomerManagement.tsx
CustomerManagement.tsx — typescript source code from the Golden-Hour-backend-layer-2 learning materials (Golden-Hour-backend-layer-2/pages/CustomerManagement.tsx).
Leer artículo →