Files
vip-coordinator/frontend/src/components/ThemeToggle.tsx
kyle 3b0b1205df feat: comprehensive update with Signal, Copilot, themes, and PDF features
## Signal Messaging Integration
- Added SignalService for sending messages to drivers via Signal
- SignalMessage model for tracking message history
- Driver chat modal for real-time messaging
- Send schedule via Signal (ICS + PDF attachments)

## AI Copilot
- Natural language interface for VIP Coordinator
- Capabilities: create VIPs, schedule events, assign drivers
- Help and guidance for users
- Floating copilot button in UI

## Theme System
- Dark/light/system theme support
- Color scheme selection (blue, green, purple, orange, red)
- ThemeContext for global state
- AppearanceMenu in header

## PDF Schedule Export
- VIPSchedulePDF component for schedule generation
- PDF settings (header, footer, branding)
- Preview PDF in browser
- Settings stored in database

## Database Migrations
- add_signal_messages: SignalMessage model
- add_pdf_settings: Settings model for PDF config
- add_reminder_tracking: lastReminderSent for events
- make_driver_phone_optional: phone field nullable

## Event Management
- Event status service for automated updates
- IN_PROGRESS/COMPLETED status tracking
- Reminder tracking for notifications

## UI/UX Improvements
- Driver schedule modal
- Improved My Schedule page
- Better error handling and loading states
- Responsive design improvements

## Other Changes
- AGENT_TEAM.md documentation
- Seed data improvements
- Ability factory updates
- Driver profile page

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 19:30:41 +01:00

66 lines
2.3 KiB
TypeScript

import { useState, useRef, useEffect } from 'react';
import { Sun, Moon, Monitor } from 'lucide-react';
import { useTheme, ThemeMode } from '@/hooks/useTheme';
const modes: { value: ThemeMode; label: string; icon: typeof Sun }[] = [
{ value: 'light', label: 'Light', icon: Sun },
{ value: 'dark', label: 'Dark', icon: Moon },
{ value: 'system', label: 'System', icon: Monitor },
];
export function ThemeToggle() {
const { mode, resolvedTheme, setMode } = useTheme();
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
// Close dropdown when clicking outside
useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
setIsOpen(false);
}
}
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
// Get current icon based on resolved theme
const CurrentIcon = resolvedTheme === 'dark' ? Moon : Sun;
return (
<div className="relative" ref={dropdownRef}>
<button
onClick={() => setIsOpen(!isOpen)}
className="flex items-center justify-center w-9 h-9 rounded-lg bg-muted hover:bg-accent transition-colors focus-ring"
aria-label={`Current theme: ${mode}. Click to change.`}
aria-expanded={isOpen}
aria-haspopup="true"
>
<CurrentIcon className="h-5 w-5 text-foreground" />
</button>
{isOpen && (
<div className="absolute right-0 mt-2 w-36 rounded-lg bg-popover border border-border shadow-elevated z-50 overflow-hidden animate-in fade-in slide-in-from-top-2 duration-150">
{modes.map(({ value, label, icon: Icon }) => (
<button
key={value}
onClick={() => {
setMode(value);
setIsOpen(false);
}}
className={`flex items-center gap-3 w-full px-3 py-2.5 text-sm transition-colors ${
mode === value
? 'bg-primary/10 text-primary font-medium'
: 'text-popover-foreground hover:bg-accent'
}`}
>
<Icon className="h-4 w-4" />
{label}
</button>
))}
</div>
)}
</div>
);
}