139 lines
3.7 KiB
TypeScript
139 lines
3.7 KiB
TypeScript
'use client';
|
|
|
|
import type { TicketActor, TicketDetail } from '@/types';
|
|
import { formatDate } from '@/utils/date';
|
|
|
|
import {
|
|
TicketStatusBadge,
|
|
} from '../../components/TicketStatusBadge';
|
|
|
|
function formatCreatedDate(value: string | null | undefined) {
|
|
if (!value) return '-';
|
|
return new Intl.DateTimeFormat('en-IN', { dateStyle: 'medium' }).format(
|
|
new Date(value),
|
|
);
|
|
}
|
|
|
|
function getInitials(person: TicketActor | null | undefined) {
|
|
const source = person?.name || person?.email || '?';
|
|
return source
|
|
.split(/[\s@._-]+/)
|
|
.filter(Boolean)
|
|
.slice(0, 2)
|
|
.map((part) => part.charAt(0).toUpperCase())
|
|
.join('');
|
|
}
|
|
|
|
function getPersonLabel(person: TicketActor | null | undefined) {
|
|
return person?.name || person?.email || '-';
|
|
}
|
|
|
|
function MetaLabel({ children }: { children: React.ReactNode }) {
|
|
return (
|
|
<p className="text-xs font-medium tracking-wide text-muted-foreground">
|
|
{children}
|
|
</p>
|
|
);
|
|
}
|
|
|
|
function MetaValue({ children }: { children: React.ReactNode }) {
|
|
return <p className="text-sm font-semibold text-foreground">{children}</p>;
|
|
}
|
|
|
|
function PersonMetaValue({
|
|
person,
|
|
accentClassName = 'bg-primary/15 text-primary',
|
|
}: {
|
|
person: TicketActor | null | undefined;
|
|
accentClassName?: string;
|
|
}) {
|
|
if (!person) {
|
|
return <MetaValue>-</MetaValue>;
|
|
}
|
|
|
|
return (
|
|
<div className="flex min-w-0 items-center gap-2">
|
|
<span
|
|
className={`flex size-5 shrink-0 items-center justify-center rounded-full text-[10px] font-bold ${accentClassName}`}
|
|
>
|
|
{getInitials(person)}
|
|
</span>
|
|
<p className="truncate text-sm font-medium">{getPersonLabel(person)}</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function OverviewField({
|
|
label,
|
|
children,
|
|
}: {
|
|
label: string;
|
|
children: React.ReactNode;
|
|
}) {
|
|
return (
|
|
<div className="space-y-1">
|
|
<MetaLabel>{label}</MetaLabel>
|
|
{children}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function TicketOverviewCard({ ticket }: { ticket: TicketDetail }) {
|
|
const detectionCount = ticket.ai_result?.detection_count;
|
|
|
|
return (
|
|
<section className="overflow-hidden rounded-xl border bg-card">
|
|
<div className="border-b px-4 py-2.5">
|
|
<h2 className="text-xs font-medium ">
|
|
Overview
|
|
</h2>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-x-4 gap-y-6 p-4 md:grid-cols-4">
|
|
<OverviewField label="Detection Count">
|
|
<MetaValue>
|
|
{detectionCount != null ? `${detectionCount} Occurrences` : '-'}
|
|
</MetaValue>
|
|
</OverviewField>
|
|
|
|
<OverviewField label="Chainage">
|
|
<MetaValue>{ticket.chainage_id || '-'}</MetaValue>
|
|
</OverviewField>
|
|
|
|
<OverviewField label="Uploaded By">
|
|
<PersonMetaValue person={ticket.uploader} />
|
|
</OverviewField>
|
|
|
|
<OverviewField label="Created Date">
|
|
<MetaValue>{formatCreatedDate(ticket.timestamps.created_at)}</MetaValue>
|
|
</OverviewField>
|
|
|
|
<OverviewField label="Assigned To">
|
|
<PersonMetaValue
|
|
person={ticket.worker}
|
|
accentClassName="bg-secondary text-secondary-foreground"
|
|
/>
|
|
</OverviewField>
|
|
|
|
<OverviewField label="Reviewer">
|
|
<PersonMetaValue
|
|
person={ticket.reviewer}
|
|
accentClassName="bg-primary/20 text-primary"
|
|
/>
|
|
</OverviewField>
|
|
|
|
<OverviewField label="Assigned At">
|
|
<MetaValue>{formatDate(ticket.worker?.assigned_at)}</MetaValue>
|
|
</OverviewField>
|
|
|
|
</div>
|
|
|
|
{ticket.ai_result?.completed_at ? (
|
|
<div className="border-t px-4 py-3 text-xs text-muted-foreground">
|
|
AI analysis completed {formatDate(ticket.ai_result.completed_at)}
|
|
</div>
|
|
) : null}
|
|
</section>
|
|
);
|
|
}
|