BOD-21 Initial creation of the production schedule list framework.

This commit is contained in:
Patrick Fic
2020-04-21 16:02:12 -07:00
parent 5303ab0114
commit f2fd1bf7eb
30 changed files with 2657 additions and 514 deletions

View File

@@ -0,0 +1,22 @@
import React from "react";
import { Transfer } from "antd";
import dataSource from "./production-list-columns.data";
export default function ProductionColumnsComponent({ columnState }) {
const [columns, setColumns] = columnState;
return (
<div>
<Transfer
dataSource={dataSource}
titles={["Source", "Target"]}
targetKeys={columns.map((c) => c.key)}
render={(item) => item.title}
onChange={(nextTargetKeys, direction, moveKeys) => {
setColumns(dataSource.filter((i) => nextTargetKeys.includes(i.key)));
}}
/>
</div>
);
}

View File

@@ -0,0 +1,158 @@
import i18n from "i18next";
import React from "react";
import { Link } from "react-router-dom";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter";
import PhoneFormatter from "../../utils/PhoneFormatter";
import { alphaSort } from "../../utils/sorters";
import { ExclamationCircleFilled } from "@ant-design/icons";
export default [
{
title: i18n.t("jobs.fields.ro_number"),
dataIndex: "ro_number",
key: "ro_number",
ellipsis: true,
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
render: (text, record) => (
<Link to={`/manage/jobs/${record.id}`}>{record.ro_number}</Link>
),
},
{
title: i18n.t("jobs.fields.owner"),
dataIndex: "ownr",
key: "ownr",
ellipsis: true,
render: (text, record) => (
<span>{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${
record.ownr_co_nm || ""
}`}</span>
),
sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
},
{
title: i18n.t("jobs.fields.vehicle"),
dataIndex: "vehicle",
key: "vehicle",
ellipsis: true,
render: (text, record) => (
<span>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
record.v_model_desc || ""
} ${record.v_color || ""} ${record.plate_no || ""}`}</span>
),
},
{
title: i18n.t("jobs.fields.actual_in"),
dataIndex: "actual_in",
key: "actual_in",
ellipsis: true,
sorter: (a, b) => a.actual_in - b.actual_in,
render: (text, record) => (
<DateFormatter>{record.actual_in || ""}</DateFormatter>
),
},
{
title: i18n.t("jobs.fields.scheduled_completion"),
dataIndex: "scheduled_completion",
key: "scheduled_completion",
ellipsis: true,
sorter: (a, b) => a.scheduled_completion - b.scheduled_completion,
render: (text, record) => (
<DateFormatter>{record.scheduled_completion}</DateFormatter>
),
},
{
title: i18n.t("jobs.fields.scheduled_delivery"),
dataIndex: "scheduled_delivery",
key: "scheduled_delivery",
ellipsis: true,
sorter: (a, b) => a.scheduled_delivery - b.scheduled_delivery,
render: (text, record) => (
<DateFormatter>{record.scheduled_delivery}</DateFormatter>
),
},
{
title: i18n.t("jobs.fields.ins_co_nm"),
dataIndex: "ins_co_nm",
key: "ins_co_nm",
ellipsis: true,
sorter: (a, b) => alphaSort(a.ins_co_nm, b.ins_co_nm),
},
{
title: i18n.t("jobs.fields.clm_no"),
dataIndex: "clm_no",
key: "clm_no",
ellipsis: true,
sorter: (a, b) => alphaSort(a.clm_no, b.clm_no),
},
{
title: i18n.t("jobs.fields.clm_total"),
dataIndex: "clm_total",
key: "clm_total",
ellipsis: true,
sorter: (a, b) => a.clm_total - b.clm_total,
render: (text, record) => (
<CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
),
},
{
title: i18n.t("jobs.fields.owner_owing"),
dataIndex: "owner_owing",
key: "owner_owing",
ellipsis: true,
sorter: (a, b) => a.owner_owing - b.owner_owing,
render: (text, record) => (
<CurrencyFormatter>{record.owner_owing}</CurrencyFormatter>
),
},
{
title: i18n.t("jobs.fields.ownr_ph1"),
dataIndex: "ownr_ph1",
key: "ownr_ph1",
ellipsis: true,
render: (text, record) => (
<PhoneFormatter>{record.ownr_ph1}</PhoneFormatter>
),
},
{
title: i18n.t("jobs.fields.specialcoveragepolicy"),
dataIndex: "special_coverage_policy",
key: "special_coverage_policy",
ellipsis: true,
},
{
title: i18n.t("jobs.fields.csr"),
dataIndex: "csr",
key: "csr",
ellipsis: true,
sorter: (a, b) => alphaSort(a.csr, b.csr),
},
{
title: i18n.t("jobs.fields.status"),
dataIndex: "status",
key: "status",
ellipsis: true,
},
{
title: i18n.t("production.labels.alert"),
dataIndex: "alert",
key: "alert",
render: (text, record) => (
<div>
{record.production_vars && record.production_vars.alert ? (
<ExclamationCircleFilled style={{ color: "red" }} />
) : null}
</div>
),
},
{
title: i18n.t("production.labels.note"),
dataIndex: "note",
key: "note",
render: (text, record) => (
<span>
{(record.production_vars && record.production_vars.note) || ""}
</span>
),
},
];

View File

@@ -0,0 +1,52 @@
import { useMutation } from "@apollo/react-hooks";
import React from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { Button } from "antd";
import { useTranslation } from "react-i18next";
import { UPDATE_SHOP } from "../../graphql/bodyshop.queries";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export function ProductionListSaveConfigButton({
columns,
bodyshop,
tableState,
}) {
const [updateShop] = useMutation(UPDATE_SHOP);
const { t } = useTranslation();
const handleSaveConfig = () => {
updateShop({
variables: {
id: bodyshop.id,
shop: {
production_config: {
columnKeys: columns.map((i) => i.key),
tableState,
},
},
},
}).then((response) => {
const shopDetails = response.data.update_bodyshops.returning[0];
console.log("handleSaveConfig -> shopDetails", shopDetails);
});
};
return (
<Button onClick={() => handleSaveConfig()}>
{t("production.actions.saveconfig")}
</Button>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(ProductionListSaveConfigButton);

View File

@@ -0,0 +1,70 @@
import { Table, Button } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { SyncOutlined } from "@ant-design/icons";
import ProductionListSaveConfigButton from "../production-list-save-config-button/production-list-save-config-button.component";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export function ProductionListTable({
columns,
loading,
data,
bodyshop,
refetch,
}) {
const [state, setState] = useState(
bodyshop.production_config.tableState || {
sortedInfo: {},
filteredInfo: { text: "" },
}
);
console.log("Filter State", state);
const { t } = useTranslation();
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
};
if (!!!columns) return <div>No columns found.</div>;
return (
<Table
size="small"
title={() => (
<div>
<ProductionListSaveConfigButton
columns={columns}
tableState={state}
/>
<Button onClick={() => refetch()}>
<SyncOutlined />
</Button>
</div>
)}
pagination={{ position: "top" }}
columns={columns.map((c) => {
return {
...c,
sortOrder:
state.sortedInfo.columnKey === c.key && state.sortedInfo.order,
};
})}
rowKey="id"
loading={loading}
dataSource={data}
onChange={handleTableChange}
/>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(ProductionListTable);

View File

@@ -0,0 +1,24 @@
import { useQuery } from "@apollo/react-hooks";
import React from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { QUERY_JOBS_IN_PRODUCTION } from "../../graphql/jobs.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import ProductionListTable from "./production-list-table.component";
export default function ProductionListTableContainer({ columnState }) {
const { loading, data, refetch } = useQuery(QUERY_JOBS_IN_PRODUCTION, {
pollInterval: 30000,
});
return (
<div>
<ProductionListTable
loading={loading}
data={data ? data.jobs : []}
refetch={refetch}
columns={columnState[0]}
/>
</div>
);
}

View File

@@ -26,6 +26,7 @@ export const QUERY_BODYSHOP = gql`
messagingservicesid
template_header
textid
production_config
employees {
id
first_name
@@ -49,7 +50,37 @@ export const UPDATE_SHOP = gql`
mutation UPDATE_SHOP($id: uuid, $shop: bodyshops_set_input!) {
update_bodyshops(where: { id: { _eq: $id } }, _set: $shop) {
returning {
address1
address2
city
country
created_at
email
federal_tax_id
id
insurance_vendor_id
logo_img_path
md_ro_statuses
md_order_statuses
shopname
state
state_tax_id
updated_at
zip_post
shoprates
region_config
md_responsibility_centers
messagingservicesid
template_header
textid
production_config
employees {
id
first_name
last_name
employee_number
cost_center
}
}
}
}

View File

@@ -50,35 +50,30 @@ export const QUERY_ALL_ACTIVE_JOBS = gql`
}
`;
export const SUBSCRIPTION_JOBS_IN_PRODUCTION = gql`
subscription SUBSCRIPTION_JOBS_IN_PRODUCTION {
job_status(
where: { isproductionstatus: { _eq: true } }
order_by: { order: asc }
) {
name
order
isproductionstatus
export const QUERY_JOBS_IN_PRODUCTION = gql`
query QUERY_JOBS_IN_PRODUCTION {
jobs {
id
jobs {
id
scheduled_completion
actual_in
est_number
ro_number
vehicle {
id
v_model_yr
v_model_desc
v_make_desc
v_vin
}
owner {
id
first_name
last_name
}
}
status
ro_number
est_number
ownr_fn
ownr_ln
v_model_yr
v_model_desc
clm_no
v_make_desc
v_color
plate_no
actual_in
scheduled_completion
scheduled_delivery
ins_co_nm
clm_total
ownr_ph1
special_coverage_policy
status
production_vars
}
}
`;

View File

@@ -77,6 +77,9 @@ const EnterInvoiceModalContainer = lazy(() =>
const TimeTicketModalContainer = lazy(() =>
import("../../components/time-ticket-modal/time-ticket-modal.container")
);
const ProductionListPage = lazy(() =>
import("../production-list/production-list.container")
);
const { Header, Content, Footer } = Layout;
export default function Manage({ match }) {
@@ -165,6 +168,11 @@ export default function Manage({ match }) {
path={`${match.path}/vehicles`}
component={VehiclesContainer}
/>
<Route
exact
path={`${match.path}/production/list`}
component={ProductionListPage}
/>
<Route
exact
path={`${match.path}/vehicles/:vehId`}

View File

@@ -0,0 +1,12 @@
import React from "react";
import ProductionListColumns from "../../components/production-list-columns/production-list-columns.component";
import ProductionListTable from "../../components/production-list-table/production-list-table.container";
export default function ProductionListComponent({ columnState }) {
return (
<div>
<ProductionListColumns columnState={columnState} />
<ProductionListTable columnState={columnState} />
</div>
);
}

View File

@@ -0,0 +1,42 @@
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { setBreadcrumbs } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import ProductionListComponent from "./production-list.component";
import ProductionListColumns from "../../components/production-list-columns/production-list-columns.data";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
});
export function ProductionListContainer({ setBreadcrumbs, bodyshop }) {
const { t } = useTranslation();
const columnState = useState(
ProductionListColumns.filter((c) =>
bodyshop.production_config.columnKeys.includes(c.key)
) || []
);
useEffect(() => {
document.title = t("titles.productionlist");
setBreadcrumbs([
{ link: "/manage/production/list", label: t("titles.bc.productionlist") },
]);
}, [t, setBreadcrumbs]);
return (
<div>
<ProductionListComponent columnState={columnState} />
</div>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(ProductionListContainer);

View File

@@ -717,6 +717,15 @@
"created": "Parts order created successfully. "
}
},
"production": {
"actions": {
"saveconfig": "Save Configuration"
},
"labels": {
"alert": "Alert",
"note": "Production Note"
}
},
"profile": {
"errors": {
"state": "Error reading page state. Please refresh."
@@ -758,6 +767,7 @@
"jobs-new": "Create a New Job",
"owner-detail": "{{name}}",
"owners": "Owners",
"productionlist": "Production - List",
"schedule": "Schedule",
"vehicle-details": "Vehicle: {{vehicle}}",
"vehicles": "Vehicles"
@@ -776,6 +786,7 @@
"manageroot": "Home | $t(titles.app)",
"owners": "All Owners | $t(titles.app)",
"owners-detail": "{{name}} | $t(titles.app)",
"productionlist": "Production - List View | $t(titles.app)",
"profile": "My Profile | $t(titles.app)",
"schedule": "Schedule | $t(titles.app)",
"shop": "My Shop | $t(titles.app)",

View File

@@ -717,6 +717,15 @@
"created": "Pedido de piezas creado con éxito."
}
},
"production": {
"actions": {
"saveconfig": ""
},
"labels": {
"alert": "",
"note": ""
}
},
"profile": {
"errors": {
"state": "Error al leer el estado de la página. Porfavor refresca."
@@ -758,6 +767,7 @@
"jobs-new": "",
"owner-detail": "",
"owners": "",
"productionlist": "",
"schedule": "",
"vehicle-details": "",
"vehicles": ""
@@ -776,6 +786,7 @@
"manageroot": "Casa | $t(titles.app)",
"owners": "Todos los propietarios | $t(titles.app)",
"owners-detail": "",
"productionlist": "",
"profile": "Mi perfil | $t(titles.app)",
"schedule": "Horario | $t(titles.app)",
"shop": "Mi tienda | $t(titles.app)",

View File

@@ -717,6 +717,15 @@
"created": "Commande de pièces créée avec succès."
}
},
"production": {
"actions": {
"saveconfig": ""
},
"labels": {
"alert": "",
"note": ""
}
},
"profile": {
"errors": {
"state": "Erreur lors de la lecture de l'état de la page. Rafraichissez, s'il vous plait."
@@ -758,6 +767,7 @@
"jobs-new": "",
"owner-detail": "",
"owners": "",
"productionlist": "",
"schedule": "",
"vehicle-details": "",
"vehicles": ""
@@ -776,6 +786,7 @@
"manageroot": "Accueil | $t(titles.app)",
"owners": "Tous les propriétaires | $t(titles.app)",
"owners-detail": "",
"productionlist": "",
"profile": "Mon profil | $t(titles.app)",
"schedule": "Horaire | $t(titles.app)",
"shop": "Mon magasin | $t(titles.app)",