Five places where the data existed and the screen did not use it. Booking a whole course had no button because it needs a doctor and the course does not carry one — each session can be with a different doctor. The page now asks for the doctor the same way the resource booking page does, and the button explains that it is all-or-nothing before it is pressed. A course whose package does not cover the remaining sessions is still valid — the rest is simply charged normally — but nobody was told. The course response carries package_balance and the shortfall, and the page warns. Before session six, not during it. The credit ledger already returned who recorded a row and which appointment it belonged to, and showed neither. An adjustable ledger without the name of the person who adjusted it is half an audit trail. Version history printed a JSON blob of each version's effects, which does not answer the question anyone actually has: what changed? It now diffs each version against the previous one, field by field, and says so plainly when a version changed nothing meaningful. A resource with no calendar showed "—" for utilization. Null means undefined, not zero, and the next step is always the same: set up the calendar. It is a link now. The report range also accepts a custom from/to, kept in the URL like the rest. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
98 lines
3.8 KiB
TypeScript
98 lines
3.8 KiB
TypeScript
import React from 'react';
|
|
import { formatDate } from '../lib/utils';
|
|
|
|
interface VersionRow {
|
|
version: number;
|
|
created_at: number;
|
|
snapshot: Record<string, unknown>;
|
|
}
|
|
|
|
/** فقط فیلدهایی که تغییرشان معنا دارد — `updated_at` و نسخه خودشان همیشه فرق دارند. */
|
|
const WATCHED: Array<{ key: string; label: string }> = [
|
|
{ key: 'name', label: 'نام' },
|
|
{ key: 'priority', label: 'اولویت' },
|
|
{ key: 'active', label: 'فعال' },
|
|
{ key: 'condition', label: 'شرط' },
|
|
{ key: 'effects', label: 'اثرها' },
|
|
{ key: 'valid_from', label: 'شروع اعتبار' },
|
|
{ key: 'valid_to', label: 'پایان اعتبار' },
|
|
{ key: 'service_uuid', label: 'سرویس' },
|
|
{ key: 'address_uuid', label: 'شعبه' },
|
|
];
|
|
|
|
function show(value: unknown): string {
|
|
if (value === null || value === undefined) return '—';
|
|
if (typeof value === 'boolean') return value ? 'بله' : 'خیر';
|
|
if (typeof value === 'object') return JSON.stringify(value, null, 0);
|
|
|
|
return String(value);
|
|
}
|
|
|
|
/**
|
|
* تفاوت هر نسخه با نسخهٔ پیش از خودش.
|
|
*
|
|
* فهرست کامل اثرها روی هر نسخه، سؤال واقعی را جواب نمیدهد: «چه چیزی عوض شد؟». وقتی
|
|
* نوبتی به نسخهٔ ۳ ارجاع میدهد و کسی میپرسد چرا قیمتش فرق دارد، همین ستون جواب است.
|
|
*/
|
|
export default function PolicyVersionDiff({ versions }: { versions: VersionRow[] }) {
|
|
if (!versions || versions.length === 0) return null;
|
|
|
|
const ordered = [...versions].sort((a, b) => a.version - b.version);
|
|
|
|
return (
|
|
<div className="card card-pad" style={{ marginBottom: 16 }}>
|
|
<h3 style={{ fontSize: 14, margin: '0 0 10px' }}>تاریخچهٔ نسخهها</h3>
|
|
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
|
{ordered.map((v, i) => {
|
|
const previous = i === 0 ? null : ordered[i - 1].snapshot;
|
|
|
|
const changes = previous
|
|
? WATCHED.filter((f) => show(v.snapshot[f.key]) !== show(previous[f.key]))
|
|
: [];
|
|
|
|
return (
|
|
<div
|
|
key={v.version}
|
|
style={{ borderTop: i === 0 ? undefined : '1px solid var(--border)', paddingTop: i === 0 ? 0 : 10 }}
|
|
>
|
|
<div style={{ display: 'flex', gap: 10, fontSize: 12, alignItems: 'center' }}>
|
|
<span style={{ fontWeight: 600, minWidth: 60 }}>نسخهٔ {v.version}</span>
|
|
<span style={{ color: 'var(--text-3)' }}>{formatDate(v.created_at)}</span>
|
|
{previous === null && (
|
|
<span style={{ color: 'var(--text-3)' }}>نسخهٔ نخست</span>
|
|
)}
|
|
{previous !== null && changes.length === 0 && (
|
|
<span style={{ color: 'var(--text-3)' }}>بدون تغییرِ معنادار</span>
|
|
)}
|
|
</div>
|
|
|
|
{changes.map((f) => (
|
|
<div
|
|
key={f.key}
|
|
style={{ display: 'flex', gap: 8, fontSize: 12, marginTop: 6, flexWrap: 'wrap' }}
|
|
>
|
|
<span style={{ minWidth: 90, color: 'var(--text-2)' }}>{f.label}</span>
|
|
<span
|
|
style={{
|
|
color: 'var(--danger)',
|
|
textDecoration: 'line-through',
|
|
wordBreak: 'break-all',
|
|
}}
|
|
>
|
|
{show(previous?.[f.key])}
|
|
</span>
|
|
<span style={{ color: 'var(--text-3)' }}>←</span>
|
|
<span style={{ color: 'var(--success)', wordBreak: 'break-all' }}>
|
|
{show(v.snapshot[f.key])}
|
|
</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|