- Merge client update into test-beta

Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
Dave Richer
2024-01-18 19:20:08 -05:00
696 changed files with 92291 additions and 107075 deletions

View File

@@ -1,85 +1,83 @@
import { useQuery } from "@apollo/client";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {useQuery} from "@apollo/client";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import AccountingPayablesTable from "../../components/accounting-payables-table/accounting-payables-table.component";
import AlertComponent from "../../components/alert/alert.component";
import { checkPartnerStatus } from "../../components/partner-ping/partner-ping.component";
import {checkPartnerStatus} from "../../components/partner-ping/partner-ping.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { QUERY_BILLS_FOR_EXPORT } from "../../graphql/accounting.queries";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import { selectPartnerVersion } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {QUERY_BILLS_FOR_EXPORT} from "../../graphql/accounting.queries";
import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import {selectPartnerVersion} from "../../redux/application/application.selectors";
import {selectBodyshop} from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
partnerVersion: selectPartnerVersion,
bodyshop: selectBodyshop,
partnerVersion: selectPartnerVersion,
});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function AccountingPayablesContainer({
bodyshop,
setBreadcrumbs,
setSelectedHeader,
partnerVersion,
}) {
const { t } = useTranslation();
bodyshop,
setBreadcrumbs,
setSelectedHeader,
partnerVersion,
}) {
const {t} = useTranslation();
useEffect(() => {
document.title = t("titles.accounting-payables");
setSelectedHeader("payables");
setBreadcrumbs([
{
link: "/manage/accounting/payables",
label: t("titles.bc.accounting-payables"),
},
]);
checkPartnerStatus(bodyshop, true);
}, [t, setBreadcrumbs, setSelectedHeader, bodyshop]);
useEffect(() => {
document.title = t("titles.accounting-payables");
setSelectedHeader("payables");
setBreadcrumbs([
{
link: "/manage/accounting/payables",
label: t("titles.bc.accounting-payables"),
},
]);
checkPartnerStatus(bodyshop, true);
}, [t, setBreadcrumbs, setSelectedHeader, bodyshop]);
const { loading, error, data, refetch } = useQuery(QUERY_BILLS_FOR_EXPORT, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
const {loading, error, data, refetch} = useQuery(QUERY_BILLS_FOR_EXPORT, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
if (error) return <AlertComponent message={error.message} type="error" />;
if (error) return <AlertComponent message={error.message} type="error"/>;
const noPath =
!partnerVersion?.qbpath &&
!(
bodyshop &&
(bodyshop.cdk_dealerid ||
bodyshop.pbs_serialnumber ||
bodyshop.accountingconfig.qbo)
const noPath =
!partnerVersion?.qbpath &&
!(
bodyshop &&
(bodyshop.cdk_dealerid ||
bodyshop.pbs_serialnumber ||
bodyshop.accountingconfig.qbo)
);
return (
<div>
<RbacWrapper action="accounting:payables">
{noPath && (
<AlertComponent
type="error"
message={t("general.messages.noacctfilepath")}
/>
)}
<AccountingPayablesTable
loadaing={loading}
bills={data ? data.bills : []}
refetch={refetch}
/>
</RbacWrapper>
</div>
);
return (
<div>
<RbacWrapper action="accounting:payables">
{noPath && (
<AlertComponent
type="error"
message={t("general.messages.noacctfilepath")}
/>
)}
<AccountingPayablesTable
loadaing={loading}
bills={data ? data.bills : []}
refetch={refetch}
/>
</RbacWrapper>
</div>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
mapStateToProps,
mapDispatchToProps
)(AccountingPayablesContainer);

View File

@@ -1,85 +1,84 @@
import { useQuery } from "@apollo/client";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {useQuery} from "@apollo/client";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import AccountingPaymentsTable from "../../components/accounting-payments-table/accounting-payments-table.component";
import AlertComponent from "../../components/alert/alert.component";
import { QUERY_PAYMENTS_FOR_EXPORT } from "../../graphql/accounting.queries";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {QUERY_PAYMENTS_FOR_EXPORT} from "../../graphql/accounting.queries";
import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import {selectBodyshop} from "../../redux/user/user.selectors";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { checkPartnerStatus } from "../../components/partner-ping/partner-ping.component";
import { selectPartnerVersion } from "../../redux/application/application.selectors";
import {checkPartnerStatus} from "../../components/partner-ping/partner-ping.component";
import {selectPartnerVersion} from "../../redux/application/application.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
partnerVersion: selectPartnerVersion,
bodyshop: selectBodyshop,
partnerVersion: selectPartnerVersion,
});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function AccountingPaymentsContainer({
bodyshop,
setBreadcrumbs,
setSelectedHeader,
partnerVersion,
}) {
const { t } = useTranslation();
bodyshop,
setBreadcrumbs,
setSelectedHeader,
partnerVersion,
}) {
const {t} = useTranslation();
useEffect(() => {
document.title = t("titles.accounting-payments");
setSelectedHeader("payments");
setBreadcrumbs([
{
link: "/manage/accounting/payments",
label: t("titles.bc.accounting-payments"),
},
]);
checkPartnerStatus(bodyshop, true);
}, [t, setBreadcrumbs, setSelectedHeader, bodyshop]);
useEffect(() => {
document.title = t("titles.accounting-payments");
setSelectedHeader("payments");
setBreadcrumbs([
{
link: "/manage/accounting/payments",
label: t("titles.bc.accounting-payments"),
},
]);
checkPartnerStatus(bodyshop, true);
}, [t, setBreadcrumbs, setSelectedHeader, bodyshop]);
const { loading, error, data, refetch } = useQuery(
QUERY_PAYMENTS_FOR_EXPORT,
{
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
}
);
if (error) return <AlertComponent message={error.message} type="error" />;
const noPath =
!partnerVersion?.qbpath &&
!(
bodyshop &&
(bodyshop.cdk_dealerid ||
bodyshop.pbs_serialnumber ||
bodyshop.accountingconfig.qbo)
const {loading, error, data, refetch} = useQuery(
QUERY_PAYMENTS_FOR_EXPORT,
{
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
}
);
if (error) return <AlertComponent message={error.message} type="error"/>;
const noPath =
!partnerVersion?.qbpath &&
!(
bodyshop &&
(bodyshop.cdk_dealerid ||
bodyshop.pbs_serialnumber ||
bodyshop.accountingconfig.qbo)
);
return (
<div>
<RbacWrapper action="accounting:payments">
{noPath && (
<AlertComponent
type="error"
message={t("general.messages.noacctfilepath")}
/>
)}
<AccountingPaymentsTable
loadaing={loading}
payments={data ? data.payments : []}
refetch={refetch}
/>
</RbacWrapper>
</div>
);
return (
<div>
<RbacWrapper action="accounting:payments">
{noPath && (
<AlertComponent
type="error"
message={t("general.messages.noacctfilepath")}
/>
)}
<AccountingPaymentsTable
loadaing={loading}
payments={data ? data.payments : []}
refetch={refetch}
/>
</RbacWrapper>
</div>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
mapStateToProps,
mapDispatchToProps
)(AccountingPaymentsContainer);

View File

@@ -1,47 +1,46 @@
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import QboAuthorizeComponent from "../../components/qbo-authorize/qbo-authorize.component";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import {selectBodyshop} from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function AccountingReceivablesContainer({
bodyshop,
setBreadcrumbs,
setSelectedHeader,
}) {
const { t } = useTranslation();
bodyshop,
setBreadcrumbs,
setSelectedHeader,
}) {
const {t} = useTranslation();
useEffect(() => {
document.title = t("titles.accounting-qbo");
setSelectedHeader("qbo");
setBreadcrumbs([
{
link: "/manage/accounting/qbo",
label: t("titles.bc.accounting-qbo"),
},
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
useEffect(() => {
document.title = t("titles.accounting-qbo");
setSelectedHeader("qbo");
setBreadcrumbs([
{
link: "/manage/accounting/qbo",
label: t("titles.bc.accounting-qbo"),
},
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
return (
<div>
<QboAuthorizeComponent />
</div>
);
return (
<div>
<QboAuthorizeComponent/>
</div>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
mapStateToProps,
mapDispatchToProps
)(AccountingReceivablesContainer);

View File

@@ -1,87 +1,87 @@
import { useQuery } from "@apollo/client";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import AccountingReceivablesTable from "../../components/accounting-receivables-table/accounting-receivables-table.component";
import {useQuery} from "@apollo/client";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import AccountingReceivablesTable
from "../../components/accounting-receivables-table/accounting-receivables-table.component";
import AlertComponent from "../../components/alert/alert.component";
import { QUERY_JOBS_FOR_EXPORT } from "../../graphql/accounting.queries";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {QUERY_JOBS_FOR_EXPORT} from "../../graphql/accounting.queries";
import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import {selectBodyshop} from "../../redux/user/user.selectors";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { checkPartnerStatus } from "../../components/partner-ping/partner-ping.component";
import { selectPartnerVersion } from "../../redux/application/application.selectors";
import {checkPartnerStatus} from "../../components/partner-ping/partner-ping.component";
import {selectPartnerVersion} from "../../redux/application/application.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
partnerVersion: selectPartnerVersion,
bodyshop: selectBodyshop,
partnerVersion: selectPartnerVersion,
});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function AccountingReceivablesContainer({
bodyshop,
setBreadcrumbs,
setSelectedHeader,
partnerVersion,
}) {
const { t } = useTranslation();
bodyshop,
setBreadcrumbs,
setSelectedHeader,
partnerVersion,
}) {
const {t} = useTranslation();
useEffect(() => {
document.title = t("titles.accounting-receivables");
setSelectedHeader("receivables");
setBreadcrumbs([
{
link: "/manage/accounting/receivables",
label: t("titles.bc.accounting-receivables"),
},
]);
checkPartnerStatus(bodyshop, true);
}, [t, setBreadcrumbs, setSelectedHeader, bodyshop]);
useEffect(() => {
document.title = t("titles.accounting-receivables");
setSelectedHeader("receivables");
setBreadcrumbs([
{
link: "/manage/accounting/receivables",
label: t("titles.bc.accounting-receivables"),
},
]);
checkPartnerStatus(bodyshop, true);
}, [t, setBreadcrumbs, setSelectedHeader, bodyshop]);
const { loading, error, data, refetch } = useQuery(QUERY_JOBS_FOR_EXPORT, {
variables: {
invoicedStatus: bodyshop.md_ro_statuses.default_invoiced || "Invoiced*",
},
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
const {loading, error, data, refetch} = useQuery(QUERY_JOBS_FOR_EXPORT, {
variables: {
invoicedStatus: bodyshop.md_ro_statuses.default_invoiced || "Invoiced*",
},
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
if (error) return <AlertComponent message={error.message} type="error" />;
if (error) return <AlertComponent message={error.message} type="error"/>;
const noPath =
!partnerVersion?.qbpath &&
!(
bodyshop &&
(bodyshop.cdk_dealerid ||
bodyshop.pbs_serialnumber ||
bodyshop.accountingconfig.qbo)
const noPath =
!partnerVersion?.qbpath &&
!(
bodyshop &&
(bodyshop.cdk_dealerid ||
bodyshop.pbs_serialnumber ||
bodyshop.accountingconfig.qbo)
);
return (
<div>
<RbacWrapper action="accounting:receivables">
{noPath && (
<AlertComponent
type="error"
message={t("general.messages.noacctfilepath")}
/>
)}
<AccountingReceivablesTable
loadaing={loading}
jobs={data ? data.jobs : []}
refetch={refetch}
/>
</RbacWrapper>
</div>
);
return (
<div>
<RbacWrapper action="accounting:receivables">
{noPath && (
<AlertComponent
type="error"
message={t("general.messages.noacctfilepath")}
/>
)}
<AccountingReceivablesTable
loadaing={loading}
jobs={data ? data.jobs : []}
refetch={refetch}
/>
</RbacWrapper>
</div>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
mapStateToProps,
mapDispatchToProps
)(AccountingReceivablesContainer);

View File

@@ -1,317 +1,318 @@
import { EditFilled, SyncOutlined } from "@ant-design/icons";
import { Button, Card, Checkbox, Input, Space, Table, Typography } from "antd";
import {EditFilled, SyncOutlined} 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 { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link, useHistory, useLocation } from "react-router-dom";
import React, {useEffect, useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {Link, useLocation, useNavigate} from "react-router-dom";
import BillDeleteButton from "../../components/bill-delete-button/bill-delete-button.component";
import PartsOrderModalContainer from "../../components/parts-order-modal/parts-order-modal.container";
import PrintWrapperComponent from "../../components/print-wrapper/print-wrapper.component";
import { setModalContext } from "../../redux/modals/modals.actions";
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 {DateFormatter} from "../../utils/DateFormatter";
import {TemplateList} from "../../utils/TemplateConstants";
import {alphaSort, dateSort} from "../../utils/sorters";
import {pageLimit} from "../../utils/config";
const mapDispatchToProps = (dispatch) => ({
setPartsOrderContext: (context) =>
dispatch(setModalContext({ context: context, modal: "partsOrder" })),
setBillEnterContext: (context) =>
dispatch(setModalContext({ context: context, modal: "billEnter" })),
setPartsOrderContext: (context) =>
dispatch(setModalContext({context: context, modal: "partsOrder"})),
setBillEnterContext: (context) =>
dispatch(setModalContext({context: context, modal: "billEnter"})),
});
export function BillsListPage({
loading,
data,
refetch,
total,
setPartsOrderContext,
setBillEnterContext,
}) {
const search = queryString.parse(useLocation().search);
const [openSearchResults, setOpenSearchResults] = useState([]);
const [searchLoading, setSearchLoading] = useState(false);
const { page } = search;
const history = useHistory();
const [state, setState] = useState({
sortedInfo: {},
filteredInfo: { text: "" },
});
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,
render: (text, record) => <span>{record.vendor.name}</span>,
},
{
title: t("bills.fields.invoice_number"),
dataIndex: "invoice_number",
key: "invoice_number",
sorter: (a, b) => alphaSort(a.invoice_number, b.invoice_number),
sortOrder:
state.sortedInfo.columnKey === "invoice_number" &&
state.sortedInfo.order,
},
{
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,
render: (text, record) =>
record.job && (
<Link to={`/manage/jobs/${record.job.id}`}>
{record.job.ro_number}
</Link>
),
},
{
title: t("bills.fields.date"),
dataIndex: "date",
key: "date",
sorter: (a, b) => dateSort(a.date, b.date),
sortOrder:
state.sortedInfo.columnKey === "date" && state.sortedInfo.order,
render: (text, record) => <DateFormatter>{record.date}</DateFormatter>,
},
{
title: t("bills.fields.total"),
dataIndex: "total",
key: "total",
sorter: (a, b) => a.total - b.total,
sortOrder:
state.sortedInfo.columnKey === "total" && state.sortedInfo.order,
render: (text, record) => (
<CurrencyFormatter>{record.total}</CurrencyFormatter>
),
},
{
title: t("bills.fields.is_credit_memo"),
dataIndex: "is_credit_memo",
key: "is_credit_memo",
sorter: (a, b) => a.is_credit_memo - b.is_credit_memo,
sortOrder:
state.sortedInfo.columnKey === "is_credit_memo" &&
state.sortedInfo.order,
render: (text, record) => (
<Checkbox disabled checked={record.is_credit_memo} />
),
},
{
title: t("bills.fields.exported"),
dataIndex: "exported",
key: "exported",
sorter: (a, b) => a.exported - b.exported,
sortOrder:
state.sortedInfo.columnKey === "exported" && state.sortedInfo.order,
render: (text, record) => <Checkbox disabled checked={record.exported} />,
},
{
title: t("general.labels.actions"),
dataIndex: "actions",
key: "actions",
render: (text, record) => (
<Space wrap>
<Link to={`/manage/bills?billid=${record.id}`}>
<Button>
<EditFilled />
</Button>
</Link>
{
// <Button
// disabled={record.is_credit_memo}
// onClick={() =>
// setPartsOrderContext({
// actions: {},
// context: {
// jobId: record.jobid,
// vendorId: record.vendorid,
// returnFromBill: record.id,
// invoiceNumber: record.invoice_number,
// linesToOrder: record.billlines.map((i) => {
// return {
// line_desc: i.line_desc,
// // db_price: i.actual_price,
// act_price: i.actual_price,
// cost: i.actual_cost,
// quantity: i.quantity,
// joblineid: i.joblineid,
// };
// }),
// isReturn: true,
// },
// })
// }
// >
// {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)
);
}}
/>
{record.isinhouse && (
<PrintWrapperComponent
templateObject={{
name: Templates.inhouse_invoice.key,
variables: { id: record.id },
}}
messageObject={{ subject: Templates.inhouse_invoice.subject }}
/>
)}
</Space>
),
},
];
loading,
data,
refetch,
total,
setPartsOrderContext,
setBillEnterContext,
}) {
const search = queryString.parse(useLocation().search);
const [openSearchResults, setOpenSearchResults] = useState([]);
const [searchLoading, setSearchLoading] = useState(false);
const {page} = search;
const history = useNavigate();
const [state, setState] = useState({
sortedInfo: {},
filteredInfo: {text: ""},
});
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,
render: (text, record) => <span>{record.vendor.name}</span>,
},
{
title: t("bills.fields.invoice_number"),
dataIndex: "invoice_number",
key: "invoice_number",
sorter: (a, b) => alphaSort(a.invoice_number, b.invoice_number),
sortOrder:
state.sortedInfo.columnKey === "invoice_number" &&
state.sortedInfo.order,
},
{
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,
render: (text, record) =>
record.job && (
<Link to={`/manage/jobs/${record.job.id}`}>
{record.job.ro_number}
</Link>
),
},
{
title: t("bills.fields.date"),
dataIndex: "date",
key: "date",
sorter: (a, b) => dateSort(a.date, b.date),
sortOrder:
state.sortedInfo.columnKey === "date" && state.sortedInfo.order,
render: (text, record) => <DateFormatter>{record.date}</DateFormatter>,
},
{
title: t("bills.fields.total"),
dataIndex: "total",
key: "total",
sorter: (a, b) => a.total - b.total,
sortOrder:
state.sortedInfo.columnKey === "total" && state.sortedInfo.order,
render: (text, record) => (
<CurrencyFormatter>{record.total}</CurrencyFormatter>
),
},
{
title: t("bills.fields.is_credit_memo"),
dataIndex: "is_credit_memo",
key: "is_credit_memo",
sorter: (a, b) => a.is_credit_memo - b.is_credit_memo,
sortOrder:
state.sortedInfo.columnKey === "is_credit_memo" &&
state.sortedInfo.order,
render: (text, record) => (
<Checkbox disabled checked={record.is_credit_memo}/>
),
},
{
title: t("bills.fields.exported"),
dataIndex: "exported",
key: "exported",
sorter: (a, b) => a.exported - b.exported,
sortOrder:
state.sortedInfo.columnKey === "exported" && state.sortedInfo.order,
render: (text, record) => <Checkbox disabled checked={record.exported}/>,
},
{
title: t("general.labels.actions"),
dataIndex: "actions",
key: "actions",
render: (text, record) => (
<Space wrap>
<Link to={`/manage/bills?billid=${record.id}`}>
<Button>
<EditFilled/>
</Button>
</Link>
{
// <Button
// disabled={record.is_credit_memo}
// onClick={() =>
// setPartsOrderContext({
// actions: {},
// context: {
// jobId: record.jobid,
// vendorId: record.vendorid,
// returnFromBill: record.id,
// invoiceNumber: record.invoice_number,
// linesToOrder: record.billlines.map((i) => {
// return {
// line_desc: i.line_desc,
// // db_price: i.actual_price,
// act_price: i.actual_price,
// cost: i.actual_cost,
// quantity: i.quantity,
// joblineid: i.joblineid,
// };
// }),
// isReturn: true,
// },
// })
// }
// >
// {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)
);
}}
/>
{record.isinhouse && (
<PrintWrapperComponent
templateObject={{
name: Templates.inhouse_invoice.key,
variables: {id: record.id},
}}
messageObject={{subject: Templates.inhouse_invoice.subject}}
/>
)}
</Space>
),
},
];
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
search.page = pagination.current;
if (sorter && sorter.column && sorter.column.sortObject) {
search.searchObj = JSON.stringify(sorter.column.sortObject(sorter.order));
} else {
delete search.searchObj;
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")}
extra={
<Space wrap>
{search.search && (
<>
<Typography.Title level={4}>
{t("general.labels.searchresults", { search: search.search })}
</Typography.Title>
<Button
onClick={() => {
delete search.search;
delete search.page;
history.push({ search: queryString.stringify(search) });
}}
>
{t("general.actions.clear")}
</Button>
</>
)}
<Button onClick={() => refetch()}>
<SyncOutlined />
</Button>
<Button
onClick={() => {
setBillEnterContext({
actions: { refetch: refetch },
context: {},
});
}}
>
{t("jobs.actions.postbills")}
</Button>
<Input.Search
placeholder={search.search || t("general.labels.search")}
onSearch={(value) => {
search.search = value;
history.push({ search: queryString.stringify(search) });
searchBills(value);
}}
loading={loading || searchLoading}
enterButton
/>
</Space>
}
>
<PartsOrderModalContainer />
<Table
loading={loading || searchLoading}
// scroll={{
// x: "50%", // y: "40rem"
// }}
scroll={{ x: true }}
pagination={
search?.search
? {
pageSize: pageLimit,
showSizeChanger: false,
}
: {
pageSize: pageLimit,
current: parseInt(page || 1),
total: total,
showSizeChanger: false,
}
const handleTableChange = (pagination, filters, sorter) => {
setState({...state, filteredInfo: filters, sortedInfo: sorter});
search.page = pagination.current;
if (sorter && sorter.column && sorter.column.sortObject) {
search.searchObj = JSON.stringify(sorter.column.sortObject(sorter.order));
} else {
delete search.searchObj;
search.sortcolumn = sorter.order ? sorter.columnKey : null;
search.sortorder = sorter.order;
}
columns={columns}
rowKey="id"
dataSource={search?.search ? openSearchResults : data}
onChange={handleTableChange}
/>
</Card>
);
search.sort = JSON.stringify({[sorter.columnKey]: sorter.order});
history({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")}
extra={
<Space wrap>
{search.search && (
<>
<Typography.Title level={4}>
{t("general.labels.searchresults", {search: search.search})}
</Typography.Title>
<Button
onClick={() => {
delete search.search;
delete search.page;
history({search: queryString.stringify(search)});
}}
>
{t("general.actions.clear")}
</Button>
</>
)}
<Button onClick={() => refetch()}>
<SyncOutlined/>
</Button>
<Button
onClick={() => {
setBillEnterContext({
actions: {refetch: refetch},
context: {},
});
}}
>
{t("jobs.actions.postbills")}
</Button>
<Input.Search
placeholder={search.search || t("general.labels.search")}
onSearch={(value) => {
search.search = value;
history({search: queryString.stringify(search)});
searchBills(value);
}}
loading={loading || searchLoading}
enterButton
/>
</Space>
}
>
<PartsOrderModalContainer/>
<Table
loading={loading || searchLoading}
// scroll={{
// x: "50%", // y: "40rem"
// }}
scroll={{x: true}}
pagination={
search?.search
? {
pageSize: pageLimit,
showSizeChanger: false,
}
: {
pageSize: pageLimit,
current: parseInt(page || 1),
total: total,
showSizeChanger: false,
}
}
columns={columns}
rowKey="id"
dataSource={search?.search ? openSearchResults : data}
onChange={handleTableChange}
/>
</Card>
);
}
export default connect(null, mapDispatchToProps)(BillsListPage);

View File

@@ -1,75 +1,73 @@
import { useQuery } from "@apollo/client";
import {useQuery} from "@apollo/client";
import queryString from "query-string";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useLocation } from "react-router-dom";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {useLocation} from "react-router-dom";
import AlertComponent from "../../components/alert/alert.component";
import BillDetailEditContainer from "../../components/bill-detail-edit/bill-detail-edit.container";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { QUERY_ALL_BILLS_PAGINATED } from "../../graphql/bills.queries";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import {QUERY_ALL_BILLS_PAGINATED} from "../../graphql/bills.queries";
import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import BillsPageComponent from "./bills.page.component";
import {pageLimit} from "../../utils/config";
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function BillsPageContainer({ setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation();
const searchParams = queryString.parse(useLocation().search);
const { page, sortcolumn, sortorder, searchObj } = searchParams;
export function BillsPageContainer({setBreadcrumbs, setSelectedHeader}) {
const {t} = useTranslation();
const searchParams = queryString.parse(useLocation().search);
const {page, sortcolumn, sortorder, searchObj} = searchParams;
useEffect(() => {
document.title = t("titles.bills-list");
setSelectedHeader("bills");
setBreadcrumbs([
{ link: "/manage/bills", label: t("titles.bc.bills-list") },
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
useEffect(() => {
document.title = t("titles.bills-list");
setSelectedHeader("bills");
setBreadcrumbs([
{link: "/manage/bills", label: t("titles.bc.bills-list")},
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
const { loading, error, data, refetch } = useQuery(
QUERY_ALL_BILLS_PAGINATED,
{
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
variables: {
offset: page ? (page - 1) * pageLimit : 0,
limit: pageLimit,
order: [
searchObj
? JSON.parse(searchObj)
: {
[sortcolumn || "date"]: sortorder
? sortorder === "descend"
? "desc"
: "asc"
: "desc",
},
],
},
}
);
const {loading, error, data, refetch} = useQuery(
QUERY_ALL_BILLS_PAGINATED,
{
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
variables: {
offset: page ? (page - 1) * pageLimit : 0,
limit: pageLimit,
order: [
searchObj
? JSON.parse(searchObj)
: {
[sortcolumn || "date"]: sortorder
? sortorder === "descend"
? "desc"
: "asc"
: "desc",
},
],
},
}
);
if (error) return <AlertComponent message={error.message} type="error" />;
return (
<RbacWrapper action="bills:list">
<div>
<BillsPageComponent
data={data ? data.bills : []}
loading={loading}
refetch={refetch}
total={data ? data.bills_aggregate.aggregate.count : 0}
/>
if (error) return <AlertComponent message={error.message} type="error"/>;
return (
<RbacWrapper action="bills:list">
<div>
<BillsPageComponent
data={data ? data.bills : []}
loading={loading}
refetch={refetch}
total={data ? data.bills_aggregate.aggregate.count : 0}
/>
<BillDetailEditContainer />
</div>
</RbacWrapper>
);
<BillDetailEditContainer/>
</div>
</RbacWrapper>
);
}
export default connect(null, mapDispatchToProps)(BillsPageContainer);

View File

@@ -1,68 +1,70 @@
import { Button, Col, PageHeader, Row, Space, Form, Switch } from "antd";
import {Button, Col, Form, Row, Space, Switch} from "antd";
import {PageHeader} from "@ant-design/pro-layout";
import React from "react";
import { useTranslation } from "react-i18next";
import {useTranslation} from "react-i18next";
import ContractCarsContainer from "../../components/contract-cars/contract-cars.container";
import ContractFormComponent from "../../components/contract-form/contract-form.component";
import ContractJobsContainer from "../../components/contract-jobs/contract-jobs.container";
export default function ContractCreatePageComponent({
form,
selectedJobState,
selectedCarState,
loading,
}) {
const { t } = useTranslation();
form,
selectedJobState,
selectedCarState,
loading,
}) {
const {t} = useTranslation();
const CreateButton = (
<Space size="large">
{selectedJobState[0] && selectedCarState[0] && (
<Form.Item
label={t("jobs.actions.addtoproduction")}
name="addtoproduction"
valuePropName="checked"
>
<Switch />
</Form.Item>
)}
<Button
disabled={!selectedJobState[0] || !selectedCarState[0]}
type="primary"
onClick={() => form.submit()}
loading={loading}
>
{t("general.actions.create")}
</Button>
</Space>
);
const CreateButton = (
<Space size="large">
{selectedJobState[0] && selectedCarState[0] && (
<Form.Item
label={t("jobs.actions.addtoproduction")}
name="addtoproduction"
valuePropName="checked"
>
<Switch/>
</Form.Item>
)}
<Button
disabled={!selectedJobState[0] || !selectedCarState[0]}
type="primary"
onClick={() => form.submit()}
loading={loading}
>
{t("general.actions.create")}
</Button>
</Space>
);
return (
<div>
<Row gutter={[16, 16]}>
<Col span={24}>
<ContractJobsContainer selectedJobState={selectedJobState} />
</Col>
<Col span={24}>
<ContractCarsContainer
selectedCarState={selectedCarState}
form={form}
/>
</Col>
<Col span={24}>
<div
style={{
display: selectedJobState[0] && selectedCarState[0] ? "" : "none",
}}
>
<ContractFormComponent
create
form={form}
selectedJobState={selectedJobState}
selectedCar={selectedCarState[0]}
/>
</div>
</Col>
</Row>
<PageHeader extra={CreateButton} />
</div>
);
return (
<div>
<Row gutter={[16, 16]}>
<Col span={24}>
<ContractJobsContainer selectedJobState={selectedJobState}/>
</Col>
<Col span={24}>
<ContractCarsContainer
selectedCarState={selectedCarState}
form={form}
/>
</Col>
<Col span={24}>
<div
style={{
display: selectedJobState[0] && selectedCarState[0] ? "" : "none",
}}
>
<ContractFormComponent
create
form={form}
selectedJobState={selectedJobState}
selectedCar={selectedCarState[0]}
/>
</div>
</Col>
</Row>
<PageHeader extra={CreateButton}/>
</div>
);
}

View File

@@ -1,145 +1,145 @@
import { useMutation } from "@apollo/client";
import { Form, notification } from "antd";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useHistory, useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import {useMutation} from "@apollo/client";
import {Form, notification} from "antd";
import React, {useEffect, useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {useLocation, useNavigate} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { INSERT_NEW_CONTRACT } from "../../graphql/cccontracts.queries";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {INSERT_NEW_CONTRACT} from "../../graphql/cccontracts.queries";
import {UPDATE_JOB} from "../../graphql/jobs.queries";
import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import {selectBodyshop} from "../../redux/user/user.selectors";
import ContractCreatePageComponent from "./contract-create.page.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function ContractCreatePageContainer({
bodyshop,
setBreadcrumbs,
setSelectedHeader,
}) {
const [form] = Form.useForm();
const { t } = useTranslation();
const history = useHistory();
const location = useLocation();
const [loading, setLoading] = useState(false);
const selectedCarState = useState(null);
const selectedJobState = useState(
(location.state && location.state.jobId) || null
);
const [insertContract] = useMutation(INSERT_NEW_CONTRACT);
const [intakeJob] = useMutation(UPDATE_JOB);
bodyshop,
setBreadcrumbs,
setSelectedHeader,
}) {
const [form] = Form.useForm();
const {t} = useTranslation();
const handleFinish = async ({ addtoproduction, ...values }) => {
if (!!selectedCarState[0] && !!selectedJobState[0]) {
setLoading(true);
const result = await insertContract({
variables: {
ccId: selectedCarState[0].id,
damage: values.damage,
mileage: values.kmstart,
contract: {
...values,
status: "contracts.status.out",
courtesycarid: selectedCarState[0].id,
jobid: selectedJobState[0],
},
},
});
const history = useNavigate();
const location = useLocation();
const [loading, setLoading] = useState(false);
const selectedCarState = useState(null);
const selectedJobState = useState(
(location.state && location.state.jobId) || null
);
if (!result.errors) {
//Update the courtesy car to have the damage.
notification["success"]({
message: t("contracts.successes.saved"),
});
const [insertContract] = useMutation(INSERT_NEW_CONTRACT);
const [intakeJob] = useMutation(UPDATE_JOB);
//Intake the job if required
if (addtoproduction) {
const result2 = await intakeJob({
variables: {
jobId: selectedJobState[0],
job: {
actual_in: new Date(),
inproduction: true,
status: bodyshop.md_ro_statuses.default_arrived,
},
},
});
if (result2.errors) {
notification["error"]({
message: t("jobs.errors.saving", {
error: JSON.stringify(!result2.errors),
}),
const handleFinish = async ({addtoproduction, ...values}) => {
if (!!selectedCarState[0] && !!selectedJobState[0]) {
setLoading(true);
const result = await insertContract({
variables: {
ccId: selectedCarState[0].id,
damage: values.damage,
mileage: values.kmstart,
contract: {
...values,
status: "contracts.status.out",
courtesycarid: selectedCarState[0].id,
jobid: selectedJobState[0],
},
},
});
if (!result.errors) {
//Update the courtesy car to have the damage.
notification["success"]({
message: t("contracts.successes.saved"),
});
//Intake the job if required
if (addtoproduction) {
const result2 = await intakeJob({
variables: {
jobId: selectedJobState[0],
job: {
actual_in: new Date(),
inproduction: true,
status: bodyshop.md_ro_statuses.default_arrived,
},
},
});
if (result2.errors) {
notification["error"]({
message: t("jobs.errors.saving", {
error: JSON.stringify(!result2.errors),
}),
});
return;
}
}
form.resetFields();
form.resetFields();
history(
`/manage/courtesycars/contracts/${result.data.insert_cccontracts.returning[0].id}`
);
} else {
notification["error"]({
message: t("contracts.errors.saving", {
error: JSON.stringify(!result.errors),
}),
});
}
} else {
notification["error"]({
message: t("contracts.errors.selectjobandcar"),
});
return;
}
}
setLoading(false);
};
form.resetFields();
form.resetFields();
history.push(
`/manage/courtesycars/contracts/${result.data.insert_cccontracts.returning[0].id}`
);
} else {
notification["error"]({
message: t("contracts.errors.saving", {
error: JSON.stringify(!result.errors),
}),
});
}
} else {
notification["error"]({
message: t("contracts.errors.selectjobandcar"),
});
}
setLoading(false);
};
useEffect(() => {
document.title = t("titles.contracts-create");
setSelectedHeader("newcontract");
setBreadcrumbs([
{link: "/manage/courtesycars", label: t("titles.bc.courtesycars")},
{
link: "/manage/courtesycars/contracts",
label: t("titles.bc.contracts"),
},
{
link: "/manage/courtesycars/contracts/new",
label: t("titles.bc.contracts-create"),
},
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
useEffect(() => {
document.title = t("titles.contracts-create");
setSelectedHeader("newcontract");
setBreadcrumbs([
{ link: "/manage/courtesycars", label: t("titles.bc.courtesycars") },
{
link: "/manage/courtesycars/contracts",
label: t("titles.bc.contracts"),
},
{
link: "/manage/courtesycars/contracts/new",
label: t("titles.bc.contracts-create"),
},
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
return (
<RbacWrapper action="contracts:create">
<Form
form={form}
layout="vertical"
autoComplete="no"
onFinish={handleFinish}
>
<ContractCreatePageComponent
loading={loading}
form={form}
selectedJobState={selectedJobState}
selectedCarState={selectedCarState}
/>
</Form>
</RbacWrapper>
);
return (
<RbacWrapper action="contracts:create">
<Form
form={form}
layout="vertical"
autoComplete="no"
onFinish={handleFinish}
>
<ContractCreatePageComponent
loading={loading}
form={form}
selectedJobState={selectedJobState}
selectedCarState={selectedCarState}
/>
</Form>
</RbacWrapper>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
mapStateToProps,
mapDispatchToProps
)(ContractCreatePageContainer);

View File

@@ -1,160 +1,126 @@
import {
Button,
Col,
Dropdown,
Form,
Menu,
PageHeader,
Row,
Space,
Typography,
} from "antd";
import {Button, Col, Dropdown, Form, Row, Space, Typography,} from "antd";
import {PageHeader} from "@ant-design/pro-layout";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import ContractConvertToRo from "../../components/contract-convert-to-ro/contract-convert-to-ro.component";
import ContractCourtesyCarBlock from "../../components/contract-courtesy-car-block/contract-courtesy-car-block.component";
import ContractCourtesyCarBlock
from "../../components/contract-courtesy-car-block/contract-courtesy-car-block.component";
import ContractFormComponent from "../../components/contract-form/contract-form.component";
import ContractJobBlock from "../../components/contract-job-block/contract-job-block.component";
import { setModalContext } from "../../redux/modals/modals.actions";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
import {setModalContext} from "../../redux/modals/modals.actions";
import {GenerateDocument} from "../../utils/RenderTemplate";
import {TemplateList} from "../../utils/TemplateConstants";
const mapDispatchToProps = (dispatch) => ({
setCourtesyCarReturnModalContext: (context) =>
dispatch(setModalContext({ context: context, modal: "courtesyCarReturn" })),
setCourtesyCarReturnModalContext: (context) =>
dispatch(setModalContext({context: context, modal: "courtesyCarReturn"})),
});
export function ContractDetailPage({
contract,
job,
courtesyCar,
setCourtesyCarReturnModalContext,
refetch,
form,
saveLoading,
}) {
const { t } = useTranslation();
return (
<div>
<Row align="middle">
<Typography.Title></Typography.Title>
</Row>
<PageHeader
title={t("contracts.labels.agreement", {
agreement_num: contract && contract.agreementnumber,
status: t(contract && contract.status),
})}
extra={
<Form.Item shouldUpdate>
{() => {
return (
<Space>
<Button
type="primary"
htmlType="submit"
loading={saveLoading}
>
{t("general.actions.save")}
</Button>
<Button
disabled={
!!!contract ||
(contract && contract.status !== "contracts.status.out")
}
onClick={() => {
setCourtesyCarReturnModalContext({
actions: { refetch },
context: {
contractId: contract.id,
courtesyCarId: courtesyCar.id,
},
});
}}
>
{t("courtesycars.actions.return")}
</Button>
<Dropdown
trigger="click"
overlay={
<Menu>
<Menu.Item
onClick={() =>
GenerateDocument(
{
name: TemplateList("courtesycarcontract")
.courtesy_car_contract.key,
variables: { id: contract.id },
},
{},
"p"
)
}
>
{t("contracts.actions.printcontract")}
</Menu.Item>
<Menu.Item
onClick={() =>
GenerateDocument(
{
name: TemplateList("courtesycarcontract")
.courtesy_car_terms.key,
variables: { id: contract.id },
},
{},
"p"
)
}
>
{t(
"printcenter.courtesycarcontract.courtesy_car_terms"
)}
</Menu.Item>
<Menu.Item
onClick={() =>
GenerateDocument(
{
name: TemplateList("courtesycarcontract")
.courtesy_car_impound.key,
variables: { id: contract.id },
},
{},
"p"
)
}
>
{t(
"printcenter.courtesycarcontract.courtesy_car_impound"
)}
</Menu.Item>
</Menu>
}
>
<Button>{t("general.labels.print")}</Button>
</Dropdown>
contract,
job,
courtesyCar,
setCourtesyCarReturnModalContext,
refetch,
form,
saveLoading
}) {
const {t} = useTranslation();
return (
<div>
<Row align="middle">
<Typography.Title></Typography.Title>
</Row>
<PageHeader
title={t("contracts.labels.agreement", {
agreement_num: contract && contract.agreementnumber,
status: t(contract && contract.status),
})}
extra={
<Form.Item shouldUpdate>
{() => {
const menu = {
onClick: (e) => {
GenerateDocument(
{
name: TemplateList("courtesycarcontract")[e.key].key,
variables: {id: contract.id},
},
{},
"p"
);
},
items: [
{
key: "courtesy_car_contract",
label: t("contracts.actions.printcontract"),
},
{
key: "courtesy_car_terms",
label: t("printcenter.courtesycarcontract.courtesy_car_terms"),
},
{
key: "courtesy_car_impound",
label: t("printcenter.courtesycarcontract.courtesy_car_impound"),
},
]
};
<ContractConvertToRo
contract={contract}
disabled={form.isFieldsTouched()}
/>
</Space>
);
}}
</Form.Item>
}
/>
<Row gutter={[16, 16]}>
<Col sm={24} md={12}>
<ContractJobBlock job={job} />
</Col>
<Col sm={24} md={12}>
<ContractCourtesyCarBlock courtesyCar={courtesyCar} />
</Col>
<Col span={24}>
<ContractFormComponent form={form} />
</Col>
</Row>
</div>
);
return (
<Space>
<Button
type="primary"
htmlType="submit"
loading={saveLoading}
>
{t("general.actions.save")}
</Button>
<Button
disabled={
!!!contract ||
(contract && contract.status !== "contracts.status.out")
}
onClick={() => {
setCourtesyCarReturnModalContext({
actions: {refetch},
context: {
contractId: contract.id,
courtesyCarId: courtesyCar.id,
},
});
}}
>
{t("courtesycars.actions.return")}
</Button>
<Dropdown trigger="click" menu={menu}>
<Button>{t("general.labels.print")}</Button>
</Dropdown>
<ContractConvertToRo
contract={contract}
disabled={form.isFieldsTouched()}
/>
</Space>
);
}}
</Form.Item>
}
/>
<Row gutter={[16, 16]}>
<Col sm={24} md={12}>
<ContractJobBlock job={job}/>
</Col>
<Col sm={24} md={12}>
<ContractCourtesyCarBlock courtesyCar={courtesyCar}/>
</Col>
<Col span={24}>
<ContractFormComponent form={form}/>
</Col>
</Row>
</div>
);
}
export default connect(null, mapDispatchToProps)(ContractDetailPage);

View File

@@ -1,171 +1,166 @@
import { useMutation, useQuery } from "@apollo/client";
import { Form, notification } from "antd";
import moment from "moment";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useParams } from "react-router-dom";
import {useMutation, useQuery} from "@apollo/client";
import {Form, notification} from "antd";
import dayjs from "../../utils/day";
import React, {useEffect, useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {useParams} from "react-router-dom";
import AlertComponent from "../../components/alert/alert.component";
import CourtesyCarReturnModalContainer from "../../components/courtesy-car-return-modal/courtesy-car-return-modal.container";
import CourtesyCarReturnModalContainer
from "../../components/courtesy-car-return-modal/courtesy-car-return-modal.container";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import {
QUERY_CONTRACT_BY_PK,
UPDATE_CONTRACT,
} from "../../graphql/cccontracts.queries";
import {
addRecentItem,
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import { CreateRecentItem } from "../../utils/create-recent-item";
import {QUERY_CONTRACT_BY_PK, UPDATE_CONTRACT,} from "../../graphql/cccontracts.queries";
import {addRecentItem, setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import {CreateRecentItem} from "../../utils/create-recent-item";
import ContractDetailPageComponent from "./contract-detail.page.component";
import NotFound from "../../components/not-found/not-found.component";
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
addRecentItem: (item) => dispatch(addRecentItem(item)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
addRecentItem: (item) => dispatch(addRecentItem(item)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function ContractDetailPageContainer({
setBreadcrumbs,
addRecentItem,
setSelectedHeader,
}) {
const { t } = useTranslation();
const [updateContract] = useMutation(UPDATE_CONTRACT);
const [saveLoading, setsaveLoading] = useState(false);
const [form] = Form.useForm();
const { contractId } = useParams();
setBreadcrumbs,
addRecentItem,
setSelectedHeader,
}) {
const {t} = useTranslation();
const [updateContract] = useMutation(UPDATE_CONTRACT);
const [saveLoading, setsaveLoading] = useState(false);
const [form] = Form.useForm();
const {contractId} = useParams();
const { loading, error, data, refetch } = useQuery(QUERY_CONTRACT_BY_PK, {
variables: { id: contractId },
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
const {loading, error, data, refetch} = useQuery(QUERY_CONTRACT_BY_PK, {
variables: {id: contractId},
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
useEffect(() => {
setSelectedHeader("contracts");
document.title = loading
? t("titles.app")
: error
? t("titles.app")
: t("titles.contracts-detail", {
id:
(data &&
data.cccontracts_by_pk &&
data.cccontracts_by_pk.agreementnumber) ||
"",
});
useEffect(() => {
setSelectedHeader("contracts");
document.title = loading
? t("titles.app")
: error
? t("titles.app")
: t("titles.contracts-detail", {
id:
(data &&
data.cccontracts_by_pk &&
data.cccontracts_by_pk.agreementnumber) ||
"",
});
setBreadcrumbs([
{ link: "/manage/courtesycars", label: t("titles.bc.courtesycars") },
{
link: "/manage/courtesycars/contracts",
label: t("titles.bc.contracts"),
},
{
link: "/manage/courtesycars/contracts/new",
label: t("titles.bc.contracts-detail", {
number:
(data &&
data.cccontracts_by_pk &&
data.cccontracts_by_pk.agreementnumber) ||
"",
}),
},
setBreadcrumbs([
{link: "/manage/courtesycars", label: t("titles.bc.courtesycars")},
{
link: "/manage/courtesycars/contracts",
label: t("titles.bc.contracts"),
},
{
link: "/manage/courtesycars/contracts/new",
label: t("titles.bc.contracts-detail", {
number:
(data &&
data.cccontracts_by_pk &&
data.cccontracts_by_pk.agreementnumber) ||
"",
}),
},
]);
if (data && data.cccontracts_by_pk)
addRecentItem(
CreateRecentItem(
contractId,
"contract",
data.cccontracts_by_pk.agreementnumber,
`/manage/courtesycars/contracts/${contractId}`
)
);
}, [
t,
data,
error,
loading,
setBreadcrumbs,
addRecentItem,
contractId,
setSelectedHeader,
]);
if (data && data.cccontracts_by_pk)
addRecentItem(
CreateRecentItem(
contractId,
"contract",
data.cccontracts_by_pk.agreementnumber,
`/manage/courtesycars/contracts/${contractId}`
)
);
}, [
t,
data,
error,
loading,
setBreadcrumbs,
addRecentItem,
contractId,
setSelectedHeader,
]);
const handleFinish = async (values) => {
setsaveLoading(true);
const result = await updateContract({
variables: {cccontract: {...values}, contractId: contractId},
});
if (!!result.errors) {
notification["error"]({
message: t("contracts.errors.saving", {
message: JSON.stringify(result.errors),
}),
});
return;
}
notification["success"]({message: t("contracts.successes.saved")});
if (refetch) await refetch();
setsaveLoading(false);
const handleFinish = async (values) => {
setsaveLoading(true);
const result = await updateContract({
variables: { cccontract: { ...values }, contractId: contractId },
});
if (!!result.errors) {
notification["error"]({
message: t("contracts.errors.saving", {
message: JSON.stringify(result.errors),
}),
});
return;
}
notification["success"]({ message: t("contracts.successes.saved") });
if (refetch) await refetch();
setsaveLoading(false);
form.resetFields();
form.resetFields();
};
form.resetFields();
form.resetFields();
};
useEffect(() => {
if (data && data.cccontracts_by_pk) form.resetFields();
}, [data, form]);
useEffect(() => {
if (data && data.cccontracts_by_pk) form.resetFields();
}, [data, form]);
if (error) return <AlertComponent message={error.message} type="error"/>;
if (loading) return <LoadingSpinner/>;
if (error) return <AlertComponent message={error.message} type="error" />;
if (loading) return <LoadingSpinner />;
if (!!!data.cccontracts_by_pk) return <NotFound/>;
if (!!!data.cccontracts_by_pk) return <NotFound />;
return (
<RbacWrapper action="contracts:detail">
<div>
<CourtesyCarReturnModalContainer />
<Form
form={form}
autoComplete="no"
layout="vertical"
onFinish={handleFinish}
initialValues={{
...data.cccontracts_by_pk,
start: data.cccontracts_by_pk.start
? moment(data.cccontracts_by_pk.start)
: null,
scheduledreturn: data.cccontracts_by_pk.scheduledreturn
? moment(data.cccontracts_by_pk.scheduledreturn)
: null,
actualreturn: data.cccontracts_by_pk.actualreturn
? moment(data.cccontracts_by_pk.actualreturn)
: null,
driver_dlexpiry: data.cccontracts_by_pk.driver_dlexpiry
? moment(data.cccontracts_by_pk.driver_dlexpiry)
: null,
driver_dob: data.cccontracts_by_pk.driver_dob
? moment(data.cccontracts_by_pk.driver_dob)
: null,
}}
>
<ContractDetailPageComponent
contract={data ? data.cccontracts_by_pk : null}
job={data ? data.cccontracts_by_pk.job : null}
courtesyCar={data ? data.cccontracts_by_pk.courtesycar : null}
refetch={refetch}
form={form}
saveLoading={saveLoading}
/>
</Form>
</div>
</RbacWrapper>
);
return (
<RbacWrapper action="contracts:detail">
<div>
<CourtesyCarReturnModalContainer/>
<Form
form={form}
autoComplete="no"
layout="vertical"
onFinish={handleFinish}
initialValues={{
...data.cccontracts_by_pk,
start: data.cccontracts_by_pk.start
? dayjs(data.cccontracts_by_pk.start)
: null,
scheduledreturn: data.cccontracts_by_pk.scheduledreturn
? dayjs(data.cccontracts_by_pk.scheduledreturn)
: null,
actualreturn: data.cccontracts_by_pk.actualreturn
? dayjs(data.cccontracts_by_pk.actualreturn)
: null,
driver_dlexpiry: data.cccontracts_by_pk.driver_dlexpiry
? dayjs(data.cccontracts_by_pk.driver_dlexpiry)
: null,
driver_dob: data.cccontracts_by_pk.driver_dob
? dayjs(data.cccontracts_by_pk.driver_dob)
: null,
}}
>
<ContractDetailPageComponent
contract={data ? data.cccontracts_by_pk : null}
job={data ? data.cccontracts_by_pk.job : null}
courtesyCar={data ? data.cccontracts_by_pk.courtesycar : null}
refetch={refetch}
form={form}
saveLoading={saveLoading}
/>
</Form>
</div>
</RbacWrapper>
);
}
export default connect(null, mapDispatchToProps)(ContractDetailPageContainer);

View File

@@ -2,17 +2,17 @@ import React from "react";
import ContractsList from "../../components/contracts-list/contracts-list.component";
export default function ContractsPageComponent({
loading,
data,
refetch,
total,
}) {
return (
<ContractsList
loading={loading}
contracts={data}
refetch={refetch}
total={total}
/>
);
loading,
data,
refetch,
total,
}) {
return (
<ContractsList
loading={loading}
contracts={data}
refetch={refetch}
total={total}
/>
);
}

View File

@@ -1,72 +1,70 @@
import { useQuery } from "@apollo/client";
import {useQuery} from "@apollo/client";
import queryString from "query-string";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useLocation } from "react-router-dom";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {useLocation} from "react-router-dom";
import AlertComponent from "../../components/alert/alert.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { QUERY_ACTIVE_CONTRACTS_PAGINATED } from "../../graphql/cccontracts.queries";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import {QUERY_ACTIVE_CONTRACTS_PAGINATED} from "../../graphql/cccontracts.queries";
import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import ContractsPageComponent from "./contracts.page.component";
import {pageLimit} from "../../utils/config";
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function ContractsPageContainer({ setBreadcrumbs, setSelectedHeader }) {
const searchParams = queryString.parse(useLocation().search);
const { search, page, sortcolumn, sortorder } = searchParams;
export function ContractsPageContainer({setBreadcrumbs, setSelectedHeader}) {
const searchParams = queryString.parse(useLocation().search);
const {search, page, sortcolumn, sortorder} = searchParams;
const { loading, error, data, refetch } = useQuery(
QUERY_ACTIVE_CONTRACTS_PAGINATED,
{
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
variables: {
search: search || "",
offset: page ? (page - 1) * pageLimit : 0,
limit: pageLimit,
order: [
{
[sortcolumn || "start"]: sortorder
? sortorder === "descend"
? "desc"
: "asc"
: "desc",
},
],
},
}
);
const { t } = useTranslation();
useEffect(() => {
document.title = t("titles.contracts");
setSelectedHeader("contracts");
setBreadcrumbs([
{ link: "/manage/courtesycars", label: t("titles.bc.courtesycars") },
{
link: "/manage/courtesycars/contracts",
label: t("titles.bc.contracts"),
},
]);
}, [setBreadcrumbs, t, setSelectedHeader]);
const {loading, error, data, refetch} = useQuery(
QUERY_ACTIVE_CONTRACTS_PAGINATED,
{
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
variables: {
search: search || "",
offset: page ? (page - 1) * pageLimit : 0,
limit: pageLimit,
order: [
{
[sortcolumn || "start"]: sortorder
? sortorder === "descend"
? "desc"
: "asc"
: "desc",
},
],
},
}
);
const {t} = useTranslation();
useEffect(() => {
document.title = t("titles.contracts");
setSelectedHeader("contracts");
setBreadcrumbs([
{link: "/manage/courtesycars", label: t("titles.bc.courtesycars")},
{
link: "/manage/courtesycars/contracts",
label: t("titles.bc.contracts"),
},
]);
}, [setBreadcrumbs, t, setSelectedHeader]);
if (error) return <AlertComponent message={error.message} type="error" />;
return (
<RbacWrapper action="contracts:list">
<ContractsPageComponent
loading={loading}
refetch={refetch}
data={data ? data.search_cccontracts : []}
total={data ? data.search_cccontracts_aggregate.aggregate.count : 0}
/>
</RbacWrapper>
);
if (error) return <AlertComponent message={error.message} type="error"/>;
return (
<RbacWrapper action="contracts:list">
<ContractsPageComponent
loading={loading}
refetch={refetch}
data={data ? data.search_cccontracts : []}
total={data ? data.search_cccontracts_aggregate.aggregate.count : 0}
/>
</RbacWrapper>
);
}
export default connect(null, mapDispatchToProps)(ContractsPageContainer);

View File

@@ -1,87 +1,86 @@
import { useMutation } from "@apollo/client";
import { Form, notification } from "antd";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useHistory } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import {useMutation} from "@apollo/client";
import {Form, notification} from "antd";
import React, {useEffect, useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {useNavigate} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import CourtesyCarFormComponent from "../../components/courtesy-car-form/courtesy-car-form.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { INSERT_NEW_COURTESY_CAR } from "../../graphql/courtesy-car.queries";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {INSERT_NEW_COURTESY_CAR} from "../../graphql/courtesy-car.queries";
import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import {selectBodyshop} from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function CourtesyCarCreateContainer({
bodyshop,
setBreadcrumbs,
setSelectedHeader,
}) {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [insertCourtesyCar] = useMutation(INSERT_NEW_COURTESY_CAR);
const { t } = useTranslation();
const history = useHistory();
bodyshop,
setBreadcrumbs,
setSelectedHeader,
}) {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [insertCourtesyCar] = useMutation(INSERT_NEW_COURTESY_CAR);
const {t} = useTranslation();
const history = useNavigate();
const handleFinish = async (values) => {
setLoading(true);
const result = await insertCourtesyCar({
variables: { courtesycar: { ...values, bodyshopid: bodyshop.id } },
});
const handleFinish = async (values) => {
setLoading(true);
const result = await insertCourtesyCar({
variables: {courtesycar: {...values, bodyshopid: bodyshop.id}},
});
if (!!result.errors) {
notification["error"]({
message: t("courtesycars.errors.saving", {
message: JSON.stringify(result.errors),
}),
});
setLoading(false);
} else {
setLoading(false);
form.resetFields();
form.resetFields();
notification["success"]({ message: t("courtesycars.successes.saved") });
history.push(
`/manage/courtesycars/${result.data.insert_courtesycars.returning[0].id}`
);
}
};
if (!!result.errors) {
notification["error"]({
message: t("courtesycars.errors.saving", {
message: JSON.stringify(result.errors),
}),
});
setLoading(false);
} else {
setLoading(false);
form.resetFields();
form.resetFields();
notification["success"]({message: t("courtesycars.successes.saved")});
history(
`/manage/courtesycars/${result.data.insert_courtesycars.returning[0].id}`
);
}
};
useEffect(() => {
setSelectedHeader("courtesycarsall");
document.title = t("titles.courtesycars-create");
setBreadcrumbs([
{ link: "/manage/courtesycars", label: t("titles.bc.courtesycars") },
{
link: "/manage/courtesycars/new",
label: t("titles.bc.courtesycars-new"),
},
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
useEffect(() => {
setSelectedHeader("courtesycarsall");
document.title = t("titles.courtesycars-create");
setBreadcrumbs([
{link: "/manage/courtesycars", label: t("titles.bc.courtesycars")},
{
link: "/manage/courtesycars/new",
label: t("titles.bc.courtesycars-new"),
},
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
return (
<RbacWrapper action="courtesycar:create">
<Form
form={form}
autoComplete="new-password"
onFinish={handleFinish}
layout="vertical"
>
<CourtesyCarFormComponent form={form} saveLoading={loading} />
</Form>
</RbacWrapper>
);
return (
<RbacWrapper action="courtesycar:create">
<Form
form={form}
autoComplete="new-password"
onFinish={handleFinish}
layout="vertical"
>
<CourtesyCarFormComponent form={form} saveLoading={loading}/>
</Form>
</RbacWrapper>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
mapStateToProps,
mapDispatchToProps
)(CourtesyCarCreateContainer);

View File

@@ -1,26 +1,27 @@
import React from "react";
import CourtesyCarCreateFormComponent from "../../components/courtesy-car-form/courtesy-car-form.component";
import CourtesyCarContractListComponent from "../../components/courtesy-car-contract-list/courtesy-car-contract-list.component";
import { Col, Divider, Row } from "antd";
import CourtesyCarContractListComponent
from "../../components/courtesy-car-contract-list/courtesy-car-contract-list.component";
import {Col, Divider, Row} from "antd";
export default function CourtesyCarDetailPageComponent({
contracts,
form,
saveLoading,
totalContracts,
}) {
return (
<Row gutter={[16, 16]}>
<Col span={24}>
<CourtesyCarCreateFormComponent form={form} saveLoading={saveLoading} />
</Col>
<Divider type="horizontal" />
<Col span={24}>
<CourtesyCarContractListComponent
contracts={contracts}
totalContracts={totalContracts}
/>
</Col>
</Row>
);
contracts,
form,
saveLoading,
totalContracts,
}) {
return (
<Row gutter={[16, 16]}>
<Col span={24}>
<CourtesyCarCreateFormComponent form={form} saveLoading={saveLoading}/>
</Col>
<Divider type="horizontal"/>
<Col span={24}>
<CourtesyCarContractListComponent
contracts={contracts}
totalContracts={totalContracts}
/>
</Col>
</Row>
);
}

View File

@@ -1,201 +1,199 @@
import { useMutation, useQuery } from "@apollo/client";
import { Form, notification } from "antd";
import moment from "moment";
import queryString from "query-string";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useLocation, useParams } from "react-router-dom";
import {useMutation, useQuery} from "@apollo/client";
import {Form, notification} from "antd";
import dayjs from "../../utils/day";
import React, {useEffect, useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {useLocation, useParams} from "react-router-dom";
import AlertComponent from "../../components/alert/alert.component";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import NotFound from "../../components/not-found/not-found.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { QUERY_CC_BY_PK, UPDATE_CC } from "../../graphql/courtesy-car.queries";
import {
addRecentItem,
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import { pageLimit } from "../../utils/config";
import { CreateRecentItem } from "../../utils/create-recent-item";
import {QUERY_CC_BY_PK, UPDATE_CC} from "../../graphql/courtesy-car.queries";
import {addRecentItem, setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import {CreateRecentItem} from "../../utils/create-recent-item";
import UndefinedToNull from "./../../utils/undefinedtonull";
import CourtesyCarDetailPageComponent from "./courtesy-car-detail.page.component";
import NotFound from "../../components/not-found/not-found.component";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import queryString from "query-string";
import {pageLimit} from "../../utils/config";
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
addRecentItem: (item) => dispatch(addRecentItem(item)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
addRecentItem: (item) => dispatch(addRecentItem(item)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function CourtesyCarDetailPageContainer({
setBreadcrumbs,
addRecentItem,
setSelectedHeader,
}) {
const searchParams = queryString.parse(useLocation().search);
const { page, sortcolumn, sortorder } = searchParams;
setBreadcrumbs,
addRecentItem,
setSelectedHeader,
}) {
const searchParams = queryString.parse(useLocation().search);
const {page, sortcolumn, sortorder} = searchParams;
const { t } = useTranslation();
const [updateCourtesyCar] = useMutation(UPDATE_CC);
const [form] = Form.useForm();
const { ccId } = useParams();
const [saveLoading, setSaveLoading] = useState(false);
const { loading, error, data } = useQuery(QUERY_CC_BY_PK, {
variables: {
id: ccId,
offset: page ? (page - 1) * pageLimit : 0,
limit: pageLimit,
order: [
{
[sortcolumn || "start"]: sortorder
? sortorder === "descend"
? "desc"
: "asc"
: "desc",
const {t} = useTranslation();
const [updateCourtesyCar] = useMutation(UPDATE_CC);
const [form] = Form.useForm();
const {ccId} = useParams();
const [saveLoading, setSaveLoading] = useState(false);
const {loading, error, data} = useQuery(QUERY_CC_BY_PK, {
variables: {
id: ccId,
offset: page ? (page - 1) * pageLimit : 0,
limit: pageLimit,
order: [
{
[sortcolumn || "start"]: sortorder
? sortorder === "descend"
? "desc"
: "asc"
: "desc",
},
],
},
],
},
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
useEffect(() => {
setSelectedHeader("courtesycarsall");
useEffect(() => {
setSelectedHeader("courtesycarsall");
document.title = loading
? t("titles.app")
: error
? t("titles.app")
: t("titles.courtesycars-detail", {
id:
(data &&
data.courtesycars_by_pk &&
data.courtesycars_by_pk.fleet_number) ||
"",
});
setBreadcrumbs([
{ link: "/manage/courtesycars", label: t("titles.bc.courtesycars") },
{
link: `/manage/courtesycars/${
(data && data.courtesycars_by_pk && data.courtesycars_by_pk.id) || ""
}`,
label: t("titles.bc.courtesycars-detail", {
number:
(data &&
data.courtesycars_by_pk &&
data.courtesycars_by_pk.fleetnumber) ||
"",
}),
},
document.title = loading
? t("titles.app")
: error
? t("titles.app")
: t("titles.courtesycars-detail", {
id:
(data &&
data.courtesycars_by_pk &&
data.courtesycars_by_pk.fleet_number) ||
"",
});
setBreadcrumbs([
{link: "/manage/courtesycars", label: t("titles.bc.courtesycars")},
{
link: `/manage/courtesycars/${
(data && data.courtesycars_by_pk && data.courtesycars_by_pk.id) || ""
}`,
label: t("titles.bc.courtesycars-detail", {
number:
(data &&
data.courtesycars_by_pk &&
data.courtesycars_by_pk.fleetnumber) ||
"",
}),
},
]);
if (data && data.courtesycars_by_pk)
addRecentItem(
CreateRecentItem(
ccId,
"courtesycar",
data.courtesycars_by_pk.fleet_number || data.courtesycars_by_pk.vin,
`/manage/courtesycars/${ccId}`
)
);
}, [
t,
data,
error,
loading,
setBreadcrumbs,
ccId,
addRecentItem,
setSelectedHeader,
]);
if (data && data.courtesycars_by_pk)
addRecentItem(
CreateRecentItem(
ccId,
"courtesycar",
data.courtesycars_by_pk.fleet_number || data.courtesycars_by_pk.vin,
`/manage/courtesycars/${ccId}`
)
);
}, [
t,
data,
error,
loading,
setBreadcrumbs,
ccId,
addRecentItem,
setSelectedHeader,
]);
const handleFinish = async (values) => {
setSaveLoading(true);
const handleFinish = async (values) => {
setSaveLoading(true);
const result = await updateCourtesyCar({
variables: {
cc: {...UndefinedToNull(values, ["readiness"])},
ccId: ccId,
},
refetchQueries: ["QUERY_CC_BY_PK"],
awaitRefetchQueries: true,
});
const result = await updateCourtesyCar({
variables: {
cc: { ...UndefinedToNull(values, ["readiness"]) },
ccId: ccId,
},
refetchQueries: ["QUERY_CC_BY_PK"],
awaitRefetchQueries: true,
});
if (!!result.errors) {
notification["error"]({
message: t("courtesycars.errors.saving", { error: error }),
});
}
notification["success"]({
message: t("courtesycars.successes.saved"),
});
setSaveLoading(false);
};
useEffect(() => {
if (data && data.courtesycars_by_pk) {
form.resetFields();
form.resetFields();
}
}, [data, form]);
if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />;
if (!!!data.courtesycars_by_pk) return <NotFound />;
return (
<RbacWrapper action="courtesycar:detail">
<Form
form={form}
autoComplete="no"
onFinish={handleFinish}
layout="vertical"
initialValues={
data
? {
...data.courtesycars_by_pk,
purchasedate: data.courtesycars_by_pk.purchasedate
? moment(data.courtesycars_by_pk.purchasedate)
: null,
servicestartdate: data.courtesycars_by_pk.servicestartdate
? moment(data.courtesycars_by_pk.servicestartdate)
: null,
serviceenddate: data.courtesycars_by_pk.serviceenddate
? moment(data.courtesycars_by_pk.serviceenddate)
: null,
leaseenddate: data.courtesycars_by_pk.leaseenddate
? moment(data.courtesycars_by_pk.leaseenddate)
: null,
nextservicedate: data.courtesycars_by_pk.nextservicedate
? moment(data.courtesycars_by_pk.nextservicedate)
: null,
registrationexpires: data.courtesycars_by_pk.registrationexpires
? moment(data.courtesycars_by_pk.registrationexpires)
: null,
insuranceexpires: data.courtesycars_by_pk.insuranceexpires
? moment(data.courtesycars_by_pk.insuranceexpires)
: null,
}
: {}
if (!!result.errors) {
notification["error"]({
message: t("courtesycars.errors.saving", {error: error}),
});
}
>
<CourtesyCarDetailPageComponent
contracts={data ? data.courtesycars_by_pk.cccontracts : []}
form={form}
saveLoading={saveLoading}
totalContracts={
data
? data.courtesycars_by_pk.cccontracts_aggregate.aggregate.count
: 0
}
/>
</Form>
</RbacWrapper>
);
notification["success"]({
message: t("courtesycars.successes.saved"),
});
setSaveLoading(false);
};
useEffect(() => {
if (data && data.courtesycars_by_pk) {
form.resetFields();
form.resetFields();
}
}, [data, form]);
if (loading) return <LoadingSpinner/>;
if (error) return <AlertComponent message={error.message} type="error"/>;
if (!!!data.courtesycars_by_pk) return <NotFound/>;
return (
<RbacWrapper action="courtesycar:detail">
<Form
form={form}
autoComplete="no"
onFinish={handleFinish}
layout="vertical"
initialValues={
data
? {
...data.courtesycars_by_pk,
purchasedate: data.courtesycars_by_pk.purchasedate
? dayjs(data.courtesycars_by_pk.purchasedate)
: null,
servicestartdate: data.courtesycars_by_pk.servicestartdate
? dayjs(data.courtesycars_by_pk.servicestartdate)
: null,
serviceenddate: data.courtesycars_by_pk.serviceenddate
? dayjs(data.courtesycars_by_pk.serviceenddate)
: null,
leaseenddate: data.courtesycars_by_pk.leaseenddate
? dayjs(data.courtesycars_by_pk.leaseenddate)
: null,
nextservicedate: data.courtesycars_by_pk.nextservicedate
? dayjs(data.courtesycars_by_pk.nextservicedate)
: null,
registrationexpires: data.courtesycars_by_pk.registrationexpires
? dayjs(data.courtesycars_by_pk.registrationexpires)
: null,
insuranceexpires: data.courtesycars_by_pk.insuranceexpires
? dayjs(data.courtesycars_by_pk.insuranceexpires)
: null,
}
: {}
}
>
<CourtesyCarDetailPageComponent
contracts={data ? data.courtesycars_by_pk.cccontracts : []}
form={form}
saveLoading={saveLoading}
totalContracts={
data
? data.courtesycars_by_pk.cccontracts_aggregate.aggregate.count
: 0
}
/>
</Form>
</RbacWrapper>
);
}
export default connect(
null,
mapDispatchToProps
null,
mapDispatchToProps
)(CourtesyCarDetailPageContainer);

View File

@@ -1,12 +1,12 @@
import React from "react";
import CourtesyCarsListComponent from "../../components/courtesy-cars-list/courtesy-cars-list.component";
export default function CourtesyCarsPageComponent({ loading, data, refetch }) {
return (
<CourtesyCarsListComponent
loading={loading}
courtesycars={data}
refetch={refetch}
/>
);
export default function CourtesyCarsPageComponent({loading, data, refetch}) {
return (
<CourtesyCarsListComponent
loading={loading}
courtesycars={data}
refetch={refetch}
/>
);
}

View File

@@ -1,48 +1,45 @@
import { useQuery } from "@apollo/client";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import {useQuery} from "@apollo/client";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import AlertComponent from "../../components/alert/alert.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { QUERY_ALL_CC } from "../../graphql/courtesy-car.queries";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import {QUERY_ALL_CC} from "../../graphql/courtesy-car.queries";
import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import CourtesyCarsPageComponent from "./courtesy-cars.page.component";
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function CourtesyCarsPageContainer({
setBreadcrumbs,
setSelectedHeader,
}) {
const { loading, error, data, refetch } = useQuery(QUERY_ALL_CC, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
const { t } = useTranslation();
useEffect(() => {
document.title = t("titles.courtesycars");
setSelectedHeader("courtesycarsall");
setBreadcrumbs([
{ link: "/manage/courtesycars", label: t("titles.bc.courtesycars") },
]);
}, [setBreadcrumbs, t, setSelectedHeader]);
setBreadcrumbs,
setSelectedHeader,
}) {
const {loading, error, data, refetch} = useQuery(QUERY_ALL_CC, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
const {t} = useTranslation();
useEffect(() => {
document.title = t("titles.courtesycars");
setSelectedHeader("courtesycarsall");
setBreadcrumbs([
{link: "/manage/courtesycars", label: t("titles.bc.courtesycars")},
]);
}, [setBreadcrumbs, t, setSelectedHeader]);
if (error) return <AlertComponent message={error.message} type="error" />;
return (
<RbacWrapper action="courtesycar:list">
<CourtesyCarsPageComponent
loading={loading}
data={(data && data.courtesycars) || []}
refetch={refetch}
/>
</RbacWrapper>
);
if (error) return <AlertComponent message={error.message} type="error"/>;
return (
<RbacWrapper action="courtesycar:list">
<CourtesyCarsPageComponent
loading={loading}
data={(data && data.courtesycars) || []}
refetch={refetch}
/>
</RbacWrapper>
);
}
export default connect(null, mapDispatchToProps)(CourtesyCarsPageContainer);

View File

@@ -1,178 +1,178 @@
import { useQuery, useMutation } from "@apollo/client";
import { Form, Layout, Typography, Button, Result } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { useParams } from "react-router-dom";
import {useMutation, useQuery} from "@apollo/client";
import {Button, Form, Layout, Result, Typography} from "antd";
import React, {useState} from "react";
import {useTranslation} from "react-i18next";
import {useParams} from "react-router-dom";
import AlertComponent from "../../components/alert/alert.component";
import ConfigFormComponents from "../../components/config-form-components/config-form-components.component";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import { QUERY_SURVEY, COMPLETE_SURVEY } from "../../graphql/csi.queries";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectCurrentUser } from "../../redux/user/user.selectors";
import {COMPLETE_SURVEY, QUERY_SURVEY} from "../../graphql/csi.queries";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {selectCurrentUser} from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
currentUser: selectCurrentUser,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(CsiContainerPage);
export function CsiContainerPage({ currentUser }) {
const { surveyId } = useParams();
const [form] = Form.useForm();
const [submitting, setSubmitting] = useState({
loading: false,
submitted: false,
});
const { loading, error, data } = useQuery(QUERY_SURVEY, {
variables: { surveyId },
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
const { t } = useTranslation();
const [completeSurvey] = useMutation(COMPLETE_SURVEY);
if (loading) return <LoadingSpinner />;
if (error || !!!data.csi_by_pk)
return (
<div>
<Result
status="error"
title={t("csi.errors.notfoundtitle")}
subTitle={t("csi.errors.notfoundsubtitle")}
>
{error ? (
<div>ERROR: {error.graphQLErrors.map((e) => e.message)}</div>
) : null}
</Result>
</div>
);
const handleFinish = async (values) => {
setSubmitting({ ...submitting, loading: true });
const result = await completeSurvey({
variables: {
surveyId,
survey: {
response: values,
valid: false,
completedon: new Date(),
},
},
export function CsiContainerPage({currentUser}) {
const {surveyId} = useParams();
const [form] = Form.useForm();
const [submitting, setSubmitting] = useState({
loading: false,
submitted: false,
});
if (!!!result.errors) {
setSubmitting({ ...submitting, loading: false, submitted: true });
} else {
setSubmitting({
...submitting,
loading: false,
error: JSON.stringify(result.errors),
});
}
};
const {loading, error, data} = useQuery(QUERY_SURVEY, {
variables: {surveyId},
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
const {
relateddata: { bodyshop, job },
csiquestion: { config: csiquestions },
} = data.csi_by_pk;
const {t} = useTranslation();
const [completeSurvey] = useMutation(COMPLETE_SURVEY);
if (loading) return <LoadingSpinner/>;
if (currentUser && currentUser.authorized)
return (
<Layout
style={{ height: "100vh", display: "flex", flexDirection: "column" }}
>
<Result
status="error"
title={t("csi.labels.nologgedinuser")}
subTitle={t("csi.labels.nologgedinuser_sub")}
/>
</Layout>
);
if (error || !!!data.csi_by_pk)
return (
<div>
<Result
status="error"
title={t("csi.errors.notfoundtitle")}
subTitle={t("csi.errors.notfoundsubtitle")}
>
{error ? (
<div>ERROR: {error.graphQLErrors.map((e) => e.message)}</div>
) : null}
</Result>
</div>
);
return (
<Layout
style={{ height: "100vh", display: "flex", flexDirection: "column" }}
>
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<div style={{ display: "flex", alignItems: "center", margin: "2em" }}>
{bodyshop.logo_img_path && bodyshop.logo_img_path.src ? (
<img src={bodyshop.logo_img_path.src} alt="Logo" />
) : null}
<div style={{ margin: "2em" }}>
<strong>{bodyshop.shopname || ""}</strong>
<div>{`${bodyshop.address1 || ""}`}</div>
<div>{`${bodyshop.address2 || ""}`}</div>
<div>{`${bodyshop.city || ""} ${bodyshop.state || ""} ${
bodyshop.zip_post || ""
}`}</div>
</div>
</div>
<Typography.Title>{t("csi.labels.title")}</Typography.Title>
<strong>{`Hi ${job.ownr_co_nm || job.ownr_fn || ""}!`}</strong>
<Typography.Paragraph>
{`At ${
bodyshop.shopname || ""
}, we value your feedback. We would love to
hear what you have to say. Please fill out the form below.`}
</Typography.Paragraph>
</div>
const handleFinish = async (values) => {
setSubmitting({...submitting, loading: true});
{submitting.error ? (
<AlertComponent message={submitting.error} type="error" />
) : null}
const result = await completeSurvey({
variables: {
surveyId,
survey: {
response: values,
valid: false,
completedon: new Date(),
},
},
});
{submitting.submitted ? (
<Layout.Content
style={{
backgroundColor: "#fff",
margin: "2em 4em",
padding: "2em",
overflowY: "auto",
}}
>
<Result
status="success"
title={t("csi.successes.submitted")}
subTitle={t("csi.successes.submittedsub")}
/>
</Layout.Content>
) : (
<Layout.Content
style={{
backgroundColor: "#fff",
margin: "2em 4em",
padding: "2em",
overflowY: "auto",
}}
>
<Form form={form} onFinish={handleFinish}>
<ConfigFormComponents componentList={csiquestions} />
<Button
loading={submitting.loading}
type="primary"
htmlType="submit"
if (!!!result.errors) {
setSubmitting({...submitting, loading: false, submitted: true});
} else {
setSubmitting({
...submitting,
loading: false,
error: JSON.stringify(result.errors),
});
}
};
const {
relateddata: {bodyshop, job},
csiquestion: {config: csiquestions},
} = data.csi_by_pk;
if (currentUser && currentUser.authorized)
return (
<Layout
style={{height: "100vh", display: "flex", flexDirection: "column"}}
>
{t("general.actions.submit")}
</Button>
</Form>
</Layout.Content>
)}
<Result
status="error"
title={t("csi.labels.nologgedinuser")}
subTitle={t("csi.labels.nologgedinuser_sub")}
/>
</Layout>
);
<Layout.Footer>
{`Survey ID: ${surveyId}`}
</Layout.Footer>
</Layout>
);
return (
<Layout
style={{height: "100vh", display: "flex", flexDirection: "column"}}
>
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<div style={{display: "flex", alignItems: "center", margin: "2em"}}>
{bodyshop.logo_img_path && bodyshop.logo_img_path.src ? (
<img src={bodyshop.logo_img_path.src} alt="Logo"/>
) : null}
<div style={{margin: "2em"}}>
<strong>{bodyshop.shopname || ""}</strong>
<div>{`${bodyshop.address1 || ""}`}</div>
<div>{`${bodyshop.address2 || ""}`}</div>
<div>{`${bodyshop.city || ""} ${bodyshop.state || ""} ${
bodyshop.zip_post || ""
}`}</div>
</div>
</div>
<Typography.Title>{t("csi.labels.title")}</Typography.Title>
<strong>{`Hi ${job.ownr_co_nm || job.ownr_fn || ""}!`}</strong>
<Typography.Paragraph>
{`At ${
bodyshop.shopname || ""
}, we value your feedback. We would love to
hear what you have to say. Please fill out the form below.`}
</Typography.Paragraph>
</div>
{submitting.error ? (
<AlertComponent message={submitting.error} type="error"/>
) : null}
{submitting.submitted ? (
<Layout.Content
style={{
backgroundColor: "#fff",
margin: "2em 4em",
padding: "2em",
overflowY: "auto",
}}
>
<Result
status="success"
title={t("csi.successes.submitted")}
subTitle={t("csi.successes.submittedsub")}
/>
</Layout.Content>
) : (
<Layout.Content
style={{
backgroundColor: "#fff",
margin: "2em 4em",
padding: "2em",
overflowY: "auto",
}}
>
<Form form={form} onFinish={handleFinish}>
<ConfigFormComponents componentList={csiquestions}/>
<Button
loading={submitting.loading}
type="primary"
htmlType="submit"
>
{t("general.actions.submit")}
</Button>
</Form>
</Layout.Content>
)}
<Layout.Footer>
{`Survey ID: ${surveyId}`}
</Layout.Footer>
</Layout>
);
}

View File

@@ -1,39 +1,37 @@
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import DashboardGridComponent from "../../components/dashboard-grid/dashboard-grid.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component";
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function ExportsLogPageContainer({ setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation();
export function ExportsLogPageContainer({setBreadcrumbs, setSelectedHeader}) {
const {t} = useTranslation();
useEffect(() => {
document.title = t("titles.dashboard");
setSelectedHeader("dashboard");
setBreadcrumbs([
{
link: "/manage/accounting/exportlogs",
label: t("titles.bc.dashboard"),
},
]);
}, [setBreadcrumbs, t, setSelectedHeader]);
useEffect(() => {
document.title = t("titles.dashboard");
setSelectedHeader("dashboard");
setBreadcrumbs([
{
link: "/manage/accounting/exportlogs",
label: t("titles.bc.dashboard"),
},
]);
}, [setBreadcrumbs, t, setSelectedHeader]);
return (
<FeatureWrapper featureName="dashboard">
<RbacWrapper action="shop:dashboard">
<DashboardGridComponent />
</RbacWrapper>
</FeatureWrapper>
);
return (
<FeatureWrapper featureName="dashboard">
<RbacWrapper action="shop:dashboard">
<DashboardGridComponent/>
</RbacWrapper>
</FeatureWrapper>
);
}
export default connect(null, mapDispatchToProps)(ExportsLogPageContainer);

View File

@@ -1,23 +1,23 @@
import React from "react";
import { Typography } from "antd";
import {Typography} from "antd";
export default function AboutPage() {
return (
<div style={{ textAlign: "center", margin: "1rem 0rem" }}>
<Typography.Title
level={2}
>{`Rome Online V.${process.env.NODE_ENV}-${process.env.REACT_APP_GIT_SHA}`}</Typography.Title>
<Typography.Title level={4}>
&copy; 2019 - {new Date().getFullYear()} ImEX Systems Inc. used under
license to Rome Technologies
</Typography.Title>
<Typography.Title level={2}>Third Party Notices</Typography.Title>
<a href="/3rdparty-app.txt">
<Typography.Title level={4}>Application</Typography.Title>
</a>
<a href="/3rdparty-api.txt">
<Typography.Title level={4}>API</Typography.Title>
</a>
</div>
);
return (
<div style={{textAlign: "center", margin: "1rem 0rem"}}>
<Typography.Title
level={2}
>{`Rome Online V.${process.env.NODE_ENV}-${process.env.REACT_APP_GIT_SHA}`}</Typography.Title>
<Typography.Title level={4}>
&copy; 2019 - {new Date().getFullYear()} ImEX Systems Inc. used under
license to Rome Technologies
</Typography.Title>
<Typography.Title level={2}>Third Party Notices</Typography.Title>
<a href="/3rdparty-app.txt">
<Typography.Title level={4}>Application</Typography.Title>
</a>
<a href="/3rdparty-api.txt">
<Typography.Title level={4}>API</Typography.Title>
</a>
</div>
);
}

View File

@@ -1,163 +1,161 @@
import { Button, Card, Col, notification, Row, Select, Space } from "antd";
import React, { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useHistory, useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import {Button, Card, Col, notification, Row, Select, Space} from "antd";
import React, {useEffect, useRef, useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {useLocation, useNavigate} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import SocketIO from "socket.io-client";
import DmsAllocationsSummaryApComponent from "../../components/dms-allocations-summary-ap/dms-allocations-summary-ap.component";
import DmsAllocationsSummaryApComponent
from "../../components/dms-allocations-summary-ap/dms-allocations-summary-ap.component";
import DmsLogEvents from "../../components/dms-log-events/dms-log-events.component";
import { auth } from "../../firebase/firebase.utils";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {auth} from "../../firebase/firebase.utils";
import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import {selectBodyshop} from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export default connect(mapStateToProps, mapDispatchToProps)(DmsContainer);
export const socket = SocketIO(
process.env.NODE_ENV === "production"
? process.env.REACT_APP_AXIOS_BASE_API_URL
: window.location.origin,
{
path: "/ws",
withCredentials: true,
auth: async (callback) => {
const token = auth.currentUser && (await auth.currentUser.getIdToken());
callback({ token });
},
}
process.env.NODE_ENV === "production"
? process.env.REACT_APP_AXIOS_BASE_API_URL
: window.location.origin,
{
path: "/ws",
withCredentials: true,
auth: async (callback) => {
const token = auth.currentUser && (await auth.currentUser.getIdToken());
callback({token});
},
}
);
export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation();
const [logLevel, setLogLevel] = useState("DEBUG");
const history = useHistory();
const [logs, setLogs] = useState([]);
export function DmsContainer({bodyshop, setBreadcrumbs, setSelectedHeader}) {
const {t} = useTranslation();
const [logLevel, setLogLevel] = useState("DEBUG");
const history = useNavigate();
const [logs, setLogs] = useState([]);
const { state } = useLocation();
const {state} = useLocation();
const logsRef = useRef(null);
const logsRef = useRef(null);
useEffect(() => {
document.title = t("titles.dms");
setSelectedHeader("dms");
setBreadcrumbs([
{
link: "/manage/accounting/payables",
label: t("titles.bc.accounting-payables"),
},
{
link: "/manage/dms",
label: t("titles.bc.dms"),
},
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
useEffect(() => {
document.title = t("titles.dms");
setSelectedHeader("dms");
setBreadcrumbs([
{
link: "/manage/accounting/payables",
label: t("titles.bc.accounting-payables"),
},
{
link: "/manage/dms",
label: t("titles.bc.dms"),
},
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
useEffect(() => {
socket.on("connect", () => socket.emit("set-log-level", logLevel));
socket.on("reconnect", () => {
setLogs((logs) => {
return [
...logs,
{
timestamp: new Date(),
level: "WARNING",
message: "Reconnected to CDK Export Service",
},
];
});
});
useEffect(() => {
socket.on("connect", () => socket.emit("set-log-level", logLevel));
socket.on("reconnect", () => {
setLogs((logs) => {
return [
...logs,
{
timestamp: new Date(),
level: "WARNING",
message: "Reconnected to CDK Export Service",
},
];
});
});
socket.on("log-event", (payload) => {
setLogs((logs) => {
return [...logs, payload];
});
});
socket.on("log-event", (payload) => {
setLogs((logs) => {
return [...logs, payload];
});
});
socket.on("ap-export-complete", (payload) => {
notification.open({
type: "success",
message: t("jobs.labels.dms.apexported"),
});
});
socket.on("ap-export-complete", (payload) => {
notification.open({
type: "success",
message: t("jobs.labels.dms.apexported"),
});
});
if (socket.disconnected) socket.connect();
return () => {
socket.removeAllListeners();
socket.disconnect();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (socket.disconnected) socket.connect();
return () => {
socket.removeAllListeners();
socket.disconnect();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (!state?.billids) {
history.push(`/manage/accounting/payables`);
}
if (!state?.billids) {
history(`/manage/accounting/payables`);
}
return (
<Row gutter={[16, 16]}>
<Col md={24} lg={12}>
<DmsAllocationsSummaryApComponent
socket={socket}
billids={state?.billids}
/>
</Col>
return (
<Row gutter={[16, 16]}>
<Col md={24} lg={12}>
<DmsAllocationsSummaryApComponent
socket={socket}
billids={state?.billids}
/>
</Col>
<Col md={24} lg={12}>
<div ref={logsRef}>
<Card
title={t("jobs.labels.dms.logs")}
extra={
<Space wrap>
<Select
placeholder="Log Level"
value={logLevel}
onChange={(value) => {
setLogLevel(value);
socket.emit("set-log-level", value);
}}
>
<Select.Option key="TRACE">TRACE</Select.Option>
<Select.Option key="DEBUG">DEBUG</Select.Option>
<Select.Option key="INFO">INFO</Select.Option>
<Select.Option key="WARNING">WARNING</Select.Option>
<Select.Option key="ERROR">ERROR</Select.Option>
</Select>
<Button onClick={() => setLogs([])}>Clear Logs</Button>
<Button
onClick={() => {
setLogs([]);
<Col md={24} lg={12}>
<div ref={logsRef}>
<Card
title={t("jobs.labels.dms.logs")}
extra={
<Space wrap>
<Select
placeholder="Log Level"
value={logLevel}
onChange={(value) => {
setLogLevel(value);
socket.emit("set-log-level", value);
}}
>
<Select.Option key="TRACE">TRACE</Select.Option>
<Select.Option key="DEBUG">DEBUG</Select.Option>
<Select.Option key="INFO">INFO</Select.Option>
<Select.Option key="WARNING">WARNING</Select.Option>
<Select.Option key="ERROR">ERROR</Select.Option>
</Select>
<Button onClick={() => setLogs([])}>Clear Logs</Button>
<Button
onClick={() => {
setLogs([]);
socket.disconnect();
socket.connect();
}}
>
Reconnect
</Button>
</Space>
}
>
<DmsLogEvents socket={socket} logs={logs} />
</Card>
</div>
</Col>
</Row>
);
socket.disconnect();
socket.connect();
}}
>
Reconnect
</Button>
</Space>
}
>
<DmsLogEvents socket={socket} logs={logs}/>
</Card>
</div>
</Col>
</Row>
);
}
export const determineDmsType = (bodyshop) => {
if (bodyshop.cdk_dealerid) return "cdk";
else {
return "pbs";
}
if (bodyshop.cdk_dealerid) return "cdk";
else {
return "pbs";
}
};

View File

@@ -1,20 +1,11 @@
import { useQuery } from "@apollo/client";
import {
Button,
Card,
Col,
notification,
Result,
Row,
Select,
Space,
} from "antd";
import {useQuery} from "@apollo/client";
import {Button, Card, Col, notification, Result, Row, Select, Space,} from "antd";
import queryString from "query-string";
import React, { useEffect, useRef, 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 React, {useEffect, useRef, useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {Link, useLocation, useNavigate} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import SocketIO from "socket.io-client";
import AlertComponent from "../../components/alert/alert.component";
import DmsAllocationsSummary from "../../components/dms-allocations-summary/dms-allocations-summary.component";
@@ -22,200 +13,197 @@ 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 {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
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 {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import {selectBodyshop} from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export default connect(mapStateToProps, mapDispatchToProps)(DmsContainer);
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,
auth: async (callback) => {
const token = auth.currentUser && (await auth.currentUser.getIdToken());
callback({ token });
},
}
// 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,
auth: async (callback) => {
const token = auth.currentUser && (await auth.currentUser.getIdToken());
callback({token});
},
}
);
export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation();
const [logLevel, setLogLevel] = useState("DEBUG");
const history = useHistory();
const [logs, setLogs] = useState([]);
const search = queryString.parse(useLocation().search);
const { jobId } = search;
export function DmsContainer({bodyshop, setBreadcrumbs, setSelectedHeader}) {
const {t} = useTranslation();
const [logLevel, setLogLevel] = useState("DEBUG");
const history = useNavigate();
const [logs, setLogs] = useState([]);
const search = queryString.parse(useLocation().search);
const {jobId} = search;
const { loading, error, data } = useQuery(QUERY_JOB_EXPORT_DMS, {
variables: { id: jobId },
skip: !jobId,
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
const logsRef = useRef(null);
useEffect(() => {
document.title = t("titles.dms");
setSelectedHeader("dms");
setBreadcrumbs([
{
link: "/manage/accounting/receivables",
label: t("titles.bc.accounting-receivables"),
},
{
link: "/manage/dms",
label: t("titles.bc.dms"),
},
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
useEffect(() => {
socket.on("connect", () => socket.emit("set-log-level", logLevel));
socket.on("reconnect", () => {
setLogs((logs) => {
return [
...logs,
{
timestamp: new Date(),
level: "WARNING",
message: "Reconnected to CDK Export Service",
},
];
});
});
socket.on("connect_error", (err) => {
console.log(`connect_error due to ${err}`, err);
notification.error({ message: err.message });
});
socket.on("log-event", (payload) => {
setLogs((logs) => {
return [...logs, payload];
});
});
socket.on("export-success", (payload) => {
notification.success({
message: t("jobs.successes.exported"),
});
history.push("/manage/accounting/receivables");
const {loading, error, data} = useQuery(QUERY_JOB_EXPORT_DMS, {
variables: {id: jobId},
skip: !jobId,
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
const logsRef = useRef(null);
if (socket.disconnected) socket.connect();
return () => {
socket.removeAllListeners();
socket.disconnect();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
document.title = t("titles.dms");
setSelectedHeader("dms");
setBreadcrumbs([
{
link: "/manage/accounting/receivables",
label: t("titles.bc.accounting-receivables"),
},
{
link: "/manage/dms",
label: t("titles.bc.dms"),
},
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />;
useEffect(() => {
socket.on("connect", () => socket.emit("set-log-level", logLevel));
socket.on("reconnect", () => {
setLogs((logs) => {
return [
...logs,
{
timestamp: new Date(),
level: "WARNING",
message: "Reconnected to CDK Export Service",
},
];
});
});
socket.on("connect_error", (err) => {
console.log(`connect_error due to ${err}`, err);
notification.error({message: err.message});
});
socket.on("log-event", (payload) => {
setLogs((logs) => {
return [...logs, payload];
});
});
socket.on("export-success", (payload) => {
notification.success({
message: t("jobs.successes.exported"),
});
history("/manage/accounting/receivables");
});
if (
!jobId ||
!(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) ||
!(data && data.jobs_by_pk)
)
return <Result status="404" title={t("general.errors.notfound")} />;
if (socket.disconnected) socket.connect();
return () => {
socket.removeAllListeners();
socket.disconnect();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (data.jobs_by_pk && data.jobs_by_pk.date_exported)
return <Result status="warning" title={t("dms.errors.alreadyexported")} />;
if (loading) return <LoadingSpinner/>;
if (error) return <AlertComponent message={error.message} type="error"/>;
return (
<div>
<Row gutter={[16, 16]}>
<Col md={24} lg={10}>
<DmsAllocationsSummary
title={
<span>
if (
!jobId ||
!(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) ||
!(data && data.jobs_by_pk)
)
return <Result status="404" title={t("general.errors.notfound")}/>;
if (data.jobs_by_pk && data.jobs_by_pk.date_exported)
return <Result status="warning" title={t("dms.errors.alreadyexported")}/>;
return (
<div>
<Row gutter={[16, 16]}>
<Col md={24} lg={10}>
<DmsAllocationsSummary
title={
<span>
<Link to={`/manage/jobs/${data && data.jobs_by_pk.id}`}>{`${
data && data.jobs_by_pk && data.jobs_by_pk.ro_number
data && data.jobs_by_pk && data.jobs_by_pk.ro_number
}`}</Link>
{` | ${OwnerNameDisplayFunction(data.jobs_by_pk)} | ${
data.jobs_by_pk.v_model_yr || ""
} ${data.jobs_by_pk.v_make_desc || ""} ${
data.jobs_by_pk.v_model_desc || ""
}`}
{` | ${OwnerNameDisplayFunction(data.jobs_by_pk)} | ${
data.jobs_by_pk.v_model_yr || ""
} ${data.jobs_by_pk.v_make_desc || ""} ${
data.jobs_by_pk.v_model_desc || ""
}`}
</span>
}
socket={socket}
jobId={jobId}
/>
</Col>
<Col md={24} lg={14}>
<DmsPostForm
socket={socket}
jobId={jobId}
job={data && data.jobs_by_pk}
logsRef={logsRef}
/>
</Col>
}
socket={socket}
jobId={jobId}
/>
</Col>
<Col md={24} lg={14}>
<DmsPostForm
socket={socket}
jobId={jobId}
job={data && data.jobs_by_pk}
logsRef={logsRef}
/>
</Col>
<DmsCustomerSelector />
<DmsCustomerSelector/>
<Col span={24}>
<div ref={logsRef}>
<Card
title={t("jobs.labels.dms.logs")}
extra={
<Space wrap>
<Select
placeholder="Log Level"
value={logLevel}
onChange={(value) => {
setLogLevel(value);
socket.emit("set-log-level", value);
}}
>
<Select.Option key="TRACE">TRACE</Select.Option>
<Select.Option key="DEBUG">DEBUG</Select.Option>
<Select.Option key="INFO">INFO</Select.Option>
<Select.Option key="WARNING">WARNING</Select.Option>
<Select.Option key="ERROR">ERROR</Select.Option>
</Select>
<Button onClick={() => setLogs([])}>Clear Logs</Button>
<Button
onClick={() => {
setLogs([]);
socket.disconnect();
socket.connect();
}}
>
Reconnect
</Button>
</Space>
}
>
<DmsLogEvents socket={socket} logs={logs} />
</Card>
</div>
</Col>
</Row>
</div>
);
<Col span={24}>
<div ref={logsRef}>
<Card
title={t("jobs.labels.dms.logs")}
extra={
<Space wrap>
<Select
placeholder="Log Level"
value={logLevel}
onChange={(value) => {
setLogLevel(value);
socket.emit("set-log-level", value);
}}
>
<Select.Option key="TRACE">TRACE</Select.Option>
<Select.Option key="DEBUG">DEBUG</Select.Option>
<Select.Option key="INFO">INFO</Select.Option>
<Select.Option key="WARNING">WARNING</Select.Option>
<Select.Option key="ERROR">ERROR</Select.Option>
</Select>
<Button onClick={() => setLogs([])}>Clear Logs</Button>
<Button
onClick={() => {
setLogs([]);
socket.disconnect();
socket.connect();
}}
>
Reconnect
</Button>
</Space>
}
>
<DmsLogEvents socket={socket} logs={logs}/>
</Card>
</div>
</Col>
</Row>
</div>
);
}
export const determineDmsType = (bodyshop) => {
if (bodyshop.cdk_dealerid) return "cdk";
else {
return "pbs";
}
if (bodyshop.cdk_dealerid) return "cdk";
else {
return "pbs";
}
};

View File

@@ -1,197 +1,197 @@
import { SyncOutlined } from "@ant-design/icons";
import { useQuery } from "@apollo/client";
import { Button, Card, Checkbox, Input, Space, Table, Typography } from "antd";
import {SyncOutlined} from "@ant-design/icons";
import {useQuery} from "@apollo/client";
import {Button, Card, Checkbox, Input, Space, Table, Typography} from "antd";
import _ from "lodash";
import queryString from "query-string";
import React 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 {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {Link, useLocation, useNavigate} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import AlertComponent from "../../components/alert/alert.component";
import { QUERY_EXPORT_LOG_PAGINATED } from "../../graphql/accounting.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import {QUERY_EXPORT_LOG_PAGINATED} from "../../graphql/accounting.queries";
import {selectBodyshop} from "../../redux/user/user.selectors";
import {DateTimeFormatter} from "../../utils/DateFormatter";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
bodyshop: selectBodyshop,
});
export function ExportLogsPageComponent({ bodyshop }) {
const searchParams = queryString.parse(useLocation().search);
const { page, sortcolumn, sortorder, search } = searchParams;
const history = useHistory();
export function ExportLogsPageComponent({bodyshop}) {
const searchParams = queryString.parse(useLocation().search);
const {page, sortcolumn, sortorder, search} = searchParams;
const history = useNavigate();
const { loading, error, data, refetch } = useQuery(
QUERY_EXPORT_LOG_PAGINATED,
{
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
variables: {
search: search || "",
offset: page ? (page - 1) * pageLimit : 0,
limit: pageLimit,
order: [
{
[sortcolumn || "created_at"]: sortorder
? sortorder === "descend"
? "desc"
: "asc"
: "desc",
},
],
},
}
);
const {loading, error, data, refetch} = useQuery(
QUERY_EXPORT_LOG_PAGINATED,
{
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
variables: {
search: search || "",
offset: page ? (page - 1) * pageLimit : 0,
limit: pageLimit,
order: [
{
[sortcolumn || "created_at"]: sortorder
? sortorder === "descend"
? "desc"
: "asc"
: "desc",
},
],
},
}
);
const { t } = useTranslation();
const {t} = useTranslation();
if (error) return <AlertComponent message={error.message} type="error" />;
if (error) return <AlertComponent message={error.message} type="error"/>;
const handleTableChange = (pagination, filters, sorter) => {
searchParams.page = pagination.current;
searchParams.sortcolumn = sorter.columnKey;
searchParams.sortorder = sorter.order;
if (filters.status) {
searchParams.statusFilters = JSON.stringify(
_.flattenDeep(filters.status)
);
} else {
delete searchParams.statusFilters;
}
history.push({ search: queryString.stringify(searchParams) });
};
const handleTableChange = (pagination, filters, sorter) => {
searchParams.page = pagination.current;
searchParams.sortcolumn = sorter.columnKey;
searchParams.sortorder = sorter.order;
if (filters.status) {
searchParams.statusFilters = JSON.stringify(
_.flattenDeep(filters.status)
);
} else {
delete searchParams.statusFilters;
}
history({search: queryString.stringify(searchParams)});
};
const columns = [
{
title: t("general.labels.created_at"),
dataIndex: "created_at",
key: "created_at",
render: (text, record) => (
<DateTimeFormatter>{record.created_at}</DateTimeFormatter>
),
},
{
title: t("employees.fields.user_email"),
dataIndex: "useremail",
key: "useremail",
},
{
title: t("jobs.fields.ro_number"),
dataIndex: "ro_number",
key: "ro_number",
const columns = [
{
title: t("general.labels.created_at"),
dataIndex: "created_at",
key: "created_at",
render: (text, record) => (
<DateTimeFormatter>{record.created_at}</DateTimeFormatter>
),
},
{
title: t("employees.fields.user_email"),
dataIndex: "useremail",
key: "useremail",
},
{
title: t("jobs.fields.ro_number"),
dataIndex: "ro_number",
key: "ro_number",
render: (text, record) =>
record.job && (
<Link to={`/manage/jobs/${record.job.id}`}>
{(record.job && record.job.ro_number) || t("general.labels.na")}
</Link>
),
},
{
title: t("bills.fields.invoice_number"),
dataIndex: "invoice_number",
key: "invoice_number",
render: (text, record) =>
record.bill && (
<Link to={"/manage/bills?billid=" + (record.bill && record.bill.id)}>
{record.bill && record.bill.invoice_number}
</Link>
),
},
{
title: t("payments.fields.paymentnum"),
dataIndex: "paymentnum",
key: "paymentnum",
render: (text, record) =>
record.payment && (
<Link
to={
"/manage/payments?search=" +
(record.payment && record.payment.paymentnum)
render: (text, record) =>
record.job && (
<Link to={`/manage/jobs/${record.job.id}`}>
{(record.job && record.job.ro_number) || t("general.labels.na")}
</Link>
),
},
{
title: t("bills.fields.invoice_number"),
dataIndex: "invoice_number",
key: "invoice_number",
render: (text, record) =>
record.bill && (
<Link to={"/manage/bills?billid=" + (record.bill && record.bill.id)}>
{record.bill && record.bill.invoice_number}
</Link>
),
},
{
title: t("payments.fields.paymentnum"),
dataIndex: "paymentnum",
key: "paymentnum",
render: (text, record) =>
record.payment && (
<Link
to={
"/manage/payments?search=" +
(record.payment && record.payment.paymentnum)
}
>
{record.payment && record.payment.paymentnum}
</Link>
),
},
{
title: t("general.labels.successful"),
dataIndex: "successful",
key: "successful",
render: (text, record) => (
<Checkbox disabled checked={record.successful}/>
),
},
{
title: t("general.labels.message"),
dataIndex: "message",
key: "message",
render: (text, record) =>
record.message && (
<div>
<ul>
{JSON.parse(record.message).map((m, idx) => (
<li key={idx}>{m}</li>
))}
</ul>
</div>
),
},
];
return (
<Card
extra={
<Space wrap>
{searchParams.search && (
<>
<Typography.Title level={4}>
{t("general.labels.searchresults", {
search: searchParams.search,
})}
</Typography.Title>
<Button
onClick={() => {
delete searchParams.search;
history({search: queryString.stringify(searchParams)});
}}
>
{t("general.actions.clear")}
</Button>
</>
)}
<Button onClick={() => refetch()}>
<SyncOutlined/>
</Button>
<Input.Search
placeholder={searchParams.search || t("general.labels.search")}
onSearch={(value) => {
searchParams.search = value;
history({search: queryString.stringify(searchParams)});
}}
/>
</Space>
}
>
{record.payment && record.payment.paymentnum}
</Link>
),
},
{
title: t("general.labels.successful"),
dataIndex: "successful",
key: "successful",
render: (text, record) => (
<Checkbox disabled checked={record.successful} />
),
},
{
title: t("general.labels.message"),
dataIndex: "message",
key: "message",
render: (text, record) =>
record.message && (
<div>
<ul>
{JSON.parse(record.message).map((m, idx) => (
<li key={idx}>{m}</li>
))}
</ul>
</div>
),
},
];
return (
<Card
extra={
<Space wrap>
{searchParams.search && (
<>
<Typography.Title level={4}>
{t("general.labels.searchresults", {
search: searchParams.search,
})}
</Typography.Title>
<Button
onClick={() => {
delete searchParams.search;
history.push({ search: queryString.stringify(searchParams) });
>
<Table
loading={loading}
pagination={{
position: "top",
pageSize: pageLimit,
current: parseInt(page || 1),
total: data && data.search_exportlog_aggregate.aggregate.count,
}}
>
{t("general.actions.clear")}
</Button>
</>
)}
<Button onClick={() => refetch()}>
<SyncOutlined />
</Button>
<Input.Search
placeholder={searchParams.search || t("general.labels.search")}
onSearch={(value) => {
searchParams.search = value;
history.push({ search: queryString.stringify(searchParams) });
}}
/>
</Space>
}
>
<Table
loading={loading}
pagination={{
position: "top",
pageSize: pageLimit,
current: parseInt(page || 1),
total: data && data.search_exportlog_aggregate.aggregate.count,
}}
columns={columns}
rowKey="id"
dataSource={data && data.search_exportlog}
style={{ height: "100%" }}
scroll={{ x: true }}
onChange={handleTableChange}
/>
</Card>
);
columns={columns}
rowKey="id"
dataSource={data && data.search_exportlog}
style={{height: "100%"}}
scroll={{x: true}}
onChange={handleTableChange}
/>
</Card>
);
}
export default connect(mapStateToProps, null)(ExportLogsPageComponent);

View File

@@ -1,36 +1,34 @@
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import ExportLogsPage from "./export-logs.page.component";
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function ExportsLogPageContainer({ setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation();
export function ExportsLogPageContainer({setBreadcrumbs, setSelectedHeader}) {
const {t} = useTranslation();
useEffect(() => {
document.title = t("titles.export-logs");
setSelectedHeader("export-logs");
setBreadcrumbs([
{
link: "/manage/accounting/exportlogs",
label: t("titles.bc.export-logs"),
},
]);
}, [setBreadcrumbs, t, setSelectedHeader]);
useEffect(() => {
document.title = t("titles.export-logs");
setSelectedHeader("export-logs");
setBreadcrumbs([
{
link: "/manage/accounting/exportlogs",
label: t("titles.bc.export-logs"),
},
]);
}, [setBreadcrumbs, t, setSelectedHeader]);
return (
<RbacWrapper action="accounting:exportlogs">
<ExportLogsPage />
</RbacWrapper>
);
return (
<RbacWrapper action="accounting:exportlogs">
<ExportLogsPage/>
</RbacWrapper>
);
}
export default connect(null, mapDispatchToProps)(ExportsLogPageContainer);

View File

@@ -2,9 +2,9 @@ import React from "react";
import HelpRescue from "../../components/help-rescue/help-rescue.component";
export default function HelpPage() {
return (
<div>
<HelpRescue />
</div>
);
return (
<div>
<HelpRescue/>
</div>
);
}

View File

@@ -1,34 +1,31 @@
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import InventoryList from "../../components/inventory-list/inventory-list.container";
import InventoryUpsertModalContainer from "../../components/inventory-upsert-modal/inventory-upsert-modal.container";
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function InventoryPage({ setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation();
export function InventoryPage({setBreadcrumbs, setSelectedHeader}) {
const {t} = useTranslation();
useEffect(() => {
document.title = t("titles.inventory");
setSelectedHeader("inventory");
setBreadcrumbs([{ link: "/manage/jobs", label: t("titles.bc.inventory") }]);
}, [t, setBreadcrumbs, setSelectedHeader]);
useEffect(() => {
document.title = t("titles.inventory");
setSelectedHeader("inventory");
setBreadcrumbs([{link: "/manage/jobs", label: t("titles.bc.inventory")}]);
}, [t, setBreadcrumbs, setSelectedHeader]);
return (
<RbacWrapper action="inventory:list">
<InventoryUpsertModalContainer />
<InventoryList />
</RbacWrapper>
);
return (
<RbacWrapper action="inventory:list">
<InventoryUpsertModalContainer/>
<InventoryList/>
</RbacWrapper>
);
}
export default connect(null, mapDispatchToProps)(InventoryPage);

View File

@@ -1,9 +1,9 @@
import { useQuery } from "@apollo/client";
import { Card, Col, Result, Row, Space, Typography } from "antd";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useParams } from "react-router-dom";
import {useQuery} from "@apollo/client";
import {Card, Col, Result, Row, Space, Typography} from "antd";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {useParams} from "react-router-dom";
import AlertComponent from "../../components/alert/alert.component";
import JobCalculateTotals from "../../components/job-calculate-totals/job-calculate-totals.component";
import ScoreboardAddButton from "../../components/job-scoreboard-add-button/job-scoreboard-add-button.component";
@@ -11,125 +11,125 @@ import JobsAdminClass from "../../components/jobs-admin-class/jobs-admin-class.c
import JobsAdminDatesChange from "../../components/jobs-admin-dates/jobs-admin-dates.component";
import JobsAdminDeleteIntake from "../../components/jobs-admin-delete-intake/jobs-admin-delete-intake.component";
import JobsAdminMarkReexport from "../../components/jobs-admin-mark-reexport/jobs-admin-mark-reexport.component";
import JobAdminOwnerReassociate from "../../components/jobs-admin-owner-reassociate/jobs-admin-owner-reassociate.component";
import JobAdminOwnerReassociate
from "../../components/jobs-admin-owner-reassociate/jobs-admin-owner-reassociate.component";
import JobsAdminUnvoid from "../../components/jobs-admin-unvoid/jobs-admin-unvoid.component";
import JobAdminVehicleReassociate from "../../components/jobs-admin-vehicle-reassociate/jobs-admin-vehicle-reassociate.component";
import JobAdminVehicleReassociate
from "../../components/jobs-admin-vehicle-reassociate/jobs-admin-vehicle-reassociate.component";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import JobsAdminStatus from "../../components/jobs-admin-change-status/jobs-admin-change.status.component";
import NotFound from "../../components/not-found/not-found.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { GET_JOB_BY_PK } from "../../graphql/jobs.queries";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import {GET_JOB_BY_PK} from "../../graphql/jobs.queries";
import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
const colSpan = {
sm: { span: 24 },
md: { span: 12 },
lg: { span: 8 },
xl: { span: 6 },
sm: {span: 24},
md: {span: 12},
lg: {span: 8},
xl: {span: 6},
};
const cardStyle = {
height: "100%",
height: "100%",
};
export function JobsCloseContainer({ setBreadcrumbs, setSelectedHeader }) {
const { jobId } = useParams();
const { loading, error, data } = useQuery(GET_JOB_BY_PK, {
variables: { id: jobId },
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
const { t } = useTranslation();
useEffect(() => {
setSelectedHeader("activejobs");
document.title = t("titles.jobs-admin", {
ro_number: data ? data.jobs_by_pk && data.jobs_by_pk.ro_number : null,
export function JobsCloseContainer({setBreadcrumbs, setSelectedHeader}) {
const {jobId} = useParams();
const {loading, error, data} = useQuery(GET_JOB_BY_PK, {
variables: {id: jobId},
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
const {t} = useTranslation();
useEffect(() => {
setSelectedHeader("activejobs");
document.title = t("titles.jobs-admin", {
ro_number: data ? data.jobs_by_pk && data.jobs_by_pk.ro_number : null,
});
setBreadcrumbs([
{
link: `/manage/jobs/`,
label: t("titles.bc.jobs"),
},
{
link: `/manage/jobs/${jobId}/`,
label: t("titles.bc.jobs-detail", {
number: data ? data.jobs_by_pk && data.jobs_by_pk.ro_number : null,
}),
},
{
link: `/manage/jobs/${jobId}/admin`,
label: t("titles.bc.jobs-admin"),
},
]);
}, [setBreadcrumbs, t, jobId, data, setSelectedHeader]);
setBreadcrumbs([
{
link: `/manage/jobs/`,
label: t("titles.bc.jobs"),
},
{
link: `/manage/jobs/${jobId}/`,
label: t("titles.bc.jobs-detail", {
number: data ? data.jobs_by_pk && data.jobs_by_pk.ro_number : null,
}),
},
{
link: `/manage/jobs/${jobId}/admin`,
label: t("titles.bc.jobs-admin"),
},
]);
}, [setBreadcrumbs, t, jobId, data, setSelectedHeader]);
if (loading) return <LoadingSpinner/>;
if (error) return <AlertComponent message={error.message} type="error"/>;
if (!!!data.jobs_by_pk) return <NotFound/>;
if (!data.jobs_by_pk.job_totals)
return (
<Result
title={t("jobs.errors.nofinancial")}
extra={<JobCalculateTotals job={data.jobs_by_pk}/>}
/>
);
if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />;
if (!!!data.jobs_by_pk) return <NotFound />;
if (!data.jobs_by_pk.job_totals)
return (
<Result
title={t("jobs.errors.nofinancial")}
extra={<JobCalculateTotals job={data.jobs_by_pk} />}
/>
<RbacWrapper action="jobs:admin">
<Typography.Title level={4} style={{color: "tomato"}}>
{t("jobs.labels.adminwarning")}
</Typography.Title>
<Row gutter={[16, 16]}>
<Col {...colSpan}>
<Card style={cardStyle}>
<Space wrap>
<ScoreboardAddButton
job={data ? data.jobs_by_pk : {}}
disabled={
(data && data.jobs_by_pk.voided) ||
(data.jobs_by_pk && !data.jobs_by_pk.converted)
}
/>
<JobsAdminDeleteIntake job={data ? data.jobs_by_pk : {}}/>
<JobsAdminMarkReexport job={data ? data.jobs_by_pk : {}}/>
<JobsAdminUnvoid job={data ? data.jobs_by_pk : {}}/>
<JobsAdminStatus job={data ? data.jobs_by_pk : {}}/>
</Space>
</Card>
</Col>
<Col {...colSpan}>
<Card style={cardStyle}>
<JobsAdminClass job={data ? data.jobs_by_pk : {}}/>
</Card>
</Col>
<Col {...colSpan}>
<Card style={cardStyle}>
<JobAdminOwnerReassociate job={data ? data.jobs_by_pk : {}}/>
</Card>
</Col>
<Col {...colSpan}>
<Card style={cardStyle}>
<JobAdminVehicleReassociate job={data ? data.jobs_by_pk : {}}/>
</Card>
</Col>
<Col span={24}>
<Card style={cardStyle}>
<JobsAdminDatesChange job={data ? data.jobs_by_pk : {}}/>
</Card>
</Col>
</Row>
</RbacWrapper>
);
return (
<RbacWrapper action="jobs:admin">
<Typography.Title level={4} style={{ color: "tomato" }}>
{t("jobs.labels.adminwarning")}
</Typography.Title>
<Row gutter={[16, 16]}>
<Col {...colSpan}>
<Card style={cardStyle}>
<Space wrap>
<ScoreboardAddButton
job={data ? data.jobs_by_pk : {}}
disabled={
(data && data.jobs_by_pk.voided) ||
(data.jobs_by_pk && !data.jobs_by_pk.converted)
}
/>
<JobsAdminDeleteIntake job={data ? data.jobs_by_pk : {}} />
<JobsAdminMarkReexport job={data ? data.jobs_by_pk : {}} />
<JobsAdminUnvoid job={data ? data.jobs_by_pk : {}} />
<JobsAdminStatus job={data ? data.jobs_by_pk : {}} />
</Space>
</Card>
</Col>
<Col {...colSpan}>
<Card style={cardStyle}>
<JobsAdminClass job={data ? data.jobs_by_pk : {}} />
</Card>
</Col>
<Col {...colSpan}>
<Card style={cardStyle}>
<JobAdminOwnerReassociate job={data ? data.jobs_by_pk : {}} />
</Card>
</Col>
<Col {...colSpan}>
<Card style={cardStyle}>
<JobAdminVehicleReassociate job={data ? data.jobs_by_pk : {}} />
</Card>
</Col>
<Col span={24}>
<Card style={cardStyle}>
<JobsAdminDatesChange job={data ? data.jobs_by_pk : {}} />
</Card>
</Col>
</Row>
</RbacWrapper>
);
}
export default connect(null, mapDispatchToProps)(JobsCloseContainer);

View File

@@ -1,77 +1,74 @@
import { useQuery } from "@apollo/client";
import {useQuery} from "@apollo/client";
import queryString from "query-string";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {useLocation} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import AlertComponent from "../../components/alert/alert.component";
import JobsListPaginated from "../../components/jobs-list-paginated/jobs-list-paginated.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED } from "../../graphql/jobs.queries";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import {QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED} from "../../graphql/jobs.queries";
import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({
//bodyshop: selectBodyshop,
//bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function AllJobs({ setBreadcrumbs, setSelectedHeader }) {
const searchParams = queryString.parse(useLocation().search);
const { page, sortcolumn, sortorder, statusFilters } = searchParams;
export function AllJobs({setBreadcrumbs, setSelectedHeader}) {
const searchParams = queryString.parse(useLocation().search);
const {page, sortcolumn, sortorder, statusFilters} = searchParams;
const { loading, error, data, refetch } = useQuery(
QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED,
{
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
variables: {
offset: page ? (page - 1) * pageLimit : 0,
limit: pageLimit,
...(statusFilters ? { statusList: JSON.parse(statusFilters) } : {}),
order: [
{
[sortcolumn || "ro_number"]:
sortorder && sortorder !== "false"
? (sortorder === "descend"
? "desc"
: "asc")
: "desc",
},
],
},
}
);
const { t } = useTranslation();
const {loading, error, data, refetch} = useQuery(
QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED,
{
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
variables: {
offset: page ? (page - 1) * pageLimit : 0,
limit: pageLimit,
...(statusFilters ? {statusList: JSON.parse(statusFilters)} : {}),
order: [
{
[sortcolumn || "ro_number"]:
sortorder && sortorder !== "false"
? (sortorder === "descend"
? "desc"
: "asc")
: "desc",
},
],
},
}
);
const {t} = useTranslation();
useEffect(() => {
document.title = t("titles.jobs-all");
setSelectedHeader("alljobs");
setBreadcrumbs([
{ link: "/manage/jobs/all", label: t("titles.bc.jobs-all") },
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
useEffect(() => {
document.title = t("titles.jobs-all");
setSelectedHeader("alljobs");
setBreadcrumbs([
{link: "/manage/jobs/all", label: t("titles.bc.jobs-all")},
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
if (error) return <AlertComponent message={error.message} type="error" />;
return (
<RbacWrapper action="jobs:list-all">
<JobsListPaginated
refetch={refetch}
loading={loading}
searchParams={searchParams}
total={data ? data.jobs_aggregate.aggregate.count : 0}
jobs={data ? data.jobs : []}
/>
</RbacWrapper>
);
if (error) return <AlertComponent message={error.message} type="error"/>;
return (
<RbacWrapper action="jobs:list-all">
<JobsListPaginated
refetch={refetch}
loading={loading}
searchParams={searchParams}
total={data ? data.jobs_aggregate.aggregate.count : 0}
jobs={data ? data.jobs : []}
/>
</RbacWrapper>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(AllJobs);

View File

@@ -1,65 +1,65 @@
import { Button, PageHeader } from "antd";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import {Button} from "antd";
import {PageHeader} from "@ant-design/pro-layout";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {Link} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import AlertComponent from "../../components/alert/alert.component";
import JobsAvailableTableContainer from "../../components/jobs-available-table/jobs-available-table.container";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import { selectPartnerVersion } from "../../redux/application/application.selectors";
import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import {selectPartnerVersion} from "../../redux/application/application.selectors";
const mapStateToProps = createStructuredSelector({
partnerVersion: selectPartnerVersion,
partnerVersion: selectPartnerVersion,
});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function JobsAvailablePageContainer({
partnerVersion,
setBreadcrumbs,
setSelectedHeader,
}) {
const { t } = useTranslation();
partnerVersion,
setBreadcrumbs,
setSelectedHeader,
}) {
const {t} = useTranslation();
useEffect(() => {
document.title = t("titles.jobsavailable");
setSelectedHeader("availablejobs");
setBreadcrumbs([
{ link: "/manage/available", label: t("titles.bc.availablejobs") },
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
useEffect(() => {
document.title = t("titles.jobsavailable");
setSelectedHeader("availablejobs");
setBreadcrumbs([
{link: "/manage/available", label: t("titles.bc.availablejobs")},
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
return (
<RbacWrapper action="jobs:available-list">
<div>
<PageHeader
title={t("titles.bc.availablejobs")}
extra={
<Link to="/manage/jobs/new">
<Button>{t("jobs.actions.manualnew")}</Button>
</Link>
}
/>
{!partnerVersion && (
<AlertComponent
type="warning"
message={t("general.messages.partnernotrunning")}
/>
)}
<JobsAvailableTableContainer />
</div>
</RbacWrapper>
);
return (
<RbacWrapper action="jobs:available-list">
<div>
<PageHeader
title={t("titles.bc.availablejobs")}
extra={
<Link to="/manage/jobs/new">
<Button>{t("jobs.actions.manualnew")}</Button>
</Link>
}
/>
{!partnerVersion && (
<AlertComponent
type="warning"
message={t("general.messages.partnernotrunning")}
/>
)}
<JobsAvailableTableContainer/>
</div>
</RbacWrapper>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
mapStateToProps,
mapDispatchToProps
)(JobsAvailablePageContainer);

View File

@@ -1,126 +1,125 @@
import { useQuery } from "@apollo/client";
import { Col, Row, Typography } from "antd";
import moment from "moment";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useParams } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import {useQuery} from "@apollo/client";
import {Col, Row, Typography} from "antd";
import dayjs from "../../utils/day";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {useParams} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import AlertComponent from "../../components/alert/alert.component";
import JobChecklistForm from "../../components/job-checklist/components/job-checklist-form/job-checklist-form.component";
import JobChecklistForm
from "../../components/job-checklist/components/job-checklist-form/job-checklist-form.component";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { QUERY_JOB_CHECKLISTS } from "../../graphql/jobs.queries";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {QUERY_JOB_CHECKLISTS} from "../../graphql/jobs.queries";
import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import {selectBodyshop} from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function JobsChecklistViewContainer({
bodyshop,
setBreadcrumbs,
setSelectedHeader,
}) {
const { t } = useTranslation();
const { jobId } = useParams();
const { loading, error, data } = useQuery(QUERY_JOB_CHECKLISTS, {
variables: { id: jobId },
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
bodyshop,
setBreadcrumbs,
setSelectedHeader,
}) {
const {t} = useTranslation();
const {jobId} = useParams();
const {loading, error, data} = useQuery(QUERY_JOB_CHECKLISTS, {
variables: {id: jobId},
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
useEffect(() => {
document.title = t("titles.jobs-checklist");
setSelectedHeader("activejobs");
setBreadcrumbs([
{ link: "/manage/jobs", label: t("titles.bc.jobs") },
{
link: `/manage/jobs/${jobId}`,
label: t("titles.bc.jobs-detail", {
number: (data && data.jobs_by_pk && data.jobs_by_pk.ro_number) || "",
}),
},
{
link: `/manage/jobs/${jobId}/checklist`,
label: t("titles.bc.jobs-checklist"),
},
]);
}, [t, setBreadcrumbs, jobId, data, setSelectedHeader]);
useEffect(() => {
document.title = t("titles.jobs-checklist");
setSelectedHeader("activejobs");
setBreadcrumbs([
{link: "/manage/jobs", label: t("titles.bc.jobs")},
{
link: `/manage/jobs/${jobId}`,
label: t("titles.bc.jobs-detail", {
number: (data && data.jobs_by_pk && data.jobs_by_pk.ro_number) || "",
}),
},
{
link: `/manage/jobs/${jobId}/checklist`,
label: t("titles.bc.jobs-checklist"),
},
]);
}, [t, setBreadcrumbs, jobId, data, setSelectedHeader]);
if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />;
if (loading) return <LoadingSpinner/>;
if (error) return <AlertComponent message={error.message} type="error"/>;
//The Form is the actual config to use.
//The Form is the actual config to use.
const CompletedBy = ({ checklist }) => (
<div>
<div>
{t("jobs.labels.checklistcompletedby", {
by: checklist.completed_by,
at: moment(checklist.completed_at).format("MM/DD/YYYY @ h:mm a"),
})}
</div>
</div>
);
const CompletedBy = ({checklist}) => (
<div>
<div>
{t("jobs.labels.checklistcompletedby", {
by: checklist.completed_by,
at: dayjs(checklist.completed_at).format("MM/DD/YYYY @ h:mm a"),
})}
</div>
</div>
);
return (
<RbacWrapper action="jobs:checklist-view">
<Row gutter={[16, 16]}>
<Col span={12}>
<Typography.Title level={4}>
{t("jobs.labels.intakechecklist")}
</Typography.Title>
{data.jobs_by_pk.intakechecklist &&
data.jobs_by_pk.intakechecklist.form && (
<>
<JobChecklistForm
formItems={
data.jobs_by_pk.intakechecklist &&
data.jobs_by_pk.intakechecklist.form
}
type="intake"
job={data.jobs_by_pk}
readOnly
/>
<CompletedBy checklist={data.jobs_by_pk.intakechecklist} />
</>
)}
</Col>
<Col span={12}>
<Typography.Title level={4}>
{t("jobs.labels.deliverchecklist")}
</Typography.Title>
{data.jobs_by_pk.deliverchecklist &&
data.jobs_by_pk.deliverchecklist.form && (
<>
<JobChecklistForm
formItems={
data.jobs_by_pk.deliverchecklist &&
data.jobs_by_pk.deliverchecklist.form
}
type="deliver"
job={data.jobs_by_pk}
readOnly
/>
<CompletedBy checklist={data.jobs_by_pk.deliverchecklist} />
</>
)}
</Col>
</Row>
</RbacWrapper>
);
return (
<RbacWrapper action="jobs:checklist-view">
<Row gutter={[16, 16]}>
<Col span={12}>
<Typography.Title level={4}>
{t("jobs.labels.intakechecklist")}
</Typography.Title>
{data.jobs_by_pk.intakechecklist &&
data.jobs_by_pk.intakechecklist.form && (
<>
<JobChecklistForm
formItems={
data.jobs_by_pk.intakechecklist &&
data.jobs_by_pk.intakechecklist.form
}
type="intake"
job={data.jobs_by_pk}
readOnly
/>
<CompletedBy checklist={data.jobs_by_pk.intakechecklist}/>
</>
)}
</Col>
<Col span={12}>
<Typography.Title level={4}>
{t("jobs.labels.deliverchecklist")}
</Typography.Title>
{data.jobs_by_pk.deliverchecklist &&
data.jobs_by_pk.deliverchecklist.form && (
<>
<JobChecklistForm
formItems={
data.jobs_by_pk.deliverchecklist &&
data.jobs_by_pk.deliverchecklist.form
}
type="deliver"
job={data.jobs_by_pk}
readOnly
/>
<CompletedBy checklist={data.jobs_by_pk.deliverchecklist}/>
</>
)}
</Col>
</Row>
</RbacWrapper>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
mapStateToProps,
mapDispatchToProps
)(JobsChecklistViewContainer);

File diff suppressed because it is too large Load Diff

View File

@@ -1,88 +1,85 @@
import { useQuery } from "@apollo/client";
import { Result } from "antd";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useParams } from "react-router-dom";
import {useQuery} from "@apollo/client";
import {Result} from "antd";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {useParams} from "react-router-dom";
import AlertComponent from "../../components/alert/alert.component";
import JobCalculateTotals from "../../components/job-calculate-totals/job-calculate-totals.component";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import NotFound from "../../components/not-found/not-found.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { QUERY_JOB_CLOSE_DETAILS } from "../../graphql/jobs.queries";
import {
setBreadcrumbs,
setJobReadOnly,
setSelectedHeader,
} from "../../redux/application/application.actions";
import {QUERY_JOB_CLOSE_DETAILS} from "../../graphql/jobs.queries";
import {setBreadcrumbs, setJobReadOnly, setSelectedHeader,} from "../../redux/application/application.actions";
import IsJobReadOnly from "../../utils/jobReadOnly";
import JobsCloseComponent from "./jobs-close.component";
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setJobReadOnly: (bool) => dispatch(setJobReadOnly(bool)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setJobReadOnly: (bool) => dispatch(setJobReadOnly(bool)),
});
export function JobsCloseContainer({
setBreadcrumbs,
setSelectedHeader,
setJobReadOnly,
}) {
const { jobId } = useParams();
const { loading, error, data } = useQuery(QUERY_JOB_CLOSE_DETAILS, {
variables: { id: jobId },
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
const { t } = useTranslation();
useEffect(() => {
if (data && data.jobs_by_pk) {
setJobReadOnly(IsJobReadOnly(data.jobs_by_pk));
}
}, [data, setJobReadOnly]);
useEffect(() => {
setSelectedHeader("activejobs");
document.title = t("titles.jobs-close", {
number: data ? data.jobs_by_pk && data.jobs_by_pk.ro_number : null,
setBreadcrumbs,
setSelectedHeader,
setJobReadOnly,
}) {
const {jobId} = useParams();
const {loading, error, data} = useQuery(QUERY_JOB_CLOSE_DETAILS, {
variables: {id: jobId},
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
const {t} = useTranslation();
useEffect(() => {
if (data && data.jobs_by_pk) {
setJobReadOnly(IsJobReadOnly(data.jobs_by_pk));
}
}, [data, setJobReadOnly]);
useEffect(() => {
setSelectedHeader("activejobs");
document.title = t("titles.jobs-close", {
number: data ? data.jobs_by_pk && data.jobs_by_pk.ro_number : null,
});
setBreadcrumbs([
{
link: `/manage/jobs/${jobId}/`,
label: t("titles.bc.jobs"),
},
{
link: `/manage/jobs/${jobId}/`,
label: t("titles.bc.jobs-detail", {
number: data ? data.jobs_by_pk && data.jobs_by_pk.ro_number : null,
}),
},
{
link: `/manage/jobs/${jobId}/close`,
label: t("titles.bc.jobs-close"),
},
]);
}, [setBreadcrumbs, t, jobId, data, setSelectedHeader]);
setBreadcrumbs([
{
link: `/manage/jobs/${jobId}/`,
label: t("titles.bc.jobs"),
},
{
link: `/manage/jobs/${jobId}/`,
label: t("titles.bc.jobs-detail", {
number: data ? data.jobs_by_pk && data.jobs_by_pk.ro_number : null,
}),
},
{
link: `/manage/jobs/${jobId}/close`,
label: t("titles.bc.jobs-close"),
},
]);
}, [setBreadcrumbs, t, jobId, data, setSelectedHeader]);
if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />;
if (!!!data.jobs_by_pk) return <NotFound />;
if (loading) return <LoadingSpinner/>;
if (error) return <AlertComponent message={error.message} type="error"/>;
if (!!!data.jobs_by_pk) return <NotFound/>;
if (!data.jobs_by_pk.job_totals)
return (
<Result
title={t("jobs.errors.nofinancial")}
extra={<JobCalculateTotals job={data.jobs_by_pk}/>}
/>
);
if (!data.jobs_by_pk.job_totals)
return (
<Result
title={t("jobs.errors.nofinancial")}
extra={<JobCalculateTotals job={data.jobs_by_pk} />}
/>
<RbacWrapper action="jobs:close">
<div>
<JobsCloseComponent job={data ? data.jobs_by_pk : {}}/>
</div>
</RbacWrapper>
);
return (
<RbacWrapper action="jobs:close">
<div>
<JobsCloseComponent job={data ? data.jobs_by_pk : {}} />
</div>
</RbacWrapper>
);
}
export default connect(null, mapDispatchToProps)(JobsCloseContainer);

View File

@@ -1,155 +1,158 @@
import { Button, PageHeader, Result, Space, Steps } from "antd";
import React, { useContext, useState } from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import {Button, Result, Space, Steps} from "antd";
import {PageHeader} from "@ant-design/pro-layout";
import React, {useContext, useState} from "react";
import {useTranslation} from "react-i18next";
import {Link} from "react-router-dom";
import AlertComponent from "../../components/alert/alert.component";
import JobsCreateJobsInfo from "../../components/jobs-create-jobs-info/jobs-create-jobs-info.component";
import JobsCreateOwnerInfoContainer from "../../components/jobs-create-owner-info/jobs-create-owner-info.container";
import JobsCreateVehicleInfoContainer from "../../components/jobs-create-vehicle-info/jobs-create-vehicle-info.container";
import JobsCreateVehicleInfoContainer
from "../../components/jobs-create-vehicle-info/jobs-create-vehicle-info.container";
import JobCreateContext from "../../pages/jobs-create/jobs-create.context";
export default function JobsCreateComponent({ form }) {
const [pageIndex, setPageIndex] = useState(0);
const [errorMessage, setErrorMessage] = useState(null);
const [state] = useContext(JobCreateContext);
export default function JobsCreateComponent({form}) {
const [pageIndex, setPageIndex] = useState(0);
const [errorMessage, setErrorMessage] = useState(null);
const [state] = useContext(JobCreateContext);
const { t } = useTranslation();
const steps = [
{
title: t("jobs.labels.create.vehicleinfo"),
content: <JobsCreateVehicleInfoContainer form={form} />,
validation:
!!state.vehicle.new ||
!!state.vehicle.selectedid ||
!!state.vehicle.none,
error: t("vehicles.errors.selectexistingornew"),
},
{
title: t("jobs.labels.create.ownerinfo"),
content: <JobsCreateOwnerInfoContainer />,
validation: !!state.owner.new || !!state.owner.selectedid,
error: t("owners.errors.selectexistingornew"),
},
{
title: t("jobs.labels.create.jobinfo"),
content: <JobsCreateJobsInfo form={form} selected={pageIndex === 2} />,
},
];
const {t} = useTranslation();
const steps = [
{
title: t("jobs.labels.create.vehicleinfo"),
content: <JobsCreateVehicleInfoContainer form={form}/>,
validation:
!!state.vehicle.new ||
!!state.vehicle.selectedid ||
!!state.vehicle.none,
error: t("vehicles.errors.selectexistingornew"),
},
{
title: t("jobs.labels.create.ownerinfo"),
content: <JobsCreateOwnerInfoContainer/>,
validation: !!state.owner.new || !!state.owner.selectedid,
error: t("owners.errors.selectexistingornew"),
},
{
title: t("jobs.labels.create.jobinfo"),
content: <JobsCreateJobsInfo form={form} selected={pageIndex === 2}/>,
},
];
const next = () => {
setPageIndex(pageIndex + 1);
};
const prev = () => {
setPageIndex(pageIndex - 1);
};
const { Step } = Steps;
const next = () => {
setPageIndex(pageIndex + 1);
};
const prev = () => {
setPageIndex(pageIndex - 1);
};
const {Step} = Steps;
const ProgressButtons = ({ top }) => {
return (
<PageHeader
extra={
<Space wrap>
{pageIndex > 0 && <Button onClick={() => prev()}>Previous</Button>}
{pageIndex < steps.length - 1 && (
<Button
type="primary"
onClick={() => {
form
.validateFields()
.then((r) => {
if (steps[pageIndex].validation) {
setErrorMessage(null);
next();
} else {
setErrorMessage(steps[pageIndex].error);
}
})
.catch((error) => console.log("error", error));
}}
>
Next
</Button>
)}
{pageIndex === steps.length - 1 && (
<Button type="primary" htmlType="submit">
Done
</Button>
)}
</Space>
}
>
{top && (
<Steps current={pageIndex}>
{steps.map((item, idx) => (
<Step
key={item.title}
title={item.title}
style={{
cursor: "pointer",
fontWeight: idx === pageIndex && "bolder",
}}
onClick={() => {
form
.validateFields()
.then((r) => {
if (steps[pageIndex].validation) {
setErrorMessage(null);
setPageIndex(idx);
} else {
setErrorMessage(steps[pageIndex].error);
}
})
.catch((error) => console.log("error", error));
}}
/>
))}
</Steps>
)}
</PageHeader>
);
};
return (
<div>
{state.created ? (
<div>
<Result
status="success"
title={t("jobs.successes.creatednoclick")}
extra={[
<Link to={`/manage/jobs/${state.newJobId}`} key="gotojob">
<Button type="primary">{t("jobs.actions.gotojob")}</Button>
</Link>,
<Link to={`/manage/jobs/`} key="gotojoblist">
<Button>{t("menus.header.activejobs")}</Button>
</Link>,
]}
/>
</div>
) : (
<div>
<ProgressButtons top />
{errorMessage ? (
<div>
<AlertComponent message={errorMessage} type="error" />
</div>
) : null}
{steps.map((item, idx) => (
<div
key={idx}
style={{
display: idx === pageIndex ? "" : "none",
margin: "1rem",
}}
const ProgressButtons = ({top}) => {
return (
<PageHeader
extra={
<Space wrap>
{pageIndex > 0 && <Button onClick={() => prev()}>Previous</Button>}
{pageIndex < steps.length - 1 && (
<Button
type="primary"
onClick={() => {
form
.validateFields()
.then((r) => {
if (steps[pageIndex].validation) {
setErrorMessage(null);
next();
} else {
setErrorMessage(steps[pageIndex].error);
}
})
.catch((error) => console.log("error", error));
}}
>
Next
</Button>
)}
{pageIndex === steps.length - 1 && (
<Button type="primary" htmlType="submit">
Done
</Button>
)}
</Space>
}
>
{item.content}
</div>
))}
<ProgressButtons />
{top && (
<Steps current={pageIndex}>
{steps.map((item, idx) => (
<Step
key={item.title}
title={item.title}
style={{
cursor: "pointer",
fontWeight: idx === pageIndex && "bolder",
}}
onClick={() => {
form
.validateFields()
.then((r) => {
if (steps[pageIndex].validation) {
setErrorMessage(null);
setPageIndex(idx);
} else {
setErrorMessage(steps[pageIndex].error);
}
})
.catch((error) => console.log("error", error));
}}
/>
))}
</Steps>
)}
</PageHeader>
);
};
return (
<div>
{state.created ? (
<div>
<Result
status="success"
title={t("jobs.successes.creatednoclick")}
extra={[
<Link to={`/manage/jobs/${state.newJobId}`} key="gotojob">
<Button type="primary">{t("jobs.actions.gotojob")}</Button>
</Link>,
<Link to={`/manage/jobs/`} key="gotojoblist">
<Button>{t("menus.header.activejobs")}</Button>
</Link>,
]}
/>
</div>
) : (
<div>
<ProgressButtons top/>
{errorMessage ? (
<div>
<AlertComponent message={errorMessage} type="error"/>
</div>
) : null}
{steps.map((item, idx) => (
<div
key={idx}
style={{
display: idx === pageIndex ? "" : "none",
margin: "1rem",
}}
>
{item.content}
</div>
))}
<ProgressButtons/>
</div>
)}
</div>
)}
</div>
);
);
}

View File

@@ -1,194 +1,193 @@
import { useLazyQuery, useMutation } from "@apollo/client";
import { Form, notification } from "antd";
import {useLazyQuery, useMutation} from "@apollo/client";
import {Form, notification} from "antd";
import _ from "lodash";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import React, {useEffect, useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { INSERT_NEW_JOB } from "../../graphql/jobs.queries";
import { QUERY_OWNER_FOR_JOB_CREATION } from "../../graphql/owners.queries";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {INSERT_NEW_JOB} from "../../graphql/jobs.queries";
import {QUERY_OWNER_FOR_JOB_CREATION} from "../../graphql/owners.queries";
import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import {selectBodyshop} from "../../redux/user/user.selectors";
import JobsCreateComponent from "./jobs-create.component";
import JobCreateContext from "./jobs-create.context";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
function JobsCreateContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation();
const contextState = useState({
vehicle: {
new: false,
search: "",
selectedid: null,
vehicleObj: null,
none: false,
},
owner: { new: false, search: "", selectedid: null },
job: null,
created: false,
error: null,
newJobId: null,
newJobEstNum: null,
});
const [form] = Form.useForm();
const [state, setState] = contextState;
const [insertJob] = useMutation(INSERT_NEW_JOB);
const [loadOwner, RemoteOwnerData] = useLazyQuery(
QUERY_OWNER_FOR_JOB_CREATION
);
useEffect(() => {
if (!!state.owner.selectedid) {
loadOwner({
variables: { id: state.owner.selectedid },
});
}
}, [state.owner.selectedid, loadOwner]);
useEffect(() => {
document.title = t("titles.jobs-create");
setSelectedHeader("newjob");
setBreadcrumbs([
{ link: "/manage/available", label: t("titles.bc.availablejobs") },
{
link: "/manage/jobs/new",
label: t("titles.bc.jobs-new"),
},
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
const runInsertJob = (job) => {
insertJob({ variables: { job: job } })
.then((resp) => {
setState({
...state,
created: true,
error: null,
newJobId: resp.data.insert_jobs.returning[0].id,
});
})
.catch((error) => {
notification["error"]({
message: t("jobs.errors.creating", { error: error }),
});
setState({ ...state, error: error });
});
};
const handleFinish = (values) => {
let job = Object.assign(
{},
values,
{ date_open: new Date() },
{ date_estimated: new Date() },
{
vehicle:
state.vehicle.selectedid || state.vehicle.none
? null
: values.vehicle,
vehicleid: state.vehicle.selectedid || null,
},
{
owner: state.owner.selectedid ? null : values.owner,
ownerid: state.owner.selectedid || null,
},
{
status: bodyshop.md_ro_statuses.default_imported || "Open*",
shopid: bodyshop.id,
}
function JobsCreateContainer({bodyshop, setBreadcrumbs, setSelectedHeader}) {
const {t} = useTranslation();
const contextState = useState({
vehicle: {
new: false,
search: "",
selectedid: null,
vehicleObj: null,
none: false,
},
owner: {new: false, search: "", selectedid: null},
job: null,
created: false,
error: null,
newJobId: null,
newJobEstNum: null,
});
const [form] = Form.useForm();
const [state, setState] = contextState;
const [insertJob] = useMutation(INSERT_NEW_JOB);
const [loadOwner, RemoteOwnerData] = useLazyQuery(
QUERY_OWNER_FOR_JOB_CREATION
);
let ownerData;
if (!!!job.ownerid) {
ownerData = job.owner.data;
ownerData.shopid = bodyshop.id;
delete ownerData.allow_text_message;
delete ownerData.preferred_contact;
delete job.ownerid;
} else {
ownerData = _.cloneDeep(RemoteOwnerData.data.owners_by_pk);
delete ownerData.id;
delete ownerData.__typename;
}
if (!state.vehicle.none) {
if (!!!job.vehicleid) {
delete job.vehicleid;
job.vehicle.data.shopid = bodyshop.id;
job.plate_no = job.vehicle.data.plate_no;
job.plate_st = job.vehicle.data.plate_st;
job.v_vin = job.vehicle.data.v_vin;
job.v_model_yr = job.vehicle.data.v_model_yr;
job.v_model_desc = job.vehicle.data.v_model_desc;
job.v_make_desc = job.vehicle.data.v_make_desc;
job.v_color = job.vehicle.data.v_color;
} else {
job.plate_no = state.vehicle.vehicleObj.plate_no;
job.plate_st = state.vehicle.vehicleObj.plate_st;
job.v_vin = state.vehicle.vehicleObj.v_vin;
job.v_model_yr = state.vehicle.vehicleObj.v_model_yr;
job.v_model_desc = state.vehicle.vehicleObj.v_model_desc;
job.v_make_desc = state.vehicle.vehicleObj.v_make_desc;
job.v_color = state.vehicle.vehicleObj.v_color;
}
}
useEffect(() => {
if (!!state.owner.selectedid) {
loadOwner({
variables: {id: state.owner.selectedid},
});
}
}, [state.owner.selectedid, loadOwner]);
job = { ...job, ...ownerData };
if (job.owner === null) delete job.owner;
if (job.vehicle === null) delete job.vehicle;
runInsertJob(job);
};
return (
<JobCreateContext.Provider value={contextState}>
<RbacWrapper action="jobs:create">
<Form
scrollToFirstError
form={form}
onFinish={handleFinish}
layout="vertical"
autoComplete={"off"}
initialValues={{
tax_tow_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
tax_str_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
tax_paint_mat_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
tax_shop_mat_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
tax_sub_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
tax_lbr_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
tax_levies_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
federal_tax_rate: bodyshop.bill_tax_rates.federal_tax_rate / 100,
state_tax_rate: bodyshop.bill_tax_rates.state_tax_rate / 100,
local_tax_rate: bodyshop.bill_tax_rates.local_tax_rate / 100,
cieca_pft: {
...bodyshop.md_responsibility_centers.taxes.tax_ty1,
...bodyshop.md_responsibility_centers.taxes.tax_ty2,
...bodyshop.md_responsibility_centers.taxes.tax_ty3,
...bodyshop.md_responsibility_centers.taxes.tax_ty4,
...bodyshop.md_responsibility_centers.taxes.tax_ty5,
useEffect(() => {
document.title = t("titles.jobs-create");
setSelectedHeader("newjob");
setBreadcrumbs([
{link: "/manage/available", label: t("titles.bc.availablejobs")},
{
link: "/manage/jobs/new",
label: t("titles.bc.jobs-new"),
},
materials: bodyshop.md_responsibility_centers.cieca_pfm,
cieca_pfl: bodyshop.md_responsibility_centers.cieca_pfl,
parts_tax_rates: bodyshop.md_responsibility_centers.parts_tax_rates,
}}
>
<JobsCreateComponent form={form} />
</Form>
</RbacWrapper>
</JobCreateContext.Provider>
);
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
const runInsertJob = (job) => {
insertJob({variables: {job: job}})
.then((resp) => {
setState({
...state,
created: true,
error: null,
newJobId: resp.data.insert_jobs.returning[0].id,
});
})
.catch((error) => {
notification["error"]({
message: t("jobs.errors.creating", {error: error}),
});
setState({...state, error: error});
});
};
const handleFinish = (values) => {
let job = Object.assign(
{},
values,
{date_open: new Date()},
{date_estimated: new Date()},
{
vehicle:
state.vehicle.selectedid || state.vehicle.none
? null
: values.vehicle,
vehicleid: state.vehicle.selectedid || null,
},
{
owner: state.owner.selectedid ? null : values.owner,
ownerid: state.owner.selectedid || null,
},
{
status: bodyshop.md_ro_statuses.default_imported || "Open*",
shopid: bodyshop.id,
}
);
let ownerData;
if (!!!job.ownerid) {
ownerData = job.owner.data;
ownerData.shopid = bodyshop.id;
delete ownerData.allow_text_message;
delete ownerData.preferred_contact;
delete job.ownerid;
} else {
ownerData = _.cloneDeep(RemoteOwnerData.data.owners_by_pk);
delete ownerData.id;
delete ownerData.__typename;
}
if (!state.vehicle.none) {
if (!!!job.vehicleid) {
delete job.vehicleid;
job.vehicle.data.shopid = bodyshop.id;
job.plate_no = job.vehicle.data.plate_no;
job.plate_st = job.vehicle.data.plate_st;
job.v_vin = job.vehicle.data.v_vin;
job.v_model_yr = job.vehicle.data.v_model_yr;
job.v_model_desc = job.vehicle.data.v_model_desc;
job.v_make_desc = job.vehicle.data.v_make_desc;
job.v_color = job.vehicle.data.v_color;
} else {
job.plate_no = state.vehicle.vehicleObj.plate_no;
job.plate_st = state.vehicle.vehicleObj.plate_st;
job.v_vin = state.vehicle.vehicleObj.v_vin;
job.v_model_yr = state.vehicle.vehicleObj.v_model_yr;
job.v_model_desc = state.vehicle.vehicleObj.v_model_desc;
job.v_make_desc = state.vehicle.vehicleObj.v_make_desc;
job.v_color = state.vehicle.vehicleObj.v_color;
}
}
job = {...job, ...ownerData};
if (job.owner === null) delete job.owner;
if (job.vehicle === null) delete job.vehicle;
runInsertJob(job);
};
return (
<JobCreateContext.Provider value={contextState}>
<RbacWrapper action="jobs:create">
<Form
scrollToFirstError
form={form}
onFinish={handleFinish}
layout="vertical"
autoComplete={"off"}
initialValues={{
tax_tow_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
tax_str_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
tax_paint_mat_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
tax_shop_mat_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
tax_sub_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
tax_lbr_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
tax_levies_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
federal_tax_rate: bodyshop.bill_tax_rates.federal_tax_rate / 100,
state_tax_rate: bodyshop.bill_tax_rates.state_tax_rate / 100,
local_tax_rate: bodyshop.bill_tax_rates.local_tax_rate / 100,
cieca_pft: {
...bodyshop.md_responsibility_centers.taxes.tax_ty1,
...bodyshop.md_responsibility_centers.taxes.tax_ty2,
...bodyshop.md_responsibility_centers.taxes.tax_ty3,
...bodyshop.md_responsibility_centers.taxes.tax_ty4,
...bodyshop.md_responsibility_centers.taxes.tax_ty5,
},
materials: bodyshop.md_responsibility_centers.cieca_pfm,
cieca_pfl: bodyshop.md_responsibility_centers.cieca_pfl,
parts_tax_rates: bodyshop.md_responsibility_centers.parts_tax_rates,
}}
>
<JobsCreateComponent form={form}/>
</Form>
</RbacWrapper>
</JobCreateContext.Provider>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
mapStateToProps,
mapDispatchToProps
)(JobsCreateContainer);

View File

@@ -1,3 +1,4 @@
import React from "react";
const JobCreateContext = React.createContext(null);
export default JobCreateContext;

View File

@@ -1,82 +1,79 @@
import { useQuery } from "@apollo/client";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useParams } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import {useQuery} from "@apollo/client";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {useParams} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import AlertComponent from "../../components/alert/alert.component";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { QUERY_DELIVER_CHECKLIST } from "../../graphql/bodyshop.queries";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {QUERY_DELIVER_CHECKLIST} from "../../graphql/bodyshop.queries";
import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import {selectBodyshop} from "../../redux/user/user.selectors";
import JobchecklistComponent from "../../components/job-checklist/job-checklist.component";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function JobsDeliverContainer({
bodyshop,
setBreadcrumbs,
setSelectedHeader,
}) {
const { t } = useTranslation();
const { jobId } = useParams();
const { loading, error, data } = useQuery(QUERY_DELIVER_CHECKLIST, {
variables: { shopId: bodyshop.id, jobId: jobId },
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
bodyshop,
setBreadcrumbs,
setSelectedHeader,
}) {
const {t} = useTranslation();
const {jobId} = useParams();
const {loading, error, data} = useQuery(QUERY_DELIVER_CHECKLIST, {
variables: {shopId: bodyshop.id, jobId: jobId},
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
useEffect(() => {
document.title = t("titles.jobs-deliver");
setSelectedHeader("activejobs");
setBreadcrumbs([
{ link: "/manage/jobs", label: t("titles.bc.jobs") },
{
link: `/manage/jobs/${jobId}`,
label: t("titles.bc.jobs-detail", {
number: (data && data.jobs_by_pk && data.jobs_by_pk.ro_number) || "",
}),
},
{
link: `/manage/jobs/${jobId}/deliver`,
label: t("titles.bc.jobs-deliver"),
},
]);
}, [t, setBreadcrumbs, jobId, data, setSelectedHeader]);
useEffect(() => {
document.title = t("titles.jobs-deliver");
setSelectedHeader("activejobs");
setBreadcrumbs([
{link: "/manage/jobs", label: t("titles.bc.jobs")},
{
link: `/manage/jobs/${jobId}`,
label: t("titles.bc.jobs-detail", {
number: (data && data.jobs_by_pk && data.jobs_by_pk.ro_number) || "",
}),
},
{
link: `/manage/jobs/${jobId}/deliver`,
label: t("titles.bc.jobs-deliver"),
},
]);
}, [t, setBreadcrumbs, jobId, data, setSelectedHeader]);
if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />;
if (data && !!!data.bodyshops_by_pk.deliverchecklist)
if (loading) return <LoadingSpinner/>;
if (error) return <AlertComponent message={error.message} type="error"/>;
if (data && !!!data.bodyshops_by_pk.deliverchecklist)
return (
<AlertComponent message={t("deliver.errors.nochecklist")} type="error"/>
);
return (
<AlertComponent message={t("deliver.errors.nochecklist")} type="error" />
<RbacWrapper action="jobs:deliver">
<div>
<JobchecklistComponent
type="deliver"
checklistConfig={
(data && data.bodyshops_by_pk.deliverchecklist) || {}
}
job={data ? data.jobs_by_pk : {}}
/>
</div>
</RbacWrapper>
);
return (
<RbacWrapper action="jobs:deliver">
<div>
<JobchecklistComponent
type="deliver"
checklistConfig={
(data && data.bodyshops_by_pk.deliverchecklist) || {}
}
job={data ? data.jobs_by_pk : {}}
/>
</div>
</RbacWrapper>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
mapStateToProps,
mapDispatchToProps
)(JobsDeliverContainer);

View File

@@ -1,31 +1,25 @@
import Icon, {
BarsOutlined,
CalendarFilled,
DollarCircleOutlined,
FileImageFilled,
HistoryOutlined,
PrinterFilled,
SyncOutlined,
ToolFilled,
BarsOutlined,
CalendarFilled,
DollarCircleOutlined,
FileImageFilled,
HistoryOutlined,
PrinterFilled,
SyncOutlined,
ToolFilled,
} from "@ant-design/icons";
import {
Button,
Divider,
Form,
PageHeader,
Space,
Tabs,
notification,
} from "antd";
import {Button, Divider, Form, notification, Space, Tabs,} from "antd";
import {PageHeader} from "@ant-design/pro-layout";
import Axios from "axios";
import moment from "moment";
import dayjs from "../../utils/day";
import queryString from "query-string";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { FaHardHat, FaRegStickyNote, FaShieldAlt } from "react-icons/fa";
import { connect } from "react-redux";
import { useHistory, useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import React, {useEffect, useState} from "react";
import {useTranslation} from "react-i18next";
import {FaHardHat, FaRegStickyNote, FaShieldAlt} from "react-icons/fa";
import {connect} from "react-redux";
import {useLocation, useNavigate} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import FormFieldsChanged from "../../components/form-fields-changed-alert/form-fields-changed-alert.component";
import JobAuditTrail from "../../components/job-audit-trail/job-audit-trail.component";
import JobsLinesContainer from "../../components/job-detail-lines/job-lines.container";
@@ -43,409 +37,364 @@ import JobsDetailPliContainer from "../../components/jobs-detail-pli/jobs-detail
import JobsDetailRates from "../../components/jobs-detail-rates/jobs-detail-rates.component";
import JobsDetailTotals from "../../components/jobs-detail-totals/jobs-detail-totals.component";
import JobsDocumentsGalleryContainer from "../../components/jobs-documents-gallery/jobs-documents-gallery.container";
import JobsDocumentsLocalGallery from "../../components/jobs-documents-local-gallery/jobs-documents-local-gallery.container";
import JobsDocumentsLocalGallery
from "../../components/jobs-documents-local-gallery/jobs-documents-local-gallery.container";
import JobNotesContainer from "../../components/jobs-notes/jobs-notes.container";
import NoteUpsertModalComponent from "../../components/note-upsert-modal/note-upsert-modal.container";
import ScheduleJobModalContainer from "../../components/schedule-job-modal/schedule-job-modal.container";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {insertAuditTrail} from "../../redux/application/application.actions";
import {selectJobReadOnly} from "../../redux/application/application.selectors";
import {setModalContext} from "../../redux/modals/modals.actions";
import {selectBodyshop} from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import UndefinedToNull from "../../utils/undefinedtonull";
import _ from "lodash";
import JobProfileDataWarning from "../../components/job-profile-data-warning/job-profile-data-warning.component";
import { DateTimeFormat } from "./../../utils/DateFormatter";
import {DateTimeFormat} from "./../../utils/DateFormatter";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
jobRO: selectJobReadOnly,
bodyshop: selectBodyshop,
jobRO: selectJobReadOnly,
});
const mapDispatchToProps = (dispatch) => ({
setPrintCenterContext: (context) =>
dispatch(setModalContext({ context: context, modal: "printCenter" })),
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
setPrintCenterContext: (context) =>
dispatch(setModalContext({context: context, modal: "printCenter"})),
insertAuditTrail: ({jobid, operation}) =>
dispatch(insertAuditTrail({jobid, operation})),
});
export function JobsDetailPage({
bodyshop,
setPrintCenterContext,
jobRO,
job,
mutationUpdateJob,
handleSubmit,
insertAuditTrail,
refetch,
}) {
const { t } = useTranslation();
const [form] = Form.useForm();
const history = useHistory();
const [loading, setLoading] = useState(false);
const search = queryString.parse(useLocation().search);
bodyshop,
setPrintCenterContext,
jobRO,
job,
mutationUpdateJob,
handleSubmit,
insertAuditTrail,
refetch,
}) {
const {t} = useTranslation();
const [form] = Form.useForm();
const history = useNavigate();
const [loading, setLoading] = useState(false);
const search = queryString.parse(useLocation().search);
const formItemLayout = {
layout: "vertical",
};
const formItemLayout = {
layout: "vertical",
};
useEffect(() => {
//form.setFieldsValue(transormJobToForm(job));
form.resetFields();
}, [form, job]);
//useKeyboardSaveShortcut(form.submit);
const handleFinish = async (values) => {
setLoading(true);
const result = await mutationUpdateJob({
variables: {
jobId: job.id,
job: {
...UndefinedToNull(values, [
"alt_transport",
"category",
"referral_source",
]),
// The union and spread is required to keep values coming in from the estimating system that aren't displayed.
parts_tax_rates: _.union(
Object.keys(job.parts_tax_rates),
Object.keys(values.parts_tax_rates)
).reduce((acc, val) => {
acc[val] = {
...job.parts_tax_rates[val],
...values.parts_tax_rates[val],
};
return acc;
}, {}),
materials: _.union(
Object.keys(job.materials),
Object.keys(values.materials)
).reduce((acc, val) => {
acc[val] = {
...job.materials[val],
...values.materials[val],
};
return acc;
}, {}),
cieca_pfl: _.union(
Object.keys(job.cieca_pfl),
Object.keys(values.cieca_pfl)
).reduce((acc, val) => {
acc[val] = {
...job.cieca_pfl[val],
...values.cieca_pfl[val],
};
return acc;
}, {}),
cieca_pfo: { ...job.cieca_pfo, ...values.cieca_pfo },
},
},
});
try {
const newTotals = await Axios.post("/job/totalsssu", {
id: job.id,
});
if (newTotals.status !== 200 || result.errors) {
notification["error"]({
message: t("jobs.errors.totalscalc"),
});
} else {
notification["success"]({
message: t("jobs.successes.savetitle"),
});
const changedAuditFields = form.getFieldsValue(
[
"scheduled_in",
"actual_in",
"scheduled_completion",
"actual_completion",
"scheduled_delivery",
"actual_delivery",
"date_invoiced",
"ins_co_nm",
"ded_amt",
"ded_status",
"date_exported",
"special_coverage_policy",
"ca_gst_registrant",
"ca_bc_pvrt",
"scheduled_in",
"rate_la1",
"rate_la2",
"rate_la3",
"rate_la4",
"rate_laa",
"rate_lab",
"rate_lad",
"rate_lae",
"rate_laf",
"rate_lag",
"rate_lam",
"rate_lar",
"rate_las",
"rate_lau",
"rate_ma2s",
"rate_ma2t",
"rate_ma3s",
"rate_mabl",
"rate_macs",
"rate_mapa",
"rate_mahw",
"rate_mash",
"rate_matd",
],
(meta) => meta && meta.touched
);
Object.keys(changedAuditFields).forEach((key) => {
insertAuditTrail({
jobid: job.id,
operation: AuditTrailMapping.jobfieldchange(
key,
changedAuditFields[key] instanceof moment
? DateTimeFormat(changedAuditFields[key])
: changedAuditFields[key]
),
});
});
await refetch();
form.setFieldsValue(transormJobToForm(job));
useEffect(() => {
//form.setFieldsValue(transormJobToForm(job));
form.resetFields();
}
} catch (error) {
notification["error"]({
message: t("jobs.errors.totalscalc"),
});
} finally {
setLoading(false);
}
};
}, [form, job]);
const menuExtra = (
<Space wrap>
<Button
onClick={() => {
refetch();
}}
key="refresh"
>
<SyncOutlined />
{t("general.labels.refresh")}
</Button>
<JobsChangeStatus job={job} />
<JobSyncButton job={job} />
<Button
onClick={() => {
setPrintCenterContext({
actions: { refetch: refetch },
context: {
id: job.id,
job: job,
type: "job",
//useKeyboardSaveShortcut(form.submit);
const handleFinish = async (values) => {
setLoading(true);
const result = await mutationUpdateJob({
variables: {
jobId: job.id,
job: {
...UndefinedToNull(values, [
"alt_transport",
"category",
"referral_source",
]),
// The union and spread is required to keep values coming in from the estimating system that aren't displayed.
parts_tax_rates: _.union(
Object.keys(job.parts_tax_rates),
Object.keys(values.parts_tax_rates)
).reduce((acc, val) => {
acc[val] = {
...job.parts_tax_rates[val],
...values.parts_tax_rates[val],
};
return acc;
}, {}),
materials: _.union(
Object.keys(job.materials),
Object.keys(values.materials)
).reduce((acc, val) => {
acc[val] = {
...job.materials[val],
...values.materials[val],
};
return acc;
}, {}),
cieca_pfl: _.union(
Object.keys(job.cieca_pfl),
Object.keys(values.cieca_pfl)
).reduce((acc, val) => {
acc[val] = {
...job.cieca_pfl[val],
...values.cieca_pfl[val],
};
return acc;
}, {}),
cieca_pfo: {...job.cieca_pfo, ...values.cieca_pfo},
},
},
});
}}
key="printing"
>
<PrinterFilled />
{t("jobs.actions.printCenter")}
</Button>
<JobsConvertButton
job={job}
refetch={refetch}
parentFormIsFieldsTouched={form.isFieldsTouched}
/>
<JobsDetailHeaderActions key="actions" job={job} refetch={refetch} />
<Button
type="primary"
loading={loading}
disabled={jobRO}
onClick={() => form.submit()}
>
{t("general.actions.save")}
</Button>
</Space>
);
});
try {
const newTotals = await Axios.post("/job/totalsssu", {
id: job.id,
});
return (
<div>
<ScheduleJobModalContainer />
<JobReconciliationModal />
<JobLineUpsertModalContainer />
<NoteUpsertModalComponent />
<Form
form={form}
name="JobDetailForm"
onFinish={handleFinish}
{...formItemLayout}
autoComplete={"off"}
initialValues={transormJobToForm(job)}
>
<PageHeader
// onBack={() => window.history.back()}
title={job.ro_number || t("general.labels.na")}
extra={menuExtra}
/>
<JobsDetailHeader job={job} />
<Divider type="horizontal" />
<JobProfileDataWarning job={job} />
<FormFieldsChanged form={form} />
<Tabs
defaultActiveKey={search.tab}
onChange={(key) => history.push({ search: `?tab=${key}` })}
tabBarStyle={{ fontWeight: "bold", borderBottom: "10px" }}
>
<Tabs.TabPane
forceRender
tab={
<span>
<Icon component={FaShieldAlt} />
{t("menus.jobsdetail.general")}
</span>
if (newTotals.status !== 200 || result.errors) {
notification["error"]({
message: t("jobs.errors.totalscalc"),
});
} else {
notification["success"]({
message: t("jobs.successes.savetitle"),
});
const changedAuditFields = form.getFieldsValue(
[
"scheduled_in",
"actual_in",
"scheduled_completion",
"actual_completion",
"scheduled_delivery",
"actual_delivery",
"date_invoiced",
"ins_co_nm",
"ded_amt",
"ded_status",
"date_exported",
"special_coverage_policy",
"ca_gst_registrant",
"ca_bc_pvrt",
"scheduled_in",
"rate_la1",
"rate_la2",
"rate_la3",
"rate_la4",
"rate_laa",
"rate_lab",
"rate_lad",
"rate_lae",
"rate_laf",
"rate_lag",
"rate_lam",
"rate_lar",
"rate_las",
"rate_lau",
"rate_ma2s",
"rate_ma2t",
"rate_ma3s",
"rate_mabl",
"rate_macs",
"rate_mapa",
"rate_mahw",
"rate_mash",
"rate_matd",
],
(meta) => meta && meta.touched
);
Object.keys(changedAuditFields).forEach((key) => {
insertAuditTrail({
jobid: job.id,
operation: AuditTrailMapping.jobfieldchange(
key,
changedAuditFields[key] instanceof dayjs
? DateTimeFormat(changedAuditFields[key])
: changedAuditFields[key]
),
});
});
await refetch();
form.setFieldsValue(transormJobToForm(job));
form.resetFields();
}
key="general"
>
<JobsDetailGeneral job={job} form={form} />
</Tabs.TabPane>
<Tabs.TabPane
forceRender
tab={
<span>
<BarsOutlined />
{t("menus.jobsdetail.repairdata")}
</span>
}
key="repairdata"
>
<JobsLinesContainer
job={job}
joblines={job.joblines}
refetch={refetch}
form={form}
} catch (error) {
notification["error"]({
message: t("jobs.errors.totalscalc"),
});
} finally {
setLoading(false);
}
};
const menuExtra = (
<Space wrap>
<Button
onClick={() => {
refetch();
}}
key="refresh"
>
<SyncOutlined/>
{t("general.labels.refresh")}
</Button>
<JobsChangeStatus job={job}/>
<JobSyncButton job={job}/>
<Button
onClick={() => {
setPrintCenterContext({
actions: {refetch: refetch},
context: {
id: job.id,
job: job,
type: "job",
},
});
}}
key="printing"
>
<PrinterFilled/>
{t("jobs.actions.printCenter")}
</Button>
<JobsConvertButton
job={job}
refetch={refetch}
parentFormIsFieldsTouched={form.isFieldsTouched}
/>
</Tabs.TabPane>
<Tabs.TabPane
forceRender
tab={
<span>
<DollarCircleOutlined />
{t("menus.jobsdetail.rates")}
</span>
}
key="rates"
>
<JobsDetailRates job={job} form={form} />
</Tabs.TabPane>
<Tabs.TabPane
tab={
<span>
<DollarCircleOutlined />
{t("menus.jobsdetail.totals")}
</span>
}
key="totals"
>
<JobsDetailTotals job={job} refetch={refetch} />
</Tabs.TabPane>
<Tabs.TabPane
tab={
<span>
<ToolFilled />
{t("menus.jobsdetail.partssublet")}
</span>
}
key="partssublet"
>
<JobsDetailPliContainer job={job} />
</Tabs.TabPane>
<Tabs.TabPane
tab={
<span>
<Icon component={FaHardHat} />
{t("menus.jobsdetail.labor")}
</span>
}
key="labor"
>
<JobsDetailLaborContainer job={job} jobId={job.id} />
</Tabs.TabPane>
<Tabs.TabPane
forceRender
tab={
<span>
<CalendarFilled />
{t("menus.jobsdetail.dates")}
</span>
}
key="dates"
>
<JobsDetailDatesComponent job={job} />
</Tabs.TabPane>
<Tabs.TabPane
tab={
<span>
<FileImageFilled />
{t("jobs.labels.documents")}
</span>
}
key="documents"
>
{bodyshop.uselocalmediaserver ? (
<JobsDocumentsLocalGallery job={job} />
) : (
<JobsDocumentsGalleryContainer jobId={job.id} />
)}
</Tabs.TabPane>
<Tabs.TabPane
tab={
<span>
<Icon component={FaRegStickyNote} />
{t("jobs.labels.notes")}
</span>
}
key="notes"
>
<JobNotesContainer jobId={job.id} />
</Tabs.TabPane>
<Tabs.TabPane
tab={
<span>
<HistoryOutlined />
{t("jobs.labels.audit")}
</span>
}
key="audit"
>
<JobAuditTrail jobId={job.id} />
</Tabs.TabPane>
</Tabs>
</Form>
</div>
);
<JobsDetailHeaderActions key="actions" job={job} refetch={refetch}/>
<Button
type="primary"
loading={loading}
disabled={jobRO}
onClick={() => form.submit()}
>
{t("general.actions.save")}
</Button>
</Space>
);
return (
<div>
<ScheduleJobModalContainer/>
<JobReconciliationModal/>
<JobLineUpsertModalContainer/>
<NoteUpsertModalComponent/>
<Form
form={form}
name="JobDetailForm"
onFinish={handleFinish}
{...formItemLayout}
autoComplete={"off"}
initialValues={transormJobToForm(job)}
>
<PageHeader
// onBack={() => window.history.back()}
title={job.ro_number || t("general.labels.na")}
extra={menuExtra}
/>
<JobsDetailHeader job={job}/>
<Divider type="horizontal"/>
<JobProfileDataWarning job={job}/>
<FormFieldsChanged form={form}/>
<Tabs
defaultActiveKey={search.tab}
onChange={(key) => history({search: `?tab=${key}`})}
tabBarStyle={{fontWeight: "bold", borderBottom: "10px"}}
items={[
{
key: "general",
icon: <Icon component={FaShieldAlt}/>,
label: t("menus.jobsdetail.general"),
forceRender: true,
children: <JobsDetailGeneral job={job} form={form}/>,
},
{
key: "repairdata",
icon: <BarsOutlined/>,
label: t("menus.jobsdetail.repairdata"),
forceRender: true,
children: (
<JobsLinesContainer
job={job}
joblines={job.joblines}
refetch={refetch}
form={form}
/>
),
},
{
key: "rates",
icon: <DollarCircleOutlined/>,
label: t("menus.jobsdetail.rates"),
forceRender: true,
children: <JobsDetailRates job={job} form={form}/>,
},
{
key: "totals",
icon: <DollarCircleOutlined/>,
label: t("menus.jobsdetail.totals"),
children: <JobsDetailTotals job={job} refetch={refetch}/>,
},
{
key: "partssublet",
icon: <ToolFilled/>,
label: t("menus.jobsdetail.partssublet"),
children: <JobsDetailPliContainer job={job}/>,
},
{
key: "labor",
icon: <Icon component={FaHardHat}/>,
label: t("menus.jobsdetail.labor"),
children: <JobsDetailLaborContainer job={job} jobId={job.id}/>,
},
{
key: "dates",
icon: <CalendarFilled/>,
label: t("menus.jobsdetail.dates"),
forceRender: true,
children: <JobsDetailDatesComponent job={job}/>,
},
{
key: "documents",
icon: <FileImageFilled/>,
label: t("jobs.labels.documents"),
children: bodyshop.uselocalmediaserver ? (
<JobsDocumentsLocalGallery job={job}/>
) : (
<JobsDocumentsGalleryContainer jobId={job.id}/>
),
},
{
key: "notes",
icon: <Icon component={FaRegStickyNote}/>,
label: t("jobs.labels.notes"),
children: <JobNotesContainer jobId={job.id}/>,
},
{
key: "audit",
icon: <HistoryOutlined/>,
label: t("jobs.labels.audit"),
children: <JobAuditTrail jobId={job.id}/>,
},
]}
/>
</Form>
</div>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(JobsDetailPage);
const transormJobToForm = (job) => {
Object.keys(job.parts_tax_rates).forEach((parttype) => {
Object.keys(job.parts_tax_rates[parttype]).forEach((key) => {
if (key.includes("tx_in")) {
if (
job.parts_tax_rates[parttype][key] === "Y" ||
job.parts_tax_rates[parttype][key] === true
) {
job.parts_tax_rates[parttype][key] = true;
} else {
job.parts_tax_rates[parttype][key] = false;
}
}
Object.keys(job.parts_tax_rates).forEach((parttype) => {
Object.keys(job.parts_tax_rates[parttype]).forEach((key) => {
if (key.includes("tx_in")) {
if (
job.parts_tax_rates[parttype][key] === "Y" ||
job.parts_tax_rates[parttype][key] === true
) {
job.parts_tax_rates[parttype][key] = true;
} else {
job.parts_tax_rates[parttype][key] = false;
}
}
});
});
});
return {
...job,
loss_date: job.loss_date ? moment(job.loss_date) : null,
date_estimated: job.date_estimated ? moment(job.date_estimated) : null,
};
return {
...job,
loss_date: job.loss_date ? dayjs(job.loss_date) : null,
date_estimated: job.date_estimated ? dayjs(job.date_estimated) : null,
};
};

View File

@@ -1,121 +1,121 @@
import { useMutation, useQuery } from "@apollo/client";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {useMutation, useQuery} from "@apollo/client";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {useParams} from 'react-router-dom';
import {createStructuredSelector} from "reselect";
import AlertComponent from "../../components/alert/alert.component";
import SpinComponent from "../../components/loading-spinner/loading-spinner.component";
import NotFound from "../../components/not-found/not-found.component";
import { OwnerNameDisplayFunction } from "../../components/owner-name-display/owner-name-display.component";
import {OwnerNameDisplayFunction} from "../../components/owner-name-display/owner-name-display.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { GET_JOB_BY_PK, UPDATE_JOB } from "../../graphql/jobs.queries";
import {GET_JOB_BY_PK, UPDATE_JOB} from "../../graphql/jobs.queries";
import {
addRecentItem,
setBreadcrumbs,
setJobReadOnly,
setSelectedHeader,
} from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { CreateRecentItem } from "../../utils/create-recent-item";
import {selectBodyshop} from "../../redux/user/user.selectors";
import {CreateRecentItem} from "../../utils/create-recent-item";
import IsJobReadOnly from "../../utils/jobReadOnly";
import JobsDetailPage from "./jobs-detail.page.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
addRecentItem: (item) => dispatch(addRecentItem(item)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setJobReadOnly: (bool) => dispatch(setJobReadOnly(bool)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
addRecentItem: (item) => dispatch(addRecentItem(item)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setJobReadOnly: (bool) => dispatch(setJobReadOnly(bool)),
});
function JobsDetailPageContainer({
bodyshop,
match,
setBreadcrumbs,
addRecentItem,
setSelectedHeader,
setJobReadOnly,
}) {
const { jobId } = match.params;
const { t } = useTranslation();
setBreadcrumbs,
addRecentItem,
setSelectedHeader,
setJobReadOnly,
}) {
const {jobId} = useParams();
const {t} = useTranslation();
const { loading, error, data, refetch } = useQuery(GET_JOB_BY_PK, {
variables: { id: jobId },
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
const [mutationUpdateJob] = useMutation(UPDATE_JOB);
const {loading, error, data, refetch} = useQuery(GET_JOB_BY_PK, {
variables: {id: jobId},
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
const [mutationUpdateJob] = useMutation(UPDATE_JOB);
useEffect(() => {
setSelectedHeader("activejobs");
document.title = loading
? t("titles.app")
: error
? t("titles.app")
: t("titles.jobsdetail", {
ro_number:
(data.jobs_by_pk && data.jobs_by_pk.ro_number) ||
t("general.labels.na"),
});
setBreadcrumbs([
{ link: "/manage/jobs", label: t("titles.bc.jobs") },
{
link: `/manage/jobs/${jobId}`,
label: t("titles.bc.jobs-detail", {
number:
(data && data.jobs_by_pk && data.jobs_by_pk.ro_number) ||
t("general.labels.na"),
}),
},
useEffect(() => {
setSelectedHeader("activejobs");
document.title = loading
? t("titles.app")
: error
? t("titles.app")
: t("titles.jobsdetail", {
ro_number:
(data.jobs_by_pk && data.jobs_by_pk.ro_number) ||
t("general.labels.na"),
});
setBreadcrumbs([
{link: "/manage/jobs", label: t("titles.bc.jobs")},
{
link: `/manage/jobs/${jobId}`,
label: t("titles.bc.jobs-detail", {
number:
(data && data.jobs_by_pk && data.jobs_by_pk.ro_number) ||
t("general.labels.na"),
}),
},
]);
if (data && data.jobs_by_pk) {
setJobReadOnly(IsJobReadOnly(data.jobs_by_pk));
addRecentItem(
CreateRecentItem(
jobId,
"job",
`${
data.jobs_by_pk.ro_number || t("general.labels.na")
} | ${OwnerNameDisplayFunction(data.jobs_by_pk)}`,
`/manage/jobs/${jobId}`
)
);
}
}, [
loading,
data,
t,
error,
setBreadcrumbs,
jobId,
addRecentItem,
setSelectedHeader,
setJobReadOnly,
]);
if (data && data.jobs_by_pk) {
setJobReadOnly(IsJobReadOnly(data.jobs_by_pk));
if (loading) return <SpinComponent/>;
if (error) return <AlertComponent message={error.message} type="error"/>;
if (!!!data.jobs_by_pk) return <NotFound/>;
addRecentItem(
CreateRecentItem(
jobId,
"job",
`${
data.jobs_by_pk.ro_number || t("general.labels.na")
} | ${OwnerNameDisplayFunction(data.jobs_by_pk)}`,
`/manage/jobs/${jobId}`
)
);
}
}, [
loading,
data,
t,
error,
setBreadcrumbs,
jobId,
addRecentItem,
setSelectedHeader,
setJobReadOnly,
]);
if (loading) return <SpinComponent />;
if (error) return <AlertComponent message={error.message} type="error" />;
if (!!!data.jobs_by_pk) return <NotFound />;
return data.jobs_by_pk ? (
<RbacWrapper action="jobs:detail">
<JobsDetailPage
job={data.jobs_by_pk}
mutationUpdateJob={mutationUpdateJob}
refetch={refetch}
/>
</RbacWrapper>
) : (
<AlertComponent message={t("jobs.errors.noaccess")} type="error" />
);
return data.jobs_by_pk ? (
<RbacWrapper action="jobs:detail">
<JobsDetailPage
job={data.jobs_by_pk}
mutationUpdateJob={mutationUpdateJob}
refetch={refetch}
/>
</RbacWrapper>
) : (
<AlertComponent message={t("jobs.errors.noaccess")} type="error"/>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
mapStateToProps,
mapDispatchToProps
)(JobsDetailPageContainer);

View File

@@ -1,96 +1,93 @@
import { useQuery } from "@apollo/client";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useParams } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import {useQuery} from "@apollo/client";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {useParams} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import AlertComponent from "../../components/alert/alert.component";
import JobChecklist from "../../components/job-checklist/job-checklist.component";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import { QUERY_INTAKE_CHECKLIST } from "../../graphql/bodyshop.queries";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {QUERY_INTAKE_CHECKLIST} from "../../graphql/bodyshop.queries";
import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import {selectBodyshop} from "../../redux/user/user.selectors";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { Result } from "antd";
import {Result} from "antd";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function JobsIntakeContainer({
bodyshop,
setBreadcrumbs,
setSelectedHeader,
}) {
const { t } = useTranslation();
const { jobId } = useParams();
bodyshop,
setBreadcrumbs,
setSelectedHeader,
}) {
const {t} = useTranslation();
const {jobId} = useParams();
const { loading, error, data } = useQuery(QUERY_INTAKE_CHECKLIST, {
variables: { shopId: bodyshop.id, jobId: jobId },
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
const {loading, error, data} = useQuery(QUERY_INTAKE_CHECKLIST, {
variables: {shopId: bodyshop.id, jobId: jobId},
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
useEffect(() => {
document.title = t("titles.jobs-intake");
setSelectedHeader("activejobs");
setBreadcrumbs([
{ link: "/manage/jobs", label: t("titles.bc.jobs") },
{
link: `/manage/jobs/${jobId}`,
label: t("titles.bc.jobs-detail", {
number:
data &&
((data.jobs_by_pk && data.jobs_by_pk.ro_number) ||
t("general.labels.na")),
}),
},
{
link: `/manage/jobs/${jobId}/intake`,
label: t("titles.bc.jobs-intake"),
},
]);
}, [t, setBreadcrumbs, jobId, data, setSelectedHeader]);
useEffect(() => {
document.title = t("titles.jobs-intake");
setSelectedHeader("activejobs");
setBreadcrumbs([
{link: "/manage/jobs", label: t("titles.bc.jobs")},
{
link: `/manage/jobs/${jobId}`,
label: t("titles.bc.jobs-detail", {
number:
data &&
((data.jobs_by_pk && data.jobs_by_pk.ro_number) ||
t("general.labels.na")),
}),
},
{
link: `/manage/jobs/${jobId}/intake`,
label: t("titles.bc.jobs-intake"),
},
]);
}, [t, setBreadcrumbs, jobId, data, setSelectedHeader]);
if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />;
if (loading) return <LoadingSpinner/>;
if (error) return <AlertComponent message={error.message} type="error"/>;
if (data && !!!data.bodyshops_by_pk.intakechecklist)
return (
<AlertComponent message={t("intake.errors.nochecklist")} type="error"/>
);
if (data && !!!data.bodyshops_by_pk.intakechecklist)
return (
<AlertComponent message={t("intake.errors.nochecklist")} type="error" />
<RbacWrapper action="jobs:intake">
<div>
{!!data.jobs_by_pk.intakechecklist ||
!bodyshop.md_ro_statuses.pre_production_statuses.includes(
data.jobs_by_pk.status
) ? (
<Result status="warning" title={t("jobs.errors.cannotintake")}/>
) : (
<JobChecklist
type="intake"
checklistConfig={
(data && data.bodyshops_by_pk.intakechecklist) || {}
}
job={data && data.jobs_by_pk}
/>
)}
</div>
</RbacWrapper>
);
return (
<RbacWrapper action="jobs:intake">
<div>
{!!data.jobs_by_pk.intakechecklist ||
!bodyshop.md_ro_statuses.pre_production_statuses.includes(
data.jobs_by_pk.status
) ? (
<Result status="warning" title={t("jobs.errors.cannotintake")} />
) : (
<JobChecklist
type="intake"
checklistConfig={
(data && data.bodyshops_by_pk.intakechecklist) || {}
}
job={data && data.jobs_by_pk}
/>
)}
</div>
</RbacWrapper>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
mapStateToProps,
mapDispatchToProps
)(JobsIntakeContainer);

View File

@@ -1,36 +1,33 @@
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import JobDetailCards from "../../components/job-detail-cards/job-detail-cards.component";
import JobsReadyList from "../../components/jobs-ready-list/jobs-ready-list.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function JobsReadyPage({ setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation();
export function JobsReadyPage({setBreadcrumbs, setSelectedHeader}) {
const {t} = useTranslation();
useEffect(() => {
document.title = t("titles.readyjobs");
setSelectedHeader("readyjobs");
setBreadcrumbs([
{ link: "/manage/jobs", label: t("titles.bc.jobs-ready") },
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
useEffect(() => {
document.title = t("titles.readyjobs");
setSelectedHeader("readyjobs");
setBreadcrumbs([
{link: "/manage/jobs", label: t("titles.bc.jobs-ready")},
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
return (
<RbacWrapper action="jobs:list-ready">
<JobsReadyList />
<JobDetailCards />
</RbacWrapper>
);
return (
<RbacWrapper action="jobs:list-ready">
<JobsReadyList/>
<JobDetailCards/>
</RbacWrapper>
);
}
export default connect(null, mapDispatchToProps)(JobsReadyPage);

View File

@@ -1,36 +1,33 @@
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import JobDetailCards from "../../components/job-detail-cards/job-detail-cards.component";
import JobsList from "../../components/jobs-list/jobs-list.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function JobsPage({ setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation();
export function JobsPage({setBreadcrumbs, setSelectedHeader}) {
const {t} = useTranslation();
useEffect(() => {
document.title = t("titles.jobs");
setSelectedHeader("activejobs");
setBreadcrumbs([
{ link: "/manage/jobs", label: t("titles.bc.jobs-active") },
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
useEffect(() => {
document.title = t("titles.jobs");
setSelectedHeader("activejobs");
setBreadcrumbs([
{link: "/manage/jobs", label: t("titles.bc.jobs-active")},
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
return (
<RbacWrapper action="jobs:list-active">
<JobsList />
<JobDetailCards />
</RbacWrapper>
);
return (
<RbacWrapper action="jobs:list-active">
<JobsList/>
<JobDetailCards/>
</RbacWrapper>
);
}
export default connect(null, mapDispatchToProps)(JobsPage);

View File

@@ -1,19 +1,24 @@
import React from "react";
import { connect } from "react-redux";
import { Redirect } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { selectCurrentUser } from "../../redux/user/user.selectors";
import React, {useEffect} from "react";
import {connect} from "react-redux";
import {useNavigate} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import {selectCurrentUser} from "../../redux/user/user.selectors";
import LandingPageStatic from "../../landing/index";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
currentUser: selectCurrentUser,
});
export default connect(mapStateToProps, null)(LandingPage);
export function LandingPage({ currentUser }) {
if (currentUser.authorized) return <Redirect to={"/manage"} />;
export function LandingPage({currentUser}) {
const navigate = useNavigate();
return <LandingPageStatic />;
//return <Redirect to={"/signin"} />;
useEffect(() => {
if (currentUser.authorized) {
navigate('/manage/jobs');
}
}, [currentUser, navigate]);
return <LandingPageStatic/>;
}

View File

@@ -1,14 +1,20 @@
import React from "react";
import React, {useEffect} from "react";
//import DashboardGridComponent from "../../components/dashboard-grid/dashboard-grid.component";
import { Redirect } from "react-router-dom";
import {useNavigate} from "react-router-dom";
export default function ManageRootPageComponent() {
//const client = useApolloClient();
//const client = useApolloClient();
const navigate = useNavigate();
return <Redirect to={`/manage/jobs`} />;
useEffect(() => {
navigate('/manage/jobs');
}, [navigate]);
return <div/>;
// return (
// <div>
// <DashboardGridComponent />
// </div>
// );
// return (
// <div>
// <DashboardGridComponent />
// </div>
// );
}

View File

@@ -1,29 +1,30 @@
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { setBreadcrumbs } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {setBreadcrumbs} from "../../redux/application/application.actions";
import {selectBodyshop} from "../../redux/user/user.selectors";
import ManageRootPageComponent from "./manage-root.page.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
});
export function ManageRootPageContainer({ setBreadcrumbs, bodyshop }) {
const { t } = useTranslation();
useEffect(() => {
document.title = t("titles.manageroot");
setBreadcrumbs([]);
}, [t, setBreadcrumbs]);
export function ManageRootPageContainer({setBreadcrumbs, bodyshop}) {
const {t} = useTranslation();
useEffect(() => {
document.title = t("titles.manageroot");
setBreadcrumbs([]);
}, [t, setBreadcrumbs]);
return <ManageRootPageComponent />;
return <ManageRootPageComponent/>;
}
export default connect(
mapStateToProps,
mapDispatchToProps
mapStateToProps,
mapDispatchToProps
)(ManageRootPageContainer);

View File

@@ -1,10 +1,10 @@
import { BackTop, Layout } from "antd";
import {FloatButton, Layout} from "antd";
import preval from "preval.macro";
import React, { lazy, Suspense, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link, Route, Switch } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import React, {lazy, Suspense, useEffect, useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {Link, Route, Routes} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import BreadCrumbs from "../../components/breadcrumbs/breadcrumbs.component";
import ChatAffixContainer from "../../components/chat-affix/chat-affix.container";
import ConflictComponent from "../../components/conflict/conflict.component";
@@ -17,436 +17,400 @@ import PartnerPingComponent from "../../components/partner-ping/partner-ping.com
import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container";
import ShopSubStatusComponent from "../../components/shop-sub-status/shop-sub-status.component";
import TestComponent from "../../components/_test/test.page";
import { requestForToken } from "../../firebase/firebase.utils";
import {
selectBodyshop,
selectInstanceConflict,
} from "../../redux/user/user.selectors";
import {requestForToken} from "../../firebase/firebase.utils";
import {selectBodyshop, selectInstanceConflict,} from "../../redux/user/user.selectors";
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")
);
const JobsPage = lazy(() => import("../jobs/jobs.page"));
const CardPaymentModalContainer = lazy(() =>
import("../../components/card-payment-modal/card-payment-modal.container.")
import("../../components/card-payment-modal/card-payment-modal.container.")
);
const JobsDetailPage = lazy(() =>
import("../jobs-detail/jobs-detail.page.container")
import("../jobs-detail/jobs-detail.page.container")
);
const InventoryListPage = lazy(() => import("../inventory/inventory.page"));
const ProfilePage = lazy(() => import("../profile/profile.container.page"));
const JobsAvailablePage = lazy(() =>
import("../jobs-available/jobs-available.page.container")
import("../jobs-available/jobs-available.page.container")
);
const ScheduleContainer = lazy(() =>
import("../schedule/schedule.page.container")
import("../schedule/schedule.page.container")
);
const VehiclesContainer = lazy(() =>
import("../vehicles/vehicles.page.container")
import("../vehicles/vehicles.page.container")
);
const VehiclesDetailContainer = lazy(() =>
import("../vehicles-detail/vehicles-detail.page.container")
import("../vehicles-detail/vehicles-detail.page.container")
);
const OwnersContainer = lazy(() => import("../owners/owners.page.container"));
const OwnersDetailContainer = lazy(() =>
import("../owners-detail/owners-detail.page.container")
import("../owners-detail/owners-detail.page.container")
);
const ShopPage = lazy(() => import("../shop/shop.page.component"));
const ShopVendorPageContainer = lazy(() =>
import("../shop-vendor/shop-vendor.page.container")
import("../shop-vendor/shop-vendor.page.container")
);
const EmailOverlayContainer = lazy(() =>
import("../../components/email-overlay/email-overlay.container.jsx")
import("../../components/email-overlay/email-overlay.container.jsx")
);
const JobsCreateContainerPage = lazy(() =>
import("../jobs-create/jobs-create.container")
import("../jobs-create/jobs-create.container")
);
const CourtesyCarCreateContainer = lazy(() =>
import("../courtesy-car-create/courtesy-car-create.page.container")
import("../courtesy-car-create/courtesy-car-create.page.container")
);
const CourtesyCarDetailContainer = lazy(() =>
import("../courtesy-car-detail/courtesy-car-detail.page.container")
import("../courtesy-car-detail/courtesy-car-detail.page.container")
);
const CourtesyCarsPage = lazy(() =>
import("../courtesy-cars/courtesy-cars.page.container")
import("../courtesy-cars/courtesy-cars.page.container")
);
const ContractCreatePage = lazy(() =>
import("../contract-create/contract-create.page.container")
import("../contract-create/contract-create.page.container")
);
const ContractDetailPage = lazy(() =>
import("../contract-detail/contract-detail.page.container")
import("../contract-detail/contract-detail.page.container")
);
const ContractsList = lazy(() =>
import("../contracts/contracts.page.container")
import("../contracts/contracts.page.container")
);
const BillsListPage = lazy(() => import("../bills/bills.page.container"));
const JobCostingModal = lazy(() =>
import("../../components/job-costing-modal/job-costing-modal.container")
import("../../components/job-costing-modal/job-costing-modal.container")
);
const ReportCenterModal = lazy(() =>
import("../../components/report-center-modal/report-center-modal.container")
import("../../components/report-center-modal/report-center-modal.container")
);
const BillEnterModalContainer = lazy(() =>
import("../../components/bill-enter-modal/bill-enter-modal.container")
import("../../components/bill-enter-modal/bill-enter-modal.container")
);
const TimeTicketModalContainer = lazy(() =>
import("../../components/time-ticket-modal/time-ticket-modal.container")
import("../../components/time-ticket-modal/time-ticket-modal.container")
);
const TimeTicketModalTask = lazy(() =>
import(
"../../components/time-ticket-task-modal/time-ticket-task-modal.container"
)
import(
"../../components/time-ticket-task-modal/time-ticket-task-modal.container"
)
);
const PaymentModalContainer = lazy(() =>
import("../../components/payment-modal/payment-modal.container")
import("../../components/payment-modal/payment-modal.container")
);
const ProductionListPage = lazy(() =>
import("../production-list/production-list.container")
import("../production-list/production-list.container")
);
const ProductionBoardPage = lazy(() =>
import("../production-board/production-board.container")
import("../production-board/production-board.container")
);
// const ShopTemplates = lazy(() =>
// import("../shop-templates/shop-templates.container")
// );
const JobIntake = lazy(() =>
import("../jobs-intake/jobs-intake.page.container")
import("../jobs-intake/jobs-intake.page.container")
);
const JobChecklistView = lazy(() =>
import("../jobs-checklist-view/jobs-checklist-view.page")
import("../jobs-checklist-view/jobs-checklist-view.page")
);
const JobDeliver = lazy(() =>
import("../jobs-deliver/jobs-delivery.page.container")
import("../jobs-deliver/jobs-delivery.page.container")
);
const AccountingQboCallback = lazy(() =>
import("../accounting-qbo/accounting-qbo.page")
import("../accounting-qbo/accounting-qbo.page")
);
const AccountingReceivables = lazy(() =>
import("../accounting-receivables/accounting-receivables.container")
import("../accounting-receivables/accounting-receivables.container")
);
const AccountingPayables = lazy(() =>
import("../accounting-payables/accounting-payables.container")
import("../accounting-payables/accounting-payables.container")
);
const AccountingPayments = lazy(() =>
import("../accounting-payments/accounting-payments.container")
import("../accounting-payments/accounting-payments.container")
);
const AllJobs = lazy(() => import("../jobs-all/jobs-all.container"));
const ReadyJobs = lazy(() => import("../jobs-ready/jobs-ready.page"));
const JobsClose = lazy(() => import("../jobs-close/jobs-close.container"));
const JobsAdmin = lazy(() => import("../jobs-admin/jobs-admin.page"));
const TempDocs = lazy(() =>
import("../temporary-docs/temporary-docs.container")
import("../temporary-docs/temporary-docs.container")
);
const ShopCsiPageContainer = lazy(() =>
import("../shop-csi/shop-csi.container.page")
import("../shop-csi/shop-csi.container.page")
);
const PaymentsAll = lazy(() =>
import("../payments-all/payments-all.container.page")
import("../payments-all/payments-all.container.page")
);
const ShiftClock = lazy(() => import("../shift-clock/shift-clock.page"));
const Scoreboard = lazy(() =>
import("../scoreboard/scoreboard.page.container")
import("../scoreboard/scoreboard.page.container")
);
const TimeTicketsAll = lazy(() =>
import("../time-tickets/time-tickets.container")
import("../time-tickets/time-tickets.container")
);
const Help = lazy(() => import("../help/help.page"));
const PartsQueue = lazy(() =>
import("../parts-queue/parts-queue.page.container")
import("../parts-queue/parts-queue.page.container")
);
const ExportLogs = lazy(() =>
import("../export-logs/export-logs.page.container")
import("../export-logs/export-logs.page.container")
);
const Phonebook = lazy(() => import("../phonebook/phonebook.page.container"));
const EmailTest = lazy(() =>
import("../../components/email-test/email-test-component")
import("../../components/email-test/email-test-component")
);
const Dashboard = lazy(() => import("../dashboard/dashboard.container"));
const Dms = lazy(() => import("../dms/dms.container"));
const DmsPayables = lazy(() =>
import("../dms-payables/dms-payables.container")
import("../dms-payables/dms-payables.container")
);
const ManageRootPage = lazy(() =>
import("../manage-root/manage-root.page.container")
);
const TtApprovals = lazy(() =>
import("../tt-approvals/tt-approvals.page.container")
import("../tt-approvals/tt-approvals.page.container")
);
const { Content, Footer } = Layout;
const {Content, Footer} = Layout;
const mapStateToProps = createStructuredSelector({
conflict: selectInstanceConflict,
bodyshop: selectBodyshop,
conflict: selectInstanceConflict,
bodyshop: selectBodyshop,
});
export function Manage({ match, conflict, bodyshop }) {
const { t } = useTranslation();
useEffect(() => {
const widgetId = "mQdqARMzkZRUVugJ6TdS";
window.noticeable.render("widget", widgetId);
try {
requestForToken();
} catch (error) {
console.log("Unable to request for token.", error);
}
}, []);
export function Manage({conflict, bodyshop}) {
const {t} = useTranslation();
const [chatVisible] = useState(false);
useEffect(() => {
document.title = t("titles.app");
}, [t]);
const AppRouteTable = (
<Suspense
fallback={<LoadingSpinner message={t("general.labels.loadingapp")} />}
>
<PaymentModalContainer />
useEffect(() => {
const widgetId = "mQdqARMzkZRUVugJ6TdS";
window.noticeable.render("widget", widgetId);
requestForToken().catch((error) => {
console.error(`Unable to request for token.`, error)
});
}, []);
<CardPaymentModalContainer />
useEffect(() => {
document.title = t("titles.app");
}, [t]);
const AppRouteTable = (
<Suspense
fallback={<LoadingSpinner message={t("general.labels.loadingapp")}/>} This
>
<PaymentModalContainer/>
<BreadCrumbs />
<BillEnterModalContainer />
<JobCostingModal />
<ReportCenterModal />
<EmailOverlayContainer />
<TimeTicketModalContainer />
<TimeTicketModalTask />
<PrintCenterModalContainer />
<Route exact path={`${match.path}/_test`} component={TestComponent} />
<Route exact path={`${match.path}`} component={ManageRootPage} />
<Route exact path={`${match.path}/jobs`} component={JobsPage} />
<Switch>
<Route
exact
path={`${match.path}/jobs/:jobId/intake`}
component={JobIntake}
/>
<Route
exact
path={`${match.path}/jobs/:jobId/deliver`}
component={JobDeliver}
/>
<Route
exact
path={`${match.path}/jobs/:jobId/checklist`}
component={JobChecklistView}
/>
<Route
exact
path={`${match.path}/jobs/:jobId/close`}
component={JobsClose}
/>
<Route
exact
path={`${match.path}/jobs/:jobId/admin`}
component={JobsAdmin}
/>
<Route exact path={`${match.path}/jobs/all`} component={AllJobs} />
<Route exact path={`${match.path}/jobs/ready`} component={ReadyJobs} />
<Route
exact
path={`${match.path}/jobs/new`}
component={JobsCreateContainerPage}
/>
<Route path={`${match.path}/jobs/:jobId`} component={JobsDetailPage} />
</Switch>
<Route exact path={`${match.path}/temporarydocs/`} component={TempDocs} />
<Route
exact
path={`${match.path}/inventory/`}
component={InventoryListPage}
/>
<Route
exact
path={`${match.path}/courtesycars/`}
component={CourtesyCarsPage}
/>
<Switch>
<Route
exact
path={`${match.path}/courtesycars/new`}
component={CourtesyCarCreateContainer}
/>
<CardPaymentModalContainer/>
<Route
exact
path={`${match.path}/courtesycars/contracts`}
component={ContractsList}
/>
<BreadCrumbs/>
<BillEnterModalContainer/>
<JobCostingModal/>
<ReportCenterModal/>
<EmailOverlayContainer/>
<TimeTicketModalContainer/>
<TimeTicketModalTask/>
<PrintCenterModalContainer/>
<Routes>
<Route path='/_test' element={<TestComponent/>}/>
<Route path='/' element={<ManageRootPage/>}/>
<Route path='/jobs' element={<JobsPage/>}/>
<Route
path='/jobs/:jobId/intake'
element={<JobIntake/>}
/>
<Route
path='/jobs/:jobId/deliver'
element={<JobDeliver/>}
/>
<Route
path='/jobs/:jobId/checklist'
element={<JobChecklistView/>}
/>
<Route
path='/jobs/:jobId/close'
element={<JobsClose/>}
/>
<Route
path='/jobs/:jobId/admin'
element={<JobsAdmin/>}
/>
<Route path='/jobs/all' element={<AllJobs/>}/>
<Route path='/jobs/ready' element={<ReadyJobs/>}/>
<Route
path='/jobs/new'
element={<JobsCreateContainerPage/>}
/>
<Route path='/jobs/:jobId' element={<JobsDetailPage/>}/>
<Route path='/temporarydocs/' element={<TempDocs/>}/>
<Route
path='/inventory/'
element={<InventoryListPage/>}
/>
<Route
path='/courtesycars/'
element={<CourtesyCarsPage/>}
/>
<Route
path='/courtesycars/new'
element={<CourtesyCarCreateContainer/>}
/>
<Route
path='/courtesycars/contracts'
element={<ContractsList/>}
/>
<Route
path='/courtesycars/contracts/new'
element={<ContractCreatePage/>}
/>
<Route
path='/courtesycars/contracts/:contractId'
element={<ContractDetailPage/>}
/>
<Route
path='/courtesycars/:ccId'
element={<CourtesyCarDetailContainer/>}
/>
<Route path='/profile' element={<ProfilePage/>}/>
<Route
path='/vehicles'
element={<VehiclesContainer/>}
/>
<Route
path='/production/list'
element={<ProductionListPage/>}
/>
<Route
path='/production/board'
element={<ProductionBoardPage/>}
/>
<Route
path='/vehicles/:vehId'
element={<VehiclesDetailContainer/>}
/>
<Route path='/bills' element={<BillsListPage/>}/>
<Route path='/owners' element={<OwnersContainer/>}/>
<Route
path='/owners/:ownerId'
element={<OwnersDetailContainer/>}
/>
<Route
path='/schedule'
element={<ScheduleContainer/>}
/>
<Route
path='/available'
element={<JobsAvailablePage/>}
/>
<Route path='/shop' element={<ShopPage/>}/>
{
// <Route
// path='/shop/templates'
// element={<ShopTemplates />}
// />
}
<Route
path='/shop/vendors'
element={<ShopVendorPageContainer/>}
/>
<Route
path='/shop/csi'
element={<ShopCsiPageContainer/>}
/>
<Route
exact
path={`${match.path}/courtesycars/contracts/new`}
component={ContractCreatePage}
/>
<Route
exact
path={`${match.path}/courtesycars/contracts/:contractId`}
component={ContractDetailPage}
/>
<Route
path='/accounting/qbo'
element={<AccountingQboCallback/>}
/>
<Route
exact
path={`${match.path}/courtesycars/:ccId`}
component={CourtesyCarDetailContainer}
/>
</Switch>
<Route exact path={`${match.path}/profile`} component={ProfilePage} />
<Route
exact
path={`${match.path}/vehicles`}
component={VehiclesContainer}
/>
<Route
exact
path={`${match.path}/production/list`}
component={ProductionListPage}
/>
<Route
exact
path={`${match.path}/production/board`}
component={ProductionBoardPage}
/>
<Route
exact
path={`${match.path}/vehicles/:vehId`}
component={VehiclesDetailContainer}
/>
<Route exact path={`${match.path}/bills`} component={BillsListPage} />
<Route exact path={`${match.path}/owners`} component={OwnersContainer} />
<Route
exact
path={`${match.path}/owners/:ownerId`}
component={OwnersDetailContainer}
/>
<Route
exact
path={`${match.path}/schedule`}
component={ScheduleContainer}
/>
<Route
exact
path={`${match.path}/available`}
component={JobsAvailablePage}
/>
<Route exact path={`${match.path}/shop/`} component={ShopPage} />
{
// <Route
// exact
// path={`${match.path}/shop/templates`}
// component={ShopTemplates}
// />
}
<Route
exact
path={`${match.path}/shop/vendors`}
component={ShopVendorPageContainer}
/>
<Route
exact
path={`${match.path}/shop/csi`}
component={ShopCsiPageContainer}
/>
<Route
path='/accounting/receivables'
element={<AccountingReceivables/>}
/>
<Route
path='/accounting/payables'
element={<AccountingPayables/>}
/>
<Route
path='/accounting/payments'
element={<AccountingPayments/>}
/>
<Route
path='/accounting/exportlogs'
element={<ExportLogs/>}
/>
<Route path='/ttapprovals' element={<TtApprovals/>}/>
<Route path='/partsqueue' element={<PartsQueue/>}/>
<Route path='/phonebook' element={<Phonebook/>}/>
<Route
exact
path={`${match.path}/accounting/qbo`}
component={AccountingQboCallback}
/>
<Route path='/payments' element={<PaymentsAll/>}/>
<Route path='/shiftclock' element={<ShiftClock/>}/>
<Route path='/scoreboard' element={<Scoreboard/>}/>
<Route
path='/timetickets'
element={<TimeTicketsAll/>}
/>
<Route path='/help' element={<Help/>}/>
<Route path='/emailtest' element={<EmailTest/>}/>
<Route path='/dashboard' element={<Dashboard/>}/>
<Route path='/dms' element={<Dms/>}/>
<Route path='/dmsap' element={<DmsPayables/>}/>
</Routes>
</Suspense>
);
<Route
exact
path={`${match.path}/accounting/receivables`}
component={AccountingReceivables}
/>
<Route
exact
path={`${match.path}/accounting/payables`}
component={AccountingPayables}
/>
<Route
exact
path={`${match.path}/accounting/payments`}
component={AccountingPayments}
/>
<Route
exact
path={`${match.path}/accounting/exportlogs`}
component={ExportLogs}
/>
<Route exact path={`${match.path}/ttapprovals`} component={TtApprovals} />
<Route exact path={`${match.path}/partsqueue`} component={PartsQueue} />
<Route exact path={`${match.path}/phonebook`} component={Phonebook} />
let PageContent;
<Route exact path={`${match.path}/payments`} component={PaymentsAll} />
<Route exact path={`${match.path}/shiftclock`} component={ShiftClock} />
<Route exact path={`${match.path}/scoreboard`} component={Scoreboard} />
<Route
exact
path={`${match.path}/timetickets`}
component={TimeTicketsAll}
/>
<Route exact path={`${match.path}/help`} component={Help} />
<Route exact path={`${match.path}/emailtest`} component={EmailTest} />
<Route exact path={`${match.path}/dashboard`} component={Dashboard} />
<Route exact path={`${match.path}/dms`} component={Dms} />
<Route exact path={`${match.path}/dmsap`} component={DmsPayables} />
</Suspense>
);
if (conflict) PageContent = <ConflictComponent/>;
else if (bodyshop && bodyshop.sub_status !== "active")
PageContent = <ShopSubStatusComponent/>;
else PageContent = AppRouteTable;
let PageContent;
return (
<>
<ChatAffixContainer bodyshop={bodyshop} chatVisible={chatVisible}/>
<Layout style={{minHeight: '100vh'}} className="layout-container">
<UpdateAlert/>
<HeaderContainer/>
<Content className="content-container">
<PartnerPingComponent/>
<Sentry.ErrorBoundary fallback={<ErrorBoundary/>} showDialog>
{PageContent}
</Sentry.ErrorBoundary>
if (conflict) PageContent = <ConflictComponent />;
else if (bodyshop && bodyshop.sub_status !== "active")
PageContent = <ShopSubStatusComponent />;
else PageContent = AppRouteTable;
<FloatButton.BackTop style={{right: 100, bottom: 25}}/>
return (
<>
<ChatAffixContainer />
<Layout className="layout-container">
<UpdateAlert />
<HeaderContainer />
<Content className="content-container">
<PartnerPingComponent />
<Sentry.ErrorBoundary fallback={<ErrorBoundary />} showDialog>
{PageContent}
</Sentry.ErrorBoundary>
<BackTop />
<Footer>
<div
style={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
margin: "1rem 0rem",
}}
>
<div style={{ display: "flex" }}>
<div>
{`${t("titles.app")} ${
process.env.REACT_APP_GIT_SHA
} - ${preval`module.exports = new Date().toLocaleString("en-US", {timeZone: "America/Los_Angeles"});`}`}
</div>
<div id="noticeable-widget" style={{ marginLeft: "1rem" }} />
</div>
<Link to="/disclaimer" target="_blank" style={{ color: "#ccc" }}>
Disclaimer & Notices
</Link>
</div>
</Footer>
</Content>
</Layout>
</>
);
</Content>
<Footer>
<div
style={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
margin: "1rem 0rem",
}}
>
<div style={{display: "flex"}}>
<div>
{`${t("titles.app")} ${
process.env.REACT_APP_GIT_SHA
} - ${preval`module.exports = new Date().toLocaleString("en-US", {timeZone: "America/Los_Angeles"});`}`}
</div>
<div id="noticeable-widget" style={{marginLeft: "1rem"}}/>
</div>
<Link to="/disclaimer" target="_blank" style={{color: "#ccc"}}>
Disclaimer & Notices
</Link>
</div>
</Footer>
</Layout>
</>
);
}
export default connect(mapStateToProps, null)(Manage);

View File

@@ -1,37 +1,37 @@
import { useQuery } from "@apollo/client";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import {useQuery} from "@apollo/client";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
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 {QUERY_BODYSHOP} from "../../graphql/bodyshop.queries";
import {setBodyshop} from "../../redux/user/user.actions";
//import "../../utils/RegisterSw";
import ManagePage from "./manage.page.component";
const mapDispatchToProps = (dispatch) => ({
setBodyshop: (bs) => dispatch(setBodyshop(bs)),
setBodyshop: (bs) => dispatch(setBodyshop(bs)),
});
function ManagePageContainer({ match, setBodyshop }) {
const { loading, error, data } = useQuery(QUERY_BODYSHOP, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
function ManagePageContainer({setBodyshop}) {
const {loading, error, data} = useQuery(QUERY_BODYSHOP, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
const { t } = useTranslation();
const {t} = useTranslation();
useEffect(() => {
if (data) {
setBodyshop(data.bodyshops[0] || { notfound: true });
}
}, [data, setBodyshop]);
useEffect(() => {
if (data) {
setBodyshop(data.bodyshops[0] || {notfound: true});
}
}, [data, setBodyshop]);
if (loading)
return <LoadingSpinner message={t("general.labels.loadingshop")} />;
if (error) return <AlertComponent message={error.message} type="error" />;
if (loading)
return <LoadingSpinner message={t("general.labels.loadingshop")}/>;
if (error) return <AlertComponent message={error.message} type="error"/>;
return <ManagePage match={match} />;
return <ManagePage/>;
}
export default connect(null, mapDispatchToProps)(ManagePageContainer);

View File

@@ -1,18 +1,18 @@
import { Col, Divider, Row } from "antd";
import {Col, Divider, Row} from "antd";
import React from "react";
import OwnerDetailForm from "../../components/owner-detail-form/owner-detail-form.container";
import OwnerDetailJobsComponent from "../../components/owner-detail-jobs/owner-detail-jobs.component";
export default function OwnersDetailComponent({ owner, refetch }) {
return (
<Row gutter={[16, 16]}>
<Col span={24}>
<OwnerDetailForm owner={owner} refetch={refetch} />
</Col>
<Divider type="horizontal" />
<Col span={24}>
<OwnerDetailJobsComponent owner={owner} />
</Col>
</Row>
);
export default function OwnersDetailComponent({owner, refetch}) {
return (
<Row gutter={[16, 16]}>
<Col span={24}>
<OwnerDetailForm owner={owner} refetch={refetch}/>
</Col>
<Divider type="horizontal"/>
<Col span={24}>
<OwnerDetailJobsComponent owner={owner}/>
</Col>
</Row>
);
}

View File

@@ -1,76 +1,73 @@
import { useQuery } from "@apollo/client";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import {useQuery} from "@apollo/client";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {useParams} from 'react-router-dom';
import AlertComponent from "../../components/alert/alert.component";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { QUERY_OWNER_BY_ID } from "../../graphql/owners.queries";
import {
addRecentItem,
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import { CreateRecentItem } from "../../utils/create-recent-item";
import {QUERY_OWNER_BY_ID} from "../../graphql/owners.queries";
import {addRecentItem, setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import {CreateRecentItem} from "../../utils/create-recent-item";
import OwnersDetailComponent from "./owners-detail.page.component";
import NotFound from "../../components/not-found/not-found.component";
import { OwnerNameDisplayFunction } from "../../components/owner-name-display/owner-name-display.component";
import {OwnerNameDisplayFunction} from "../../components/owner-name-display/owner-name-display.component";
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
addRecentItem: (item) => dispatch(addRecentItem(item)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
addRecentItem: (item) => dispatch(addRecentItem(item)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function OwnersDetailContainer({
match,
setBreadcrumbs,
addRecentItem,
setSelectedHeader,
}) {
const { ownerId } = match.params;
const { t } = useTranslation();
setBreadcrumbs,
addRecentItem,
setSelectedHeader,
}) {
const {ownerId} = useParams();
const {t} = useTranslation();
const { loading, data, error, refetch } = useQuery(QUERY_OWNER_BY_ID, {
variables: { id: ownerId },
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
useEffect(() => {
document.title = t("titles.owners-detail", {
name: data ? OwnerNameDisplayFunction(data.owners_by_pk) : "",
const {loading, data, error, refetch} = useQuery(QUERY_OWNER_BY_ID, {
variables: {id: ownerId},
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
setSelectedHeader("owners");
setBreadcrumbs([
{ link: "/manage/owners", label: t("titles.bc.owners") },
{
link: `/manage/owners/${ownerId}`,
label: t("titles.bc.owner-detail", {
name: data ? OwnerNameDisplayFunction(data.owners_by_pk) : "",
}),
},
]);
if (data && data.owners_by_pk)
addRecentItem(
CreateRecentItem(
ownerId,
"owner",
OwnerNameDisplayFunction(data.owners_by_pk),
`/manage/owners/${ownerId}`
)
);
}, [setBreadcrumbs, t, data, ownerId, addRecentItem, setSelectedHeader]);
useEffect(() => {
document.title = t("titles.owners-detail", {
name: data ? OwnerNameDisplayFunction(data.owners_by_pk) : "",
});
setSelectedHeader("owners");
setBreadcrumbs([
{link: "/manage/owners", label: t("titles.bc.owners")},
{
link: `/manage/owners/${ownerId}`,
label: t("titles.bc.owner-detail", {
name: data ? OwnerNameDisplayFunction(data.owners_by_pk) : "",
}),
},
]);
if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />;
if (!!!data.owners_by_pk) return <NotFound />;
if (data && data.owners_by_pk)
addRecentItem(
CreateRecentItem(
ownerId,
"owner",
OwnerNameDisplayFunction(data.owners_by_pk),
`/manage/owners/${ownerId}`
)
);
}, [setBreadcrumbs, t, data, ownerId, addRecentItem, setSelectedHeader]);
return (
<RbacWrapper action="owners:detail">
<OwnersDetailComponent owner={data.owners_by_pk} refetch={refetch} />
</RbacWrapper>
);
if (loading) return <LoadingSpinner/>;
if (error) return <AlertComponent message={error.message} type="error"/>;
if (!!!data.owners_by_pk) return <NotFound/>;
return (
<RbacWrapper action="owners:detail">
<OwnersDetailComponent owner={data.owners_by_pk} refetch={refetch}/>
</RbacWrapper>
);
}
export default connect(null, mapDispatchToProps)(OwnersDetailContainer);

View File

@@ -2,5 +2,5 @@ import React from "react";
import OwnersListContainer from "../../components/owners-list/owners-list.container";
export default function OwnersPageComponent() {
return <OwnersListContainer />;
return <OwnersListContainer/>;
}

View File

@@ -1,30 +1,31 @@
import React, { useEffect } from "react";
import React, {useEffect} from "react";
import OwnersPageComponent from "./owners.page.component";
import { useTranslation } from "react-i18next";
import {useTranslation} from "react-i18next";
import {
setBreadcrumbs,
setSelectedHeader,
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import { connect } from "react-redux";
import {connect} from "react-redux";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function OwnersPageContainer({ setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation();
useEffect(() => {
document.title = t("titles.owners");
setSelectedHeader("owners");
setBreadcrumbs([{ link: "/manage/owners", label: t("titles.bc.owners") }]);
}, [t, setBreadcrumbs, setSelectedHeader]);
export function OwnersPageContainer({setBreadcrumbs, setSelectedHeader}) {
const {t} = useTranslation();
useEffect(() => {
document.title = t("titles.owners");
setSelectedHeader("owners");
setBreadcrumbs([{link: "/manage/owners", label: t("titles.bc.owners")}]);
}, [t, setBreadcrumbs, setSelectedHeader]);
return (
<RbacWrapper action="owners:list">
<OwnersPageComponent />
</RbacWrapper>
);
return (
<RbacWrapper action="owners:list">
<OwnersPageComponent/>
</RbacWrapper>
);
}
export default connect(null, mapDispatchToProps)(OwnersPageContainer);

View File

@@ -1,315 +1,317 @@
import { SyncOutlined } from "@ant-design/icons";
import { useQuery } from "@apollo/client";
import { Button, Card, Input, Space, Table } from "antd";
import {SyncOutlined} from "@ant-design/icons";
import {useQuery} from "@apollo/client";
import {Button, Card, Input, Space, Table} from "antd";
import _ from "lodash";
import queryString from "query-string";
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 React, {useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {Link, useLocation, useNavigate} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import AlertComponent from "../../components/alert/alert.component";
import JobPartsQueueCount from "../../components/job-parts-queue-count/job-parts-queue-count.component";
import JobRemoveFromPartsQueue from "../../components/job-remove-from-parst-queue/job-remove-from-parts-queue.component";
import JobRemoveFromPartsQueue
from "../../components/job-remove-from-parst-queue/job-remove-from-parts-queue.component";
import OwnerNameDisplay from "../../components/owner-name-display/owner-name-display.component";
import ProductionListColumnComment from "../../components/production-list-columns/production-list-columns.comment.component";
import { QUERY_PARTS_QUEUE } from "../../graphql/jobs.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { DateTimeFormatter, TimeAgoFormatter } from "../../utils/DateFormatter";
import { alphaSort, dateSort } from "../../utils/sorters";
import ProductionListColumnComment
from "../../components/production-list-columns/production-list-columns.comment.component";
import {QUERY_PARTS_QUEUE} from "../../graphql/jobs.queries";
import {selectBodyshop} from "../../redux/user/user.selectors";
import {DateTimeFormatter, TimeAgoFormatter} from "../../utils/DateFormatter";
import {alphaSort, dateSort} from "../../utils/sorters";
import useLocalStorage from "../../utils/useLocalStorage";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
bodyshop: selectBodyshop,
});
export function PartsQueuePageComponent({ bodyshop }) {
const searchParams = queryString.parse(useLocation().search);
const {
//page,
sortcolumn,
sortorder,
statusFilters,
} = searchParams;
const history = useHistory();
const [filter, setFilter] = useLocalStorage("filter_parts_queue", null);
export function PartsQueuePageComponent({bodyshop}) {
const searchParams = queryString.parse(useLocation().search);
const {
//page,
sortcolumn,
sortorder,
statusFilters,
} = searchParams;
const history = useNavigate();
const [filter, setFilter] = useLocalStorage("filter_parts_queue", null);
const { loading, error, data, refetch } = useQuery(QUERY_PARTS_QUEUE, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
variables: {
// offset: page ? (page - 1) * 25 : 0,
// limit: 25,
statuses: (statusFilters && JSON.parse(statusFilters)) ||
bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"],
order: [
{
[sortcolumn || "ro_number"]: sortorder
? sortorder === "descend"
? "desc"
: "asc"
: "desc",
const {loading, error, data, refetch} = useQuery(QUERY_PARTS_QUEUE, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
variables: {
// offset: page ? (page - 1) * 25 : 0,
// limit: 25,
statuses: (statusFilters && JSON.parse(statusFilters)) ||
bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"],
order: [
{
[sortcolumn || "ro_number"]: sortorder
? sortorder === "descend"
? "desc"
: "asc"
: "desc",
},
],
},
],
},
});
});
const { t } = useTranslation();
const [searchText, setSearchText] = useState("");
const {t} = useTranslation();
const [searchText, setSearchText] = useState("");
if (error) return <AlertComponent message={error.message} type="error" />;
if (error) return <AlertComponent message={error.message} type="error"/>;
const jobs = data
? searchText === ""
? data.jobs
: data.jobs.filter(
(j) =>
(j.ro_number || "")
.toString()
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_co_nm || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_fn || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_ln || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) ||
(j.plate_no || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.v_model_desc || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.v_make_desc || "")
.toLowerCase()
.includes(searchText.toLowerCase())
)
: [];
const jobs = data
? searchText === ""
? data.jobs
: data.jobs.filter(
(j) =>
(j.ro_number || "")
.toString()
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_co_nm || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_fn || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_ln || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) ||
(j.plate_no || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.v_model_desc || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.v_make_desc || "")
.toLowerCase()
.includes(searchText.toLowerCase())
)
: [];
const handleTableChange = (pagination, filters, sorter) => {
// searchParams.page = pagination.current;
searchParams.sortcolumn = sorter.columnKey;
searchParams.sortorder = sorter.order;
const handleTableChange = (pagination, filters, sorter) => {
// searchParams.page = pagination.current;
searchParams.sortcolumn = sorter.columnKey;
searchParams.sortorder = sorter.order;
if (filters.status) {
searchParams.statusFilters = JSON.stringify(
_.flattenDeep(filters.status)
);
} else {
delete searchParams.statusFilters;
}
setFilter(filters);
history.push({ search: queryString.stringify(searchParams) });
};
if (filters.status) {
searchParams.statusFilters = JSON.stringify(
_.flattenDeep(filters.status)
);
} else {
delete searchParams.statusFilters;
}
setFilter(filters);
history({search: queryString.stringify(searchParams)});
};
const columns = [
{
title: t("jobs.fields.ro_number"),
dataIndex: "ro_number",
key: "ro_number",
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
sortOrder: sortcolumn === "ro_number" && sortorder,
const columns = [
{
title: t("jobs.fields.ro_number"),
dataIndex: "ro_number",
key: "ro_number",
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
sortOrder: sortcolumn === "ro_number" && sortorder,
render: (text, record) => (
<Link to={"/manage/jobs/" + record.id}>
{record.ro_number || t("general.labels.na")}
</Link>
),
},
{
title: t("jobs.fields.owner"),
dataIndex: "ownr_ln",
key: "ownr_ln",
sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
sortOrder: sortcolumn === "ownr_ln" && sortorder,
render: (text, record) => {
return record.ownerid ? (
<Link to={"/manage/owners/" + record.ownerid}>
<OwnerNameDisplay ownerObject={record} />
</Link>
) : (
<span>
<OwnerNameDisplay ownerObject={record} />
render: (text, record) => (
<Link to={"/manage/jobs/" + record.id}>
{record.ro_number || t("general.labels.na")}
</Link>
),
},
{
title: t("jobs.fields.owner"),
dataIndex: "ownr_ln",
key: "ownr_ln",
sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
sortOrder: sortcolumn === "ownr_ln" && sortorder,
render: (text, record) => {
return record.ownerid ? (
<Link to={"/manage/owners/" + record.ownerid}>
<OwnerNameDisplay ownerObject={record}/>
</Link>
) : (
<span>
<OwnerNameDisplay ownerObject={record}/>
</span>
);
},
},
{
title: t("jobs.fields.status"),
dataIndex: "status",
key: "status",
sorter: (a, b) => alphaSort(a.status, b.status),
sortOrder: sortcolumn === "status" && sortorder,
filteredValue: statusFilters ? JSON.parse(statusFilters) : null,
filters:
bodyshop.md_ro_statuses.active_statuses.map((s) => {
return {
text: s || "No Status*",
value: [s],
};
}) || [],
render: (text, record) => {
return record.status || t("general.labels.na");
},
},
{
title: t("jobs.fields.scheduled_in"),
dataIndex: "scheduled_in",
key: "scheduled_in",
ellipsis: true,
sorter: (a, b) => dateSort(a.scheduled_in, b.scheduled_in),
sortOrder: sortcolumn === "scheduled_in" && sortorder,
render: (text, record) => (
<DateTimeFormatter>{record.scheduled_in}</DateTimeFormatter>
),
},
{
title: t("jobs.fields.vehicle"),
dataIndex: "vehicle",
key: "vehicle",
ellipsis: true,
render: (text, record) => {
return record.vehicleid ? (
<Link to={"/manage/vehicles/" + record.vehicleid}>
{`${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("vehicles.fields.plate_no"),
// dataIndex: "plate_no",
// key: "plate_no",
// sorter: (a, b) => alphaSort(a.plate_no, b.plate_no),
// sortOrder: sortcolumn === "plate_no" && sortorder,
// render: (text, record) => {
// return record.plate_no ? record.plate_no : "";
// },
// },
{
title: t("jobs.fields.clm_no"),
dataIndex: "clm_no",
key: "clm_no",
ellipsis: true,
sorter: (a, b) => alphaSort(a.clm_no, b.clm_no),
sortOrder: sortcolumn === "clm_no" && sortorder,
},
// {
// title: t("jobs.fields.clm_total"),
// dataIndex: "clm_total",
// key: "clm_total",
// sorter: (a, b) => a.clm_total - b.clm_total,
// sortOrder: sortcolumn === "clm_total" && sortorder,
// render: (text, record) => {
// return record.clm_total ? (
// <CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
// ) : (
// t("general.labels.unknown")
// );
// },
// },
{
title: t("jobs.fields.updated_at"),
dataIndex: "updated_at",
key: "updated_at",
sorter: (a, b) => dateSort(a.updated_at, b.updated_at),
sortOrder: sortcolumn === "updated_at" && sortorder,
render: (text, record) => (
<TimeAgoFormatter>{record.updated_at}</TimeAgoFormatter>
),
},
{
title: t("jobs.fields.partsstatus"),
dataIndex: "partsstatus",
key: "partsstatus",
render: (text, record) => (
<JobPartsQueueCount
style={{ minWidth: "10rem" }}
parts={record.joblines_status}
/>
),
},
{
title: t("jobs.fields.comment"),
dataIndex: "comment",
key: "comment",
render: (text, record) => <ProductionListColumnComment record={record} />,
},
{
title: t("jobs.fields.queued_for_parts"),
dataIndex: "queued_for_parts",
key: "queued_for_parts",
sorter: (a, b) => a.queued_for_parts - b.queued_for_parts,
sortOrder: sortcolumn === "queued_for_parts" && sortorder,
filteredValue: filter?.queued_for_parts || null,
filters: [
{
text: "Queued",
value: true,
);
},
},
{
text: "Unqueued",
value: false,
title: t("jobs.fields.status"),
dataIndex: "status",
key: "status",
sorter: (a, b) => alphaSort(a.status, b.status),
sortOrder: sortcolumn === "status" && sortorder,
filteredValue: statusFilters ? JSON.parse(statusFilters) : null,
filters:
bodyshop.md_ro_statuses.active_statuses.map((s) => {
return {
text: s || "No Status*",
value: [s],
};
}) || [],
render: (text, record) => {
return record.status || t("general.labels.na");
},
},
],
onFilter: (value, record) => record.queued_for_parts === value,
render: (text, record) => (
<JobRemoveFromPartsQueue
checked={record.queued_for_parts}
jobId={record.id}
/>
),
},
];
return (
<Card
extra={
<Space wrap>
<Button onClick={() => refetch()}>
<SyncOutlined />
</Button>
<Input.Search
className="imex-table-header__search"
placeholder={t("general.labels.search")}
onChange={(e) => {
setSearchText(e.target.value);
}}
value={searchText}
enterButton
/>
</Space>
}
>
<Table
loading={loading}
pagination={{
position: "top",
pageSize: pageLimit,
// current: parseInt(page || 1),
// total: data && data.jobs_aggregate.aggregate.count,
}}
columns={columns}
rowKey="id"
dataSource={jobs}
style={{ height: "100%" }}
scroll={{ x: true }}
onChange={handleTableChange}
/>
</Card>
);
{
title: t("jobs.fields.scheduled_in"),
dataIndex: "scheduled_in",
key: "scheduled_in",
ellipsis: true,
sorter: (a, b) => dateSort(a.scheduled_in, b.scheduled_in),
sortOrder: sortcolumn === "scheduled_in" && sortorder,
render: (text, record) => (
<DateTimeFormatter>{record.scheduled_in}</DateTimeFormatter>
),
},
{
title: t("jobs.fields.vehicle"),
dataIndex: "vehicle",
key: "vehicle",
ellipsis: true,
render: (text, record) => {
return record.vehicleid ? (
<Link to={"/manage/vehicles/" + record.vehicleid}>
{`${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("vehicles.fields.plate_no"),
// dataIndex: "plate_no",
// key: "plate_no",
// sorter: (a, b) => alphaSort(a.plate_no, b.plate_no),
// sortOrder: sortcolumn === "plate_no" && sortorder,
// render: (text, record) => {
// return record.plate_no ? record.plate_no : "";
// },
// },
{
title: t("jobs.fields.clm_no"),
dataIndex: "clm_no",
key: "clm_no",
ellipsis: true,
sorter: (a, b) => alphaSort(a.clm_no, b.clm_no),
sortOrder: sortcolumn === "clm_no" && sortorder,
},
// {
// title: t("jobs.fields.clm_total"),
// dataIndex: "clm_total",
// key: "clm_total",
// sorter: (a, b) => a.clm_total - b.clm_total,
// sortOrder: sortcolumn === "clm_total" && sortorder,
// render: (text, record) => {
// return record.clm_total ? (
// <CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
// ) : (
// t("general.labels.unknown")
// );
// },
// },
{
title: t("jobs.fields.updated_at"),
dataIndex: "updated_at",
key: "updated_at",
sorter: (a, b) => dateSort(a.updated_at, b.updated_at),
sortOrder: sortcolumn === "updated_at" && sortorder,
render: (text, record) => (
<TimeAgoFormatter>{record.updated_at}</TimeAgoFormatter>
),
},
{
title: t("jobs.fields.partsstatus"),
dataIndex: "partsstatus",
key: "partsstatus",
render: (text, record) => (
<JobPartsQueueCount
style={{minWidth: "10rem"}}
parts={record.joblines_status}
/>
),
},
{
title: t("jobs.fields.comment"),
dataIndex: "comment",
key: "comment",
render: (text, record) => <ProductionListColumnComment record={record}/>,
},
{
title: t("jobs.fields.queued_for_parts"),
dataIndex: "queued_for_parts",
key: "queued_for_parts",
sorter: (a, b) => a.queued_for_parts - b.queued_for_parts,
sortOrder: sortcolumn === "queued_for_parts" && sortorder,
filteredValue: filter?.queued_for_parts || null,
filters: [
{
text: "Queued",
value: true,
},
{
text: "Unqueued",
value: false,
},
],
onFilter: (value, record) => record.queued_for_parts === value,
render: (text, record) => (
<JobRemoveFromPartsQueue
checked={record.queued_for_parts}
jobId={record.id}
/>
),
},
];
return (
<Card
extra={
<Space wrap>
<Button onClick={() => refetch()}>
<SyncOutlined/>
</Button>
<Input.Search
className="imex-table-header__search"
placeholder={t("general.labels.search")}
onChange={(e) => {
setSearchText(e.target.value);
}}
value={searchText}
enterButton
/>
</Space>
}
>
<Table
loading={loading}
pagination={{
position: "top",
pageSize: pageLimit,
// current: parseInt(page || 1),
// total: data && data.jobs_aggregate.aggregate.count,
}}
columns={columns}
rowKey="id"
dataSource={jobs}
style={{height: "100%"}}
scroll={{x: true}}
onChange={handleTableChange}
/>
</Card>
);
}
export default connect(mapStateToProps, null)(PartsQueuePageComponent);

View File

@@ -1,33 +1,31 @@
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import PartsQueuePage from "./parts-queue.page.component";
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function PartsQueuePageContainer({ setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation();
export function PartsQueuePageContainer({setBreadcrumbs, setSelectedHeader}) {
const {t} = useTranslation();
useEffect(() => {
document.title = t("titles.parts-queue");
setSelectedHeader("parts-queue");
setBreadcrumbs([
{ link: "/manage/partsqueue", label: t("titles.bc.parts-queue") },
]);
}, [setBreadcrumbs, t, setSelectedHeader]);
useEffect(() => {
document.title = t("titles.parts-queue");
setSelectedHeader("parts-queue");
setBreadcrumbs([
{link: "/manage/partsqueue", label: t("titles.bc.parts-queue")},
]);
}, [setBreadcrumbs, t, setSelectedHeader]);
return (
<RbacWrapper action="jobs:partsqueue">
<PartsQueuePage />
</RbacWrapper>
);
return (
<RbacWrapper action="jobs:partsqueue">
<PartsQueuePage/>
</RbacWrapper>
);
}
export default connect(null, mapDispatchToProps)(PartsQueuePageContainer);

View File

@@ -1,78 +1,75 @@
import { useQuery } from "@apollo/client";
import {useQuery} from "@apollo/client";
import queryString from "query-string";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {useLocation} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import AlertComponent from "../../components/alert/alert.component";
import PaymentsListPaginated from "../../components/payments-list-paginated/payment-list-paginated.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { QUERY_ALL_PAYMENTS_PAGINATED } from "../../graphql/payments.queries";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {QUERY_ALL_PAYMENTS_PAGINATED} from "../../graphql/payments.queries";
import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import {selectBodyshop} from "../../redux/user/user.selectors";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function AllJobs({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
const searchParams = queryString.parse(useLocation().search);
const { page, sortcolumn, sortorder, searchObj } = searchParams;
export function AllJobs({bodyshop, setBreadcrumbs, setSelectedHeader}) {
const searchParams = queryString.parse(useLocation().search);
const {page, sortcolumn, sortorder, searchObj} = searchParams;
const { loading, error, data, refetch } = useQuery(
QUERY_ALL_PAYMENTS_PAGINATED,
{
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
variables: {
offset: page ? (page - 1) * pageLimit : 0,
limit: pageLimit,
order: [
searchObj
? JSON.parse(searchObj)
: {
[sortcolumn || "date"]: sortorder
? sortorder === "descend"
? "desc"
: "asc"
: "desc",
},
],
},
}
);
const { t } = useTranslation();
const {loading, error, data, refetch} = useQuery(
QUERY_ALL_PAYMENTS_PAGINATED,
{
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
variables: {
offset: page ? (page - 1) * pageLimit : 0,
limit: pageLimit,
order: [
searchObj
? JSON.parse(searchObj)
: {
[sortcolumn || "date"]: sortorder
? sortorder === "descend"
? "desc"
: "asc"
: "desc",
},
],
},
}
);
const {t} = useTranslation();
useEffect(() => {
document.title = t("titles.payments-all");
setSelectedHeader("allpayments");
setBreadcrumbs([
{ link: "/manage/payments", label: t("titles.bc.payments-all") },
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
useEffect(() => {
document.title = t("titles.payments-all");
setSelectedHeader("allpayments");
setBreadcrumbs([
{link: "/manage/payments", label: t("titles.bc.payments-all")},
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
if (error) return <AlertComponent message={error.message} type="error" />;
return (
<RbacWrapper action="payments:list">
<PaymentsListPaginated
refetch={refetch}
loading={loading}
searchParams={searchParams}
total={data ? data.payments_aggregate.aggregate.count : 0}
payments={data ? data.payments : []}
/>
</RbacWrapper>
);
if (error) return <AlertComponent message={error.message} type="error"/>;
return (
<RbacWrapper action="payments:list">
<PaymentsListPaginated
refetch={refetch}
loading={loading}
searchParams={searchParams}
total={data ? data.payments_aggregate.aggregate.count : 0}
payments={data ? data.payments : []}
/>
</RbacWrapper>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(AllJobs);

View File

@@ -1,219 +1,216 @@
import { SyncOutlined } from "@ant-design/icons";
import { useQuery } from "@apollo/client";
import { Button, Card, Input, Space, Table, Typography } from "antd";
import {SyncOutlined} from "@ant-design/icons";
import {useQuery} from "@apollo/client";
import {Button, Card, Input, Space, Table, Typography} from "antd";
import _ from "lodash";
import queryString from "query-string";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useHistory, useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {useLocation, useNavigate} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import AlertComponent from "../../components/alert/alert.component";
import { QUERY_PHONEBOOK_PAGINATED } from "../../graphql/phonebook.queries";
import {
selectAuthLevel,
selectBodyshop,
} from "../../redux/user/user.selectors";
import {QUERY_PHONEBOOK_PAGINATED} from "../../graphql/phonebook.queries";
import {selectAuthLevel, selectBodyshop,} from "../../redux/user/user.selectors";
import ChatOpenButton from "../../components/chat-open-button/chat-open-button.component";
import { alphaSort } from "../../utils/sorters";
import { HasRbacAccess } from "../../components/rbac-wrapper/rbac-wrapper.component";
import {alphaSort} from "../../utils/sorters";
import {HasRbacAccess} from "../../components/rbac-wrapper/rbac-wrapper.component";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
authLevel: selectAuthLevel,
bodyshop: selectBodyshop,
authLevel: selectAuthLevel,
});
export function PhonebookPageComponent({ bodyshop, authLevel }) {
const searchParams = queryString.parse(useLocation().search);
const { page, sortcolumn, sortorder, search, phonebookentry } = searchParams;
const history = useHistory();
export function PhonebookPageComponent({bodyshop, authLevel}) {
const searchParams = queryString.parse(useLocation().search);
const {page, sortcolumn, sortorder, search, phonebookentry} = searchParams;
const history = useNavigate();
const { loading, error, data, refetch } = useQuery(
QUERY_PHONEBOOK_PAGINATED,
{
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
variables: {
search: search || "",
offset: page ? (page - 1) * pageLimit : 0,
limit: pageLimit,
order: [
{
[sortcolumn || "lastname"]: sortorder
? sortorder === "descend"
? "desc"
: "asc"
: "asc",
},
],
},
}
);
const { t } = useTranslation();
if (error) return <AlertComponent message={error.message} type="error" />;
const handleTableChange = (pagination, filters, sorter) => {
searchParams.page = pagination.current;
searchParams.sortcolumn = sorter.columnKey;
searchParams.sortorder = sorter.order;
if (filters.status) {
searchParams.statusFilters = JSON.stringify(
_.flattenDeep(filters.status)
);
} else {
delete searchParams.statusFilters;
}
history.push({ search: queryString.stringify(searchParams) });
};
const columns = [
{
title: t("phonebook.fields.firstname"),
dataIndex: "firstname",
key: "firstname",
sorter: (a, b) => alphaSort(a.firstname, b.firstname),
sortOrder: sortcolumn === "firstname" && sortorder,
},
{
title: t("phonebook.fields.lastname"),
dataIndex: "lastname",
key: "lastname",
sorter: (a, b) => alphaSort(a.lastname, b.lastname),
sortOrder: sortcolumn === "lastname" && sortorder,
},
{
title: t("phonebook.fields.company"),
dataIndex: "company",
key: "company",
sorter: (a, b) => alphaSort(a.company, b.company),
sortOrder: sortcolumn === "company" && sortorder,
},
{
title: t("phonebook.fields.category"),
dataIndex: "category",
key: "category",
sorter: (a, b) => alphaSort(a.category, b.category),
sortOrder: sortcolumn === "category" && sortorder,
},
{
title: t("phonebook.fields.email"),
dataIndex: "email",
key: "email",
},
{
title: t("phonebook.fields.phone1"),
dataIndex: "phone1",
key: "phone1",
render: (text, record) => <ChatOpenButton phone={text} />,
},
{
title: t("phonebook.fields.phone2"),
dataIndex: "phone2",
key: "phone2",
render: (text, record) => <ChatOpenButton phone={text} />,
},
{
title: t("phonebook.fields.address1"),
dataIndex: "address1",
key: "address1",
},
{
title: t("phonebook.fields.city"),
dataIndex: "city",
key: "city",
},
];
const handleNewPhonebook = () => {
searchParams.phonebookentry = "new";
history.push({ search: queryString.stringify(searchParams) });
};
const handleOnRowClick = (record) => {
if (record) {
searchParams.phonebookentry = record.id;
history.push({ search: queryString.stringify(searchParams) });
} else {
delete searchParams.phonebookentry;
history.push({ search: queryString.stringify(searchParams) });
}
};
const hasNoAccess = !HasRbacAccess({
bodyshop,
authLevel,
action: "phonebook:edit",
});
return (
<Card
extra={
<Space wrap>
{searchParams.search && (
<>
<Typography.Title level={4}>
{t("general.labels.searchresults", {
search: searchParams.search,
})}
</Typography.Title>
<Button
onClick={() => {
delete searchParams.search;
searchParams.page = 1;
history.push({ search: queryString.stringify(searchParams) });
}}
>
{t("general.actions.clear")}
</Button>
</>
)}
<Button disabled={hasNoAccess} onClick={handleNewPhonebook}>
{t("phonebook.actions.new")}
</Button>
<Button onClick={() => refetch()}>
<SyncOutlined />
</Button>
<Input.Search
placeholder={searchParams.search || t("general.labels.search")}
onSearch={(value) => {
searchParams.search = value;
searchParams.page = 1;
history.push({ search: queryString.stringify(searchParams) });
}}
/>
</Space>
}
>
<Table
loading={loading}
pagination={{
position: "top",
pageSize: pageLimit,
current: parseInt(page || 1),
total: data && data.search_phonebook_aggregate.aggregate.count,
}}
columns={columns}
rowKey="id"
dataSource={data && data.search_phonebook}
//scroll={{ x: true }}
onChange={handleTableChange}
rowSelection={{
onSelect: handleOnRowClick,
type: "radio",
selectedRowKeys: [phonebookentry],
}}
onRow={(record, rowIndex) => {
return {
onClick: (event) => {
handleOnRowClick(record);
const {loading, error, data, refetch} = useQuery(
QUERY_PHONEBOOK_PAGINATED,
{
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
variables: {
search: search || "",
offset: page ? (page - 1) * pageLimit : 0,
limit: pageLimit,
order: [
{
[sortcolumn || "lastname"]: sortorder
? sortorder === "descend"
? "desc"
: "asc"
: "asc",
},
],
},
};
}}
/>
</Card>
);
}
);
const {t} = useTranslation();
if (error) return <AlertComponent message={error.message} type="error"/>;
const handleTableChange = (pagination, filters, sorter) => {
searchParams.page = pagination.current;
searchParams.sortcolumn = sorter.columnKey;
searchParams.sortorder = sorter.order;
if (filters.status) {
searchParams.statusFilters = JSON.stringify(
_.flattenDeep(filters.status)
);
} else {
delete searchParams.statusFilters;
}
history({search: queryString.stringify(searchParams)});
};
const columns = [
{
title: t("phonebook.fields.firstname"),
dataIndex: "firstname",
key: "firstname",
sorter: (a, b) => alphaSort(a.firstname, b.firstname),
sortOrder: sortcolumn === "firstname" && sortorder,
},
{
title: t("phonebook.fields.lastname"),
dataIndex: "lastname",
key: "lastname",
sorter: (a, b) => alphaSort(a.lastname, b.lastname),
sortOrder: sortcolumn === "lastname" && sortorder,
},
{
title: t("phonebook.fields.company"),
dataIndex: "company",
key: "company",
sorter: (a, b) => alphaSort(a.company, b.company),
sortOrder: sortcolumn === "company" && sortorder,
},
{
title: t("phonebook.fields.category"),
dataIndex: "category",
key: "category",
sorter: (a, b) => alphaSort(a.category, b.category),
sortOrder: sortcolumn === "category" && sortorder,
},
{
title: t("phonebook.fields.email"),
dataIndex: "email",
key: "email",
},
{
title: t("phonebook.fields.phone1"),
dataIndex: "phone1",
key: "phone1",
render: (text, record) => <ChatOpenButton phone={text}/>,
},
{
title: t("phonebook.fields.phone2"),
dataIndex: "phone2",
key: "phone2",
render: (text, record) => <ChatOpenButton phone={text}/>,
},
{
title: t("phonebook.fields.address1"),
dataIndex: "address1",
key: "address1",
},
{
title: t("phonebook.fields.city"),
dataIndex: "city",
key: "city",
},
];
const handleNewPhonebook = () => {
searchParams.phonebookentry = "new";
history({search: queryString.stringify(searchParams)});
};
const handleOnRowClick = (record) => {
if (record) {
searchParams.phonebookentry = record.id;
history({search: queryString.stringify(searchParams)});
} else {
delete searchParams.phonebookentry;
history({search: queryString.stringify(searchParams)});
}
};
const hasNoAccess = !HasRbacAccess({
bodyshop,
authLevel,
action: "phonebook:edit",
});
return (
<Card
extra={
<Space wrap>
{searchParams.search && (
<>
<Typography.Title level={4}>
{t("general.labels.searchresults", {
search: searchParams.search,
})}
</Typography.Title>
<Button
onClick={() => {
delete searchParams.search;
searchParams.page = 1;
history({search: queryString.stringify(searchParams)});
}}
>
{t("general.actions.clear")}
</Button>
</>
)}
<Button disabled={hasNoAccess} onClick={handleNewPhonebook}>
{t("phonebook.actions.new")}
</Button>
<Button onClick={() => refetch()}>
<SyncOutlined/>
</Button>
<Input.Search
placeholder={searchParams.search || t("general.labels.search")}
onSearch={(value) => {
searchParams.search = value;
searchParams.page = 1;
history({search: queryString.stringify(searchParams)});
}}
/>
</Space>
}
>
<Table
loading={loading}
pagination={{
position: "top",
pageSize: pageLimit,
current: parseInt(page || 1),
total: data && data.search_phonebook_aggregate.aggregate.count,
}}
columns={columns}
rowKey="id"
dataSource={data && data.search_phonebook}
//scroll={{ x: true }}
onChange={handleTableChange}
rowSelection={{
onSelect: handleOnRowClick,
type: "radio",
selectedRowKeys: [phonebookentry],
}}
onRow={(record, rowIndex) => {
return {
onClick: (event) => {
handleOnRowClick(record);
},
};
}}
/>
</Card>
);
}
export default connect(mapStateToProps, null)(PhonebookPageComponent);

View File

@@ -1,68 +1,69 @@
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import PhonebookPage from "./phonebook.page.component";
import { Drawer, Grid } from "antd";
import queryString from "query-string";
import { useHistory, useLocation } from "react-router-dom";
import {Drawer, Grid} from "antd";
import {useLocation, useNavigate} from "react-router-dom";
import PhonebookFormContainer from "../../components/phonebook-form/phonebook-form.container";
import queryString from "query-string";
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function PhonebookContainer({ setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation();
export function PhonebookContainer({setBreadcrumbs, setSelectedHeader}) {
const {t} = useTranslation();
useEffect(() => {
document.title = t("titles.phonebook");
setSelectedHeader("phonebook");
setBreadcrumbs([
{
link: "/manage/phonebook",
label: t("titles.bc.phonebook"),
},
]);
}, [setBreadcrumbs, t, setSelectedHeader]);
const search = queryString.parse(useLocation().search);
const { phonebookentry } = search;
const history = useHistory();
useEffect(() => {
document.title = t("titles.phonebook");
setSelectedHeader("phonebook");
setBreadcrumbs([
{
link: "/manage/phonebook",
label: t("titles.bc.phonebook"),
},
]);
}, [setBreadcrumbs, t, setSelectedHeader]);
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
.filter((screen) => !!screen[1])
.slice(-1)[0];
const search = queryString.parse(useLocation().search);
const {phonebookentry} = search;
const bpoints = {
xs: "100%",
sm: "100%",
md: "100%",
lg: "50%",
xl: "50%",
xxl: "45%",
};
const drawerPercentage = selectedBreakpoint
? bpoints[selectedBreakpoint[0]]
: "100%";
const navigate = useNavigate();
return (
<RbacWrapper action="phonebook:view">
<PhonebookPage />
<Drawer
width={drawerPercentage}
onClose={() => {
delete search.phonebookentry;
history.push({ search: queryString.stringify(search) });
}}
visible={phonebookentry}
>
<PhonebookFormContainer />
</Drawer>
</RbacWrapper>
);
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
.filter((screen) => !!screen[1])
.slice(-1)[0];
const bpoints = {
xs: "100%",
sm: "100%",
md: "100%",
lg: "50%",
xl: "50%",
xxl: "45%",
};
const drawerPercentage = selectedBreakpoint
? bpoints[selectedBreakpoint[0]]
: "100%";
return (
(<RbacWrapper action="phonebook:view">
<PhonebookPage/>
<Drawer
width={drawerPercentage}
onClose={() => {
delete search.phonebookentry;
navigate({search: queryString.stringify(search)});
}}
open={phonebookentry}
>
<PhonebookFormContainer/>
</Drawer>
</RbacWrapper>)
);
}
export default connect(null, mapDispatchToProps)(PhonebookContainer);
export default connect(null, mapDispatchToProps)(PhonebookContainer);

View File

@@ -2,5 +2,5 @@ import React from "react";
import ProductionBoardKanbanContainer from "../../components/production-board-kanban/production-board-kanban.container";
export default function ProductionBoardComponent() {
return <ProductionBoardKanbanContainer />;
return <ProductionBoardKanbanContainer/>;
}

View File

@@ -1,52 +1,50 @@
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import {selectBodyshop} from "../../redux/user/user.selectors";
import ProductionBoardComponent from "./production-board.component";
import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function ProductionBoardContainer({
setBreadcrumbs,
bodyshop,
setSelectedHeader,
}) {
const { t } = useTranslation();
setBreadcrumbs,
bodyshop,
setSelectedHeader,
}) {
const {t} = useTranslation();
useEffect(() => {
document.title = t("titles.productionboard");
setSelectedHeader("productionboard");
setBreadcrumbs([
{
link: "/manage/production/board",
label: t("titles.bc.productionboard"),
},
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
useEffect(() => {
document.title = t("titles.productionboard");
setSelectedHeader("productionboard");
setBreadcrumbs([
{
link: "/manage/production/board",
label: t("titles.bc.productionboard"),
},
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
return (
<FeatureWrapper featureName="production-board">
<RbacWrapper action="production:board">
<ProductionBoardComponent />
</RbacWrapper>
</FeatureWrapper>
);
return (
<FeatureWrapper featureName="production-board">
<RbacWrapper action="production:board">
<ProductionBoardComponent/>
</RbacWrapper>
</FeatureWrapper>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
mapStateToProps,
mapDispatchToProps
)(ProductionBoardContainer);

View File

@@ -2,5 +2,5 @@ import React from "react";
import ProductionListTable from "../../components/production-list-table/production-list-table.container";
export default function ProductionListComponent() {
return <ProductionListTable />;
return <ProductionListTable/>;
}

View File

@@ -1,37 +1,35 @@
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import ProductionListComponent from "./production-list.component";
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function ProductionListContainer({
setBreadcrumbs,
setBreadcrumbs,
setSelectedHeader,
}) {
const { t } = useTranslation();
setSelectedHeader,
}) {
const {t} = useTranslation();
useEffect(() => {
document.title = t("titles.productionlist");
setSelectedHeader("productionlist");
setBreadcrumbs([
{ link: "/manage/production/list", label: t("titles.bc.productionlist") },
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
useEffect(() => {
document.title = t("titles.productionlist");
setSelectedHeader("productionlist");
setBreadcrumbs([
{link: "/manage/production/list", label: t("titles.bc.productionlist")},
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
return (
<RbacWrapper action="production:list">
<ProductionListComponent />
</RbacWrapper>
);
return (
<RbacWrapper action="production:list">
<ProductionListComponent/>
</RbacWrapper>
);
}
export default connect(null, mapDispatchToProps)(ProductionListContainer);

View File

@@ -1,27 +1,25 @@
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import ProfilePage from "./profile.page";
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function ProfileContainerPage({ setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation();
useEffect(() => {
setSelectedHeader("profile");
setBreadcrumbs([
{ link: "/manage/profile", label: t("titles.bc.profile") },
]);
document.title = t("titles.profile");
}, [t, setBreadcrumbs, setSelectedHeader]);
export function ProfileContainerPage({setBreadcrumbs, setSelectedHeader}) {
const {t} = useTranslation();
useEffect(() => {
setSelectedHeader("profile");
setBreadcrumbs([
{link: "/manage/profile", label: t("titles.bc.profile")},
]);
document.title = t("titles.profile");
}, [t, setBreadcrumbs, setSelectedHeader]);
return <ProfilePage />;
return <ProfilePage/>;
}
export default connect(null, mapDispatchToProps)(ProfileContainerPage);

View File

@@ -1,15 +1,15 @@
import { Row } from "antd";
import {Row} from "antd";
import React from "react";
import ProfileMyComponent from "../../components/profile-my/profile-my.component";
import ProfileShopsContainer from "../../components/profile-shops/profile-shops.container";
export default function ProfilePage() {
return (
<div>
<Row gutter={[16, 16]}>
<ProfileMyComponent />
<ProfileShopsContainer />
</Row>
</div>
);
return (
<div>
<Row gutter={[16, 16]}>
<ProfileMyComponent/>
<ProfileShopsContainer/>
</Row>
</div>
);
}

View File

@@ -1,14 +1,14 @@
import queryString from "query-string";
import React from "react";
import { useLocation } from "react-router-dom";
import {useLocation} from "react-router-dom";
import UserRequestResetPw from "../../components/user-request-pw-reset/user-request-reset-pw.component";
import UserValidatePwReset from "../../components/user-validate-pw-reset/user-validate-pw-reset.component";
import queryString from "query-string";
export default function ResetPassword() {
const searchParams = queryString.parse(useLocation().search);
const { mode, oobCode } = searchParams;
const searchParams = queryString.parse(useLocation().search);
const {mode, oobCode} = searchParams;
if (mode === "resetPassword")
return <UserValidatePwReset oobCode={oobCode} />;
return <UserRequestResetPw />;
}
if (mode === "resetPassword")
return <UserValidatePwReset oobCode={oobCode}/>;
return <UserRequestResetPw/>;
}

View File

@@ -2,5 +2,5 @@ import React from "react";
import ScheduleCalendarContainer from "../../components/schedule-calendar/schedule-calendar.container";
export default function SchedulePageComponent() {
return <ScheduleCalendarContainer />;
return <ScheduleCalendarContainer/>;
}

View File

@@ -1,33 +1,31 @@
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import SchedulePageComponent from "./schedule.page.component";
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function SchedulePageContainer({ setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation();
export function SchedulePageContainer({setBreadcrumbs, setSelectedHeader}) {
const {t} = useTranslation();
useEffect(() => {
document.title = t("titles.schedule");
setSelectedHeader("schedule");
setBreadcrumbs([
{ link: "/manage/schedule", label: t("titles.bc.schedule") },
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
useEffect(() => {
document.title = t("titles.schedule");
setSelectedHeader("schedule");
setBreadcrumbs([
{link: "/manage/schedule", label: t("titles.bc.schedule")},
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
return (
<RbacWrapper action="schedule:view">
<SchedulePageComponent />
</RbacWrapper>
);
return (
<RbacWrapper action="schedule:view">
<SchedulePageComponent/>
</RbacWrapper>
);
}
export default connect(null, mapDispatchToProps)(SchedulePageContainer);

View File

@@ -1,103 +1,109 @@
import Icon, { FieldTimeOutlined } from "@ant-design/icons";
import { Tabs } from "antd";
import Icon, {FieldTimeOutlined} from "@ant-design/icons";
import {Tabs} from "antd";
import queryString from "query-string";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { FaShieldAlt } from "react-icons/fa";
import { connect } from "react-redux";
import { useHistory, useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import {FaShieldAlt} from "react-icons/fa";
import {connect} from "react-redux";
import {useLocation, useNavigate} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import ScoreboardDisplay from "../../components/scoreboard-display/scoreboard-display.component";
import ScoreboardTimeTicketsStats from "../../components/scoreboard-timetickets-stats/scoreboard-timetickets.component";
import ScoreboardTimeTickets from "../../components/scoreboard-timetickets/scoreboard-timetickets.component";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import {selectBodyshop} from "../../redux/user/user.selectors";
/**
* Mapping state to props
*/
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
bodyshop: selectBodyshop,
});
/**
* Mapping dispatch to props
*/
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function ScoreboardContainer({ setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation();
const searchParams = queryString.parse(useLocation().search);
const { tab } = searchParams;
const history = useHistory();
useEffect(() => {
document.title = t("titles.scoreboard");
setSelectedHeader("scoreboard");
setBreadcrumbs([
{
link: "/manage/scoreboard",
label: t("titles.bc.scoreboard"),
},
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
/**
* ScoreboardContainer component
* @param {Object} props - The props that were defined by the caller of this component.
* @param {Function} props.setBreadcrumbs - Function to set breadcrumbs.
* @param {Function} props.setSelectedHeader - Function to set selected header.
*/
export function ScoreboardContainer({setBreadcrumbs, setSelectedHeader}) {
const {t} = useTranslation();
const searchParams = queryString.parse(useLocation().search);
const {tab} = searchParams;
const history = useNavigate();
return (
<FeatureWrapper featureName="scoreboard">
<RbacWrapper action="scoreboard:view">
<Tabs
activeKey={tab || "sb"}
destroyInactiveTabPane
onChange={(key) => {
searchParams.tab = key;
history.push({
search: queryString.stringify(searchParams),
});
}}
>
<Tabs.TabPane
tab={
<span>
<Icon component={FaShieldAlt} />
{t("scoreboard.labels.jobs")}
</span>
}
destroyInactiveTabPane
key="sb"
>
<ScoreboardDisplay />
</Tabs.TabPane>
<Tabs.TabPane
tab={
<span>
<FieldTimeOutlined />
{t("scoreboard.labels.timeticketsemployee")}
</span>
}
destroyInactiveTabPane
key="tickets"
>
<ScoreboardTimeTickets />
</Tabs.TabPane>
<Tabs.TabPane
tab={
<span>
<FieldTimeOutlined />
{t("scoreboard.labels.allemployeetimetickets")}
</span>
}
destroyInactiveTabPane
key="ticketsstats"
>
<ScoreboardTimeTicketsStats />
</Tabs.TabPane>
</Tabs>
</RbacWrapper>
</FeatureWrapper>
);
/**
* useEffect hook to set document title, selected header and breadcrumbs
*/
useEffect(() => {
document.title = t("titles.scoreboard");
setSelectedHeader("scoreboard");
setBreadcrumbs([
{
link: "/manage/scoreboard",
label: t("titles.bc.scoreboard"),
},
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
/**
* Render the component
*/
return (
<FeatureWrapper featureName="scoreboard">
<RbacWrapper action="scoreboard:view">
<Tabs
activeKey={tab || "sb"}
destroyInactiveTabPane
onChange={(key) => {
searchParams.tab = key;
history({
search: queryString.stringify(searchParams),
});
}}
items={[
{
key: "sb",
icon: <Icon component={FaShieldAlt}/>,
label: t("scoreboard.labels.jobs"),
forceRender: true,
children: <ScoreboardDisplay/>,
},
{
key: "tickets",
icon: <FieldTimeOutlined/>,
label: t("scoreboard.labels.timeticketsemployee"),
forceRender: true,
children: <ScoreboardTimeTickets/>,
},
{
key: "ticketsstats",
icon: <FieldTimeOutlined/>,
label: t("scoreboard.labels.allemployeetimetickets"),
forceRender: true,
children: <ScoreboardTimeTicketsStats/>,
},
]}
/>
</RbacWrapper>
</FeatureWrapper>
);
}
/**
* Connecting the component to Redux store
*/
export default connect(
mapStateToProps,
mapDispatchToProps
)(ScoreboardContainer);
mapStateToProps,
mapDispatchToProps
)(ScoreboardContainer);

View File

@@ -3,9 +3,9 @@ import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import TimeTicketShift from "../../components/time-ticket-shift/time-ticket-shift.container";
export default function ShiftClock() {
return (
<RbacWrapper action="shiftclock:view">
<TimeTicketShift />
</RbacWrapper>
);
return (
<RbacWrapper action="shiftclock:view">
<TimeTicketShift/>
</RbacWrapper>
);
}

View File

@@ -1,98 +1,87 @@
import { Row, Col } from "antd";
import { useQuery } from "@apollo/client";
import queryString from "query-string";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import {Col, Row} from "antd";
import {useQuery} from "@apollo/client";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {useLocation} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import AlertComponent from "../../components/alert/alert.component";
import CsiResponseFormContainer from "../../components/csi-response-form/csi-response-form.container";
import CsiResponseListPaginated from "../../components/csi-response-list-paginated/csi-response-list-paginated.component";
import { QUERY_CSI_RESPONSE_PAGINATED } from "../../graphql/csi.queries";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CsiResponseListPaginated
from "../../components/csi-response-list-paginated/csi-response-list-paginated.component";
import {QUERY_CSI_RESPONSE_PAGINATED} from "../../graphql/csi.queries";
import {setBreadcrumbs, setSelectedHeader} from "../../redux/application/application.actions";
import {selectBodyshop} from "../../redux/user/user.selectors";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import {pageLimit} from "../../utils/config";
import queryString from "query-string";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function ShopCsiContainer({
bodyshop,
setBreadcrumbs,
setSelectedHeader,
}) {
const { t } = useTranslation();
export function ShopCsiContainer({bodyshop, setBreadcrumbs, setSelectedHeader}) {
const {t} = useTranslation();
const searchParams = queryString.parse(useLocation().search);
const {page, sortcolumn, sortorder} = searchParams;
const searchParams = queryString.parse(useLocation().search);
const { page, sortcolumn, sortorder } = searchParams;
const {loading, error, data, refetch} = useQuery(
QUERY_CSI_RESPONSE_PAGINATED,
{
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
variables: {
offset: page ? (page - 1) * pageLimit : 0,
limit: pageLimit,
order: [
{
[sortcolumn || "completedon"]: sortorder
? sortorder === "descend"
? "desc_nulls_last"
: "asc"
: "desc_nulls_last",
},
],
},
}
);
const { loading, error, data, refetch } = useQuery(
QUERY_CSI_RESPONSE_PAGINATED,
{
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
variables: {
//search: search || "",
offset: page ? (page - 1) * pageLimit : 0,
limit: pageLimit,
order: [
{
[sortcolumn || "completedon"]: sortorder
? sortorder === "descend"
? "desc_nulls_last"
: "asc"
: "desc_nulls_last",
},
],
},
}
);
useEffect(() => {
document.title = t("titles.shop-csi");
setSelectedHeader("shop-csi");
setBreadcrumbs([
{
link: "/manage/shop",
label: t("titles.bc.shop", {shopname: bodyshop.shopname}),
},
{link: "/manage/shop/csi", label: t("titles.bc.shop-csi")},
]);
}, [t, setBreadcrumbs, bodyshop.shopname, setSelectedHeader]);
useEffect(() => {
document.title = t("titles.shop-csi");
setSelectedHeader("shop-csi");
setBreadcrumbs([
{
link: "/manage/shop",
label: t("titles.bc.shop", { shopname: bodyshop.shopname }),
},
{ link: "/manage/shop/csi", label: t("titles.bc.shop-csi") },
]);
}, [t, setBreadcrumbs, bodyshop.shopname, setSelectedHeader]);
if (error) return <AlertComponent message={error.message} type="error"/>;
if (error) return <AlertComponent message={error.message} type="error" />;
return (
<RbacWrapper
action="csi:page"
// noauth={
// <AlertComponent message="You don't have acess to see this screen." />
// }
>
<Row gutter={16}>
<Col span={10}>
<CsiResponseListPaginated
refetch={refetch}
loading={loading}
responses={data ? data.csi : []}
total={data ? data.csi_aggregate.aggregate.count : 0}
/>
</Col>
<Col span={14}>
<CsiResponseFormContainer />
</Col>
</Row>
</RbacWrapper>
);
return (
<RbacWrapper action="csi:page">
<Row gutter={16}>
<Col span={10}>
<CsiResponseListPaginated
refetch={refetch}
loading={loading}
responses={data ? data.csi : []}
total={data ? data.csi_aggregate.aggregate.count : 0}
/>
</Col>
<Col span={14}>
<CsiResponseFormContainer/>
</Col>
</Row>
</RbacWrapper>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(ShopCsiContainer);
export default connect(mapStateToProps, mapDispatchToProps)(ShopCsiContainer);

View File

@@ -42,7 +42,7 @@
// return (
// <RbacWrapper action="shop:templates">
// <div>
// <ShopTemplatesListContainer visibleState={drawerVisibility} />
// <ShopTemplatesListContainer openState={drawerVisibility} />
// <Button onClick={() => drawerVisibility[1](true)}>Show List</Button>
// <ShopTemplateEditor />
// </div>

View File

@@ -1,45 +1,43 @@
import { Drawer, Grid } from "antd";
import queryString from "query-string";
import {Drawer, Grid} from "antd";
import React from "react";
import { useHistory, useLocation } from "react-router-dom";
import {useNavigate, useSearchParams} from "react-router-dom";
import VendorsFormContainer from "../../components/vendors-form/vendors-form.container";
import VendorsListContainer from "../../components/vendors-list/vendors-list.container";
export default function ShopVendorPageComponent() {
const search = queryString.parse(useLocation().search);
const { selectedvendor } = search;
const history = useHistory();
const [searchParams] = useSearchParams();
const {selectedvendor} = Object.fromEntries(searchParams);
const navigate = useNavigate();
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
.filter((screen) => !!screen[1])
.slice(-1)[0];
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
.filter((screen) => !!screen[1])
.slice(-1)[0];
const bpoints = {
xs: "100%",
sm: "100%",
md: "100%",
lg: "50%",
xl: "50%",
xxl: "45%",
};
const drawerPercentage = selectedBreakpoint
? bpoints[selectedBreakpoint[0]]
: "100%";
const bpoints = {
xs: "100%",
sm: "100%",
md: "100%",
lg: "50%",
xl: "50%",
xxl: "45%",
};
const drawerPercentage = selectedBreakpoint
? bpoints[selectedBreakpoint[0]]
: "100%";
return (
<div>
<VendorsListContainer />
<Drawer
width={drawerPercentage}
onClose={() => {
delete search.selectedvendor;
history.push({ search: queryString.stringify(search) });
}}
visible={selectedvendor}
>
<VendorsFormContainer />
</Drawer>
</div>
);
}
return (
(<div>
<VendorsListContainer/>
<Drawer
width={drawerPercentage}
onClose={() => {
searchParams.delete("selectedvendor");
navigate({search: searchParams.toString()});
}}
open={selectedvendor}
>
<VendorsFormContainer/>
</Drawer>
</div>)
);
}

View File

@@ -1,48 +1,47 @@
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import ShopVendorPageComponent from "./shop-vendor.page.component";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {selectBodyshop} from "../../redux/user/user.selectors";
import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function ShopVendorPageContainer({
bodyshop,
setBreadcrumbs,
setSelectedHeader,
}) {
const { t } = useTranslation();
useEffect(() => {
document.title = t("titles.shop_vendors");
setSelectedHeader("shop-vendors");
setBreadcrumbs([
{
link: "/manage/shop",
label: t("titles.bc.shop", { shopname: bodyshop.shopname }),
},
{ link: "/manage/shop/vendors", label: t("titles.bc.shop-vendors") },
]);
}, [t, bodyshop.shopname, setBreadcrumbs, setSelectedHeader]);
bodyshop,
setBreadcrumbs,
setSelectedHeader,
}) {
const {t} = useTranslation();
useEffect(() => {
document.title = t("titles.shop_vendors");
setSelectedHeader("shop-vendors");
setBreadcrumbs([
{
link: "/manage/shop",
label: t("titles.bc.shop", {shopname: bodyshop.shopname}),
},
{link: "/manage/shop/vendors", label: t("titles.bc.shop-vendors")},
]);
}, [t, bodyshop.shopname, setBreadcrumbs, setSelectedHeader]);
return (
<RbacWrapper action="shop:vendors">
<ShopVendorPageComponent />
</RbacWrapper>
);
return (
<RbacWrapper action="shop:vendors">
<ShopVendorPageComponent/>
</RbacWrapper>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
mapStateToProps,
mapDispatchToProps
)(ShopVendorPageContainer);

View File

@@ -1,77 +1,89 @@
import { Tabs } from "antd";
import React, { useEffect } from "react";
import { useHistory, useLocation } from "react-router-dom";
import {Tabs} from "antd";
import React, {useEffect} from "react";
import {useLocation, useNavigate} from "react-router-dom";
import queryString from "query-string";
import { useTranslation } from "react-i18next";
import {useTranslation} from "react-i18next";
import ShopEmployeesContainer from "../../components/shop-employees/shop-employees.container";
import ShopInfoContainer from "../../components/shop-info/shop-info.container";
import ShopCsiConfig from "../../components/shop-csi-config/shop-csi-config.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {
setSelectedHeader,
setBreadcrumbs,
} from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import {selectBodyshop} from "../../redux/user/user.selectors";
import ShopInfoUsersComponent from "../../components/shop-users/shop-users.component";
import ShopTeamsContainer from "../../components/shop-teams/shop-teams.container";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
});
export function ShopPage({ bodyshop, setSelectedHeader, setBreadcrumbs }) {
const { t } = useTranslation();
const history = useHistory();
const search = queryString.parse(useLocation().search);
export function ShopPage({bodyshop, setSelectedHeader, setBreadcrumbs}) {
const {t} = useTranslation();
const history = useNavigate();
const search = queryString.parse(useLocation().search);
useEffect(() => {
document.title = t("titles.shop");
setSelectedHeader("shop");
setBreadcrumbs([
{
link: "/manage/shop",
label: t("titles.bc.shop", { shopname: bodyshop.shopname }),
},
]);
}, [t, setSelectedHeader, setBreadcrumbs, bodyshop.shopname]);
useEffect(() => {
document.title = t("titles.shop");
setSelectedHeader("shop");
setBreadcrumbs([
{
link: "/manage/shop",
label: t("titles.bc.shop", {shopname: bodyshop.shopname}),
},
]);
}, [t, setSelectedHeader, setBreadcrumbs, bodyshop.shopname]);
useEffect(() => {
if (!search.tab) history.push({ search: "?tab=info" });
}, [history, search]);
useEffect(() => {
if (!search.tab) history({search: "?tab=info"});
}, [history, search]);
return (
<RbacWrapper action="shop:config">
<Tabs
defaultActiveKey={search.tab}
onChange={(key) => history.push({ search: `?tab=${key}` })}
>
<Tabs.TabPane tab={t("bodyshop.labels.shopinfo")} key="info">
<ShopInfoContainer />
</Tabs.TabPane>
<Tabs.TabPane tab={t("bodyshop.labels.employees")} key="employees">
<ShopEmployeesContainer />
</Tabs.TabPane>
const items = [
{
bodyshop.md_tasks_presets.enable_tasks &&
<Tabs.TabPane tab={t("bodyshop.labels.employee_teams")} key="teams">
<ShopTeamsContainer />
</Tabs.TabPane>
}
<Tabs.TabPane tab={t("bodyshop.labels.licensing")} key="licensing">
<ShopInfoUsersComponent />
</Tabs.TabPane>
<Tabs.TabPane tab={t("bodyshop.labels.csiq")} key="csiq">
<ShopCsiConfig />
</Tabs.TabPane>
</Tabs>
</RbacWrapper>
);
key: "info",
label: t("bodyshop.labels.shopinfo"),
children: <ShopInfoContainer/>,
},
{
key: "employees",
label: t("bodyshop.labels.employees"),
children: <ShopEmployeesContainer/>,
}];
if (bodyshop.md_tasks_presets.enable_tasks) {
items.push({
key: "teams",
label: t("bodyshop.labels.employee_teams"),
children: <ShopTeamsContainer/>
});
}
items.push({
key: "licensing",
label: t("bodyshop.labels.licensing"),
children: <ShopInfoUsersComponent/>,
},
{
key: "csiq",
label: t("bodyshop.labels.csiq"),
children: <ShopCsiConfig/>,
});
return (
<RbacWrapper action="shop:config">
<Tabs
activeKey={search.tab}
onChange={(key) => history({search: `?tab=${key}`})}
items={items}
/>
</RbacWrapper>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(ShopPage);

View File

@@ -2,9 +2,9 @@ import React from "react";
import SignIn from "../../components/sign-in-form/sign-in-form.component";
export default function SignInPage() {
return (
<div>
<SignIn />
</div>
);
return (
<div>
<SignIn/>
</div>
);
}

View File

@@ -1,261 +1,261 @@
import { useQuery } from "@apollo/client";
import React, { useState } from "react";
import { Table } from "antd";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { QUERY_JOBS_TECH_ASIGNED_TO_BY_TEAM } from "../../graphql/jobs.queries";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { alphaSort } from "../../utils/sorters";
import { SyncOutlined } from "@ant-design/icons";
import { Button, Card, Input, Space } from "antd";
import {useQuery} from "@apollo/client";
import React, {useState} from "react";
import {Button, Card, Input, Space, Table} from "antd";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {QUERY_JOBS_TECH_ASIGNED_TO_BY_TEAM} from "../../graphql/jobs.queries";
import {selectTechnician} from "../../redux/tech/tech.selectors";
import {selectBodyshop} from "../../redux/user/user.selectors";
import {alphaSort} from "../../utils/sorters";
import {SyncOutlined} from "@ant-design/icons";
import queryString from "query-string";
import { useTranslation } from "react-i18next";
import { useHistory, useLocation } from "react-router-dom";
import { onlyUnique } from "../../utils/arrayHelper";
import {useTranslation} from "react-i18next";
import {useNavigate, useLocation} from "react-router-dom";
import {onlyUnique} from "../../utils/arrayHelper";
import AlertComponent from "../../components/alert/alert.component";
import OwnerNameDisplay from "../../components/owner-name-display/owner-name-display.component";
import { setModalContext } from "../../redux/modals/modals.actions";
import {setModalContext} from "../../redux/modals/modals.actions";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
technician: selectTechnician,
bodyshop: selectBodyshop,
//currentUser: selectCurrentUser
technician: selectTechnician,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setTimeTicketTaskContext: (context) =>
dispatch(setModalContext({ context: context, modal: "timeTicketTask" })),
setTimeTicketTaskContext: (context) =>
dispatch(setModalContext({context: context, modal: "timeTicketTask"})),
});
export function TechAssignedProdJobs({
setTimeTicketTaskContext,
technician,
bodyshop,
}) {
const { loading, error, data, refetch } = useQuery(
QUERY_JOBS_TECH_ASIGNED_TO_BY_TEAM,
{
variables: {
teamIds: bodyshop.employee_teams
.filter((et) =>
et.employee_team_members.find(
(etm) => etm.employeeid === technician.id
setTimeTicketTaskContext,
technician,
bodyshop,
}) {
const {loading, error, data, refetch} = useQuery(
QUERY_JOBS_TECH_ASIGNED_TO_BY_TEAM,
{
variables: {
teamIds: bodyshop.employee_teams
.filter((et) =>
et.employee_team_members.find(
(etm) => etm.employeeid === technician.id
)
)
.map((et) => et.id),
},
}
);
const searchParams = queryString.parse(useLocation().search);
const {selected} = searchParams;
const [state, setState] = useState({
sortedInfo: {},
filteredInfo: {text: ""},
});
const {t} = useTranslation();
const history = useNavigate();
const [searchText, setSearchText] = useState("");
if (error) return <AlertComponent message={error.message} type="error"/>;
const jobs = data
? searchText === ""
? data.jobs
: data.jobs.filter(
(j) =>
(j.ro_number || "")
.toString()
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_fn || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_ln || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_co_nm || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) ||
(j.plate_no || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.v_model_desc || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.v_make_desc || "")
.toLowerCase()
.includes(searchText.toLowerCase())
)
)
.map((et) => et.id),
},
}
);
: [];
const columns = [
{
title: t("jobs.fields.ro_number"),
dataIndex: "ro_number",
key: "ro_number",
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
sortOrder:
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
const searchParams = queryString.parse(useLocation().search);
const { selected } = searchParams;
const [state, setState] = useState({
sortedInfo: {},
filteredInfo: { text: "" },
});
const { t } = useTranslation();
const history = useHistory();
const [searchText, setSearchText] = useState("");
if (error) return <AlertComponent message={error.message} type="error" />;
const jobs = data
? searchText === ""
? data.jobs
: data.jobs.filter(
(j) =>
(j.ro_number || "")
.toString()
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_fn || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_ln || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_co_nm || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) ||
(j.plate_no || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.v_model_desc || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.v_make_desc || "")
.toLowerCase()
.includes(searchText.toLowerCase())
)
: [];
const columns = [
{
title: t("jobs.fields.ro_number"),
dataIndex: "ro_number",
key: "ro_number",
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
sortOrder:
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
render: (text, record) => record.ro_number || t("general.labels.na"),
},
{
title: t("jobs.fields.owner"),
dataIndex: "owner",
key: "owner",
sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
sortOrder:
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
ellipsis: true,
render: (text, record) => (
<span>
<OwnerNameDisplay ownerObject={record} />
render: (text, record) => record.ro_number || t("general.labels.na"),
},
{
title: t("jobs.fields.owner"),
dataIndex: "owner",
key: "owner",
sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
sortOrder:
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
ellipsis: true,
render: (text, record) => (
<span>
<OwnerNameDisplay ownerObject={record}/>
</span>
),
},
{
title: t("jobs.fields.status"),
dataIndex: "status",
key: "status",
sorter: (a, b) => alphaSort(a.status, b.status),
sortOrder:
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
filters:
(jobs &&
jobs
.map((j) => j.status)
.filter(onlyUnique)
.map((s) => {
return {
text: s || "No Status*",
value: [s],
};
})) ||
[],
onFilter: (value, record) => value.includes(record.status),
render: (text, record) => {
return record.status || t("general.labels.na");
},
},
),
},
{
title: t("jobs.fields.status"),
dataIndex: "status",
key: "status",
sorter: (a, b) => alphaSort(a.status, b.status),
sortOrder:
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
filters:
(jobs &&
jobs
.map((j) => j.status)
.filter(onlyUnique)
.map((s) => {
return {
text: s || "No Status*",
value: [s],
};
})) ||
[],
onFilter: (value, record) => value.includes(record.status),
render: (text, record) => {
return record.status || t("general.labels.na");
},
},
{
title: t("jobs.fields.vehicle"),
dataIndex: "vehicle",
key: "vehicle",
ellipsis: true,
render: (text, record) => (
<span>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
record.v_model_desc || ""
}`}</span>
),
},
{
title: t("vehicles.fields.plate_no"),
dataIndex: "plate_no",
key: "plate_no",
sorter: (a, b) => alphaSort(a.plate_no, b.plate_no),
sortOrder:
state.sortedInfo.columnKey === "plate_no" && state.sortedInfo.order,
render: (text, record) => {
return record.plate_no ? record.plate_no : "";
},
},
{
title: t("jobs.fields.clm_no"),
dataIndex: "clm_no",
key: "clm_no",
ellipsis: true,
sorter: (a, b) => alphaSort(a.clm_no, b.clm_no),
sortOrder:
state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order,
render: (text, record) => {
return record.clm_no ? (
<span>{record.clm_no}</span>
) : (
t("general.labels.unknown")
);
},
},
{
title: t("general.labels.actions"),
dataIndex: "actions",
key: "actions",
render: (text, record) => (
<Button
onClick={() => {
setTimeTicketTaskContext({
actions: { refetch: refetch },
context: { jobid: record.id },
});
}}
{
title: t("jobs.fields.vehicle"),
dataIndex: "vehicle",
key: "vehicle",
ellipsis: true,
render: (text, record) => (
<span>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
record.v_model_desc || ""
}`}</span>
),
},
{
title: t("vehicles.fields.plate_no"),
dataIndex: "plate_no",
key: "plate_no",
sorter: (a, b) => alphaSort(a.plate_no, b.plate_no),
sortOrder:
state.sortedInfo.columnKey === "plate_no" && state.sortedInfo.order,
render: (text, record) => {
return record.plate_no ? record.plate_no : "";
},
},
{
title: t("jobs.fields.clm_no"),
dataIndex: "clm_no",
key: "clm_no",
ellipsis: true,
sorter: (a, b) => alphaSort(a.clm_no, b.clm_no),
sortOrder:
state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order,
render: (text, record) => {
return record.clm_no ? (
<span>{record.clm_no}</span>
) : (
t("general.labels.unknown")
);
},
},
{
title: t("general.labels.actions"),
dataIndex: "actions",
key: "actions",
render: (text, record) => (
<Button
onClick={() => {
setTimeTicketTaskContext({
actions: {refetch: refetch},
context: {jobid: record.id},
});
}}
>
{t("timetickets.actions.claimtasks")}
</Button>
),
},
];
const handleTableChange = (pagination, filters, sorter) => {
setState({...state, filteredInfo: filters, sortedInfo: sorter});
};
const handleOnRowClick = (record) => {
if (record) {
if (record.id) {
history({
search: queryString.stringify({
...searchParams,
selected: record.id,
}),
});
}
}
};
return (
<Card
extra={
<Space wrap>
<Button onClick={() => refetch()}>
<SyncOutlined/>
</Button>
<Input.Search
placeholder={t("general.labels.search")}
onChange={(e) => {
setSearchText(e.target.value);
}}
value={searchText}
enterButton
/>
</Space>
}
>
{t("timetickets.actions.claimtasks")}
</Button>
),
},
];
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
};
const handleOnRowClick = (record) => {
if (record) {
if (record.id) {
history.push({
search: queryString.stringify({
...searchParams,
selected: record.id,
}),
});
}
}
};
return (
<Card
extra={
<Space wrap>
<Button onClick={() => refetch()}>
<SyncOutlined />
</Button>
<Input.Search
placeholder={t("general.labels.search")}
onChange={(e) => {
setSearchText(e.target.value);
}}
value={searchText}
enterButton
/>
</Space>
}
>
<Table
loading={loading}
pagination={false}
columns={columns}
rowKey="id"
dataSource={jobs}
scroll={{ x: true }}
rowSelection={{
onSelect: (record) => {
handleOnRowClick(record);
},
selectedRowKeys: [selected],
type: "radio",
}}
onChange={handleTableChange}
// onRow={(record, rowIndex) => {
// return {
// onClick: (event) => {
// handleOnRowClick(record);
// },
// };
// }}
/>
</Card>
);
<Table
loading={loading}
pagination={false}
columns={columns}
rowKey="id"
dataSource={jobs}
scroll={{x: true}}
rowSelection={{
onSelect: (record) => {
handleOnRowClick(record);
},
selectedRowKeys: [selected],
type: "radio",
}}
onChange={handleTableChange}
// onRow={(record, rowIndex) => {
// return {
// onClick: (event) => {
// handleOnRowClick(record);
// },
// };
// }}
/>
</Card>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
mapStateToProps,
mapDispatchToProps
)(TechAssignedProdJobs);

View File

@@ -1,139 +1,137 @@
import {
MinusCircleTwoTone,
PlusCircleTwoTone,
SyncOutlined,
} from "@ant-design/icons";
import { useQuery } from "@apollo/client";
import { Button, Card, Space, Table } from "antd";
import {MinusCircleTwoTone, PlusCircleTwoTone, SyncOutlined,} from "@ant-design/icons";
import {useQuery} from "@apollo/client";
import {Button, Card, Space, Table} from "antd";
import queryString from "query-string";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useHistory, useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {useNavigate, useLocation} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import AlertComponent from "../../components/alert/alert.component";
import PartsDispatchExpander from "../../components/parts-dispatch-expander/parts-dispatch-expander.component";
import { GET_UNACCEPTED_PARTS_DISPATCH } from "../../graphql/parts-dispatch.queries";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { alphaSort } from "../../utils/sorters";
import {GET_UNACCEPTED_PARTS_DISPATCH} from "../../graphql/parts-dispatch.queries";
import {selectTechnician} from "../../redux/tech/tech.selectors";
import {selectBodyshop} from "../../redux/user/user.selectors";
import {alphaSort} from "../../utils/sorters";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
technician: selectTechnician,
bodyshop: selectBodyshop,
//currentUser: selectCurrentUser
technician: selectTechnician,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({});
export function TechDispatchedParts({ technician, bodyshop }) {
const searchParams = queryString.parse(useLocation().search);
const { page } = searchParams;
export function TechDispatchedParts({technician, bodyshop}) {
const searchParams = queryString.parse(useLocation().search);
const {page} = searchParams;
const { loading, error, data, refetch } = useQuery(
GET_UNACCEPTED_PARTS_DISPATCH,
{
variables: {
techId: technician.id,
offset: page ? (page - 1) * 25 : 0,
limit: 25,
},
}
);
const {loading, error, data, refetch} = useQuery(
GET_UNACCEPTED_PARTS_DISPATCH,
{
variables: {
techId: technician.id,
offset: page ? (page - 1) * 25 : 0,
limit: 25,
},
}
);
const { t } = useTranslation();
const history = useHistory();
const {t} = useTranslation();
const history = useNavigate();
if (error) return <AlertComponent message={error.message} type="error" />;
if (error) return <AlertComponent message={error.message} type="error"/>;
const parts_dispatch = data?.parts_dispatch;
const parts_dispatch = data?.parts_dispatch;
const columns = [
{
title: t("jobs.fields.ro_number"),
dataIndex: "job.ro_number",
key: "ro_number",
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
const columns = [
{
title: t("jobs.fields.ro_number"),
dataIndex: "job.ro_number",
key: "ro_number",
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
render: (text, record) => record.job.ro_number || t("general.labels.na"),
},
{
title: t("jobs.fields.status"),
dataIndex: "status",
key: "status",
sorter: (a, b) => alphaSort(a.status, b.status),
render: (text, record) => {
return record.job.status || t("general.labels.na");
},
},
render: (text, record) => record.job.ro_number || t("general.labels.na"),
},
{
title: t("jobs.fields.status"),
dataIndex: "status",
key: "status",
sorter: (a, b) => alphaSort(a.status, b.status),
render: (text, record) => {
return record.job.status || t("general.labels.na");
},
},
{
title: t("jobs.fields.vehicle"),
dataIndex: "vehicle",
key: "vehicle",
ellipsis: true,
render: (text, record) => (
<span>{`${record.job.v_model_yr || ""} ${
record.job.v_make_desc || ""
} ${record.job.v_model_desc || ""}`}</span>
),
},
{
title: t("general.labels.actions"),
dataIndex: "actions",
key: "actions",
render: (text, record) => (
<Button onClick={() => {}}>
{t("timetickets.actions.claimtasks")}
</Button>
),
},
];
const handleTableChange = (pagination, filters, sorter) => {
searchParams.page = pagination.current;
history.push({ search: queryString.stringify(searchParams) });
};
return (
<Card
extra={
<Space wrap>
<Button onClick={() => refetch()}>
<SyncOutlined />
</Button>
</Space>
}
>
<Table
loading={loading}
pagination={{
pageSize: 25,
current: parseInt(page || 1),
total: data ? data.parts_dispatch_aggregate.aggregate.count : 0,
showSizeChanger: false,
}}
columns={columns}
rowKey="id"
dataSource={parts_dispatch}
scroll={{ x: true }}
onChange={handleTableChange}
expandable={{
expandedRowRender: (record) => (
<PartsDispatchExpander dispatch={record} />
),
rowExpandable: (record) => true,
//expandRowByClick: true,
expandIcon: ({ expanded, onExpand, record }) =>
expanded ? (
<MinusCircleTwoTone onClick={(e) => onExpand(record, e)} />
) : (
<PlusCircleTwoTone onClick={(e) => onExpand(record, e)} />
{
title: t("jobs.fields.vehicle"),
dataIndex: "vehicle",
key: "vehicle",
ellipsis: true,
render: (text, record) => (
<span>{`${record.job.v_model_yr || ""} ${
record.job.v_make_desc || ""
} ${record.job.v_model_desc || ""}`}</span>
),
}}
/>
</Card>
);
},
{
title: t("general.labels.actions"),
dataIndex: "actions",
key: "actions",
render: (text, record) => (
<Button onClick={() => {
}}>
{t("timetickets.actions.claimtasks")}
</Button>
),
},
];
const handleTableChange = (pagination, filters, sorter) => {
searchParams.page = pagination.current;
history({search: queryString.stringify(searchParams)});
};
return (
<Card
extra={
<Space wrap>
<Button onClick={() => refetch()}>
<SyncOutlined/>
</Button>
</Space>
}
>
<Table
loading={loading}
pagination={{
pageSize: 25,
current: parseInt(page || 1),
total: data ? data.parts_dispatch_aggregate.aggregate.count : 0,
showSizeChanger: false,
}}
columns={columns}
rowKey="id"
dataSource={parts_dispatch}
scroll={{x: true}}
onChange={handleTableChange}
expandable={{
expandedRowRender: (record) => (
<PartsDispatchExpander dispatch={record}/>
),
rowExpandable: (record) => true,
//expandRowByClick: true,
expandIcon: ({expanded, onExpand, record}) =>
expanded ? (
<MinusCircleTwoTone onClick={(e) => onExpand(record, e)}/>
) : (
<PlusCircleTwoTone onClick={(e) => onExpand(record, e)}/>
),
}}
/>
</Card>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
mapStateToProps,
mapDispatchToProps
)(TechDispatchedParts);

View File

@@ -1,16 +1,23 @@
import { Divider } from "antd";
import React from "react";
import {Divider} from "antd";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
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 />
</div>
);
const {t} = useTranslation();
useEffect(() => {
document.title = t("titles.techjobclock");
}, [t]);
return (
<div>
<TechJobStatistics/>
<TechClockInFormContainer/>
<Divider/>
<TechClockedInList/>
</div>
);
}

View File

@@ -1,13 +1,20 @@
import React from "react";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import RbacWrapperComponent from "../../components/rbac-wrapper/rbac-wrapper.component";
import TechLookupJobsList from "../../components/tech-lookup-jobs-list/tech-lookup-jobs-list.component";
export default function TechLookupContainer() {
return (
<div>
<RbacWrapperComponent action="jobs:list-active">
<TechLookupJobsList />
</RbacWrapperComponent>
</div>
);
const {t} = useTranslation();
useEffect(() => {
document.title = t("titles.techjoblookup");
}, [t]);
return (
<div>
<RbacWrapperComponent action="jobs:list-active">
<TechLookupJobsList/>
</RbacWrapperComponent>
</div>
);
}

View File

@@ -1,10 +1,17 @@
import React from "react";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import TimeTicketShift from "../../components/time-ticket-shift/time-ticket-shift.container";
export default function TechShiftClock() {
return (
<div>
<TimeTicketShift isTechConsole />
</div>
);
const {t} = useTranslation();
useEffect(() => {
document.title = t("titles.techshiftclock");
}, [t]);
return (
<div>
<TimeTicketShift isTechConsole/>
</div>
);
}

View File

@@ -1,9 +1,9 @@
import { BackTop, Layout } from "antd";
import React, { Suspense, lazy, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Redirect, Route, Switch } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import {FloatButton, Layout} from "antd";
import React, {lazy, Suspense, useEffect} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {Route, Routes, useNavigate} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import ErrorBoundary from "../../components/error-boundary/error-boundary.component";
import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component";
@@ -12,132 +12,107 @@ import TechHeader from "../../components/tech-header/tech-header.component";
import TechLookupJobsDrawer from "../../components/tech-lookup-jobs-drawer/tech-lookup-jobs-drawer.component";
import TechSider from "../../components/tech-sider/tech-sider.component";
import UpdateAlert from "../../components/update-alert/update-alert.component";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import "./tech.page.styles.scss";
import {selectTechnician} from "../../redux/tech/tech.selectors";
import "./tech.page.styles.scss";
const TimeTicketModalContainer = lazy(() =>
import("../../components/time-ticket-modal/time-ticket-modal.container")
import("../../components/time-ticket-modal/time-ticket-modal.container")
);
const EmailOverlayContainer = lazy(() =>
import("../../components/email-overlay/email-overlay.container.jsx")
import("../../components/email-overlay/email-overlay.container.jsx")
);
const PrintCenterModalContainer = lazy(() =>
import("../../components/print-center-modal/print-center-modal.container")
import("../../components/print-center-modal/print-center-modal.container")
);
const TechLogin = lazy(() =>
import("../../components/tech-login/tech-login.component")
import("../../components/tech-login/tech-login.component")
);
const TechLookup = lazy(() => import("../tech-lookup/tech-lookup.container"));
const ProductionListPage = lazy(() =>
import("../production-list/production-list.container")
import("../production-list/production-list.container")
);
const ProductionBoardPage = lazy(() =>
import("../production-board/production-board.container")
import("../production-board/production-board.container")
);
const TechJobClock = lazy(() =>
import("../tech-job-clock/tech-job-clock.component")
import("../tech-job-clock/tech-job-clock.component")
);
const TechShiftClock = lazy(() =>
import("../tech-shift-clock/tech-shift-clock.component")
import("../tech-shift-clock/tech-shift-clock.component")
);
const TimeTicketModalTask = lazy(() =>
import(
"../../components/time-ticket-task-modal/time-ticket-task-modal.container"
)
import(
"../../components/time-ticket-task-modal/time-ticket-task-modal.container"
)
);
const TechAssignedProdJobs = lazy(() =>
import("../tech-assigned-prod-jobs/tech-assigned-prod-jobs.component")
import("../tech-assigned-prod-jobs/tech-assigned-prod-jobs.component")
);
const TechDispatchedParts = lazy(() =>
import("../tech-dispatched-parts/tech-dispatched-parts.page")
import("../tech-dispatched-parts/tech-dispatched-parts.page")
);
const { Content } = Layout;
const {Content} = Layout;
const mapStateToProps = createStructuredSelector({
technician: selectTechnician,
technician: selectTechnician,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export function TechPage({ technician, match }) {
const { t } = useTranslation();
export function TechPage({technician}) {
const {t} = useTranslation();
const navigate = useNavigate();
useEffect(() => {
document.title = t("titles.app");
}, [t]);
useEffect(() => {
document.title = t("titles.app");
}, [t]);
return (
<Layout className="tech-layout-container">
<TechSider />
<Layout>
{technician ? null : <Redirect to={`${match.path}/login`} />}
<UpdateAlert />
<TechHeader />
<TechLookupJobsDrawer />
<Content className="tech-content-container">
<ErrorBoundary>
<Suspense
fallback={
<LoadingSpinner message={t("general.labels.loadingapp")} />
}
>
<FeatureWrapper featureName="tech-console">
<TimeTicketModalContainer />
<EmailOverlayContainer />
<PrintCenterModalContainer />
<TimeTicketModalTask />
<Switch>
<Route
exact
path={`${match.path}/login`}
component={TechLogin}
/>
<Route
exact
path={`${match.path}/joblookup`}
component={TechLookup}
/>
<Route
exact
path={`${match.path}/list`}
component={ProductionListPage}
/>
<Route
exact
path={`${match.path}/jobclock`}
component={TechJobClock}
/>
<Route
exact
path={`${match.path}/shiftclock`}
component={TechShiftClock}
/>
<Route
exact
path={`${match.path}/board`}
component={ProductionBoardPage}
/>
<Route
exact
path={`${match.path}/assigned`}
component={TechAssignedProdJobs}
/>
<Route
exact
path={`${match.path}/dispatchedparts`}
component={TechDispatchedParts}
/>
</Switch>
</FeatureWrapper>
</Suspense>
</ErrorBoundary>
useEffect(() => {
if (!technician) {
navigate(`/tech/login`);
}
}, [technician, navigate]);
<BackTop />
</Content>
</Layout>
</Layout>
);
return (
<Layout className="tech-layout-container">
<TechSider/>
<Layout>
<UpdateAlert/>
<TechHeader/>
<TechLookupJobsDrawer/>
<Content className="tech-content-container">
<ErrorBoundary>
<Suspense
fallback={
<LoadingSpinner message={t("general.labels.loadingapp")}/>
}
>
<FeatureWrapper featureName="tech-console">
<TimeTicketModalContainer/>
<EmailOverlayContainer/>
<PrintCenterModalContainer/>
<TimeTicketModalTask/>
<Routes>
<Route path='/login' element={<TechLogin/>}/>
<Route path='/joblookup' element={<TechLookup/>}/>
<Route path='/list' element={<ProductionListPage/>}/>
<Route path='/jobclock' element={<TechJobClock/>}/>
<Route path='/shiftclock' element={<TechShiftClock/>}/>
<Route path='/board' element={<ProductionBoardPage/>}/>
<Route path='/assigned' element={<TechAssignedProdJobs/>}/>
<Route path='/dispatchedparts' element={<TechDispatchedParts/>}/>
</Routes>
</FeatureWrapper>
</Suspense>
</ErrorBoundary>
<FloatButton.BackTop/>
</Content>
</Layout>
</Layout>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(TechPage);
export default connect(mapStateToProps, mapDispatchToProps)(TechPage);

View File

@@ -1,38 +1,40 @@
import { useQuery } from "@apollo/client";
import React, { useEffect } from "react";
import { connect } from "react-redux";
import {useQuery} from "@apollo/client";
import React, {useEffect} from "react";
import {connect} from "react-redux";
import AlertComponent from "../../components/alert/alert.component";
import { QUERY_BODYSHOP } from "../../graphql/bodyshop.queries";
import { setBodyshop } from "../../redux/user/user.actions";
import {QUERY_BODYSHOP} from "../../graphql/bodyshop.queries";
import {setBodyshop} from "../../redux/user/user.actions";
import TechPage from "./tech.page.component";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import { useTranslation } from "react-i18next";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { createStructuredSelector } from "reselect";
import {useTranslation} from "react-i18next";
import {selectBodyshop} from "../../redux/user/user.selectors";
import {createStructuredSelector} from "reselect";
//import "../../utils/RegisterSw";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setBodyshop: (bs) => dispatch(setBodyshop(bs)),
setBodyshop: (bs) => dispatch(setBodyshop(bs)),
});
export function TechPageContainer({ bodyshop, setBodyshop, match }) {
const { loading, error, data } = useQuery(QUERY_BODYSHOP, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
const { t } = useTranslation();
useEffect(() => {
if (data) setBodyshop(data.bodyshops[0]);
}, [data, setBodyshop]);
export function TechPageContainer({bodyshop, setBodyshop}) {
const {loading, error, data} = useQuery(QUERY_BODYSHOP, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
const {t} = useTranslation();
if (loading || !bodyshop)
return <LoadingSpinner message={t("general.labels.loadingshop")} />;
if (error) return <AlertComponent message={error.message} type="error" />;
return <TechPage match={match} />;
useEffect(() => {
if (data) setBodyshop(data.bodyshops[0]);
}, [data, setBodyshop]);
if (loading || !bodyshop)
return <LoadingSpinner message={t("general.labels.loadingshop")}/>;
if (error) return <AlertComponent message={error.message} type="error"/>;
return <TechPage/>;
}
export default connect(mapStateToProps, mapDispatchToProps)(TechPageContainer);

View File

@@ -1,45 +1,47 @@
import { useQuery } from "@apollo/client";
import {useQuery} from "@apollo/client";
import React from "react";
import AlertComponent from "../../components/alert/alert.component";
import JobsDocumentsComponent from "../../components/jobs-documents-gallery/jobs-documents-gallery.component";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import { QUERY_TEMPORARY_DOCS } from "../../graphql/documents.queries";
import {QUERY_TEMPORARY_DOCS} from "../../graphql/documents.queries";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {selectBodyshop} from "../../redux/user/user.selectors";
import JobsDocumentsLocalGallery
from "../../components/jobs-documents-local-gallery/jobs-documents-local-gallery.container";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import JobsDocumentsLocalGallery from "../../components/jobs-documents-local-gallery/jobs-documents-local-gallery.container";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
mapStateToProps,
mapDispatchToProps
)(TemporaryDocsComponent);
export function TemporaryDocsComponent({ bodyshop }) {
const { loading, error, data, refetch } = useQuery(QUERY_TEMPORARY_DOCS, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
skip: bodyshop.uselocalmediaserver,
});
export function TemporaryDocsComponent({bodyshop}) {
const {loading, error, data, refetch} = useQuery(QUERY_TEMPORARY_DOCS, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
skip: bodyshop.uselocalmediaserver,
});
if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />;
if (loading) return <LoadingSpinner/>;
if (error) return <AlertComponent message={error.message} type="error"/>;
if (bodyshop.uselocalmediaserver) {
return <JobsDocumentsLocalGallery job={{ id: "temporary" }} />;
}
return (
<JobsDocumentsComponent
data={data ? data.documents : []}
jobId={null}
billId={null}
refetch={refetch}
ignoreSizeLimit
/>
);
if (bodyshop.uselocalmediaserver) {
return <JobsDocumentsLocalGallery job={{id: "temporary"}}/>;
}
return (
<JobsDocumentsComponent
data={data ? data.documents : []}
jobId={null}
billId={null}
refetch={refetch}
ignoreSizeLimit
/>
);
}

View File

@@ -1,45 +1,43 @@
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import {selectBodyshop} from "../../redux/user/user.selectors";
import TemporaryDocsComponent from "./temporary-docs.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function TempDocumentsContainer({ setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation();
export function TempDocumentsContainer({setBreadcrumbs, setSelectedHeader}) {
const {t} = useTranslation();
useEffect(() => {
document.title = t("titles.temporarydocs");
setSelectedHeader("temporarydocs");
setBreadcrumbs([
{
link: "/manage/temporarydocs",
label: t("titles.bc.temporarydocs"),
},
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
useEffect(() => {
document.title = t("titles.temporarydocs");
setSelectedHeader("temporarydocs");
setBreadcrumbs([
{
link: "/manage/temporarydocs",
label: t("titles.bc.temporarydocs"),
},
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
return (
<RbacWrapper action="temporarydocs:view">
<TemporaryDocsComponent />
</RbacWrapper>
);
return (
<RbacWrapper action="temporarydocs:view">
<TemporaryDocsComponent/>
</RbacWrapper>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
mapStateToProps,
mapDispatchToProps
)(TempDocumentsContainer);

View File

@@ -1,100 +1,99 @@
import { useQuery } from "@apollo/client";
import { Col, Row, Space } from "antd";
import moment from "moment";
import queryString from "query-string";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import {useQuery} from "@apollo/client";
import {Col, Row, Space} from "antd";
import dayjs from "../../utils/day";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {useSearchParams} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import AlertComponent from "../../components/alert/alert.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import TimeTicketsDatesSelector from "../../components/ticket-tickets-dates-selector/time-tickets-dates-selector.component";
import TimeTicketsDatesSelector
from "../../components/ticket-tickets-dates-selector/time-tickets-dates-selector.component";
import TimeTicketList from "../../components/time-ticket-list/time-ticket-list.component";
import TimeTicketsPayrollTable from "../../components/time-tickets-payroll-table/time-tickets-payroll-table.component";
import TimeTicketsSummaryEmployees from "../../components/time-tickets-summary-employees/time-tickets-summary-employees.component";
import { QUERY_TIME_TICKETS_IN_RANGE } from "../../graphql/timetickets.queries";
import TimeTicketsAttendanceTable from "../../components/time-tickets-attendance-table/time-tickets-attendance-table.component";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import TimeTicketsSummaryEmployees
from "../../components/time-tickets-summary-employees/time-tickets-summary-employees.component";
import {QUERY_TIME_TICKETS_IN_RANGE} from "../../graphql/timetickets.queries";
import TimeTicketsAttendanceTable
from "../../components/time-tickets-attendance-table/time-tickets-attendance-table.component";
import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import TimeTicketsCommit from "../../components/time-tickets-commit/time-tickets-commit.component";
const mapStateToProps = createStructuredSelector({});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function TimeTicketsContainer({
bodyshop,
setBreadcrumbs,
setSelectedHeader,
}) {
const { t } = useTranslation();
useEffect(() => {
document.title = t("titles.timetickets");
setSelectedHeader("timetickets");
setBreadcrumbs([
{
link: "/manage/timetickets",
label: t("titles.bc.timetickets"),
},
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
bodyshop,
setBreadcrumbs,
setSelectedHeader,
}) {
const {t} = useTranslation();
const [searchParams] = useSearchParams();
const {start, end} = Object.fromEntries(searchParams);
const searchParams = queryString.parse(useLocation().search);
const { start, end } = searchParams;
const startDate = start
? dayjs(start)
: dayjs().startOf("week").subtract(7, "day");
const endDate = end ? dayjs(end) : dayjs().endOf("week");
const startDate = start
? moment(start)
: moment().startOf("week").subtract(7, "days");
const endDate = end ? moment(end) : moment().endOf("week");
const {loading, error, data} = useQuery(QUERY_TIME_TICKETS_IN_RANGE, {
variables: {
start: startDate,
end: endDate,
},
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
const { loading, error, data } = useQuery(QUERY_TIME_TICKETS_IN_RANGE, {
variables: {
start: startDate,
end: endDate,
},
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
useEffect(() => {
document.title = t("titles.timetickets");
setSelectedHeader("timetickets");
setBreadcrumbs([
{
link: "/manage/timetickets",
label: t("titles.bc.timetickets"),
},
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
if (error) return <AlertComponent message={error.message} type="error" />;
if (error) return <AlertComponent message={error.message} type="error"/>;
return (
<RbacWrapper action="timetickets:list">
<Row gutter={[16, 16]}>
<Col span={24}>
<TimeTicketList
loading={loading}
timetickets={data ? data.timetickets : []}
extra={
<Space wrap>
<TimeTicketsAttendanceTable />
<TimeTicketsPayrollTable />
<TimeTicketsCommit timetickets={data ? data.timetickets : []} />
<TimeTicketsDatesSelector />
</Space>
}
/>
</Col>
<Col span={24}>
<TimeTicketsSummaryEmployees
loading={loading}
timetickets={data ? data.timetickets : []}
startDate={startDate}
endDate={endDate}
/>
</Col>
</Row>
</RbacWrapper>
);
return (
<RbacWrapper action="timetickets:list">
<Row gutter={[16, 16]}>
<Col span={24}>
<TimeTicketList
loading={loading}
timetickets={data ? data.timetickets : []}
extra={
<Space wrap>
<TimeTicketsAttendanceTable/>
<TimeTicketsPayrollTable/>
<TimeTicketsCommit timetickets={data ? data.timetickets : []}/>
<TimeTicketsDatesSelector/>
</Space>
}
/>
</Col>
<Col span={24}>
<TimeTicketsSummaryEmployees
loading={loading}
timetickets={data ? data.timetickets : []}
startDate={startDate}
endDate={endDate}
/>
</Col>
</Row>
</RbacWrapper>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(TimeTicketsContainer);
mapStateToProps,
mapDispatchToProps
)(TimeTicketsContainer);

View File

@@ -1,42 +1,40 @@
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import TtApprovalsList from "../../components/tt-approvals-list/tt-approvals-list.container";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import {selectBodyshop} from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function TtApprovalsPage({ setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation();
export function TtApprovalsPage({setBreadcrumbs, setSelectedHeader}) {
const {t} = useTranslation();
useEffect(() => {
document.title = t("titles.ttapprovals");
setSelectedHeader("ttapprovals");
setBreadcrumbs([
{
link: "/manage/ttapprovals",
label: t("titles.bc.ttapprovals"),
},
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
useEffect(() => {
document.title = t("titles.ttapprovals");
setSelectedHeader("ttapprovals");
setBreadcrumbs([
{
link: "/manage/ttapprovals",
label: t("titles.bc.ttapprovals"),
},
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
return (
<RbacWrapper action="ttapprovals:view">
<TtApprovalsList />
</RbacWrapper>
);
return (
<RbacWrapper action="ttapprovals:view">
<TtApprovalsList/>
</RbacWrapper>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(TtApprovalsPage);

View File

@@ -1,20 +1,20 @@
import { Col, Divider, Row } from "antd";
import {Col, Divider, Row} from "antd";
import React from "react";
import VehicleDetailFormContainer from "../../components/vehicle-detail-form/vehicle-detail-form.container";
import VehicleDetailJobsComponent from "../../components/vehicle-detail-jobs/vehicle-detail-jobs.component";
export default function VehicleDetailComponent({ vehicle, refetch }) {
return (
<div>
<Row gutter={[16, 16]}>
<Col span={24}>
<VehicleDetailFormContainer vehicle={vehicle} refetch={refetch} />
</Col>
<Divider type="horizontal" />
<Col span={24}>
<VehicleDetailJobsComponent vehicle={vehicle} />
</Col>
</Row>
</div>
);
export default function VehicleDetailComponent({vehicle, refetch}) {
return (
<div>
<Row gutter={[16, 16]}>
<Col span={24}>
<VehicleDetailFormContainer vehicle={vehicle} refetch={refetch}/>
</Col>
<Divider type="horizontal"/>
<Col span={24}>
<VehicleDetailJobsComponent vehicle={vehicle}/>
</Col>
</Row>
</div>
);
}

View File

@@ -1,94 +1,90 @@
import React, { useEffect } from "react";
import React, {useEffect} from "react";
import VehicleDetailComponent from "./vehicles-detail.page.component";
import { useQuery } from "@apollo/client";
import { QUERY_VEHICLE_BY_ID } from "../../graphql/vehicles.queries";
import {useQuery} from "@apollo/client";
import {useParams} from 'react-router-dom';
import {QUERY_VEHICLE_BY_ID} from "../../graphql/vehicles.queries";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import AlertComponent from "../../components/alert/alert.component";
import { useTranslation } from "react-i18next";
import {
setBreadcrumbs,
addRecentItem,
setSelectedHeader,
} from "../../redux/application/application.actions";
import { connect } from "react-redux";
import { CreateRecentItem } from "../../utils/create-recent-item";
import {useTranslation} from "react-i18next";
import {addRecentItem, setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import {connect} from "react-redux";
import {CreateRecentItem} from "../../utils/create-recent-item";
import NotFound from "../../components/not-found/not-found.component";
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
addRecentItem: (item) => dispatch(addRecentItem(item)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
addRecentItem: (item) => dispatch(addRecentItem(item)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function VehicleDetailContainer({
match,
setBreadcrumbs,
addRecentItem,
setSelectedHeader,
}) {
const { vehId } = match.params;
const { t } = useTranslation();
setBreadcrumbs,
addRecentItem,
setSelectedHeader,
}) {
const {vehId} = useParams();
const {t} = useTranslation();
const { loading, data, error, refetch } = useQuery(QUERY_VEHICLE_BY_ID, {
variables: { id: vehId },
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
useEffect(() => {
document.title = t("titles.vehicledetail", {
vehicle:
data && data.vehicles_by_pk
? `${(data.vehicles_by_pk && data.vehicles_by_pk.v_model_yr) || ""} ${
(data.vehicles_by_pk && data.vehicles_by_pk.v_make_desc) || ""
} ${
(data.vehicles_by_pk && data.vehicles_by_pk.v_model_desc) || ""
}`
: "",
const {loading, data, error, refetch} = useQuery(QUERY_VEHICLE_BY_ID, {
variables: {id: vehId},
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
setSelectedHeader("vehicles");
setBreadcrumbs([
{ link: "/manage/vehicles", label: t("titles.bc.vehicles") },
{
link: `/manage/vehicles/${vehId}`,
label: t("titles.bc.vehicle-details", {
vehicle:
data && data.vehicles_by_pk
? `${
(data.vehicles_by_pk && data.vehicles_by_pk.v_model_yr) || ""
} ${
(data.vehicles_by_pk && data.vehicles_by_pk.v_make_desc) || ""
} ${
(data.vehicles_by_pk && data.vehicles_by_pk.v_model_desc) ||
""
}`
: "",
}),
},
]);
useEffect(() => {
document.title = t("titles.vehicledetail", {
vehicle:
data && data.vehicles_by_pk
? `${(data.vehicles_by_pk && data.vehicles_by_pk.v_model_yr) || ""} ${
(data.vehicles_by_pk && data.vehicles_by_pk.v_make_desc) || ""
} ${
(data.vehicles_by_pk && data.vehicles_by_pk.v_model_desc) || ""
}`
: "",
});
setSelectedHeader("vehicles");
if (data && data.vehicles_by_pk)
addRecentItem(
CreateRecentItem(
vehId,
"vehicle",
`${data.vehicles_by_pk.v_vin || "N/A"} | ${
data.vehicles_by_pk.v_model_yr || ""
} ${data.vehicles_by_pk.v_make_desc || ""} ${
data.vehicles_by_pk.v_model_desc || ""
}`.trim(),
`/manage/vehicles/${vehId}`
)
);
}, [t, data, setBreadcrumbs, vehId, addRecentItem, setSelectedHeader]);
setBreadcrumbs([
{link: "/manage/vehicles", label: t("titles.bc.vehicles")},
{
link: `/manage/vehicles/${vehId}`,
label: t("titles.bc.vehicle-details", {
vehicle:
data && data.vehicles_by_pk
? `${
(data.vehicles_by_pk && data.vehicles_by_pk.v_model_yr) || ""
} ${
(data.vehicles_by_pk && data.vehicles_by_pk.v_make_desc) || ""
} ${
(data.vehicles_by_pk && data.vehicles_by_pk.v_model_desc) ||
""
}`
: "",
}),
},
]);
if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />;
if (data && data.vehicles_by_pk)
addRecentItem(
CreateRecentItem(
vehId,
"vehicle",
`${data.vehicles_by_pk.v_vin || "N/A"} | ${
data.vehicles_by_pk.v_model_yr || ""
} ${data.vehicles_by_pk.v_make_desc || ""} ${
data.vehicles_by_pk.v_model_desc || ""
}`.trim(),
`/manage/vehicles/${vehId}`
)
);
}, [t, data, setBreadcrumbs, vehId, addRecentItem, setSelectedHeader]);
if (!!!data.vehicles_by_pk) return <NotFound />;
return (
<VehicleDetailComponent vehicle={data.vehicles_by_pk} refetch={refetch} />
);
if (loading) return <LoadingSpinner/>;
if (error) return <AlertComponent message={error.message} type="error"/>;
if (!data.vehicles_by_pk) return <NotFound/>;
return (
<VehicleDetailComponent vehicle={data.vehicles_by_pk} refetch={refetch}/>
);
}
export default connect(null, mapDispatchToProps)(VehicleDetailContainer);

View File

@@ -2,5 +2,5 @@ import React from "react";
import VehiclesListContainer from "../../components/vehicles-list/vehicles-list.container";
export default function VehiclesPageComponent() {
return <VehiclesListContainer />;
return <VehiclesListContainer/>;
}

View File

@@ -1,28 +1,27 @@
import React, { useEffect } from "react";
import React, {useEffect} from "react";
import VehiclesPageComponent from "./vehicles.page.component";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function VehiclesPageContainer({ setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation();
export function VehiclesPageContainer({setBreadcrumbs, setSelectedHeader}) {
const {t} = useTranslation();
useEffect(() => {
document.title = t("titles.vehicles");
setSelectedHeader("vehicles");
setBreadcrumbs([
{ link: "/manage/vehicles", label: t("titles.bc.vehicles") },
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
useEffect(() => {
document.title = t("titles.vehicles");
setSelectedHeader("vehicles");
setBreadcrumbs([
{link: "/manage/vehicles", label: t("titles.bc.vehicles")},
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
return <VehiclesPageComponent />;
return <VehiclesPageComponent/>;
}
export default connect(null, mapDispatchToProps)(VehiclesPageContainer);