Merge remote-tracking branch 'origin/master-AIO' into feature/IO-2776-cdk-fortellis

# Conflicts:
#	client/src/components/dms-post-form/dms-post-form.component.jsx
#	package-lock.json
#	package.json
#	server/web-sockets/redisSocketEvents.js
This commit is contained in:
Dave
2025-08-21 11:05:03 -04:00
345 changed files with 23812 additions and 16731 deletions

View File

@@ -2,7 +2,7 @@ import { EditFilled, SyncOutlined } from "@ant-design/icons";
import { Button, Card, Checkbox, Input, Space, Table, Typography } from "antd";
import axios from "axios";
import queryString from "query-string";
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link, useLocation, useNavigate } from "react-router-dom";
@@ -13,8 +13,9 @@ import { setModalContext } from "../../redux/modals/modals.actions";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter";
import { TemplateList } from "../../utils/TemplateConstants";
import { alphaSort, dateSort } from "../../utils/sorters";
import { pageLimit } from "../../utils/config";
import { alphaSort, dateSort } from "../../utils/sorters";
import useLocalStorage from "../../utils/useLocalStorage";
const mapDispatchToProps = (dispatch) => ({
setPartsOrderContext: (context) => dispatch(setModalContext({ context: context, modal: "partsOrder" })),
@@ -27,7 +28,7 @@ export function BillsListPage({ loading, data, refetch, total, setPartsOrderCont
const [searchLoading, setSearchLoading] = useState(false);
const { page } = search;
const history = useNavigate();
const [state, setState] = useState({
const [state, setState] = useLocalStorage("bills_list_sort", {
sortedInfo: {},
filteredInfo: { text: "" }
});

View File

@@ -1,7 +1,7 @@
//import {useMutation, useQuery } from "@apollo/client";
import { Button, Form, Layout, Result, Typography } from "antd";
import axios from "axios";
import React, { useCallback, useEffect, useState } from "react";
import { useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useParams } from "react-router-dom";
@@ -16,7 +16,8 @@ import InstanceRenderManager from "../../utils/instanceRenderMgr";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser
});
const mapDispatchToProps = (dispatch) => ({});
const mapDispatchToProps = () => ({});
export default connect(mapStateToProps, mapDispatchToProps)(CsiContainerPage);
@@ -28,7 +29,6 @@ export function CsiContainerPage({ currentUser }) {
loading: false,
submitted: false
});
const { t } = useTranslation();
const getAxiosData = useCallback(async () => {
@@ -39,7 +39,6 @@ export function CsiContainerPage({ currentUser }) {
console.log("Unable to attach to crisp instance. ");
}
setSubmitting((prevSubmitting) => ({ ...prevSubmitting, loading: true }));
const response = await axios.post("/csi/lookup", {
surveyId
});
@@ -91,7 +90,7 @@ export function CsiContainerPage({ currentUser }) {
setSubmitting({ ...submitting, loading: true, submitting: true });
const result = await axios.post("/csi/submit", { surveyId, values });
console.log("result", result);
if (!!!result.errors && result.data.update_csi.affected_rows > 0) {
if (!result.errors && result.data.update_csi.affected_rows > 0) {
setSubmitting({ ...submitting, loading: false, submitted: true });
}
} catch (error) {
@@ -110,7 +109,7 @@ export function CsiContainerPage({ currentUser }) {
<Layout style={{ display: "flex", flexDirection: "column" }}>
<Layout.Content
style={{
backgroundColor: "#fff",
backgroundColor: "var(--content-bg)",
margin: "2em 4em",
padding: "2em",
overflowY: "auto",
@@ -139,7 +138,6 @@ export function CsiContainerPage({ currentUser }) {
relateddata: { bodyshop, job },
csiquestion: { config: csiquestions }
} = axiosResponse.csi_by_pk;
return (
<Layout style={{ display: "flex", flexDirection: "column" }}>
<div
@@ -184,13 +182,11 @@ export function CsiContainerPage({ currentUser }) {
})}
</Typography.Paragraph>
</div>
{submitting.error ? <AlertComponent message={submitting.error} type="error" /> : null}
{submitting.submitted ? (
<Layout.Content
style={{
backgroundColor: "#fff",
backgroundColor: "var(--content-bg)",
margin: "2em 4em",
padding: "2em",
overflowY: "auto"
@@ -201,7 +197,7 @@ export function CsiContainerPage({ currentUser }) {
) : (
<Layout.Content
style={{
backgroundColor: "#fff",
backgroundColor: "var(--content-bg)",
margin: "2em 4em",
padding: "2em",
overflowY: "auto"

View File

@@ -0,0 +1,42 @@
import axios from "axios";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key))
});
export function FeedbackPage({ setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation();
useEffect(() => {
document.title = t("titles.feature-request", {
app: InstanceRenderManager({
imex: "$t(titles.imexonline)",
rome: "$t(titles.romeonline)"
})
});
setBreadcrumbs([{ link: "/manage/feature-request", label: t("titles.bc.feature-request") }]);
}, [t, setBreadcrumbs, setSelectedHeader]);
useEffect(() => {
async function RenderCanny() {
const ssoToken = await axios.post("/sso/canny");
window.Canny("render", {
boardToken: "bba97b06-70db-0334-dee7-8108d73ef614",
basePath: `/manage/feature-request`, // See step 2
ssoToken: ssoToken.data, // See step 3,
theme: "light" // options: light [default], dark, auto
});
}
RenderCanny();
}, []);
return <div data-canny />;
}
export default connect(null, mapDispatchToProps)(FeedbackPage);

View File

@@ -271,7 +271,7 @@ export function JobsCloseComponent({ job, bodyshop, jobRO, insertAuditTrail, set
{
required: true
},
({ getFieldValue }) => ({
() => ({
validator(_, value) {
if (!bodyshop.cdk_dealerid) return Promise.resolve();
if (!value || dayjs(value).isSameOrAfter(dayjs(), "day")) {
@@ -280,7 +280,7 @@ export function JobsCloseComponent({ job, bodyshop, jobRO, insertAuditTrail, set
return Promise.reject(new Error(t("jobs.labels.dms.invoicedatefuture")));
}
}),
({ getFieldValue }) => ({
() => ({
validator(_, value) {
if (ClosingPeriod.treatment === "on" && bodyshop.accountingconfig.ClosingPeriod) {
if (
@@ -369,8 +369,8 @@ export function JobsCloseComponent({ job, bodyshop, jobRO, insertAuditTrail, set
<Form.List
name={["qb_multiple_payers"]}
rules={[
({ getFieldValue }) => ({
validator(_, value) {
() => ({
validator() {
let totalAllocated = Dinero();
const payers = form.getFieldValue("qb_multiple_payers");
@@ -492,7 +492,7 @@ export function JobsCloseComponent({ job, bodyshop, jobRO, insertAuditTrail, set
<Statistic
title={t("jobs.labels.pimraryamountpayable")}
valueStyle={{
color: discrep.getAmount() > 0 ? "green" : "red"
color: discrep.getAmount() >= 0 ? "green" : "red"
}}
value={discrep.toFormat()}
/>

View File

@@ -114,7 +114,6 @@ function JobsCreateContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
if (!!!job.ownerid) {
ownerData = job.owner.data;
ownerData.shopid = bodyshop.id;
delete ownerData.allow_text_message;
delete ownerData.preferred_contact;
delete job.ownerid;
} else {

View File

@@ -14,7 +14,7 @@ import { Badge, Button, Divider, Form, Space, Tabs } from "antd";
import Axios from "axios";
import _ from "lodash";
import queryString from "query-string";
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { FaHardHat, FaRegStickyNote, FaShieldAlt, FaTasks } from "react-icons/fa";
import { connect } from "react-redux";
@@ -28,6 +28,7 @@ import JobLineUpsertModalContainer from "../../components/job-lines-upsert-modal
import JobProfileDataWarning from "../../components/job-profile-data-warning/job-profile-data-warning.component";
import JobReconciliationModal from "../../components/job-reconciliation-modal/job-reconciliation.modal.container";
import JobSyncButton from "../../components/job-sync-button/job-sync-button.component";
import JobWatcherToggleContainer from "../../components/job-watcher-toggle/job-watcher-toggle.container.jsx";
import JobsChangeStatus from "../../components/jobs-change-status/jobs-change-status.component";
import JobsConvertButton from "../../components/jobs-convert-button/jobs-convert-button.component";
import JobsDetailDatesComponent from "../../components/jobs-detail-dates/jobs-detail-dates.component";
@@ -45,6 +46,8 @@ import LockWrapperComponent from "../../components/lock-wrapper/lock-wrapper.com
import NoteUpsertModalComponent from "../../components/note-upsert-modal/note-upsert-modal.container";
import ScheduleJobModalContainer from "../../components/schedule-job-modal/schedule-job-modal.container";
import TaskListContainer from "../../components/task-list/task-list.container.jsx";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
import { QUERY_PARTS_BILLS_BY_JOBID } from "../../graphql/bills.queries.js";
import { QUERY_JOB_TASKS_PAGINATED } from "../../graphql/tasks.queries.js";
import { insertAuditTrail } from "../../redux/application/application.actions";
@@ -55,9 +58,6 @@ import AuditTrailMapping from "../../utils/AuditTrailMappings";
import { DateTimeFormat } from "../../utils/DateFormatter";
import dayjs from "../../utils/day";
import UndefinedToNull from "../../utils/undefinedtonull";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
import JobWatcherToggleContainer from "../../components/job-watcher-toggle/job-watcher-toggle.container.jsx";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -322,11 +322,11 @@ export function JobsDetailPage({
>
<PageHeader
// onBack={() => window.history.back()}
id="job-detail-header"
title={
<Space>
{scenarioNotificationsOn && <JobWatcherToggleContainer job={job} />}
{job.ro_number || t("general.labels.na")}
<span id="job-ro_number">{job.ro_number || t("general.labels.na")}</span>
</Space>
}
extra={menuExtra}

View File

@@ -1,4 +1,5 @@
import { FloatButton, Layout, Spin } from "antd";
import { Button, FloatButton, Layout, Space, Spin } from "antd";
import { AlertOutlined, BulbOutlined } from "@ant-design/icons";
// import preval from "preval.macro";
import React, { lazy, Suspense, useEffect, useState } from "react";
@@ -19,7 +20,6 @@ import LoadingSpinner from "../../components/loading-spinner/loading-spinner.com
import PartnerPingComponent from "../../components/partner-ping/partner-ping.component";
import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container";
import ShopSubStatusComponent from "../../components/shop-sub-status/shop-sub-status.component";
import { requestForToken } from "../../firebase/firebase.utils";
import { selectBodyshop, selectInstanceConflict } from "../../redux/user/user.selectors";
import UpdateAlert from "../../components/update-alert/update-alert.component";
import InstanceRenderManager from "../../utils/instanceRenderMgr.js";
@@ -56,6 +56,7 @@ const ContractCreatePage = lazy(() => import("../contract-create/contract-create
const ContractDetailPage = lazy(() => import("../contract-detail/contract-detail.page.container"));
const ContractsList = lazy(() => import("../contracts/contracts.page.container"));
const BillsListPage = lazy(() => import("../bills/bills.page.container"));
const FeatureRequestPage = lazy(() => import("../feature-request/feature-request.page.jsx"));
const JobCostingModal = lazy(() => import("../../components/job-costing-modal/job-costing-modal.container"));
const ReportCenterModal = lazy(() => import("../../components/report-center-modal/report-center-modal.container"));
@@ -180,15 +181,12 @@ export function Manage({ conflict, bodyshop, alerts, setAlerts }) {
});
}
}, [alerts, displayedAlertIds, notification]);
useEffect(() => {
const widgetId = InstanceRenderManager({
imex: "IABVNO4scRKY11XBQkNr",
rome: "mQdqARMzkZRUVugJ6TdS"
});
window.noticeable.render("widget", widgetId);
requestForToken().catch((error) => {
console.error(`Unable to request for token.`, error);
window.Canny("initChangelog", {
appID: "680bd2c7ee501290377f6686",
position: "top",
align: "left",
theme: "light" // options: light [default], dark, auto
});
}, []);
@@ -480,6 +478,8 @@ export function Manage({ conflict, bodyshop, alerts, setAlerts }) {
// element={<ShopTemplates />}
// />
}
<Route path="/feature-request/*" index element={<FeatureRequestPage />} />
<Route
path="/shop/vendors"
element={
@@ -669,7 +669,12 @@ export function Manage({ conflict, bodyshop, alerts, setAlerts }) {
margin: "1rem 0rem"
}}
>
<div style={{ display: "flex" }}>
<Link to="/manage/feature-request">
<Button icon={<BulbOutlined />} type="text">
{t("general.labels.feature-request")}
</Button>
</Link>
<Space>
<WssStatusDisplayComponent />
<div onClick={broadcastMessage}>
{`${InstanceRenderManager({
@@ -677,8 +682,10 @@ export function Manage({ conflict, bodyshop, alerts, setAlerts }) {
rome: t("titles.romeonline")
})} - ${import.meta.env.VITE_APP_GIT_SHA_DATE}`}
</div>
<div id="noticeable-widget" style={{ marginLeft: "1rem" }} />
</div>
<Button icon={<AlertOutlined />} data-canny-changelog type="text">
{t("general.labels.changelog")}
</Button>
</Space>
<Link to="/disclaimer" target="_blank" style={{ color: "#ccc" }}>
Disclaimer & Notices
</Link>

View File

@@ -1,4 +1,3 @@
import React from "react";
import ScheduleCalendarContainer from "../../components/schedule-calendar/schedule-calendar.container";
export default function SchedulePageComponent() {

View File

@@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";

View File

@@ -1,7 +1,7 @@
import Icon, { FieldTimeOutlined } from "@ant-design/icons";
import { Card, Tabs } from "antd";
import queryString from "query-string";
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { FaShieldAlt } from "react-icons/fa";
import { connect } from "react-redux";
@@ -78,7 +78,7 @@ export function ScoreboardContainer({ setBreadcrumbs, setSelectedHeader }) {
<RbacWrapper action="scoreboard:view">
<Tabs
activeKey={tab || "sb"}
destroyInactiveTabPane
destroyOnHidden
onChange={(key) => {
searchParams.tab = key;
history({

View File

@@ -10,10 +10,10 @@ import ShopCsiConfig from "../../components/shop-csi-config/shop-csi-config.comp
import ShopEmployeesContainer from "../../components/shop-employees/shop-employees.container";
import ShopInfoContainer from "../../components/shop-info/shop-info.container";
import ShopInfoUsersComponent from "../../components/shop-users/shop-users.component";
import ShopInfoConsentComponent from "../../components/shop-info/shop-info.consent.component";
import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import { HasFeatureAccess } from "../../components/feature-wrapper/feature-wrapper.component";
import ShopTeamsContainer from "../../components/shop-teams/shop-teams.container";
@@ -91,6 +91,16 @@ export function ShopPage({ bodyshop, setSelectedHeader, setBreadcrumbs }) {
children: <ShopCsiConfig />
});
}
if (bodyshop.messagingservicesid) {
// Add Consent Settings tab
items.push({
key: "consent",
label: t("bodyshop.labels.consent_settings"),
children: <ShopInfoConsentComponent bodyshop={bodyshop} />
});
}
return (
<RbacWrapper action="shop:config">
<Tabs activeKey={search.tab} onChange={(key) => history({ search: `?tab=${key}` })} items={items} />

View File

@@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import TasksPageComponent from "./tasks.page.component";
import queryString from "query-string";

View File

@@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import TasksPageComponent from "./tasks.page.component";

View File

@@ -1,4 +1,3 @@
import React from "react";
import TaskListContainer from "../../components/task-list/task-list.container.jsx";
import { QUERY_ALL_TASKS_PAGINATED, QUERY_MY_TASKS_PAGINATED } from "../../graphql/tasks.queries.js";
import taskPageTypes from "./taskPageTypes.jsx";
@@ -10,7 +9,7 @@ const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
bodyshop: selectBodyshop
});
const mapDispatchToProps = (dispatch) => ({});
const mapDispatchToProps = () => ({});
export default connect(mapStateToProps, mapDispatchToProps)(TasksPageComponent);

View File

@@ -1,7 +1,7 @@
.tech-content-container {
overflow-y: visible;
padding: 1rem;
background: #fff;
background: var(--tech-content-bg);
}
.tech-layout-container {