IO-3532 Resolve parts queue pages.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useMemo } from "react";
|
||||
import { Tag, Tooltip } from "antd";
|
||||
import { Tooltip } from "antd";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
@@ -12,6 +12,40 @@ const mapDispatchToProps = () => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
|
||||
const colorMap = {
|
||||
gray: { bg: "#fafafa", border: "#d9d9d9", text: "#000000" },
|
||||
gold: { bg: "#fffbe6", border: "#ffe58f", text: "#d48806" },
|
||||
red: { bg: "#fff1f0", border: "#ffccc7", text: "#cf1322" },
|
||||
blue: { bg: "#e6f7ff", border: "#91d5ff", text: "#0958d9" },
|
||||
green: { bg: "#f6ffed", border: "#b7eb8f", text: "#389e0d" },
|
||||
orange: { bg: "#fff7e6", border: "#ffd591", text: "#d46b08" }
|
||||
};
|
||||
|
||||
function CompactTag({ color = "gray", children }) {
|
||||
const colors = colorMap[color] || colorMap.gray;
|
||||
return (
|
||||
<span
|
||||
style={{
|
||||
display: "inline-flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
padding: "0 2px",
|
||||
fontSize: "12px",
|
||||
lineHeight: "20px",
|
||||
backgroundColor: colors.bg,
|
||||
border: `1px solid ${colors.border}`,
|
||||
borderRadius: "2px",
|
||||
color: colors.text,
|
||||
//width: "100%",
|
||||
minWidth: "24px",
|
||||
textAlign: "center"
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(JobPartsQueueCount);
|
||||
|
||||
export function JobPartsQueueCount({ bodyshop, parts }) {
|
||||
@@ -36,42 +70,25 @@ export function JobPartsQueueCount({ bodyshop, parts }) {
|
||||
|
||||
if (!parts) return null;
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(auto-fit, minmax(40px, 1fr))",
|
||||
gap: "8px",
|
||||
width: "100%",
|
||||
justifyItems: "start"
|
||||
}}
|
||||
>
|
||||
<div style={{ display: "flex", gap: 2 }}>
|
||||
<Tooltip title="Total">
|
||||
<Tag style={{ minWidth: "40px", textAlign: "center" }}>{partsStatus.total}</Tag>
|
||||
<CompactTag color="gray">{partsStatus.total}</CompactTag>
|
||||
</Tooltip>
|
||||
<Tooltip title={t("dashboard.errors.status_normal")}>
|
||||
<Tag color="gold" style={{ minWidth: "40px", textAlign: "center" }}>
|
||||
{partsStatus["null"]}
|
||||
</Tag>
|
||||
<CompactTag color="gold">{partsStatus["null"]}</CompactTag>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title={bodyshop.md_order_statuses.default_bo}>
|
||||
<Tag color="red" style={{ minWidth: "40px", textAlign: "center" }}>
|
||||
{partsStatus[bodyshop.md_order_statuses.default_bo]}
|
||||
</Tag>
|
||||
<CompactTag color="red">{partsStatus[bodyshop.md_order_statuses.default_bo]}</CompactTag>
|
||||
</Tooltip>
|
||||
<Tooltip title={bodyshop.md_order_statuses.default_ordered}>
|
||||
<Tag color="blue" style={{ minWidth: "40px", textAlign: "center" }}>
|
||||
{partsStatus[bodyshop.md_order_statuses.default_ordered]}
|
||||
</Tag>
|
||||
<CompactTag color="blue">{partsStatus[bodyshop.md_order_statuses.default_ordered]}</CompactTag>
|
||||
</Tooltip>
|
||||
<Tooltip title={bodyshop.md_order_statuses.default_received}>
|
||||
<Tag color="green" style={{ minWidth: "40px", textAlign: "center" }}>
|
||||
{partsStatus[bodyshop.md_order_statuses.default_received]}
|
||||
</Tag>
|
||||
<CompactTag color="green">{partsStatus[bodyshop.md_order_statuses.default_received]}</CompactTag>
|
||||
</Tooltip>
|
||||
<Tooltip title={bodyshop.md_order_statuses.default_returned}>
|
||||
<Tag color="orange" style={{ minWidth: "40px", textAlign: "center" }}>
|
||||
{partsStatus[bodyshop.md_order_statuses.default_returned]}
|
||||
</Tag>
|
||||
<CompactTag color="orange">{partsStatus[bodyshop.md_order_statuses.default_returned]}</CompactTag>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -18,10 +18,17 @@ const mapStateToProps = createStructuredSelector({
|
||||
* @param parts
|
||||
* @param displayMode
|
||||
* @param popoverPlacement
|
||||
* @param countsOnly
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
export function JobPartsReceived({ bodyshop, parts, displayMode = "full", popoverPlacement = "top" }) {
|
||||
export function JobPartsReceived({
|
||||
bodyshop,
|
||||
parts,
|
||||
displayMode = "full",
|
||||
popoverPlacement = "top",
|
||||
countsOnly = false
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -61,6 +68,8 @@ export function JobPartsReceived({ bodyshop, parts, displayMode = "full", popove
|
||||
[canOpen]
|
||||
);
|
||||
|
||||
if (countsOnly) return <JobPartsQueueCount parts={parts} />;
|
||||
|
||||
const displayText =
|
||||
displayMode === "compact" ? summary.percentLabel : `${summary.percentLabel} (${summary.received}/${summary.total})`;
|
||||
|
||||
@@ -99,7 +108,8 @@ JobPartsReceived.propTypes = {
|
||||
bodyshop: PropTypes.object,
|
||||
parts: PropTypes.array,
|
||||
displayMode: PropTypes.oneOf(["full", "compact"]),
|
||||
popoverPlacement: PropTypes.string
|
||||
popoverPlacement: PropTypes.string,
|
||||
countsOnly: PropTypes.bool
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(JobPartsReceived);
|
||||
|
||||
@@ -2,6 +2,7 @@ import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { store } from "../../redux/store";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { Tooltip } from "antd";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
@@ -11,15 +12,23 @@ const mapDispatchToProps = () => ({
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(OwnerNameDisplay);
|
||||
|
||||
export function OwnerNameDisplay({ bodyshop, ownerObject }) {
|
||||
export function OwnerNameDisplay({ bodyshop, ownerObject, withToolTip = false }) {
|
||||
const emptyTest = ownerObject?.ownr_fn + ownerObject?.ownr_ln + ownerObject?.ownr_co_nm;
|
||||
|
||||
if (!emptyTest || emptyTest === "null" || emptyTest.trim() === "") return "N/A";
|
||||
|
||||
if (bodyshop.last_name_first)
|
||||
return `${ownerObject?.ownr_ln || ""}, ${ownerObject?.ownr_fn || ""} ${ownerObject?.ownr_co_nm || ""}`.trim();
|
||||
|
||||
return `${ownerObject?.ownr_fn || ""} ${ownerObject?.ownr_ln || ""} ${ownerObject.ownr_co_nm || ""}`.trim();
|
||||
let returnString;
|
||||
if (bodyshop.last_name_first) {
|
||||
returnString =
|
||||
`${ownerObject?.ownr_ln || ""}, ${ownerObject?.ownr_fn || ""} ${ownerObject?.ownr_co_nm || ""}`.trim();
|
||||
} else {
|
||||
return `${ownerObject?.ownr_fn || ""} ${ownerObject?.ownr_ln || ""} ${ownerObject.ownr_co_nm || ""}`.trim();
|
||||
}
|
||||
if (withToolTip) {
|
||||
return <Tooltip title={returnString}>{returnString}</Tooltip>;
|
||||
} else {
|
||||
return returnString;
|
||||
}
|
||||
}
|
||||
|
||||
export function OwnerNameDisplayFunction(ownerObject, forceFirstLast = false) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { SyncOutlined } from "@ant-design/icons";
|
||||
import { useQuery } from "@apollo/client/react";
|
||||
import { Button, Card, Input, Space, Table } from "antd";
|
||||
import { Button, Card, Checkbox, Input, Space, Table } from "antd";
|
||||
import _ from "lodash";
|
||||
import queryString from "query-string";
|
||||
import { useState } from "react";
|
||||
@@ -31,6 +31,8 @@ export function PartsQueueListComponent({ bodyshop }) {
|
||||
const { selected, sortcolumn, sortorder, statusFilters } = searchParams;
|
||||
const history = useNavigate();
|
||||
const [filter, setFilter] = useLocalStorage("filter_parts_queue", null);
|
||||
const [viewTimeStamp, setViewTimeStamp] = useLocalStorage("parts_queue_timestamps", false);
|
||||
const [countsOnly, setCountsOnly] = useLocalStorage("parts_queue_counts_only", false);
|
||||
|
||||
const { loading, error, data, refetch } = useQuery(QUERY_PARTS_QUEUE, {
|
||||
fetchPolicy: "network-only",
|
||||
@@ -92,6 +94,7 @@ export function PartsQueueListComponent({ bodyshop }) {
|
||||
title: t("jobs.fields.ro_number"),
|
||||
dataIndex: "ro_number",
|
||||
key: "ro_number",
|
||||
width: "110px",
|
||||
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
|
||||
sortOrder: sortcolumn === "ro_number" && sortorder,
|
||||
|
||||
@@ -103,16 +106,20 @@ export function PartsQueueListComponent({ bodyshop }) {
|
||||
title: t("jobs.fields.owner"),
|
||||
dataIndex: "ownr_ln",
|
||||
key: "ownr_ln",
|
||||
width: "8%",
|
||||
ellipsis: {
|
||||
showTitle: true
|
||||
},
|
||||
sorter: (a, b) => alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)),
|
||||
sortOrder: sortcolumn === "ownr_ln" && sortorder,
|
||||
render: (text, record) => {
|
||||
return record.ownerid ? (
|
||||
<Link to={"/manage/owners/" + record.ownerid}>
|
||||
<OwnerNameDisplay ownerObject={record} />
|
||||
<OwnerNameDisplay ownerObject={record} withToolTip />
|
||||
</Link>
|
||||
) : (
|
||||
<span>
|
||||
<OwnerNameDisplay ownerObject={record} />
|
||||
<OwnerNameDisplay ownerObject={record} withToolTip />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -187,7 +194,7 @@ export function PartsQueueListComponent({ bodyshop }) {
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => dateSort(a.scheduled_in, b.scheduled_in),
|
||||
sortOrder: sortcolumn === "scheduled_in" && sortorder,
|
||||
render: (text, record) => <DateTimeFormatter>{record.scheduled_in}</DateTimeFormatter>
|
||||
render: (text, record) => <DateTimeFormatter hideTime={!viewTimeStamp}>{record.scheduled_in}</DateTimeFormatter>
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.scheduled_completion"),
|
||||
@@ -196,7 +203,9 @@ export function PartsQueueListComponent({ bodyshop }) {
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => dateSort(a.scheduled_completion, b.scheduled_completion),
|
||||
sortOrder: sortcolumn === "scheduled_completion" && sortorder,
|
||||
render: (text, record) => <DateTimeFormatter>{record.scheduled_completion}</DateTimeFormatter>
|
||||
render: (text, record) => (
|
||||
<DateTimeFormatter hideTime={!viewTimeStamp}>{record.scheduled_completion}</DateTimeFormatter>
|
||||
)
|
||||
},
|
||||
// {
|
||||
// title: t("vehicles.fields.plate_no"),
|
||||
@@ -227,16 +236,23 @@ export function PartsQueueListComponent({ bodyshop }) {
|
||||
title: t("jobs.fields.updated_at"),
|
||||
dataIndex: "updated_at",
|
||||
key: "updated_at",
|
||||
width: "110px",
|
||||
sorter: (a, b) => dateSort(a.updated_at, b.updated_at),
|
||||
sortOrder: sortcolumn === "updated_at" && sortorder,
|
||||
render: (text, record) => <TimeAgoFormatter>{record.updated_at}</TimeAgoFormatter>
|
||||
render: (text, record) => <TimeAgoFormatter removeAgoString>{record.updated_at}</TimeAgoFormatter>
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.partsstatus"),
|
||||
dataIndex: "partsstatus",
|
||||
key: "partsstatus",
|
||||
width: countsOnly ? "180px" : "110px",
|
||||
render: (text, record) => (
|
||||
<JobPartsReceived parts={record.joblines_status} displayMode="full" popoverPlacement="topLeft" />
|
||||
<JobPartsReceived
|
||||
parts={record.joblines_status}
|
||||
displayMode="full"
|
||||
popoverPlacement="middle"
|
||||
countsOnly={countsOnly}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
@@ -249,6 +265,7 @@ export function PartsQueueListComponent({ bodyshop }) {
|
||||
title: t("jobs.fields.queued_for_parts"),
|
||||
dataIndex: "queued_for_parts",
|
||||
key: "queued_for_parts",
|
||||
width: "120px",
|
||||
sorter: (a, b) => a.queued_for_parts - b.queued_for_parts,
|
||||
sortOrder: sortcolumn === "queued_for_parts" && sortorder,
|
||||
filteredValue: filter?.queued_for_parts || null,
|
||||
@@ -275,6 +292,12 @@ export function PartsQueueListComponent({ bodyshop }) {
|
||||
<Button onClick={() => refetch()}>
|
||||
<SyncOutlined />
|
||||
</Button>
|
||||
<Checkbox checked={countsOnly} onChange={(e) => setCountsOnly(e.target.checked)}>
|
||||
{t("parts.labels.view_counts_only")}
|
||||
</Checkbox>
|
||||
<Checkbox checked={viewTimeStamp} onChange={(e) => setViewTimeStamp(e.target.checked)}>
|
||||
{t("parts.labels.view_timestamps")}
|
||||
</Checkbox>
|
||||
<Input.Search
|
||||
className="imex-table-header__search"
|
||||
placeholder={t("general.labels.search")}
|
||||
@@ -299,7 +322,7 @@ export function PartsQueueListComponent({ bodyshop }) {
|
||||
rowKey="id"
|
||||
dataSource={jobs}
|
||||
style={{ height: "100%" }}
|
||||
scroll={{ x: true }}
|
||||
//scroll={{ x: true }}
|
||||
onChange={handleTableChange}
|
||||
rowSelection={{
|
||||
onSelect: (record) => {
|
||||
|
||||
Reference in New Issue
Block a user