feat(admin): implement ServiceVisitCard component for improved session display
This commit is contained in:
@@ -2,7 +2,9 @@ import {
|
||||
Bars3Icon,
|
||||
CheckCircleIcon,
|
||||
ChevronRightIcon,
|
||||
ClockIcon,
|
||||
FolderOpenIcon,
|
||||
FunnelIcon,
|
||||
MagnifyingGlassIcon,
|
||||
PencilIcon,
|
||||
PhoneIcon,
|
||||
@@ -1126,52 +1128,37 @@ function MyPatientsPageInner() {
|
||||
{detailTab === "services" && (
|
||||
sessionsLoading ? (
|
||||
<div style={{ padding: 32, textAlign: "center", color: "var(--text-3)" }}>در حال بارگذاری…</div>
|
||||
) : sessions.length === 0 ? (
|
||||
<div className="card" style={{ padding: 32, textAlign: "center", color: "var(--text-3)" }}>
|
||||
هنوز سرویسی برای این بیمار ثبت نشده است.
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(280px, 1fr))", gap: 16 }}>
|
||||
{sessions.map((s) => {
|
||||
const svcNames = (s.services ?? []).map((x) => x.service_name).join("، ") || "ویزیت";
|
||||
const debt = s.patient_debt_rials ?? 0;
|
||||
return (
|
||||
<div key={s.uuid} className="card" style={{ padding: 16, display: "flex", flexDirection: "column", gap: 8 }}>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 8 }}>
|
||||
<div style={{ fontWeight: 700, fontSize: 14, lineHeight: 1.5 }}>{svcNames}</div>
|
||||
{s.is_paid
|
||||
? <span className="badge green"><span className="bdot" />تسویه شده</span>
|
||||
: <span className="badge amber"><span className="bdot" />تسویه نشده</span>}
|
||||
</div>
|
||||
<div className="cp-info-row"><span className="cp-info-label">انجامدهنده</span><span className="cp-info-value">{s.doctor_name || "—"}</span></div>
|
||||
<div className="cp-info-row"><span className="cp-info-label">تاریخ</span><span className="cp-info-value">{formatDate(s.created_at)}</span></div>
|
||||
<div className="cp-info-row"><span className="cp-info-label">هزینه</span><span className="cp-info-value">{formatRial(s.final_price_rials)}</span></div>
|
||||
<div className="cp-info-row" style={{ borderBottom: "none" }}>
|
||||
<span className="cp-info-label">مانده بدهی</span>
|
||||
<span className="cp-info-value" style={{ color: debt > 0 ? "var(--danger)" : "var(--success)" }}>
|
||||
{debt > 0 ? formatRial(debt) : "ندارد"}
|
||||
</span>
|
||||
</div>
|
||||
{s.is_paid ? (
|
||||
s.invoice_uuid ? (
|
||||
<button className="cp-btn-secondary" style={{ width: "100%", marginTop: 4 }} onClick={() => setInvoiceUuid(s.invoice_uuid!)}>مشاهده فاکتور</button>
|
||||
) : (
|
||||
<button className="cp-btn-secondary" style={{ width: "100%", marginTop: 4 }} disabled>پرداخت شده</button>
|
||||
)
|
||||
) : (
|
||||
<button
|
||||
className="cp-btn-primary"
|
||||
style={{ width: "100%", marginTop: 4 }}
|
||||
disabled={settleSessionMut.isPending}
|
||||
onClick={() => settleSessionMut.mutate(s.uuid)}
|
||||
>
|
||||
تکمیل پرداخت
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<>
|
||||
<div style={{ display: "flex", gap: 8, marginBottom: 16 }}>
|
||||
<button
|
||||
className="cp-btn-primary"
|
||||
onClick={() => navigate(`/admin/my-patients/${selectedRecord.uuid}/session/new`)}
|
||||
>
|
||||
<PlusIcon style={{ width: 16 }} /> سرویس جدید
|
||||
</button>
|
||||
<button className="cp-btn-secondary" style={{ padding: "0 12px" }} title="فیلتر">
|
||||
<FunnelIcon style={{ width: 18 }} />
|
||||
</button>
|
||||
</div>
|
||||
{sessions.length === 0 ? (
|
||||
<div className="card" style={{ padding: 32, textAlign: "center", color: "var(--text-3)" }}>
|
||||
هنوز سرویسی برای این بیمار ثبت نشده است.
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(277px, 1fr))", gap: 16 }}>
|
||||
{sessions.map((s) => (
|
||||
<ServiceVisitCard
|
||||
key={s.uuid}
|
||||
session={s}
|
||||
settling={settleSessionMut.isPending}
|
||||
onSettle={(uuid) => settleSessionMut.mutate(uuid)}
|
||||
onViewInvoice={(uuid) => setInvoiceUuid(uuid)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
)}
|
||||
|
||||
@@ -1719,6 +1706,110 @@ function PatientRow({
|
||||
);
|
||||
}
|
||||
|
||||
// کارت هر مراجعه در تب «سرویسها»: اول مراجعه (سرویسها در سرِ کارت)، سپس جزئیات و پرداخت.
|
||||
function ServiceVisitCard({
|
||||
session,
|
||||
settling,
|
||||
onSettle,
|
||||
onViewInvoice,
|
||||
}: {
|
||||
session: PatientSession;
|
||||
settling: boolean;
|
||||
onSettle: (uuid: string) => void;
|
||||
onViewInvoice: (uuid: string) => void;
|
||||
}) {
|
||||
const services = session.services ?? [];
|
||||
const title = services[0]?.service_name || "ویزیت";
|
||||
const subtitle = services.length
|
||||
? services.map((s) => s.service_name).join(" - ")
|
||||
: "ویزیت";
|
||||
const assistant = services.find((s) => s.staff_name)?.staff_name || null;
|
||||
const debt = session.patient_debt_rials ?? 0;
|
||||
const paid = !!session.is_paid;
|
||||
|
||||
const row = (
|
||||
label: string,
|
||||
value: React.ReactNode,
|
||||
danger = false,
|
||||
) => (
|
||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 8 }}>
|
||||
<span style={{ color: "var(--text-3)", fontSize: 12 }}>{label}</span>
|
||||
<span style={{ color: danger ? "var(--danger)" : "var(--text-2)", fontSize: 14, fontWeight: 500 }}>{value}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
const divider = <div style={{ height: 1, background: "var(--border)", margin: "12px 0" }} />;
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
background: "var(--surface)",
|
||||
border: "1px solid var(--border)",
|
||||
borderRadius: 8,
|
||||
boxShadow: "0 1px 24.8px rgba(204,204,204,0.18)",
|
||||
padding: 12,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
{/* سرِ کارت: سرویسها + وضعیت + منو */}
|
||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 8 }}>
|
||||
<div style={{ display: "flex", alignItems: "flex-start", gap: 8 }}>
|
||||
<div style={{ display: "flex", flexDirection: "column", alignItems: "flex-end", gap: 6, textAlign: "right" }}>
|
||||
<span style={{ fontWeight: 700, fontSize: 14, color: "var(--text)", lineHeight: 1.5 }}>{title}</span>
|
||||
<span style={{ fontSize: 12, color: "var(--text-2)", lineHeight: 1.5 }}>{subtitle}</span>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
width: 36, height: 36, borderRadius: 8, flexShrink: 0,
|
||||
display: "grid", placeItems: "center",
|
||||
background: paid ? "rgba(60,154,79,0.15)" : "rgba(241,119,50,0.15)",
|
||||
}}
|
||||
>
|
||||
{paid
|
||||
? <CheckCircleIcon style={{ width: 20, color: "#3C9A4F" }} />
|
||||
: <ClockIcon style={{ width: 20, color: "#F17732" }} />}
|
||||
</div>
|
||||
</div>
|
||||
<EllipsisHorizontalIcon style={{ width: 20, color: "var(--text-3)", flexShrink: 0 }} />
|
||||
</div>
|
||||
|
||||
{divider}
|
||||
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
|
||||
{row("انجام دهنده:", session.doctor_name || "—")}
|
||||
{row("دستیار:", assistant || "—")}
|
||||
{row("تاریخ:", formatDate(session.created_at))}
|
||||
{session.notes && (
|
||||
<div style={{ fontSize: 12, color: "var(--text-3)", textAlign: "right", lineHeight: 1.6 }}>
|
||||
توضیحات:{" "}
|
||||
<span style={{ color: "var(--text-2)", fontWeight: 500 }}>{session.notes}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{divider}
|
||||
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
||||
{row("هزینه:", formatRial(session.final_price_rials))}
|
||||
{row("مانده بدهی:", debt > 0 ? formatRial(debt) : "ندارد", debt > 0)}
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: 12 }}>
|
||||
{paid ? (
|
||||
session.invoice_uuid ? (
|
||||
<button className="cp-btn-secondary" style={{ width: "100%" }} onClick={() => onViewInvoice(session.invoice_uuid!)}>مشاهده فاکتور</button>
|
||||
) : (
|
||||
<button className="cp-btn-secondary" style={{ width: "100%" }} disabled>پرداخت شده</button>
|
||||
)
|
||||
) : (
|
||||
<button className="cp-btn-primary" style={{ width: "100%" }} disabled={settling} onClick={() => onSettle(session.uuid)}>تکمیل پرداخت</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SessionRow({
|
||||
session,
|
||||
onEdit,
|
||||
|
||||
Reference in New Issue
Block a user