Merge pull request #11 from snaptsoft/dev-patrick

Dev patrick
This commit is contained in:
2020-02-19 20:34:29 -08:00
committed by GitHub
144 changed files with 4781 additions and 382 deletions

View File

@@ -4,6 +4,7 @@
..\*Include the statuses file in the format of: ..\*Include the statuses file in the format of:
```json ```json
{
"statuses": [ "statuses": [
"Open", "Open",
"Scheduled", "Scheduled",
@@ -21,14 +22,29 @@
"Invoiced", "Invoiced",
"Exported" "Exported"
], ],
"default_imported": "Open", "open_statuses": [
"default_scheduled": "Scheduled", "Open",
"Scheduled",
"Arrived",
"Repair Plan",
"Parts",
"Body",
"Prep",
"Paint",
"Reassembly",
"Sublet",
"Detail",
"Completed"
],
"default_arrived": "Arrived", "default_arrived": "Arrived",
"default_exported": "Exported",
"default_imported": "Open",
"default_invoiced": "Invoiced",
"default_completed": "Completed", "default_completed": "Completed",
"default_delivered": "Delivered", "default_delivered": "Delivered",
"default_invoiced": "Invoiced", "default_scheduled": "Scheduled"
"default_exported": "Exported"
} }
``` ```
--\* Set the region for the shop. --\* Set the region for the shop.

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
@@ -1283,6 +1283,48 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>loadingshop</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>loggingin</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>na</name> <name>na</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -1346,6 +1388,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>search</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>unknown</name> <name>unknown</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -1466,6 +1529,27 @@
<folder_node> <folder_node>
<name>validation</name> <name>validation</name>
<children> <children>
<concept_node>
<name>invalidemail</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>required</name> <name>required</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -4823,6 +4907,48 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>shop_config</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>shop_vendors</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>vehicles</name> <name>vehicles</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -5769,6 +5895,167 @@
</folder_node> </folder_node>
</children> </children>
</folder_node> </folder_node>
<folder_node>
<name>parts</name>
<children>
<folder_node>
<name>actions</name>
<children>
<concept_node>
<name>order</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
</children>
</folder_node>
<folder_node>
<name>parts_orders</name>
<children>
<folder_node>
<name>errors</name>
<children>
<concept_node>
<name>creating</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>fields</name>
<children>
<concept_node>
<name>deliver_by</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>labels</name>
<children>
<concept_node>
<name>email</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>print</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>created</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
</children>
</folder_node>
<folder_node> <folder_node>
<name>profile</name> <name>profile</name>
<children> <children>
@@ -6013,6 +6300,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>shop_vendors</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>vehicledetail</name> <name>vehicledetail</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -6701,6 +7009,519 @@
</folder_node> </folder_node>
</children> </children>
</folder_node> </folder_node>
<folder_node>
<name>vendors</name>
<children>
<folder_node>
<name>actions</name>
<children>
<concept_node>
<name>new</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>errors</name>
<children>
<concept_node>
<name>deleting</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>saving</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>fields</name>
<children>
<concept_node>
<name>city</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_center</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>country</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>discount</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>display_name</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>due_date</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>email</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>favorite</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>name</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>prompt_discount</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>state</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>street1</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>street2</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>taxid</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>terms</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>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>
</children>
</folder_node>
<folder_node>
<name>labels</name>
<children>
<concept_node>
<name>noneselected</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>search</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>deleted</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>saved</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
</children>
</folder_node>
</children> </children>
</folder_node> </folder_node>
</children> </children>

View File

@@ -4,6 +4,8 @@
"private": true, "private": true,
"proxy": "https://localhost:5000", "proxy": "https://localhost:5000",
"dependencies": { "dependencies": {
"@ckeditor/ckeditor5-build-classic": "^16.0.0",
"@ckeditor/ckeditor5-react": "^2.1.0",
"antd": "^3.26.8", "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",
@@ -23,6 +25,7 @@
"react-big-calendar": "^0.23.0", "react-big-calendar": "^0.23.0",
"react-chartjs-2": "^2.9.0", "react-chartjs-2": "^2.9.0",
"react-dom": "^16.12.0", "react-dom": "^16.12.0",
"react-html-email": "^3.0.0",
"react-i18next": "^11.3.1", "react-i18next": "^11.3.1",
"react-icons": "^3.9.0", "react-icons": "^3.9.0",
"react-image-file-resizer": "^0.2.1", "react-image-file-resizer": "^0.2.1",
@@ -37,7 +40,8 @@
"redux-saga": "^1.1.3", "redux-saga": "^1.1.3",
"reselect": "^4.0.0", "reselect": "^4.0.0",
"styled-components": "^5.0.1", "styled-components": "^5.0.1",
"subscriptions-transport-ws": "^0.9.16" "subscriptions-transport-ws": "^0.9.16",
"twilio": "^3.39.5"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",

View File

@@ -1,5 +1,6 @@
import i18next from "i18next"; import i18next from "i18next";
import React, { lazy, Suspense, useEffect } from "react"; import React, { lazy, Suspense, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Route, Switch } from "react-router-dom"; import { Route, Switch } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
@@ -33,7 +34,7 @@ export default connect(
checkUserSession(); checkUserSession();
return () => {}; return () => {};
}, [checkUserSession]); }, [checkUserSession]);
const { t } = useTranslation();
if (currentUser && currentUser.language) if (currentUser && currentUser.language)
i18next.changeLanguage(currentUser.language, (err, t) => { i18next.changeLanguage(currentUser.language, (err, t) => {
if (err) if (err)
@@ -41,8 +42,7 @@ export default connect(
}); });
if (currentUser.authorized === null) { if (currentUser.authorized === null) {
//TODO: Translate this. return <LoadingSpinner message={t("general.labels.loggingin")} />;
return <LoadingSpinner message="Waiting for Current Auth to persist." />;
} }
return ( return (
@@ -50,15 +50,15 @@ export default connect(
<Switch> <Switch>
<ErrorBoundary> <ErrorBoundary>
<Suspense fallback={<LoadingSpinner />}> <Suspense fallback={<LoadingSpinner />}>
<Route exact path="/" component={LandingPage} /> <Route exact path='/' component={LandingPage} />
<Route exact path="/unauthorized" component={Unauthorized} /> <Route exact path='/unauthorized' component={Unauthorized} />
<Route exact path="/signin" component={SignInPage} /> <Route exact path='/signin' component={SignInPage} />
<PrivateRoute <PrivateRoute
//isAuthorized={HookCurrentUser.data.currentUser ? true : false} //isAuthorized={HookCurrentUser.data.currentUser ? true : false}
isAuthorized={currentUser.authorized} isAuthorized={currentUser.authorized}
path="/manage" path='/manage'
component={ManagePage} component={ManagePage}
/> />
</Suspense> </Suspense>

View File

@@ -2,10 +2,12 @@ import React from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import Alert from "./alert.component"; import Alert from "./alert.component";
import { MockedProvider } from "@apollo/react-testing"; import { MockedProvider } from "@apollo/react-testing";
import { shallow } from "enzyme"; import { shallow, mount } from "enzyme";
const div = document.createElement("div"); const div = document.createElement("div");
it("renders without crashing", () => { it("renders without crashing", () => {
shallow(<Alert type="error" />); const wrapper = mount(<Alert type="error" message="Test Error" />);
console.log("wrapper", wrapper);
// expect(wrapper.children()).to.have.lengthOf(1);
}); });

View File

@@ -5,7 +5,11 @@ import { INSERT_ALLOCATION } from "../../graphql/allocations.queries";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { notification } from "antd"; import { notification } from "antd";
export default function AllocationsAssignmentContainer({ jobLineId, hours }) { export default function AllocationsAssignmentContainer({
jobLineId,
hours,
refetch
}) {
const visibilityState = useState(false); const visibilityState = useState(false);
const { t } = useTranslation(); const { t } = useTranslation();
const [assignment, setAssignment] = useState({ const [assignment, setAssignment] = useState({
@@ -20,9 +24,9 @@ export default function AllocationsAssignmentContainer({ jobLineId, hours }) {
notification["success"]({ notification["success"]({
message: t("employees.successes.save") message: t("employees.successes.save")
}); });
//TODO: Better way to reset the field decorators? //TODO Better way to reset the field decorators?
visibilityState[1](false); visibilityState[1](false);
//refetch().then(r => form.resetFields()); if (refetch) refetch();
}); });
}; };

View File

@@ -0,0 +1,20 @@
import React from "react";
import { shallow } from "enzyme";
import AllocationsAssignmentContainer from "./allocations-assignment.container";
describe("LineAllocationsContainer", () => {
let mockRefetch;
let jobLineId;
let wrapper;
beforeEach(() => {
mockRefetch = jest.fn;
jobLineId = "b76e44a8-943f-4c67-b8f4-38d14db8b4b8";
const mockProps = {
refetch: mockRefetch,
jobLineId,
hours: 5
};
shallow(<AllocationsAssignmentContainer {...mockProps} />);
});
});

View File

@@ -1,10 +1,53 @@
import React from "react"; import { Card } from "antd";
import React, { useState, useEffect } from "react";
import "./chat-window.styles.scss"; //https://bootsnipp.com/snippets/exR5v
import twilio from "twilio";
// const client = require("twilio")(
// "ACf1b1aaf0e04740828b49b6e58467d787",
// "0bea5e29a6d77593183ab1caa01d23de"
// );
const client = twilio(
"ACf1b1aaf0e04740828b49b6e58467d787",
"0bea5e29a6d77593183ab1caa01d23de"
);
export default function ChatWindowComponent({ toggleChatVisible }) { export default function ChatWindowComponent({ toggleChatVisible }) {
const [conversations, setConversations] = useState([]);
useEffect(() => {
client.messages.list({ limit: 20 }, (error, items) => {
setConversations(
items.reduce((acc, value) => {
acc.push({
sid: value.sid,
direction: value.direction,
body: value.body
});
return acc;
}, [])
);
});
return () => {};
}, [setConversations]);
console.log(conversations);
return ( return (
<div style={{ height: "300px" }}> <Card style={{ width: "400px" }}>
<button onClick={() => toggleChatVisible()}>hide</button> This is a chat <div>
window! <button onClick={() => toggleChatVisible()}>X</button>
</div> <div className='messages' style={{ height: "400px" }}>
<ul>
{conversations.map(item => (
<li
key={item.sid}
className={`${
item.direction === "inbound" ? "replies" : "sent"
}`}>
<p> {item.body}</p>
</li>
))}
</ul>
</div>
</div>
</Card>
); );
} }

View File

@@ -1,3 +1,4 @@
import { Affix, Button, Badge } from "antd";
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
@@ -17,8 +18,20 @@ export default connect(
mapStateToProps, mapStateToProps,
mapDispatchToProps mapDispatchToProps
)(function ChatWindowContainer({ chatVisible, toggleChatVisible }) { )(function ChatWindowContainer({ chatVisible, toggleChatVisible }) {
if (chatVisible) return (
return <ChatWindowComponent toggleChatVisible={toggleChatVisible} />; <Affix offsetBottom={25}>
{chatVisible ? (
return <div onClick={() => toggleChatVisible()}>Chat</div>; <ChatWindowComponent toggleChatVisible={toggleChatVisible} />
) : (
<Badge count={5}>
<Button
type='primary'
shape='circle'
icon='message'
onClick={() => toggleChatVisible()}
/>
</Badge>
)}
</Affix>
);
}); });

View File

@@ -0,0 +1,127 @@
.messages {
height: auto;
min-height: calc(100% - 93px);
max-height: calc(100% - 93px);
overflow-y: scroll;
overflow-x: hidden;
}
@media screen and (max-width: 735px) {
.messages {
max-height: calc(100% - 105px);
}
}
.messages::-webkit-scrollbar {
width: 8px;
background: transparent;
}
.messages::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.3);
}
.messages ul li {
display: inline-block;
clear: both;
float: left;
margin: 5px 15px 5px 15px;
width: calc(100% - 25px);
font-size: 0.9em;
}
.messages ul li:nth-last-child(1) {
margin-bottom: 20px;
}
.messages ul li.sent img {
margin: 6px 8px 0 0;
}
.messages ul li.sent p {
background: #435f7a;
color: #f5f5f5;
}
.messages ul li.replies img {
float: right;
margin: 6px 0 0 8px;
}
.messages ul li.replies p {
background: #f5f5f5;
float: right;
}
.messages ul li img {
width: 22px;
border-radius: 50%;
float: left;
}
.messages ul li p {
display: inline-block;
padding: 10px 15px;
border-radius: 20px;
max-width: 205px;
line-height: 130%;
}
@media screen and (min-width: 735px) {
.messages ul li p {
max-width: 300px;
}
}
.message-input {
position: absolute;
bottom: 0;
width: 100%;
z-index: 99;
}
.message-input .wrap {
position: relative;
}
.message-input .wrap input {
font-family: "proxima-nova", "Source Sans Pro", sans-serif;
float: left;
border: none;
width: calc(100% - 90px);
padding: 11px 32px 10px 8px;
font-size: 0.8em;
color: #32465a;
}
@media screen and (max-width: 735px) {
.message-input .wrap input {
padding: 15px 32px 16px 8px;
}
}
.message-input .wrap input:focus {
outline: none;
}
.message-input .wrap .attachment {
position: absolute;
right: 60px;
z-index: 4;
margin-top: 10px;
font-size: 1.1em;
color: #435f7a;
opacity: 0.5;
cursor: pointer;
}
@media screen and (max-width: 735px) {
.message-input .wrap .attachment {
margin-top: 17px;
right: 65px;
}
}
.message-input .wrap .attachment:hover {
opacity: 1;
}
.message-input .wrap button {
float: right;
border: none;
width: 50px;
padding: 12px 0;
cursor: pointer;
background: #32465a;
color: #f5f5f5;
}
@media screen and (max-width: 735px) {
.message-input .wrap button {
padding: 16px 0;
}
}
.message-input .wrap button:hover {
background: #435f7a;
}
.message-input .wrap button:focus {
outline: none;
}

View File

@@ -16,102 +16,99 @@ export default ({
const { t } = useTranslation(); const { t } = useTranslation();
//TODO Add //TODO Add
return ( return (
<Row type="flex" justify="space-around" align="middle"> <Row type='flex' justify='space-around' align='middle'>
{logo ? ( {logo ? (
<Col span={4}> <Col span={4}>
<img alt="Shop Logo" src={logo} style={{ height: "40px" }} /> <img alt='Shop Logo' src={logo} style={{ height: "40px" }} />
</Col> </Col>
) : null} ) : null}
<Col span={14}> <Col span={14}>
<Menu <Menu
theme="dark" theme='dark'
className="header" className='header'
selectedKeys={selectedNavItem} selectedKeys={selectedNavItem}
mode="horizontal" mode='horizontal'
onClick={handleMenuClick} onClick={handleMenuClick}>
> <Menu.Item key='home'>
<Menu.Item key="home"> <Link to='/manage'>
<Link to="/manage"> <Icon type='home' />
<Icon type="home" />
{t("menus.header.home")} {t("menus.header.home")}
</Link> </Link>
</Menu.Item> </Menu.Item>
<Menu.SubMenu title={t("menus.header.jobs")}> <Menu.SubMenu title={t("menus.header.jobs")}>
<Menu.Item key="schedule"> <Menu.Item key='schedule'>
<Link to="/manage/schedule"> <Link to='/manage/schedule'>
<Icon type="calendar" /> <Icon type='calendar' />
{t("menus.header.schedule")} {t("menus.header.schedule")}
</Link> </Link>
</Menu.Item> </Menu.Item>
<Menu.Item key="activejobs"> <Menu.Item key='activejobs'>
<Link to="/manage/jobs"> <Link to='/manage/jobs'>{t("menus.header.activejobs")}</Link>
<Icon type="home" />
{t("menus.header.activejobs")}
</Link>
</Menu.Item> </Menu.Item>
<Menu.Item key="availablejobs"> <Menu.Item key='availablejobs'>
<Link to="/manage/available"> <Link to='/manage/available'>
<Icon type="home" />
{t("menus.header.availablejobs")} {t("menus.header.availablejobs")}
</Link> </Link>
</Menu.Item> </Menu.Item>
</Menu.SubMenu> </Menu.SubMenu>
<Menu.SubMenu title={t("menus.header.customers")}> <Menu.SubMenu title={t("menus.header.customers")}>
<Menu.Item key="owners"> <Menu.Item key='owners'>
<Link to="/manage/owners"> <Link to='/manage/owners'>
<Icon type="team" /> <Icon type='team' />
{t("menus.header.owners")} {t("menus.header.owners")}
</Link> </Link>
</Menu.Item> </Menu.Item>
<Menu.Item key="vehicles"> <Menu.Item key='vehicles'>
<Link to="/manage/vehicles"> <Link to='/manage/vehicles'>
<Icon type="car" /> <Icon type='car' />
{t("menus.header.vehicles")} {t("menus.header.vehicles")}
</Link> </Link>
</Menu.Item> </Menu.Item>
</Menu.SubMenu> </Menu.SubMenu>
<Menu.SubMenu title={t("menus.header.shop")}>
<Menu.Item key="shop"> <Menu.Item key='shop'>
<Link to="/manage/shop"> <Link to='/manage/shop'>{t("menus.header.shop_config")}</Link>
{t("menus.header.shop")} </Menu.Item>
</Link> <Menu.Item key='shop-vendors'>
</Menu.Item> <Link to='/manage/shop/vendors'>
{t("menus.header.shop_vendors")}
</Link>
</Menu.Item>
</Menu.SubMenu>
<Menu.SubMenu <Menu.SubMenu
title={ title={
<div> <div>
<Avatar <Avatar
size="medium" size='medium'
alt="Avatar" alt='Avatar'
src={currentUser.photoURL ? currentUser.photoURL : UserImage} src={currentUser.photoURL ? currentUser.photoURL : UserImage}
style={{ margin: "10px" }} style={{ margin: "10px" }}
/> />
{currentUser.displayName || t("general.labels.unknown")} {currentUser.displayName || t("general.labels.unknown")}
</div> </div>
} }>
>
<Menu.Item onClick={signOutStart()}> <Menu.Item onClick={signOutStart()}>
{t("user.actions.signout")} {t("user.actions.signout")}
</Menu.Item> </Menu.Item>
<Menu.Item> <Menu.Item>
<Link to="/manage/profile">{t("menus.currentuser.profile")}</Link> <Link to='/manage/profile'>{t("menus.currentuser.profile")}</Link>
</Menu.Item> </Menu.Item>
<Menu.SubMenu <Menu.SubMenu
title={ title={
<span> <span>
<Icon type="global" /> <Icon type='global' />
<span>{t("menus.currentuser.languageselector")}</span> <span>{t("menus.currentuser.languageselector")}</span>
</span> </span>
} }>
> <Menu.Item actiontype='lang-select' key='en_us'>
<Menu.Item actiontype="lang-select" key="en_us">
{t("general.languages.english")} {t("general.languages.english")}
</Menu.Item> </Menu.Item>
<Menu.Item actiontype="lang-select" key="fr"> <Menu.Item actiontype='lang-select' key='fr'>
{t("general.languages.french")} {t("general.languages.french")}
</Menu.Item> </Menu.Item>
<Menu.Item actiontype="lang-select" key="es"> <Menu.Item actiontype='lang-select' key='es'>
{t("general.languages.spanish")} {t("general.languages.spanish")}
</Menu.Item> </Menu.Item>
</Menu.SubMenu> </Menu.SubMenu>

View File

@@ -17,6 +17,7 @@ import JobDetailCardsNotesComponent from "./job-detail-cards.notes.component";
import JobDetailCardsPartsComponent from "./job-detail-cards.parts.component"; import JobDetailCardsPartsComponent from "./job-detail-cards.parts.component";
import "./job-detail-cards.styles.scss"; import "./job-detail-cards.styles.scss";
import JobDetailCardsTotalsComponent from "./job-detail-cards.totals.component"; import JobDetailCardsTotalsComponent from "./job-detail-cards.totals.component";
import ScheduleJobModalContainer from "../schedule-job-modal/schedule-job-modal.container";
export default function JobDetailCards({ selectedJob }) { export default function JobDetailCards({ selectedJob }) {
const { loading, error, data, refetch } = useQuery(QUERY_JOB_CARD_DETAILS, { const { loading, error, data, refetch } = useQuery(QUERY_JOB_CARD_DETAILS, {
@@ -25,29 +26,35 @@ export default function JobDetailCards({ selectedJob }) {
skip: !selectedJob skip: !selectedJob
}); });
const [noteModalVisible, setNoteModalVisible] = useState(false); const [noteModalVisible, setNoteModalVisible] = useState(false);
const scheduleModalState = useState(false);
const { t } = useTranslation(); const { t } = useTranslation();
if (!selectedJob) { if (!selectedJob) {
return <div>{t("jobs.errors.nojobselected")}</div>; return <div>{t("jobs.errors.nojobselected")}</div>;
} }
if (loading) return <LoadingSpinner />; if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />; if (error) return <AlertComponent message={error.message} type='error' />;
return ( return (
<div className="job-cards-container"> <div className='job-cards-container'>
<NoteUpsertModal <NoteUpsertModal
jobId={data.jobs_by_pk.id} jobId={data.jobs_by_pk.id}
visible={noteModalVisible} visible={noteModalVisible}
changeVisibility={setNoteModalVisible} changeVisibility={setNoteModalVisible}
refetch={refetch} refetch={refetch}
/> />
<ScheduleJobModalContainer
scheduleModalState={scheduleModalState}
jobId={data.jobs_by_pk.id}
refetch={refetch}
/>
<PageHeader <PageHeader
ghost={false} ghost={false}
onBack={() => window.history.back()} onBack={() => window.history.back()}
tags={ tags={
<span key="job-status"> <span key='job-status'>
{data.jobs_by_pk.status ? ( {data.jobs_by_pk.status ? (
<Tag color="blue">{data.jobs_by_pk.status}</Tag> <Tag color='blue'>{data.jobs_by_pk.status}</Tag>
) : null} ) : null}
</span> </span>
} }
@@ -65,35 +72,40 @@ export default function JobDetailCards({ selectedJob }) {
) )
} }
extra={[ extra={[
<Button
key='schedule'
//TODO Enabled logic based on status.
onClick={() => {
scheduleModalState[1](true);
}}>
{t("jobs.actions.schedule")}
</Button>,
<Link <Link
key="documents" key='documents'
to={`/manage/jobs/${data.jobs_by_pk.id}#documents`} to={`/manage/jobs/${data.jobs_by_pk.id}#documents`}>
>
<Button> <Button>
<Icon type="file-image" /> <Icon type='file-image' />
{t("jobs.actions.addDocuments")} {t("jobs.actions.addDocuments")}
</Button> </Button>
</Link>, </Link>,
<Button key="printing"> <Button key='printing'>
<Icon type="printer" /> <Icon type='printer' />
{t("jobs.actions.printCenter")} {t("jobs.actions.printCenter")}
</Button>, </Button>,
<Button <Button
key="notes" key='notes'
actiontype="addNote" actiontype='addNote'
onClick={() => { onClick={() => {
setNoteModalVisible(!noteModalVisible); setNoteModalVisible(!noteModalVisible);
}} }}>
> <Icon type='edit' />
<Icon type="edit" />
{t("jobs.actions.addNote")} {t("jobs.actions.addNote")}
</Button>, </Button>,
<Button key="postinvoices"> <Button key='postinvoices'>
<Icon type="shopping-cart" /> <Icon type='shopping-cart' />
{t("jobs.actions.postInvoices")} {t("jobs.actions.postInvoices")}
</Button> </Button>
]} ]}>
>
{ {
// loading ? ( // loading ? (
// <LoadingSkeleton /> // <LoadingSkeleton />
@@ -114,7 +126,7 @@ export default function JobDetailCards({ selectedJob }) {
// ) // )
} }
<section className="job-cards"> <section className='job-cards'>
<JobDetailCardsCustomerComponent <JobDetailCardsCustomerComponent
loading={loading} loading={loading}
data={data ? data.jobs_by_pk : null} data={data ? data.jobs_by_pk : null}

View File

@@ -14,7 +14,10 @@ export default function JobDetailCardsCustomerComponent({ loading, data }) {
extraLink={data && data.owner ? `/manage/owners/${data.owner.id}` : null}> extraLink={data && data.owner ? `/manage/owners/${data.owner.id}` : null}>
{data ? ( {data ? (
<span> <span>
<div>{`${data.ownr_fn || ""} ${data.ownr_ln || ""}`}</div> <div>
<Link to={`/manage/owners/${data.owner.id}`}>{`${data.ownr_fn ||
""} ${data.ownr_ln || ""}`}</Link>
</div>
<div> <div>
{t("jobs.fields.phoneshort")}: {t("jobs.fields.phoneshort")}:
<PhoneFormatter>{`${data.ownr_ph1 || <PhoneFormatter>{`${data.ownr_ph1 ||

View File

@@ -18,13 +18,12 @@ export default function JobDetailCardsDocumentsComponent({ loading, data }) {
<CardTemplate <CardTemplate
loading={loading} loading={loading}
title={t("jobs.labels.cards.documents")} title={t("jobs.labels.cards.documents")}
extraLink={`/manage/jobs/${data.id}#documents`}> extraLink={`/manage/jobs/${data.id}#documents`}
{data.documents.count > 0 ? ( >
{data.documents.length > 0 ? (
<Carousel autoplay> <Carousel autoplay>
{data.documents.map(item => ( {data.documents.map(item => (
<div key={item.id}> <img key={item.id} src={item.thumb_url} alt={item.name} />
<img src={item.thumb_url} alt={item.name} />
</div>
))} ))}
</Carousel> </Carousel>
) : ( ) : (

View File

@@ -1,11 +1,12 @@
.ant-carousel .slick-slide { .ant-carousel .slick-slide {
text-align: center; text-align: center;
height: 160px; height: 50px;
line-height: 160px; width: 50px;
background: #364d79; line-height: 50px;
overflow: hidden; background: #364d79;
} overflow: hidden;
}
.ant-carousel .slick-slide h3 { .ant-carousel .slick-slide h3 {
color: #fff; color: #ccddaa;
} }

View File

@@ -1,52 +0,0 @@
import React from "react";
import { Form, Input, InputNumber } from "antd";
import JobDetailFormContext from "../../pages/jobs-detail/jobs-detail.page.context";
export default class EditableCell extends React.Component {
getInput = () => {
if (this.props.inputType === "number") {
return <InputNumber />;
}
return <Input />;
};
renderCell = ({ getFieldDecorator }) => {
const {
editing,
dataIndex,
title,
inputType,
record,
index,
children,
...restProps
} = this.props;
return (
<td {...restProps}>
{editing ? (
<Form.Item style={{ margin: 0 }}>
{getFieldDecorator(dataIndex, {
rules: [
{
required: true,
message: `Please Input ${title}!`
}
],
initialValue: record[dataIndex]
})(this.getInput())}
</Form.Item>
) : (
children
)}
</td>
);
};
render() {
return (
<JobDetailFormContext.Consumer>
{this.renderCell}
</JobDetailFormContext.Consumer>
);
}
}

View File

@@ -1,23 +1,27 @@
import { Input, Table } from "antd"; import { Button, Input, Table } 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 { alphaSort } from "../../utils/sorters"; import { alphaSort } from "../../utils/sorters";
//import EditableCell from "./job-lines-cell.component"; //import EditableCell from "./job-lines-cell.component";
import AllocationsAssignmentContainer from "../allocations-assignment/allocations-assignment.container"; import AllocationsAssignmentContainer from "../allocations-assignment/allocations-assignment.container";
import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.container";
export default function JobLinesComponent({ export default function JobLinesComponent({
loading,
refetch,
jobLines, jobLines,
form, setSearchText,
handleSubmit, selectedLines,
setSearchText setSelectedLines,
partsOrderModalVisible,
jobId
}) { }) {
const [state, setState] = useState({ const [state, setState] = useState({
sortedInfo: {} sortedInfo: {}
}); });
const { t } = useTranslation(); const { t } = useTranslation();
const setPartsModalVisible = partsOrderModalVisible[1];
const columns = [ const columns = [
{ {
title: t("joblines.fields.unq_seq"), title: t("joblines.fields.unq_seq"),
@@ -119,17 +123,29 @@ export default function JobLinesComponent({
{record.allocations && record.allocations.length > 0 {record.allocations && record.allocations.length > 0
? record.allocations.map(item => ( ? record.allocations.map(item => (
<div <div
key={item.id} key={
>{`${item.employee.first_name} ${item.employee.last_name} (${item.hours})`}</div> item.id
}>{`${item.employee.first_name} ${item.employee.last_name} (${item.hours})`}</div>
)) ))
: null} : null}
<AllocationsAssignmentContainer <AllocationsAssignmentContainer
key={record.id} key={record.id}
refetch={refetch}
jobLineId={record.id} jobLineId={record.id}
hours={record.mod_lb_hrs} hours={record.mod_lb_hrs}
/> />
</span> </span>
) )
},
{
title: t("general.labels.actions"),
dataIndex: "actions",
key: "actions",
render: (text, record) => (
<span>
<Button>{t("general.actions.edit")}</Button>
</span>
)
} }
]; ];
@@ -137,10 +153,6 @@ export default function JobLinesComponent({
setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
}; };
// const handleChange = event => {
// const { value } = event.target;
// setState({ ...state, filterinfo: { text: [value] } });
// };
const formItemLayout = { const formItemLayout = {
labelCol: { labelCol: {
xs: { span: 12 }, xs: { span: 12 },
@@ -153,25 +165,46 @@ export default function JobLinesComponent({
}; };
return ( return (
<Table <div>
title={() => { <PartsOrderModalContainer
return ( partsOrderModalVisible={partsOrderModalVisible}
<Input.Search linesToOrder={selectedLines}
placeholder="Search..." jobId={jobId}
onChange={e => { />
e.preventDefault();
setSearchText(e.target.value); <Table
}} title={() => {
/> return (
); <div>
}} <Input.Search
{...formItemLayout} placeholder={t("general.labels.search")}
size="small" onChange={e => {
pagination={{ position: "bottom", defaultPageSize: 50 }} e.preventDefault();
columns={columns.map(item => ({ ...item }))} setSearchText(e.target.value);
rowKey="id" }}
dataSource={jobLines} />
onChange={handleTableChange} <Button
/> disabled={selectedLines.length > 0 ? false : true}
onClick={() => setPartsModalVisible(true)}>
{t("parts.actions.order")}
</Button>
</div>
);
}}
{...formItemLayout}
loading={loading}
size='small'
pagination={{ position: "bottom", defaultPageSize: 50 }}
rowSelection={{
// selectedRowKeys: selectedLines,
onSelect: (record, selected, selectedRows, nativeEvent) =>
setSelectedLines(selectedRows)
}}
columns={columns.map(item => ({ ...item }))}
rowKey='id'
dataSource={jobLines}
onChange={handleTableChange}
/>
</div>
); );
} }

View File

@@ -1,52 +1,27 @@
import { useQuery } from "@apollo/react-hooks"; import { useQuery } from "@apollo/react-hooks";
import { notification } from "antd";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { GET_JOB_LINES_BY_PK } from "../../graphql/jobs-lines.queries"; import { GET_JOB_LINES_BY_PK } from "../../graphql/jobs-lines.queries";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import JobLinesComponent from "./job-lines.component"; import JobLinesComponent from "./job-lines.component";
//export default Form.create({ name: "JobsDetailJobLines" })( //export default Form.create({ name: "JobsDetailJobLines" })(
export default function JobLinesContainer({ jobId, form }) { export default function JobLinesContainer({ jobId }) {
const { loading, error, data } = useQuery(GET_JOB_LINES_BY_PK, { const { loading, error, data, refetch } = useQuery(GET_JOB_LINES_BY_PK, {
variables: { id: jobId }, variables: { id: jobId },
fetchPolicy: "network-only" fetchPolicy: "network-only"
}); });
const [searchText, setSearchText] = useState(""); const [searchText, setSearchText] = useState("");
const { t } = useTranslation(); const [selectedLines, setSelectedLines] = useState([]);
const partsOrderModalVisible = useState(false);
const handleSubmit = e => { if (error) return <AlertComponent message={error.message} type='error' />;
e.preventDefault();
form.validateFieldsAndScroll((err, values) => {
if (err) {
notification["error"]({
message: t("jobs.errors.validationtitle"),
description: t("jobs.errors.validation")
});
}
if (!err) {
console.log("Save the est lines!", values);
// mutationUpdateJob({
// variables: { jobId: data.jobs_by_pk.id, job: values }
// }).then(r => {
// notification["success"]({
// message: t("jobs.successes.savetitle")
// });
// //TODO: Better way to reset the field decorators?
// refetch().then(r => form.resetFields());
// });
}
});
};
if (error) return <AlertComponent message={error.message} type="error" />;
return ( return (
<JobLinesComponent <JobLinesComponent
loading={loading} loading={loading}
refetch={refetch}
jobLines={ jobLines={
data && data.joblines data && data.joblines
? searchText ? searchText
@@ -78,9 +53,11 @@ export default function JobLinesContainer({ jobId, form }) {
: data.joblines : data.joblines
: null : null
} }
handleSubmit={handleSubmit}
form={form}
setSearchText={setSearchText} setSearchText={setSearchText}
selectedLines={selectedLines}
setSelectedLines={setSelectedLines}
partsOrderModalVisible={partsOrderModalVisible}
jobId={jobId}
/> />
); );
} }

View File

@@ -20,7 +20,7 @@ export default function JobsDetailClaims({ job }) {
initialValue: job.loss_desc initialValue: job.loss_desc
})(<Input name='loss_desc' />)} })(<Input name='loss_desc' />)}
</Form.Item> </Form.Item>
TODO: How to handle different taxes and marking them as exempt? TODO How to handle different taxes and marking them as exempt?
{ {
// <Form.Item label={t("jobs.fields.exempt")}> // <Form.Item label={t("jobs.fields.exempt")}>
// {getFieldDecorator("exempt", { // {getFieldDecorator("exempt", {

View File

@@ -25,13 +25,13 @@ export default function JobsDetailFinancials({ job }) {
initialValue: job.depreciation_taxes initialValue: job.depreciation_taxes
})(<InputNumber name="depreciation_taxes" />)} })(<InputNumber name="depreciation_taxes" />)}
</Form.Item> </Form.Item>
TODO: This is equivalent of GST payable. TODO This is equivalent of GST payable.
<Form.Item label={t("jobs.fields.federal_tax_payable")}> <Form.Item label={t("jobs.fields.federal_tax_payable")}>
{getFieldDecorator("federal_tax_payable", { {getFieldDecorator("federal_tax_payable", {
initialValue: job.federal_tax_payable initialValue: job.federal_tax_payable
})(<InputNumber name="federal_tax_payable" />)} })(<InputNumber name="federal_tax_payable" />)}
</Form.Item> </Form.Item>
TODO: equivalent of other customer amount TODO equivalent of other customer amount
<Form.Item label={t("jobs.fields.other_amount_payable")}> <Form.Item label={t("jobs.fields.other_amount_payable")}>
{getFieldDecorator("other_amount_payable", { {getFieldDecorator("other_amount_payable", {
initialValue: job.other_amount_payable initialValue: job.other_amount_payable
@@ -99,7 +99,7 @@ export default function JobsDetailFinancials({ job }) {
initialValue: job.rate_lag initialValue: job.rate_lag
})(<InputNumber name="rate_lag" />)} })(<InputNumber name="rate_lag" />)}
</Form.Item> </Form.Item>
Note //TODO: Remove ATP rate? Note //TODO Remove ATP rate?
<Form.Item label={t("jobs.fields.rate_atp")}> <Form.Item label={t("jobs.fields.rate_atp")}>
{getFieldDecorator("rate_atp", { {getFieldDecorator("rate_atp", {
initialValue: job.rate_atp initialValue: job.rate_atp

View File

@@ -43,7 +43,7 @@ export default connect(
const tombstoneTitle = ( const tombstoneTitle = (
<div> <div>
<Avatar size="large" alt="Vehicle Image" src={CarImage} /> <Avatar size='large' alt='Vehicle Image' src={CarImage} />
{`${t("jobs.fields.ro_number")} ${ {`${t("jobs.fields.ro_number")} ${
job.ro_number ? job.ro_number : t("general.labels.na") job.ro_number ? job.ro_number : t("general.labels.na")
}`} }`}
@@ -54,8 +54,7 @@ export default connect(
<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>
))} ))}
@@ -63,24 +62,23 @@ export default connect(
); );
const menuExtra = [ const menuExtra = [
<Dropdown overlay={statusmenu} key="changestatus"> <Dropdown overlay={statusmenu} key='changestatus'>
<Button> <Button>
{t("jobs.actions.changestatus")} <Icon type="down" /> {t("jobs.actions.changestatus")} <Icon type='down' />
</Button> </Button>
</Dropdown>, </Dropdown>,
<Badge key="schedule" count={job.appointments_aggregate.aggregate.count}> <Badge key='schedule' count={job.appointments_aggregate.aggregate.count}>
<Button <Button
//TODO: Enabled logic based on status. //TODO Enabled logic based on status.
onClick={() => { onClick={() => {
setscheduleModalVisible(true); setscheduleModalVisible(true);
}} }}>
>
{t("jobs.actions.schedule")} {t("jobs.actions.schedule")}
</Button> </Button>
</Badge>, </Badge>,
<Button <Button
key="convert" key='convert'
type="dashed" type='dashed'
disabled={job.converted} disabled={job.converted}
onClick={() => { onClick={() => {
mutationConvertJob({ mutationConvertJob({
@@ -92,16 +90,14 @@ export default connect(
message: t("jobs.successes.converted") message: t("jobs.successes.converted")
}); });
}); });
}} }}>
>
{t("jobs.actions.convert")} {t("jobs.actions.convert")}
</Button>, </Button>,
<Button <Button
type="primary" type='primary'
key="submit" key='submit'
htmlType="button" htmlType='button'
onClick={handleSubmit} onClick={handleSubmit}>
>
{t("general.labels.save")} {t("general.labels.save")}
</Button> </Button>
]; ];
@@ -114,9 +110,9 @@ export default connect(
title={tombstoneTitle} title={tombstoneTitle}
//subTitle={tombstoneSubtitle} //subTitle={tombstoneSubtitle}
tags={ tags={
<span key="job-status"> <span key='job-status'>
{job.status ? <Tag color="blue">{job.status}</Tag> : null} {job.status ? <Tag color='blue'>{job.status}</Tag> : null}
<Tag color="red"> <Tag color='red'>
{job.owner ? ( {job.owner ? (
<Link to={`/manage/owners/${job.owner.id}`}> <Link to={`/manage/owners/${job.owner.id}`}>
{`${job.ownr_co_nm || ""}${job.ownr_fn || ""} ${job.ownr_ln || {`${job.ownr_co_nm || ""}${job.ownr_fn || ""} ${job.ownr_ln ||
@@ -126,7 +122,7 @@ export default connect(
t("jobs.errors.noowner") t("jobs.errors.noowner")
)} )}
</Tag> </Tag>
<Tag color="green"> <Tag color='green'>
{job.vehicle ? ( {job.vehicle ? (
<Link to={`/manage/vehicles/${job.vehicle.id}`}> <Link to={`/manage/vehicles/${job.vehicle.id}`}>
{job.vehicle.v_model_yr || t("general.labels.na")}{" "} {job.vehicle.v_model_yr || t("general.labels.na")}{" "}
@@ -140,11 +136,10 @@ export default connect(
<BarcodePopup value={job.id} /> <BarcodePopup value={job.id} />
</span> </span>
} }
extra={menuExtra} extra={menuExtra}>
> <Descriptions size='small' column={5}>
<Descriptions size="small" column={5}>
<Descriptions.Item label={t("jobs.fields.repairtotal")}> <Descriptions.Item label={t("jobs.fields.repairtotal")}>
<CurrencyFormatter>{job.claim_total}</CurrencyFormatter> <CurrencyFormatter>{job.clm_total}</CurrencyFormatter>
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label={t("jobs.fields.customerowing")}> <Descriptions.Item label={t("jobs.fields.customerowing")}>
@@ -157,7 +152,7 @@ export default connect(
<Descriptions.Item label={t("jobs.fields.scheduled_completion")}> <Descriptions.Item label={t("jobs.fields.scheduled_completion")}>
{job.scheduled_completion ? ( {job.scheduled_completion ? (
<Moment format="MM/DD/YYYY">{job.scheduled_completion}</Moment> <Moment format='MM/DD/YYYY'>{job.scheduled_completion}</Moment>
) : null} ) : null}
</Descriptions.Item> </Descriptions.Item>

View File

@@ -2,12 +2,22 @@ import { Modal } from "antd";
import React from "react"; import React from "react";
import { useQuery } from "react-apollo"; import { useQuery } from "react-apollo";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { QUERY_ALL_OPEN_JOBS } from "../../graphql/jobs.queries"; import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries";
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 JobsFindModalComponent from "./jobs-find-modal.component"; import JobsFindModalComponent from "./jobs-find-modal.component";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
export default function JobsFindModalContainer({ export default connect(
mapStateToProps,
null
)(function JobsFindModalContainer({
bodyshop,
loading, loading,
error, error,
selectedJob, selectedJob,
@@ -17,8 +27,11 @@ export default function JobsFindModalContainer({
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const jobsList = useQuery(QUERY_ALL_OPEN_JOBS, { const jobsList = useQuery(QUERY_ALL_ACTIVE_JOBS, {
fetchPolicy: "network-only" fetchPolicy: "network-only",
variables: {
statuses: bodyshop.md_ro_statuses.open_statuses || ["Open"]
}
}); });
return ( return (
@@ -26,10 +39,9 @@ export default function JobsFindModalContainer({
title={t("jobs.labels.existing_jobs")} title={t("jobs.labels.existing_jobs")}
width={"80%"} width={"80%"}
okButtonProps={{ disabled: selectedJob ? false : true }} okButtonProps={{ disabled: selectedJob ? false : true }}
{...modalProps} {...modalProps}>
>
{loading ? <LoadingSpinner /> : null} {loading ? <LoadingSpinner /> : null}
{error ? <AlertComponent message={error.message} type="error" /> : null} {error ? <AlertComponent message={error.message} type='error' /> : null}
{true ? ( {true ? (
<JobsFindModalComponent <JobsFindModalComponent
selectedJob={selectedJob} selectedJob={selectedJob}
@@ -43,4 +55,4 @@ export default function JobsFindModalContainer({
) : null} ) : null}
</Modal> </Modal>
); );
} });

View File

@@ -1,4 +1,4 @@
import { Input, Table, Icon } from "antd"; import { Input, Table, Icon, Button } from "antd";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
@@ -8,6 +8,8 @@ import { withRouter } from "react-router-dom";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
export default withRouter(function JobsList({ export default withRouter(function JobsList({
searchTextState,
refetch,
loading, loading,
jobs, jobs,
selectedJob, selectedJob,
@@ -21,6 +23,7 @@ export default withRouter(function JobsList({
const { t } = useTranslation(); const { t } = useTranslation();
const setSearchText = searchTextState[1];
const columns = [ const columns = [
{ {
title: t("jobs.fields.ro_number"), title: t("jobs.fields.ro_number"),
@@ -29,7 +32,11 @@ export default withRouter(function JobsList({
width: "8%", width: "8%",
// onFilter: (value, record) => record.ro_number.includes(value), // onFilter: (value, record) => record.ro_number.includes(value),
// filteredValue: state.filteredInfo.text || null, // filteredValue: state.filteredInfo.text || null,
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number), sorter: (a, b) =>
alphaSort(
a.ro_number ? a.ro_number : "EST-" + a.est_number,
b.ro_number ? b.ro_number : "EST-" + b.est_number
),
sortOrder: sortOrder:
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
@@ -73,7 +80,7 @@ export default withRouter(function JobsList({
<PhoneFormatter>{record.ownr_ph1}</PhoneFormatter> <PhoneFormatter>{record.ownr_ph1}</PhoneFormatter>
<Icon <Icon
style={{ margin: 4 }} style={{ margin: 4 }}
type="message" type='message'
onClick={() => { onClick={() => {
alert("SMSing will happen here."); alert("SMSing will happen here.");
}} }}
@@ -205,19 +212,24 @@ export default withRouter(function JobsList({
loading={loading} loading={loading}
title={() => { title={() => {
return ( return (
<Input.Search <div style={{ display: "flex" }}>
placeholder="Search..." <Button onClick={() => refetch()}>
onSearch={value => { <Icon type='sync' />
console.log(value); </Button>
}} <Input.Search
enterButton placeholder='Search...'
/> onChange={e => {
setSearchText(e.target.value);
}}
enterButton
/>
</div>
); );
}} }}
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={jobs} dataSource={jobs}
rowSelection={{ selectedRowKeys: [selectedJob] }} rowSelection={{ selectedRowKeys: [selectedJob] }}
onChange={handleTableChange} onChange={handleTableChange}

View File

@@ -33,7 +33,7 @@ export default function NoteUpsertModalContainer({
insertNote({ insertNote({
variables: { variables: {
noteInput: [ noteInput: [
{ ...noteState, jobid: jobId, created_by: "patrick@bodyshop.app" } //TODO: Fix the created by. { ...noteState, jobid: jobId, created_by: "patrick@bodyshop.app" } //TODO Fix the created by.
] ]
} }
}).then(r => { }).then(r => {

View File

@@ -27,7 +27,7 @@ function OwnerDetailFormContainer({ form, owner, refetch }) {
notification["success"]({ notification["success"]({
message: t("owners.successes.save") message: t("owners.successes.save")
}); });
//TODO: Better way to reset the field decorators? //TODO Better way to reset the field decorators?
if (refetch) refetch().then(); if (refetch) refetch().then();
form.resetFields(); form.resetFields();
}); });

View File

@@ -20,12 +20,7 @@ export default function OwnerFindModalContainer({
const ownersList = useQuery(QUERY_SEARCH_OWNER_BY_IDX, { const ownersList = useQuery(QUERY_SEARCH_OWNER_BY_IDX, {
variables: { variables: {
search: owner search: owner ? `${owner.ownr_fn || ""} ${owner.ownr_ln || ""}` : null
? `${owner.ownr_fn || ""} ${owner.ownr_ln || ""} ${owner.ownr_addr1 ||
""} ${owner.ownr_city || ""} ${owner.ownr_zip ||
""} ${owner.ownr_ea || ""} ${owner.ownr_ph1 ||
""} ${owner.ownr_ph2 || ""}`
: null
}, },
skip: !owner, skip: !owner,
fetchPolicy: "network-only" fetchPolicy: "network-only"
@@ -35,18 +30,17 @@ export default function OwnerFindModalContainer({
<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_owners ownersList.data && ownersList.data.search_owner
? ownersList.data.search_owners ? ownersList.data.search_owner
: null : null
} }
/> />

View File

@@ -0,0 +1,63 @@
import React, { useState } from "react";
import { AutoComplete, Icon, DatePicker, Radio } from "antd";
import { useTranslation } from "react-i18next";
export default function PartsOrderModalComponent({
vendorList,
state,
sendTypeState
}) {
const [partsOrder, setPartsOrder] = state;
const [sendType, setSendType] = sendTypeState;
const [vendorComplete, setVendorComplete] = useState(vendorList);
const { t } = useTranslation();
const handleSearch = value => {
if (value === "") setVendorComplete(vendorList);
else
setVendorComplete(
vendorList.filter(v =>
v.name.toLowerCase().includes(value.toLowerCase())
)
);
};
const handleSelect = (value, option) => {
console.log("value", value);
console.log("option", option);
setPartsOrder({ ...partsOrder, vendorid: option.key });
};
return (
<div>
<AutoComplete
onSearch={handleSearch}
onSelect={handleSelect}
defaultOpen
backfill
optionLabelProp='value'
dataSource={vendorComplete}
placeholder={t("vendors.labels.search")}>
{vendorComplete.map(v => (
<AutoComplete.Option value={v.name} key={v.id}>
<div>{v.name}</div>
<div> {v.favorite ? <Icon type='heart' /> : null}</div>
</AutoComplete.Option>
))}
</AutoComplete>
{t("parts_orders.fields.deliver_by")}
<DatePicker
defaultValue={partsOrder.deliver_by}
onChange={e => {
setPartsOrder({ ...partsOrder, deliver_by: e });
}}
/>
<Radio.Group
defaultValue={sendType}
onChange={e => setSendType(e.target.value)}>
<Radio value={"e"}>{t("parts_orders.labels.email")}</Radio>
<Radio value={"p"}>{t("parts_orders.labels.print")}</Radio>
</Radio.Group>
</div>
);
}

View File

@@ -0,0 +1,103 @@
import { Modal, notification } from "antd";
import React, { useState } from "react";
import { useQuery, useMutation } from "react-apollo";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { QUERY_ALL_VENDORS_FOR_ORDER } from "../../graphql/vendors.queries";
import { INSERT_NEW_PARTS_ORDERS } from "../../graphql/parts-orders.queries";
import {
selectCurrentUser,
selectBodyshop
} from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import PartsOrderModalComponent from "./parts-order-modal.component";
import { useTranslation } from "react-i18next";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
bodyshop: selectBodyshop
});
export default connect(
mapStateToProps,
null
)(function PartsOrderModalContainer({
partsOrderModalVisible,
linesToOrder,
jobId,
currentUser,
bodyshop
}) {
const [modalVisible, setModalVisible] = partsOrderModalVisible;
const { loading, error, data } = useQuery(QUERY_ALL_VENDORS_FOR_ORDER, {
fetchPolicy: "network-only",
skip: !modalVisible
});
const { t } = useTranslation();
const [insertPartOrder] = useMutation(INSERT_NEW_PARTS_ORDERS);
const sendTypeState = useState("e");
const partsOrderState = useState({
vendorid: null,
jobid: jobId,
user_email: currentUser.email
});
console.log("sendTypeState[0]", sendTypeState[0]);
const partsOrder = partsOrderState[0];
const handleOk = () => {
insertPartOrder({
variables: {
po: [
{
...partsOrder,
status: bodyshop.md_order_statuses.default_ordered || "Ordered*",
parts_order_lines: {
data: linesToOrder.reduce((acc, value) => {
acc.push({
line_desc: value.line_desc,
oem_partno: value.oem_partno,
db_price: value.db_price,
act_price: value.act_price,
line_remarks: "Alalala",
joblineid: value.joblineid,
status:
bodyshop.md_order_statuses.default_ordered || "Ordered*"
});
return acc;
}, [])
}
}
]
}
})
.then(r => {
notification["success"]({
message: t("parts_orders.successes.created")
});
setModalVisible(false);
})
.catch(error => {
notification["error"]({
message: t("parts_orders.errors.creating"),
description: error.message
});
});
};
return (
<Modal
visible={modalVisible}
onCancel={() => setModalVisible(false)}
onOk={handleOk}>
{error ? <AlertComponent message={error.message} type='error' /> : null}
<LoadingSpinner loading={loading}>
<PartsOrderModalComponent
vendorList={(data && data.vendors) || []}
state={partsOrderState}
sendTypeState={sendTypeState}
/>
</LoadingSpinner>
</Modal>
);
});

View File

@@ -1,11 +1,11 @@
import { Button, Form, Input, notification } 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 { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectCurrentUser } from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component";
import { Form, Input, notification, Button } from "antd";
import { updateUserDetails } from "../../redux/user/user.actions"; import { updateUserDetails } from "../../redux/user/user.actions";
import { selectCurrentUser } from "../../redux/user/user.selectors";
import ResetForm from "../form-items-formatted/reset-form-item.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser currentUser: selectCurrentUser
@@ -47,33 +47,26 @@ export default connect(
return ( return (
<div> <div>
{isFieldsTouched() ? ( {isFieldsTouched() ? <ResetForm resetFields={resetFields} /> : null}
//TODO: Appropriate Error
<AlertComponent
message={t("jobs.errors.validation")}
onClick={() => resetFields()}
/>
) : null}
<Form onSubmit={handleSubmit} autoComplete={"no"}> <Form onSubmit={handleSubmit} autoComplete={"no"}>
<Form.Item label={t("user.fields.displayname")}> <Form.Item label={t("user.fields.displayname")}>
{getFieldDecorator("displayname", { {getFieldDecorator("displayname", {
initialValue: currentUser.displayName, initialValue: currentUser.displayName,
rules: [{ required: true }] rules: [{ required: true }]
})(<Input name="displayname" />)} })(<Input name='displayname' />)}
</Form.Item> </Form.Item>
<Form.Item label={t("user.fields.photourl")}> <Form.Item label={t("user.fields.photourl")}>
{getFieldDecorator("photoURL", { {getFieldDecorator("photoURL", {
initialValue: currentUser.photoURL initialValue: currentUser.photoURL
})(<Input name="photoURL" />)} })(<Input name='photoURL' />)}
</Form.Item> </Form.Item>
<Button <Button
type="primary" type='primary'
key="submit" key='submit'
htmlType="submit" htmlType='submit'
onClick={handleSubmit} onClick={handleSubmit}>
>
{t("user.actions.updateprofile")} {t("user.actions.updateprofile")}
</Button> </Button>
</Form> </Form>

View File

@@ -11,7 +11,7 @@ export default function ScheduleJobModalComponent({
...props ...props
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
//TODO: Existing appointments list only refreshes sometimes after modal close. May have to do with the container class. //TODO Existing appointments list only refreshes sometimes after modal close. May have to do with the container class.
return ( return (
<Modal <Modal
{...props} {...props}
@@ -47,7 +47,7 @@ export default function ScheduleJobModalComponent({
</Row> </Row>
{ {
//TODO: Build out notifications. //TODO Build out notifications.
} }
<Checkbox <Checkbox
defaultChecked={formData.notifyCustomer} defaultChecked={formData.notifyCustomer}

View File

@@ -43,7 +43,7 @@ export default connect(
visible={scheduleModalVisible} visible={scheduleModalVisible}
onCancel={() => setscheduleModalVisible(false)} onCancel={() => setscheduleModalVisible(false)}
onOk={() => { onOk={() => {
//TODO: Customize the amount of minutes it will add. //TODO Customize the amount of minutes it will add.
insertAppointment({ insertAppointment({
variables: { variables: {
app: { ...appData, end: moment(appData.start).add(60, "minutes") } app: { ...appData, end: moment(appData.start).add(60, "minutes") }
@@ -55,7 +55,7 @@ export default connect(
}); });
if (formData.notifyCustomer) { if (formData.notifyCustomer) {
//TODO: Implement customer reminder on scheduling. //TODO Implement customer reminder on scheduling.
alert("Chosed to notify the customer somehow!"); alert("Chosed to notify the customer somehow!");
} }
setscheduleModalVisible(false); setscheduleModalVisible(false);

View File

@@ -0,0 +1 @@
@import 'react-big-calendar/lib/sass/styles';

View File

@@ -1,7 +1,8 @@
import moment from "moment"; import moment from "moment";
import React from "react"; import React from "react";
import { Calendar, momentLocalizer } from "react-big-calendar"; import { Calendar, momentLocalizer } from "react-big-calendar";
import "react-big-calendar/lib/css/react-big-calendar.css"; //import "react-big-calendar/lib/css/react-big-calendar.css";
import "./schedule-calendar.styles.scss";
import DateCellWrapper from "../schedule-datecellwrapper/schedule-datecellwrapper.component"; import DateCellWrapper from "../schedule-datecellwrapper/schedule-datecellwrapper.component";
import Event from "../schedule-event/schedule-event.container"; import Event from "../schedule-event/schedule-event.container";
const localizer = momentLocalizer(moment); const localizer = momentLocalizer(moment);
@@ -19,7 +20,7 @@ export default function ScheduleCalendarWrapperComponent({
step={30} step={30}
showMultiDayTimes showMultiDayTimes
localizer={localizer} localizer={localizer}
min={new Date("2020-01-01T06:00:00")} //TODO: Read from business settings. min={new Date("2020-01-01T06:00:00")} //TODO Read from business settings.
max={new Date("2020-01-01T20:00:00")} max={new Date("2020-01-01T20:00:00")}
components={{ components={{
event: e => { event: e => {

View File

@@ -61,14 +61,14 @@ export default function ScheduleEventComponent({ event, handleCancel }) {
</Button> </Button>
<Button> <Button>
{ {
//TODO: Add reschedule Func. //TODO Add reschedule Func.
} }
{t("appointments.actions.reschedule")} {t("appointments.actions.reschedule")}
</Button> </Button>
{event.isintake ? ( {event.isintake ? (
<Button> <Button>
{ {
//TODO: Add intake func. //TODO Add intake func.
} }
{t("appointments.actions.intake")} {t("appointments.actions.intake")}
</Button> </Button>

View File

@@ -13,7 +13,7 @@ export default function ScheduleJobModalComponent({
...props ...props
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
//TODO: Existing appointments list only refreshes sometimes after modal close. May have to do with the container class. //TODO Existing appointments list only refreshes sometimes after modal close. May have to do with the container class.
return ( return (
<Modal <Modal
{...props} {...props}
@@ -52,7 +52,7 @@ export default function ScheduleJobModalComponent({
existingAppointments={existingAppointments} existingAppointments={existingAppointments}
/> />
{ {
//TODO: Build out notifications. //TODO Build out notifications.
} }
<Checkbox <Checkbox
defaultChecked={formData.notifyCustomer} defaultChecked={formData.notifyCustomer}

View File

@@ -60,7 +60,7 @@ export default connect(
visible={scheduleModalVisible} visible={scheduleModalVisible}
onCancel={() => setscheduleModalVisible(false)} onCancel={() => setscheduleModalVisible(false)}
onOk={() => { onOk={() => {
//TODO: Customize the amount of minutes it will add. //TODO Customize the amount of minutes it will add.
insertAppointment({ insertAppointment({
variables: { variables: {
app: { ...appData, end: moment(appData.start).add(60, "minutes") } app: { ...appData, end: moment(appData.start).add(60, "minutes") }
@@ -73,7 +73,7 @@ export default connect(
}); });
if (formData.notifyCustomer) { if (formData.notifyCustomer) {
//TODO: Implement customer reminder on scheduling. //TODO Implement customer reminder on scheduling.
alert("Chosed to notify the customer somehow!"); alert("Chosed to notify the customer somehow!");
} }
setscheduleModalVisible(false); setscheduleModalVisible(false);

View File

@@ -0,0 +1,46 @@
import React from "react";
import { Input } from "antd";
import CKEditor from "@ckeditor/ckeditor5-react";
import ClassicEditor from "@ckeditor/ckeditor5-build-classic";
export default function SendEmailButtonComponent({
emailConfig,
handleConfigChange,
handleHtmlChange
}) {
return (
<div>
THis is where the text editing will happen To
<Input
defaultValue={emailConfig.to}
onChange={handleConfigChange}
name='to'
/>
CC
<Input
defaultValue={emailConfig.cc}
onChange={handleConfigChange}
name='cc'
/>
Subject
<Input
defaultValue={emailConfig.subject}
onChange={handleConfigChange}
name='subject'
/>
<CKEditor
editor={ClassicEditor}
data={emailConfig.html}
onInit={editor => {
// You can store the "editor" and use when it is needed.
console.log("Editor is ready to use!", editor);
}}
onChange={(event, editor) => {
const data = editor.getData();
console.log({ event, editor, data });
handleHtmlChange(data);
}}
/>
</div>
);
}

View File

@@ -0,0 +1,72 @@
import { Button, Modal } from "antd";
import axios from "axios";
import React, { useState } from "react";
import { useQuery } from "react-apollo";
//Message options has the to & from details
//Query Config is what can get popped into UseQuery(QUERY_NAME, {variables: {vars}, fetchonly})
//Template Which template should be used to send the email.
import ReactDOMServer from "react-dom/server";
import { renderEmail } from "react-html-email";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import SendEmailButtonComponent from "./send-email-button.component";
export default function SendEmail({
MessageOptions,
QueryConfig,
Template,
...otherProps
}) {
const [modalVisible, setModalVisible] = useState(false);
const [emailConfig, setEmailConfig] = useState({ ...MessageOptions });
const [gqlQuery, vars] = QueryConfig;
const { loading, data } = useQuery(gqlQuery, {
...vars,
fetchPolicy: "network-only",
skip: !modalVisible
});
if (data && !emailConfig.html) {
console.log(ReactDOMServer.renderToStaticMarkup(<Template data={data} />));
setEmailConfig({
...emailConfig,
//html: ReactDOMServer.renderToStaticMarkup(<Template data={data} />)
html: renderEmail(<Template data={data} />)
});
}
const handleConfigChange = event => {
const { name, value } = event.target;
setEmailConfig({ ...emailConfig, [name]: value });
};
const handleHtmlChange = text => {
setEmailConfig({ ...emailConfig, html: text });
};
const sendEmail = () => {
axios.post("/sendemail", emailConfig).then(response => {
alert(JSON.stringify(response));
});
};
return (
<div>
<Modal
destroyOnClose={true}
visible={modalVisible}
width={"80%"}
onOk={sendEmail}
onCancel={() => setModalVisible(false)}>
<LoadingSpinner loading={loading}>
<SendEmailButtonComponent
handleConfigChange={handleConfigChange}
emailConfig={emailConfig}
handleHtmlChange={handleHtmlChange}
/>
</LoadingSpinner>
</Modal>
<Button onClick={() => setModalVisible(true)}>
{otherProps.children}
</Button>
</div>
);
}

View File

@@ -30,7 +30,7 @@ function ShopEmployeesContainer({ form, bodyshop }) {
notification["success"]({ notification["success"]({
message: t("employees.successes.delete") message: t("employees.successes.delete")
}); });
//TODO: Better way to reset the field decorators? //TODO Better way to reset the field decorators?
employeeState[1](null); employeeState[1](null);
refetch().then(r => form.resetFields()); refetch().then(r => form.resetFields());
}) })
@@ -61,7 +61,7 @@ function ShopEmployeesContainer({ form, bodyshop }) {
notification["success"]({ notification["success"]({
message: t("employees.successes.save") message: t("employees.successes.save")
}); });
//TODO: Better way to reset the field decorators? //TODO Better way to reset the field decorators?
employeeState[1](null); employeeState[1](null);
refetch().then(r => form.resetFields()); refetch().then(r => form.resetFields());
}) })
@@ -78,7 +78,7 @@ function ShopEmployeesContainer({ form, bodyshop }) {
notification["success"]({ notification["success"]({
message: t("employees.successes.save") message: t("employees.successes.save")
}); });
//TODO: Better way to reset the field decorators? //TODO Better way to reset the field decorators?
employeeState[1](null); employeeState[1](null);
refetch() refetch()
.then(r => form.resetFields()) .then(r => form.resetFields())

View File

@@ -26,7 +26,7 @@ function VehicleDetailFormContainer({ form, vehicle, refetch }) {
notification["success"]({ notification["success"]({
message: t("vehicles.successes.save") message: t("vehicles.successes.save")
}); });
//TODO: Better way to reset the field decorators? //TODO Better way to reset the field decorators?
if (refetch) refetch().then(); if (refetch) refetch().then();
form.resetFields(); form.resetFields();
}); });

View File

@@ -0,0 +1,195 @@
import {
Button,
Checkbox,
Col,
Form,
Icon,
Input,
InputNumber,
Row
} from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import FormItemEmail from "../form-items-formatted/email-form-item.component";
import ResetForm from "../form-items-formatted/reset-form-item.component";
let id = 0;
export default function VendorsFormComponent({ form, vendor, handleDelete }) {
const {
getFieldDecorator,
isFieldsTouched,
getFieldValue,
resetFields
} = form;
getFieldDecorator("keys", {
initialValue: Array.isArray(vendor.favorite) ? vendor.favorite : []
});
const remove = k => {
// can use data-binding to get
const keys = form.getFieldValue("keys");
console.log("keys", keys);
// We need at least one passenger
if (keys.length === 1) {
return;
}
// can use data-binding to set
form.setFieldsValue({
keys: keys.filter(key => key !== k)
});
};
const add = props => {
console.log("props", props);
// can use data-binding to get
const keys = form.getFieldValue("keys");
console.log("keys", keys);
const nextKeys = keys.concat(id++);
// can use data-binding to set
// important! notify form to detect changes
form.setFieldsValue({
keys: nextKeys
});
};
const { t } = useTranslation();
return (
<div>
{isFieldsTouched() ? <ResetForm resetFields={resetFields} /> : null}
<Button htmlType="submit" type="primary">
{t("general.actions.save")}
</Button>
<Button type="danger" onClick={handleDelete}>
{t("general.actions.delete")}
</Button>
{getFieldValue("keys").map((k, index) => (
<Form.Item required={false} key={k}>
{getFieldDecorator(`favorite[${k}].make`, {
validateTrigger: ["onChange", "onBlur"]
})(
<Input
placeholder="passenger name"
style={{ width: "60%", marginRight: 8 }}
/>
)}
{getFieldValue("keys").length > 1 ? (
<Icon
className="dynamic-delete-button"
type="minus-circle-o"
onClick={() => remove(k)}
/>
) : null}
<Form.Item label="Group">
{getFieldDecorator(`favorite[${k}].type`, {
initialValue: null
})(
<Checkbox.Group style={{ width: "100%" }}>
<Row>
<Col span={8}>
<Checkbox value="OEM">OEM</Checkbox>
</Col>
<Col span={8}>
<Checkbox value="LKQ">LKQ</Checkbox>
</Col>
<Col span={8}>
<Checkbox value="AM">AM</Checkbox>
</Col>
</Row>
</Checkbox.Group>
)}
</Form.Item>
</Form.Item>
))}
<Form.Item label={t("vendors.fields.favorite")}>
<Button type="dashed" onClick={add} style={{ width: "60%" }}>
<Icon type="plus" /> Add field
</Button>
</Form.Item>
<Form.Item label={t("vendors.fields.zip")}>
{getFieldDecorator("zip", {
initialValue: vendor.zip
})(<Input name="zip" />)}
</Form.Item>
<Form.Item label={t("vendors.fields.terms")}>
{getFieldDecorator("terms", {
initialValue: vendor.terms
})(<Input name="terms" />)}
</Form.Item>
<Form.Item label={t("vendors.fields.taxid")}>
{getFieldDecorator("taxid", {
initialValue: vendor.taxid
})(<Input name="taxid" />)}
</Form.Item>
<Form.Item label={t("vendors.fields.street1")}>
{getFieldDecorator("street1", {
initialValue: vendor.street1
})(<Input name="street1" />)}
</Form.Item>
<Form.Item label={t("vendors.fields.street2")}>
{getFieldDecorator("street2", {
initialValue: vendor.street2
})(<Input name="street2" />)}
</Form.Item>
<Form.Item label={t("vendors.fields.state")}>
{getFieldDecorator("state", {
initialValue: vendor.state
})(<Input name="state" />)}
</Form.Item>
<Form.Item label={t("vendors.fields.prompt_discount")}>
{getFieldDecorator("prompt_discount", {
initialValue: vendor.prompt_discount
})(<InputNumber name="prompt_discount" />)}
</Form.Item>
<Form.Item label={t("vendors.fields.name")}>
{getFieldDecorator("name", {
initialValue: vendor.name
})(<Input name="name" />)}
</Form.Item>
<Form.Item label={t("vendors.fields.email")}>
{getFieldDecorator("email", {
initialValue: vendor.email,
rules: [
{
type: "email",
message: t("general.validation.invalidemail")
}
]
})(<FormItemEmail name="email" email={getFieldValue("email")} />)}
</Form.Item>
<Form.Item label={t("vendors.fields.due_date")}>
{getFieldDecorator("due_date", {
initialValue: vendor.due_date
})(<InputNumber name="due_date" />)}
</Form.Item>
<Form.Item label={t("vendors.fields.display_name")}>
{getFieldDecorator("display_name", {
initialValue: vendor.display_name
})(<Input name="display_name" />)}
</Form.Item>
<Form.Item label={t("vendors.fields.discount")}>
{getFieldDecorator("discount", {
initialValue: vendor.discount
})(<InputNumber name="discount" />)}
</Form.Item>
<Form.Item label={t("vendors.fields.country")}>
{getFieldDecorator("country", {
initialValue: vendor.country
})(<Input name="country" />)}
</Form.Item>
<Form.Item label={t("vendors.fields.cost_center")}>
{getFieldDecorator("cost_center", {
initialValue: vendor.cost_center,
rules: [{ required: true, message: t("general.validation.required") }]
})(<Input name="cost_center" />)}
</Form.Item>
<Form.Item label={t("vendors.fields.city")}>
{getFieldDecorator("city", {
initialValue: vendor.city
})(<Input name="city" />)}
</Form.Item>
</div>
);
}

View File

@@ -0,0 +1,118 @@
import { Form, notification } from "antd";
import React from "react";
import { useMutation, useQuery } from "react-apollo";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {
DELETE_VENDOR,
INSERT_NEW_VENDOR,
UPDATE_VENDOR,
QUERY_VENDOR_BY_ID
} from "../../graphql/vendors.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import VendorsFormComponent from "./vendors-form.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import AlertComponent from "../alert/alert.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
function VendorsFormContainer({ form, vendorId, refetch, bodyshop }) {
const { t } = useTranslation();
const { loading, error, data } = useQuery(QUERY_VENDOR_BY_ID, {
variables: { id: vendorId },
fetchPolicy: "network-only",
skip: !vendorId
});
const [updateVendor] = useMutation(UPDATE_VENDOR);
const [insertvendor] = useMutation(INSERT_NEW_VENDOR);
const [deleteVendor] = useMutation(DELETE_VENDOR);
const handleDelete = () => {
deleteVendor({ variables: { id: vendorId } })
.then(r => {
notification["success"]({
message: t("vendors.successes.deleted")
});
//TODO Better way to reset the field decorators?
if (refetch) refetch().then(r => form.resetFields());
})
.catch(error => {
notification["error"]({
message: t("vendors.errors.deleting")
});
});
};
const handleSubmit = e => {
e.preventDefault();
form.validateFieldsAndScroll((err, values) => {
if (err) {
notification["error"]({
message: t("jobs.errors.validationtitle"),
description: t("jobs.errors.validation")
});
}
if (!err) {
console.log("Received values of form: ", values);
delete values.keys;
if (vendorId) {
//It's a vendor to update.
updateVendor({
variables: { id: vendorId, vendor: values }
})
.then(r => {
notification["success"]({
message: t("vendors.successes.saved")
});
//TODO Better way to reset the field decorators?
if (refetch) refetch().then(r => form.resetFields());
})
.catch(error => {
notification["error"]({
message: t("vendors.errors.saving")
});
});
} else {
//It's a new vendor to insert.
insertvendor({
variables: { vendorInput: [{ ...values, bodyshopid: bodyshop.id }] }
})
.then(r => {
notification["success"]({
message: t("vendors.successes.saved")
});
//TODO Better way to reset the field decorators?
if (refetch) refetch().then(r => form.resetFields());
})
.catch(error => {
notification["error"]({
message: t("vendors.errors.saving")
});
});
}
}
});
};
if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />;
return (
<Form onSubmit={handleSubmit} autoComplete="new-password">
{data ? (
<VendorsFormComponent
form={form}
vendor={data ? data.vendors_by_pk : null}
handleDelete={handleDelete}
/>
) : (
t("vendors.labels.noneselected")
)}
</Form>
);
}
export default connect(
mapStateToProps,
null
)(Form.create({ name: "VendorsFormContainer" })(VendorsFormContainer));

View File

@@ -0,0 +1,102 @@
import { Input, Table, Button } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { alphaSort } from "../../utils/sorters";
export default function VendorsListComponent({
selectedVendor,
setSelectedVendor,
handleNewVendor,
loading,
refetch,
handleOnRowClick,
vendors
}) {
const [state, setState] = useState({
sortedInfo: {},
filteredInfo: { text: "" }
});
const { t } = useTranslation();
const columns = [
{
title: t("vendors.fields.name"),
dataIndex: "name",
key: "name",
sorter: (a, b) => alphaSort(a.name, b.name),
sortOrder: state.sortedInfo.columnKey === "name" && state.sortedInfo.order
},
{
title: t("vendors.fields.cost_center"),
dataIndex: "cost_center",
key: "cost_center",
sorter: (a, b) => alphaSort(a.cost_center, b.cost_center),
sortOrder:
state.sortedInfo.columnKey === "cost_center" && state.sortedInfo.order
},
{
title: t("vendors.fields.street1"),
dataIndex: "street1",
key: "street1",
width: "10%",
sorter: (a, b) => alphaSort(a.street1, b.street1),
sortOrder:
state.sortedInfo.columnKey === "street1" && state.sortedInfo.order
},
{
title: t("vendors.fields.city"),
dataIndex: "city",
key: "city"
}
];
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
};
//TODO Implement search
return (
<div>
<Table
loading={loading}
title={() => {
return (
<div>
{" "}
<Input.Search
placeholder={t("general.labels.search")}
onSearch={value => {
console.log(value);
}}
enterButton
/>
<Button onClick={handleNewVendor}>
{t("vendors.actions.new")}
</Button>
</div>
);
}}
size="small"
pagination={{ position: "top" }}
columns={columns.map(item => ({ ...item }))}
rowKey="id"
onChange={handleTableChange}
dataSource={vendors}
rowSelection={{
onSelect: record => {
setSelectedVendor(record);
},
type: "radio",
selectedRowKeys: selectedVendor ? selectedVendor.id : null
}}
onRow={(record, rowIndex) => {
return {
onClick: event => {
handleOnRowClick(record);
}
};
}}
/>
</div>
);
}

View File

@@ -0,0 +1,37 @@
import React from "react";
import { useQuery } from "react-apollo";
import AlertComponent from "../../components/alert/alert.component";
import { QUERY_ALL_VENDORS } from "../../graphql/vendors.queries";
import VendorsListComponent from "./vendors-list.component";
export default function VendorsListContainer({ selectedVendorState }) {
const [selectedVendor, setSelectedVendor] = selectedVendorState;
const { loading, error, data, refetch } = useQuery(QUERY_ALL_VENDORS, {
fetchPolicy: "network-only"
});
const handleNewVendor = () => {
setSelectedVendor({});
};
const handleOnRowClick = record => {
if (record) {
setSelectedVendor(record);
return;
}
setSelectedVendor(null);
};
if (error) return <AlertComponent message={error.message} type='error' />;
return (
<VendorsListComponent
selectedVendor={selectedVendor}
setSelectedVendor={setSelectedVendor}
handleNewVendor={handleNewVendor}
handleOnRowClick={handleOnRowClick}
loading={loading}
refetch={refetch}
vendors={data ? data.vendors : null}
/>
);
}

View File

@@ -0,0 +1,46 @@
import React from "react";
function Cell({ children }) {
return <td>{children}</td>;
}
function Row({ children }) {
return (
<tr>
{React.Children.map(children, el => {
if (el.type === Cell) return el;
return <td>{el}</td>;
})}
</tr>
);
}
function Grid({ children }) {
return (
<table>
<tbody>
{React.Children.map(children, el => {
if (!el) return;
if (el.type === Row) return el;
if (el.type === Cell) {
return <tr>{el}</tr>;
}
return (
<tr>
<td>{el}</td>
</tr>
);
})}
</tbody>
</table>
);
}
Grid.Row = Row;
Grid.Cell = Cell;
export default Grid;

View File

@@ -0,0 +1,30 @@
import React from "react";
import { A, Box, Email, Item, Span } from "react-html-email";
export default function PartsOrderEmail({ data }) {
console.log("Data", data);
return (
<Email title='Hello World!'>
<Box>
<Item align='center'>
<Span fontSize={20}>
This is an example email made with:
<A href='https://github.com/chromakode/react-html-email'>
react-html-email
</A>
.
</Span>
</Item>
<Item align='center'>
<Span fontSize={20}>
This is an example email made with:
<A href='https://github.com/chromakode/react-html-email'>
react-html-email
</A>
.
</Span>
</Item>
</Box>
</Email>
);
}

View File

@@ -0,0 +1,45 @@
import { gql } from "apollo-boost";
export const REPORT_QUERY_PARTS_ORDER_BY_PK = gql`
query REPORT_QUERY_PARTS_ORDER_BY_PK($id: uuid!) {
parts_orders_by_pk(id: $id) {
job {
id
vehicle {
id
v_model_desc
v_make_desc
v_model_yr
v_vin
}
ro_number
est_number
}
id
deliver_by
parts_order_lines {
id
db_price
act_price
line_desc
line_remarks
oem_partno
status
}
status
user_email
}
bodyshops(where: { associations: { active: { _eq: true } } }) {
id
address1
address2
city
email
federal_tax_id
state
shopname
zip_post
logo_img_path
}
}
`;

View File

@@ -33,16 +33,17 @@ const errorLink = onError(
console.log("Got the new token.", token); console.log("Got the new token.", token);
window.localStorage.setItem("token", token); window.localStorage.setItem("token", token);
// const oldHeaders = operation.getContext().headers; const oldHeaders = operation.getContext().headers;
// operation.setContext({ operation.setContext({
// headers: { headers: {
// ...oldHeaders, ...oldHeaders,
// authorization: token ? `Bearer ${token}` : "" authorization: token ? `Bearer ${token}` : ""
// } }
// }); });
console.log(operation.getContext());
// console.log("forward", forward); // console.log("forward", forward);
// console.log("operation", operation); // console.log("operation", operation);
return forward(operation).subscribe(); return forward(operation);
// return new Observable(observer => { // return new Observable(observer => {
// const subscriber = { // const subscriber = {

View File

@@ -14,6 +14,7 @@ export const QUERY_BODYSHOP = gql`
insurance_vendor_id insurance_vendor_id
logo_img_path logo_img_path
md_ro_statuses md_ro_statuses
md_order_statuses
shopname shopname
state state
state_tax_id state_tax_id

View File

@@ -1,8 +1,8 @@
import { gql } from "apollo-boost"; import { gql } from "apollo-boost";
export const QUERY_ALL_OPEN_JOBS = gql` export const QUERY_ALL_ACTIVE_JOBS = gql`
query QUERY_ALL_OPEN_JOBS { query QUERY_ALL_ACTIVE_JOBS($statuses: [String!]!) {
jobs { jobs(where: { status: { _in: $statuses } }) {
ownr_fn ownr_fn
ownr_ln ownr_ln
ownr_ph1 ownr_ph1
@@ -44,7 +44,6 @@ export const QUERY_ALL_OPEN_JOBS = gql`
scheduled_delivery scheduled_delivery
status status
updated_at updated_at
clm_total
ded_amt ded_amt
vehicle { vehicle {
id id
@@ -108,6 +107,7 @@ export const GET_JOB_BY_PK = gql`
converted converted
est_number est_number
ro_number ro_number
clm_total
vehicle { vehicle {
id id
plate_no plate_no

View File

@@ -2,7 +2,7 @@ import { gql } from "apollo-boost";
export const QUERY_SEARCH_OWNER_BY_IDX = gql` export const QUERY_SEARCH_OWNER_BY_IDX = gql`
query QUERY_SEARCH_OWNER_BY_IDX($search: String!) { query QUERY_SEARCH_OWNER_BY_IDX($search: String!) {
search_owners(args: { search: $search }) { search_owner(args: { search: $search }) {
ownr_fn ownr_fn
ownr_ln ownr_ln
ownr_ph1 ownr_ph1

View File

@@ -0,0 +1,11 @@
import { gql } from "apollo-boost";
export const INSERT_NEW_PARTS_ORDERS = gql`
mutation INSERT_NEW_PARTS_ORDERS($po: [parts_orders_insert_input!]!) {
insert_parts_orders(objects: $po) {
returning {
id
}
}
}
`;

View File

@@ -0,0 +1,78 @@
import { gql } from "apollo-boost";
export const QUERY_VENDOR_BY_ID = gql`
query QUERY_VENDOR_BY_ID($id: uuid!) {
vendors_by_pk(id: $id) {
zip
terms
taxid
street2
state
prompt_discount
name
id
favorite
email
due_date
display_name
discount
country
cost_center
city
street1
}
}
`;
export const UPDATE_VENDOR = gql`
mutation UPDATE_VENDOR($id: uuid!, $vendor: vendors_set_input!) {
update_vendors(where: { id: { _eq: $id } }, _set: $vendor) {
returning {
id
}
}
}
`;
export const QUERY_ALL_VENDORS = gql`
query QUERY_ALL_VENDORS {
vendors {
name
id
street1
cost_center
city
}
}
`;
export const INSERT_NEW_VENDOR = gql`
mutation INSERT_NEW_VENDOR($vendorInput: [vendors_insert_input!]!) {
insert_vendors(objects: $vendorInput) {
returning {
id
}
}
}
`;
export const DELETE_VENDOR = gql`
mutation DELETE_VENDOR($id: uuid!) {
delete_vendors(where: { id: { _eq: $id } }) {
returning {
id
}
}
}
`;
export const QUERY_ALL_VENDORS_FOR_ORDER = gql`
query QUERY_ALL_VENDORS_FOR_ORDER {
vendors(order_by: { favorite: desc }) {
name
cost_center
id
favorite
}
}
`;

View File

@@ -13,7 +13,7 @@ export default function JobsAvailablePageComponent({
deleteJob={deleteJob} deleteJob={deleteJob}
estDataLazyLoad={estDataLazyLoad} estDataLazyLoad={estDataLazyLoad}
/> />
Available Supplements (//TODO: LOGIC HAS NOT YET BEEN COPIED FROM OTHER COMPONENT) Available Supplements (//TODO LOGIC HAS NOT YET BEEN COPIED FROM OTHER COMPONENT)
<JobsAvailableSupplementContainer <JobsAvailableSupplementContainer
deleteJob={deleteJob} deleteJob={deleteJob}
estDataLazyLoad={estDataLazyLoad} estDataLazyLoad={estDataLazyLoad}

View File

@@ -36,8 +36,7 @@ function JobsDetailPageContainer({ match, form }) {
refetch(); refetch();
}) })
.catch(error => { .catch(error => {
//TODO Error handling. notification[error]({ message: t("jobs.errors.saving") });
console.log("error", error);
}); });
}; };
@@ -70,7 +69,6 @@ function JobsDetailPageContainer({ match, form }) {
notification["success"]({ notification["success"]({
message: t("jobs.successes.savetitle") message: t("jobs.successes.savetitle")
}); });
//TODO: Better way to reset the field decorators?
refetch().then(r => form.resetFields()); refetch().then(r => form.resetFields());
}); });
} }
@@ -78,7 +76,7 @@ function JobsDetailPageContainer({ match, form }) {
}; };
if (loading) return <SpinComponent />; if (loading) return <SpinComponent />;
if (error) return <AlertComponent message={error.message} type="error" />; if (error) return <AlertComponent message={error.message} type='error' />;
return data.jobs_by_pk ? ( return data.jobs_by_pk ? (
<JobDetailFormContext.Provider value={form}> <JobDetailFormContext.Provider value={form}>
@@ -94,7 +92,7 @@ function JobsDetailPageContainer({ match, form }) {
/> />
</JobDetailFormContext.Provider> </JobDetailFormContext.Provider>
) : ( ) : (
<AlertComponent message={t("jobs.errors.noaccess")} type="error" /> <AlertComponent message={t("jobs.errors.noaccess")} type='error' />
); );
} }
export default Form.create({ name: "JobsDetailPageContainer" })( export default Form.create({ name: "JobsDetailPageContainer" })(

View File

@@ -4,12 +4,25 @@ import { useTranslation } from "react-i18next";
import AlertComponent from "../../components/alert/alert.component"; import AlertComponent from "../../components/alert/alert.component";
import JobDetailCards from "../../components/job-detail-cards/job-detail-cards.component"; import JobDetailCards from "../../components/job-detail-cards/job-detail-cards.component";
import JobsList from "../../components/jobs-list/jobs-list.component"; import JobsList from "../../components/jobs-list/jobs-list.component";
import { QUERY_ALL_OPEN_JOBS } from "../../graphql/jobs.queries"; import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries";
//TODO: Implement pagination for this. import { selectBodyshop } from "../../redux/user/user.selectors";
export default function JobsPage({ match, location }) {
const { loading, error, data } = useQuery(QUERY_ALL_OPEN_JOBS, { import { connect } from "react-redux";
fetchPolicy: "network-only" import { createStructuredSelector } from "reselect";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
export default connect(
mapStateToProps,
null
)(function JobsPage({ match, location, bodyshop }) {
const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, {
fetchPolicy: "network-only",
variables: {
statuses: bodyshop.md_ro_statuses.open_statuses || ["Open"]
}
}); });
const { t } = useTranslation(); const { t } = useTranslation();
@@ -19,17 +32,53 @@ export default function JobsPage({ match, location }) {
const { hash } = location; const { hash } = location;
const [selectedJob, setSelectedJob] = useState(hash ? hash.substr(1) : null); const [selectedJob, setSelectedJob] = useState(hash ? hash.substr(1) : null);
const searchTextState = useState("");
const searchText = searchTextState[0];
if (error) return <AlertComponent message={error.message} type='error' />;
//TODO Implement pagination for this.
if (error) return <AlertComponent message={error.message} type="error" />; console.log(typeof searchText);
return ( return (
<div> <div>
<JobsList <JobsList
searchTextState={searchTextState}
refetch={refetch}
loading={loading} loading={loading}
selectedJob={selectedJob} selectedJob={selectedJob}
setSelectedJob={setSelectedJob} setSelectedJob={setSelectedJob}
jobs={data ? data.jobs : null} jobs={
data
? searchTextState[0] === ""
? data.jobs
: data.jobs.filter(
j =>
(j.ro_number || "")
.toString()
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_fn || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_ln || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.clm_no || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.vehicle.plate_no || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.vehicle.v_model_desc || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.vehicle.v_make_desc || "")
.toLowerCase()
.includes(searchText.toLowerCase())
)
: null
}
/> />
<JobDetailCards selectedJob={selectedJob} /> <JobDetailCards selectedJob={selectedJob} />
</div> </div>
); );
} });

View File

@@ -1,13 +1,19 @@
import React from "react"; import React from "react";
import { Typography } from "antd"; import { Typography, Layout } from "antd";
import HeaderContainer from "../../components/header/header.container"; import HeaderContainer from "../../components/header/header.container";
export default function LandingPage() { export default function LandingPage() {
const { Header, Content } = Layout;
return ( return (
<div> <Layout style={{ minHeight: "100vh" }}>
<HeaderContainer landingHeader /> <Header>
<Typography.Title>Welcome to bodyshop.app.</Typography.Title> <HeaderContainer landingHeader />
</div> </Header>
<Content className="content-container" style={{ padding: "0em 4em 4em" }}>
<Typography.Title>Welcome to bodyshop.app.</Typography.Title>
</Content>
</Layout>
); );
} }

View File

@@ -1,9 +1,29 @@
import React from 'react' import React from "react";
import SendEmailButton from "../../components/send-email-button/send-email-button.container";
import PartsOrderEmail from "../../emails/parts-order/parts-order.email";
import { REPORT_QUERY_PARTS_ORDER_BY_PK } from "../../emails/parts-order/parts-order.query";
export default function ManageRootPageComponent() { export default function ManageRootPageComponent() {
return ( //const client = useApolloClient();
<div> return (
Temporary Home Page. <div>
</div> <SendEmailButton
) MessageOptions={{
from: {
name: "Kavia"
},
to: "patrickwf@gmail.com",
replyTo: "patrickwf@gmail.com"
}}
Template={PartsOrderEmail}
QueryConfig={[
REPORT_QUERY_PARTS_ORDER_BY_PK,
{
variables: { id: "ebe0fb6b-6ec4-4ae0-8fdc-49bdf1e37ff3" }
}
]}>
Send an Email in new Window
</SendEmailButton>
</div>
);
} }

View File

@@ -7,7 +7,10 @@ import { createStructuredSelector } from "reselect";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import { QUERY_BODYSHOP } from "../../graphql/bodyshop.queries"; import { QUERY_BODYSHOP } from "../../graphql/bodyshop.queries";
import { setBodyshop } from "../../redux/user/user.actions"; import { setBodyshop } from "../../redux/user/user.actions";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import {
selectBodyshop,
selectCurrentUser
} from "../../redux/user/user.selectors";
import ManagePage from "./manage.page"; import ManagePage from "./manage.page";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
@@ -33,12 +36,10 @@ export default connect(
useEffect(() => { useEffect(() => {
if (data) setBodyshop(data.bodyshops[0]); if (data) setBodyshop(data.bodyshops[0]);
return () => { return () => {};
//
};
}, [data, setBodyshop]); }, [data, setBodyshop]);
//TODO Translate later. if (!bodyshop)
if (!bodyshop) return <LoadingSpinner message="Loading bodyshop data." />; return <LoadingSpinner message={t("general.labels.loadingshop")} />;
return <ManagePage match={match} />; return <ManagePage match={match} />;
}); });

View File

@@ -40,6 +40,9 @@ const OwnersDetailContainer = lazy(() =>
import("../owners-detail/owners-detail.page.container") import("../owners-detail/owners-detail.page.container")
); );
const ShopPage = lazy(() => import("../shop/shop.page.component")); const ShopPage = lazy(() => import("../shop/shop.page.component"));
const ShopVendorPageContainer = lazy(() =>
import("../shop-vendor/shop-vendor.page.container")
);
const { Header, Content, Footer } = Layout; const { Header, Content, Footer } = Layout;
@@ -57,15 +60,13 @@ export default function Manage({ match }) {
</Header> </Header>
<Layout> <Layout>
<Content <Content
className="content-container" className='content-container'
style={{ padding: "0em 4em 4em" }} style={{ padding: "0em 4em 4em" }}>
>
<ErrorBoundary> <ErrorBoundary>
<Suspense <Suspense
fallback={ fallback={
<LoadingSpinner message={t("general.labels.loadingapp")} /> <LoadingSpinner message={t("general.labels.loadingapp")} />
} }>
>
<Route exact path={`${match.path}`} component={ManageRootPage} /> <Route exact path={`${match.path}`} component={ManageRootPage} />
<Route exact path={`${match.path}/jobs`} component={JobsPage} /> <Route exact path={`${match.path}/jobs`} component={JobsPage} />
@@ -118,6 +119,11 @@ export default function Manage({ match }) {
/> />
<Route exact path={`${match.path}/shop/`} component={ShopPage} /> <Route exact path={`${match.path}/shop/`} component={ShopPage} />
<Route
exact
path={`${match.path}/shop/vendors`}
component={ShopVendorPageContainer}
/>
</Suspense> </Suspense>
</ErrorBoundary> </ErrorBoundary>
</Content> </Content>

View File

@@ -0,0 +1,15 @@
import React from "react";
import VendorsListContainer from "../../components/vendors-list/vendors-list.container";
import VendorsFormContainer from "../../components/vendors-form/vendors-form.container";
export default function ShopVendorPageComponent({ selectedVendorState }) {
//TODO Figure out how to handle the refresh list when saving form
return (
<div>
<VendorsListContainer selectedVendorState={selectedVendorState} />
<VendorsFormContainer
vendorId={selectedVendorState[0] ? selectedVendorState[0].id : null}
/>
</div>
);
}

View File

@@ -0,0 +1,17 @@
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import ShopVendorPageComponent from "./shop-vendor.page.component";
export default function ShopVendorPageContainer() {
const { t } = useTranslation();
useEffect(() => {
document.title = t("titles.shop_vendors");
}, [t]);
const selectedVendorState = useState();
return (
<div>
<ShopVendorPageComponent selectedVendorState={selectedVendorState} />
</div>
);
}

View File

@@ -99,7 +99,7 @@ export function* updateUserDetails(userDetails) {
yield put(updateUserDetailsSuccess(userDetails.payload)); yield put(updateUserDetailsSuccess(userDetails.payload));
} catch (error) { } catch (error) {
//yield put(signOutFailure(error.message)); //yield put(signOutFailure(error.message));
//TODO: error handling //TODO error handling
} }
} }

View File

@@ -106,9 +106,12 @@
"in": "In", "in": "In",
"loading": "Loading...", "loading": "Loading...",
"loadingapp": "Loading Bodyshop.app", "loadingapp": "Loading Bodyshop.app",
"loadingshop": "Loading shop data...",
"loggingin": "Logging you in...",
"na": "N/A", "na": "N/A",
"out": "Out", "out": "Out",
"save": "Save", "save": "Save",
"search": "Search...",
"unknown": "Unknown" "unknown": "Unknown"
}, },
"languages": { "languages": {
@@ -120,6 +123,7 @@
"unsavedchanges": "You have unsaved changes." "unsavedchanges": "You have unsaved changes."
}, },
"validation": { "validation": {
"invalidemail": "Please enter a valid email.",
"required": "This field is required. " "required": "This field is required. "
} }
}, },
@@ -301,6 +305,8 @@
"owners": "Owners", "owners": "Owners",
"schedule": "Schedule", "schedule": "Schedule",
"shop": "My Shop", "shop": "My Shop",
"shop_config": "Configuration",
"shop_vendors": "Vendors",
"vehicles": "Vehicles" "vehicles": "Vehicles"
}, },
"jobsdetail": { "jobsdetail": {
@@ -369,6 +375,26 @@
"save": "Owner saved successfully." "save": "Owner saved successfully."
} }
}, },
"parts": {
"actions": {
"order": "Order Parts"
}
},
"parts_orders": {
"errors": {
"creating": "Error encountered when creating parts order. "
},
"fields": {
"deliver_by": "Deliver By"
},
"labels": {
"email": "Send by Email",
"print": "Show Printed Form"
},
"successes": {
"created": "Parts order created successfully. "
}
},
"profile": { "profile": {
"errors": { "errors": {
"state": "Error reading page state. Please refresh." "state": "Error reading page state. Please refresh."
@@ -385,6 +411,7 @@
"profile": "My Profile | $t(titles.app)", "profile": "My Profile | $t(titles.app)",
"schedule": "Schedule | $t(titles.app)", "schedule": "Schedule | $t(titles.app)",
"shop": "My Shop | $t(titles.app)", "shop": "My Shop | $t(titles.app)",
"shop_vendors": "Vendors | $t(titles.app)",
"vehicledetail": "Vehicle Details {{vehicle}} | $t(titles.app)", "vehicledetail": "Vehicle Details {{vehicle}} | $t(titles.app)",
"vehicles": "All Vehicles | $t(titles.app)" "vehicles": "All Vehicles | $t(titles.app)"
}, },
@@ -430,6 +457,41 @@
"successes": { "successes": {
"save": "Vehicle saved successfully." "save": "Vehicle saved successfully."
} }
},
"vendors": {
"actions": {
"new": "New Vendor"
},
"errors": {
"deleting": "Error encountered while deleting vendor. ",
"saving": "Error encountered while saving vendor. "
},
"fields": {
"city": "City",
"cost_center": "Cost Center",
"country": "Country",
"discount": "Discount %",
"display_name": "Display Name",
"due_date": "Payment Due Date",
"email": "Contact Email",
"favorite": "Favorite?",
"name": "Vendor Name",
"prompt_discount": "Prompt Discount %",
"state": "State/Province",
"street1": "Street",
"street2": "Address 2",
"taxid": "Tax ID",
"terms": "Payment Terms",
"zip": "Zip/Postal Code"
},
"labels": {
"noneselected": "No vendor is selected.",
"search": "Type a Vendor's Name"
},
"successes": {
"deleted": "Vendor deleted successfully. ",
"saved": "Vendor saved successfully."
}
} }
} }
} }

View File

@@ -106,9 +106,12 @@
"in": "en", "in": "en",
"loading": "Cargando...", "loading": "Cargando...",
"loadingapp": "Cargando Bodyshop.app", "loadingapp": "Cargando Bodyshop.app",
"loadingshop": "Cargando datos de la tienda ...",
"loggingin": "Iniciando sesión ...",
"na": "N / A", "na": "N / A",
"out": "Afuera", "out": "Afuera",
"save": "Salvar", "save": "Salvar",
"search": "Buscar...",
"unknown": "Desconocido" "unknown": "Desconocido"
}, },
"languages": { "languages": {
@@ -120,6 +123,7 @@
"unsavedchanges": "Usted tiene cambios no guardados." "unsavedchanges": "Usted tiene cambios no guardados."
}, },
"validation": { "validation": {
"invalidemail": "Por favor introduzca una dirección de correo electrónico válida.",
"required": "Este campo es requerido." "required": "Este campo es requerido."
} }
}, },
@@ -301,6 +305,8 @@
"owners": "propietarios", "owners": "propietarios",
"schedule": "Programar", "schedule": "Programar",
"shop": "Mi tienda", "shop": "Mi tienda",
"shop_config": "Configuración",
"shop_vendors": "Vendedores",
"vehicles": "Vehículos" "vehicles": "Vehículos"
}, },
"jobsdetail": { "jobsdetail": {
@@ -369,6 +375,26 @@
"save": "Propietario guardado con éxito." "save": "Propietario guardado con éxito."
} }
}, },
"parts": {
"actions": {
"order": "Pedido de piezas"
}
},
"parts_orders": {
"errors": {
"creating": "Se encontró un error al crear el pedido de piezas."
},
"fields": {
"deliver_by": "Entregado por"
},
"labels": {
"email": "Enviar por correo electrónico",
"print": "Mostrar formulario impreso"
},
"successes": {
"created": "Pedido de piezas creado con éxito."
}
},
"profile": { "profile": {
"errors": { "errors": {
"state": "Error al leer el estado de la página. Porfavor refresca." "state": "Error al leer el estado de la página. Porfavor refresca."
@@ -385,6 +411,7 @@
"profile": "Mi perfil | $t(titles.app)", "profile": "Mi perfil | $t(titles.app)",
"schedule": "Horario | $t(titles.app)", "schedule": "Horario | $t(titles.app)",
"shop": "Mi tienda | $t(titles.app)", "shop": "Mi tienda | $t(titles.app)",
"shop_vendors": "Vendedores | $t(titles.app)",
"vehicledetail": "Detalles del vehículo {{vehicle}} | $t(titles.app)", "vehicledetail": "Detalles del vehículo {{vehicle}} | $t(titles.app)",
"vehicles": "Todos los vehiculos | $t(titles.app)" "vehicles": "Todos los vehiculos | $t(titles.app)"
}, },
@@ -430,6 +457,41 @@
"successes": { "successes": {
"save": "Vehículo guardado con éxito." "save": "Vehículo guardado con éxito."
} }
},
"vendors": {
"actions": {
"new": "Nuevo vendedor"
},
"errors": {
"deleting": "Se encontró un error al eliminar el proveedor.",
"saving": "Se encontró un error al guardar el proveedor."
},
"fields": {
"city": "ciudad",
"cost_center": "Centro de costos",
"country": "País",
"discount": "% De descuento",
"display_name": "Nombre para mostrar",
"due_date": "Fecha de vencimiento del pago",
"email": "Email de contacto",
"favorite": "¿Favorito?",
"name": "Nombre del vendedor",
"prompt_discount": "Descuento pronto",
"state": "Provincia del estado",
"street1": "calle",
"street2": "Dirección 2",
"taxid": "Identificación del impuesto",
"terms": "Términos de pago",
"zip": "código postal"
},
"labels": {
"noneselected": "Ningún vendedor está seleccionado.",
"search": "Escriba el nombre de un proveedor"
},
"successes": {
"deleted": "Proveedor eliminado correctamente.",
"saved": "Proveedor guardado con éxito."
}
} }
} }
} }

View File

@@ -106,9 +106,12 @@
"in": "dans", "in": "dans",
"loading": "Chargement...", "loading": "Chargement...",
"loadingapp": "Chargement de Bodyshop.app", "loadingapp": "Chargement de Bodyshop.app",
"loadingshop": "Chargement des données de la boutique ...",
"loggingin": "Vous connecter ...",
"na": "N / A", "na": "N / A",
"out": "En dehors", "out": "En dehors",
"save": "sauvegarder", "save": "sauvegarder",
"search": "Chercher...",
"unknown": "Inconnu" "unknown": "Inconnu"
}, },
"languages": { "languages": {
@@ -120,6 +123,7 @@
"unsavedchanges": "Vous avez des changements non enregistrés." "unsavedchanges": "Vous avez des changements non enregistrés."
}, },
"validation": { "validation": {
"invalidemail": "S'il vous plaît entrer un email valide.",
"required": "Ce champ est requis." "required": "Ce champ est requis."
} }
}, },
@@ -301,6 +305,8 @@
"owners": "Propriétaires", "owners": "Propriétaires",
"schedule": "Programme", "schedule": "Programme",
"shop": "Mon magasin", "shop": "Mon magasin",
"shop_config": "Configuration",
"shop_vendors": "Vendeurs",
"vehicles": "Véhicules" "vehicles": "Véhicules"
}, },
"jobsdetail": { "jobsdetail": {
@@ -369,6 +375,26 @@
"save": "Le propriétaire a bien enregistré." "save": "Le propriétaire a bien enregistré."
} }
}, },
"parts": {
"actions": {
"order": "Commander des pièces"
}
},
"parts_orders": {
"errors": {
"creating": "Erreur rencontrée lors de la création de la commande de pièces."
},
"fields": {
"deliver_by": "Livrer par"
},
"labels": {
"email": "Envoyé par email",
"print": "Afficher le formulaire imprimé"
},
"successes": {
"created": "Commande de pièces créée avec succès."
}
},
"profile": { "profile": {
"errors": { "errors": {
"state": "Erreur lors de la lecture de l'état de la page. Rafraichissez, s'il vous plait." "state": "Erreur lors de la lecture de l'état de la page. Rafraichissez, s'il vous plait."
@@ -385,6 +411,7 @@
"profile": "Mon profil | $t(titles.app)", "profile": "Mon profil | $t(titles.app)",
"schedule": "Horaire | $t(titles.app)", "schedule": "Horaire | $t(titles.app)",
"shop": "Mon magasin | $t(titles.app)", "shop": "Mon magasin | $t(titles.app)",
"shop_vendors": "Vendeurs | $t(titles.app)",
"vehicledetail": "Détails du véhicule {{vehicle} | $t(titles.app)", "vehicledetail": "Détails du véhicule {{vehicle} | $t(titles.app)",
"vehicles": "Tous les véhicules | $t(titles.app)" "vehicles": "Tous les véhicules | $t(titles.app)"
}, },
@@ -430,6 +457,41 @@
"successes": { "successes": {
"save": "Le véhicule a été enregistré avec succès." "save": "Le véhicule a été enregistré avec succès."
} }
},
"vendors": {
"actions": {
"new": "Nouveau vendeur"
},
"errors": {
"deleting": "Erreur rencontrée lors de la suppression du fournisseur.",
"saving": "Erreur rencontrée lors de l'enregistrement du fournisseur."
},
"fields": {
"city": "Ville",
"cost_center": "Centre de coûts",
"country": "Pays",
"discount": "Remise %",
"display_name": "Afficher un nom",
"due_date": "Date limite de paiement",
"email": "Email du contact",
"favorite": "Préféré?",
"name": "Nom du vendeur",
"prompt_discount": "Remise rapide%",
"state": "Etat / Province",
"street1": "rue",
"street2": "Adresse 2 ",
"taxid": "Identifiant de taxe",
"terms": "Modalités de paiement",
"zip": "Zip / code postal"
},
"labels": {
"noneselected": "Aucun fournisseur n'est sélectionné.",
"search": "Tapez le nom d'un vendeur"
},
"successes": {
"deleted": "Le fournisseur a bien été supprimé.",
"saved": "Le fournisseur a bien enregistré."
}
} }
} }
} }

View File

@@ -4,6 +4,7 @@ export function alphaSort(a, b) {
A = a ? a.toLowerCase() : ""; A = a ? a.toLowerCase() : "";
B = b ? b.toLowerCase() : ""; B = b ? b.toLowerCase() : "";
console.log("Objects", A, B, A < B, A > B);
if (A < B) if (A < B)
//sort string ascending //sort string ascending

View File

@@ -967,6 +967,18 @@
lodash "^4.17.13" lodash "^4.17.13"
to-fast-properties "^2.0.0" to-fast-properties "^2.0.0"
"@ckeditor/ckeditor5-build-classic@^16.0.0":
version "16.0.0"
resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-build-classic/-/ckeditor5-build-classic-16.0.0.tgz#9141e94ea6765eda4925aaf062448507410bbe70"
integrity sha512-gBfZqWg3hmCvhq6/wX5UJp4qwl6gB+NJPpOkya2Y3jWKA0HKf3UdlhIvaHq7dRaqhi4unmqaJZVEk5VpZ1vDOg==
"@ckeditor/ckeditor5-react@^2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-react/-/ckeditor5-react-2.1.0.tgz#f612546a5a328899a436d71b72ffd632600049e8"
integrity sha512-rlHjRKhwP9tNK0Yj2UJShL14mRfxLPgJ+Pv6zTv/Mvmd4wrwGnJf+5ybOAGK92S02hP1cXH+9km+PRO1b4V+ng==
dependencies:
prop-types "^15.6.1"
"@cnakazawa/watch@^1.0.3": "@cnakazawa/watch@^1.0.3":
version "1.0.3" version "1.0.3"
resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.3.tgz#099139eaec7ebf07a27c1786a3ff64f39464d2ef" resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.3.tgz#099139eaec7ebf07a27c1786a3ff64f39464d2ef"
@@ -1668,6 +1680,14 @@
dependencies: dependencies:
"@babel/types" "^7.3.0" "@babel/types" "^7.3.0"
"@types/body-parser@*":
version "1.19.0"
resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.0.tgz#0685b3c47eb3006ffed117cdd55164b61f80538f"
integrity sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==
dependencies:
"@types/connect" "*"
"@types/node" "*"
"@types/bytebuffer@^5.0.40": "@types/bytebuffer@^5.0.40":
version "5.0.40" version "5.0.40"
resolved "https://registry.yarnpkg.com/@types/bytebuffer/-/bytebuffer-5.0.40.tgz#d6faac40dcfb09cd856cdc4c01d3690ba536d3ee" resolved "https://registry.yarnpkg.com/@types/bytebuffer/-/bytebuffer-5.0.40.tgz#d6faac40dcfb09cd856cdc4c01d3690ba536d3ee"
@@ -1681,6 +1701,13 @@
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
"@types/connect@*":
version "3.4.33"
resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.33.tgz#31610c901eca573b8713c3330abc6e6b9f588546"
integrity sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==
dependencies:
"@types/node" "*"
"@types/eslint-visitor-keys@^1.0.0": "@types/eslint-visitor-keys@^1.0.0":
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
@@ -1691,6 +1718,23 @@
resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==
"@types/express-serve-static-core@*":
version "4.17.2"
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.2.tgz#f6f41fa35d42e79dbf6610eccbb2637e6008a0cf"
integrity sha512-El9yMpctM6tORDAiBwZVLMcxoTMcqqRO9dVyYcn7ycLWbvR8klrDn8CAOwRfZujZtWD7yS/mshTdz43jMOejbg==
dependencies:
"@types/node" "*"
"@types/range-parser" "*"
"@types/express@^4.17.2":
version "4.17.2"
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.2.tgz#a0fb7a23d8855bac31bc01d5a58cadd9b2173e6c"
integrity sha512-5mHFNyavtLoJmnusB8OKJ5bshSzw+qkMIBAobLrIM48HJvunFva9mOa6aBwh64lBFyNwBbs0xiEFuj4eU/NjCA==
dependencies:
"@types/body-parser" "*"
"@types/express-serve-static-core" "*"
"@types/serve-static" "*"
"@types/glob@^7.1.1": "@types/glob@^7.1.1":
version "7.1.1" version "7.1.1"
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575"
@@ -1730,6 +1774,11 @@
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9"
integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w== integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==
"@types/mime@*":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d"
integrity sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==
"@types/minimatch@*": "@types/minimatch@*":
version "3.0.3" version "3.0.3"
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
@@ -1760,6 +1809,11 @@
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/range-parser@*":
version "1.2.3"
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c"
integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==
"@types/react-slick@^0.23.4": "@types/react-slick@^0.23.4":
version "0.23.4" version "0.23.4"
resolved "https://registry.yarnpkg.com/@types/react-slick/-/react-slick-0.23.4.tgz#c97e2a9e7e3d1933c68593b8e82752fab1e8ce53" resolved "https://registry.yarnpkg.com/@types/react-slick/-/react-slick-0.23.4.tgz#c97e2a9e7e3d1933c68593b8e82752fab1e8ce53"
@@ -1775,6 +1829,14 @@
"@types/prop-types" "*" "@types/prop-types" "*"
csstype "^2.2.0" csstype "^2.2.0"
"@types/serve-static@*":
version "1.13.3"
resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.3.tgz#eb7e1c41c4468272557e897e9171ded5e2ded9d1"
integrity sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g==
dependencies:
"@types/express-serve-static-core" "*"
"@types/mime" "*"
"@types/stack-utils@^1.0.1": "@types/stack-utils@^1.0.1":
version "1.0.1" version "1.0.1"
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"
@@ -2518,7 +2580,7 @@ arrify@^1.0.1:
resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=
asap@~2.0.3, asap@~2.0.6: asap@^2.0.0, asap@~2.0.3, asap@~2.0.6:
version "2.0.6" version "2.0.6"
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
@@ -3051,6 +3113,11 @@ bser@2.1.1:
dependencies: dependencies:
node-int64 "^0.4.0" node-int64 "^0.4.0"
buffer-equal-constant-time@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=
buffer-from@^1.0.0: buffer-from@^1.0.0:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
@@ -4148,6 +4215,11 @@ date-arithmetic@^4.0.1:
resolved "https://registry.yarnpkg.com/date-arithmetic/-/date-arithmetic-4.1.0.tgz#e5d6434e9deb71f79760a37b729e4a515e730ddf" resolved "https://registry.yarnpkg.com/date-arithmetic/-/date-arithmetic-4.1.0.tgz#e5d6434e9deb71f79760a37b729e4a515e730ddf"
integrity sha512-QWxYLR5P/6GStZcdem+V1xoto6DMadYWpMXU82ES3/RfR3Wdwr3D0+be7mgOJ+Ov0G9D5Dmb9T17sNLQYj9XOg== integrity sha512-QWxYLR5P/6GStZcdem+V1xoto6DMadYWpMXU82ES3/RfR3Wdwr3D0+be7mgOJ+Ov0G9D5Dmb9T17sNLQYj9XOg==
dayjs@^1.8.20:
version "1.8.20"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.8.20.tgz#724a5cb6ad1f6fc066b0bd9a800dedcc7886f19e"
integrity sha512-mH0MCDxw6UCGJYxVN78h8ugWycZAO8thkj3bW6vApL5tS0hQplIDdAQcmbvl7n35H0AKdCJQaArTrIQw2xt4Qg==
debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9: debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9:
version "2.6.9" version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@@ -4535,6 +4607,13 @@ ecc-jsbn@~0.1.1:
jsbn "~0.1.0" jsbn "~0.1.0"
safer-buffer "^2.1.0" safer-buffer "^2.1.0"
ecdsa-sig-formatter@1.0.11:
version "1.0.11"
resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
dependencies:
safe-buffer "^5.0.1"
ee-first@1.1.1: ee-first@1.1.1:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
@@ -5798,7 +5877,7 @@ har-schema@^2.0.0:
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=
har-validator@~5.1.0: har-validator@~5.1.0, har-validator@~5.1.3:
version "5.1.3" version "5.1.3"
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080"
integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==
@@ -7320,6 +7399,22 @@ jsonify@~0.0.0:
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=
jsonwebtoken@^8.5.1:
version "8.5.1"
resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d"
integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==
dependencies:
jws "^3.2.2"
lodash.includes "^4.3.0"
lodash.isboolean "^3.0.3"
lodash.isinteger "^4.0.4"
lodash.isnumber "^3.0.3"
lodash.isplainobject "^4.0.6"
lodash.isstring "^4.0.1"
lodash.once "^4.0.0"
ms "^2.1.1"
semver "^5.6.0"
jsprim@^1.2.2: jsprim@^1.2.2:
version "1.4.1" version "1.4.1"
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
@@ -7338,6 +7433,23 @@ jsx-ast-utils@^2.2.1, jsx-ast-utils@^2.2.3:
array-includes "^3.0.3" array-includes "^3.0.3"
object.assign "^4.1.0" object.assign "^4.1.0"
jwa@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==
dependencies:
buffer-equal-constant-time "1.0.1"
ecdsa-sig-formatter "1.0.11"
safe-buffer "^5.0.1"
jws@^3.2.2:
version "3.2.2"
resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
dependencies:
jwa "^1.4.1"
safe-buffer "^5.0.1"
killable@^1.0.1: killable@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892" resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892"
@@ -7552,16 +7664,51 @@ lodash.flattendeep@^4.4.0:
resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2"
integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI= integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=
lodash.includes@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=
lodash.isboolean@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=
lodash.isequal@^4.5.0: lodash.isequal@^4.5.0:
version "4.5.0" version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA=
lodash.isinteger@^4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=
lodash.isnumber@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=
lodash.isplainobject@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=
lodash.isstring@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=
lodash.memoize@^4.1.2: lodash.memoize@^4.1.2:
version "4.1.2" version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
lodash.once@^4.0.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
lodash.sortby@^4.7.0: lodash.sortby@^4.7.0:
version "4.7.0" version "4.7.0"
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
@@ -8980,6 +9127,11 @@ pnp-webpack-plugin@1.6.0:
dependencies: dependencies:
ts-pnp "^1.1.2" ts-pnp "^1.1.2"
pop-iterate@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/pop-iterate/-/pop-iterate-1.0.1.tgz#ceacfdab4abf353d7a0f2aaa2c1fc7b3f9413ba3"
integrity sha1-zqz9q0q/NT16DyqqLB/Hs/lBO6M=
popper.js@^1.15.0: popper.js@^1.15.0:
version "1.16.1" version "1.16.1"
resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b" resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b"
@@ -9755,7 +9907,7 @@ prop-types-exact@^1.2.0:
object.assign "^4.1.0" object.assign "^4.1.0"
reflect.ownkeys "^0.2.0" reflect.ownkeys "^0.2.0"
prop-types@15.x, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.5.9, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2: prop-types@15.x, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.5.9, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
version "15.7.2" version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@@ -9868,6 +10020,15 @@ punycode@^2.1.0, punycode@^2.1.1:
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
q@2.0.x:
version "2.0.3"
resolved "https://registry.yarnpkg.com/q/-/q-2.0.3.tgz#75b8db0255a1a5af82f58c3f3aaa1efec7d0d134"
integrity sha1-dbjbAlWhpa+C9Yw/Oqoe/sfQ0TQ=
dependencies:
asap "^2.0.0"
pop-iterate "^1.0.1"
weak-map "^1.0.5"
q@^1.1.2: q@^1.1.2:
version "1.5.1" version "1.5.1"
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
@@ -10499,6 +10660,13 @@ react-error-overlay@^6.0.5:
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.5.tgz#55d59c2a3810e8b41922e0b4e5f85dcf239bd533" resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.5.tgz#55d59c2a3810e8b41922e0b4e5f85dcf239bd533"
integrity sha512-+DMR2k5c6BqMDSMF8hLH0vYKtKTeikiFW+fj0LClN+XZg4N9b8QUAdHC62CGWNLTi/gnuuemNcNcTFrCvK1f+A== integrity sha512-+DMR2k5c6BqMDSMF8hLH0vYKtKTeikiFW+fj0LClN+XZg4N9b8QUAdHC62CGWNLTi/gnuuemNcNcTFrCvK1f+A==
react-html-email@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/react-html-email/-/react-html-email-3.0.0.tgz#a5a51b78271a33daf9b14638e9e4c5df93f691aa"
integrity sha512-eIBmZjqoS1SLAOi/QYTHLnZSmBM5AhP/cScJGhVDHlBJFtMyiR4qBUQYQDCqRtNPDKHWGMbbS/+XHSgD+C2hng==
dependencies:
prop-types "^15.5.10"
react-i18next@^11.3.1: react-i18next@^11.3.1:
version "11.3.1" version "11.3.1"
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.3.1.tgz#9269282c3f566015f0bdf8fdbf46782bbe50f5a7" resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.3.1.tgz#9269282c3f566015f0bdf8fdbf46782bbe50f5a7"
@@ -11002,6 +11170,32 @@ request@^2.87.0, request@^2.88.0:
tunnel-agent "^0.6.0" tunnel-agent "^0.6.0"
uuid "^3.3.2" uuid "^3.3.2"
request@^2.88.2:
version "2.88.2"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
dependencies:
aws-sign2 "~0.7.0"
aws4 "^1.8.0"
caseless "~0.12.0"
combined-stream "~1.0.6"
extend "~3.0.2"
forever-agent "~0.6.1"
form-data "~2.3.2"
har-validator "~5.1.3"
http-signature "~1.2.0"
is-typedarray "~1.0.0"
isstream "~0.1.2"
json-stringify-safe "~5.0.1"
mime-types "~2.1.19"
oauth-sign "~0.9.0"
performance-now "^2.1.0"
qs "~6.5.2"
safe-buffer "^5.1.2"
tough-cookie "~2.5.0"
tunnel-agent "^0.6.0"
uuid "^3.3.2"
require-directory@^2.1.1: require-directory@^2.1.1:
version "2.1.1" version "2.1.1"
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
@@ -11165,6 +11359,11 @@ rmc-feedback@^2.0.0:
babel-runtime "6.x" babel-runtime "6.x"
classnames "^2.2.5" classnames "^2.2.5"
rootpath@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/rootpath/-/rootpath-0.1.2.tgz#5b379a87dca906e9b91d690a599439bef267ea6b"
integrity sha1-Wzeah9ypBum5HWkKWZQ5vvJn6ms=
rst-selector-parser@^2.2.3: rst-selector-parser@^2.2.3:
version "2.2.3" version "2.2.3"
resolved "https://registry.yarnpkg.com/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz#81b230ea2fcc6066c89e3472de794285d9b03d91" resolved "https://registry.yarnpkg.com/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz#81b230ea2fcc6066c89e3472de794285d9b03d91"
@@ -11196,8 +11395,6 @@ rxjs@^6.5.3:
version "6.5.4" version "6.5.4"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.4.tgz#e0777fe0d184cec7872df147f303572d414e211c" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.4.tgz#e0777fe0d184cec7872df147f303572d414e211c"
integrity sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q== integrity sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==
dependencies:
tslib "^1.9.0"
safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2" version "5.1.2"
@@ -11299,6 +11496,11 @@ schema-utils@^2.5.0, schema-utils@^2.6.0, schema-utils@^2.6.1, schema-utils@^2.6
ajv "^6.10.2" ajv "^6.10.2"
ajv-keywords "^3.4.1" ajv-keywords "^3.4.1"
scmp@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/scmp/-/scmp-2.1.0.tgz#37b8e197c425bdeb570ab91cc356b311a11f9c9a"
integrity sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q==
scss-tokenizer@^0.2.3: scss-tokenizer@^0.2.3:
version "0.2.3" version "0.2.3"
resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1" resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1"
@@ -12275,7 +12477,7 @@ toidentifier@1.0.0:
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
tough-cookie@^2.3.3, tough-cookie@^2.3.4, tough-cookie@^2.5.0: tough-cookie@^2.3.3, tough-cookie@^2.3.4, tough-cookie@^2.5.0, tough-cookie@~2.5.0:
version "2.5.0" version "2.5.0"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==
@@ -12351,6 +12553,22 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0:
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=
twilio@^3.39.5:
version "3.39.5"
resolved "https://registry.yarnpkg.com/twilio/-/twilio-3.39.5.tgz#1512ae1de83441609acf7328a75442c439e6c3af"
integrity sha512-IuiHFRuq7e+rXMNRDqwXo16ITefPsPh1reI/rRKFVbvRNRY88LnnN5Qdjyw90UWqWxqMwGqYXt253tAvt1pI3g==
dependencies:
"@types/express" "^4.17.2"
dayjs "^1.8.20"
jsonwebtoken "^8.5.1"
lodash "^4.17.15"
q "2.0.x"
request "^2.88.2"
rootpath "^0.1.2"
scmp "^2.1.0"
url-parse "^1.4.7"
xmlbuilder "^13.0.2"
type-check@~0.3.2: type-check@~0.3.2:
version "0.3.2" version "0.3.2"
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72"
@@ -12531,7 +12749,7 @@ url-loader@2.3.0:
mime "^2.4.4" mime "^2.4.4"
schema-utils "^2.5.0" schema-utils "^2.5.0"
url-parse@^1.4.3: url-parse@^1.4.3, url-parse@^1.4.7:
version "1.4.7" version "1.4.7"
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278"
integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg== integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==
@@ -12697,6 +12915,11 @@ wbuf@^1.1.0, wbuf@^1.7.3:
dependencies: dependencies:
minimalistic-assert "^1.0.0" minimalistic-assert "^1.0.0"
weak-map@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/weak-map/-/weak-map-1.0.5.tgz#79691584d98607f5070bd3b70a40e6bb22e401eb"
integrity sha1-eWkVhNmGB/UHC9O3CkDmuyLkAes=
webidl-conversions@^4.0.2: webidl-conversions@^4.0.2:
version "4.0.2" version "4.0.2"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
@@ -13108,6 +13331,11 @@ xml-name-validator@^3.0.0:
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==
xmlbuilder@^13.0.2:
version "13.0.2"
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-13.0.2.tgz#02ae33614b6a047d1c32b5389c1fdacb2bce47a7"
integrity sha512-Eux0i2QdDYKbdbA6AM6xE4m6ZTZr4G4xF9kahI2ukSEMCzwce2eX9WlTI5J3s+NU7hpasFsr8hWIONae7LluAQ==
xmlchars@^2.1.1: xmlchars@^2.1.1:
version "2.2.0" version "2.2.0"
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"

View File

@@ -2,7 +2,7 @@ const functions = require("firebase-functions");
const admin = require("firebase-admin"); const admin = require("firebase-admin");
admin.initializeApp(functions.config().firebase); admin.initializeApp(functions.config().firebase);
//Todo: Move this to an environment parameter. //TODO Move this to an environment parameter.
const GRAPHQL_ENDPOINT = functions.config().auth.graphql_endpoint; const GRAPHQL_ENDPOINT = functions.config().auth.graphql_endpoint;
const HASURA_SECRET_ADMIN_KEY = functions.config().auth.hasura_secret_admin_key; const HASURA_SECRET_ADMIN_KEY = functions.config().auth.hasura_secret_admin_key;
const UPSERT_USER = ` const UPSERT_USER = `

View File

@@ -0,0 +1,3 @@
- args:
sql: ALTER TABLE "public"."vendors" DROP COLUMN "cost_center";
type: run_sql

View File

@@ -0,0 +1,3 @@
- args:
sql: ALTER TABLE "public"."vendors" ADD COLUMN "cost_center" text NOT NULL;
type: run_sql

View File

@@ -0,0 +1,3 @@
- args:
sql: ALTER TABLE "public"."vendors" DROP COLUMN "favorite";
type: run_sql

View File

@@ -0,0 +1,4 @@
- args:
sql: ALTER TABLE "public"."vendors" ADD COLUMN "favorite" boolean NOT NULL DEFAULT
false;
type: run_sql

View File

@@ -0,0 +1,45 @@
- args:
role: user
table:
name: vendors
schema: public
type: drop_insert_permission
- args:
permission:
check:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- due_date
- discount
- prompt_discount
- city
- country
- display_name
- email
- name
- state
- street1
- street2
- taxid
- terms
- zip
- created_at
- updated_at
- bodyshopid
- id
localPresets:
- key: ""
value: ""
set: {}
role: user
table:
name: vendors
schema: public
type: create_insert_permission

View File

@@ -0,0 +1,47 @@
- args:
role: user
table:
name: vendors
schema: public
type: drop_insert_permission
- args:
permission:
check:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- id
- created_at
- updated_at
- bodyshopid
- name
- street1
- street2
- city
- state
- zip
- country
- email
- taxid
- discount
- prompt_discount
- due_date
- terms
- display_name
- cost_center
- favorite
localPresets:
- key: ""
value: ""
set: {}
role: user
table:
name: vendors
schema: public
type: create_insert_permission

View File

@@ -0,0 +1,43 @@
- args:
role: user
table:
name: vendors
schema: public
type: drop_select_permission
- args:
permission:
allow_aggregations: false
columns:
- due_date
- discount
- prompt_discount
- city
- country
- display_name
- email
- name
- state
- street1
- street2
- taxid
- terms
- zip
- created_at
- updated_at
- bodyshopid
- id
computed_fields: []
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
role: user
table:
name: vendors
schema: public
type: create_select_permission

View File

@@ -0,0 +1,45 @@
- args:
role: user
table:
name: vendors
schema: public
type: drop_select_permission
- args:
permission:
allow_aggregations: false
columns:
- favorite
- due_date
- discount
- prompt_discount
- city
- cost_center
- country
- display_name
- email
- name
- state
- street1
- street2
- taxid
- terms
- zip
- created_at
- updated_at
- bodyshopid
- id
computed_fields: []
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
role: user
table:
name: vendors
schema: public
type: create_select_permission

View File

@@ -0,0 +1,45 @@
- args:
role: user
table:
name: vendors
schema: public
type: drop_update_permission
- args:
permission:
columns:
- due_date
- discount
- prompt_discount
- city
- country
- display_name
- email
- name
- state
- street1
- street2
- taxid
- terms
- zip
- created_at
- updated_at
- bodyshopid
- id
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
localPresets:
- key: ""
value: ""
set: {}
role: user
table:
name: vendors
schema: public
type: create_update_permission

View File

@@ -0,0 +1,47 @@
- args:
role: user
table:
name: vendors
schema: public
type: drop_update_permission
- args:
permission:
columns:
- favorite
- due_date
- discount
- prompt_discount
- city
- cost_center
- country
- display_name
- email
- name
- state
- street1
- street2
- taxid
- terms
- zip
- created_at
- updated_at
- bodyshopid
- id
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
localPresets:
- key: ""
value: ""
set: {}
role: user
table:
name: vendors
schema: public
type: create_update_permission

View File

@@ -0,0 +1,9 @@
- args:
sql: ALTER TABLE "public"."vendors" ALTER COLUMN "favorite" TYPE boolean;
type: run_sql
- args:
sql: ALTER TABLE "public"."vendors" ALTER COLUMN "favorite" SET NOT NULL;
type: run_sql
- args:
sql: COMMENT ON COLUMN "public"."vendors"."favorite" IS E'null'
type: run_sql

View File

@@ -0,0 +1,9 @@
- args:
sql: ALTER TABLE "public"."vendors" ALTER COLUMN "favorite" TYPE bool;
type: run_sql
- args:
sql: ALTER TABLE "public"."vendors" ALTER COLUMN "favorite" DROP NOT NULL;
type: run_sql
- args:
sql: COMMENT ON COLUMN "public"."vendors"."favorite" IS E''
type: run_sql

View File

@@ -0,0 +1,47 @@
- args:
role: user
table:
name: vendors
schema: public
type: drop_insert_permission
- args:
permission:
check:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- id
- created_at
- updated_at
- bodyshopid
- name
- street1
- street2
- city
- state
- zip
- country
- email
- taxid
- discount
- prompt_discount
- due_date
- terms
- display_name
- cost_center
- favorite
localPresets:
- key: ""
value: ""
set: {}
role: user
table:
name: vendors
schema: public
type: create_insert_permission

View File

@@ -0,0 +1,46 @@
- args:
role: user
table:
name: vendors
schema: public
type: drop_insert_permission
- args:
permission:
check:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- bodyshopid
- city
- cost_center
- country
- created_at
- discount
- display_name
- due_date
- email
- id
- name
- prompt_discount
- state
- street1
- street2
- taxid
- terms
- updated_at
- zip
localPresets:
- key: ""
value: ""
set: {}
role: user
table:
name: vendors
schema: public
type: create_insert_permission

View File

@@ -0,0 +1,45 @@
- args:
role: user
table:
name: vendors
schema: public
type: drop_select_permission
- args:
permission:
allow_aggregations: false
columns:
- favorite
- due_date
- discount
- prompt_discount
- city
- cost_center
- country
- display_name
- email
- name
- state
- street1
- street2
- taxid
- terms
- zip
- created_at
- updated_at
- bodyshopid
- id
computed_fields: []
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
role: user
table:
name: vendors
schema: public
type: create_select_permission

View File

@@ -0,0 +1,44 @@
- args:
role: user
table:
name: vendors
schema: public
type: drop_select_permission
- args:
permission:
allow_aggregations: false
columns:
- bodyshopid
- city
- cost_center
- country
- created_at
- discount
- display_name
- due_date
- email
- id
- name
- prompt_discount
- state
- street1
- street2
- taxid
- terms
- updated_at
- zip
computed_fields: []
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
role: user
table:
name: vendors
schema: public
type: create_select_permission

View File

@@ -0,0 +1,47 @@
- args:
role: user
table:
name: vendors
schema: public
type: drop_update_permission
- args:
permission:
columns:
- favorite
- due_date
- discount
- prompt_discount
- city
- cost_center
- country
- display_name
- email
- name
- state
- street1
- street2
- taxid
- terms
- zip
- created_at
- updated_at
- bodyshopid
- id
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
localPresets:
- key: ""
value: ""
set: {}
role: user
table:
name: vendors
schema: public
type: create_update_permission

View File

@@ -0,0 +1,46 @@
- args:
role: user
table:
name: vendors
schema: public
type: drop_update_permission
- args:
permission:
columns:
- bodyshopid
- city
- cost_center
- country
- created_at
- discount
- display_name
- due_date
- email
- id
- name
- prompt_discount
- state
- street1
- street2
- taxid
- terms
- updated_at
- zip
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
localPresets:
- key: ""
value: ""
set: {}
role: user
table:
name: vendors
schema: public
type: create_update_permission

View File

@@ -0,0 +1,9 @@
- args:
sql: ALTER TABLE "public"."vendors" ADD COLUMN "favorite" bool
type: run_sql
- args:
sql: ALTER TABLE "public"."vendors" ALTER COLUMN "favorite" DROP NOT NULL
type: run_sql
- args:
sql: ALTER TABLE "public"."vendors" ALTER COLUMN "favorite" SET DEFAULT false
type: run_sql

View File

@@ -0,0 +1,3 @@
- args:
sql: ALTER TABLE "public"."vendors" DROP COLUMN "favorite" CASCADE
type: run_sql

View File

@@ -0,0 +1,3 @@
- args:
sql: ALTER TABLE "public"."vendors" DROP COLUMN "favorite";
type: run_sql

View File

@@ -0,0 +1,3 @@
- args:
sql: ALTER TABLE "public"."vendors" ADD COLUMN "favorite" jsonb NULL;
type: run_sql

View File

@@ -0,0 +1,46 @@
- args:
role: user
table:
name: vendors
schema: public
type: drop_insert_permission
- args:
permission:
check:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- bodyshopid
- city
- cost_center
- country
- created_at
- discount
- display_name
- due_date
- email
- id
- name
- prompt_discount
- state
- street1
- street2
- taxid
- terms
- updated_at
- zip
localPresets:
- key: ""
value: ""
set: {}
role: user
table:
name: vendors
schema: public
type: create_insert_permission

View File

@@ -0,0 +1,47 @@
- args:
role: user
table:
name: vendors
schema: public
type: drop_insert_permission
- args:
permission:
check:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- due_date
- favorite
- discount
- prompt_discount
- city
- cost_center
- country
- display_name
- email
- name
- state
- street1
- street2
- taxid
- terms
- zip
- created_at
- updated_at
- bodyshopid
- id
localPresets:
- key: ""
value: ""
set: {}
role: user
table:
name: vendors
schema: public
type: create_insert_permission

View File

@@ -0,0 +1,44 @@
- args:
role: user
table:
name: vendors
schema: public
type: drop_select_permission
- args:
permission:
allow_aggregations: false
columns:
- bodyshopid
- city
- cost_center
- country
- created_at
- discount
- display_name
- due_date
- email
- id
- name
- prompt_discount
- state
- street1
- street2
- taxid
- terms
- updated_at
- zip
computed_fields: []
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
role: user
table:
name: vendors
schema: public
type: create_select_permission

View File

@@ -0,0 +1,45 @@
- args:
role: user
table:
name: vendors
schema: public
type: drop_select_permission
- args:
permission:
allow_aggregations: false
columns:
- due_date
- favorite
- discount
- prompt_discount
- city
- cost_center
- country
- display_name
- email
- name
- state
- street1
- street2
- taxid
- terms
- zip
- created_at
- updated_at
- bodyshopid
- id
computed_fields: []
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
role: user
table:
name: vendors
schema: public
type: create_select_permission

View File

@@ -0,0 +1,46 @@
- args:
role: user
table:
name: vendors
schema: public
type: drop_update_permission
- args:
permission:
columns:
- bodyshopid
- city
- cost_center
- country
- created_at
- discount
- display_name
- due_date
- email
- id
- name
- prompt_discount
- state
- street1
- street2
- taxid
- terms
- updated_at
- zip
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
localPresets:
- key: ""
value: ""
set: {}
role: user
table:
name: vendors
schema: public
type: create_update_permission

View File

@@ -0,0 +1,47 @@
- args:
role: user
table:
name: vendors
schema: public
type: drop_update_permission
- args:
permission:
columns:
- due_date
- favorite
- discount
- prompt_discount
- city
- cost_center
- country
- display_name
- email
- name
- state
- street1
- street2
- taxid
- terms
- zip
- created_at
- updated_at
- bodyshopid
- id
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
localPresets:
- key: ""
value: ""
set: {}
role: user
table:
name: vendors
schema: public
type: create_update_permission

View File

@@ -0,0 +1,46 @@
- args:
role: user
table:
name: vendors
schema: public
type: drop_insert_permission
- args:
permission:
check:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- bodyshopid
- city
- cost_center
- country
- created_at
- discount
- display_name
- due_date
- email
- id
- name
- prompt_discount
- state
- street1
- street2
- taxid
- terms
- updated_at
- zip
localPresets:
- key: ""
value: ""
set: {}
role: user
table:
name: vendors
schema: public
type: create_insert_permission

View File

@@ -0,0 +1,47 @@
- args:
role: user
table:
name: vendors
schema: public
type: drop_insert_permission
- args:
permission:
check:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- due_date
- favorite
- discount
- prompt_discount
- city
- cost_center
- country
- display_name
- email
- name
- state
- street1
- street2
- taxid
- terms
- zip
- created_at
- updated_at
- bodyshopid
- id
localPresets:
- key: ""
value: ""
set: {}
role: user
table:
name: vendors
schema: public
type: create_insert_permission

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