Added closed date to jobs and reformatted some items

This commit is contained in:
Patrick Fic
2020-10-19 11:44:34 -07:00
parent 65165906d4
commit 2c696425b6
27 changed files with 603 additions and 211 deletions

View File

@@ -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":

View File

@@ -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 "?";
}
};

View File

@@ -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>
);
}

View File

@@ -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>

View File

@@ -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",

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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} />

View File

@@ -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>
);

View File

@@ -1,5 +1,4 @@
.jobs-detail-container {
height: 100%;
overflow-y: auto;
}

View File

@@ -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. ${

View File

@@ -0,0 +1,9 @@
.jobs-list-container {
height: 100%;
}
.jobs-list-infinite-container {
overflow-y: auto;
overflow-x: hidden;
height: 100%;
}

View File

@@ -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;
}
}

View File

@@ -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 />