Files
resolutionflow/frontend/src/components/session/TicketContextPanel.tsx
chihlasm 5ff9a9d75e feat: replace all hardcoded orange rgba with blue rgba
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>
2026-03-29 16:20:13 +00:00

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>
)
}