Files
pn-new-crm/src/components/admin/document-templates/template-version-history.tsx
Matt 0e8feb1073 chore: prettier format pass on branch files
Auto-format all files modified during the documents-hub-split feature
branch that were not yet aligned with the project's Prettier config
(single quotes, semicolons, trailing commas).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 13:01:47 +02:00

140 lines
4.1 KiB
TypeScript

'use client';
import { useState, useEffect, useCallback } from 'react';
import { RotateCcw, Clock } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { apiFetch } from '@/lib/api/client';
interface TemplateVersion {
version: number;
content: Record<string, unknown>;
changedBy: string | null;
changedAt: string;
auditLogId: string;
}
interface TemplateVersionHistoryProps {
templateId: string;
currentVersion: number;
onRollback: () => void;
}
export function TemplateVersionHistory({
templateId,
currentVersion,
onRollback,
}: TemplateVersionHistoryProps) {
const [versions, setVersions] = useState<TemplateVersion[]>([]);
const [loading, setLoading] = useState(true);
const [rollingBack, setRollingBack] = useState<number | null>(null);
const [error, setError] = useState<string | null>(null);
const fetchVersions = useCallback(async () => {
setLoading(true);
setError(null);
try {
const res = await apiFetch<{ data: TemplateVersion[] }>(
`/api/v1/admin/templates/${templateId}/versions`,
);
setVersions(res.data);
} catch (err: unknown) {
setError(err instanceof Error ? err.message : 'Failed to load versions');
} finally {
setLoading(false);
}
}, [templateId]);
useEffect(() => {
void fetchVersions();
}, [fetchVersions]);
async function handleRollback(version: number) {
if (
!confirm(
`Roll back to version ${version}? This will create a new version ${currentVersion + 1}.`,
)
)
return;
setRollingBack(version);
setError(null);
try {
await apiFetch(`/api/v1/admin/templates/${templateId}/rollback`, {
method: 'POST',
body: { version },
});
onRollback();
await fetchVersions();
} catch (err: unknown) {
setError(err instanceof Error ? err.message : 'Rollback failed');
} finally {
setRollingBack(null);
}
}
if (loading) {
return (
<div className="py-4 text-center text-sm text-muted-foreground">Loading version history</div>
);
}
if (versions.length === 0) {
return (
<div className="flex flex-col items-center gap-2 rounded-md border border-dashed p-6 text-center">
<Clock className="h-6 w-6 text-muted-foreground" />
<p className="text-sm text-muted-foreground">
No previous versions found. Versions are saved whenever you update the template content.
</p>
</div>
);
}
return (
<div className="space-y-3">
{error && (
<p className="rounded bg-destructive/10 px-3 py-2 text-sm text-destructive">{error}</p>
)}
<p className="text-sm text-muted-foreground">
Current version: <strong>v{currentVersion}</strong>. Click Restore to roll back to a
previous version (creates a new version).
</p>
<div className="divide-y rounded-md border">
{versions.map((v) => (
<div key={v.auditLogId} className="flex items-center justify-between px-4 py-3">
<div className="space-y-0.5">
<div className="flex items-center gap-2">
<Badge variant="outline">v{v.version}</Badge>
<span className="text-sm font-medium">Version {v.version}</span>
</div>
<p className="text-xs text-muted-foreground">
Saved{' '}
{new Date(v.changedAt).toLocaleString('en-GB', {
day: '2-digit',
month: 'short',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
})}
{v.changedBy ? ` by ${v.changedBy}` : ''}
</p>
</div>
<Button
variant="outline"
size="sm"
onClick={() => handleRollback(v.version)}
disabled={rollingBack === v.version}
>
<RotateCcw className="mr-1.5 h-3.5 w-3.5" />
{rollingBack === v.version ? 'Restoring…' : 'Restore'}
</Button>
</div>
))}
</div>
</div>
);
}