Added closed date to jobs and reformatted some items
This commit is contained in:
@@ -3,6 +3,7 @@ import React, { useMemo } from "react";
|
||||
import { Cell, Pie, PieChart, ResponsiveContainer } from "recharts";
|
||||
import ErrorResultAtom from "../error-result/error-result.atom";
|
||||
import Dinero from "dinero.js";
|
||||
import partTypeConverterAtom from "../part-type-converter/part-type-converter.atom";
|
||||
export default function JobPartsGraphAtom({
|
||||
job,
|
||||
loading,
|
||||
@@ -35,24 +36,31 @@ export default function JobPartsGraphAtom({
|
||||
|
||||
if (loading) return <Skeleton active />;
|
||||
if (!job) return <ErrorResultAtom title="Error displaying job data." />;
|
||||
|
||||
console.log("data", data);
|
||||
return (
|
||||
<div
|
||||
style={{ display: "flex", flexDirection: "column", alignItems: "center" }}
|
||||
style={{
|
||||
display: "flex",
|
||||
flex: 1,
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Typography.Title level={4}>
|
||||
{price === "act_price" ? "Actual Price" : "Database Price"}
|
||||
</Typography.Title>
|
||||
<ResponsiveContainer width="100%" height={250}>
|
||||
<ResponsiveContainer>
|
||||
<PieChart>
|
||||
<Pie
|
||||
data={data}
|
||||
innerRadius={40}
|
||||
outerRadius={50}
|
||||
outerRadius={80}
|
||||
fill="#8884d8"
|
||||
paddingAngle={5}
|
||||
// paddingAngle={5}
|
||||
dataKey="value"
|
||||
label={(entry) => `${entry.name} - ${entry.label}`}
|
||||
label={(entry) =>
|
||||
`${partTypeConverterAtom(entry.name)} (${entry.label})`
|
||||
}
|
||||
labelLine
|
||||
>
|
||||
{data.map((entry, index) => (
|
||||
@@ -68,7 +76,7 @@ export default function JobPartsGraphAtom({
|
||||
const getColor = (key) => {
|
||||
switch (key) {
|
||||
case "PAA":
|
||||
return "tomato";
|
||||
return "slategray";
|
||||
case "PAL":
|
||||
return "dodgeblue";
|
||||
case "PAN":
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
export default (part_type) => {
|
||||
switch (part_type) {
|
||||
case "PAA":
|
||||
return "Aftermarket";
|
||||
case "PAE":
|
||||
return "Existing";
|
||||
case "PAN":
|
||||
return "OEM";
|
||||
case "PAL":
|
||||
return "LKQ";
|
||||
default:
|
||||
return "?";
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { DatePicker, message, Spin } from "antd";
|
||||
import moment from "moment";
|
||||
import React, { useState } from "react";
|
||||
import { UPDATE_JOB } from "../../../graphql/jobs.queries";
|
||||
|
||||
export default function CloseDateDisplayMolecule({ jobId, close_date }) {
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
const [value, setValue] = useState(moment(close_date));
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [updateJob] = useMutation(UPDATE_JOB);
|
||||
|
||||
const handleChange = async (newDate) => {
|
||||
setLoading(true);
|
||||
setValue(newDate);
|
||||
const result = await updateJob({
|
||||
variables: { jobId: jobId, job: { close_date: newDate } },
|
||||
});
|
||||
|
||||
if (!result.errors) {
|
||||
message.success("Close date updated.");
|
||||
} else {
|
||||
message.error("Error updating job.");
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
if (editMode)
|
||||
return (
|
||||
<div onBlur={() => setEditMode(false)}>
|
||||
<DatePicker
|
||||
value={value.isValid() ? value : null}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
{loading && <Spin size="small" />}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div style={{ cursor: "pointer" }} onClick={() => setEditMode(true)}>
|
||||
{value.isValid() ? value.format("MM/DD/yyyy") : "No date set"}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { Descriptions, PageHeader, Skeleton } from "antd";
|
||||
import React from "react";
|
||||
import CurrencyFormatterAtom from "../../atoms/currency-formatter/currency-formatter.atom";
|
||||
import ErrorResultAtom from "../../atoms/error-result/error-result.atom";
|
||||
import CloseDateDisplayMolecule from "../close-date-display/close-date-display.molecule";
|
||||
|
||||
export default function JobsDetailDescriptionMolecule({ loading, job }) {
|
||||
if (loading) return <Skeleton active />;
|
||||
@@ -19,6 +20,12 @@ export default function JobsDetailDescriptionMolecule({ loading, job }) {
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="Group">{job.group}</Descriptions.Item>
|
||||
<Descriptions.Item label="Age">{job.v_age}</Descriptions.Item>
|
||||
<Descriptions.Item label="Close Date">
|
||||
<CloseDateDisplayMolecule
|
||||
jobId={job.id}
|
||||
close_date={job.close_date}
|
||||
/>
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
</PageHeader>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Table } from "antd";
|
||||
import React from "react";
|
||||
import CurrencyFormatterAtom from "../../atoms/currency-formatter/currency-formatter.atom";
|
||||
import partTypeConverterAtom from "../../atoms/part-type-converter/part-type-converter.atom";
|
||||
import PriceDiffPcFormatterAtom from "../../atoms/price-diff-pc-formatter/price-diff-pc-formatter.atom";
|
||||
|
||||
export default function JobLinesTableMolecule({ loading, job }) {
|
||||
@@ -25,6 +26,7 @@ export default function JobLinesTableMolecule({ loading, job }) {
|
||||
title: "Part Type",
|
||||
dataIndex: "part_type",
|
||||
key: "part_type",
|
||||
render: (text, record) => partTypeConverterAtom(text),
|
||||
},
|
||||
{
|
||||
title: "Part Number",
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import { List } from "antd";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { setSelectedJobId } from "../../../redux/application/application.actions";
|
||||
import { selectSelectedJobId } from "../../../redux/application/application.selectors";
|
||||
import TimeAgoFormatter from "../../atoms/time-ago-formatter/time-ago-formatter.atom";
|
||||
import "./jobs-list-item.styles.scss";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
selectedJobId: selectSelectedJobId,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setSelectedJobId: (jobId) => dispatch(setSelectedJobId(jobId)),
|
||||
});
|
||||
|
||||
export function JobsListItemMolecule({
|
||||
selectedJobId,
|
||||
setSelectedJobId,
|
||||
item,
|
||||
}) {
|
||||
const handleSelect = (jobId) => {
|
||||
setSelectedJobId(jobId);
|
||||
};
|
||||
|
||||
return (
|
||||
<List.Item
|
||||
className="jobs-list-item"
|
||||
key={item.id}
|
||||
onClick={() => handleSelect(item.id)}
|
||||
>
|
||||
<div
|
||||
className={`jobs-list-item-content ${
|
||||
item.id === selectedJobId ? "jobs-list-item-content-selected" : ""
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<strong>{item.clm_no || "No Claim Number"}</strong>
|
||||
<span style={{ fontStyle: "italic" }}>
|
||||
<TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>{item.ins_co_nm || "No Insurance Co."}</div>
|
||||
<div>{`${item.ownr_fn} ${item.ownr_ln}`}</div>
|
||||
<div>
|
||||
{`${item.v_model_yr} ${item.v_makedesc} ${item.v_model} ${item.v_vin}`}
|
||||
</div>
|
||||
</div>
|
||||
</List.Item>
|
||||
);
|
||||
}
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(JobsListItemMolecule);
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
.jobs-list-item {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
.jobs-list-item-content {
|
||||
&-selected {
|
||||
border-left: 3px solid #1890ff;
|
||||
}
|
||||
|
||||
display: inline;
|
||||
margin: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #e6f7ff;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,8 @@ export default function JobsTargetsStatsMolecule({ loading, job }) {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-around",
|
||||
marginTop: "1rem",
|
||||
marginBottom: "1rem",
|
||||
}}
|
||||
>
|
||||
<TargetPriceDiffPcAtom v_age={job.v_age} group={job.group} />
|
||||
|
||||
@@ -49,16 +49,23 @@ export function JobsDetailOrganism({ selectedJobId }) {
|
||||
loading={loading}
|
||||
job={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
<div style={{ display: "flex", justifyContent: "space-evenly" }}>
|
||||
<JobsPartsGraphAtom
|
||||
job={data ? data.jobs_by_pk : null}
|
||||
loading={loading}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-evenly",
|
||||
minHeight: "20rem",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<JobsPartsGraphAtom
|
||||
job={data ? data.jobs_by_pk : null}
|
||||
loading={loading}
|
||||
price="db_price"
|
||||
/>
|
||||
<JobsPartsGraphAtom
|
||||
job={data ? data.jobs_by_pk : null}
|
||||
loading={loading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
.jobs-detail-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { SyncOutlined } from "@ant-design/icons";
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { Button, List, Spin } from "antd";
|
||||
import { Dropdown, List, Menu, Spin } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import InfiniteScroll from "react-infinite-scroller";
|
||||
import { connect } from "react-redux";
|
||||
@@ -9,8 +9,9 @@ import { QUERY_ALL_JOBS_PAGINATED } from "../../../graphql/jobs.queries";
|
||||
import { setSelectedJobId } from "../../../redux/application/application.actions";
|
||||
import { selectSelectedJobId } from "../../../redux/application/application.selectors";
|
||||
import ErrorResultAtom from "../../atoms/error-result/error-result.atom";
|
||||
import TimeAgoFormatter from "../../atoms/time-ago-formatter/time-ago-formatter.atom";
|
||||
import "./jobs-list.organism.styles.scss";
|
||||
import JobsListItemMolecule from "../../molecules/jobs-list-item/jobs-list-item.molecule";
|
||||
import "./jobs-list-latest.organism.styles.scss";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
selectedJobId: selectSelectedJobId,
|
||||
});
|
||||
@@ -33,6 +34,15 @@ export function JobsTableOrganism({ selectedJobId, setSelectedJobId }) {
|
||||
}
|
||||
);
|
||||
|
||||
const menu = (
|
||||
<Menu>
|
||||
<Menu.Item onClick={() => refetch()}>
|
||||
<SyncOutlined />
|
||||
Reload
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
const handleInfiniteOnLoad = (page) => {
|
||||
fetchMore({
|
||||
variables: {
|
||||
@@ -62,10 +72,6 @@ export function JobsTableOrganism({ selectedJobId, setSelectedJobId }) {
|
||||
});
|
||||
};
|
||||
|
||||
const handleSelect = (jobId) => {
|
||||
setSelectedJobId(jobId);
|
||||
};
|
||||
|
||||
if (error)
|
||||
return (
|
||||
<ErrorResultAtom
|
||||
@@ -76,9 +82,6 @@ export function JobsTableOrganism({ selectedJobId, setSelectedJobId }) {
|
||||
|
||||
return (
|
||||
<div className="jobs-list-container">
|
||||
<Button onClick={() => refetch()}>
|
||||
<SyncOutlined />
|
||||
</Button>
|
||||
<div className="jobs-list-infinite-container">
|
||||
<InfiniteScroll
|
||||
pageStart={0}
|
||||
@@ -86,50 +89,19 @@ export function JobsTableOrganism({ selectedJobId, setSelectedJobId }) {
|
||||
hasMore={!loading && state.hasMore}
|
||||
useWindow={false}
|
||||
>
|
||||
<List
|
||||
bordered
|
||||
dataSource={data ? data.jobs : []}
|
||||
renderItem={(item) => (
|
||||
<List.Item
|
||||
className="jobs-list-item"
|
||||
key={item.id}
|
||||
onClick={() => handleSelect(item.id)}
|
||||
>
|
||||
<div
|
||||
className={`jobs-list-item-content ${
|
||||
item.id === selectedJobId
|
||||
? "jobs-list-item-content-selected"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<strong>{item.clm_no || "No Claim Number"}</strong>
|
||||
<span style={{ fontStyle: "italic" }}>
|
||||
<TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>{item.ins_co_nm || "No Insurance Co."}</div>
|
||||
<div>{`${item.ownr_fn} ${item.ownr_ln}`}</div>
|
||||
<div>
|
||||
{`${item.v_model_yr} ${item.v_makedesc} ${item.v_model} ${item.v_vin}`}
|
||||
</div>
|
||||
<Dropdown overlay={menu} trigger={["contextMenu"]}>
|
||||
<List
|
||||
bordered
|
||||
dataSource={data ? data.jobs : []}
|
||||
renderItem={(item) => <JobsListItemMolecule item={item} />}
|
||||
>
|
||||
{loading && state.hasMore && (
|
||||
<div>
|
||||
<Spin />
|
||||
</div>
|
||||
</List.Item>
|
||||
)}
|
||||
>
|
||||
{loading && state.hasMore && (
|
||||
<div>
|
||||
<Spin />
|
||||
</div>
|
||||
)}
|
||||
</List>
|
||||
)}
|
||||
</List>
|
||||
</Dropdown>
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
{`${data ? data.jobs.length : 0} jobs loaded. ${
|
||||
@@ -0,0 +1,9 @@
|
||||
.jobs-list-container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.jobs-list-infinite-container {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
height: 100%;
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
.jobs-list-container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.jobs-list-infinite-container {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.jobs-list-item {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
.jobs-list-item-content {
|
||||
&-selected {
|
||||
border-left: 3px solid #1890ff;
|
||||
}
|
||||
|
||||
display: inline;
|
||||
margin: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #e6f7ff;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Col, Row } from "antd";
|
||||
import { Col, Row, Tabs } from "antd";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import JobsDetailOrganism from "../../organisms/jobs-detail/jobs-detail.organism";
|
||||
import JobsListOrganism from "../../organisms/jobs-list/jobs-list.organism";
|
||||
import JobsListOrganism from "../../organisms/jobs-list-latest/jobs-list-latest.organism";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({});
|
||||
const mapDispatchToProps = (dispatch) => ({});
|
||||
@@ -13,7 +13,14 @@ export function JobsPage() {
|
||||
<div style={{ height: "100%" }}>
|
||||
<Row gutter={[16, 16]} style={{ height: "100%" }}>
|
||||
<Col span={6} style={{ height: "100%" }}>
|
||||
<JobsListOrganism />
|
||||
<Tabs defaultActiveKey="search" style={{ height: "100%" }}>
|
||||
<Tabs.TabPane key="latest" tab="Latest" style={{ height: "100%" }}>
|
||||
<JobsListOrganism />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="search" tab="Search" style={{ height: "100%" }}>
|
||||
Search
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</Col>
|
||||
<Col span={18} style={{ height: "100%" }}>
|
||||
<JobsDetailOrganism />
|
||||
|
||||
Reference in New Issue
Block a user