'use client';
import * as React from 'react';
import { useMutation, useSuspenseQuery } from '@tanstack/react-query';
import { toast } from 'sonner';
import { Icons } from '@/components/icons';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import {
activateDocumentTemplateVersionMutation,
archiveDocumentTemplateVersionMutation,
compareDocumentTemplateVersionsMutation,
duplicateDocumentTemplateVersionMutation,
previewDocumentTemplateVersionMutation,
publishDocumentTemplateVersionMutation,
rollbackDocumentTemplateVersionMutation,
validateDocumentTemplateVersionMutation,
} from '../mutations';
import { documentTemplateVersionManagementQueryOptions } from '../queries';
import type {
DocumentTemplateVersionAuditSummary,
DocumentTemplateVersionCompareResponse,
DocumentTemplateVersionDetail,
DocumentTemplateVersionValidationSummary,
DocumentTemplateVersionVisualSummary,
} from '../types';
function getStatusBadgeVariant(status: 'PASS' | 'WARNING' | 'FAIL' | 'NOT_RUN') {
if (status === 'PASS') {
return 'secondary' as const;
}
if (status === 'FAIL') {
return 'destructive' as const;
}
return 'outline' as const;
}
function getLifecycleBadgeVariant(
status: DocumentTemplateVersionDetail['lifecycleStatus'],
) {
if (status === 'active') {
return 'secondary' as const;
}
if (status === 'archived') {
return 'outline' as const;
}
return 'default' as const;
}
function SummaryCard({
label,
value,
}: {
label: string;
value: React.ReactNode;
}) {
return (
);
}
function StringList({
title,
items,
}: {
title: string;
items: string[];
}) {
if (!items.length) {
return null;
}
return (
{title}
{items.map((item) => (
{item}
))}
);
}
function ValidationIssues({
validation,
}: {
validation: DocumentTemplateVersionValidationSummary | null | undefined;
}) {
if (!validation?.issues.length) {
return null;
}
return (
Template Health Issues
{validation.issues.map((issue) => (
{issue.severity}
{issue.code}
{issue.location}
{issue.message}
{issue.suggestedFix ? (
{issue.suggestedFix}
) : null}
))}
);
}
function AuditSummary({
audit,
visual,
}: {
audit: DocumentTemplateVersionAuditSummary | null;
visual: DocumentTemplateVersionVisualSummary | null | undefined;
}) {
if (!audit) {
return (
No audit summary available.
);
}
return (
{audit.status}}
/>
{audit.visualRegressionStatus}
}
/>
);
}
function CompareDialog({
open,
onOpenChange,
templateId,
currentVersion,
versions,
}: {
open: boolean;
onOpenChange: (open: boolean) => void;
templateId: string;
currentVersion: DocumentTemplateVersionDetail;
versions: DocumentTemplateVersionDetail[];
}) {
const [rightVersionId, setRightVersionId] = React.useState(
versions.find((item) => item.id !== currentVersion.id)?.id ?? currentVersion.id,
);
const [comparison, setComparison] = React.useState<
DocumentTemplateVersionCompareResponse['comparison'] | null
>(null);
const compareMutation = useMutation({
...compareDocumentTemplateVersionsMutation,
onError: (error) =>
toast.error(error instanceof Error ? error.message : 'Compare failed'),
});
React.useEffect(() => {
if (open) {
setComparison(null);
setRightVersionId(
versions.find((item) => item.id !== currentVersion.id)?.id ?? currentVersion.id,
);
}
}, [currentVersion.id, open, versions]);
async function handleCompare() {
const result = await compareMutation.mutateAsync({
templateId,
leftVersionId: currentVersion.id,
rightVersionId,
});
setComparison(result.comparison);
}
return (
);
}
export function TemplateVersionManagementPanel({
templateId,
version,
versions,
}: {
templateId: string;
version: DocumentTemplateVersionDetail;
versions: DocumentTemplateVersionDetail[];
}) {
const { data } = useSuspenseQuery(
documentTemplateVersionManagementQueryOptions(version.id),
);
const managedVersion = data.version;
const [compareOpen, setCompareOpen] = React.useState(false);
const validateMutation = useMutation({
...validateDocumentTemplateVersionMutation,
onSuccess: (result) => toast.success(`Validation: ${result.validation.status}`),
onError: (error) =>
toast.error(error instanceof Error ? error.message : 'Validate failed'),
});
const publishMutation = useMutation({
...publishDocumentTemplateVersionMutation,
onSuccess: () => toast.success('Version published'),
onError: (error) =>
toast.error(error instanceof Error ? error.message : 'Publish failed'),
});
const activateMutation = useMutation({
...activateDocumentTemplateVersionMutation,
onSuccess: () => toast.success('Version activated'),
onError: (error) =>
toast.error(error instanceof Error ? error.message : 'Activate failed'),
});
const rollbackMutation = useMutation({
...rollbackDocumentTemplateVersionMutation,
onSuccess: () => toast.success('Rollback completed'),
onError: (error) =>
toast.error(error instanceof Error ? error.message : 'Rollback failed'),
});
const archiveMutation = useMutation({
...archiveDocumentTemplateVersionMutation,
onSuccess: () => toast.success('Version archived'),
onError: (error) =>
toast.error(error instanceof Error ? error.message : 'Archive failed'),
});
const duplicateMutation = useMutation({
...duplicateDocumentTemplateVersionMutation,
onSuccess: () => toast.success('Draft duplicate created'),
onError: (error) =>
toast.error(error instanceof Error ? error.message : 'Duplicate failed'),
});
const previewMutation = useMutation({
...previewDocumentTemplateVersionMutation,
onSuccess: (result) => {
const url = `data:${result.preview.contentType};base64,${result.preview.base64}`;
window.open(url, '_blank', 'noopener,noreferrer');
toast.success(`Preview ready (${result.preview.pageCount} pages)`);
},
onError: (error) =>
toast.error(error instanceof Error ? error.message : 'Preview failed'),
});
const validation = managedVersion.validationSummary ?? null;
const audit = managedVersion.auditSummary ?? null;
const visual = managedVersion.visualSummary ?? null;
return (
<>
Version Lifecycle Management
{managedVersion.lifecycleStatus ?? 'draft'}
{managedVersion.isActive ? 'Active Runtime' : 'Inactive Runtime'}
{validation ? (
Validation {validation.status}
) : null}
Validation Summary
{validation ? (
) : (
Validation has not been run for this version yet.
)}
>
);
}