Compare commits

..

2 Commits

Author SHA1 Message Date
Patrick Fic
2bff7d9567 Merge branch 'release/2023-03-21' into release/2023-03-24 2023-03-21 09:10:15 -07:00
Patrick Fic
d3f49094d8 Add ppc field to job lines. 2023-03-20 13:20:43 -07:00
198 changed files with 1669 additions and 8326 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,3 @@
GENERATE_SOURCEMAP=false
REACT_APP_GRAPHQL_ENDPOINT=https://db.imex.online/v1/graphql
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.imex.online/v1/graphql
REACT_APP_GA_CODE=231103507

View File

@@ -46,12 +46,10 @@
"react-cookie": "^4.1.1",
"react-dom": "^17.0.2",
"react-drag-listview": "^0.2.1",
"react-grid-gallery": "^1.0.0",
"react-grid-gallery": "^0.5.5",
"react-grid-layout": "^1.3.4",
"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",

View File

@@ -143,24 +143,13 @@
}
}
//Update row highlighting on production board.
//Update row highlighting on production board.
.ant-table-tbody > tr.ant-table-row:hover > td {
background: #e7f3ff !important;
background: #eaeaea !important;
}
.job-line-manual {
.job-line-manual{
color: tomato;
font-style: italic;
}
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;
}

View File

@@ -6,7 +6,7 @@ import { useTranslation } from "react-i18next";
import { DELETE_BILL } from "../../graphql/bills.queries";
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
export default function BillDeleteButton({ bill, callback }) {
export default function BillDeleteButton({ bill }) {
const [loading, setLoading] = useState(false);
const { t } = useTranslation();
const [deleteBill] = useMutation(DELETE_BILL);
@@ -36,8 +36,6 @@ export default function BillDeleteButton({ bill, callback }) {
if (!!!result.errors) {
notification["success"]({ message: t("bills.successes.deleted") });
if (callback && typeof callback === "function") callback(bill.id);
} else {
//Check if it's an fkey violation.
const error = JSON.stringify(result.errors);

View File

@@ -7,9 +7,7 @@ import { createStructuredSelector } from "reselect";
import { selectBreadcrumbs } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import GlobalSearch from "../global-search/global-search.component";
import GlobalSearchOs from "../global-search/global-search-os.component";
import "./breadcrumbs.styles.scss";
import { useTreatments } from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({
breadcrumbs: selectBreadcrumbs,
@@ -17,12 +15,6 @@ const mapStateToProps = createStructuredSelector({
});
export function BreadCrumbs({ breadcrumbs, bodyshop }) {
const { OpenSearch } = useTreatments(
["OpenSearch"],
{},
bodyshop && bodyshop.imexshopid
);
return (
<Row className="breadcrumb-container">
<Col xs={24} sm={24} md={16}>
@@ -46,7 +38,7 @@ export function BreadCrumbs({ breadcrumbs, bodyshop }) {
</Breadcrumb>
</Col>
<Col xs={24} sm={24} md={8}>
{OpenSearch.treatment === "on" ? <GlobalSearchOs /> : <GlobalSearch />}
<GlobalSearch />
</Col>
</Row>
);

View File

@@ -10,10 +10,7 @@ export default function CABCpvrtCalculator({ disabled, form }) {
const handleFinish = async (values) => {
logImEXEvent("job_ca_bc_pvrt_calculate");
form.setFieldsValue({
ca_bc_pvrt: ((values.rate || 0) * (values.days || 0)).toFixed(2),
});
form.setFields([{ name: "ca_bc_pvrt", touched: true }]);
form.setFieldsValue({ ca_bc_pvrt: ((values.rate||0) * (values.days||0)).toFixed(2) });
setVisibility(false);
};

View File

@@ -1,19 +1,12 @@
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({
@@ -25,95 +18,59 @@ const mapDispatchToProps = (dispatch) => ({
dispatch(setSelectedConversation(conversationId)),
});
function ChatConversationListComponent({
export 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">
<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();
}
}}
/>
<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>
/>
</div>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps

View File

@@ -3,9 +3,8 @@
}
.chat-list-container {
flex: 1;
overflow: hidden;
overflow: auto;
height: 100%;
border: 1px solid gainsboro;
}
.chat-list-item {
@@ -22,6 +21,4 @@
.ro-number-tag {
align-self: baseline;
}
padding: 12px 24px;
border-bottom: 1px solid gainsboro;
}

View File

@@ -4,9 +4,9 @@ import {
ShrinkOutlined,
SyncOutlined,
} from "@ant-design/icons";
import { useLazyQuery, useQuery } from "@apollo/client";
import { useQuery } from "@apollo/client";
import { Badge, Card, Col, Row, Space, Tag, Tooltip, Typography } from "antd";
import React, { useCallback, useEffect, useState } from "react";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
@@ -47,13 +47,12 @@ export function ChatPopupComponent({
...(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 { loading, data, refetch, called } = useQuery(CONVERSATION_LIST_QUERY, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
skip: !chatVisible,
...(pollInterval > 0 ? { pollInterval } : {}),
});
const fcmToken = sessionStorage.getItem("fcmtoken");
@@ -66,22 +65,15 @@ export function ChatPopupComponent({
}, [fcmToken]);
useEffect(() => {
if (chatVisible)
getConversations({
variables: {
offset: 0,
},
});
}, [chatVisible, getConversations]);
if (called && chatVisible) refetch();
}, [chatVisible, called, refetch]);
const loadMoreConversations = useCallback(() => {
if (data)
fetchMore({
variables: {
offset: data.conversations.length,
},
});
}, [data, fetchMore]);
// const unreadCount = data
// ? data.conversations.reduce(
// (acc, val) => val.messages_aggregate.aggregate.count + acc,
// 0
// )
// : 0;
const unreadCount = unreadData?.messages_aggregate.aggregate.count || 0;
@@ -118,7 +110,6 @@ export function ChatPopupComponent({
) : (
<ChatConversationListComponent
conversationList={data ? data.conversations : []}
loadMoreConversations={loadMoreConversations}
/>
)}
</Col>

View File

@@ -1,244 +0,0 @@
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) => {
if (item.job) {
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 moment(a.start) - new moment(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
}
`;

View File

@@ -1,210 +0,0 @@
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
}
`;

View File

@@ -1,6 +1,6 @@
import Icon, { SyncOutlined } from "@ant-design/icons";
import { gql, useMutation, useQuery } from "@apollo/client";
import { Button, Dropdown, Menu, PageHeader, Space, notification } from "antd";
import { Button, Dropdown, Menu, notification, PageHeader, Space } from "antd";
import i18next from "i18next";
import _ from "lodash";
import moment from "moment";
@@ -37,12 +37,6 @@ 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";
@@ -274,28 +268,6 @@ 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) => {
@@ -311,12 +283,8 @@ 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

View File

@@ -66,7 +66,7 @@ export function DmsAllocationsSummaryAp({ socket, bodyshop, billids, title }) {
key: "status",
},
{
title: t("bills.fields.invoice_number"),
title: t("jobs.fields.ro_number"),
dataIndex: ["Posting", "Reference"],
key: "reference",
},

View File

@@ -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 EmailDocumentsComponent from "../email-documents/email-documents.component";
import { selectEmailConfig } from "../../redux/email/email.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -54,15 +54,6 @@ 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>
@@ -83,25 +74,6 @@ 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
@@ -150,23 +122,7 @@ export function EmailOverlayComponent({
>
<Select mode="tags" tokenSeparators={[",", ";"]} />
</Form.Item>
<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"
>
<Form.Item label={t("emails.fields.cc")} name="cc">
<Select mode="tags" tokenSeparators={[",", ";"]} />
</Form.Item>
<Form.Item

View File

@@ -1,216 +0,0 @@
import { AutoComplete, Divider, Input, Space } from "antd";
import axios from "axios";
import _ from "lodash";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { Link, useHistory } from "react-router-dom";
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import OwnerNameDisplay, {
OwnerNameDisplayFunction,
} from "../owner-name-display/owner-name-display.component";
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
export default function GlobalSearchOs() {
const { t } = useTranslation();
const history = useHistory();
const [loading, setLoading] = useState(false);
const [data, setData] = useState(false);
const executeSearch = async (v) => {
if (v && v && v !== "" && v.length >= 3) {
try {
setLoading(true);
const searchData = await axios.post("/search", {
search: v,
});
const resultsByType = {
payments: [],
jobs: [],
bills: [],
owners: [],
vehicles: [],
};
searchData.data.hits.hits.forEach((hit) => {
resultsByType[hit._index].push(hit._source);
});
setData([
{
label: renderTitle(t("menus.header.search.jobs")),
options: resultsByType.jobs.map((job) => {
return {
key: job.id,
value: job.ro_number || "N/A",
label: (
<Link to={`/manage/jobs/${job.id}`}>
<Space size="small" split={<Divider type="vertical" />}>
<strong>{job.ro_number || t("general.labels.na")}</strong>
<span>{`${job.status || ""}`}</span>
<span>
<OwnerNameDisplay ownerObject={job} />
</span>
<span>{`${job.v_model_yr || ""} ${
job.v_make_desc || ""
} ${job.v_model_desc || ""}`}</span>
<span>{`${job.clm_no || ""}`}</span>
</Space>
</Link>
),
};
}),
},
{
label: renderTitle(t("menus.header.search.owners")),
options: resultsByType.owners.map((owner) => {
return {
key: owner.id,
value: OwnerNameDisplayFunction(owner),
label: (
<Link to={`/manage/owners/${owner.id}`}>
<Space
size="small"
split={<Divider type="vertical" />}
wrap
>
<span>
<OwnerNameDisplay ownerObject={owner} />
</span>
<PhoneNumberFormatter>
{owner.ownr_ph1}
</PhoneNumberFormatter>
<PhoneNumberFormatter>
{owner.ownr_ph2}
</PhoneNumberFormatter>
</Space>
</Link>
),
};
}),
},
{
label: renderTitle(t("menus.header.search.vehicles")),
options: resultsByType.vehicles.map((vehicle) => {
return {
key: vehicle.id,
value: `${vehicle.v_model_yr || ""} ${
vehicle.v_make_desc || ""
} ${vehicle.v_model_desc || ""}`,
label: (
<Link to={`/manage/vehicles/${vehicle.id}`}>
<Space size="small" split={<Divider type="vertical" />}>
<span>
{`${vehicle.v_model_yr || ""} ${
vehicle.v_make_desc || ""
} ${vehicle.v_model_desc || ""}`}
</span>
<span>{vehicle.plate_no || ""}</span>
<span>
<VehicleVinDisplay>
{vehicle.v_vin || ""}
</VehicleVinDisplay>
</span>
</Space>
</Link>
),
};
}),
},
{
label: renderTitle(t("menus.header.search.payments")),
options: resultsByType.payments.map((payment) => {
return {
key: payment.id,
value: `${payment.job?.ro_number} ${payment.amount}`,
label: (
<Link to={`/manage/jobs/${payment.job?.id}`}>
<Space size="small" split={<Divider type="vertical" />}>
<span>{payment.paymentnum}</span>
<span>{payment.job?.ro_number}</span>
<span>{payment.memo || ""}</span>
<span>{payment.amount || ""}</span>
<span>{payment.transactionid || ""}</span>
</Space>
</Link>
),
};
}),
},
{
label: renderTitle(t("menus.header.search.bills")),
options: resultsByType.bills.map((bill) => {
return {
key: bill.id,
value: `${bill.invoice_number} - ${bill.vendor.name}`,
label: (
<Link to={`/manage/bills?billid=${bill.id}`}>
<Space size="small" split={<Divider type="vertical" />}>
<span>{bill.invoice_number}</span>
<span>{bill.vendor.name}</span>
<span>{bill.date}</span>
</Space>
</Link>
),
};
}),
},
// {
// label: renderTitle(t("menus.header.search.phonebook")),
// options: resultsByType.search_phonebook.map((pb) => {
// return {
// key: pb.id,
// value: `${pb.firstname || ""} ${pb.lastname || ""} ${
// pb.company || ""
// }`,
// label: (
// <Link to={`/manage/phonebook?phonebookentry=${pb.id}`}>
// <Space size="small" split={<Divider type="vertical" />}>
// <span>{`${pb.firstname || ""} ${pb.lastname || ""} ${
// pb.company || ""
// }`}</span>
// <PhoneNumberFormatter>{pb.phone1}</PhoneNumberFormatter>
// <span>{pb.email}</span>
// </Space>
// </Link>
// ),
// };
// }),
// },
]);
} catch (error) {
console.log("Error while fetching search results", error);
} finally {
setLoading(false);
}
}
};
const debouncedExecuteSearch = _.debounce(executeSearch, 750);
const handleSearch = (value) => {
debouncedExecuteSearch(value);
};
const renderTitle = (title) => {
return <span>{title}</span>;
};
return (
<AutoComplete
options={data}
onSearch={handleSearch}
defaultActiveFirstOption
onSelect={(val, opt) => {
history.push(opt.label.props.to);
}}
onClear={() => setData([])}
>
<Input.Search
size="large"
placeholder={t("general.labels.globalsearch")}
enterButton
allowClear
loading={loading}
/>
</AutoComplete>
);
}

View File

@@ -8,7 +8,7 @@ import { GLOBAL_SEARCH_QUERY } from "../../graphql/search.queries";
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import AlertComponent from "../alert/alert.component";
import OwnerNameDisplay, {
OwnerNameDisplayFunction,
OwnerNameDisplayFunction
} from "../owner-name-display/owner-name-display.component";
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
export default function GlobalSearch() {
@@ -18,18 +18,11 @@ export default function GlobalSearch() {
useLazyQuery(GLOBAL_SEARCH_QUERY);
const executeSearch = (v) => {
if (
v &&
v.variables.search &&
v.variables.search !== "" &&
v.variables.search.length >= 3
)
callSearch(v);
if (v && v.variables.search && v.variables.search !== "") callSearch(v);
};
const debouncedExecuteSearch = _.debounce(executeSearch, 750);
const handleSearch = (value) => {
console.log("Handle Search");
debouncedExecuteSearch({ variables: { search: value } });
};
@@ -44,7 +37,7 @@ export default function GlobalSearch() {
options: data.search_jobs.map((job) => {
return {
key: job.id,
value: job.ro_number || "N/A",
value: job.ro_number,
label: (
<Link to={`/manage/jobs/${job.id}`}>
<Space size="small" split={<Divider type="vertical" />}>

View File

@@ -311,9 +311,7 @@ function Header({
icon={<SettingOutlined />}
>
<Menu.Item key="shop" icon={<Icon component={GiSettingsKnobs} />}>
<Link to="/manage/shop?tab=info">
{t("menus.header.shop_config")}
</Link>
<Link to="/manage/shop">{t("menus.header.shop_config")}</Link>
</Menu.Item>
<Menu.Item key="dashboard" icon={<DashboardFilled />}>
<Link to="/manage/dashboard">{t("menus.header.dashboard")}</Link>

View File

@@ -99,7 +99,7 @@ export function JobPayments({
render: (text, record) => (
<Space wrap>
<Button
// disabled={record.exportedat}
disabled={record.exportedat}
onClick={() => {
setPaymentContext({
actions: { refetch: refetch },

View File

@@ -109,8 +109,8 @@ export function JobsConvertButton({
]}
>
<Select>
{bodyshop.md_ins_cos.map((s, i) => (
<Select.Option key={i} value={s.name}>
{bodyshop.md_ins_cos.map((s) => (
<Select.Option key={s.name} value={s.name}>
{s.name}
</Select.Option>
))}
@@ -219,15 +219,13 @@ export function JobsConvertButton({
</Select>
</Form.Item>
)}
{bodyshop.region_config.toLowerCase().startsWith("ca") && (
<Form.Item
label={t("jobs.fields.ca_gst_registrant")}
name="ca_gst_registrant"
valuePropName="checked"
>
<Switch />
</Form.Item>
)}
<Form.Item
label={t("jobs.fields.ca_gst_registrant")}
name="ca_gst_registrant"
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.driveable")}
name="driveable"

View File

@@ -224,15 +224,13 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
>
<CurrencyInput />
</Form.Item>
{bodyshop.region_config.toLowerCase().startsWith("ca") && (
<Form.Item
label={t("jobs.fields.ca_gst_registrant")}
name="ca_gst_registrant"
valuePropName="checked"
>
<Switch />
</Form.Item>
)}
<Form.Item
label={t("jobs.fields.ca_gst_registrant")}
name="ca_gst_registrant"
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.other_amount_payable")}
name="other_amount_payable"

View File

@@ -29,7 +29,6 @@ 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,
@@ -462,56 +461,54 @@ export function JobsDetailHeaderActions({
)}
<JobsDetailHeaderActionsAddevent jobid={job.id} />
{!jobRO && job.converted && (
<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"),
},
],
<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>
</RbacWrapper>
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>
)}
</Menu>
);

View File

@@ -40,26 +40,24 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
>
<CurrencyInput disabled={jobRO} min={0} />
</Form.Item>
{bodyshop.region_config.toLowerCase().startsWith("ca") && (
<Tooltip title={t("jobs.labels.ca_gst_all_if_null")}>
<Form.Item
label={t("jobs.fields.ca_customer_gst")}
name="ca_customer_gst"
>
<CurrencyInput
disabled={jobRO}
min={0}
max={
Math.round(
(job.job_totals &&
job.job_totals.totals.federal_tax.amount) ||
0
) / 100
}
/>
</Form.Item>
</Tooltip>
)}
<Tooltip title={t("jobs.labels.ca_gst_all_if_null")}>
<Form.Item
label={t("jobs.fields.ca_customer_gst")}
name="ca_customer_gst"
>
<CurrencyInput
disabled={jobRO}
min={0}
max={
Math.round(
(job.job_totals &&
job.job_totals.totals.federal_tax.amount) ||
0
) / 100
}
/>
</Form.Item>
</Tooltip>
<Form.Item
label={t("jobs.fields.other_amount_payable")}
name="other_amount_payable"
@@ -84,14 +82,12 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
>
<CurrencyInput disabled={jobRO || bodyshop.cdk_dealerid} />
</Form.Item>
{bodyshop.region_config === "CA_BC" && (
<Space align="center">
<Form.Item label={t("jobs.fields.ca_bc_pvrt")} name="ca_bc_pvrt">
<CurrencyInput disabled={jobRO} min={0} />
</Form.Item>
<CABCpvrtCalculator form={form} disabled={jobRO} />
</Space>
)}
<Space align="end">
<Form.Item label={t("jobs.fields.ca_bc_pvrt")} name="ca_bc_pvrt">
<CurrencyInput disabled={jobRO} min={0} />
</Form.Item>
<CABCpvrtCalculator form={form} disabled={jobRO} />
</Space>
<Form.Item
label={t("jobs.fields.auto_add_ats")}
name="auto_add_ats"
@@ -145,15 +141,13 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
{bodyshop.region_config.toLowerCase().startsWith("ca") && (
<Form.Item
label={t("jobs.fields.ca_gst_registrant")}
name="ca_gst_registrant"
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
)}
<Form.Item
label={t("jobs.fields.ca_gst_registrant")}
name="ca_gst_registrant"
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
</FormRow>
<Divider
orientation="left"

View File

@@ -1,7 +1,7 @@
import { EditFilled, FileExcelFilled, SyncOutlined } from "@ant-design/icons";
import { Button, Card, Col, Row, Space } from "antd";
import React, { useEffect, useState } from "react";
import { Gallery } from "react-grid-gallery";
import Gallery from "react-grid-gallery";
import { useTranslation } from "react-i18next";
import DocumentsUploadComponent from "../documents-upload/documents-upload.component";
import { DetermineFileType } from "../documents-upload/documents-upload.utility";
@@ -11,9 +11,6 @@ import JobsDocumentsGalleryReassign from "./jobs-document-gallery.reassign.compo
import JobsDocumentsDeleteButton from "./jobs-documents-gallery.delete.component";
import JobsDocumentsGallerySelectAllComponent from "./jobs-documents-gallery.selectall.component";
import Lightbox from "react-image-lightbox";
import "react-image-lightbox/style.css";
function JobsDocumentsComponent({
data,
jobId,
@@ -26,7 +23,11 @@ function JobsDocumentsComponent({
}) {
const [galleryImages, setgalleryImages] = useState({ images: [], other: [] });
const { t } = useTranslation();
const [modalState, setModalState] = useState({ open: false, index: 0 });
const [index, setIndex] = useState(0);
const onCurrentImageChange = (index) => {
setIndex(index);
};
useEffect(() => {
let documents = data.reduce(
@@ -34,16 +35,14 @@ function JobsDocumentsComponent({
const fileType = DetermineFileType(value.type);
if (value.type.startsWith("image")) {
acc.images.push({
// src: GenerateSrcUrl(value),
src: GenerateThumbUrl(value),
// src: GenerateSrcUrl(value),
// thumbnail: GenerateThumbUrl(value),
fullsize: GenerateSrcUrl(value),
height: 225,
width: 225,
src: GenerateSrcUrl(value),
thumbnail: GenerateThumbUrl(value),
thumbnailHeight: 225,
thumbnailWidth: 225,
isSelected: false,
key: value.key,
extension: value.extension,
id: value.id,
type: value.type,
size: value.size,
@@ -63,7 +62,7 @@ function JobsDocumentsComponent({
const fileName = value.key.split("/").pop();
acc.other.push({
source: GenerateSrcUrl(value),
src: thumb,
src: "",
thumbnail: thumb,
tags: [
{
@@ -86,9 +85,10 @@ function JobsDocumentsComponent({
]
: []),
],
height: 225,
width: 225,
thumbnailHeight: 225,
thumbnailWidth: 225,
isSelected: false,
extension: value.extension,
key: value.key,
id: value.id,
@@ -148,15 +148,35 @@ function JobsDocumentsComponent({
<Card title={t("jobs.labels.documents-images")}>
<Gallery
images={galleryImages.images}
onClick={(index, item) => {
setModalState({ open: true, index: index });
// window.open(
// item.fullsize,
// "_blank",
// "toolbar=0,location=0,menubar=0"
// );
backdropClosesModal={true}
currentImageWillChange={onCurrentImageChange}
customControls={[
<Button
key="edit-button"
style={{
float: "right",
zIndex: "5",
}}
onClick={() => {
const newWindow = window.open(
`${window.location.protocol}//${window.location.host}/edit?documentId=${galleryImages.images[index].id}`,
"_blank",
"noopener,noreferrer"
);
if (newWindow) newWindow.opener = null;
}}
>
<EditFilled />
</Button>,
]}
onClickImage={(props) => {
window.open(
props.target.src,
"_blank",
"toolbar=0,location=0,menubar=0"
);
}}
onSelect={(index, image) => {
onSelectImage={(index, image) => {
setgalleryImages({
...galleryImages,
images: galleryImages.images.map((g, idx) =>
@@ -171,6 +191,8 @@ function JobsDocumentsComponent({
<Card title={t("jobs.labels.documents-other")}>
<Gallery
images={galleryImages.other}
backdropClosesModal={true}
enableLightbox={false}
thumbnailStyle={() => {
return {
backgroundImage: <FileExcelFilled />,
@@ -179,14 +201,14 @@ function JobsDocumentsComponent({
cursor: "pointer",
};
}}
onClick={(index) => {
onClickThumbnail={(index) => {
window.open(
galleryImages.other[index].source,
"_blank",
"toolbar=0,location=0,menubar=0"
);
}}
onSelect={(index) => {
onSelectImage={(index) => {
setgalleryImages({
...galleryImages,
other: galleryImages.other.map((g, idx) =>
@@ -197,53 +219,6 @@ function JobsDocumentsComponent({
/>
</Card>
</Col>
{modalState.open && (
<Lightbox
toolbarButtons={[
<EditFilled
onClick={() => {
const newWindow = window.open(
`${window.location.protocol}//${
window.location.host
}/edit?documentId=${
galleryImages.images[modalState.index].id
}`,
"_blank",
"noopener,noreferrer"
);
if (newWindow) newWindow.opener = null;
}}
/>,
]}
mainSrc={galleryImages.images[modalState.index].fullsize}
nextSrc={
galleryImages.images[
(modalState.index + 1) % galleryImages.images.length
].fullsize
}
prevSrc={
galleryImages.images[
(modalState.index + galleryImages.images.length - 1) %
galleryImages.images.length
].fullsize
}
onCloseRequest={() => setModalState({ open: false, index: 0 })}
onMovePrevRequest={() =>
setModalState({
...modalState,
index:
(modalState.index + galleryImages.images.length - 1) %
galleryImages.images.length,
})
}
onMoveNextRequest={() =>
setModalState({
...modalState,
index: (modalState.index + 1) % galleryImages.images.length,
})
}
/>
)}
</Row>
</div>
);

View File

@@ -1,7 +1,7 @@
import React, { useEffect } from "react";
import { Gallery } from "react-grid-gallery";
import Gallery from "react-grid-gallery";
import { useTranslation } from "react-i18next";
import { GenerateThumbUrl } from "./job-documents.utility";
import { GenerateSrcUrl, GenerateThumbUrl } from "./job-documents.utility";
function JobsDocumentGalleryExternal({
data,
@@ -15,8 +15,8 @@ function JobsDocumentGalleryExternal({
let documents = data.reduce((acc, value) => {
if (value.type.startsWith("image")) {
acc.push({
//src: GenerateSrcUrl(value),
src: GenerateThumbUrl(value),
src: GenerateSrcUrl(value),
thumbnail: GenerateThumbUrl(value),
thumbnailHeight: 225,
thumbnailWidth: 225,
isSelected: false,
@@ -39,7 +39,7 @@ function JobsDocumentGalleryExternal({
<Gallery
images={galleryImages}
backdropClosesModal={true}
onSelect={(index, image) => {
onSelectImage={(index, image) => {
setgalleryImages(
galleryImages.map((g, idx) =>
index === idx ? { ...g, isSelected: !g.isSelected } : g

View File

@@ -1,7 +1,7 @@
import { SyncOutlined, FileExcelFilled } from "@ant-design/icons";
import { Alert, Button, Card, Space } from "antd";
import React, { useEffect, useState } from "react";
import { Gallery } from "react-grid-gallery";
import React, { useEffect } from "react";
import Gallery from "react-grid-gallery";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
@@ -19,9 +19,6 @@ import JobsLocalGalleryDownloadButton from "./jobs-documents-local-gallery.downl
import JobsDocumentsLocalGalleryReassign from "./jobs-documents-local-gallery.reassign.component";
import JobsDocumentsLocalGallerySelectAllComponent from "./jobs-documents-local-gallery.selectall.component";
import Lightbox from "react-image-lightbox";
import "react-image-lightbox/style.css";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
allMedia: selectAllMedia,
@@ -52,7 +49,6 @@ export function JobsDocumentsLocalGallery({
vendorid,
}) {
const { t } = useTranslation();
const [modalState, setModalState] = useState({ open: false, index: 0 });
useEffect(() => {
if (job) {
if (invoice_number) {
@@ -74,20 +70,12 @@ export function JobsDocumentsLocalGallery({
) {
acc.images.push({
...val,
fullsize: val.src,
src: val.thumbnail,
height: val.thumbnailHeight,
width: val.thumbnailWidth,
...(val.optimized && { src: val.optimized, fullsize: val.src }),
});
if (val.optimized) optimized = true;
} else {
acc.other.push({
...val,
fullsize: val.src,
src: val.thumbnail,
height: val.thumbnailHeight,
width: val.thumbnailWidth,
tags: [{ value: val.filename, title: val.filename }],
});
}
@@ -132,7 +120,8 @@ export function JobsDocumentsLocalGallery({
<Card title={t("jobs.labels.documents-images")}>
<Gallery
images={jobMedia.images}
onSelect={(index, image) => {
backdropClosesModal={true}
onSelectImage={(index, image) => {
toggleMediaSelected({ jobid: job.id, filename: image.filename });
}}
{...(optimized && {
@@ -144,23 +133,24 @@ export function JobsDocumentsLocalGallery({
/>,
],
})}
onClick={(index) => {
setModalState({ open: true, index: index });
// const media = allMedia[job.id].find(
// (m) => m.optimized === item.src
// );
onClickImage={(props) => {
const media = allMedia[job.id].find(
(m) => m.optimized === props.target.src
);
// window.open(
// media ? media.fullsize : item.fullsize,
// "_blank",
// "toolbar=0,location=0,menubar=0"
// );
window.open(
media ? media.src : props.target.src,
"_blank",
"toolbar=0,location=0,menubar=0"
);
}}
/>
</Card>
<Card title={t("jobs.labels.documents-other")}>
<Gallery
images={jobMedia.other}
backdropClosesModal={true}
enableLightbox={false}
thumbnailStyle={() => {
return {
backgroundImage: <FileExcelFilled />,
@@ -169,48 +159,18 @@ export function JobsDocumentsLocalGallery({
cursor: "pointer",
};
}}
onClick={(index) => {
onClickThumbnail={(index) => {
window.open(
jobMedia.other[index].fullsize,
jobMedia.other[index].src,
"_blank",
"toolbar=0,location=0,menubar=0"
);
}}
onSelect={(index, image) => {
onSelectImage={(index, image) => {
toggleMediaSelected({ jobid: job.id, filename: image.filename });
}}
/>
</Card>
{modalState.open && (
<Lightbox
mainSrc={jobMedia.images[modalState.index].fullsize}
nextSrc={
jobMedia.images[(modalState.index + 1) % jobMedia.images.length]
.fullsize
}
prevSrc={
jobMedia.images[
(modalState.index + jobMedia.images.length - 1) %
jobMedia.images.length
].fullsize
}
onCloseRequest={() => setModalState({ open: false, index: 0 })}
onMovePrevRequest={() =>
setModalState({
...modalState,
index:
(modalState.index + jobMedia.images.length - 1) %
jobMedia.images.length,
})
}
onMoveNextRequest={() =>
setModalState({
...modalState,
index: (modalState.index + 1) % jobMedia.images.length,
})
}
/>
)}
</div>
);
}

View File

@@ -1,5 +1,5 @@
import React, { useEffect } from "react";
import { Gallery } from "react-grid-gallery";
import Gallery from "react-grid-gallery";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
@@ -38,7 +38,7 @@ function JobDocumentsLocalGalleryExternal({
const { t } = useTranslation();
useEffect(() => {
if (jobId) {
if ( jobId) {
getJobMedia(jobId);
}
}, [jobId, getJobMedia]);
@@ -52,15 +52,11 @@ function JobDocumentsLocalGalleryExternal({
val.type.mime &&
val.type.mime.startsWith("image")
) {
acc.push({ ...val, src: val.thumbnail });
acc.push(val);
}
return acc;
}, [])
: [];
console.log(
"🚀 ~ file: jobs-documents-local-gallery.external.component.jsx:48 ~ useEffect ~ documents:",
documents
);
setgalleryImages(documents);
}, [allMedia, jobId, setgalleryImages, t]);
@@ -69,7 +65,8 @@ function JobDocumentsLocalGalleryExternal({
<div className="clearfix">
<Gallery
images={galleryImages}
onSelect={(index, image) => {
backdropClosesModal={true}
onSelectImage={(index, image) => {
setgalleryImages(
galleryImages.map((g, idx) =>
index === idx ? { ...g, isSelected: !g.isSelected } : g

View File

@@ -1,9 +1,8 @@
import { SyncOutlined } from "@ant-design/icons";
import { Button, Card, Input, Space, Table, Typography } from "antd";
import axios from "axios";
import _ from "lodash";
import queryString from "query-string";
import React, { useEffect, useState } from "react";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link, useHistory, useLocation } from "react-router-dom";
@@ -22,8 +21,6 @@ const mapDispatchToProps = (dispatch) => ({
export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
const search = queryString.parse(useLocation().search);
const [openSearchResults, setOpenSearchResults] = useState([]);
const [searchLoading, setSearchLoading] = useState(false);
const { page, sortcolumn, sortorder } = search;
const history = useHistory();
@@ -196,28 +193,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
history.push({ search: queryString.stringify(search) });
};
useEffect(() => {
if (search.search && search.search.trim() !== "") {
searchJobs();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
async function searchJobs(value) {
try {
setSearchLoading(true);
const searchData = await axios.post("/search", {
search: value || search.search,
index: "jobs",
});
setOpenSearchResults(searchData.data.hits.hits.map((s) => s._source));
} catch (error) {
console.log("Error while fetching search results", error);
} finally {
setSearchLoading(false);
}
}
return (
<Card
extra={
@@ -230,7 +205,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
<Button
onClick={() => {
delete search.search;
delete search.page;
history.push({ search: queryString.stringify(search) });
}}
>
@@ -246,32 +220,24 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
onSearch={(value) => {
search.search = value;
history.push({ search: queryString.stringify(search) });
searchJobs(value);
}}
loading={loading || searchLoading}
enterButton
/>
</Space>
}
>
<Table
loading={loading || searchLoading}
pagination={
search?.search
? {
pageSize: 25,
showSizeChanger: false,
}
: {
pageSize: 25,
current: parseInt(page || 1),
total: total,
showSizeChanger: false,
}
}
loading={loading}
pagination={{
position: "top",
pageSize: 25,
current: parseInt(page || 1),
total: total,
showSizeChanger: false,
}}
columns={columns}
rowKey="id"
dataSource={search?.search ? openSearchResults : jobs}
dataSource={jobs}
onChange={handleTableChange}
/>
</Card>

View File

@@ -112,9 +112,7 @@ export function JobsList({ bodyshop }) {
title: t("jobs.fields.ro_number"),
dataIndex: "ro_number",
key: "ro_number",
sorter: (a, b) =>
parseInt((a.ro_number || "0").replace(/\D/g, "")) -
parseInt((b.ro_number || "0").replace(/\D/g, "")),
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
sortOrder:
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,

View File

@@ -75,27 +75,6 @@ export function JobNotesComponent({
</span>
),
},
{
title: t("notes.fields.type"),
dataIndex: "type",
key: "type",
width: 120,
filteredValue: filter?.type || null,
filters: [
{ value: "general", text: t("notes.fields.types.general") },
{ value: "customer", text: t("notes.fields.types.customer") },
{ value: "shop", text: t("notes.fields.types.shop") },
{ value: "office", text: t("notes.fields.types.office") },
{ value: "parts", text: t("notes.fields.types.parts") },
{ value: "paint", text: t("notes.fields.types.paint") },
{
value: "supplement",
text: t("notes.fields.types.supplement"),
},
],
onFilter: (value, record) => value.includes(record.type),
render: (text, record) => t(`notes.fields.types.${record.type}`),
},
{
title: t("notes.fields.text"),
dataIndex: "text",
@@ -127,7 +106,7 @@ export function JobNotesComponent({
title: t("notes.actions.actions"),
dataIndex: "actions",
key: "actions",
width: 200,
width: 150,
render: (text, record) => (
<Space wrap>
<Button

View File

@@ -207,7 +207,7 @@ export function LaborAllocationsTable({
<Card title={t("jobs.labels.laborallocations")}>
<Table
columns={columns}
rowKey={(record) => `${record.cost_center} ${record.mod_lbr_ty}`}
rowKey="cost_center"
pagination={false}
onChange={handleTableChange}
dataSource={totals}

View File

@@ -6,6 +6,10 @@ export const CalculateAllocationsTotals = (
timetickets,
adjustments = []
) => {
console.log(
"🚀 ~ file: labor-allocations-table.utility.js ~ line 9 ~ adjustments",
adjustments
);
const responsibilitycenters = bodyshop.md_responsibility_centers;
const jobCodes = joblines.map((item) => item.mod_lbr_ty);
//.filter((value, index, self) => self.indexOf(value) === index && !!value);

View File

@@ -1,14 +1,4 @@
import {
Checkbox,
Col,
Form,
Input,
Row,
Select,
Space,
Switch,
Tag,
} from "antd";
import { Checkbox, Col, Form, Input, Row, Space, Switch, Tag } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -56,28 +46,6 @@ export function NoteUpsertModalComponent({ form, noteUpsertModal }) {
<Switch />
</Form.Item>
</Col>
<Col span={8}>
<Form.Item
label={t("notes.fields.type")}
name="type"
initialValue="general"
>
<Select
options={[
{ value: "general", label: t("notes.fields.types.general") },
{ value: "customer", label: t("notes.fields.types.customer") },
{ value: "shop", label: t("notes.fields.types.shop") },
{ value: "office", label: t("notes.fields.types.office") },
{ value: "parts", label: t("notes.fields.types.parts") },
{ value: "paint", label: t("notes.fields.types.paint") },
{
value: "supplement",
label: t("notes.fields.types.supplement"),
},
]}
/>
</Form.Item>
</Col>
<Col span={8}>
<NotesPresetButton form={form} />
</Col>

View File

@@ -34,7 +34,7 @@ export function NoteUpsertModalContainer({
const [updateNote] = useMutation(UPDATE_NOTE);
const { visible, context, actions } = noteUpsertModal;
const { jobId, existingNote, text } = context;
const { jobId, existingNote } = context;
const { refetch } = actions;
const [form] = Form.useForm();
@@ -45,12 +45,8 @@ export function NoteUpsertModalContainer({
form.setFieldsValue(existingNote);
} else if (!existingNote && visible) {
form.resetFields();
if (text) {
form.setFieldValue("text", text);
}
}
}, [existingNote, form, visible, text]);
}, [existingNote, form, visible]);
const handleFinish = async (formValues) => {
const { relatedros, ...values } = formValues;
@@ -86,7 +82,6 @@ export function NoteUpsertModalContainer({
{ ...values, jobid: jobId, created_by: currentUser.email },
],
},
refetchQueries: ["QUERY_NOTES_BY_JOB_PK"],
});
if (AdditionalNoteInserts.length > 0) {

View File

@@ -201,7 +201,6 @@ export function PartsOrderListTableComponent({
subject: record.return
? Templates.parts_return_slip.subject
: Templates.parts_order.subject,
to: record.vendor.email,
}}
id={job.id}
/>
@@ -297,6 +296,7 @@ export function PartsOrderListTableComponent({
sortOrder:
state.sortedInfo.columnKey === "quantity" && state.sortedInfo.order,
},
{
title: t("parts_orders.fields.act_price"),
dataIndex: "act_price",

View File

@@ -1,100 +0,0 @@
import React from "react";
import { Button, notification } from "antd";
import { useMutation } from "@apollo/client";
import { useTranslation } from "react-i18next";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { setModalContext } from "../../redux/modals/modals.actions";
import { connect } from "react-redux";
import { UPDATE_PAYMENT } from "../../graphql/payments.queries";
import { selectCurrentUser } from "../../redux/user/user.selectors";
import { createStructuredSelector } from "reselect";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
});
const mapDispatchToProps = (dispatch) => ({
setPaymentContext: (context) =>
dispatch(setModalContext({ context: context, modal: "payment" })),
});
const PaymentMarkForExportButton = ({
bodyshop,
payment,
refetch,
setPaymentContext,
currentUser,
}) => {
const { t } = useTranslation();
const [insertExportLog, { loading: exportLogLoading }] =
useMutation(INSERT_EXPORT_LOG);
const [updatePayment, { loading: updatePaymentLoading }] =
useMutation(UPDATE_PAYMENT);
const handleClick = async () => {
const today = new Date();
await insertExportLog({
variables: {
logs: [
{
bodyshopid: bodyshop.id,
paymentid: payment.id,
successful: true,
useremail: currentUser.email,
},
],
},
});
const paymentUpdateResponse = await updatePayment({
variables: {
paymentId: payment.id,
payment: {
exportedat: today,
},
},
});
if (!!!paymentUpdateResponse.errors) {
notification.open({
type: "success",
key: "paymentsuccessmarkforexport",
message: t("payments.successes.markexported"),
});
if (refetch) refetch();
setPaymentContext({
actions: {
refetch,
},
context: {
...payment,
exportedat: today,
},
});
} else {
notification["error"]({
message: t("payments.errors.exporting", {
error: JSON.stringify(paymentUpdateResponse.error),
}),
});
}
};
return (
<Button
onClick={handleClick}
loading={exportLogLoading || updatePaymentLoading}
disabled={!!payment.exportedat}
>
{t("payments.labels.markexported")}
</Button>
);
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(PaymentMarkForExportButton);

View File

@@ -1,6 +1,6 @@
import { useMutation } from "@apollo/client";
import { Button, Form, Modal, notification, Space } from "antd";
import { Button, Form, Modal, notification } from "antd";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -19,8 +19,6 @@ import {
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
import PaymentForm from "../payment-form/payment-form.component";
import PaymentReexportButton from "../payment-reexport-button/payment-reexport-button.component";
import PaymentMarkForExportButton from "../payment-mark-export-button/payment-mark-export-button-component";
const mapStateToProps = createStructuredSelector({
paymentModal: selectPayment,
@@ -54,7 +52,7 @@ function PaymentModalContainer({
const { useStripe, sendby, ...paymentObj } = values;
setLoading(true);
let updatedPayment; //Moved up from if statement for greater scope.
try {
if (!context || (context && !context.id)) {
const newPayment = await insertPayment({
@@ -89,7 +87,7 @@ function PaymentModalContainer({
);
}
} else {
updatedPayment = await updatePayment({
const updatedPayment = await updatePayment({
variables: {
paymentId: context.id,
payment: paymentObj,
@@ -103,11 +101,7 @@ function PaymentModalContainer({
}
}
if (actions.refetch)
actions.refetch(
updatedPayment && updatedPayment.data.update_payments.returning[0]
);
if (actions.refetch) actions.refetch();
if (enterAgain) {
const prev = form.getFieldsValue(["date"]);
@@ -178,24 +172,12 @@ function PaymentModalContainer({
</span>
}
>
{!context || (context && !context.id) ? null : (
<Space>
<PaymentReexportButton payment={context} refetch={actions.refetch} />
<PaymentMarkForExportButton
bodyshop={bodyshop}
payment={context}
refetch={actions.refetch}
/>
</Space>
)}
<Form
onFinish={handleFinish}
autoComplete={"off"}
form={form}
layout="vertical"
initialValues={context || {}}
disabled={context?.exportedat}
>
<PaymentForm form={form} />
</Form>

View File

@@ -1,66 +0,0 @@
import React from "react";
import { Button, notification } from "antd";
import { useTranslation } from "react-i18next";
import { UPDATE_PAYMENT } from "../../graphql/payments.queries";
import { useMutation } from "@apollo/client";
import { setModalContext } from "../../redux/modals/modals.actions";
import { connect } from "react-redux";
const mapDispatchToProps = (dispatch) => ({
setPaymentContext: (context) =>
dispatch(setModalContext({ context: context, modal: "payment" })),
});
const PaymentReexportButton = ({ payment, refetch, setPaymentContext }) => {
const { t } = useTranslation();
const [updatePayment, { loading }] = useMutation(UPDATE_PAYMENT);
const handleClick = async () => {
const paymentUpdateResponse = await updatePayment({
variables: {
paymentId: payment.id,
payment: {
exportedat: null,
},
},
});
if (!!!paymentUpdateResponse.errors) {
notification.open({
type: "success",
key: "paymentsuccessexport",
message: t("payments.successes.markreexported"),
});
if (refetch) refetch();
setPaymentContext({
actions: {
refetch,
},
context: {
...payment,
exportedat: null,
},
});
} else {
notification["error"]({
message: t("payments.errors.exporting", {
error: JSON.stringify(paymentUpdateResponse.error),
}),
});
}
};
return (
<Button
onClick={handleClick}
loading={loading}
disabled={!payment.exportedat}
>
{t("payments.labels.markforreexport")}
</Button>
);
};
export default connect(null, mapDispatchToProps)(PaymentReexportButton);

View File

@@ -1,23 +1,20 @@
import { EditFilled, SyncOutlined } from "@ant-design/icons";
import { useApolloClient } from "@apollo/client";
import { Button, Card, Input, Space, Table, Typography } from "antd";
import axios from "axios";
import queryString from "query-string";
import React, { useEffect, useState } from "react";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link, useHistory, useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { QUERY_PAYMENT_BY_ID } from "../../graphql/payments.queries";
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
import { TemplateList } from "../../utils/TemplateConstants";
import { alphaSort } from "../../utils/sorters";
import { TemplateList } from "../../utils/TemplateConstants";
import CaBcEtfTableModalContainer from "../ca-bc-etf-table-modal/ca-bc-etf-table-modal.container";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
@@ -42,10 +39,7 @@ export function PaymentsListPaginated({
bodyshop,
}) {
const search = queryString.parse(useLocation().search);
const [openSearchResults, setOpenSearchResults] = useState([]);
const [searchLoading, setSearchLoading] = useState(false);
const { page, sortcolumn, sortorder } = search;
const client = useApolloClient();
const history = useHistory();
const [state, setState] = useState({
sortedInfo: {},
@@ -58,17 +52,13 @@ export function PaymentsListPaginated({
title: t("jobs.fields.ro_number"),
dataIndex: "ro_number",
key: "ro_number",
// sorter: (a, b) => alphaSort(a.job.ro_number, b.job.ro_number),
// sortOrder: sortcolumn === "ro_number" && sortorder,
render: (text, record) => {
return record.job ? (
<Link to={"/manage/jobs/" + record.job.id}>
{record.job.ro_number || t("general.labels.na")}
</Link>
) : (
<span>{t("general.labels.na")}</span>
);
},
sorter: (a, b) => alphaSort(a.job.ro_number, b.job.ro_number),
sortOrder: sortcolumn === "ro_number" && sortorder,
render: (text, record) => (
<Link to={"/manage/jobs/" + record.job.id}>
{record.job.ro_number || t("general.labels.na")}
</Link>
),
},
{
title: t("payments.fields.paymentnum"),
@@ -82,16 +72,16 @@ export function PaymentsListPaginated({
dataIndex: "owner",
key: "owner",
ellipsis: true,
// sorter: (a, b) => alphaSort(a.job.ownr_ln, b.job.ownr_ln),
// sortOrder: sortcolumn === "owner" && sortorder,
sorter: (a, b) => alphaSort(a.job.ownr_ln, b.job.ownr_ln),
sortOrder: sortcolumn === "owner" && sortorder,
render: (text, record) => {
return record.job?.owner ? (
<Link to={"/manage/owners/" + record.job?.owner?.id}>
<OwnerNameDisplay ownerObject={record.job} />
return record.job.owner ? (
<Link to={"/manage/owners/" + record.job.owner.id}>
<OwnerNameDisplay ownerObject={record} />
</Link>
) : (
<span>
<OwnerNameDisplay ownerObject={record.job} />
<OwnerNameDisplay ownerObject={record} />
</span>
);
},
@@ -156,34 +146,11 @@ export function PaymentsListPaginated({
render: (text, record) => (
<Space>
<Button
// disabled={record.exportedat}
onClick={async () => {
let apolloResults;
if (search.search) {
const { data } = await client.query({
query: QUERY_PAYMENT_BY_ID,
variables: {
paymentId: record.id,
},
});
apolloResults = data.payments_by_pk;
}
disabled={record.exportedat}
onClick={() => {
setPaymentContext({
actions: {
refetch: apolloResults
? (updatedRecord) => {
setOpenSearchResults((results) =>
results.map((result) => {
if (result.id !== record.id) {
return result;
}
return updatedRecord;
})
);
}
: refetch,
},
context: apolloResults ? apolloResults : record,
actions: { refetch: refetch },
context: record,
});
}}
>
@@ -210,28 +177,6 @@ export function PaymentsListPaginated({
history.push({ search: queryString.stringify(search) });
};
useEffect(() => {
if (search.search && search.search.trim() !== "") {
searchPayments();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
async function searchPayments(value) {
try {
setSearchLoading(true);
const searchData = await axios.post("/search", {
search: value || search.search,
index: "payments",
});
setOpenSearchResults(searchData.data.hits.hits.map((s) => s._source));
} catch (error) {
console.log("Error while fetching search results", error);
} finally {
setSearchLoading(false);
}
}
return (
<Card
extra={
@@ -244,7 +189,6 @@ export function PaymentsListPaginated({
<Button
onClick={() => {
delete search.search;
delete search.page;
history.push({ search: queryString.stringify(search) });
}}
>
@@ -268,33 +212,24 @@ export function PaymentsListPaginated({
onSearch={(value) => {
search.search = value;
history.push({ search: queryString.stringify(search) });
searchPayments(value);
}}
loading={loading || searchLoading}
enterButton
/>
</Space>
}
>
<Table
loading={loading || searchLoading}
loading={loading}
scroll={{ x: true }}
pagination={
search?.search
? {
pageSize: 25,
showSizeChanger: false,
}
: {
pageSize: 25,
current: parseInt(page || 1),
total: total,
showSizeChanger: false,
}
}
pagination={{
position: "top",
pageSize: 25,
current: parseInt(page || 1),
total: total,
}}
columns={columns}
rowKey="id"
dataSource={search?.search ? openSearchResults : payments}
dataSource={payments}
onChange={handleTableChange}
/>
</Card>

View File

@@ -23,34 +23,14 @@ export function PrintCenterJobsComponent({ printCenterModal, bodyshop }) {
const { id: jobId, job } = printCenterModal.context;
const tempList = TemplateList("job", {});
const { t } = useTranslation();
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 JobsReportsList = Object.keys(tempList)
.map((key) => {
return tempList[key];
})
.filter(
(temp) =>
!temp.regions || (temp.regions && temp.regions[bodyshop.region_config])
);
const filteredJobsReportsList =
search !== ""

View File

@@ -1,51 +0,0 @@
import { Col, List, Space, Typography } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
const CardColorLegend = ({ bodyshop }) => {
const { t } = useTranslation();
const data = bodyshop.ssbuckets.map((bucket) => {
let color = { r: 255, g: 255, b: 255 };
if (bucket.color) {
color = bucket.color;
if (bucket.color.rgb) {
color = bucket.color.rgb;
}
}
return {
label: bucket.label,
color,
};
});
return (
<Col>
<Typography>{t("production.labels.legend")}</Typography>
<List
grid={{
gutter: 16,
}}
dataSource={data}
renderItem={(item) => (
<List.Item>
<Space>
<div
style={{
width: "1.5rem",
aspectRatio: "1/1",
backgroundColor: `rgba(${item.color.r},${item.color.g},${item.color.b},${item.color.a})`,
}}
></div>
<div>{item.label}</div>
</Space>
</List.Item>
)}
/>
</Col>
);
};
export default CardColorLegend;

View File

@@ -18,31 +18,6 @@ import moment from "moment";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component";
const cardColor = (ssbuckets, totalHrs) => {
const bucket = ssbuckets.filter(
(bucket) =>
bucket.gte <= totalHrs && (!!bucket.lt ? bucket.lt > totalHrs : true)
)[0];
let color = { r: 255, g: 255, b: 255 };
if (bucket && bucket.color) {
color = bucket.color;
if (bucket.color.rgb) {
color = bucket.color.rgb;
}
}
return color;
};
function getContrastYIQ(bgColor) {
const yiq = (bgColor.r * 299 + bgColor.g * 587 + bgColor.b * 114) / 1000;
return yiq >= 128 ? "black" : "white";
}
export default function ProductionBoardCard(
technician,
card,
@@ -79,22 +54,10 @@ export default function ProductionBoardCard(
.isSame(moment(card.scheduled_completion), "day") &&
"production-completion-soon"));
const totalHrs =
card.labhrs.aggregate.sum.mod_lb_hrs + card.larhrs.aggregate.sum.mod_lb_hrs;
const bgColor = cardColor(bodyshop.ssbuckets, totalHrs);
return (
<Card
className="react-kanban-card imex-kanban-card"
size="small"
style={{
backgroundColor:
cardSettings &&
cardSettings.cardcolor &&
`rgba(${bgColor.r},${bgColor.g},${bgColor.b},${bgColor.a})`,
color:
cardSettings && cardSettings.cardcolor && getContrastYIQ(bgColor),
}}
title={
<Space>
<ProductionAlert record={card} key="alert" />

View File

@@ -104,13 +104,6 @@ export default function ProductionBoardKanbanCardSettings({
>
<Switch />
</Form.Item>
<Form.Item
valuePropName="checked"
label={t("production.labels.cardcolor")}
name="cardcolor"
>
<Switch />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
@@ -173,7 +166,7 @@ export default function ProductionBoardKanbanCardSettings({
</div>
);
return (
<Popover content={overlay} visible={visible} placement="topRight">
<Popover content={overlay} visible={visible}>
<Button loading={loading} onClick={() => setVisible(true)}>
{t("production.labels.cardsettings")}
</Button>

View File

@@ -22,7 +22,6 @@ import ProductionBoardKanbanCardSettings from "./production-board-kanban.card-se
//import "@asseinfo/react-kanban/dist/styles.css";
import "./production-board-kanban.styles.scss";
import { createBoardData } from "./production-board-kanban.utils.js";
import CardColorLegend from "../production-board-kanban-card/production-board-kanban-card-color-legend.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
technician: selectTechnician,
@@ -222,7 +221,6 @@ export function ProductionBoardKanbanComponent({
employeeassignments: true,
scheduled_completion: true,
stickyheader: false,
cardcolor: false,
};
return (
@@ -258,11 +256,6 @@ export function ProductionBoardKanbanComponent({
</Space>
}
/>
{cardSettings.cardcolor && (
<CardColorLegend cardSettings={cardSettings} bodyshop={bodyshop} />
)}
<ProductionListDetailComponent jobs={data} />
<StickyContainer>
<Board

View File

@@ -91,13 +91,11 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
b.v_make_desc + b.v_model_desc
),
sortOrder:
state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order,
state.sortedInfo.columnKey === "ownr" && state.sortedInfo.order,
render: (text, record) => (
<Link to={`/manage/vehicles/${record.vehicleid}`}>{`${
record.v_model_yr || ""
} ${record.v_make_desc || ""} ${record.v_model_desc || ""} ${
record.v_color || ""
} ${record.plate_no || ""}`}</Link>
<span>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
record.v_model_desc || ""
} ${record.v_color || ""} ${record.plate_no || ""}`}</span>
),
},
{

View File

@@ -25,7 +25,6 @@ export default function ProductionListDate({
// }
//e.stopPropagation();
updateAlert({
variables: {
jobId: record.id,
@@ -33,11 +32,6 @@ export default function ProductionListDate({
[field]: date,
},
},
optimisticResponse: {
update_jobs: {
[field]: date,
},
},
}).then(() => {
if (record.refetch) record.refetch();
if (!time) {
@@ -55,11 +49,9 @@ export default function ProductionListDate({
(moment().add(1, "day").isSame(moment(record[field]), "day") &&
"production-completion-soon"));
}
return (
<Dropdown
trigger={["click"]}
onVisibleChange={(v) => setVisible(v)}
//trigger={["click"]}
visible={visible}
style={{
height: "19px",

View File

@@ -1,23 +1,12 @@
import Icon from "@ant-design/icons";
import { useMutation } from "@apollo/client";
import { Button, Input, Popover, Space } from "antd";
import { Button, Input, Popover } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { FaRegStickyNote } from "react-icons/fa";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { setModalContext } from "../../redux/modals/modals.actions";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
const mapStateToProps = createStructuredSelector({});
const mapDispatchToProps = (dispatch) => ({
setNoteUpsertContext: (context) =>
dispatch(setModalContext({ context: context, modal: "noteUpsert" })),
});
function ProductionListColumnProductionNote({ record, setNoteUpsertContext }) {
export default function ProductionListColumnProductionNote({ record }) {
const { t } = useTranslation();
const [note, setNote] = useState(
@@ -71,26 +60,12 @@ function ProductionListColumnProductionNote({ record, setNoteUpsertContext }) {
// onPressEnter={handleSaveNote}
autoFocus
allowClear
style={{ marginBottom: "1em" }}
/>
<Space>
<Button onClick={handleSaveNote} type="primary">
<div>
<Button onClick={handleSaveNote}>
{t("general.actions.save")}
</Button>
<Button
onClick={() => {
setVisible(false);
setNoteUpsertContext({
context: {
jobId: record.id,
text: note,
},
});
}}
>
{t("notes.actions.savetojobnotes")}
</Button>
</Space>
</div>
</div>
}
trigger={["click"]}
@@ -110,8 +85,3 @@ function ProductionListColumnProductionNote({ record, setNoteUpsertContext }) {
</Popover>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(ProductionListColumnProductionNote);

View File

@@ -81,7 +81,7 @@ export function ProductionListTable({
state,
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
}).find((e) => e.key === k.key),
width: k.width ?? 100,
width: k.width,
};
})) ||
[]
@@ -246,21 +246,11 @@ export function ProductionListTable({
(x) => x.status === record.status
);
if (!color) {
if (index % 2 === 0)
return {
style: {
backgroundColor: `rgb(236, 236, 236)`,
},
};
return null;
}
if (!color) return null;
return {
className: "rowWithColor",
style: {
"--bgColor": `rgb(${color.color.r},${color.color.g},${color.color.b},${color.color.a})`,
backgroundColor: `rgb(${color.color.r},${color.color.g},${color.color.b},${color.color.a})`,
},
};
},
@@ -277,8 +267,6 @@ export function ProductionListTable({
sortOrder:
state.sortedInfo.columnKey === c.key && state.sortedInfo.order,
title: headerItem(c),
ellipsis: true,
width: c.width ?? 100,
onHeaderCell: (column) => ({
width: column.width,
onResize: handleResize(index),
@@ -288,12 +276,11 @@ export function ProductionListTable({
rowKey="id"
loading={loading}
dataSource={dataSource}
scroll={{ x: 1000 }}
// scroll={{ x: true }}
onChange={handleTableChange}
/>
</ReactDragListView.DragColumn>
</div>
);
}
export default connect(mapStateToProps, null)(ProductionListTable);

View File

@@ -3,26 +3,8 @@ import { Resizable } from "react-resizable";
export default function ResizableComponent(props) {
const { onResize, width, ...restProps } = props;
if (!width) {
return <th {...restProps} />;
}
return (
<Resizable
width={width || 200}
height={0}
onResize={onResize}
draggableOpts={{ enableUserSelectHack: false }}
handle={
<span
className="react-resizable-handle"
onClick={(e) => {
e.stopPropagation();
}}
/>
}
>
<Resizable width={width || 200} height={0} onResize={onResize}>
<th {...restProps} />
</Resizable>
);

View File

@@ -26,8 +26,6 @@ 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,

View File

@@ -92,11 +92,7 @@ export function ReportCenterModalComponent({ reportCenterModal }) {
to: values.to,
subject: Templates[values.key]?.subject,
},
values.sendbyexcel === "excel"
? "x"
: values.sendby === "email"
? "e"
: "p",
values.sendby === "email" ? "e" : "p",
id
);
setLoading(false);
@@ -254,38 +250,15 @@ export function ReportCenterModalComponent({ reportCenterModal }) {
ranges={DatePIckerRanges}
/>
</Form.Item>
<Form.Item style={{ margin: 0, padding: 0 }} dependencies={["key"]}>
{() => {
const key = form.getFieldValue("key");
//Kind of Id
const reporttype = Templates[key] && Templates[key].reporttype;
if (reporttype === "excel")
return (
<Form.Item
label={t("general.labels.sendby")}
name="sendbyexcel"
initialValue="excel"
>
<Radio.Group>
<Radio value="excel">{t("general.labels.excel")}</Radio>
</Radio.Group>
</Form.Item>
);
if (reporttype !== "excel")
return (
<Form.Item
label={t("general.labels.sendby")}
name="sendby"
initialValue="print"
>
<Radio.Group>
<Radio value="email">{t("general.labels.email")}</Radio>
<Radio value="print">{t("general.labels.print")}</Radio>
</Radio.Group>
</Form.Item>
);
}}
<Form.Item
label={t("general.labels.sendby")}
name="sendby"
initialValue="print"
>
<Radio.Group>
<Radio value="email">{t("general.labels.email")}</Radio>
<Radio value="print">{t("general.labels.print")}</Radio>
</Radio.Group>
</Form.Item>
<div

View File

@@ -12,8 +12,7 @@ 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, Trans } from "react-i18next";
import { Link } from "react-router-dom";
import { useTranslation } from "react-i18next";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -67,21 +66,10 @@ export function ScheduleCalendarWrapperComponent({
<Alert
key={problem.id}
type="error"
message={
<Trans
i18nKey="appointments.labels.dataconsistency"
components={[
<Link
to={`/manage/jobs/${problem.id}`}
target="_blank"
/>,
]}
values={{
ro_number: problem.ro_number,
code: problem.code,
}}
/>
}
message={t("appointments.labels.dataconsistency", {
ro_number: problem.ro_number,
code: problem.code,
})}
/>
))}
</Collapse.Panel>
@@ -91,18 +79,10 @@ export function ScheduleCalendarWrapperComponent({
<Alert
key={problem.id}
type="error"
message={
<Trans
i18nKey="appointments.labels.dataconsistency"
components={[
<Link to={`/manage/jobs/${problem.id}`} target="_blank" />,
]}
values={{
ro_number: problem.ro_number,
code: problem.code,
}}
/>
}
message={t("appointments.labels.dataconsistency", {
ro_number: problem.ro_number,
code: problem.code,
})}
/>
))
)}

View File

@@ -21,7 +21,6 @@ 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,
});
@@ -40,39 +39,9 @@ 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) => {
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 (
return data.filter(
(d) =>
(d.block ||
(filter.intake && d.isintake) ||
(filter.manual && !d.isintake && d.block === false) ||
@@ -81,11 +50,9 @@ 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) &&
estFilter
);
});
}, [data, filter, estimatorsFilter]);
: true)
);
}, [data, filter]);
return (
<Row gutter={[16, 16]}>
@@ -96,21 +63,6 @@ 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"

View File

@@ -1,39 +0,0 @@
import Dinero from "dinero.js";
const CustomTooltip = ({ active, payload, label }) => {
if (active && payload && payload.length) {
return (
<div
style={{
backgroundColor: "white",
border: "1px solid gray",
padding: "0.5rem",
}}
>
<p style={{ margin: "0" }}>{label}</p>
{payload.map((data, index) => {
if (data.dataKey === "sales" || data.dataKey === "accSales")
return (
<p
style={{ margin: "10px 0", color: data.color }}
key={index}
>{`${data.name} : ${Dinero({
amount: Math.round(data.value * 100),
}).toFormat()}`}</p>
);
return (
<p
style={{ margin: "10px 0", color: data.color }}
key={index}
>{`${data.name} : ${data.value}`}</p>
);
})}
</div>
);
}
return null;
};
export default CustomTooltip;

View File

@@ -1,6 +1,4 @@
import { Card } from "antd";
import Dinero from "dinero.js";
import _ from "lodash";
import moment from "moment";
import React from "react";
import { connect } from "react-redux";
@@ -19,7 +17,7 @@ import {
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import * as Utils from "../scoreboard-targets-table/scoreboard-targets-table.util";
import CustomTooltip from "./chart-custom-tooltip";
import _ from "lodash";
const graphProps = {
strokeWidth: 3,
@@ -46,19 +44,14 @@ export function ScoreboardChart({ sbEntriesByDate, bodyshop }) {
return {
bodyhrs: dayAcc.bodyhrs + dayVal.bodyhrs,
painthrs: dayAcc.painthrs + dayVal.painthrs,
sales:
dayAcc.painthrs +
dayVal.job.job_totals.totals.subtotal.amount / 100 +
2500,
};
},
{ bodyhrs: 0, painthrs: 0, sales: 0 }
{ bodyhrs: 0, painthrs: 0 }
);
} else {
dayhrs = {
bodyhrs: 0,
painthrs: 0,
sales: 0,
};
}
@@ -71,9 +64,7 @@ export function ScoreboardChart({ sbEntriesByDate, bodyshop }) {
bodyshop.scoreboard_target.dailyBodyTarget +
bodyshop.scoreboard_target.dailyPaintTarget,
val
) +
bodyshop.scoreboard_target.dailyBodyTarget +
bodyshop.scoreboard_target.dailyPaintTarget,
),
1
),
accHrs: _.round(
@@ -82,13 +73,6 @@ export function ScoreboardChart({ sbEntriesByDate, bodyshop }) {
: dayhrs.painthrs + dayhrs.bodyhrs,
1
),
sales: _.round(dayhrs.sales, 2),
accSales: _.round(
acc.length > 0
? acc[acc.length - 1].accSales + dayhrs.sales
: dayhrs.sales,
2
),
};
return [...acc, theValue];
@@ -103,27 +87,22 @@ export function ScoreboardChart({ sbEntriesByDate, bodyshop }) {
>
<CartesianGrid stroke="#f5f5f5" />
<XAxis dataKey="date" strokeWidth={graphProps.strokeWidth} />
<YAxis
strokeWidth={graphProps.strokeWidth}
// allowDataOverflow
dataKey="sales"
yAxisId="right"
tickFormatter={(value) =>
Dinero({ amount: Math.round(value * 100) }).toFormat()
}
orientation="right"
/>
<YAxis yAxisId="left" strokeWidth={graphProps.strokeWidth} />
<Tooltip content={<CustomTooltip />} />
<YAxis strokeWidth={graphProps.strokeWidth} />
<Tooltip />
<Legend />
<Area
type="monotone"
name="Accumulated Hours"
dataKey="accHrs"
fill="lightgreen"
stroke="green"
/>
<Bar
name="Body Hours"
dataKey="bodyHrs"
stackId="day"
barSize={20}
fill="darkblue"
yAxisId="left"
/>
<Bar
name="Paint Hours"
@@ -131,42 +110,12 @@ export function ScoreboardChart({ sbEntriesByDate, bodyshop }) {
stackId="day"
barSize={20}
fill="darkred"
yAxisId="left"
/>
<Line
name="Target Hours"
type="monotone"
dataKey="accTargetHrs"
stroke="#ff7300"
yAxisId="left"
strokeWidth={graphProps.strokeWidth}
/>
<Area
type="monotone"
name="MTD Hours"
dataKey="accHrs"
fill="lightblue"
stroke="blue"
yAxisId="left"
/>
{
// <Area
// type="monotone"
// name="MTD Sales"
// dataKey="accSales"
// fill="lightgreen"
// stroke="green"
// yAxisId="right"
// />
}
<Bar
name="Sales"
dataKey="sales"
stackId="day"
barSize={20}
fill="darkgreen"
yAxisId="right"
strokeWidth={graphProps.strokeWidth}
/>
</ComposedChart>

View File

@@ -1,4 +1,4 @@
import { Card, Divider, Statistic } from "antd";
import { Card, Statistic } from "antd";
import moment from "moment";
import React from "react";
import { connect } from "react-redux";
@@ -41,9 +41,6 @@ export function ScoreboardDayStats({ bodyshop, date, entries }) {
label="P"
value={paintHrs.toFixed(1)}
/>
<Divider style={{ margin: 0 }} />
<Statistic value={(bodyHrs + paintHrs).toFixed(1)} />
</Card>
);
}

View File

@@ -1,5 +1,5 @@
import { CalendarOutlined } from "@ant-design/icons";
import { Card, Col, Divider, Row, Statistic } from "antd";
import { Card, Col, Row, Statistic } from "antd";
import _ from "lodash";
import moment from "moment";
import React, { useMemo } from "react";
@@ -177,9 +177,6 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
<Statistic value={values.toDatePaint.toFixed(1)} />
</Col>
</Row>
<Row>
<Divider style={{ margin: 5 }} />
</Row>
<Row>
<Col {...statSpans}></Col>
<Col {...statSpans}>
@@ -187,53 +184,14 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
value={(values.todayPaint + values.todayBody).toFixed(1)}
/>
</Col>
<Col {...statSpans}>
<Statistic
value={(
Util.WeeklyTargetHrs(
bodyshop.scoreboard_target.dailyBodyTarget,
bodyshop
) +
Util.WeeklyTargetHrs(
bodyshop.scoreboard_target.dailyPaintTarget,
bodyshop
)
).toFixed(1)}
/>
</Col>
<Col {...statSpans}></Col>
<Col {...statSpans}>
<Statistic
value={(values.weeklyPaint + values.weeklyBody).toFixed(1)}
/>
</Col>
<Col {...statSpans}>
<Statistic
value={(
Util.MonthlyTargetHrs(
bodyshop.scoreboard_target.dailyBodyTarget,
bodyshop
) +
Util.MonthlyTargetHrs(
bodyshop.scoreboard_target.dailyPaintTarget,
bodyshop
)
).toFixed(1)}
/>
</Col>
<Col {...statSpans}>
<Statistic
value={(
Util.AsOfTodayTargetHrs(
bodyshop.scoreboard_target.dailyBodyTarget,
bodyshop
) +
Util.AsOfTodayTargetHrs(
bodyshop.scoreboard_target.dailyPaintTarget,
bodyshop
)
).toFixed(1)}
/>
</Col>
<Col {...statSpans}></Col>
<Col {...statSpans}></Col>
<Col {...statSpans}>
<Statistic
value={(values.toDatePaint + values.toDateBody).toFixed(1)}

View File

@@ -81,7 +81,6 @@ export default function ScoreboardTimeTickets() {
totalLastMonth: 0,
totalOverPeriod: 0,
actualTotalOverPeriod: 0,
totalEffieciencyOverPeriod: 0,
employees: {},
};
data.fixedperiod.forEach((ticket) => {
@@ -95,7 +94,6 @@ export default function ScoreboardTimeTickets() {
totalLastMonth: 0,
totalOverPeriod: 0,
actualTotalOverPeriod: 0,
totalEffieciencyOverPeriod: 0,
};
}
@@ -223,30 +221,6 @@ export default function ScoreboardTimeTickets() {
ret2.push(r);
});
// Add total efficiency of employees
const totalActualAndProductive = Object.keys(ret.employees)
.map((key) => {
return { employee_number: key, ...ret.employees[key] };
})
.reduce(
(acc, e) => {
return {
totalOverPeriod: acc.totalOverPeriod + e.totalOverPeriod,
actualTotalOverPeriod:
acc.actualTotalOverPeriod + e.actualTotalOverPeriod,
};
},
{ totalOverPeriod: 0, actualTotalOverPeriod: 0 }
);
ret.totalEffieciencyOverPeriod =
totalActualAndProductive.actualTotalOverPeriod
? (totalActualAndProductive.totalOverPeriod /
totalActualAndProductive.actualTotalOverPeriod) *
100
: 0;
roundObject(ret);
roundObject(totals);
roundObject(ret2);

View File

@@ -62,7 +62,7 @@ export function ScoreboardTicketsStats({ data, bodyshop }) {
key: "efficiencyoverperiod",
render: (text, record) =>
`${(
(record.totalOverPeriod / (record.actualTotalOverPeriod || 0.1)) *
(record.totalOverPeriod / (record.actualTotalOverPeriod || .1)) *
100
).toFixed(1)} %`,
},
@@ -113,12 +113,6 @@ export function ScoreboardTicketsStats({ data, bodyshop }) {
value={data.totalOverPeriod}
/>
</Col>
<Col span={12}>
<Statistic
title={t("scoreboard.labels.efficiencyoverperiod")}
value={`${data.totalEffieciencyOverPeriod || 0}%`}
/>
</Col>
</Row>
<Typography.Text type="secondary">
{t("scoreboard.labels.calendarperiod")}
@@ -127,7 +121,7 @@ export function ScoreboardTicketsStats({ data, bodyshop }) {
<Col md={24} lg={20}>
<Table
columns={columns}
rowKey="employee_number"
rowKey='employee_number'
dataSource={tableData}
id="employee_number"
scroll={{ y: "300px" }}

View File

@@ -15,8 +15,6 @@ 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,
@@ -33,10 +31,6 @@ 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={
@@ -49,14 +43,7 @@ export function ShopInfoComponent({ bodyshop, form, saveLoading }) {
</Button>
}
>
<Tabs
defaultActiveKey={search.subtab}
onChange={(key) =>
history.push({
search: `?tab=${search.tab}&subtab=${key}`,
})
}
>
<Tabs>
<Tabs.TabPane key="general" tab={t("bodyshop.labels.shopinfo")}>
<ShopInfoGeneral form={form} />
</Tabs.TabPane>

View File

@@ -11,26 +11,22 @@ import {
} from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import FormItemEmail from "../form-items-formatted/email-form-item.component";
import PhoneFormItem, {
PhoneItemFormatterValidation,
} from "../form-items-formatted/phone-form-item.component";
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import FormItemEmail from "../form-items-formatted/email-form-item.component";
import momentTZ from "moment-timezone";
const timeZonesList = momentTZ.tz.names();
export default function ShopInfoGeneral({ form }) {
const { t } = useTranslation();
return (
<div>
<LayoutFormRow
header={t("bodyshop.labels.businessinformation")}
id="businessinformation"
>
<LayoutFormRow header={t("bodyshop.labels.businessinformation")}>
<Form.Item
label={t("bodyshop.fields.shopname")}
name="shopname"
@@ -159,10 +155,7 @@ export default function ShopInfoGeneral({ form }) {
<InputNumber min={0} />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow
header={t("bodyshop.labels.accountingsetup")}
id="accountingsetup"
>
<LayoutFormRow header={t("bodyshop.labels.accountingsetup")}>
<Form.Item
label={t("bodyshop.labels.qbo")}
valuePropName="checked"
@@ -393,10 +386,7 @@ export default function ShopInfoGeneral({ form }) {
<Select mode="tags" />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow
header={t("bodyshop.labels.scoreboardsetup")}
id="scoreboardsetup"
>
<LayoutFormRow header={t("bodyshop.labels.scoreboardsetup")}>
<Form.Item
label={t("bodyshop.fields.dailypainttarget")}
name={["scoreboard_target", "dailyPaintTarget"]}
@@ -455,10 +445,7 @@ export default function ShopInfoGeneral({ form }) {
<InputNumber min={1} precision={1} />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow
header={t("bodyshop.labels.systemsettings")}
id="systemsettings"
>
<LayoutFormRow header={t("bodyshop.labels.systemsettings")}>
<Form.Item
name={["md_referral_sources"]}
label={t("bodyshop.fields.md_referral_sources")}
@@ -564,13 +551,6 @@ export default function ShopInfoGeneral({ form }) {
>
<CurrencyInput />
</Form.Item>
<Form.Item
name={["use_paint_scale_data"]}
label={t("bodyshop.fields.use_paint_scale_data")}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
name={["attach_pdf_to_email"]}
label={t("bodyshop.fields.attach_pdf_to_email")}
@@ -609,13 +589,6 @@ export default function ShopInfoGeneral({ form }) {
>
<Switch />
</Form.Item>
<Form.Item
name={["tt_enforce_hours_for_tech_console"]}
label={t("bodyshop.fields.tt_enforce_hours_for_tech_console")}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
name={["bill_allow_post_to_closed"]}
label={t("bodyshop.fields.bill_allow_post_to_closed")}
@@ -668,11 +641,7 @@ export default function ShopInfoGeneral({ form }) {
<Input />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow
grow
header={t("bodyshop.labels.messagingpresets")}
id="messagingpresets"
>
<LayoutFormRow grow header={t("bodyshop.labels.messagingpresets")}>
<Form.List name={["md_messaging_presets"]}>
{(fields, { add, remove, move }) => {
return (
@@ -737,11 +706,7 @@ export default function ShopInfoGeneral({ form }) {
}}
</Form.List>
</LayoutFormRow>
<LayoutFormRow
grow
header={t("bodyshop.labels.notespresets")}
id="notespresets"
>
<LayoutFormRow grow header={t("bodyshop.labels.notespresets")}>
<Form.List name={["md_notes_presets"]}>
{(fields, { add, remove, move }) => {
return (
@@ -806,11 +771,7 @@ export default function ShopInfoGeneral({ form }) {
}}
</Form.List>
</LayoutFormRow>
<LayoutFormRow
grow
header={t("bodyshop.labels.partslocations")}
id="partslocations"
>
<LayoutFormRow grow header={t("bodyshop.labels.partslocations")}>
<Form.List name={["md_parts_locations"]}>
{(fields, { add, remove, move }) => {
return (
@@ -864,11 +825,7 @@ export default function ShopInfoGeneral({ form }) {
}}
</Form.List>
</LayoutFormRow>
<LayoutFormRow
grow
header={t("bodyshop.labels.insurancecos")}
id="insurancecos"
>
<LayoutFormRow grow header={t("bodyshop.labels.insurancecos")}>
<Form.List name={["md_ins_cos"]}>
{(fields, { add, remove, move }) => {
return (
@@ -964,11 +921,7 @@ export default function ShopInfoGeneral({ form }) {
}}
</Form.List>
</LayoutFormRow>
<LayoutFormRow
grow
header={t("bodyshop.labels.estimators")}
id="estimators"
>
<LayoutFormRow grow header={t("bodyshop.labels.estimators")}>
<Form.List name={["md_estimators"]}>
{(fields, { add, remove, move }) => {
return (
@@ -1057,11 +1010,7 @@ export default function ShopInfoGeneral({ form }) {
}}
</Form.List>
</LayoutFormRow>
<LayoutFormRow
grow
header={t("bodyshop.labels.filehandlers")}
id="filehandlers"
>
<LayoutFormRow grow header={t("bodyshop.labels.filehandlers")}>
<Form.List name={["md_filehandlers"]}>
{(fields, { add, remove, move }) => {
return (
@@ -1143,11 +1092,7 @@ export default function ShopInfoGeneral({ form }) {
}}
</Form.List>
</LayoutFormRow>
<LayoutFormRow
grow
header={t("bodyshop.fields.md_ccc_rates")}
id="md_ccc_rates"
>
<LayoutFormRow grow header={t("bodyshop.fields.md_ccc_rates")}>
<Form.List name={["md_ccc_rates"]}>
{(fields, { add, remove, move }) => {
return (
@@ -1264,11 +1209,7 @@ export default function ShopInfoGeneral({ form }) {
}}
</Form.List>
</LayoutFormRow>
<LayoutFormRow
grow
header={t("bodyshop.fields.md_jobline_presets")}
id="md_jobline_presets"
>
<LayoutFormRow grow header={t("bodyshop.fields.md_jobline_presets")}>
<Form.List name={["md_jobline_presets"]}>
{(fields, { add, remove, move }) => {
return (
@@ -1449,11 +1390,7 @@ export default function ShopInfoGeneral({ form }) {
}}
</Form.List>
</LayoutFormRow>
<LayoutFormRow
grow
header={t("bodyshop.fields.md_parts_order_comment")}
id="md_parts_order_comment"
>
<LayoutFormRow grow header={t("bodyshop.fields.md_parts_order_comment")}>
<Form.List name={["md_parts_order_comment"]}>
{(fields, { add, remove, move }) => {
return (
@@ -1519,11 +1456,7 @@ export default function ShopInfoGeneral({ form }) {
}}
</Form.List>
</LayoutFormRow>
<LayoutFormRow
grow
header={t("bodyshop.labels.md_to_emails")}
id="md_to_emails"
>
<LayoutFormRow grow header={t("bodyshop.labels.md_to_emails")}>
<Form.List name={["md_to_emails"]}>
{(fields, { add, remove, move }) => {
return (

View File

@@ -20,10 +20,7 @@ export default function ShopInfoIntakeChecklistComponent({ form }) {
const TemplateListGenerated = TemplateList();
return (
<div>
<LayoutFormRow
header={t("bodyshop.labels.intakechecklist")}
id="intakechecklist"
>
<LayoutFormRow header={t("bodyshop.labels.intakechecklist")}>
<Form.List name={["intakechecklist", "form"]}>
{(fields, { add, remove, move }) => {
return (
@@ -191,10 +188,7 @@ export default function ShopInfoIntakeChecklistComponent({ form }) {
</Form.Item>
</SelectorDiv>
<LayoutFormRow
header={t("bodyshop.labels.deliverchecklist")}
id="deliverchecklist"
>
<LayoutFormRow header={t("bodyshop.labels.deliverchecklist")}>
<Form.List name={["deliverchecklist", "form"]}>
{(fields, { add, remove, move }) => {
return (

View File

@@ -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,18 +316,6 @@ 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={[

View File

@@ -95,6 +95,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
{form.getFieldValue("pbs_serialnumber")}
</DataLabel>
)}
<LayoutFormRow>
<Form.Item
label={t("bodyshop.fields.dms.default_journal")}
@@ -314,10 +315,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
</DataLabel>
</>
)}
<LayoutFormRow
header={t("bodyshop.labels.responsibilitycenters.costs")}
id="costs"
>
<LayoutFormRow header={t("bodyshop.labels.responsibilitycenters.costs")}>
<Form.List name={["md_responsibility_centers", "costs"]}>
{(fields, { add, remove }) => {
return (
@@ -464,7 +462,6 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
<LayoutFormRow
header={t("bodyshop.labels.responsibilitycenters.profits")}
id="profits"
>
<Form.List name={["md_responsibility_centers", "profits"]}>
{(fields, { add, remove }) => {
@@ -604,7 +601,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
{fields.map((field, index) => (
<Form.Item key={field.key}>
<div>
<LayoutFormRow id="mappingname">
<LayoutFormRow>
<Form.Item
label={t("bodyshop.fields.dms.mappingname")}
key={`${index}name`}
@@ -634,7 +631,6 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
</LayoutFormRow>
<LayoutFormRow
header={t("bodyshop.labels.defaultcostsmapping")}
id="defaultcostsmapping"
>
<Form.Item
label={t(
@@ -4092,7 +4088,6 @@ 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")}
@@ -4207,7 +4202,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
</Form.Item>
</LayoutFormRow>
{DmsAp.treatment === "on" && (
<LayoutFormRow id="federal_tax_itc">
<LayoutFormRow>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.federal_tax_itc")}
rules={[
@@ -4321,7 +4316,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
</Form.Item>
</LayoutFormRow>
)}
<LayoutFormRow id="state_tax">
<LayoutFormRow>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.state_tax")}
rules={[
@@ -4419,7 +4414,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
<InputNumber precision={2} />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow id="local_tax">
<LayoutFormRow>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.local_tax")}
rules={[
@@ -4517,7 +4512,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
<InputNumber precision={2} />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={<div>AR</div>} id="AR">
<LayoutFormRow header={<div>AR</div>}>
{/* <Form.Item
label={t("bodyshop.fields.responsibilitycenters.ar")}
rules={[
@@ -4581,10 +4576,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
</LayoutFormRow>
{DmsAp.treatment === "on" && (
<LayoutFormRow
header={t("bodyshop.fields.responsibilitycenters.ap")}
id="ap"
>
<LayoutFormRow header={t("bodyshop.fields.responsibilitycenters.ap")}>
{/* <Form.Item
label={t("bodyshop.fields.responsibilitycenters.ap")}
rules={[
@@ -4647,7 +4639,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
</Form.Item>
</LayoutFormRow>
)}
<LayoutFormRow header={<div>Refund</div>} id="refund">
<LayoutFormRow header={<div>Refund</div>}>
{/* <Form.Item
label={t("bodyshop.fields.responsibilitycenters.refund")}
rules={[
@@ -4710,10 +4702,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
</Form.Item>
</LayoutFormRow>
{Qb_Multi_Ar.treatment === "on" && (
<LayoutFormRow
header={<div>Multiple Payers Item</div>}
id="accountitem"
>
<LayoutFormRow header={<div>Multiple Payers Item</div>}>
<Form.Item
label={t("bodyshop.fields.responsibilitycenter_accountitem")}
rules={[
@@ -4741,7 +4730,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
<div>
{fields.map((field, index) => (
<Form.Item key={field.key}>
<LayoutFormRow id="sales_tax_codes">
<LayoutFormRow>
<Form.Item
label={t(
"bodyshop.fields.responsibilitycenters.sales_tax_codes.description"

View File

@@ -44,7 +44,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
};
return (
<SelectorDiv id="jobstatus">
<SelectorDiv>
<Form.Item
name={["md_ro_statuses", "statuses"]}
label={t("bodyshop.labels.alljobstatuses")}
@@ -322,7 +322,6 @@ 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 }) => {
@@ -397,7 +396,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
);
}
export const ColorPicker = ({ value, onChange, style, ...restProps }) => {
const ColorPicker = ({ value, onChange, style, ...restProps }) => {
const handleChange = (color) => {
if (onChange) onChange(color.rgb);
};

View File

@@ -15,7 +15,6 @@ import { useTranslation } from "react-i18next";
import ColorpickerFormItemComponent from "../form-items-formatted/colorpicker-form-item.component";
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import { ColorPicker } from "./shop-info.rostatus.component";
export default function ShopInfoSchedulingComponent({ form }) {
const { t } = useTranslation();
@@ -92,7 +91,7 @@ export default function ShopInfoSchedulingComponent({ form }) {
</Form.Item>
</LayoutFormRow>
<Divider orientation="left">{t("bodyshop.labels.workingdays")}</Divider>
<Space wrap size="large" id="workingdays">
<Space wrap size="large">
<Form.Item
label={t("general.labels.sunday")}
name={["workingdays", "sunday"]}
@@ -143,7 +142,7 @@ export default function ShopInfoSchedulingComponent({ form }) {
<Switch />
</Form.Item>
</Space>
<LayoutFormRow header={t("bodyshop.labels.apptcolors")} id="apptcolors">
<LayoutFormRow header={t("bodyshop.labels.apptcolors")}>
<Form.List name={["appt_colors"]}>
{(fields, { add, remove, move }) => {
return (
@@ -208,7 +207,7 @@ export default function ShopInfoSchedulingComponent({ form }) {
}}
</Form.List>
</LayoutFormRow>
<LayoutFormRow header={t("bodyshop.labels.ssbuckets")} id="ssbuckets">
<LayoutFormRow header={t("bodyshop.labels.ssbuckets")}>
<Form.List name={["ssbuckets"]}>
{(fields, { add, remove, move }) => {
return (
@@ -278,50 +277,17 @@ export default function ShopInfoSchedulingComponent({ form }) {
>
<InputNumber />
</Form.Item>
<Space direction="horizontal">
<Form.Item
label={
<Space>
{t("bodyshop.fields.ssbuckets.color")}
<Button
size="small"
onClick={() => {
form.setFieldValue([
"ssbuckets",
field.name,
"color",
]);
form.setFields([
{
name: ["ssbuckets", field.name, "color"],
touched: true,
},
]);
}}
>
Reset
</Button>
</Space>
}
key={`${index}color`}
name={[field.name, "color"]}
>
<ColorPicker />
</Form.Item>
<Space wrap>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
/>
</Space>
<Space wrap>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
/>
</Space>
</LayoutFormRow>
</Form.Item>

View File

@@ -33,7 +33,6 @@ 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>

View File

@@ -26,10 +26,6 @@ export function TechClockInContainer({
technician,
bodyshop,
}) {
console.log(
"🚀 ~ file: tech-job-clock-in-form.container.jsx:29 ~ technician:",
technician
);
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [insertTimeTicket] = useMutation(INSERT_NEW_TIME_TICKET, {
@@ -37,10 +33,6 @@ export function TechClockInContainer({
});
const { t } = useTranslation();
const emps = bodyshop.employees.filter(
(e) => e.id === (technician && technician.id)
)[0];
const handleFinish = async (values) => {
setLoading(true);
const theTime = (await axios.post("/utils/time")).data;
@@ -95,12 +87,7 @@ export function TechClockInContainer({
onClick={() => {
setTimeTicketContext({
actions: {},
context: {
timeticket: {
employeeid: technician.id,
flat_rate: emps.flat_rate,
},
},
context: { timeticket: { employeeid: technician.id } },
});
}}
>

View File

@@ -1,4 +1,4 @@
import { useMutation, useQuery } from "@apollo/client";
import { useMutation } from "@apollo/client";
import {
Button,
Card,
@@ -21,8 +21,6 @@ import { selectTechnician } from "../../redux/tech/tech.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
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,
@@ -41,17 +39,7 @@ export function TechClockOffButton({
const [loading, setLoading] = useState(false);
const [updateTimeticket] = useMutation(UPDATE_TIME_TICKET);
const [form] = Form.useForm();
const { queryLoading, data: lineTicketData } = useQuery(
GET_LINE_TICKET_BY_PK,
{
variables: {
id: jobId,
},
skip: !jobId,
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
}
);
const { t } = useTranslation();
const emps = bodyshop.employees.filter(
(e) => e.id === (technician && technician.id)
@@ -71,7 +59,6 @@ export function TechClockOffButton({
emps &&
emps.rates.filter((r) => r.cost_center === values.cost_center)[0]
?.rate,
flat_rate: emps && emps.flat_rate,
ciecacode:
bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
? values.cost_center
@@ -141,54 +128,6 @@ export function TechClockOffButton({
required: true,
//message: t("general.validation.required"),
},
({ getFieldValue }) => ({
validator(rule, value) {
console.log(
bodyshop.tt_enforce_hours_for_tech_console
);
if (!bodyshop.tt_enforce_hours_for_tech_console) {
return Promise.resolve();
}
if (
!value ||
getFieldValue("cost_center") === null ||
!lineTicketData
)
return Promise.resolve();
//Check the cost center,
const totals = CalculateAllocationsTotals(
bodyshop,
lineTicketData.joblines,
lineTicketData.timetickets,
lineTicketData.jobs_by_pk.lbr_adjustments
);
const fieldTypeToCheck =
bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
? "mod_lbr_ty"
: "cost_center";
const costCenterDiff =
Math.round(
totals.find(
(total) =>
total[fieldTypeToCheck] ===
getFieldValue("cost_center")
)?.difference * 10
) / 10;
if (value > costCenterDiff)
return Promise.reject(
t(
"timetickets.validation.hoursenteredmorethanavailable"
)
);
else {
return Promise.resolve();
}
},
}),
]}
>
<InputNumber min={0} precision={1} />
@@ -238,11 +177,7 @@ export function TechClockOffButton({
</Col>
{!isShiftTicket && (
<Col span={16}>
<LaborAllocationContainer
jobid={jobId || null}
loading={queryLoading}
lineTicketData={lineTicketData}
/>
<LaborAllocationContainer jobid={jobId} />
</Col>
)}
</Row>

View File

@@ -1,131 +0,0 @@
import { useQuery } from "@apollo/client";
import { Card, Col, Space, Statistic, Typography } from "antd";
import moment from "moment";
import { useMemo } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { QUERY_TIME_TICKETS_TECHNICIAN_IN_RANGE } from "../../graphql/timetickets.queries";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import AlertComponent from "../alert/alert.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
const { Title } = Typography;
const mapStateToProps = createStructuredSelector({
technician: selectTechnician,
});
const mapDispatchToProps = (dispatch) => ({});
const TechJobStatistics = ({ technician }) => {
const { t } = useTranslation();
const startDate = moment().startOf("week");
const endDate = moment().endOf("week");
const { loading, error, data } = useQuery(
QUERY_TIME_TICKETS_TECHNICIAN_IN_RANGE,
{
variables: {
start: startDate.format("YYYY-MM-DD"),
end: endDate.format("YYYY-MM-DD"),
fixedStart: moment().startOf("month").format("YYYY-MM-DD"),
fixedEnd: moment().endOf("month").format("YYYY-MM-DD"),
employeeid: technician.id,
},
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
}
);
const totals = useMemo(() => {
if (data && data.timetickets && data.fixedperiod) {
const week = data.timetickets.reduce(
(acc, val) => {
acc.productivehrs = acc.productivehrs + val.productivehrs;
acc.actualhrs = acc.actualhrs + val.actualhrs;
return acc;
},
{ productivehrs: 0, actualhrs: 0 }
);
const month = data.fixedperiod.reduce(
(acc, val) => {
acc.productivehrs = acc.productivehrs + val.productivehrs;
acc.actualhrs = acc.actualhrs + val.actualhrs;
return acc;
},
{ productivehrs: 0, actualhrs: 0 }
);
return {
week,
month,
};
}
return {
week: { productivehrs: 0, actualhrs: 0 },
month: { productivehrs: 0, actualhrs: 0 },
};
}, [data]);
if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />;
return (
<Card title={t("scoreboard.labels.productivestatistics")}>
<Space size={100}>
<Col>
<Title level={5}>{t("scoreboard.labels.thisweek")}</Title>
<Space size={20}>
<Statistic
title={t("timetickets.fields.productivehrs")}
value={totals.week.productivehrs.toFixed(2)}
/>
<Statistic
title={t("timetickets.fields.actualhrs")}
value={totals.week.actualhrs.toFixed(2)}
/>
<Statistic
title={t("timetickets.labels.efficiency")}
value={
totals.week.actualhrs
? `${(
(totals.week.productivehrs / totals.week.actualhrs) *
100
).toFixed(2)}%`
: "0%"
}
/>
</Space>
</Col>
<Col>
<Title level={5}>{t("scoreboard.labels.thismonth")}</Title>
<Space size={20}>
<Statistic
title={t("timetickets.fields.productivehrs")}
value={totals.month.productivehrs.toFixed(2)}
/>
<Statistic
title={t("timetickets.fields.actualhrs")}
value={totals.month.actualhrs.toFixed(2)}
/>
<Statistic
title={t("timetickets.labels.efficiency")}
value={
totals.month.actualhrs
? `${(
(totals.month.productivehrs / totals.month.actualhrs) *
100
).toFixed(2)}%`
: "0%"
}
/>
</Space>
</Col>
</Space>
</Card>
);
};
export default connect(mapStateToProps, mapDispatchToProps)(TechJobStatistics);

View File

@@ -29,17 +29,7 @@ export function TechSider({ technician, techLogout }) {
};
return (
<Sider
style={{
height: "100vh",
position: "sticky",
top: 0,
left: 0,
}}
collapsible
collapsed={collapsed}
onCollapse={onCollapse}
>
<Sider collapsible collapsed={collapsed} onCollapse={onCollapse}>
<Menu theme="dark" defaultSelectedKeys={["1"]} mode="inline">
<Menu.Item
key="1"

View File

@@ -1,4 +1,4 @@
import { useLazyQuery } from "@apollo/client";
import { useQuery } from "@apollo/client";
import { Form, Input, InputNumber, Select, Switch } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
@@ -14,7 +14,6 @@ import FormDatePicker from "../form-date-picker/form-date-picker.component";
import FormDateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
import JobSearchSelect from "../job-search-select/job-search-select.component";
import LaborAllocationsTable from "../labor-allocations-table/labor-allocations-table.component";
import { CalculateAllocationsTotals } from "../labor-allocations-table/labor-allocations-table.utility";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
@@ -39,11 +38,7 @@ export function TimeTicketModalComponent({
employeeSelectDisabled,
}) {
const { t } = useTranslation();
const [loadLineTicketData, { called, loading, data: lineTicketData }] =
useLazyQuery(GET_LINE_TICKET_BY_PK, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
const CostCenterSelect = ({ emps, value, ...props }) => {
return (
<Select
@@ -96,9 +91,8 @@ export function TimeTicketModalComponent({
]}
>
<JobSearchSelect
convertedOnly={true}
convertedOnly={!bodyshop.tt_allow_post_to_invoiced}
notExported={!bodyshop.tt_allow_post_to_invoiced}
notInvoiced={!bodyshop.tt_allow_post_to_invoiced}
/>
</Form.Item>
)}
@@ -182,51 +176,6 @@ export function TimeTicketModalComponent({
label={t("timetickets.fields.productivehrs")}
name="productivehrs"
rules={[
({ getFieldValue }) => ({
validator(rule, value) {
if (!bodyshop.tt_enforce_hours_for_tech_console) {
return Promise.resolve();
}
if (
!value ||
getFieldValue("cost_center") === null ||
!lineTicketData
)
return Promise.resolve();
//Check the cost center,
const totals = CalculateAllocationsTotals(
bodyshop,
lineTicketData.joblines,
lineTicketData.timetickets,
lineTicketData.jobs_by_pk.lbr_adjustments
);
const fieldTypeToCheck =
bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
? "mod_lbr_ty"
: "cost_center";
const costCenterDiff =
Math.round(
totals.find(
(total) =>
total[fieldTypeToCheck] ===
getFieldValue("cost_center")
)?.difference * 10
) / 10;
if (value > costCenterDiff)
return Promise.reject(
t(
"timetickets.validation.hoursenteredmorethanavailable"
)
);
else {
return Promise.resolve();
}
},
}),
{
required:
form.getFieldValue("cost_center") !==
@@ -342,28 +291,23 @@ export function TimeTicketModalComponent({
</Form.Item>
</LayoutFormRow>
<Form.Item dependencies={["jobid"]}>
{() => {
const jobid = form.getFieldValue("jobid");
if (
(!called && jobid) ||
(jobid && lineTicketData?.jobs_by_pk?.id !== jobid && !loading)
) {
loadLineTicketData({ variables: { id: jobid } });
}
return (
<LaborAllocationContainer
jobid={jobid || null}
loading={loading}
lineTicketData={lineTicketData}
/>
);
}}
{() => (
<LaborAllocationContainer
jobid={form.getFieldValue("jobid") || null}
/>
)}
</Form.Item>
</div>
);
}
export function LaborAllocationContainer({ jobid, loading, lineTicketData }) {
export function LaborAllocationContainer({ jobid }) {
const { loading, data: lineTicketData } = useQuery(GET_LINE_TICKET_BY_PK, {
variables: { id: jobid },
skip: !jobid,
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
if (loading) return <LoadingSkeleton />;
if (!lineTicketData) return null;
return (

View File

@@ -1,78 +0,0 @@
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 });

View File

@@ -57,8 +57,6 @@ 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 } }
) {

View File

@@ -20,11 +20,13 @@ export const DELETE_BILL = gql`
export const QUERY_ALL_BILLS_PAGINATED = gql`
query QUERY_ALL_BILLS_PAGINATED(
$search: String
$offset: Int
$limit: Int
$order: [bills_order_by!]!
) {
bills(
search_bills(
args: { search: $search }
offset: $offset
limit: $limit
order_by: $order
@@ -49,7 +51,7 @@ export const QUERY_ALL_BILLS_PAGINATED = gql`
ro_number
}
}
bills_aggregate {
search_bills_aggregate(args: { search: $search }) {
aggregate {
count(distinct: true)
}
@@ -67,7 +69,6 @@ export const QUERY_BILLS_BY_JOBID = gql`
vendor {
id
name
email
}
order_date
deliver_by
@@ -103,7 +104,6 @@ export const QUERY_BILLS_BY_JOBID = gql`
vendor {
id
name
email
}
total
invoice_number

View File

@@ -116,8 +116,6 @@ export const QUERY_BODYSHOP = gql`
md_lost_sale_reasons
md_parts_scan
enforce_conversion_category
tt_enforce_hours_for_tech_console
use_paint_scale_data
employees {
user_email
id
@@ -127,7 +125,6 @@ export const QUERY_BODYSHOP = gql`
employee_number
rates
external_id
flat_rate
}
}
}
@@ -232,7 +229,6 @@ export const UPDATE_SHOP = gql`
md_lost_sale_reasons
md_parts_scan
enforce_conversion_category
tt_enforce_hours_for_tech_console
employees {
id
first_name

View File

@@ -1,5 +1,36 @@
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(
@@ -13,11 +44,10 @@ export const UNREAD_CONVERSATION_COUNT = gql`
`;
export const CONVERSATION_LIST_QUERY = gql`
query CONVERSATION_LIST_QUERY($offset: Int!) {
query CONVERSATION_LIST_QUERY {
conversations(
order_by: { updated_at: desc }
limit: 50
offset: $offset
where: { archived: { _eq: false } }
) {
phone_num

View File

@@ -284,7 +284,6 @@ export const QUERY_JOBS_IN_PRODUCTION = gql`
clm_no
v_make_desc
v_color
vehicleid
plate_no
actual_in
scheduled_completion
@@ -1075,9 +1074,6 @@ export const UPDATE_JOB = gql`
lbr_adjustments
suspended
queued_for_parts
scheduled_completion
actual_in
date_repairstarted
}
}
}
@@ -1281,7 +1277,7 @@ export const SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE = gql`
export const SEARCH_FOR_JOBS = gql`
query SEARCH_FOR_JOBS($search: String!) {
search_jobs(args: { search: $search }, limit: 25) {
search_jobs(args: { search: $search }) {
id
ro_number
ownr_fn
@@ -1784,12 +1780,14 @@ export const QUERY_ALL_JOB_FIELDS = gql`
export const QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED = gql`
query QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED(
$search: String
$offset: Int
$limit: Int
$order: [jobs_order_by!]
$statusList: [String!]
) {
jobs(
search_jobs(
args: { search: $search }
offset: $offset
limit: $limit
order_by: $order
@@ -1820,7 +1818,10 @@ export const QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED = gql`
updated_at
ded_amt
}
jobs_aggregate(where: { status: { _in: $statusList } }) {
search_jobs_aggregate(
args: { search: $search }
where: { status: { _in: $statusList } }
) {
aggregate {
count(distinct: true)
}

View File

@@ -4,16 +4,7 @@ export const INSERT_NEW_NOTE = gql`
mutation INSERT_NEW_NOTE($noteInput: [notes_insert_input!]!) {
insert_notes(objects: $noteInput) {
returning {
created_at
created_by
critical
id
jobid
private
text
updated_at
audit
type
}
}
}
@@ -24,8 +15,8 @@ export const QUERY_NOTES_BY_JOB_PK = gql`
jobs_by_pk(id: $id) {
id
ro_number
vehicle {
jobs {
vehicle{
jobs{
id
ro_number
status
@@ -42,7 +33,6 @@ export const QUERY_NOTES_BY_JOB_PK = gql`
text
updated_at
audit
type
}
}
}
@@ -62,7 +52,6 @@ export const UPDATE_NOTE = gql`
text
updated_at
audit
type
}
}
}

View File

@@ -12,39 +12,39 @@ export const INSERT_NEW_PAYMENT = gql`
export const QUERY_ALL_PAYMENTS_PAGINATED = gql`
query QUERY_ALL_PAYMENTS_PAGINATED(
$search: String
$offset: Int
$limit: Int
$order: [payments_order_by!]!
) {
payments(offset: $offset, limit: $limit, order_by: $order) {
search_payments(
args: { search: $search }
offset: $offset
limit: $limit
order_by: $order
) {
id
amount
created_at
date
exportedat
jobid
paymentnum
date
job {
id
ownerid
ownr_co_nm
ro_number
ownr_fn
ownr_ln
owner {
id
ownr_co_nm
ownr_fn
ownr_ln
}
ro_number
ownr_co_nm
}
memo
payer
paymentnum
stripeid
transactionid
memo
type
amount
stripeid
exportedat
stripeid
payer
}
payments_aggregate {
search_payments_aggregate(args: { search: $search }) {
aggregate {
count(distinct: true)
}
@@ -57,31 +57,16 @@ export const UPDATE_PAYMENT = gql`
update_payments(where: { id: { _eq: $paymentId } }, _set: $payment) {
returning {
id
amount
created_at
date
exportedat
jobid
job {
id
ownerid
ownr_co_nm
ownr_fn
ownr_ln
owner {
id
ownr_co_nm
ownr_fn
ownr_ln
}
ro_number
}
transactionid
memo
type
amount
stripeid
exportedat
stripeid
payer
paymentnum
stripeid
transactionid
type
date
}
}
}
@@ -95,31 +80,17 @@ export const UPDATE_PAYMENTS = gql`
update_payments(where: { id: { _in: $paymentIdList } }, _set: $payment) {
returning {
id
amount
created_at
date
exportedat
jobid
job {
id
ownerid
ownr_co_nm
ownr_fn
ownr_ln
owner {
id
ownr_co_nm
ownr_fn
ownr_ln
}
ro_number
}
transactionid
memo
type
amount
stripeid
exportedat
stripeid
payer
paymentnum
stripeid
transactionid
type
date
}
}
}
@@ -138,36 +109,3 @@ export const QUERY_JOB_PAYMENT_TOTALS = gql`
}
}
`;
export const QUERY_PAYMENT_BY_ID = gql`
query QUERY_PAYMENT_BY_ID($paymentId: uuid!) {
payments_by_pk(id: $paymentId) {
id
amount
created_at
exportedat
date
jobid
job {
id
ownerid
ownr_co_nm
ownr_fn
ownr_ln
owner {
id
ownr_co_nm
ownr_fn
ownr_ln
}
ro_number
}
memo
payer
paymentnum
stripeid
transactionid
type
}
}
`;

View File

@@ -19,7 +19,6 @@ export const QUERY_SCOREBOARD = gql`
v_make_desc
v_model_desc
v_model_yr
job_totals
}
}
}

View File

@@ -2,20 +2,22 @@ import { gql } from "@apollo/client";
export const GLOBAL_SEARCH_QUERY = gql`
query GLOBAL_SEARCH_QUERY($search: String) {
search_jobs(args: { search: $search }, limit: 25) {
search_jobs(args: { search: $search }) {
id
ro_number
status
clm_no
v_model_yr
v_model_desc
v_make_desc
v_color
ownr_fn
ownr_ln
ownr_co_nm
}
search_owners(args: { search: $search }, limit: 25) {
search_owners(args: { search: $search }) {
id
ownr_fn
ownr_ln
@@ -23,7 +25,7 @@ export const GLOBAL_SEARCH_QUERY = gql`
ownr_ph1
ownr_ph2
}
search_vehicles(args: { search: $search }, limit: 25) {
search_vehicles(args: { search: $search }) {
id
v_model_yr
v_model_desc
@@ -32,7 +34,7 @@ export const GLOBAL_SEARCH_QUERY = gql`
v_vin
plate_no
}
search_payments(args: { search: $search }, limit: 25) {
search_payments(args: { search: $search }) {
id
amount
paymentnum
@@ -43,7 +45,7 @@ export const GLOBAL_SEARCH_QUERY = gql`
memo
transactionid
}
search_bills(args: { search: $search }, limit: 25) {
search_bills(args: { search: $search }) {
id
date
invoice_number
@@ -52,7 +54,7 @@ export const GLOBAL_SEARCH_QUERY = gql`
name
}
}
search_phonebook(args: { search: $search }, limit: 25) {
search_phonebook(args: { search: $search }) {
id
firstname
lastname

View File

@@ -59,81 +59,6 @@ export const QUERY_TIME_TICKETS_IN_RANGE = gql`
}
`;
export const QUERY_TIME_TICKETS_TECHNICIAN_IN_RANGE = gql`
query QUERY_TIME_TICKETS_TECHNICIAN_IN_RANGE(
$employeeid: uuid!
$start: date!
$end: date!
$fixedStart: date!
$fixedEnd: date!
) {
timetickets(
where: {
date: { _gte: $start, _lte: $end }
employeeid: { _eq: $employeeid }
}
order_by: { date: desc_nulls_first }
) {
actualhrs
ciecacode
clockoff
clockon
cost_center
created_at
date
id
rate
productivehrs
memo
jobid
flat_rate
job {
id
ro_number
}
employeeid
employee {
id
employee_number
first_name
last_name
}
}
fixedperiod: timetickets(
where: {
date: { _gte: $fixedStart, _lte: $fixedEnd }
employeeid: { _eq: $employeeid }
}
order_by: { date: desc_nulls_first }
) {
actualhrs
ciecacode
clockoff
clockon
cost_center
created_at
date
id
rate
productivehrs
memo
jobid
flat_rate
job {
id
ro_number
}
employeeid
employee {
id
employee_number
first_name
last_name
}
}
}
`;
export const QUERY_TIME_TICKETS_IN_RANGE_SB = gql`
query QUERY_TIME_TICKETS_IN_RANGE_SB(
$start: date!

View File

@@ -1,8 +1,7 @@
import { EditFilled, SyncOutlined } from "@ant-design/icons";
import { SyncOutlined, EditFilled } from "@ant-design/icons";
import { Button, Card, Checkbox, Input, Space, Table, Typography } from "antd";
import axios from "axios";
import queryString from "query-string";
import React, { useEffect, useState } from "react";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link, useHistory, useLocation } from "react-router-dom";
@@ -12,8 +11,8 @@ import PrintWrapperComponent from "../../components/print-wrapper/print-wrapper.
import { setModalContext } from "../../redux/modals/modals.actions";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter";
import { TemplateList } from "../../utils/TemplateConstants";
import { alphaSort, dateSort } from "../../utils/sorters";
import { TemplateList } from "../../utils/TemplateConstants";
const mapDispatchToProps = (dispatch) => ({
setPartsOrderContext: (context) =>
@@ -30,36 +29,34 @@ export function BillsListPage({
setPartsOrderContext,
setBillEnterContext,
}) {
const search = queryString.parse(useLocation().search);
const [openSearchResults, setOpenSearchResults] = useState([]);
const [searchLoading, setSearchLoading] = useState(false);
const { page } = search;
const history = useHistory();
const { t } = useTranslation();
const [state, setState] = useState({
sortedInfo: {},
filteredInfo: { text: "" },
});
const history = useHistory();
const search = queryString.parse(useLocation().search);
const { page } = search;
const Templates = TemplateList("bill");
const { t } = useTranslation();
const columns = [
{
title: t("bills.fields.vendorname"),
dataIndex: "vendorname",
key: "vendorname",
// sortObject: (direction) => {
// return {
// vendor: {
// name: direction
// ? direction === "descend"
// ? "desc"
// : "asc"
// : "desc",
// },
// };
// },
// sorter: (a, b) => alphaSort(a.vendor.name, b.vendor.name),
// sortOrder:
// state.sortedInfo.columnKey === "vendorname" && state.sortedInfo.order,
sortObject: (direction) => {
return {
vendor: {
name: direction
? direction === "descend"
? "desc"
: "asc"
: "desc",
},
};
},
sorter: (a, b) => alphaSort(a.vendor.name, b.vendor.name),
sortOrder:
state.sortedInfo.columnKey === "vendorname" && state.sortedInfo.order,
render: (text, record) => <span>{record.vendor.name}</span>,
},
{
@@ -75,20 +72,20 @@ export function BillsListPage({
title: t("jobs.fields.ro_number"),
dataIndex: "ro_number",
key: "ro_number",
// sortObject: (direction) => {
// return {
// job: {
// ro_number: direction
// ? direction === "descend"
// ? "desc"
// : "asc"
// : "desc",
// },
// };
// },
// sorter: (a, b) => alphaSort(a.job.ro_number, b.job.ro_number),
// sortOrder:
// state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
sortObject: (direction) => {
return {
job: {
ro_number: direction
? direction === "descend"
? "desc"
: "asc"
: "desc",
},
};
},
sorter: (a, b) => alphaSort(a.job.ro_number, b.job.ro_number),
sortOrder:
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
render: (text, record) =>
record.job && (
<Link to={`/manage/jobs/${record.job.id}`}>
@@ -177,15 +174,7 @@ export function BillsListPage({
// {t("bills.actions.return")}
// </Button>
}
<BillDeleteButton
bill={record}
callback={(deletedBillid) => {
//Filter out the state and set it again.
setOpenSearchResults((currentResults) =>
currentResults.filter((bill) => bill.id !== deletedBillid)
);
}}
/>
<BillDeleteButton bill={record} />
{record.isinhouse && (
<PrintWrapperComponent
templateObject={{
@@ -210,32 +199,11 @@ export function BillsListPage({
search.sortcolumn = sorter.order ? sorter.columnKey : null;
search.sortorder = sorter.order;
}
search.sort = JSON.stringify({ [sorter.columnKey]: sorter.order });
history.push({ search: queryString.stringify(search) });
};
useEffect(() => {
if (search.search && search.search.trim() !== "") {
searchBills();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
async function searchBills(value) {
try {
setSearchLoading(true);
const searchData = await axios.post("/search", {
search: value || search.search,
index: "bills",
});
setOpenSearchResults(searchData.data.hits.hits.map((s) => s._source));
} catch (error) {
console.log("Error while fetching search results", error);
} finally {
setSearchLoading(false);
}
}
return (
<Card
title={t("bills.labels.bills")}
@@ -249,7 +217,6 @@ export function BillsListPage({
<Button
onClick={() => {
delete search.search;
delete search.page;
history.push({ search: queryString.stringify(search) });
}}
>
@@ -276,10 +243,7 @@ export function BillsListPage({
onSearch={(value) => {
search.search = value;
history.push({ search: queryString.stringify(search) });
searchBills(value);
}}
loading={loading || searchLoading}
enterButton
/>
</Space>
}
@@ -287,27 +251,19 @@ export function BillsListPage({
<PartsOrderModalContainer />
<Table
loading={loading || searchLoading}
// scroll={{
// x: "50%", // y: "40rem"
// }}
scroll={{ x: true }}
pagination={
search?.search
? {
pageSize: 25,
showSizeChanger: false,
}
: {
pageSize: 25,
current: parseInt(page || 1),
total: total,
showSizeChanger: false,
}
}
loading={loading}
scroll={{
x: "50%", // y: "40rem"
}}
pagination={{
position: "top",
pageSize: 25,
current: parseInt(page || 1),
total: total,
}}
columns={columns}
rowKey="id"
dataSource={search?.search ? openSearchResults : data}
dataSource={data}
onChange={handleTableChange}
/>
</Card>

View File

@@ -1,6 +1,6 @@
import { useQuery } from "@apollo/client";
import queryString from "query-string";
import React, { useEffect } from "react";
import { useQuery } from "@apollo/client";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useLocation } from "react-router-dom";
@@ -22,7 +22,7 @@ const mapDispatchToProps = (dispatch) => ({
export function BillsPageContainer({ setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation();
const searchParams = queryString.parse(useLocation().search);
const { page, sortcolumn, sortorder, searchObj } = searchParams;
const { page, sortcolumn, sortorder, search, searchObj } = searchParams;
useEffect(() => {
document.title = t("titles.bills-list");
@@ -38,6 +38,7 @@ export function BillsPageContainer({ setBreadcrumbs, setSelectedHeader }) {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
variables: {
search: search || "",
offset: page ? (page - 1) * 25 : 0,
limit: 25,
order: [
@@ -60,10 +61,10 @@ export function BillsPageContainer({ setBreadcrumbs, setSelectedHeader }) {
<RbacWrapper action="bills:list">
<div>
<BillsPageComponent
data={data ? data.bills : []}
data={data ? data.search_bills : []}
loading={loading}
refetch={refetch}
total={data ? data.bills_aggregate.aggregate.count : 0}
total={data ? data.search_bills_aggregate.aggregate.count : 0}
/>
<BillDetailEditContainer />

View File

@@ -25,7 +25,7 @@ const mapDispatchToProps = (dispatch) => ({
export function AllJobs({ setBreadcrumbs, setSelectedHeader }) {
const searchParams = queryString.parse(useLocation().search);
const { page, sortcolumn, sortorder, statusFilters } = searchParams;
const { page, sortcolumn, sortorder, search, statusFilters } = searchParams;
const { loading, error, data, refetch } = useQuery(
QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED,
@@ -33,12 +33,13 @@ export function AllJobs({ setBreadcrumbs, setSelectedHeader }) {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
variables: {
search: search || "",
offset: page ? (page - 1) * 25 : 0,
limit: 25,
...(statusFilters ? { statusList: JSON.parse(statusFilters) } : {}),
order: [
{
[sortcolumn || "ro_number"]:
[sortcolumn || "created_at"]:
sortorder && sortorder !== "false"
? sortorder === "descend"
? "desc"
@@ -66,8 +67,8 @@ export function AllJobs({ setBreadcrumbs, setSelectedHeader }) {
refetch={refetch}
loading={loading}
searchParams={searchParams}
total={data ? data.jobs_aggregate.aggregate.count : 0}
jobs={data ? data.jobs : []}
total={data ? data.search_jobs_aggregate.aggregate.count : 0}
jobs={data ? data.search_jobs : []}
/>
</RbacWrapper>
);

View File

@@ -52,7 +52,6 @@ import AuditTrailMapping from "../../utils/AuditTrailMappings";
import { insertAuditTrail } from "../../redux/application/application.actions";
import JobsDocumentsLocalGallery from "../../components/jobs-documents-local-gallery/jobs-documents-local-gallery.container";
import UndefinedToNull from "../../utils/undefinedtonull";
import NoteUpsertModalComponent from "../../components/note-upsert-modal/note-upsert-modal.container";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -98,11 +97,7 @@ export function JobsDetailPage({
variables: {
jobId: job.id,
job: {
...UndefinedToNull(values, [
"alt_transport",
"category",
"referral_source",
]),
...UndefinedToNull(values, ["alt_transport", "category", "referral_source"]),
parts_tax_rates: {
...job.parts_tax_rates,
...values.parts_tax_rates,
@@ -236,7 +231,6 @@ export function JobsDetailPage({
<ScheduleJobModalContainer />
<JobReconciliationModal />
<JobLineUpsertModalContainer />
<NoteUpsertModalComponent />
<Form
form={form}
name="JobDetailForm"

View File

@@ -25,7 +25,6 @@ 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")
@@ -395,7 +394,6 @@ export function Manage({ match, conflict, bodyshop }) {
<>
<ChatAffixContainer />
<Layout className="layout-container">
<UpdateAlert />
<HeaderContainer />
<Content className="content-container">

View File

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

View File

@@ -26,7 +26,7 @@ const mapDispatchToProps = (dispatch) => ({
export function AllJobs({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
const searchParams = queryString.parse(useLocation().search);
const { page, sortcolumn, sortorder, searchObj } = searchParams;
const { page, sortcolumn, sortorder, search } = searchParams;
const { loading, error, data, refetch } = useQuery(
QUERY_ALL_PAYMENTS_PAGINATED,
@@ -34,12 +34,11 @@ export function AllJobs({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
variables: {
search: search || "",
offset: page ? (page - 1) * 25 : 0,
limit: 25,
order: [
searchObj
? JSON.parse(searchObj)
: {
{
[sortcolumn || "date"]: sortorder
? sortorder === "descend"
? "desc"
@@ -67,8 +66,8 @@ export function AllJobs({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
refetch={refetch}
loading={loading}
searchParams={searchParams}
total={data ? data.payments_aggregate.aggregate.count : 0}
payments={data ? data.payments : []}
total={data ? data.search_payments_aggregate.aggregate.count : 0}
payments={data ? data.search_payments : []}
/>
</RbacWrapper>
);

View File

@@ -1,7 +1,5 @@
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";
@@ -26,9 +24,6 @@ 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");
@@ -40,16 +35,9 @@ 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
defaultActiveKey={search.tab}
onChange={(key) => history.push({ search: `?tab=${key}` })}
>
<Tabs>
<Tabs.TabPane tab={t("bodyshop.labels.shopinfo")} key="info">
<ShopInfoContainer />
</Tabs.TabPane>

View File

@@ -2,12 +2,10 @@ import { Divider } from "antd";
import React from "react";
import TechClockInFormContainer from "../../components/tech-job-clock-in-form/tech-job-clock-in-form.container";
import TechClockedInList from "../../components/tech-job-clocked-in-list/tech-job-clocked-in-list.component";
import TechJobStatistics from "../../components/tech-job-statistics/tech-job-statistics.component";
export default function TechClockComponent() {
return (
<div>
<TechJobStatistics />
<TechClockInFormContainer />
<Divider />
<TechClockedInList />

View File

@@ -12,7 +12,6 @@ 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")
);
@@ -57,9 +56,7 @@ export function TechPage({ technician, match }) {
<TechSider />
<Layout>
{technician ? null : <Redirect to={`${match.path}/login`} />}
<UpdateAlert />
<TechHeader />
<Content className="tech-content-container">
<ErrorBoundary>
<Suspense

View File

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

View File

@@ -1,10 +1,9 @@
.tech-content-container {
overflow-y: visible;
overflow-y: auto;
padding: 1rem;
background: #fff;
}
.tech-layout-container {
position: relative;
min-height: 100vh;
height: 100vh;
}

View File

@@ -62,8 +62,3 @@ export const setProblemJobs = (problemJobs) => ({
type: ApplicationActionTypes.SET_PROBLEM_JOBS,
payload: problemJobs,
});
export const setUpdateAvailable = (isUpdateAvailable) => ({
type: ApplicationActionTypes.SET_UPDATE_AVAILABLE,
payload: isUpdateAvailable,
});

View File

@@ -3,7 +3,6 @@ import ApplicationActionTypes from "./application.types";
const INITIAL_STATE = {
loading: false,
online: true,
updateAvailable: false,
breadcrumbs: [],
recentItems: [],
selectedHeader: "home",
@@ -19,11 +18,6 @@ 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,

View File

@@ -48,7 +48,3 @@ export const selectProblemJobs = createSelector(
[selectApplication],
(application) => application.problemJobs
);
export const selectUpdateAvailable = createSelector(
[selectApplication],
(application) => application.updateAvailable
);

View File

@@ -12,6 +12,5 @@ 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;

View File

@@ -49,7 +49,7 @@
"blocked": "Blocked",
"cancelledappointment": "Canceled appointment for: ",
"completingjobs": "Completing Jobs",
"dataconsistency": "<0>{{ro_number}}</0> has a data consistency issue. It may have been excluded for scheduling purposes. CODE: {{code}}.",
"dataconsistency": "{{ro_number}} 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",
@@ -63,7 +63,6 @@
"scheduledfor": "Scheduled appointment for: ",
"severalerrorsfound": "Several jobs have issues which may prevent accurate smart scheduling. Click to expand.",
"smartscheduling": "Smart Scheduling",
"smspaymentreminder": "",
"suggesteddates": "Suggested Dates"
},
"successes": {
@@ -104,7 +103,6 @@
"admin_jobunvoid": "ADMIN: Job has been unvoided.",
"billposted": "Bill with invoice number {{invoice_number}} posted.",
"billupdated": "Bill with invoice number {{invoice_number}} updated.",
"failedpayment": "",
"jobassignmentchange": "Employee {{name}} assigned to {{operation}}",
"jobassignmentremoved": "Employee assignment removed for {{operation}}",
"jobchecklist": "Checklist type \"{{type}}\" completed. In production set to {{inproduction}}. Status set to {{status}}.",
@@ -229,7 +227,6 @@
},
"bodyshop": {
"actions": {
"add_task_preset": "",
"addapptcolor": "Add Appointment Color",
"addbucket": "Add Definition",
"addpartslocation": "Add Parts Location",
@@ -342,12 +339,6 @@
},
"md_payment_types": "Payment Types",
"md_referral_sources": "Referral Sources",
"md_tasks_presets": {
"hourstype": "",
"memo": "",
"name": "",
"percent": ""
},
"messaginglabel": "Messaging Preset Label",
"messagingtext": "Messaging Preset Text",
"noteslabel": "Note Label",
@@ -383,9 +374,6 @@
"export": "CSI -> Export",
"page": "CSI -> Page"
},
"employee_teams": {
"page": ""
},
"employees": {
"page": "Employees -> List"
},
@@ -405,8 +393,7 @@
"list-active": "Jobs -> List Active",
"list-all": "Jobs -> List All",
"list-ready": "Jobs -> List Ready",
"partsqueue": "Jobs -> Parts Queue",
"void": "Jobs -> Void"
"partsqueue": "Jobs -> Parts Queue"
},
"owners": {
"detail": "Owners -> Detail",
@@ -445,15 +432,10 @@
},
"timetickets": {
"edit": "Time Tickets -> Edit",
"editcommitted": "",
"enter": "Time Tickets -> Enter",
"list": "Time Tickets -> List",
"shiftedit": "Time Tickets -> Shift Edit"
},
"ttapprovals": {
"approve": "",
"view": ""
},
"users": {
"editaccess": "Users -> Edit access"
}
@@ -471,8 +453,6 @@
"federal_tax": "Federal Tax",
"federal_tax_itc": "Federal Tax Credit",
"gst_override": "GST Override Account #",
"invoiceexemptcode": "",
"itemexemptcode": "",
"la1": "LA1",
"la2": "LA2",
"la3": "LA3",
@@ -524,7 +504,6 @@
"dailyhrslimit": "Daily Incoming Hours Limit"
},
"ssbuckets": {
"color": "Job Color",
"gte": "Greater Than/Equal to (hrs)",
"id": "ID",
"label": "Label",
@@ -562,9 +541,7 @@
"target_touchtime": "Target Touch Time",
"timezone": "Timezone",
"tt_allow_post_to_invoiced": "Allow Time Tickets to be posted to Invoiced & Exported Jobs",
"tt_enforce_hours_for_tech_console": "Restrict Claimable hours from Tech Console",
"use_fippa": "Use FIPPA for Names on Generated Documents?",
"use_paint_scale_data": "Use Paint Scale Data for Job Costing?",
"uselocalmediaserver": "Use Local Media Server?",
"website": "Website",
"zip_post": "Zip/Postal Code"
@@ -595,7 +572,6 @@
"title": "DMS"
},
"emaillater": "Email Later",
"employee_teams": "",
"employees": "Employees",
"estimators": "Estimators",
"filehandlers": "File Handlers",
@@ -604,7 +580,6 @@
"jobstatuses": "Job Statuses",
"laborrates": "Labor Rates",
"licensing": "Licensing",
"md_tasks_presets": "",
"md_to_emails": "Preset To Emails",
"md_to_emails_emails": "Emails",
"messagingpresets": "Messaging Presets",
@@ -631,7 +606,6 @@
"speedprint": "Speed Print Configuration",
"ssbuckets": "Job Size Definitions",
"systemsettings": "System Settings",
"task-presets": "",
"workingdays": "Working Days"
},
"successes": {
@@ -858,9 +832,7 @@
"prodhrssummary": "Production Hours Summary",
"productiondollars": "Total dollars in Production",
"productionhours": "Total hours in Production",
"projectedmonthlysales": "Projected Monthly Sales",
"scheduledintoday": "Sheduled In Today: {{date}}",
"scheduledouttoday": "Sheduled Out Today: {{date}}"
"projectedmonthlysales": "Projected Monthly Sales"
}
},
"dms": {
@@ -933,18 +905,6 @@
"sent": "Email sent successfully."
}
},
"employee_teams": {
"actions": {
"new": "",
"newmember": ""
},
"fields": {
"active": "",
"employeeid": "",
"name": "",
"percentage": ""
}
},
"employees": {
"actions": {
"addvacation": "Add Vacation",
@@ -1054,7 +1014,6 @@
"created_at": "Created At",
"email": "Email",
"errors": "Errors",
"excel": "Excel",
"exceptiontitle": "An error has occurred.",
"friday": "Friday",
"globalsearch": "Global Search",
@@ -1104,7 +1063,6 @@
"sunday": "Sunday",
"text": "Text",
"thursday": "Thursday",
"total": "Total",
"totals": "Totals",
"tuesday": "Tuesday",
"unknown": "Unknown",
@@ -1120,7 +1078,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 to update to the latest available version of ImEX Online. Please make sure all other tabs and windows are closed.",
"newversionmessage": "Click refresh below 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.",
@@ -1184,30 +1142,6 @@
"updated": "Inventory line updated."
}
},
"job_payments": {
"buttons": {
"goback": "",
"proceedtopayment": "",
"refundpayment": ""
},
"notifications": {
"error": {
"description": "",
"title": ""
}
},
"titles": {
"amount": "",
"dateOfPayment": "",
"descriptions": "",
"payer": "",
"payername": "",
"paymentid": "",
"paymenttype": "",
"refundamount": "",
"transactionid": ""
}
},
"joblines": {
"actions": {
"converttolabor": "Convert amount to Labor.",
@@ -1440,7 +1374,7 @@
"ded_amt": "Deductible",
"ded_note": "Deductible Note",
"ded_status": "Deductible Status",
"depreciation_taxes": "Betterment/Depreciation/Taxes",
"depreciation_taxes": "Depreciation/Taxes",
"dms": {
"address": "Customer Address",
"amount": "Amount",
@@ -1640,7 +1574,6 @@
"scheddates": "Schedule Dates"
},
"labels": {
"act_price_ppc": "",
"actual_completion_inferred": "$t(jobs.fields.actual_completion) inferred using $t(jobs.fields.scheduled_completion).",
"actual_delivery_inferred": "$t(jobs.fields.actual_delivery) inferred using $t(jobs.fields.scheduled_delivery).",
"actual_in_inferred": "$t(jobs.fields.actual_in) inferred using $t(jobs.fields.scheduled_in).",
@@ -1777,7 +1710,6 @@
"partstotal": "This is the total of all parts and sublet amounts on the vehicle (some of these may require an in-house invoice).<br/>\nItems such as shop and paint materials, labor online lines, etc. are not included in this total.",
"totalreturns": "The total <b>retail</b> amount of returns created for this job."
},
"ppc": "",
"profileadjustments": "",
"prt_dsmk_total": "Line Item Adjustment",
"rates": "Rates",
@@ -1918,7 +1850,6 @@
"customers": "Customers",
"dashboard": "Dashboard",
"enterbills": "Enter Bills",
"entercardpayment": "",
"enterpayment": "Enter Payments",
"entertimeticket": "Enter Time Tickets",
"export": "Export",
@@ -1930,7 +1861,6 @@
"newjob": "Create New Job",
"owners": "Owners",
"parts-queue": "Parts Queue",
"paymentremindersms": "",
"phonebook": "Phonebook",
"productionboard": "Production Board - Visual",
"productionlist": "Production Board - List",
@@ -1956,7 +1886,6 @@
"shop_vendors": "Vendors",
"temporarydocs": "Temporary Documents",
"timetickets": "Time Tickets",
"ttapprovals": "",
"vehicles": "Vehicles"
},
"jobsactions": {
@@ -2029,8 +1958,7 @@
"actions": "Actions",
"deletenote": "Delete Note",
"edit": "Edit Note",
"new": "New Note",
"savetojobnotes": "Save to Job Notes"
"new": "New Note"
},
"errors": {
"inserting": "Error inserting note. {{error}}"
@@ -2040,16 +1968,6 @@
"critical": "Critical",
"private": "Private",
"text": "Contents",
"type": "Type",
"types": {
"customer": "Customer",
"general": "General",
"office": "Office",
"paint": "Paint",
"parts": "Parts",
"shop": "Shop",
"supplement": "Supplement"
},
"updatedat": "Updated At"
},
"labels": {
@@ -2220,13 +2138,10 @@
"new": "New Payment",
"signup": "Please contact support to sign up for electronic payments.",
"title": "Payments",
"totalpayments": "Total Payments",
"markexported": "Mark Exported",
"markforreexport": "Mark for Re-export"
"totalpayments": "Total Payments"
},
"successes": {
"exported": "Payment(s) exported successfully.",
"markreexported": "Payment marked for re-export successfully",
"markexported": "Payment(s) marked exported.",
"payment": "Payment created successfully. ",
"stripe": "Credit card transaction charged successfully."
@@ -2309,7 +2224,6 @@
"csi_invitation": "CSI Invitation",
"csi_invitation_action": "CSI Invite",
"diagnostic_authorization": "Diagnostic Authorization",
"dms_posting_sheet": "DMS Posting Sheet",
"envelope_return_address": "#10 Envelope Return Address Label",
"estimate": "Estimate Only",
"estimate_detail": "Estimate Details",
@@ -2322,7 +2236,7 @@
"folder_label_multiple": "Folder Label - Multi",
"glass_express_checklist": "Glass Express Checklist",
"guarantee": "Repair Guarantee",
"individual_job_note": "RO Job Note",
"individual_job_note": "Job Note RO # {{ro_number}}",
"invoice_customer_payable": "Invoice (Customer Payable)",
"invoice_total_payable": "Invoice (Total Payable)",
"iou_form": "IOU Form",
@@ -2338,8 +2252,7 @@
"mechanical_authorization": "Mechanical Authorization",
"mpi_animal_checklist": "MPI - Animal Checklist",
"mpi_eglass_auth": "MPI - eGlass Auth",
"mpi_final_acct_sheet": "MPI - Final Accounting Sheet (Direct Repair)",
"mpi_final_repair_acct_sheet": "MPI - Final Accounting Sheet",
"mpi_final_acct_sheet": "MPI - Final Accounting Sheet",
"paint_grid": "Paint Grid",
"parts_invoice_label_single": "Parts Label Single",
"parts_label_multiple": "Parts Label - Multi",
@@ -2401,7 +2314,6 @@
},
"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}}"
}
@@ -2445,7 +2357,6 @@
"qbo_usa": "QBO USA"
}
},
"cardcolor": "Card Colors",
"cardsettings": "Card Settings",
"clm_no": "Claim Number",
"comment": "Comment",
@@ -2456,7 +2367,6 @@
"ins_co_nm": "Insurance Company Name",
"jobdetail": "Job Details",
"laborhrs": "Labor Hours",
"legend": "Legend:",
"note": "Production Note",
"ownr_nm": "Owner Name",
"paintpriority": "P/P",
@@ -2532,8 +2442,6 @@
"export_payables": "Export Log - Payables",
"export_payments": "Export Log - Payments",
"export_receivables": "Export Log - Receivables",
"exported_gsr_by_ro": "Exported Gross Sales - Excel",
"exported_gsr_by_ro_labor": "Exported Gross Sales (Labor) - Excel",
"gsr_by_atp": "",
"gsr_by_ats": "Gross Sales by ATS",
"gsr_by_category": "Gross Sales by Category",
@@ -2627,7 +2535,6 @@
"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"
@@ -2696,7 +2603,6 @@
},
"timetickets": {
"actions": {
"claimtasks": "",
"clockin": "Clock In",
"clockout": "Clock Out",
"enter": "Enter New Time Ticket",
@@ -2717,12 +2623,10 @@
"clockhours": "Clock Hours",
"clockoff": "Clock Off",
"clockon": "Clocked In",
"committed": "",
"cost_center": "Cost Center",
"date": "Ticket Date",
"efficiency": "Efficiency",
"employee": "Employee",
"employee_team": "",
"flat_rate": "Flat Rate?",
"memo": "Memo",
"productivehrs": "Productive Hours",
@@ -2757,8 +2661,7 @@
},
"validation": {
"clockoffmustbeafterclockon": "Clock off time must be the same or after clock in time.",
"clockoffwithoutclockon": "Clock off time cannot be set without a clock in time.",
"hoursenteredmorethanavailable": "The number of hours entered is more than what is available for this cost center."
"clockoffwithoutclockon": "Clock off time cannot be set without a clock in time."
}
},
"titles": {
@@ -2809,7 +2712,6 @@
"shop-vendors": "Vendors",
"temporarydocs": "Temporary Documents",
"timetickets": "Time Tickets",
"ttapprovals": "",
"vehicle-details": "Vehicle: {{vehicle}}",
"vehicles": "Vehicles"
},
@@ -2855,15 +2757,9 @@
"shop_vendors": "Vendors | $t(titles.app)",
"temporarydocs": "Temporary Documents | $t(titles.app)",
"timetickets": "Time Tickets | $t(titles.app)",
"ttapprovals": "",
"vehicledetail": "Vehicle Details {{vehicle}} | $t(titles.app)",
"vehicles": "All Vehicles | $t(titles.app)"
},
"tt_approvals": {
"actions": {
"approveselected": ""
}
},
"user": {
"actions": {
"changepassword": "Change Password",

Some files were not shown because too many files have changed in this diff Show More