From 3b413474f67f917b3539d1a380e2768c4a28c69d Mon Sep 17 00:00:00 2001 From: totregex Date: Sun, 24 Nov 2024 10:28:40 +0530 Subject: [PATCH 01/23] fix api call issue #9198 --- src/components/Facility/AssetCreate.tsx | 147 +++++++++++++++++++++--- 1 file changed, 129 insertions(+), 18 deletions(-) diff --git a/src/components/Facility/AssetCreate.tsx b/src/components/Facility/AssetCreate.tsx index 5c507bb0a18..4e2f2372ec2 100644 --- a/src/components/Facility/AssetCreate.tsx +++ b/src/components/Facility/AssetCreate.tsx @@ -1,5 +1,6 @@ import { IDetectedBarcode, Scanner } from "@yudiel/react-qr-scanner"; import { navigate } from "raviger"; +import { useRef } from "react"; import { LegacyRef, MutableRefObject, @@ -109,6 +110,45 @@ const AssetCreate = (props: AssetProps) => { let assetClassInitial: AssetClass; + const [initialValues, setInitialValues] = useState<{ + name: string; + description: string; + location: string; + asset_class: AssetClass | undefined; + is_working: string | undefined; + not_working_reason: string; + serial_number: string; + vendor_name: string; + support_name: string; + support_email: string; + support_phone: string; + qr_code_id: string; + manufacturer: string; + warranty_amc_end_of_validity: any; + last_serviced_on: any; + notes: string; + }>({ + name: "", + description: "", + location: "", + asset_class: undefined, + is_working: undefined, + not_working_reason: "", + serial_number: "", + vendor_name: "", + support_name: "", + support_email: "", + support_phone: "", + qr_code_id: "", + manufacturer: "", + warranty_amc_end_of_validity: null, + last_serviced_on: null, + notes: "", + }); + + const [isButtonEnabled, setIsButtonEnabled] = useState(false); + const isInitialLoad = useRef(true); + const [state, dispatch] = useReducer(asset_create_reducer, initialState); const [name, setName] = useState(""); const [asset_class, setAssetClass] = useState(); @@ -182,27 +222,96 @@ const AssetCreate = (props: AssetProps) => { onResponse: ({ data: asset }) => { if (!asset) return; - setName(asset.name); - setDescription(asset.description); - setLocation(asset.location_object.id!); - setAssetClass(asset.asset_class); - setIsWorking(String(asset.is_working)); - setNotWorkingReason(asset.not_working_reason); - setSerialNumber(asset.serial_number); - setVendorName(asset.vendor_name); - setSupportName(asset.support_name); - setSupportEmail(asset.support_email); - setSupportPhone(asset.support_phone); - setQrCodeId(asset.qr_code_id); - setManufacturer(asset.manufacturer); - asset.warranty_amc_end_of_validity && - setWarrantyAmcEndOfValidity(asset.warranty_amc_end_of_validity); - asset.last_service?.serviced_on && - setLastServicedOn(asset.last_service?.serviced_on); - asset.last_service?.note && setNotes(asset.last_service?.note); + const fetchedValues = { + name: asset.name || "", + description: asset.description || "", + location: asset.location_object.id! || "", + asset_class: asset.asset_class, + is_working: String(asset.is_working), + not_working_reason: asset.not_working_reason || "", + serial_number: asset.serial_number || "", + vendor_name: asset.vendor_name || "", + support_name: asset.support_name || "", + support_email: asset.support_email || "", + support_phone: asset.support_phone || "", + qr_code_id: asset.qr_code_id || "", + manufacturer: asset.manufacturer || "", + warranty_amc_end_of_validity: asset.warranty_amc_end_of_validity, + last_serviced_on: asset.last_service?.serviced_on || null, + notes: asset.last_service?.note || "", + }; + + setInitialValues(fetchedValues); + + setName(fetchedValues.name); + setDescription(fetchedValues.description); + setLocation(fetchedValues.location); + setAssetClass(fetchedValues.asset_class); + setIsWorking(fetchedValues.is_working); + setNotWorkingReason(fetchedValues.not_working_reason); + setSerialNumber(fetchedValues.serial_number); + setVendorName(fetchedValues.vendor_name); + setSupportName(fetchedValues.support_name); + setSupportEmail(fetchedValues.support_email); + setSupportPhone(fetchedValues.support_phone); + setQrCodeId(fetchedValues.qr_code_id); + setManufacturer(fetchedValues.manufacturer); + setWarrantyAmcEndOfValidity(fetchedValues.warranty_amc_end_of_validity); + setLastServicedOn(fetchedValues.last_serviced_on); + setNotes(fetchedValues.notes); + + isInitialLoad.current = false; }, }); + useEffect(() => { + if (isInitialLoad.current) return; + + const currentValues = { + name, + description, + location, + asset_class, + is_working, + not_working_reason, + serial_number, + vendor_name, + support_name, + support_email, + support_phone, + qr_code_id: qrCodeId, + manufacturer, + warranty_amc_end_of_validity, + last_serviced_on, + notes, + }; + console.log("Initial Values " + currentValues); + + const isChanged = ( + Object.keys(currentValues) as Array + ).some((key) => currentValues[key] !== initialValues[key]); + + setIsButtonEnabled(isChanged); + }, [ + name, + description, + location, + asset_class, + is_working, + not_working_reason, + serial_number, + vendor_name, + support_name, + support_email, + support_phone, + qrCodeId, + manufacturer, + warranty_amc_end_of_validity, + last_serviced_on, + notes, + initialValues, + ]); + const validateForm = () => { const errors = { ...initError }; let invalidForm = false; @@ -893,12 +1002,14 @@ const AssetCreate = (props: AssetProps) => { handleSubmit(e, false)} label={assetId ? t("update") : t("create_asset")} + disabled={!isButtonEnabled} /> {!assetId && ( handleSubmit(e, true)} label={t("create_add_more")} + disabled={!isButtonEnabled} /> )} From 20634fb6c6796b9267d65a7dfef669d7b706193b Mon Sep 17 00:00:00 2001 From: totregex Date: Mon, 25 Nov 2024 16:28:22 +0530 Subject: [PATCH 02/23] make assetCreate page to use Form component --- src/components/Facility/AssetCreate.tsx | 1413 ++++++++++++----------- 1 file changed, 742 insertions(+), 671 deletions(-) diff --git a/src/components/Facility/AssetCreate.tsx b/src/components/Facility/AssetCreate.tsx index 4e2f2372ec2..3747be6e2b2 100644 --- a/src/components/Facility/AssetCreate.tsx +++ b/src/components/Facility/AssetCreate.tsx @@ -1,13 +1,11 @@ import { IDetectedBarcode, Scanner } from "@yudiel/react-qr-scanner"; import { navigate } from "raviger"; -import { useRef } from "react"; import { LegacyRef, MutableRefObject, RefObject, createRef, useEffect, - useReducer, useState, } from "react"; import { useTranslation } from "react-i18next"; @@ -15,7 +13,6 @@ import { useTranslation } from "react-i18next"; import CareIcon, { IconName } from "@/CAREUI/icons/CareIcon"; import { AssetClass, AssetType } from "@/components/Assets/AssetTypes"; -import { Cancel, Submit } from "@/components/Common/ButtonV2"; import Loading from "@/components/Common/Loading"; import { LocationSelect } from "@/components/Common/LocationSelect"; import Page from "@/components/Common/Page"; @@ -43,6 +40,10 @@ import request from "@/Utils/request/request"; import useQuery from "@/Utils/request/useQuery"; import { dateQueryString, parsePhoneNumber } from "@/Utils/utils"; +import { FieldError, RequiredFieldValidator } from "../Form/FieldValidators"; +import Form from "../Form/Form"; +import { FormErrors } from "../Form/Utils"; + const formErrorKeys = [ "name", "asset_class", @@ -56,17 +57,35 @@ const formErrorKeys = [ "support_email", "manufacturer", "warranty_amc_end_of_validity", - "last_serviced_on", + "last_serviced", "notes", ]; -const initError = formErrorKeys.reduce( - (acc: { [key: string]: string }, key) => { - acc[key] = ""; - return acc; - }, - {}, -); +interface AssetT { + id?: string; + name?: string; + location?: string; + description?: string; + is_working?: boolean; + not_working_reason?: string; + created_date?: string; + modified_date?: string; + serial_number?: string; + asset_type?: AssetType; + asset_class?: AssetClass; + status?: "ACTIVE" | "TRANSFER_IN_PROGRESS"; + vendor_name?: string; + support_name?: string; + support_email?: string; + support_phone?: string; + qr_code_id?: string; + manufacturer?: string; + warranty_amc_end_of_validity?: string; + serviced_on?: string; + latest_status?: string; + last_service?: any; + notes: string; +} const fieldRef = formErrorKeys.reduce( (acc: { [key: string]: RefObject }, key) => { @@ -76,27 +95,27 @@ const fieldRef = formErrorKeys.reduce( {}, ); -const initialState = { - errors: { ...initError }, -}; +// const initialState = { +// errors: { ...initError }, +// }; interface AssetProps { facilityId: string; assetId?: string; } -const asset_create_reducer = (state = initialState, action: any) => { - switch (action.type) { - case "set_error": { - return { - ...state, - errors: action.errors, - }; - } - default: - return state; - } -}; +// const asset_create_reducer = (state = initialState, action: any) => { +// switch (action.type) { +// case "set_error": { +// return { +// ...state, +// errors: action.errors, +// }; +// } +// default: +// return state; +// } +// }; type AssetFormSection = | "General Details" @@ -108,66 +127,55 @@ const AssetCreate = (props: AssetProps) => { const { t } = useTranslation(); const { facilityId, assetId } = props; - let assetClassInitial: AssetClass; - - const [initialValues, setInitialValues] = useState<{ - name: string; - description: string; - location: string; - asset_class: AssetClass | undefined; - is_working: string | undefined; - not_working_reason: string; - serial_number: string; - vendor_name: string; - support_name: string; - support_email: string; - support_phone: string; - qr_code_id: string; - manufacturer: string; - warranty_amc_end_of_validity: any; - last_serviced_on: any; - notes: string; - }>({ + const initialAssetData: AssetT = { + id: "", name: "", - description: "", location: "", - asset_class: undefined, - is_working: undefined, + description: "", + is_working: false, not_working_reason: "", + created_date: "", + modified_date: "", serial_number: "", + asset_type: undefined, + asset_class: undefined, + status: "ACTIVE", vendor_name: "", support_name: "", support_email: "", support_phone: "", qr_code_id: "", manufacturer: "", - warranty_amc_end_of_validity: null, - last_serviced_on: null, + warranty_amc_end_of_validity: "", + latest_status: "", + last_service: null, + serviced_on: "", notes: "", - }); + }; + + // State to store asset data + const [initAssetData, setinitialAssetData] = + useState(initialAssetData); - const [isButtonEnabled, setIsButtonEnabled] = useState(false); - const isInitialLoad = useRef(true); - - const [state, dispatch] = useReducer(asset_create_reducer, initialState); - const [name, setName] = useState(""); - const [asset_class, setAssetClass] = useState(); - const [not_working_reason, setNotWorkingReason] = useState(""); - const [description, setDescription] = useState(""); - const [is_working, setIsWorking] = useState(undefined); - const [serial_number, setSerialNumber] = useState(""); - const [vendor_name, setVendorName] = useState(""); - const [support_name, setSupportName] = useState(""); - const [support_phone, setSupportPhone] = useState(""); - const [support_email, setSupportEmail] = useState(""); - const [location, setLocation] = useState(""); + // const [state, dispatch] = useReducer(asset_create_reducer, initialState); + // const [name, setName] = useState(""); + // const [asset_class, setAssetClass] = useState(); + // const [not_working_reason, setNotWorkingReason] = useState(""); + // const [description, setDescription] = useState(""); + // const [is_working, setIsWorking] = useState(undefined); + // const [serial_number, setSerialNumber] = useState(""); + // const [vendor_name, setVendorName] = useState(""); + // const [support_name, setSupportName] = useState(""); + // const [support_phone, setSupportPhone] = useState(""); + // const [support_email, setSupportEmail] = useState(""); + // const [location, setLocation] = useState(""); const [isLoading, setIsLoading] = useState(false); - const [qrCodeId, setQrCodeId] = useState(""); - const [manufacturer, setManufacturer] = useState(""); - const [warranty_amc_end_of_validity, setWarrantyAmcEndOfValidity] = - useState(null); - const [last_serviced_on, setLastServicedOn] = useState(null); - const [notes, setNotes] = useState(""); + // const [qrCodeId, setQrCodeId] = useState(""); + // const [manufacturer, setManufacturer] = useState(""); + // const [warranty_amc_end_of_validity, setWarrantyAmcEndOfValidity] = + // useState(null); + // const [last_serviced_on, setLastServicedOn] = useState(null); + // const [notes, setNotes] = useState(""); const [isScannerActive, setIsScannerActive] = useState(false); const [currentSection, setCurrentSection] = @@ -216,255 +224,327 @@ const AssetCreate = (props: AssetProps) => { query: { limit: 1 }, }); + // const assetQuery = useQuery(routes.getAsset, { + // pathParams: { external_id: assetId! }, + // prefetch: !!assetId, + // onResponse: ({ data: asset }) => { + // if (!asset) return; + + // setName(asset.name); + // setDescription(asset.description); + // setLocation(asset.location_object.id!); + // setAssetClass(asset.asset_class); + // setIsWorking(String(asset.is_working)); + // setNotWorkingReason(asset.not_working_reason); + // setSerialNumber(asset.serial_number); + // setVendorName(asset.vendor_name); + // setSupportName(asset.support_name); + // setSupportEmail(asset.support_email); + // setSupportPhone(asset.support_phone); + // setQrCodeId(asset.qr_code_id); + // setManufacturer(asset.manufacturer); + // asset.warranty_amc_end_of_validity && + // setWarrantyAmcEndOfValidity(asset.warranty_amc_end_of_validity); + // asset.last_service?.serviced_on && + // setLastServicedOn(asset.last_service?.serviced_on); + // asset.last_service?.note && setNotes(asset.last_service?.note); + // }, + // }); + const assetQuery = useQuery(routes.getAsset, { pathParams: { external_id: assetId! }, prefetch: !!assetId, onResponse: ({ data: asset }) => { if (!asset) return; - const fetchedValues = { - name: asset.name || "", - description: asset.description || "", - location: asset.location_object.id! || "", + // Update the state with the fetched data + setinitialAssetData({ + id: asset.id, + name: asset.name, + location: asset.location_object?.id, + description: asset.description, + is_working: asset.is_working, + not_working_reason: asset.not_working_reason, + created_date: asset.created_date, + modified_date: asset.modified_date, + serial_number: asset.serial_number, + asset_type: asset.asset_type, asset_class: asset.asset_class, - is_working: String(asset.is_working), - not_working_reason: asset.not_working_reason || "", - serial_number: asset.serial_number || "", - vendor_name: asset.vendor_name || "", - support_name: asset.support_name || "", - support_email: asset.support_email || "", - support_phone: asset.support_phone || "", - qr_code_id: asset.qr_code_id || "", - manufacturer: asset.manufacturer || "", + status: asset.status, + vendor_name: asset.vendor_name, + support_name: asset.support_name, + support_email: asset.support_email, + support_phone: asset.support_phone, + qr_code_id: asset.qr_code_id, + manufacturer: asset.manufacturer, warranty_amc_end_of_validity: asset.warranty_amc_end_of_validity, - last_serviced_on: asset.last_service?.serviced_on || null, - notes: asset.last_service?.note || "", - }; - - setInitialValues(fetchedValues); - - setName(fetchedValues.name); - setDescription(fetchedValues.description); - setLocation(fetchedValues.location); - setAssetClass(fetchedValues.asset_class); - setIsWorking(fetchedValues.is_working); - setNotWorkingReason(fetchedValues.not_working_reason); - setSerialNumber(fetchedValues.serial_number); - setVendorName(fetchedValues.vendor_name); - setSupportName(fetchedValues.support_name); - setSupportEmail(fetchedValues.support_email); - setSupportPhone(fetchedValues.support_phone); - setQrCodeId(fetchedValues.qr_code_id); - setManufacturer(fetchedValues.manufacturer); - setWarrantyAmcEndOfValidity(fetchedValues.warranty_amc_end_of_validity); - setLastServicedOn(fetchedValues.last_serviced_on); - setNotes(fetchedValues.notes); - - isInitialLoad.current = false; + latest_status: asset.latest_status, + last_service: asset.last_service, + serviced_on: asset.last_service?.serviced_on, + notes: asset.last_service?.note, + }); }, }); - useEffect(() => { - if (isInitialLoad.current) return; - - const currentValues = { - name, - description, - location, - asset_class, - is_working, - not_working_reason, - serial_number, - vendor_name, - support_name, - support_email, - support_phone, - qr_code_id: qrCodeId, - manufacturer, - warranty_amc_end_of_validity, - last_serviced_on, - notes, - }; - console.log("Initial Values " + currentValues); - - const isChanged = ( - Object.keys(currentValues) as Array - ).some((key) => currentValues[key] !== initialValues[key]); - - setIsButtonEnabled(isChanged); - }, [ - name, - description, - location, - asset_class, - is_working, - not_working_reason, - serial_number, - vendor_name, - support_name, - support_email, - support_phone, - qrCodeId, - manufacturer, - warranty_amc_end_of_validity, - last_serviced_on, - notes, - initialValues, - ]); - - const validateForm = () => { - const errors = { ...initError }; - let invalidForm = false; - Object.keys(state.errors).forEach((field) => { - switch (field) { - case "name": - if (!name) { - errors[field] = "Asset name can't be empty"; - invalidForm = true; - } - return; - case "is_working": - if (is_working === undefined) { - errors[field] = t("field_required"); - invalidForm = true; - } - return; - case "location": - if (!location || location === "0" || location === "") { - errors[field] = "Select a location"; - invalidForm = true; - } - return; - case "support_phone": { - if (!support_phone) { - errors[field] = t("field_required"); - invalidForm = true; - } - // eslint-disable-next-line no-case-declarations - const checkTollFree = support_phone.startsWith("1800"); - const supportPhoneSimple = support_phone - .replace(/[^0-9]/g, "") - .slice(2); - if (supportPhoneSimple.length != 10 && !checkTollFree) { - errors[field] = "Please enter valid phone number"; - invalidForm = true; - } else if ( - (support_phone.length < 10 || support_phone.length > 11) && - checkTollFree - ) { - errors[field] = "Please enter valid phone number"; - invalidForm = true; - } - return; - } - case "support_email": - if (support_email && !validateEmailAddress(support_email)) { - errors[field] = "Please enter valid email id"; - invalidForm = true; - } - return; - case "last_serviced_on": - if (notes && !last_serviced_on) { - errors[field] = "Last serviced on date is require with notes"; - invalidForm = true; - } - return; - default: - return; - } - }); - if (invalidForm) { - dispatch({ type: "set_error", errors }); - const firstError = Object.keys(errors).find((key) => errors[key]); - if (firstError) { - fieldRef[firstError].current?.scrollIntoView({ - behavior: "smooth", - block: "center", - }); + const AssetFormValidator = (form: AssetT): FormErrors => { + const errors: Partial> = {}; // Initialize error object + + errors.name = RequiredFieldValidator()(form.name); + + if (form.is_working === undefined) { + errors.is_working = t("field_required"); + } + + if (!form.location || form.location === "0" || form.location === "") { + errors.location = "Select a location"; + } + + if (!form.support_phone) { + errors.support_phone = t("field_required"); + } else { + const checkTollFree = form.support_phone.startsWith("1800"); + const supportPhoneSimple = form.support_phone + .replace(/[^0-9]/g, "") + .slice(2); + if (supportPhoneSimple.length !== 10 && !checkTollFree) { + errors.support_phone = "Please enter valid phone number"; + } else if ( + (form.support_phone.length < 10 || form.support_phone.length > 11) && + checkTollFree + ) { + errors.support_phone = "Please enter valid phone number"; } - return false; } - dispatch({ type: "set_error", errors }); - return true; - }; - const resetFilters = () => { - setName(""); - setDescription(""); - setLocation(""); - setAssetClass(assetClassInitial); - setIsWorking(undefined); - setNotWorkingReason(""); - setSerialNumber(""); - setVendorName(""); - setSupportName(""); - setSupportEmail(""); - setSupportPhone(""); - setQrCodeId(""); - setManufacturer(""); - setWarrantyAmcEndOfValidity(""); - setLastServicedOn(""); - setNotes(""); - setWarrantyAmcEndOfValidity(null); - setLastServicedOn(null); + if (form.support_email && !validateEmailAddress(form.support_email)) { + errors.support_email = "Please enter valid email id"; + } + + if (form.notes && !form.last_service) { + errors.serviced_on = "Last serviced on date is required with notes"; + } + + return errors; }; - const handleSubmit = async (e: React.SyntheticEvent, addMore: boolean) => { - e.preventDefault(); - const validated = validateForm(); - if (validated) { - setIsLoading(true); - const data: any = { - name: name, - asset_type: AssetType.INTERNAL, - asset_class: asset_class || "", - description: description, - is_working: is_working, - not_working_reason: is_working === "true" ? "" : not_working_reason, - serial_number: serial_number, - location: location, - vendor_name: vendor_name, - support_name: support_name, - support_email: support_email, - support_phone: support_phone.startsWith("1800") - ? support_phone - : parsePhoneNumber(support_phone), - qr_code_id: qrCodeId !== "" ? qrCodeId : null, - manufacturer: manufacturer, - warranty_amc_end_of_validity: warranty_amc_end_of_validity - ? dateQueryString(warranty_amc_end_of_validity) - : null, - }; - - if (last_serviced_on) { - data["last_serviced_on"] = dateQueryString(last_serviced_on); - data["note"] = notes ?? ""; - } + // const validateForm = () => { + // const errors = { ...initError }; + // let invalidForm = false; + // Object.keys(state.errors).forEach((field) => { + // switch (field) { + // case "name": + // if (!name) { + // errors[field] = "Asset name can't be empty"; + // invalidForm = true; + // } + // return; + // case "is_working": + // if (is_working === undefined) { + // errors[field] = t("field_required"); + // invalidForm = true; + // } + // return; + // case "location": + // if (!location || location === "0" || location === "") { + // errors[field] = "Select a location"; + // invalidForm = true; + // } + // return; + // case "support_phone": { + // if (!support_phone) { + // errors[field] = t("field_required"); + // invalidForm = true; + // } + // // eslint-disable-next-line no-case-declarations + // const checkTollFree = support_phone.startsWith("1800"); + // const supportPhoneSimple = support_phone + // .replace(/[^0-9]/g, "") + // .slice(2); + // if (supportPhoneSimple.length != 10 && !checkTollFree) { + // errors[field] = "Please enter valid phone number"; + // invalidForm = true; + // } else if ( + // (support_phone.length < 10 || support_phone.length > 11) && + // checkTollFree + // ) { + // errors[field] = "Please enter valid phone number"; + // invalidForm = true; + // } + // return; + // } + // case "support_email": + // if (support_email && !validateEmailAddress(support_email)) { + // errors[field] = "Please enter valid email id"; + // invalidForm = true; + // } + // return; + // case "last_serviced_on": + // if (notes && !last_serviced_on) { + // errors[field] = "Last serviced on date is require with notes"; + // invalidForm = true; + // } + // return; + // default: + // return; + // } + // }); + // if (invalidForm) { + // dispatch({ type: "set_error", errors }); + // const firstError = Object.keys(errors).find((key) => errors[key]); + // if (firstError) { + // fieldRef[firstError].current?.scrollIntoView({ + // behavior: "smooth", + // block: "center", + // }); + // } + // return false; + // } + // dispatch({ type: "set_error", errors }); + // return true; + // }; - if (!assetId) { + // const resetFilters = () => { + // setName(""); + // setDescription(""); + // setLocation(""); + // setAssetClass(assetClassInitial); + // setIsWorking(undefined); + // setNotWorkingReason(""); + // setSerialNumber(""); + // setVendorName(""); + // setSupportName(""); + // setSupportEmail(""); + // setSupportPhone(""); + // setQrCodeId(""); + // setManufacturer(""); + // setWarrantyAmcEndOfValidity(""); + // setLastServicedOn(""); + // setNotes(""); + // setWarrantyAmcEndOfValidity(null); + // setLastServicedOn(null); + // }; + + // const handleSubmit = async (e: React.SyntheticEvent, addMore: boolean) => { + // e.preventDefault(); + // const validated = validateForm(); + // if (validated) { + // setIsLoading(true); + // const data: any = { + // name: name, + // asset_type: AssetType.INTERNAL, + // asset_class: asset_class || "", + // description: description, + // is_working: is_working, + // not_working_reason: is_working === "true" ? "" : not_working_reason, + // serial_number: serial_number, + // location: location, + // vendor_name: vendor_name, + // support_name: support_name, + // support_email: support_email, + // support_phone: support_phone.startsWith("1800") + // ? support_phone + // : parsePhoneNumber(support_phone), + // qr_code_id: qrCodeId !== "" ? qrCodeId : null, + // manufacturer: manufacturer, + // warranty_amc_end_of_validity: warranty_amc_end_of_validity + // ? dateQueryString(warranty_amc_end_of_validity) + // : null, + // }; + + // if (last_serviced_on) { + // data["last_serviced_on"] = dateQueryString(last_serviced_on); + // data["note"] = notes ?? ""; + // } + + // if (!assetId) { + // const { res } = await request(routes.createAsset, { body: data }); + // if (res?.ok) { + // Notification.Success({ msg: "Asset created successfully" }); + // if (addMore) { + // resetFilters(); + // const pageContainer = window.document.getElementById("pages"); + // pageContainer?.scroll(0, 0); + // } else { + // goBack(); + // } + // } + // setIsLoading(false); + // } else { + // const { res } = await request(routes.updateAsset, { + // pathParams: { external_id: assetId }, + // body: data, + // }); + // if (res?.ok) { + // Notification.Success({ msg: "Asset updated successfully" }); + // goBack(); + // } + // setIsLoading(false); + // } + // } + // }; + + const handleSubmitAsync = async (form: AssetT, addMore: boolean) => { + setIsLoading(true); + + const data: any = { + name: form.name, + asset_type: AssetType.INTERNAL, + asset_class: form.asset_class || "", + description: form.description, + is_working: form.is_working, + not_working_reason: + form.is_working === true ? "" : form.not_working_reason, + serial_number: form.serial_number, + location: form.location, + vendor_name: form.vendor_name, + support_name: form.support_name, + support_email: form.support_email, + support_phone: form.support_phone?.startsWith("1800") + ? form.support_phone + : parsePhoneNumber(String(form.support_phone)), + qr_code_id: form.qr_code_id !== "" ? form.qr_code_id : null, + manufacturer: form.manufacturer, + warranty_amc_end_of_validity: form.warranty_amc_end_of_validity + ? dateQueryString(form.warranty_amc_end_of_validity) + : null, + }; + + if (form.serviced_on) { + data["last_serviced_on"] = dateQueryString(form.serviced_on); + data["note"] = form.notes ?? ""; + } + + try { + if (!form.id) { const { res } = await request(routes.createAsset, { body: data }); if (res?.ok) { Notification.Success({ msg: "Asset created successfully" }); + // Handle "Add More" logic if necessary if (addMore) { - resetFilters(); + // resetFilters(); const pageContainer = window.document.getElementById("pages"); pageContainer?.scroll(0, 0); } else { goBack(); } } - setIsLoading(false); } else { const { res } = await request(routes.updateAsset, { - pathParams: { external_id: assetId }, + pathParams: { external_id: form.id }, body: data, }); if (res?.ok) { Notification.Success({ msg: "Asset updated successfully" }); goBack(); } - setIsLoading(false); } + } catch (error) { + // Handle error (optional) + Notification.Error({ + msg: "An error occurred while processing the asset", + }); + } finally { + setIsLoading(false); } }; @@ -474,7 +554,7 @@ const AssetCreate = (props: AssetProps) => { // QR Maybe searchParams "asset" or "assetQR" const assetId = params.asset || params.assetQR; if (assetId) { - setQrCodeId(assetId); + // setQrCodeId(assetId); setIsScannerActive(false); return; } @@ -490,6 +570,9 @@ const AssetCreate = (props: AssetProps) => { return ; } + const name: string | undefined = + locationsQuery.data?.results[0].facility?.name || undefined; + if (locationsQuery.data?.count === 0) { return ( { ); } + const handleOnCancel: () => void = () => { + navigate(`/facility/${facilityId}/location/add`); + }; + if (isScannerActive) return (
@@ -582,10 +669,10 @@ const AssetCreate = (props: AssetProps) => { className="grow-0 pl-6" crumbsReplacements={{ [facilityId]: { - name: locationsQuery.data?.results[0].facility?.name, + name, }, assets: { style: "text-secondary-200 pointer-events-none" }, - [assetId || "????"]: { name }, + [assetId || "????"]: { name: name || undefined }, }} backUrl={ assetId @@ -619,402 +706,386 @@ const AssetCreate = (props: AssetProps) => {
-
handleSubmit(e, false)} + + disabled={isLoading} + defaults={initAssetData} + onCancel={handleOnCancel} + onSubmit={async (obj) => { + await handleSubmitAsync(obj, Boolean(assetId)); + }} className="rounded bg-white p-6 transition-all sm:rounded-xl sm:p-12" + noPadding + validate={AssetFormValidator} + submitLabel={assetId ? t("update") : t("create_asset")} > -
-
- {/* General Details Section */} - {sectionTitle("General Details")} - {/* Asset Name */} -
- setName(value)} - error={state.errors.name} - /> -
+ {(field) => ( + <> +
+
+ {/* General Details Section */} + {sectionTitle("General Details")} + {/* Asset Name */} +
+ +
- {/* Location */} - - {t("asset_location")} - -
- - setLocation((selectedId as string) || "") - } - selected={location} - showAll={false} - multiple={false} - facilityId={facilityId} - errors={state.errors.location} - /> -
+ {/* Location */} + + {t("asset_location")} + +
+ { + field("location").onChange({ + name: "location", + value: selected, + }); + }} + errors={field("location").error} + facilityId={facilityId} + multiple={false} + showAll={false} + /> +
- {/* Asset Class */} -
- title} - optionValue={({ value }) => value} - onChange={({ value }) => setAssetClass(value)} - error={state.errors.asset_class} - /> -
- {/* Description */} -
- setDescription(value)} - error={state.errors.description} - /> -
- {/* Divider */} -
-
-
- {/* Working Status */} -
- { - return ( - { - true: "Working", - false: "Not Working", - }[option] || "undefined" - ); - }} - optionClassName={(option) => - option === "false" && - "bg-danger-500 text-white border-danger-500 focus:ring-danger-500" - } - value={is_working} - onChange={setIsWorking} - error={state.errors.is_working} - /> -
- {/* Not Working Reason */} -
- setNotWorkingReason(e.value)} - error={state.errors.not_working_reason} - /> -
- {/* Divider */} -
-
-
- {/* Asset QR ID */} -
-
- setQrCodeId(e.value)} - error={state.errors.qr_code_id} - /> -
-
setIsScannerActive(true)} - > - + {/* Asset Class */} +
+ title} + optionValue={({ value }) => value} + /> +
+ {/* Description */} +
+ +
+ {/* Divider */} +
+
+
+ {/* Working Status */} +
+ + field("is_working").onChange({ + name: "is_working", + value: option === "true", // Convert back to boolean + }) + } + optionLabel={(option: "true" | "false") => { + return ( + { + true: "Working", + false: "Not Working", + }[option] || "undefined" + ); + }} + optionClassName={(option) => + option === "false" && + "bg-danger-500 text-white border-danger-500 focus:ring-danger-500" + } + /> +
+ {/* Not Working Reason */} +
+ +
+ {/* Divider */} +
+
+
+ {/* Asset QR ID */} +
+
+ +
+
setIsScannerActive(true)} + > + +
+
-
-
-
- {sectionTitle("Warranty Details")} - - {/* Manufacturer */} -
- setManufacturer(e.value)} - error={state.errors.manufacturer} - /> -
+
+ {sectionTitle("Warranty Details")} - {/* Warranty / AMC Expiry */} -
- { - const value = dayjs(event.value); - const date = new Date(value.toDate().toDateString()); - const today = new Date(new Date().toDateString()); - if (date < today) { - Notification.Error({ - msg: "Warranty / AMC Expiry date can't be in past", - }); - } else { - setWarrantyAmcEndOfValidity(dateQueryString(value)); - } - }} - type="date" - min={dayjs().format("YYYY-MM-DD")} - /> -
+ {/* Manufacturer */} +
+ +
- {/* Customer Support Name */} -
- setSupportName(e.value)} - error={state.errors.support_name} - /> -
+ {/* Warranty / AMC Expiry */} +
+ { + const { value } = event; + const selectedDate = dayjs(value); + const formattedDate = + selectedDate.format("YYYY-MM-DD"); + const today = dayjs().format("YYYY-MM-DD"); - {/* Customer Support Number */} -
- setSupportPhone(e.value)} - error={state.errors.support_phone} - types={["mobile", "landline", "support"]} - /> -
+ if (selectedDate.isBefore(today)) { + Notification.Error({ + msg: "Warranty / AMC Expiry date can't be in the past", + }); + } else { + // Update the provider's state with the valid date + field("warranty_amc_end_of_validity").onChange({ + name: "warranty_amc_end_of_validity", + value: formattedDate, + }); + } + }} + type="date" + min={dayjs().format("YYYY-MM-DD")} + /> +
- {/* Customer Support Email */} -
- setSupportEmail(e.value)} - error={state.errors.support_email} - /> -
+ {/* Customer Support Name */} +
+ +
-
- - {/* Vendor Name */} -
- setVendorName(e.value)} - error={state.errors.vendor_name} - /> -
+ {/* Customer Support Number */} +
+ +
- {/* Serial Number */} -
- setSerialNumber(e.value)} - error={state.errors.serial_number} - /> -
+ {/* Customer Support Email */} +
+ +
-
- {sectionTitle("Service Details")} - - {/* Last serviced on */} -
- { - if ( - dayjs(date.value).format("YYYY-MM-DD") > - new Date().toLocaleDateString("en-ca") - ) { - Notification.Error({ - msg: "Last Serviced date can't be in future", - }); - } else { - setLastServicedOn( - dayjs(date.value).format("YYYY-MM-DD"), - ); - } - }} - /> - -
+
+ + {/* Vendor Name */} +
+ +
+ + {/* Serial Number */} +
+ +
+ +
+ {sectionTitle("Service Details")} + + {/* Last serviced on */} +
+ { + const selectedDate = dayjs(date.value).format( + "YYYY-MM-DD", + ); + const today = new Date().toLocaleDateString( + "en-ca", + ); + + if (selectedDate > today) { + Notification.Error({ + msg: "Last Serviced date can't be in the future", + }); + } else { + field("serviced_on").onChange({ + name: "last_serviced_on", + value: selectedDate, + }); + } + }} + /> + + +
+ + {/* Notes */} +
+ +
+
- {/* Notes */} -
- setNotes(e.value)} - error={state.errors.notes} - /> +
-
- -
- - navigate( - assetId - ? `/facility/${facilityId}/assets/${assetId}` - : `/facility/${facilityId}`, - ) - } - /> - handleSubmit(e, false)} - label={assetId ? t("update") : t("create_asset")} - disabled={!isButtonEnabled} - /> - {!assetId && ( - handleSubmit(e, true)} - label={t("create_add_more")} - disabled={!isButtonEnabled} - /> - )} -
-
- + + )} +
From ea14bd0d495d6de4b7dd46cffc3bdd34c2aa8a7f Mon Sep 17 00:00:00 2001 From: totregex Date: Mon, 25 Nov 2024 21:56:21 +0530 Subject: [PATCH 03/23] wrap form in AssetCreate page using Form component --- src/components/Facility/AssetCreate.tsx | 237 +----------------------- 1 file changed, 9 insertions(+), 228 deletions(-) diff --git a/src/components/Facility/AssetCreate.tsx b/src/components/Facility/AssetCreate.tsx index 3747be6e2b2..4bac8055a27 100644 --- a/src/components/Facility/AssetCreate.tsx +++ b/src/components/Facility/AssetCreate.tsx @@ -66,7 +66,7 @@ interface AssetT { name?: string; location?: string; description?: string; - is_working?: boolean; + is_working?: boolean | null; not_working_reason?: string; created_date?: string; modified_date?: string; @@ -95,28 +95,11 @@ const fieldRef = formErrorKeys.reduce( {}, ); -// const initialState = { -// errors: { ...initError }, -// }; - interface AssetProps { facilityId: string; assetId?: string; } -// const asset_create_reducer = (state = initialState, action: any) => { -// switch (action.type) { -// case "set_error": { -// return { -// ...state, -// errors: action.errors, -// }; -// } -// default: -// return state; -// } -// }; - type AssetFormSection = | "General Details" | "Warranty Details" @@ -132,7 +115,7 @@ const AssetCreate = (props: AssetProps) => { name: "", location: "", description: "", - is_working: false, + is_working: null, not_working_reason: "", created_date: "", modified_date: "", @@ -153,29 +136,10 @@ const AssetCreate = (props: AssetProps) => { notes: "", }; - // State to store asset data const [initAssetData, setinitialAssetData] = useState(initialAssetData); - // const [state, dispatch] = useReducer(asset_create_reducer, initialState); - // const [name, setName] = useState(""); - // const [asset_class, setAssetClass] = useState(); - // const [not_working_reason, setNotWorkingReason] = useState(""); - // const [description, setDescription] = useState(""); - // const [is_working, setIsWorking] = useState(undefined); - // const [serial_number, setSerialNumber] = useState(""); - // const [vendor_name, setVendorName] = useState(""); - // const [support_name, setSupportName] = useState(""); - // const [support_phone, setSupportPhone] = useState(""); - // const [support_email, setSupportEmail] = useState(""); - // const [location, setLocation] = useState(""); const [isLoading, setIsLoading] = useState(false); - // const [qrCodeId, setQrCodeId] = useState(""); - // const [manufacturer, setManufacturer] = useState(""); - // const [warranty_amc_end_of_validity, setWarrantyAmcEndOfValidity] = - // useState(null); - // const [last_serviced_on, setLastServicedOn] = useState(null); - // const [notes, setNotes] = useState(""); const [isScannerActive, setIsScannerActive] = useState(false); const [currentSection, setCurrentSection] = @@ -224,40 +188,12 @@ const AssetCreate = (props: AssetProps) => { query: { limit: 1 }, }); - // const assetQuery = useQuery(routes.getAsset, { - // pathParams: { external_id: assetId! }, - // prefetch: !!assetId, - // onResponse: ({ data: asset }) => { - // if (!asset) return; - - // setName(asset.name); - // setDescription(asset.description); - // setLocation(asset.location_object.id!); - // setAssetClass(asset.asset_class); - // setIsWorking(String(asset.is_working)); - // setNotWorkingReason(asset.not_working_reason); - // setSerialNumber(asset.serial_number); - // setVendorName(asset.vendor_name); - // setSupportName(asset.support_name); - // setSupportEmail(asset.support_email); - // setSupportPhone(asset.support_phone); - // setQrCodeId(asset.qr_code_id); - // setManufacturer(asset.manufacturer); - // asset.warranty_amc_end_of_validity && - // setWarrantyAmcEndOfValidity(asset.warranty_amc_end_of_validity); - // asset.last_service?.serviced_on && - // setLastServicedOn(asset.last_service?.serviced_on); - // asset.last_service?.note && setNotes(asset.last_service?.note); - // }, - // }); - const assetQuery = useQuery(routes.getAsset, { pathParams: { external_id: assetId! }, prefetch: !!assetId, onResponse: ({ data: asset }) => { if (!asset) return; - // Update the state with the fetched data setinitialAssetData({ id: asset.id, name: asset.name, @@ -320,169 +256,13 @@ const AssetCreate = (props: AssetProps) => { errors.support_email = "Please enter valid email id"; } - if (form.notes && !form.last_service) { + if (form.notes && !form.serviced_on) { errors.serviced_on = "Last serviced on date is required with notes"; } return errors; }; - // const validateForm = () => { - // const errors = { ...initError }; - // let invalidForm = false; - // Object.keys(state.errors).forEach((field) => { - // switch (field) { - // case "name": - // if (!name) { - // errors[field] = "Asset name can't be empty"; - // invalidForm = true; - // } - // return; - // case "is_working": - // if (is_working === undefined) { - // errors[field] = t("field_required"); - // invalidForm = true; - // } - // return; - // case "location": - // if (!location || location === "0" || location === "") { - // errors[field] = "Select a location"; - // invalidForm = true; - // } - // return; - // case "support_phone": { - // if (!support_phone) { - // errors[field] = t("field_required"); - // invalidForm = true; - // } - // // eslint-disable-next-line no-case-declarations - // const checkTollFree = support_phone.startsWith("1800"); - // const supportPhoneSimple = support_phone - // .replace(/[^0-9]/g, "") - // .slice(2); - // if (supportPhoneSimple.length != 10 && !checkTollFree) { - // errors[field] = "Please enter valid phone number"; - // invalidForm = true; - // } else if ( - // (support_phone.length < 10 || support_phone.length > 11) && - // checkTollFree - // ) { - // errors[field] = "Please enter valid phone number"; - // invalidForm = true; - // } - // return; - // } - // case "support_email": - // if (support_email && !validateEmailAddress(support_email)) { - // errors[field] = "Please enter valid email id"; - // invalidForm = true; - // } - // return; - // case "last_serviced_on": - // if (notes && !last_serviced_on) { - // errors[field] = "Last serviced on date is require with notes"; - // invalidForm = true; - // } - // return; - // default: - // return; - // } - // }); - // if (invalidForm) { - // dispatch({ type: "set_error", errors }); - // const firstError = Object.keys(errors).find((key) => errors[key]); - // if (firstError) { - // fieldRef[firstError].current?.scrollIntoView({ - // behavior: "smooth", - // block: "center", - // }); - // } - // return false; - // } - // dispatch({ type: "set_error", errors }); - // return true; - // }; - - // const resetFilters = () => { - // setName(""); - // setDescription(""); - // setLocation(""); - // setAssetClass(assetClassInitial); - // setIsWorking(undefined); - // setNotWorkingReason(""); - // setSerialNumber(""); - // setVendorName(""); - // setSupportName(""); - // setSupportEmail(""); - // setSupportPhone(""); - // setQrCodeId(""); - // setManufacturer(""); - // setWarrantyAmcEndOfValidity(""); - // setLastServicedOn(""); - // setNotes(""); - // setWarrantyAmcEndOfValidity(null); - // setLastServicedOn(null); - // }; - - // const handleSubmit = async (e: React.SyntheticEvent, addMore: boolean) => { - // e.preventDefault(); - // const validated = validateForm(); - // if (validated) { - // setIsLoading(true); - // const data: any = { - // name: name, - // asset_type: AssetType.INTERNAL, - // asset_class: asset_class || "", - // description: description, - // is_working: is_working, - // not_working_reason: is_working === "true" ? "" : not_working_reason, - // serial_number: serial_number, - // location: location, - // vendor_name: vendor_name, - // support_name: support_name, - // support_email: support_email, - // support_phone: support_phone.startsWith("1800") - // ? support_phone - // : parsePhoneNumber(support_phone), - // qr_code_id: qrCodeId !== "" ? qrCodeId : null, - // manufacturer: manufacturer, - // warranty_amc_end_of_validity: warranty_amc_end_of_validity - // ? dateQueryString(warranty_amc_end_of_validity) - // : null, - // }; - - // if (last_serviced_on) { - // data["last_serviced_on"] = dateQueryString(last_serviced_on); - // data["note"] = notes ?? ""; - // } - - // if (!assetId) { - // const { res } = await request(routes.createAsset, { body: data }); - // if (res?.ok) { - // Notification.Success({ msg: "Asset created successfully" }); - // if (addMore) { - // resetFilters(); - // const pageContainer = window.document.getElementById("pages"); - // pageContainer?.scroll(0, 0); - // } else { - // goBack(); - // } - // } - // setIsLoading(false); - // } else { - // const { res } = await request(routes.updateAsset, { - // pathParams: { external_id: assetId }, - // body: data, - // }); - // if (res?.ok) { - // Notification.Success({ msg: "Asset updated successfully" }); - // goBack(); - // } - // setIsLoading(false); - // } - // } - // }; - const handleSubmitAsync = async (form: AssetT, addMore: boolean) => { setIsLoading(true); @@ -833,11 +613,11 @@ const AssetCreate = (props: AssetProps) => { String(field("is_working").value) as | "true" | "false" - } // Convert to string + } onChange={(option: "true" | "false") => field("is_working").onChange({ name: "is_working", - value: option === "true", // Convert back to boolean + value: option === "true", }) } optionLabel={(option: "true" | "false") => { @@ -1036,8 +816,9 @@ const AssetCreate = (props: AssetProps) => { className="mt-2" disableFuture value={ - field("serviced_on").value && - new Date(field("serviced_on").value) + field("serviced_on").value + ? new Date(field("serviced_on").value) + : undefined } onChange={(date) => { const selectedDate = dayjs(date.value).format( @@ -1053,7 +834,7 @@ const AssetCreate = (props: AssetProps) => { }); } else { field("serviced_on").onChange({ - name: "last_serviced_on", + name: "serviced_on", value: selectedDate, }); } From 3c1a2acfd69bbed7dfbf372fa266c22a7b217f81 Mon Sep 17 00:00:00 2001 From: totregex Date: Tue, 26 Nov 2024 17:56:17 +0530 Subject: [PATCH 04/23] pass additionalButtons prop --- src/components/Facility/AssetCreate.tsx | 57 ++++++++++++++++++++----- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/src/components/Facility/AssetCreate.tsx b/src/components/Facility/AssetCreate.tsx index 4bac8055a27..cde94960079 100644 --- a/src/components/Facility/AssetCreate.tsx +++ b/src/components/Facility/AssetCreate.tsx @@ -61,7 +61,7 @@ const formErrorKeys = [ "notes", ]; -interface AssetT { +interface AssetData { id?: string; name?: string; location?: string; @@ -110,7 +110,7 @@ const AssetCreate = (props: AssetProps) => { const { t } = useTranslation(); const { facilityId, assetId } = props; - const initialAssetData: AssetT = { + const initialAssetData: AssetData = { id: "", name: "", location: "", @@ -137,7 +137,7 @@ const AssetCreate = (props: AssetProps) => { }; const [initAssetData, setinitialAssetData] = - useState(initialAssetData); + useState(initialAssetData); const [isLoading, setIsLoading] = useState(false); const [isScannerActive, setIsScannerActive] = useState(false); @@ -222,8 +222,8 @@ const AssetCreate = (props: AssetProps) => { }, }); - const AssetFormValidator = (form: AssetT): FormErrors => { - const errors: Partial> = {}; // Initialize error object + const AssetFormValidator = (form: AssetData): FormErrors => { + const errors: Partial> = {}; // Initialize error object errors.name = RequiredFieldValidator()(form.name); @@ -232,7 +232,7 @@ const AssetCreate = (props: AssetProps) => { } if (!form.location || form.location === "0" || form.location === "") { - errors.location = "Select a location"; + errors.location = t("select_local_body"); } if (!form.support_phone) { @@ -263,7 +263,35 @@ const AssetCreate = (props: AssetProps) => { return errors; }; - const handleSubmitAsync = async (form: AssetT, addMore: boolean) => { + const resetFilters = () => { + setinitialAssetData({ + id: "", + name: "", + location: "", + description: "", + is_working: null, + not_working_reason: "", + created_date: "", + modified_date: "", + serial_number: "", + asset_type: undefined, + asset_class: undefined, + status: "ACTIVE", + vendor_name: "", + support_name: "", + support_email: "", + support_phone: "", + qr_code_id: "", + manufacturer: "", + warranty_amc_end_of_validity: "", + latest_status: "", + last_service: null, + serviced_on: "", + notes: "", + }); + }; + + const handleSubmitAsync = async (form: AssetData, addMore: boolean) => { setIsLoading(true); const data: any = { @@ -301,7 +329,7 @@ const AssetCreate = (props: AssetProps) => { Notification.Success({ msg: "Asset created successfully" }); // Handle "Add More" logic if necessary if (addMore) { - // resetFilters(); + resetFilters(); const pageContainer = window.document.getElementById("pages"); pageContainer?.scroll(0, 0); } else { @@ -334,7 +362,7 @@ const AssetCreate = (props: AssetProps) => { // QR Maybe searchParams "asset" or "assetQR" const assetId = params.asset || params.assetQR; if (assetId) { - // setQrCodeId(assetId); + setinitialAssetData({ ...initAssetData, qr_code_id: assetId }); setIsScannerActive(false); return; } @@ -452,7 +480,7 @@ const AssetCreate = (props: AssetProps) => { name, }, assets: { style: "text-secondary-200 pointer-events-none" }, - [assetId || "????"]: { name: name || undefined }, + [assetId || "????"]: { name: name || "Asset" }, }} backUrl={ assetId @@ -486,7 +514,7 @@ const AssetCreate = (props: AssetProps) => {
- + disabled={isLoading} defaults={initAssetData} onCancel={handleOnCancel} @@ -497,6 +525,13 @@ const AssetCreate = (props: AssetProps) => { noPadding validate={AssetFormValidator} submitLabel={assetId ? t("update") : t("create_asset")} + additionalButtons={[ + { + type: "submit", + label: t("create_add_more"), + id: "create-asset-add-more-button", + }, + ]} > {(field) => ( <> From 29aa6296dc8faf97735189a2418e51ee64df8ea8 Mon Sep 17 00:00:00 2001 From: totregex Date: Tue, 26 Nov 2024 21:59:20 +0530 Subject: [PATCH 05/23] edit handleOnCancel --- src/components/Facility/AssetCreate.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Facility/AssetCreate.tsx b/src/components/Facility/AssetCreate.tsx index cde94960079..c108d014994 100644 --- a/src/components/Facility/AssetCreate.tsx +++ b/src/components/Facility/AssetCreate.tsx @@ -413,7 +413,7 @@ const AssetCreate = (props: AssetProps) => { } const handleOnCancel: () => void = () => { - navigate(`/facility/${facilityId}/location/add`); + goBack(); }; if (isScannerActive) From c5312fb7f8f6701d77c39a5077af1e900073b9cc Mon Sep 17 00:00:00 2001 From: totregex Date: Thu, 28 Nov 2024 17:29:41 +0530 Subject: [PATCH 06/23] add create asset and add more button --- src/components/Facility/AssetCreate.tsx | 27 +++++++++++++++---------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/components/Facility/AssetCreate.tsx b/src/components/Facility/AssetCreate.tsx index c108d014994..dd8c02efe1a 100644 --- a/src/components/Facility/AssetCreate.tsx +++ b/src/components/Facility/AssetCreate.tsx @@ -291,10 +291,11 @@ const AssetCreate = (props: AssetProps) => { }); }; - const handleSubmitAsync = async (form: AssetData, addMore: boolean) => { + const handleSubmitAsync = async (form: AssetData, buttonId: string) => { setIsLoading(true); const data: any = { + id: form.id, name: form.name, asset_type: AssetType.INTERNAL, asset_class: form.asset_class || "", @@ -328,7 +329,7 @@ const AssetCreate = (props: AssetProps) => { if (res?.ok) { Notification.Success({ msg: "Asset created successfully" }); // Handle "Add More" logic if necessary - if (addMore) { + if (buttonId == "create-asset-add-more-button") { resetFilters(); const pageContainer = window.document.getElementById("pages"); pageContainer?.scroll(0, 0); @@ -518,20 +519,24 @@ const AssetCreate = (props: AssetProps) => { disabled={isLoading} defaults={initAssetData} onCancel={handleOnCancel} - onSubmit={async (obj) => { - await handleSubmitAsync(obj, Boolean(assetId)); + onSubmit={async (obj, buttonId) => { + await handleSubmitAsync(obj, buttonId); }} className="rounded bg-white p-6 transition-all sm:rounded-xl sm:p-12" noPadding validate={AssetFormValidator} submitLabel={assetId ? t("update") : t("create_asset")} - additionalButtons={[ - { - type: "submit", - label: t("create_add_more"), - id: "create-asset-add-more-button", - }, - ]} + additionalButtons={ + !assetId + ? [ + { + type: "submit", + label: t("create_add_more"), + id: "create-asset-add-more-button", + }, + ] + : [] + } > {(field) => ( <> From e602030015177d4400115c542e8ba2c38f71c5c4 Mon Sep 17 00:00:00 2001 From: totregex Date: Sat, 30 Nov 2024 07:13:50 +0530 Subject: [PATCH 07/23] add translation and use PhoneNumberValidator --- public/locale/en.json | 1 + src/components/Facility/AssetCreate.tsx | 38 ++++++++++++------------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/public/locale/en.json b/public/locale/en.json index 5faa645b133..321e803e77d 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -811,6 +811,7 @@ "last_name": "Last Name", "last_online": "Last Online", "last_serviced_on": "Last Serviced On", + "last_serviced_required": "Last Serviced On Date is required with notes", "latitude_invalid": "Latitude must be between -90 and 90", "left": "Left", "length": "Length ({{unit}})", diff --git a/src/components/Facility/AssetCreate.tsx b/src/components/Facility/AssetCreate.tsx index dd8c02efe1a..860b0e30fe4 100644 --- a/src/components/Facility/AssetCreate.tsx +++ b/src/components/Facility/AssetCreate.tsx @@ -17,6 +17,12 @@ import Loading from "@/components/Common/Loading"; import { LocationSelect } from "@/components/Common/LocationSelect"; import Page from "@/components/Common/Page"; import SwitchV2 from "@/components/Common/Switch"; +import { + FieldError, + PhoneNumberValidator, + RequiredFieldValidator, +} from "@/components/Form/FieldValidators"; +import Form from "@/components/Form/Form"; import DateFormField from "@/components/Form/FormFields/DateFormField"; import { FieldErrorText, @@ -26,6 +32,7 @@ import PhoneNumberFormField from "@/components/Form/FormFields/PhoneNumberFormFi import { SelectFormField } from "@/components/Form/FormFields/SelectFormField"; import TextAreaFormField from "@/components/Form/FormFields/TextAreaFormField"; import TextFormField from "@/components/Form/FormFields/TextFormField"; +import { FormErrors } from "@/components/Form/Utils"; import useAppHistory from "@/hooks/useAppHistory"; import useVisibility from "@/hooks/useVisibility"; @@ -40,10 +47,6 @@ import request from "@/Utils/request/request"; import useQuery from "@/Utils/request/useQuery"; import { dateQueryString, parsePhoneNumber } from "@/Utils/utils"; -import { FieldError, RequiredFieldValidator } from "../Form/FieldValidators"; -import Form from "../Form/Form"; -import { FormErrors } from "../Form/Utils"; - const formErrorKeys = [ "name", "asset_class", @@ -238,26 +241,23 @@ const AssetCreate = (props: AssetProps) => { if (!form.support_phone) { errors.support_phone = t("field_required"); } else { - const checkTollFree = form.support_phone.startsWith("1800"); - const supportPhoneSimple = form.support_phone - .replace(/[^0-9]/g, "") - .slice(2); - if (supportPhoneSimple.length !== 10 && !checkTollFree) { - errors.support_phone = "Please enter valid phone number"; - } else if ( - (form.support_phone.length < 10 || form.support_phone.length > 11) && - checkTollFree - ) { - errors.support_phone = "Please enter valid phone number"; + const validatePhoneNumber = PhoneNumberValidator( + ["mobile", "landline", "support"], + t("invalid_phone_number"), + ); + const isValid = validatePhoneNumber(form.support_phone); + // console.log("Is Valid" ,isValid) + if (isValid == "Invalid Phone Number") { + errors.support_phone = t("invalid_phone_number"); } } if (form.support_email && !validateEmailAddress(form.support_email)) { - errors.support_email = "Please enter valid email id"; + errors.support_email = t("invalid_email"); } if (form.notes && !form.serviced_on) { - errors.serviced_on = "Last serviced on date is required with notes"; + errors.serviced_on = t("last_serviced_on_required"); } return errors; @@ -291,7 +291,7 @@ const AssetCreate = (props: AssetProps) => { }); }; - const handleSubmitAsync = async (form: AssetData, buttonId: string) => { + const handleSubmit = async (form: AssetData, buttonId: string) => { setIsLoading(true); const data: any = { @@ -520,7 +520,7 @@ const AssetCreate = (props: AssetProps) => { defaults={initAssetData} onCancel={handleOnCancel} onSubmit={async (obj, buttonId) => { - await handleSubmitAsync(obj, buttonId); + await handleSubmit(obj, buttonId); }} className="rounded bg-white p-6 transition-all sm:rounded-xl sm:p-12" noPadding From 5387f60d55283644faa99002d690c2a210b2b9e1 Mon Sep 17 00:00:00 2001 From: totregex Date: Mon, 23 Dec 2024 22:12:52 +0530 Subject: [PATCH 08/23] use shadcn form in AssetCreate with other shadcn components | add dirty state to prevent unnecessary network requests --- package-lock.json | 451 ++++++- package.json | 9 +- src/components/Common/LocationSelect.tsx | 74 +- src/components/Facility/AssetCreate.tsx | 1343 ++++++++++--------- src/components/Facility/AssetFormSchema.tsx | 59 + src/components/ui/calendar.tsx | 76 ++ src/components/ui/form.tsx | 184 +++ src/components/ui/select.tsx | 161 +++ src/components/ui/switch.tsx | 27 + src/components/ui/textarea.tsx | 22 + 10 files changed, 1698 insertions(+), 708 deletions(-) create mode 100644 src/components/Facility/AssetFormSchema.tsx create mode 100644 src/components/ui/calendar.tsx create mode 100644 src/components/ui/form.tsx create mode 100644 src/components/ui/select.tsx create mode 100644 src/components/ui/switch.tsx create mode 100644 src/components/ui/textarea.tsx diff --git a/package-lock.json b/package-lock.json index 0c68d4ed99d..ec6cb286992 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@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.4", @@ -25,7 +26,9 @@ "@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-select": "^2.1.4", "@radix-ui/react-slot": "^1.1.1", + "@radix-ui/react-switch": "^1.1.2", "@radix-ui/react-toast": "^1.2.2", "@radix-ui/react-tooltip": "^1.1.6", "@sentry/browser": "^8.45.1", @@ -41,6 +44,7 @@ "cmdk": "^1.0.4", "cross-env": "^7.0.3", "cypress": "^13.17.0", + "date-fns": "^4.1.0", "dayjs": "^1.11.13", "echarts": "^5.5.1", "echarts-for-react": "^3.0.2", @@ -49,13 +53,16 @@ "i18next": "^23.16.4", "i18next-browser-languagedetector": "^8.0.2", "i18next-http-backend": "^3.0.1", + "lucide-react": "^0.469.0", "postcss-loader": "^8.1.1", "qrcode.react": "^4.1.0", "raviger": "^4.1.2", "react": "18.3.1", "react-copy-to-clipboard": "^5.1.0", + "react-day-picker": "^9.4.4", "react-dom": "18.3.1", "react-google-recaptcha": "^3.1.0", + "react-hook-form": "^7.54.2", "react-i18next": "^15.1.3", "react-infinite-scroll-component": "^6.1.0", "react-pdf": "^9.2.1", @@ -113,7 +120,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" @@ -2009,6 +2016,11 @@ "ms": "^2.1.1" } }, + "node_modules/@date-fns/tz": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@date-fns/tz/-/tz-1.2.0.tgz", + "integrity": "sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg==" + }, "node_modules/@dependents/detective-less": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@dependents/detective-less/-/detective-less-5.0.0.tgz", @@ -2646,6 +2658,14 @@ "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==", + "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", @@ -3693,7 +3713,6 @@ "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" }, @@ -4084,7 +4103,6 @@ "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.1" }, @@ -4107,7 +4125,6 @@ "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" }, @@ -4170,7 +4187,6 @@ "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" }, @@ -4224,7 +4240,6 @@ "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" }, @@ -4360,7 +4375,6 @@ "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" }, @@ -4450,11 +4464,290 @@ } } }, + "node_modules/@radix-ui/react-select": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.4.tgz", + "integrity": "sha512-pOkb2u8KgO47j/h7AylCj7dJsm69BXcjkrvTqMptFqsE2i0p8lHkfgneXKjAgPzBMivnoMyt8o4KiV4wYzDdyQ==", + "dependencies": { + "@radix-ui/number": "1.1.0", + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-collection": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.3", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.1", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.1", + "@radix-ui/react-portal": "1.1.3", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-slot": "1.1.1", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.1", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "^2.6.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-select/node_modules/@radix-ui/primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz", + "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==" + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-arrow": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.1.tgz", + "integrity": "sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w==", + "dependencies": { + "@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-select/node_modules/@radix-ui/react-collection": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.1.tgz", + "integrity": "sha512-LwT3pSho9Dljg+wY2KN2mrrh6y3qELfftINERIzBUO9e0N+t0oMTyn3k9iv+ZqgrwGkRnLpNJrsMv9BZlt2yuA==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-slot": "1.1.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-select/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==", + "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-select/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.3.tgz", + "integrity": "sha512-onrWn/72lQoEucDmJnr8uczSNTujT0vJnA/X5+3AkChVPowr8n1yvIKIabhWyMQeMvvmdpsvcyDqx3X1LEXCPg==", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "1.1.0" + }, + "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-select/node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.1.tgz", + "integrity": "sha512-01omzJAYRxXdG2/he/+xy+c8a8gCydoQ1yOxnWNcRhrrBW5W+RQJ22EK1SaO8tb3WoUsuEw7mJjBozPzihDFjA==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "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-select/node_modules/@radix-ui/react-popper": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.1.tgz", + "integrity": "sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw==", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-rect": "1.1.0", + "@radix-ui/react-use-size": "1.1.0", + "@radix-ui/rect": "1.1.0" + }, + "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-select/node_modules/@radix-ui/react-portal": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.3.tgz", + "integrity": "sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw==", + "dependencies": { + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "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-select/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==", + "dependencies": { + "@radix-ui/react-slot": "1.1.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-select/node_modules/@radix-ui/react-visually-hidden": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.1.tgz", + "integrity": "sha512-vVfA2IZ9q/J+gEamvj761Oq1FpWgCDaNOOIfbPVp2MVPLEomUr5+Vf7kJGwQ24YxZSlQVar7Bes8kyTo5Dshpg==", + "dependencies": { + "@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-select/node_modules/react-remove-scroll": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.2.tgz", + "integrity": "sha512-KmONPx5fnlXYJQqC62Q+lwIeAk64ws/cUw6omIumRzMRPqgnYqhSSti99nbj0Ry13bv7dF+BKn7NB+OqkdZGTw==", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-slot": { "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.1" }, @@ -4472,7 +4765,6 @@ "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" @@ -4483,6 +4775,75 @@ } } }, + "node_modules/@radix-ui/react-switch": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.1.2.tgz", + "integrity": "sha512-zGukiWHjEdBCRyXvKR6iXAQG6qXm2esuAD6kDOi9Cn+1X6ev3ASo4+CsYaD6Fov9r/AQFekqnD/7+V0Cs6/98g==", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-use-size": "1.1.0" + }, + "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-switch/node_modules/@radix-ui/primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz", + "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==" + }, + "node_modules/@radix-ui/react-switch/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==", + "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-switch/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==", + "dependencies": { + "@radix-ui/react-slot": "1.1.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-toast": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.2.tgz", @@ -4814,6 +5175,20 @@ } } }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz", + "integrity": "sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==", + "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-rect": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz", @@ -8480,6 +8855,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/dayjs": { "version": "1.11.13", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", @@ -13080,6 +13464,14 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "0.469.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.469.0.tgz", + "integrity": "sha512-28vvUnnKQ/dBwiCQtwJw7QauYnE7yd2Cyp4tTTJpvglX4EMpbflcdBgrgToX2j71B3YvugK/NH3BGUk+E/p/Fw==", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/magic-string": { "version": "0.30.12", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz", @@ -16429,6 +16821,25 @@ "react": "^15.3.0 || 16 || 17 || 18" } }, + "node_modules/react-day-picker": { + "version": "9.4.4", + "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-9.4.4.tgz", + "integrity": "sha512-1s+jA/bFYtoxhhr8M0kkFHLiMTSII6qU8UfDFprRAUStTVHljLTjg4oarvAngPlQ1cQAC+LUb0k/qMc+jjhmxw==", + "dependencies": { + "@date-fns/tz": "^1.2.0", + "date-fns": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/gpbl" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/react-dom": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", @@ -16455,6 +16866,21 @@ "react": ">=16.4.1" } }, + "node_modules/react-hook-form": { + "version": "7.54.2", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.54.2.tgz", + "integrity": "sha512-eHpAUgUjWbZocoQYUHposymRb4ZP6d0uwUnooL2uOybA9/3tPUvoAKqEWK1WaSiTxxOfTpffNZP7QwlnM3/gEg==", + "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", @@ -21617,11 +22043,10 @@ } }, "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": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index 3bc1009db80..40f0e41a177 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "@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.4", @@ -64,7 +65,9 @@ "@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-select": "^2.1.4", "@radix-ui/react-slot": "^1.1.1", + "@radix-ui/react-switch": "^1.1.2", "@radix-ui/react-toast": "^1.2.2", "@radix-ui/react-tooltip": "^1.1.6", "@sentry/browser": "^8.45.1", @@ -80,6 +83,7 @@ "cmdk": "^1.0.4", "cross-env": "^7.0.3", "cypress": "^13.17.0", + "date-fns": "^4.1.0", "dayjs": "^1.11.13", "echarts": "^5.5.1", "echarts-for-react": "^3.0.2", @@ -88,13 +92,16 @@ "i18next": "^23.16.4", "i18next-browser-languagedetector": "^8.0.2", "i18next-http-backend": "^3.0.1", + "lucide-react": "^0.469.0", "postcss-loader": "^8.1.1", "qrcode.react": "^4.1.0", "raviger": "^4.1.2", "react": "18.3.1", "react-copy-to-clipboard": "^5.1.0", + "react-day-picker": "^9.4.4", "react-dom": "18.3.1", "react-google-recaptcha": "^3.1.0", + "react-hook-form": "^7.54.2", "react-i18next": "^15.1.3", "react-infinite-scroll-component": "^6.1.0", "react-pdf": "^9.2.1", @@ -152,7 +159,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/Common/LocationSelect.tsx b/src/components/Common/LocationSelect.tsx index 8c2d1f2eea4..767ac88a774 100644 --- a/src/components/Common/LocationSelect.tsx +++ b/src/components/Common/LocationSelect.tsx @@ -1,5 +1,10 @@ -import AutocompleteFormField from "@/components/Form/FormFields/Autocomplete"; -import AutocompleteMultiSelectFormField from "@/components/Form/FormFields/AutocompleteMultiselect"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; import routes from "@/Utils/request/api"; import useTanStackQueryInstead from "@/Utils/request/useQuery"; @@ -22,7 +27,7 @@ interface LocationSelectProps { } export const LocationSelect = (props: LocationSelectProps) => { - const { data, loading, refetch } = useTanStackQueryInstead( + const { data, loading } = useTanStackQueryInstead( routes.listFacilityAssetLocation, { query: { @@ -41,36 +46,37 @@ export const LocationSelect = (props: LocationSelectProps) => { props = { ...props, disabled: true }; } - return props.multiple ? ( - props.setSelected(value)} - onQuery={(search_text) => refetch({ query: { search_text } })} - placeholder="Search by location name" - optionLabel={(option) => option.name} - optionValue={(option) => option.id} - error={props.errors} - className={props.className} - errorClassName={props.errorClassName} - /> - ) : ( - props.setSelected(value)} - onQuery={(search_text) => refetch({ query: { search_text } })} - isLoading={loading} - placeholder="Search by location name" - optionLabel={(option) => option.name} - optionValue={(option) => option.id} - error={props.errors} - className={props.className} - errorClassName={props.errorClassName} - /> + const handleSelectChange = (value: string) => { + props.setSelected(value); + }; + console.log("Selected", props.selected); + return ( +
+ +
); }; diff --git a/src/components/Facility/AssetCreate.tsx b/src/components/Facility/AssetCreate.tsx index 6e89bbd0e00..0a156ba2492 100644 --- a/src/components/Facility/AssetCreate.tsx +++ b/src/components/Facility/AssetCreate.tsx @@ -1,103 +1,58 @@ +import { zodResolver } from "@hookform/resolvers/zod"; import { IDetectedBarcode, Scanner } from "@yudiel/react-qr-scanner"; +import { format } from "date-fns"; import { navigate } from "raviger"; -import { - LegacyRef, - MutableRefObject, - RefObject, - createRef, - useEffect, - useState, -} from "react"; +import { LegacyRef, MutableRefObject, useEffect, useState } from "react"; +import { useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; +import { z } from "zod"; + +import { cn } from "@/lib/utils"; import CareIcon, { IconName } from "@/CAREUI/icons/CareIcon"; +import { Button } from "@/components/ui/button"; +import { Calendar } from "@/components/ui/calendar"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Switch } from "@/components/ui/switch"; +import { Textarea } from "@/components/ui/textarea"; + import { AssetClass, AssetType } from "@/components/Assets/AssetTypes"; import Loading from "@/components/Common/Loading"; import { LocationSelect } from "@/components/Common/LocationSelect"; import Page from "@/components/Common/Page"; -import SwitchV2 from "@/components/Common/Switch"; -import { - FieldError, - PhoneNumberValidator, - RequiredFieldValidator, -} from "@/components/Form/FieldValidators"; -import Form from "@/components/Form/Form"; -import DateFormField from "@/components/Form/FormFields/DateFormField"; -import { - FieldErrorText, - FieldLabel, -} from "@/components/Form/FormFields/FormField"; -import PhoneNumberFormField from "@/components/Form/FormFields/PhoneNumberFormField"; -import { SelectFormField } from "@/components/Form/FormFields/SelectFormField"; -import TextAreaFormField from "@/components/Form/FormFields/TextAreaFormField"; -import TextFormField from "@/components/Form/FormFields/TextFormField"; -import { FormErrors } from "@/components/Form/Utils"; +import { AssetFormSchema } from "@/components/Facility/AssetFormSchema"; import useAppHistory from "@/hooks/useAppHistory"; import useVisibility from "@/hooks/useVisibility"; -import { validateEmailAddress } from "@/common/validation"; - import * as Notification from "@/Utils/Notifications"; -import dayjs from "@/Utils/dayjs"; import { parseQueryParams } from "@/Utils/primitives"; import routes from "@/Utils/request/api"; import request from "@/Utils/request/request"; import useTanStackQueryInstead from "@/Utils/request/useQuery"; import { dateQueryString, parsePhoneNumber } from "@/Utils/utils"; -const formErrorKeys = [ - "name", - "asset_class", - "description", - "is_working", - "serial_number", - "location", - "vendor_name", - "support_name", - "support_phone", - "support_email", - "manufacturer", - "warranty_amc_end_of_validity", - "last_serviced", - "notes", -]; - -interface AssetData { - id?: string; - name?: string; - location?: string; - description?: string; - is_working?: boolean | null; - not_working_reason?: string; - created_date?: string; - modified_date?: string; - serial_number?: string; - asset_type?: AssetType; - asset_class?: AssetClass; - status?: "ACTIVE" | "TRANSFER_IN_PROGRESS"; - vendor_name?: string; - support_name?: string; - support_email?: string; - support_phone?: string; - qr_code_id?: string; - manufacturer?: string; - warranty_amc_end_of_validity?: string; - serviced_on?: string; - latest_status?: string; - last_service?: any; - notes: string; -} - -const fieldRef = formErrorKeys.reduce( - (acc: { [key: string]: RefObject }, key) => { - acc[key] = createRef(); - return acc; - }, - {}, -); - interface AssetProps { facilityId: string; assetId?: string; @@ -109,41 +64,132 @@ type AssetFormSection = | "Service Details"; const AssetCreate = (props: AssetProps) => { + const [addMore, setAddMore] = useState(false); + const [loading, setLoading] = useState(false); + const [isScannerActive, setIsScannerActive] = useState(false); + const { goBack } = useAppHistory(); const { t } = useTranslation(); const { facilityId, assetId } = props; - const initialAssetData: AssetData = { - id: "", - name: "", - location: "", - description: "", - is_working: null, - not_working_reason: "", - created_date: "", - modified_date: "", - serial_number: "", - asset_type: undefined, - asset_class: undefined, - status: "ACTIVE", - vendor_name: "", - support_name: "", - support_email: "", - support_phone: "", - qr_code_id: "", - manufacturer: "", - warranty_amc_end_of_validity: "", - latest_status: "", - last_service: null, - serviced_on: "", - notes: "", + const onSubmit = async (values: FormData) => { + if (Object.keys(errors).length > 0) { + const firstErrorField = Object.keys(errors)[0] as keyof FormData; + form.setFocus(firstErrorField, { shouldSelect: true }); + const firstErrorElement = document.querySelector( + `[name=${firstErrorField}]`, + ); + if (firstErrorElement) { + firstErrorElement.scrollIntoView({ + behavior: "smooth", + block: "center", + }); + } + } + setLoading(true); + + const data: any = { + name: values.name, + asset_type: AssetType.INTERNAL, + asset_class: values.asset_class || "", + description: values.description, + is_working: values.is_working, + not_working_reason: values.is_working ? "" : values.not_working_reason, + serial_number: values.serial_number, + location: values.location, + vendor_name: values.vendor_name, + support_name: values.support_name, + support_email: values.support_email, + support_phone: values.support_phone?.startsWith("1800") + ? values.support_phone + : parsePhoneNumber(String(values.support_phone)), + qr_code_id: values.qr_code_id !== "" ? values.qr_code_id : null, + manufacturer: values.manufacturer, + warranty_amc_end_of_validity: values.warranty_amc_end_of_validity + ? dateQueryString(values.warranty_amc_end_of_validity) + : null, + }; + + console.log(data); + + if (values.serviced_on) { + data["last_serviced_on"] = dateQueryString(values.serviced_on); + data["note"] = values.note ?? ""; + } + + // If the assetId is not null, it means we are updating an asset + if (!assetId) { + const { res } = await request(routes.createAsset, { body: data }); + if (res?.ok) { + Notification.Success({ msg: "Asset created successfully" }); + if (addMore) { + form.reset(); + const pageContainer = window.document.getElementById("pages"); + pageContainer?.scroll(0, 0); + setAddMore(false); + } else { + goBack(); + } + } + setLoading(false); + } else { + const { res } = await request(routes.updateAsset, { + pathParams: { external_id: assetId }, + body: data, + }); + if (res?.ok) { + Notification.Success({ msg: "Asset updated successfully" }); + goBack(); + } + setLoading(false); + } + }; + const parseAssetId = (assetUrl: string) => { + try { + const params = parseQueryParams(assetUrl); + // QR Maybe searchParams "asset" or "assetQR" + const assetId = params.asset || params.assetQR; + if (assetId) { + form.setValue("qr_code_id", assetId); + form.clearErrors("qr_code_id"); + setIsScannerActive(false); + return; + } + } catch (err) { + console.error(err); + Notification.Error({ msg: err }); + } + Notification.Error({ msg: "Invalid Asset Id" }); + setIsScannerActive(false); }; - const [initAssetData, setinitialAssetData] = - useState(initialAssetData); + type FormData = z.infer; + + const form = useForm({ + resolver: zodResolver(AssetFormSchema), + defaultValues: { + name: "", + location: "", + asset_class: AssetClass.NONE, + description: "", + is_working: true, + not_working_reason: "", + qr_code_id: "", + manufacturer: "", + warranty_amc_end_of_validity: new Date(), + support_name: "", + support_phone: "", + support_email: "", + vendor_name: "", + serial_number: "", + serviced_on: new Date(), + note: "", + }, + }); - const [isLoading, setIsLoading] = useState(false); - const [isScannerActive, setIsScannerActive] = useState(false); + const { + formState: { errors, isDirty }, + } = form; const [currentSection, setCurrentSection] = useState("General Details"); @@ -195,195 +241,40 @@ const AssetCreate = (props: AssetProps) => { ); const assetQuery = useTanStackQueryInstead(routes.getAsset, { - pathParams: { external_id: assetId! }, + pathParams: { external_id: String(assetId) }, prefetch: !!assetId, onResponse: ({ data: asset }) => { + console.log("Asset Data:", asset); if (!asset) return; - - setinitialAssetData({ - id: asset.id, + form.reset({ name: asset.name, - location: asset.location_object?.id, + location: asset.location_object.id, + asset_class: asset.asset_class, description: asset.description, is_working: asset.is_working, not_working_reason: asset.not_working_reason, - created_date: asset.created_date, - modified_date: asset.modified_date, - serial_number: asset.serial_number, - asset_type: asset.asset_type, - asset_class: asset.asset_class, - status: asset.status, - vendor_name: asset.vendor_name, + qr_code_id: asset.qr_code_id || "", + manufacturer: asset.manufacturer, + warranty_amc_end_of_validity: + new Date(asset.warranty_amc_end_of_validity) || new Date(), support_name: asset.support_name, - support_email: asset.support_email, support_phone: asset.support_phone, - qr_code_id: asset.qr_code_id, - manufacturer: asset.manufacturer, - warranty_amc_end_of_validity: asset.warranty_amc_end_of_validity, - latest_status: asset.latest_status, - last_service: asset.last_service, - serviced_on: asset.last_service?.serviced_on, - notes: asset.last_service?.note, + support_email: asset.support_email, + vendor_name: asset.vendor_name, + serial_number: asset.serial_number, + serviced_on: asset.last_service?.serviced_on + ? new Date(asset.last_service.serviced_on) + : new Date(), + note: asset.last_service?.note, }); }, }); - const AssetFormValidator = (form: AssetData): FormErrors => { - const errors: Partial> = {}; // Initialize error object - - errors.name = RequiredFieldValidator()(form.name); - - if (form.is_working === undefined) { - errors.is_working = t("field_required"); - } - - if (!form.location || form.location === "0" || form.location === "") { - errors.location = t("select_local_body"); - } - - if (!form.support_phone) { - errors.support_phone = t("field_required"); - } else { - const validatePhoneNumber = PhoneNumberValidator( - ["mobile", "landline", "support"], - t("invalid_phone_number"), - ); - const isValid = validatePhoneNumber(form.support_phone); - // console.log("Is Valid" ,isValid) - if (isValid == "Invalid Phone Number") { - errors.support_phone = t("invalid_phone_number"); - } - } - - if (form.support_email && !validateEmailAddress(form.support_email)) { - errors.support_email = t("invalid_email"); - } - - if (form.notes && !form.serviced_on) { - errors.serviced_on = t("last_serviced_on_required"); - } - - return errors; - }; - - const resetFilters = () => { - setinitialAssetData({ - id: "", - name: "", - location: "", - description: "", - is_working: null, - not_working_reason: "", - created_date: "", - modified_date: "", - serial_number: "", - asset_type: undefined, - asset_class: undefined, - status: "ACTIVE", - vendor_name: "", - support_name: "", - support_email: "", - support_phone: "", - qr_code_id: "", - manufacturer: "", - warranty_amc_end_of_validity: "", - latest_status: "", - last_service: null, - serviced_on: "", - notes: "", - }); - }; - - const handleSubmit = async (form: AssetData, buttonId: string) => { - setIsLoading(true); - - const data: any = { - id: form.id, - name: form.name, - asset_type: AssetType.INTERNAL, - asset_class: form.asset_class || "", - description: form.description, - is_working: form.is_working, - not_working_reason: - form.is_working === true ? "" : form.not_working_reason, - serial_number: form.serial_number, - location: form.location, - vendor_name: form.vendor_name, - support_name: form.support_name, - support_email: form.support_email, - support_phone: form.support_phone?.startsWith("1800") - ? form.support_phone - : parsePhoneNumber(String(form.support_phone)), - qr_code_id: form.qr_code_id !== "" ? form.qr_code_id : null, - manufacturer: form.manufacturer, - warranty_amc_end_of_validity: form.warranty_amc_end_of_validity - ? dateQueryString(form.warranty_amc_end_of_validity) - : null, - }; - - if (form.serviced_on) { - data["last_serviced_on"] = dateQueryString(form.serviced_on); - data["note"] = form.notes ?? ""; - } - - try { - if (!form.id) { - const { res } = await request(routes.createAsset, { body: data }); - if (res?.ok) { - Notification.Success({ msg: "Asset created successfully" }); - // Handle "Add More" logic if necessary - if (buttonId == "create-asset-add-more-button") { - resetFilters(); - const pageContainer = window.document.getElementById("pages"); - pageContainer?.scroll(0, 0); - } else { - goBack(); - } - } - } else { - const { res } = await request(routes.updateAsset, { - pathParams: { external_id: form.id }, - body: data, - }); - if (res?.ok) { - Notification.Success({ msg: "Asset updated successfully" }); - goBack(); - } - } - } catch (error) { - // Handle error (optional) - Notification.Error({ - msg: "An error occurred while processing the asset", - }); - } finally { - setIsLoading(false); - } - }; - - const parseAssetId = (assetUrl: string) => { - try { - const params = parseQueryParams(assetUrl); - // QR Maybe searchParams "asset" or "assetQR" - const assetId = params.asset || params.assetQR; - if (assetId) { - setinitialAssetData({ ...initAssetData, qr_code_id: assetId }); - setIsScannerActive(false); - return; - } - } catch (err) { - console.error(err); - Notification.Error({ msg: err }); - } - Notification.Error({ msg: "Invalid Asset Id" }); - setIsScannerActive(false); - }; - - if (isLoading || locationsQuery.loading || assetQuery.loading) { + if (loading || locationsQuery.loading || assetQuery.loading) { return ; } - const name: string | undefined = - locationsQuery.data?.results[0].facility?.name || undefined; + const name = form.watch("name"); if (locationsQuery.data?.count === 0) { return ( @@ -416,10 +307,6 @@ const AssetCreate = (props: AssetProps) => { ); } - const handleOnCancel: () => void = () => { - goBack(); - }; - if (isScannerActive) return (
@@ -481,10 +368,10 @@ const AssetCreate = (props: AssetProps) => { className="grow-0 pl-6" crumbsReplacements={{ [facilityId]: { - name, + name: locationsQuery.data?.results[0].facility?.name, }, assets: { style: "text-secondary-200 pointer-events-none" }, - [assetId || "????"]: { name: name || "Asset" }, + [assetId || "????"]: { name }, }} backUrl={ assetId @@ -518,397 +405,533 @@ const AssetCreate = (props: AssetProps) => {
- - disabled={isLoading} - defaults={initAssetData} - onCancel={handleOnCancel} - onSubmit={async (obj, buttonId) => { - await handleSubmit(obj, buttonId); - }} - className="rounded bg-white p-6 transition-all sm:rounded-xl sm:p-12" - noPadding - validate={AssetFormValidator} - submitLabel={assetId ? t("update") : t("create_asset")} - additionalButtons={ - !assetId - ? [ - { - type: "submit", - label: t("create_add_more"), - id: "create-asset-add-more-button", - }, - ] - : [] - } - > - {(field) => ( - <> -
-
- {/* General Details Section */} - {sectionTitle("General Details")} - {/* Asset Name */} -
- -
- - {/* Location */} - - {t("asset_location")} - -
- { - field("location").onChange({ - name: "location", - value: selected, - }); - }} - errors={field("location").error} - facilityId={facilityId} - multiple={false} - showAll={false} - /> -
- - {/* Asset Class */} -
- title} - optionValue={({ value }) => value} - /> -
- {/* Description */} -
- -
- {/* Divider */} -
-
-
- {/* Working Status */} -
- - field("is_working").onChange({ - name: "is_working", - value: option === "true", - }) - } - optionLabel={(option: "true" | "false") => { - return ( - { - true: "Working", - false: "Not Working", - }[option] || "undefined" - ); - }} - optionClassName={(option) => - option === "false" && - "bg-danger-500 text-white border-danger-500 focus:ring-danger-500" - } - /> -
- {/* Not Working Reason */} -
+
+
+
+ {/* General Details Section */} + {sectionTitle("General Details")} + {/* Asset Name */} +
+ ( + + Asset Name + + + + + + )} + /> +
+ {/* Location */} +
+ ( + + Location + + + + + + )} + /> +
+ {/* Asset Class */} +
+ ( + + + Asset Class + + + + + )} + /> +
+ {/* Description */} +
+ ( + + + Description + + +