@@ -326,28 +326,27 @@ async function DecodeLinFile(extensionlessFilePath) {
|
||||
}
|
||||
);
|
||||
})
|
||||
.filter(
|
||||
(jobline) =>
|
||||
jobline.part_type &&
|
||||
!jobline.db_ref.startsWith("900") &&
|
||||
!jobline.db_ref.toLowerCase().startsWith("urethane") &&
|
||||
!jobline.db_ref.toLowerCase().startsWith("wheel") &&
|
||||
!jobline.db_ref.toLowerCase().startsWith("hazardous") &&
|
||||
!jobline.db_ref.toLowerCase().startsWith("detail") &&
|
||||
!jobline.db_ref.toLowerCase().startsWith("clean") &&
|
||||
jobline.part_type.toUpperCase() !== "PAG" &&
|
||||
jobline.part_type.toUpperCase() !== "PAS" &&
|
||||
jobline.part_type.toUpperCase() !== "PASL" &&
|
||||
jobline.part_type.toUpperCase() !== "PAE" &&
|
||||
jobline.glass_flag === false
|
||||
)
|
||||
// .filter(
|
||||
// (jobline) =>
|
||||
// jobline.part_type &&
|
||||
// !jobline.db_ref.startsWith("900") &&
|
||||
// !jobline.line_desc.toLowerCase().startsWith("urethane") &&
|
||||
// !jobline.line_desc.toLowerCase().startsWith("wheel") &&
|
||||
// !jobline.line_desc.toLowerCase().startsWith("hazardous") &&
|
||||
// !jobline.line_desc.toLowerCase().startsWith("detail") &&
|
||||
// !jobline.line_desc.toLowerCase().startsWith("clean") &&
|
||||
// jobline.part_type.toUpperCase() !== "PAG" &&
|
||||
// jobline.part_type.toUpperCase() !== "PAS" &&
|
||||
// jobline.part_type.toUpperCase() !== "PASL" &&
|
||||
// jobline.part_type.toUpperCase() !== "PAE" &&
|
||||
// jobline.glass_flag === false
|
||||
// )
|
||||
.map((jobline) => {
|
||||
if (
|
||||
(jobline.db_price === null || jobline.db_price === 0) &&
|
||||
!!jobline.act_price &&
|
||||
jobline.act_price > 0
|
||||
) {
|
||||
console.log(1, jobline.line_desc, jobline.db_price, jobline.act_price);
|
||||
log.info(
|
||||
"DB Price null/lower than act price",
|
||||
jobline.line_desc,
|
||||
@@ -368,10 +367,25 @@ async function DecodeLinFile(extensionlessFilePath) {
|
||||
jobline.db_price,
|
||||
jobline.act_price
|
||||
);
|
||||
console.log(2, jobline.line_desc, jobline.db_price, jobline.act_price);
|
||||
jobline.db_price = jobline.act_price;
|
||||
}
|
||||
|
||||
if (
|
||||
!jobline.part_type ||
|
||||
jobline.db_ref.startsWith("900") ||
|
||||
jobline.line_desc.toLowerCase().startsWith("urethane") ||
|
||||
jobline.line_desc.toLowerCase().startsWith("wheel") ||
|
||||
jobline.line_desc.toLowerCase().startsWith("hazardous") ||
|
||||
jobline.line_desc.toLowerCase().startsWith("detail") ||
|
||||
jobline.line_desc.toLowerCase().startsWith("clean") ||
|
||||
jobline.part_type.toUpperCase() === "PAG" ||
|
||||
jobline.part_type.toUpperCase() === "PAS" ||
|
||||
jobline.part_type.toUpperCase() === "PASL" ||
|
||||
jobline.part_type.toUpperCase() === "PAE" ||
|
||||
jobline.glass_flag === true
|
||||
)
|
||||
jobline.ignore = true;
|
||||
|
||||
delete jobline.glass_flag;
|
||||
return jobline;
|
||||
});
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
const Store = require("electron-store");
|
||||
|
||||
const store = new Store({ defaults: { filePaths: [], accepted_ins_co: [] } });
|
||||
const store = new Store({
|
||||
defaults: {
|
||||
filePaths: [],
|
||||
accepted_ins_co: [],
|
||||
polling: {
|
||||
enabled: false,
|
||||
pollingInterval: 100,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
exports.store = store;
|
||||
|
||||
@@ -7,35 +7,38 @@ const { store } = require("../electron-store");
|
||||
const {
|
||||
NewNotification,
|
||||
} = require("../notification-wrapper/notification-wrapper");
|
||||
|
||||
const log = require("electron-log");
|
||||
var watcher;
|
||||
|
||||
async function StartWatcher() {
|
||||
const filePaths =
|
||||
store.get("filePaths").map((fp) => path.join(fp, "**.[eE][nN][vV]")) || [];
|
||||
console.log("StartWatcher -> filePaths", filePaths);
|
||||
|
||||
log.info("StartWatcher -> filePaths", filePaths);
|
||||
log.info("Use polling? ", store.get("polling").enabled);
|
||||
|
||||
if (filePaths.length === 0) {
|
||||
NewNotification({
|
||||
title: "RPS Watcher cannot start",
|
||||
body: "Please set the appropriate file paths and try again.",
|
||||
}).show();
|
||||
log.warn("Cannot start watcher. No file paths set.");
|
||||
return [];
|
||||
}
|
||||
|
||||
if (watcher) {
|
||||
try {
|
||||
console.log("Trying to close watcher - it already existed.");
|
||||
log.info("Trying to close watcher - it already existed.");
|
||||
await watcher.close();
|
||||
|
||||
console.log("Watcher closed successfully!");
|
||||
log.info("Watcher closed successfully!");
|
||||
} catch (error) {
|
||||
console.log("Error trying to close Watcher.", error);
|
||||
log.error("Error trying to close Watcher.", error);
|
||||
}
|
||||
}
|
||||
|
||||
watcher = chokidar.watch(filePaths, {
|
||||
//ignored: /[\/\\]\./,
|
||||
usePolling: store.get("polling").enabled,
|
||||
persistent: true,
|
||||
ignoreInitial: true,
|
||||
awaitWriteFinish: {
|
||||
@@ -88,7 +91,7 @@ function onWatcherReady() {
|
||||
|
||||
async function StopWatcher() {
|
||||
await watcher.close();
|
||||
console.log("Watcher stopped.");
|
||||
log.info("Watcher stopped.");
|
||||
const b = BrowserWindow.getAllWindows()[0];
|
||||
b.webContents.send(ipcTypes.default.fileWatcher.toRenderer.stopSuccess);
|
||||
NewNotification({
|
||||
@@ -111,12 +114,13 @@ async function HandleNewFile(path) {
|
||||
ipcTypes.default.estimate.toRenderer.estimateDecodeSuccess,
|
||||
newJob
|
||||
);
|
||||
|
||||
log.info(`Sent job for upload. ${newJob.clm_no}`);
|
||||
NewNotification({
|
||||
title: "Job Uploaded",
|
||||
body: "A new job has been uploaded.",
|
||||
}).show();
|
||||
} else {
|
||||
log.info(`Ignored job. ${newJob.ERROR}`);
|
||||
NewNotification({
|
||||
title: "Job Ignored",
|
||||
body: newJob.ERROR,
|
||||
|
||||
@@ -17,3 +17,20 @@ ipcMain.on("test", async (event, object) => {
|
||||
ipcMain.on(ipcTypes.app.toMain.setAcceptableInsCoNm, (event, insCos) => {
|
||||
store.set("accepted_ins_co", insCos);
|
||||
});
|
||||
|
||||
ipcMain.on(ipcTypes.store.get, (event, key) => {
|
||||
const val = store.get(key);
|
||||
event.sender.send(ipcTypes.store.response, { [key]: val });
|
||||
});
|
||||
|
||||
ipcMain.on(ipcTypes.store.set, (event, key, val) => {
|
||||
store.set(key, val);
|
||||
|
||||
const st = store.get();
|
||||
event.sender.send(ipcTypes.store.response, st);
|
||||
});
|
||||
|
||||
ipcMain.on(ipcTypes.store.getAll, (event, obj) => {
|
||||
const val = store.get();
|
||||
event.sender.send(ipcTypes.store.response, val);
|
||||
});
|
||||
|
||||
@@ -23,11 +23,11 @@ log.info("App starting...");
|
||||
|
||||
// Conditionally include the dev tools installer to load React Dev Tools
|
||||
let installExtension, REACT_DEVELOPER_TOOLS;
|
||||
// if (isDev) {
|
||||
// const devTools = require("electron-devtools-installer");
|
||||
// installExtension = devTools.default;
|
||||
// REACT_DEVELOPER_TOOLS = devTools.REACT_DEVELOPER_TOOLS;
|
||||
// }
|
||||
if (isDev) {
|
||||
const devTools = require("electron-devtools-installer");
|
||||
installExtension = devTools.default;
|
||||
REACT_DEVELOPER_TOOLS = devTools.REACT_DEVELOPER_TOOLS;
|
||||
}
|
||||
|
||||
var menu = Menu.buildFromTemplate([
|
||||
{
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
[1014/195617.530:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3)
|
||||
[1015/081931.328:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3)
|
||||
[1020/073641.000:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
18
hasura/migrations/1603204459690_run_sql_migration/up.yaml
Normal file
18
hasura/migrations/1603204459690_run_sql_migration/up.yaml
Normal file
@@ -0,0 +1,18 @@
|
||||
- args:
|
||||
cascade: true
|
||||
read_only: false
|
||||
sql: "CREATE OR REPLACE FUNCTION public.search_jobs(search text, startdate date,
|
||||
enddate date)\n RETURNS SETOF jobs\n LANGUAGE plpgsql\n STABLE\nAS $function$
|
||||
BEGIN if search = '' then return query\nselect *\nfrom jobs j;\nelse\n\nif (startDate
|
||||
is null) or (endDate is null) then \nreturn query\nSELECT *\nFROM jobs j2\nWHERE
|
||||
\n\nownr_fn ILIKE '%' || search || '%'\n or ownr_ln ILIKE '%' || search ||
|
||||
'%'\n \n or clm_no ILIKE '%' || search || '%'\nORDER BY \n clm_no ILIKE
|
||||
'%' || search || '%'\n OR null,\n ownr_fn ILIKE '%' || search || '%'\n
|
||||
\ OR NULL,\n ownr_ln ILIKE '%' || search || '%'\n OR NULL;\nelse \nreturn
|
||||
query\nSELECT *\nFROM jobs j2\nWHERE \nclose_date between startDate and endDate
|
||||
and close_date is not null and\n(\nownr_fn ILIKE '%' || search || '%'\n or
|
||||
ownr_ln ILIKE '%' || search || '%'\n \n or clm_no ILIKE '%' || search ||
|
||||
'%')\n\nORDER BY \n clm_no ILIKE '%' || search || '%'\n OR null,\n ownr_fn
|
||||
ILIKE '%' || search || '%'\n OR NULL,\n ownr_ln ILIKE '%' || search ||
|
||||
'%'\n OR NULL;\n\nend if;\n\n\nend if;\nEND $function$;"
|
||||
type: run_sql
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
19
hasura/migrations/1603204604474_run_sql_migration/up.yaml
Normal file
19
hasura/migrations/1603204604474_run_sql_migration/up.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
- args:
|
||||
cascade: true
|
||||
read_only: false
|
||||
sql: "CREATE OR REPLACE FUNCTION public.search_jobs(search text, startdate date,
|
||||
enddate date)\n RETURNS SETOF jobs\n LANGUAGE plpgsql\n STABLE\nAS $function$\nBEGIN
|
||||
if search = '' and ((startDate is null) or (endDate is null)) then return query\nselect
|
||||
*\nfrom jobs j;\nelse\n\nif (startDate is null) or (endDate is null) then \nreturn
|
||||
query\nSELECT *\nFROM jobs j2\nWHERE \n\nownr_fn ILIKE '%' || search || '%'\n
|
||||
\ or ownr_ln ILIKE '%' || search || '%'\n \n or clm_no ILIKE '%' || search
|
||||
|| '%'\nORDER BY \n clm_no ILIKE '%' || search || '%'\n OR null,\n ownr_fn
|
||||
ILIKE '%' || search || '%'\n OR NULL,\n ownr_ln ILIKE '%' || search ||
|
||||
'%'\n OR NULL;\nelse \nreturn query\nSELECT *\nFROM jobs j2\nWHERE \nclose_date
|
||||
between startDate and endDate and close_date is not null and\n(\nownr_fn ILIKE
|
||||
'%' || search || '%'\n or ownr_ln ILIKE '%' || search || '%'\n \n or
|
||||
clm_no ILIKE '%' || search || '%')\n\nORDER BY \n clm_no ILIKE '%' || search
|
||||
|| '%'\n OR null,\n ownr_fn ILIKE '%' || search || '%'\n OR NULL,\n
|
||||
\ ownr_ln ILIKE '%' || search || '%'\n OR NULL;\n\nend if;\n\n\nend if;\nEND
|
||||
$function$;"
|
||||
type: run_sql
|
||||
@@ -0,0 +1,5 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."joblines" DROP COLUMN "ignore";
|
||||
type: run_sql
|
||||
@@ -0,0 +1,6 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."joblines" ADD COLUMN "ignore" boolean NOT NULL DEFAULT
|
||||
false;
|
||||
type: run_sql
|
||||
@@ -0,0 +1,39 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: joblines
|
||||
schema: public
|
||||
type: drop_insert_permission
|
||||
- args:
|
||||
permission:
|
||||
backend_only: false
|
||||
check:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
columns:
|
||||
- act_price
|
||||
- created_at
|
||||
- db_price
|
||||
- db_ref
|
||||
- id
|
||||
- jobid
|
||||
- line_desc
|
||||
- line_ind
|
||||
- line_no
|
||||
- oem_partno
|
||||
- part_qty
|
||||
- part_type
|
||||
- price_diff
|
||||
- price_diff_pc
|
||||
- unq_seq
|
||||
- updated_at
|
||||
set: {}
|
||||
role: user
|
||||
table:
|
||||
name: joblines
|
||||
schema: public
|
||||
type: create_insert_permission
|
||||
@@ -0,0 +1,40 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: joblines
|
||||
schema: public
|
||||
type: drop_insert_permission
|
||||
- args:
|
||||
permission:
|
||||
backend_only: false
|
||||
check:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
columns:
|
||||
- act_price
|
||||
- created_at
|
||||
- db_price
|
||||
- db_ref
|
||||
- id
|
||||
- ignore
|
||||
- jobid
|
||||
- line_desc
|
||||
- line_ind
|
||||
- line_no
|
||||
- oem_partno
|
||||
- part_qty
|
||||
- part_type
|
||||
- price_diff
|
||||
- price_diff_pc
|
||||
- unq_seq
|
||||
- updated_at
|
||||
set: {}
|
||||
role: user
|
||||
table:
|
||||
name: joblines
|
||||
schema: public
|
||||
type: create_insert_permission
|
||||
@@ -0,0 +1,39 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: joblines
|
||||
schema: public
|
||||
type: drop_select_permission
|
||||
- args:
|
||||
permission:
|
||||
allow_aggregations: false
|
||||
columns:
|
||||
- act_price
|
||||
- created_at
|
||||
- db_price
|
||||
- db_ref
|
||||
- id
|
||||
- jobid
|
||||
- line_desc
|
||||
- line_ind
|
||||
- line_no
|
||||
- oem_partno
|
||||
- part_qty
|
||||
- part_type
|
||||
- price_diff
|
||||
- price_diff_pc
|
||||
- unq_seq
|
||||
- updated_at
|
||||
computed_fields: []
|
||||
filter:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
role: user
|
||||
table:
|
||||
name: joblines
|
||||
schema: public
|
||||
type: create_select_permission
|
||||
@@ -0,0 +1,40 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: joblines
|
||||
schema: public
|
||||
type: drop_select_permission
|
||||
- args:
|
||||
permission:
|
||||
allow_aggregations: false
|
||||
columns:
|
||||
- act_price
|
||||
- created_at
|
||||
- db_price
|
||||
- db_ref
|
||||
- id
|
||||
- ignore
|
||||
- jobid
|
||||
- line_desc
|
||||
- line_ind
|
||||
- line_no
|
||||
- oem_partno
|
||||
- part_qty
|
||||
- part_type
|
||||
- price_diff
|
||||
- price_diff_pc
|
||||
- unq_seq
|
||||
- updated_at
|
||||
computed_fields: []
|
||||
filter:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
role: user
|
||||
table:
|
||||
name: joblines
|
||||
schema: public
|
||||
type: create_select_permission
|
||||
@@ -0,0 +1,38 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: joblines
|
||||
schema: public
|
||||
type: drop_update_permission
|
||||
- args:
|
||||
permission:
|
||||
columns:
|
||||
- act_price
|
||||
- created_at
|
||||
- db_price
|
||||
- db_ref
|
||||
- id
|
||||
- jobid
|
||||
- line_desc
|
||||
- line_ind
|
||||
- line_no
|
||||
- oem_partno
|
||||
- part_qty
|
||||
- part_type
|
||||
- price_diff
|
||||
- price_diff_pc
|
||||
- unq_seq
|
||||
- updated_at
|
||||
filter:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
set: {}
|
||||
role: user
|
||||
table:
|
||||
name: joblines
|
||||
schema: public
|
||||
type: create_update_permission
|
||||
@@ -0,0 +1,39 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: joblines
|
||||
schema: public
|
||||
type: drop_update_permission
|
||||
- args:
|
||||
permission:
|
||||
columns:
|
||||
- act_price
|
||||
- created_at
|
||||
- db_price
|
||||
- db_ref
|
||||
- id
|
||||
- ignore
|
||||
- jobid
|
||||
- line_desc
|
||||
- line_ind
|
||||
- line_no
|
||||
- oem_partno
|
||||
- part_qty
|
||||
- part_type
|
||||
- price_diff
|
||||
- price_diff_pc
|
||||
- unq_seq
|
||||
- updated_at
|
||||
filter:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
set: {}
|
||||
role: user
|
||||
table:
|
||||
name: joblines
|
||||
schema: public
|
||||
type: create_update_permission
|
||||
@@ -89,6 +89,7 @@ tables:
|
||||
- db_price
|
||||
- db_ref
|
||||
- id
|
||||
- ignore
|
||||
- jobid
|
||||
- line_desc
|
||||
- line_ind
|
||||
@@ -110,6 +111,7 @@ tables:
|
||||
- db_price
|
||||
- db_ref
|
||||
- id
|
||||
- ignore
|
||||
- jobid
|
||||
- line_desc
|
||||
- line_ind
|
||||
@@ -137,6 +139,7 @@ tables:
|
||||
- db_price
|
||||
- db_ref
|
||||
- id
|
||||
- ignore
|
||||
- jobid
|
||||
- line_desc
|
||||
- line_ind
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"productName": "ImEX RPS",
|
||||
"author": "ImEX Systems Inc. <support@thinkimex.com>",
|
||||
"description": "ImEX RPS",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"main": "electron/main.js",
|
||||
"homepage": "./",
|
||||
"dependencies": {
|
||||
|
||||
26
src/components/atoms/data-label/data-label.atom.jsx
Normal file
26
src/components/atoms/data-label/data-label.atom.jsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from "react";
|
||||
|
||||
export default function DataLabel({
|
||||
label,
|
||||
hideIfNull,
|
||||
children,
|
||||
vertical,
|
||||
visible = true,
|
||||
...props
|
||||
}) {
|
||||
if (!visible || (hideIfNull && !!!children)) return null;
|
||||
|
||||
return (
|
||||
<div {...props}>
|
||||
<div
|
||||
style={{
|
||||
display: vertical ? "block" : "inline-block",
|
||||
marginRight: ".2rem",
|
||||
}}
|
||||
>{`${label}: `}</div>
|
||||
<div style={{ display: vertical ? "block" : "inline-block" }}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { message, Switch } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { UPDATE_JOB_LINE } from "../../../graphql/joblines.queries";
|
||||
const { log } = window;
|
||||
|
||||
export default function IgnoreJobLineAtom({ ignore, lineId }) {
|
||||
const [updateJobLine] = useMutation(UPDATE_JOB_LINE);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const handleChange = async (checked) => {
|
||||
setLoading(true);
|
||||
const result = await updateJobLine({
|
||||
variables: { lineId: lineId, line: { ignore: checked } },
|
||||
});
|
||||
if (result.errors) {
|
||||
message.error("Error updating line.");
|
||||
log.error("Error updating job.", result.errors);
|
||||
} else {
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
return <Switch checked={ignore} onChange={handleChange} loading={loading} />;
|
||||
}
|
||||
@@ -12,17 +12,19 @@ export default function JobPartsGraphAtom({
|
||||
const data = useMemo(() => {
|
||||
if (!job) return [];
|
||||
|
||||
const sums = job.joblines.reduce((acc, val) => {
|
||||
if (!acc[val.part_type]) {
|
||||
acc[val.part_type] = Dinero();
|
||||
}
|
||||
const sums = job.joblines
|
||||
.filter((j) => !j.ignore)
|
||||
.reduce((acc, val) => {
|
||||
if (!acc[val.part_type]) {
|
||||
acc[val.part_type] = Dinero();
|
||||
}
|
||||
|
||||
acc[val.part_type] = acc[val.part_type].add(
|
||||
Dinero({ amount: Math.round((val[price] || 0) * 100) })
|
||||
);
|
||||
acc[val.part_type] = acc[val.part_type].add(
|
||||
Dinero({ amount: Math.round((val[price] || 0) * 100) })
|
||||
);
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return Object.keys(sums).map((key) => {
|
||||
return {
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
export default (part_type) => {
|
||||
switch (part_type) {
|
||||
case "PAA":
|
||||
case "PAL":
|
||||
case "PAC":
|
||||
return "A/M";
|
||||
case "PAE":
|
||||
return "Exist.";
|
||||
case "PAN":
|
||||
case "PAP":
|
||||
return "OEM";
|
||||
case "PAL":
|
||||
return "LKQ";
|
||||
|
||||
default:
|
||||
return "?";
|
||||
return part_type;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Typography } from "antd";
|
||||
import React from "react";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectDates } from "../../../redux/reporting/reporting.selectors";
|
||||
import moment from "moment";
|
||||
import { DateFormat } from "../../../util/constants";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
dates: selectDates,
|
||||
});
|
||||
|
||||
export function ReportingTitleAtom({ dates }) {
|
||||
return (
|
||||
<Typography.Title level={2}>
|
||||
{`RPS Report for Period from ${moment(dates.startDate).format(
|
||||
DateFormat
|
||||
)} to ${moment(dates.endDate).format(DateFormat)}`}
|
||||
</Typography.Title>
|
||||
);
|
||||
}
|
||||
export default connect(mapStateToProps, null)(ReportingTitleAtom);
|
||||
@@ -3,6 +3,7 @@ import { DatePicker, message, Spin } from "antd";
|
||||
import moment from "moment";
|
||||
import React, { useState } from "react";
|
||||
import { UPDATE_JOB } from "../../../graphql/jobs.queries";
|
||||
import { DateFormat } from "../../../util/constants";
|
||||
|
||||
export default function CloseDateDisplayMolecule({ jobId, close_date }) {
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
@@ -29,7 +30,7 @@ export default function CloseDateDisplayMolecule({ jobId, close_date }) {
|
||||
return (
|
||||
<div onBlur={() => setEditMode(false)}>
|
||||
<DatePicker
|
||||
value={value.isValid() ? value : null}
|
||||
value={value && value.isValid() ? value : null}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
{loading && <Spin size="small" />}
|
||||
@@ -38,7 +39,7 @@ export default function CloseDateDisplayMolecule({ jobId, close_date }) {
|
||||
|
||||
return (
|
||||
<div style={{ cursor: "pointer" }} onClick={() => setEditMode(true)}>
|
||||
{value.isValid() ? value.format("MM/DD/yyyy") : "No date set"}
|
||||
{value && value.isValid() ? value.format(DateFormat) : "No date set"}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ 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";
|
||||
import TimeAgoFormatter from "../../atoms/time-ago-formatter/time-ago-formatter.atom";
|
||||
|
||||
export default function JobsDetailDescriptionMolecule({ loading, job }) {
|
||||
if (loading) return <Skeleton active />;
|
||||
@@ -26,6 +27,9 @@ export default function JobsDetailDescriptionMolecule({ loading, job }) {
|
||||
close_date={job.close_date}
|
||||
/>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="Last Updated">
|
||||
<TimeAgoFormatter>{job.updated_at}</TimeAgoFormatter>
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
</PageHeader>
|
||||
</div>
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import { Table } from "antd";
|
||||
import React from "react";
|
||||
import { Input, Table } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import CurrencyFormatterAtom from "../../atoms/currency-formatter/currency-formatter.atom";
|
||||
import IgnoreJobLine from "../../atoms/ignore-job-line/ignore-job-line.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 }) {
|
||||
const [searchText, setSearchText] = useState("");
|
||||
|
||||
const { joblines } = job;
|
||||
const columns = [
|
||||
{
|
||||
title: "#",
|
||||
dataIndex: "unq_seq",
|
||||
key: "unq_seq",
|
||||
dataIndex: "line_no",
|
||||
key: "line_no",
|
||||
},
|
||||
{
|
||||
title: "S#",
|
||||
@@ -73,17 +76,47 @@ export default function JobLinesTableMolecule({ loading, job }) {
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Ignore?",
|
||||
dataIndex: "ignore",
|
||||
key: "ignore",
|
||||
filters: [
|
||||
{ text: "True", value: true },
|
||||
{ text: "False", value: false },
|
||||
],
|
||||
onFilter: (value, record) => value === record.ignore,
|
||||
render: (text, record) => (
|
||||
<IgnoreJobLine lineId={record.id} ignore={record.ignore} />
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const data =
|
||||
searchText !== ""
|
||||
? joblines.filter((j) =>
|
||||
j.line_desc.toLowerCase().includes(searchText.toLowerCase())
|
||||
)
|
||||
: joblines;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Table
|
||||
title={() => (
|
||||
<Input.Search
|
||||
placeholder="Search"
|
||||
onSearch={(val) => {
|
||||
setSearchText(val);
|
||||
}}
|
||||
enterButton
|
||||
allowClear
|
||||
/>
|
||||
)}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
size="small"
|
||||
pagination={false}
|
||||
dataSource={joblines}
|
||||
dataSource={data}
|
||||
scroll={{
|
||||
x: true,
|
||||
y: "20rem",
|
||||
|
||||
@@ -7,7 +7,7 @@ export default function JobsSearchFieldsMolecule({ callSearchQuery }) {
|
||||
const handleFinish = (values) => {
|
||||
callSearchQuery({
|
||||
variables: {
|
||||
search: values.search,
|
||||
search: values.search || "",
|
||||
startDate: (values.dateRange && values.dateRange[0]) || null,
|
||||
endDate: (values.dateRange && values.dateRange[1]) || null,
|
||||
},
|
||||
|
||||
@@ -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,31 +25,12 @@ export function JobsTargetsStatsMolecule({
|
||||
job,
|
||||
selectedJobTargetPc,
|
||||
}) {
|
||||
const currentRpsDollars = useMemo(() => {
|
||||
if (!job) {
|
||||
return 0;
|
||||
}
|
||||
return job.joblines.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.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 <Skeleton active />;
|
||||
if (!job) return <ErrorResultAtom title="Error displaying job data." />;
|
||||
@@ -69,10 +53,9 @@ export function JobsTargetsStatsMolecule({
|
||||
<Statistic
|
||||
title="Current RPS %"
|
||||
valueStyle={{
|
||||
color:
|
||||
selectedJobTargetPc * 100 > currentRpsPc ? "tomato" : "seagreen",
|
||||
color: selectedJobTargetPc > currentRpsPc ? "tomato" : "seagreen",
|
||||
}}
|
||||
value={currentRpsPc}
|
||||
value={(currentRpsPc * 100).toFixed(1)}
|
||||
suffix="%"
|
||||
/>
|
||||
<Statistic title="Current RPS $" value={currentRpsDollars.toFormat()} />
|
||||
|
||||
@@ -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 (
|
||||
<Form form={form} onFinish={handleFinish}>
|
||||
<div style={{ display: "flex" }}>
|
||||
<Form.Item
|
||||
label="Close Date Between"
|
||||
name="dateRange"
|
||||
rules={[{ type: "array", required: true }]}
|
||||
>
|
||||
<DatePicker.RangePicker />
|
||||
</Form.Item>
|
||||
<Button type="primary" htmlType="submit">
|
||||
Run Search
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
import { Input, Table } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import {
|
||||
selectReportData,
|
||||
selectReportLoading,
|
||||
} from "../../../redux/reporting/reporting.selectors";
|
||||
import { setSelectedJobId } from "../../../redux/application/application.actions";
|
||||
import { Link } from "react-router-dom";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
reportingLoading: selectReportLoading,
|
||||
reportData: selectReportData,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setSelectedJobId: (id) => dispatch(setSelectedJobId(id)),
|
||||
});
|
||||
|
||||
export function ReportingJobsListMolecule({
|
||||
reportingLoading,
|
||||
reportData,
|
||||
setSelectedJobId,
|
||||
}) {
|
||||
const [searchText, setSearchText] = useState("");
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: "Claim No.",
|
||||
dataIndex: "clm_no",
|
||||
key: "clm_no",
|
||||
render: (text, record) => (
|
||||
<Link onClick={() => setSelectedJobId(record.id)} to={"/"}>
|
||||
{text}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Ins Co.",
|
||||
dataIndex: "ins_co_nm",
|
||||
key: "ins_co_nm",
|
||||
},
|
||||
{
|
||||
title: "First Name",
|
||||
dataIndex: "ownr_fn",
|
||||
key: "ownr_fn",
|
||||
},
|
||||
{
|
||||
title: "Last Name",
|
||||
dataIndex: "ownr_ln",
|
||||
key: "ownr_ln",
|
||||
},
|
||||
{
|
||||
title: "Vehicle",
|
||||
dataIndex: "vehicle",
|
||||
key: "vehicle",
|
||||
render: (text, record) =>
|
||||
`${record.v_model_yr} ${record.v_makedesc} ${record.v_model} (${
|
||||
record.v_type
|
||||
}) - ${record.group} @ ${
|
||||
record.v_age === 1 ? `${record.v_age} year` : `${record.v_age} years`
|
||||
}`,
|
||||
},
|
||||
{
|
||||
title: "Database Price Sum",
|
||||
dataIndex: "dbPriceSum",
|
||||
key: "dbPriceSum",
|
||||
render: (text, record) => record.dbPriceSum.toFormat(),
|
||||
},
|
||||
{
|
||||
title: "Actual Price Sum ",
|
||||
dataIndex: "actPriceSum",
|
||||
key: "actPriceSum",
|
||||
render: (text, record) => record.actPriceSum.toFormat(),
|
||||
},
|
||||
{
|
||||
title: "Price Diff.",
|
||||
dataIndex: "jobRpsDollars",
|
||||
key: "jobRpsDollars",
|
||||
render: (text, record) => (
|
||||
<span
|
||||
style={{
|
||||
color: record.jobRpsPc > record.jobTarget ? "seagreen" : "tomato",
|
||||
}}
|
||||
>
|
||||
{`${record.jobRpsDollars.toFormat()} / ${record.expectedRpsDollars.toFormat()}`}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Price Diff. %",
|
||||
dataIndex: "price_diff_pc",
|
||||
key: "price_diff_pc",
|
||||
render: (text, record) => (
|
||||
<span
|
||||
style={{
|
||||
color: record.jobRpsPc > record.jobTarget ? "seagreen" : "tomato",
|
||||
}}
|
||||
>
|
||||
{`${(record.jobRpsPc * 100).toFixed(1)}% / ${(
|
||||
record.jobTarget * 100
|
||||
).toFixed(1)}%`}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const data =
|
||||
searchText !== ""
|
||||
? reportData.filter(
|
||||
(j) =>
|
||||
j.ownr_fn.toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
j.ownr_ln.toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
j.ownr_clm_no.toLowerCase().includes(searchText.toLowerCase())
|
||||
)
|
||||
: reportData;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Table
|
||||
title={() => (
|
||||
<Input.Search
|
||||
placeholder="Search"
|
||||
onSearch={(val) => {
|
||||
setSearchText(val);
|
||||
}}
|
||||
enterButton
|
||||
allowClear
|
||||
/>
|
||||
)}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
loading={reportingLoading}
|
||||
size="small"
|
||||
pagination={false}
|
||||
dataSource={data}
|
||||
scroll={{
|
||||
x: true,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ReportingJobsListMolecule);
|
||||
@@ -0,0 +1,72 @@
|
||||
import { Skeleton, Statistic } from "antd";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import {
|
||||
selectReportLoading,
|
||||
selectScorecard,
|
||||
} from "../../../redux/reporting/reporting.selectors";
|
||||
import ErrorResultAtom from "../../atoms/error-result/error-result.atom";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
reportingLoading: selectReportLoading,
|
||||
scoreCard: selectScorecard,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ReportingTotalsStatsMolecule);
|
||||
|
||||
export function ReportingTotalsStatsMolecule({ reportingLoading, scoreCard }) {
|
||||
if (reportingLoading) return <Skeleton active />;
|
||||
if (!scoreCard)
|
||||
return <ErrorResultAtom title="Error displaying score card data." />;
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-around",
|
||||
marginTop: "1rem",
|
||||
marginBottom: "1rem",
|
||||
}}
|
||||
>
|
||||
<Statistic
|
||||
title="RPS Total"
|
||||
value={scoreCard.shopRpsTotalDollars.toFormat()}
|
||||
/>
|
||||
<Statistic
|
||||
title="RPS Expectation"
|
||||
value={scoreCard.shopRpsExpectedDollars.toFormat()}
|
||||
/>
|
||||
<Statistic
|
||||
title="RPS Variance $"
|
||||
valueStyle={{
|
||||
color:
|
||||
scoreCard.varianceDollars.getAmount() < 0 ? "tomato" : "seagreen",
|
||||
}}
|
||||
value={scoreCard.varianceDollars.toFormat()}
|
||||
/>
|
||||
<Statistic
|
||||
title="Current RPS %"
|
||||
valueStyle={{
|
||||
color:
|
||||
scoreCard.currentRpsPc <= scoreCard.targetRpsPc
|
||||
? "tomato"
|
||||
: "seagreen",
|
||||
}}
|
||||
value={(scoreCard.currentRpsPc * 100).toFixed(1)}
|
||||
suffix="%"
|
||||
/>
|
||||
<Statistic
|
||||
title="Target RPS %"
|
||||
value={(scoreCard.targetRpsPc * 100).toFixed(1)}
|
||||
suffix="%"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<Menu.Item
|
||||
icon={<UserOutlined />}
|
||||
icon={<LogoutOutlined />}
|
||||
{...restProps}
|
||||
onClick={() => signOutStart()}
|
||||
>
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { InputNumber, Switch } from "antd";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import ipcTypes from "../../../ipc.types";
|
||||
import { selectSettings } from "../../../redux/application/application.selectors";
|
||||
import DataLabel from "../../atoms/data-label/data-label.atom";
|
||||
const { ipcRenderer } = window;
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
appSettings: selectSettings,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
|
||||
export function WatcherPollingMolecule({ appSettings }) {
|
||||
const handlePollingToggle = (val) => {
|
||||
ipcRenderer.send(ipcTypes.default.store.set, { "polling.enabled": val });
|
||||
};
|
||||
const handleIntervalChange = (val) => {
|
||||
ipcRenderer.send(ipcTypes.default.store.set, {
|
||||
"polling.pollingInterval": val,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<DataLabel label="Polling Enabled? (Recommended for Network Monitoring)">
|
||||
<Switch
|
||||
onChange={handlePollingToggle}
|
||||
checked={
|
||||
appSettings && appSettings.polling && appSettings.polling.enabled
|
||||
}
|
||||
/>
|
||||
</DataLabel>
|
||||
<DataLabel label="Polling Interval">
|
||||
<InputNumber
|
||||
onChange={handleIntervalChange}
|
||||
value={
|
||||
appSettings &&
|
||||
appSettings.polling &&
|
||||
appSettings.polling.pollingInterval
|
||||
}
|
||||
/>
|
||||
</DataLabel>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(WatcherPollingMolecule);
|
||||
@@ -21,7 +21,6 @@ export function FilePathsList({ watchedPaths }) {
|
||||
ipcRenderer.send(ipcTypes.default.fileWatcher.toMain.filepathsGet);
|
||||
}, []);
|
||||
|
||||
console.log("watchedPaths", watchedPaths);
|
||||
return (
|
||||
<div>
|
||||
<Typography.Title>Watcher File Paths</Typography.Title>
|
||||
|
||||
@@ -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() {
|
||||
<Menu.Item key="/" icon={<PieChartOutlined />}>
|
||||
<Link to="/">Jobs</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="/reporting" icon={<BarChartOutlined />}>
|
||||
<Link to="/reporting">Reporting</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="/settings" icon={<SettingFilled />}>
|
||||
<Link to="/settings">Settings</Link>
|
||||
</Menu.Item>
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import { Col, Row, Tabs, Grid } from "antd";
|
||||
import { Col, Grid, 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-latest/jobs-list-latest.organism";
|
||||
import JobsListSearchOrganism from "../../organisms/jobs-list-search/jobs-list-search.organism";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({});
|
||||
const mapDispatchToProps = (dispatch) => ({});
|
||||
|
||||
export function JobsPage() {
|
||||
export default function JobsPage() {
|
||||
console.log("Jobs Page Rerender");
|
||||
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
|
||||
.filter((screen) => !!screen[1])
|
||||
.slice(-1)[0];
|
||||
@@ -47,4 +43,3 @@ export function JobsPage() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(JobsPage);
|
||||
|
||||
31
src/components/pages/reporting/reporting.page.jsx
Normal file
31
src/components/pages/reporting/reporting.page.jsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectDates } from "../../../redux/reporting/reporting.selectors";
|
||||
import ReportingTitleAtom from "../../atoms/reporting-title/reporting-title.atom";
|
||||
import ReportingDatesMolecule from "../../molecules/reporting-dates/reporting-dates.molecule";
|
||||
import ReportingJobsListMolecule from "../../molecules/reporting-jobs-list/reporting-jobs-list.molecule";
|
||||
import ReportingTotalsStatsMolecule from "../../molecules/reporting-totals-stats/reporting-totals-stats.molecule";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
dates: selectDates,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ReportingPage);
|
||||
|
||||
export function ReportingPage({ dates }) {
|
||||
return (
|
||||
<div>
|
||||
<ReportingDatesMolecule />
|
||||
{dates && dates.startDate && dates.endDate && (
|
||||
<div>
|
||||
<ReportingTitleAtom />
|
||||
<ReportingTotalsStatsMolecule />
|
||||
<ReportingJobsListMolecule />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,13 +1,15 @@
|
||||
import { Layout } from "antd";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { Route, Switch } from "react-router-dom";
|
||||
import { Route } 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 JobsPage from "../jobs/jobs.page";
|
||||
import ReportingPage from "../reporting/reporting.page";
|
||||
import SettingsPage from "../settings/settings.page";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop });
|
||||
const mapDispatchToProps = (dispatch) => ({});
|
||||
|
||||
@@ -19,6 +21,7 @@ export function RoutesPage({ bodyshop }) {
|
||||
errorMessage="You do not currently have access to any shop. Please reach out to technical support."
|
||||
/>
|
||||
);
|
||||
console.log("routes render");
|
||||
return (
|
||||
<Layout style={{ background: "#fff", height: "100vh" }} hasSider>
|
||||
<Layout.Sider
|
||||
@@ -30,10 +33,9 @@ export function RoutesPage({ bodyshop }) {
|
||||
</Layout.Sider>
|
||||
<Layout style={{ background: "#fff" }}>
|
||||
<Layout.Content style={{ margin: "1rem", height: "100%" }}>
|
||||
<Switch>
|
||||
<Route exact path="/settings" component={SettingsPage} />
|
||||
<Route path="/" component={Jobs} />
|
||||
</Switch>
|
||||
<Route exact path="/settings" component={SettingsPage} />
|
||||
<Route exact path="/reporting" component={ReportingPage} />
|
||||
<Route exact path="/" component={JobsPage} />
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
</Layout>
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
import { Col, Row } from "antd";
|
||||
import React from "react";
|
||||
import React, { useEffect } from "react";
|
||||
import ipcTypes from "../../../ipc.types";
|
||||
import WatcherPollingMolecule from "../../molecules/watcher-polling/watcher-polling.molecule";
|
||||
import FilePathsListOrganism from "../../organisms/filepaths-list/filepaths-list.organism";
|
||||
import ShopSettingsOrganism from "../../organisms/shop-settings/shop-settings.organism";
|
||||
import WatcherManagerOrganism from "../../organisms/watcher-manager/watcher-manager.organism";
|
||||
const { ipcRenderer } = window;
|
||||
|
||||
export default function SettingsPage() {
|
||||
useEffect(() => {
|
||||
ipcRenderer.send(ipcTypes.default.store.getAll);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Row gutter={[16, 16]}>
|
||||
@@ -13,6 +20,7 @@ export default function SettingsPage() {
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<WatcherManagerOrganism />
|
||||
<WatcherPollingMolecule />
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
@@ -20,11 +28,3 @@ export default function SettingsPage() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// <Button
|
||||
// onClick={() => {
|
||||
// ipcRenderer.send(ipcTypes.default.filewatcher.start);
|
||||
// }}
|
||||
// >
|
||||
// Start Watcher
|
||||
// </Button>
|
||||
|
||||
23
src/graphql/joblines.queries.js
Normal file
23
src/graphql/joblines.queries.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import gql from "graphql-tag";
|
||||
|
||||
export const UPDATE_JOB_LINE = gql`
|
||||
mutation UPDATE_JOB_LINE($lineId: uuid!, $line: joblines_set_input!) {
|
||||
update_joblines(where: { id: { _eq: $lineId } }, _set: $line) {
|
||||
returning {
|
||||
id
|
||||
line_no
|
||||
act_price
|
||||
db_price
|
||||
line_desc
|
||||
line_ind
|
||||
oem_partno
|
||||
part_qty
|
||||
part_type
|
||||
unq_seq
|
||||
price_diff
|
||||
price_diff_pc
|
||||
ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -10,21 +10,6 @@ export const INSERT_NEW_JOB = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
// on_conflict: {
|
||||
// constraint: jobs_clm_no_bodyshopid_key
|
||||
// update_columns: [
|
||||
// ins_co_nm
|
||||
// clm_no
|
||||
// clm_total
|
||||
// ownr_ln
|
||||
// ownr_fn
|
||||
// v_vin
|
||||
// v_make_desc
|
||||
// v_model_desc
|
||||
// v_type
|
||||
// ]
|
||||
// }
|
||||
|
||||
export const QUERY_ALL_JOBS_PAGINATED = gql`
|
||||
query QUERY_ALL_JOBS_PAGINATED(
|
||||
$offset: Int
|
||||
@@ -104,8 +89,10 @@ export const QUERY_JOB_BY_PK = gql`
|
||||
v_age
|
||||
loss_date
|
||||
close_date
|
||||
updated_at
|
||||
joblines(order_by: { line_no: asc }) {
|
||||
id
|
||||
line_no
|
||||
act_price
|
||||
db_price
|
||||
line_desc
|
||||
@@ -116,6 +103,7 @@ export const QUERY_JOB_BY_PK = gql`
|
||||
unq_seq
|
||||
price_diff
|
||||
price_diff_pc
|
||||
ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
49
src/graphql/reporting.queries.js
Normal file
49
src/graphql/reporting.queries.js
Normal file
@@ -0,0 +1,49 @@
|
||||
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
|
||||
v_vin
|
||||
v_type
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -2,7 +2,7 @@ import "antd/dist/antd.css";
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { Provider } from "react-redux";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import { MemoryRouter } from "react-router-dom";
|
||||
import { PersistGate } from "redux-persist/integration/react";
|
||||
import App from "./App/App";
|
||||
import "./index.css";
|
||||
@@ -11,11 +11,11 @@ require("dotenv").config();
|
||||
|
||||
ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
<BrowserRouter>
|
||||
<MemoryRouter>
|
||||
<PersistGate persistor={persistor}>
|
||||
<App />
|
||||
</PersistGate>
|
||||
</BrowserRouter>
|
||||
</MemoryRouter>
|
||||
</Provider>,
|
||||
document.getElementById("root")
|
||||
);
|
||||
|
||||
@@ -9,6 +9,12 @@ exports.default = {
|
||||
setAcceptableInsCoNm: "setAcceptableInsCoNm",
|
||||
},
|
||||
},
|
||||
store: {
|
||||
get: "store__get",
|
||||
getAll: "store_getAll",
|
||||
set: "store_set",
|
||||
response: "store_response",
|
||||
},
|
||||
fileWatcher: {
|
||||
toMain: {
|
||||
filepathsGet: "filewatcher__filepathsget",
|
||||
@@ -16,6 +22,7 @@ exports.default = {
|
||||
stop: "filewatcher__stop",
|
||||
addPath: "filewatcher__addPath",
|
||||
removePath: "filewatcher__removePath",
|
||||
setPolling: "filewatcher__setPolling",
|
||||
},
|
||||
toRenderer: {
|
||||
filepathsList: "filewatcher__filepathslist",
|
||||
@@ -23,6 +30,7 @@ exports.default = {
|
||||
startFailure: "filewatcher__start-failure",
|
||||
stopSuccess: "filewatcher__stop-success",
|
||||
error: "filewatcher__error",
|
||||
getPolling: "filewatcher__getPolling",
|
||||
},
|
||||
},
|
||||
estimate: {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import ipcTypes from "../ipc.types";
|
||||
import {
|
||||
setSettings,
|
||||
setWatchedPaths,
|
||||
setWatcherStatus,
|
||||
} from "../redux/application/application.actions";
|
||||
@@ -52,3 +53,7 @@ ipcRenderer.on(
|
||||
await UpsertEstimate(obj);
|
||||
}
|
||||
);
|
||||
|
||||
ipcRenderer.on(ipcTypes.default.store.response, (event, obj) => {
|
||||
store.dispatch(setSettings(obj));
|
||||
});
|
||||
|
||||
@@ -38,3 +38,7 @@ export const setSelectedJobTargetPcSuccess = (pct) => ({
|
||||
type: ApplicationActionTypes.SET_SELECTED_JOB_TARGET_PC_SUCCESS,
|
||||
payload: pct,
|
||||
});
|
||||
export const setSettings = (settingsObj) => ({
|
||||
type: ApplicationActionTypes.SET_SETTINGS,
|
||||
payload: settingsObj,
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ const INITIAL_STATE = {
|
||||
watcherError: null,
|
||||
selectedJobId: null,
|
||||
selectedJobTargetPc: 100,
|
||||
settings: {},
|
||||
};
|
||||
|
||||
const applicationReducer = (state = INITIAL_STATE, action) => {
|
||||
@@ -41,6 +42,9 @@ const applicationReducer = (state = INITIAL_STATE, action) => {
|
||||
};
|
||||
case ApplicationActionTypes.SET_SELECTED_JOB_ID:
|
||||
return { ...state, selectedJobId: action.payload };
|
||||
case ApplicationActionTypes.SET_SETTINGS:
|
||||
return { ...state, settings: { ...state.settings, ...action.payload } };
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -26,3 +26,8 @@ export const selectSelectedJobTargetPc = createSelector(
|
||||
[selectApplication],
|
||||
(application) => application.selectedJobTargetPc
|
||||
);
|
||||
|
||||
export const selectSettings = createSelector(
|
||||
[selectApplication],
|
||||
(application) => application.settings
|
||||
);
|
||||
|
||||
@@ -7,5 +7,6 @@ const ApplicationActionTypes = {
|
||||
SET_SELECTED_JOB_ID: "SET_SELECTED_JOB_ID",
|
||||
SET_SELECTED_JOB_TARGET_PC: "SET_SELECTED_JOB_TARGET_PC",
|
||||
SET_SELECTED_JOB_TARGET_PC_SUCCESS: "SET_SELECTED_JOB_TARGET_PC_SUCCESS",
|
||||
SET_SETTINGS: "SET_SETTINGS",
|
||||
};
|
||||
export default ApplicationActionTypes;
|
||||
|
||||
24
src/redux/reporting/reporting.actions.js
Normal file
24
src/redux/reporting/reporting.actions.js
Normal file
@@ -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,
|
||||
});
|
||||
30
src/redux/reporting/reporting.reducer.js
Normal file
30
src/redux/reporting/reporting.reducer.js
Normal file
@@ -0,0 +1,30 @@
|
||||
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: {
|
||||
startDate: action.payload.startDate.toISOString(),
|
||||
endDate: action.payload.endDate.toISOString(),
|
||||
},
|
||||
};
|
||||
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;
|
||||
128
src/redux/reporting/reporting.sagas.js
Normal file
128
src/redux/reporting/reporting.sagas.js
Normal file
@@ -0,0 +1,128 @@
|
||||
import { all, call, takeLatest, select, put } from "redux-saga/effects";
|
||||
import {
|
||||
calculateScorecard,
|
||||
setReportingData,
|
||||
setScoreCard,
|
||||
} from "./reporting.actions";
|
||||
import ReportingApplicationTypes from "./reporting.types";
|
||||
import client from "../../graphql/GraphQLClient";
|
||||
import { REPORTING_GET_JOBS } from "../../graphql/reporting.queries";
|
||||
import Dinero from "dinero.js";
|
||||
import {
|
||||
CalculateJobRpsDollars,
|
||||
CalculateJobRpsPc,
|
||||
} from "../../util/CalculateJobRps";
|
||||
import GetJobTarget from "../../util/GetJobTarget";
|
||||
|
||||
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(calculateScorecard(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);
|
||||
const targets = yield select((state) => state.user.bodyshop.targets);
|
||||
|
||||
const scoreCard = {
|
||||
shopRpsTotalDollars: Dinero(),
|
||||
shopRpsExpectedDollars: Dinero(),
|
||||
varianceDollars: null,
|
||||
variancePc: 0,
|
||||
allJobsSumDbPrice: Dinero(),
|
||||
allJobsSumActPrice: Dinero(),
|
||||
currentRpsPc: 0,
|
||||
targetRpsPc: 0,
|
||||
};
|
||||
|
||||
//Get the RPS on a per job basis.
|
||||
jobs = jobs.map((job) => {
|
||||
const { actPriceSum, jobRpsDollars } = CalculateJobRpsDollars(job, true);
|
||||
const { dbPriceSum, jobRpsPc } = CalculateJobRpsPc(
|
||||
job,
|
||||
jobRpsDollars,
|
||||
true
|
||||
);
|
||||
const jobTarget = GetJobTarget(job.group, job.v_age, targets);
|
||||
scoreCard.shopRpsTotalDollars = scoreCard.shopRpsTotalDollars.add(
|
||||
jobRpsDollars
|
||||
);
|
||||
const expectedRpsDollars = dbPriceSum.percentage(jobTarget * 100);
|
||||
scoreCard.shopRpsExpectedDollars = scoreCard.shopRpsExpectedDollars.add(
|
||||
expectedRpsDollars
|
||||
);
|
||||
|
||||
scoreCard.allJobsSumDbPrice = scoreCard.allJobsSumDbPrice.add(dbPriceSum);
|
||||
scoreCard.allJobsSumActPrice = scoreCard.allJobsSumActPrice.add(
|
||||
actPriceSum
|
||||
);
|
||||
|
||||
//sum db price * percentage expected.
|
||||
return {
|
||||
...job,
|
||||
actPriceSum,
|
||||
jobRpsDollars,
|
||||
dbPriceSum,
|
||||
jobRpsPc,
|
||||
jobTarget,
|
||||
expectedRpsDollars,
|
||||
};
|
||||
});
|
||||
|
||||
scoreCard.varianceDollars = scoreCard.shopRpsTotalDollars.subtract(
|
||||
scoreCard.shopRpsExpectedDollars
|
||||
);
|
||||
|
||||
scoreCard.variancePc =
|
||||
scoreCard.varianceDollars.getAmount() /
|
||||
scoreCard.shopRpsExpectedDollars.getAmount();
|
||||
|
||||
scoreCard.currentRpsPc =
|
||||
scoreCard.shopRpsTotalDollars.getAmount() /
|
||||
scoreCard.allJobsSumDbPrice.getAmount();
|
||||
scoreCard.targetRpsPc =
|
||||
scoreCard.shopRpsExpectedDollars.getAmount() /
|
||||
scoreCard.allJobsSumDbPrice.getAmount();
|
||||
//Set the data.
|
||||
yield put(setScoreCard(scoreCard));
|
||||
yield put(setReportingData(jobs));
|
||||
}
|
||||
|
||||
export function* reportingSagas() {
|
||||
yield all([
|
||||
call(onQueryReportData),
|
||||
call(onSetReportData),
|
||||
call(onCalculateScoreCard),
|
||||
]);
|
||||
}
|
||||
49
src/redux/reporting/reporting.selectors.js
Normal file
49
src/redux/reporting/reporting.selectors.js
Normal file
@@ -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
|
||||
// );
|
||||
8
src/redux/reporting/reporting.types.js
Normal file
8
src/redux/reporting/reporting.types.js
Normal file
@@ -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;
|
||||
@@ -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);
|
||||
|
||||
@@ -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)]);
|
||||
}
|
||||
|
||||
42
src/util/CalculateJobRps.js
Normal file
42
src/util/CalculateJobRps.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import Dinero from "dinero.js";
|
||||
|
||||
export function CalculateJobRpsDollars(job, returnSumActPrice) {
|
||||
if (!job) {
|
||||
return 0;
|
||||
}
|
||||
let actPriceSum = Dinero();
|
||||
const jobRpsDollars = job.joblines
|
||||
.filter((j) => !j.ignore)
|
||||
.reduce((acc, val) => {
|
||||
actPriceSum = actPriceSum.add(
|
||||
Dinero({ amount: Math.round((val.act_price || 0) * 100) })
|
||||
);
|
||||
if (val.price_diff > 0) {
|
||||
return acc.add(
|
||||
Dinero({ amount: Math.round((val.price_diff || 0) * 100) })
|
||||
);
|
||||
} else {
|
||||
return acc;
|
||||
}
|
||||
}, Dinero());
|
||||
return returnSumActPrice ? { actPriceSum, jobRpsDollars } : jobRpsDollars;
|
||||
}
|
||||
|
||||
export function CalculateJobRpsPc(
|
||||
job,
|
||||
currentRpsDollars,
|
||||
returnSumDbPrice = false
|
||||
) {
|
||||
//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.add(Dinero({ amount: Math.round((val.db_price || 0) * 100) }));
|
||||
}, Dinero());
|
||||
|
||||
const jobRpsPc = currentRpsDollars.getAmount() / dbPriceSum.getAmount();
|
||||
return returnSumDbPrice ? { dbPriceSum, jobRpsPc } : jobRpsPc;
|
||||
}
|
||||
12
src/util/GetJobTarget.js
Normal file
12
src/util/GetJobTarget.js
Normal file
@@ -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 1;
|
||||
else if (targetPc.length === 1) return targetPc[0].target;
|
||||
else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
1
src/util/constants.js
Normal file
1
src/util/constants.js
Normal file
@@ -0,0 +1 @@
|
||||
export const DateFormat = "MM/DD/yyyy";
|
||||
Reference in New Issue
Block a user