Compare commits
141 Commits
feature/IO
...
feature/IO
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90ea2cd699 | ||
|
|
94353bb342 | ||
|
|
1dfb309223 | ||
|
|
29c9fb37a1 | ||
|
|
41d6f0a4bc | ||
|
|
af70c80e09 | ||
|
|
b02d4e0fdd | ||
|
|
27bf8d9ed6 | ||
|
|
582ad03e05 | ||
|
|
babdfe4cc5 | ||
|
|
8a7a94dd70 | ||
|
|
2654519277 | ||
|
|
02eddcbbf4 | ||
|
|
b967bb6d4e | ||
|
|
f60870a087 | ||
|
|
39aa21d985 | ||
|
|
512bb5e013 | ||
|
|
7581b8634e | ||
|
|
78f041a34f | ||
|
|
2c456cbf03 | ||
|
|
da76021802 | ||
|
|
6de06e084b | ||
|
|
cb49c91983 | ||
|
|
b0df5fa91c | ||
|
|
0652404334 | ||
|
|
bd7d8068df | ||
|
|
4dd868130c | ||
|
|
71860cf899 | ||
|
|
3512905264 | ||
|
|
2c072a9e7a | ||
|
|
fee5bee569 | ||
|
|
0a1cdbdfe3 | ||
|
|
8af79989ff | ||
|
|
5d2bdc7ee1 | ||
|
|
255d65e47d | ||
|
|
f0805e0a79 | ||
|
|
c875ade35c | ||
|
|
31b4f4e561 | ||
|
|
9b485bfe45 | ||
|
|
91279c27fe | ||
|
|
2c1844fb13 | ||
|
|
3812a0650e | ||
|
|
b1c5bbb01f | ||
|
|
bd59e40761 | ||
|
|
f440a2b022 | ||
|
|
6262b3ff83 | ||
|
|
0652114013 | ||
|
|
307c77b30c | ||
|
|
3150647ff6 | ||
|
|
f1d7a98fe8 | ||
|
|
be259317f9 | ||
|
|
046d104bfa | ||
|
|
e2258bb91f | ||
|
|
9c693a2b74 | ||
|
|
2be0f3de09 | ||
|
|
3dd4b3dd77 | ||
|
|
de8c2cd5a2 | ||
|
|
b791f9846f | ||
|
|
daa7631056 | ||
|
|
d2f7585ea5 | ||
|
|
14b8a2daef | ||
|
|
90b38d817d | ||
|
|
ace48e2890 | ||
|
|
fb810be5d5 | ||
|
|
50230e9f50 | ||
|
|
86e14967ca | ||
|
|
c45c3b4037 | ||
|
|
c4c11528b9 | ||
|
|
1f9c4e92f1 | ||
|
|
371e148e09 | ||
|
|
33af544ded | ||
|
|
6b8d0ec91c | ||
|
|
5a3ddfad0f | ||
|
|
043c44ed51 | ||
|
|
6d5dbf3145 | ||
|
|
6d8463265c | ||
|
|
0669282432 | ||
|
|
1af511be2f | ||
|
|
bf4dc7e158 | ||
|
|
14b38604a3 | ||
|
|
5de2036fdb | ||
|
|
1629663e15 | ||
|
|
e25f2db2b1 | ||
|
|
cbf5d268ea | ||
|
|
a92a95a9fa | ||
|
|
0be7bf2c8e | ||
|
|
56b810dd40 | ||
|
|
33cfa531b8 | ||
|
|
40c7b706aa | ||
|
|
000ded6649 | ||
|
|
72181e1ff7 | ||
|
|
d73b1d2220 | ||
|
|
88c03ce655 | ||
|
|
8645b434c8 | ||
|
|
38a13bd082 | ||
|
|
3bc5f5d626 | ||
|
|
86a2351316 | ||
|
|
de62e994bd | ||
|
|
d5b1496898 | ||
|
|
d0d8354395 | ||
|
|
3486e16d4e | ||
|
|
e2e02945cc | ||
|
|
404efcaf05 | ||
|
|
9b96460e4f | ||
|
|
618eff4973 | ||
|
|
109c34bebd | ||
|
|
1aab5aa740 | ||
|
|
484cb8e39e | ||
|
|
1ca483d4b0 | ||
|
|
3641363d3d | ||
|
|
fde13436c9 | ||
|
|
e7be4c6e61 | ||
|
|
708fe63852 | ||
|
|
3f579f49b9 | ||
|
|
ddb919e2cc | ||
|
|
27e0f497bb | ||
|
|
b9a9f07d7b | ||
|
|
3a06e813a8 | ||
|
|
c2aaf8844f | ||
|
|
488f79ddc8 | ||
|
|
5704e54e48 | ||
|
|
5fb62aa16b | ||
|
|
f4473d11a8 | ||
|
|
dd469bad12 | ||
|
|
a716535795 | ||
|
|
c2ec476324 | ||
|
|
3ecd29c640 | ||
|
|
01443c478d | ||
|
|
3a9e989d70 | ||
|
|
8839fc0293 | ||
|
|
028bf3c7a0 | ||
|
|
ed05754368 | ||
|
|
7c3043988b | ||
|
|
a16f0df7de | ||
|
|
2562151f6e | ||
|
|
44f2287b07 | ||
|
|
5e96ccdd99 | ||
|
|
965af6da5f | ||
|
|
c5c47e9bfc | ||
|
|
d66fdfb2e0 | ||
|
|
4bc8ff26d2 |
@@ -51,6 +51,7 @@
|
||||
"react-i18next": "^12.2.0",
|
||||
"react-icons": "^4.7.1",
|
||||
"react-image-lightbox": "^5.1.4",
|
||||
"react-intersection-observer": "^9.4.3",
|
||||
"react-number-format": "^5.1.3",
|
||||
"react-redux": "^8.0.5",
|
||||
"react-resizable": "^3.0.4",
|
||||
|
||||
@@ -145,7 +145,11 @@
|
||||
|
||||
//Update row highlighting on production board.
|
||||
.ant-table-tbody > tr.ant-table-row:hover > td {
|
||||
background: #eaeaea !important;
|
||||
background: #e7f3ff !important;
|
||||
}
|
||||
|
||||
.ant-table-tbody > tr.ant-table-row-selected > td {
|
||||
background: #e6f7ff !important;
|
||||
}
|
||||
|
||||
.job-line-manual {
|
||||
@@ -156,3 +160,11 @@
|
||||
td.ant-table-column-sort {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.ant-table-tbody > tr.ant-table-row:nth-child(2n) > td {
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
|
||||
.rowWithColor > td {
|
||||
background-color: var(--bgColor) !important;
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { useApolloClient, useMutation } from "@apollo/client";
|
||||
import { Button, Checkbox, Form, Modal, notification, Space } from "antd";
|
||||
import { Button, Checkbox, Form, Modal, Space, notification } from "antd";
|
||||
import _ from "lodash";
|
||||
import React, { useEffect, useState, useMemo } from "react";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { INSERT_NEW_BILL } from "../../graphql/bills.queries";
|
||||
import { UPDATE_INVENTORY_LINES } from "../../graphql/inventory.queries";
|
||||
import { UPDATE_JOB_LINE } from "../../graphql/jobs-lines.queries";
|
||||
import {
|
||||
QUERY_JOB_LBR_ADJUSTMENTS,
|
||||
UPDATE_JOB,
|
||||
} from "../../graphql/jobs.queries";
|
||||
import { MUTATION_MARK_RETURN_RECEIVED } from "../../graphql/parts-orders.queries";
|
||||
import { UPDATE_INVENTORY_LINES } from "../../graphql/inventory.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||
import { selectBillEnterModal } from "../../redux/modals/modals.selectors";
|
||||
@@ -20,15 +20,15 @@ import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import confirmDialog from "../../utils/asyncConfirm";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import BillFormContainer from "../bill-form/bill-form.container";
|
||||
import { CalculateBillTotal } from "../bill-form/bill-form.totals.utility";
|
||||
import { handleUpload } from "../documents-upload/documents-upload.utility";
|
||||
import { handleUpload as handleLocalUpload } from "../documents-local-upload/documents-local-upload.utility";
|
||||
import useLocalStorage from "../../utils/useLocalStorage";
|
||||
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
import confirmDialog from "../../utils/asyncConfirm";
|
||||
import useLocalStorage from "../../utils/useLocalStorage";
|
||||
import BillFormContainer from "../bill-form/bill-form.container";
|
||||
import { CalculateBillTotal } from "../bill-form/bill-form.totals.utility";
|
||||
import { handleUpload as handleLocalUpload } from "../documents-local-upload/documents-local-upload.utility";
|
||||
import { handleUpload } from "../documents-upload/documents-upload.utility";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
billEnterModal: selectBillEnterModal,
|
||||
@@ -126,6 +126,17 @@ function BillEnterModalContainer({
|
||||
deductedfromlbr: deductedfromlbr,
|
||||
lbr_adjustment,
|
||||
joblineid: i.joblineid === "noline" ? null : i.joblineid,
|
||||
applicable_taxes: {
|
||||
federal:
|
||||
(i.applicable_taxes && i.applicable_taxes.federal) ||
|
||||
false,
|
||||
state:
|
||||
(i.applicable_taxes && i.applicable_taxes.state) ||
|
||||
false,
|
||||
local:
|
||||
(i.applicable_taxes && i.applicable_taxes.local) ||
|
||||
false,
|
||||
},
|
||||
};
|
||||
}),
|
||||
},
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
import { Badge, List, Tag } from "antd";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
AutoSizer,
|
||||
CellMeasurer,
|
||||
CellMeasurerCache,
|
||||
List as VirtualizedList,
|
||||
} from "react-virtualized";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { setSelectedConversation } from "../../redux/messaging/messaging.actions";
|
||||
import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors";
|
||||
import { TimeAgoFormatter } from "../../utils/DateFormatter";
|
||||
import PhoneFormatter from "../../utils/PhoneFormatter";
|
||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||
|
||||
import "./chat-conversation-list.styles.scss";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
@@ -18,59 +25,95 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
dispatch(setSelectedConversation(conversationId)),
|
||||
});
|
||||
|
||||
export function ChatConversationListComponent({
|
||||
function ChatConversationListComponent({
|
||||
conversationList,
|
||||
selectedConversation,
|
||||
setSelectedConversation,
|
||||
loadMoreConversations,
|
||||
}) {
|
||||
const cache = new CellMeasurerCache({
|
||||
fixedWidth: true,
|
||||
defaultHeight: 60,
|
||||
});
|
||||
|
||||
const rowRenderer = ({ index, key, style, parent }) => {
|
||||
const item = conversationList[index];
|
||||
|
||||
return (
|
||||
<CellMeasurer
|
||||
key={key}
|
||||
cache={cache}
|
||||
parent={parent}
|
||||
columnIndex={0}
|
||||
rowIndex={index}
|
||||
>
|
||||
<List.Item
|
||||
onClick={() => setSelectedConversation(item.id)}
|
||||
className={`chat-list-item ${
|
||||
item.id === selectedConversation
|
||||
? "chat-list-selected-conversation"
|
||||
: null
|
||||
}`}
|
||||
style={style}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "inline-block",
|
||||
}}
|
||||
>
|
||||
{item.label && <div className="chat-name">{item.label}</div>}
|
||||
{item.job_conversations.length > 0 ? (
|
||||
<div className="chat-name">
|
||||
{item.job_conversations.map((j, idx) => (
|
||||
<div key={idx}>
|
||||
<OwnerNameDisplay ownerObject={j.job} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<PhoneFormatter>{item.phone_num}</PhoneFormatter>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ display: "inline-block" }}>
|
||||
<div>
|
||||
{item.job_conversations.length > 0
|
||||
? item.job_conversations.map((j, idx) => (
|
||||
<Tag key={idx} className="ro-number-tag">
|
||||
{j.job.ro_number}
|
||||
</Tag>
|
||||
))
|
||||
: null}
|
||||
</div>
|
||||
<TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>
|
||||
</div>
|
||||
<Badge count={item.messages_aggregate.aggregate.count || 0} />
|
||||
</List.Item>
|
||||
</CellMeasurer>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="chat-list-container">
|
||||
<List
|
||||
bordered
|
||||
dataSource={conversationList}
|
||||
renderItem={(item) => (
|
||||
<List.Item
|
||||
key={item.id}
|
||||
onClick={() => setSelectedConversation(item.id)}
|
||||
className={`chat-list-item ${
|
||||
item.id === selectedConversation
|
||||
? "chat-list-selected-conversation"
|
||||
: null
|
||||
}`}
|
||||
>
|
||||
<div sryle={{ display: "inline-block" }}>
|
||||
{item.label && <div className="chat-name">{item.label}</div>}
|
||||
{item.job_conversations.length > 0 ? (
|
||||
<div className="chat-name">
|
||||
{item.job_conversations.map((j, idx) => (
|
||||
<div key={idx}>
|
||||
<OwnerNameDisplay ownerObject={j.job} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<PhoneFormatter>{item.phone_num}</PhoneFormatter>
|
||||
)}
|
||||
</div>
|
||||
<div sryle={{ display: "inline-block" }}>
|
||||
<div>
|
||||
{item.job_conversations.length > 0
|
||||
? item.job_conversations.map((j, idx) => (
|
||||
<Tag key={idx} className="ro-number-tag">
|
||||
{j.job.ro_number}
|
||||
</Tag>
|
||||
))
|
||||
: null}
|
||||
</div>
|
||||
<TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>
|
||||
</div>
|
||||
<Badge count={item.messages_aggregate.aggregate.count || 0} />
|
||||
</List.Item>
|
||||
<AutoSizer>
|
||||
{({ height, width }) => (
|
||||
<VirtualizedList
|
||||
height={height}
|
||||
width={width}
|
||||
rowCount={conversationList.length}
|
||||
rowHeight={cache.rowHeight}
|
||||
rowRenderer={rowRenderer}
|
||||
onScroll={({ scrollTop, scrollHeight, clientHeight }) => {
|
||||
if (scrollTop + clientHeight === scrollHeight) {
|
||||
loadMoreConversations();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</AutoSizer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
}
|
||||
.chat-list-container {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
border: 1px solid gainsboro;
|
||||
}
|
||||
|
||||
.chat-list-item {
|
||||
@@ -21,4 +22,6 @@
|
||||
.ro-number-tag {
|
||||
align-self: baseline;
|
||||
}
|
||||
padding: 12px 24px;
|
||||
border-bottom: 1px solid gainsboro;
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ import {
|
||||
ShrinkOutlined,
|
||||
SyncOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { useLazyQuery, useQuery } from "@apollo/client";
|
||||
import { Badge, Card, Col, Row, Space, Tag, Tooltip, Typography } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -47,12 +47,13 @@ export function ChatPopupComponent({
|
||||
...(pollInterval > 0 ? { pollInterval } : {}),
|
||||
});
|
||||
|
||||
const { loading, data, refetch, called } = useQuery(CONVERSATION_LIST_QUERY, {
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
skip: !chatVisible,
|
||||
...(pollInterval > 0 ? { pollInterval } : {}),
|
||||
});
|
||||
const [getConversations, { loading, data, refetch, fetchMore }] =
|
||||
useLazyQuery(CONVERSATION_LIST_QUERY, {
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
skip: !chatVisible,
|
||||
...(pollInterval > 0 ? { pollInterval } : {}),
|
||||
});
|
||||
|
||||
const fcmToken = sessionStorage.getItem("fcmtoken");
|
||||
|
||||
@@ -65,15 +66,22 @@ export function ChatPopupComponent({
|
||||
}, [fcmToken]);
|
||||
|
||||
useEffect(() => {
|
||||
if (called && chatVisible) refetch();
|
||||
}, [chatVisible, called, refetch]);
|
||||
if (chatVisible)
|
||||
getConversations({
|
||||
variables: {
|
||||
offset: 0,
|
||||
},
|
||||
});
|
||||
}, [chatVisible, getConversations]);
|
||||
|
||||
// const unreadCount = data
|
||||
// ? data.conversations.reduce(
|
||||
// (acc, val) => val.messages_aggregate.aggregate.count + acc,
|
||||
// 0
|
||||
// )
|
||||
// : 0;
|
||||
const loadMoreConversations = useCallback(() => {
|
||||
if (data)
|
||||
fetchMore({
|
||||
variables: {
|
||||
offset: data.conversations.length,
|
||||
},
|
||||
});
|
||||
}, [data, fetchMore]);
|
||||
|
||||
const unreadCount = unreadData?.messages_aggregate.aggregate.count || 0;
|
||||
|
||||
@@ -110,6 +118,7 @@ export function ChatPopupComponent({
|
||||
) : (
|
||||
<ChatConversationListComponent
|
||||
conversationList={data ? data.conversations : []}
|
||||
loadMoreConversations={loadMoreConversations}
|
||||
/>
|
||||
)}
|
||||
</Col>
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
import {
|
||||
BranchesOutlined,
|
||||
ExclamationCircleFilled,
|
||||
PauseCircleOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { Card, Space, Table, Tooltip } from "antd";
|
||||
import moment from "moment";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import ChatOpenButton from "../../chat-open-button/chat-open-button.component";
|
||||
import OwnerNameDisplay from "../../owner-name-display/owner-name-display.component";
|
||||
import DashboardRefreshRequired from "../refresh-required.component";
|
||||
|
||||
export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
||||
const { t } = useTranslation();
|
||||
const [state, setState] = useState({
|
||||
sortedInfo: {},
|
||||
});
|
||||
if (!data) return null;
|
||||
if (!data.scheduled_in_today)
|
||||
return <DashboardRefreshRequired {...cardProps} />;
|
||||
|
||||
const appt = []; // Flatten Data
|
||||
data.scheduled_in_today.forEach((item) => {
|
||||
var i = {
|
||||
canceled: item.canceled,
|
||||
id: item.id,
|
||||
alt_transport: item.job.alt_transport,
|
||||
clm_no: item.job.clm_no,
|
||||
jobid: item.job.jobid,
|
||||
ins_co_nm: item.job.ins_co_nm,
|
||||
iouparent: item.job.iouparent,
|
||||
ownerid: item.job.ownerid,
|
||||
ownr_co_nm: item.job.ownr_co_nm,
|
||||
ownr_ea: item.job.ownr_ea,
|
||||
ownr_fn: item.job.ownr_fn,
|
||||
ownr_ln: item.job.ownr_ln,
|
||||
ownr_ph1: item.job.ownr_ph1,
|
||||
ownr_ph2: item.job.ownr_ph2,
|
||||
production_vars: item.job.production_vars,
|
||||
ro_number: item.job.ro_number,
|
||||
suspended: item.job.suspended,
|
||||
v_make_desc: item.job.v_make_desc,
|
||||
v_model_desc: item.job.v_model_desc,
|
||||
v_model_yr: item.job.v_model_yr,
|
||||
v_vin: item.job.v_vin,
|
||||
vehicleid: item.job.vehicleid,
|
||||
note: item.note,
|
||||
start: moment(item.start).format("hh:mm a"),
|
||||
title: item.title,
|
||||
};
|
||||
appt.push(i);
|
||||
});
|
||||
appt.sort ( function (a, b) { return new Date(a.start) - new Date(b.start); });
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t("jobs.fields.ro_number"),
|
||||
dataIndex: "ro_number",
|
||||
key: "ro_number",
|
||||
render: (text, record) => (
|
||||
<Link
|
||||
to={"/manage/jobs/" + record.jobid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Space>
|
||||
{record.ro_number || t("general.labels.na")}
|
||||
{record.production_vars && record.production_vars.alert ? (
|
||||
<ExclamationCircleFilled className="production-alert" />
|
||||
) : null}
|
||||
{record.suspended && (
|
||||
<PauseCircleOutlined style={{ color: "orangered" }} />
|
||||
)}
|
||||
{record.iouparent && (
|
||||
<Tooltip title={t("jobs.labels.iou")}>
|
||||
<BranchesOutlined style={{ color: "orangered" }} />
|
||||
</Tooltip>
|
||||
)}
|
||||
</Space>
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.owner"),
|
||||
dataIndex: "owner",
|
||||
key: "owner",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => {
|
||||
return record.ownerid ? (
|
||||
<Link
|
||||
to={"/manage/owners/" + record.ownerid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<OwnerNameDisplay ownerObject={record} />
|
||||
</Link>
|
||||
) : (
|
||||
<span>
|
||||
<OwnerNameDisplay ownerObject={record} />
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ownr_ph1"),
|
||||
dataIndex: "ownr_ph1",
|
||||
key: "ownr_ph1",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => (
|
||||
<ChatOpenButton phone={record.ownr_ph1} jobid={record.jobid} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ownr_ph2"),
|
||||
dataIndex: "ownr_ph2",
|
||||
key: "ownr_ph2",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => (
|
||||
<ChatOpenButton phone={record.ownr_ph2} jobid={record.jobid} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ownr_ea"),
|
||||
dataIndex: "ownr_ea",
|
||||
key: "ownr_ea",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => (
|
||||
<ChatOpenButton phone={record.ownr_ea} jobid={record.jobid} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.vehicle"),
|
||||
dataIndex: "vehicle",
|
||||
key: "vehicle",
|
||||
ellipsis: true,
|
||||
render: (text, record) => {
|
||||
return record.vehicleid ? (
|
||||
<Link
|
||||
to={"/manage/vehicles/" + record.vehicleid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
|
||||
record.v_model_desc || ""
|
||||
}`}
|
||||
</Link>
|
||||
) : (
|
||||
<span>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
|
||||
record.v_model_desc || ""
|
||||
}`}</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ins_co_nm"),
|
||||
dataIndex: "ins_co_nm",
|
||||
key: "ins_co_nm",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
},
|
||||
{
|
||||
title: t("appointments.fields.time"),
|
||||
dataIndex: "start",
|
||||
key: "start",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
},
|
||||
{
|
||||
title: t("appointments.fields.alt_transport"),
|
||||
dataIndex: "alt_transport",
|
||||
key: "alt_transport",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
},
|
||||
];
|
||||
|
||||
const handleTableChange = (sorter) => {
|
||||
setState({ ...state, sortedInfo: sorter });
|
||||
};
|
||||
|
||||
return (
|
||||
<Card title={t("dashboard.titles.scheduledintoday", {date: moment().startOf("day").format("MM/DD/YYYY")})} {...cardProps}>
|
||||
<div style={{ height: "100%" }}>
|
||||
<Table
|
||||
onChange={handleTableChange}
|
||||
pagination={{ position: "top", defaultPageSize: 50 }}
|
||||
columns={columns}
|
||||
scroll={{ x: true, y: "calc(100% - 2em)" }}
|
||||
rowKey="id"
|
||||
style={{ height: "85%" }}
|
||||
dataSource={appt}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export const DashboardScheduledInTodayGql = `
|
||||
scheduled_in_today: appointments(where: {start: {_gte: "${moment()
|
||||
.startOf("day")
|
||||
.toISOString()}", _lte: "${moment()
|
||||
.endOf("day")
|
||||
.toISOString()}"}, canceled: {_eq: false}, block: {_neq: true}}) {
|
||||
canceled
|
||||
id
|
||||
job {
|
||||
alt_transport
|
||||
clm_no
|
||||
jobid: id
|
||||
ins_co_nm
|
||||
iouparent
|
||||
ownerid
|
||||
ownr_co_nm
|
||||
ownr_ea
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
production_vars
|
||||
ro_number
|
||||
suspended
|
||||
v_make_desc
|
||||
v_model_desc
|
||||
v_model_yr
|
||||
v_vin
|
||||
vehicleid
|
||||
}
|
||||
note
|
||||
start
|
||||
title
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,210 @@
|
||||
import {
|
||||
BranchesOutlined,
|
||||
ExclamationCircleFilled,
|
||||
PauseCircleOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { Card, Space, Table, Tooltip } from "antd";
|
||||
import moment from "moment";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import ChatOpenButton from "../../chat-open-button/chat-open-button.component";
|
||||
import OwnerNameDisplay from "../../owner-name-display/owner-name-display.component";
|
||||
import DashboardRefreshRequired from "../refresh-required.component";
|
||||
|
||||
export default function DashboardScheduledOutToday({ data, ...cardProps }) {
|
||||
const { t } = useTranslation();
|
||||
const [state, setState] = useState({
|
||||
sortedInfo: {},
|
||||
});
|
||||
if (!data) return null;
|
||||
if (!data.scheduled_out_today)
|
||||
return <DashboardRefreshRequired {...cardProps} />;
|
||||
|
||||
data.scheduled_out_today.forEach((item) => {
|
||||
item.scheduled_completion= moment(item.scheduled_completion).format("hh:mm a")
|
||||
});
|
||||
data.scheduled_out_today.sort(function (a, b) {
|
||||
return new Date(a.scheduled_completion) - new Date(b.scheduled_completion);
|
||||
});
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t("jobs.fields.ro_number"),
|
||||
dataIndex: "ro_number",
|
||||
key: "ro_number",
|
||||
render: (text, record) => (
|
||||
<Link
|
||||
to={"/manage/jobs/" + record.jobid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Space>
|
||||
{record.ro_number || t("general.labels.na")}
|
||||
{record.production_vars && record.production_vars.alert ? (
|
||||
<ExclamationCircleFilled className="production-alert" />
|
||||
) : null}
|
||||
{record.suspended && (
|
||||
<PauseCircleOutlined style={{ color: "orangered" }} />
|
||||
)}
|
||||
{record.iouparent && (
|
||||
<Tooltip title={t("jobs.labels.iou")}>
|
||||
<BranchesOutlined style={{ color: "orangered" }} />
|
||||
</Tooltip>
|
||||
)}
|
||||
</Space>
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.owner"),
|
||||
dataIndex: "owner",
|
||||
key: "owner",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => {
|
||||
return record.ownerid ? (
|
||||
<Link
|
||||
to={"/manage/owners/" + record.ownerid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<OwnerNameDisplay ownerObject={record} />
|
||||
</Link>
|
||||
) : (
|
||||
<span>
|
||||
<OwnerNameDisplay ownerObject={record} />
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ownr_ph1"),
|
||||
dataIndex: "ownr_ph1",
|
||||
key: "ownr_ph1",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => (
|
||||
<ChatOpenButton phone={record.ownr_ph1} jobid={record.jobid} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ownr_ph2"),
|
||||
dataIndex: "ownr_ph2",
|
||||
key: "ownr_ph2",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => (
|
||||
<ChatOpenButton phone={record.ownr_ph2} jobid={record.jobid} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ownr_ea"),
|
||||
dataIndex: "ownr_ea",
|
||||
key: "ownr_ea",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => (
|
||||
<ChatOpenButton phone={record.ownr_ea} jobid={record.jobid} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.vehicle"),
|
||||
dataIndex: "vehicle",
|
||||
key: "vehicle",
|
||||
ellipsis: true,
|
||||
render: (text, record) => {
|
||||
return record.vehicleid ? (
|
||||
<Link
|
||||
to={"/manage/vehicles/" + record.vehicleid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
|
||||
record.v_model_desc || ""
|
||||
}`}
|
||||
</Link>
|
||||
) : (
|
||||
<span>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
|
||||
record.v_model_desc || ""
|
||||
}`}</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ins_co_nm"),
|
||||
dataIndex: "ins_co_nm",
|
||||
key: "ins_co_nm",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.scheduled_completion"),
|
||||
dataIndex: "scheduled_completion",
|
||||
key: "scheduled_completion",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
},
|
||||
{
|
||||
title: t("appointments.fields.alt_transport"),
|
||||
dataIndex: "alt_transport",
|
||||
key: "alt_transport",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
},
|
||||
];
|
||||
|
||||
const handleTableChange = (sorter) => {
|
||||
setState({ ...state, sortedInfo: sorter });
|
||||
};
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={t("dashboard.titles.scheduledouttoday", {
|
||||
date: moment().startOf("day").format("MM/DD/YYYY"),
|
||||
})}
|
||||
{...cardProps}
|
||||
>
|
||||
<div style={{ height: "100%" }}>
|
||||
<Table
|
||||
onChange={handleTableChange}
|
||||
pagination={{ position: "top", defaultPageSize: 50 }}
|
||||
columns={columns}
|
||||
scroll={{ x: true, y: "calc(100% - 2em)" }}
|
||||
rowKey="id"
|
||||
style={{ height: "85%" }}
|
||||
dataSource={data.scheduled_out_today}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export const DashboardScheduledOutTodayGql = `
|
||||
scheduled_out_today: jobs(where: {
|
||||
date_invoiced: {_is_null: true},
|
||||
ro_number: {_is_null: false},
|
||||
voided: {_eq: false},
|
||||
scheduled_completion: {_gte: "${moment().startOf("day").toISOString()}",
|
||||
_lte: "${moment().endOf("day").toISOString()}"}}) {
|
||||
alt_transport
|
||||
clm_no
|
||||
jobid: id
|
||||
ins_co_nm
|
||||
iouparent
|
||||
ownerid
|
||||
ownr_co_nm
|
||||
ownr_ea
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
production_vars
|
||||
ro_number
|
||||
scheduled_completion
|
||||
suspended
|
||||
v_make_desc
|
||||
v_model_desc
|
||||
v_model_yr
|
||||
v_vin
|
||||
vehicleid
|
||||
|
||||
}
|
||||
`;
|
||||
@@ -1,6 +1,6 @@
|
||||
import Icon, { SyncOutlined } from "@ant-design/icons";
|
||||
import { gql, useMutation, useQuery } from "@apollo/client";
|
||||
import { Button, Dropdown, Menu, notification, PageHeader, Space } from "antd";
|
||||
import { Button, Dropdown, Menu, PageHeader, Space, notification } from "antd";
|
||||
import i18next from "i18next";
|
||||
import _ from "lodash";
|
||||
import moment from "moment";
|
||||
@@ -37,6 +37,12 @@ import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||
//Combination of the following:
|
||||
// /node_modules/react-grid-layout/css/styles.css
|
||||
// /node_modules/react-resizable/css/styles.css
|
||||
import DashboardScheduledInToday, {
|
||||
DashboardScheduledInTodayGql,
|
||||
} from "../dashboard-components/scheduled-in-today/scheduled-in-today.component";
|
||||
import DashboardScheduledOutToday, {
|
||||
DashboardScheduledOutTodayGql,
|
||||
} from "../dashboard-components/scheduled-out-today/scheduled-out-today.component";
|
||||
import "./dashboard-grid.styles.scss";
|
||||
import { GenerateDashboardData } from "./dashboard-grid.utils";
|
||||
|
||||
@@ -268,6 +274,28 @@ const componentList = {
|
||||
w: 2,
|
||||
h: 2,
|
||||
},
|
||||
ScheduleInToday: {
|
||||
label: i18next.t("dashboard.titles.scheduledintoday", {
|
||||
date: moment().startOf("day").format("MM/DD/YYYY"),
|
||||
}),
|
||||
component: DashboardScheduledInToday,
|
||||
gqlFragment: DashboardScheduledInTodayGql,
|
||||
minW: 10,
|
||||
minH: 2,
|
||||
w: 10,
|
||||
h: 2,
|
||||
},
|
||||
ScheduleOutToday: {
|
||||
label: i18next.t("dashboard.titles.scheduledouttoday", {
|
||||
date: moment().startOf("day").format("MM/DD/YYYY"),
|
||||
}),
|
||||
component: DashboardScheduledOutToday,
|
||||
gqlFragment: DashboardScheduledOutTodayGql,
|
||||
minW: 10,
|
||||
minH: 2,
|
||||
w: 10,
|
||||
h: 2,
|
||||
},
|
||||
};
|
||||
|
||||
const createDashboardQuery = (state) => {
|
||||
@@ -283,8 +311,12 @@ const createDashboardQuery = (state) => {
|
||||
monthly_sales: jobs(where: {_and: [
|
||||
{ voided: {_eq: false}},
|
||||
{date_invoiced: {_gte: "${moment()
|
||||
.startOf("month").startOf('day').toISOString()}"}}, {date_invoiced: {_lte: "${moment()
|
||||
.endOf("month").endOf('day').toISOString()}"}}]}) {
|
||||
.startOf("month")
|
||||
.startOf("day")
|
||||
.toISOString()}"}}, {date_invoiced: {_lte: "${moment()
|
||||
.endOf("month")
|
||||
.endOf("day")
|
||||
.toISOString()}"}}]}) {
|
||||
id
|
||||
ro_number
|
||||
date_invoiced
|
||||
|
||||
@@ -66,7 +66,7 @@ export function DmsAllocationsSummaryAp({ socket, bodyshop, billids, title }) {
|
||||
key: "status",
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ro_number"),
|
||||
title: t("bills.fields.invoice_number"),
|
||||
dataIndex: ["Posting", "Reference"],
|
||||
key: "reference",
|
||||
},
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
Select,
|
||||
Space,
|
||||
Statistic,
|
||||
Switch,
|
||||
Typography,
|
||||
} from "antd";
|
||||
import Dinero from "dinero.js";
|
||||
@@ -183,6 +184,13 @@ export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
|
||||
<Space>
|
||||
<DmsCdkMakes form={form} socket={socket} job={job} />
|
||||
<DmsCdkMakesRefetch />
|
||||
<Form.Item
|
||||
name="dms_unsold"
|
||||
label={t("jobs.fields.dms.dms_unsold")}
|
||||
initialValue={false}
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</Space>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
import { UploadOutlined, UserAddOutlined } from "@ant-design/icons";
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
Dropdown,
|
||||
Form,
|
||||
Input,
|
||||
Menu,
|
||||
Select,
|
||||
Space,
|
||||
Tabs,
|
||||
Upload,
|
||||
Space,
|
||||
Menu,
|
||||
Dropdown,
|
||||
Button,
|
||||
} from "antd";
|
||||
import _ from "lodash";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import EmailDocumentsComponent from "../email-documents/email-documents.component";
|
||||
import _ from "lodash";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectEmailConfig } from "../../redux/email/email.selectors";
|
||||
import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import { CreateExplorerLinkForJob } from "../../utils/localmedia";
|
||||
import { selectEmailConfig } from "../../redux/email/email.selectors";
|
||||
import EmailDocumentsComponent from "../email-documents/email-documents.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -54,6 +54,15 @@ export function EmailOverlayComponent({
|
||||
]),
|
||||
});
|
||||
};
|
||||
const handle_CC_Click = ({ item, key, keyPath }) => {
|
||||
const email = item.props.value;
|
||||
form.setFieldsValue({
|
||||
cc: _.uniq([
|
||||
...(form.getFieldValue("cc") || ""),
|
||||
...(typeof email === "string" ? [email] : email),
|
||||
]),
|
||||
});
|
||||
};
|
||||
|
||||
const menu = (
|
||||
<div>
|
||||
@@ -74,6 +83,25 @@ export function EmailOverlayComponent({
|
||||
</div>
|
||||
);
|
||||
|
||||
const menuCC = (
|
||||
<div>
|
||||
<Menu onClick={handle_CC_Click}>
|
||||
{bodyshop.employees
|
||||
.filter((e) => e.user_email)
|
||||
.map((e, idx) => (
|
||||
<Menu.Item value={e.user_email} key={idx}>
|
||||
{`${e.first_name} ${e.last_name}`}
|
||||
</Menu.Item>
|
||||
))}
|
||||
{bodyshop.md_to_emails.map((e, idx) => (
|
||||
<Menu.Item value={e.emails} key={idx + "group"}>
|
||||
{e.label}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Form.Item
|
||||
@@ -122,7 +150,23 @@ export function EmailOverlayComponent({
|
||||
>
|
||||
<Select mode="tags" tokenSeparators={[",", ";"]} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("emails.fields.cc")} name="cc">
|
||||
<Form.Item
|
||||
label={
|
||||
<Space>
|
||||
{t("emails.fields.cc")}
|
||||
<Dropdown overlay={menuCC}>
|
||||
<a
|
||||
className="ant-dropdown-link"
|
||||
href=" #"
|
||||
onClick={(e) => e.preventDefault()}
|
||||
>
|
||||
<UserAddOutlined />
|
||||
</a>
|
||||
</Dropdown>
|
||||
</Space>
|
||||
}
|
||||
name="cc"
|
||||
>
|
||||
<Select mode="tags" tokenSeparators={[",", ";"]} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
|
||||
@@ -311,7 +311,9 @@ function Header({
|
||||
icon={<SettingOutlined />}
|
||||
>
|
||||
<Menu.Item key="shop" icon={<Icon component={GiSettingsKnobs} />}>
|
||||
<Link to="/manage/shop">{t("menus.header.shop_config")}</Link>
|
||||
<Link to="/manage/shop?tab=info">
|
||||
{t("menus.header.shop_config")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="dashboard" icon={<DashboardFilled />}>
|
||||
<Link to="/manage/dashboard">{t("menus.header.dashboard")}</Link>
|
||||
|
||||
@@ -29,6 +29,7 @@ import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util";
|
||||
import JobsDetaiLheaderCsi from "./jobs-detail-header-actions.csi.component";
|
||||
import DuplicateJob from "./jobs-detail-header-actions.duplicate.util";
|
||||
import JobsDetailHeaderActionsExportcustdataComponent from "./jobs-detail-header-actions.exportcustdata.component";
|
||||
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -461,54 +462,56 @@ export function JobsDetailHeaderActions({
|
||||
)}
|
||||
<JobsDetailHeaderActionsAddevent jobid={job.id} />
|
||||
{!jobRO && job.converted && (
|
||||
<Menu.Item>
|
||||
<Popconfirm
|
||||
title={t("jobs.labels.voidjob")}
|
||||
okText="Yes"
|
||||
cancelText="No"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onConfirm={async () => {
|
||||
//delete the job.
|
||||
const result = await voidJob({
|
||||
variables: {
|
||||
jobId: job.id,
|
||||
job: {
|
||||
status: bodyshop.md_ro_statuses.default_void,
|
||||
voided: true,
|
||||
scheduled_in: null,
|
||||
scheduled_completion: null,
|
||||
inproduction: false,
|
||||
},
|
||||
note: [
|
||||
{
|
||||
jobid: job.id,
|
||||
created_by: currentUser.email,
|
||||
audit: true,
|
||||
text: t("jobs.labels.voidnote"),
|
||||
<RbacWrapper action="jobs:void" noauth>
|
||||
<Menu.Item>
|
||||
<Popconfirm
|
||||
title={t("jobs.labels.voidjob")}
|
||||
okText="Yes"
|
||||
cancelText="No"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onConfirm={async () => {
|
||||
//delete the job.
|
||||
const result = await voidJob({
|
||||
variables: {
|
||||
jobId: job.id,
|
||||
job: {
|
||||
status: bodyshop.md_ro_statuses.default_void,
|
||||
voided: true,
|
||||
scheduled_in: null,
|
||||
scheduled_completion: null,
|
||||
inproduction: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
note: [
|
||||
{
|
||||
jobid: job.id,
|
||||
created_by: currentUser.email,
|
||||
audit: true,
|
||||
text: t("jobs.labels.voidnote"),
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
if (!!!result.errors) {
|
||||
notification["success"]({
|
||||
message: t("jobs.successes.voided"),
|
||||
});
|
||||
//go back to jobs list.
|
||||
history.push(`/manage/`);
|
||||
} else {
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.voiding", {
|
||||
error: JSON.stringify(result.errors),
|
||||
}),
|
||||
});
|
||||
}
|
||||
}}
|
||||
getPopupContainer={(trigger) => trigger.parentNode}
|
||||
>
|
||||
{t("menus.jobsactions.void")}
|
||||
</Popconfirm>
|
||||
</Menu.Item>
|
||||
if (!!!result.errors) {
|
||||
notification["success"]({
|
||||
message: t("jobs.successes.voided"),
|
||||
});
|
||||
//go back to jobs list.
|
||||
history.push(`/manage/`);
|
||||
} else {
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.voiding", {
|
||||
error: JSON.stringify(result.errors),
|
||||
}),
|
||||
});
|
||||
}
|
||||
}}
|
||||
getPopupContainer={(trigger) => trigger.parentNode}
|
||||
>
|
||||
{t("menus.jobsactions.void")}
|
||||
</Popconfirm>
|
||||
</Menu.Item>
|
||||
</RbacWrapper>
|
||||
)}
|
||||
</Menu>
|
||||
);
|
||||
|
||||
@@ -112,7 +112,9 @@ export function JobsList({ bodyshop }) {
|
||||
title: t("jobs.fields.ro_number"),
|
||||
dataIndex: "ro_number",
|
||||
key: "ro_number",
|
||||
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
|
||||
sorter: (a, b) =>
|
||||
parseInt((a.ro_number || "0").replace(/\D/g, "")) -
|
||||
parseInt((b.ro_number || "0").replace(/\D/g, "")),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
|
||||
|
||||
|
||||
@@ -23,17 +23,34 @@ export function PrintCenterJobsComponent({ printCenterModal, bodyshop }) {
|
||||
const { id: jobId, job } = printCenterModal.context;
|
||||
const tempList = TemplateList("job", {});
|
||||
const { t } = useTranslation();
|
||||
const JobsReportsList = Object.keys(tempList)
|
||||
.map((key) => {
|
||||
return tempList[key];
|
||||
})
|
||||
.filter(
|
||||
(temp) =>
|
||||
!temp.regions ||
|
||||
(temp.regions && temp.regions[bodyshop.region_config]) ||
|
||||
(temp.regions &&
|
||||
bodyshop.region_config.includes(Object.keys(temp.regions)) === true)
|
||||
);
|
||||
|
||||
const JobsReportsList =
|
||||
bodyshop.cdk_dealerid === null && bodyshop.pbs_serialnumber === null
|
||||
? Object.keys(tempList)
|
||||
.map((key) => {
|
||||
return tempList[key];
|
||||
})
|
||||
.filter(
|
||||
(temp) =>
|
||||
(!temp.regions ||
|
||||
(temp.regions && temp.regions[bodyshop.region_config]) ||
|
||||
(temp.regions &&
|
||||
bodyshop.region_config.includes(Object.keys(temp.regions)) ===
|
||||
true)) &&
|
||||
(!temp.dms || temp.dms === false)
|
||||
)
|
||||
: Object.keys(tempList)
|
||||
.map((key) => {
|
||||
return tempList[key];
|
||||
})
|
||||
.filter(
|
||||
(temp) =>
|
||||
!temp.regions ||
|
||||
(temp.regions && temp.regions[bodyshop.region_config]) ||
|
||||
(temp.regions &&
|
||||
bodyshop.region_config.includes(Object.keys(temp.regions)) ===
|
||||
true)
|
||||
);
|
||||
|
||||
const filteredJobsReportsList =
|
||||
search !== ""
|
||||
|
||||
@@ -25,6 +25,7 @@ export default function ProductionListDate({
|
||||
// }
|
||||
|
||||
//e.stopPropagation();
|
||||
|
||||
updateAlert({
|
||||
variables: {
|
||||
jobId: record.id,
|
||||
@@ -32,6 +33,11 @@ export default function ProductionListDate({
|
||||
[field]: date,
|
||||
},
|
||||
},
|
||||
optimisticResponse: {
|
||||
update_jobs: {
|
||||
[field]: date,
|
||||
},
|
||||
},
|
||||
}).then(() => {
|
||||
if (record.refetch) record.refetch();
|
||||
if (!time) {
|
||||
@@ -49,9 +55,11 @@ export default function ProductionListDate({
|
||||
(moment().add(1, "day").isSame(moment(record[field]), "day") &&
|
||||
"production-completion-soon"));
|
||||
}
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
//trigger={["click"]}
|
||||
trigger={["click"]}
|
||||
onVisibleChange={(v) => setVisible(v)}
|
||||
visible={visible}
|
||||
style={{
|
||||
height: "19px",
|
||||
|
||||
@@ -246,11 +246,21 @@ export function ProductionListTable({
|
||||
(x) => x.status === record.status
|
||||
);
|
||||
|
||||
if (!color) return null;
|
||||
if (!color) {
|
||||
if (index % 2 === 0)
|
||||
return {
|
||||
style: {
|
||||
backgroundColor: `rgb(236, 236, 236)`,
|
||||
},
|
||||
};
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
className: "rowWithColor",
|
||||
style: {
|
||||
backgroundColor: `rgb(${color.color.r},${color.color.g},${color.color.b},${color.color.a})`,
|
||||
"--bgColor": `rgb(${color.color.r},${color.color.g},${color.color.b},${color.color.a})`,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
@@ -26,6 +26,8 @@ const ret = {
|
||||
"jobs:partsqueue": 4,
|
||||
"jobs:checklist-view": 2,
|
||||
"jobs:list-ready": 1,
|
||||
"jobs:void": 5,
|
||||
|
||||
"bills:enter": 2,
|
||||
"bills:view": 2,
|
||||
"bills:list": 2,
|
||||
|
||||
@@ -12,7 +12,8 @@ import "./schedule-calendar.styles.scss";
|
||||
import JobDetailCards from "../job-detail-cards/job-detail-cards.component";
|
||||
import { selectProblemJobs } from "../../redux/application/application.selectors";
|
||||
import { Alert, Collapse } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -66,10 +67,21 @@ export function ScheduleCalendarWrapperComponent({
|
||||
<Alert
|
||||
key={problem.id}
|
||||
type="error"
|
||||
message={t("appointments.labels.dataconsistency", {
|
||||
ro_number: problem.ro_number,
|
||||
code: problem.code,
|
||||
})}
|
||||
message={
|
||||
<Trans
|
||||
i18nKey="appointments.labels.dataconsistency"
|
||||
components={[
|
||||
<Link
|
||||
to={`/manage/jobs/${problem.id}`}
|
||||
target="_blank"
|
||||
/>,
|
||||
]}
|
||||
values={{
|
||||
ro_number: problem.ro_number,
|
||||
code: problem.code,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</Collapse.Panel>
|
||||
@@ -79,10 +91,18 @@ export function ScheduleCalendarWrapperComponent({
|
||||
<Alert
|
||||
key={problem.id}
|
||||
type="error"
|
||||
message={t("appointments.labels.dataconsistency", {
|
||||
ro_number: problem.ro_number,
|
||||
code: problem.code,
|
||||
})}
|
||||
message={
|
||||
<Trans
|
||||
i18nKey="appointments.labels.dataconsistency"
|
||||
components={[
|
||||
<Link to={`/manage/jobs/${problem.id}`} target="_blank" />,
|
||||
]}
|
||||
values={{
|
||||
ro_number: problem.ro_number,
|
||||
code: problem.code,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
|
||||
@@ -21,6 +21,7 @@ import ScheduleVerifyIntegrity from "../schedule-verify-integrity/schedule-verif
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import _ from "lodash";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
@@ -39,9 +40,39 @@ export function ScheduleCalendarComponent({ data, refetch, bodyshop }) {
|
||||
employeevacation: true,
|
||||
ins_co_nm: null,
|
||||
});
|
||||
const [estimatorsFilter, setEstimatiorsFilter] = useLocalStorage(
|
||||
"estimators",
|
||||
[]
|
||||
);
|
||||
|
||||
const estimators = useMemo(() => {
|
||||
return _.uniq([
|
||||
...data
|
||||
.filter((d) => d.__typename === "appointments")
|
||||
.map((app) =>
|
||||
`${app.job?.est_ct_fn || ""} ${app.job?.est_ct_ln || ""}`.trim()
|
||||
)
|
||||
.filter((e) => e.length > 0),
|
||||
...bodyshop.md_estimators.map((e) =>
|
||||
`${e.est_ct_fn || ""} ${e.est_ct_ln || ""}`.trim()
|
||||
),
|
||||
]);
|
||||
}, [data, bodyshop.md_estimators]);
|
||||
|
||||
const filteredData = useMemo(() => {
|
||||
return data.filter(
|
||||
(d) =>
|
||||
return data.filter((d) => {
|
||||
const estFilter =
|
||||
d.__typename === "appointments"
|
||||
? estimatorsFilter.length === 0
|
||||
? true
|
||||
: !!estimatorsFilter.find(
|
||||
(e) =>
|
||||
e ===
|
||||
`${d.job?.est_ct_fn || ""} ${d.job?.est_ct_ln || ""}`.trim()
|
||||
)
|
||||
: true;
|
||||
|
||||
return (
|
||||
(d.block ||
|
||||
(filter.intake && d.isintake) ||
|
||||
(filter.manual && !d.isintake && d.block === false) ||
|
||||
@@ -50,9 +81,11 @@ export function ScheduleCalendarComponent({ data, refetch, bodyshop }) {
|
||||
!!d.employee)) &&
|
||||
(filter.ins_co_nm && filter.ins_co_nm.length > 0
|
||||
? filter.ins_co_nm.includes(d.job?.ins_co_nm)
|
||||
: true)
|
||||
);
|
||||
}, [data, filter]);
|
||||
: true) &&
|
||||
estFilter
|
||||
);
|
||||
});
|
||||
}, [data, filter, estimatorsFilter]);
|
||||
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
@@ -63,6 +96,21 @@ export function ScheduleCalendarComponent({ data, refetch, bodyshop }) {
|
||||
extra={
|
||||
<Space wrap>
|
||||
<ScheduleAtsSummary appointments={filteredData} />
|
||||
<Select
|
||||
style={{ minWidth: "15rem" }}
|
||||
mode="multiple"
|
||||
placeholder={t("schedule.labels.estimators")}
|
||||
allowClear
|
||||
onClear={() => setEstimatiorsFilter([])}
|
||||
value={[...estimatorsFilter]}
|
||||
onChange={(e) => {
|
||||
setEstimatiorsFilter(e);
|
||||
}}
|
||||
options={estimators.map((e) => ({
|
||||
label: e,
|
||||
value: e,
|
||||
}))}
|
||||
/>
|
||||
<Select
|
||||
style={{ minWidth: "15rem" }}
|
||||
mode="multiple"
|
||||
|
||||
@@ -15,6 +15,8 @@ import ShopInfoResponsibilityCenterComponent from "./shop-info.responsibilitycen
|
||||
import ShopInfoROStatusComponent from "./shop-info.rostatus.component";
|
||||
import ShopInfoSchedulingComponent from "./shop-info.scheduling.component";
|
||||
import ShopInfoSpeedPrint from "./shop-info.speedprint.component";
|
||||
import { useHistory, useLocation } from "react-router-dom";
|
||||
import queryString from "query-string";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -31,6 +33,10 @@ export function ShopInfoComponent({ bodyshop, form, saveLoading }) {
|
||||
bodyshop.imexshopid
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
const search = queryString.parse(location.search);
|
||||
|
||||
return (
|
||||
<Card
|
||||
extra={
|
||||
@@ -43,7 +49,14 @@ export function ShopInfoComponent({ bodyshop, form, saveLoading }) {
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Tabs>
|
||||
<Tabs
|
||||
defaultActiveKey={search.subtab}
|
||||
onChange={(key) =>
|
||||
history.push({
|
||||
search: `?tab=${search.tab}&subtab=${key}`,
|
||||
})
|
||||
}
|
||||
>
|
||||
<Tabs.TabPane key="general" tab={t("bodyshop.labels.shopinfo")}>
|
||||
<ShopInfoGeneral form={form} />
|
||||
</Tabs.TabPane>
|
||||
|
||||
@@ -24,9 +24,13 @@ const timeZonesList = momentTZ.tz.names();
|
||||
|
||||
export default function ShopInfoGeneral({ form }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<LayoutFormRow header={t("bodyshop.labels.businessinformation")}>
|
||||
<LayoutFormRow
|
||||
header={t("bodyshop.labels.businessinformation")}
|
||||
id="businessinformation"
|
||||
>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.shopname")}
|
||||
name="shopname"
|
||||
@@ -155,7 +159,10 @@ export default function ShopInfoGeneral({ form }) {
|
||||
<InputNumber min={0} />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("bodyshop.labels.accountingsetup")}>
|
||||
<LayoutFormRow
|
||||
header={t("bodyshop.labels.accountingsetup")}
|
||||
id="accountingsetup"
|
||||
>
|
||||
<Form.Item
|
||||
label={t("bodyshop.labels.qbo")}
|
||||
valuePropName="checked"
|
||||
@@ -386,7 +393,10 @@ export default function ShopInfoGeneral({ form }) {
|
||||
<Select mode="tags" />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("bodyshop.labels.scoreboardsetup")}>
|
||||
<LayoutFormRow
|
||||
header={t("bodyshop.labels.scoreboardsetup")}
|
||||
id="scoreboardsetup"
|
||||
>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.dailypainttarget")}
|
||||
name={["scoreboard_target", "dailyPaintTarget"]}
|
||||
@@ -445,7 +455,10 @@ export default function ShopInfoGeneral({ form }) {
|
||||
<InputNumber min={1} precision={1} />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("bodyshop.labels.systemsettings")}>
|
||||
<LayoutFormRow
|
||||
header={t("bodyshop.labels.systemsettings")}
|
||||
id="systemsettings"
|
||||
>
|
||||
<Form.Item
|
||||
name={["md_referral_sources"]}
|
||||
label={t("bodyshop.fields.md_referral_sources")}
|
||||
@@ -655,7 +668,11 @@ export default function ShopInfoGeneral({ form }) {
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow grow header={t("bodyshop.labels.messagingpresets")}>
|
||||
<LayoutFormRow
|
||||
grow
|
||||
header={t("bodyshop.labels.messagingpresets")}
|
||||
id="messagingpresets"
|
||||
>
|
||||
<Form.List name={["md_messaging_presets"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
return (
|
||||
@@ -720,7 +737,11 @@ export default function ShopInfoGeneral({ form }) {
|
||||
}}
|
||||
</Form.List>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow grow header={t("bodyshop.labels.notespresets")}>
|
||||
<LayoutFormRow
|
||||
grow
|
||||
header={t("bodyshop.labels.notespresets")}
|
||||
id="notespresets"
|
||||
>
|
||||
<Form.List name={["md_notes_presets"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
return (
|
||||
@@ -785,7 +806,11 @@ export default function ShopInfoGeneral({ form }) {
|
||||
}}
|
||||
</Form.List>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow grow header={t("bodyshop.labels.partslocations")}>
|
||||
<LayoutFormRow
|
||||
grow
|
||||
header={t("bodyshop.labels.partslocations")}
|
||||
id="partslocations"
|
||||
>
|
||||
<Form.List name={["md_parts_locations"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
return (
|
||||
@@ -839,7 +864,11 @@ export default function ShopInfoGeneral({ form }) {
|
||||
}}
|
||||
</Form.List>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow grow header={t("bodyshop.labels.insurancecos")}>
|
||||
<LayoutFormRow
|
||||
grow
|
||||
header={t("bodyshop.labels.insurancecos")}
|
||||
id="insurancecos"
|
||||
>
|
||||
<Form.List name={["md_ins_cos"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
return (
|
||||
@@ -935,7 +964,11 @@ export default function ShopInfoGeneral({ form }) {
|
||||
}}
|
||||
</Form.List>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow grow header={t("bodyshop.labels.estimators")}>
|
||||
<LayoutFormRow
|
||||
grow
|
||||
header={t("bodyshop.labels.estimators")}
|
||||
id="estimators"
|
||||
>
|
||||
<Form.List name={["md_estimators"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
return (
|
||||
@@ -1024,7 +1057,11 @@ export default function ShopInfoGeneral({ form }) {
|
||||
}}
|
||||
</Form.List>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow grow header={t("bodyshop.labels.filehandlers")}>
|
||||
<LayoutFormRow
|
||||
grow
|
||||
header={t("bodyshop.labels.filehandlers")}
|
||||
id="filehandlers"
|
||||
>
|
||||
<Form.List name={["md_filehandlers"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
return (
|
||||
@@ -1106,7 +1143,11 @@ export default function ShopInfoGeneral({ form }) {
|
||||
}}
|
||||
</Form.List>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow grow header={t("bodyshop.fields.md_ccc_rates")}>
|
||||
<LayoutFormRow
|
||||
grow
|
||||
header={t("bodyshop.fields.md_ccc_rates")}
|
||||
id="md_ccc_rates"
|
||||
>
|
||||
<Form.List name={["md_ccc_rates"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
return (
|
||||
@@ -1223,7 +1264,11 @@ export default function ShopInfoGeneral({ form }) {
|
||||
}}
|
||||
</Form.List>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow grow header={t("bodyshop.fields.md_jobline_presets")}>
|
||||
<LayoutFormRow
|
||||
grow
|
||||
header={t("bodyshop.fields.md_jobline_presets")}
|
||||
id="md_jobline_presets"
|
||||
>
|
||||
<Form.List name={["md_jobline_presets"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
return (
|
||||
@@ -1404,7 +1449,11 @@ export default function ShopInfoGeneral({ form }) {
|
||||
}}
|
||||
</Form.List>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow grow header={t("bodyshop.fields.md_parts_order_comment")}>
|
||||
<LayoutFormRow
|
||||
grow
|
||||
header={t("bodyshop.fields.md_parts_order_comment")}
|
||||
id="md_parts_order_comment"
|
||||
>
|
||||
<Form.List name={["md_parts_order_comment"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
return (
|
||||
@@ -1470,7 +1519,11 @@ export default function ShopInfoGeneral({ form }) {
|
||||
}}
|
||||
</Form.List>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow grow header={t("bodyshop.labels.md_to_emails")}>
|
||||
<LayoutFormRow
|
||||
grow
|
||||
header={t("bodyshop.labels.md_to_emails")}
|
||||
id="md_to_emails"
|
||||
>
|
||||
<Form.List name={["md_to_emails"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
return (
|
||||
|
||||
@@ -20,7 +20,10 @@ export default function ShopInfoIntakeChecklistComponent({ form }) {
|
||||
const TemplateListGenerated = TemplateList();
|
||||
return (
|
||||
<div>
|
||||
<LayoutFormRow header={t("bodyshop.labels.intakechecklist")}>
|
||||
<LayoutFormRow
|
||||
header={t("bodyshop.labels.intakechecklist")}
|
||||
id="intakechecklist"
|
||||
>
|
||||
<Form.List name={["intakechecklist", "form"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
return (
|
||||
@@ -188,7 +191,10 @@ export default function ShopInfoIntakeChecklistComponent({ form }) {
|
||||
</Form.Item>
|
||||
</SelectorDiv>
|
||||
|
||||
<LayoutFormRow header={t("bodyshop.labels.deliverchecklist")}>
|
||||
<LayoutFormRow
|
||||
header={t("bodyshop.labels.deliverchecklist")}
|
||||
id="deliverchecklist"
|
||||
>
|
||||
<Form.List name={["deliverchecklist", "form"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
return (
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||
import { Form, InputNumber } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
|
||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
@@ -316,6 +316,18 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.jobs.void")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "jobs:void"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.bills.enter")}
|
||||
rules={[
|
||||
|
||||
@@ -95,7 +95,6 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
||||
{form.getFieldValue("pbs_serialnumber")}
|
||||
</DataLabel>
|
||||
)}
|
||||
|
||||
<LayoutFormRow>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.dms.default_journal")}
|
||||
@@ -315,7 +314,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
||||
</DataLabel>
|
||||
</>
|
||||
)}
|
||||
<LayoutFormRow header={t("bodyshop.labels.responsibilitycenters.costs")}>
|
||||
<LayoutFormRow
|
||||
header={t("bodyshop.labels.responsibilitycenters.costs")}
|
||||
id="costs"
|
||||
>
|
||||
<Form.List name={["md_responsibility_centers", "costs"]}>
|
||||
{(fields, { add, remove }) => {
|
||||
return (
|
||||
@@ -462,6 +464,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
||||
|
||||
<LayoutFormRow
|
||||
header={t("bodyshop.labels.responsibilitycenters.profits")}
|
||||
id="profits"
|
||||
>
|
||||
<Form.List name={["md_responsibility_centers", "profits"]}>
|
||||
{(fields, { add, remove }) => {
|
||||
@@ -601,7 +604,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
||||
{fields.map((field, index) => (
|
||||
<Form.Item key={field.key}>
|
||||
<div>
|
||||
<LayoutFormRow>
|
||||
<LayoutFormRow id="mappingname">
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.dms.mappingname")}
|
||||
key={`${index}name`}
|
||||
@@ -631,6 +634,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow
|
||||
header={t("bodyshop.labels.defaultcostsmapping")}
|
||||
id="defaultcostsmapping"
|
||||
>
|
||||
<Form.Item
|
||||
label={t(
|
||||
@@ -4088,6 +4092,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
||||
|
||||
<LayoutFormRow
|
||||
header={t("bodyshop.labels.responsibilitycenters.tax_accounts")}
|
||||
id="tax_accounts"
|
||||
>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.responsibilitycenters.federal_tax")}
|
||||
@@ -4202,7 +4207,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
{DmsAp.treatment === "on" && (
|
||||
<LayoutFormRow>
|
||||
<LayoutFormRow id="federal_tax_itc">
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.responsibilitycenters.federal_tax_itc")}
|
||||
rules={[
|
||||
@@ -4316,7 +4321,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
)}
|
||||
<LayoutFormRow>
|
||||
<LayoutFormRow id="state_tax">
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.responsibilitycenters.state_tax")}
|
||||
rules={[
|
||||
@@ -4414,7 +4419,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
||||
<InputNumber precision={2} />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow>
|
||||
<LayoutFormRow id="local_tax">
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.responsibilitycenters.local_tax")}
|
||||
rules={[
|
||||
@@ -4512,7 +4517,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
||||
<InputNumber precision={2} />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={<div>AR</div>}>
|
||||
<LayoutFormRow header={<div>AR</div>} id="AR">
|
||||
{/* <Form.Item
|
||||
label={t("bodyshop.fields.responsibilitycenters.ar")}
|
||||
rules={[
|
||||
@@ -4576,7 +4581,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
||||
</LayoutFormRow>
|
||||
|
||||
{DmsAp.treatment === "on" && (
|
||||
<LayoutFormRow header={t("bodyshop.fields.responsibilitycenters.ap")}>
|
||||
<LayoutFormRow
|
||||
header={t("bodyshop.fields.responsibilitycenters.ap")}
|
||||
id="ap"
|
||||
>
|
||||
{/* <Form.Item
|
||||
label={t("bodyshop.fields.responsibilitycenters.ap")}
|
||||
rules={[
|
||||
@@ -4639,7 +4647,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
)}
|
||||
<LayoutFormRow header={<div>Refund</div>}>
|
||||
<LayoutFormRow header={<div>Refund</div>} id="refund">
|
||||
{/* <Form.Item
|
||||
label={t("bodyshop.fields.responsibilitycenters.refund")}
|
||||
rules={[
|
||||
@@ -4702,7 +4710,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
{Qb_Multi_Ar.treatment === "on" && (
|
||||
<LayoutFormRow header={<div>Multiple Payers Item</div>}>
|
||||
<LayoutFormRow
|
||||
header={<div>Multiple Payers Item</div>}
|
||||
id="accountitem"
|
||||
>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.responsibilitycenter_accountitem")}
|
||||
rules={[
|
||||
@@ -4730,7 +4741,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
||||
<div>
|
||||
{fields.map((field, index) => (
|
||||
<Form.Item key={field.key}>
|
||||
<LayoutFormRow>
|
||||
<LayoutFormRow id="sales_tax_codes">
|
||||
<Form.Item
|
||||
label={t(
|
||||
"bodyshop.fields.responsibilitycenters.sales_tax_codes.description"
|
||||
|
||||
@@ -44,7 +44,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<SelectorDiv>
|
||||
<SelectorDiv id="jobstatus">
|
||||
<Form.Item
|
||||
name={["md_ro_statuses", "statuses"]}
|
||||
label={t("bodyshop.labels.alljobstatuses")}
|
||||
@@ -322,6 +322,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
||||
<LayoutFormRow
|
||||
grow
|
||||
header={t("bodyshop.fields.statuses.production_colors")}
|
||||
id="production_colors"
|
||||
>
|
||||
<Form.List name={["md_ro_statuses", "production_colors"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
|
||||
@@ -92,7 +92,7 @@ export default function ShopInfoSchedulingComponent({ form }) {
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<Divider orientation="left">{t("bodyshop.labels.workingdays")}</Divider>
|
||||
<Space wrap size="large">
|
||||
<Space wrap size="large" id="workingdays">
|
||||
<Form.Item
|
||||
label={t("general.labels.sunday")}
|
||||
name={["workingdays", "sunday"]}
|
||||
@@ -143,7 +143,7 @@ export default function ShopInfoSchedulingComponent({ form }) {
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</Space>
|
||||
<LayoutFormRow header={t("bodyshop.labels.apptcolors")}>
|
||||
<LayoutFormRow header={t("bodyshop.labels.apptcolors")} id="apptcolors">
|
||||
<Form.List name={["appt_colors"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
return (
|
||||
@@ -208,7 +208,7 @@ export default function ShopInfoSchedulingComponent({ form }) {
|
||||
}}
|
||||
</Form.List>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("bodyshop.labels.ssbuckets")}>
|
||||
<LayoutFormRow header={t("bodyshop.labels.ssbuckets")} id="ssbuckets">
|
||||
<Form.List name={["ssbuckets"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
return (
|
||||
|
||||
@@ -33,6 +33,7 @@ export function TechClockInComponent({ form, bodyshop, technician }) {
|
||||
<JobSearchSelect
|
||||
convertedOnly={!bodyshop.tt_allow_post_to_invoiced}
|
||||
notExported={!bodyshop.tt_allow_post_to_invoiced}
|
||||
notInvoiced={!bodyshop.tt_allow_post_to_invoiced}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
|
||||
@@ -5,10 +5,10 @@ import {
|
||||
Col,
|
||||
Form,
|
||||
InputNumber,
|
||||
notification,
|
||||
Popover,
|
||||
Row,
|
||||
Select,
|
||||
notification,
|
||||
} from "antd";
|
||||
import axios from "axios";
|
||||
import React, { useState } from "react";
|
||||
@@ -16,13 +16,14 @@ import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { GET_LINE_TICKET_BY_PK } from "../../graphql/jobs-lines.queries";
|
||||
import { UPDATE_JOB_STATUS } from "../../graphql/jobs.queries";
|
||||
import { UPDATE_TIME_TICKET } from "../../graphql/timetickets.queries";
|
||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { CalculateAllocationsTotals } from "../labor-allocations-table/labor-allocations-table.utility";
|
||||
import TechJobClockoutDelete from "../tech-job-clock-out-delete/tech-job-clock-out-delete.component";
|
||||
import { LaborAllocationContainer } from "../time-ticket-modal/time-ticket-modal.component";
|
||||
import { GET_LINE_TICKET_BY_PK } from "../../graphql/jobs-lines.queries";
|
||||
import { CalculateAllocationsTotals } from "../labor-allocations-table/labor-allocations-table.utility";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -40,6 +41,7 @@ export function TechClockOffButton({
|
||||
}) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [updateTimeticket] = useMutation(UPDATE_TIME_TICKET);
|
||||
const [updateJobStatus] = useMutation(UPDATE_JOB_STATUS);
|
||||
const [form] = Form.useForm();
|
||||
const { queryLoading, data: lineTicketData } = useQuery(
|
||||
GET_LINE_TICKET_BY_PK,
|
||||
@@ -59,7 +61,8 @@ export function TechClockOffButton({
|
||||
|
||||
const handleFinish = async (values) => {
|
||||
logImEXEvent("tech_clock_out_job");
|
||||
|
||||
const status = values.status;
|
||||
delete values.status;
|
||||
setLoading(true);
|
||||
const result = await updateTimeticket({
|
||||
variables: {
|
||||
@@ -98,6 +101,26 @@ export function TechClockOffButton({
|
||||
message: t("timetickets.successes.clockedout"),
|
||||
});
|
||||
}
|
||||
if (!isShiftTicket) {
|
||||
const job_update_result = await updateJobStatus({
|
||||
variables: {
|
||||
jobId: jobId,
|
||||
status: status,
|
||||
},
|
||||
});
|
||||
|
||||
if (!!job_update_result.errors) {
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.updating", {
|
||||
message: JSON.stringify(result.errors),
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
notification["success"]({
|
||||
message: t("jobs.successes.updated"),
|
||||
});
|
||||
}
|
||||
}
|
||||
setLoading(false);
|
||||
if (completedCallback) completedCallback();
|
||||
};
|
||||
@@ -195,7 +218,6 @@ export function TechClockOffButton({
|
||||
</Form.Item>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<Form.Item
|
||||
name="cost_center"
|
||||
label={t("timetickets.fields.cost_center")}
|
||||
@@ -228,6 +250,29 @@ export function TechClockOffButton({
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
{isShiftTicket ? (
|
||||
<div></div>
|
||||
) : (
|
||||
<Form.Item
|
||||
name="status"
|
||||
label={t("jobs.fields.status")}
|
||||
initialValue={
|
||||
lineTicketData && lineTicketData.jobs_by_pk.status
|
||||
}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Select>
|
||||
{bodyshop.md_ro_statuses.production_statuses.map((item) => (
|
||||
<Select.Option key={item}></Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
)}
|
||||
<Button type="primary" htmlType="submit" loading={loading}>
|
||||
{t("general.actions.save")}
|
||||
</Button>
|
||||
|
||||
@@ -96,8 +96,9 @@ export function TimeTicketModalComponent({
|
||||
]}
|
||||
>
|
||||
<JobSearchSelect
|
||||
convertedOnly={!bodyshop.tt_allow_post_to_invoiced}
|
||||
convertedOnly={true}
|
||||
notExported={!bodyshop.tt_allow_post_to_invoiced}
|
||||
notInvoiced={!bodyshop.tt_allow_post_to_invoiced}
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import { Alert, Button, Col, Row, Space } from "antd";
|
||||
import i18n from "i18next";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectUpdateAvailable } from "../../redux/application/application.selectors";
|
||||
import { AlertOutlined } from "@ant-design/icons";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { setUpdateAvailable } from "../../redux/application/application.actions";
|
||||
import { store } from "../../redux/store";
|
||||
import * as serviceWorkerRegistration from "../../serviceWorkerRegistration";
|
||||
|
||||
let globalRegistration;
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
updateAvailable: selectUpdateAvailable,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(UpdateAlert);
|
||||
|
||||
export function UpdateAlert({ updateAvailable }) {
|
||||
const { t } = useTranslation();
|
||||
if (!updateAvailable) return null;
|
||||
return (
|
||||
<Alert
|
||||
message={t("general.messages.newversiontitle")}
|
||||
showIcon
|
||||
icon={<AlertOutlined />}
|
||||
description={
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col sm={24} md={16} lg={18}>
|
||||
{t("general.messages.newversionmessage")}
|
||||
</Col>
|
||||
<Col sm={24} md={8} lg={6}>
|
||||
<Space wrap>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
window.open("https://imex-online.noticeable.news/", "_blank");
|
||||
}}
|
||||
>
|
||||
{i18n.t("general.actions.viewreleasenotes")}
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={async () => {
|
||||
if (globalRegistration && globalRegistration.waiting) {
|
||||
await globalRegistration.unregister();
|
||||
// Makes Workbox call skipWaiting()
|
||||
globalRegistration.waiting.postMessage({
|
||||
type: "SKIP_WAITING",
|
||||
});
|
||||
// Once the service worker is unregistered, we can reload the page to let
|
||||
// the browser download a fresh copy of our app (invalidating the cache)
|
||||
window.location.reload();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{i18n.t("general.actions.refresh")}
|
||||
</Button>
|
||||
</Space>
|
||||
</Col>
|
||||
</Row>
|
||||
}
|
||||
closable={false}
|
||||
type="warning"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const onServiceWorkerUpdate = (registration) => {
|
||||
console.log("onServiceWorkerUpdate", registration);
|
||||
globalRegistration = registration;
|
||||
store.dispatch(setUpdateAvailable(true));
|
||||
};
|
||||
|
||||
serviceWorkerRegistration.register({ onUpdate: onServiceWorkerUpdate });
|
||||
@@ -57,6 +57,8 @@ export const QUERY_ALL_ACTIVE_APPOINTMENTS = gql`
|
||||
v_model_yr
|
||||
v_make_desc
|
||||
v_model_desc
|
||||
est_ct_fn
|
||||
est_ct_ln
|
||||
labhrs: joblines_aggregate(
|
||||
where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }
|
||||
) {
|
||||
|
||||
@@ -1,36 +1,5 @@
|
||||
import { gql } from "@apollo/client";
|
||||
|
||||
// export const CONVERSATION_LIST_SUBSCRIPTION = gql`
|
||||
// subscription CONVERSATION_LIST_SUBSCRIPTION {
|
||||
// conversations(
|
||||
// order_by: { updated_at: desc }
|
||||
// limit: 50
|
||||
// where: { archived: { _eq: false } }
|
||||
// ) {
|
||||
// phone_num
|
||||
// id
|
||||
// updated_at
|
||||
// unreadcnt
|
||||
// messages_aggregate(
|
||||
// where: { read: { _eq: false }, isoutbound: { _eq: false } }
|
||||
// ) {
|
||||
// aggregate {
|
||||
// count
|
||||
// }
|
||||
// }
|
||||
// job_conversations {
|
||||
// job {
|
||||
// id
|
||||
// ro_number
|
||||
// ownr_fn
|
||||
// ownr_ln
|
||||
// ownr_co_nm
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// `;
|
||||
|
||||
export const UNREAD_CONVERSATION_COUNT = gql`
|
||||
query UNREAD_CONVERSATION_COUNT {
|
||||
messages_aggregate(
|
||||
@@ -44,10 +13,11 @@ export const UNREAD_CONVERSATION_COUNT = gql`
|
||||
`;
|
||||
|
||||
export const CONVERSATION_LIST_QUERY = gql`
|
||||
query CONVERSATION_LIST_QUERY {
|
||||
query CONVERSATION_LIST_QUERY($offset: Int!) {
|
||||
conversations(
|
||||
order_by: { updated_at: desc }
|
||||
limit: 50
|
||||
offset: $offset
|
||||
where: { archived: { _eq: false } }
|
||||
) {
|
||||
phone_num
|
||||
|
||||
@@ -34,6 +34,7 @@ export const GET_LINE_TICKET_BY_PK = gql`
|
||||
id
|
||||
lbr_adjustments
|
||||
converted
|
||||
status
|
||||
}
|
||||
joblines(where: { jobid: { _eq: $id }, removed: { _eq: false } }) {
|
||||
id
|
||||
|
||||
@@ -1075,6 +1075,9 @@ export const UPDATE_JOB = gql`
|
||||
lbr_adjustments
|
||||
suspended
|
||||
queued_for_parts
|
||||
scheduled_completion
|
||||
actual_in
|
||||
date_repairstarted
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1216,10 +1219,10 @@ export const ACTIVE_JOBS_FOR_AUTOCOMPLETE = gql`
|
||||
query ACTIVE_JOBS_FOR_AUTOCOMPLETE($statuses: [String!]!) {
|
||||
jobs(where: { status: { _in: $statuses } }) {
|
||||
id
|
||||
ownr_co_nm
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ro_number
|
||||
|
||||
vehicleid
|
||||
v_make_desc
|
||||
v_model_desc
|
||||
@@ -1247,6 +1250,7 @@ export const SEARCH_JOBS_FOR_AUTOCOMPLETE = gql`
|
||||
}
|
||||
) {
|
||||
id
|
||||
ownr_co_nm
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ro_number
|
||||
@@ -1263,6 +1267,7 @@ export const SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE = gql`
|
||||
query SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE($id: uuid!) {
|
||||
jobs_by_pk(id: $id) {
|
||||
id
|
||||
ownr_co_nm
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ro_number
|
||||
@@ -1281,6 +1286,7 @@ export const SEARCH_FOR_JOBS = gql`
|
||||
search_jobs(args: { search: $search }, limit: 25) {
|
||||
id
|
||||
ro_number
|
||||
ownr_co_nm
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import queryString from "query-string";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { useHistory, useLocation, Link } from "react-router-dom";
|
||||
import { Link, useHistory, useLocation } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import SocketIO from "socket.io-client";
|
||||
import AlertComponent from "../../components/alert/alert.component";
|
||||
@@ -22,6 +22,7 @@ import DmsCustomerSelector from "../../components/dms-customer-selector/dms-cust
|
||||
import DmsLogEvents from "../../components/dms-log-events/dms-log-events.component";
|
||||
import DmsPostForm from "../../components/dms-post-form/dms-post-form.component";
|
||||
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
|
||||
import { OwnerNameDisplayFunction } from "../../components/owner-name-display/owner-name-display.component";
|
||||
import { auth } from "../../firebase/firebase.utils";
|
||||
import { QUERY_JOB_EXPORT_DMS } from "../../graphql/jobs.queries";
|
||||
import {
|
||||
@@ -29,7 +30,6 @@ import {
|
||||
setSelectedHeader,
|
||||
} from "../../redux/application/application.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { OwnerNameDisplayFunction } from "../../components/owner-name-display/owner-name-display.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -46,6 +46,7 @@ export const socket = SocketIO(
|
||||
process.env.NODE_ENV === "production"
|
||||
? process.env.REACT_APP_AXIOS_BASE_API_URL
|
||||
: window.location.origin,
|
||||
// "http://localhost:4000", // for dev testing,
|
||||
{
|
||||
path: "/ws",
|
||||
withCredentials: true,
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
import * as Sentry from "@sentry/react";
|
||||
|
||||
import "./manage.page.styles.scss";
|
||||
import UpdateAlert from "../../components/update-alert/update-alert.component";
|
||||
|
||||
const ManageRootPage = lazy(() =>
|
||||
import("../manage-root/manage-root.page.container")
|
||||
@@ -394,6 +395,7 @@ export function Manage({ match, conflict, bodyshop }) {
|
||||
<>
|
||||
<ChatAffixContainer />
|
||||
<Layout className="layout-container">
|
||||
<UpdateAlert />
|
||||
<HeaderContainer />
|
||||
|
||||
<Content className="content-container">
|
||||
|
||||
@@ -6,7 +6,7 @@ import AlertComponent from "../../components/alert/alert.component";
|
||||
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
|
||||
import { QUERY_BODYSHOP } from "../../graphql/bodyshop.queries";
|
||||
import { setBodyshop } from "../../redux/user/user.actions";
|
||||
import "../../utils/RegisterSw";
|
||||
//import "../../utils/RegisterSw";
|
||||
import ManagePage from "./manage.page.component";
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Tabs } from "antd";
|
||||
import React, { useEffect } from "react";
|
||||
import { useHistory, useLocation } from "react-router-dom";
|
||||
import queryString from "query-string";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ShopEmployeesContainer from "../../components/shop-employees/shop-employees.container";
|
||||
import ShopInfoContainer from "../../components/shop-info/shop-info.container";
|
||||
@@ -24,6 +26,9 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
|
||||
export function ShopPage({ bodyshop, setSelectedHeader, setBreadcrumbs }) {
|
||||
const { t } = useTranslation();
|
||||
const history = useHistory();
|
||||
const search = queryString.parse(useLocation().search);
|
||||
|
||||
useEffect(() => {
|
||||
document.title = t("titles.shop");
|
||||
setSelectedHeader("shop");
|
||||
@@ -35,9 +40,16 @@ export function ShopPage({ bodyshop, setSelectedHeader, setBreadcrumbs }) {
|
||||
]);
|
||||
}, [t, setSelectedHeader, setBreadcrumbs, bodyshop.shopname]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!search.tab) history.push({ search: "?tab=info" });
|
||||
}, [history, search]);
|
||||
|
||||
return (
|
||||
<RbacWrapper action="shop:config">
|
||||
<Tabs>
|
||||
<Tabs
|
||||
defaultActiveKey={search.tab}
|
||||
onChange={(key) => history.push({ search: `?tab=${key}` })}
|
||||
>
|
||||
<Tabs.TabPane tab={t("bodyshop.labels.shopinfo")} key="info">
|
||||
<ShopInfoContainer />
|
||||
</Tabs.TabPane>
|
||||
|
||||
@@ -12,6 +12,7 @@ import TechSider from "../../components/tech-sider/tech-sider.component";
|
||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||
import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component";
|
||||
import "./tech.page.styles.scss";
|
||||
import UpdateAlert from "../../components/update-alert/update-alert.component";
|
||||
const TimeTicketModalContainer = lazy(() =>
|
||||
import("../../components/time-ticket-modal/time-ticket-modal.container")
|
||||
);
|
||||
@@ -56,7 +57,9 @@ export function TechPage({ technician, match }) {
|
||||
<TechSider />
|
||||
<Layout>
|
||||
{technician ? null : <Redirect to={`${match.path}/login`} />}
|
||||
<UpdateAlert />
|
||||
<TechHeader />
|
||||
|
||||
<Content className="tech-content-container">
|
||||
<ErrorBoundary>
|
||||
<Suspense
|
||||
|
||||
@@ -9,7 +9,7 @@ import LoadingSpinner from "../../components/loading-spinner/loading-spinner.com
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import "../../utils/RegisterSw";
|
||||
//import "../../utils/RegisterSw";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
|
||||
@@ -62,3 +62,8 @@ export const setProblemJobs = (problemJobs) => ({
|
||||
type: ApplicationActionTypes.SET_PROBLEM_JOBS,
|
||||
payload: problemJobs,
|
||||
});
|
||||
|
||||
export const setUpdateAvailable = (isUpdateAvailable) => ({
|
||||
type: ApplicationActionTypes.SET_UPDATE_AVAILABLE,
|
||||
payload: isUpdateAvailable,
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import ApplicationActionTypes from "./application.types";
|
||||
const INITIAL_STATE = {
|
||||
loading: false,
|
||||
online: true,
|
||||
updateAvailable: false,
|
||||
breadcrumbs: [],
|
||||
recentItems: [],
|
||||
selectedHeader: "home",
|
||||
@@ -18,6 +19,11 @@ const INITIAL_STATE = {
|
||||
|
||||
const applicationReducer = (state = INITIAL_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case ApplicationActionTypes.SET_UPDATE_AVAILABLE:
|
||||
return {
|
||||
...state,
|
||||
updateAvailable: action.payload,
|
||||
};
|
||||
case ApplicationActionTypes.SET_SELECTED_HEADER:
|
||||
return {
|
||||
...state,
|
||||
|
||||
@@ -48,3 +48,7 @@ export const selectProblemJobs = createSelector(
|
||||
[selectApplication],
|
||||
(application) => application.problemJobs
|
||||
);
|
||||
export const selectUpdateAvailable = createSelector(
|
||||
[selectApplication],
|
||||
(application) => application.updateAvailable
|
||||
);
|
||||
|
||||
@@ -12,5 +12,6 @@ const ApplicationActionTypes = {
|
||||
SET_ONLINE_STATUS: "SET_ONLINE_STATUS",
|
||||
INSERT_AUDIT_TRAIL: "INSERT_AUDIT_TRAIL",
|
||||
SET_PROBLEM_JOBS: "SET_PROBLEM_JOBS",
|
||||
SET_UPDATE_AVAILABLE: "SET_UPDATE_AVAILABLE"
|
||||
};
|
||||
export default ApplicationActionTypes;
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
"blocked": "Blocked",
|
||||
"cancelledappointment": "Canceled appointment for: ",
|
||||
"completingjobs": "Completing Jobs",
|
||||
"dataconsistency": "{{ro_number}} has a data consistency issue. It may have been excluded for scheduling purposes. CODE: {{code}}.",
|
||||
"dataconsistency": "<0>{{ro_number}}</0> has a data consistency issue. It may have been excluded for scheduling purposes. CODE: {{code}}.",
|
||||
"expectedjobs": "Expected Jobs in Production: ",
|
||||
"expectedprodhrs": "Expected Production Hours:",
|
||||
"history": "History",
|
||||
@@ -405,7 +405,8 @@
|
||||
"list-active": "Jobs -> List Active",
|
||||
"list-all": "Jobs -> List All",
|
||||
"list-ready": "Jobs -> List Ready",
|
||||
"partsqueue": "Jobs -> Parts Queue"
|
||||
"partsqueue": "Jobs -> Parts Queue",
|
||||
"void": "Jobs -> Void"
|
||||
},
|
||||
"owners": {
|
||||
"detail": "Owners -> Detail",
|
||||
@@ -857,7 +858,9 @@
|
||||
"prodhrssummary": "Production Hours Summary",
|
||||
"productiondollars": "Total dollars in Production",
|
||||
"productionhours": "Total hours in Production",
|
||||
"projectedmonthlysales": "Projected Monthly Sales"
|
||||
"projectedmonthlysales": "Projected Monthly Sales",
|
||||
"scheduledintoday": "Sheduled In Today: {{date}}",
|
||||
"scheduledouttoday": "Sheduled Out Today: {{date}}"
|
||||
}
|
||||
},
|
||||
"dms": {
|
||||
@@ -1117,7 +1120,7 @@
|
||||
},
|
||||
"messages": {
|
||||
"exception": "$t(titles.app) has encountered an error. Please try again. If the problem persists, please submit a support ticket or contact us.",
|
||||
"newversionmessage": "Click refresh below to update to the latest available version of ImEX Online. Please make sure all other tabs and windows are closed.",
|
||||
"newversionmessage": "Click refresh to update to the latest available version of ImEX Online. Please make sure all other tabs and windows are closed.",
|
||||
"newversiontitle": "New version of ImEX Online Available",
|
||||
"noacctfilepath": "There is no accounting file path set. You will not be able to export any items.",
|
||||
"nofeatureaccess": "You do not have access to this feature of ImEX Online. Please contact support to request a license for this feature.",
|
||||
@@ -1450,6 +1453,7 @@
|
||||
"dms_make": "DMS Make",
|
||||
"dms_model": "DMS Model",
|
||||
"dms_wip_acctnumber": "Cost WIP DMS Acct #",
|
||||
"dms_unsold": "New, Unsold Vehicle",
|
||||
"id": "DMS ID",
|
||||
"inservicedate": "In Service Date",
|
||||
"journal": "Journal #",
|
||||
@@ -2319,7 +2323,7 @@
|
||||
"folder_label_multiple": "Folder Label - Multi",
|
||||
"glass_express_checklist": "Glass Express Checklist",
|
||||
"guarantee": "Repair Guarantee",
|
||||
"individual_job_note": "Job Note RO # {{ro_number}}",
|
||||
"individual_job_note": "RO Job Note",
|
||||
"invoice_customer_payable": "Invoice (Customer Payable)",
|
||||
"invoice_total_payable": "Invoice (Total Payable)",
|
||||
"iou_form": "IOU Form",
|
||||
@@ -2335,7 +2339,8 @@
|
||||
"mechanical_authorization": "Mechanical Authorization",
|
||||
"mpi_animal_checklist": "MPI - Animal Checklist",
|
||||
"mpi_eglass_auth": "MPI - eGlass Auth",
|
||||
"mpi_final_acct_sheet": "MPI - Final Accounting Sheet",
|
||||
"mpi_final_acct_sheet": "MPI - Final Accounting Sheet (Direct Repair)",
|
||||
"mpi_final_repair_acct_sheet": "MPI - Final Accounting Sheet",
|
||||
"paint_grid": "Paint Grid",
|
||||
"parts_invoice_label_single": "Parts Label Single",
|
||||
"parts_label_multiple": "Parts Label - Multi",
|
||||
@@ -2397,6 +2402,7 @@
|
||||
},
|
||||
"subjects": {
|
||||
"jobs": {
|
||||
"individual_job_note": "Job Note RO: {{ro_number}}",
|
||||
"parts_order": "Parts Order PO: {{ro_number}} - {{name}}",
|
||||
"sublet_order": "Sublet Order PO: {{ro_number}} - {{name}}"
|
||||
}
|
||||
@@ -2564,6 +2570,7 @@
|
||||
"job_costing_ro_ins_co": "Job Costing by RO Source",
|
||||
"jobs_completed_not_invoiced": "Jobs Completed not Invoiced",
|
||||
"jobs_invoiced_not_exported": "Jobs Invoiced not Exported",
|
||||
"jobs_scheduled_completion": "Jobs Scheduled Completion",
|
||||
"jobs_reconcile": "Parts/Sublet/Labor Reconciliation",
|
||||
"lag_time": "Lag Time",
|
||||
"open_orders": "Open Orders by Date",
|
||||
@@ -2622,6 +2629,7 @@
|
||||
"atssummary": "ATS Summary",
|
||||
"employeevacation": "Employee Vacations",
|
||||
"ins_co_nm_filter": "Filter by Insurance Company",
|
||||
"estimators": "Filter by Writer/Customer Rep.",
|
||||
"intake": "Intake Events",
|
||||
"manual": "Manual Events",
|
||||
"manualevent": "Add Manual Event"
|
||||
|
||||
@@ -405,7 +405,8 @@
|
||||
"list-active": "",
|
||||
"list-all": "",
|
||||
"list-ready": "",
|
||||
"partsqueue": ""
|
||||
"partsqueue": "",
|
||||
"void": ""
|
||||
},
|
||||
"owners": {
|
||||
"detail": "",
|
||||
@@ -857,7 +858,9 @@
|
||||
"prodhrssummary": "",
|
||||
"productiondollars": "",
|
||||
"productionhours": "",
|
||||
"projectedmonthlysales": ""
|
||||
"projectedmonthlysales": "",
|
||||
"scheduledintoday": "",
|
||||
"scheduledouttoday": ""
|
||||
}
|
||||
},
|
||||
"dms": {
|
||||
@@ -1450,6 +1453,7 @@
|
||||
"dms_make": "",
|
||||
"dms_model": "",
|
||||
"dms_wip_acctnumber": "",
|
||||
"dms_unsold": "",
|
||||
"id": "",
|
||||
"inservicedate": "",
|
||||
"journal": "",
|
||||
@@ -2333,6 +2337,7 @@
|
||||
"mpi_animal_checklist": "",
|
||||
"mpi_eglass_auth": "",
|
||||
"mpi_final_acct_sheet": "",
|
||||
"mpi_final_repair_acct_sheet": "",
|
||||
"paint_grid": "",
|
||||
"parts_invoice_label_single": "",
|
||||
"parts_label_multiple": "",
|
||||
@@ -2394,6 +2399,7 @@
|
||||
},
|
||||
"subjects": {
|
||||
"jobs": {
|
||||
"individual_job_note": "",
|
||||
"parts_order": "",
|
||||
"sublet_order": ""
|
||||
}
|
||||
@@ -2561,6 +2567,7 @@
|
||||
"job_costing_ro_ins_co": "",
|
||||
"jobs_completed_not_invoiced": "",
|
||||
"jobs_invoiced_not_exported": "",
|
||||
"jobs_scheduled_completion": "",
|
||||
"jobs_reconcile": "",
|
||||
"lag_time": "",
|
||||
"open_orders": "",
|
||||
|
||||
@@ -405,7 +405,8 @@
|
||||
"list-active": "",
|
||||
"list-all": "",
|
||||
"list-ready": "",
|
||||
"partsqueue": ""
|
||||
"partsqueue": "",
|
||||
"void": ""
|
||||
},
|
||||
"owners": {
|
||||
"detail": "",
|
||||
@@ -857,7 +858,9 @@
|
||||
"prodhrssummary": "",
|
||||
"productiondollars": "",
|
||||
"productionhours": "",
|
||||
"projectedmonthlysales": ""
|
||||
"projectedmonthlysales": "",
|
||||
"scheduledintoday": "",
|
||||
"scheduledouttoday": ""
|
||||
}
|
||||
},
|
||||
"dms": {
|
||||
@@ -1450,6 +1453,7 @@
|
||||
"dms_make": "",
|
||||
"dms_model": "",
|
||||
"dms_wip_acctnumber": "",
|
||||
"dms_unsold": "",
|
||||
"id": "",
|
||||
"inservicedate": "",
|
||||
"journal": "",
|
||||
@@ -2333,6 +2337,7 @@
|
||||
"mpi_animal_checklist": "",
|
||||
"mpi_eglass_auth": "",
|
||||
"mpi_final_acct_sheet": "",
|
||||
"mpi_final_repair_acct_sheet": "",
|
||||
"paint_grid": "",
|
||||
"parts_invoice_label_single": "",
|
||||
"parts_label_multiple": "",
|
||||
@@ -2394,6 +2399,7 @@
|
||||
},
|
||||
"subjects": {
|
||||
"jobs": {
|
||||
"individual_job_note": "",
|
||||
"parts_order": "",
|
||||
"sublet_order": ""
|
||||
}
|
||||
@@ -2561,6 +2567,7 @@
|
||||
"job_costing_ro_ins_co": "",
|
||||
"jobs_completed_not_invoiced": "",
|
||||
"jobs_invoiced_not_exported": "",
|
||||
"jobs_scheduled_completion": "",
|
||||
"jobs_reconcile": "",
|
||||
"lag_time": "",
|
||||
"open_orders": "",
|
||||
|
||||
@@ -3,7 +3,10 @@ import { setContext } from "@apollo/client/link/context";
|
||||
import { HttpLink } from "@apollo/client/link/http"; //"apollo-link-http";
|
||||
import { RetryLink } from "@apollo/client/link/retry";
|
||||
import { WebSocketLink } from "@apollo/client/link/ws";
|
||||
import { getMainDefinition } from "@apollo/client/utilities";
|
||||
import {
|
||||
getMainDefinition,
|
||||
offsetLimitPagination,
|
||||
} from "@apollo/client/utilities";
|
||||
//import { split } from "apollo-link";
|
||||
import apolloLogger from "apollo-link-logger";
|
||||
//import axios from "axios";
|
||||
@@ -140,7 +143,15 @@ middlewares.push(
|
||||
)
|
||||
);
|
||||
|
||||
const cache = new InMemoryCache({});
|
||||
const cache = new InMemoryCache({
|
||||
typePolicies: {
|
||||
Query: {
|
||||
fields: {
|
||||
conversations: offsetLimitPagination(),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const client = new ApolloClient({
|
||||
link: ApolloLink.from(middlewares),
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Button, notification, Space } from "antd";
|
||||
import i18n from "i18next";
|
||||
import React from "react";
|
||||
import * as serviceWorkerRegistration from "../serviceWorkerRegistration";
|
||||
import { store } from "../redux/store";
|
||||
|
||||
const onServiceWorkerUpdate = (registration) => {
|
||||
console.log("onServiceWorkerUpdate", registration);
|
||||
@@ -33,6 +34,9 @@ const onServiceWorkerUpdate = (registration) => {
|
||||
</Button>
|
||||
</Space>
|
||||
);
|
||||
|
||||
store.dispatch()
|
||||
|
||||
notification.open({
|
||||
icon: <AlertOutlined />,
|
||||
message: i18n.t("general.messages.newversiontitle"),
|
||||
|
||||
@@ -421,6 +421,17 @@ export const TemplateList = (type, context) => {
|
||||
CA_MB: true,
|
||||
},
|
||||
},
|
||||
mpi_final_repair_acct_sheet: {
|
||||
title: i18n.t("printcenter.jobs.mpi_final_repair_acct_sheet"),
|
||||
description: "",
|
||||
key: "mpi_final_repair_acct_sheet",
|
||||
subject: i18n.t("printcenter.jobs.mpi_final_repair_acct_sheet"),
|
||||
disabled: false,
|
||||
group: "post",
|
||||
regions: {
|
||||
CA_MB: true,
|
||||
},
|
||||
},
|
||||
mpi_eglass_auth: {
|
||||
title: i18n.t("printcenter.jobs.mpi_eglass_auth"),
|
||||
description: "",
|
||||
@@ -501,6 +512,7 @@ export const TemplateList = (type, context) => {
|
||||
key: "dms_posting_sheet",
|
||||
disabled: false,
|
||||
group: "financial",
|
||||
dms: true,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
@@ -542,7 +554,7 @@ export const TemplateList = (type, context) => {
|
||||
title: i18n.t("printcenter.jobs.individual_job_note"),
|
||||
description: "",
|
||||
key: "individual_job_note",
|
||||
subject: i18n.t("printcenter.jobs.individual_job_note", {
|
||||
subject: i18n.t("printcenter.subjects.jobs.individual_job_note", {
|
||||
ro_number: (context && context.ro_number) || "",
|
||||
}),
|
||||
disabled: false,
|
||||
@@ -1885,6 +1897,18 @@ export const TemplateList = (type, context) => {
|
||||
},
|
||||
group: "sales",
|
||||
},
|
||||
jobs_scheduled_completion: {
|
||||
title: i18n.t("reportcenter.templates.jobs_scheduled_completion"),
|
||||
subject: i18n.t("reportcenter.templates.jobs_scheduled_completion"),
|
||||
key: "jobs_scheduled_completion",
|
||||
//idtype: "vendor",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.jobs"),
|
||||
field: i18n.t("jobs.fields.scheduled_completion"),
|
||||
},
|
||||
group: "jobs",
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(!type || type === "courtesycarcontract"
|
||||
|
||||
@@ -11168,6 +11168,11 @@ react-image-lightbox@^5.1.4:
|
||||
prop-types "^15.7.2"
|
||||
react-modal "^3.11.1"
|
||||
|
||||
react-intersection-observer@^9.4.3:
|
||||
version "9.4.3"
|
||||
resolved "https://registry.yarnpkg.com/react-intersection-observer/-/react-intersection-observer-9.4.3.tgz#ec84ce0c25cad548075130ea045ac5c7adf908f3"
|
||||
integrity sha512-WNRqMQvKpupr6MzecAQI0Pj0+JQong307knLP4g/nBex7kYfIaZsPpXaIhKHR+oV8z+goUbH9e10j6lGRnTzlQ==
|
||||
|
||||
react-is@^16.10.2, react-is@^16.12.0, react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
|
||||
@@ -2092,6 +2092,13 @@
|
||||
table:
|
||||
name: employee_team_members
|
||||
schema: public
|
||||
- name: joblines
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: assigned_team
|
||||
table:
|
||||
name: joblines
|
||||
schema: public
|
||||
insert_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
@@ -2665,6 +2672,9 @@
|
||||
name: joblines
|
||||
schema: public
|
||||
object_relationships:
|
||||
- name: employee_team
|
||||
using:
|
||||
foreign_key_constraint_on: assigned_team
|
||||
- name: job
|
||||
using:
|
||||
foreign_key_constraint_on: jobid
|
||||
@@ -2690,6 +2700,13 @@
|
||||
table:
|
||||
name: inventory
|
||||
schema: public
|
||||
- name: parts_dispatch_lines
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: joblineid
|
||||
table:
|
||||
name: parts_dispatch_lines
|
||||
schema: public
|
||||
- name: parts_order_lines
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
@@ -2719,6 +2736,7 @@
|
||||
- alt_part_i
|
||||
- alt_partm
|
||||
- alt_partno
|
||||
- assigned_team
|
||||
- bett_amt
|
||||
- bett_pctg
|
||||
- bett_tax
|
||||
@@ -2727,6 +2745,7 @@
|
||||
- convertedtolbr
|
||||
- convertedtolbr_data
|
||||
- created_at
|
||||
- critical
|
||||
- db_hrs
|
||||
- db_price
|
||||
- db_ref
|
||||
@@ -2786,6 +2805,7 @@
|
||||
- alt_part_i
|
||||
- alt_partm
|
||||
- alt_partno
|
||||
- assigned_team
|
||||
- bett_amt
|
||||
- bett_pctg
|
||||
- bett_tax
|
||||
@@ -2865,6 +2885,7 @@
|
||||
- alt_part_i
|
||||
- alt_partm
|
||||
- alt_partno
|
||||
- assigned_team
|
||||
- bett_amt
|
||||
- bett_pctg
|
||||
- bett_tax
|
||||
@@ -3131,6 +3152,13 @@
|
||||
table:
|
||||
name: notes
|
||||
schema: public
|
||||
- name: parts_dispatches
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: jobid
|
||||
table:
|
||||
name: parts_dispatch
|
||||
schema: public
|
||||
- name: parts_orders
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
@@ -4556,6 +4584,165 @@
|
||||
template_engine: Kriti
|
||||
url: '{{$base_url}}/opensearch'
|
||||
version: 2
|
||||
- table:
|
||||
name: parts_dispatch
|
||||
schema: public
|
||||
object_relationships:
|
||||
- name: job
|
||||
using:
|
||||
foreign_key_constraint_on: jobid
|
||||
array_relationships:
|
||||
- name: parts_dispatch_lines
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: partsdispatchid
|
||||
table:
|
||||
name: parts_dispatch_lines
|
||||
schema: public
|
||||
insert_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
check:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
columns:
|
||||
- id
|
||||
- created_at
|
||||
- updated_at
|
||||
- jobid
|
||||
- number
|
||||
- employeeid
|
||||
- dispatched_at
|
||||
- dispatched_by
|
||||
select_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
columns:
|
||||
- number
|
||||
- dispatched_by
|
||||
- created_at
|
||||
- dispatched_at
|
||||
- updated_at
|
||||
- employeeid
|
||||
- id
|
||||
- jobid
|
||||
filter:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
update_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
columns:
|
||||
- number
|
||||
- dispatched_by
|
||||
- created_at
|
||||
- dispatched_at
|
||||
- updated_at
|
||||
- employeeid
|
||||
- id
|
||||
- jobid
|
||||
filter:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
check: null
|
||||
- table:
|
||||
name: parts_dispatch_lines
|
||||
schema: public
|
||||
object_relationships:
|
||||
- name: jobline
|
||||
using:
|
||||
foreign_key_constraint_on: joblineid
|
||||
- name: parts_dispatch
|
||||
using:
|
||||
foreign_key_constraint_on: partsdispatchid
|
||||
insert_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
check:
|
||||
parts_dispatch:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
columns:
|
||||
- id
|
||||
- created_at
|
||||
- updated_at
|
||||
- partsdispatchid
|
||||
- joblineid
|
||||
- quantity
|
||||
- accepted_at
|
||||
select_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
columns:
|
||||
- quantity
|
||||
- accepted_at
|
||||
- created_at
|
||||
- updated_at
|
||||
- id
|
||||
- joblineid
|
||||
- partsdispatchid
|
||||
filter:
|
||||
parts_dispatch:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
update_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
columns:
|
||||
- id
|
||||
- created_at
|
||||
- updated_at
|
||||
- partsdispatchid
|
||||
- joblineid
|
||||
- quantity
|
||||
- accepted_at
|
||||
filter:
|
||||
parts_dispatch:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
check: null
|
||||
- table:
|
||||
name: parts_order_lines
|
||||
schema: public
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE "public"."parts_dispatch";
|
||||
@@ -0,0 +1,18 @@
|
||||
CREATE TABLE "public"."parts_dispatch" ("id" uuid NOT NULL DEFAULT gen_random_uuid(), "created_at" timestamptz NOT NULL DEFAULT now(), "updated_at" timestamptz NOT NULL DEFAULT now(), "jobid" uuid NOT NULL, "number" serial NOT NULL, "employeeid" uuid NOT NULL, "dispatched_at" timestamptz NOT NULL, "dispatched_by" text NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("jobid") REFERENCES "public"."jobs"("id") ON UPDATE cascade ON DELETE cascade);
|
||||
CREATE OR REPLACE FUNCTION "public"."set_current_timestamp_updated_at"()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
_new record;
|
||||
BEGIN
|
||||
_new := NEW;
|
||||
_new."updated_at" = NOW();
|
||||
RETURN _new;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
CREATE TRIGGER "set_public_parts_dispatch_updated_at"
|
||||
BEFORE UPDATE ON "public"."parts_dispatch"
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE "public"."set_current_timestamp_updated_at"();
|
||||
COMMENT ON TRIGGER "set_public_parts_dispatch_updated_at" ON "public"."parts_dispatch"
|
||||
IS 'trigger to set value of column "updated_at" to current timestamp on row update';
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE "public"."parts_dispatch_lines";
|
||||
@@ -0,0 +1,18 @@
|
||||
CREATE TABLE "public"."parts_dispatch_lines" ("id" uuid NOT NULL DEFAULT gen_random_uuid(), "created_at" timestamptz NOT NULL DEFAULT now(), "updated_at" timestamptz NOT NULL DEFAULT now(), "partsdispatchid" UUID NOT NULL, "joblineid" uuid NOT NULL, "quantity" numeric NOT NULL DEFAULT 1, "accepted_at" timestamptz NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("joblineid") REFERENCES "public"."joblines"("id") ON UPDATE cascade ON DELETE cascade);
|
||||
CREATE OR REPLACE FUNCTION "public"."set_current_timestamp_updated_at"()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
_new record;
|
||||
BEGIN
|
||||
_new := NEW;
|
||||
_new."updated_at" = NOW();
|
||||
RETURN _new;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
CREATE TRIGGER "set_public_parts_dispatch_lines_updated_at"
|
||||
BEFORE UPDATE ON "public"."parts_dispatch_lines"
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE "public"."set_current_timestamp_updated_at"();
|
||||
COMMENT ON TRIGGER "set_public_parts_dispatch_lines_updated_at" ON "public"."parts_dispatch_lines"
|
||||
IS 'trigger to set value of column "updated_at" to current timestamp on row update';
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
@@ -0,0 +1 @@
|
||||
alter table "public"."parts_dispatch_lines" drop constraint "parts_dispatch_lines_partsdispatchid_fkey";
|
||||
@@ -0,0 +1,5 @@
|
||||
alter table "public"."parts_dispatch_lines"
|
||||
add constraint "parts_dispatch_lines_partsdispatchid_fkey"
|
||||
foreign key ("partsdispatchid")
|
||||
references "public"."parts_dispatch"
|
||||
("id") on update cascade on delete cascade;
|
||||
@@ -0,0 +1 @@
|
||||
alter table "public"."parts_dispatch_lines" alter column "accepted_at" set not null;
|
||||
@@ -0,0 +1 @@
|
||||
alter table "public"."parts_dispatch_lines" alter column "accepted_at" drop not null;
|
||||
@@ -0,0 +1,5 @@
|
||||
alter table "public"."job_conversations" drop constraint "job_conversations_conversationid_fkey",
|
||||
add constraint "job_conversations_conversationid_fkey"
|
||||
foreign key ("conversationid")
|
||||
references "public"."conversations"
|
||||
("id") on update restrict on delete restrict;
|
||||
@@ -0,0 +1,5 @@
|
||||
alter table "public"."job_conversations" drop constraint "job_conversations_conversationid_fkey",
|
||||
add constraint "job_conversations_conversationid_fkey"
|
||||
foreign key ("conversationid")
|
||||
references "public"."conversations"
|
||||
("id") on update cascade on delete cascade;
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- alter table "public"."joblines" add column "assigned_team" uuid
|
||||
-- null;
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."joblines" add column "assigned_team" uuid
|
||||
null;
|
||||
@@ -0,0 +1 @@
|
||||
alter table "public"."joblines" drop constraint "joblines_assigned_team_fkey";
|
||||
@@ -0,0 +1,5 @@
|
||||
alter table "public"."joblines"
|
||||
add constraint "joblines_assigned_team_fkey"
|
||||
foreign key ("assigned_team")
|
||||
references "public"."employee_teams"
|
||||
("id") on update restrict on delete restrict;
|
||||
@@ -133,7 +133,13 @@ const generateBillLine = (billLine, responsibilityCenters, jobClass) => {
|
||||
const findTaxCode = (billLine, taxcode) => {
|
||||
const {
|
||||
applicable_taxes: { local, state, federal },
|
||||
} = billLine;
|
||||
} =
|
||||
billLine.applicable_taxes === null
|
||||
? {
|
||||
...billLine,
|
||||
applicable_taxes: { local: false, state: false, federal: false },
|
||||
}
|
||||
: billLine;
|
||||
const t = taxcode.filter(
|
||||
(t) =>
|
||||
!!t.local === !!local &&
|
||||
|
||||
@@ -262,10 +262,10 @@ const generateInvoiceQbxml = (
|
||||
RefNumber: jobs_by_pk.ro_number,
|
||||
BillAddress: {
|
||||
Addr1: jobs_by_pk.ownr_co_nm
|
||||
? jobs_by_pk.ownr_co_nm.substring(0, 30)
|
||||
: `${`${jobs_by_pk.ownr_ln || ""} ${
|
||||
jobs_by_pk.ownr_fn || ""
|
||||
}`.substring(0, 30)}`,
|
||||
? jobs_by_pk.ownr_co_nm.substring(0, 30).trim()
|
||||
: `${`${jobs_by_pk.ownr_ln || ""} ${jobs_by_pk.ownr_fn || ""}`
|
||||
.substring(0, 30)
|
||||
.trim()}`,
|
||||
Addr2: jobs_by_pk.ownr_addr1,
|
||||
Addr3: jobs_by_pk.ownr_addr2,
|
||||
City: jobs_by_pk.ownr_city,
|
||||
@@ -274,10 +274,10 @@ const generateInvoiceQbxml = (
|
||||
},
|
||||
ShipAddress: {
|
||||
Addr1: jobs_by_pk.ownr_co_nm
|
||||
? jobs_by_pk.ownr_co_nm.substring(0, 30)
|
||||
: `${`${jobs_by_pk.ownr_ln || ""} ${
|
||||
jobs_by_pk.ownr_fn || ""
|
||||
}`.substring(0, 30)}`,
|
||||
? jobs_by_pk.ownr_co_nm.substring(0, 30).trim()
|
||||
: `${`${jobs_by_pk.ownr_ln || ""} ${jobs_by_pk.ownr_fn || ""}`
|
||||
.substring(0, 30)
|
||||
.trim()}`,
|
||||
Addr2: jobs_by_pk.ownr_addr1,
|
||||
Addr3: jobs_by_pk.ownr_addr2,
|
||||
City: jobs_by_pk.ownr_city,
|
||||
|
||||
@@ -21,9 +21,9 @@ exports.generateOwnerTier = (jobs_by_pk, isThreeTier, twotierpref) => {
|
||||
? `${jobs_by_pk.ownr_co_nm.substring(0, 30)} #${
|
||||
jobs_by_pk.owner.accountingid || ""
|
||||
}`
|
||||
: `${`${jobs_by_pk.ownr_ln || ""} ${
|
||||
jobs_by_pk.ownr_fn || ""
|
||||
}`.substring(0, 30)} #${jobs_by_pk.owner.accountingid || ""}`
|
||||
: `${`${jobs_by_pk.ownr_ln || ""} ${jobs_by_pk.ownr_fn || ""}`
|
||||
.substring(0, 30)
|
||||
.trim()} #${jobs_by_pk.owner.accountingid || ""}`
|
||||
)
|
||||
.trim()
|
||||
.replace(":", " ");
|
||||
@@ -39,9 +39,9 @@ exports.generateOwnerTier = (jobs_by_pk, isThreeTier, twotierpref) => {
|
||||
? `${jobs_by_pk.ownr_co_nm.substring(0, 30)} #${
|
||||
jobs_by_pk.owner.accountingid || ""
|
||||
}`
|
||||
: `${`${jobs_by_pk.ownr_ln || ""} ${
|
||||
jobs_by_pk.ownr_fn || ""
|
||||
}`.substring(0, 30)} #${jobs_by_pk.owner.accountingid || ""}`
|
||||
: `${`${jobs_by_pk.ownr_ln || ""} ${jobs_by_pk.ownr_fn || ""}`
|
||||
.substring(0, 30)
|
||||
.trim()} #${jobs_by_pk.owner.accountingid || ""}`
|
||||
)
|
||||
.trim()
|
||||
.replace(":", " ");
|
||||
|
||||
@@ -224,7 +224,7 @@ async function CdkSelectedCustomer(socket, selectedCustomerId) {
|
||||
} finally {
|
||||
//Ensure we always insert logEvents
|
||||
//GQL to insert logevents.
|
||||
|
||||
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"DEBUG",
|
||||
@@ -432,7 +432,7 @@ async function QueryDmsCustomerById(socket, JobData, CustomerId) {
|
||||
|
||||
async function QueryDmsCustomerByName(socket, JobData) {
|
||||
const ownerName = (
|
||||
JobData.ownr_co_nm && JobData.ownr_co_nm !== ""
|
||||
JobData.ownr_co_nm && JobData.ownr_co_nm.trim() !== ""
|
||||
? JobData.ownr_co_nm
|
||||
: `${JobData.ownr_ln},${JobData.ownr_fn}`
|
||||
).replace(replaceSpecialRegex, "");
|
||||
@@ -642,7 +642,8 @@ async function InsertDmsCustomer(socket, newCustomerNumber) {
|
||||
.toUpperCase(),
|
||||
middleName: null,
|
||||
nameType:
|
||||
socket.JobData.ownr_co_nm && socket.JobData.ownr_co_nm
|
||||
socket.JobData.ownr_co_nm &&
|
||||
String(socket.JobData.ownr_co_nm).trim() !== ""
|
||||
? "Business"
|
||||
: "Person",
|
||||
suffix: null,
|
||||
@@ -716,19 +717,33 @@ async function InsertDmsVehicle(socket) {
|
||||
dealer: {
|
||||
dealerNumber: socket.JobData.bodyshop.cdk_dealerid,
|
||||
...(socket.txEnvelope.inservicedate && {
|
||||
inServiceDate: moment(socket.txEnvelope.inservicedate)
|
||||
//.tz(socket.JobData.bodyshop.timezone)
|
||||
.startOf("day")
|
||||
.toISOString(),
|
||||
inServiceDate:
|
||||
socket.txEnvelope.dms_unsold === true
|
||||
? ""
|
||||
: moment(socket.txEnvelope.inservicedate)
|
||||
//.tz(socket.JobData.bodyshop.timezone)
|
||||
.startOf("day")
|
||||
.toISOString(),
|
||||
}),
|
||||
vehicleId: socket.DMSVid.vehiclesVehId,
|
||||
},
|
||||
manufacturer: {},
|
||||
vehicle: {
|
||||
deliveryDate: moment()
|
||||
// .tz(socket.JobData.bodyshop.timezone)
|
||||
.format("YYYYMMDD"),
|
||||
licensePlateNo: socket.JobData.plate_no,
|
||||
deliveryDate:
|
||||
socket.txEnvelope.dms_unsold === true
|
||||
? ""
|
||||
: moment()
|
||||
// .tz(socket.JobData.bodyshop.timezone)
|
||||
.format("YYYYMMDD"),
|
||||
licensePlateNo:
|
||||
socket.JobData.plate_no === null
|
||||
? null
|
||||
: String(socket.JobData.plate_no).replace(/([^\w]|_)/g, "")
|
||||
.length === 0
|
||||
? null
|
||||
: String(socket.JobData.plate_no)
|
||||
.replace(/([^\w]|_)/g, "")
|
||||
.toUpperCase(),
|
||||
make: socket.txEnvelope.dms_make,
|
||||
modelAbrev: socket.txEnvelope.dms_model,
|
||||
modelYear: socket.JobData.v_model_yr,
|
||||
@@ -851,19 +866,25 @@ async function UpdateDmsVehicle(socket) {
|
||||
...socket.DMSVeh.dealer,
|
||||
...((socket.txEnvelope.inservicedate ||
|
||||
socket.DMSVeh.dealer.inServiceDate) && {
|
||||
inServiceDate: moment(
|
||||
socket.DMSVeh.dealer.inServiceDate ||
|
||||
socket.txEnvelope.inservicedate
|
||||
)
|
||||
// .tz(socket.JobData.bodyshop.timezone)
|
||||
.toISOString(),
|
||||
inServiceDate:
|
||||
socket.txEnvelope.dms_unsold === true
|
||||
? ""
|
||||
: moment(
|
||||
socket.DMSVeh.dealer.inServiceDate ||
|
||||
socket.txEnvelope.inservicedate
|
||||
)
|
||||
// .tz(socket.JobData.bodyshop.timezone)
|
||||
.toISOString(),
|
||||
}),
|
||||
},
|
||||
vehicle: {
|
||||
...socket.DMSVeh.vehicle,
|
||||
deliveryDate: moment(socket.DMSVeh.vehicle.deliveryDate)
|
||||
//.tz(socket.JobData.bodyshop.timezone)
|
||||
.toISOString(),
|
||||
deliveryDate:
|
||||
socket.txEnvelope.dms_unsold === true
|
||||
? ""
|
||||
: moment(socket.DMSVeh.vehicle.deliveryDate)
|
||||
//.tz(socket.JobData.bodyshop.timezone)
|
||||
.toISOString(),
|
||||
},
|
||||
owners: ids,
|
||||
},
|
||||
|
||||
@@ -86,6 +86,7 @@ async function OpenSearchUpdateHandler(req, res) {
|
||||
"v_model_yr",
|
||||
"v_make_desc",
|
||||
"v_model_desc",
|
||||
"v_vin",
|
||||
]);
|
||||
document.bodyshopid = req.body.event.data.new.shopid;
|
||||
break;
|
||||
@@ -139,7 +140,7 @@ async function OpenSearchUpdateHandler(req, res) {
|
||||
"exported_at",
|
||||
"invoice_number",
|
||||
"is_credit_memo",
|
||||
"total"
|
||||
"total",
|
||||
]),
|
||||
...bill.bills_by_pk,
|
||||
bodyshopid: bill.bills_by_pk.job.shopid,
|
||||
@@ -244,17 +245,54 @@ async function OpensearchSearchHandler(req, res) {
|
||||
bool: {
|
||||
must: [
|
||||
{
|
||||
multi_match: {
|
||||
query: search,
|
||||
type: "phrase_prefix",
|
||||
//fields: ["*"],
|
||||
// fuzziness: "5",
|
||||
//prefix_length: 2,
|
||||
match: {
|
||||
bodyshopid: assocs.associations[0].shopid,
|
||||
},
|
||||
},
|
||||
{
|
||||
match: {
|
||||
bodyshopid: assocs.associations[0].shopid,
|
||||
bool: {
|
||||
should: [
|
||||
{
|
||||
multi_match: {
|
||||
query: search,
|
||||
type: "cross_fields",
|
||||
fields: ["*ownr_fn", "*ownr_ln"],
|
||||
},
|
||||
},
|
||||
{
|
||||
multi_match: {
|
||||
query: search,
|
||||
type: "most_fields",
|
||||
fields: [
|
||||
"*v_model_yr",
|
||||
"*v_make_desc^2",
|
||||
"*v_model_desc^3",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
query_string: {
|
||||
query: `*${search}*`,
|
||||
|
||||
fields: [
|
||||
"*ro_number^20",
|
||||
"*clm_no^14",
|
||||
"*v_vin^12",
|
||||
"*plate_no^12",
|
||||
"*ownr_ln^10",
|
||||
"transactionid^10",
|
||||
"paymentnum^10",
|
||||
"invoice_number^10",
|
||||
"*ownr_fn^8",
|
||||
"*ownr_co_nm^8",
|
||||
"*ownr_ph1^8",
|
||||
"*ownr_ph2^8",
|
||||
"*",
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
11
yarn.lock
11
yarn.lock
@@ -4312,14 +4312,9 @@ tslib@^1.11.1:
|
||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||
|
||||
tslib@^2.0.1, tslib@^2.1.0:
|
||||
|
||||
tslib@^2.3.1, tslib@^2.5.0:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf"
|
||||
integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==
|
||||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf"
|
||||
integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==
|
||||
version "2.5.2"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.2.tgz#1b6f07185c881557b0ffa84b111a0106989e8338"
|
||||
integrity sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==
|
||||
|
||||
tslib@^2.3.1, tslib@^2.5.0:
|
||||
version "2.5.0"
|
||||
|
||||
Reference in New Issue
Block a user