diff --git a/next.config.ts b/next.config.ts index 078e9e1..8f2b429 100644 --- a/next.config.ts +++ b/next.config.ts @@ -4,6 +4,16 @@ const nextConfig: NextConfig = { turbopack: { root: process.cwd(), }, + images: { + remotePatterns: [ + { + protocol: "http", + hostname: "127.0.0.1", + port: "5264", + pathname: "/uploads/**", + }, + ], + }, }; export default nextConfig; diff --git a/src/app/consultation/history/page.tsx b/src/app/consultation/history/page.tsx new file mode 100644 index 0000000..0f10967 --- /dev/null +++ b/src/app/consultation/history/page.tsx @@ -0,0 +1,14 @@ +"use client"; +import BaseLayout from "@/components/layouts/BaseLayout"; +import TableHistoryPatient from "@/components/page/consultation/TableHistoryPatient"; +import React from "react"; + +const page = () => { + return ( + + + + ); +}; + +export default page; diff --git a/src/common/funcs/optionConvert.ts b/src/common/funcs/optionConvert.ts index 0ad0a05..68f54ac 100644 --- a/src/common/funcs/optionConvert.ts +++ b/src/common/funcs/optionConvert.ts @@ -63,6 +63,19 @@ export default function fancyTimeFormat(duration: number) { return ret; } +export function checkTime(i: any) { + if (Math.abs(i) < 10) { + i = "0" + i; + } + return i; +} + +export const timeSubmit = (date: Date | null | undefined, isTo?: boolean) => { + return date + ? `${date.getFullYear()}-${checkTime(date.getMonth() + 1)}-${checkTime(date.getDate())}T${isTo ? "23:59:59" : "00:00:00"}` + : null; +}; + export function getKeyCert(): { time: string; keyCert: string; diff --git a/src/components/layouts/BaseLayout/BaseLayout.tsx b/src/components/layouts/BaseLayout/BaseLayout.tsx index 7b0c758..d9c7e6a 100644 --- a/src/components/layouts/BaseLayout/BaseLayout.tsx +++ b/src/components/layouts/BaseLayout/BaseLayout.tsx @@ -11,6 +11,16 @@ export const ContextBaseLayout = React.createContext({}); const BaseLayout = ({ children, title, breadcrumb }: PropsBaseLayout) => { const [showFull, setShowFull] = React.useState(false); const [openMenuMobile, setOpenMenuMobile] = React.useState(false); + // Khi mount trên client, mở sidebar mặc định nếu là màn hình desktop (xl) + // React.useEffect(() => { + // try { + // if (window?.innerWidth >= 1280) { + // setShowFull(true); + // } + // } catch (e) { + // // ignore + // } + // }, []); return ( { return ( {/* PROFILE */} - { Chi tiết tài khoản - + */} {/* CHANGE PASSWORD */} - { Thay đổi mật khẩu - + */} {/* LOGOUT */} { const router = useRouter(); @@ -111,6 +112,9 @@ const CreateConsultation = () => { }); const handleSubmit = () => { + if (!form?.patientId) { + return toastWarn({ msg: "Vui lòng chọn bệnh nhân" }); + } createConsultationMutation.mutate(); }; @@ -118,14 +122,14 @@ const CreateConsultation = () => { @@ -139,56 +143,13 @@ const CreateConsultation = () => { " > - Thêm phiên khám + Tạo phiên khám - - router.back()} - > - Hủy bỏ - - - - {createConsultationMutation.isPending - ? "Đang tạo..." - : "Tạo phiên khám"} - - {/* BODY */} - + { getOptionValue={(item: PatientItem) => item.id} /> - {/* - - - - - - - - - - - */} {/* FOOTER */} + + router.back()} + > + Hủy bỏ + + + + {createConsultationMutation.isPending + ? "Đang tạo..." + : "Tạo phiên khám"} + + ); diff --git a/src/components/page/consultation/MainPageConsultation/MainPageConsultation.tsx b/src/components/page/consultation/MainPageConsultation/MainPageConsultation.tsx index 615d3cb..05ccd4a 100644 --- a/src/components/page/consultation/MainPageConsultation/MainPageConsultation.tsx +++ b/src/components/page/consultation/MainPageConsultation/MainPageConsultation.tsx @@ -272,20 +272,6 @@ const MainPageConsultation = () => { rowKey={(item) => item.id} data={consultationData} column={[ - { - fixedLeft: true, - - title: "ID PHIÊN KHÁM", - - render: (item: ConsultationItem) => ( - - {item.id} - - ), - }, { title: "TÊN BỆNH NHÂN", @@ -296,9 +282,14 @@ const MainPageConsultation = () => { { title: "MÃ PHIÊN KHÁM", - + fixedLeft: true, render: (item: ConsultationItem) => ( - {item.sessionCode} + + {item.sessionCode} + ), }, diff --git a/src/components/page/consultation/TableHistoryPatient/TableHistoryPatient.tsx b/src/components/page/consultation/TableHistoryPatient/TableHistoryPatient.tsx new file mode 100644 index 0000000..12d1bec --- /dev/null +++ b/src/components/page/consultation/TableHistoryPatient/TableHistoryPatient.tsx @@ -0,0 +1,196 @@ +"use client"; + +import React from "react"; +import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { useQuery } from "@tanstack/react-query"; +import Link from "next/link"; + +import consultationServices, { + ConsultationItem, + ConsultationResponse, +} from "@/services/consultationServices"; +import { httpRequest } from "@/services"; + +import { QUERY_KEY, TYPE_STATUS } from "@/constant/config/enum"; + +import DataWrapper from "@/components/customs/DataWrapper"; +import Table from "@/components/customs/custom-table"; +import StateActive from "@/components/customs/StateActive"; +import Pagination from "@/components/customs/custom-pagination"; +import Search from "@/components/customs/custom-search"; + +const TableHistoryPatient = () => { + const searchParams = useSearchParams(); + const router = useRouter(); + const pathname = usePathname(); + const patientId = searchParams.get("_id"); + + const consultationQuery = useQuery({ + queryKey: [QUERY_KEY.table_list_consultation, patientId, "full-history"], + enabled: !!patientId, + queryFn: async () => { + const res = await httpRequest({ + showMessageFailed: true, + http: consultationServices.getConsultations({ + Page: 1, + PageSize: 1000, + PatientId: patientId || "", + }), + }); + + return ( + res || { + items: [], + page: 1, + pageSize: 10, + total: 0, + totalPages: 0, + } + ); + }, + }); + + const _page = searchParams.get("_page"); + + const _pageSize = searchParams.get("_pageSize"); + + const _keyword = searchParams.get("_keyword"); + + const page = _page ? Number(_page) : 1; + const pageSize = _pageSize ? Number(_pageSize) : 10; + + const updateQuery = (key: string, value: string | number) => { + const params = new URLSearchParams(searchParams.toString()); + + if (!value || value === "" || (key === "_page" && value === 1)) { + params.delete(key); + } else { + params.set(key, String(value)); + } + + if (key === "_pageSize") { + params.delete("_page"); + } + + const queryString = params.toString(); + + router.push(queryString ? `?${queryString}` : pathname); + }; + + const data = consultationQuery.data?.items || []; + + // lấy tên bệnh nhân từ record đầu tiên (nếu backend có trả) + const patientName = data?.[0]?.patientName || "Bệnh nhân"; + + return ( + + + + Lịch sử phiên khám của {patientName} + + + router.back()} + className="text-blue-500 hover:underline" + > + ← Quay lại + + + + + updateQuery("_keyword", value)} + placeholder="Tìm kiếm phiên khám..." + /> + + + + + item.id} + data={data} + column={[ + { + title: "MÃ PHIÊN KHÁM", + render: (item: ConsultationItem) => ( + + {item.sessionCode} + + ), + }, + { + title: "NGÀY KHÁM", + render: (item: ConsultationItem) => {item.visitDate}, + }, + { + title: "DẤU HIỆU", + render: (item: ConsultationItem) => ( + {item.symptomsText || "---"} + ), + }, + { + title: "CHẨN ĐOÁN", + render: (item: ConsultationItem) => ( + {item.diagnosis || "---"} + ), + }, + { + title: "TRẠNG THÁI", + render: (item: ConsultationItem) => ( + + ), + }, + ]} + /> + + + updateQuery("_page", value)} + onSetPageSize={(value) => updateQuery("_pageSize", value)} + dependencies={[_pageSize, _keyword]} + /> + + ); +}; + +export default TableHistoryPatient; diff --git a/src/components/page/consultation/TableHistoryPatient/index.ts b/src/components/page/consultation/TableHistoryPatient/index.ts new file mode 100644 index 0000000..2b62655 --- /dev/null +++ b/src/components/page/consultation/TableHistoryPatient/index.ts @@ -0,0 +1 @@ +export { default } from "./TableHistoryPatient"; diff --git a/src/components/page/consultation/UpdateConsultation/UpdateConsultation.tsx b/src/components/page/consultation/UpdateConsultation/UpdateConsultation.tsx index a667205..cdf1620 100644 --- a/src/components/page/consultation/UpdateConsultation/UpdateConsultation.tsx +++ b/src/components/page/consultation/UpdateConsultation/UpdateConsultation.tsx @@ -174,34 +174,12 @@ const UpdateConsultation = () => { - Chỉnh sửa phiên khám + Phiên khám Cập nhật thông tin chi tiết quá trình khám bệnh - - router.back()} - className="min-w-[120px]" - > - Hủy bỏ - - - {updateConsultationMutation.isPending - ? "Đang cập nhật..." - : "Cập nhật"} - - {/* GENERAL INFO (PATIENT) */} @@ -251,9 +229,9 @@ const UpdateConsultation = () => { @@ -297,6 +275,28 @@ const UpdateConsultation = () => { } /> + + router.back()} + className="min-w-[120px]" + > + Hủy bỏ + + + {updateConsultationMutation.isPending + ? "Đang cập nhật..." + : "Cập nhật"} + + ); diff --git a/src/components/page/patient/DetailPatient/DetailPatient.tsx b/src/components/page/patient/DetailPatient/DetailPatient.tsx index a2f4c31..b760a01 100644 --- a/src/components/page/patient/DetailPatient/DetailPatient.tsx +++ b/src/components/page/patient/DetailPatient/DetailPatient.tsx @@ -119,9 +119,8 @@ const DetailPatient = () => { queryKey: [QUERY_KEY.table_list_consultation, patientId], }); - // Nếu API trả về thông tin phiên khám vừa tạo (có chứa id), chuyển thẳng tới trang update if (res?.id) { - router.push(`${PATH.CONSULTATION}/update/${res.id}`); + router.push(`${PATH.CONSULTATION}/${res.id}`); // detail } }, }); @@ -293,7 +292,7 @@ const DetailPatient = () => { Lịch sử phiên khám Tất cả @@ -310,23 +309,17 @@ const DetailPatient = () => { data={consultationData} column={[ { + title: "MÃ PHIÊN KHÁM", fixedLeft: true, - title: "ID PHIÊN KHÁM", render: (item: ConsultationItem) => ( - {item.id} + {item.sessionCode} ), }, - { - title: "MÃ PHIÊN KHÁM", - render: (item: ConsultationItem) => ( - {item.sessionCode} - ), - }, { title: "NGÀY KHÁM", render: (item: ConsultationItem) => ( diff --git a/src/components/page/patient/PopupCreatePatient/PopupCreatePatient.tsx b/src/components/page/patient/PopupCreatePatient/PopupCreatePatient.tsx index 09cf363..0c594a2 100644 --- a/src/components/page/patient/PopupCreatePatient/PopupCreatePatient.tsx +++ b/src/components/page/patient/PopupCreatePatient/PopupCreatePatient.tsx @@ -14,6 +14,8 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import { httpRequest } from "@/services"; import patientServices from "@/services/patientServices"; +import { toastWarn } from "@/common/funcs/toast"; +import { timeSubmit } from "@/common/funcs/optionConvert"; export interface ICreatePatient { identificationNumber: string; @@ -77,6 +79,16 @@ const PopupCreatePatient = ({ onClose }: PropsPopupCreatePatient) => { }); const handleSubmit = () => { + if (!form.gender) { + return toastWarn({ msg: "Vui lòng chọn giới tính!" }); + } + const today = new Date(timeSubmit(new Date())!); + const birthDay = new Date(form.dateOfBirth); + + if (today < birthDay) { + return toastWarn({ msg: "Ngày sinh không hợp lệ!" }); + } + createPatientMutation.mutate(); }; @@ -241,29 +253,31 @@ const PopupCreatePatient = ({ onClose }: PropsPopupCreatePatient) => { } placeholder="Số điện thoại" name="phone" - type="text" + type="phone" + isPhone value={form.phone} isRequired /> - Email - * - - } + label={Email} placeholder="Email" name="email" - type="text" + type="email" + isEmail value={form.email} - isRequired /> + Địa chỉ + * + + } + isRequired isBlur max={5000} /> diff --git a/src/components/page/patient/PopupUpdatePatient/PopupUpdatePatient.tsx b/src/components/page/patient/PopupUpdatePatient/PopupUpdatePatient.tsx index 2fe88f7..2a9b00c 100644 --- a/src/components/page/patient/PopupUpdatePatient/PopupUpdatePatient.tsx +++ b/src/components/page/patient/PopupUpdatePatient/PopupUpdatePatient.tsx @@ -12,6 +12,8 @@ import { ContextFormCustom } from "@/components/utils/FormCustom/contexts"; import { QUERY_KEY, TYPE_GENDER } from "@/constant/config/enum"; import { httpRequest } from "@/services"; import patientServices, { PatientDetail } from "@/services/patientServices"; +import { toastWarn } from "@/common/funcs/toast"; +import { timeSubmit } from "@/common/funcs/optionConvert"; interface PopupUpdatePatientProps { onClose: () => void; id: string; @@ -147,6 +149,10 @@ const PopupUpdatePatient = ({ onClose, id }: PopupUpdatePatientProps) => { }); const handleSubmit = () => { + if (!form.gender) { + return toastWarn({ msg: "Vui lòng chọn giới tính!" }); + } + updatePatientMutation.mutate(); }; @@ -268,23 +274,6 @@ const PopupUpdatePatient = ({ onClose, id }: PopupUpdatePatientProps) => { Nữ - - {/* KHÁC */} - - - setForm((prev) => ({ - ...prev, - gender: TYPE_GENDER.OTHER, - })) - } - /> - - Khác - @@ -297,23 +286,19 @@ const PopupUpdatePatient = ({ onClose, id }: PopupUpdatePatientProps) => { } placeholder="Số điện thoại" name="phone" - type="text" + type="number" + isPhone value={form.phone} isRequired /> - Email - * - - } + label={Email} placeholder="Email" name="email" - type="text" + type="email" + isEmail value={form.email} - isRequired /> { placeholder="Địa chỉ" label="Địa chỉ" value={form.address} + isRequired isBlur max={5000} /> { label="SĐT khẩn cấp" placeholder="SĐT khẩn cấp" name="emergencyContactPhone" - type="text" + type="number" + isRequired + isPhone value={form.emergencyContactPhone} /> @@ -364,6 +353,7 @@ const PopupUpdatePatient = ({ onClose, id }: PopupUpdatePatientProps) => { placeholder="Số bảo hiểm" name="insuranceNumber" type="text" + isRequired value={form.insuranceNumber} /> diff --git a/src/components/page/prescription/CreatePrescription/CreatePrescription.tsx b/src/components/page/prescription/CreatePrescription/CreatePrescription.tsx index f23483c..bd7388f 100644 --- a/src/components/page/prescription/CreatePrescription/CreatePrescription.tsx +++ b/src/components/page/prescription/CreatePrescription/CreatePrescription.tsx @@ -29,7 +29,7 @@ import consultationServices, { ConsultationResponse, } from "@/services/consultationServices"; -import { QUERY_KEY } from "@/constant/config/enum"; +import { QUERY_KEY, TYPE_STATUS } from "@/constant/config/enum"; import { toastWarn } from "@/common/funcs/toast"; @@ -111,7 +111,12 @@ const CreatePrescription = () => { }, }); - const examinationSessionOptions = examinationSessionQuery.data?.items || []; + const examinationSessionOptions = + examinationSessionQuery.data?.items?.filter( + (item) => + item.status === TYPE_STATUS.Draft || + item.status === TYPE_STATUS.InProgress, + ) || []; /* ========================= CREATE MUTATION @@ -119,11 +124,17 @@ const CreatePrescription = () => { const createPrescriptionMutation = useMutation({ mutationFn: async () => { + /** + * CREATE FORMDATA + */ const formData = new FormData(); - formData.append("examinationSessionId", form.examinationSessionId); + /** + * BASIC INFO + */ + formData.append("ExaminationSessionId", form.examinationSessionId); - formData.append("notes", form.notes); + formData.append("Notes", form.notes); /** * FILE @@ -133,22 +144,17 @@ const CreatePrescription = () => { } /** - * MEDICINE ITEMS + * ITEMS + * BACKEND EXPECT: + * Items = JSON.stringify([...]) */ - form.items - .filter((x) => x.medicineName.trim() !== "") - .forEach((item, index) => { - formData.append(`items[${index}].medicineName`, item.medicineName); + const validItems = form.items.filter((x) => x.medicineName.trim() !== ""); - formData.append(`items[${index}].dosage`, item.dosage); - - formData.append(`items[${index}].frequency`, item.frequency); - - formData.append(`items[${index}].duration`, item.duration); - - formData.append(`items[${index}].instructions`, item.instructions); - }); + formData.append("Items", JSON.stringify(validItems)); + /** + * API + */ return await httpRequest({ showMessageFailed: true, @@ -237,6 +243,10 @@ const CreatePrescription = () => { ========================= */ const handleSubmit = () => { + /** + * VALIDATE + */ + if (!form.examinationSessionId) { return toastWarn({ msg: "Vui lòng chọn phiên khám", @@ -249,6 +259,17 @@ const CreatePrescription = () => { }); } + const validMedicine = form.items.some((x) => x.medicineName.trim() !== ""); + + if (!validMedicine) { + return toastWarn({ + msg: "Vui lòng nhập ít nhất 1 loại thuốc", + }); + } + + /** + * MUTATE + */ createPrescriptionMutation.mutate(); }; diff --git a/src/components/page/prescription/DetailPrescription/DetailPrescription.tsx b/src/components/page/prescription/DetailPrescription/DetailPrescription.tsx index 5aa0999..7c2b835 100644 --- a/src/components/page/prescription/DetailPrescription/DetailPrescription.tsx +++ b/src/components/page/prescription/DetailPrescription/DetailPrescription.tsx @@ -6,12 +6,15 @@ import prescriptionServices, { PrescriptionDetail, } from "@/services/prescriptionServices"; import { useQuery } from "@tanstack/react-query"; +import Image from "next/image"; import { useParams } from "next/navigation"; -import React from "react"; +import React, { useState } from "react"; +import CustomPopup from "@/components/customs/custom-popup"; const DetailPrescription = () => { const params = useParams(); const PrescriptionId = params?.id as string; + const [openImagePreview, setOpenImagePreview] = useState(false); const prescriptionDetailQuery = useQuery({ queryKey: [QUERY_KEY.chi_tiet_don_thuoc, PrescriptionId], @@ -61,7 +64,7 @@ const DetailPrescription = () => { - Phiên khám: {data.examinationSessionId} + Id phiên khám: {data.examinationSessionId} @@ -73,11 +76,34 @@ const DetailPrescription = () => { Ảnh đơn thuốc: {data.prescriptionImg ? ( - + <> + setOpenImagePreview(true)} + unoptimized + /> + + {/* IMAGE PREVIEW POPUP */} + setOpenImagePreview(false)} + > + + + + + > ) : ( Không có ảnh )} diff --git a/src/components/page/prescription/MainPrescription/MainPrescription.tsx b/src/components/page/prescription/MainPrescription/MainPrescription.tsx index 2865877..c7ab0e1 100644 --- a/src/components/page/prescription/MainPrescription/MainPrescription.tsx +++ b/src/components/page/prescription/MainPrescription/MainPrescription.tsx @@ -130,12 +130,6 @@ const MainPrescription = () => { ), }, - // ================= SESSION ================= - { - title: "PHIÊN KHÁM", - render: (item: any) => {item.examinationSessionId}, - }, - // ================= NOTES ================= { title: "GHI CHÚ", @@ -148,11 +142,12 @@ const MainPrescription = () => { render: (item: any) => item.prescriptionImg ? ( ) : ( Không có ảnh diff --git a/src/constant/config/index.ts b/src/constant/config/index.ts index ff31e45..afb4da4 100644 --- a/src/constant/config/index.ts +++ b/src/constant/config/index.ts @@ -45,12 +45,12 @@ export const Menus: { path: PATH.CONSULTATION, pathActive: PATH.CONSULTATION, }, - // { - // title: "Đơn thuốc", - // icon: Pill, - // path: PATH.PRESCRIPTION, - // pathActive: PATH.PRESCRIPTION, - // }, + { + title: "Đơn thuốc", + icon: Pill, + path: PATH.PRESCRIPTION, + pathActive: PATH.PRESCRIPTION, + }, ], }, ]; diff --git a/src/services/prescriptionServices.ts b/src/services/prescriptionServices.ts index 0251d40..0028b0b 100644 --- a/src/services/prescriptionServices.ts +++ b/src/services/prescriptionServices.ts @@ -53,59 +53,55 @@ export interface PrescriptionResponse { } const prescriptionServices = { - createPrescription: ( - data: { - examinationSessionId: string; - notes: string; - PrescriptionFile: string; - items: { - medicineName: string; - dosage: string; - frequency: string; - duration: string; - instructions: string; - }[]; - }, - tokenAxios?: any, - ) => { + /** + * CREATE + */ + createPrescription: (data: FormData, tokenAxios?: any) => { return axiosClient.post(`/Prescription`, data, { + headers: { + "Content-Type": "multipart/form-data", + }, + cancelToken: tokenAxios, }); }, + /** + * GET LIST + */ getPrescription: (params?: GetPrescriptionParams, tokenAxios?: any) => { return axiosClient.get(`/Prescription`, { params, + cancelToken: tokenAxios, }); }, - putPrescription: ( - id: string, - data: { - notes: string; - prescriptionImg: string; - items: { - medicineName: string; - dosage: string; - frequency: string; - duration: string; - instructions: string; - }[]; - }, - tokenAxios?: any, - ) => { + /** + * UPDATE + */ + putPrescription: (id: string, data: FormData, tokenAxios?: any) => { return axiosClient.put(`/Prescription/${id}`, data, { + headers: { + "Content-Type": "multipart/form-data", + }, + cancelToken: tokenAxios, }); }, + /** + * DETAIL + */ detailPrescription: (id: string, tokenAxios?: any) => { return axiosClient.get(`/Prescription/${id}`, { cancelToken: tokenAxios, }); }, + /** + * UPLOAD IMAGE + */ uploadImage: (body: FormData) => { return axiosClient.post("/upload", body, { headers: {
Chi tiết tài khoản
Thay đổi mật khẩu
Cập nhật thông tin chi tiết quá trình khám bệnh