418 lines
14 KiB
TypeScript
418 lines
14 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 moment from "moment";
|
|
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: "",
|
|
patientName: "",
|
|
sessionCode: "",
|
|
visitDate: "",
|
|
status: 1,
|
|
chiefComplaint: null,
|
|
symptomsText: null,
|
|
vitalSigns: null,
|
|
diagnosis: null,
|
|
treatmentPlan: null,
|
|
doctorNotes: null,
|
|
isGlassesDelivered: false,
|
|
createdBy: "",
|
|
createdByName: "",
|
|
isDeleted: false,
|
|
generalAttachmentUrls: [],
|
|
specialtyClinics: [],
|
|
aggregatedPrescriptionItems: [],
|
|
}
|
|
);
|
|
},
|
|
});
|
|
|
|
// Khai báo dữ liệu lấy từ Query phục vụ hiển thị
|
|
const generalData = consulDetailQuery.data;
|
|
|
|
// Tìm chuyên khoa hiện tại từ data trả về
|
|
const selectedSpecialtyClinic = generalData?.specialtyClinics?.find(
|
|
(item) => item.id === specialtyClinicId,
|
|
);
|
|
|
|
const isEyeClinic =
|
|
selectedSpecialtyClinic?.specialtyName?.toLowerCase().includes("mắt") ||
|
|
selectedSpecialtyClinic?.specialtyId?.toLowerCase().includes("mat");
|
|
|
|
useEffect(() => {
|
|
if (!generalData) return;
|
|
|
|
const specialtyClinic = generalData.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(
|
|
(specialtyClinic.attachmentUrls || []).map((url) => ({
|
|
file: null,
|
|
img: url,
|
|
path: url,
|
|
})),
|
|
);
|
|
}, [generalData, 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 kết quả khám chuyên khoa thành công!",
|
|
http: consultationServices.specialtyResults(
|
|
form.specialtyAssignmentId,
|
|
{
|
|
specialtyNotes: form.specialtyNotes,
|
|
diagnosis: form.diagnosis,
|
|
procedureNotes: form.procedureNotes,
|
|
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: generalData?.sessionCode,
|
|
});
|
|
|
|
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>
|
|
|
|
{/* THÔNG TIN HÀNH CHÍNH & PHIÊN KHÁM TỔNG QUÁT */}
|
|
<div className="rounded-xl border border-gray-200 bg-[#f8fafc] p-5">
|
|
<h3 className="mb-4 text-xs font-bold uppercase tracking-wider text-gray-500">
|
|
Thông tin tổng quan phiên khám
|
|
</h3>
|
|
|
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
|
<div>
|
|
<span className="text-xs font-medium text-gray-400 block uppercase">
|
|
Tên bệnh nhân
|
|
</span>
|
|
<span className="text-base font-bold text-gray-700 mt-0.5 block">
|
|
{generalData?.patientName || "Chưa có thông tin"}
|
|
</span>
|
|
</div>
|
|
<div>
|
|
<span className="text-xs font-medium text-gray-400 block uppercase">
|
|
Ngày sinh
|
|
</span>
|
|
<span className="text-base font-medium text-gray-700 mt-0.5 block">
|
|
{generalData?.patientDateOfBirth
|
|
? moment(generalData?.patientDateOfBirth).format("DD/MM/YYYY")
|
|
: "---"}
|
|
</span>
|
|
</div>
|
|
<div>
|
|
<span className="text-xs font-medium text-gray-400 block uppercase">
|
|
Mã phiên khám tổng quát
|
|
</span>
|
|
<span className="text-base font-semibold text-blue-600 mt-0.5 block">
|
|
#{generalData?.sessionCode || "Chưa có mã"}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* KHỐI THÔNG TIN KHÁM CHUYÊN KHOA */}
|
|
<div className="mt-2">
|
|
<h3 className="mb-4 text-xs font-bold uppercase tracking-wider text-gray-500">
|
|
Kết quả khám chuyên khoa đảm nhận
|
|
</h3>
|
|
|
|
<div className="mb-5">
|
|
<InputForm
|
|
label="Chuyên khoa tiếp 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
|
|
/>
|
|
</div>
|
|
|
|
<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="Chẩn đoán chuyên khoa"
|
|
name="diagnosis"
|
|
placeholder="Nhập chẩn đoán chuyên khoa"
|
|
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">
|
|
<div>
|
|
<label className="text-sm font-medium text-gray-700">
|
|
Bệnh nhân có 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 }))
|
|
}
|
|
/>
|
|
Có 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>
|
|
|
|
{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 chuyên khoa
|
|
</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;
|