diff --git a/src/app/consultation/update-specialty/page.tsx b/src/app/consultation/update-specialty/page.tsx
new file mode 100644
index 0000000..268f39d
--- /dev/null
+++ b/src/app/consultation/update-specialty/page.tsx
@@ -0,0 +1,14 @@
+"use client";
+import BaseLayout from "@/components/layouts/BaseLayout";
+import UpdateSpecialtyClinicId from "@/components/page/consultation/UpdateSpecialtyClinicId";
+import React from "react";
+
+const page = () => {
+ return (
+
+
+
+ );
+};
+
+export default page;
diff --git a/src/app/statistical/page.tsx b/src/app/statistical/page.tsx
new file mode 100644
index 0000000..482ba8e
--- /dev/null
+++ b/src/app/statistical/page.tsx
@@ -0,0 +1,14 @@
+"use client";
+import BaseLayout from "@/components/layouts/BaseLayout";
+import MainPageStatistical from "@/components/page/statistical/MainPageStatistical";
+import React from "react";
+
+const page = () => {
+ return (
+
+
+
+ );
+};
+
+export default page;
diff --git a/src/components/customs/FilterCustom.tsx b/src/components/customs/FilterCustom.tsx
new file mode 100644
index 0000000..e8e0ce1
--- /dev/null
+++ b/src/components/customs/FilterCustom.tsx
@@ -0,0 +1,194 @@
+"use client";
+
+import React, { useEffect, useRef, useState } from "react";
+import clsx from "clsx";
+import { Check, ChevronDown } from "lucide-react";
+
+import { removeVietnameseTones } from "@/common/funcs/optionConvert";
+
+interface OptionItem {
+ uuid: string | number;
+ name: string;
+ code?: string;
+}
+
+interface PropsFilterCustom {
+ styleRounded?: boolean;
+ showOptionAll?: boolean;
+ name: string;
+ listOption: OptionItem[];
+ value: T;
+ onChange: (value: T) => void;
+}
+
+function FilterCustom({
+ styleRounded = false,
+ showOptionAll = true,
+ name,
+ listOption,
+ value,
+ onChange,
+}: PropsFilterCustom) {
+ const wrapperRef = useRef(null);
+ const inputRef = useRef(null);
+
+ const [open, setOpen] = useState(false);
+ const [keyword, setKeyword] = useState("");
+
+ const normalizedKeyword = removeVietnameseTones(keyword);
+
+ useEffect(() => {
+ if (open) {
+ requestAnimationFrame(() => {
+ inputRef.current?.focus();
+ });
+ }
+ }, [open]);
+
+ useEffect(() => {
+ const handleClickOutside = (event: MouseEvent) => {
+ if (
+ wrapperRef.current &&
+ !wrapperRef.current.contains(event.target as Node)
+ ) {
+ setOpen(false);
+ }
+ };
+
+ document.addEventListener("mousedown", handleClickOutside);
+
+ return () => {
+ document.removeEventListener("mousedown", handleClickOutside);
+ };
+ }, []);
+
+ const filteredOptions = listOption.filter((item) =>
+ removeVietnameseTones(item.name).includes(normalizedKeyword),
+ );
+
+ const selectedLabel =
+ listOption.find((item) => item.uuid === value)?.name || "Tất cả";
+
+ return (
+
+ {/* Trigger */}
+
setOpen((prev) => !prev)}
+ className={clsx(
+ "flex items-center justify-between gap-2 rounded-full border",
+ "h-12 min-w-[240px] px-4",
+ "cursor-pointer select-none",
+ "border border-[#e1e5ed] bg-white",
+ "transition-all",
+ "hover:border-[#0019f6]",
+ styleRounded ? "rounded-full" : "rounded-md",
+ open && "border-[#0019f6]",
+ )}
+ >
+
+ {name}:
+
+
+ {selectedLabel}
+
+
+
+
+
+
+ {/* Dropdown */}
+ {open && (
+
+
+ {/* Search */}
+
setKeyword(e.target.value)}
+ placeholder="Tìm kiếm..."
+ className="
+ mb-2 w-full
+ rounded-lg border border-[#e1e5ed]
+ px-3 py-2 text-sm
+ outline-none
+ focus:border-[#0019f6]
+ "
+ />
+
+ {/* Options */}
+
+ {showOptionAll && (
+
{
+ onChange(null as T);
+ setOpen(false);
+ }}
+ className={clsx(
+ "flex cursor-pointer items-center justify-between",
+ "border-b border-[#e2e0ec]",
+ "px-3 py-2 text-sm",
+ "hover:text-[#0019f6]",
+ (value === null || value === "") && "text-[#0019f6]",
+ )}
+ >
+ Tất cả
+
+ {(value === null || value === "") && (
+
+ )}
+
+ )}
+
+ {filteredOptions.map((item) => {
+ const isActive = item.uuid === value;
+
+ return (
+
{
+ onChange(item.uuid as T);
+ setOpen(false);
+ }}
+ className={clsx(
+ "mt-1 flex cursor-pointer items-center justify-between",
+ "border-b border-[#e2e0ec]",
+ "px-3 py-2 text-sm",
+ "hover:text-[#0019f6]",
+ "last:border-none",
+ isActive && "text-[#0019f6]",
+ )}
+ >
+ {item.name}
+
+ {isActive && }
+
+ );
+ })}
+
+ {filteredOptions.length === 0 && (
+
+ Không tìm thấy dữ liệu
+
+ )}
+
+
+
+ )}
+
+ );
+}
+
+export default FilterCustom;
diff --git a/src/components/customs/FilterMany.tsx b/src/components/customs/FilterMany.tsx
new file mode 100644
index 0000000..b289c66
--- /dev/null
+++ b/src/components/customs/FilterMany.tsx
@@ -0,0 +1,205 @@
+"use client";
+
+import React, {
+ Dispatch,
+ SetStateAction,
+ useEffect,
+ useRef,
+ useState,
+} from "react";
+import clsx from "clsx";
+import { Check, ChevronDown } from "lucide-react";
+import Button from "./custom-button";
+import { removeVietnameseTones } from "@/common/funcs/optionConvert";
+
+interface OptionItem {
+ uuid: T;
+ name: string;
+ code?: string;
+}
+
+interface PropsFilterMany {
+ styleRounded?: boolean;
+ small?: boolean;
+ name: string;
+ listOption: OptionItem[];
+ value: T[];
+ setValue: Dispatch>;
+}
+
+function FilterMany({
+ styleRounded = false,
+ small = false,
+ name,
+ listOption,
+ value,
+ setValue,
+}: PropsFilterMany) {
+ const inputRef = useRef(null);
+
+ const [open, setOpen] = useState(false);
+ const [keyword, setKeyword] = useState("");
+ const [tempSelected, setTempSelected] = useState([]);
+
+ const wrapperRef = useRef(null);
+
+ // sync khi mở dropdown
+ useEffect(() => {
+ const handleClickOutside = (event: MouseEvent) => {
+ if (
+ wrapperRef.current &&
+ !wrapperRef.current.contains(event.target as Node)
+ ) {
+ setOpen(false);
+ }
+ };
+
+ document.addEventListener("mousedown", handleClickOutside);
+
+ return () => {
+ document.removeEventListener("mousedown", handleClickOutside);
+ };
+ }, []);
+
+ const handleOpen = () => {
+ setOpen((prev) => !prev);
+ };
+
+ useEffect(() => {
+ if (open) {
+ requestAnimationFrame(() => {
+ inputRef.current?.focus();
+ });
+ }
+ }, [open]);
+
+ const isAllSelected =
+ tempSelected.length === 0 || tempSelected.length === listOption.length;
+
+ const filteredList = listOption.filter((item) =>
+ removeVietnameseTones(item.name).includes(
+ removeVietnameseTones(keyword || ""),
+ ),
+ );
+
+ return (
+
+ {/* TRIGGER */}
+
+
+
{name}:
+
+
+ {value.length === 0 || value.length === listOption.length
+ ? "Tất cả"
+ : `${value.length} ${name.toLowerCase()}`}
+
+
+
+
+
+
+ {/* DROPDOWN */}
+ {open && (
+
+ {/* SEARCH */}
+
setKeyword(e.target.value)}
+ placeholder="Tìm kiếm..."
+ className="
+ w-full px-3 py-2 text-sm
+ border border-gray-200 rounded-lg outline-none
+ focus:border-blue-500
+ "
+ />
+
+ {/* LIST */}
+
+ {/* ALL */}
+
setTempSelected([])}
+ className={clsx(
+ "flex items-center justify-between px-3 py-2 cursor-pointer rounded-lg hover:bg-gray-100",
+ isAllSelected && "bg-blue-50",
+ )}
+ >
+
Tất cả
+
+ {isAllSelected &&
}
+
+
+ {/* OPTIONS */}
+ {filteredList.map((item) => {
+ const active = tempSelected.includes(item.uuid);
+
+ return (
+
{
+ setTempSelected((prev) =>
+ prev.includes(item.uuid)
+ ? prev.filter((x) => x !== item.uuid)
+ : [...prev, item.uuid],
+ );
+ }}
+ className={clsx(
+ "flex items-center justify-between px-3 py-2 cursor-pointer rounded-lg hover:bg-gray-100",
+ active && "bg-blue-50",
+ )}
+ >
+
{item.name}
+
+ {active &&
}
+
+ );
+ })}
+
+
+ {/* ACTION */}
+
+
+
+
+
+
+ )}
+
+ );
+}
+
+export default FilterMany;
diff --git a/src/components/layouts/BaseLayout/BaseLayout.tsx b/src/components/layouts/BaseLayout/BaseLayout.tsx
index d9c7e6a..e165e55 100644
--- a/src/components/layouts/BaseLayout/BaseLayout.tsx
+++ b/src/components/layouts/BaseLayout/BaseLayout.tsx
@@ -9,18 +9,9 @@ import RequireAuth from "@/components/protected/RequiredAuth";
export const ContextBaseLayout = React.createContext({});
const BaseLayout = ({ children, title, breadcrumb }: PropsBaseLayout) => {
- const [showFull, setShowFull] = React.useState(false);
+ const [showFull, setShowFull] = React.useState(true);
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 (
{
{/* SIDEBAR (Dùng chung cho cả Desktop & Mobile) */}
- {/* DẤU HIỆU SINH TỒN */}
+ {/* CHỈ SỐ HUYẾT ÁP, CHỈ SỐ MẠCH */}
- DẤU HIỆU SINH TỒN
+ CHỈ SỐ HUYẾT ÁP, CHỈ SỐ MẠCH
@@ -189,6 +189,32 @@ const DetailConsultation = () => {
+
+
+ CÁC CHUYÊN KHOA ĐANG CHỜ KHÁM
+
+
+
+ {consultation.specialtyClinics
+ ?.filter((specialty) => specialty.specialtyStatus === 1)
+ .map((specialty) => specialty.specialtyName)
+ .join(", ") || "---"}
+
+
+
+
+
+ CÁC CHUYÊN KHOA ĐÃ KHÁM
+
+
+
+ {consultation.specialtyClinics
+ ?.filter((specialty) => specialty.specialtyStatus === 3)
+ .map((specialty) => specialty.specialtyName)
+ .join(", ") || "---"}
+
+
+
{/* TRẠNG THÁI */}
@@ -200,29 +226,44 @@ const DetailConsultation = () => {
listState={[
{
state: TYPE_STATUS.Draft,
- text: "Đang chờ",
- textColor: "#000",
- backgroundColor: "#FFE4C4",
+ text: "Đang chờ khám",
+ textColor: "#92400E",
+ backgroundColor: "#FEF3C7",
},
{
state: TYPE_STATUS.InProgress,
- text: "Đang thực hiện",
- textColor: "#000",
- backgroundColor: "#ff3300",
+ text: "Đang khám tổng quát",
+ textColor: "#1E3A8A",
+ backgroundColor: "#DBEAFE",
+ },
+
+ {
+ state: TYPE_STATUS.ToSpecialty,
+ text: "Chờ khám chuyên khoa",
+ textColor: "#6B21A8",
+ backgroundColor: "#E9D5FF",
+ },
+
+ {
+ state: TYPE_STATUS.PendingPrescription,
+ text: "Chờ nhận thuốc/kính",
+ textColor: "#9A3412",
+ backgroundColor: "#FED7AA",
},
{
state: TYPE_STATUS.Completed,
text: "Hoàn thành",
- textColor: "#000",
- backgroundColor: "#3d69eb",
+ textColor: "#166534",
+ backgroundColor: "#DCFCE7",
},
+
{
state: TYPE_STATUS.Cancelled,
text: "Đã hủy",
- textColor: "#000",
- backgroundColor: "#cccc",
+ textColor: "#991B1B",
+ backgroundColor: "#FEE2E2",
},
]}
/>
diff --git a/src/components/page/consultation/MainPageConsultation/MainPageConsultation.tsx b/src/components/page/consultation/MainPageConsultation/MainPageConsultation.tsx
index 05ccd4a..6cfcd38 100644
--- a/src/components/page/consultation/MainPageConsultation/MainPageConsultation.tsx
+++ b/src/components/page/consultation/MainPageConsultation/MainPageConsultation.tsx
@@ -2,7 +2,6 @@
import CustomButton from "@/components/customs/custom-button";
import CustomDialog from "@/components/customs/custom-dialog";
-import CustomPopup from "@/components/customs/custom-popup";
import DataWrapper from "@/components/customs/DataWrapper";
import Search from "@/components/customs/custom-search";
import StateActive from "@/components/customs/StateActive";
@@ -27,8 +26,11 @@ import { usePathname, useRouter, useSearchParams } from "next/navigation";
import React, { useMemo, useState } from "react";
-import { PATH } from "@/constant/config";
import Pagination from "@/components/customs/custom-pagination";
+import moment from "moment";
+import specialtyServices, { SpecialtyItem } from "@/services/specialtyServices";
+
+import FilterCustom from "@/components/customs/FilterCustom";
const MainPageConsultation = () => {
const router = useRouter();
@@ -49,6 +51,10 @@ const MainPageConsultation = () => {
const _keyword = searchParams.get("_keyword");
+ const _status = searchParams.get("_status");
+
+ const _specialtyId = searchParams.get("_specialtyId");
+
/* =========================
STATE
========================= */
@@ -78,7 +84,7 @@ const MainPageConsultation = () => {
Search: _keyword || "",
- SortBy: "id",
+ SortBy: _specialtyId || undefined,
Desc: true,
}),
@@ -100,9 +106,35 @@ const MainPageConsultation = () => {
DATA
========================= */
+ const specialtyQuery = useQuery({
+ queryKey: [QUERY_KEY.list_specialty_lookup],
+ queryFn: async () => {
+ const res = await httpRequest({
+ http: specialtyServices.getSpecialty(),
+ showMessageFailed: true,
+ });
+
+ return res || [];
+ },
+ });
+
const consultationData = useMemo(() => {
- return consultationQuery.data?.items || [];
- }, [consultationQuery.data]);
+ let data = consultationQuery.data?.items || [];
+
+ if (_status) {
+ data = data.filter((item) => item.status === Number(_status));
+ }
+
+ if (_specialtyId) {
+ data = data.filter((item) =>
+ item.specialtyClinics?.some(
+ (specialty) => specialty.specialtyId === _specialtyId,
+ ),
+ );
+ }
+
+ return data;
+ }, [consultationQuery.data, _status, _specialtyId]);
/* =========================
UPDATE QUERY
@@ -114,13 +146,18 @@ const MainPageConsultation = () => {
const updateQuery = (key: string, value: string | number) => {
const params = new URLSearchParams(searchParams.toString());
- if (!value || value === "" || (key === "_page" && value === 1)) {
+ if (!value || value === "") {
params.delete(key);
} else {
params.set(key, String(value));
}
- if (key === "_pageSize") {
+ if (
+ key === "_pageSize" ||
+ key === "_keyword" ||
+ key === "_status" ||
+ key === "_specialtyId"
+ ) {
params.delete("_page");
}
@@ -259,6 +296,51 @@ const MainPageConsultation = () => {
placeholder="Tìm kiếm phiên khám..."
/>
+
+
+ name="Chuyên khoa"
+ value={_specialtyId}
+ onChange={(value) => updateQuery("_specialtyId", value ?? "")}
+ listOption={useMemo(() => {
+ return (
+ specialtyQuery.data?.map((item) => ({
+ uuid: item.id,
+ name: item.name,
+ })) || []
+ );
+ }, [specialtyQuery.data])}
+ />
+
+ name="Trạng thái"
+ value={_status ? Number(_status) : null}
+ onChange={(value) => updateQuery("_status", value ?? "")}
+ listOption={[
+ {
+ uuid: TYPE_STATUS.Draft,
+ name: "Đang chờ khám",
+ },
+ {
+ uuid: TYPE_STATUS.InProgress,
+ name: "Đang khám tổng quát",
+ },
+ {
+ uuid: TYPE_STATUS.ToSpecialty,
+ name: "Chờ khám chuyên khoa",
+ },
+ {
+ uuid: TYPE_STATUS.PendingPrescription,
+ name: "Chờ nhận thuốc/kính",
+ },
+ {
+ uuid: TYPE_STATUS.Completed,
+ name: "Hoàn thành",
+ },
+ {
+ uuid: TYPE_STATUS.Cancelled,
+ name: "Đã hủy",
+ },
+ ]}
+ />
{/* TABLE */}
@@ -295,8 +377,13 @@ const MainPageConsultation = () => {
{
title: "NGÀY KHÁM",
-
- render: (item: ConsultationItem) => {item.visitDate},
+ render: (item: ConsultationItem) => (
+
+ {item.visitDate
+ ? moment(item.visitDate).format("DD/MM/YYYY")
+ : "---"}
+
+ ),
},
{
@@ -330,7 +417,30 @@ const MainPageConsultation = () => {
{item.doctorNotes || "---"}
),
},
+ {
+ 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 {waitingSpecialties || "---"};
+ },
+ },
+ {
+ 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 {completedSpecialties || "---"};
+ },
+ },
{
title: "TRẠNG THÁI",
@@ -340,29 +450,44 @@ const MainPageConsultation = () => {
listState={[
{
state: TYPE_STATUS.Draft,
- text: "Đang chờ",
- textColor: "#000",
- backgroundColor: "#FFE4C4",
+ text: "Đang chờ khám",
+ textColor: "#92400E",
+ backgroundColor: "#FEF3C7",
},
{
state: TYPE_STATUS.InProgress,
- text: "Đang thực hiện",
- textColor: "#000",
- backgroundColor: "#ff3300",
+ text: "Đang khám tổng quát",
+ textColor: "#1E3A8A",
+ backgroundColor: "#DBEAFE",
+ },
+
+ {
+ state: TYPE_STATUS.ToSpecialty,
+ text: "Chờ khám chuyên khoa",
+ textColor: "#6B21A8",
+ backgroundColor: "#E9D5FF",
+ },
+
+ {
+ state: TYPE_STATUS.PendingPrescription,
+ text: "Chờ nhận thuốc/kính",
+ textColor: "#9A3412",
+ backgroundColor: "#FED7AA",
},
{
state: TYPE_STATUS.Completed,
text: "Hoàn thành",
- textColor: "#000",
- backgroundColor: "#3d69eb",
+ textColor: "#166534",
+ backgroundColor: "#DCFCE7",
},
+
{
state: TYPE_STATUS.Cancelled,
text: "Đã hủy",
- textColor: "#000",
- backgroundColor: "#cccc",
+ textColor: "#991B1B",
+ backgroundColor: "#FEE2E2",
},
]}
/>
@@ -381,15 +506,32 @@ const MainPageConsultation = () => {
const isDisabled = isCancelled || isCompleted;
+ // Tìm chuyên khoa có specialtyStatus = 1
+ const specialtyClinicActive = item.specialtyClinics?.find(
+ (specialty) => specialty.specialtyStatus === 1,
+ );
+
return (
{/* UPDATE */}
}
- href={`/consultation/update?_id=${item?.id}`}
- />
+ icon={
+ specialtyClinicActive ? undefined : (
+
+ )
+ }
+ href={
+ specialtyClinicActive
+ ? `/consultation/update-specialty?_id=${item.id}&specialtyClinicId=${specialtyClinicActive.id}`
+ : `/consultation/update?_id=${item.id}`
+ }
+ >
+ {specialtyClinicActive
+ ? `Cập nhật ${specialtyClinicActive.specialtyName}`
+ : undefined}
+
{/* CANCEL */}
{
pageSize={pageSize}
onSetPage={(value) => updateQuery("_page", value)}
onSetPageSize={(value) => updateQuery("_pageSize", value)}
- dependencies={[_pageSize, _keyword]}
+ dependencies={[_pageSize, _keyword, _specialtyId, _status]}
/>
{/* CANCEL DIALOG */}
diff --git a/src/components/page/consultation/TableHistoryPatient/TableHistoryPatient.tsx b/src/components/page/consultation/TableHistoryPatient/TableHistoryPatient.tsx
index 12d1bec..79f509e 100644
--- a/src/components/page/consultation/TableHistoryPatient/TableHistoryPatient.tsx
+++ b/src/components/page/consultation/TableHistoryPatient/TableHistoryPatient.tsx
@@ -18,6 +18,7 @@ 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";
+import moment from "moment";
const TableHistoryPatient = () => {
const searchParams = useSearchParams();
@@ -129,7 +130,13 @@ const TableHistoryPatient = () => {
},
{
title: "NGÀY KHÁM",
- render: (item: ConsultationItem) => {item.visitDate},
+ render: (item: ConsultationItem) => (
+
+ {item.visitDate
+ ? moment(item.visitDate).format("DD/MM/YYYY")
+ : "---"}
+
+ ),
},
{
title: "DẤU HIỆU",
@@ -151,27 +158,44 @@ const TableHistoryPatient = () => {
listState={[
{
state: TYPE_STATUS.Draft,
- text: "Đang chờ",
- textColor: "#000",
- backgroundColor: "#FFE4C4",
+ text: "Đang chờ khám",
+ textColor: "#92400E",
+ backgroundColor: "#FEF3C7",
},
+
{
state: TYPE_STATUS.InProgress,
- text: "Đang thực hiện",
- textColor: "#000",
- backgroundColor: "#ff3300",
+ text: "Đang khám tổng quát",
+ textColor: "#1E3A8A",
+ backgroundColor: "#DBEAFE",
},
+
+ {
+ state: TYPE_STATUS.ToSpecialty,
+ text: "Chờ khám chuyên khoa",
+ textColor: "#6B21A8",
+ backgroundColor: "#E9D5FF",
+ },
+
+ {
+ state: TYPE_STATUS.PendingPrescription,
+ text: "Chờ nhận thuốc/kính",
+ textColor: "#9A3412",
+ backgroundColor: "#FED7AA",
+ },
+
{
state: TYPE_STATUS.Completed,
text: "Hoàn thành",
- textColor: "#000",
- backgroundColor: "#3d69eb",
+ textColor: "#166534",
+ backgroundColor: "#DCFCE7",
},
+
{
state: TYPE_STATUS.Cancelled,
text: "Đã hủy",
- textColor: "#000",
- backgroundColor: "#cccccc",
+ textColor: "#991B1B",
+ backgroundColor: "#FEE2E2",
},
]}
/>
diff --git a/src/components/page/consultation/UpdateConsultation/UpdateConsultation.tsx b/src/components/page/consultation/UpdateConsultation/UpdateConsultation.tsx
index cdf1620..8cfe234 100644
--- a/src/components/page/consultation/UpdateConsultation/UpdateConsultation.tsx
+++ b/src/components/page/consultation/UpdateConsultation/UpdateConsultation.tsx
@@ -27,6 +27,7 @@ interface IUpdateConsul {
diagnosis: string;
treatmentPlan: string;
doctorNotes: string;
+ isGlassesDelivered: boolean;
}
/* =========================
@@ -39,6 +40,7 @@ const defaultForm: IUpdateConsul = {
diagnosis: "",
treatmentPlan: "",
doctorNotes: "",
+ isGlassesDelivered: false,
};
/* =========================
@@ -103,6 +105,7 @@ const UpdateConsultation = () => {
diagnosis: consul.diagnosis || "",
treatmentPlan: consul.treatmentPlan || "",
doctorNotes: consul.doctorNotes || "",
+ isGlassesDelivered: consul.isGlassesDelivered,
});
}, [consulDetailQuery.data]);
@@ -126,6 +129,7 @@ const UpdateConsultation = () => {
diagnosis: form.diagnosis,
treatmentPlan: form.treatmentPlan,
doctorNotes: form.doctorNotes,
+ isGlassesDelivered: form?.isGlassesDelivered,
}),
});
},
diff --git a/src/components/page/consultation/UpdateSpecialtyClinicId/UpdateSpecialtyClinicId.tsx b/src/components/page/consultation/UpdateSpecialtyClinicId/UpdateSpecialtyClinicId.tsx
new file mode 100644
index 0000000..95e679a
--- /dev/null
+++ b/src/components/page/consultation/UpdateSpecialtyClinicId/UpdateSpecialtyClinicId.tsx
@@ -0,0 +1,341 @@
+"use client";
+
+import CustomButton from "@/components/customs/custom-button";
+import GridColumn from "@/components/layouts/GridColumn";
+import FormCustom from "@/components/utils/FormCustom";
+import InputForm from "@/components/utils/FormCustom/components/InputForm";
+import TextArea from "@/components/utils/FormCustom/components/TextArea";
+import { PATH } from "@/constant/config";
+import { QUERY_KEY } from "@/constant/config/enum";
+import { httpRequest } from "@/services";
+import consultationServices, {
+ ConsultationDetail,
+} from "@/services/consultationServices";
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
+import { useParams, useRouter, useSearchParams } from "next/navigation";
+import React, { useEffect, useState } from "react";
+
+const UpdateSpecialtyClinicId = () => {
+ const router = useRouter();
+ const params = useParams();
+ const searchParams = useSearchParams();
+ const queryClient = useQueryClient();
+
+ const consultationId = searchParams.get("_id") || "";
+ const specialtyClinicId = searchParams.get("specialtyClinicId") || "";
+
+ const [form, setForm] = useState<{
+ specialtyAssignmentId: string;
+ specialtyNotes: string;
+ diagnosis: string;
+ procedureNotes: string;
+ glassRequired: boolean;
+ glassType: string;
+ }>({
+ specialtyAssignmentId: "",
+ specialtyNotes: "",
+ diagnosis: "",
+ procedureNotes: "",
+ glassRequired: false,
+ glassType: "",
+ });
+
+ /* =========================
+ GET DETAIL API
+ ========================= */
+ const consulDetailQuery = useQuery({
+ queryKey: [QUERY_KEY.chi_tiet_phien_kham, consultationId],
+ enabled: !!consultationId,
+ queryFn: async () => {
+ const res = await httpRequest({
+ showMessageFailed: true,
+ http: consultationServices.detailConsultations(consultationId),
+ });
+
+ return (
+ res || {
+ id: "",
+ patientId: "",
+ sessionCode: "",
+ visitDate: "",
+ status: 1,
+ chiefComplaint: null,
+ symptomsText: null,
+ vitalSigns: null,
+ diagnosis: null,
+ treatmentPlan: null,
+ doctorNotes: null,
+ createdBy: "",
+ isDeleted: false,
+ specialtyClinics: [
+ {
+ id: "",
+ specialtyId: "",
+ specialtyName: "",
+ specialtyNotes: null,
+ diagnosis: null,
+ procedureNotes: null,
+ glassRequired: null,
+ glassType: null,
+ examinerName: "",
+ specialtyStatus: 1,
+ sharedItems: [],
+ },
+ ],
+ aggregatedPrescriptionItems: [],
+ }
+ );
+ },
+ });
+
+ // Tìm chuyên khoa hiện tại từ data trả về
+ const selectedSpecialtyClinic =
+ consulDetailQuery.data?.specialtyClinics?.find(
+ (item) => item.id === specialtyClinicId,
+ );
+
+ /**
+ * ĐIỀU KIỆN KIỂM TRA KHOA MẮT
+ * Bạn hãy thay đổi điều kiện này tùy thuộc vào database của bạn.
+ * Ví dụ: selectedSpecialtyClinic?.specialtyId === "MẮT" hoặc kiểm tra theo tên.
+ */
+ const isEyeClinic =
+ selectedSpecialtyClinic?.specialtyName?.toLowerCase().includes("mắt") ||
+ selectedSpecialtyClinic?.specialtyId?.toLowerCase().includes("mat");
+
+ useEffect(() => {
+ const consul = consulDetailQuery.data;
+ if (!consul) return;
+
+ const specialtyClinic = consul.specialtyClinics?.find(
+ (item) => item.id === specialtyClinicId,
+ );
+
+ if (!specialtyClinic) return;
+
+ setForm({
+ specialtyAssignmentId: specialtyClinic.id || "",
+ specialtyNotes: specialtyClinic.specialtyNotes || "",
+ diagnosis: specialtyClinic.diagnosis || "",
+ procedureNotes: specialtyClinic.procedureNotes || "",
+ glassRequired: specialtyClinic.glassRequired || false,
+ glassType: specialtyClinic.glassType || "",
+ });
+ }, [consulDetailQuery.data, specialtyClinicId]);
+
+ /* =========================
+ UPDATE MUTATION
+ ========================= */
+ const specialtyResultsMutation = useMutation({
+ mutationFn: async () => {
+ if (!consultationId) {
+ throw new Error("Không tìm thấy ID phiên khám!");
+ }
+
+ return httpRequest({
+ showMessageFailed: true,
+ showMessageSuccess: true,
+ msgSuccess: "Cập nhật phiên khám thành công!",
+ http: consultationServices.specialtyResults(
+ form.specialtyAssignmentId,
+ {
+ specialtyNotes: form.specialtyNotes,
+ diagnosis: form.diagnosis,
+ procedureNotes: form.procedureNotes,
+ // Nếu không phải khoa mắt, gán mặc định false và chuỗi rỗng khi gửi API
+ glassRequired: isEyeClinic ? form.glassRequired : false,
+ glassType: isEyeClinic ? form.glassType : "",
+ },
+ ),
+ });
+ },
+ onSuccess(data) {
+ if (!data) return;
+
+ queryClient.invalidateQueries({
+ queryKey: [QUERY_KEY.table_list_consultation],
+ });
+ queryClient.invalidateQueries({
+ queryKey: [QUERY_KEY.chi_tiet_phien_kham, consultationId],
+ });
+
+ router.push(PATH.CONSULTATION || "/consultation");
+ },
+ });
+
+ const handleSubmit = () => {
+ specialtyResultsMutation.mutate();
+ };
+
+ const isLoading =
+ consulDetailQuery.isLoading || specialtyResultsMutation.isPending;
+
+ if (consulDetailQuery.isLoading) {
+ return (
+
+ Đang tải dữ liệu phiên khám...
+
+ );
+ }
+
+ return (
+
+
+ {/* HEADER */}
+
+
+
+ Phiên khám chuyên khoa
+
+
+ Cập nhật kết quả chi tiết quá trình khám bệnh của bệnh nhân
+
+
+
+
+ {/* CHUYÊN KHOA INFO */}
+
+
+ {/* KHỐI THÔNG TIN KHÁM CHUNG (Luôn cố định 3 cột đẹp mắt) */}
+
+
+ Thông tin khám chung
+
+
+
+ setForm((prev) => ({ ...prev, specialtyNotes: String(v) }))
+ }
+ />
+
+ setForm((prev) => ({ ...prev, diagnosis: String(v) }))
+ }
+ />
+
+ setForm((prev) => ({ ...prev, procedureNotes: String(v) }))
+ }
+ />
+
+
+
+ {/* KHỐI THÔNG TIN KHÁM MẮT (Chỉ hiển thị khi là khoa mắt) */}
+ {isEyeClinic && (
+
+
+ Chỉ định thị lực (Khoa Mắt)
+
+
+
+ {/* Radio Chọn Có/Không */}
+
+
+ {/* TextArea Loại Kính (Trải rộng ra 100% dòng tạo cảm giác thoáng đãng) */}
+ {form.glassRequired && (
+
+
+
+ )}
+
+
+ )}
+
+ {/* BUTTON ACTION */}
+
+ router.back()}
+ className="min-w-[120px]"
+ >
+ Hủy bỏ
+
+
+ {specialtyResultsMutation.isPending
+ ? "Đang cập nhật..."
+ : "Cập nhật kết quả"}
+
+
+
+
+ );
+};
+
+export default UpdateSpecialtyClinicId;
diff --git a/src/components/page/consultation/UpdateSpecialtyClinicId/index.ts b/src/components/page/consultation/UpdateSpecialtyClinicId/index.ts
new file mode 100644
index 0000000..3d1235d
--- /dev/null
+++ b/src/components/page/consultation/UpdateSpecialtyClinicId/index.ts
@@ -0,0 +1 @@
+export { default } from "./UpdateSpecialtyClinicId";
diff --git a/src/components/page/patient/DetailPatient/DetailPatient.tsx b/src/components/page/patient/DetailPatient/DetailPatient.tsx
index b760a01..4e6b08b 100644
--- a/src/components/page/patient/DetailPatient/DetailPatient.tsx
+++ b/src/components/page/patient/DetailPatient/DetailPatient.tsx
@@ -20,18 +20,16 @@ import CustomButton from "@/components/customs/custom-button";
import StateActive from "@/components/customs/StateActive";
import DataWrapper from "@/components/customs/DataWrapper";
import Table from "@/components/customs/custom-table";
-import CustomDialog from "@/components/customs/custom-dialog";
+import moment from "moment";
+// import CustomDialog from "@/components/customs/custom-dialog";
const DetailPatient = () => {
const params = useParams();
const router = useRouter();
- const queryClient = useQueryClient();
+ // const queryClient = useQueryClient();
const patientId = params?.id as string;
- // State quản lý đóng/mở Dialog xác nhận tạo phiên khám
- const [openCreateDialog, setOpenCreateDialog] = useState(false);
-
/* =========================
GET DETAIL PATIENT
========================= */
@@ -95,54 +93,9 @@ const DetailPatient = () => {
},
});
- /* =========================
- CREATE MUTATION
- ========================= */
- const createConsultationMutation = useMutation({
- mutationFn: async () => {
- return httpRequest({
- showMessageFailed: true,
- showMessageSuccess: true,
- msgSuccess: "Tạo phiên khám mới thành công!",
- // Gọi API với payload chứa patientId của bệnh nhân hiện tại
- http: consultationServices.createConsultations({
- patientId: patientId,
- }),
- });
- },
- onSuccess: (res: any) => {
- // Đóng dialog
- setOpenCreateDialog(false);
-
- // Làm mới danh sách lịch sử phiên khám của bệnh nhân này
- queryClient.invalidateQueries({
- queryKey: [QUERY_KEY.table_list_consultation, patientId],
- });
-
- if (res?.id) {
- router.push(`${PATH.CONSULTATION}/${res.id}`); // detail
- }
- },
- });
-
const patient = patientDetailQuery.data;
const consultationData = consultationQuery.data?.items || [];
- /* =========================
- HANDLERS DIALOG
- ========================= */
- const handleOpenCreate = () => {
- setOpenCreateDialog(true);
- };
-
- const handleCloseDialog = () => {
- setOpenCreateDialog(false);
- };
-
- const handleCreateDialog = () => {
- createConsultationMutation.mutate();
- };
-
if (patientDetailQuery.isLoading) {
return (
@@ -169,8 +122,11 @@ const DetailPatient = () => {
variant="midnightBlue"
fullWidth={false}
icon={
}
- onClick={handleOpenCreate}
- disabled={createConsultationMutation.isPending}
+ onClick={() =>
+ router.push(
+ `${PATH.CONSULTATION}/create?patientId=${patient?.id}`,
+ )
+ }
>
Tạo phiên khám
@@ -209,7 +165,9 @@ const DetailPatient = () => {
Ngày sinh
- {patient?.dateOfBirth}
+ {patient?.dateOfBirth
+ ? moment(patient?.dateOfBirth).format("DD/MM/YYYY")
+ : "---"}
@@ -289,7 +247,7 @@ const DetailPatient = () => {
- Lịch sử phiên khám
+ Lịch sử phiên khám của {patient?.fullName}
{
rowKey={(item) => item.id}
data={consultationData}
column={[
+ {
+ title: "TÊN BỆNH NHÂN",
+
+ render: (item: ConsultationItem) => (
+ {item.patientName}
+ ),
+ },
+
{
title: "MÃ PHIÊN KHÁM",
fixedLeft: true,
@@ -320,65 +286,120 @@ const DetailPatient = () => {
),
},
+
{
title: "NGÀY KHÁM",
render: (item: ConsultationItem) => (
- {item.visitDate}
+
+ {item.visitDate
+ ? moment(item.visitDate).format("DD/MM/YYYY")
+ : "---"}
+
),
},
+
{
title: "DẤU HIỆU",
+
render: (item: ConsultationItem) => (
{item.symptomsText || "---"}
),
},
+
{
title: "CHUẨN ĐOÁN",
+
render: (item: ConsultationItem) => (
{item.diagnosis || "---"}
),
},
+
{
title: "KẾ HOẠCH ĐIỀU TRỊ",
+
render: (item: ConsultationItem) => (
{item.treatmentPlan || "---"}
),
},
+
{
title: "GHI CHÚ BÁC SĨ",
+
render: (item: ConsultationItem) => (
{item.doctorNotes || "---"}
),
},
+ {
+ 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 {waitingSpecialties || "---"};
+ },
+ },
+ {
+ 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 {completedSpecialties || "---"};
+ },
+ },
{
title: "TRẠNG THÁI",
+ fixedRight: true,
render: (item: ConsultationItem) => (
@@ -388,20 +409,6 @@ const DetailPatient = () => {
/>
-
- {/* DIALOG XÁC NHẬN */}
-
);
};
diff --git a/src/components/page/patient/MainPagePatient/MainPagePatient.tsx b/src/components/page/patient/MainPagePatient/MainPagePatient.tsx
index e305794..cf8096e 100644
--- a/src/components/page/patient/MainPagePatient/MainPagePatient.tsx
+++ b/src/components/page/patient/MainPagePatient/MainPagePatient.tsx
@@ -221,7 +221,7 @@ const MainPagePatient = () => {
href={`/patient/${item.id}`}
className="text-blue-500 hover:underline"
>
- {item.fullName}
+ {item.fullName || "---"}
),
},
@@ -230,26 +230,28 @@ const MainPagePatient = () => {
title: "MÃ ĐỊNH DANH",
render: (item: PatientItem) => (
- {item.identificationNumber}
+ {item.identificationNumber || "---"}
),
},
{
title: "SỐ ĐIỆN THOẠI",
- render: (item: PatientItem) => {item.phone},
+ render: (item: PatientItem) => {item.phone || "---"},
},
{
title: "EMAIL",
- render: (item: PatientItem) => {item.email},
+ render: (item: PatientItem) => {item.email || "---"},
},
{
title: "GIỚI TÍNH",
- render: (item: PatientItem) => {item.gender},
+ render: (item: PatientItem) => (
+ {item.gender || "---"}
+ ),
},
{
diff --git a/src/components/page/patient/PopupUpdatePatient/PopupUpdatePatient.tsx b/src/components/page/patient/PopupUpdatePatient/PopupUpdatePatient.tsx
index 2a9b00c..b81ce8d 100644
--- a/src/components/page/patient/PopupUpdatePatient/PopupUpdatePatient.tsx
+++ b/src/components/page/patient/PopupUpdatePatient/PopupUpdatePatient.tsx
@@ -286,7 +286,7 @@ const PopupUpdatePatient = ({ onClose, id }: PopupUpdatePatientProps) => {
}
placeholder="Số điện thoại"
name="phone"
- type="number"
+ type="phone"
isPhone
value={form.phone}
isRequired
@@ -324,7 +324,7 @@ const PopupUpdatePatient = ({ onClose, id }: PopupUpdatePatientProps) => {
label="SĐT khẩn cấp"
placeholder="SĐT khẩn cấp"
name="emergencyContactPhone"
- type="number"
+ type="phone"
isRequired
isPhone
value={form.emergencyContactPhone}
diff --git a/src/components/page/prescription/CreatePrescription/CreatePrescription.tsx b/src/components/page/prescription/CreatePrescription/CreatePrescription.tsx
index bd7388f..32b69af 100644
--- a/src/components/page/prescription/CreatePrescription/CreatePrescription.tsx
+++ b/src/components/page/prescription/CreatePrescription/CreatePrescription.tsx
@@ -1,44 +1,26 @@
"use client";
import React, { useState } from "react";
-
import { Plus, Trash2 } from "lucide-react";
-
-import { useMutation, useQuery } from "@tanstack/react-query";
-
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useRouter } from "next/navigation";
-
import CustomButton from "@/components/customs/custom-button";
-
import FormCustom from "@/components/utils/FormCustom";
-
import InputForm from "@/components/utils/FormCustom/components/InputForm";
-
import TextArea from "@/components/utils/FormCustom/components/TextArea";
-
import SelectForm from "@/components/utils/FormCustom/components/SelectForm";
-
import UploadImage from "@/components/utils/UploadImage";
-
import { httpRequest } from "@/services";
-
import prescriptionServices from "@/services/prescriptionServices";
-
import consultationServices, {
- ConsultationItem,
ConsultationResponse,
} from "@/services/consultationServices";
-
-import { QUERY_KEY, TYPE_STATUS } from "@/constant/config/enum";
-
+import { QUERY_KEY } from "@/constant/config/enum";
import { toastWarn } from "@/common/funcs/toast";
-import { PATH } from "@/constant/config";
-
/* =========================
TYPES
========================= */
-
interface PrescriptionItem {
medicineName: string;
dosage: string;
@@ -48,15 +30,15 @@ interface PrescriptionItem {
}
interface PrescriptionForm {
- examinationSessionId: string;
+ examinationSessionSpecialtyId: string;
notes: string;
+ prescriptionImg: string;
items: PrescriptionItem[];
}
/* =========================
DEFAULT
========================= */
-
const defaultMedicine: PrescriptionItem = {
medicineName: "",
dosage: "",
@@ -68,29 +50,28 @@ const defaultMedicine: PrescriptionItem = {
/* =========================
COMPONENT
========================= */
-
const CreatePrescription = () => {
const router = useRouter();
-
+ const queryClient = useQueryClient();
+ const [loading, setLoading] = useState(false);
+ const queryKeys = [QUERY_KEY.table_list_consultation];
const [file, setFile] = useState(null);
const [form, setForm] = useState({
- examinationSessionId: "",
+ examinationSessionSpecialtyId: "",
notes: "",
+ prescriptionImg: "",
items: [defaultMedicine],
});
/* =========================
- GET EXAMINATION SESSION
+ GET EXAMINATION SESSION
========================= */
-
const examinationSessionQuery = useQuery({
queryKey: [QUERY_KEY.table_list_consultation],
-
queryFn: async () => {
const res = await httpRequest({
showMessageFailed: true,
-
http: consultationServices.getConsultations({
Page: 1,
PageSize: 100,
@@ -112,92 +93,82 @@ const CreatePrescription = () => {
});
const examinationSessionOptions =
- examinationSessionQuery.data?.items?.filter(
- (item) =>
- item.status === TYPE_STATUS.Draft ||
- item.status === TYPE_STATUS.InProgress,
- ) || [];
+ examinationSessionQuery.data?.items
+ ?.filter((session) => {
+ const isWaitingPrescription = session.status === 4;
+ const allSpecialtyCompleted = session.specialtyClinics?.every(
+ (specialty) => specialty.specialtyStatus === 3,
+ );
+ return isWaitingPrescription && allSpecialtyCompleted;
+ })
+ .flatMap((session) =>
+ session.specialtyClinics
+ .filter(
+ (specialty) =>
+ specialty.specialtyStatus === 3 &&
+ specialty.sharedItems.length === 0,
+ )
+ .map((specialty) => ({
+ id: specialty.id,
+ specialtyName: specialty.specialtyName,
+ sessionCode: session.sessionCode,
+ patientName: session.patientName,
+ })),
+ ) || [];
/* =========================
- CREATE MUTATION
+ CREATE MUTATION
========================= */
+ const funcCreatePrescription = useMutation({
+ mutationFn: (payload: any) => {
+ console.log("=== GỬI PAYLOAD LÊN API TẠO ĐƠN THUỐC ===", payload);
- const createPrescriptionMutation = useMutation({
- mutationFn: async () => {
- /**
- * CREATE FORMDATA
- */
- const formData = new FormData();
-
- /**
- * BASIC INFO
- */
- formData.append("ExaminationSessionId", form.examinationSessionId);
-
- formData.append("Notes", form.notes);
-
- /**
- * FILE
- */
- if (file) {
- formData.append("PrescriptionFile", file);
- }
-
- /**
- * ITEMS
- * BACKEND EXPECT:
- * Items = JSON.stringify([...])
- */
- const validItems = form.items.filter((x) => x.medicineName.trim() !== "");
-
- formData.append("Items", JSON.stringify(validItems));
-
- /**
- * API
- */
- return await httpRequest({
+ return httpRequest({
showMessageFailed: true,
-
showMessageSuccess: true,
-
msgSuccess: "Tạo đơn thuốc thành công!",
-
- http: prescriptionServices.createPrescription(formData),
+ http: prescriptionServices.createPrescription(payload),
});
},
- onSuccess: () => {
- router.push(PATH.PRESCRIPTION);
- },
+ onSuccess(data) {
+ console.log("TẠO ĐƠN THUỐC THÀNH CÔNG:", data);
- onError: (error: any) => {
- const message =
- error?.response?.data?.message || error?.message || "Có lỗi xảy ra";
-
- toastWarn({
- msg: message,
+ queryKeys.forEach((key) => {
+ queryClient.invalidateQueries({
+ queryKey: [key],
+ });
});
- console.log("CREATE PRESCRIPTION ERROR:", error);
+ setForm({
+ examinationSessionSpecialtyId: "",
+ notes: "",
+ prescriptionImg: "",
+ items: [defaultMedicine],
+ });
+ setFile(null);
+
+ // Điều hướng quay lại danh sách
+ router.back();
+ },
+ onError(error: any) {
+ console.error("LỖI HỆ THỐNG API TẠO ĐƠN THUỐC:", error);
},
});
/* =========================
- CHANGE MEDICINE FIELD
+ CHANGE MEDICINE FIELD
========================= */
-
const handleChangeMedicine = (
index: number,
key: keyof PrescriptionItem,
value: string,
) => {
const cloneItems = [...form.items];
-
cloneItems[index] = {
...cloneItems[index],
[key]: value,
};
-
setForm((prev) => ({
...prev,
items: cloneItems,
@@ -205,33 +176,20 @@ const CreatePrescription = () => {
};
/* =========================
- ADD MEDICINE
+ ADD MEDICINE
========================= */
-
const handleAddMedicine = () => {
setForm((prev) => ({
...prev,
-
- items: [
- ...prev.items,
- {
- medicineName: "",
- dosage: "",
- frequency: "",
- duration: "",
- instructions: "",
- },
- ],
+ items: [...prev.items, { ...defaultMedicine }],
}));
};
/* =========================
- REMOVE MEDICINE
+ REMOVE MEDICINE
========================= */
-
const handleRemoveMedicine = (index: number) => {
const newItems = form.items.filter((_, i) => i !== index);
-
setForm((prev) => ({
...prev,
items: newItems,
@@ -239,44 +197,103 @@ const CreatePrescription = () => {
};
/* =========================
- SUBMIT
+ SUBMIT VALIDATION
========================= */
+ const handleSubmit = async () => {
+ try {
+ // Kiểm tra dữ liệu đầu vào phía client
+ if (!form.examinationSessionSpecialtyId) {
+ return toastWarn({
+ msg: "Vui lòng chọn phiên khám chuyên khoa",
+ });
+ }
- const handleSubmit = () => {
- /**
- * VALIDATE
- */
+ if (!file) {
+ return toastWarn({
+ msg: "Vui lòng chọn ảnh đơn thuốc",
+ });
+ }
- if (!form.examinationSessionId) {
- return toastWarn({
- msg: "Vui lòng chọn phiên khám",
+ const validMedicineList = form.items.filter(
+ (item) => item.medicineName && item.medicineName.trim() !== "",
+ );
+
+ if (validMedicineList.length === 0) {
+ return toastWarn({
+ msg: "Vui lòng nhập ít nhất 1 loại thuốc hợp lệ",
+ });
+ }
+
+ // 1. Thực hiện gọi API upload ảnh
+ setLoading(true);
+ const uploadResponse: any = await prescriptionServices.uploadImage(
+ file,
+ "prescriptions",
+ );
+
+ console.log("KẾT QUẢ PHẢN HỒI GỐC TỪ API UPLOAD:", uploadResponse);
+
+ // Trích xuất URL linh hoạt tùy thuộc vào bộ bọc interceptor của axiosClient
+ let imageUrl = "";
+
+ // Trường hợp 1: axiosClient trả về thẳng dữ liệu (hoặc chuỗi URL) từ server
+ if (typeof uploadResponse === "string") {
+ imageUrl = uploadResponse;
+ } else if (uploadResponse?.fileUrl) {
+ imageUrl = uploadResponse.fileUrl;
+ } else if (uploadResponse?.url) {
+ imageUrl = uploadResponse.url;
+ }
+ // Trường hợp 2: axiosClient trả về nguyên bản cấu trúc AxiosResponse chứa thuộc tính .data
+ else if (uploadResponse?.data) {
+ const innerData = uploadResponse.data;
+ if (typeof innerData === "string") {
+ imageUrl = innerData;
+ } else {
+ imageUrl =
+ innerData?.fileUrl ||
+ innerData?.url ||
+ innerData?.data?.fileUrl ||
+ innerData?.data?.url ||
+ "";
+ }
+ }
+
+ console.log("ĐƯỜNG DẪN ẢNH CUỐI CÙNG TRÍCH XUẤT ĐƯỢC:", imageUrl);
+
+ if (!imageUrl) {
+ setLoading(false);
+ return toastWarn({
+ msg: "Không tìm thấy đường dẫn ảnh từ phản hồi của máy chủ",
+ });
+ }
+
+ // 2. Thiết lập cấu trúc dữ liệu Payload chuẩn xác theo interface của createPrescription
+ const finalPayload = {
+ examinationSessionSpecialtyId: form.examinationSessionSpecialtyId,
+ notes: form.notes || "",
+ prescriptionImg: imageUrl,
+ items: validMedicineList.map((item) => ({
+ medicineName: item.medicineName.trim(),
+ dosage: item.dosage || "",
+ frequency: item.frequency || "",
+ duration: item.duration || "",
+ instructions: item.instructions || "",
+ })),
+ };
+
+ // 3. Kích hoạt gọi Mutation gửi yêu cầu tạo đơn thuốc mới lên Server
+ funcCreatePrescription.mutate(finalPayload);
+ } catch (error) {
+ console.error("LỖI XỬ LÝ SUBMIT HOẶC UPLOAD ĐƠN THUỐC:", error);
+ toastWarn({
+ msg: "Có lỗi xảy ra trong quá trình xử lý tải ảnh lên hoặc lưu đơn thuốc",
});
+ } finally {
+ setLoading(false);
}
-
- if (!file) {
- return toastWarn({
- msg: "Vui lòng chọn ảnh đơn thuốc",
- });
- }
-
- 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();
};
- /* =========================
- UI
- ========================= */
-
return (
@@ -286,7 +303,6 @@ const CreatePrescription = () => {
Tạo đơn thuốc
-
Tạo mới đơn thuốc cho bệnh nhân
@@ -296,11 +312,11 @@ const CreatePrescription = () => {
- {createPrescriptionMutation.isPending
- ? "Đang tạo..."
+ {funcCreatePrescription.isPending || loading
+ ? "Đang tạo đơn..."
: "Tạo đơn thuốc"}
@@ -317,30 +333,32 @@ const CreatePrescription = () => {
- Phiên khám
+ Phiên khám chuyên khoa
*
}
- placeholder="Chọn phiên khám"
- value={form.examinationSessionId}
+ placeholder="Chọn phiên khám chuyên khoa"
+ value={form.examinationSessionSpecialtyId}
options={examinationSessionOptions}
- onSelect={(item: ConsultationItem) =>
+ onSelect={(item: any) =>
setForm((prev) => ({
...prev,
- examinationSessionId: item.id,
+ examinationSessionSpecialtyId: item.id,
}))
}
onClean={() =>
setForm((prev) => ({
...prev,
- examinationSessionId: "",
+ examinationSessionSpecialtyId: "",
}))
}
- getOptionLabel={(item: ConsultationItem) => `${item.sessionCode}`}
- getOptionValue={(item: ConsultationItem) => item.id}
+ getOptionLabel={(item: any) =>
+ `${item.sessionCode} - ${item.specialtyName} (${item.patientName})`
+ }
+ getOptionValue={(item: any) => item.id}
/>
- {/* IMAGE */}
+ {/* IMAGE UPLOAD */}
{
Hình ảnh đơn thuốc *
}
- name="PrescriptionFile"
+ name="prescriptionImg"
file={file}
setFile={setFile}
- path=""
+ path={""}
/>
diff --git a/src/components/page/prescription/DetailPrescription/DetailPrescription.tsx b/src/components/page/prescription/DetailPrescription/DetailPrescription.tsx
index 7c2b835..b4018aa 100644
--- a/src/components/page/prescription/DetailPrescription/DetailPrescription.tsx
+++ b/src/components/page/prescription/DetailPrescription/DetailPrescription.tsx
@@ -78,7 +78,7 @@ const DetailPrescription = () => {
{data.prescriptionImg ? (
<>
{
render: (item: any) =>
item.prescriptionImg ? (
{
+ // const router = useRouter();
+ // const pathname = usePathname();
+ // const searchParams = useSearchParams();
+
+ // const _page = searchParams.get("_page");
+
+ // const _pageSize = searchParams.get("_pageSize");
+ const reportQuery = useQuery({
+ queryKey: [QUERY_KEY.table_list_report],
+
+ queryFn: async () => {
+ const toDate = moment().format("YYYY-MM-DD");
+ const fromDate = moment().subtract(14, "days").format("YYYY-MM-DD");
+
+ const res = await httpRequest({
+ showMessageFailed: true,
+ http: consultationServices.getConsultationsReport({
+ FormDate: fromDate,
+ ToDate: toDate,
+ }),
+ });
+
+ return res as ReportResponse;
+ },
+ });
+
+ const reportData = useMemo(() => {
+ return reportQuery.data?.specialtyDetails || [];
+ }, [reportQuery.data]);
+
+ // 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) {
+ // params.delete(key);
+ // } else {
+ // params.set(key, String(value));
+ // }
+
+ // const queryString = params.toString();
+
+ // router.push(queryString ? `${pathname}?${queryString}` : pathname);
+ // };
+
+ return (
+
+ {/* HEADER */}
+
+
+ Thống kê các phiên khám
+
+
+
+ item.specialtyId}
+ data={reportData}
+ column={[
+ {
+ title: "MÃ CHUYÊN KHOA",
+ render: (item) => {item.specialtyId},
+ },
+ {
+ title: "TÊN CHUYÊN KHOA",
+ render: (item) => {item.specialtyName},
+ },
+ {
+ title: "SỐ CA KHÁM",
+ render: (item) => {item.totalCases},
+ },
+ ]}
+ />
+
+ {/* updateQuery("_page", value)}
+ onSetPageSize={(value) => updateQuery("_pageSize", value)}
+ dependencies={[_pageSize]}
+ /> */}
+
+ );
+};
+
+export default MainPageStatistical;
diff --git a/src/components/page/statistical/MainPageStatistical/index.ts b/src/components/page/statistical/MainPageStatistical/index.ts
new file mode 100644
index 0000000..48c9011
--- /dev/null
+++ b/src/components/page/statistical/MainPageStatistical/index.ts
@@ -0,0 +1 @@
+export { default } from "./MainPageStatistical";
diff --git a/src/components/utils/FormCustom/components/SelectForm/interface/index.ts b/src/components/utils/FormCustom/components/SelectForm/interface/index.ts
index d674375..22e082a 100644
--- a/src/components/utils/FormCustom/components/SelectForm/interface/index.ts
+++ b/src/components/utils/FormCustom/components/SelectForm/interface/index.ts
@@ -5,7 +5,7 @@ export interface PropsSelectForm {
isSearch?: boolean;
readOnly?: boolean;
- value: string | number;
+ value: string | number | React.ReactNode;
options: OptionType[];
onClean?: () => void;
diff --git a/src/constant/config/enum.ts b/src/constant/config/enum.ts
index 83c9b10..c49cbcd 100644
--- a/src/constant/config/enum.ts
+++ b/src/constant/config/enum.ts
@@ -2,17 +2,29 @@ export enum QUERY_KEY {
table_list_patient,
table_list_consultation,
table_list_patientrescription,
+ table_list_specialty,
+ table_list_report,
+
+ list_specialty_lookup,
chi_tiet_benh_nhan,
chi_tiet_don_thuoc,
chi_tiet_phien_kham,
+
+ user_me,
}
+// Draft = 1,
+// InProgress = 2,
+// Completed = 3,
+// Cancelled = 4,
export enum TYPE_STATUS {
- Draft = 1,
- InProgress = 2,
- Completed = 3,
- Cancelled = 4,
+ Draft = 1, // Phiên tạm (mới tiếp nhận, chưa khám)
+ InProgress = 2, // Đang khám tổng quát
+ ToSpecialty = 3, // Đã chỉ định & Đang chờ khám chuyên khoa 💡 (MỚI)
+ PendingPrescription = 4, // Đã khám xong hết, Chờ quầy dược phát thuốc/kính 💡 (MỚI)
+ Completed = 5, // Đã hoàn thành hoàn toàn (Đã khám + Đã nhận thuốc)
+ Cancelled = 6, // Phiên khám bị hủy
}
export enum TYPE_GENDER {
diff --git a/src/constant/config/index.ts b/src/constant/config/index.ts
index afb4da4..4ec16e1 100644
--- a/src/constant/config/index.ts
+++ b/src/constant/config/index.ts
@@ -1,4 +1,10 @@
-import { CircleGauge, HeartPulse, Pill, Users } from "lucide-react";
+import {
+ ChartNoAxesCombined,
+ CircleGauge,
+ HeartPulse,
+ Pill,
+ Users,
+} from "lucide-react";
export enum PATH {
HOME = "/",
@@ -8,6 +14,8 @@ export enum PATH {
PRESCRIPTION = "/prescription",
CREATE_PRESCRIPTION = "/prescription/create",
+ STATISTICAL = "/statistical",
+
LOGIN = "/auth/login",
REGISTER = "/auth/register",
FORGOT_PASSWORD = "/auth/forgot-password",
@@ -51,6 +59,12 @@ export const Menus: {
path: PATH.PRESCRIPTION,
pathActive: PATH.PRESCRIPTION,
},
+ {
+ title: "Thống kê chuyên khoa",
+ icon: ChartNoAxesCombined,
+ path: PATH.STATISTICAL,
+ pathActive: PATH.STATISTICAL,
+ },
],
},
];
diff --git a/src/services/authServices.ts b/src/services/authServices.ts
index 4977eef..da4cdc8 100644
--- a/src/services/authServices.ts
+++ b/src/services/authServices.ts
@@ -9,7 +9,7 @@ const authServices = {
},
tokenAxios?: any,
) => {
- return axiosClient.post(`/Auth/login`, data, {
+ return axiosClient.post(`/api/v1/Auth/login`, data, {
cancelToken: tokenAxios,
});
},
@@ -22,7 +22,7 @@ const authServices = {
},
tokenAxios?: any,
) => {
- return axiosClient.post(`/Auth/register`, data, {
+ return axiosClient.post(`/api/v1/Auth/register`, data, {
cancelToken: tokenAxios,
});
},
@@ -32,13 +32,13 @@ const authServices = {
},
tokenAxios?: any,
) => {
- return axiosClient.post(`/Auth/refresh`, data, {
+ return axiosClient.post(`/api/v1/Auth/refresh`, data, {
cancelToken: tokenAxios,
});
},
logout: (tokenAxios?: any) => {
return axiosClient.post(
- `/Auth/logout`,
+ `/api/v1/Auth/logout`,
{},
{
cancelToken: tokenAxios,
diff --git a/src/services/consultationServices.ts b/src/services/consultationServices.ts
index 0247c13..d1d4b51 100644
--- a/src/services/consultationServices.ts
+++ b/src/services/consultationServices.ts
@@ -24,6 +24,20 @@ export interface ConsultationItem {
doctorNotes: string | null;
createdBy: string;
isDeleted: boolean;
+ specialtyClinics: {
+ id: string;
+ specialtyId: string;
+ specialtyName: string;
+ specialtyNotes: string | null;
+ diagnosis: string | null;
+ procedureNotes: string | null;
+ glassRequired: string | null;
+ glassType: string | null;
+ examinerName: string;
+ specialtyStatus: number;
+ sharedItems: any[];
+ }[];
+ aggregatedPrescriptionItems: any[];
}
export interface ConsultationDetail {
@@ -39,8 +53,23 @@ export interface ConsultationDetail {
diagnosis: string | null;
treatmentPlan: string | null;
doctorNotes: string | null;
+ isGlassesDelivered: boolean;
createdBy: string;
isDeleted: boolean;
+ specialtyClinics: {
+ id: string;
+ specialtyId: string;
+ specialtyName: string;
+ specialtyNotes: string | null;
+ diagnosis: string | null;
+ procedureNotes: string | null;
+ glassRequired: boolean | null;
+ glassType: string | null;
+ examinerName: string;
+ specialtyStatus: number;
+ sharedItems: any[];
+ }[];
+ aggregatedPrescriptionItems: any[];
}
export interface ConsultationResponse {
@@ -51,23 +80,63 @@ export interface ConsultationResponse {
totalPages: number;
}
+export interface ReportResponse {
+ fromDate: string;
+ toDate: string;
+ totalSessions: number;
+ totalSpecialtySubSessions: number;
+ specialtyDetails: {
+ specialtyId: string;
+ specialtyName: string;
+ totalCases: number;
+ }[];
+ totalPrescriptionsIssued: number;
+}
+
const consultationServices = {
getConsultations: (params?: GetConsultationParams, tokenAxios?: any) => {
- return axiosClient.get(`/ExaminationSession/list`, {
- params,
- cancelToken: tokenAxios,
- });
+ return axiosClient.get(
+ `/api/v1/ExaminationSession/list`,
+ {
+ params,
+ cancelToken: tokenAxios,
+ },
+ );
},
- createConsultations: (data?: { patientId: string }, tokenAxios?: any) => {
- return axiosClient.post(`/ExaminationSession`, data, {
+ getConsultationsReport: (
+ params: { FormDate: string; ToDate: string },
+ tokenAxios?: any,
+ ) => {
+ return axiosClient.get(
+ `/api/v1/ExaminationSession/report`,
+ {
+ params,
+ cancelToken: tokenAxios,
+ },
+ );
+ },
+ createConsultations: (
+ data?: {
+ patientId: string;
+ chiefComplaint: string;
+ symptomsText: string;
+ vitalSigns: string;
+ specialtyIds: string[];
+ },
+ tokenAxios?: any,
+ ) => {
+ return axiosClient.post(`/api/v1/ExaminationSession`, data, {
cancelToken: tokenAxios,
});
},
detailConsultations: (id: string, tokenAxios?: any) => {
- return axiosClient.get(`/ExaminationSession/${id}`, {
- cancelToken: tokenAxios,
- });
+ return axiosClient.get(
+ `/api/v1/ExaminationSession/${id}`,
+ {
+ cancelToken: tokenAxios,
+ },
+ );
},
putConsultations: (
@@ -79,25 +148,46 @@ const consultationServices = {
diagnosis: string | null;
treatmentPlan: string | null;
doctorNotes: string | null;
+ isGlassesDelivered: boolean;
},
tokenAxios?: any,
) => {
- return axiosClient.put(`/ExaminationSession/${id}`, data, {
+ return axiosClient.put(`/api/v1/ExaminationSession/${id}`, data, {
cancelToken: tokenAxios,
});
},
cancelConsultations: (id: string, tokenAxios?: any) => {
- return axiosClient.post(`/ExaminationSession/${id}/cancel`, {
+ return axiosClient.post(`/api/v1/ExaminationSession/${id}/cancel`, {
cancelToken: tokenAxios,
});
},
completeConsultations: (id: string, tokenAxios?: any) => {
- return axiosClient.post(`/ExaminationSession/${id}/complete`, {
+ return axiosClient.post(`/api/v1/ExaminationSession/${id}/complete`, {
cancelToken: tokenAxios,
});
},
+
+ specialtyResults: (
+ specialtyAssignmentId: string,
+ data: {
+ specialtyNotes: string;
+ diagnosis: string;
+ procedureNotes: string;
+ glassRequired: boolean;
+ glassType: string;
+ },
+ tokenAxios?: any,
+ ) => {
+ return axiosClient.put(
+ `/api/v1/ExaminationSession/specialty-results/${specialtyAssignmentId}`,
+ data,
+ {
+ cancelToken: tokenAxios,
+ },
+ );
+ },
};
export default consultationServices;
diff --git a/src/services/index.ts b/src/services/index.ts
index 6077f94..f43de4e 100644
--- a/src/services/index.ts
+++ b/src/services/index.ts
@@ -256,6 +256,7 @@ axiosClient.interceptors.response.use(
if ((status === 401 || status === 403) && !data) {
return Promise.reject({
+ status,
success: false,
error: {
@@ -265,15 +266,36 @@ axiosClient.interceptors.response.use(
});
}
- return Promise.reject(
- data || {
+ // If server provided a body (data), forward it and attach HTTP status so
+ // callers receive the actual error and the numeric status code.
+ const serverBody = error.response?.data;
+
+ let payload: any;
+
+ if (serverBody && typeof serverBody === "object") {
+ payload = { ...serverBody, status };
+ } else if (serverBody) {
+ payload = {
+ status,
success: false,
error: {
- code: "SERVER_ERROR",
- message: "Có lỗi xảy ra",
+ code: status ?? "SERVER_ERROR",
+ message: String(serverBody),
},
- },
- );
+ };
+ } else {
+ payload = {
+ status,
+ success: false,
+ error: {
+ code: status ?? "SERVER_ERROR",
+ message:
+ error.response?.statusText || error.message || "Có lỗi xảy ra",
+ },
+ };
+ }
+
+ return Promise.reject(payload);
},
);
@@ -288,7 +310,7 @@ export const httpRequest = async ({
setLoading,
msgSuccess = "Thành công",
showMessageSuccess = false,
- showMessageFailed = false,
+ showMessageFailed = true,
onError,
}: HttpRequestOptions): Promise => {
try {
@@ -308,16 +330,20 @@ export const httpRequest = async ({
},
} as ApiResponse);
- // ================= SUCCESS =================
+ /* ================= SUCCESS ================= */
+
if (safeRes.success) {
if (showMessageSuccess) {
- toastSuccess({ msg: msgSuccess });
+ toastSuccess({
+ msg: msgSuccess,
+ });
}
return safeRes.data as T;
}
- // ================= FAIL =================
+ /* ================= FAIL ================= */
+
const error = {
message: safeRes.error?.message || "Có lỗi xảy ra",
code: safeRes.error?.code,
@@ -325,9 +351,20 @@ export const httpRequest = async ({
onError?.();
- throw error; // 👈 QUAN TRỌNG
- } catch (error) {
- throw error; // 👈 QUAN TRỌNG: KHÔNG NUỐT
+ throw error;
+ } catch (error: any) {
+ console.log("HTTP ERROR:", error);
+
+ const message = error?.error?.message || error?.message || "Có lỗi xảy ra";
+
+ /* SHOW TOAST */
+ if (showMessageFailed) {
+ toastWarn({
+ msg: message,
+ });
+ }
+
+ throw error;
} finally {
setLoading?.(false);
}
diff --git a/src/services/patientServices.ts b/src/services/patientServices.ts
index 0bff7e9..1b648ba 100644
--- a/src/services/patientServices.ts
+++ b/src/services/patientServices.ts
@@ -57,7 +57,7 @@ export interface PatientResponse {
const patientServices = {
getPatient: (params?: GetPatientParams, tokenAxios?: any) => {
- return axiosClient.get(`/Patient/list`, {
+ return axiosClient.get(`/api/v1/Patient/list`, {
params,
cancelToken: tokenAxios,
});
@@ -75,7 +75,7 @@ const patientServices = {
},
tokenAxios?: any,
) => {
- return axiosClient.post(`/Patient`, data, {
+ return axiosClient.post(`/api/v1/Patient`, data, {
cancelToken: tokenAxios,
});
},
@@ -97,19 +97,19 @@ const patientServices = {
},
tokenAxios?: any,
) => {
- return axiosClient.put(`/Patient/${id}`, data, {
+ return axiosClient.put(`/api/v1/Patient/${id}`, data, {
cancelToken: tokenAxios,
});
},
detailPatient: (id: string, tokenAxios?: any) => {
- return axiosClient.get(`/Patient/${id}`, {
+ return axiosClient.get(`/api/v1/Patient/${id}`, {
cancelToken: tokenAxios,
});
},
deletePatient: (id: string, tokenAxios?: any) => {
- return axiosClient.delete(`/Patient/${id}`, {
+ return axiosClient.delete(`/api/v1/Patient/${id}`, {
cancelToken: tokenAxios,
});
},
diff --git a/src/services/prescriptionServices.ts b/src/services/prescriptionServices.ts
index 0028b0b..7cc86e6 100644
--- a/src/services/prescriptionServices.ts
+++ b/src/services/prescriptionServices.ts
@@ -56,12 +56,22 @@ const prescriptionServices = {
/**
* CREATE
*/
- createPrescription: (data: FormData, tokenAxios?: any) => {
- return axiosClient.post(`/Prescription`, data, {
- headers: {
- "Content-Type": "multipart/form-data",
- },
-
+ createPrescription: (
+ data: {
+ examinationSessionSpecialtyId: string;
+ notes: string;
+ prescriptionImg: string;
+ items: {
+ medicineName: string;
+ dosage: string;
+ frequency: string;
+ duration: string;
+ instructions: string;
+ }[];
+ },
+ tokenAxios?: any,
+ ) => {
+ return axiosClient.post(`/api/v1/Prescription`, data, {
cancelToken: tokenAxios,
});
},
@@ -70,7 +80,7 @@ const prescriptionServices = {
* GET LIST
*/
getPrescription: (params?: GetPrescriptionParams, tokenAxios?: any) => {
- return axiosClient.get(`/Prescription`, {
+ return axiosClient.get(`/api/v1/Prescription`, {
params,
cancelToken: tokenAxios,
@@ -81,7 +91,7 @@ const prescriptionServices = {
* UPDATE
*/
putPrescription: (id: string, data: FormData, tokenAxios?: any) => {
- return axiosClient.put(`/Prescription/${id}`, data, {
+ return axiosClient.put(`/api/v1/Prescription/${id}`, data, {
headers: {
"Content-Type": "multipart/form-data",
},
@@ -94,7 +104,7 @@ const prescriptionServices = {
* DETAIL
*/
detailPrescription: (id: string, tokenAxios?: any) => {
- return axiosClient.get(`/Prescription/${id}`, {
+ return axiosClient.get(`/api/v1/Prescription/${id}`, {
cancelToken: tokenAxios,
});
},
@@ -102,8 +112,22 @@ const prescriptionServices = {
/**
* UPLOAD IMAGE
*/
- uploadImage: (body: FormData) => {
- return axiosClient.post("/upload", body, {
+ /**
+ * UPLOAD IMAGE
+ * @param file Đối tượng File lấy từ thẻ input hoặc upload component
+ * @param folderName Tên thư mục lưu trữ (Không bắt buộc)
+ */
+ uploadImage: (file: File, folderName?: string) => {
+ const formData = new FormData();
+
+ // Key phải viết hoa chữ cái đầu đúng như Swagger yêu cầu (File và FolderName)
+ formData.append("File", file);
+
+ if (folderName) {
+ formData.append("FolderName", folderName);
+ }
+
+ return axiosClient.post("/api/Media/upload", formData, {
headers: {
"Content-Type": "multipart/form-data",
},
diff --git a/src/services/specialtyServices.ts b/src/services/specialtyServices.ts
new file mode 100644
index 0000000..0585adf
--- /dev/null
+++ b/src/services/specialtyServices.ts
@@ -0,0 +1,16 @@
+import axiosClient from ".";
+
+export interface SpecialtyItem {
+ id: string;
+ name: string;
+}
+
+const specialtyServices = {
+ getSpecialty: (tokenAxios?: any) => {
+ return axiosClient.get("/api/v1/Specialty/lookup", {
+ cancelToken: tokenAxios,
+ });
+ },
+};
+
+export default specialtyServices;
diff --git a/src/services/userServices.ts b/src/services/userServices.ts
index 5c38277..caa90c7 100644
--- a/src/services/userServices.ts
+++ b/src/services/userServices.ts
@@ -10,9 +10,20 @@ interface GetUserParams {
Desc?: boolean;
}
+export interface UserMe {
+ id: string;
+ username: string;
+ role: number;
+ roleType: number | string | null;
+ isActive: boolean;
+ fullName: string | null;
+ email: string | null;
+ phone: string | number | null;
+}
+
const userServices = {
getUser: (params?: GetUserParams, tokenAxios?: any) => {
- return axiosClient.get(`/User/list`, {
+ return axiosClient.get(`/api/v1/User/list`, {
params,
cancelToken: tokenAxios,
});
@@ -29,7 +40,7 @@ const userServices = {
},
tokenAxios?: any,
) => {
- return axiosClient.post(`/Users/`, data, {
+ return axiosClient.post(`/api/v1/Users/`, data, {
cancelToken: tokenAxios,
});
},
@@ -44,22 +55,22 @@ const userServices = {
},
tokenAxios?: any,
) => {
- return axiosClient.put(`/Users/${id}`, data, {
+ return axiosClient.put(`/api/v1/Users/${id}`, data, {
cancelToken: tokenAxios,
});
},
deleteUser: (id: string, tokenAxios?: any) => {
- return axiosClient.delete(`/Users/${id}`, {
+ return axiosClient.delete(`/api/v1/Users/${id}`, {
cancelToken: tokenAxios,
});
},
detailUser: (id: string, tokenAxios?: any) => {
- return axiosClient.get(`/Users/${id}`, {
+ return axiosClient.get(`/api/v1/Users/${id}`, {
cancelToken: tokenAxios,
});
},
meUser: (tokenAxios?: any) => {
- return axiosClient.get(`/Users/me`, {
+ return axiosClient.get(`/api/v1/Users/me`, {
cancelToken: tokenAxios,
});
},