14
README.MD
14
README.MD
@@ -1,12 +1,14 @@
|
|||||||
React App:
|
React App:
|
||||||
React Hooks are used for Authentication ONLY to ensure the correct web token is passed.
|
|
||||||
|
Yarn Dependency Management:
|
||||||
|
To force upgrades for some packages: yarn upgrade-interactive --latest
|
||||||
|
|
||||||
GraphQL API:
|
GraphQL API:
|
||||||
Hasura is hosted on another dyno. Several environmental variables are required, including disabling the console.
|
Hasura is hosted on another dyno. Several environmental variables are required, including disabling the console.
|
||||||
ALL CHANGES MUST BE MADE USING LOCAL CONSOLE TO ENSURE DATABASE MIGRATION FILES ARE CREATED.
|
ALL CHANGES MUST BE MADE USING LOCAL CONSOLE TO ENSURE DATABASE MIGRATION FILES ARE CREATED.
|
||||||
|
|
||||||
To Start Hasura CLI:
|
To Start Hasura CLI:
|
||||||
npx hasura console --admin-secret Dev-BodyShopAppBySnaptSoftware!
|
npx hasura console --admin-secret Dev-BodyShopAppBySnaptSoftware!
|
||||||
|
|
||||||
Migrating to Staging:
|
Migrating to Staging:
|
||||||
npx hasura migrate apply --up 10 --endpoint https://bodyshop-staging-db.herokuapp.com/ --admin-secret Staging-BodyShopAppBySnaptSoftware!
|
npx hasura migrate apply --up 10 --endpoint https://bodyshop-staging-db.herokuapp.com/ --admin-secret Staging-BodyShopAppBySnaptSoftware!
|
||||||
|
|||||||
@@ -1,5 +1,51 @@
|
|||||||
**Required items**
|
**Required items**
|
||||||
|
|
||||||
|
|
||||||
-Bodyshop Record
|
-Bodyshop Record
|
||||||
-Counter Record - type: ronum
|
..\*Include the statuses file in the format of:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"statuses": [
|
||||||
|
"Open",
|
||||||
|
"Scheduled",
|
||||||
|
"Arrived",
|
||||||
|
"Repair Plan",
|
||||||
|
"Parts",
|
||||||
|
"Body",
|
||||||
|
"Prep",
|
||||||
|
"Paint",
|
||||||
|
"Reassembly",
|
||||||
|
"Sublet",
|
||||||
|
"Detail",
|
||||||
|
"Completed",
|
||||||
|
"Delivered",
|
||||||
|
"Invoiced",
|
||||||
|
"Exported"
|
||||||
|
],
|
||||||
|
"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_scheduled": "Scheduled"
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
--\* Set the region for the shop.
|
||||||
|
-Counter Record - type: ronum
|
||||||
|
|||||||
192
_reference/CiecaOpCodesReference.json
Normal file
192
_reference/CiecaOpCodesReference.json
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
{
|
||||||
|
"OP0": {
|
||||||
|
"desc": "REMOVE / REPLACE PARTIAL",
|
||||||
|
"opcode": "OP11",
|
||||||
|
"partcode": "PAA"
|
||||||
|
},
|
||||||
|
"OP1": {
|
||||||
|
"desc": "REFINISH / REPAIR",
|
||||||
|
"opcode": "OP1",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP10": {
|
||||||
|
"desc": "REPAIR , PARTIAL",
|
||||||
|
"opcode": "OP9",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP100": {
|
||||||
|
"desc": "REPLACE PRE-PRICED",
|
||||||
|
"opcode": "OP11",
|
||||||
|
"partcode": "PAA"
|
||||||
|
},
|
||||||
|
"OP101": {
|
||||||
|
"desc": "REMOVE/REPLACE RECYCLED PART",
|
||||||
|
"opcode": "OP11",
|
||||||
|
"partcode": "PAL"
|
||||||
|
},
|
||||||
|
"OP103": {
|
||||||
|
"desc": "REMOVE / REPLACE PARTIAL",
|
||||||
|
"opcode": "OP11",
|
||||||
|
"partcode": "PAA"
|
||||||
|
},
|
||||||
|
"OP104": {
|
||||||
|
"desc": "REMOVE / REPLACE PARTIAL LABOUR",
|
||||||
|
"opcode": "OP11",
|
||||||
|
"partcode": "PAA"
|
||||||
|
},
|
||||||
|
"OP105": {
|
||||||
|
"desc": "!!ADJUST MANUALLY!!",
|
||||||
|
"opcode": "OP99",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP106": {
|
||||||
|
"desc": "REPAIR , PARTIAL",
|
||||||
|
"opcode": "OP9",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP107": {
|
||||||
|
"desc": "CHIPGUARD",
|
||||||
|
"opcode": "OP6",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP108": {
|
||||||
|
"desc": "MULTI TONE",
|
||||||
|
"opcode": "OP6",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP109": {
|
||||||
|
"desc": "REPLACE PRE-PRICED",
|
||||||
|
"opcode": "OP11",
|
||||||
|
"partcode": "PAA"
|
||||||
|
},
|
||||||
|
"OP11": {
|
||||||
|
"desc": "REMOVE / REPLACE",
|
||||||
|
"opcode": "OP11",
|
||||||
|
"partcode": "PAN"
|
||||||
|
},
|
||||||
|
"OP110": {
|
||||||
|
"desc": "REFINISH / REPAIR",
|
||||||
|
"opcode": "OP1",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP111": {
|
||||||
|
"desc": "REMOVE / REPLACE",
|
||||||
|
"opcode": "OP11",
|
||||||
|
"partcode": "PAN"
|
||||||
|
},
|
||||||
|
"OP112": {
|
||||||
|
"desc": "REMOVE / REPLACE",
|
||||||
|
"opcode": "OP11",
|
||||||
|
"partcode": "PAA"
|
||||||
|
},
|
||||||
|
"OP113": {
|
||||||
|
"desc": "REPLACE PRE-PRICED",
|
||||||
|
"opcode": "OP11",
|
||||||
|
"partcode": "PAA"
|
||||||
|
},
|
||||||
|
"OP114": {
|
||||||
|
"desc": "REPLACE PRE-PRICED",
|
||||||
|
"opcode": "OP11",
|
||||||
|
"partcode": "PAA"
|
||||||
|
},
|
||||||
|
"OP12": {
|
||||||
|
"desc": "REMOVE / REPLACE PARTIAL",
|
||||||
|
"opcode": "OP11",
|
||||||
|
"partcode": "PAN"
|
||||||
|
},
|
||||||
|
"OP120": {
|
||||||
|
"desc": "REPAIR , PARTIAL",
|
||||||
|
"opcode": "OP9",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP13": {
|
||||||
|
"desc": "ADDITIONAL COSTS",
|
||||||
|
"opcode": "OP13",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP14": {
|
||||||
|
"desc": "ADDITIONAL OPERATIONS",
|
||||||
|
"opcode": "OP14",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP15": {
|
||||||
|
"desc": "BLEND",
|
||||||
|
"opcode": "OP15",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP16": {
|
||||||
|
"desc": "SUBLET",
|
||||||
|
"opcode": "OP16",
|
||||||
|
"partcode": "PAS"
|
||||||
|
},
|
||||||
|
"OP17": {
|
||||||
|
"desc": "POLICY LIMIT ADJUSTMENT",
|
||||||
|
"opcode": "OP9",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP18": {
|
||||||
|
"desc": "APPEAR ALLOWANCE",
|
||||||
|
"opcode": "OP7",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP2": {
|
||||||
|
"desc": "REMOVE / INSTALL",
|
||||||
|
"opcode": "OP2",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP24": {
|
||||||
|
"desc": "CHIPGUARD",
|
||||||
|
"opcode": "OP6",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP25": {
|
||||||
|
"desc": "TWO TONE",
|
||||||
|
"opcode": "OP6",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP26": {
|
||||||
|
"desc": "PAINTLESS DENT REPAIR",
|
||||||
|
"opcode": "OP16",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP260": {
|
||||||
|
"desc": "SUBLET",
|
||||||
|
"opcode": "OP16",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP3": {
|
||||||
|
"desc": "ADDITIONAL LABOR",
|
||||||
|
"opcode": "OP9",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP4": {
|
||||||
|
"desc": "ALIGNMENT",
|
||||||
|
"opcode": "OP4",
|
||||||
|
"partcode": "PAS"
|
||||||
|
},
|
||||||
|
"OP5": {
|
||||||
|
"desc": "OVERHAUL",
|
||||||
|
"opcode": "OP5",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP6": {
|
||||||
|
"desc": "REFINISH",
|
||||||
|
"opcode": "OP6",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP7": {
|
||||||
|
"desc": "INSPECT",
|
||||||
|
"opcode": "OP7",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP8": {
|
||||||
|
"desc": "CHECK / ADJUST",
|
||||||
|
"opcode": "OP8",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP9": {
|
||||||
|
"desc": "REPAIR",
|
||||||
|
"opcode": "OP9",
|
||||||
|
"partcode": "PAE"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,4 +8,7 @@ Bucket=
|
|||||||
|
|
||||||
__React Based__
|
__React Based__
|
||||||
REACT_APP_GRAPHQL_ENDPOINT
|
REACT_APP_GRAPHQL_ENDPOINT
|
||||||
REACT_APP_GRAPHQL_ENDPOINT_WS
|
REACT_APP_GRAPHQL_ENDPOINT_WS
|
||||||
|
|
||||||
|
__MetaData__
|
||||||
|
Region based OpCodes
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -4,33 +4,44 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"proxy": "https://localhost:5000",
|
"proxy": "https://localhost:5000",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"antd": "^3.26.0",
|
"@ckeditor/ckeditor5-build-classic": "^16.0.0",
|
||||||
|
"@ckeditor/ckeditor5-react": "^2.1.0",
|
||||||
|
"antd": "^3.26.8",
|
||||||
"apollo-boost": "^0.4.4",
|
"apollo-boost": "^0.4.4",
|
||||||
"apollo-link-context": "^1.0.19",
|
"apollo-link-context": "^1.0.19",
|
||||||
"apollo-link-error": "^1.1.12",
|
"apollo-link-error": "^1.1.12",
|
||||||
"apollo-link-logger": "^1.2.3",
|
"apollo-link-logger": "^1.2.3",
|
||||||
"apollo-link-ws": "^1.0.19",
|
"apollo-link-ws": "^1.0.19",
|
||||||
"axios": "^0.19.1",
|
"axios": "^0.19.2",
|
||||||
"chart.js": "^2.9.3",
|
"chart.js": "^2.9.3",
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
"firebase": "^7.5.0",
|
"firebase": "^7.8.1",
|
||||||
"graphql": "^14.5.8",
|
"graphql": "^14.6.0",
|
||||||
"i18next": "^19.0.2",
|
"i18next": "^19.1.0",
|
||||||
"node-sass": "^4.13.0",
|
"node-sass": "^4.13.1",
|
||||||
"react": "^16.12.0",
|
"react": "^16.12.0",
|
||||||
"react-apollo": "^3.1.3",
|
"react-apollo": "^3.1.3",
|
||||||
"react-chartjs-2": "^2.8.0",
|
"react-barcode": "^1.4.0",
|
||||||
|
"react-big-calendar": "^0.23.0",
|
||||||
|
"react-chartjs-2": "^2.9.0",
|
||||||
"react-dom": "^16.12.0",
|
"react-dom": "^16.12.0",
|
||||||
"react-i18next": "^11.2.7",
|
"react-html-email": "^3.0.0",
|
||||||
"react-icons": "^3.8.0",
|
"react-i18next": "^11.3.1",
|
||||||
|
"react-icons": "^3.9.0",
|
||||||
"react-image-file-resizer": "^0.2.1",
|
"react-image-file-resizer": "^0.2.1",
|
||||||
"react-moment": "^0.9.7",
|
"react-moment": "^0.9.7",
|
||||||
"react-number-format": "^4.3.1",
|
"react-number-format": "^4.3.1",
|
||||||
|
"react-redux": "^7.1.3",
|
||||||
"react-router-dom": "^5.1.2",
|
"react-router-dom": "^5.1.2",
|
||||||
"react-scripts": "3.2.0",
|
"react-scripts": "3.3.1",
|
||||||
"react-trello": "^2.2.3",
|
"redux": "^4.0.5",
|
||||||
"styled-components": "^4.4.1",
|
"redux-logger": "^3.0.6",
|
||||||
"subscriptions-transport-ws": "^0.9.16"
|
"redux-persist": "^6.0.0",
|
||||||
|
"redux-saga": "^1.1.3",
|
||||||
|
"reselect": "^4.0.0",
|
||||||
|
"styled-components": "^5.0.1",
|
||||||
|
"subscriptions-transport-ws": "^0.9.16",
|
||||||
|
"twilio": "^3.39.5"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"short_name": "Bodyshop",
|
"short_name": "Bodyshop.app",
|
||||||
"name": "Bodyshop Management System",
|
"name": "Bodyshop Management System",
|
||||||
|
"description": "The ultimate bodyshop management system",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "favicon.ico",
|
"src": "favicon.ico",
|
||||||
@@ -20,6 +21,6 @@
|
|||||||
],
|
],
|
||||||
"start_url": ".",
|
"start_url": ".",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"theme_color": "#002366",
|
"theme_color": "#fff",
|
||||||
"background_color": "#000000"
|
"background_color": "#fff"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,18 @@
|
|||||||
import React, { Component } from "react";
|
import { ApolloLink } from "apollo-boost";
|
||||||
|
import { InMemoryCache } from "apollo-cache-inmemory";
|
||||||
import App from "./App";
|
|
||||||
import Spin from "../components/loading-spinner/loading-spinner.component";
|
|
||||||
|
|
||||||
import ApolloClient from "apollo-client";
|
import ApolloClient from "apollo-client";
|
||||||
import { split } from "apollo-link";
|
import { split } from "apollo-link";
|
||||||
|
import { setContext } from "apollo-link-context";
|
||||||
import { HttpLink } from "apollo-link-http";
|
import { HttpLink } from "apollo-link-http";
|
||||||
|
import apolloLogger from "apollo-link-logger";
|
||||||
import { WebSocketLink } from "apollo-link-ws";
|
import { WebSocketLink } from "apollo-link-ws";
|
||||||
import { getMainDefinition } from "apollo-utilities";
|
import { getMainDefinition } from "apollo-utilities";
|
||||||
import { InMemoryCache } from "apollo-cache-inmemory";
|
import React, { Component } from "react";
|
||||||
import { setContext } from "apollo-link-context";
|
|
||||||
import { resolvers, typeDefs } from "../graphql/resolvers";
|
|
||||||
import apolloLogger from "apollo-link-logger";
|
|
||||||
import { ApolloLink } from "apollo-boost";
|
|
||||||
import { ApolloProvider } from "react-apollo";
|
import { ApolloProvider } from "react-apollo";
|
||||||
import { persistCache } from "apollo-cache-persist";
|
import SpinnerComponent from "../components/loading-spinner/loading-spinner.component";
|
||||||
import initialState from "../graphql/initial-state";
|
|
||||||
//import { shouldRefreshToken, refreshToken } from "../graphql/middleware";
|
//import { shouldRefreshToken, refreshToken } from "../graphql/middleware";
|
||||||
import errorLink from "../graphql/apollo-error-handling";
|
import errorLink from "../graphql/apollo-error-handling";
|
||||||
|
import App from "./App";
|
||||||
|
|
||||||
class AppContainer extends Component {
|
class AppContainer extends Component {
|
||||||
state = {
|
state = {
|
||||||
@@ -69,14 +64,8 @@ class AppContainer extends Component {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const authLink = setContext((_, { headers }) => {
|
const authLink = setContext((_, { headers }) => {
|
||||||
// get the authentication token from local storage if it exists
|
|
||||||
const token = localStorage.getItem("token");
|
const token = localStorage.getItem("token");
|
||||||
// return the headers to the context so httpLink can read them
|
|
||||||
if (token) {
|
if (token) {
|
||||||
// if (shouldRefreshToken) {
|
|
||||||
// refreshToken();
|
|
||||||
// }
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
headers: {
|
headers: {
|
||||||
...headers,
|
...headers,
|
||||||
@@ -99,31 +88,13 @@ class AppContainer extends Component {
|
|||||||
const client = new ApolloClient({
|
const client = new ApolloClient({
|
||||||
link: ApolloLink.from(middlewares),
|
link: ApolloLink.from(middlewares),
|
||||||
cache,
|
cache,
|
||||||
typeDefs,
|
|
||||||
resolvers,
|
|
||||||
connectToDevTools: true
|
connectToDevTools: true
|
||||||
});
|
});
|
||||||
|
|
||||||
client.writeData({
|
|
||||||
data: initialState
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
await persistCache({
|
|
||||||
cache,
|
|
||||||
storage: window.sessionStorage,
|
|
||||||
debug: true
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error restoring Apollo cache", error);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
client,
|
client,
|
||||||
loaded: true
|
loaded: true
|
||||||
});
|
});
|
||||||
|
|
||||||
//Init local state.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {}
|
componentWillUnmount() {}
|
||||||
@@ -132,7 +103,7 @@ class AppContainer extends Component {
|
|||||||
const { client, loaded } = this.state;
|
const { client, loaded } = this.state;
|
||||||
|
|
||||||
if (!loaded) {
|
if (!loaded) {
|
||||||
return <Spin />;
|
return <SpinnerComponent />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,113 +1,50 @@
|
|||||||
import React, { useEffect, Suspense, lazy, useState } from "react";
|
|
||||||
import { useApolloClient, useQuery } from "@apollo/react-hooks";
|
|
||||||
import { Switch, Route, Redirect } from "react-router-dom";
|
|
||||||
import firebase from "../firebase/firebase.utils";
|
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import React, { lazy, Suspense, useEffect } from "react";
|
||||||
import "./App.css";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { Route, Switch } from "react-router-dom";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import ErrorBoundary from "../components/error-boundary/error-boundary.component";
|
||||||
//Component Imports
|
//Component Imports
|
||||||
import LoadingSpinner from "../components/loading-spinner/loading-spinner.component";
|
import LoadingSpinner from "../components/loading-spinner/loading-spinner.component";
|
||||||
import AlertComponent from "../components/alert/alert.component";
|
import { checkUserSession } from "../redux/user/user.actions";
|
||||||
import ErrorBoundary from "../components/error-boundary/error-boundary.component";
|
import { selectCurrentUser } from "../redux/user/user.selectors";
|
||||||
|
|
||||||
import { auth } from "../firebase/firebase.utils";
|
|
||||||
import { UPSERT_USER } from "../graphql/user.queries";
|
|
||||||
import { GET_CURRENT_USER, GET_LANGUAGE } from "../graphql/local.queries";
|
|
||||||
// import { QUERY_BODYSHOP } from "../graphql/bodyshop.queries";
|
// import { QUERY_BODYSHOP } from "../graphql/bodyshop.queries";
|
||||||
|
|
||||||
import PrivateRoute from "../utils/private-route";
|
import PrivateRoute from "../utils/private-route";
|
||||||
|
import "./App.css";
|
||||||
|
|
||||||
const LandingPage = lazy(() => import("../pages/landing/landing.page"));
|
const LandingPage = lazy(() => import("../pages/landing/landing.page"));
|
||||||
const ManagePage = lazy(() => import("../pages/manage/manage.page"));
|
const ManagePage = lazy(() => import("../pages/manage/manage.page.container"));
|
||||||
const SignInPage = lazy(() => import("../pages/sign-in/sign-in.page"));
|
const SignInPage = lazy(() => import("../pages/sign-in/sign-in.page"));
|
||||||
const Unauthorized = lazy(() =>
|
const Unauthorized = lazy(() =>
|
||||||
import("../pages/unauthorized/unauthorized.component")
|
import("../pages/unauthorized/unauthorized.component")
|
||||||
);
|
);
|
||||||
|
|
||||||
export default () => {
|
const mapStateToProps = createStructuredSelector({
|
||||||
const apolloClient = useApolloClient();
|
currentUser: selectCurrentUser
|
||||||
const [loaded, setloaded] = useState(false);
|
});
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
checkUserSession: () => dispatch(checkUserSession())
|
||||||
|
});
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(({ checkUserSession, currentUser }) => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
//Run the auth code only on the first render.
|
checkUserSession();
|
||||||
const unsubscribeFromAuth = auth.onAuthStateChanged(async user => {
|
return () => {};
|
||||||
console.log("Auth State Changed.");
|
}, [checkUserSession]);
|
||||||
setloaded(true);
|
const { t } = useTranslation();
|
||||||
if (user) {
|
if (currentUser && currentUser.language)
|
||||||
let token;
|
i18next.changeLanguage(currentUser.language, (err, t) => {
|
||||||
token = await user.getIdToken();
|
|
||||||
const idTokenResult = await user.getIdTokenResult();
|
|
||||||
const hasuraClaim =
|
|
||||||
idTokenResult.claims["https://hasura.io/jwt/claims"];
|
|
||||||
if (!hasuraClaim) {
|
|
||||||
// Check if refresh is required.
|
|
||||||
const metadataRef = firebase
|
|
||||||
.database()
|
|
||||||
.ref("metadata/" + user.uid + "/refreshTime");
|
|
||||||
|
|
||||||
metadataRef.on("value", async () => {
|
|
||||||
// Force refresh to pick up the latest custom claims changes.
|
|
||||||
token = await user.getIdToken(true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
//add the bearer token to the headers.
|
|
||||||
localStorage.setItem("token", token);
|
|
||||||
const now = new Date();
|
|
||||||
window.sessionStorage.setItem(`lastTokenRefreshTime`, now);
|
|
||||||
// window.sessionStorage.setItem("user", user);
|
|
||||||
|
|
||||||
apolloClient
|
|
||||||
.mutate({
|
|
||||||
mutation: UPSERT_USER,
|
|
||||||
variables: { authEmail: user.email, authToken: user.uid }
|
|
||||||
})
|
|
||||||
.then()
|
|
||||||
.catch(error => {
|
|
||||||
console.log("User login upsert error.", error);
|
|
||||||
});
|
|
||||||
|
|
||||||
apolloClient.writeData({
|
|
||||||
data: {
|
|
||||||
currentUser: {
|
|
||||||
email: user.email,
|
|
||||||
displayName: user.displayName,
|
|
||||||
token,
|
|
||||||
uid: user.uid,
|
|
||||||
photoUrl: user.photoURL,
|
|
||||||
__typename: "currentUser"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
apolloClient.writeData({ data: { currentUser: null } });
|
|
||||||
localStorage.removeItem("token");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return function cleanup() {
|
|
||||||
unsubscribeFromAuth();
|
|
||||||
};
|
|
||||||
}, [apolloClient]);
|
|
||||||
const HookCurrentUser = useQuery(GET_CURRENT_USER);
|
|
||||||
const HookLanguage = useQuery(GET_LANGUAGE);
|
|
||||||
|
|
||||||
if (!loaded) return <LoadingSpinner />;
|
|
||||||
if (HookCurrentUser.loading || HookLanguage.loading)
|
|
||||||
return <LoadingSpinner />;
|
|
||||||
if (HookCurrentUser.error || HookLanguage.error)
|
|
||||||
return (
|
|
||||||
<AlertComponent
|
|
||||||
message={HookCurrentUser.error.message || HookLanguage.error.message}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (HookLanguage.data.language)
|
|
||||||
i18next.changeLanguage(HookLanguage.data.language, (err, t) => {
|
|
||||||
if (err)
|
if (err)
|
||||||
return console.log("Error encountered when changing languages.", err);
|
return console.log("Error encountered when changing languages.", err);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (currentUser.authorized === null) {
|
||||||
|
return <LoadingSpinner message={t("general.labels.loggingin")} />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Switch>
|
<Switch>
|
||||||
@@ -115,19 +52,12 @@ export default () => {
|
|||||||
<Suspense fallback={<LoadingSpinner />}>
|
<Suspense fallback={<LoadingSpinner />}>
|
||||||
<Route exact path='/' component={LandingPage} />
|
<Route exact path='/' component={LandingPage} />
|
||||||
<Route exact path='/unauthorized' component={Unauthorized} />
|
<Route exact path='/unauthorized' component={Unauthorized} />
|
||||||
<Route
|
|
||||||
exact
|
<Route exact path='/signin' component={SignInPage} />
|
||||||
path='/signin'
|
|
||||||
render={() =>
|
|
||||||
HookCurrentUser.data.currentUser ? (
|
|
||||||
<Redirect to='/manage' />
|
|
||||||
) : (
|
|
||||||
<SignInPage />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<PrivateRoute
|
<PrivateRoute
|
||||||
isAuthorized={HookCurrentUser.data.currentUser ? true : false}
|
//isAuthorized={HookCurrentUser.data.currentUser ? true : false}
|
||||||
|
isAuthorized={currentUser.authorized}
|
||||||
path='/manage'
|
path='/manage'
|
||||||
component={ManagePage}
|
component={ManagePage}
|
||||||
/>
|
/>
|
||||||
@@ -136,4 +66,4 @@ export default () => {
|
|||||||
</Switch>
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|||||||
787
client/src/assets/unfolded_car-orig.svg
Normal file
787
client/src/assets/unfolded_car-orig.svg
Normal file
@@ -0,0 +1,787 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="1668"
|
||||||
|
height="1160"
|
||||||
|
id="svg2"
|
||||||
|
version="1.1"
|
||||||
|
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
|
||||||
|
sodipodi:docname="unfolded_car.svg">
|
||||||
|
<defs
|
||||||
|
id="defs4" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="0.71043165"
|
||||||
|
inkscape:cx="463.20424"
|
||||||
|
inkscape:cy="602.99002"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="primary"
|
||||||
|
showgrid="false"
|
||||||
|
fit-margin-top="0"
|
||||||
|
fit-margin-left="0"
|
||||||
|
fit-margin-right="0"
|
||||||
|
fit-margin-bottom="0"
|
||||||
|
showguides="true"
|
||||||
|
inkscape:guide-bbox="true"
|
||||||
|
inkscape:snap-global="false"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1017"
|
||||||
|
inkscape:window-x="-8"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1" />
|
||||||
|
<metadata
|
||||||
|
id="metadata7">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="car"
|
||||||
|
inkscape:label="CAR"
|
||||||
|
transform="translate(253.99998,-253.99995)"
|
||||||
|
style="display:inline">
|
||||||
|
<g
|
||||||
|
id="g4113"
|
||||||
|
transform="translate(-13.779768,3.524026)">
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="csssscccsssscsccscccsscccssc"
|
||||||
|
transform="translate(-253.99998,253.99995)"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path3070"
|
||||||
|
d="M 748.57143,752.85714 C 790,737.14285 888.57143,741.42857 940,740 c 51.42857,-1.42857 160.4745,10.23062 201.4286,27.14286 40.9958,16.92944 134.7843,67.65586 151.4285,72.85714 22.8572,7.14286 41.4286,7.14286 80,20 38.5715,12.85714 25.7143,32.85714 25.7143,32.85714 l -30,-4.28571 -5.7562,52.92008 c 0,0 37.1848,1.36563 41.4705,15.65135 4.2857,14.28571 5.7143,31.42857 -2.8571,41.42857 -8.5715,9.99997 -14.2857,-1.42857 -18.5715,12.85717 -4.2857,14.2857 -2.8571,28.5714 -27.1428,27.1428 -24.2857,-1.4285 -98.5715,0 -98.5715,0 0,0 -15.7142,-108.5714 -98.5714,-105.71426 -82.8571,2.85715 -95.7143,105.71426 -95.7143,105.71426 H 562.85714 c 0,0 -5.71428,-104.28569 -97.14286,-105.71426 -91.42857,-1.42857 -98.57142,105.71426 -98.57142,105.71426 H 301.42857 L 282.85714,1000 c -0.51524,0 -26.24328,-10e-6 -21.42857,-17.14285 4.65143,-16.56149 -4.28571,-41.42858 17.14286,-41.42858 21.42857,0 47.14286,1.42857 47.14286,1.42857 L 341.42857,898.57143 300,895.71429 c 0,0 34.28571,-24.28572 118.57143,-32.85715 84.28571,-8.57143 157.14286,-8.57143 192.85714,-31.42857 35.71429,-22.85714 137.14286,-78.57143 137.14286,-78.57143 z"
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
transform="translate(-253.99998,253.99995)"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path3846"
|
||||||
|
d="m 282.85714,1000 h 92.85715"
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
transform="translate(-253.99998,253.99995)"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path3850"
|
||||||
|
d="M 555.71429,1000 H 1072.8571"
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
transform="translate(-253.99998,253.99995)"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path3852"
|
||||||
|
d="m 1245.7143,1000 h 151.4286"
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="csccc"
|
||||||
|
transform="translate(-253.99998,253.99995)"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path3884"
|
||||||
|
d="M 618.57143,847.14286 C 634.28572,828.57143 741.94515,765.61839 770,758.57143 c 29.50156,-7.41035 103.00398,-7.14286 103.00398,-7.14286 l -7.14285,95.71429 z"
|
||||||
|
style="fill:#f0ffeb;fill-opacity:1;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
transform="translate(-253.99998,253.99995)"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path3888"
|
||||||
|
d="m 658.57143,817.14286 v 28.57143"
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="ccccc"
|
||||||
|
transform="translate(-253.99998,253.99995)"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path3892"
|
||||||
|
d="m 898.69729,752.83617 -4.28572,94.32767 h 207.16383 c -11.3076,-20.75266 -46.6124,-74.9056 -72.8572,-88.57143 -14.2857,-10 -82.87805,-4.32767 -130.02091,-5.75624 z"
|
||||||
|
style="fill:#f0ffeb;fill-opacity:1;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="cscsc"
|
||||||
|
transform="translate(-253.99998,253.99995)"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path3896"
|
||||||
|
d="m 1065.7143,760 c 0,0 80,84.28571 85.7143,87.14286 5.7143,2.85714 115.7143,1.42857 115.7143,1.42857 0,0 -77.1429,-47.14286 -102.8572,-58.57143 -25.7143,-11.42857 -90,-31.42857 -98.5714,-30 z"
|
||||||
|
style="fill:#f0ffeb;fill-opacity:1;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="csc"
|
||||||
|
transform="translate(-253.99998,253.99995)"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path3900"
|
||||||
|
d="m 599.63544,837.66076 c -14.07595,30.96709 -18.29873,71.78734 -18.29873,94.30886 0,22.52152 4.22279,91.49368 22.52152,105.56958"
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="cccc"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path3900-6"
|
||||||
|
d="m 632.78482,993.12906 c -1.40759,5.63038 -4.04683,81.90444 -6.51012,106.93324 -3.67029,18.7146 -4.98821,51.2184 -6.15823,87.3149 0,22.5215 7.03798,80.2329 12.66836,105.5696"
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
transform="translate(-253.99998,253.99995)"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path3920"
|
||||||
|
d="m 357.52911,998.12658 c 0,0 15.48355,-85.86329 106.97722,-85.86329 91.49367,0 109.7924,87.27089 109.7924,87.27089"
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path3920-5"
|
||||||
|
d="m 800.28863,1253.5341 c 0,0 15.48355,-85.8633 106.97722,-85.8633 91.49364,0 109.79245,87.2709 109.79245,87.2709"
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="cc"
|
||||||
|
transform="translate(-253.99998,253.99995)"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path3940"
|
||||||
|
d="M 323.74684,943.23038 H 387.0886"
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="cssc"
|
||||||
|
transform="translate(-253.99998,253.99995)"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path3942"
|
||||||
|
d="m 1033.1747,746.16708 c 18.2988,12.66836 56.3038,50.67343 92.9012,104.16203 36.5975,53.48861 8.4456,59.11899 -18.2987,74.60254 -26.7443,15.48354 -49.2658,42.22784 -67.5645,112.60755"
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="cc"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path3944"
|
||||||
|
d="M 1112.7747,1196.0455 H 983.27594"
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
transform="translate(-253.99998,253.99995)"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path3946"
|
||||||
|
d="M 540.51646,941.82278 H 1085.2557"
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
transform="translate(-253.99998,253.99995)"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path3948"
|
||||||
|
d="m 1062.7342,791.21013 v 54.8962"
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<rect
|
||||||
|
rx="2.9330556"
|
||||||
|
ry="7.3789682"
|
||||||
|
y="1144.9166"
|
||||||
|
x="558.65344"
|
||||||
|
height="14.541238"
|
||||||
|
width="41.285542"
|
||||||
|
id="rect3950"
|
||||||
|
style="display:inline;fill:#e6e6e6;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<rect
|
||||||
|
rx="2.9330556"
|
||||||
|
ry="7.3789682"
|
||||||
|
y="1144.9166"
|
||||||
|
x="816.24335"
|
||||||
|
height="14.541238"
|
||||||
|
width="41.285542"
|
||||||
|
id="rect3950-4"
|
||||||
|
style="display:inline;fill:#e6e6e6;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<rect
|
||||||
|
ry="7.691968"
|
||||||
|
rx="1.6302098"
|
||||||
|
y="1146.318"
|
||||||
|
x="259.53336"
|
||||||
|
height="11.738417"
|
||||||
|
width="18.776392"
|
||||||
|
id="rect4014"
|
||||||
|
style="fill:#ffcb00;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
id="g4451"
|
||||||
|
transform="translate(-13.779768,15.524026)">
|
||||||
|
<path
|
||||||
|
inkscape:transform-center-x="-1.6185511"
|
||||||
|
transform="translate(-37.23036,423.94932)"
|
||||||
|
d="m 99.997791,388.63797 -11.711946,16.12011 -18.950327,-6.15733 0,-19.92556 18.950327,-6.15733 z"
|
||||||
|
inkscape:randomized="0"
|
||||||
|
inkscape:rounded="0"
|
||||||
|
inkscape:flatsided="true"
|
||||||
|
sodipodi:arg2="0.62831853"
|
||||||
|
sodipodi:arg1="0"
|
||||||
|
sodipodi:r2="13.712585"
|
||||||
|
sodipodi:r1="16.949688"
|
||||||
|
sodipodi:cy="388.63797"
|
||||||
|
sodipodi:cx="83.048103"
|
||||||
|
sodipodi:sides="5"
|
||||||
|
id="path4141"
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
sodipodi:type="star" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="cc"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path4143"
|
||||||
|
d="m 31.741795,745.02273 315.301265,-111.2"
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="cc"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path4145"
|
||||||
|
d="M 30.3342,888.59742 345.63546,1004.0202"
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="ccssccccc"
|
||||||
|
transform="translate(-253.99998,253.99995)"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path4165"
|
||||||
|
d="m 595.41266,378.78481 140.75949,38.00506 c 0,0 -8.44557,49.22222 -8.44557,71.9633 0,20.14574 0,124.39619 0,147.62151 0,30.96708 8.44557,78.82532 8.44557,78.82532 l -140.7595,33.78228 c 0,0 -17.24303,-61.90221 -17.24303,-92.90127 0.38411,-33.80191 1.75909,-154.79945 2.1114,-185.80253 -1.4076,-30.96709 15.13164,-91.49367 15.13164,-91.49367 z"
|
||||||
|
style="fill:#f0ffeb;fill-opacity:1;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="csccsc"
|
||||||
|
transform="translate(-253.99998,253.99995)"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path4175"
|
||||||
|
d="m 736.17216,416.78987 c 0,0 94.30885,5.63038 152.02025,5.63038 106.97721,0 201.28609,-5.63038 201.28609,-5.63038 m -1.4076,297.00254 c 0,0 -77.4177,-5.63039 -199.87849,-5.63039 -68.97215,0 -152.02026,7.03798 -152.02026,7.03798"
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<rect
|
||||||
|
ry="34.43626"
|
||||||
|
rx="31.189682"
|
||||||
|
y="729.20502"
|
||||||
|
x="528.6228"
|
||||||
|
height="181.57974"
|
||||||
|
width="106.97722"
|
||||||
|
id="rect4177"
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="cccccccc"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path4135-9"
|
||||||
|
d="M 345.77724,632.78476 H 51.589892 l -14.07595,102.75443 -7.03797,11.26076 v 142.16709 l 7.03797,14.07595 15.48355,101.34681 H 341.55445"
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="csscccssccc"
|
||||||
|
transform="translate(-253.99998,253.99995)"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path4203"
|
||||||
|
d="m 1086.6633,416.78987 c 0,0 -12.6684,47.85823 -12.6684,73.19494 0,25.33671 0,121.05316 0,146.38987 0,25.33671 12.6684,77.41773 12.6684,77.41773 l 205.5089,38.00505 108.3848,10e-6 c 0,0 14.0759,-81.64051 14.0759,-115.42279 0,-33.78227 0,-109.7924 0,-147.79746 0,-38.00507 -14.0759,-109.79241 -14.0759,-109.79241 h -108.3848 z"
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="csscsccsc"
|
||||||
|
transform="translate(-253.99998,253.99995)"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path4205"
|
||||||
|
d="m 1097.9241,435.08861 c 0,0 -8.4456,40.82025 -8.4456,56.3038 0,15.48354 0,125.27594 0,144.98227 0,19.70633 7.038,57.7114 7.038,57.7114 0,0 94.3088,26.7443 123.8683,26.7443 29.5595,0 42.2279,0 42.2279,0 V 408.3443 c 0,0 -22.5216,0 -47.8583,0 -25.3367,0 -116.8303,26.74431 -116.8303,26.74431 z"
|
||||||
|
style="fill:#f0ffeb;fill-opacity:1;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
transform="translate(-253.99998,253.99995)"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path4207"
|
||||||
|
d="m 1292.1722,378.78481 c 0,0 30.967,40.82025 30.967,111.2 0,70.37975 0,81.64051 0,146.38987 0,64.74937 -30.967,116.83038 -30.967,116.83038"
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
transform="translate(-253.99998,253.99995)"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path4209"
|
||||||
|
d="m 578.52152,489.98481 32.37468,-32.37468 v -40.82026 112.6076"
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path4209-7"
|
||||||
|
d="m 323.81774,865.03792 32.37468,-32.37468 v -40.82026 112.6076"
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path4209-76"
|
||||||
|
d="m 1005.0937,787.62021 -32.37469,-32.37468 v -40.82026 112.6076"
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="cssc"
|
||||||
|
transform="translate(-253.99998,253.99995)"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path4243"
|
||||||
|
d="m 595.41266,378.78481 c 0,0 -42.22785,78.82532 -42.22785,111.2 0,32.37468 0,105.56962 0,146.38987 0,40.82026 42.22785,114.01519 42.22785,114.01519"
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
</g>
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m -106.79241,740.91638 1.4076,-91.49367 c 0,0 5.630382,-23.92911 -25.33671,-25.3367 -30.96709,-1.4076 -26.74431,2.81518 -26.74431,2.81518 l 1.40759,415.24051 c 0,0 9.85317,0 28.1519,0 28.151917,0 22.52153,-22.5216 22.52153,-22.5216 l -1e-5,-95.71637 c -12.67694,0 -11.26075,-9.61416 -11.26075,-16.89115 0,-16.94968 -10e-6,-136.5367 -10e-6,-149.20506 0.45035,-18.89009 9.85317,-16.89114 9.85317,-16.89114 z"
|
||||||
|
id="path4245"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="ccsccsccccc" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:2.20000005;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m -155.98734,646.16195 h 49.26582"
|
||||||
|
id="path4247"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:2.20000005;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m -155.13671,1020.4303 h 49.26582"
|
||||||
|
id="path4247-2"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:2.20000005;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m -156.69114,705.05816 h 49.26582"
|
||||||
|
id="path4247-1"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:2.20000005;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m -153.87595,964.87082 h 49.26582"
|
||||||
|
id="path4247-0"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="M -147.09621,703.82272 V 964.22778"
|
||||||
|
id="path4281-1"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="M -137.24304,705.38727 V 965.79233"
|
||||||
|
id="path4281-8"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<g
|
||||||
|
id="g4428"
|
||||||
|
transform="translate(-13.779768,15.524026)">
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="csssscccsssscsccscccsscccssc"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path3070-9"
|
||||||
|
d="m 494.57145,641.6137 c 41.42857,15.71429 140,11.42857 191.42857,12.85714 51.42857,1.42857 160.4745,-10.23062 201.4286,-27.14286 40.9958,-16.92944 134.78428,-67.65586 151.42848,-72.85714 22.8572,-7.14286 41.4286,-7.14286 80,-20 38.5715,-12.85714 25.7143,-32.85714 25.7143,-32.85714 l -30,4.28571 -5.7562,-52.92008 c 0,0 37.1848,-1.36563 41.4705,-15.65135 4.2857,-14.28571 5.7143,-31.42857 -2.8571,-41.42857 -8.5715,-9.99997 -14.2857,1.42857 -18.5715,-12.85717 -4.2857,-14.2857 -2.8571,-28.5714 -27.1428,-27.1428 -24.2857,1.4285 -98.5715,0 -98.5715,0 0,0 -15.71418,108.5714 -98.57138,105.71426 -82.8571,-2.85715 -95.7143,-105.71426 -95.7143,-105.71426 H 308.85716 c 0,0 -5.71428,104.28569 -97.14286,105.71426 -91.42857,1.42857 -98.57142,-105.71426 -98.57142,-105.71426 H 47.428587 l -18.57143,38.5714 c -0.51524,0 -26.243277,10e-6 -21.428567,17.14285 4.65143,16.56149 -4.28571,41.42858 17.14286,41.42858 21.428567,0 47.142857,-1.42857 47.142857,-1.42857 l 15.71428,44.28571 -41.42857,2.85714 c 0,0 34.28571,24.28572 118.571433,32.85715 84.28571,8.57143 157.14286,8.57143 192.85714,31.42857 35.71429,22.85714 137.14286,78.57143 137.14286,78.57143 z"
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path3846-2"
|
||||||
|
d="M 28.857157,394.47084 H 121.71431"
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path3850-6"
|
||||||
|
d="M 301.71431,394.47084 H 818.85712"
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path3852-6"
|
||||||
|
d="M 991.71432,394.47084 H 1143.1429"
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="csccc"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path3884-4"
|
||||||
|
d="m 364.57145,547.32798 c 15.71429,18.57143 123.37372,81.52447 151.42857,88.57143 29.50156,7.41035 103.00398,7.14286 103.00398,7.14286 l -7.14285,-95.71429 z"
|
||||||
|
style="display:inline;fill:#f0ffeb;fill-opacity:1;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path3888-9"
|
||||||
|
d="M 404.57145,577.32798 V 548.75655"
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="ccccc"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path3892-5"
|
||||||
|
d="M 644.69731,641.63467 640.41159,547.307 h 207.16383 c -11.3076,20.75266 -46.6124,74.9056 -72.8572,88.57143 -14.2857,10 -82.87805,4.32767 -130.02091,5.75624 z"
|
||||||
|
style="display:inline;fill:#f0ffeb;fill-opacity:1;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="cscsc"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path3896-0"
|
||||||
|
d="m 811.71432,634.47084 c 0,0 80,-84.28571 85.7143,-87.14286 5.7143,-2.85714 115.71428,-1.42857 115.71428,-1.42857 0,0 -77.14288,47.14286 -102.85718,58.57143 -25.7143,11.42857 -90,31.42857 -98.5714,30 z"
|
||||||
|
style="display:inline;fill:#f0ffeb;fill-opacity:1;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="csc"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path3900-4"
|
||||||
|
d="m 345.63546,556.81008 c -14.07595,-30.96709 -18.29873,-71.78734 -18.29873,-94.30886 0,-22.52152 4.22279,-91.49368 22.52152,-105.56958"
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="cccc"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path3900-6-8"
|
||||||
|
d="m 632.78482,655.34173 c -1.40759,-5.63038 -4.04683,-81.90444 -6.51012,-106.93324 -3.67029,-18.7146 -4.98821,-51.2184 -6.15823,-87.3149 0,-22.5215 7.03798,-80.2329 12.66836,-105.5696"
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path3920-7"
|
||||||
|
d="m 103.52913,396.34426 c 0,0 15.48355,85.86329 106.97722,85.86329 91.49367,0 109.7924,-87.27089 109.7924,-87.27089"
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path3920-5-1"
|
||||||
|
d="m 800.28863,394.93669 c 0,0 15.48355,85.8633 106.97722,85.8633 91.49364,0 109.79245,-87.2709 109.79245,-87.2709"
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="cc"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path3940-7"
|
||||||
|
d="M 69.746857,451.24046 H 133.08862"
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="cssc"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path3942-2"
|
||||||
|
d="m 779.17472,648.30376 c 18.2988,-12.66836 56.3038,-50.67343 92.9012,-104.16203 36.5975,-53.48861 8.4456,-59.11899 -18.2987,-74.60254 -26.7443,-15.48354 -49.2658,-42.22784 -67.5645,-112.60755"
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="cc"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path3944-7"
|
||||||
|
d="M 1112.7747,452.42529 H 983.27594"
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path3946-2"
|
||||||
|
d="M 286.51648,452.64806 H 831.25572"
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path3948-2"
|
||||||
|
d="m 808.73422,603.26071 v -54.8962"
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<rect
|
||||||
|
transform="scale(1,-1)"
|
||||||
|
rx="2.9330556"
|
||||||
|
ry="7.3789682"
|
||||||
|
y="-503.55417"
|
||||||
|
x="558.65344"
|
||||||
|
height="14.541238"
|
||||||
|
width="41.285542"
|
||||||
|
id="rect3950-6"
|
||||||
|
style="display:inline;fill:#e6e6e6;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<rect
|
||||||
|
transform="scale(1,-1)"
|
||||||
|
rx="2.9330556"
|
||||||
|
ry="7.3789682"
|
||||||
|
y="-503.55417"
|
||||||
|
x="816.24329"
|
||||||
|
height="14.541238"
|
||||||
|
width="41.285542"
|
||||||
|
id="rect3950-4-1"
|
||||||
|
style="display:inline;fill:#e6e6e6;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<rect
|
||||||
|
transform="scale(1,-1)"
|
||||||
|
ry="7.691968"
|
||||||
|
rx="1.6302098"
|
||||||
|
y="-502.1528"
|
||||||
|
x="259.53333"
|
||||||
|
height="11.738417"
|
||||||
|
width="18.776392"
|
||||||
|
id="rect4014-0"
|
||||||
|
style="display:inline;fill:#ffcb00;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<circle
|
||||||
|
transform="translate(941.34179,284.00501)"
|
||||||
|
id="path4335"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
cx="59.118988"
|
||||||
|
cy="211.28101"
|
||||||
|
r="16.89114" />
|
||||||
|
</g>
|
||||||
|
<path
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="M -126.57469,704.93158 V 965.33664"
|
||||||
|
id="path4281-8-0"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
style="fill:#ffffc0;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m -153.87595,992.4894 v -26.0405 h 22.52152 22.52152 v 26.0405 26.0405 h -22.52152 -22.52152 z"
|
||||||
|
id="path4361"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
style="fill:#ffffc0;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m -153.87595,675.78054 v -27.4481 h 22.52152 22.52152 v 27.4481 27.4481 h -22.52152 -22.52152 z"
|
||||||
|
id="path4363"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m -157.39494,624.37968 c 0,0 -4.22278,-12.66836 -18.29873,-12.66836 -14.07595,0 -14.07595,8.44557 -14.07595,8.44557 v 423.68601 c 0,0 1.40759,9.8532 14.07595,9.8532 12.66835,0 18.29873,-9.8532 18.29873,-9.8532"
|
||||||
|
id="path4377"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="csccsc" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m -191.17722,624.37967 c 0,0 -35.18987,-1.40759 -35.18987,21.11393 0,22.52152 0,349.08354 0,371.605 0,22.5215 36.59747,25.3367 36.59747,25.3367"
|
||||||
|
id="path4381"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="cssc" />
|
||||||
|
<rect
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="rect4467"
|
||||||
|
width="16.891144"
|
||||||
|
height="35.189873"
|
||||||
|
x="-216.51393"
|
||||||
|
y="648.30878"
|
||||||
|
rx="7.7417746"
|
||||||
|
ry="6.2843671" />
|
||||||
|
<rect
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="rect4467-6"
|
||||||
|
width="16.891144"
|
||||||
|
height="35.189873"
|
||||||
|
x="-216.51393"
|
||||||
|
y="985.57465"
|
||||||
|
rx="7.7417746"
|
||||||
|
ry="6.2843671" />
|
||||||
|
<rect
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="rect4487"
|
||||||
|
width="30.967089"
|
||||||
|
height="377.23544"
|
||||||
|
x="-62.939251"
|
||||||
|
y="645.49365" />
|
||||||
|
<path
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 1200.9342,633.85815 v 73.19494 h 67.5645 v -83.0481 l -25.3367,-14.07595 z"
|
||||||
|
id="path4499"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="cccccc" />
|
||||||
|
<path
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 1200.9342,963.23538 h 67.5645 v 78.82532 l -23.9291,14.0759 -43.6354,-18.2988 z"
|
||||||
|
id="path4501"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="cccccc" />
|
||||||
|
<path
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="M 1200.9342,692.97714 V 977.31132"
|
||||||
|
id="path4503"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="M 1268.4987,693.56955 V 977.90373"
|
||||||
|
id="path4505"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<rect
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="rect4534"
|
||||||
|
width="14.075921"
|
||||||
|
height="147.79749"
|
||||||
|
x="1216.5645"
|
||||||
|
y="759.50879" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="M 1224.1949,758.1012 V 704.61259"
|
||||||
|
id="path4536"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="M 1224.1949,961.97968 V 908.49107"
|
||||||
|
id="path4536-0"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="M 1254.5696,707.42778 V 963.61006"
|
||||||
|
id="path4556"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 1268.757,624.97209 c 0,0 4.2228,-12.66836 18.2987,-12.66836 14.076,0 14.076,8.44557 14.076,8.44557 v 423.686 c 0,0 -1.4076,9.8532 -14.076,9.8532 -12.6683,0 -18.2987,-9.8532 -18.2987,-9.8532"
|
||||||
|
id="path4377-2"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="csccsc" />
|
||||||
|
<path
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 1302.5393,624.97208 c 0,0 35.1898,-1.40759 35.1898,21.11393 0,22.52152 0,349.08354 0,371.60499 0,22.5215 -36.5974,25.3367 -36.5974,25.3367"
|
||||||
|
id="path4381-0"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="cssc" />
|
||||||
|
<rect
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="rect4467-5"
|
||||||
|
width="16.891144"
|
||||||
|
height="35.189873"
|
||||||
|
x="-1329.876"
|
||||||
|
y="648.90118"
|
||||||
|
rx="7.7417746"
|
||||||
|
ry="6.2843671"
|
||||||
|
transform="scale(-1,1)" />
|
||||||
|
<rect
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="rect4467-6-5"
|
||||||
|
width="16.891144"
|
||||||
|
height="35.189873"
|
||||||
|
x="-1329.876"
|
||||||
|
y="986.16711"
|
||||||
|
rx="7.7417746"
|
||||||
|
ry="6.2843671"
|
||||||
|
transform="scale(-1,1)" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 1199.6734,632.82525 67.5645,74.60253"
|
||||||
|
id="path4585"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 1199.6734,708.02018 67.5645,-73.19493"
|
||||||
|
id="path4587"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="cc" />
|
||||||
|
<path
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 1199.8962,962.97968 67.5645,74.60252"
|
||||||
|
id="path4585-9"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 1199.8962,1038.1746 67.5645,-73.19492"
|
||||||
|
id="path4587-0"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="cc" />
|
||||||
|
<circle
|
||||||
|
style="fill:#c8c8c8;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4610"
|
||||||
|
transform="translate(78.488602,1074.6683)"
|
||||||
|
cx="119.64557"
|
||||||
|
cy="202.83545"
|
||||||
|
r="76.010124" />
|
||||||
|
<circle
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4612"
|
||||||
|
transform="translate(94.675942,1204.8708)"
|
||||||
|
cx="103.45823"
|
||||||
|
cy="72.632912"
|
||||||
|
r="44.339241" />
|
||||||
|
<circle
|
||||||
|
style="fill:#c8c8c8;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4610-9"
|
||||||
|
transform="translate(78.488602,187.66068)"
|
||||||
|
cx="119.64557"
|
||||||
|
cy="202.83545"
|
||||||
|
r="76.010124" />
|
||||||
|
<circle
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4612-4"
|
||||||
|
transform="translate(94.675942,317.86322)"
|
||||||
|
cx="103.45823"
|
||||||
|
cy="72.632912"
|
||||||
|
r="44.339241" />
|
||||||
|
<circle
|
||||||
|
style="fill:#c8c8c8;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4610-5"
|
||||||
|
transform="translate(773.02535,187.66068)"
|
||||||
|
cx="119.64557"
|
||||||
|
cy="202.83545"
|
||||||
|
r="76.010124" />
|
||||||
|
<circle
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4612-1"
|
||||||
|
transform="translate(789.21269,317.86322)"
|
||||||
|
cx="103.45823"
|
||||||
|
cy="72.632912"
|
||||||
|
r="44.339241" />
|
||||||
|
<circle
|
||||||
|
style="fill:#c8c8c8;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4610-4"
|
||||||
|
transform="translate(773.02535,1074.6683)"
|
||||||
|
cx="119.64557"
|
||||||
|
cy="202.83545"
|
||||||
|
r="76.010124" />
|
||||||
|
<circle
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4612-3"
|
||||||
|
transform="translate(789.21269,1204.8708)"
|
||||||
|
cx="103.45823"
|
||||||
|
cy="72.632912"
|
||||||
|
r="44.339241" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 1338.5088,829.07334 h 40.8203"
|
||||||
|
id="path3083"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<circle
|
||||||
|
style="fill:#3c3c3c;fill-opacity:1;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||||
|
id="path3853"
|
||||||
|
transform="translate(1441.2633,600.30879)"
|
||||||
|
cx="-59.118988"
|
||||||
|
cy="229.57974"
|
||||||
|
r="4.222785" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 778.06325,845.37208 -38.7088,-38.70886"
|
||||||
|
id="path3855"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<g
|
||||||
|
id="g3952"
|
||||||
|
transform="translate(-13.779768,15.524026)">
|
||||||
|
<circle
|
||||||
|
transform="translate(-79.458203,449.80248)"
|
||||||
|
id="path3857"
|
||||||
|
style="fill:#3c3c3c;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||||
|
cx="-81.640511"
|
||||||
|
cy="188.75949"
|
||||||
|
r="4.222785" />
|
||||||
|
<circle
|
||||||
|
transform="translate(-79.458203,569.9172)"
|
||||||
|
id="path3857-2"
|
||||||
|
style="display:inline;fill:#3c3c3c;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||||
|
cx="-81.640511"
|
||||||
|
cy="188.75949"
|
||||||
|
r="4.222785" />
|
||||||
|
<circle
|
||||||
|
transform="translate(-79.458203,690.03203)"
|
||||||
|
id="path3857-3"
|
||||||
|
style="display:inline;fill:#3c3c3c;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||||
|
cx="-81.640511"
|
||||||
|
cy="188.75949"
|
||||||
|
r="4.222785" />
|
||||||
|
<circle
|
||||||
|
transform="translate(-79.458203,810.14679)"
|
||||||
|
id="path3857-7"
|
||||||
|
style="display:inline;fill:#3c3c3c;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||||
|
cx="-81.640511"
|
||||||
|
cy="188.75949"
|
||||||
|
r="4.222785" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
id="g3946"
|
||||||
|
transform="translate(-13.779768,17.524026)">
|
||||||
|
<circle
|
||||||
|
transform="translate(1381.6254,448.24805)"
|
||||||
|
id="path3857-97"
|
||||||
|
style="display:inline;fill:#3c3c3c;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||||
|
cx="-81.640511"
|
||||||
|
cy="188.75949"
|
||||||
|
r="4.222785" />
|
||||||
|
<circle
|
||||||
|
transform="translate(1381.6254,568.36277)"
|
||||||
|
id="path3857-2-3"
|
||||||
|
style="display:inline;fill:#3c3c3c;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||||
|
cx="-81.640511"
|
||||||
|
cy="188.75949"
|
||||||
|
r="4.222785" />
|
||||||
|
<circle
|
||||||
|
transform="translate(1381.6254,688.4776)"
|
||||||
|
id="path3857-3-6"
|
||||||
|
style="display:inline;fill:#3c3c3c;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||||
|
cx="-81.640511"
|
||||||
|
cy="188.75949"
|
||||||
|
r="4.222785" />
|
||||||
|
<circle
|
||||||
|
transform="translate(1381.6254,808.59236)"
|
||||||
|
id="path3857-7-1"
|
||||||
|
style="display:inline;fill:#3c3c3c;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||||
|
cx="-81.640511"
|
||||||
|
cy="188.75949"
|
||||||
|
r="4.222785" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="primary"
|
||||||
|
inkscape:label="primary">
|
||||||
|
<circle
|
||||||
|
style="fill:#a02c2c"
|
||||||
|
id="01"
|
||||||
|
cx="320.93164"
|
||||||
|
cy="-388.63797"
|
||||||
|
r="66.15696"
|
||||||
|
transform="scale(1,-1)"
|
||||||
|
inkscape:label="01" />
|
||||||
|
<circle
|
||||||
|
style="fill:#a02c2c"
|
||||||
|
id="02"
|
||||||
|
cx="320.93164"
|
||||||
|
cy="-757.42786"
|
||||||
|
r="66.15696"
|
||||||
|
transform="scale(1,-1)"
|
||||||
|
inkscape:label="01" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 43 KiB |
File diff suppressed because it is too large
Load Diff
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 24 KiB |
144
client/src/assets/unfolded_car_clean.svg
Normal file
144
client/src/assets/unfolded_car_clean.svg
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
<svg id="svg166" version="1.1" viewBox="0 0 1668 1160" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g id="g158" transform="translate(254 -254)">
|
||||||
|
<g id="g34" transform="translate(-13.78 3.524)" stroke="#000">
|
||||||
|
<path id="path10" d="m494.57 1006.9c41.429-15.714 140-11.427 191.43-12.857 51.429-1.429 160.48 10.23 201.43 27.143 40.995 16.93 134.78 67.656 151.43 72.857 22.857 7.143 41.429 7.143 80 20 38.572 12.857 25.714 32.857 25.714 32.857l-30-4.286-5.756 52.92s37.185 1.366 41.47 15.652c4.286 14.286 5.715 31.428-2.856 41.428-8.572 10-14.286-1.428-18.572 12.858-4.286 14.285-2.857 28.571-27.143 27.142-24.285-1.428-98.571 0-98.571 0s-15.714-108.57-98.573-105.71c-82.857 2.857-95.714 105.71-95.714 105.71h-500s-5.714-104.28-97.143-105.71c-91.428-1.428-98.571 105.71-98.571 105.71h-65.713l-18.572-38.571c-0.515 0-26.243 0-21.428-17.143 4.651-16.561-4.286-41.428 17.142-41.428 21.429 0 47.143 1.428 47.143 1.428l15.715-44.286-41.429-2.857s34.286-24.285 118.57-32.857c84.286-8.571 157.14-8.571 192.86-31.428 35.714-22.858 137.14-78.572 137.14-78.572z" fill="none" stroke-width="5"/>
|
||||||
|
<path id="path12" d="m28.857 1254h92.857m180 0h517.14m172.86 0h151.43" fill="none" stroke-width="2"/>
|
||||||
|
<path id="path14" d="m364.57 1101.1c15.715-18.572 123.37-81.525 151.43-88.572 29.502-7.41 103-7.142 103-7.142l-7.143 95.714z" fill="#f0ffeb" stroke-width="5"/>
|
||||||
|
<path id="path16" d="m404.57 1071.1v28.571" fill="none" stroke-width="2"/>
|
||||||
|
<path id="path18" d="m644.7 1006.8-4.285 94.328h207.16c-11.307-20.753-46.612-74.906-72.857-88.572-14.285-10-82.878-4.327-130.02-5.756zm167.02 7.164s80 84.286 85.715 87.143c5.714 2.857 115.71 1.428 115.71 1.428s-77.143-47.141-102.86-58.571c-25.715-11.429-90-31.429-98.572-30z" fill="#f0ffeb" stroke-width="5"/>
|
||||||
|
<g fill="none">
|
||||||
|
<path id="path20" d="m345.64 1091.7c-14.075 30.968-18.298 71.788-18.298 94.31 0 22.521 4.223 91.493 22.521 105.57m282.93-298.41c-1.408 5.63-4.047 81.903-6.51 106.93-3.67 18.715-4.989 51.219-6.159 87.315 0 22.522 7.038 80.233 12.669 105.57" stroke-width="5"/>
|
||||||
|
<path id="path22" d="m103.53 1252.1s15.483-85.864 106.98-85.864c91.494 0 109.79 87.271 109.79 87.271m479.99 0s15.483-85.863 106.98-85.863c91.493 0 109.79 87.27 109.79 87.27m-947.31-57.711h63.342" stroke-width="2"/>
|
||||||
|
<path id="path24" d="m779.18 1000.2c18.299 12.668 56.304 50.673 92.9 104.16 36.598 53.489 8.447 59.12-18.298 74.603-26.744 15.483-49.266 42.227-67.564 112.61" stroke-width="5"/>
|
||||||
|
<path id="path26" d="m1112.8 1196h-129.5m-696.76-0.222h544.74m-22.522-150.61v54.896" stroke-width="2"/>
|
||||||
|
</g>
|
||||||
|
<g stroke-width="2">
|
||||||
|
<rect id="rect28" x="558.65" y="1144.9" width="41.286" height="14.541" rx="2.933" ry="7.379" fill="#e6e6e6"/>
|
||||||
|
<rect id="rect30" x="816.24" y="1144.9" width="41.286" height="14.541" rx="2.933" ry="7.379" fill="#e6e6e6"/>
|
||||||
|
<rect id="rect32" x="259.53" y="1146.3" width="18.776" height="11.738" rx="1.63" ry="7.692" fill="#ffcb00"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g id="g54" transform="translate(-13.78 15.524)" stroke="#000">
|
||||||
|
<path id="path36" d="m62.767 812.59-11.712 16.12-18.95-6.157v-19.925l18.95-6.158z" fill="none" stroke-width="5"/>
|
||||||
|
<path id="path38" d="m31.742 745.02 315.3-111.2m-316.71 254.77 315.3 115.42" fill="none" stroke-width="2"/>
|
||||||
|
<path id="path40" d="m341.41 632.78 140.76 38.005s-8.446 49.222-8.446 71.963v147.62c0 30.967 8.445 78.825 8.445 78.825l-140.76 33.782s-17.242-61.902-17.242-92.901l2.111-185.8c-1.408-30.967 15.132-91.493 15.132-91.493z" fill="#f0ffeb" stroke-width="5"/>
|
||||||
|
<g fill="none">
|
||||||
|
<path id="path42" d="m482.17 670.79s94.309 5.63 152.02 5.63c106.98 0 201.29-5.63 201.29-5.63m-1.408 297s-77.418-5.63-199.88-5.63c-68.972 0-152.02 7.038-152.02 7.038" stroke-width="5"/>
|
||||||
|
<rect id="rect44" x="528.62" y="729.2" width="106.98" height="181.58" rx="31.19" ry="34.436" stroke-width="2"/>
|
||||||
|
<path id="path46" d="m345.78 632.78h-294.19l-14.076 102.75-7.038 11.26v142.17l7.038 14.076 15.483 101.35h288.56m491.11-333.6s-12.668 47.858-12.668 73.195v146.39c0 25.336 12.668 77.417 12.668 77.417l205.51 38.005h108.38s14.076-81.64 14.076-115.42v-147.8c0-38.005-14.076-109.79-14.076-109.79h-108.38z" stroke-width="5"/>
|
||||||
|
</g>
|
||||||
|
<path id="path48" d="m843.92 689.09s-8.445 40.82-8.445 56.303v144.98c0 19.706 7.038 57.711 7.038 57.711s94.308 26.744 123.87 26.744h42.228v-312.49h-47.859c-25.336 0-116.83 26.745-116.83 26.745z" fill="#f0ffeb" stroke-width="5"/>
|
||||||
|
<path id="path50" d="m1038.2 632.78s30.967 40.82 30.967 111.2v146.39c0 64.749-30.967 116.83-30.967 116.83m-713.65-263.22 32.374-32.375v-40.82 112.61m-33.078 81.641 32.374-32.375v-40.82 112.61m648.9-116.83-32.375-32.374v-40.82 112.61" fill="none" stroke-width="2"/>
|
||||||
|
<path id="path52" d="m341.41 632.78s-42.228 78.825-42.228 111.2v146.39c0 40.82 42.228 114.02 42.228 114.02" fill="none" stroke-width="2"/>
|
||||||
|
</g>
|
||||||
|
<g fill="none" stroke="#000">
|
||||||
|
<path id="path56" d="m-106.79 740.92 1.407-91.493s5.63-23.93-25.337-25.337c-30.967-1.408-26.744 2.815-26.744 2.815l1.408 415.24h28.152c28.152 0 22.521-22.52 22.521-22.52v-95.717c-12.677 0-11.26-9.614-11.26-16.891v-149.2c0.45-18.89 9.853-16.892 9.853-16.892z" stroke-width="5"/>
|
||||||
|
<path id="path58" d="m-155.99 646.16h49.265m-48.415 374.27h49.266m-50.82-315.37h49.266m-46.451 259.81h49.266" stroke-width="2.2"/>
|
||||||
|
<path id="path60" d="m-147.1 703.82v260.4m9.853-258.84v260.4" stroke-width="2"/>
|
||||||
|
</g>
|
||||||
|
<g id="g88" transform="translate(-13.78 15.524)" stroke="#000">
|
||||||
|
<path id="path62" d="m494.57 641.61c41.429 15.714 140 11.428 191.43 12.856s160.48-10.23 201.43-27.143c40.995-16.93 134.78-67.656 151.43-72.857 22.857-7.143 41.429-7.143 80-20 38.572-12.857 25.714-32.857 25.714-32.857l-30 4.285-5.756-52.92s37.185-1.365 41.47-15.651c4.286-14.286 5.715-31.429-2.856-41.429-8.572-10-14.286 1.429-18.572-12.857-4.286-14.285-2.857-28.571-27.143-27.143-24.285 1.429-98.571 0-98.571 0s-15.714 108.57-98.572 105.72c-82.857-2.857-95.714-105.72-95.714-105.72h-500s-5.714 104.29-97.143 105.72c-91.428 1.428-98.571-105.72-98.571-105.72h-65.714l-18.572 38.572c-0.515 0-26.243 0-21.428 17.143 4.651 16.561-4.286 41.428 17.142 41.428 21.429 0 47.143-1.428 47.143-1.428l15.715 44.285-41.429 2.859s34.286 24.285 118.57 32.857c84.286 8.571 157.14 8.571 192.86 31.428 35.714 22.857 137.14 78.572 137.14 78.572z" fill="none" stroke-width="5"/>
|
||||||
|
<path id="path64" d="m28.857 394.47h92.857m180 0h517.14m172.86 0h151.43" fill="none" stroke-width="2"/>
|
||||||
|
<path id="path66" d="m364.57 547.33c15.715 18.571 123.37 81.524 151.43 88.571 29.502 7.41 103 7.143 103 7.143l-7.143-95.714z" fill="#f0ffeb" stroke-width="5"/>
|
||||||
|
<path id="path68" d="m404.57 577.33v-28.571" fill="none" stroke-width="2"/>
|
||||||
|
<path id="path70" d="m644.7 641.64-4.285-94.328h207.16c-11.307 20.753-46.612 74.906-72.857 88.571-14.285 10-82.878 4.328-130.02 5.757zm167.02-7.165s80-84.285 85.715-87.142c5.714-2.857 115.71-1.429 115.71-1.429s-77.143 47.143-102.86 58.572c-25.715 11.428-90 31.428-98.572 30z" fill="#f0ffeb" stroke-width="5"/>
|
||||||
|
<g fill="none">
|
||||||
|
<path id="path72" d="m345.64 556.81c-14.075-30.967-18.298-71.787-18.298-94.309 0-22.521 4.223-91.493 22.521-105.57m282.93 298.41c-1.408-5.63-4.047-81.905-6.51-106.93-3.67-18.714-4.989-51.218-6.159-87.314 0-22.522 7.038-80.233 12.669-105.57" stroke-width="5"/>
|
||||||
|
<path id="path74" d="m103.53 396.34s15.483 85.864 106.98 85.864c91.494 0 109.79-87.271 109.79-87.271m479.99 0s15.483 85.863 106.98 85.863c91.493 0 109.79-87.27 109.79-87.27m-947.31 57.71h63.342" stroke-width="2"/>
|
||||||
|
<path id="path76" d="m779.18 648.3c18.299-12.669 56.304-50.674 92.9-104.16 36.598-53.489 8.447-59.12-18.298-74.603-26.744-15.483-49.266-42.228-67.564-112.61" stroke-width="5"/>
|
||||||
|
<path id="path78" d="m1112.8 452.42h-129.5m-696.76 0.223h544.74m-22.522 150.61v-54.895" stroke-width="2"/>
|
||||||
|
</g>
|
||||||
|
<g stroke-width="2">
|
||||||
|
<rect id="rect80" transform="scale(1 -1)" x="558.65" y="-503.55" width="41.286" height="14.541" rx="2.933" ry="7.379" fill="#e6e6e6"/>
|
||||||
|
<rect id="rect82" transform="scale(1 -1)" x="816.24" y="-503.55" width="41.286" height="14.541" rx="2.933" ry="7.379" fill="#e6e6e6"/>
|
||||||
|
<rect id="rect84" transform="scale(1 -1)" x="259.53" y="-502.15" width="18.776" height="11.738" rx="1.63" ry="7.692" fill="#ffcb00"/>
|
||||||
|
<circle id="circle86" transform="translate(941.34 284)" cx="59.119" cy="211.28" r="16.891" fill="#fff"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<path id="path90" d="m-126.58 704.93v260.4" fill="none" stroke="#000" stroke-width="2"/>
|
||||||
|
<path id="path92" d="m-153.88 992.49v-26.041h45.043v52.08h-45.043zm0-316.71v-27.448h45.043v54.898h-45.043z" fill="#ffffc0"/>
|
||||||
|
<g fill="none" stroke="#000">
|
||||||
|
<path id="path94" d="m-157.4 624.38s-4.223-12.669-18.299-12.669-14.076 8.446-14.076 8.446v423.69s1.408 9.853 14.076 9.853c12.669 0 18.3-9.853 18.3-9.853" stroke-width="5"/>
|
||||||
|
<path id="path96" d="m-191.18 624.38s-35.19-1.408-35.19 21.114v371.6c0 22.521 36.597 25.336 36.597 25.336" stroke-width="5"/>
|
||||||
|
<g stroke-width="2">
|
||||||
|
<rect id="rect98" x="-216.51" y="648.31" width="16.891" height="35.19" rx="7.742" ry="6.284"/>
|
||||||
|
<rect id="rect100" x="-216.51" y="985.58" width="16.891" height="35.19" rx="7.742" ry="6.284"/>
|
||||||
|
<path id="path102" d="m-62.939 645.49h30.967v377.24h-30.967z"/>
|
||||||
|
</g>
|
||||||
|
<path id="path104" d="m1200.9 633.86v73.195h67.565v-83.048l-25.337-14.076zm0 329.38h67.565v78.826l-23.93 14.076-43.635-18.3zm0-270.26v284.33m67.565-283.74v284.33" stroke-width="5"/>
|
||||||
|
<path id="path106" d="m1216.6 759.51h14.076v147.8h-14.076zm7.631-1.408v-53.488m0 257.37v-53.49m30.375-201.06v256.18" stroke-width="2"/>
|
||||||
|
<path id="path108" d="m1268.8 624.97s4.223-12.668 18.299-12.668 14.076 8.445 14.076 8.445v423.69s-1.408 9.853-14.076 9.853c-12.669 0-18.299-9.853-18.299-9.853m33.783-419.46s35.19-1.408 35.19 21.114v371.6c0 22.521-36.598 25.337-36.598 25.337" stroke-width="5"/>
|
||||||
|
<g stroke-width="2">
|
||||||
|
<rect id="rect110" transform="scale(-1 1)" x="-1329.9" y="648.9" width="16.891" height="35.19" rx="7.742" ry="6.284"/>
|
||||||
|
<rect id="rect112" transform="scale(-1 1)" x="-1329.9" y="986.17" width="16.891" height="35.19" rx="7.742" ry="6.284"/>
|
||||||
|
<path id="path114" d="m1199.7 632.82 67.565 74.603m-67.565 0.592 67.565-73.195m-67.342 328.16 67.565 74.602m-67.565 0.593 67.565-73.195"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g stroke="#000">
|
||||||
|
<g stroke-width="2">
|
||||||
|
<circle id="circle116" transform="translate(78.489 1074.7)" cx="119.65" cy="202.84" r="76.01" fill="#c8c8c8"/>
|
||||||
|
<circle id="circle118" transform="translate(94.676 1204.9)" cx="103.46" cy="72.633" r="44.339" fill="#fff"/>
|
||||||
|
<circle id="circle120" transform="translate(78.489 187.66)" cx="119.65" cy="202.84" r="76.01" fill="#c8c8c8"/>
|
||||||
|
<circle id="circle122" transform="translate(94.676 317.86)" cx="103.46" cy="72.633" r="44.339" fill="#fff"/>
|
||||||
|
<circle id="circle124" transform="translate(773.02 187.66)" cx="119.65" cy="202.84" r="76.01" fill="#c8c8c8"/>
|
||||||
|
<circle id="circle126" transform="translate(789.21 317.86)" cx="103.46" cy="72.633" r="44.339" fill="#fff"/>
|
||||||
|
<circle id="circle128" transform="translate(773.02 1074.7)" cx="119.65" cy="202.84" r="76.01" fill="#c8c8c8"/>
|
||||||
|
<circle id="circle130" transform="translate(789.21 1204.9)" cx="103.46" cy="72.633" r="44.339" fill="#fff"/>
|
||||||
|
</g>
|
||||||
|
<path id="path132" d="m1338.5 829.07h40.82" fill="none" stroke-width="5"/>
|
||||||
|
<circle id="circle134" transform="translate(1441.3 600.31)" cx="-59.119" cy="229.58" r="4.223" fill="#3c3c3c" stroke-width="5"/>
|
||||||
|
<g stroke-width="2">
|
||||||
|
<path id="path136" d="m778.06 845.37-38.709-38.709" fill="none"/>
|
||||||
|
<g id="g146" transform="translate(-13.78 15.524)" fill="#3c3c3c">
|
||||||
|
<circle id="circle138" transform="translate(-79.458 449.8)" cx="-81.641" cy="188.76" r="4.223"/>
|
||||||
|
<circle id="circle140" transform="translate(-79.458 569.92)" cx="-81.641" cy="188.76" r="4.223"/>
|
||||||
|
<circle id="circle142" transform="translate(-79.458 690.03)" cx="-81.641" cy="188.76" r="4.223"/>
|
||||||
|
<circle id="circle144" transform="translate(-79.458 810.15)" cx="-81.641" cy="188.76" r="4.223"/>
|
||||||
|
</g>
|
||||||
|
<g id="g156" transform="translate(-13.78 17.524)" fill="#3c3c3c">
|
||||||
|
<circle id="circle148" transform="translate(1381.6 448.25)" cx="-81.641" cy="188.76" r="4.223"/>
|
||||||
|
<circle id="circle150" transform="translate(1381.6 568.36)" cx="-81.641" cy="188.76" r="4.223"/>
|
||||||
|
<circle id="circle152" transform="translate(1381.6 688.48)" cx="-81.641" cy="188.76" r="4.223"/>
|
||||||
|
<circle id="circle154" transform="translate(1381.6 808.59)" cx="-81.641" cy="188.76" r="4.223"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g id="layer2" fill="#d00000">
|
||||||
|
<circle id="p02" cx="503.65" cy="248.75" r="61.935" />
|
||||||
|
<circle id="p03" cx="863.41" cy="248.75" r="61.935"/>
|
||||||
|
<circle id="p04" cx="1181.5" cy="248.75" r="61.935"/>
|
||||||
|
<circle id="p05" cx="1378.4" cy="151.16" r="61.935"/>
|
||||||
|
<circle id="p06" cx="1535.1" cy="581.37" r="61.935"/>
|
||||||
|
<circle id="p07" cx="1378.4" cy="997.9" r="61.935"/>
|
||||||
|
<circle id="p08" cx="1181.5" cy="914.24" r="61.935"/>
|
||||||
|
<circle id="p09" transform="scale(1,-1)" cx="863.41" cy="-914.24" r="61.935"/>
|
||||||
|
<circle id="p10" cx="503.65" cy="914.24" r="61.935"/>
|
||||||
|
<circle id="p11" cx="297.77" cy="997.9" r="61.935"/>
|
||||||
|
<circle id="p12" cx="93.269" cy="581.37" r="61.935"/>
|
||||||
|
<circle id="p25" cx="424.31" cy="581.37" r="61.935"/>
|
||||||
|
<circle id="p27" cx="972.84" cy="581.37" r="61.935"/>
|
||||||
|
<circle id="p01" cx="297.77" cy="151.16" r="61.935"/>
|
||||||
|
<circle id="p26" cx="1339.4" cy="581.37" r="61.935"/>
|
||||||
|
</g>
|
||||||
|
<g id="g4994" fill="#ffef00">
|
||||||
|
<circle id="s02" cx="503.65" cy="248.75" r="61.935"/>
|
||||||
|
<circle id="s03" cx="863.41" cy="248.75" r="61.935"/>
|
||||||
|
<circle id="s04" cx="1181.5" cy="248.75" r="61.935"/>
|
||||||
|
<circle id="s05" cx="1378.4" cy="151.16" r="61.935"/>
|
||||||
|
<circle id="s06" cx="1535.1" cy="581.37" r="61.935"/>
|
||||||
|
<circle id="s07" cx="1378.4" cy="997.9" r="61.935"/>
|
||||||
|
<circle id="s08" cx="1181.5" cy="914.24" r="61.935"/>
|
||||||
|
<circle id="s09" transform="scale(1,-1)" cx="863.41" cy="-914.24" r="61.935"/>
|
||||||
|
<circle id="s10" cx="503.65" cy="914.24" r="61.935"/>
|
||||||
|
<circle id="s11" cx="297.77" cy="997.9" r="61.935"/>
|
||||||
|
<circle id="s12" cx="93.269" cy="581.37" r="61.935"/>
|
||||||
|
<circle id="s25" cx="424.31" cy="581.37" r="61.935"/>
|
||||||
|
<circle id="s27" cx="972.84" cy="581.37" r="61.935"/>
|
||||||
|
<circle id="s01" cx="297.77" cy="151.16" r="61.935"/>
|
||||||
|
<circle id="s26" cx="1339.4" cy="581.37" r="61.935"/>
|
||||||
|
</g>
|
||||||
|
<g id="layer3">
|
||||||
|
<text id="p15" opacity="0" x="382.62802" y="1034.3463" fill="#fd0000" font-family="sans-serif" font-size="1696.9px" letter-spacing="0px" stroke-width="17.676" word-spacing="0px" style="line-height:5.25" xml:space="preserve"><tspan id="tspan4997" x="382.62802" y="1034.3463" fill="#fd0000" stroke-width="17.676">x</tspan></text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 14 KiB |
37
client/src/components/_test/test.component.jsx
Normal file
37
client/src/components/_test/test.component.jsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { setEmailOptions } from "../../redux/email/email.actions";
|
||||||
|
import T from "../../emails/parts-order/parts-order.email";
|
||||||
|
import { REPORT_QUERY_PARTS_ORDER_BY_PK } from "../../emails/parts-order/parts-order.query";
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
//currentUser: selectCurrentUser
|
||||||
|
});
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
setEmailOptions: e => dispatch(setEmailOptions(e))
|
||||||
|
});
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(function Test({ setEmailOptions }) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={() =>
|
||||||
|
setEmailOptions({
|
||||||
|
messageOptions: {
|
||||||
|
from: { name: "Kavia Autobdoy", address: "noreply@bodyshop.app" },
|
||||||
|
to: "patrickwf@gmail.com",
|
||||||
|
replyTo: "snaptsoft@gmail.com"
|
||||||
|
},
|
||||||
|
template: T,
|
||||||
|
queryConfig: [
|
||||||
|
REPORT_QUERY_PARTS_ORDER_BY_PK,
|
||||||
|
{ variables: { id: "46f3aa34-c3bd-46c8-83fc-c93b7ce84f46" } }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}>
|
||||||
|
Set email config.
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -2,10 +2,12 @@ import React from "react";
|
|||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
import Alert from "./alert.component";
|
import Alert from "./alert.component";
|
||||||
import { MockedProvider } from "@apollo/react-testing";
|
import { MockedProvider } from "@apollo/react-testing";
|
||||||
import { shallow } from "enzyme";
|
import { shallow, mount } from "enzyme";
|
||||||
|
|
||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
|
|
||||||
it("renders without crashing", () => {
|
it("renders without crashing", () => {
|
||||||
shallow(<Alert type="error" />);
|
const wrapper = mount(<Alert type="error" message="Test Error" />);
|
||||||
|
console.log("wrapper", wrapper);
|
||||||
|
// expect(wrapper.children()).to.have.lengthOf(1);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import { Select, Button, Popover, InputNumber } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
bodyshop: selectBodyshop
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
null
|
||||||
|
)(function AllocationsAssignmentComponent({
|
||||||
|
bodyshop,
|
||||||
|
handleAssignment,
|
||||||
|
assignment,
|
||||||
|
setAssignment,
|
||||||
|
visibilityState,
|
||||||
|
maxHours
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const onChange = e => {
|
||||||
|
console.log("e", e);
|
||||||
|
setAssignment({ ...assignment, employeeid: e });
|
||||||
|
};
|
||||||
|
|
||||||
|
const [visibility, setVisibility] = visibilityState;
|
||||||
|
|
||||||
|
const popContent = (
|
||||||
|
<div>
|
||||||
|
<Select
|
||||||
|
showSearch
|
||||||
|
style={{ width: 200 }}
|
||||||
|
placeholder="Select a person"
|
||||||
|
optionFilterProp="children"
|
||||||
|
onChange={onChange}
|
||||||
|
filterOption={(input, option) =>
|
||||||
|
option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{bodyshop.employees.map(emp => (
|
||||||
|
<Select.Option value={emp.id} key={emp.id}>
|
||||||
|
{`${emp.first_name} ${emp.last_name}`}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
<InputNumber
|
||||||
|
defaultValue={assignment.hours}
|
||||||
|
placeholder={t("joblines.fields.mod_lb_hrs")}
|
||||||
|
max={parseFloat(maxHours)}
|
||||||
|
min={0}
|
||||||
|
onChange={e => setAssignment({ ...assignment, hours: e })}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
disabled={!assignment.employeeid}
|
||||||
|
onClick={handleAssignment}
|
||||||
|
>
|
||||||
|
Assign
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => setVisibility(false)}>Close</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover content={popContent} visible={visibility}>
|
||||||
|
<Button onClick={() => setVisibility(true)}>
|
||||||
|
{t("allocations.actions.assign")}
|
||||||
|
</Button>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import AllocationsAssignmentComponent from "./allocations-assignment.component";
|
||||||
|
import { useMutation } from "react-apollo";
|
||||||
|
import { INSERT_ALLOCATION } from "../../graphql/allocations.queries";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { notification } from "antd";
|
||||||
|
|
||||||
|
export default function AllocationsAssignmentContainer({
|
||||||
|
jobLineId,
|
||||||
|
hours,
|
||||||
|
refetch
|
||||||
|
}) {
|
||||||
|
const visibilityState = useState(false);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [assignment, setAssignment] = useState({
|
||||||
|
joblineid: jobLineId,
|
||||||
|
hours: parseFloat(hours),
|
||||||
|
employeeid: null
|
||||||
|
});
|
||||||
|
const [insertAllocation] = useMutation(INSERT_ALLOCATION);
|
||||||
|
|
||||||
|
const handleAssignment = () => {
|
||||||
|
insertAllocation({ variables: { alloc: { ...assignment } } })
|
||||||
|
.then(r => {
|
||||||
|
notification["success"]({
|
||||||
|
message: t("allocations.successes.save")
|
||||||
|
});
|
||||||
|
visibilityState[1](false);
|
||||||
|
if (refetch) refetch();
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
notification["error"]({
|
||||||
|
message: t("employees.errors.saving", { message: error.message })
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AllocationsAssignmentComponent
|
||||||
|
handleAssignment={handleAssignment}
|
||||||
|
maxHours={hours}
|
||||||
|
assignment={assignment}
|
||||||
|
setAssignment={setAssignment}
|
||||||
|
visibilityState={visibilityState}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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} />);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { Button, Popover, Select } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
bodyshop: selectBodyshop
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
null
|
||||||
|
)(function AllocationsBulkAssignmentComponent({
|
||||||
|
disabled,
|
||||||
|
bodyshop,
|
||||||
|
handleAssignment,
|
||||||
|
assignment,
|
||||||
|
setAssignment,
|
||||||
|
visibilityState
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const onChange = e => {
|
||||||
|
console.log("e", e);
|
||||||
|
setAssignment({ ...assignment, employeeid: e });
|
||||||
|
};
|
||||||
|
|
||||||
|
const [visibility, setVisibility] = visibilityState;
|
||||||
|
|
||||||
|
const popContent = (
|
||||||
|
<div>
|
||||||
|
<Select
|
||||||
|
showSearch
|
||||||
|
style={{ width: 200 }}
|
||||||
|
placeholder='Select a person'
|
||||||
|
optionFilterProp='children'
|
||||||
|
onChange={onChange}
|
||||||
|
filterOption={(input, option) =>
|
||||||
|
option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||||
|
}>
|
||||||
|
{bodyshop.employees.map(emp => (
|
||||||
|
<Select.Option value={emp.id} key={emp.id}>
|
||||||
|
{`${emp.first_name} ${emp.last_name}`}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type='primary'
|
||||||
|
disabled={!assignment.employeeid}
|
||||||
|
onClick={handleAssignment}>
|
||||||
|
Assign
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => setVisibility(false)}>Close</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover content={popContent} visible={visibility}>
|
||||||
|
<Button disabled={disabled} onClick={() => setVisibility(true)}>
|
||||||
|
{t("allocations.actions.assign")}
|
||||||
|
</Button>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import AllocationsBulkAssignment from "./allocations-bulk-assignment.component";
|
||||||
|
import { useMutation } from "react-apollo";
|
||||||
|
import { INSERT_ALLOCATION } from "../../graphql/allocations.queries";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { notification } from "antd";
|
||||||
|
|
||||||
|
export default function AllocationsBulkAssignmentContainer({
|
||||||
|
jobLines,
|
||||||
|
refetch
|
||||||
|
}) {
|
||||||
|
const visibilityState = useState(false);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [assignment, setAssignment] = useState({
|
||||||
|
employeeid: null
|
||||||
|
});
|
||||||
|
const [insertAllocation] = useMutation(INSERT_ALLOCATION);
|
||||||
|
|
||||||
|
const handleAssignment = () => {
|
||||||
|
const allocs = jobLines.reduce((acc, value) => {
|
||||||
|
acc.push({
|
||||||
|
joblineid: value.id,
|
||||||
|
hours: parseFloat(value.mod_lb_hrs) || 0,
|
||||||
|
employeeid: assignment.employeeid
|
||||||
|
});
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
insertAllocation({ variables: { alloc: allocs } }).then(r => {
|
||||||
|
notification["success"]({
|
||||||
|
message: t("employees.successes.save")
|
||||||
|
});
|
||||||
|
visibilityState[1](false);
|
||||||
|
if (refetch) refetch();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AllocationsBulkAssignment
|
||||||
|
disabled={jobLines.length > 0 ? false : true}
|
||||||
|
handleAssignment={handleAssignment}
|
||||||
|
assignment={assignment}
|
||||||
|
setAssignment={setAssignment}
|
||||||
|
visibilityState={visibilityState}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { Icon } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
import { MdRemoveCircleOutline } from "react-icons/md";
|
||||||
|
|
||||||
|
export default function AllocationsLabelComponent({ allocation, handleClick }) {
|
||||||
|
return (
|
||||||
|
<div style={{ display: "flex" }}>
|
||||||
|
<span>
|
||||||
|
{`${allocation.employee.first_name || ""} ${allocation.employee
|
||||||
|
.last_name || ""} (${allocation.hours || ""})`}
|
||||||
|
</span>
|
||||||
|
<Icon
|
||||||
|
style={{ color: "red", padding: "0px 4px" }}
|
||||||
|
component={MdRemoveCircleOutline}
|
||||||
|
onClick={handleClick}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useMutation } from "react-apollo";
|
||||||
|
import { DELETE_ALLOCATION } from "../../graphql/allocations.queries";
|
||||||
|
import AllocationsLabelComponent from "./allocations-employee-label.component";
|
||||||
|
import { notification } from "antd";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
export default function AllocationsLabelContainer({ allocation, refetch }) {
|
||||||
|
const [deleteAllocation] = useMutation(DELETE_ALLOCATION);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const handleClick = e => {
|
||||||
|
e.preventDefault();
|
||||||
|
deleteAllocation({ variables: { id: allocation.id } })
|
||||||
|
.then(r => {
|
||||||
|
notification["success"]({
|
||||||
|
message: t("allocations.successes.deleted")
|
||||||
|
});
|
||||||
|
if (refetch) refetch();
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
notification["error"]({ message: t("allocations.errors.deleting") });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<AllocationsLabelComponent
|
||||||
|
allocation={allocation}
|
||||||
|
handleClick={handleClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { Tag, Popover } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
import Barcode from "react-barcode";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
export default function BarcodePopupComponent({ value }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Popover
|
||||||
|
content={
|
||||||
|
<Barcode
|
||||||
|
value={value}
|
||||||
|
background="transparent"
|
||||||
|
displayValue={false}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Tag>{t("general.labels.barcode")}</Tag>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
import { Button, Card, Input, Icon } from "antd";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import twilio from "twilio";
|
||||||
|
import {
|
||||||
|
closeConversation,
|
||||||
|
toggleConversationVisible
|
||||||
|
} from "../../redux/messaging/messaging.actions";
|
||||||
|
import PhoneFormatter from "../../utils/PhoneFormatter";
|
||||||
|
import "./chat-conversation.styles.scss"; //https://bootsnipp.com/snippets/exR5v
|
||||||
|
import { MdSend } from "react-icons/md";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
const client = twilio(
|
||||||
|
"ACf1b1aaf0e04740828b49b6e58467d787",
|
||||||
|
"0bea5e29a6d77593183ab1caa01d23de"
|
||||||
|
);
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
//currentUser: selectCurrentUser
|
||||||
|
});
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
toggleConversationVisible: conversationId =>
|
||||||
|
dispatch(toggleConversationVisible(conversationId)),
|
||||||
|
closeConversation: phone => dispatch(closeConversation(phone))
|
||||||
|
});
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(function ChatConversationComponent({
|
||||||
|
conversation,
|
||||||
|
toggleConversationVisible,
|
||||||
|
closeConversation
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const [messages, setMessages] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
client.messages.list({ limit: 20 }, (error, items) => {
|
||||||
|
setMessages(
|
||||||
|
items.reduce((acc, value) => {
|
||||||
|
acc.push({
|
||||||
|
sid: value.sid,
|
||||||
|
direction: value.direction,
|
||||||
|
body: value.body
|
||||||
|
});
|
||||||
|
return acc;
|
||||||
|
}, [])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return () => {};
|
||||||
|
}, [setMessages]);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Card
|
||||||
|
title={
|
||||||
|
conversation.open ? (
|
||||||
|
<div style={{ display: "flex" }}>
|
||||||
|
<div
|
||||||
|
onClick={() => toggleConversationVisible(conversation.phone)}
|
||||||
|
>
|
||||||
|
<PhoneFormatter>{conversation.phone}</PhoneFormatter>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
type="danger"
|
||||||
|
shape="circle-outline"
|
||||||
|
onClick={() => closeConversation(conversation.phone)}
|
||||||
|
>
|
||||||
|
X
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
style={{
|
||||||
|
width: conversation.open ? "400px" : "175px",
|
||||||
|
margin: "0px 10px"
|
||||||
|
}}
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
{conversation.open ? (
|
||||||
|
<div>
|
||||||
|
<div className="messages" style={{ height: "400px" }}>
|
||||||
|
<ul>
|
||||||
|
{messages.map(item => (
|
||||||
|
<li
|
||||||
|
key={item.sid}
|
||||||
|
className={`${
|
||||||
|
item.direction === "inbound" ? "sent" : "replies"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<p> {item.body}</p>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<Input.Search
|
||||||
|
placeholder={t("messaging.labels.typeamessage")}
|
||||||
|
enterButton={<Icon component={MdSend} />}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div style={{ display: "flex" }}>
|
||||||
|
<div onClick={() => toggleConversationVisible(conversation.phone)}>
|
||||||
|
<PhoneFormatter>{conversation.phone}</PhoneFormatter>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
type="dashed"
|
||||||
|
shape="circle-outline"
|
||||||
|
onClick={() => closeConversation(conversation.phone)}
|
||||||
|
>
|
||||||
|
X
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import React from "react";
|
||||||
|
import ChatConversationComponent from "./chat-conversation.component";
|
||||||
|
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
//currentUser: selectCurrentUser
|
||||||
|
});
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
|
});
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(function ChatConversationContainer({ conversation }) {
|
||||||
|
return <ChatConversationComponent conversation={conversation} />;
|
||||||
|
});
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { openConversation } from "../../redux/messaging/messaging.actions";
|
||||||
|
import { Icon } from "antd";
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
//currentUser: selectCurrentUser
|
||||||
|
});
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
openConversation: phone => dispatch(openConversation(phone))
|
||||||
|
});
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(function ChatOpenButton({ openConversation, phone }) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
style={{ margin: 4 }}
|
||||||
|
type="message"
|
||||||
|
onClick={() => openConversation(phone)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { Badge, Card, Icon } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
export default function ChatWindowComponent({
|
||||||
|
chatVisible,
|
||||||
|
toggleChatVisible
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Badge count={5}>
|
||||||
|
<Card
|
||||||
|
onClick={() => toggleChatVisible()}
|
||||||
|
style={{
|
||||||
|
width: chatVisible ? "300px" : "125px",
|
||||||
|
margin: "0px 10px"
|
||||||
|
}}
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
{chatVisible ? (
|
||||||
|
<div className="messages" style={{ height: "400px" }}>
|
||||||
|
List of chats here.
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
<Icon type="message" />
|
||||||
|
<strong style={{ paddingLeft: "10px" }}>
|
||||||
|
{t("messaging.labels.messaging")}
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { Affix, Badge } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { toggleChatVisible } from "../../redux/messaging/messaging.actions";
|
||||||
|
import {
|
||||||
|
selectChatVisible,
|
||||||
|
selectConversations
|
||||||
|
} from "../../redux/messaging/messaging.selectors";
|
||||||
|
import ChatConversationContainer from "../chat-conversation/chat-conversation.container";
|
||||||
|
import ChatOverlayComponent from "./chat-overlay.component";
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
chatVisible: selectChatVisible,
|
||||||
|
conversations: selectConversations
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
toggleChatVisible: () => dispatch(toggleChatVisible())
|
||||||
|
});
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(function ChatWindowContainer({
|
||||||
|
chatVisible,
|
||||||
|
toggleChatVisible,
|
||||||
|
conversations
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Affix offsetBottom={0}>
|
||||||
|
<div>
|
||||||
|
<Badge count={10}>
|
||||||
|
<ChatOverlayComponent
|
||||||
|
chatVisible={chatVisible}
|
||||||
|
toggleChatVisible={toggleChatVisible}
|
||||||
|
/>
|
||||||
|
</Badge>
|
||||||
|
{conversations
|
||||||
|
? conversations.map((conversation, idx) => (
|
||||||
|
<Badge key={idx} count={5}>
|
||||||
|
<ChatConversationContainer conversation={conversation} />
|
||||||
|
</Badge>
|
||||||
|
))
|
||||||
|
: null}
|
||||||
|
</div>
|
||||||
|
</Affix>
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
export default function ChatWindowComponent() {
|
|
||||||
return <div>Chat Windows and more</div>;
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import React, { useState } from "react";
|
|
||||||
import ChatWindowComponent from "./chat-window.component";
|
|
||||||
import { Button } from "antd";
|
|
||||||
|
|
||||||
export default function ChatWindowContainer() {
|
|
||||||
const [visible, setVisible] = useState(false);
|
|
||||||
return (
|
|
||||||
<div style={{ position: "absolute", zIndex: 1000 }}>
|
|
||||||
{visible ? <ChatWindowComponent /> : null}
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
setVisible(!visible);
|
|
||||||
}}>
|
|
||||||
Open!
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
import { useApolloClient, useQuery } from "@apollo/react-hooks";
|
|
||||||
import { Avatar, Col, Dropdown, Icon, Menu, Row } from "antd";
|
|
||||||
import i18next from "i18next";
|
|
||||||
import React from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import UserImage from "../../assets/User.svg";
|
|
||||||
import { GET_CURRENT_USER } from "../../graphql/local.queries";
|
|
||||||
import AlertComponent from "../alert/alert.component";
|
|
||||||
import SignOut from "../sign-out/sign-out.component";
|
|
||||||
|
|
||||||
export default function CurrentUserDropdown() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { loading, error, data } = useQuery(GET_CURRENT_USER);
|
|
||||||
const client = useApolloClient();
|
|
||||||
|
|
||||||
const handleMenuClick = e => {
|
|
||||||
if (e.item.props.actiontype === "lang-select") {
|
|
||||||
i18next.changeLanguage(e.key, (err, t) => {
|
|
||||||
if (err)
|
|
||||||
return console.log("Error encountered when changing languages.", err);
|
|
||||||
client.writeData({ data: { language: e.key } });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const menu = (
|
|
||||||
<Menu mode='vertical' onClick={handleMenuClick}>
|
|
||||||
<Menu.Item>
|
|
||||||
<SignOut />
|
|
||||||
</Menu.Item>
|
|
||||||
<Menu.Item>
|
|
||||||
<Link to='/manage/profile'> {t("menus.currentuser.profile")}</Link>
|
|
||||||
</Menu.Item>
|
|
||||||
<Menu.SubMenu
|
|
||||||
title={
|
|
||||||
<span>
|
|
||||||
<Icon type='global' />
|
|
||||||
<span>{t("menus.currentuser.languageselector")}</span>
|
|
||||||
</span>
|
|
||||||
}>
|
|
||||||
<Menu.Item actiontype='lang-select' key='en_us'>
|
|
||||||
{t("general.languages.english")}
|
|
||||||
</Menu.Item>
|
|
||||||
<Menu.Item actiontype='lang-select' key='fr'>
|
|
||||||
{t("general.languages.french")}
|
|
||||||
</Menu.Item>
|
|
||||||
<Menu.Item actiontype='lang-select' key='es'>
|
|
||||||
{t("general.languages.spanish")}
|
|
||||||
</Menu.Item>
|
|
||||||
</Menu.SubMenu>
|
|
||||||
</Menu>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (loading) return null;
|
|
||||||
if (error) return <AlertComponent message={error.message} type='error' />;
|
|
||||||
|
|
||||||
const { currentUser } = data;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dropdown overlay={menu}>
|
|
||||||
<Row>
|
|
||||||
<Col span={8}>
|
|
||||||
<Avatar size='large' alt='Avatar' src={UserImage} />
|
|
||||||
</Col>
|
|
||||||
<Col span={16} style={{ color: "white" }}>
|
|
||||||
{currentUser.displayName || t("general.labels.unknown")}
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</Dropdown>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
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({
|
||||||
|
messageOptions,
|
||||||
|
handleConfigChange,
|
||||||
|
handleHtmlChange
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Input
|
||||||
|
defaultValue={messageOptions.to}
|
||||||
|
onChange={handleConfigChange}
|
||||||
|
name='to'
|
||||||
|
/>
|
||||||
|
CC
|
||||||
|
<Input
|
||||||
|
defaultValue={messageOptions.cc}
|
||||||
|
onChange={handleConfigChange}
|
||||||
|
name='cc'
|
||||||
|
/>
|
||||||
|
Subject
|
||||||
|
<Input
|
||||||
|
defaultValue={messageOptions.subject}
|
||||||
|
onChange={handleConfigChange}
|
||||||
|
name='subject'
|
||||||
|
/>
|
||||||
|
<CKEditor
|
||||||
|
editor={ClassicEditor}
|
||||||
|
data={messageOptions.html}
|
||||||
|
onChange={(event, editor) => {
|
||||||
|
handleHtmlChange(editor.getData());
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
104
client/src/components/email-overlay/email-overlay.container.jsx
Normal file
104
client/src/components/email-overlay/email-overlay.container.jsx
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import { Button, Modal, notification } from "antd";
|
||||||
|
import axios from "axios";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { useLazyQuery } from "react-apollo";
|
||||||
|
import ReactDOMServer from "react-dom/server";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { toggleEmailOverlayVisible } from "../../redux/email/email.actions";
|
||||||
|
import { selectEmailConfig, selectEmailVisible } from "../../redux/email/email.selectors.js";
|
||||||
|
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||||
|
import EmailOverlayComponent from "./email-overlay.component";
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
modalVisible: selectEmailVisible,
|
||||||
|
emailConfig: selectEmailConfig
|
||||||
|
});
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
toggleEmailOverlayVisible: () => dispatch(toggleEmailOverlayVisible())
|
||||||
|
});
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(function SendEmail({ emailConfig, modalVisible, toggleEmailOverlayVisible }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [messageOptions, setMessageOptions] = useState(
|
||||||
|
emailConfig.messageOptions
|
||||||
|
);
|
||||||
|
useEffect(() => {
|
||||||
|
setMessageOptions(emailConfig.messageOptions);
|
||||||
|
}, [setMessageOptions, emailConfig.messageOptions]);
|
||||||
|
|
||||||
|
const [executeQuery, { called, loading, data }] = useLazyQuery(
|
||||||
|
emailConfig.queryConfig[0],
|
||||||
|
{
|
||||||
|
...emailConfig.queryConfig[1],
|
||||||
|
fetchPolicy: "network-only"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
emailConfig.queryConfig[0] &&
|
||||||
|
emailConfig.queryConfig[1] &&
|
||||||
|
modalVisible &&
|
||||||
|
!called
|
||||||
|
) {
|
||||||
|
executeQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data && !messageOptions.html && emailConfig.template) {
|
||||||
|
setMessageOptions({
|
||||||
|
...messageOptions,
|
||||||
|
html: ReactDOMServer.renderToStaticMarkup(
|
||||||
|
<emailConfig.template data={data} />
|
||||||
|
)
|
||||||
|
//html: renderEmail(<emailConfig.template data={data} />)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleOk = () => {
|
||||||
|
//sendEmail(messageOptions);
|
||||||
|
axios
|
||||||
|
.post("/sendemail", messageOptions)
|
||||||
|
.then(response => {
|
||||||
|
console.log(JSON.stringify(response));
|
||||||
|
notification["success"]({ message: t("emails.successes.sent") });
|
||||||
|
toggleEmailOverlayVisible();
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.log(JSON.stringify(error));
|
||||||
|
notification["error"]({
|
||||||
|
message: t("emails.errors.notsent", { message: error.message })
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const handleConfigChange = event => {
|
||||||
|
const { name, value } = event.target;
|
||||||
|
setMessageOptions({ ...messageOptions, [name]: value });
|
||||||
|
};
|
||||||
|
const handleHtmlChange = text => {
|
||||||
|
setMessageOptions({ ...messageOptions, html: text });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Modal
|
||||||
|
destroyOnClose={true}
|
||||||
|
visible={modalVisible}
|
||||||
|
width={"80%"}
|
||||||
|
onOk={handleOk}
|
||||||
|
onCancel={() => toggleEmailOverlayVisible()}>
|
||||||
|
<LoadingSpinner loading={loading}>
|
||||||
|
<EmailOverlayComponent
|
||||||
|
handleConfigChange={handleConfigChange}
|
||||||
|
messageOptions={messageOptions}
|
||||||
|
handleHtmlChange={handleHtmlChange}
|
||||||
|
/>
|
||||||
|
</LoadingSpinner>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<Button onClick={() => toggleEmailOverlayVisible()}>Show</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -4,7 +4,7 @@ import React from "react";
|
|||||||
export default function FooterComponent() {
|
export default function FooterComponent() {
|
||||||
return (
|
return (
|
||||||
<Row>
|
<Row>
|
||||||
<Col span={8} offset={9}>
|
<Col span={8} offset={8}>
|
||||||
Copyright Snapt Software 2019. All rights reserved.
|
Copyright Snapt Software 2019. All rights reserved.
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|||||||
@@ -5,9 +5,13 @@ function FormItemEmail(props, ref) {
|
|||||||
<Input
|
<Input
|
||||||
{...props}
|
{...props}
|
||||||
addonAfter={
|
addonAfter={
|
||||||
<a href={`mailto:${props.email}`}>
|
props.email ? (
|
||||||
|
<a href={`mailto:${props.email}`}>
|
||||||
|
<Icon type="mail" />
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
<Icon type="mail" />
|
<Icon type="mail" />
|
||||||
</a>
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { Button } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import AlertComponent from "../alert/alert.component";
|
||||||
|
|
||||||
|
export default function ResetForm({ resetFields }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<AlertComponent
|
||||||
|
message={
|
||||||
|
<div>
|
||||||
|
{t("general.messages.unsavedchanges")}
|
||||||
|
<Button style={{ marginLeft: "20px" }} onClick={() => resetFields()}>
|
||||||
|
{t("general.actions.reset")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
closable
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
// import { Icon, Button, Input, AutoComplete } from "antd";
|
|
||||||
|
|
||||||
// const { Option } = AutoComplete;
|
|
||||||
|
|
||||||
// function onSelect(value) {
|
|
||||||
// console.log("onSelect", value);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// function getRandomInt(max, min = 0) {
|
|
||||||
// return Math.floor(Math.random() * (max - min + 1)) + min; // eslint-disable-line no-mixed-operators
|
|
||||||
// }
|
|
||||||
|
|
||||||
// function searchResult(query) {
|
|
||||||
// return new Array(getRandomInt(5))
|
|
||||||
// .join(".")
|
|
||||||
// .split(".")
|
|
||||||
// .map((item, idx) => ({
|
|
||||||
// query,
|
|
||||||
// category: `${query}${idx}`,
|
|
||||||
// count: getRandomInt(200, 100)
|
|
||||||
// }));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// function renderOption(item) {
|
|
||||||
// return (
|
|
||||||
// <Option key={item.category} text={item.category}>
|
|
||||||
// <div className='global-search-item'>
|
|
||||||
// <span className='global-search-item-desc'>
|
|
||||||
// Found {item.query} on
|
|
||||||
// <a
|
|
||||||
// href={`https://s.taobao.com/search?q=${item.query}`}
|
|
||||||
// target='_blank'
|
|
||||||
// rel='noopener noreferrer'>
|
|
||||||
// {item.category}
|
|
||||||
// </a>
|
|
||||||
// </span>
|
|
||||||
// <span className='global-search-item-count'>{item.count} results</span>
|
|
||||||
// </div>
|
|
||||||
// </Option>
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
export default class GlobalSearch extends React.Component {
|
|
||||||
state = {
|
|
||||||
dataSource: []
|
|
||||||
};
|
|
||||||
|
|
||||||
// handleSearch = value => {
|
|
||||||
// this.setState({
|
|
||||||
// dataSource: value ? searchResult(value) : []
|
|
||||||
// });
|
|
||||||
// };
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div />
|
|
||||||
// <div style={{ width: 300 }}>
|
|
||||||
// <AutoComplete
|
|
||||||
// size="large"
|
|
||||||
// style={{ width: "100%" }}
|
|
||||||
// dataSource={dataSource.map(renderOption)}
|
|
||||||
// onSelect={onSelect}
|
|
||||||
// onSearch={this.handleSearch}
|
|
||||||
// placeholder="input here"
|
|
||||||
// optionLabelProp="text"
|
|
||||||
// >
|
|
||||||
// <Input
|
|
||||||
// suffix={
|
|
||||||
// <Button style={{ marginRight: -12 }} size="large" type="primary">
|
|
||||||
// <Icon type="search" />
|
|
||||||
// </Button>
|
|
||||||
// }
|
|
||||||
// />
|
|
||||||
// </AutoComplete>
|
|
||||||
// </div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,73 +1,181 @@
|
|||||||
import { useApolloClient } from "@apollo/react-hooks";
|
import { Avatar, Col, Icon, Menu, Row } from "antd";
|
||||||
import { Col, Icon, Menu, Row } from "antd";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import CurrentUserDropdown from "../current-user-dropdown/current-user-dropdown.component";
|
import UserImage from "../../assets/User.svg";
|
||||||
import GlobalSearch from "../global-search/global-search.component";
|
import { signOutStart } from "../../redux/user/user.actions";
|
||||||
import ManageSignInButton from "../manage-sign-in-button/manage-sign-in-button.component";
|
import ManageSignInButton from "../manage-sign-in-button/manage-sign-in-button.component";
|
||||||
import "./header.styles.scss";
|
|
||||||
|
|
||||||
export default ({ landingHeader, navItems, selectedNavItem }) => {
|
export default ({
|
||||||
const apolloClient = useApolloClient();
|
landingHeader,
|
||||||
|
selectedNavItem,
|
||||||
|
logo,
|
||||||
|
handleMenuClick,
|
||||||
|
currentUser
|
||||||
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const handleClick = e => {
|
//TODO Add
|
||||||
apolloClient.writeData({ data: { selectedNavItem: e.key } });
|
|
||||||
};
|
|
||||||
return (
|
return (
|
||||||
<Row type='flex' justify='space-around'>
|
<Row type="flex" justify="space-around" align="middle">
|
||||||
<Col span={16}>
|
{logo ? (
|
||||||
<Menu
|
<Col span={3}>
|
||||||
theme='dark'
|
<img alt="Shop Logo" src={logo} style={{ height: "40px" }} />
|
||||||
className='header'
|
</Col>
|
||||||
onClick={handleClick}
|
) : null}
|
||||||
selectedKeys={selectedNavItem}
|
<Col span={14}>
|
||||||
mode='horizontal'>
|
{landingHeader ? (
|
||||||
<Menu.Item>
|
<Menu
|
||||||
<GlobalSearch />
|
theme="dark"
|
||||||
</Menu.Item>
|
className="header"
|
||||||
|
selectedKeys={selectedNavItem}
|
||||||
|
mode="horizontal"
|
||||||
|
onClick={handleMenuClick}
|
||||||
|
>
|
||||||
|
<ManageSignInButton />
|
||||||
|
|
||||||
<Menu.Item key='home'>
|
<Menu.SubMenu
|
||||||
<Link to='/manage'>
|
title={
|
||||||
<Icon type='home' />
|
<div>
|
||||||
{t("menus.header.home")}
|
<Avatar
|
||||||
</Link>
|
size="medium"
|
||||||
</Menu.Item>
|
alt="Avatar"
|
||||||
<Menu.SubMenu title={t("menus.header.jobs")}>
|
src={
|
||||||
<Menu.Item key='jobs'>
|
currentUser.photoURL ? currentUser.photoURL : UserImage
|
||||||
<Link to='/manage/jobs'>
|
}
|
||||||
<Icon type='home' />
|
style={{ margin: "10px" }}
|
||||||
{t("menus.header.activejobs")}
|
/>
|
||||||
|
{currentUser.displayName || t("general.labels.unknown")}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Menu.Item onClick={signOutStart()}>
|
||||||
|
{t("user.actions.signout")}
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item>
|
||||||
|
<Link to="/manage/profile">
|
||||||
|
{t("menus.currentuser.profile")}
|
||||||
|
</Link>
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.SubMenu
|
||||||
|
title={
|
||||||
|
<span>
|
||||||
|
<Icon type="global" />
|
||||||
|
<span>{t("menus.currentuser.languageselector")}</span>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Menu.Item actiontype="lang-select" key="en_us">
|
||||||
|
{t("general.languages.english")}
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item actiontype="lang-select" key="fr">
|
||||||
|
{t("general.languages.french")}
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item actiontype="lang-select" key="es">
|
||||||
|
{t("general.languages.spanish")}
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu.SubMenu>
|
||||||
|
</Menu.SubMenu>
|
||||||
|
</Menu>
|
||||||
|
) : (
|
||||||
|
<Menu
|
||||||
|
theme="dark"
|
||||||
|
className="header"
|
||||||
|
selectedKeys={selectedNavItem}
|
||||||
|
mode="horizontal"
|
||||||
|
onClick={handleMenuClick}
|
||||||
|
>
|
||||||
|
<Menu.Item key="home">
|
||||||
|
<Link to="/manage">
|
||||||
|
<Icon type="home" />
|
||||||
|
{t("menus.header.home")}
|
||||||
</Link>
|
</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item key='availablejobs'>
|
<Menu.SubMenu title={t("menus.header.jobs")}>
|
||||||
<Link to='/manage/available'>
|
<Menu.Item key="schedule">
|
||||||
<Icon type='home' />
|
<Link to="/manage/schedule">
|
||||||
{t("menus.header.availablejobs")}
|
<Icon type="calendar" />
|
||||||
</Link>
|
{t("menus.header.schedule")}
|
||||||
</Menu.Item>
|
</Link>
|
||||||
</Menu.SubMenu>
|
</Menu.Item>
|
||||||
|
<Menu.Item key="activejobs">
|
||||||
|
<Link to="/manage/jobs">{t("menus.header.activejobs")}</Link>
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item key="availablejobs">
|
||||||
|
<Link to="/manage/available">
|
||||||
|
{t("menus.header.availablejobs")}
|
||||||
|
</Link>
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu.SubMenu>
|
||||||
|
<Menu.SubMenu title={t("menus.header.customers")}>
|
||||||
|
<Menu.Item key="owners">
|
||||||
|
<Link to="/manage/owners">
|
||||||
|
<Icon type="team" />
|
||||||
|
{t("menus.header.owners")}
|
||||||
|
</Link>
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item key="vehicles">
|
||||||
|
<Link to="/manage/vehicles">
|
||||||
|
<Icon type="car" />
|
||||||
|
{t("menus.header.vehicles")}
|
||||||
|
</Link>
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu.SubMenu>
|
||||||
|
<Menu.SubMenu title={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
|
||||||
// navItems.map(navItem => (
|
title={
|
||||||
// <Menu.Item key={navItem.title}>
|
<div>
|
||||||
// <Link to={navItem.path}>
|
<Avatar
|
||||||
// {navItem.icontype ? <Icon type={navItem.icontype} /> : null}
|
size="medium"
|
||||||
// {navItem.title}
|
alt="Avatar"
|
||||||
// </Link>
|
src={
|
||||||
// </Menu.Item>
|
currentUser.photoURL ? currentUser.photoURL : UserImage
|
||||||
// ))
|
}
|
||||||
}
|
style={{ margin: "10px" }}
|
||||||
|
/>
|
||||||
{!landingHeader ? null : (
|
{currentUser.displayName || t("general.labels.unknown")}
|
||||||
<Menu.Item>
|
</div>
|
||||||
<ManageSignInButton />
|
}
|
||||||
</Menu.Item>
|
>
|
||||||
)}
|
<Menu.Item onClick={signOutStart()}>
|
||||||
</Menu>
|
{t("user.actions.signout")}
|
||||||
</Col>
|
</Menu.Item>
|
||||||
<Col span={6} offset={2}>
|
<Menu.Item>
|
||||||
{!landingHeader ? <CurrentUserDropdown /> : null}
|
<Link to="/manage/profile">
|
||||||
|
{t("menus.currentuser.profile")}
|
||||||
|
</Link>
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.SubMenu
|
||||||
|
title={
|
||||||
|
<span>
|
||||||
|
<Icon type="global" />
|
||||||
|
<span>{t("menus.currentuser.languageselector")}</span>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Menu.Item actiontype="lang-select" key="en_us">
|
||||||
|
{t("general.languages.english")}
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item actiontype="lang-select" key="fr">
|
||||||
|
{t("general.languages.french")}
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item actiontype="lang-select" key="es">
|
||||||
|
{t("general.languages.spanish")}
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu.SubMenu>
|
||||||
|
</Menu.SubMenu>
|
||||||
|
</Menu>
|
||||||
|
)}
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,46 +1,52 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import "./header.styles.scss";
|
|
||||||
import { useQuery } from "react-apollo";
|
|
||||||
// //import {
|
|
||||||
// GET_LANDING_NAV_ITEMS,
|
|
||||||
// GET_NAV_ITEMS
|
|
||||||
// } from "../../graphql/metadata.queries";
|
|
||||||
import { GET_CURRENT_SELECTED_NAV_ITEM } from "../../graphql/local.queries";
|
|
||||||
//import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
|
||||||
//import AlertComponent from "../alert/alert.component";
|
|
||||||
import HeaderComponent from "./header.component";
|
import HeaderComponent from "./header.component";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import { setUserLanguage, signOutStart } from "../../redux/user/user.actions";
|
||||||
|
import {
|
||||||
|
selectCurrentUser,
|
||||||
|
selectBodyshop
|
||||||
|
} from "../../redux/user/user.selectors";
|
||||||
|
|
||||||
export default ({ landingHeader, signedIn }) => {
|
const mapStateToProps = createStructuredSelector({
|
||||||
const hookSelectedNavItem = useQuery(GET_CURRENT_SELECTED_NAV_ITEM);
|
currentUser: selectCurrentUser,
|
||||||
|
bodyshop: selectBodyshop
|
||||||
|
});
|
||||||
|
|
||||||
// let hookNavItems;
|
const mapDispatchToProps = dispatch => ({
|
||||||
// if (landingHeader) {
|
signOutStart: () => dispatch(signOutStart()),
|
||||||
// hookNavItems = useQuery(GET_LANDING_NAV_ITEMS, {
|
setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
// fetchPolicy: "network-only"
|
});
|
||||||
// });
|
|
||||||
// } else {
|
|
||||||
// hookNavItems = useQuery(GET_NAV_ITEMS, {
|
|
||||||
// fetchPolicy: "network-only"
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (hookNavItems.loading || hookSelectedNavItem.loading)
|
export default connect(
|
||||||
// return <LoadingSpinner />;
|
mapStateToProps,
|
||||||
// if (hookNavItems.error)
|
mapDispatchToProps
|
||||||
// return <AlertComponent message={hookNavItems.error.message} />;
|
)(function HeaderContainer({
|
||||||
// if (hookSelectedNavItem.error)
|
landingHeader,
|
||||||
// return console.log(
|
currentUser,
|
||||||
// "Unable to load Selected Navigation Item.",
|
bodyshop,
|
||||||
// hookSelectedNavItem.error
|
signOutStart,
|
||||||
// );
|
setUserLanguage
|
||||||
|
}) {
|
||||||
const { selectedNavItem } = hookSelectedNavItem.data;
|
const handleMenuClick = e => {
|
||||||
// const navItems = JSON.parse(hookNavItems.data.masterdata_by_pk.value);
|
if (e.item.props.actiontype === "lang-select") {
|
||||||
|
i18next.changeLanguage(e.key, (err, t) => {
|
||||||
|
if (err)
|
||||||
|
return console.log("Error encountered when changing languages.", err);
|
||||||
|
setUserLanguage(e.key);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HeaderComponent
|
<HeaderComponent
|
||||||
|
handleMenuClick={handleMenuClick}
|
||||||
|
signOutStart={signOutStart}
|
||||||
landingHeader={landingHeader}
|
landingHeader={landingHeader}
|
||||||
selectedNavItem={selectedNavItem}
|
selectedNavItem={null}
|
||||||
|
currentUser={currentUser}
|
||||||
|
logo={bodyshop ? bodyshop.logo_img_path : null}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
.header{
|
|
||||||
text-align: center;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import { Modal, Form, Input, InputNumber } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import ResetForm from "../form-items-formatted/reset-form-item.component";
|
||||||
|
|
||||||
|
export default function InvoiceEnterModalComponent({
|
||||||
|
visible,
|
||||||
|
invoice,
|
||||||
|
handleCancel,
|
||||||
|
handleSubmit,
|
||||||
|
form
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { getFieldDecorator, isFieldsTouched, resetFields } = form;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={
|
||||||
|
invoice && invoice.id
|
||||||
|
? t("invoice.labels.edit")
|
||||||
|
: t("invoice.labels.new")
|
||||||
|
}
|
||||||
|
visible={visible}
|
||||||
|
okText={t("general.labels.save")}
|
||||||
|
onOk={handleSubmit}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
>
|
||||||
|
{isFieldsTouched() ? <ResetForm resetFields={resetFields} /> : null}
|
||||||
|
<Form onSubmit={handleSubmit} autoComplete={"off"}>
|
||||||
|
{JSON.stringify(invoice)}
|
||||||
|
{
|
||||||
|
// <Form.Item label={t("joblines.fields.line_desc")}>
|
||||||
|
// {getFieldDecorator("line_desc", {
|
||||||
|
// initialValue: jobLine.line_desc
|
||||||
|
// })(<Input name="line_desc" />)}
|
||||||
|
// </Form.Item>
|
||||||
|
// <Form.Item label={t("joblines.fields.oem_partno")}>
|
||||||
|
// {getFieldDecorator("oem_partno", {
|
||||||
|
// initialValue: jobLine.oem_partno
|
||||||
|
// })(<Input name="oem_partno" />)}
|
||||||
|
// </Form.Item>
|
||||||
|
// <Form.Item label={t("joblines.fields.part_type")}>
|
||||||
|
// {getFieldDecorator("part_type", {
|
||||||
|
// initialValue: jobLine.part_type
|
||||||
|
// })(<Input name="part_type" />)}
|
||||||
|
// </Form.Item>
|
||||||
|
// <Form.Item label={t("joblines.fields.mod_lbr_ty")}>
|
||||||
|
// {getFieldDecorator("mod_lbr_ty", {
|
||||||
|
// initialValue: jobLine.mod_lbr_ty
|
||||||
|
// })(<Input name="mod_lbr_ty" />)}
|
||||||
|
// </Form.Item>
|
||||||
|
// <Form.Item label={t("joblines.fields.op_code_desc")}>
|
||||||
|
// {getFieldDecorator("op_code_desc", {
|
||||||
|
// initialValue: jobLine.op_code_desc
|
||||||
|
// })(<Input name="op_code_desc" />)}
|
||||||
|
// </Form.Item>
|
||||||
|
// <Form.Item label={t("joblines.fields.mod_lb_hrs")}>
|
||||||
|
// {getFieldDecorator("mod_lb_hrs", {
|
||||||
|
// initialValue: jobLine.mod_lb_hrs
|
||||||
|
// })(<InputNumber name="mod_lb_hrs" />)}
|
||||||
|
// </Form.Item>
|
||||||
|
// <Form.Item label={t("joblines.fields.act_price")}>
|
||||||
|
// {getFieldDecorator("act_price", {
|
||||||
|
// initialValue: jobLine.act_price
|
||||||
|
// })(<InputNumber name="act_price" />)}
|
||||||
|
// </Form.Item>
|
||||||
|
}
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
import { Form, notification } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
import { useMutation } from "react-apollo";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import {
|
||||||
|
INSERT_NEW_JOB_LINE,
|
||||||
|
UPDATE_JOB_LINE
|
||||||
|
} from "../../graphql/jobs-lines.queries";
|
||||||
|
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||||
|
import { selectInvoiceEnterModal } from "../../redux/modals/modals.selectors";
|
||||||
|
import InvoiceEnterModalComponent from "./invoice-enter-modal.component";
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
invoiceEnterModal: selectInvoiceEnterModal
|
||||||
|
});
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
toggleModalVisible: () => dispatch(toggleModalVisible("invoiceEnter"))
|
||||||
|
});
|
||||||
|
|
||||||
|
function InvoiceEnterModalContainer({
|
||||||
|
invoiceEnterModal,
|
||||||
|
toggleModalVisible,
|
||||||
|
form
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
// const [insertJobLine] = useMutation(INSERT_NEW_JOB_LINE);
|
||||||
|
// const [updateJobLine] = useMutation(UPDATE_JOB_LINE);
|
||||||
|
|
||||||
|
const handleSubmit = e => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
form.validateFieldsAndScroll((err, values) => {
|
||||||
|
if (err) {
|
||||||
|
notification["error"]({
|
||||||
|
message: t("invoices.errors.validation"),
|
||||||
|
description: err.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!err) {
|
||||||
|
alert("Closing this modal.");
|
||||||
|
toggleModalVisible();
|
||||||
|
// if (!jobLineEditModal.context.id) {
|
||||||
|
// insertJobLine({
|
||||||
|
// variables: {
|
||||||
|
// lineInput: [{ jobid: jobLineEditModal.context.jobid, ...values }]
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// .then(r => {
|
||||||
|
// if (jobLineEditModal.actions.refetch)
|
||||||
|
// jobLineEditModal.actions.refetch();
|
||||||
|
// toggleModalVisible();
|
||||||
|
// notification["success"]({
|
||||||
|
// message: t("joblines.successes.created")
|
||||||
|
// });
|
||||||
|
// })
|
||||||
|
// .catch(error => {
|
||||||
|
// notification["error"]({
|
||||||
|
// message: t("joblines.errors.creating", {
|
||||||
|
// message: error.message
|
||||||
|
// })
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// } else {
|
||||||
|
// updateJobLine({
|
||||||
|
// variables: {
|
||||||
|
// lineId: jobLineEditModal.context.id,
|
||||||
|
// line: values
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// .then(r => {
|
||||||
|
// notification["success"]({
|
||||||
|
// message: t("joblines.successes.updated")
|
||||||
|
// });
|
||||||
|
// })
|
||||||
|
// .catch(error => {
|
||||||
|
// notification["success"]({
|
||||||
|
// message: t("joblines.errors.updating", {
|
||||||
|
// message: error.message
|
||||||
|
// })
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// if (jobLineEditModal.actions.refetch)
|
||||||
|
// jobLineEditModal.actions.refetch();
|
||||||
|
// toggleModalVisible();
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
toggleModalVisible();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InvoiceEnterModalComponent
|
||||||
|
visible={invoiceEnterModal.visible}
|
||||||
|
invoice={invoiceEnterModal.context}
|
||||||
|
handleSubmit={handleSubmit}
|
||||||
|
handleCancel={handleCancel}
|
||||||
|
form={form}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(
|
||||||
|
Form.create({ name: "InvoiceEnterModalContainer" })(
|
||||||
|
InvoiceEnterModalContainer
|
||||||
|
)
|
||||||
|
);
|
||||||
@@ -0,0 +1,735 @@
|
|||||||
|
import React from "react";
|
||||||
|
export default ({ dmg1, dmg2 }) => (
|
||||||
|
<svg
|
||||||
|
id='svg166'
|
||||||
|
version='1.1'
|
||||||
|
viewBox='0 0 1668 1160'
|
||||||
|
xmlns='http://www.w3.org/2000/svg'>
|
||||||
|
<g id='g158' transform='translate(254 -254)'>
|
||||||
|
<g id='g34' transform='translate(-13.78 3.524)' stroke='#000'>
|
||||||
|
<path
|
||||||
|
id='path10'
|
||||||
|
d='m494.57 1006.9c41.429-15.714 140-11.427 191.43-12.857 51.429-1.429 160.48 10.23 201.43 27.143 40.995 16.93 134.78 67.656 151.43 72.857 22.857 7.143 41.429 7.143 80 20 38.572 12.857 25.714 32.857 25.714 32.857l-30-4.286-5.756 52.92s37.185 1.366 41.47 15.652c4.286 14.286 5.715 31.428-2.856 41.428-8.572 10-14.286-1.428-18.572 12.858-4.286 14.285-2.857 28.571-27.143 27.142-24.285-1.428-98.571 0-98.571 0s-15.714-108.57-98.573-105.71c-82.857 2.857-95.714 105.71-95.714 105.71h-500s-5.714-104.28-97.143-105.71c-91.428-1.428-98.571 105.71-98.571 105.71h-65.713l-18.572-38.571c-0.515 0-26.243 0-21.428-17.143 4.651-16.561-4.286-41.428 17.142-41.428 21.429 0 47.143 1.428 47.143 1.428l15.715-44.286-41.429-2.857s34.286-24.285 118.57-32.857c84.286-8.571 157.14-8.571 192.86-31.428 35.714-22.858 137.14-78.572 137.14-78.572z'
|
||||||
|
fill='none'
|
||||||
|
strokeWidth='5'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
id='path12'
|
||||||
|
d='m28.857 1254h92.857m180 0h517.14m172.86 0h151.43'
|
||||||
|
fill='none'
|
||||||
|
strokeWidth='2'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
id='path14'
|
||||||
|
d='m364.57 1101.1c15.715-18.572 123.37-81.525 151.43-88.572 29.502-7.41 103-7.142 103-7.142l-7.143 95.714z'
|
||||||
|
fill='#f0ffeb'
|
||||||
|
strokeWidth='5'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
id='path16'
|
||||||
|
d='m404.57 1071.1v28.571'
|
||||||
|
fill='none'
|
||||||
|
strokeWidth='2'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
id='path18'
|
||||||
|
d='m644.7 1006.8-4.285 94.328h207.16c-11.307-20.753-46.612-74.906-72.857-88.572-14.285-10-82.878-4.327-130.02-5.756zm167.02 7.164s80 84.286 85.715 87.143c5.714 2.857 115.71 1.428 115.71 1.428s-77.143-47.141-102.86-58.571c-25.715-11.429-90-31.429-98.572-30z'
|
||||||
|
fill='#f0ffeb'
|
||||||
|
strokeWidth='5'
|
||||||
|
/>
|
||||||
|
<g fill='none'>
|
||||||
|
<path
|
||||||
|
id='path20'
|
||||||
|
d='m345.64 1091.7c-14.075 30.968-18.298 71.788-18.298 94.31 0 22.521 4.223 91.493 22.521 105.57m282.93-298.41c-1.408 5.63-4.047 81.903-6.51 106.93-3.67 18.715-4.989 51.219-6.159 87.315 0 22.522 7.038 80.233 12.669 105.57'
|
||||||
|
strokeWidth='5'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
id='path22'
|
||||||
|
d='m103.53 1252.1s15.483-85.864 106.98-85.864c91.494 0 109.79 87.271 109.79 87.271m479.99 0s15.483-85.863 106.98-85.863c91.493 0 109.79 87.27 109.79 87.27m-947.31-57.711h63.342'
|
||||||
|
strokeWidth='2'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
id='path24'
|
||||||
|
d='m779.18 1000.2c18.299 12.668 56.304 50.673 92.9 104.16 36.598 53.489 8.447 59.12-18.298 74.603-26.744 15.483-49.266 42.227-67.564 112.61'
|
||||||
|
strokeWidth='5'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
id='path26'
|
||||||
|
d='m1112.8 1196h-129.5m-696.76-0.222h544.74m-22.522-150.61v54.896'
|
||||||
|
strokeWidth='2'
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g strokeWidth='2'>
|
||||||
|
<rect
|
||||||
|
id='rect28'
|
||||||
|
x='558.65'
|
||||||
|
y='1144.9'
|
||||||
|
width='41.286'
|
||||||
|
height='14.541'
|
||||||
|
rx='2.933'
|
||||||
|
ry='7.379'
|
||||||
|
fill='#e6e6e6'
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
id='rect30'
|
||||||
|
x='816.24'
|
||||||
|
y='1144.9'
|
||||||
|
width='41.286'
|
||||||
|
height='14.541'
|
||||||
|
rx='2.933'
|
||||||
|
ry='7.379'
|
||||||
|
fill='#e6e6e6'
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
id='rect32'
|
||||||
|
x='259.53'
|
||||||
|
y='1146.3'
|
||||||
|
width='18.776'
|
||||||
|
height='11.738'
|
||||||
|
rx='1.63'
|
||||||
|
ry='7.692'
|
||||||
|
fill='#ffcb00'
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g id='g54' transform='translate(-13.78 15.524)' stroke='#000'>
|
||||||
|
<path
|
||||||
|
id='path36'
|
||||||
|
d='m62.767 812.59-11.712 16.12-18.95-6.157v-19.925l18.95-6.158z'
|
||||||
|
fill='none'
|
||||||
|
strokeWidth='5'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
id='path38'
|
||||||
|
d='m31.742 745.02 315.3-111.2m-316.71 254.77 315.3 115.42'
|
||||||
|
fill='none'
|
||||||
|
strokeWidth='2'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
id='path40'
|
||||||
|
d='m341.41 632.78 140.76 38.005s-8.446 49.222-8.446 71.963v147.62c0 30.967 8.445 78.825 8.445 78.825l-140.76 33.782s-17.242-61.902-17.242-92.901l2.111-185.8c-1.408-30.967 15.132-91.493 15.132-91.493z'
|
||||||
|
fill='#f0ffeb'
|
||||||
|
strokeWidth='5'
|
||||||
|
/>
|
||||||
|
<g fill='none'>
|
||||||
|
<path
|
||||||
|
id='path42'
|
||||||
|
d='m482.17 670.79s94.309 5.63 152.02 5.63c106.98 0 201.29-5.63 201.29-5.63m-1.408 297s-77.418-5.63-199.88-5.63c-68.972 0-152.02 7.038-152.02 7.038'
|
||||||
|
strokeWidth='5'
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
id='rect44'
|
||||||
|
x='528.62'
|
||||||
|
y='729.2'
|
||||||
|
width='106.98'
|
||||||
|
height='181.58'
|
||||||
|
rx='31.19'
|
||||||
|
ry='34.436'
|
||||||
|
strokeWidth='2'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
id='path46'
|
||||||
|
d='m345.78 632.78h-294.19l-14.076 102.75-7.038 11.26v142.17l7.038 14.076 15.483 101.35h288.56m491.11-333.6s-12.668 47.858-12.668 73.195v146.39c0 25.336 12.668 77.417 12.668 77.417l205.51 38.005h108.38s14.076-81.64 14.076-115.42v-147.8c0-38.005-14.076-109.79-14.076-109.79h-108.38z'
|
||||||
|
strokeWidth='5'
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<path
|
||||||
|
id='path48'
|
||||||
|
d='m843.92 689.09s-8.445 40.82-8.445 56.303v144.98c0 19.706 7.038 57.711 7.038 57.711s94.308 26.744 123.87 26.744h42.228v-312.49h-47.859c-25.336 0-116.83 26.745-116.83 26.745z'
|
||||||
|
fill='#f0ffeb'
|
||||||
|
strokeWidth='5'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
id='path50'
|
||||||
|
d='m1038.2 632.78s30.967 40.82 30.967 111.2v146.39c0 64.749-30.967 116.83-30.967 116.83m-713.65-263.22 32.374-32.375v-40.82 112.61m-33.078 81.641 32.374-32.375v-40.82 112.61m648.9-116.83-32.375-32.374v-40.82 112.61'
|
||||||
|
fill='none'
|
||||||
|
strokeWidth='2'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
id='path52'
|
||||||
|
d='m341.41 632.78s-42.228 78.825-42.228 111.2v146.39c0 40.82 42.228 114.02 42.228 114.02'
|
||||||
|
fill='none'
|
||||||
|
strokeWidth='2'
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g fill='none' stroke='#000'>
|
||||||
|
<path
|
||||||
|
id='path56'
|
||||||
|
d='m-106.79 740.92 1.407-91.493s5.63-23.93-25.337-25.337c-30.967-1.408-26.744 2.815-26.744 2.815l1.408 415.24h28.152c28.152 0 22.521-22.52 22.521-22.52v-95.717c-12.677 0-11.26-9.614-11.26-16.891v-149.2c0.45-18.89 9.853-16.892 9.853-16.892z'
|
||||||
|
strokeWidth='5'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
id='path58'
|
||||||
|
d='m-155.99 646.16h49.265m-48.415 374.27h49.266m-50.82-315.37h49.266m-46.451 259.81h49.266'
|
||||||
|
strokeWidth='2.2'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
id='path60'
|
||||||
|
d='m-147.1 703.82v260.4m9.853-258.84v260.4'
|
||||||
|
strokeWidth='2'
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g id='g88' transform='translate(-13.78 15.524)' stroke='#000'>
|
||||||
|
<path
|
||||||
|
id='path62'
|
||||||
|
d='m494.57 641.61c41.429 15.714 140 11.428 191.43 12.856s160.48-10.23 201.43-27.143c40.995-16.93 134.78-67.656 151.43-72.857 22.857-7.143 41.429-7.143 80-20 38.572-12.857 25.714-32.857 25.714-32.857l-30 4.285-5.756-52.92s37.185-1.365 41.47-15.651c4.286-14.286 5.715-31.429-2.856-41.429-8.572-10-14.286 1.429-18.572-12.857-4.286-14.285-2.857-28.571-27.143-27.143-24.285 1.429-98.571 0-98.571 0s-15.714 108.57-98.572 105.72c-82.857-2.857-95.714-105.72-95.714-105.72h-500s-5.714 104.29-97.143 105.72c-91.428 1.428-98.571-105.72-98.571-105.72h-65.714l-18.572 38.572c-0.515 0-26.243 0-21.428 17.143 4.651 16.561-4.286 41.428 17.142 41.428 21.429 0 47.143-1.428 47.143-1.428l15.715 44.285-41.429 2.859s34.286 24.285 118.57 32.857c84.286 8.571 157.14 8.571 192.86 31.428 35.714 22.857 137.14 78.572 137.14 78.572z'
|
||||||
|
fill='none'
|
||||||
|
strokeWidth='5'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
id='path64'
|
||||||
|
d='m28.857 394.47h92.857m180 0h517.14m172.86 0h151.43'
|
||||||
|
fill='none'
|
||||||
|
strokeWidth='2'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
id='path66'
|
||||||
|
d='m364.57 547.33c15.715 18.571 123.37 81.524 151.43 88.571 29.502 7.41 103 7.143 103 7.143l-7.143-95.714z'
|
||||||
|
fill='#f0ffeb'
|
||||||
|
strokeWidth='5'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
id='path68'
|
||||||
|
d='m404.57 577.33v-28.571'
|
||||||
|
fill='none'
|
||||||
|
strokeWidth='2'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
id='path70'
|
||||||
|
d='m644.7 641.64-4.285-94.328h207.16c-11.307 20.753-46.612 74.906-72.857 88.571-14.285 10-82.878 4.328-130.02 5.757zm167.02-7.165s80-84.285 85.715-87.142c5.714-2.857 115.71-1.429 115.71-1.429s-77.143 47.143-102.86 58.572c-25.715 11.428-90 31.428-98.572 30z'
|
||||||
|
fill='#f0ffeb'
|
||||||
|
strokeWidth='5'
|
||||||
|
/>
|
||||||
|
<g fill='none'>
|
||||||
|
<path
|
||||||
|
id='path72'
|
||||||
|
d='m345.64 556.81c-14.075-30.967-18.298-71.787-18.298-94.309 0-22.521 4.223-91.493 22.521-105.57m282.93 298.41c-1.408-5.63-4.047-81.905-6.51-106.93-3.67-18.714-4.989-51.218-6.159-87.314 0-22.522 7.038-80.233 12.669-105.57'
|
||||||
|
strokeWidth='5'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
id='path74'
|
||||||
|
d='m103.53 396.34s15.483 85.864 106.98 85.864c91.494 0 109.79-87.271 109.79-87.271m479.99 0s15.483 85.863 106.98 85.863c91.493 0 109.79-87.27 109.79-87.27m-947.31 57.71h63.342'
|
||||||
|
strokeWidth='2'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
id='path76'
|
||||||
|
d='m779.18 648.3c18.299-12.669 56.304-50.674 92.9-104.16 36.598-53.489 8.447-59.12-18.298-74.603-26.744-15.483-49.266-42.228-67.564-112.61'
|
||||||
|
strokeWidth='5'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
id='path78'
|
||||||
|
d='m1112.8 452.42h-129.5m-696.76 0.223h544.74m-22.522 150.61v-54.895'
|
||||||
|
strokeWidth='2'
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g strokeWidth='2'>
|
||||||
|
<rect
|
||||||
|
id='rect80'
|
||||||
|
transform='scale(1 -1)'
|
||||||
|
x='558.65'
|
||||||
|
y='-503.55'
|
||||||
|
width='41.286'
|
||||||
|
height='14.541'
|
||||||
|
rx='2.933'
|
||||||
|
ry='7.379'
|
||||||
|
fill='#e6e6e6'
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
id='rect82'
|
||||||
|
transform='scale(1 -1)'
|
||||||
|
x='816.24'
|
||||||
|
y='-503.55'
|
||||||
|
width='41.286'
|
||||||
|
height='14.541'
|
||||||
|
rx='2.933'
|
||||||
|
ry='7.379'
|
||||||
|
fill='#e6e6e6'
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
id='rect84'
|
||||||
|
transform='scale(1 -1)'
|
||||||
|
x='259.53'
|
||||||
|
y='-502.15'
|
||||||
|
width='18.776'
|
||||||
|
height='11.738'
|
||||||
|
rx='1.63'
|
||||||
|
ry='7.692'
|
||||||
|
fill='#ffcb00'
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='circle86'
|
||||||
|
transform='translate(941.34 284)'
|
||||||
|
cx='59.119'
|
||||||
|
cy='211.28'
|
||||||
|
r='16.891'
|
||||||
|
fill='#fff'
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<path
|
||||||
|
id='path90'
|
||||||
|
d='m-126.58 704.93v260.4'
|
||||||
|
fill='none'
|
||||||
|
stroke='#000'
|
||||||
|
strokeWidth='2'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
id='path92'
|
||||||
|
d='m-153.88 992.49v-26.041h45.043v52.08h-45.043zm0-316.71v-27.448h45.043v54.898h-45.043z'
|
||||||
|
fill='#ffffc0'
|
||||||
|
/>
|
||||||
|
<g fill='none' stroke='#000'>
|
||||||
|
<path
|
||||||
|
id='path94'
|
||||||
|
d='m-157.4 624.38s-4.223-12.669-18.299-12.669-14.076 8.446-14.076 8.446v423.69s1.408 9.853 14.076 9.853c12.669 0 18.3-9.853 18.3-9.853'
|
||||||
|
strokeWidth='5'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
id='path96'
|
||||||
|
d='m-191.18 624.38s-35.19-1.408-35.19 21.114v371.6c0 22.521 36.597 25.336 36.597 25.336'
|
||||||
|
strokeWidth='5'
|
||||||
|
/>
|
||||||
|
<g strokeWidth='2'>
|
||||||
|
<rect
|
||||||
|
id='rect98'
|
||||||
|
x='-216.51'
|
||||||
|
y='648.31'
|
||||||
|
width='16.891'
|
||||||
|
height='35.19'
|
||||||
|
rx='7.742'
|
||||||
|
ry='6.284'
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
id='rect100'
|
||||||
|
x='-216.51'
|
||||||
|
y='985.58'
|
||||||
|
width='16.891'
|
||||||
|
height='35.19'
|
||||||
|
rx='7.742'
|
||||||
|
ry='6.284'
|
||||||
|
/>
|
||||||
|
<path id='path102' d='m-62.939 645.49h30.967v377.24h-30.967z' />
|
||||||
|
</g>
|
||||||
|
<path
|
||||||
|
id='path104'
|
||||||
|
d='m1200.9 633.86v73.195h67.565v-83.048l-25.337-14.076zm0 329.38h67.565v78.826l-23.93 14.076-43.635-18.3zm0-270.26v284.33m67.565-283.74v284.33'
|
||||||
|
strokeWidth='5'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
id='path106'
|
||||||
|
d='m1216.6 759.51h14.076v147.8h-14.076zm7.631-1.408v-53.488m0 257.37v-53.49m30.375-201.06v256.18'
|
||||||
|
strokeWidth='2'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
id='path108'
|
||||||
|
d='m1268.8 624.97s4.223-12.668 18.299-12.668 14.076 8.445 14.076 8.445v423.69s-1.408 9.853-14.076 9.853c-12.669 0-18.299-9.853-18.299-9.853m33.783-419.46s35.19-1.408 35.19 21.114v371.6c0 22.521-36.598 25.337-36.598 25.337'
|
||||||
|
strokeWidth='5'
|
||||||
|
/>
|
||||||
|
<g strokeWidth='2'>
|
||||||
|
<rect
|
||||||
|
id='rect110'
|
||||||
|
transform='scale(-1 1)'
|
||||||
|
x='-1329.9'
|
||||||
|
y='648.9'
|
||||||
|
width='16.891'
|
||||||
|
height='35.19'
|
||||||
|
rx='7.742'
|
||||||
|
ry='6.284'
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
id='rect112'
|
||||||
|
transform='scale(-1 1)'
|
||||||
|
x='-1329.9'
|
||||||
|
y='986.17'
|
||||||
|
width='16.891'
|
||||||
|
height='35.19'
|
||||||
|
rx='7.742'
|
||||||
|
ry='6.284'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
id='path114'
|
||||||
|
d='m1199.7 632.82 67.565 74.603m-67.565 0.592 67.565-73.195m-67.342 328.16 67.565 74.602m-67.565 0.593 67.565-73.195'
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g stroke='#000'>
|
||||||
|
<g strokeWidth='2'>
|
||||||
|
<circle
|
||||||
|
id='circle116'
|
||||||
|
transform='translate(78.489 1074.7)'
|
||||||
|
cx='119.65'
|
||||||
|
cy='202.84'
|
||||||
|
r='76.01'
|
||||||
|
fill='#c8c8c8'
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='circle118'
|
||||||
|
transform='translate(94.676 1204.9)'
|
||||||
|
cx='103.46'
|
||||||
|
cy='72.633'
|
||||||
|
r='44.339'
|
||||||
|
fill='#fff'
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='circle120'
|
||||||
|
transform='translate(78.489 187.66)'
|
||||||
|
cx='119.65'
|
||||||
|
cy='202.84'
|
||||||
|
r='76.01'
|
||||||
|
fill='#c8c8c8'
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='circle122'
|
||||||
|
transform='translate(94.676 317.86)'
|
||||||
|
cx='103.46'
|
||||||
|
cy='72.633'
|
||||||
|
r='44.339'
|
||||||
|
fill='#fff'
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='circle124'
|
||||||
|
transform='translate(773.02 187.66)'
|
||||||
|
cx='119.65'
|
||||||
|
cy='202.84'
|
||||||
|
r='76.01'
|
||||||
|
fill='#c8c8c8'
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='circle126'
|
||||||
|
transform='translate(789.21 317.86)'
|
||||||
|
cx='103.46'
|
||||||
|
cy='72.633'
|
||||||
|
r='44.339'
|
||||||
|
fill='#fff'
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='circle128'
|
||||||
|
transform='translate(773.02 1074.7)'
|
||||||
|
cx='119.65'
|
||||||
|
cy='202.84'
|
||||||
|
r='76.01'
|
||||||
|
fill='#c8c8c8'
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='circle130'
|
||||||
|
transform='translate(789.21 1204.9)'
|
||||||
|
cx='103.46'
|
||||||
|
cy='72.633'
|
||||||
|
r='44.339'
|
||||||
|
fill='#fff'
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<path
|
||||||
|
id='path132'
|
||||||
|
d='m1338.5 829.07h40.82'
|
||||||
|
fill='none'
|
||||||
|
strokeWidth='5'
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='circle134'
|
||||||
|
transform='translate(1441.3 600.31)'
|
||||||
|
cx='-59.119'
|
||||||
|
cy='229.58'
|
||||||
|
r='4.223'
|
||||||
|
fill='#3c3c3c'
|
||||||
|
strokeWidth='5'
|
||||||
|
/>
|
||||||
|
<g strokeWidth='2'>
|
||||||
|
<path id='path136' d='m778.06 845.37-38.709-38.709' fill='none' />
|
||||||
|
<g id='g146' transform='translate(-13.78 15.524)' fill='#3c3c3c'>
|
||||||
|
<circle
|
||||||
|
id='circle138'
|
||||||
|
transform='translate(-79.458 449.8)'
|
||||||
|
cx='-81.641'
|
||||||
|
cy='188.76'
|
||||||
|
r='4.223'
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='circle140'
|
||||||
|
transform='translate(-79.458 569.92)'
|
||||||
|
cx='-81.641'
|
||||||
|
cy='188.76'
|
||||||
|
r='4.223'
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='circle142'
|
||||||
|
transform='translate(-79.458 690.03)'
|
||||||
|
cx='-81.641'
|
||||||
|
cy='188.76'
|
||||||
|
r='4.223'
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='circle144'
|
||||||
|
transform='translate(-79.458 810.15)'
|
||||||
|
cx='-81.641'
|
||||||
|
cy='188.76'
|
||||||
|
r='4.223'
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g id='g156' transform='translate(-13.78 17.524)' fill='#3c3c3c'>
|
||||||
|
<circle
|
||||||
|
id='circle148'
|
||||||
|
transform='translate(1381.6 448.25)'
|
||||||
|
cx='-81.641'
|
||||||
|
cy='188.76'
|
||||||
|
r='4.223'
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='circle150'
|
||||||
|
transform='translate(1381.6 568.36)'
|
||||||
|
cx='-81.641'
|
||||||
|
cy='188.76'
|
||||||
|
r='4.223'
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='circle152'
|
||||||
|
transform='translate(1381.6 688.48)'
|
||||||
|
cx='-81.641'
|
||||||
|
cy='188.76'
|
||||||
|
r='4.223'
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='circle154'
|
||||||
|
transform='translate(1381.6 808.59)'
|
||||||
|
cx='-81.641'
|
||||||
|
cy='188.76'
|
||||||
|
r='4.223'
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g id='layer2' fill='#d00000'>
|
||||||
|
<circle
|
||||||
|
id='p02'
|
||||||
|
cx='503.65'
|
||||||
|
cy='248.75'
|
||||||
|
r='61.935'
|
||||||
|
opacity={dmg1 === "02" ? 100 : 0}
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='p03'
|
||||||
|
cx='863.41'
|
||||||
|
cy='248.75'
|
||||||
|
r='61.935'
|
||||||
|
opacity={dmg1 === "03" ? 100 : 0}
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='p04'
|
||||||
|
cx='1181.5'
|
||||||
|
cy='248.75'
|
||||||
|
r='61.935'
|
||||||
|
opacity={dmg1 === "04" ? 100 : 0}
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='p05'
|
||||||
|
cx='1378.4'
|
||||||
|
cy='151.16'
|
||||||
|
r='61.935'
|
||||||
|
opacity={dmg1 === "05" ? 100 : 0}
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='p06'
|
||||||
|
cx='1535.1'
|
||||||
|
cy='581.37'
|
||||||
|
r='61.935'
|
||||||
|
opacity={dmg1 === "06" ? 100 : 0}
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='p07'
|
||||||
|
cx='1378.4'
|
||||||
|
cy='997.9'
|
||||||
|
r='61.935'
|
||||||
|
opacity={dmg1 === "07" ? 100 : 0}
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='p08'
|
||||||
|
cx='1181.5'
|
||||||
|
cy='914.24'
|
||||||
|
r='61.935'
|
||||||
|
opacity={dmg1 === "08" ? 100 : 0}
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='p09'
|
||||||
|
transform='scale(1,-1)'
|
||||||
|
cx='863.41'
|
||||||
|
cy='-914.24'
|
||||||
|
r='61.935'
|
||||||
|
opacity={dmg1 === "09" ? 100 : 0}
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='p10'
|
||||||
|
cx='503.65'
|
||||||
|
cy='914.24'
|
||||||
|
r='61.935'
|
||||||
|
opacity={dmg1 === "10" ? 100 : 0}
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='p11'
|
||||||
|
cx='297.77'
|
||||||
|
cy='997.9'
|
||||||
|
r='61.935'
|
||||||
|
opacity={dmg1 === "11" ? 100 : 0}
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='p12'
|
||||||
|
cx='93.269'
|
||||||
|
cy='581.37'
|
||||||
|
r='61.935'
|
||||||
|
opacity={dmg1 === "12" ? 100 : 0}
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='p25'
|
||||||
|
cx='424.31'
|
||||||
|
cy='581.37'
|
||||||
|
r='61.935'
|
||||||
|
opacity={dmg1 === "25" ? 100 : 0}
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='p27'
|
||||||
|
cx='972.84'
|
||||||
|
cy='581.37'
|
||||||
|
r='61.935'
|
||||||
|
opacity={dmg1 === "27" ? 100 : 0}
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='p01'
|
||||||
|
cx='297.77'
|
||||||
|
cy='151.16'
|
||||||
|
r='61.935'
|
||||||
|
opacity={dmg1 === "01" ? 100 : 0}
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='p26'
|
||||||
|
cx='1339.4'
|
||||||
|
cy='581.37'
|
||||||
|
r='61.935'
|
||||||
|
opacity={dmg1 === "26" ? 100 : 0}
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g id='g4994' fill='#ffef00'>
|
||||||
|
<circle
|
||||||
|
id='s02'
|
||||||
|
cx='503.65'
|
||||||
|
cy='248.75'
|
||||||
|
r='61.935'
|
||||||
|
opacity={dmg2 === "02" ? 100 : 0}
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='s03'
|
||||||
|
cx='863.41'
|
||||||
|
cy='248.75'
|
||||||
|
r='61.935'
|
||||||
|
opacity={dmg2 === "03" ? 100 : 0}
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='s04'
|
||||||
|
cx='1181.5'
|
||||||
|
cy='248.75'
|
||||||
|
r='61.935'
|
||||||
|
opacity={dmg2 === "04" ? 100 : 0}
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='s05'
|
||||||
|
cx='1378.4'
|
||||||
|
cy='151.16'
|
||||||
|
r='61.935'
|
||||||
|
opacity={dmg2 === "05" ? 100 : 0}
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='s06'
|
||||||
|
cx='1535.1'
|
||||||
|
cy='581.37'
|
||||||
|
r='61.935'
|
||||||
|
opacity={dmg2 === "06" ? 100 : 0}
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='s07'
|
||||||
|
cx='1378.4'
|
||||||
|
cy='997.9'
|
||||||
|
r='61.935'
|
||||||
|
opacity={dmg2 === "07" ? 100 : 0}
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='s08'
|
||||||
|
cx='1181.5'
|
||||||
|
cy='914.24'
|
||||||
|
r='61.935'
|
||||||
|
opacity={dmg2 === "08" ? 100 : 0}
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='s09'
|
||||||
|
transform='scale(1,-1)'
|
||||||
|
cx='863.41'
|
||||||
|
cy='-914.24'
|
||||||
|
r='61.935'
|
||||||
|
opacity={dmg2 === "09" ? 100 : 0}
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='s10'
|
||||||
|
cx='503.65'
|
||||||
|
cy='914.24'
|
||||||
|
r='61.935'
|
||||||
|
opacity={dmg2 === "10" ? 100 : 0}
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='s11'
|
||||||
|
cx='297.77'
|
||||||
|
cy='997.9'
|
||||||
|
r='61.935'
|
||||||
|
opacity={dmg2 === "11" ? 100 : 0}
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='s12'
|
||||||
|
cx='93.269'
|
||||||
|
cy='581.37'
|
||||||
|
r='61.935'
|
||||||
|
opacity={dmg2 === "12" ? 100 : 0}
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='s25'
|
||||||
|
cx='424.31'
|
||||||
|
cy='581.37'
|
||||||
|
r='61.935'
|
||||||
|
opacity={dmg2 === "25" ? 100 : 0}
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='s27'
|
||||||
|
cx='972.84'
|
||||||
|
cy='581.37'
|
||||||
|
r='61.935'
|
||||||
|
opacity={dmg2 === "27" ? 100 : 0}
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='s01'
|
||||||
|
cx='297.77'
|
||||||
|
cy='151.16'
|
||||||
|
r='61.935'
|
||||||
|
opacity={dmg2 === "01" ? 100 : 0}
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
id='s26'
|
||||||
|
cx='1339.4'
|
||||||
|
cy='581.37'
|
||||||
|
r='61.935'
|
||||||
|
opacity={dmg2 === "26" ? 100 : 0}
|
||||||
|
/>
|
||||||
|
{
|
||||||
|
// <text
|
||||||
|
// id='p15'
|
||||||
|
// opacity='0'
|
||||||
|
// x='382.62802'
|
||||||
|
// y='1034.3463'
|
||||||
|
// fill='#fd0000'
|
||||||
|
// fontFamily='sans-serif'
|
||||||
|
// fontSize='1696.9px'
|
||||||
|
// letterSpacing='0px'
|
||||||
|
// strokeWidth='17.676'
|
||||||
|
// wordSpacing='0px'
|
||||||
|
// style='line-height:5.25'>
|
||||||
|
// x
|
||||||
|
// </text>
|
||||||
|
}
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
@@ -17,8 +17,7 @@ import JobDetailCardsNotesComponent from "./job-detail-cards.notes.component";
|
|||||||
import JobDetailCardsPartsComponent from "./job-detail-cards.parts.component";
|
import JobDetailCardsPartsComponent from "./job-detail-cards.parts.component";
|
||||||
import "./job-detail-cards.styles.scss";
|
import "./job-detail-cards.styles.scss";
|
||||||
import JobDetailCardsTotalsComponent from "./job-detail-cards.totals.component";
|
import JobDetailCardsTotalsComponent from "./job-detail-cards.totals.component";
|
||||||
|
import ScheduleJobModalContainer from "../schedule-job-modal/schedule-job-modal.container";
|
||||||
|
|
||||||
|
|
||||||
export default function JobDetailCards({ selectedJob }) {
|
export default function JobDetailCards({ selectedJob }) {
|
||||||
const { loading, error, data, refetch } = useQuery(QUERY_JOB_CARD_DETAILS, {
|
const { loading, error, data, refetch } = useQuery(QUERY_JOB_CARD_DETAILS, {
|
||||||
@@ -27,6 +26,7 @@ export default function JobDetailCards({ selectedJob }) {
|
|||||||
skip: !selectedJob
|
skip: !selectedJob
|
||||||
});
|
});
|
||||||
const [noteModalVisible, setNoteModalVisible] = useState(false);
|
const [noteModalVisible, setNoteModalVisible] = useState(false);
|
||||||
|
const scheduleModalState = useState(false);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
if (!selectedJob) {
|
if (!selectedJob) {
|
||||||
@@ -43,13 +43,18 @@ export default function JobDetailCards({ selectedJob }) {
|
|||||||
changeVisibility={setNoteModalVisible}
|
changeVisibility={setNoteModalVisible}
|
||||||
refetch={refetch}
|
refetch={refetch}
|
||||||
/>
|
/>
|
||||||
|
<ScheduleJobModalContainer
|
||||||
|
scheduleModalState={scheduleModalState}
|
||||||
|
jobId={data.jobs_by_pk.id}
|
||||||
|
refetch={refetch}
|
||||||
|
/>
|
||||||
<PageHeader
|
<PageHeader
|
||||||
ghost={false}
|
ghost={false}
|
||||||
onBack={() => window.history.back()}
|
onBack={() => window.history.back()}
|
||||||
tags={
|
tags={
|
||||||
<span key='job-status'>
|
<span key='job-status'>
|
||||||
{data.jobs_by_pk.job_status ? (
|
{data.jobs_by_pk.status ? (
|
||||||
<Tag color='blue'>{data.jobs_by_pk.job_status.name}</Tag>
|
<Tag color='blue'>{data.jobs_by_pk.status}</Tag>
|
||||||
) : null}
|
) : null}
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
@@ -67,6 +72,14 @@ export default function JobDetailCards({ selectedJob }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
extra={[
|
extra={[
|
||||||
|
<Button
|
||||||
|
key='schedule'
|
||||||
|
//TODO Enabled logic based on status.
|
||||||
|
onClick={() => {
|
||||||
|
scheduleModalState[1](true);
|
||||||
|
}}>
|
||||||
|
{t("jobs.actions.schedule")}
|
||||||
|
</Button>,
|
||||||
<Link
|
<Link
|
||||||
key='documents'
|
key='documents'
|
||||||
to={`/manage/jobs/${data.jobs_by_pk.id}#documents`}>
|
to={`/manage/jobs/${data.jobs_by_pk.id}#documents`}>
|
||||||
|
|||||||
@@ -14,7 +14,10 @@ export default function JobDetailCardsCustomerComponent({ loading, data }) {
|
|||||||
extraLink={data && data.owner ? `/manage/owners/${data.owner.id}` : null}>
|
extraLink={data && data.owner ? `/manage/owners/${data.owner.id}` : null}>
|
||||||
{data ? (
|
{data ? (
|
||||||
<span>
|
<span>
|
||||||
<div>{`${data.ownr_fn || ""} ${data.ownr_ln || ""}`}</div>
|
<div>
|
||||||
|
<Link to={`/manage/owners/${data.owner.id}`}>{`${data.ownr_fn ||
|
||||||
|
""} ${data.ownr_ln || ""}`}</Link>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{t("jobs.fields.phoneshort")}:
|
{t("jobs.fields.phoneshort")}:
|
||||||
<PhoneFormatter>{`${data.ownr_ph1 ||
|
<PhoneFormatter>{`${data.ownr_ph1 ||
|
||||||
|
|||||||
@@ -1,18 +1,14 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import CardTemplate from "./job-detail-cards.template.component";
|
import CardTemplate from "./job-detail-cards.template.component";
|
||||||
import UnfoldedCar from "../../assets/unfolded_car.svg";
|
import Car from "../job-damage-visual/job-damage-visual.component";
|
||||||
|
|
||||||
export default function JobDetailCardsDamageComponent({ loading, data }) {
|
export default function JobDetailCardsDamageComponent({ loading, data }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { area_of_damage } = data;
|
||||||
return (
|
return (
|
||||||
<CardTemplate loading={loading} title={t("jobs.labels.cards.damage")}>
|
<CardTemplate loading={loading} title={t("jobs.labels.cards.damage")}>
|
||||||
{data ? (
|
<Car dmg1={area_of_damage.impact1} dmg2={area_of_damage.impact2} />
|
||||||
<span>
|
|
||||||
<img src={UnfoldedCar} alt='Damaged Area' width={200} height={200} />
|
|
||||||
</span>
|
|
||||||
) : null}
|
|
||||||
</CardTemplate>
|
</CardTemplate>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
|
import { Timeline } from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { DateFormatter } from "../../utils/DateFormatter";
|
||||||
import CardTemplate from "./job-detail-cards.template.component";
|
import CardTemplate from "./job-detail-cards.template.component";
|
||||||
import Moment from "react-moment";
|
|
||||||
import { Timeline } from "antd";
|
|
||||||
|
|
||||||
export default function JobDetailCardsDatesComponent({ loading, data }) {
|
export default function JobDetailCardsDatesComponent({ loading, data }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -31,90 +31,84 @@ export default function JobDetailCardsDatesComponent({ loading, data }) {
|
|||||||
{data.actual_in ? (
|
{data.actual_in ? (
|
||||||
<Timeline.Item>
|
<Timeline.Item>
|
||||||
{t("jobs.fields.actual_in")}
|
{t("jobs.fields.actual_in")}
|
||||||
<Moment format='MM/DD/YYYY'>{data.actual_in || ""}</Moment>
|
<DateFormatter>{data.actual_in}</DateFormatter>
|
||||||
</Timeline.Item>
|
</Timeline.Item>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{data.scheduled_completion ? (
|
{data.scheduled_completion ? (
|
||||||
<Timeline.Item>
|
<Timeline.Item>
|
||||||
{t("jobs.fields.scheduled_completion")}
|
{t("jobs.fields.scheduled_completion")}
|
||||||
<Moment format='MM/DD/YYYY'>
|
<DateFormatter>{data.scheduled_completion}</DateFormatter>
|
||||||
{data.scheduled_completion || ""}
|
|
||||||
</Moment>
|
|
||||||
</Timeline.Item>
|
</Timeline.Item>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{data.scheduled_in ? (
|
{data.scheduled_in ? (
|
||||||
<Timeline.Item>
|
<Timeline.Item>
|
||||||
{t("jobs.fields.scheduled_in")}
|
{t("jobs.fields.scheduled_in")}
|
||||||
<Moment format='MM/DD/YYYY'>{data.scheduled_in || ""}</Moment>
|
<DateFormatter>{data.scheduled_in}</DateFormatter>
|
||||||
</Timeline.Item>
|
</Timeline.Item>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{data.actual_completion ? (
|
{data.actual_completion ? (
|
||||||
<Timeline.Item>
|
<Timeline.Item>
|
||||||
{t("jobs.fields.actual_completion")}
|
{t("jobs.fields.actual_completion")}
|
||||||
<Moment format='MM/DD/YYYY'>
|
<DateFormatter>{data.actual_completion}</DateFormatter>
|
||||||
{data.actual_completion || ""}
|
|
||||||
</Moment>
|
|
||||||
</Timeline.Item>
|
</Timeline.Item>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{data.scheduled_delivery ? (
|
{data.scheduled_delivery ? (
|
||||||
<Timeline.Item>
|
<Timeline.Item>
|
||||||
{t("jobs.fields.scheduled_delivery")}
|
{t("jobs.fields.scheduled_delivery")}
|
||||||
<Moment format='MM/DD/YYYY'>
|
<DateFormatter>{data.scheduled_delivery}</DateFormatter>
|
||||||
{data.scheduled_delivery || ""}
|
|
||||||
</Moment>
|
|
||||||
</Timeline.Item>
|
</Timeline.Item>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{data.actual_delivery ? (
|
{data.actual_delivery ? (
|
||||||
<Timeline.Item>
|
<Timeline.Item>
|
||||||
{t("jobs.fields.actual_delivery")}
|
{t("jobs.fields.actual_delivery")}
|
||||||
<Moment format='MM/DD/YYYY'>{data.actual_delivery || ""}</Moment>
|
<DateFormatter>{data.actual_delivery}</DateFormatter>
|
||||||
</Timeline.Item>
|
</Timeline.Item>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{data.date_estimated ? (
|
{data.date_estimated ? (
|
||||||
<Timeline.Item>
|
<Timeline.Item>
|
||||||
{t("jobs.fields.date_estimated")}
|
{t("jobs.fields.date_estimated")}
|
||||||
<Moment format='MM/DD/YYYY'>{data.date_estimated || ""}</Moment>
|
<DateFormatter>{data.date_estimated}</DateFormatter>
|
||||||
</Timeline.Item>
|
</Timeline.Item>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{data.date_open ? (
|
{data.date_open ? (
|
||||||
<Timeline.Item>
|
<Timeline.Item>
|
||||||
{t("jobs.fields.date_open")}
|
{t("jobs.fields.date_open")}
|
||||||
<Moment format='MM/DD/YYYY'>{data.date_open || ""}</Moment>
|
<DateFormatter>{data.date_open}</DateFormatter>
|
||||||
</Timeline.Item>
|
</Timeline.Item>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{data.date_scheduled ? (
|
{data.date_scheduled ? (
|
||||||
<Timeline.Item>
|
<Timeline.Item>
|
||||||
{t("jobs.fields.date_scheduled")}
|
{t("jobs.fields.date_scheduled")}
|
||||||
<Moment format='MM/DD/YYYY'>{data.date_scheduled || ""}</Moment>
|
<DateFormatter>{data.date_scheduled}</DateFormatter>
|
||||||
</Timeline.Item>
|
</Timeline.Item>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{data.date_invoiced ? (
|
{data.date_invoiced ? (
|
||||||
<Timeline.Item>
|
<Timeline.Item>
|
||||||
{t("jobs.fields.date_invoiced")}
|
{t("jobs.fields.date_invoiced")}
|
||||||
<Moment format='MM/DD/YYYY'>{data.date_invoiced || ""}</Moment>
|
<DateFormatter>{data.date_invoiced}</DateFormatter>
|
||||||
</Timeline.Item>
|
</Timeline.Item>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{data.date_closed ? (
|
{data.date_closed ? (
|
||||||
<Timeline.Item>
|
<Timeline.Item>
|
||||||
{t("jobs.fields.date_closed")}
|
{t("jobs.fields.date_closed")}
|
||||||
<Moment format='MM/DD/YYYY'>{data.date_closed || ""}</Moment>
|
<DateFormatter>{data.date_closed}</DateFormatter>
|
||||||
</Timeline.Item>
|
</Timeline.Item>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{data.date_exported ? (
|
{data.date_exported ? (
|
||||||
<Timeline.Item>
|
<Timeline.Item>
|
||||||
{t("jobs.fields.date_exported")}
|
{t("jobs.fields.date_exported")}
|
||||||
<Moment format='MM/DD/YYYY'>{data.date_exported || ""}</Moment>
|
<DateFormatter>{data.date_exported}</DateFormatter>
|
||||||
</Timeline.Item>
|
</Timeline.Item>
|
||||||
) : null}
|
) : null}
|
||||||
</Timeline>
|
</Timeline>
|
||||||
|
|||||||
@@ -18,13 +18,12 @@ export default function JobDetailCardsDocumentsComponent({ loading, data }) {
|
|||||||
<CardTemplate
|
<CardTemplate
|
||||||
loading={loading}
|
loading={loading}
|
||||||
title={t("jobs.labels.cards.documents")}
|
title={t("jobs.labels.cards.documents")}
|
||||||
extraLink={`/manage/jobs/${data.id}#documents`}>
|
extraLink={`/manage/jobs/${data.id}#documents`}
|
||||||
{data.documents.count > 0 ? (
|
>
|
||||||
|
{data.documents.length > 0 ? (
|
||||||
<Carousel autoplay>
|
<Carousel autoplay>
|
||||||
{data.documents.map(item => (
|
{data.documents.map(item => (
|
||||||
<div key={item.id}>
|
<img key={item.id} src={item.thumb_url} alt={item.name} />
|
||||||
<img src={item.thumb_url} alt={item.name} />
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
</Carousel>
|
</Carousel>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
.ant-carousel .slick-slide {
|
.ant-carousel .slick-slide {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
height: 160px;
|
height: 50px;
|
||||||
line-height: 160px;
|
width: 50px;
|
||||||
background: #364d79;
|
line-height: 50px;
|
||||||
overflow: hidden;
|
background: #364d79;
|
||||||
}
|
overflow: hidden;
|
||||||
|
}
|
||||||
.ant-carousel .slick-slide h3 {
|
|
||||||
color: #fff;
|
.ant-carousel .slick-slide h3 {
|
||||||
}
|
color: #ccddaa;
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,45 +10,45 @@ export default function JobDetailCardsInsuranceComponent({ loading, data }) {
|
|||||||
<CardTemplate loading={loading} title={t("jobs.labels.cards.insurance")}>
|
<CardTemplate loading={loading} title={t("jobs.labels.cards.insurance")}>
|
||||||
{data ? (
|
{data ? (
|
||||||
<span>
|
<span>
|
||||||
<div>{data?.ins_co_nm || t("general.labels.unknown")}</div>
|
<div>{data.ins_co_nm || t("general.labels.unknown")}</div>
|
||||||
<div>{data?.clm_no || t("general.labels.unknown")}</div>
|
<div>{data.clm_no || t("general.labels.unknown")}</div>
|
||||||
<div>
|
<div>
|
||||||
{t("jobs.labels.cards.filehandler")}
|
{t("jobs.labels.cards.filehandler")}
|
||||||
{data?.ins_ea ? (
|
{data.ins_ea ? (
|
||||||
<a href={`mailto:${data.ins_ea}`}>
|
<a href={`mailto:${data.ins_ea}`}>
|
||||||
<div>{`${data?.ins_ct_fn || ""} ${data?.ins_ct_ln || ""}`}</div>
|
<div>{`${data.ins_ct_fn || ""} ${data.ins_ct_ln || ""}`}</div>
|
||||||
</a>
|
</a>
|
||||||
) : (
|
) : (
|
||||||
<div>{`${data?.ins_ct_fn || ""} ${data?.ins_ct_ln || ""}`}</div>
|
<div>{`${data.ins_ct_fn || ""} ${data.ins_ct_ln || ""}`}</div>
|
||||||
)}
|
)}
|
||||||
{data?.ins_ph1 ? (
|
{data.ins_ph1 ? (
|
||||||
<PhoneFormatter>{data?.ins_ph1}</PhoneFormatter>
|
<PhoneFormatter>{data.ins_ph1}</PhoneFormatter>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
{t("jobs.labels.cards.appraiser")}
|
{t("jobs.labels.cards.appraiser")}
|
||||||
{data?.est_ea ? (
|
{data.est_ea ? (
|
||||||
<a href={`mailto:${data.est_ea}`}>
|
<a href={`mailto:${data.est_ea}`}>
|
||||||
<div>{`${data?.ins_ct_fn || ""} ${data?.ins_ct_ln || ""}`}</div>
|
<div>{`${data.ins_ct_fn || ""} ${data.ins_ct_ln || ""}`}</div>
|
||||||
</a>
|
</a>
|
||||||
) : (
|
) : (
|
||||||
<div>{`${data?.ins_ct_fn || ""} ${data?.ins_ct_ln || ""}`}</div>
|
<div>{`${data.ins_ct_fn || ""} ${data.ins_ct_ln || ""}`}</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
{t("jobs.labels.cards.estimator")}
|
{t("jobs.labels.cards.estimator")}
|
||||||
{data?.est_ea ? (
|
{data.est_ea ? (
|
||||||
<a href={`mailto:${data.est_ea}`}>
|
<a href={`mailto:${data.est_ea}`}>
|
||||||
<div>{`${data?.est_ct_fn || ""} ${data?.est_ct_ln || ""}`}</div>
|
<div>{`${data.est_ct_fn || ""} ${data.est_ct_ln || ""}`}</div>
|
||||||
</a>
|
</a>
|
||||||
) : (
|
) : (
|
||||||
<div>{`${data?.est_ct_fn || ""} ${data?.est_ct_ln || ""}`}</div>
|
<div>{`${data.est_ct_fn || ""} ${data.est_ct_ln || ""}`}</div>
|
||||||
)}
|
)}
|
||||||
{data?.est_ph1 ? (
|
{data.est_ph1 ? (
|
||||||
<PhoneFormatter>{data?.est_ph1}</PhoneFormatter>
|
<PhoneFormatter>{data.est_ph1}</PhoneFormatter>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export default function JobDetailCardsNotesComponent({ loading, data }) {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CardTemplate
|
<CardTemplate
|
||||||
loading={loading}
|
loading={loading}
|
||||||
title={t("jobs.labels.cards.notes")}
|
title={t("jobs.labels.cards.notes")}
|
||||||
extraLink={`/manage/jobs/${data.id}#notes`}>
|
extraLink={`/manage/jobs/${data.id}#notes`}>
|
||||||
@@ -22,7 +22,7 @@ export default function JobDetailCardsNotesComponent({ loading, data }) {
|
|||||||
<List
|
<List
|
||||||
size='small'
|
size='small'
|
||||||
bordered
|
bordered
|
||||||
dataSource={data?.notes}
|
dataSource={data.notes}
|
||||||
renderItem={item => (
|
renderItem={item => (
|
||||||
<List.Item>
|
<List.Item>
|
||||||
{item.critical ? (
|
{item.critical ? (
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export default function JobDetailCardsVehicleComponent({ loading, data }) {
|
|||||||
<CardTemplate
|
<CardTemplate
|
||||||
loading={loading}
|
loading={loading}
|
||||||
title={t("jobs.labels.cards.vehicle")}
|
title={t("jobs.labels.cards.vehicle")}
|
||||||
extraLink={data?.vehicle ? `/manage/vehicles/${data?.vehicle?.id}` : null}
|
extraLink={data.vehicle ? `/manage/vehicles/${data.vehicle.id}` : null}
|
||||||
>
|
>
|
||||||
{data ? (
|
{data ? (
|
||||||
<span>
|
<span>
|
||||||
|
|||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,62 +1,103 @@
|
|||||||
import { Table, Button } from "antd";
|
import { Button, Input, Table } from "antd";
|
||||||
import React, { useContext, useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import JobDetailFormContext from "../../pages/jobs-detail/jobs-detail.page.context";
|
import { Link } from "react-router-dom";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
import EditableCell from "./job-lines-cell.component";
|
import AllocationsAssignmentContainer from "../allocations-assignment/allocations-assignment.container";
|
||||||
|
import AllocationsBulkAssignmentContainer from "../allocations-bulk-assignment/allocations-bulk-assignment.container";
|
||||||
|
import AllocationsEmployeeLabelContainer from "../allocations-employee-label/allocations-employee-label.container";
|
||||||
|
import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.container";
|
||||||
|
|
||||||
export default function JobLinesComponent({ job }) {
|
export default function JobLinesComponent({
|
||||||
//const form = useContext(JobDetailFormContext);
|
loading,
|
||||||
//const { getFieldDecorator } = form;
|
refetch,
|
||||||
|
jobLines,
|
||||||
|
setSearchText,
|
||||||
|
selectedLines,
|
||||||
|
setSelectedLines,
|
||||||
|
partsOrderModalVisible,
|
||||||
|
jobId,
|
||||||
|
setJobLineEditContext
|
||||||
|
}) {
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
sortedInfo: {},
|
sortedInfo: {}
|
||||||
filteredInfo: { text: "" }
|
|
||||||
});
|
});
|
||||||
const [editingKey, setEditingKey] = useState("");
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const setPartsModalVisible = partsOrderModalVisible[1];
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: t("joblines.fields.unq_seq"),
|
title: t("joblines.fields.unq_seq"),
|
||||||
dataIndex: "joblines.unq_seq",
|
dataIndex: "unq_seq",
|
||||||
key: "joblines.unq_seq",
|
key: "unq_seq",
|
||||||
// onFilter: (value, record) => record.ro_number.includes(value),
|
// onFilter: (value, record) => record.ro_number.includes(value),
|
||||||
// filteredValue: state.filteredInfo.text || null,
|
// filteredValue: state.filteredInfo.text || null,
|
||||||
sorter: (a, b) => alphaSort(a, b),
|
sorter: (a, b) => a.unq_seq - b.unq_seq,
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "unq_seq" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "unq_seq" && state.sortedInfo.order,
|
||||||
//ellipsis: true,
|
//ellipsis: true,
|
||||||
editable: true
|
editable: true,
|
||||||
|
width: 75
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("joblines.fields.line_desc"),
|
title: t("joblines.fields.line_desc"),
|
||||||
dataIndex: "line_desc",
|
dataIndex: "line_desc",
|
||||||
key: "joblines.line_desc",
|
key: "line_desc",
|
||||||
sorter: (a, b) => alphaSort(a.line_desc, b.line_desc),
|
sorter: (a, b) => alphaSort(a.line_desc, b.line_desc),
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
editable: true
|
editable: true,
|
||||||
|
width: "20%"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("joblines.fields.oem_partno"),
|
||||||
|
dataIndex: "oem_partno",
|
||||||
|
key: "oem_partno",
|
||||||
|
sorter: (a, b) =>
|
||||||
|
alphaSort(
|
||||||
|
a.oem_partno ? a.oem_partno : a.op_code_desc,
|
||||||
|
b.oem_partno ? b.oem_partno : b.op_code_desc
|
||||||
|
),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "oem_partno" && state.sortedInfo.order,
|
||||||
|
ellipsis: true,
|
||||||
|
editable: true,
|
||||||
|
width: "10%",
|
||||||
|
render: (text, record) => (
|
||||||
|
<span>
|
||||||
|
{record.oem_partno ? record.oem_partno : record.op_code_desc}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("joblines.fields.part_type"),
|
title: t("joblines.fields.part_type"),
|
||||||
dataIndex: "part_type",
|
dataIndex: "part_type",
|
||||||
key: "joblines.part_type",
|
key: "part_type",
|
||||||
sorter: (a, b) => alphaSort(a.part_type, b.part_type),
|
sorter: (a, b) => alphaSort(a.part_type, b.part_type),
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "part_type" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "part_type" && state.sortedInfo.order,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
editable: true
|
editable: true,
|
||||||
|
width: "7%"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("joblines.fields.line_ind"),
|
||||||
|
dataIndex: "line_ind",
|
||||||
|
key: "line_ind",
|
||||||
|
sorter: (a, b) => alphaSort(a.line_ind, b.line_ind),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "line_ind" && state.sortedInfo.order
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("joblines.fields.db_price"),
|
title: t("joblines.fields.db_price"),
|
||||||
dataIndex: "db_price",
|
dataIndex: "db_price",
|
||||||
key: "joblines.db_price",
|
key: "db_price",
|
||||||
sorter: (a, b) => a.db_price - b.db_price,
|
sorter: (a, b) => a.db_price - b.db_price,
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "db_price" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "db_price" && state.sortedInfo.order,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
|
width: "8%",
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<CurrencyFormatter>{record.db_price}</CurrencyFormatter>
|
<CurrencyFormatter>{record.db_price}</CurrencyFormatter>
|
||||||
)
|
)
|
||||||
@@ -64,22 +105,85 @@ export default function JobLinesComponent({ job }) {
|
|||||||
{
|
{
|
||||||
title: t("joblines.fields.act_price"),
|
title: t("joblines.fields.act_price"),
|
||||||
dataIndex: "act_price",
|
dataIndex: "act_price",
|
||||||
key: "joblines.act_price",
|
key: "act_price",
|
||||||
sorter: (a, b) => a.act_price - b.act_price,
|
sorter: (a, b) => a.act_price - b.act_price,
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "act_price" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "act_price" && state.sortedInfo.order,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
|
width: "8%",
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<div>
|
<CurrencyFormatter>{record.act_price}</CurrencyFormatter>
|
||||||
{" "}
|
)
|
||||||
<CurrencyFormatter>{record.act_price}</CurrencyFormatter>{" "}
|
},
|
||||||
|
{
|
||||||
|
title: t("joblines.fields.mod_lb_hrs"),
|
||||||
|
dataIndex: "mod_lb_hrs",
|
||||||
|
key: "mod_lb_hrs",
|
||||||
|
sorter: (a, b) => a.mod_lb_hrs - b.mod_lb_hrs,
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "mod_lb_hrs" && state.sortedInfo.order
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("joblines.fields.status"),
|
||||||
|
dataIndex: "status",
|
||||||
|
key: "status",
|
||||||
|
sorter: (a, b) => alphaSort(a.status, b.status),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "status" && state.sortedInfo.order
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("allocations.fields.employee"),
|
||||||
|
dataIndex: "employee",
|
||||||
|
key: "employee",
|
||||||
|
width: "10%",
|
||||||
|
sorter: (a, b) =>
|
||||||
|
alphaSort(
|
||||||
|
a.allocations[0] &&
|
||||||
|
a.allocations[0].employee.first_name +
|
||||||
|
a.allocations[0].employee.last_name,
|
||||||
|
b.allocations[0] &&
|
||||||
|
b.allocations[0].employee.first_name +
|
||||||
|
b.allocations[0].employee.last_name
|
||||||
|
),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "employee" && state.sortedInfo.order,
|
||||||
|
render: (text, record) => (
|
||||||
|
<span>
|
||||||
|
{record.allocations && record.allocations.length > 0
|
||||||
|
? record.allocations.map(item => (
|
||||||
|
<AllocationsEmployeeLabelContainer
|
||||||
|
key={item.id}
|
||||||
|
refetch={refetch}
|
||||||
|
allocation={item}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
: 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
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setEditingKey(record.id);
|
setJobLineEditContext({
|
||||||
}}>
|
actions: { refetch: refetch },
|
||||||
EDIT
|
context: record
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("general.actions.edit")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
@@ -88,37 +192,91 @@ export default function JobLinesComponent({ job }) {
|
|||||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||||
};
|
};
|
||||||
|
|
||||||
// const handleChange = event => {
|
const formItemLayout = {
|
||||||
// const { value } = event.target;
|
labelCol: {
|
||||||
// setState({ ...state, filterinfo: { text: [value] } });
|
xs: { span: 12 },
|
||||||
// };
|
sm: { span: 5 }
|
||||||
|
},
|
||||||
|
wrapperCol: {
|
||||||
|
xs: { span: 24 },
|
||||||
|
sm: { span: 12 }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table
|
<div>
|
||||||
size='small'
|
<PartsOrderModalContainer
|
||||||
pagination={{ position: "bottom" }}
|
partsOrderModalVisible={partsOrderModalVisible}
|
||||||
columns={columns.map(col => {
|
linesToOrder={selectedLines}
|
||||||
if (!col.editable) {
|
refetch={refetch}
|
||||||
return col;
|
jobId={jobId}
|
||||||
}
|
/>
|
||||||
return {
|
|
||||||
...col,
|
<Table
|
||||||
onCell: record => ({
|
title={() => {
|
||||||
record,
|
return (
|
||||||
inputType: col.dataIndex === "age" ? "number" : "text",
|
<div>
|
||||||
dataIndex: col.dataIndex,
|
<Input.Search
|
||||||
title: col.title,
|
placeholder={t("general.labels.search")}
|
||||||
editing: editingKey === record.id
|
onChange={e => {
|
||||||
})
|
e.preventDefault();
|
||||||
};
|
setSearchText(e.target.value);
|
||||||
})}
|
}}
|
||||||
components={{
|
/>
|
||||||
body: {
|
<Button
|
||||||
cell: EditableCell
|
disabled={selectedLines.length > 0 ? false : true}
|
||||||
}
|
onClick={() => setPartsModalVisible(true)}
|
||||||
}}
|
>
|
||||||
rowKey='id'
|
{t("parts.actions.order")}
|
||||||
dataSource={job.joblines}
|
</Button>
|
||||||
onChange={handleTableChange}
|
<AllocationsBulkAssignmentContainer
|
||||||
/>
|
jobLines={selectedLines}
|
||||||
|
refetch={refetch}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setJobLineEditContext({
|
||||||
|
actions: { refetch: refetch },
|
||||||
|
context: { jobid: jobId }
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("joblines.actions.new")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
{...formItemLayout}
|
||||||
|
loading={loading}
|
||||||
|
size="small"
|
||||||
|
expandedRowRender={record => (
|
||||||
|
<div style={{ margin: 0 }}>
|
||||||
|
<strong>{t("parts_orders.labels.orderhistory")}</strong>
|
||||||
|
{record.parts_order_lines.map(item => (
|
||||||
|
<div key={item.id}>
|
||||||
|
{`${item.parts_order.order_number || ""} from `}
|
||||||
|
<Link to={`/manage/shop/vendors/${item.parts_order.vendor.id}`}>
|
||||||
|
{item.parts_order.vendor.name || ""}
|
||||||
|
</Link>
|
||||||
|
{` on ${item.parts_order.order_date || ""}`}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
pagination={{ position: "top", defaultPageSize: 25 }}
|
||||||
|
rowSelection={{
|
||||||
|
// selectedRowKeys: selectedLines,
|
||||||
|
onSelectAll: (selected, selectedRows, changeRows) => {
|
||||||
|
setSelectedLines(selectedRows);
|
||||||
|
},
|
||||||
|
onSelect: (record, selected, selectedRows, nativeEvent) =>
|
||||||
|
setSelectedLines(selectedRows)
|
||||||
|
}}
|
||||||
|
columns={columns.map(item => ({ ...item }))}
|
||||||
|
rowKey="id"
|
||||||
|
dataSource={jobLines}
|
||||||
|
onChange={handleTableChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,72 @@
|
|||||||
import React from "react";
|
|
||||||
import JobLinesComponent from "./job-lines.component";
|
|
||||||
import { useQuery } from "@apollo/react-hooks";
|
import { useQuery } from "@apollo/react-hooks";
|
||||||
import AlertComponent from "../alert/alert.component";
|
import React, { useState } from "react";
|
||||||
|
|
||||||
import { GET_JOB_LINES_BY_PK } from "../../graphql/jobs-lines.queries";
|
import { GET_JOB_LINES_BY_PK } from "../../graphql/jobs-lines.queries";
|
||||||
|
import AlertComponent from "../alert/alert.component";
|
||||||
|
import JobLinesComponent from "./job-lines.component";
|
||||||
|
|
||||||
export default function JobLinesContainer({ jobId }) {
|
import { connect } from "react-redux";
|
||||||
|
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||||
const { loading, error, data } = useQuery(GET_JOB_LINES_BY_PK, {
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
setJobLineEditContext: context =>
|
||||||
|
dispatch(setModalContext({ context: context, modal: "jobLineEdit" }))
|
||||||
|
});
|
||||||
|
export default connect(
|
||||||
|
null,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(function JobLinesContainer({ jobId, setJobLineEditContext }) {
|
||||||
|
const { loading, error, data, refetch } = useQuery(GET_JOB_LINES_BY_PK, {
|
||||||
variables: { id: jobId },
|
variables: { id: jobId },
|
||||||
fetchPolicy: "network-only"
|
fetchPolicy: "network-only"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [searchText, setSearchText] = useState("");
|
||||||
|
const [selectedLines, setSelectedLines] = useState([]);
|
||||||
|
const partsOrderModalVisible = useState(false);
|
||||||
|
|
||||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<JobLinesComponent loading={loading} joblines={data ? data.joblines : null} />
|
<JobLinesComponent
|
||||||
|
loading={loading}
|
||||||
|
refetch={refetch}
|
||||||
|
jobLines={
|
||||||
|
data && data.joblines
|
||||||
|
? searchText
|
||||||
|
? data.joblines.filter(
|
||||||
|
jl =>
|
||||||
|
(jl.unq_seq || "")
|
||||||
|
.toString()
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(searchText.toLowerCase()) ||
|
||||||
|
(jl.line_desc || "")
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(searchText.toLowerCase()) ||
|
||||||
|
(jl.part_type || "")
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(searchText.toLowerCase()) ||
|
||||||
|
(jl.oem_partno || "")
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(searchText.toLowerCase()) ||
|
||||||
|
(jl.op_code_desc || "")
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(searchText.toLowerCase()) ||
|
||||||
|
(jl.db_price || "")
|
||||||
|
.toString()
|
||||||
|
.includes(searchText.toLowerCase()) ||
|
||||||
|
(jl.act_price || "")
|
||||||
|
.toString()
|
||||||
|
.includes(searchText.toLowerCase())
|
||||||
|
)
|
||||||
|
: data.joblines
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
setSearchText={setSearchText}
|
||||||
|
selectedLines={selectedLines}
|
||||||
|
setSelectedLines={setSelectedLines}
|
||||||
|
partsOrderModalVisible={partsOrderModalVisible}
|
||||||
|
jobId={jobId}
|
||||||
|
setJobLineEditContext={setJobLineEditContext}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import { Modal, Form, Input, InputNumber } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import ResetForm from "../form-items-formatted/reset-form-item.component";
|
||||||
|
|
||||||
|
export default function JobLinesUpsertModalComponent({
|
||||||
|
visible,
|
||||||
|
jobLine,
|
||||||
|
handleOk,
|
||||||
|
handleCancel,
|
||||||
|
handleSubmit,
|
||||||
|
form
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { getFieldDecorator, isFieldsTouched, resetFields } = form;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={
|
||||||
|
jobLine && jobLine.id
|
||||||
|
? t("joblines.labels.edit")
|
||||||
|
: t("joblines.labels.new")
|
||||||
|
}
|
||||||
|
visible={visible}
|
||||||
|
okText={t("general.labels.save")}
|
||||||
|
onOk={handleSubmit}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
>
|
||||||
|
{isFieldsTouched() ? <ResetForm resetFields={resetFields} /> : null}
|
||||||
|
<Form onSubmit={handleSubmit} autoComplete={"off"}>
|
||||||
|
<Form.Item label={t("joblines.fields.line_desc")}>
|
||||||
|
{getFieldDecorator("line_desc", {
|
||||||
|
initialValue: jobLine.line_desc
|
||||||
|
})(<Input name="line_desc" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("joblines.fields.oem_partno")}>
|
||||||
|
{getFieldDecorator("oem_partno", {
|
||||||
|
initialValue: jobLine.oem_partno
|
||||||
|
})(<Input name="oem_partno" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("joblines.fields.part_type")}>
|
||||||
|
{getFieldDecorator("part_type", {
|
||||||
|
initialValue: jobLine.part_type
|
||||||
|
})(<Input name="part_type" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("joblines.fields.mod_lbr_ty")}>
|
||||||
|
{getFieldDecorator("mod_lbr_ty", {
|
||||||
|
initialValue: jobLine.mod_lbr_ty
|
||||||
|
})(<Input name="mod_lbr_ty" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("joblines.fields.op_code_desc")}>
|
||||||
|
{getFieldDecorator("op_code_desc", {
|
||||||
|
initialValue: jobLine.op_code_desc
|
||||||
|
})(<Input name="op_code_desc" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("joblines.fields.mod_lb_hrs")}>
|
||||||
|
{getFieldDecorator("mod_lb_hrs", {
|
||||||
|
initialValue: jobLine.mod_lb_hrs
|
||||||
|
})(<InputNumber name="mod_lb_hrs" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("joblines.fields.act_price")}>
|
||||||
|
{getFieldDecorator("act_price", {
|
||||||
|
initialValue: jobLine.act_price
|
||||||
|
})(<InputNumber name="act_price" />)}
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
import { Form, notification } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
import { useMutation } from "react-apollo";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import {
|
||||||
|
INSERT_NEW_JOB_LINE,
|
||||||
|
UPDATE_JOB_LINE
|
||||||
|
} from "../../graphql/jobs-lines.queries";
|
||||||
|
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||||
|
import { selectJobLineEditModal } from "../../redux/modals/modals.selectors";
|
||||||
|
import JobLinesUpdsertModal from "./job-lines-upsert-modal.component";
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
jobLineEditModal: selectJobLineEditModal
|
||||||
|
});
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
toggleModalVisible: () => dispatch(toggleModalVisible("jobLineEdit"))
|
||||||
|
});
|
||||||
|
|
||||||
|
function JobLinesUpsertModalContainer({
|
||||||
|
jobLineEditModal,
|
||||||
|
toggleModalVisible,
|
||||||
|
form
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [insertJobLine] = useMutation(INSERT_NEW_JOB_LINE);
|
||||||
|
const [updateJobLine] = useMutation(UPDATE_JOB_LINE);
|
||||||
|
|
||||||
|
const handleSubmit = e => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
form.validateFieldsAndScroll((err, values) => {
|
||||||
|
if (err) {
|
||||||
|
notification["error"]({
|
||||||
|
message: t("joblines.errors.validation"),
|
||||||
|
description: err.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!err) {
|
||||||
|
if (!jobLineEditModal.context.id) {
|
||||||
|
insertJobLine({
|
||||||
|
variables: {
|
||||||
|
lineInput: [{ jobid: jobLineEditModal.context.jobid, ...values }]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(r => {
|
||||||
|
if (jobLineEditModal.actions.refetch)
|
||||||
|
jobLineEditModal.actions.refetch();
|
||||||
|
toggleModalVisible();
|
||||||
|
notification["success"]({
|
||||||
|
message: t("joblines.successes.created")
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
notification["error"]({
|
||||||
|
message: t("joblines.errors.creating", {
|
||||||
|
message: error.message
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
updateJobLine({
|
||||||
|
variables: {
|
||||||
|
lineId: jobLineEditModal.context.id,
|
||||||
|
line: values
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(r => {
|
||||||
|
notification["success"]({
|
||||||
|
message: t("joblines.successes.updated")
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
notification["success"]({
|
||||||
|
message: t("joblines.errors.updating", {
|
||||||
|
message: error.message
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if (jobLineEditModal.actions.refetch)
|
||||||
|
jobLineEditModal.actions.refetch();
|
||||||
|
toggleModalVisible();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
toggleModalVisible();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<JobLinesUpdsertModal
|
||||||
|
visible={jobLineEditModal.visible}
|
||||||
|
jobLine={jobLineEditModal.context}
|
||||||
|
handleSubmit={handleSubmit}
|
||||||
|
handleCancel={handleCancel}
|
||||||
|
form={form}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(
|
||||||
|
Form.create({ name: "JobsDetailPageContainer" })(JobLinesUpsertModalContainer)
|
||||||
|
);
|
||||||
@@ -4,6 +4,8 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
import OwnerFindModalContainer from "../owner-find-modal/owner-find-modal.container";
|
import OwnerFindModalContainer from "../owner-find-modal/owner-find-modal.container";
|
||||||
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
|
|
||||||
export default function JobsAvailableComponent({
|
export default function JobsAvailableComponent({
|
||||||
loading,
|
loading,
|
||||||
data,
|
data,
|
||||||
@@ -77,7 +79,10 @@ export default function JobsAvailableComponent({
|
|||||||
key: "clm_amt",
|
key: "clm_amt",
|
||||||
sorter: (a, b) => a.clm_amt - b.clm_amt,
|
sorter: (a, b) => a.clm_amt - b.clm_amt,
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "clm_amt" && state.sortedInfo.order
|
state.sortedInfo.columnKey === "clm_amt" && state.sortedInfo.order,
|
||||||
|
render: (text, record) => (
|
||||||
|
<CurrencyFormatter>{record.clm_amt}</CurrencyFormatter>
|
||||||
|
)
|
||||||
//width: "12%",
|
//width: "12%",
|
||||||
//ellipsis: true
|
//ellipsis: true
|
||||||
},
|
},
|
||||||
@@ -141,7 +146,8 @@ export default function JobsAvailableComponent({
|
|||||||
estData.data.available_jobs_by_pk &&
|
estData.data.available_jobs_by_pk &&
|
||||||
estData.data.available_jobs_by_pk.est_data &&
|
estData.data.available_jobs_by_pk.est_data &&
|
||||||
estData.data.available_jobs_by_pk.est_data.owner &&
|
estData.data.available_jobs_by_pk.est_data.owner &&
|
||||||
estData.data.available_jobs_by_pk.est_data.owner.data
|
estData.data.available_jobs_by_pk.est_data.owner.data &&
|
||||||
|
!estData.data.available_jobs_by_pk.issupplement
|
||||||
? estData.data.available_jobs_by_pk.est_data.owner.data
|
? estData.data.available_jobs_by_pk.est_data.owner.data
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
@@ -164,7 +170,7 @@ export default function JobsAvailableComponent({
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Input.Search
|
<Input.Search
|
||||||
placeholder="Search..."
|
placeholder="Search...//TODO Implement Search"
|
||||||
onSearch={value => {
|
onSearch={value => {
|
||||||
console.log(value);
|
console.log(value);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -29,14 +29,7 @@ export default withRouter(function JobsAvailableContainer({
|
|||||||
|
|
||||||
const onModalOk = () => {
|
const onModalOk = () => {
|
||||||
setModalVisible(false);
|
setModalVisible(false);
|
||||||
console.log("selectedOwner", selectedOwner);
|
|
||||||
setInsertLoading(true);
|
setInsertLoading(true);
|
||||||
console.log(
|
|
||||||
"logitest",
|
|
||||||
estData.data &&
|
|
||||||
estData.data.available_jobs_by_pk &&
|
|
||||||
estData.data.available_jobs_by_pk.est_data
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!(
|
!(
|
||||||
|
|||||||
@@ -3,13 +3,25 @@ import React, { useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||||
|
import JobsFindModalContainer from "../jobs-find-modal/jobs-find-modal.container";
|
||||||
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
|
|
||||||
export default function JobsAvailableSupplementComponent({
|
export default function JobsAvailableSupplementComponent({
|
||||||
loading,
|
loading,
|
||||||
data,
|
data,
|
||||||
refetch,
|
refetch,
|
||||||
deleteJob,
|
deleteJob,
|
||||||
|
updateJob,
|
||||||
|
onModalOk,
|
||||||
|
onModalCancel,
|
||||||
|
modalVisible,
|
||||||
|
setModalVisible,
|
||||||
|
selectedJob,
|
||||||
|
setSelectedJob,
|
||||||
deleteAllNewJobs,
|
deleteAllNewJobs,
|
||||||
estDataLazyLoad
|
loadEstData,
|
||||||
|
estData,
|
||||||
|
importOptionsState
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -81,7 +93,10 @@ export default function JobsAvailableSupplementComponent({
|
|||||||
key: "clm_amt",
|
key: "clm_amt",
|
||||||
sorter: (a, b) => a.clm_amt - b.clm_amt,
|
sorter: (a, b) => a.clm_amt - b.clm_amt,
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "clm_amt" && state.sortedInfo.order
|
state.sortedInfo.columnKey === "clm_amt" && state.sortedInfo.order,
|
||||||
|
render: (text, record) => (
|
||||||
|
<CurrencyFormatter>{record.clm_amt}</CurrencyFormatter>
|
||||||
|
)
|
||||||
//width: "12%",
|
//width: "12%",
|
||||||
//ellipsis: true
|
//ellipsis: true
|
||||||
},
|
},
|
||||||
@@ -127,7 +142,8 @@ export default function JobsAvailableSupplementComponent({
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
alert("Add");
|
loadEstData({ variables: { id: record.id } });
|
||||||
|
setModalVisible(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon type="plus" />
|
<Icon type="plus" />
|
||||||
@@ -140,54 +156,66 @@ export default function JobsAvailableSupplementComponent({
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table
|
<div>
|
||||||
loading={loading}
|
<JobsFindModalContainer
|
||||||
title={() => {
|
loading={estData.loading}
|
||||||
return (
|
error={estData.error}
|
||||||
<div>
|
selectedJob={selectedJob}
|
||||||
<Input.Search
|
setSelectedJob={setSelectedJob}
|
||||||
placeholder="Search..."
|
importOptionsState={importOptionsState}
|
||||||
onSearch={value => {
|
visible={modalVisible}
|
||||||
console.log(value);
|
onOk={onModalOk}
|
||||||
}}
|
onCancel={onModalCancel}
|
||||||
enterButton
|
/>
|
||||||
/>
|
<Table
|
||||||
<Button
|
loading={loading}
|
||||||
onClick={() => {
|
title={() => {
|
||||||
refetch();
|
return (
|
||||||
}}
|
<div>
|
||||||
>
|
<Input.Search
|
||||||
<Icon type="sync" />
|
placeholder="Search..."
|
||||||
</Button>
|
onSearch={value => {
|
||||||
<Button
|
console.log(value);
|
||||||
onClick={() => {
|
}}
|
||||||
deleteAllNewJobs()
|
enterButton
|
||||||
.then(r => {
|
/>
|
||||||
notification["success"]({
|
<Button
|
||||||
message: t("jobs.successes.all_deleted", {
|
onClick={() => {
|
||||||
count: r.data.delete_available_jobs.affected_rows
|
refetch();
|
||||||
})
|
}}
|
||||||
|
>
|
||||||
|
<Icon type="sync" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
deleteAllNewJobs()
|
||||||
|
.then(r => {
|
||||||
|
notification["success"]({
|
||||||
|
message: t("jobs.successes.all_deleted", {
|
||||||
|
count: r.data.delete_available_jobs.affected_rows
|
||||||
|
})
|
||||||
|
});
|
||||||
|
refetch();
|
||||||
|
})
|
||||||
|
.catch(r => {
|
||||||
|
notification["error"]({
|
||||||
|
message: t("jobs.errors.deleted") + " " + r.message
|
||||||
|
});
|
||||||
});
|
});
|
||||||
refetch();
|
}}
|
||||||
})
|
>
|
||||||
.catch(r => {
|
Delete All
|
||||||
notification["error"]({
|
</Button>
|
||||||
message: t("jobs.errors.deleted") + " " + r.message
|
</div>
|
||||||
});
|
);
|
||||||
});
|
}}
|
||||||
}}
|
size="small"
|
||||||
>
|
pagination={{ position: "top" }}
|
||||||
Delete All
|
columns={columns.map(item => ({ ...item }))}
|
||||||
</Button>
|
rowKey="id"
|
||||||
</div>
|
dataSource={data && data.available_jobs}
|
||||||
);
|
onChange={handleTableChange}
|
||||||
}}
|
/>
|
||||||
size="small"
|
</div>
|
||||||
pagination={{ position: "top" }}
|
|
||||||
columns={columns.map(item => ({ ...item }))}
|
|
||||||
rowKey="id"
|
|
||||||
dataSource={data && data.available_jobs}
|
|
||||||
onChange={handleTableChange}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,21 @@
|
|||||||
import React from "react";
|
import { notification } from "antd";
|
||||||
|
import React, { useState } from "react";
|
||||||
import { useMutation, useQuery } from "react-apollo";
|
import { useMutation, useQuery } from "react-apollo";
|
||||||
import { DELETE_ALL_AVAILABLE_SUPPLEMENT_JOBS, QUERY_AVAILABLE_SUPPLEMENT_JOBS } from "../../graphql/available-jobs.queries";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { withRouter } from "react-router-dom";
|
||||||
|
import {
|
||||||
|
DELETE_ALL_AVAILABLE_SUPPLEMENT_JOBS,
|
||||||
|
QUERY_AVAILABLE_SUPPLEMENT_JOBS
|
||||||
|
} from "../../graphql/available-jobs.queries";
|
||||||
|
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import JobsAvailableSupplementComponent from "./jobs-available-supplement.component";
|
import JobsAvailableSupplementComponent from "./jobs-available-supplement.component";
|
||||||
|
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||||
|
|
||||||
export default function JobsAvailableSupplementContainer({
|
export default withRouter(function JobsAvailableSupplementContainer({
|
||||||
deleteJob,
|
deleteJob,
|
||||||
estDataLazyLoad
|
estDataLazyLoad,
|
||||||
|
history
|
||||||
}) {
|
}) {
|
||||||
const { loading, error, data, refetch } = useQuery(
|
const { loading, error, data, refetch } = useQuery(
|
||||||
QUERY_AVAILABLE_SUPPLEMENT_JOBS,
|
QUERY_AVAILABLE_SUPPLEMENT_JOBS,
|
||||||
@@ -14,17 +23,107 @@ export default function JobsAvailableSupplementContainer({
|
|||||||
fetchPolicy: "network-only"
|
fetchPolicy: "network-only"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [deleteAllNewJobs] = useMutation(DELETE_ALL_AVAILABLE_SUPPLEMENT_JOBS);
|
const [deleteAllNewJobs] = useMutation(DELETE_ALL_AVAILABLE_SUPPLEMENT_JOBS);
|
||||||
|
|
||||||
if (error) return <AlertComponent type="error" message={error.message} />;
|
const [modalVisible, setModalVisible] = useState(false);
|
||||||
|
const [selectedJob, setSelectedJob] = useState(null);
|
||||||
|
const [insertLoading, setInsertLoading] = useState(false);
|
||||||
|
const [updateJob] = useMutation(UPDATE_JOB);
|
||||||
|
const [loadEstData, estData] = estDataLazyLoad;
|
||||||
|
const importOptionsState = useState({ overrideHeaders: false });
|
||||||
|
const importOptions = importOptionsState[0];
|
||||||
|
const onModalOk = () => {
|
||||||
|
setModalVisible(false);
|
||||||
|
setInsertLoading(true);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!(
|
||||||
|
estData.data &&
|
||||||
|
estData.data.available_jobs_by_pk &&
|
||||||
|
estData.data.available_jobs_by_pk.est_data
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
//We don't have the right data. Error!
|
||||||
|
setInsertLoading(false);
|
||||||
|
notification["error"]({
|
||||||
|
message: t("jobs.errors.creating", { error: "No job data present." })
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
//create upsert job
|
||||||
|
let supp = estData.data.available_jobs_by_pk.est_data;
|
||||||
|
delete supp.joblines;
|
||||||
|
//TODO How to update the estimate lines.
|
||||||
|
delete supp.owner;
|
||||||
|
delete supp.vehicle;
|
||||||
|
|
||||||
|
if (!importOptions.overrideHeaders) {
|
||||||
|
delete supp["ins_ea"];
|
||||||
|
//TODO Remove all required fields.
|
||||||
|
}
|
||||||
|
|
||||||
|
updateJob({
|
||||||
|
variables: {
|
||||||
|
jobId: selectedJob,
|
||||||
|
job: supp
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(r => {
|
||||||
|
notification["success"]({
|
||||||
|
message: t("jobs.successes.supplemented"),
|
||||||
|
onClick: () => {
|
||||||
|
history.push(
|
||||||
|
`/manage/jobs/${r.data.update_jobs.returning[0].id}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//Job has been inserted. Clean up the available jobs record.
|
||||||
|
deleteJob({
|
||||||
|
variables: { id: estData.data.available_jobs_by_pk.id }
|
||||||
|
}).then(r => {
|
||||||
|
refetch();
|
||||||
|
setInsertLoading(false);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(r => {
|
||||||
|
//error while inserting
|
||||||
|
notification["error"]({
|
||||||
|
message: t("jobs.errors.creating", { error: r.message })
|
||||||
|
});
|
||||||
|
refetch();
|
||||||
|
setInsertLoading(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onModalCancel = () => {
|
||||||
|
setModalVisible(false);
|
||||||
|
setSelectedJob(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (error) return <AlertComponent type='error' message={error.message} />;
|
||||||
return (
|
return (
|
||||||
<JobsAvailableSupplementComponent
|
<LoadingSpinner
|
||||||
loading={loading}
|
loading={insertLoading}
|
||||||
data={data}
|
message={t("jobs.labels.creating_new_job")}>
|
||||||
refetch={refetch}
|
<JobsAvailableSupplementComponent
|
||||||
deleteJob={deleteJob}
|
loading={loading}
|
||||||
deleteAllNewJobs={deleteAllNewJobs}
|
data={data}
|
||||||
estDataLazyLoad={estDataLazyLoad}
|
refetch={refetch}
|
||||||
/>
|
deleteJob={deleteJob}
|
||||||
|
updateJob={updateJob}
|
||||||
|
onModalOk={onModalOk}
|
||||||
|
onModalCancel={onModalCancel}
|
||||||
|
modalVisible={modalVisible}
|
||||||
|
setModalVisible={setModalVisible}
|
||||||
|
selectedJob={selectedJob}
|
||||||
|
setSelectedJob={setSelectedJob}
|
||||||
|
deleteAllNewJobs={deleteAllNewJobs}
|
||||||
|
loadEstData={loadEstData}
|
||||||
|
estData={estData}
|
||||||
|
importOptionsState={importOptionsState}
|
||||||
|
/>
|
||||||
|
</LoadingSpinner>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export default function JobsDetailClaims({ job }) {
|
|||||||
initialValue: job.loss_desc
|
initialValue: job.loss_desc
|
||||||
})(<Input name='loss_desc' />)}
|
})(<Input name='loss_desc' />)}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
TODO: How to handle different taxes and marking them as exempt?
|
TODO How to handle different taxes and marking them as exempt?
|
||||||
{
|
{
|
||||||
// <Form.Item label={t("jobs.fields.exempt")}>
|
// <Form.Item label={t("jobs.fields.exempt")}>
|
||||||
// {getFieldDecorator("exempt", {
|
// {getFieldDecorator("exempt", {
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
import { DatePicker, Form } from "antd";
|
||||||
|
import moment from "moment";
|
||||||
|
import React, { useContext } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import JobDetailFormContext from "../../pages/jobs-detail/jobs-detail.page.context";
|
||||||
|
|
||||||
|
export default function JobsDetailDatesComponent({ job }) {
|
||||||
|
const form = useContext(JobDetailFormContext);
|
||||||
|
const { getFieldDecorator } = form;
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Form.Item label={t("jobs.fields.loss_date")}>
|
||||||
|
{getFieldDecorator("loss_date", {
|
||||||
|
initialValue: job.loss_date ? moment(job.loss_date) : null
|
||||||
|
})(<DatePicker name="loss_date" />)}
|
||||||
|
</Form.Item>
|
||||||
|
DAMAGE {JSON.stringify(job.area_of_damage)}
|
||||||
|
CAA # seems not correct based on field mapping Class seems not correct
|
||||||
|
based on field mapping
|
||||||
|
<Form.Item label={t("jobs.fields.date_estimated")}>
|
||||||
|
{getFieldDecorator("date_estimated", {
|
||||||
|
initialValue: job.date_estimated ? moment(job.date_estimated) : null
|
||||||
|
})(<DatePicker name="date_estimated" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.date_open")}>
|
||||||
|
{getFieldDecorator("date_open", {
|
||||||
|
initialValue: job.date_open ? moment(job.date_open) : null
|
||||||
|
})(<DatePicker name="date_open" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.date_scheduled")}>
|
||||||
|
{getFieldDecorator("date_scheduled", {
|
||||||
|
initialValue: job.date_scheduled ? moment(job.date_scheduled) : null
|
||||||
|
})(<DatePicker name="date_scheduled" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.scheduled_in")}>
|
||||||
|
{getFieldDecorator("scheduled_in", {
|
||||||
|
initialValue: job.scheduled_in ? moment(job.scheduled_in) : null
|
||||||
|
})(<DatePicker name="scheduled_in" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.actual_in")}>
|
||||||
|
{getFieldDecorator("actual_in", {
|
||||||
|
initialValue: job.actual_in ? moment(job.actual_in) : null
|
||||||
|
})(<DatePicker name="actual_in" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.scheduled_completion")}>
|
||||||
|
{getFieldDecorator("scheduled_completion", {
|
||||||
|
initialValue: job.scheduled_completion
|
||||||
|
? moment(job.scheduled_completion)
|
||||||
|
: null
|
||||||
|
})(<DatePicker name="scheduled_completion" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.actual_completion")}>
|
||||||
|
{getFieldDecorator("actual_completion", {
|
||||||
|
initialValue: job.actual_completion
|
||||||
|
? moment(job.actual_completion)
|
||||||
|
: null
|
||||||
|
})(<DatePicker name="actual_completion" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.scheduled_delivery")}>
|
||||||
|
{getFieldDecorator("scheduled_delivery", {
|
||||||
|
initialValue: job.scheduled_delivery
|
||||||
|
? moment(job.scheduled_delivery)
|
||||||
|
: null
|
||||||
|
})(<DatePicker name="scheduled_delivery" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.actual_delivery")}>
|
||||||
|
{getFieldDecorator("actual_delivery", {
|
||||||
|
initialValue: job.actual_delivery ? moment(job.actual_delivery) : null
|
||||||
|
})(<DatePicker name="actual_delivery" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.date_invoiced")}>
|
||||||
|
{getFieldDecorator("date_invoiced", {
|
||||||
|
initialValue: job.date_invoiced ? moment(job.date_invoiced) : null
|
||||||
|
})(<DatePicker name="date_invoiced" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.date_closed")}>
|
||||||
|
{getFieldDecorator("date_closed", {
|
||||||
|
initialValue: job.date_closed ? moment(job.date_closed) : null
|
||||||
|
})(<DatePicker name="date_closed" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.date_exported")}>
|
||||||
|
{getFieldDecorator("date_exported", {
|
||||||
|
initialValue: job.date_exported ? moment(job.date_exported) : null
|
||||||
|
})(<DatePicker name="date_exported" />)}
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Form, Input, InputNumber } from "antd";
|
import { Form, Input, InputNumber, Divider } from "antd";
|
||||||
import React, { useContext } from "react";
|
import React, { useContext } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import JobDetailFormContext from "../../pages/jobs-detail/jobs-detail.page.context";
|
import JobDetailFormContext from "../../pages/jobs-detail/jobs-detail.page.context";
|
||||||
@@ -25,13 +25,13 @@ export default function JobsDetailFinancials({ job }) {
|
|||||||
initialValue: job.depreciation_taxes
|
initialValue: job.depreciation_taxes
|
||||||
})(<InputNumber name="depreciation_taxes" />)}
|
})(<InputNumber name="depreciation_taxes" />)}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
TODO: This is equivalent of GST payable.
|
TODO This is equivalent of GST payable.
|
||||||
<Form.Item label={t("jobs.fields.federal_tax_payable")}>
|
<Form.Item label={t("jobs.fields.federal_tax_payable")}>
|
||||||
{getFieldDecorator("federal_tax_payable", {
|
{getFieldDecorator("federal_tax_payable", {
|
||||||
initialValue: job.federal_tax_payable
|
initialValue: job.federal_tax_payable
|
||||||
})(<InputNumber name="federal_tax_payable" />)}
|
})(<InputNumber name="federal_tax_payable" />)}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
TODO: equivalent of other customer amount
|
TODO equivalent of other customer amount
|
||||||
<Form.Item label={t("jobs.fields.other_amount_payable")}>
|
<Form.Item label={t("jobs.fields.other_amount_payable")}>
|
||||||
{getFieldDecorator("other_amount_payable", {
|
{getFieldDecorator("other_amount_payable", {
|
||||||
initialValue: job.other_amount_payable
|
initialValue: job.other_amount_payable
|
||||||
@@ -52,6 +52,130 @@ export default function JobsDetailFinancials({ job }) {
|
|||||||
initialValue: job.adjustment_bottom_line
|
initialValue: job.adjustment_bottom_line
|
||||||
})(<InputNumber name="adjustment_bottom_line" />)}
|
})(<InputNumber name="adjustment_bottom_line" />)}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Divider />
|
||||||
|
Totals Table
|
||||||
|
<Form.Item label={t("jobs.fields.labor_rate_desc")}>
|
||||||
|
{getFieldDecorator("labor_rate_desc", {
|
||||||
|
initialValue: job.labor_rate_desc
|
||||||
|
})(<Input name="labor_rate_desc" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.rate_lab")}>
|
||||||
|
{getFieldDecorator("rate_lab", {
|
||||||
|
initialValue: job.rate_lab
|
||||||
|
})(<InputNumber name="rate_lab" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.rate_lad")}>
|
||||||
|
{getFieldDecorator("rate_lad", {
|
||||||
|
initialValue: job.rate_lad
|
||||||
|
})(<InputNumber name="rate_lad" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.rate_lae")}>
|
||||||
|
{getFieldDecorator("rate_lae", {
|
||||||
|
initialValue: job.rate_lae
|
||||||
|
})(<InputNumber name="rate_lae" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.rate_lar")}>
|
||||||
|
{getFieldDecorator("rate_lar", {
|
||||||
|
initialValue: job.rate_lar
|
||||||
|
})(<InputNumber name="rate_lar" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.rate_las")}>
|
||||||
|
{getFieldDecorator("rate_las", {
|
||||||
|
initialValue: job.rate_las
|
||||||
|
})(<InputNumber name="rate_las" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.rate_laf")}>
|
||||||
|
{getFieldDecorator("rate_laf", {
|
||||||
|
initialValue: job.rate_laf
|
||||||
|
})(<InputNumber name="rate_laf" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.rate_lam")}>
|
||||||
|
{getFieldDecorator("rate_lam", {
|
||||||
|
initialValue: job.rate_lam
|
||||||
|
})(<InputNumber name="rate_lam" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.rate_lag")}>
|
||||||
|
{getFieldDecorator("rate_lag", {
|
||||||
|
initialValue: job.rate_lag
|
||||||
|
})(<InputNumber name="rate_lag" />)}
|
||||||
|
</Form.Item>
|
||||||
|
Note //TODO Remove ATP rate?
|
||||||
|
<Form.Item label={t("jobs.fields.rate_atp")}>
|
||||||
|
{getFieldDecorator("rate_atp", {
|
||||||
|
initialValue: job.rate_atp
|
||||||
|
})(<InputNumber name="rate_atp" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.rate_lau")}>
|
||||||
|
{getFieldDecorator("rate_lau", {
|
||||||
|
initialValue: job.rate_lau
|
||||||
|
})(<InputNumber name="rate_lau" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.rate_la1")}>
|
||||||
|
{getFieldDecorator("rate_la1", {
|
||||||
|
initialValue: job.rate_la1
|
||||||
|
})(<InputNumber name="rate_la1" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.rate_la2")}>
|
||||||
|
{getFieldDecorator("rate_la2", {
|
||||||
|
initialValue: job.rate_la2
|
||||||
|
})(<InputNumber name="rate_la2" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.rate_la3")}>
|
||||||
|
{getFieldDecorator("rate_la3", {
|
||||||
|
initialValue: job.rate_la3
|
||||||
|
})(<InputNumber name="rate_la3" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.rate_la4")}>
|
||||||
|
{getFieldDecorator("rate_la4", {
|
||||||
|
initialValue: job.rate_la4
|
||||||
|
})(<InputNumber name="rate_la4" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.rate_mapa")}>
|
||||||
|
{getFieldDecorator("rate_mapa", {
|
||||||
|
initialValue: job.rate_mapa
|
||||||
|
})(<InputNumber name="rate_mapa" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.rate_mash")}>
|
||||||
|
{getFieldDecorator("rate_mash", {
|
||||||
|
initialValue: job.rate_mash
|
||||||
|
})(<InputNumber name="rate_mash" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.rate_mahw")}>
|
||||||
|
{getFieldDecorator("rate_mahw", {
|
||||||
|
initialValue: job.rate_mahw
|
||||||
|
})(<InputNumber name="rate_mahw" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.rate_ma2s")}>
|
||||||
|
{getFieldDecorator("rate_ma2s", {
|
||||||
|
initialValue: job.rate_ma2s
|
||||||
|
})(<InputNumber name="rate_ma2s" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.rate_ma3s")}>
|
||||||
|
{getFieldDecorator("rate_ma3s", {
|
||||||
|
initialValue: job.rate_ma3s
|
||||||
|
})(<InputNumber name="rate_ma3s" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.rate_mabl")}>
|
||||||
|
{getFieldDecorator("rate_mabl", {
|
||||||
|
initialValue: job.rate_mabl
|
||||||
|
})(<InputNumber name="rate_mabl" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.rate_macs")}>
|
||||||
|
{getFieldDecorator("rate_macs", {
|
||||||
|
initialValue: job.rate_macs
|
||||||
|
})(<InputNumber name="rate_macs" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.rate_matd")}>
|
||||||
|
{getFieldDecorator("rate_matd", {
|
||||||
|
initialValue: job.rate_matd
|
||||||
|
})(<InputNumber name="rate_matd" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.rate_laa")}>
|
||||||
|
{getFieldDecorator("rate_laa", {
|
||||||
|
initialValue: job.rate_laa
|
||||||
|
})(<InputNumber name="rate_laa" />)}
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
|
Badge,
|
||||||
Button,
|
Button,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
Descriptions,
|
Descriptions,
|
||||||
|
Dropdown,
|
||||||
|
Icon,
|
||||||
|
Menu,
|
||||||
notification,
|
notification,
|
||||||
PageHeader,
|
PageHeader,
|
||||||
Tag
|
Tag
|
||||||
@@ -10,57 +14,71 @@ import {
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import Moment from "react-moment";
|
import Moment from "react-moment";
|
||||||
|
import { connect } from "react-redux";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
import CarImage from "../../assets/car.svg";
|
import CarImage from "../../assets/car.svg";
|
||||||
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
|
import BarcodePopup from "../barcode-popup/barcode-popup.component";
|
||||||
|
|
||||||
export default function JobsDetailHeader({
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
bodyshop: selectBodyshop
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
null
|
||||||
|
)(function JobsDetailHeader({
|
||||||
job,
|
job,
|
||||||
mutationConvertJob,
|
mutationConvertJob,
|
||||||
refetch,
|
refetch,
|
||||||
getFieldDecorator
|
handleSubmit,
|
||||||
|
scheduleModalState,
|
||||||
|
bodyshop,
|
||||||
|
updateJobStatus
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const setscheduleModalVisible = scheduleModalState[1];
|
||||||
|
|
||||||
const tombstoneTitle = (
|
const tombstoneTitle = (
|
||||||
<div>
|
<div>
|
||||||
<Avatar size="large" alt="Vehicle Image" src={CarImage} />
|
<Avatar size='large' alt='Vehicle Image' src={CarImage} />
|
||||||
{`${t("jobs.fields.ro_number")} ${
|
{`${t("jobs.fields.ro_number")} ${
|
||||||
job.ro_number ? job.ro_number : t("general.labels.na")
|
job.ro_number ? job.ro_number : t("general.labels.na")
|
||||||
}`}
|
}`}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const tombstoneSubtitle = (
|
const statusmenu = (
|
||||||
<div>
|
<Menu
|
||||||
<Tag color="red">
|
onClick={e => {
|
||||||
{job.owner ? (
|
updateJobStatus(e.key);
|
||||||
<Link to={`/manage/owners/${job.owner.id}`}>
|
}}>
|
||||||
{`${job.ownr_co_nm || ""}${job.ownr_fn || ""} ${job.ownr_ln || ""}`}
|
{bodyshop.md_ro_statuses.statuses.map(item => (
|
||||||
</Link>
|
<Menu.Item key={item}>{item}</Menu.Item>
|
||||||
) : (
|
))}
|
||||||
t("jobs.errors.noowner")
|
</Menu>
|
||||||
)}
|
|
||||||
</Tag>
|
|
||||||
|
|
||||||
<Tag color="green">
|
|
||||||
{job.vehicle ? (
|
|
||||||
<Link to={`/manage/vehicles/${job.vehicle.id}`}>
|
|
||||||
{job.vehicle.v_model_yr || t("general.labels.na")}{" "}
|
|
||||||
{job.vehicle.v_make_desc || t("general.labels.na")}{" "}
|
|
||||||
{job.vehicle.v_model_desc || t("general.labels.na")} |{" "}
|
|
||||||
{job.vehicle.plate_no || t("general.labels.na")} |{" "}
|
|
||||||
{job.vehicle.v_vin || t("general.labels.na")}
|
|
||||||
</Link>
|
|
||||||
) : null}
|
|
||||||
</Tag>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const menuExtra = [
|
const menuExtra = [
|
||||||
|
<Dropdown overlay={statusmenu} key='changestatus'>
|
||||||
|
<Button>
|
||||||
|
{t("jobs.actions.changestatus")} <Icon type='down' />
|
||||||
|
</Button>
|
||||||
|
</Dropdown>,
|
||||||
|
<Badge key='schedule' count={job.appointments_aggregate.aggregate.count}>
|
||||||
|
<Button
|
||||||
|
//TODO Enabled logic based on status.
|
||||||
|
onClick={() => {
|
||||||
|
setscheduleModalVisible(true);
|
||||||
|
}}>
|
||||||
|
{t("jobs.actions.schedule")}
|
||||||
|
</Button>
|
||||||
|
</Badge>,
|
||||||
<Button
|
<Button
|
||||||
key="convert"
|
key='convert'
|
||||||
type="dashed"
|
type='dashed'
|
||||||
disabled={job.converted}
|
disabled={job.converted}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
mutationConvertJob({
|
mutationConvertJob({
|
||||||
@@ -72,11 +90,14 @@ export default function JobsDetailHeader({
|
|||||||
message: t("jobs.successes.converted")
|
message: t("jobs.successes.converted")
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
{t("jobs.actions.convert")}
|
{t("jobs.actions.convert")}
|
||||||
</Button>,
|
</Button>,
|
||||||
<Button type="primary" key="submit" htmlType="submit">
|
<Button
|
||||||
|
type='primary'
|
||||||
|
key='submit'
|
||||||
|
htmlType='button'
|
||||||
|
onClick={handleSubmit}>
|
||||||
{t("general.labels.save")}
|
{t("general.labels.save")}
|
||||||
</Button>
|
</Button>
|
||||||
];
|
];
|
||||||
@@ -87,19 +108,38 @@ export default function JobsDetailHeader({
|
|||||||
border: "1px solid rgb(235, 237, 240)"
|
border: "1px solid rgb(235, 237, 240)"
|
||||||
}}
|
}}
|
||||||
title={tombstoneTitle}
|
title={tombstoneTitle}
|
||||||
subTitle={tombstoneSubtitle}
|
//subTitle={tombstoneSubtitle}
|
||||||
tags={
|
tags={
|
||||||
<span key="job-status">
|
<span key='job-status'>
|
||||||
{job.job_status ? (
|
{job.status ? <Tag color='blue'>{job.status}</Tag> : null}
|
||||||
<Tag color="blue">{job.job_status.name}</Tag>
|
<Tag color='red'>
|
||||||
) : null}
|
{job.owner ? (
|
||||||
|
<Link to={`/manage/owners/${job.owner.id}`}>
|
||||||
|
{`${job.ownr_co_nm || ""}${job.ownr_fn || ""} ${job.ownr_ln ||
|
||||||
|
""}`}
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
t("jobs.errors.noowner")
|
||||||
|
)}
|
||||||
|
</Tag>
|
||||||
|
<Tag color='green'>
|
||||||
|
{job.vehicle ? (
|
||||||
|
<Link to={`/manage/vehicles/${job.vehicle.id}`}>
|
||||||
|
{job.vehicle.v_model_yr || t("general.labels.na")}{" "}
|
||||||
|
{job.vehicle.v_make_desc || t("general.labels.na")}{" "}
|
||||||
|
{job.vehicle.v_model_desc || t("general.labels.na")} |{" "}
|
||||||
|
{job.vehicle.plate_no || t("general.labels.na")} |{" "}
|
||||||
|
{job.vehicle.v_vin || t("general.labels.na")}
|
||||||
|
</Link>
|
||||||
|
) : null}
|
||||||
|
</Tag>
|
||||||
|
<BarcodePopup value={job.id} />
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
extra={menuExtra}
|
extra={menuExtra}>
|
||||||
>
|
<Descriptions size='small' column={5}>
|
||||||
<Descriptions size="small" column={5}>
|
|
||||||
<Descriptions.Item label={t("jobs.fields.repairtotal")}>
|
<Descriptions.Item label={t("jobs.fields.repairtotal")}>
|
||||||
<CurrencyFormatter>{job.claim_total}</CurrencyFormatter>
|
<CurrencyFormatter>{job.clm_total}</CurrencyFormatter>
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
|
|
||||||
<Descriptions.Item label={t("jobs.fields.customerowing")}>
|
<Descriptions.Item label={t("jobs.fields.customerowing")}>
|
||||||
@@ -112,7 +152,7 @@ export default function JobsDetailHeader({
|
|||||||
|
|
||||||
<Descriptions.Item label={t("jobs.fields.scheduled_completion")}>
|
<Descriptions.Item label={t("jobs.fields.scheduled_completion")}>
|
||||||
{job.scheduled_completion ? (
|
{job.scheduled_completion ? (
|
||||||
<Moment format="MM/DD/YYYY">{job.scheduled_completion}</Moment>
|
<Moment format='MM/DD/YYYY'>{job.scheduled_completion}</Moment>
|
||||||
) : null}
|
) : null}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
|
|
||||||
@@ -122,4 +162,4 @@ export default function JobsDetailHeader({
|
|||||||
</Descriptions>
|
</Descriptions>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ export default function JobsDetailInsurance({ job }) {
|
|||||||
const { getFieldDecorator, getFieldValue } = form;
|
const { getFieldDecorator, getFieldValue } = form;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
console.log("job.loss_date", job.loss_date);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Form.Item label={t("jobs.fields.ins_co_id")}>
|
<Form.Item label={t("jobs.fields.ins_co_id")}>
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import {
|
||||||
|
toggleModalVisible,
|
||||||
|
setModalContext
|
||||||
|
} from "../../redux/modals/modals.actions";
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
//currentUser: selectCurrentUser
|
||||||
|
});
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
toggleModalVisible: () => dispatch(toggleModalVisible("invoiceEnter")),
|
||||||
|
setInvoiceEnterContext: context =>
|
||||||
|
dispatch(setModalContext({ context: context, modal: "invoiceEnter" }))
|
||||||
|
});
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(function JobsDetailPliComponent({
|
||||||
|
toggleModalVisible,
|
||||||
|
setInvoiceEnterContext,
|
||||||
|
job
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
setInvoiceEnterContext({
|
||||||
|
actions: { refetch: null },
|
||||||
|
context: {
|
||||||
|
job
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Enter Invoice
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import React from "react";
|
||||||
|
import JobsDetailPliComponent from "./jobs-detail-pli.component";
|
||||||
|
|
||||||
|
export default function JobsDetailPliContainer({ job }) {
|
||||||
|
console.log("job", job);
|
||||||
|
return <JobsDetailPliComponent job={job} />;
|
||||||
|
}
|
||||||
@@ -1,13 +1,22 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useQuery } from "react-apollo";
|
import { useQuery } from "react-apollo";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
import { QUERY_SHOP_ID } from "../../graphql/bodyshop.queries";
|
import { QUERY_SHOP_ID } from "../../graphql/bodyshop.queries";
|
||||||
import { GET_DOCUMENTS_BY_JOB } from "../../graphql/documents.queries";
|
import { GET_DOCUMENTS_BY_JOB } from "../../graphql/documents.queries";
|
||||||
|
import { selectCurrentUser } from "../../redux/user/user.selectors";
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||||
import JobDocuments from "./jobs-documents.component";
|
import JobDocuments from "./jobs-documents.component";
|
||||||
import { GET_CURRENT_USER } from "../../graphql/local.queries";
|
|
||||||
|
|
||||||
export default function JobsDocumentsContainer({ jobId }) {
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
currentUser: selectCurrentUser
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
null
|
||||||
|
)(function JobsDocumentsContainer({ jobId, currentUser }) {
|
||||||
const { loading, error, data } = useQuery(GET_DOCUMENTS_BY_JOB, {
|
const { loading, error, data } = useQuery(GET_DOCUMENTS_BY_JOB, {
|
||||||
variables: { jobId: jobId },
|
variables: { jobId: jobId },
|
||||||
fetchPolicy: "network-only"
|
fetchPolicy: "network-only"
|
||||||
@@ -17,14 +26,12 @@ export default function JobsDocumentsContainer({ jobId }) {
|
|||||||
fetchPolicy: "network-only"
|
fetchPolicy: "network-only"
|
||||||
});
|
});
|
||||||
|
|
||||||
const user = useQuery(GET_CURRENT_USER);
|
if (loading || shopData.loading) return <LoadingSpinner />;
|
||||||
|
if (error || shopData.error)
|
||||||
if (loading || shopData.loading || user.loading) return <LoadingSpinner />;
|
|
||||||
if (error || shopData.error || user.error)
|
|
||||||
return (
|
return (
|
||||||
<AlertComponent
|
<AlertComponent
|
||||||
type='error'
|
type="error"
|
||||||
message={error.message || shopData.error.message || user.error.message}
|
message={error.message || shopData.error.message}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -32,12 +39,10 @@ export default function JobsDocumentsContainer({ jobId }) {
|
|||||||
<JobDocuments
|
<JobDocuments
|
||||||
data={data.documents}
|
data={data.documents}
|
||||||
jobId={jobId}
|
jobId={jobId}
|
||||||
currentUser={user.data.currentUser}
|
currentUser={currentUser}
|
||||||
shopId={
|
shopId={
|
||||||
shopData.data?.bodyshops[0]?.id
|
shopData.data.bodyshops[0].id ? shopData.data.bodyshops[0].id : "error"
|
||||||
? shopData.data?.bodyshops[0]?.id
|
|
||||||
: "error"
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|||||||
@@ -0,0 +1,172 @@
|
|||||||
|
import { Checkbox, Divider, Table } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import PhoneFormatter from "../../utils/PhoneFormatter";
|
||||||
|
|
||||||
|
export default function JobsFindModalComponent({
|
||||||
|
selectedJob,
|
||||||
|
setSelectedJob,
|
||||||
|
jobsList,
|
||||||
|
jobsListLoading,
|
||||||
|
importOptionsState
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const [importOptions, setImportOptions] = importOptionsState;
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: t("jobs.fields.ro_number"),
|
||||||
|
dataIndex: "ro_number",
|
||||||
|
key: "ro_number",
|
||||||
|
width: "8%",
|
||||||
|
render: (text, record) => (
|
||||||
|
<span>
|
||||||
|
<Link to={"/manage/jobs/" + record.id}>
|
||||||
|
{record.ro_number ? record.ro_number : "EST-" + record.est_number}
|
||||||
|
</Link>
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("jobs.fields.owner"),
|
||||||
|
dataIndex: "owner",
|
||||||
|
key: "owner",
|
||||||
|
ellipsis: true,
|
||||||
|
|
||||||
|
width: "25%",
|
||||||
|
|
||||||
|
render: (text, record) => {
|
||||||
|
return record.owner ? (
|
||||||
|
<Link to={"/manage/owners/" + record.owner.id}>
|
||||||
|
{record.ownr_fn} {record.ownr_ln}
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
// t("jobs.errors.noowner")
|
||||||
|
<span>{`${record.ownr_fn} ${record.ownr_ln}`}</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("jobs.fields.ownr_ph1"),
|
||||||
|
dataIndex: "ownr_ph1",
|
||||||
|
key: "ownr_ph1",
|
||||||
|
width: "12%",
|
||||||
|
ellipsis: true,
|
||||||
|
render: (text, record) => {
|
||||||
|
return record.ownr_ph1 ? (
|
||||||
|
<PhoneFormatter>{record.ownr_ph1}</PhoneFormatter>
|
||||||
|
) : (
|
||||||
|
t("general.labels.unknown")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("jobs.fields.status"),
|
||||||
|
dataIndex: "status",
|
||||||
|
key: "status",
|
||||||
|
width: "10%",
|
||||||
|
ellipsis: true,
|
||||||
|
render: (text, record) => {
|
||||||
|
return record.status || t("general.labels.na");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: t("jobs.fields.vehicle"),
|
||||||
|
dataIndex: "vehicle",
|
||||||
|
key: "vehicle",
|
||||||
|
width: "15%",
|
||||||
|
ellipsis: true,
|
||||||
|
render: (text, record) => {
|
||||||
|
return record.vehicle ? (
|
||||||
|
<Link to={"/manage/vehicles/" + record.vehicle.id}>
|
||||||
|
{record.vehicle.v_model_yr} {record.vehicle.v_make_desc}{" "}
|
||||||
|
{record.vehicle.v_model_desc}
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
t("jobs.errors.novehicle")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("vehicles.fields.plate_no"),
|
||||||
|
dataIndex: "plate_no",
|
||||||
|
key: "plate_no",
|
||||||
|
width: "8%",
|
||||||
|
ellipsis: true,
|
||||||
|
render: (text, record) => {
|
||||||
|
return record.vehicle.plate_no ? (
|
||||||
|
<span>{record.vehicle.plate_no}</span>
|
||||||
|
) : (
|
||||||
|
t("general.labels.unknown")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("jobs.fields.clm_no"),
|
||||||
|
dataIndex: "clm_no",
|
||||||
|
key: "clm_no",
|
||||||
|
width: "12%",
|
||||||
|
ellipsis: true,
|
||||||
|
render: (text, record) => {
|
||||||
|
return record.clm_no ? (
|
||||||
|
<span>{record.clm_no}</span>
|
||||||
|
) : (
|
||||||
|
t("general.labels.unknown")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleOnRowClick = record => {
|
||||||
|
if (record) {
|
||||||
|
if (record.id) {
|
||||||
|
setSelectedJob(record.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setSelectedJob(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Table
|
||||||
|
title={() => t("jobs.labels.existing_jobs")}
|
||||||
|
size="small"
|
||||||
|
pagination={{ position: "bottom" }}
|
||||||
|
columns={columns.map(item => ({ ...item }))}
|
||||||
|
rowKey="id"
|
||||||
|
loading={jobsListLoading}
|
||||||
|
dataSource={jobsList}
|
||||||
|
rowSelection={{
|
||||||
|
onSelect: props => {
|
||||||
|
setSelectedJob(props.id);
|
||||||
|
},
|
||||||
|
type: "radio",
|
||||||
|
selectedRowKeys: [selectedJob]
|
||||||
|
}}
|
||||||
|
onRow={(record, rowIndex) => {
|
||||||
|
return {
|
||||||
|
onClick: event => {
|
||||||
|
handleOnRowClick(record);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Divider />
|
||||||
|
<Checkbox
|
||||||
|
defaultChecked={importOptions.overrideHeader}
|
||||||
|
onChange={e =>
|
||||||
|
setImportOptions({
|
||||||
|
...importOptions,
|
||||||
|
overrideHeader: e.target.checked
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t("jobs.labels.override_header")}
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { Modal } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
import { useQuery } from "react-apollo";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
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 connect(
|
||||||
|
mapStateToProps,
|
||||||
|
null
|
||||||
|
)(function JobsFindModalContainer({
|
||||||
|
bodyshop,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
selectedJob,
|
||||||
|
setSelectedJob,
|
||||||
|
importOptionsState,
|
||||||
|
...modalProps
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const jobsList = useQuery(QUERY_ALL_ACTIVE_JOBS, {
|
||||||
|
fetchPolicy: "network-only",
|
||||||
|
variables: {
|
||||||
|
statuses: bodyshop.md_ro_statuses.open_statuses || ["Open"]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={t("jobs.labels.existing_jobs")}
|
||||||
|
width={"80%"}
|
||||||
|
okButtonProps={{ disabled: selectedJob ? false : true }}
|
||||||
|
{...modalProps}>
|
||||||
|
{loading ? <LoadingSpinner /> : null}
|
||||||
|
{error ? <AlertComponent message={error.message} type='error' /> : null}
|
||||||
|
{true ? (
|
||||||
|
<JobsFindModalComponent
|
||||||
|
selectedJob={selectedJob}
|
||||||
|
setSelectedJob={setSelectedJob}
|
||||||
|
importOptionsState={importOptionsState}
|
||||||
|
jobsListLoading={jobsList.loading}
|
||||||
|
jobsList={
|
||||||
|
jobsList.data && jobsList.data.jobs ? jobsList.data.jobs : null
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -1,12 +1,15 @@
|
|||||||
import { Input, Table, Icon } from "antd";
|
import { Button, Icon, Input, Table } from "antd";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link, withRouter } from "react-router-dom";
|
||||||
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
import PhoneFormatter from "../../utils/PhoneFormatter";
|
import PhoneFormatter from "../../utils/PhoneFormatter";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
import { withRouter } from "react-router-dom";
|
import StartChatButton from "../chat-open-button/chat-open-button.component";
|
||||||
|
|
||||||
export default withRouter(function JobsList({
|
export default withRouter(function JobsList({
|
||||||
|
searchTextState,
|
||||||
|
refetch,
|
||||||
loading,
|
loading,
|
||||||
jobs,
|
jobs,
|
||||||
selectedJob,
|
selectedJob,
|
||||||
@@ -20,6 +23,7 @@ export default withRouter(function JobsList({
|
|||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const setSearchText = searchTextState[1];
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: t("jobs.fields.ro_number"),
|
title: t("jobs.fields.ro_number"),
|
||||||
@@ -28,7 +32,11 @@ export default withRouter(function JobsList({
|
|||||||
width: "8%",
|
width: "8%",
|
||||||
// onFilter: (value, record) => record.ro_number.includes(value),
|
// onFilter: (value, record) => record.ro_number.includes(value),
|
||||||
// filteredValue: state.filteredInfo.text || null,
|
// filteredValue: state.filteredInfo.text || null,
|
||||||
sorter: (a, b) => alphaSort(a, b),
|
sorter: (a, b) =>
|
||||||
|
alphaSort(
|
||||||
|
a.ro_number ? a.ro_number : "EST-" + a.est_number,
|
||||||
|
b.ro_number ? b.ro_number : "EST-" + b.est_number
|
||||||
|
),
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
|
||||||
|
|
||||||
@@ -56,14 +64,12 @@ export default withRouter(function JobsList({
|
|||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
// t("jobs.errors.noowner")
|
// t("jobs.errors.noowner")
|
||||||
<span>
|
<span>{`${record.ownr_fn} ${record.ownr_ln}`}</span>
|
||||||
{record.ownr_fn} {record.ownr_ln}
|
|
||||||
</span>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("jobs.fields.phone1"),
|
title: t("jobs.fields.ownr_ph1"),
|
||||||
dataIndex: "ownr_ph1",
|
dataIndex: "ownr_ph1",
|
||||||
key: "ownr_ph1",
|
key: "ownr_ph1",
|
||||||
width: "12%",
|
width: "12%",
|
||||||
@@ -72,13 +78,7 @@ export default withRouter(function JobsList({
|
|||||||
return record.ownr_ph1 ? (
|
return record.ownr_ph1 ? (
|
||||||
<span>
|
<span>
|
||||||
<PhoneFormatter>{record.ownr_ph1}</PhoneFormatter>
|
<PhoneFormatter>{record.ownr_ph1}</PhoneFormatter>
|
||||||
<Icon
|
<StartChatButton phone={record.ownr_ph1} />
|
||||||
style={{ margin: 4 }}
|
|
||||||
type='message'
|
|
||||||
onClick={() => {
|
|
||||||
alert("SMSing will happen here.");
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
t("general.labels.unknown")
|
t("general.labels.unknown")
|
||||||
@@ -91,11 +91,11 @@ export default withRouter(function JobsList({
|
|||||||
key: "status",
|
key: "status",
|
||||||
width: "10%",
|
width: "10%",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
sorter: (a, b) => alphaSort(a, b),
|
sorter: (a, b) => alphaSort(a.status, b.status),
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
return record.job_status?.name || t("general.labels.na");
|
return record.status || t("general.labels.na");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -107,7 +107,7 @@ export default withRouter(function JobsList({
|
|||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
return record.vehicle ? (
|
return record.vehicle ? (
|
||||||
<Link to={"manage/vehicles/" + record.vehicle.id}>
|
<Link to={"/manage/vehicles/" + record.vehicle.id}>
|
||||||
{record.vehicle.v_model_yr} {record.vehicle.v_make_desc}{" "}
|
{record.vehicle.v_model_yr} {record.vehicle.v_make_desc}{" "}
|
||||||
{record.vehicle.v_model_desc}
|
{record.vehicle.v_model_desc}
|
||||||
</Link>
|
</Link>
|
||||||
@@ -122,11 +122,11 @@ export default withRouter(function JobsList({
|
|||||||
key: "plate_no",
|
key: "plate_no",
|
||||||
width: "8%",
|
width: "8%",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
sorter: (a, b) => alphaSort(a, b),
|
sorter: (a, b) => alphaSort(a.vehicle.plate_no, b.vehicle.plate_no),
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "plate_no" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "plate_no" && state.sortedInfo.order,
|
||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
return record.vehicle?.plate_no ? (
|
return record.vehicle.plate_no ? (
|
||||||
<span>{record.vehicle.plate_no}</span>
|
<span>{record.vehicle.plate_no}</span>
|
||||||
) : (
|
) : (
|
||||||
t("general.labels.unknown")
|
t("general.labels.unknown")
|
||||||
@@ -139,7 +139,7 @@ export default withRouter(function JobsList({
|
|||||||
key: "clm_no",
|
key: "clm_no",
|
||||||
width: "12%",
|
width: "12%",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
sorter: (a, b) => alphaSort(a, b),
|
sorter: (a, b) => alphaSort(a.clm_no, b.clm_no),
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order,
|
||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
@@ -154,15 +154,13 @@ export default withRouter(function JobsList({
|
|||||||
title: t("jobs.fields.clm_total"),
|
title: t("jobs.fields.clm_total"),
|
||||||
dataIndex: "clm_total",
|
dataIndex: "clm_total",
|
||||||
key: "clm_total",
|
key: "clm_total",
|
||||||
width: "8%",
|
width: "10%",
|
||||||
// sorter: (a, b) => {
|
sorter: (a, b) => a.clm_total - b.clm_total,
|
||||||
// return a > b;
|
sortOrder:
|
||||||
// },
|
state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order,
|
||||||
// sortOrder:
|
|
||||||
// state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order,
|
|
||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
return record.clm_total ? (
|
return record.clm_total ? (
|
||||||
<span>{record.clm_total}</span>
|
<CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
|
||||||
) : (
|
) : (
|
||||||
t("general.labels.unknown")
|
t("general.labels.unknown")
|
||||||
);
|
);
|
||||||
@@ -196,7 +194,6 @@ export default withRouter(function JobsList({
|
|||||||
if (record) {
|
if (record) {
|
||||||
if (record.id) {
|
if (record.id) {
|
||||||
setSelectedJob(record.id);
|
setSelectedJob(record.id);
|
||||||
history.push(`#${record.id}`);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -209,19 +206,24 @@ export default withRouter(function JobsList({
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
title={() => {
|
title={() => {
|
||||||
return (
|
return (
|
||||||
<Input.Search
|
<div style={{ display: "flex" }}>
|
||||||
placeholder='Search...'
|
<Button onClick={() => refetch()}>
|
||||||
onSearch={value => {
|
<Icon type="sync" />
|
||||||
console.log(value);
|
</Button>
|
||||||
}}
|
<Input.Search
|
||||||
enterButton
|
placeholder="Search..."
|
||||||
/>
|
onChange={e => {
|
||||||
|
setSearchText(e.target.value);
|
||||||
|
}}
|
||||||
|
enterButton
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
size='small'
|
size="small"
|
||||||
pagination={{ position: "top" }}
|
pagination={{ position: "top" }}
|
||||||
columns={columns.map(item => ({ ...item }))}
|
columns={columns.map(item => ({ ...item }))}
|
||||||
rowKey='id'
|
rowKey="id"
|
||||||
dataSource={jobs}
|
dataSource={jobs}
|
||||||
rowSelection={{ selectedRowKeys: [selectedJob] }}
|
rowSelection={{ selectedRowKeys: [selectedJob] }}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ import "./loading-skeleton.styles.scss";
|
|||||||
import { Skeleton } from "antd";
|
import { Skeleton } from "antd";
|
||||||
|
|
||||||
export default function LoadingSkeleton(props) {
|
export default function LoadingSkeleton(props) {
|
||||||
return <Skeleton {...props} className='loading-skeleton' active />;
|
return <Skeleton {...props} className="loading-skeleton" active />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,11 @@ export default function LoadingSpinner({ loading = true, message, ...props }) {
|
|||||||
spinning={loading}
|
spinning={loading}
|
||||||
className="loading-spinner"
|
className="loading-spinner"
|
||||||
size="large"
|
size="large"
|
||||||
//delay="500"
|
style={{
|
||||||
|
position: "relative",
|
||||||
|
alignContent: "center"
|
||||||
|
}}
|
||||||
|
delay={200}
|
||||||
tip={message ? message : null}
|
tip={message ? message : null}
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
|
|||||||
@@ -1,3 +1,2 @@
|
|||||||
.loading-spinner {
|
.loading-spinner {
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,27 @@
|
|||||||
import React from "react";
|
|
||||||
import { useQuery } from "react-apollo";
|
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import { GET_CURRENT_USER } from "../../graphql/local.queries";
|
|
||||||
import { Icon } from "antd";
|
import { Icon } from "antd";
|
||||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
import React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { selectCurrentUser } from "../../redux/user/user.selectors";
|
||||||
|
|
||||||
export default function ManageSignInButton() {
|
const mapStateToProps = createStructuredSelector({
|
||||||
const {
|
currentUser: selectCurrentUser
|
||||||
loading,
|
});
|
||||||
error,
|
|
||||||
data: { currentUser }
|
|
||||||
} = useQuery(GET_CURRENT_USER);
|
|
||||||
|
|
||||||
if (loading) return <LoadingSpinner />;
|
export default connect(
|
||||||
if (error) return error.message;
|
mapStateToProps,
|
||||||
|
null
|
||||||
return currentUser ? (
|
)(function ManageSignInButton({ currentUser }) {
|
||||||
<div>
|
return currentUser.authorized ? (
|
||||||
{" "}
|
<Link to="/manage">
|
||||||
<Link to="/manage">
|
<Icon type="build" />
|
||||||
<Icon type="build" />
|
Manage
|
||||||
Manage
|
</Link>
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
) : (
|
) : (
|
||||||
<div>
|
<Link to="/signin">
|
||||||
<Link to="/signin">
|
<Icon type="login" />
|
||||||
<Icon type="login" />
|
Sign In
|
||||||
Sign In
|
</Link>
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export default function NoteUpsertModalComponent({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={noteState?.id ? t("notes.actions.edit") : t("notes.actions.new")}
|
title={noteState.id ? t("notes.actions.edit") : t("notes.actions.new")}
|
||||||
visible={visible}
|
visible={visible}
|
||||||
okText={t("general.labels.save")}
|
okText={t("general.labels.save")}
|
||||||
onOk={() => {
|
onOk={() => {
|
||||||
@@ -22,7 +22,8 @@ export default function NoteUpsertModalComponent({
|
|||||||
}}
|
}}
|
||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
changeVisibility(false);
|
changeVisibility(false);
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
<div>
|
<div>
|
||||||
{t("notes.fields.critical")}
|
{t("notes.fields.critical")}
|
||||||
<Switch
|
<Switch
|
||||||
|
|||||||
@@ -5,12 +5,20 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { INSERT_NEW_NOTE, UPDATE_NOTE } from "../../graphql/notes.queries";
|
import { INSERT_NEW_NOTE, UPDATE_NOTE } from "../../graphql/notes.queries";
|
||||||
import NoteUpsertModalComponent from "./note-upsert-modal.component";
|
import NoteUpsertModalComponent from "./note-upsert-modal.component";
|
||||||
|
|
||||||
export default function NoteUpsertModalContainer({
|
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { selectCurrentUser } from "../../redux/user/user.selectors";
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
currentUser: selectCurrentUser
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect (mapStateToProps,null)( function NoteUpsertModalContainer({
|
||||||
jobId,
|
jobId,
|
||||||
visible,
|
visible,
|
||||||
changeVisibility,
|
changeVisibility,
|
||||||
refetch,
|
refetch,
|
||||||
existingNote
|
existingNote,currentUser
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [insertNote] = useMutation(INSERT_NEW_NOTE);
|
const [insertNote] = useMutation(INSERT_NEW_NOTE);
|
||||||
@@ -33,7 +41,7 @@ export default function NoteUpsertModalContainer({
|
|||||||
insertNote({
|
insertNote({
|
||||||
variables: {
|
variables: {
|
||||||
noteInput: [
|
noteInput: [
|
||||||
{ ...noteState, jobid: jobId, created_by: "patrick@bodyshop.app" } //TODO: Fix the created by.
|
{ ...noteState, jobid: jobId, created_by: currentUser.email }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}).then(r => {
|
}).then(r => {
|
||||||
@@ -73,3 +81,4 @@ export default function NoteUpsertModalContainer({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
import { Button, Col, Form, Input, Row, Switch } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import FormItemEmail from "../form-items-formatted/email-form-item.component";
|
||||||
|
import FormItemPhone from "../form-items-formatted/phone-form-item.component";
|
||||||
|
import ResetForm from "../form-items-formatted/reset-form-item.component";
|
||||||
|
|
||||||
|
export default function OwnerDetailFormComponent({ form, owner }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const {
|
||||||
|
isFieldsTouched,
|
||||||
|
resetFields,
|
||||||
|
getFieldDecorator,
|
||||||
|
getFieldValue
|
||||||
|
} = form;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{isFieldsTouched() ? <ResetForm resetFields={resetFields} /> : null}
|
||||||
|
<Button type="primary" key="submit" htmlType="submit">
|
||||||
|
{t("general.labels.save")}
|
||||||
|
</Button>
|
||||||
|
<Row>
|
||||||
|
<Col span={8}>
|
||||||
|
<Form.Item label={t("owners.fields.ownr_ln")}>
|
||||||
|
{getFieldDecorator("ownr_ln", {
|
||||||
|
initialValue: owner.ownr_ln
|
||||||
|
})(<Input name="ownr_ln" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("owners.fields.ownr_fn")}>
|
||||||
|
{getFieldDecorator("ownr_fn", {
|
||||||
|
initialValue: owner.ownr_fn
|
||||||
|
})(<Input name="ownr_fn" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("owners.fields.allow_text_message")}>
|
||||||
|
{getFieldDecorator("allow_text_message", {
|
||||||
|
initialValue: owner.allow_text_message,
|
||||||
|
valuePropName: "checked"
|
||||||
|
})(<Switch name="allow_text_message" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("owners.fields.ownr_addr1")}>
|
||||||
|
{getFieldDecorator("ownr_addr1", {
|
||||||
|
initialValue: owner.ownr_addr1
|
||||||
|
})(<Input name="ownr_addr1" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("owners.fields.ownr_addr2")}>
|
||||||
|
{getFieldDecorator("ownr_addr2", {
|
||||||
|
initialValue: owner.ownr_addr2
|
||||||
|
})(<Input name="ownr_addr2" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("owners.fields.ownr_city")}>
|
||||||
|
{getFieldDecorator("ownr_city", {
|
||||||
|
initialValue: owner.ownr_city
|
||||||
|
})(<Input name="ownr_city" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("owners.fields.ownr_ctry")}>
|
||||||
|
{getFieldDecorator("ownr_ctry", {
|
||||||
|
initialValue: owner.ownr_ctry
|
||||||
|
})(<Input name="ownr_ctry" />)}
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={8}>
|
||||||
|
{" "}
|
||||||
|
<Form.Item label={t("owners.fields.ownr_ea")}>
|
||||||
|
{getFieldDecorator("ownr_ea", {
|
||||||
|
initialValue: owner.ownr_ea,
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
type: "email",
|
||||||
|
message: "This is not a valid email address."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})(
|
||||||
|
<FormItemEmail name="ownr_ea" email={getFieldValue("ownr_ea")} />
|
||||||
|
)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("owners.fields.ownr_ph1")}>
|
||||||
|
{getFieldDecorator("ownr_ph1", {
|
||||||
|
initialValue: owner.ownr_ph1
|
||||||
|
})(<FormItemPhone customInput={Input} name="ownr_ph1" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("owners.fields.ownr_st")}>
|
||||||
|
{getFieldDecorator("ownr_st", {
|
||||||
|
initialValue: owner.ownr_st
|
||||||
|
})(<Input name="ownr_st" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("owners.fields.ownr_zip")}>
|
||||||
|
{getFieldDecorator("ownr_zip", {
|
||||||
|
initialValue: owner.ownr_zip
|
||||||
|
})(<Input name="ownr_zip" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("owners.fields.preferred_contact")}>
|
||||||
|
{getFieldDecorator("preferred_contact", {
|
||||||
|
initialValue: owner.preferred_contact
|
||||||
|
})(<Input name="preferred_contact" />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("owners.fields.ownr_title")}>
|
||||||
|
{getFieldDecorator("ownr_title", {
|
||||||
|
initialValue: owner.ownr_title
|
||||||
|
})(<Input name="ownr_title" />)}
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { Form, notification } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
import { useMutation } from "react-apollo";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { UPDATE_OWNER } from "../../graphql/owners.queries";
|
||||||
|
import OwnerDetailFormComponent from "./owner-detail-form.component";
|
||||||
|
|
||||||
|
function OwnerDetailFormContainer({ form, owner, refetch }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const [updateOwner] = useMutation(UPDATE_OWNER);
|
||||||
|
|
||||||
|
const handleSubmit = e => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
form.validateFieldsAndScroll((err, values) => {
|
||||||
|
if (err) {
|
||||||
|
notification["error"]({
|
||||||
|
message: t("owners.errors.validationtitle"),
|
||||||
|
description: t("owners.errors.validation")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!err) {
|
||||||
|
updateOwner({
|
||||||
|
variables: { ownerId: owner.id, owner: values }
|
||||||
|
}).then(r => {
|
||||||
|
notification["success"]({
|
||||||
|
message: t("owners.successes.save")
|
||||||
|
});
|
||||||
|
//TODO Better way to reset the field decorators?
|
||||||
|
if (refetch) refetch().then();
|
||||||
|
form.resetFields();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form onSubmit={handleSubmit} autoComplete="off">
|
||||||
|
<OwnerDetailFormComponent form={form} owner={owner} />
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default Form.create({ name: "OwnerDetailFormContainer" })(
|
||||||
|
OwnerDetailFormContainer
|
||||||
|
);
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Table } from "antd";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
|
export default function OwnerDetailJobsComponent({ owner }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: t("jobs.fields.ro_number"),
|
||||||
|
dataIndex: "ro_number",
|
||||||
|
key: "ro_number",
|
||||||
|
ellipsis: true,
|
||||||
|
render: (text, record) => (
|
||||||
|
<Link to={`/manage/jobs/${record.id}`}>
|
||||||
|
{record.ro_number ? record.ro_number : `EST ${record.est_number}`}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("jobs.fields.vehicle"),
|
||||||
|
dataIndex: "owner",
|
||||||
|
key: "owner",
|
||||||
|
render: (text, record) => (
|
||||||
|
<Link to={`/manage/vehicles/${record.vehicle.id}`}>
|
||||||
|
{`${record.vehicle.v_model_yr} ${record.vehicle.v_make_desc} ${record.vehicle.v_model_desc}`}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("jobs.fields.clm_no"),
|
||||||
|
dataIndex: "clm_no",
|
||||||
|
key: "clm_no"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("jobs.fields.status"),
|
||||||
|
dataIndex: "status",
|
||||||
|
key: "status"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: t("jobs.fields.clm_total"),
|
||||||
|
dataIndex: "clm_total",
|
||||||
|
key: "clm_total",
|
||||||
|
render: (text, record) => (
|
||||||
|
<CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table
|
||||||
|
pagination={{ position: "bottom" }}
|
||||||
|
columns={columns.map(item => ({ ...item }))}
|
||||||
|
rowKey="id"
|
||||||
|
dataSource={owner.jobs}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -118,7 +118,8 @@ export default function OwnerFindModalComponent({
|
|||||||
checked={selectedOwner ? false : true}
|
checked={selectedOwner ? false : true}
|
||||||
onClick={() => setSelectedOwner(null)}
|
onClick={() => setSelectedOwner(null)}
|
||||||
>
|
>
|
||||||
Create a new Owner record for this job.
|
|
||||||
|
{t("owners.labels.create_new")}
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -20,9 +20,7 @@ export default function OwnerFindModalContainer({
|
|||||||
|
|
||||||
const ownersList = useQuery(QUERY_SEARCH_OWNER_BY_IDX, {
|
const ownersList = useQuery(QUERY_SEARCH_OWNER_BY_IDX, {
|
||||||
variables: {
|
variables: {
|
||||||
search: owner
|
search: owner ? `${owner.ownr_fn || ""} ${owner.ownr_ln || ""}` : null
|
||||||
? `${owner.ownr_fn} ${owner.ownr_ln} ${owner.ownr_addr1} ${owner.ownr_city} ${owner.ownr_zip} ${owner.ownr_ea} ${owner.ownr_ph1} ${owner.ownr_ph2}`
|
|
||||||
: null
|
|
||||||
},
|
},
|
||||||
skip: !owner,
|
skip: !owner,
|
||||||
fetchPolicy: "network-only"
|
fetchPolicy: "network-only"
|
||||||
@@ -32,18 +30,17 @@ export default function OwnerFindModalContainer({
|
|||||||
<Modal
|
<Modal
|
||||||
title={t("owners.labels.existing_owners")}
|
title={t("owners.labels.existing_owners")}
|
||||||
width={"80%"}
|
width={"80%"}
|
||||||
{...modalProps}
|
{...modalProps}>
|
||||||
>
|
|
||||||
{loading ? <LoadingSpinner /> : null}
|
{loading ? <LoadingSpinner /> : null}
|
||||||
{error ? <AlertComponent message={error.message} type="error" /> : null}
|
{error ? <AlertComponent message={error.message} type='error' /> : null}
|
||||||
{owner ? (
|
{owner ? (
|
||||||
<OwnerFindModalComponent
|
<OwnerFindModalComponent
|
||||||
selectedOwner={selectedOwner}
|
selectedOwner={selectedOwner}
|
||||||
setSelectedOwner={setSelectedOwner}
|
setSelectedOwner={setSelectedOwner}
|
||||||
ownersListLoading={ownersList.loading}
|
ownersListLoading={ownersList.loading}
|
||||||
ownersList={
|
ownersList={
|
||||||
ownersList.data && ownersList.data.search_owners
|
ownersList.data && ownersList.data.search_owner
|
||||||
? ownersList.data.search_owners
|
? ownersList.data.search_owner
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
89
client/src/components/owners-list/owners-list.component.jsx
Normal file
89
client/src/components/owners-list/owners-list.component.jsx
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import { Input, Table } from "antd";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import PhoneFormatter from "../../utils/PhoneFormatter";
|
||||||
|
import { alphaSort } from "../../utils/sorters";
|
||||||
|
|
||||||
|
export default function OwnersListComponent({ loading, owners, refetch }) {
|
||||||
|
const [state, setState] = useState({
|
||||||
|
sortedInfo: {},
|
||||||
|
filteredInfo: { text: "" }
|
||||||
|
});
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: t("owners.fields.name"),
|
||||||
|
dataIndex: "name",
|
||||||
|
key: "name",
|
||||||
|
// onFilter: (value, record) => record.ro_number.includes(value),
|
||||||
|
// filteredValue: state.filteredInfo.text || null,
|
||||||
|
sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "name" && state.sortedInfo.order,
|
||||||
|
|
||||||
|
render: (text, record) => (
|
||||||
|
<Link to={"/manage/owners/" + record.id}>
|
||||||
|
{`${record.ownr_fn} ${record.ownr_ln}`}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("owners.fields.ownr_ph1"),
|
||||||
|
dataIndex: "ownr_ph1",
|
||||||
|
key: "ownr_ph1",
|
||||||
|
sorter: (a, b) => alphaSort(a.ownr_ph1, b.ownr_ph1),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "ownr_ph1" && state.sortedInfo.order,
|
||||||
|
render: (text, record) => {
|
||||||
|
return <PhoneFormatter>{record.ownr_ph1}</PhoneFormatter>;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("owners.fields.ownr_ea"),
|
||||||
|
dataIndex: "ownr_ea",
|
||||||
|
key: "ownr_ea"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("owners.fields.address"),
|
||||||
|
dataIndex: "address",
|
||||||
|
key: "address",
|
||||||
|
render: (text, record) => {
|
||||||
|
return (
|
||||||
|
<div>{`${record.ownr_addr1 || ""} ${record.ownr_addr2 ||
|
||||||
|
""} ${record.ownr_city || ""} ${record.ownr_st ||
|
||||||
|
""} ${record.ownr_zip || ""}`}</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleTableChange = (pagination, filters, sorter) => {
|
||||||
|
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||||
|
};
|
||||||
|
//TODO Implement searching & pagination
|
||||||
|
return (
|
||||||
|
<Table
|
||||||
|
loading={loading}
|
||||||
|
title={() => {
|
||||||
|
return (
|
||||||
|
<Input.Search
|
||||||
|
placeholder="Search..."
|
||||||
|
onSearch={value => {
|
||||||
|
console.log(value);
|
||||||
|
}}
|
||||||
|
enterButton
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
size="small"
|
||||||
|
pagination={{ position: "top" }}
|
||||||
|
columns={columns.map(item => ({ ...item }))}
|
||||||
|
rowKey="id"
|
||||||
|
dataSource={owners}
|
||||||
|
onChange={handleTableChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
20
client/src/components/owners-list/owners-list.container.jsx
Normal file
20
client/src/components/owners-list/owners-list.container.jsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useQuery } from "react-apollo";
|
||||||
|
import { QUERY_ALL_OWNERS } from "../../graphql/owners.queries";
|
||||||
|
import AlertComponent from "../alert/alert.component";
|
||||||
|
import OwnersListComponent from "./owners-list.component";
|
||||||
|
|
||||||
|
export default function OwnersListContainer() {
|
||||||
|
const { loading, error, data, refetch } = useQuery(QUERY_ALL_OWNERS, {
|
||||||
|
fetchPolicy: "network-only"
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||||
|
return (
|
||||||
|
<OwnersListComponent
|
||||||
|
loading={loading}
|
||||||
|
owners={data ? data.owners : null}
|
||||||
|
refetch={refetch}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
import { AutoComplete, DatePicker, Icon, Input, List, Radio } from "antd";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
export default function PartsOrderModalComponent({
|
||||||
|
vendorList,
|
||||||
|
state,
|
||||||
|
sendTypeState,
|
||||||
|
orderLinesState
|
||||||
|
}) {
|
||||||
|
const [partsOrder, setPartsOrder] = state;
|
||||||
|
const [sendType, setSendType] = sendTypeState;
|
||||||
|
const orderLines = orderLinesState[0];
|
||||||
|
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) => {
|
||||||
|
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 });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{t("parts_orders.labels.inthisorder")}
|
||||||
|
|
||||||
|
<List
|
||||||
|
itemLayout='horizontal'
|
||||||
|
dataSource={orderLines}
|
||||||
|
renderItem={item => (
|
||||||
|
<List.Item
|
||||||
|
actions={[
|
||||||
|
<Input placeholder={t("parts_orders.fields.lineremarks")} />
|
||||||
|
//TODO Editable table/adding line remarks to the order.
|
||||||
|
]}>
|
||||||
|
{
|
||||||
|
// <List.Item.Meta
|
||||||
|
// avatar={
|
||||||
|
// <Avatar src='https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png' />
|
||||||
|
// }
|
||||||
|
// title={<a href='https://ant.design'>{item.name.last}</a>}
|
||||||
|
// description='Ant Design, a design language for background applications, is refined by Ant UED Team'
|
||||||
|
// />
|
||||||
|
}
|
||||||
|
<div>{`${item.line_desc}${
|
||||||
|
item.oem_partno ? " | " + item.oem_partno : ""
|
||||||
|
}`}</div>
|
||||||
|
</List.Item>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
import { Modal, notification } from "antd";
|
||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import { useMutation, useQuery } from "react-apollo";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { INSERT_NEW_PARTS_ORDERS } from "../../graphql/parts-orders.queries";
|
||||||
|
import { UPDATE_JOB_LINE_STATUS } from "../../graphql/jobs-lines.queries";
|
||||||
|
import { QUERY_ALL_VENDORS_FOR_ORDER } from "../../graphql/vendors.queries";
|
||||||
|
import {
|
||||||
|
selectBodyshop,
|
||||||
|
selectCurrentUser
|
||||||
|
} 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 {
|
||||||
|
setEmailOptions,
|
||||||
|
toggleEmailOverlayVisible
|
||||||
|
} from "../../redux/email/email.actions";
|
||||||
|
import PartsOrderEmailTemplate from "../../emails/parts-order/parts-order.email";
|
||||||
|
import { REPORT_QUERY_PARTS_ORDER_BY_PK } from "../../emails/parts-order/parts-order.query";
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
currentUser: selectCurrentUser,
|
||||||
|
bodyshop: selectBodyshop
|
||||||
|
});
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
setEmailOptions: e => dispatch(setEmailOptions(e)),
|
||||||
|
toggleEmailOverlayVisible: () => dispatch(toggleEmailOverlayVisible())
|
||||||
|
});
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(function PartsOrderModalContainer({
|
||||||
|
partsOrderModalVisible,
|
||||||
|
linesToOrder,
|
||||||
|
jobId,
|
||||||
|
currentUser,
|
||||||
|
bodyshop,
|
||||||
|
refetch,
|
||||||
|
setEmailOptions,
|
||||||
|
toggleEmailOverlayVisible
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [modalVisible, setModalVisible] = partsOrderModalVisible;
|
||||||
|
|
||||||
|
//set order lines to be a version of the incoming lines.
|
||||||
|
const orderLinesState = useState(
|
||||||
|
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",
|
||||||
|
job_line_id: value.id,
|
||||||
|
status: bodyshop.md_order_statuses.default_ordered || "Ordered*"
|
||||||
|
});
|
||||||
|
return acc;
|
||||||
|
}, [])
|
||||||
|
);
|
||||||
|
const [orderLines, setOrderLinesState] = orderLinesState;
|
||||||
|
useEffect(() => {
|
||||||
|
if (modalVisible)
|
||||||
|
setOrderLinesState(
|
||||||
|
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",
|
||||||
|
job_line_id: value.id,
|
||||||
|
status: bodyshop.md_order_statuses.default_ordered || "Ordered*"
|
||||||
|
});
|
||||||
|
return acc;
|
||||||
|
}, [])
|
||||||
|
);
|
||||||
|
}, [
|
||||||
|
modalVisible,
|
||||||
|
setOrderLinesState,
|
||||||
|
linesToOrder,
|
||||||
|
bodyshop.md_order_statuses.default_ordered
|
||||||
|
]);
|
||||||
|
|
||||||
|
const sendTypeState = useState("e");
|
||||||
|
const sendType = sendTypeState[0];
|
||||||
|
const partsOrderState = useState({
|
||||||
|
vendorid: null,
|
||||||
|
jobid: jobId,
|
||||||
|
user_email: currentUser.email
|
||||||
|
});
|
||||||
|
const partsOrder = partsOrderState[0];
|
||||||
|
|
||||||
|
const { loading, error, data } = useQuery(QUERY_ALL_VENDORS_FOR_ORDER, {
|
||||||
|
fetchPolicy: "network-only",
|
||||||
|
skip: !modalVisible
|
||||||
|
});
|
||||||
|
const [insertPartOrder] = useMutation(INSERT_NEW_PARTS_ORDERS);
|
||||||
|
const [updateJobLines] = useMutation(UPDATE_JOB_LINE_STATUS);
|
||||||
|
|
||||||
|
const handleOk = () => {
|
||||||
|
insertPartOrder({
|
||||||
|
variables: {
|
||||||
|
po: [
|
||||||
|
{
|
||||||
|
...partsOrder,
|
||||||
|
status: bodyshop.md_order_statuses.default_ordered || "Ordered*",
|
||||||
|
parts_order_lines: {
|
||||||
|
data: orderLines
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(r => {
|
||||||
|
updateJobLines({
|
||||||
|
variables: {
|
||||||
|
ids: linesToOrder.map(item => item.id),
|
||||||
|
status: bodyshop.md_order_statuses.default_ordered || "Ordered*"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
notification["success"]({
|
||||||
|
message: t("parts_orders.successes.created")
|
||||||
|
});
|
||||||
|
if (refetch) refetch();
|
||||||
|
setModalVisible(false);
|
||||||
|
|
||||||
|
if (sendType === "e") {
|
||||||
|
//Show the email modal and set the data.
|
||||||
|
|
||||||
|
//TODO Remove some of the options below.
|
||||||
|
setEmailOptions({
|
||||||
|
messageOptions: {
|
||||||
|
from: {
|
||||||
|
name: "Kavia Autobdoy",
|
||||||
|
address: "noreply@bodyshop.app"
|
||||||
|
},
|
||||||
|
to: "patrickwf@gmail.com",
|
||||||
|
replyTo: "snaptsoft@gmail.com"
|
||||||
|
},
|
||||||
|
template: PartsOrderEmailTemplate,
|
||||||
|
queryConfig: [
|
||||||
|
REPORT_QUERY_PARTS_ORDER_BY_PK,
|
||||||
|
{
|
||||||
|
variables: {
|
||||||
|
id: r.data.insert_parts_orders.returning[0].id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
toggleEmailOverlayVisible();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
notification["error"]({
|
||||||
|
message: t("parts_orders.errors.creating"),
|
||||||
|
description: error.message
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.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}
|
||||||
|
orderLinesState={orderLinesState}
|
||||||
|
/>
|
||||||
|
</LoadingSpinner>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -1,15 +1,17 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
|
import ProfileMyComponent from "../profile-my/profile-my.component";
|
||||||
|
import ProfileShopsContainer from "../profile-shops/profile-shops.container";
|
||||||
|
|
||||||
export default function ProfileContent({ sidebarSelection }) {
|
export default function ProfileContent({ sidebarSelection }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
switch (sidebarSelection.key) {
|
switch (sidebarSelection.key) {
|
||||||
case "profile":
|
case "profile":
|
||||||
return <div>Profile stuff</div>;
|
return <ProfileMyComponent />;
|
||||||
case "shop":
|
case "shops":
|
||||||
return <div>Shop stuff</div>;
|
return <ProfileShopsContainer />;
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<AlertComponent message={t("profile.errors.state")} type="error" />
|
<AlertComponent message={t("profile.errors.state")} type="error" />
|
||||||
|
|||||||
76
client/src/components/profile-my/profile-my.component.jsx
Normal file
76
client/src/components/profile-my/profile-my.component.jsx
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
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 { 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
|
||||||
|
});
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
updateUserDetails: userDetails => dispatch(updateUserDetails(userDetails))
|
||||||
|
});
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(
|
||||||
|
Form.create({ name: "ProfileMyComponentForm" })(function ProfileMyComponent({
|
||||||
|
currentUser,
|
||||||
|
form,
|
||||||
|
updateUserDetails
|
||||||
|
}) {
|
||||||
|
const { isFieldsTouched, resetFields, getFieldDecorator } = form;
|
||||||
|
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("values", values);
|
||||||
|
updateUserDetails({
|
||||||
|
displayName: values.displayname,
|
||||||
|
photoURL: values.photoURL
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{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' />)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("user.fields.photourl")}>
|
||||||
|
{getFieldDecorator("photoURL", {
|
||||||
|
initialValue: currentUser.photoURL
|
||||||
|
})(<Input name='photoURL' />)}
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type='primary'
|
||||||
|
key='submit'
|
||||||
|
htmlType='submit'
|
||||||
|
onClick={handleSubmit}>
|
||||||
|
{t("user.actions.updateprofile")}
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Table, Button } from "antd";
|
||||||
|
export default function ProfileShopsComponent({
|
||||||
|
loading,
|
||||||
|
data,
|
||||||
|
updateActiveShop
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: t("associations.fields.shopname"),
|
||||||
|
dataIndex: "shopname",
|
||||||
|
key: "shopname",
|
||||||
|
width: "25%",
|
||||||
|
render: (text, record) => <span>{record.bodyshop.shopname}</span>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("associations.fields.active"),
|
||||||
|
dataIndex: "active",
|
||||||
|
key: "active",
|
||||||
|
width: "25%",
|
||||||
|
render: (text, record) => <span>{record.active ? "Yes" : "No"}</span>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("associations.labels.actions"),
|
||||||
|
dataIndex: "actions",
|
||||||
|
key: "actions",
|
||||||
|
width: "25%",
|
||||||
|
render: (text, record) => (
|
||||||
|
<span>
|
||||||
|
{record.active ? null : (
|
||||||
|
<Button onClick={() => updateActiveShop(record.id)}>
|
||||||
|
Activate
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table
|
||||||
|
loading={loading}
|
||||||
|
size="small"
|
||||||
|
columns={columns.map(item => ({ ...item }))}
|
||||||
|
rowKey="id"
|
||||||
|
dataSource={data}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useQuery, useMutation } from "react-apollo";
|
||||||
|
import {
|
||||||
|
QUERY_ALL_ASSOCIATIONS,
|
||||||
|
UPDATE_ASSOCIATION
|
||||||
|
} from "../../graphql/associations.queries";
|
||||||
|
import AlertComponent from "../alert/alert.component";
|
||||||
|
import ProfileShopsComponent from "./profile-shops.component";
|
||||||
|
|
||||||
|
export default function ProfileShopsContainer() {
|
||||||
|
const { loading, error, data, refetch } = useQuery(QUERY_ALL_ASSOCIATIONS);
|
||||||
|
const [updateAssocation] = useMutation(UPDATE_ASSOCIATION);
|
||||||
|
|
||||||
|
const updateActiveShop = activeShopId => {
|
||||||
|
data.associations.forEach(record => {
|
||||||
|
updateAssocation({
|
||||||
|
variables: {
|
||||||
|
assocId: record.id,
|
||||||
|
assocActive: record.id === activeShopId ? true : false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
refetch();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (error) return <AlertComponent type="error" message={error.message} />;
|
||||||
|
return (
|
||||||
|
<ProfileShopsComponent
|
||||||
|
loading={loading}
|
||||||
|
data={data ? data.associations : null}
|
||||||
|
updateActiveShop={updateActiveShop}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { Checkbox, Col, DatePicker, Modal, Row, TimePicker, Input } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import ScheduleDayViewContainer from "../schedule-day-view/schedule-day-view.container";
|
||||||
|
|
||||||
|
export default function ScheduleJobModalComponent({
|
||||||
|
appData,
|
||||||
|
setAppData,
|
||||||
|
formData,
|
||||||
|
setFormData,
|
||||||
|
...props
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
//TODO Existing appointments list only refreshes sometimes after modal close. May have to do with the container class.
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
{...props}
|
||||||
|
width={"80%"}
|
||||||
|
maskClosable={false}
|
||||||
|
destroyOnClose={true}
|
||||||
|
okButtonProps={{ disabled: appData.start ? false : true }}
|
||||||
|
>
|
||||||
|
<Row>
|
||||||
|
<Col span={14}>
|
||||||
|
<Row>
|
||||||
|
Manual Job Selection Scheduled Time
|
||||||
|
<Input
|
||||||
|
placeholder={t("appointments.fields.title")}
|
||||||
|
onChange={e => {
|
||||||
|
setAppData({ ...appData, title: e.target.value });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<DatePicker
|
||||||
|
value={appData.start}
|
||||||
|
onChange={e => {
|
||||||
|
setAppData({ ...appData, start: e });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TimePicker
|
||||||
|
value={appData.start}
|
||||||
|
format={"HH:mm"}
|
||||||
|
minuteStep={15}
|
||||||
|
onChange={e => {
|
||||||
|
setAppData({ ...appData, start: e });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
{
|
||||||
|
//TODO Build out notifications.
|
||||||
|
}
|
||||||
|
<Checkbox
|
||||||
|
defaultChecked={formData.notifyCustomer}
|
||||||
|
onChange={e =>
|
||||||
|
setFormData({ ...formData, notifyCustomer: e.target.checked })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t("jobs.labels.appointmentconfirmation")}
|
||||||
|
</Checkbox>
|
||||||
|
</Col>
|
||||||
|
<Col span={10}>
|
||||||
|
<ScheduleDayViewContainer day={appData.start} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import { notification } from "antd";
|
||||||
|
import moment from "moment";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { useMutation } from "react-apollo";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { INSERT_APPOINTMENT } from "../../graphql/appointments.queries";
|
||||||
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
import ScheduleAppointmentModalComponent from "./schedule-appointment-modal.component";
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
bodyshop: selectBodyshop
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
null
|
||||||
|
)(function ScheduleAppointmentModalContainer({
|
||||||
|
scheduleModalState,
|
||||||
|
jobId,
|
||||||
|
bodyshop,
|
||||||
|
refetch
|
||||||
|
}) {
|
||||||
|
const [scheduleModalVisible, setscheduleModalVisible] = scheduleModalState;
|
||||||
|
const [appData, setAppData] = useState({
|
||||||
|
jobid: jobId,
|
||||||
|
bodyshopid: bodyshop.id,
|
||||||
|
isintake: false,
|
||||||
|
start: null
|
||||||
|
});
|
||||||
|
const [insertAppointment] = useMutation(INSERT_APPOINTMENT);
|
||||||
|
const [formData, setFormData] = useState({ notifyCustomer: false });
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScheduleAppointmentModalComponent
|
||||||
|
appData={appData}
|
||||||
|
setAppData={setAppData}
|
||||||
|
formData={formData}
|
||||||
|
setFormData={setFormData}
|
||||||
|
//Spreadable Modal Props
|
||||||
|
visible={scheduleModalVisible}
|
||||||
|
onCancel={() => setscheduleModalVisible(false)}
|
||||||
|
onOk={() => {
|
||||||
|
//TODO Customize the amount of minutes it will add.
|
||||||
|
insertAppointment({
|
||||||
|
variables: {
|
||||||
|
app: { ...appData, end: moment(appData.start).add(60, "minutes") }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(r => {
|
||||||
|
notification["success"]({
|
||||||
|
message: t("appointments.successes.created")
|
||||||
|
});
|
||||||
|
|
||||||
|
if (formData.notifyCustomer) {
|
||||||
|
//TODO Implement customer reminder on scheduling.
|
||||||
|
alert("Chosed to notify the customer somehow!");
|
||||||
|
}
|
||||||
|
setscheduleModalVisible(false);
|
||||||
|
if (refetch) refetch();
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
notification["error"]({
|
||||||
|
message: t("appointments.errors.saving", {
|
||||||
|
message: error.message
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
@import 'react-big-calendar/lib/sass/styles';
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
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 "./schedule-calendar.styles.scss";
|
||||||
|
import DateCellWrapper from "../schedule-datecellwrapper/schedule-datecellwrapper.component";
|
||||||
|
import Event from "../schedule-event/schedule-event.container";
|
||||||
|
const localizer = momentLocalizer(moment);
|
||||||
|
|
||||||
|
export default function ScheduleCalendarWrapperComponent({
|
||||||
|
data,
|
||||||
|
refetch,
|
||||||
|
defaultView,
|
||||||
|
...otherProps
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Calendar
|
||||||
|
events={data}
|
||||||
|
defaultView={defaultView}
|
||||||
|
step={30}
|
||||||
|
showMultiDayTimes
|
||||||
|
localizer={localizer}
|
||||||
|
min={new Date("2020-01-01T06:00:00")} //TODO Read from business settings.
|
||||||
|
max={new Date("2020-01-01T20:00:00")}
|
||||||
|
components={{
|
||||||
|
event: e => {
|
||||||
|
return Event({ event: e.event, refetch: refetch });
|
||||||
|
},
|
||||||
|
dateCellWrapper: DateCellWrapper
|
||||||
|
}}
|
||||||
|
{...otherProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import React from "react";
|
||||||
|
//import "react-big-calendar/lib/css/react-big-calendar.css";
|
||||||
|
import ScheduleCalendarWrapperComponent from "../schedule-calendar-wrapper/scheduler-calendar-wrapper.component";
|
||||||
|
import { Button, Icon } from "antd";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import ScheduleAppointmentModalContainer from "../schedule-appointment-modal/schedule-appointment-modal.container";
|
||||||
|
|
||||||
|
export default function ScheduleCalendarComponent({
|
||||||
|
data,
|
||||||
|
refetch,
|
||||||
|
scheduleModalState
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
refetch();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon type="sync" />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
scheduleModalState[1](true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("appointments.actions.new")}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<ScheduleAppointmentModalContainer
|
||||||
|
scheduleModalState={scheduleModalState}
|
||||||
|
jobId={null}
|
||||||
|
refetch={refetch}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ScheduleCalendarWrapperComponent
|
||||||
|
data={data}
|
||||||
|
defaultView="week"
|
||||||
|
refetch={refetch}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { useQuery } from "react-apollo";
|
||||||
|
import ScheduleCalendarComponent from "./schedule-calendar.component";
|
||||||
|
import { QUERY_ALL_ACTIVE_APPOINTMENTS } from "../../graphql/appointments.queries";
|
||||||
|
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||||
|
import AlertComponent from "../alert/alert.component";
|
||||||
|
|
||||||
|
export default function ScheduleCalendarContainer() {
|
||||||
|
const { loading, error, data, refetch } = useQuery(
|
||||||
|
QUERY_ALL_ACTIVE_APPOINTMENTS,
|
||||||
|
{
|
||||||
|
fetchPolicy: "network-only"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const scheduleModalState = useState(false);
|
||||||
|
|
||||||
|
if (loading) return <LoadingSpinner />;
|
||||||
|
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||||
|
let normalizedData = data.appointments.map(e => {
|
||||||
|
//Required becuase Hasura returns a string instead of a date object.
|
||||||
|
return Object.assign(
|
||||||
|
{},
|
||||||
|
e,
|
||||||
|
{ start: new Date(e.start) },
|
||||||
|
{ end: new Date(e.end) }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScheduleCalendarComponent
|
||||||
|
scheduleModalState={scheduleModalState}
|
||||||
|
refetch={refetch}
|
||||||
|
data={data ? normalizedData : null}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export default function ScheduleDateCellWrapper(dateCellWrapperProps) {
|
||||||
|
// Show 'click me' text in arbitrary places by using the range prop
|
||||||
|
|
||||||
|
const style = {
|
||||||
|
display: "flex",
|
||||||
|
flex: 1,
|
||||||
|
borderLeft: "1px solid #DDD",
|
||||||
|
backgroundColor: "#fff"
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div style={style}>
|
||||||
|
PLACEHOLDER:DATA
|
||||||
|
{dateCellWrapperProps.children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import React from "react";
|
||||||
|
import "react-big-calendar/lib/css/react-big-calendar.css";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import ScheduleCalendarWrapperComponent from "../schedule-calendar-wrapper/scheduler-calendar-wrapper.component";
|
||||||
|
|
||||||
|
export default function ScheduleDayViewComponent({ data, day }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
if (data)
|
||||||
|
//TODO Remove addtional calendar elements from day view.
|
||||||
|
return (
|
||||||
|
<ScheduleCalendarWrapperComponent
|
||||||
|
events={data}
|
||||||
|
defaultView="day"
|
||||||
|
views={["day"]}
|
||||||
|
style={{ height: "40vh" }}
|
||||||
|
defaultDate={new Date(day)}
|
||||||
|
//onNavigate={e => console.log("e", e)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
else return <div>{t("appointments.labels.nodateselected")}</div>;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user