IO-3310 Shop Data Preservation on Hidden Fields
Signed-off-by: Allan Carr <allan@imexsystems.ca>
This commit is contained in:
@@ -1,15 +1,16 @@
|
|||||||
import { useMutation, useQuery } from "@apollo/client";
|
import { useMutation, useQuery } from "@apollo/client";
|
||||||
import { Form } from "antd";
|
import { Form } from "antd";
|
||||||
import dayjs from "../../utils/day";
|
import { useEffect, useState } from "react";
|
||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
import { QUERY_BODYSHOP, UPDATE_SHOP } from "../../graphql/bodyshop.queries";
|
import { QUERY_BODYSHOP, UPDATE_SHOP } from "../../graphql/bodyshop.queries";
|
||||||
|
import dayjs from "../../utils/day";
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import FormsFieldChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
import FormsFieldChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
||||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||||
import ShopInfoComponent from "./shop-info.component";
|
import ShopInfoComponent from "./shop-info.component";
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
import { FEATURE_CONFIGS, useFormDataPreservation } from "./useFormDataPreservation";
|
||||||
|
|
||||||
export default function ShopInfoContainer() {
|
export default function ShopInfoContainer() {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
@@ -22,16 +23,24 @@ export default function ShopInfoContainer() {
|
|||||||
});
|
});
|
||||||
const notification = useNotification();
|
const notification = useNotification();
|
||||||
|
|
||||||
const handleFinish = (values) => {
|
const combinedFeatureConfig = {
|
||||||
|
...FEATURE_CONFIGS.general,
|
||||||
|
...FEATURE_CONFIGS.responsibilitycenters
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use form data preservation for all shop-info features
|
||||||
|
const { createSubmissionHandler } = useFormDataPreservation(form, data?.bodyshops[0], combinedFeatureConfig);
|
||||||
|
|
||||||
|
const handleFinish = createSubmissionHandler((values) => {
|
||||||
setSaveLoading(true);
|
setSaveLoading(true);
|
||||||
logImEXEvent("shop_update");
|
logImEXEvent("shop_update");
|
||||||
|
|
||||||
updateBodyshop({
|
updateBodyshop({
|
||||||
variables: { id: data.bodyshops[0].id, shop: values }
|
variables: { id: data.bodyshops[0].id, shop: values }
|
||||||
})
|
})
|
||||||
.then((r) => {
|
.then(() => {
|
||||||
notification["success"]({ message: t("bodyshop.successes.save") });
|
notification["success"]({ message: t("bodyshop.successes.save") });
|
||||||
refetch().then((_) => form.resetFields());
|
refetch().then(() => form.resetFields());
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
notification["error"]({
|
notification["error"]({
|
||||||
@@ -39,7 +48,7 @@ export default function ShopInfoContainer() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
setSaveLoading(false);
|
setSaveLoading(false);
|
||||||
};
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data) form.resetFields();
|
if (data) form.resetFields();
|
||||||
|
|||||||
140
client/src/components/shop-info/useFormDataPreservation.js
Normal file
140
client/src/components/shop-info/useFormDataPreservation.js
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { HasFeatureAccess } from "./../feature-wrapper/feature-wrapper.component";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom hook to preserve form data for conditionally hidden fields based on feature access
|
||||||
|
* @param {Object} form - Ant Design form instance
|
||||||
|
* @param {Object} bodyshop - Bodyshop data for feature access checks (also contains existing database values)
|
||||||
|
* @param {Object} featureConfig - Configuration object defining which features and their associated fields to preserve
|
||||||
|
*/
|
||||||
|
export const useFormDataPreservation = (form, bodyshop, featureConfig) => {
|
||||||
|
const getNestedValue = (obj, path) => {
|
||||||
|
return path.reduce((current, key) => current?.[key], obj);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setNestedValue = (obj, path, value) => {
|
||||||
|
const lastKey = path[path.length - 1];
|
||||||
|
const parentPath = path.slice(0, -1);
|
||||||
|
|
||||||
|
const parent = parentPath.reduce((current, key) => {
|
||||||
|
if (!current[key]) current[key] = {};
|
||||||
|
return current[key];
|
||||||
|
}, obj);
|
||||||
|
|
||||||
|
parent[lastKey] = value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const preserveHiddenFormData = () => {
|
||||||
|
const preservationData = {};
|
||||||
|
let hasDataToPreserve = false;
|
||||||
|
|
||||||
|
Object.entries(featureConfig).forEach(([featureName, fieldPaths]) => {
|
||||||
|
const hasAccess = HasFeatureAccess({ featureName, bodyshop });
|
||||||
|
|
||||||
|
if (!hasAccess) {
|
||||||
|
fieldPaths.forEach((fieldPath) => {
|
||||||
|
const currentValues = form.getFieldsValue();
|
||||||
|
let value = getNestedValue(currentValues, fieldPath);
|
||||||
|
|
||||||
|
if (value === undefined || value === null) {
|
||||||
|
value = getNestedValue(bodyshop, fieldPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value !== undefined && value !== null) {
|
||||||
|
setNestedValue(preservationData, fieldPath, value);
|
||||||
|
hasDataToPreserve = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (hasDataToPreserve) {
|
||||||
|
form.setFieldsValue(preservationData);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCompleteFormValues = () => {
|
||||||
|
const currentFormValues = form.getFieldsValue();
|
||||||
|
const completeValues = { ...currentFormValues };
|
||||||
|
|
||||||
|
Object.entries(featureConfig).forEach(([featureName, fieldPaths]) => {
|
||||||
|
const hasAccess = HasFeatureAccess({ featureName, bodyshop });
|
||||||
|
|
||||||
|
if (!hasAccess) {
|
||||||
|
fieldPaths.forEach((fieldPath) => {
|
||||||
|
let value = getNestedValue(currentFormValues, fieldPath);
|
||||||
|
if (value === undefined || value === null) {
|
||||||
|
value = getNestedValue(bodyshop, fieldPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value !== undefined && value !== null) {
|
||||||
|
setNestedValue(completeValues, fieldPath, value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return completeValues;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createSubmissionHandler = (originalHandler) => {
|
||||||
|
return () => {
|
||||||
|
const completeValues = getCompleteFormValues();
|
||||||
|
|
||||||
|
// Call the original handler with complete values including hidden data
|
||||||
|
return originalHandler(completeValues);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
preserveHiddenFormData();
|
||||||
|
}, [bodyshop]);
|
||||||
|
|
||||||
|
return { preserveHiddenFormData, getCompleteFormValues, createSubmissionHandler };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Predefined feature configurations for common shop-info components
|
||||||
|
*/
|
||||||
|
export const FEATURE_CONFIGS = {
|
||||||
|
responsibilitycenters: {
|
||||||
|
export: [
|
||||||
|
["md_responsibility_centers", "costs"],
|
||||||
|
["md_responsibility_centers", "profits"],
|
||||||
|
["md_responsibility_centers", "defaults"],
|
||||||
|
["md_responsibility_centers", "dms_defaults"],
|
||||||
|
["md_responsibility_centers", "taxes", "itemexemptcode"],
|
||||||
|
["md_responsibility_centers", "taxes", "invoiceexemptcode"],
|
||||||
|
["md_responsibility_centers", "ar"],
|
||||||
|
["md_responsibility_centers", "refund"],
|
||||||
|
["md_responsibility_centers", "sales_tax_codes"],
|
||||||
|
["md_responsibility_centers", "ttl_adjustment"],
|
||||||
|
["md_responsibility_centers", "ttl_tax_adjustment"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
general: {
|
||||||
|
export: [
|
||||||
|
["accountingconfig", "qbo"],
|
||||||
|
["accountingconfig", "qbo_usa"],
|
||||||
|
["accountingconfig", "qbo_departmentid"],
|
||||||
|
["accountingconfig", "tiers"],
|
||||||
|
["accountingconfig", "twotierpref"],
|
||||||
|
["accountingconfig", "printlater"],
|
||||||
|
["accountingconfig", "emaillater"],
|
||||||
|
["accountingconfig", "ReceivableCustomField1"],
|
||||||
|
["accountingconfig", "ReceivableCustomField2"],
|
||||||
|
["accountingconfig", "ReceivableCustomField3"],
|
||||||
|
["md_classes"],
|
||||||
|
["enforce_class"],
|
||||||
|
["accountingconfig", "ClosingPeriod"],
|
||||||
|
["accountingconfig", "companyCode"],
|
||||||
|
["accountingconfig", "batchID"]
|
||||||
|
],
|
||||||
|
bills: [
|
||||||
|
["bill_tax_rates", "federal_tax_rate"],
|
||||||
|
["bill_tax_rates", "state_tax_rate"],
|
||||||
|
["bill_tax_rates", "local_tax_rate"]
|
||||||
|
],
|
||||||
|
timetickets: [["tt_allow_post_to_invoiced"], ["tt_enforce_hours_for_tech_console"], ["bill_allow_post_to_closed"]]
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user