import React, { useState } from 'react'; import { Calendar, Video, Send, Clock, MapPin, Users, CheckCircle2, AlertCircle, Plus, ExternalLink, Filter, Sparkles, Gavel, Building2, ShieldCheck, FileText, RefreshCw, Mail, Zap, Check, ChevronDown, ChevronUp } from 'lucide-react'; import { MASTER_PLEADING_MILESTONES, MasterPleadingMilestone, CourtAttendee } from '../data/courtSchedulingData'; import { CourtCalendarEvent } from '../services/googleWorkspaceService'; interface Props { token: string | null; userEmail?: string | null; isLoading: boolean; onScheduleMilestone: (event: CourtCalendarEvent, milestoneTitle: string) => Promise; onBatchScheduleAll: (events: { event: CourtCalendarEvent; milestoneTitle: string }[]) => Promise; onRefreshCalendar: () => Promise; existingEvents: any[]; } export const CourtSchedulingPanel: React.FC = ({ token, userEmail, isLoading, onScheduleMilestone, onBatchScheduleAll, onRefreshCalendar, existingEvents }) => { const [filterJurisdiction, setFilterJurisdiction] = useState<'all' | 'state' | 'federal' | 'trust' | 'agency'>('all'); const [selectedMilestoneId, setSelectedMilestoneId] = useState(null); // Custom attendees and time edits mapped by milestone ID const [milestoneEdits, setMilestoneEdits] = useState<{ [id: string]: { date: string; durationMinutes: number; includeMeet: boolean; selectedAttendees: string[]; // array of emails customAttendeeInput: string; customNotes: string; } }>({}); // Ad-hoc new court milestone modal const [isCustomModalOpen, setIsCustomModalOpen] = useState(false); const [customMilestone, setCustomMilestone] = useState<{ title: string; docketNumber: string; courtName: string; location: string; date: string; durationMinutes: number; description: string; attendees: string; includeMeet: boolean; }>({ title: 'Emergency Case Management & Docket Conference', docketNumber: 'Case No. C-03-CV-26-004128', courtName: 'Circuit Court for Baltimore County, Maryland', location: '401 Bosley Ave, Towson, MD 21204', date: '2026-11-20T10:00', durationMinutes: 60, description: 'Supplemental case status review and CUSIP custody stipulation.', attendees: 'bizcaustin@gmail.com, trustees@psdlef.org', includeMeet: true }); // Track scheduled milestone IDs in current session for immediate visual feedback const [syncedMilestoneIds, setSyncedMilestoneIds] = useState>(new Set()); // Get current editing state for a milestone const getMilestoneEditState = (m: MasterPleadingMilestone) => { if (milestoneEdits[m.id]) return milestoneEdits[m.id]; return { date: m.scheduledDate.slice(0, 16), durationMinutes: m.durationMinutes, includeMeet: true, selectedAttendees: m.defaultAttendees.filter(a => a.selectedByDefault).map(a => a.email), customAttendeeInput: '', customNotes: '' }; }; const updateMilestoneEditState = (id: string, updates: Partial>) => { setMilestoneEdits(prev => { const current = prev[id] || { date: MASTER_PLEADING_MILESTONES.find(m => m.id === id)?.scheduledDate.slice(0, 16) || '', durationMinutes: MASTER_PLEADING_MILESTONES.find(m => m.id === id)?.durationMinutes || 60, includeMeet: true, selectedAttendees: MASTER_PLEADING_MILESTONES.find(m => m.id === id)?.defaultAttendees.filter(a => a.selectedByDefault).map(a => a.email) || [], customAttendeeInput: '', customNotes: '' }; return { ...prev, [id]: { ...current, ...updates } }; }); }; // Check if a milestone has already been synced to Google Calendar (by summary comparison or session state) const isMilestoneSynced = (m: MasterPleadingMilestone) => { if (syncedMilestoneIds.has(m.id)) return true; return existingEvents.some(ev => { const summary = (ev.summary || '').toLowerCase(); return summary.includes(m.docketNumber.toLowerCase()) || summary.includes(m.courtName.toLowerCase()) || summary.includes(m.milestoneTitle.toLowerCase().slice(0, 30)); }); }; // Find matching event link if exists const getExistingEvent = (m: MasterPleadingMilestone) => { return existingEvents.find(ev => { const summary = (ev.summary || '').toLowerCase(); return summary.includes(m.docketNumber.toLowerCase()) || summary.includes(m.courtName.toLowerCase()) || summary.includes(m.milestoneTitle.toLowerCase().slice(0, 30)); }); }; // Filtered milestones const filteredMilestones = MASTER_PLEADING_MILESTONES.filter(m => { if (filterJurisdiction === 'all') return true; return m.jurisdiction === filterJurisdiction; }); // Schedule a single milestone with Google Calendar invitation const handleTriggerScheduleMilestone = async (m: MasterPleadingMilestone) => { const edit = getMilestoneEditState(m); const startDate = new Date(edit.date); const endDate = new Date(startDate.getTime() + edit.durationMinutes * 60000); // Combine default selected attendees with any custom typed attendees const attendeeEmails = new Set(edit.selectedAttendees); if (edit.customAttendeeInput.trim()) { edit.customAttendeeInput.split(/[,;\s]+/).forEach(email => { if (email.includes('@')) attendeeEmails.add(email.trim()); }); } const attendeeList = Array.from(attendeeEmails).map(email => { const found = m.defaultAttendees.find(a => a.email === email); return { email, displayName: found ? found.displayName : undefined }; }); const eventPayload: CourtCalendarEvent = { summary: `${m.milestoneTitle} (${m.docketNumber})`, description: `${m.description} Statutory Authority: ${m.statutoryBasis} Pleading Reference: ${m.instrumentPart} Action Demanded: ${m.actionRequested} ${edit.customNotes ? `\nAdditional Case Notes: ${edit.customNotes}` : ''} Generated via Google Workspace Legal Hub for Case C-03-CV-26-004128.`, location: m.location, startTime: startDate.toISOString(), endTime: endDate.toISOString(), createMeetRoom: edit.includeMeet, attendees: attendeeList, sendUpdates: 'all' // Dispatches Google Calendar email invitations }; await onScheduleMilestone(eventPayload, m.milestoneTitle); setSyncedMilestoneIds(prev => new Set([...prev, m.id])); }; // 1-Click Batch Schedule All Milestones const handleBatchScheduleAll = async () => { const batchPayload = filteredMilestones.map(m => { const edit = getMilestoneEditState(m); const startDate = new Date(edit.date); const endDate = new Date(startDate.getTime() + edit.durationMinutes * 60000); const attendeeEmails = new Set(edit.selectedAttendees); if (edit.customAttendeeInput.trim()) { edit.customAttendeeInput.split(/[,;\s]+/).forEach(email => { if (email.includes('@')) attendeeEmails.add(email.trim()); }); } const attendeeList = Array.from(attendeeEmails).map(email => { const found = m.defaultAttendees.find(a => a.email === email); return { email, displayName: found ? found.displayName : undefined }; }); const eventPayload: CourtCalendarEvent = { summary: `${m.milestoneTitle} (${m.docketNumber})`, description: `${m.description}\n\nStatutory Basis: ${m.statutoryBasis}\nAction: ${m.actionRequested}`, location: m.location, startTime: startDate.toISOString(), endTime: endDate.toISOString(), createMeetRoom: true, attendees: attendeeList, sendUpdates: 'all' }; return { event: eventPayload, milestoneTitle: m.milestoneTitle }; }); await onBatchScheduleAll(batchPayload); setSyncedMilestoneIds(prev => new Set([...prev, ...filteredMilestones.map(m => m.id)])); }; // Schedule Custom Ad-Hoc Hearing const handleSaveCustomMilestone = async (e: React.FormEvent) => { e.preventDefault(); const startDate = new Date(customMilestone.date); const endDate = new Date(startDate.getTime() + customMilestone.durationMinutes * 60000); const attendees = customMilestone.attendees .split(/[,;\s]+/) .filter(em => em.includes('@')) .map(email => ({ email: email.trim() })); const eventPayload: CourtCalendarEvent = { summary: `${customMilestone.title} (${customMilestone.docketNumber})`, description: `${customMilestone.description}\nCourt: ${customMilestone.courtName}`, location: customMilestone.location, startTime: startDate.toISOString(), endTime: endDate.toISOString(), createMeetRoom: customMilestone.includeMeet, attendees, sendUpdates: 'all' }; await onScheduleMilestone(eventPayload, customMilestone.title); setIsCustomModalOpen(false); }; return (
{/* Header and Hero Banner */}
DIRECT MASTER PLEADING SYNCHRONIZATION • Google Calendar API v3 + Meet + Attendee Invites

Court Scheduling & Appearance Invitation Hub

Synchronizes scheduled appearance dates, show-cause orders, Rule 18 conferences, and directed trust convenings directly with Google Calendar. Automatically generates and dispatches official Google Calendar invites with video conference links (Google Meet) to all named co-trustees, depository bank representatives, and court clerks.

{/* Global Action Buttons */}
{/* Jurisdiction Filters & Overview Stats */}
Jurisdiction: {[ { id: 'all', label: 'All Dockets (6)' }, { id: 'state', label: 'Baltimore County (3)' }, { id: 'federal', label: 'D.C. Circuit (1)' }, { id: 'trust', label: 'Wyoming Trust (1)' }, { id: 'agency', label: 'SSA OHO (1)' } ].map(tab => ( ))}
Synced to Calendar: {MASTER_PLEADING_MILESTONES.filter(isMilestoneSynced).length} / {MASTER_PLEADING_MILESTONES.length}
{/* Milestones Cards List */}
{filteredMilestones.map((m) => { const isSynced = isMilestoneSynced(m); const existing = getExistingEvent(m); const edit = getMilestoneEditState(m); const isExpanded = selectedMilestoneId === m.id; const meetUrl = existing?.conferenceData?.entryPoints?.find((ep: any) => ep.entryPointType === 'video')?.uri; return (
{/* Card Header */}
{m.docketNumber} {m.jurisdictionLabel} {isSynced && ( Synced to Google Calendar )}

{m.milestoneTitle}

{m.instrumentPart}
{new Date(edit.date).toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric' })} at {new Date(edit.date).toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' })} ({edit.durationMinutes} min)
{m.location}
{edit.selectedAttendees.length} Invitees
{/* Actions Bar */}
{meetUrl && ( )}
{/* Statutory Basis & Action Demanded Pill */}
Statutory Authority: {m.statutoryBasis}
Action: {m.actionRequested.slice(0, 95)}...
{/* Expandable Customization Panel */} {isExpanded && (
{/* Time & Duration Configuration */}
Event Date, Time & Video Bridge
updateMilestoneEditState(m.id, { date: e.target.value })} className="w-full px-2 py-1.5 bg-slate-900 border border-slate-700 rounded text-slate-100 font-mono text-xs focus:ring-1 focus:ring-amber-500" />