feat: add Help page with search, streamline copilot, misc UI fixes

Adds searchable Help/User Guide page, trims copilot tool bloat,
adds OTHER department option, and various form/layout improvements.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-07 19:42:39 +01:00
parent b35c14fddc
commit 74a292ea93
33 changed files with 1815 additions and 453 deletions

View File

@@ -0,0 +1,2 @@
-- AlterEnum
ALTER TYPE "Department" ADD VALUE 'OTHER';

View File

@@ -139,6 +139,10 @@ async function main() {
airportPickup: true,
venueTransport: true,
partySize: 3, // Roger + 2 handlers
phone: '+1 (202) 555-0140',
email: 'roger.mosby@scouting.org',
emergencyContactName: 'Linda Mosby',
emergencyContactPhone: '+1 (202) 555-0141',
notes: 'Chief Scout Executive. Travels with 2 staff handlers. Requires accessible vehicle.',
flights: {
create: [
@@ -167,6 +171,10 @@ async function main() {
airportPickup: true,
venueTransport: true,
partySize: 2, // Patricia + spouse
phone: '+1 (404) 555-0230',
email: 'patricia.hawkins@bsaboard.org',
emergencyContactName: 'Richard Hawkins',
emergencyContactPhone: '+1 (404) 555-0231',
notes: 'National Board Chair. Traveling with husband (Richard). Both attend all events.',
flights: {
create: [
@@ -195,6 +203,10 @@ async function main() {
airportPickup: true,
venueTransport: true,
partySize: 1, // Solo
phone: '+1 (214) 555-0375',
email: 'jwhitfield@whitfieldfoundation.org',
emergencyContactName: 'Catherine Whitfield',
emergencyContactPhone: '+1 (214) 555-0376',
notes: 'Major donor ($2M+). Eagle Scout class of 1978. Very punctual — do not be late.',
flights: {
create: [
@@ -223,6 +235,10 @@ async function main() {
airportPickup: true,
venueTransport: true,
partySize: 2, // Dr. Baker + assistant
phone: '+1 (301) 555-0488',
email: 'abaker@natgeo.com',
emergencyContactName: 'Marcus Webb',
emergencyContactPhone: '+1 (301) 555-0489',
notes: 'Keynote speaker, Day 1. Traveling with assistant (Marcus). Needs quiet space before keynote.',
flights: {
create: [
@@ -252,6 +268,10 @@ async function main() {
airportPickup: false,
venueTransport: true,
partySize: 4, // Governor + security officer + aide + driver (their own driver stays)
phone: '+1 (303) 555-0100',
email: 'gov.martinez@state.co.us',
emergencyContactName: 'Elena Martinez',
emergencyContactPhone: '+1 (303) 555-0101',
notes: 'Governor arriving by motorcade. Party of 4: Gov, 1 state trooper, 1 aide, 1 advance staff. Their driver does NOT need a seat.',
},
});
@@ -267,6 +287,10 @@ async function main() {
airportPickup: false,
venueTransport: true,
partySize: 1,
phone: '+1 (720) 555-0550',
email: 'somalley@denvercouncil.org',
emergencyContactName: 'Patrick O\'Malley',
emergencyContactPhone: '+1 (720) 555-0551',
notes: 'Local council president. Knows the venue well. Can help with directions if needed.',
},
});

View File

@@ -31,7 +31,7 @@ export class CopilotService {
properties: {
name: { type: 'string', description: 'VIP name to search for (partial match)' },
organization: { type: 'string', description: 'Organization name to filter by' },
department: { type: 'string', enum: ['OFFICE_OF_DEVELOPMENT', 'ADMIN'], description: 'Department to filter by' },
department: { type: 'string', enum: ['OFFICE_OF_DEVELOPMENT', 'ADMIN', 'OTHER'], description: 'Department to filter by' },
arrivalMode: { type: 'string', enum: ['FLIGHT', 'SELF_DRIVING'], description: 'Arrival mode to filter by' },
},
required: [],
@@ -55,7 +55,7 @@ export class CopilotService {
type: 'object' as const,
properties: {
name: { type: 'string', description: 'Driver name to search for' },
department: { type: 'string', enum: ['OFFICE_OF_DEVELOPMENT', 'ADMIN'], description: 'Department to filter by' },
department: { type: 'string', enum: ['OFFICE_OF_DEVELOPMENT', 'ADMIN', 'OTHER'], description: 'Department to filter by' },
availableOnly: { type: 'boolean', description: 'Only return available drivers' },
},
required: [],
@@ -240,31 +240,43 @@ export class CopilotService {
properties: {
name: { type: 'string', description: 'VIP full name' },
organization: { type: 'string', description: 'Organization/company name' },
department: { type: 'string', enum: ['OFFICE_OF_DEVELOPMENT', 'ADMIN'], description: 'Department' },
department: { type: 'string', enum: ['OFFICE_OF_DEVELOPMENT', 'ADMIN', 'OTHER'], description: 'Department' },
arrivalMode: { type: 'string', enum: ['FLIGHT', 'SELF_DRIVING'], description: 'How VIP will arrive' },
expectedArrival: { type: 'string', description: 'Expected arrival time for self-driving (ISO format)' },
airportPickup: { type: 'boolean', description: 'Whether VIP needs airport pickup' },
venueTransport: { type: 'boolean', description: 'Whether VIP needs venue transport' },
partySize: { type: 'number', description: 'Total party size including VIP plus companions/entourage (default 1)' },
notes: { type: 'string', description: 'Additional notes about the VIP' },
isRosterOnly: { type: 'boolean', description: 'True if VIP is roster-only (accountability tracking, no active transport coordination)' },
phone: { type: 'string', description: 'VIP phone number' },
email: { type: 'string', description: 'VIP email address' },
emergencyContactName: { type: 'string', description: 'Emergency contact name' },
emergencyContactPhone: { type: 'string', description: 'Emergency contact phone' },
},
required: ['name', 'department', 'arrivalMode'],
},
},
{
name: 'update_vip',
description: 'Update VIP information.',
description: 'Update VIP information including party size, contact info, and roster status.',
input_schema: {
type: 'object' as const,
properties: {
vipId: { type: 'string', description: 'The VIP ID to update' },
name: { type: 'string', description: 'New name' },
organization: { type: 'string', description: 'New organization' },
department: { type: 'string', enum: ['OFFICE_OF_DEVELOPMENT', 'ADMIN'], description: 'New department' },
department: { type: 'string', enum: ['OFFICE_OF_DEVELOPMENT', 'ADMIN', 'OTHER'], description: 'New department' },
arrivalMode: { type: 'string', enum: ['FLIGHT', 'SELF_DRIVING'], description: 'New arrival mode' },
expectedArrival: { type: 'string', description: 'New expected arrival time' },
airportPickup: { type: 'boolean', description: 'Whether VIP needs airport pickup' },
venueTransport: { type: 'boolean', description: 'Whether VIP needs venue transport' },
partySize: { type: 'number', description: 'Total party size including VIP plus companions/entourage' },
notes: { type: 'string', description: 'New notes' },
isRosterOnly: { type: 'boolean', description: 'True if VIP is roster-only (no active transport coordination)' },
phone: { type: 'string', description: 'VIP phone number' },
email: { type: 'string', description: 'VIP email address' },
emergencyContactName: { type: 'string', description: 'Emergency contact name' },
emergencyContactPhone: { type: 'string', description: 'Emergency contact phone' },
},
required: ['vipId'],
},
@@ -290,7 +302,7 @@ export class CopilotService {
driverId: { type: 'string', description: 'The driver ID to update' },
name: { type: 'string', description: 'New name' },
phone: { type: 'string', description: 'New phone number' },
department: { type: 'string', enum: ['OFFICE_OF_DEVELOPMENT', 'ADMIN'], description: 'New department' },
department: { type: 'string', enum: ['OFFICE_OF_DEVELOPMENT', 'ADMIN', 'OTHER'], description: 'New department' },
isAvailable: { type: 'boolean', description: 'Whether driver is available' },
shiftStartTime: { type: 'string', description: 'Shift start time (HH:MM format)' },
shiftEndTime: { type: 'string', description: 'Shift end time (HH:MM format)' },
@@ -358,7 +370,7 @@ export class CopilotService {
properties: {
startTime: { type: 'string', description: 'Start time of the time range (ISO format)' },
endTime: { type: 'string', description: 'End time of the time range (ISO format)' },
preferredDepartment: { type: 'string', enum: ['OFFICE_OF_DEVELOPMENT', 'ADMIN'], description: 'Optional: filter by department' },
preferredDepartment: { type: 'string', enum: ['OFFICE_OF_DEVELOPMENT', 'ADMIN', 'OTHER'], description: 'Optional: filter by department' },
},
required: ['startTime', 'endTime'],
},
@@ -491,71 +503,16 @@ export class CopilotService {
required: ['startDate', 'endDate'],
},
},
// ==================== SELF-AWARENESS / HELP TOOLS ====================
{
name: 'get_my_capabilities',
description: 'Get a comprehensive list of all available tools and capabilities. Use this when the user asks "what can you do?" or when you need to understand your own capabilities. Returns tools organized by category with usage examples.',
input_schema: {
type: 'object' as const,
properties: {
category: {
type: 'string',
enum: ['all', 'search', 'create', 'update', 'delete', 'communication', 'analytics', 'scheduling'],
description: 'Filter by category (optional, defaults to all)'
},
},
required: [],
},
},
{
name: 'get_workflow_guide',
description: 'Get step-by-step guidance for common VIP coordination tasks. Use this when you need to understand how to accomplish a complex task or when the user asks "how do I...?" questions.',
input_schema: {
type: 'object' as const,
properties: {
task: {
type: 'string',
enum: [
'schedule_airport_pickup',
'reassign_driver',
'handle_flight_delay',
'create_vip_itinerary',
'morning_briefing',
'send_driver_notifications',
'check_schedule_problems',
'vehicle_assignment',
'bulk_schedule_update'
],
description: 'The task to get guidance for'
},
},
required: ['task'],
},
},
// ==================== SYSTEM STATUS ====================
{
name: 'get_current_system_status',
description: 'Get a quick status overview of the entire VIP Coordinator system - counts of VIPs, drivers, vehicles, upcoming events, and any immediate issues. Use this to understand the current state of operations.',
description: 'Get system overview: VIP/driver/vehicle counts, today\'s events, alerts.',
input_schema: {
type: 'object' as const,
properties: {},
required: [],
},
},
{
name: 'get_api_documentation',
description: 'Get documentation for the VIP Coordinator REST API endpoints. Use this when the user asks about API capabilities, how to integrate, or what endpoints are available. Shows all HTTP endpoints with methods, paths, and descriptions.',
input_schema: {
type: 'object' as const,
properties: {
resource: {
type: 'string',
enum: ['all', 'auth', 'users', 'vips', 'drivers', 'events', 'vehicles', 'flights', 'signal'],
description: 'Filter by resource type (optional, defaults to all)'
},
},
required: [],
},
},
];
constructor(
@@ -662,37 +619,31 @@ User role: ${userRole}
## Your capabilities:
- Search and retrieve information about VIPs, drivers, vehicles, events, and flights
- CREATE new VIPs, events, and flights
- UPDATE existing events, flights, VIP info, and driver assignments
- UPDATE existing events, flights, VIP info (including party size, contact info, roster status), and driver assignments
- DELETE events and flights that are no longer needed
- Assign/reassign drivers and vehicles to events
- Check for scheduling conflicts and identify gaps
- Get VIP itineraries and driver manifests
- **BULK REASSIGN** events from one driver to another by NAME
- **SEND MESSAGES** to drivers via Signal
- **SEND SCHEDULES** to drivers (PDF/ICS via Signal)
- **FIND AVAILABLE DRIVERS** for specific time ranges
- **SUGGEST VEHICLES** based on capacity and availability
- **AUDIT SCHEDULES** for conflicts, unassigned events, and capacity issues
- **WORKLOAD ANALYSIS** for driver utilization
- Bulk reassign events, send Signal messages/schedules, find available drivers
- Suggest vehicles, audit schedules, analyze workloads
## IMPORTANT: Use the right tool for the job
- To MODIFY an existing event → use update_event (don't create a new one)
- To MODIFY an existing event → use update_event
- To REMOVE an event → use delete_event
- To CHANGE a flight → use update_flight
- To ADD a flight → use create_flight
- To ASSIGN a driver → use assign_driver_to_event
- To ASSIGN a vehicle → use assign_vehicle_to_event
- **To REASSIGN all events from one driver to another**use reassign_driver_events (works by NAME, no IDs needed!)
- **To SEND a message to a driver** → use send_driver_notification_via_signal
- **To SEND schedules to drivers**use bulk_send_driver_schedules
- **To FIND available drivers** → use find_available_drivers_for_timerange
- **To GET a driver's daily schedule** → use get_daily_driver_manifest
- **To FIND unassigned events** → use find_unassigned_events
- **To CHECK for VIP conflicts** → use check_vip_conflicts
- **To AUDIT the schedule** → use identify_scheduling_gaps
- **To SUGGEST vehicles**use suggest_vehicle_for_event
- **To GET vehicle schedule** → use get_vehicle_schedule
- **To ANALYZE workload** → use get_driver_workload_summary
- To CHANGE a flight → use update_flight; ADD a flight → create_flight
- To ASSIGN a driver → assign_driver_to_event; vehicle → assign_vehicle_to_event
- To REASSIGN events between driversreassign_driver_events (by NAME)
- To UPDATE VIP party size, contacts, or roster status → use update_vip
- To SEND a message to a driver → send_driver_notification_via_signal
- To SEND schedules → bulk_send_driver_schedules
- To FIND available drivers → find_available_drivers_for_timerange
- To AUDIT schedule → identify_scheduling_gaps
- To SUGGEST vehicles → suggest_vehicle_for_event
## Party size and companions:
- partySize = the VIP + all companions/handlers/entourage
- If user says "add 20 companions" → set partySize to 21 (VIP + 20)
- Always use update_vip with partySize to change this, not notes
## CRITICAL: Never ask for IDs - use names!
- You can search for drivers, VIPs, vehicles, and events by NAME
@@ -703,8 +654,12 @@ User role: ${userRole}
## For actions that MODIFY data (create, update, delete):
1. First, search to find the relevant records (use names, not IDs)
2. Clearly state what changes you're proposing
3. Ask for confirmation before executing
4. After execution, show a summary of what was changed
3. For BULK operations (updating many records, reassigning multiple events, etc.):
- Tell the user upfront: "This is a larger task - I'll be updating X records. Give me a moment to work through them all."
- Then proceed immediately with all the tool calls WITHOUT waiting for confirmation
- Summarize all changes at the end
4. For single-record changes, ask for confirmation before executing
5. After execution, show a summary of what was changed
## When reassigning a driver's events (driver sick, swapping schedules, etc.):
1. Use reassign_driver_events with the FROM and TO driver names
@@ -801,14 +756,8 @@ User role: ${userRole}
return await this.getVehicleSchedule(input);
case 'get_driver_workload_summary':
return await this.getDriverWorkloadSummary(input);
case 'get_my_capabilities':
return await this.getMyCapabilities(input);
case 'get_workflow_guide':
return await this.getWorkflowGuide(input);
case 'get_current_system_status':
return await this.getCurrentSystemStatus();
case 'get_api_documentation':
return await this.getApiDocumentation(input);
default:
return { success: false, error: `Unknown tool: ${name}` };
}
@@ -1344,7 +1293,13 @@ User role: ${userRole}
expectedArrival: input.expectedArrival ? new Date(input.expectedArrival) : null,
airportPickup: input.airportPickup ?? false,
venueTransport: input.venueTransport ?? false,
partySize: input.partySize ?? 1,
notes: input.notes,
isRosterOnly: input.isRosterOnly ?? false,
phone: input.phone || null,
email: input.email || null,
emergencyContactName: input.emergencyContactName || null,
emergencyContactPhone: input.emergencyContactPhone || null,
},
});
@@ -1362,7 +1317,13 @@ User role: ${userRole}
if (updateData.expectedArrival !== undefined) data.expectedArrival = updateData.expectedArrival ? new Date(updateData.expectedArrival) : null;
if (updateData.airportPickup !== undefined) data.airportPickup = updateData.airportPickup;
if (updateData.venueTransport !== undefined) data.venueTransport = updateData.venueTransport;
if (updateData.partySize !== undefined) data.partySize = updateData.partySize;
if (updateData.notes !== undefined) data.notes = updateData.notes;
if (updateData.isRosterOnly !== undefined) data.isRosterOnly = updateData.isRosterOnly;
if (updateData.phone !== undefined) data.phone = updateData.phone || null;
if (updateData.email !== undefined) data.email = updateData.email || null;
if (updateData.emergencyContactName !== undefined) data.emergencyContactName = updateData.emergencyContactName || null;
if (updateData.emergencyContactPhone !== undefined) data.emergencyContactPhone = updateData.emergencyContactPhone || null;
const vip = await this.prisma.vIP.update({
where: { id: vipId },
@@ -2711,223 +2672,6 @@ User role: ${userRole}
};
}
// ==================== SELF-AWARENESS / HELP TOOLS ====================
private async getMyCapabilities(input: Record<string, any>): Promise<ToolResult> {
const category = input.category || 'all';
const capabilities = {
search: {
description: 'Find and lookup information',
tools: [
{ name: 'search_vips', description: 'Search VIPs by name, organization, department', example: 'Find VIP named John' },
{ name: 'search_drivers', description: 'Search drivers by name, department, availability', example: 'Find available drivers' },
{ name: 'search_events', description: 'Search events by VIP, driver, date, status', example: 'Events for tomorrow' },
{ name: 'get_vip_details', description: 'Get full VIP info with flights and events', example: 'Details for VIP John Smith' },
{ name: 'get_vip_itinerary', description: 'Get complete VIP itinerary', example: 'Itinerary for Dr. Martinez' },
{ name: 'list_all_drivers', description: 'List all drivers in system', example: 'Show all drivers' },
{ name: 'get_available_vehicles', description: 'Find available vehicles by type/capacity', example: 'Available SUVs' },
{ name: 'find_available_drivers_for_timerange', description: 'Find drivers free during specific time', example: 'Who is free 2-4pm tomorrow?' },
{ name: 'get_daily_driver_manifest', description: 'Full daily schedule for a driver', example: "What's Mike's schedule today?" },
{ name: 'get_vehicle_schedule', description: 'Get vehicle assignments for date range', example: 'Blue van schedule this week' },
],
},
create: {
description: 'Create new records',
tools: [
{ name: 'create_vip', description: 'Create a new VIP profile', example: 'Add VIP Jane Doe from Acme Corp' },
{ name: 'create_event', description: 'Schedule a new event/transport', example: 'Schedule pickup at 3pm' },
{ name: 'create_flight', description: 'Add flight for a VIP', example: 'Add flight AA1234 for John' },
],
},
update: {
description: 'Modify existing records',
tools: [
{ name: 'update_vip', description: 'Update VIP information', example: 'Change VIP phone number' },
{ name: 'update_event', description: 'Modify event details, time, location', example: 'Move pickup to 4pm' },
{ name: 'update_flight', description: 'Update flight times/status', example: 'Flight delayed to 5pm' },
{ name: 'update_driver', description: 'Update driver info, availability', example: 'Mark driver as unavailable' },
{ name: 'assign_driver_to_event', description: 'Assign/change driver for event', example: 'Assign Mike to this pickup' },
{ name: 'assign_vehicle_to_event', description: 'Assign/change vehicle for event', example: 'Use the SUV for this trip' },
{ name: 'reassign_driver_events', description: 'Bulk reassign from one driver to another', example: 'Move all of John\'s events to Mike' },
],
},
delete: {
description: 'Remove records (soft delete)',
tools: [
{ name: 'delete_event', description: 'Cancel/delete an event', example: 'Cancel the 3pm pickup' },
{ name: 'delete_flight', description: 'Remove a flight record', example: 'Delete cancelled flight' },
],
},
communication: {
description: 'Driver messaging via Signal',
tools: [
{ name: 'send_driver_notification_via_signal', description: 'Send message to driver', example: 'Tell Mike the pickup is delayed' },
{ name: 'bulk_send_driver_schedules', description: 'Send schedules to all drivers', example: 'Send tomorrow\'s schedules to everyone' },
],
},
analytics: {
description: 'Reports, summaries, and audits',
tools: [
{ name: 'get_todays_summary', description: 'Today\'s events, arrivals, stats', example: "What's happening today?" },
{ name: 'get_weekly_lookahead', description: 'Week-by-week event summary', example: 'What does next week look like?' },
{ name: 'get_driver_workload_summary', description: 'Driver utilization statistics', example: 'Who is overworked this week?' },
{ name: 'identify_scheduling_gaps', description: 'Find problems in schedule', example: 'Any issues with the schedule?' },
{ name: 'find_unassigned_events', description: 'Events without driver/vehicle', example: 'What needs assignment?' },
],
},
scheduling: {
description: 'Conflict detection and scheduling',
tools: [
{ name: 'check_driver_conflicts', description: 'Check driver for time conflicts', example: 'Can Mike do 2-4pm?' },
{ name: 'check_vip_conflicts', description: 'Check VIP for double-booking', example: 'Is VIP free at 3pm?' },
{ name: 'suggest_vehicle_for_event', description: 'Recommend best vehicle', example: 'What vehicle for 6 passengers?' },
{ name: 'get_driver_schedule', description: 'Driver events for date range', example: 'Mike\'s schedule this week' },
],
},
help: {
description: 'Self-help and system info',
tools: [
{ name: 'get_my_capabilities', description: 'List all available tools (this tool)', example: 'What can you do?' },
{ name: 'get_workflow_guide', description: 'Step-by-step task guides', example: 'How do I handle a flight delay?' },
{ name: 'get_current_system_status', description: 'System overview and stats', example: 'System status' },
],
},
};
const validCategory = category as keyof typeof capabilities;
if (category !== 'all' && capabilities[validCategory]) {
return {
success: true,
data: { [category]: capabilities[validCategory] },
message: `Showing ${category} tools. I have ${capabilities[validCategory].tools.length} tools in this category.`,
};
}
const totalTools = Object.values(capabilities).reduce((sum, cat) => sum + cat.tools.length, 0);
return {
success: true,
data: capabilities,
message: `I have ${totalTools} tools available across ${Object.keys(capabilities).length} categories. Ask me about any specific capability!`,
};
}
private async getWorkflowGuide(input: Record<string, any>): Promise<ToolResult> {
const workflows = {
schedule_airport_pickup: {
title: 'Schedule an Airport Pickup',
steps: [
'1. Find the VIP: Use search_vips with the VIP name',
'2. Get flight info: Use get_flights_for_vip or check if flight already exists',
'3. Find available driver: Use find_available_drivers_for_timerange for the arrival time',
'4. Find suitable vehicle: Use suggest_vehicle_for_event or get_available_vehicles',
'5. Create the event: Use create_event with type TRANSPORT, pickup at airport, dropoff at destination',
'6. Notify driver: Use send_driver_notification_via_signal to inform them',
],
tips: ['Add 30 min buffer after flight arrival', 'Check flight status before the day'],
},
reassign_driver: {
title: 'Reassign a Driver\'s Events (Driver Sick/Unavailable)',
steps: [
'1. Use reassign_driver_events with fromDriverName and toDriverName',
'2. Optionally specify a date to only reassign that day\'s events',
'3. Review the returned list of reassigned events',
'4. Use send_driver_notification_via_signal to notify both drivers',
],
tips: ['Check the new driver has no conflicts first', 'Update driver availability with update_driver'],
},
handle_flight_delay: {
title: 'Handle a Flight Delay',
steps: [
'1. Find the VIP: Use search_vips to get VIP info',
'2. Update flight: Use update_flight with new arrival time',
'3. Find affected events: Use search_events filtered by VIP and date',
'4. Update pickup event: Use update_event to adjust start/end times',
'5. Check driver conflicts: Use check_driver_conflicts for new time',
'6. Notify driver: Use send_driver_notification_via_signal about the change',
],
tips: ['Check if other VIPs share the same flight', 'Consider ripple effects on later events'],
},
create_vip_itinerary: {
title: 'Create a Complete VIP Itinerary',
steps: [
'1. Create VIP: Use create_vip with all details',
'2. Add flights: Use create_flight for each flight segment',
'3. Create events: Use create_event for airport pickup, meetings, dinners, etc.',
'4. Assign resources: Use assign_driver_to_event and assign_vehicle_to_event',
'5. Review: Use get_vip_itinerary to see the complete schedule',
],
tips: ['Schedule in chronological order', 'Add buffer time between events'],
},
morning_briefing: {
title: 'Get Morning Briefing',
steps: [
'1. Get today summary: Use get_todays_summary for overview',
'2. Check for problems: Use identify_scheduling_gaps',
'3. Review unassigned: Use find_unassigned_events',
'4. Check workload: Use get_driver_workload_summary for balance',
'5. Send schedules: Use bulk_send_driver_schedules to notify all drivers',
],
tips: ['Do this 30 min before operations start', 'Address gaps before drivers arrive'],
},
send_driver_notifications: {
title: 'Send Notifications to Drivers',
steps: [
'1. For single driver: Use send_driver_notification_via_signal with message',
'2. For all drivers (schedules): Use bulk_send_driver_schedules with date',
'3. Reference specific event: Include relatedEventId for context',
],
tips: ['Keep messages concise', 'Include pickup time and location'],
},
check_schedule_problems: {
title: 'Audit Schedule for Problems',
steps: [
'1. Run full audit: Use identify_scheduling_gaps with lookahead days',
'2. Review driver conflicts: Check the conflicts list',
'3. Review VIP conflicts: Check for double-bookings',
'4. Review unassigned: Check events missing drivers/vehicles',
'5. Fix issues: Use update_event, assign_driver_to_event as needed',
],
tips: ['Run this daily', 'Prioritize same-day issues'],
},
vehicle_assignment: {
title: 'Choose and Assign the Right Vehicle',
steps: [
'1. Get recommendation: Use suggest_vehicle_for_event with eventId',
'2. Or search manually: Use get_available_vehicles with type/capacity filters',
'3. Check availability: Use get_vehicle_schedule to see existing assignments',
'4. Assign: Use assign_vehicle_to_event',
],
tips: ['Consider VIP count for capacity', 'Check vehicle location if multiple stops'],
},
bulk_schedule_update: {
title: 'Handle Bulk Schedule Changes',
steps: [
'1. Search affected events: Use search_events with date/driver/VIP filter',
'2. Update each event: Use update_event for each (or reassign_driver_events for driver swap)',
'3. Notify drivers: Use send_driver_notification_via_signal or bulk_send_driver_schedules',
],
tips: ['Work chronologically', 'Verify no new conflicts after changes'],
},
};
const task = input.task as keyof typeof workflows;
if (!workflows[task]) {
return {
success: false,
error: `Unknown workflow: ${task}. Available: ${Object.keys(workflows).join(', ')}`,
};
}
const workflow = workflows[task];
return {
success: true,
data: workflow,
message: `Here's the step-by-step guide for: ${workflow.title}`,
};
}
private async getCurrentSystemStatus(): Promise<ToolResult> {
const now = new Date();
const today = new Date(now);
@@ -3011,142 +2755,4 @@ User role: ${userRole}
};
}
private async getApiDocumentation(input: Record<string, any>): Promise<ToolResult> {
const resource = input.resource || 'all';
const apiDocs = {
auth: {
description: 'Authentication endpoints (Auth0 JWT-based)',
baseUrl: '/api/v1/auth',
endpoints: [
{ method: 'GET', path: '/profile', description: 'Get current user profile and role', auth: 'Required' },
],
notes: 'Uses Auth0 for authentication. Token must be included as Bearer token in Authorization header.',
},
users: {
description: 'User management (Admin only)',
baseUrl: '/api/v1/users',
endpoints: [
{ method: 'GET', path: '/', description: 'List all users', auth: 'Admin only' },
{ method: 'GET', path: '/pending', description: 'List users pending approval', auth: 'Admin only' },
{ method: 'GET', path: '/:id', description: 'Get specific user by ID', auth: 'Admin only' },
{ method: 'PATCH', path: '/:id', description: 'Update user details', auth: 'Admin only' },
{ method: 'PATCH', path: '/:id/approve', description: 'Approve a pending user', auth: 'Admin only' },
{ method: 'DELETE', path: '/:id', description: 'Delete user (soft delete)', auth: 'Admin only' },
],
notes: 'First user to register automatically becomes Admin. Other users need approval.',
},
vips: {
description: 'VIP profile management',
baseUrl: '/api/v1/vips',
endpoints: [
{ method: 'GET', path: '/', description: 'List all VIPs', auth: 'All roles' },
{ method: 'GET', path: '/:id', description: 'Get VIP details with flights and events', auth: 'All roles' },
{ method: 'POST', path: '/', description: 'Create new VIP', auth: 'Admin, Coordinator' },
{ method: 'PATCH', path: '/:id', description: 'Update VIP information', auth: 'Admin, Coordinator' },
{ method: 'DELETE', path: '/:id', description: 'Soft delete VIP', auth: 'Admin, Coordinator' },
],
fields: ['name', 'organization', 'department (OFFICE_OF_DEVELOPMENT | ADMIN)', 'arrivalMode (FLIGHT | SELF_DRIVING)', 'expectedArrival', 'airportPickup', 'venueTransport', 'notes'],
},
drivers: {
description: 'Driver resource management',
baseUrl: '/api/v1/drivers',
endpoints: [
{ method: 'GET', path: '/', description: 'List all drivers', auth: 'All roles' },
{ method: 'GET', path: '/me', description: 'Get current user\'s driver profile', auth: 'Driver role' },
{ method: 'GET', path: '/:id', description: 'Get driver details', auth: 'All roles' },
{ method: 'GET', path: '/:id/schedule', description: 'Get driver\'s schedule', auth: 'All roles' },
{ method: 'POST', path: '/', description: 'Create new driver', auth: 'Admin, Coordinator' },
{ method: 'POST', path: '/:id/send-schedule', description: 'Send schedule to driver via Signal (ICS + PDF)', auth: 'Admin, Coordinator' },
{ method: 'POST', path: '/send-all-schedules', description: 'Send schedules to all drivers with events', auth: 'Admin, Coordinator' },
{ method: 'PATCH', path: '/:id', description: 'Update driver', auth: 'Admin, Coordinator' },
{ method: 'PATCH', path: '/me', description: 'Update own profile', auth: 'Driver role' },
{ method: 'DELETE', path: '/:id', description: 'Soft delete driver', auth: 'Admin, Coordinator' },
],
fields: ['name', 'phone', 'department', 'isAvailable', 'shiftStartTime', 'shiftEndTime', 'userId (link to User)'],
},
events: {
description: 'Schedule event management (transports, meetings, etc.)',
baseUrl: '/api/v1/events',
endpoints: [
{ method: 'GET', path: '/', description: 'List events (supports filters: date, driverId, status)', auth: 'All roles' },
{ method: 'GET', path: '/:id', description: 'Get event details', auth: 'All roles' },
{ method: 'POST', path: '/', description: 'Create new event (checks for conflicts)', auth: 'Admin, Coordinator' },
{ method: 'PATCH', path: '/:id', description: 'Update event', auth: 'Admin, Coordinator' },
{ method: 'PATCH', path: '/:id/status', description: 'Update event status only', auth: 'All roles (Drivers can update their events)' },
{ method: 'DELETE', path: '/:id', description: 'Cancel/delete event', auth: 'Admin, Coordinator' },
],
fields: ['vipIds[]', 'title', 'type (TRANSPORT | MEETING | EVENT | MEAL | ACCOMMODATION)', 'status (SCHEDULED | IN_PROGRESS | COMPLETED | CANCELLED)', 'startTime', 'endTime', 'pickupLocation', 'dropoffLocation', 'location', 'driverId', 'vehicleId', 'description', 'notes'],
notes: 'Events support multiple VIPs via vipIds array. Conflict detection runs on create/update.',
},
vehicles: {
description: 'Vehicle fleet management',
baseUrl: '/api/v1/vehicles',
endpoints: [
{ method: 'GET', path: '/', description: 'List all vehicles (supports filters: type, status)', auth: 'All roles' },
{ method: 'GET', path: '/:id', description: 'Get vehicle details', auth: 'All roles' },
{ method: 'POST', path: '/', description: 'Create new vehicle', auth: 'Admin, Coordinator' },
{ method: 'PATCH', path: '/:id', description: 'Update vehicle', auth: 'Admin, Coordinator' },
{ method: 'DELETE', path: '/:id', description: 'Soft delete vehicle', auth: 'Admin, Coordinator' },
],
fields: ['name', 'type (VAN | SUV | SEDAN | BUS | GOLF_CART | TRUCK)', 'licensePlate', 'seatCapacity', 'status (AVAILABLE | IN_USE | MAINTENANCE)', 'notes'],
},
flights: {
description: 'Flight tracking and management',
baseUrl: '/api/v1/flights',
endpoints: [
{ method: 'GET', path: '/', description: 'List all flights', auth: 'All roles' },
{ method: 'GET', path: '/:id', description: 'Get flight details', auth: 'All roles' },
{ method: 'GET', path: '/vip/:vipId', description: 'Get all flights for a VIP', auth: 'All roles' },
{ method: 'POST', path: '/', description: 'Create flight record', auth: 'Admin, Coordinator' },
{ method: 'POST', path: '/track/:flightNumber', description: 'Fetch live flight status from AviationStack', auth: 'Admin, Coordinator' },
{ method: 'PATCH', path: '/:id', description: 'Update flight info', auth: 'Admin, Coordinator' },
{ method: 'DELETE', path: '/:id', description: 'Delete flight', auth: 'Admin, Coordinator' },
],
fields: ['vipId', 'flightNumber', 'flightDate', 'segment', 'departureAirport (IATA code)', 'arrivalAirport', 'scheduledDeparture', 'scheduledArrival', 'actualDeparture', 'actualArrival', 'status'],
},
signal: {
description: 'Signal messaging integration for driver communication',
baseUrl: '/api/v1/signal',
endpoints: [
{ method: 'GET', path: '/status', description: 'Get Signal service status and linked number', auth: 'Admin, Coordinator' },
{ method: 'GET', path: '/messages', description: 'Get message history (supports driverId filter)', auth: 'Admin, Coordinator' },
{ method: 'GET', path: '/messages/unread-counts', description: 'Get unread message counts per driver', auth: 'Admin, Coordinator' },
{ method: 'GET', path: '/messages/driver/:driverId', description: 'Get messages for specific driver', auth: 'Admin, Coordinator' },
{ method: 'POST', path: '/messages/send', description: 'Send message to driver via Signal', auth: 'Admin, Coordinator' },
{ method: 'POST', path: '/messages/mark-read/:driverId', description: 'Mark driver messages as read', auth: 'Admin, Coordinator' },
{ method: 'POST', path: '/messages/check-responses', description: 'Check if drivers responded since event start times', auth: 'Admin, Coordinator' },
],
notes: 'Requires Signal CLI to be running and linked. Messages are stored in database for history.',
},
};
const validResource = resource as keyof typeof apiDocs;
if (resource !== 'all' && apiDocs[validResource]) {
const doc = apiDocs[validResource];
return {
success: true,
data: { [resource]: doc },
message: `API documentation for ${resource} endpoints. Base URL: ${doc.baseUrl}`,
};
}
const totalEndpoints = Object.values(apiDocs).reduce(
(sum, r) => sum + r.endpoints.length,
0
);
return {
success: true,
data: {
overview: {
baseUrl: '/api/v1',
authentication: 'Auth0 JWT Bearer token required on all endpoints',
roles: ['ADMINISTRATOR (full access)', 'COORDINATOR (manage VIPs, drivers, events)', 'DRIVER (view + update own events)'],
},
resources: apiDocs,
},
message: `VIP Coordinator API has ${totalEndpoints} endpoints across ${Object.keys(apiDocs).length} resources. All endpoints require authentication.`,
};
}
}

630
docs/USER_GUIDE.md Normal file
View File

@@ -0,0 +1,630 @@
# VIP Coordinator - User Guide
A comprehensive guide to using the VIP Coordinator application for managing VIP transportation logistics, driver coordination, event scheduling, and fleet management.
---
## Table of Contents
1. [Getting Started](#getting-started)
- [Logging In](#logging-in)
- [Understanding Your Role](#understanding-your-role)
- [Navigation Overview](#navigation-overview)
2. [Dashboard](#dashboard)
3. [War Room (Command Center)](#war-room-command-center)
4. [Managing VIPs](#managing-vips)
- [Viewing the VIP List](#viewing-the-vip-list)
- [Adding a New VIP](#adding-a-new-vip)
- [Editing a VIP](#editing-a-vip)
- [VIP Contact & Emergency Info](#vip-contact--emergency-info)
- [Deleting a VIP](#deleting-a-vip)
5. [Fleet Management](#fleet-management)
- [Drivers Tab](#drivers-tab)
- [Adding a Driver](#adding-a-driver)
- [Vehicles Tab](#vehicles-tab)
- [Adding a Vehicle](#adding-a-vehicle)
6. [Activities (Events & Scheduling)](#activities-events--scheduling)
- [Viewing Activities](#viewing-activities)
- [Creating an Activity](#creating-an-activity)
- [Activity Types](#activity-types)
- [Conflict Detection](#conflict-detection)
7. [Flight Tracking](#flight-tracking)
- [Viewing Flights](#viewing-flights)
- [Adding a Flight](#adding-a-flight)
8. [GPS Tracking](#gps-tracking)
- [Overview](#gps-overview)
- [Enrolling a Driver for GPS](#enrolling-a-driver-for-gps)
- [Live Map](#live-map)
- [GPS Settings](#gps-settings)
9. [Reports](#reports)
- [VIP Accountability Roster](#vip-accountability-roster)
- [PDF Customization](#pdf-customization)
10. [User Management](#user-management)
- [Approving New Users](#approving-new-users)
- [Changing User Roles](#changing-user-roles)
11. [Admin Tools](#admin-tools)
- [Database Statistics](#database-statistics)
- [PDF Customization](#pdf-customization-settings)
- [Signal Messaging](#signal-messaging)
- [Test Data Management](#test-data-management)
12. [AI Assistant](#ai-assistant)
13. [Driver View (My Schedule)](#driver-view-my-schedule)
---
## Getting Started
### Logging In
1. Navigate to your VIP Coordinator URL (e.g., `https://vip.madeamess.online`).
2. Click the **"Sign in with Auth0"** button on the login page.
![Login Page](screenshots/01-login-page.png)
3. You will be redirected to the Auth0 login screen. Enter your email address and password, then click **Continue**.
![Auth0 Login](screenshots/02-auth0-login.png)
4. After successful authentication, you will be redirected to the application.
- **First-time users:** Your account requires administrator approval before you can access the system. You'll see a "Pending Approval" page until an admin approves your account.
- **Returning users:** You'll land on the Dashboard (or My Schedule if you're a Driver).
> **Tip:** Your login session persists across browser refreshes and tabs. You won't need to log in again unless you explicitly sign out or your session expires.
### Understanding Your Role
VIP Coordinator has three user roles, each with different levels of access:
| Feature | Administrator | Coordinator | Driver |
|---------|:---:|:---:|:---:|
| Dashboard & War Room | Full access | Full access | -- |
| VIP Management | Create, Edit, Delete | Create, Edit, Delete | View only |
| Fleet (Drivers/Vehicles) | Create, Edit, Delete | Create, Edit, Delete | View only |
| Activities/Events | Create, Edit, Delete | Create, Edit, Delete | View & Update status |
| Flight Tracking | Full access | Full access | -- |
| GPS Tracking | Full access | Full access | View own location |
| Reports | Full access | Full access | -- |
| User Management | Full access | -- | -- |
| Admin Tools | Full access | -- | -- |
| AI Assistant | Full access | Full access | -- |
### Navigation Overview
The top navigation bar provides access to all major sections:
- **Dashboard** - Quick overview of today's activities and stats
- **War Room** - Real-time command center for active operations
- **VIPs** - Manage VIP profiles and their travel details
- **Fleet** - Manage drivers and vehicles
- **Activities** - Schedule and track events/transport
- **Flights** - Track flight arrivals and departures
- **Admin** (dropdown) - User Management, GPS Tracking, Reports, Admin Tools
Your user avatar and email appear in the top-right corner. Click it to access your profile or sign out.
---
## Dashboard
The Dashboard is your home base, providing a quick overview of the current situation.
![Dashboard](screenshots/03-dashboard.png)
**What you'll see:**
- **Summary Cards** - Quick counts of VIPs, drivers, vehicles, and today's events
- **Today's Schedule** - A timeline of upcoming activities for the day
- **Recent Activity** - Latest changes and updates in the system
- **Quick Actions** - Shortcuts to common tasks like adding a VIP or creating an event
> **Tip:** The Dashboard automatically refreshes to show you the latest data. It's a great page to keep open as your main monitoring screen.
---
## War Room (Command Center)
The War Room is your real-time operations center, designed for active event coordination.
![War Room](screenshots/04-war-room.png)
**Key Features:**
- **Active Events Panel** - Shows all currently in-progress events with live status
- **Upcoming Events** - Events starting soon, sorted by urgency
- **Driver Status** - Which drivers are currently assigned and available
- **Quick Status Updates** - One-click buttons to mark events as started, completed, or cancelled
**How to use the War Room:**
1. Open the **War Room** from the top navigation.
2. Events are color-coded by status:
- **Red/Urgent** - Events starting in the next 5-15 minutes
- **Blue/In Progress** - Currently active events
- **Green/Completed** - Recently finished events
- **Gray/Scheduled** - Upcoming events
3. Click on any event card to see full details or update its status.
4. Use the **Refresh** button to get the latest data instantly.
> **Tip:** The War Room is ideal for day-of-event coordination. Keep it open on a large screen or dedicated monitor during active operations.
---
## Managing VIPs
### Viewing the VIP List
Navigate to **VIPs** from the top menu to see all VIP profiles.
![VIP List](screenshots/05-vip-list.png)
**Features:**
- **Search** - Filter VIPs by name or organization using the search bar
- **Department Filter** - Filter by department (Office of Development, Admin, Other)
- **Arrival Mode** - See whether each VIP is arriving by flight or self-driving
- **Party Size** - Shows the total number of people in the VIP's group
- **Quick Actions** - Edit or view schedule for each VIP
### Adding a New VIP
1. Click the **"+ Add VIP"** button in the top-right corner of the VIP List page.
2. Fill in the VIP's details:
![VIP Edit Form](screenshots/06-vip-edit-form.png)
**Required fields:**
- **Name** - Full name of the VIP
- **Department** - Which department is hosting (Office of Development, Admin, or Other)
- **Arrival Mode** - How the VIP is arriving:
- **Flight** - Arriving by air (enables flight tracking)
- **Self-Driving** - Arriving by personal vehicle (allows setting expected arrival time)
**Optional fields:**
- **Organization** - The VIP's company or organization
- **Airport Pickup** - Check if the VIP needs airport pickup service
- **Venue Transport** - Check if the VIP needs transportation between venues
- **Party Size** - Total number of people (VIP + entourage, default is 1)
- **Notes** - Any special instructions or requirements
- **Roster Only** - Check this if you're only tracking the VIP for accountability purposes (not active coordination)
3. Click **Save** to create the VIP profile.
### Editing a VIP
1. On the VIP List, click the **Edit** (pencil) icon on any VIP row.
2. The edit form opens with the VIP's current information pre-filled.
3. Make your changes and click **Save**.
### VIP Contact & Emergency Info
Scroll down in the VIP edit form to find the contact and emergency information section.
![VIP Contact Info](screenshots/07-vip-edit-contact-info.png)
**Contact fields:**
- **Phone** - VIP's phone number
- **Email** - VIP's email address
- **Emergency Contact Name** - Name of the VIP's emergency contact
- **Emergency Contact Phone** - Phone number for the emergency contact
> **Important:** Emergency contact information is included in the Accountability Roster report. Filling this in is recommended for all VIPs attending large events.
### Deleting a VIP
1. On the VIP List, click the **Delete** (trash) icon on the VIP's row.
2. Confirm the deletion when prompted.
> **Note:** VIP deletion is a "soft delete" - the record is hidden but preserved in the database for audit purposes.
---
## Fleet Management
The Fleet page manages both **Drivers** and **Vehicles** from a single location.
### Drivers Tab
![Fleet - Drivers](screenshots/08-fleet-drivers.png)
The Drivers tab shows all drivers in the system, including:
- **Name** and **Phone** number
- **Department** assignment
- **Availability Status** - Whether the driver is available for assignments
- **Shift Times** - When the driver's shift starts and ends
- **Linked Account** - Whether the driver has a user account for app login
### Adding a Driver
1. Navigate to **Fleet** and ensure the **Drivers** tab is selected.
2. Click the **"+ Add Driver"** button.
3. Fill in the required information:
- **Full Name** (required)
- **Phone Number** (required)
- **Department** (optional - Office of Development, Admin, or Other)
- **User Account ID** (optional - links the driver to a login account)
4. Click **Create Driver**.
> **Tip:** When you link a driver to a user account, that user will be able to log in and see their own schedule on the "My Schedule" page. Create the user account first (they sign up and get approved), then link it here.
### Vehicles Tab
![Fleet - Vehicles](screenshots/09-fleet-vehicles.png)
The Vehicles tab displays your entire fleet, showing:
- **Vehicle Name** - Descriptive name (e.g., "Blue Van", "Suburban #3")
- **Type** - Van, SUV, Sedan, Bus, Golf Cart, or Truck
- **License Plate** number
- **Seat Capacity** - Total available seats
- **Status** - Available, In Use, Maintenance, or Reserved
- **Current Driver** - Who is currently assigned to the vehicle
### Adding a Vehicle
1. Navigate to **Fleet** and click the **Vehicles** tab.
2. Click the **"+ Add Vehicle"** button.
3. Fill in:
- **Vehicle Name** (required) - Give it a recognizable name
- **Type** (required) - Select the vehicle type
- **License Plate** (optional)
- **Seat Capacity** (required) - Total number of passenger seats
- **Notes** (optional) - Any special notes about the vehicle
4. Click **Create Vehicle**.
> **Tip:** Keep vehicle names simple and distinctive. During hectic operations, coordinators need to quickly identify vehicles. Names like "White Suburban" or "Van #2" work well.
---
## Activities (Events & Scheduling)
### Viewing Activities
Navigate to **Activities** from the top menu to see all scheduled events.
![Activities](screenshots/10-activities.png)
**Features:**
- **Status Filters** - Filter by Scheduled, In Progress, Completed, or Cancelled
- **Date Filtering** - View events for specific dates
- **Type Filtering** - Filter by Transport, Meeting, Event, Meal, or Accommodation
- **Search** - Find events by title, VIP name, or location
### Creating an Activity
1. Click **"+ New Activity"** on the Activities page.
2. Fill in the event details:
- **Title** (required) - Descriptive name for the event
- **Type** - Transport, Meeting, Event, Meal, or Accommodation
- **VIP(s)** - Select one or more VIPs for this event
- **Start Time** and **End Time** (required)
- **Driver** (optional) - Assign a driver
- **Vehicle** (optional) - Assign a vehicle
- **Pickup Location** and **Dropoff Location** (for transport events)
- **Location** (for non-transport events)
- **Description** and **Notes** (optional)
3. Click **Create** to save the event.
### Activity Types
| Type | Use For |
|------|---------|
| **Transport** | Airport pickups, venue-to-venue rides, departure drops |
| **Meeting** | Scheduled meetings between VIPs and hosts |
| **Event** | Conferences, ceremonies, tours, and other events |
| **Meal** | Breakfast, lunch, dinner, and receptions |
| **Accommodation** | Hotel check-in/check-out |
### Conflict Detection
When creating or editing an activity, the system automatically checks for scheduling conflicts:
- **Driver conflicts** - A driver can't be assigned to two events at the same time
- **Vehicle conflicts** - A vehicle can't be double-booked
- **VIP conflicts** - VIPs can't be in two places at once
If a conflict is detected, you'll see a warning with details about the overlapping event. You can choose to proceed anyway or adjust the timing.
---
## Flight Tracking
### Viewing Flights
Navigate to **Flights** from the top menu to see all tracked flights.
![Flights](screenshots/11-flights.png)
**The flights page shows:**
- **Flight Number** - Airline and flight number (e.g., AA1234)
- **Route** - Departure and arrival airports (IATA codes)
- **Date** - Flight date
- **Scheduled Times** - Planned departure and arrival
- **Actual Times** - Real departure and arrival (when available)
- **Status** - Scheduled, Delayed, In Air, Landed, etc.
- **VIP** - Which VIP is on this flight
### Adding a Flight
Flights are typically added through the VIP edit form:
1. Navigate to a VIP's profile (edit the VIP).
2. In the **Flights** section, click **"+ Add Flight"**.
3. Enter:
- **Flight Number** (e.g., "AA1234")
- **Flight Date**
- **Departure Airport** (IATA code, e.g., "JFK")
- **Arrival Airport** (IATA code, e.g., "LAX")
- **Segment** - For multi-leg itineraries (1 for first leg, 2 for second, etc.)
4. The system will attempt to look up real-time flight data if an API key is configured.
> **Tip:** Use standard IATA 3-letter airport codes (e.g., JFK, LAX, ORD, ATL). The system uses these to track flight status automatically.
---
## GPS Tracking
### GPS Overview
The GPS Tracking page provides real-time location monitoring for your driver fleet.
![GPS Tracking](screenshots/15-gps-tracking.png)
**Dashboard cards at the top show:**
- **Total Enrolled** - Number of drivers enrolled in GPS tracking
- **Active Now** - Drivers currently reporting their location
- **Update Interval** - How frequently locations update (e.g., 30 seconds)
- **Shift Hours** - Hours during which tracking is active
The page has four tabs: **Live Map**, **Devices**, **Stats**, and **Settings**.
### Enrolling a Driver for GPS
To enable GPS tracking for a driver, you need to enroll them:
![GPS Devices](screenshots/16-gps-devices.png)
1. Go to **GPS Tracking** and click the **Devices** tab.
2. Click the **"Enroll Driver"** button.
3. Select the driver you want to enroll from the dropdown.
4. The system will create a unique device identifier for that driver.
5. The driver then needs to install the **Traccar Client** app on their phone:
- Available for both **iOS** (App Store) and **Android** (Google Play)
- Search for "Traccar Client" in the app store
6. In the Traccar Client app, configure:
- **Device identifier** - Enter the unique ID shown after enrollment
- **Server URL** - Enter the Traccar server URL provided by your administrator
- **Frequency** - Set to match your GPS settings (e.g., 30 seconds)
- **Location accuracy** - Set to "High"
7. Enable tracking in the app and the driver's location will appear on the Live Map.
> **Important:** GPS tracking respects driver privacy. Tracking only occurs during configured shift hours. Drivers must give consent, and the system clearly shows when tracking is active.
### Live Map
The **Live Map** tab shows all active drivers on an interactive map:
- **Green dots** indicate active drivers currently reporting location
- **Gray dots** indicate enrolled but inactive drivers
- Click on any driver marker to see their name, speed, and last update time
- The map auto-refreshes based on the configured update interval
### GPS Settings
![GPS Settings](screenshots/17-gps-settings.png)
Administrators can configure GPS tracking behavior:
1. Go to **GPS Tracking** and click the **Settings** tab.
2. Adjustable settings:
- **Update Interval** (30-300 seconds) - How often driver phones report location. Lower values = more precise tracking but higher battery usage.
- **Data Retention** (7-90 days) - How long location history is kept before automatic cleanup.
- **Tracking Hours** - Set the start and end time for when GPS tracking is active. Drivers are NOT tracked outside these hours.
3. Click **Save** to apply changes.
> **Tip:** For most events, a 30-60 second update interval provides good tracking while preserving driver phone battery. During critical operations, you can temporarily lower this to 15-30 seconds.
---
## Reports
### VIP Accountability Roster
Navigate to **Reports** under the **Admin** dropdown to access the accountability roster.
![Reports](screenshots/12-reports.png)
The **VIP Accountability Roster** is a comprehensive report designed for event-day accountability. It includes:
- **VIP Name and Organization**
- **Contact Information** (phone, email)
- **Emergency Contact** details
- **Arrival Mode** and expected arrival time
- **Assigned Driver and Vehicle**
- **Flight Details** (for VIPs arriving by air)
- **Party Size**
- **Special Notes**
**To generate the report:**
1. Navigate to **Reports**.
2. The roster is displayed on screen with all active VIPs.
3. Click **"Download PDF"** to generate a professionally formatted PDF document.
4. The PDF uses your configured branding (logo, colors, contact info) from the Admin Tools settings.
> **Tip:** Print the Accountability Roster before each event starts. It serves as a backup reference when technology isn't available and is useful for emergency situations where you need quick access to VIP contact and emergency information.
### PDF Customization
The appearance of generated PDF reports can be fully customized. See [Admin Tools > PDF Customization](#pdf-customization-settings) for details.
---
## User Management
Administrators can manage user accounts from the **Users** page.
![User Management](screenshots/13-users.png)
### Approving New Users
When a new person signs up, their account starts in a "Pending Approval" state:
1. Navigate to **Admin > Users**.
2. Look for users with a **"Pending"** status badge.
3. Click **"Approve"** to grant them access to the system.
4. The user will be able to log in on their next attempt.
> **Note:** The very first user to register is automatically approved and given the Administrator role. All subsequent users require manual approval.
### Changing User Roles
1. On the Users page, find the user whose role you want to change.
2. Use the **Role** dropdown to select:
- **Administrator** - Full system access, can manage users and settings
- **Coordinator** - Can manage VIPs, drivers, events, and view all data
- **Driver** - Limited view, can see their own schedule and update event status
3. The change takes effect immediately.
> **Warning:** Be careful when changing roles. Removing someone's Administrator role cannot be undone by that user - another admin must restore it.
---
## Admin Tools
The Admin Tools page is only accessible to Administrators and provides system management capabilities.
![Admin Tools](screenshots/14-admin-tools.png)
### Database Statistics
At the top of the page, you'll see a live count of all records in the system:
- Number of VIPs, Drivers, Vehicles, Events, Flights, and Users
- Click **Refresh** to update the counts
### PDF Customization Settings
Customize how generated PDF documents look:
**Branding:**
- **Organization Name** - Appears in the PDF header
- **Organization Logo** - Upload your logo (PNG, JPG, or SVG, max 2MB)
- **Accent Color** - The primary color used for headers and section titles
- **Tagline** - Optional text below the organization name
**Contact Information:**
- **Contact Email** and **Phone** - Shown in the PDF footer
- **Secondary Contact** - Optional backup contact
- **Contact Label** - The heading above contact info (e.g., "Questions or Changes?")
**Document Options:**
- **Draft Watermark** - Add a diagonal "DRAFT" watermark
- **Confidential Watermark** - Add a "CONFIDENTIAL" watermark
- **Show Timestamp** - Include generation date/time
- **Page Size** - Letter or A4
**Content Display:**
- Toggle visibility of flight info, driver names, vehicle names, VIP notes, and event descriptions
**Custom Messages:**
- **Header Message** - Custom text at the top of the document
- **Footer Message** - Custom text at the bottom
Click **"Preview Sample PDF"** to see how your settings look before saving, then click **"Save PDF Settings"** to apply.
### Signal Messaging
The Signal Messaging section allows you to communicate with drivers via Signal (encrypted messaging):
- **Connection Status** - Shows whether the Signal service is connected and which phone number is linked
- **Send Test Message** - Send a test message to verify the connection
- **Chat History** - View message statistics and manage chat history
### Test Data Management
For development and demo purposes:
- **Generate Complete Test Data** - Creates a full set of realistic test data (20 VIPs, 8 drivers, 10 vehicles, 100+ events)
- **Refresh Event Times** - Keeps existing VIPs/drivers/vehicles but regenerates all events with fresh timestamps relative to the current time
- **Clear All Data** - Removes all VIPs, drivers, vehicles, events, flights, and messages
> **Warning:** "Clear All Data" is irreversible. Only use it when you want to start completely fresh.
---
## AI Assistant
The AI Assistant is a built-in copilot that can help you with VIP coordination tasks.
![AI Assistant](screenshots/18-ai-assistant.png)
**To open the AI Assistant:**
1. Click the blue **"AI Assistant"** button in the bottom-right corner of any page.
2. The chat panel slides open.
**What the AI Assistant can do:**
- Answer questions about your VIPs, drivers, and events
- Look up what's happening today or at specific times
- Find available drivers for assignments
- Check which VIPs are arriving by flight
- Help you understand the current status of operations
- Process screenshots of emails (upload an image of an email with VIP travel details)
**Example questions you can ask:**
- *"What's happening today?"*
- *"Who are the VIPs arriving by flight?"*
- *"Which drivers are available right now?"*
- *"Show me the schedule for Roger Mosby"*
- *"What events are in progress?"*
**To upload an image:**
1. Click the **image upload** button (camera icon) in the chat input area.
2. Select a screenshot or photo (e.g., an email with travel itinerary details).
3. The AI will read the image and extract relevant information.
> **Tip:** The AI Assistant has access to your live data. It can query VIPs, drivers, events, and more in real-time. Use it as a quick way to get answers without navigating to different pages.
---
## Driver View (My Schedule)
Drivers who have a linked user account see a simplified interface focused on their assignments.
**When a driver logs in, they see:**
- **My Schedule** - Their personal schedule showing only events assigned to them
- **Today's Events** - Quick view of what's coming up
- **Status Updates** - Ability to mark their events as "In Progress" or "Completed"
**How drivers update event status:**
1. On their schedule, find the current event.
2. Click the status button to cycle through:
- **Scheduled** (default) - Not yet started
- **In Progress** - Currently underway (click when you start the pickup/transport)
- **Completed** - Finished (click when the VIP has been dropped off)
3. Coordinators and administrators see these status changes in real-time on the War Room.
> **Tip for Drivers:** Keep your event statuses updated! This helps the coordination team know exactly where VIPs are at all times. Mark "In Progress" when you begin a pickup and "Completed" when the VIP is delivered to their destination.
---
## Frequently Asked Questions
**Q: I just signed up but can't access anything. What do I do?**
A: Your account needs to be approved by an administrator. Contact your team lead and ask them to approve your account in the User Management section.
**Q: I'm a driver but I can't see my schedule. What's wrong?**
A: Make sure your user account is linked to a driver profile. An administrator needs to go to Fleet > Drivers, find your driver record, and enter your User Account ID.
**Q: Can I use the app on my phone?**
A: Yes! The web application is responsive and works on mobile browsers. Simply navigate to the same URL on your phone's browser. For GPS tracking, you'll also need the Traccar Client app.
**Q: How do I change my password?**
A: Passwords are managed through Auth0. Click your profile avatar in the top-right corner, then follow the "Change Password" link, or use the "Forgot Password" option on the login screen.
**Q: What happens if two events conflict?**
A: The system will warn you about scheduling conflicts when creating or editing events. You'll see which driver, vehicle, or VIP has an overlapping booking and can choose to adjust the timing or proceed anyway.
**Q: Is the GPS tracking always on?**
A: No. GPS tracking only operates during the configured Shift Hours (set by administrators). Outside those hours, driver locations are not tracked or recorded.
**Q: How long is location data kept?**
A: Location data is automatically deleted after the configured retention period (default: 30 days). Administrators can adjust this in GPS Settings.
---
*This documentation was generated for VIP Coordinator. For technical support or feature requests, contact your system administrator.*

View File

@@ -16,6 +16,7 @@
"axios": "^1.6.5",
"clsx": "^2.1.0",
"date-fns": "^3.2.0",
"fuse.js": "^7.1.0",
"leaflet": "^1.9.4",
"lucide-react": "^0.309.0",
"qrcode.react": "^4.2.0",
@@ -3442,6 +3443,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/fuse.js": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.1.0.tgz",
"integrity": "sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ==",
"license": "Apache-2.0",
"engines": {
"node": ">=10"
}
},
"node_modules/gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",

View File

@@ -23,6 +23,7 @@
"axios": "^1.6.5",
"clsx": "^2.1.0",
"date-fns": "^3.2.0",
"fuse.js": "^7.1.0",
"leaflet": "^1.9.4",
"lucide-react": "^0.309.0",
"qrcode.react": "^4.2.0",

Binary file not shown.

After

Width:  |  Height:  |  Size: 468 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

View File

@@ -24,6 +24,7 @@ import { DriverProfile } from '@/pages/DriverProfile';
import { MySchedule } from '@/pages/MySchedule';
import { GpsTracking } from '@/pages/GpsTracking';
import { Reports } from '@/pages/Reports';
import { Help } from '@/pages/Help';
import { useAuth } from '@/contexts/AuthContext';
// Smart redirect based on user role
@@ -63,7 +64,7 @@ function App() {
scope: 'openid profile email offline_access',
}}
useRefreshTokens={true}
cacheLocation="memory"
cacheLocation="localstorage"
>
<QueryClientProvider client={queryClient}>
<AuthProvider>
@@ -124,6 +125,7 @@ function App() {
<Route path="/admin-tools" element={<AdminTools />} />
<Route path="/gps-tracking" element={<GpsTracking />} />
<Route path="/reports" element={<Reports />} />
<Route path="/help" element={<Help />} />
<Route path="/profile" element={<DriverProfile />} />
<Route path="/my-schedule" element={<MySchedule />} />
<Route path="/" element={<HomeRedirect />} />

View File

@@ -114,6 +114,7 @@ export function DriverForm({ driver, onSubmit, onCancel, isSubmitting }: DriverF
<option value="">Select Department</option>
<option value="OFFICE_OF_DEVELOPMENT">Office of Development</option>
<option value="ADMIN">Admin</option>
<option value="OTHER">Other</option>
</select>
</div>

View File

@@ -22,6 +22,7 @@ import {
Phone,
AlertCircle,
FileText,
HelpCircle,
} from 'lucide-react';
import { UserMenu } from '@/components/UserMenu';
import { AppearanceMenu } from '@/components/AppearanceMenu';
@@ -98,6 +99,7 @@ export function Layout({ children }: LayoutProps) {
{ name: 'Reports', href: '/reports', icon: FileText },
{ name: 'GPS Tracking', href: '/gps-tracking', icon: Radio },
{ name: 'Admin Tools', href: '/admin-tools', icon: Settings },
{ name: 'Help Guide', href: '/help', icon: HelpCircle },
];
// Filter navigation based on role and CASL permissions

View File

@@ -196,6 +196,7 @@ export function VIPForm({ vip, onSubmit, onCancel, isSubmitting }: VIPFormProps)
>
<option value="OFFICE_OF_DEVELOPMENT">Office of Development</option>
<option value="ADMIN">Admin</option>
<option value="OTHER">Other</option>
</select>
</div>

View File

@@ -17,7 +17,7 @@ export const copilotApi = axios.create({
headers: {
'Content-Type': 'application/json',
},
timeout: 120000, // 2 minute timeout for AI requests
timeout: 300000, // 5 minute timeout for AI requests (large tasks need multiple tool calls)
});
// Token getter function - set by AuthContext when authenticated

View File

@@ -196,6 +196,7 @@ export function DriverList({ embedded = false }: { embedded?: boolean }) {
const labels = {
'OFFICE_OF_DEVELOPMENT': 'Office of Development',
'ADMIN': 'Admin',
'OTHER': 'Other',
};
return labels[value as keyof typeof labels] || value;
};
@@ -541,6 +542,7 @@ export function DriverList({ embedded = false }: { embedded?: boolean }) {
options: [
{ value: 'OFFICE_OF_DEVELOPMENT', label: 'Office of Development' },
{ value: 'ADMIN', label: 'Admin' },
{ value: 'OTHER', label: 'Other' },
],
selectedValues: selectedDepartments,
onToggle: handleDepartmentToggle,

1078
frontend/src/pages/Help.tsx Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -234,6 +234,7 @@ export function Reports() {
<option value="all">All Departments</option>
<option value="OFFICE_OF_DEVELOPMENT">Office of Development</option>
<option value="ADMIN">Admin</option>
<option value="OTHER">Other</option>
</select>
</div>
<button

View File

@@ -192,6 +192,7 @@ export function VIPList() {
department: {
'OFFICE_OF_DEVELOPMENT': 'Office of Development',
'ADMIN': 'Admin',
'OTHER': 'Other',
},
arrivalMode: {
'FLIGHT': 'Flight',
@@ -546,6 +547,7 @@ export function VIPList() {
options: [
{ value: 'OFFICE_OF_DEVELOPMENT', label: 'Office of Development' },
{ value: 'ADMIN', label: 'Admin' },
{ value: 'OTHER', label: 'Other' },
],
selectedValues: selectedDepartments,
onToggle: handleDepartmentToggle,