Merge remote-tracking branch 'origin/release/2025-08-15' into feature/IO-3255-simplified-part-management, resolve conflicts

This commit is contained in:
Dave Richer
2025-08-08 12:24:13 -04:00
48 changed files with 666 additions and 404 deletions

View File

@@ -2,15 +2,19 @@ import { ApolloProvider } from "@apollo/client";
import { SplitFactoryProvider, useSplitClient } from "@splitsoftware/splitio-react"; import { SplitFactoryProvider, useSplitClient } from "@splitsoftware/splitio-react";
import { ConfigProvider } from "antd"; import { ConfigProvider } from "antd";
import enLocale from "antd/es/locale/en_US"; import enLocale from "antd/es/locale/en_US";
import { useEffect } from "react"; import { useEffect, useMemo } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useSelector } from "react-redux"; import { connect, useSelector } from "react-redux";
import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component"; import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
import client from "../utils/GraphQLClient"; import client from "../utils/GraphQLClient";
import App from "./App"; import App from "./App";
import * as Sentry from "@sentry/react"; import * as Sentry from "@sentry/react";
import themeProvider from "./themeProvider"; import getTheme from "./themeProvider";
import { CookiesProvider } from "react-cookie"; import { CookiesProvider } from "react-cookie";
import { createStructuredSelector } from "reselect";
import { selectCurrentUser } from "../redux/user/user.selectors.js";
import { selectDarkMode } from "../redux/application/application.selectors";
import { setDarkMode } from "../redux/application/application.actions";
// Base Split configuration // Base Split configuration
const config = { const config = {
@@ -24,19 +28,54 @@ const config = {
function SplitClientProvider({ children }) { function SplitClientProvider({ children }) {
const imexshopid = useSelector((state) => state.user.imexshopid); // Access imexshopid from Redux store const imexshopid = useSelector((state) => state.user.imexshopid); // Access imexshopid from Redux store
const splitClient = useSplitClient({ key: imexshopid || "anon" }); // Use imexshopid or fallback to "anon" const splitClient = useSplitClient({ key: imexshopid || "anon" }); // Use imexshopid or fallback to "anon"
useEffect(() => { useEffect(() => {
if (splitClient && imexshopid) { if (splitClient && imexshopid) {
// Log readiness for debugging; no need for ready() since isReady is available
console.log(`Split client initialized with key: ${imexshopid}, isReady: ${splitClient.isReady}`); console.log(`Split client initialized with key: ${imexshopid}, isReady: ${splitClient.isReady}`);
} }
}, [splitClient, imexshopid]); }, [splitClient, imexshopid]);
return children; return children;
} }
function AppContainer() { const mapDispatchToProps = (dispatch) => ({
setDarkMode: (isDarkMode) => dispatch(setDarkMode(isDarkMode))
});
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser
});
function AppContainer({ currentUser, setDarkMode }) {
const { t } = useTranslation(); const { t } = useTranslation();
const isDarkMode = useSelector(selectDarkMode);
const theme = useMemo(() => getTheme(isDarkMode), [isDarkMode]);
// Update data-theme attribute when dark mode changes
useEffect(() => {
document.documentElement.setAttribute("data-theme", isDarkMode ? "dark" : "light");
return () => document.documentElement.removeAttribute("data-theme");
}, [isDarkMode]);
// Sync Redux darkMode with localStorage on user change
useEffect(() => {
if (currentUser?.uid) {
const savedMode = localStorage.getItem(`dark-mode-${currentUser.uid}`);
if (savedMode !== null) {
setDarkMode(JSON.parse(savedMode));
} else {
setDarkMode(false); // default to light mode
}
} else {
setDarkMode(false);
}
// eslint-disable-next-line
}, [currentUser?.uid]);
// Persist darkMode to localStorage when it or user changes
useEffect(() => {
if (currentUser?.uid) {
localStorage.setItem(`dark-mode-${currentUser.uid}`, JSON.stringify(isDarkMode));
}
}, [isDarkMode, currentUser?.uid]);
return ( return (
<CookiesProvider> <CookiesProvider>
@@ -44,11 +83,10 @@ function AppContainer() {
<ConfigProvider <ConfigProvider
input={{ autoComplete: "new-password" }} input={{ autoComplete: "new-password" }}
locale={enLocale} locale={enLocale}
theme={themeProvider} theme={theme}
form={{ form={{
validateMessages: { validateMessages: {
// eslint-disable-next-line no-template-curly-in-string required: t("general.validation.required", { label: "{{label}}" })
required: t("general.validation.required", { label: "${label}" })
} }
}} }}
> >
@@ -64,4 +102,4 @@ function AppContainer() {
); );
} }
export default Sentry.withProfiler(AppContainer); export default Sentry.withProfiler(connect(mapStateToProps, mapDispatchToProps)(AppContainer));

View File

@@ -1,15 +1,225 @@
//Global Styles. :root {
--table-stripe-bg: #f4f4f4; /* Light mode table stripe */
--menu-divider-color: #74695c; /* Light mode menu divider */
--menu-submenu-text: rgba(255, 255, 255, 0.65); /* Light mode submenu text */
--kanban-column-bg: #ddd; /* Light mode kanban column */
--alert-color: blue; /* Light mode alert */
--completion-soon-color: rgba(255, 140, 0, 0.8); /* Light mode completion soon */
--completion-past-color: rgba(255, 0, 0, 0.8); /* Light mode completion past */
--job-line-manual-color: tomato; /* Light mode job line manual */
--muted-button-color: lightgray; /* Light mode muted button */
--muted-button-hover-color: darkgrey; /* Light mode muted button hover */
--table-border-color: #ddd; /* Light mode table border */
--table-hover-bg: #f5f5f5; /* Light mode table hover */
--popover-bg: #fff; /* Light mode popover background */
--error-text: red; /* Light mode error message */
--no-jobs-text: #888; /* Light mode no jobs message */
--message-yours-bg: #eee; /* Light mode yours message background */
--message-mine-bg-start: #00d0ea; /* Light mode mine message gradient start */
--message-mine-bg-end: #0085d1; /* Light mode mine message gradient end */
--message-mine-text: white; /* Light mode mine message text */
--message-mine-tail-bg: white; /* Light mode mine/yours message tail */
--system-message-bg: #f5f5f5; /* Light mode system message background */
--system-message-text: #555; /* Light mode system message text */
--system-label-text: #888; /* Light mode system label/date text */
--message-icon-color: whitesmoke; /* Light mode message icon */
--eula-card-bg: lightgray; /* Light mode eula card background */
--notification-bg: #fff; /* Light mode notification background */
--notification-text: rgba(0, 0, 0, 0.85); /* Light mode notification text */
--notification-border: #d9d9d9; /* Light mode notification border */
--notification-header-bg: #fafafa; /* Light mode notification header background */
--notification-header-border: #f0f0f0; /* Light mode notification header border */
--notification-header-text: rgba(0, 0, 0, 0.85); /* Light mode notification header text */
--notification-toggle-icon: #1677ff; /* Light mode notification toggle icon */
--notification-switch-bg: #1677ff; /* Light mode notification switch background */
--notification-btn-link: #1677ff; /* Light mode notification link button */
--notification-btn-link-hover: #69b1ff; /* Light mode notification link button hover */
--notification-btn-link-disabled: rgba(0, 0, 0, 0.25); /* Light mode notification link button disabled */
--notification-btn-link-active: #0958d9; /* Light mode notification link button active */
--notification-read-bg: #fff; /* Light mode notification read background */
--notification-read-text: rgba(0, 0, 0, 0.65); /* Light mode notification read text */
--notification-unread-bg: #f5f5f5; /* Light mode notification unread background */
--notification-unread-text: rgba(0, 0, 0, 0.85); /* Light mode notification unread text */
--notification-item-hover-bg: #fafafa; /* Light mode notification item hover background */
--notification-ro-number: #1677ff; /* Light mode notification RO number */
--notification-relative-time: rgba(0, 0, 0, 0.45); /* Light mode notification relative time */
--alert-bg: #fff1f0; /* Light mode alert background */
--alert-text: rgba(0, 0, 0, 0.85); /* Light mode alert text */
--alert-border: #ffa39e; /* Light mode alert border */
--alert-message: #ff4d4f; /* Light mode alert message */
--share-badge-bg: #cccccc; /* Light mode share badge background */
--column-header-bg: #d0d0d0; /* Light mode column header background */
--footer-bg: #d0d0d0; /* Light mode footer background */
--tech-icon-color: orangered; /* Light mode tech icon color */
--clone-border-color: #1890ff; /* Light mode clone border color */
--event-arrived-bg: rgba(4, 141, 4, 0.4); /* Light mode arrived event background */
--event-block-bg: rgba(212, 2, 2, 0.6); /* Light mode block event background */
--event-selected-bg: slategrey; /* Light mode selected event background */
--task-bg: #fff; /* Light mode task center background */
--task-text: rgba(0, 0, 0, 0.85); /* Light mode task text */
--task-border: #d9d9d9; /* Light mode task border */
--task-header-bg: #fafafa; /* Light mode task header background */
--task-header-border: #f0f0f0; /* Light mode task header border */
--task-section-bg: #f5f5f5; /* Light mode task section background */
--task-section-border: #e8e8e8; /* Light mode task section border */
--task-row-hover-bg: #f5f5f5; /* Light mode task row hover background */
--task-row-border: #f0f0f0; /* Light mode task row border */
--task-ro-number: #1677ff; /* Light mode task RO number */
--task-due-text: rgba(0, 0, 0, 0.45); /* Light mode task due text */
--task-button-bg: #1677ff; /* Light mode task button background */
--task-button-hover-bg: #4096ff; /* Light mode task button hover background */
--task-button-disabled-bg: #d9d9d9; /* Light mode task button disabled background */
--task-button-text: white; /* Light mode task button text */
--task-message-text: rgba(0, 0, 0, 0.45); /* Light mode task message text */
--mask-bg: rgba(0, 0, 0, 0.05); /* Light mode mask background */
--board-text-color: #393939; /* Light mode board text color */
--section-bg: #e3e3e3; /* Light mode section background */
--detail-text-color: #4d4d4d; /* Light mode detail text color */
--card-selected-bg: rgba(128, 128, 128, 0.2); /* Light mode selected card background */
--card-stripe-even-bg: #f0f2f5; /* Light mode even card background */
--card-stripe-odd-bg: #ffffff; /* Light mode odd card background */
--bar-border-color: #f0f2f5; /* Light mode bar border and background */
--tag-wrapper-bg: #f0f2f5; /* Light mode tag wrapper background */
--tag-wrapper-text: #000; /* Light mode tag wrapper text */
--preview-bg: lightgray; /* Light mode preview background */
--preview-border-color: #2196F3; /* Light mode preview border color */
--event-bg-fallback: #ffffff; /* Light mode event background fallback */
--card-bg-fallback: #ffffff; /* Light mode card background fallback */
--card-text-fallback: black; /* Light mode card text fallback */
--table-row-even-bg: rgb(236, 236, 236); /* Light mode table row even background */
--status-row-bg-fallback: #ffffff; /* Light mode status row fallback background */
--reset-link-color: #0000ff; /* Light mode reset link color */
--error-header-text: tomato; /* Light mode error header text */
--tooltip-bg: white; /* Light mode tooltip background */
--tooltip-border: gray; /* Light mode tooltip border */
--tooltip-text-fallback: black; /* Light mode tooltip text fallback */
--teams-button-bg: #6264A7; /* Light mode Teams button background */
--teams-button-border: #6264A7; /* Light mode Teams button border */
--teams-button-text: #FFFFFF; /* Light mode Teams button text and icon */
--content-bg: #fff; /* Light mode content background */
--legend-bg-fallback: #ffffff; /* Light mode legend background fallback */
--tech-content-bg: #fff; /* Light mode tech content background */
--today-bg: #ffffff; /* Light mode today background */
--today-text: #000000; /* Light mode today text */
--off-range-bg: #f8f8f8; /* Light mode off-range background */
}
[data-theme="dark"] {
--table-stripe-bg: #2a2a2a; /* Dark mode table stripe */
--menu-divider-color: #5c5c5c; /* Dark mode menu divider */
--menu-submenu-text: rgba(255, 255, 255, 0.85); /* Dark mode submenu text */
--kanban-column-bg: #333333; /* Dark mode kanban column */
--alert-color: #4da8ff; /* Dark mode alert */
--completion-soon-color: #ff8c1a; /* Dark mode completion soon */
--completion-past-color: #ff4d4f; /* Dark mode completion past */
--job-line-manual-color: #ff6347; /* Dark mode job line manual */
--muted-button-color: #666666; /* Dark mode muted button */
--muted-button-hover-color: #999999; /* Dark mode muted button hover */
--table-border-color: #5c5c5c; /* Dark mode table border */
--table-hover-bg: #2a2a2a; /* Dark mode table hover */
--popover-bg: #2a2a2a; /* Dark mode popover background */
--error-text: #ff4d4f; /* Dark mode error message */
--no-jobs-text: #999999; /* Dark mode no jobs message */
--message-yours-bg: #2a2a2a; /* Dark mode yours message background */
--message-mine-bg-start: #4da8ff; /* Dark mode mine message gradient start */
--message-mine-bg-end: #326ade; /* Dark mode mine message gradient end */
--message-mine-text: #ffffff; /* Dark mode mine message text */
--message-mine-tail-bg: #1f1f1f; /* Dark mode mine/yours message tail */
--system-message-bg: #333333; /* Dark mode system message background */
--system-message-text: #cccccc; /* Dark mode system message text */
--system-label-text: #999999; /* Dark mode system label/date text */
--message-icon-color: #cccccc; /* Dark mode message icon */
--eula-card-bg: #2a2a2a; /* Dark mode eula card background */
--notification-bg: #2a2a2a; /* Dark mode notification background */
--notification-text: rgba(255, 255, 255, 0.85); /* Dark mode notification text */
--notification-border: #5c5c5c; /* Dark mode notification border */
--notification-header-bg: #333333; /* Dark mode notification header background */
--notification-header-border: #444444; /* Dark mode notification header border */
--notification-header-text: rgba(255, 255, 255, 0.85); /* Dark mode notification header text */
--notification-toggle-icon: #4da8ff; /* Dark mode notification toggle icon */
--notification-switch-bg: #4da8ff; /* Dark mode notification switch background */
--notification-btn-link: #4da8ff; /* Dark mode notification link button */
--notification-btn-link-hover: #80c1ff; /* Dark mode notification link button hover */
--notification-btn-link-disabled: rgba(255, 255, 255, 0.25); /* Dark mode notification link button disabled */
--notification-btn-link-active: #2681ff; /* Dark mode notification link button active */
--notification-read-bg: #2a2a2a; /* Dark mode notification read background */
--notification-read-text: rgba(255, 255, 255, 0.65); /* Dark mode notification read text */
--notification-unread-bg: #333333; /* Dark mode notification unread background */
--notification-unread-text: rgba(255, 255, 255, 0.85); /* Dark mode notification unread text */
--notification-item-hover-bg: #3a3a3a; /* Dark mode notification item hover background */
--notification-ro-number: #4da8ff; /* Dark mode notification RO number */
--notification-relative-time: rgba(255, 255, 255, 0.45); /* Dark mode notification relative time */
--alert-bg: #3a1a1a; /* Dark mode alert background */
--alert-text: rgba(255, 255, 255, 0.85); /* Dark mode alert text */
--alert-border: #ff6666; /* Dark mode alert border */
--alert-message: #ff6666; /* Dark mode alert message */
--share-badge-bg: #666666; /* Dark mode share badge background */
--column-header-bg: #333333; /* Dark mode column header background */
--footer-bg: #333333; /* Dark mode footer background */
--tech-icon-color: #ff4500; /* Dark mode tech icon color */
--clone-border-color: #4da8ff; /* Dark mode clone border color */
--event-arrived-bg: rgba(4, 141, 4, 0.6); /* Dark mode arrived event background */
--event-block-bg: rgba(212, 2, 2, 0.8); /* Dark mode block event background */
--event-selected-bg: #4a5e6e; /* Dark mode selected event background */
--task-bg: #2a2a2a; /* Dark mode task center background */
--task-text: rgba(255, 255, 255, 0.85); /* Dark mode task text */
--task-border: #5c5c5c; /* Dark mode task border */
--task-header-bg: #333333; /* Dark mode task header background */
--task-header-border: #444444; /* Dark mode task header border */
--task-section-bg: #333333; /* Dark mode task section background */
--task-section-border: #444444; /* Dark mode task section border */
--task-row-hover-bg: #3a3a3a; /* Dark mode task row hover background */
--task-row-border: #444444; /* Dark mode task row border */
--task-ro-number: #4da8ff; /* Dark mode task RO number */
--task-due-text: rgba(255, 255, 255, 0.45); /* Dark mode task due text */
--task-button-bg: #4da8ff; /* Dark mode task button background */
--task-button-hover-bg: #80c1ff; /* Dark mode task button hover background */
--task-button-disabled-bg: #666666; /* Dark mode task button disabled background */
--task-button-text: #ffffff; /* Dark mode task button text */
--task-message-text: rgba(255, 255, 255, 0.45); /* Dark mode task message text */
--mask-bg: rgba(255, 255, 255, 0.05); /* Dark mode mask background */
--board-text-color: #cccccc; /* Dark mode board text color */
--section-bg: #333333; /* Dark mode section background */
--detail-text-color: #bbbbbb; /* Dark mode detail text color */
--card-selected-bg: rgba(255, 255, 255, 0.1); /* Dark mode selected card background */
--card-stripe-even-bg: #2a2a2a; /* Dark mode even card background */
--card-stripe-odd-bg: #1f1f1f; /* Dark mode odd card background */
--bar-border-color: #2a2a2a; /* Dark mode bar border and background */
--tag-wrapper-bg: #2a2a2a; /* Dark mode tag wrapper background */
--tag-wrapper-text: #cccccc; /* Dark mode tag wrapper text */
--preview-bg: #2a2a2a; /* Dark mode preview background */
--preview-border-color: #4da8ff; /* Dark mode preview border color */
--event-bg-fallback: #2a2a2a; /* Dark mode event background fallback */
--card-bg-fallback: #2a2a2a; /* Dark mode card background fallback */
--card-text-fallback: #cccccc; /* Dark mode card text fallback */
--table-row-even-bg: #2a2a2a; /* Dark mode table row even background */
--status-row-bg-fallback: #1f1f1f; /* Dark mode status row fallback background */
--reset-link-color: #4da8ff; /* Dark mode reset link color */
--error-header-text: #ff6347; /* Dark mode error header text */
--tooltip-bg: #2a2a2a; /* Dark mode tooltip background */
--tooltip-border: #5c5c5c; /* Dark mode tooltip border */
--tooltip-text-fallback: #cccccc; /* Dark mode tooltip text fallback */
--teams-button-bg: #7b7dc4; /* Dark mode Teams button background */
--teams-button-border: #7b7dc4; /* Dark mode Teams button border */
--teams-button-text: #ffffff; /* Dark mode Teams button text and icon */
--content-bg: #2a2a2a; /* Dark mode content background */
--legend-bg-fallback: #2a2a2a; /* Dark mode legend background fallback */
--tech-content-bg: #2a2a2a; /* Dark mode tech content background */
--today-bg: #4a5e6e; /* Dark mode today background */
--today-text: #ffffff; /* Dark mode today text */
--off-range-bg: #333333; /* Dark mode off-range background */
}
// Global Styles
@import "react-big-calendar/lib/sass/styles"; @import "react-big-calendar/lib/sass/styles";
.ant-menu-item-divider { .ant-menu-item-divider {
border-bottom: 1px solid #74695c !important; border-bottom: 1px solid var(--menu-divider-color) !important;
} }
// TODO: This was added because the newest release of ant was making the text color and the background color the same on a selected header // Note: Monitor this in dark mode to ensure text visibility
// Tried all available tokens (https://ant.design/components/menu?locale=en-US) and even reverted all our custom styles, to no avail
// This should be kept an eye on, especially if implementing DARK MODE
.ant-menu-submenu-title { .ant-menu-submenu-title {
color: rgba(255, 255, 255, 0.65) !important; color: var(--menu-submenu-text) !important;
} }
.imex-table-header { .imex-table-header {
@@ -46,7 +256,7 @@
} }
.ellipses { .ellipses {
display: inline-block; /* for em, a, span, etc (inline by default) */ display: inline-block;
text-overflow: ellipsis; text-overflow: ellipsis;
width: calc(95%); width: calc(95%);
overflow: hidden; overflow: hidden;
@@ -60,23 +270,24 @@
} }
} }
// ::-webkit-scrollbar-track { // Scrollbar styles (uncomment if needed, updated for dark mode)
// -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); ::-webkit-scrollbar-track {
// border-radius: 0.2rem; -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
// background-color: #f5f5f5; border-radius: 0.2rem;
// } background-color: var(--table-stripe-bg);
}
// ::-webkit-scrollbar { ::-webkit-scrollbar {
// width: 0.25rem; width: 0.25rem;
// max-height: 0.25rem; max-height: 0.25rem;
// background-color: #f5f5f5; background-color: var(--table-stripe-bg);
// } }
// ::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {
// border-radius: 0.2rem; border-radius: 0.2rem;
// -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
// background-color: #188fff; background-color: var(--alert-color);
// } }
.ant-input-number-input, .ant-input-number-input,
.ant-input-number, .ant-input-number,
@@ -88,28 +299,27 @@
.production-alert { .production-alert {
animation: alertBlinker 1s linear infinite; animation: alertBlinker 1s linear infinite;
color: blue; color: var(--alert-color);
} }
@keyframes alertBlinker { @keyframes alertBlinker {
50% { 50% {
color: red; color: var(--completion-past-color);
opacity: 100; opacity: 100;
//opacity: 0;
} }
} }
.blue { .blue {
color: blue; color: var(--alert-color);
} }
.production-completion-soon { .production-completion-soon {
color: rgba(255, 140, 0, 0.8); color: var(--completion-soon-color);
font-weight: bold; font-weight: bold;
} }
.production-completion-past { .production-completion-past {
color: rgba(255, 0, 0, 0.8); color: var(--completion-past-color);
font-weight: bold; font-weight: bold;
} }
@@ -139,7 +349,7 @@
} }
.react-kanban-column { .react-kanban-column {
background-color: #ddd !important; background-color: var(--kanban-column-bg) !important;
} }
.production-list-table { .production-list-table {
@@ -151,18 +361,18 @@
.ReactGridGallery_tile-icon-bar { .ReactGridGallery_tile-icon-bar {
div { div {
svg { svg {
fill: #1890ff; fill: var(--alert-color);
} }
} }
} }
.job-line-manual { .job-line-manual {
color: tomato; color: var(--job-line-manual-color);
font-style: italic; font-style: italic;
} }
.ant-table-tbody > tr.ant-table-row:nth-child(2n) > td { .ant-table-tbody > tr.ant-table-row:nth-child(2n) > td {
background-color: #f4f4f4; background-color: var(--table-stripe-bg);
} }
.rowWithColor > td { .rowWithColor > td {
@@ -170,15 +380,15 @@
} }
.muted-button { .muted-button {
color: lightgray; color: var(--muted-button-color);
border: none; border: none;
background: none; background: none;
cursor: pointer; cursor: pointer;
font-size: 16px; /* Adjust as needed */ font-size: 16px;
} }
.muted-button:hover { .muted-button:hover {
color: darkgrey; color: var(--muted-button-hover-color);
} }
.notification-alert-unordered-list { .notification-alert-unordered-list {
@@ -194,3 +404,19 @@
.content-container { .content-container {
padding: 1rem; padding: 1rem;
} }
// Override react-big-calendar styles for dark mode only
[data-theme="dark"] {
.rbc-today {
background-color: var(--today-bg);
color: var(--today-text);
}
.rbc-off-range {
background-color: var(--off-range-bg);
}
.rbc-day-bg.rbc-today {
background-color: var(--today-bg);
}
}

View File

@@ -4,36 +4,42 @@ import InstanceRenderMgr from "../utils/instanceRenderMgr";
const { defaultAlgorithm, darkAlgorithm } = theme; const { defaultAlgorithm, darkAlgorithm } = theme;
let isDarkMode = false;
/** /**
* Default theme * Default theme
* @type {{components: {Menu: {itemDividerBorderColor: string}}}} * @type {{components: {Menu: {itemDividerBorderColor: string}}}}
*/ */
const defaultTheme = { const defaultTheme = (isDarkMode) => ({
components: { components: {
Table: { Table: {
rowHoverBg: "#e7f3ff", rowHoverBg: isDarkMode ? "#2a2a2a" : "#e7f3ff",
rowSelectedBg: "#e6f7ff", rowSelectedBg: isDarkMode ? "#333333" : "#e6f7ff",
headerSortHoverBg: "transparent" headerSortHoverBg: "transparent"
}, },
Menu: { Menu: {
darkItemHoverBg: "#1890ff", darkItemHoverBg: isDarkMode ? "#004a77" : "#1890ff",
itemHoverBg: "#1890ff", itemHoverBg: isDarkMode ? "#004a77" : "#1890ff",
horizontalItemHoverBg: "#1890ff" horizontalItemHoverBg: isDarkMode ? "#004a77" : "#1890ff"
} }
}, },
token: { token: {
colorPrimary: InstanceRenderMgr({ colorPrimary: InstanceRenderMgr(
imex: "#1890ff", {
rome: "#326ade" imex: isDarkMode ? "#4da8ff" : "#1890ff",
}), rome: isDarkMode ? "#5b8ce6" : "#326ade"
colorInfo: InstanceRenderMgr({ },
imex: "#1890ff", isDarkMode
rome: "#326ade" ),
}) colorInfo: InstanceRenderMgr(
{
imex: isDarkMode ? "#4da8ff" : "#1890ff",
rome: isDarkMode ? "#5b8ce6" : "#326ade"
},
isDarkMode
),
colorError: isDarkMode ? "#ff4d4f" : "#f5222d",
colorBgBase: isDarkMode ? "#1f1f1f" : "#ffffff" // Align with Ant Design dark mode
} }
}; });
/** /**
* Development theme * Development theme
@@ -60,8 +66,9 @@ const prodTheme = {};
const currentTheme = import.meta.env.DEV ? devTheme : prodTheme; const currentTheme = import.meta.env.DEV ? devTheme : prodTheme;
const finaltheme = { const getTheme = (isDarkMode) => ({
algorithm: isDarkMode ? darkAlgorithm : defaultAlgorithm, algorithm: isDarkMode ? darkAlgorithm : defaultAlgorithm,
...defaultsDeep(currentTheme, defaultTheme) ...defaultsDeep(currentTheme, defaultTheme)
}; });
export default finaltheme;
export default getTheme;

View File

@@ -6,7 +6,7 @@
td { td {
padding: 8px; padding: 8px;
text-align: left; text-align: left;
border-bottom: 1px solid #ddd; border-bottom: 1px solid var(--table-border-color);
.ant-form-item { .ant-form-item {
margin-bottom: 0px !important; margin-bottom: 0px !important;
@@ -14,6 +14,6 @@
} }
tr:hover { tr:hover {
background-color: #f5f5f5; background-color: var(--table-hover-bg);
} }
} }

View File

@@ -6,7 +6,7 @@
td { td {
padding: 8px; padding: 8px;
text-align: left; text-align: left;
border-bottom: 1px solid #ddd; border-bottom: 1px solid var(--table-border-color);
.ant-form-item { .ant-form-item {
margin-bottom: 0px !important; margin-bottom: 0px !important;
@@ -14,6 +14,6 @@
} }
tr:hover { tr:hover {
background-color: #f5f5f5; background-color: var(--table-hover-bg);
} }
} }

View File

@@ -29,9 +29,7 @@ const mapDispatchToProps = (dispatch) => ({
function ChatConversationListComponent({ conversationList, selectedConversation, setSelectedConversation, bodyshop }) { function ChatConversationListComponent({ conversationList, selectedConversation, setSelectedConversation, bodyshop }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [, forceUpdate] = useState(false); const [, forceUpdate] = useState(false);
const phoneNumbers = conversationList.map((item) => phone(item.phone_num, "CA").phoneNumber.replace(/^\+1/, "")); const phoneNumbers = conversationList.map((item) => phone(item.phone_num, "CA").phoneNumber.replace(/^\+1/, ""));
const { data: optOutData } = useQuery(GET_PHONE_NUMBER_OPT_OUTS, { const { data: optOutData } = useQuery(GET_PHONE_NUMBER_OPT_OUTS, {
variables: { variables: {
bodyshopid: bodyshop.id, bodyshopid: bodyshop.id,
@@ -64,15 +62,12 @@ function ChatConversationListComponent({ conversationList, selectedConversation,
const item = sortedConversationList[index]; const item = sortedConversationList[index];
const normalizedPhone = phone(item.phone_num, "CA").phoneNumber.replace(/^\+1/, ""); const normalizedPhone = phone(item.phone_num, "CA").phoneNumber.replace(/^\+1/, "");
const hasOptOutEntry = optOutMap.has(normalizedPhone); const hasOptOutEntry = optOutMap.has(normalizedPhone);
const cardContentRight = <TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>; const cardContentRight = <TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>;
const cardContentLeft = const cardContentLeft =
item.job_conversations.length > 0 item.job_conversations.length > 0
? item.job_conversations.map((j, idx) => <Tag key={idx}>{j.job.ro_number}</Tag>) ? item.job_conversations.map((j, idx) => <Tag key={idx}>{j.job.ro_number}</Tag>)
: null; : null;
const names = <>{_.uniq(item.job_conversations.map((j, idx) => OwnerNameDisplayFunction(j.job)))}</>; const names = <>{_.uniq(item.job_conversations.map((j, idx) => OwnerNameDisplayFunction(j.job)))}</>;
const cardTitle = ( const cardTitle = (
<> <>
{item.label && <Tag color="blue">{item.label}</Tag>} {item.label && <Tag color="blue">{item.label}</Tag>}
@@ -85,7 +80,6 @@ function ChatConversationListComponent({ conversationList, selectedConversation,
)} )}
</> </>
); );
const cardExtra = ( const cardExtra = (
<> <>
<Badge count={item.messages_aggregate.aggregate.count} /> <Badge count={item.messages_aggregate.aggregate.count} />
@@ -98,11 +92,10 @@ function ChatConversationListComponent({ conversationList, selectedConversation,
)} )}
</> </>
); );
const getCardStyle = () => const getCardStyle = () =>
item.id === selectedConversation item.id === selectedConversation
? { backgroundColor: "rgba(128, 128, 128, 0.2)" } ? { backgroundColor: "var(--card-selected-bg)" }
: { backgroundColor: index % 2 === 0 ? "#f0f2f5" : "#ffffff" }; : { backgroundColor: index % 2 === 0 ? "var(--card-stripe-even-bg)" : "var(--card-stripe-odd-bg)" };
return ( return (
<List.Item <List.Item

View File

@@ -5,7 +5,7 @@
max-height: 480px; max-height: 480px;
overflow-y: auto; overflow-y: auto;
padding: 8px; padding: 8px;
background-color: #fff; background-color: var(--popover-bg);
border-radius: 8px; border-radius: 8px;
} }
} }
@@ -17,7 +17,7 @@
} }
.error-message { .error-message {
color: red; color: var(--error-text);
font-size: 12px; font-size: 12px;
text-align: center; text-align: center;
margin-bottom: 8px; margin-bottom: 8px;
@@ -25,14 +25,13 @@
.no-jobs-message { .no-jobs-message {
font-size: 14px; font-size: 14px;
color: #888; color: var(--no-jobs-text);
text-align: center; text-align: center;
padding: 8px; padding: 8px;
} }
/* Style images within gallery components */ /* Style images within gallery components */
.media-selector-content img { .media-selector-content img {
object-fit: cover; object-fit: cover;
border-radius: 4px; border-radius: 4px;
margin: 4px; margin: 4px;
@@ -40,8 +39,8 @@
} }
/* Grid layout for gallery components */ /* Grid layout for gallery components */
.media-selector-content .ant-image, /* Assuming gallery components use Ant Design's Image */ .media-selector-content .ant-image,
.media-selector-content .gallery-container { /* Fallback for custom gallery classes */ .media-selector-content .gallery-container {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
gap: 4px; gap: 4px;

View File

@@ -44,7 +44,6 @@
.chat-send-message-button { .chat-send-message-button {
margin: 0.3rem; margin: 0.3rem;
padding-left: 0.5rem; padding-left: 0.5rem;
} }
.message-icon { .message-icon {
@@ -52,7 +51,7 @@
bottom: 0.1rem; bottom: 0.1rem;
right: 0.3rem; right: 0.3rem;
margin: 0 0.1rem; margin: 0 0.1rem;
color: whitesmoke; color: var(--message-icon-color);
z-index: 5; z-index: 5;
} }
@@ -80,7 +79,7 @@
&:last-child:after { &:last-child:after {
width: 10px; width: 10px;
background: white; background: var(--message-mine-tail-bg);
z-index: 1; z-index: 1;
} }
} }
@@ -92,11 +91,11 @@
.message { .message {
margin-right: 20%; margin-right: 20%;
background-color: #eee; background-color: var(--message-yours-bg);
&:last-child:before { &:last-child:before {
left: -7px; left: -7px;
background: #eee; background: var(--message-yours-bg);
border-bottom-right-radius: 15px; border-bottom-right-radius: 15px;
} }
@@ -112,14 +111,14 @@
align-items: flex-end; align-items: flex-end;
.message { .message {
color: white; color: var(--message-mine-text);
margin-left: 25%; margin-left: 25%;
background: linear-gradient(to bottom, #00d0ea 0%, #0085d1 100%); background: linear-gradient(to bottom, var(--message-mine-bg-start) 0%, var(--message-mine-bg-end) 100%);
padding-bottom: 0.6rem; padding-bottom: 0.6rem;
&:last-child:before { &:last-child:before {
right: -8px; right: -8px;
background: linear-gradient(to bottom, #00d0ea 0%, #0085d1 100%); background: linear-gradient(to bottom, var(--message-mine-bg-start) 0%, var(--message-mine-bg-end) 100%);
border-bottom-left-radius: 15px; border-bottom-left-radius: 15px;
} }
@@ -135,32 +134,31 @@
margin: 0.5rem 10%; margin: 0.5rem 10%;
.message { .message {
background-color: #f5f5f5; background-color: var(--system-message-bg);
border-radius: 10px; border-radius: 10px;
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
text-align: center; text-align: center;
font-style: italic; font-style: italic;
color: #555; color: var(--system-message-text);
width: fit-content; width: fit-content;
max-width: 80%; max-width: 80%;
} }
.system-label { .system-label {
font-size: 0.75rem; font-size: 0.75rem;
color: #888; color: var(--system-label-text);
margin-bottom: 0.2rem; margin-bottom: 0.2rem;
display: block; display: block;
} }
.system-date { .system-date {
font-size: 0.75rem; font-size: 0.75rem;
color: #888; color: var(--system-label-text);
margin-top: 0.2rem; margin-top: 0.2rem;
text-align: center; text-align: center;
} }
} }
.virtuoso-container { .virtuoso-container {
flex: 1; flex: 1;
overflow: auto; overflow: auto;

View File

@@ -1,6 +1,6 @@
import { Card, Table, Tag } from "antd"; import { Card, Table, Tag } from "antd";
import axios from "axios"; import axios from "axios";
import React, { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import dayjs from "../../../utils/day"; import dayjs from "../../../utils/day";
import LoadingSkeleton from "../../loading-skeleton/loading-skeleton.component"; import LoadingSkeleton from "../../loading-skeleton/loading-skeleton.component";
@@ -69,7 +69,6 @@ export default function JobLifecycleDashboardComponent({ data, bodyshop, ...card
]; ];
if (!data) return null; if (!data) return null;
if (!data.job_lifecycle || !lifecycleData) return <DashboardRefreshRequired {...cardProps} />; if (!data.job_lifecycle || !lifecycleData) return <DashboardRefreshRequired {...cardProps} />;
const extra = `${t("job_lifecycle.content.calculated_based_on")} ${lifecycleData.jobs} ${t("job_lifecycle.content.jobs_in_since")} ${fortyFiveDaysAgo()}`; const extra = `${t("job_lifecycle.content.calculated_based_on")} ${lifecycleData.jobs} ${t("job_lifecycle.content.jobs_in_since")} ${fortyFiveDaysAgo()}`;
@@ -88,7 +87,7 @@ export default function JobLifecycleDashboardComponent({ data, bodyshop, ...card
borderRadius: "5px", borderRadius: "5px",
borderWidth: "5px", borderWidth: "5px",
borderStyle: "solid", borderStyle: "solid",
borderColor: "#f0f2f5", borderColor: "var(--bar-border-color)",
margin: 0, margin: 0,
padding: 0 padding: 0
}} }}
@@ -107,12 +106,10 @@ export default function JobLifecycleDashboardComponent({ data, bodyshop, ...card
alignItems: "center", alignItems: "center",
margin: 0, margin: 0,
padding: 0, padding: 0,
borderTop: "1px solid var(--bar-border-color)",
borderTop: "1px solid #f0f2f5", borderBottom: "1px solid var(--bar-border-color)",
borderBottom: "1px solid #f0f2f5", borderLeft: isFirst ? "1px solid var(--bar-border-color)" : undefined,
borderLeft: isFirst ? "1px solid #f0f2f5" : undefined, borderRight: isLast ? "1px solid var(--bar-border-color)" : undefined,
borderRight: isLast ? "1px solid #f0f2f5" : undefined,
backgroundColor: key.color, backgroundColor: key.color,
width: `${key.percentage}%` width: `${key.percentage}%`
}} }}
@@ -124,7 +121,7 @@ export default function JobLifecycleDashboardComponent({ data, bodyshop, ...card
<div>{key.roundedPercentage}</div> <div>{key.roundedPercentage}</div>
<div <div
style={{ style={{
backgroundColor: "#f0f2f5", backgroundColor: "var(--tag-wrapper-bg)",
borderRadius: "5px", borderRadius: "5px",
paddingRight: "2px", paddingRight: "2px",
paddingLeft: "2px", paddingLeft: "2px",
@@ -152,8 +149,8 @@ export default function JobLifecycleDashboardComponent({ data, bodyshop, ...card
aria-label={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`} aria-label={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
title={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`} title={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
style={{ style={{
backgroundColor: "#f0f2f5", backgroundColor: "var(--tag-wrapper-bg)",
color: "#000", color: "var(--tag-wrapper-text)",
padding: "4px", padding: "4px",
textAlign: "center" textAlign: "center"
}} }}

View File

@@ -1,7 +1,6 @@
import { UploadOutlined, UserAddOutlined } from "@ant-design/icons"; import { UploadOutlined, UserAddOutlined } from "@ant-design/icons";
import { Button, Divider, Dropdown, Form, Input, Select, Space, Tabs, Upload } from "antd"; import { Button, Divider, Dropdown, Form, Input, Select, Space, Tabs, Upload } from "antd";
import _ from "lodash"; import _ from "lodash";
import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
@@ -15,20 +14,24 @@ const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
emailConfig: selectEmailConfig emailConfig: selectEmailConfig
}); });
const mapDispatchToProps = (dispatch) => ({
const mapDispatchToProps = () => ({
//setUserLanguage: language => dispatch(setUserLanguage(language)) //setUserLanguage: language => dispatch(setUserLanguage(language))
}); });
export default connect(mapStateToProps, mapDispatchToProps)(EmailOverlayComponent); export default connect(mapStateToProps, mapDispatchToProps)(EmailOverlayComponent);
export function EmailOverlayComponent({ emailConfig, form, selectedMediaState, bodyshop, currentUser }) { export function EmailOverlayComponent({ emailConfig, form, selectedMediaState, bodyshop, currentUser }) {
const { t } = useTranslation(); const { t } = useTranslation();
const handleClick = ({ item, key, keyPath }) => {
const handleClick = ({ item }) => {
const email = item.props.value; const email = item.props.value;
form.setFieldsValue({ form.setFieldsValue({
to: _.uniq([...form.getFieldValue("to"), ...(typeof email === "string" ? [email] : email)]) to: _.uniq([...form.getFieldValue("to"), ...(typeof email === "string" ? [email] : email)])
}); });
}; };
const handle_CC_Click = ({ item, key, keyPath }) => {
const handle_CC_Click = ({ item }) => {
const email = item.props.value; const email = item.props.value;
form.setFieldsValue({ form.setFieldsValue({
cc: _.uniq([...(form.getFieldValue("cc") || ""), ...(typeof email === "string" ? [email] : email)]) cc: _.uniq([...(form.getFieldValue("cc") || ""), ...(typeof email === "string" ? [email] : email)])
@@ -52,6 +55,7 @@ export function EmailOverlayComponent({ emailConfig, form, selectedMediaState, b
], ],
onClick: handleClick onClick: handleClick
}; };
const menuCC = { const menuCC = {
items: [ items: [
...bodyshop.employees ...bodyshop.employees
@@ -136,26 +140,22 @@ export function EmailOverlayComponent({ emailConfig, form, selectedMediaState, b
> >
<Input /> <Input />
</Form.Item> </Form.Item>
<Divider>{t("emails.labels.preview")}</Divider> <Divider>{t("emails.labels.preview")}</Divider>
{bodyshop.attach_pdf_to_email && <strong>{t("emails.labels.pdfcopywillbeattached")}</strong>} {bodyshop.attach_pdf_to_email && <strong>{t("emails.labels.pdfcopywillbeattached")}</strong>}
<Form.Item shouldUpdate> <Form.Item shouldUpdate>
{() => { {() => {
return ( return (
<div <div
style={{ style={{
padding: "1rem", padding: "1rem",
backgroundColor: "var(--preview-bg)",
backgroundColor: "lightgray", borderLeft: "6px solid var(--preview-border-color)"
borderLeft: "6px solid #2196F3"
}} }}
dangerouslySetInnerHTML={{ __html: form.getFieldValue("html") }} dangerouslySetInnerHTML={{ __html: form.getFieldValue("html") }}
/> />
); );
}} }}
</Form.Item> </Form.Item>
<Tabs <Tabs
defaultActiveKey="documents" defaultActiveKey="documents"
items={[ items={[
@@ -184,12 +184,10 @@ export function EmailOverlayComponent({ emailConfig, form, selectedMediaState, b
return e && e.fileList; return e && e.fileList;
}} }}
rules={[ rules={[
({ getFieldValue }) => ({ () => ({
validator(rule, value) { validator(rule, value) {
const totalSize = value.reduce((acc, val) => (acc = acc + val.size), 0); const totalSize = value.reduce((acc, val) => (acc = acc + val.size), 0);
const limit = 10485760 - new Blob([form.getFieldValue("html")]).size; const limit = 10485760 - new Blob([form.getFieldValue("html")]).size;
if (totalSize > limit) { if (totalSize > limit) {
return Promise.reject(t("general.errors.sizelimit")); return Promise.reject(t("general.errors.sizelimit"));
} }

View File

@@ -5,7 +5,7 @@
.eula-markdown-card { .eula-markdown-card {
max-height: 50vh; max-height: 50vh;
overflow-y: auto; overflow-y: auto;
background-color: lightgray; background-color: var(--eula-card-bg);
} }
.eula-markdown-div { .eula-markdown-div {

View File

@@ -1,5 +1,4 @@
import Dinero from "dinero.js"; import Dinero from "dinero.js";
import { forwardRef } from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
@@ -28,4 +27,4 @@ const ReadOnlyFormItem = ({ bodyshop, value, type = "text" }) => {
} }
}; };
export default connect(mapStateToProps, mapDispatchToProps)(forwardRef(ReadOnlyFormItem)); export default connect(mapStateToProps, mapDispatchToProps)(ReadOnlyFormItem);

View File

@@ -25,7 +25,7 @@ import {
UsergroupAddOutlined, UsergroupAddOutlined,
UserOutlined UserOutlined
} from "@ant-design/icons"; } from "@ant-design/icons";
import { FaCalendarAlt, FaCarCrash, FaTasks } from "react-icons/fa"; import { FaCalendarAlt, FaCarCrash, FaMoon, FaSun, FaTasks } from "react-icons/fa";
import { BsKanban } from "react-icons/bs"; import { BsKanban } from "react-icons/bs";
import { FiLogOut } from "react-icons/fi"; import { FiLogOut } from "react-icons/fi";
import { GiPlayerTime, GiSettingsKnobs } from "react-icons/gi"; import { GiPlayerTime, GiSettingsKnobs } from "react-icons/gi";
@@ -33,6 +33,7 @@ import { RiSurveyLine } from "react-icons/ri";
import { IoBusinessOutline } from "react-icons/io5"; import { IoBusinessOutline } from "react-icons/io5";
import InstanceRenderManager from "../../utils/instanceRenderMgr"; import InstanceRenderManager from "../../utils/instanceRenderMgr";
import LockWrapper from "../../components/lock-wrapper/lock-wrapper.component.jsx"; import LockWrapper from "../../components/lock-wrapper/lock-wrapper.component.jsx";
import { Tooltip } from "antd";
const buildLeftMenuItems = ({ const buildLeftMenuItems = ({
t, t,
@@ -41,7 +42,9 @@ const buildLeftMenuItems = ({
setTaskUpsertContext, setTaskUpsertContext,
setReportCenterContext, setReportCenterContext,
signOutStart, signOutStart,
accountingChildren accountingChildren,
handleDarkModeToggle,
darkMode
}) => { }) => {
return [ return [
{ {
@@ -331,6 +334,15 @@ const buildLeftMenuItems = ({
label: t("user.actions.signout"), label: t("user.actions.signout"),
onClick: () => signOutStart() onClick: () => signOutStart()
}, },
{
key: "darkmode-toggle",
id: "header-darkmode-toggle",
label: darkMode ? t("user.actions.light_theme") : t("user.actions.dark_theme"),
icon: (
<Tooltip title={darkMode ? t("Light mode") : t("Dark mode")}>{darkMode ? <FaSun /> : <FaMoon />}</Tooltip>
),
onClick: handleDarkModeToggle
},
{ {
key: "help", key: "help",
id: "header-help", id: "header-help",

View File

@@ -12,7 +12,7 @@ import { createStructuredSelector } from "reselect";
import { TASKS_CENTER_POLL_INTERVAL, useSocket } from "../../contexts/SocketIO/useSocket.js"; import { TASKS_CENTER_POLL_INTERVAL, useSocket } from "../../contexts/SocketIO/useSocket.js";
import { GET_UNREAD_COUNT } from "../../graphql/notifications.queries.js"; import { GET_UNREAD_COUNT } from "../../graphql/notifications.queries.js";
import { QUERY_MY_TASKS_COUNT } from "../../graphql/tasks.queries.js"; import { QUERY_MY_TASKS_COUNT } from "../../graphql/tasks.queries.js";
import { selectRecentItems, selectSelectedHeader } from "../../redux/application/application.selectors"; import { selectDarkMode, selectRecentItems, selectSelectedHeader } from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
import { signOutStart } from "../../redux/user/user.actions"; import { signOutStart } from "../../redux/user/user.actions";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
@@ -22,13 +22,15 @@ import NotificationCenterContainer from "../notification-center/notification-cen
import TaskCenterContainer from "../task-center/task-center.container.jsx"; import TaskCenterContainer from "../task-center/task-center.container.jsx";
import buildAccountingChildren from "./buildAccountingChildren.jsx"; import buildAccountingChildren from "./buildAccountingChildren.jsx";
import buildLeftMenuItems from "./buildLeftMenuItems.jsx"; import buildLeftMenuItems from "./buildLeftMenuItems.jsx";
import { toggleDarkMode } from "../../redux/application/application.actions";
// --- Redux mappings --- // --- Redux mappings ---
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
recentItems: selectRecentItems, recentItems: selectRecentItems,
selectedHeader: selectSelectedHeader, selectedHeader: selectSelectedHeader,
bodyshop: selectBodyshop bodyshop: selectBodyshop,
darkMode: selectDarkMode
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
@@ -38,7 +40,8 @@ const mapDispatchToProps = (dispatch) => ({
setReportCenterContext: (context) => dispatch(setModalContext({ context, modal: "reportCenter" })), setReportCenterContext: (context) => dispatch(setModalContext({ context, modal: "reportCenter" })),
signOutStart: () => dispatch(signOutStart()), signOutStart: () => dispatch(signOutStart()),
setCardPaymentContext: (context) => dispatch(setModalContext({ context, modal: "cardPayment" })), setCardPaymentContext: (context) => dispatch(setModalContext({ context, modal: "cardPayment" })),
setTaskUpsertContext: (context) => dispatch(setModalContext({ context, modal: "taskUpsert" })) setTaskUpsertContext: (context) => dispatch(setModalContext({ context, modal: "taskUpsert" })),
toggleDarkMode: () => dispatch(toggleDarkMode())
}); });
// --- Utility Hooks --- // --- Utility Hooks ---
@@ -84,22 +87,22 @@ function useIncompleteTaskCount(assignedToId, bodyshopId, isEmployee, isConnecte
} }
// --- Main Component --- // --- Main Component ---
function Header(props) { function Header({
const { handleMenuClick,
handleMenuClick, currentUser,
currentUser, bodyshop,
bodyshop, selectedHeader,
selectedHeader, signOutStart,
signOutStart, setBillEnterContext,
setBillEnterContext, setTimeTicketContext,
setTimeTicketContext, setPaymentContext,
setPaymentContext, setReportCenterContext,
setReportCenterContext, recentItems,
recentItems, setCardPaymentContext,
setCardPaymentContext, setTaskUpsertContext,
setTaskUpsertContext toggleDarkMode,
} = props; darkMode
}) {
// Feature flags // Feature flags
const { const {
treatments: { ImEXPay, DmsAp, Simple_Inventory } treatments: { ImEXPay, DmsAp, Simple_Inventory }
@@ -216,6 +219,10 @@ function Header(props) {
[handleMenuClick] [handleMenuClick]
); );
const handleDarkModeToggle = useCallback(() => {
toggleDarkMode();
}, [toggleDarkMode]);
// --- Menu Items --- // --- Menu Items ---
// built externally to keep the component clean, but on this level to prevent unnecessary re-renders // built externally to keep the component clean, but on this level to prevent unnecessary re-renders
@@ -257,9 +264,21 @@ function Header(props) {
setTaskUpsertContext, setTaskUpsertContext,
setReportCenterContext, setReportCenterContext,
signOutStart, signOutStart,
accountingChildren accountingChildren,
darkMode,
handleDarkModeToggle
}), }),
[t, bodyshop, recentItems, setTaskUpsertContext, setReportCenterContext, signOutStart, accountingChildren] [
t,
bodyshop,
recentItems,
setTaskUpsertContext,
setReportCenterContext,
signOutStart,
accountingChildren,
darkMode,
handleDarkModeToggle
]
); );
const rightMenuItems = useMemo(() => { const rightMenuItems = useMemo(() => {
@@ -292,6 +311,7 @@ function Header(props) {
), ),
onClick: handleTaskCenterClick onClick: handleTaskCenterClick
}); });
return items; return items;
}, [ }, [
scenarioNotificationsOn, scenarioNotificationsOn,

View File

@@ -36,6 +36,7 @@ import ScheduleEventNote from "./schedule-event.note.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop bodyshop: selectBodyshop
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setScheduleContext: (context) => dispatch(setModalContext({ context: context, modal: "schedule" })), setScheduleContext: (context) => dispatch(setModalContext({ context: context, modal: "schedule" })),
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)), openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
@@ -64,7 +65,6 @@ export function ScheduleEventComponent({
const notification = useNotification(); const notification = useNotification();
const [form] = Form.useForm(); const [form] = Form.useForm();
const [popOverVisible, setPopOverVisible] = useState(false); const [popOverVisible, setPopOverVisible] = useState(false);
const [getJobDetails] = useLazyQuery(GET_JOB_BY_PK_QUICK_INTAKE, { const [getJobDetails] = useLazyQuery(GET_JOB_BY_PK_QUICK_INTAKE, {
variables: { id: event.job?.id }, variables: { id: event.job?.id },
onCompleted: (data) => { onCompleted: (data) => {
@@ -83,7 +83,6 @@ export function ScheduleEventComponent({
}); });
} }
}, },
fetchPolicy: "network-only" fetchPolicy: "network-only"
}); });
@@ -115,7 +114,6 @@ export function ScheduleEventComponent({
}); });
}} }}
/> />
<Button onClick={() => handleCancel({ id: event.id })} disabled={event.arrived}> <Button onClick={() => handleCancel({ id: event.id })} disabled={event.arrived}>
{t("appointments.actions.unblock")} {t("appointments.actions.unblock")}
</Button> </Button>
@@ -133,7 +131,6 @@ export function ScheduleEventComponent({
} }
} }
}); });
if (!res.errors) { if (!res.errors) {
notification["success"]({ notification["success"]({
message: t("jobs.successes.converted") message: t("jobs.successes.converted")
@@ -180,7 +177,6 @@ export function ScheduleEventComponent({
<Form.Item name={["scheduled_delivery"]} label={t("jobs.fields.scheduled_delivery")}> <Form.Item name={["scheduled_delivery"]} label={t("jobs.fields.scheduled_delivery")}>
<FormDateTimePickerComponent disabled={event.ro_number} /> <FormDateTimePickerComponent disabled={event.ro_number} />
</Form.Item> </Form.Item>
<Space wrap> <Space wrap>
<Button type="primary" onClick={() => form.submit()}> <Button type="primary" onClick={() => form.submit()}>
{t("general.actions.save")} {t("general.actions.save")}
@@ -210,7 +206,6 @@ export function ScheduleEventComponent({
<ScheduleEventColor event={event} /> <ScheduleEventColor event={event} />
</Space> </Space>
)} )}
{event.job ? ( {event.job ? (
<div> <div>
<DataLabel label={t("jobs.fields.ro_number")}>{(event.job && event.job.ro_number) || ""}</DataLabel> <DataLabel label={t("jobs.fields.ro_number")}>{(event.job && event.job.ro_number) || ""}</DataLabel>
@@ -371,7 +366,6 @@ export function ScheduleEventComponent({
</Button> </Button>
</Popover> </Popover>
)} )}
{event.isintake ? ( {event.isintake ? (
<Button <Button
disabled={event.arrived} disabled={event.arrived}
@@ -428,27 +422,30 @@ export function ScheduleEventComponent({
</div> </div>
); );
// Adjust event color for dark mode if needed
const getEventBackground = () => {
const baseColor = event.color && event.color.hex ? event.color.hex : event.color || "var(--event-bg-fallback)";
// Optionally adjust color for dark mode (e.g., lighten if too dark)
return baseColor;
};
const RegularEvent = event.isintake ? ( const RegularEvent = event.isintake ? (
<Space <Space
wrap wrap
size="small" size="small"
style={{ style={{
backgroundColor: event.color && event.color.hex ? event.color.hex : event.color backgroundColor: getEventBackground()
}} }}
> >
{event.note && <AlertFilled className="production-alert" />} {event.note && <AlertFilled className="production-alert" />}
<strong>{`${event.job.ro_number || t("general.labels.na")}`}</strong> <strong>{`${event.job.ro_number || t("general.labels.na")}`}</strong>
<OwnerNameDisplay ownerObject={event.job} /> <OwnerNameDisplay ownerObject={event.job} />
{`${(event.job && event.job.v_model_yr) || ""} ${ {`${(event.job && event.job.v_model_yr) || ""} ${
(event.job && event.job.v_make_desc) || "" (event.job && event.job.v_make_desc) || ""
} ${(event.job && event.job.v_model_desc) || ""}`} } ${(event.job && event.job.v_model_desc) || ""}`}
{`(${(event.job && event.job.labhrs.aggregate.sum.mod_lb_hrs) || "0"} / ${ {`(${(event.job && event.job.labhrs.aggregate.sum.mod_lb_hrs) || "0"} / ${
(event.job && event.job.larhrs.aggregate.sum.mod_lb_hrs) || "0" (event.job && event.job.larhrs.aggregate.sum.mod_lb_hrs) || "0"
})`} })`}
{event.job && event.job.alt_transport && <div style={{ margin: ".1rem" }}>{event.job.alt_transport}</div>} {event.job && event.job.alt_transport && <div style={{ margin: ".1rem" }}>{event.job.alt_transport}</div>}
{event?.job?.comment && `C: ${event.job.comment}`} {event?.job?.comment && `C: ${event.job.comment}`}
</Space> </Space>
@@ -457,7 +454,7 @@ export function ScheduleEventComponent({
style={{ style={{
height: "100%", height: "100%",
width: "100%", width: "100%",
backgroundColor: event.color && event.color.hex ? event.color.hex : event.color backgroundColor: getEventBackground()
}} }}
> >
<strong>{`${event.title || ""}`}</strong> <strong>{`${event.title || ""}`}</strong>
@@ -473,8 +470,7 @@ export function ScheduleEventComponent({
style={{ style={{
height: "100%", height: "100%",
width: "100%", width: "100%",
backgroundColor: getEventBackground()
backgroundColor: event.color && event.color.hex ? event.color.hex : event.color
}} }}
> >
{RegularEvent} {RegularEvent}

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import dayjs from "../../utils/day"; import dayjs from "../../utils/day";
import axios from "axios"; import axios from "axios";
import { Badge, Card, Space, Table, Tag } from "antd"; import { Badge, Card, Space, Table, Tag } from "antd";
@@ -6,24 +6,24 @@ import { gql, useQuery } from "@apollo/client";
import { DateTimeFormatterFunction } from "../../utils/DateFormatter"; import { DateTimeFormatterFunction } from "../../utils/DateFormatter";
import { isEmpty } from "lodash"; import { isEmpty } from "lodash";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import "./job-lifecycle.styles.scss"; import "./job-lifecycle.styles.scss";
import BlurWrapperComponent from "../feature-wrapper/blur-wrapper.component"; import BlurWrapperComponent from "../feature-wrapper/blur-wrapper.component";
import UpsellComponent, { upsellEnum } from "../upsell/upsell.component"; import UpsellComponent, { upsellEnum } from "../upsell/upsell.component";
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component"; import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop bodyshop: selectBodyshop
}); });
const mapDispatchToProps = (dispatch) => ({
const mapDispatchToProps = () => ({
//setUserLanguage: language => dispatch(setUserLanguage(language)) //setUserLanguage: language => dispatch(setUserLanguage(language))
}); });
// show text on bar if text can fit // show text on bar if text can fit
export function JobLifecycleComponent({ bodyshop, job, statuses, ...rest }) { export function JobLifecycleComponent({ bodyshop, job, statuses }) {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [lifecycleData, setLifecycleData] = useState(null); const [lifecycleData, setLifecycleData] = useState(null);
const { t } = useTranslation(); // Used for tracking external state changes. const { t } = useTranslation(); // Used for tracking external state changes.
@@ -79,7 +79,7 @@ export function JobLifecycleComponent({ bodyshop, job, statuses, ...rest }) {
title: t("job_lifecycle.columns.value"), title: t("job_lifecycle.columns.value"),
dataIndex: "value", dataIndex: "value",
key: "value", key: "value",
render: (text, record) => ( render: (text) => (
<BlurWrapperComponent <BlurWrapperComponent
featureName="lifecycle" featureName="lifecycle"
bypass bypass
@@ -95,7 +95,7 @@ export function JobLifecycleComponent({ bodyshop, job, statuses, ...rest }) {
dataIndex: "start", dataIndex: "start",
key: "start", key: "start",
sorter: (a, b) => dayjs(a.start).unix() - dayjs(b.start).unix(), sorter: (a, b) => dayjs(a.start).unix() - dayjs(b.start).unix(),
render: (text, record) => ( render: (text) => (
<BlurWrapperComponent featureName="lifecycle" bypass valueProp="children" overrideValueFunction="RandomDate"> <BlurWrapperComponent featureName="lifecycle" bypass valueProp="children" overrideValueFunction="RandomDate">
<span>{DateTimeFormatterFunction(text)}</span> <span>{DateTimeFormatterFunction(text)}</span>
</BlurWrapperComponent> </BlurWrapperComponent>
@@ -119,8 +119,7 @@ export function JobLifecycleComponent({ bodyshop, job, statuses, ...rest }) {
} }
return dayjs(a.end).unix() - dayjs(b.end).unix(); return dayjs(a.end).unix() - dayjs(b.end).unix();
}, },
render: (text) => (
render: (text, record) => (
<BlurWrapperComponent featureName="lifecycle" bypass valueProp="children" overrideValueFunction="RandomDate"> <BlurWrapperComponent featureName="lifecycle" bypass valueProp="children" overrideValueFunction="RandomDate">
<span>{isEmpty(text) ? t("job_lifecycle.content.not_available") : DateTimeFormatterFunction(text)}</span> <span>{isEmpty(text) ? t("job_lifecycle.content.not_available") : DateTimeFormatterFunction(text)}</span>
</BlurWrapperComponent> </BlurWrapperComponent>
@@ -170,7 +169,7 @@ export function JobLifecycleComponent({ bodyshop, job, statuses, ...rest }) {
borderRadius: "5px", borderRadius: "5px",
borderWidth: "5px", borderWidth: "5px",
borderStyle: "solid", borderStyle: "solid",
borderColor: "#f0f2f5", borderColor: "var(--bar-border-color)",
margin: 0, margin: 0,
padding: 0 padding: 0
}} }}
@@ -189,12 +188,10 @@ export function JobLifecycleComponent({ bodyshop, job, statuses, ...rest }) {
alignItems: "center", alignItems: "center",
margin: 0, margin: 0,
padding: 0, padding: 0,
borderTop: "1px solid var(--bar-border-color)",
borderTop: "1px solid #f0f2f5", borderBottom: "1px solid var(--bar-border-color)",
borderBottom: "1px solid #f0f2f5", borderLeft: isFirst ? "1px solid var(--bar-border-color)" : undefined,
borderLeft: isFirst ? "1px solid #f0f2f5" : undefined, borderRight: isLast ? "1px solid var(--bar-border-color)" : undefined,
borderRight: isLast ? "1px solid #f0f2f5" : undefined,
backgroundColor: key.color, backgroundColor: key.color,
width: `${key.percentage}%` width: `${key.percentage}%`
}} }}
@@ -206,7 +203,7 @@ export function JobLifecycleComponent({ bodyshop, job, statuses, ...rest }) {
<div>{key.roundedPercentage}</div> <div>{key.roundedPercentage}</div>
<div <div
style={{ style={{
backgroundColor: "#f0f2f5", backgroundColor: "var(--tag-wrapper-bg)",
borderRadius: "5px", borderRadius: "5px",
paddingRight: "2px", paddingRight: "2px",
paddingLeft: "2px", paddingLeft: "2px",
@@ -230,8 +227,8 @@ export function JobLifecycleComponent({ bodyshop, job, statuses, ...rest }) {
aria-label={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`} aria-label={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
title={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`} title={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
style={{ style={{
backgroundColor: "#f0f2f5", backgroundColor: "var(--tag-wrapper-bg)",
color: "#000", color: "var(--tag-wrapper-text)",
padding: "4px", padding: "4px",
textAlign: "center" textAlign: "center"
}} }}
@@ -315,4 +312,5 @@ export function JobLifecycleComponent({ bodyshop, job, statuses, ...rest }) {
</Card> </Card>
); );
} }
export default connect(mapStateToProps, mapDispatchToProps)(JobLifecycleComponent); export default connect(mapStateToProps, mapDispatchToProps)(JobLifecycleComponent);

View File

@@ -6,7 +6,7 @@
td { td {
padding: 8px; padding: 8px;
text-align: left; text-align: left;
border-bottom: 1px solid #ddd; border-bottom: 1px solid var(--table-border-color);
.ant-form-item { .ant-form-item {
margin-bottom: 0px !important; margin-bottom: 0px !important;
@@ -14,6 +14,6 @@
} }
tr:hover { tr:hover {
background-color: #f5f5f5; background-color: var(--table-hover-bg);
} }
} }

View File

@@ -42,7 +42,6 @@ export function NoteUpsertModalContainer({ currentUser, noteUpsertModal, toggleM
const { refetch } = actions; const { refetch } = actions;
const [form] = Form.useForm(); const [form] = Form.useForm();
useEffect(() => { useEffect(() => {
//Required to prevent infinite looping. //Required to prevent infinite looping.
if (existingNote && open) { if (existingNote && open) {

View File

@@ -4,9 +4,9 @@
right: 0; right: 0;
width: 400px; width: 400px;
max-width: 400px; max-width: 400px;
background: #fff; background: var(--notification-bg);
color: rgba(0, 0, 0, 0.85); color: var(--notification-text);
border: 1px solid #d9d9d9; border: 1px solid var(--notification-border);
border-radius: 6px; border-radius: 6px;
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.08), 0 3px 6px rgba(0, 0, 0, 0.06); box-shadow: 0 6px 16px rgba(0, 0, 0, 0.08), 0 3px 6px rgba(0, 0, 0, 0.06);
z-index: 1000; z-index: 1000;
@@ -19,23 +19,22 @@
.notification-header { .notification-header {
padding: 4px 16px; padding: 4px 16px;
border-bottom: 1px solid #f0f0f0; border-bottom: 1px solid var(--notification-header-border);
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
background: #fafafa; background: var(--notification-header-bg);
h3 { h3 {
margin: 0; margin: 0;
font-size: 14px; font-size: 14px;
color: rgba(0, 0, 0, 0.85); color: var(--notification-header-text);
} }
.notification-controls { .notification-controls {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
// Styles for the eye icon and switch (custom classes) // Styles for the eye icon and switch (custom classes)
.notification-toggle { .notification-toggle {
align-items: center; // Ensure vertical alignment align-items: center; // Ensure vertical alignment
@@ -43,7 +42,7 @@
.notification-toggle-icon { .notification-toggle-icon {
font-size: 14px; font-size: 14px;
color: #1677ff; color: var(--notification-toggle-icon);
vertical-align: middle; vertical-align: middle;
} }
@@ -59,7 +58,8 @@
} }
&.ant-switch-checked { &.ant-switch-checked {
background-color: #1677ff; background-color: var(--notification-switch-bg);
.ant-switch-handle { .ant-switch-handle {
left: calc(100% - 14px); left: calc(100% - 14px);
} }
@@ -70,37 +70,37 @@
// Styles for the "Mark All Read" button (restore original link button style) // Styles for the "Mark All Read" button (restore original link button style)
.ant-btn-link { .ant-btn-link {
padding: 0; padding: 0;
color: #1677ff; color: var(--notification-btn-link);
&:hover { &:hover {
color: #69b1ff; color: var(--notification-btn-link-hover);
} }
&:disabled { &:disabled {
color: rgba(0, 0, 0, 0.25); color: var(--notification-btn-link-disabled);
cursor: not-allowed; cursor: not-allowed;
} }
&.active { &.active {
color: #0958d9; color: var(--notification-btn-link-active);
} }
} }
} }
} }
.notification-read { .notification-read {
background: #fff; background: var(--notification-read-bg);
color: rgba(0, 0, 0, 0.65); color: var(--notification-read-text);
} }
.notification-unread { .notification-unread {
background: #f5f5f5; background: var(--notification-unread-bg);
color: rgba(0, 0, 0, 0.85); color: var(--notification-unread-text);
} }
.notification-item { .notification-item {
padding: 12px 16px; padding: 12px 16px;
border-bottom: 1px solid #f0f0f0; border-bottom: 1px solid var(--notification-header-border);
display: block; display: block;
overflow: visible; overflow: visible;
width: 100%; width: 100%;
@@ -108,7 +108,7 @@
cursor: pointer; cursor: pointer;
&:hover { &:hover {
background: #fafafa; background: var(--notification-item-hover-bg);
} }
.notification-content { .notification-content {
@@ -125,7 +125,7 @@
.ro-number { .ro-number {
margin: 0; margin: 0;
color: #1677ff; color: var(--notification-ro-number);
flex-shrink: 0; flex-shrink: 0;
white-space: nowrap; white-space: nowrap;
} }
@@ -133,7 +133,7 @@
.relative-time { .relative-time {
margin: 0; margin: 0;
font-size: 12px; font-size: 12px;
color: rgba(0, 0, 0, 0.45); color: var(--notification-relative-time);
white-space: nowrap; white-space: nowrap;
flex-shrink: 0; flex-shrink: 0;
margin-left: auto; margin-left: auto;
@@ -164,12 +164,12 @@
.ant-alert { .ant-alert {
margin: 8px; margin: 8px;
background: #fff1f0; background: var(--alert-bg);
color: rgba(0, 0, 0, 0.85); color: var(--alert-text);
border: 1px solid #ffa39e; border: 1px solid var(--alert-border);
.ant-alert-message { .ant-alert-message {
color: #ff4d4f; color: var(--alert-message);
} }
} }
} }

View File

@@ -1,26 +1,29 @@
import { Col, List, Space, Typography } from "antd"; import { Col, List, Space, Typography } from "antd";
import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
const CardColorLegend = ({ bodyshop }) => { const CardColorLegend = ({ bodyshop }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const data = bodyshop.ssbuckets.map((bucket) => { const data = bodyshop.ssbuckets.map((bucket) => {
let color = { r: 255, g: 255, b: 255 }; let color = { r: 255, g: 255, b: 255, a: 1 }; // Default to white with full opacity
if (bucket.color) { if (bucket.color) {
color = bucket.color; color = bucket.color;
if (bucket.color.rgb) { if (bucket.color.rgb) {
color = bucket.color.rgb; color = { ...bucket.color.rgb, a: bucket.color.a || 1 };
} }
} }
return { return {
label: bucket.label, label: bucket.label,
color color
}; };
}); });
const getBackgroundColor = (color) => {
// Return dynamic color if valid, otherwise use fallback
return color && color.r !== undefined && color.g !== undefined && color.b !== undefined
? `rgba(${color.r},${color.g},${color.b},${color.a || 1})`
: "var(--legend-bg-fallback)";
};
return ( return (
<Col> <Col>
<Typography>{t("production.labels.legend")}</Typography> <Typography>{t("production.labels.legend")}</Typography>
@@ -36,7 +39,7 @@ const CardColorLegend = ({ bodyshop }) => {
style={{ style={{
width: "1.5rem", width: "1.5rem",
aspectRatio: "1/1", aspectRatio: "1/1",
backgroundColor: `rgba(${item.color.r},${item.color.g},${item.color.b},${item.color.a})` backgroundColor: getBackgroundColor(item.color)
}} }}
></div> ></div>
<div>{item.label}</div> <div>{item.label}</div>

View File

@@ -11,13 +11,10 @@ import React, { useMemo } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { DateTimeFormatter } from "../../utils/DateFormatter"; import { DateTimeFormatter } from "../../utils/DateFormatter";
import ProductionAlert from "../production-list-columns/production-list-columns.alert.component"; import ProductionAlert from "../production-list-columns/production-list-columns.alert.component";
import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component"; import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component";
import ProductionSubletsManageComponent from "../production-sublets-manage/production-sublets-manage.component"; import ProductionSubletsManageComponent from "../production-sublets-manage/production-sublets-manage.component";
import dayjs from "../../utils/day"; import dayjs from "../../utils/day";
import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component"; import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import ShareToTeamsButton from "../share-to-teams/share-to-teams.component.jsx"; import ShareToTeamsButton from "../share-to-teams/share-to-teams.component.jsx";
@@ -25,11 +22,25 @@ import { PiMicrosoftTeamsLogo } from "react-icons/pi";
const cardColor = (ssbuckets, totalHrs) => { const cardColor = (ssbuckets, totalHrs) => {
const bucket = ssbuckets.find((bucket) => bucket.gte <= totalHrs && (!bucket.lt || bucket.lt > totalHrs)); const bucket = ssbuckets.find((bucket) => bucket.gte <= totalHrs && (!bucket.lt || bucket.lt > totalHrs));
return bucket && bucket.color ? bucket.color.rgb || bucket.color : { r: 255, g: 255, b: 255 }; return bucket && bucket.color
? bucket.color.rgb || bucket.color
: {
r: 255,
g: 255,
b: 255,
a: 1,
fallback: "var(--card-bg-fallback)"
};
}; };
const getContrastYIQ = (bgColor) => const getContrastYIQ = (bgColor, isDarkMode = document.documentElement.getAttribute("data-theme") === "dark") => {
(bgColor.r * 299 + bgColor.g * 587 + bgColor.b * 114) / 1000 >= 128 ? "black" : "white"; // Use fallback if bgColor is invalid
if (!bgColor || bgColor.fallback) return isDarkMode ? "var(--card-text-fallback)" : "black";
// Calculate luminance for contrast
const luminance = (bgColor.r * 299 + bgColor.g * 587 + bgColor.b * 114) / 1000;
// Adjust threshold for dark mode to ensure readable text
return luminance >= (isDarkMode ? 150 : 128) ? "black" : isDarkMode ? "var(--card-text-fallback)" : "white";
};
const findEmployeeById = (employees, id) => employees.find((e) => e.id === id); const findEmployeeById = (employees, id) => employees.find((e) => e.id === id);
@@ -44,6 +55,8 @@ const EllipsesToolTip = React.memo(({ title, children, kiosk }) => {
); );
}); });
EllipsesToolTip.displayName = "EllipsesToolTip";
const OwnerNameToolTip = ({ metadata, cardSettings }) => const OwnerNameToolTip = ({ metadata, cardSettings }) =>
cardSettings?.ownr_nm && ( cardSettings?.ownr_nm && (
<Col span={24}> <Col span={24}>
@@ -214,9 +227,8 @@ const EstimatorToolTip = ({ metadata, cardSettings }) => {
); );
}; };
const SubtotalTooltip = ({ metadata, cardSettings, t }) => { const SubtotalTooltip = ({ metadata, cardSettings }) => {
const dineroAmount = Dinero(metadata?.job_totals?.totals?.subtotal ?? Dinero()).toFormat(); const dineroAmount = Dinero(metadata?.job_totals?.totals?.subtotal ?? Dinero()).toFormat();
return ( return (
cardSettings?.subtotal && ( cardSettings?.subtotal && (
<Col span={cardSettings.compact ? 24 : 12}> <Col span={cardSettings.compact ? 24 : 12}>
@@ -300,12 +312,10 @@ const TasksToolTip = ({ metadata, cardSettings, t }) =>
</Col> </Col>
); );
export default function ProductionBoardCard({ technician, card, bodyshop, cardSettings, clone }) { export default function ProductionBoardCard({ technician, card, bodyshop, cardSettings }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { metadata } = card; const { metadata } = card;
const employees = useMemo(() => bodyshop.employees, [bodyshop.employees]); const employees = useMemo(() => bodyshop.employees, [bodyshop.employees]);
const { employee_body, employee_prep, employee_refinish, employee_csr } = useMemo(() => { const { employee_body, employee_prep, employee_refinish, employee_csr } = useMemo(() => {
return { return {
employee_body: metadata?.employee_body && findEmployeeById(employees, metadata.employee_body), employee_body: metadata?.employee_body && findEmployeeById(employees, metadata.employee_body),
@@ -314,7 +324,6 @@ export default function ProductionBoardCard({ technician, card, bodyshop, cardSe
employee_csr: metadata?.employee_csr && findEmployeeById(employees, metadata.employee_csr) employee_csr: metadata?.employee_csr && findEmployeeById(employees, metadata.employee_csr)
}; };
}, [metadata, employees]); }, [metadata, employees]);
const pastDueAlert = useMemo(() => { const pastDueAlert = useMemo(() => {
if (!metadata?.scheduled_completion) return null; if (!metadata?.scheduled_completion) return null;
const completionDate = dayjs(metadata.scheduled_completion); const completionDate = dayjs(metadata.scheduled_completion);
@@ -322,16 +331,13 @@ export default function ProductionBoardCard({ technician, card, bodyshop, cardSe
if (dayjs().add(1, "day").isSame(completionDate, "day")) return "production-completion-soon"; if (dayjs().add(1, "day").isSame(completionDate, "day")) return "production-completion-soon";
return null; return null;
}, [metadata?.scheduled_completion]); }, [metadata?.scheduled_completion]);
const totalHrs = useMemo(() => { const totalHrs = useMemo(() => {
return metadata?.labhrs && metadata?.larhrs return metadata?.labhrs && metadata?.larhrs
? metadata.labhrs.aggregate.sum.mod_lb_hrs + metadata.larhrs.aggregate.sum.mod_lb_hrs ? metadata.labhrs.aggregate.sum.mod_lb_hrs + metadata.larhrs.aggregate.sum.mod_lb_hrs
: 0; : 0;
}, [metadata?.labhrs, metadata?.larhrs]); }, [metadata?.labhrs, metadata?.larhrs]);
const bgColor = useMemo(() => cardColor(bodyshop.ssbuckets, totalHrs), [bodyshop.ssbuckets, totalHrs]); const bgColor = useMemo(() => cardColor(bodyshop.ssbuckets, totalHrs), [bodyshop.ssbuckets, totalHrs]);
const contrastYIQ = useMemo(() => getContrastYIQ(bgColor), [bgColor]); const contrastYIQ = useMemo(() => getContrastYIQ(bgColor), [bgColor]);
const isBodyEmpty = useMemo(() => { const isBodyEmpty = useMemo(() => {
return !( return !(
cardSettings?.ownr_nm || cardSettings?.ownr_nm ||
@@ -413,8 +419,10 @@ export default function ProductionBoardCard({ technician, card, bodyshop, cardSe
className={`react-trello-card ${cardSettings.kiosk ? "kiosk-mode" : ""}`} className={`react-trello-card ${cardSettings.kiosk ? "kiosk-mode" : ""}`}
size="small" size="small"
style={{ style={{
backgroundColor: cardSettings?.cardcolor && `rgba(${bgColor.r},${bgColor.g},${bgColor.b},${bgColor.a})`, backgroundColor: cardSettings?.cardcolor
color: cardSettings?.cardcolor && contrastYIQ ? bgColor.fallback || `rgba(${bgColor.r},${bgColor.g},${bgColor.b},${bgColor.a || 1})`
: "var(--card-bg-fallback)",
color: cardSettings?.cardcolor ? contrastYIQ : "var(--card-text-fallback)"
}} }}
title={!isBodyEmpty ? headerContent : null} title={!isBodyEmpty ? headerContent : null}
extra={ extra={

View File

@@ -11,7 +11,7 @@
} }
.share-to-teams-badge { .share-to-teams-badge {
background-color: #cccccc; background-color: var(--share-badge-bg);
border-radius: 50%; border-radius: 50%;
width: 24px; width: 24px;
height: 24px; height: 24px;
@@ -23,7 +23,7 @@
.react-trello-column-header { .react-trello-column-header {
font-weight: bold; font-weight: bold;
cursor: pointer; cursor: pointer;
background-color: #d0d0d0; background-color: var(--column-header-bg);
border-radius: 5px 5px 0 0; border-radius: 5px 5px 0 0;
} }
@@ -31,13 +31,14 @@
background: transparent; background: transparent;
border: none; border: none;
} }
.react-trello-footer { .react-trello-footer {
background-color: #d0d0d0; background-color: var(--footer-bg);
border-radius: 0 0 5px 5px; border-radius: 0 0 5px 5px;
} }
.grid-item { .grid-item {
margin: 1px; // TODO: (Note) THis is where we set the margin for vertical margin: 1px; // TODO: (Note) This is where we set the margin for vertical
} }
.lane-title { .lane-title {
@@ -53,27 +54,33 @@
justify-content: center; justify-content: center;
align-items: center; align-items: center;
position: relative; position: relative;
.body-empty-container { .body-empty-container {
position: absolute; position: absolute;
right: 0; right: 0;
} }
.tech-container { .tech-container {
font-weight: bolder; font-weight: bolder;
text-align: center; text-align: center;
flex: 1; flex: 1;
.branches-outlined { .branches-outlined {
color: orangered; color: var(--tech-icon-color);
} }
} }
.inner-container { .inner-container {
display: flex; display: flex;
align-items: center; align-items: center;
position: absolute; position: absolute;
left: 0; left: 0;
.circle-outline { .circle-outline {
color: orangered; color: var(--tech-icon-color);
margin-left: 8px; margin-left: 8px;
} }
.iou-parent { .iou-parent {
margin-left: 8px; margin-left: 8px;
} }
@@ -81,6 +88,6 @@
} }
.clone.is-dragging .ant-card { .clone.is-dragging .ant-card {
border: #1890ff 2px solid !important; border: 2px solid var(--clone-border-color) !important;
border-radius: 12px; border-radius: 12px;
} }

View File

@@ -58,7 +58,7 @@ export const StyleHorizontal = styled.div`
height: 100%; height: 100%;
min-height: 1px; min-height: 1px;
overflow-y: visible; overflow-y: visible;
overflow-x: visible; // change this line overflow-x: visible;
} }
.react-trello-lane.lane-collapsed { .react-trello-lane.lane-collapsed {
@@ -85,17 +85,17 @@ export const StyleHorizontal = styled.div`
.react-trello-card { .react-trello-card {
height: auto; height: auto;
margin: 2px; margin: 2px 0 2px;
} }
.size-memory-wrapper { .size-memory-wrapper {
display: flex; /* This makes it a flex container */ display: flex;
flex-direction: column; /* Aligns children vertically */ flex-direction: column;
} }
.size-memory-wrapper .ant-card { .size-memory-wrapper .ant-card {
flex-grow: 1; /* Allows the card to expand to fill the available space */ flex-grow: 1;
width: 100%; /* Ensures the card stretches to fill the width of its parent */ width: 100%;
} }
`; `;
@@ -131,7 +131,7 @@ export const StyleVertical = styled.div`
.grid-item { .grid-item {
display: flex; display: flex;
width: ${(props) => props.gridItemWidth}; /* Use props to set width */ width: ${(props) => props.gridItemWidth};
align-content: stretch; align-content: stretch;
box-sizing: border-box; box-sizing: border-box;
} }
@@ -148,13 +148,13 @@ export const StyleVertical = styled.div`
} }
.size-memory-wrapper { .size-memory-wrapper {
display: flex; /* This makes it a flex container */ display: flex;
flex-direction: column; /* Aligns children vertically */ flex-direction: column;
} }
.size-memory-wrapper .ant-card { .size-memory-wrapper .ant-card {
flex-grow: 1; /* Allows the card to expand to fill the available space */ flex-grow: 1;
width: 100%; /* Ensures the card stretches to fill the width of its parent */ width: 100%;
} }
.react-trello-lane .lane-collapsed { .react-trello-lane .lane-collapsed {
@@ -163,7 +163,7 @@ export const StyleVertical = styled.div`
`; `;
export const BoardWrapper = styled.div` export const BoardWrapper = styled.div`
color: #393939; color: var(--board-text-color);
height: 100%; height: 100%;
overflow-x: auto; overflow-x: auto;
overflow-y: hidden; overflow-y: hidden;
@@ -171,7 +171,7 @@ export const BoardWrapper = styled.div`
`; `;
export const Section = styled.section` export const Section = styled.section`
background-color: #e3e3e3; background-color: var(--section-bg);
border-radius: 3px; border-radius: 3px;
margin: 2px 2px; margin: 2px 2px;
height: 100%; height: 100%;
@@ -197,6 +197,6 @@ export const ScrollableLane = styled.div`
export const Detail = styled.div` export const Detail = styled.div`
font-size: 12px; font-size: 12px;
color: #4d4d4d; color: var(--detail-text-color);
white-space: pre-wrap; white-space: pre-wrap;
`; `;

View File

@@ -28,7 +28,6 @@ const mapStateToProps = createStructuredSelector({
export function ProductionListTable({ loading, data, refetch, bodyshop, technician, currentUser }) { export function ProductionListTable({ loading, data, refetch, bodyshop, technician, currentUser }) {
const [searchText, setSearchText] = useState(""); const [searchText, setSearchText] = useState("");
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
const { const {
treatments: { Production_List_Status_Colors, Enhanced_Payroll } treatments: { Production_List_Status_Colors, Enhanced_Payroll }
} = useSplitTreatments({ } = useSplitTreatments({
@@ -36,10 +35,8 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
names: ["Production_List_Status_Colors", "Enhanced_Payroll"], names: ["Production_List_Status_Colors", "Enhanced_Payroll"],
splitKey: bodyshop.imexshopid splitKey: bodyshop.imexshopid
}); });
const assoc = bodyshop.associations.find((a) => a.useremail === currentUser.email); const assoc = bodyshop.associations.find((a) => a.useremail === currentUser.email);
const defaultView = assoc && assoc.default_prod_list_view; const defaultView = assoc && assoc.default_prod_list_view;
const initialStateRef = useRef( const initialStateRef = useRef(
(bodyshop.production_config && (bodyshop.production_config &&
bodyshop.production_config.find((p) => p.name === defaultView)?.columns.tableState) || bodyshop.production_config.find((p) => p.name === defaultView)?.columns.tableState) ||
@@ -48,7 +45,6 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
filteredInfo: { text: "" } filteredInfo: { text: "" }
} }
); );
const initialColumnsRef = useRef( const initialColumnsRef = useRef(
(initialStateRef.current && (initialStateRef.current &&
bodyshop?.production_config bodyshop?.production_config
@@ -69,12 +65,9 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
})) || })) ||
[] []
); );
const [state, setState] = useState(initialStateRef.current); const [state, setState] = useState(initialStateRef.current);
const [columns, setColumns] = useState(initialColumnsRef.current); const [columns, setColumns] = useState(initialColumnsRef.current);
const { t } = useTranslation(); const { t } = useTranslation();
const matchingColumnConfig = useMemo(() => { const matchingColumnConfig = useMemo(() => {
return bodyshop?.production_config?.find((p) => p.name === defaultView); return bodyshop?.production_config?.find((p) => p.name === defaultView);
}, [bodyshop.production_config, defaultView]); }, [bodyshop.production_config, defaultView]);
@@ -95,7 +88,6 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
width: k.width ?? 100 width: k.width ?? 100
}; };
}) || []; }) || [];
// Only update columns if they haven't been manually changed by the user // Only update columns if they haven't been manually changed by the user
if (_.isEqual(initialColumnsRef.current, columns)) { if (_.isEqual(initialColumnsRef.current, columns)) {
setColumns(newColumns); setColumns(newColumns);
@@ -126,11 +118,9 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
const onDragEnd = (fromIndex, toIndex) => { const onDragEnd = (fromIndex, toIndex) => {
if (fromIndex === toIndex) return; if (fromIndex === toIndex) return;
const columnsCopy = [...columns]; const columnsCopy = [...columns];
const [movedItem] = columnsCopy.splice(fromIndex, 1); const [movedItem] = columnsCopy.splice(fromIndex, 1);
columnsCopy.splice(toIndex, 0, movedItem); columnsCopy.splice(toIndex, 0, movedItem);
if (!_.isEqual(columnsCopy, columns)) { if (!_.isEqual(columnsCopy, columns)) {
setColumns(columnsCopy); setColumns(columnsCopy);
setHasUnsavedChanges(true); setHasUnsavedChanges(true);
@@ -140,7 +130,6 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
const removeColumn = (e) => { const removeColumn = (e) => {
const { key } = e; const { key } = e;
const newColumns = columns.filter((i) => i.key !== key); const newColumns = columns.filter((i) => i.key !== key);
if (!_.isEqual(newColumns, columns)) { if (!_.isEqual(newColumns, columns)) {
setColumns(newColumns); setColumns(newColumns);
setHasUnsavedChanges(true); setHasUnsavedChanges(true);
@@ -155,7 +144,6 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
...nextColumns[index], ...nextColumns[index],
width: size.width width: size.width
}; };
if (!_.isEqual(nextColumns, columns)) { if (!_.isEqual(nextColumns, columns)) {
setColumns(nextColumns); setColumns(nextColumns);
setHasUnsavedChanges(true); setHasUnsavedChanges(true);
@@ -180,7 +168,6 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
} }
] ]
}; };
return ( return (
<Dropdown className="prod-header-dropdown" menu={menu} trigger={["contextMenu"]}> <Dropdown className="prod-header-dropdown" menu={menu} trigger={["contextMenu"]}>
<span>{col.title}</span> <span>{col.title}</span>
@@ -206,13 +193,12 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
item.v_model_desc, item.v_model_desc,
item.v_make_desc item.v_make_desc
]; ];
return fieldsToSearch.some((field) => (field || "").toString().toLowerCase().includes(searchText.toLowerCase())); return fieldsToSearch.some((field) => (field || "").toString().toLowerCase().includes(searchText.toLowerCase()));
}; };
const dataSource = searchText === "" ? data : data.filter((j) => filterData(j, searchText)); const dataSource = searchText === "" ? data : data.filter((j) => filterData(j, searchText));
if (!!!columns) return <div>No columns found.</div>; if (!columns) return <div>No columns found.</div>;
const totalHrs = data const totalHrs = data
.reduce( .reduce(
@@ -236,7 +222,8 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
onClick={resetChanges} onClick={resetChanges}
style={{ style={{
cursor: "pointer", cursor: "pointer",
textDecoration: "underline" textDecoration: "underline",
color: "var(--reset-link-color)"
}} }}
> >
{t("general.actions.reset")} {t("general.actions.reset")}
@@ -269,7 +256,6 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
data={data} data={data}
onColumnAdd={addColumn} onColumnAdd={addColumn}
/> />
<ProductionListConfigManager <ProductionListConfigManager
columns={columns} columns={columns}
setColumns={setColumns} setColumns={setColumns}
@@ -305,24 +291,22 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
{...(Production_List_Status_Colors.treatment === "on" && { {...(Production_List_Status_Colors.treatment === "on" && {
onRow: (record, index) => { onRow: (record, index) => {
if (!bodyshop.md_ro_statuses.production_colors) return null; if (!bodyshop.md_ro_statuses.production_colors) return null;
const color = bodyshop.md_ro_statuses.production_colors.find((x) => x.status === record.status); const color = bodyshop.md_ro_statuses.production_colors.find((x) => x.status === record.status);
if (!color) { if (!color) {
if (index % 2 === 0) if (index % 2 === 0)
return { return {
style: { style: {
backgroundColor: `rgb(236, 236, 236)` backgroundColor: "var(--table-row-even-bg)"
} }
}; };
return null; return null;
} }
return { return {
className: "rowWithColor", className: "rowWithColor",
style: { style: {
"--bgColor": `rgb(${color.color.r},${color.color.g},${color.color.b},${color.color.a})` "--bgColor": color.color
? `rgba(${color.color.r},${color.color.g},${color.color.b},${color.color.a || 1})`
: "var(--status-row-bg-fallback)"
} }
}; };
} }

View File

@@ -19,11 +19,11 @@
// } // }
.imex-event-arrived { .imex-event-arrived {
background-color: rgba(4, 141, 4, 0.4); background-color: var(--event-arrived-bg);
} }
.imex-event-block { .imex-event-block {
background-color: rgba(212, 2, 2, 0.6); background-color: var(--event-block-bg);
} }
.rbc-month-view { .rbc-month-view {
@@ -31,12 +31,12 @@
} }
.rbc-event.rbc-selected { .rbc-event.rbc-selected {
background-color: slategrey; background-color: var(--event-selected-bg);
} }
.imex-calendar-load { .imex-calendar-load {
max-width: 12rem; max-width: 12rem;
position: relative; position: relative;
left: 50%; left: 50%;
transform: translateX(-50%); transform: translate(-50%);
} }

View File

@@ -36,12 +36,14 @@ export function ScheduleCalendarWrapperComponent({
const search = queryString.parse(useLocation().search); const search = queryString.parse(useLocation().search);
const history = useNavigate(); const history = useNavigate();
const { t } = useTranslation(); const { t } = useTranslation();
const handleEventPropStyles = (event, start, end, isSelected) => { const handleEventPropStyles = (event, start, end, isSelected) => {
return { return {
...(event.color && !((search.view || defaultView) === "agenda") ...(event.color && !((search.view || defaultView) === "agenda")
? { ? {
style: { style: {
backgroundColor: event.color && event.color.hex ? event.color.hex : event.color backgroundColor:
event.color && event.color.hex ? event.color.hex : event.color || "var(--event-bg-fallback)"
} }
} }
: {}), : {}),
@@ -60,7 +62,9 @@ export function ScheduleCalendarWrapperComponent({
<Collapse style={{ marginBottom: "5px" }}> <Collapse style={{ marginBottom: "5px" }}>
<Collapse.Panel <Collapse.Panel
key="1" key="1"
header={<span style={{ color: "tomato" }}>{t("appointments.labels.severalerrorsfound")}</span>} header={
<span style={{ color: "var(--error-header-text)" }}>{t("appointments.labels.severalerrorsfound")}</span>
}
> >
<Space direction="vertical" style={{ width: "100%" }}> <Space direction="vertical" style={{ width: "100%" }}>
{problemJobs.map((problem) => ( {problemJobs.map((problem) => (
@@ -70,7 +74,7 @@ export function ScheduleCalendarWrapperComponent({
message={ message={
<Trans <Trans
i18nKey="appointments.labels.dataconsistency" i18nKey="appointments.labels.dataconsistency"
components={[<Link to={`/manage/jobs/${problem.id}`} target="_blank" />]} components={[<Link key={problem.id} to={`/manage/jobs/${problem.id}`} target="_blank" />]}
values={{ values={{
ro_number: problem.ro_number, ro_number: problem.ro_number,
code: problem.code code: problem.code
@@ -91,7 +95,7 @@ export function ScheduleCalendarWrapperComponent({
message={ message={
<Trans <Trans
i18nKey="appointments.labels.dataconsistency" i18nKey="appointments.labels.dataconsistency"
components={[<Link to={`/manage/jobs/${problem.id}`} target="_blank" />]} components={[<Link key={problem.id} to={`/manage/jobs/${problem.id}`} target="_blank" />]}
values={{ values={{
ro_number: problem.ro_number, ro_number: problem.ro_number,
code: problem.code code: problem.code
@@ -102,12 +106,11 @@ export function ScheduleCalendarWrapperComponent({
))} ))}
</Space> </Space>
))} ))}
<Calendar <Calendar
events={data} events={data}
defaultView={search.view || defaultView || "week"} defaultView={search.view || defaultView || "week"}
date={selectedDate} date={selectedDate}
onNavigate={(date, view, action) => { onNavigate={(date) => {
search.date = date.toISOString().substr(0, 10); search.date = date.toISOString().substr(0, 10);
history({ search: queryString.stringify(search) }); history({ search: queryString.stringify(search) });
}} }}

View File

@@ -2,7 +2,7 @@ import { SyncOutlined } from "@ant-design/icons";
import { Button, Card, Checkbox, Col, Row, Select, Space } from "antd"; import { Button, Card, Checkbox, Col, Row, Select, Space } from "antd";
import { PageHeader } from "@ant-design/pro-layout"; import { PageHeader } from "@ant-design/pro-layout";
import { t } from "i18next"; import { t } from "i18next";
import React, { useMemo } from "react"; import { useMemo } from "react";
import useLocalStorage from "../../utils/useLocalStorage"; import useLocalStorage from "../../utils/useLocalStorage";
import ScheduleAtsSummary from "../schedule-ats-summary/schedule-ats-summary.component"; import ScheduleAtsSummary from "../schedule-ats-summary/schedule-ats-summary.component";
import ScheduleCalendarWrapperComponent from "../schedule-calendar-wrapper/scheduler-calendar-wrapper.component"; import ScheduleCalendarWrapperComponent from "../schedule-calendar-wrapper/scheduler-calendar-wrapper.component";
@@ -18,7 +18,7 @@ import _ from "lodash";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop bodyshop: selectBodyshop
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = () => ({
//setUserLanguage: language => dispatch(setUserLanguage(language)) //setUserLanguage: language => dispatch(setUserLanguage(language))
}); });
export default connect(mapStateToProps, mapDispatchToProps)(ScheduleCalendarComponent); export default connect(mapStateToProps, mapDispatchToProps)(ScheduleCalendarComponent);

View File

@@ -1,6 +1,6 @@
import { useQuery } from "@apollo/client"; import { useQuery } from "@apollo/client";
import queryString from "query-string"; import queryString from "query-string";
import React, { useEffect, useMemo } from "react"; import { useEffect, useMemo } from "react";
import { useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";
import { QUERY_ALL_ACTIVE_APPOINTMENTS } from "../../graphql/appointments.queries"; import { QUERY_ALL_ACTIVE_APPOINTMENTS } from "../../graphql/appointments.queries";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
@@ -32,7 +32,7 @@ export function ScheduleCalendarContainer({ calculateScheduleLoad }) {
startd: range.start, startd: range.start,
endd: range.end endd: range.end
}, },
skip: !!!range.start || !!!range.end, skip: !range.start || !range.end,
fetchPolicy: "network-only", fetchPolicy: "network-only",
nextFetchPolicy: "network-only" nextFetchPolicy: "network-only"
}); });

View File

@@ -5,26 +5,25 @@ const CustomTooltip = ({ active, payload, label }) => {
return ( return (
<div <div
style={{ style={{
backgroundColor: "white", backgroundColor: "var(--tooltip-bg)",
border: "1px solid gray", border: "1px solid var(--tooltip-border)",
padding: "0.5rem" padding: "0.5rem"
}} }}
> >
<p style={{ margin: "0" }}>{label}</p> <p style={{ margin: "0" }}>{label}</p>
{payload.map((data, index) => { {payload.map((data, index) => {
const textColor = data.color || "var(--tooltip-text-fallback)";
if (data.dataKey === "sales" || data.dataKey === "accSales") if (data.dataKey === "sales" || data.dataKey === "accSales")
return ( return (
<p style={{ margin: "10px 0", color: data.color }} key={index}>{`${data.name} : ${Dinero({ <p style={{ margin: "10px 0", color: textColor }} key={index}>{`${data.name} : ${Dinero({
amount: Math.round(data.value * 100) amount: Math.round(data.value * 100)
}).toFormat()}`}</p> }).toFormat()}`}</p>
); );
return <p style={{ margin: "10px 0", color: textColor }} key={index}>{`${data.name} : ${data.value}`}</p>;
return <p style={{ margin: "10px 0", color: data.color }} key={index}>{`${data.name} : ${data.value}`}</p>;
})} })}
</div> </div>
); );
} }
return null; return null;
}; };

View File

@@ -3,15 +3,16 @@ const CustomTooltip = ({ active, payload, label }) => {
return ( return (
<div <div
style={{ style={{
backgroundColor: "white", backgroundColor: "var(--tooltip-bg)",
border: "1px solid gray", border: "1px solid var(--tooltip-border)",
padding: "0.5rem" padding: "0.5rem"
}} }}
> >
<p style={{ margin: "0" }}>{label}</p> <p style={{ margin: "0" }}>{label}</p>
{payload.map((data, index) => { {payload.map((data, index) => {
const textColor = data.color || "var(--tooltip-text-fallback)";
return ( return (
<p style={{ margin: "10px 0", color: data.color }} key={index}>{`${ <p style={{ margin: "10px 0", color: textColor }} key={index}>{`${
data.name data.name
} : ${data.value.toFixed(1)}`}</p> } : ${data.value.toFixed(1)}`}</p>
); );
@@ -19,7 +20,6 @@ const CustomTooltip = ({ active, payload, label }) => {
</div> </div>
); );
} }
return null; return null;
}; };

View File

@@ -41,7 +41,6 @@ const ShareToTeamsComponent = ({
}) => { }) => {
const location = useLocation(); const location = useLocation();
const { t } = useTranslation(); const { t } = useTranslation();
const currentUrl = const currentUrl =
urlOverride || urlOverride ||
encodeURIComponent(`${window.location.origin}${location.pathname}${location.search}${location.hash}`); encodeURIComponent(`${window.location.origin}${location.pathname}${location.search}${location.hash}`);
@@ -49,31 +48,24 @@ const ShareToTeamsComponent = ({
pageTitleOverride || pageTitleOverride ||
encodeURIComponent(typeof document !== "undefined" ? document.title : t("general.actions.sharetoteams")); encodeURIComponent(typeof document !== "undefined" ? document.title : t("general.actions.sharetoteams"));
const messageText = messageTextOverride || encodeURIComponent(t("general.actions.sharetoteams")); const messageText = messageTextOverride || encodeURIComponent(t("general.actions.sharetoteams"));
// Construct the Teams share URL with parameters // Construct the Teams share URL with parameters
const teamsShareUrl = `https://teams.microsoft.com/share?href=${currentUrl}&preText=${messageText}&title=${pageTitle}`; const teamsShareUrl = `https://teams.microsoft.com/share?href=${currentUrl}&preText=${messageText}&title=${pageTitle}`;
// Function to open the centered share link in a new window/tab // Function to open the centered share link in a new window/tab
const handleShare = () => { const handleShare = () => {
const screenWidth = window.screen.width; const screenWidth = window.screen.width;
const screenHeight = window.screen.height; const screenHeight = window.screen.height;
const windowWidth = 600; const windowWidth = 600;
const windowHeight = 400; const windowHeight = 400;
const left = screenWidth / 2 - windowWidth / 2; const left = screenWidth / 2 - windowWidth / 2;
const top = screenHeight / 2 - windowHeight / 2; const top = screenHeight / 2 - windowHeight / 2;
const windowFeatures = `width=${windowWidth},height=${windowHeight},left=${left},top=${top}`; const windowFeatures = `width=${windowWidth},height=${windowHeight},left=${left},top=${top}`;
// noinspection JSIgnoredPromiseFromCall // noinspection JSIgnoredPromiseFromCall
window.open(teamsShareUrl, "_blank", windowFeatures); window.open(teamsShareUrl, "_blank", windowFeatures);
}; };
// Feature is disabled // Feature is disabled
if (!bodyshop?.md_functionality_toggles?.teams) { if (!bodyshop?.md_functionality_toggles?.teams) {
return null; return null;
} }
if (noIcon) { if (noIcon) {
return ( return (
<div style={{ cursor: "pointer", ...noIconStyle }} onClick={handleShare}> <div style={{ cursor: "pointer", ...noIconStyle }} onClick={handleShare}>
@@ -81,16 +73,15 @@ const ShareToTeamsComponent = ({
</div> </div>
); );
} }
return ( return (
<Button <Button
style={{ style={{
backgroundColor: "#6264A7", backgroundColor: "var(--teams-button-bg)",
borderColor: "#6264A7", borderColor: "var(--teams-button-border)",
color: "#FFFFFF", color: "var(--teams-button-text)",
...buttonStyle ...buttonStyle
}} }}
icon={<PiMicrosoftTeamsLogo style={{ color: "#FFFFFF", ...buttonIconStyle }} />} icon={<PiMicrosoftTeamsLogo style={{ color: "var(--teams-button-text)", ...buttonIconStyle }} />}
onClick={handleShare} onClick={handleShare}
title={linkText === null ? t("general.actions.sharetoteams") : linkText} title={linkText === null ? t("general.actions.sharetoteams") : linkText}
/> />

View File

@@ -51,7 +51,7 @@ export const useFormDataPreservation = (form, bodyshop, featureConfig) => {
if (hasDataToPreserve) { if (hasDataToPreserve) {
form.setFieldsValue(preservationData); form.setFieldsValue(preservationData);
} }
}, [form, bodyshop, featureConfig]); }, [form, featureConfig, bodyshop]);
const getCompleteFormValues = () => { const getCompleteFormValues = () => {
const currentFormValues = form.getFieldsValue(); const currentFormValues = form.getFieldsValue();

View File

@@ -4,9 +4,9 @@
right: 0; right: 0;
width: 500px; width: 500px;
max-width: 500px; max-width: 500px;
background: #fff; background: var(--task-bg);
color: rgba(0, 0, 0, 0.85); color: var(--task-text);
border: 1px solid #d9d9d9; border: 1px solid var(--task-border);
border-radius: 6px; border-radius: 6px;
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.08), 0 3px 6px rgba(0, 0, 0, 0.06); box-shadow: 0 6px 16px rgba(0, 0, 0, 0.08), 0 3px 6px rgba(0, 0, 0, 0.06);
z-index: 1000; z-index: 1000;
@@ -19,11 +19,11 @@
.task-header { .task-header {
padding: 4px 10px; padding: 4px 10px;
border-bottom: 1px solid #f0f0f0; border-bottom: 1px solid var(--task-header-border);
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
background: #fafafa; background: var(--task-header-bg);
h3 { h3 {
font-size: 14px; font-size: 14px;
@@ -32,14 +32,14 @@
.create-task-button { .create-task-button {
border: none; border: none;
color: white; color: var(--task-button-text);
padding: 4px 12px; padding: 4px 12px;
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
font-weight: 500; font-weight: 500;
&:hover { &:hover {
background-color: #40a9ff; background-color: var(--task-button-hover-bg);
} }
} }
} }
@@ -52,10 +52,9 @@
.section-title { .section-title {
padding: 0px 10px; padding: 0px 10px;
margin: 0px; margin: 0px;
//font-size: 12px; background: var(--task-section-bg);
background: #f5f5f5;
font-weight: 650; font-weight: 650;
border-bottom: 1px solid #e8e8e8; border-bottom: 1px solid var(--task-section-border);
position: sticky; position: sticky;
top: 0; top: 0;
z-index: 1; z-index: 1;
@@ -68,22 +67,21 @@
.task-row { .task-row {
cursor: pointer; cursor: pointer;
border-bottom: 1px solid #f0f0f0; border-bottom: 1px solid var(--task-row-border);
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: flex-start; align-items: flex-start;
&:hover { &:hover {
background: #f5f5f5; background: var(--task-row-hover-bg);
} }
.task-title-cell { .task-title-cell {
flex: 1; flex: 1;
padding: 6px 8px; padding: 6px 8px;
vertical-align: top; vertical-align: top;
//font-size: 12px;
line-height: 1.2; line-height: 1.2;
max-width: 350px; // or whatever fits your layout max-width: 350px;
.task-title { .task-title {
font-size: 16px; font-size: 16px;
@@ -91,44 +89,42 @@
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
max-width: 100%; // Or a specific width if you want more control max-width: 100%;
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;
} }
.task-ro-number { .task-ro-number {
margin-top: 20px; margin-top: 20px;
color: #1677ff; color: var(--task-ro-number);
} }
} }
.task-due-cell { .task-due-cell {
padding: 6px 8px; padding: 6px 8px;
vertical-align: top; vertical-align: top;
//font-size: 12px;
line-height: 1.2; line-height: 1.2;
text-align: right; text-align: right;
white-space: nowrap; white-space: nowrap;
color: rgba(0, 0, 0, 0.45); color: var(--task-due-text);
} }
} }
button { button {
margin: 8px auto; margin: 8px auto;
padding: 4px 10px; padding: 4px 10px;
background-color: #1677ff; background-color: var(--task-button-bg);
color: white; color: var(--task-button-text);
border: none; border: none;
border-radius: 4px; border-radius: 4px;
//font-size: 12px;
cursor: pointer; cursor: pointer;
&:hover { &:hover {
background-color: #4096ff; background-color: var(--task-button-hover-bg);
} }
&:disabled { &:disabled {
background-color: #d9d9d9; background-color: var(--task-button-disabled-bg);
cursor: not-allowed; cursor: not-allowed;
} }
} }
@@ -137,7 +133,7 @@
.error-message { .error-message {
padding: 16px; padding: 16px;
text-align: center; text-align: center;
color: rgba(0, 0, 0, 0.45); color: var(--task-message-text);
} }
.loading-footer { .loading-footer {

View File

@@ -6,7 +6,7 @@
td { td {
padding: 8px; padding: 8px;
text-align: left; text-align: left;
border-bottom: 1px solid #ddd; border-bottom: 1px solid var(--table-border-color);
.ant-form-item { .ant-form-item {
margin-bottom: 0px !important; margin-bottom: 0px !important;
@@ -14,6 +14,6 @@
} }
tr:hover { tr:hover {
background-color: #f5f5f5; background-color: var(--table-hover-bg);
} }
} }

View File

@@ -11,7 +11,7 @@ import {
} from "@ant-design/icons"; } from "@ant-design/icons";
import { Button, Card, Result } from "antd"; import { Button, Card, Result } from "antd";
import i18n from "i18next"; import i18n from "i18next";
import React, { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { store } from "../../redux/store.js"; import { store } from "../../redux/store.js";
@@ -21,7 +21,6 @@ import "./upsell.styles.scss";
export default function UpsellComponent({ featureName, subFeatureName, upsell, disableMask }) { export default function UpsellComponent({ featureName, subFeatureName, upsell, disableMask }) {
const { t } = useTranslation(); const { t } = useTranslation();
const resultProps = upsell || upsellEnum[featureName][subFeatureName]; const resultProps = upsell || upsellEnum[featureName][subFeatureName];
const componentRef = useRef(null); const componentRef = useRef(null);
useEffect(() => { useEffect(() => {
@@ -34,12 +33,10 @@ export default function UpsellComponent({ featureName, subFeatureName, upsell, d
mask.style.left = 0; mask.style.left = 0;
mask.style.width = "100%"; mask.style.width = "100%";
mask.style.height = "100%"; mask.style.height = "100%";
mask.style.backgroundColor = "rgba(0, 0, 0, 0.05)"; mask.style.backgroundColor = "var(--mask-bg)";
// mask.style.zIndex = 9999; // mask.style.zIndex = 9999;
parentElement.style.position = "relative"; parentElement.style.position = "relative";
parentElement.prepend(mask); parentElement.prepend(mask);
return () => { return () => {
parentElement.removeChild(mask); parentElement.removeChild(mask);
}; };
@@ -47,18 +44,22 @@ export default function UpsellComponent({ featureName, subFeatureName, upsell, d
}, [disableMask]); }, [disableMask]);
if (!resultProps) return <Result status="info" title={t("upsell.messages.generic")} />; if (!resultProps) return <Result status="info" title={t("upsell.messages.generic")} />;
return ( return (
<div ref={componentRef}> <div ref={componentRef}>
<Result status="info" icon={<AppstoreAddOutlined />} {...resultProps} /> <Result status="info" icon={<AppstoreAddOutlined />} {...resultProps} />
</div> </div>
); );
} }
//Kept in the same function as the result props line must mirror and doesnt warrant a separate function. //Kept in the same function as the result props line must mirror and doesnt warrant a separate function.
export function UpsellMaskWrapper({ children, upsell, featureName, subFeatureName }) { export function UpsellMaskWrapper({ children, upsell, featureName, subFeatureName }) {
const resultProps = upsell || upsellEnum[featureName][subFeatureName]; const resultProps = upsell || upsellEnum[featureName][subFeatureName];
return ( return (
<div className="mask-wrapper"> <div className="mask-wrapper">
<div className="mask-content">{children}</div> <div className="mask-content" style={{ backgroundColor: "var(--mask-bg)" }}>
{children}
</div>
<div className="mask-overlay"> <div className="mask-overlay">
<Card size="small"> <Card size="small">
<Result status="info" icon={<AppstoreAddOutlined />} {...resultProps} /> <Result status="info" icon={<AppstoreAddOutlined />} {...resultProps} />
@@ -71,7 +72,6 @@ export function UpsellMaskWrapper({ children, upsell, featureName, subFeatureNam
//This is kept in this function as pulling it out into it's own util/enum prevents passing JSX as an `extra` prop //This is kept in this function as pulling it out into it's own util/enum prevents passing JSX as an `extra` prop
export const upsellEnum = () => { export const upsellEnum = () => {
const { currentUser, bodyshop } = store.getState().user; const { currentUser, bodyshop } = store.getState().user;
const [first_name, ...last_name] = currentUser?.displayName ? currentUser.displayName.split(" ") : []; const [first_name, ...last_name] = currentUser?.displayName ? currentUser.displayName.split(" ") : [];
const LearnMoreLink = encodeURI( const LearnMoreLink = encodeURI(
InstanceRenderManager({ InstanceRenderManager({
@@ -79,7 +79,6 @@ export const upsellEnum = () => {
rome: `https://forms.zohopublic.com/rometech/form/ROLearnMore/formperma/0G29z8LgLlvKK8nno-b7s-GHgNXwIFlrMeE0mC394L4?first_name=${first_name || ""}&last_name=${last_name.join(" ") || ""}&shop_name=${bodyshop?.shopname || ""}&email=${currentUser?.email || ""}&shop_phone=${bodyshop?.phone || ""}` rome: `https://forms.zohopublic.com/rometech/form/ROLearnMore/formperma/0G29z8LgLlvKK8nno-b7s-GHgNXwIFlrMeE0mC394L4?first_name=${first_name || ""}&last_name=${last_name.join(" ") || ""}&shop_name=${bodyshop?.shopname || ""}&email=${currentUser?.email || ""}&shop_phone=${bodyshop?.phone || ""}`
}) })
); );
return { return {
bills: { bills: {
autoreconcile: { autoreconcile: {

View File

@@ -1,6 +1,5 @@
.mask-wrapper { .mask-wrapper {
position: relative; position: relative;
//Newly added
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@@ -8,12 +7,8 @@
} }
.mask-content { .mask-content {
// filter: blur(5px); background-color: var(--mask-content-bg);
background-color: rgba(0, 0, 0, 0.05);
pointer-events: none; pointer-events: none;
//Newly added
//width: 100%;
} }
.mask-overlay { .mask-overlay {
@@ -22,35 +17,8 @@
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
z-index: 10; z-index: 10;
// width: 100%
} }
.mask-overlay .ant-card { .mask-overlay .ant-card {
max-width: 100%; max-width: 100%;
} }
// .mask-wrapper {
// position: relative;
// display: inline-block;
// }
// .mask-content {
// filter: blur(5px);
// pointer-events: none;
// }
// .mask-overlay {
// position: absolute;
// top: 0;
// left: 0;
// width: 100%;
// height: 100%;
// display: flex;
// justify-content: center;
// align-items: center;
// z-index: 10;
// }
// .mask-overlay .ant-card {
// max-width: 100%;
// }

View File

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

View File

@@ -1,4 +1,3 @@
import React from "react";
import ScheduleCalendarContainer from "../../components/schedule-calendar/schedule-calendar.container"; import ScheduleCalendarContainer from "../../components/schedule-calendar/schedule-calendar.container";
export default function SchedulePageComponent() { 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 { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";

View File

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

View File

@@ -77,6 +77,14 @@ export const setWssStatus = (status) => ({
type: ApplicationActionTypes.SET_WSS_STATUS, type: ApplicationActionTypes.SET_WSS_STATUS,
payload: status payload: status
}); });
export const toggleDarkMode = () => ({
type: ApplicationActionTypes.TOGGLE_DARK_MODE
});
export const setDarkMode = (value) => ({
type: ApplicationActionTypes.SET_DARK_MODE,
payload: value
});
export const setIsPartsEntry = (isParts) => ({ export const setIsPartsEntry = (isParts) => ({
type: ApplicationActionTypes.PARTS_ENTRY, type: ApplicationActionTypes.PARTS_ENTRY,

View File

@@ -16,7 +16,8 @@ const INITIAL_STATE = {
}, },
jobReadOnly: false, jobReadOnly: false,
partnerVersion: null, partnerVersion: null,
alerts: {} alerts: {},
darkMode: false
}; };
const applicationReducer = (state = INITIAL_STATE, action) => { const applicationReducer = (state = INITIAL_STATE, action) => {
@@ -104,6 +105,16 @@ const applicationReducer = (state = INITIAL_STATE, action) => {
alerts: newAlertsMap alerts: newAlertsMap
}; };
} }
case ApplicationActionTypes.TOGGLE_DARK_MODE:
return {
...state,
darkMode: !state.darkMode
};
case ApplicationActionTypes.SET_DARK_MODE:
return {
...state,
darkMode: action.payload
};
case ApplicationActionTypes.PARTS_ENTRY: case ApplicationActionTypes.PARTS_ENTRY:
return { return {
...state, ...state,

View File

@@ -25,3 +25,4 @@ export const selectUpdateAvailable = createSelector([selectApplication], (applic
export const selectWssStatus = createSelector([selectApplication], (application) => application.wssStatus); export const selectWssStatus = createSelector([selectApplication], (application) => application.wssStatus);
export const selectAlerts = createSelector([selectApplication], (application) => application.alerts); export const selectAlerts = createSelector([selectApplication], (application) => application.alerts);
export const selectIsPartsEntry = createSelector([selectApplication], (application) => application.isPartsEntry); export const selectIsPartsEntry = createSelector([selectApplication], (application) => application.isPartsEntry);
export const selectDarkMode = createSelector([selectApplication], (application) => application.darkMode);

View File

@@ -15,6 +15,8 @@ const ApplicationActionTypes = {
SET_UPDATE_AVAILABLE: "SET_UPDATE_AVAILABLE", SET_UPDATE_AVAILABLE: "SET_UPDATE_AVAILABLE",
SET_WSS_STATUS: "SET_WSS_STATUS", SET_WSS_STATUS: "SET_WSS_STATUS",
ADD_ALERTS: "ADD_ALERTS", ADD_ALERTS: "ADD_ALERTS",
PARTS_ENTRY: "PARTS_ENTRY" PARTS_ENTRY: "PARTS_ENTRY",
TOGGLE_DARK_MODE: "TOGGLE_DARK_MODE",
SET_DARK_MODE: "SET_DARK_MODE"
}; };
export default ApplicationActionTypes; export default ApplicationActionTypes;

View File

@@ -3787,7 +3787,9 @@
"actions": { "actions": {
"changepassword": "Change Password", "changepassword": "Change Password",
"signout": "Sign Out", "signout": "Sign Out",
"updateprofile": "Update Profile" "updateprofile": "Update Profile",
"light_theme": "Switch to Light Theme",
"dark_theme": "Switch to Dark Theme"
}, },
"errors": { "errors": {
"updating": "Error updating user or association {{message}}" "updating": "Error updating user or association {{message}}"

View File

@@ -3787,7 +3787,9 @@
"actions": { "actions": {
"changepassword": "", "changepassword": "",
"signout": "desconectar", "signout": "desconectar",
"updateprofile": "Actualización del perfil" "updateprofile": "Actualización del perfil",
"light_theme": "",
"dark_theme": ""
}, },
"errors": { "errors": {
"updating": "" "updating": ""

View File

@@ -3787,7 +3787,9 @@
"actions": { "actions": {
"changepassword": "", "changepassword": "",
"signout": "Déconnexion", "signout": "Déconnexion",
"updateprofile": "Mettre à jour le profil" "updateprofile": "Mettre à jour le profil",
"light_theme": "",
"dark_theme": ""
}, },
"errors": { "errors": {
"updating": "" "updating": ""

View File

@@ -144,6 +144,7 @@ const paymentRefund = async (req, res) => {
logger.log("intellipay-refund-success", "DEBUG", req.user?.email, null, { logger.log("intellipay-refund-success", "DEBUG", req.user?.email, null, {
requestOptions: options, requestOptions: options,
response: response?.data,
...logResponseMeta ...logResponseMeta
}); });