From 49b2ca60d7541fed4ba86dd3edae9e830a04387f Mon Sep 17 00:00:00 2001 From: hamed <15238-genius.ha@users.noreply.drupalcode.org> Date: Fri, 17 Jul 2026 11:13:23 +0330 Subject: [PATCH] feat(appointment): log cancellation events and show them in a Timeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce the first per-appointment event system. On cancel (via the status or general update endpoints) an AppointmentEvent (type=cancelled, «نوبت لغو شد») is recorded with the actor, cancel time, and an optional cancel_reason, plus a warning-level app_log entry. New GET /appointment/{uuid}/events returns the ordered event list. The admin appointment detail page renders a Timeline section and the cancel dialog now collects an optional reason. Co-Authored-By: Claude Fable 5 --- assets/admin/components/ui/ConfirmDialog.tsx | 4 + assets/admin/pages/AppointmentDetailPage.tsx | 63 ++++++++++++++- assets/admin/types/index.ts | 8 ++ docs/api/appointment.md | 46 ++++++++++- migrations/Version20260717073617.php | 31 ++++++++ .../Controller/AppointmentController.php | 59 +++++++++++++- src/Appointment/Entity/AppointmentEvent.php | 77 +++++++++++++++++++ .../Repository/AppointmentEventRepository.php | 31 ++++++++ 8 files changed, 311 insertions(+), 8 deletions(-) create mode 100644 migrations/Version20260717073617.php create mode 100644 src/Appointment/Entity/AppointmentEvent.php create mode 100644 src/Appointment/Repository/AppointmentEventRepository.php diff --git a/assets/admin/components/ui/ConfirmDialog.tsx b/assets/admin/components/ui/ConfirmDialog.tsx index f031428b..1b9d298e 100644 --- a/assets/admin/components/ui/ConfirmDialog.tsx +++ b/assets/admin/components/ui/ConfirmDialog.tsx @@ -12,6 +12,8 @@ interface Props { loading?: boolean; onConfirm: () => void; onCancel: () => void; + /** محتوای اضافه زیر پیام (مثلاً فیلد دلیل). */ + children?: React.ReactNode; } export default function ConfirmDialog({ @@ -24,6 +26,7 @@ export default function ConfirmDialog({ loading = false, onConfirm, onCancel, + children, }: Props) { if (!open) return null; @@ -48,6 +51,7 @@ export default function ConfirmDialog({

{message}

+ {children}
+ +
+

تاریخچه رویدادها

+ {eventsQuery.isLoading ? ( +
+ ) : events.length === 0 ? ( +

رویدادی برای این نوبت ثبت نشده است.

+ ) : ( +
    + {events.map((ev, i) => ( +
  1. + +
    +
    + {ev.title} + {formatDateTime(ev.created_at)} +
    + {ev.actor_name && ( +
    توسط: {ev.actor_name}
    + )} + {ev.reason && ( +
    دلیل: {ev.reason}
    + )} +
    +
  2. + ))} +
+ )} +
) : (
@@ -176,8 +219,20 @@ export default function AppointmentDetailPage() { danger loading={cancelMutation.isPending} onConfirm={() => cancelMutation.mutate()} - onCancel={() => setCancelOpen(false)} - /> + onCancel={() => { setCancelOpen(false); setCancelReason(''); }} + > +
+ +