diff --git a/client/src/components/parts-queue-card/parts-queue-card.component.jsx b/client/src/components/parts-queue-card/parts-queue-card.component.jsx
index 3044c7e97..34c47bf77 100644
--- a/client/src/components/parts-queue-card/parts-queue-card.component.jsx
+++ b/client/src/components/parts-queue-card/parts-queue-card.component.jsx
@@ -1,9 +1,8 @@
import { useQuery } from "@apollo/client";
import { Card, Divider, Drawer, Grid } from "antd";
import queryString from "query-string";
-import React from "react";
import { useTranslation } from "react-i18next";
-import { Link, useNavigate, useLocation } from "react-router-dom";
+import { Link, useLocation, useNavigate } from "react-router-dom";
import { QUERY_PARTS_QUEUE_CARD_DETAILS } from "../../graphql/jobs.queries";
import AlertComponent from "../alert/alert.component";
import JobsDetailHeader from "../jobs-detail-header/jobs-detail-header.component";
diff --git a/client/src/components/parts-shop-info/parts-business-info.component.jsx b/client/src/components/parts-shop-info/parts-business-info.component.jsx
new file mode 100644
index 000000000..ad119477d
--- /dev/null
+++ b/client/src/components/parts-shop-info/parts-business-info.component.jsx
@@ -0,0 +1,129 @@
+import { Form, Input, InputNumber, Select } from "antd";
+import { useTranslation } from "react-i18next";
+import { connect } from "react-redux";
+import { createStructuredSelector } from "reselect";
+import { selectBodyshop } from "../../redux/user/user.selectors";
+import PhoneFormItem, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component";
+import LayoutFormRow from "../layout-form-row/layout-form-row.component";
+
+// eslint-disable-next-line no-undef
+const timeZonesList = Intl.supportedValuesOf("timeZone");
+
+const mapStateToProps = createStructuredSelector({
+ bodyshop: selectBodyshop
+});
+
+const mapDispatchToProps = () => ({});
+
+export function PartsBusinessInfoComponent({ form }) {
+ const { t } = useTranslation();
+
+ return (
+
+ );
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(PartsBusinessInfoComponent);
diff --git a/client/src/components/parts-shop-info/parts-email-presets.component.jsx b/client/src/components/parts-shop-info/parts-email-presets.component.jsx
new file mode 100644
index 000000000..449a35ac3
--- /dev/null
+++ b/client/src/components/parts-shop-info/parts-email-presets.component.jsx
@@ -0,0 +1,60 @@
+import { DeleteFilled } from "@ant-design/icons";
+import { Button, Form, Input, Select, Space } from "antd";
+import { useTranslation } from "react-i18next";
+import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
+import LayoutFormRow from "../layout-form-row/layout-form-row.component";
+
+export default function PartsEmailPresetsComponent({ form }) {
+ const { t } = useTranslation();
+
+ return (
+
+
+
+ {(fields, { add, remove, move }) => {
+ return (
+
+ {fields.map((field, index) => (
+
+
+
+
+
+
+
+
+
+
+ {
+ remove(field.name);
+ }}
+ />
+
+
+
+
+ ))}
+
+
+
+
+ );
+ }}
+
+
+
+ );
+}
diff --git a/client/src/components/parts-shop-info/parts-locations.component.jsx b/client/src/components/parts-shop-info/parts-locations.component.jsx
new file mode 100644
index 000000000..be5e96277
--- /dev/null
+++ b/client/src/components/parts-shop-info/parts-locations.component.jsx
@@ -0,0 +1,63 @@
+import { DeleteFilled } from "@ant-design/icons";
+import { Button, Form, Input, Space } from "antd";
+import { useTranslation } from "react-i18next";
+import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
+import LayoutFormRow from "../layout-form-row/layout-form-row.component";
+
+export default function PartsLocationsComponent({ form }) {
+ const { t } = useTranslation();
+
+ return (
+
+
+
+ {(fields, { add, remove, move }) => {
+ return (
+
+ {fields.map((field, index) => (
+
+
+
+
+
+
+ {
+ remove(field.name);
+ }}
+ />
+
+
+
+
+ ))}
+
+
+
+
+ );
+ }}
+
+
+
+ );
+}
diff --git a/client/src/components/parts-shop-info/parts-order-comments.component.jsx b/client/src/components/parts-shop-info/parts-order-comments.component.jsx
new file mode 100644
index 000000000..536f1a2e9
--- /dev/null
+++ b/client/src/components/parts-shop-info/parts-order-comments.component.jsx
@@ -0,0 +1,74 @@
+import { DeleteFilled } from "@ant-design/icons";
+import { Button, Form, Input, Space } from "antd";
+import { useTranslation } from "react-i18next";
+import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
+import LayoutFormRow from "../layout-form-row/layout-form-row.component";
+
+export default function PartsOrderCommentsComponent({ form }) {
+ const { t } = useTranslation();
+
+ return (
+
+
+
+ );
+}
diff --git a/client/src/components/parts-shop-info/parts-orders-comments.component.jsx b/client/src/components/parts-shop-info/parts-orders-comments.component.jsx
new file mode 100644
index 000000000..7815bca9b
--- /dev/null
+++ b/client/src/components/parts-shop-info/parts-orders-comments.component.jsx
@@ -0,0 +1,61 @@
+import { Button, Card, Divider, Form, Input, Space } from "antd";
+import { DeleteOutlined, PlusOutlined } from "@ant-design/icons";
+import { useTranslation } from "react-i18next";
+
+const { TextArea } = Input;
+
+export default function PartsOrdersCommentsComponent({ form }) {
+ const { t } = useTranslation();
+
+ return (
+
+
+ {(fields, { add, remove }) => (
+ <>
+ {fields.map(({ key, name, ...restField }) => (
+
+
+
+
+
+
+
+
+
+
+ } onClick={() => remove(name)} />
+
+ ))}
+
+
+
+
+ >
+ )}
+
+
+ );
+}
diff --git a/client/src/components/parts-shop-info/parts-shop-info-email-presets.component.jsx b/client/src/components/parts-shop-info/parts-shop-info-email-presets.component.jsx
new file mode 100644
index 000000000..2320c97a7
--- /dev/null
+++ b/client/src/components/parts-shop-info/parts-shop-info-email-presets.component.jsx
@@ -0,0 +1,70 @@
+import { Button, Card, Divider, Form, Input, Select, Space } from "antd";
+import { DeleteOutlined, PlusOutlined } from "@ant-design/icons";
+import { useTranslation } from "react-i18next";
+
+const { Option } = Select;
+
+export default function PartsShopInfoEmailPresets({ form }) {
+ const { t } = useTranslation();
+
+ const emailTypes = [
+ { value: "parts_order", label: t("bodyshop.labels.parts_order") },
+ { value: "parts_receipt", label: t("bodyshop.labels.parts_receipt") },
+ { value: "parts_notification", label: t("bodyshop.labels.parts_notification") }
+ ];
+
+ return (
+
+
+ {(fields, { add, remove }) => (
+ <>
+ {fields.map(({ key, name, ...restField }) => (
+
+
+
+
+
+
+
+
+
+
+ } onClick={() => remove(name)} />
+
+ ))}
+
+
+
+
+ >
+ )}
+
+
+ );
+}
diff --git a/client/src/components/parts-shop-info/parts-shop-info-limited.component.jsx b/client/src/components/parts-shop-info/parts-shop-info-limited.component.jsx
new file mode 100644
index 000000000..629010796
--- /dev/null
+++ b/client/src/components/parts-shop-info/parts-shop-info-limited.component.jsx
@@ -0,0 +1,80 @@
+import { Button, Card, Tabs } from "antd";
+import queryString from "query-string";
+import { useTranslation } from "react-i18next";
+import { connect } from "react-redux";
+import { useLocation, useNavigate } from "react-router-dom";
+import { createStructuredSelector } from "reselect";
+import { useSocket } from "../../contexts/SocketIO/useSocket.js";
+import { selectBodyshop } from "../../redux/user/user.selectors";
+import ShopInfoGeneral from "../shop-info/shop-info.general.component";
+import ShopInfoResponsibilityCenterComponent from "../shop-info/shop-info.responsibilitycenters.component";
+import ShopInfoOrderStatusComponent from "../shop-info/shop-info.orderstatus.component";
+import ShopInfoNotificationsAutoadd from "../shop-info/shop-info.notifications-autoadd.component";
+
+const mapStateToProps = createStructuredSelector({
+ bodyshop: selectBodyshop
+});
+
+const mapDispatchToProps = () => ({});
+
+export function PartsShopInfoComponent({ bodyshop, form, saveLoading }) {
+ const { scenarioNotificationsOn } = useSocket();
+ const { t } = useTranslation();
+ const history = useNavigate();
+ const location = useLocation();
+ const search = queryString.parse(location.search);
+
+ // Only include the 4 specific sections requested for parts settings
+ const tabItems = [
+ {
+ key: "general",
+ label: t("bodyshop.labels.shopinfo"), // Business Information
+ children: ,
+ id: "tab-parts-general"
+ },
+ {
+ key: "responsibilityCenters",
+ label: t("bodyshop.labels.responsibilitycenters.title"), // Parts Locations
+ children: ,
+ id: "tab-parts-responsibilitycenters"
+ },
+ {
+ key: "orderStatus",
+ label: t("bodyshop.labels.orderstatuses"), // Parts Orders Comments
+ children: ,
+ id: "tab-parts-orderstatus"
+ }
+ ];
+
+ // Only add notifications tab if scenario notifications are enabled (Preset To Emails)
+ if (scenarioNotificationsOn) {
+ tabItems.push({
+ key: "notifications_autoadd",
+ label: t("bodyshop.labels.notifications.followers"), // Preset To Emails
+ children: ,
+ id: "tab-parts-notifications"
+ });
+ }
+
+ return (
+ form.submit()} id="parts-shop-info-save-button">
+ {t("general.actions.save")}
+
+ }
+ >
+
+ history({
+ search: `?tab=${search.tab}&subtab=${key}`
+ })
+ }
+ items={tabItems}
+ />
+
+ );
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(PartsShopInfoComponent);
diff --git a/client/src/components/parts-shop-info/parts-shop-info-locations.component.jsx b/client/src/components/parts-shop-info/parts-shop-info-locations.component.jsx
new file mode 100644
index 000000000..538b57f74
--- /dev/null
+++ b/client/src/components/parts-shop-info/parts-shop-info-locations.component.jsx
@@ -0,0 +1,48 @@
+import { Button, Card, Divider, Form, Input, Space } from "antd";
+import { DeleteOutlined, PlusOutlined } from "@ant-design/icons";
+import { useTranslation } from "react-i18next";
+
+export default function PartsShopInfoLocations({ form }) {
+ const { t } = useTranslation();
+
+ return (
+
+
+ {(fields, { add, remove }) => (
+ <>
+ {fields.map(({ key, name, ...restField }) => (
+
+
+
+
+
+
+
+
+
+
+ } onClick={() => remove(name)} />
+
+ ))}
+
+
+
+
+ >
+ )}
+
+
+ );
+}
diff --git a/client/src/components/parts-shop-info/parts-shop-info.component.jsx b/client/src/components/parts-shop-info/parts-shop-info.component.jsx
new file mode 100644
index 000000000..3d583dd2d
--- /dev/null
+++ b/client/src/components/parts-shop-info/parts-shop-info.component.jsx
@@ -0,0 +1,79 @@
+import { Button, Card, Tabs } from "antd";
+import queryString from "query-string";
+import { useTranslation } from "react-i18next";
+import { connect } from "react-redux";
+import { useLocation, useNavigate } from "react-router-dom";
+import { createStructuredSelector } from "reselect";
+import { useSocket } from "../../contexts/SocketIO/useSocket.js";
+import { selectBodyshop } from "../../redux/user/user.selectors";
+import ShopInfoGeneral from "../shop-info/shop-info.general.component";
+import ShopInfoResponsibilityCenterComponent from "../shop-info/shop-info.responsibilitycenters.component";
+import ShopInfoOrderStatusComponent from "../shop-info/shop-info.orderstatus.component";
+import ShopInfoNotificationsAutoadd from "../shop-info/shop-info.notifications-autoadd.component";
+
+const mapStateToProps = createStructuredSelector({
+ bodyshop: selectBodyshop
+});
+
+const mapDispatchToProps = () => ({});
+
+export function PartsShopInfoComponent({ bodyshop, form, saveLoading }) {
+ const { scenarioNotificationsOn } = useSocket();
+ const { t } = useTranslation();
+ const history = useNavigate();
+ const location = useLocation();
+ const search = queryString.parse(location.search);
+
+ const tabItems = [
+ {
+ key: "general",
+ label: t("bodyshop.labels.shopinfo"),
+ children: ,
+ id: "tab-parts-general"
+ },
+ {
+ key: "responsibilityCenters",
+ label: t("bodyshop.labels.responsibilitycenters.title"),
+ children: ,
+ id: "tab-parts-responsibilitycenters"
+ },
+ {
+ key: "orderStatus",
+ label: t("bodyshop.labels.orderstatuses"),
+ children: ,
+ id: "tab-parts-orderstatus"
+ }
+ ];
+
+ // Only add notifications tab if scenario notifications are enabled (same condition as full shop settings)
+ if (scenarioNotificationsOn) {
+ tabItems.push({
+ key: "notifications_autoadd",
+ label: t("bodyshop.labels.notifications.followers"),
+ children: ,
+ id: "tab-parts-notifications"
+ });
+ }
+
+ return (
+ form.submit()} id="parts-shop-info-save-button">
+ {t("general.actions.save")}
+
+ }
+ >
+
+ history({
+ search: `?tab=${search.tab}&subtab=${key}`
+ })
+ }
+ items={tabItems}
+ />
+
+ );
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(PartsShopInfoComponent);
diff --git a/client/src/components/parts-shop-info/parts-shop-info.container.jsx b/client/src/components/parts-shop-info/parts-shop-info.container.jsx
new file mode 100644
index 000000000..0614050d9
--- /dev/null
+++ b/client/src/components/parts-shop-info/parts-shop-info.container.jsx
@@ -0,0 +1,71 @@
+import { useMutation, useQuery } from "@apollo/client";
+import { Form } from "antd";
+import { useEffect, useState } from "react";
+import { useTranslation } from "react-i18next";
+import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
+import { logImEXEvent } from "../../firebase/firebase.utils";
+import { QUERY_BODYSHOP, UPDATE_SHOP } from "../../graphql/bodyshop.queries";
+import dayjs from "../../utils/day";
+import AlertComponent from "../alert/alert.component";
+import FormsFieldChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
+import LoadingSpinner from "../loading-spinner/loading-spinner.component";
+import PartsShopManagementComponent from "./parts-shop-management.component";
+
+export default function PartsShopInfoContainer() {
+ const [form] = Form.useForm();
+ const { t } = useTranslation();
+ const [saveLoading, setSaveLoading] = useState(false);
+ const [updateBodyshop] = useMutation(UPDATE_SHOP);
+ const { loading, error, data, refetch } = useQuery(QUERY_BODYSHOP, {
+ fetchPolicy: "network-only",
+ nextFetchPolicy: "network-only"
+ });
+ const notification = useNotification();
+
+ const handleFinish = (values) => {
+ setSaveLoading(true);
+ logImEXEvent("parts_shop_update");
+
+ updateBodyshop({
+ variables: { id: data.bodyshops[0].id, shop: values }
+ })
+ .then(() => {
+ notification["success"]({ message: t("bodyshop.successes.save") });
+ refetch().then(() => form.resetFields());
+ })
+ .catch((error) => {
+ notification["error"]({
+ message: t("bodyshop.errors.saving", { message: error })
+ });
+ });
+ setSaveLoading(false);
+ };
+
+ useEffect(() => {
+ if (data) form.resetFields();
+ }, [form, data]);
+
+ if (error) return ;
+ if (loading) return ;
+
+ return (
+
+ );
+}
diff --git a/client/src/components/parts-shop-info/parts-shop-management.component.jsx b/client/src/components/parts-shop-info/parts-shop-management.component.jsx
new file mode 100644
index 000000000..253995854
--- /dev/null
+++ b/client/src/components/parts-shop-info/parts-shop-management.component.jsx
@@ -0,0 +1,56 @@
+import { Button, Card, Divider, Space } from "antd";
+import { useTranslation } from "react-i18next";
+import { connect } from "react-redux";
+import { createStructuredSelector } from "reselect";
+import { useSocket } from "../../contexts/SocketIO/useSocket.js";
+import { selectBodyshop } from "../../redux/user/user.selectors";
+import PartsBusinessInfoComponent from "./parts-business-info.component";
+import PartsLocationsComponent from "./parts-locations.component";
+import PartsOrderCommentsComponent from "./parts-order-comments.component";
+import PartsEmailPresetsComponent from "./parts-email-presets.component";
+
+const mapStateToProps = createStructuredSelector({
+ bodyshop: selectBodyshop
+});
+
+const mapDispatchToProps = () => ({});
+
+export function PartsShopManagementComponent({ bodyshop, form, saveLoading }) {
+ const { scenarioNotificationsOn } = useSocket();
+ const { t } = useTranslation();
+
+ return (
+ form.submit()} id="parts-shop-save-button">
+ {t("general.actions.save")}
+
+ }
+ >
+
+ {/* Business Information Section - Limited to basic shop info only */}
+
+
+
+
+ {/* Parts Locations Section */}
+
+
+
+
+ {/* Parts Orders Comments Section */}
+
+
+ {/* Preset To Emails Section - only show if notifications are enabled */}
+ {scenarioNotificationsOn && (
+ <>
+
+
+ >
+ )}
+
+
+ );
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(PartsShopManagementComponent);
diff --git a/client/src/components/simplified-parts-jobs-list/simplified-parts-jobs-list.component.jsx b/client/src/components/simplified-parts-jobs-list/simplified-parts-jobs-list.component.jsx
index db57f62b5..e224f151d 100644
--- a/client/src/components/simplified-parts-jobs-list/simplified-parts-jobs-list.component.jsx
+++ b/client/src/components/simplified-parts-jobs-list/simplified-parts-jobs-list.component.jsx
@@ -1,4 +1,4 @@
-import { SyncOutlined } from "@ant-design/icons";
+import { SettingOutlined, SyncOutlined } from "@ant-design/icons";
import { Button, Card, Input, Space, Table, Typography } from "antd";
import axios from "axios";
import _ from "lodash";
@@ -15,6 +15,7 @@ import { alphaSort, statusSort } from "../../utils/sorters";
import useLocalStorage from "../../utils/useLocalStorage";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component";
+import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
@@ -192,6 +193,15 @@ export function SimplifiedPartsJobsListComponent({ bodyshop, refetch, loading, j
+
+ }
+ onClick={() => history("/parts/parts-settings")}
+ title={t("general.labels.settings")}
+ >
+ {t("general.labels.settings")}
+
+
{search.search && (
<>
diff --git a/client/src/pages/parts-settings/parts-settings.page.component.jsx b/client/src/pages/parts-settings/parts-settings.page.component.jsx
new file mode 100644
index 000000000..1fd47bbbb
--- /dev/null
+++ b/client/src/pages/parts-settings/parts-settings.page.component.jsx
@@ -0,0 +1,77 @@
+import { Tabs } from "antd";
+import queryString from "query-string";
+import React, { useEffect } from "react";
+import { useTranslation } from "react-i18next";
+import { connect } from "react-redux";
+import { useLocation, useNavigate } from "react-router-dom";
+import { createStructuredSelector } from "reselect";
+import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
+import ShopVendorPageComponent from "../shop-vendor/shop-vendor.page.component";
+import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
+import { selectBodyshop } from "../../redux/user/user.selectors";
+import InstanceRenderManager from "../../utils/instanceRenderMgr";
+import PartsShopInfoContainer from "../../components/parts-shop-info/parts-shop-info.container";
+
+const mapStateToProps = createStructuredSelector({
+ bodyshop: selectBodyshop
+});
+
+const mapDispatchToProps = (dispatch) => ({
+ setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
+ setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs))
+});
+
+export function PartsSettingsPage({ bodyshop, setSelectedHeader, setBreadcrumbs }) {
+ const { t } = useTranslation();
+ const history = useNavigate();
+ const search = queryString.parse(useLocation().search);
+
+ useEffect(() => {
+ document.title = t("titles.parts_settings", {
+ app: InstanceRenderManager({
+ imex: "$t(titles.imexonline)",
+ rome: "$t(titles.romeonline)"
+ })
+ });
+ setSelectedHeader("parts-settings");
+ setBreadcrumbs([
+ {
+ link: "/parts",
+ label: t("titles.bc.parts")
+ },
+ {
+ link: "/parts/settings",
+ label: t("titles.bc.parts_settings")
+ }
+ ]);
+ }, [t, setSelectedHeader, setBreadcrumbs]);
+
+ useEffect(() => {
+ if (!search.tab) history({ search: "?tab=shop" });
+ }, [history, search]);
+
+ const items = [
+ {
+ key: "shop",
+ label: t("bodyshop.labels.shop_management"),
+ children: (
+
+
+
+ )
+ },
+ {
+ key: "vendors",
+ label: t("bodyshop.labels.vendor_management"),
+ children: (
+
+
+
+ )
+ }
+ ];
+
+ return history({ search: `?tab=${key}` })} items={items} />;
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(PartsSettingsPage);
diff --git a/client/src/pages/simplified-parts/simplified-parts.page.component.jsx b/client/src/pages/simplified-parts/simplified-parts.page.component.jsx
index a3e18b299..9f22bfbcb 100644
--- a/client/src/pages/simplified-parts/simplified-parts.page.component.jsx
+++ b/client/src/pages/simplified-parts/simplified-parts.page.component.jsx
@@ -22,6 +22,7 @@ const SimplifiedPartsJobsPage = lazy(() => import("../simplified-parts-jobs/simp
const SimplifiedPartsJobsDetailPage = lazy(
() => import("../simplified-parts-jobs-detail/simplified-parts-jobs-detail.container.jsx")
);
+const PartsSettingsPage = lazy(() => import("../parts-settings/parts-settings.page.component.jsx"));
const ShopPage = lazy(() => import("../shop/shop.page.component.jsx"));
const ShopVendorPageContainer = lazy(() => import("../shop-vendor/shop-vendor.page.container.jsx"));
const EmailOverlayContainer = lazy(() => import("../../components/email-overlay/email-overlay.container.jsx"));
@@ -183,6 +184,14 @@ export function SimplifiedPartsPage({ conflict, bodyshop, alerts, setAlerts }) {
}
/>
+ }>
+
+
+ }
+ />
);