170 lines
6.1 KiB
TypeScript
170 lines
6.1 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(key: string, 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 (typeof value[0] === 'object' && value[0] !== null) {
|
|
return <RenderObjectArray items={value} />;
|
|
}
|
|
|
|
return value.join(', ');
|
|
}
|
|
|
|
if (typeof value === 'object') {
|
|
return JSON.stringify(value);
|
|
}
|
|
|
|
return String(value);
|
|
}
|
|
|
|
function RenderObjectArray({ items }: { items: Record<string, unknown>[] }) {
|
|
if (!items.length) {
|
|
return <span className="text-muted-foreground">-</span>;
|
|
}
|
|
|
|
const keys = Object.keys(items[0]);
|
|
|
|
return (
|
|
<div className="rounded-md border">
|
|
<div className="max-h-[300px] overflow-auto">
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
{keys.map((key) => (
|
|
<TableHead key={key} className="h-8 text-xs">
|
|
{key}
|
|
</TableHead>
|
|
))}
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{items.map((item, index) => (
|
|
<TableRow key={index}>
|
|
{keys.map((key) => (
|
|
<TableCell key={key} className="py-1.5 text-xs">
|
|
{renderValue(key, item[key])}
|
|
</TableCell>
|
|
))}
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function AttributeChangesDialog({ open, onOpenChange, item }: AttributeChangesDialogProps) {
|
|
if (!item) {
|
|
return null;
|
|
}
|
|
|
|
const changes = item.attribute_changes;
|
|
const hasNew = changes?.new && Object.keys(changes.new).length > 0;
|
|
const hasOld = changes?.old && Object.keys(changes.old).length > 0;
|
|
|
|
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>
|
|
|
|
{hasNew && (
|
|
<div className="space-y-2">
|
|
<h4 className="text-sm font-medium">Nilai Baru</h4>
|
|
<div className="rounded-md border p-3 bg-muted/50">
|
|
<dl className="space-y-2">
|
|
{Object.entries(changes!.new!).map(([key, value]) => (
|
|
<div key={key} className="flex flex-col">
|
|
<dt className="text-xs text-muted-foreground">{key}</dt>
|
|
<dd className="text-sm font-medium">{renderValue(key, value)}</dd>
|
|
</div>
|
|
))}
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{hasOld && (
|
|
<div className="space-y-2">
|
|
<h4 className="text-sm font-medium">Nilai Lama</h4>
|
|
<div className="rounded-md border p-3 bg-muted/50">
|
|
<dl className="space-y-2">
|
|
{Object.entries(changes!.old!).map(([key, value]) => (
|
|
<div key={key} className="flex flex-col">
|
|
<dt className="text-xs text-muted-foreground">{key}</dt>
|
|
<dd className="text-sm font-medium">{renderValue(key, value)}</dd>
|
|
</div>
|
|
))}
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{!hasNew && !hasOld && (
|
|
<p className="text-sm text-muted-foreground text-center py-4">
|
|
Tidak ada perubahan data.
|
|
</p>
|
|
)}
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
}
|