metms_fe/src/components/page/consultation/UpdateSpecialtyClinicId/UpdateSpecialtyClinicId.tsx
2026-06-04 15:59:39 +07:00

401 lines
13 KiB
TypeScript

"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 UploadMultipleFile from "@/components/utils/UploadMultipleFile";
import { PATH } from "@/constant/config";
import { QUERY_KEY } from "@/constant/config/enum";
import { httpRequest } from "@/services";
import consultationServices, {
ConsultationDetail,
} from "@/services/consultationServices";
import uploadServices from "@/services/uploadServices";
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 [images, setImages] = useState<any[]>([]);
const [uploading, setUploading] = useState(false);
const [form, setForm] = useState<{
specialtyAssignmentId: string;
specialtyNotes: string;
diagnosis: string;
procedureNotes: string;
glassRequired: boolean;
glassType: string;
fileUrls: string[];
}>({
specialtyAssignmentId: "",
specialtyNotes: "",
diagnosis: "",
procedureNotes: "",
glassRequired: false,
glassType: "",
fileUrls: [],
});
/* =========================
GET DETAIL API
========================= */
const consulDetailQuery = useQuery<ConsultationDetail>({
queryKey: [QUERY_KEY.chi_tiet_phien_kham, consultationId],
enabled: !!consultationId,
queryFn: async () => {
const res = await httpRequest<ConsultationDetail>({
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,
attachmentUrls: [],
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 || "",
fileUrls: specialtyClinic.attachmentUrls || [],
});
setImages(
(consul.generalAttachmentUrls || []).map((url) => ({
file: null,
img: url,
path: url,
})),
);
}, [consulDetailQuery.data, specialtyClinicId]);
/* =========================
UPDATE MUTATION
========================= */
const specialtyResultsMutation = useMutation({
mutationFn: async (body: { paths: string[] }) => {
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 : "",
fileUrls: body.paths,
},
),
});
},
onSuccess(data) {
if (!data) return;
queryClient.invalidateQueries({
queryKey: [QUERY_KEY.table_list_consultation],
});
queryClient.invalidateQueries({
queryKey: [QUERY_KEY.chi_tiet_phien_kham, consultationId],
});
router.back();
router.push(PATH.CONSULTATION || "/consultation");
},
});
const handleSubmit = async () => {
try {
const currentImage = images
.filter((item) => !!item.img)
.map((item) => item.img);
const files = images
.filter((item) => !!item.file)
.map((item) => item.file);
if (files.length > 0) {
const uploadResult: any = await uploadServices.uploadImage({
Files: files,
SessionCode: consulDetailQuery.data?.sessionCode,
});
console.log("Cấu trúc log kiểm tra uploadResult:", uploadResult);
// CẬP NHẬT Ở ĐÂY: Lấy trực tiếp từ uploadResult hoặc bóc tách đúng tầng data
const uploadedUrls =
uploadResult?.fileUrls || uploadResult?.data?.fileUrls || [];
return specialtyResultsMutation.mutate({
paths: [...currentImage, ...uploadedUrls],
});
}
return specialtyResultsMutation.mutate({
paths: currentImage,
});
} catch (error) {
console.error(error);
}
};
const isLoading =
consulDetailQuery.isLoading || specialtyResultsMutation.isPending;
if (consulDetailQuery.isLoading) {
return (
<div className="flex items-center justify-center p-10 font-medium text-gray-500">
Đang tải dữ liệu phiên khám...
</div>
);
}
return (
<FormCustom form={form} setForm={setForm} onSubmit={handleSubmit}>
<div className="flex flex-col gap-6 rounded-2xl bg-white p-6 shadow-xl">
{/* HEADER */}
<div className="flex items-center justify-between border-b pb-5">
<div>
<h2 className="text-[24px] font-bold text-[#111827]">
Phiên khám chuyên khoa
</h2>
<p className="mt-1 text-sm text-[#697586]">
Cập nhật kết quả chi tiết quá trình khám bệnh của bệnh nhân
</p>
</div>
</div>
{/* CHUYÊN KHOA INFO */}
<InputForm
label="Chuyên khoa đảm nhận"
name="specialtyAssignmentId"
type="text"
value={
selectedSpecialtyClinic
? `${selectedSpecialtyClinic.specialtyName}`
: "Chưa có thông tin chuyên khoa"
}
placeholder="Thông tin chuyên khoa"
isRequired
readOnly
/>
{/* KHỐI THÔNG TIN KHÁM CHUNG (Luôn cố định 3 cột đẹp mắt) */}
<div className="mt-2">
<h3 className="mb-4 text-sm font-semibold uppercase tracking-wider text-gray-400">
Thông tin khám chung
</h3>
<GridColumn col={3}>
<InputForm
label="Ghi chú chuyên khoa"
name="specialtyNotes"
type="text"
value={form.specialtyNotes}
placeholder="Nhập ghi chú chuyên khoa"
isRequired
onChangeValue={(v) =>
setForm((prev) => ({ ...prev, specialtyNotes: String(v) }))
}
/>
<InputForm
type="text"
label="Chuẩn đoán chuyên khoa"
name="diagnosis"
placeholder="Nhập Chuẩn đoán"
isRequired
value={form.diagnosis}
onChangeValue={(v) =>
setForm((prev) => ({ ...prev, diagnosis: String(v) }))
}
/>
<InputForm
type="text"
label="Ghi chú thủ thuật / can thiệp"
name="procedureNotes"
placeholder="Nhập ghi chú thủ thuật / can thiệp"
value={form.procedureNotes}
isRequired
onChangeValue={(v) =>
setForm((prev) => ({ ...prev, procedureNotes: String(v) }))
}
/>
</GridColumn>
</div>
{/* KHỐI THÔNG TIN KHÁM MẮT (Chỉ hiển thị khi là khoa mắt) */}
{isEyeClinic && (
<div className="mt-2 rounded-xl border border-blue-100 bg-[#f8fafc] p-5 transition-all">
<h3 className="mb-4 text-sm font-bold uppercase tracking-wider text-blue-600">
Chỉ đnh thị lực (Khoa Mắt)
</h3>
<div className="flex flex-col gap-5">
{/* Radio Chọn Có/Không */}
<div>
<label className="text-sm font-medium text-gray-700">
Bệnh nhân chỉ đnh đeo kính không?
</label>
<div className="mt-2.5 flex items-center gap-8">
<label className="flex cursor-pointer items-center gap-2 text-sm font-medium text-gray-900">
<input
type="radio"
name="glassRequired"
className="h-4 w-4 border-gray-300 text-blue-600 focus:ring-blue-500"
checked={form.glassRequired === true}
onChange={() =>
setForm((prev) => ({ ...prev, glassRequired: true }))
}
/>
chỉ đnh
</label>
<label className="flex cursor-pointer items-center gap-2 text-sm font-medium text-gray-900">
<input
type="radio"
name="glassRequired"
className="h-4 w-4 border-gray-300 text-blue-600 focus:ring-blue-500"
checked={form.glassRequired === false}
onChange={() =>
setForm((prev) => ({ ...prev, glassRequired: false }))
}
/>
Không chỉ đnh
</label>
</div>
</div>
{/* TextArea Loại Kính (Trải rộng ra 100% dòng tạo cảm giác thoáng đãng) */}
{form.glassRequired && (
<div className="mt-2 animate-fadeIn ">
<TextArea
name="glassType"
placeholder="Nhập thông số chi tiết loại kính được chỉ định (Cận, viễn, loạn, độ kính...)"
label="Thông số / Loại kính chỉ định"
value={form.glassType}
isRequired
isBlur
max={5000}
/>
</div>
)}
</div>
</div>
)}
<div className="space-y-4">
<label className="text-sm font-medium">Tài liệu đính kèm</label>
<UploadMultipleFile images={images} setImages={setImages} />
{uploading && (
<p className="text-sm text-blue-500">Đang upload file...</p>
)}
</div>
{/* BUTTON ACTION */}
<div className="mt-4 flex items-center justify-end gap-3 border-t pt-5">
<CustomButton
type="button"
variant="grey"
rounded="md"
onClick={() => router.back()}
className="min-w-[120px]"
>
Hủy bỏ
</CustomButton>
<CustomButton
type="submit"
variant="midnightBlue"
rounded="md"
disabled={isLoading || !consultationId}
className="min-w-[140px]"
>
{specialtyResultsMutation.isPending
? "Đang cập nhật..."
: "Cập nhật kết quả"}
</CustomButton>
</div>
</div>
</FormCustom>
);
};
export default UpdateSpecialtyClinicId;