Merge remote-tracking branch 'origin/release/2025-08-15' into feature/IO-3255-simplified-part-management, resolve conflicts
This commit is contained in:
@@ -2,15 +2,19 @@ import { ApolloProvider } from "@apollo/client";
|
||||
import { SplitFactoryProvider, useSplitClient } from "@splitsoftware/splitio-react";
|
||||
import { ConfigProvider } from "antd";
|
||||
import enLocale from "antd/es/locale/en_US";
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useMemo } from "react";
|
||||
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 client from "../utils/GraphQLClient";
|
||||
import App from "./App";
|
||||
import * as Sentry from "@sentry/react";
|
||||
import themeProvider from "./themeProvider";
|
||||
import getTheme from "./themeProvider";
|
||||
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
|
||||
const config = {
|
||||
@@ -24,19 +28,54 @@ const config = {
|
||||
function SplitClientProvider({ children }) {
|
||||
const imexshopid = useSelector((state) => state.user.imexshopid); // Access imexshopid from Redux store
|
||||
const splitClient = useSplitClient({ key: imexshopid || "anon" }); // Use imexshopid or fallback to "anon"
|
||||
|
||||
useEffect(() => {
|
||||
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}`);
|
||||
}
|
||||
}, [splitClient, imexshopid]);
|
||||
|
||||
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 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 (
|
||||
<CookiesProvider>
|
||||
@@ -44,11 +83,10 @@ function AppContainer() {
|
||||
<ConfigProvider
|
||||
input={{ autoComplete: "new-password" }}
|
||||
locale={enLocale}
|
||||
theme={themeProvider}
|
||||
theme={theme}
|
||||
form={{
|
||||
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));
|
||||
|
||||
@@ -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";
|
||||
|
||||
.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
|
||||
// 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
|
||||
// Note: Monitor this in dark mode to ensure text visibility
|
||||
.ant-menu-submenu-title {
|
||||
color: rgba(255, 255, 255, 0.65) !important;
|
||||
color: var(--menu-submenu-text) !important;
|
||||
}
|
||||
|
||||
.imex-table-header {
|
||||
@@ -46,7 +256,7 @@
|
||||
}
|
||||
|
||||
.ellipses {
|
||||
display: inline-block; /* for em, a, span, etc (inline by default) */
|
||||
display: inline-block;
|
||||
text-overflow: ellipsis;
|
||||
width: calc(95%);
|
||||
overflow: hidden;
|
||||
@@ -60,23 +270,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ::-webkit-scrollbar-track {
|
||||
// -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
|
||||
// border-radius: 0.2rem;
|
||||
// background-color: #f5f5f5;
|
||||
// }
|
||||
// Scrollbar styles (uncomment if needed, updated for dark mode)
|
||||
::-webkit-scrollbar-track {
|
||||
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
|
||||
border-radius: 0.2rem;
|
||||
background-color: var(--table-stripe-bg);
|
||||
}
|
||||
|
||||
// ::-webkit-scrollbar {
|
||||
// width: 0.25rem;
|
||||
// max-height: 0.25rem;
|
||||
// background-color: #f5f5f5;
|
||||
// }
|
||||
::-webkit-scrollbar {
|
||||
width: 0.25rem;
|
||||
max-height: 0.25rem;
|
||||
background-color: var(--table-stripe-bg);
|
||||
}
|
||||
|
||||
// ::-webkit-scrollbar-thumb {
|
||||
// border-radius: 0.2rem;
|
||||
// -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
|
||||
// background-color: #188fff;
|
||||
// }
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius: 0.2rem;
|
||||
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
|
||||
background-color: var(--alert-color);
|
||||
}
|
||||
|
||||
.ant-input-number-input,
|
||||
.ant-input-number,
|
||||
@@ -88,28 +299,27 @@
|
||||
|
||||
.production-alert {
|
||||
animation: alertBlinker 1s linear infinite;
|
||||
color: blue;
|
||||
color: var(--alert-color);
|
||||
}
|
||||
|
||||
@keyframes alertBlinker {
|
||||
50% {
|
||||
color: red;
|
||||
color: var(--completion-past-color);
|
||||
opacity: 100;
|
||||
//opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.blue {
|
||||
color: blue;
|
||||
color: var(--alert-color);
|
||||
}
|
||||
|
||||
.production-completion-soon {
|
||||
color: rgba(255, 140, 0, 0.8);
|
||||
color: var(--completion-soon-color);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.production-completion-past {
|
||||
color: rgba(255, 0, 0, 0.8);
|
||||
color: var(--completion-past-color);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@@ -139,7 +349,7 @@
|
||||
}
|
||||
|
||||
.react-kanban-column {
|
||||
background-color: #ddd !important;
|
||||
background-color: var(--kanban-column-bg) !important;
|
||||
}
|
||||
|
||||
.production-list-table {
|
||||
@@ -151,18 +361,18 @@
|
||||
.ReactGridGallery_tile-icon-bar {
|
||||
div {
|
||||
svg {
|
||||
fill: #1890ff;
|
||||
fill: var(--alert-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.job-line-manual {
|
||||
color: tomato;
|
||||
color: var(--job-line-manual-color);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.ant-table-tbody > tr.ant-table-row:nth-child(2n) > td {
|
||||
background-color: #f4f4f4;
|
||||
background-color: var(--table-stripe-bg);
|
||||
}
|
||||
|
||||
.rowWithColor > td {
|
||||
@@ -170,15 +380,15 @@
|
||||
}
|
||||
|
||||
.muted-button {
|
||||
color: lightgray;
|
||||
color: var(--muted-button-color);
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
font-size: 16px; /* Adjust as needed */
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.muted-button:hover {
|
||||
color: darkgrey;
|
||||
color: var(--muted-button-hover-color);
|
||||
}
|
||||
|
||||
.notification-alert-unordered-list {
|
||||
@@ -194,3 +404,19 @@
|
||||
.content-container {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,36 +4,42 @@ import InstanceRenderMgr from "../utils/instanceRenderMgr";
|
||||
|
||||
const { defaultAlgorithm, darkAlgorithm } = theme;
|
||||
|
||||
let isDarkMode = false;
|
||||
|
||||
/**
|
||||
* Default theme
|
||||
* @type {{components: {Menu: {itemDividerBorderColor: string}}}}
|
||||
*/
|
||||
const defaultTheme = {
|
||||
const defaultTheme = (isDarkMode) => ({
|
||||
components: {
|
||||
Table: {
|
||||
rowHoverBg: "#e7f3ff",
|
||||
rowSelectedBg: "#e6f7ff",
|
||||
rowHoverBg: isDarkMode ? "#2a2a2a" : "#e7f3ff",
|
||||
rowSelectedBg: isDarkMode ? "#333333" : "#e6f7ff",
|
||||
headerSortHoverBg: "transparent"
|
||||
},
|
||||
Menu: {
|
||||
darkItemHoverBg: "#1890ff",
|
||||
itemHoverBg: "#1890ff",
|
||||
horizontalItemHoverBg: "#1890ff"
|
||||
darkItemHoverBg: isDarkMode ? "#004a77" : "#1890ff",
|
||||
itemHoverBg: isDarkMode ? "#004a77" : "#1890ff",
|
||||
horizontalItemHoverBg: isDarkMode ? "#004a77" : "#1890ff"
|
||||
}
|
||||
},
|
||||
token: {
|
||||
colorPrimary: InstanceRenderMgr({
|
||||
imex: "#1890ff",
|
||||
rome: "#326ade"
|
||||
}),
|
||||
colorInfo: InstanceRenderMgr({
|
||||
imex: "#1890ff",
|
||||
rome: "#326ade"
|
||||
})
|
||||
colorPrimary: InstanceRenderMgr(
|
||||
{
|
||||
imex: isDarkMode ? "#4da8ff" : "#1890ff",
|
||||
rome: isDarkMode ? "#5b8ce6" : "#326ade"
|
||||
},
|
||||
isDarkMode
|
||||
),
|
||||
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
|
||||
@@ -60,8 +66,9 @@ const prodTheme = {};
|
||||
|
||||
const currentTheme = import.meta.env.DEV ? devTheme : prodTheme;
|
||||
|
||||
const finaltheme = {
|
||||
const getTheme = (isDarkMode) => ({
|
||||
algorithm: isDarkMode ? darkAlgorithm : defaultAlgorithm,
|
||||
...defaultsDeep(currentTheme, defaultTheme)
|
||||
};
|
||||
export default finaltheme;
|
||||
});
|
||||
|
||||
export default getTheme;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
td {
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #ddd;
|
||||
border-bottom: 1px solid var(--table-border-color);
|
||||
|
||||
.ant-form-item {
|
||||
margin-bottom: 0px !important;
|
||||
@@ -14,6 +14,6 @@
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background-color: #f5f5f5;
|
||||
background-color: var(--table-hover-bg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
td {
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #ddd;
|
||||
border-bottom: 1px solid var(--table-border-color);
|
||||
|
||||
.ant-form-item {
|
||||
margin-bottom: 0px !important;
|
||||
@@ -14,6 +14,6 @@
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background-color: #f5f5f5;
|
||||
background-color: var(--table-hover-bg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,9 +29,7 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
function ChatConversationListComponent({ conversationList, selectedConversation, setSelectedConversation, bodyshop }) {
|
||||
const { t } = useTranslation();
|
||||
const [, forceUpdate] = useState(false);
|
||||
|
||||
const phoneNumbers = conversationList.map((item) => phone(item.phone_num, "CA").phoneNumber.replace(/^\+1/, ""));
|
||||
|
||||
const { data: optOutData } = useQuery(GET_PHONE_NUMBER_OPT_OUTS, {
|
||||
variables: {
|
||||
bodyshopid: bodyshop.id,
|
||||
@@ -64,15 +62,12 @@ function ChatConversationListComponent({ conversationList, selectedConversation,
|
||||
const item = sortedConversationList[index];
|
||||
const normalizedPhone = phone(item.phone_num, "CA").phoneNumber.replace(/^\+1/, "");
|
||||
const hasOptOutEntry = optOutMap.has(normalizedPhone);
|
||||
|
||||
const cardContentRight = <TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>;
|
||||
const cardContentLeft =
|
||||
item.job_conversations.length > 0
|
||||
? item.job_conversations.map((j, idx) => <Tag key={idx}>{j.job.ro_number}</Tag>)
|
||||
: null;
|
||||
|
||||
const names = <>{_.uniq(item.job_conversations.map((j, idx) => OwnerNameDisplayFunction(j.job)))}</>;
|
||||
|
||||
const cardTitle = (
|
||||
<>
|
||||
{item.label && <Tag color="blue">{item.label}</Tag>}
|
||||
@@ -85,7 +80,6 @@ function ChatConversationListComponent({ conversationList, selectedConversation,
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
const cardExtra = (
|
||||
<>
|
||||
<Badge count={item.messages_aggregate.aggregate.count} />
|
||||
@@ -98,11 +92,10 @@ function ChatConversationListComponent({ conversationList, selectedConversation,
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
const getCardStyle = () =>
|
||||
item.id === selectedConversation
|
||||
? { backgroundColor: "rgba(128, 128, 128, 0.2)" }
|
||||
: { backgroundColor: index % 2 === 0 ? "#f0f2f5" : "#ffffff" };
|
||||
? { backgroundColor: "var(--card-selected-bg)" }
|
||||
: { backgroundColor: index % 2 === 0 ? "var(--card-stripe-even-bg)" : "var(--card-stripe-odd-bg)" };
|
||||
|
||||
return (
|
||||
<List.Item
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
max-height: 480px;
|
||||
overflow-y: auto;
|
||||
padding: 8px;
|
||||
background-color: #fff;
|
||||
background-color: var(--popover-bg);
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: red;
|
||||
color: var(--error-text);
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
margin-bottom: 8px;
|
||||
@@ -25,14 +25,13 @@
|
||||
|
||||
.no-jobs-message {
|
||||
font-size: 14px;
|
||||
color: #888;
|
||||
color: var(--no-jobs-text);
|
||||
text-align: center;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
/* Style images within gallery components */
|
||||
.media-selector-content img {
|
||||
|
||||
object-fit: cover;
|
||||
border-radius: 4px;
|
||||
margin: 4px;
|
||||
@@ -40,8 +39,8 @@
|
||||
}
|
||||
|
||||
/* Grid layout for gallery components */
|
||||
.media-selector-content .ant-image, /* Assuming gallery components use Ant Design's Image */
|
||||
.media-selector-content .gallery-container { /* Fallback for custom gallery classes */
|
||||
.media-selector-content .ant-image,
|
||||
.media-selector-content .gallery-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
||||
gap: 4px;
|
||||
|
||||
@@ -44,7 +44,6 @@
|
||||
.chat-send-message-button {
|
||||
margin: 0.3rem;
|
||||
padding-left: 0.5rem;
|
||||
|
||||
}
|
||||
|
||||
.message-icon {
|
||||
@@ -52,7 +51,7 @@
|
||||
bottom: 0.1rem;
|
||||
right: 0.3rem;
|
||||
margin: 0 0.1rem;
|
||||
color: whitesmoke;
|
||||
color: var(--message-icon-color);
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
@@ -80,7 +79,7 @@
|
||||
|
||||
&:last-child:after {
|
||||
width: 10px;
|
||||
background: white;
|
||||
background: var(--message-mine-tail-bg);
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
@@ -92,11 +91,11 @@
|
||||
|
||||
.message {
|
||||
margin-right: 20%;
|
||||
background-color: #eee;
|
||||
background-color: var(--message-yours-bg);
|
||||
|
||||
&:last-child:before {
|
||||
left: -7px;
|
||||
background: #eee;
|
||||
background: var(--message-yours-bg);
|
||||
border-bottom-right-radius: 15px;
|
||||
}
|
||||
|
||||
@@ -112,14 +111,14 @@
|
||||
align-items: flex-end;
|
||||
|
||||
.message {
|
||||
color: white;
|
||||
color: var(--message-mine-text);
|
||||
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;
|
||||
|
||||
&:last-child:before {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -135,32 +134,31 @@
|
||||
margin: 0.5rem 10%;
|
||||
|
||||
.message {
|
||||
background-color: #f5f5f5;
|
||||
background-color: var(--system-message-bg);
|
||||
border-radius: 10px;
|
||||
padding: 0.5rem 1rem;
|
||||
text-align: center;
|
||||
font-style: italic;
|
||||
color: #555;
|
||||
color: var(--system-message-text);
|
||||
width: fit-content;
|
||||
max-width: 80%;
|
||||
}
|
||||
|
||||
.system-label {
|
||||
font-size: 0.75rem;
|
||||
color: #888;
|
||||
color: var(--system-label-text);
|
||||
margin-bottom: 0.2rem;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.system-date {
|
||||
font-size: 0.75rem;
|
||||
color: #888;
|
||||
color: var(--system-label-text);
|
||||
margin-top: 0.2rem;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.virtuoso-container {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Card, Table, Tag } from "antd";
|
||||
import axios from "axios";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import dayjs from "../../../utils/day";
|
||||
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.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()}`;
|
||||
@@ -88,7 +87,7 @@ export default function JobLifecycleDashboardComponent({ data, bodyshop, ...card
|
||||
borderRadius: "5px",
|
||||
borderWidth: "5px",
|
||||
borderStyle: "solid",
|
||||
borderColor: "#f0f2f5",
|
||||
borderColor: "var(--bar-border-color)",
|
||||
margin: 0,
|
||||
padding: 0
|
||||
}}
|
||||
@@ -107,12 +106,10 @@ export default function JobLifecycleDashboardComponent({ data, bodyshop, ...card
|
||||
alignItems: "center",
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
|
||||
borderTop: "1px solid #f0f2f5",
|
||||
borderBottom: "1px solid #f0f2f5",
|
||||
borderLeft: isFirst ? "1px solid #f0f2f5" : undefined,
|
||||
borderRight: isLast ? "1px solid #f0f2f5" : undefined,
|
||||
|
||||
borderTop: "1px solid var(--bar-border-color)",
|
||||
borderBottom: "1px solid var(--bar-border-color)",
|
||||
borderLeft: isFirst ? "1px solid var(--bar-border-color)" : undefined,
|
||||
borderRight: isLast ? "1px solid var(--bar-border-color)" : undefined,
|
||||
backgroundColor: key.color,
|
||||
width: `${key.percentage}%`
|
||||
}}
|
||||
@@ -124,7 +121,7 @@ export default function JobLifecycleDashboardComponent({ data, bodyshop, ...card
|
||||
<div>{key.roundedPercentage}</div>
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: "#f0f2f5",
|
||||
backgroundColor: "var(--tag-wrapper-bg)",
|
||||
borderRadius: "5px",
|
||||
paddingRight: "2px",
|
||||
paddingLeft: "2px",
|
||||
@@ -152,8 +149,8 @@ export default function JobLifecycleDashboardComponent({ data, bodyshop, ...card
|
||||
aria-label={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
||||
title={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
||||
style={{
|
||||
backgroundColor: "#f0f2f5",
|
||||
color: "#000",
|
||||
backgroundColor: "var(--tag-wrapper-bg)",
|
||||
color: "var(--tag-wrapper-text)",
|
||||
padding: "4px",
|
||||
textAlign: "center"
|
||||
}}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { UploadOutlined, UserAddOutlined } from "@ant-design/icons";
|
||||
import { Button, Divider, Dropdown, Form, Input, Select, Space, Tabs, Upload } from "antd";
|
||||
import _ from "lodash";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -15,20 +14,24 @@ const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser,
|
||||
emailConfig: selectEmailConfig
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
|
||||
const mapDispatchToProps = () => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(EmailOverlayComponent);
|
||||
|
||||
export function EmailOverlayComponent({ emailConfig, form, selectedMediaState, bodyshop, currentUser }) {
|
||||
const { t } = useTranslation();
|
||||
const handleClick = ({ item, key, keyPath }) => {
|
||||
|
||||
const handleClick = ({ item }) => {
|
||||
const email = item.props.value;
|
||||
form.setFieldsValue({
|
||||
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;
|
||||
form.setFieldsValue({
|
||||
cc: _.uniq([...(form.getFieldValue("cc") || ""), ...(typeof email === "string" ? [email] : email)])
|
||||
@@ -52,6 +55,7 @@ export function EmailOverlayComponent({ emailConfig, form, selectedMediaState, b
|
||||
],
|
||||
onClick: handleClick
|
||||
};
|
||||
|
||||
const menuCC = {
|
||||
items: [
|
||||
...bodyshop.employees
|
||||
@@ -136,26 +140,22 @@ export function EmailOverlayComponent({ emailConfig, form, selectedMediaState, b
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Divider>{t("emails.labels.preview")}</Divider>
|
||||
{bodyshop.attach_pdf_to_email && <strong>{t("emails.labels.pdfcopywillbeattached")}</strong>}
|
||||
|
||||
<Form.Item shouldUpdate>
|
||||
{() => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
padding: "1rem",
|
||||
|
||||
backgroundColor: "lightgray",
|
||||
borderLeft: "6px solid #2196F3"
|
||||
backgroundColor: "var(--preview-bg)",
|
||||
borderLeft: "6px solid var(--preview-border-color)"
|
||||
}}
|
||||
dangerouslySetInnerHTML={{ __html: form.getFieldValue("html") }}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
|
||||
<Tabs
|
||||
defaultActiveKey="documents"
|
||||
items={[
|
||||
@@ -184,12 +184,10 @@ export function EmailOverlayComponent({ emailConfig, form, selectedMediaState, b
|
||||
return e && e.fileList;
|
||||
}}
|
||||
rules={[
|
||||
({ getFieldValue }) => ({
|
||||
() => ({
|
||||
validator(rule, value) {
|
||||
const totalSize = value.reduce((acc, val) => (acc = acc + val.size), 0);
|
||||
|
||||
const limit = 10485760 - new Blob([form.getFieldValue("html")]).size;
|
||||
|
||||
if (totalSize > limit) {
|
||||
return Promise.reject(t("general.errors.sizelimit"));
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
.eula-markdown-card {
|
||||
max-height: 50vh;
|
||||
overflow-y: auto;
|
||||
background-color: lightgray;
|
||||
background-color: var(--eula-card-bg);
|
||||
}
|
||||
|
||||
.eula-markdown-div {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import Dinero from "dinero.js";
|
||||
import { forwardRef } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
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);
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
UsergroupAddOutlined,
|
||||
UserOutlined
|
||||
} 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 { FiLogOut } from "react-icons/fi";
|
||||
import { GiPlayerTime, GiSettingsKnobs } from "react-icons/gi";
|
||||
@@ -33,6 +33,7 @@ import { RiSurveyLine } from "react-icons/ri";
|
||||
import { IoBusinessOutline } from "react-icons/io5";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import LockWrapper from "../../components/lock-wrapper/lock-wrapper.component.jsx";
|
||||
import { Tooltip } from "antd";
|
||||
|
||||
const buildLeftMenuItems = ({
|
||||
t,
|
||||
@@ -41,7 +42,9 @@ const buildLeftMenuItems = ({
|
||||
setTaskUpsertContext,
|
||||
setReportCenterContext,
|
||||
signOutStart,
|
||||
accountingChildren
|
||||
accountingChildren,
|
||||
handleDarkModeToggle,
|
||||
darkMode
|
||||
}) => {
|
||||
return [
|
||||
{
|
||||
@@ -331,6 +334,15 @@ const buildLeftMenuItems = ({
|
||||
label: t("user.actions.signout"),
|
||||
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",
|
||||
id: "header-help",
|
||||
|
||||
@@ -12,7 +12,7 @@ import { createStructuredSelector } from "reselect";
|
||||
import { TASKS_CENTER_POLL_INTERVAL, useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||
import { GET_UNREAD_COUNT } from "../../graphql/notifications.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 { signOutStart } from "../../redux/user/user.actions";
|
||||
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 buildAccountingChildren from "./buildAccountingChildren.jsx";
|
||||
import buildLeftMenuItems from "./buildLeftMenuItems.jsx";
|
||||
import { toggleDarkMode } from "../../redux/application/application.actions";
|
||||
|
||||
// --- Redux mappings ---
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser,
|
||||
recentItems: selectRecentItems,
|
||||
selectedHeader: selectSelectedHeader,
|
||||
bodyshop: selectBodyshop
|
||||
bodyshop: selectBodyshop,
|
||||
darkMode: selectDarkMode
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
@@ -38,7 +40,8 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
setReportCenterContext: (context) => dispatch(setModalContext({ context, modal: "reportCenter" })),
|
||||
signOutStart: () => dispatch(signOutStart()),
|
||||
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 ---
|
||||
@@ -84,22 +87,22 @@ function useIncompleteTaskCount(assignedToId, bodyshopId, isEmployee, isConnecte
|
||||
}
|
||||
|
||||
// --- Main Component ---
|
||||
function Header(props) {
|
||||
const {
|
||||
handleMenuClick,
|
||||
currentUser,
|
||||
bodyshop,
|
||||
selectedHeader,
|
||||
signOutStart,
|
||||
setBillEnterContext,
|
||||
setTimeTicketContext,
|
||||
setPaymentContext,
|
||||
setReportCenterContext,
|
||||
recentItems,
|
||||
setCardPaymentContext,
|
||||
setTaskUpsertContext
|
||||
} = props;
|
||||
|
||||
function Header({
|
||||
handleMenuClick,
|
||||
currentUser,
|
||||
bodyshop,
|
||||
selectedHeader,
|
||||
signOutStart,
|
||||
setBillEnterContext,
|
||||
setTimeTicketContext,
|
||||
setPaymentContext,
|
||||
setReportCenterContext,
|
||||
recentItems,
|
||||
setCardPaymentContext,
|
||||
setTaskUpsertContext,
|
||||
toggleDarkMode,
|
||||
darkMode
|
||||
}) {
|
||||
// Feature flags
|
||||
const {
|
||||
treatments: { ImEXPay, DmsAp, Simple_Inventory }
|
||||
@@ -216,6 +219,10 @@ function Header(props) {
|
||||
[handleMenuClick]
|
||||
);
|
||||
|
||||
const handleDarkModeToggle = useCallback(() => {
|
||||
toggleDarkMode();
|
||||
}, [toggleDarkMode]);
|
||||
|
||||
// --- Menu Items ---
|
||||
|
||||
// built externally to keep the component clean, but on this level to prevent unnecessary re-renders
|
||||
@@ -257,9 +264,21 @@ function Header(props) {
|
||||
setTaskUpsertContext,
|
||||
setReportCenterContext,
|
||||
signOutStart,
|
||||
accountingChildren
|
||||
accountingChildren,
|
||||
darkMode,
|
||||
handleDarkModeToggle
|
||||
}),
|
||||
[t, bodyshop, recentItems, setTaskUpsertContext, setReportCenterContext, signOutStart, accountingChildren]
|
||||
[
|
||||
t,
|
||||
bodyshop,
|
||||
recentItems,
|
||||
setTaskUpsertContext,
|
||||
setReportCenterContext,
|
||||
signOutStart,
|
||||
accountingChildren,
|
||||
darkMode,
|
||||
handleDarkModeToggle
|
||||
]
|
||||
);
|
||||
|
||||
const rightMenuItems = useMemo(() => {
|
||||
@@ -292,6 +311,7 @@ function Header(props) {
|
||||
),
|
||||
onClick: handleTaskCenterClick
|
||||
});
|
||||
|
||||
return items;
|
||||
}, [
|
||||
scenarioNotificationsOn,
|
||||
|
||||
@@ -36,6 +36,7 @@ import ScheduleEventNote from "./schedule-event.note.component";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setScheduleContext: (context) => dispatch(setModalContext({ context: context, modal: "schedule" })),
|
||||
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
|
||||
@@ -64,7 +65,6 @@ export function ScheduleEventComponent({
|
||||
const notification = useNotification();
|
||||
const [form] = Form.useForm();
|
||||
const [popOverVisible, setPopOverVisible] = useState(false);
|
||||
|
||||
const [getJobDetails] = useLazyQuery(GET_JOB_BY_PK_QUICK_INTAKE, {
|
||||
variables: { id: event.job?.id },
|
||||
onCompleted: (data) => {
|
||||
@@ -83,7 +83,6 @@ export function ScheduleEventComponent({
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
fetchPolicy: "network-only"
|
||||
});
|
||||
|
||||
@@ -115,7 +114,6 @@ export function ScheduleEventComponent({
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
<Button onClick={() => handleCancel({ id: event.id })} disabled={event.arrived}>
|
||||
{t("appointments.actions.unblock")}
|
||||
</Button>
|
||||
@@ -133,7 +131,6 @@ export function ScheduleEventComponent({
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!res.errors) {
|
||||
notification["success"]({
|
||||
message: t("jobs.successes.converted")
|
||||
@@ -180,7 +177,6 @@ export function ScheduleEventComponent({
|
||||
<Form.Item name={["scheduled_delivery"]} label={t("jobs.fields.scheduled_delivery")}>
|
||||
<FormDateTimePickerComponent disabled={event.ro_number} />
|
||||
</Form.Item>
|
||||
|
||||
<Space wrap>
|
||||
<Button type="primary" onClick={() => form.submit()}>
|
||||
{t("general.actions.save")}
|
||||
@@ -210,7 +206,6 @@ export function ScheduleEventComponent({
|
||||
<ScheduleEventColor event={event} />
|
||||
</Space>
|
||||
)}
|
||||
|
||||
{event.job ? (
|
||||
<div>
|
||||
<DataLabel label={t("jobs.fields.ro_number")}>{(event.job && event.job.ro_number) || ""}</DataLabel>
|
||||
@@ -371,7 +366,6 @@ export function ScheduleEventComponent({
|
||||
</Button>
|
||||
</Popover>
|
||||
)}
|
||||
|
||||
{event.isintake ? (
|
||||
<Button
|
||||
disabled={event.arrived}
|
||||
@@ -428,27 +422,30 @@ export function ScheduleEventComponent({
|
||||
</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 ? (
|
||||
<Space
|
||||
wrap
|
||||
size="small"
|
||||
style={{
|
||||
backgroundColor: event.color && event.color.hex ? event.color.hex : event.color
|
||||
backgroundColor: getEventBackground()
|
||||
}}
|
||||
>
|
||||
{event.note && <AlertFilled className="production-alert" />}
|
||||
<strong>{`${event.job.ro_number || t("general.labels.na")}`}</strong>
|
||||
|
||||
<OwnerNameDisplay ownerObject={event.job} />
|
||||
|
||||
{`${(event.job && event.job.v_model_yr) || ""} ${
|
||||
(event.job && event.job.v_make_desc) || ""
|
||||
} ${(event.job && event.job.v_model_desc) || ""}`}
|
||||
|
||||
{`(${(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.alt_transport && <div style={{ margin: ".1rem" }}>{event.job.alt_transport}</div>}
|
||||
{event?.job?.comment && `C: ${event.job.comment}`}
|
||||
</Space>
|
||||
@@ -457,7 +454,7 @@ export function ScheduleEventComponent({
|
||||
style={{
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
backgroundColor: event.color && event.color.hex ? event.color.hex : event.color
|
||||
backgroundColor: getEventBackground()
|
||||
}}
|
||||
>
|
||||
<strong>{`${event.title || ""}`}</strong>
|
||||
@@ -473,8 +470,7 @@ export function ScheduleEventComponent({
|
||||
style={{
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
|
||||
backgroundColor: event.color && event.color.hex ? event.color.hex : event.color
|
||||
backgroundColor: getEventBackground()
|
||||
}}
|
||||
>
|
||||
{RegularEvent}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import dayjs from "../../utils/day";
|
||||
import axios from "axios";
|
||||
import { Badge, Card, Space, Table, Tag } from "antd";
|
||||
@@ -6,24 +6,24 @@ import { gql, useQuery } from "@apollo/client";
|
||||
import { DateTimeFormatterFunction } from "../../utils/DateFormatter";
|
||||
import { isEmpty } from "lodash";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import "./job-lifecycle.styles.scss";
|
||||
import BlurWrapperComponent from "../feature-wrapper/blur-wrapper.component";
|
||||
|
||||
import UpsellComponent, { upsellEnum } from "../upsell/upsell.component";
|
||||
|
||||
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
|
||||
const mapDispatchToProps = () => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
|
||||
// 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 [lifecycleData, setLifecycleData] = useState(null);
|
||||
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"),
|
||||
dataIndex: "value",
|
||||
key: "value",
|
||||
render: (text, record) => (
|
||||
render: (text) => (
|
||||
<BlurWrapperComponent
|
||||
featureName="lifecycle"
|
||||
bypass
|
||||
@@ -95,7 +95,7 @@ export function JobLifecycleComponent({ bodyshop, job, statuses, ...rest }) {
|
||||
dataIndex: "start",
|
||||
key: "start",
|
||||
sorter: (a, b) => dayjs(a.start).unix() - dayjs(b.start).unix(),
|
||||
render: (text, record) => (
|
||||
render: (text) => (
|
||||
<BlurWrapperComponent featureName="lifecycle" bypass valueProp="children" overrideValueFunction="RandomDate">
|
||||
<span>{DateTimeFormatterFunction(text)}</span>
|
||||
</BlurWrapperComponent>
|
||||
@@ -119,8 +119,7 @@ export function JobLifecycleComponent({ bodyshop, job, statuses, ...rest }) {
|
||||
}
|
||||
return dayjs(a.end).unix() - dayjs(b.end).unix();
|
||||
},
|
||||
|
||||
render: (text, record) => (
|
||||
render: (text) => (
|
||||
<BlurWrapperComponent featureName="lifecycle" bypass valueProp="children" overrideValueFunction="RandomDate">
|
||||
<span>{isEmpty(text) ? t("job_lifecycle.content.not_available") : DateTimeFormatterFunction(text)}</span>
|
||||
</BlurWrapperComponent>
|
||||
@@ -170,7 +169,7 @@ export function JobLifecycleComponent({ bodyshop, job, statuses, ...rest }) {
|
||||
borderRadius: "5px",
|
||||
borderWidth: "5px",
|
||||
borderStyle: "solid",
|
||||
borderColor: "#f0f2f5",
|
||||
borderColor: "var(--bar-border-color)",
|
||||
margin: 0,
|
||||
padding: 0
|
||||
}}
|
||||
@@ -189,12 +188,10 @@ export function JobLifecycleComponent({ bodyshop, job, statuses, ...rest }) {
|
||||
alignItems: "center",
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
|
||||
borderTop: "1px solid #f0f2f5",
|
||||
borderBottom: "1px solid #f0f2f5",
|
||||
borderLeft: isFirst ? "1px solid #f0f2f5" : undefined,
|
||||
borderRight: isLast ? "1px solid #f0f2f5" : undefined,
|
||||
|
||||
borderTop: "1px solid var(--bar-border-color)",
|
||||
borderBottom: "1px solid var(--bar-border-color)",
|
||||
borderLeft: isFirst ? "1px solid var(--bar-border-color)" : undefined,
|
||||
borderRight: isLast ? "1px solid var(--bar-border-color)" : undefined,
|
||||
backgroundColor: key.color,
|
||||
width: `${key.percentage}%`
|
||||
}}
|
||||
@@ -206,7 +203,7 @@ export function JobLifecycleComponent({ bodyshop, job, statuses, ...rest }) {
|
||||
<div>{key.roundedPercentage}</div>
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: "#f0f2f5",
|
||||
backgroundColor: "var(--tag-wrapper-bg)",
|
||||
borderRadius: "5px",
|
||||
paddingRight: "2px",
|
||||
paddingLeft: "2px",
|
||||
@@ -230,8 +227,8 @@ export function JobLifecycleComponent({ bodyshop, job, statuses, ...rest }) {
|
||||
aria-label={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
||||
title={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
||||
style={{
|
||||
backgroundColor: "#f0f2f5",
|
||||
color: "#000",
|
||||
backgroundColor: "var(--tag-wrapper-bg)",
|
||||
color: "var(--tag-wrapper-text)",
|
||||
padding: "4px",
|
||||
textAlign: "center"
|
||||
}}
|
||||
@@ -315,4 +312,5 @@ export function JobLifecycleComponent({ bodyshop, job, statuses, ...rest }) {
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(JobLifecycleComponent);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
td {
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #ddd;
|
||||
border-bottom: 1px solid var(--table-border-color);
|
||||
|
||||
.ant-form-item {
|
||||
margin-bottom: 0px !important;
|
||||
@@ -14,6 +14,6 @@
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background-color: #f5f5f5;
|
||||
background-color: var(--table-hover-bg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,6 @@ export function NoteUpsertModalContainer({ currentUser, noteUpsertModal, toggleM
|
||||
const { refetch } = actions;
|
||||
|
||||
const [form] = Form.useForm();
|
||||
|
||||
useEffect(() => {
|
||||
//Required to prevent infinite looping.
|
||||
if (existingNote && open) {
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
right: 0;
|
||||
width: 400px;
|
||||
max-width: 400px;
|
||||
background: #fff;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
border: 1px solid #d9d9d9;
|
||||
background: var(--notification-bg);
|
||||
color: var(--notification-text);
|
||||
border: 1px solid var(--notification-border);
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.08), 0 3px 6px rgba(0, 0, 0, 0.06);
|
||||
z-index: 1000;
|
||||
@@ -19,23 +19,22 @@
|
||||
|
||||
.notification-header {
|
||||
padding: 4px 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
border-bottom: 1px solid var(--notification-header-border);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: #fafafa;
|
||||
background: var(--notification-header-bg);
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
color: var(--notification-header-text);
|
||||
}
|
||||
|
||||
.notification-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
// Styles for the eye icon and switch (custom classes)
|
||||
.notification-toggle {
|
||||
align-items: center; // Ensure vertical alignment
|
||||
@@ -43,7 +42,7 @@
|
||||
|
||||
.notification-toggle-icon {
|
||||
font-size: 14px;
|
||||
color: #1677ff;
|
||||
color: var(--notification-toggle-icon);
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
@@ -59,7 +58,8 @@
|
||||
}
|
||||
|
||||
&.ant-switch-checked {
|
||||
background-color: #1677ff;
|
||||
background-color: var(--notification-switch-bg);
|
||||
|
||||
.ant-switch-handle {
|
||||
left: calc(100% - 14px);
|
||||
}
|
||||
@@ -70,37 +70,37 @@
|
||||
// Styles for the "Mark All Read" button (restore original link button style)
|
||||
.ant-btn-link {
|
||||
padding: 0;
|
||||
color: #1677ff;
|
||||
color: var(--notification-btn-link);
|
||||
|
||||
&:hover {
|
||||
color: #69b1ff;
|
||||
color: var(--notification-btn-link-hover);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
color: rgba(0, 0, 0, 0.25);
|
||||
color: var(--notification-btn-link-disabled);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: #0958d9;
|
||||
color: var(--notification-btn-link-active);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.notification-read {
|
||||
background: #fff;
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
background: var(--notification-read-bg);
|
||||
color: var(--notification-read-text);
|
||||
}
|
||||
|
||||
.notification-unread {
|
||||
background: #f5f5f5;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
background: var(--notification-unread-bg);
|
||||
color: var(--notification-unread-text);
|
||||
}
|
||||
|
||||
.notification-item {
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
border-bottom: 1px solid var(--notification-header-border);
|
||||
display: block;
|
||||
overflow: visible;
|
||||
width: 100%;
|
||||
@@ -108,7 +108,7 @@
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: #fafafa;
|
||||
background: var(--notification-item-hover-bg);
|
||||
}
|
||||
|
||||
.notification-content {
|
||||
@@ -125,7 +125,7 @@
|
||||
|
||||
.ro-number {
|
||||
margin: 0;
|
||||
color: #1677ff;
|
||||
color: var(--notification-ro-number);
|
||||
flex-shrink: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -133,7 +133,7 @@
|
||||
.relative-time {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
color: var(--notification-relative-time);
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
@@ -164,12 +164,12 @@
|
||||
|
||||
.ant-alert {
|
||||
margin: 8px;
|
||||
background: #fff1f0;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
border: 1px solid #ffa39e;
|
||||
background: var(--alert-bg);
|
||||
color: var(--alert-text);
|
||||
border: 1px solid var(--alert-border);
|
||||
|
||||
.ant-alert-message {
|
||||
color: #ff4d4f;
|
||||
color: var(--alert-message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,29 @@
|
||||
import { Col, List, Space, Typography } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const CardColorLegend = ({ bodyshop }) => {
|
||||
const { t } = useTranslation();
|
||||
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) {
|
||||
color = bucket.color;
|
||||
|
||||
if (bucket.color.rgb) {
|
||||
color = bucket.color.rgb;
|
||||
color = { ...bucket.color.rgb, a: bucket.color.a || 1 };
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
label: bucket.label,
|
||||
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 (
|
||||
<Col>
|
||||
<Typography>{t("production.labels.legend")}</Typography>
|
||||
@@ -36,7 +39,7 @@ const CardColorLegend = ({ bodyshop }) => {
|
||||
style={{
|
||||
width: "1.5rem",
|
||||
aspectRatio: "1/1",
|
||||
backgroundColor: `rgba(${item.color.r},${item.color.g},${item.color.b},${item.color.a})`
|
||||
backgroundColor: getBackgroundColor(item.color)
|
||||
}}
|
||||
></div>
|
||||
<div>{item.label}</div>
|
||||
|
||||
@@ -11,13 +11,10 @@ import React, { useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
|
||||
import ProductionAlert from "../production-list-columns/production-list-columns.alert.component";
|
||||
import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component";
|
||||
import ProductionSubletsManageComponent from "../production-sublets-manage/production-sublets-manage.component";
|
||||
|
||||
import dayjs from "../../utils/day";
|
||||
|
||||
import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component";
|
||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||
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 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) =>
|
||||
(bgColor.r * 299 + bgColor.g * 587 + bgColor.b * 114) / 1000 >= 128 ? "black" : "white";
|
||||
const getContrastYIQ = (bgColor, isDarkMode = document.documentElement.getAttribute("data-theme") === "dark") => {
|
||||
// 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);
|
||||
|
||||
@@ -44,6 +55,8 @@ const EllipsesToolTip = React.memo(({ title, children, kiosk }) => {
|
||||
);
|
||||
});
|
||||
|
||||
EllipsesToolTip.displayName = "EllipsesToolTip";
|
||||
|
||||
const OwnerNameToolTip = ({ metadata, cardSettings }) =>
|
||||
cardSettings?.ownr_nm && (
|
||||
<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();
|
||||
|
||||
return (
|
||||
cardSettings?.subtotal && (
|
||||
<Col span={cardSettings.compact ? 24 : 12}>
|
||||
@@ -300,12 +312,10 @@ const TasksToolTip = ({ metadata, cardSettings, t }) =>
|
||||
</Col>
|
||||
);
|
||||
|
||||
export default function ProductionBoardCard({ technician, card, bodyshop, cardSettings, clone }) {
|
||||
export default function ProductionBoardCard({ technician, card, bodyshop, cardSettings }) {
|
||||
const { t } = useTranslation();
|
||||
const { metadata } = card;
|
||||
|
||||
const employees = useMemo(() => bodyshop.employees, [bodyshop.employees]);
|
||||
|
||||
const { employee_body, employee_prep, employee_refinish, employee_csr } = useMemo(() => {
|
||||
return {
|
||||
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)
|
||||
};
|
||||
}, [metadata, employees]);
|
||||
|
||||
const pastDueAlert = useMemo(() => {
|
||||
if (!metadata?.scheduled_completion) return null;
|
||||
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";
|
||||
return null;
|
||||
}, [metadata?.scheduled_completion]);
|
||||
|
||||
const totalHrs = useMemo(() => {
|
||||
return metadata?.labhrs && metadata?.larhrs
|
||||
? metadata.labhrs.aggregate.sum.mod_lb_hrs + metadata.larhrs.aggregate.sum.mod_lb_hrs
|
||||
: 0;
|
||||
}, [metadata?.labhrs, metadata?.larhrs]);
|
||||
|
||||
const bgColor = useMemo(() => cardColor(bodyshop.ssbuckets, totalHrs), [bodyshop.ssbuckets, totalHrs]);
|
||||
const contrastYIQ = useMemo(() => getContrastYIQ(bgColor), [bgColor]);
|
||||
|
||||
const isBodyEmpty = useMemo(() => {
|
||||
return !(
|
||||
cardSettings?.ownr_nm ||
|
||||
@@ -413,8 +419,10 @@ export default function ProductionBoardCard({ technician, card, bodyshop, cardSe
|
||||
className={`react-trello-card ${cardSettings.kiosk ? "kiosk-mode" : ""}`}
|
||||
size="small"
|
||||
style={{
|
||||
backgroundColor: cardSettings?.cardcolor && `rgba(${bgColor.r},${bgColor.g},${bgColor.b},${bgColor.a})`,
|
||||
color: cardSettings?.cardcolor && contrastYIQ
|
||||
backgroundColor: cardSettings?.cardcolor
|
||||
? 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}
|
||||
extra={
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
}
|
||||
|
||||
.share-to-teams-badge {
|
||||
background-color: #cccccc;
|
||||
background-color: var(--share-badge-bg);
|
||||
border-radius: 50%;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
@@ -23,7 +23,7 @@
|
||||
.react-trello-column-header {
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
background-color: #d0d0d0;
|
||||
background-color: var(--column-header-bg);
|
||||
border-radius: 5px 5px 0 0;
|
||||
}
|
||||
|
||||
@@ -31,13 +31,14 @@
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.react-trello-footer {
|
||||
background-color: #d0d0d0;
|
||||
background-color: var(--footer-bg);
|
||||
border-radius: 0 0 5px 5px;
|
||||
}
|
||||
|
||||
.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 {
|
||||
@@ -53,27 +54,33 @@
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
.body-empty-container {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.tech-container {
|
||||
font-weight: bolder;
|
||||
text-align: center;
|
||||
flex: 1;
|
||||
|
||||
.branches-outlined {
|
||||
color: orangered;
|
||||
color: var(--tech-icon-color);
|
||||
}
|
||||
}
|
||||
|
||||
.inner-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
|
||||
.circle-outline {
|
||||
color: orangered;
|
||||
color: var(--tech-icon-color);
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.iou-parent {
|
||||
margin-left: 8px;
|
||||
}
|
||||
@@ -81,6 +88,6 @@
|
||||
}
|
||||
|
||||
.clone.is-dragging .ant-card {
|
||||
border: #1890ff 2px solid !important;
|
||||
border: 2px solid var(--clone-border-color) !important;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ export const StyleHorizontal = styled.div`
|
||||
height: 100%;
|
||||
min-height: 1px;
|
||||
overflow-y: visible;
|
||||
overflow-x: visible; // change this line
|
||||
overflow-x: visible;
|
||||
}
|
||||
|
||||
.react-trello-lane.lane-collapsed {
|
||||
@@ -85,17 +85,17 @@ export const StyleHorizontal = styled.div`
|
||||
|
||||
.react-trello-card {
|
||||
height: auto;
|
||||
margin: 2px;
|
||||
margin: 2px 0 2px;
|
||||
}
|
||||
|
||||
.size-memory-wrapper {
|
||||
display: flex; /* This makes it a flex container */
|
||||
flex-direction: column; /* Aligns children vertically */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.size-memory-wrapper .ant-card {
|
||||
flex-grow: 1; /* Allows the card to expand to fill the available space */
|
||||
width: 100%; /* Ensures the card stretches to fill the width of its parent */
|
||||
flex-grow: 1;
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -131,7 +131,7 @@ export const StyleVertical = styled.div`
|
||||
|
||||
.grid-item {
|
||||
display: flex;
|
||||
width: ${(props) => props.gridItemWidth}; /* Use props to set width */
|
||||
width: ${(props) => props.gridItemWidth};
|
||||
align-content: stretch;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
@@ -148,13 +148,13 @@ export const StyleVertical = styled.div`
|
||||
}
|
||||
|
||||
.size-memory-wrapper {
|
||||
display: flex; /* This makes it a flex container */
|
||||
flex-direction: column; /* Aligns children vertically */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.size-memory-wrapper .ant-card {
|
||||
flex-grow: 1; /* Allows the card to expand to fill the available space */
|
||||
width: 100%; /* Ensures the card stretches to fill the width of its parent */
|
||||
flex-grow: 1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.react-trello-lane .lane-collapsed {
|
||||
@@ -163,7 +163,7 @@ export const StyleVertical = styled.div`
|
||||
`;
|
||||
|
||||
export const BoardWrapper = styled.div`
|
||||
color: #393939;
|
||||
color: var(--board-text-color);
|
||||
height: 100%;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
@@ -171,7 +171,7 @@ export const BoardWrapper = styled.div`
|
||||
`;
|
||||
|
||||
export const Section = styled.section`
|
||||
background-color: #e3e3e3;
|
||||
background-color: var(--section-bg);
|
||||
border-radius: 3px;
|
||||
margin: 2px 2px;
|
||||
height: 100%;
|
||||
@@ -197,6 +197,6 @@ export const ScrollableLane = styled.div`
|
||||
|
||||
export const Detail = styled.div`
|
||||
font-size: 12px;
|
||||
color: #4d4d4d;
|
||||
color: var(--detail-text-color);
|
||||
white-space: pre-wrap;
|
||||
`;
|
||||
|
||||
@@ -28,7 +28,6 @@ const mapStateToProps = createStructuredSelector({
|
||||
export function ProductionListTable({ loading, data, refetch, bodyshop, technician, currentUser }) {
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
|
||||
|
||||
const {
|
||||
treatments: { Production_List_Status_Colors, Enhanced_Payroll }
|
||||
} = useSplitTreatments({
|
||||
@@ -36,10 +35,8 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
||||
names: ["Production_List_Status_Colors", "Enhanced_Payroll"],
|
||||
splitKey: bodyshop.imexshopid
|
||||
});
|
||||
|
||||
const assoc = bodyshop.associations.find((a) => a.useremail === currentUser.email);
|
||||
const defaultView = assoc && assoc.default_prod_list_view;
|
||||
|
||||
const initialStateRef = useRef(
|
||||
(bodyshop.production_config &&
|
||||
bodyshop.production_config.find((p) => p.name === defaultView)?.columns.tableState) ||
|
||||
@@ -48,7 +45,6 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
||||
filteredInfo: { text: "" }
|
||||
}
|
||||
);
|
||||
|
||||
const initialColumnsRef = useRef(
|
||||
(initialStateRef.current &&
|
||||
bodyshop?.production_config
|
||||
@@ -69,12 +65,9 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
||||
})) ||
|
||||
[]
|
||||
);
|
||||
|
||||
const [state, setState] = useState(initialStateRef.current);
|
||||
const [columns, setColumns] = useState(initialColumnsRef.current);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const matchingColumnConfig = useMemo(() => {
|
||||
return bodyshop?.production_config?.find((p) => p.name === defaultView);
|
||||
}, [bodyshop.production_config, defaultView]);
|
||||
@@ -95,7 +88,6 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
||||
width: k.width ?? 100
|
||||
};
|
||||
}) || [];
|
||||
|
||||
// Only update columns if they haven't been manually changed by the user
|
||||
if (_.isEqual(initialColumnsRef.current, columns)) {
|
||||
setColumns(newColumns);
|
||||
@@ -126,11 +118,9 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
||||
|
||||
const onDragEnd = (fromIndex, toIndex) => {
|
||||
if (fromIndex === toIndex) return;
|
||||
|
||||
const columnsCopy = [...columns];
|
||||
const [movedItem] = columnsCopy.splice(fromIndex, 1);
|
||||
columnsCopy.splice(toIndex, 0, movedItem);
|
||||
|
||||
if (!_.isEqual(columnsCopy, columns)) {
|
||||
setColumns(columnsCopy);
|
||||
setHasUnsavedChanges(true);
|
||||
@@ -140,7 +130,6 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
||||
const removeColumn = (e) => {
|
||||
const { key } = e;
|
||||
const newColumns = columns.filter((i) => i.key !== key);
|
||||
|
||||
if (!_.isEqual(newColumns, columns)) {
|
||||
setColumns(newColumns);
|
||||
setHasUnsavedChanges(true);
|
||||
@@ -155,7 +144,6 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
||||
...nextColumns[index],
|
||||
width: size.width
|
||||
};
|
||||
|
||||
if (!_.isEqual(nextColumns, columns)) {
|
||||
setColumns(nextColumns);
|
||||
setHasUnsavedChanges(true);
|
||||
@@ -180,7 +168,6 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
return (
|
||||
<Dropdown className="prod-header-dropdown" menu={menu} trigger={["contextMenu"]}>
|
||||
<span>{col.title}</span>
|
||||
@@ -206,13 +193,12 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
||||
item.v_model_desc,
|
||||
item.v_make_desc
|
||||
];
|
||||
|
||||
return fieldsToSearch.some((field) => (field || "").toString().toLowerCase().includes(searchText.toLowerCase()));
|
||||
};
|
||||
|
||||
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
|
||||
.reduce(
|
||||
@@ -236,7 +222,8 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
||||
onClick={resetChanges}
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
textDecoration: "underline"
|
||||
textDecoration: "underline",
|
||||
color: "var(--reset-link-color)"
|
||||
}}
|
||||
>
|
||||
{t("general.actions.reset")}
|
||||
@@ -269,7 +256,6 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
||||
data={data}
|
||||
onColumnAdd={addColumn}
|
||||
/>
|
||||
|
||||
<ProductionListConfigManager
|
||||
columns={columns}
|
||||
setColumns={setColumns}
|
||||
@@ -305,24 +291,22 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
||||
{...(Production_List_Status_Colors.treatment === "on" && {
|
||||
onRow: (record, index) => {
|
||||
if (!bodyshop.md_ro_statuses.production_colors) return null;
|
||||
|
||||
const color = bodyshop.md_ro_statuses.production_colors.find((x) => x.status === record.status);
|
||||
|
||||
if (!color) {
|
||||
if (index % 2 === 0)
|
||||
return {
|
||||
style: {
|
||||
backgroundColor: `rgb(236, 236, 236)`
|
||||
backgroundColor: "var(--table-row-even-bg)"
|
||||
}
|
||||
};
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
className: "rowWithColor",
|
||||
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)"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -19,11 +19,11 @@
|
||||
// }
|
||||
|
||||
.imex-event-arrived {
|
||||
background-color: rgba(4, 141, 4, 0.4);
|
||||
background-color: var(--event-arrived-bg);
|
||||
}
|
||||
|
||||
.imex-event-block {
|
||||
background-color: rgba(212, 2, 2, 0.6);
|
||||
background-color: var(--event-block-bg);
|
||||
}
|
||||
|
||||
.rbc-month-view {
|
||||
@@ -31,12 +31,12 @@
|
||||
}
|
||||
|
||||
.rbc-event.rbc-selected {
|
||||
background-color: slategrey;
|
||||
background-color: var(--event-selected-bg);
|
||||
}
|
||||
|
||||
.imex-calendar-load {
|
||||
max-width: 12rem;
|
||||
position: relative;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
transform: translate(-50%);
|
||||
}
|
||||
|
||||
@@ -36,12 +36,14 @@ export function ScheduleCalendarWrapperComponent({
|
||||
const search = queryString.parse(useLocation().search);
|
||||
const history = useNavigate();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleEventPropStyles = (event, start, end, isSelected) => {
|
||||
return {
|
||||
...(event.color && !((search.view || defaultView) === "agenda")
|
||||
? {
|
||||
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.Panel
|
||||
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%" }}>
|
||||
{problemJobs.map((problem) => (
|
||||
@@ -70,7 +74,7 @@ export function ScheduleCalendarWrapperComponent({
|
||||
message={
|
||||
<Trans
|
||||
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={{
|
||||
ro_number: problem.ro_number,
|
||||
code: problem.code
|
||||
@@ -91,7 +95,7 @@ export function ScheduleCalendarWrapperComponent({
|
||||
message={
|
||||
<Trans
|
||||
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={{
|
||||
ro_number: problem.ro_number,
|
||||
code: problem.code
|
||||
@@ -102,12 +106,11 @@ export function ScheduleCalendarWrapperComponent({
|
||||
))}
|
||||
</Space>
|
||||
))}
|
||||
|
||||
<Calendar
|
||||
events={data}
|
||||
defaultView={search.view || defaultView || "week"}
|
||||
date={selectedDate}
|
||||
onNavigate={(date, view, action) => {
|
||||
onNavigate={(date) => {
|
||||
search.date = date.toISOString().substr(0, 10);
|
||||
history({ search: queryString.stringify(search) });
|
||||
}}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { SyncOutlined } from "@ant-design/icons";
|
||||
import { Button, Card, Checkbox, Col, Row, Select, Space } from "antd";
|
||||
import { PageHeader } from "@ant-design/pro-layout";
|
||||
import { t } from "i18next";
|
||||
import React, { useMemo } from "react";
|
||||
import { useMemo } from "react";
|
||||
import useLocalStorage from "../../utils/useLocalStorage";
|
||||
import ScheduleAtsSummary from "../schedule-ats-summary/schedule-ats-summary.component";
|
||||
import ScheduleCalendarWrapperComponent from "../schedule-calendar-wrapper/scheduler-calendar-wrapper.component";
|
||||
@@ -18,7 +18,7 @@ import _ from "lodash";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
const mapDispatchToProps = () => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ScheduleCalendarComponent);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import queryString from "query-string";
|
||||
import React, { useEffect, useMemo } from "react";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { QUERY_ALL_ACTIVE_APPOINTMENTS } from "../../graphql/appointments.queries";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
@@ -32,7 +32,7 @@ export function ScheduleCalendarContainer({ calculateScheduleLoad }) {
|
||||
startd: range.start,
|
||||
endd: range.end
|
||||
},
|
||||
skip: !!!range.start || !!!range.end,
|
||||
skip: !range.start || !range.end,
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only"
|
||||
});
|
||||
|
||||
@@ -5,26 +5,25 @@ const CustomTooltip = ({ active, payload, label }) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: "white",
|
||||
border: "1px solid gray",
|
||||
backgroundColor: "var(--tooltip-bg)",
|
||||
border: "1px solid var(--tooltip-border)",
|
||||
padding: "0.5rem"
|
||||
}}
|
||||
>
|
||||
<p style={{ margin: "0" }}>{label}</p>
|
||||
{payload.map((data, index) => {
|
||||
const textColor = data.color || "var(--tooltip-text-fallback)";
|
||||
if (data.dataKey === "sales" || data.dataKey === "accSales")
|
||||
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)
|
||||
}).toFormat()}`}</p>
|
||||
);
|
||||
|
||||
return <p style={{ margin: "10px 0", color: data.color }} key={index}>{`${data.name} : ${data.value}`}</p>;
|
||||
return <p style={{ margin: "10px 0", color: textColor }} key={index}>{`${data.name} : ${data.value}`}</p>;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
@@ -3,15 +3,16 @@ const CustomTooltip = ({ active, payload, label }) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: "white",
|
||||
border: "1px solid gray",
|
||||
backgroundColor: "var(--tooltip-bg)",
|
||||
border: "1px solid var(--tooltip-border)",
|
||||
padding: "0.5rem"
|
||||
}}
|
||||
>
|
||||
<p style={{ margin: "0" }}>{label}</p>
|
||||
{payload.map((data, index) => {
|
||||
const textColor = data.color || "var(--tooltip-text-fallback)";
|
||||
return (
|
||||
<p style={{ margin: "10px 0", color: data.color }} key={index}>{`${
|
||||
<p style={{ margin: "10px 0", color: textColor }} key={index}>{`${
|
||||
data.name
|
||||
} : ${data.value.toFixed(1)}`}</p>
|
||||
);
|
||||
@@ -19,7 +20,6 @@ const CustomTooltip = ({ active, payload, label }) => {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
@@ -41,7 +41,6 @@ const ShareToTeamsComponent = ({
|
||||
}) => {
|
||||
const location = useLocation();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const currentUrl =
|
||||
urlOverride ||
|
||||
encodeURIComponent(`${window.location.origin}${location.pathname}${location.search}${location.hash}`);
|
||||
@@ -49,31 +48,24 @@ const ShareToTeamsComponent = ({
|
||||
pageTitleOverride ||
|
||||
encodeURIComponent(typeof document !== "undefined" ? document.title : t("general.actions.sharetoteams"));
|
||||
const messageText = messageTextOverride || encodeURIComponent(t("general.actions.sharetoteams"));
|
||||
|
||||
// Construct the Teams share URL with parameters
|
||||
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
|
||||
const handleShare = () => {
|
||||
const screenWidth = window.screen.width;
|
||||
const screenHeight = window.screen.height;
|
||||
const windowWidth = 600;
|
||||
const windowHeight = 400;
|
||||
|
||||
const left = screenWidth / 2 - windowWidth / 2;
|
||||
const top = screenHeight / 2 - windowHeight / 2;
|
||||
|
||||
const windowFeatures = `width=${windowWidth},height=${windowHeight},left=${left},top=${top}`;
|
||||
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
window.open(teamsShareUrl, "_blank", windowFeatures);
|
||||
};
|
||||
|
||||
// Feature is disabled
|
||||
if (!bodyshop?.md_functionality_toggles?.teams) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (noIcon) {
|
||||
return (
|
||||
<div style={{ cursor: "pointer", ...noIconStyle }} onClick={handleShare}>
|
||||
@@ -81,16 +73,15 @@ const ShareToTeamsComponent = ({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
style={{
|
||||
backgroundColor: "#6264A7",
|
||||
borderColor: "#6264A7",
|
||||
color: "#FFFFFF",
|
||||
backgroundColor: "var(--teams-button-bg)",
|
||||
borderColor: "var(--teams-button-border)",
|
||||
color: "var(--teams-button-text)",
|
||||
...buttonStyle
|
||||
}}
|
||||
icon={<PiMicrosoftTeamsLogo style={{ color: "#FFFFFF", ...buttonIconStyle }} />}
|
||||
icon={<PiMicrosoftTeamsLogo style={{ color: "var(--teams-button-text)", ...buttonIconStyle }} />}
|
||||
onClick={handleShare}
|
||||
title={linkText === null ? t("general.actions.sharetoteams") : linkText}
|
||||
/>
|
||||
|
||||
@@ -51,7 +51,7 @@ export const useFormDataPreservation = (form, bodyshop, featureConfig) => {
|
||||
if (hasDataToPreserve) {
|
||||
form.setFieldsValue(preservationData);
|
||||
}
|
||||
}, [form, bodyshop, featureConfig]);
|
||||
}, [form, featureConfig, bodyshop]);
|
||||
|
||||
const getCompleteFormValues = () => {
|
||||
const currentFormValues = form.getFieldsValue();
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
right: 0;
|
||||
width: 500px;
|
||||
max-width: 500px;
|
||||
background: #fff;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
border: 1px solid #d9d9d9;
|
||||
background: var(--task-bg);
|
||||
color: var(--task-text);
|
||||
border: 1px solid var(--task-border);
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.08), 0 3px 6px rgba(0, 0, 0, 0.06);
|
||||
z-index: 1000;
|
||||
@@ -19,11 +19,11 @@
|
||||
|
||||
.task-header {
|
||||
padding: 4px 10px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
border-bottom: 1px solid var(--task-header-border);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: #fafafa;
|
||||
background: var(--task-header-bg);
|
||||
|
||||
h3 {
|
||||
font-size: 14px;
|
||||
@@ -32,14 +32,14 @@
|
||||
|
||||
.create-task-button {
|
||||
border: none;
|
||||
color: white;
|
||||
color: var(--task-button-text);
|
||||
padding: 4px 12px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
|
||||
&:hover {
|
||||
background-color: #40a9ff;
|
||||
background-color: var(--task-button-hover-bg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,10 +52,9 @@
|
||||
.section-title {
|
||||
padding: 0px 10px;
|
||||
margin: 0px;
|
||||
//font-size: 12px;
|
||||
background: #f5f5f5;
|
||||
background: var(--task-section-bg);
|
||||
font-weight: 650;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
border-bottom: 1px solid var(--task-section-border);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
@@ -68,22 +67,21 @@
|
||||
|
||||
.task-row {
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
border-bottom: 1px solid var(--task-row-border);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
|
||||
&:hover {
|
||||
background: #f5f5f5;
|
||||
background: var(--task-row-hover-bg);
|
||||
}
|
||||
|
||||
.task-title-cell {
|
||||
flex: 1;
|
||||
padding: 6px 8px;
|
||||
vertical-align: top;
|
||||
//font-size: 12px;
|
||||
line-height: 1.2;
|
||||
max-width: 350px; // or whatever fits your layout
|
||||
max-width: 350px;
|
||||
|
||||
.task-title {
|
||||
font-size: 16px;
|
||||
@@ -91,44 +89,42 @@
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100%; // Or a specific width if you want more control
|
||||
max-width: 100%;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.task-ro-number {
|
||||
margin-top: 20px;
|
||||
color: #1677ff;
|
||||
color: var(--task-ro-number);
|
||||
}
|
||||
}
|
||||
|
||||
.task-due-cell {
|
||||
padding: 6px 8px;
|
||||
vertical-align: top;
|
||||
//font-size: 12px;
|
||||
line-height: 1.2;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
color: var(--task-due-text);
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
margin: 8px auto;
|
||||
padding: 4px 10px;
|
||||
background-color: #1677ff;
|
||||
color: white;
|
||||
background-color: var(--task-button-bg);
|
||||
color: var(--task-button-text);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
//font-size: 12px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: #4096ff;
|
||||
background-color: var(--task-button-hover-bg);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: #d9d9d9;
|
||||
background-color: var(--task-button-disabled-bg);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
@@ -137,7 +133,7 @@
|
||||
.error-message {
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
color: var(--task-message-text);
|
||||
}
|
||||
|
||||
.loading-footer {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
td {
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #ddd;
|
||||
border-bottom: 1px solid var(--table-border-color);
|
||||
|
||||
.ant-form-item {
|
||||
margin-bottom: 0px !important;
|
||||
@@ -14,6 +14,6 @@
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background-color: #f5f5f5;
|
||||
background-color: var(--table-hover-bg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from "@ant-design/icons";
|
||||
import { Button, Card, Result } from "antd";
|
||||
import i18n from "i18next";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import { store } from "../../redux/store.js";
|
||||
@@ -21,7 +21,6 @@ import "./upsell.styles.scss";
|
||||
export default function UpsellComponent({ featureName, subFeatureName, upsell, disableMask }) {
|
||||
const { t } = useTranslation();
|
||||
const resultProps = upsell || upsellEnum[featureName][subFeatureName];
|
||||
|
||||
const componentRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -34,12 +33,10 @@ export default function UpsellComponent({ featureName, subFeatureName, upsell, d
|
||||
mask.style.left = 0;
|
||||
mask.style.width = "100%";
|
||||
mask.style.height = "100%";
|
||||
mask.style.backgroundColor = "rgba(0, 0, 0, 0.05)";
|
||||
mask.style.backgroundColor = "var(--mask-bg)";
|
||||
// mask.style.zIndex = 9999;
|
||||
parentElement.style.position = "relative";
|
||||
|
||||
parentElement.prepend(mask);
|
||||
|
||||
return () => {
|
||||
parentElement.removeChild(mask);
|
||||
};
|
||||
@@ -47,18 +44,22 @@ export default function UpsellComponent({ featureName, subFeatureName, upsell, d
|
||||
}, [disableMask]);
|
||||
|
||||
if (!resultProps) return <Result status="info" title={t("upsell.messages.generic")} />;
|
||||
|
||||
return (
|
||||
<div ref={componentRef}>
|
||||
<Result status="info" icon={<AppstoreAddOutlined />} {...resultProps} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
//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 }) {
|
||||
const resultProps = upsell || upsellEnum[featureName][subFeatureName];
|
||||
return (
|
||||
<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">
|
||||
<Card size="small">
|
||||
<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
|
||||
export const upsellEnum = () => {
|
||||
const { currentUser, bodyshop } = store.getState().user;
|
||||
|
||||
const [first_name, ...last_name] = currentUser?.displayName ? currentUser.displayName.split(" ") : [];
|
||||
const LearnMoreLink = encodeURI(
|
||||
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 || ""}`
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
bills: {
|
||||
autoreconcile: {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
.mask-wrapper {
|
||||
position: relative;
|
||||
//Newly added
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
@@ -8,12 +7,8 @@
|
||||
}
|
||||
|
||||
.mask-content {
|
||||
// filter: blur(5px);
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
background-color: var(--mask-content-bg);
|
||||
pointer-events: none;
|
||||
|
||||
//Newly added
|
||||
//width: 100%;
|
||||
}
|
||||
|
||||
.mask-overlay {
|
||||
@@ -22,35 +17,8 @@
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 10;
|
||||
// width: 100%
|
||||
}
|
||||
|
||||
.mask-overlay .ant-card {
|
||||
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%;
|
||||
// }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//import {useMutation, useQuery } from "@apollo/client";
|
||||
import { Button, Form, Layout, Result, Typography } from "antd";
|
||||
import axios from "axios";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { useParams } from "react-router-dom";
|
||||
@@ -16,7 +16,8 @@ import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({});
|
||||
|
||||
const mapDispatchToProps = () => ({});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(CsiContainerPage);
|
||||
|
||||
@@ -28,7 +29,6 @@ export function CsiContainerPage({ currentUser }) {
|
||||
loading: false,
|
||||
submitted: false
|
||||
});
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const getAxiosData = useCallback(async () => {
|
||||
@@ -39,7 +39,6 @@ export function CsiContainerPage({ currentUser }) {
|
||||
console.log("Unable to attach to crisp instance. ");
|
||||
}
|
||||
setSubmitting((prevSubmitting) => ({ ...prevSubmitting, loading: true }));
|
||||
|
||||
const response = await axios.post("/csi/lookup", {
|
||||
surveyId
|
||||
});
|
||||
@@ -91,7 +90,7 @@ export function CsiContainerPage({ currentUser }) {
|
||||
setSubmitting({ ...submitting, loading: true, submitting: true });
|
||||
const result = await axios.post("/csi/submit", { surveyId, values });
|
||||
console.log("result", result);
|
||||
if (!!!result.errors && result.data.update_csi.affected_rows > 0) {
|
||||
if (!result.errors && result.data.update_csi.affected_rows > 0) {
|
||||
setSubmitting({ ...submitting, loading: false, submitted: true });
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -110,7 +109,7 @@ export function CsiContainerPage({ currentUser }) {
|
||||
<Layout style={{ display: "flex", flexDirection: "column" }}>
|
||||
<Layout.Content
|
||||
style={{
|
||||
backgroundColor: "#fff",
|
||||
backgroundColor: "var(--content-bg)",
|
||||
margin: "2em 4em",
|
||||
padding: "2em",
|
||||
overflowY: "auto",
|
||||
@@ -139,7 +138,6 @@ export function CsiContainerPage({ currentUser }) {
|
||||
relateddata: { bodyshop, job },
|
||||
csiquestion: { config: csiquestions }
|
||||
} = axiosResponse.csi_by_pk;
|
||||
|
||||
return (
|
||||
<Layout style={{ display: "flex", flexDirection: "column" }}>
|
||||
<div
|
||||
@@ -184,13 +182,11 @@ export function CsiContainerPage({ currentUser }) {
|
||||
})}
|
||||
</Typography.Paragraph>
|
||||
</div>
|
||||
|
||||
{submitting.error ? <AlertComponent message={submitting.error} type="error" /> : null}
|
||||
|
||||
{submitting.submitted ? (
|
||||
<Layout.Content
|
||||
style={{
|
||||
backgroundColor: "#fff",
|
||||
backgroundColor: "var(--content-bg)",
|
||||
margin: "2em 4em",
|
||||
padding: "2em",
|
||||
overflowY: "auto"
|
||||
@@ -201,7 +197,7 @@ export function CsiContainerPage({ currentUser }) {
|
||||
) : (
|
||||
<Layout.Content
|
||||
style={{
|
||||
backgroundColor: "#fff",
|
||||
backgroundColor: "var(--content-bg)",
|
||||
margin: "2em 4em",
|
||||
padding: "2em",
|
||||
overflowY: "auto"
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import ScheduleCalendarContainer from "../../components/schedule-calendar/schedule-calendar.container";
|
||||
|
||||
export default function SchedulePageComponent() {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.tech-content-container {
|
||||
overflow-y: visible;
|
||||
padding: 1rem;
|
||||
background: #fff;
|
||||
background: var(--tech-content-bg);
|
||||
}
|
||||
|
||||
.tech-layout-container {
|
||||
|
||||
@@ -77,6 +77,14 @@ export const setWssStatus = (status) => ({
|
||||
type: ApplicationActionTypes.SET_WSS_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) => ({
|
||||
type: ApplicationActionTypes.PARTS_ENTRY,
|
||||
|
||||
@@ -16,7 +16,8 @@ const INITIAL_STATE = {
|
||||
},
|
||||
jobReadOnly: false,
|
||||
partnerVersion: null,
|
||||
alerts: {}
|
||||
alerts: {},
|
||||
darkMode: false
|
||||
};
|
||||
|
||||
const applicationReducer = (state = INITIAL_STATE, action) => {
|
||||
@@ -104,6 +105,16 @@ const applicationReducer = (state = INITIAL_STATE, action) => {
|
||||
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:
|
||||
return {
|
||||
...state,
|
||||
|
||||
@@ -25,3 +25,4 @@ export const selectUpdateAvailable = createSelector([selectApplication], (applic
|
||||
export const selectWssStatus = createSelector([selectApplication], (application) => application.wssStatus);
|
||||
export const selectAlerts = createSelector([selectApplication], (application) => application.alerts);
|
||||
export const selectIsPartsEntry = createSelector([selectApplication], (application) => application.isPartsEntry);
|
||||
export const selectDarkMode = createSelector([selectApplication], (application) => application.darkMode);
|
||||
|
||||
@@ -15,6 +15,8 @@ const ApplicationActionTypes = {
|
||||
SET_UPDATE_AVAILABLE: "SET_UPDATE_AVAILABLE",
|
||||
SET_WSS_STATUS: "SET_WSS_STATUS",
|
||||
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;
|
||||
|
||||
@@ -3787,7 +3787,9 @@
|
||||
"actions": {
|
||||
"changepassword": "Change Password",
|
||||
"signout": "Sign Out",
|
||||
"updateprofile": "Update Profile"
|
||||
"updateprofile": "Update Profile",
|
||||
"light_theme": "Switch to Light Theme",
|
||||
"dark_theme": "Switch to Dark Theme"
|
||||
},
|
||||
"errors": {
|
||||
"updating": "Error updating user or association {{message}}"
|
||||
|
||||
@@ -3787,7 +3787,9 @@
|
||||
"actions": {
|
||||
"changepassword": "",
|
||||
"signout": "desconectar",
|
||||
"updateprofile": "Actualización del perfil"
|
||||
"updateprofile": "Actualización del perfil",
|
||||
"light_theme": "",
|
||||
"dark_theme": ""
|
||||
},
|
||||
"errors": {
|
||||
"updating": ""
|
||||
|
||||
@@ -3787,7 +3787,9 @@
|
||||
"actions": {
|
||||
"changepassword": "",
|
||||
"signout": "Déconnexion",
|
||||
"updateprofile": "Mettre à jour le profil"
|
||||
"updateprofile": "Mettre à jour le profil",
|
||||
"light_theme": "",
|
||||
"dark_theme": ""
|
||||
},
|
||||
"errors": {
|
||||
"updating": ""
|
||||
|
||||
@@ -144,6 +144,7 @@ const paymentRefund = async (req, res) => {
|
||||
|
||||
logger.log("intellipay-refund-success", "DEBUG", req.user?.email, null, {
|
||||
requestOptions: options,
|
||||
response: response?.data,
|
||||
...logResponseMeta
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user