BOD-20 added onclick to production schedule + parts totals.

This commit is contained in:
Patrick Fic
2020-04-27 15:08:37 -07:00
parent 2b19d1af0b
commit f04a6fc2af
25 changed files with 580 additions and 120 deletions

View File

@@ -11394,6 +11394,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>jobdetail</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>
<name>note</name>
<definition_loaded>false</definition_loaded>

View File

@@ -33,6 +33,19 @@ export function JobLinesComponent({
const { t } = useTranslation();
const columns = [
{
title: t("joblines.fields.line_no"),
dataIndex: "line_no",
key: "line_no",
// onFilter: (value, record) => record.ro_number.includes(value),
// filteredValue: state.filteredInfo.text || null,
sorter: (a, b) => a.line_no - b.line_no,
sortOrder:
state.sortedInfo.columnKey === "line_no" && state.sortedInfo.order,
//ellipsis: true,
editable: true,
width: 75,
},
{
title: t("joblines.fields.unq_seq"),
dataIndex: "unq_seq",
@@ -192,8 +205,7 @@ export function JobLinesComponent({
actions: { refetch: refetch },
context: record,
});
}}
>
}}>
{t("general.actions.edit")}
</Button>
</span>
@@ -229,8 +241,7 @@ export function JobLinesComponent({
linesToOrder: selectedLines,
},
});
}}
>
}}>
{t("parts.actions.order")}
</Button>
<AllocationsBulkAssignmentContainer
@@ -243,15 +254,14 @@ export function JobLinesComponent({
actions: { refetch: refetch },
context: { jobid: jobId },
});
}}
>
}}>
{t("joblines.actions.new")}
</Button>
</div>
);
}}
loading={loading}
size="small"
size='small'
expandedRowRender={(record) => (
<div style={{ margin: 0 }}>
<strong>{t("parts_orders.labels.orderhistory")}</strong>
@@ -276,7 +286,7 @@ export function JobLinesComponent({
setSelectedLines(selectedRows),
}}
columns={columns.map((item) => ({ ...item }))}
rowKey="id"
rowKey='id'
dataSource={jobLines}
onChange={handleTableChange}
/>

View File

@@ -18,7 +18,7 @@ const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export function JobsDetailHeaderActions({ job, bodyshop }) {
export function JobsDetailHeaderActions({ job, bodyshop, refetch }) {
const { t } = useTranslation();
const client = useApolloClient();
const history = useHistory();
@@ -36,7 +36,7 @@ export function JobsDetailHeaderActions({ job, bodyshop }) {
<Menu.Item
key='addtoproduction'
disabled={!!!job.converted || !!job.inproduction}
onClick={() => AddToProduction(client, job.id)}>
onClick={() => AddToProduction(client, job.id, refetch)}>
{t("jobs.actions.addtoproduction")}
</Menu.Item>
<Menu.Item key='duplicatejob'>

View File

@@ -101,7 +101,7 @@ export function JobsDetailHeader({
}}>
{t("jobs.actions.convert")}
</Button>,
<JobsDetailHeaderActions key='actions' job={job} />,
<JobsDetailHeaderActions key='actions' job={job} refetch={refetch} />,
<Button type='primary' key='submit' htmlType='submit'>
{t("general.actions.save")}
</Button>,

View File

@@ -0,0 +1,43 @@
import React, { useState } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { Pie } from "@nivo/pie";
import { useTranslation } from "react-i18next";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
export function PartsStatusPie({ partsList }) {
const { t } = useTranslation();
//const [pieData, setPieData] = useState([]);
const result = partsList
? partsList.reduce((names, name) => {
const val = name || "?";
const count = names[val] || 0;
names[val] = count + 1;
return names;
}, {})
: {};
const pieData = Object.keys(result).map((i) => {
console.log("i", i);
return {
id: i,
label: i,
value: result[i],
};
});
const commonProperties = {
width: 250,
height: 250,
margin: { top: 40, right: 60, bottom: 40, left: 60 },
animate: true,
};
return <Pie {...commonProperties} data={pieData} innerRadius={0.5} />;
}
export default connect(mapStateToProps, null)(PartsStatusPie);

View File

@@ -0,0 +1,58 @@
import React from "react";
import { Descriptions, Drawer } from "antd";
import { useTranslation } from "react-i18next";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter";
import PartsPieGraph from "../parts-status-pie/parts-status-pie.component";
import Barcode from "react-barcode";
export default function ProductionListDetail({ selected, setSelected, jobs }) {
const { t } = useTranslation();
const theJob = jobs.find((j) => j.id === selected) || {};
return (
<Drawer
title={t("production.labels.jobdetail")}
placement='right'
width={"25%"}
onClose={() => setSelected(null)}
visible={!!selected}>
<div>
<Barcode
value={theJob.id || ""}
background='transparent'
displayValue={false}
width={1}
height={15}
/>
<Descriptions bordered size='small' column={1}>
<Descriptions.Item label={t("jobs.fields.ro_number")}>
{theJob.ro_number || ""}
</Descriptions.Item>
<Descriptions.Item label={t("jobs.fields.owner")}>
{`${theJob.ownr_fn || ""} ${theJob.ownr_ln || ""} ${
theJob.ownr_co_nm || ""
}`}
</Descriptions.Item>
<Descriptions.Item label={t("jobs.fields.vehicle")}>
{`${theJob.v_model_yr || ""} ${theJob.v_color || ""} ${
theJob.v_make_desc || ""
} ${theJob.v_model_desc || ""}`}
</Descriptions.Item>
<Descriptions.Item label={t("jobs.fields.clm_total")}>
<CurrencyFormatter>{theJob.clm_total}</CurrencyFormatter>
</Descriptions.Item>
<Descriptions.Item label={t("jobs.fields.actual_in")}>
<DateFormatter>{theJob.actual_in}</DateFormatter>
</Descriptions.Item>
<Descriptions.Item label={t("jobs.fields.scheduled_completion")}>
<DateFormatter>{theJob.scheduled_completion}</DateFormatter>
</Descriptions.Item>
<Descriptions.Item label={t("jobs.labels.parts")}>
<PartsPieGraph partsList={theJob.partcount} />
</Descriptions.Item>
</Descriptions>
</div>
</Drawer>
);
}

View File

@@ -1,23 +1,20 @@
import { Table, Button, Menu, Dropdown, Input } from "antd";
import React, { useState } from "react";
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 { Button, Dropdown, Input, Menu, Table } from "antd";
import React, { useState } from "react";
import ReactDragListView from "react-drag-listview"; //TODO Is there a better way? This library is too big.
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import ReactDragListView from "react-drag-listview"; //TODO Is there a better way? This library is too big.
import "./production-list-table.styles.scss";
import { useTranslation } from "react-i18next";
import { selectBodyshop } from "../../redux/user/user.selectors";
import ProductionListColumnsAdd from "../production-list-columns/production-list-columns.add.component";
import ProductionListSaveConfigButton from "../production-list-save-config-button/production-list-save-config-button.component";
import ResizeableTitle from "./production-list-table.resizeable.component";
import "./production-list-table.styles.scss";
import ProductionListDetail from "../production-list-detail/production-list-detail.component";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
const OneCalendarDay = 60 * 60 * 24 * 1000;
@@ -36,10 +33,10 @@ export function ProductionListTable({
filteredInfo: { text: "" },
}
);
const [selected, setSelected] = useState(null);
const { t } = useTranslation();
const Now = new Date();
const handleTableChange = (pagination, filters, sorter) => {
console.log("sorter", sorter);
setState({
...state,
filteredInfo: filters,
@@ -68,8 +65,6 @@ export function ProductionListTable({
setColumns(nextColumns);
};
const Now = new Date();
const headerItem = (col) => (
<Dropdown
className='prod-header-dropdown'
@@ -84,102 +79,117 @@ export function ProductionListTable({
<span>{col.title}</span>
</Dropdown>
);
const dataSource =
searchText === ""
? data
: data.filter(
(j) =>
(j.ro_number || "")
.toString()
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_fn || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_ln || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.status || "").toLowerCase().includes(searchText.toLowerCase()) ||
(j.ins_co_nm || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) ||
(j.v_model_desc || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.v_make_desc || "")
.toLowerCase()
.includes(searchText.toLowerCase())
);
const tableTitle = () => (
<div style={{ display: "flex" }}>
<ProductionListColumnsAdd columnState={columnState} />
<ProductionListSaveConfigButton columns={columns} tableState={state} />
<Button
onClick={() => {
if (refetch) refetch();
}}>
<SyncOutlined />
</Button>
<Input
onChange={(e) => setSearchText(e.target.value)}
placeholder={t("general.labels.search")}
value={searchText}
/>
</div>
);
const handleSelectRecord = (record) => {
if (selected !== record.id) {
setSelected(record.id);
} else {
setSelected(null);
}
};
if (!!!columns) return <div>No columns found.</div>;
return (
<ReactDragListView.DragColumn
onDragEnd={onDragEnd}
nodeSelector='th'
handleSelector='.prod-header-dropdown'>
<Table
size='small'
pagination={false}
components={{
header: {
cell: ResizeableTitle,
},
}}
title={() => (
<div style={{ display: "flex" }}>
<ProductionListColumnsAdd columnState={columnState} />
<ProductionListSaveConfigButton
columns={columns}
tableState={state}
/>
<Button
onClick={() => {
if (refetch) refetch();
}}>
<SyncOutlined />
</Button>
<Input
onChange={(e) => setSearchText(e.target.value)}
placeholder={t("general.labels.search")}
value={searchText}
/>
</div>
)}
columns={columns.map((c, index) => {
return {
...c,
sortOrder:
state.sortedInfo.columnKey === c.key && state.sortedInfo.order,
title: headerItem(c),
onHeaderCell: (column) => ({
width: column.width,
onResize: handleResize(index),
}),
};
})}
rowKey='id'
loading={loading}
dataSource={
searchText === ""
? data
: data.filter(
(j) =>
(j.ro_number || "")
.toString()
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_fn || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_ln || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.status || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ins_co_nm || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.clm_no || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.v_model_desc || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.v_make_desc || "")
.toLowerCase()
.includes(searchText.toLowerCase())
)
}
onChange={handleTableChange}
rowClassName={(record, index) => {
const classes = [];
if (!!record.scheduled_completion) {
if (new Date(record.scheduled_completion) - Now < OneCalendarDay)
classes.push("production-completion-1");
}
return classes.join(" ");
}} //TODO What could be good usage here?
onRow={(record, index) => (record.refetch = refetch)}
<div>
<ProductionListDetail
selected={selected}
setSelected={setSelected}
jobs={dataSource}
/>
</ReactDragListView.DragColumn>
<ReactDragListView.DragColumn
onDragEnd={onDragEnd}
nodeSelector='th'
handleSelector='.prod-header-dropdown'>
<Table
size='small'
pagination={false}
components={{
header: {
cell: ResizeableTitle,
},
}}
title={tableTitle}
columns={columns.map((c, index) => {
return {
...c,
sortOrder:
state.sortedInfo.columnKey === c.key && state.sortedInfo.order,
title: headerItem(c),
onHeaderCell: (column) => ({
width: column.width,
onResize: handleResize(index),
}),
};
})}
rowKey='id'
loading={loading}
onRow={(record, rowIndex) => {
return {
onClick: (event) => {
handleSelectRecord(record);
},
};
}}
dataSource={dataSource}
onChange={handleTableChange}
rowClassName={(record, index) => {
const classes = []; //TODO What could be good usage here?
if (!!record.scheduled_completion) {
if (new Date(record.scheduled_completion) - Now < OneCalendarDay)
classes.push("production-completion-1");
}
return classes.join(" ");
}}
/>
</ReactDragListView.DragColumn>
</div>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(ProductionListTable);
export default connect(mapStateToProps, null)(ProductionListTable);

View File

@@ -104,6 +104,7 @@ export const SUBSCRIPTION_JOBS_IN_PRODUCTION = gql`
production_vars
labhrs
larhrs
partcount
}
}
`;
@@ -248,7 +249,7 @@ export const GET_JOB_BY_PK = gql`
date_exported
status
owner_owing
joblines {
id
unq_seq

View File

@@ -739,6 +739,7 @@
"bodyhours": "B",
"bodypriority": "B/P",
"cycletime": "C/T",
"jobdetail": "Job Details",
"note": "Production Note",
"paintpriority": "P/P",
"refinishhours": "R"

View File

@@ -739,6 +739,7 @@
"bodyhours": "",
"bodypriority": "",
"cycletime": "",
"jobdetail": "",
"note": "",
"paintpriority": "",
"refinishhours": ""

View File

@@ -739,6 +739,7 @@
"bodyhours": "",
"bodypriority": "",
"cycletime": "",
"jobdetail": "",
"note": "",
"paintpriority": "",
"refinishhours": ""

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1,18 @@
- args:
cascade: true
read_only: false
sql: "CREATE OR REPLACE VIEW \"public\".\"productionview\" AS \n SELECT j.id,\n
\ j.status,\n j.ro_number,\n j.est_number,\n j.ownr_fn,\n j.ownr_ln,\n
\ j.v_model_yr,\n j.v_model_desc,\n j.clm_no,\n j.v_make_desc,\n
\ j.v_color,\n j.plate_no,\n j.actual_in,\n j.scheduled_completion,\n
\ j.scheduled_delivery,\n j.ins_co_nm,\n j.clm_total,\n j.ownr_ph1,\n
\ j.special_coverage_policy,\n j.production_vars,\n lab.labhrs,\n lar.larhrs,\n
\ j.shopid,\n parts.*\n FROM ((jobs j\n LEFT JOIN ( SELECT l.jobid,\n
\ sum(l.mod_lb_hrs) AS labhrs\n FROM joblines l\n WHERE
(l.mod_lbr_ty = 'LAB'::text)\n GROUP BY l.jobid) lab ON ((lab.jobid
= j.id)))\n LEFT JOIN ( SELECT l2.jobid,\n sum(l2.mod_lb_hrs)
AS larhrs\n FROM joblines l2\n WHERE (l2.mod_lbr_ty = 'LAR'::text)\n
\ GROUP BY l2.jobid) lar ON ((lar.jobid = j.id)))\n left join ( select
\ l3.jobid, json_agg(l3.status) partcount from joblines l3 group by l3.jobid,
l3.status ) parts on parts.jobid = j.id\n WHERE (j.inproduction = true);"
type: run_sql

View File

@@ -0,0 +1,48 @@
- args:
role: user
table:
name: productionview
schema: public
type: drop_select_permission
- args:
permission:
allow_aggregations: false
columns:
- id
- 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
- production_vars
- labhrs
- larhrs
- shopid
computed_fields: []
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
role: user
table:
name: productionview
schema: public
type: create_select_permission

View File

@@ -0,0 +1,50 @@
- args:
role: user
table:
name: productionview
schema: public
type: drop_select_permission
- args:
permission:
allow_aggregations: false
columns:
- actual_in
- clm_no
- clm_total
- est_number
- id
- ins_co_nm
- jobid
- labhrs
- larhrs
- ownr_fn
- ownr_ln
- ownr_ph1
- partcount
- plate_no
- production_vars
- ro_number
- scheduled_completion
- scheduled_delivery
- shopid
- special_coverage_policy
- status
- v_color
- v_make_desc
- v_model_desc
- v_model_yr
computed_fields: []
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
role: user
table:
name: productionview
schema: public
type: create_select_permission

View File

@@ -0,0 +1,50 @@
- args:
role: user
table:
name: productionview
schema: public
type: drop_select_permission
- args:
permission:
allow_aggregations: false
columns:
- actual_in
- clm_no
- clm_total
- est_number
- id
- ins_co_nm
- jobid
- labhrs
- larhrs
- ownr_fn
- ownr_ln
- ownr_ph1
- partcount
- plate_no
- production_vars
- ro_number
- scheduled_completion
- scheduled_delivery
- shopid
- special_coverage_policy
- status
- v_color
- v_make_desc
- v_model_desc
- v_model_yr
computed_fields: []
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
role: user
table:
name: productionview
schema: public
type: create_select_permission

View File

@@ -0,0 +1,49 @@
- args:
role: user
table:
name: productionview
schema: public
type: drop_select_permission
- args:
permission:
allow_aggregations: false
columns:
- actual_in
- clm_no
- clm_total
- est_number
- id
- ins_co_nm
- jobid
- labhrs
- larhrs
- ownr_fn
- ownr_ln
- ownr_ph1
- plate_no
- production_vars
- ro_number
- scheduled_completion
- scheduled_delivery
- shopid
- special_coverage_policy
- status
- v_color
- v_make_desc
- v_model_desc
- v_model_yr
computed_fields: []
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
role: user
table:
name: productionview
schema: public
type: create_select_permission

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: DROP VIEW "public"."productionview";
type: run_sql

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1,22 @@
- args:
cascade: true
read_only: false
sql: "CREATE OR REPLACE VIEW \"public\".\"productionview\" AS \n SELECT j.id,\n
\ j.status,\n j.ro_number,\n j.est_number,\n j.ownr_fn,\n j.ownr_ln,\n
\ j.v_model_yr,\n j.v_model_desc,\n j.clm_no,\n j.v_make_desc,\n
\ j.v_color,\n j.plate_no,\n j.actual_in,\n j.scheduled_completion,\n
\ j.scheduled_delivery,\n j.ins_co_nm,\n j.clm_total,\n j.ownr_ph1,\n
\ j.special_coverage_policy,\n j.production_vars,\n lab.labhrs,\n lar.larhrs,\n
\ j.shopid,\n parts.partcount\n FROM ((jobs j\n LEFT JOIN ( SELECT
l.jobid,\n sum(l.mod_lb_hrs) AS labhrs\n FROM joblines
l\n WHERE (l.mod_lbr_ty = 'LAB'::text)\n GROUP BY l.jobid)
lab ON ((lab.jobid = j.id)))\n LEFT JOIN ( SELECT l2.jobid,\n sum(l2.mod_lb_hrs)
AS larhrs\n FROM joblines l2\n WHERE (l2.mod_lbr_ty = 'LAR'::text)\n
\ GROUP BY l2.jobid) lar ON ((lar.jobid = j.id)))\n left join ( select
\ l3.jobid, json_agg(l3.status) partcount from joblines l3 group by l3.jobid
) parts on parts.jobid = j.id\n WHERE (j.inproduction = true);"
type: run_sql
- args:
name: productionview
schema: public
type: add_existing_table_or_view

View File

@@ -0,0 +1,6 @@
- args:
relationship: bodyshop
table:
name: productionview
schema: public
type: drop_relationship

View File

@@ -0,0 +1,13 @@
- args:
name: bodyshop
table:
name: productionview
schema: public
using:
manual_configuration:
column_mapping:
shopid: id
remote_table:
name: bodyshops
schema: public
type: create_object_relationship

View File

@@ -0,0 +1,6 @@
- args:
role: user
table:
name: productionview
schema: public
type: drop_select_permission

View File

@@ -0,0 +1,44 @@
- args:
permission:
allow_aggregations: false
columns:
- id
- 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
- production_vars
- labhrs
- larhrs
- shopid
- partcount
computed_fields: []
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
limit: null
role: user
table:
name: productionview
schema: public
type: create_select_permission