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:
```json
{
"statuses": [
"Open",
"Scheduled",
@@ -21,14 +22,29 @@
"Invoiced",
"Exported"
],
"default_imported": "Open",
"default_scheduled": "Scheduled",
"open_statuses": [
"Open",
"Scheduled",
"Arrived",
"Repair Plan",
"Parts",
"Body",
"Prep",
"Paint",
"Reassembly",
"Sublet",
"Detail",
"Completed"
],
"default_arrived": "Arrived",
"default_exported": "Exported",
"default_imported": "Open",
"default_invoiced": "Invoiced",
"default_completed": "Completed",
"default_delivered": "Delivered",
"default_invoiced": "Invoiced",
"default_exported": "Exported"
"default_scheduled": "Scheduled"
}
```
--\* 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
@@ -1283,6 +1283,48 @@
</translation>
</translations>
</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>
<name>na</name>
<definition_loaded>false</definition_loaded>
@@ -1346,6 +1388,27 @@
</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>
<concept_node>
<name>unknown</name>
<definition_loaded>false</definition_loaded>
@@ -1466,6 +1529,27 @@
<folder_node>
<name>validation</name>
<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>
<name>required</name>
<definition_loaded>false</definition_loaded>
@@ -4823,6 +4907,48 @@
</translation>
</translations>
</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>
<name>vehicles</name>
<definition_loaded>false</definition_loaded>
@@ -5769,6 +5895,167 @@
</folder_node>
</children>
</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>
<name>profile</name>
<children>
@@ -6013,6 +6300,27 @@
</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>
<name>vehicledetail</name>
<definition_loaded>false</definition_loaded>
@@ -6701,6 +7009,519 @@
</folder_node>
</children>
</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>
</folder_node>
</children>

View File

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

View File

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

View File

@@ -2,10 +2,12 @@ import React from "react";
import ReactDOM from "react-dom";
import Alert from "./alert.component";
import { MockedProvider } from "@apollo/react-testing";
import { shallow } from "enzyme";
import { shallow, mount } from "enzyme";
const div = document.createElement("div");
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 { notification } from "antd";
export default function AllocationsAssignmentContainer({ jobLineId, hours }) {
export default function AllocationsAssignmentContainer({
jobLineId,
hours,
refetch
}) {
const visibilityState = useState(false);
const { t } = useTranslation();
const [assignment, setAssignment] = useState({
@@ -20,9 +24,9 @@ export default function AllocationsAssignmentContainer({ jobLineId, hours }) {
notification["success"]({
message: t("employees.successes.save")
});
//TODO: Better way to reset the field decorators?
//TODO Better way to reset the field decorators?
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 }) {
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 (
<div style={{ height: "300px" }}>
<button onClick={() => toggleChatVisible()}>hide</button> This is a chat
window!
</div>
<Card style={{ width: "400px" }}>
<div>
<button onClick={() => toggleChatVisible()}>X</button>
<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 { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
@@ -17,8 +18,20 @@ export default connect(
mapStateToProps,
mapDispatchToProps
)(function ChatWindowContainer({ chatVisible, toggleChatVisible }) {
if (chatVisible)
return <ChatWindowComponent toggleChatVisible={toggleChatVisible} />;
return <div onClick={() => toggleChatVisible()}>Chat</div>;
return (
<Affix offsetBottom={25}>
{chatVisible ? (
<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();
//TODO Add
return (
<Row type="flex" justify="space-around" align="middle">
<Row type='flex' justify='space-around' align='middle'>
{logo ? (
<Col span={4}>
<img alt="Shop Logo" src={logo} style={{ height: "40px" }} />
<img alt='Shop Logo' src={logo} style={{ height: "40px" }} />
</Col>
) : null}
<Col span={14}>
<Menu
theme="dark"
className="header"
theme='dark'
className='header'
selectedKeys={selectedNavItem}
mode="horizontal"
onClick={handleMenuClick}
>
<Menu.Item key="home">
<Link to="/manage">
<Icon type="home" />
mode='horizontal'
onClick={handleMenuClick}>
<Menu.Item key='home'>
<Link to='/manage'>
<Icon type='home' />
{t("menus.header.home")}
</Link>
</Menu.Item>
<Menu.SubMenu title={t("menus.header.jobs")}>
<Menu.Item key="schedule">
<Link to="/manage/schedule">
<Icon type="calendar" />
<Menu.Item key='schedule'>
<Link to='/manage/schedule'>
<Icon type='calendar' />
{t("menus.header.schedule")}
</Link>
</Menu.Item>
<Menu.Item key="activejobs">
<Link to="/manage/jobs">
<Icon type="home" />
{t("menus.header.activejobs")}
</Link>
<Menu.Item key='activejobs'>
<Link to='/manage/jobs'>{t("menus.header.activejobs")}</Link>
</Menu.Item>
<Menu.Item key="availablejobs">
<Link to="/manage/available">
<Icon type="home" />
<Menu.Item key='availablejobs'>
<Link to='/manage/available'>
{t("menus.header.availablejobs")}
</Link>
</Menu.Item>
</Menu.SubMenu>
<Menu.SubMenu title={t("menus.header.customers")}>
<Menu.Item key="owners">
<Link to="/manage/owners">
<Icon type="team" />
<Menu.Item key='owners'>
<Link to='/manage/owners'>
<Icon type='team' />
{t("menus.header.owners")}
</Link>
</Menu.Item>
<Menu.Item key="vehicles">
<Link to="/manage/vehicles">
<Icon type="car" />
<Menu.Item key='vehicles'>
<Link to='/manage/vehicles'>
<Icon type='car' />
{t("menus.header.vehicles")}
</Link>
</Menu.Item>
</Menu.SubMenu>
<Menu.Item key="shop">
<Link to="/manage/shop">
{t("menus.header.shop")}
</Link>
</Menu.Item>
<Menu.SubMenu title={t("menus.header.shop")}>
<Menu.Item key='shop'>
<Link to='/manage/shop'>{t("menus.header.shop_config")}</Link>
</Menu.Item>
<Menu.Item key='shop-vendors'>
<Link to='/manage/shop/vendors'>
{t("menus.header.shop_vendors")}
</Link>
</Menu.Item>
</Menu.SubMenu>
<Menu.SubMenu
title={
<div>
<Avatar
size="medium"
alt="Avatar"
size='medium'
alt='Avatar'
src={currentUser.photoURL ? currentUser.photoURL : UserImage}
style={{ margin: "10px" }}
/>
{currentUser.displayName || t("general.labels.unknown")}
</div>
}
>
}>
<Menu.Item onClick={signOutStart()}>
{t("user.actions.signout")}
</Menu.Item>
<Menu.Item>
<Link to="/manage/profile">{t("menus.currentuser.profile")}</Link>
<Link to='/manage/profile'>{t("menus.currentuser.profile")}</Link>
</Menu.Item>
<Menu.SubMenu
title={
<span>
<Icon type="global" />
<Icon type='global' />
<span>{t("menus.currentuser.languageselector")}</span>
</span>
}
>
<Menu.Item actiontype="lang-select" key="en_us">
}>
<Menu.Item actiontype='lang-select' key='en_us'>
{t("general.languages.english")}
</Menu.Item>
<Menu.Item actiontype="lang-select" key="fr">
<Menu.Item actiontype='lang-select' key='fr'>
{t("general.languages.french")}
</Menu.Item>
<Menu.Item actiontype="lang-select" key="es">
<Menu.Item actiontype='lang-select' key='es'>
{t("general.languages.spanish")}
</Menu.Item>
</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 "./job-detail-cards.styles.scss";
import JobDetailCardsTotalsComponent from "./job-detail-cards.totals.component";
import ScheduleJobModalContainer from "../schedule-job-modal/schedule-job-modal.container";
export default function JobDetailCards({ selectedJob }) {
const { loading, error, data, refetch } = useQuery(QUERY_JOB_CARD_DETAILS, {
@@ -25,29 +26,35 @@ export default function JobDetailCards({ selectedJob }) {
skip: !selectedJob
});
const [noteModalVisible, setNoteModalVisible] = useState(false);
const scheduleModalState = useState(false);
const { t } = useTranslation();
if (!selectedJob) {
return <div>{t("jobs.errors.nojobselected")}</div>;
}
if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />;
if (error) return <AlertComponent message={error.message} type='error' />;
return (
<div className="job-cards-container">
<div className='job-cards-container'>
<NoteUpsertModal
jobId={data.jobs_by_pk.id}
visible={noteModalVisible}
changeVisibility={setNoteModalVisible}
refetch={refetch}
/>
<ScheduleJobModalContainer
scheduleModalState={scheduleModalState}
jobId={data.jobs_by_pk.id}
refetch={refetch}
/>
<PageHeader
ghost={false}
onBack={() => window.history.back()}
tags={
<span key="job-status">
<span key='job-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}
</span>
}
@@ -65,35 +72,40 @@ export default function JobDetailCards({ selectedJob }) {
)
}
extra={[
<Button
key='schedule'
//TODO Enabled logic based on status.
onClick={() => {
scheduleModalState[1](true);
}}>
{t("jobs.actions.schedule")}
</Button>,
<Link
key="documents"
to={`/manage/jobs/${data.jobs_by_pk.id}#documents`}
>
key='documents'
to={`/manage/jobs/${data.jobs_by_pk.id}#documents`}>
<Button>
<Icon type="file-image" />
<Icon type='file-image' />
{t("jobs.actions.addDocuments")}
</Button>
</Link>,
<Button key="printing">
<Icon type="printer" />
<Button key='printing'>
<Icon type='printer' />
{t("jobs.actions.printCenter")}
</Button>,
<Button
key="notes"
actiontype="addNote"
key='notes'
actiontype='addNote'
onClick={() => {
setNoteModalVisible(!noteModalVisible);
}}
>
<Icon type="edit" />
}}>
<Icon type='edit' />
{t("jobs.actions.addNote")}
</Button>,
<Button key="postinvoices">
<Icon type="shopping-cart" />
<Button key='postinvoices'>
<Icon type='shopping-cart' />
{t("jobs.actions.postInvoices")}
</Button>
]}
>
]}>
{
// loading ? (
// <LoadingSkeleton />
@@ -114,7 +126,7 @@ export default function JobDetailCards({ selectedJob }) {
// )
}
<section className="job-cards">
<section className='job-cards'>
<JobDetailCardsCustomerComponent
loading={loading}
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}>
{data ? (
<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>
{t("jobs.fields.phoneshort")}:
<PhoneFormatter>{`${data.ownr_ph1 ||

View File

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

View File

@@ -1,11 +1,12 @@
.ant-carousel .slick-slide {
text-align: center;
height: 160px;
line-height: 160px;
background: #364d79;
overflow: hidden;
}
.ant-carousel .slick-slide h3 {
color: #fff;
}
text-align: center;
height: 50px;
width: 50px;
line-height: 50px;
background: #364d79;
overflow: hidden;
}
.ant-carousel .slick-slide h3 {
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 { useTranslation } from "react-i18next";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort } from "../../utils/sorters";
//import EditableCell from "./job-lines-cell.component";
import AllocationsAssignmentContainer from "../allocations-assignment/allocations-assignment.container";
import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.container";
export default function JobLinesComponent({
loading,
refetch,
jobLines,
form,
handleSubmit,
setSearchText
setSearchText,
selectedLines,
setSelectedLines,
partsOrderModalVisible,
jobId
}) {
const [state, setState] = useState({
sortedInfo: {}
});
const { t } = useTranslation();
const setPartsModalVisible = partsOrderModalVisible[1];
const columns = [
{
title: t("joblines.fields.unq_seq"),
@@ -119,17 +123,29 @@ export default function JobLinesComponent({
{record.allocations && record.allocations.length > 0
? record.allocations.map(item => (
<div
key={item.id}
>{`${item.employee.first_name} ${item.employee.last_name} (${item.hours})`}</div>
key={
item.id
}>{`${item.employee.first_name} ${item.employee.last_name} (${item.hours})`}</div>
))
: null}
<AllocationsAssignmentContainer
key={record.id}
refetch={refetch}
jobLineId={record.id}
hours={record.mod_lb_hrs}
/>
</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 });
};
// const handleChange = event => {
// const { value } = event.target;
// setState({ ...state, filterinfo: { text: [value] } });
// };
const formItemLayout = {
labelCol: {
xs: { span: 12 },
@@ -153,25 +165,46 @@ export default function JobLinesComponent({
};
return (
<Table
title={() => {
return (
<Input.Search
placeholder="Search..."
onChange={e => {
e.preventDefault();
setSearchText(e.target.value);
}}
/>
);
}}
{...formItemLayout}
size="small"
pagination={{ position: "bottom", defaultPageSize: 50 }}
columns={columns.map(item => ({ ...item }))}
rowKey="id"
dataSource={jobLines}
onChange={handleTableChange}
/>
<div>
<PartsOrderModalContainer
partsOrderModalVisible={partsOrderModalVisible}
linesToOrder={selectedLines}
jobId={jobId}
/>
<Table
title={() => {
return (
<div>
<Input.Search
placeholder={t("general.labels.search")}
onChange={e => {
e.preventDefault();
setSearchText(e.target.value);
}}
/>
<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 { notification } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { GET_JOB_LINES_BY_PK } from "../../graphql/jobs-lines.queries";
import AlertComponent from "../alert/alert.component";
import JobLinesComponent from "./job-lines.component";
//export default Form.create({ name: "JobsDetailJobLines" })(
export default function JobLinesContainer({ jobId, form }) {
const { loading, error, data } = useQuery(GET_JOB_LINES_BY_PK, {
export default function JobLinesContainer({ jobId }) {
const { loading, error, data, refetch } = useQuery(GET_JOB_LINES_BY_PK, {
variables: { id: jobId },
fetchPolicy: "network-only"
});
const [searchText, setSearchText] = useState("");
const { t } = useTranslation();
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("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" />;
const [selectedLines, setSelectedLines] = useState([]);
const partsOrderModalVisible = useState(false);
if (error) return <AlertComponent message={error.message} type='error' />;
return (
<JobLinesComponent
loading={loading}
refetch={refetch}
jobLines={
data && data.joblines
? searchText
@@ -78,9 +53,11 @@ export default function JobLinesContainer({ jobId, form }) {
: data.joblines
: null
}
handleSubmit={handleSubmit}
form={form}
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
})(<Input name='loss_desc' />)}
</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")}>
// {getFieldDecorator("exempt", {

View File

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

View File

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

View File

@@ -2,12 +2,22 @@ import { Modal } from "antd";
import React from "react";
import { useQuery } from "react-apollo";
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 LoadingSpinner from "../loading-spinner/loading-spinner.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,
error,
selectedJob,
@@ -17,8 +27,11 @@ export default function JobsFindModalContainer({
}) {
const { t } = useTranslation();
const jobsList = useQuery(QUERY_ALL_OPEN_JOBS, {
fetchPolicy: "network-only"
const jobsList = useQuery(QUERY_ALL_ACTIVE_JOBS, {
fetchPolicy: "network-only",
variables: {
statuses: bodyshop.md_ro_statuses.open_statuses || ["Open"]
}
});
return (
@@ -26,10 +39,9 @@ export default function JobsFindModalContainer({
title={t("jobs.labels.existing_jobs")}
width={"80%"}
okButtonProps={{ disabled: selectedJob ? false : true }}
{...modalProps}
>
{...modalProps}>
{loading ? <LoadingSpinner /> : null}
{error ? <AlertComponent message={error.message} type="error" /> : null}
{error ? <AlertComponent message={error.message} type='error' /> : null}
{true ? (
<JobsFindModalComponent
selectedJob={selectedJob}
@@ -43,4 +55,4 @@ export default function JobsFindModalContainer({
) : null}
</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 { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
@@ -8,6 +8,8 @@ import { withRouter } from "react-router-dom";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
export default withRouter(function JobsList({
searchTextState,
refetch,
loading,
jobs,
selectedJob,
@@ -21,6 +23,7 @@ export default withRouter(function JobsList({
const { t } = useTranslation();
const setSearchText = searchTextState[1];
const columns = [
{
title: t("jobs.fields.ro_number"),
@@ -29,7 +32,11 @@ export default withRouter(function JobsList({
width: "8%",
// onFilter: (value, record) => record.ro_number.includes(value),
// 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:
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
@@ -73,7 +80,7 @@ export default withRouter(function JobsList({
<PhoneFormatter>{record.ownr_ph1}</PhoneFormatter>
<Icon
style={{ margin: 4 }}
type="message"
type='message'
onClick={() => {
alert("SMSing will happen here.");
}}
@@ -205,19 +212,24 @@ export default withRouter(function JobsList({
loading={loading}
title={() => {
return (
<Input.Search
placeholder="Search..."
onSearch={value => {
console.log(value);
}}
enterButton
/>
<div style={{ display: "flex" }}>
<Button onClick={() => refetch()}>
<Icon type='sync' />
</Button>
<Input.Search
placeholder='Search...'
onChange={e => {
setSearchText(e.target.value);
}}
enterButton
/>
</div>
);
}}
size="small"
size='small'
pagination={{ position: "top" }}
columns={columns.map(item => ({ ...item }))}
rowKey="id"
rowKey='id'
dataSource={jobs}
rowSelection={{ selectedRowKeys: [selectedJob] }}
onChange={handleTableChange}

View File

@@ -33,7 +33,7 @@ export default function NoteUpsertModalContainer({
insertNote({
variables: {
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 => {

View File

@@ -27,7 +27,7 @@ function OwnerDetailFormContainer({ form, owner, refetch }) {
notification["success"]({
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();
form.resetFields();
});

View File

@@ -20,12 +20,7 @@ export default function OwnerFindModalContainer({
const ownersList = useQuery(QUERY_SEARCH_OWNER_BY_IDX, {
variables: {
search: owner
? `${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
search: owner ? `${owner.ownr_fn || ""} ${owner.ownr_ln || ""}` : null
},
skip: !owner,
fetchPolicy: "network-only"
@@ -35,18 +30,17 @@ export default function OwnerFindModalContainer({
<Modal
title={t("owners.labels.existing_owners")}
width={"80%"}
{...modalProps}
>
{...modalProps}>
{loading ? <LoadingSpinner /> : null}
{error ? <AlertComponent message={error.message} type="error" /> : null}
{error ? <AlertComponent message={error.message} type='error' /> : null}
{owner ? (
<OwnerFindModalComponent
selectedOwner={selectedOwner}
setSelectedOwner={setSelectedOwner}
ownersListLoading={ownersList.loading}
ownersList={
ownersList.data && ownersList.data.search_owners
? ownersList.data.search_owners
ownersList.data && ownersList.data.search_owner
? ownersList.data.search_owner
: 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 { useTranslation } from "react-i18next";
import { connect } from "react-redux";
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 { selectCurrentUser } from "../../redux/user/user.selectors";
import ResetForm from "../form-items-formatted/reset-form-item.component";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser
@@ -47,33 +47,26 @@ export default connect(
return (
<div>
{isFieldsTouched() ? (
//TODO: Appropriate Error
<AlertComponent
message={t("jobs.errors.validation")}
onClick={() => resetFields()}
/>
) : null}
{isFieldsTouched() ? <ResetForm resetFields={resetFields} /> : null}
<Form onSubmit={handleSubmit} autoComplete={"no"}>
<Form.Item label={t("user.fields.displayname")}>
{getFieldDecorator("displayname", {
initialValue: currentUser.displayName,
rules: [{ required: true }]
})(<Input name="displayname" />)}
})(<Input name='displayname' />)}
</Form.Item>
<Form.Item label={t("user.fields.photourl")}>
{getFieldDecorator("photoURL", {
initialValue: currentUser.photoURL
})(<Input name="photoURL" />)}
})(<Input name='photoURL' />)}
</Form.Item>
<Button
type="primary"
key="submit"
htmlType="submit"
onClick={handleSubmit}
>
type='primary'
key='submit'
htmlType='submit'
onClick={handleSubmit}>
{t("user.actions.updateprofile")}
</Button>
</Form>

View File

@@ -11,7 +11,7 @@ export default function ScheduleJobModalComponent({
...props
}) {
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 (
<Modal
{...props}
@@ -47,7 +47,7 @@ export default function ScheduleJobModalComponent({
</Row>
{
//TODO: Build out notifications.
//TODO Build out notifications.
}
<Checkbox
defaultChecked={formData.notifyCustomer}

View File

@@ -43,7 +43,7 @@ export default connect(
visible={scheduleModalVisible}
onCancel={() => setscheduleModalVisible(false)}
onOk={() => {
//TODO: Customize the amount of minutes it will add.
//TODO Customize the amount of minutes it will add.
insertAppointment({
variables: {
app: { ...appData, end: moment(appData.start).add(60, "minutes") }
@@ -55,7 +55,7 @@ export default connect(
});
if (formData.notifyCustomer) {
//TODO: Implement customer reminder on scheduling.
//TODO Implement customer reminder on scheduling.
alert("Chosed to notify the customer somehow!");
}
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 React from "react";
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 Event from "../schedule-event/schedule-event.container";
const localizer = momentLocalizer(moment);
@@ -19,7 +20,7 @@ export default function ScheduleCalendarWrapperComponent({
step={30}
showMultiDayTimes
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")}
components={{
event: e => {

View File

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

View File

@@ -13,7 +13,7 @@ export default function ScheduleJobModalComponent({
...props
}) {
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 (
<Modal
{...props}
@@ -52,7 +52,7 @@ export default function ScheduleJobModalComponent({
existingAppointments={existingAppointments}
/>
{
//TODO: Build out notifications.
//TODO Build out notifications.
}
<Checkbox
defaultChecked={formData.notifyCustomer}

View File

@@ -60,7 +60,7 @@ export default connect(
visible={scheduleModalVisible}
onCancel={() => setscheduleModalVisible(false)}
onOk={() => {
//TODO: Customize the amount of minutes it will add.
//TODO Customize the amount of minutes it will add.
insertAppointment({
variables: {
app: { ...appData, end: moment(appData.start).add(60, "minutes") }
@@ -73,7 +73,7 @@ export default connect(
});
if (formData.notifyCustomer) {
//TODO: Implement customer reminder on scheduling.
//TODO Implement customer reminder on scheduling.
alert("Chosed to notify the customer somehow!");
}
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"]({
message: t("employees.successes.delete")
});
//TODO: Better way to reset the field decorators?
//TODO Better way to reset the field decorators?
employeeState[1](null);
refetch().then(r => form.resetFields());
})
@@ -61,7 +61,7 @@ function ShopEmployeesContainer({ form, bodyshop }) {
notification["success"]({
message: t("employees.successes.save")
});
//TODO: Better way to reset the field decorators?
//TODO Better way to reset the field decorators?
employeeState[1](null);
refetch().then(r => form.resetFields());
})
@@ -78,7 +78,7 @@ function ShopEmployeesContainer({ form, bodyshop }) {
notification["success"]({
message: t("employees.successes.save")
});
//TODO: Better way to reset the field decorators?
//TODO Better way to reset the field decorators?
employeeState[1](null);
refetch()
.then(r => form.resetFields())

View File

@@ -26,7 +26,7 @@ function VehicleDetailFormContainer({ form, vehicle, refetch }) {
notification["success"]({
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();
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);
window.localStorage.setItem("token", token);
// const oldHeaders = operation.getContext().headers;
// operation.setContext({
// headers: {
// ...oldHeaders,
// authorization: token ? `Bearer ${token}` : ""
// }
// });
const oldHeaders = operation.getContext().headers;
operation.setContext({
headers: {
...oldHeaders,
authorization: token ? `Bearer ${token}` : ""
}
});
console.log(operation.getContext());
// console.log("forward", forward);
// console.log("operation", operation);
return forward(operation).subscribe();
return forward(operation);
// return new Observable(observer => {
// const subscriber = {

View File

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

View File

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

View File

@@ -2,7 +2,7 @@ import { gql } from "apollo-boost";
export const QUERY_SEARCH_OWNER_BY_IDX = gql`
query QUERY_SEARCH_OWNER_BY_IDX($search: String!) {
search_owners(args: { search: $search }) {
search_owner(args: { search: $search }) {
ownr_fn
ownr_ln
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}
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
deleteJob={deleteJob}
estDataLazyLoad={estDataLazyLoad}

View File

@@ -36,8 +36,7 @@ function JobsDetailPageContainer({ match, form }) {
refetch();
})
.catch(error => {
//TODO Error handling.
console.log("error", error);
notification[error]({ message: t("jobs.errors.saving") });
});
};
@@ -70,7 +69,6 @@ function JobsDetailPageContainer({ match, form }) {
notification["success"]({
message: t("jobs.successes.savetitle")
});
//TODO: Better way to reset the field decorators?
refetch().then(r => form.resetFields());
});
}
@@ -78,7 +76,7 @@ function JobsDetailPageContainer({ match, form }) {
};
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 ? (
<JobDetailFormContext.Provider value={form}>
@@ -94,7 +92,7 @@ function JobsDetailPageContainer({ match, form }) {
/>
</JobDetailFormContext.Provider>
) : (
<AlertComponent message={t("jobs.errors.noaccess")} type="error" />
<AlertComponent message={t("jobs.errors.noaccess")} type='error' />
);
}
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 JobDetailCards from "../../components/job-detail-cards/job-detail-cards.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.
export default function JobsPage({ match, location }) {
const { loading, error, data } = useQuery(QUERY_ALL_OPEN_JOBS, {
fetchPolicy: "network-only"
import { selectBodyshop } from "../../redux/user/user.selectors";
import { connect } from "react-redux";
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();
@@ -19,17 +32,53 @@ export default function JobsPage({ match, location }) {
const { hash } = location;
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 (
<div>
<JobsList
searchTextState={searchTextState}
refetch={refetch}
loading={loading}
selectedJob={selectedJob}
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} />
</div>
);
}
});

View File

@@ -1,13 +1,19 @@
import React from "react";
import { Typography } from "antd";
import { Typography, Layout } from "antd";
import HeaderContainer from "../../components/header/header.container";
export default function LandingPage() {
const { Header, Content } = Layout;
return (
<div>
<HeaderContainer landingHeader />
<Typography.Title>Welcome to bodyshop.app.</Typography.Title>
</div>
<Layout style={{ minHeight: "100vh" }}>
<Header>
<HeaderContainer landingHeader />
</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() {
return (
<div>
Temporary Home Page.
</div>
)
//const client = useApolloClient();
return (
<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 { QUERY_BODYSHOP } from "../../graphql/bodyshop.queries";
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";
const mapStateToProps = createStructuredSelector({
@@ -33,12 +36,10 @@ export default connect(
useEffect(() => {
if (data) setBodyshop(data.bodyshops[0]);
return () => {
//
};
return () => {};
}, [data, setBodyshop]);
//TODO Translate later.
if (!bodyshop) return <LoadingSpinner message="Loading bodyshop data." />;
if (!bodyshop)
return <LoadingSpinner message={t("general.labels.loadingshop")} />;
return <ManagePage match={match} />;
});

View File

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

View File

@@ -106,9 +106,12 @@
"in": "In",
"loading": "Loading...",
"loadingapp": "Loading Bodyshop.app",
"loadingshop": "Loading shop data...",
"loggingin": "Logging you in...",
"na": "N/A",
"out": "Out",
"save": "Save",
"search": "Search...",
"unknown": "Unknown"
},
"languages": {
@@ -120,6 +123,7 @@
"unsavedchanges": "You have unsaved changes."
},
"validation": {
"invalidemail": "Please enter a valid email.",
"required": "This field is required. "
}
},
@@ -301,6 +305,8 @@
"owners": "Owners",
"schedule": "Schedule",
"shop": "My Shop",
"shop_config": "Configuration",
"shop_vendors": "Vendors",
"vehicles": "Vehicles"
},
"jobsdetail": {
@@ -369,6 +375,26 @@
"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": {
"errors": {
"state": "Error reading page state. Please refresh."
@@ -385,6 +411,7 @@
"profile": "My Profile | $t(titles.app)",
"schedule": "Schedule | $t(titles.app)",
"shop": "My Shop | $t(titles.app)",
"shop_vendors": "Vendors | $t(titles.app)",
"vehicledetail": "Vehicle Details {{vehicle}} | $t(titles.app)",
"vehicles": "All Vehicles | $t(titles.app)"
},
@@ -430,6 +457,41 @@
"successes": {
"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",
"loading": "Cargando...",
"loadingapp": "Cargando Bodyshop.app",
"loadingshop": "Cargando datos de la tienda ...",
"loggingin": "Iniciando sesión ...",
"na": "N / A",
"out": "Afuera",
"save": "Salvar",
"search": "Buscar...",
"unknown": "Desconocido"
},
"languages": {
@@ -120,6 +123,7 @@
"unsavedchanges": "Usted tiene cambios no guardados."
},
"validation": {
"invalidemail": "Por favor introduzca una dirección de correo electrónico válida.",
"required": "Este campo es requerido."
}
},
@@ -301,6 +305,8 @@
"owners": "propietarios",
"schedule": "Programar",
"shop": "Mi tienda",
"shop_config": "Configuración",
"shop_vendors": "Vendedores",
"vehicles": "Vehículos"
},
"jobsdetail": {
@@ -369,6 +375,26 @@
"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": {
"errors": {
"state": "Error al leer el estado de la página. Porfavor refresca."
@@ -385,6 +411,7 @@
"profile": "Mi perfil | $t(titles.app)",
"schedule": "Horario | $t(titles.app)",
"shop": "Mi tienda | $t(titles.app)",
"shop_vendors": "Vendedores | $t(titles.app)",
"vehicledetail": "Detalles del vehículo {{vehicle}} | $t(titles.app)",
"vehicles": "Todos los vehiculos | $t(titles.app)"
},
@@ -430,6 +457,41 @@
"successes": {
"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",
"loading": "Chargement...",
"loadingapp": "Chargement de Bodyshop.app",
"loadingshop": "Chargement des données de la boutique ...",
"loggingin": "Vous connecter ...",
"na": "N / A",
"out": "En dehors",
"save": "sauvegarder",
"search": "Chercher...",
"unknown": "Inconnu"
},
"languages": {
@@ -120,6 +123,7 @@
"unsavedchanges": "Vous avez des changements non enregistrés."
},
"validation": {
"invalidemail": "S'il vous plaît entrer un email valide.",
"required": "Ce champ est requis."
}
},
@@ -301,6 +305,8 @@
"owners": "Propriétaires",
"schedule": "Programme",
"shop": "Mon magasin",
"shop_config": "Configuration",
"shop_vendors": "Vendeurs",
"vehicles": "Véhicules"
},
"jobsdetail": {
@@ -369,6 +375,26 @@
"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": {
"errors": {
"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)",
"schedule": "Horaire | $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)",
"vehicles": "Tous les véhicules | $t(titles.app)"
},
@@ -430,6 +457,41 @@
"successes": {
"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() : "";
B = b ? b.toLowerCase() : "";
console.log("Objects", A, B, A < B, A > B);
if (A < B)
//sort string ascending

View File

@@ -967,6 +967,18 @@
lodash "^4.17.13"
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":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.3.tgz#099139eaec7ebf07a27c1786a3ff64f39464d2ef"
@@ -1668,6 +1680,14 @@
dependencies:
"@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":
version "5.0.40"
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"
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":
version "1.0.0"
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"
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":
version "7.1.1"
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"
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@*":
version "3.0.3"
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"
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":
version "0.23.4"
resolved "https://registry.yarnpkg.com/@types/react-slick/-/react-slick-0.23.4.tgz#c97e2a9e7e3d1933c68593b8e82752fab1e8ce53"
@@ -1775,6 +1829,14 @@
"@types/prop-types" "*"
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":
version "1.0.1"
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"
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"
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
@@ -3051,6 +3113,11 @@ bser@2.1.1:
dependencies:
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:
version "1.1.1"
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"
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:
version "2.6.9"
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"
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:
version "1.1.1"
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"
integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=
har-validator@~5.1.0:
har-validator@~5.1.0, har-validator@~5.1.3:
version "5.1.3"
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080"
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"
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:
version "1.4.1"
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"
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:
version "1.0.1"
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"
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:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
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:
version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
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:
version "4.7.0"
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:
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:
version "1.16.1"
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"
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"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
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"
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:
version "1.5.1"
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"
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:
version "11.3.1"
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"
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:
version "2.1.1"
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"
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:
version "2.2.3"
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"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.4.tgz#e0777fe0d184cec7872df147f303572d414e211c"
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:
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-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:
version "0.2.3"
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"
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"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
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"
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:
version "0.3.2"
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"
schema-utils "^2.5.0"
url-parse@^1.4.3:
url-parse@^1.4.3, url-parse@^1.4.7:
version "1.4.7"
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278"
integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==
@@ -12697,6 +12915,11 @@ wbuf@^1.1.0, wbuf@^1.7.3:
dependencies:
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:
version "4.0.2"
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"
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:
version "2.2.0"
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");
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 HASURA_SECRET_ADMIN_KEY = functions.config().auth.hasura_secret_admin_key;
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