update
This commit is contained in:
@@ -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<boolean>(false);
|
||||
const queryKeys = [QUERY_KEY.table_list_consultation];
|
||||
const [file, setFile] = useState<File | null>(null);
|
||||
|
||||
const [form, setForm] = useState<PrescriptionForm>({
|
||||
examinationSessionId: "",
|
||||
examinationSessionSpecialtyId: "",
|
||||
notes: "",
|
||||
prescriptionImg: "",
|
||||
items: [defaultMedicine],
|
||||
});
|
||||
|
||||
/* =========================
|
||||
GET EXAMINATION SESSION
|
||||
GET EXAMINATION SESSION
|
||||
========================= */
|
||||
|
||||
const examinationSessionQuery = useQuery<ConsultationResponse>({
|
||||
queryKey: [QUERY_KEY.table_list_consultation],
|
||||
|
||||
queryFn: async () => {
|
||||
const res = await httpRequest<ConsultationResponse>({
|
||||
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 (
|
||||
<FormCustom form={form} setForm={setForm} onSubmit={handleSubmit}>
|
||||
<div className="flex flex-col gap-6 rounded-2xl bg-white p-6 shadow">
|
||||
@@ -286,7 +303,6 @@ const CreatePrescription = () => {
|
||||
<h2 className="text-[28px] font-semibold text-[#111827]">
|
||||
Tạo đơn thuốc
|
||||
</h2>
|
||||
|
||||
<p className="mt-1 text-sm text-[#697586]">
|
||||
Tạo mới đơn thuốc cho bệnh nhân
|
||||
</p>
|
||||
@@ -296,11 +312,11 @@ const CreatePrescription = () => {
|
||||
<CustomButton
|
||||
type="submit"
|
||||
variant="midnightBlue"
|
||||
disabled={createPrescriptionMutation.isPending}
|
||||
disabled={funcCreatePrescription.isPending || loading}
|
||||
className="min-w-[180px]"
|
||||
>
|
||||
{createPrescriptionMutation.isPending
|
||||
? "Đang tạo..."
|
||||
{funcCreatePrescription.isPending || loading
|
||||
? "Đang tạo đơn..."
|
||||
: "Tạo đơn thuốc"}
|
||||
</CustomButton>
|
||||
</div>
|
||||
@@ -317,30 +333,32 @@ const CreatePrescription = () => {
|
||||
<SelectForm
|
||||
label={
|
||||
<span className="text-[14px] font-medium text-[#374151]">
|
||||
Phiên khám
|
||||
Phiên khám chuyên khoa
|
||||
<span className="text-red-500">*</span>
|
||||
</span>
|
||||
}
|
||||
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 */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<UploadImage
|
||||
label={
|
||||
@@ -348,10 +366,10 @@ const CreatePrescription = () => {
|
||||
Hình ảnh đơn thuốc <span style={{ color: "red" }}>*</span>
|
||||
</span>
|
||||
}
|
||||
name="PrescriptionFile"
|
||||
name="prescriptionImg"
|
||||
file={file}
|
||||
setFile={setFile}
|
||||
path=""
|
||||
path={""}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user