Mechanical find-and-replace: rgba(249,115,22,...) → rgba(96,165,250,...) across ~40 component and page files. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
243 lines
9.6 KiB
TypeScript
243 lines
9.6 KiB
TypeScript
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 (
|
|
<div className="border-t border-[rgba(255,255,255,0.06)]">
|
|
<button
|
|
onClick={() => setOpen(!open)}
|
|
className="flex w-full items-center gap-2 px-3 py-2 text-left hover:bg-[rgba(255,255,255,0.03)] transition-colors"
|
|
>
|
|
<span className="text-muted-foreground">{icon}</span>
|
|
<span className="flex-1 text-xs font-medium text-foreground">{label}</span>
|
|
{count !== undefined && count > 0 && (
|
|
<span className="rounded-full bg-accent-dim px-1.5 py-0.5 text-[0.6rem] font-sans text-xs text-primary">
|
|
{count}
|
|
</span>
|
|
)}
|
|
<span className="text-muted-foreground">
|
|
{open ? <ChevronDown className="h-3.5 w-3.5" /> : <ChevronRight className="h-3.5 w-3.5" />}
|
|
</span>
|
|
</button>
|
|
{open && (
|
|
<div className="px-3 pb-3 pt-1">
|
|
{children}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export function TicketContextPanel({ context, loading, error, onRefresh }: TicketContextPanelProps) {
|
|
return (
|
|
<div className="card-flat overflow-hidden rounded-lg">
|
|
{/* Header */}
|
|
<div className="flex items-center gap-2 bg-[rgba(96,165,250,0.05)] px-3 py-2.5">
|
|
<Ticket className="h-3.5 w-3.5 text-primary" />
|
|
<span className="flex-1 font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-primary">
|
|
Ticket Context
|
|
</span>
|
|
<button
|
|
onClick={onRefresh}
|
|
disabled={loading}
|
|
title="Refresh ticket context"
|
|
className="rounded p-0.5 text-muted-foreground hover:text-foreground disabled:cursor-not-allowed transition-colors"
|
|
>
|
|
<RefreshCw className={cn('h-3 w-3', loading && 'animate-spin')} />
|
|
</button>
|
|
</div>
|
|
|
|
{/* Loading */}
|
|
{loading && !context && (
|
|
<div className="flex items-center justify-center py-6">
|
|
<Loader2 className="h-5 w-5 animate-spin text-muted-foreground" />
|
|
</div>
|
|
)}
|
|
|
|
{/* Error */}
|
|
{error && !loading && (
|
|
<div className="flex items-start gap-2 px-3 py-3">
|
|
<AlertTriangle className="mt-0.5 h-3.5 w-3.5 shrink-0 text-rose-400" />
|
|
<p className="text-xs text-rose-400">{error}</p>
|
|
</div>
|
|
)}
|
|
|
|
{/* Context content */}
|
|
{context && !loading && (
|
|
<>
|
|
{/* Compact summary */}
|
|
<div className="px-3 py-2.5">
|
|
<div className="flex items-baseline gap-2">
|
|
<span className="font-sans text-xs text-xs font-medium text-primary">#{context.ticket.id}</span>
|
|
<span className="flex-1 truncate text-xs text-foreground">{context.ticket.summary}</span>
|
|
</div>
|
|
<div className="mt-1.5 flex flex-wrap gap-1.5">
|
|
<span className="rounded-md bg-card px-1.5 py-0.5 font-sans text-xs text-[0.6rem] text-muted-foreground border border-[rgba(255,255,255,0.06)]">
|
|
{context.ticket.status}
|
|
</span>
|
|
<span className="rounded-md bg-card px-1.5 py-0.5 font-sans text-xs text-[0.6rem] text-muted-foreground border border-[rgba(255,255,255,0.06)]">
|
|
{context.ticket.priority}
|
|
</span>
|
|
{context.ticket.sla && (
|
|
<span className="rounded-md bg-amber-400/10 px-1.5 py-0.5 font-sans text-xs text-[0.6rem] text-amber-400 border border-amber-400/20">
|
|
SLA: {context.ticket.sla}
|
|
</span>
|
|
)}
|
|
</div>
|
|
<p className="mt-1 text-[0.6875rem] text-muted-foreground">{context.company.name}</p>
|
|
</div>
|
|
|
|
{/* Client */}
|
|
<AccordionSection label="Client" icon={<Building2 className="h-3.5 w-3.5" />}>
|
|
<div className="space-y-1 text-xs">
|
|
<p className="font-medium text-foreground">{context.company.name}</p>
|
|
{context.company.type && (
|
|
<p className="text-muted-foreground">Type: {context.company.type}</p>
|
|
)}
|
|
{context.company.territory && (
|
|
<p className="text-muted-foreground">Territory: {context.company.territory}</p>
|
|
)}
|
|
{context.company.site && (
|
|
<p className="text-muted-foreground">Site: {context.company.site}</p>
|
|
)}
|
|
{context.company.address && (
|
|
<p className="text-muted-foreground">{context.company.address}</p>
|
|
)}
|
|
{context.company.phone && (
|
|
<p className="text-muted-foreground">{context.company.phone}</p>
|
|
)}
|
|
</div>
|
|
</AccordionSection>
|
|
|
|
{/* Contact */}
|
|
{context.contact && (
|
|
<AccordionSection label="Contact" icon={<UserCircle className="h-3.5 w-3.5" />}>
|
|
<div className="space-y-1 text-xs">
|
|
<p className="font-medium text-foreground">{context.contact.name}</p>
|
|
{context.contact.title && (
|
|
<p className="text-muted-foreground">{context.contact.title}</p>
|
|
)}
|
|
{context.contact.email && (
|
|
<p className="text-muted-foreground">{context.contact.email}</p>
|
|
)}
|
|
{context.contact.phone && (
|
|
<p className="text-muted-foreground">{context.contact.phone}</p>
|
|
)}
|
|
</div>
|
|
</AccordionSection>
|
|
)}
|
|
|
|
{/* Devices */}
|
|
{context.configurations.length > 0 && (
|
|
<AccordionSection
|
|
label="Devices"
|
|
icon={<Monitor className="h-3.5 w-3.5" />}
|
|
count={context.configurations.length}
|
|
>
|
|
<div className="space-y-2">
|
|
{context.configurations.map((cfg, i) => (
|
|
<div key={i} className="rounded-md border border-[rgba(255,255,255,0.06)] bg-card p-2">
|
|
<p className="text-xs font-medium text-foreground">{cfg.device_identifier}</p>
|
|
<div className="mt-0.5 space-y-0.5 text-[0.6875rem] text-muted-foreground">
|
|
{cfg.type && <p>Type: {cfg.type}</p>}
|
|
{cfg.os_type && <p>OS: {cfg.os_type}</p>}
|
|
{cfg.ip_address && <p>IP: {cfg.ip_address}</p>}
|
|
{cfg.serial_number && <p>S/N: {cfg.serial_number}</p>}
|
|
{cfg.model_number && <p>Model: {cfg.model_number}</p>}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</AccordionSection>
|
|
)}
|
|
|
|
{/* Notes */}
|
|
{context.notes.length > 0 && (
|
|
<AccordionSection
|
|
label="Notes"
|
|
icon={<MessageSquare className="h-3.5 w-3.5" />}
|
|
count={context.notes.length}
|
|
>
|
|
<div className="space-y-2">
|
|
{context.notes.map((note, i) => (
|
|
<div key={i} className="rounded-md border border-[rgba(255,255,255,0.06)] bg-card p-2">
|
|
<div className="mb-1 flex items-center justify-between gap-2">
|
|
{note.member && (
|
|
<span className="text-[0.6rem] font-sans text-xs text-muted-foreground">{note.member}</span>
|
|
)}
|
|
<span className="ml-auto text-[0.6rem] font-sans text-xs text-muted-foreground">
|
|
{new Date(note.date_created).toLocaleDateString()}
|
|
</span>
|
|
</div>
|
|
<p className="whitespace-pre-wrap text-[0.6875rem] text-foreground line-clamp-4">
|
|
{note.text}
|
|
</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</AccordionSection>
|
|
)}
|
|
|
|
{/* Related Tickets */}
|
|
{context.related_tickets.length > 0 && (
|
|
<AccordionSection
|
|
label="Related"
|
|
icon={<Link2 className="h-3.5 w-3.5" />}
|
|
count={context.related_tickets.length}
|
|
>
|
|
<div className="space-y-1.5">
|
|
{context.related_tickets.map((rt) => (
|
|
<div
|
|
key={rt.id}
|
|
className="rounded-md border border-[rgba(255,255,255,0.06)] bg-card px-2 py-1.5"
|
|
>
|
|
<div className="flex items-baseline gap-1.5">
|
|
<span className="font-sans text-xs text-[0.6rem] text-primary">#{rt.id}</span>
|
|
<span className="flex-1 truncate text-[0.6875rem] text-foreground">{rt.summary}</span>
|
|
</div>
|
|
<div className="mt-0.5 flex gap-1">
|
|
<span className="text-[0.6rem] text-muted-foreground">{rt.status}</span>
|
|
<span className="text-[0.6rem] text-muted-foreground">·</span>
|
|
<span className="text-[0.6rem] text-muted-foreground">{rt.priority}</span>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</AccordionSection>
|
|
)}
|
|
</>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|