feat:update web
This commit is contained in:
@@ -0,0 +1,409 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { Plus, Trash2 } from "lucide-react";
|
||||
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
|
||||
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 UploadMultipleFile from "@/components/utils/UploadMultipleFile";
|
||||
|
||||
import { httpRequest } from "@/services";
|
||||
|
||||
import prescriptionServices from "@/services/prescriptionServices";
|
||||
|
||||
import consultationServices, {
|
||||
ConsultationItem,
|
||||
ConsultationResponse,
|
||||
} from "@/services/consultationServices";
|
||||
|
||||
import { QUERY_KEY } from "@/constant/config/enum";
|
||||
import { toastWarn } from "@/common/funcs/toast";
|
||||
import { PATH } from "@/constant/config";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
interface PrescriptionItem {
|
||||
medicineName: string;
|
||||
dosage: string;
|
||||
frequency: string;
|
||||
duration: string;
|
||||
instructions: string;
|
||||
}
|
||||
|
||||
interface PrescriptionForm {
|
||||
examinationSessionId: string;
|
||||
notes: string;
|
||||
prescriptionImg: string;
|
||||
items: PrescriptionItem[];
|
||||
}
|
||||
|
||||
interface UploadImageItem {
|
||||
url?: string;
|
||||
path?: string;
|
||||
file?: File;
|
||||
img?: string;
|
||||
}
|
||||
|
||||
const defaultMedicine: PrescriptionItem = {
|
||||
medicineName: "",
|
||||
dosage: "",
|
||||
frequency: "",
|
||||
duration: "",
|
||||
instructions: "",
|
||||
};
|
||||
|
||||
const CreatePrescription = () => {
|
||||
const router = useRouter();
|
||||
const [images, setImages] = useState<UploadImageItem[]>([]);
|
||||
|
||||
const [form, setForm] = useState<PrescriptionForm>({
|
||||
examinationSessionId: "",
|
||||
notes: "",
|
||||
prescriptionImg: "",
|
||||
items: [defaultMedicine],
|
||||
});
|
||||
|
||||
/**
|
||||
* 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,
|
||||
SortBy: "id",
|
||||
Desc: true,
|
||||
}),
|
||||
});
|
||||
|
||||
return (
|
||||
res || {
|
||||
items: [],
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
totalPages: 0,
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const examinationSessionOptions = examinationSessionQuery.data?.items || [];
|
||||
|
||||
const createPrescriptionMutation = useMutation({
|
||||
mutationFn: async () => {
|
||||
return await httpRequest({
|
||||
showMessageFailed: true,
|
||||
showMessageSuccess: true,
|
||||
msgSuccess: "Tạo đơn thuốc thành công!",
|
||||
|
||||
http: prescriptionServices.createPrescription({
|
||||
examinationSessionId: form.examinationSessionId,
|
||||
notes: form.notes,
|
||||
prescriptionImg: form.prescriptionImg,
|
||||
items: form.items,
|
||||
}),
|
||||
});
|
||||
},
|
||||
|
||||
onSuccess: () => {
|
||||
router.push(PATH.PRESCRIPTION);
|
||||
},
|
||||
|
||||
onError: (error: any) => {
|
||||
// 👇 BACKEND ERROR HERE
|
||||
const message =
|
||||
error?.message || error?.error?.message || "Có lỗi xảy ra";
|
||||
|
||||
const code = error?.code || error?.error?.code;
|
||||
|
||||
toastWarn({
|
||||
msg: message,
|
||||
});
|
||||
|
||||
console.log("ERROR CODE:", code);
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 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,
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* ADD MEDICINE
|
||||
*/
|
||||
const handleAddMedicine = () => {
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
|
||||
items: [
|
||||
...prev.items,
|
||||
{
|
||||
medicineName: "",
|
||||
dosage: "",
|
||||
frequency: "",
|
||||
duration: "",
|
||||
instructions: "",
|
||||
},
|
||||
],
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* REMOVE MEDICINE
|
||||
*/
|
||||
const handleRemoveMedicine = (index: number) => {
|
||||
const newItems = form.items.filter((_, i) => i !== index);
|
||||
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
items: newItems,
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* SUBMIT
|
||||
*/
|
||||
const handleSubmit = () => {
|
||||
createPrescriptionMutation.mutate();
|
||||
};
|
||||
|
||||
return (
|
||||
<FormCustom form={form} setForm={setForm} onSubmit={handleSubmit}>
|
||||
<div className="flex flex-col gap-6 rounded-2xl bg-white p-6 shadow">
|
||||
{/* HEADER */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-[28px] font-semibold text-[#111827]">
|
||||
Thêm đơ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>
|
||||
</div>
|
||||
<div>
|
||||
<CustomButton
|
||||
type="submit"
|
||||
variant="midnightBlue"
|
||||
disabled={createPrescriptionMutation.isPending}
|
||||
>
|
||||
{createPrescriptionMutation.isPending
|
||||
? "Đang lưu..."
|
||||
: "Lưu đơn thuốc"}
|
||||
</CustomButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* GENERAL INFO */}
|
||||
<div className="rounded-2xl border border-gray-200 p-5">
|
||||
<h3 className="mb-5 text-lg font-semibold text-[#111827]">
|
||||
Thông tin chung
|
||||
</h3>
|
||||
|
||||
<div className="grid grid-cols-1 gap-5">
|
||||
{/* EXAMINATION SESSION */}
|
||||
<SelectForm
|
||||
label={
|
||||
<span className="text-[14px] font-medium text-[#374151]">
|
||||
Phiên khám
|
||||
<span className="text-red-500">*</span>
|
||||
</span>
|
||||
}
|
||||
placeholder="Chọn phiên khám"
|
||||
value={form.examinationSessionId}
|
||||
options={examinationSessionOptions}
|
||||
onSelect={(item: ConsultationItem) =>
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
examinationSessionId: item.id,
|
||||
}))
|
||||
}
|
||||
onClean={() =>
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
examinationSessionId: "",
|
||||
}))
|
||||
}
|
||||
getOptionLabel={(item: ConsultationItem) =>
|
||||
`${item.sessionCode} `
|
||||
}
|
||||
getOptionValue={(item: ConsultationItem) => item.id}
|
||||
/>
|
||||
|
||||
{/* IMAGE */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<UploadMultipleFile
|
||||
images={images}
|
||||
setImages={(files: UploadImageItem[]) => {
|
||||
setImages(files);
|
||||
|
||||
const imagePath =
|
||||
files?.[0]?.path ||
|
||||
files?.[0]?.url ||
|
||||
files?.[0]?.img ||
|
||||
"";
|
||||
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
prescriptionImg: imagePath,
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* NOTES */}
|
||||
<TextArea
|
||||
label="Ghi chú"
|
||||
name="notes"
|
||||
placeholder="Nhập ghi chú"
|
||||
value={form.notes}
|
||||
isBlur
|
||||
max={5000}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* MEDICINE LIST */}
|
||||
<div className="rounded-2xl border border-gray-200 p-5">
|
||||
<div className="mb-5 flex items-center justify-between">
|
||||
<h3 className="text-lg font-semibold text-[#111827]">
|
||||
Danh sách thuốc
|
||||
</h3>
|
||||
<div>
|
||||
<CustomButton
|
||||
type="button"
|
||||
variant="midnightBlue"
|
||||
icon={<Plus size={18} />}
|
||||
onClick={handleAddMedicine}
|
||||
>
|
||||
Thêm thuốc
|
||||
</CustomButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-6">
|
||||
{form.items.map((medicine, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="rounded-2xl border border-gray-200 p-5"
|
||||
>
|
||||
{/* TITLE */}
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<h4 className="font-semibold text-[#111827]">
|
||||
Thuốc #{index + 1}
|
||||
</h4>
|
||||
|
||||
{form.items.length > 1 && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleRemoveMedicine(index)}
|
||||
className="text-red-500 transition hover:text-red-600"
|
||||
>
|
||||
<Trash2 size={18} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* FORM */}
|
||||
<div className="grid grid-cols-2 gap-5">
|
||||
<InputForm
|
||||
label="Tên thuốc"
|
||||
name={`items.${index}.medicineName`}
|
||||
placeholder="Nhập tên thuốc"
|
||||
value={medicine.medicineName}
|
||||
type="text"
|
||||
onChangeValue={(value) =>
|
||||
handleChangeMedicine(index, "medicineName", String(value))
|
||||
}
|
||||
/>
|
||||
|
||||
<InputForm
|
||||
label="Liều lượng"
|
||||
name={`items.${index}.dosage`}
|
||||
placeholder="Ví dụ: 500mg"
|
||||
value={medicine.dosage}
|
||||
type="text"
|
||||
onChangeValue={(value) =>
|
||||
handleChangeMedicine(index, "dosage", String(value))
|
||||
}
|
||||
/>
|
||||
|
||||
<InputForm
|
||||
label="Tần suất"
|
||||
name={`items.${index}.frequency`}
|
||||
placeholder="Ví dụ: 2 lần/ngày"
|
||||
value={medicine.frequency}
|
||||
type="text"
|
||||
onChangeValue={(value) =>
|
||||
handleChangeMedicine(index, "frequency", String(value))
|
||||
}
|
||||
/>
|
||||
|
||||
<InputForm
|
||||
label="Thời gian dùng"
|
||||
name={`items.${index}.duration`}
|
||||
placeholder="Ví dụ: 7 ngày"
|
||||
value={medicine.duration}
|
||||
type="text"
|
||||
onChangeValue={(value) =>
|
||||
handleChangeMedicine(index, "duration", String(value))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-5">
|
||||
<TextArea
|
||||
label="Hướng dẫn sử dụng"
|
||||
name={`items.${index}.instructions`}
|
||||
placeholder="Nhập hướng dẫn sử dụng thuốc"
|
||||
value={medicine.instructions}
|
||||
isBlur
|
||||
max={5000}
|
||||
onChangeValue={(value) =>
|
||||
handleChangeMedicine(index, "instructions", String(value))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</FormCustom>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreatePrescription;
|
||||
Reference in New Issue
Block a user