Merge pull request #9 from snaptsoft/dev-patrick

Dev patrick
This commit is contained in:
2020-02-07 17:04:12 -08:00
committed by GitHub
57 changed files with 4026 additions and 1485 deletions

View File

@@ -1,5 +1,7 @@
React App: React App:
React Hooks are used for Authentication ONLY to ensure the correct web token is passed.
Yarn Dependency Management:
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.
@@ -9,4 +11,4 @@ To Start Hasura CLI:
npx hasura console --admin-secret Dev-BodyShopAppBySnaptSoftware! npx hasura console --admin-secret Dev-BodyShopAppBySnaptSoftware!
Migrating to Staging: Migrating to Staging:
npx hasura migrate apply --up 10 --endpoint https://bodyshop-staging-db.herokuapp.com/ --admin-secret Staging-BodyShopAppBySnaptSoftware! npx hasura migrate apply --up 10 --endpoint https://bodyshop-staging-db.herokuapp.com/ --admin-secret Staging-BodyShopAppBySnaptSoftware!

View File

@@ -1,4 +1,4 @@
<babeledit_project be_version="2.6.1" version="1.2"> <babeledit_project version="1.2" be_version="2.6.1">
<!-- <!--
BabelEdit project file BabelEdit project file
@@ -4038,6 +4038,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>customers</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>home</name> <name>home</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -4080,6 +4101,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>owners</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>schedule</name> <name>schedule</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -4101,6 +4143,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>vehicles</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>
@@ -4605,9 +4668,56 @@
<folder_node> <folder_node>
<name>owners</name> <name>owners</name>
<children> <children>
<folder_node>
<name>errors</name>
<children>
<concept_node>
<name>noaccess</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
<folder_node> <folder_node>
<name>fields</name> <name>fields</name>
<children> <children>
<concept_node>
<name>allow_text_message</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>ownr_addr1</name> <name>ownr_addr1</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -4629,6 +4739,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>ownr_addr2</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>ownr_city</name> <name>ownr_city</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -4650,6 +4781,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>ownr_ctry</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>ownr_ea</name> <name>ownr_ea</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -4734,6 +4886,90 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>ownr_st</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>ownr_title</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>ownr_zip</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>preferred_contact</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>
@@ -4762,6 +4998,32 @@
</concept_node> </concept_node>
</children> </children>
</folder_node> </folder_node>
<folder_node>
<name>successes</name>
<children>
<concept_node>
<name>save</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>
@@ -4903,6 +5165,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>manageroot</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>profile</name> <name>profile</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -4945,6 +5228,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>vehicledetail</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>
@@ -5021,6 +5325,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>photourl</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>
</children> </children>
@@ -5028,6 +5353,74 @@
<folder_node> <folder_node>
<name>vehicles</name> <name>vehicles</name>
<children> <children>
<folder_node>
<name>errors</name>
<children>
<concept_node>
<name>noaccess</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>validation</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>validationtitle</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
<folder_node> <folder_node>
<name>fields</name> <name>fields</name>
<children> <children>
@@ -5052,6 +5445,431 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>plate_st</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>trim_color</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>v_bstyle</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>v_color</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>v_cond</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>v_engine</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>v_make_desc</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>v_makecode</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>v_mldgcode</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>v_model_desc</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>v_model_yr</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>v_options</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>v_paint_codes</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>v_prod_dt</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>v_stage</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>v_tone</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>v_trimcode</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>v_type</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>v_vin</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
<folder_node>
<name>successes</name>
<children>
<concept_node>
<name>save</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>
</children> </children>

View File

@@ -4,39 +4,39 @@
"private": true, "private": true,
"proxy": "https://localhost:5000", "proxy": "https://localhost:5000",
"dependencies": { "dependencies": {
"antd": "^3.26.0", "antd": "^3.26.8",
"apollo-boost": "^0.4.4", "apollo-boost": "^0.4.4",
"apollo-link-context": "^1.0.19", "apollo-link-context": "^1.0.19",
"apollo-link-error": "^1.1.12", "apollo-link-error": "^1.1.12",
"apollo-link-logger": "^1.2.3", "apollo-link-logger": "^1.2.3",
"apollo-link-ws": "^1.0.19", "apollo-link-ws": "^1.0.19",
"axios": "^0.19.1", "axios": "^0.19.2",
"chart.js": "^2.9.3", "chart.js": "^2.9.3",
"dotenv": "^8.2.0", "dotenv": "^8.2.0",
"firebase": "^7.5.0", "firebase": "^7.8.1",
"graphql": "^14.5.8", "graphql": "^14.6.0",
"i18next": "^19.0.2", "i18next": "^19.1.0",
"node-sass": "^4.13.0", "node-sass": "^4.13.1",
"react": "^16.12.0", "react": "^16.12.0",
"react-apollo": "^3.1.3", "react-apollo": "^3.1.3",
"react-big-calendar": "^0.23.0", "react-big-calendar": "^0.23.0",
"react-chartjs-2": "^2.8.0", "react-chartjs-2": "^2.9.0",
"react-dom": "^16.12.0", "react-dom": "^16.12.0",
"react-i18next": "^11.2.7", "react-i18next": "^11.3.1",
"react-icons": "^3.8.0", "react-icons": "^3.9.0",
"react-image-file-resizer": "^0.2.1", "react-image-file-resizer": "^0.2.1",
"react-moment": "^0.9.7", "react-moment": "^0.9.7",
"react-number-format": "^4.3.1", "react-number-format": "^4.3.1",
"react-redux": "^7.1.3", "react-redux": "^7.1.3",
"react-router-dom": "^5.1.2", "react-router-dom": "^5.1.2",
"react-scripts": "3.2.0", "react-scripts": "3.3.1",
"react-trello": "^2.2.3", "react-trello": "^2.2.3",
"redux": "^4.0.5", "redux": "^4.0.5",
"redux-logger": "^3.0.6", "redux-logger": "^3.0.6",
"redux-persist": "^6.0.0", "redux-persist": "^6.0.0",
"redux-saga": "^1.1.3", "redux-saga": "^1.1.3",
"reselect": "^4.0.0", "reselect": "^4.0.0",
"styled-components": "^4.4.1", "styled-components": "^5.0.1",
"subscriptions-transport-ws": "^0.9.16" "subscriptions-transport-ws": "^0.9.16"
}, },
"scripts": { "scripts": {

View File

@@ -1,6 +1,7 @@
{ {
"short_name": "Bodyshop", "short_name": "Bodyshop.app",
"name": "Bodyshop Management System", "name": "Bodyshop Management System",
"description": "The ultimate bodyshop management system",
"icons": [ "icons": [
{ {
"src": "favicon.ico", "src": "favicon.ico",
@@ -20,6 +21,6 @@
], ],
"start_url": ".", "start_url": ".",
"display": "standalone", "display": "standalone",
"theme_color": "#002366", "theme_color": "#fff",
"background_color": "#000000" "background_color": "#fff"
} }

View File

@@ -13,7 +13,7 @@ import PrivateRoute from "../utils/private-route";
import "./App.css"; import "./App.css";
const LandingPage = lazy(() => import("../pages/landing/landing.page")); const LandingPage = lazy(() => import("../pages/landing/landing.page"));
const ManagePage = lazy(() => import("../pages/manage/manage.page")); 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(() => const Unauthorized = lazy(() =>
import("../pages/unauthorized/unauthorized.component") import("../pages/unauthorized/unauthorized.component")
@@ -28,8 +28,7 @@ const mapDispatchToProps = dispatch => ({
export default connect( export default connect(
mapStateToProps, mapStateToProps,
mapDispatchToProps mapDispatchToProps
)(({ checkUserSession, currentUser, setBodyshop }) => { )(({ checkUserSession, currentUser }) => {
useEffect(() => { useEffect(() => {
checkUserSession(); checkUserSession();
return () => {}; return () => {};

View File

@@ -1,79 +0,0 @@
import { Avatar, Col, Dropdown, Icon, Menu, Row } from "antd";
import i18next from "i18next";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import UserImage from "../../assets/User.svg";
import { setUserLanguage, signOutStart } from "../../redux/user/user.actions";
import { selectCurrentUser } from "../../redux/user/user.selectors";
import SignOut from "../sign-out/sign-out.component";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser
});
const mapDispatchToProps = dispatch => ({
signOutStart: () => dispatch(signOutStart()),
setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(function CurrentUserDropdown({ currentUser, signOutStart, setUserLanguage }) {
const { t } = useTranslation();
const handleMenuClick = e => {
if (e.item.props.actiontype === "lang-select") {
i18next.changeLanguage(e.key, (err, t) => {
if (err)
return console.log("Error encountered when changing languages.", err);
setUserLanguage(e.key);
console.log("clicking");
});
}
};
const menu = (
<Menu mode="vertical" onClick={handleMenuClick}>
<Menu.Item>
<SignOut signOutStart={signOutStart} />
</Menu.Item>
<Menu.Item>
<Link to="/manage/profile"> {t("menus.currentuser.profile")}</Link>
</Menu.Item>
<Menu.SubMenu
title={
<span>
<Icon type="global" />
<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">
{t("general.languages.french")}
</Menu.Item>
<Menu.Item actiontype="lang-select" key="es">
{t("general.languages.spanish")}
</Menu.Item>
</Menu.SubMenu>
</Menu>
);
return (
<Dropdown overlay={menu}>
<Row>
<Col span={8}>
<Avatar size="large" alt="Avatar" src={UserImage} />
</Col>
<Col span={16} style={{ color: "white" }}>
{currentUser.displayName || t("general.labels.unknown")}
</Col>
</Row>
</Dropdown>
);
});

View File

@@ -4,7 +4,7 @@ import React from "react";
export default function FooterComponent() { export default function FooterComponent() {
return ( return (
<Row> <Row>
<Col span={8} offset={9}> <Col span={8} offset={8}>
Copyright Snapt Software 2019. All rights reserved. Copyright Snapt Software 2019. All rights reserved.
</Col> </Col>
</Row> </Row>

View File

@@ -5,9 +5,13 @@ function FormItemEmail(props, ref) {
<Input <Input
{...props} {...props}
addonAfter={ addonAfter={
<a href={`mailto:${props.email}`}> props.email ? (
<a href={`mailto:${props.email}`}>
<Icon type="mail" />
</a>
) : (
<Icon type="mail" /> <Icon type="mail" />
</a> )
} }
/> />
); );

View File

@@ -0,0 +1,21 @@
import { Button } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import AlertComponent from "../alert/alert.component";
export default function ResetForm({ resetFields }) {
const { t } = useTranslation();
return (
<AlertComponent
message={
<div>
{t("general.messages.unsavedchanges")}
<Button onClick={() => resetFields()}>
{t("general.actions.reset")}
</Button>
</div>
}
closable
/>
);
}

View File

@@ -1,79 +0,0 @@
import React from "react";
// import { Icon, Button, Input, AutoComplete } from "antd";
// const { Option } = AutoComplete;
// function onSelect(value) {
// console.log("onSelect", value);
// }
// function getRandomInt(max, min = 0) {
// return Math.floor(Math.random() * (max - min + 1)) + min; // eslint-disable-line no-mixed-operators
// }
// function searchResult(query) {
// return new Array(getRandomInt(5))
// .join(".")
// .split(".")
// .map((item, idx) => ({
// query,
// category: `${query}${idx}`,
// count: getRandomInt(200, 100)
// }));
// }
// function renderOption(item) {
// return (
// <Option key={item.category} text={item.category}>
// <div className='global-search-item'>
// <span className='global-search-item-desc'>
// Found {item.query} on
// <a
// href={`https://s.taobao.com/search?q=${item.query}`}
// target='_blank'
// rel='noopener noreferrer'>
// {item.category}
// </a>
// </span>
// <span className='global-search-item-count'>{item.count} results</span>
// </div>
// </Option>
// );
// }
export default class GlobalSearch extends React.Component {
state = {
dataSource: []
};
// handleSearch = value => {
// this.setState({
// dataSource: value ? searchResult(value) : []
// });
// };
render() {
return (
<div />
// <div style={{ width: 300 }}>
// <AutoComplete
// size="large"
// style={{ width: "100%" }}
// dataSource={dataSource.map(renderOption)}
// onSelect={onSelect}
// onSearch={this.handleSearch}
// placeholder="input here"
// optionLabelProp="text"
// >
// <Input
// suffix={
// <Button style={{ marginRight: -12 }} size="large" type="primary">
// <Icon type="search" />
// </Button>
// }
// />
// </AutoComplete>
// </div>
);
}
}

View File

@@ -1,27 +1,35 @@
import { Col, Icon, Menu, Row } from "antd"; import { Avatar, Col, Icon, Menu, Row } from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import CurrentUserDropdown from "../current-user-dropdown/current-user-dropdown.component"; import UserImage from "../../assets/User.svg";
import GlobalSearch from "../global-search/global-search.component"; import { signOutStart } from "../../redux/user/user.actions";
import ManageSignInButton from "../manage-sign-in-button/manage-sign-in-button.component"; import ManageSignInButton from "../manage-sign-in-button/manage-sign-in-button.component";
export default ({ landingHeader, selectedNavItem }) => { export default ({
landingHeader,
selectedNavItem,
logo,
handleMenuClick,
currentUser
}) => {
const { t } = useTranslation(); const { t } = useTranslation();
//TODO Add
return ( return (
<Row type="flex" justify="space-around"> <Row type="flex" justify="space-around" align="middle">
<Col span={16}> {logo ? (
<Col span={4}>
<img alt="Shop Logo" src={logo} style={{ height: "40px" }} />
</Col>
) : null}
<Col span={14}>
<Menu <Menu
theme="dark" theme="dark"
className="header" className="header"
selectedKeys={selectedNavItem} selectedKeys={selectedNavItem}
mode="horizontal" mode="horizontal"
onClick={handleMenuClick}
> >
<Menu.Item>
<GlobalSearch />
</Menu.Item>
<Menu.Item key="home"> <Menu.Item key="home">
<Link to="/manage"> <Link to="/manage">
<Icon type="home" /> <Icon type="home" />
@@ -49,16 +57,62 @@ export default ({ landingHeader, selectedNavItem }) => {
</Menu.Item> </Menu.Item>
</Menu.SubMenu> </Menu.SubMenu>
{!landingHeader ? null : ( <Menu.SubMenu title={t("menus.header.customers")}>
<Menu.Item> <Menu.Item key="owners">
<ManageSignInButton /> <Link to="/manage/owners">
<Icon type="team" />
{t("menus.header.owners")}
</Link>
</Menu.Item> </Menu.Item>
)} <Menu.Item key="vehicles">
<Link to="/manage/vehicles">
<Icon type="car" />
{t("menus.header.vehicles")}
</Link>
</Menu.Item>
</Menu.SubMenu>
<Menu.SubMenu
title={
<div>
<Avatar
size="medium"
alt="Avatar"
src={currentUser.photoURL ? currentUser.photoURL : UserImage}
style={{ margin: "10px" }}
/>
{currentUser.displayName || t("general.labels.unknown")}
</div>
}
>
<Menu.Item onClick={signOutStart()}>
{t("user.actions.signout")}
</Menu.Item>
<Menu.Item>
<Link to="/manage/profile">{t("menus.currentuser.profile")}</Link>
</Menu.Item>
<Menu.SubMenu
title={
<span>
<Icon type="global" />
<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">
{t("general.languages.french")}
</Menu.Item>
<Menu.Item actiontype="lang-select" key="es">
{t("general.languages.spanish")}
</Menu.Item>
</Menu.SubMenu>
</Menu.SubMenu>
</Menu> </Menu>
</Col> </Col>
<Col span={6} offset={2}> <Col span={4}>{!landingHeader ? null : <ManageSignInButton />}</Col>
{!landingHeader ? <CurrentUserDropdown /> : null}
</Col>
</Row> </Row>
); );
}; };

View File

@@ -1,8 +1,52 @@
import React from "react"; import React from "react";
import HeaderComponent from "./header.component"; import HeaderComponent from "./header.component";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import i18next from "i18next";
import { setUserLanguage, signOutStart } from "../../redux/user/user.actions";
import {
selectCurrentUser,
selectBodyshop
} from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
bodyshop: selectBodyshop
});
const mapDispatchToProps = dispatch => ({
signOutStart: () => dispatch(signOutStart()),
setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(function HeaderContainer({
landingHeader,
currentUser,
bodyshop,
signOutStart,
setUserLanguage
}) {
const handleMenuClick = e => {
if (e.item.props.actiontype === "lang-select") {
i18next.changeLanguage(e.key, (err, t) => {
if (err)
return console.log("Error encountered when changing languages.", err);
setUserLanguage(e.key);
});
}
};
export default ({ landingHeader, signedIn }) => {
return ( return (
<HeaderComponent landingHeader={landingHeader} selectedNavItem={null} /> <HeaderComponent
handleMenuClick={handleMenuClick}
signOutStart={signOutStart}
landingHeader={landingHeader}
selectedNavItem={null}
currentUser={currentUser}
logo={bodyshop ? bodyshop.logo_img_path : null}
/>
); );
}; });

View File

@@ -10,45 +10,45 @@ export default function JobDetailCardsInsuranceComponent({ loading, data }) {
<CardTemplate loading={loading} title={t("jobs.labels.cards.insurance")}> <CardTemplate loading={loading} title={t("jobs.labels.cards.insurance")}>
{data ? ( {data ? (
<span> <span>
<div>{data?.ins_co_nm || t("general.labels.unknown")}</div> <div>{data.ins_co_nm || t("general.labels.unknown")}</div>
<div>{data?.clm_no || t("general.labels.unknown")}</div> <div>{data.clm_no || t("general.labels.unknown")}</div>
<div> <div>
{t("jobs.labels.cards.filehandler")} {t("jobs.labels.cards.filehandler")}
{data?.ins_ea ? ( {data.ins_ea ? (
<a href={`mailto:${data.ins_ea}`}> <a href={`mailto:${data.ins_ea}`}>
<div>{`${data?.ins_ct_fn || ""} ${data?.ins_ct_ln || ""}`}</div> <div>{`${data.ins_ct_fn || ""} ${data.ins_ct_ln || ""}`}</div>
</a> </a>
) : ( ) : (
<div>{`${data?.ins_ct_fn || ""} ${data?.ins_ct_ln || ""}`}</div> <div>{`${data.ins_ct_fn || ""} ${data.ins_ct_ln || ""}`}</div>
)} )}
{data?.ins_ph1 ? ( {data.ins_ph1 ? (
<PhoneFormatter>{data?.ins_ph1}</PhoneFormatter> <PhoneFormatter>{data.ins_ph1}</PhoneFormatter>
) : null} ) : null}
</div> </div>
<div> <div>
{t("jobs.labels.cards.appraiser")} {t("jobs.labels.cards.appraiser")}
{data?.est_ea ? ( {data.est_ea ? (
<a href={`mailto:${data.est_ea}`}> <a href={`mailto:${data.est_ea}`}>
<div>{`${data?.ins_ct_fn || ""} ${data?.ins_ct_ln || ""}`}</div> <div>{`${data.ins_ct_fn || ""} ${data.ins_ct_ln || ""}`}</div>
</a> </a>
) : ( ) : (
<div>{`${data?.ins_ct_fn || ""} ${data?.ins_ct_ln || ""}`}</div> <div>{`${data.ins_ct_fn || ""} ${data.ins_ct_ln || ""}`}</div>
)} )}
</div> </div>
<div> <div>
{t("jobs.labels.cards.estimator")} {t("jobs.labels.cards.estimator")}
{data?.est_ea ? ( {data.est_ea ? (
<a href={`mailto:${data.est_ea}`}> <a href={`mailto:${data.est_ea}`}>
<div>{`${data?.est_ct_fn || ""} ${data?.est_ct_ln || ""}`}</div> <div>{`${data.est_ct_fn || ""} ${data.est_ct_ln || ""}`}</div>
</a> </a>
) : ( ) : (
<div>{`${data?.est_ct_fn || ""} ${data?.est_ct_ln || ""}`}</div> <div>{`${data.est_ct_fn || ""} ${data.est_ct_ln || ""}`}</div>
)} )}
{data?.est_ph1 ? ( {data.est_ph1 ? (
<PhoneFormatter>{data?.est_ph1}</PhoneFormatter> <PhoneFormatter>{data.est_ph1}</PhoneFormatter>
) : null} ) : null}
</div> </div>
</span> </span>
) : null} ) : null}

View File

@@ -22,7 +22,7 @@ export default function JobDetailCardsNotesComponent({ loading, data }) {
<List <List
size='small' size='small'
bordered bordered
dataSource={data?.notes} dataSource={data.notes}
renderItem={item => ( renderItem={item => (
<List.Item> <List.Item>
{item.critical ? ( {item.critical ? (

View File

@@ -9,7 +9,7 @@ export default function JobDetailCardsVehicleComponent({ loading, data }) {
<CardTemplate <CardTemplate
loading={loading} loading={loading}
title={t("jobs.labels.cards.vehicle")} title={t("jobs.labels.cards.vehicle")}
extraLink={data?.vehicle ? `/manage/vehicles/${data?.vehicle?.id}` : null} extraLink={data.vehicle ? `/manage/vehicles/${data.vehicle.id}` : null}
> >
{data ? ( {data ? (
<span> <span>

View File

@@ -52,24 +52,28 @@ export default function JobLinesContainer({ jobId, form }) {
? searchText ? searchText
? data.joblines.filter( ? data.joblines.filter(
jl => jl =>
jl.unq_seq (jl.unq_seq || "")
?.toString() .toString()
.toLowerCase() .toLowerCase()
.includes(searchText.toLowerCase()) || .includes(searchText.toLowerCase()) ||
jl.line_desc (jl.line_desc || "")
?.toLowerCase() .toLowerCase()
.includes(searchText.toLowerCase()) || .includes(searchText.toLowerCase()) ||
jl.part_type (jl.part_type || "")
?.toLowerCase() .toLowerCase()
.includes(searchText.toLowerCase()) || .includes(searchText.toLowerCase()) ||
jl.oem_partno (jl.oem_partno || "")
?.toLowerCase() .toLowerCase()
.includes(searchText.toLowerCase()) || .includes(searchText.toLowerCase()) ||
jl.op_code_desc (jl.op_code_desc || "")
?.toLowerCase() .toLowerCase()
.includes(searchText.toLowerCase()) || .includes(searchText.toLowerCase()) ||
jl.db_price?.toString().includes(searchText.toLowerCase()) || (jl.db_price || "")
jl.act_price?.toString().includes(searchText.toLowerCase()) .toString()
.includes(searchText.toLowerCase()) ||
(jl.act_price || "")
.toString()
.includes(searchText.toLowerCase())
) )
: data.joblines : data.joblines
: null : null

View File

@@ -41,9 +41,7 @@ export default connect(
jobId={jobId} jobId={jobId}
currentUser={currentUser} currentUser={currentUser}
shopId={ shopId={
shopData.data?.bodyshops[0]?.id shopData.data.bodyshops[0].id ? shopData.data.bodyshops[0].id : "error"
? shopData.data?.bodyshops[0]?.id
: "error"
} }
/> />
); );

View File

@@ -106,7 +106,7 @@ export default withRouter(function JobsList({
ellipsis: true, ellipsis: true,
render: (text, record) => { render: (text, record) => {
return record.vehicle ? ( return record.vehicle ? (
<Link to={"manage/vehicles/" + record.vehicle.id}> <Link to={"/manage/vehicles/" + record.vehicle.id}>
{record.vehicle.v_model_yr} {record.vehicle.v_make_desc}{" "} {record.vehicle.v_model_yr} {record.vehicle.v_make_desc}{" "}
{record.vehicle.v_model_desc} {record.vehicle.v_model_desc}
</Link> </Link>
@@ -121,11 +121,11 @@ export default withRouter(function JobsList({
key: "plate_no", key: "plate_no",
width: "8%", width: "8%",
ellipsis: true, ellipsis: true,
sorter: (a, b) => alphaSort(a.vehicle?.plate_no, b.vehicle?.plate_no), sorter: (a, b) => alphaSort(a.vehicle.plate_no, b.vehicle.plate_no),
sortOrder: sortOrder:
state.sortedInfo.columnKey === "plate_no" && state.sortedInfo.order, state.sortedInfo.columnKey === "plate_no" && state.sortedInfo.order,
render: (text, record) => { render: (text, record) => {
return record.vehicle?.plate_no ? ( return record.vehicle.plate_no ? (
<span>{record.vehicle.plate_no}</span> <span>{record.vehicle.plate_no}</span>
) : ( ) : (
t("general.labels.unknown") t("general.labels.unknown")

View File

@@ -8,7 +8,11 @@ export default function LoadingSpinner({ loading = true, message, ...props }) {
spinning={loading} spinning={loading}
className="loading-spinner" className="loading-spinner"
size="large" size="large"
//delay="500" style={{
position: "relative",
alignContent: "center"
}}
delay={200}
tip={message ? message : null} tip={message ? message : null}
> >
{props.children} {props.children}

View File

@@ -1,3 +1,2 @@
.loading-spinner { .loading-spinner {
text-align: center;
} }

View File

@@ -14,7 +14,7 @@ export default function NoteUpsertModalComponent({
return ( return (
<Modal <Modal
title={noteState?.id ? t("notes.actions.edit") : t("notes.actions.new")} title={noteState.id ? t("notes.actions.edit") : t("notes.actions.new")}
visible={visible} visible={visible}
okText={t("general.labels.save")} okText={t("general.labels.save")}
onOk={() => { onOk={() => {
@@ -22,7 +22,8 @@ export default function NoteUpsertModalComponent({
}} }}
onCancel={() => { onCancel={() => {
changeVisibility(false); changeVisibility(false);
}}> }}
>
<div> <div>
{t("notes.fields.critical")} {t("notes.fields.critical")}
<Switch <Switch

View File

@@ -0,0 +1,106 @@
import { Button, Col, Form, Input, Row, Switch } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import FormItemEmail from "../form-items-formatted/email-form-item.component";
import FormItemPhone from "../form-items-formatted/phone-form-item.component";
import ResetForm from "../form-items-formatted/reset-form-item.component";
export default function OwnerDetailFormComponent({ form, owner }) {
const { t } = useTranslation();
const {
isFieldsTouched,
resetFields,
getFieldDecorator,
getFieldValue
} = form;
return (
<div>
{isFieldsTouched() ? <ResetForm resetFields={resetFields} /> : null}
<Button type="primary" key="submit" htmlType="submit">
{t("general.labels.save")}
</Button>
<Row>
<Col span={8}>
<Form.Item label={t("owners.fields.ownr_ln")}>
{getFieldDecorator("ownr_ln", {
initialValue: owner.ownr_ln
})(<Input name="ownr_ln" />)}
</Form.Item>
<Form.Item label={t("owners.fields.ownr_fn")}>
{getFieldDecorator("ownr_fn", {
initialValue: owner.ownr_fn
})(<Input name="ownr_fn" />)}
</Form.Item>
<Form.Item label={t("owners.fields.allow_text_message")}>
{getFieldDecorator("allow_text_message", {
initialValue: owner.allow_text_message,
valuePropName: "checked"
})(<Switch name="allow_text_message" />)}
</Form.Item>
<Form.Item label={t("owners.fields.ownr_addr1")}>
{getFieldDecorator("ownr_addr1", {
initialValue: owner.ownr_addr1
})(<Input name="ownr_addr1" />)}
</Form.Item>
<Form.Item label={t("owners.fields.ownr_addr2")}>
{getFieldDecorator("ownr_addr2", {
initialValue: owner.ownr_addr2
})(<Input name="ownr_addr2" />)}
</Form.Item>
<Form.Item label={t("owners.fields.ownr_city")}>
{getFieldDecorator("ownr_city", {
initialValue: owner.ownr_city
})(<Input name="ownr_city" />)}
</Form.Item>
<Form.Item label={t("owners.fields.ownr_ctry")}>
{getFieldDecorator("ownr_ctry", {
initialValue: owner.ownr_ctry
})(<Input name="ownr_ctry" />)}
</Form.Item>
</Col>
<Col span={8}>
{" "}
<Form.Item label={t("owners.fields.ownr_ea")}>
{getFieldDecorator("ownr_ea", {
initialValue: owner.ownr_ea,
rules: [
{
type: "email",
message: "This is not a valid email address."
}
]
})(
<FormItemEmail name="ownr_ea" email={getFieldValue("ownr_ea")} />
)}
</Form.Item>
<Form.Item label={t("owners.fields.ownr_ph1")}>
{getFieldDecorator("ownr_ph1", {
initialValue: owner.ownr_ph1
})(<FormItemPhone customInput={Input} name="ownr_ph1" />)}
</Form.Item>
<Form.Item label={t("owners.fields.ownr_st")}>
{getFieldDecorator("ownr_st", {
initialValue: owner.ownr_st
})(<Input name="ownr_st" />)}
</Form.Item>
<Form.Item label={t("owners.fields.ownr_zip")}>
{getFieldDecorator("ownr_zip", {
initialValue: owner.ownr_zip
})(<Input name="ownr_zip" />)}
</Form.Item>
<Form.Item label={t("owners.fields.preferred_contact")}>
{getFieldDecorator("preferred_contact", {
initialValue: owner.preferred_contact
})(<Input name="preferred_contact" />)}
</Form.Item>
<Form.Item label={t("owners.fields.ownr_title")}>
{getFieldDecorator("ownr_title", {
initialValue: owner.ownr_title
})(<Input name="ownr_title" />)}
</Form.Item>
</Col>
</Row>
</div>
);
}

View File

@@ -0,0 +1,46 @@
import { Form, notification } from "antd";
import React from "react";
import { useMutation } from "react-apollo";
import { useTranslation } from "react-i18next";
import { UPDATE_OWNER } from "../../graphql/owners.queries";
import OwnerDetailFormComponent from "./owner-detail-form.component";
function OwnerDetailFormContainer({ form, owner, refetch }) {
const { t } = useTranslation();
const [updateOwner] = useMutation(UPDATE_OWNER);
const handleSubmit = e => {
e.preventDefault();
form.validateFieldsAndScroll((err, values) => {
if (err) {
notification["error"]({
message: t("owners.errors.validationtitle"),
description: t("owners.errors.validation")
});
}
if (!err) {
updateOwner({
variables: { ownerId: owner.id, owner: values }
}).then(r => {
notification["success"]({
message: t("owners.successes.save")
});
//TODO: Better way to reset the field decorators?
if (refetch) refetch().then();
form.resetFields();
});
}
});
};
return (
<Form onSubmit={handleSubmit} autoComplete="off">
<OwnerDetailFormComponent form={form} owner={owner} />
</Form>
);
}
export default Form.create({ name: "OwnerDetailFormContainer" })(
OwnerDetailFormContainer
);

View File

@@ -0,0 +1,59 @@
import React from "react";
import { Table } from "antd";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
export default function OwnerDetailJobsComponent({ owner }) {
const { t } = useTranslation();
const columns = [
{
title: t("jobs.fields.ro_number"),
dataIndex: "ro_number",
key: "ro_number",
ellipsis: true,
render: (text, record) => (
<Link to={`/manage/jobs/${record.id}`}>
{record.ro_number ? record.ro_number : `EST ${record.est_number}`}
</Link>
)
},
{
title: t("jobs.fields.vehicle"),
dataIndex: "owner",
key: "owner",
render: (text, record) => (
<Link to={`/manage/vehicles/${record.vehicle.id}`}>
{`${record.vehicle.v_model_yr} ${record.vehicle.v_make_desc} ${record.vehicle.v_model_desc}`}
</Link>
)
},
{
title: t("jobs.fields.clm_no"),
dataIndex: "clm_no",
key: "clm_no"
},
{
title: t("jobs.fields.status"),
dataIndex: "status",
key: "status"
},
{
title: t("jobs.fields.clm_total"),
dataIndex: "clm_total",
key: "clm_total",
render: (text, record) => (
<CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
)
}
];
return (
<Table
pagination={{ position: "bottom" }}
columns={columns.map(item => ({ ...item }))}
rowKey="id"
dataSource={owner.jobs}
/>
);
}

View File

@@ -37,7 +37,10 @@ export default connect(
} }
if (!err) { if (!err) {
console.log("values", values); console.log("values", values);
updateUserDetails({ displayName: values.displayname }); updateUserDetails({
displayName: values.displayname,
photoURL: values.photoURL
});
} }
}); });
}; };
@@ -59,6 +62,12 @@ export default connect(
rules: [{ required: true }] rules: [{ required: true }]
})(<Input name="displayname" />)} })(<Input name="displayname" />)}
</Form.Item> </Form.Item>
<Form.Item label={t("user.fields.photourl")}>
{getFieldDecorator("photoURL", {
initialValue: currentUser.photoURL
})(<Input name="photoURL" />)}
</Form.Item>
<Button <Button
type="primary" type="primary"
key="submit" key="submit"

View File

@@ -11,6 +11,7 @@ export default function ScheduleDayViewComponent({ data, day }) {
<ScheduleCalendarWrapperComponent <ScheduleCalendarWrapperComponent
events={data} events={data}
defaultView="day" defaultView="day"
views={["day"]}
style={{ height: "40vh" }} style={{ height: "40vh" }}
defaultDate={new Date(day)} defaultDate={new Date(day)}
//onNavigate={e => console.log("e", e)} //onNavigate={e => console.log("e", e)}

View File

@@ -27,11 +27,6 @@ export default connect(
bodyshop, bodyshop,
refetch refetch
}) { }) {
const existingAppointments = useQuery(QUERY_APPOINTMENTS_BY_JOBID, {
variables: { jobid: jobId },
fetchPolicy: "network-only",
skip: !jobId
});
const [scheduleModalVisible, setscheduleModalVisible] = scheduleModalState; const [scheduleModalVisible, setscheduleModalVisible] = scheduleModalState;
const [appData, setAppData] = useState({ jobid: jobId, start: null }); const [appData, setAppData] = useState({ jobid: jobId, start: null });
const [insertAppointment] = useMutation(INSERT_APPOINTMENT); const [insertAppointment] = useMutation(INSERT_APPOINTMENT);
@@ -44,6 +39,12 @@ export default connect(
const [formData, setFormData] = useState({ notifyCustomer: false }); const [formData, setFormData] = useState({ notifyCustomer: false });
const { t } = useTranslation(); const { t } = useTranslation();
const existingAppointments = useQuery(QUERY_APPOINTMENTS_BY_JOBID, {
variables: { jobid: jobId },
fetchPolicy: "network-only",
skip: !scheduleModalVisible
});
return ( return (
<ScheduleJobModalComponent <ScheduleJobModalComponent
existingAppointments={existingAppointments} existingAppointments={existingAppointments}

View File

@@ -1,8 +0,0 @@
import React from "react";
import { useTranslation } from "react-i18next";
export default function SignoutComponent({ signOutStart }) {
const { t } = useTranslation();
return <div onClick={signOutStart}>{t("user.actions.signout")}</div>;
}

View File

@@ -0,0 +1,129 @@
import { Button, DatePicker, Form, Input, Row, Col } from "antd";
import moment from "moment";
import React from "react";
import { useTranslation } from "react-i18next";
import ResetForm from "../form-items-formatted/reset-form-item.component";
export default function VehicleDetailFormComponent({ vehicle, form }) {
const { t } = useTranslation();
const { isFieldsTouched, resetFields, getFieldDecorator } = form;
return (
<div>
{isFieldsTouched() ? <ResetForm resetFields={resetFields} /> : null}
<Button type="primary" key="submit" htmlType="submit">
{t("general.labels.save")}
</Button>
<Row>
<Col span={8}>
<Form.Item label={t("vehicles.fields.v_vin")}>
{getFieldDecorator("v_vin", {
initialValue: vehicle.v_vin
})(<Input name="v_vin" />)}
</Form.Item>
<Form.Item label={t("vehicles.fields.plate_no")}>
{getFieldDecorator("plate_no", {
initialValue: vehicle.plate_no
})(<Input name="plate_no" />)}
</Form.Item>
<Form.Item label={t("vehicles.fields.plate_st")}>
{getFieldDecorator("plate_st", {
initialValue: vehicle.plate_st
})(<Input name="plate_st" />)}
</Form.Item>
<Form.Item label={t("vehicles.fields.v_type")}>
{getFieldDecorator("v_type", {
initialValue: vehicle.v_type
})(<Input name="v_type" />)}
</Form.Item>
<Form.Item label={t("vehicles.fields.v_trimcode")}>
{getFieldDecorator("v_trimcode", {
initialValue: vehicle.v_trimcode
})(<Input name="v_trimcode" />)}
</Form.Item>
<Form.Item label={t("vehicles.fields.v_tone")}>
{getFieldDecorator("v_tone", {
initialValue: vehicle.v_tone
})(<Input name="v_tone" />)}
</Form.Item>
<Form.Item label={t("vehicles.fields.v_bstyle")}>
{getFieldDecorator("v_bstyle", {
initialValue: vehicle.v_bstyle
})(<Input name="v_bstyle" />)}
</Form.Item>
</Col>
<Col span={8}>
<Form.Item label={t("vehicles.fields.v_stage")}>
{getFieldDecorator("v_stage", {
initialValue: vehicle.v_stage
})(<Input name="v_stage" />)}
</Form.Item>
<Form.Item label={t("vehicles.fields.v_prod_dt")}>
{getFieldDecorator("v_prod_dt", {
initialValue: vehicle.v_prod_dt ? moment(vehicle.v_prod_dt) : null
})(<DatePicker name="v_prod_dt" />)}
</Form.Item>
{
//TODO Add handling for paint code json
}
<Form.Item label={t("vehicles.fields.v_paint_codes")}>
{getFieldDecorator("v_paint_codes", {
initialValue: JSON.stringify(vehicle.v_paint_codes)
})(<Input name="v_paint_codes" />)}
</Form.Item>
<Form.Item label={t("vehicles.fields.v_options")}>
{getFieldDecorator("v_options", {
initialValue: vehicle.v_options
})(<Input name="v_options" />)}
</Form.Item>
<Form.Item label={t("vehicles.fields.v_model_yr")}>
{getFieldDecorator("v_model_yr", {
initialValue: vehicle.v_model_yr
})(<Input name="v_model_yr" />)}
</Form.Item>
<Form.Item label={t("vehicles.fields.v_model_desc")}>
{getFieldDecorator("v_model_desc", {
initialValue: vehicle.v_model_desc
})(<Input name="v_model_desc" />)}
</Form.Item>
<Form.Item label={t("vehicles.fields.trim_color")}>
{getFieldDecorator("trim_color", {
initialValue: vehicle.trim_color
})(<Input name="trim_color" />)}
</Form.Item>
</Col>
<Col span={8}>
<Form.Item label={t("vehicles.fields.v_mldgcode")}>
{getFieldDecorator("v_mldgcode", {
initialValue: vehicle.v_mldgcode
})(<Input name="v_mldgcode" />)}
</Form.Item>
<Form.Item label={t("vehicles.fields.v_makecode")}>
{getFieldDecorator("v_makecode", {
initialValue: vehicle.v_makecode
})(<Input name="v_makecode" />)}
</Form.Item>
<Form.Item label={t("vehicles.fields.v_make_desc")}>
{getFieldDecorator("v_make_desc", {
initialValue: vehicle.v_make_desc
})(<Input name="v_make_desc" />)}
</Form.Item>
<Form.Item label={t("vehicles.fields.v_engine")}>
{getFieldDecorator("v_engine", {
initialValue: vehicle.v_engine
})(<Input name="v_engine" />)}
</Form.Item>
<Form.Item label={t("vehicles.fields.v_cond")}>
{getFieldDecorator("v_cond", {
initialValue: vehicle.v_cond
})(<Input name="v_cond" />)}
</Form.Item>
<Form.Item label={t("vehicles.fields.v_color")}>
{getFieldDecorator("v_color", {
initialValue: vehicle.v_color
})(<Input name="v_color" />)}
</Form.Item>
</Col>
</Row>
</div>
);
}

View File

@@ -0,0 +1,46 @@
import React from "react";
import { Form, notification } from "antd";
import { useMutation } from "react-apollo";
import VehicleDetailFormComponent from "./vehicle-detail-form.component";
import { useTranslation } from "react-i18next";
import { UPDATE_VEHICLE } from "../../graphql/vehicles.queries";
function VehicleDetailFormContainer({ form, vehicle, refetch }) {
const { t } = useTranslation();
const [updateVehicle] = useMutation(UPDATE_VEHICLE);
const handleSubmit = e => {
e.preventDefault();
form.validateFieldsAndScroll((err, values) => {
if (err) {
notification["error"]({
message: t("vehicles.errors.validationtitle"),
description: t("vehicles.errors.validation")
});
}
if (!err) {
updateVehicle({
variables: { vehId: vehicle.id, vehicle: values }
}).then(r => {
notification["success"]({
message: t("vehicles.successes.save")
});
//TODO: Better way to reset the field decorators?
if (refetch) refetch().then();
form.resetFields();
});
}
});
};
return (
<Form onSubmit={handleSubmit} autoComplete="off">
<VehicleDetailFormComponent form={form} vehicle={vehicle} />
</Form>
);
}
export default Form.create({ name: "VehicleDetailFormContainer" })(
VehicleDetailFormContainer
);

View File

@@ -0,0 +1,59 @@
import React from "react";
import { Table } from "antd";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
export default function VehicleDetailJobsComponent({ vehicle }) {
const { t } = useTranslation();
const columns = [
{
title: t("jobs.fields.ro_number"),
dataIndex: "ro_number",
key: "ro_number",
ellipsis: true,
render: (text, record) => (
<Link to={`/manage/jobs/${record.id}`}>
{record.ro_number ? record.ro_number : `EST ${record.est_number}`}
</Link>
)
},
{
title: t("jobs.fields.owner"),
dataIndex: "owner",
key: "owner",
render: (text, record) => (
<Link to={`/manage/owners/${record.owner.id}`}>
{`${record.ownr_fn} ${record.ownr_ln}`}
</Link>
)
},
{
title: t("jobs.fields.clm_no"),
dataIndex: "clm_no",
key: "clm_no"
},
{
title: t("jobs.fields.status"),
dataIndex: "status",
key: "status"
},
{
title: t("jobs.fields.clm_total"),
dataIndex: "clm_total",
key: "clm_total",
render: (text, record) => (
<CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
)
}
];
return (
<Table
pagination={{ position: "bottom" }}
columns={columns.map(item => ({ ...item }))}
rowKey="id"
dataSource={vehicle.jobs}
/>
);
}

View File

@@ -1,45 +0,0 @@
import React, { useState } from "react";
export default WithInlineEdit = WrappedComponent => props => {
const [editing, setEditing] = useState(false);
const [modified, setModified] = useState(false);
const [originalValue, setOriginalValue] = useState(null);
const toggleEdit = () => {
setEditing(!editing);
if (editing) {
this.input.focus();
}
};
return editing ? (
<Form.Item style={{ margin: 0 }}>
{form.getFieldDecorator(dataIndex, {
rules: [
{
required: true,
message: `${title} is required.`
}
],
initialValue: record[dataIndex]
})(
<Input
ref={node => (this.input = node)}
onPressEnter={this.save}
onBlur={this.save}
/>
)}
</Form.Item>
) : (
<div
className="editable-cell-value-wrap"
style={{ paddingRight: 24 }}
onClick={toggleEdit}
>
{children}
</div>
);
};
export default WithInlineEdit;

View File

@@ -19,7 +19,7 @@ const errorLink = onError(
} }
} }
if (networkError) { if (networkError) {
if (networkError?.message.includes("JWTExpired")) { if (networkError.message.includes("JWTExpired")) {
expired = true; expired = true;
} }
} }

View File

@@ -18,3 +18,50 @@ export const QUERY_SEARCH_OWNER_BY_IDX = gql`
} }
} }
`; `;
export const QUERY_OWNER_BY_ID = gql`
query QUERY_OWNER_BY_ID($id: uuid!) {
owners_by_pk(id: $id) {
id
allow_text_message
ownr_addr1
ownr_addr2
ownr_co_nm
ownr_city
ownr_ctry
ownr_ea
ownr_fn
ownr_ph1
ownr_ln
ownr_ph2
ownr_st
ownr_title
ownr_zip
preferred_contact
jobs {
id
ro_number
est_number
clm_no
status
clm_total
vehicle {
id
v_model_yr
v_model_desc
v_make_desc
}
}
}
}
`;
export const UPDATE_OWNER = gql`
mutation UPDATE_OWNER($ownerId: uuid!, $owner: owners_set_input!) {
update_owners(where: { id: { _eq: $ownerId } }, _set: $owner) {
returning {
id
}
}
}
`;

View File

@@ -0,0 +1,55 @@
import { gql } from "apollo-boost";
export const QUERY_VEHICLE_BY_ID = gql`
query QUERY_VEHICLE_BY_ID($id: uuid!) {
vehicles_by_pk(id: $id) {
created_at
db_v_code
id
plate_no
plate_st
v_vin
v_type
v_trimcode
v_tone
v_stage
v_prod_dt
v_paint_codes
v_options
v_model_yr
v_model_desc
v_mldgcode
v_makecode
v_make_desc
v_engine
v_cond
v_color
v_bstyle
updated_at
trim_color
jobs {
id
ro_number
ownr_fn
est_number
ownr_ln
owner {
id
}
clm_no
status
clm_total
}
}
}
`;
export const UPDATE_VEHICLE = gql`
mutation UPDATE_VEHICLE($vehId: uuid!, $vehicle: vehicles_set_input!) {
update_vehicles(where: { id: { _eq: $vehId } }, _set: $vehicle) {
returning {
id
}
}
}
`;

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.0 KiB

View File

@@ -1,22 +1,18 @@
import { Alert, Button, Form, Icon, Tabs } from "antd"; import { Form, Icon, Tabs } from "antd";
import React, { useContext } from "react"; import React, { useContext } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import { FaHardHat, FaInfo, FaRegStickyNote, FaShieldAlt } from "react-icons/fa";
FaHardHat, import ResetForm from "../../components/form-items-formatted/reset-form-item.component";
FaInfo,
FaRegStickyNote,
FaShieldAlt
} from "react-icons/fa";
import JobsLinesContainer from "../../components/job-detail-lines/job-lines.container"; import JobsLinesContainer from "../../components/job-detail-lines/job-lines.container";
import JobsDetailClaims from "../../components/jobs-detail-claims/jobs-detail-claims.component"; import JobsDetailClaims from "../../components/jobs-detail-claims/jobs-detail-claims.component";
import JobsDetailDatesComponent from "../../components/jobs-detail-dates/jobs-detail-dates.component";
import JobsDetailFinancials from "../../components/jobs-detail-financial/jobs-detail-financial.component"; import JobsDetailFinancials from "../../components/jobs-detail-financial/jobs-detail-financial.component";
import JobsDetailHeader from "../../components/jobs-detail-header/jobs-detail-header.component"; import JobsDetailHeader from "../../components/jobs-detail-header/jobs-detail-header.component";
import JobsDetailInsurance from "../../components/jobs-detail-insurance/jobs-detail-insurance.component"; import JobsDetailInsurance from "../../components/jobs-detail-insurance/jobs-detail-insurance.component";
import JobsDocumentsContainer from "../../components/jobs-documents/jobs-documents.container"; import JobsDocumentsContainer from "../../components/jobs-documents/jobs-documents.container";
import JobNotesContainer from "../../components/jobs-notes/jobs-notes.container"; import JobNotesContainer from "../../components/jobs-notes/jobs-notes.container";
import JobDetailFormContext from "./jobs-detail.page.context";
import JobsDetailDatesComponent from "../../components/jobs-detail-dates/jobs-detail-dates.component";
import ScheduleJobModalContainer from "../../components/schedule-job-modal/schedule-job-modal.container"; import ScheduleJobModalContainer from "../../components/schedule-job-modal/schedule-job-modal.container";
import JobDetailFormContext from "./jobs-detail.page.context";
export default function JobsDetailPage({ export default function JobsDetailPage({
job, job,
@@ -60,19 +56,7 @@ export default function JobsDetailPage({
updateJobStatus={updateJobStatus} updateJobStatus={updateJobStatus}
/> />
{isFieldsTouched() ? ( {isFieldsTouched() ? <ResetForm resetFields={resetFields} /> : null}
<Alert
message={
<div>
{t("general.messages.unsavedchanges")}
<Button onClick={() => resetFields()}>
{t("general.actions.reset")}
</Button>
</div>
}
closable
/>
) : null}
<Tabs defaultActiveKey="claimdetail"> <Tabs defaultActiveKey="claimdetail">
<Tabs.TabPane <Tabs.TabPane

View File

@@ -1,8 +1,11 @@
import React from 'react' import React, { useEffect } from "react";
import ManageRootPageComponent from './manage-root.page.component' import ManageRootPageComponent from "./manage-root.page.component";
import { useTranslation } from "react-i18next";
export default function ManageRootPageContainer() { export default function ManageRootPageContainer() {
return ( const { t } = useTranslation();
<ManageRootPageComponent /> useEffect(() => {
) document.title = t("titles.manageroot");
}, [t]);
return <ManageRootPageComponent />;
} }

View File

@@ -1,6 +1,34 @@
import React from "react"; import React, { useEffect } from "react";
import ManagePage from "./manage.page"; import ManagePage from "./manage.page";
import { useQuery } from "react-apollo";
import { QUERY_BODYSHOP } from "../../graphql/bodyshop.queries";
import { setBodyshop } from "../../redux/user/user.actions";
import { connect } from "react-redux";
import { notification } from "antd";
import { useTranslation } from "react-i18next";
export default function ManagePageContainer() { const mapDispatchToProps = dispatch => ({
return <ManagePage />; setBodyshop: bs => dispatch(setBodyshop(bs))
} });
export default connect(
null,
mapDispatchToProps
)(function ManagePageContainer({ match, setBodyshop }) {
const { error, data } = useQuery(QUERY_BODYSHOP, {
fetchPolicy: "network-only"
});
const { t } = useTranslation();
if (error) {
notification["error"]({ message: t("bodyshop.errors.loading") });
}
useEffect(() => {
if (data) setBodyshop(data.bodyshops[0]);
return () => {
//
};
}, [data, setBodyshop]);
return <ManagePage match={match} />;
});

View File

@@ -1,19 +1,12 @@
import { BackTop, Layout, notification } from "antd"; import { BackTop, Layout } from "antd";
import React, { lazy, Suspense, useEffect } from "react"; import React, { lazy, Suspense, useEffect } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
//This page will handle all routing for the entire application.
import { connect } from "react-redux";
import { Route } from "react-router"; import { Route } from "react-router";
import { createStructuredSelector } from "reselect";
import { useQuery } from "react-apollo";
import { QUERY_BODYSHOP } from "../../graphql/bodyshop.queries";
import ErrorBoundary from "../../components/error-boundary/error-boundary.component"; import ErrorBoundary from "../../components/error-boundary/error-boundary.component";
import FooterComponent from "../../components/footer/footer.component"; import FooterComponent from "../../components/footer/footer.component";
//Component Imports //Component Imports
import HeaderContainer from "../../components/header/header.container"; import HeaderContainer from "../../components/header/header.container";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
//const WhiteBoardPage = lazy(() => import("../white-board/white-board.page"));
import { setBodyshop } from "../../redux/user/user.actions";
import "./manage.page.styles.scss"; import "./manage.page.styles.scss";
const ManageRootPage = lazy(() => const ManageRootPage = lazy(() =>
@@ -36,29 +29,21 @@ const ChatWindowContainer = lazy(() =>
const ScheduleContainer = lazy(() => const ScheduleContainer = lazy(() =>
import("../schedule/schedule.page.container") import("../schedule/schedule.page.container")
); );
const VehiclesContainer = lazy(() =>
import("../vehicles/vehicles.page.container")
);
const VehiclesDetailContainer = lazy(() =>
import("../vehicles-detail/vehicles-detail.page.container")
);
const OwnersContainer = lazy(() => import("../owners/owners.page.container"));
const OwnersDetailContainer = lazy(() =>
import("../owners-detail/owners-detail.page.container")
);
const { Header, Content, Footer } = Layout; const { Header, Content, Footer } = Layout;
const mapDispatchToProps = dispatch => ({ export default function Manage({ match }) {
setBodyshop: bs => dispatch(setBodyshop(bs))
});
export default connect(
null,
mapDispatchToProps
)(function Manage({ match, setBodyshop }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { error, data } = useQuery(QUERY_BODYSHOP, {
fetchPolicy: "network-only"
});
if (error) {
notification["error"]({ message: t("bodyshop.errors.loading") });
}
if (data) {
setBodyshop(data.bodyshops[0]);
}
useEffect(() => { useEffect(() => {
document.title = t("titles.app"); document.title = t("titles.app");
}, [t]); }, [t]);
@@ -97,7 +82,26 @@ export default connect(
path={`${match.path}/profile`} path={`${match.path}/profile`}
component={ProfilePage} component={ProfilePage}
/> />
<Route
exact
path={`${match.path}/vehicles`}
component={VehiclesContainer}
/>
<Route
exact
path={`${match.path}/vehicles/:vehId`}
component={VehiclesDetailContainer}
/>
<Route
exact
path={`${match.path}/owners`}
component={OwnersContainer}
/>
<Route
exact
path={`${match.path}/owners/:ownerId`}
component={OwnersDetailContainer}
/>
<Route <Route
exact exact
path={`${match.path}/schedule`} path={`${match.path}/schedule`}
@@ -119,4 +123,4 @@ export default connect(
<BackTop /> <BackTop />
</Layout> </Layout>
); );
}); }

View File

@@ -0,0 +1,11 @@
import React from "react";
import OwnerDetailForm from "../../components/owner-detail-form/owner-detail-form.container";
import OwnerDetailJobsComponent from "../../components/owner-detail-jobs/owner-detail-jobs.component";
export default function OwnersDetailComponent({ owner, refetch }) {
return (
<div>
<OwnerDetailForm owner={owner} refetch={refetch} />
<OwnerDetailJobsComponent owner={owner} />
</div>
);
}

View File

@@ -0,0 +1,28 @@
import React from "react";
import OwnersDetailComponent from "./owners-detail.page.component";
import { useTranslation } from "react-i18next";
import { useQuery } from "react-apollo";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import AlertComponent from "../../components/alert/alert.component";
import { QUERY_OWNER_BY_ID } from "../../graphql/owners.queries";
export default function OwnersDetailContainer({ match }) {
const { ownerId } = match.params;
const { t } = useTranslation();
const { loading, data, error, refetch } = useQuery(QUERY_OWNER_BY_ID, {
variables: { id: ownerId },
fetchPolicy: "network-only"
});
if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />;
if (data.owners_by_pk)
return (
<OwnersDetailComponent owner={data.owners_by_pk} refetch={refetch} />
);
else
return (
<AlertComponent message={t("owners.errors.noaccess")} type="error" />
);
}

View File

@@ -0,0 +1,9 @@
import React from 'react'
export default function OwnersPageComponent() {
return (
<div>
Owners Page
</div>
)
}

View File

@@ -0,0 +1,8 @@
import React from 'react'
import OwnersPageComponent from './owners.page.component'
export default function OwnersPageContainer() {
return (
<OwnersPageComponent />
)
}

View File

@@ -1,6 +1,11 @@
import React from "react"; import React, { useEffect } from "react";
import ProfilePage from "./profile.page"; import ProfilePage from "./profile.page";
import { useTranslation } from "react-i18next";
export default function ProfileContainerPage() { export default function ProfileContainerPage() {
const { t } = useTranslation();
useEffect(() => {
document.title = t("titles.profile");
}, [t]);
return <ProfilePage />; return <ProfilePage />;
} }

View File

@@ -1,16 +1,9 @@
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Layout } from "antd"; import { Layout } from "antd";
import ProfileSideBar from "../../components/profile-sidebar/profile-sidebar.component"; import React, { useState } from "react";
import ProfileContent from "../../components/profile-content/profile-content.component"; import ProfileContent from "../../components/profile-content/profile-content.component";
import ProfileSideBar from "../../components/profile-sidebar/profile-sidebar.component";
export default function ProfilePage() { export default function ProfilePage() {
const { t } = useTranslation();
useEffect(() => {
document.title = t("titles.profile");
}, [t]);
const [sidebarSelection, setSidebarSelection] = useState({ key: "profile" }); const [sidebarSelection, setSidebarSelection] = useState({ key: "profile" });
return ( return (
<Layout> <Layout>

View File

@@ -0,0 +1,12 @@
import React from "react";
import VehicleDetailFormContainer from "../../components/vehicle-detail-form/vehicle-detail-form.container";
import VehicleDetailJobsComponent from "../../components/vehicle-detail-jobs/vehicle-detail-jobs.component";
export default function VehicleDetailComponent({ vehicle, refetch }) {
return (
<div>
<VehicleDetailFormContainer vehicle={vehicle} refetch={refetch} />
<VehicleDetailJobsComponent vehicle={vehicle} />
</div>
);
}

View File

@@ -0,0 +1,37 @@
import React, { useEffect } from "react";
import VehicleDetailComponent from "./vehicles-detail.page.component";
import { useQuery } from "react-apollo";
import { QUERY_VEHICLE_BY_ID } from "../../graphql/vehicles.queries";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import AlertComponent from "../../components/alert/alert.component";
import { useTranslation } from "react-i18next";
export default function VehicleDetailContainer({ match }) {
const { vehId } = match.params;
const { t } = useTranslation();
const { loading, data, error, refetch } = useQuery(QUERY_VEHICLE_BY_ID, {
variables: { id: vehId },
fetchPolicy: "network-only"
});
useEffect(() => {
document.title = t("titles.vehicledetail", {
vehicle:
data && data.vehicles_by_pk
? `${data.vehicles_by_pk.v_model_yr} ${data.vehicles_by_pk.v_make_desc} ${data.vehicles_by_pk.v_model_desc}`
: ""
});
}, [t, data]);
if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />;
if (data.vehicles_by_pk)
return (
<VehicleDetailComponent vehicle={data.vehicles_by_pk} refetch={refetch} />
);
else
return (
<AlertComponent message={t("vehicles.errors.noaccess")} type="error" />
);
}

View File

@@ -0,0 +1,9 @@
import React from 'react'
export default function VehiclesPageComponent() {
return (
<div>
Vehiculos
</div>
)
}

View File

@@ -0,0 +1,6 @@
import React from "react";
import VehiclesPageComponent from "./vehicles.page.component";
export default function VehiclesPageContainer() {
return <VehiclesPageComponent />;
}

View File

@@ -34,6 +34,7 @@ export function* signInWithEmail({ payload: { email, password } }) {
uid: user.uid, uid: user.uid,
email: user.email, email: user.email,
displayName: user.displayName, displayName: user.displayName,
photoURL: user.photoURL,
authorized: true authorized: true
}) })
); );
@@ -62,6 +63,7 @@ export function* isUserAuthenticated() {
uid: user.uid, uid: user.uid,
email: user.email, email: user.email,
displayName: user.displayName, displayName: user.displayName,
photoURL: user.photoURL,
authorized: true authorized: true
}) })
); );

View File

@@ -246,9 +246,12 @@
"header": { "header": {
"activejobs": "Active Jobs", "activejobs": "Active Jobs",
"availablejobs": "Available Jobs", "availablejobs": "Available Jobs",
"customers": "Customers",
"home": "Home", "home": "Home",
"jobs": "Jobs", "jobs": "Jobs",
"schedule": "Schedule" "owners": "Owners",
"schedule": "Schedule",
"vehicles": "Vehicles"
}, },
"jobsdetail": { "jobsdetail": {
"claimdetail": "Claim Details", "claimdetail": "Claim Details",
@@ -288,16 +291,29 @@
} }
}, },
"owners": { "owners": {
"errors": {
"noaccess": "The record does not exist or you do not have access to it. "
},
"fields": { "fields": {
"allow_text_message": "Permission to Text?",
"ownr_addr1": "Address", "ownr_addr1": "Address",
"ownr_addr2": "Address 2",
"ownr_city": "City", "ownr_city": "City",
"ownr_ctry": "Country",
"ownr_ea": "Email", "ownr_ea": "Email",
"ownr_fn": "First Name", "ownr_fn": "First Name",
"ownr_ln": "Last Name", "ownr_ln": "Last Name",
"ownr_ph1": "Phone 1" "ownr_ph1": "Phone 1",
"ownr_st": "State/Province",
"ownr_title": "Title",
"ownr_zip": "Zip/Postal Code",
"preferred_contact": "Preferred Contact Method"
}, },
"labels": { "labels": {
"existing_owners": "Existing Owners" "existing_owners": "Existing Owners"
},
"successes": {
"save": "Owner saved successfully."
} }
}, },
"profile": { "profile": {
@@ -311,8 +327,10 @@
"jobsavailable": "Available Jobs | $t(titles.app)", "jobsavailable": "Available Jobs | $t(titles.app)",
"jobsdetail": "Job {{ro_number}} | $t(titles.app)", "jobsdetail": "Job {{ro_number}} | $t(titles.app)",
"jobsdocuments": "Job Documents {{ro_number}} | $t(titles.app)", "jobsdocuments": "Job Documents {{ro_number}} | $t(titles.app)",
"manageroot": "Home | $t(titles.app)",
"profile": "My Profile | $t(titles.app)", "profile": "My Profile | $t(titles.app)",
"schedule": "Schedule | $t(titles.app)" "schedule": "Schedule | $t(titles.app)",
"vehicledetail": "Vehicle Details {{vehicle}} | $t(titles.app)"
}, },
"user": { "user": {
"actions": { "actions": {
@@ -320,12 +338,40 @@
"updateprofile": "Update Profile" "updateprofile": "Update Profile"
}, },
"fields": { "fields": {
"displayname": "Display Name" "displayname": "Display Name",
"photourl": "Avatar URL"
} }
}, },
"vehicles": { "vehicles": {
"errors": {
"noaccess": "The vehicle does not exist or you do not have access to it.",
"validation": "Please ensure all fields are entered correctly.",
"validationtitle": "Validation Error"
},
"fields": { "fields": {
"plate_no": "License Plate" "plate_no": "License Plate",
"plate_st": "Plate Jurisdiction",
"trim_color": "Trim Color",
"v_bstyle": "Body Style",
"v_color": "Color",
"v_cond": "Condition",
"v_engine": "Engine",
"v_make_desc": "Make",
"v_makecode": "Make Code",
"v_mldgcode": "Molding Code",
"v_model_desc": "Model",
"v_model_yr": "Year",
"v_options": "Options",
"v_paint_codes": "Paint Codes",
"v_prod_dt": "Production Date",
"v_stage": "Stage",
"v_tone": "Tone",
"v_trimcode": "Trim Code",
"v_type": "Type",
"v_vin": "Vehicle Identification Number"
},
"successes": {
"save": "Vehicle saved successfully."
} }
} }
} }

View File

@@ -246,9 +246,12 @@
"header": { "header": {
"activejobs": "Empleos activos", "activejobs": "Empleos activos",
"availablejobs": "Trabajos disponibles", "availablejobs": "Trabajos disponibles",
"customers": "Clientes",
"home": "Casa", "home": "Casa",
"jobs": "Trabajos", "jobs": "Trabajos",
"schedule": "Programar" "owners": "propietarios",
"schedule": "Programar",
"vehicles": "Vehículos"
}, },
"jobsdetail": { "jobsdetail": {
"claimdetail": "Detalles de la reclamación", "claimdetail": "Detalles de la reclamación",
@@ -288,16 +291,29 @@
} }
}, },
"owners": { "owners": {
"errors": {
"noaccess": "El registro no existe o no tiene acceso a él."
},
"fields": { "fields": {
"allow_text_message": "Permiso de texto?",
"ownr_addr1": "Dirección", "ownr_addr1": "Dirección",
"ownr_addr2": "Dirección 2",
"ownr_city": "ciudad", "ownr_city": "ciudad",
"ownr_ctry": "País",
"ownr_ea": "Email", "ownr_ea": "Email",
"ownr_fn": "Nombre de pila", "ownr_fn": "Nombre de pila",
"ownr_ln": "Apellido", "ownr_ln": "Apellido",
"ownr_ph1": "" "ownr_ph1": "Teléfono 1",
"ownr_st": "Provincia del estado",
"ownr_title": "Título",
"ownr_zip": "código postal",
"preferred_contact": "Método de Contacto Preferido"
}, },
"labels": { "labels": {
"existing_owners": "Propietarios existentes" "existing_owners": "Propietarios existentes"
},
"successes": {
"save": "Propietario guardado con éxito."
} }
}, },
"profile": { "profile": {
@@ -311,8 +327,10 @@
"jobsavailable": "Empleos disponibles | $t(titles.app)", "jobsavailable": "Empleos disponibles | $t(titles.app)",
"jobsdetail": "Trabajo {{ro_number}} | $t(titles.app)", "jobsdetail": "Trabajo {{ro_number}} | $t(titles.app)",
"jobsdocuments": "Documentos de trabajo {{ro_number}} | $ t (títulos.app)", "jobsdocuments": "Documentos de trabajo {{ro_number}} | $ t (títulos.app)",
"manageroot": "Casa | $t(titles.app)",
"profile": "Mi perfil | $t(titles.app)", "profile": "Mi perfil | $t(titles.app)",
"schedule": "Horario | $t(titles.app)" "schedule": "Horario | $t(titles.app)",
"vehicledetail": "Detalles del vehículo {{vehicle}} | $t(titles.app)"
}, },
"user": { "user": {
"actions": { "actions": {
@@ -320,12 +338,40 @@
"updateprofile": "Actualización del perfil" "updateprofile": "Actualización del perfil"
}, },
"fields": { "fields": {
"displayname": "Nombre para mostrar" "displayname": "Nombre para mostrar",
"photourl": "URL de avatar"
} }
}, },
"vehicles": { "vehicles": {
"errors": {
"noaccess": "El vehículo no existe o usted no tiene acceso a él.",
"validation": "Asegúrese de que todos los campos se ingresen correctamente.",
"validationtitle": "Error de validacion"
},
"fields": { "fields": {
"plate_no": "Placa" "plate_no": "Placa",
"plate_st": "Jurisdicción de placas",
"trim_color": "Recortar color",
"v_bstyle": "Tipo de cuerpo",
"v_color": "Color",
"v_cond": "condición",
"v_engine": "Motor",
"v_make_desc": "Hacer",
"v_makecode": "Hacer código",
"v_mldgcode": "Código de moldeo",
"v_model_desc": "Modelo",
"v_model_yr": "año",
"v_options": "Opciones",
"v_paint_codes": "Códigos de pintura",
"v_prod_dt": "Fecha de producción",
"v_stage": "Escenario",
"v_tone": "Tono",
"v_trimcode": "Código de recorte",
"v_type": "Tipo",
"v_vin": "Número de identificación del vehículo"
},
"successes": {
"save": "Vehículo guardado con éxito."
} }
} }
} }

View File

@@ -246,9 +246,12 @@
"header": { "header": {
"activejobs": "Emplois actifs", "activejobs": "Emplois actifs",
"availablejobs": "Emplois disponibles", "availablejobs": "Emplois disponibles",
"customers": "Les clients",
"home": "Accueil", "home": "Accueil",
"jobs": "Emplois", "jobs": "Emplois",
"schedule": "Programme" "owners": "Propriétaires",
"schedule": "Programme",
"vehicles": "Véhicules"
}, },
"jobsdetail": { "jobsdetail": {
"claimdetail": "Détails de la réclamation", "claimdetail": "Détails de la réclamation",
@@ -288,16 +291,29 @@
} }
}, },
"owners": { "owners": {
"errors": {
"noaccess": "L'enregistrement n'existe pas ou vous n'y avez pas accès."
},
"fields": { "fields": {
"allow_text_message": "Autorisation de texte?",
"ownr_addr1": "Adresse", "ownr_addr1": "Adresse",
"ownr_addr2": "Adresse 2 ",
"ownr_city": "Ville", "ownr_city": "Ville",
"ownr_ctry": "Pays",
"ownr_ea": "Email", "ownr_ea": "Email",
"ownr_fn": "Prénom", "ownr_fn": "Prénom",
"ownr_ln": "Nom de famille", "ownr_ln": "Nom de famille",
"ownr_ph1": "" "ownr_ph1": "Téléphone 1",
"ownr_st": "Etat / Province",
"ownr_title": "Titre",
"ownr_zip": "Zip / code postal",
"preferred_contact": "Méthode de contact préférée"
}, },
"labels": { "labels": {
"existing_owners": "Propriétaires existants" "existing_owners": "Propriétaires existants"
},
"successes": {
"save": "Le propriétaire a bien enregistré."
} }
}, },
"profile": { "profile": {
@@ -311,8 +327,10 @@
"jobsavailable": "Emplois disponibles | $t(titles.app)", "jobsavailable": "Emplois disponibles | $t(titles.app)",
"jobsdetail": "Travail {{ro_number}} | $t(titles.app)", "jobsdetail": "Travail {{ro_number}} | $t(titles.app)",
"jobsdocuments": "Documents de travail {{ro_number}} | $ t (titres.app)", "jobsdocuments": "Documents de travail {{ro_number}} | $ t (titres.app)",
"manageroot": "Accueil | $t(titles.app)",
"profile": "Mon profil | $t(titles.app)", "profile": "Mon profil | $t(titles.app)",
"schedule": "Horaire | $t(titles.app)" "schedule": "Horaire | $t(titles.app)",
"vehicledetail": "Détails du véhicule {{vehicle} | $t(titles.app)"
}, },
"user": { "user": {
"actions": { "actions": {
@@ -320,12 +338,40 @@
"updateprofile": "Mettre à jour le profil" "updateprofile": "Mettre à jour le profil"
}, },
"fields": { "fields": {
"displayname": "Afficher un nom" "displayname": "Afficher un nom",
"photourl": "URL de l'avatar"
} }
}, },
"vehicles": { "vehicles": {
"errors": {
"noaccess": "Le véhicule n'existe pas ou vous n'y avez pas accès.",
"validation": "Veuillez vous assurer que tous les champs sont correctement entrés.",
"validationtitle": "Erreur de validation"
},
"fields": { "fields": {
"plate_no": "Plaque d'immatriculation" "plate_no": "Plaque d'immatriculation",
"plate_st": "Juridiction de la plaque",
"trim_color": "Couleur de garniture",
"v_bstyle": "Style corporel",
"v_color": "Couleur",
"v_cond": "Etat",
"v_engine": "moteur",
"v_make_desc": "Faire",
"v_makecode": "Faire du code",
"v_mldgcode": "Code de moulage",
"v_model_desc": "Modèle",
"v_model_yr": "année",
"v_options": "Les options",
"v_paint_codes": "Codes de peinture",
"v_prod_dt": "Date de production",
"v_stage": "Étape",
"v_tone": "ton",
"v_trimcode": "Code de coupe",
"v_type": "Type",
"v_vin": "Plaque d'immatriculation"
},
"successes": {
"save": "Le véhicule a été enregistré avec succès."
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -17,14 +17,14 @@
}, },
"dependencies": { "dependencies": {
"apollo-cache-persist": "^0.1.1", "apollo-cache-persist": "^0.1.1",
"aws-sdk": "^2.603.0", "aws-sdk": "^2.613.0",
"body-parser": "^1.18.3", "body-parser": "^1.18.3",
"compression": "^1.7.4", "compression": "^1.7.4",
"cors": "2.8.5", "cors": "2.8.5",
"dotenv": "7.0.0", "dotenv": "8.2.0",
"express": "^4.16.4", "express": "^4.16.4",
"express-sslify": "^1.2.0", "express-sslify": "^1.2.0",
"firebase-tools": "^7.9.0" "firebase-tools": "^7.12.1"
}, },
"devDependencies": { "devDependencies": {
"concurrently": "^4.0.1", "concurrently": "^4.0.1",

View File

@@ -386,10 +386,10 @@ asynckit@^0.4.0:
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
aws-sdk@^2.603.0: aws-sdk@^2.613.0:
version "2.603.0" version "2.613.0"
resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.603.0.tgz#0920756d2666f4fcfa7233841ef35cd04da81348" resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.613.0.tgz#f0d4c99f0df7ee8227d331dde19d5251b31a5da7"
integrity sha512-+VlskUDLZLQDDlaVa0Tb02aEFEWcKkTfTew1SGYwce9hUrKcR33IX4e9kM6MyI7UeLQAl0v8dagTniP67UrTQw== integrity sha512-FYLaVtC/AlrcnjsPw1JhAsKd6yapr918Mk0jAcw3yFZp1sI2V0Um+2pmijLFsV+nNRxFlCVJRhhFWB5GK6yALA==
dependencies: dependencies:
buffer "4.9.1" buffer "4.9.1"
events "1.1.1" events "1.1.1"
@@ -1092,10 +1092,10 @@ dot-prop@^4.1.0:
dependencies: dependencies:
is-obj "^1.0.0" is-obj "^1.0.0"
dotenv@7.0.0: dotenv@8.2.0:
version "7.0.0" version "8.2.0"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-7.0.0.tgz#a2be3cd52736673206e8a85fb5210eea29628e7c" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a"
integrity sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g== integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==
dotenv@^6.1.0: dotenv@^6.1.0:
version "6.2.0" version "6.2.0"
@@ -1548,10 +1548,10 @@ find-up@^3.0.0:
dependencies: dependencies:
locate-path "^3.0.0" locate-path "^3.0.0"
firebase-tools@^7.9.0: firebase-tools@^7.12.1:
version "7.9.0" version "7.12.1"
resolved "https://registry.yarnpkg.com/firebase-tools/-/firebase-tools-7.9.0.tgz#32661980943f5ebbb2d82c126762c468ae0cff9c" resolved "https://registry.yarnpkg.com/firebase-tools/-/firebase-tools-7.12.1.tgz#af78bbc446ae46d722938bd8009c351d52ec01f3"
integrity sha512-dYxuFflaPAkic2KQyPJ6spox8KzlQRb4Xyk6oVDorBV0YmBIJ3VQhJYbwSZwwDlbCmiPiMXNBM26JUifp09Qfw== integrity sha512-4cfHltsfUzKyt03OH9JUYrCPsq0OIZ9i54IzJRjJgs68NJDLrhUislBipw5Am0QgF8QDYpzz8wLTH56fNgmx8g==
dependencies: dependencies:
"@google-cloud/pubsub" "^1.1.5" "@google-cloud/pubsub" "^1.1.5"
JSONStream "^1.2.1" JSONStream "^1.2.1"
@@ -1574,6 +1574,7 @@ firebase-tools@^7.9.0:
fs-extra "^0.23.1" fs-extra "^0.23.1"
glob "^7.1.2" glob "^7.1.2"
google-auto-auth "^0.7.2" google-auto-auth "^0.7.2"
google-gax "~1.12.0"
inquirer "~6.3.1" inquirer "~6.3.1"
jsonschema "^1.0.2" jsonschema "^1.0.2"
jsonwebtoken "^8.2.1" jsonwebtoken "^8.2.1"
@@ -1587,7 +1588,7 @@ firebase-tools@^7.9.0:
portfinder "^1.0.23" portfinder "^1.0.23"
progress "^2.0.3" progress "^2.0.3"
request "^2.87.0" request "^2.87.0"
semver "^5.0.3" semver "^5.7.1"
superstatic "^6.0.1" superstatic "^6.0.1"
tar "^4.3.0" tar "^4.3.0"
tcp-port-used "^1.0.1" tcp-port-used "^1.0.1"
@@ -1843,7 +1844,7 @@ google-auto-auth@^0.7.2:
google-auth-library "^0.10.0" google-auth-library "^0.10.0"
request "^2.79.0" request "^2.79.0"
google-gax@^1.7.5: google-gax@^1.7.5, google-gax@~1.12.0:
version "1.12.0" version "1.12.0"
resolved "https://registry.yarnpkg.com/google-gax/-/google-gax-1.12.0.tgz#f926f7e6abda245db38ecbebbbf58daaf3a8f687" resolved "https://registry.yarnpkg.com/google-gax/-/google-gax-1.12.0.tgz#f926f7e6abda245db38ecbebbbf58daaf3a8f687"
integrity sha512-BeeoxVO6y9K20gUsexUwptutd0PfrTItrA02JWwwstlBIOAcvgFp86MHWufQsnrkPVhxBjHXq65aIkSejtJjDg== integrity sha512-BeeoxVO6y9K20gUsexUwptutd0PfrTItrA02JWwwstlBIOAcvgFp86MHWufQsnrkPVhxBjHXq65aIkSejtJjDg==
@@ -3508,7 +3509,7 @@ semver-diff@^2.0.0:
dependencies: dependencies:
semver "^5.0.3" semver "^5.0.3"
"semver@2 || 3 || 4 || 5", semver@^5.0.1, semver@^5.0.3, semver@^5.1.0, semver@^5.5.0, semver@^5.6.0: "semver@2 || 3 || 4 || 5", semver@^5.0.1, semver@^5.0.3, semver@^5.1.0, semver@^5.5.0, semver@^5.6.0, semver@^5.7.1:
version "5.7.1" version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==