Compare commits
64 Commits
rome/test
...
feature/IO
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1ca09bd4f | ||
|
|
f9ca36ec89 | ||
|
|
f98c9e6f71 | ||
|
|
28f2e8ad30 | ||
|
|
c27e206687 | ||
|
|
01fd253f1d | ||
|
|
e67bc0d953 | ||
|
|
3eab3e2fb6 | ||
|
|
3adf6b649b | ||
|
|
f8243aa2b3 | ||
|
|
3c3f50d138 | ||
|
|
4f7e1b81ac | ||
|
|
806bdc4c70 | ||
|
|
a0572a0cec | ||
|
|
04cdf13e86 | ||
|
|
50349e91dc | ||
|
|
9998a8f154 | ||
|
|
9dcbcb2a43 | ||
|
|
5773f7a0f3 | ||
|
|
8614d88e71 | ||
|
|
fa5e26c52a | ||
|
|
90e1cbd390 | ||
|
|
947a3c6a88 | ||
|
|
a33662e6f0 | ||
|
|
6d1f04369e | ||
|
|
4a27726ef3 | ||
|
|
eedba97237 | ||
|
|
bcf095ed4f | ||
|
|
c3f9e268c7 | ||
|
|
c8442f0750 | ||
|
|
da5b446c30 | ||
|
|
e13b2bb969 | ||
|
|
e8969c4698 | ||
|
|
346d32a2bb | ||
|
|
6ba00a90be | ||
|
|
4293d20313 | ||
|
|
706c70c509 | ||
|
|
e872b1bf0a | ||
|
|
379fa060d8 | ||
|
|
a6258c6456 | ||
|
|
d6bf0a225b | ||
|
|
ec2b914e5e | ||
|
|
309a20148a | ||
|
|
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
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -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"]({
|
||||||
|
|||||||
@@ -18,10 +18,8 @@ export default function JobDetailCardsTotalsComponent({ loading, data }) {
|
|||||||
/>
|
/>
|
||||||
<Statistic
|
<Statistic
|
||||||
className="imex-flex-row__margin-large"
|
className="imex-flex-row__margin-large"
|
||||||
title={t("jobs.fields.ded_amt")}
|
title={t("jobs.fields.customerowing")}
|
||||||
value={Dinero({
|
value={Dinero(data.job_totals.totals.custPayable.total).toFormat()}
|
||||||
amount: Math.round((data.ded_amt || 0) * 100),
|
|
||||||
}).toFormat()}
|
|
||||||
/>
|
/>
|
||||||
<Statistic
|
<Statistic
|
||||||
className="imex-flex-row__margin-large"
|
className="imex-flex-row__margin-large"
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ export function JobEmployeeAssignments({
|
|||||||
jobRO,
|
jobRO,
|
||||||
body,
|
body,
|
||||||
refinish,
|
refinish,
|
||||||
|
|
||||||
prep,
|
prep,
|
||||||
csr,
|
csr,
|
||||||
handleAdd,
|
handleAdd,
|
||||||
@@ -78,7 +77,7 @@ export function JobEmployeeAssignments({
|
|||||||
setVisibility(false);
|
setVisibility(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Assign
|
{t("allocations.actions.assign")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={() => setVisibility(false)}>Close</Button>
|
<Button onClick={() => setVisibility(false)}>Close</Button>
|
||||||
</Space>
|
</Space>
|
||||||
|
|||||||
@@ -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,
|
||||||
@@ -43,12 +43,13 @@ export function JobEmployeeAssignmentsContainer({
|
|||||||
});
|
});
|
||||||
if (refetch) refetch();
|
if (refetch) refetch();
|
||||||
|
|
||||||
insertAuditTrail({
|
if (!!!result.errors) {
|
||||||
jobid: job.id,
|
insertAuditTrail({
|
||||||
operation: AuditTrailMapping.jobassignmentchange(operation, name),
|
jobid: job.id,
|
||||||
});
|
operation: AuditTrailMapping.jobassignmentchange(operation, name),
|
||||||
|
type: "jobassignmentchange",
|
||||||
if (!!result.errors) {
|
});
|
||||||
|
} else {
|
||||||
notification["error"]({
|
notification["error"]({
|
||||||
message: t("jobs.errors.assigning", {
|
message: t("jobs.errors.assigning", {
|
||||||
message: JSON.stringify(result.errors),
|
message: JSON.stringify(result.errors),
|
||||||
@@ -66,17 +67,19 @@ export function JobEmployeeAssignmentsContainer({
|
|||||||
variables: { jobId: job.id, job: { [empAssignment]: null } },
|
variables: { jobId: job.id, job: { [empAssignment]: null } },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!!result.errors) {
|
if (!!!result.errors) {
|
||||||
|
insertAuditTrail({
|
||||||
|
jobid: job.id,
|
||||||
|
operation: AuditTrailMapping.jobassignmentremoved(operation),
|
||||||
|
type: "jobassignmentremoved",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
notification["error"]({
|
notification["error"]({
|
||||||
message: t("jobs.errors.assigning", {
|
message: t("jobs.errors.assigning", {
|
||||||
message: JSON.stringify(result.errors),
|
message: JSON.stringify(result.errors),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
insertAuditTrail({
|
|
||||||
jobid: job.id,
|
|
||||||
operation: AuditTrailMapping.jobassignmentremoved(operation),
|
|
||||||
});
|
|
||||||
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/`);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
PauseCircleOutlined,
|
PauseCircleOutlined,
|
||||||
WarningFilled,
|
WarningFilled,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { Card, Col, Row, Space, Tag, Tooltip } from "antd";
|
import { Card, Col, Divider, Row, Space, Tag, Tooltip } from "antd";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
@@ -26,6 +26,7 @@ import ProductionListColumnComment from "../production-list-columns/production-l
|
|||||||
import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component";
|
import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component";
|
||||||
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
|
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
|
||||||
import "./jobs-detail-header.styles.scss";
|
import "./jobs-detail-header.styles.scss";
|
||||||
|
import moment from "moment";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
jobRO: selectJobReadOnly,
|
jobRO: selectJobReadOnly,
|
||||||
@@ -62,6 +63,13 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
|||||||
${job.v_make_desc || ""}
|
${job.v_make_desc || ""}
|
||||||
${job.v_model_desc || ""}`.trim();
|
${job.v_model_desc || ""}`.trim();
|
||||||
|
|
||||||
|
const bodyHrs = job.joblines
|
||||||
|
.filter((j) => j.mod_lbr_ty !== "LAR")
|
||||||
|
.reduce((acc, val) => acc + val.mod_lb_hrs, 0);
|
||||||
|
const refinishHrs = job.joblines
|
||||||
|
.filter((line) => line.mod_lbr_ty === "LAR")
|
||||||
|
.reduce((acc, val) => acc + val.mod_lb_hrs, 0);
|
||||||
|
|
||||||
const ownerTitle = OwnerNameDisplayFunction(job).trim();
|
const ownerTitle = OwnerNameDisplayFunction(job).trim();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -93,7 +101,9 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
|||||||
{job.status === bodyshop.md_ro_statuses.default_scheduled &&
|
{job.status === bodyshop.md_ro_statuses.default_scheduled &&
|
||||||
job.scheduled_in ? (
|
job.scheduled_in ? (
|
||||||
<Tag>
|
<Tag>
|
||||||
<DateTimeFormatter>{job.scheduled_in}</DateTimeFormatter>
|
<Link to={`/manage/schedule?date=${moment(job.scheduled_in).format('YYYY-MM-DD')}`}>
|
||||||
|
<DateTimeFormatter>{job.scheduled_in}</DateTimeFormatter>
|
||||||
|
</Link>
|
||||||
</Tag>
|
</Tag>
|
||||||
) : null}
|
) : null}
|
||||||
</Space>
|
</Space>
|
||||||
@@ -299,6 +309,11 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
|||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<JobEmployeeAssignments job={job} />
|
<JobEmployeeAssignments job={job} />
|
||||||
|
<Divider style={{ margin: ".5rem" }} />
|
||||||
|
<DataLabel label={t("jobs.labels.labor_hrs")}>
|
||||||
|
{bodyHrs.toFixed(1)} / {refinishHrs.toFixed(1)} /{" "}
|
||||||
|
{(bodyHrs + refinishHrs).toFixed(1)}
|
||||||
|
</DataLabel>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
|
|||||||
@@ -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",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ function OwnerDetailJobsComponent({ bodyshop, owner }) {
|
|||||||
const handleTableChange = (pagination, filters, sorter) => {
|
const handleTableChange = (pagination, filters, sorter) => {
|
||||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||||
};
|
};
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: t("jobs.fields.ro_number"),
|
title: t("jobs.fields.ro_number"),
|
||||||
@@ -44,6 +44,15 @@ function OwnerDetailJobsComponent({ bodyshop, owner }) {
|
|||||||
title: t("jobs.fields.vehicle"),
|
title: t("jobs.fields.vehicle"),
|
||||||
dataIndex: "vehicleid",
|
dataIndex: "vehicleid",
|
||||||
key: "vehicleid",
|
key: "vehicleid",
|
||||||
|
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 === "vehicleid" && state.sortedInfo.order,
|
||||||
render: (text, record) =>
|
render: (text, record) =>
|
||||||
record.vehicleid ? (
|
record.vehicleid ? (
|
||||||
<Link to={`/manage/vehicles/${record.vehicleid}`}>
|
<Link to={`/manage/vehicles/${record.vehicleid}`}>
|
||||||
@@ -67,9 +76,15 @@ function OwnerDetailJobsComponent({ bodyshop, owner }) {
|
|||||||
title: t("jobs.fields.status"),
|
title: t("jobs.fields.status"),
|
||||||
dataIndex: "status",
|
dataIndex: "status",
|
||||||
key: "status",
|
key: "status",
|
||||||
sorter: (a, b) => statusSort(a.status, b.status, bodyshop.md_ro_statuses.statuses),
|
sorter: (a, b) =>
|
||||||
|
statusSort(a.status, b.status, bodyshop.md_ro_statuses.statuses),
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
||||||
|
filters: bodyshop.md_ro_statuses.statuses.map((status) => ({
|
||||||
|
text: status,
|
||||||
|
value: status,
|
||||||
|
})),
|
||||||
|
onFilter: (value, record) => value.includes(record.status),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -95,15 +95,13 @@ export function PartsQueueListComponent({ bodyshop }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleOnRowClick = (record) => {
|
const handleOnRowClick = (record) => {
|
||||||
if (record) {
|
if (record?.id) {
|
||||||
if (record.id) {
|
history.replace({
|
||||||
history.push({
|
search: queryString.stringify({
|
||||||
search: queryString.stringify({
|
...searchParams,
|
||||||
...searchParams,
|
selected: record.id,
|
||||||
selected: record.id,
|
}),
|
||||||
}),
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -350,6 +348,13 @@ export function PartsQueueListComponent({ bodyshop }) {
|
|||||||
selectedRowKeys: [selected],
|
selectedRowKeys: [selected],
|
||||||
type: "radio",
|
type: "radio",
|
||||||
}}
|
}}
|
||||||
|
onRow={(record, rowIndex) => {
|
||||||
|
return {
|
||||||
|
onClick: (event) => {
|
||||||
|
handleOnRowClick(record);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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,7 +1,6 @@
|
|||||||
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 { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
import { TimeFormatter } from "../../utils/DateFormatter";
|
import { TimeFormatter } from "../../utils/DateFormatter";
|
||||||
@@ -10,7 +9,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 +85,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 +97,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,
|
||||||
@@ -185,17 +189,12 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
|
|||||||
state.sortedInfo.columnKey === "date_next_contact" &&
|
state.sortedInfo.columnKey === "date_next_contact" &&
|
||||||
state.sortedInfo.order,
|
state.sortedInfo.order,
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<span
|
<ProductionListDate
|
||||||
style={{
|
record={record}
|
||||||
color:
|
field="date_next_contact"
|
||||||
record.date_next_contact &&
|
pastIndicator
|
||||||
moment(record.date_next_contact).isBefore(moment())
|
time
|
||||||
? "red"
|
/>
|
||||||
: "",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ProductionListDate record={record} field="date_next_contact" time />
|
|
||||||
</span>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -291,6 +290,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 +315,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 +405,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",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
Input,
|
Input,
|
||||||
InputNumber,
|
InputNumber,
|
||||||
Select,
|
Select,
|
||||||
|
Space,
|
||||||
Switch,
|
Switch,
|
||||||
Typography,
|
Typography,
|
||||||
} from "antd";
|
} from "antd";
|
||||||
@@ -17,6 +18,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||||
|
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
|
||||||
|
|
||||||
const SelectorDiv = styled.div`
|
const SelectorDiv = styled.div`
|
||||||
.ant-form-item .ant-select {
|
.ant-form-item .ant-select {
|
||||||
@@ -191,7 +193,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
|||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
<LayoutFormRow header={t("bodyshop.labels.dms.cdk.payers")}>
|
<LayoutFormRow header={t("bodyshop.labels.dms.cdk.payers")}>
|
||||||
<Form.List name={["cdk_configuration", "payers"]}>
|
<Form.List name={["cdk_configuration", "payers"]}>
|
||||||
{(fields, { add, remove }) => {
|
{(fields, { add, remove, move }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => (
|
||||||
@@ -249,11 +251,18 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
|||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<DeleteFilled
|
<Space align="center">
|
||||||
onClick={() => {
|
<DeleteFilled
|
||||||
remove(field.name);
|
onClick={() => {
|
||||||
}}
|
remove(field.name);
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
<FormListMoveArrows
|
||||||
|
move={move}
|
||||||
|
index={index}
|
||||||
|
total={fields.length}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
))}
|
))}
|
||||||
@@ -345,7 +354,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
|||||||
id="costs"
|
id="costs"
|
||||||
>
|
>
|
||||||
<Form.List name={["md_responsibility_centers", "costs"]}>
|
<Form.List name={["md_responsibility_centers", "costs"]}>
|
||||||
{(fields, { add, remove }) => {
|
{(fields, { add, remove, move }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => (
|
||||||
@@ -462,12 +471,18 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
|||||||
<Input onBlur={handleBlur} />
|
<Input onBlur={handleBlur} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
|
<Space align="center">
|
||||||
<DeleteFilled
|
<DeleteFilled
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
remove(field.name);
|
remove(field.name);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<FormListMoveArrows
|
||||||
|
move={move}
|
||||||
|
index={index}
|
||||||
|
total={fields.length}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
))}
|
))}
|
||||||
@@ -493,7 +508,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
|||||||
id="profits"
|
id="profits"
|
||||||
>
|
>
|
||||||
<Form.List name={["md_responsibility_centers", "profits"]}>
|
<Form.List name={["md_responsibility_centers", "profits"]}>
|
||||||
{(fields, { add, remove }) => {
|
{(fields, { add, remove, move }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => (
|
||||||
@@ -595,11 +610,18 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
|||||||
<Input onBlur={handleBlur} />
|
<Input onBlur={handleBlur} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
<DeleteFilled
|
<Space align="center">
|
||||||
onClick={() => {
|
<DeleteFilled
|
||||||
remove(field.name);
|
onClick={() => {
|
||||||
}}
|
remove(field.name);
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
<FormListMoveArrows
|
||||||
|
move={move}
|
||||||
|
index={index}
|
||||||
|
total={fields.length}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import { createStructuredSelector } from "reselect";
|
|||||||
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 { alphaSort, statusSort } from "../../utils/sorters";
|
import { alphaSort, statusSort } from "../../utils/sorters";
|
||||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
import OwnerNameDisplay, {
|
||||||
|
OwnerNameDisplayFunction,
|
||||||
|
} from "../owner-name-display/owner-name-display.component";
|
||||||
import VehicleDetailUpdateJobsComponent from "../vehicle-detail-update-jobs/vehicle-detail-update-jobs.component";
|
import VehicleDetailUpdateJobsComponent from "../vehicle-detail-update-jobs/vehicle-detail-update-jobs.component";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
@@ -45,6 +47,10 @@ export function VehicleDetailJobsComponent({ vehicle, bodyshop }) {
|
|||||||
title: t("jobs.fields.owner"),
|
title: t("jobs.fields.owner"),
|
||||||
dataIndex: "owner",
|
dataIndex: "owner",
|
||||||
key: "owner",
|
key: "owner",
|
||||||
|
sorter: (a, b) =>
|
||||||
|
alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<Link to={`/manage/owners/${record.owner.id}`}>
|
<Link to={`/manage/owners/${record.owner.id}`}>
|
||||||
<OwnerNameDisplay ownerObject={record} />
|
<OwnerNameDisplay ownerObject={record} />
|
||||||
@@ -63,9 +69,15 @@ export function VehicleDetailJobsComponent({ vehicle, bodyshop }) {
|
|||||||
title: t("jobs.fields.status"),
|
title: t("jobs.fields.status"),
|
||||||
dataIndex: "status",
|
dataIndex: "status",
|
||||||
key: "status",
|
key: "status",
|
||||||
sorter: (a, b) => statusSort(a.status, b.status, bodyshop.md_ro_statuses.statuses),
|
sorter: (a, b) =>
|
||||||
|
statusSort(a.status, b.status, bodyshop.md_ro_statuses.statuses),
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
||||||
|
filters: bodyshop.md_ro_statuses.statuses.map((status) => ({
|
||||||
|
text: status,
|
||||||
|
value: status,
|
||||||
|
})),
|
||||||
|
onFilter: (value, record) => value.includes(record.status),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ 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, useHistory, useLocation } from "react-router-dom";
|
import { Link, useHistory, useLocation } from "react-router-dom";
|
||||||
|
import { pageLimit } from "../../utils/config";
|
||||||
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
|
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
|
||||||
import {pageLimit} from "../../utils/config";
|
import { alphaSort } from './../../utils/sorters';
|
||||||
export default function VehiclesListComponent({
|
export default function VehiclesListComponent({
|
||||||
loading,
|
loading,
|
||||||
vehicles,
|
vehicles,
|
||||||
@@ -31,6 +32,8 @@ export default function VehiclesListComponent({
|
|||||||
title: t("vehicles.fields.v_vin"),
|
title: t("vehicles.fields.v_vin"),
|
||||||
dataIndex: "v_vin",
|
dataIndex: "v_vin",
|
||||||
key: "v_vin",
|
key: "v_vin",
|
||||||
|
sorter: (a, b) => alphaSort(a.v_vin, b.v_vin),
|
||||||
|
sortOrder: state.sortedInfo.columnKey === "v_vin" && state.sortedInfo.order,
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<Link to={"/manage/vehicles/" + record.id}>
|
<Link to={"/manage/vehicles/" + record.id}>
|
||||||
<VehicleVinDisplay>{record.v_vin || "N/A"}</VehicleVinDisplay>
|
<VehicleVinDisplay>{record.v_vin || "N/A"}</VehicleVinDisplay>
|
||||||
@@ -51,8 +54,10 @@ export default function VehiclesListComponent({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("vehicles.fields.plate_no"),
|
title: t("vehicles.fields.plate_no"),
|
||||||
dataIndex: "plate",
|
dataIndex: "plate_no",
|
||||||
key: "plate",
|
key: "plate_no",
|
||||||
|
sorter: (a, b) => alphaSort(a.plate_no, b.plate_no),
|
||||||
|
sortOrder: state.sortedInfo.columnKey === "plate_no" && state.sortedInfo.order,
|
||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
return (
|
return (
|
||||||
<span>{`${record.plate_st || ""} | ${record.plate_no || ""}`}</span>
|
<span>{`${record.plate_st || ""} | ${record.plate_no || ""}`}</span>
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { getAuth, updatePassword, updateProfile } from "firebase/auth";
|
|||||||
import { getFirestore } from "firebase/firestore";
|
import { getFirestore } from "firebase/firestore";
|
||||||
import { getMessaging, getToken, onMessage } from "firebase/messaging";
|
import { getMessaging, getToken, onMessage } from "firebase/messaging";
|
||||||
import { store } from "../redux/store";
|
import { store } from "../redux/store";
|
||||||
|
import axios from "axios";
|
||||||
|
import { checkBeta } from "../utils/handleBeta";
|
||||||
|
|
||||||
const config = JSON.parse(process.env.REACT_APP_FIREBASE_CONFIG);
|
const config = JSON.parse(process.env.REACT_APP_FIREBASE_CONFIG);
|
||||||
initializeApp(config);
|
initializeApp(config);
|
||||||
@@ -86,6 +88,18 @@ export const logImEXEvent = (eventName, additionalParams, stateProp = null) => {
|
|||||||
null,
|
null,
|
||||||
...additionalParams,
|
...additionalParams,
|
||||||
};
|
};
|
||||||
|
axios.post("/ioevent", {
|
||||||
|
useremail:
|
||||||
|
(state.user && state.user.currentUser && state.user.currentUser.email) ||
|
||||||
|
null,
|
||||||
|
bodyshopid:
|
||||||
|
(state.user && state.user.bodyshop && state.user.bodyshop.id) || null,
|
||||||
|
operationName: eventName,
|
||||||
|
variables: additionalParams,
|
||||||
|
dbevent: false,
|
||||||
|
env: checkBeta() ? "beta" : "master",
|
||||||
|
});
|
||||||
|
|
||||||
// console.log(
|
// console.log(
|
||||||
// "%c[Analytics]",
|
// "%c[Analytics]",
|
||||||
// "background-color: green ;font-weight:bold;",
|
// "background-color: green ;font-weight:bold;",
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export const QUERY_VEHICLE_BY_ID = gql`
|
|||||||
jobs(order_by: { date_open: desc }) {
|
jobs(order_by: { date_open: desc }) {
|
||||||
id
|
id
|
||||||
ro_number
|
ro_number
|
||||||
|
ownr_co_nm
|
||||||
ownr_fn
|
ownr_fn
|
||||||
ownr_ln
|
ownr_ln
|
||||||
owner {
|
owner {
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -259,6 +259,7 @@
|
|||||||
"saving": "Error encountered while saving. {{message}}"
|
"saving": "Error encountered while saving. {{message}}"
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"ReceivableCustomField": "QBO Receivable Custom Field {{number}}",
|
||||||
"address1": "Address 1",
|
"address1": "Address 1",
|
||||||
"address2": "Address 2",
|
"address2": "Address 2",
|
||||||
"appt_alt_transport": "Appointment Alternative Transportation Options",
|
"appt_alt_transport": "Appointment Alternative Transportation Options",
|
||||||
@@ -477,7 +478,6 @@
|
|||||||
"editaccess": "Users -> Edit access"
|
"editaccess": "Users -> Edit access"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ReceivableCustomField": "QBO Receivable Custom Field {{number}}",
|
|
||||||
"responsibilitycenter": "Responsibility Center",
|
"responsibilitycenter": "Responsibility Center",
|
||||||
"responsibilitycenter_accountdesc": "Account Description",
|
"responsibilitycenter_accountdesc": "Account Description",
|
||||||
"responsibilitycenter_accountitem": "Item",
|
"responsibilitycenter_accountitem": "Item",
|
||||||
@@ -608,7 +608,7 @@
|
|||||||
"dms": {
|
"dms": {
|
||||||
"cdk": {
|
"cdk": {
|
||||||
"controllist": "Control Number List",
|
"controllist": "Control Number List",
|
||||||
"payers": "CDK Payers"
|
"payers": "Payers"
|
||||||
},
|
},
|
||||||
"cdk_dealerid": "CDK Dealer ID",
|
"cdk_dealerid": "CDK Dealer ID",
|
||||||
"pbs_serialnumber": "PBS Serial Number",
|
"pbs_serialnumber": "PBS Serial Number",
|
||||||
@@ -844,8 +844,8 @@
|
|||||||
"notconfigured": "You do not have any current CSI Question Sets configured.",
|
"notconfigured": "You do not have any current CSI Question Sets configured.",
|
||||||
"notfoundsubtitle": "We were unable to find a survey using the link you provided. Please ensure the URL is correct or reach out to your shop for more help.",
|
"notfoundsubtitle": "We were unable to find a survey using the link you provided. Please ensure the URL is correct or reach out to your shop for more help.",
|
||||||
"notfoundtitle": "No survey found.",
|
"notfoundtitle": "No survey found.",
|
||||||
"surveycompletetitle": "Survey previously completed",
|
"surveycompletesubtitle": "This survey was already completed on {{date}}.",
|
||||||
"surveycompletesubtitle": "This survey was already completed on {{date}}."
|
"surveycompletetitle": "Survey previously completed"
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"completedon": "Completed On",
|
"completedon": "Completed On",
|
||||||
@@ -854,13 +854,13 @@
|
|||||||
"validuntil": "Valid Until"
|
"validuntil": "Valid Until"
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
|
"copyright": "Copyright © $t(titles.app). All Rights Reserved.",
|
||||||
|
"greeting": "Hi {{name}}!",
|
||||||
|
"intro": "At {{shopname}}, we value your feedback. We would love to hear what you have to say. Please fill out the form below.",
|
||||||
"nologgedinuser": "Please log out of $t(titles.app)",
|
"nologgedinuser": "Please log out of $t(titles.app)",
|
||||||
"nologgedinuser_sub": "Users of $t(titles.app) cannot complete CSI surveys while logged in. Please log out and try again.",
|
"nologgedinuser_sub": "Users of $t(titles.app) cannot complete CSI surveys while logged in. Please log out and try again.",
|
||||||
"noneselected": "No response selected.",
|
"noneselected": "No response selected.",
|
||||||
"title": "Customer Satisfaction Survey",
|
"title": "Customer Satisfaction Survey"
|
||||||
"greeting": "Hi {{name}}!",
|
|
||||||
"intro": "At {{shopname}}, we value your feedback. We would love to hear what you have to say. Please fill out the form below.",
|
|
||||||
"copyright": "Copyright © $t(titles.app). All Rights Reserved."
|
|
||||||
},
|
},
|
||||||
"successes": {
|
"successes": {
|
||||||
"created": "CSI created successfully.",
|
"created": "CSI created successfully.",
|
||||||
@@ -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"
|
||||||
@@ -1823,6 +1834,7 @@
|
|||||||
"job": "Job Details",
|
"job": "Job Details",
|
||||||
"jobcosting": "Job Costing",
|
"jobcosting": "Job Costing",
|
||||||
"jobtotals": "Job Totals",
|
"jobtotals": "Job Totals",
|
||||||
|
"labor_hrs": "B/P/T Hrs",
|
||||||
"labor_rates_subtotal": "Labor Rates Subtotal",
|
"labor_rates_subtotal": "Labor Rates Subtotal",
|
||||||
"laborallocations": "Labor Allocations",
|
"laborallocations": "Labor Allocations",
|
||||||
"labortotals": "Labor Totals",
|
"labortotals": "Labor Totals",
|
||||||
@@ -2417,6 +2429,7 @@
|
|||||||
"invoice_total_payable": "Invoice (Total Payable)",
|
"invoice_total_payable": "Invoice (Total Payable)",
|
||||||
"iou_form": "IOU Form",
|
"iou_form": "IOU Form",
|
||||||
"job_costing_ro": "Job Costing",
|
"job_costing_ro": "Job Costing",
|
||||||
|
"job_lifecycle_ro": "Job Lifecycle",
|
||||||
"job_notes": "Job Notes",
|
"job_notes": "Job Notes",
|
||||||
"key_tag": "Key Tag",
|
"key_tag": "Key Tag",
|
||||||
"labels": {
|
"labels": {
|
||||||
@@ -2583,17 +2596,17 @@
|
|||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"advanced_filters": "Advanced Filters and Sorters",
|
"advanced_filters": "Advanced Filters and Sorters",
|
||||||
"advanced_filters_show": "Show",
|
|
||||||
"advanced_filters_hide": "Hide",
|
|
||||||
"advanced_filters_filters": "Filters",
|
|
||||||
"advanced_filters_sorters": "Sorters",
|
|
||||||
"advanced_filters_filter_field": "Field",
|
|
||||||
"advanced_filters_sorter_field": "Field",
|
|
||||||
"advanced_filters_true": "True",
|
|
||||||
"advanced_filters_false": "False",
|
"advanced_filters_false": "False",
|
||||||
"advanced_filters_sorter_direction": "Direction",
|
"advanced_filters_filter_field": "Field",
|
||||||
"advanced_filters_filter_operator": "Operator",
|
"advanced_filters_filter_operator": "Operator",
|
||||||
"advanced_filters_filter_value": "Value",
|
"advanced_filters_filter_value": "Value",
|
||||||
|
"advanced_filters_filters": "Filters",
|
||||||
|
"advanced_filters_hide": "Hide",
|
||||||
|
"advanced_filters_show": "Show",
|
||||||
|
"advanced_filters_sorter_direction": "Direction",
|
||||||
|
"advanced_filters_sorter_field": "Field",
|
||||||
|
"advanced_filters_sorters": "Sorters",
|
||||||
|
"advanced_filters_true": "True",
|
||||||
"dates": "Dates",
|
"dates": "Dates",
|
||||||
"employee": "Employee",
|
"employee": "Employee",
|
||||||
"filterson": "Filters on {{object}}: {{field}}",
|
"filterson": "Filters on {{object}}: {{field}}",
|
||||||
@@ -2675,6 +2688,8 @@
|
|||||||
"job_costing_ro_date_summary": "Job Costing by RO - Summary",
|
"job_costing_ro_date_summary": "Job Costing by RO - Summary",
|
||||||
"job_costing_ro_estimator": "Job Costing by Estimator",
|
"job_costing_ro_estimator": "Job Costing by Estimator",
|
||||||
"job_costing_ro_ins_co": "Job Costing by RO Source",
|
"job_costing_ro_ins_co": "Job Costing by RO Source",
|
||||||
|
"job_lifecycle_date_detail": "Job Lifecycle by Date - Detail",
|
||||||
|
"job_lifecycle_date_summary": "Job Lifecycle by Date - Summary",
|
||||||
"jobs_completed_not_invoiced": "Jobs Completed not Invoiced",
|
"jobs_completed_not_invoiced": "Jobs Completed not Invoiced",
|
||||||
"jobs_invoiced_not_exported": "Jobs Invoiced not Exported",
|
"jobs_invoiced_not_exported": "Jobs Invoiced not Exported",
|
||||||
"jobs_reconcile": "Parts/Sublet/Labor Reconciliation",
|
"jobs_reconcile": "Parts/Sublet/Labor Reconciliation",
|
||||||
|
|||||||
@@ -259,6 +259,7 @@
|
|||||||
"saving": ""
|
"saving": ""
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"ReceivableCustomField": "",
|
||||||
"address1": "",
|
"address1": "",
|
||||||
"address2": "",
|
"address2": "",
|
||||||
"appt_alt_transport": "",
|
"appt_alt_transport": "",
|
||||||
@@ -477,7 +478,6 @@
|
|||||||
"editaccess": ""
|
"editaccess": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ReceivableCustomField": "",
|
|
||||||
"responsibilitycenter": "",
|
"responsibilitycenter": "",
|
||||||
"responsibilitycenter_accountdesc": "",
|
"responsibilitycenter_accountdesc": "",
|
||||||
"responsibilitycenter_accountitem": "",
|
"responsibilitycenter_accountitem": "",
|
||||||
@@ -844,8 +844,8 @@
|
|||||||
"notconfigured": "",
|
"notconfigured": "",
|
||||||
"notfoundsubtitle": "",
|
"notfoundsubtitle": "",
|
||||||
"notfoundtitle": "",
|
"notfoundtitle": "",
|
||||||
"surveycompletetitle": "",
|
"surveycompletesubtitle": "",
|
||||||
"surveycompletesubtitle": ""
|
"surveycompletetitle": ""
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"completedon": "",
|
"completedon": "",
|
||||||
@@ -854,13 +854,13 @@
|
|||||||
"validuntil": ""
|
"validuntil": ""
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
|
"copyright": "",
|
||||||
|
"greeting": "",
|
||||||
|
"intro": "",
|
||||||
"nologgedinuser": "",
|
"nologgedinuser": "",
|
||||||
"nologgedinuser_sub": "",
|
"nologgedinuser_sub": "",
|
||||||
"noneselected": "",
|
"noneselected": "",
|
||||||
"title": "",
|
"title": ""
|
||||||
"greeting": "",
|
|
||||||
"intro": "",
|
|
||||||
"copyright": ""
|
|
||||||
},
|
},
|
||||||
"successes": {
|
"successes": {
|
||||||
"created": "",
|
"created": "",
|
||||||
@@ -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"
|
||||||
@@ -1823,6 +1834,7 @@
|
|||||||
"job": "",
|
"job": "",
|
||||||
"jobcosting": "",
|
"jobcosting": "",
|
||||||
"jobtotals": "",
|
"jobtotals": "",
|
||||||
|
"labor_hrs": "",
|
||||||
"labor_rates_subtotal": "",
|
"labor_rates_subtotal": "",
|
||||||
"laborallocations": "",
|
"laborallocations": "",
|
||||||
"labortotals": "",
|
"labortotals": "",
|
||||||
@@ -2417,6 +2429,7 @@
|
|||||||
"invoice_total_payable": "",
|
"invoice_total_payable": "",
|
||||||
"iou_form": "",
|
"iou_form": "",
|
||||||
"job_costing_ro": "",
|
"job_costing_ro": "",
|
||||||
|
"job_lifecycle_ro": "",
|
||||||
"job_notes": "",
|
"job_notes": "",
|
||||||
"key_tag": "",
|
"key_tag": "",
|
||||||
"labels": {
|
"labels": {
|
||||||
@@ -2583,17 +2596,17 @@
|
|||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"advanced_filters": "",
|
"advanced_filters": "",
|
||||||
"advanced_filters_show": "",
|
|
||||||
"advanced_filters_hide": "",
|
|
||||||
"advanced_filters_filters": "",
|
|
||||||
"advanced_filters_sorters": "",
|
|
||||||
"advanced_filters_filter_field": "",
|
|
||||||
"advanced_filters_sorter_field": "",
|
|
||||||
"advanced_filters_true": "",
|
|
||||||
"advanced_filters_false": "",
|
"advanced_filters_false": "",
|
||||||
"advanced_filters_sorter_direction": "",
|
"advanced_filters_filter_field": "",
|
||||||
"advanced_filters_filter_operator": "",
|
"advanced_filters_filter_operator": "",
|
||||||
"advanced_filters_filter_value": "",
|
"advanced_filters_filter_value": "",
|
||||||
|
"advanced_filters_filters": "",
|
||||||
|
"advanced_filters_hide": "",
|
||||||
|
"advanced_filters_show": "",
|
||||||
|
"advanced_filters_sorter_direction": "",
|
||||||
|
"advanced_filters_sorter_field": "",
|
||||||
|
"advanced_filters_sorters": "",
|
||||||
|
"advanced_filters_true": "",
|
||||||
"dates": "",
|
"dates": "",
|
||||||
"employee": "",
|
"employee": "",
|
||||||
"filterson": "",
|
"filterson": "",
|
||||||
@@ -2675,6 +2688,8 @@
|
|||||||
"job_costing_ro_date_summary": "",
|
"job_costing_ro_date_summary": "",
|
||||||
"job_costing_ro_estimator": "",
|
"job_costing_ro_estimator": "",
|
||||||
"job_costing_ro_ins_co": "",
|
"job_costing_ro_ins_co": "",
|
||||||
|
"job_lifecycle_date_detail": "",
|
||||||
|
"job_lifecycle_date_summary": "",
|
||||||
"jobs_completed_not_invoiced": "",
|
"jobs_completed_not_invoiced": "",
|
||||||
"jobs_invoiced_not_exported": "",
|
"jobs_invoiced_not_exported": "",
|
||||||
"jobs_reconcile": "",
|
"jobs_reconcile": "",
|
||||||
|
|||||||
@@ -259,6 +259,7 @@
|
|||||||
"saving": ""
|
"saving": ""
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"ReceivableCustomField": "",
|
||||||
"address1": "",
|
"address1": "",
|
||||||
"address2": "",
|
"address2": "",
|
||||||
"appt_alt_transport": "",
|
"appt_alt_transport": "",
|
||||||
@@ -477,7 +478,6 @@
|
|||||||
"editaccess": ""
|
"editaccess": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ReceivableCustomField": "",
|
|
||||||
"responsibilitycenter": "",
|
"responsibilitycenter": "",
|
||||||
"responsibilitycenter_accountdesc": "",
|
"responsibilitycenter_accountdesc": "",
|
||||||
"responsibilitycenter_accountitem": "",
|
"responsibilitycenter_accountitem": "",
|
||||||
@@ -844,8 +844,8 @@
|
|||||||
"notconfigured": "",
|
"notconfigured": "",
|
||||||
"notfoundsubtitle": "",
|
"notfoundsubtitle": "",
|
||||||
"notfoundtitle": "",
|
"notfoundtitle": "",
|
||||||
"surveycompletetitle": "",
|
"surveycompletesubtitle": "",
|
||||||
"surveycompletesubtitle": ""
|
"surveycompletetitle": ""
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"completedon": "",
|
"completedon": "",
|
||||||
@@ -854,13 +854,13 @@
|
|||||||
"validuntil": ""
|
"validuntil": ""
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
|
"copyright": "",
|
||||||
|
"greeting": "",
|
||||||
|
"intro": "",
|
||||||
"nologgedinuser": "",
|
"nologgedinuser": "",
|
||||||
"nologgedinuser_sub": "",
|
"nologgedinuser_sub": "",
|
||||||
"noneselected": "",
|
"noneselected": "",
|
||||||
"title": "",
|
"title": ""
|
||||||
"greeting": "",
|
|
||||||
"intro": "",
|
|
||||||
"copyright": ""
|
|
||||||
},
|
},
|
||||||
"successes": {
|
"successes": {
|
||||||
"created": "",
|
"created": "",
|
||||||
@@ -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"
|
||||||
@@ -1823,6 +1834,7 @@
|
|||||||
"job": "",
|
"job": "",
|
||||||
"jobcosting": "",
|
"jobcosting": "",
|
||||||
"jobtotals": "",
|
"jobtotals": "",
|
||||||
|
"labor_hrs": "",
|
||||||
"labor_rates_subtotal": "",
|
"labor_rates_subtotal": "",
|
||||||
"laborallocations": "",
|
"laborallocations": "",
|
||||||
"labortotals": "",
|
"labortotals": "",
|
||||||
@@ -2417,6 +2429,7 @@
|
|||||||
"invoice_total_payable": "",
|
"invoice_total_payable": "",
|
||||||
"iou_form": "",
|
"iou_form": "",
|
||||||
"job_costing_ro": "",
|
"job_costing_ro": "",
|
||||||
|
"job_lifecycle_ro": "",
|
||||||
"job_notes": "",
|
"job_notes": "",
|
||||||
"key_tag": "",
|
"key_tag": "",
|
||||||
"labels": {
|
"labels": {
|
||||||
@@ -2583,17 +2596,17 @@
|
|||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"advanced_filters": "",
|
"advanced_filters": "",
|
||||||
"advanced_filters_show": "",
|
|
||||||
"advanced_filters_hide": "",
|
|
||||||
"advanced_filters_filters": "",
|
|
||||||
"advanced_filters_sorters": "",
|
|
||||||
"advanced_filters_filter_field": "",
|
|
||||||
"advanced_filters_sorter_field": "",
|
|
||||||
"advanced_filters_true": "",
|
|
||||||
"advanced_filters_false": "",
|
"advanced_filters_false": "",
|
||||||
"advanced_filters_sorter_direction": "",
|
"advanced_filters_filter_field": "",
|
||||||
"advanced_filters_filter_operator": "",
|
"advanced_filters_filter_operator": "",
|
||||||
"advanced_filters_filter_value": "",
|
"advanced_filters_filter_value": "",
|
||||||
|
"advanced_filters_filters": "",
|
||||||
|
"advanced_filters_hide": "",
|
||||||
|
"advanced_filters_show": "",
|
||||||
|
"advanced_filters_sorter_direction": "",
|
||||||
|
"advanced_filters_sorter_field": "",
|
||||||
|
"advanced_filters_sorters": "",
|
||||||
|
"advanced_filters_true": "",
|
||||||
"dates": "",
|
"dates": "",
|
||||||
"employee": "",
|
"employee": "",
|
||||||
"filterson": "",
|
"filterson": "",
|
||||||
@@ -2675,6 +2688,8 @@
|
|||||||
"job_costing_ro_date_summary": "",
|
"job_costing_ro_date_summary": "",
|
||||||
"job_costing_ro_estimator": "",
|
"job_costing_ro_estimator": "",
|
||||||
"job_costing_ro_ins_co": "",
|
"job_costing_ro_ins_co": "",
|
||||||
|
"job_lifecycle_date_detail": "",
|
||||||
|
"job_lifecycle_date_summary": "",
|
||||||
"jobs_completed_not_invoiced": "",
|
"jobs_completed_not_invoiced": "",
|
||||||
"jobs_invoiced_not_exported": "",
|
"jobs_invoiced_not_exported": "",
|
||||||
"jobs_reconcile": "",
|
"jobs_reconcile": "",
|
||||||
|
|||||||
@@ -514,6 +514,14 @@ export const TemplateList = (type, context) => {
|
|||||||
group: "financial",
|
group: "financial",
|
||||||
dms: true,
|
dms: true,
|
||||||
},
|
},
|
||||||
|
job_lifecycle_ro: {
|
||||||
|
title: i18n.t("printcenter.jobs.job_lifecycle_ro"),
|
||||||
|
description: "",
|
||||||
|
subject: i18n.t("printcenter.jobs.job_lifecycle_ro"),
|
||||||
|
key: "job_lifecycle_ro",
|
||||||
|
disabled: false,
|
||||||
|
group: "post",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
...(!type || type === "job_special"
|
...(!type || type === "job_special"
|
||||||
@@ -2048,6 +2056,30 @@ export const TemplateList = (type, context) => {
|
|||||||
datedisable: true,
|
datedisable: true,
|
||||||
group: "customers",
|
group: "customers",
|
||||||
},
|
},
|
||||||
|
job_lifecycle_date_detail: {
|
||||||
|
title: i18n.t("reportcenter.templates.job_lifecycle_date_detail"),
|
||||||
|
subject: i18n.t("reportcenter.templates.job_lifecycle_date_detail"),
|
||||||
|
key: "job_lifecycle_date_detail",
|
||||||
|
//idtype: "vendor",
|
||||||
|
disabled: false,
|
||||||
|
rangeFilter: {
|
||||||
|
object: i18n.t("reportcenter.labels.objects.jobs"),
|
||||||
|
field: i18n.t("jobs.fields.date_invoiced"),
|
||||||
|
},
|
||||||
|
group: "jobs",
|
||||||
|
},
|
||||||
|
job_lifecycle_date_summary: {
|
||||||
|
title: i18n.t("reportcenter.templates.job_lifecycle_date_summary"),
|
||||||
|
subject: i18n.t("reportcenter.templates.job_lifecycle_date_summary"),
|
||||||
|
key: "job_lifecycle_date_summary",
|
||||||
|
//idtype: "vendor",
|
||||||
|
disabled: false,
|
||||||
|
rangeFilter: {
|
||||||
|
object: i18n.t("reportcenter.labels.objects.jobs"),
|
||||||
|
field: i18n.t("jobs.fields.date_invoiced"),
|
||||||
|
},
|
||||||
|
group: "jobs",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
...(!type || type === "courtesycarcontract"
|
...(!type || type === "courtesycarcontract"
|
||||||
|
|||||||
@@ -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:
|
||||||
@@ -4198,7 +4200,7 @@
|
|||||||
interval_sec: 10
|
interval_sec: 10
|
||||||
num_retries: 0
|
num_retries: 0
|
||||||
timeout_sec: 60
|
timeout_sec: 60
|
||||||
webhook_from_env: HASURA_API_URL
|
webhook: https://worktest.home.irony.online
|
||||||
headers:
|
headers:
|
||||||
- name: event-secret
|
- name: event-secret
|
||||||
value_from_env: EVENT_SECRET
|
value_from_env: EVENT_SECRET
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
-- Could not auto-generate a down migration.
|
||||||
|
-- Please write an appropriate down migration for the SQL below:
|
||||||
|
-- alter table "public"."ioevents" add column "useremail" text
|
||||||
|
-- not null;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
alter table "public"."ioevents" add column "useremail" text;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
-- Could not auto-generate a down migration.
|
||||||
|
-- Please write an appropriate down migration for the SQL below:
|
||||||
|
-- alter table "public"."ioevents" add column "bodyshopid" uuid
|
||||||
|
-- null;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
alter table "public"."ioevents" add column "bodyshopid" uuid
|
||||||
|
null;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
alter table "public"."ioevents" alter column "useremail" set not null;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
alter table "public"."ioevents" alter column "useremail" drop not null;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
-- Could not auto-generate a down migration.
|
||||||
|
-- Please write an appropriate down migration for the SQL below:
|
||||||
|
-- alter table "public"."ioevents" add column "env" text
|
||||||
|
-- null;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
alter table "public"."ioevents" add column "env" text
|
||||||
|
null;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
DROP INDEX IF EXISTS "public"."ioevents_useremail";
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
CREATE INDEX "ioevents_useremail" on
|
||||||
|
"public"."ioevents" using btree ("useremail");
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
alter table "public"."ioevents" drop constraint "ioevents_useremail_fkey";
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
alter table "public"."ioevents"
|
||||||
|
add constraint "ioevents_useremail_fkey"
|
||||||
|
foreign key ("useremail")
|
||||||
|
references "public"."users"
|
||||||
|
("email") on update set null on delete set null;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
alter table "public"."ioevents" drop constraint "ioevents_bodyshopid_fkey";
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
alter table "public"."ioevents"
|
||||||
|
add constraint "ioevents_bodyshopid_fkey"
|
||||||
|
foreign key ("bodyshopid")
|
||||||
|
references "public"."bodyshops"
|
||||||
|
("id") on update set null on delete set null;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
DROP INDEX IF EXISTS "public"."idx_audit_trail_type";
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
CREATE INDEX "idx_audit_trail_type" on
|
||||||
|
"public"."audit_trail" using btree ("type");
|
||||||
@@ -201,19 +201,24 @@ exports.default = async (req, res) => {
|
|||||||
} finally {
|
} finally {
|
||||||
sftp.end();
|
sftp.end();
|
||||||
}
|
}
|
||||||
sendServerEmail({
|
// sendServerEmail({
|
||||||
subject: `Kaizen Report ${moment().format("MM-DD-YY")}`,
|
// subject: `Kaizen Report ${moment().format("MM-DD-YY")}`,
|
||||||
text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}
|
// text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}
|
||||||
Uploaded: ${JSON.stringify(
|
// Uploaded: ${JSON.stringify(
|
||||||
allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count })),
|
// allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count })),
|
||||||
null,
|
// null,
|
||||||
2
|
// 2
|
||||||
)}
|
// )}
|
||||||
`,
|
// `,
|
||||||
});
|
// });
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(200).json(error);
|
res.status(200).json(error);
|
||||||
|
sendServerEmail({
|
||||||
|
subject: `Kaizen Report ${moment().format("MM-DD-YY @ HH:mm:ss")}`,
|
||||||
|
text: `Errors: JSON.stringify(error)}
|
||||||
|
All Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}`,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -11,27 +11,40 @@ require("dotenv").config({
|
|||||||
});
|
});
|
||||||
|
|
||||||
exports.default = async (req, res) => {
|
exports.default = async (req, res) => {
|
||||||
const { operationName, time, dbevent, user, imexshopid } = req.body;
|
const {
|
||||||
|
useremail,
|
||||||
|
bodyshopid,
|
||||||
|
operationName,
|
||||||
|
variables,
|
||||||
|
env,
|
||||||
|
time,
|
||||||
|
dbevent,
|
||||||
|
user,
|
||||||
|
} = req.body;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// await client.request(queries.INSERT_IOEVENT, {
|
await client.request(queries.INSERT_IOEVENT, {
|
||||||
// event: {
|
event: {
|
||||||
// operationname: operationName,
|
operationname: operationName,
|
||||||
// time,
|
time,
|
||||||
// dbevent,
|
dbevent,
|
||||||
// },
|
env,
|
||||||
// });
|
variables,
|
||||||
console.log("IOEVENT", operationName, time, dbevent, user, imexshopid);
|
bodyshopid,
|
||||||
logger.log("ioevent", "trace", user, null, {
|
useremail,
|
||||||
imexshopid,
|
},
|
||||||
operationName,
|
|
||||||
time,
|
|
||||||
dbevent,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("error", error);
|
logger.log("ioevent-error", "trace", user, null, {
|
||||||
res.status(400).send(error);
|
operationname: operationName,
|
||||||
|
time,
|
||||||
|
dbevent,
|
||||||
|
env,
|
||||||
|
variables,
|
||||||
|
bodyshopid,
|
||||||
|
useremail,
|
||||||
|
});
|
||||||
|
res.sendStatus(200);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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