diff --git a/client/src/components/schedule-calendar-wrapper/schedule-calendar-header-graph.component.js b/client/src/components/schedule-calendar-wrapper/schedule-calendar-header-graph.component.js new file mode 100644 index 000000000..63332437d --- /dev/null +++ b/client/src/components/schedule-calendar-wrapper/schedule-calendar-header-graph.component.js @@ -0,0 +1,68 @@ +import { Popover } from "antd"; +import React from "react"; +import { connect } from "react-redux"; +import { + PolarAngleAxis, + PolarGrid, + PolarRadiusAxis, + Radar, + RadarChart, +} from "recharts"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; + +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop, +}); +const mapDispatchToProps = (dispatch) => ({ + //setUserLanguage: language => dispatch(setUserLanguage(language)) +}); + +export function ScheduleCalendarHeaderGraph({ bodyshop, loadData }) { + const { ssbuckets } = bodyshop; + + const data = Object.keys(loadData.expectedLoad).map((key) => { + const metadataBucket = ssbuckets.filter((b) => b.id === key)[0]; + + return { + bucket: loadData.expectedLoad[key].label, + current: loadData.expectedLoad[key].count, + target: metadataBucket && metadataBucket.target, + }; + }); + +//d console.log("data", data); + const popContent = ( +
+ + + + + + +
+ ); + + return ( + + G + + ); +} +export default connect( + mapStateToProps, + mapDispatchToProps +)(ScheduleCalendarHeaderGraph); diff --git a/client/src/components/schedule-calendar-wrapper/schedule-calendar-header.component.js b/client/src/components/schedule-calendar-wrapper/schedule-calendar-header.component.js index f2929c3b7..20dbeb33b 100644 --- a/client/src/components/schedule-calendar-wrapper/schedule-calendar-header.component.js +++ b/client/src/components/schedule-calendar-wrapper/schedule-calendar-header.component.js @@ -1,5 +1,5 @@ import Icon from "@ant-design/icons"; -import { Popover, Statistic } from "antd"; +import { Popover } from "antd"; import React from "react"; import { useTranslation } from "react-i18next"; import { MdFileDownload, MdFileUpload } from "react-icons/md"; @@ -8,23 +8,21 @@ import { Link } from "react-router-dom"; import { createStructuredSelector } from "reselect"; import { selectScheduleLoad, - selectScheduleLoadCalculating, + selectScheduleLoadCalculating } from "../../redux/application/application.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors"; import { DateTimeFormatter } from "../../utils/DateFormatter"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; import ScheduleBlockDay from "../schedule-block-day/schedule-block-day.component"; +import ScheduleCalendarHeaderGraph from "./schedule-calendar-header-graph.component"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser bodyshop: selectBodyshop, load: selectScheduleLoad, calculating: selectScheduleLoadCalculating, }); -const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) -}); +const mapDispatchToProps = (dispatch) => ({}); export function ScheduleCalendarHeaderComponent({ bodyshop, @@ -122,23 +120,7 @@ export function ScheduleCalendarHeaderComponent({ {(loadData.hoursOut || 0) && loadData.hoursOut.toFixed(2)} - + ) : null; diff --git a/client/src/components/shop-info/shop-info.scheduling.component.jsx b/client/src/components/shop-info/shop-info.scheduling.component.jsx index 57f0b14d6..e6f81504d 100644 --- a/client/src/components/shop-info/shop-info.scheduling.component.jsx +++ b/client/src/components/shop-info/shop-info.scheduling.component.jsx @@ -102,7 +102,7 @@ export default function ShopInfoSchedulingComponent({ form }) { key={field.key} style={{ padding: 0, margin: 2 }} > -
+ - { - remove(field.name); - }} - /> - -
+
+ { + remove(field.name); + }} + /> + +
+ ))} diff --git a/client/src/debug.log b/client/src/debug.log new file mode 100644 index 000000000..a56efb4ec --- /dev/null +++ b/client/src/debug.log @@ -0,0 +1,4 @@ +[1008/110112.528:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) +[1008/114603.993:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) +[1008/121110.259:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) +[1008/122424.146:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) diff --git a/client/src/graphql/appointments.queries.js b/client/src/graphql/appointments.queries.js index e61b90ebe..e7e0551f2 100644 --- a/client/src/graphql/appointments.queries.js +++ b/client/src/graphql/appointments.queries.js @@ -166,32 +166,23 @@ export const QUERY_APPOINTMENTS_BY_JOBID = gql` `; export const QUERY_SCHEDULE_LOAD_DATA = gql` query QUERY_SCHEDULE_LOAD_DATA($start: timestamptz!, $end: timestamptz!) { - labhrs: joblines_aggregate( - where: { - mod_lbr_ty: { _eq: "LAB" } - job: { inproduction: { _eq: true } } + prodJobs: jobs(where: { inproduction: { _eq: true } }) { + id + labhrs: joblines_aggregate(where: { mod_lbr_ty: { _eq: "LAB" } }) { + aggregate { + sum { + mod_lb_hrs + } + } } - ) { - aggregate { - sum { - mod_lb_hrs + larhrs: joblines_aggregate(where: { mod_lbr_ty: { _eq: "LAR" } }) { + aggregate { + sum { + mod_lb_hrs + } } } } - - larhrs: joblines_aggregate( - where: { - mod_lbr_ty: { _eq: "LAR" } - job: { inproduction: { _eq: true } } - } - ) { - aggregate { - sum { - mod_lb_hrs - } - } - } - compJobs: jobs( where: { scheduled_completion: { _gte: $start, _lte: $end } } ) { diff --git a/client/src/redux/application/application.sagas.js b/client/src/redux/application/application.sagas.js index 69bd72263..5b2864002 100644 --- a/client/src/redux/application/application.sagas.js +++ b/client/src/redux/application/application.sagas.js @@ -1,12 +1,13 @@ -import { all, takeLatest, call, put } from "redux-saga/effects"; -import ApplicationActionTypes from "./application.types"; -import client from "../../utils/GraphQLClient"; +import moment from "moment"; +import { all, call, put, select, takeLatest } from "redux-saga/effects"; import { QUERY_SCHEDULE_LOAD_DATA } from "../../graphql/appointments.queries"; +import client from "../../utils/GraphQLClient"; +import { CalculateLoad, CheckJobBucket } from "../../utils/SSSUtils"; import { scheduleLoadFailure, scheduleLoadSuccess, } from "./application.actions"; -import moment from "moment"; +import ApplicationActionTypes from "./application.types"; export function* onCalculateScheduleLoad() { yield takeLatest( @@ -17,6 +18,9 @@ export function* onCalculateScheduleLoad() { export function* calculateScheduleLoad({ payload: end }) { //REMINDER: Moment.js is not immutable. Today WILL change when adjusted. const today = moment(new Date()).startOf("day"); + const state = yield select(); + const buckets = state.user.bodyshop.ssbuckets; + try { const result = yield client.query({ query: QUERY_SCHEDULE_LOAD_DATA, @@ -26,14 +30,26 @@ export function* calculateScheduleLoad({ payload: end }) { }, fetchPolicy: "network-only", }); + const { prodJobs, arrJobs, compJobs } = result.data; - let load = { - productionHoursTotal: - result.data.larhrs.aggregate.sum.mod_lb_hrs + - result.data.labhrs.aggregate.sum.mod_lb_hrs, + const load = { + productionTotal: {}, }; - const { arrJobs, compJobs } = result.data; + //Set the current load. + buckets.forEach((bucket) => { + load.productionTotal[bucket.id] = { count: 0, label: bucket.label }; + }); + + prodJobs.forEach((item) => { + const bucketId = CheckJobBucket(buckets, item); + if (bucketId) { + load.productionTotal[bucketId].count = + load.productionTotal[bucketId].count + 1; + } else { + console.log("Uh oh, this job doesn't fit in a bucket!", item); + } + }); arrJobs.forEach((item) => { const itemDate = moment(item.scheduled_in).format("yyyy-MM-DD"); @@ -84,17 +100,23 @@ export function* calculateScheduleLoad({ payload: end }) { } if (day === 0) { //Starting on day 1. The load is current. - load[current].expectedLoad = - load.productionHoursTotal + - (load[current].hoursIn || 0) - - (load[current].hoursOut || 0); + load[current].expectedLoad = CalculateLoad( + load.productionTotal, + buckets, + load[current].jobsIn || [], + load[current].jobsOut || [] + ); } else { - load[current].expectedLoad = - load[prev].expectedLoad + - (load[current].hoursIn || 0) - - (load[current].hoursOut || 0); + load[current].expectedLoad = CalculateLoad( + load[prev].expectedLoad, + buckets, + load[current].jobsIn || [], + load[current].jobsOut || [] + ); } + console.log(load); } + yield put(scheduleLoadSuccess(load)); } catch (error) { //console.log("Error in sendEmailFailure saga.", error.message); diff --git a/client/src/utils/SSSUtils.js b/client/src/utils/SSSUtils.js new file mode 100644 index 000000000..f64777fcc --- /dev/null +++ b/client/src/utils/SSSUtils.js @@ -0,0 +1,42 @@ +export const CheckJobBucket = (buckets, job) => { + const jobHours = + job.labhrs.aggregate.sum.mod_lb_hrs + job.larhrs.aggregate.sum.mod_lb_hrs; + + const matchingBucket = buckets.filter( + (b) => b.gte <= jobHours && b.lt > jobHours + ); + + return matchingBucket[0] && matchingBucket[0].id; +}; + +export const CalculateLoad = (currentLoad, buckets, jobsIn, jobsOut) => { + //Add the jobs coming + const newLoad = { ...currentLoad }; + jobsIn.forEach((job) => { + const bucketId = CheckJobBucket(buckets, job); + if (bucketId) { + newLoad[bucketId].count = newLoad[bucketId].count + 1; + } else { + console.log( + "[Util Arr Job]Uh oh, this job doesn't fit in a bucket!", + job + ); + } + }); + + jobsOut.forEach((job) => { + const bucketId = CheckJobBucket(buckets, job); + if (bucketId) { + newLoad[bucketId].count = newLoad[bucketId].count - 1; + } else { + console.log( + "[Util Arr Job]Uh oh, this job doesn't fit in a bucket!", + job + ); + } + }); + + console.log("newLoad", newLoad); + + return newLoad; +};