Compare commits
28 Commits
rome/test
...
feature/IO
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f7e1b81ac | ||
|
|
a0572a0cec | ||
|
|
50349e91dc | ||
|
|
9998a8f154 | ||
|
|
90e1cbd390 | ||
|
|
6ba00a90be | ||
|
|
4293d20313 | ||
|
|
013b56778b | ||
|
|
9dec4a3a61 | ||
|
|
189b4db90f | ||
|
|
c49fa1c527 | ||
|
|
53ef048f6f | ||
|
|
1cc7eed983 | ||
|
|
1c8f377212 | ||
|
|
c050947276 | ||
|
|
9bde1f820d | ||
|
|
393765640c | ||
|
|
69c2836425 | ||
|
|
ec9edf30eb | ||
|
|
6a812f9ea7 | ||
|
|
fa86254bfd | ||
|
|
58ab7afbb3 | ||
|
|
fa7d90d2a9 | ||
|
|
fbf9047974 | ||
|
|
3e9b795052 | ||
|
|
d29ffc21e5 | ||
|
|
959f7780e8 | ||
|
|
e47731702a |
@@ -59,7 +59,7 @@ Filters can make use of reflection to pre-fill select boxes, the following is an
|
|||||||
"type": "internal",
|
"type": "internal",
|
||||||
"name": "special.job_statuses"
|
"name": "special.job_statuses"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
in this example, a reflector with the type 'internal' (all types at the moment require this, and it is used for future functionality), with a name of `special.job_statuses`
|
in this example, a reflector with the type 'internal' (all types at the moment require this, and it is used for future functionality), with a name of `special.job_statuses`
|
||||||
@@ -74,9 +74,13 @@ The following cases are available
|
|||||||
- `special.employees` - This will reflect the employees `bodyshop.employees`
|
- `special.employees` - This will reflect the employees `bodyshop.employees`
|
||||||
- `special.first_names` - This will reflect the first names `bodyshop.employees`
|
- `special.first_names` - This will reflect the first names `bodyshop.employees`
|
||||||
- `special.last_names` - This will reflect the last names `bodyshop.employees`
|
- `special.last_names` - This will reflect the last names `bodyshop.employees`
|
||||||
- `special.referral_sources` - This will reflect the referral sources `bodyshop.md_referral_sources
|
- `special.referral_sources` - This will reflect the referral sources `bodyshop.md_referral_sources`
|
||||||
- `special.class`- This will reflect the class `bodyshop.md_classes`
|
- `special.class`- This will reflect the class `bodyshop.md_classes`
|
||||||
-
|
- `special.lost_sale_reasons` - This will reflect the lost sale reasons `bodyshop.md_lost_sale_reasons`
|
||||||
|
- `special.alt_transports` - This will reflect the alternative transports `bodyshop.appt_alt_transport`
|
||||||
|
- `special.payment_types` - This will reflect the payment types `bodyshop.md_payment_types`
|
||||||
|
- `special.payment_payers` - This is a special case with a key value set of [Customer, Insurance]
|
||||||
|
|
||||||
### Path without brackets, multi level
|
### Path without brackets, multi level
|
||||||
|
|
||||||
`"name": "jobs.joblines.mod_lb_hrs",`
|
`"name": "jobs.joblines.mod_lb_hrs",`
|
||||||
@@ -156,6 +160,7 @@ query gendoc_hours_sold_detail_open($starttz: timestamptz!, $endtz: timestamptz!
|
|||||||
- Do not add the ability to filter things that are already filtered as part of the original query, this would be
|
- Do not add the ability to filter things that are already filtered as part of the original query, this would be
|
||||||
redundant and could cause issues.
|
redundant and could cause issues.
|
||||||
- Do not add the ability to filter on things like FK constraints, must like the above example.
|
- Do not add the ability to filter on things like FK constraints, must like the above example.
|
||||||
|
- *INHERITANCE CAVEAT* If you have a filters file on a parent report that has a child that you do not want the filters inherited from, you must place a blank filters file (valid json so {}) on the child report level. This will than fetch the child filters, which are empty and move along, versus inheriting the parent filters.
|
||||||
|
|
||||||
## Sorters
|
## Sorters
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
import { Input, Table, Checkbox, Card, Space } from "antd";
|
import { Card, Checkbox, Input, Space, Table } from "antd";
|
||||||
|
import queryString from "query-string";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
|
||||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
|
||||||
import PayableExportButton from "../payable-export-button/payable-export-button.component";
|
|
||||||
import PayableExportAll from "../payable-export-all-button/payable-export-all-button.component";
|
|
||||||
import { DateFormatter } from "../../utils/DateFormatter";
|
|
||||||
import queryString from "query-string";
|
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
|
||||||
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
|
import { DateFormatter } from "../../utils/DateFormatter";
|
||||||
|
import { pageLimit } from "../../utils/config";
|
||||||
|
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||||
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
||||||
|
import PayableExportAll from "../payable-export-all-button/payable-export-all-button.component";
|
||||||
|
import PayableExportButton from "../payable-export-button/payable-export-button.component";
|
||||||
import BillMarkSelectedExported from "../payable-mark-selected-exported/payable-mark-selected-exported.component";
|
import BillMarkSelectedExported from "../payable-mark-selected-exported/payable-mark-selected-exported.component";
|
||||||
import {pageLimit} from "../../utils/config";
|
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -138,7 +138,6 @@ export function AccountingPayablesTableComponent({
|
|||||||
title: t("exportlogs.labels.attempts"),
|
title: t("exportlogs.labels.attempts"),
|
||||||
dataIndex: "attempts",
|
dataIndex: "attempts",
|
||||||
key: "attempts",
|
key: "attempts",
|
||||||
|
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<ExportLogsCountDisplay logs={record.exportlogs} />
|
<ExportLogsCountDisplay logs={record.exportlogs} />
|
||||||
),
|
),
|
||||||
@@ -147,8 +146,6 @@ export function AccountingPayablesTableComponent({
|
|||||||
title: t("general.labels.actions"),
|
title: t("general.labels.actions"),
|
||||||
dataIndex: "actions",
|
dataIndex: "actions",
|
||||||
key: "actions",
|
key: "actions",
|
||||||
sorter: (a, b) => a.clm_total - b.clm_total,
|
|
||||||
|
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<PayableExportButton
|
<PayableExportButton
|
||||||
billId={record.id}
|
billId={record.id}
|
||||||
|
|||||||
@@ -8,14 +8,16 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
|
|||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
|
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
|
||||||
|
import { pageLimit } from "../../utils/config";
|
||||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||||
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
||||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
import OwnerNameDisplay, {
|
||||||
|
OwnerNameDisplayFunction,
|
||||||
|
} from "../owner-name-display/owner-name-display.component";
|
||||||
import PaymentExportButton from "../payment-export-button/payment-export-button.component";
|
import PaymentExportButton from "../payment-export-button/payment-export-button.component";
|
||||||
import PaymentMarkSelectedExported from "../payment-mark-selected-exported/payment-mark-selected-exported.component";
|
import PaymentMarkSelectedExported from "../payment-mark-selected-exported/payment-mark-selected-exported.component";
|
||||||
import PaymentsExportAllButton from "../payments-export-all-button/payments-export-all-button.component";
|
import PaymentsExportAllButton from "../payments-export-all-button/payments-export-all-button.component";
|
||||||
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
||||||
import {pageLimit} from "../../utils/config";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -75,7 +77,11 @@ export function AccountingPayablesTableComponent({
|
|||||||
dataIndex: "owner",
|
dataIndex: "owner",
|
||||||
key: "owner",
|
key: "owner",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
sorter: (a, b) => alphaSort(a.job.ownr_ln, b.job.ownr_ln),
|
sorter: (a, b) =>
|
||||||
|
alphaSort(
|
||||||
|
OwnerNameDisplayFunction(a.job),
|
||||||
|
OwnerNameDisplayFunction(b.job)
|
||||||
|
),
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
|
||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
@@ -94,6 +100,9 @@ export function AccountingPayablesTableComponent({
|
|||||||
title: t("payments.fields.amount"),
|
title: t("payments.fields.amount"),
|
||||||
dataIndex: "amount",
|
dataIndex: "amount",
|
||||||
key: "amount",
|
key: "amount",
|
||||||
|
sorter: (a, b) => a.amount - b.amount,
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "amount" && state.sortedInfo.order,
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<CurrencyFormatter>{record.amount}</CurrencyFormatter>
|
<CurrencyFormatter>{record.amount}</CurrencyFormatter>
|
||||||
),
|
),
|
||||||
@@ -112,18 +121,21 @@ export function AccountingPayablesTableComponent({
|
|||||||
title: t("payments.fields.created_at"),
|
title: t("payments.fields.created_at"),
|
||||||
dataIndex: "created_at",
|
dataIndex: "created_at",
|
||||||
key: "created_at",
|
key: "created_at",
|
||||||
|
sorter: (a, b) => dateSort(a.created_at, b.created_at),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "created_at" && state.sortedInfo.order,
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<DateTimeFormatter>{record.created_at}</DateTimeFormatter>
|
<DateTimeFormatter>{record.created_at}</DateTimeFormatter>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
title: t("payments.fields.exportedat"),
|
// title: t("payments.fields.exportedat"),
|
||||||
dataIndex: "exportedat",
|
// dataIndex: "exportedat",
|
||||||
key: "exportedat",
|
// key: "exportedat",
|
||||||
render: (text, record) => (
|
// render: (text, record) => (
|
||||||
<DateTimeFormatter>{record.exportedat}</DateTimeFormatter>
|
// <DateTimeFormatter>{record.exportedat}</DateTimeFormatter>
|
||||||
),
|
// ),
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
title: t("exportlogs.labels.attempts"),
|
title: t("exportlogs.labels.attempts"),
|
||||||
dataIndex: "attempts",
|
dataIndex: "attempts",
|
||||||
@@ -137,8 +149,6 @@ export function AccountingPayablesTableComponent({
|
|||||||
title: t("general.labels.actions"),
|
title: t("general.labels.actions"),
|
||||||
dataIndex: "actions",
|
dataIndex: "actions",
|
||||||
key: "actions",
|
key: "actions",
|
||||||
sorter: (a, b) => a.clm_total - b.clm_total,
|
|
||||||
|
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<PaymentExportButton
|
<PaymentExportButton
|
||||||
paymentId={record.id}
|
paymentId={record.id}
|
||||||
|
|||||||
@@ -4,17 +4,19 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
import { alphaSort, dateSort, statusSort } from "../../utils/sorters";
|
||||||
import JobExportButton from "../jobs-close-export-button/jobs-close-export-button.component";
|
import JobExportButton from "../jobs-close-export-button/jobs-close-export-button.component";
|
||||||
import JobsExportAllButton from "../jobs-export-all-button/jobs-export-all-button.component";
|
import JobsExportAllButton from "../jobs-export-all-button/jobs-export-all-button.component";
|
||||||
|
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
|
||||||
import { DateFormatter } from "../../utils/DateFormatter";
|
import { DateFormatter } from "../../utils/DateFormatter";
|
||||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
|
||||||
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
||||||
|
import OwnerNameDisplay, {
|
||||||
|
OwnerNameDisplayFunction,
|
||||||
|
} from "../owner-name-display/owner-name-display.component";
|
||||||
|
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -63,7 +65,7 @@ export function AccountingReceivablesTableComponent({
|
|||||||
title: t("jobs.fields.status"),
|
title: t("jobs.fields.status"),
|
||||||
dataIndex: "status",
|
dataIndex: "status",
|
||||||
key: "status",
|
key: "status",
|
||||||
sorter: (a, b) => a.status - b.status,
|
sorter: (a, b) => statusSort(a, b, bodyshop.md_ro_statuses.statuses),
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
||||||
},
|
},
|
||||||
@@ -83,7 +85,8 @@ export function AccountingReceivablesTableComponent({
|
|||||||
title: t("jobs.fields.owner"),
|
title: t("jobs.fields.owner"),
|
||||||
dataIndex: "owner",
|
dataIndex: "owner",
|
||||||
key: "owner",
|
key: "owner",
|
||||||
sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
|
sorter: (a, b) =>
|
||||||
|
alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)),
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
|
||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
@@ -103,6 +106,15 @@ export function AccountingReceivablesTableComponent({
|
|||||||
dataIndex: "vehicle",
|
dataIndex: "vehicle",
|
||||||
key: "vehicle",
|
key: "vehicle",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
|
sorter: (a, b) =>
|
||||||
|
alphaSort(
|
||||||
|
`${a.v_model_yr || ""} ${a.v_make_desc || ""} ${
|
||||||
|
a.v_model_desc || ""
|
||||||
|
}`,
|
||||||
|
`${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}`
|
||||||
|
),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order,
|
||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
return record.vehicleid ? (
|
return record.vehicleid ? (
|
||||||
<Link to={"/manage/vehicles/" + record.vehicleid}>
|
<Link to={"/manage/vehicles/" + record.vehicleid}>
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
|
|||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({});
|
const mapStateToProps = createStructuredSelector({});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
insertAuditTrail: ({ jobid, operation }) =>
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
dispatch(insertAuditTrail({ jobid, operation })),
|
dispatch(insertAuditTrail({ jobid, operation, type })),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(BillDeleteButton);
|
export default connect(mapStateToProps, mapDispatchToProps)(BillDeleteButton);
|
||||||
@@ -51,6 +51,7 @@ export function BillDeleteButton({ bill, jobid, callback, insertAuditTrail }) {
|
|||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: jobid,
|
jobid: jobid,
|
||||||
operation: AuditTrailMapping.billdeleted(bill.invoice_number),
|
operation: AuditTrailMapping.billdeleted(bill.invoice_number),
|
||||||
|
type: "billdeleted",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (callback && typeof callback === "function") callback(bill.id);
|
if (callback && typeof callback === "function") callback(bill.id);
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
setPartsOrderContext: (context) =>
|
setPartsOrderContext: (context) =>
|
||||||
dispatch(setModalContext({ context: context, modal: "partsOrder" })),
|
dispatch(setModalContext({ context: context, modal: "partsOrder" })),
|
||||||
insertAuditTrail: ({ jobid, operation }) =>
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
dispatch(insertAuditTrail({ jobid, operation })),
|
dispatch(insertAuditTrail({ jobid, operation, type })),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
@@ -150,6 +150,7 @@ export function BillDetailEditcontainer({
|
|||||||
jobid: bill.jobid,
|
jobid: bill.jobid,
|
||||||
billid: search.billid,
|
billid: search.billid,
|
||||||
operation: AuditTrailMapping.billupdated(bill.invoice_number),
|
operation: AuditTrailMapping.billupdated(bill.invoice_number),
|
||||||
|
type: "billupdated",
|
||||||
});
|
});
|
||||||
|
|
||||||
await refetch();
|
await refetch();
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
setPartsOrderContext: (context) =>
|
setPartsOrderContext: (context) =>
|
||||||
dispatch(setModalContext({ context: context, modal: "partsOrder" })),
|
dispatch(setModalContext({ context: context, modal: "partsOrder" })),
|
||||||
insertAuditTrail: ({ jobid, operation }) =>
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
dispatch(insertAuditTrail({ jobid, operation })),
|
dispatch(insertAuditTrail({ jobid, operation, type })),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
toggleModalVisible: () => dispatch(toggleModalVisible("billEnter")),
|
toggleModalVisible: () => dispatch(toggleModalVisible("billEnter")),
|
||||||
insertAuditTrail: ({ jobid, billid, operation }) =>
|
insertAuditTrail: ({ jobid, billid, operation, type }) =>
|
||||||
dispatch(insertAuditTrail({ jobid, billid, operation })),
|
dispatch(insertAuditTrail({ jobid, billid, operation, type })),
|
||||||
});
|
});
|
||||||
|
|
||||||
const Templates = TemplateList("job_special");
|
const Templates = TemplateList("job_special");
|
||||||
@@ -171,6 +171,7 @@ function BillEnterModalContainer({
|
|||||||
mod_lbr_ty: key,
|
mod_lbr_ty: key,
|
||||||
hours: adjustmentsToInsert[key].toFixed(1),
|
hours: adjustmentsToInsert[key].toFixed(1),
|
||||||
}),
|
}),
|
||||||
|
type: "jobmodifylbradj",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -320,6 +321,7 @@ function BillEnterModalContainer({
|
|||||||
operation: AuditTrailMapping.billposted(
|
operation: AuditTrailMapping.billposted(
|
||||||
r1.data.insert_bills.returning[0].invoice_number
|
r1.data.insert_bills.returning[0].invoice_number
|
||||||
),
|
),
|
||||||
|
type: "billposted",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (enterAgain) {
|
if (enterAgain) {
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
insertAuditTrail: ({ jobid, operation }) =>
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
dispatch(insertAuditTrail({ jobid, operation })),
|
dispatch(insertAuditTrail({ jobid, operation, type })),
|
||||||
toggleModalVisible: () => dispatch(toggleModalVisible("cardPayment")),
|
toggleModalVisible: () => dispatch(toggleModalVisible("cardPayment")),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -102,6 +102,7 @@ const CardPaymentModalComponent = ({
|
|||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: payment.jobid,
|
jobid: payment.jobid,
|
||||||
operation: AuditTrailMapping.failedpayment(),
|
operation: AuditTrailMapping.failedpayment(),
|
||||||
|
type: "failedpayment",
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
import {Card, Table, Tag} from "antd";
|
||||||
|
import LoadingSkeleton from "../../loading-skeleton/loading-skeleton.component";
|
||||||
|
import {useTranslation} from "react-i18next";
|
||||||
|
import React, {useEffect, useState} from "react";
|
||||||
|
import moment from "moment";
|
||||||
|
import DashboardRefreshRequired from "../refresh-required.component";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
const fortyFiveDaysAgo = () => moment().subtract(45, 'days').toLocaleString();
|
||||||
|
|
||||||
|
export default function JobLifecycleDashboardComponent({data, bodyshop, ...cardProps}) {
|
||||||
|
const {t} = useTranslation();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [lifecycleData, setLifecycleData] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function getLifecycleData() {
|
||||||
|
if (data && data.job_lifecycle) {
|
||||||
|
setLoading(true);
|
||||||
|
const response = await axios.post("/job/lifecycle", {
|
||||||
|
jobids: data.job_lifecycle.map(x => x.id),
|
||||||
|
statuses: bodyshop.md_order_statuses
|
||||||
|
});
|
||||||
|
setLifecycleData(response.data.durations);
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getLifecycleData().catch(e => {
|
||||||
|
console.error(`Error in getLifecycleData: ${e}`);
|
||||||
|
})
|
||||||
|
}, [data, bodyshop]);
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: t('job_lifecycle.columns.status'),
|
||||||
|
dataIndex: 'status',
|
||||||
|
bgColor: 'red',
|
||||||
|
key: 'status',
|
||||||
|
render: (text, record) => {
|
||||||
|
return <Tag color={record.color}>{record.status}</Tag>
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('job_lifecycle.columns.human_readable'),
|
||||||
|
dataIndex: 'humanReadable',
|
||||||
|
key: 'humanReadable',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('job_lifecycle.columns.status_count'),
|
||||||
|
key: 'statusCount',
|
||||||
|
render: (text, record) => {
|
||||||
|
return lifecycleData.statusCounts[record.status];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('job_lifecycle.columns.percentage'),
|
||||||
|
dataIndex: 'percentage',
|
||||||
|
key: 'percentage',
|
||||||
|
render: (text, record) => {
|
||||||
|
return record.percentage.toFixed(2) + '%';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!data) return null;
|
||||||
|
|
||||||
|
if (!data.job_lifecycle || !lifecycleData) return <DashboardRefreshRequired {...cardProps} />;
|
||||||
|
|
||||||
|
const extra = `${t('job_lifecycle.content.calculated_based_on')} ${lifecycleData.jobs} ${t('job_lifecycle.content.jobs_in_since')} ${fortyFiveDaysAgo()}`
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card title={t("job_lifecycle.titles.dashboard")} {...cardProps}>
|
||||||
|
<LoadingSkeleton loading={loading}>
|
||||||
|
<div style={{overflow: 'scroll', height: "100%"}}>
|
||||||
|
<div id="bar-container" style={{
|
||||||
|
display: 'flex',
|
||||||
|
width: '100%',
|
||||||
|
height: '100px',
|
||||||
|
textAlign: 'center',
|
||||||
|
borderRadius: '5px',
|
||||||
|
borderWidth: '5px',
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderColor: '#f0f2f5',
|
||||||
|
margin: 0,
|
||||||
|
padding: 0
|
||||||
|
}}>
|
||||||
|
{lifecycleData.summations.map((key, index, array) => {
|
||||||
|
const isFirst = index === 0;
|
||||||
|
const isLast = index === array.length - 1;
|
||||||
|
return (
|
||||||
|
<div key={key.status} style={{
|
||||||
|
overflow: 'hidden',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
margin: 0,
|
||||||
|
padding: 0,
|
||||||
|
|
||||||
|
borderTop: '1px solid #f0f2f5',
|
||||||
|
borderBottom: '1px solid #f0f2f5',
|
||||||
|
borderLeft: isFirst ? '1px solid #f0f2f5' : undefined,
|
||||||
|
borderRight: isLast ? '1px solid #f0f2f5' : undefined,
|
||||||
|
|
||||||
|
backgroundColor: key.color,
|
||||||
|
width: `${key.percentage}%`
|
||||||
|
}}
|
||||||
|
aria-label={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
||||||
|
title={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
||||||
|
>
|
||||||
|
|
||||||
|
{key.percentage > 15 ?
|
||||||
|
<>
|
||||||
|
<div>{key.roundedPercentage}</div>
|
||||||
|
<div style={{
|
||||||
|
backgroundColor: '#f0f2f5',
|
||||||
|
borderRadius: '5px',
|
||||||
|
paddingRight: '2px',
|
||||||
|
paddingLeft: '2px',
|
||||||
|
fontSize: '0.8rem',
|
||||||
|
}}>
|
||||||
|
{key.status}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
: null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<Card extra={extra} type='inner' title={t('job_lifecycle.content.legend_title')}
|
||||||
|
style={{marginTop: '10px'}}>
|
||||||
|
<div>
|
||||||
|
{lifecycleData.summations.map((key) => (
|
||||||
|
<Tag color={key.color} style={{width: '13vh', padding: '4px', margin: '4px'}}>
|
||||||
|
<div
|
||||||
|
aria-label={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
||||||
|
title={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#f0f2f5',
|
||||||
|
color: '#000',
|
||||||
|
padding: '4px',
|
||||||
|
textAlign: 'center'
|
||||||
|
}}>
|
||||||
|
{key.status} [{lifecycleData.statusCounts[key.status]}] ({key.roundedPercentage})
|
||||||
|
</div>
|
||||||
|
</Tag>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
<Card style={{marginTop: "5px"}} type='inner' title={t("job_lifecycle.titles.top_durations")}>
|
||||||
|
<Table size="small" pagination={false} columns={columns}
|
||||||
|
dataSource={lifecycleData.summations.sort((a, b) => b.value - a.value).slice(0, 3)}/>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</LoadingSkeleton>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const JobLifecycleDashboardGQL = `
|
||||||
|
job_lifecycle: jobs(where: {
|
||||||
|
actual_in: {
|
||||||
|
_gte: "${moment().subtract(45, 'days').toISOString()}"
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
id
|
||||||
|
actual_in
|
||||||
|
} `;
|
||||||
@@ -1,380 +1,391 @@
|
|||||||
import Icon, { SyncOutlined } from "@ant-design/icons";
|
import Icon, {SyncOutlined} from "@ant-design/icons";
|
||||||
import { gql, useMutation, useQuery } from "@apollo/client";
|
import {gql, useMutation, useQuery} from "@apollo/client";
|
||||||
import { Button, Dropdown, Menu, PageHeader, Space, notification } from "antd";
|
import {Button, Dropdown, Menu, notification, PageHeader, Space} from "antd";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import React, { useState } from "react";
|
import React, {useState} from "react";
|
||||||
import { Responsive, WidthProvider } from "react-grid-layout";
|
import {Responsive, WidthProvider} from "react-grid-layout";
|
||||||
import { useTranslation } from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
import { MdClose } from "react-icons/md";
|
import {MdClose} from "react-icons/md";
|
||||||
import { connect } from "react-redux";
|
import {connect} from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import {createStructuredSelector} from "reselect";
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import {logImEXEvent} from "../../firebase/firebase.utils";
|
||||||
import { UPDATE_DASHBOARD_LAYOUT } from "../../graphql/user.queries";
|
import {UPDATE_DASHBOARD_LAYOUT} from "../../graphql/user.queries";
|
||||||
import {
|
import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
|
||||||
selectBodyshop,
|
|
||||||
selectCurrentUser,
|
|
||||||
} from "../../redux/user/user.selectors";
|
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import DashboardMonthlyEmployeeEfficiency, {
|
import DashboardMonthlyEmployeeEfficiency, {
|
||||||
DashboardMonthlyEmployeeEfficiencyGql,
|
DashboardMonthlyEmployeeEfficiencyGql,
|
||||||
} from "../dashboard-components/monthly-employee-efficiency/monthly-employee-efficiency.component";
|
} from "../dashboard-components/monthly-employee-efficiency/monthly-employee-efficiency.component";
|
||||||
import DashboardMonthlyJobCosting from "../dashboard-components/monthly-job-costing/monthly-job-costing.component";
|
import DashboardMonthlyJobCosting from "../dashboard-components/monthly-job-costing/monthly-job-costing.component";
|
||||||
import DashboardMonthlyLaborSales from "../dashboard-components/monthly-labor-sales/monthly-labor-sales.component";
|
import DashboardMonthlyLaborSales from "../dashboard-components/monthly-labor-sales/monthly-labor-sales.component";
|
||||||
import DashboardMonthlyPartsSales from "../dashboard-components/monthly-parts-sales/monthly-parts-sales.component";
|
import DashboardMonthlyPartsSales from "../dashboard-components/monthly-parts-sales/monthly-parts-sales.component";
|
||||||
import DashboardMonthlyRevenueGraph, {
|
import DashboardMonthlyRevenueGraph, {
|
||||||
DashboardMonthlyRevenueGraphGql,
|
DashboardMonthlyRevenueGraphGql,
|
||||||
} from "../dashboard-components/monthly-revenue-graph/monthly-revenue-graph.component";
|
} from "../dashboard-components/monthly-revenue-graph/monthly-revenue-graph.component";
|
||||||
import DashboardProjectedMonthlySales, {
|
import DashboardProjectedMonthlySales, {
|
||||||
DashboardProjectedMonthlySalesGql,
|
DashboardProjectedMonthlySalesGql,
|
||||||
} from "../dashboard-components/pojected-monthly-sales/projected-monthly-sales.component";
|
} from "../dashboard-components/pojected-monthly-sales/projected-monthly-sales.component";
|
||||||
import DashboardTotalProductionDollars from "../dashboard-components/total-production-dollars/total-production-dollars.component";
|
import DashboardTotalProductionDollars
|
||||||
|
from "../dashboard-components/total-production-dollars/total-production-dollars.component";
|
||||||
import DashboardTotalProductionHours, {
|
import DashboardTotalProductionHours, {
|
||||||
DashboardTotalProductionHoursGql,
|
DashboardTotalProductionHoursGql,
|
||||||
} from "../dashboard-components/total-production-hours/total-production-hours.component";
|
} from "../dashboard-components/total-production-hours/total-production-hours.component";
|
||||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||||
//Combination of the following:
|
//Combination of the following:
|
||||||
// /node_modules/react-grid-layout/css/styles.css
|
// /node_modules/react-grid-layout/css/styles.css
|
||||||
// /node_modules/react-resizable/css/styles.css
|
// /node_modules/react-resizable/css/styles.css
|
||||||
import DashboardScheduledInToday, {
|
import DashboardScheduledInToday, {
|
||||||
DashboardScheduledInTodayGql,
|
DashboardScheduledInTodayGql,
|
||||||
} from "../dashboard-components/scheduled-in-today/scheduled-in-today.component";
|
} from "../dashboard-components/scheduled-in-today/scheduled-in-today.component";
|
||||||
import DashboardScheduledOutToday, {
|
import DashboardScheduledOutToday, {
|
||||||
DashboardScheduledOutTodayGql,
|
DashboardScheduledOutTodayGql,
|
||||||
} from "../dashboard-components/scheduled-out-today/scheduled-out-today.component";
|
} from "../dashboard-components/scheduled-out-today/scheduled-out-today.component";
|
||||||
|
import JobLifecycleDashboardComponent, {
|
||||||
|
JobLifecycleDashboardGQL
|
||||||
|
} from "../dashboard-components/job-lifecycle/job-lifecycle-dashboard.component";
|
||||||
import "./dashboard-grid.styles.scss";
|
import "./dashboard-grid.styles.scss";
|
||||||
import { GenerateDashboardData } from "./dashboard-grid.utils";
|
import {GenerateDashboardData} from "./dashboard-grid.utils";
|
||||||
|
|
||||||
const ResponsiveReactGridLayout = WidthProvider(Responsive);
|
const ResponsiveReactGridLayout = WidthProvider(Responsive);
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
currentUser: selectCurrentUser,
|
currentUser: selectCurrentUser,
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
});
|
});
|
||||||
|
|
||||||
export function DashboardGridComponent({ currentUser, bodyshop }) {
|
export function DashboardGridComponent({currentUser, bodyshop}) {
|
||||||
const { t } = useTranslation();
|
const {t} = useTranslation();
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
...(bodyshop.associations[0].user.dashboardlayout
|
...(bodyshop.associations[0].user.dashboardlayout
|
||||||
? bodyshop.associations[0].user.dashboardlayout
|
? bodyshop.associations[0].user.dashboardlayout
|
||||||
: { items: [], layout: {}, layouts: [] }),
|
: {items: [], layout: {}, layouts: []}),
|
||||||
});
|
|
||||||
|
|
||||||
const { loading, error, data, refetch } = useQuery(
|
|
||||||
createDashboardQuery(state),
|
|
||||||
{ fetchPolicy: "network-only", nextFetchPolicy: "network-only" }
|
|
||||||
);
|
|
||||||
|
|
||||||
const [updateLayout] = useMutation(UPDATE_DASHBOARD_LAYOUT);
|
|
||||||
|
|
||||||
const handleLayoutChange = async (layout, layouts) => {
|
|
||||||
logImEXEvent("dashboard_change_layout");
|
|
||||||
|
|
||||||
setState({ ...state, layout, layouts });
|
|
||||||
|
|
||||||
const result = await updateLayout({
|
|
||||||
variables: {
|
|
||||||
email: currentUser.email,
|
|
||||||
layout: { ...state, layout, layouts },
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
if (!!result.errors) {
|
|
||||||
notification["error"]({
|
|
||||||
message: t("dashboard.errors.updatinglayout", {
|
|
||||||
message: JSON.stringify(result.errors),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const handleRemoveComponent = (key) => {
|
|
||||||
logImEXEvent("dashboard_remove_component", { name: key });
|
|
||||||
const idxToRemove = state.items.findIndex((i) => i.i === key);
|
|
||||||
|
|
||||||
const items = _.cloneDeep(state.items);
|
const {loading, error, data, refetch} = useQuery(
|
||||||
|
createDashboardQuery(state),
|
||||||
|
{fetchPolicy: "network-only", nextFetchPolicy: "network-only"}
|
||||||
|
);
|
||||||
|
|
||||||
items.splice(idxToRemove, 1);
|
const [updateLayout] = useMutation(UPDATE_DASHBOARD_LAYOUT);
|
||||||
setState({ ...state, items });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAddComponent = (e) => {
|
const handleLayoutChange = async (layout, layouts) => {
|
||||||
logImEXEvent("dashboard_add_component", { name: e });
|
logImEXEvent("dashboard_change_layout");
|
||||||
setState({
|
|
||||||
...state,
|
|
||||||
items: [
|
|
||||||
...state.items,
|
|
||||||
{
|
|
||||||
i: e.key,
|
|
||||||
x: (state.items.length * 2) % (state.cols || 12),
|
|
||||||
y: 99, // puts it at the bottom
|
|
||||||
w: componentList[e.key].w || 2,
|
|
||||||
h: componentList[e.key].h || 2,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const dashboarddata = React.useMemo(
|
setState({...state, layout, layouts});
|
||||||
() => GenerateDashboardData(data),
|
|
||||||
[data]
|
|
||||||
);
|
|
||||||
const existingLayoutKeys = state.items.map((i) => i.i);
|
|
||||||
const addComponentOverlay = (
|
|
||||||
<Menu onClick={handleAddComponent}>
|
|
||||||
{Object.keys(componentList).map((key) => (
|
|
||||||
<Menu.Item
|
|
||||||
key={key}
|
|
||||||
value={key}
|
|
||||||
disabled={existingLayoutKeys.includes(key)}
|
|
||||||
>
|
|
||||||
{componentList[key].label}
|
|
||||||
</Menu.Item>
|
|
||||||
))}
|
|
||||||
</Menu>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
const result = await updateLayout({
|
||||||
|
variables: {
|
||||||
return (
|
email: currentUser.email,
|
||||||
<div>
|
layout: {...state, layout, layouts},
|
||||||
<PageHeader
|
},
|
||||||
extra={
|
});
|
||||||
<Space>
|
if (!!result.errors) {
|
||||||
<Button onClick={() => refetch()}>
|
notification["error"]({
|
||||||
<SyncOutlined />
|
message: t("dashboard.errors.updatinglayout", {
|
||||||
</Button>
|
message: JSON.stringify(result.errors),
|
||||||
<Dropdown overlay={addComponentOverlay} trigger={["click"]}>
|
}),
|
||||||
<Button>{t("dashboard.actions.addcomponent")}</Button>
|
});
|
||||||
</Dropdown>
|
|
||||||
</Space>
|
|
||||||
}
|
}
|
||||||
/>
|
};
|
||||||
|
const handleRemoveComponent = (key) => {
|
||||||
|
logImEXEvent("dashboard_remove_component", {name: key});
|
||||||
|
const idxToRemove = state.items.findIndex((i) => i.i === key);
|
||||||
|
|
||||||
<ResponsiveReactGridLayout
|
const items = _.cloneDeep(state.items);
|
||||||
className="layout"
|
|
||||||
breakpoints={{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }}
|
items.splice(idxToRemove, 1);
|
||||||
cols={{ lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 }}
|
setState({...state, items});
|
||||||
width="100%"
|
};
|
||||||
layouts={state.layouts}
|
|
||||||
onLayoutChange={handleLayoutChange}
|
const handleAddComponent = (e) => {
|
||||||
// onBreakpointChange={onBreakpointChange}
|
logImEXEvent("dashboard_add_component", {name: e});
|
||||||
>
|
setState({
|
||||||
{state.items.map((item, index) => {
|
...state,
|
||||||
const TheComponent = componentList[item.i].component;
|
items: [
|
||||||
return (
|
...state.items,
|
||||||
<div
|
{
|
||||||
key={item.i}
|
i: e.key,
|
||||||
data-grid={{
|
x: (state.items.length * 2) % (state.cols || 12),
|
||||||
...item,
|
y: 99, // puts it at the bottom
|
||||||
minH: componentList[item.i].minH || 1,
|
w: componentList[e.key].w || 2,
|
||||||
minW: componentList[item.i].minW || 1,
|
h: componentList[e.key].h || 2,
|
||||||
}}
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const dashboarddata = React.useMemo(
|
||||||
|
() => GenerateDashboardData(data),
|
||||||
|
[data]
|
||||||
|
);
|
||||||
|
const existingLayoutKeys = state.items.map((i) => i.i);
|
||||||
|
const addComponentOverlay = (
|
||||||
|
<Menu onClick={handleAddComponent}>
|
||||||
|
{Object.keys(componentList).map((key) => (
|
||||||
|
<Menu.Item
|
||||||
|
key={key}
|
||||||
|
value={key}
|
||||||
|
disabled={existingLayoutKeys.includes(key)}
|
||||||
|
>
|
||||||
|
{componentList[key].label}
|
||||||
|
</Menu.Item>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (error) return <AlertComponent message={error.message} type="error"/>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<PageHeader
|
||||||
|
extra={
|
||||||
|
<Space>
|
||||||
|
<Button onClick={() => refetch()}>
|
||||||
|
<SyncOutlined/>
|
||||||
|
</Button>
|
||||||
|
<Dropdown overlay={addComponentOverlay} trigger={["click"]}>
|
||||||
|
<Button>{t("dashboard.actions.addcomponent")}</Button>
|
||||||
|
</Dropdown>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ResponsiveReactGridLayout
|
||||||
|
className="layout"
|
||||||
|
breakpoints={{lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0}}
|
||||||
|
cols={{lg: 12, md: 10, sm: 6, xs: 4, xxs: 2}}
|
||||||
|
width="100%"
|
||||||
|
layouts={state.layouts}
|
||||||
|
onLayoutChange={handleLayoutChange}
|
||||||
|
// onBreakpointChange={onBreakpointChange}
|
||||||
>
|
>
|
||||||
<LoadingSkeleton loading={loading}>
|
{state.items.map((item, index) => {
|
||||||
<Icon
|
const TheComponent = componentList[item.i].component;
|
||||||
component={MdClose}
|
return (
|
||||||
key={item.i}
|
<div
|
||||||
style={{
|
key={item.i}
|
||||||
position: "absolute",
|
data-grid={{
|
||||||
zIndex: "2",
|
...item,
|
||||||
right: ".25rem",
|
minH: componentList[item.i].minH || 1,
|
||||||
top: ".25rem",
|
minW: componentList[item.i].minW || 1,
|
||||||
cursor: "pointer",
|
}}
|
||||||
}}
|
>
|
||||||
onClick={() => handleRemoveComponent(item.i)}
|
<LoadingSkeleton loading={loading}>
|
||||||
/>
|
<Icon
|
||||||
<TheComponent className="dashboard-card" data={dashboarddata} />
|
component={MdClose}
|
||||||
</LoadingSkeleton>
|
key={item.i}
|
||||||
</div>
|
style={{
|
||||||
);
|
position: "absolute",
|
||||||
})}
|
zIndex: "2",
|
||||||
</ResponsiveReactGridLayout>
|
right: ".25rem",
|
||||||
</div>
|
top: ".25rem",
|
||||||
);
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
onClick={() => handleRemoveComponent(item.i)}
|
||||||
|
/>
|
||||||
|
<TheComponent className="dashboard-card" bodyshop={bodyshop} data={dashboarddata}/>
|
||||||
|
</LoadingSkeleton>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ResponsiveReactGridLayout>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
mapDispatchToProps
|
mapDispatchToProps
|
||||||
)(DashboardGridComponent);
|
)(DashboardGridComponent);
|
||||||
|
|
||||||
const componentList = {
|
const componentList = {
|
||||||
ProductionDollars: {
|
ProductionDollars: {
|
||||||
label: i18next.t("dashboard.titles.productiondollars"),
|
label: i18next.t("dashboard.titles.productiondollars"),
|
||||||
component: DashboardTotalProductionDollars,
|
component: DashboardTotalProductionDollars,
|
||||||
gqlFragment: null,
|
gqlFragment: null,
|
||||||
w: 1,
|
w: 1,
|
||||||
h: 1,
|
h: 1,
|
||||||
minW: 2,
|
minW: 2,
|
||||||
minH: 1,
|
minH: 1,
|
||||||
},
|
},
|
||||||
ProductionHours: {
|
ProductionHours: {
|
||||||
label: i18next.t("dashboard.titles.productionhours"),
|
label: i18next.t("dashboard.titles.productionhours"),
|
||||||
component: DashboardTotalProductionHours,
|
component: DashboardTotalProductionHours,
|
||||||
gqlFragment: DashboardTotalProductionHoursGql,
|
gqlFragment: DashboardTotalProductionHoursGql,
|
||||||
w: 3,
|
w: 3,
|
||||||
h: 1,
|
h: 1,
|
||||||
minW: 3,
|
minW: 3,
|
||||||
minH: 1,
|
minH: 1,
|
||||||
},
|
},
|
||||||
ProjectedMonthlySales: {
|
ProjectedMonthlySales: {
|
||||||
label: i18next.t("dashboard.titles.projectedmonthlysales"),
|
label: i18next.t("dashboard.titles.projectedmonthlysales"),
|
||||||
component: DashboardProjectedMonthlySales,
|
component: DashboardProjectedMonthlySales,
|
||||||
gqlFragment: DashboardProjectedMonthlySalesGql,
|
gqlFragment: DashboardProjectedMonthlySalesGql,
|
||||||
w: 2,
|
w: 2,
|
||||||
h: 1,
|
h: 1,
|
||||||
minW: 2,
|
minW: 2,
|
||||||
minH: 1,
|
minH: 1,
|
||||||
},
|
},
|
||||||
MonthlyRevenueGraph: {
|
MonthlyRevenueGraph: {
|
||||||
label: i18next.t("dashboard.titles.monthlyrevenuegraph"),
|
label: i18next.t("dashboard.titles.monthlyrevenuegraph"),
|
||||||
component: DashboardMonthlyRevenueGraph,
|
component: DashboardMonthlyRevenueGraph,
|
||||||
gqlFragment: DashboardMonthlyRevenueGraphGql,
|
gqlFragment: DashboardMonthlyRevenueGraphGql,
|
||||||
w: 4,
|
w: 4,
|
||||||
h: 2,
|
h: 2,
|
||||||
minW: 4,
|
minW: 4,
|
||||||
minH: 2,
|
minH: 2,
|
||||||
},
|
},
|
||||||
MonthlyJobCosting: {
|
MonthlyJobCosting: {
|
||||||
label: i18next.t("dashboard.titles.monthlyjobcosting"),
|
label: i18next.t("dashboard.titles.monthlyjobcosting"),
|
||||||
component: DashboardMonthlyJobCosting,
|
component: DashboardMonthlyJobCosting,
|
||||||
gqlFragment: null,
|
gqlFragment: null,
|
||||||
minW: 6,
|
minW: 6,
|
||||||
minH: 3,
|
minH: 3,
|
||||||
w: 6,
|
w: 6,
|
||||||
h: 3,
|
h: 3,
|
||||||
},
|
},
|
||||||
MonthlyPartsSales: {
|
MonthlyPartsSales: {
|
||||||
label: i18next.t("dashboard.titles.monthlypartssales"),
|
label: i18next.t("dashboard.titles.monthlypartssales"),
|
||||||
component: DashboardMonthlyPartsSales,
|
component: DashboardMonthlyPartsSales,
|
||||||
gqlFragment: null,
|
gqlFragment: null,
|
||||||
minW: 2,
|
minW: 2,
|
||||||
minH: 2,
|
minH: 2,
|
||||||
w: 2,
|
w: 2,
|
||||||
h: 2,
|
h: 2,
|
||||||
},
|
},
|
||||||
MonthlyLaborSales: {
|
MonthlyLaborSales: {
|
||||||
label: i18next.t("dashboard.titles.monthlylaborsales"),
|
label: i18next.t("dashboard.titles.monthlylaborsales"),
|
||||||
component: DashboardMonthlyLaborSales,
|
component: DashboardMonthlyLaborSales,
|
||||||
gqlFragment: null,
|
gqlFragment: null,
|
||||||
minW: 2,
|
minW: 2,
|
||||||
minH: 2,
|
minH: 2,
|
||||||
w: 2,
|
w: 2,
|
||||||
h: 2,
|
h: 2,
|
||||||
},
|
},
|
||||||
MonthlyEmployeeEfficency: {
|
// Typo in Efficency should be Efficiency, but changing it would reset users dashboard settings
|
||||||
label: i18next.t("dashboard.titles.monthlyemployeeefficiency"),
|
MonthlyEmployeeEfficency: {
|
||||||
component: DashboardMonthlyEmployeeEfficiency,
|
label: i18next.t("dashboard.titles.monthlyemployeeefficiency"),
|
||||||
gqlFragment: DashboardMonthlyEmployeeEfficiencyGql,
|
component: DashboardMonthlyEmployeeEfficiency,
|
||||||
minW: 2,
|
gqlFragment: DashboardMonthlyEmployeeEfficiencyGql,
|
||||||
minH: 2,
|
minW: 2,
|
||||||
w: 2,
|
minH: 2,
|
||||||
h: 2,
|
w: 2,
|
||||||
},
|
h: 2,
|
||||||
ScheduleInToday: {
|
},
|
||||||
label: i18next.t("dashboard.titles.scheduledintoday"),
|
ScheduleInToday: {
|
||||||
component: DashboardScheduledInToday,
|
label: i18next.t("dashboard.titles.scheduledintoday"),
|
||||||
gqlFragment: DashboardScheduledInTodayGql,
|
component: DashboardScheduledInToday,
|
||||||
minW: 6,
|
gqlFragment: DashboardScheduledInTodayGql,
|
||||||
minH: 2,
|
minW: 6,
|
||||||
w: 10,
|
minH: 2,
|
||||||
h: 3,
|
w: 10,
|
||||||
},
|
h: 3,
|
||||||
ScheduleOutToday: {
|
},
|
||||||
label: i18next.t("dashboard.titles.scheduledouttoday"),
|
ScheduleOutToday: {
|
||||||
component: DashboardScheduledOutToday,
|
label: i18next.t("dashboard.titles.scheduledouttoday"),
|
||||||
gqlFragment: DashboardScheduledOutTodayGql,
|
component: DashboardScheduledOutToday,
|
||||||
minW: 6,
|
gqlFragment: DashboardScheduledOutTodayGql,
|
||||||
minH: 2,
|
minW: 6,
|
||||||
w: 10,
|
minH: 2,
|
||||||
h: 3,
|
w: 10,
|
||||||
},
|
h: 3,
|
||||||
|
},
|
||||||
|
JobLifecycle: {
|
||||||
|
label: i18next.t("dashboard.titles.joblifecycle"),
|
||||||
|
component: JobLifecycleDashboardComponent,
|
||||||
|
gqlFragment: JobLifecycleDashboardGQL,
|
||||||
|
minW: 6,
|
||||||
|
minH: 3,
|
||||||
|
w: 6,
|
||||||
|
h: 3,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const createDashboardQuery = (state) => {
|
const createDashboardQuery = (state) => {
|
||||||
const componentBasedAdditions =
|
const componentBasedAdditions =
|
||||||
state &&
|
state &&
|
||||||
Array.isArray(state.layout) &&
|
Array.isArray(state.layout) &&
|
||||||
state.layout
|
state.layout
|
||||||
.map((item, index) => componentList[item.i].gqlFragment || "")
|
.map((item, index) => componentList[item.i].gqlFragment || "")
|
||||||
.join("");
|
.join("");
|
||||||
return gql`
|
return gql`
|
||||||
query QUERY_DASHBOARD_DETAILS { ${componentBasedAdditions || ""}
|
query QUERY_DASHBOARD_DETAILS { ${componentBasedAdditions || ""}
|
||||||
monthly_sales: jobs(where: {_and: [
|
monthly_sales: jobs(where: {_and: [
|
||||||
{ voided: {_eq: false}},
|
{ voided: {_eq: false}},
|
||||||
{date_invoiced: {_gte: "${moment()
|
{date_invoiced: {_gte: "${moment()
|
||||||
.startOf("month")
|
.startOf("month")
|
||||||
.startOf("day")
|
.startOf("day")
|
||||||
.toISOString()}"}}, {date_invoiced: {_lte: "${moment()
|
.toISOString()}"}}, {date_invoiced: {_lte: "${moment()
|
||||||
.endOf("month")
|
.endOf("month")
|
||||||
.endOf("day")
|
.endOf("day")
|
||||||
.toISOString()}"}}]}) {
|
.toISOString()}"}}]}) {
|
||||||
id
|
id
|
||||||
ro_number
|
ro_number
|
||||||
date_invoiced
|
date_invoiced
|
||||||
job_totals
|
job_totals
|
||||||
rate_la1
|
rate_la1
|
||||||
rate_la2
|
rate_la2
|
||||||
rate_la3
|
rate_la3
|
||||||
rate_la4
|
rate_la4
|
||||||
rate_laa
|
rate_laa
|
||||||
rate_lab
|
rate_lab
|
||||||
rate_lad
|
rate_lad
|
||||||
rate_lae
|
rate_lae
|
||||||
rate_laf
|
rate_laf
|
||||||
rate_lag
|
rate_lag
|
||||||
rate_lam
|
rate_lam
|
||||||
rate_lar
|
rate_lar
|
||||||
rate_las
|
rate_las
|
||||||
rate_lau
|
rate_lau
|
||||||
rate_ma2s
|
rate_ma2s
|
||||||
rate_ma2t
|
rate_ma2t
|
||||||
rate_ma3s
|
rate_ma3s
|
||||||
rate_mabl
|
rate_mabl
|
||||||
rate_macs
|
rate_macs
|
||||||
rate_mahw
|
rate_mahw
|
||||||
rate_mapa
|
rate_mapa
|
||||||
rate_mash
|
rate_mash
|
||||||
rate_matd
|
rate_matd
|
||||||
joblines(where: { removed: { _eq: false } }) {
|
joblines(where: { removed: { _eq: false } }) {
|
||||||
id
|
id
|
||||||
mod_lbr_ty
|
mod_lbr_ty
|
||||||
mod_lb_hrs
|
mod_lb_hrs
|
||||||
act_price
|
act_price
|
||||||
part_qty
|
part_qty
|
||||||
part_type
|
part_type
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
production_jobs: jobs(where: { inproduction: { _eq: true } }) {
|
production_jobs: jobs(where: { inproduction: { _eq: true } }) {
|
||||||
id
|
id
|
||||||
ro_number
|
ro_number
|
||||||
ins_co_nm
|
ins_co_nm
|
||||||
job_totals
|
job_totals
|
||||||
joblines(where: { removed: { _eq: false } }) {
|
joblines(where: { removed: { _eq: false } }) {
|
||||||
id
|
id
|
||||||
mod_lbr_ty
|
mod_lbr_ty
|
||||||
mod_lb_hrs
|
mod_lb_hrs
|
||||||
act_price
|
act_price
|
||||||
part_qty
|
part_qty
|
||||||
part_type
|
part_type
|
||||||
}
|
}
|
||||||
labhrs: joblines_aggregate(where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }) {
|
labhrs: joblines_aggregate(where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }) {
|
||||||
aggregate {
|
aggregate {
|
||||||
sum {
|
sum {
|
||||||
mod_lb_hrs
|
mod_lb_hrs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
larhrs: joblines_aggregate(where: { mod_lbr_ty: { _eq: "LAR" }, removed: { _eq: false } }) {
|
larhrs: joblines_aggregate(where: { mod_lbr_ty: { _eq: "LAR" }, removed: { _eq: false } }) {
|
||||||
aggregate {
|
aggregate {
|
||||||
sum {
|
sum {
|
||||||
mod_lb_hrs
|
mod_lb_hrs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`;
|
}`;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ export default function ScheduleEventContainer({ bodyshop, event, refetch }) {
|
|||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: event.job.id,
|
jobid: event.job.id,
|
||||||
operation: AuditTrailMapping.appointmentcancel(lost_sale_reason),
|
operation: AuditTrailMapping.appointmentcancel(lost_sale_reason),
|
||||||
|
type: "appointmentcancel",
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
currentUser: selectCurrentUser,
|
currentUser: selectCurrentUser,
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
insertAuditTrail: ({ jobid, operation }) =>
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
dispatch(insertAuditTrail({ jobid, operation })),
|
dispatch(insertAuditTrail({ jobid, operation, type })),
|
||||||
});
|
});
|
||||||
|
|
||||||
export function JobChecklistForm({
|
export function JobChecklistForm({
|
||||||
@@ -183,6 +183,7 @@ export function JobChecklistForm({
|
|||||||
(type === "intake" && bodyshop.md_ro_statuses.default_arrived) ||
|
(type === "intake" && bodyshop.md_ro_statuses.default_arrived) ||
|
||||||
(type === "deliver" && bodyshop.md_ro_statuses.default_delivered)
|
(type === "deliver" && bodyshop.md_ro_statuses.default_delivered)
|
||||||
),
|
),
|
||||||
|
type: "jobchecklist",
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
notification["error"]({
|
notification["error"]({
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
//currentUser: selectCurrentUser
|
//currentUser: selectCurrentUser
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
insertAuditTrail: ({ jobid, operation }) =>
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
dispatch(insertAuditTrail({ jobid, operation })),
|
dispatch(insertAuditTrail({ jobid, operation, type })),
|
||||||
});
|
});
|
||||||
export default connect(
|
export default connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
@@ -46,6 +46,7 @@ export function JobEmployeeAssignmentsContainer({
|
|||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: job.id,
|
jobid: job.id,
|
||||||
operation: AuditTrailMapping.jobassignmentchange(operation, name),
|
operation: AuditTrailMapping.jobassignmentchange(operation, name),
|
||||||
|
type: "jobassignmentchange",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!!result.errors) {
|
if (!!result.errors) {
|
||||||
@@ -76,6 +77,7 @@ export function JobEmployeeAssignmentsContainer({
|
|||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: job.id,
|
jobid: job.id,
|
||||||
operation: AuditTrailMapping.jobassignmentremoved(operation),
|
operation: AuditTrailMapping.jobassignmentremoved(operation),
|
||||||
|
type: "jobassignmentremoved",
|
||||||
});
|
});
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
//currentUser: selectCurrentUser
|
//currentUser: selectCurrentUser
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
insertAuditTrail: ({ jobid, operation }) =>
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
dispatch(insertAuditTrail({ jobid, operation })),
|
dispatch(insertAuditTrail({ jobid, operation, type })),
|
||||||
});
|
});
|
||||||
export default connect(
|
export default connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
@@ -113,6 +113,7 @@ export function JobLineConvertToLabor({
|
|||||||
hours: calculateAdjustment({ mod_lbr_ty, job, jobline }).toFixed(1),
|
hours: calculateAdjustment({ mod_lbr_ty, job, jobline }).toFixed(1),
|
||||||
mod_lbr_ty,
|
mod_lbr_ty,
|
||||||
}),
|
}),
|
||||||
|
type: "jobmodifylbradj",
|
||||||
});
|
});
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setVisibility(false);
|
setVisibility(false);
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
insertAuditTrail: ({ jobid, operation }) =>
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
dispatch(insertAuditTrail({ jobid, operation })),
|
dispatch(insertAuditTrail({ jobid, operation, type })),
|
||||||
});
|
});
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminStatus);
|
export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminStatus);
|
||||||
|
|
||||||
@@ -32,6 +32,7 @@ export function JobsAdminStatus({ insertAuditTrail, bodyshop, job }) {
|
|||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: job.id,
|
jobid: job.id,
|
||||||
operation: AuditTrailMapping.admin_jobstatuschange(status),
|
operation: AuditTrailMapping.admin_jobstatuschange(status),
|
||||||
|
type: "admin_jobstatuschange",
|
||||||
});
|
});
|
||||||
// refetch();
|
// refetch();
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
insertAuditTrail: ({ jobid, operation }) =>
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
dispatch(insertAuditTrail({ jobid, operation })),
|
dispatch(insertAuditTrail({ jobid, operation, type })),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
@@ -57,6 +57,7 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) {
|
|||||||
? DateTimeFormat(changedAuditFields[key])
|
? DateTimeFormat(changedAuditFields[key])
|
||||||
: changedAuditFields[key]
|
: changedAuditFields[key]
|
||||||
),
|
),
|
||||||
|
type: "admin_jobfieldchange",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
currentUser: selectCurrentUser,
|
currentUser: selectCurrentUser,
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
insertAuditTrail: ({ jobid, operation }) =>
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
dispatch(insertAuditTrail({ jobid, operation })),
|
dispatch(insertAuditTrail({ jobid, operation, type })),
|
||||||
});
|
});
|
||||||
export default connect(
|
export default connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
@@ -59,6 +59,7 @@ export function JobAdminMarkReexport({
|
|||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: job.id,
|
jobid: job.id,
|
||||||
operation: AuditTrailMapping.admin_jobmarkforreexport(),
|
operation: AuditTrailMapping.admin_jobmarkforreexport(),
|
||||||
|
type: "admin_jobmarkforreexport",
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
notification["error"]({
|
notification["error"]({
|
||||||
@@ -99,6 +100,7 @@ export function JobAdminMarkReexport({
|
|||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: job.id,
|
jobid: job.id,
|
||||||
operation: AuditTrailMapping.admin_jobmarkexported(),
|
operation: AuditTrailMapping.admin_jobmarkexported(),
|
||||||
|
type: "admin_jobmarkexported",
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
notification["error"]({
|
notification["error"]({
|
||||||
@@ -124,6 +126,7 @@ export function JobAdminMarkReexport({
|
|||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: job.id,
|
jobid: job.id,
|
||||||
operation: AuditTrailMapping.admin_jobuninvoice(),
|
operation: AuditTrailMapping.admin_jobuninvoice(),
|
||||||
|
type: "admin_jobuninvoice",
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
notification["error"]({
|
notification["error"]({
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
|||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({});
|
const mapStateToProps = createStructuredSelector({});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
insertAuditTrail: ({ jobid, operation }) =>
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
dispatch(insertAuditTrail({ jobid, operation })),
|
dispatch(insertAuditTrail({ jobid, operation, type })),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminRemoveAR);
|
export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminRemoveAR);
|
||||||
@@ -34,6 +34,7 @@ export function JobsAdminRemoveAR({ insertAuditTrail, job }) {
|
|||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: job.id,
|
jobid: job.id,
|
||||||
operation: AuditTrailMapping.admin_job_remove_from_ar(value),
|
operation: AuditTrailMapping.admin_job_remove_from_ar(value),
|
||||||
|
type: "admin_job_remove_from_ar",
|
||||||
});
|
});
|
||||||
setSwitchValue(value);
|
setSwitchValue(value);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
currentUser: selectCurrentUser,
|
currentUser: selectCurrentUser,
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
insertAuditTrail: ({ jobid, operation }) =>
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
dispatch(insertAuditTrail({ jobid, operation })),
|
dispatch(insertAuditTrail({ jobid, operation, type })),
|
||||||
});
|
});
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminUnvoid);
|
export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminUnvoid);
|
||||||
|
|
||||||
@@ -49,6 +49,7 @@ export function JobsAdminUnvoid({
|
|||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: job.id,
|
jobid: job.id,
|
||||||
operation: AuditTrailMapping.admin_jobunvoid(),
|
operation: AuditTrailMapping.admin_jobunvoid(),
|
||||||
|
type: "admin_jobunvoid",
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
notification["error"]({
|
notification["error"]({
|
||||||
|
|||||||
@@ -47,8 +47,8 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
currentUser: selectCurrentUser,
|
currentUser: selectCurrentUser,
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
insertAuditTrail: ({ jobid, operation }) =>
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
dispatch(insertAuditTrail({ jobid, operation })),
|
dispatch(insertAuditTrail({ jobid, operation, type })),
|
||||||
});
|
});
|
||||||
export function JobsAvailableContainer({
|
export function JobsAvailableContainer({
|
||||||
bodyshop,
|
bodyshop,
|
||||||
@@ -190,6 +190,7 @@ export function JobsAvailableContainer({
|
|||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: r.data.insert_jobs.returning[0].id,
|
jobid: r.data.insert_jobs.returning[0].id,
|
||||||
operation: AuditTrailMapping.jobimported(),
|
operation: AuditTrailMapping.jobimported(),
|
||||||
|
type: "jobimported",
|
||||||
});
|
});
|
||||||
|
|
||||||
deleteJob({
|
deleteJob({
|
||||||
@@ -350,6 +351,7 @@ export function JobsAvailableContainer({
|
|||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: selectedJob,
|
jobid: selectedJob,
|
||||||
operation: AuditTrailMapping.jobsupplement(),
|
operation: AuditTrailMapping.jobsupplement(),
|
||||||
|
type: "jobsupplement",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
jobRO: selectJobReadOnly,
|
jobRO: selectJobReadOnly,
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
insertAuditTrail: ({ jobid, operation }) =>
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
dispatch(insertAuditTrail({ jobid, operation })),
|
dispatch(insertAuditTrail({ jobid, operation, type })),
|
||||||
});
|
});
|
||||||
|
|
||||||
export function JobsChangeStatus({ job, bodyshop, jobRO, insertAuditTrail }) {
|
export function JobsChangeStatus({ job, bodyshop, jobRO, insertAuditTrail }) {
|
||||||
@@ -35,6 +35,7 @@ export function JobsChangeStatus({ job, bodyshop, jobRO, insertAuditTrail }) {
|
|||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: job.id,
|
jobid: job.id,
|
||||||
operation: AuditTrailMapping.jobstatuschange(status),
|
operation: AuditTrailMapping.jobstatuschange(status),
|
||||||
|
type: "jobstatuschange",
|
||||||
});
|
});
|
||||||
// refetch();
|
// refetch();
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
insertAuditTrail: ({ jobid, operation }) =>
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
dispatch(insertAuditTrail({ jobid, operation })),
|
dispatch(insertAuditTrail({ jobid, operation, type })),
|
||||||
});
|
});
|
||||||
|
|
||||||
function updateJobCache(items) {
|
function updateJobCache(items) {
|
||||||
@@ -192,6 +192,7 @@ export function JobsCloseExportButton({
|
|||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: jobId,
|
jobid: jobId,
|
||||||
operation: AuditTrailMapping.jobexported(),
|
operation: AuditTrailMapping.jobexported(),
|
||||||
|
type: "jobexported",
|
||||||
});
|
});
|
||||||
updateJobCache(
|
updateJobCache(
|
||||||
jobUpdateResponse.data.update_jobs.returning.map((job) => job.id)
|
jobUpdateResponse.data.update_jobs.returning.map((job) => job.id)
|
||||||
@@ -217,6 +218,7 @@ export function JobsCloseExportButton({
|
|||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: jobId,
|
jobid: jobId,
|
||||||
operation: AuditTrailMapping.jobexported(),
|
operation: AuditTrailMapping.jobexported(),
|
||||||
|
type: "jobexported",
|
||||||
});
|
});
|
||||||
updateJobCache([
|
updateJobCache([
|
||||||
...new Set(
|
...new Set(
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
jobRO: selectJobReadOnly,
|
jobRO: selectJobReadOnly,
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
insertAuditTrail: ({ jobid, operation }) =>
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
dispatch(insertAuditTrail({ jobid, operation })),
|
dispatch(insertAuditTrail({ jobid, operation, type })),
|
||||||
});
|
});
|
||||||
|
|
||||||
export function JobsConvertButton({
|
export function JobsConvertButton({
|
||||||
@@ -78,6 +78,7 @@ export function JobsConvertButton({
|
|||||||
operation: AuditTrailMapping.jobconverted(
|
operation: AuditTrailMapping.jobconverted(
|
||||||
res.data.update_jobs.returning[0].ro_number
|
res.data.update_jobs.returning[0].ro_number
|
||||||
),
|
),
|
||||||
|
type: "jobconverted",
|
||||||
});
|
});
|
||||||
|
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export default function AddToProduction(
|
|||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: jobId,
|
jobid: jobId,
|
||||||
operation: AuditTrailMapping.jobinproductionchange(!remove),
|
operation: AuditTrailMapping.jobinproductionchange(!remove),
|
||||||
|
type: "jobinproductionchange",
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
if (completionCallback) completionCallback();
|
if (completionCallback) completionCallback();
|
||||||
|
|||||||
@@ -52,8 +52,8 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
dispatch(setModalContext({ context: context, modal: "timeTicket" })),
|
dispatch(setModalContext({ context: context, modal: "timeTicket" })),
|
||||||
setCardPaymentContext: (context) =>
|
setCardPaymentContext: (context) =>
|
||||||
dispatch(setModalContext({ context: context, modal: "cardPayment" })),
|
dispatch(setModalContext({ context: context, modal: "cardPayment" })),
|
||||||
insertAuditTrail: ({ jobid, operation }) =>
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
dispatch(insertAuditTrail({ jobid, operation })),
|
dispatch(insertAuditTrail({ jobid, operation, type })),
|
||||||
});
|
});
|
||||||
|
|
||||||
export function JobsDetailHeaderActions({
|
export function JobsDetailHeaderActions({
|
||||||
@@ -115,6 +115,7 @@ export function JobsDetailHeaderActions({
|
|||||||
? !job.production_vars.alert
|
? !job.production_vars.alert
|
||||||
: true
|
: true
|
||||||
),
|
),
|
||||||
|
type: "alertToggle",
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -134,6 +135,7 @@ export function JobsDetailHeaderActions({
|
|||||||
operation: AuditTrailMapping.jobsuspend(
|
operation: AuditTrailMapping.jobsuspend(
|
||||||
!!job.suspended ? !job.suspended : true
|
!!job.suspended ? !job.suspended : true
|
||||||
),
|
),
|
||||||
|
type: "jobsuspend",
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -190,6 +192,7 @@ export function JobsDetailHeaderActions({
|
|||||||
jobid: job.id,
|
jobid: job.id,
|
||||||
operation:
|
operation:
|
||||||
AuditTrailMapping.appointmentcancel(lost_sale_reason),
|
AuditTrailMapping.appointmentcancel(lost_sale_reason),
|
||||||
|
type: "appointmentcancel",
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -549,6 +552,7 @@ export function JobsDetailHeaderActions({
|
|||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: job.id,
|
jobid: job.id,
|
||||||
operation: AuditTrailMapping.jobvoid(),
|
operation: AuditTrailMapping.jobvoid(),
|
||||||
|
type: "jobvoid",
|
||||||
});
|
});
|
||||||
//go back to jobs list.
|
//go back to jobs list.
|
||||||
history.push(`/manage/`);
|
history.push(`/manage/`);
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
insertAuditTrail: ({ jobid, operation }) =>
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
dispatch(insertAuditTrail({ jobid, operation })),
|
dispatch(insertAuditTrail({ jobid, operation, type })),
|
||||||
});
|
});
|
||||||
|
|
||||||
function updateJobCache(items) {
|
function updateJobCache(items) {
|
||||||
@@ -189,6 +189,7 @@ export function JobsExportAllButton({
|
|||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: job.id,
|
jobid: job.id,
|
||||||
operation: AuditTrailMapping.jobexported(),
|
operation: AuditTrailMapping.jobexported(),
|
||||||
|
type: "jobexported",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
updateJobCache(
|
updateJobCache(
|
||||||
@@ -230,6 +231,7 @@ export function JobsExportAllButton({
|
|||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: successfulTransactionsSet[0],
|
jobid: successfulTransactionsSet[0],
|
||||||
operation: AuditTrailMapping.jobexported(),
|
operation: AuditTrailMapping.jobexported(),
|
||||||
|
type: "jobexported",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
updateJobCache(successfulTransactionsSet);
|
updateJobCache(successfulTransactionsSet);
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import useLocalStorage from "../../utils/useLocalStorage";
|
|||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
|
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
|
||||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||||
|
import { OwnerNameDisplayFunction } from "./../owner-name-display/owner-name-display.component";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -145,7 +146,8 @@ export function JobsList({ bodyshop }) {
|
|||||||
key: "owner",
|
key: "owner",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
responsive: ["md"],
|
responsive: ["md"],
|
||||||
sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
|
sorter: (a, b) =>
|
||||||
|
alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)),
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
|
||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
@@ -188,7 +190,8 @@ export function JobsList({ bodyshop }) {
|
|||||||
dataIndex: "status",
|
dataIndex: "status",
|
||||||
key: "status",
|
key: "status",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
sorter: (a, b) => alphaSort(a.status, b.status),
|
sorter: (a, b) =>
|
||||||
|
statusSort(a.status, b.status, bodyshop.md_ro_statuses.active_statuses),
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
||||||
filteredValue: filter?.status || null,
|
filteredValue: filter?.status || null,
|
||||||
@@ -219,6 +222,15 @@ export function JobsList({ bodyshop }) {
|
|||||||
dataIndex: "vehicle",
|
dataIndex: "vehicle",
|
||||||
key: "vehicle",
|
key: "vehicle",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
|
sorter: (a, b) =>
|
||||||
|
alphaSort(
|
||||||
|
`${a.v_model_yr || ""} ${a.v_make_desc || ""} ${
|
||||||
|
a.v_model_desc || ""
|
||||||
|
}`,
|
||||||
|
`${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}`
|
||||||
|
),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order,
|
||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
return record.vehicleid ? (
|
return record.vehicleid ? (
|
||||||
<Link
|
<Link
|
||||||
@@ -266,6 +278,9 @@ export function JobsList({ bodyshop }) {
|
|||||||
dataIndex: "ins_co_nm",
|
dataIndex: "ins_co_nm",
|
||||||
key: "ins_co_nm",
|
key: "ins_co_nm",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
|
sorter: (a, b) => alphaSort(a.ins_co_nm, b.ins_co_nm),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "ins_co_nm" && state.sortedInfo.order,
|
||||||
filteredValue: filter?.ins_co_nm || null,
|
filteredValue: filter?.ins_co_nm || null,
|
||||||
filters:
|
filters:
|
||||||
(jobs &&
|
(jobs &&
|
||||||
@@ -302,6 +317,13 @@ export function JobsList({ bodyshop }) {
|
|||||||
key: "estimator",
|
key: "estimator",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
responsive: ["xl"],
|
responsive: ["xl"],
|
||||||
|
sorter: (a, b) =>
|
||||||
|
alphaSort(
|
||||||
|
`${a.est_ct_fn || ""} ${a.est_ct_ln || ""}`.trim(),
|
||||||
|
`${b.est_ct_fn || ""} ${b.est_ct_ln || ""}`.trim()
|
||||||
|
),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "estimator" && state.sortedInfo.order,
|
||||||
filterSearch: true,
|
filterSearch: true,
|
||||||
filteredValue: filter?.estimator || null,
|
filteredValue: filter?.estimator || null,
|
||||||
filters:
|
filters:
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
//currentUser: selectCurrentUser
|
//currentUser: selectCurrentUser
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
insertAuditTrail: ({ jobid, operation }) =>
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
dispatch(insertAuditTrail({ jobid, operation })),
|
dispatch(insertAuditTrail({ jobid, operation, type })),
|
||||||
});
|
});
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(JobNotesContainer);
|
export default connect(mapStateToProps, mapDispatchToProps)(JobNotesContainer);
|
||||||
|
|
||||||
@@ -49,6 +49,7 @@ export function JobNotesContainer({ jobId, insertAuditTrail }) {
|
|||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: jobId,
|
jobid: jobId,
|
||||||
operation: AuditTrailMapping.jobnotedeleted(),
|
operation: AuditTrailMapping.jobnotedeleted(),
|
||||||
|
type: "jobnotedeleted",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
setDeleteLoading(false);
|
setDeleteLoading(false);
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
//currentUser: selectCurrentUser
|
//currentUser: selectCurrentUser
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
insertAuditTrail: ({ jobid, operation }) =>
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
dispatch(insertAuditTrail({ jobid, operation })),
|
dispatch(insertAuditTrail({ jobid, operation, type })),
|
||||||
});
|
});
|
||||||
export default connect(
|
export default connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
@@ -76,6 +76,7 @@ export function LaborAllocationsAdjustmentEdit({
|
|||||||
values.hours -
|
values.hours -
|
||||||
((adjustments && adjustments[mod_lbr_ty]) || 0).toFixed(1),
|
((adjustments && adjustments[mod_lbr_ty]) || 0).toFixed(1),
|
||||||
}),
|
}),
|
||||||
|
type: "jobmodifylbradj",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
toggleModalVisible: () => dispatch(toggleModalVisible("noteUpsert")),
|
toggleModalVisible: () => dispatch(toggleModalVisible("noteUpsert")),
|
||||||
insertAuditTrail: ({ jobid, operation }) =>
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
dispatch(insertAuditTrail({ jobid, operation })),
|
dispatch(insertAuditTrail({ jobid, operation, type })),
|
||||||
});
|
});
|
||||||
|
|
||||||
export function NoteUpsertModalContainer({
|
export function NoteUpsertModalContainer({
|
||||||
@@ -70,6 +70,7 @@ export function NoteUpsertModalContainer({
|
|||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: context.jobId,
|
jobid: context.jobId,
|
||||||
operation: AuditTrailMapping.jobnoteupdated(),
|
operation: AuditTrailMapping.jobnoteupdated(),
|
||||||
|
type: "jobnoteupdated",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
if (refetch) refetch();
|
if (refetch) refetch();
|
||||||
@@ -102,6 +103,7 @@ export function NoteUpsertModalContainer({
|
|||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: newJobId,
|
jobid: newJobId,
|
||||||
operation: AuditTrailMapping.jobnoteadded(),
|
operation: AuditTrailMapping.jobnoteadded(),
|
||||||
|
type: "jobnoteadded",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -115,6 +117,7 @@ export function NoteUpsertModalContainer({
|
|||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: context.jobId,
|
jobid: context.jobId,
|
||||||
operation: AuditTrailMapping.jobnoteadded(),
|
operation: AuditTrailMapping.jobnoteadded(),
|
||||||
|
type: "jobnoteadded",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
import { useMutation, useQuery, useApolloClient } from "@apollo/client";
|
import { useApolloClient, useMutation, useQuery } from "@apollo/client";
|
||||||
|
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||||
import { Form, Modal, notification } from "antd";
|
import { Form, Modal, notification } from "antd";
|
||||||
|
import axios from "axios";
|
||||||
|
import _ from "lodash";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { logImEXEvent, auth } from "../../firebase/firebase.utils";
|
import { auth, logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
import { UPDATE_JOB_LINE_STATUS } from "../../graphql/jobs-lines.queries";
|
import { UPDATE_JOB_LINE_STATUS } from "../../graphql/jobs-lines.queries";
|
||||||
|
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||||
import {
|
import {
|
||||||
INSERT_NEW_PARTS_ORDERS,
|
INSERT_NEW_PARTS_ORDERS,
|
||||||
QUERY_PARTS_ORDER_OEC,
|
QUERY_PARTS_ORDER_OEC,
|
||||||
@@ -29,10 +33,6 @@ import { TemplateList } from "../../utils/TemplateConstants";
|
|||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||||
import PartsOrderModalComponent from "./parts-order-modal.component";
|
import PartsOrderModalComponent from "./parts-order-modal.component";
|
||||||
import axios from "axios";
|
|
||||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
|
||||||
import _ from "lodash";
|
|
||||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
currentUser: selectCurrentUser,
|
currentUser: selectCurrentUser,
|
||||||
@@ -45,8 +45,8 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
toggleModalVisible: () => dispatch(toggleModalVisible("partsOrder")),
|
toggleModalVisible: () => dispatch(toggleModalVisible("partsOrder")),
|
||||||
setBillEnterContext: (context) =>
|
setBillEnterContext: (context) =>
|
||||||
dispatch(setModalContext({ context: context, modal: "billEnter" })),
|
dispatch(setModalContext({ context: context, modal: "billEnter" })),
|
||||||
insertAuditTrail: ({ jobid, operation }) =>
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
dispatch(insertAuditTrail({ jobid, operation })),
|
dispatch(insertAuditTrail({ jobid, operation, type })),
|
||||||
});
|
});
|
||||||
|
|
||||||
export function PartsOrderModalContainer({
|
export function PartsOrderModalContainer({
|
||||||
@@ -142,6 +142,7 @@ export function PartsOrderModalContainer({
|
|||||||
: AuditTrailMapping.jobspartsorder(
|
: AuditTrailMapping.jobspartsorder(
|
||||||
insertResult.data.insert_parts_orders.returning[0].order_number
|
insertResult.data.insert_parts_orders.returning[0].order_number
|
||||||
),
|
),
|
||||||
|
type: isReturn ? "jobspartsreturn" : "jobspartsorder",
|
||||||
});
|
});
|
||||||
|
|
||||||
const jobLinesResult = await updateJobLines({
|
const jobLinesResult = await updateJobLines({
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import { Button, Form, Input, PageHeader, Space } from "antd";
|
import { Button, Form, Input, PageHeader, Space } from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import {
|
||||||
|
selectAuthLevel,
|
||||||
|
selectBodyshop,
|
||||||
|
} from "../../redux/user/user.selectors";
|
||||||
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
||||||
import FormItemEmail from "../form-items-formatted/email-form-item.component";
|
import FormItemEmail from "../form-items-formatted/email-form-item.component";
|
||||||
import PhoneFormItem, {
|
import PhoneFormItem, {
|
||||||
@@ -8,12 +14,6 @@ import PhoneFormItem, {
|
|||||||
} from "../form-items-formatted/phone-form-item.component";
|
} from "../form-items-formatted/phone-form-item.component";
|
||||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
|
import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import {
|
|
||||||
selectAuthLevel,
|
|
||||||
selectBodyshop,
|
|
||||||
} from "../../redux/user/user.selectors";
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
authLevel: selectAuthLevel,
|
authLevel: selectAuthLevel,
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -46,13 +46,19 @@ export function PhonebookFormComponent({
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title={`${form.getFieldValue("firstname") || ""} ${
|
title={
|
||||||
form.getFieldValue("lastname") || ""
|
<Form.Item shouldUpdate>
|
||||||
}${
|
{() =>
|
||||||
form.getFieldValue("company")
|
`${form.getFieldValue("firstname") || ""} ${
|
||||||
? ` - ${form.getFieldValue("company")}`
|
form.getFieldValue("lastname") || ""
|
||||||
: ""
|
}${
|
||||||
}`}
|
form.getFieldValue("company")
|
||||||
|
? ` - ${form.getFieldValue("company")}`
|
||||||
|
: ""
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
</Form.Item>
|
||||||
|
}
|
||||||
extra={
|
extra={
|
||||||
<Space>
|
<Space>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
insertAuditTrail: ({ jobid, operation }) =>
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
dispatch(insertAuditTrail({ jobid, operation })),
|
dispatch(insertAuditTrail({ jobid, operation, type })),
|
||||||
});
|
});
|
||||||
|
|
||||||
export function ProductionBoardKanbanComponent({
|
export function ProductionBoardKanbanComponent({
|
||||||
@@ -133,6 +133,7 @@ export function ProductionBoardKanbanComponent({
|
|||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: card.id,
|
jobid: card.id,
|
||||||
operation: AuditTrailMapping.jobstatuschange(destination.toColumnId),
|
operation: AuditTrailMapping.jobstatuschange(destination.toColumnId),
|
||||||
|
type: "jobstatuschange",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (update.errors) {
|
if (update.errors) {
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
|||||||
const mapStateToProps = createStructuredSelector({});
|
const mapStateToProps = createStructuredSelector({});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
insertAuditTrail: ({ jobid, operation }) =>
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
dispatch(insertAuditTrail({ jobid, operation })),
|
dispatch(insertAuditTrail({ jobid, operation, type })),
|
||||||
});
|
});
|
||||||
|
|
||||||
export function ProductionListColumnAlert({ record, insertAuditTrail }) {
|
export function ProductionListColumnAlert({ record, insertAuditTrail }) {
|
||||||
@@ -46,6 +46,7 @@ export function ProductionListColumnAlert({ record, insertAuditTrail }) {
|
|||||||
? !record.production_vars.alert
|
? !record.production_vars.alert
|
||||||
: true
|
: true
|
||||||
),
|
),
|
||||||
|
type: "alertToggle",
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
if (record.refetch) record.refetch();
|
if (record.refetch) record.refetch();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { BranchesOutlined, PauseCircleOutlined } from "@ant-design/icons";
|
import { BranchesOutlined, PauseCircleOutlined } from "@ant-design/icons";
|
||||||
import { Space, Tooltip } from "antd";
|
import { Checkbox, Space, Tooltip } from "antd";
|
||||||
import i18n from "i18next";
|
import i18n from "i18next";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
@@ -10,7 +10,9 @@ import { onlyUnique } from "../../utils/arrayHelper";
|
|||||||
import { alphaSort, dateSort, statusSort } from "../../utils/sorters";
|
import { alphaSort, dateSort, statusSort } from "../../utils/sorters";
|
||||||
import JobAltTransportChange from "../job-at-change/job-at-change.component";
|
import JobAltTransportChange from "../job-at-change/job-at-change.component";
|
||||||
import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component";
|
import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component";
|
||||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
import OwnerNameDisplay, {
|
||||||
|
OwnerNameDisplayFunction,
|
||||||
|
} from "../owner-name-display/owner-name-display.component";
|
||||||
import ProductionSubletsManageComponent from "../production-sublets-manage/production-sublets-manage.component";
|
import ProductionSubletsManageComponent from "../production-sublets-manage/production-sublets-manage.component";
|
||||||
import ProductionListColumnAlert from "./production-list-columns.alert.component";
|
import ProductionListColumnAlert from "./production-list-columns.alert.component";
|
||||||
import ProductionListColumnBodyPriority from "./production-list-columns.bodypriority.component";
|
import ProductionListColumnBodyPriority from "./production-list-columns.bodypriority.component";
|
||||||
@@ -84,7 +86,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
|
|||||||
<OwnerNameDisplay ownerObject={record} />
|
<OwnerNameDisplay ownerObject={record} />
|
||||||
</Link>
|
</Link>
|
||||||
),
|
),
|
||||||
sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
|
sorter: (a, b) =>
|
||||||
|
alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)),
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "ownr" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "ownr" && state.sortedInfo.order,
|
||||||
},
|
},
|
||||||
@@ -95,8 +98,10 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
|
|||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
sorter: (a, b) =>
|
sorter: (a, b) =>
|
||||||
alphaSort(
|
alphaSort(
|
||||||
a.v_make_desc + a.v_model_desc,
|
`${a.v_model_yr || ""} ${a.v_make_desc || ""} ${
|
||||||
b.v_make_desc + b.v_model_desc
|
a.v_model_desc || ""
|
||||||
|
}`,
|
||||||
|
`${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}`
|
||||||
),
|
),
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order,
|
||||||
@@ -291,6 +296,20 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
|
|||||||
dataIndex: "special_coverage_policy",
|
dataIndex: "special_coverage_policy",
|
||||||
key: "special_coverage_policy",
|
key: "special_coverage_policy",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
|
sorter: (a, b) =>
|
||||||
|
Number(a.special_coverage_policy) - Number(b.special_coverage_policy),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "special_coverage_policy" &&
|
||||||
|
state.sortedInfo.order,
|
||||||
|
filters: [
|
||||||
|
{ text: "True", value: true },
|
||||||
|
{ text: "False", value: false },
|
||||||
|
],
|
||||||
|
onFilter: (value, record) =>
|
||||||
|
value.includes(record.special_coverage_policy),
|
||||||
|
render: (text, record) => (
|
||||||
|
<Checkbox disabled checked={record.special_coverage_policy} />
|
||||||
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -302,6 +321,16 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
|
|||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "alt_transport" &&
|
state.sortedInfo.columnKey === "alt_transport" &&
|
||||||
state.sortedInfo.order,
|
state.sortedInfo.order,
|
||||||
|
filters:
|
||||||
|
(bodyshop &&
|
||||||
|
bodyshop.appt_alt_transport.map((s) => {
|
||||||
|
return {
|
||||||
|
text: s,
|
||||||
|
value: [s],
|
||||||
|
};
|
||||||
|
})) ||
|
||||||
|
[],
|
||||||
|
onFilter: (value, record) => value.includes(record.alt_transport),
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<div>
|
<div>
|
||||||
{record.alt_transport}
|
{record.alt_transport}
|
||||||
@@ -382,7 +411,11 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
|
|||||||
title: i18n.t("production.labels.alert"),
|
title: i18n.t("production.labels.alert"),
|
||||||
dataIndex: "alert",
|
dataIndex: "alert",
|
||||||
key: "alert",
|
key: "alert",
|
||||||
|
sorter: (a, b) =>
|
||||||
|
Number(a.production_vars?.alert || false) -
|
||||||
|
Number(b.production_vars?.alert || false),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "alert" && state.sortedInfo.order,
|
||||||
render: (text, record) => <ProductionListColumnAlert record={record} />,
|
render: (text, record) => <ProductionListColumnAlert record={record} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ import { useMutation } from "@apollo/client";
|
|||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Col,
|
Col,
|
||||||
notification,
|
|
||||||
Popover,
|
Popover,
|
||||||
Row,
|
Row,
|
||||||
Select,
|
Select,
|
||||||
Space,
|
Space,
|
||||||
Spin,
|
Spin,
|
||||||
|
notification,
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -25,8 +25,8 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
insertAuditTrail: ({ jobid, operation }) =>
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
dispatch(insertAuditTrail({ jobid, operation })),
|
dispatch(insertAuditTrail({ jobid, operation, type })),
|
||||||
});
|
});
|
||||||
|
|
||||||
export function ProductionListEmpAssignment({
|
export function ProductionListEmpAssignment({
|
||||||
@@ -55,6 +55,7 @@ export function ProductionListEmpAssignment({
|
|||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: record.id,
|
jobid: record.id,
|
||||||
operation: AuditTrailMapping.jobassignmentchange(empAssignment, name),
|
operation: AuditTrailMapping.jobassignmentchange(empAssignment, name),
|
||||||
|
type: "jobassignmentchange",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!!result.errors) {
|
if (!!result.errors) {
|
||||||
@@ -80,6 +81,7 @@ export function ProductionListEmpAssignment({
|
|||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: record.id,
|
jobid: record.id,
|
||||||
operation: AuditTrailMapping.jobassignmentremoved(empAssignment),
|
operation: AuditTrailMapping.jobassignmentremoved(empAssignment),
|
||||||
|
type: "jobassignmentremoved",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!!result.errors) {
|
if (!!result.errors) {
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
insertAuditTrail: ({ jobid, operation }) =>
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
dispatch(insertAuditTrail({ jobid, operation })),
|
dispatch(insertAuditTrail({ jobid, operation, type })),
|
||||||
});
|
});
|
||||||
export function ProductionListColumnCategory({ record, bodyshop }) {
|
export function ProductionListColumnCategory({ record, bodyshop }) {
|
||||||
const [updateJob] = useMutation(UPDATE_JOB);
|
const [updateJob] = useMutation(UPDATE_JOB);
|
||||||
|
|||||||
@@ -5,16 +5,16 @@ import { connect } from "react-redux";
|
|||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
|
||||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||||
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
insertAuditTrail: ({ jobid, operation }) =>
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
dispatch(insertAuditTrail({ jobid, operation })),
|
dispatch(insertAuditTrail({ jobid, operation, type })),
|
||||||
});
|
});
|
||||||
export function ProductionListColumnStatus({
|
export function ProductionListColumnStatus({
|
||||||
record,
|
record,
|
||||||
@@ -40,6 +40,7 @@ export function ProductionListColumnStatus({
|
|||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: record.id,
|
jobid: record.id,
|
||||||
operation: AuditTrailMapping.jobstatuschange(key),
|
operation: AuditTrailMapping.jobstatuschange(key),
|
||||||
|
type: "jobstatuschange",
|
||||||
});
|
});
|
||||||
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|||||||
@@ -142,7 +142,8 @@ function FiltersSection({filters, form, bodyshop}) {
|
|||||||
return generateInternalReflections({
|
return generateInternalReflections({
|
||||||
bodyshop,
|
bodyshop,
|
||||||
upperPath,
|
upperPath,
|
||||||
finalPath
|
finalPath,
|
||||||
|
t
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ const generateOptionsFromArray = (bodyshop, path) => {
|
|||||||
})), 'value');
|
})), 'value');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Valid internal reflections
|
* Valid internal reflections
|
||||||
* Note: This is intended for future functionality
|
* Note: This is intended for future functionality
|
||||||
@@ -57,10 +56,34 @@ const generateOptionsFromObject = (bodyshop, path, labelPath, valuePath) => {
|
|||||||
* Generate special reflections
|
* Generate special reflections
|
||||||
* @param bodyshop
|
* @param bodyshop
|
||||||
* @param finalPath
|
* @param finalPath
|
||||||
|
* @param t - i18n
|
||||||
* @returns {{label: *, value: *}[]|{label: *, value: *}[]|{label: string, value: *}[]|*[]}
|
* @returns {{label: *, value: *}[]|{label: *, value: *}[]|{label: string, value: *}[]|*[]}
|
||||||
*/
|
*/
|
||||||
const generateSpecialReflections = (bodyshop, finalPath) => {
|
const generateSpecialReflections = (bodyshop, finalPath, t) => {
|
||||||
switch (finalPath) {
|
switch (finalPath) {
|
||||||
|
case 'payment_payers':
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: t("payments.labels.customer"),
|
||||||
|
value: t("payments.labels.customer"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t("payments.labels.insurance"),
|
||||||
|
value: t("payments.labels.insurance"),
|
||||||
|
},
|
||||||
|
// This is a weird one supposedly only used by one shop and could potentially be
|
||||||
|
// placed behind a SplitSDK
|
||||||
|
{
|
||||||
|
label: t("payments.labels.external"),
|
||||||
|
value: t("payments.labels.external"),
|
||||||
|
}
|
||||||
|
];
|
||||||
|
case 'payment_types':
|
||||||
|
return generateOptionsFromArray(bodyshop, 'md_payment_types');
|
||||||
|
case 'alt_transports':
|
||||||
|
return generateOptionsFromArray(bodyshop, 'appt_alt_transport');
|
||||||
|
case 'lost_sale_reasons':
|
||||||
|
return generateOptionsFromArray(bodyshop, 'md_lost_sale_reasons');
|
||||||
// Special case because Referral Sources is an Array, not an Object.
|
// Special case because Referral Sources is an Array, not an Object.
|
||||||
case 'referral_source':
|
case 'referral_source':
|
||||||
return generateOptionsFromArray(bodyshop, 'md_referral_sources');
|
return generateOptionsFromArray(bodyshop, 'md_referral_sources');
|
||||||
@@ -121,12 +144,13 @@ const generateBodyshopReflections = (bodyshop, finalPath) => {
|
|||||||
* @param bodyshop
|
* @param bodyshop
|
||||||
* @param upperPath
|
* @param upperPath
|
||||||
* @param finalPath
|
* @param finalPath
|
||||||
|
* @param t - i18n
|
||||||
* @returns {{label: *, value: *}[]|[]|{label: *, value: *}[]|{label: string, value: *}[]|{label: *, value: *}[]|*[]}
|
* @returns {{label: *, value: *}[]|[]|{label: *, value: *}[]|{label: string, value: *}[]|{label: *, value: *}[]|*[]}
|
||||||
*/
|
*/
|
||||||
const generateInternalReflections = ({bodyshop, upperPath, finalPath}) => {
|
const generateInternalReflections = ({bodyshop, upperPath, finalPath, t}) => {
|
||||||
switch (upperPath) {
|
switch (upperPath) {
|
||||||
case 'special':
|
case 'special':
|
||||||
return generateSpecialReflections(bodyshop, finalPath);
|
return generateSpecialReflections(bodyshop, finalPath, t);
|
||||||
case 'bodyshop':
|
case 'bodyshop':
|
||||||
return generateBodyshopReflections(bodyshop, finalPath);
|
return generateBodyshopReflections(bodyshop, finalPath);
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
toggleModalVisible: () => dispatch(toggleModalVisible("schedule")),
|
toggleModalVisible: () => dispatch(toggleModalVisible("schedule")),
|
||||||
setEmailOptions: (e) => dispatch(setEmailOptions(e)),
|
setEmailOptions: (e) => dispatch(setEmailOptions(e)),
|
||||||
insertAuditTrail: ({ jobid, operation }) =>
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
dispatch(insertAuditTrail({ jobid, operation })),
|
dispatch(insertAuditTrail({ jobid, operation, type })),
|
||||||
});
|
});
|
||||||
|
|
||||||
export function ScheduleJobModalContainer({
|
export function ScheduleJobModalContainer({
|
||||||
@@ -146,6 +146,7 @@ export function ScheduleJobModalContainer({
|
|||||||
operation: AuditTrailMapping.appointmentinsert(
|
operation: AuditTrailMapping.appointmentinsert(
|
||||||
DateTimeFormat(values.start)
|
DateTimeFormat(values.start)
|
||||||
),
|
),
|
||||||
|
type: "appointmentinsert",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export const INSERT_AUDIT_TRAIL = gql`
|
|||||||
bodyshopid
|
bodyshopid
|
||||||
created
|
created
|
||||||
operation
|
operation
|
||||||
|
type
|
||||||
useremail
|
useremail
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1862,6 +1862,7 @@ export const QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED = gql`
|
|||||||
ownr_co_nm
|
ownr_co_nm
|
||||||
ownr_ph1
|
ownr_ph1
|
||||||
ownr_ph2
|
ownr_ph2
|
||||||
|
ownerid
|
||||||
plate_no
|
plate_no
|
||||||
plate_st
|
plate_st
|
||||||
v_vin
|
v_vin
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
|
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
|
||||||
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
|
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
|
||||||
insertAuditTrail: ({ jobid, operation }) =>
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
dispatch(insertAuditTrail({ jobid, operation })),
|
dispatch(insertAuditTrail({ jobid, operation, type })),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(DmsContainer);
|
export default connect(mapStateToProps, mapDispatchToProps)(DmsContainer);
|
||||||
@@ -127,6 +127,7 @@ export function DmsContainer({
|
|||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: payload,
|
jobid: payload,
|
||||||
operation: AuditTrailMapping.jobexported(),
|
operation: AuditTrailMapping.jobexported(),
|
||||||
|
type: "jobexported",
|
||||||
});
|
});
|
||||||
history.push("/manage/accounting/receivables");
|
history.push("/manage/accounting/receivables");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ import AlertComponent from "../../components/alert/alert.component";
|
|||||||
import { QUERY_EXPORT_LOG_PAGINATED } from "../../graphql/accounting.queries";
|
import { QUERY_EXPORT_LOG_PAGINATED } from "../../graphql/accounting.queries";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||||
import {pageLimit} from "../../utils/config";
|
import { pageLimit } from "../../utils/config";
|
||||||
|
import { alphaSort, dateSort } from "./../../utils/sorters";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -34,11 +35,43 @@ export function ExportLogsPageComponent({ bodyshop }) {
|
|||||||
limit: pageLimit,
|
limit: pageLimit,
|
||||||
order: [
|
order: [
|
||||||
{
|
{
|
||||||
[sortcolumn || "created_at"]: sortorder
|
...(sortcolumn === "ro_number"
|
||||||
? sortorder === "descend"
|
? {
|
||||||
? "desc"
|
job: {
|
||||||
: "asc"
|
[sortcolumn || "created_at"]: sortorder
|
||||||
: "desc",
|
? sortorder === "descend"
|
||||||
|
? "desc"
|
||||||
|
: "asc"
|
||||||
|
: "desc",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: sortcolumn === "invoice_number"
|
||||||
|
? {
|
||||||
|
bill: {
|
||||||
|
[sortcolumn || "created_at"]: sortorder
|
||||||
|
? sortorder === "descend"
|
||||||
|
? "desc"
|
||||||
|
: "asc"
|
||||||
|
: "desc",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: sortcolumn === "paymentnum"
|
||||||
|
? {
|
||||||
|
payment: {
|
||||||
|
[sortcolumn || "created_at"]: sortorder
|
||||||
|
? sortorder === "descend"
|
||||||
|
? "desc"
|
||||||
|
: "asc"
|
||||||
|
: "desc",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
[sortcolumn || "created_at"]: sortorder
|
||||||
|
? sortorder === "descend"
|
||||||
|
? "desc"
|
||||||
|
: "asc"
|
||||||
|
: "desc",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -68,6 +101,8 @@ export function ExportLogsPageComponent({ bodyshop }) {
|
|||||||
title: t("general.labels.created_at"),
|
title: t("general.labels.created_at"),
|
||||||
dataIndex: "created_at",
|
dataIndex: "created_at",
|
||||||
key: "created_at",
|
key: "created_at",
|
||||||
|
sorter: (a, b) => dateSort(a.created_at, b.created_at),
|
||||||
|
sortOrder: sortcolumn === "created_at" && sortorder,
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<DateTimeFormatter>{record.created_at}</DateTimeFormatter>
|
<DateTimeFormatter>{record.created_at}</DateTimeFormatter>
|
||||||
),
|
),
|
||||||
@@ -81,7 +116,8 @@ export function ExportLogsPageComponent({ bodyshop }) {
|
|||||||
title: t("jobs.fields.ro_number"),
|
title: t("jobs.fields.ro_number"),
|
||||||
dataIndex: "ro_number",
|
dataIndex: "ro_number",
|
||||||
key: "ro_number",
|
key: "ro_number",
|
||||||
|
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
|
||||||
|
sortOrder: sortcolumn === "ro_number" && sortorder,
|
||||||
render: (text, record) =>
|
render: (text, record) =>
|
||||||
record.job && (
|
record.job && (
|
||||||
<Link to={`/manage/jobs/${record.job.id}`}>
|
<Link to={`/manage/jobs/${record.job.id}`}>
|
||||||
@@ -93,6 +129,8 @@ export function ExportLogsPageComponent({ bodyshop }) {
|
|||||||
title: t("bills.fields.invoice_number"),
|
title: t("bills.fields.invoice_number"),
|
||||||
dataIndex: "invoice_number",
|
dataIndex: "invoice_number",
|
||||||
key: "invoice_number",
|
key: "invoice_number",
|
||||||
|
sorter: (a, b) => alphaSort(a.invoice_number, b.invoice_number),
|
||||||
|
sortOrder: sortcolumn === "invoice_number" && sortorder,
|
||||||
render: (text, record) =>
|
render: (text, record) =>
|
||||||
record.bill && (
|
record.bill && (
|
||||||
<Link to={"/manage/bills?billid=" + (record.bill && record.bill.id)}>
|
<Link to={"/manage/bills?billid=" + (record.bill && record.bill.id)}>
|
||||||
@@ -104,6 +142,8 @@ export function ExportLogsPageComponent({ bodyshop }) {
|
|||||||
title: t("payments.fields.paymentnum"),
|
title: t("payments.fields.paymentnum"),
|
||||||
dataIndex: "paymentnum",
|
dataIndex: "paymentnum",
|
||||||
key: "paymentnum",
|
key: "paymentnum",
|
||||||
|
sorter: (a, b) => alphaSort(a.paymentnum, b.paymentnum),
|
||||||
|
sortOrder: sortcolumn === "paymentnum" && sortorder,
|
||||||
render: (text, record) =>
|
render: (text, record) =>
|
||||||
record.payment && (
|
record.payment && (
|
||||||
<Link
|
<Link
|
||||||
@@ -120,6 +160,13 @@ export function ExportLogsPageComponent({ bodyshop }) {
|
|||||||
title: t("general.labels.successful"),
|
title: t("general.labels.successful"),
|
||||||
dataIndex: "successful",
|
dataIndex: "successful",
|
||||||
key: "successful",
|
key: "successful",
|
||||||
|
sorter: (a, b) => Number(a.successful) - Number(b.successful),
|
||||||
|
sortOrder: sortcolumn === "successful" && sortorder,
|
||||||
|
filters: [
|
||||||
|
{ text: "True", value: true },
|
||||||
|
{ text: "False", value: false },
|
||||||
|
],
|
||||||
|
onFilter: (value, record) => record.successful === value,
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<Checkbox disabled checked={record.successful} />
|
<Checkbox disabled checked={record.successful} />
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -47,11 +47,11 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
insertAuditTrail: ({ jobid, operation }) =>
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
dispatch(insertAuditTrail({ jobid, operation })),
|
dispatch(insertAuditTrail({ jobid, operation, type })),
|
||||||
});
|
});
|
||||||
|
|
||||||
export function JobsCloseComponent({ job, bodyshop, jobRO, insertAuditTrail, }) {
|
export function JobsCloseComponent({ job, bodyshop, jobRO, insertAuditTrail }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
@@ -121,6 +121,7 @@ export function JobsCloseComponent({ job, bodyshop, jobRO, insertAuditTrail, })
|
|||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: job.id,
|
jobid: job.id,
|
||||||
operation: AuditTrailMapping.jobinvoiced(),
|
operation: AuditTrailMapping.jobinvoiced(),
|
||||||
|
type: "jobinvoiced",
|
||||||
});
|
});
|
||||||
// history.push(`/manage/jobs/${job.id}`);
|
// history.push(`/manage/jobs/${job.id}`);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import { createStructuredSelector } from "reselect";
|
|||||||
import FormFieldsChanged from "../../components/form-fields-changed-alert/form-fields-changed-alert.component";
|
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 JobAuditTrail from "../../components/job-audit-trail/job-audit-trail.component";
|
||||||
import JobsLinesContainer from "../../components/job-detail-lines/job-lines.container";
|
import JobsLinesContainer from "../../components/job-detail-lines/job-lines.container";
|
||||||
|
import JobLifecycleComponent from "../../components/job-lifecycle/job-lifecycle.component";
|
||||||
import JobLineUpsertModalContainer from "../../components/job-lines-upsert-modal/job-lines-upsert-modal.container";
|
import JobLineUpsertModalContainer from "../../components/job-lines-upsert-modal/job-lines-upsert-modal.container";
|
||||||
import JobReconciliationModal from "../../components/job-reconciliation-modal/job-reconciliation.modal.container";
|
import JobReconciliationModal from "../../components/job-reconciliation-modal/job-reconciliation.modal.container";
|
||||||
import JobSyncButton from "../../components/job-sync-button/job-sync-button.component";
|
import JobSyncButton from "../../components/job-sync-button/job-sync-button.component";
|
||||||
@@ -54,7 +55,6 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
|
|||||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
import UndefinedToNull from "../../utils/undefinedtonull";
|
import UndefinedToNull from "../../utils/undefinedtonull";
|
||||||
import { DateTimeFormat } from "./../../utils/DateFormatter";
|
import { DateTimeFormat } from "./../../utils/DateFormatter";
|
||||||
import JobLifecycleComponent from "../../components/job-lifecycle/job-lifecycle.component";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -63,8 +63,8 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
setPrintCenterContext: (context) =>
|
setPrintCenterContext: (context) =>
|
||||||
dispatch(setModalContext({ context: context, modal: "printCenter" })),
|
dispatch(setModalContext({ context: context, modal: "printCenter" })),
|
||||||
insertAuditTrail: ({ jobid, operation }) =>
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
dispatch(insertAuditTrail({ jobid, operation })),
|
dispatch(insertAuditTrail({ jobid, operation, type })),
|
||||||
});
|
});
|
||||||
export function JobsDetailPage({
|
export function JobsDetailPage({
|
||||||
bodyshop,
|
bodyshop,
|
||||||
@@ -177,6 +177,7 @@ export function JobsDetailPage({
|
|||||||
? DateTimeFormat(changedAuditFields[key])
|
? DateTimeFormat(changedAuditFields[key])
|
||||||
: changedAuditFields[key]
|
: changedAuditFields[key]
|
||||||
),
|
),
|
||||||
|
type: "jobfieldchange",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -334,15 +335,23 @@ export function JobsDetailPage({
|
|||||||
>
|
>
|
||||||
<JobsDetailLaborContainer job={job} jobId={job.id} />
|
<JobsDetailLaborContainer job={job} jobId={job.id} />
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
<Tabs.TabPane
|
<Tabs.TabPane
|
||||||
forceRender
|
forceRender
|
||||||
tab={<span><BarsOutlined />{t('menus.jobsdetail.lifecycle')}</span>}
|
tab={
|
||||||
key="lifecycle"
|
<span>
|
||||||
>
|
<BarsOutlined />
|
||||||
<JobLifecycleComponent job={job} statuses={bodyshop.md_ro_statuses}/>
|
{t("menus.jobsdetail.lifecycle")}
|
||||||
</Tabs.TabPane>
|
</span>
|
||||||
|
}
|
||||||
|
key="lifecycle"
|
||||||
|
>
|
||||||
|
<JobLifecycleComponent
|
||||||
|
job={job}
|
||||||
|
statuses={bodyshop.md_ro_statuses}
|
||||||
|
/>
|
||||||
|
</Tabs.TabPane>
|
||||||
|
|
||||||
<Tabs.TabPane
|
<Tabs.TabPane
|
||||||
forceRender
|
forceRender
|
||||||
tab={
|
tab={
|
||||||
<span>
|
<span>
|
||||||
|
|||||||
@@ -54,9 +54,9 @@ export const setOnline = (isOnline) => ({
|
|||||||
payload: isOnline,
|
payload: isOnline,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const insertAuditTrail = ({ jobid, billid, operation }) => ({
|
export const insertAuditTrail = ({ jobid, billid, operation, type }) => ({
|
||||||
type: ApplicationActionTypes.INSERT_AUDIT_TRAIL,
|
type: ApplicationActionTypes.INSERT_AUDIT_TRAIL,
|
||||||
payload: { jobid, billid, operation },
|
payload: { jobid, billid, operation, type },
|
||||||
});
|
});
|
||||||
export const setProblemJobs = (problemJobs) => ({
|
export const setProblemJobs = (problemJobs) => ({
|
||||||
type: ApplicationActionTypes.SET_PROBLEM_JOBS,
|
type: ApplicationActionTypes.SET_PROBLEM_JOBS,
|
||||||
|
|||||||
@@ -266,7 +266,7 @@ export function* onInsertAuditTrail() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function* insertAuditTrailSaga({
|
export function* insertAuditTrailSaga({
|
||||||
payload: { jobid, billid, operation },
|
payload: { jobid, billid, operation, type },
|
||||||
}) {
|
}) {
|
||||||
const state = yield select();
|
const state = yield select();
|
||||||
const bodyshop = state.user.bodyshop;
|
const bodyshop = state.user.bodyshop;
|
||||||
@@ -278,6 +278,7 @@ export function* insertAuditTrailSaga({
|
|||||||
jobid,
|
jobid,
|
||||||
billid,
|
billid,
|
||||||
operation,
|
operation,
|
||||||
|
type,
|
||||||
useremail: currentUser.email,
|
useremail: currentUser.email,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -898,7 +898,8 @@
|
|||||||
"scheduledindate": "Sheduled In Today: {{date}}",
|
"scheduledindate": "Sheduled In Today: {{date}}",
|
||||||
"scheduledintoday": "Sheduled In Today",
|
"scheduledintoday": "Sheduled In Today",
|
||||||
"scheduledoutdate": "Sheduled Out Today: {{date}}",
|
"scheduledoutdate": "Sheduled Out Today: {{date}}",
|
||||||
"scheduledouttoday": "Sheduled Out Today"
|
"scheduledouttoday": "Sheduled Out Today",
|
||||||
|
"joblifecycle": "Job Lifecycle"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dms": {
|
"dms": {
|
||||||
@@ -1236,7 +1237,15 @@
|
|||||||
"relative_end": "Relative End",
|
"relative_end": "Relative End",
|
||||||
"relative_start": "Relative Start",
|
"relative_start": "Relative Start",
|
||||||
"start": "Start",
|
"start": "Start",
|
||||||
"value": "Value"
|
"value": "Value",
|
||||||
|
"status": "Status",
|
||||||
|
"percentage": "Percentage",
|
||||||
|
"human_readable": "Human Readable",
|
||||||
|
"status_count": "In Status"
|
||||||
|
},
|
||||||
|
"titles": {
|
||||||
|
"dashboard": "Job Lifecycle",
|
||||||
|
"top_durations": "Top Durations"
|
||||||
},
|
},
|
||||||
"content": {
|
"content": {
|
||||||
"current_status_accumulated_time": "Current Status Accumulated Time",
|
"current_status_accumulated_time": "Current Status Accumulated Time",
|
||||||
@@ -1248,7 +1257,9 @@
|
|||||||
"title": "Job Lifecycle Component",
|
"title": "Job Lifecycle Component",
|
||||||
"title_durations": "Historical Status Durations",
|
"title_durations": "Historical Status Durations",
|
||||||
"title_loading": "Loading",
|
"title_loading": "Loading",
|
||||||
"title_transitions": "Transitions"
|
"title_transitions": "Transitions",
|
||||||
|
"calculated_based_on": "Calculated based on",
|
||||||
|
"jobs_in_since": "Jobs in since"
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"fetch": "Error getting Job Lifecycle Data"
|
"fetch": "Error getting Job Lifecycle Data"
|
||||||
|
|||||||
@@ -898,7 +898,8 @@
|
|||||||
"scheduledindate": "",
|
"scheduledindate": "",
|
||||||
"scheduledintoday": "",
|
"scheduledintoday": "",
|
||||||
"scheduledoutdate": "",
|
"scheduledoutdate": "",
|
||||||
"scheduledouttoday": ""
|
"scheduledouttoday": "",
|
||||||
|
"joblifecycle": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dms": {
|
"dms": {
|
||||||
@@ -1236,7 +1237,15 @@
|
|||||||
"relative_end": "",
|
"relative_end": "",
|
||||||
"relative_start": "",
|
"relative_start": "",
|
||||||
"start": "",
|
"start": "",
|
||||||
"value": ""
|
"value": "",
|
||||||
|
"status": "",
|
||||||
|
"percentage": "",
|
||||||
|
"human_readable": "",
|
||||||
|
"status_count": ""
|
||||||
|
},
|
||||||
|
"titles": {
|
||||||
|
"dashboard": "",
|
||||||
|
"top_durations": ""
|
||||||
},
|
},
|
||||||
"content": {
|
"content": {
|
||||||
"current_status_accumulated_time": "",
|
"current_status_accumulated_time": "",
|
||||||
@@ -1248,7 +1257,9 @@
|
|||||||
"title": "",
|
"title": "",
|
||||||
"title_durations": "",
|
"title_durations": "",
|
||||||
"title_loading": "",
|
"title_loading": "",
|
||||||
"title_transitions": ""
|
"title_transitions": "",
|
||||||
|
"calculated_based_on": "",
|
||||||
|
"jobs_in_since": ""
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"fetch": "Error al obtener los datos del ciclo de vida del trabajo"
|
"fetch": "Error al obtener los datos del ciclo de vida del trabajo"
|
||||||
|
|||||||
@@ -1236,7 +1236,15 @@
|
|||||||
"relative_end": "",
|
"relative_end": "",
|
||||||
"relative_start": "",
|
"relative_start": "",
|
||||||
"start": "",
|
"start": "",
|
||||||
"value": ""
|
"value": "",
|
||||||
|
"status": "",
|
||||||
|
"percentage": "",
|
||||||
|
"human_readable": "",
|
||||||
|
"status_count": ""
|
||||||
|
},
|
||||||
|
"titles": {
|
||||||
|
"dashboard": "",
|
||||||
|
"top_durations": ""
|
||||||
},
|
},
|
||||||
"content": {
|
"content": {
|
||||||
"current_status_accumulated_time": "",
|
"current_status_accumulated_time": "",
|
||||||
@@ -1248,7 +1256,10 @@
|
|||||||
"title": "",
|
"title": "",
|
||||||
"title_durations": "",
|
"title_durations": "",
|
||||||
"title_loading": "",
|
"title_loading": "",
|
||||||
"title_transitions": ""
|
"title_transitions": "",
|
||||||
|
"calculated_based_on": "",
|
||||||
|
"jobs_in_since": "",
|
||||||
|
"joblifecycle": ""
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"fetch": "Erreur lors de l'obtention des données du cycle de vie des tâches"
|
"fetch": "Erreur lors de l'obtention des données du cycle de vie des tâches"
|
||||||
|
|||||||
@@ -259,28 +259,30 @@
|
|||||||
- active:
|
- active:
|
||||||
_eq: true
|
_eq: true
|
||||||
columns:
|
columns:
|
||||||
- id
|
- billid
|
||||||
|
- bodyshopid
|
||||||
- created
|
- created
|
||||||
- operation
|
- id
|
||||||
|
- jobid
|
||||||
- new_val
|
- new_val
|
||||||
- old_val
|
- old_val
|
||||||
|
- operation
|
||||||
|
- type
|
||||||
- useremail
|
- useremail
|
||||||
- bodyshopid
|
|
||||||
- jobid
|
|
||||||
- billid
|
|
||||||
select_permissions:
|
select_permissions:
|
||||||
- role: user
|
- role: user
|
||||||
permission:
|
permission:
|
||||||
columns:
|
columns:
|
||||||
|
- billid
|
||||||
|
- bodyshopid
|
||||||
|
- created
|
||||||
- id
|
- id
|
||||||
|
- jobid
|
||||||
- new_val
|
- new_val
|
||||||
- old_val
|
- old_val
|
||||||
- operation
|
- operation
|
||||||
|
- type
|
||||||
- useremail
|
- useremail
|
||||||
- created
|
|
||||||
- billid
|
|
||||||
- bodyshopid
|
|
||||||
- jobid
|
|
||||||
filter:
|
filter:
|
||||||
bodyshop:
|
bodyshop:
|
||||||
associations:
|
associations:
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
-- Could not auto-generate a down migration.
|
||||||
|
-- Please write an appropriate down migration for the SQL below:
|
||||||
|
-- alter table "public"."audit_trail" add column "type" text
|
||||||
|
-- null;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
alter table "public"."audit_trail" add column "type" text
|
||||||
|
null;
|
||||||
@@ -3,6 +3,7 @@ const queries = require("../graphql-client/queries");
|
|||||||
const moment = require("moment");
|
const moment = require("moment");
|
||||||
const durationToHumanReadable = require("../utils/durationToHumanReadable");
|
const durationToHumanReadable = require("../utils/durationToHumanReadable");
|
||||||
const calculateStatusDuration = require("../utils/calculateStatusDuration");
|
const calculateStatusDuration = require("../utils/calculateStatusDuration");
|
||||||
|
const getLifecycleStatusColor = require("../utils/getLifecycleStatusColor");
|
||||||
|
|
||||||
const jobLifecycle = async (req, res) => {
|
const jobLifecycle = async (req, res) => {
|
||||||
// Grab the jobids and statuses from the request body
|
// Grab the jobids and statuses from the request body
|
||||||
@@ -28,12 +29,12 @@ const jobLifecycle = async (req, res) => {
|
|||||||
jobIDs,
|
jobIDs,
|
||||||
transitions: []
|
transitions: []
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const transitionsByJobId = _.groupBy(resp.transitions, 'jobid');
|
const transitionsByJobId = _.groupBy(resp.transitions, 'jobid');
|
||||||
|
|
||||||
const groupedTransitions = {};
|
const groupedTransitions = {};
|
||||||
|
const allDurations = [];
|
||||||
|
|
||||||
for (let jobId in transitionsByJobId) {
|
for (let jobId in transitionsByJobId) {
|
||||||
let lifecycle = transitionsByJobId[jobId].map(transition => {
|
let lifecycle = transitionsByJobId[jobId].map(transition => {
|
||||||
@@ -53,15 +54,57 @@ const jobLifecycle = async (req, res) => {
|
|||||||
return transition;
|
return transition;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const durations = calculateStatusDuration(lifecycle, statuses);
|
||||||
|
|
||||||
groupedTransitions[jobId] = {
|
groupedTransitions[jobId] = {
|
||||||
lifecycle: lifecycle,
|
lifecycle,
|
||||||
durations: calculateStatusDuration(lifecycle, statuses),
|
durations
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (durations?.summations) {
|
||||||
|
allDurations.push(durations.summations);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const finalSummations = [];
|
||||||
|
const flatGroupedAllDurations = _.groupBy(allDurations.flat(),'status');
|
||||||
|
|
||||||
|
const finalStatusCounts = Object.keys(flatGroupedAllDurations).reduce((acc, status) => {
|
||||||
|
acc[status] = flatGroupedAllDurations[status].length;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
// Calculate total value of all statuses
|
||||||
|
const finalTotal = Object.values(flatGroupedAllDurations).reduce((total, statusArr) => {
|
||||||
|
return total + statusArr.reduce((acc, curr) => acc + curr.value, 0);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
Object.keys(flatGroupedAllDurations).forEach(status => {
|
||||||
|
const value = flatGroupedAllDurations[status].reduce((acc, curr) => acc + curr.value, 0);
|
||||||
|
const humanReadable = durationToHumanReadable(moment.duration(value));
|
||||||
|
const percentage = (value / finalTotal) * 100;
|
||||||
|
const color = getLifecycleStatusColor(status);
|
||||||
|
const roundedPercentage = `${Math.round(percentage)}%`;
|
||||||
|
finalSummations.push({
|
||||||
|
status,
|
||||||
|
value,
|
||||||
|
humanReadable,
|
||||||
|
percentage,
|
||||||
|
color,
|
||||||
|
roundedPercentage
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
jobIDs,
|
jobIDs,
|
||||||
transition: groupedTransitions,
|
transition: groupedTransitions,
|
||||||
|
durations: {
|
||||||
|
jobs: jobIDs.length,
|
||||||
|
summations: finalSummations,
|
||||||
|
totalStatuses: finalSummations.length,
|
||||||
|
total: finalTotal,
|
||||||
|
statusCounts: finalStatusCounts,
|
||||||
|
humanReadable: durationToHumanReadable(moment.duration(finalTotal))
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,7 @@
|
|||||||
const durationToHumanReadable = require("./durationToHumanReadable");
|
const durationToHumanReadable = require("./durationToHumanReadable");
|
||||||
const moment = require("moment");
|
const moment = require("moment");
|
||||||
|
const getLifecycleStatusColor = require("./getLifecycleStatusColor");
|
||||||
const _ = require("lodash");
|
const _ = require("lodash");
|
||||||
const crypto = require('crypto');
|
|
||||||
|
|
||||||
const getColor = (key) => {
|
|
||||||
const hash = crypto.createHash('sha256');
|
|
||||||
hash.update(key);
|
|
||||||
const hashedKey = hash.digest('hex');
|
|
||||||
const num = parseInt(hashedKey, 16);
|
|
||||||
return '#' + (num % 16777215).toString(16).padStart(6, '0');
|
|
||||||
};
|
|
||||||
|
|
||||||
const calculateStatusDuration = (transitions, statuses) => {
|
const calculateStatusDuration = (transitions, statuses) => {
|
||||||
let statusDuration = {};
|
let statusDuration = {};
|
||||||
@@ -33,26 +25,16 @@ const calculateStatusDuration = (transitions, statuses) => {
|
|||||||
if (!transition.prev_value) {
|
if (!transition.prev_value) {
|
||||||
statusDuration[transition.value] = {
|
statusDuration[transition.value] = {
|
||||||
value: duration,
|
value: duration,
|
||||||
humanReadable: transition.duration_readable
|
humanReadable: durationToHumanReadable(moment.duration(duration))
|
||||||
};
|
};
|
||||||
} else if (!transition.next_value) {
|
} else {
|
||||||
if (statusDuration[transition.value]) {
|
if (statusDuration[transition.value]) {
|
||||||
statusDuration[transition.value].value += duration;
|
statusDuration[transition.value].value += duration;
|
||||||
statusDuration[transition.value].humanReadable = transition.duration_readable;
|
statusDuration[transition.value].humanReadable = durationToHumanReadable(moment.duration(statusDuration[transition.value].value));
|
||||||
} else {
|
} else {
|
||||||
statusDuration[transition.value] = {
|
statusDuration[transition.value] = {
|
||||||
value: duration,
|
value: duration,
|
||||||
humanReadable: transition.duration_readable
|
humanReadable: durationToHumanReadable(moment.duration(duration))
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (statusDuration[transition.value]) {
|
|
||||||
statusDuration[transition.value].value += duration;
|
|
||||||
statusDuration[transition.value].humanReadable = transition.duration_readable;
|
|
||||||
} else {
|
|
||||||
statusDuration[transition.value] = {
|
|
||||||
value: duration,
|
|
||||||
humanReadable: transition.duration_readable
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,7 +61,7 @@ const calculateStatusDuration = (transitions, statuses) => {
|
|||||||
value,
|
value,
|
||||||
humanReadable,
|
humanReadable,
|
||||||
percentage: statusDuration[status].percentage,
|
percentage: statusDuration[status].percentage,
|
||||||
color: getColor(status),
|
color: getLifecycleStatusColor(status),
|
||||||
roundedPercentage: `${Math.round(statusDuration[status].percentage)}%`
|
roundedPercentage: `${Math.round(statusDuration[status].percentage)}%`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
11
server/utils/getLifecycleStatusColor.js
Normal file
11
server/utils/getLifecycleStatusColor.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
|
const getLifecycleStatusColor = (key) => {
|
||||||
|
const hash = crypto.createHash('sha256');
|
||||||
|
hash.update(key);
|
||||||
|
const hashedKey = hash.digest('hex');
|
||||||
|
const num = parseInt(hashedKey, 16);
|
||||||
|
return '#' + (num % 16777215).toString(16).padStart(6, '0');
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = getLifecycleStatusColor;
|
||||||
Reference in New Issue
Block a user