From 75a47966c3a4db30b7cf03a34cdff30a9c4790e5 Mon Sep 17 00:00:00 2001 From: Saikiran Patil <84263946+saikiranpatil@users.noreply.github.com> Date: Tue, 10 Dec 2024 19:29:53 +0530 Subject: [PATCH 01/16] prefilled middleware prefilled middleware displayed as message in asset congiguration, added new attributes `message` and `messageClass` to `FormFeild` component` --- .../Assets/AssetType/HL7Monitor.tsx | 17 ++++---------- src/components/CameraFeed/ConfigureCamera.tsx | 22 +++++++----------- src/components/Form/FormFields/FormField.tsx | 23 +++++++++++++++++++ src/components/Form/FormFields/Utils.ts | 2 ++ 4 files changed, 38 insertions(+), 26 deletions(-) diff --git a/src/components/Assets/AssetType/HL7Monitor.tsx b/src/components/Assets/AssetType/HL7Monitor.tsx index 76d761383bd..c53ae325111 100644 --- a/src/components/Assets/AssetType/HL7Monitor.tsx +++ b/src/components/Assets/AssetType/HL7Monitor.tsx @@ -100,20 +100,13 @@ const HL7Monitor = (props: HL7MonitorProps) => { label={ <div className="flex flex-row gap-1"> <p>Middleware Hostname</p> - {resolvedMiddleware?.source != "asset" && ( - <div className="tooltip"> - <CareIcon - icon="l-info-circle" - className="tooltip text-indigo-500 hover:text-indigo-600" - /> - <span className="tooltip-text w-56 whitespace-normal"> - Middleware hostname sourced from asset{" "} - {resolvedMiddleware?.source} - </span> - </div> - )} </div> } + message={ + resolvedMiddleware?.source != "asset" + ? `Middleware hostname sourced from asset ${resolvedMiddleware?.source}` + : undefined + } placeholder={resolvedMiddleware?.hostname} value={middlewareHostname} onChange={(e) => setMiddlewareHostname(e.value)} diff --git a/src/components/CameraFeed/ConfigureCamera.tsx b/src/components/CameraFeed/ConfigureCamera.tsx index eec453053d6..3a80a585b16 100644 --- a/src/components/CameraFeed/ConfigureCamera.tsx +++ b/src/components/CameraFeed/ConfigureCamera.tsx @@ -149,22 +149,16 @@ export default function ConfigureCamera(props: Props) { label={ <div className="flex flex-row gap-1"> <p>{t("middleware_hostname")}</p> - {!!props.asset.resolved_middleware && - props.asset.resolved_middleware.source != "asset" && ( - <div className="tooltip"> - <CareIcon - icon="l-info-circle" - className="tooltip text-indigo-500 hover:text-indigo-600" - /> - <span className="tooltip-text w-56 whitespace-normal"> - {t("middleware_hostname_sourced_from", { - source: props.asset.resolved_middleware?.source, - })} - </span> - </div> - )} </div> } + message={ + !!props.asset.resolved_middleware && + props.asset.resolved_middleware.source != "asset" + ? t("middleware_hostname_sourced_from", { + source: props.asset.resolved_middleware?.source, + }) + : undefined + } placeholder={ props.asset.resolved_middleware?.hostname ?? t("middleware_hostname_example") diff --git a/src/components/Form/FormFields/FormField.tsx b/src/components/Form/FormFields/FormField.tsx index 7f9c2699d64..d5533877fbc 100644 --- a/src/components/Form/FormFields/FormField.tsx +++ b/src/components/Form/FormFields/FormField.tsx @@ -48,6 +48,25 @@ export const FieldErrorText = (props: ErrorProps) => { ); }; +type MessageProps = { + message: string | undefined; + className?: string | undefined; +}; + +export const FieldMessageText = (props: MessageProps) => { + return ( + <span + className={classNames( + "text-primary-400 ml-1 mt-2 text-xs tracking-wide transition-opacity duration-300", + props.message ? "opacity-100" : "opacity-0", + props.className, + )} + > + {props.message} + </span> + ); +}; + const FormField = ({ field, ...props @@ -73,6 +92,10 @@ const FormField = ({ </div> <div className={field?.className}>{props.children}</div> <FieldErrorText error={field?.error} className={field?.errorClassName} /> + <FieldMessageText + message={field?.message} + className={field?.messageClassName} + /> </div> ); }; diff --git a/src/components/Form/FormFields/Utils.ts b/src/components/Form/FormFields/Utils.ts index 1e88bcbd6a0..b017f7b92ee 100644 --- a/src/components/Form/FormFields/Utils.ts +++ b/src/components/Form/FormFields/Utils.ts @@ -20,9 +20,11 @@ export type FieldChangeEventHandler<T> = (event: FieldChangeEvent<T>) => void; export type FormFieldBaseProps<T> = { label?: React.ReactNode; labelSuffix?: React.ReactNode; + message?: string; disabled?: boolean; className?: string; required?: boolean; + messageClassName?: string; labelClassName?: string; errorClassName?: string; name: string; From 4effcf0e9e0649fa410528d21a1239849b126c73 Mon Sep 17 00:00:00 2001 From: Saikiran Patil <84263946+saikiranpatil@users.noreply.github.com> Date: Tue, 10 Dec 2024 20:05:31 +0530 Subject: [PATCH 02/16] add fine message text in create location form --- src/components/Facility/AddLocationForm.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Facility/AddLocationForm.tsx b/src/components/Facility/AddLocationForm.tsx index bb0039285f5..ff4bec38382 100644 --- a/src/components/Facility/AddLocationForm.tsx +++ b/src/components/Facility/AddLocationForm.tsx @@ -209,6 +209,7 @@ export const AddLocationForm = ({ facilityId, locationId }: Props) => { name="Location Middleware Address" type="text" label="Location Middleware Address" + message="Leave blank to apply facility middleware to assets" value={middlewareAddress} onChange={(e) => setMiddlewareAddress(e.value)} error={errors.middlewareAddress} From 3c9172071436bc98d07a3721997c2a64cd45b0eb Mon Sep 17 00:00:00 2001 From: Saikiran Patil <84263946+saikiranpatil@users.noreply.github.com> Date: Tue, 10 Dec 2024 20:27:37 +0530 Subject: [PATCH 03/16] Add fine text facility confiuration page add fine default behaviour text in facility configuration page --- src/components/Facility/FacilityConfigure.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Facility/FacilityConfigure.tsx b/src/components/Facility/FacilityConfigure.tsx index 1cb7c7d69cf..a4b9bc5756b 100644 --- a/src/components/Facility/FacilityConfigure.tsx +++ b/src/components/Facility/FacilityConfigure.tsx @@ -150,6 +150,7 @@ export const FacilityConfigure = (props: IProps) => { <TextFormField name="middleware_address" label="Facility Middleware Address" + message="This addredd will be applied to all assets when asset and location middleware are Unspecified" required value={state.form.middleware_address} onChange={handleChange} From 4d2cf0d6d2ba41609fe0492281ada7bab00a5586 Mon Sep 17 00:00:00 2001 From: Saikiran Patil <84263946+saikiranpatil@users.noreply.github.com> Date: Tue, 10 Dec 2024 20:55:31 +0530 Subject: [PATCH 04/16] corrected mispelled address word --- src/components/Facility/FacilityConfigure.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Facility/FacilityConfigure.tsx b/src/components/Facility/FacilityConfigure.tsx index a4b9bc5756b..315181a3ab0 100644 --- a/src/components/Facility/FacilityConfigure.tsx +++ b/src/components/Facility/FacilityConfigure.tsx @@ -150,7 +150,7 @@ export const FacilityConfigure = (props: IProps) => { <TextFormField name="middleware_address" label="Facility Middleware Address" - message="This addredd will be applied to all assets when asset and location middleware are Unspecified" + message="This address will be applied to all assets when asset and location middleware are Unspecified" required value={state.form.middleware_address} onChange={handleChange} From 937e6fd87db1d651b7d4b44a7632486d57f177d1 Mon Sep 17 00:00:00 2001 From: Saikiran Patil <84263946+saikiranpatil@users.noreply.github.com> Date: Wed, 11 Dec 2024 00:10:14 +0530 Subject: [PATCH 05/16] added resolved middleware text at location management --- .../Facility/LocationManagement.tsx | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/components/Facility/LocationManagement.tsx b/src/components/Facility/LocationManagement.tsx index 2f80a879651..ab6865ebf88 100644 --- a/src/components/Facility/LocationManagement.tsx +++ b/src/components/Facility/LocationManagement.tsx @@ -27,6 +27,7 @@ interface Props { interface LocationProps extends LocationModel { facilityId: string; disabled: boolean; + facilityMiddleware?: string; setShowDeletePopup: (e: { open: boolean; name: string; id: string }) => void; } @@ -42,6 +43,18 @@ export default function LocationManagement({ facilityId }: Props) { name: "", id: "", }); + const [facilityMiddleware, setFacilityMiddleware] = useState< + string | undefined + >(undefined); + + useQuery(routes.getPermittedFacility, { + pathParams: { id: facilityId }, + onResponse: (res) => { + if (res.data) { + setFacilityMiddleware(res.data?.middleware_address); + } + }, + }); const closeDeleteFailModal = () => { setShowDeleteFailModal({ ...showDeleteFailModal, open: false }); @@ -121,6 +134,7 @@ export default function LocationManagement({ facilityId }: Props) { <Location setShowDeletePopup={setShowDeletePopup} facilityId={facilityId} + facilityMiddleware={facilityMiddleware} {...item} disabled={ ["DistrictAdmin", "StateAdmin"].includes(authUser.user_type) @@ -221,6 +235,7 @@ const Location = ({ name, description, middleware_address, + facilityMiddleware, location_type, created_date, modified_date, @@ -268,14 +283,19 @@ const Location = ({ > {description || "-"} </p> - <p className="mt-3 text-sm font-semibold text-secondary-700"> + <span className="mt-3 text-sm font-semibold text-secondary-700"> Middleware Address: - </p> + </span> + {!middleware_address && facilityMiddleware && ( + <span className="ml-1 text-xs text-primary-400 opaticy-70"> + Fetched from facility + </span> + )} <p className="mt-1 break-all font-mono text-sm font-bold text-secondary-700" id="view-location-middleware" > - {middleware_address || "-"} + {middleware_address || facilityMiddleware || "-"} </p> <Uptime route={routes.listFacilityAssetLocationAvailability} From 6c38aa900a781aee492f648a2371350e308ee4ad Mon Sep 17 00:00:00 2001 From: Saikiran Patil <84263946+saikiranpatil@users.noreply.github.com> Date: Wed, 11 Dec 2024 00:37:59 +0530 Subject: [PATCH 06/16] Fixed typo in CSS class name --- src/components/Facility/LocationManagement.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Facility/LocationManagement.tsx b/src/components/Facility/LocationManagement.tsx index ab6865ebf88..4ff0f3a1655 100644 --- a/src/components/Facility/LocationManagement.tsx +++ b/src/components/Facility/LocationManagement.tsx @@ -287,7 +287,7 @@ const Location = ({ Middleware Address: </span> {!middleware_address && facilityMiddleware && ( - <span className="ml-1 text-xs text-primary-400 opaticy-70"> + <span className="ml-1 text-xs text-primary-400"> Fetched from facility </span> )} From 6fa2c556a798866897073059c1c0da812d8beb35 Mon Sep 17 00:00:00 2001 From: Saikiran Patil <84263946+saikiranpatil@users.noreply.github.com> Date: Thu, 12 Dec 2024 12:32:52 +0530 Subject: [PATCH 07/16] added (fetched from facility) badge in location management page --- src/components/Facility/LocationManagement.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/Facility/LocationManagement.tsx b/src/components/Facility/LocationManagement.tsx index 4ff0f3a1655..954badd8e05 100644 --- a/src/components/Facility/LocationManagement.tsx +++ b/src/components/Facility/LocationManagement.tsx @@ -287,8 +287,10 @@ const Location = ({ Middleware Address: </span> {!middleware_address && facilityMiddleware && ( - <span className="ml-1 text-xs text-primary-400"> - Fetched from facility + <span className="ml-2 mt-2 text-xs h-fit rounded-full border-2 border-primary-500 bg-primary-100 px-3 py-[3px]"> + <span className="text-xs font-bold text-primary-500"> + Fetched from facility + </span> </span> )} <p From e5d881e002efddbad9ce1bb034493dd5f267b326 Mon Sep 17 00:00:00 2001 From: Saikiran Patil <84263946+saikiranpatil@users.noreply.github.com> Date: Thu, 12 Dec 2024 12:52:40 +0530 Subject: [PATCH 08/16] used shadcn label component in formfeild, changed color of message to gray --- src/components/Form/FormFields/FormField.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/Form/FormFields/FormField.tsx b/src/components/Form/FormFields/FormField.tsx index d5533877fbc..1a90f059586 100644 --- a/src/components/Form/FormFields/FormField.tsx +++ b/src/components/Form/FormFields/FormField.tsx @@ -1,3 +1,5 @@ +import { Label } from "@radix-ui/react-label"; + import { FieldError } from "@/components/Form/FieldValidators"; import { FormFieldBaseProps } from "@/components/Form/FormFields/Utils"; @@ -14,7 +16,7 @@ type LabelProps = { export const FieldLabel = (props: LabelProps) => { return ( - <label + <Label id={props.id} className={classNames( "block text-base font-normal text-secondary-900", @@ -25,7 +27,7 @@ export const FieldLabel = (props: LabelProps) => { > {props.children} {props.required && <span className="text-danger-500">{" *"}</span>} - </label> + </Label> ); }; @@ -57,8 +59,8 @@ export const FieldMessageText = (props: MessageProps) => { return ( <span className={classNames( - "text-primary-400 ml-1 mt-2 text-xs tracking-wide transition-opacity duration-300", - props.message ? "opacity-100" : "opacity-0", + "text-secondary-700 ml-1 mt-2 text-xs tracking-wide transition-opacity duration-300", + props.message ? "opacity-80" : "opacity-0", props.className, )} > From d7e4a45a802d52aa18b3d68fcd45ea3afb62d7d2 Mon Sep 17 00:00:00 2001 From: Saikiran Patil <84263946+saikiranpatil@users.noreply.github.com> Date: Thu, 12 Dec 2024 13:00:29 +0530 Subject: [PATCH 09/16] minor changes --- src/components/Form/FormFields/FormField.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Form/FormFields/FormField.tsx b/src/components/Form/FormFields/FormField.tsx index 1a90f059586..7013f7caa37 100644 --- a/src/components/Form/FormFields/FormField.tsx +++ b/src/components/Form/FormFields/FormField.tsx @@ -1,4 +1,4 @@ -import { Label } from "@radix-ui/react-label"; +import { Label } from "@/components/ui/label"; import { FieldError } from "@/components/Form/FieldValidators"; import { FormFieldBaseProps } from "@/components/Form/FormFields/Utils"; From be5285de04928340dfff978e4974216eaadb2bca Mon Sep 17 00:00:00 2001 From: Saikiran Patil <84263946+saikiranpatil@users.noreply.github.com> Date: Sun, 15 Dec 2024 19:27:11 +0530 Subject: [PATCH 10/16] used shadcn badge in location management page --- .../Facility/LocationManagement.tsx | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/components/Facility/LocationManagement.tsx b/src/components/Facility/LocationManagement.tsx index 954badd8e05..674a922dbe0 100644 --- a/src/components/Facility/LocationManagement.tsx +++ b/src/components/Facility/LocationManagement.tsx @@ -20,6 +20,8 @@ import routes from "@/Utils/request/api"; import request from "@/Utils/request/request"; import useQuery from "@/Utils/request/useQuery"; +import { Badge } from "../ui/badge"; + interface Props { facilityId: string; } @@ -267,14 +269,9 @@ const Location = ({ > {name} </p> - <div - className="mt-2 h-fit rounded-full border-2 border-primary-500 bg-primary-100 px-3 py-[3px]" - id="location-type" - > - <p className="text-xs font-bold text-primary-500"> - {location_type} - </p> - </div> + <Badge className="rounded-full bg-primary-100 text-primary-500 hover:bg-primary-100 border-2 border-primary-500 font-bold px-3 py-[3px] ml-2"> + {location_type} + </Badge> </div> </div> <p @@ -287,11 +284,9 @@ const Location = ({ Middleware Address: </span> {!middleware_address && facilityMiddleware && ( - <span className="ml-2 mt-2 text-xs h-fit rounded-full border-2 border-primary-500 bg-primary-100 px-3 py-[3px]"> - <span className="text-xs font-bold text-primary-500"> - Fetched from facility - </span> - </span> + <Badge className="rounded-full bg-primary-100 text-primary-500 hover:bg-primary-100 border-2 border-primary-500 font-bold px-3 py-[3px] ml-2"> + Fetched from facility + </Badge> )} <p className="mt-1 break-all font-mono text-sm font-bold text-secondary-700" From 33ae347992b7185640018f7e6cce8739f027e2b6 Mon Sep 17 00:00:00 2001 From: Saikiran Patil <84263946+saikiranpatil@users.noreply.github.com> Date: Mon, 16 Dec 2024 13:37:39 +0530 Subject: [PATCH 11/16] updated useQuery --- src/components/Facility/LocationManagement.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/Facility/LocationManagement.tsx b/src/components/Facility/LocationManagement.tsx index 4e6e77c0a3f..1fe0551b4d3 100644 --- a/src/components/Facility/LocationManagement.tsx +++ b/src/components/Facility/LocationManagement.tsx @@ -4,6 +4,8 @@ import RecordMeta from "@/CAREUI/display/RecordMeta"; import CareIcon from "@/CAREUI/icons/CareIcon"; import PaginatedList from "@/CAREUI/misc/PaginatedList"; +import { Badge } from "@/components/ui/badge"; + import ButtonV2, { Cancel } from "@/components/Common/ButtonV2"; import ConfirmDialog from "@/components/Common/ConfirmDialog"; import DialogModal from "@/components/Common/Dialog"; @@ -20,8 +22,6 @@ import routes from "@/Utils/request/api"; import request from "@/Utils/request/request"; import useTanStackQueryInstead from "@/Utils/request/useQuery"; -import { Badge } from "../ui/badge"; - interface Props { facilityId: string; } @@ -49,7 +49,7 @@ export default function LocationManagement({ facilityId }: Props) { string | undefined >(undefined); - useQuery(routes.getPermittedFacility, { + useTanStackQueryInstead(routes.getPermittedFacility, { pathParams: { id: facilityId }, onResponse: (res) => { if (res.data) { From b9b0ed0ca7c1089884d2ae32b19ed72c01c100b6 Mon Sep 17 00:00:00 2001 From: Saikiran Patil <84263946+saikiranpatil@users.noreply.github.com> Date: Wed, 18 Dec 2024 10:39:55 +0530 Subject: [PATCH 12/16] changed useTanstackuseQuery to useQuery --- .../Facility/LocationManagement.tsx | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/components/Facility/LocationManagement.tsx b/src/components/Facility/LocationManagement.tsx index 1fe0551b4d3..e9c1323142f 100644 --- a/src/components/Facility/LocationManagement.tsx +++ b/src/components/Facility/LocationManagement.tsx @@ -1,3 +1,4 @@ +import { useQuery } from "@tanstack/react-query"; import { useState } from "react"; import RecordMeta from "@/CAREUI/display/RecordMeta"; @@ -19,6 +20,7 @@ import useAuthUser from "@/hooks/useAuthUser"; import AuthorizeFor, { NonReadOnlyUsers } from "@/Utils/AuthorizeFor"; import * as Notification from "@/Utils/Notifications"; import routes from "@/Utils/request/api"; +import query from "@/Utils/request/query"; import request from "@/Utils/request/request"; import useTanStackQueryInstead from "@/Utils/request/useQuery"; @@ -45,19 +47,16 @@ export default function LocationManagement({ facilityId }: Props) { name: "", id: "", }); - const [facilityMiddleware, setFacilityMiddleware] = useState< - string | undefined - >(undefined); - useTanStackQueryInstead(routes.getPermittedFacility, { - pathParams: { id: facilityId }, - onResponse: (res) => { - if (res.data) { - setFacilityMiddleware(res.data?.middleware_address); - } - }, + const { data } = useQuery({ + queryKey: [routes.getPermittedFacility.path, facilityId], + queryFn: query(routes.getPermittedFacility, { + pathParams: { id: facilityId }, + }), }); + const facilityMiddleware = data?.middleware_address; + const closeDeleteFailModal = () => { setShowDeleteFailModal({ ...showDeleteFailModal, open: false }); }; From c28d3a08d15ee1b4131f51aa9bbab0cd139340ef Mon Sep 17 00:00:00 2001 From: Saikiran Patil <84263946+saikiranpatil@users.noreply.github.com> Date: Wed, 18 Dec 2024 15:52:22 +0530 Subject: [PATCH 13/16] used react forms in facility configuration page --- package-lock.json | 202 ++++++++++++++++-- package.json | 8 +- src/components/Facility/FacilityConfigure.tsx | 191 ++++++++--------- src/components/ui/form.tsx | 184 ++++++++++++++++ 4 files changed, 462 insertions(+), 123 deletions(-) create mode 100644 src/components/ui/form.tsx diff --git a/package-lock.json b/package-lock.json index 94a84d3d179..d3057b01be0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,15 +17,16 @@ "@googlemaps/typescript-guards": "^2.0.3", "@headlessui/react": "^2.2.0", "@hello-pangea/dnd": "^17.0.0", + "@hookform/resolvers": "^3.9.1", "@pnotify/core": "^5.2.0", "@pnotify/mobile": "^5.2.0", "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-icons": "^1.3.2", - "@radix-ui/react-label": "^2.1.0", + "@radix-ui/react-label": "^2.1.1", "@radix-ui/react-popover": "^1.1.2", "@radix-ui/react-scroll-area": "^1.2.0", - "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-slot": "^1.1.1", "@radix-ui/react-toast": "^1.2.2", "@radix-ui/react-tooltip": "^1.1.4", "@sentry/browser": "^8.42.0", @@ -56,6 +57,7 @@ "react-copy-to-clipboard": "^5.1.0", "react-dom": "18.3.1", "react-google-recaptcha": "^3.1.0", + "react-hook-form": "^7.54.1", "react-i18next": "^15.1.3", "react-infinite-scroll-component": "^6.1.0", "react-pdf": "^9.1.1", @@ -113,7 +115,7 @@ "vite-plugin-checker": "^0.8.0", "vite-plugin-pwa": "^0.20.5", "vite-plugin-static-copy": "^2.0.0", - "zod": "^3.23.8" + "zod": "^3.24.1" }, "engines": { "node": ">=22.11.0" @@ -2646,6 +2648,15 @@ "react-dom": "^18.0.0" } }, + "node_modules/@hookform/resolvers": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.9.1.tgz", + "integrity": "sha512-ud2HqmGBM0P0IABqoskKWI6PEf6ZDDBZkFqe2Vnl+mTHCEHzr3ISjjZyCwTjC/qpL25JC9aIDkloQejvMeq0ug==", + "license": "MIT", + "peerDependencies": { + "react-hook-form": "^7.0.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -3687,6 +3698,24 @@ } } }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", + "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-compose-refs": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", @@ -3752,6 +3781,24 @@ } } }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", + "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-direction": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", @@ -3891,11 +3938,35 @@ } }, "node_modules/@radix-ui/react-label": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.0.tgz", - "integrity": "sha512-peLblDlFw/ngk3UWq0VnYaOLy6agTZZ+MUO/WhVfm14vJGML+xH4FAl2XQGLqdefjNb7ApRg6Yn7U42ZhmYXdw==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.1.tgz", + "integrity": "sha512-UUw5E4e/2+4kFMH7+YxORXGWggtY6sM8WIwh5RZchhLuUg2H1hc98Py+pr8HMz6rdaYrK2t296ZEjYLOCO5uUw==", + "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.0.0" + "@radix-ui/react-primitive": "2.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz", + "integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", @@ -3952,6 +4023,24 @@ } } }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", + "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popover": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.2.tgz", @@ -3988,6 +4077,24 @@ } } }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", + "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popper": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz", @@ -4106,6 +4213,24 @@ } } }, + "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", + "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-roving-focus": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.0.tgz", @@ -4183,12 +4308,12 @@ } }, "node_modules/@radix-ui/react-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", - "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz", + "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0" + "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", @@ -4200,6 +4325,21 @@ } } }, + "node_modules/@radix-ui/react-slot/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", + "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-toast": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.2.tgz", @@ -4268,6 +4408,24 @@ } } }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", + "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-callback-ref": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", @@ -15776,6 +15934,22 @@ "react": ">=16.4.1" } }, + "node_modules/react-hook-form": { + "version": "7.54.1", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.54.1.tgz", + "integrity": "sha512-PUNzFwQeQ5oHiiTUO7GO/EJXGEtuun2Y1A59rLnZBBj+vNEOWt/3ERTiG1/zt7dVeJEM+4vDX/7XQ/qanuvPMg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, "node_modules/react-i18next": { "version": "15.1.3", "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.1.3.tgz", @@ -20931,9 +21105,9 @@ } }, "node_modules/zod": { - "version": "3.23.8", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", - "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", + "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", "dev": true, "license": "MIT", "funding": { diff --git a/package.json b/package.json index e5eaf3342c9..8edf7842d55 100644 --- a/package.json +++ b/package.json @@ -56,15 +56,16 @@ "@googlemaps/typescript-guards": "^2.0.3", "@headlessui/react": "^2.2.0", "@hello-pangea/dnd": "^17.0.0", + "@hookform/resolvers": "^3.9.1", "@pnotify/core": "^5.2.0", "@pnotify/mobile": "^5.2.0", "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-icons": "^1.3.2", - "@radix-ui/react-label": "^2.1.0", + "@radix-ui/react-label": "^2.1.1", "@radix-ui/react-popover": "^1.1.2", "@radix-ui/react-scroll-area": "^1.2.0", - "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-slot": "^1.1.1", "@radix-ui/react-toast": "^1.2.2", "@radix-ui/react-tooltip": "^1.1.4", "@sentry/browser": "^8.42.0", @@ -95,6 +96,7 @@ "react-copy-to-clipboard": "^5.1.0", "react-dom": "18.3.1", "react-google-recaptcha": "^3.1.0", + "react-hook-form": "^7.54.1", "react-i18next": "^15.1.3", "react-infinite-scroll-component": "^6.1.0", "react-pdf": "^9.1.1", @@ -152,7 +154,7 @@ "vite-plugin-checker": "^0.8.0", "vite-plugin-pwa": "^0.20.5", "vite-plugin-static-copy": "^2.0.0", - "zod": "^3.23.8" + "zod": "^3.24.1" }, "browserslist": { "production": [ diff --git a/src/components/Facility/FacilityConfigure.tsx b/src/components/Facility/FacilityConfigure.tsx index 5dc938df99b..501b6959fd3 100644 --- a/src/components/Facility/FacilityConfigure.tsx +++ b/src/components/Facility/FacilityConfigure.tsx @@ -1,109 +1,72 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { useQuery } from "@tanstack/react-query"; import { t } from "i18next"; import { navigate } from "raviger"; -import { useReducer, useState } from "react"; +import { useEffect, useState } from "react"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; + +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; -import { Submit } from "@/components/Common/ButtonV2"; import Loading from "@/components/Common/Loading"; import Page from "@/components/Common/Page"; -import TextFormField from "@/components/Form/FormFields/TextFormField"; -import { FieldChangeEvent } from "@/components/Form/FormFields/Utils"; import { PLUGIN_Component } from "@/PluginEngine"; import * as Notification from "@/Utils/Notifications"; import routes from "@/Utils/request/api"; +import query from "@/Utils/request/query"; import request from "@/Utils/request/request"; -import useTanStackQueryInstead from "@/Utils/request/useQuery"; - -const initForm = { - name: "", - state: 0, - district: 0, - localbody: 0, - ward: 0, - middleware_address: "", -}; -const initialState = { - form: { ...initForm }, - errors: {}, -}; - -const FormReducer = (state = initialState, action: any) => { - switch (action.type) { - case "set_form": { - return { - ...state, - form: action.form, - }; - } - case "set_error": { - return { - ...state, - errors: action.errors, - }; - } - default: - return state; - } -}; +import { Submit } from "../Common/ButtonV2"; interface IProps { facilityId: string; } +const formSchema = z.object({ + middleware_address: z + .string() + .nonempty({ message: "Middleware Address is required" }) + .regex(/^(?!https?:\/\/)[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)*\.[a-zA-Z]{2,}$/, { + message: "Invalid Middleware Address", + }), +}); + export const FacilityConfigure = (props: IProps) => { - const [state, dispatch] = useReducer(FormReducer, initialState); const { facilityId } = props; const [isLoading, setIsLoading] = useState(false); - const { loading } = useTanStackQueryInstead(routes.getPermittedFacility, { - pathParams: { id: facilityId }, - onResponse: (res) => { - if (res.data) { - const formData = { - name: res.data.name, - state: res.data.state, - district: res.data.district, - local_body: res.data.local_body, - ward: res.data.ward, - middleware_address: res.data.middleware_address, - }; - dispatch({ type: "set_form", form: formData }); - } - }, + const { isPending: loading, data } = useQuery({ + queryKey: [routes.getPermittedFacility.path, facilityId], + queryFn: query(routes.getPermittedFacility, { + pathParams: { id: facilityId }, + }), }); - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - setIsLoading(true); - if (!state.form.middleware_address) { - dispatch({ - type: "set_error", - errors: { middleware_address: ["Middleware Address is required"] }, - }); - setIsLoading(false); - return; - } - if ( - state.form.middleware_address.match( - /^(?!https?:\/\/)[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)*\.[a-zA-Z]{2,}$/, - ) === null - ) { - dispatch({ - type: "set_error", - errors: { - middleware_address: ["Invalid Middleware Address"], - }, - }); - setIsLoading(false); - return; - } + const onSubmit = async (values: z.infer<typeof formSchema>) => { + if (!data) return; - const data = { - ...state.form, - middleware_address: state.form.middleware_address, + const formData = { + name: data.name, + state: data.state, + district: data.district, + local_body: data.local_body, + ward: data.ward, + middleware_address: values.middleware_address, }; + setIsLoading(true); + console.log(formData); + const { res, error } = await request(routes.partialUpdateFacility, { pathParams: { id: facilityId }, body: data, @@ -123,14 +86,18 @@ export const FacilityConfigure = (props: IProps) => { setIsLoading(false); }; - const handleChange = (e: FieldChangeEvent<string>) => { - dispatch({ - type: "set_form", - form: { ...state.form, [e.name]: e.value }, - }); - }; + const form = useForm({ + resolver: zodResolver(formSchema), + defaultValues: { middleware_address: "" }, + }); + + useEffect(() => { + if (data && data.middleware_address) { + form.setValue("middleware_address", data.middleware_address); + } + }, [form, data]); - if (isLoading || loading) { + if (isLoading || !data || loading) { return <Loading />; } @@ -138,30 +105,42 @@ export const FacilityConfigure = (props: IProps) => { <Page title="Configure Facility" crumbsReplacements={{ - [facilityId]: { name: state.form.name }, + [facilityId]: { name: data.name }, }} className="w-full overflow-x-hidden" > <div className="mx-auto max-w-3xl"> <div className="cui-card mt-4"> - <form onSubmit={handleSubmit}> - <div className="mt-2 grid grid-cols-1 gap-4"> - <div> - <TextFormField - name="middleware_address" - label="Facility Middleware Address" - message="This address will be applied to all assets when asset and location middleware are Unspecified" - required - value={state.form.middleware_address} - onChange={handleChange} - error={state.errors?.middleware_address} - /> + <Form {...form}> + <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8"> + <FormField + control={form.control} + name="middleware_address" + render={({ field }) => ( + <FormItem> + <FormLabel className="block text-base font-normal text-secondary-900"> + Facility Middleware Address + <span className="text-danger-500"> *</span> + </FormLabel> + <FormControl> + <Input + {...field} + placeholder="Enter Middleware Address" + /> + </FormControl> + <FormDescription> + This address will be applied to all assets when asset and + location middleware are Unspecified. + </FormDescription> + <FormMessage /> + </FormItem> + )} + /> + <div className="flex flex-col gap-3 sm:flex-row sm:justify-end"> + <Submit label="Update" /> </div> - </div> - <div className="flex flex-col gap-3 sm:flex-row sm:justify-end"> - <Submit onClick={handleSubmit} label="Update" /> - </div> - </form> + </form> + </Form> </div> <PLUGIN_Component diff --git a/src/components/ui/form.tsx b/src/components/ui/form.tsx new file mode 100644 index 00000000000..d642a211c7d --- /dev/null +++ b/src/components/ui/form.tsx @@ -0,0 +1,184 @@ +import * as LabelPrimitive from "@radix-ui/react-label"; +import { Slot } from "@radix-ui/react-slot"; +import * as React from "react"; +import { + Controller, + ControllerProps, + FieldPath, + FieldValues, + FormProvider, + useFormContext, +} from "react-hook-form"; + +import { cn } from "@/lib/utils"; + +import { Label } from "@/components/ui/label"; + +const Form = FormProvider; + +type FormFieldContextValue< + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>, +> = { + name: TName; +}; + +const FormFieldContext = React.createContext<FormFieldContextValue>( + {} as FormFieldContextValue, +); + +const FormField = < + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>, +>({ + ...props +}: ControllerProps<TFieldValues, TName>) => { + return ( + <FormFieldContext.Provider value={{ name: props.name }}> + <Controller {...props} /> + </FormFieldContext.Provider> + ); +}; + +const useFormField = () => { + const fieldContext = React.useContext(FormFieldContext); + const itemContext = React.useContext(FormItemContext); + const { getFieldState, formState } = useFormContext(); + + const fieldState = getFieldState(fieldContext.name, formState); + + if (!fieldContext) { + throw new Error("useFormField should be used within <FormField>"); + } + + const { id } = itemContext; + + return { + id, + name: fieldContext.name, + formItemId: `${id}-form-item`, + formDescriptionId: `${id}-form-item-description`, + formMessageId: `${id}-form-item-message`, + ...fieldState, + }; +}; + +type FormItemContextValue = { + id: string; +}; + +const FormItemContext = React.createContext<FormItemContextValue>( + {} as FormItemContextValue, +); + +const FormItem = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes<HTMLDivElement> +>(({ className, ...props }, ref) => { + const id = React.useId(); + + return ( + <FormItemContext.Provider value={{ id }}> + <div ref={ref} className={cn("space-y-2", className)} {...props} /> + </FormItemContext.Provider> + ); +}); +FormItem.displayName = "FormItem"; + +const FormLabel = React.forwardRef< + React.ElementRef<typeof LabelPrimitive.Root>, + React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> +>(({ className, ...props }, ref) => { + const { error, formItemId } = useFormField(); + + return ( + <Label + ref={ref} + className={cn(error && "text-red-500 dark:text-red-900", className)} + htmlFor={formItemId} + {...props} + /> + ); +}); +FormLabel.displayName = "FormLabel"; + +const FormControl = React.forwardRef< + React.ElementRef<typeof Slot>, + React.ComponentPropsWithoutRef<typeof Slot> +>(({ ...props }, ref) => { + const { error, formItemId, formDescriptionId, formMessageId } = + useFormField(); + + return ( + <Slot + ref={ref} + id={formItemId} + aria-describedby={ + !error + ? `${formDescriptionId}` + : `${formDescriptionId} ${formMessageId}` + } + aria-invalid={!!error} + {...props} + /> + ); +}); +FormControl.displayName = "FormControl"; + +const FormDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes<HTMLParagraphElement> +>(({ className, ...props }, ref) => { + const { formDescriptionId } = useFormField(); + + return ( + <p + ref={ref} + id={formDescriptionId} + className={cn( + "text-[0.8rem] text-gray-500 dark:text-gray-400", + className, + )} + {...props} + /> + ); +}); +FormDescription.displayName = "FormDescription"; + +const FormMessage = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes<HTMLParagraphElement> +>(({ className, children, ...props }, ref) => { + const { error, formMessageId } = useFormField(); + const body = error ? String(error?.message) : children; + + if (!body) { + return null; + } + + return ( + <p + ref={ref} + id={formMessageId} + className={cn( + "text-[0.8rem] font-medium text-red-500 dark:text-red-900", + className, + )} + {...props} + > + {body} + </p> + ); +}); +FormMessage.displayName = "FormMessage"; + +export { + useFormField, + Form, + FormItem, + FormLabel, + FormControl, + FormDescription, + FormMessage, + FormField, +}; From c3d564c5c7f412b376c38c68a747dd95fb1b841c Mon Sep 17 00:00:00 2001 From: Saikiran Patil <84263946+saikiranpatil@users.noreply.github.com> Date: Wed, 18 Dec 2024 15:59:16 +0530 Subject: [PATCH 14/16] minor changes --- src/components/Facility/FacilityConfigure.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/Facility/FacilityConfigure.tsx b/src/components/Facility/FacilityConfigure.tsx index 501b6959fd3..d6bab357bfd 100644 --- a/src/components/Facility/FacilityConfigure.tsx +++ b/src/components/Facility/FacilityConfigure.tsx @@ -65,11 +65,10 @@ export const FacilityConfigure = (props: IProps) => { }; setIsLoading(true); - console.log(formData); const { res, error } = await request(routes.partialUpdateFacility, { pathParams: { id: facilityId }, - body: data, + body: formData, }); setIsLoading(false); From 9aca644707c6f85bac7b8da08ef0353eae255884 Mon Sep 17 00:00:00 2001 From: Saikiran Patil <84263946+saikiranpatil@users.noreply.github.com> Date: Tue, 24 Dec 2024 14:14:45 +0530 Subject: [PATCH 15/16] used useMutation and shadcn button in faciility configuration page --- src/components/Facility/FacilityConfigure.tsx | 58 ++++++++++--------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/src/components/Facility/FacilityConfigure.tsx b/src/components/Facility/FacilityConfigure.tsx index d6bab357bfd..8dee8f47163 100644 --- a/src/components/Facility/FacilityConfigure.tsx +++ b/src/components/Facility/FacilityConfigure.tsx @@ -1,11 +1,13 @@ import { zodResolver } from "@hookform/resolvers/zod"; -import { useQuery } from "@tanstack/react-query"; +import { useMutation, useQuery } from "@tanstack/react-query"; import { t } from "i18next"; import { navigate } from "raviger"; -import { useEffect, useState } from "react"; +import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; +import CareIcon from "@/CAREUI/icons/CareIcon"; + import { Form, FormControl, @@ -23,10 +25,10 @@ import Page from "@/components/Common/Page"; import { PLUGIN_Component } from "@/PluginEngine"; import * as Notification from "@/Utils/Notifications"; import routes from "@/Utils/request/api"; +import mutate from "@/Utils/request/mutate"; import query from "@/Utils/request/query"; -import request from "@/Utils/request/request"; -import { Submit } from "../Common/ButtonV2"; +import { Button } from "../ui/button"; interface IProps { facilityId: string; @@ -43,16 +45,27 @@ const formSchema = z.object({ export const FacilityConfigure = (props: IProps) => { const { facilityId } = props; - const [isLoading, setIsLoading] = useState(false); - const { isPending: loading, data } = useQuery({ + const { isPending: queryLoading, data } = useQuery({ queryKey: [routes.getPermittedFacility.path, facilityId], queryFn: query(routes.getPermittedFacility, { pathParams: { id: facilityId }, }), }); - const onSubmit = async (values: z.infer<typeof formSchema>) => { + const { isPending: mutateLoading, mutate: updateFacility } = useMutation({ + mutationFn: mutate(routes.partialUpdateFacility, { + pathParams: { id: facilityId }, + }), + onSuccess: () => { + Notification.Success({ + msg: t("update_facility_middleware_success"), + }); + navigate(`/facility/${facilityId}`); + }, + }); + + const onSubmit = (values: z.infer<typeof formSchema>) => { if (!data) return; const formData = { @@ -64,25 +77,7 @@ export const FacilityConfigure = (props: IProps) => { middleware_address: values.middleware_address, }; - setIsLoading(true); - - const { res, error } = await request(routes.partialUpdateFacility, { - pathParams: { id: facilityId }, - body: formData, - }); - - setIsLoading(false); - if (res?.ok) { - Notification.Success({ - msg: t("update_facility_middleware_success"), - }); - navigate(`/facility/${facilityId}`); - } else { - Notification.Error({ - msg: error?.detail ?? "Something went wrong", - }); - } - setIsLoading(false); + updateFacility(formData); }; const form = useForm({ @@ -96,7 +91,7 @@ export const FacilityConfigure = (props: IProps) => { } }, [form, data]); - if (isLoading || !data || loading) { + if (queryLoading || !data || mutateLoading) { return <Loading />; } @@ -136,7 +131,14 @@ export const FacilityConfigure = (props: IProps) => { )} /> <div className="flex flex-col gap-3 sm:flex-row sm:justify-end"> - <Submit label="Update" /> + <Button + variant="primary" + type="submit" + className="bg-primary-500 gap-2 px-4 py-2 rounded-sm hover:bg-primary-400" + > + <CareIcon icon="l-check-circle" className="text-lg" /> + Update + </Button> </div> </form> </Form> From ccf886f9c6382454e8e76f00a57610e429c218e2 Mon Sep 17 00:00:00 2001 From: Saikiran Patil <84263946+saikiranpatil@users.noreply.github.com> Date: Sat, 28 Dec 2024 22:47:15 +0530 Subject: [PATCH 16/16] used shadcn buttons in location management page --- src/components/Common/AuthorizedButton.tsx | 2 +- src/components/Common/CancelButton.tsx | 32 ++++++ src/components/Facility/FacilityConfigure.tsx | 3 +- .../Facility/LocationManagement.tsx | 107 +++++++++++------- 4 files changed, 99 insertions(+), 45 deletions(-) create mode 100644 src/components/Common/CancelButton.tsx diff --git a/src/components/Common/AuthorizedButton.tsx b/src/components/Common/AuthorizedButton.tsx index 2b611a0c9f2..94eca834f00 100644 --- a/src/components/Common/AuthorizedButton.tsx +++ b/src/components/Common/AuthorizedButton.tsx @@ -5,7 +5,7 @@ import AuthorizedChild from "@/CAREUI/misc/AuthorizedChild"; import { AuthorizedElementProps } from "@/Utils/AuthorizeFor"; export const AuthorizedButton: React.FC< - AuthorizedElementProps & ButtonProps + AuthorizedElementProps & ButtonProps & { variant?: string } > = ({ authorizeFor = () => true, ...props }) => { return ( <AuthorizedChild authorizeFor={authorizeFor}> diff --git a/src/components/Common/CancelButton.tsx b/src/components/Common/CancelButton.tsx new file mode 100644 index 00000000000..092fbac5398 --- /dev/null +++ b/src/components/Common/CancelButton.tsx @@ -0,0 +1,32 @@ +import { useTranslation } from "react-i18next"; + +import { cn } from "@/lib/utils"; + +import CareIcon from "@/CAREUI/icons/CareIcon"; + +import { Button, ButtonProps } from "@/components/ui/button"; + +export interface CancelButtonProps extends ButtonProps { + label?: string; +} + +const CancelButton = ({ label = "Cancel", ...props }: CancelButtonProps) => { + const { t } = useTranslation(); + return ( + <Button + id="cancel" + type="button" + variant="secondary" + {...props} + className={cn( + "w-full md:w-auto button-secondary-border", + props.className, + )} + > + <CareIcon icon="l-times-circle" className="text-lg" /> + {label && <span className="whitespace-pre-wrap">{t(label)}</span>} + </Button> + ); +}; + +export default CancelButton; diff --git a/src/components/Facility/FacilityConfigure.tsx b/src/components/Facility/FacilityConfigure.tsx index 8dee8f47163..af4816de0c7 100644 --- a/src/components/Facility/FacilityConfigure.tsx +++ b/src/components/Facility/FacilityConfigure.tsx @@ -8,6 +8,7 @@ import { z } from "zod"; import CareIcon from "@/CAREUI/icons/CareIcon"; +import { Button } from "@/components/ui/button"; import { Form, FormControl, @@ -28,8 +29,6 @@ import routes from "@/Utils/request/api"; import mutate from "@/Utils/request/mutate"; import query from "@/Utils/request/query"; -import { Button } from "../ui/button"; - interface IProps { facilityId: string; } diff --git a/src/components/Facility/LocationManagement.tsx b/src/components/Facility/LocationManagement.tsx index e9c1323142f..36cd3ce1c26 100644 --- a/src/components/Facility/LocationManagement.tsx +++ b/src/components/Facility/LocationManagement.tsx @@ -1,4 +1,5 @@ import { useQuery } from "@tanstack/react-query"; +import { navigate } from "raviger"; import { useState } from "react"; import RecordMeta from "@/CAREUI/display/RecordMeta"; @@ -6,8 +7,16 @@ import CareIcon from "@/CAREUI/icons/CareIcon"; import PaginatedList from "@/CAREUI/misc/PaginatedList"; import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; -import ButtonV2, { Cancel } from "@/components/Common/ButtonV2"; +import { AuthorizedButton } from "@/components/Common/AuthorizedButton"; +import CancelButton from "@/components/Common/CancelButton"; import ConfirmDialog from "@/components/Common/ConfirmDialog"; import DialogModal from "@/components/Common/Dialog"; import Loading from "@/components/Common/Loading"; @@ -101,26 +110,26 @@ export default function LocationManagement({ facilityId }: Props) { title="Location Management" backUrl={`/facility/${facilityId}`} options={ - <ButtonV2 + <AuthorizedButton id="add-new-location" - href={`/facility/${facilityId}/location/add`} + onClick={() => navigate(`/facility/${facilityId}/location/add`)} authorizeFor={NonReadOnlyUsers} className="hidden lg:block" > <CareIcon icon="l-plus" className="text-lg" /> Add New Location - </ButtonV2> + </AuthorizedButton> } > <div className="mx-auto mt-4 lg:mt-0"> - <ButtonV2 - href={`/facility/${facilityId}/location/add`} + <AuthorizedButton + onClick={() => navigate(`/facility/${facilityId}/location/add`)} authorizeFor={NonReadOnlyUsers} className="w-full lg:hidden" > <CareIcon icon="l-plus" className="text-lg" /> Add New Location - </ButtonV2> + </AuthorizedButton> </div> <div className="w-full @container"> <PaginatedList.WhenEmpty className="flex w-full justify-center border-b border-secondary-200 bg-white p-5 text-center text-2xl font-bold text-secondary-500"> @@ -186,19 +195,23 @@ export default function LocationManagement({ facilityId }: Props) { deleted first before the location can be removed </div> <div className="mt-2 flex flex-col justify-end gap-2 md:flex-row"> - <Cancel + <CancelButton onClick={() => { closeDeleteFailModal(); }} /> - <ButtonV2 + <AuthorizedButton id="manage-beds" - href={`/facility/${facilityId}/location/${showDeleteFailModal.id}/beds`} + onClick={() => + navigate( + `/facility/${facilityId}/location/${showDeleteFailModal.id}/beds`, + ) + } authorizeFor={NonReadOnlyUsers} className="w-full" > Manage Beds - </ButtonV2> + </AuthorizedButton> </div> </div> ) : ( @@ -208,19 +221,23 @@ export default function LocationManagement({ facilityId }: Props) { be deleted first before the location can be removed </div> <div className="mt-2 flex flex-col justify-end gap-2 md:flex-row"> - <Cancel + <CancelButton onClick={() => { closeDeleteFailModal(); }} /> - <ButtonV2 + <AuthorizedButton id="manage-assets" - href={`/assets?page=1&limit=18&facility=${facilityId}&asset_type=&asset_class=&status=&location=${showDeleteFailModal.id}&warranty_amc_end_of_validity_before=&warranty_amc_end_of_validity_after=`} + onClick={() => + navigate( + `/assets?page=1&limit=18&facility=${facilityId}&asset_type=&asset_class=&status=&location=${showDeleteFailModal.id}&warranty_amc_end_of_validity_before=&warranty_amc_end_of_validity_after=`, + ) + } authorizeFor={NonReadOnlyUsers} className="w-full" > Manage Assets - </ButtonV2> + </AuthorizedButton> </div> </div> )} @@ -305,12 +322,11 @@ const Location = ({ /> </div> - <ButtonV2 + <Button id="manage-bed-button" variant="secondary" - border - className="mt-3 flex w-full items-center justify-between" - href={`location/${id}/beds`} + className="mt-3 flex w-full items-center justify-between button-secondary-border" + onClick={() => navigate(`location/${id}/beds`)} disabled={totalBeds == null} > Manage Beds @@ -318,39 +334,46 @@ const Location = ({ <CareIcon icon="l-bed" className="text-lg" /> {totalBeds ?? "--"} </span> - </ButtonV2> + </Button> <div className="mt-2 flex w-full flex-col gap-2 md:flex-row"> <div className="w-full md:w-1/2"> - <ButtonV2 + <AuthorizedButton id="edit-location-button" variant="secondary" - border - className="w-full" - href={`location/${id}/update`} + className="w-full button-secondary-border" + onClick={() => navigate(`location/${id}/update`)} authorizeFor={NonReadOnlyUsers} > <CareIcon icon="l-pen" className="text-lg" /> Edit - </ButtonV2> + </AuthorizedButton> </div> <div className="w-full md:w-1/2"> - <ButtonV2 - authorizeFor={AuthorizeFor(["DistrictAdmin", "StateAdmin"])} - id="delete-location-button" - variant="secondary" - border - className="w-full" - tooltip={ - disabled ? "Contact your admin to delete the location" : "" - } - tooltipClassName=" text-xs w-full lg:w-auto" - onClick={() => - setShowDeletePopup({ open: true, name: name ?? "", id: id ?? "" }) - } - > - <CareIcon icon="l-trash" className="text-lg" /> - Delete - </ButtonV2> + <TooltipProvider> + <Tooltip> + <TooltipTrigger asChild> + <AuthorizedButton + authorizeFor={AuthorizeFor(["DistrictAdmin", "StateAdmin"])} + id="delete-location-button" + variant="secondary" + className="w-full button-secondary-border" + onClick={() => + setShowDeletePopup({ + open: true, + name: name ?? "", + id: id ?? "", + }) + } + > + <CareIcon icon="l-trash" className="text-lg" /> + Delete + </AuthorizedButton> + </TooltipTrigger> + <TooltipContent className="text-xs w-full lg:w-auto"> + {disabled ? "Contact your admin to delete the location" : ""} + </TooltipContent> + </Tooltip> + </TooltipProvider> </div> </div>