Some checks failed
CI/CD Pipeline / Backend Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Tests (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
Complete rewrite from Express to NestJS with enterprise-grade features: ## Backend Improvements - Migrated from Express to NestJS 11.0.1 with TypeScript - Implemented Prisma ORM 7.3.0 for type-safe database access - Added CASL authorization system replacing role-based guards - Created global exception filters with structured logging - Implemented Auth0 JWT authentication with Passport.js - Added vehicle management with conflict detection - Enhanced event scheduling with driver/vehicle assignment - Comprehensive error handling and logging ## Frontend Improvements - Upgraded to React 19.2.0 with Vite 7.2.4 - Implemented CASL-based permission system - Added AbilityContext for declarative permissions - Created ErrorHandler utility for consistent error messages - Enhanced API client with request/response logging - Added War Room (Command Center) dashboard - Created VIP Schedule view with complete itineraries - Implemented Vehicle Management UI - Added mock data generators for testing (288 events across 20 VIPs) ## New Features - Vehicle fleet management (types, capacity, status tracking) - Complete 3-day Jamboree schedule generation - Individual VIP schedule pages with PDF export (planned) - Real-time War Room dashboard with auto-refresh - Permission-based navigation filtering - First user auto-approval as administrator ## Documentation - Created CASL_AUTHORIZATION.md (comprehensive guide) - Created ERROR_HANDLING.md (error handling patterns) - Updated CLAUDE.md with new architecture - Added migration guides and best practices ## Technical Debt Resolved - Removed custom authentication in favor of Auth0 - Replaced role checks with CASL abilities - Standardized error responses across API - Implemented proper TypeScript typing - Added comprehensive logging Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
160 lines
5.0 KiB
TypeScript
160 lines
5.0 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-white 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">
|
|
<h2 className="text-2xl font-bold text-gray-900">
|
|
{driver ? 'Edit Driver' : 'Add New Driver'}
|
|
</h2>
|
|
<button
|
|
onClick={onCancel}
|
|
className="text-gray-400 hover:text-gray-600"
|
|
>
|
|
<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-gray-700 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-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary"
|
|
/>
|
|
</div>
|
|
|
|
{/* Phone */}
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 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-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary"
|
|
/>
|
|
</div>
|
|
|
|
{/* Department */}
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
Department
|
|
</label>
|
|
<select
|
|
name="department"
|
|
value={formData.department}
|
|
onChange={handleChange}
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-md 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-gray-700 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-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary"
|
|
/>
|
|
<p className="mt-1 text-xs text-gray-500">
|
|
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-gray-200 text-gray-800 py-2 px-4 rounded-md hover:bg-gray-300"
|
|
>
|
|
Cancel
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|