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", "version": "0.2.0",
"configurations": [ "configurations": [
{
"name": "Attach to Chrome",
"port": 9222,
"request": "attach",
"type": "pwa-chrome",
"webRoot": "${workspaceFolder}/client/src"
},
{ {
"name": "Chrome", "name": "Chrome",
"type": "chrome", "type": "chrome",
"request": "launch", "request": "launch",
"url": "http://localhost:3000", "url": "http://localhost:3000",
"webRoot": "${workspaceRoot}/src" "webRoot": "${workspaceRoot}/client/src"
},
{
"name": "Yarn Dev Server",
"type": "node",
"request": "launch",
"runtimeExecutable": "yarn",
"runtimeArgs": ["dev"]
} }
] ]
} }

View File

@@ -1,7 +1,8 @@
React App: React App:
Yarn Dependency Management: 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: GraphQL API:
Hasura is hosted on another dyno. Several environmental variables are required, including disabling the console. 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 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", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@apollo/client": "^3.0.2", "@apollo/client": "^3.1.2",
"@testing-library/jest-dom": "^4.2.4", "@testing-library/jest-dom": "^5.11.2",
"@testing-library/react": "^9.3.2", "@testing-library/react": "^10.4.8",
"@testing-library/user-event": "^7.1.2", "@testing-library/user-event": "^12.1.0",
"@types/prop-types": "^15.7.3",
"apollo-boost": "^0.4.9", "apollo-boost": "^0.4.9",
"apollo-link-context": "^1.0.20", "apollo-link-context": "^1.0.20",
"apollo-link-logger": "^1.2.3", "apollo-link-logger": "^1.2.3",
"dotenv": "^8.2.0", "dotenv": "^8.2.0",
"firebase": "^7.17.0", "firebase": "^7.17.1",
"graphql": "^15.3.0", "graphql": "^15.3.0",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"ra-data-hasura-graphql": "^0.1.12", "ra-data-hasura-graphql": "^0.1.12",
"react": "^16.13.1", "react": "^16.13.1",
"react-admin": "^3.7.1", "react-admin": "^3.7.2",
"react-dom": "^16.13.1", "react-dom": "^16.13.1",
"react-icons": "^3.10.0",
"react-scripts": "3.4.1" "react-scripts": "3.4.1"
}, },
"scripts": { "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 { ApolloLink } from "apollo-boost";
import { setContext } from "apollo-link-context"; import { setContext } from "apollo-link-context";
import { HttpLink } from "apollo-link-http"; import { HttpLink } from "apollo-link-http";
@@ -12,6 +12,7 @@ import {
Resource, Resource,
ShowGuesser, ShowGuesser,
} from "react-admin"; } from "react-admin";
import { FaFileInvoiceDollar } from "react-icons/fa";
import { auth } from "../../firebase/admin-firebase-utils"; import { auth } from "../../firebase/admin-firebase-utils";
import authProvider from "../auth-provider/auth-provider"; import authProvider from "../auth-provider/auth-provider";
import JoblinesCreate from "../joblines/joblines.create"; import JoblinesCreate from "../joblines/joblines.create";
@@ -91,6 +92,7 @@ class AdminRoot extends Component {
<ApolloProvider client={client}> <ApolloProvider client={client}>
<Admin dataProvider={dataProvider} authProvider={authProvider}> <Admin dataProvider={dataProvider} authProvider={authProvider}>
<Resource <Resource
icon={FaFileInvoiceDollar}
name="jobs" name="jobs"
list={JobsList} list={JobsList}
edit={JobsEdit} edit={JobsEdit}

View File

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

View File

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

View File

@@ -1,53 +1,150 @@
import React from "react"; 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) => ( const JobsEdit = (props) => (
<Edit {...props}> <Edit {...props}>
<SimpleForm margin="normal" variant="standard"> <TabbedForm margin="normal" variant="standard">
<div <FormTab label="Record Info">
style={{ <div
columns: "3 auto", style={{
// display: "flex", columns: "3 auto",
width: "100%", // display: "flex",
// justifyContent: "space-around", width: "100%",
}} // justifyContent: "space-around",
> }}
<TextInput fullWidth source="id" /> >
<TextInput fullWidth source="created_at" /> <TextInput disabled fullWidth source="id" />
<TextInput fullWidth source="updated_at" /> <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 /> <FormTab label="Labor Rates">
<TextInput fullWidth source="ro_number" /> <div
<TextInput fullWidth source="ownerid" /> style={{
<TextInput fullWidth source="vehicleid" /> columns: "3 auto",
<TextInput fullWidth source="labor_rate_id" /> // display: "flex",
<TextInput fullWidth source="labor_rate_desc" /> width: "100%",
<TextInput fullWidth source="rate_lab" /> // justifyContent: "space-around",
<TextInput fullWidth source="rate_lad" /> }}
<TextInput fullWidth source="rate_lae" /> >
<TextInput fullWidth source="rate_lar" /> <NumberInput fullWidth source="labor_rate_id" />
<TextInput fullWidth source="rate_las" /> <NumberInput fullWidth source="labor_rate_desc" />
<TextInput fullWidth source="rate_laf" /> <NumberInput fullWidth source="rate_lab" />
<TextInput fullWidth source="rate_lam" /> <NumberInput fullWidth source="rate_lad" />
<TextInput fullWidth source="rate_lag" /> <NumberInput fullWidth source="rate_lae" />
<TextInput fullWidth source="rate_atp" /> <NumberInput fullWidth source="rate_lar" />
<TextInput fullWidth source="rate_lau" /> <NumberInput fullWidth source="rate_las" />
<TextInput fullWidth source="rate_la1" /> <NumberInput fullWidth source="rate_laf" />
<TextInput fullWidth source="rate_la2" /> <NumberInput fullWidth source="rate_lam" />
<TextInput fullWidth source="rate_la3" /> <NumberInput fullWidth source="rate_lag" />
<TextInput fullWidth source="rate_la4" /> <NumberInput fullWidth source="rate_atp" />
<TextInput fullWidth source="rate_mapa" /> <NumberInput fullWidth source="rate_lau" />
<TextInput fullWidth source="rate_mash" /> <NumberInput fullWidth source="rate_la1" />
<TextInput fullWidth source="rate_mahw" /> <NumberInput fullWidth source="rate_la2" />
<TextInput fullWidth source="rate_ma2s" /> <NumberInput fullWidth source="rate_la3" />
<TextInput fullWidth source="rate_ma3s" /> <NumberInput fullWidth source="rate_la4" />
<TextInput fullWidth source="rate_ma2t" /> <NumberInput fullWidth source="rate_mapa" />
<TextInput fullWidth source="rate_mabl" /> <NumberInput fullWidth source="rate_mash" />
<TextInput fullWidth source="rate_macs" /> <NumberInput fullWidth source="rate_mahw" />
<TextInput fullWidth source="rate_matd" /> <NumberInput fullWidth source="rate_ma2s" />
<TextInput fullWidth source="federal_tax_rate" /> <NumberInput fullWidth source="rate_ma3s" />
<TextInput fullWidth source="state_tax_rate" /> <NumberInput fullWidth source="rate_ma2t" />
<TextInput fullWidth source="local_tax_rate" /> <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_co_nm" />
<TextInput fullWidth source="est_addr1" /> <TextInput fullWidth source="est_addr1" />
<TextInput fullWidth source="est_addr2" /> <TextInput fullWidth source="est_addr2" />
@@ -59,15 +156,7 @@ const JobsEdit = (props) => (
<TextInput fullWidth source="est_ea" /> <TextInput fullWidth source="est_ea" />
<TextInput fullWidth source="est_ct_ln" /> <TextInput fullWidth source="est_ct_ln" />
<TextInput fullWidth source="est_ct_fn" /> <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="regie_number" />
<TextInput fullWidth source="invoice_date" />
<TextInput fullWidth source="inproduction" />
<TextInput fullWidth source="statusid" /> <TextInput fullWidth source="statusid" />
<TextInput fullWidth source="ins_co_id" /> <TextInput fullWidth source="ins_co_id" />
<TextInput fullWidth source="ins_co_nm" /> <TextInput fullWidth source="ins_co_nm" />
@@ -98,15 +187,31 @@ const JobsEdit = (props) => (
<TextInput fullWidth source="asgn_type" /> <TextInput fullWidth source="asgn_type" />
<TextInput fullWidth source="clm_no" /> <TextInput fullWidth source="clm_no" />
<TextInput fullWidth source="clm_ofc_id" /> <TextInput fullWidth source="clm_ofc_id" />
<TextInput fullWidth source="date_estimated" /> <TextInput fullWidth source="agt_co_id" />
<TextInput fullWidth source="date_open" /> <TextInput fullWidth source="agt_co_nm" />
<TextInput fullWidth source="date_scheduled" /> <TextInput fullWidth source="agt_addr1" />
<TextInput fullWidth source="date_invoiced" /> <TextInput fullWidth source="agt_addr2" />
<TextInput fullWidth source="date_closed" /> <TextInput fullWidth source="agt_city" />
<TextInput fullWidth source="date_exported" /> <TextInput fullWidth source="agt_st" />
<TextInput fullWidth source="clm_total" /> <TextInput fullWidth source="agt_zip" />
<TextInput fullWidth source="owner_owing" /> <TextInput fullWidth source="agt_ctry" />
<TextInput fullWidth source="converted" /> <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="ciecaid" />
<TextInput fullWidth source="loss_date" /> <TextInput fullWidth source="loss_date" />
<TextInput fullWidth source="clm_ofc_nm" /> <TextInput fullWidth source="clm_ofc_nm" />
@@ -133,31 +238,8 @@ const JobsEdit = (props) => (
<TextInput fullWidth source="pay_date" /> <TextInput fullWidth source="pay_date" />
<TextInput fullWidth source="pay_chknm" /> <TextInput fullWidth source="pay_chknm" />
<TextInput fullWidth source="pay_amt" /> <TextInput fullWidth source="pay_amt" />
<TextInput fullWidth source="agt_co_id" /> </FormTab>
<TextInput fullWidth source="agt_co_nm" /> <FormTab label="Owner Data on Job">
<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="cust_pr" /> <TextInput fullWidth source="cust_pr" />
<TextInput fullWidth source="insd_ln" /> <TextInput fullWidth source="insd_ln" />
<TextInput fullWidth source="insd_fn" /> <TextInput fullWidth source="insd_fn" />
@@ -193,6 +275,12 @@ const JobsEdit = (props) => (
<TextInput fullWidth source="ownr_fax" /> <TextInput fullWidth source="ownr_fax" />
<TextInput fullWidth source="ownr_faxx" /> <TextInput fullWidth source="ownr_faxx" />
<TextInput fullWidth source="ownr_ea" /> <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="area_of_damage" />
<TextInput fullWidth source="loss_cat" /> <TextInput fullWidth source="loss_cat" />
<TextInput fullWidth source="est_number" /> <TextInput fullWidth source="est_number" />
@@ -247,8 +335,8 @@ const JobsEdit = (props) => (
<TextInput fullWidth source="employee_body" /> <TextInput fullWidth source="employee_body" />
<TextInput fullWidth source="employee_refinish" /> <TextInput fullWidth source="employee_refinish" />
<TextInput fullWidth source="employee_prep" /> <TextInput fullWidth source="employee_prep" />
</div> </FormTab>
</SimpleForm> </TabbedForm>
</Edit> </Edit>
); );

View File

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

View File

@@ -2,16 +2,16 @@
# yarn lockfile v1 # yarn lockfile v1
"@apollo/client@^3.0.2": "@apollo/client@^3.1.2":
version "3.0.2" version "3.1.2"
resolved "https://registry.yarnpkg.com/@apollo/client/-/client-3.0.2.tgz#fadb2b39a0e32950baaef2566442cb3f6de74a52" resolved "https://registry.yarnpkg.com/@apollo/client/-/client-3.1.2.tgz#e384f691706c46aef4d9234b63a03ccc06e9c33c"
integrity sha512-4ighan5Anlj4tK/tdUHs4Mi1njqXZ7AxRCVolz/H702DjPphAJfm+FRkIadPTmwz+OLO+d+tX+6V1VBshf02rg== integrity sha512-GaA/J0CDSSNe0HVm1abeOIJA3M4fs9Ih7wF2z1AI2SLqv5TBLvwBxh0+0+jCSntPZ3gnDQvR7MHjmXota5V1LQ==
dependencies: dependencies:
"@types/zen-observable" "^0.8.0" "@types/zen-observable" "^0.8.0"
"@wry/context" "^0.5.2" "@wry/context" "^0.5.2"
"@wry/equality" "^0.1.9" "@wry/equality" "^0.2.0"
fast-json-stable-stringify "^2.0.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" hoist-non-react-statics "^3.3.2"
optimism "^0.12.1" optimism "^0.12.1"
prop-types "^15.7.2" prop-types "^15.7.2"
@@ -1022,13 +1022,20 @@
dependencies: dependencies:
regenerator-runtime "^0.13.4" 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" version "7.10.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.5.tgz#303d8bd440ecd5a491eae6117fd3367698674c5c" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.5.tgz#303d8bd440ecd5a491eae6117fd3367698674c5c"
integrity sha512-otddXKhdNn7d0ptoFRHtMLa8LqDxLYwTjB4nYgM1yy5N6gU/MUf8zqyyLltCH3yAVitBzmwK4us+DD0l/MauAg== integrity sha512-otddXKhdNn7d0ptoFRHtMLa8LqDxLYwTjB4nYgM1yy5N6gU/MUf8zqyyLltCH3yAVitBzmwK4us+DD0l/MauAg==
dependencies: dependencies:
regenerator-runtime "^0.13.4" 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": "@babel/template@^7.10.4":
version "7.10.4" version "7.10.4"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278" 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" resolved "https://registry.yarnpkg.com/@firebase/analytics-types/-/analytics-types-0.3.1.tgz#3c5f5d71129c88295e17e914e34b391ffda1723c"
integrity sha512-63vVJ5NIBh/JF8l9LuPrQYSzFimk7zYHySQB4Dk9rVdJ8kV/vGQoVTvRu1UW05sEc2Ug5PqtEChtTHU+9hvPcA== integrity sha512-63vVJ5NIBh/JF8l9LuPrQYSzFimk7zYHySQB4Dk9rVdJ8kV/vGQoVTvRu1UW05sEc2Ug5PqtEChtTHU+9hvPcA==
"@firebase/analytics@0.4.0": "@firebase/analytics@0.4.1":
version "0.4.0" version "0.4.1"
resolved "https://registry.yarnpkg.com/@firebase/analytics/-/analytics-0.4.0.tgz#15dfee56619af18c262d4b7cb2f1d4e7c25194de" resolved "https://registry.yarnpkg.com/@firebase/analytics/-/analytics-0.4.1.tgz#0f1e6f4e56af11c3956b1652520095a1fbd2c418"
integrity sha512-8DC2OBXGYxeeRxCh6eFnrrswNcKm2WsD8EeqGcl0F1P7J0bJ4Q+WpP3DvxofQZ/PtVHdAhzmfmt9r6Xa9mHnrQ== integrity sha512-y5ZuhqX/PwLi0t7AKxNAi3NnlEwXe0rpknulUWUg3/1dALqtd2RrAOATQoV5FNnKK6YUH5UmK0Jb9KcSjsFeNw==
dependencies: dependencies:
"@firebase/analytics-types" "0.3.1" "@firebase/analytics-types" "0.3.1"
"@firebase/component" "0.1.16" "@firebase/component" "0.1.17"
"@firebase/installations" "0.4.14" "@firebase/installations" "0.4.15"
"@firebase/logger" "0.2.6" "@firebase/logger" "0.2.6"
"@firebase/util" "0.2.50" "@firebase/util" "0.3.0"
tslib "^1.11.1" tslib "^1.11.1"
"@firebase/app-types@0.6.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" resolved "https://registry.yarnpkg.com/@firebase/app-types/-/app-types-0.6.1.tgz#dcbd23030a71c0c74fc95d4a3f75ba81653850e9"
integrity sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg== integrity sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg==
"@firebase/app@0.6.8": "@firebase/app@0.6.9":
version "0.6.8" version "0.6.9"
resolved "https://registry.yarnpkg.com/@firebase/app/-/app-0.6.8.tgz#e7ccf31cc1d01f16744d6d27c5f9ba8b64338f12" resolved "https://registry.yarnpkg.com/@firebase/app/-/app-0.6.9.tgz#e60412d9b6012afb73caef2a1353e1b4c4182954"
integrity sha512-Tm7Pi6Dtpx4FFKcpm0jcrZ/qI9oREBxmP3pWlw1jgDW4syRJHmN9/5DYvfFk6FAhj3FrY8E/6F+ngWJfqONotQ== integrity sha512-X2riRgK49IK8LCQ3j7BKLu3zqHDTJSaT6YgcLewtHuOVwtpHfGODiS1cL5VMvKm3ogxP84GA70tN3sdoL/vTog==
dependencies: dependencies:
"@firebase/app-types" "0.6.1" "@firebase/app-types" "0.6.1"
"@firebase/component" "0.1.16" "@firebase/component" "0.1.17"
"@firebase/logger" "0.2.6" "@firebase/logger" "0.2.6"
"@firebase/util" "0.2.50" "@firebase/util" "0.3.0"
dom-storage "2.1.0" dom-storage "2.1.0"
tslib "^1.11.1" tslib "^1.11.1"
xmlhttprequest "1.8.0" xmlhttprequest "1.8.0"
@@ -1170,12 +1177,12 @@
dependencies: dependencies:
"@firebase/auth-types" "0.10.1" "@firebase/auth-types" "0.10.1"
"@firebase/component@0.1.16": "@firebase/component@0.1.17":
version "0.1.16" version "0.1.17"
resolved "https://registry.yarnpkg.com/@firebase/component/-/component-0.1.16.tgz#7a0dbdfff1485d45b8485db87a982f053e68761a" resolved "https://registry.yarnpkg.com/@firebase/component/-/component-0.1.17.tgz#2ce3e1aa060eccf0f06d20368ef9a32cf07c07be"
integrity sha512-FvffvFN0LWgv1H/FIyruTECOL69Dhy+JfwoTq+mV39V8Mz9lNpo41etonL5AOr7KmXxYJVbNwkx0L9Ei88i7JA== integrity sha512-/tN5iLcFp9rdpTfCJPfQ/o2ziGHlDxOzNx6XD2FoHlu4pG/PPGu+59iRfQXIowBGhxcTGD/l7oJhZEY/PVg0KQ==
dependencies: dependencies:
"@firebase/util" "0.2.50" "@firebase/util" "0.3.0"
tslib "^1.11.1" tslib "^1.11.1"
"@firebase/database-types@0.5.1": "@firebase/database-types@0.5.1":
@@ -1185,16 +1192,16 @@
dependencies: dependencies:
"@firebase/app-types" "0.6.1" "@firebase/app-types" "0.6.1"
"@firebase/database@0.6.8": "@firebase/database@0.6.9":
version "0.6.8" version "0.6.9"
resolved "https://registry.yarnpkg.com/@firebase/database/-/database-0.6.8.tgz#28c9fb4e6a3322cdf9b5f58e768f21d9ac948840" resolved "https://registry.yarnpkg.com/@firebase/database/-/database-0.6.9.tgz#18a4bdc93b0b10c19a8ad4ff616bba196e5a16e0"
integrity sha512-Psibz/LD9WBvZRS7A/kkYd5i5l6tBw49adSFmCM2ZJlKE9fxZhxay02AerwfXHiq3gPKVeqXUjBIRuHOWdEXmw== integrity sha512-+X2dNFDpcLEcDRdXp2Hgkf0RnNz3AOIC+Y7UFMQYadm9buB+snXomlnlkMzOj6o+Cp3V7GnpBrKKeeFqzF6wGQ==
dependencies: dependencies:
"@firebase/auth-interop-types" "0.1.5" "@firebase/auth-interop-types" "0.1.5"
"@firebase/component" "0.1.16" "@firebase/component" "0.1.17"
"@firebase/database-types" "0.5.1" "@firebase/database-types" "0.5.1"
"@firebase/logger" "0.2.6" "@firebase/logger" "0.2.6"
"@firebase/util" "0.2.50" "@firebase/util" "0.3.0"
faye-websocket "0.11.3" faye-websocket "0.11.3"
tslib "^1.11.1" tslib "^1.11.1"
@@ -1203,15 +1210,15 @@
resolved "https://registry.yarnpkg.com/@firebase/firestore-types/-/firestore-types-1.12.0.tgz#511e572e946b07f5a603c90e078f0cd714923fac" resolved "https://registry.yarnpkg.com/@firebase/firestore-types/-/firestore-types-1.12.0.tgz#511e572e946b07f5a603c90e078f0cd714923fac"
integrity sha512-OqNxVb63wPZdUc7YnpacAW1WNIMSKERSewCRi+unCQ0YI0KNfrDSypyGCyel+S3GdOtKMk9KnvDknaGbnaFX4g== integrity sha512-OqNxVb63wPZdUc7YnpacAW1WNIMSKERSewCRi+unCQ0YI0KNfrDSypyGCyel+S3GdOtKMk9KnvDknaGbnaFX4g==
"@firebase/firestore@1.16.1": "@firebase/firestore@1.16.2":
version "1.16.1" version "1.16.2"
resolved "https://registry.yarnpkg.com/@firebase/firestore/-/firestore-1.16.1.tgz#ddf4e357b4d847abe6a57a89d3e2d5b950339176" resolved "https://registry.yarnpkg.com/@firebase/firestore/-/firestore-1.16.2.tgz#66eedeefab569331efc1ad9ab49a8f1c867a9163"
integrity sha512-TGtvNIGHMEFFEuOSsRswou576GPZY39vXIsenn0B1Dqz9ACpyDtvAT9YdbG38srlPq7ZKwsP5x04LB43zZ6eAg== integrity sha512-iIkAL860oD/QA1uYI9JBbWqBYFWd+DnuSj//BIbOGn3DNAruDFy07g8re1vn+0MMas9bMk6CZATJNCFPeH8AsQ==
dependencies: dependencies:
"@firebase/component" "0.1.16" "@firebase/component" "0.1.17"
"@firebase/firestore-types" "1.12.0" "@firebase/firestore-types" "1.12.0"
"@firebase/logger" "0.2.6" "@firebase/logger" "0.2.6"
"@firebase/util" "0.2.50" "@firebase/util" "0.3.0"
"@firebase/webchannel-wrapper" "0.2.41" "@firebase/webchannel-wrapper" "0.2.41"
"@grpc/grpc-js" "^1.0.0" "@grpc/grpc-js" "^1.0.0"
"@grpc/proto-loader" "^0.5.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" resolved "https://registry.yarnpkg.com/@firebase/functions-types/-/functions-types-0.3.17.tgz#348bf5528b238eeeeeae1d52e8ca547b21d33a94"
integrity sha512-DGR4i3VI55KnYk4IxrIw7+VG7Q3gA65azHnZxo98Il8IvYLr2UTBlSh72dTLlDf25NW51HqvJgYJDKvSaAeyHQ== integrity sha512-DGR4i3VI55KnYk4IxrIw7+VG7Q3gA65azHnZxo98Il8IvYLr2UTBlSh72dTLlDf25NW51HqvJgYJDKvSaAeyHQ==
"@firebase/functions@0.4.48": "@firebase/functions@0.4.49":
version "0.4.48" version "0.4.49"
resolved "https://registry.yarnpkg.com/@firebase/functions/-/functions-0.4.48.tgz#aee8efeacbfdd74834db0c1b44297f59c5bdddaf" resolved "https://registry.yarnpkg.com/@firebase/functions/-/functions-0.4.49.tgz#cca60a2f8e188e020c7e5a5ecf075474885ffb03"
integrity sha512-BwI/JzO/f/nquKG1IS3VqmwMaKEhvM58/08vTnp46krHBsOYqsdD9T2amz+HXGT9fe2HhDsUhgFE8D00S0vqbg== integrity sha512-ma3+z1wMKervmEJCLWxwIjbSV+n3/BTfFPSZdTjt18Wgiso5q4BzEObFkorxaXZiyT3KpZ0qOO97lgcoth2hIA==
dependencies: dependencies:
"@firebase/component" "0.1.16" "@firebase/component" "0.1.17"
"@firebase/functions-types" "0.3.17" "@firebase/functions-types" "0.3.17"
"@firebase/messaging-types" "0.4.5" "@firebase/messaging-types" "0.4.5"
isomorphic-fetch "2.2.1" isomorphic-fetch "2.2.1"
@@ -1238,14 +1245,14 @@
resolved "https://registry.yarnpkg.com/@firebase/installations-types/-/installations-types-0.3.4.tgz#589a941d713f4f64bf9f4feb7f463505bab1afa2" resolved "https://registry.yarnpkg.com/@firebase/installations-types/-/installations-types-0.3.4.tgz#589a941d713f4f64bf9f4feb7f463505bab1afa2"
integrity sha512-RfePJFovmdIXb6rYwtngyxuEcWnOrzdZd9m7xAW0gRxDIjBT20n3BOhjpmgRWXo/DAxRmS7bRjWAyTHY9cqN7Q== integrity sha512-RfePJFovmdIXb6rYwtngyxuEcWnOrzdZd9m7xAW0gRxDIjBT20n3BOhjpmgRWXo/DAxRmS7bRjWAyTHY9cqN7Q==
"@firebase/installations@0.4.14": "@firebase/installations@0.4.15":
version "0.4.14" version "0.4.15"
resolved "https://registry.yarnpkg.com/@firebase/installations/-/installations-0.4.14.tgz#e0c240869bed834d1b5cc696bec0020e8fcb5f7b" resolved "https://registry.yarnpkg.com/@firebase/installations/-/installations-0.4.15.tgz#ec5a098aea6b5e3e29e73270eeaaf9791587d20a"
integrity sha512-hQPsaU7wdTq3CFMtFQwZy6LgdXZAkXoUToV4O+ekPbjM65QzaGVogJVU8O2H6ADXoq37SarcUXKe86pcUWdFLA== integrity sha512-6uGgDocDGu5gI7FeDBDcLaH4npz0cm2f0kctOFK+5N1CyK8Tv2YGv5/uGqlrTtSwDW+8tgKNo/5XXJJOPr9Jsw==
dependencies: dependencies:
"@firebase/component" "0.1.16" "@firebase/component" "0.1.17"
"@firebase/installations-types" "0.3.4" "@firebase/installations-types" "0.3.4"
"@firebase/util" "0.2.50" "@firebase/util" "0.3.0"
idb "3.0.2" idb "3.0.2"
tslib "^1.11.1" tslib "^1.11.1"
@@ -1259,15 +1266,15 @@
resolved "https://registry.yarnpkg.com/@firebase/messaging-types/-/messaging-types-0.4.5.tgz#452572d3c5b7fa83659fdb1884450477229f5dc4" resolved "https://registry.yarnpkg.com/@firebase/messaging-types/-/messaging-types-0.4.5.tgz#452572d3c5b7fa83659fdb1884450477229f5dc4"
integrity sha512-sux4fgqr/0KyIxqzHlatI04Ajs5rc3WM+WmtCpxrKP1E5Bke8xu/0M+2oy4lK/sQ7nov9z15n3iltAHCgTRU3Q== integrity sha512-sux4fgqr/0KyIxqzHlatI04Ajs5rc3WM+WmtCpxrKP1E5Bke8xu/0M+2oy4lK/sQ7nov9z15n3iltAHCgTRU3Q==
"@firebase/messaging@0.6.20": "@firebase/messaging@0.6.21":
version "0.6.20" version "0.6.21"
resolved "https://registry.yarnpkg.com/@firebase/messaging/-/messaging-0.6.20.tgz#c6139dad753185706196972629e9235cdda8c2d6" resolved "https://registry.yarnpkg.com/@firebase/messaging/-/messaging-0.6.21.tgz#d301de72ad055c3f302b917b8a11373cd78c7431"
integrity sha512-1MqyljXnbFBeHYhL6QInVM9aO5MW820yhNmOIVxk58wNXq4tOQLzqnKuvlgZ+ttgqlDzrIYiVf3EOHh5DptttQ== integrity sha512-cunbFNCtUy25Zp4/jn5lenYUPqgHpjKNUwRjKc7vIzYb4IT2Vu/7kaEptO3K0KQBC6O0QV3ZtqQxKrI9aLiSHg==
dependencies: dependencies:
"@firebase/component" "0.1.16" "@firebase/component" "0.1.17"
"@firebase/installations" "0.4.14" "@firebase/installations" "0.4.15"
"@firebase/messaging-types" "0.4.5" "@firebase/messaging-types" "0.4.5"
"@firebase/util" "0.2.50" "@firebase/util" "0.3.0"
idb "3.0.2" idb "3.0.2"
tslib "^1.11.1" tslib "^1.11.1"
@@ -1276,16 +1283,16 @@
resolved "https://registry.yarnpkg.com/@firebase/performance-types/-/performance-types-0.0.13.tgz#58ce5453f57e34b18186f74ef11550dfc558ede6" resolved "https://registry.yarnpkg.com/@firebase/performance-types/-/performance-types-0.0.13.tgz#58ce5453f57e34b18186f74ef11550dfc558ede6"
integrity sha512-6fZfIGjQpwo9S5OzMpPyqgYAUZcFzZxHFqOyNtorDIgNXq33nlldTL/vtaUZA8iT9TT5cJlCrF/jthKU7X21EA== integrity sha512-6fZfIGjQpwo9S5OzMpPyqgYAUZcFzZxHFqOyNtorDIgNXq33nlldTL/vtaUZA8iT9TT5cJlCrF/jthKU7X21EA==
"@firebase/performance@0.3.9": "@firebase/performance@0.3.10":
version "0.3.9" version "0.3.10"
resolved "https://registry.yarnpkg.com/@firebase/performance/-/performance-0.3.9.tgz#01e27616aca8486c7602e4f51c325c2e7caae6e8" resolved "https://registry.yarnpkg.com/@firebase/performance/-/performance-0.3.10.tgz#b68336e23f4b5422bd67f6ce35e28293a6b8945e"
integrity sha512-Fj22DZXRhhKv1OSUzDxX7AqpJUcDld6tzXK1yxOC8e3v1DFPQMQdM9FoG1m1b/Vrqa6pCCqnqG6gh6VPnEcAzQ== integrity sha512-j/hsx2xfOO1hZulmz7KxemoTIVXxrv94rt79x8qO1HzysT7ziViNvQ9cQGjDZWwVSO29TpLH31GOWLVnwmnxWQ==
dependencies: dependencies:
"@firebase/component" "0.1.16" "@firebase/component" "0.1.17"
"@firebase/installations" "0.4.14" "@firebase/installations" "0.4.15"
"@firebase/logger" "0.2.6" "@firebase/logger" "0.2.6"
"@firebase/performance-types" "0.0.13" "@firebase/performance-types" "0.0.13"
"@firebase/util" "0.2.50" "@firebase/util" "0.3.0"
tslib "^1.11.1" tslib "^1.11.1"
"@firebase/polyfill@0.3.36": "@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" resolved "https://registry.yarnpkg.com/@firebase/remote-config-types/-/remote-config-types-0.1.9.tgz#fe6bbe4d08f3b6e92fce30e4b7a9f4d6a96d6965"
integrity sha512-G96qnF3RYGbZsTRut7NBX0sxyczxt1uyCgXQuH/eAfUCngxjEGcZQnBdy6mvSdqdJh5mC31rWPO4v9/s7HwtzA== integrity sha512-G96qnF3RYGbZsTRut7NBX0sxyczxt1uyCgXQuH/eAfUCngxjEGcZQnBdy6mvSdqdJh5mC31rWPO4v9/s7HwtzA==
"@firebase/remote-config@0.1.25": "@firebase/remote-config@0.1.26":
version "0.1.25" version "0.1.26"
resolved "https://registry.yarnpkg.com/@firebase/remote-config/-/remote-config-0.1.25.tgz#93c5bda311d6c1302697d6148bdb33bb8dcb9e15" resolved "https://registry.yarnpkg.com/@firebase/remote-config/-/remote-config-0.1.26.tgz#62f448237bc46b986c27ac623b5cc5852007ea05"
integrity sha512-8YWefBhy77HMbWXWdbenalx+IDY/XkS+iURQ9qRYvSIFYx6RL04DzlakZNOY9CQAcxTA+cTSt4NNlhjopBjf2Q== integrity sha512-B6+nARVNcswysd6C16nK5tdGECgEpr1wdH6LyqylEQ8hUxYWN18qe49b9uPu+ktaHq0gFLg03gayZvQs7fxJOg==
dependencies: dependencies:
"@firebase/component" "0.1.16" "@firebase/component" "0.1.17"
"@firebase/installations" "0.4.14" "@firebase/installations" "0.4.15"
"@firebase/logger" "0.2.6" "@firebase/logger" "0.2.6"
"@firebase/remote-config-types" "0.1.9" "@firebase/remote-config-types" "0.1.9"
"@firebase/util" "0.2.50" "@firebase/util" "0.3.0"
tslib "^1.11.1" tslib "^1.11.1"
"@firebase/storage-types@0.3.13": "@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" resolved "https://registry.yarnpkg.com/@firebase/storage-types/-/storage-types-0.3.13.tgz#cd43e939a2ab5742e109eb639a313673a48b5458"
integrity sha512-pL7b8d5kMNCCL0w9hF7pr16POyKkb3imOW7w0qYrhBnbyJTdVxMWZhb0HxCFyQWC0w3EiIFFmxoz8NTFZDEFog== integrity sha512-pL7b8d5kMNCCL0w9hF7pr16POyKkb3imOW7w0qYrhBnbyJTdVxMWZhb0HxCFyQWC0w3EiIFFmxoz8NTFZDEFog==
"@firebase/storage@0.3.40": "@firebase/storage@0.3.41":
version "0.3.40" version "0.3.41"
resolved "https://registry.yarnpkg.com/@firebase/storage/-/storage-0.3.40.tgz#4e6ea66d9c3ce489cd1a892940c57c3078853410" resolved "https://registry.yarnpkg.com/@firebase/storage/-/storage-0.3.41.tgz#cba8946f980d70e68d52cfb110ad109592a645d0"
integrity sha512-xTUvSSXh8tNSlch4V+kNbw736H0z/lbW3rHlx1kZVnT8V5M4bXE+TEcG4WpqvcWH3p+N6N1bUorkDbOFgBrztw== integrity sha512-2imzI78HcB7FjUqXMRHsGLlZnTYkaCHBjJflSbypwLrEty0hreR6vx3ThOO5y0MFH93WwifqUFJAa+Twkx6CIA==
dependencies: dependencies:
"@firebase/component" "0.1.16" "@firebase/component" "0.1.17"
"@firebase/storage-types" "0.3.13" "@firebase/storage-types" "0.3.13"
"@firebase/util" "0.2.50" "@firebase/util" "0.3.0"
tslib "^1.11.1" tslib "^1.11.1"
"@firebase/util@0.2.50": "@firebase/util@0.3.0":
version "0.2.50" version "0.3.0"
resolved "https://registry.yarnpkg.com/@firebase/util/-/util-0.2.50.tgz#77666b845dcb49bc217650aa296a7a8986c06b44" resolved "https://registry.yarnpkg.com/@firebase/util/-/util-0.3.0.tgz#c3e938192cde4e1c6260aecaaf22103add2352f5"
integrity sha512-vFE6+Jfc25u0ViSpFxxq0q5s+XmuJ/y7CL3ud79RQe+WLFFg+j0eH1t23k0yNSG9vZNM7h3uHRIXbV97sYLAyw== integrity sha512-GTwC+FSLeCPc44/TXCDReNQ5FPRIS5cb8Gr1XcD1TgiNBOvmyx61Om2YLwHp2GnN++6m6xmwmXARm06HOukATA==
dependencies: dependencies:
tslib "^1.11.1" tslib "^1.11.1"
@@ -1835,17 +1842,6 @@
"@svgr/plugin-svgo" "^4.3.1" "@svgr/plugin-svgo" "^4.3.1"
loader-utils "^1.2.3" 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": "@testing-library/dom@^5.6.1":
version "5.6.1" version "5.6.1"
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-5.6.1.tgz#705a1cb4a039b877c1e69e916824038e837ab637" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-5.6.1.tgz#705a1cb4a039b877c1e69e916824038e837ab637"
@@ -1857,34 +1853,41 @@
pretty-format "^24.8.0" pretty-format "^24.8.0"
wait-for-expect "^1.2.0" wait-for-expect "^1.2.0"
"@testing-library/dom@^6.15.0": "@testing-library/dom@^7.17.1":
version "6.16.0" version "7.21.8"
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-6.16.0.tgz#04ada27ed74ad4c0f0d984a1245bb29b1fd90ba9" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.21.8.tgz#b64b266264bff9135eba3b5c6d4ddc995a3371e6"
integrity sha512-lBD88ssxqEfz0wFL6MeUyyWZfV/2cjEZZV3YRpb2IoJRej/4f1jB0TzqIOznTpfR1r34CNesrubxwIlAQ8zgPA== integrity sha512-iK1rJubFoeD5gxCryokwh09tnJa1Y4doNDbNFYYqOqz6ELwB1+kEAwlezA5xwMi8QrK7xg+1/aBMzb9X/A/EmA==
dependencies: dependencies:
"@babel/runtime" "^7.8.4" "@babel/runtime" "^7.10.3"
"@sheerun/mutationobserver-shim" "^0.3.2" "@types/aria-query" "^4.2.0"
"@types/testing-library__dom" "^6.12.1" aria-query "^4.2.2"
aria-query "^4.0.2" dom-accessibility-api "^0.4.6"
dom-accessibility-api "^0.3.0" pretty-format "^25.5.0"
pretty-format "^25.1.0"
wait-for-expect "^3.0.2"
"@testing-library/jest-dom@^4.2.4": "@testing-library/jest-dom@^5.11.2":
version "4.2.4" version "5.11.2"
resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-4.2.4.tgz#00dfa0cbdd837d9a3c2a7f3f0a248ea6e7b89742" resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.11.2.tgz#c49de331555c70127b5d7fc97344ad5265f4c54c"
integrity sha512-j31Bn0rQo12fhCWOUWy9fl7wtqkp7In/YP2p5ZFyRuiiB9Qs3g+hS4gAmDWONbAHcRmVooNJ5eOHQDCOmUFXHg== integrity sha512-s+rWJx+lanEGKqvOl4qJR0rGjCrxsEjj9qjxFlg4NV4/FRD7fnUUAWPHqwpyafNHfLYArs58FADgdn4UKmjFmw==
dependencies: dependencies:
"@babel/runtime" "^7.5.1" "@babel/runtime" "^7.9.2"
chalk "^2.4.1" "@types/testing-library__jest-dom" "^5.9.1"
css "^2.2.3" aria-query "^4.2.2"
chalk "^3.0.0"
css "^3.0.0"
css.escape "^1.5.1" css.escape "^1.5.1"
jest-diff "^24.0.0" jest-diff "^25.1.0"
jest-matcher-utils "^24.0.0" jest-matcher-utils "^25.1.0"
lodash "^4.17.11" lodash "^4.17.15"
pretty-format "^24.0.0"
redent "^3.0.0" 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": "@testing-library/react@^8.0.7":
version "8.0.9" version "8.0.9"
resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-8.0.9.tgz#1ecd96bc3471b06dd2f9763b6e53a7ace28a54a2" resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-8.0.9.tgz#1ecd96bc3471b06dd2f9763b6e53a7ace28a54a2"
@@ -1893,19 +1896,12 @@
"@babel/runtime" "^7.5.5" "@babel/runtime" "^7.5.5"
"@testing-library/dom" "^5.6.1" "@testing-library/dom" "^5.6.1"
"@testing-library/react@^9.3.2": "@testing-library/user-event@^12.1.0":
version "9.5.0" version "12.1.0"
resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-9.5.0.tgz#71531655a7890b61e77a1b39452fbedf0472ca5e" resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-12.1.0.tgz#a2597419466a93e338c91baa7bb22d4da0309d1d"
integrity sha512-di1b+D0p+rfeboHO5W7gTVeZDIK5+maEgstrZbWZSSvxDyfDRkkyBE1AJR5Psd6doNldluXlCWqXriUfqu/9Qg== integrity sha512-aH/XuNFpPD6dA+fh754EGqKeAzpH66HpLJYkv9vOAih2yGmTM8JiZ8uisQDGWRPkc6sxE2zCqDwLR4ZskhRCxw==
dependencies: dependencies:
"@babel/runtime" "^7.8.4" "@babel/runtime" "^7.10.2"
"@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==
"@types/aria-query@^4.2.0": "@types/aria-query@^4.2.0":
version "4.2.0" version "4.2.0"
@@ -1989,6 +1985,14 @@
"@types/istanbul-lib-coverage" "*" "@types/istanbul-lib-coverage" "*"
"@types/istanbul-lib-report" "*" "@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": "@types/json-schema@^7.0.3":
version "7.0.4" version "7.0.4"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339" 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" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
"@types/prop-types@*": "@types/prop-types@*", "@types/prop-types@^15.7.3":
version "15.7.3" version "15.7.3"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
@@ -2034,13 +2038,6 @@
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8" resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8"
integrity sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw== 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": "@types/react-transition-group@^4.2.0":
version "4.4.0" version "4.4.0"
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.0.tgz#882839db465df1320e4753e6e9f70ca7e9b4d46d" 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" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw== integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==
"@types/testing-library__dom@*": "@types/testing-library__jest-dom@^5.9.1":
version "7.5.0" version "5.9.2"
resolved "https://registry.yarnpkg.com/@types/testing-library__dom/-/testing-library__dom-7.5.0.tgz#e0a00dd766983b1d6e9d10d33e708005ce6ad13e" resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.9.2.tgz#59e4771a1cf87d51e89a5cc8195cd3b647cba322"
integrity sha512-mj1aH4cj3XUpMEgVpognma5kHVtbm6U6cHZmEFzCRiXPvKkuHrFr3+yXdGLXvfFRBaQIVshPGHI+hGTOJlhS/g== integrity sha512-K7nUSpH/5i8i0NagTJ+uFUDRueDlnMNhJtMjMwTGPPSqyImbWC/hgKPDCKt6Phu2iMJg2kWqlax+Ucj2DKMwpA==
dependencies: dependencies:
"@testing-library/dom" "*" "@types/jest" "*"
"@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/yargs-parser@*": "@types/yargs-parser@*":
version "15.0.0" version "15.0.0"
@@ -2312,13 +2293,20 @@
dependencies: dependencies:
tslib "^1.9.3" tslib "^1.9.3"
"@wry/equality@^0.1.2", "@wry/equality@^0.1.9": "@wry/equality@^0.1.2":
version "0.1.11" version "0.1.11"
resolved "https://registry.yarnpkg.com/@wry/equality/-/equality-0.1.11.tgz#35cb156e4a96695aa81a9ecc4d03787bc17f1790" resolved "https://registry.yarnpkg.com/@wry/equality/-/equality-0.1.11.tgz#35cb156e4a96695aa81a9ecc4d03787bc17f1790"
integrity sha512-mwEVBDUVODlsQQ5dfuLUS5/Tf7jqUKyhKYHmVi4fPB6bDMOfWvUPJmKgS1Z7Za/sOI3vzWt4+O7yCiL/70MogA== integrity sha512-mwEVBDUVODlsQQ5dfuLUS5/Tf7jqUKyhKYHmVi4fPB6bDMOfWvUPJmKgS1Z7Za/sOI3vzWt4+O7yCiL/70MogA==
dependencies: dependencies:
tslib "^1.9.3" 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": "@xtuc/ieee754@^1.2.0":
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" 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" ast-types-flow "0.0.7"
commander "^2.11.0" commander "^2.11.0"
aria-query@^4.0.2, aria-query@^4.2.2: aria-query@^4.2.2:
version "4.2.2" version "4.2.2"
resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b" resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b"
integrity sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA== 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" resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb"
integrity sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s= integrity sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=
css@^2.0.0, css@^2.2.3: css@^2.0.0:
version "2.2.4" version "2.2.4"
resolved "https://registry.yarnpkg.com/css/-/css-2.2.4.tgz#c646755c73971f2bba6a601e2cf2fd71b1298929" resolved "https://registry.yarnpkg.com/css/-/css-2.2.4.tgz#c646755c73971f2bba6a601e2cf2fd71b1298929"
integrity sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw== 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" source-map-resolve "^0.5.2"
urix "^0.1.0" 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: cssdb@^4.4.0:
version "4.4.0" version "4.4.0"
resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-4.4.0.tgz#3bf2f2a68c10f5c6a08abd92378331ee803cddb0" 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" resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5"
integrity sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew== 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: diffie-hellman@^5.0.0:
version "5.0.3" version "5.0.3"
resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875"
@@ -4411,11 +4413,6 @@ doctrine@^3.0.0:
dependencies: dependencies:
esutils "^2.0.2" 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: dom-accessibility-api@^0.4.6:
version "0.4.6" version "0.4.6"
resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.4.6.tgz#f3f2af68aee01b1c862f37918d41841bb1aaf92a" 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: dependencies:
locate-path "^3.0.0" locate-path "^3.0.0"
firebase@^7.17.0: firebase@^7.17.1:
version "7.17.0" version "7.17.1"
resolved "https://registry.yarnpkg.com/firebase/-/firebase-7.17.0.tgz#85dcd7c85d6dbcba7740dd5920a7f56bf8177e12" resolved "https://registry.yarnpkg.com/firebase/-/firebase-7.17.1.tgz#6b2566d91a820a7993e3d2c75435f8baaabb58bb"
integrity sha512-+y7c1pCj8xp98CIDhVjg0rKhGtsFskGB8hyhjsyp549Upwa0cropdK5emCFTmMIbvDjZmP8rTuuDXPBeREAaCg== integrity sha512-g2Wkk2fz8VoeSrxv2PIQizm2j74EtbpxQ+wd2AvH2iEF5LRaJOsk3zVBtIlyJIQ3vGTmlutIxtyyoDAQcPO9TA==
dependencies: dependencies:
"@firebase/analytics" "0.4.0" "@firebase/analytics" "0.4.1"
"@firebase/app" "0.6.8" "@firebase/app" "0.6.9"
"@firebase/app-types" "0.6.1" "@firebase/app-types" "0.6.1"
"@firebase/auth" "0.14.9" "@firebase/auth" "0.14.9"
"@firebase/database" "0.6.8" "@firebase/database" "0.6.9"
"@firebase/firestore" "1.16.1" "@firebase/firestore" "1.16.2"
"@firebase/functions" "0.4.48" "@firebase/functions" "0.4.49"
"@firebase/installations" "0.4.14" "@firebase/installations" "0.4.15"
"@firebase/messaging" "0.6.20" "@firebase/messaging" "0.6.21"
"@firebase/performance" "0.3.9" "@firebase/performance" "0.3.10"
"@firebase/polyfill" "0.3.36" "@firebase/polyfill" "0.3.36"
"@firebase/remote-config" "0.1.25" "@firebase/remote-config" "0.1.26"
"@firebase/storage" "0.3.40" "@firebase/storage" "0.3.41"
"@firebase/util" "0.2.50" "@firebase/util" "0.3.0"
flat-cache@^2.0.1: flat-cache@^2.0.1:
version "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" resolved "https://registry.yarnpkg.com/graphql-ast-types-browser/-/graphql-ast-types-browser-1.0.2.tgz#474305af7e76f9692df6e50a88fb668ce258c4a4"
integrity sha512-QuKZ+Et3dE7SyO5c41eNPlJc7+HwQxOzHfmIhqzj4cUgAGyhSwVkKb7K24zom8y6y0VnG7Xb3RRypjIVvfIevQ== 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" version "2.10.4"
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.10.4.tgz#2f301a98219be8b178a6453bb7e33b79b66d8f83" resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.10.4.tgz#2f301a98219be8b178a6453bb7e33b79b66d8f83"
integrity sha512-O7vG5BT3w6Sotc26ybcvLKNTdfr4GfsIVMD+LdYqXCeJIYPRyp8BIsDOUtxw7S1PYvRw5vH3278J2EDezR6mfA== 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: graphql@^15.3.0:
version "15.3.0" version "15.3.0"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.3.0.tgz#3ad2b0caab0d110e3be4a5a9b2aa281e362b5278" resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.3.0.tgz#3ad2b0caab0d110e3be4a5a9b2aa281e362b5278"
@@ -6109,7 +6111,7 @@ inflight@^1.0.4:
once "^1.3.0" once "^1.3.0"
wrappy "1" 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" version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -6663,7 +6665,7 @@ jest-config@^24.9.0:
pretty-format "^24.9.0" pretty-format "^24.9.0"
realpath-native "^1.1.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" version "24.9.0"
resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.9.0.tgz#931b7d0d5778a1baf7452cb816e325e3724055da" resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.9.0.tgz#931b7d0d5778a1baf7452cb816e325e3724055da"
integrity sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ== integrity sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ==
@@ -6673,6 +6675,16 @@ jest-diff@^24.0.0, jest-diff@^24.9.0:
jest-get-type "^24.9.0" jest-get-type "^24.9.0"
pretty-format "^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: jest-docblock@^24.3.0:
version "24.9.0" version "24.9.0"
resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-24.9.0.tgz#7970201802ba560e1c4092cc25cbedf5af5a8ce2" 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" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.9.0.tgz#1684a0c8a50f2e4901b6644ae861f579eed2ef0e"
integrity sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q== 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: jest-haste-map@^24.9.0:
version "24.9.0" version "24.9.0"
resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.9.0.tgz#b38a5d64274934e21fa417ae9a9fbeb77ceaac7d" 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" jest-get-type "^24.9.0"
pretty-format "^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" version "24.9.0"
resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.9.0.tgz#f5b3661d5e628dffe6dd65251dfdae0e87c3a073" resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.9.0.tgz#f5b3661d5e628dffe6dd65251dfdae0e87c3a073"
integrity sha512-OZz2IXsu6eaiMAwe67c1T+5tUAtQyQx27/EMEkbFAGiw52tB9em+uGbzpcgYVpA8wl0hlxKPZxrly4CXU/GjHA== 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" jest-get-type "^24.9.0"
pretty-format "^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: jest-message-util@^24.9.0:
version "24.9.0" version "24.9.0"
resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-24.9.0.tgz#527f54a1e380f5e202a8d1149b0ec872f43119e3" 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" renderkid "^2.0.1"
utila "~0.4" 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" version "24.9.0"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.9.0.tgz#12fac31b37019a4eea3c11aa9a959eb7628aa7c9" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.9.0.tgz#12fac31b37019a4eea3c11aa9a959eb7628aa7c9"
integrity sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA== 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" ansi-styles "^3.2.0"
react-is "^16.8.4" 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" version "25.5.0"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.5.0.tgz#7873c1d774f682c34b8d48b6743a2bf2ac55791a" 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== 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" resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e"
integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA== integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==
ra-core@^3.7.1: ra-core@^3.7.2:
version "3.7.1" version "3.7.2"
resolved "https://registry.yarnpkg.com/ra-core/-/ra-core-3.7.1.tgz#369453d8cce3a1e8ef7d01a6bb2dcd6bbd5faa1c" resolved "https://registry.yarnpkg.com/ra-core/-/ra-core-3.7.2.tgz#efbc873cfbf840c4c53e9a7317d6fd526bf55b39"
integrity sha512-T6gYppeTMoG4qbpD4cHJ76EQTuf0b6DedMSL8ekFuZdh+MDtpGywUlCzkcSQK9J4Ck31prtVvONXGm9uwEyEyg== integrity sha512-cm/RGWX9WUoVVTJkdKKhOOx6tG7IOaY9p+YxbFvweau0Gi9sK+i9t687ifrENukW2YCxIeKpyImjKAT7l8BiLA==
dependencies: dependencies:
"@testing-library/react" "^8.0.7" "@testing-library/react" "^8.0.7"
classnames "~2.2.5" classnames "~2.2.5"
@@ -9601,25 +9628,25 @@ ra-data-hasura-graphql@^0.1.12:
minimist ">=1.2.3" minimist ">=1.2.3"
ra-data-graphql "^3.6.1" ra-data-graphql "^3.6.1"
ra-i18n-polyglot@^3.7.1: ra-i18n-polyglot@^3.7.2:
version "3.7.1" version "3.7.2"
resolved "https://registry.yarnpkg.com/ra-i18n-polyglot/-/ra-i18n-polyglot-3.7.1.tgz#68ae7ec78c43f2700dd0921d7263546ae35c5b34" resolved "https://registry.yarnpkg.com/ra-i18n-polyglot/-/ra-i18n-polyglot-3.7.2.tgz#66589baf838a24ab71f251b1c41ec75bcc294f1f"
integrity sha512-BWYJKGp0nZP8ATsYqfWkvQpyaVx4KrYw/XT6Taf8HfhfPqZaiOQUhfw4a9co3Nm/0Gu3mFdHq5CVMDsEZL1r+w== integrity sha512-WFb6tw/lBnmne6BZit3bO0R45jTRBIkbd/8UMNMRBpZn9vFdphF9NShrNuVPLGnxQPBoz0CBRSTEvyKX7HGnTg==
dependencies: dependencies:
node-polyglot "^2.2.2" node-polyglot "^2.2.2"
ra-core "^3.7.1" ra-core "^3.7.2"
ra-language-english@^3.7.1: ra-language-english@^3.7.2:
version "3.7.1" version "3.7.2"
resolved "https://registry.yarnpkg.com/ra-language-english/-/ra-language-english-3.7.1.tgz#ed1aa4dd827454572fcc43a53c3a2944d5619d68" resolved "https://registry.yarnpkg.com/ra-language-english/-/ra-language-english-3.7.2.tgz#19a929cba73c03d34aa0586145005a4759cc2c81"
integrity sha512-85jgvpEdRgfQ1SVVVudUh7AOlmhbiLhT/iLn5tXO9N7HCudK7Fno37y+txjrPywki6Vs/XGkRLG/eNsrYmyMFQ== integrity sha512-1TQPTDgJ4gvF6uh7FcJhWqRUmi7P4Mo1Oy5e57BkrFO6M50Fl+e3McrI0grDQlSbn2bcFRNm13tWK/Z+KXdGeQ==
dependencies: dependencies:
ra-core "^3.7.1" ra-core "^3.7.2"
ra-ui-materialui@^3.7.1: ra-ui-materialui@^3.7.2:
version "3.7.1" version "3.7.2"
resolved "https://registry.yarnpkg.com/ra-ui-materialui/-/ra-ui-materialui-3.7.1.tgz#111883d4d27c197f3171e58a836811839619f509" resolved "https://registry.yarnpkg.com/ra-ui-materialui/-/ra-ui-materialui-3.7.2.tgz#1afeb943afc1940787e91094f4fbca72cd082f4c"
integrity sha512-EA5z/2fnqv1HTJuzXCoxZoCIgohHY+C+zVjDjEBQ/ABa/PosZT+epOsKK9Gn4gLuHbLHJCwdjcOZXJHH6F1jiQ== integrity sha512-dNmgkZ2YDmXMrCRLLhSBBjSzM9B1x5qsSU0C4az2LyDnniphE4UOjkUCSj0DpgzVC4sevZub5H7QnjpIF3LZcQ==
dependencies: dependencies:
autosuggest-highlight "^3.1.1" autosuggest-highlight "^3.1.1"
classnames "~2.2.5" classnames "~2.2.5"
@@ -9632,7 +9659,7 @@ ra-ui-materialui@^3.7.1:
prop-types "^15.7.0" prop-types "^15.7.0"
query-string "^5.1.1" query-string "^5.1.1"
react-dropzone "^10.1.7" react-dropzone "^10.1.7"
react-transition-group "^4.3.0" react-transition-group "^4.4.1"
recompose "~0.26.0" recompose "~0.26.0"
raf@^3.4.1: raf@^3.4.1:
@@ -9672,10 +9699,10 @@ raw-body@2.4.0:
iconv-lite "0.4.24" iconv-lite "0.4.24"
unpipe "1.0.0" unpipe "1.0.0"
react-admin@^3.7.1: react-admin@^3.7.2:
version "3.7.1" version "3.7.2"
resolved "https://registry.yarnpkg.com/react-admin/-/react-admin-3.7.1.tgz#315573908361678125085005589c51512be2235e" resolved "https://registry.yarnpkg.com/react-admin/-/react-admin-3.7.2.tgz#f1c2895c208c06f63bc8f5eb952ad232a225de18"
integrity sha512-eVOdvh7HIc83BmoFjZNTcRU5l9XNhk6rHn5vBu1zcl5i//91jPw0NTS1uxG18STjiERckh/DVphELC5tFRIGwA== integrity sha512-VK99I+s1Dd1f4lFCUY3BRdTO6P1c6Yg1r/px0ZbbM6WyJ7+qFRVoXEhDlIB494WEcjZoJKZSDB464fyxePEWtA==
dependencies: dependencies:
"@material-ui/core" "^4.3.3" "@material-ui/core" "^4.3.3"
"@material-ui/icons" "^4.2.1" "@material-ui/icons" "^4.2.1"
@@ -9683,10 +9710,10 @@ react-admin@^3.7.1:
connected-react-router "^6.5.2" connected-react-router "^6.5.2"
final-form "^4.18.5" final-form "^4.18.5"
final-form-arrays "^3.0.1" final-form-arrays "^3.0.1"
ra-core "^3.7.1" ra-core "^3.7.2"
ra-i18n-polyglot "^3.7.1" ra-i18n-polyglot "^3.7.2"
ra-language-english "^3.7.1" ra-language-english "^3.7.2"
ra-ui-materialui "^3.7.1" ra-ui-materialui "^3.7.2"
react-final-form "^6.3.3" react-final-form "^6.3.3"
react-final-form-arrays "^3.1.1" react-final-form-arrays "^3.1.1"
react-redux "^7.1.0" react-redux "^7.1.0"
@@ -9775,6 +9802,13 @@ react-final-form@^6.3.3:
dependencies: dependencies:
"@babel/runtime" "^7.10.0" "@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: 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" version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@@ -9880,7 +9914,7 @@ react-scripts@3.4.1:
optionalDependencies: optionalDependencies:
fsevents "2.1.2" 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" version "4.4.1"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9"
integrity sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw== 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" source-map-url "^0.4.0"
urix "^0.1.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: source-map-support@^0.5.6, source-map-support@~0.5.12:
version "0.5.16" version "0.5.16"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042" 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" resolved "https://registry.yarnpkg.com/wait-for-expect/-/wait-for-expect-1.3.0.tgz#65241ce355425f907f5d127bdb5e72c412ff830c"
integrity sha512-8fJU7jiA96HfGPt+P/UilelSAZfhMBJ52YhKzlmZQvKEZU2EcD1GQ0yqGB6liLdHjYtYAoGVigYwdxr5rktvzA== 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: walker@^1.0.7, walker@~1.0.5:
version "1.0.7" version "1.0.7"
resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" 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 BabelEdit project file
@@ -526,6 +526,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </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> <concept_node>
<name>history</name> <name>history</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -547,6 +568,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </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> <concept_node>
<name>nodateselected</name> <name>nodateselected</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -1190,6 +1232,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </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> <concept_node>
<name>federal_tax_id</name> <name>federal_tax_id</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -1337,6 +1400,69 @@
</translation> </translation>
</translations> </translations>
</concept_node> </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> <concept_node>
<name>md_referral_sources</name> <name>md_referral_sources</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -1463,6 +1589,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </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> <concept_node>
<name>responsibilitycenter</name> <name>responsibilitycenter</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -3000,6 +3147,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </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> <folder_node>
<name>responsibilitycenters</name> <name>responsibilitycenters</name>
<children> <children>
@@ -6711,6 +6879,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </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> <concept_node>
<name>in</name> <name>in</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -7194,6 +7383,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </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> <concept_node>
<name>yes</name> <name>yes</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -7309,6 +7519,111 @@
</translation> </translation>
</translations> </translations>
</concept_node> </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> <concept_node>
<name>unsavedchanges</name> <name>unsavedchanges</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -9985,6 +10300,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </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> <concept_node>
<name>reconcile</name> <name>reconcile</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -10048,6 +10384,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </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> </children>
</folder_node> </folder_node>
<folder_node> <folder_node>
@@ -10499,6 +10856,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </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> <concept_node>
<name>ccc</name> <name>ccc</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -10646,6 +11024,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </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> <concept_node>
<name>clm_no</name> <name>clm_no</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -13003,6 +13402,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </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> <concept_node>
<name>towing_payable</name> <name>towing_payable</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -13176,27 +13596,6 @@
</translation> </translation>
</translations> </translations>
</concept_node> </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> <concept_node>
<name>estdates</name> <name>estdates</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -13218,27 +13617,6 @@
</translation> </translation>
</translations> </translations>
</concept_node> </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> <concept_node>
<name>laborrates</name> <name>laborrates</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -13281,6 +13659,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </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> <concept_node>
<name>repairdates</name> <name>repairdates</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -13711,6 +14110,48 @@
</translation> </translation>
</translations> </translations>
</concept_node> </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> <folder_node>
<name>create</name> <name>create</name>
<children> <children>
@@ -14204,6 +14645,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </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> <concept_node>
<name>lines</name> <name>lines</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -16004,28 +16466,7 @@
</translations> </translations>
</concept_node> </concept_node>
<concept_node> <concept_node>
<name>financials</name> <name>general</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>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
<description></description> <description></description>
<comment></comment> <comment></comment>
@@ -16087,6 +16528,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </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> <concept_node>
<name>repairdata</name> <name>repairdata</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -16108,6 +16570,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </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> </children>
</folder_node> </folder_node>
<folder_node> <folder_node>
@@ -19025,6 +19508,32 @@
</concept_node> </concept_node>
</children> </children>
</folder_node> </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> </children>
</folder_node> </folder_node>
<folder_node> <folder_node>
@@ -20385,6 +20894,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </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> <concept_node>
<name>accounting-receivables</name> <name>accounting-receivables</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -20451,6 +20981,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </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> <concept_node>
<name>accounting-receivables</name> <name>accounting-receivables</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>

View File

@@ -2,66 +2,69 @@
"name": "bodyshop", "name": "bodyshop",
"version": "0.1.0001", "version": "0.1.0001",
"private": true, "private": true,
"proxy": "https://localhost:5000", "proxy": "http://localhost:5000",
"dependencies": { "dependencies": {
"@lourenci/react-kanban": "^2.0.0", "@lourenci/react-kanban": "^2.0.0",
"@stripe/react-stripe-js": "^1.1.2", "@stripe/react-stripe-js": "^1.1.2",
"@stripe/stripe-js": "^1.8.0", "@stripe/stripe-js": "^1.9.0",
"@tanem/react-nprogress": "^3.0.34", "@tanem/react-nprogress": "^3.0.40",
"@tinymce/tinymce-react": "^3.6.0", "@tinymce/tinymce-react": "^3.6.0",
"antd": "^4.4.2", "antd": "^4.6.1",
"apollo-boost": "^0.4.9", "apollo-boost": "^0.4.9",
"apollo-link-context": "^1.0.20", "apollo-link-context": "^1.0.20",
"apollo-link-error": "^1.1.13", "apollo-link-error": "^1.1.13",
"apollo-link-logger": "^1.2.3", "apollo-link-logger": "^1.2.3",
"apollo-link-retry": "^2.2.16", "apollo-link-retry": "^2.2.16",
"apollo-link-ws": "^1.0.20", "apollo-link-ws": "^1.0.20",
"axios": "^0.19.2", "axios": "^0.20.0",
"dinero.js": "^1.8.1", "dinero.js": "^1.8.1",
"dotenv": "^8.2.0", "dotenv": "^8.2.0",
"fingerprintjs2": "^2.1.0", "fingerprintjs2": "^2.1.2",
"firebase": "^7.16.0", "firebase": "^7.19.0",
"graphql": "^15.3.0", "graphql": "^15.3.0",
"i18next": "^19.6.0", "i18next": "^19.7.0",
"i18next-browser-languagedetector": "^5.0.0", "i18next-browser-languagedetector": "^6.0.1",
"logrocket": "^1.0.9", "inline-css": "^2.6.3",
"logrocket": "^1.0.11",
"moment-business-days": "^1.2.0", "moment-business-days": "^1.2.0",
"node-sass": "^4.14.1", "node-sass": "^4.14.1",
"phone": "^2.4.13", "phone": "^2.4.15",
"prop-types": "^15.7.2",
"query-string": "^6.13.1", "query-string": "^6.13.1",
"react": "^16.13.1", "react": "^16.13.1",
"react-apollo": "^3.1.5", "react-apollo": "^3.1.5",
"react-barcode": "^1.4.0", "react-barcode": "^1.4.0",
"react-big-calendar": "^0.26.0", "react-big-calendar": "^0.26.1",
"react-dom": "^16.13.1", "react-dom": "^16.13.1",
"react-drag-listview": "^0.1.7", "react-drag-listview": "^0.1.7",
"react-email-editor": "^1.1.1",
"react-ga": "^3.1.2", "react-ga": "^3.1.2",
"react-grid-gallery": "^0.5.5", "react-grid-gallery": "^0.5.5",
"react-grid-layout": "^0.18.3", "react-grid-layout": "^1.0.0",
"react-i18next": "^11.7.0", "react-i18next": "^11.7.1",
"react-icons": "^3.10.0", "react-icons": "^3.11.0",
"react-image-file-resizer": "^0.3.1", "react-image-file-resizer": "^0.3.6",
"react-moment": "^0.9.7", "react-moment": "^0.9.7",
"react-number-format": "^4.4.1", "react-number-format": "^4.4.1",
"react-redux": "^7.2.0", "react-redux": "^7.2.1",
"react-resizable": "^1.10.1", "react-resizable": "^1.10.1",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"react-scripts": "3.4.1", "react-scripts": "3.4.3",
"react-trello": "^2.2.7", "react-trello": "^2.2.8",
"react-virtualized": "^9.21.2", "react-virtualized": "^9.22.2",
"recharts": "^1.8.5", "recharts": "^1.8.5",
"redux": "^4.0.5", "redux": "^4.0.5",
"redux-persist": "^6.0.0", "redux-persist": "^6.0.0",
"redux-saga": "^1.1.3", "redux-saga": "^1.1.3",
"redux-state-sync": "^3.1.1", "redux-state-sync": "^3.1.2",
"reselect": "^4.0.0", "reselect": "^4.0.0",
"styled-components": "^5.1.1", "styled-components": "^5.1.1",
"subscriptions-transport-ws": "^0.9.17" "subscriptions-transport-ws": "^0.9.18"
}, },
"scripts": { "scripts": {
"analyze": "source-map-explorer 'build/static/js/*.js'", "analyze": "source-map-explorer 'build/static/js/*.js'",
"start": "react-scripts start", "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", "test": "react-scripts test",
"eject": "react-scripts eject" "eject": "react-scripts eject"
}, },
@@ -81,10 +84,10 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"@apollo/react-testing": "^3.1.4", "@apollo/react-testing": "^4.0.0",
"enzyme": "^3.11.0", "enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.2", "enzyme-adapter-react-16": "^1.15.3",
"redux-logger": "^3.0.6", "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 { ApolloProvider } from "@apollo/react-common";
import { ConfigProvider } from "antd";
import enLocale from "antd/es/locale/en_US";
import { ApolloLink } from "apollo-boost"; import { ApolloLink } from "apollo-boost";
import { InMemoryCache } from "apollo-cache-inmemory"; import { InMemoryCache } from "apollo-cache-inmemory";
import ApolloClient from "apollo-client"; import ApolloClient from "apollo-client";
@@ -9,18 +11,32 @@ import apolloLogger from "apollo-link-logger";
import { RetryLink } from "apollo-link-retry"; import { RetryLink } from "apollo-link-retry";
import { WebSocketLink } from "apollo-link-ws"; import { WebSocketLink } from "apollo-link-ws";
import { getMainDefinition } from "apollo-utilities"; import { getMainDefinition } from "apollo-utilities";
import axios from "axios";
import LogRocket from "logrocket"; import LogRocket from "logrocket";
import moment from "moment";
import React from "react"; import React from "react";
import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component"; import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
import { auth } from "../firebase/firebase.utils"; import { auth } from "../firebase/firebase.utils";
import errorLink from "../graphql/apollo-error-handling"; import errorLink from "../graphql/apollo-error-handling";
import App from "./App"; import App from "./App";
import { ConfigProvider } from "antd";
import enLocale from "antd/es/locale/en_US";
import moment from "moment";
moment.locale("en-US"); 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"); if (process.env.NODE_ENV === "production") LogRocket.init("gvfvfw/bodyshopapp");
const httpLink = new HttpLink({ const httpLink = new HttpLink({

View File

@@ -1,4 +1,3 @@
import { Grid } from "antd";
import "antd/dist/antd.css"; import "antd/dist/antd.css";
import React, { lazy, Suspense, useEffect } from "react"; import React, { lazy, Suspense, useEffect } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -20,9 +19,7 @@ const ResetPassword = lazy(() =>
); );
const ManagePage = lazy(() => import("../pages/manage/manage.page.container")); const ManagePage = lazy(() => import("../pages/manage/manage.page.container"));
const SignInPage = lazy(() => import("../pages/sign-in/sign-in.page")); 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 CsiPage = lazy(() => import("../pages/csi/csi.container.page"));
const MobilePaymentContainer = lazy(() => const MobilePaymentContainer = lazy(() =>
import("../pages/mobile-payment/mobile-payment.container") import("../pages/mobile-payment/mobile-payment.container")
@@ -40,8 +37,8 @@ export function App({ checkUserSession, currentUser }) {
checkUserSession(); checkUserSession();
}, [checkUserSession]); }, [checkUserSession]);
const b = Grid.useBreakpoint(); //const b = Grid.useBreakpoint();
console.log("Breakpoints:", b); // console.log("Breakpoints:", b);
const { t } = useTranslation(); const { t } = useTranslation();
@@ -52,30 +49,41 @@ export function App({ checkUserSession, currentUser }) {
return ( return (
<div> <div>
<Switch> <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="/" component={LandingPage} />
<Route exact path="/unauthorized" component={Unauthorized} /> </ErrorBoundary>
<ErrorBoundary>
<Route exact path="/signin" component={SignInPage} /> <Route exact path="/signin" component={SignInPage} />
</ErrorBoundary>
<ErrorBoundary>
<Route exact path="/resetpassword" component={ResetPassword} /> <Route exact path="/resetpassword" component={ResetPassword} />
</ErrorBoundary>
<ErrorBoundary>
<Route exact path="/csi/:surveyId" component={CsiPage} /> <Route exact path="/csi/:surveyId" component={CsiPage} />
</ErrorBoundary>
<ErrorBoundary>
<Route <Route
exact exact
path="/mp/:paymentIs" path="/mp/:paymentIs"
component={MobilePaymentContainer} component={MobilePaymentContainer}
/> />
</ErrorBoundary>
<ErrorBoundary>
<PrivateRoute <PrivateRoute
isAuthorized={currentUser.authorized} isAuthorized={currentUser.authorized}
path="/manage" path="/manage"
component={ManagePage} component={ManagePage}
/> />
</ErrorBoundary>
<ErrorBoundary>
<PrivateRoute <PrivateRoute
isAuthorized={currentUser.authorized} isAuthorized={currentUser.authorized}
path="/tech" path="/tech"
component={TechPageContainer} component={TechPageContainer}
/> />
</Suspense> </ErrorBoundary>
</ErrorBoundary> </Suspense>
</Switch> </Switch>
</div> </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 { import Axios from "axios";
PaymentRequestButtonElement, import React from "react";
useStripe export default function Test() {
} from "@stripe/react-stripe-js"; const handleQbSignIn = async () => {
import React, { useEffect, useState } from "react"; const result = await Axios.post("/qbo/authorize", { userId: "1234" });
import { connect } from "react-redux"; console.log("handleQbSignIn -> result", result.data);
import { createStructuredSelector } from "reselect"; // window.open(result.data, "_blank", "toolbar=0,location=0,menubar=0");
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({ var parameters = "location=1,width=800,height=650";
bodyshop: selectBodyshop, parameters +=
}); ",left=" +
(window.screen.width - 800) / 2 +
",top=" +
(window.screen.height - 650) / 2;
const mapDispatchToProps = (dispatch) => ({ // Launch Popup
setEmailOptions: (e) => dispatch(setEmailOptions(e)), window.open(result.data, "connectPopup", parameters);
}); };
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 ( return (
<div> <div>
<button <button onClick={handleQbSignIn}>Sign Into Qb.</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> </div>
); );
} }
export default connect(mapStateToProps, mapDispatchToProps)(Test);

View File

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

View File

@@ -63,10 +63,14 @@ export default function AccountingReceivablesTableComponent({ loading, jobs }) {
render: (text, record) => { render: (text, record) => {
return record.owner ? ( return record.owner ? (
<Link to={"/manage/owners/" + record.owner.id}> <Link to={"/manage/owners/" + record.owner.id}>
{`${record.ownr_fn || ""} ${record.ownr_ln || ""}`} {`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${
record.ownr_co_nm || ""
}`}
</Link> </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: {}, assignment: {},
setAssignment: jest.fn(), setAssignment: jest.fn(),
visibilityState: [false, jest.fn()], visibilityState: [false, jest.fn()],
maxHours: 4 maxHours: 4,
}; };
wrapper = mount(<AllocationsAssignmentComponent {...mockProps} />); wrapper = mount(<AllocationsAssignmentComponent {...mockProps} />);
@@ -27,7 +27,6 @@ describe("AllocationsAssignmentComponent component", () => {
it("should render a list of employees", () => { it("should render a list of employees", () => {
const empList = wrapper.find("#employeeSelector"); const empList = wrapper.find("#employeeSelector");
console.log(empList.debug());
expect(empList.children()).to.have.lengthOf(2); expect(empList.children()).to.have.lengthOf(2);
}); });

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,15 +3,15 @@ import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component"; 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 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 FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.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(); const { t } = useTranslation();
return ( return (
<div> <div>
<Button type="primary" htmlType="submit"> <Button type="primary" loading={saveLoading} htmlType="submit">
{t("general.actions.save")} {t("general.actions.save")}
</Button> </Button>
<div className="imex-flex-row__grow imex-flex-row__margin-large"> <div className="imex-flex-row__grow imex-flex-row__margin-large">

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,30 +1,40 @@
import Icon, { import Icon, {
ClockCircleFilled,
CarFilled, CarFilled,
ClockCircleFilled,
DollarCircleFilled, DollarCircleFilled,
FileAddFilled, FileAddFilled,
FileFilled, FileFilled,
GlobalOutlined, GlobalOutlined,
HomeFilled, HomeFilled,
ImportOutlined,
LineChartOutlined,
ScheduleOutlined,
TeamOutlined, TeamOutlined,
UnorderedListOutlined,
UserOutlined, UserOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { Avatar, Col, Layout, Menu, Row } from "antd"; import { Avatar, Layout, Menu } from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; 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 { connect } from "react-redux";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectRecentItems } from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
import { signOutStart } from "../../redux/user/user.actions"; import { signOutStart } from "../../redux/user/user.actions";
import { import {
selectBodyshop, selectBodyshop,
selectCurrentUser, selectCurrentUser,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import "./header.styles.scss";
import GlobalSearch from "../global-search/global-search.component"; import GlobalSearch from "../global-search/global-search.component";
import { selectRecentItems } from "../../redux/application/application.selectors"; import "./header.styles.scss";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
@@ -42,30 +52,6 @@ const mapDispatchToProps = (dispatch) => ({
signOutStart: () => dispatch(signOutStart()), 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({ function Header({
bodyshop, bodyshop,
handleMenuClick, handleMenuClick,
@@ -81,278 +67,276 @@ function Header({
return ( return (
<Header> <Header>
<Row> <Menu
<Col {...logoSpan}> mode="horizontal"
<img theme="dark"
className="header-shop-logo" className="header-main-menu"
alt={bodyshop ? bodyshop.shopname : "ImEX Online Logo"} selectedKeys={["home"]}
src={ onClick={handleMenuClick}
bodyshop && bodyshop.logo_img_path >
? bodyshop.logo_img_path <Menu.Item key="home">
: "./logo192.png" <Link to="/manage">
} <HomeFilled />
/> {t("menus.header.home")}
</Col> </Link>
<Col {...menuSpan}> </Menu.Item>
<Menu <Menu.Item key="schedule">
mode="horizontal" <Link to="/manage/schedule">
theme="dark" <Icon component={FaCalendarAlt} />
className="header-main-menu" {t("menus.header.schedule")}
selectedKeys={["home"]} </Link>
onClick={handleMenuClick} </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"> <Icon component={FaFileInvoiceDollar} />
<Link to="/manage"> {t("menus.header.enterinvoices")}
<HomeFilled /> </Menu.Item>
{t("menus.header.home")} <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> </Link>
</Menu.Item> </Menu.Item>
<Menu.SubMenu <Menu.Item key="payables">
title={ <Link to="/manage/accounting/payables">
<span> {t("menus.header.accounting-payables")}
<Icon component={FaCarCrash} /> </Link>
<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> </Menu.Item>
<Menu.SubMenu title={<ClockCircleFilled />}> <Menu.Item key="payments">
{recentItems.map((i, idx) => ( <Link to="/manage/accounting/payments">
<Menu.Item key={idx}> {t("menus.header.accounting-payments")}
<Link to={i.url}>{i.label}</Link> </Link>
</Menu.Item> </Menu.Item>
))} </Menu.SubMenu>
</Menu.SubMenu> </Menu.SubMenu>
<Menu.SubMenu <Menu.SubMenu title={t("menus.header.shop")}>
title={ <Menu.Item key="shop">
<div> <Link to="/manage/shop">{t("menus.header.shop_config")}</Link>
{currentUser.photoURL ? ( </Menu.Item>
<Avatar
src={currentUser.photoURL}
style={{
margin: "10px",
}}
/>
) : (
<Avatar
style={{
backgroundColor: "#87d068",
margin: "10px",
}}
icon={<UserOutlined />}
/>
)}
{currentUser.displayName || t("general.labels.unknown")} <Menu.Item key="shop-templates">
</div> <Link to="/manage/shop/templates">
} {t("menus.header.shop_templates")}
> </Link>
<Menu.Item danger onClick={() => signOutStart()}> </Menu.Item>
{t("user.actions.signout")}
</Menu.Item> <Menu.Item key="shop-vendors">
<Menu.Item key="shiftclock"> <Link to="/manage/shop/vendors">
<Link to="/manage/shiftclock"> {t("menus.header.shop_vendors")}
{t("menus.header.shiftclock")} </Link>
</Link> </Menu.Item>
</Menu.Item> <Menu.Item key="shop-csi">
<Menu.Item> <Link to="/manage/shop/csi">{t("menus.header.shop_csi")}</Link>
<Link to="/manage/profile"> </Menu.Item>
{t("menus.currentuser.profile")} </Menu.SubMenu>
</Link> <Menu.Item>
</Menu.Item> <GlobalSearch />
<Menu.SubMenu </Menu.Item>
title={ <Menu.SubMenu title={<ClockCircleFilled />}>
<span> {recentItems.map((i, idx) => (
<GlobalOutlined /> <Menu.Item key={idx}>
<span>{t("menus.currentuser.languageselector")}</span> <Link to={i.url}>{i.label}</Link>
</span> </Menu.Item>
} ))}
> </Menu.SubMenu>
<Menu.Item actiontype="lang-select" key="en-US"> <Menu.SubMenu
{t("general.languages.english")} title={
</Menu.Item> <div>
<Menu.Item actiontype="lang-select" key="fr-CA"> {currentUser.photoURL ? (
{t("general.languages.french")} <Avatar
</Menu.Item> src={currentUser.photoURL}
<Menu.Item actiontype="lang-select" key="es-MX"> style={{
{t("general.languages.spanish")} margin: "10px",
</Menu.Item> }}
</Menu.SubMenu> />
</Menu.SubMenu> ) : (
</Menu> <Avatar
</Col> style={{
</Row> 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> </Header>
); );
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -208,19 +208,12 @@ export function InvoicesListTableComponent({
]; ];
const handleOnInvoiceRowclick = (selectedRows) => { const handleOnInvoiceRowclick = (selectedRows) => {
console.log("selectedRows", selectedRows);
console.log("record.id", record.id);
setSelectedInvoiceLinesByInvoice({ setSelectedInvoiceLinesByInvoice({
...selectedInvoiceLinesByInvoice, ...selectedInvoiceLinesByInvoice,
[record.id]: selectedRows.map((r) => r.id), [record.id]: selectedRows.map((r) => r.id),
}); });
}; };
console.log(
"selectedInvoiceLinesByInvoice[record.id]",
selectedInvoiceLinesByInvoice[record.id]
);
return ( return (
<div> <div>
<Typography.Title level={3}>{`${t("invoices.fields.invoice_number")} ${ <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( const jobLineTotalsByProfitCenter = job.joblines.reduce(
(acc, val) => { (acc, val) => {
const laborProfitCenter = defaultProfits[val.mod_lbr_ty]; const laborProfitCenter = defaultProfits[val.mod_lbr_ty] || "?";
if (!!!laborProfitCenter)
console.log(
"Unknown cost/profit center mapping for labor.",
val.mod_lbr_ty
);
const rateName = `rate_${(val.mod_lbr_ty || "").toLowerCase()}`; const rateName = `rate_${(val.mod_lbr_ty || "").toLowerCase()}`;
const laborAmount = Dinero({ const laborAmount = Dinero({
@@ -36,7 +31,7 @@ export function JobCostingModalComponent({ bodyshop, job }) {
laborAmount laborAmount
); );
const partsProfitCenter = defaultProfits[val.part_type]; const partsProfitCenter = defaultProfits[val.part_type] || "?";
if (!!!partsProfitCenter) if (!!!partsProfitCenter)
console.log( console.log(
"Unknown cost/profit center mapping for parts.", "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 = { const summaryData = {
totalLaborSales: Dinero({ amount: 0 }), totalLaborSales: Dinero({ amount: 0 }),
totalPartsSales: Dinero({ amount: 0 }), totalPartsSales: Dinero({ amount: 0 }),
totalSales: Dinero({ amount: 0 }), totalSales: Dinero({ amount: 0 }),
totalLaborCost: Dinero({ amount: 0 }),
totalPartsCost: Dinero({ amount: 0 }),
totalCost: Dinero({ amount: 0 }), totalCost: Dinero({ amount: 0 }),
gpdollars: Dinero({ amount: 0 }), gpdollars: Dinero({ amount: 0 }),
gppercent: null, gppercent: null,
@@ -95,7 +111,15 @@ export function JobCostingModalComponent({ bodyshop, job }) {
jobLineTotalsByProfitCenter.labor[ccVal] || Dinero({ amount: 0 }); jobLineTotalsByProfitCenter.labor[ccVal] || Dinero({ amount: 0 });
const sale_parts = const sale_parts =
jobLineTotalsByProfitCenter.parts[ccVal] || Dinero({ amount: 0 }); 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 totalSales = sale_labor.add(sale_parts);
const gpdollars = totalSales.subtract(cost); const gpdollars = totalSales.subtract(cost);
const gppercent = ( const gppercent = (
@@ -115,6 +139,8 @@ export function JobCostingModalComponent({ bodyshop, job }) {
summaryData.totalSales = summaryData.totalSales summaryData.totalSales = summaryData.totalSales
.add(sale_labor) .add(sale_labor)
.add(sale_parts); .add(sale_parts);
summaryData.totalLaborCost = summaryData.totalLaborCost.add(cost_labor);
summaryData.totalPartsCost = summaryData.totalPartsCost.add(cost_parts);
summaryData.totalCost = summaryData.totalCost.add(cost); summaryData.totalCost = summaryData.totalCost.add(cost);
return { return {
@@ -122,6 +148,8 @@ export function JobCostingModalComponent({ bodyshop, job }) {
cost_center: ccVal, cost_center: ccVal,
sale_labor: sale_labor && sale_labor.toFormat(), sale_labor: sale_labor && sale_labor.toFormat(),
sale_parts: sale_parts && sale_parts.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(), cost: cost && cost.toFormat(),
gpdollars: gpdollars.toFormat(), gpdollars: gpdollars.toFormat(),
gppercent: gppercentFormatted, gppercent: gppercentFormatted,
@@ -143,8 +171,6 @@ export function JobCostingModalComponent({ bodyshop, job }) {
summaryData.gppercentFormatted = summaryData.gppercent; summaryData.gppercentFormatted = summaryData.gppercent;
} }
console.log("JobCostingModalComponent -> summaryData", summaryData);
return ( return (
<div> <div>
<JobCostingStatistics job={job} summaryData={summaryData} /> <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, state.sortedInfo.columnKey === "sale_parts" && state.sortedInfo.order,
}, },
{ {
title: t("jobs.labels.cost"), title: t("jobs.labels.cost_labor"),
dataIndex: "cost", dataIndex: "cost_labor",
key: "cost", key: "cost_labor",
sorter: (a, b) => a.cost - b.cost, sorter: (a, b) => a.cost_labor - b.cost_labor,
sortOrder: 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"), title: t("jobs.labels.gpdollars"),

View File

@@ -20,6 +20,14 @@ export default function JobCostingStatistics({ job, summaryData }) {
value={summaryData.totalSales.toFormat()} value={summaryData.totalSales.toFormat()}
title={t("jobs.labels.total_sales")} 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 <Statistic
value={summaryData.totalCost.toFormat()} value={summaryData.totalCost.toFormat()}
title={t("jobs.labels.total_cost")} title={t("jobs.labels.total_cost")}

View File

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

View File

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

View File

@@ -17,7 +17,7 @@ export default function JobDetailCardsNotesComponent({ loading, data }) {
<CardTemplate <CardTemplate
loading={loading} loading={loading}
title={t("jobs.labels.cards.notes")} title={t("jobs.labels.cards.notes")}
extraLink={`/manage/jobs/${data.id}?notes`} extraLink={`/manage/jobs/${data.id}?tab=notes`}
> >
{data ? ( {data ? (
<Container> <Container>
@@ -25,7 +25,7 @@ export default function JobDetailCardsNotesComponent({ loading, data }) {
size="small" size="small"
bordered bordered
dataSource={data.notes} dataSource={data.notes}
renderItem={item => ( renderItem={(item) => (
<List.Item> <List.Item>
{item.critical ? ( {item.critical ? (
<EyeInvisibleFilled style={{ margin: 4, color: "red" }} /> <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 { useTranslation } from "react-i18next";
import { Pie, PieChart, Sector } from "recharts";
import CardTemplate from "./job-detail-cards.template.component"; import CardTemplate from "./job-detail-cards.template.component";
export default function JobDetailCardsPartsComponent({ loading, data }) { export default function JobDetailCardsPartsComponent({ loading, data }) {
const { t } = useTranslation(); 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 ( return (
<div> <div>
<CardTemplate loading={loading} title={t("jobs.labels.cards.parts")}> <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> </CardTemplate>
</div> </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 ( return (
<CardTemplate loading={loading} title={t("jobs.labels.cards.totals")}> <CardTemplate loading={loading} title={t("jobs.labels.cards.totals")}>
{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 <Statistic
className='imex-flex-row__margin-large' className="imex-flex-row__margin-large"
title={t("jobs.labels.total_repairs")} title={t("jobs.labels.total_repairs")}
value={Dinero(totals.totals.total_repairs).toFormat()} value={Dinero(totals.totals.total_repairs).toFormat()}
/> />
<Statistic <Statistic
className='imex-flex-row__margin-large' className="imex-flex-row__margin-large"
title={t("jobs.fields.ded_amt")} title={t("jobs.fields.ded_amt")}
value={Dinero({ value={Dinero({
amount: Math.round((data.ded_amt || 0) * 100), amount: Math.round((data.ded_amt || 0) * 100),
}).toFormat()} }).toFormat()}
/> />
<Statistic <Statistic
className='imex-flex-row__margin-large' className="imex-flex-row__margin-large"
title={t("jobs.labels.net_repairs")} title={t("jobs.labels.net_repairs")}
value={Dinero(totals.totals.net_repairs).toFormat()} value={Dinero(totals.totals.net_repairs).toFormat()}
/> />

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,24 +1,38 @@
.job-totals-container { .job-totals-half {
display: flex;
flex-flow: wrap;
}
.job-totals-tables {
flex: 1; flex: 1;
display: block; display: flex;
} flex-direction: column;
align-items: center;
.job-totals-rates-table, table {
.job-totals-parts-table { border: 1px solid #ccc;
border: black; border-collapse: collapse;
width: 100%; 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 { .job-totals-stats {
margin: 1rem; margin: 1rem;
display: flex; display: flex;
flex-direction: column; width: 100%;
align-content: center; //flex-direction: column;
.ant-statistic { justify-content: space-evenly;
margin: 0.5rem;
}
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,8 @@
import { Descriptions, Statistic } from "antd"; import { Descriptions, Statistic } from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import Dinero from "dinero.js";
export default function JobsCloseTotals({ export default function JobsCloseTotals({
jobTotals, jobTotals,
labMatTotal, labMatTotal,
@@ -17,47 +19,57 @@ export default function JobsCloseTotals({
> >
<Descriptions.Item label={t("jobs.labels.partstotal")}> <Descriptions.Item label={t("jobs.labels.partstotal")}>
<Statistic <Statistic
value={jobTotals.parts.parts.total.toFormat()} value={Dinero(jobTotals.parts.parts.total).toFormat()}
suffix={`(${jobTotals.parts.parts.subtotal.toFormat()} ± ${jobTotals.parts.parts.adjustments.toFormat()})`} suffix={`(${Dinero(
jobTotals.parts.parts.subtotal
).toFormat()} ± ${Dinero(
jobTotals.parts.parts.adjustments
).toFormat()})`}
/> />
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label={t("jobs.labels.subletstotal")}> <Descriptions.Item label={t("jobs.labels.subletstotal")}>
<Statistic <Statistic
value={jobTotals.parts.sublets.total.toFormat()} value={Dinero(jobTotals.parts.sublets.total).toFormat()}
suffix={`(${jobTotals.parts.sublets.subtotal.toFormat()} ± ${jobTotals.parts.sublets.adjustments.toFormat()})`} suffix={`(${Dinero(
jobTotals.parts.sublets.subtotal
).toFormat()} ± ${Dinero(
jobTotals.parts.sublets.adjustments
).toFormat()})`}
/> />
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label={t("jobs.labels.subtotal")}> <Descriptions.Item label={t("jobs.labels.subtotal")}>
<Statistic value={jobTotals.totals.subtotal.toFormat()} /> <Statistic value={Dinero(jobTotals.totals.subtotal).toFormat()} />
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label={t("jobs.labels.federal_tax_amt")}> <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>
<Descriptions.Item label={t("jobs.labels.state_tax_amt")}> <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>
<Descriptions.Item label={t("jobs.labels.local_tax_amt")}> <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.Item>
</Descriptions> </Descriptions>
<Statistic <Statistic
title={t("jobs.labels.total_repairs")} title={t("jobs.labels.total_repairs")}
value={jobTotals.totals.total_repairs.toFormat()} value={Dinero(jobTotals.totals.total_repairs).toFormat()}
/> />
<Statistic <Statistic
title={t("jobs.labels.net_repairs")} title={t("jobs.labels.net_repairs")}
value={jobTotals.totals.net_repairs.toFormat()} value={Dinero(jobTotals.totals.net_repairs).toFormat()}
/> />
<Statistic <Statistic
title={t("jobs.labels.suspense")} title={t("jobs.labels.suspense")}
valueStyle={{ valueStyle={{
color: color:
jobTotals.totals.subtotal.subtract(labMatTotal).subtract(partsTotal) Dinero(jobTotals.totals.subtotal)
.subtract(labMatTotal)
.subtract(partsTotal)
.getAmount() === 0 .getAmount() === 0
? "green" ? "green"
: "red", : "red",
}} }}
value={jobTotals.totals.subtotal value={Dinero(jobTotals.totals.subtotal)
.subtract(labMatTotal) .subtract(labMatTotal)
.subtract(partsTotal) .subtract(partsTotal)
.toFormat()} .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 React from "react";
import { useTranslation } from "react-i18next"; 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 FormItemEmail from "../form-items-formatted/email-form-item.component";
import FormItemPhone from "../form-items-formatted/phone-form-item.component"; import FormItemPhone from "../form-items-formatted/phone-form-item.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.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 { t } = useTranslation();
const { getFieldValue } = form; const { getFieldValue } = form;
return ( return (
<div> <div>
<Collapse defaultActiveKey='insurance'> <Collapse defaultActiveKey="insurance">
<Collapse.Panel <Collapse.Panel
key='insurance' key="insurance"
header={t("menus.jobsdetail.insurance")}> header={t("menus.jobsdetail.insurance")}
>
<LayoutFormRow> <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 /> <Input />
</Form.Item> </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 /> <Input />
</Form.Item> </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 /> <Input />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("jobs.fields.regie_number")} label={t("jobs.fields.regie_number")}
name='regie_number'> name="regie_number"
>
<Input /> <Input />
</Form.Item> </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 /> <FormDatePicker />
</Form.Item> </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 /> <Input />
</Form.Item> </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 /> <Input />
</Form.Item> </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 /> <Input />
</Form.Item> </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 /> <Input />
</Form.Item> </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 /> <Input />
</Form.Item> </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} /> <FormItemPhone customInput={Input} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("jobs.fields.ins_ea")} label={t("jobs.fields.ins_ea")}
name='ins_ea' name="ins_ea"
rules={[ rules={[
{ {
type: "email", type: "email",
message: "This is not a valid email address.", message: "This is not a valid email address.",
}, },
]}> ]}
>
<FormItemEmail email={getFieldValue("ins_ea")} /> <FormItemEmail email={getFieldValue("ins_ea")} />
</Form.Item> </Form.Item>
Appraiser Info 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 /> <Input />
</Form.Item> </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 /> <Input />
</Form.Item> </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 /> <Input />
</Form.Item> </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 /> <Input />
</Form.Item> </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 /> <Input />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("jobs.fields.est_ea")} label={t("jobs.fields.est_ea")}
name='est_ea' name="est_ea"
rules={[ rules={[
{ {
type: "email", type: "email",
message: "This is not a valid email address.", message: "This is not a valid email address.",
}, },
]}> ]}
>
<FormItemEmail email={getFieldValue("est_ea")} /> <FormItemEmail email={getFieldValue("est_ea")} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("jobs.fields.selling_dealer")} label={t("jobs.fields.selling_dealer")}
name='selling_dealer'> name="selling_dealer"
>
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("jobs.fields.servicing_dealer")} label={t("jobs.fields.servicing_dealer")}
name='servicing_dealer'> name="servicing_dealer"
>
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("jobs.fields.selling_dealer_contact")} label={t("jobs.fields.selling_dealer_contact")}
name='selling_dealer_contact'> name="selling_dealer_contact"
>
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("jobs.fields.servicing_dealer_contact")} label={t("jobs.fields.servicing_dealer_contact")}
name='servicing_dealer_contact'> name="servicing_dealer_contact"
>
<Input /> <Input />
</Form.Item> </Form.Item>
</LayoutFormRow> </LayoutFormRow>
</Collapse.Panel> </Collapse.Panel>
<Collapse.Panel key='claim' header={t("menus.jobsdetail.claimdetail")}> <Collapse.Panel key="claim" header={t("menus.jobsdetail.claimdetail")}>
<LayoutFormRow> <LayoutFormRow>
<Form.Item label={t("jobs.fields.csr")} name='csr'> <Form.Item label={t("jobs.fields.csr")} name="csr">
<Input /> <Input />
</Form.Item> </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 /> <Input />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.ponumber")} name='po_number'> <Form.Item label={t("jobs.fields.ponumber")} name="po_number">
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.unitnumber")} name='unit_number'> <Form.Item label={t("jobs.fields.unitnumber")} name="unit_number">
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("jobs.fields.specialcoveragepolicy")} label={t("jobs.fields.specialcoveragepolicy")}
valuePropName='checked' valuePropName="checked"
name='special_coverage_policy'> name="special_coverage_policy"
>
<Switch /> <Switch />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.kmin")} name='kmin'> <Form.Item label={t("jobs.fields.kmin")} name="kmin">
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.kmout")} name='kmout'> <Form.Item label={t("jobs.fields.kmout")} name="kmout">
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("jobs.fields.referralsource")} label={t("jobs.fields.referralsource")}
name='referral_source'> name="referral_source"
<Input /> >
<Select>
{bodyshop.md_referral_sources.map((s) => (
<Select.Option key={s} value={s}>
{s}
</Select.Option>
))}
</Select>
</Form.Item> </Form.Item>
</LayoutFormRow> </LayoutFormRow>
TODO How to handle different taxes and marking them as exempt? TODO How to handle different taxes and marking them as exempt?
@@ -153,120 +198,128 @@ export default function JobsCreateJobsInfo({ form }) {
} }
</Collapse.Panel> </Collapse.Panel>
<Collapse.Panel <Collapse.Panel
key='financial' key="financial"
header={t("menus.jobsdetail.financials")}> header={t("menus.jobsdetail.financials")}
>
<LayoutFormRow> <LayoutFormRow>
<Form.Item label={t("jobs.fields.ded_amt")} name='ded_amt'> <Form.Item label={t("jobs.fields.ded_amt")} name="ded_amt">
<InputNumber /> <InputNumber />
</Form.Item> </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 /> <Input />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("jobs.fields.depreciation_taxes")} label={t("jobs.fields.depreciation_taxes")}
name='depreciation_taxes'> name="depreciation_taxes"
>
<InputNumber /> <InputNumber />
</Form.Item> </Form.Item>
TODO This is equivalent of GST payable. TODO This is equivalent of GST payable.
<Form.Item <Form.Item
label={t("jobs.fields.federal_tax_payable")} label={t("jobs.fields.federal_tax_payable")}
name='federal_tax_payable'> name="federal_tax_payable"
>
<InputNumber /> <InputNumber />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("jobs.fields.other_amount_payable")} label={t("jobs.fields.other_amount_payable")}
name='other_amount_payable'> name="other_amount_payable"
>
<InputNumber /> <InputNumber />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("jobs.fields.towing_payable")} label={t("jobs.fields.towing_payable")}
name='towing_payable'> name="towing_payable"
>
<InputNumber /> <InputNumber />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("jobs.fields.storage_payable")} label={t("jobs.fields.storage_payable")}
name='storage_payable'> name="storage_payable"
>
<InputNumber /> <InputNumber />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("jobs.fields.adjustment_bottom_line")} label={t("jobs.fields.adjustment_bottom_line")}
name='adjustment_bottom_line'> name="adjustment_bottom_line"
>
<InputNumber /> <InputNumber />
</Form.Item> </Form.Item>
Totals Table Totals Table
<Form.Item <Form.Item
label={t("jobs.fields.labor_rate_desc")} label={t("jobs.fields.labor_rate_desc")}
name='labor_rate_desc'> name="labor_rate_desc"
>
<Input /> <Input />
</Form.Item> </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 /> <InputNumber />
</Form.Item> </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 /> <InputNumber />
</Form.Item> </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 /> <InputNumber />
</Form.Item> </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 /> <InputNumber />
</Form.Item> </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 /> <InputNumber />
</Form.Item> </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 /> <InputNumber />
</Form.Item> </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 /> <InputNumber />
</Form.Item> </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 /> <InputNumber />
</Form.Item> </Form.Item>
Note //TODO Remove ATP rate? 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 /> <InputNumber />
</Form.Item> </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 /> <InputNumber />
</Form.Item> </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 /> <InputNumber />
</Form.Item> </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 /> <InputNumber />
</Form.Item> </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 /> <InputNumber />
</Form.Item> </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 /> <InputNumber />
</Form.Item> </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 /> <InputNumber />
</Form.Item> </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 /> <InputNumber />
</Form.Item> </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 /> <InputNumber />
</Form.Item> </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 /> <InputNumber />
</Form.Item> </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 /> <InputNumber />
</Form.Item> </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 /> <InputNumber />
</Form.Item> </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 /> <InputNumber />
</Form.Item> </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 /> <InputNumber />
</Form.Item> </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 /> <InputNumber />
</Form.Item> </Form.Item>
</LayoutFormRow> </LayoutFormRow>
@@ -275,3 +328,4 @@ export default function JobsCreateJobsInfo({ form }) {
</div> </div>
); );
} }
export default connect(mapStateToProps, mapDispatchToProps)(JobsCreateJobsInfo);

View File

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

View File

@@ -36,6 +36,15 @@ export default function JobsCreateOwnerInfoSearchComponent({
tableState.sortedInfo.columnKey === "ownr_fn" && tableState.sortedInfo.columnKey === "ownr_fn" &&
tableState.sortedInfo.order, 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"), title: t("owners.fields.ownr_addr1"),
dataIndex: "ownr_addr1", dataIndex: "ownr_addr1",
@@ -86,9 +95,9 @@ export default function JobsCreateOwnerInfoSearchComponent({
loading={loading} loading={loading}
title={() => { title={() => {
return ( return (
<div className='imex-table-header'> <div className="imex-table-header">
<Input.Search <Input.Search
className='imex-table-header__search' className="imex-table-header__search"
placeholder={t("general.labels.search")} placeholder={t("general.labels.search")}
onSearch={(value) => { onSearch={(value) => {
setState({ setState({
@@ -101,11 +110,11 @@ export default function JobsCreateOwnerInfoSearchComponent({
</div> </div>
); );
}} }}
size='small' size="small"
scroll={{ x: true }} scroll={{ x: true }}
pagination={{ position: "top" }} pagination={{ position: "top" }}
columns={columns} columns={columns}
rowKey='id' rowKey="id"
dataSource={owners} dataSource={owners}
onChange={handleTableChange} onChange={handleTableChange}
rowSelection={{ 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 React from "react";
import { useTranslation } from "react-i18next"; 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 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 FormItemEmail from "../form-items-formatted/email-form-item.component";
import FormItemPhone from "../form-items-formatted/phone-form-item.component"; import FormItemPhone from "../form-items-formatted/phone-form-item.component";
import Car from "../job-damage-visual/job-damage-visual.component"; import Car from "../job-damage-visual/job-damage-visual.component";
import FormRow from "../layout-form-row/layout-form-row.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 { getFieldValue } = form;
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<div> <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"> <Form.Item label={t("jobs.fields.ins_co_id")} name="ins_co_id">
<Input /> <Input />
</Form.Item> </Form.Item>
@@ -47,36 +86,67 @@ export default function JobsDetailInsurance({ job, form }) {
> >
<FormItemEmail email={getFieldValue("ins_ea")} /> <FormItemEmail email={getFieldValue("ins_ea")} />
</Form.Item> </Form.Item>
</FormRow> <Form.Item
<FormRow header={t("jobs.forms.claiminfo")}> label={t("jobs.fields.referralsource")}
<Form.Item label={t("jobs.fields.clm_no")} name="clm_no"> name="referral_source"
<Input /> >
</Form.Item> <Select>
<Form.Item label={t("jobs.fields.policy_no")} name="policy_no"> {bodyshop.md_referral_sources.map((s) => (
<Input /> <Select.Option key={s} value={s}>
</Form.Item> {s}
<Form.Item label={t("jobs.fields.regie_number")} name="regie_number"> </Select.Option>
<Input /> ))}
</Select>
</Form.Item> </Form.Item>
</FormRow> </FormRow>
<FormRow header={t("jobs.forms.lossinfo")}> <Row gutter={[16, 16]}>
<div style={{ display: "inline", height: "8rem" }}> <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 ? ( {job.area_of_damage ? (
<Car <Car
dmg1={job.area_of_damage.impact1 || null} dmg1={job.area_of_damage.impact1}
dmg2={job.area_of_damage.impact2 || null} dmg2={job.area_of_damage.impact2}
/> />
) : ( ) : (
t("jobs.errors.nodamage") t("jobs.errors.nodamage")
)} )}
</div> </Col>
<Form.Item label={t("jobs.fields.loss_desc")} name="loss_desc"> </Row>
<Input />
</Form.Item>
<Form.Item label={t("jobs.fields.loss_date")} name="loss_date">
<FormDatePicker />
</Form.Item>
</FormRow>
<FormRow header={t("jobs.forms.appraiserinfo")}> <FormRow header={t("jobs.forms.appraiserinfo")}>
<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 /> <Input />
@@ -103,10 +173,21 @@ export default function JobsDetailInsurance({ job, form }) {
<FormItemEmail email={getFieldValue("est_ea")} /> <FormItemEmail email={getFieldValue("est_ea")} />
</Form.Item> </Form.Item>
</FormRow> </FormRow>
<FormRow header="TODO: TO BE PLACED"> <FormRow header={t("jobs.forms.other")}>
<Form.Item label={t("jobs.fields.pay_date")} name="pay_date"> <Form.Item label={t("jobs.fields.csr")} name="csr">
<Input /> <Input />
</Form.Item> </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 <Form.Item
label={t("jobs.fields.selling_dealer")} label={t("jobs.fields.selling_dealer")}
name="selling_dealer" name="selling_dealer"
@@ -131,8 +212,8 @@ export default function JobsDetailInsurance({ job, form }) {
> >
<Input /> <Input />
</Form.Item> </Form.Item>
TODO: Adding servicing/selling dealer contact info?
</FormRow> </FormRow>
</div> </div>
); );
} }
export default connect(mapStateToProps, mapDispatchToProps)(JobsDetailGeneral);

View File

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

View File

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

View File

@@ -1,27 +1,17 @@
import { DownCircleFilled, PrinterFilled } from "@ant-design/icons"; import { DownCircleFilled, PrinterFilled } from "@ant-design/icons";
import { import { Button, Dropdown, Menu, PageHeader, Tag } from "antd";
Button,
Checkbox,
Descriptions,
Dropdown,
Menu,
notification,
PageHeader,
Tag,
} from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import Moment from "react-moment";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; 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 JobsDetailHeaderActions from "../jobs-detail-header-actions/jobs-detail-header-actions.component";
import OwnerTagPopoverComponent from "../owner-tag-popover/owner-tag-popover.component"; import OwnerTagPopoverComponent from "../owner-tag-popover/owner-tag-popover.component";
import VehicleTagPopoverComponent from "../vehicle-tag-popover/vehicle-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"; import "./jobs-detail-header.styles.scss";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
@@ -35,12 +25,13 @@ const mapDispatchToProps = (dispatch) => ({
export function JobsDetailHeader({ export function JobsDetailHeader({
job, job,
mutationConvertJob,
refetch, refetch,
setPrintCenterContext, setPrintCenterContext,
bodyshop, bodyshop,
updateJobStatus, updateJobStatus,
setScheduleContext, setScheduleContext,
loading,
form,
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -48,7 +39,8 @@ export function JobsDetailHeader({
<Menu <Menu
onClick={(e) => { onClick={(e) => {
updateJobStatus(e.key); updateJobStatus(e.key);
}}> }}
>
{bodyshop.md_ro_statuses.statuses.map((item) => ( {bodyshop.md_ro_statuses.statuses.map((item) => (
<Menu.Item key={item}>{item}</Menu.Item> <Menu.Item key={item}>{item}</Menu.Item>
))} ))}
@@ -56,19 +48,20 @@ export function JobsDetailHeader({
); );
const menuExtra = ( const menuExtra = (
<div className='imex-flex-row'> <div className="imex-flex-row">
<Dropdown <Dropdown
className='imex-flex-row__margin' className="imex-flex-row__margin"
overlay={statusmenu} overlay={statusmenu}
trigger={["click"]} trigger={["click"]}
key='changestatus'> key="changestatus"
>
<Button> <Button>
{t("jobs.actions.changestatus")} <DownCircleFilled /> {t("jobs.actions.changestatus")} <DownCircleFilled />
</Button> </Button>
</Dropdown> </Dropdown>
<Button <Button
className='imex-flex-row__margin' className="imex-flex-row__margin"
onClick={() => { onClick={() => {
setPrintCenterContext({ setPrintCenterContext({
actions: { refetch: refetch }, actions: { refetch: refetch },
@@ -78,35 +71,19 @@ export function JobsDetailHeader({
}, },
}); });
}} }}
key='printing'> key="printing"
>
<PrinterFilled /> <PrinterFilled />
{t("jobs.actions.printCenter")} {t("jobs.actions.printCenter")}
</Button> </Button>
<JobsConvertButton job={job} refetch={refetch} />
<JobsDetailHeaderActions key="actions" job={job} refetch={refetch} />
<Button <Button
key='convert' type="primary"
className='imex-flex-row__margin' loading={loading}
type='danger' className="imex-flex-row__margin"
style={{ display: job.converted ? "none" : "" }} onClick={() => form.submit()}
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'>
{t("general.actions.save")} {t("general.actions.save")}
</Button> </Button>
</div> </div>
@@ -121,59 +98,26 @@ export function JobsDetailHeader({
} }
subTitle={job.status} subTitle={job.status}
tags={[ tags={[
<OwnerTagPopoverComponent key='owner' job={job} />, <OwnerTagPopoverComponent key="owner" job={job} />,
<VehicleTagPopoverComponent key='vehicle' job={job} />, <VehicleTagPopoverComponent key="vehicle" job={job} />,
<Tag <Tag
color='#f50' color="#f50"
key='production' key="production"
style={{ display: job.inproduction ? "" : "none" }}> style={{ display: job.inproduction ? "" : "none" }}
>
{t("jobs.labels.inproduction")} {t("jobs.labels.inproduction")}
</Tag>, </Tag>,
]} <Tag title={t("jobs.fields.repairtotal")} key="total" color="green">
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")}>
<CurrencyFormatter>{job.clm_total}</CurrencyFormatter> <CurrencyFormatter>{job.clm_total}</CurrencyFormatter>
</Descriptions.Item> <span style={{ margin: "0rem .5rem" }}>/</span>
<Descriptions.Item
key='custowing'
label={t("jobs.fields.customerowing")}>
<CurrencyFormatter>{job.owner_owing}</CurrencyFormatter> <CurrencyFormatter>{job.owner_owing}</CurrencyFormatter>
</Descriptions.Item> </Tag>,
]}
<Descriptions.Item extra={menuExtra}
key='scp' >
label={t("jobs.fields.specialcoveragepolicy")}> <div style={{ display: "flex", justifyContent: "flex-end" }}>
<Checkbox checked={job.special_coverage_policy} /> <JobEmployeeAssignments job={job} />
</Descriptions.Item> </div>
<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>
</PageHeader> </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) { } catch (error) {
console.log("Error getting QBXML from Server.", error); console.log("Error getting QBXML from Server.", error);
notification["error"]({ notification["error"]({

View File

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

View File

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

View File

@@ -39,7 +39,7 @@ export function JobsList({ bodyshop }) {
const history = useHistory(); const history = useHistory();
const [searchText, setSearchText] = useState(""); 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 const jobs = data
? searchText === "" ? searchText === ""
@@ -50,6 +50,9 @@ export function JobsList({ bodyshop }) {
.toString() .toString()
.toLowerCase() .toLowerCase()
.includes(searchText.toLowerCase()) || .includes(searchText.toLowerCase()) ||
(j.ownr_co_nm || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_fn || "") (j.ownr_fn || "")
.toLowerCase() .toLowerCase()
.includes(searchText.toLowerCase()) || .includes(searchText.toLowerCase()) ||
@@ -122,10 +125,14 @@ export function JobsList({ bodyshop }) {
render: (text, record) => { render: (text, record) => {
return record.owner ? ( return record.owner ? (
<Link to={"/manage/owners/" + record.owner.id}> <Link to={"/manage/owners/" + record.owner.id}>
{`${record.ownr_fn || ""} ${record.ownr_ln || ""}`} {`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${
record.ownr_co_nm || ""
}`}
</Link> </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 ( return (
<Table <Table
loading={loading} loading={loading}
size='small' size="small"
pagination={false} pagination={false}
columns={columns} columns={columns}
rowKey='id' rowKey="id"
dataSource={jobs} dataSource={jobs}
style={{ height: "100%" }} style={{ height: "100%" }}
scroll={{ x: true }} scroll={{ x: true }}
title={() => { title={() => {
return ( return (
<div className='imex-table-header'> <div className="imex-table-header">
<Button onClick={() => refetch()}> <Button onClick={() => refetch()}>
<SyncOutlined /> <SyncOutlined />
</Button> </Button>
<Input.Search <Input.Search
className='imex-table-header__search' className="imex-table-header__search"
placeholder={t("general.labels.search")} placeholder={t("general.labels.search")}
onChange={(e) => { onChange={(e) => {
setSearchText(e.target.value); 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 FormItemPhone from "../form-items-formatted/phone-form-item.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.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 { t } = useTranslation();
const { getFieldValue } = form; const { getFieldValue } = form;
return ( return (
<div> <div>
<div className='imex-flex-row imex-flex-row__flex-space-around'> <div className="imex-flex-row imex-flex-row__flex-space-around">
<Button <Button
className='imex-flex-row__margin-large' className="imex-flex-row__margin-large"
type='primary' type="primary"
key='submit' key="submit"
htmlType='submit'> loading={loading}
htmlType="submit"
>
{t("general.actions.save")} {t("general.actions.save")}
</Button> </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} /> <FormFieldsChanged form={form} />
</div> </div>
</div> </div>
<LayoutFormRow header={t("owners.forms.name")}> <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 /> <Input />
</Form.Item> </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 /> <Input />
</Form.Item> </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 /> <Input />
</Form.Item> </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 /> <Input />
</Form.Item> </Form.Item>
</LayoutFormRow> </LayoutFormRow>
@@ -42,52 +44,56 @@ export default function OwnerDetailFormComponent({ form }) {
<LayoutFormRow header={t("owners.forms.address")}> <LayoutFormRow header={t("owners.forms.address")}>
<Form.Item <Form.Item
label={t("owners.fields.ownr_addr1")} label={t("owners.fields.ownr_addr1")}
name='ownr_addr1' name="ownr_addr1"
rules={[ rules={[
{ {
required: true, required: true,
message: t("general.validation.required"), message: t("general.validation.required"),
}, },
]}> ]}
>
<Input /> <Input />
</Form.Item> </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 /> <Input />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("owners.fields.ownr_city")} label={t("owners.fields.ownr_city")}
name='ownr_city' name="ownr_city"
rules={[ rules={[
{ {
required: true, required: true,
message: t("general.validation.required"), message: t("general.validation.required"),
}, },
]}> ]}
>
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("owners.fields.ownr_st")} label={t("owners.fields.ownr_st")}
name='ownr_st' name="ownr_st"
rules={[ rules={[
{ {
required: true, required: true,
message: t("general.validation.required"), message: t("general.validation.required"),
}, },
]}> ]}
>
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("owners.fields.ownr_zip")} label={t("owners.fields.ownr_zip")}
name='ownr_zip' name="ownr_zip"
rules={[ rules={[
{ {
required: true, required: true,
message: t("general.validation.required"), message: t("general.validation.required"),
}, },
]}> ]}
>
<Input /> <Input />
</Form.Item> </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 /> <Input />
</Form.Item> </Form.Item>
</LayoutFormRow> </LayoutFormRow>
@@ -95,27 +101,30 @@ export default function OwnerDetailFormComponent({ form }) {
<LayoutFormRow header={t("owners.forms.contact")}> <LayoutFormRow header={t("owners.forms.contact")}>
<Form.Item <Form.Item
label={t("owners.fields.allow_text_message")} label={t("owners.fields.allow_text_message")}
name='allow_text_message' name="allow_text_message"
valuePropName='checked'> valuePropName="checked"
>
<Switch /> <Switch />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("owners.fields.ownr_ea")} label={t("owners.fields.ownr_ea")}
name='ownr_ea' name="ownr_ea"
rules={[ rules={[
{ {
type: "email", type: "email",
message: "This is not a valid email address.", message: "This is not a valid email address.",
}, },
]}> ]}
>
<FormItemEmail email={getFieldValue("ownr_ea")} /> <FormItemEmail email={getFieldValue("ownr_ea")} />
</Form.Item> </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} /> <FormItemPhone customInput={Input} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("owners.fields.preferred_contact")} label={t("owners.fields.preferred_contact")}
name='preferred_contact'> name="preferred_contact"
>
<Input /> <Input />
</Form.Item> </Form.Item>
</LayoutFormRow> </LayoutFormRow>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,25 +1,25 @@
import React from "react"; import React from "react";
export default function PartsStatusPie({ partsList }) { export default function PartsStatusPie({ partsList }) {
return <div>Parts Pie</div>;
//const [pieData, setPieData] = useState([]); //const [pieData, setPieData] = useState([]);
const result = partsList // const result = partsList
? partsList.reduce((names, name) => { // ? partsList.reduce((names, name) => {
const val = name || "?"; // const val = name || "?";
const count = names[val] || 0; // const count = names[val] || 0;
names[val] = count + 1; // names[val] = count + 1;
return names; // return names;
}, {}) // }, {})
: {}; // : {};
const pieData = Object.keys(result).map((i) => { // const pieData = Object.keys(result).map((i) => {
console.log("i", i); // return {
return { // id: i,
id: i, // label: i,
label: i, // value: result[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 { t } = useTranslation();
const handleStripeChange = (e) => { const handleStripeChange = (e) => {
console.log("e", e);
setStripeState({ error: e.error, cardComplete: e.complete }); 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") { if (stripePayment.paymentIntent.status === "succeeded") {
notification["success"]({ message: t("payments.successes.stripe") }); notification["success"]({ message: t("payments.successes.stripe") });
@@ -172,13 +171,15 @@ function InvoiceEnterModalContainer({
okButtonProps={{ okButtonProps={{
loading: loading, loading: loading,
}} }}
destroyOnClose> destroyOnClose
>
<Form <Form
onFinish={handleFinish} onFinish={handleFinish}
autoComplete={"off"} autoComplete={"off"}
form={form} form={form}
layout='vertical' layout="vertical"
initialValues={{ jobid: context.jobId }}> initialValues={{ jobid: context.jobId }}
>
<PaymentForm form={form} stripeStateArr={stripeStateArr} /> <PaymentForm form={form} stripeStateArr={stripeStateArr} />
</Form> </Form>
</Modal> </Modal>

View File

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

View File

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

View File

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

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