Merged in dev-patrick (pull request #8)

Merge all WIP dev changes to Dev Branch.
This commit is contained in:
Snapt Software
2020-08-25 23:55:41 +00:00
320 changed files with 15370 additions and 5140 deletions

17
.vscode/launch.json vendored
View File

@@ -1,19 +1,20 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Attach to Chrome",
"port": 9222,
"request": "attach",
"type": "pwa-chrome",
"webRoot": "${workspaceFolder}/client/src"
},
{
"name": "Chrome",
"type": "chrome",
"request": "launch",
"url": "http://localhost:3000",
"webRoot": "${workspaceRoot}/src"
},
{
"name": "Yarn Dev Server",
"type": "node",
"request": "launch",
"runtimeExecutable": "yarn",
"runtimeArgs": ["dev"]
"webRoot": "${workspaceRoot}/client/src"
}
]
}

View File

@@ -1,7 +1,8 @@
React App:
Yarn Dependency Management:
To force upgrades for some packages: yarn upgrade-interactive --latest
To force upgrades for some packages:
yarn upgrade-interactive --latest
GraphQL API:
Hasura is hosted on another dyno. Several environmental variables are required, including disabling the console.
@@ -19,4 +20,6 @@ NGROK TEsting:
Finding deadfiles - run from client directory
npx deadfile ./src/index.js --exclude build templates
npx deadfile ./src/index.js --exclude build templates
cd client && yarn build && cd build && scp -r ** imex@prod-tor1.imex.online:~/bodyshop/client/build && cd .. &&cd ..

View File

@@ -3,21 +3,23 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@apollo/client": "^3.0.2",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@apollo/client": "^3.1.2",
"@testing-library/jest-dom": "^5.11.2",
"@testing-library/react": "^10.4.8",
"@testing-library/user-event": "^12.1.0",
"@types/prop-types": "^15.7.3",
"apollo-boost": "^0.4.9",
"apollo-link-context": "^1.0.20",
"apollo-link-logger": "^1.2.3",
"dotenv": "^8.2.0",
"firebase": "^7.17.0",
"firebase": "^7.17.1",
"graphql": "^15.3.0",
"prop-types": "^15.7.2",
"ra-data-hasura-graphql": "^0.1.12",
"react": "^16.13.1",
"react-admin": "^3.7.1",
"react-admin": "^3.7.2",
"react-dom": "^16.13.1",
"react-icons": "^3.10.0",
"react-scripts": "3.4.1"
},
"scripts": {

View File

@@ -1,4 +1,4 @@
import { ApolloClient, InMemoryCache, ApolloProvider } from "@apollo/client";
import { ApolloClient, ApolloProvider, InMemoryCache } from "@apollo/client";
import { ApolloLink } from "apollo-boost";
import { setContext } from "apollo-link-context";
import { HttpLink } from "apollo-link-http";
@@ -12,6 +12,7 @@ import {
Resource,
ShowGuesser,
} from "react-admin";
import { FaFileInvoiceDollar } from "react-icons/fa";
import { auth } from "../../firebase/admin-firebase-utils";
import authProvider from "../auth-provider/auth-provider";
import JoblinesCreate from "../joblines/joblines.create";
@@ -91,6 +92,7 @@ class AdminRoot extends Component {
<ApolloProvider client={client}>
<Admin dataProvider={dataProvider} authProvider={authProvider}>
<Resource
icon={FaFileInvoiceDollar}
name="jobs"
list={JobsList}
edit={JobsEdit}

View File

@@ -9,7 +9,6 @@ const authProvider = {
password
);
const token = await user.getIdToken(true);
console.log("token", token);
localStorage.setItem("token", token);
return Promise.resolve();
} catch (error) {
@@ -23,23 +22,17 @@ const authProvider = {
return Promise.resolve();
},
checkAuth: async (params) => {
console.log("Check Auth", params);
const user = await getCurrentUser();
if (!!user) {
console.log("AuthProvider => checkAuth => Authorized");
return Promise.resolve();
} else {
console.log("AuthProvider => checkAuth => Unauthorized");
return Promise.reject();
}
},
checkError: (error) => {
console.log("Check error");
return Promise.resolve();
},
getPermissions: (params) => {
console.log("get permissions", params);
return Promise.resolve();
},
};

View File

@@ -1,15 +1,12 @@
import React from "react";
import {
Edit,
EmailField,
DateTimeInput,
DateField,
NumberInput,
BooleanInput,
DateField,
Edit,
NumberInput,
SimpleForm,
TextInput,
} from "react-admin";
import { number } from "prop-types";
const JoblinesEdit = (props) => (
<Edit {...props}>

View File

@@ -1,53 +1,150 @@
import React from "react";
import { Edit, SimpleForm, TextInput } from "react-admin";
//@ts-ignore
import {
AutocompleteInput,
Edit,
FormTab,
NumberInput,
ReferenceInput,
SelectInput,
TabbedForm,
TextInput,
} from "react-admin";
const JobsEdit = (props) => (
<Edit {...props}>
<SimpleForm margin="normal" variant="standard">
<div
style={{
columns: "3 auto",
// display: "flex",
width: "100%",
// justifyContent: "space-around",
}}
>
<TextInput fullWidth source="id" />
<TextInput fullWidth source="created_at" />
<TextInput fullWidth source="updated_at" />
<TabbedForm margin="normal" variant="standard">
<FormTab label="Record Info">
<div
style={{
columns: "3 auto",
// display: "flex",
width: "100%",
// justifyContent: "space-around",
}}
>
<TextInput disabled fullWidth source="id" />
<TextInput disabled fullWidth source="created_at" />
<TextInput disabled fullWidth source="updated_at" />
</div>
</FormTab>
<FormTab label="Job Info">
<div
style={{
columns: "3 auto",
// display: "flex",
width: "100%",
// justifyContent: "space-around",
}}
>
<ReferenceInput label="Shopid" source="shopid" reference="bodyshops">
<SelectInput disabled optionText="shopname" />
</ReferenceInput>
<TextInput fullWidth source="ro_number" />
<ReferenceInput label="Owner ID" source="ownerid" reference="owners">
<AutocompleteInput
matchSuggestion={(filter, choice) =>
choice.ownr_fn &&
choice.ownr_fn.toLowerCase().includes(filter.toLowerCase())
}
optionText={(record) =>
`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${
record.ownr_co_nm || ""
} (${record.ownr_ph1 || ""})`
}
/>
</ReferenceInput>
<ReferenceInput
label="Vehicle Id"
source="vehicleid"
reference="vehicles"
>
<SelectInput optionText="v_vin" />
</ReferenceInput>
<ReferenceInput label="Shopid" source="shopid" reference="bodyshops">
<SelectInput disabled optionText="shopname" />
</ReferenceInput>
<TextInput fullWidth source="ro_number" />
<ReferenceInput label="Owner ID" source="ownerid" reference="owners">
<AutocompleteInput
matchSuggestion={(filter, choice) =>
choice.ownr_fn &&
choice.ownr_fn.toLowerCase().includes(filter.toLowerCase())
}
optionText={(record) =>
`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${
record.ownr_co_nm || ""
} (${record.ownr_ph1 || ""})`
}
/>
</ReferenceInput>
<ReferenceInput
label="Vehicle Id"
source="vehicleid"
reference="vehicles"
>
<SelectInput optionText="v_vin" />
</ReferenceInput>
<TextInput fullWidth source="inproduction" />
<TextInput fullWidth source="converted" />
</div>
</FormTab>
<TextInput fullWidth source="shopid" disabled />
<TextInput fullWidth source="ro_number" />
<TextInput fullWidth source="ownerid" />
<TextInput fullWidth source="vehicleid" />
<TextInput fullWidth source="labor_rate_id" />
<TextInput fullWidth source="labor_rate_desc" />
<TextInput fullWidth source="rate_lab" />
<TextInput fullWidth source="rate_lad" />
<TextInput fullWidth source="rate_lae" />
<TextInput fullWidth source="rate_lar" />
<TextInput fullWidth source="rate_las" />
<TextInput fullWidth source="rate_laf" />
<TextInput fullWidth source="rate_lam" />
<TextInput fullWidth source="rate_lag" />
<TextInput fullWidth source="rate_atp" />
<TextInput fullWidth source="rate_lau" />
<TextInput fullWidth source="rate_la1" />
<TextInput fullWidth source="rate_la2" />
<TextInput fullWidth source="rate_la3" />
<TextInput fullWidth source="rate_la4" />
<TextInput fullWidth source="rate_mapa" />
<TextInput fullWidth source="rate_mash" />
<TextInput fullWidth source="rate_mahw" />
<TextInput fullWidth source="rate_ma2s" />
<TextInput fullWidth source="rate_ma3s" />
<TextInput fullWidth source="rate_ma2t" />
<TextInput fullWidth source="rate_mabl" />
<TextInput fullWidth source="rate_macs" />
<TextInput fullWidth source="rate_matd" />
<TextInput fullWidth source="federal_tax_rate" />
<TextInput fullWidth source="state_tax_rate" />
<TextInput fullWidth source="local_tax_rate" />
<FormTab label="Labor Rates">
<div
style={{
columns: "3 auto",
// display: "flex",
width: "100%",
// justifyContent: "space-around",
}}
>
<NumberInput fullWidth source="labor_rate_id" />
<NumberInput fullWidth source="labor_rate_desc" />
<NumberInput fullWidth source="rate_lab" />
<NumberInput fullWidth source="rate_lad" />
<NumberInput fullWidth source="rate_lae" />
<NumberInput fullWidth source="rate_lar" />
<NumberInput fullWidth source="rate_las" />
<NumberInput fullWidth source="rate_laf" />
<NumberInput fullWidth source="rate_lam" />
<NumberInput fullWidth source="rate_lag" />
<NumberInput fullWidth source="rate_atp" />
<NumberInput fullWidth source="rate_lau" />
<NumberInput fullWidth source="rate_la1" />
<NumberInput fullWidth source="rate_la2" />
<NumberInput fullWidth source="rate_la3" />
<NumberInput fullWidth source="rate_la4" />
<NumberInput fullWidth source="rate_mapa" />
<NumberInput fullWidth source="rate_mash" />
<NumberInput fullWidth source="rate_mahw" />
<NumberInput fullWidth source="rate_ma2s" />
<NumberInput fullWidth source="rate_ma3s" />
<NumberInput fullWidth source="rate_ma2t" />
<NumberInput fullWidth source="rate_mabl" />
<NumberInput fullWidth source="rate_macs" />
<NumberInput fullWidth source="rate_matd" />
<NumberInput fullWidth source="federal_tax_rate" />
<NumberInput fullWidth source="state_tax_rate" />
<NumberInput fullWidth source="local_tax_rate" />
</div>
</FormTab>
<FormTab label="Dates">
<TextInput fullWidth source="scheduled_in" />
<TextInput fullWidth source="actual_in" />
<TextInput fullWidth source="scheduled_completion" />
<TextInput fullWidth source="actual_completion" />
<TextInput fullWidth source="scheduled_delivery" />
<TextInput fullWidth source="actual_delivery" />
<TextInput fullWidth source="invoice_date" />
<TextInput fullWidth source="date_estimated" />
<TextInput fullWidth source="date_open" />
<TextInput fullWidth source="date_scheduled" />
<TextInput fullWidth source="date_invoiced" />
<TextInput fullWidth source="date_closed" />
<TextInput fullWidth source="date_exported" />
</FormTab>
<FormTab label="Insurance info">
<TextInput fullWidth source="est_co_nm" />
<TextInput fullWidth source="est_addr1" />
<TextInput fullWidth source="est_addr2" />
@@ -59,15 +156,7 @@ const JobsEdit = (props) => (
<TextInput fullWidth source="est_ea" />
<TextInput fullWidth source="est_ct_ln" />
<TextInput fullWidth source="est_ct_fn" />
<TextInput fullWidth source="scheduled_in" />
<TextInput fullWidth source="actual_in" />
<TextInput fullWidth source="scheduled_completion" />
<TextInput fullWidth source="actual_completion" />
<TextInput fullWidth source="scheduled_delivery" />
<TextInput fullWidth source="actual_delivery" />
<TextInput fullWidth source="regie_number" />
<TextInput fullWidth source="invoice_date" />
<TextInput fullWidth source="inproduction" />
<TextInput fullWidth source="statusid" />
<TextInput fullWidth source="ins_co_id" />
<TextInput fullWidth source="ins_co_nm" />
@@ -98,15 +187,31 @@ const JobsEdit = (props) => (
<TextInput fullWidth source="asgn_type" />
<TextInput fullWidth source="clm_no" />
<TextInput fullWidth source="clm_ofc_id" />
<TextInput fullWidth source="date_estimated" />
<TextInput fullWidth source="date_open" />
<TextInput fullWidth source="date_scheduled" />
<TextInput fullWidth source="date_invoiced" />
<TextInput fullWidth source="date_closed" />
<TextInput fullWidth source="date_exported" />
<TextInput fullWidth source="clm_total" />
<TextInput fullWidth source="owner_owing" />
<TextInput fullWidth source="converted" />
<TextInput fullWidth source="agt_co_id" />
<TextInput fullWidth source="agt_co_nm" />
<TextInput fullWidth source="agt_addr1" />
<TextInput fullWidth source="agt_addr2" />
<TextInput fullWidth source="agt_city" />
<TextInput fullWidth source="agt_st" />
<TextInput fullWidth source="agt_zip" />
<TextInput fullWidth source="agt_ctry" />
<TextInput fullWidth source="agt_ph1" />
<TextInput fullWidth source="agt_ph1x" />
<TextInput fullWidth source="agt_ph2" />
<TextInput fullWidth source="agt_ph2x" />
<TextInput fullWidth source="agt_fax" />
<TextInput fullWidth source="agt_faxx" />
<TextInput fullWidth source="agt_ct_ln" />
<TextInput fullWidth source="agt_ct_fn" />
<TextInput fullWidth source="agt_ct_ph" />
<TextInput fullWidth source="agt_ct_phx" />
<TextInput fullWidth source="agt_ea" />
<TextInput fullWidth source="agt_lic_no" />
<TextInput fullWidth source="loss_type" />
<TextInput fullWidth source="loss_desc" />
<TextInput fullWidth source="theft_ind" />
<TextInput fullWidth source="cat_no" />
<TextInput fullWidth source="tlos_ind" />
<TextInput fullWidth source="ciecaid" />
<TextInput fullWidth source="loss_date" />
<TextInput fullWidth source="clm_ofc_nm" />
@@ -133,31 +238,8 @@ const JobsEdit = (props) => (
<TextInput fullWidth source="pay_date" />
<TextInput fullWidth source="pay_chknm" />
<TextInput fullWidth source="pay_amt" />
<TextInput fullWidth source="agt_co_id" />
<TextInput fullWidth source="agt_co_nm" />
<TextInput fullWidth source="agt_addr1" />
<TextInput fullWidth source="agt_addr2" />
<TextInput fullWidth source="agt_city" />
<TextInput fullWidth source="agt_st" />
<TextInput fullWidth source="agt_zip" />
<TextInput fullWidth source="agt_ctry" />
<TextInput fullWidth source="agt_ph1" />
<TextInput fullWidth source="agt_ph1x" />
<TextInput fullWidth source="agt_ph2" />
<TextInput fullWidth source="agt_ph2x" />
<TextInput fullWidth source="agt_fax" />
<TextInput fullWidth source="agt_faxx" />
<TextInput fullWidth source="agt_ct_ln" />
<TextInput fullWidth source="agt_ct_fn" />
<TextInput fullWidth source="agt_ct_ph" />
<TextInput fullWidth source="agt_ct_phx" />
<TextInput fullWidth source="agt_ea" />
<TextInput fullWidth source="agt_lic_no" />
<TextInput fullWidth source="loss_type" />
<TextInput fullWidth source="loss_desc" />
<TextInput fullWidth source="theft_ind" />
<TextInput fullWidth source="cat_no" />
<TextInput fullWidth source="tlos_ind" />
</FormTab>
<FormTab label="Owner Data on Job">
<TextInput fullWidth source="cust_pr" />
<TextInput fullWidth source="insd_ln" />
<TextInput fullWidth source="insd_fn" />
@@ -193,6 +275,12 @@ const JobsEdit = (props) => (
<TextInput fullWidth source="ownr_fax" />
<TextInput fullWidth source="ownr_faxx" />
<TextInput fullWidth source="ownr_ea" />
</FormTab>
<FormTab label="Financial">
<TextInput fullWidth source="clm_total" />
<TextInput fullWidth source="owner_owing" />
</FormTab>
<FormTab label="Other">
<TextInput fullWidth source="area_of_damage" />
<TextInput fullWidth source="loss_cat" />
<TextInput fullWidth source="est_number" />
@@ -247,8 +335,8 @@ const JobsEdit = (props) => (
<TextInput fullWidth source="employee_body" />
<TextInput fullWidth source="employee_refinish" />
<TextInput fullWidth source="employee_prep" />
</div>
</SimpleForm>
</FormTab>
</TabbedForm>
</Edit>
);

View File

@@ -2,14 +2,12 @@ import React from "react";
import {
Datagrid,
EditButton,
NumberField,
ReferenceManyField,
Show,
Tab, TabbedShowLayout,
TextField
Tab,
TabbedShowLayout,
TextField,
} from "react-admin";
const JobsShow = (props) => (
@@ -31,7 +29,7 @@ const JobsShow = (props) => (
label="Job Lines"
>
<Datagrid>
<TextField source="id" />
<TextField source="id" />
<TextField source="line_ref" />
<TextField source="line_desc" />

View File

@@ -2,16 +2,16 @@
# yarn lockfile v1
"@apollo/client@^3.0.2":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@apollo/client/-/client-3.0.2.tgz#fadb2b39a0e32950baaef2566442cb3f6de74a52"
integrity sha512-4ighan5Anlj4tK/tdUHs4Mi1njqXZ7AxRCVolz/H702DjPphAJfm+FRkIadPTmwz+OLO+d+tX+6V1VBshf02rg==
"@apollo/client@^3.1.2":
version "3.1.2"
resolved "https://registry.yarnpkg.com/@apollo/client/-/client-3.1.2.tgz#e384f691706c46aef4d9234b63a03ccc06e9c33c"
integrity sha512-GaA/J0CDSSNe0HVm1abeOIJA3M4fs9Ih7wF2z1AI2SLqv5TBLvwBxh0+0+jCSntPZ3gnDQvR7MHjmXota5V1LQ==
dependencies:
"@types/zen-observable" "^0.8.0"
"@wry/context" "^0.5.2"
"@wry/equality" "^0.1.9"
"@wry/equality" "^0.2.0"
fast-json-stable-stringify "^2.0.0"
graphql-tag "^2.10.4"
graphql-tag "^2.11.0"
hoist-non-react-statics "^3.3.2"
optimism "^0.12.1"
prop-types "^15.7.2"
@@ -1022,13 +1022,20 @@
dependencies:
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.3":
"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.3":
version "7.10.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.5.tgz#303d8bd440ecd5a491eae6117fd3367698674c5c"
integrity sha512-otddXKhdNn7d0ptoFRHtMLa8LqDxLYwTjB4nYgM1yy5N6gU/MUf8zqyyLltCH3yAVitBzmwK4us+DD0l/MauAg==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.9.2":
version "7.11.2"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.11.2.tgz#f549c13c754cc40b87644b9fa9f09a6a95fe0736"
integrity sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/template@^7.10.4":
version "7.10.4"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278"
@@ -1123,16 +1130,16 @@
resolved "https://registry.yarnpkg.com/@firebase/analytics-types/-/analytics-types-0.3.1.tgz#3c5f5d71129c88295e17e914e34b391ffda1723c"
integrity sha512-63vVJ5NIBh/JF8l9LuPrQYSzFimk7zYHySQB4Dk9rVdJ8kV/vGQoVTvRu1UW05sEc2Ug5PqtEChtTHU+9hvPcA==
"@firebase/analytics@0.4.0":
version "0.4.0"
resolved "https://registry.yarnpkg.com/@firebase/analytics/-/analytics-0.4.0.tgz#15dfee56619af18c262d4b7cb2f1d4e7c25194de"
integrity sha512-8DC2OBXGYxeeRxCh6eFnrrswNcKm2WsD8EeqGcl0F1P7J0bJ4Q+WpP3DvxofQZ/PtVHdAhzmfmt9r6Xa9mHnrQ==
"@firebase/analytics@0.4.1":
version "0.4.1"
resolved "https://registry.yarnpkg.com/@firebase/analytics/-/analytics-0.4.1.tgz#0f1e6f4e56af11c3956b1652520095a1fbd2c418"
integrity sha512-y5ZuhqX/PwLi0t7AKxNAi3NnlEwXe0rpknulUWUg3/1dALqtd2RrAOATQoV5FNnKK6YUH5UmK0Jb9KcSjsFeNw==
dependencies:
"@firebase/analytics-types" "0.3.1"
"@firebase/component" "0.1.16"
"@firebase/installations" "0.4.14"
"@firebase/component" "0.1.17"
"@firebase/installations" "0.4.15"
"@firebase/logger" "0.2.6"
"@firebase/util" "0.2.50"
"@firebase/util" "0.3.0"
tslib "^1.11.1"
"@firebase/app-types@0.6.1":
@@ -1140,15 +1147,15 @@
resolved "https://registry.yarnpkg.com/@firebase/app-types/-/app-types-0.6.1.tgz#dcbd23030a71c0c74fc95d4a3f75ba81653850e9"
integrity sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg==
"@firebase/app@0.6.8":
version "0.6.8"
resolved "https://registry.yarnpkg.com/@firebase/app/-/app-0.6.8.tgz#e7ccf31cc1d01f16744d6d27c5f9ba8b64338f12"
integrity sha512-Tm7Pi6Dtpx4FFKcpm0jcrZ/qI9oREBxmP3pWlw1jgDW4syRJHmN9/5DYvfFk6FAhj3FrY8E/6F+ngWJfqONotQ==
"@firebase/app@0.6.9":
version "0.6.9"
resolved "https://registry.yarnpkg.com/@firebase/app/-/app-0.6.9.tgz#e60412d9b6012afb73caef2a1353e1b4c4182954"
integrity sha512-X2riRgK49IK8LCQ3j7BKLu3zqHDTJSaT6YgcLewtHuOVwtpHfGODiS1cL5VMvKm3ogxP84GA70tN3sdoL/vTog==
dependencies:
"@firebase/app-types" "0.6.1"
"@firebase/component" "0.1.16"
"@firebase/component" "0.1.17"
"@firebase/logger" "0.2.6"
"@firebase/util" "0.2.50"
"@firebase/util" "0.3.0"
dom-storage "2.1.0"
tslib "^1.11.1"
xmlhttprequest "1.8.0"
@@ -1170,12 +1177,12 @@
dependencies:
"@firebase/auth-types" "0.10.1"
"@firebase/component@0.1.16":
version "0.1.16"
resolved "https://registry.yarnpkg.com/@firebase/component/-/component-0.1.16.tgz#7a0dbdfff1485d45b8485db87a982f053e68761a"
integrity sha512-FvffvFN0LWgv1H/FIyruTECOL69Dhy+JfwoTq+mV39V8Mz9lNpo41etonL5AOr7KmXxYJVbNwkx0L9Ei88i7JA==
"@firebase/component@0.1.17":
version "0.1.17"
resolved "https://registry.yarnpkg.com/@firebase/component/-/component-0.1.17.tgz#2ce3e1aa060eccf0f06d20368ef9a32cf07c07be"
integrity sha512-/tN5iLcFp9rdpTfCJPfQ/o2ziGHlDxOzNx6XD2FoHlu4pG/PPGu+59iRfQXIowBGhxcTGD/l7oJhZEY/PVg0KQ==
dependencies:
"@firebase/util" "0.2.50"
"@firebase/util" "0.3.0"
tslib "^1.11.1"
"@firebase/database-types@0.5.1":
@@ -1185,16 +1192,16 @@
dependencies:
"@firebase/app-types" "0.6.1"
"@firebase/database@0.6.8":
version "0.6.8"
resolved "https://registry.yarnpkg.com/@firebase/database/-/database-0.6.8.tgz#28c9fb4e6a3322cdf9b5f58e768f21d9ac948840"
integrity sha512-Psibz/LD9WBvZRS7A/kkYd5i5l6tBw49adSFmCM2ZJlKE9fxZhxay02AerwfXHiq3gPKVeqXUjBIRuHOWdEXmw==
"@firebase/database@0.6.9":
version "0.6.9"
resolved "https://registry.yarnpkg.com/@firebase/database/-/database-0.6.9.tgz#18a4bdc93b0b10c19a8ad4ff616bba196e5a16e0"
integrity sha512-+X2dNFDpcLEcDRdXp2Hgkf0RnNz3AOIC+Y7UFMQYadm9buB+snXomlnlkMzOj6o+Cp3V7GnpBrKKeeFqzF6wGQ==
dependencies:
"@firebase/auth-interop-types" "0.1.5"
"@firebase/component" "0.1.16"
"@firebase/component" "0.1.17"
"@firebase/database-types" "0.5.1"
"@firebase/logger" "0.2.6"
"@firebase/util" "0.2.50"
"@firebase/util" "0.3.0"
faye-websocket "0.11.3"
tslib "^1.11.1"
@@ -1203,15 +1210,15 @@
resolved "https://registry.yarnpkg.com/@firebase/firestore-types/-/firestore-types-1.12.0.tgz#511e572e946b07f5a603c90e078f0cd714923fac"
integrity sha512-OqNxVb63wPZdUc7YnpacAW1WNIMSKERSewCRi+unCQ0YI0KNfrDSypyGCyel+S3GdOtKMk9KnvDknaGbnaFX4g==
"@firebase/firestore@1.16.1":
version "1.16.1"
resolved "https://registry.yarnpkg.com/@firebase/firestore/-/firestore-1.16.1.tgz#ddf4e357b4d847abe6a57a89d3e2d5b950339176"
integrity sha512-TGtvNIGHMEFFEuOSsRswou576GPZY39vXIsenn0B1Dqz9ACpyDtvAT9YdbG38srlPq7ZKwsP5x04LB43zZ6eAg==
"@firebase/firestore@1.16.2":
version "1.16.2"
resolved "https://registry.yarnpkg.com/@firebase/firestore/-/firestore-1.16.2.tgz#66eedeefab569331efc1ad9ab49a8f1c867a9163"
integrity sha512-iIkAL860oD/QA1uYI9JBbWqBYFWd+DnuSj//BIbOGn3DNAruDFy07g8re1vn+0MMas9bMk6CZATJNCFPeH8AsQ==
dependencies:
"@firebase/component" "0.1.16"
"@firebase/component" "0.1.17"
"@firebase/firestore-types" "1.12.0"
"@firebase/logger" "0.2.6"
"@firebase/util" "0.2.50"
"@firebase/util" "0.3.0"
"@firebase/webchannel-wrapper" "0.2.41"
"@grpc/grpc-js" "^1.0.0"
"@grpc/proto-loader" "^0.5.0"
@@ -1222,12 +1229,12 @@
resolved "https://registry.yarnpkg.com/@firebase/functions-types/-/functions-types-0.3.17.tgz#348bf5528b238eeeeeae1d52e8ca547b21d33a94"
integrity sha512-DGR4i3VI55KnYk4IxrIw7+VG7Q3gA65azHnZxo98Il8IvYLr2UTBlSh72dTLlDf25NW51HqvJgYJDKvSaAeyHQ==
"@firebase/functions@0.4.48":
version "0.4.48"
resolved "https://registry.yarnpkg.com/@firebase/functions/-/functions-0.4.48.tgz#aee8efeacbfdd74834db0c1b44297f59c5bdddaf"
integrity sha512-BwI/JzO/f/nquKG1IS3VqmwMaKEhvM58/08vTnp46krHBsOYqsdD9T2amz+HXGT9fe2HhDsUhgFE8D00S0vqbg==
"@firebase/functions@0.4.49":
version "0.4.49"
resolved "https://registry.yarnpkg.com/@firebase/functions/-/functions-0.4.49.tgz#cca60a2f8e188e020c7e5a5ecf075474885ffb03"
integrity sha512-ma3+z1wMKervmEJCLWxwIjbSV+n3/BTfFPSZdTjt18Wgiso5q4BzEObFkorxaXZiyT3KpZ0qOO97lgcoth2hIA==
dependencies:
"@firebase/component" "0.1.16"
"@firebase/component" "0.1.17"
"@firebase/functions-types" "0.3.17"
"@firebase/messaging-types" "0.4.5"
isomorphic-fetch "2.2.1"
@@ -1238,14 +1245,14 @@
resolved "https://registry.yarnpkg.com/@firebase/installations-types/-/installations-types-0.3.4.tgz#589a941d713f4f64bf9f4feb7f463505bab1afa2"
integrity sha512-RfePJFovmdIXb6rYwtngyxuEcWnOrzdZd9m7xAW0gRxDIjBT20n3BOhjpmgRWXo/DAxRmS7bRjWAyTHY9cqN7Q==
"@firebase/installations@0.4.14":
version "0.4.14"
resolved "https://registry.yarnpkg.com/@firebase/installations/-/installations-0.4.14.tgz#e0c240869bed834d1b5cc696bec0020e8fcb5f7b"
integrity sha512-hQPsaU7wdTq3CFMtFQwZy6LgdXZAkXoUToV4O+ekPbjM65QzaGVogJVU8O2H6ADXoq37SarcUXKe86pcUWdFLA==
"@firebase/installations@0.4.15":
version "0.4.15"
resolved "https://registry.yarnpkg.com/@firebase/installations/-/installations-0.4.15.tgz#ec5a098aea6b5e3e29e73270eeaaf9791587d20a"
integrity sha512-6uGgDocDGu5gI7FeDBDcLaH4npz0cm2f0kctOFK+5N1CyK8Tv2YGv5/uGqlrTtSwDW+8tgKNo/5XXJJOPr9Jsw==
dependencies:
"@firebase/component" "0.1.16"
"@firebase/component" "0.1.17"
"@firebase/installations-types" "0.3.4"
"@firebase/util" "0.2.50"
"@firebase/util" "0.3.0"
idb "3.0.2"
tslib "^1.11.1"
@@ -1259,15 +1266,15 @@
resolved "https://registry.yarnpkg.com/@firebase/messaging-types/-/messaging-types-0.4.5.tgz#452572d3c5b7fa83659fdb1884450477229f5dc4"
integrity sha512-sux4fgqr/0KyIxqzHlatI04Ajs5rc3WM+WmtCpxrKP1E5Bke8xu/0M+2oy4lK/sQ7nov9z15n3iltAHCgTRU3Q==
"@firebase/messaging@0.6.20":
version "0.6.20"
resolved "https://registry.yarnpkg.com/@firebase/messaging/-/messaging-0.6.20.tgz#c6139dad753185706196972629e9235cdda8c2d6"
integrity sha512-1MqyljXnbFBeHYhL6QInVM9aO5MW820yhNmOIVxk58wNXq4tOQLzqnKuvlgZ+ttgqlDzrIYiVf3EOHh5DptttQ==
"@firebase/messaging@0.6.21":
version "0.6.21"
resolved "https://registry.yarnpkg.com/@firebase/messaging/-/messaging-0.6.21.tgz#d301de72ad055c3f302b917b8a11373cd78c7431"
integrity sha512-cunbFNCtUy25Zp4/jn5lenYUPqgHpjKNUwRjKc7vIzYb4IT2Vu/7kaEptO3K0KQBC6O0QV3ZtqQxKrI9aLiSHg==
dependencies:
"@firebase/component" "0.1.16"
"@firebase/installations" "0.4.14"
"@firebase/component" "0.1.17"
"@firebase/installations" "0.4.15"
"@firebase/messaging-types" "0.4.5"
"@firebase/util" "0.2.50"
"@firebase/util" "0.3.0"
idb "3.0.2"
tslib "^1.11.1"
@@ -1276,16 +1283,16 @@
resolved "https://registry.yarnpkg.com/@firebase/performance-types/-/performance-types-0.0.13.tgz#58ce5453f57e34b18186f74ef11550dfc558ede6"
integrity sha512-6fZfIGjQpwo9S5OzMpPyqgYAUZcFzZxHFqOyNtorDIgNXq33nlldTL/vtaUZA8iT9TT5cJlCrF/jthKU7X21EA==
"@firebase/performance@0.3.9":
version "0.3.9"
resolved "https://registry.yarnpkg.com/@firebase/performance/-/performance-0.3.9.tgz#01e27616aca8486c7602e4f51c325c2e7caae6e8"
integrity sha512-Fj22DZXRhhKv1OSUzDxX7AqpJUcDld6tzXK1yxOC8e3v1DFPQMQdM9FoG1m1b/Vrqa6pCCqnqG6gh6VPnEcAzQ==
"@firebase/performance@0.3.10":
version "0.3.10"
resolved "https://registry.yarnpkg.com/@firebase/performance/-/performance-0.3.10.tgz#b68336e23f4b5422bd67f6ce35e28293a6b8945e"
integrity sha512-j/hsx2xfOO1hZulmz7KxemoTIVXxrv94rt79x8qO1HzysT7ziViNvQ9cQGjDZWwVSO29TpLH31GOWLVnwmnxWQ==
dependencies:
"@firebase/component" "0.1.16"
"@firebase/installations" "0.4.14"
"@firebase/component" "0.1.17"
"@firebase/installations" "0.4.15"
"@firebase/logger" "0.2.6"
"@firebase/performance-types" "0.0.13"
"@firebase/util" "0.2.50"
"@firebase/util" "0.3.0"
tslib "^1.11.1"
"@firebase/polyfill@0.3.36":
@@ -1302,16 +1309,16 @@
resolved "https://registry.yarnpkg.com/@firebase/remote-config-types/-/remote-config-types-0.1.9.tgz#fe6bbe4d08f3b6e92fce30e4b7a9f4d6a96d6965"
integrity sha512-G96qnF3RYGbZsTRut7NBX0sxyczxt1uyCgXQuH/eAfUCngxjEGcZQnBdy6mvSdqdJh5mC31rWPO4v9/s7HwtzA==
"@firebase/remote-config@0.1.25":
version "0.1.25"
resolved "https://registry.yarnpkg.com/@firebase/remote-config/-/remote-config-0.1.25.tgz#93c5bda311d6c1302697d6148bdb33bb8dcb9e15"
integrity sha512-8YWefBhy77HMbWXWdbenalx+IDY/XkS+iURQ9qRYvSIFYx6RL04DzlakZNOY9CQAcxTA+cTSt4NNlhjopBjf2Q==
"@firebase/remote-config@0.1.26":
version "0.1.26"
resolved "https://registry.yarnpkg.com/@firebase/remote-config/-/remote-config-0.1.26.tgz#62f448237bc46b986c27ac623b5cc5852007ea05"
integrity sha512-B6+nARVNcswysd6C16nK5tdGECgEpr1wdH6LyqylEQ8hUxYWN18qe49b9uPu+ktaHq0gFLg03gayZvQs7fxJOg==
dependencies:
"@firebase/component" "0.1.16"
"@firebase/installations" "0.4.14"
"@firebase/component" "0.1.17"
"@firebase/installations" "0.4.15"
"@firebase/logger" "0.2.6"
"@firebase/remote-config-types" "0.1.9"
"@firebase/util" "0.2.50"
"@firebase/util" "0.3.0"
tslib "^1.11.1"
"@firebase/storage-types@0.3.13":
@@ -1319,20 +1326,20 @@
resolved "https://registry.yarnpkg.com/@firebase/storage-types/-/storage-types-0.3.13.tgz#cd43e939a2ab5742e109eb639a313673a48b5458"
integrity sha512-pL7b8d5kMNCCL0w9hF7pr16POyKkb3imOW7w0qYrhBnbyJTdVxMWZhb0HxCFyQWC0w3EiIFFmxoz8NTFZDEFog==
"@firebase/storage@0.3.40":
version "0.3.40"
resolved "https://registry.yarnpkg.com/@firebase/storage/-/storage-0.3.40.tgz#4e6ea66d9c3ce489cd1a892940c57c3078853410"
integrity sha512-xTUvSSXh8tNSlch4V+kNbw736H0z/lbW3rHlx1kZVnT8V5M4bXE+TEcG4WpqvcWH3p+N6N1bUorkDbOFgBrztw==
"@firebase/storage@0.3.41":
version "0.3.41"
resolved "https://registry.yarnpkg.com/@firebase/storage/-/storage-0.3.41.tgz#cba8946f980d70e68d52cfb110ad109592a645d0"
integrity sha512-2imzI78HcB7FjUqXMRHsGLlZnTYkaCHBjJflSbypwLrEty0hreR6vx3ThOO5y0MFH93WwifqUFJAa+Twkx6CIA==
dependencies:
"@firebase/component" "0.1.16"
"@firebase/component" "0.1.17"
"@firebase/storage-types" "0.3.13"
"@firebase/util" "0.2.50"
"@firebase/util" "0.3.0"
tslib "^1.11.1"
"@firebase/util@0.2.50":
version "0.2.50"
resolved "https://registry.yarnpkg.com/@firebase/util/-/util-0.2.50.tgz#77666b845dcb49bc217650aa296a7a8986c06b44"
integrity sha512-vFE6+Jfc25u0ViSpFxxq0q5s+XmuJ/y7CL3ud79RQe+WLFFg+j0eH1t23k0yNSG9vZNM7h3uHRIXbV97sYLAyw==
"@firebase/util@0.3.0":
version "0.3.0"
resolved "https://registry.yarnpkg.com/@firebase/util/-/util-0.3.0.tgz#c3e938192cde4e1c6260aecaaf22103add2352f5"
integrity sha512-GTwC+FSLeCPc44/TXCDReNQ5FPRIS5cb8Gr1XcD1TgiNBOvmyx61Om2YLwHp2GnN++6m6xmwmXARm06HOukATA==
dependencies:
tslib "^1.11.1"
@@ -1835,17 +1842,6 @@
"@svgr/plugin-svgo" "^4.3.1"
loader-utils "^1.2.3"
"@testing-library/dom@*":
version "7.21.4"
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.21.4.tgz#24b045f3161b7c91fdb35da7c001908cdc99b55b"
integrity sha512-IXjKRTAH31nQ+mx6q3IPw85RTLul8VlWBm1rxURoxDt7JI0HPlAAfbtrKTdeq83XYCYO7HSHogyV+OsD+6FX0Q==
dependencies:
"@babel/runtime" "^7.10.3"
"@types/aria-query" "^4.2.0"
aria-query "^4.2.2"
dom-accessibility-api "^0.4.6"
pretty-format "^25.5.0"
"@testing-library/dom@^5.6.1":
version "5.6.1"
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-5.6.1.tgz#705a1cb4a039b877c1e69e916824038e837ab637"
@@ -1857,34 +1853,41 @@
pretty-format "^24.8.0"
wait-for-expect "^1.2.0"
"@testing-library/dom@^6.15.0":
version "6.16.0"
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-6.16.0.tgz#04ada27ed74ad4c0f0d984a1245bb29b1fd90ba9"
integrity sha512-lBD88ssxqEfz0wFL6MeUyyWZfV/2cjEZZV3YRpb2IoJRej/4f1jB0TzqIOznTpfR1r34CNesrubxwIlAQ8zgPA==
"@testing-library/dom@^7.17.1":
version "7.21.8"
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.21.8.tgz#b64b266264bff9135eba3b5c6d4ddc995a3371e6"
integrity sha512-iK1rJubFoeD5gxCryokwh09tnJa1Y4doNDbNFYYqOqz6ELwB1+kEAwlezA5xwMi8QrK7xg+1/aBMzb9X/A/EmA==
dependencies:
"@babel/runtime" "^7.8.4"
"@sheerun/mutationobserver-shim" "^0.3.2"
"@types/testing-library__dom" "^6.12.1"
aria-query "^4.0.2"
dom-accessibility-api "^0.3.0"
pretty-format "^25.1.0"
wait-for-expect "^3.0.2"
"@babel/runtime" "^7.10.3"
"@types/aria-query" "^4.2.0"
aria-query "^4.2.2"
dom-accessibility-api "^0.4.6"
pretty-format "^25.5.0"
"@testing-library/jest-dom@^4.2.4":
version "4.2.4"
resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-4.2.4.tgz#00dfa0cbdd837d9a3c2a7f3f0a248ea6e7b89742"
integrity sha512-j31Bn0rQo12fhCWOUWy9fl7wtqkp7In/YP2p5ZFyRuiiB9Qs3g+hS4gAmDWONbAHcRmVooNJ5eOHQDCOmUFXHg==
"@testing-library/jest-dom@^5.11.2":
version "5.11.2"
resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.11.2.tgz#c49de331555c70127b5d7fc97344ad5265f4c54c"
integrity sha512-s+rWJx+lanEGKqvOl4qJR0rGjCrxsEjj9qjxFlg4NV4/FRD7fnUUAWPHqwpyafNHfLYArs58FADgdn4UKmjFmw==
dependencies:
"@babel/runtime" "^7.5.1"
chalk "^2.4.1"
css "^2.2.3"
"@babel/runtime" "^7.9.2"
"@types/testing-library__jest-dom" "^5.9.1"
aria-query "^4.2.2"
chalk "^3.0.0"
css "^3.0.0"
css.escape "^1.5.1"
jest-diff "^24.0.0"
jest-matcher-utils "^24.0.0"
lodash "^4.17.11"
pretty-format "^24.0.0"
jest-diff "^25.1.0"
jest-matcher-utils "^25.1.0"
lodash "^4.17.15"
redent "^3.0.0"
"@testing-library/react@^10.4.8":
version "10.4.8"
resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-10.4.8.tgz#5eb730291b8fd81cdb2d8877770d060b044ae4a4"
integrity sha512-clgpFR6QHiRRcdhFfAKDhH8UXpNASyfkkANhtCsCVBnai+O+mK1rGtMES+Apc7ql5Wyxu7j8dcLiC4pV5VblHA==
dependencies:
"@babel/runtime" "^7.10.3"
"@testing-library/dom" "^7.17.1"
"@testing-library/react@^8.0.7":
version "8.0.9"
resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-8.0.9.tgz#1ecd96bc3471b06dd2f9763b6e53a7ace28a54a2"
@@ -1893,19 +1896,12 @@
"@babel/runtime" "^7.5.5"
"@testing-library/dom" "^5.6.1"
"@testing-library/react@^9.3.2":
version "9.5.0"
resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-9.5.0.tgz#71531655a7890b61e77a1b39452fbedf0472ca5e"
integrity sha512-di1b+D0p+rfeboHO5W7gTVeZDIK5+maEgstrZbWZSSvxDyfDRkkyBE1AJR5Psd6doNldluXlCWqXriUfqu/9Qg==
"@testing-library/user-event@^12.1.0":
version "12.1.0"
resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-12.1.0.tgz#a2597419466a93e338c91baa7bb22d4da0309d1d"
integrity sha512-aH/XuNFpPD6dA+fh754EGqKeAzpH66HpLJYkv9vOAih2yGmTM8JiZ8uisQDGWRPkc6sxE2zCqDwLR4ZskhRCxw==
dependencies:
"@babel/runtime" "^7.8.4"
"@testing-library/dom" "^6.15.0"
"@types/testing-library__react" "^9.1.2"
"@testing-library/user-event@^7.1.2":
version "7.2.1"
resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-7.2.1.tgz#2ad4e844175a3738cb9e7064be5ea070b8863a1c"
integrity sha512-oZ0Ib5I4Z2pUEcoo95cT1cr6slco9WY7yiPpG+RGNkj8YcYgJnM7pXmYmorNOReh8MIGcKSqXyeGjxnr8YiZbA==
"@babel/runtime" "^7.10.2"
"@types/aria-query@^4.2.0":
version "4.2.0"
@@ -1989,6 +1985,14 @@
"@types/istanbul-lib-coverage" "*"
"@types/istanbul-lib-report" "*"
"@types/jest@*":
version "26.0.8"
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.8.tgz#f5c5559cf25911ce227f7ce30f1f160f24966369"
integrity sha512-eo3VX9jGASSuv680D4VQ89UmuLZneNxv2MCZjfwlInav05zXVJTzfc//lavdV0GPwSxsXJTy2jALscB7Acqg0g==
dependencies:
jest-diff "^25.2.1"
pretty-format "^25.2.1"
"@types/json-schema@^7.0.3":
version "7.0.4"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339"
@@ -2024,7 +2028,7 @@
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
"@types/prop-types@*":
"@types/prop-types@*", "@types/prop-types@^15.7.3":
version "15.7.3"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
@@ -2034,13 +2038,6 @@
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8"
integrity sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==
"@types/react-dom@*":
version "16.9.8"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.8.tgz#fe4c1e11dfc67155733dfa6aa65108b4971cb423"
integrity sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA==
dependencies:
"@types/react" "*"
"@types/react-transition-group@^4.2.0":
version "4.4.0"
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.0.tgz#882839db465df1320e4753e6e9f70ca7e9b4d46d"
@@ -2061,28 +2058,12 @@
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==
"@types/testing-library__dom@*":
version "7.5.0"
resolved "https://registry.yarnpkg.com/@types/testing-library__dom/-/testing-library__dom-7.5.0.tgz#e0a00dd766983b1d6e9d10d33e708005ce6ad13e"
integrity sha512-mj1aH4cj3XUpMEgVpognma5kHVtbm6U6cHZmEFzCRiXPvKkuHrFr3+yXdGLXvfFRBaQIVshPGHI+hGTOJlhS/g==
"@types/testing-library__jest-dom@^5.9.1":
version "5.9.2"
resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.9.2.tgz#59e4771a1cf87d51e89a5cc8195cd3b647cba322"
integrity sha512-K7nUSpH/5i8i0NagTJ+uFUDRueDlnMNhJtMjMwTGPPSqyImbWC/hgKPDCKt6Phu2iMJg2kWqlax+Ucj2DKMwpA==
dependencies:
"@testing-library/dom" "*"
"@types/testing-library__dom@^6.12.1":
version "6.14.0"
resolved "https://registry.yarnpkg.com/@types/testing-library__dom/-/testing-library__dom-6.14.0.tgz#1aede831cb4ed4a398448df5a2c54b54a365644e"
integrity sha512-sMl7OSv0AvMOqn1UJ6j1unPMIHRXen0Ita1ujnMX912rrOcawe4f7wu0Zt9GIQhBhJvH2BaibqFgQ3lP+Pj2hA==
dependencies:
pretty-format "^24.3.0"
"@types/testing-library__react@^9.1.2":
version "9.1.3"
resolved "https://registry.yarnpkg.com/@types/testing-library__react/-/testing-library__react-9.1.3.tgz#35eca61cc6ea923543796f16034882a1603d7302"
integrity sha512-iCdNPKU3IsYwRK9JieSYAiX0+aYDXOGAmrC/3/M7AqqSDKnWWVv07X+Zk1uFSL7cMTUYzv4lQRfohucEocn5/w==
dependencies:
"@types/react-dom" "*"
"@types/testing-library__dom" "*"
pretty-format "^25.1.0"
"@types/jest" "*"
"@types/yargs-parser@*":
version "15.0.0"
@@ -2312,13 +2293,20 @@
dependencies:
tslib "^1.9.3"
"@wry/equality@^0.1.2", "@wry/equality@^0.1.9":
"@wry/equality@^0.1.2":
version "0.1.11"
resolved "https://registry.yarnpkg.com/@wry/equality/-/equality-0.1.11.tgz#35cb156e4a96695aa81a9ecc4d03787bc17f1790"
integrity sha512-mwEVBDUVODlsQQ5dfuLUS5/Tf7jqUKyhKYHmVi4fPB6bDMOfWvUPJmKgS1Z7Za/sOI3vzWt4+O7yCiL/70MogA==
dependencies:
tslib "^1.9.3"
"@wry/equality@^0.2.0":
version "0.2.0"
resolved "https://registry.yarnpkg.com/@wry/equality/-/equality-0.2.0.tgz#a312d1b6a682d0909904c2bcd355b02303104fb7"
integrity sha512-Y4d+WH6hs+KZJUC8YKLYGarjGekBrhslDbf/R20oV+AakHPINSitHfDRQz3EGcEWc1luXYNUvMhawWtZVWNGvQ==
dependencies:
tslib "^1.9.3"
"@xtuc/ieee754@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
@@ -2641,7 +2629,7 @@ aria-query@3.0.0, aria-query@^3.0.0:
ast-types-flow "0.0.7"
commander "^2.11.0"
aria-query@^4.0.2, aria-query@^4.2.2:
aria-query@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b"
integrity sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==
@@ -4047,7 +4035,7 @@ css.escape@^1.5.1:
resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb"
integrity sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=
css@^2.0.0, css@^2.2.3:
css@^2.0.0:
version "2.2.4"
resolved "https://registry.yarnpkg.com/css/-/css-2.2.4.tgz#c646755c73971f2bba6a601e2cf2fd71b1298929"
integrity sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==
@@ -4057,6 +4045,15 @@ css@^2.0.0, css@^2.2.3:
source-map-resolve "^0.5.2"
urix "^0.1.0"
css@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/css/-/css-3.0.0.tgz#4447a4d58fdd03367c516ca9f64ae365cee4aa5d"
integrity sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==
dependencies:
inherits "^2.0.4"
source-map "^0.6.1"
source-map-resolve "^0.6.0"
cssdb@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-4.4.0.tgz#3bf2f2a68c10f5c6a08abd92378331ee803cddb0"
@@ -4352,6 +4349,11 @@ diff-sequences@^24.9.0:
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5"
integrity sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew==
diff-sequences@^25.2.6:
version "25.2.6"
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd"
integrity sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg==
diffie-hellman@^5.0.0:
version "5.0.3"
resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875"
@@ -4411,11 +4413,6 @@ doctrine@^3.0.0:
dependencies:
esutils "^2.0.2"
dom-accessibility-api@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.3.0.tgz#511e5993dd673b97c87ea47dba0e3892f7e0c983"
integrity sha512-PzwHEmsRP3IGY4gv/Ug+rMeaTIyTJvadCb+ujYXYeIylbHJezIyNToe8KfEgHTCEYyC+/bUghYOGg8yMGlZ6vA==
dom-accessibility-api@^0.4.6:
version "0.4.6"
resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.4.6.tgz#f3f2af68aee01b1c862f37918d41841bb1aaf92a"
@@ -5315,25 +5312,25 @@ find-up@^3.0.0:
dependencies:
locate-path "^3.0.0"
firebase@^7.17.0:
version "7.17.0"
resolved "https://registry.yarnpkg.com/firebase/-/firebase-7.17.0.tgz#85dcd7c85d6dbcba7740dd5920a7f56bf8177e12"
integrity sha512-+y7c1pCj8xp98CIDhVjg0rKhGtsFskGB8hyhjsyp549Upwa0cropdK5emCFTmMIbvDjZmP8rTuuDXPBeREAaCg==
firebase@^7.17.1:
version "7.17.1"
resolved "https://registry.yarnpkg.com/firebase/-/firebase-7.17.1.tgz#6b2566d91a820a7993e3d2c75435f8baaabb58bb"
integrity sha512-g2Wkk2fz8VoeSrxv2PIQizm2j74EtbpxQ+wd2AvH2iEF5LRaJOsk3zVBtIlyJIQ3vGTmlutIxtyyoDAQcPO9TA==
dependencies:
"@firebase/analytics" "0.4.0"
"@firebase/app" "0.6.8"
"@firebase/analytics" "0.4.1"
"@firebase/app" "0.6.9"
"@firebase/app-types" "0.6.1"
"@firebase/auth" "0.14.9"
"@firebase/database" "0.6.8"
"@firebase/firestore" "1.16.1"
"@firebase/functions" "0.4.48"
"@firebase/installations" "0.4.14"
"@firebase/messaging" "0.6.20"
"@firebase/performance" "0.3.9"
"@firebase/database" "0.6.9"
"@firebase/firestore" "1.16.2"
"@firebase/functions" "0.4.49"
"@firebase/installations" "0.4.15"
"@firebase/messaging" "0.6.21"
"@firebase/performance" "0.3.10"
"@firebase/polyfill" "0.3.36"
"@firebase/remote-config" "0.1.25"
"@firebase/storage" "0.3.40"
"@firebase/util" "0.2.50"
"@firebase/remote-config" "0.1.26"
"@firebase/storage" "0.3.41"
"@firebase/util" "0.3.0"
flat-cache@^2.0.1:
version "2.0.1"
@@ -5651,11 +5648,16 @@ graphql-ast-types-browser@~1.0.2:
resolved "https://registry.yarnpkg.com/graphql-ast-types-browser/-/graphql-ast-types-browser-1.0.2.tgz#474305af7e76f9692df6e50a88fb668ce258c4a4"
integrity sha512-QuKZ+Et3dE7SyO5c41eNPlJc7+HwQxOzHfmIhqzj4cUgAGyhSwVkKb7K24zom8y6y0VnG7Xb3RRypjIVvfIevQ==
graphql-tag@^2.10.1, graphql-tag@^2.10.4, graphql-tag@^2.4.2:
graphql-tag@^2.10.1, graphql-tag@^2.4.2:
version "2.10.4"
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.10.4.tgz#2f301a98219be8b178a6453bb7e33b79b66d8f83"
integrity sha512-O7vG5BT3w6Sotc26ybcvLKNTdfr4GfsIVMD+LdYqXCeJIYPRyp8BIsDOUtxw7S1PYvRw5vH3278J2EDezR6mfA==
graphql-tag@^2.11.0:
version "2.11.0"
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.11.0.tgz#1deb53a01c46a7eb401d6cb59dec86fa1cccbffd"
integrity sha512-VmsD5pJqWJnQZMUeRwrDhfgoyqcfwEkvtpANqcoUG8/tOLkwNgU9mzub/Mc78OJMhHjx7gfAMTxzdG43VGg3bA==
graphql@^15.3.0:
version "15.3.0"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.3.0.tgz#3ad2b0caab0d110e3be4a5a9b2aa281e362b5278"
@@ -6109,7 +6111,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -6663,7 +6665,7 @@ jest-config@^24.9.0:
pretty-format "^24.9.0"
realpath-native "^1.1.0"
jest-diff@^24.0.0, jest-diff@^24.9.0:
jest-diff@^24.9.0:
version "24.9.0"
resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.9.0.tgz#931b7d0d5778a1baf7452cb816e325e3724055da"
integrity sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ==
@@ -6673,6 +6675,16 @@ jest-diff@^24.0.0, jest-diff@^24.9.0:
jest-get-type "^24.9.0"
pretty-format "^24.9.0"
jest-diff@^25.1.0, jest-diff@^25.2.1, jest-diff@^25.5.0:
version "25.5.0"
resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.5.0.tgz#1dd26ed64f96667c068cef026b677dfa01afcfa9"
integrity sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A==
dependencies:
chalk "^3.0.0"
diff-sequences "^25.2.6"
jest-get-type "^25.2.6"
pretty-format "^25.5.0"
jest-docblock@^24.3.0:
version "24.9.0"
resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-24.9.0.tgz#7970201802ba560e1c4092cc25cbedf5af5a8ce2"
@@ -6731,6 +6743,11 @@ jest-get-type@^24.9.0:
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.9.0.tgz#1684a0c8a50f2e4901b6644ae861f579eed2ef0e"
integrity sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==
jest-get-type@^25.2.6:
version "25.2.6"
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-25.2.6.tgz#0b0a32fab8908b44d508be81681487dbabb8d877"
integrity sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==
jest-haste-map@^24.9.0:
version "24.9.0"
resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.9.0.tgz#b38a5d64274934e21fa417ae9a9fbeb77ceaac7d"
@@ -6780,7 +6797,7 @@ jest-leak-detector@^24.9.0:
jest-get-type "^24.9.0"
pretty-format "^24.9.0"
jest-matcher-utils@^24.0.0, jest-matcher-utils@^24.9.0:
jest-matcher-utils@^24.9.0:
version "24.9.0"
resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.9.0.tgz#f5b3661d5e628dffe6dd65251dfdae0e87c3a073"
integrity sha512-OZz2IXsu6eaiMAwe67c1T+5tUAtQyQx27/EMEkbFAGiw52tB9em+uGbzpcgYVpA8wl0hlxKPZxrly4CXU/GjHA==
@@ -6790,6 +6807,16 @@ jest-matcher-utils@^24.0.0, jest-matcher-utils@^24.9.0:
jest-get-type "^24.9.0"
pretty-format "^24.9.0"
jest-matcher-utils@^25.1.0:
version "25.5.0"
resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-25.5.0.tgz#fbc98a12d730e5d2453d7f1ed4a4d948e34b7867"
integrity sha512-VWI269+9JS5cpndnpCwm7dy7JtGQT30UHfrnM3mXl22gHGt/b7NkjBqXfbhZ8V4B7ANUsjK18PlSBmG0YH7gjw==
dependencies:
chalk "^3.0.0"
jest-diff "^25.5.0"
jest-get-type "^25.2.6"
pretty-format "^25.5.0"
jest-message-util@^24.9.0:
version "24.9.0"
resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-24.9.0.tgz#527f54a1e380f5e202a8d1149b0ec872f43119e3"
@@ -9346,7 +9373,7 @@ pretty-error@^2.1.1:
renderkid "^2.0.1"
utila "~0.4"
pretty-format@^24.0.0, pretty-format@^24.3.0, pretty-format@^24.8.0, pretty-format@^24.9.0:
pretty-format@^24.8.0, pretty-format@^24.9.0:
version "24.9.0"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.9.0.tgz#12fac31b37019a4eea3c11aa9a959eb7628aa7c9"
integrity sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==
@@ -9356,7 +9383,7 @@ pretty-format@^24.0.0, pretty-format@^24.3.0, pretty-format@^24.8.0, pretty-form
ansi-styles "^3.2.0"
react-is "^16.8.4"
pretty-format@^25.1.0, pretty-format@^25.5.0:
pretty-format@^25.2.1, pretty-format@^25.5.0:
version "25.5.0"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.5.0.tgz#7873c1d774f682c34b8d48b6743a2bf2ac55791a"
integrity sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==
@@ -9563,10 +9590,10 @@ querystringify@^2.1.1:
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e"
integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==
ra-core@^3.7.1:
version "3.7.1"
resolved "https://registry.yarnpkg.com/ra-core/-/ra-core-3.7.1.tgz#369453d8cce3a1e8ef7d01a6bb2dcd6bbd5faa1c"
integrity sha512-T6gYppeTMoG4qbpD4cHJ76EQTuf0b6DedMSL8ekFuZdh+MDtpGywUlCzkcSQK9J4Ck31prtVvONXGm9uwEyEyg==
ra-core@^3.7.2:
version "3.7.2"
resolved "https://registry.yarnpkg.com/ra-core/-/ra-core-3.7.2.tgz#efbc873cfbf840c4c53e9a7317d6fd526bf55b39"
integrity sha512-cm/RGWX9WUoVVTJkdKKhOOx6tG7IOaY9p+YxbFvweau0Gi9sK+i9t687ifrENukW2YCxIeKpyImjKAT7l8BiLA==
dependencies:
"@testing-library/react" "^8.0.7"
classnames "~2.2.5"
@@ -9601,25 +9628,25 @@ ra-data-hasura-graphql@^0.1.12:
minimist ">=1.2.3"
ra-data-graphql "^3.6.1"
ra-i18n-polyglot@^3.7.1:
version "3.7.1"
resolved "https://registry.yarnpkg.com/ra-i18n-polyglot/-/ra-i18n-polyglot-3.7.1.tgz#68ae7ec78c43f2700dd0921d7263546ae35c5b34"
integrity sha512-BWYJKGp0nZP8ATsYqfWkvQpyaVx4KrYw/XT6Taf8HfhfPqZaiOQUhfw4a9co3Nm/0Gu3mFdHq5CVMDsEZL1r+w==
ra-i18n-polyglot@^3.7.2:
version "3.7.2"
resolved "https://registry.yarnpkg.com/ra-i18n-polyglot/-/ra-i18n-polyglot-3.7.2.tgz#66589baf838a24ab71f251b1c41ec75bcc294f1f"
integrity sha512-WFb6tw/lBnmne6BZit3bO0R45jTRBIkbd/8UMNMRBpZn9vFdphF9NShrNuVPLGnxQPBoz0CBRSTEvyKX7HGnTg==
dependencies:
node-polyglot "^2.2.2"
ra-core "^3.7.1"
ra-core "^3.7.2"
ra-language-english@^3.7.1:
version "3.7.1"
resolved "https://registry.yarnpkg.com/ra-language-english/-/ra-language-english-3.7.1.tgz#ed1aa4dd827454572fcc43a53c3a2944d5619d68"
integrity sha512-85jgvpEdRgfQ1SVVVudUh7AOlmhbiLhT/iLn5tXO9N7HCudK7Fno37y+txjrPywki6Vs/XGkRLG/eNsrYmyMFQ==
ra-language-english@^3.7.2:
version "3.7.2"
resolved "https://registry.yarnpkg.com/ra-language-english/-/ra-language-english-3.7.2.tgz#19a929cba73c03d34aa0586145005a4759cc2c81"
integrity sha512-1TQPTDgJ4gvF6uh7FcJhWqRUmi7P4Mo1Oy5e57BkrFO6M50Fl+e3McrI0grDQlSbn2bcFRNm13tWK/Z+KXdGeQ==
dependencies:
ra-core "^3.7.1"
ra-core "^3.7.2"
ra-ui-materialui@^3.7.1:
version "3.7.1"
resolved "https://registry.yarnpkg.com/ra-ui-materialui/-/ra-ui-materialui-3.7.1.tgz#111883d4d27c197f3171e58a836811839619f509"
integrity sha512-EA5z/2fnqv1HTJuzXCoxZoCIgohHY+C+zVjDjEBQ/ABa/PosZT+epOsKK9Gn4gLuHbLHJCwdjcOZXJHH6F1jiQ==
ra-ui-materialui@^3.7.2:
version "3.7.2"
resolved "https://registry.yarnpkg.com/ra-ui-materialui/-/ra-ui-materialui-3.7.2.tgz#1afeb943afc1940787e91094f4fbca72cd082f4c"
integrity sha512-dNmgkZ2YDmXMrCRLLhSBBjSzM9B1x5qsSU0C4az2LyDnniphE4UOjkUCSj0DpgzVC4sevZub5H7QnjpIF3LZcQ==
dependencies:
autosuggest-highlight "^3.1.1"
classnames "~2.2.5"
@@ -9632,7 +9659,7 @@ ra-ui-materialui@^3.7.1:
prop-types "^15.7.0"
query-string "^5.1.1"
react-dropzone "^10.1.7"
react-transition-group "^4.3.0"
react-transition-group "^4.4.1"
recompose "~0.26.0"
raf@^3.4.1:
@@ -9672,10 +9699,10 @@ raw-body@2.4.0:
iconv-lite "0.4.24"
unpipe "1.0.0"
react-admin@^3.7.1:
version "3.7.1"
resolved "https://registry.yarnpkg.com/react-admin/-/react-admin-3.7.1.tgz#315573908361678125085005589c51512be2235e"
integrity sha512-eVOdvh7HIc83BmoFjZNTcRU5l9XNhk6rHn5vBu1zcl5i//91jPw0NTS1uxG18STjiERckh/DVphELC5tFRIGwA==
react-admin@^3.7.2:
version "3.7.2"
resolved "https://registry.yarnpkg.com/react-admin/-/react-admin-3.7.2.tgz#f1c2895c208c06f63bc8f5eb952ad232a225de18"
integrity sha512-VK99I+s1Dd1f4lFCUY3BRdTO6P1c6Yg1r/px0ZbbM6WyJ7+qFRVoXEhDlIB494WEcjZoJKZSDB464fyxePEWtA==
dependencies:
"@material-ui/core" "^4.3.3"
"@material-ui/icons" "^4.2.1"
@@ -9683,10 +9710,10 @@ react-admin@^3.7.1:
connected-react-router "^6.5.2"
final-form "^4.18.5"
final-form-arrays "^3.0.1"
ra-core "^3.7.1"
ra-i18n-polyglot "^3.7.1"
ra-language-english "^3.7.1"
ra-ui-materialui "^3.7.1"
ra-core "^3.7.2"
ra-i18n-polyglot "^3.7.2"
ra-language-english "^3.7.2"
ra-ui-materialui "^3.7.2"
react-final-form "^6.3.3"
react-final-form-arrays "^3.1.1"
react-redux "^7.1.0"
@@ -9775,6 +9802,13 @@ react-final-form@^6.3.3:
dependencies:
"@babel/runtime" "^7.10.0"
react-icons@^3.10.0:
version "3.10.0"
resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-3.10.0.tgz#6c217a2dde2e8fa8d293210023914b123f317297"
integrity sha512-WsQ5n1JToG9VixWilSo1bHv842Cj5aZqTGiS3Ud47myF6aK7S/IUY2+dHcBdmkQcCFRuHsJ9OMUI0kTDfjyZXQ==
dependencies:
camelcase "^5.0.0"
react-is@^16.12.0, react-is@^16.5.2, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.9.0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@@ -9880,7 +9914,7 @@ react-scripts@3.4.1:
optionalDependencies:
fsevents "2.1.2"
react-transition-group@^4.3.0, react-transition-group@^4.4.0:
react-transition-group@^4.4.0, react-transition-group@^4.4.1:
version "4.4.1"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9"
integrity sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw==
@@ -10727,6 +10761,14 @@ source-map-resolve@^0.5.0, source-map-resolve@^0.5.2:
source-map-url "^0.4.0"
urix "^0.1.0"
source-map-resolve@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.6.0.tgz#3d9df87e236b53f16d01e58150fc7711138e5ed2"
integrity sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==
dependencies:
atob "^2.1.2"
decode-uri-component "^0.2.0"
source-map-support@^0.5.6, source-map-support@~0.5.12:
version "0.5.16"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042"
@@ -11692,11 +11734,6 @@ wait-for-expect@^1.2.0:
resolved "https://registry.yarnpkg.com/wait-for-expect/-/wait-for-expect-1.3.0.tgz#65241ce355425f907f5d127bdb5e72c412ff830c"
integrity sha512-8fJU7jiA96HfGPt+P/UilelSAZfhMBJ52YhKzlmZQvKEZU2EcD1GQ0yqGB6liLdHjYtYAoGVigYwdxr5rktvzA==
wait-for-expect@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/wait-for-expect/-/wait-for-expect-3.0.2.tgz#d2f14b2f7b778c9b82144109c8fa89ceaadaa463"
integrity sha512-cfS1+DZxuav1aBYbaO/kE06EOS8yRw7qOFoD3XtjTkYvCvh3zUvNST8DXK/nPaeqIzIv3P3kL3lRJn8iwOiSag==
walker@^1.0.7, walker@~1.0.5:
version "1.0.7"
resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb"

View File

@@ -1,4 +1,4 @@
<babeledit_project be_version="2.7.1" version="1.2">
<babeledit_project version="1.2" be_version="2.7.1">
<!--
BabelEdit project file
@@ -526,6 +526,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>completingjobs</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>history</name>
<definition_loaded>false</definition_loaded>
@@ -547,6 +568,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>nocompletingjobs</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>nodateselected</name>
<definition_loaded>false</definition_loaded>
@@ -1190,6 +1232,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>enforce_class</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>federal_tax_id</name>
<definition_loaded>false</definition_loaded>
@@ -1337,6 +1400,69 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>md_categories</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>md_classes</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>md_ins_cos</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>md_referral_sources</name>
<definition_loaded>false</definition_loaded>
@@ -1463,6 +1589,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>prodtargethrs</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>responsibilitycenter</name>
<definition_loaded>false</definition_loaded>
@@ -3000,6 +3147,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>rbac</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<folder_node>
<name>responsibilitycenters</name>
<children>
@@ -6711,6 +6879,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>hours</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>in</name>
<definition_loaded>false</definition_loaded>
@@ -7194,6 +7383,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>view</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>yes</name>
<definition_loaded>false</definition_loaded>
@@ -7309,6 +7519,111 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>newversionmessage</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>newversiontitle</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>notfoundsub</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>notfoundtitle</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>rbacunauth</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>unsavedchanges</name>
<definition_loaded>false</definition_loaded>
@@ -9985,6 +10300,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>recalculate</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>reconcile</name>
<definition_loaded>false</definition_loaded>
@@ -10048,6 +10384,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>viewdetail</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
<folder_node>
@@ -10499,6 +10856,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>category</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>ccc</name>
<definition_loaded>false</definition_loaded>
@@ -10646,6 +11024,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>class</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>clm_no</name>
<definition_loaded>false</definition_loaded>
@@ -13003,6 +13402,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>tax_registration_number</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>towing_payable</name>
<definition_loaded>false</definition_loaded>
@@ -13176,27 +13596,6 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>dedinfo</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>estdates</name>
<definition_loaded>false</definition_loaded>
@@ -13218,27 +13617,6 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>inscoinfo</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>laborrates</name>
<definition_loaded>false</definition_loaded>
@@ -13281,6 +13659,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>other</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>repairdates</name>
<definition_loaded>false</definition_loaded>
@@ -13711,6 +14110,48 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>cost_labor</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>cost_parts</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<folder_node>
<name>create</name>
<children>
@@ -14204,6 +14645,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>labortotals</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>lines</name>
<definition_loaded>false</definition_loaded>
@@ -16004,28 +16466,7 @@
</translations>
</concept_node>
<concept_node>
<name>financials</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>insurance</name>
<name>general</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
@@ -16087,6 +16528,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>rates</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>repairdata</name>
<definition_loaded>false</definition_loaded>
@@ -16108,6 +16570,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>totals</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
<folder_node>
@@ -19025,6 +19508,32 @@
</concept_node>
</children>
</folder_node>
<folder_node>
<name>successes</name>
<children>
<concept_node>
<name>removed</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
</children>
</folder_node>
<folder_node>
@@ -20385,6 +20894,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>accounting-payments</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>accounting-receivables</name>
<definition_loaded>false</definition_loaded>
@@ -20451,6 +20981,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>accounting-payments</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>accounting-receivables</name>
<definition_loaded>false</definition_loaded>

View File

@@ -2,66 +2,69 @@
"name": "bodyshop",
"version": "0.1.0001",
"private": true,
"proxy": "https://localhost:5000",
"proxy": "http://localhost:5000",
"dependencies": {
"@lourenci/react-kanban": "^2.0.0",
"@stripe/react-stripe-js": "^1.1.2",
"@stripe/stripe-js": "^1.8.0",
"@tanem/react-nprogress": "^3.0.34",
"@stripe/stripe-js": "^1.9.0",
"@tanem/react-nprogress": "^3.0.40",
"@tinymce/tinymce-react": "^3.6.0",
"antd": "^4.4.2",
"antd": "^4.6.1",
"apollo-boost": "^0.4.9",
"apollo-link-context": "^1.0.20",
"apollo-link-error": "^1.1.13",
"apollo-link-logger": "^1.2.3",
"apollo-link-retry": "^2.2.16",
"apollo-link-ws": "^1.0.20",
"axios": "^0.19.2",
"axios": "^0.20.0",
"dinero.js": "^1.8.1",
"dotenv": "^8.2.0",
"fingerprintjs2": "^2.1.0",
"firebase": "^7.16.0",
"fingerprintjs2": "^2.1.2",
"firebase": "^7.19.0",
"graphql": "^15.3.0",
"i18next": "^19.6.0",
"i18next-browser-languagedetector": "^5.0.0",
"logrocket": "^1.0.9",
"i18next": "^19.7.0",
"i18next-browser-languagedetector": "^6.0.1",
"inline-css": "^2.6.3",
"logrocket": "^1.0.11",
"moment-business-days": "^1.2.0",
"node-sass": "^4.14.1",
"phone": "^2.4.13",
"phone": "^2.4.15",
"prop-types": "^15.7.2",
"query-string": "^6.13.1",
"react": "^16.13.1",
"react-apollo": "^3.1.5",
"react-barcode": "^1.4.0",
"react-big-calendar": "^0.26.0",
"react-big-calendar": "^0.26.1",
"react-dom": "^16.13.1",
"react-drag-listview": "^0.1.7",
"react-email-editor": "^1.1.1",
"react-ga": "^3.1.2",
"react-grid-gallery": "^0.5.5",
"react-grid-layout": "^0.18.3",
"react-i18next": "^11.7.0",
"react-icons": "^3.10.0",
"react-image-file-resizer": "^0.3.1",
"react-grid-layout": "^1.0.0",
"react-i18next": "^11.7.1",
"react-icons": "^3.11.0",
"react-image-file-resizer": "^0.3.6",
"react-moment": "^0.9.7",
"react-number-format": "^4.4.1",
"react-redux": "^7.2.0",
"react-redux": "^7.2.1",
"react-resizable": "^1.10.1",
"react-router-dom": "^5.2.0",
"react-scripts": "3.4.1",
"react-trello": "^2.2.7",
"react-virtualized": "^9.21.2",
"react-scripts": "3.4.3",
"react-trello": "^2.2.8",
"react-virtualized": "^9.22.2",
"recharts": "^1.8.5",
"redux": "^4.0.5",
"redux-persist": "^6.0.0",
"redux-saga": "^1.1.3",
"redux-state-sync": "^3.1.1",
"redux-state-sync": "^3.1.2",
"reselect": "^4.0.0",
"styled-components": "^5.1.1",
"subscriptions-transport-ws": "^0.9.17"
"subscriptions-transport-ws": "^0.9.18"
},
"scripts": {
"analyze": "source-map-explorer 'build/static/js/*.js'",
"start": "react-scripts start",
"build": "react-scripts build",
"build": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
@@ -81,10 +84,10 @@
]
},
"devDependencies": {
"@apollo/react-testing": "^3.1.4",
"@apollo/react-testing": "^4.0.0",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.2",
"enzyme-adapter-react-16": "^1.15.3",
"redux-logger": "^3.0.6",
"source-map-explorer": "^2.4.2"
"source-map-explorer": "^2.5.0"
}
}

95
client/public/editor.js Normal file
View File

@@ -0,0 +1,95 @@
// unlayer.registerPropertyEditor({
// name: "field_name",
// layout: "bottom",
// Widget: unlayer.createWidget({
// render(value) {
// return `
// <input class="field" value=${value} />
// `;
// },
// mount(node, value, updateValue) {
// var input = node.getElementsByClassName("field")[0];
// input.onchange = function (event) {
// updateValue(event.target.value);
// };
// },
// }),
// });
// unlayer.registerTool({
// type: "whatever",
// category: "contents",
// label: "Begin Repeat",
// icon: "fa-smile",
// values: {},
// options: {
// default: {
// title: null,
// },
// text: {
// title: "Field",
// position: 1,
// options: {
// field: {
// label: "Field",
// defaultValue: "",
// widget: "field_name",
// },
// },
// },
// },
// renderer: {
// Viewer: unlayer.createViewer({
// render(values) {
// console.log(values);
// return `
// <div style="display: none;">{{#each ${values.field}}}</div>
// `;
// },
// }),
// exporters: {
// web: function () {},
// email: function () {},
// },
// },
// });
// unlayer.registerTool({
// type: "whatever",
// category: "contents",
// label: "End Repeat",
// icon: "fa-smile",
// values: {},
// options: {
// default: {
// title: null,
// },
// text: {
// title: "Field",
// position: 1,
// options: {
// field: {
// label: "Field",
// defaultValue: "",
// widget: "field_name",
// },
// },
// },
// },
// renderer: {
// Viewer: unlayer.createViewer({
// render(values) {
// return `
// <div style="display: none;">{{ /each }}</div>
// `;
// },
// }),
// exporters: {
// web: function () {},
// email: function () {},
// },
// },
// });
unlayer.registerColumns([2, 2, 2, 2, 2, 2]);
unlayer.registerColumns([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);

View File

@@ -0,0 +1,85 @@
/* body {
font-family: "Open Sans", sans-serif;
line-height: 1.25;
} */
table {
border: 1px solid #ccc;
border-collapse: collapse;
margin: 0;
padding: 0;
width: 100%;
table-layout: fixed;
}
table caption {
font-size: 1.5em;
margin: 0.5em 0 0.75em;
}
table tr {
background-color: #f8f8f8;
border: 1px solid #ddd;
padding: 0.35em;
}
table th,
table td {
padding: 0.625em;
text-align: center;
}
table th {
font-size: 0.85em;
letter-spacing: 0.1em;
text-transform: uppercase;
}
@media screen and (max-width: 600px) {
table {
border: 0;
}
table caption {
font-size: 1.3em;
}
table thead {
border: none;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
table tr {
border-bottom: 3px solid #ddd;
display: block;
margin-bottom: 0.625em;
}
table td {
border-bottom: 1px solid #ddd;
display: block;
font-size: 0.8em;
text-align: right;
}
table td::before {
/*
* aria-label has no advantage, it won't be read inside a table
content: attr(aria-label);
*/
content: attr(data-label);
float: left;
font-weight: bold;
text-transform: uppercase;
}
table td:last-child {
border-bottom: 0;
}
}

View File

@@ -1,4 +1,6 @@
import { ApolloProvider } from "@apollo/react-common";
import { ConfigProvider } from "antd";
import enLocale from "antd/es/locale/en_US";
import { ApolloLink } from "apollo-boost";
import { InMemoryCache } from "apollo-cache-inmemory";
import ApolloClient from "apollo-client";
@@ -9,18 +11,32 @@ import apolloLogger from "apollo-link-logger";
import { RetryLink } from "apollo-link-retry";
import { WebSocketLink } from "apollo-link-ws";
import { getMainDefinition } from "apollo-utilities";
import axios from "axios";
import LogRocket from "logrocket";
import moment from "moment";
import React from "react";
import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
import { auth } from "../firebase/firebase.utils";
import errorLink from "../graphql/apollo-error-handling";
import App from "./App";
import { ConfigProvider } from "antd";
import enLocale from "antd/es/locale/en_US";
import moment from "moment";
moment.locale("en-US");
axios.interceptors.request.use(
async (config) => {
if (!config.headers.Authorization) {
const token =
auth.currentUser && (await auth.currentUser.getIdToken(true));
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
}
return config;
},
(error) => Promise.reject(error)
);
if (process.env.NODE_ENV === "production") LogRocket.init("gvfvfw/bodyshopapp");
const httpLink = new HttpLink({

View File

@@ -1,4 +1,3 @@
import { Grid } from "antd";
import "antd/dist/antd.css";
import React, { lazy, Suspense, useEffect } from "react";
import { useTranslation } from "react-i18next";
@@ -20,9 +19,7 @@ const ResetPassword = lazy(() =>
);
const ManagePage = lazy(() => import("../pages/manage/manage.page.container"));
const SignInPage = lazy(() => import("../pages/sign-in/sign-in.page"));
const Unauthorized = lazy(() =>
import("../pages/unauthorized/unauthorized.component")
);
const CsiPage = lazy(() => import("../pages/csi/csi.container.page"));
const MobilePaymentContainer = lazy(() =>
import("../pages/mobile-payment/mobile-payment.container")
@@ -40,8 +37,8 @@ export function App({ checkUserSession, currentUser }) {
checkUserSession();
}, [checkUserSession]);
const b = Grid.useBreakpoint();
console.log("Breakpoints:", b);
//const b = Grid.useBreakpoint();
// console.log("Breakpoints:", b);
const { t } = useTranslation();
@@ -52,30 +49,41 @@ export function App({ checkUserSession, currentUser }) {
return (
<div>
<Switch>
<ErrorBoundary>
<Suspense fallback={<LoadingSpinner message="App.Js Suspense" />}>
<Suspense fallback={<LoadingSpinner message="App.Js Suspense" />}>
<ErrorBoundary>
<Route exact path="/" component={LandingPage} />
<Route exact path="/unauthorized" component={Unauthorized} />
</ErrorBoundary>
<ErrorBoundary>
<Route exact path="/signin" component={SignInPage} />
</ErrorBoundary>
<ErrorBoundary>
<Route exact path="/resetpassword" component={ResetPassword} />
</ErrorBoundary>
<ErrorBoundary>
<Route exact path="/csi/:surveyId" component={CsiPage} />
</ErrorBoundary>
<ErrorBoundary>
<Route
exact
path="/mp/:paymentIs"
component={MobilePaymentContainer}
/>
</ErrorBoundary>
<ErrorBoundary>
<PrivateRoute
isAuthorized={currentUser.authorized}
path="/manage"
component={ManagePage}
/>
</ErrorBoundary>
<ErrorBoundary>
<PrivateRoute
isAuthorized={currentUser.authorized}
path="/tech"
component={TechPageContainer}
/>
</Suspense>
</ErrorBoundary>
</ErrorBoundary>
</Suspense>
</Switch>
</div>
);

View File

@@ -0,0 +1,42 @@
import { AlertOutlined } from "@ant-design/icons";
import { Button, notification } from "antd";
import i18n from "i18next";
import React from "react";
import * as serviceWorker from "../serviceWorker";
const onServiceWorkerUpdate = (registration) => {
console.log("[RSW] onServiceWorkerUpdate", registration);
const key = `open${Date.now()}`;
const btn = (
<Button
type="primary"
onClick={async () => {
if (registration && registration.waiting) {
await registration.unregister();
// Makes Workbox call skipWaiting()
registration.waiting.postMessage({ type: "SKIP_WAITING" });
// Once the service worker is unregistered, we can reload the page to let
// the browser download a fresh copy of our app (invalidating the cache)
window.location.reload();
}
}}
>
{i18n.t("general.actions.refresh")}
</Button>
);
notification.open({
icon: <AlertOutlined />,
message: i18n.t("general.messages.newversiontitle"),
description: i18n.t("general.messages.newversionmessage"),
duration: 0,
btn,
key,
});
};
// if (process.env.NODE_ENV === "production") {
// console.log("SWR Registering SW...");
console.log("Registering Service Worker...");
serviceWorker.register({ onUpdate: onServiceWorkerUpdate });
// }

View File

@@ -0,0 +1,96 @@
import {
PaymentRequestButtonElement,
useStripe
} from "@stripe/react-stripe-js";
import React, { useEffect, useState } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { setEmailOptions } from "../../redux/email/email.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { TemplateList } from "../../utils/TemplateConstants";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setEmailOptions: (e) => dispatch(setEmailOptions(e)),
});
function Test({ bodyshop, setEmailOptions }) {
const stripe = useStripe();
const [paymentRequest, setPaymentRequest] = useState(null);
useEffect(() => {
if (stripe) {
const pr = stripe.paymentRequest({
country: "CA",
displayItems: [{ label: "Deductible", amount: 1099 }],
currency: "cad",
total: {
label: "Demo total",
amount: 1099,
},
requestPayerName: true,
requestPayerEmail: true,
});
// Check the availability of the Payment Request API.
pr.canMakePayment().then((result) => {
if (result) {
setPaymentRequest(pr);
} else {
// var details = {
// total: { label: "", amount: { currency: "CAD", value: "0.00" } },
// };
new PaymentRequest(
[{ supportedMethods: ["basic-card"] }],
{}
// details
).show();
}
});
}
}, [stripe]);
if (paymentRequest) {
return (
<div style={{ height: "300px" }}>
<PaymentRequestButtonElement options={{ paymentRequest }} />
</div>
);
}
return (
<div>
<button
onClick={() => {
setEmailOptions({
messageOptions: {
to: ["patrickwf@gmail.com"],
replyTo: bodyshop.email,
},
template: {
name: TemplateList.parts_order_confirmation.key,
variables: {
id: "a7c2d4e1-f519-42a9-a071-c48cf0f22979",
},
},
});
}}
>
send email
</button>
<button
onClick={() => {
logImEXEvent("IMEXEVENT", { somethignArThare: 5 });
}}
>
Log an ImEX Event.
</button>
</div>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(Test);

View File

@@ -1,96 +1,25 @@
import {
PaymentRequestButtonElement,
useStripe
} from "@stripe/react-stripe-js";
import React, { useEffect, useState } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { setEmailOptions } from "../../redux/email/email.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { TemplateList } from "../../utils/TemplateConstants";
import Axios from "axios";
import React from "react";
export default function Test() {
const handleQbSignIn = async () => {
const result = await Axios.post("/qbo/authorize", { userId: "1234" });
console.log("handleQbSignIn -> result", result.data);
// window.open(result.data, "_blank", "toolbar=0,location=0,menubar=0");
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
var parameters = "location=1,width=800,height=650";
parameters +=
",left=" +
(window.screen.width - 800) / 2 +
",top=" +
(window.screen.height - 650) / 2;
const mapDispatchToProps = (dispatch) => ({
setEmailOptions: (e) => dispatch(setEmailOptions(e)),
});
function Test({ bodyshop, setEmailOptions }) {
const stripe = useStripe();
const [paymentRequest, setPaymentRequest] = useState(null);
useEffect(() => {
if (stripe) {
const pr = stripe.paymentRequest({
country: "CA",
displayItems: [{ label: "Deductible", amount: 1099 }],
currency: "cad",
total: {
label: "Demo total",
amount: 1099,
},
requestPayerName: true,
requestPayerEmail: true,
});
// Check the availability of the Payment Request API.
pr.canMakePayment().then((result) => {
if (result) {
setPaymentRequest(pr);
} else {
// var details = {
// total: { label: "", amount: { currency: "CAD", value: "0.00" } },
// };
new PaymentRequest(
[{ supportedMethods: ["basic-card"] }],
{}
// details
).show();
}
});
}
}, [stripe]);
if (paymentRequest) {
return (
<div style={{ height: "300px" }}>
<PaymentRequestButtonElement options={{ paymentRequest }} />
</div>
);
}
// Launch Popup
window.open(result.data, "connectPopup", parameters);
};
return (
<div>
<button
onClick={() => {
setEmailOptions({
messageOptions: {
to: ["patrickwf@gmail.com"],
replyTo: bodyshop.email,
},
template: {
name: TemplateList.parts_order_confirmation.key,
variables: {
id: "a7c2d4e1-f519-42a9-a071-c48cf0f22979",
},
},
});
}}
>
send email
</button>
<button
onClick={() => {
logImEXEvent("IMEXEVENT", { somethignArThare: 5 });
}}
>
Log an ImEX Event.
</button>
<button onClick={handleQbSignIn}>Sign Into Qb.</button>
</div>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(Test);

View File

@@ -7,7 +7,7 @@ import { DateTimeFormatter } from "../../utils/DateFormatter";
import { alphaSort } from "../../utils/sorters";
import PaymentExportButton from "../payment-export-button/payment-export-button.component";
import { PaymentsExportAllButton } from "../payments-export-all-button/payments-export-all-button.component";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { logImEXEvent } from "../../firebase/firebase.utils";
export default function AccountingPayablesTableComponent({
loading,
@@ -61,11 +61,13 @@ export default function AccountingPayablesTableComponent({
render: (text, record) => {
return record.job.owner ? (
<Link to={"/manage/owners/" + record.job.owner.id}>
{`${record.job.ownr_fn || ""} ${record.job.ownr_ln || ""}`}
{`${record.job.ownr_fn || ""} ${record.job.ownr_ln || ""} ${
record.job.ownr_co_nm
}`}
</Link>
) : (
<span>{`${record.job.ownr_fn || ""} ${
record.job.ownr_ln || ""
<span>{`${record.job.ownr_fn || ""} ${record.job.ownr_ln || ""} ${
record.job.ownr_co_nm
}`}</span>
);
},
@@ -168,10 +170,10 @@ export default function AccountingPayablesTableComponent({
);
}}
dataSource={dataSource}
size='small'
size="small"
pagination={{ position: "top", pageSize: 50 }}
columns={columns}
rowKey='id'
rowKey="id"
onChange={handleTableChange}
rowSelection={{
onSelectAll: (selected, selectedRows) =>

View File

@@ -63,10 +63,14 @@ export default function AccountingReceivablesTableComponent({ loading, jobs }) {
render: (text, record) => {
return record.owner ? (
<Link to={"/manage/owners/" + record.owner.id}>
{`${record.ownr_fn || ""} ${record.ownr_ln || ""}`}
{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${
record.ownr_co_nm || ""
}`}
</Link>
) : (
<span>{`${record.ownr_fn || ""} ${record.ownr_ln || ""}`}</span>
<span>{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${
record.ownr_co_nm || ""
}`}</span>
);
},
},

View File

@@ -15,7 +15,7 @@ describe("AllocationsAssignmentComponent component", () => {
assignment: {},
setAssignment: jest.fn(),
visibilityState: [false, jest.fn()],
maxHours: 4
maxHours: 4,
};
wrapper = mount(<AllocationsAssignmentComponent {...mockProps} />);
@@ -27,7 +27,6 @@ describe("AllocationsAssignmentComponent component", () => {
it("should render a list of employees", () => {
const empList = wrapper.find("#employeeSelector");
console.log(empList.debug());
expect(empList.children()).to.have.lengthOf(2);
});

View File

@@ -6,7 +6,7 @@ import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
bodyshop: selectBodyshop,
});
export default connect(
@@ -18,12 +18,11 @@ export default connect(
handleAssignment,
assignment,
setAssignment,
visibilityState
visibilityState,
}) {
const { t } = useTranslation();
const onChange = e => {
console.log("e", e);
const onChange = (e) => {
setAssignment({ ...assignment, employeeid: e });
};
@@ -34,13 +33,14 @@ export default connect(
<Select
showSearch
style={{ width: 200 }}
placeholder='Select a person'
optionFilterProp='children'
placeholder="Select a person"
optionFilterProp="children"
onChange={onChange}
filterOption={(input, option) =>
option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}>
{bodyshop.employees.map(emp => (
}
>
{bodyshop.employees.map((emp) => (
<Select.Option value={emp.id} key={emp.id}>
{`${emp.first_name} ${emp.last_name}`}
</Select.Option>
@@ -48,9 +48,10 @@ export default connect(
</Select>
<Button
type='primary'
type="primary"
disabled={!assignment.employeeid}
onClick={handleAssignment}>
onClick={handleAssignment}
>
Assign
</Button>
<Button onClick={() => setVisibility(false)}>Close</Button>

View File

@@ -1,6 +1,6 @@
import Icon from "@ant-design/icons";
import React, { useEffect, useRef } from "react";
import { FaCheck, FaCheckDouble } from "react-icons/fa";
import { MdDone, MdDoneAll } from "react-icons/md";
import {
AutoSizer,
CellMeasurer,
@@ -38,8 +38,9 @@ export default function ChatMessageListComponent({ messages }) {
style={style}
className={`${
messages[index].isoutbound ? "mine messages" : "yours messages"
}`}>
<div className='message msgmargin'>
}`}
>
<div className="message msgmargin">
{MessageRender(messages[index])}
{StatusRender(messages[index].status)}
</div>
@@ -50,7 +51,7 @@ export default function ChatMessageListComponent({ messages }) {
};
return (
<div className='chat'>
<div className="chat">
<AutoSizer>
{({ height, width }) => (
<List
@@ -73,12 +74,8 @@ export default function ChatMessageListComponent({ messages }) {
const MessageRender = (message) => {
if (message.image) {
return (
<a href={message.image_path} target='__blank'>
<img
alt='Received'
className='message-img'
src={message.image_path}
/>
<a href={message.image_path} target="__blank">
<img alt="Received" className="message-img" src={message.image_path} />
</a>
);
} else {
@@ -89,9 +86,9 @@ const MessageRender = (message) => {
const StatusRender = (status) => {
switch (status) {
case "sent":
return <Icon component={FaCheck} className='message-icon' />;
return <Icon component={MdDone} className="message-icon" />;
case "delivered":
return <Icon component={FaCheckDouble} className='message-icon' />;
return <Icon component={MdDoneAll} className="message-icon" />;
default:
return null;
}

View File

@@ -31,12 +31,13 @@ export default function ChatTagRoComponent({
onSearch={handleSearchQuery}
onSelect={handleInsertTag}
placeholder={t("general.labels.search")}
onKeyDown={handleKeyDown}>
onKeyDown={handleKeyDown}
>
{roOptions.map((item, idx) => (
<AutoComplete.Option key={item.id || idx}>
{` ${item.ro_number || ""} | ${item.ownr_fn || ""} ${
item.ownr_ln || ""
}`}
} ${item.ownr_co_nm || ""}`}
</AutoComplete.Option>
))}
</AutoComplete>

View File

@@ -7,12 +7,12 @@ export default function ContractsJobsComponent({
loading,
data,
selectedJob,
handleSelect
handleSelect,
}) {
const [state, setState] = useState({
sortedInfo: {},
filteredInfo: { text: "" },
search: ""
search: "",
});
const { t } = useTranslation();
@@ -35,7 +35,7 @@ export default function ContractsJobsComponent({
<span>
{record.ro_number ? record.ro_number : "EST-" + record.est_number}
</span>
)
),
},
{
title: t("jobs.fields.owner"),
@@ -49,12 +49,14 @@ export default function ContractsJobsComponent({
render: (text, record) => {
return record.owner ? (
<span>
{record.ownr_fn} {record.ownr_ln}
{record.ownr_fn} {record.ownr_ln} {record.ownr_co_nm || ""}
</span>
) : (
<span>{`${record.ownr_fn} ${record.ownr_ln}`}</span>
<span>{`${record.ownr_fn} ${record.ownr_ln} ${
record.ownr_co_nm || ""
}`}</span>
);
}
},
},
{
title: t("jobs.fields.status"),
@@ -67,7 +69,7 @@ export default function ContractsJobsComponent({
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
render: (text, record) => {
return record.status || t("general.labels.na");
}
},
},
{
@@ -79,13 +81,14 @@ export default function ContractsJobsComponent({
render: (text, record) => {
return record.vehicleid ? (
<span>
{`${record.v_model_yr || ""} ${record.v_make_desc ||
""} ${record.v_model_desc || ""}`}
{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
record.v_model_desc || ""
}`}
</span>
) : (
t("jobs.errors.novehicle")
);
}
},
},
{
title: t("vehicles.fields.plate_no"),
@@ -102,7 +105,7 @@ export default function ContractsJobsComponent({
) : (
t("general.labels.unknown")
);
}
},
},
{
title: t("jobs.fields.clm_no"),
@@ -119,8 +122,8 @@ export default function ContractsJobsComponent({
) : (
t("general.labels.unknown")
);
}
}
},
},
];
const handleTableChange = (pagination, filters, sorter) => {
@@ -131,7 +134,7 @@ export default function ContractsJobsComponent({
state.search === ""
? data
: data.filter(
j =>
(j) =>
(j.est_number || "")
.toString()
.toLowerCase()
@@ -140,6 +143,9 @@ export default function ContractsJobsComponent({
.toString()
.toLowerCase()
.includes(state.search.toLowerCase()) ||
(j.ownr_co_nm || "")
.toLowerCase()
.includes(state.search.toLowerCase()) ||
(j.ownr_fn || "")
.toLowerCase()
.includes(state.search.toLowerCase()) ||
@@ -160,7 +166,6 @@ export default function ContractsJobsComponent({
.includes(state.search.toLowerCase())
);
return (
<Table
loading={loading}
@@ -168,19 +173,19 @@ export default function ContractsJobsComponent({
<Input.Search
placeholder={t("general.labels.search")}
value={state.search}
onChange={e => setState({ ...state, search: e.target.value })}
onChange={(e) => setState({ ...state, search: e.target.value })}
/>
)}
size="small"
pagination={{ position: "top" }}
columns={columns.map(item => ({ ...item }))}
columns={columns.map((item) => ({ ...item }))}
rowKey="id"
dataSource={filteredData}
onChange={handleTableChange}
rowSelection={{
onSelect: handleSelect,
type: "radio",
selectedRowKeys: [selectedJob]
selectedRowKeys: [selectedJob],
}}
/>
);

View File

@@ -12,13 +12,11 @@ export default function ContractLicenseDecodeButton({ form }) {
const [modalVisible, setModalVisible] = useState(false);
const [loading, setLoading] = useState(false);
const [decodedBarcode, setDecodedBarcode] = useState(null);
console.log("form", form);
const handleDecode = (e) => {
logImEXEvent("contract_license_decode");
setLoading(true);
const aamvaParse = aamva.parse(e.currentTarget.value);
console.log("AAMVA", aamvaParse);
setDecodedBarcode(aamvaParse);
setLoading(false);
};
@@ -61,7 +59,8 @@ export default function ContractLicenseDecodeButton({ form }) {
okText={t("contracts.actions.senddltoform")}
onOk={handleInsertForm}
okButtonProps={{ disabled: !!!decodedBarcode }}
onCancel={handleCancel}>
onCancel={handleCancel}
>
<div>
<div>
<Input

View File

@@ -3,10 +3,7 @@ import { Select } from "antd";
import { useTranslation } from "react-i18next";
const { Option } = Select;
const ContractStatusComponent = (
{ value = "contracts.status.new", onChange },
ref
) => {
const ContractStatusComponent = ({ value, onChange }, ref) => {
const [option, setOption] = useState(value);
const { t } = useTranslation();

View File

@@ -3,15 +3,15 @@ import React from "react";
import { useTranslation } from "react-i18next";
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component";
import CourtesyCarStatus from "../courtesy-car-status-select/courtesy-car-status-select.component";
import FormDatePicker from '../form-date-picker/form-date-picker.component';
import FormDatePicker from "../form-date-picker/form-date-picker.component";
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
export default function CourtesyCarCreateFormComponent({ form }) {
export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
const { t } = useTranslation();
return (
<div>
<Button type="primary" htmlType="submit">
<Button type="primary" loading={saveLoading} htmlType="submit">
{t("general.actions.save")}
</Button>
<div className="imex-flex-row__grow imex-flex-row__margin-large">

View File

@@ -60,11 +60,13 @@ export default function CsiResponseListPaginated({
render: (text, record) => {
return record.owner ? (
<Link to={"/manage/owners/" + record.owner.id}>
{`${record.job.ownr_fn || ""} ${record.job.ownr_ln || ""}`}
{`${record.job.ownr_fn || ""} ${record.job.ownr_ln || ""} ${
record.job.ownr_co_nm
}`}
</Link>
) : (
<span>{`${record.job.ownr_fn || ""} ${
record.job.ownr_ln || ""
<span>{`${record.job.ownr_fn || ""} ${record.job.ownr_ln || ""} ${
record.job.ownr_co_nm
}`}</span>
);
},

View File

@@ -38,7 +38,6 @@ const mapDispatchToProps = (dispatch) => ({
export function DashboardGridComponent({ currentUser, bodyshop }) {
const { loading, error, data } = useQuery(QUERY_DASHBOARD_DETAILS);
console.log("DashboardGridComponent -> data", data)
const { t } = useTranslation();
const [state, setState] = useState({
layout: bodyshop.associations[0].user.dashboardlayout || [
@@ -70,7 +69,6 @@ export function DashboardGridComponent({ currentUser, bodyshop }) {
const idxToRemove = state.layout.findIndex((i) => i.i === key);
const newLayout = state.layout;
newLayout.splice(idxToRemove, 1);
console.log(newLayout);
handleLayoutChange(newLayout);
};
@@ -100,14 +98,15 @@ export function DashboardGridComponent({ currentUser, bodyshop }) {
<Menu.Item
key={key}
value={key}
disabled={existingLayoutKeys.includes(key)}>
disabled={existingLayoutKeys.includes(key)}
>
{componentList[key].label}
</Menu.Item>
))}
</Menu>
);
if (error) return <AlertComponent message={error.message} type='error' />;
if (error) return <AlertComponent message={error.message} type="error" />;
return (
<div>
@@ -115,12 +114,13 @@ export function DashboardGridComponent({ currentUser, bodyshop }) {
<Button>{t("dashboard.actions.addcomponent")}</Button>
</Dropdown>
<ResponsiveReactGridLayout
className='layout'
className="layout"
breakpoints={{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }}
cols={{ lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 }}
width='100%'
width="100%"
onLayoutChange={handleLayoutChange}
onBreakpointChange={onBreakpointChange}>
onBreakpointChange={onBreakpointChange}
>
{state.layout.map((item, index) => {
const TheComponent = componentList[item.i].component;
return (
@@ -139,8 +139,8 @@ export function DashboardGridComponent({ currentUser, bodyshop }) {
onClick={() => handleRemoveComponent(item.i)}
/>
<TheComponent
className='dashboard-card'
size='small'
className="dashboard-card"
size="small"
style={{ height: "100%", width: "100%" }}
/>
</LoadingSkeleton>

View File

@@ -62,7 +62,6 @@ export function EmailOverlayContainer({
};
attachments.push(t);
});
console.log("messageOptions", messageOptions);
setSending(true);
try {

View File

@@ -5,7 +5,7 @@ const { Option } = Select;
//To be used as a form element only.
const EmployeeSearchSelect = (
{ value, onChange, options, onSelect, onBlur },
{ value, onChange, options, onSelect, onBlur, ...restProps },
ref
) => {
const [option, setOption] = useState(value);
@@ -27,6 +27,7 @@ const EmployeeSearchSelect = (
optionFilterProp="search"
onSelect={onSelect}
onBlur={onBlur}
{...restProps}
>
{options
? options.map((o) => (

View File

@@ -21,7 +21,6 @@ export default function FormsFieldChanged({ form }) {
<Prompt
when={true}
message={(location) => {
//console.log("location", location);
if (loc.pathname === location.pathname) return false;
return t("general.messages.unsavedchangespopup");
}}
@@ -45,7 +44,7 @@ export default function FormsFieldChanged({ form }) {
/>
</div>
);
return null;
return <div style={{ display: "none" }}></div>;
}}
</Form.Item>
);

View File

@@ -106,7 +106,7 @@ export default function GlobalSearch() {
vehicle.v_model_yr || ""
} ${vehicle.v_make_desc || ""} ${
vehicle.v_model_desc || ""
}`}</span>
} - ${vehicle.plate_no} - ${vehicle.v_vin}`}</span>
</div>
</Link>
),

View File

@@ -1,30 +1,40 @@
import Icon, {
ClockCircleFilled,
CarFilled,
ClockCircleFilled,
DollarCircleFilled,
FileAddFilled,
FileFilled,
GlobalOutlined,
HomeFilled,
ImportOutlined,
LineChartOutlined,
ScheduleOutlined,
TeamOutlined,
UnorderedListOutlined,
UserOutlined,
} from "@ant-design/icons";
import { Avatar, Col, Layout, Menu, Row } from "antd";
import { Avatar, Layout, Menu } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { FaCalendarAlt, FaCarCrash, FaCreditCard } from "react-icons/fa";
import { BsKanban } from "react-icons/bs";
import {
FaCalendarAlt,
FaCarCrash,
FaCreditCard,
FaFileInvoiceDollar,
} from "react-icons/fa";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { selectRecentItems } from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions";
import { signOutStart } from "../../redux/user/user.actions";
import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import "./header.styles.scss";
import GlobalSearch from "../global-search/global-search.component";
import { selectRecentItems } from "../../redux/application/application.selectors";
import "./header.styles.scss";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
@@ -42,30 +52,6 @@ const mapDispatchToProps = (dispatch) => ({
signOutStart: () => dispatch(signOutStart()),
});
const logoSpan = {
xs: {
span: 0,
},
sm: { span: 0 },
md: {
span: 0,
},
lg: {
span: 2,
},
};
const menuSpan = {
md: {
span: 24,
//offset: 1,
},
lg: {
span: 21,
offset: 1,
},
};
function Header({
bodyshop,
handleMenuClick,
@@ -81,278 +67,276 @@ function Header({
return (
<Header>
<Row>
<Col {...logoSpan}>
<img
className="header-shop-logo"
alt={bodyshop ? bodyshop.shopname : "ImEX Online Logo"}
src={
bodyshop && bodyshop.logo_img_path
? bodyshop.logo_img_path
: "./logo192.png"
}
/>
</Col>
<Col {...menuSpan}>
<Menu
mode="horizontal"
theme="dark"
className="header-main-menu"
selectedKeys={["home"]}
onClick={handleMenuClick}
<Menu
mode="horizontal"
theme="dark"
className="header-main-menu"
selectedKeys={["home"]}
onClick={handleMenuClick}
>
<Menu.Item key="home">
<Link to="/manage">
<HomeFilled />
{t("menus.header.home")}
</Link>
</Menu.Item>
<Menu.Item key="schedule">
<Link to="/manage/schedule">
<Icon component={FaCalendarAlt} />
{t("menus.header.schedule")}
</Link>
</Menu.Item>
<Menu.SubMenu
title={
<span>
<Icon component={FaCarCrash} />
<span>{t("menus.header.jobs")}</span>
</span>
}
>
<Menu.Item key="activejobs">
<FileFilled />
<Link to="/manage/jobs">{t("menus.header.activejobs")}</Link>
</Menu.Item>
<Menu.Item key="availablejobs">
<Link to="/manage/available">
<ImportOutlined /> {t("menus.header.availablejobs")}
</Link>
</Menu.Item>
<Menu.Divider />
<Menu.Item key="alljobs">
<UnorderedListOutlined />
<Link to="/manage/jobs/all">{t("menus.header.alljobs")}</Link>
</Menu.Item>
<Menu.Divider />
<Menu.Item key="productionlist">
<Link to="/manage/production/list">
<ScheduleOutlined />
{t("menus.header.productionlist")}
</Link>
</Menu.Item>
<Menu.Item key="productionboard">
<Link to="/manage/production/board">
<Icon component={BsKanban} />
{t("menus.header.productionboard")}
</Link>
</Menu.Item>
<Menu.Divider />
<Menu.Item key="scoreboard">
<LineChartOutlined />
<Link to="/manage/scoreboard">{t("menus.header.scoreboard")}</Link>
</Menu.Item>
</Menu.SubMenu>
<Menu.SubMenu
title={
<span>
<UserOutlined />
<span>{t("menus.header.customers")}</span>
</span>
}
>
<Menu.Item key="owners">
<Link to="/manage/owners">
<TeamOutlined />
{t("menus.header.owners")}
</Link>
</Menu.Item>
<Menu.Item key="vehicles">
<Link to="/manage/vehicles">
<CarFilled />
{t("menus.header.vehicles")}
</Link>
</Menu.Item>
</Menu.SubMenu>
<Menu.SubMenu
title={
<span>
<CarFilled />
<span>{t("menus.header.courtesycars")}</span>
</span>
}
>
<Menu.Item key="courtesycarsall">
<Link to="/manage/courtesycars">
<CarFilled />
{t("menus.header.courtesycars-all")}
</Link>
</Menu.Item>
<Menu.Item key="contracts">
<Link to="/manage/courtesycars/contracts">
<FileFilled />
{t("menus.header.courtesycars-contracts")}
</Link>
</Menu.Item>
<Menu.Item key="newcontract">
<Link to="/manage/courtesycars/contracts/new">
<FileAddFilled />
{t("menus.header.courtesycars-newcontract")}
</Link>
</Menu.Item>
</Menu.SubMenu>
<Menu.SubMenu
title={
<span>
<DollarCircleFilled />
<span>{t("menus.header.accounting")}</span>
</span>
}
>
<Menu.Item key="invoices">
<Link to="/manage/invoices">{t("menus.header.invoices")}</Link>
</Menu.Item>
<Menu.Item
key="enterinvoices"
onClick={() => {
setInvoiceEnterContext({
actions: {},
context: {},
});
}}
>
<Menu.Item key="home">
<Link to="/manage">
<HomeFilled />
{t("menus.header.home")}
<Icon component={FaFileInvoiceDollar} />
{t("menus.header.enterinvoices")}
</Menu.Item>
<Menu.Divider />
<Menu.Item key="allpayments">
<Link to="/manage/payments">{t("menus.header.allpayments")}</Link>
</Menu.Item>
<Menu.Item
key="enterpayments"
onClick={() => {
setPaymentContext({
actions: {},
context: {},
});
}}
>
<Icon component={FaCreditCard} />
{t("menus.header.enterpayment")}
</Menu.Item>
<Menu.Divider />
<Menu.Item key="timetickets">
<Link to="/manage/timetickets">
{t("menus.header.timetickets")}
</Link>
</Menu.Item>
<Menu.Item
key="entertimetickets"
onClick={() => {
setTimeTicketContext({
actions: {},
context: {},
});
}}
>
{t("menus.header.entertimeticket")}
</Menu.Item>
<Menu.Divider />
<Menu.SubMenu title={t("menus.header.export")}>
<Menu.Item key="receivables">
<Link to="/manage/accounting/receivables">
{t("menus.header.accounting-receivables")}
</Link>
</Menu.Item>
<Menu.SubMenu
title={
<span>
<Icon component={FaCarCrash} />
<span>{t("menus.header.jobs")}</span>
</span>
}
>
<Menu.Item key="schedule">
<Link to="/manage/schedule">
<Icon component={FaCalendarAlt} />
{t("menus.header.schedule")}
</Link>
</Menu.Item>
<Menu.Item key="productionlist">
<Link to="/manage/production/list">
<Icon component={FaCalendarAlt} />
{t("menus.header.productionlist")}
</Link>
</Menu.Item>
<Menu.Item key="productionboard">
<Link to="/manage/production/board">
{t("menus.header.productionboard")}
</Link>
</Menu.Item>
<Menu.Item key="scoreboard">
<Link to="/manage/scoreboard">
{t("menus.header.scoreboard")}
</Link>
</Menu.Item>
<Menu.Item key="activejobs">
<Link to="/manage/jobs">{t("menus.header.activejobs")}</Link>
</Menu.Item>
<Menu.Item key="alljobs">
<Link to="/manage/jobs/all">{t("menus.header.alljobs")}</Link>
</Menu.Item>
<Menu.Item key="availablejobs">
<Link to="/manage/available">
{t("menus.header.availablejobs")}
</Link>
</Menu.Item>
</Menu.SubMenu>
<Menu.SubMenu title={t("menus.header.customers")}>
<Menu.Item key="owners">
<Link to="/manage/owners">
<TeamOutlined />
{t("menus.header.owners")}
</Link>
</Menu.Item>
<Menu.Item key="vehicles">
<Link to="/manage/vehicles">
<CarFilled />
{t("menus.header.vehicles")}
</Link>
</Menu.Item>
</Menu.SubMenu>
<Menu.SubMenu
title={
<span>
<CarFilled />
<span>{t("menus.header.courtesycars")}</span>
</span>
}
>
<Menu.Item key="courtesycarsall">
<Link to="/manage/courtesycars">
<CarFilled />
{t("menus.header.courtesycars-all")}
</Link>
</Menu.Item>
<Menu.Item key="contracts">
<Link to="/manage/courtesycars/contracts">
<FileFilled />
{t("menus.header.courtesycars-contracts")}
</Link>
</Menu.Item>
<Menu.Item key="newcontract">
<Link to="/manage/courtesycars/contracts/new">
<FileAddFilled />
{t("menus.header.courtesycars-newcontract")}
</Link>
</Menu.Item>
</Menu.SubMenu>
<Menu.SubMenu
title={
<span>
<DollarCircleFilled />
<span>{t("menus.header.accounting")}</span>
</span>
}
>
<Menu.Item
key="enterpayments"
onClick={() => {
setPaymentContext({
actions: {},
context: {},
});
}}
>
<Icon component={FaCreditCard} />
{t("menus.header.enterpayment")}
</Menu.Item>
<Menu.Item
key="enterinvoices"
onClick={() => {
setInvoiceEnterContext({
actions: {},
context: {},
});
}}
>
{t("menus.header.enterinvoices")}
</Menu.Item>
<Menu.Item key="invoices">
<Link to="/manage/invoices">{t("menus.header.invoices")}</Link>
</Menu.Item>
<Menu.Item key="timetickets">
<Link to="/manage/timetickets">
{t("menus.header.timetickets")}
</Link>
</Menu.Item>
<Menu.Item
key="entertimetickets"
onClick={() => {
setTimeTicketContext({
actions: {},
context: {},
});
}}
>
{t("menus.header.entertimeticket")}
</Menu.Item>
<Menu.SubMenu title={t("menus.header.export")}>
<Menu.Item key="receivables">
<Link to="/manage/accounting/receivables">
{t("menus.header.accounting-receivables")}
</Link>
</Menu.Item>
<Menu.Item key="payables">
<Link to="/manage/accounting/payables">
{t("menus.header.accounting-payables")}
</Link>
</Menu.Item>
<Menu.Item key="payments">
<Link to="/manage/accounting/payments">
{t("menus.header.accounting-payments")}
</Link>
</Menu.Item>
</Menu.SubMenu>
<Menu.Item key="allpayments">
<Link to="/manage/payments">
{t("menus.header.allpayments")}
</Link>
</Menu.Item>
</Menu.SubMenu>
<Menu.SubMenu title={t("menus.header.shop")}>
<Menu.Item key="shop">
<Link to="/manage/shop">{t("menus.header.shop_config")}</Link>
</Menu.Item>
<Menu.Item key="shop-templates">
<Link to="/manage/shop/templates">
{t("menus.header.shop_templates")}
</Link>
</Menu.Item>
<Menu.Item key="shop-vendors">
<Link to="/manage/shop/vendors">
{t("menus.header.shop_vendors")}
</Link>
</Menu.Item>
<Menu.Item key="shop-csi">
<Link to="/manage/shop/csi">{t("menus.header.shop_csi")}</Link>
</Menu.Item>
</Menu.SubMenu>
<Menu.Item>
<GlobalSearch />
<Menu.Item key="payables">
<Link to="/manage/accounting/payables">
{t("menus.header.accounting-payables")}
</Link>
</Menu.Item>
<Menu.SubMenu title={<ClockCircleFilled />}>
{recentItems.map((i, idx) => (
<Menu.Item key={idx}>
<Link to={i.url}>{i.label}</Link>
</Menu.Item>
))}
</Menu.SubMenu>
<Menu.SubMenu
title={
<div>
{currentUser.photoURL ? (
<Avatar
src={currentUser.photoURL}
style={{
margin: "10px",
}}
/>
) : (
<Avatar
style={{
backgroundColor: "#87d068",
margin: "10px",
}}
icon={<UserOutlined />}
/>
)}
<Menu.Item key="payments">
<Link to="/manage/accounting/payments">
{t("menus.header.accounting-payments")}
</Link>
</Menu.Item>
</Menu.SubMenu>
</Menu.SubMenu>
<Menu.SubMenu title={t("menus.header.shop")}>
<Menu.Item key="shop">
<Link to="/manage/shop">{t("menus.header.shop_config")}</Link>
</Menu.Item>
{currentUser.displayName || t("general.labels.unknown")}
</div>
}
>
<Menu.Item danger onClick={() => signOutStart()}>
{t("user.actions.signout")}
</Menu.Item>
<Menu.Item key="shiftclock">
<Link to="/manage/shiftclock">
{t("menus.header.shiftclock")}
</Link>
</Menu.Item>
<Menu.Item>
<Link to="/manage/profile">
{t("menus.currentuser.profile")}
</Link>
</Menu.Item>
<Menu.SubMenu
title={
<span>
<GlobalOutlined />
<span>{t("menus.currentuser.languageselector")}</span>
</span>
}
>
<Menu.Item actiontype="lang-select" key="en-US">
{t("general.languages.english")}
</Menu.Item>
<Menu.Item actiontype="lang-select" key="fr-CA">
{t("general.languages.french")}
</Menu.Item>
<Menu.Item actiontype="lang-select" key="es-MX">
{t("general.languages.spanish")}
</Menu.Item>
</Menu.SubMenu>
</Menu.SubMenu>
</Menu>
</Col>
</Row>
<Menu.Item key="shop-templates">
<Link to="/manage/shop/templates">
{t("menus.header.shop_templates")}
</Link>
</Menu.Item>
<Menu.Item key="shop-vendors">
<Link to="/manage/shop/vendors">
{t("menus.header.shop_vendors")}
</Link>
</Menu.Item>
<Menu.Item key="shop-csi">
<Link to="/manage/shop/csi">{t("menus.header.shop_csi")}</Link>
</Menu.Item>
</Menu.SubMenu>
<Menu.Item>
<GlobalSearch />
</Menu.Item>
<Menu.SubMenu title={<ClockCircleFilled />}>
{recentItems.map((i, idx) => (
<Menu.Item key={idx}>
<Link to={i.url}>{i.label}</Link>
</Menu.Item>
))}
</Menu.SubMenu>
<Menu.SubMenu
title={
<div>
{currentUser.photoURL ? (
<Avatar
src={currentUser.photoURL}
style={{
margin: "10px",
}}
/>
) : (
<Avatar
style={{
backgroundColor: "#87d068",
margin: "10px",
}}
icon={<UserOutlined />}
/>
)}
{currentUser.displayName || t("general.labels.unknown")}
</div>
}
>
<Menu.Item danger onClick={() => signOutStart()}>
{t("user.actions.signout")}
</Menu.Item>
<Menu.Item key="shiftclock">
<Link to="/manage/shiftclock">{t("menus.header.shiftclock")}</Link>
</Menu.Item>
<Menu.Item>
<Link to="/manage/profile">{t("menus.currentuser.profile")}</Link>
</Menu.Item>
<Menu.SubMenu
title={
<span>
<GlobalOutlined />
<span>{t("menus.currentuser.languageselector")}</span>
</span>
}
>
<Menu.Item actiontype="lang-select" key="en-US">
{t("general.languages.english")}
</Menu.Item>
<Menu.Item actiontype="lang-select" key="fr-CA">
{t("general.languages.french")}
</Menu.Item>
<Menu.Item actiontype="lang-select" key="es-MX">
{t("general.languages.spanish")}
</Menu.Item>
</Menu.SubMenu>
</Menu.SubMenu>
</Menu>
</Header>
);
}

View File

@@ -49,7 +49,13 @@ export function InvoiceDetailEditContainer({ bodyshop }) {
delete il.__typename;
updates.push(
updateInvoiceLine({
variables: { invoicelineId: il.id, invoiceLine: il },
variables: {
invoicelineId: il.id,
invoiceLine: {
...il,
joblineid: il.joblineid === "noline" ? null : il.joblineid,
},
},
})
);
});

View File

@@ -38,8 +38,6 @@ function InvoiceEnterModalContainer({
const [loading, setLoading] = useState(false);
const handleFinish = (values) => {
console.log("handleFinish -> values", values);
setLoading(true);
const { upload, ...remainingValues } = values;
insertInvoice({
@@ -48,11 +46,6 @@ function InvoiceEnterModalContainer({
Object.assign({}, remainingValues, {
invoicelines: {
data: remainingValues.invoicelines.map((i) => {
console.log(
"Initial insert value",
i.joblineid,
i.joblineid === "noline"
);
return {
...i,
joblineid: i.joblineid === "noline" ? null : i.joblineid,
@@ -127,6 +120,10 @@ function InvoiceEnterModalContainer({
if (enterAgain) form.submit();
}, [enterAgain, form]);
useEffect(() => {
if (invoiceEnterModal.visible) form.resetFields();
}, [invoiceEnterModal.visible, form]);
return (
<Modal
title={t("invoices.labels.new")}
@@ -165,6 +162,7 @@ function InvoiceEnterModalContainer({
setEnterAgain(false);
}}
initialValues={{
...invoiceEnterModal.context.invoice,
jobid:
(invoiceEnterModal.context.job &&
invoiceEnterModal.context.job.id) ||
@@ -172,7 +170,6 @@ function InvoiceEnterModalContainer({
federal_tax_rate: bodyshop.invoice_tax_rates.federal_tax_rate || 0,
state_tax_rate: bodyshop.invoice_tax_rates.state_tax_rate || 0,
local_tax_rate: bodyshop.invoice_tax_rates.local_tax_rate || 0,
...invoiceEnterModal.context.invoice,
}}
>
<InvoiceFormContainer form={form} />

View File

@@ -42,7 +42,6 @@ export function InvoiceExportAllButton({
},
}
);
console.log("handle -> XML", QbXmlResponse);
} catch (error) {
console.log("Error getting QBXML from Server.", error);
notification["error"]({
@@ -121,7 +120,8 @@ export function InvoiceExportAllButton({
onClick={handleQbxml}
loading={loading}
disabled={disabled}
type='dashed'>
type="dashed"
>
{t("jobs.actions.exportselected")}
</Button>
);

View File

@@ -41,7 +41,6 @@ export function InvoiceExportButton({
},
}
);
console.log("handle -> XML", QbXmlResponse);
} catch (error) {
console.log("Error getting QBXML from Server.", error);
notification["error"]({
@@ -118,7 +117,8 @@ export function InvoiceExportButton({
onClick={handleQbxml}
loading={loading}
disabled={disabled}
type='dashed'>
type="dashed"
>
{t("jobs.actions.export")}
</Button>
);

View File

@@ -159,7 +159,6 @@ export default function InvoiceFormComponent({
style={{ display: invoiceEdit ? "none" : null }}
valuePropName="fileList"
getValueFromEvent={(e) => {
console.log("Upload event:", e);
if (Array.isArray(e)) {
return e;
}

View File

@@ -4,7 +4,7 @@ import React from "react";
import { useTranslation } from "react-i18next";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import InvoiceLineSearchSelect from "../invoice-line-search-select/invoice-line-search-select.component";
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
export default function InvoiceEnterModalLinesComponent({
lineData,
discount,
@@ -16,7 +16,7 @@ export default function InvoiceEnterModalLinesComponent({
return (
<Form.List name="invoicelines">
{(fields, { add, remove }) => {
{(fields, { add, remove, move }) => {
return (
<div className="invoice-form-lines-wrapper">
{fields.map((field, index) => (
@@ -197,6 +197,11 @@ export default function InvoiceEnterModalLinesComponent({
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
/>
</div>
</Form.Item>
))}

View File

@@ -208,19 +208,12 @@ export function InvoicesListTableComponent({
];
const handleOnInvoiceRowclick = (selectedRows) => {
console.log("selectedRows", selectedRows);
console.log("record.id", record.id);
setSelectedInvoiceLinesByInvoice({
...selectedInvoiceLinesByInvoice,
[record.id]: selectedRows.map((r) => r.id),
});
};
console.log(
"selectedInvoiceLinesByInvoice[record.id]",
selectedInvoiceLinesByInvoice[record.id]
);
return (
<div>
<Typography.Title level={3}>{`${t("invoices.fields.invoice_number")} ${

View File

@@ -0,0 +1,59 @@
import { Button, notification } from "antd";
import Axios from "axios";
import React, { useState } from "react";
import { useMutation } from "react-apollo";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
export function JobCalculateTotals({ bodyshop, job }) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const [updateJob] = useMutation(UPDATE_JOB);
const handleCalculate = async () => {
setLoading(true);
const newTotals = (
await Axios.post("/job/totals", {
job: job,
shoprates: bodyshop.shoprates,
})
).data;
const result = await updateJob({
refetchQueries: ["GET_JOB_BY_PK"],
awaitRefetchQueries: true,
variables: {
jobId: job.id,
job: {
job_totals: newTotals,
},
},
});
if (!!!result.errors) {
notification["success"]({ message: t("jobs.successes.updated") });
} else {
notification["error"]({
message: t("jobs.errors.updating", {
error: JSON.stringify(result.errors),
}),
});
}
setLoading(false);
};
return (
<div>
<Button loading={loading} onClick={handleCalculate}>
{t("jobs.actions.recalculate")}
</Button>
</div>
);
}
export default connect(mapStateToProps, null)(JobCalculateTotals);

View File

@@ -19,12 +19,7 @@ export function JobCostingModalComponent({ bodyshop, job }) {
const jobLineTotalsByProfitCenter = job.joblines.reduce(
(acc, val) => {
const laborProfitCenter = defaultProfits[val.mod_lbr_ty];
if (!!!laborProfitCenter)
console.log(
"Unknown cost/profit center mapping for labor.",
val.mod_lbr_ty
);
const laborProfitCenter = defaultProfits[val.mod_lbr_ty] || "?";
const rateName = `rate_${(val.mod_lbr_ty || "").toLowerCase()}`;
const laborAmount = Dinero({
@@ -36,7 +31,7 @@ export function JobCostingModalComponent({ bodyshop, job }) {
laborAmount
);
const partsProfitCenter = defaultProfits[val.part_type];
const partsProfitCenter = defaultProfits[val.part_type] || "?";
if (!!!partsProfitCenter)
console.log(
"Unknown cost/profit center mapping for parts.",
@@ -79,10 +74,31 @@ export function JobCostingModalComponent({ bodyshop, job }) {
{}
);
const ticketTotalsByProfitCenter = job.timetickets.reduce(
(ticket_acc, ticket_val) => {
//At the invoice level.
if (!!!ticket_acc[ticket_val.cost_center])
ticket_acc[ticket_val.cost_center] = Dinero();
ticket_acc[ticket_val.cost_center] = ticket_acc[
ticket_val.cost_center
].add(
Dinero({
amount: Math.round((ticket_val.rate || 0) * 100),
}).multiply(ticket_val.actualhrs || 0)
);
return ticket_acc;
},
{}
);
const summaryData = {
totalLaborSales: Dinero({ amount: 0 }),
totalPartsSales: Dinero({ amount: 0 }),
totalSales: Dinero({ amount: 0 }),
totalLaborCost: Dinero({ amount: 0 }),
totalPartsCost: Dinero({ amount: 0 }),
totalCost: Dinero({ amount: 0 }),
gpdollars: Dinero({ amount: 0 }),
gppercent: null,
@@ -95,7 +111,15 @@ export function JobCostingModalComponent({ bodyshop, job }) {
jobLineTotalsByProfitCenter.labor[ccVal] || Dinero({ amount: 0 });
const sale_parts =
jobLineTotalsByProfitCenter.parts[ccVal] || Dinero({ amount: 0 });
const cost = invoiceTotalsByProfitCenter[ccVal] || Dinero({ amount: 0 });
const cost_labor =
ticketTotalsByProfitCenter[ccVal] || Dinero({ amount: 0 });
const cost_parts =
invoiceTotalsByProfitCenter[ccVal] || Dinero({ amount: 0 });
const cost = (
invoiceTotalsByProfitCenter[ccVal] || Dinero({ amount: 0 })
).add(ticketTotalsByProfitCenter[ccVal] || Dinero({ amount: 0 }));
const totalSales = sale_labor.add(sale_parts);
const gpdollars = totalSales.subtract(cost);
const gppercent = (
@@ -115,6 +139,8 @@ export function JobCostingModalComponent({ bodyshop, job }) {
summaryData.totalSales = summaryData.totalSales
.add(sale_labor)
.add(sale_parts);
summaryData.totalLaborCost = summaryData.totalLaborCost.add(cost_labor);
summaryData.totalPartsCost = summaryData.totalPartsCost.add(cost_parts);
summaryData.totalCost = summaryData.totalCost.add(cost);
return {
@@ -122,6 +148,8 @@ export function JobCostingModalComponent({ bodyshop, job }) {
cost_center: ccVal,
sale_labor: sale_labor && sale_labor.toFormat(),
sale_parts: sale_parts && sale_parts.toFormat(),
cost_parts: cost_parts && cost_parts.toFormat(),
cost_labor: cost_labor && cost_labor.toFormat(),
cost: cost && cost.toFormat(),
gpdollars: gpdollars.toFormat(),
gppercent: gppercentFormatted,
@@ -143,8 +171,6 @@ export function JobCostingModalComponent({ bodyshop, job }) {
summaryData.gppercentFormatted = summaryData.gppercent;
}
console.log("JobCostingModalComponent -> summaryData", summaryData);
return (
<div>
<JobCostingStatistics job={job} summaryData={summaryData} />

View File

@@ -40,12 +40,20 @@ export default function JobCostingPartsTable({ job, data }) {
state.sortedInfo.columnKey === "sale_parts" && state.sortedInfo.order,
},
{
title: t("jobs.labels.cost"),
dataIndex: "cost",
key: "cost",
sorter: (a, b) => a.cost - b.cost,
title: t("jobs.labels.cost_labor"),
dataIndex: "cost_labor",
key: "cost_labor",
sorter: (a, b) => a.cost_labor - b.cost_labor,
sortOrder:
state.sortedInfo.columnKey === "cost" && state.sortedInfo.order,
state.sortedInfo.columnKey === "cost_labor" && state.sortedInfo.order,
},
{
title: t("jobs.labels.cost_parts"),
dataIndex: "cost_parts",
key: "cost_parts",
sorter: (a, b) => a.cost_parts - b.cost_parts,
sortOrder:
state.sortedInfo.columnKey === "cost_parts" && state.sortedInfo.order,
},
{
title: t("jobs.labels.gpdollars"),

View File

@@ -20,6 +20,14 @@ export default function JobCostingStatistics({ job, summaryData }) {
value={summaryData.totalSales.toFormat()}
title={t("jobs.labels.total_sales")}
/>
<Statistic
value={summaryData.totalLaborCost.toFormat()}
title={t("jobs.labels.cost_labor")}
/>
<Statistic
value={summaryData.totalPartsCost.toFormat()}
title={t("jobs.labels.cost_parts")}
/>
<Statistic
value={summaryData.totalCost.toFormat()}
title={t("jobs.labels.total_cost")}

View File

@@ -145,7 +145,7 @@ export function JobDetailCards({ setPrintCenterContext }) {
data={data ? data.jobs_by_pk : null}
/>
</Col>
<Col {...colBreakPoints}>
<Col span={24}>
<JobDetailCardsPartsComponent
loading={loading}
data={data ? data.jobs_by_pk : null}

View File

@@ -9,11 +9,10 @@ export default function JobDetailCardsDamageComponent({ loading, data }) {
return (
<CardTemplate loading={loading} title={t("jobs.labels.cards.damage")}>
{area_of_damage ? (
<Car
dmg1={area_of_damage.impact1 || null}
dmg2={area_of_damage.impact2 || null}
/>
) : t("jobs.errors.nodamage")}
<Car dmg1={area_of_damage.impact1} dmg2={area_of_damage.impact2} />
) : (
t("jobs.errors.nodamage")
)}
</CardTemplate>
);
}

View File

@@ -17,7 +17,7 @@ export default function JobDetailCardsNotesComponent({ loading, data }) {
<CardTemplate
loading={loading}
title={t("jobs.labels.cards.notes")}
extraLink={`/manage/jobs/${data.id}?notes`}
extraLink={`/manage/jobs/${data.id}?tab=notes`}
>
{data ? (
<Container>
@@ -25,7 +25,7 @@ export default function JobDetailCardsNotesComponent({ loading, data }) {
size="small"
bordered
dataSource={data.notes}
renderItem={item => (
renderItem={(item) => (
<List.Item>
{item.critical ? (
<EyeInvisibleFilled style={{ margin: 4, color: "red" }} />

View File

@@ -1,14 +1,141 @@
import React from "react";
import React, { useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { Pie, PieChart, Sector } from "recharts";
import CardTemplate from "./job-detail-cards.template.component";
export default function JobDetailCardsPartsComponent({ loading, data }) {
const { t } = useTranslation();
const { joblines_status } = data;
// console.log(
// "JobDetailCardsPartsComponent -> joblines_stats",
// joblines_status
// );
const memoizedData = useMemo(() => Calculatedata(joblines_status), [
joblines_status,
]);
const [state, setState] = useState({ activeIndex: 0 });
const onPieEnter = (data, index) => {
setState({
activeIndex: index,
});
};
return (
<div>
<CardTemplate loading={loading} title={t("jobs.labels.cards.parts")}>
Placeholder piechart.
<PieChart width={400} height={400}>
<Pie
activeIndex={state.activeIndex}
activeShape={renderActiveShape}
data={memoizedData}
cx={200}
cy={200}
innerRadius={60}
outerRadius={80}
fill="#8884d8"
dataKey="value"
onMouseEnter={onPieEnter}
/>
</PieChart>
</CardTemplate>
</div>
);
}
const Calculatedata = (data) => {
if (data.length > 0) {
const statusMapping = {};
data.map((i) => {
if (!statusMapping[i.status])
statusMapping[i.status] = { name: i.status || "No Status*", value: 0 };
statusMapping[i.status].value = statusMapping[i.status].value + i.count;
return null;
});
return Object.keys(statusMapping).map((key) => {
return statusMapping[key];
});
} else {
return [
{ name: "Group A", value: 400 },
{ name: "Group B", value: 300 },
{ name: "Group C", value: 300 },
{ name: "Group D", value: 200 },
];
}
};
const renderActiveShape = (props) => {
const RADIAN = Math.PI / 180;
const {
cx,
cy,
midAngle,
innerRadius,
outerRadius,
startAngle,
endAngle,
fill,
payload,
percent,
value,
} = props;
const sin = Math.sin(-RADIAN * midAngle);
const cos = Math.cos(-RADIAN * midAngle);
const sx = cx + (outerRadius + 10) * cos;
const sy = cy + (outerRadius + 10) * sin;
const mx = cx + (outerRadius + 30) * cos;
const my = cy + (outerRadius + 30) * sin;
const ex = mx + (cos >= 0 ? 1 : -1) * 22;
const ey = my;
const textAnchor = cos >= 0 ? "start" : "end";
return (
<g>
<text x={cx} y={cy} dy={8} textAnchor="middle" fill={fill}>
{payload.name}
</text>
<Sector
cx={cx}
cy={cy}
innerRadius={innerRadius}
outerRadius={outerRadius}
startAngle={startAngle}
endAngle={endAngle}
fill={fill}
/>
<Sector
cx={cx}
cy={cy}
startAngle={startAngle}
endAngle={endAngle}
innerRadius={outerRadius + 6}
outerRadius={outerRadius + 10}
fill={fill}
/>
<path
d={`M${sx},${sy}L${mx},${my}L${ex},${ey}`}
stroke={fill}
fill="none"
/>
<circle cx={ex} cy={ey} r={2} fill={fill} stroke="none" />
<text
x={ex + (cos >= 0 ? 1 : -1) * 12}
y={ey}
textAnchor={textAnchor}
fill="#333"
>{`Count: ${value}`}</text>
<text
x={ex + (cos >= 0 ? 1 : -1) * 12}
y={ey}
dy={18}
textAnchor={textAnchor}
fill="#999"
>
{`(${(percent * 100).toFixed(2)}%)`}
</text>
</g>
);
};

View File

@@ -17,21 +17,21 @@ export default function JobDetailCardsTotalsComponent({ loading, data }) {
return (
<CardTemplate loading={loading} title={t("jobs.labels.cards.totals")}>
{totals ? (
<div className='imex-flex-row imex-flex-row__flex-space-around'>
<div className="imex-flex-row imex-flex-row__flex-space-around">
<Statistic
className='imex-flex-row__margin-large'
className="imex-flex-row__margin-large"
title={t("jobs.labels.total_repairs")}
value={Dinero(totals.totals.total_repairs).toFormat()}
/>
<Statistic
className='imex-flex-row__margin-large'
className="imex-flex-row__margin-large"
title={t("jobs.fields.ded_amt")}
value={Dinero({
amount: Math.round((data.ded_amt || 0) * 100),
}).toFormat()}
/>
<Statistic
className='imex-flex-row__margin-large'
className="imex-flex-row__margin-large"
title={t("jobs.labels.net_repairs")}
value={Dinero(totals.totals.net_repairs).toFormat()}
/>

View File

@@ -267,7 +267,6 @@ export function JobLinesComponent({
];
const handleTableChange = (pagination, filters, sorter) => {
console.log("filters", filters);
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
};
@@ -286,7 +285,7 @@ export function JobLinesComponent({
<Menu.Item key="PAS">{t("joblines.fields.part_types.PAS")}</Menu.Item>
</Menu>
);
console.log("state", state);
return (
<div>
<PartsOrderModalContainer />

View File

@@ -1,51 +1,38 @@
import { useQuery } from "@apollo/react-hooks";
import React, { useState } from "react";
import { GET_JOB_LINES_BY_PK } from "../../graphql/jobs-lines.queries";
import AlertComponent from "../alert/alert.component";
import JobLinesComponent from "./job-lines.component";
function JobLinesContainer({ jobId }) {
const { loading, error, data, refetch } = useQuery(GET_JOB_LINES_BY_PK, {
variables: { id: jobId },
});
function JobLinesContainer({ jobId, joblines, refetch }) {
const [searchText, setSearchText] = useState("");
const [selectedLines, setSelectedLines] = useState([]);
if (error) return <AlertComponent message={error.message} type='error' />;
const jobLines =
data && data.joblines
? searchText
? data.joblines.filter(
(jl) =>
(jl.unq_seq || "")
.toString()
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(jl.line_desc || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(jl.part_type || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(jl.oem_partno || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(jl.op_code_desc || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(jl.db_price || "")
.toString()
.includes(searchText.toLowerCase()) ||
(jl.act_price || "").toString().includes(searchText.toLowerCase())
)
: data.joblines
: null;
const jobLines = joblines
? searchText
? joblines.filter(
(jl) =>
(jl.unq_seq || "")
.toString()
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(jl.line_desc || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(jl.part_type || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(jl.oem_partno || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(jl.op_code_desc || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(jl.db_price || "").toString().includes(searchText.toLowerCase()) ||
(jl.act_price || "").toString().includes(searchText.toLowerCase())
)
: joblines
: null;
return (
<JobLinesComponent
loading={loading}
refetch={refetch}
jobLines={jobLines}
setSearchText={setSearchText}

View File

@@ -67,62 +67,77 @@ export function JobEmployeeAssignments({
return (
<div>
<Popover destroyTooltipOnHide content={popContent} visible={visibility}>
<DataLabel label={t("jobs.fields.employee_body")}>
{body ? (
<div>
<span>{`${body.first_name || ""} ${body.last_name || ""}`}</span>
<MinusOutlined
operation="body"
onClick={() => handleRemove("body")}
<div style={{ display: "flex" }}>
<DataLabel
label={t("jobs.fields.employee_body")}
style={{ margin: "0rem .5rem" }}
>
{body ? (
<div>
<span>{`${body.first_name || ""} ${
body.last_name || ""
}`}</span>
<MinusOutlined
operation="body"
onClick={() => handleRemove("body")}
/>
</div>
) : (
<PlusCircleFilled
onClick={() => {
setAssignment({ operation: "body" });
setVisibility(true);
}}
/>
</div>
) : (
<PlusCircleFilled
onClick={() => {
setAssignment({ operation: "body" });
setVisibility(true);
}}
/>
)}
</DataLabel>
<DataLabel label={t("jobs.fields.employee_prep")}>
{prep ? (
<div>
<span>{`${prep.first_name || ""} ${prep.last_name || ""}`}</span>
<MinusOutlined
operation="prep"
onClick={() => handleRemove("prep")}
)}
</DataLabel>
<DataLabel
label={t("jobs.fields.employee_prep")}
style={{ margin: "0rem .5rem" }}
>
{prep ? (
<div>
<span>{`${prep.first_name || ""} ${
prep.last_name || ""
}`}</span>
<MinusOutlined
operation="prep"
onClick={() => handleRemove("prep")}
/>
</div>
) : (
<PlusCircleFilled
onClick={() => {
setAssignment({ operation: "prep" });
setVisibility(true);
}}
/>
</div>
) : (
<PlusCircleFilled
onClick={() => {
setAssignment({ operation: "prep" });
setVisibility(true);
}}
/>
)}
</DataLabel>
<DataLabel label={t("jobs.fields.employee_refinish")}>
{refinish ? (
<div>
<span>{`${refinish.first_name || ""} ${
refinish.last_name || ""
}`}</span>
<MinusOutlined
operation="refinish"
onClick={() => handleRemove("refinish")}
)}
</DataLabel>
<DataLabel
label={t("jobs.fields.employee_refinish")}
style={{ margin: "0rem .5rem" }}
>
{refinish ? (
<div>
<span>{`${refinish.first_name || ""} ${
refinish.last_name || ""
}`}</span>
<MinusOutlined
operation="refinish"
onClick={() => handleRemove("refinish")}
/>
</div>
) : (
<PlusCircleFilled
onClick={() => {
setAssignment({ operation: "refinish" });
setVisibility(true);
}}
/>
</div>
) : (
<PlusCircleFilled
onClick={() => {
setAssignment({ operation: "refinish" });
setVisibility(true);
}}
/>
)}
</DataLabel>
)}
</DataLabel>
</div>
</Popover>
</div>
);

View File

@@ -30,7 +30,6 @@ export function JobIntakeForm({ formItems, bodyshop }) {
const search = queryString.parse(useLocation().search);
const handleFinish = async (values) => {
console.log("values", values);
setLoading(true);
logImEXEvent("job_complete_intake");
@@ -71,7 +70,6 @@ export function JobIntakeForm({ formItems, bodyshop }) {
}),
});
}
console.log("handleFinish -> result", result);
setLoading(false);
};

View File

@@ -16,11 +16,6 @@ export default function JobReconciliationModalComponent({ job, invoices }) {
)
.flat() || [];
console.log(
"JobReconciliationModalComponent -> invoiceLineData",
invoiceLineData
);
const jobLineData = job.joblines.filter((j) => j.part_type !== null);
return (

View File

@@ -36,9 +36,11 @@ const JobSearchSelect = (
<Option key={o.id} value={o.id}>
{`${o.ro_number ? o.ro_number : o.est_number} | ${
o.ownr_ln || ""
} ${o.ownr_fn || ""} | ${o.v_model_yr || ""} ${
o.v_make_desc || ""
} ${o.v_model_desc || ""}`}
} ${o.ownr_fn || ""} ${
o.ownr_co_nm ? ` ${o.ownr_co_num}` : ""
}| ${o.v_model_yr || ""} ${o.v_make_desc || ""} ${
o.v_model_desc || ""
}`}
</Option>
))
: null}

View File

@@ -1,219 +1,305 @@
import { Statistic } from "antd";
import React, { useEffect, useState } from "react";
import { Col, Result, Row, Statistic, Typography } from "antd";
import Dinero from "dinero.js";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import JobCalculateTotals from "../job-calculate-totals/job-calculate-totals.component";
import "./job-totals-table.styles.scss";
import { CalculateJob } from "./job-totals.utility";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
});
const colSpan = {
md: { span: 24 },
lg: { span: 12 },
};
export function JobsTotalsTableComponent({ bodyshop, job }) {
const { t } = useTranslation();
const [totals, setTotals] = useState(null);
useEffect(() => {
setTotals(CalculateJob(job, bodyshop.shoprates));
}, [bodyshop, job]);
console.log("job", job);
if (!!!totals) {
return <LoadingSkeleton />;
if (!!!job.job_totals) {
return (
<Result
title={t("jobs.errors.nofinancial")}
extra={<JobCalculateTotals job={job} />}
/>
);
}
return (
<div className='job-totals-container'>
<div className='job-totals-tables'>
<table className='job-totals-rates-table'>
<thead>
<tr>
<th>{t("jobs.labels.rates")}</th>
<th>$</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>{t("jobs.fields.rate_laa")}</td>
<td>{totals.rates.laa.total.toFormat()}</td>
<td>{`(${totals.rates.laa.hours.toFixed(2)} @ ${
totals.rates.laa.rate
})`}</td>
</tr>
<tr>
<td>{t("jobs.fields.rate_lab")}</td>
<td>{totals.rates.lab.total.toFormat()}</td>
<td>{`(${totals.rates.lab.hours.toFixed(2)} @ ${
totals.rates.lab.rate
})`}</td>
</tr>
<tr>
<td>{t("jobs.fields.rate_lad")}</td>
<td>{totals.rates.lad.total.toFormat()}</td>
<td>{`(${totals.rates.lad.hours.toFixed(2)} @ ${
totals.rates.lad.rate
})`}</td>
</tr>
<tr>
<td>{t("jobs.fields.rate_lae")}</td>
<td>{totals.rates.lae.total.toFormat()}</td>
<td>{`(${totals.rates.lae.hours.toFixed(2)} @ ${
totals.rates.lae.rate
})`}</td>
</tr>
<tr>
<td>{t("jobs.fields.rate_laf")}</td>
<td>{totals.rates.laf.total.toFormat()}</td>
<td>{`(${totals.rates.laf.hours.toFixed(2)} @ ${
totals.rates.laf.rate
})`}</td>
</tr>
<tr>
<td>{t("jobs.fields.rate_lag")}</td>
<td>{totals.rates.lag.total.toFormat()}</td>
<td>{`(${totals.rates.lag.hours.toFixed(2)} @ ${
totals.rates.lag.rate
})`}</td>
</tr>
<tr>
<td>{t("jobs.fields.rate_lam")}</td>
<td>{totals.rates.lam.total.toFormat()}</td>
<td>{`(${totals.rates.lam.hours.toFixed(2)} @ ${
totals.rates.lam.rate
})`}</td>
</tr>
<tr>
<td>{t("jobs.fields.rate_lar")}</td>
<td>{totals.rates.lar.total.toFormat()}</td>
<td>{`(${totals.rates.lar.hours.toFixed(2)} @ ${
totals.rates.lar.rate
})`}</td>
</tr>
<tr>
<td>{t("jobs.fields.rate_las")}</td>
<td>{totals.rates.las.total.toFormat()}</td>
<td>{`(${totals.rates.las.hours.toFixed(2)} @ ${
totals.rates.las.rate
})`}</td>
</tr>
<tr>
<td>{t("jobs.fields.rate_lau")}</td>
<td>{totals.rates.lau.total.toFormat()}</td>
<td>{`(${totals.rates.lau.hours.toFixed(2)} @ ${
totals.rates.lau.rate
})`}</td>
</tr>
<tr>
<td>{t("jobs.fields.rate_la1")}</td>
<td>{totals.rates.la1.total.toFormat()}</td>
<td>{`(${totals.rates.la1.hours.toFixed(2)} @ ${
totals.rates.la1.rate
})`}</td>
</tr>
<tr>
<td>{t("jobs.fields.rate_la2")}</td>
<td>{totals.rates.la2.total.toFormat()}</td>
<td>{`(${totals.rates.la2.hours.toFixed(2)} @ ${
totals.rates.la2.rate
})`}</td>
</tr>
<tr>
<td>{t("jobs.fields.rate_la3")}</td>
<td>{totals.rates.la3.total.toFormat()}</td>
<td>{`(${totals.rates.la3.hours.toFixed(2)} @ ${
totals.rates.la3.rate
})`}</td>
</tr>
<tr>
<td>{t("jobs.fields.rate_la4")}</td>
<td>{totals.rates.la4.total.toFormat()}</td>
<td>{`(${totals.rates.la4.hours.toFixed(2)} @ ${
totals.rates.la4.rate
})`}</td>
</tr>
<tr>
<td>{t("jobs.fields.rate_atp")}</td>
<td>{totals.rates.atp.total.toFormat()}</td>
<td>{`(${totals.rates.atp.hours.toFixed(2)} @ ${
totals.rates.atp.rate
})`}</td>
</tr>
<tr>
<td>{t("jobs.labels.mapa")}</td>
<td>{totals.rates.mapa.total.toFormat()}</td>
<td>{`(${totals.rates.mapa.hours.toFixed(2)} @ ${
totals.rates.mapa.rate
})`}</td>
</tr>
<tr>
<td>{t("jobs.labels.mash")}</td>
<td>{totals.rates.mash.total.toFormat()}</td>
<td>{`(${totals.rates.mash.hours.toFixed(2)} @ ${
totals.rates.mash.rate
})`}</td>
</tr>
<tr>
<td>{t("jobs.labels.rates_subtotal")}</td>
<td>{totals.rates.subtotal.toFormat()}</td>
<td></td>
</tr>
</tbody>
</table>
<table className='job-totals-parts-table'>
<tbody>
<tr>
<td>{t("jobs.labels.partstotal")}</td>
<td>{totals.parts.parts.total.toFormat()}</td>
<td>{`(${totals.parts.parts.subtotal.toFormat()} ± ${totals.parts.parts.adjustments.toFormat()})`}</td>
</tr>
<tr>
<td>{t("jobs.labels.subletstotal")}</td>
<td>{totals.parts.sublets.total.toFormat()}</td>
<td>{`(${totals.parts.sublets.subtotal.toFormat()} ± ${totals.parts.sublets.adjustments.toFormat()})`}</td>
</tr>
</tbody>
</table>
</div>
<div
className='job-totals-stats'
onClick={(e) => {
if (e.detail === 3) {
try {
console.log("Job", job);
} catch {
console.log("Unable to show job.");
}
}
}}>
<Statistic
title={t("jobs.labels.subtotal")}
value={totals.totals.subtotal.toFormat()}
/>
<Statistic
title={t("jobs.labels.state_tax_amt")}
value={totals.totals.state_tax.toFormat()}
/>
<Statistic
title={t("jobs.labels.local_tax_amt")}
value={totals.totals.local_tax.toFormat()}
/>
<Statistic
title={t("jobs.labels.federal_tax_amt")}
value={totals.totals.federal_tax.toFormat()}
/>
<Statistic
title={t("jobs.labels.total_repairs")}
value={totals.totals.total_repairs.toFormat()}
/>
<Statistic
title={t("jobs.labels.net_repairs")}
value={totals.totals.net_repairs.toFormat()}
/>
</div>
<div>
<Row gutter={[32, 32]}>
<Col {...colSpan}>
<div className="job-totals-half">
<Typography.Title level={4}>
{t("jobs.labels.labortotals")}
</Typography.Title>
<table>
<tbody>
<tr>
<td>{t("jobs.fields.rate_laa")}</td>
<td className="currency">
{Dinero(job.job_totals.rates.laa.total).toFormat()}
</td>
<td>{`(${job.job_totals.rates.laa.hours.toFixed(2)} @ ${
job.job_totals.rates.laa.rate
})`}</td>
</tr>
<tr>
<td>{t("jobs.fields.rate_lab")}</td>
<td className="currency">
{Dinero(job.job_totals.rates.lab.total).toFormat()}
</td>
<td>{`(${job.job_totals.rates.lab.hours.toFixed(2)} @ ${
job.job_totals.rates.lab.rate
})`}</td>
</tr>
<tr>
<td>{t("jobs.fields.rate_lad")}</td>
<td className="currency">
{Dinero(job.job_totals.rates.lad.total).toFormat()}
</td>
<td>{`(${job.job_totals.rates.lad.hours.toFixed(2)} @ ${
job.job_totals.rates.lad.rate
})`}</td>
</tr>
<tr>
<td>{t("jobs.fields.rate_lae")}</td>
<td className="currency">
{Dinero(job.job_totals.rates.lae.total).toFormat()}
</td>
<td>{`(${job.job_totals.rates.lae.hours.toFixed(2)} @ ${
job.job_totals.rates.lae.rate
})`}</td>
</tr>
<tr>
<td>{t("jobs.fields.rate_laf")}</td>
<td className="currency">
{Dinero(job.job_totals.rates.laf.total).toFormat()}
</td>
<td>{`(${job.job_totals.rates.laf.hours.toFixed(2)} @ ${
job.job_totals.rates.laf.rate
})`}</td>
</tr>
<tr>
<td>{t("jobs.fields.rate_lag")}</td>
<td className="currency">
{Dinero(job.job_totals.rates.lag.total).toFormat()}
</td>
<td>{`(${job.job_totals.rates.lag.hours.toFixed(2)} @ ${
job.job_totals.rates.lag.rate
})`}</td>
</tr>
<tr>
<td>{t("jobs.fields.rate_lam")}</td>
<td className="currency">
{Dinero(job.job_totals.rates.lam.total).toFormat()}
</td>
<td>{`(${job.job_totals.rates.lam.hours.toFixed(2)} @ ${
job.job_totals.rates.lam.rate
})`}</td>
</tr>
<tr>
<td>{t("jobs.fields.rate_lar")}</td>
<td className="currency">
{Dinero(job.job_totals.rates.lar.total).toFormat()}
</td>
<td>{`(${job.job_totals.rates.lar.hours.toFixed(2)} @ ${
job.job_totals.rates.lar.rate
})`}</td>
</tr>
<tr>
<td>{t("jobs.fields.rate_las")}</td>
<td className="currency">
{Dinero(job.job_totals.rates.las.total).toFormat()}
</td>
<td>{`(${job.job_totals.rates.las.hours.toFixed(2)} @ ${
job.job_totals.rates.las.rate
})`}</td>
</tr>
<tr>
<td>{t("jobs.fields.rate_lau")}</td>
<td className="currency">
{Dinero(job.job_totals.rates.lau.total).toFormat()}
</td>
<td>{`(${job.job_totals.rates.lau.hours.toFixed(2)} @ ${
job.job_totals.rates.lau.rate
})`}</td>
</tr>
<tr>
<td>{t("jobs.fields.rate_la1")}</td>
<td className="currency">
{Dinero(job.job_totals.rates.la1.total).toFormat()}
</td>
<td>{`(${job.job_totals.rates.la1.hours.toFixed(2)} @ ${
job.job_totals.rates.la1.rate
})`}</td>
</tr>
<tr>
<td>{t("jobs.fields.rate_la2")}</td>
<td className="currency">
{Dinero(job.job_totals.rates.la2.total).toFormat()}
</td>
<td>{`(${job.job_totals.rates.la2.hours.toFixed(2)} @ ${
job.job_totals.rates.la2.rate
})`}</td>
</tr>
<tr>
<td>{t("jobs.fields.rate_la3")}</td>
<td className="currency">
{Dinero(job.job_totals.rates.la3.total).toFormat()}
</td>
<td>{`(${job.job_totals.rates.la3.hours.toFixed(2)} @ ${
job.job_totals.rates.la3.rate
})`}</td>
</tr>
<tr>
<td>{t("jobs.fields.rate_la4")}</td>
<td className="currency">
{Dinero(job.job_totals.rates.la4.total).toFormat()}
</td>
<td>{`(${job.job_totals.rates.la4.hours.toFixed(2)} @ ${
job.job_totals.rates.la4.rate
})`}</td>
</tr>
<tr>
<td>{t("jobs.fields.rate_atp")}</td>
<td className="currency">
{Dinero(job.job_totals.rates.atp.total).toFormat()}
</td>
<td>{`(${job.job_totals.rates.atp.hours.toFixed(2)} @ ${
job.job_totals.rates.atp.rate
})`}</td>
</tr>
<tr>
<td>{t("jobs.labels.rates_subtotal")}</td>
<td>{Dinero(job.job_totals.rates.subtotal).toFormat()}</td>
<td></td>
</tr>
</tbody>
</table>
</div>
</Col>
<Col {...colSpan}>
<div className="job-totals-half">
<table>
<tbody>
{Object.keys(job.job_totals.parts.parts.list).map(
(key, idx) => (
<tr key={idx}>
<td>{t(`jobs.fields.${key.toLowerCase()}`)}</td>
<td className="currency">
{Dinero(
job.job_totals.parts.parts.list[key].total
).toFormat()}
</td>
<td></td>
</tr>
)
)}
<tr>
<td>{t("jobs.labels.partstotal")}</td>
<td className="currency">
{Dinero(job.job_totals.parts.parts.total).toFormat()}
</td>
<td>{`(${Dinero(
job.job_totals.parts.parts.subtotal
).toFormat()} ± ${Dinero(
job.job_totals.parts.parts.adjustments
).toFormat()})`}</td>
</tr>
<tr>
<td>{t("jobs.labels.subletstotal")}</td>
<td className="currency">
{Dinero(job.job_totals.parts.sublets.total).toFormat()}
</td>
<td>{`(${Dinero(
job.job_totals.parts.sublets.subtotal
).toFormat()} ± ${Dinero(
job.job_totals.parts.sublets.adjustments
).toFormat()})`}</td>
</tr>
<tr>
<td>{t("jobs.labels.mapa")}</td>
<td className="currency">
{Dinero(job.job_totals.rates.mapa.total).toFormat()}
</td>
<td>{`(${job.job_totals.rates.mapa.hours.toFixed(2)} @ ${
job.job_totals.rates.mapa.rate
})`}</td>
</tr>
<tr>
<td>{t("jobs.labels.mash")}</td>
<td className="currency">
{Dinero(job.job_totals.rates.mash.total).toFormat()}
</td>
<td>{`(${job.job_totals.rates.mash.hours.toFixed(2)} @ ${
job.job_totals.rates.mash.rate
})`}</td>
</tr>
</tbody>
</table>
<div
className="job-totals-stats"
onClick={(e) => {
if (e.detail === 3) {
try {
console.log("Job", job);
} catch {
console.log("Unable to show job.");
}
}
}}
>
<Statistic
title={t("jobs.labels.state_tax_amt")}
value={Dinero(job.job_totals.totals.state_tax).toFormat()}
/>
<Statistic
title={t("jobs.labels.local_tax_amt")}
value={Dinero(job.job_totals.totals.local_tax).toFormat()}
/>
<Statistic
title={t("jobs.labels.federal_tax_amt")}
value={Dinero(job.job_totals.totals.federal_tax).toFormat()}
/>
</div>
<div
className="job-totals-stats"
onClick={(e) => {
if (e.detail === 3) {
try {
console.log("Job", job);
} catch {
console.log("Unable to show job.");
}
}
}}
>
<Statistic
title={t("jobs.labels.subtotal")}
value={Dinero(job.job_totals.totals.subtotal).toFormat()}
/>
<Statistic
title={t("jobs.labels.total_repairs")}
value={Dinero(job.job_totals.totals.total_repairs).toFormat()}
/>
<Statistic
title={t("jobs.labels.net_repairs")}
value={Dinero(job.job_totals.totals.net_repairs).toFormat()}
/>
</div>
<JobCalculateTotals job={job} />
</div>
</Col>
</Row>
</div>
);
}

View File

@@ -1,24 +1,38 @@
.job-totals-container {
display: flex;
flex-flow: wrap;
}
.job-totals-tables {
.job-totals-half {
flex: 1;
display: block;
}
display: flex;
flex-direction: column;
align-items: center;
.job-totals-rates-table,
.job-totals-parts-table {
border: black;
width: 100%;
table {
border: 1px solid #ccc;
border-collapse: collapse;
margin: 0;
padding: 0;
width: 80%;
table-layout: fixed;
}
table tr {
//background-color: #f8f8f8;
border: 1px solid #ddd;
padding: 0.35em;
}
table th,
table td {
padding: 0.625em;
//text-align: center;
}
table td.currency {
text-align: right;
}
}
.job-totals-stats {
margin: 1rem;
display: flex;
flex-direction: column;
align-content: center;
.ant-statistic {
margin: 0.5rem;
}
width: 100%;
//flex-direction: column;
justify-content: space-evenly;
}

View File

@@ -1,323 +1,322 @@
import Dinero from "dinero.js";
import { logImEXEvent } from "../../firebase/firebase.utils";
// import Dinero from "dinero.js";
// import { logImEXEvent } from "../../firebase/firebase.utils";
export function CalculateJob(job, shoprates) {
logImEXEvent("job_calculate_total");
// export function CalculateJob(job, shoprates) {
// logImEXEvent("job_calculate_total");
let ret = {
parts: CalculatePartsTotals(job.joblines),
rates: CalculateRatesTotals(job, shoprates),
custPayable: CalculateCustPayable(job),
};
ret.totals = CalculateTaxesTotals(job, ret);
console.log("CalculateJob -> Final", ret);
return ret;
}
// let ret = {
// parts: CalculatePartsTotals(job.joblines),
// rates: CalculateRatesTotals(job, shoprates),
// custPayable: CalculateCustPayable(job),
// };
// ret.totals = CalculateTaxesTotals(job, ret);
// console.log("CalculateJob -> Final", ret);
// return ret;
// }
function CalculateTaxesTotals(job, otherTotals) {
const subtotal = otherTotals.parts.parts.subtotal
.add(otherTotals.parts.sublets.subtotal)
.add(otherTotals.rates.subtotal)
.add(Dinero({ amount: (job.towing_payable || 0) * 100 }))
.add(Dinero({ amount: (job.storage_payable || 0) * 100 }));
//TODO Levies should be included??
// function CalculateTaxesTotals(job, otherTotals) {
// const subtotal = otherTotals.parts.parts.subtotal
// .add(otherTotals.parts.sublets.subtotal)
// .add(otherTotals.rates.subtotal)
// .add(Dinero({ amount: (job.towing_payable || 0) * 100 }))
// .add(Dinero({ amount: (job.storage_payable || 0) * 100 }));
// //TODO Levies should be included??
const statePartsTax = job.joblines.reduce((acc, val) => {
if (!!!val.tax_part) return acc;
if (!!job.parts_tax_rates[val.part_type]) {
return acc.add(
Dinero({ amount: Math.round(val.act_price * 100) })
.multiply(val.part_qty)
.percentage(
(job.parts_tax_rates[val.part_type].prt_tax_rt || 0) * 100
)
);
} else {
return acc;
}
}, Dinero({ amount: 0 }));
console.log("job.federal_tax_rate ", job.federal_tax_rate);
console.log(subtotal.percentage((job.federal_tax_rate || 0) * 100));
let ret = {
subtotal: subtotal,
federal_tax: subtotal.percentage((job.federal_tax_rate || 0) * 100),
statePartsTax,
state_tax: statePartsTax
.add(
otherTotals.rates.rates_subtotal.percentage((job.tax_lbr_rt || 0) * 100)
)
.add(
Dinero({
amount: Math.round((job.towing_payable || 0) * 100),
}).percentage((job.tax_tow_rt || 0) * 100)
)
.add(
Dinero({
amount: Math.round((job.storage_payable || 0) * 100),
}).percentage((job.tax_str_rt || 0) * 100)
)
.add(
otherTotals.rates.mapa.total
.add(otherTotals.rates.mash.total)
.percentage((job.tax_paint_mat_rt || 0) * 100)
),
local_tax: subtotal.percentage((job.local_tax_rate || 0) * 100),
};
ret.total_repairs = ret.subtotal
.add(ret.federal_tax)
.add(ret.state_tax)
.add(ret.local_tax);
ret.net_repairs = ret.total_repairs.subtract(otherTotals.custPayable.total);
// const statePartsTax = job.joblines.reduce((acc, val) => {
// if (!!!val.tax_part) return acc;
// if (!!job.parts_tax_rates[val.part_type]) {
// return acc.add(
// Dinero({ amount: Math.round(val.act_price * 100) })
// .multiply(val.part_qty)
// .percentage(
// (job.parts_tax_rates[val.part_type].prt_tax_rt || 0) * 100
// )
// );
// } else {
// return acc;
// }
// }, Dinero({ amount: 0 }));
// let ret = {
// subtotal: subtotal,
// federal_tax: subtotal.percentage((job.federal_tax_rate || 0) * 100),
// statePartsTax,
// state_tax: statePartsTax
// .add(
// otherTotals.rates.rates_subtotal.percentage((job.tax_lbr_rt || 0) * 100)
// )
// .add(
// Dinero({
// amount: Math.round((job.towing_payable || 0) * 100),
// }).percentage((job.tax_tow_rt || 0) * 100)
// )
// .add(
// Dinero({
// amount: Math.round((job.storage_payable || 0) * 100),
// }).percentage((job.tax_str_rt || 0) * 100)
// )
// .add(
// otherTotals.rates.mapa.total
// .add(otherTotals.rates.mash.total)
// .percentage((job.tax_paint_mat_rt || 0) * 100)
// ),
// local_tax: subtotal.percentage((job.local_tax_rate || 0) * 100),
// };
// ret.total_repairs = ret.subtotal
// .add(ret.federal_tax)
// .add(ret.state_tax)
// .add(ret.local_tax);
// ret.net_repairs = ret.total_repairs.subtract(otherTotals.custPayable.total);
return ret;
}
// return ret;
// }
//Rates are multipled by 10 to reduce the errors of rounding.
//Adjusted for when adding to total by dividing by 10.
function CalculateRatesTotals(ratesList, shoprates) {
const jobLines = ratesList.joblines;
// //Rates are multipled by 10 to reduce the errors of rounding.
// //Adjusted for when adding to total by dividing by 10.
// function CalculateRatesTotals(ratesList, shoprates) {
// const jobLines = ratesList.joblines;
let ret = {
la1: {
hours: jobLines
.filter((item) => item.mod_lbr_ty === "LA1")
.reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
rate: ratesList.rate_la1 || 0,
},
la2: {
hours: jobLines
.filter((item) => item.mod_lbr_ty === "LA2")
.reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
rate: ratesList.rate_la2 || 0,
},
la3: {
rate: ratesList.rate_la3 || 0,
hours: jobLines
.filter((item) => item.mod_lbr_ty === "LA3")
.reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
},
la4: {
rate: ratesList.rate_la4 || 0,
hours: jobLines
.filter((item) => item.mod_lbr_ty === "LA4")
.reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
},
laa: {
rate: ratesList.rate_laa || 0,
hours: jobLines
.filter((item) => item.mod_lbr_ty === "LAA")
.reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
},
lab: {
rate: ratesList.rate_lab || 0,
hours: jobLines
.filter((item) => item.mod_lbr_ty === "LAB")
.reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
},
lad: {
rate: ratesList.rate_lad || 0,
hours: jobLines
.filter((item) => item.mod_lbr_ty === "LAD")
.reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
},
lae: {
rate: ratesList.rate_lae || 0,
hours: jobLines
.filter((item) => item.mod_lbr_ty === "LAE")
.reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
},
laf: {
rate: ratesList.rate_laf || 0,
hours: jobLines
.filter((item) => item.mod_lbr_ty === "LAF")
.reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
},
lag: {
rate: ratesList.rate_lag || 0,
hours: jobLines
.filter((item) => item.mod_lbr_ty === "LAG")
.reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
},
lam: {
rate: ratesList.rate_lam || 0,
hours: jobLines
.filter((item) => item.mod_lbr_ty === "LAM")
.reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
},
lar: {
rate: ratesList.rate_lar || 0,
hours: jobLines
.filter((item) => item.mod_lbr_ty === "LAR")
.reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
},
las: {
rate: ratesList.rate_las || 0,
hours: jobLines
.filter((item) => item.mod_lbr_ty === "LAS")
.reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
},
lau: {
rate: ratesList.rate_lau || 0,
hours: jobLines
.filter((item) => item.mod_lbr_ty === "LAU")
.reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
},
atp: {
rate: shoprates.rate_atp || 0,
hours:
jobLines.filter((item) => item.line_desc.includes("ATS Amount"))
.length > 0
? jobLines
.filter(
(item) =>
item.mod_lbr_ty !== "LA1" &&
item.mod_lbr_ty !== "LA2" &&
item.mod_lbr_ty !== "LA3" &&
item.mod_lbr_ty !== "LA4" &&
item.mod_lbr_ty !== "LAU" &&
item.mod_lbr_ty !== "LAG" &&
item.mod_lbr_ty !== "LAS" &&
item.mod_lbr_ty !== "LAA"
)
.reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0)
: 0,
},
mapa: {
rate: ratesList.rate_mapa || 0,
hours: jobLines
.filter((item) => item.mod_lbr_ty === "LAR")
.reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
},
mash: {
rate: ratesList.rate_mash || 0,
hours: jobLines
.filter((item) => item.mod_lbr_ty !== "LAR")
.reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
},
};
// let ret = {
// la1: {
// hours: jobLines
// .filter((item) => item.mod_lbr_ty === "LA1")
// .reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
// rate: ratesList.rate_la1 || 0,
// },
// la2: {
// hours: jobLines
// .filter((item) => item.mod_lbr_ty === "LA2")
// .reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
// rate: ratesList.rate_la2 || 0,
// },
// la3: {
// rate: ratesList.rate_la3 || 0,
// hours: jobLines
// .filter((item) => item.mod_lbr_ty === "LA3")
// .reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
// },
// la4: {
// rate: ratesList.rate_la4 || 0,
// hours: jobLines
// .filter((item) => item.mod_lbr_ty === "LA4")
// .reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
// },
// laa: {
// rate: ratesList.rate_laa || 0,
// hours: jobLines
// .filter((item) => item.mod_lbr_ty === "LAA")
// .reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
// },
// lab: {
// rate: ratesList.rate_lab || 0,
// hours: jobLines
// .filter((item) => item.mod_lbr_ty === "LAB")
// .reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
// },
// lad: {
// rate: ratesList.rate_lad || 0,
// hours: jobLines
// .filter((item) => item.mod_lbr_ty === "LAD")
// .reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
// },
// lae: {
// rate: ratesList.rate_lae || 0,
// hours: jobLines
// .filter((item) => item.mod_lbr_ty === "LAE")
// .reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
// },
// laf: {
// rate: ratesList.rate_laf || 0,
// hours: jobLines
// .filter((item) => item.mod_lbr_ty === "LAF")
// .reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
// },
// lag: {
// rate: ratesList.rate_lag || 0,
// hours: jobLines
// .filter((item) => item.mod_lbr_ty === "LAG")
// .reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
// },
// lam: {
// rate: ratesList.rate_lam || 0,
// hours: jobLines
// .filter((item) => item.mod_lbr_ty === "LAM")
// .reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
// },
// lar: {
// rate: ratesList.rate_lar || 0,
// hours: jobLines
// .filter((item) => item.mod_lbr_ty === "LAR")
// .reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
// },
// las: {
// rate: ratesList.rate_las || 0,
// hours: jobLines
// .filter((item) => item.mod_lbr_ty === "LAS")
// .reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
// },
// lau: {
// rate: ratesList.rate_lau || 0,
// hours: jobLines
// .filter((item) => item.mod_lbr_ty === "LAU")
// .reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
// },
// atp: {
// rate: shoprates.rate_atp || 0,
// hours:
// jobLines.filter((item) => item.line_desc.includes("ATS Amount"))
// .length > 0
// ? jobLines
// .filter(
// (item) =>
// item.mod_lbr_ty !== "LA1" &&
// item.mod_lbr_ty !== "LA2" &&
// item.mod_lbr_ty !== "LA3" &&
// item.mod_lbr_ty !== "LA4" &&
// item.mod_lbr_ty !== "LAU" &&
// item.mod_lbr_ty !== "LAG" &&
// item.mod_lbr_ty !== "LAS" &&
// item.mod_lbr_ty !== "LAA"
// )
// .reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0)
// : 0,
// },
// mapa: {
// rate: ratesList.rate_mapa || 0,
// hours: jobLines
// .filter((item) => item.mod_lbr_ty === "LAR")
// .reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
// },
// mash: {
// rate: ratesList.rate_mash || 0,
// hours: jobLines
// .filter((item) => item.mod_lbr_ty !== "LAR")
// .reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
// },
// };
let subtotal = Dinero({ amount: 0 });
let rates_subtotal = Dinero({ amount: 0 });
for (const property in ret) {
ret[property].total = Dinero({ amount: ret[property].rate * 100 })
.multiply(ret[property].hours)
.divide(10);
subtotal = subtotal.add(ret[property].total);
if (
property !== "mapa" &&
property !== "mash"
//&& property !== "rate_atp"
)
rates_subtotal = rates_subtotal.add(ret[property].total);
}
ret.subtotal = subtotal;
ret.rates_subtotal = rates_subtotal;
// let subtotal = Dinero({ amount: 0 });
// let rates_subtotal = Dinero({ amount: 0 });
// for (const property in ret) {
// ret[property].total = Dinero({ amount: ret[property].rate * 100 })
// .multiply(ret[property].hours)
// .divide(10);
// subtotal = subtotal.add(ret[property].total);
// if (
// property !== "mapa" &&
// property !== "mash"
// //&& property !== "rate_atp"
// )
// rates_subtotal = rates_subtotal.add(ret[property].total);
// }
// ret.subtotal = subtotal;
// ret.rates_subtotal = rates_subtotal;
return ret;
}
// return ret;
// }
function CalculatePartsTotals(jobLines) {
const ret = jobLines.reduce(
(acc, value) => {
switch (value.part_type) {
case "PAS":
case "PASL":
return {
...acc,
sublets: {
...acc.sublets,
subtotal: acc.sublets.subtotal.add(
Dinero({ amount: Math.round(value.act_price * 100) })
),
//TODO Add Adjustments in
},
};
// case "PAA":
// case "PAC":
// case "PAG":
// case "PAL":
// case "PAM":
// case "PAN":
// case "PAO":
// case "PAP":
// case "PAR":
default:
return {
...acc,
parts: {
...acc.parts,
list: {
...acc.parts.list,
[value.part_type]:
acc.parts.list[value.part_type] &&
acc.parts.list[value.part_type].total
? {
total: acc.parts.list[value.part_type].total.add(
Dinero({
amount: Math.round((value.act_price || 0) * 100),
}).multiply(value.part_qty || 1)
),
}
: {
total: Dinero({
amount: Math.round((value.act_price || 0) * 100),
}).multiply(value.part_qty || 1),
},
},
subtotal: acc.parts.subtotal.add(
Dinero({ amount: Math.round(value.act_price * 100) }).multiply(
value.part_qty
)
),
//TODO Add Adjustments in
},
};
// default:
// return acc;
}
},
{
parts: {
list: {},
subtotal: Dinero({ amount: 0 }),
adjustments: Dinero({ amount: 0 }),
total: Dinero({ amount: 0 }),
},
sublets: {
subtotal: Dinero({ amount: 0 }),
adjustments: Dinero({ amount: 0 }),
total: Dinero({ amount: 0 }),
},
}
);
// function CalculatePartsTotals(jobLines) {
// const ret = jobLines.reduce(
// (acc, value) => {
// switch (value.part_type) {
// case "PAS":
// case "PASL":
// return {
// ...acc,
// sublets: {
// ...acc.sublets,
// subtotal: acc.sublets.subtotal.add(
// Dinero({ amount: Math.round(value.act_price * 100) })
// ),
// //TODO Add Adjustments in
// },
// };
// // case "PAA":
// // case "PAC":
// // case "PAG":
// // case "PAL":
// // case "PAM":
// // case "PAN":
// // case "PAO":
// // case "PAP":
// // case "PAR":
// default:
// if (value.part_type === null) return acc;
// return {
// ...acc,
// parts: {
// ...acc.parts,
// list: {
// ...acc.parts.list,
// [value.part_type]:
// acc.parts.list[value.part_type] &&
// acc.parts.list[value.part_type].total
// ? {
// total: acc.parts.list[value.part_type].total.add(
// Dinero({
// amount: Math.round((value.act_price || 0) * 100),
// }).multiply(value.part_qty || 1)
// ),
// }
// : {
// total: Dinero({
// amount: Math.round((value.act_price || 0) * 100),
// }).multiply(value.part_qty || 1),
// },
// },
// subtotal: acc.parts.subtotal.add(
// Dinero({ amount: Math.round(value.act_price * 100) }).multiply(
// value.part_qty
// )
// ),
// //TODO Add Adjustments in
// },
// };
// // default:
// // return acc;
// }
// },
// {
// parts: {
// list: {},
// subtotal: Dinero({ amount: 0 }),
// adjustments: Dinero({ amount: 0 }),
// total: Dinero({ amount: 0 }),
// },
// sublets: {
// subtotal: Dinero({ amount: 0 }),
// adjustments: Dinero({ amount: 0 }),
// total: Dinero({ amount: 0 }),
// },
// }
// );
return {
parts: {
...ret.parts,
total: ret.parts.subtotal, //+ ret.parts.adjustments
},
sublets: {
...ret.sublets,
total: ret.sublets.subtotal, // + ret.sublets.adjustments,
},
};
}
// return {
// parts: {
// ...ret.parts,
// total: ret.parts.subtotal, //+ ret.parts.adjustments
// },
// sublets: {
// ...ret.sublets,
// total: ret.sublets.subtotal, // + ret.sublets.adjustments,
// },
// };
// }
function CalculateCustPayable(job) {
let ret = {
deductible: Dinero({ amount: (job.ded_amt || 0) * 100 }) || 0,
federal_tax: Dinero({ amount: (job.federal_tax_payable || 0) * 100 }), //TODO Should this be renamed to make it more clear this is customer GST?
other_customer_amount: Dinero({
amount: (job.other_amount_payable || 0) * 100,
}),
dep_taxes: Dinero({ amount: job.depreciation_taxes || 0 }),
};
// function CalculateCustPayable(job) {
// let ret = {
// deductible: Dinero({ amount: (job.ded_amt || 0) * 100 }) || 0,
// federal_tax: Dinero({ amount: (job.federal_tax_payable || 0) * 100 }), //TODO Should this be renamed to make it more clear this is customer GST?
// other_customer_amount: Dinero({
// amount: (job.other_amount_payable || 0) * 100,
// }),
// dep_taxes: Dinero({ amount: job.depreciation_taxes || 0 }),
// };
ret.total = ret.deductible
.add(ret.federal_tax)
.add(ret.federal_tax)
.add(ret.other_customer_amount)
.add(ret.dep_taxes);
// ret.total = ret.deductible
// .add(ret.federal_tax)
// .add(ret.federal_tax)
// .add(ret.other_customer_amount)
// .add(ret.dep_taxes);
return ret;
}
// return ret;
// }

View File

@@ -7,7 +7,7 @@ import { Button, notification, Table, Input } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import { TimeAgoFormatter } from "../../utils/DateFormatter";
import { alphaSort } from "../../utils/sorters";
import OwnerFindModalContainer from "../owner-find-modal/owner-find-modal.container";
@@ -109,7 +109,7 @@ export default function JobsAvailableComponent({
sortOrder:
state.sortedInfo.columnKey === "updated_at" && state.sortedInfo.order,
render: (text, record) => (
<DateTimeFormatter>{record.updated_at}</DateTimeFormatter>
<TimeAgoFormatter>{record.updated_at}</TimeAgoFormatter>
),
//width: "12%",
//ellipsis: true

View File

@@ -1,11 +1,13 @@
import { useMutation, useQuery } from "@apollo/react-hooks";
import { notification } from "antd";
import Axios from "axios";
import Dinero from "dinero.js";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useHistory } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { CalculateJob } from "../../components/job-totals-table/job-totals.utility";
import { logImEXEvent } from "../../firebase/firebase.utils";
import {
DELETE_ALL_AVAILABLE_NEW_JOBS,
QUERY_AVAILABLE_NEW_JOBS,
@@ -15,7 +17,6 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import JobsAvailableComponent from "./jobs-available-new.component";
import { logImEXEvent } from "../../firebase/firebase.utils";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -40,7 +41,7 @@ export function JobsAvailableContainer({
const [insertNewJob] = useMutation(INSERT_NEW_JOB);
const [loadEstData, estData] = estDataLazyLoad;
const onModalOk = () => {
const onModalOk = async () => {
logImEXEvent("job_import_new");
setModalVisible(false);
@@ -59,19 +60,21 @@ export function JobsAvailableContainer({
message: t("jobs.errors.creating", { error: "No job data present." }),
});
} else {
const newTotals = CalculateJob(
{
...estData.data.available_jobs_by_pk.est_data,
joblines: estData.data.available_jobs_by_pk.est_data.joblines.data,
},
bodyshop.shoprates
);
const newTotals = (
await Axios.post("/job/totals", {
job: {
...estData.data.available_jobs_by_pk.est_data,
joblines: estData.data.available_jobs_by_pk.est_data.joblines.data,
},
shoprates: bodyshop.shoprates,
})
).data;
const newJob = {
...estData.data.available_jobs_by_pk.est_data,
clm_total: newTotals.totals.total_repairs.toFormat("0.00"),
owner_owing: newTotals.custPayable.total.toFormat("0.00"),
job_totals: JSON.stringify(newTotals),
clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"),
owner_owing: Dinero(newTotals.custPayable.total).toFormat("0.00"),
job_totals: newTotals,
};
insertNewJob({

View File

@@ -7,7 +7,7 @@ import { Button, notification, Table, Input } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import { TimeAgoFormatter } from "../../utils/DateFormatter";
import { alphaSort } from "../../utils/sorters";
import JobsFindModalContainer from "../jobs-find-modal/jobs-find-modal.container";
@@ -124,7 +124,7 @@ export default function JobsAvailableSupplementComponent({
sortOrder:
state.sortedInfo.columnKey === "updated_at" && state.sortedInfo.order,
render: (text, record) => (
<DateTimeFormatter>{record.updated_at}</DateTimeFormatter>
<TimeAgoFormatter>{record.updated_at}</TimeAgoFormatter>
),
//width: "12%",
//ellipsis: true

View File

@@ -1,12 +1,14 @@
import { useApolloClient, useMutation, useQuery } from "@apollo/react-hooks";
import { notification } from "antd";
import Axios from "axios";
import Dinero from "dinero.js";
import gql from "graphql-tag";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useHistory } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { CalculateJob } from "../../components/job-totals-table/job-totals.utility";
import { logImEXEvent } from "../../firebase/firebase.utils";
import {
DELETE_ALL_AVAILABLE_SUPPLEMENT_JOBS,
QUERY_AVAILABLE_SUPPLEMENT_JOBS,
@@ -18,7 +20,6 @@ import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import JobsAvailableSupplementComponent from "./jobs-available-supplement.component";
import { GetSupplementDelta } from "./jobs-available-supplement.estlines.util";
import HeaderFields from "./jobs-available-supplement.headerfields";
import { logImEXEvent } from "../../firebase/firebase.utils";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -67,7 +68,6 @@ export function JobsAvailableSupplementContainer({
} else {
//create upsert job
let supp = estData.data.available_jobs_by_pk.est_data;
console.log("supp before", supp);
delete supp.owner;
delete supp.vehicle;
@@ -75,13 +75,15 @@ export function JobsAvailableSupplementContainer({
HeaderFields.forEach((item) => delete supp[item]);
}
const newTotals = CalculateJob(
{
...estData.data.available_jobs_by_pk.est_data,
joblines: estData.data.available_jobs_by_pk.est_data.joblines.data,
},
bodyshop.shoprates
);
const newTotals = (
await Axios.post("/job/totals", {
job: {
...estData.data.available_jobs_by_pk.est_data,
joblines: estData.data.available_jobs_by_pk.est_data.joblines.data,
},
shoprates: bodyshop.shoprates,
})
).data;
let suppDelta = await GetSupplementDelta(
client,
@@ -100,9 +102,9 @@ export function JobsAvailableSupplementContainer({
jobId: selectedJob,
job: {
...supp,
clm_total: newTotals.totals.total_repairs.toFormat("0.00"),
owner_owing: newTotals.custPayable.total.toFormat("0.00"),
job_totals: JSON.stringify(newTotals),
clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"),
owner_owing: Dinero(newTotals.custPayable.total).toFormat("0.00"),
job_totals: newTotals,
},
},
})
@@ -140,11 +142,12 @@ export function JobsAvailableSupplementContainer({
setSelectedJob(null);
};
if (error) return <AlertComponent type='error' message={error.message} />;
if (error) return <AlertComponent type="error" message={error.message} />;
return (
<LoadingSpinner
loading={insertLoading}
message={t("jobs.labels.creating_new_job")}>
message={t("jobs.labels.creating_new_job")}
>
<JobsAvailableSupplementComponent
loading={loading}
data={data}

View File

@@ -16,7 +16,7 @@ export function JobsCloseSaveButton({
bodyshop,
suspenseAmount,
jobId,
jobTotals,
labMatAllocations,
partsAllocations,
setInvoicedState,
@@ -60,9 +60,10 @@ export function JobsCloseSaveButton({
return (
<Button
onClick={handleSave}
type='primary'
type="primary"
disabled={suspenseAmount > 0 || disabled}
loading={loading}>
loading={loading}
>
{t("general.actions.save")}
</Button>
);

View File

@@ -1,6 +1,8 @@
import { Descriptions, Statistic } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import Dinero from "dinero.js";
export default function JobsCloseTotals({
jobTotals,
labMatTotal,
@@ -17,47 +19,57 @@ export default function JobsCloseTotals({
>
<Descriptions.Item label={t("jobs.labels.partstotal")}>
<Statistic
value={jobTotals.parts.parts.total.toFormat()}
suffix={`(${jobTotals.parts.parts.subtotal.toFormat()} ± ${jobTotals.parts.parts.adjustments.toFormat()})`}
value={Dinero(jobTotals.parts.parts.total).toFormat()}
suffix={`(${Dinero(
jobTotals.parts.parts.subtotal
).toFormat()} ± ${Dinero(
jobTotals.parts.parts.adjustments
).toFormat()})`}
/>
</Descriptions.Item>
<Descriptions.Item label={t("jobs.labels.subletstotal")}>
<Statistic
value={jobTotals.parts.sublets.total.toFormat()}
suffix={`(${jobTotals.parts.sublets.subtotal.toFormat()} ± ${jobTotals.parts.sublets.adjustments.toFormat()})`}
value={Dinero(jobTotals.parts.sublets.total).toFormat()}
suffix={`(${Dinero(
jobTotals.parts.sublets.subtotal
).toFormat()} ± ${Dinero(
jobTotals.parts.sublets.adjustments
).toFormat()})`}
/>
</Descriptions.Item>
<Descriptions.Item label={t("jobs.labels.subtotal")}>
<Statistic value={jobTotals.totals.subtotal.toFormat()} />
<Statistic value={Dinero(jobTotals.totals.subtotal).toFormat()} />
</Descriptions.Item>
<Descriptions.Item label={t("jobs.labels.federal_tax_amt")}>
<Statistic value={jobTotals.totals.federal_tax.toFormat()} />
<Statistic value={Dinero(jobTotals.totals.federal_tax).toFormat()} />
</Descriptions.Item>
<Descriptions.Item label={t("jobs.labels.state_tax_amt")}>
<Statistic value={jobTotals.totals.state_tax.toFormat()} />
<Statistic value={Dinero(jobTotals.totals.state_tax).toFormat()} />
</Descriptions.Item>
<Descriptions.Item label={t("jobs.labels.local_tax_amt")}>
<Statistic value={jobTotals.totals.local_tax.toFormat()} />
<Statistic value={Dinero(jobTotals.totals.local_tax).toFormat()} />
</Descriptions.Item>
</Descriptions>
<Statistic
title={t("jobs.labels.total_repairs")}
value={jobTotals.totals.total_repairs.toFormat()}
value={Dinero(jobTotals.totals.total_repairs).toFormat()}
/>
<Statistic
title={t("jobs.labels.net_repairs")}
value={jobTotals.totals.net_repairs.toFormat()}
value={Dinero(jobTotals.totals.net_repairs).toFormat()}
/>
<Statistic
title={t("jobs.labels.suspense")}
valueStyle={{
color:
jobTotals.totals.subtotal.subtract(labMatTotal).subtract(partsTotal)
Dinero(jobTotals.totals.subtotal)
.subtract(labMatTotal)
.subtract(partsTotal)
.getAmount() === 0
? "green"
: "red",
}}
value={jobTotals.totals.subtotal
value={Dinero(jobTotals.totals.subtotal)
.subtract(labMatTotal)
.subtract(partsTotal)
.toFormat()}

View File

@@ -0,0 +1,99 @@
import { Button, notification, Popover, Form, Select } from "antd";
import React, { useState } from "react";
import { useMutation } from "react-apollo";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { CONVERT_JOB_TO_RO } from "../../graphql/jobs.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export function JobsConvertButton({ bodyshop, job, refetch }) {
const [visible, setVisible] = useState(false);
const [mutationConvertJob] = useMutation(CONVERT_JOB_TO_RO);
const { t } = useTranslation();
const handleConvert = (values) => {
mutationConvertJob({
variables: { jobId: job.id, ...values },
}).then((r) => {
refetch();
notification["success"]({
message: t("jobs.successes.converted"),
});
});
};
const popMenu = (
<div>
<Form layout="vertical" onFinish={handleConvert}>
<Form.Item
name={["ins_co_nm"]}
label={t("jobs.fields.ins_co_nm")}
rules={[
{
required: true,
message: t("general.validation.required"),
},
]}
>
<Select>
{bodyshop.md_ins_cos.map((s) => (
<Select.Option key={s} value={s}>
{s}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
name={["class"]}
label={t("jobs.fields.class")}
rules={[
{
required: bodyshop.enforce_class,
message: t("general.validation.required"),
},
]}
>
<Select>
{bodyshop.md_classes.map((s) => (
<Select.Option key={s} value={s}>
{s}
</Select.Option>
))}
</Select>
</Form.Item>
<Button type="danger" htmlType="submit">
{t("jobs.actions.convert")}
</Button>
<Button onClick={() => setVisible(false)}>
{t("general.actions.close")}
</Button>
</Form>
</div>
);
return (
<Popover visible={visible} content={popMenu}>
<Button
key="convert"
className="imex-flex-row__margin"
type="danger"
style={{ display: job.converted ? "none" : "" }}
disabled={job.converted}
onClick={() => setVisible(true)}
>
{t("jobs.actions.convert")}
</Button>
</Popover>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(JobsConvertButton);

View File

@@ -1,146 +1,191 @@
import { Collapse, Form, Input, InputNumber, Switch } from "antd";
import { Collapse, Form, Input, InputNumber, Select, Switch } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import FormDatePicker from '../form-date-picker/form-date-picker.component';
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
import FormItemEmail from "../form-items-formatted/email-form-item.component";
import FormItemPhone from "../form-items-formatted/phone-form-item.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
export default function JobsCreateJobsInfo({ form }) {
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
const { t } = useTranslation();
const { getFieldValue } = form;
return (
<div>
<Collapse defaultActiveKey='insurance'>
<Collapse defaultActiveKey="insurance">
<Collapse.Panel
key='insurance'
header={t("menus.jobsdetail.insurance")}>
key="insurance"
header={t("menus.jobsdetail.insurance")}
>
<LayoutFormRow>
<Form.Item label={t("jobs.fields.ins_co_id")} name='ins_co_id'>
<Form.Item label={t("jobs.fields.ins_co_id")} name="ins_co_id">
<Input />
</Form.Item>
<Form.Item label={t("jobs.fields.policy_no")} name='policy_no'>
<Form.Item label={t("jobs.fields.policy_no")} name="policy_no">
<Input />
</Form.Item>
<Form.Item label={t("jobs.fields.clm_no")} name='clm_no'>
<Form.Item label={t("jobs.fields.clm_no")} name="clm_no">
<Input />
</Form.Item>
<Form.Item
label={t("jobs.fields.regie_number")}
name='regie_number'>
name="regie_number"
>
<Input />
</Form.Item>
<Form.Item label={t("jobs.fields.loss_date")} name='loss_date'>
<Form.Item label={t("jobs.fields.loss_date")} name="loss_date">
<FormDatePicker />
</Form.Item>
<Form.Item label={t("jobs.fields.ins_co_nm")} name='ins_co_nm'>
<Form.Item label={t("jobs.fields.ins_co_nm")} name="ins_co_nm">
<Input />
</Form.Item>
<Form.Item label={t("jobs.fields.ins_addr1")} name='ins_addr1'>
<Form.Item label={t("jobs.fields.ins_addr1")} name="ins_addr1">
<Input />
</Form.Item>
<Form.Item label={t("jobs.fields.ins_city")} name='ins_city'>
<Form.Item label={t("jobs.fields.ins_city")} name="ins_city">
<Input />
</Form.Item>
<Form.Item label={t("jobs.fields.ins_ct_ln")} name='ins_ct_ln'>
<Form.Item label={t("jobs.fields.ins_ct_ln")} name="ins_ct_ln">
<Input />
</Form.Item>
<Form.Item label={t("jobs.fields.ins_ct_fn")} name='ins_ct_fn'>
<Form.Item label={t("jobs.fields.ins_ct_fn")} name="ins_ct_fn">
<Input />
</Form.Item>
<Form.Item label={t("jobs.fields.ins_ph1")} name='ins_ph1'>
<Form.Item label={t("jobs.fields.ins_ph1")} name="ins_ph1">
<FormItemPhone customInput={Input} />
</Form.Item>
<Form.Item
label={t("jobs.fields.ins_ea")}
name='ins_ea'
name="ins_ea"
rules={[
{
type: "email",
message: "This is not a valid email address.",
},
]}>
]}
>
<FormItemEmail email={getFieldValue("ins_ea")} />
</Form.Item>
Appraiser Info
<Form.Item label={t("jobs.fields.est_co_nm")} name='est_co_nm'>
<Form.Item label={t("jobs.fields.est_co_nm")} name="est_co_nm">
<Input />
</Form.Item>
<Form.Item label={t("jobs.fields.est_ct_fn")} name='est_ct_fn'>
<Form.Item
label={t("jobs.fields.est_ct_fn")}
name="est_ct_fn"
rules={[
{
required: selected && true,
message: t("general.validation.required"),
},
]}
>
<Input />
</Form.Item>
<Form.Item label={t("jobs.fields.est_ct_ln")} name='est_ct_ln'>
<Form.Item
label={t("jobs.fields.est_ct_ln")}
name="est_ct_ln"
rules={[
{
required: selected && true,
message: t("general.validation.required"),
},
]}
>
<Input />
</Form.Item>
<Form.Item label={t("jobs.fields.pay_date")} name='pay_date'>
<Form.Item label={t("jobs.fields.pay_date")} name="pay_date">
<Input />
</Form.Item>
<Form.Item label={t("jobs.fields.est_ph1")} name='est_ph1'>
<Form.Item label={t("jobs.fields.est_ph1")} name="est_ph1">
<Input />
</Form.Item>
<Form.Item
label={t("jobs.fields.est_ea")}
name='est_ea'
name="est_ea"
rules={[
{
type: "email",
message: "This is not a valid email address.",
},
]}>
]}
>
<FormItemEmail email={getFieldValue("est_ea")} />
</Form.Item>
<Form.Item
label={t("jobs.fields.selling_dealer")}
name='selling_dealer'>
name="selling_dealer"
>
<Input />
</Form.Item>
<Form.Item
label={t("jobs.fields.servicing_dealer")}
name='servicing_dealer'>
name="servicing_dealer"
>
<Input />
</Form.Item>
<Form.Item
label={t("jobs.fields.selling_dealer_contact")}
name='selling_dealer_contact'>
name="selling_dealer_contact"
>
<Input />
</Form.Item>
<Form.Item
label={t("jobs.fields.servicing_dealer_contact")}
name='servicing_dealer_contact'>
name="servicing_dealer_contact"
>
<Input />
</Form.Item>
</LayoutFormRow>
</Collapse.Panel>
<Collapse.Panel key='claim' header={t("menus.jobsdetail.claimdetail")}>
<Collapse.Panel key="claim" header={t("menus.jobsdetail.claimdetail")}>
<LayoutFormRow>
<Form.Item label={t("jobs.fields.csr")} name='csr'>
<Form.Item label={t("jobs.fields.csr")} name="csr">
<Input />
</Form.Item>
<Form.Item label={t("jobs.fields.loss_desc")} name='loss_desc'>
<Form.Item label={t("jobs.fields.loss_desc")} name="loss_desc">
<Input />
</Form.Item>
<Form.Item label={t("jobs.fields.ponumber")} name='po_number'>
<Form.Item label={t("jobs.fields.ponumber")} name="po_number">
<Input />
</Form.Item>
<Form.Item label={t("jobs.fields.unitnumber")} name='unit_number'>
<Form.Item label={t("jobs.fields.unitnumber")} name="unit_number">
<Input />
</Form.Item>
<Form.Item
label={t("jobs.fields.specialcoveragepolicy")}
valuePropName='checked'
name='special_coverage_policy'>
valuePropName="checked"
name="special_coverage_policy"
>
<Switch />
</Form.Item>
<Form.Item label={t("jobs.fields.kmin")} name='kmin'>
<Form.Item label={t("jobs.fields.kmin")} name="kmin">
<Input />
</Form.Item>
<Form.Item label={t("jobs.fields.kmout")} name='kmout'>
<Form.Item label={t("jobs.fields.kmout")} name="kmout">
<Input />
</Form.Item>
<Form.Item
label={t("jobs.fields.referralsource")}
name='referral_source'>
<Input />
name="referral_source"
>
<Select>
{bodyshop.md_referral_sources.map((s) => (
<Select.Option key={s} value={s}>
{s}
</Select.Option>
))}
</Select>
</Form.Item>
</LayoutFormRow>
TODO How to handle different taxes and marking them as exempt?
@@ -153,120 +198,128 @@ export default function JobsCreateJobsInfo({ form }) {
}
</Collapse.Panel>
<Collapse.Panel
key='financial'
header={t("menus.jobsdetail.financials")}>
key="financial"
header={t("menus.jobsdetail.financials")}
>
<LayoutFormRow>
<Form.Item label={t("jobs.fields.ded_amt")} name='ded_amt'>
<Form.Item label={t("jobs.fields.ded_amt")} name="ded_amt">
<InputNumber />
</Form.Item>
<Form.Item label={t("jobs.fields.ded_status")} name='ded_status'>
<Form.Item label={t("jobs.fields.ded_status")} name="ded_status">
<Input />
</Form.Item>
<Form.Item
label={t("jobs.fields.depreciation_taxes")}
name='depreciation_taxes'>
name="depreciation_taxes"
>
<InputNumber />
</Form.Item>
TODO This is equivalent of GST payable.
<Form.Item
label={t("jobs.fields.federal_tax_payable")}
name='federal_tax_payable'>
name="federal_tax_payable"
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("jobs.fields.other_amount_payable")}
name='other_amount_payable'>
name="other_amount_payable"
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("jobs.fields.towing_payable")}
name='towing_payable'>
name="towing_payable"
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("jobs.fields.storage_payable")}
name='storage_payable'>
name="storage_payable"
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("jobs.fields.adjustment_bottom_line")}
name='adjustment_bottom_line'>
name="adjustment_bottom_line"
>
<InputNumber />
</Form.Item>
Totals Table
<Form.Item
label={t("jobs.fields.labor_rate_desc")}
name='labor_rate_desc'>
name="labor_rate_desc"
>
<Input />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_lab")} name='rate_lab'>
<Form.Item label={t("jobs.fields.rate_lab")} name="rate_lab">
<InputNumber />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_lad")} name='rate_lad'>
<Form.Item label={t("jobs.fields.rate_lad")} name="rate_lad">
<InputNumber />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_lae")} name='rate_lae'>
<Form.Item label={t("jobs.fields.rate_lae")} name="rate_lae">
<InputNumber />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_lar")} name='rate_lar'>
<Form.Item label={t("jobs.fields.rate_lar")} name="rate_lar">
<InputNumber />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_las")} name='rate_las'>
<Form.Item label={t("jobs.fields.rate_las")} name="rate_las">
<InputNumber />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_laf")} name='rate_laf'>
<Form.Item label={t("jobs.fields.rate_laf")} name="rate_laf">
<InputNumber />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_lam")} name='rate_lam'>
<Form.Item label={t("jobs.fields.rate_lam")} name="rate_lam">
<InputNumber />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_lag")} name='rate_lag'>
<Form.Item label={t("jobs.fields.rate_lag")} name="rate_lag">
<InputNumber />
</Form.Item>
Note //TODO Remove ATP rate?
<Form.Item label={t("jobs.fields.rate_atp")} name='rate_atp'>
<Form.Item label={t("jobs.fields.rate_atp")} name="rate_atp">
<InputNumber />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_lau")} name='rate_lau'>
<Form.Item label={t("jobs.fields.rate_lau")} name="rate_lau">
<InputNumber />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_la1")} name='rate_la1'>
<Form.Item label={t("jobs.fields.rate_la1")} name="rate_la1">
<InputNumber />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_la2")} name='rate_la2'>
<Form.Item label={t("jobs.fields.rate_la2")} name="rate_la2">
<InputNumber />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_la3")} name='rate_la3'>
<Form.Item label={t("jobs.fields.rate_la3")} name="rate_la3">
<InputNumber />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_la4")} name='rate_la4'>
<Form.Item label={t("jobs.fields.rate_la4")} name="rate_la4">
<InputNumber />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_mapa")} name='rate_mapa'>
<Form.Item label={t("jobs.fields.rate_mapa")} name="rate_mapa">
<InputNumber />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_mash")} name='rate_mash'>
<Form.Item label={t("jobs.fields.rate_mash")} name="rate_mash">
<InputNumber />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_mahw")} name='rate_mahw'>
<Form.Item label={t("jobs.fields.rate_mahw")} name="rate_mahw">
<InputNumber />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_ma2s")} name='rate_ma2s'>
<Form.Item label={t("jobs.fields.rate_ma2s")} name="rate_ma2s">
<InputNumber />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_ma3s")} name='rate_ma3s'>
<Form.Item label={t("jobs.fields.rate_ma3s")} name="rate_ma3s">
<InputNumber />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_mabl")} name='rate_mabl'>
<Form.Item label={t("jobs.fields.rate_mabl")} name="rate_mabl">
<InputNumber />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_macs")} name='rate_macs'>
<Form.Item label={t("jobs.fields.rate_macs")} name="rate_macs">
<InputNumber />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_matd")} name='rate_matd'>
<Form.Item label={t("jobs.fields.rate_matd")} name="rate_matd">
<InputNumber />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_laa")} name='rate_laa'>
<Form.Item label={t("jobs.fields.rate_laa")} name="rate_laa">
<InputNumber />
</Form.Item>
</LayoutFormRow>
@@ -275,3 +328,4 @@ export default function JobsCreateJobsInfo({ form }) {
</div>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(JobsCreateJobsInfo);

View File

@@ -12,11 +12,11 @@ export default function JobsCreateOwnerContainer() {
skip: !state.owner.search,
});
if (error) return <AlertComponent message={error.message} type='error' />;
if (error) return <AlertComponent message={error.message} type="error" />;
return (
<JobsCreateOwnerInfoComponent
loading={loading}
owners={data ? data.search_owner : null}
owners={data ? data.search_owners : null}
/>
);
}

View File

@@ -24,19 +24,34 @@ export default function JobsCreateOwnerInfoNewComponent() {
selectedid: null,
},
});
}}>
}}
>
{t("jobs.labels.create.newowner")}
</Checkbox>
<LayoutFormRow header={t("owners.forms.name")} grow>
<Form.Item
label={t("owners.fields.ownr_ln")}
name={["owner", "data", "ownr_ln"]}>
name={["owner", "data", "ownr_ln"]}
rules={[
{
required: state.owner.new,
message: t("general.validation.required"),
},
]}
>
<Input disabled={!state.owner.new} />
</Form.Item>
<Form.Item
label={t("owners.fields.ownr_fn")}
name={["owner", "data", "ownr_fn"]}>
name={["owner", "data", "ownr_fn"]}
rules={[
{
required: state.owner.new,
message: t("general.validation.required"),
},
]}
>
<Input disabled={!state.owner.new} />
</Form.Item>
</LayoutFormRow>
@@ -44,12 +59,14 @@ export default function JobsCreateOwnerInfoNewComponent() {
<LayoutFormRow grow>
<Form.Item
label={t("owners.fields.ownr_title")}
name={["owner", "data", "ownr_title"]}>
name={["owner", "data", "ownr_title"]}
>
<Input disabled={!state.owner.new} />
</Form.Item>
<Form.Item
label={t("owners.fields.ownr_co_nm")}
name={["owner", "data", "ownr_co_nm"]}>
name={["owner", "data", "ownr_co_nm"]}
>
<Input disabled={!state.owner.new} />
</Form.Item>
</LayoutFormRow>
@@ -57,12 +74,14 @@ export default function JobsCreateOwnerInfoNewComponent() {
<LayoutFormRow header={t("owners.forms.address")} grow>
<Form.Item
label={t("owners.fields.ownr_addr1")}
name={["owner", "data", "ownr_addr1"]}>
name={["owner", "data", "ownr_addr1"]}
>
<Input disabled={!state.owner.new} />
</Form.Item>
<Form.Item
label={t("owners.fields.ownr_addr2")}
name={["owner", "data", "ownr_addr2"]}>
name={["owner", "data", "ownr_addr2"]}
>
<Input disabled={!state.owner.new} />
</Form.Item>
</LayoutFormRow>
@@ -70,24 +89,28 @@ export default function JobsCreateOwnerInfoNewComponent() {
<LayoutFormRow grow>
<Form.Item
label={t("owners.fields.ownr_city")}
name={["owner", "data", "ownr_city"]}>
name={["owner", "data", "ownr_city"]}
>
<Input disabled={!state.owner.new} />
</Form.Item>
<Form.Item
label={t("owners.fields.ownr_st")}
name={["owner", "data", "ownr_st"]}>
name={["owner", "data", "ownr_st"]}
>
<Input disabled={!state.owner.new} />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow grow>
<Form.Item
label={t("owners.fields.ownr_zip")}
name={["owner", "data", "ownr_zip"]}>
name={["owner", "data", "ownr_zip"]}
>
<Input disabled={!state.owner.new} />
</Form.Item>
<Form.Item
label={t("owners.fields.ownr_ctry")}
name={["owner", "data", "ownr_ctry"]}>
name={["owner", "data", "ownr_ctry"]}
>
<Input disabled={!state.owner.new} />
</Form.Item>
</LayoutFormRow>
@@ -102,32 +125,31 @@ export default function JobsCreateOwnerInfoNewComponent() {
},
]}
name={["owner", "data", "ownr_ea"]}
shouldUpdate>
{() => {
return (
<FormItemEmail
//email={form.getFieldValue("ownr_ea")}
disabled={!state.owner.new}
/>
);
}}
>
<FormItemEmail
//email={form.getFieldValue("ownr_ea")}
disabled={!state.owner.new}
/>
</Form.Item>
<Form.Item
label={t("owners.fields.ownr_ph1")}
name={["owner", "data", "ownr_ph1"]}>
name={["owner", "data", "ownr_ph1"]}
>
<FormItemPhone customInput={Input} disabled={!state.owner.new} />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow grow>
<Form.Item
label={t("owners.fields.preferred_contact")}
name={["owner", "data", "preferred_contact"]}>
name={["owner", "data", "preferred_contact"]}
>
<Input disabled={!state.owner.new} />
</Form.Item>
<Form.Item
label={t("owners.fields.allow_text_message")}
valuePropName='checked'
name={["owner", "data", "allow_text_message"]}>
valuePropName="checked"
name={["owner", "data", "allow_text_message"]}
>
<Switch disabled={!state.owner.new} />
</Form.Item>
</LayoutFormRow>

View File

@@ -36,6 +36,15 @@ export default function JobsCreateOwnerInfoSearchComponent({
tableState.sortedInfo.columnKey === "ownr_fn" &&
tableState.sortedInfo.order,
},
{
title: t("owners.fields.ownr_co_nm"),
dataIndex: "ownr_co_nm",
key: "ownr_co_nm",
sorter: (a, b) => alphaSort(a.ownr_co_nm, b.ownr_co_nm),
sortOrder:
tableState.sortedInfo.columnKey === "ownr_co_nm" &&
tableState.sortedInfo.order,
},
{
title: t("owners.fields.ownr_addr1"),
dataIndex: "ownr_addr1",
@@ -86,9 +95,9 @@ export default function JobsCreateOwnerInfoSearchComponent({
loading={loading}
title={() => {
return (
<div className='imex-table-header'>
<div className="imex-table-header">
<Input.Search
className='imex-table-header__search'
className="imex-table-header__search"
placeholder={t("general.labels.search")}
onSearch={(value) => {
setState({
@@ -101,11 +110,11 @@ export default function JobsCreateOwnerInfoSearchComponent({
</div>
);
}}
size='small'
size="small"
scroll={{ x: true }}
pagination={{ position: "top" }}
columns={columns}
rowKey='id'
rowKey="id"
dataSource={owners}
onChange={handleTableChange}
rowSelection={{

View File

@@ -1,66 +0,0 @@
import { Form, Input, InputNumber, Switch, Select } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import FormRow from "../layout-form-row/layout-form-row.component";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export function JobsDetailClaims({ bodyshop, job }) {
const { t } = useTranslation();
return (
<div>
<FormRow>
<Form.Item label={t("jobs.fields.csr")} name="csr">
<Input />
</Form.Item>
<Form.Item label={t("jobs.fields.ponumber")} name="po_number">
<Input />
</Form.Item>
<Form.Item label={t("jobs.fields.unitnumber")} name="unit_number">
<Input />
</Form.Item>
<Form.Item label={t("jobs.fields.kmin")} name="kmin">
<InputNumber precision={1} />
</Form.Item>
<Form.Item label={t("jobs.fields.kmout")} name="kmout">
<InputNumber precision={1} />
</Form.Item>
<Form.Item
label={t("jobs.fields.exempt")}
valuePropName="checked"
name="exempt"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.specialcoveragepolicy")}
valuePropName="checked"
name="special_coverage_policy"
>
<Switch />
</Form.Item>
</FormRow>
<Form.Item label={t("jobs.fields.referralsource")} name="referral_source">
<Select>
{bodyshop.md_referral_sources.map((s) => (
<Select.Option key={s} value={s}>
{s}
</Select.Option>
))}
</Select>
</Form.Item>
</div>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(JobsDetailClaims);

View File

@@ -1,215 +0,0 @@
import { Col, Divider, Form, Row, Select } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import JobTotalsTable from "../job-totals-table/job-totals-table.component";
import FormRow from "../layout-form-row/layout-form-row.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const stripeTestEnv = process.env.REACT_APP_STRIPE_PUBLIC_KEY; //.includes("test");
export function JobsDetailFinancials({ job, bodyshop }) {
const { t } = useTranslation();
const colSpan = {
sm: { span: 24 },
lg: { span: 12 },
};
return (
<div>
<Row>
<Col span={24}>
<FormRow header={t("payments.labels.title")}>
<table style={{ width: "100%" }}>
<thead>
<tr>
<th>{t("payments.fields.created_at")}</th>
<th>{t("payments.fields.payer")}</th>
<th>{t("payments.fields.amount")}</th>
<th>{t("payments.fields.memo")}</th>
<th>{t("payments.fields.type")}</th>
<th>{t("payments.fields.transactionid")}</th>
<th>{t("payments.fields.stripeid")}</th>
</tr>
</thead>
<tbody>
{job.payments.map((p, idx) => (
<tr key={idx}>
<td>
<DateTimeFormatter>{p.created_at}</DateTimeFormatter>
</td>
<td>{p.payer}</td>
<td>
<CurrencyFormatter>{p.amount}</CurrencyFormatter>
</td>
<td>{p.memo}</td>
<td>{p.type}</td>
<td>{p.transactionid}</td>
<td>
{p.stripeid ? (
<a
href={
stripeTestEnv
? `https://dashboard.stripe.com/${bodyshop.stripe_acct_id}/test/payments/${p.stripeid}`
: `https://dashboard.stripe.com/${bodyshop.stripe_acct_id}/payments/${p.stripeid}`
}
>
{p.stripeid}
</a>
) : null}
</td>
</tr>
))}
</tbody>
</table>
</FormRow>
</Col>
</Row>
<Divider />
<Row gutter={[32, 32]}>
<Col {...colSpan}>
<FormRow header={t("jobs.forms.dedinfo")}>
<Form.Item label={t("jobs.fields.ded_status")} name="ded_status">
<Select>
<Select.Option value="W">
{t("jobs.labels.deductible.waived")}
</Select.Option>
<Select.Option value="Y">
{t("jobs.labels.deductible.yes")}
</Select.Option>
<Select.Option value="N">
{t("jobs.labels.deductible.no")}
</Select.Option>
</Select>
</Form.Item>
<Form.Item label={t("jobs.fields.ded_amt")} name="ded_amt">
<CurrencyInput />
</Form.Item>
</FormRow>
<FormRow>
<Form.Item
label={t("jobs.fields.depreciation_taxes")}
name="depreciation_taxes"
>
<CurrencyInput />
</Form.Item>
<Form.Item
label={t("jobs.fields.other_amount_payable")}
name="other_amount_payable"
>
<CurrencyInput />
</Form.Item>
<Form.Item
label={t("jobs.fields.towing_payable")}
name="towing_payable"
>
<CurrencyInput />
</Form.Item>
<Form.Item
label={t("jobs.fields.storage_payable")}
name="storage_payable"
>
<CurrencyInput />
</Form.Item>
<Form.Item
label={t("jobs.fields.federal_tax_payable")}
name="federal_tax_payable"
>
<CurrencyInput />
</Form.Item>
<Form.Item
label={t("jobs.fields.adjustment_bottom_line")}
name="adjustment_bottom_line"
>
<CurrencyInput />
</Form.Item>
</FormRow>
<FormRow header={t("jobs.forms.laborrates")}>
<Form.Item label={t("jobs.fields.rate_laa")} name="rate_laa">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_lab")} name="rate_lab">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_lad")} name="rate_lad">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_lae")} name="rate_lae">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_lar")} name="rate_lar">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_las")} name="rate_las">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_laf")} name="rate_laf">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_lam")} name="rate_lam">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_lag")} name="rate_lag">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_la1")} name="rate_la1">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_la2")} name="rate_la2">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_la3")} name="rate_la3">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_la4")} name="rate_la4">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_atp")} name="rate_atp">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_lau")} name="rate_lau">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_mapa")} name="rate_mapa">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_mash")} name="rate_mash">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_mahw")} name="rate_mahw">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_ma2s")} name="rate_ma2s">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_ma3s")} name="rate_ma3s">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_mabl")} name="rate_mabl">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_macs")} name="rate_macs">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_matd")} name="rate_matd">
<CurrencyInput />
</Form.Item>
</FormRow>
</Col>
<Col {...colSpan}>
<JobTotalsTable job={job} />
</Col>
</Row>
</div>
);
}
export default connect(mapStateToProps, null)(JobsDetailFinancials);

View File

@@ -1,19 +1,58 @@
import { Form, Input } from "antd";
import { Col, Form, Input, InputNumber, Row, Select, Switch } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import FormItemEmail from "../form-items-formatted/email-form-item.component";
import FormItemPhone from "../form-items-formatted/phone-form-item.component";
import Car from "../job-damage-visual/job-damage-visual.component";
import FormRow from "../layout-form-row/layout-form-row.component";
export default function JobsDetailInsurance({ job, form }) {
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
const lossColFields = { sm: { span: 24 }, md: { span: 18 }, lg: { span: 20 } };
const lossColDamage = { sm: { span: 24 }, md: { span: 6 }, lg: { span: 4 } };
export function JobsDetailGeneral({ bodyshop, job, form }) {
const { getFieldValue } = form;
const { t } = useTranslation();
return (
<div>
<FormRow header={t("jobs.forms.inscoinfo")}>
<FormRow header={t("jobs.forms.claiminfo")}>
<Form.Item label={t("jobs.fields.clm_no")} name="clm_no">
<Input />
</Form.Item>
<Form.Item label={t("jobs.fields.ded_status")} name="ded_status">
<Select>
<Select.Option value="W">
{t("jobs.labels.deductible.waived")}
</Select.Option>
<Select.Option value="Y">
{t("jobs.labels.deductible.yes")}
</Select.Option>
<Select.Option value="N">
{t("jobs.labels.deductible.no")}
</Select.Option>
</Select>
</Form.Item>
<Form.Item label={t("jobs.fields.ded_amt")} name="ded_amt">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.policy_no")} name="policy_no">
<Input />
</Form.Item>
<Form.Item label={t("jobs.fields.regie_number")} name="regie_number">
<Input />
</Form.Item>
<Form.Item label={t("jobs.fields.ins_co_id")} name="ins_co_id">
<Input />
</Form.Item>
@@ -47,36 +86,67 @@ export default function JobsDetailInsurance({ job, form }) {
>
<FormItemEmail email={getFieldValue("ins_ea")} />
</Form.Item>
</FormRow>
<FormRow header={t("jobs.forms.claiminfo")}>
<Form.Item label={t("jobs.fields.clm_no")} name="clm_no">
<Input />
</Form.Item>
<Form.Item label={t("jobs.fields.policy_no")} name="policy_no">
<Input />
</Form.Item>
<Form.Item label={t("jobs.fields.regie_number")} name="regie_number">
<Input />
<Form.Item
label={t("jobs.fields.referralsource")}
name="referral_source"
>
<Select>
{bodyshop.md_referral_sources.map((s) => (
<Select.Option key={s} value={s}>
{s}
</Select.Option>
))}
</Select>
</Form.Item>
</FormRow>
<FormRow header={t("jobs.forms.lossinfo")}>
<div style={{ display: "inline", height: "8rem" }}>
<Row gutter={[16, 16]}>
<Col {...lossColFields}>
<FormRow header={t("jobs.forms.lossinfo")}>
<Form.Item label={t("jobs.fields.loss_desc")} name="loss_desc">
<Input />
</Form.Item>
<Form.Item label={t("jobs.fields.loss_date")} name="loss_date">
<FormDatePicker />
</Form.Item>
<Form.Item label={t("jobs.fields.kmin")} name="kmin">
<InputNumber precision={1} min={0} />
</Form.Item>
<Form.Item label={t("jobs.fields.kmout")} name="kmout">
<InputNumber precision={1} min={0} />
</Form.Item>
<Form.Item label={t("jobs.fields.ponumber")} name="po_number">
<Input />
</Form.Item>
<Form.Item label={t("jobs.fields.unitnumber")} name="unit_number">
<Input />
</Form.Item>
<Form.Item
label={t("jobs.fields.specialcoveragepolicy")}
valuePropName="checked"
name="special_coverage_policy"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.tax_registration_number")}
name="tax_registration_number"
>
<Input />
</Form.Item>
</FormRow>
</Col>
<Col {...lossColDamage}>
{job.area_of_damage ? (
<Car
dmg1={job.area_of_damage.impact1 || null}
dmg2={job.area_of_damage.impact2 || null}
dmg1={job.area_of_damage.impact1}
dmg2={job.area_of_damage.impact2}
/>
) : (
t("jobs.errors.nodamage")
)}
</div>
<Form.Item label={t("jobs.fields.loss_desc")} name="loss_desc">
<Input />
</Form.Item>
<Form.Item label={t("jobs.fields.loss_date")} name="loss_date">
<FormDatePicker />
</Form.Item>
</FormRow>
</Col>
</Row>
<FormRow header={t("jobs.forms.appraiserinfo")}>
<Form.Item label={t("jobs.fields.est_co_nm")} name="est_co_nm">
<Input />
@@ -103,10 +173,21 @@ export default function JobsDetailInsurance({ job, form }) {
<FormItemEmail email={getFieldValue("est_ea")} />
</Form.Item>
</FormRow>
<FormRow header="TODO: TO BE PLACED">
<Form.Item label={t("jobs.fields.pay_date")} name="pay_date">
<FormRow header={t("jobs.forms.other")}>
<Form.Item label={t("jobs.fields.csr")} name="csr">
<Input />
</Form.Item>
<Form.Item label={t("jobs.fields.category")} name="category">
<Select>
{bodyshop.md_categories.map((s) => (
<Select.Option key={s} value={s}>
{s}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
label={t("jobs.fields.selling_dealer")}
name="selling_dealer"
@@ -131,8 +212,8 @@ export default function JobsDetailInsurance({ job, form }) {
>
<Input />
</Form.Item>
TODO: Adding servicing/selling dealer contact info?
</FormRow>
</div>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(JobsDetailGeneral);

View File

@@ -41,7 +41,7 @@ export function JobsDetailHeaderActions({
const client = useApolloClient();
const history = useHistory();
const statusmenu = (
<Menu key='popovermenu'>
<Menu key="popovermenu">
<Menu.Item
onClick={() => {
logImEXEvent("job_header_schedule");
@@ -53,16 +53,21 @@ export function JobsDetailHeaderActions({
job: job,
},
});
}}>
}}
>
{t("jobs.actions.schedule")}
</Menu.Item>
<Menu.Item>
<Link to={`/manage/jobs/${job.id}/intake`}>
{t("jobs.actions.intake")}
</Link>
<Menu.Item disabled={!!job.intakechecklist}>
{!!job.intakechecklist ? (
t("jobs.actions.intake")
) : (
<Link to={`/manage/jobs/${job.id}/intake`}>
{t("jobs.actions.intake")}
</Link>
)}
</Menu.Item>
<Menu.Item
key='enterpayments'
key="enterpayments"
onClick={() => {
logImEXEvent("job_header_enter_payment");
@@ -70,29 +75,32 @@ export function JobsDetailHeaderActions({
actions: {},
context: { jobId: job.id },
});
}}>
}}
>
{t("menus.header.enterpayment")}
</Menu.Item>
<Menu.Item key='cccontract'>
<Menu.Item key="cccontract">
<Link
to={{
pathname: "/manage/courtesycars/contracts/new",
state: { jobId: job.id },
}}>
}}
>
{t("menus.jobsactions.newcccontract")}
</Link>
</Menu.Item>
<Menu.Item
key='addtoproduction'
key="addtoproduction"
disabled={!!!job.converted || !!job.inproduction}
onClick={() => AddToProduction(client, job.id, refetch)}>
onClick={() => AddToProduction(client, job.id, refetch)}
>
{t("jobs.actions.addtoproduction")}
</Menu.Item>
<Menu.Item key='duplicatejob'>
<Menu.Item key="duplicatejob">
<Popconfirm
title={t("jobs.labels.duplicateconfirm")}
okText='Yes'
cancelText='No'
okText="Yes"
cancelText="No"
onClick={(e) => e.stopPropagation()}
onConfirm={() =>
DuplicateJob(
@@ -104,12 +112,13 @@ export function JobsDetailHeaderActions({
}
)
}
getPopupContainer={(trigger) => trigger.parentNode}>
getPopupContainer={(trigger) => trigger.parentNode}
>
{t("menus.jobsactions.duplicate")}
</Popconfirm>
</Menu.Item>
<Menu.Item
key='postinvoices'
key="postinvoices"
onClick={() => {
logImEXEvent("job_header_enter_invoice");
@@ -119,20 +128,22 @@ export function JobsDetailHeaderActions({
job: job,
},
});
}}>
}}
>
{t("jobs.actions.postInvoices")}
</Menu.Item>
<Menu.Item key='closejob'>
<Menu.Item key="closejob">
<Link
to={{
pathname: `/manage/jobs/${job.id}/close`,
}}>
}}
>
{t("menus.jobsactions.closejob")}
</Link>
</Menu.Item>
<JobsDetaiLheaderCsi job={job} />
<Menu.Item
key='jobcosting'
key="jobcosting"
onClick={() => {
logImEXEvent("job_header_job_costing");
@@ -142,17 +153,19 @@ export function JobsDetailHeaderActions({
jobId: job.id,
},
});
}}>
}}
>
{t("jobs.labels.jobcosting")}
</Menu.Item>
</Menu>
);
return (
<Dropdown
className='imex-flex-row__margin'
className="imex-flex-row__margin"
overlay={statusmenu}
trigger={["click"]}
key='changestatus'>
key="changestatus"
>
<Button>
{t("general.labels.actions")} <DownCircleFilled />
</Button>

View File

@@ -39,8 +39,6 @@ export default function DuplicateJob(
variables: { job: [existingJob] },
})
.then((res2) => {
console.log("res2", res2);
if (completionCallback)
completionCallback(res2.data.insert_jobs.returning[0].id);
});

View File

@@ -1,27 +1,17 @@
import { DownCircleFilled, PrinterFilled } from "@ant-design/icons";
import {
Button,
Checkbox,
Descriptions,
Dropdown,
Menu,
notification,
PageHeader,
Tag,
} from "antd";
import { Button, Dropdown, Menu, PageHeader, Tag } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import Moment from "react-moment";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import JobEmployeeAssignments from "../job-employee-assignments/job-employee-assignments.container";
import JobsConvertButton from "../jobs-convert-button/jobs-convert-button.component";
import JobsDetailHeaderActions from "../jobs-detail-header-actions/jobs-detail-header-actions.component";
import OwnerTagPopoverComponent from "../owner-tag-popover/owner-tag-popover.component";
import VehicleTagPopoverComponent from "../vehicle-tag-popover/vehicle-tag-popover.component";
import JobEmployeeAssignments from "../job-employee-assignments/job-employee-assignments.container";
import "./jobs-detail-header.styles.scss";
const mapStateToProps = createStructuredSelector({
@@ -35,12 +25,13 @@ const mapDispatchToProps = (dispatch) => ({
export function JobsDetailHeader({
job,
mutationConvertJob,
refetch,
setPrintCenterContext,
bodyshop,
updateJobStatus,
setScheduleContext,
loading,
form,
}) {
const { t } = useTranslation();
@@ -48,7 +39,8 @@ export function JobsDetailHeader({
<Menu
onClick={(e) => {
updateJobStatus(e.key);
}}>
}}
>
{bodyshop.md_ro_statuses.statuses.map((item) => (
<Menu.Item key={item}>{item}</Menu.Item>
))}
@@ -56,19 +48,20 @@ export function JobsDetailHeader({
);
const menuExtra = (
<div className='imex-flex-row'>
<div className="imex-flex-row">
<Dropdown
className='imex-flex-row__margin'
className="imex-flex-row__margin"
overlay={statusmenu}
trigger={["click"]}
key='changestatus'>
key="changestatus"
>
<Button>
{t("jobs.actions.changestatus")} <DownCircleFilled />
</Button>
</Dropdown>
<Button
className='imex-flex-row__margin'
className="imex-flex-row__margin"
onClick={() => {
setPrintCenterContext({
actions: { refetch: refetch },
@@ -78,35 +71,19 @@ export function JobsDetailHeader({
},
});
}}
key='printing'>
key="printing"
>
<PrinterFilled />
{t("jobs.actions.printCenter")}
</Button>
<JobsConvertButton job={job} refetch={refetch} />
<JobsDetailHeaderActions key="actions" job={job} refetch={refetch} />
<Button
key='convert'
className='imex-flex-row__margin'
type='danger'
style={{ display: job.converted ? "none" : "" }}
disabled={job.converted}
onClick={() => {
mutationConvertJob({
variables: { jobId: job.id },
}).then((r) => {
refetch();
notification["success"]({
message: t("jobs.successes.converted"),
});
});
}}>
{t("jobs.actions.convert")}
</Button>
<JobsDetailHeaderActions key='actions' job={job} refetch={refetch} />
<Button
type='primary'
className='imex-flex-row__margin'
key='submit'
htmlType='submit'>
type="primary"
loading={loading}
className="imex-flex-row__margin"
onClick={() => form.submit()}
>
{t("general.actions.save")}
</Button>
</div>
@@ -121,59 +98,26 @@ export function JobsDetailHeader({
}
subTitle={job.status}
tags={[
<OwnerTagPopoverComponent key='owner' job={job} />,
<VehicleTagPopoverComponent key='vehicle' job={job} />,
<OwnerTagPopoverComponent key="owner" job={job} />,
<VehicleTagPopoverComponent key="vehicle" job={job} />,
<Tag
color='#f50'
key='production'
style={{ display: job.inproduction ? "" : "none" }}>
color="#f50"
key="production"
style={{ display: job.inproduction ? "" : "none" }}
>
{t("jobs.labels.inproduction")}
</Tag>,
]}
extra={menuExtra}>
<Descriptions
size='small'
column={{ xs: 1, sm: 1, md: 2, lg: 3, xl: 3, xxl: 6 }}>
<Descriptions.Item key='total' label={t("jobs.fields.repairtotal")}>
<Tag title={t("jobs.fields.repairtotal")} key="total" color="green">
<CurrencyFormatter>{job.clm_total}</CurrencyFormatter>
</Descriptions.Item>
<Descriptions.Item
key='custowing'
label={t("jobs.fields.customerowing")}>
<span style={{ margin: "0rem .5rem" }}>/</span>
<CurrencyFormatter>{job.owner_owing}</CurrencyFormatter>
</Descriptions.Item>
<Descriptions.Item
key='scp'
label={t("jobs.fields.specialcoveragepolicy")}>
<Checkbox checked={job.special_coverage_policy} />
</Descriptions.Item>
<Descriptions.Item
key='sched_comp'
label={t("jobs.fields.scheduled_completion")}>
{job.scheduled_completion ? (
<Moment format='MM/DD/YYYY'>{job.scheduled_completion}</Moment>
) : null}
</Descriptions.Item>
<Descriptions.Item key='servicecar' label={t("jobs.fields.servicecar")}>
{job.cccontracts &&
job.cccontracts.map((item) => (
<Link
key={item.id}
to={`/manage/courtesycars/contracts/${item.id}`}>
<div>{`${item.agreementnumber} - ${item.start} - ${item.scheduledreturn}`}</div>
</Link>
))}
</Descriptions.Item>
<Descriptions.Item
key='assignments'
label={t("jobs.labels.employeeassignments")}>
<JobEmployeeAssignments job={job} />
</Descriptions.Item>
</Descriptions>
</Tag>,
]}
extra={menuExtra}
>
<div style={{ display: "flex", justifyContent: "flex-end" }}>
<JobEmployeeAssignments job={job} />
</div>
</PageHeader>
);
}

View File

@@ -0,0 +1,134 @@
import { Form, Select } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import FormRow from "../layout-form-row/layout-form-row.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
export function JobsDetailRates({ job, bodyshop }) {
const { t } = useTranslation();
return (
<div>
<FormRow>
<Form.Item label={t("jobs.fields.class")} name="class">
<Select disabled={true} />
</Form.Item>
<Form.Item
label={t("jobs.fields.depreciation_taxes")}
name="depreciation_taxes"
>
<CurrencyInput />
</Form.Item>
<Form.Item
label={t("jobs.fields.other_amount_payable")}
name="other_amount_payable"
>
<CurrencyInput />
</Form.Item>
<Form.Item
label={t("jobs.fields.towing_payable")}
name="towing_payable"
>
<CurrencyInput />
</Form.Item>
<Form.Item
label={t("jobs.fields.storage_payable")}
name="storage_payable"
>
<CurrencyInput />
</Form.Item>
<Form.Item
label={t("jobs.fields.federal_tax_payable")}
name="federal_tax_payable"
>
<CurrencyInput />
</Form.Item>
<Form.Item
label={t("jobs.fields.adjustment_bottom_line")}
name="adjustment_bottom_line"
>
<CurrencyInput />
</Form.Item>
</FormRow>
<FormRow header={t("jobs.forms.laborrates")}>
<Form.Item label={t("jobs.fields.rate_laa")} name="rate_laa">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_lab")} name="rate_lab">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_lad")} name="rate_lad">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_lae")} name="rate_lae">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_lar")} name="rate_lar">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_las")} name="rate_las">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_laf")} name="rate_laf">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_lam")} name="rate_lam">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_lag")} name="rate_lag">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_la1")} name="rate_la1">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_la2")} name="rate_la2">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_la3")} name="rate_la3">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_la4")} name="rate_la4">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_atp")} name="rate_atp">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_lau")} name="rate_lau">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_mapa")} name="rate_mapa">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_mash")} name="rate_mash">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_mahw")} name="rate_mahw">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_ma2s")} name="rate_ma2s">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_ma3s")} name="rate_ma3s">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_mabl")} name="rate_mabl">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_macs")} name="rate_macs">
<CurrencyInput />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_matd")} name="rate_matd">
<CurrencyInput />
</Form.Item>
</FormRow>
</div>
);
}
export default connect(mapStateToProps, null)(JobsDetailRates);

View File

@@ -0,0 +1,74 @@
import { Divider, Typography } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import JobTotalsTable from "../job-totals-table/job-totals-table.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const stripeTestEnv = process.env.REACT_APP_STRIPE_PUBLIC_KEY; //.includes("test");
export function JobsDetailTotals({ job, bodyshop }) {
const { t } = useTranslation();
return (
<div>
<Typography.Title level={4}>
{t("payments.labels.title")}
</Typography.Title>
<table style={{ width: "100%" }}>
<thead>
<tr>
<th>{t("payments.fields.created_at")}</th>
<th>{t("payments.fields.payer")}</th>
<th>{t("payments.fields.amount")}</th>
<th>{t("payments.fields.memo")}</th>
<th>{t("payments.fields.type")}</th>
<th>{t("payments.fields.transactionid")}</th>
<th>{t("payments.fields.stripeid")}</th>
</tr>
</thead>
<tbody>
{job.payments.map((p, idx) => (
<tr key={idx}>
<td>
<DateTimeFormatter>{p.created_at}</DateTimeFormatter>
</td>
<td>{p.payer}</td>
<td>
<CurrencyFormatter>{p.amount}</CurrencyFormatter>
</td>
<td>{p.memo}</td>
<td>{p.type}</td>
<td>{p.transactionid}</td>
<td>
{p.stripeid ? (
<a
href={
stripeTestEnv
? `https://dashboard.stripe.com/${bodyshop.stripe_acct_id}/test/payments/${p.stripeid}`
: `https://dashboard.stripe.com/${bodyshop.stripe_acct_id}/payments/${p.stripeid}`
}
>
{p.stripeid}
</a>
) : null}
</td>
</tr>
))}
</tbody>
</table>
<Divider />
<JobTotalsTable job={job} />
</div>
);
}
export default connect(mapStateToProps, null)(JobsDetailTotals);

View File

@@ -38,7 +38,6 @@ export function JobsExportAllButton({
},
}
);
console.log("handle -> XML", QbXmlResponse);
} catch (error) {
console.log("Error getting QBXML from Server.", error);
notification["error"]({

View File

@@ -43,11 +43,15 @@ export default function JobsFindModalComponent({
render: (text, record) => {
return record.owner ? (
<Link to={"/manage/owners/" + record.owner.id}>
{record.ownr_fn} {record.ownr_ln}
{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${
record.ownr_co_nm || ""
}`}
</Link>
) : (
// t("jobs.errors.noowner")
<span>{`${record.ownr_fn} ${record.ownr_ln}`}</span>
<span>{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${
record.ownr_co_nm || ""
}`}</span>
);
},
},
@@ -143,7 +147,8 @@ export default function JobsFindModalComponent({
<Button
onClick={() => {
jobsListRefetch();
}}>
}}
>
<SyncOutlined />
</Button>
<Input
@@ -154,10 +159,10 @@ export default function JobsFindModalComponent({
/>
</div>
)}
size='small'
size="small"
pagination={{ position: "bottom" }}
columns={columns.map((item) => ({ ...item }))}
rowKey='id'
rowKey="id"
loading={jobsListLoading}
dataSource={jobsList}
rowSelection={{
@@ -183,7 +188,8 @@ export default function JobsFindModalComponent({
...importOptions,
overrideHeaders: e.target.checked,
})
}>
}
>
{t("jobs.labels.override_header")}
</Checkbox>
</div>

View File

@@ -55,10 +55,14 @@ export default function JobsList({ refetch, loading, jobs, total }) {
render: (text, record) => {
return record.owner ? (
<Link to={"/manage/owners/" + record.owner.id}>
{`${record.ownr_fn || ""} ${record.ownr_ln || ""}`}
{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${
record.ownr_co_nm || ""
}`}
</Link>
) : (
<span>{`${record.ownr_fn || ""} ${record.ownr_ln || ""}`}</span>
<span>{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${
record.ownr_co_nm || ""
}`}</span>
);
},
},
@@ -176,7 +180,7 @@ export default function JobsList({ refetch, loading, jobs, total }) {
<div>
<Table
loading={loading}
size='small'
size="small"
scroll={{ x: true }}
pagination={{
position: "top",
@@ -185,17 +189,17 @@ export default function JobsList({ refetch, loading, jobs, total }) {
total: total,
}}
columns={columns}
rowKey='id'
rowKey="id"
dataSource={jobs}
onChange={handleTableChange}
title={() => {
return (
<div className='imex-table-header'>
<div className="imex-table-header">
<Button onClick={() => refetch()}>
<SyncOutlined />
</Button>
<Input.Search
className='imex-table-header__search'
className="imex-table-header__search"
placeholder={t("general.labels.search")}
onSearch={(value) => {
search.search = value;

View File

@@ -39,7 +39,7 @@ export function JobsList({ bodyshop }) {
const history = useHistory();
const [searchText, setSearchText] = useState("");
if (error) return <AlertComponent message={error.message} type='error' />;
if (error) return <AlertComponent message={error.message} type="error" />;
const jobs = data
? searchText === ""
@@ -50,6 +50,9 @@ export function JobsList({ bodyshop }) {
.toString()
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_co_nm || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_fn || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
@@ -122,10 +125,14 @@ export function JobsList({ bodyshop }) {
render: (text, record) => {
return record.owner ? (
<Link to={"/manage/owners/" + record.owner.id}>
{`${record.ownr_fn || ""} ${record.ownr_ln || ""}`}
{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${
record.ownr_co_nm || ""
}`}
</Link>
) : (
<span>{`${record.ownr_fn || ""} ${record.ownr_ln || ""}`}</span>
<span>{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${
record.ownr_co_nm || ""
}`}</span>
);
},
},
@@ -242,21 +249,21 @@ export function JobsList({ bodyshop }) {
return (
<Table
loading={loading}
size='small'
size="small"
pagination={false}
columns={columns}
rowKey='id'
rowKey="id"
dataSource={jobs}
style={{ height: "100%" }}
scroll={{ x: true }}
title={() => {
return (
<div className='imex-table-header'>
<div className="imex-table-header">
<Button onClick={() => refetch()}>
<SyncOutlined />
</Button>
<Input.Search
className='imex-table-header__search'
className="imex-table-header__search"
placeholder={t("general.labels.search")}
onChange={(e) => {
setSearchText(e.target.value);

View File

@@ -0,0 +1,16 @@
import React from "react";
import { Result } from "antd";
import { useTranslation } from "react-i18next";
export default function NotFound() {
const { t } = useTranslation();
return (
<div>
<Result
status="404"
title={t("general.messages.notfoundtitle")}
subTitle={t("general.messages.notfoundsub")}
/>
</div>
);
}

View File

@@ -6,35 +6,37 @@ import FormItemEmail from "../form-items-formatted/email-form-item.component";
import FormItemPhone from "../form-items-formatted/phone-form-item.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
export default function OwnerDetailFormComponent({ form }) {
export default function OwnerDetailFormComponent({ form, loading }) {
const { t } = useTranslation();
const { getFieldValue } = form;
return (
<div>
<div className='imex-flex-row imex-flex-row__flex-space-around'>
<div className="imex-flex-row imex-flex-row__flex-space-around">
<Button
className='imex-flex-row__margin-large'
type='primary'
key='submit'
htmlType='submit'>
className="imex-flex-row__margin-large"
type="primary"
key="submit"
loading={loading}
htmlType="submit"
>
{t("general.actions.save")}
</Button>
<div className='imex-flex-row__grow imex-flex-row__margin-large'>
<div className="imex-flex-row__grow imex-flex-row__margin-large">
<FormFieldsChanged form={form} />
</div>
</div>
<LayoutFormRow header={t("owners.forms.name")}>
<Form.Item label={t("owners.fields.ownr_title")} name='ownr_title'>
<Form.Item label={t("owners.fields.ownr_title")} name="ownr_title">
<Input />
</Form.Item>
<Form.Item label={t("owners.fields.ownr_ln")} name='ownr_ln'>
<Form.Item label={t("owners.fields.ownr_ln")} name="ownr_ln">
<Input />
</Form.Item>
<Form.Item label={t("owners.fields.ownr_fn")} name='ownr_fn'>
<Form.Item label={t("owners.fields.ownr_fn")} name="ownr_fn">
<Input />
</Form.Item>
<Form.Item label={t("owners.fields.ownr_co_nm")} name='ownr_co_nm'>
<Form.Item label={t("owners.fields.ownr_co_nm")} name="ownr_co_nm">
<Input />
</Form.Item>
</LayoutFormRow>
@@ -42,52 +44,56 @@ export default function OwnerDetailFormComponent({ form }) {
<LayoutFormRow header={t("owners.forms.address")}>
<Form.Item
label={t("owners.fields.ownr_addr1")}
name='ownr_addr1'
name="ownr_addr1"
rules={[
{
required: true,
message: t("general.validation.required"),
},
]}>
]}
>
<Input />
</Form.Item>
<Form.Item label={t("owners.fields.ownr_addr2")} name='ownr_addr2'>
<Form.Item label={t("owners.fields.ownr_addr2")} name="ownr_addr2">
<Input />
</Form.Item>
<Form.Item
label={t("owners.fields.ownr_city")}
name='ownr_city'
name="ownr_city"
rules={[
{
required: true,
message: t("general.validation.required"),
},
]}>
]}
>
<Input />
</Form.Item>
<Form.Item
label={t("owners.fields.ownr_st")}
name='ownr_st'
name="ownr_st"
rules={[
{
required: true,
message: t("general.validation.required"),
},
]}>
]}
>
<Input />
</Form.Item>
<Form.Item
label={t("owners.fields.ownr_zip")}
name='ownr_zip'
name="ownr_zip"
rules={[
{
required: true,
message: t("general.validation.required"),
},
]}>
]}
>
<Input />
</Form.Item>
<Form.Item label={t("owners.fields.ownr_ctry")} name='ownr_ctry'>
<Form.Item label={t("owners.fields.ownr_ctry")} name="ownr_ctry">
<Input />
</Form.Item>
</LayoutFormRow>
@@ -95,27 +101,30 @@ export default function OwnerDetailFormComponent({ form }) {
<LayoutFormRow header={t("owners.forms.contact")}>
<Form.Item
label={t("owners.fields.allow_text_message")}
name='allow_text_message'
valuePropName='checked'>
name="allow_text_message"
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("owners.fields.ownr_ea")}
name='ownr_ea'
name="ownr_ea"
rules={[
{
type: "email",
message: "This is not a valid email address.",
},
]}>
]}
>
<FormItemEmail email={getFieldValue("ownr_ea")} />
</Form.Item>
<Form.Item label={t("owners.fields.ownr_ph1")} name='ownr_ph1'>
<Form.Item label={t("owners.fields.ownr_ph1")} name="ownr_ph1">
<FormItemPhone customInput={Input} />
</Form.Item>
<Form.Item
label={t("owners.fields.preferred_contact")}
name='preferred_contact'>
name="preferred_contact"
>
<Input />
</Form.Item>
</LayoutFormRow>

View File

@@ -1,5 +1,5 @@
import { Form, notification } from "antd";
import React from "react";
import React, { useState } from "react";
import { useMutation } from "@apollo/react-hooks";
import { useTranslation } from "react-i18next";
import { UPDATE_OWNER } from "../../graphql/owners.queries";
@@ -8,10 +8,11 @@ import OwnerDetailFormComponent from "./owner-detail-form.component";
function OwnerDetailFormContainer({ owner, refetch }) {
const { t } = useTranslation();
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [updateOwner] = useMutation(UPDATE_OWNER);
const handleFinish = async (values) => {
setLoading(true);
const result = await updateOwner({
variables: { ownerId: owner.id, owner: values },
});
@@ -32,6 +33,7 @@ function OwnerDetailFormContainer({ owner, refetch }) {
if (refetch) await refetch();
form.resetFields();
form.resetFields();
setLoading(false);
};
return (
@@ -42,7 +44,7 @@ function OwnerDetailFormContainer({ owner, refetch }) {
layout="vertical"
initialValues={owner}
>
<OwnerDetailFormComponent form={form} />
<OwnerDetailFormComponent loading={loading} form={form} />
</Form>
);
}

View File

@@ -7,7 +7,7 @@ export default function OwnerFindModalComponent({
selectedOwner,
setSelectedOwner,
ownersListLoading,
ownersList
ownersList,
}) {
//setSelectedOwner is used to set the record id of the owner to use for adding the job.
const { t } = useTranslation();
@@ -16,27 +16,32 @@ export default function OwnerFindModalComponent({
{
title: t("owners.fields.ownr_ln"),
dataIndex: "ownr_ln",
key: "ownr_ln"
key: "ownr_ln",
},
{
title: t("owners.fields.ownr_fn"),
dataIndex: "ownr_fn",
key: "ownr_fn"
key: "ownr_fn",
},
{
title: t("owners.fields.ownr_co_nm"),
dataIndex: "ownr_co_nm",
key: "ownr_co_nm",
},
{
title: t("owners.fields.ownr_addr1"),
dataIndex: "ownr_addr1",
key: "ownr_addr1"
key: "ownr_addr1",
},
{
title: t("owners.fields.ownr_city"),
dataIndex: "ownr_city",
key: "ownr_city"
key: "ownr_city",
},
{
title: t("owners.fields.ownr_ea"),
dataIndex: "ownr_ea",
key: "ownr_ea"
key: "ownr_ea",
},
{
title: t("owners.fields.ownr_ph1"),
@@ -44,11 +49,11 @@ export default function OwnerFindModalComponent({
key: "ownr_ph1",
render: (text, record) => (
<PhoneFormatter>{record.ownr_ph1}</PhoneFormatter>
)
}
),
},
];
const handleOnRowClick = record => {
const handleOnRowClick = (record) => {
if (record) {
if (record.id) {
setSelectedOwner(record.id);
@@ -64,22 +69,22 @@ export default function OwnerFindModalComponent({
title={() => t("owners.labels.existing_owners")}
size="small"
pagination={{ position: "bottom" }}
columns={columns.map(item => ({ ...item }))}
columns={columns.map((item) => ({ ...item }))}
rowKey="id"
loading={ownersListLoading}
dataSource={ownersList}
rowSelection={{
onSelect: props => {
onSelect: (props) => {
setSelectedOwner(props.id);
},
type: "radio",
selectedRowKeys: [selectedOwner]
selectedRowKeys: [selectedOwner],
}}
onRow={(record, rowIndex) => {
return {
onClick: event => {
onClick: (event) => {
handleOnRowClick(record);
}
},
};
}}
/>

View File

@@ -20,27 +20,28 @@ export default function OwnerFindModalContainer({
const ownersList = useQuery(QUERY_SEARCH_OWNER_BY_IDX, {
variables: {
search: owner ? `${owner.ownr_fn || ""} ${owner.ownr_ln || ""}` : null
search: owner ? `${owner.ownr_fn || ""} ${owner.ownr_ln || ""}` : null,
},
skip: !owner,
fetchPolicy: "network-only"
fetchPolicy: "network-only",
});
return (
<Modal
title={t("owners.labels.existing_owners")}
width={"80%"}
{...modalProps}>
{...modalProps}
>
{loading ? <LoadingSpinner /> : null}
{error ? <AlertComponent message={error.message} type='error' /> : null}
{error ? <AlertComponent message={error.message} type="error" /> : null}
{owner ? (
<OwnerFindModalComponent
selectedOwner={selectedOwner}
setSelectedOwner={setSelectedOwner}
ownersListLoading={ownersList.loading}
ownersList={
ownersList.data && ownersList.data.search_owner
? ownersList.data.search_owner
ownersList.data && ownersList.data.search_owners
? ownersList.data.search_owners
: null
}
/>

View File

@@ -33,7 +33,9 @@ export default function OwnersListComponent({
key: "name",
render: (text, record) => (
<Link to={"/manage/owners/" + record.id}>
{`${record.ownr_fn} ${record.ownr_ln}`}
{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${
record.ownr_co_nm || ""
}`}
</Link>
),
},

View File

@@ -5,11 +5,13 @@ import { useTranslation } from "react-i18next";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
export default function PartsOrderModalComponent({
vendorList,
sendTypeState,
isReturn,
preferredMake,
}) {
const [sendType, setSendType] = sendTypeState;
@@ -27,7 +29,11 @@ export default function PartsOrderModalComponent({
},
]}
>
<VendorSearchSelect options={vendorList} disabled={isReturn} />
<VendorSearchSelect
options={vendorList}
disabled={isReturn}
preferredMake={preferredMake}
/>
</Form.Item>
<Form.Item
name="deliver_by"
@@ -44,7 +50,7 @@ export default function PartsOrderModalComponent({
{t("parts_orders.labels.inthisorder")}
<Form.List name={["parts_order_lines", "data"]}>
{(fields, { add, remove }) => {
{(fields, { add, remove, move }) => {
return (
<div>
{fields.map((field, index) => (
@@ -96,6 +102,11 @@ export default function PartsOrderModalComponent({
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
/>
</div>
</Form.Item>
))}

View File

@@ -63,7 +63,9 @@ export function PartsOrderModalContainer({
const { loading, error, data } = useQuery(QUERY_ALL_VENDORS_FOR_ORDER, {
skip: !visible,
variables: { jobId: jobId },
});
const [insertPartOrder] = useMutation(INSERT_NEW_PARTS_ORDERS);
const [updateJobLines] = useMutation(UPDATE_JOB_LINE_STATUS);
const [insertInvoice] = useMutation(INSERT_NEW_INVOICE);
@@ -111,7 +113,6 @@ export function PartsOrderModalContainer({
});
if (values.vendorid === bodyshop.inhousevendorid) {
console.log("Inhouse Invoice needs to be psoted.");
logImEXEvent("parts_order_inhouse_invoice");
let invoiceToPost = {
@@ -233,6 +234,7 @@ export function PartsOrderModalContainer({
vendorList={(data && data.vendors) || []}
sendTypeState={sendTypeState}
isReturn={isReturn}
preferredMake={data && data.jobs[0] && data.jobs[0].v_make_desc}
/>
</Form>
</LoadingSpinner>

View File

@@ -1,25 +1,25 @@
import React from "react";
export default function PartsStatusPie({ partsList }) {
return <div>Parts Pie</div>;
//const [pieData, setPieData] = useState([]);
const result = partsList
? partsList.reduce((names, name) => {
const val = name || "?";
const count = names[val] || 0;
names[val] = count + 1;
return names;
}, {})
: {};
// const result = partsList
// ? partsList.reduce((names, name) => {
// const val = name || "?";
// const count = names[val] || 0;
// names[val] = count + 1;
// return names;
// }, {})
// : {};
const pieData = Object.keys(result).map((i) => {
console.log("i", i);
return {
id: i,
label: i,
value: result[i],
};
});
// const pieData = Object.keys(result).map((i) => {
// return {
// id: i,
// label: i,
// value: result[i],
// };
// });
return <div>{JSON.stringify(pieData)}</div>;
// return <div>{JSON.stringify(pieData)}</div>;
}

View File

@@ -23,7 +23,6 @@ export function PaymentFormComponent({
const { t } = useTranslation();
const handleStripeChange = (e) => {
console.log("e", e);
setStripeState({ error: e.error, cardComplete: e.complete });
};

View File

@@ -88,7 +88,6 @@ function InvoiceEnterModalContainer({
},
}
);
console.log("handleFinish -> stripePayment", stripePayment);
if (stripePayment.paymentIntent.status === "succeeded") {
notification["success"]({ message: t("payments.successes.stripe") });
@@ -172,13 +171,15 @@ function InvoiceEnterModalContainer({
okButtonProps={{
loading: loading,
}}
destroyOnClose>
destroyOnClose
>
<Form
onFinish={handleFinish}
autoComplete={"off"}
form={form}
layout='vertical'
initialValues={{ jobid: context.jobId }}>
layout="vertical"
initialValues={{ jobid: context.jobId }}
>
<PaymentForm form={form} stripeStateArr={stripeStateArr} />
</Form>
</Modal>

View File

@@ -38,7 +38,6 @@ export function PaymentsExportAllButton({
},
}
);
console.log("handle -> XML", QbXmlResponse);
} catch (error) {
console.log("Error getting QBXML from Server.", error);
notification["error"]({

View File

@@ -56,11 +56,13 @@ export default function PaymentsListPaginated({
render: (text, record) => {
return record.job.owner ? (
<Link to={"/manage/owners/" + record.job.owner.id}>
{`${record.job.ownr_fn || ""} ${record.job.ownr_ln || ""}`}
{`${record.job.ownr_fn || ""} ${record.job.ownr_ln || ""} ${
record.job.ownr_co_nm
}`}
</Link>
) : (
<span>{`${record.job.ownr_fn || ""} ${
record.job.ownr_ln || ""
<span>{`${record.job.ownr_fn || ""} ${record.job.ownr_ln || ""} ${
record.job.ownr_co_nm
}`}</span>
);
},

View File

@@ -0,0 +1,40 @@
import { Input } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import EmployeeSearchSelectComponent from "../employee-search-select/employee-search-select.component";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(ProductionBoardFilters);
export function ProductionBoardFilters({ bodyshop, filter, setFilter }) {
const { t } = useTranslation();
return (
<div>
<Input.Search
//value={filter.search}
placeholder={t("general.labels.search")}
onChange={(e) => {
setFilter({ ...filter, search: e.target.value });
}}
/>
<EmployeeSearchSelectComponent
options={bodyshop.employees}
value={filter.employeeId}
onChange={(emp) => setFilter({ ...filter, employeeId: emp })}
allowClear
/>
</div>
);
}

View File

@@ -1,74 +1,87 @@
import React from "react";
import { Card, Row, Col } from "antd";
import { Card, Row, Col, Dropdown } from "antd";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import ProductionAlert from "../production-list-columns/production-list-columns.alert.component";
import { EyeFilled } from "@ant-design/icons";
import { Link } from "react-router-dom";
import "./production-board-card.styles.scss";
import ProductionRemoveButton from "../production-remove-button/production-remove-button.component";
import { useTranslation } from "react-i18next";
export default function ProductionBoardCard(card) {
const { t } = useTranslation();
const menu = (
<div>
<Card title={t("general.labels.actions")}>
<ProductionRemoveButton jobId={card.id} />
</Card>
</div>
);
return (
<Card
className="react-kanban-card imex-kanban-card tight-antd-rows"
style={{ margin: ".2rem 0rem" }}
size="small"
title={`${card.ro_number || card.est_number} - ${card.v_model_yr} ${
card.v_make_desc || ""
} ${card.v_model_desc || ""}`}
>
<Row>
<Col span={24}>
<div className="ellipses">{`${card.ownr_fn || ""} ${
card.ownr_ln || ""
} ${card.ownr_co_nm || ""}`}</div>
</Col>
</Row>
<Row>
<Col span={12}>
<div className="ellipses">{card.clm_no || ""}</div>
</Col>
<Col span={12}>
<div className="ellipses">{card.ins_co_nm || ""}</div>
</Col>
</Row>
<Row>
<Col span={24}>
<div className="imex-flex-row imex-flex-row__flex-space-around">
<div className="mex-flex-row__margin">
<div>{`B: ${card.labhrs.aggregate.sum.mod_lb_hrs || "?"}`}</div>
<div>{`R: ${card.labhrs.aggregate.sum.mod_lb_hrs || "?"}`}</div>
<Dropdown overlay={menu} trigger={["contextMenu"]}>
<Card
className="react-kanban-card imex-kanban-card tight-antd-rows"
style={{ margin: ".2rem 0rem" }}
size="small"
title={`${card.ro_number || card.est_number} - ${card.v_model_yr} ${
card.v_make_desc || ""
} ${card.v_model_desc || ""}`}
>
<Row>
<Col span={24}>
<div className="ellipses">{`${card.ownr_fn || ""} ${
card.ownr_ln || ""
} ${card.ownr_co_nm || ""}`}</div>
</Col>
</Row>
<Row>
<Col span={12}>
<div className="ellipses">{card.clm_no || ""}</div>
</Col>
<Col span={12}>
<div className="ellipses">{card.ins_co_nm || ""}</div>
</Col>
</Row>
<Row>
<Col span={24}>
<div className="imex-flex-row imex-flex-row__flex-space-around">
<div className="mex-flex-row__margin">
<div>{`B: ${card.labhrs.aggregate.sum.mod_lb_hrs || "?"}`}</div>
<div>{`R: ${card.larhrs.aggregate.sum.mod_lb_hrs || "?"}`}</div>
</div>
<div className="mex-flex-row__margin">
<div>{`B: ${
card.employee_body_rel
? `${card.employee_body_rel.first_name} ${card.employee_body_rel.last_name}`
: ""
}`}</div>
<div>{`P: ${
card.employee_prep_rel
? `${card.employee_prep_rel.first_name} ${card.employee_prep_rel.last_name}`
: ""
}`}</div>
<div>{`R: ${
card.employee_refinish_rel
? `${card.employee_refinish_rel.first_name} ${card.employee_refinish_rel.last_name}`
: ""
}`}</div>
</div>
</div>
<div className="mex-flex-row__margin">
<div>{`B: ${
card.employee_body_rel
? `${card.employee_body_rel.first_name} ${card.employee_body_rel.last_name}`
: ""
}`}</div>
<div>{`P: ${
card.employee_prep_rel
? `${card.employee_prep_rel.first_name} ${card.employee_prep_rel.last_name}`
: ""
}`}</div>
<div>{`R: ${
card.employee_refinish_rel
? `${card.employee_refinish_rel.first_name} ${card.employee_refinish_rel.last_name}`
: ""
}`}</div>
</div>
</div>
</Col>
</Row>
<Row>
<Col span={24}>
<DateTimeFormatter>{card.scheduled_completion}</DateTimeFormatter>
</Col>
</Row>
<div className="imex-flex-row imex-flex-row__flex-space-around">
<ProductionAlert record={card} key="alert" />
<Link to={`/manage/jobs/${card.id}`}>
<EyeFilled key="setting" />
</Link>
</div>
</Card>
</Col>
</Row>
<Row>
<Col span={24}>
<DateTimeFormatter>{card.scheduled_completion}</DateTimeFormatter>
</Col>
</Row>
<div className="imex-flex-row imex-flex-row__flex-space-around">
<ProductionAlert record={card} key="alert" />
<Link to={`/manage/jobs/${card.id}`}>
<EyeFilled key="setting" />
</Link>
</div>
</Card>
</Dropdown>
);
}

View File

@@ -12,6 +12,7 @@ import ProductionBoardCard from "../production-board-kanban-card/production-boar
import { createBoardData } from "./production-board-kanban.utils.js";
import IndefiniteLoading from "../indefinite-loading/indefinite-loading.component";
import { logImEXEvent } from "../../firebase/firebase.utils";
import ProductionBoardFilters from "../production-board-filters/production-board-filters.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -22,12 +23,14 @@ export function ProductionBoardKanbanComponent({ data, bodyshop }) {
columns: [{ id: "Loading...", title: "Loading...", cards: [] }],
});
const [filter, setFilter] = useState({ search: "", employeeId: null });
const [isMoving, setIsMoving] = useState(false);
const { t } = useTranslation();
useEffect(() => {
setBoardLanes(
createBoardData(bodyshop.md_ro_statuses.production_statuses, data)
createBoardData(bodyshop.md_ro_statuses.production_statuses, data, filter)
);
setIsMoving(false);
}, [
@@ -35,6 +38,7 @@ export function ProductionBoardKanbanComponent({ data, bodyshop }) {
setBoardLanes,
setIsMoving,
bodyshop.md_ro_statuses.production_statuses,
filter,
]);
const client = useApolloClient();
@@ -76,13 +80,13 @@ export function ProductionBoardKanbanComponent({ data, bodyshop }) {
let movedCardNewKanbanParent;
if (movedCardWillBeFirst) {
console.log("==> New Card is first.");
//console.log("==> New Card is first.");
movedCardNewKanbanParent = "-1";
} else if (movedCardWillBeLast) {
console.log("==> New Card is last.");
// console.log("==> New Card is last.");
movedCardNewKanbanParent = lastCardInDestinationColumn.id;
} else if (!!newChildCard) {
console.log("==> New Card is somewhere in the middle");
// console.log("==> New Card is somewhere in the middle");
movedCardNewKanbanParent = newChildCard.kanbanparent;
} else {
throw new Error("==> !!!!!!Couldn't find a parent.!!!! <==");
@@ -111,6 +115,7 @@ export function ProductionBoardKanbanComponent({ data, bodyshop }) {
return (
<div>
<IndefiniteLoading loading={isMoving} />
<ProductionBoardFilters filter={filter} setFilter={setFilter} />
<Board
children={boardLanes}
disableCardDrag={isMoving}

Some files were not shown because too many files have changed in this diff Show More