From 16af629c188f7da7fc914101d018b6ee34e4d30a Mon Sep 17 00:00:00 2001 From: Nikhila C <115739037+NikhilA8606@users.noreply.github.com> Date: Thu, 13 Feb 2025 16:58:49 +0530 Subject: [PATCH] Migrated Dialog and ConfirmDialog with shadCN component (#10510) --- public/locale/en.json | 11 + src/components/Common/AvatarEditModal.tsx | 460 +++++++++--------- src/components/Common/ConfirmDialog.tsx | 51 -- src/components/Common/Dialog.tsx | 87 ---- src/components/Common/FilePreviewDialog.tsx | 447 ++++++++--------- .../Facility/DuplicatePatientDialog.tsx | 206 ++++---- src/components/Facility/FacilityHome.tsx | 91 ++-- src/components/Files/CameraCaptureDialog.tsx | 283 +++++------ src/components/Patient/PatientHome.tsx | 24 - .../Patient/PatientRegistration.tsx | 7 +- src/components/Users/UserAvatar.tsx | 2 +- src/components/Users/UserDeleteDialog.tsx | 53 +- src/components/Users/UserSummary.tsx | 2 + src/hooks/useFileManager.tsx | 393 +++++++-------- src/hooks/useFileUpload.tsx | 4 +- 15 files changed, 1033 insertions(+), 1088 deletions(-) delete mode 100644 src/components/Common/ConfirmDialog.tsx delete mode 100644 src/components/Common/Dialog.tsx diff --git a/public/locale/en.json b/public/locale/en.json index 131cb515b31..6b6d794ed53 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -383,6 +383,7 @@ "approved_by_district_covid_control_room": "Approved by District COVID Control Room", "approving_facility": "Name of Approving Facility", "archive": "Archive", + "archive_file": "Archive File", "archived": "Archived", "archived_at": "Archived at", "archived_by": "Archived by", @@ -394,6 +395,7 @@ "are_you_sure_want_to_delete": "Are you sure you want to delete {{name}}?", "are_you_sure_want_to_delete_this_record": "Are you sure want to delete this record?", "are_you_sure_want_to_remove": "Are you sure you want to remove {{name}} from the patient? This action cannot be undone", + "are_you_sure_you_want_to_delete_user": "Are you sure you want to delete this user?", "ari": "ARI - Acute Respiratory illness", "arrived": "Arrived", "as_needed_prn": "As Needed / PRN", @@ -696,8 +698,10 @@ "delete_account_btn": "Yes, delete this account", "delete_account_note": "Deleting this account will remove all associated data and cannot be undone.", "delete_facility": "Delete Facility", + "delete_facility_confirmation": "Are you sure you want to delete {{name}}? This action cannot be undone.", "delete_item": "Delete {{name}}", "delete_record": "Delete Record", + "delete_user": "Delete User", "deleting": "Deleting...", "demography": "Demography", "denied_on": "Denied On", @@ -994,6 +998,7 @@ "facility_consent_requests_page_title": "Patient Consent List", "facility_count_one": "{{count}} Facility", "facility_count_other": "{{count}} Facilities ", + "facility_deleted_successfully": "{{name}} has been deleted successfully.", "facility_district_name": "Facility/District Name", "facility_district_pincode": "Facility/District/Pincode", "facility_for_care_support": "Facility for Care Support", @@ -1373,6 +1378,8 @@ "new_password_same_as_old": "Your new password must not match the old password ", "new_password_validation": "New password is not valid.", "new_session": "New Session", + "next": "Next", + "next_file": "Next file", "next_month": "Next month", "next_sessions": "Next Sessions", "next_week_short": "Next wk", @@ -1695,6 +1702,7 @@ "preset_name_placeholder": "Specify an identifiable name for the new preset", "preset_updated": "Preset updated", "prev_sessions": "Prev Sessions", + "previous": "Previous", "primary_ph_no": "Primary Ph No.", "primary_phone_no": "Primary ph. no.", "principal": "Principal", @@ -1857,6 +1865,8 @@ "right": "Right", "role": "Role", "room_apt": "Room/Apt & Time", + "rotate_left": "Rotate Left", + "rotate_right": "Rotate Right", "route": "Route", "routine": "Routine", "sample_collection_date": "Sample Collection Date", @@ -2053,6 +2063,7 @@ "something_wrong": "Something went wrong! Try again later!", "sort_by": "Sort By", "source": "Source", + "source_file": "Source Files", "spdx_sbom_version": "SPDX SBOM Version", "spokes": "Spoke Facilities", "srf_id": "SRF ID", diff --git a/src/components/Common/AvatarEditModal.tsx b/src/components/Common/AvatarEditModal.tsx index 31d0f772a60..3c278ea80b0 100644 --- a/src/components/Common/AvatarEditModal.tsx +++ b/src/components/Common/AvatarEditModal.tsx @@ -12,18 +12,22 @@ import { toast } from "sonner"; import CareIcon from "@/CAREUI/icons/CareIcon"; import { Button } from "@/components/ui/button"; - -import DialogModal from "@/components/Common/Dialog"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; import useDragAndDrop from "@/hooks/useDragAndDrop"; interface Props { title: string; open: boolean; + onOpenChange: (open: boolean) => void; imageUrl?: string; handleUpload: (file: File, onError: () => void) => Promise; handleDelete: (onError: () => void) => Promise; - onClose?: () => void; hint?: React.ReactNode; } @@ -48,10 +52,10 @@ type IVideoConstraint = const AvatarEditModal = ({ title, open, + onOpenChange, imageUrl, handleUpload, handleDelete, - onClose, hint, }: Props) => { const [isProcessing, setIsProcessing] = useState(false); @@ -91,7 +95,7 @@ const AvatarEditModal = ({ setPreview(undefined); setIsProcessing(false); setSelectedFile(undefined); - onClose?.(); + onOpenChange(false); }; useEffect(() => { @@ -179,246 +183,246 @@ const AvatarEditModal = ({ const hintMessage = hint || defaultHint; return ( - - - - {!isCameraOpen ? ( - <> - {preview || imageUrl ? ( - <> - - - - - {hintMessage} - - > - ) : ( - - + + + {title} + + + + {!isCameraOpen ? ( + <> + {preview || imageUrl ? ( + <> + + + + + {hintMessage} + + > + ) : ( + - - - - {dragProps.fileDropError !== "" - ? dragProps.fileDropError - : `${t("drag_drop_image_to_upload")}`} - - - {t("no_image_found")}. {hintMessage} - - - )} + + + + + {dragProps.fileDropError !== "" + ? dragProps.fileDropError + : `${t("drag_drop_image_to_upload")}`} + + + {t("no_image_found")}. {hintMessage} + + + )} - - + + + + + + {t("upload_an_image")} + + + + { + setConstraint(() => VideoConstraints.user); + setIsCameraOpen(true); + }} > - - - {t("upload_an_image")} - - + {`${t("open_camera")}`} - - { - setConstraint(() => VideoConstraints.user); - setIsCameraOpen(true); - }} - > - {`${t("open")} ${t("camera")}`} - - - { - e.stopPropagation(); - closeModal(); - dragProps.setFileDropError(""); - }} - disabled={isProcessing} - > - {t("cancel")} - - {imageUrl && ( + { + e.stopPropagation(); + closeModal(); + dragProps.setFileDropError(""); + }} disabled={isProcessing} > - {t("delete")} + {t("cancel")} - )} - - {isProcessing ? ( - - ) : ( - - )} - - {isProcessing ? `${t("uploading")}...` : `${t("save")}`} - - - - > - ) : ( - <> - - {!previewImage ? ( - <> - { - setIsCameraOpen(false); - toast.warning(t("camera_permission_denied")); - }} - /> - > - ) : ( - <> - - > - )} - - {/* buttons for mobile screens */} - - {!previewImage ? ( - <> - - - {`${t("switch")} ${t("camera")}`} - - { - captureImage(); - }} - > - - {t("capture")} - - > - ) : ( - <> + {imageUrl && ( { - setPreviewImage(null); - }} - > - {t("retake")} - - - {isCaptureImgBeingUploaded ? ( - <> - - {`${t("submitting")}...`} - > - ) : ( - <> {t("submit")}> - )} + {t("delete")} - > - )} - - { - setPreviewImage(null); - setIsCameraOpen(false); - webRef.current.stopCamera(); - }} - disabled={isProcessing} - > - {t("close")} - - - > - )} + )} + + {isProcessing ? ( + + ) : ( + + )} + + {isProcessing ? `${t("uploading")}...` : `${t("save")}`} + + + + > + ) : ( + <> + + {!previewImage ? ( + <> + { + setIsCameraOpen(false); + toast.warning(t("camera_permission_denied")); + }} + /> + > + ) : ( + <> + + > + )} + + {/* buttons for mobile screens */} + + {!previewImage ? ( + <> + + + {`${t("switch")} ${t("camera")}`} + + { + captureImage(); + }} + > + + {t("capture")} + + > + ) : ( + <> + { + setPreviewImage(null); + }} + > + {t("retake")} + + + {isCaptureImgBeingUploaded ? ( + <> + + {`${t("submitting")}...`} + > + ) : ( + <> {t("submit")}> + )} + + > + )} + + { + setPreviewImage(null); + setIsCameraOpen(false); + webRef.current.stopCamera(); + }} + disabled={isProcessing} + > + {t("close")} + + + > + )} + - - + + ); }; diff --git a/src/components/Common/ConfirmDialog.tsx b/src/components/Common/ConfirmDialog.tsx deleted file mode 100644 index 78cebe23f8e..00000000000 --- a/src/components/Common/ConfirmDialog.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { Button, ButtonVariant } from "@/components/ui/button"; - -import DialogModal from "@/components/Common/Dialog"; - -type ConfirmDialogProps = { - className?: string; - title: React.ReactNode; - description?: React.ReactNode; - disabled?: boolean; - show: boolean; - action: React.ReactNode; - variant?: ButtonVariant; - onClose: () => void; - onConfirm: () => void; - children?: React.ReactNode; - cancelLabel?: string; - name?: string; -}; - -const ConfirmDialog = ({ - disabled, - variant, - action, - onConfirm, - cancelLabel, - children, - name, - ...props -}: ConfirmDialogProps) => { - return ( - - {children} - - - {cancelLabel} - - - {action} - - - - ); -}; - -export default ConfirmDialog; diff --git a/src/components/Common/Dialog.tsx b/src/components/Common/Dialog.tsx deleted file mode 100644 index a67981dd3f8..00000000000 --- a/src/components/Common/Dialog.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { - Dialog, - DialogPanel, - DialogTitle, - Transition, - TransitionChild, -} from "@headlessui/react"; - -import { classNames } from "@/Utils/utils"; - -type DialogProps = { - title: React.ReactNode; - description?: React.ReactNode; - show: boolean; - onClose: () => void; - children: React.ReactNode; - className?: string; - titleAction?: React.ReactNode; - fixedWidth?: boolean; -}; - -const DialogModal = (props: DialogProps) => { - const { - title, - description, - show, - onClose, - children, - className, - fixedWidth = true, - } = props; - return ( - - - - - - - - - - - - - - {title} - - {description} - - - {props.titleAction} - - {children} - - - - - - - - ); -}; - -export default DialogModal; diff --git a/src/components/Common/FilePreviewDialog.tsx b/src/components/Common/FilePreviewDialog.tsx index fbcaf9ae69a..cb556b84aae 100644 --- a/src/components/Common/FilePreviewDialog.tsx +++ b/src/components/Common/FilePreviewDialog.tsx @@ -16,9 +16,14 @@ import useKeyboardShortcut from "use-keyboard-shortcut"; import CareIcon, { IconName } from "@/CAREUI/icons/CareIcon"; import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; import CircularProgress from "@/components/Common/CircularProgress"; -import DialogModal from "@/components/Common/Dialog"; import { StateInterface } from "@/components/Files/FileUpload"; import { FileUploadModel } from "@/components/Patient/models"; @@ -148,11 +153,11 @@ const FilePreviewDialog = (props: FilePreviewProps) => { }; function getRotationClass(rotation: number) { - let normalizedRotation = ((rotation % 360) + 360) % 360; // Normalize rotation to be within [0, 360) + let normalizedRotation = ((rotation % 360) + 360) % 360; if (normalizedRotation > 180) { - normalizedRotation -= 360; // Adjust to be within [-180, 180) + normalizedRotation -= 360; } - return normalizedRotation === -90 // Special case for -90 rotation since tailwind doesn't support 270deg + return normalizedRotation === -90 ? "-rotate-90" : `rotate-${normalizedRotation}`; } @@ -164,232 +169,236 @@ const FilePreviewDialog = (props: FilePreviewProps) => { ); return ( - { - handleClose(); - }} - title={{t("file_preview")}} - show={show} - > - {fileUrl ? ( - <> - - - - - - - {fileNameTooltip} - - - - - {fileName} + !open && handleClose()}> + + + + {t("file_preview")} + + + + {fileUrl ? ( + <> + + + + + + + {fileNameTooltip} + + + + + {fileName} + + + + + {uploadedFiles && + uploadedFiles[index] && + uploadedFiles[index].created_date && ( + + {t("created_on")}{" "} + {new Date( + uploadedFiles[index].created_date!, + ).toLocaleString("en-US", { + dateStyle: "long", + timeStyle: "short", + })} - - - - {uploadedFiles && - uploadedFiles[index] && - uploadedFiles[index].created_date && ( - - {t("created_on")}{" "} - {new Date( - uploadedFiles[index].created_date!, - ).toLocaleString("en-US", { - dateStyle: "long", - timeStyle: "short", - })} - + )} + + + {downloadURL && downloadURL.length > 0 && ( + + + + {t("download")} + + )} + + {t("close")} + + - - {downloadURL && downloadURL.length > 0 && ( - - - - {t("download")} - + + {uploadedFiles && uploadedFiles.length > 1 && ( + handleNext(index - 1)} + disabled={index <= 0} + aria-label="Previous file" + > + )} - - {t("close")} - - - - - {uploadedFiles && uploadedFiles.length > 1 && ( - handleNext(index - 1)} - disabled={index <= 0} - aria-label="Previous file" - > - - - )} - - {file_state.isImage ? ( - - ) : file_state.extension === "pdf" ? ( - }> - { - setPage(1); - setNumPages(numPages); - }} - pageNumber={page} - scale={scale} + + {file_state.isImage ? ( + - - ) : previewExtensions.includes(file_state.extension) ? ( - - ) : ( - - }> + { + setPage(1); + setNumPages(numPages); + }} + pageNumber={page} + scale={scale} + /> + + ) : previewExtensions.includes(file_state.extension) ? ( + - {t("file_preview_not_supported")} - - )} - + ) : ( + + + {t("file_preview_not_supported")} + + )} + - {uploadedFiles && uploadedFiles.length > 1 && ( - handleNext(index + 1)} - disabled={index >= uploadedFiles.length - 1} - aria-label="Next file" - > - - - )} - - - - {file_state.isImage && ( - <> - {[ - [ - t("Zoom In"), - "l-search-plus", - handleZoomIn, - file_state.zoom === zoom_values.length, - ], - [ - `${25 * file_state.zoom}%`, - false, - () => { - setFileState({ ...file_state, zoom: 4 }); - }, - false, - ], - [ - t("Zoom Out"), - "l-search-minus", - handleZoomOut, - file_state.zoom === 1, - ], - [ - t("Rotate Left"), - "l-corner-up-left", - () => handleRotate(-90), - false, - ], - [ - t("Rotate Right"), - "l-corner-up-right", - () => handleRotate(90), - false, - ], - ].map((button, index) => ( - void} - className="z-50 rounded bg-white/60 px-4 py-2 text-black backdrop-blur transition hover:bg-white/70" - disabled={button[3] as boolean} - > - {button[1] && ( - - )} - {button[0] as string} - - ))} - > - )} - {file_state.extension === "pdf" && ( - <> - {[ - ["Zoom In", "l-search-plus", handleZoomIn, scale >= 2], - [`${Math.round(scale * 100)}%`, false, () => {}, false], - ["Zoom Out", "l-search-minus", handleZoomOut, scale <= 0.5], - [ - "Previous", - "l-arrow-left", - () => setPage((prev) => prev - 1), - page === 1, - ], - [`${page}/${numPages}`, false, () => ({}), false], - [ - "Next", - "l-arrow-right", - () => setPage((prev) => prev + 1), - page === numPages, - ], - ].map((button, index) => ( - void} - className="z-50 rounded bg-white/60 px-4 py-2 text-black backdrop-blur transition hover:bg-white/70" - disabled={button[3] as boolean} - > - {button[1] && ( - - )} - {button[0] as string} - - ))} - > + {uploadedFiles && uploadedFiles.length > 1 && ( + handleNext(index + 1)} + disabled={index >= uploadedFiles.length - 1} + aria-label={t("next_file")} + > + + )} + + + {file_state.isImage && ( + <> + {[ + [ + t("zoom_in"), + "l-search-plus", + handleZoomIn, + file_state.zoom === zoom_values.length, + ], + [ + `${25 * file_state.zoom}%`, + false, + () => { + setFileState({ ...file_state, zoom: 4 }); + }, + false, + ], + [ + t("zoom_out"), + "l-search-minus", + handleZoomOut, + file_state.zoom === 1, + ], + [ + t("rotate_left"), + "l-corner-up-left", + () => handleRotate(-90), + false, + ], + [ + t("rotate_right"), + "l-corner-up-right", + () => handleRotate(90), + false, + ], + ].map((button, index) => ( + void} + className="z-50 rounded bg-white/60 px-4 py-2 text-black backdrop-blur transition hover:bg-white/70" + disabled={button[3] as boolean} + > + {button[1] && ( + + )} + {button[0] as string} + + ))} + > + )} + {file_state.extension === "pdf" && ( + <> + {[ + [t("zoom_in"), "l-search-plus", handleZoomIn, scale >= 2], + [`${Math.round(scale * 100)}%`, false, () => {}, false], + [ + t("zoom_out"), + "l-search-minus", + handleZoomOut, + scale <= 0.5, + ], + [ + t("previous"), + "l-arrow-left", + () => setPage((prev) => prev - 1), + page === 1, + ], + [`${page}/${numPages}`, false, () => ({}), false], + [ + t("next"), + "l-arrow-right", + () => setPage((prev) => prev + 1), + page === numPages, + ], + ].map((button, index) => ( + void} + className="z-50 rounded bg-white/60 px-4 py-2 text-black backdrop-blur transition hover:bg-white/70" + disabled={button[3] as boolean} + > + {button[1] && ( + + )} + {button[0] as string} + + ))} + > + )} + + + > + ) : ( + + - > - ) : ( - - - - )} - + )} + + ); }; diff --git a/src/components/Facility/DuplicatePatientDialog.tsx b/src/components/Facility/DuplicatePatientDialog.tsx index 2e795092042..beac625f852 100644 --- a/src/components/Facility/DuplicatePatientDialog.tsx +++ b/src/components/Facility/DuplicatePatientDialog.tsx @@ -4,6 +4,13 @@ import { useTranslation } from "react-i18next"; import CareIcon from "@/CAREUI/icons/CareIcon"; import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; import { Table, TableBody, @@ -13,120 +20,125 @@ import { TableRow, } from "@/components/ui/table"; -import DialogModal from "@/components/Common/Dialog"; - import { PartialPatientModel } from "@/types/emr/newPatient"; interface Props { + open: boolean; + onOpenChange: (open: boolean) => void; patientList: Array; handleOk: (action: string) => void; - handleCancel: () => void; } const DuplicatePatientDialog = (props: Props) => { const { t } = useTranslation(); - const { patientList, handleOk, handleCancel } = props; + const { open, onOpenChange, patientList, handleOk } = props; const [action, setAction] = useState(""); return ( - - - - - {t("patient_records_found_description")}( - {patientList[0].phone_number}) - - - - - - - - {[`${t("patient_name")} / ID`, t("gender")].map( - (heading, i) => ( - {heading} - ), - )} - - - - {patientList.map((patient, i) => { - return ( - - - - {patient.name} - - - ID : {patient.id} - - - {patient.gender} - - ); - })} - - + + + + {t("patient_records_found")} + + + + + {t("patient_records_found_description")}( + {patientList[0].phone_number}) + - - - - - setAction(e.target.value)} - /> - {t("duplicate_patient_record_confirmation")} - + + + + + + {[`${t("patient_name")} / ID`, t("gender")].map( + (heading, i) => ( + {heading} + ), + )} + + + + {patientList.map((patient, i) => { + return ( + + + + {patient.name} + + + ID : {patient.id} + + + {patient.gender} + + ); + })} + + + + + + + setAction(e.target.value)} + /> + {t("duplicate_patient_record_confirmation")} + + - - - setAction(e.target.value)} - /> - {t("duplicate_patient_record_rejection")} - - + + + setAction(e.target.value)} + /> + {t("duplicate_patient_record_rejection")} + + - {t("duplicate_patient_record_birth_unknown")} + {t("duplicate_patient_record_birth_unknown")} + - - - - - {t("close")} - - handleOk(action)} - disabled={!action} - variant={"primary"} - > - - {t("continue")} - - - + + + onOpenChange(false)} + className="gap-1" + variant={"secondary"} + > + + {t("close")} + + handleOk(action)} + disabled={!action} + variant={"primary"} + > + + {t("continue")} + + + + + ); }; diff --git a/src/components/Facility/FacilityHome.tsx b/src/components/Facility/FacilityHome.tsx index 547d1b209d6..195e4d700bf 100644 --- a/src/components/Facility/FacilityHome.tsx +++ b/src/components/Facility/FacilityHome.tsx @@ -7,14 +7,26 @@ import { } from "@radix-ui/react-tooltip"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useMutation } from "@tanstack/react-query"; -import { Hospital, MapPin, MoreVertical, Settings } from "lucide-react"; +import { Edit2, Hospital, MapPin, MoreVertical, Settings } from "lucide-react"; import { navigate } from "raviger"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; +import { cn } from "@/lib/utils"; + +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; +import { Button, buttonVariants } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { DropdownMenu, @@ -26,7 +38,6 @@ import { Markdown } from "@/components/ui/markdown"; import { Avatar } from "@/components/Common/Avatar"; import AvatarEditModal from "@/components/Common/AvatarEditModal"; -import ConfirmDialog from "@/components/Common/ConfirmDialog"; import ContactLink from "@/components/Common/ContactLink"; import Loading from "@/components/Common/Loading"; @@ -96,7 +107,6 @@ const renderGeoOrganizations = (geoOrg: Organization) => { export const FacilityHome = ({ facilityId }: Props) => { const { t } = useTranslation(); - const [openDeleteDialog, setOpenDeleteDialog] = useState(false); const [editCoverImage, setEditCoverImage] = useState(false); const queryClient = useQueryClient(); @@ -106,13 +116,14 @@ export const FacilityHome = ({ facilityId }: Props) => { pathParams: { id: facilityId }, }), }); - const { mutate: deleteFacility } = useMutation({ + + const { mutate: deleteFacility, isPending: isDeleting } = useMutation({ mutationFn: mutate(routes.deleteFacility, { pathParams: { id: facilityId }, }), onSuccess: () => { toast.success( - t("entity_deleted_successfully", { name: facilityData?.name }), + t("facility_deleted_successfully", { name: facilityData?.name }), ); navigate("/facility"); }, @@ -184,26 +195,13 @@ export const FacilityHome = ({ facilityId }: Props) => { return ( - - {t("are_you_sure_want_to_delete", { name: facilityData?.name })} - - } - action="Delete" - variant="destructive" - show={openDeleteDialog} - onClose={() => setOpenDeleteDialog(false)} - onConfirm={() => deleteFacility()} - /> setEditCoverImage(false)} + onOpenChange={(open) => setEditCoverImage(open)} hint={coverImageHint} /> @@ -267,7 +265,7 @@ export const FacilityHome = ({ facilityId }: Props) => { className="cursor-pointer" onClick={() => setEditCoverImage(true)} > - + {t("edit_cover_photo")} )} @@ -276,7 +274,7 @@ export const FacilityHome = ({ facilityId }: Props) => { facilityId={facilityId} trigger={ { e.preventDefault(); }} @@ -286,16 +284,45 @@ export const FacilityHome = ({ facilityId }: Props) => { } /> - {/* TODO: get permissions from backend */} - {/* {hasPermissionToDeleteFacility && ( - setOpenDeleteDialog(true)} - > - - {t("delete_facility")} - - )} */} + + {/* TODO: add delete facility */} + {/* + e.preventDefault()} + > + + {t("delete_facility")} + + */} + + + + {t("delete_facility")} + + + {t("delete_facility_confirmation", { + name: facilityData?.name, + })} + + + + + {t("cancel")} + + deleteFacility()} + className={cn( + buttonVariants({ variant: "destructive" }), + )} + disabled={isDeleting} + > + {isDeleting ? t("deleting") : t("delete")} + + + + + void; + open: boolean; + onOpenChange: (open: boolean) => void; onCapture: (file: File, fileName: string) => void; onResetCapture: () => void; setPreview?: (isPreview: boolean) => void; } export default function CameraCaptureDialog(props: CameraCaptureDialogProps) { - const { show, onHide, onCapture, onResetCapture, setPreview } = props; + const { open, onOpenChange, onCapture, onResetCapture, setPreview } = props; const isLaptopScreen = useBreakpoints({ lg: true, default: false }); const [cameraFacingMode, setCameraFacingMode] = useState( @@ -34,8 +38,9 @@ export default function CameraCaptureDialog(props: CameraCaptureDialogProps) { height: { ideal: 2160 }, facingMode: cameraFacingMode, }; + useEffect(() => { - if (!show) return; + if (!open) return; let stream: MediaStream | null = null; navigator.mediaDevices @@ -45,7 +50,7 @@ export default function CameraCaptureDialog(props: CameraCaptureDialogProps) { }) .catch(() => { toast.warning(t("camera_permission_denied")); - onHide(); + onOpenChange(false); }); return () => { @@ -55,7 +60,7 @@ export default function CameraCaptureDialog(props: CameraCaptureDialogProps) { }); } }; - }, [show, cameraFacingMode, onHide]); + }, [open, cameraFacingMode, onOpenChange]); const handleSwitchCamera = useCallback(async () => { const devices = await navigator.mediaDevices.enumerateDevices(); @@ -87,130 +92,62 @@ export default function CameraCaptureDialog(props: CameraCaptureDialogProps) { }; return ( - - - - - - {t("camera")} - - - } - className="max-w-2xl" - onClose={onHide} - > - - {!previewImage ? ( - - - - ) : ( - - - - )} - + + + + + + + + + + + {t("camera")} + + + + - {/* buttons for mobile screens */} - - - {!previewImage ? ( - - {t("switch")} - - ) : ( - <>> - )} - {!previewImage ? ( - <> - - { - captureImage(); - setPreview?.(true); - }} - className="m-2" - > - {t("capture")} - - - > + + + ) : ( - <> - - { - setPreviewImage(null); - onResetCapture(); - setPreview?.(false); - }} - className="m-2" - > - {t("retake")} - - { - setPreviewImage(null); - onHide(); - setPreview?.(false); - }} - className="m-2" - > - {t("submit")} - - - > + + + )} - - { - setPreviewImage(null); - onResetCapture(); - onHide(); - }} - className="m-2" - > - {t("close")} - - - - {/* buttons for laptop screens */} - - - - - {`${t("switch")} ${t("camera")}`} - - - + {/* buttons for mobile and tablet screens */} + + + {!previewImage ? ( + + {t("switch")} + + ) : ( + <>> + )} + {!previewImage ? ( <> @@ -221,8 +158,8 @@ export default function CameraCaptureDialog(props: CameraCaptureDialogProps) { captureImage(); setPreview?.(true); }} + className="m-2" > - {t("capture")} @@ -237,16 +174,18 @@ export default function CameraCaptureDialog(props: CameraCaptureDialogProps) { onResetCapture(); setPreview?.(false); }} + className="m-2" > {t("retake")} { - onHide(); setPreviewImage(null); + onOpenChange(false); setPreview?.(false); }} + className="m-2" > {t("submit")} @@ -254,20 +193,82 @@ export default function CameraCaptureDialog(props: CameraCaptureDialogProps) { > )} - - { - setPreviewImage(null); - onResetCapture(); - onHide(); - setPreview?.(false); - }} - > - {`${t("close")} ${t("camera")}`} - + + { + setPreviewImage(null); + onResetCapture(); + onOpenChange(false); + }} + className="m-2" + > + {t("close")} + + + + + {/* buttons for laptop screens */} + + + + {!previewImage ? ( + <> + + { + captureImage(); + setPreview?.(true); + }} + > + + {t("capture")} + + + > + ) : ( + <> + + { + setPreviewImage(null); + onResetCapture(); + setPreview?.(false); + }} + > + {t("retake")} + + { + onOpenChange(false); + setPreviewImage(null); + setPreview?.(false); + }} + > + {t("submit")} + + + > + )} + + + { + setPreviewImage(null); + onResetCapture(); + onOpenChange(false); + setPreview?.(false); + }} + > + {`${t("close")} ${t("camera")}`} + + - - + + ); } diff --git a/src/components/Patient/PatientHome.tsx b/src/components/Patient/PatientHome.tsx index 0807023978a..69df69c4143 100644 --- a/src/components/Patient/PatientHome.tsx +++ b/src/components/Patient/PatientHome.tsx @@ -247,30 +247,6 @@ export const PatientHome = (props: { - - {/* setOpenAssignVolunteerDialog(false)} - description={ - - setAssignedVolunteer(user.value)} - userType={"Volunteer"} - name={"assign_volunteer"} - error={errors.assignedVolunteer} - /> - - } - action={ - assignedVolunteer || !patientData.assigned_to - ? t("assign") - : t("unassign") - } - onConfirm={handleAssignedVolunteer} - /> */} ); }; diff --git a/src/components/Patient/PatientRegistration.tsx b/src/components/Patient/PatientRegistration.tsx index 8557b4fab89..64ded99c691 100644 --- a/src/components/Patient/PatientRegistration.tsx +++ b/src/components/Patient/PatientRegistration.tsx @@ -747,10 +747,13 @@ export default function PatientRegistration( {showDuplicate && ( { - handleDialogClose("close"); + onOpenChange={(open) => { + if (!open) { + handleDialogClose("close"); + } }} /> )} diff --git a/src/components/Users/UserAvatar.tsx b/src/components/Users/UserAvatar.tsx index 1d16f4ba8bd..fd288000919 100644 --- a/src/components/Users/UserAvatar.tsx +++ b/src/components/Users/UserAvatar.tsx @@ -97,7 +97,7 @@ export default function UserAvatar({ username }: { username: string }) { imageUrl={userData?.profile_picture_url} handleUpload={handleAvatarUpload} handleDelete={handleAvatarDelete} - onClose={() => setEditAvatar(false)} + onOpenChange={(open) => setEditAvatar(open)} /> diff --git a/src/components/Users/UserDeleteDialog.tsx b/src/components/Users/UserDeleteDialog.tsx index 5a2becefe3d..a088c9f2512 100644 --- a/src/components/Users/UserDeleteDialog.tsx +++ b/src/components/Users/UserDeleteDialog.tsx @@ -1,26 +1,51 @@ -import ConfirmDialog from "@/components/Common/ConfirmDialog"; +import { useTranslation } from "react-i18next"; + +import { cn } from "@/lib/utils"; + +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; +import { buttonVariants } from "@/components/ui/button"; interface ConfirmDialogProps { name: string; handleCancel: () => void; handleOk: () => void; + show: boolean; } const UserDeleteDialog = (props: ConfirmDialogProps) => { + const { t } = useTranslation(); return ( - - Are you sure you want to delete user {props.name} ? - - } - action="Delete" - variant="destructive" - show - onConfirm={props.handleOk} - onClose={props.handleCancel} - /> + + + + {t("delete_user")} + + {t("are_you_sure_you_want_to_delete_user")} + {props.name}? + + + + + {t("cancel")} + + + {t("delete")} + + + + ); }; diff --git a/src/components/Users/UserSummary.tsx b/src/components/Users/UserSummary.tsx index 10c6dca9d42..a28425e6eef 100644 --- a/src/components/Users/UserSummary.tsx +++ b/src/components/Users/UserSummary.tsx @@ -89,6 +89,7 @@ export default function UserSummaryTab({ userData }: { userData?: UserBase }) { <> {showDeleteDialog && ( { @@ -96,6 +97,7 @@ export default function UserSummaryTab({ userData }: { userData?: UserBase }) { }} /> )} + - - - setArchiveDialogueOpen(null)} + > + + + + + + + + + {t("archive_file")} + + {t("this_action_is_irreversible")} + + + + + + + { + event.preventDefault(); + handleFileArchive(archiveDialogueOpen); + }} + className="mx-2 my-4 flex w-full flex-col" + > + + + }} + /> + + setArchiveReason(e.target.value)} + className={cn( + archiveReasonError && + "border-red-500 focus-visible:ring-red-500", + )} /> + {archiveReasonError && ( + {archiveReasonError} + )} - - Archive File - - {t("this_action_is_irreversible")} - + + setArchiveDialogueOpen(null)} + > + {t("cancel")} + + + {t("proceed")} + - + + + + setArchiveDialogueOpen(null)} + onOpenChange={() => setArchiveDialogueOpen(null)} > - { - event.preventDefault(); - handleFileArchive(archiveDialogueOpen); - }} - className="mx-2 my-4 flex w-full flex-col" - > - - - }} - /> - - setArchiveReason(e.target.value)} - className={cn( - archiveReasonError && - "border-red-500 focus-visible:ring-red-500", - )} - /> - {archiveReasonError && ( - {archiveReasonError} - )} + + + + {archiveDialogueOpen?.name} {t("archived")} + + + + + {t("this_file_has_been_archived")} + + + {[ + { + label: "File Name", + content: archiveDialogueOpen?.name, + icon: "l-file", + }, + { + label: "Uploaded By", + content: archiveDialogueOpen?.uploaded_by?.username, + icon: "l-user", + }, + { + label: "Uploaded On", + content: formatDateTime(archiveDialogueOpen?.created_date), + icon: "l-clock", + }, + { + label: "Archive Reason", + content: archiveDialogueOpen?.archive_reason, + icon: "l-archive", + }, + { + label: "Archived By", + content: archiveDialogueOpen?.archived_by?.username, + icon: "l-user", + }, + { + label: "Archived On", + content: formatDateTime(archiveDialogueOpen?.archived_datetime), + icon: "l-clock", + }, + ].map((item, index) => ( + + + + + + + {item.label} + + + {item.content} + + + + ))} - + {t("cancel")} - - {t("proceed")} - - - - - {archiveDialogueOpen?.name} (Archived) - - } - fixedWidth={false} - className="md:w-[700px]" - onClose={() => setArchiveDialogueOpen(null)} + + + setEditDialogueOpen(null)} > - - - {t("this_file_has_been_archived")} - - - {[ - { - label: "File Name", - content: archiveDialogueOpen?.name, - icon: "l-file", - }, - { - label: "Uploaded By", - content: archiveDialogueOpen?.uploaded_by?.username, - icon: "l-user", - }, - { - label: "Uploaded On", - content: formatDateTime(archiveDialogueOpen?.created_date), - icon: "l-clock", - }, - { - label: "Archive Reason", - content: archiveDialogueOpen?.archive_reason, - icon: "l-archive", - }, - { - label: "Archived By", - content: archiveDialogueOpen?.archived_by?.username, - icon: "l-user", - }, - { - label: "Archived On", - content: formatDateTime(archiveDialogueOpen?.archived_datetime), - icon: "l-clock", - }, - ].map((item, index) => ( - - - - - - - {item.label} + + + + + + - - {item.content} + + {t("rename_file")} - - ))} - - - setArchiveDialogueOpen(null)} + + + { + event.preventDefault(); + setEditing(true); + if (editDialogueOpen) partialupdateFileName(editDialogueOpen); + }} + className="flex w-full flex-col" > - {t("cancel")} - - - - - - + {t("enter_the_file_name")} + { + setEditDialogueOpen({ + ...editDialogueOpen, + name: e.target.value, + }); + }} /> + {editError && {editError}} - - {t("rename_file")} + + setEditDialogueOpen(null)} + > + {t("cancel")} + + + {t("proceed")} + - - } - onClose={() => setEditDialogueOpen(null)} - > - { - event.preventDefault(); - setEditing(true); - if (editDialogueOpen) partialupdateFileName(editDialogueOpen); - }} - className="flex w-full flex-col" - > - - {t("enter_the_file_name")} - { - setEditDialogueOpen({ - ...editDialogueOpen, - name: e.target.value, - }); - }} - /> - {editError && {editError}} - - - setEditDialogueOpen(null)} - > - {t("cancel")} - - - {t("proceed")} - - - - + + + > ); diff --git a/src/hooks/useFileUpload.tsx b/src/hooks/useFileUpload.tsx index d53b9497e08..d1b556fbb22 100644 --- a/src/hooks/useFileUpload.tsx +++ b/src/hooks/useFileUpload.tsx @@ -363,8 +363,8 @@ export default function useFileUpload( const Dialogues = ( <> setCameraModalOpen(false)} + open={cameraModalOpen} + onOpenChange={(open) => setCameraModalOpen(open)} onCapture={(file) => { setFiles((prev) => [...prev, file]); }}
- {hintMessage} -
+ {hintMessage} +
- {dragProps.fileDropError !== "" - ? dragProps.fileDropError - : `${t("drag_drop_image_to_upload")}`} -
- {t("no_image_found")}. {hintMessage} -
+ {dragProps.fileDropError !== "" + ? dragProps.fileDropError + : `${t("drag_drop_image_to_upload")}`} +
+ {t("no_image_found")}. {hintMessage} +
- {description} -
- {fileNameTooltip} -
- {fileName} + !open && handleClose()}> + + + + {t("file_preview")} + + + + {fileUrl ? ( + <> + + + + + + + {fileNameTooltip} + + + + + {fileName} + + + + + {uploadedFiles && + uploadedFiles[index] && + uploadedFiles[index].created_date && ( + + {t("created_on")}{" "} + {new Date( + uploadedFiles[index].created_date!, + ).toLocaleString("en-US", { + dateStyle: "long", + timeStyle: "short", + })} - - - - {uploadedFiles && - uploadedFiles[index] && - uploadedFiles[index].created_date && ( - - {t("created_on")}{" "} - {new Date( - uploadedFiles[index].created_date!, - ).toLocaleString("en-US", { - dateStyle: "long", - timeStyle: "short", - })} - + )} + + + {downloadURL && downloadURL.length > 0 && ( + + + + {t("download")} + + )} + + {t("close")} + + - - {downloadURL && downloadURL.length > 0 && ( - - - - {t("download")} - + + {uploadedFiles && uploadedFiles.length > 1 && ( + handleNext(index - 1)} + disabled={index <= 0} + aria-label="Previous file" + > + )} - - {t("close")} - - - - - {uploadedFiles && uploadedFiles.length > 1 && ( - handleNext(index - 1)} - disabled={index <= 0} - aria-label="Previous file" - > - - - )} - - {file_state.isImage ? ( - - ) : file_state.extension === "pdf" ? ( - }> - { - setPage(1); - setNumPages(numPages); - }} - pageNumber={page} - scale={scale} + + {file_state.isImage ? ( + - - ) : previewExtensions.includes(file_state.extension) ? ( - - ) : ( - - }> + { + setPage(1); + setNumPages(numPages); + }} + pageNumber={page} + scale={scale} + /> + + ) : previewExtensions.includes(file_state.extension) ? ( + - {t("file_preview_not_supported")} - - )} - + ) : ( + + + {t("file_preview_not_supported")} + + )} + - {uploadedFiles && uploadedFiles.length > 1 && ( - handleNext(index + 1)} - disabled={index >= uploadedFiles.length - 1} - aria-label="Next file" - > - - - )} - - - - {file_state.isImage && ( - <> - {[ - [ - t("Zoom In"), - "l-search-plus", - handleZoomIn, - file_state.zoom === zoom_values.length, - ], - [ - `${25 * file_state.zoom}%`, - false, - () => { - setFileState({ ...file_state, zoom: 4 }); - }, - false, - ], - [ - t("Zoom Out"), - "l-search-minus", - handleZoomOut, - file_state.zoom === 1, - ], - [ - t("Rotate Left"), - "l-corner-up-left", - () => handleRotate(-90), - false, - ], - [ - t("Rotate Right"), - "l-corner-up-right", - () => handleRotate(90), - false, - ], - ].map((button, index) => ( - void} - className="z-50 rounded bg-white/60 px-4 py-2 text-black backdrop-blur transition hover:bg-white/70" - disabled={button[3] as boolean} - > - {button[1] && ( - - )} - {button[0] as string} - - ))} - > - )} - {file_state.extension === "pdf" && ( - <> - {[ - ["Zoom In", "l-search-plus", handleZoomIn, scale >= 2], - [`${Math.round(scale * 100)}%`, false, () => {}, false], - ["Zoom Out", "l-search-minus", handleZoomOut, scale <= 0.5], - [ - "Previous", - "l-arrow-left", - () => setPage((prev) => prev - 1), - page === 1, - ], - [`${page}/${numPages}`, false, () => ({}), false], - [ - "Next", - "l-arrow-right", - () => setPage((prev) => prev + 1), - page === numPages, - ], - ].map((button, index) => ( - void} - className="z-50 rounded bg-white/60 px-4 py-2 text-black backdrop-blur transition hover:bg-white/70" - disabled={button[3] as boolean} - > - {button[1] && ( - - )} - {button[0] as string} - - ))} - > + {uploadedFiles && uploadedFiles.length > 1 && ( + handleNext(index + 1)} + disabled={index >= uploadedFiles.length - 1} + aria-label={t("next_file")} + > + + )} + + + {file_state.isImage && ( + <> + {[ + [ + t("zoom_in"), + "l-search-plus", + handleZoomIn, + file_state.zoom === zoom_values.length, + ], + [ + `${25 * file_state.zoom}%`, + false, + () => { + setFileState({ ...file_state, zoom: 4 }); + }, + false, + ], + [ + t("zoom_out"), + "l-search-minus", + handleZoomOut, + file_state.zoom === 1, + ], + [ + t("rotate_left"), + "l-corner-up-left", + () => handleRotate(-90), + false, + ], + [ + t("rotate_right"), + "l-corner-up-right", + () => handleRotate(90), + false, + ], + ].map((button, index) => ( + void} + className="z-50 rounded bg-white/60 px-4 py-2 text-black backdrop-blur transition hover:bg-white/70" + disabled={button[3] as boolean} + > + {button[1] && ( + + )} + {button[0] as string} + + ))} + > + )} + {file_state.extension === "pdf" && ( + <> + {[ + [t("zoom_in"), "l-search-plus", handleZoomIn, scale >= 2], + [`${Math.round(scale * 100)}%`, false, () => {}, false], + [ + t("zoom_out"), + "l-search-minus", + handleZoomOut, + scale <= 0.5, + ], + [ + t("previous"), + "l-arrow-left", + () => setPage((prev) => prev - 1), + page === 1, + ], + [`${page}/${numPages}`, false, () => ({}), false], + [ + t("next"), + "l-arrow-right", + () => setPage((prev) => prev + 1), + page === numPages, + ], + ].map((button, index) => ( + void} + className="z-50 rounded bg-white/60 px-4 py-2 text-black backdrop-blur transition hover:bg-white/70" + disabled={button[3] as boolean} + > + {button[1] && ( + + )} + {button[0] as string} + + ))} + > + )} + + + > + ) : ( + + - > - ) : ( - - - - )} - + )} + + ); }; diff --git a/src/components/Facility/DuplicatePatientDialog.tsx b/src/components/Facility/DuplicatePatientDialog.tsx index 2e795092042..beac625f852 100644 --- a/src/components/Facility/DuplicatePatientDialog.tsx +++ b/src/components/Facility/DuplicatePatientDialog.tsx @@ -4,6 +4,13 @@ import { useTranslation } from "react-i18next"; import CareIcon from "@/CAREUI/icons/CareIcon"; import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; import { Table, TableBody, @@ -13,120 +20,125 @@ import { TableRow, } from "@/components/ui/table"; -import DialogModal from "@/components/Common/Dialog"; - import { PartialPatientModel } from "@/types/emr/newPatient"; interface Props { + open: boolean; + onOpenChange: (open: boolean) => void; patientList: Array; handleOk: (action: string) => void; - handleCancel: () => void; } const DuplicatePatientDialog = (props: Props) => { const { t } = useTranslation(); - const { patientList, handleOk, handleCancel } = props; + const { open, onOpenChange, patientList, handleOk } = props; const [action, setAction] = useState(""); return ( - - - - - {t("patient_records_found_description")}( - {patientList[0].phone_number}) - - - - - - - - {[`${t("patient_name")} / ID`, t("gender")].map( - (heading, i) => ( - {heading} - ), - )} - - - - {patientList.map((patient, i) => { - return ( - - - - {patient.name} - - - ID : {patient.id} - - - {patient.gender} - - ); - })} - - + + + + {t("patient_records_found")} + + + + + {t("patient_records_found_description")}( + {patientList[0].phone_number}) + - - - - - setAction(e.target.value)} - /> - {t("duplicate_patient_record_confirmation")} - + + + + + + {[`${t("patient_name")} / ID`, t("gender")].map( + (heading, i) => ( + {heading} + ), + )} + + + + {patientList.map((patient, i) => { + return ( + + + + {patient.name} + + + ID : {patient.id} + + + {patient.gender} + + ); + })} + + + + + + + setAction(e.target.value)} + /> + {t("duplicate_patient_record_confirmation")} + + - - - setAction(e.target.value)} - /> - {t("duplicate_patient_record_rejection")} - - + + + setAction(e.target.value)} + /> + {t("duplicate_patient_record_rejection")} + + - {t("duplicate_patient_record_birth_unknown")} + {t("duplicate_patient_record_birth_unknown")} + - - - - - {t("close")} - - handleOk(action)} - disabled={!action} - variant={"primary"} - > - - {t("continue")} - - - + + + onOpenChange(false)} + className="gap-1" + variant={"secondary"} + > + + {t("close")} + + handleOk(action)} + disabled={!action} + variant={"primary"} + > + + {t("continue")} + + + + + ); }; diff --git a/src/components/Facility/FacilityHome.tsx b/src/components/Facility/FacilityHome.tsx index 547d1b209d6..195e4d700bf 100644 --- a/src/components/Facility/FacilityHome.tsx +++ b/src/components/Facility/FacilityHome.tsx @@ -7,14 +7,26 @@ import { } from "@radix-ui/react-tooltip"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useMutation } from "@tanstack/react-query"; -import { Hospital, MapPin, MoreVertical, Settings } from "lucide-react"; +import { Edit2, Hospital, MapPin, MoreVertical, Settings } from "lucide-react"; import { navigate } from "raviger"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; +import { cn } from "@/lib/utils"; + +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; +import { Button, buttonVariants } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { DropdownMenu, @@ -26,7 +38,6 @@ import { Markdown } from "@/components/ui/markdown"; import { Avatar } from "@/components/Common/Avatar"; import AvatarEditModal from "@/components/Common/AvatarEditModal"; -import ConfirmDialog from "@/components/Common/ConfirmDialog"; import ContactLink from "@/components/Common/ContactLink"; import Loading from "@/components/Common/Loading"; @@ -96,7 +107,6 @@ const renderGeoOrganizations = (geoOrg: Organization) => { export const FacilityHome = ({ facilityId }: Props) => { const { t } = useTranslation(); - const [openDeleteDialog, setOpenDeleteDialog] = useState(false); const [editCoverImage, setEditCoverImage] = useState(false); const queryClient = useQueryClient(); @@ -106,13 +116,14 @@ export const FacilityHome = ({ facilityId }: Props) => { pathParams: { id: facilityId }, }), }); - const { mutate: deleteFacility } = useMutation({ + + const { mutate: deleteFacility, isPending: isDeleting } = useMutation({ mutationFn: mutate(routes.deleteFacility, { pathParams: { id: facilityId }, }), onSuccess: () => { toast.success( - t("entity_deleted_successfully", { name: facilityData?.name }), + t("facility_deleted_successfully", { name: facilityData?.name }), ); navigate("/facility"); }, @@ -184,26 +195,13 @@ export const FacilityHome = ({ facilityId }: Props) => { return ( - - {t("are_you_sure_want_to_delete", { name: facilityData?.name })} - - } - action="Delete" - variant="destructive" - show={openDeleteDialog} - onClose={() => setOpenDeleteDialog(false)} - onConfirm={() => deleteFacility()} - /> setEditCoverImage(false)} + onOpenChange={(open) => setEditCoverImage(open)} hint={coverImageHint} /> @@ -267,7 +265,7 @@ export const FacilityHome = ({ facilityId }: Props) => { className="cursor-pointer" onClick={() => setEditCoverImage(true)} > - + {t("edit_cover_photo")} )} @@ -276,7 +274,7 @@ export const FacilityHome = ({ facilityId }: Props) => { facilityId={facilityId} trigger={ { e.preventDefault(); }} @@ -286,16 +284,45 @@ export const FacilityHome = ({ facilityId }: Props) => { } /> - {/* TODO: get permissions from backend */} - {/* {hasPermissionToDeleteFacility && ( - setOpenDeleteDialog(true)} - > - - {t("delete_facility")} - - )} */} + + {/* TODO: add delete facility */} + {/* + e.preventDefault()} + > + + {t("delete_facility")} + + */} + + + + {t("delete_facility")} + + + {t("delete_facility_confirmation", { + name: facilityData?.name, + })} + + + + + {t("cancel")} + + deleteFacility()} + className={cn( + buttonVariants({ variant: "destructive" }), + )} + disabled={isDeleting} + > + {isDeleting ? t("deleting") : t("delete")} + + + + + void; + open: boolean; + onOpenChange: (open: boolean) => void; onCapture: (file: File, fileName: string) => void; onResetCapture: () => void; setPreview?: (isPreview: boolean) => void; } export default function CameraCaptureDialog(props: CameraCaptureDialogProps) { - const { show, onHide, onCapture, onResetCapture, setPreview } = props; + const { open, onOpenChange, onCapture, onResetCapture, setPreview } = props; const isLaptopScreen = useBreakpoints({ lg: true, default: false }); const [cameraFacingMode, setCameraFacingMode] = useState( @@ -34,8 +38,9 @@ export default function CameraCaptureDialog(props: CameraCaptureDialogProps) { height: { ideal: 2160 }, facingMode: cameraFacingMode, }; + useEffect(() => { - if (!show) return; + if (!open) return; let stream: MediaStream | null = null; navigator.mediaDevices @@ -45,7 +50,7 @@ export default function CameraCaptureDialog(props: CameraCaptureDialogProps) { }) .catch(() => { toast.warning(t("camera_permission_denied")); - onHide(); + onOpenChange(false); }); return () => { @@ -55,7 +60,7 @@ export default function CameraCaptureDialog(props: CameraCaptureDialogProps) { }); } }; - }, [show, cameraFacingMode, onHide]); + }, [open, cameraFacingMode, onOpenChange]); const handleSwitchCamera = useCallback(async () => { const devices = await navigator.mediaDevices.enumerateDevices(); @@ -87,130 +92,62 @@ export default function CameraCaptureDialog(props: CameraCaptureDialogProps) { }; return ( - - - - - - {t("camera")} - - - } - className="max-w-2xl" - onClose={onHide} - > - - {!previewImage ? ( - - - - ) : ( - - - - )} - + + + + + + + + + + + {t("camera")} + + + + - {/* buttons for mobile screens */} - - - {!previewImage ? ( - - {t("switch")} - - ) : ( - <>> - )} - {!previewImage ? ( - <> - - { - captureImage(); - setPreview?.(true); - }} - className="m-2" - > - {t("capture")} - - - > + + + ) : ( - <> - - { - setPreviewImage(null); - onResetCapture(); - setPreview?.(false); - }} - className="m-2" - > - {t("retake")} - - { - setPreviewImage(null); - onHide(); - setPreview?.(false); - }} - className="m-2" - > - {t("submit")} - - - > + + + )} - - { - setPreviewImage(null); - onResetCapture(); - onHide(); - }} - className="m-2" - > - {t("close")} - - - - {/* buttons for laptop screens */} - - - - - {`${t("switch")} ${t("camera")}`} - - - + {/* buttons for mobile and tablet screens */} + + + {!previewImage ? ( + + {t("switch")} + + ) : ( + <>> + )} + {!previewImage ? ( <> @@ -221,8 +158,8 @@ export default function CameraCaptureDialog(props: CameraCaptureDialogProps) { captureImage(); setPreview?.(true); }} + className="m-2" > - {t("capture")} @@ -237,16 +174,18 @@ export default function CameraCaptureDialog(props: CameraCaptureDialogProps) { onResetCapture(); setPreview?.(false); }} + className="m-2" > {t("retake")} { - onHide(); setPreviewImage(null); + onOpenChange(false); setPreview?.(false); }} + className="m-2" > {t("submit")} @@ -254,20 +193,82 @@ export default function CameraCaptureDialog(props: CameraCaptureDialogProps) { > )} - - { - setPreviewImage(null); - onResetCapture(); - onHide(); - setPreview?.(false); - }} - > - {`${t("close")} ${t("camera")}`} - + + { + setPreviewImage(null); + onResetCapture(); + onOpenChange(false); + }} + className="m-2" + > + {t("close")} + + + + + {/* buttons for laptop screens */} + + + + {!previewImage ? ( + <> + + { + captureImage(); + setPreview?.(true); + }} + > + + {t("capture")} + + + > + ) : ( + <> + + { + setPreviewImage(null); + onResetCapture(); + setPreview?.(false); + }} + > + {t("retake")} + + { + onOpenChange(false); + setPreviewImage(null); + setPreview?.(false); + }} + > + {t("submit")} + + + > + )} + + + { + setPreviewImage(null); + onResetCapture(); + onOpenChange(false); + setPreview?.(false); + }} + > + {`${t("close")} ${t("camera")}`} + + - - + + ); } diff --git a/src/components/Patient/PatientHome.tsx b/src/components/Patient/PatientHome.tsx index 0807023978a..69df69c4143 100644 --- a/src/components/Patient/PatientHome.tsx +++ b/src/components/Patient/PatientHome.tsx @@ -247,30 +247,6 @@ export const PatientHome = (props: { - - {/* setOpenAssignVolunteerDialog(false)} - description={ - - setAssignedVolunteer(user.value)} - userType={"Volunteer"} - name={"assign_volunteer"} - error={errors.assignedVolunteer} - /> - - } - action={ - assignedVolunteer || !patientData.assigned_to - ? t("assign") - : t("unassign") - } - onConfirm={handleAssignedVolunteer} - /> */} ); }; diff --git a/src/components/Patient/PatientRegistration.tsx b/src/components/Patient/PatientRegistration.tsx index 8557b4fab89..64ded99c691 100644 --- a/src/components/Patient/PatientRegistration.tsx +++ b/src/components/Patient/PatientRegistration.tsx @@ -747,10 +747,13 @@ export default function PatientRegistration( {showDuplicate && ( { - handleDialogClose("close"); + onOpenChange={(open) => { + if (!open) { + handleDialogClose("close"); + } }} /> )} diff --git a/src/components/Users/UserAvatar.tsx b/src/components/Users/UserAvatar.tsx index 1d16f4ba8bd..fd288000919 100644 --- a/src/components/Users/UserAvatar.tsx +++ b/src/components/Users/UserAvatar.tsx @@ -97,7 +97,7 @@ export default function UserAvatar({ username }: { username: string }) { imageUrl={userData?.profile_picture_url} handleUpload={handleAvatarUpload} handleDelete={handleAvatarDelete} - onClose={() => setEditAvatar(false)} + onOpenChange={(open) => setEditAvatar(open)} /> diff --git a/src/components/Users/UserDeleteDialog.tsx b/src/components/Users/UserDeleteDialog.tsx index 5a2becefe3d..a088c9f2512 100644 --- a/src/components/Users/UserDeleteDialog.tsx +++ b/src/components/Users/UserDeleteDialog.tsx @@ -1,26 +1,51 @@ -import ConfirmDialog from "@/components/Common/ConfirmDialog"; +import { useTranslation } from "react-i18next"; + +import { cn } from "@/lib/utils"; + +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; +import { buttonVariants } from "@/components/ui/button"; interface ConfirmDialogProps { name: string; handleCancel: () => void; handleOk: () => void; + show: boolean; } const UserDeleteDialog = (props: ConfirmDialogProps) => { + const { t } = useTranslation(); return ( - - Are you sure you want to delete user {props.name} ? - - } - action="Delete" - variant="destructive" - show - onConfirm={props.handleOk} - onClose={props.handleCancel} - /> + + + + {t("delete_user")} + + {t("are_you_sure_you_want_to_delete_user")} + {props.name}? + + + + + {t("cancel")} + + + {t("delete")} + + + + ); }; diff --git a/src/components/Users/UserSummary.tsx b/src/components/Users/UserSummary.tsx index 10c6dca9d42..a28425e6eef 100644 --- a/src/components/Users/UserSummary.tsx +++ b/src/components/Users/UserSummary.tsx @@ -89,6 +89,7 @@ export default function UserSummaryTab({ userData }: { userData?: UserBase }) { <> {showDeleteDialog && ( { @@ -96,6 +97,7 @@ export default function UserSummaryTab({ userData }: { userData?: UserBase }) { }} /> )} + - - - setArchiveDialogueOpen(null)} + > + + + + + + + + + {t("archive_file")} + + {t("this_action_is_irreversible")} + + + + + + + { + event.preventDefault(); + handleFileArchive(archiveDialogueOpen); + }} + className="mx-2 my-4 flex w-full flex-col" + > + + + }} + /> + + setArchiveReason(e.target.value)} + className={cn( + archiveReasonError && + "border-red-500 focus-visible:ring-red-500", + )} /> + {archiveReasonError && ( + {archiveReasonError} + )} - - Archive File - - {t("this_action_is_irreversible")} - + + setArchiveDialogueOpen(null)} + > + {t("cancel")} + + + {t("proceed")} + - + + + + setArchiveDialogueOpen(null)} + onOpenChange={() => setArchiveDialogueOpen(null)} > - { - event.preventDefault(); - handleFileArchive(archiveDialogueOpen); - }} - className="mx-2 my-4 flex w-full flex-col" - > - - - }} - /> - - setArchiveReason(e.target.value)} - className={cn( - archiveReasonError && - "border-red-500 focus-visible:ring-red-500", - )} - /> - {archiveReasonError && ( - {archiveReasonError} - )} + + + + {archiveDialogueOpen?.name} {t("archived")} + + + + + {t("this_file_has_been_archived")} + + + {[ + { + label: "File Name", + content: archiveDialogueOpen?.name, + icon: "l-file", + }, + { + label: "Uploaded By", + content: archiveDialogueOpen?.uploaded_by?.username, + icon: "l-user", + }, + { + label: "Uploaded On", + content: formatDateTime(archiveDialogueOpen?.created_date), + icon: "l-clock", + }, + { + label: "Archive Reason", + content: archiveDialogueOpen?.archive_reason, + icon: "l-archive", + }, + { + label: "Archived By", + content: archiveDialogueOpen?.archived_by?.username, + icon: "l-user", + }, + { + label: "Archived On", + content: formatDateTime(archiveDialogueOpen?.archived_datetime), + icon: "l-clock", + }, + ].map((item, index) => ( + + + + + + + {item.label} + + + {item.content} + + + + ))} - + {t("cancel")} - - {t("proceed")} - - - - - {archiveDialogueOpen?.name} (Archived) - - } - fixedWidth={false} - className="md:w-[700px]" - onClose={() => setArchiveDialogueOpen(null)} + + + setEditDialogueOpen(null)} > - - - {t("this_file_has_been_archived")} - - - {[ - { - label: "File Name", - content: archiveDialogueOpen?.name, - icon: "l-file", - }, - { - label: "Uploaded By", - content: archiveDialogueOpen?.uploaded_by?.username, - icon: "l-user", - }, - { - label: "Uploaded On", - content: formatDateTime(archiveDialogueOpen?.created_date), - icon: "l-clock", - }, - { - label: "Archive Reason", - content: archiveDialogueOpen?.archive_reason, - icon: "l-archive", - }, - { - label: "Archived By", - content: archiveDialogueOpen?.archived_by?.username, - icon: "l-user", - }, - { - label: "Archived On", - content: formatDateTime(archiveDialogueOpen?.archived_datetime), - icon: "l-clock", - }, - ].map((item, index) => ( - - - - - - - {item.label} + + + + + + - - {item.content} + + {t("rename_file")} - - ))} - - - setArchiveDialogueOpen(null)} + + + { + event.preventDefault(); + setEditing(true); + if (editDialogueOpen) partialupdateFileName(editDialogueOpen); + }} + className="flex w-full flex-col" > - {t("cancel")} - - - - - - + {t("enter_the_file_name")} + { + setEditDialogueOpen({ + ...editDialogueOpen, + name: e.target.value, + }); + }} /> + {editError && {editError}} - - {t("rename_file")} + + setEditDialogueOpen(null)} + > + {t("cancel")} + + + {t("proceed")} + - - } - onClose={() => setEditDialogueOpen(null)} - > - { - event.preventDefault(); - setEditing(true); - if (editDialogueOpen) partialupdateFileName(editDialogueOpen); - }} - className="flex w-full flex-col" - > - - {t("enter_the_file_name")} - { - setEditDialogueOpen({ - ...editDialogueOpen, - name: e.target.value, - }); - }} - /> - {editError && {editError}} - - - setEditDialogueOpen(null)} - > - {t("cancel")} - - - {t("proceed")} - - - - + + + > ); diff --git a/src/hooks/useFileUpload.tsx b/src/hooks/useFileUpload.tsx index d53b9497e08..d1b556fbb22 100644 --- a/src/hooks/useFileUpload.tsx +++ b/src/hooks/useFileUpload.tsx @@ -363,8 +363,8 @@ export default function useFileUpload( const Dialogues = ( <> setCameraModalOpen(false)} + open={cameraModalOpen} + onOpenChange={(open) => setCameraModalOpen(open)} onCapture={(file) => { setFiles((prev) => [...prev, file]); }}
+ {fileNameTooltip} +
+ {fileName} +
+ {t("created_on")}{" "} + {new Date( + uploadedFiles[index].created_date!, + ).toLocaleString("en-US", { + dateStyle: "long", + timeStyle: "short", + })}
- {t("created_on")}{" "} - {new Date( - uploadedFiles[index].created_date!, - ).toLocaleString("en-US", { - dateStyle: "long", - timeStyle: "short", - })} -
- {t("patient_records_found_description")}( - {patientList[0].phone_number}) -
+ {t("patient_records_found_description")}( + {patientList[0].phone_number}) +
{t("duplicate_patient_record_confirmation")}
{t("duplicate_patient_record_rejection")}
{t("duplicate_patient_record_birth_unknown")}
{archiveReasonError}
{editError}