gbclient/app/calendar/page.tsx

959 lines
36 KiB
TypeScript
Raw Normal View History

"use client";
import React, { useState } from 'react';
import {
Calendar, ChevronLeft, ChevronRight, Plus, Settings,
Clock, Users, MapPin, Repeat,
Edit3, Trash2, Copy, Forward, Reply, MoreHorizontal,
Filter, RefreshCw, Share2,
User, AlertCircle,
CheckCircle2, X, Eye, EyeOff, Flag,
PrinterIcon} from 'lucide-react';
import { cn } from "@/lib/utils";
import Footer from '../footer';
// Sample calendar data
const sampleEvents = [
{
id: 1,
title: "Team Standup",
start: "2025-06-28T09:00:00",
end: "2025-06-28T09:30:00",
type: "meeting",
location: "Conference Room A",
attendees: ["john@company.com", "jane@company.com", "mike@company.com"],
description: "Daily team synchronization meeting",
priority: "high",
status: "accepted",
organizer: "sarah@company.com",
category: "work",
recurring: true
},
{
id: 2,
title: "Client Presentation",
start: "2025-06-28T14:00:00",
end: "2025-06-28T15:30:00",
type: "meeting",
location: "Board Room",
attendees: ["client@external.com", "manager@company.com"],
description: "Q2 results presentation to key client",
priority: "high",
status: "tentative",
organizer: "me@company.com",
category: "important",
recurring: false
},
{
id: 3,
title: "Lunch with Marketing Team",
start: "2025-06-28T12:00:00",
end: "2025-06-28T13:00:00",
type: "personal",
location: "Downtown Cafe",
attendees: ["marketing@company.com"],
description: "Monthly team lunch",
priority: "normal",
status: "accepted",
organizer: "marketing@company.com",
category: "social",
recurring: true
},
{
id: 4,
title: "Project Review",
start: "2025-06-29T10:00:00",
end: "2025-06-29T11:00:00",
type: "meeting",
location: "Online",
attendees: ["team@company.com"],
description: "Weekly project status review",
priority: "normal",
status: "accepted",
organizer: "me@company.com",
category: "work",
recurring: true
},
{
id: 5,
title: "Doctor Appointment",
start: "2025-06-30T15:00:00",
end: "2025-06-30T16:00:00",
type: "personal",
location: "Medical Center",
attendees: [],
description: "Annual checkup",
priority: "normal",
status: "accepted",
organizer: "me@company.com",
category: "personal",
recurring: false
}
];
const categoriesData = [
{ name: "Work", color: "blue", visible: true, count: 12 },
{ name: "Personal", color: "green", visible: true, count: 5 },
{ name: "Important", color: "red", visible: true, count: 3 },
{ name: "Social", color: "purple", visible: true, count: 8 },
{ name: "Travel", color: "orange", visible: false, count: 2 },
{ name: "Family", color: "pink", visible: true, count: 4 }
];
// Date utilities
const formatDate = (date) => {
return new Intl.DateTimeFormat('en-US', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
}).format(date);
};
const formatTime = (dateString) => {
return new Intl.DateTimeFormat('en-US', {
hour: 'numeric',
minute: '2-digit',
hour12: true
}).format(new Date(dateString));
};
const formatDateShort = (date) => {
return new Intl.DateTimeFormat('en-US', {
month: 'short',
day: 'numeric'
}).format(date);
};
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
DropdownMenuSeparator,
} from "@/components/ui/dropdown-menu";
import {
ContextMenu,
ContextMenuContent,
ContextMenuItem,
ContextMenuTrigger,
ContextMenuSeparator,
} from "@/components/ui/context-menu";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { ScrollArea } from "@/components/ui/scroll-area";
const CalendarSidebar = ({ isCollapsed, categories, onCategoryToggle, onDateSelect }) => {
const [selectedCategories, setSelectedCategories] = useState(
categories.filter(cat => cat.visible).map(cat => cat.name)
);
const toggleCategory = (categoryName) => {
const newSelected = selectedCategories.includes(categoryName)
? selectedCategories.filter(name => name !== categoryName)
: [...selectedCategories, categoryName];
setSelectedCategories(newSelected);
onCategoryToggle(categoryName);
};
const generateCalendarDays = () => {
const today = new Date();
const currentMonth = today.getMonth();
const currentYear = today.getFullYear();
const firstDay = new Date(currentYear, currentMonth, 1);
const startDate = new Date(firstDay);
startDate.setDate(startDate.getDate() - firstDay.getDay());
const days = [];
for (let i = 0; i < 42; i++) {
const date = new Date(startDate);
date.setDate(startDate.getDate() + i);
days.push(date);
}
return days;
};
const calendarDays = generateCalendarDays();
const today = new Date();
const quickActions = [
{ icon: Plus, label: "New Event", action: "new-event" },
{ icon: Users, label: "New Meeting", action: "new-meeting" },
];
if (isCollapsed) {
return (
<div className="p-2 space-y-2 bg-card border-r border-border">
{quickActions.map((action, index) => (
<Tooltip key={index} delayDuration={0}>
<TooltipTrigger asChild>
<Button variant="ghost" size="icon" className="w-9 h-9 bg-secondary hover:bg-secondary/80">
<action.icon className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent side="right" className="bg-popover text-popover-foreground border-border">
{action.label}
</TooltipContent>
</Tooltip>
))}
</div>
);
}
return (
<div className="flex flex-col h-full bg-card border-r border-border w-[200px]">
{/* Quick Actions */}
<div className="p-3 border-b border-border bg-secondary">
<h3 className="text-sm font-semibold text-foreground mb-2 font-mono">Quick Actions</h3>
<div className="space-y-1">
{quickActions.map((action, index) => (
<Button
key={index}
variant="ghost"
size="sm"
className="w-full justify-start text-xs h-8 hover:bg-secondary/80 bg-secondary"
>
<action.icon className="h-3 w-3 mr-2" />
{action.label}
</Button>
))}
</div>
</div>
{/* Mini Calendar */}
<div className="p-3 border-b border-border bg-secondary">
<div className="flex items-center justify-between mb-2">
<h3 className="text-sm font-semibold text-foreground font-mono">
{today.toLocaleDateString('en-US', { month: 'long', year: 'numeric' })}
</h3>
<div className="flex gap-1">
<Button variant="ghost" size="icon" className="h-6 w-6 bg-secondary hover:bg-secondary/80">
<ChevronLeft className="h-3 w-3" />
</Button>
<Button variant="ghost" size="icon" className="h-6 w-6 bg-secondary hover:bg-secondary/80">
<ChevronRight className="h-3 w-3" />
</Button>
</div>
</div>
<div className="grid grid-cols-7 gap-1 text-xs font-mono">
{['S', 'M', 'T', 'W', 'T', 'F', 'S'].map(day => (
<div key={day} className="text-center text-foreground font-medium p-1">
{day}
</div>
))}
{calendarDays.map((date, index) => {
const isToday = date.toDateString() === today.toDateString();
const isCurrentMonth = date.getMonth() === today.getMonth();
return (
<button
key={index}
onClick={() => onDateSelect(date)}
className={cn(
"p-1 text-center rounded-none hover:bg-secondary/80 transition-colors font-mono",
isToday && "bg-primary text-primary-foreground hover:bg-primary",
!isCurrentMonth && "text-muted-foreground",
isCurrentMonth && !isToday && "text-foreground"
)}
>
{date.getDate()}
</button>
);
})}
</div>
</div>
{/* Categories */}
<div className="flex-1 p-3 bg-secondary">
<h3 className="text-sm font-semibold text-foreground mb-2 font-mono">My Calendars</h3>
<ScrollArea className="h-full">
<div className="space-y-1">
{categories.map((category) => (
<div
key={category.name}
className="flex items-center justify-between p-1 rounded-none hover:bg-secondary/80"
>
<button
onClick={() => toggleCategory(category.name)}
className="flex items-center gap-2 flex-1 text-left"
>
<div className="flex items-center gap-2">
{category.visible ? (
<Eye className="h-3 w-3 text-foreground" />
) : (
<EyeOff className="h-3 w-3 text-muted-foreground" />
)}
<div
className={cn(
"w-3 h-3 rounded-none",
category.color === 'blue' && "bg-blue-500",
category.color === 'green' && "bg-green-500",
category.color === 'red' && "bg-red-500",
category.color === 'purple' && "bg-purple-500",
category.color === 'orange' && "bg-orange-500",
category.color === 'pink' && "bg-pink-500"
)}
/>
</div>
<span className={cn(
"text-xs font-mono",
category.visible ? "text-foreground" : "text-muted-foreground"
)}>
{category.name}
</span>
</button>
<span className="text-xs text-foreground font-mono">({category.count})</span>
</div>
))}
</div>
</ScrollArea>
</div>
</div>
);
};
const EventCard = ({ event, onAction }) => {
const getCategoryColor = (category) => {
const colors = {
work: "bg-blue-500/20 border-l-blue-500 text-foreground",
personal: "bg-green-500/20 border-l-green-500 text-foreground",
important: "bg-red-500/20 border-l-red-500 text-foreground",
social: "bg-purple-500/20 border-l-purple-500 text-foreground"
};
return colors[category] || "bg-secondary border-l-muted text-foreground";
};
const getPriorityIcon = (priority) => {
if (priority === 'high') return <Flag className="h-3 w-3 text-red-500" />;
return null;
};
const getStatusIcon = (status) => {
switch (status) {
case 'accepted': return <CheckCircle2 className="h-3 w-3 text-green-500" />;
case 'tentative': return <AlertCircle className="h-3 w-3 text-yellow-500" />;
case 'declined': return <X className="h-3 w-3 text-red-500" />;
default: return null;
}
};
return (
<ContextMenu>
<ContextMenuTrigger asChild>
<div className={cn(
"p-2 rounded-none border-l-4 cursor-pointer hover:shadow-none transition-all mb-1 border border-border",
getCategoryColor(event.category)
)}>
<div className="flex items-start justify-between mb-1">
<div className="flex items-center gap-1">
{getPriorityIcon(event.priority)}
<h4 className="text-sm font-medium truncate font-mono">{event.title}</h4>
</div>
<div className="flex items-center gap-1">
{event.recurring && <Repeat className="h-3 w-3 text-foreground" />}
{getStatusIcon(event.status)}
</div>
</div>
<div className="text-xs text-foreground space-y-1 font-mono">
<div className="flex items-center gap-1">
<Clock className="h-3 w-3" />
{formatTime(event.start)} - {formatTime(event.end)}
</div>
{event.location && (
<div className="flex items-center gap-1">
<MapPin className="h-3 w-3" />
<span className="truncate">{event.location}</span>
</div>
)}
{event.attendees.length > 0 && (
<div className="flex items-center gap-1">
<Users className="h-3 w-3" />
<span>{event.attendees.length} attendees</span>
</div>
)}
</div>
</div>
</ContextMenuTrigger>
<ContextMenuContent className="bg-popover text-popover-foreground border-border font-mono">
<ContextMenuItem onClick={() => onAction('open', event)} className="hover:bg-primary hover:text-primary-foreground">
<Eye className="h-4 w-4 mr-2" />
Open
</ContextMenuItem>
<ContextMenuItem onClick={() => onAction('edit', event)} className="hover:bg-primary hover:text-primary-foreground">
<Edit3 className="h-4 w-4 mr-2" />
Edit
</ContextMenuItem>
<ContextMenuSeparator className="border-t border-border" />
<ContextMenuItem onClick={() => onAction('forward', event)} className="hover:bg-primary hover:text-primary-foreground">
<Forward className="h-4 w-4 mr-2" />
Forward
</ContextMenuItem>
<ContextMenuItem onClick={() => onAction('copy', event)} className="hover:bg-primary hover:text-primary-foreground">
<Copy className="h-4 w-4 mr-2" />
Copy
</ContextMenuItem>
<ContextMenuSeparator className="border-t border-border" />
<ContextMenuItem onClick={() => onAction('delete', event)} className="text-destructive hover:bg-primary hover:text-primary-foreground">
<Trash2 className="h-4 w-4 mr-2" />
Delete
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
);
};
const CalendarView = ({ view, events, currentDate, onEventAction }) => {
const filteredEvents = events.filter(event => {
const eventDate = new Date(event.start);
if (view === 'day') {
return eventDate.toDateString() === currentDate.toDateString();
} else if (view === 'week') {
const weekStart = new Date(currentDate);
weekStart.setDate(currentDate.getDate() - currentDate.getDay());
const weekEnd = new Date(weekStart);
weekEnd.setDate(weekStart.getDate() + 6);
return eventDate >= weekStart && eventDate <= weekEnd;
} else if (view === 'month') {
return eventDate.getMonth() === currentDate.getMonth() &&
eventDate.getFullYear() === currentDate.getFullYear();
}
return true;
});
if (view === 'day') {
const dayEvents = filteredEvents.sort((a, b) => new Date(a.start).getTime() - new Date(b.start).getTime());
return (
<div className="flex flex-col h-full bg-background">
<div className="p-4 bg-card border-b border-border">
<h2 className="text-lg font-semibold text-foreground font-mono">
{formatDate(currentDate)}
</h2>
</div>
<ScrollArea className="flex-1">
<div className="p-4 space-y-2">
{dayEvents.length > 0 ? (
dayEvents.map(event => (
<EventCard
key={event.id}
event={event}
onAction={onEventAction}
/>
))
) : (
<div className="text-center py-8 text-foreground font-mono">
<Calendar className="h-12 w-12 mx-auto mb-2 text-muted-foreground" />
<p>No events scheduled for this day</p>
</div>
)}
</div>
</ScrollArea>
</div>
);
}
if (view === 'week') {
const weekStart = new Date(currentDate);
weekStart.setDate(currentDate.getDate() - currentDate.getDay());
const weekDays = Array.from({ length: 7 }, (_, i) => {
const day = new Date(weekStart);
day.setDate(weekStart.getDate() + i);
return day;
});
return (
<div className="flex flex-col h-full bg-background">
<div className="p-4 bg-card border-b border-border">
<h2 className="text-lg font-semibold text-foreground font-mono">
Week of {formatDateShort(weekStart)} - {formatDateShort(weekDays[6])}
</h2>
</div>
<div className="flex-1 grid grid-cols-7 gap-1 p-2">
{weekDays.map((day, index) => {
const dayEvents = filteredEvents.filter(event =>
new Date(event.start).toDateString() === day.toDateString()
);
const isToday = day.toDateString() === new Date().toDateString();
return (
<div key={index} className="border border-border rounded-none bg-card min-h-[200px]">
<div className={cn(
"p-2 text-center border-b border-border text-sm font-medium font-mono",
isToday ? "bg-primary text-primary-foreground" : "bg-secondary text-foreground"
)}>
<div>{day.toLocaleDateString('en-US', { weekday: 'short' })}</div>
<div className="text-lg">{day.getDate()}</div>
</div>
<div className="p-1 space-y-1">
{dayEvents.map(event => (
<div
key={event.id}
className={cn(
"p-1 rounded-none text-xs border-l-2 cursor-pointer hover:shadow-none border border-border font-mono",
event.category === 'work' && "bg-blue-500/20 border-l-blue-500",
event.category === 'personal' && "bg-green-500/20 border-l-green-500",
event.category === 'important' && "bg-red-500/20 border-l-red-500",
event.category === 'social' && "bg-purple-500/20 border-l-purple-500"
)}
onClick={() => onEventAction('open', event)}
>
<div className="font-medium truncate">{event.title}</div>
<div className="text-foreground">{formatTime(event.start)}</div>
</div>
))}
</div>
</div>
);
})}
</div>
</div>
);
}
// Month view
const monthStart = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1);
const calendarStart = new Date(monthStart);
calendarStart.setDate(calendarStart.getDate() - monthStart.getDay());
const calendarDays = Array.from({ length: 42 }, (_, i) => {
const day = new Date(calendarStart);
day.setDate(calendarStart.getDate() + i);
return day;
});
return (
<div className="flex flex-col h-full bg-background">
<div className="p-4 bg-card border-b border-border">
<h2 className="text-lg font-semibold text-foreground font-mono">
{currentDate.toLocaleDateString('en-US', { month: 'long', year: 'numeric' })}
</h2>
</div>
<div className="flex-1 grid grid-cols-7 gap-1 p-2">
{['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'].map(day => (
<div key={day} className="p-2 text-center text-sm font-medium text-foreground bg-secondary border border-border font-mono">
{day.substring(0, 3)}
</div>
))}
{calendarDays.map((day, index) => {
const dayEvents = filteredEvents.filter(event =>
new Date(event.start).toDateString() === day.toDateString()
);
const isToday = day.toDateString() === new Date().toDateString();
const isCurrentMonth = day.getMonth() === currentDate.getMonth();
return (
<div key={index} className={cn(
"border border-border bg-card min-h-[100px] p-1",
!isCurrentMonth && "bg-secondary"
)}>
<div className={cn(
"text-sm font-medium mb-1 font-mono",
isToday ? "bg-primary text-primary-foreground rounded-none px-1" : "text-foreground",
!isCurrentMonth && "text-muted-foreground"
)}>
{day.getDate()}
</div>
<div className="space-y-1">
{dayEvents.slice(0, 3).map(event => (
<div
key={event.id}
className={cn(
"p-1 rounded-none text-xs border-l-2 cursor-pointer border border-border font-mono",
event.category === 'work' && "bg-blue-500/20 border-l-blue-500",
event.category === 'personal' && "bg-green-500/20 border-l-green-500",
event.category === 'important' && "bg-red-500/20 border-l-red-500",
event.category === 'social' && "bg-purple-500/20 border-l-purple-500"
)}
onClick={() => onEventAction('open', event)}
>
<div className="truncate font-medium">{event.title}</div>
</div>
))}
{dayEvents.length > 3 && (
<div className="text-xs text-foreground text-center font-mono">
+{dayEvents.length - 3} more
</div>
)}
</div>
</div>
);
})}
</div>
</div>
);
};
const EventDetails = ({ event }) => {
if (!event) {
return (
<div className="flex flex-col items-center justify-center h-full text-center text-foreground bg-background font-mono">
<Calendar className="w-12 h-12 mb-4 text-muted-foreground" />
<div className="text-lg font-medium">No event selected</div>
<div className="text-sm">Select an event to view details</div>
</div>
);
}
return (
<div className="flex flex-col h-full bg-background border-l border-border">
{/* Header */}
<div className="p-4 border-b border-border bg-card">
<div className="flex items-center justify-between mb-2">
<h3 className="text-lg font-semibold text-foreground font-mono">{event.title}</h3>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="bg-secondary hover:bg-secondary/80">
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="bg-popover text-popover-foreground border-border font-mono">
<DropdownMenuItem className="hover:bg-primary hover:text-primary-foreground">
<Edit3 className="h-4 w-4 mr-2" />
Edit
</DropdownMenuItem>
<DropdownMenuItem className="hover:bg-primary hover:text-primary-foreground">
<Forward className="h-4 w-4 mr-2" />
Forward
</DropdownMenuItem>
<DropdownMenuItem className="hover:bg-primary hover:text-primary-foreground">
<Reply className="h-4 w-4 mr-2" />
Reply
</DropdownMenuItem>
<DropdownMenuSeparator className="border-t border-border" />
<DropdownMenuItem className="text-destructive hover:bg-primary hover:text-primary-foreground">
<Trash2 className="h-4 w-4 mr-2" />
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
<Badge variant="outline" className={cn(
"rounded-none border border-border font-mono",
event.category === 'work' && "border-blue-500 text-foreground",
event.category === 'personal' && "border-green-500 text-foreground",
event.category === 'important' && "border-red-500 text-foreground",
event.category === 'social' && "border-purple-500 text-foreground"
)}>
{event.category}
</Badge>
</div>
{/* Content */}
<ScrollArea className="flex-1">
<div className="p-4 space-y-4 font-mono">
{/* Time */}
<div className="flex items-center gap-2">
<Clock className="h-4 w-4 text-foreground" />
<div>
<div className="font-medium">
{formatTime(event.start)} - {formatTime(event.end)}
</div>
<div className="text-sm text-foreground">
{new Date(event.start).toLocaleDateString('en-US', {
weekday: 'long',
month: 'long',
day: 'numeric',
year: 'numeric'
})}
</div>
</div>
</div>
{/* Location */}
{event.location && (
<div className="flex items-center gap-2">
<MapPin className="h-4 w-4 text-foreground" />
<span className="text-foreground">{event.location}</span>
</div>
)}
{/* Organizer */}
<div className="flex items-center gap-2">
<User className="h-4 w-4 text-foreground" />
<div>
<div className="font-medium text-foreground">Organizer</div>
<div className="text-sm text-foreground">{event.organizer}</div>
</div>
</div>
{/* Attendees */}
{event.attendees.length > 0 && (
<div className="flex items-start gap-2">
<Users className="h-4 w-4 text-foreground mt-1" />
<div className="flex-1">
<div className="font-medium mb-1 text-foreground">Attendees ({event.attendees.length})</div>
<div className="space-y-1">
{event.attendees.map((attendee, index) => (
<div key={index} className="text-sm text-foreground flex items-center gap-2">
<div className="w-2 h-2 bg-green-500 rounded-none"></div>
{attendee}
</div>
))}
</div>
</div>
</div>
)}
{/* Description */}
{event.description && (
<div>
<div className="font-medium mb-2 text-foreground">Description</div>
<div className="text-sm text-foreground leading-relaxed">
{event.description}
</div>
</div>
)}
{/* Properties */}
<div className="space-y-2">
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-foreground">Priority</span>
<Badge variant={event.priority === 'high' ? 'destructive' : 'secondary'} className="rounded-none border border-border font-mono">
{event.priority}
</Badge>
</div>
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-foreground">Status</span>
<Badge variant={
event.status === 'accepted' ? 'default' :
event.status === 'tentative' ? 'secondary' : 'destructive'
} className="rounded-none border border-border font-mono">
{event.status}
</Badge>
</div>
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-foreground">Recurring</span>
<Badge variant={event.recurring ? 'default' : 'secondary'} className="rounded-none border border-border font-mono">
{event.recurring ? 'Yes' : 'No'}
</Badge>
</div>
</div>
</div>
</ScrollArea>
{/* Footer */}
<div className="p-4 border-t border-border flex justify-end gap-2">
<Button variant="outline" size="sm" className="rounded-none border border-border font-mono">
<X className="h-4 w-4 mr-2" />
Decline
</Button>
<Button variant="outline" size="sm" className="rounded-none border border-border font-mono">
<Clock className="h-4 w-4 mr-2" />
Tentative
</Button>
<Button size="sm" className="rounded-none border border-border bg-primary text-primary-foreground font-mono hover:bg-primary/90">
<CheckCircle2 className="h-4 w-4 mr-2" />
Accept
</Button>
</div>
</div>
);
};
const CalendarPage = () => {
const [currentDate, setCurrentDate] = useState(new Date());
const [selectedEvent, setSelectedEvent] = useState(null);
const [categories, setCategories] = useState(categoriesData);
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
const handleEventAction = (action, event) => {
switch (action) {
case 'open':
setSelectedEvent(event);
break;
case 'edit':
// Handle edit
break;
case 'delete':
// Handle delete
break;
default:
break;
}
};
const handleCategoryToggle = (categoryName) => {
setCategories(categories.map(cat =>
cat.name === categoryName ? { ...cat, visible: !cat.visible } : cat
));
};
const handleDateSelect = (date) => {
setCurrentDate(date);
};
const shortcuts = [
// Calendar actions row (unique qkeys)
[
{ key: 'Q', label: 'New Event', action: () => console.log('New Event') },
{ key: 'W', label: 'New Meeting', action: () => console.log('New Meeting') },
{ key: 'E', label: 'Edit Event', action: () => console.log('Edit Event') },
{ key: 'R', label: 'Refresh', action: () => console.log('Refresh') },
{ key: 'T', label: 'Today', action: () => setCurrentDate(new Date()) },
{ key: 'Y', label: 'Delete Event', action: () => console.log('Delete Event') },
{ key: 'U', label: 'Duplicate', action: () => console.log('Duplicate Event') },
{ key: 'I', label: 'Invite', action: () => console.log('Invite Attendees') },
{ key: 'O', label: 'Open Event', action: () => console.log('Open Event') },
{ key: 'P', label: 'Print', action: () => console.log('Print Calendar') },
],
// Navigation/info row (unique qkeys)
[
{ key: 'A', label: 'Accept', action: () => console.log('Accept Invitation') },
{ key: 'S', label: 'Send', action: () => console.log('Send Invitation') },
{ key: 'D', label: 'Decline', action: () => console.log('Decline Invitation') },
{ key: 'G', label: 'Go to Date', action: () => console.log('Go to Date') },
{ key: 'H', label: 'Help', action: () => console.log('Help') },
{ key: 'J', label: 'Share', action: () => console.log('Share Calendar') },
{ key: 'K', label: 'Show/Hide Weekends', action: () => console.log('Toggle Weekends') },
{ key: 'L', label: 'List View', action: () => console.log('List View') },
{ key: 'Z', label: 'Month View', action: () => console.log('Month View') },
{ key: 'X', label: 'Week View', action: () => console.log('Week View') },
]
];
return (
<div className="flex flex-col h-[calc(100vh-50px)]">
<div className="flex h-screen bg-background font-mono">
{/* Sidebar */}
<CalendarSidebar
isCollapsed={sidebarCollapsed}
categories={categories}
onCategoryToggle={handleCategoryToggle}
onDateSelect={handleDateSelect}
/>
{/* Main Content */}
<div className="flex-1 flex flex-col">
{/* Toolbar */}
<div className="p-2 border-b border-border flex items-center justify-between bg-card">
<div className="flex items-center gap-2">
<Button
variant="ghost"
size="icon"
onClick={() => setSidebarCollapsed(!sidebarCollapsed)}
className="bg-secondary hover:bg-secondary/80"
>
{sidebarCollapsed ? (
<ChevronRight className="h-4 w-4" />
) : (
<ChevronLeft className="h-4 w-4" />
)}
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => setCurrentDate(new Date())}
className="bg-secondary hover:bg-secondary/80 font-mono"
>
Today
</Button>
<div className="flex items-center gap-1">
<Button
variant="ghost"
size="icon"
onClick={() => {
const newDate = new Date(currentDate);
newDate.setDate(currentDate.getDate() - 1);
setCurrentDate(newDate);
}}
className="bg-secondary hover:bg-secondary/80"
>
<ChevronLeft className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="icon"
onClick={() => {
const newDate = new Date(currentDate);
newDate.setDate(currentDate.getDate() + 1);
setCurrentDate(newDate);
}}
className="bg-secondary hover:bg-secondary/80"
>
<ChevronRight className="h-4 w-4" />
</Button>
</div>
<div className="text-sm font-medium ml-2 text-foreground font-mono">
{formatDate(currentDate)}
</div>
</div>
<div className="flex items-center gap-2">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm" className="bg-secondary hover:bg-secondary/80 border-border rounded-none font-mono">
<Settings className="h-4 w-4 mr-2" />
Settings
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="bg-popover text-popover-foreground border-border font-mono">
<DropdownMenuItem className="hover:bg-primary hover:text-primary-foreground">
<RefreshCw className="h-4 w-4 mr-2" />
Refresh
</DropdownMenuItem>
<DropdownMenuItem className="hover:bg-primary hover:text-primary-foreground">
<Filter className="h-4 w-4 mr-2" />
Filters
</DropdownMenuItem>
<DropdownMenuSeparator className="border-t border-border" />
<DropdownMenuItem className="hover:bg-primary hover:text-primary-foreground">
<PrinterIcon className="h-4 w-4 mr-2" />
Print
</DropdownMenuItem>
<DropdownMenuItem className="hover:bg-primary hover:text-primary-foreground">
<Share2 className="h-4 w-4 mr-2" />
Share
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
{/* Calendar Area */}
<div className="flex-1 flex">
{/* Three calendar views on the left */}
<div className="w-[60%] flex flex-col border-r border-border">
<div className="h-1/3 border-b border-border">
<CalendarView
view="day"
events={sampleEvents}
currentDate={currentDate}
onEventAction={handleEventAction}
/>
</div>
<div className="h-1/3 border-b border-border">
<CalendarView
view="week"
events={sampleEvents}
currentDate={currentDate}
onEventAction={handleEventAction}
/>
</div>
<div className="h-1/3">
<CalendarView
view="month"
events={sampleEvents}
currentDate={currentDate}
onEventAction={handleEventAction}
/>
</div>
</div>
{/* Event details on the right */}
<div className="w-[40%]">
<EventDetails event={selectedEvent} />
</div>
</div>
</div>
</div>
<Footer shortcuts={shortcuts} />
</div>
);
};
export default CalendarPage;