diff --git a/src/components/documents/signing-details-dialog.tsx b/src/components/documents/signing-details-dialog.tsx new file mode 100644 index 00000000..71ee7019 --- /dev/null +++ b/src/components/documents/signing-details-dialog.tsx @@ -0,0 +1,162 @@ +'use client'; + +import { useQuery } from '@tanstack/react-query'; +import { Loader2 } from 'lucide-react'; + +import { apiFetch } from '@/lib/api/client'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { StatusPill, type StatusPillStatus } from '@/components/ui/status-pill'; + +interface SigningDetailsResponse { + data: { + workflow: { + id: string; + title: string; + status: string; + documentType: string; + createdAt: string; + updatedAt: string; + }; + signers: Array<{ + id: string; + signerName: string; + signerEmail: string; + signerRole: string; + status: string; + signedAt: string | null; + }>; + events: Array<{ + id: string; + eventType: string; + createdAt: string; + }>; + }; +} + +interface Props { + documentId: string | null; + open: boolean; + onOpenChange: (open: boolean) => void; +} + +export function SigningDetailsDialog({ documentId, open, onOpenChange }: Props) { + const { data, isLoading } = useQuery({ + queryKey: ['document-signing-details', documentId], + queryFn: () => + apiFetch(`/api/v1/documents/${documentId}/signing-details`), + enabled: Boolean(documentId) && open, + }); + + return ( + + + + Signing details + + Audit trail for this signed document — signers and timeline. + + + {isLoading || !data ? ( +
+ + Loading… +
+ ) : ( +
+
+

{data.data.workflow.title}

+

+ Status: + + {data.data.workflow.status} + + · + + Created {new Date(data.data.workflow.createdAt).toLocaleString('en-GB')} + +

+
+ +
+
+ Signers +
+
    + {data.data.signers.map((s) => ( +
  • +
    + {s.signerName} + {s.signerEmail} +
    +
    + {s.signedAt ? ( + + {new Date(s.signedAt).toLocaleDateString('en-GB')} + + ) : null} + {s.status} +
    +
  • + ))} +
+
+ +
+
+ Timeline +
+
    + {data.data.events.map((e) => ( +
  1. + + {new Date(e.createdAt).toLocaleString('en-GB')} + + {e.eventType.replace(/_/g, ' ')} +
  2. + ))} +
+
+
+ )} +
+
+ ); +} + +function mapSignerStatus(status: string): StatusPillStatus { + const known: Record = { + pending: 'pending', + sent: 'sent', + signed: 'signed', + declined: 'declined', + expired: 'expired', + cancelled: 'cancelled', + rejected: 'rejected', + }; + return known[status] ?? 'pending'; +} + +function mapWorkflowStatus(status: string): StatusPillStatus { + const known: Record = { + pending: 'pending', + draft: 'draft', + sent: 'sent', + partial: 'partial', + completed: 'completed', + signed: 'signed', + expired: 'expired', + cancelled: 'cancelled', + declined: 'declined', + rejected: 'rejected', + }; + return known[status] ?? 'pending'; +}