Files
leocrm/frontend/src/components/common/HistoryDiff.tsx
T

108 lines
3.3 KiB
TypeScript
Raw Normal View History

/**
* HistoryDiff — Visual diff table of field changes for an entity history entry.
* Shows old value (red strikethrough) → new value (green) per field.
*/
import React from 'react';
import clsx from 'clsx';
import { ArrowRight } from 'lucide-react';
import { useTranslation } from 'react-i18next';
export interface HistoryDiffProps {
changes: Record<string, { old: any; new: any }>;
className?: string;
}
/**
* Format a value for display, handling null/undefined gracefully.
*/
function formatValue(value: any): string {
if (value === null || value === undefined) {
return '—';
}
if (typeof value === 'boolean') {
return value ? 'true' : 'false';
}
if (typeof value === 'object') {
try {
return JSON.stringify(value);
} catch {
return String(value);
}
}
return String(value);
}
export function HistoryDiff({ changes, className }: HistoryDiffProps) {
const { t } = useTranslation();
const entries = Object.entries(changes);
if (entries.length === 0) {
return (
<p className="text-sm text-secondary-500 italic">
{t('entityHistory.noChanges', 'Keine Änderungen')}
</p>
);
}
return (
<div className={clsx('overflow-x-auto', className)}>
<table className="w-full text-sm border-collapse">
<thead>
<tr className="border-b border-secondary-200">
<th className="text-left font-medium text-secondary-600 py-2 pr-4">
{t('entityHistory.field', 'Feld')}
</th>
<th className="text-left font-medium text-secondary-600 py-2 pr-4">
{t('entityHistory.oldValue', 'Alter Wert')}
</th>
<th className="w-8 py-2" aria-hidden="true" />
<th className="text-left font-medium text-secondary-600 py-2">
{t('entityHistory.newValue', 'Neuer Wert')}
</th>
</tr>
</thead>
<tbody>
{entries.map(([field, change]) => {
const oldDisplay = formatValue(change.old);
const newDisplay = formatValue(change.new);
const isOldEmpty = change.old === null || change.old === undefined;
const isNewEmpty = change.new === null || change.new === undefined;
return (
<tr key={field} className="border-b border-secondary-100 last:border-0">
<td className="py-2 pr-4 font-medium text-secondary-700">
{field}
</td>
<td
className={clsx(
'py-2 pr-4',
isOldEmpty
? 'text-secondary-400 italic'
: 'text-danger-700 line-through'
)}
>
{oldDisplay}
</td>
<td className="py-2 text-center text-secondary-400">
<ArrowRight className="inline h-3.5 w-3.5" aria-hidden="true" />
</td>
<td
className={clsx(
'py-2',
isNewEmpty
? 'text-secondary-400 italic'
: 'text-success-700 font-medium'
)}
>
{newDisplay}
</td>
</tr>
);
})}
</tbody>
</table>
</div>
);
}