Merge remote-tracking branch 'origin/release/2025-09-12' into feature/IO-3255-simplified-part-management

This commit is contained in:
Dave
2025-09-10 15:58:06 -04:00
15 changed files with 142 additions and 154 deletions

View File

@@ -67,11 +67,14 @@ export const uploadToS3 = async (
}
//Key should be same as we provided to maintain backwards compatibility.
const { presignedUrl: preSignedUploadUrlToS3, key: s3Key } = signedURLResponse.data.signedUrls[0];
const { presignedUrl: preSignedUploadUrlToS3, key: s3Key, contentType } = signedURLResponse.data.signedUrls[0];
const options = {
onUploadProgress: (e) => {
if (onProgress) onProgress({ percent: (e.loaded / e.total) * 100 });
},
headers: {
...contentType ? { "Content-Type": fileType } : {}
}
};

View File

@@ -20,35 +20,27 @@ export function JobTotalsCashDiscount({ bodyshop, amountDinero }) {
const notification = useNotification();
const fetchData = useCallback(async () => {
if (amountDinero && bodyshop) {
setLoading(true);
let response;
try {
response = await axios.post("/intellipay/checkfee", {
bodyshop: { id: bodyshop.id, imexshopid: bodyshop.imexshopid, state: bodyshop.state },
amount: Dinero(amountDinero).toFormat("0.00")
});
if (!amountDinero || !bodyshop) return;
if (response?.data?.error) {
notification.open({
type: "error",
message:
response.data?.error ||
"Error encountered when contacting IntelliPay service to determine cash discounted price."
});
} else {
setFee(response.data?.fee || 0);
}
} catch (error) {
notification.open({
type: "error",
message:
error.response?.data?.error ||
"Error encountered when contacting IntelliPay service to determine cash discounted price."
});
} finally {
setLoading(false);
setLoading(true);
const errorMessage = "Error encountered when contacting IntelliPay service to determine cash discounted price.";
try {
const { id, imexshopid, state } = bodyshop;
const { data } = await axios.post("/intellipay/checkfee", {
bodyshop: { id, imexshopid, state },
amount: Dinero(amountDinero).toUnit()
});
if (data?.error) {
notification.open({ type: "error", message: data.error || errorMessage });
} else {
setFee(data?.fee ?? 0);
}
} catch (error) {
notification.open({ type: "error", message: error.response?.data?.error || errorMessage });
} finally {
setLoading(false);
}
}, [amountDinero, bodyshop, notification]);

View File

@@ -40,27 +40,26 @@ export function ScheduleCalendarWrapperComponent({
const currentView = search.view || defaultView || "week";
const handleEventPropStyles = (event) => {
const hasColor = Boolean(event?.color?.hex || event?.color);
const { color, block, arrived } = event ?? {};
const hasColor = Boolean(color?.hex || color);
const useBg = currentView !== "agenda";
// Prioritize explicit blocked-day background to ensure red in all themes
let bg;
if (useBg) {
if (event?.block) {
bg = "var(--event-block-bg)";
} else if (hasColor) {
bg = event?.color?.hex ?? event?.color;
} else {
bg = "var(--event-bg-fallback)";
}
bg = block
? "var(--event-block-bg)"
: arrived
? "var(--event-arrived-bg)"
: (color?.hex ?? color ?? "var(--event-bg-fallback)");
}
const usedFallback = !hasColor && !event?.block; // only mark as fallback when not blocked
const usedFallback = !hasColor && !block && !arrived; // only mark as fallback when not blocked or arrived
const classes = [
"imex-event",
event.arrived && "imex-event-arrived",
event.block && "imex-event-block",
arrived && "imex-event-arrived",
block && "imex-event-block",
usedFallback && "imex-event-fallback"
]
.filter(Boolean)

View File

@@ -23,13 +23,24 @@ export default function ShopInfoContainer() {
});
const notification = useNotification();
const combinedFeatureConfig = {
...FEATURE_CONFIGS.general,
...FEATURE_CONFIGS.responsibilitycenters
};
const combineFeatureConfigs = (...configs) =>
(configs || [])
.filter(Boolean)
.flatMap((cfg) => Object.entries(cfg))
.reduce((acc, [featureName, fieldPaths]) => {
if (!Array.isArray(fieldPaths)) return acc;
acc[featureName] = [...(acc[featureName] ?? []), ...fieldPaths];
return acc;
}, {});
const combinedFeatureConfig = combineFeatureConfigs(FEATURE_CONFIGS.general, FEATURE_CONFIGS.responsibilitycenters);
// Use form data preservation for all shop-info features
const { createSubmissionHandler } = useFormDataPreservation(form, data?.bodyshops[0], combinedFeatureConfig);
const { createSubmissionHandler, preserveHiddenFormData } = useFormDataPreservation(
form,
data?.bodyshops[0],
combinedFeatureConfig
);
const handleFinish = createSubmissionHandler((values) => {
setSaveLoading(true);
@@ -51,8 +62,11 @@ export default function ShopInfoContainer() {
});
useEffect(() => {
if (data) form.resetFields();
}, [form, data]);
if (!data) return;
form.resetFields();
// After reset, re-apply hidden field preservation so values aren't wiped
preserveHiddenFormData();
}, [data, form, preserveHiddenFormData]);
if (error) return <AlertComponent message={error.message} type="error" />;
if (loading) return <LoadingSpinner />;

View File

@@ -1,4 +1,4 @@
import { useCallback, useEffect } from "react";
import { useCallback, useEffect, useMemo } from "react";
import { HasFeatureAccess } from "./../feature-wrapper/feature-wrapper.component";
/**
@@ -8,73 +8,57 @@ import { HasFeatureAccess } from "./../feature-wrapper/feature-wrapper.component
* @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);
};
// Safe nested getters/setters using path arrays
const getNestedValue = (obj, path) => path?.reduce((acc, key) => acc?.[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];
const parent = path.slice(0, -1).reduce((curr, key) => {
if (!curr[key] || typeof curr[key] !== "object") curr[key] = {};
return curr[key];
}, obj);
parent[lastKey] = value;
};
const preserveHiddenFormData = useCallback(() => {
const preservationData = {};
let hasDataToPreserve = false;
// Paths for features that are NOT accessible
const disabledPaths = useMemo(() => {
const result = [];
if (!featureConfig) return result;
Object.entries(featureConfig).forEach(([featureName, fieldPaths]) => {
const hasAccess = HasFeatureAccess({ featureName, bodyshop });
if (hasAccess || !Array.isArray(fieldPaths)) return;
fieldPaths.forEach((p) => Array.isArray(p) && p.length && result.push(p));
});
return result;
}, [featureConfig, bodyshop]);
if (!hasAccess) {
fieldPaths.forEach((fieldPath) => {
const currentValues = form.getFieldsValue();
let value = getNestedValue(currentValues, fieldPath);
const preserveHiddenFormData = useCallback(() => {
const currentValues = form.getFieldsValue();
const preservationData = {};
let hasAny = false;
if (value === undefined || value === null) {
value = getNestedValue(bodyshop, fieldPath);
}
if (value !== undefined && value !== null) {
setNestedValue(preservationData, fieldPath, value);
hasDataToPreserve = true;
}
});
disabledPaths.forEach((path) => {
let value = getNestedValue(currentValues, path);
if (value == null) value = getNestedValue(bodyshop, path);
if (value != null) {
setNestedValue(preservationData, path, value);
hasAny = true;
}
});
if (hasDataToPreserve) {
form.setFieldsValue(preservationData);
}
}, [form, featureConfig, bodyshop]);
if (hasAny) form.setFieldsValue(preservationData);
}, [form, bodyshop, disabledPaths]);
const getCompleteFormValues = () => {
const currentFormValues = form.getFieldsValue();
const completeValues = { ...currentFormValues };
const currentValues = form.getFieldsValue();
const complete = { ...currentValues };
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);
}
});
}
disabledPaths.forEach((path) => {
let value = getNestedValue(currentValues, path);
if (value == null) value = getNestedValue(bodyshop, path);
if (value != null) setNestedValue(complete, path, value);
});
return completeValues;
return complete;
};
const createSubmissionHandler = (originalHandler) => {
@@ -103,8 +87,8 @@ export const FEATURE_CONFIGS = {
["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", "taxes"],
["md_responsibility_centers", "cieca_pfl"],
["md_responsibility_centers", "ar"],
["md_responsibility_centers", "refund"],
["md_responsibility_centers", "sales_tax_codes"],

View File

@@ -180,7 +180,7 @@ export function VendorsFormComponent({ bodyshop, form, formLoading, handleDelete
<Form.Item
name="tags"
label={t("vendor.fields.tags")}
label={t("vendors.fields.tags")}
rules={[
{
//message: t("general.validation.required"),