dstpabuaran.com/resources/js/pages/admin/system/form-histories/attribute-changes-dialog.tsx

181 lines
6.9 KiB
TypeScript

import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { Badge } from '@/components/ui/badge';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import type { FormHistory } from './columns';
type AttributeChangesDialogProps = {
open: boolean;
onOpenChange: (open: boolean) => void;
item: FormHistory | null;
};
const eventBadgeVariant: Record<string, 'default' | 'secondary' | 'destructive' | 'outline'> = {
created: 'default',
updated: 'secondary',
deleted: 'destructive',
};
const eventLabel: Record<string, string> = {
created: 'Ditambahkan',
updated: 'Diperbarui',
deleted: 'Dihapus',
};
function renderValue(value: unknown): React.ReactNode {
if (value === null || value === undefined) {
return <span className="text-muted-foreground">-</span>;
}
if (typeof value === 'boolean') {
return value ? 'Ya' : 'Tidak';
}
if (Array.isArray(value)) {
if (value.length === 0) {
return <span className="text-muted-foreground">-</span>;
}
if (value.every((item) => typeof item !== 'object' || item === null)) {
return value.join(', ');
}
return (
<div className="space-y-2">
{value.map((item, index) => (
<div key={index} className="rounded border border-border/50 px-2 py-1">
{renderValue(item)}
</div>
))}
</div>
);
}
if (typeof value === 'object') {
const entries = Object.entries(value as Record<string, unknown>);
if (entries.length === 0) {
return <span className="text-muted-foreground">-</span>;
}
return (
<div className="space-y-0.5">
{entries.map(([key, val]) => (
<div key={key}>
<span className="text-muted-foreground">{key}:</span>{' '}
{renderValue(val)}
</div>
))}
</div>
);
}
return String(value);
}
function flattenChanges(attributeChanges: Record<string, { old?: unknown; new?: unknown }>): { field: string; old?: unknown; new?: unknown }[] {
const entries: { field: string; old?: unknown; new?: unknown }[] = [];
for (const [key, change] of Object.entries(attributeChanges)) {
entries.push({
field: key,
old: change.old,
new: change.new,
});
}
return entries;
}
export function AttributeChangesDialog({ open, onOpenChange, item }: AttributeChangesDialogProps) {
if (!item) {
return null;
}
const changes = item.attribute_changes;
const hasChanges = changes && Object.keys(changes).length > 0;
const flattened = hasChanges ? flattenChanges(changes) : [];
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-3xl overflow-hidden">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<span>{item.description}</span>
<Badge variant={eventBadgeVariant[item.event] ?? 'outline'}>
{eventLabel[item.event] ?? item.event}
</Badge>
</DialogTitle>
</DialogHeader>
<div className="space-y-4 overflow-y-auto max-h-[calc(85vh-8rem)]">
<div className="text-sm text-muted-foreground space-y-1">
<div>Oleh: <span className="font-medium text-foreground">{item.causer?.full_name ?? item.causer?.username ?? '-'}</span></div>
<div>{item.formatted_created_at}</div>
</div>
{hasChanges && (
<div className="rounded-md border">
<div className="max-h-[300px] overflow-auto">
<Table>
<TableHeader>
<TableRow>
<TableHead className="h-8 text-xs w-[30%]">Field</TableHead>
<TableHead className="h-8 text-xs w-[35%]">Lama</TableHead>
<TableHead className="h-8 text-xs w-[35%]">Baru</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{flattened.map((row) => (
<TableRow key={row.field}>
<TableCell className="py-1.5 text-xs font-medium">
{row.field}
</TableCell>
<TableCell className="py-1.5 text-xs">
{row.old !== undefined ? (
<span className="text-red-600 dark:text-red-400">
{renderValue(row.old)}
</span>
) : (
<span className="text-muted-foreground">-</span>
)}
</TableCell>
<TableCell className="py-1.5 text-xs">
{row.new !== undefined ? (
<span className="text-green-600 dark:text-green-400">
{renderValue(row.new)}
</span>
) : (
<span className="text-muted-foreground">-</span>
)}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
)}
{!hasChanges && (
<p className="text-sm text-muted-foreground text-center py-4">
Tidak ada perubahan data.
</p>
)}
</div>
</DialogContent>
</Dialog>
);
}