feat: add national code to patient records and user entity, update related functionality
This commit is contained in:
@@ -2,6 +2,7 @@ import {
|
||||
CheckCircleIcon,
|
||||
ChevronRightIcon,
|
||||
FolderOpenIcon,
|
||||
IdentificationIcon,
|
||||
MagnifyingGlassIcon,
|
||||
PencilIcon,
|
||||
PhoneIcon,
|
||||
@@ -15,7 +16,6 @@ import React, { useCallback, useRef, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import DataTable, { type Column } from "../components/ui/DataTable";
|
||||
import FeatureGate from "../components/ui/FeatureGate";
|
||||
import Modal from "../components/ui/Modal";
|
||||
import PageHeader from "../components/ui/PageHeader";
|
||||
@@ -65,13 +65,15 @@ const EMPTY_RECORDS: PatientRecord[] = [];
|
||||
const EMPTY_SESSIONS: PatientSession[] = [];
|
||||
|
||||
function getPatientName(record?: PatientRecord | null) {
|
||||
const user = record?.user;
|
||||
return user?.fullName || user?.name || record?.user_name || "—";
|
||||
return record?.user_name || "—";
|
||||
}
|
||||
|
||||
function getPatientPhone(record?: PatientRecord | null) {
|
||||
const user = record?.user;
|
||||
return user?.phone || record?.user_mobile || "—";
|
||||
return record?.user_mobile || "—";
|
||||
}
|
||||
|
||||
function getPatientNationalCode(record?: PatientRecord | null) {
|
||||
return record?.user_national_code || null;
|
||||
}
|
||||
|
||||
function calcFinalPrice(
|
||||
@@ -121,7 +123,9 @@ function MyPatientsPageInner() {
|
||||
uuid: string;
|
||||
name: string | null;
|
||||
mobile: string;
|
||||
national_code?: string | null;
|
||||
} | null>(null);
|
||||
const [recordNationalCode, setRecordNationalCode] = useState("");
|
||||
const [searchError, setSearchError] = useState("");
|
||||
const mobileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
@@ -296,72 +300,6 @@ function MyPatientsPageInner() {
|
||||
setPage(1);
|
||||
}, []);
|
||||
|
||||
const recordColumns: Column<PatientRecord>[] = [
|
||||
{
|
||||
key: "user",
|
||||
header: "بیمار",
|
||||
render: (r) => (
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 10 }}>
|
||||
<div
|
||||
className="avatar sm"
|
||||
style={{
|
||||
background:
|
||||
"linear-gradient(145deg, oklch(0.62 0.15 256), oklch(0.48 0.16 256))",
|
||||
flexShrink: 0,
|
||||
fontWeight: 700,
|
||||
}}
|
||||
>
|
||||
{(r.user?.fullName ?? "?").charAt(0)}
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ fontWeight: 600, fontSize: 14 }}>
|
||||
{r.user?.fullName ?? "—"}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: 12,
|
||||
color: "var(--text-3)",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 4,
|
||||
marginTop: 2,
|
||||
}}
|
||||
>
|
||||
<PhoneIcon style={{ width: 11 }} />
|
||||
<span dir="ltr">{r.user?.phone ?? "—"}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "created_at",
|
||||
header: "تاریخ ثبت",
|
||||
render: (r) => (
|
||||
<span style={{ fontSize: 13, color: "var(--text-3)" }}>
|
||||
{formatDate(r.created_at)}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "uuid",
|
||||
header: "",
|
||||
render: (r) => (
|
||||
<button
|
||||
className="btn primary sm"
|
||||
style={{ display: "flex", alignItems: "center", gap: 5 }}
|
||||
onClick={() => {
|
||||
setSelectedRecord(r);
|
||||
setSessionPage(1);
|
||||
}}
|
||||
>
|
||||
<FolderOpenIcon style={{ width: 14 }} />
|
||||
مشاهده پرونده
|
||||
</button>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
if (!selectedRecord) {
|
||||
return (
|
||||
<>
|
||||
@@ -408,65 +346,77 @@ function MyPatientsPageInner() {
|
||||
}}
|
||||
value={search}
|
||||
onChange={(e) => handleSearch(e.target.value)}
|
||||
placeholder="جستجو بر اساس نام یا تلفن..."
|
||||
placeholder="جستجو بر اساس نام، شماره موبایل یا کد ملی..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card">
|
||||
{records.length === 0 && !isLoading ? (
|
||||
{records.length === 0 && !isLoading ? (
|
||||
<div
|
||||
className="card"
|
||||
style={{
|
||||
textAlign: "center",
|
||||
padding: "60px 24px",
|
||||
color: "var(--text-3)",
|
||||
}}
|
||||
>
|
||||
<UsersIcon
|
||||
style={{
|
||||
width: 48,
|
||||
margin: "0 auto 16px",
|
||||
display: "block",
|
||||
opacity: 0.4,
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
textAlign: "center",
|
||||
padding: "60px 24px",
|
||||
color: "var(--text-3)",
|
||||
fontWeight: 600,
|
||||
fontSize: 15,
|
||||
marginBottom: 8,
|
||||
color: "var(--text-2)",
|
||||
}}
|
||||
>
|
||||
<UsersIcon
|
||||
style={{
|
||||
width: 48,
|
||||
margin: "0 auto 16px",
|
||||
display: "block",
|
||||
opacity: 0.4,
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
fontWeight: 600,
|
||||
fontSize: 15,
|
||||
marginBottom: 8,
|
||||
color: "var(--text-2)",
|
||||
}}
|
||||
>
|
||||
{search
|
||||
? "بیماری یافت نشد"
|
||||
: "هنوز بیماری ثبت نشده است"}
|
||||
</div>
|
||||
<div style={{ fontSize: 13 }}>
|
||||
{search
|
||||
? "عبارت جستجو را تغییر دهید"
|
||||
: "پس از ثبت نوبت، پرونده بیمار به طور خودکار ایجاد میشود"}
|
||||
</div>
|
||||
{search
|
||||
? "بیماری یافت نشد"
|
||||
: "هنوز بیماری ثبت نشده است"}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<DataTable
|
||||
columns={recordColumns}
|
||||
data={records}
|
||||
loading={isLoading}
|
||||
emptyMessage="بیماری ثبت نشده است"
|
||||
/>
|
||||
<div style={{ padding: "0 16px 12px" }}>
|
||||
<Pagination
|
||||
page={page}
|
||||
total={totalRec}
|
||||
limit={limit}
|
||||
onPageChange={setPage}
|
||||
<div style={{ fontSize: 13 }}>
|
||||
{search
|
||||
? "عبارت جستجو را تغییر دهید"
|
||||
: "پس از ثبت نوبت، پرونده بیمار به طور خودکار ایجاد میشود"}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns:
|
||||
"repeat(auto-fill, minmax(280px, 1fr))",
|
||||
gap: 14,
|
||||
}}
|
||||
>
|
||||
{records.map((r) => (
|
||||
<PatientCard
|
||||
key={r.uuid}
|
||||
record={r}
|
||||
onOpen={() => {
|
||||
setSelectedRecord(r);
|
||||
setSessionPage(1);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div style={{ marginTop: 16 }}>
|
||||
<Pagination
|
||||
page={page}
|
||||
total={totalRec}
|
||||
limit={limit}
|
||||
onPageChange={setPage}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Modal ایجاد پرونده دستی */}
|
||||
<Modal
|
||||
@@ -1115,6 +1065,136 @@ function MyPatientsPageInner() {
|
||||
);
|
||||
}
|
||||
|
||||
function PatientCard({
|
||||
record,
|
||||
onOpen,
|
||||
}: {
|
||||
record: PatientRecord;
|
||||
onOpen: () => void;
|
||||
}) {
|
||||
const name = getPatientName(record);
|
||||
const phone = getPatientPhone(record);
|
||||
const nationalCode = getPatientNationalCode(record);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="card"
|
||||
style={{
|
||||
padding: 16,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 14,
|
||||
cursor: "pointer",
|
||||
transition: "border-color .15s, box-shadow .15s",
|
||||
}}
|
||||
onClick={onOpen}
|
||||
>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 12 }}>
|
||||
<div
|
||||
className="avatar"
|
||||
style={{
|
||||
width: 44,
|
||||
height: 44,
|
||||
borderRadius: "50%",
|
||||
background:
|
||||
"linear-gradient(145deg, oklch(0.62 0.15 256), oklch(0.48 0.16 256))",
|
||||
color: "#fff",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
fontWeight: 700,
|
||||
fontSize: 17,
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
{(name === "—" ? "؟" : name).charAt(0)}
|
||||
</div>
|
||||
<div style={{ minWidth: 0, flex: 1 }}>
|
||||
<div
|
||||
style={{
|
||||
fontWeight: 600,
|
||||
fontSize: 15,
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: 12,
|
||||
color: "var(--text-3)",
|
||||
marginTop: 2,
|
||||
}}
|
||||
>
|
||||
{formatDate(record.created_at)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 7,
|
||||
fontSize: 13,
|
||||
color: "var(--text-2)",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{ display: "flex", alignItems: "center", gap: 7 }}
|
||||
>
|
||||
<PhoneIcon
|
||||
style={{
|
||||
width: 14,
|
||||
color: "var(--text-3)",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
/>
|
||||
<span dir="ltr">{phone}</span>
|
||||
</div>
|
||||
<div
|
||||
style={{ display: "flex", alignItems: "center", gap: 7 }}
|
||||
>
|
||||
<IdentificationIcon
|
||||
style={{
|
||||
width: 14,
|
||||
color: "var(--text-3)",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
/>
|
||||
<span dir="ltr">
|
||||
{nationalCode ?? (
|
||||
<span style={{ color: "var(--text-3)" }}>
|
||||
کد ملی ثبت نشده
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
className="btn primary sm"
|
||||
style={{
|
||||
width: "100%",
|
||||
justifyContent: "center",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 5,
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onOpen();
|
||||
}}
|
||||
>
|
||||
<FolderOpenIcon style={{ width: 14 }} />
|
||||
مشاهده پرونده
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SessionRow({
|
||||
session,
|
||||
onEdit,
|
||||
|
||||
@@ -426,15 +426,10 @@ export interface PatientRecord {
|
||||
uuid: string;
|
||||
entity_type: string;
|
||||
entity_id: number;
|
||||
user?: {
|
||||
uuid?: string;
|
||||
fullName?: string;
|
||||
name?: string;
|
||||
phone?: string;
|
||||
} | null;
|
||||
user_uuid?: string;
|
||||
user_name?: string | null;
|
||||
user_mobile?: string | null;
|
||||
user_national_code?: string | null;
|
||||
created_at: number;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user