import React, { useState, useEffect } from 'react' import { PlusCircle, Edit3, Check, X, RotateCw, AlertTriangle, Briefcase, Trash2, Calendar, } from 'lucide-react' import { fetchEntries, createEntry, updateEntry, deleteEntry } from '../lib/pocketbase.js' import { calculatePatientStatus, getPatientISOs } from '../lib/patientUtils.js' import CountrySelect from './CountrySelect.jsx' const STATUS_META = { 'pre-travel': { label: 'Pre-Travel', className: 'status-pre-travel' }, 'in-transit': { label: 'In Transit', className: 'status-in-transit' }, 'at-risk': { label: 'At Risk', className: 'status-at-risk' }, 'post-travel': { label: 'Post-Travel', className: 'status-post-travel' }, } const INITIAL_NEW_FORM = { emrID: '', name: '', location: '', countriesISO: [], departure: '', arrival: '', } export default function Trips() { const [travellers, setTravellers] = useState([]) const [loading, setLoading] = useState(true) const [submitting, setSubmitting] = useState(false) const [error, setError] = useState(null) const [successMsg, setSuccessMsg] = useState(null) // New Trip form state (Left Panel) const [newTripForm, setNewTripForm] = useState(INITIAL_NEW_FORM) // Edit Trip state (Right Panel when editing) const [editingTrip, setEditingTrip] = useState(null) const [editTripForm, setEditTripForm] = useState(INITIAL_NEW_FORM) async function loadData() { setLoading(true) setError(null) try { const data = await fetchEntries() setTravellers(data || []) } catch (err) { setError(err.message || 'Unable to connect to PocketBase backend.') } finally { setLoading(false) } } useEffect(() => { loadData() }, []) // Auto-dismiss success notification useEffect(() => { if (successMsg) { const timer = setTimeout(() => setSuccessMsg(null), 4000) return () => clearTimeout(timer) } }, [successMsg]) // Handle New Trip Form submission (Left Form) async function handleCreateTrip(e) { e.preventDefault() if (!newTripForm.name.trim()) { setError('Please enter a patient / traveller name.') return } setSubmitting(true) setError(null) // Convert comma-separated string to array if needed const isoArray = typeof newTripForm.countriesISO === 'string' ? newTripForm.countriesISO.split(',').map((s) => s.trim().toUpperCase()).filter(Boolean) : newTripForm.countriesISO const payload = { emrID: newTripForm.emrID ? Number(newTripForm.emrID) : null, name: newTripForm.name.trim(), location: newTripForm.location.trim(), countriesISO: isoArray, departure: newTripForm.departure, arrival: newTripForm.arrival, } try { await createEntry(payload) setNewTripForm(INITIAL_NEW_FORM) setSuccessMsg('New trip created successfully!') await loadData() } catch (err) { setError(err.message || 'Failed to create trip record.') } finally { setSubmitting(false) } } // Initiate Edit mode for a specific trip function startEditing(trip) { setEditingTrip(trip) const isos = getPatientISOs(trip) setEditTripForm({ emrID: trip.emrID !== undefined && trip.emrID !== null ? trip.emrID : '', name: trip.name || '', location: trip.location || '', countriesISO: Array.isArray(isos) ? isos : typeof isos === 'string' && isos ? isos.split(',').map((s) => s.trim().toUpperCase()) : [], departure: trip.departure ? trip.departure.slice(0, 10) : '', arrival: trip.arrival ? trip.arrival.slice(0, 10) : '', }) // Scroll top container into view smoothly window.scrollTo({ top: 0, behavior: 'smooth' }) } function cancelEditing() { setEditingTrip(null) setEditTripForm(INITIAL_NEW_FORM) } // Handle Save Changes submission (Right Edit Form) async function handleUpdateTrip(e) { e.preventDefault() if (!editingTrip) return if (!editTripForm.name.trim()) { setError('Patient name cannot be empty.') return } setSubmitting(true) setError(null) const isoArray = typeof editTripForm.countriesISO === 'string' ? editTripForm.countriesISO.split(',').map((s) => s.trim().toUpperCase()).filter(Boolean) : editTripForm.countriesISO const payload = { emrID: editTripForm.emrID ? Number(editTripForm.emrID) : null, name: editTripForm.name.trim(), location: editTripForm.location.trim(), countriesISO: isoArray, departure: editTripForm.departure, arrival: editTripForm.arrival, } try { await updateEntry(editingTrip.id, payload) setSuccessMsg(`Trip #${editingTrip.id} updated successfully!`) setEditingTrip(null) setEditTripForm(INITIAL_NEW_FORM) await loadData() } catch (err) { setError(err.message || `Failed to update trip #${editingTrip.id}.`) } finally { setSubmitting(false) } } // Handle Delete Trip async function handleDeleteTrip(id, name) { if (!window.confirm(`Are you sure you want to delete trip ${id} (${name})?`)) return setError(null) try { await deleteEntry(id) if (editingTrip?.id === id) { cancelEditing() } setSuccessMsg(`Trip ${id} deleted successfully.`) await loadData() } catch (err) { setError(err.message || `Failed to delete trip ${id}.`) } } // Helper for date formatting const formatDate = (dateStr) => { if (!dateStr) return 'N/A' const date = new Date(dateStr) if (isNaN(date.getTime())) return 'N/A' return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) } // Compute live preview state for New Form draft const newDraftPatient = { emrID: newTripForm.emrID, name: newTripForm.name || 'Jane / John Doe', location: newTripForm.location || 'Pending Location', countriesISO: Array.isArray(newTripForm.countriesISO) ? newTripForm.countriesISO : typeof newTripForm.countriesISO === 'string' ? newTripForm.countriesISO.split(',').map((s) => s.trim().toUpperCase()).filter(Boolean) : [], departure: newTripForm.departure, arrival: newTripForm.arrival, } const newDraftStatusKey = calculatePatientStatus(newDraftPatient) const newDraftMeta = STATUS_META[newDraftStatusKey] || STATUS_META['pre-travel'] const newDraftISOs = getPatientISOs(newDraftPatient) // Compute live preview state for Edit Form draft const editDraftPatient = { ...editingTrip, emrID: editTripForm.emrID, name: editTripForm.name, location: editTripForm.location, countriesISO: Array.isArray(editTripForm.countriesISO) ? editTripForm.countriesISO : typeof editTripForm.countriesISO === 'string' ? editTripForm.countriesISO.split(',').map((s) => s.trim().toUpperCase()).filter(Boolean) : [], departure: editTripForm.departure, arrival: editTripForm.arrival, } const editDraftStatusKey = calculatePatientStatus(editDraftPatient) const editDraftMeta = STATUS_META[editDraftStatusKey] || STATUS_META['pre-travel'] const editDraftISOs = getPatientISOs(editDraftPatient) const isEditingMode = editingTrip !== null return (
{/* ── Top Header ──────────────────────────────────────────────────────── */}

CONCIERGE OPERATIONS

Trip Management

Create new patient travel itineraries and manage existing trip records.

{/* ── Banners & Notifications ────────────────────────────────────────── */} {error && (
{error}
)} {successMsg && (
{successMsg}
)} {/* ── TOP HALF: Split Container ───────────────────────────────────────── */}
{/* ── Left Panel: Form for New Cards ─────────────────────────────── */}

Create New Trip

{isEditingMode ? 'Form Paused' : 'New Trip Form'}
{isEditingMode && (
Editing in progress on right panel — New trip creation is currently disabled.
)}
setNewTripForm({ ...newTripForm, emrID: e.target.value })} disabled={isEditingMode || submitting} />
setNewTripForm({ ...newTripForm, name: e.target.value })} disabled={isEditingMode || submitting} required />
{ setNewTripForm({ ...newTripForm, countriesISO: updatedISOs, location: updatedLocationStr, }) }} disabled={isEditingMode || submitting} placeholder="Search country (e.g. Spain, France)..." />
setNewTripForm({ ...newTripForm, departure: e.target.value })} disabled={isEditingMode || submitting} />
setNewTripForm({ ...newTripForm, arrival: e.target.value })} disabled={isEditingMode || submitting} />
{/* ── Right Panel: Live Preview Card or Editable Card ────────────── */}

{isEditingMode ? ( <> Editing Trip ({editingTrip.id}) ) : ( <> Live Itinerary Preview )}

{isEditingMode ? 'Edit Mode Active' : 'Real-time Preview'}
{/* Mode 1: Create Preview Card (Non-editing mode) */} {!isEditingMode && (
{newDraftPatient.emrID ? `EMR: ${newDraftPatient.emrID}` : 'NEW-TRIP'}

{newDraftPatient.name}

{newDraftMeta.label}
Location 📍 {newDraftPatient.location}
Destinations (ISOs)
{newDraftISOs.length > 0 ? ( newDraftISOs.map((iso) => ( 📍 {iso} )) ) : ( None specified )}
Travel Window {formatDate(newDraftPatient.departure)} – {formatDate(newDraftPatient.arrival)}

This card reflects how the new trip will appear in the Live Tracking dashboard.

)} {/* Mode 2: Editable Preview Form (Editing mode) */} {isEditingMode && (
{editTripForm.emrID ? `EMR: ${editTripForm.emrID}` : 'NO-EMR'} DB ID: {editingTrip.id} | Created: {formatDate(editingTrip.created)}
{editDraftMeta.label}
setEditTripForm({ ...editTripForm, emrID: e.target.value })} disabled={submitting} />
setEditTripForm({ ...editTripForm, name: e.target.value })} disabled={submitting} required />
setEditTripForm({ ...editTripForm, location: e.target.value })} disabled={submitting} />
{ setEditTripForm({ ...editTripForm, countriesISO: updatedISOs, location: updatedLocationStr, }) }} disabled={submitting} placeholder="Search country..." />
setEditTripForm({ ...editTripForm, departure: e.target.value })} disabled={submitting} />
setEditTripForm({ ...editTripForm, arrival: e.target.value })} disabled={submitting} />
{/* Live chips preview for edit form */}
ISO Preview: {editDraftISOs.length > 0 ? ( editDraftISOs.map((iso) => ( 📍 {iso} )) ) : ( None )}
)}
{/* ── BOTTOM HALF: Trips Database List ────────────────────────────────── */}

All Active Trips List

Total Records: {travellers.length}
{travellers.length === 0 && !loading ? ( ) : ( travellers.map((p) => { const meta = STATUS_META[p.status] || STATUS_META['pre-travel'] const isBeingEdited = editingTrip?.id === p.id const isos = getPatientISOs(p) return ( ) }) )}
EMR ID Patient Name Location & ISOs Departure Arrival Status Actions
No trips found in database. Use the form above to add a new trip.
{p.emrID ? p.emrID : '—'} {p.name}
📍 {p.location || 'N/A'} {isos.length > 0 && (
{isos.map((iso) => ( {iso} ))}
)}
{formatDate(p.departure)} {formatDate(p.arrival)} {meta.label}
) }