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

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>
);
}