feat(ticket): redesign audit timeline with structured history events
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { Lock, MessageSquare } from 'lucide-react';
|
||||
|
||||
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
|
||||
import { PersonInfo } from '@/components/person-avatar';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Card,
|
||||
@@ -14,34 +14,18 @@ import {
|
||||
} from '@/components/ui/card';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { TicketDetail } from '@/types';
|
||||
|
||||
function getInitials(name?: string | null, email?: string | null) {
|
||||
const source = name || email || '?';
|
||||
return source
|
||||
.split(/[\s@._-]+/)
|
||||
.filter(Boolean)
|
||||
.slice(0, 2)
|
||||
.map((part) => part.charAt(0).toUpperCase())
|
||||
.join('');
|
||||
}
|
||||
|
||||
function getPersonLabel(person: TicketDetail['reviewer']) {
|
||||
return person?.name || person?.email || 'Unknown';
|
||||
}
|
||||
|
||||
function formatReviewDate(value: string | null | undefined) {
|
||||
if (!value) return '-';
|
||||
return new Intl.DateTimeFormat('en-IN', { dateStyle: 'medium' }).format(
|
||||
new Date(value),
|
||||
);
|
||||
}
|
||||
import { formatDateOnly } from '@/utils/date';
|
||||
|
||||
function getClosedAt(ticket: TicketDetail) {
|
||||
const closedEntry = ticket.history?.find(
|
||||
(item) => item.to_status === 'closed',
|
||||
);
|
||||
const approvedEntry = [...(ticket.history ?? [])]
|
||||
.reverse()
|
||||
.find(
|
||||
(item) =>
|
||||
item.event_type === 'repair_approved' ||
|
||||
item.to_class_status === 'approved',
|
||||
);
|
||||
return (
|
||||
closedEntry?.created_at ??
|
||||
approvedEntry?.created_at ??
|
||||
ticket.reviewer?.reviewed_at ??
|
||||
ticket.timestamps.updated_at
|
||||
);
|
||||
@@ -101,35 +85,19 @@ export function ClosedTicketAction({ ticket }: { ticket: TicketDetail }) {
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg">Resolution Details</CardTitle>
|
||||
<CardDescription>Review record for {ticket.id}</CardDescription>
|
||||
<CardDescription>Review record for {ticket.ticket_name}</CardDescription>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="space-y-5">
|
||||
<div className="space-y-4 rounded-xl border bg-muted/20 p-4">
|
||||
<MetaField
|
||||
label="Reviewed By"
|
||||
value={
|
||||
<div className="flex min-w-0 items-center gap-3">
|
||||
<Avatar className="size-10 border border-primary/30">
|
||||
<AvatarFallback className="bg-primary/15 text-sm text-primary">
|
||||
{getInitials(reviewer?.name, reviewer?.email)}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="min-w-0">
|
||||
<p className="truncate">{getPersonLabel(reviewer)}</p>
|
||||
{reviewer?.email ? (
|
||||
<p className="mt-0.5 truncate text-xs font-medium text-muted-foreground">
|
||||
{reviewer.email}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
value={<PersonInfo person={reviewer} size="lg" />}
|
||||
/>
|
||||
|
||||
<MetaField
|
||||
label="Reviewed Date"
|
||||
value={formatReviewDate(reviewedAt)}
|
||||
value={formatDateOnly(reviewedAt)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user