181 lines
6.9 KiB
TypeScript
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>
|
|
);
|
|
}
|