feature/IO-1113-Online-Dark-Mode - Initial Commit

This commit is contained in:
Dave Richer
2025-08-08 10:23:09 -04:00
parent 3737fe457f
commit 93e9e20f6f
35 changed files with 540 additions and 367 deletions

View File

@@ -2,14 +2,14 @@ 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, useState } from "react";
import { useTranslation } from "react-i18next";
import { 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";
// Base Split configuration
@@ -24,19 +24,24 @@ 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 { t } = useTranslation();
const [isDarkMode, setIsDarkMode] = useState(true); // Manage dark mode state
const theme = useMemo(() => getTheme(isDarkMode), [isDarkMode]); // Memoize theme
// Set data-theme attribute on document root
useEffect(() => {
document.documentElement.setAttribute("data-theme", isDarkMode ? "dark" : "light");
return () => document.documentElement.removeAttribute("data-theme"); // Cleanup
}, [isDarkMode]);
return (
<CookiesProvider>
@@ -44,10 +49,9 @@ 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}" })
}
}}
@@ -56,6 +60,10 @@ function AppContainer() {
<SplitFactoryProvider config={config}>
<SplitClientProvider>
<App />
{/* Optional: Button to toggle dark mode for testing */}
<button onClick={() => setIsDarkMode(!isDarkMode)}>
{t("general.toggleDarkMode")}
</button>
</SplitClientProvider>
</SplitFactoryProvider>
</ConfigProvider>

View File

@@ -1,15 +1,225 @@
//Global Styles.
:root {
--table-stripe-bg: #f4f4f4; /* Light mode table stripe */
--menu-divider-color: #74695c; /* Light mode menu divider */
--menu-submenu-text: rgba(255, 255, 255, 0.65); /* Light mode submenu text */
--kanban-column-bg: #ddd; /* Light mode kanban column */
--alert-color: blue; /* Light mode alert */
--completion-soon-color: rgba(255, 140, 0, 0.8); /* Light mode completion soon */
--completion-past-color: rgba(255, 0, 0, 0.8); /* Light mode completion past */
--job-line-manual-color: tomato; /* Light mode job line manual */
--muted-button-color: lightgray; /* Light mode muted button */
--muted-button-hover-color: darkgrey; /* Light mode muted button hover */
--table-border-color: #ddd; /* Light mode table border */
--table-hover-bg: #f5f5f5; /* Light mode table hover */
--popover-bg: #fff; /* Light mode popover background */
--error-text: red; /* Light mode error message */
--no-jobs-text: #888; /* Light mode no jobs message */
--message-yours-bg: #eee; /* Light mode yours message background */
--message-mine-bg-start: #00d0ea; /* Light mode mine message gradient start */
--message-mine-bg-end: #0085d1; /* Light mode mine message gradient end */
--message-mine-text: white; /* Light mode mine message text */
--message-mine-tail-bg: white; /* Light mode mine/yours message tail */
--system-message-bg: #f5f5f5; /* Light mode system message background */
--system-message-text: #555; /* Light mode system message text */
--system-label-text: #888; /* Light mode system label/date text */
--message-icon-color: whitesmoke; /* Light mode message icon */
--eula-card-bg: lightgray; /* Light mode eula card background */
--notification-bg: #fff; /* Light mode notification background */
--notification-text: rgba(0, 0, 0, 0.85); /* Light mode notification text */
--notification-border: #d9d9d9; /* Light mode notification border */
--notification-header-bg: #fafafa; /* Light mode notification header background */
--notification-header-border: #f0f0f0; /* Light mode notification header border */
--notification-header-text: rgba(0, 0, 0, 0.85); /* Light mode notification header text */
--notification-toggle-icon: #1677ff; /* Light mode notification toggle icon */
--notification-switch-bg: #1677ff; /* Light mode notification switch background */
--notification-btn-link: #1677ff; /* Light mode notification link button */
--notification-btn-link-hover: #69b1ff; /* Light mode notification link button hover */
--notification-btn-link-disabled: rgba(0, 0, 0, 0.25); /* Light mode notification link button disabled */
--notification-btn-link-active: #0958d9; /* Light mode notification link button active */
--notification-read-bg: #fff; /* Light mode notification read background */
--notification-read-text: rgba(0, 0, 0, 0.65); /* Light mode notification read text */
--notification-unread-bg: #f5f5f5; /* Light mode notification unread background */
--notification-unread-text: rgba(0, 0, 0, 0.85); /* Light mode notification unread text */
--notification-item-hover-bg: #fafafa; /* Light mode notification item hover background */
--notification-ro-number: #1677ff; /* Light mode notification RO number */
--notification-relative-time: rgba(0, 0, 0, 0.45); /* Light mode notification relative time */
--alert-bg: #fff1f0; /* Light mode alert background */
--alert-text: rgba(0, 0, 0, 0.85); /* Light mode alert text */
--alert-border: #ffa39e; /* Light mode alert border */
--alert-message: #ff4d4f; /* Light mode alert message */
--share-badge-bg: #cccccc; /* Light mode share badge background */
--column-header-bg: #d0d0d0; /* Light mode column header background */
--footer-bg: #d0d0d0; /* Light mode footer background */
--tech-icon-color: orangered; /* Light mode tech icon color */
--clone-border-color: #1890ff; /* Light mode clone border color */
--event-arrived-bg: rgba(4, 141, 4, 0.4); /* Light mode arrived event background */
--event-block-bg: rgba(212, 2, 2, 0.6); /* Light mode block event background */
--event-selected-bg: slategrey; /* Light mode selected event background */
--task-bg: #fff; /* Light mode task center background */
--task-text: rgba(0, 0, 0, 0.85); /* Light mode task text */
--task-border: #d9d9d9; /* Light mode task border */
--task-header-bg: #fafafa; /* Light mode task header background */
--task-header-border: #f0f0f0; /* Light mode task header border */
--task-section-bg: #f5f5f5; /* Light mode task section background */
--task-section-border: #e8e8e8; /* Light mode task section border */
--task-row-hover-bg: #f5f5f5; /* Light mode task row hover background */
--task-row-border: #f0f0f0; /* Light mode task row border */
--task-ro-number: #1677ff; /* Light mode task RO number */
--task-due-text: rgba(0, 0, 0, 0.45); /* Light mode task due text */
--task-button-bg: #1677ff; /* Light mode task button background */
--task-button-hover-bg: #4096ff; /* Light mode task button hover background */
--task-button-disabled-bg: #d9d9d9; /* Light mode task button disabled background */
--task-button-text: white; /* Light mode task button text */
--task-message-text: rgba(0, 0, 0, 0.45); /* Light mode task message text */
--mask-bg: rgba(0, 0, 0, 0.05); /* Light mode mask background */
--board-text-color: #393939; /* Light mode board text color */
--section-bg: #e3e3e3; /* Light mode section background */
--detail-text-color: #4d4d4d; /* Light mode detail text color */
--card-selected-bg: rgba(128, 128, 128, 0.2); /* Light mode selected card background */
--card-stripe-even-bg: #f0f2f5; /* Light mode even card background */
--card-stripe-odd-bg: #ffffff; /* Light mode odd card background */
--bar-border-color: #f0f2f5; /* Light mode bar border and background */
--tag-wrapper-bg: #f0f2f5; /* Light mode tag wrapper background */
--tag-wrapper-text: #000; /* Light mode tag wrapper text */
--preview-bg: lightgray; /* Light mode preview background */
--preview-border-color: #2196F3; /* Light mode preview border color */
--event-bg-fallback: #ffffff; /* Light mode event background fallback */
--card-bg-fallback: #ffffff; /* Light mode card background fallback */
--card-text-fallback: black; /* Light mode card text fallback */
--table-row-even-bg: rgb(236, 236, 236); /* Light mode table row even background */
--status-row-bg-fallback: #ffffff; /* Light mode status row fallback background */
--reset-link-color: #0000ff; /* Light mode reset link color */
--error-header-text: tomato; /* Light mode error header text */
--tooltip-bg: white; /* Light mode tooltip background */
--tooltip-border: gray; /* Light mode tooltip border */
--tooltip-text-fallback: black; /* Light mode tooltip text fallback */
--teams-button-bg: #6264A7; /* Light mode Teams button background */
--teams-button-border: #6264A7; /* Light mode Teams button border */
--teams-button-text: #FFFFFF; /* Light mode Teams button text and icon */
--content-bg: #fff; /* Light mode content background */
--legend-bg-fallback: #ffffff; /* Light mode legend background fallback */
--tech-content-bg: #fff; /* Light mode tech content background */
--today-bg: #ffffff; /* Light mode today background */
--today-text: #000000; /* Light mode today text */
--off-range-bg: #f8f8f8; /* Light mode off-range background */
}
[data-theme="dark"] {
--table-stripe-bg: #2a2a2a; /* Dark mode table stripe */
--menu-divider-color: #5c5c5c; /* Dark mode menu divider */
--menu-submenu-text: rgba(255, 255, 255, 0.85); /* Dark mode submenu text */
--kanban-column-bg: #333333; /* Dark mode kanban column */
--alert-color: #4da8ff; /* Dark mode alert */
--completion-soon-color: #ff8c1a; /* Dark mode completion soon */
--completion-past-color: #ff4d4f; /* Dark mode completion past */
--job-line-manual-color: #ff6347; /* Dark mode job line manual */
--muted-button-color: #666666; /* Dark mode muted button */
--muted-button-hover-color: #999999; /* Dark mode muted button hover */
--table-border-color: #5c5c5c; /* Dark mode table border */
--table-hover-bg: #2a2a2a; /* Dark mode table hover */
--popover-bg: #2a2a2a; /* Dark mode popover background */
--error-text: #ff4d4f; /* Dark mode error message */
--no-jobs-text: #999999; /* Dark mode no jobs message */
--message-yours-bg: #2a2a2a; /* Dark mode yours message background */
--message-mine-bg-start: #4da8ff; /* Dark mode mine message gradient start */
--message-mine-bg-end: #326ade; /* Dark mode mine message gradient end */
--message-mine-text: #ffffff; /* Dark mode mine message text */
--message-mine-tail-bg: #1f1f1f; /* Dark mode mine/yours message tail */
--system-message-bg: #333333; /* Dark mode system message background */
--system-message-text: #cccccc; /* Dark mode system message text */
--system-label-text: #999999; /* Dark mode system label/date text */
--message-icon-color: #cccccc; /* Dark mode message icon */
--eula-card-bg: #2a2a2a; /* Dark mode eula card background */
--notification-bg: #2a2a2a; /* Dark mode notification background */
--notification-text: rgba(255, 255, 255, 0.85); /* Dark mode notification text */
--notification-border: #5c5c5c; /* Dark mode notification border */
--notification-header-bg: #333333; /* Dark mode notification header background */
--notification-header-border: #444444; /* Dark mode notification header border */
--notification-header-text: rgba(255, 255, 255, 0.85); /* Dark mode notification header text */
--notification-toggle-icon: #4da8ff; /* Dark mode notification toggle icon */
--notification-switch-bg: #4da8ff; /* Dark mode notification switch background */
--notification-btn-link: #4da8ff; /* Dark mode notification link button */
--notification-btn-link-hover: #80c1ff; /* Dark mode notification link button hover */
--notification-btn-link-disabled: rgba(255, 255, 255, 0.25); /* Dark mode notification link button disabled */
--notification-btn-link-active: #2681ff; /* Dark mode notification link button active */
--notification-read-bg: #2a2a2a; /* Dark mode notification read background */
--notification-read-text: rgba(255, 255, 255, 0.65); /* Dark mode notification read text */
--notification-unread-bg: #333333; /* Dark mode notification unread background */
--notification-unread-text: rgba(255, 255, 255, 0.85); /* Dark mode notification unread text */
--notification-item-hover-bg: #3a3a3a; /* Dark mode notification item hover background */
--notification-ro-number: #4da8ff; /* Dark mode notification RO number */
--notification-relative-time: rgba(255, 255, 255, 0.45); /* Dark mode notification relative time */
--alert-bg: #3a1a1a; /* Dark mode alert background */
--alert-text: rgba(255, 255, 255, 0.85); /* Dark mode alert text */
--alert-border: #ff6666; /* Dark mode alert border */
--alert-message: #ff6666; /* Dark mode alert message */
--share-badge-bg: #666666; /* Dark mode share badge background */
--column-header-bg: #333333; /* Dark mode column header background */
--footer-bg: #333333; /* Dark mode footer background */
--tech-icon-color: #ff4500; /* Dark mode tech icon color */
--clone-border-color: #4da8ff; /* Dark mode clone border color */
--event-arrived-bg: rgba(4, 141, 4, 0.6); /* Dark mode arrived event background */
--event-block-bg: rgba(212, 2, 2, 0.8); /* Dark mode block event background */
--event-selected-bg: #4a5e6e; /* Dark mode selected event background */
--task-bg: #2a2a2a; /* Dark mode task center background */
--task-text: rgba(255, 255, 255, 0.85); /* Dark mode task text */
--task-border: #5c5c5c; /* Dark mode task border */
--task-header-bg: #333333; /* Dark mode task header background */
--task-header-border: #444444; /* Dark mode task header border */
--task-section-bg: #333333; /* Dark mode task section background */
--task-section-border: #444444; /* Dark mode task section border */
--task-row-hover-bg: #3a3a3a; /* Dark mode task row hover background */
--task-row-border: #444444; /* Dark mode task row border */
--task-ro-number: #4da8ff; /* Dark mode task RO number */
--task-due-text: rgba(255, 255, 255, 0.45); /* Dark mode task due text */
--task-button-bg: #4da8ff; /* Dark mode task button background */
--task-button-hover-bg: #80c1ff; /* Dark mode task button hover background */
--task-button-disabled-bg: #666666; /* Dark mode task button disabled background */
--task-button-text: #ffffff; /* Dark mode task button text */
--task-message-text: rgba(255, 255, 255, 0.45); /* Dark mode task message text */
--mask-bg: rgba(255, 255, 255, 0.05); /* Dark mode mask background */
--board-text-color: #cccccc; /* Dark mode board text color */
--section-bg: #333333; /* Dark mode section background */
--detail-text-color: #bbbbbb; /* Dark mode detail text color */
--card-selected-bg: rgba(255, 255, 255, 0.1); /* Dark mode selected card background */
--card-stripe-even-bg: #2a2a2a; /* Dark mode even card background */
--card-stripe-odd-bg: #1f1f1f; /* Dark mode odd card background */
--bar-border-color: #2a2a2a; /* Dark mode bar border and background */
--tag-wrapper-bg: #2a2a2a; /* Dark mode tag wrapper background */
--tag-wrapper-text: #cccccc; /* Dark mode tag wrapper text */
--preview-bg: #2a2a2a; /* Dark mode preview background */
--preview-border-color: #4da8ff; /* Dark mode preview border color */
--event-bg-fallback: #2a2a2a; /* Dark mode event background fallback */
--card-bg-fallback: #2a2a2a; /* Dark mode card background fallback */
--card-text-fallback: #cccccc; /* Dark mode card text fallback */
--table-row-even-bg: #2a2a2a; /* Dark mode table row even background */
--status-row-bg-fallback: #1f1f1f; /* Dark mode status row fallback background */
--reset-link-color: #4da8ff; /* Dark mode reset link color */
--error-header-text: #ff6347; /* Dark mode error header text */
--tooltip-bg: #2a2a2a; /* Dark mode tooltip background */
--tooltip-border: #5c5c5c; /* Dark mode tooltip border */
--tooltip-text-fallback: #cccccc; /* Dark mode tooltip text fallback */
--teams-button-bg: #7b7dc4; /* Dark mode Teams button background */
--teams-button-border: #7b7dc4; /* Dark mode Teams button border */
--teams-button-text: #ffffff; /* Dark mode Teams button text and icon */
--content-bg: #2a2a2a; /* Dark mode content background */
--legend-bg-fallback: #2a2a2a; /* Dark mode legend background fallback */
--tech-content-bg: #2a2a2a; /* Dark mode tech content background */
--today-bg: #4a5e6e; /* Dark mode today background */
--today-text: #ffffff; /* Dark mode today text */
--off-range-bg: #333333; /* Dark mode off-range background */
}
// Global Styles
@import "react-big-calendar/lib/sass/styles";
.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 {
@@ -190,3 +400,19 @@
margin-right: 0;
}
}
// Override react-big-calendar styles for dark mode only
[data-theme="dark"] {
.rbc-today {
background-color: var(--today-bg);
color: var(--today-text);
}
.rbc-off-range {
background-color: var(--off-range-bg);
}
.rbc-day-bg.rbc-today {
background-color: var(--today-bg);
}
}

View File

@@ -4,36 +4,42 @@ import InstanceRenderMgr from "../utils/instanceRenderMgr";
const { defaultAlgorithm, darkAlgorithm } = theme;
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;

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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;

View File

@@ -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"
}}

View File

@@ -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"));
}

View File

@@ -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 {

View File

@@ -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}

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useState } from "react";
import { useCallback, useEffect, useState } from "react";
import dayjs from "../../utils/day";
import 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);

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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>

View File

@@ -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={

View File

@@ -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;
}

View File

@@ -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 {
@@ -89,13 +89,13 @@ export const StyleHorizontal = 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%;
}
`;
@@ -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;
`;

View File

@@ -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)"
}
};
}

View File

@@ -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%);
}

View File

@@ -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) });
}}

View File

@@ -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);

View File

@@ -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"
});

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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}
/>

View File

@@ -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 {

View File

@@ -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);
}
}

View File

@@ -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: {

View File

@@ -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%;
// }

View File

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

View File

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

View File

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

View File

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