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:
@@ -0,0 +1,66 @@
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { api } from '../lib/api';
|
||||
import type { ApiResponse } from '../lib/api';
|
||||
import type { PatientRecord } from '../types';
|
||||
import type { SessionCardData } from '../components/SessionServiceCard';
|
||||
import CreateStep from '../components/session/CreateStep';
|
||||
import { ArrowLeftPH, ArrowLeftD, CloseModalD } from '../components/icons/FilesServiceIcons';
|
||||
|
||||
/** ویرایش مراجعهی ثبتشده — همان فرم «ایجاد سرویس» در حالت ویرایش (PATCH). */
|
||||
export default function EditSessionPage() {
|
||||
const { recordUuid = '', sessionUuid = '' } = useParams();
|
||||
const nav = useNavigate();
|
||||
|
||||
const recordQ = useQuery<ApiResponse<PatientRecord>>({
|
||||
queryKey: ['patient-detail', recordUuid],
|
||||
queryFn: () => api.get(`/api/v1/patient/${recordUuid}`),
|
||||
enabled: !!recordUuid,
|
||||
});
|
||||
const record = recordQ.data?.data as PatientRecord | undefined;
|
||||
const patientName = record?.user_name || record?.profile?.full_name || '—';
|
||||
|
||||
const sessionsQ = useQuery<ApiResponse<SessionCardData[]>>({
|
||||
queryKey: ['patient-sessions', recordUuid, 'all'],
|
||||
queryFn: () => api.get(`/api/v1/patient/${recordUuid}/sessions?filter=all`),
|
||||
enabled: !!recordUuid,
|
||||
});
|
||||
const session = (sessionsQ.data?.data ?? []).find((s) => s.uuid === sessionUuid);
|
||||
|
||||
const finish = () => nav(`/admin/patients/${recordUuid}?tab=services`);
|
||||
|
||||
return (
|
||||
<div className="fade-in" style={{ width: '100%' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-start', marginBottom: 30 }}>
|
||||
<div className="dark:text-[#A1A1A1]" style={{ display: 'flex', alignItems: 'center', gap: 8, color: '#6B7280', fontSize: 12, padding: '0 16px' }}>
|
||||
<div onClick={() => nav(-1)} className="bg-white dark:bg-[#222433]" style={{ display: 'flex', alignItems: 'center', gap: 4, padding: '6px 8px', borderRadius: 12, cursor: 'pointer' }}>
|
||||
<ArrowLeftPH style={{ width: 18, height: 18, rotate: '180deg' }} />
|
||||
<span>بازگشت</span>
|
||||
</div>
|
||||
<ArrowLeftD />
|
||||
<span>پرونده</span>
|
||||
<ArrowLeftD />
|
||||
<span className="dark:text-[#D7D8ED]" style={{ color: '#111827' }}>{patientName}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ width: '100%', display: 'flex', justifyContent: 'center' }}>
|
||||
<div className="bg-white dark:bg-[#222433]" style={{ width: 748, maxWidth: '100%', padding: 24 }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>
|
||||
<h2 style={{ fontSize: 16, fontWeight: 700 }}>ویرایش مراجعه</h2>
|
||||
<button type="button" aria-label="بستن" onClick={() => nav(-1)}
|
||||
style={{ minWidth: 40, height: 40, borderRadius: '50%', border: 'none', background: 'transparent', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<CloseModalD />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{session ? (
|
||||
<CreateStep recordUuid={recordUuid} profile={record?.profile} editSession={session} onCreated={finish} onCancel={() => nav(-1)} />
|
||||
) : (
|
||||
<div style={{ padding: '40px 0', textAlign: 'center', color: 'var(--text-3)', fontSize: 14 }}>در حال بارگذاری...</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import { useIssueInvoice } from '../hooks/useIssueInvoice';
|
||||
import AppointmentTurnCard, { type AppointmentCardData } from '../components/AppointmentTurnCard';
|
||||
import SessionPaymentAccordion, { type SessionPaymentData } from '../components/SessionPaymentAccordion';
|
||||
import InvoiceSummaryModal from '../components/InvoiceSummaryModal';
|
||||
import SessionAuditModal from '../components/session/SessionAuditModal';
|
||||
import SearchableSelect from '../components/ui/SearchableSelect';
|
||||
import { TurnsFilter, AddTurn } from '../components/icons/FilesToolbarIcons';
|
||||
import {
|
||||
@@ -81,6 +82,7 @@ export default function PatientDetailPage() {
|
||||
const qc = useQueryClient();
|
||||
const nav = useNavigate();
|
||||
const [invoiceUuid, setInvoiceUuid] = useState<string | null>(null);
|
||||
const [auditSessionUuid, setAuditSessionUuid] = useState<string | null>(null);
|
||||
const [sessionFilter, setSessionFilter] = useState<'active' | 'all' | 'archived'>('active');
|
||||
|
||||
// «مشاهده فاکتور»: اگر session هنوز فاکتور ندارد، اول صادر میشود (idempotent).
|
||||
@@ -241,7 +243,7 @@ export default function PatientDetailPage() {
|
||||
) : (
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(240px, 1fr))', columnGap: 6, rowGap: 10, alignItems: 'stretch' }}>
|
||||
{sessions.map((s) => (
|
||||
<SessionServiceCard key={s.uuid} session={s} onSettle={(u) => nav(`/admin/patients/${uuid}/session/${u}/pay`)} onViewInvoice={viewInvoice} onArchive={(sess, archived) => archiveMut.mutate({ sessionUuid: sess.uuid, archived })} issuing={issueInvoiceMut.isPending} />
|
||||
<SessionServiceCard key={s.uuid} session={s} onSettle={(u) => nav(`/admin/patients/${uuid}/session/${u}/pay`)} onViewInvoice={viewInvoice} onEdit={(sess) => nav(`/admin/patients/${uuid}/session/${sess.uuid}/edit`)} onViewAudit={(sess) => setAuditSessionUuid(sess.uuid)} onArchive={(sess, archived) => archiveMut.mutate({ sessionUuid: sess.uuid, archived })} issuing={issueInvoiceMut.isPending} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
@@ -265,6 +267,7 @@ export default function PatientDetailPage() {
|
||||
)}
|
||||
|
||||
<InvoiceSummaryModal invoiceUuid={invoiceUuid} onClose={() => setInvoiceUuid(null)} />
|
||||
<SessionAuditModal sessionUuid={auditSessionUuid} onClose={() => setAuditSessionUuid(null)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user