Merge remote-tracking branch 'origin/release/2025-08-15' into feature/IO-3255-simplified-part-management, resolve conflicts
This commit is contained in:
@@ -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%;
|
||||
// }
|
||||
|
||||
Reference in New Issue
Block a user