IO-1477 Updated parts queue page.

This commit is contained in:
Patrick Fic
2022-04-18 13:53:21 -07:00
parent c08713bfbe
commit 988c3a9f22
8 changed files with 209 additions and 71 deletions

View File

@@ -23189,6 +23189,27 @@
</concept_node> </concept_node>
</children> </children>
</folder_node> </folder_node>
<concept_node>
<name>partsstatus</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>pas</name> <name>pas</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -23341,6 +23362,27 @@
</concept_node> </concept_node>
</children> </children>
</folder_node> </folder_node>
<concept_node>
<name>queued_for_parts</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>rate_ats</name> <name>rate_ats</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>

View File

@@ -0,0 +1,76 @@
import React, { useMemo } from "react";
import { Row, Col, Tag, Tooltip } from "antd";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(JobPartsQueueCount);
export function JobPartsQueueCount({ bodyshop, parts }) {
console.log(parts);
const partsStatus = useMemo(() => {
return parts.reduce(
(acc, val) => {
acc.total = acc.total + val.count;
acc[val.status] = acc[val.status] + val.count;
return acc;
},
{
total: 0,
[bodyshop.md_order_statuses.default_bo]: 0,
[bodyshop.md_order_statuses.default_ordered]: 0,
[bodyshop.md_order_statuses.default_received]: 0,
[bodyshop.md_order_statuses.default_returned]: 0,
}
);
}, [bodyshop, parts]);
console.log(
"🚀 ~ file: job-parts-queue-count.component.jsx ~ line 8 ~ partsStatus",
partsStatus
);
return (
<Row>
<Col span={5}>
<Tooltip title="Total">
<Tag>{partsStatus.total}</Tag>
</Tooltip>
</Col>
<Col span={5}>
<Tooltip title={bodyshop.md_order_statuses.default_ordered}>
<Tag color="blue">
{partsStatus[bodyshop.md_order_statuses.default_ordered]}
</Tag>
</Tooltip>
</Col>
<Col span={5}>
<Tooltip title={bodyshop.md_order_statuses.default_received}>
<Tag color="green">
{partsStatus[bodyshop.md_order_statuses.default_received]}
</Tag>
</Tooltip>
</Col>
<Col span={5}>
<Tooltip title={bodyshop.md_order_statuses.default_returned}>
<Tag color="orange">
{partsStatus[bodyshop.md_order_statuses.default_returned]}
</Tag>
</Tooltip>
</Col>
<Col span={4}>
<Tooltip title={bodyshop.md_order_statuses.default_bo}>
<Tag color="red">
{partsStatus[bodyshop.md_order_statuses.default_bo]}
</Tag>
</Tooltip>
</Col>
</Row>
);
}

View File

@@ -1,24 +1,23 @@
import { Button, notification } from "antd";
import React, { useState } from "react";
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { UPDATE_JOB } from "../../graphql/jobs.queries"; import { Checkbox, notification, Space, Spin } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
export default function JobRemoveFromPartsQueue({ jobId, refetch }) { export default function JobRemoveFromPartsQueue({ checked, jobId }) {
const [updateJob] = useMutation(UPDATE_JOB); const [updateJob] = useMutation(UPDATE_JOB);
const { t } = useTranslation(); const { t } = useTranslation();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const handleClick = async (e) => { const handleChange = async (e) => {
setLoading(true); setLoading(true);
const result = await updateJob({ const result = await updateJob({
variables: { jobId: jobId, job: { queued_for_parts: false } }, variables: { jobId: jobId, job: { queued_for_parts: e.target.checked } },
}); });
if (!!!result.errors) { if (!!!result.errors) {
notification["success"]({ message: t("jobs.successes.save") }); notification["success"]({ message: t("jobs.successes.save") });
if (refetch) refetch();
} else { } else {
notification["error"]({ notification["error"]({
message: t("jobs.errors.saving", { message: t("jobs.errors.saving", {
@@ -30,8 +29,9 @@ export default function JobRemoveFromPartsQueue({ jobId, refetch }) {
}; };
return ( return (
<Button onClick={handleClick} loading={loading}> <Space>
{t("general.actions.remove")} <Checkbox checked={checked} onChange={handleChange} />
</Button> {loading && <Spin size="small" />}
</Space>
); );
} }

View File

@@ -50,25 +50,13 @@ export const QUERY_PARTS_QUEUE = gql`
$limit: Int $limit: Int
$order: [jobs_order_by!] $order: [jobs_order_by!]
) { ) {
jobs_aggregate( jobs_aggregate(where: { _and: [{ status: { _in: $statuses } }] }) {
where: {
_and: [
{ status: { _in: $statuses } }
{ queued_for_parts: { _eq: true } }
]
}
) {
aggregate { aggregate {
count(distinct: true) count(distinct: true)
} }
} }
jobs( jobs(
where: { where: { _and: [{ status: { _in: $statuses } }] }
_and: [
{ status: { _in: $statuses } }
{ queued_for_parts: { _eq: true } }
]
}
offset: $offset offset: $offset
limit: $limit limit: $limit
order_by: $order order_by: $order
@@ -99,6 +87,12 @@ export const QUERY_PARTS_QUEUE = gql`
updated_at updated_at
vehicleid vehicleid
ownerid ownerid
queued_for_parts
joblines_status {
count
part_type
status
}
} }
} }
`; `;
@@ -1050,6 +1044,7 @@ export const UPDATE_JOB = gql`
production_vars production_vars
lbr_adjustments lbr_adjustments
suspended suspended
queued_for_parts
} }
} }
} }

View File

@@ -1,23 +1,22 @@
import { SyncOutlined } from "@ant-design/icons"; import { SyncOutlined } from "@ant-design/icons";
import { useQuery } from "@apollo/client"; import { useQuery } from "@apollo/client";
import { Button, Card, Input, Space, Table } from "antd"; import { Button, Card, Input, Space, Table } from "antd";
import _ from "lodash";
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 { connect } from "react-redux"; import { connect } from "react-redux";
import { Link, useHistory } from "react-router-dom"; import { Link, useHistory, useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import AlertComponent from "../../components/alert/alert.component"; import AlertComponent from "../../components/alert/alert.component";
import JobPartsQueueCount from "../../components/job-parts-queue-count/job-parts-queue-count.component";
import JobRemoveFromPartsQueue from "../../components/job-remove-from-parst-queue/job-remove-from-parts-queue.component"; import JobRemoveFromPartsQueue from "../../components/job-remove-from-parst-queue/job-remove-from-parts-queue.component";
import OwnerNameDisplay from "../../components/owner-name-display/owner-name-display.component";
import { QUERY_PARTS_QUEUE } from "../../graphql/jobs.queries"; import { QUERY_PARTS_QUEUE } from "../../graphql/jobs.queries";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import { onlyUnique } from "../../utils/arrayHelper"; import { onlyUnique } from "../../utils/arrayHelper";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { TimeAgoFormatter } from "../../utils/DateFormatter"; import { TimeAgoFormatter } from "../../utils/DateFormatter";
import { alphaSort } from "../../utils/sorters"; import { alphaSort, dateSort } from "../../utils/sorters";
import { useLocation } from "react-router-dom";
import queryString from "query-string";
import _ from "lodash";
import OwnerNameDisplay from "../../components/owner-name-display/owner-name-display.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -25,20 +24,25 @@ const mapStateToProps = createStructuredSelector({
export function PartsQueuePageComponent({ bodyshop }) { export function PartsQueuePageComponent({ bodyshop }) {
const searchParams = queryString.parse(useLocation().search); const searchParams = queryString.parse(useLocation().search);
const { page, sortcolumn, sortorder, statusFilters } = searchParams; const {
//page,
sortcolumn,
sortorder,
statusFilters,
} = searchParams;
const history = useHistory(); const history = useHistory();
const { loading, error, data, refetch } = useQuery(QUERY_PARTS_QUEUE, { const { loading, error, data, refetch } = useQuery(QUERY_PARTS_QUEUE, {
fetchPolicy: "network-only", fetchPolicy: "network-only",
nextFetchPolicy: "network-only", nextFetchPolicy: "network-only",
variables: { variables: {
offset: page ? (page - 1) * 25 : 0, // offset: page ? (page - 1) * 25 : 0,
limit: 25, // limit: 25,
statuses: (statusFilters && JSON.parse(statusFilters)) || statuses: (statusFilters && JSON.parse(statusFilters)) ||
bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"], bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"],
order: [ order: [
{ {
[sortcolumn || "updated_at"]: sortorder [sortcolumn || "ro_number"]: sortorder
? sortorder === "descend" ? sortorder === "descend"
? "desc" ? "desc"
: "asc" : "asc"
@@ -85,7 +89,7 @@ export function PartsQueuePageComponent({ bodyshop }) {
: []; : [];
const handleTableChange = (pagination, filters, sorter) => { const handleTableChange = (pagination, filters, sorter) => {
searchParams.page = pagination.current; // searchParams.page = pagination.current;
searchParams.sortcolumn = sorter.columnKey; searchParams.sortcolumn = sorter.columnKey;
searchParams.sortorder = sorter.order; searchParams.sortorder = sorter.order;
if (filters.status) { if (filters.status) {
@@ -114,10 +118,10 @@ export function PartsQueuePageComponent({ bodyshop }) {
}, },
{ {
title: t("jobs.fields.owner"), title: t("jobs.fields.owner"),
dataIndex: "owner", dataIndex: "ownr_ln",
key: "owner", key: "ownr_ln",
// sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln), sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
// sortOrder: sortcolumn === "owner" && sortorder, sortOrder: sortcolumn === "ownr_ln" && sortorder,
render: (text, record) => { render: (text, record) => {
return record.ownerid ? ( return record.ownerid ? (
<Link to={"/manage/owners/" + record.ownerid}> <Link to={"/manage/owners/" + record.ownerid}>
@@ -173,16 +177,16 @@ export function PartsQueuePageComponent({ bodyshop }) {
); );
}, },
}, },
{ // {
title: t("vehicles.fields.plate_no"), // title: t("vehicles.fields.plate_no"),
dataIndex: "plate_no", // dataIndex: "plate_no",
key: "plate_no", // key: "plate_no",
sorter: (a, b) => alphaSort(a.plate_no, b.plate_no), // sorter: (a, b) => alphaSort(a.plate_no, b.plate_no),
sortOrder: sortcolumn === "plate_no" && sortorder, // sortOrder: sortcolumn === "plate_no" && sortorder,
render: (text, record) => { // render: (text, record) => {
return record.plate_no ? record.plate_no : ""; // return record.plate_no ? record.plate_no : "";
}, // },
}, // },
{ {
title: t("jobs.fields.clm_no"), title: t("jobs.fields.clm_no"),
dataIndex: "clm_no", dataIndex: "clm_no",
@@ -198,34 +202,49 @@ export function PartsQueuePageComponent({ bodyshop }) {
); );
}, },
}, },
{ // {
title: t("jobs.fields.clm_total"), // title: t("jobs.fields.clm_total"),
dataIndex: "clm_total", // dataIndex: "clm_total",
key: "clm_total", // key: "clm_total",
sorter: (a, b) => a.clm_total - b.clm_total, // sorter: (a, b) => a.clm_total - b.clm_total,
sortOrder: sortcolumn === "clm_total" && sortorder, // sortOrder: sortcolumn === "clm_total" && sortorder,
render: (text, record) => { // render: (text, record) => {
return record.clm_total ? ( // return record.clm_total ? (
<CurrencyFormatter>{record.clm_total}</CurrencyFormatter> // <CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
) : ( // ) : (
t("general.labels.unknown") // t("general.labels.unknown")
); // );
}, // },
}, // },
{ {
title: t("jobs.fields.updated_at"), title: t("jobs.fields.updated_at"),
dataIndex: "updated_at", dataIndex: "updated_at",
key: "updated_at", key: "updated_at",
sorter: (a, b) => dateSort(a.updated_at, b.updated_at),
sortOrder: sortcolumn === "updated_at" && sortorder,
render: (text, record) => ( render: (text, record) => (
<TimeAgoFormatter>{record.updated_at}</TimeAgoFormatter> <TimeAgoFormatter>{record.updated_at}</TimeAgoFormatter>
), ),
}, },
{ {
title: t("general.labels.actions"), title: t("jobs.fields.partsstatus"),
dataIndex: "actions", dataIndex: "partsstatus",
key: "actions", key: "partsstatus",
render: (text, record) => ( render: (text, record) => (
<JobRemoveFromPartsQueue jobId={record.id} refetch={refetch} /> <JobPartsQueueCount parts={record.joblines_status} />
),
},
{
title: t("jobs.fields.queued_for_parts"),
dataIndex: "queued_for_parts",
key: "queued_for_parts",
sorter: (a, b) => a.queued_for_parts - b.queued_for_parts,
sortOrder: sortcolumn === "queued_for_parts" && sortorder,
render: (text, record) => (
<JobRemoveFromPartsQueue
checked={record.queued_for_parts}
jobId={record.id}
/>
), ),
}, },
]; ];
@@ -253,9 +272,9 @@ export function PartsQueuePageComponent({ bodyshop }) {
loading={loading} loading={loading}
pagination={{ pagination={{
position: "top", position: "top",
pageSize: 25, pageSize: 50,
current: parseInt(page || 1), // current: parseInt(page || 1),
total: data && data.jobs_aggregate.aggregate.count, // total: data && data.jobs_aggregate.aggregate.count,
}} }}
columns={columns} columns={columns}
rowKey="id" rowKey="id"

View File

@@ -1387,6 +1387,7 @@
"prt_tax_rt": "Part Tax Rate", "prt_tax_rt": "Part Tax Rate",
"prt_type": "Part Type" "prt_type": "Part Type"
}, },
"partsstatus": "Parts Status",
"pas": "Sublet", "pas": "Sublet",
"pay_date": "Pay Date", "pay_date": "Pay Date",
"phoneshort": "PH", "phoneshort": "PH",
@@ -1396,6 +1397,7 @@
"production_vars": { "production_vars": {
"note": "Production Note" "note": "Production Note"
}, },
"queued_for_parts": "Queued for Parts",
"rate_ats": "ATS Rate", "rate_ats": "ATS Rate",
"rate_la1": "LA1", "rate_la1": "LA1",
"rate_la2": "LA2", "rate_la2": "LA2",

View File

@@ -1387,6 +1387,7 @@
"prt_tax_rt": "", "prt_tax_rt": "",
"prt_type": "" "prt_type": ""
}, },
"partsstatus": "",
"pas": "", "pas": "",
"pay_date": "Fecha de Pay", "pay_date": "Fecha de Pay",
"phoneshort": "PH", "phoneshort": "PH",
@@ -1396,6 +1397,7 @@
"production_vars": { "production_vars": {
"note": "" "note": ""
}, },
"queued_for_parts": "",
"rate_ats": "", "rate_ats": "",
"rate_la1": "Tarifa LA1", "rate_la1": "Tarifa LA1",
"rate_la2": "Tarifa LA2", "rate_la2": "Tarifa LA2",

View File

@@ -1387,6 +1387,7 @@
"prt_tax_rt": "", "prt_tax_rt": "",
"prt_type": "" "prt_type": ""
}, },
"partsstatus": "",
"pas": "", "pas": "",
"pay_date": "Date d'Pay", "pay_date": "Date d'Pay",
"phoneshort": "PH", "phoneshort": "PH",
@@ -1396,6 +1397,7 @@
"production_vars": { "production_vars": {
"note": "" "note": ""
}, },
"queued_for_parts": "",
"rate_ats": "", "rate_ats": "",
"rate_la1": "Taux LA1", "rate_la1": "Taux LA1",
"rate_la2": "Taux LA2", "rate_la2": "Taux LA2",