From 045346ce4864892da019668fc33f5b18c814c05d Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Tue, 20 Oct 2020 13:55:35 -0700 Subject: [PATCH] WIP for Reporting. Pulled out calculations to utility functions. --- .../jobs-targets-stats.molecule.jsx | 40 ++++--------- .../reporting-dates.molecule.jsx | 44 ++++++++++++++ .../sider-sign-out.molecule.jsx | 4 +- .../sider-menu/sider-menu.organism.jsx | 4 ++ .../pages/reporting/reporting.page.jsx | 10 ++++ src/components/pages/routes/routes.page.jsx | 9 ++- .../pages/settings/settings.page.jsx | 8 --- src/graphql/reporting.queries.js | 47 +++++++++++++++ src/redux/application/application.sagas.js | 24 ++++---- src/redux/reporting/reporting.actions.js | 24 ++++++++ src/redux/reporting/reporting.reducer.js | 23 ++++++++ src/redux/reporting/reporting.sagas.js | 57 +++++++++++++++++++ src/redux/reporting/reporting.selectors.js | 49 ++++++++++++++++ src/redux/reporting/reporting.types.js | 8 +++ src/redux/root.reducer.js | 4 +- src/redux/root.saga.js | 4 +- src/util/CalculateJobRps.js | 31 ++++++++++ src/util/GetJobTarget.js | 12 ++++ 18 files changed, 345 insertions(+), 57 deletions(-) create mode 100644 src/components/molecules/reporting-dates/reporting-dates.molecule.jsx create mode 100644 src/components/pages/reporting/reporting.page.jsx create mode 100644 src/graphql/reporting.queries.js create mode 100644 src/redux/reporting/reporting.actions.js create mode 100644 src/redux/reporting/reporting.reducer.js create mode 100644 src/redux/reporting/reporting.sagas.js create mode 100644 src/redux/reporting/reporting.selectors.js create mode 100644 src/redux/reporting/reporting.types.js create mode 100644 src/util/CalculateJobRps.js create mode 100644 src/util/GetJobTarget.js diff --git a/src/components/molecules/jobs-targets-stats/jobs-targets-stats.molecule.jsx b/src/components/molecules/jobs-targets-stats/jobs-targets-stats.molecule.jsx index 82b53ab..2c7008e 100644 --- a/src/components/molecules/jobs-targets-stats/jobs-targets-stats.molecule.jsx +++ b/src/components/molecules/jobs-targets-stats/jobs-targets-stats.molecule.jsx @@ -1,9 +1,12 @@ import { Skeleton, Statistic } from "antd"; -import Dinero from "dinero.js"; -import React, { useMemo } from "react"; +import React, { useCallback } from "react"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { selectSelectedJobTargetPc } from "../../../redux/application/application.selectors"; +import { + CalculateJobRpsDollars, + CalculateJobRpsPc, +} from "../../../util/CalculateJobRps"; import ErrorResultAtom from "../../atoms/error-result/error-result.atom"; const mapStateToProps = createStructuredSelector({ @@ -22,35 +25,12 @@ export function JobsTargetsStatsMolecule({ job, selectedJobTargetPc, }) { - const currentRpsDollars = useMemo(() => { - if (!job) { - return 0; - } - return job.joblines - .filter((j) => !j.ignore) - .reduce((acc, val) => { - if (val.price_diff > 0) { - return acc.add( - Dinero({ amount: Math.round((val.price_diff || 0) * 100) }) - ); - } else { - return acc; - } - }, Dinero()); - }, [job]); + const currentRpsDollars = useCallback(CalculateJobRpsDollars(job), [job]); - const currentRpsPc = useMemo(() => { - //TODO Redo this to do total of db price - act price / db price - if (!job) { - return 0; - } - const dbPriceSum = job.joblines - .filter((j) => !j.ignore) - .reduce((acc, val) => { - return acc + val.db_price; - }, 0); - return (currentRpsDollars.getAmount() / dbPriceSum).toFixed(1); - }, [job, currentRpsDollars]); + const currentRpsPc = useCallback(CalculateJobRpsPc(job, currentRpsDollars), [ + job, + currentRpsDollars, + ]); if (loading) return ; if (!job) return ; diff --git a/src/components/molecules/reporting-dates/reporting-dates.molecule.jsx b/src/components/molecules/reporting-dates/reporting-dates.molecule.jsx new file mode 100644 index 0000000..b908a9a --- /dev/null +++ b/src/components/molecules/reporting-dates/reporting-dates.molecule.jsx @@ -0,0 +1,44 @@ +import { Button, DatePicker, Form } from "antd"; +import React from "react"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { queryReportingData } from "../../../redux/reporting/reporting.actions"; +const mapStateToProps = createStructuredSelector({ + //currentUser: selectCurrentUser +}); +const mapDispatchToProps = (dispatch) => ({ + queryReportingData: (dates) => dispatch(queryReportingData(dates)), +}); +export default connect( + mapStateToProps, + mapDispatchToProps +)(ReportingDatesMolecule); + +export function ReportingDatesMolecule({ queryReportingData }) { + const [form] = Form.useForm(); + + const handleFinish = (values) => { + console.log("values", values); + queryReportingData({ + startDate: values.dateRange[0], + endDate: values.dateRange[1], + }); + }; + + return ( +
+
+ + + + +
+
+ ); +} diff --git a/src/components/molecules/sider-sign-out/sider-sign-out.molecule.jsx b/src/components/molecules/sider-sign-out/sider-sign-out.molecule.jsx index daed0b4..9e50dd6 100644 --- a/src/components/molecules/sider-sign-out/sider-sign-out.molecule.jsx +++ b/src/components/molecules/sider-sign-out/sider-sign-out.molecule.jsx @@ -1,4 +1,4 @@ -import { UserOutlined } from "@ant-design/icons"; +import { LogoutOutlined } from "@ant-design/icons"; import { Menu } from "antd"; import React from "react"; import { connect } from "react-redux"; @@ -11,7 +11,7 @@ const mapDispatchToProps = (dispatch) => ({ export function SiderSignOut({ signOutStart, ...restProps }) { return ( } + icon={} {...restProps} onClick={() => signOutStart()} > diff --git a/src/components/organisms/sider-menu/sider-menu.organism.jsx b/src/components/organisms/sider-menu/sider-menu.organism.jsx index 7c02dd4..e82410e 100644 --- a/src/components/organisms/sider-menu/sider-menu.organism.jsx +++ b/src/components/organisms/sider-menu/sider-menu.organism.jsx @@ -2,6 +2,7 @@ import { PieChartOutlined, SettingFilled, CloseOutlined, + BarChartOutlined, } from "@ant-design/icons"; import { Menu } from "antd"; import React from "react"; @@ -19,6 +20,9 @@ export default function SiderMenuOrganism() { }> Jobs + }> + Reporting + }> Settings diff --git a/src/components/pages/reporting/reporting.page.jsx b/src/components/pages/reporting/reporting.page.jsx new file mode 100644 index 0000000..73a57d9 --- /dev/null +++ b/src/components/pages/reporting/reporting.page.jsx @@ -0,0 +1,10 @@ +import React from "react"; +import ReportingDatesMolecule from "../../molecules/reporting-dates/reporting-dates.molecule"; + +export default function ReportingPage() { + return ( +
+ +
+ ); +} diff --git a/src/components/pages/routes/routes.page.jsx b/src/components/pages/routes/routes.page.jsx index cf05db8..4709e39 100644 --- a/src/components/pages/routes/routes.page.jsx +++ b/src/components/pages/routes/routes.page.jsx @@ -3,11 +3,13 @@ import React from "react"; import { connect } from "react-redux"; import { Route, Switch } from "react-router-dom"; import { createStructuredSelector } from "reselect"; -import SiderMenuOrganism from "../../organisms/sider-menu/sider-menu.organism"; -import Jobs from "../jobs/jobs.page"; -import SettingsPage from "../settings/settings.page"; import { selectBodyshop } from "../../../redux/user/user.selectors"; import ErrorResultAtom from "../../atoms/error-result/error-result.atom"; +import SiderMenuOrganism from "../../organisms/sider-menu/sider-menu.organism"; +import Jobs from "../jobs/jobs.page"; +import ReportingPage from "../reporting/reporting.page"; +import SettingsPage from "../settings/settings.page"; + const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({}); @@ -32,6 +34,7 @@ export function RoutesPage({ bodyshop }) { + diff --git a/src/components/pages/settings/settings.page.jsx b/src/components/pages/settings/settings.page.jsx index f23e018..8455ac5 100644 --- a/src/components/pages/settings/settings.page.jsx +++ b/src/components/pages/settings/settings.page.jsx @@ -28,11 +28,3 @@ export default function SettingsPage() { ); } - -// diff --git a/src/graphql/reporting.queries.js b/src/graphql/reporting.queries.js new file mode 100644 index 0000000..a7bbfb5 --- /dev/null +++ b/src/graphql/reporting.queries.js @@ -0,0 +1,47 @@ +import gql from "graphql-tag"; + +export const REPORTING_GET_JOBS = gql` + query REPORTING_GET_JOBS($startDate: date, $endDate: date) { + jobs( + where: { + _and: [ + { close_date: { _gte: $startDate } } + { close_date: { _lte: $endDate } } + { close_date: { _is_null: false } } + ] + } + ) { + ownr_ln + ownr_fn + ins_co_nm + group + clm_total + clm_no + close_date + id + loss_date + updated_at + v_age + v_makedesc + v_model + v_model_yr + joblines { + act_price + db_price + part_qty + part_type + price_diff + price_diff_pc + updated_at + oem_partno + line_no + line_ind + line_desc + ignore + id + db_ref + unq_seq + } + } + } +`; diff --git a/src/redux/application/application.sagas.js b/src/redux/application/application.sagas.js index 38fbe52..c69a008 100644 --- a/src/redux/application/application.sagas.js +++ b/src/redux/application/application.sagas.js @@ -1,4 +1,5 @@ import { all, call, takeLatest, select, put } from "redux-saga/effects"; +import GetJobTarget from "../../util/GetJobTarget"; import { setSelectedJobTargetPcSuccess } from "./application.actions"; import ApplicationActionTypes from "./application.types"; @@ -12,17 +13,18 @@ export function* CalculateTarget({ payload }) { const { group, v_age } = payload; const targets = yield select((state) => state.user.bodyshop.targets); - const targetsForGroup = targets.filter((t) => t.group === group); - if (!targetsForGroup) return 0; - const targetPc = targetsForGroup.filter( - (t) => t.ageGte <= v_age && (t.ageLt ? t.ageLt > v_age : true) - ); - if (targetPc.length === 0) yield put(setSelectedJobTargetPcSuccess(100)); - else if (targetPc.length === 1) - yield put(setSelectedJobTargetPcSuccess(targetPc[0].target)); - else { - yield put(setSelectedJobTargetPcSuccess(100)); - } + yield put(setSelectedJobTargetPcSuccess(GetJobTarget(group, v_age, targets))); + // const targetsForGroup = targets.filter((t) => t.group === group); + // if (!targetsForGroup) return 0; + // const targetPc = targetsForGroup.filter( + // (t) => t.ageGte <= v_age && (t.ageLt ? t.ageLt > v_age : true) + // ); + // if (targetPc.length === 0) yield put(setSelectedJobTargetPcSuccess(100)); + // else if (targetPc.length === 1) + // yield put(setSelectedJobTargetPcSuccess(targetPc[0].target)); + // else { + // yield put(setSelectedJobTargetPcSuccess(100)); + // } } export function* applicationSagas() { diff --git a/src/redux/reporting/reporting.actions.js b/src/redux/reporting/reporting.actions.js new file mode 100644 index 0000000..a7aa440 --- /dev/null +++ b/src/redux/reporting/reporting.actions.js @@ -0,0 +1,24 @@ +import ReportingActionTypes from "./reporting.types"; + +export const queryReportingData = ({ startDate, endDate }) => ({ + type: ReportingActionTypes.QUERY_REPORTING_DATA, + payload: { startDate, endDate }, +}); + +export const setReportingData = (data) => ({ + type: ReportingActionTypes.SET_REPORTING_DATA, + payload: data, +}); + +export const calculateScorecard = (data) => ({ + type: ReportingActionTypes.CALCULATE_SCORE_CARD, + payload: data, +}); +export const setScoreCard = (data) => ({ + type: ReportingActionTypes.SET_SCORE_CARD, + payload: data, +}); +export const setReportingError = (data) => ({ + type: ReportingActionTypes.SET_REPORTING_ERROR, + payload: data, +}); diff --git a/src/redux/reporting/reporting.reducer.js b/src/redux/reporting/reporting.reducer.js new file mode 100644 index 0000000..f77390c --- /dev/null +++ b/src/redux/reporting/reporting.reducer.js @@ -0,0 +1,23 @@ +import ReportingActionTypes from "./reporting.types"; +const INITIAL_STATE = { + dates: { startDate: null, endDate: null }, + data: [], + scoreCard: null, + error: null, + loading: false, +}; + +const applicationReducer = (state = INITIAL_STATE, action) => { + switch (action.type) { + case ReportingActionTypes.QUERY_REPORTING_DATA: + return { ...state, loading: true, dates: action.payload }; + case ReportingActionTypes.SET_REPORTING_DATA: + return { ...state, data: action.payload }; + case ReportingActionTypes.SET_SCORE_CARD: + return { ...state, loading: false, scoreCard: action.payload }; + default: + return state; + } +}; + +export default applicationReducer; diff --git a/src/redux/reporting/reporting.sagas.js b/src/redux/reporting/reporting.sagas.js new file mode 100644 index 0000000..5d02944 --- /dev/null +++ b/src/redux/reporting/reporting.sagas.js @@ -0,0 +1,57 @@ +import { all, call, takeLatest, select, put } from "redux-saga/effects"; +import { calculateScorecard, setReportingData } from "./reporting.actions"; +import ReportingApplicationTypes from "./reporting.types"; +import client from "../../graphql/GraphQLClient"; +import { REPORTING_GET_JOBS } from "../../graphql/reporting.queries"; +const { log } = window; + +export function* onQueryReportData() { + yield takeLatest( + ReportingApplicationTypes.QUERY_REPORTING_DATA, + queryReportingData + ); +} +export function* queryReportingData({ payload: { startDate, endDate } }) { + const result = yield client.query({ + query: REPORTING_GET_JOBS, + variables: { startDate, endDate }, + }); + if (result.errors) { + log.error("Error fetching report data.", result.errors); + yield put(setReportingData(null)); + } else { + yield put(setReportingData(result.data.jobs)); + } +} + +export function* onSetReportData() { + yield takeLatest( + ReportingApplicationTypes.SET_REPORTING_DATA, + handleSetReportData + ); +} +export function* handleSetReportData({ payload: jobs }) { + yield put(calculateScorecard(jobs)); +} + +export function* onCalculateScoreCard() { + yield takeLatest( + ReportingApplicationTypes.CALCULATE_SCORE_CARD, + handleCalculateScoreCard + ); +} +export function* handleCalculateScoreCard({ payload: jobs }) { + console.log("jobs", jobs); + // yield put(calculateScorecard(jobs)); + + //Get the RPS on a per job basis. + +} + +export function* reportingSagas() { + yield all([ + call(onQueryReportData), + call(onSetReportData), + call(onCalculateScoreCard), + ]); +} diff --git a/src/redux/reporting/reporting.selectors.js b/src/redux/reporting/reporting.selectors.js new file mode 100644 index 0000000..7780c2a --- /dev/null +++ b/src/redux/reporting/reporting.selectors.js @@ -0,0 +1,49 @@ +import { createSelector } from "reselect"; + +const selectReporting = (state) => state.reporting; + +export const selectReportLoading = createSelector( + [selectReporting], + (reporting) => reporting.loading +); +export const selectDates = createSelector( + [selectReporting], + (reporting) => reporting.dates +); +export const selectScorecard = createSelector( + [selectReporting], + (reporting) => reporting.scoreCard +); +export const selectReportingError = createSelector( + [selectReporting], + (reporting) => reporting.error +); +export const selectReportData = createSelector( + [selectReporting], + (reporting) => reporting.data +); + +// export const selectWatchedPaths = createSelector( +// [selectReporting], +// (application) => application.watchedPaths +// ); + +// export const selectWatcherError = createSelector( +// [selectReporting], +// (application) => application.watcherError +// ); + +// export const selectSelectedJobId = createSelector( +// [selectReporting], +// (application) => application.selectedJobId +// ); + +// export const selectSelectedJobTargetPc = createSelector( +// [selectReporting], +// (application) => application.selectedJobTargetPc +// ); + +// export const selectSettings = createSelector( +// [selectReporting], +// (application) => application.settings +// ); diff --git a/src/redux/reporting/reporting.types.js b/src/redux/reporting/reporting.types.js new file mode 100644 index 0000000..18c0755 --- /dev/null +++ b/src/redux/reporting/reporting.types.js @@ -0,0 +1,8 @@ +const ReportingActionTypes = { + QUERY_REPORTING_DATA: "QUERY_REPORTING_DATA", + CALCULATE_SCORE_CARD: "CALCULATE_SCORE_CARD", + SET_REPORTING_DATA: "SET_REPORTING_DATA", + SET_SCORE_CARD: "SET_SCORE_CARD", + SET_REPORTING_ERROR: "SET_REPORTING_ERROR", +}; +export default ReportingActionTypes; diff --git a/src/redux/root.reducer.js b/src/redux/root.reducer.js index 902b0fd..eaeda26 100644 --- a/src/redux/root.reducer.js +++ b/src/redux/root.reducer.js @@ -3,16 +3,18 @@ import { persistReducer } from "redux-persist"; import storage from "redux-persist/lib/storage"; import applicationReducer from "./application/application.reducer"; import userReducer from "./user/user.reducer"; +import reportingReducer from "./reporting/reporting.reducer"; const persistConfig = { key: "root", storage, - blacklist: ["application", "user"], + blacklist: ["application", "user", "reporting"], }; const rootReducer = combineReducers({ application: applicationReducer, user: userReducer, + reporting: reportingReducer, }); export default persistReducer(persistConfig, rootReducer); diff --git a/src/redux/root.saga.js b/src/redux/root.saga.js index 64c4946..d348a07 100644 --- a/src/redux/root.saga.js +++ b/src/redux/root.saga.js @@ -1,7 +1,7 @@ import { all, call } from "redux-saga/effects"; import { applicationSagas } from "./application/application.sagas"; import { userSagas } from "./user/user.sagas"; - +import { reportingSagas } from "./reporting/reporting.sagas"; export default function* rootSaga() { - yield all([call(applicationSagas), call(userSagas)]); + yield all([call(applicationSagas), call(userSagas), call(reportingSagas)]); } diff --git a/src/util/CalculateJobRps.js b/src/util/CalculateJobRps.js new file mode 100644 index 0000000..f31aa09 --- /dev/null +++ b/src/util/CalculateJobRps.js @@ -0,0 +1,31 @@ +import Dinero from "dinero.js"; + +export function CalculateJobRpsDollars(job) { + if (!job) { + return 0; + } + return job.joblines + .filter((j) => !j.ignore) + .reduce((acc, val) => { + if (val.price_diff > 0) { + return acc.add( + Dinero({ amount: Math.round((val.price_diff || 0) * 100) }) + ); + } else { + return acc; + } + }, Dinero()); +} + +export function CalculateJobRpsPc(job, currentRpsDollars) { + //TODO Redo this to do total of db price - act price / db price + if (!job) { + return 0; + } + const dbPriceSum = job.joblines + .filter((j) => !j.ignore) + .reduce((acc, val) => { + return acc + val.db_price; + }, 0); + return (currentRpsDollars.getAmount() / dbPriceSum).toFixed(1); +} diff --git a/src/util/GetJobTarget.js b/src/util/GetJobTarget.js new file mode 100644 index 0000000..4919eaa --- /dev/null +++ b/src/util/GetJobTarget.js @@ -0,0 +1,12 @@ +export default function GetJobTarget(group, v_age, targets) { + const targetsForGroup = targets.filter((t) => t.group === group); + if (!targetsForGroup) return 0; + const targetPc = targetsForGroup.filter( + (t) => t.ageGte <= v_age && (t.ageLt ? t.ageLt > v_age : true) + ); + if (targetPc.length === 0) return 100; + else if (targetPc.length === 1) return targetPc[0].target; + else { + return 100; + } +}