feat:update-project

This commit is contained in:
TuanVT
2026-08-17 15:48:39 +07:00
parent 684d7d2490
commit 94e7975cd0
11 changed files with 453 additions and 122 deletions
@@ -28,6 +28,7 @@ import {
TooltipProvider, TooltipProvider,
TooltipTrigger, TooltipTrigger,
} from "@radix-ui/react-tooltip"; } from "@radix-ui/react-tooltip";
import { SPECIALTY_DISPLAY_NAME, SPECIALTY_ORDER } from "@/constant/config";
const DetailConsultation = () => { const DetailConsultation = () => {
const params = useParams(); const params = useParams();
@@ -37,9 +38,12 @@ const DetailConsultation = () => {
const consultationId = params?.id as string; const consultationId = params?.id as string;
const [openTooltip, setOpenTooltip] = useState(false);
const [prescriptionPage, setPrescriptionPage] = useState<number>(1); const [prescriptionPage, setPrescriptionPage] = useState<number>(1);
const [prescriptionPageSize, setPrescriptionPageSize] = useState<number>(10); const [prescriptionPageSize, setPrescriptionPageSize] = useState<number>(10);
const normalize = (text?: string) => (text ?? "").trim().toLowerCase();
const consulDetailQuery = useQuery<ConsultationDetail>({ const consulDetailQuery = useQuery<ConsultationDetail>({
queryKey: [QUERY_KEY.chi_tiet_phien_kham, consultationId], queryKey: [QUERY_KEY.chi_tiet_phien_kham, consultationId],
@@ -79,15 +83,72 @@ const DetailConsultation = () => {
consultation?.status !== TYPE_STATUS.Completed && consultation?.status !== TYPE_STATUS.Completed &&
consultation?.status !== TYPE_STATUS.Cancelled; consultation?.status !== TYPE_STATUS.Cancelled;
// const specialtyTabs = useMemo(() => {
// return (
// consultation?.specialtyClinics?.map((item) => ({
// pathname: `/consultation/${consultationId}`,
// query: item.id,
// title: item.specialtyName,
// })) || []
// );
// }, [consultation?.specialtyClinics, consultationId]);
const specialtyTabs = useMemo(() => { const specialtyTabs = useMemo(() => {
return ( if (!consultation?.specialtyClinics?.length) return [];
consultation?.specialtyClinics?.map((item) => ({
pathname: `/consultation/${consultationId}`, //------------------------------------
query: item.id, // Chuẩn hóa tên
title: item.specialtyName, //------------------------------------
})) || []
); const specialties = consultation.specialtyClinics.map((item) => ({
...item,
displayName:
SPECIALTY_DISPLAY_NAME[item.specialtyName] ?? item.specialtyName,
}));
//------------------------------------
// Ưu tiên chuyên khoa đã khám
//------------------------------------
specialties.sort((a, b) => {
const aVisited = a.specialtyStatus === 3;
const bVisited = b.specialtyStatus === 3;
// Đã khám lên trước
if (aVisited !== bVisited) {
return aVisited ? -1 : 1;
}
// Sau đó theo SPECIALTY_ORDER
const indexA = SPECIALTY_ORDER.findIndex(
(x) => normalize(x) === normalize(a.displayName),
);
const indexB = SPECIALTY_ORDER.findIndex(
(x) => normalize(x) === normalize(b.displayName),
);
if (indexA === -1 && indexB === -1) {
return a.displayName.localeCompare(b.displayName);
}
if (indexA === -1) return 1;
if (indexB === -1) return -1;
return indexA - indexB;
});
return specialties.map((item) => ({
pathname: `/consultation/${consultationId}`,
query: item.id,
title: item.displayName,
}));
}, [consultation?.specialtyClinics, consultationId]); }, [consultation?.specialtyClinics, consultationId]);
// Tính toán cắt mảng dữ liệu thuốc để hiển thị phân trang trên Client // Tính toán cắt mảng dữ liệu thuốc để hiển thị phân trang trên Client
const paginatedPrescriptionItems = useMemo(() => { const paginatedPrescriptionItems = useMemo(() => {
const items = consultation?.aggregatedPrescriptionItems || []; const items = consultation?.aggregatedPrescriptionItems || [];
@@ -153,18 +214,18 @@ const DetailConsultation = () => {
return ( return (
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div className="flex flex-row gap-4"> <GridColumn col={2}>
<div className="rounded-2xl border border-gray-200 bg-white p-6 shadow-sm"> <div className="rounded-2xl border border-gray-200 bg-white p-6 shadow-sm">
<div className="flex flex-col gap-6"> <div className="flex flex-col gap-6">
{/* HEADER */} {/* HEADER */}
<div className="flex items-center justify-between"> <div className="flex flex-col items-start gap-4 lg:flex-row lg:items-center lg:justify-between lg:gap-0">
<div <div
className="flex cursor-pointer items-center gap-3" className="flex cursor-pointer items-center gap-3"
onClick={() => router.back()} onClick={() => router.back()}
> >
<ArrowLeft className="h-6 w-6" /> <ArrowLeft className="h-6 w-6 shrink-0" />
<h2 className="text-[28px] font-semibold text-[#111827]"> <h2 className="text-[22px] font-semibold text-[#111827] sm:text-[28px]">
Chi tiết phiên khám Chi tiết phiên khám
</h2> </h2>
</div> </div>
@@ -265,7 +326,7 @@ const DetailConsultation = () => {
{/* CHẨN ĐOÁN */} {/* CHẨN ĐOÁN */}
<div className="space-y-1"> <div className="space-y-1">
<p className="text-[14px] font-medium text-[#697586]"> <p className="text-[14px] font-medium text-[#697586]">
CHUẨN ĐOÁN CHẨN ĐOÁN
</p> </p>
<p className="text-[15px] font-medium text-[#202939]"> <p className="text-[15px] font-medium text-[#202939]">
@@ -338,18 +399,24 @@ const DetailConsultation = () => {
</p> </p>
{consultation.generalAttachmentUrls?.length > 0 ? ( {consultation.generalAttachmentUrls?.length > 0 ? (
<TooltipProvider> <TooltipProvider>
<Tooltip> <Tooltip open={openTooltip} onOpenChange={setOpenTooltip}>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<div className="inline-flex cursor-pointer items-center gap-2 rounded-lg border border-blue-100 bg-blue-50 px-3 py-2 text-sm font-medium text-blue-600 transition hover:bg-blue-100"> <div
onClick={() => setOpenTooltip((prev) => !prev)}
className="inline-flex cursor-pointer items-center gap-2 rounded-lg border border-blue-100 bg-blue-50 px-3 py-2 text-sm font-medium text-blue-600 transition hover:bg-blue-100 active:bg-blue-200"
>
<span>📎</span> <span>📎</span>
<span>Chi tiết các file</span> <span>
Chi tiết các file (
{consultation.generalAttachmentUrls.length})
</span>
</div> </div>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent <TooltipContent
side="bottom" side="bottom"
align="start" align="start"
className="w-[350px] rounded-xl border bg-white p-4 shadow-xl" className="w-[calc(100vw-32px)] sm:w-[350px] rounded-xl border bg-white p-4 shadow-xl"
> >
<div className="space-y-3"> <div className="space-y-3">
<h4 className="text-sm font-semibold text-gray-800"> <h4 className="text-sm font-semibold text-gray-800">
@@ -383,10 +450,10 @@ const DetailConsultation = () => {
href={file.path} href={file.path}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="flex items-center justify-between rounded-lg border border-gray-200 p-3 transition hover:bg-gray-50" className="flex items-center justify-between rounded-lg border border-gray-200 p-3 transition hover:bg-gray-50 active:bg-gray-100"
> >
<div className="flex min-w-0 items-center gap-3"> <div className="flex min-w-0 items-center gap-3">
<span className="text-xl"> <span className="text-xl shrink-0">
{isImage ? "🖼️" : "📄"} {isImage ? "🖼️" : "📄"}
</span> </span>
@@ -401,7 +468,7 @@ const DetailConsultation = () => {
</div> </div>
</div> </div>
<span className="text-xs text-blue-500"> <span className="text-xs font-medium text-blue-500 shrink-0 ml-2">
Mở Mở
</span> </span>
</a> </a>
@@ -414,7 +481,9 @@ const DetailConsultation = () => {
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
) : ( ) : (
<div>Không file nào đưc tải lên</div> <div className="text-sm text-gray-500">
Không file nào đưc tải lên
</div>
)} )}
</div> </div>
@@ -546,7 +615,7 @@ const DetailConsultation = () => {
)} )}
</div> </div>
</div> </div>
</div> </GridColumn>
{/* TAB CHUYÊN KHOA */} {/* TAB CHUYÊN KHOA */}
{specialtyTabs.length > 0 && ( {specialtyTabs.length > 0 && (
<div className="mt-4"> <div className="mt-4">
@@ -556,8 +625,8 @@ const DetailConsultation = () => {
{/* CHI TIẾT CHUYÊN KHOA */} {/* CHI TIẾT CHUYÊN KHOA */}
{selectedSpecialty && ( {selectedSpecialty && (
<div className="rounded-2xl border border-gray-200 bg-white p-6 shadow-sm"> <div className="rounded-2xl border border-gray-200 bg-white p-6 shadow-sm">
<div className="flex items-center justify-between"> <div className="flex flex-col items-start mb-6 gap-4 lg:flex-row lg:items-center lg:justify-between lg:gap-0">
<h3 className="mb-6 text-xl font-semibold"> <h3 className=" text-xl font-semibold">
Thông tin chuyên khoa: {selectedSpecialty.specialtyName} Thông tin chuyên khoa: {selectedSpecialty.specialtyName}
</h3> </h3>
{canUpdateConsultation && ( {canUpdateConsultation && (
@@ -611,9 +680,12 @@ const DetailConsultation = () => {
</p> </p>
{selectedSpecialty.attachmentUrls?.length > 0 ? ( {selectedSpecialty.attachmentUrls?.length > 0 ? (
<TooltipProvider> <TooltipProvider>
<Tooltip> <Tooltip open={openTooltip} onOpenChange={setOpenTooltip}>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<div className="inline-flex cursor-pointer items-center gap-2 rounded-lg border border-blue-100 bg-blue-50 px-3 py-2 text-sm font-medium text-blue-600 transition hover:bg-blue-100"> <div
onClick={() => setOpenTooltip((prev) => !prev)}
className="inline-flex cursor-pointer items-center gap-2 rounded-lg border border-blue-100 bg-blue-50 px-3 py-2 text-sm font-medium text-blue-600 transition hover:bg-blue-100 active:bg-blue-200"
>
<span>📎</span> <span>📎</span>
<span>Chi tiết các file</span> <span>Chi tiết các file</span>
</div> </div>
@@ -622,7 +694,7 @@ const DetailConsultation = () => {
<TooltipContent <TooltipContent
side="bottom" side="bottom"
align="start" align="start"
className="w-[350px] rounded-xl border bg-white p-4 shadow-xl" className="w-[calc(100vw-32px)] sm:w-[350px] rounded-xl border bg-white p-4 shadow-xl"
> >
<div className="space-y-3"> <div className="space-y-3">
<h4 className="text-sm font-semibold text-gray-800"> <h4 className="text-sm font-semibold text-gray-800">
@@ -654,10 +726,10 @@ const DetailConsultation = () => {
href={file.path} href={file.path}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="flex items-center justify-between rounded-lg border border-gray-200 p-3 transition hover:bg-gray-50" className="flex items-center justify-between rounded-lg border border-gray-200 p-3 transition hover:bg-gray-50 active:bg-gray-100"
> >
<div className="flex min-w-0 items-center gap-3"> <div className="flex min-w-0 items-center gap-3">
<span className="text-xl"> <span className="text-xl shrink-0">
{isImage ? "🖼️" : "📄"} {isImage ? "🖼️" : "📄"}
</span> </span>
@@ -672,7 +744,7 @@ const DetailConsultation = () => {
</div> </div>
</div> </div>
<span className="text-xs text-blue-500"> <span className="ml-2 text-xs text-blue-500 shrink-0">
Mở Mở
</span> </span>
</a> </a>
@@ -685,7 +757,9 @@ const DetailConsultation = () => {
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
) : ( ) : (
<div>Không file nào đưc tải lên</div> <div className="text-sm text-gray-500">
Không file nào đưc tải lên
</div>
)} )}
</div> </div>
@@ -41,6 +41,7 @@ import * as Popover from "@radix-ui/react-popover";
import clsx from "clsx"; import clsx from "clsx";
import TabNavLink from "@/components/customs/custom-tabs"; import TabNavLink from "@/components/customs/custom-tabs";
import { removeVietnameseTones } from "@/common/funcs/optionConvert"; import { removeVietnameseTones } from "@/common/funcs/optionConvert";
import useSpecialtyTabs from "@/hooks/useSpecialtyTabs";
const MainPageConsultation = () => { const MainPageConsultation = () => {
const router = useRouter(); const router = useRouter();
@@ -98,6 +99,10 @@ const MainPageConsultation = () => {
}, },
}); });
const { tabs: specialtyTabs } = useSpecialtyTabs(
consultationQuery.data?.items ?? [],
);
const specialtyQuery = useQuery({ const specialtyQuery = useQuery({
queryKey: [QUERY_KEY.list_specialty_lookup], queryKey: [QUERY_KEY.list_specialty_lookup],
queryFn: async () => { queryFn: async () => {
@@ -183,30 +188,30 @@ const MainPageConsultation = () => {
const consultation = consultationQuery.data; const consultation = consultationQuery.data;
const specialtyTabs = useMemo(() => { // const specialtyTabs = useMemo(() => {
const specialtyMap = new Map(); // const specialtyMap = new Map();
consultation?.items?.forEach((consultationItem) => { // consultation?.items?.forEach((consultationItem) => {
consultationItem.specialtyClinics?.forEach((specialty) => { // consultationItem.specialtyClinics?.forEach((specialty) => {
if (!specialtyMap.has(specialty.specialtyId)) { // if (!specialtyMap.has(specialty.specialtyId)) {
specialtyMap.set(specialty.specialtyId, { // specialtyMap.set(specialty.specialtyId, {
pathname: "/consultation", // pathname: "/consultation",
query: specialty.specialtyId, // query: specialty.specialtyId,
title: specialty.specialtyName, // title: specialty.specialtyName,
}); // });
} // }
}); // });
}); // });
return [ // return [
{ // {
pathname: "/consultation", // pathname: "/consultation",
query: "all", // query: "all",
title: "Tất cả", // title: "Tất cả",
}, // },
...Array.from(specialtyMap.values()), // ...Array.from(specialtyMap.values()),
]; // ];
}, [consultation?.items]); // }, [consultation?.items]);
const handleOpenCancel = (item: ConsultationItem) => { const handleOpenCancel = (item: ConsultationItem) => {
setSelectedConsultationId(item.id); setSelectedConsultationId(item.id);
@@ -381,7 +386,6 @@ const MainPageConsultation = () => {
column={[ column={[
{ {
title: "TÊN BỆNH NHÂN", title: "TÊN BỆNH NHÂN",
render: (item: ConsultationItem) => ( render: (item: ConsultationItem) => (
<TooltipProvider> <TooltipProvider>
<Tooltip> <Tooltip>
@@ -393,9 +397,8 @@ const MainPageConsultation = () => {
{item.patientName} {item.patientName}
</Link> </Link>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
<p className="p-2 bg-gray-400 border rounded-full"> <p className="rounded-full border bg-gray-400 p-2">
Chi tiết bệnh nhân Chi tiết bệnh nhân
</p> </p>
</TooltipContent> </TooltipContent>
@@ -427,7 +430,6 @@ const MainPageConsultation = () => {
{item.sessionCode} {item.sessionCode}
</Link> </Link>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
<p className="rounded-full border bg-gray-400 p-2"> <p className="rounded-full border bg-gray-400 p-2">
Chi tiết phiên khám Chi tiết phiên khám
@@ -450,41 +452,72 @@ const MainPageConsultation = () => {
), ),
}, },
{ // 🔴 ẨN "DẤU HIỆU" khi KHÔNG PHẢI Tab All (!isAllTab)
isAllTab && {
title: "DẤU HIỆU", title: "DẤU HIỆU",
render: (item: ConsultationItem) => ( render: (item: ConsultationItem) => (
<span>{item.symptomsText || "---"}</span> <span>{item.symptomsText || "---"}</span>
), ),
}, },
{ {
title: "CHUẨN ĐOÁN", title: "CHẨN ĐOÁN",
render: (item: ConsultationItem) => {
render: (item: ConsultationItem) => ( if (!isAllTab) {
<span>{item.diagnosis || "---"}</span> const specialty = item.specialtyClinics.find(
), (x) => x.specialtyId === _type,
);
return <span>{specialty?.diagnosis || "---"}</span>;
}
return <span>{item.diagnosis || "---"}</span>;
},
}, },
{ // 🔴 ẨN "DẤU HIỆU" khi KHÔNG PHẢI Tab All (!isAllTab)
title: "KẾ HOẠCH ĐIỀU TRỊ", // !isAllTab && {
// title: "BÁC SĨ KHÁM",
// render: (item: ConsultationItem) => {
// const doctorNames = item.doctors?.join(", ");
// return <span>{doctorNames || "---"}</span>;
// },
// },
// 🔴 ẨN "KẾ HOẠCH ĐIỀU TRỊ" khi KHÔNG PHẢI Tab All (!isAllTab)
isAllTab && {
title: "KẾ HOẠCH ĐIỀU TRỊ",
render: (item: ConsultationItem) => ( render: (item: ConsultationItem) => (
<span>{item.treatmentPlan || "---"}</span> <span>{item.treatmentPlan || "---"}</span>
), ),
}, },
{ {
title: "GHI CHÚ BÁC SĨ", title: isAllTab ? "GHI CHÚ BÁC SĨ" : "GHI CHÚ KHÁM",
render: (item: ConsultationItem) => {
render: (item: ConsultationItem) => ( if (!isAllTab) {
<span>{item.doctorNotes || "---"}</span> const specialty = item.specialtyClinics.find(
), (x) => x.specialtyId === _type,
);
return <span>{specialty?.specialtyNotes || "---"}</span>;
}
return <span>{item.doctorNotes || "---"}</span>;
},
}, },
// 🔴 ẨN "THỦ THUẬT" khi LÀ Tab All (isAllTab)
!isAllTab && {
title: "THỦ THUẬT",
render: (item: ConsultationItem) => {
const specialty = item.specialtyClinics.find(
(x) => x.specialtyId === _type,
);
return <span>{specialty?.procedureNotes || "---"}</span>;
},
},
{ {
title: "TRẠNG THÁI", title: "TRẠNG THÁI",
render: (item: ConsultationItem) => { render: (item: ConsultationItem) => {
// ===== TAB CHUYÊN KHOA =====
if (!isAllTab) { if (!isAllTab) {
const specialty = item.specialtyClinics.find( const specialty = item.specialtyClinics.find(
(x) => x.specialtyId === _type, (x) => x.specialtyId === _type,
@@ -517,7 +550,6 @@ const MainPageConsultation = () => {
); );
} }
// ===== TAB TẤT CẢ =====
return ( return (
<StateActive <StateActive
stateActive={item.status} stateActive={item.status}
@@ -563,19 +595,15 @@ const MainPageConsultation = () => {
); );
}, },
}, },
{ {
fixedRight: true, fixedRight: true,
title: "ACTION", title: "ACTION",
render: (item: ConsultationItem) => { render: (item: ConsultationItem) => {
const isCancelled = item.status === TYPE_STATUS.Cancelled; const isCancelled = item.status === TYPE_STATUS.Cancelled;
const isCompleted = item.status === TYPE_STATUS.Completed; const isCompleted = item.status === TYPE_STATUS.Completed;
const isDisabled = isCancelled || isCompleted; const isDisabled = isCancelled || isCompleted;
// ===== TAB CHUYÊN KHOA =====
if (!isAllTab) { if (!isAllTab) {
const specialty = item.specialtyClinics.find( const specialty = item.specialtyClinics.find(
(x) => x.specialtyId === _type, (x) => x.specialtyId === _type,
@@ -599,14 +627,7 @@ const MainPageConsultation = () => {
); );
} }
// ===== TAB TẤT CẢ =====
return ( return (
/*
Chỉnh sửa tại đây:
- flex-col: xếp dọc trên màn mobile
- sm:flex-row: quay lại xếp ngang trên tablet / desktop (>= 640px)
- w-full sm:w-auto: mở rộng full chiều rộng ô khi ở màn mobile cho cân đối
*/
<div className="flex flex-col sm:flex-row items-center justify-center gap-2 w-full sm:w-auto"> <div className="flex flex-col sm:flex-row items-center justify-center gap-2 w-full sm:w-auto">
<Popover.Root> <Popover.Root>
<Popover.Trigger asChild> <Popover.Trigger asChild>
@@ -628,27 +649,14 @@ const MainPageConsultation = () => {
side="bottom" side="bottom"
align="end" align="end"
sideOffset={8} sideOffset={8}
className=" className="z-50 min-w-[300px] rounded-xl border bg-white p-2 shadow-xl"
z-50
min-w-[300px]
rounded-xl
border
bg-white
p-2
shadow-xl
"
> >
<div className="space-y-1"> <div className="space-y-1">
{item.specialtyClinics.map((specialty) => ( {item.specialtyClinics.map((specialty) => (
<Link <Link
key={specialty.id} key={specialty.id}
href={`/consultation/update-specialty?_id=${item.id}&specialtyClinicId=${specialty.id}`} href={`/consultation/update-specialty?_id=${item.id}&specialtyClinicId=${specialty.id}`}
className=" className="flex items-center gap-2 rounded-lg px-3 py-2 text-sm hover:bg-blue-50"
flex items-center gap-2
rounded-lg px-3 py-2
text-sm
hover:bg-blue-50
"
> >
<Pencil size={15} /> <Pencil size={15} />
<span>{specialty.specialtyName}</span> <span>{specialty.specialtyName}</span>
@@ -682,7 +690,7 @@ const MainPageConsultation = () => {
); );
}, },
}, },
]} ].filter(Boolean)} // 💡 Lọc bỏ các giá trị false để ẩn cột
/> />
</DataWrapper> </DataWrapper>
<Pagination <Pagination
@@ -150,6 +150,47 @@ const TableHistoryPatient = () => {
<span>{item.diagnosis || "---"}</span> <span>{item.diagnosis || "---"}</span>
), ),
}, },
{
title: "KẾ HOẠCH ĐIỀU TRỊ",
render: (item: ConsultationItem) => (
<span>{item.treatmentPlan || "---"}</span>
),
},
{
title: "CÁC CHUYÊN KHOA ĐANG CHỜ KHÁM",
render: (item: ConsultationItem) => {
const waitingSpecialties = item.specialtyClinics
?.filter((specialty) => specialty.specialtyStatus === 1)
.map((specialty) => specialty.specialtyName)
.join(", ");
return <span>{waitingSpecialties || "---"}</span>;
},
},
{
title: "CÁC CHUYÊN KHOA ĐÃ KHÁM",
render: (item: ConsultationItem) => {
const completedSpecialties = item.specialtyClinics
?.filter((specialty) => specialty.specialtyStatus === 3)
.map((specialty) => specialty.specialtyName)
.join(", ");
return <span>{completedSpecialties || "---"}</span>;
},
},
{
title: "BÁC SĨ KHÁM",
render: (item: ConsultationItem) => {
const doctorNames = item.doctors?.join(", ");
return <span>{doctorNames || "---"}</span>;
},
},
{ {
title: "TRẠNG THÁI", title: "TRẠNG THÁI",
render: (item: ConsultationItem) => ( render: (item: ConsultationItem) => (
@@ -324,7 +324,7 @@ const DetailPatient = () => {
}, },
{ {
title: "CHUẨN ĐOÁN", title: "CHẨN ĐOÁN",
render: (item: ConsultationItem) => ( render: (item: ConsultationItem) => (
<span>{item.diagnosis || "---"}</span> <span>{item.diagnosis || "---"}</span>
@@ -339,13 +339,6 @@ const DetailPatient = () => {
), ),
}, },
{
title: "GHI CHÚ BÁC SĨ",
render: (item: ConsultationItem) => (
<span>{item.doctorNotes || "---"}</span>
),
},
{ {
title: "CÁC CHUYÊN KHOA ĐANG CHỜ KHÁM", title: "CÁC CHUYÊN KHOA ĐANG CHỜ KHÁM",
@@ -370,6 +363,15 @@ const DetailPatient = () => {
return <span>{completedSpecialties || "---"}</span>; return <span>{completedSpecialties || "---"}</span>;
}, },
}, },
{
title: "BÁC SĨ KHÁM",
render: (item: ConsultationItem) => {
const doctorNames = item.doctors?.join(", ");
return <span>{doctorNames || "---"}</span>;
},
},
{ {
title: "TRẠNG THÁI", title: "TRẠNG THÁI",
fixedRight: true, fixedRight: true,
@@ -509,7 +509,7 @@ const CreatePrescription = () => {
<InputForm <InputForm
label={ label={
<span> <span>
Thời gian dùng<span style={{ color: "red" }}>*</span> Số lượng thuốc<span style={{ color: "red" }}>*</span>
</span> </span>
} }
isRequired isRequired
@@ -14,6 +14,7 @@ import { useParams, useRouter } from "next/navigation";
import React, { useState } from "react"; import React, { useState } from "react";
import CustomPopup from "@/components/customs/custom-popup"; import CustomPopup from "@/components/customs/custom-popup";
import { ArrowLeft } from "lucide-react"; import { ArrowLeft } from "lucide-react";
import Link from "next/link";
const DetailPrescription = () => { const DetailPrescription = () => {
const router = useRouter(); const router = useRouter();
@@ -104,16 +105,25 @@ const DetailPrescription = () => {
<div> <div>
<strong> đơn thuốc:</strong> {data.prescriptionCode} <strong> đơn thuốc:</strong> {data.prescriptionCode}
</div> </div>
<div>
<strong> bệnh nhân:</strong>
<Link
href={`/patient/${data.patientId}`}
className="text-blue-500 hover:underline"
>
{data.patientCode || "---"}
</Link>
</div>{" "}
<div>
<strong>Tên bệnh nhân:</strong> {data.patientName || "---"}
</div>
<div> <div>
<strong>Chuyên khoa:</strong>{" "} <strong>Chuyên khoa:</strong>{" "}
{specialtyName || data.examinationSessionSpecialtyId || "---"} {specialtyName || data.examinationSessionSpecialtyId || "---"}
</div> </div>
<div> <div>
<strong>Ghi chú:</strong> {data.notes || "---"} <strong>Ghi chú:</strong> {data.notes || "---"}
</div> </div>
{/* IMAGE */} {/* IMAGE */}
<div> <div>
<strong>nh đơn thuốc:</strong> <strong>nh đơn thuốc:</strong>
@@ -152,7 +162,6 @@ const DetailPrescription = () => {
)} )}
</div> </div>
</div> </div>
{/* ITEMS */} {/* ITEMS */}
<div> <div>
<strong>Danh sách thuốc:</strong> <strong>Danh sách thuốc:</strong>
@@ -171,7 +180,7 @@ const DetailPrescription = () => {
<b>Tần suất:</b> {item.frequency} <b>Tần suất:</b> {item.frequency}
</p> </p>
<p> <p>
<b>Thời gian:</b> {item.duration} <b>Số lượng thuốc:</b> {item.quantity}
</p> </p>
<p> <p>
<b>Hướng dẫn:</b> {item.instructions} <b>Hướng dẫn:</b> {item.instructions}
@@ -148,12 +148,37 @@ const MainPrescription = () => {
), ),
}, },
// ================= NOTES =================
{ {
title: "GHI CHÚ", title: "MÃ BỆNH NHÂN",
render: (item: any) => <span>{item.notes || "-"}</span>, render: (item: any) => (
}, <TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Link
href={`/patient/${item.patientId}`}
className="text-blue-500 hover:underline"
>
{item.patientCode}
</Link>
</TooltipTrigger>
<TooltipContent>
<p className="p-2 bg-gray-400 border rounded-full">
Chi tiết bệnh nhân
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
),
},
{
title: "TÊN BỆNH NHÂN",
render: (item: any) => <span>{item.patientName || "-"}</span>,
},
{
title: "TÊN CHUYÊN KHOA",
render: (item: any) => <span>{item.specialtyName || "-"}</span>,
},
// ================= IMAGE ================= // ================= IMAGE =================
{ {
title: "ẢNH ĐƠN THUỐC", title: "ẢNH ĐƠN THUỐC",
@@ -174,11 +199,15 @@ const MainPrescription = () => {
}, },
// ================= ITEMS COUNT ================= // ================= ITEMS COUNT =================
// {
// title: "SỐ THUỐC",
// render: (item: any) => <span>{item.items?.length || 0}</span>,
// },
// ================= NOTES =================
{ {
title: "SỐ THUỐC", title: "GHI CHÚ",
render: (item: any) => <span>{item.items?.length || 0}</span>, render: (item: any) => <span>{item.notes || "-"}</span>,
}, },
// ================= ACTION ================= // ================= ACTION =================
{ {
fixedRight: true, fixedRight: true,
+43
View File
@@ -113,4 +113,47 @@ export const ListOptionFilterDate: {
}, },
]; ];
export const SPECIALTY_ORDER = [
"Khám Nội",
"Khám Răng",
"Khám Tai Mũi Họng",
"Khám Mắt",
"Khám Sản phụ khoa",
"Xét nghiệm máu",
"Xét nghiệm nước tiểu",
"X-quang",
"Điện tim",
"Siêu Âm",
];
export const SPECIALTY_DISPLAY_NAME: Record<string, string> = {
Nội: "Khám Nội",
"Khám Nội": "Khám Nội",
Răng: "Khám Răng",
"Khám Răng": "Khám Răng",
"Tai Mũi Họng": "Khám Tai Mũi Họng",
"Khám Tai Mũi Họng": "Khám Tai Mũi Họng",
Mắt: "Khám Mắt",
"Khám Mắt": "Khám Mắt",
"Sản Phụ Khoa": "Khám Sản phụ khoa",
"Sản phụ khoa": "Khám Sản phụ khoa",
"Khám Sản phụ khoa": "Khám Sản phụ khoa",
"Xét nghiệm máu": "Xét nghiệm máu",
"Xét nghiệm nước tiểu": "Xét nghiệm nước tiểu",
"X-Quang": "X-quang",
"X-quang": "X-quang",
"Điện Tim": "Điện tim",
"Điện tim": "Điện tim",
"Siêu Âm": "Siêu Âm",
};
export const KEY_STORE = "management-kham-benh"; export const KEY_STORE = "management-kham-benh";
+121
View File
@@ -0,0 +1,121 @@
import { useMemo } from "react";
import { useQuery } from "@tanstack/react-query";
import { QUERY_KEY } from "@/constant/config/enum";
import { httpRequest } from "@/services";
import specialtyServices, { SpecialtyItem } from "@/services/specialtyServices";
import { SPECIALTY_ORDER, SPECIALTY_DISPLAY_NAME } from "@/constant/config";
interface ConsultationSpecialty {
specialtyClinics?: {
specialtyId: string;
specialtyName: string;
}[];
}
const normalize = (text: string) => text.trim().toLowerCase();
export default function useSpecialtyTabs(
consultationItems: ConsultationSpecialty[] = [],
) {
const specialtyQuery = useQuery({
queryKey: [QUERY_KEY.list_specialty_lookup],
queryFn: async () => {
const res = await httpRequest<SpecialtyItem[]>({
http: specialtyServices.getSpecialty(),
showMessageFailed: true,
});
return res || [];
},
});
const tabs = useMemo(() => {
const lookup = [...(specialtyQuery.data ?? [])];
//-----------------------------------------
// Các chuyên khoa đã xuất hiện trong phiên khám
//-----------------------------------------
const existed = new Set<string>();
consultationItems.forEach((item) => {
item.specialtyClinics?.forEach((specialty) => {
const displayName =
SPECIALTY_DISPLAY_NAME[specialty.specialtyName] ??
specialty.specialtyName;
existed.add(displayName);
});
});
//-----------------------------------------
// Chuẩn hóa tên
//-----------------------------------------
const specialties = lookup.map((item) => ({
...item,
displayName: SPECIALTY_DISPLAY_NAME[item.name] ?? item.name,
}));
//-----------------------------------------
// Sort
//-----------------------------------------
specialties.sort((a, b) => {
const aVisited = existed.has(a.displayName);
const bVisited = existed.has(b.displayName);
//---------------------------------
// Ưu tiên chuyên khoa đã khám
//---------------------------------
if (aVisited !== bVisited) {
return aVisited ? -1 : 1;
}
//---------------------------------
// Sau đó theo SPECIALTY_ORDER
//---------------------------------
const orderA = SPECIALTY_ORDER.findIndex(
(x) => normalize(x) === normalize(a.displayName),
);
const orderB = SPECIALTY_ORDER.findIndex(
(x) => normalize(x) === normalize(b.displayName),
);
if (orderA === -1 && orderB === -1) {
return a.displayName.localeCompare(b.displayName);
}
if (orderA === -1) return 1;
if (orderB === -1) return -1;
return orderA - orderB;
});
return [
{
pathname: "/consultation",
query: "all",
title: "Tất cả",
},
...specialties.map((item) => ({
pathname: "/consultation",
query: item.id,
title: item.displayName,
})),
];
}, [specialtyQuery.data, consultationItems]);
return {
tabs,
loading: specialtyQuery.isLoading,
};
}
+1
View File
@@ -45,6 +45,7 @@ export interface ConsultationItem {
instructions: string; instructions: string;
}[]; }[];
}[]; }[];
doctors: string[] | null;
aggregatedPrescriptionItems: { aggregatedPrescriptionItems: {
id: string; id: string;
prescriptionId: string; prescriptionId: string;
+4 -1
View File
@@ -33,13 +33,16 @@ export interface PrescriptionDetail {
prescriptionImg: string; prescriptionImg: string;
notes: string; notes: string;
createdBy: string; createdBy: string;
patientId: string;
patientCode: string;
patientName: string;
items: { items: {
id: string; id: string;
prescriptionId: string; prescriptionId: string;
medicineName: string; medicineName: string;
dosage: string; dosage: string;
frequency: string; frequency: string;
duration: string; quantity: string;
instructions: string; instructions: string;
}[]; }[];
} }