feat(admin): session edit form, payment edit/delete UI, and audit history modal
- SessionServiceCard menu gains «ویرایش» and «تاریخچه تغییرات» items.
- SessionAuditModal shows the session's change history (field, op badge,
old->new, actor, time) from /session/{uuid}/audit-log.
- PaymentStep payment rows get edit (modal) + delete (confirm) controls for
non-wallet payments, calling the new PATCH/DELETE payment endpoints.
- CreateStep accepts an editSession prop (prefill + PATCH); EditSessionPage
reuses it at /patients/:recordUuid/session/:sessionUuid/edit.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -55,6 +55,8 @@ interface Props {
|
||||
profile?: PatientProfile | null;
|
||||
onCreated: (sessionUuid: string) => void;
|
||||
onCancel: () => void;
|
||||
/** وقتی داده شود فرم در حالت ویرایش است و با PATCH به همان مراجعه ارسال میکند. */
|
||||
editSession?: import('../SessionServiceCard').SessionCardData;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,8 +64,9 @@ interface Props {
|
||||
* تاریخ/ساعت پذیرش، بخش/سرویس/پرسنل، کالای مصرفی با شمارنده، پکیج، و بلوک بیمهی
|
||||
* موجودِ NewSessionPage (نمایش شرطی: سرویسِ تحت پوشش بیمه یا بیمه در پروفایل بیمار).
|
||||
*/
|
||||
export default function CreateStep({ recordUuid, profile, onCreated, onCancel }: Props) {
|
||||
export default function CreateStep({ recordUuid, profile, onCreated, onCancel, editSession }: Props) {
|
||||
const userName = useAuthStore((s) => s.userName);
|
||||
const isEdit = !!editSession;
|
||||
|
||||
// ── state گام ایجاد ──────────────────────────────────────────────────────
|
||||
const [dateISO, setDateISO] = useState(todayISO());
|
||||
@@ -111,9 +114,31 @@ export default function CreateStep({ recordUuid, profile, onCreated, onCancel }:
|
||||
const requireVisit = (pricingData as any)?.data?.require_visit_price ?? false;
|
||||
|
||||
useEffect(() => {
|
||||
if (freeVisit > 0 && (!visitPrice || visitPrice === '0')) setVisitPrice(String(rialToToman(freeVisit)));
|
||||
if (!isEdit && freeVisit > 0 && (!visitPrice || visitPrice === '0')) setVisitPrice(String(rialToToman(freeVisit)));
|
||||
}, [freeVisit]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
// پیشپرکردن فرم در حالت ویرایش (یکبار).
|
||||
const [prefilled, setPrefilled] = useState(false);
|
||||
useEffect(() => {
|
||||
if (!editSession || prefilled) return;
|
||||
setVisitPrice(String(rialToToman(editSession.visit_price_rials ?? 0)));
|
||||
if (editSession.session_at) {
|
||||
const d = new Date(editSession.session_at * 1000);
|
||||
setDateISO(`${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`);
|
||||
setTime(`${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`);
|
||||
}
|
||||
setNotes((editSession as any).notes ?? '');
|
||||
if (editSession.insurance_base_id) { setBaseId(String(editSession.insurance_base_id)); setBasePercent(String(editSession.base_insurance_discount_percent ?? 0)); }
|
||||
if (editSession.insurance_supplementary_id) { setSuppId(String(editSession.insurance_supplementary_id)); setSuppPercent(String(editSession.supplementary_discount_percent ?? 0)); }
|
||||
setSelectedServices((editSession.services ?? []).map((s) => ({
|
||||
uuid: s.service_item_uuid ?? '', name: s.service_name || s.name || '', price: s.price_rials ?? 0, qty: s.quantity ?? 1, insured: false,
|
||||
})).filter((s) => s.uuid));
|
||||
setSelectedConsumables((editSession.consumables ?? []).map((c) => ({
|
||||
uuid: c.inventory_item_uuid ?? '', name: c.item_name ?? '', price: c.price_rials ?? 0, qty: c.quantity ?? 1,
|
||||
})).filter((c) => c.uuid));
|
||||
setPrefilled(true);
|
||||
}, [editSession, prefilled]);
|
||||
|
||||
const contracts = (contractsData as any)?.data?.data as Contract[] | undefined ?? [];
|
||||
const baseOpts = contracts.filter(c => c.insurance_kind === 'basic').map(c => ({ value: String(c.insurance_id), label: c.insurance_name ?? `#${c.insurance_id}` }));
|
||||
const suppOpts = contracts.filter(c => c.insurance_kind === 'supplementary').map(c => ({ value: String(c.insurance_id), label: c.insurance_name ?? `#${c.insurance_id}` }));
|
||||
@@ -216,10 +241,12 @@ export default function CreateStep({ recordUuid, profile, onCreated, onCancel }:
|
||||
|
||||
// ── ثبت ──────────────────────────────────────────────────────────────────
|
||||
const createMut = useMutation({
|
||||
mutationFn: (body: object) => api.post(`/api/v1/patient/${recordUuid}/session`, body),
|
||||
mutationFn: (body: object) => isEdit
|
||||
? api.patch(`/api/v1/session/${editSession!.uuid}`, body)
|
||||
: api.post(`/api/v1/patient/${recordUuid}/session`, body),
|
||||
onSuccess: (res: any) => {
|
||||
toast.success('مراجعه ثبت شد');
|
||||
onCreated(res?.data?.uuid as string);
|
||||
toast.success(isEdit ? 'مراجعه ویرایش شد' : 'مراجعه ثبت شد');
|
||||
onCreated((isEdit ? editSession!.uuid : res?.data?.uuid) as string);
|
||||
},
|
||||
onError: (e: Error) => toast.error(e.message),
|
||||
});
|
||||
@@ -236,7 +263,7 @@ export default function CreateStep({ recordUuid, profile, onCreated, onCancel }:
|
||||
supplementary_discount_percent: showInsurance ? supp : 0,
|
||||
...(showInsurance && baseId ? { insurance_base_id: Number(baseId) } : {}),
|
||||
...(showInsurance && suppId ? { insurance_supplementary_id: Number(suppId) } : {}),
|
||||
payment_method: 'pending',
|
||||
...(isEdit ? {} : { payment_method: 'pending' }),
|
||||
...(notes ? { notes } : {}),
|
||||
session_at: toSessionAt(dateISO, time),
|
||||
...(packageUuid ? { inventory_package_uuid: packageUuid } : {}),
|
||||
@@ -436,7 +463,7 @@ export default function CreateStep({ recordUuid, profile, onCreated, onCancel }:
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, marginTop: 16, width: '100%', maxWidth: 340, margin: '16px auto 0' }}>
|
||||
<button type="button" style={{ ...ghostBtn, width: 164 }} onClick={onCancel}>انصراف</button>
|
||||
<button type="button" style={{ ...primaryBtn, width: 164 }} onClick={submit} disabled={createMut.isPending}>
|
||||
{createMut.isPending ? 'در حال ذخیره...' : 'ایجاد سرویس'}
|
||||
{createMut.isPending ? 'در حال ذخیره...' : (isEdit ? 'ذخیره تغییرات' : 'ایجاد سرویس')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user