124 lines
3.1 KiB
TypeScript
124 lines
3.1 KiB
TypeScript
'use client';
|
|
|
|
import type { ReactNode } from 'react';
|
|
import { Lock, MessageSquare } from 'lucide-react';
|
|
|
|
import { PersonInfo } from '@/components/person-avatar';
|
|
import { Button } from '@/components/ui/button';
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from '@/components/ui/card';
|
|
import { cn } from '@/lib/utils';
|
|
import type { TicketDetail } from '@/types';
|
|
import { formatDateOnly } from '@/utils/date';
|
|
|
|
function getClosedAt(ticket: TicketDetail) {
|
|
const approvedEntry = [...(ticket.history ?? [])]
|
|
.reverse()
|
|
.find(
|
|
(item) =>
|
|
item.event_type === 'repair_approved' ||
|
|
item.to_class_status === 'approved',
|
|
);
|
|
return (
|
|
approvedEntry?.created_at ??
|
|
ticket.reviewer?.reviewed_at ??
|
|
ticket.timestamps.updated_at
|
|
);
|
|
}
|
|
|
|
function MetaField({
|
|
label,
|
|
value,
|
|
valueClassName,
|
|
}: {
|
|
label: string;
|
|
value: ReactNode;
|
|
valueClassName?: string;
|
|
}) {
|
|
return (
|
|
<div className="space-y-1">
|
|
<p className="text-xs font-medium tracking-wide text-muted-foreground">
|
|
{label}
|
|
</p>
|
|
<div
|
|
className={cn('text-sm font-semibold text-foreground', valueClassName)}
|
|
>
|
|
{value}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function FinalCommentsPanel({
|
|
comment,
|
|
}: {
|
|
comment: string | null | undefined;
|
|
}) {
|
|
if (!comment?.trim()) {
|
|
return (
|
|
<div className="flex min-h-28 flex-col items-center justify-center rounded-lg border border-dashed border-border bg-muted/10 px-4 py-6 text-center">
|
|
<MessageSquare className="mb-2 size-5 text-muted-foreground" />
|
|
<p className="text-sm text-muted-foreground">No comments added yet</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="rounded-lg border bg-muted/20 px-4 py-3">
|
|
<p className="text-sm leading-relaxed text-foreground">
|
|
{comment.trim()}
|
|
</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function ClosedTicketAction({ ticket }: { ticket: TicketDetail }) {
|
|
const reviewer = ticket.reviewer;
|
|
const reviewedAt = reviewer?.reviewed_at ?? getClosedAt(ticket);
|
|
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="text-lg">Resolution Details</CardTitle>
|
|
<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={<PersonInfo person={reviewer} size="lg" />}
|
|
/>
|
|
|
|
<MetaField
|
|
label="Reviewed Date"
|
|
value={formatDateOnly(reviewedAt)}
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<p className="text-xs font-medium tracking-wide text-muted-foreground">
|
|
Review Comment
|
|
</p>
|
|
<FinalCommentsPanel comment={reviewer?.review_comment} />
|
|
</div>
|
|
|
|
<Button
|
|
type="button"
|
|
variant="secondary"
|
|
className="w-full text-muted-foreground"
|
|
disabled
|
|
>
|
|
<Lock className="size-4" />
|
|
Ticket Finalized
|
|
</Button>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|