Files
vip-coordinator/frontend/src/components/DriverForm.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

160 lines
5.2 KiB
TypeScript

import { useState } from 'react';
import { X } from 'lucide-react';
interface DriverFormProps {
driver?: Driver | null;
onSubmit: (data: DriverFormData) => void;
onCancel: () => void;
isSubmitting: boolean;
}
interface Driver {
id: string;
name: string;
phone: string;
department: string | null;
userId: string | null;
}
export interface DriverFormData {
name: string;
phone: string;
department?: string;
userId?: string;
}
export function DriverForm({ driver, onSubmit, onCancel, isSubmitting }: DriverFormProps) {
const [formData, setFormData] = useState<DriverFormData>({
name: driver?.name || '',
phone: driver?.phone || '',
department: driver?.department || 'OFFICE_OF_DEVELOPMENT',
userId: driver?.userId || '',
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
// Clean up the data - remove empty strings for optional fields
const cleanedData = {
...formData,
department: formData.department || undefined,
userId: formData.userId || undefined,
};
onSubmit(cleanedData);
};
const handleChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>
) => {
const { name, value } = e.target;
setFormData((prev) => ({
...prev,
[name]: value,
}));
};
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
<div className="bg-card rounded-lg shadow-xl max-w-2xl w-full max-h-[90vh] overflow-y-auto">
<div className="flex items-center justify-between p-6 border-b border-border">
<h2 className="text-2xl font-bold text-foreground">
{driver ? 'Edit Driver' : 'Add New Driver'}
</h2>
<button
onClick={onCancel}
className="text-muted-foreground hover:text-foreground"
>
<X className="h-6 w-6" />
</button>
</div>
<form onSubmit={handleSubmit} className="p-6 space-y-4">
{/* Name */}
<div>
<label className="block text-sm font-medium text-foreground mb-1">
Full Name *
</label>
<input
type="text"
name="name"
required
value={formData.name}
onChange={handleChange}
className="w-full px-3 py-2 border border-input rounded-md bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary"
/>
</div>
{/* Phone */}
<div>
<label className="block text-sm font-medium text-foreground mb-1">
Phone Number *
</label>
<input
type="tel"
name="phone"
required
value={formData.phone}
onChange={handleChange}
className="w-full px-3 py-2 border border-input rounded-md bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary"
/>
</div>
{/* Department */}
<div>
<label className="block text-sm font-medium text-foreground mb-1">
Department
</label>
<select
name="department"
value={formData.department}
onChange={handleChange}
className="w-full px-3 py-2 border border-input rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary"
>
<option value="">Select Department</option>
<option value="OFFICE_OF_DEVELOPMENT">Office of Development</option>
<option value="ADMIN">Admin</option>
</select>
</div>
{/* User ID (optional, for linking driver to user account) */}
<div>
<label className="block text-sm font-medium text-foreground mb-1">
User Account ID (Optional)
</label>
<input
type="text"
name="userId"
value={formData.userId}
onChange={handleChange}
placeholder="Leave blank for standalone driver"
className="w-full px-3 py-2 border border-input rounded-md bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary"
/>
<p className="mt-1 text-xs text-muted-foreground">
Link this driver to a user account for login access
</p>
</div>
{/* Actions */}
<div className="flex gap-3 pt-4">
<button
type="submit"
disabled={isSubmitting}
className="flex-1 bg-primary text-white py-2 px-4 rounded-md hover:bg-primary/90 disabled:opacity-50"
>
{isSubmitting ? 'Saving...' : driver ? 'Update Driver' : 'Create Driver'}
</button>
<button
type="button"
onClick={onCancel}
className="flex-1 bg-muted text-foreground py-2 px-4 rounded-md hover:bg-muted/80"
>
Cancel
</button>
</div>
</form>
</div>
</div>
);
}