Merge branch 'master' into feature/payroll
This commit is contained in:
@@ -145,7 +145,11 @@
|
||||
|
||||
//Update row highlighting on production board.
|
||||
.ant-table-tbody > tr.ant-table-row:hover > td {
|
||||
background: #eaeaea !important;
|
||||
background: #e7f3ff !important;
|
||||
}
|
||||
|
||||
.ant-table-tbody > tr.ant-table-row-selected > td {
|
||||
background: #e6f7ff !important;
|
||||
}
|
||||
|
||||
.job-line-manual {
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
import { Badge, List, Tag } from "antd";
|
||||
import React, { useEffect } from "react";
|
||||
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 { List as VirtualizedList, AutoSizer } from "react-virtualized";
|
||||
|
||||
import "./chat-conversation-list.styles.scss";
|
||||
|
||||
@@ -24,57 +29,66 @@ function ChatConversationListComponent({
|
||||
conversationList,
|
||||
selectedConversation,
|
||||
setSelectedConversation,
|
||||
subscribeToMoreConversations,
|
||||
loadMoreConversations,
|
||||
}) {
|
||||
useEffect(
|
||||
() => subscribeToMoreConversations(),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[]
|
||||
);
|
||||
const cache = new CellMeasurerCache({
|
||||
fixedWidth: true,
|
||||
defaultHeight: 60,
|
||||
});
|
||||
|
||||
const rowRenderer = ({ index, key, style }) => {
|
||||
const rowRenderer = ({ index, key, style, parent }) => {
|
||||
const item = conversationList[index];
|
||||
|
||||
return (
|
||||
<List.Item
|
||||
<CellMeasurer
|
||||
key={key}
|
||||
onClick={() => setSelectedConversation(item.id)}
|
||||
className={`chat-list-item ${
|
||||
item.id === selectedConversation
|
||||
? "chat-list-selected-conversation"
|
||||
: null
|
||||
}`}
|
||||
style={style}
|
||||
cache={cache}
|
||||
parent={parent}
|
||||
columnIndex={0}
|
||||
rowIndex={index}
|
||||
>
|
||||
<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}
|
||||
<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>
|
||||
<TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>
|
||||
</div>
|
||||
<Badge count={item.messages_aggregate.aggregate.count || 0} />
|
||||
</List.Item>
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -86,7 +100,7 @@ function ChatConversationListComponent({
|
||||
height={height}
|
||||
width={width}
|
||||
rowCount={conversationList.length}
|
||||
rowHeight={60}
|
||||
rowHeight={cache.rowHeight}
|
||||
rowRenderer={rowRenderer}
|
||||
onScroll={({ scrollTop, scrollHeight, clientHeight }) => {
|
||||
if (scrollTop + clientHeight === scrollHeight) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
ShrinkOutlined,
|
||||
SyncOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { useLazyQuery, useSubscription } from "@apollo/client";
|
||||
import { useLazyQuery, useQuery } from "@apollo/client";
|
||||
import { Badge, Card, Col, Row, Space, Tag, Tooltip, Typography } from "antd";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -12,8 +12,7 @@ import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import {
|
||||
CONVERSATION_LIST_QUERY,
|
||||
CONVERSATION_LIST_SUBSCRIPTION,
|
||||
UNREAD_CONVERSATION_COUNT_SUBSCRIPTION,
|
||||
UNREAD_CONVERSATION_COUNT,
|
||||
} from "../../graphql/conversations.queries";
|
||||
import { toggleChatVisible } from "../../redux/messaging/messaging.actions";
|
||||
import {
|
||||
@@ -42,19 +41,20 @@ export function ChatPopupComponent({
|
||||
const { t } = useTranslation();
|
||||
const [pollInterval, setpollInterval] = useState(0);
|
||||
|
||||
const { data: unreadData } = useSubscription(
|
||||
UNREAD_CONVERSATION_COUNT_SUBSCRIPTION
|
||||
);
|
||||
|
||||
const [
|
||||
getConversations,
|
||||
{ loading, data, called, refetch, fetchMore, subscribeToMore },
|
||||
] = useLazyQuery(CONVERSATION_LIST_QUERY, {
|
||||
const { data: unreadData } = useQuery(UNREAD_CONVERSATION_COUNT, {
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
skip: !chatVisible,
|
||||
...(pollInterval > 0 ? { pollInterval } : {}),
|
||||
});
|
||||
|
||||
const [getConversations, { loading, data, refetch, fetchMore }] =
|
||||
useLazyQuery(CONVERSATION_LIST_QUERY, {
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
skip: !chatVisible,
|
||||
...(pollInterval > 0 ? { pollInterval } : {}),
|
||||
});
|
||||
|
||||
const fcmToken = sessionStorage.getItem("fcmtoken");
|
||||
|
||||
useEffect(() => {
|
||||
@@ -66,13 +66,13 @@ export function ChatPopupComponent({
|
||||
}, [fcmToken]);
|
||||
|
||||
useEffect(() => {
|
||||
if (called && chatVisible)
|
||||
if (chatVisible)
|
||||
getConversations({
|
||||
variables: {
|
||||
offset: 0,
|
||||
},
|
||||
});
|
||||
}, [chatVisible, called, getConversations]);
|
||||
}, [chatVisible, getConversations]);
|
||||
|
||||
const loadMoreConversations = useCallback(() => {
|
||||
if (data)
|
||||
@@ -119,43 +119,6 @@ export function ChatPopupComponent({
|
||||
<ChatConversationListComponent
|
||||
conversationList={data ? data.conversations : []}
|
||||
loadMoreConversations={loadMoreConversations}
|
||||
subscribeToMoreConversations={() =>
|
||||
subscribeToMore({
|
||||
document: CONVERSATION_LIST_SUBSCRIPTION,
|
||||
variables: { offset: 0 },
|
||||
updateQuery: (prev, { subscriptionData }) => {
|
||||
if (
|
||||
!subscriptionData.data ||
|
||||
subscriptionData.data.conversations.length === 0
|
||||
)
|
||||
return prev;
|
||||
|
||||
let conversations = [...prev.conversations];
|
||||
const newConversations =
|
||||
subscriptionData.data.conversations;
|
||||
|
||||
for (const conversation of newConversations) {
|
||||
const index = conversations.findIndex(
|
||||
(prevConversation) =>
|
||||
prevConversation.id === conversation.id
|
||||
);
|
||||
|
||||
if (index !== -1) {
|
||||
conversations.splice(index, 1);
|
||||
conversations.unshift(conversation);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
conversations.unshift(conversation);
|
||||
}
|
||||
|
||||
return Object.assign({}, prev, {
|
||||
conversations: conversations,
|
||||
});
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Col>
|
||||
|
||||
@@ -66,7 +66,7 @@ export function DmsAllocationsSummaryAp({ socket, bodyshop, billids, title }) {
|
||||
key: "status",
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ro_number"),
|
||||
title: t("bills.fields.invoice_number"),
|
||||
dataIndex: ["Posting", "Reference"],
|
||||
key: "reference",
|
||||
},
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
Select,
|
||||
Space,
|
||||
Statistic,
|
||||
Switch,
|
||||
Typography,
|
||||
} from "antd";
|
||||
import Dinero from "dinero.js";
|
||||
@@ -183,6 +184,13 @@ export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
|
||||
<Space>
|
||||
<DmsCdkMakes form={form} socket={socket} job={job} />
|
||||
<DmsCdkMakesRefetch />
|
||||
<Form.Item
|
||||
name="dms_unsold"
|
||||
label={t("jobs.fields.dms.dms_unsold")}
|
||||
initialValue={false}
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</Space>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -334,7 +334,9 @@ function Header({
|
||||
icon={<SettingOutlined />}
|
||||
>
|
||||
<Menu.Item key="shop" icon={<Icon component={GiSettingsKnobs} />}>
|
||||
<Link to="/manage/shop">{t("menus.header.shop_config")}</Link>
|
||||
<Link to="/manage/shop?tab=info">
|
||||
{t("menus.header.shop_config")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="dashboard" icon={<DashboardFilled />}>
|
||||
<Link to="/manage/dashboard">{t("menus.header.dashboard")}</Link>
|
||||
|
||||
@@ -53,7 +53,9 @@ export function ShopInfoComponent({ bodyshop, form, saveLoading }) {
|
||||
<Tabs
|
||||
defaultActiveKey={search.subtab}
|
||||
onChange={(key) =>
|
||||
history.push({ search: `?tab=${search.tab}&subtab=${key}` })
|
||||
history.push({
|
||||
search: `?tab=${search.tab}&subtab=${key}`,
|
||||
})
|
||||
}
|
||||
>
|
||||
<Tabs.TabPane key="general" tab={t("bodyshop.labels.shopinfo")}>
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import { Alert, Button, Col, Row, Space } from "antd";
|
||||
import i18n from "i18next";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectUpdateAvailable } from "../../redux/application/application.selectors";
|
||||
import { AlertOutlined } from "@ant-design/icons";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { setUpdateAvailable } from "../../redux/application/application.actions";
|
||||
import { store } from "../../redux/store";
|
||||
import * as serviceWorkerRegistration from "../../serviceWorkerRegistration";
|
||||
|
||||
let globalRegistration;
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
updateAvailable: selectUpdateAvailable,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(UpdateAlert);
|
||||
|
||||
export function UpdateAlert({ updateAvailable }) {
|
||||
const { t } = useTranslation();
|
||||
if (!updateAvailable) return null;
|
||||
return (
|
||||
<Alert
|
||||
message={t("general.messages.newversiontitle")}
|
||||
showIcon
|
||||
icon={<AlertOutlined />}
|
||||
description={
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col sm={24} md={16} lg={18}>
|
||||
{t("general.messages.newversionmessage")}
|
||||
</Col>
|
||||
<Col sm={24} md={8} lg={6}>
|
||||
<Space wrap>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
window.open("https://imex-online.noticeable.news/", "_blank");
|
||||
}}
|
||||
>
|
||||
{i18n.t("general.actions.viewreleasenotes")}
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={async () => {
|
||||
if (globalRegistration && globalRegistration.waiting) {
|
||||
await globalRegistration.unregister();
|
||||
// Makes Workbox call skipWaiting()
|
||||
globalRegistration.waiting.postMessage({
|
||||
type: "SKIP_WAITING",
|
||||
});
|
||||
// Once the service worker is unregistered, we can reload the page to let
|
||||
// the browser download a fresh copy of our app (invalidating the cache)
|
||||
window.location.reload();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{i18n.t("general.actions.refresh")}
|
||||
</Button>
|
||||
</Space>
|
||||
</Col>
|
||||
</Row>
|
||||
}
|
||||
closable={false}
|
||||
type="warning"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const onServiceWorkerUpdate = (registration) => {
|
||||
console.log("onServiceWorkerUpdate", registration);
|
||||
globalRegistration = registration;
|
||||
store.dispatch(setUpdateAvailable(true));
|
||||
};
|
||||
|
||||
serviceWorkerRegistration.register({ onUpdate: onServiceWorkerUpdate });
|
||||
@@ -1,37 +1,5 @@
|
||||
import { gql } from "@apollo/client";
|
||||
|
||||
export const CONVERSATION_LIST_SUBSCRIPTION = gql`
|
||||
subscription CONVERSATION_LIST_SUBSCRIPTION($offset: Int!) {
|
||||
conversations(
|
||||
order_by: { updated_at: desc }
|
||||
limit: 1
|
||||
offset: $offset
|
||||
where: { archived: { _eq: false } }
|
||||
) {
|
||||
phone_num
|
||||
id
|
||||
updated_at
|
||||
unreadcnt
|
||||
messages_aggregate(
|
||||
where: { read: { _eq: false }, isoutbound: { _eq: false } }
|
||||
) {
|
||||
aggregate {
|
||||
count
|
||||
}
|
||||
}
|
||||
job_conversations {
|
||||
job {
|
||||
id
|
||||
ro_number
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ownr_co_nm
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const UNREAD_CONVERSATION_COUNT = gql`
|
||||
query UNREAD_CONVERSATION_COUNT {
|
||||
messages_aggregate(
|
||||
@@ -44,18 +12,6 @@ export const UNREAD_CONVERSATION_COUNT = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
export const UNREAD_CONVERSATION_COUNT_SUBSCRIPTION = gql`
|
||||
subscription UNREAD_CONVERSATION_COUNT_SUBSCRIPTION {
|
||||
messages_aggregate(
|
||||
where: { read: { _eq: false }, isoutbound: { _eq: false } }
|
||||
) {
|
||||
aggregate {
|
||||
count
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const CONVERSATION_LIST_QUERY = gql`
|
||||
query CONVERSATION_LIST_QUERY($offset: Int!) {
|
||||
conversations(
|
||||
|
||||
@@ -1250,10 +1250,10 @@ export const ACTIVE_JOBS_FOR_AUTOCOMPLETE = gql`
|
||||
query ACTIVE_JOBS_FOR_AUTOCOMPLETE($statuses: [String!]!) {
|
||||
jobs(where: { status: { _in: $statuses } }) {
|
||||
id
|
||||
ownr_co_nm
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ro_number
|
||||
|
||||
vehicleid
|
||||
v_make_desc
|
||||
v_model_desc
|
||||
@@ -1281,6 +1281,7 @@ export const SEARCH_JOBS_FOR_AUTOCOMPLETE = gql`
|
||||
}
|
||||
) {
|
||||
id
|
||||
ownr_co_nm
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ro_number
|
||||
@@ -1297,6 +1298,7 @@ export const SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE = gql`
|
||||
query SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE($id: uuid!) {
|
||||
jobs_by_pk(id: $id) {
|
||||
id
|
||||
ownr_co_nm
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ro_number
|
||||
@@ -1315,6 +1317,7 @@ export const SEARCH_FOR_JOBS = gql`
|
||||
search_jobs(args: { search: $search }, limit: 25) {
|
||||
id
|
||||
ro_number
|
||||
ownr_co_nm
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import queryString from "query-string";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { useHistory, useLocation, Link } from "react-router-dom";
|
||||
import { Link, useHistory, useLocation } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import SocketIO from "socket.io-client";
|
||||
import AlertComponent from "../../components/alert/alert.component";
|
||||
@@ -22,6 +22,7 @@ import DmsCustomerSelector from "../../components/dms-customer-selector/dms-cust
|
||||
import DmsLogEvents from "../../components/dms-log-events/dms-log-events.component";
|
||||
import DmsPostForm from "../../components/dms-post-form/dms-post-form.component";
|
||||
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
|
||||
import { OwnerNameDisplayFunction } from "../../components/owner-name-display/owner-name-display.component";
|
||||
import { auth } from "../../firebase/firebase.utils";
|
||||
import { QUERY_JOB_EXPORT_DMS } from "../../graphql/jobs.queries";
|
||||
import {
|
||||
@@ -29,7 +30,6 @@ import {
|
||||
setSelectedHeader,
|
||||
} from "../../redux/application/application.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { OwnerNameDisplayFunction } from "../../components/owner-name-display/owner-name-display.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -46,6 +46,7 @@ export const socket = SocketIO(
|
||||
process.env.NODE_ENV === "production"
|
||||
? process.env.REACT_APP_AXIOS_BASE_API_URL
|
||||
: window.location.origin,
|
||||
// "http://localhost:4000", // for dev testing,
|
||||
{
|
||||
path: "/ws",
|
||||
withCredentials: true,
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
import * as Sentry from "@sentry/react";
|
||||
|
||||
import "./manage.page.styles.scss";
|
||||
import UpdateAlert from "../../components/update-alert/update-alert.component";
|
||||
|
||||
const ManageRootPage = lazy(() =>
|
||||
import("../manage-root/manage-root.page.container")
|
||||
@@ -410,6 +411,7 @@ export function Manage({ match, conflict, bodyshop }) {
|
||||
<>
|
||||
<ChatAffixContainer />
|
||||
<Layout className="layout-container">
|
||||
<UpdateAlert />
|
||||
<HeaderContainer />
|
||||
|
||||
<Content className="content-container">
|
||||
|
||||
@@ -6,7 +6,7 @@ import AlertComponent from "../../components/alert/alert.component";
|
||||
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
|
||||
import { QUERY_BODYSHOP } from "../../graphql/bodyshop.queries";
|
||||
import { setBodyshop } from "../../redux/user/user.actions";
|
||||
import "../../utils/RegisterSw";
|
||||
//import "../../utils/RegisterSw";
|
||||
import ManagePage from "./manage.page.component";
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
|
||||
@@ -42,6 +42,10 @@ 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
|
||||
|
||||
@@ -12,6 +12,7 @@ import TechSider from "../../components/tech-sider/tech-sider.component";
|
||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||
import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component";
|
||||
import "./tech.page.styles.scss";
|
||||
import UpdateAlert from "../../components/update-alert/update-alert.component";
|
||||
const TimeTicketModalContainer = lazy(() =>
|
||||
import("../../components/time-ticket-modal/time-ticket-modal.container")
|
||||
);
|
||||
@@ -56,7 +57,9 @@ export function TechPage({ technician, match }) {
|
||||
<TechSider />
|
||||
<Layout>
|
||||
{technician ? null : <Redirect to={`${match.path}/login`} />}
|
||||
<UpdateAlert />
|
||||
<TechHeader />
|
||||
|
||||
<Content className="tech-content-container">
|
||||
<ErrorBoundary>
|
||||
<Suspense
|
||||
|
||||
@@ -9,7 +9,7 @@ import LoadingSpinner from "../../components/loading-spinner/loading-spinner.com
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import "../../utils/RegisterSw";
|
||||
//import "../../utils/RegisterSw";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
|
||||
@@ -62,3 +62,8 @@ export const setProblemJobs = (problemJobs) => ({
|
||||
type: ApplicationActionTypes.SET_PROBLEM_JOBS,
|
||||
payload: problemJobs,
|
||||
});
|
||||
|
||||
export const setUpdateAvailable = (isUpdateAvailable) => ({
|
||||
type: ApplicationActionTypes.SET_UPDATE_AVAILABLE,
|
||||
payload: isUpdateAvailable,
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import ApplicationActionTypes from "./application.types";
|
||||
const INITIAL_STATE = {
|
||||
loading: false,
|
||||
online: true,
|
||||
updateAvailable: false,
|
||||
breadcrumbs: [],
|
||||
recentItems: [],
|
||||
selectedHeader: "home",
|
||||
@@ -18,6 +19,11 @@ const INITIAL_STATE = {
|
||||
|
||||
const applicationReducer = (state = INITIAL_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case ApplicationActionTypes.SET_UPDATE_AVAILABLE:
|
||||
return {
|
||||
...state,
|
||||
updateAvailable: action.payload,
|
||||
};
|
||||
case ApplicationActionTypes.SET_SELECTED_HEADER:
|
||||
return {
|
||||
...state,
|
||||
|
||||
@@ -48,3 +48,7 @@ export const selectProblemJobs = createSelector(
|
||||
[selectApplication],
|
||||
(application) => application.problemJobs
|
||||
);
|
||||
export const selectUpdateAvailable = createSelector(
|
||||
[selectApplication],
|
||||
(application) => application.updateAvailable
|
||||
);
|
||||
|
||||
@@ -12,5 +12,6 @@ const ApplicationActionTypes = {
|
||||
SET_ONLINE_STATUS: "SET_ONLINE_STATUS",
|
||||
INSERT_AUDIT_TRAIL: "INSERT_AUDIT_TRAIL",
|
||||
SET_PROBLEM_JOBS: "SET_PROBLEM_JOBS",
|
||||
SET_UPDATE_AVAILABLE: "SET_UPDATE_AVAILABLE"
|
||||
};
|
||||
export default ApplicationActionTypes;
|
||||
|
||||
@@ -1453,6 +1453,7 @@
|
||||
"dms_make": "DMS Make",
|
||||
"dms_model": "DMS Model",
|
||||
"dms_wip_acctnumber": "Cost WIP DMS Acct #",
|
||||
"dms_unsold": "New, Unsold Vehicle",
|
||||
"id": "DMS ID",
|
||||
"inservicedate": "In Service Date",
|
||||
"journal": "Journal #",
|
||||
|
||||
@@ -1453,6 +1453,7 @@
|
||||
"dms_make": "",
|
||||
"dms_model": "",
|
||||
"dms_wip_acctnumber": "",
|
||||
"dms_unsold": "",
|
||||
"id": "",
|
||||
"inservicedate": "",
|
||||
"journal": "",
|
||||
|
||||
@@ -1453,6 +1453,7 @@
|
||||
"dms_make": "",
|
||||
"dms_model": "",
|
||||
"dms_wip_acctnumber": "",
|
||||
"dms_unsold": "",
|
||||
"id": "",
|
||||
"inservicedate": "",
|
||||
"journal": "",
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Button, notification, Space } from "antd";
|
||||
import i18n from "i18next";
|
||||
import React from "react";
|
||||
import * as serviceWorkerRegistration from "../serviceWorkerRegistration";
|
||||
import { store } from "../redux/store";
|
||||
|
||||
const onServiceWorkerUpdate = (registration) => {
|
||||
console.log("onServiceWorkerUpdate", registration);
|
||||
@@ -33,6 +34,9 @@ const onServiceWorkerUpdate = (registration) => {
|
||||
</Button>
|
||||
</Space>
|
||||
);
|
||||
|
||||
store.dispatch()
|
||||
|
||||
notification.open({
|
||||
icon: <AlertOutlined />,
|
||||
message: i18n.t("general.messages.newversiontitle"),
|
||||
|
||||
@@ -2690,6 +2690,13 @@
|
||||
table:
|
||||
name: inventory
|
||||
schema: public
|
||||
- name: parts_dispatch_lines
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: joblineid
|
||||
table:
|
||||
name: parts_dispatch_lines
|
||||
schema: public
|
||||
- name: parts_order_lines
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
@@ -3131,6 +3138,13 @@
|
||||
table:
|
||||
name: notes
|
||||
schema: public
|
||||
- name: parts_dispatches
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: jobid
|
||||
table:
|
||||
name: parts_dispatch
|
||||
schema: public
|
||||
- name: parts_orders
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
@@ -4556,6 +4570,165 @@
|
||||
template_engine: Kriti
|
||||
url: '{{$base_url}}/opensearch'
|
||||
version: 2
|
||||
- table:
|
||||
name: parts_dispatch
|
||||
schema: public
|
||||
object_relationships:
|
||||
- name: job
|
||||
using:
|
||||
foreign_key_constraint_on: jobid
|
||||
array_relationships:
|
||||
- name: parts_dispatch_lines
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: partsdispatchid
|
||||
table:
|
||||
name: parts_dispatch_lines
|
||||
schema: public
|
||||
insert_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
check:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
columns:
|
||||
- id
|
||||
- created_at
|
||||
- updated_at
|
||||
- jobid
|
||||
- number
|
||||
- employeeid
|
||||
- dispatched_at
|
||||
- dispatched_by
|
||||
select_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
columns:
|
||||
- number
|
||||
- dispatched_by
|
||||
- created_at
|
||||
- dispatched_at
|
||||
- updated_at
|
||||
- employeeid
|
||||
- id
|
||||
- jobid
|
||||
filter:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
update_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
columns:
|
||||
- number
|
||||
- dispatched_by
|
||||
- created_at
|
||||
- dispatched_at
|
||||
- updated_at
|
||||
- employeeid
|
||||
- id
|
||||
- jobid
|
||||
filter:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
check: null
|
||||
- table:
|
||||
name: parts_dispatch_lines
|
||||
schema: public
|
||||
object_relationships:
|
||||
- name: jobline
|
||||
using:
|
||||
foreign_key_constraint_on: joblineid
|
||||
- name: parts_dispatch
|
||||
using:
|
||||
foreign_key_constraint_on: partsdispatchid
|
||||
insert_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
check:
|
||||
parts_dispatch:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
columns:
|
||||
- id
|
||||
- created_at
|
||||
- updated_at
|
||||
- partsdispatchid
|
||||
- joblineid
|
||||
- quantity
|
||||
- accepted_at
|
||||
select_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
columns:
|
||||
- quantity
|
||||
- accepted_at
|
||||
- created_at
|
||||
- updated_at
|
||||
- id
|
||||
- joblineid
|
||||
- partsdispatchid
|
||||
filter:
|
||||
parts_dispatch:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
update_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
columns:
|
||||
- id
|
||||
- created_at
|
||||
- updated_at
|
||||
- partsdispatchid
|
||||
- joblineid
|
||||
- quantity
|
||||
- accepted_at
|
||||
filter:
|
||||
parts_dispatch:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
check: null
|
||||
- table:
|
||||
name: parts_order_lines
|
||||
schema: public
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE "public"."parts_dispatch";
|
||||
@@ -0,0 +1,18 @@
|
||||
CREATE TABLE "public"."parts_dispatch" ("id" uuid NOT NULL DEFAULT gen_random_uuid(), "created_at" timestamptz NOT NULL DEFAULT now(), "updated_at" timestamptz NOT NULL DEFAULT now(), "jobid" uuid NOT NULL, "number" serial NOT NULL, "employeeid" uuid NOT NULL, "dispatched_at" timestamptz NOT NULL, "dispatched_by" text NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("jobid") REFERENCES "public"."jobs"("id") ON UPDATE cascade ON DELETE cascade);
|
||||
CREATE OR REPLACE FUNCTION "public"."set_current_timestamp_updated_at"()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
_new record;
|
||||
BEGIN
|
||||
_new := NEW;
|
||||
_new."updated_at" = NOW();
|
||||
RETURN _new;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
CREATE TRIGGER "set_public_parts_dispatch_updated_at"
|
||||
BEFORE UPDATE ON "public"."parts_dispatch"
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE "public"."set_current_timestamp_updated_at"();
|
||||
COMMENT ON TRIGGER "set_public_parts_dispatch_updated_at" ON "public"."parts_dispatch"
|
||||
IS 'trigger to set value of column "updated_at" to current timestamp on row update';
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE "public"."parts_dispatch_lines";
|
||||
@@ -0,0 +1,18 @@
|
||||
CREATE TABLE "public"."parts_dispatch_lines" ("id" uuid NOT NULL DEFAULT gen_random_uuid(), "created_at" timestamptz NOT NULL DEFAULT now(), "updated_at" timestamptz NOT NULL DEFAULT now(), "partsdispatchid" UUID NOT NULL, "joblineid" uuid NOT NULL, "quantity" numeric NOT NULL DEFAULT 1, "accepted_at" timestamptz NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("joblineid") REFERENCES "public"."joblines"("id") ON UPDATE cascade ON DELETE cascade);
|
||||
CREATE OR REPLACE FUNCTION "public"."set_current_timestamp_updated_at"()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
_new record;
|
||||
BEGIN
|
||||
_new := NEW;
|
||||
_new."updated_at" = NOW();
|
||||
RETURN _new;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
CREATE TRIGGER "set_public_parts_dispatch_lines_updated_at"
|
||||
BEFORE UPDATE ON "public"."parts_dispatch_lines"
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE "public"."set_current_timestamp_updated_at"();
|
||||
COMMENT ON TRIGGER "set_public_parts_dispatch_lines_updated_at" ON "public"."parts_dispatch_lines"
|
||||
IS 'trigger to set value of column "updated_at" to current timestamp on row update';
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
@@ -0,0 +1 @@
|
||||
alter table "public"."parts_dispatch_lines" drop constraint "parts_dispatch_lines_partsdispatchid_fkey";
|
||||
@@ -0,0 +1,5 @@
|
||||
alter table "public"."parts_dispatch_lines"
|
||||
add constraint "parts_dispatch_lines_partsdispatchid_fkey"
|
||||
foreign key ("partsdispatchid")
|
||||
references "public"."parts_dispatch"
|
||||
("id") on update cascade on delete cascade;
|
||||
@@ -0,0 +1 @@
|
||||
alter table "public"."parts_dispatch_lines" alter column "accepted_at" set not null;
|
||||
@@ -0,0 +1 @@
|
||||
alter table "public"."parts_dispatch_lines" alter column "accepted_at" drop not null;
|
||||
@@ -0,0 +1,5 @@
|
||||
alter table "public"."job_conversations" drop constraint "job_conversations_conversationid_fkey",
|
||||
add constraint "job_conversations_conversationid_fkey"
|
||||
foreign key ("conversationid")
|
||||
references "public"."conversations"
|
||||
("id") on update restrict on delete restrict;
|
||||
@@ -0,0 +1,5 @@
|
||||
alter table "public"."job_conversations" drop constraint "job_conversations_conversationid_fkey",
|
||||
add constraint "job_conversations_conversationid_fkey"
|
||||
foreign key ("conversationid")
|
||||
references "public"."conversations"
|
||||
("id") on update cascade on delete cascade;
|
||||
@@ -717,18 +717,24 @@ async function InsertDmsVehicle(socket) {
|
||||
dealer: {
|
||||
dealerNumber: socket.JobData.bodyshop.cdk_dealerid,
|
||||
...(socket.txEnvelope.inservicedate && {
|
||||
inServiceDate: moment(socket.txEnvelope.inservicedate)
|
||||
//.tz(socket.JobData.bodyshop.timezone)
|
||||
.startOf("day")
|
||||
.toISOString(),
|
||||
inServiceDate:
|
||||
socket.txEnvelope.dms_unsold === true
|
||||
? ""
|
||||
: moment(socket.txEnvelope.inservicedate)
|
||||
//.tz(socket.JobData.bodyshop.timezone)
|
||||
.startOf("day")
|
||||
.toISOString(),
|
||||
}),
|
||||
vehicleId: socket.DMSVid.vehiclesVehId,
|
||||
},
|
||||
manufacturer: {},
|
||||
vehicle: {
|
||||
deliveryDate: moment()
|
||||
// .tz(socket.JobData.bodyshop.timezone)
|
||||
.format("YYYYMMDD"),
|
||||
deliveryDate:
|
||||
socket.txEnvelope.dms_unsold === true
|
||||
? ""
|
||||
: moment()
|
||||
// .tz(socket.JobData.bodyshop.timezone)
|
||||
.format("YYYYMMDD"),
|
||||
licensePlateNo:
|
||||
socket.JobData.plate_no === null
|
||||
? null
|
||||
@@ -860,19 +866,25 @@ async function UpdateDmsVehicle(socket) {
|
||||
...socket.DMSVeh.dealer,
|
||||
...((socket.txEnvelope.inservicedate ||
|
||||
socket.DMSVeh.dealer.inServiceDate) && {
|
||||
inServiceDate: moment(
|
||||
socket.DMSVeh.dealer.inServiceDate ||
|
||||
socket.txEnvelope.inservicedate
|
||||
)
|
||||
// .tz(socket.JobData.bodyshop.timezone)
|
||||
.toISOString(),
|
||||
inServiceDate:
|
||||
socket.txEnvelope.dms_unsold === true
|
||||
? ""
|
||||
: moment(
|
||||
socket.DMSVeh.dealer.inServiceDate ||
|
||||
socket.txEnvelope.inservicedate
|
||||
)
|
||||
// .tz(socket.JobData.bodyshop.timezone)
|
||||
.toISOString(),
|
||||
}),
|
||||
},
|
||||
vehicle: {
|
||||
...socket.DMSVeh.vehicle,
|
||||
deliveryDate: moment(socket.DMSVeh.vehicle.deliveryDate)
|
||||
//.tz(socket.JobData.bodyshop.timezone)
|
||||
.toISOString(),
|
||||
deliveryDate:
|
||||
socket.txEnvelope.dms_unsold === true
|
||||
? ""
|
||||
: moment(socket.DMSVeh.vehicle.deliveryDate)
|
||||
//.tz(socket.JobData.bodyshop.timezone)
|
||||
.toISOString(),
|
||||
},
|
||||
owners: ids,
|
||||
},
|
||||
|
||||
@@ -86,6 +86,7 @@ async function OpenSearchUpdateHandler(req, res) {
|
||||
"v_model_yr",
|
||||
"v_make_desc",
|
||||
"v_model_desc",
|
||||
"v_vin",
|
||||
]);
|
||||
document.bodyshopid = req.body.event.data.new.shopid;
|
||||
break;
|
||||
@@ -139,7 +140,7 @@ async function OpenSearchUpdateHandler(req, res) {
|
||||
"exported_at",
|
||||
"invoice_number",
|
||||
"is_credit_memo",
|
||||
"total"
|
||||
"total",
|
||||
]),
|
||||
...bill.bills_by_pk,
|
||||
bodyshopid: bill.bills_by_pk.job.shopid,
|
||||
@@ -244,17 +245,54 @@ async function OpensearchSearchHandler(req, res) {
|
||||
bool: {
|
||||
must: [
|
||||
{
|
||||
multi_match: {
|
||||
query: search,
|
||||
type: "phrase_prefix",
|
||||
//fields: ["*"],
|
||||
// fuzziness: "5",
|
||||
//prefix_length: 2,
|
||||
match: {
|
||||
bodyshopid: assocs.associations[0].shopid,
|
||||
},
|
||||
},
|
||||
{
|
||||
match: {
|
||||
bodyshopid: assocs.associations[0].shopid,
|
||||
bool: {
|
||||
should: [
|
||||
{
|
||||
multi_match: {
|
||||
query: search,
|
||||
type: "cross_fields",
|
||||
fields: ["*ownr_fn", "*ownr_ln"],
|
||||
},
|
||||
},
|
||||
{
|
||||
multi_match: {
|
||||
query: search,
|
||||
type: "most_fields",
|
||||
fields: [
|
||||
"*v_model_yr",
|
||||
"*v_make_desc^2",
|
||||
"*v_model_desc^3",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
query_string: {
|
||||
query: `*${search}*`,
|
||||
|
||||
fields: [
|
||||
"*ro_number^20",
|
||||
"*clm_no^14",
|
||||
"*v_vin^12",
|
||||
"*plate_no^12",
|
||||
"*ownr_ln^10",
|
||||
"transactionid^10",
|
||||
"paymentnum^10",
|
||||
"invoice_number^10",
|
||||
"*ownr_fn^8",
|
||||
"*ownr_co_nm^8",
|
||||
"*ownr_ph1^8",
|
||||
"*ownr_ph2^8",
|
||||
"*",
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user