diff --git a/frontend/src/components/session/TicketContextPanel.tsx b/frontend/src/components/session/TicketContextPanel.tsx new file mode 100644 index 00000000..7148568c --- /dev/null +++ b/frontend/src/components/session/TicketContextPanel.tsx @@ -0,0 +1,242 @@ +import { useState } from 'react' +import { + Ticket, + Building2, + UserCircle, + Monitor, + MessageSquare, + Link2, + ChevronDown, + ChevronRight, + RefreshCw, + Loader2, + AlertTriangle, +} from 'lucide-react' +import { cn } from '@/lib/utils' +import type { TicketContext } from '@/api/psaContext' + +interface TicketContextPanelProps { + context: TicketContext | null + loading: boolean + error: string | null + onRefresh: () => void +} + +interface AccordionSectionProps { + label: string + icon: React.ReactNode + count?: number + children: React.ReactNode +} + +function AccordionSection({ label, icon, count, children }: AccordionSectionProps) { + const [open, setOpen] = useState(false) + + return ( +
+ + {open && ( +
+ {children} +
+ )} +
+ ) +} + +export function TicketContextPanel({ context, loading, error, onRefresh }: TicketContextPanelProps) { + return ( +
+ {/* Header */} +
+ + + Ticket Context + + +
+ + {/* Loading */} + {loading && !context && ( +
+ +
+ )} + + {/* Error */} + {error && !loading && ( +
+ +

{error}

+
+ )} + + {/* Context content */} + {context && !loading && ( + <> + {/* Compact summary */} +
+
+ #{context.ticket.id} + {context.ticket.summary} +
+
+ + {context.ticket.status} + + + {context.ticket.priority} + + {context.ticket.sla && ( + + SLA: {context.ticket.sla} + + )} +
+

{context.company.name}

+
+ + {/* Client */} + }> +
+

{context.company.name}

+ {context.company.type && ( +

Type: {context.company.type}

+ )} + {context.company.territory && ( +

Territory: {context.company.territory}

+ )} + {context.company.site && ( +

Site: {context.company.site}

+ )} + {context.company.address && ( +

{context.company.address}

+ )} + {context.company.phone && ( +

{context.company.phone}

+ )} +
+
+ + {/* Contact */} + {context.contact && ( + }> +
+

{context.contact.name}

+ {context.contact.title && ( +

{context.contact.title}

+ )} + {context.contact.email && ( +

{context.contact.email}

+ )} + {context.contact.phone && ( +

{context.contact.phone}

+ )} +
+
+ )} + + {/* Devices */} + {context.configurations.length > 0 && ( + } + count={context.configurations.length} + > +
+ {context.configurations.map((cfg, i) => ( +
+

{cfg.device_identifier}

+
+ {cfg.type &&

Type: {cfg.type}

} + {cfg.os_type &&

OS: {cfg.os_type}

} + {cfg.ip_address &&

IP: {cfg.ip_address}

} + {cfg.serial_number &&

S/N: {cfg.serial_number}

} + {cfg.model_number &&

Model: {cfg.model_number}

} +
+
+ ))} +
+
+ )} + + {/* Notes */} + {context.notes.length > 0 && ( + } + count={context.notes.length} + > +
+ {context.notes.map((note, i) => ( +
+
+ {note.member && ( + {note.member} + )} + + {new Date(note.date_created).toLocaleDateString()} + +
+

+ {note.text} +

+
+ ))} +
+
+ )} + + {/* Related Tickets */} + {context.related_tickets.length > 0 && ( + } + count={context.related_tickets.length} + > +
+ {context.related_tickets.map((rt) => ( +
+
+ #{rt.id} + {rt.summary} +
+
+ {rt.status} + ยท + {rt.priority} +
+
+ ))} +
+
+ )} + + )} +
+ ) +} diff --git a/frontend/src/components/session/index.ts b/frontend/src/components/session/index.ts index 78be3134..dfd1d696 100644 --- a/frontend/src/components/session/index.ts +++ b/frontend/src/components/session/index.ts @@ -3,3 +3,4 @@ export { ContinuationModal, type DescendantNode } from './ContinuationModal' export { ForkTreeModal } from './ForkTreeModal' export { ScratchpadSidebar } from './ScratchpadSidebar' export { SessionOutcomeModal } from './SessionOutcomeModal' +export { TicketContextPanel } from './TicketContextPanel'