Merge pull request #5 from snaptsoft/dev-patrick
UNSTABLE MERGE INTO DEV - Job Lines page is not functional.
This commit is contained in:
@@ -7,3 +7,6 @@ ALL CHANGES MUST BE MADE USING LOCAL CONSOLE TO ENSURE DATABASE MIGRATION FILES
|
||||
|
||||
To Start Hasura CLI:
|
||||
npx hasura console --admin-secret Dev-BodyShopAppBySnaptSoftware!
|
||||
|
||||
Migrating to Staging:
|
||||
npx hasura migrate apply --up 10 --endpoint https://bodyshop-staging-db.herokuapp.com/ --admin-secret Staging-BodyShopAppBySnaptSoftware!
|
||||
@@ -5,4 +5,4 @@
|
||||
1. Get a presigned URL by hitting our own express server with a unique key.
|
||||
2. Use this presigned URL to upload an individual file.
|
||||
3. Store the key + the bucket name to the documents record.
|
||||
4. ???Figure out how to add thumbnails.
|
||||
4.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -54,6 +54,7 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@apollo/react-testing": "^3.1.3",
|
||||
"enzyme": "^3.11.0",
|
||||
"enzyme-adapter-react-16": "^1.15.2"
|
||||
}
|
||||
|
||||
@@ -109,7 +109,6 @@ class AppContainer extends Component {
|
||||
});
|
||||
|
||||
try {
|
||||
// See above for additional options, including other storage providers.
|
||||
await persistCache({
|
||||
cache,
|
||||
storage: window.sessionStorage,
|
||||
@@ -127,6 +126,8 @@ class AppContainer extends Component {
|
||||
//Init local state.
|
||||
}
|
||||
|
||||
componentWillUnmount() {}
|
||||
|
||||
render() {
|
||||
const { client, loaded } = this.state;
|
||||
|
||||
|
||||
@@ -1 +1,27 @@
|
||||
@import '~antd/dist/antd.css';
|
||||
@import "~antd/dist/antd.css";
|
||||
|
||||
/* .ant-layout-header {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
height: 5vh;
|
||||
right: 0px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.ant-layout-content {
|
||||
position: absolute;
|
||||
top: 5vh;
|
||||
bottom: 3vh;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.ant-layout-footer {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
height: 3vh;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
overflow: hidden;
|
||||
} */
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React, { useEffect, Suspense, lazy } from "react";
|
||||
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 "./App.css";
|
||||
|
||||
@@ -12,7 +13,7 @@ import ErrorBoundary from "../components/error-boundary/error-boundary.component
|
||||
|
||||
import { auth } from "../firebase/firebase.utils";
|
||||
import { UPSERT_USER } from "../graphql/user.queries";
|
||||
import { GET_CURRENT_USER } from "../graphql/local.queries";
|
||||
import { GET_CURRENT_USER, GET_LANGUAGE } from "../graphql/local.queries";
|
||||
// import { QUERY_BODYSHOP } from "../graphql/bodyshop.queries";
|
||||
|
||||
import PrivateRoute from "../utils/private-route";
|
||||
@@ -26,10 +27,12 @@ const Unauthorized = lazy(() =>
|
||||
|
||||
export default () => {
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const [loaded, setloaded] = useState(false);
|
||||
useEffect(() => {
|
||||
//Run the auth code only on the first render.
|
||||
const unsubscribeFromAuth = auth.onAuthStateChanged(async user => {
|
||||
console.log("Auth State Changed.");
|
||||
setloaded(true);
|
||||
if (user) {
|
||||
let token;
|
||||
token = await user.getIdToken();
|
||||
@@ -86,16 +89,30 @@ export default () => {
|
||||
unsubscribeFromAuth();
|
||||
};
|
||||
}, [apolloClient]);
|
||||
|
||||
const HookCurrentUser = useQuery(GET_CURRENT_USER);
|
||||
if (HookCurrentUser.loading) return <LoadingSpinner />;
|
||||
if (HookCurrentUser.error)
|
||||
return <AlertComponent message={HookCurrentUser.error.message} />;
|
||||
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)
|
||||
return console.log("Error encountered when changing languages.", err);
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Switch>
|
||||
<ErrorBoundary>
|
||||
<Suspense fallback={<div>TODO: Suspense Loading</div>}>
|
||||
<Suspense fallback={<LoadingSpinner />}>
|
||||
<Route exact path='/' component={LandingPage} />
|
||||
<Route exact path='/unauthorized' component={Unauthorized} />
|
||||
<Route
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import App from "./App.container";
|
||||
import { MockedProvider } from "@apollo/react-testing";
|
||||
const div = document.createElement("div");
|
||||
|
||||
it('renders without crashing', () => {
|
||||
const div = document.createElement('div');
|
||||
ReactDOM.render(<App />, div);
|
||||
it("renders without crashing", () => {
|
||||
ReactDOM.render(
|
||||
<MockedProvider>
|
||||
<App />
|
||||
</MockedProvider>,
|
||||
div
|
||||
);
|
||||
});
|
||||
|
||||
it("unmounts without crashing", () => {
|
||||
ReactDOM.unmountComponentAtNode(div);
|
||||
});
|
||||
|
||||
11
client/src/components/alert/alert.component.test.js
Normal file
11
client/src/components/alert/alert.component.test.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import Alert from "./alert.component";
|
||||
import { MockedProvider } from "@apollo/react-testing";
|
||||
import { shallow } from "enzyme";
|
||||
|
||||
const div = document.createElement("div");
|
||||
|
||||
it("renders without crashing", () => {
|
||||
shallow(<Alert type="error" />);
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
import React from "react";
|
||||
|
||||
export default function ChatWindowComponent() {
|
||||
return <div>Chat Windows and more</div>;
|
||||
}
|
||||
18
client/src/components/chat-window/chat-window.container.jsx
Normal file
18
client/src/components/chat-window/chat-window.container.jsx
Normal file
@@ -0,0 +1,18 @@
|
||||
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,28 +1,33 @@
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Dropdown, Menu, Icon, Avatar, Row, Col } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useApolloClient, useQuery } from "@apollo/react-hooks";
|
||||
import { Avatar, Col, Dropdown, Icon, Menu, Row } from "antd";
|
||||
import i18next from "i18next";
|
||||
import { useQuery } from "@apollo/react-hooks";
|
||||
import { GET_CURRENT_USER } from "../../graphql/local.queries";
|
||||
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 => {
|
||||
console.log("e", 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>
|
||||
@@ -47,7 +52,7 @@ export default function CurrentUserDropdown() {
|
||||
);
|
||||
|
||||
if (loading) return null;
|
||||
if (error) return <AlertComponent message={error.message} />;
|
||||
if (error) return <AlertComponent message={error.message} type='error' />;
|
||||
|
||||
const { currentUser } = data;
|
||||
|
||||
@@ -57,10 +62,8 @@ export default function CurrentUserDropdown() {
|
||||
<Col span={8}>
|
||||
<Avatar size='large' alt='Avatar' src={UserImage} />
|
||||
</Col>
|
||||
<Col span={16}>
|
||||
<Link to='/manage/profile'>
|
||||
{currentUser?.displayName ?? t("general.labels.unknown")}
|
||||
</Link>
|
||||
<Col span={16} style={{ color: "white" }}>
|
||||
{currentUser.displayName || t("general.labels.unknown")}
|
||||
</Col>
|
||||
</Row>
|
||||
</Dropdown>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Col, Row } from "antd";
|
||||
import React from "react";
|
||||
import { Row, Col } from "antd";
|
||||
|
||||
export default function FooterComponent() {
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Icon, Input } from "antd";
|
||||
import React, { forwardRef } from "react";
|
||||
function FormItemEmail(props, ref) {
|
||||
return (
|
||||
<Input
|
||||
{...props}
|
||||
addonAfter={
|
||||
<a href={`mailto:${props.email}`}>
|
||||
<Icon type="mail" />
|
||||
</a>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default forwardRef(FormItemEmail);
|
||||
@@ -1,47 +1,65 @@
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useApolloClient } from "@apollo/react-hooks";
|
||||
import { Menu, Icon, Row, Col } from "antd";
|
||||
import "./header.styles.scss";
|
||||
|
||||
import SignOut from "../sign-out/sign-out.component";
|
||||
import ManageSignInButton from "../manage-sign-in-button/manage-sign-in-button.component";
|
||||
import GlobalSearch from "../global-search/global-search.component";
|
||||
import { Col, Icon, Menu, Row } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import CurrentUserDropdown from "../current-user-dropdown/current-user-dropdown.component";
|
||||
import GlobalSearch from "../global-search/global-search.component";
|
||||
import ManageSignInButton from "../manage-sign-in-button/manage-sign-in-button.component";
|
||||
import "./header.styles.scss";
|
||||
|
||||
export default ({ landingHeader, navItems, selectedNavItem }) => {
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const { t } = useTranslation();
|
||||
const handleClick = e => {
|
||||
apolloClient.writeData({ data: { selectedNavItem: e.key } });
|
||||
};
|
||||
return (
|
||||
<Row type="flex" justify="space-around">
|
||||
<Row type='flex' justify='space-around'>
|
||||
<Col span={16}>
|
||||
<Menu
|
||||
theme="dark"
|
||||
className="header"
|
||||
theme='dark'
|
||||
className='header'
|
||||
onClick={handleClick}
|
||||
selectedKeys={selectedNavItem}
|
||||
mode="horizontal"
|
||||
>
|
||||
mode='horizontal'>
|
||||
<Menu.Item>
|
||||
<GlobalSearch />
|
||||
</Menu.Item>
|
||||
{navItems.map(navItem => (
|
||||
<Menu.Item key={navItem.title}>
|
||||
<Link to={navItem.path}>
|
||||
{navItem.icontype ? <Icon type={navItem.icontype} /> : null}
|
||||
{navItem.title}
|
||||
|
||||
<Menu.Item key='home'>
|
||||
<Link to='/manage'>
|
||||
<Icon type='home' />
|
||||
{t("menus.header.home")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.SubMenu title={t("menus.header.jobs")}>
|
||||
<Menu.Item key='jobs'>
|
||||
<Link to='/manage/jobs'>
|
||||
<Icon type='home' />
|
||||
{t("menus.header.activejobs")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
))}
|
||||
|
||||
{!landingHeader ? (
|
||||
<Menu.Item>
|
||||
<SignOut />
|
||||
<Menu.Item key='availablejobs'>
|
||||
<Link to='/manage/available'>
|
||||
<Icon type='home' />
|
||||
{t("menus.header.availablejobs")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
) : (
|
||||
</Menu.SubMenu>
|
||||
|
||||
{
|
||||
// navItems.map(navItem => (
|
||||
// <Menu.Item key={navItem.title}>
|
||||
// <Link to={navItem.path}>
|
||||
// {navItem.icontype ? <Icon type={navItem.icontype} /> : null}
|
||||
// {navItem.title}
|
||||
// </Link>
|
||||
// </Menu.Item>
|
||||
// ))
|
||||
}
|
||||
|
||||
{!landingHeader ? null : (
|
||||
<Menu.Item>
|
||||
<ManageSignInButton />
|
||||
</Menu.Item>
|
||||
|
||||
@@ -1,46 +1,45 @@
|
||||
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_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 LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||
//import AlertComponent from "../alert/alert.component";
|
||||
import HeaderComponent from "./header.component";
|
||||
|
||||
export default ({ landingHeader, signedIn }) => {
|
||||
const hookSelectedNavItem = useQuery(GET_CURRENT_SELECTED_NAV_ITEM);
|
||||
|
||||
let hookNavItems;
|
||||
if (landingHeader) {
|
||||
hookNavItems = useQuery(GET_LANDING_NAV_ITEMS, {
|
||||
fetchPolicy: "network-only"
|
||||
});
|
||||
} else {
|
||||
hookNavItems = useQuery(GET_NAV_ITEMS, {
|
||||
fetchPolicy: "network-only"
|
||||
});
|
||||
}
|
||||
// let hookNavItems;
|
||||
// if (landingHeader) {
|
||||
// hookNavItems = useQuery(GET_LANDING_NAV_ITEMS, {
|
||||
// fetchPolicy: "network-only"
|
||||
// });
|
||||
// } else {
|
||||
// hookNavItems = useQuery(GET_NAV_ITEMS, {
|
||||
// fetchPolicy: "network-only"
|
||||
// });
|
||||
// }
|
||||
|
||||
if (hookNavItems.loading || hookSelectedNavItem.loading)
|
||||
return <LoadingSpinner />;
|
||||
if (hookNavItems.error)
|
||||
return <AlertComponent message={hookNavItems.error.message} />;
|
||||
if (hookSelectedNavItem.error)
|
||||
return console.log(
|
||||
"Unable to load Selected Navigation Item.",
|
||||
hookSelectedNavItem.error
|
||||
);
|
||||
// if (hookNavItems.loading || hookSelectedNavItem.loading)
|
||||
// return <LoadingSpinner />;
|
||||
// if (hookNavItems.error)
|
||||
// return <AlertComponent message={hookNavItems.error.message} />;
|
||||
// if (hookSelectedNavItem.error)
|
||||
// return console.log(
|
||||
// "Unable to load Selected Navigation Item.",
|
||||
// hookSelectedNavItem.error
|
||||
// );
|
||||
|
||||
const { selectedNavItem } = hookSelectedNavItem.data;
|
||||
const navItems = JSON.parse(hookNavItems.data.masterdata_by_pk.value);
|
||||
// const navItems = JSON.parse(hookNavItems.data.masterdata_by_pk.value);
|
||||
|
||||
return (
|
||||
<HeaderComponent
|
||||
landingHeader={landingHeader}
|
||||
navItems={navItems}
|
||||
selectedNavItem={selectedNavItem}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
import React, { useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useQuery } from "@apollo/react-hooks";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import { Button, Icon, PageHeader, Tag } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import { QUERY_JOB_CARD_DETAILS } from "../../graphql/jobs.queries";
|
||||
import { PageHeader, Button, Descriptions, Tag, Icon } from "antd";
|
||||
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||
import NoteUpsertModal from "../note-upsert-modal/note-upsert-modal.container";
|
||||
//import JobDetailCardsHeaderComponent from "./job-detail-cards.header.component";
|
||||
import JobDetailCardsCustomerComponent from "./job-detail-cards.customer.component";
|
||||
import JobDetailCardsVehicleComponent from "./job-detail-cards.vehicle.component";
|
||||
import JobDetailCardsInsuranceComponent from "./job-detail-cards.insurance.component";
|
||||
import JobDetailCardsDatesComponent from "./job-detail-cards.dates.component";
|
||||
import JobDetailCardsPartsComponent from "./job-detail-cards.parts.component";
|
||||
import JobDetailCardsNotesComponent from "./job-detail-cards.notes.component";
|
||||
import JobDetailCardsDamageComponent from "./job-detail-cards.damage.component";
|
||||
import JobDetailCardsTotalsComponent from "./job-detail-cards.totals.component";
|
||||
import JobDetailCardsDatesComponent from "./job-detail-cards.dates.component";
|
||||
import JobDetailCardsDocumentsComponent from "./job-detail-cards.documents.component";
|
||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||
|
||||
import JobDetailCardsInsuranceComponent from "./job-detail-cards.insurance.component";
|
||||
import JobDetailCardsNotesComponent from "./job-detail-cards.notes.component";
|
||||
import JobDetailCardsPartsComponent from "./job-detail-cards.parts.component";
|
||||
import "./job-detail-cards.styles.scss";
|
||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||
import NoteUpsertModal from "../note-upsert-modal/note-upsert-modal.container";
|
||||
import JobDetailCardsTotalsComponent from "./job-detail-cards.totals.component";
|
||||
|
||||
|
||||
|
||||
export default function JobDetailCards({ selectedJob }) {
|
||||
const { loading, error, data, refetch } = useQuery(QUERY_JOB_CARD_DETAILS, {
|
||||
@@ -56,11 +54,17 @@ export default function JobDetailCards({ selectedJob }) {
|
||||
</span>
|
||||
}
|
||||
title={
|
||||
loading
|
||||
? t("general.labels.loading")
|
||||
: data.jobs_by_pk.ro_number
|
||||
? `${t("jobs.fields.ro_number")} ${data.jobs_by_pk.ro_number}`
|
||||
: `${t("jobs.fields.est_number")} ${data.jobs_by_pk.est_number}`
|
||||
loading ? (
|
||||
t("general.labels.loading")
|
||||
) : (
|
||||
<Link to={`/manage/jobs/${data.jobs_by_pk.id}`}>
|
||||
{data.jobs_by_pk.ro_number
|
||||
? `${t("jobs.fields.ro_number")} ${data.jobs_by_pk.ro_number}`
|
||||
: `${t("jobs.fields.est_number")} ${
|
||||
data.jobs_by_pk.est_number
|
||||
}`}{" "}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
extra={[
|
||||
<Link
|
||||
@@ -89,33 +93,38 @@ export default function JobDetailCards({ selectedJob }) {
|
||||
{t("jobs.actions.postInvoices")}
|
||||
</Button>
|
||||
]}>
|
||||
{loading ? (
|
||||
<LoadingSkeleton />
|
||||
) : (
|
||||
<Descriptions size='small' column={3}>
|
||||
<Descriptions.Item label='Created'>Lili Qu</Descriptions.Item>
|
||||
<Descriptions.Item label='Association'>421421</Descriptions.Item>
|
||||
<Descriptions.Item label='Creation Time'>
|
||||
2017-01-10
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label='Effective Time'>
|
||||
2017-10-10
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label='Remarks'>
|
||||
Gonghu Road, Xihu District, Hangzhou, Zhejiang, China
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
)}
|
||||
{
|
||||
// loading ? (
|
||||
// <LoadingSkeleton />
|
||||
// ) : (
|
||||
// <Descriptions size='small' column={3}>
|
||||
// <Descriptions.Item label='Created'>Lili Qu</Descriptions.Item>
|
||||
// <Descriptions.Item label='Association'>421421</Descriptions.Item>
|
||||
// <Descriptions.Item label='Creation Time'>
|
||||
// 2017-01-10
|
||||
// </Descriptions.Item>
|
||||
// <Descriptions.Item label='Effective Time'>
|
||||
// 2017-10-10
|
||||
// </Descriptions.Item>
|
||||
// <Descriptions.Item label='Remarks'>
|
||||
// Gonghu Road, Xihu District, Hangzhou, Zhejiang, China
|
||||
// </Descriptions.Item>
|
||||
// </Descriptions>
|
||||
// )
|
||||
}
|
||||
|
||||
<section className='job-cards'>
|
||||
<JobDetailCardsCustomerComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
<JobDetailCardsVehicleComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
{
|
||||
// <JobDetailCardsVehicleComponent
|
||||
// loading={loading}
|
||||
// data={data ? data.jobs_by_pk : null}
|
||||
// />
|
||||
}
|
||||
|
||||
<JobDetailCardsInsuranceComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import CardTemplate from "./job-detail-cards.template.component";
|
||||
import { Link } from "react-router-dom";
|
||||
import PhoneFormatter from "../../utils/PhoneFormatter";
|
||||
import CardTemplate from "./job-detail-cards.template.component";
|
||||
|
||||
export default function JobDetailCardsCustomerComponent({ loading, data }) {
|
||||
const { t } = useTranslation();
|
||||
@@ -10,21 +11,34 @@ export default function JobDetailCardsCustomerComponent({ loading, data }) {
|
||||
<CardTemplate
|
||||
loading={loading}
|
||||
title={t("jobs.labels.cards.customer")}
|
||||
extraLink={data?.owner ? `/manage/owners/${data?.owner?.id}` : null}>
|
||||
extraLink={data && data.owner ? `/manage/owners/${data.owner.id}` : null}>
|
||||
{data ? (
|
||||
<span>
|
||||
<div>{`${data?.ownr_fn ??
|
||||
""} ${data.ownr_ln ?? ""}`}</div>
|
||||
<div>{`${data.ownr_fn || ""} ${data.ownr_ln || ""}`}</div>
|
||||
<div>
|
||||
<PhoneFormatter>{`${data?.ownr_ph1 ?? ""}`}</PhoneFormatter>
|
||||
{t("jobs.fields.phoneshort")}:
|
||||
<PhoneFormatter>{`${data.ownr_ph1 ||
|
||||
t("general.labels.na")}`}</PhoneFormatter>
|
||||
</div>
|
||||
{data?.ownr_ea ? (
|
||||
<a href={`mailto:${data.ownr_ea}`}>
|
||||
<div>{`${data?.ownr_ea ?? ""}`}</div>
|
||||
</a>
|
||||
) : null}
|
||||
|
||||
<div>{`${data?.owner?.preferred_contact ?? ""}`}</div>
|
||||
<div>
|
||||
{t("jobs.fields.ownr_ea")}:
|
||||
{data.ownr_ea ? (
|
||||
<a href={`mailto:${data.ownr_ea}`}>
|
||||
<span>{`${data.ownr_ea || ""}`}</span>
|
||||
</a>
|
||||
) : (
|
||||
t("general.labels.na")
|
||||
)}
|
||||
</div>
|
||||
<div>{`${(data.owner && data.owner.preferred_contact) || ""}`}</div>
|
||||
{data.vehicle ? (
|
||||
<Link to={`/manage/vehicles/${data.vehicle.id}`}>
|
||||
{`${data.vehicle.v_model_yr || ""} ${data.vehicle.v_make_desc ||
|
||||
""} ${data.vehicle.v_model_desc || ""}`}
|
||||
</Link>
|
||||
) : (
|
||||
<span>{t("jobs.errors.novehicle")}</span>
|
||||
)}
|
||||
</span>
|
||||
) : null}
|
||||
</CardTemplate>
|
||||
|
||||
@@ -11,13 +11,112 @@ export default function JobDetailCardsDatesComponent({ loading, data }) {
|
||||
<CardTemplate loading={loading} title={t("jobs.labels.cards.dates")}>
|
||||
{data ? (
|
||||
<Timeline>
|
||||
<Timeline.Item>
|
||||
Actual In <Moment format='MM/DD/YYYY'>{data?.actual_in}</Moment>
|
||||
</Timeline.Item>
|
||||
<Timeline.Item>
|
||||
Scheduled Completion
|
||||
<Moment format='MM/DD/YYYY'>{data?.scheduled_completion}</Moment>
|
||||
</Timeline.Item>
|
||||
{!(
|
||||
data.actual_in ||
|
||||
data.scheduled_completion ||
|
||||
data.scheduled_in ||
|
||||
data.actual_completion ||
|
||||
data.scheduled_delivery ||
|
||||
data.actual_delivery ||
|
||||
data.date_estimated ||
|
||||
data.date_open ||
|
||||
data.date_scheduled ||
|
||||
data.date_invoiced ||
|
||||
data.date_closed ||
|
||||
data.date_exported
|
||||
) ? (
|
||||
<div>{t("jobs.errors.nodates")}</div>
|
||||
) : null}
|
||||
|
||||
{data.actual_in ? (
|
||||
<Timeline.Item>
|
||||
{t("jobs.fields.actual_in")}
|
||||
<Moment format='MM/DD/YYYY'>{data.actual_in || ""}</Moment>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.scheduled_completion ? (
|
||||
<Timeline.Item>
|
||||
{t("jobs.fields.scheduled_completion")}
|
||||
<Moment format='MM/DD/YYYY'>
|
||||
{data.scheduled_completion || ""}
|
||||
</Moment>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.scheduled_in ? (
|
||||
<Timeline.Item>
|
||||
{t("jobs.fields.scheduled_in")}
|
||||
<Moment format='MM/DD/YYYY'>{data.scheduled_in || ""}</Moment>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.actual_completion ? (
|
||||
<Timeline.Item>
|
||||
{t("jobs.fields.actual_completion")}
|
||||
<Moment format='MM/DD/YYYY'>
|
||||
{data.actual_completion || ""}
|
||||
</Moment>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.scheduled_delivery ? (
|
||||
<Timeline.Item>
|
||||
{t("jobs.fields.scheduled_delivery")}
|
||||
<Moment format='MM/DD/YYYY'>
|
||||
{data.scheduled_delivery || ""}
|
||||
</Moment>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.actual_delivery ? (
|
||||
<Timeline.Item>
|
||||
{t("jobs.fields.actual_delivery")}
|
||||
<Moment format='MM/DD/YYYY'>{data.actual_delivery || ""}</Moment>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.date_estimated ? (
|
||||
<Timeline.Item>
|
||||
{t("jobs.fields.date_estimated")}
|
||||
<Moment format='MM/DD/YYYY'>{data.date_estimated || ""}</Moment>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.date_open ? (
|
||||
<Timeline.Item>
|
||||
{t("jobs.fields.date_open")}
|
||||
<Moment format='MM/DD/YYYY'>{data.date_open || ""}</Moment>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.date_scheduled ? (
|
||||
<Timeline.Item>
|
||||
{t("jobs.fields.date_scheduled")}
|
||||
<Moment format='MM/DD/YYYY'>{data.date_scheduled || ""}</Moment>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.date_invoiced ? (
|
||||
<Timeline.Item>
|
||||
{t("jobs.fields.date_invoiced")}
|
||||
<Moment format='MM/DD/YYYY'>{data.date_invoiced || ""}</Moment>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.date_closed ? (
|
||||
<Timeline.Item>
|
||||
{t("jobs.fields.date_closed")}
|
||||
<Moment format='MM/DD/YYYY'>{data.date_closed || ""}</Moment>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.date_exported ? (
|
||||
<Timeline.Item>
|
||||
{t("jobs.fields.date_exported")}
|
||||
<Moment format='MM/DD/YYYY'>{data.date_exported || ""}</Moment>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
</Timeline>
|
||||
) : null}
|
||||
</CardTemplate>
|
||||
|
||||
@@ -1,17 +1,35 @@
|
||||
import { Carousel } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import "./job-detail-cards.styles.scss";
|
||||
import CardTemplate from "./job-detail-cards.template.component";
|
||||
|
||||
export default function JobDetailCardsDocumentsComponent({ loading, data }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!data)
|
||||
return (
|
||||
<CardTemplate loading={loading} title={t("jobs.labels.cards.documents")}>
|
||||
null
|
||||
</CardTemplate>
|
||||
);
|
||||
|
||||
return (
|
||||
<CardTemplate loading={loading} title={t("jobs.labels.cards.documents")}>
|
||||
{data ? (
|
||||
<span>
|
||||
Documents stuff here.
|
||||
</span>
|
||||
) : null}
|
||||
<CardTemplate
|
||||
loading={loading}
|
||||
title={t("jobs.labels.cards.documents")}
|
||||
extraLink={`/manage/jobs/${data.id}#documents`}>
|
||||
{data.documents.count > 0 ? (
|
||||
<Carousel autoplay>
|
||||
{data.documents.map(item => (
|
||||
<div key={item.id}>
|
||||
<img src={item.thumb_url} alt={item.name} />
|
||||
</div>
|
||||
))}
|
||||
</Carousel>
|
||||
) : (
|
||||
<div>{t("documents.errors.nodocuments")}</div>
|
||||
)}
|
||||
</CardTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
.ant-carousel .slick-slide {
|
||||
text-align: center;
|
||||
height: 160px;
|
||||
line-height: 160px;
|
||||
background: #364d79;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ant-carousel .slick-slide h3 {
|
||||
color: #fff;
|
||||
}
|
||||
@@ -10,16 +10,16 @@ export default function JobDetailCardsInsuranceComponent({ loading, data }) {
|
||||
<CardTemplate loading={loading} title={t("jobs.labels.cards.insurance")}>
|
||||
{data ? (
|
||||
<span>
|
||||
<div>{data?.ins_co_nm ?? t("general.labels.unknown")}</div>
|
||||
<div>{data?.clm_no ?? 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>
|
||||
{t("jobs.labels.cards.filehandler")}
|
||||
{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>
|
||||
) : (
|
||||
<div>{`${data?.ins_ct_fn ?? ""} ${data?.ins_ct_ln ?? ""}`}</div>
|
||||
<div>{`${data?.ins_ct_fn || ""} ${data?.ins_ct_ln || ""}`}</div>
|
||||
)}
|
||||
{data?.ins_ph1 ? (
|
||||
<PhoneFormatter>{data?.ins_ph1}</PhoneFormatter>
|
||||
@@ -27,14 +27,13 @@ export default function JobDetailCardsInsuranceComponent({ loading, data }) {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
TODO:
|
||||
{t("jobs.labels.cards.appraiser")}
|
||||
{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>
|
||||
) : (
|
||||
<div>{`${data?.ins_ct_fn ?? ""} ${data?.ins_ct_ln ?? ""}`}</div>
|
||||
<div>{`${data?.ins_ct_fn || ""} ${data?.ins_ct_ln || ""}`}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -42,10 +41,10 @@ export default function JobDetailCardsInsuranceComponent({ loading, data }) {
|
||||
{t("jobs.labels.cards.estimator")}
|
||||
{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>
|
||||
) : (
|
||||
<div>{`${data?.est_ct_fn ?? ""} ${data?.est_ct_ln ?? ""}`}</div>
|
||||
<div>{`${data?.est_ct_fn || ""} ${data?.est_ct_ln || ""}`}</div>
|
||||
)}
|
||||
{data?.est_ph1 ? (
|
||||
<PhoneFormatter>{data?.est_ph1}</PhoneFormatter>
|
||||
|
||||
@@ -13,9 +13,9 @@ export default function JobDetailCardsVehicleComponent({ loading, data }) {
|
||||
>
|
||||
{data ? (
|
||||
<span>
|
||||
{data.vehicle?.v_model_yr ?? t("general.labels.na")}{" "}
|
||||
{data.vehicle?.v_make_desc ?? t("general.labels.na")}{" "}
|
||||
{data.vehicle?.v_model_desc ?? t("general.labels.na")}
|
||||
{data.vehicle?.v_model_yr || t("general.labels.na")}{" "}
|
||||
{data.vehicle?.v_make_desc || t("general.labels.na")}{" "}
|
||||
{data.vehicle?.v_model_desc || t("general.labels.na")}
|
||||
</span>
|
||||
) : null}
|
||||
</CardTemplate>
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
}
|
||||
124
client/src/components/job-detail-lines/job-lines.component.jsx
Normal file
124
client/src/components/job-detail-lines/job-lines.component.jsx
Normal file
@@ -0,0 +1,124 @@
|
||||
import { Table, Button } from "antd";
|
||||
import React, { useContext, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import JobDetailFormContext from "../../pages/jobs-detail/jobs-detail.page.context";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
import EditableCell from "./job-lines-cell.component";
|
||||
|
||||
export default function JobLinesComponent({ job }) {
|
||||
//const form = useContext(JobDetailFormContext);
|
||||
//const { getFieldDecorator } = form;
|
||||
const [state, setState] = useState({
|
||||
sortedInfo: {},
|
||||
filteredInfo: { text: "" }
|
||||
});
|
||||
const [editingKey, setEditingKey] = useState("");
|
||||
const { t } = useTranslation();
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t("joblines.fields.unq_seq"),
|
||||
dataIndex: "joblines.unq_seq",
|
||||
key: "joblines.unq_seq",
|
||||
// onFilter: (value, record) => record.ro_number.includes(value),
|
||||
// filteredValue: state.filteredInfo.text || null,
|
||||
sorter: (a, b) => alphaSort(a, b),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "unq_seq" && state.sortedInfo.order,
|
||||
//ellipsis: true,
|
||||
editable: true
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.line_desc"),
|
||||
dataIndex: "line_desc",
|
||||
key: "joblines.line_desc",
|
||||
sorter: (a, b) => alphaSort(a.line_desc, b.line_desc),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order,
|
||||
ellipsis: true,
|
||||
editable: true
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.part_type"),
|
||||
dataIndex: "part_type",
|
||||
key: "joblines.part_type",
|
||||
sorter: (a, b) => alphaSort(a.part_type, b.part_type),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "part_type" && state.sortedInfo.order,
|
||||
ellipsis: true,
|
||||
editable: true
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.db_price"),
|
||||
dataIndex: "db_price",
|
||||
key: "joblines.db_price",
|
||||
sorter: (a, b) => a.db_price - b.db_price,
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "db_price" && state.sortedInfo.order,
|
||||
ellipsis: true,
|
||||
render: (text, record) => (
|
||||
<CurrencyFormatter>{record.db_price}</CurrencyFormatter>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.act_price"),
|
||||
dataIndex: "act_price",
|
||||
key: "joblines.act_price",
|
||||
sorter: (a, b) => a.act_price - b.act_price,
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "act_price" && state.sortedInfo.order,
|
||||
ellipsis: true,
|
||||
render: (text, record) => (
|
||||
<div>
|
||||
{" "}
|
||||
<CurrencyFormatter>{record.act_price}</CurrencyFormatter>{" "}
|
||||
<Button
|
||||
onClick={() => {
|
||||
setEditingKey(record.id);
|
||||
}}>
|
||||
EDIT
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||
};
|
||||
|
||||
// const handleChange = event => {
|
||||
// const { value } = event.target;
|
||||
// setState({ ...state, filterinfo: { text: [value] } });
|
||||
// };
|
||||
return (
|
||||
<Table
|
||||
size='small'
|
||||
pagination={{ position: "bottom" }}
|
||||
columns={columns.map(col => {
|
||||
if (!col.editable) {
|
||||
return col;
|
||||
}
|
||||
return {
|
||||
...col,
|
||||
onCell: record => ({
|
||||
record,
|
||||
inputType: col.dataIndex === "age" ? "number" : "text",
|
||||
dataIndex: col.dataIndex,
|
||||
title: col.title,
|
||||
editing: editingKey === record.id
|
||||
})
|
||||
};
|
||||
})}
|
||||
components={{
|
||||
body: {
|
||||
cell: EditableCell
|
||||
}
|
||||
}}
|
||||
rowKey='id'
|
||||
dataSource={job.joblines}
|
||||
onChange={handleTableChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
import React from "react";
|
||||
import JobLinesComponent from "./job-lines.component";
|
||||
import { useQuery } from "@apollo/react-hooks";
|
||||
import AlertComponent from "../../components/alert/alert.component";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
|
||||
import { GET_JOB_LINES_BY_PK } from "../../graphql/jobs-lines.queries";
|
||||
|
||||
export default function JobLinesContainer({ match }) {
|
||||
const { jobId } = match.params;
|
||||
export default function JobLinesContainer({ jobId }) {
|
||||
|
||||
const { loading, error, data } = useQuery(GET_JOB_LINES_BY_PK, {
|
||||
variables: { id: jobId },
|
||||
@@ -17,3 +16,4 @@ export default function JobLinesContainer({ match }) {
|
||||
<JobLinesComponent loading={loading} joblines={data ? data.joblines : null} />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
import { Table } from "antd";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
|
||||
export default function JobLinesComponent({ loading, joblines }) {
|
||||
const [state, setState] = useState({
|
||||
sortedInfo: {},
|
||||
filteredInfo: { text: "" }
|
||||
});
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: "Line #",
|
||||
dataIndex: "line_ind",
|
||||
key: "line_ind",
|
||||
// onFilter: (value, record) => record.ro_number.includes(value),
|
||||
// filteredValue: state.filteredInfo.text || null,
|
||||
sorter: (a, b) => alphaSort(a, b),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "line_ind" && state.sortedInfo.order,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: "Description",
|
||||
dataIndex: "line_desc",
|
||||
key: "line_desc",
|
||||
sorter: (a, b) => alphaSort(a, b),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order,
|
||||
ellipsis: true
|
||||
}
|
||||
];
|
||||
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||
};
|
||||
|
||||
// const handleChange = event => {
|
||||
// const { value } = event.target;
|
||||
// setState({ ...state, filterinfo: { text: [value] } });
|
||||
// };
|
||||
|
||||
return (
|
||||
<Table
|
||||
loading={loading}
|
||||
pagination={{ position: "bottom" }}
|
||||
columns={columns.map(item => ({ ...item }))}
|
||||
rowKey='id'
|
||||
dataSource={joblines}
|
||||
onChange={handleTableChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,217 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import {
|
||||
Form,
|
||||
Input,
|
||||
Row,
|
||||
Col,
|
||||
Button,
|
||||
Typography,
|
||||
PageHeader,
|
||||
Descriptions,
|
||||
Tag,
|
||||
notification,
|
||||
Avatar,
|
||||
Layout
|
||||
} from "antd";
|
||||
import { UPDATE_JOB, CONVERT_JOB_TO_RO } from "../../graphql/jobs.queries";
|
||||
import { useMutation } from "@apollo/react-hooks";
|
||||
import FormItemPhone from "../form-items-formatted/phone-form-item.component";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import CarImage from "../../assets/car.svg";
|
||||
|
||||
const { Content } = Layout;
|
||||
const formItemLayout = {
|
||||
// labelCol: {
|
||||
// xs: { span: 12 },
|
||||
// sm: { span: 5 }
|
||||
// },
|
||||
// wrapperCol: {
|
||||
// xs: { span: 24 },
|
||||
// sm: { span: 12 }
|
||||
// }
|
||||
};
|
||||
|
||||
function JobTombstone({ job, ...otherProps }) {
|
||||
const [jobContext, setJobContext] = useState(job);
|
||||
const [mutationUpdateJob] = useMutation(UPDATE_JOB);
|
||||
const [mutationConvertJob] = useMutation(CONVERT_JOB_TO_RO);
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!job) {
|
||||
return <AlertComponent message={t("jobs.errors.noaccess")} type='error' />;
|
||||
}
|
||||
|
||||
const handleSubmit = e => {
|
||||
e.preventDefault();
|
||||
|
||||
otherProps.form.validateFieldsAndScroll((err, values) => {
|
||||
if (err) {
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.validationtitle"),
|
||||
description: t("jobs.errors.validation")
|
||||
});
|
||||
}
|
||||
if (!err) {
|
||||
mutationUpdateJob({
|
||||
variables: { jobId: jobContext.id, job: values }
|
||||
}).then(r =>
|
||||
notification["success"]({
|
||||
message: t("jobs.successes.savetitle")
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleChange = event => {
|
||||
const { name, value } = event.target ? event.target : event;
|
||||
setJobContext({ ...jobContext, [name]: value });
|
||||
};
|
||||
|
||||
const { getFieldDecorator } = otherProps.form;
|
||||
|
||||
const tombstoneTitle = (
|
||||
<div>
|
||||
<Avatar size='large' alt='Vehicle Image' src={CarImage} />
|
||||
{`${t("jobs.fields.ro_number")} ${
|
||||
jobContext.ro_number ? jobContext.ro_number : t("general.labels.na")
|
||||
}`}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Content>
|
||||
<Form onSubmit={handleSubmit} {...formItemLayout}>
|
||||
<PageHeader
|
||||
style={{
|
||||
border: "1px solid rgb(235, 237, 240)"
|
||||
}}
|
||||
title={tombstoneTitle}
|
||||
subTitle={
|
||||
jobContext.owner
|
||||
? (jobContext.owner?.first_name ?? "") +
|
||||
" " +
|
||||
(jobContext.owner?.last_name ?? "")
|
||||
: t("jobs.errors.noowner")
|
||||
}
|
||||
tags={
|
||||
<span key='job-status'>
|
||||
{jobContext.job_status ? (
|
||||
<Tag color='blue'>{jobContext.job_status.name}</Tag>
|
||||
) : null}
|
||||
</span>
|
||||
}
|
||||
extra={[
|
||||
<Button
|
||||
key='convert'
|
||||
type='dashed'
|
||||
disabled={jobContext.converted}
|
||||
onClick={() => {
|
||||
mutationConvertJob({
|
||||
variables: { jobId: jobContext.id }
|
||||
}).then(r => {
|
||||
console.log("r", r);
|
||||
setJobContext({
|
||||
...jobContext,
|
||||
converted: true,
|
||||
ro_number: r.data.update_jobs.returning[0].ro_number
|
||||
});
|
||||
|
||||
notification["success"]({
|
||||
message: t("jobs.successes.converted")
|
||||
});
|
||||
});
|
||||
}}>
|
||||
{t("jobs.labels.convert")}
|
||||
</Button>,
|
||||
<Button type='primary' key='submit' htmlType='submit'>
|
||||
{t("general.labels.save")}
|
||||
</Button>
|
||||
]}>
|
||||
<Descriptions size='small' column={5}>
|
||||
<Descriptions.Item label={t("jobs.fields.vehicle")}>
|
||||
<Link to={`/manage/vehicles/${jobContext.vehicle?.id}`}>
|
||||
{jobContext.vehicle?.v_model_yr ?? t("general.labels.na")}{" "}
|
||||
{jobContext.vehicle?.v_make_desc ?? t("general.labels.na")}{" "}
|
||||
{jobContext.vehicle?.v_model_desc ?? t("general.labels.na")} |{" "}
|
||||
{jobContext.vehicle?.plate_no ?? t("general.labels.na")}
|
||||
</Link>
|
||||
</Descriptions.Item>
|
||||
|
||||
<Descriptions.Item label={t("jobs.fields.est_number")}>
|
||||
{jobContext.est_number}
|
||||
</Descriptions.Item>
|
||||
|
||||
<Descriptions.Item label={t("jobs.fields.claim_total")}>
|
||||
$ {jobContext.claim_total?.toFixed(2)}
|
||||
</Descriptions.Item>
|
||||
|
||||
<Descriptions.Item label={t("jobs.fields.deductible")}>
|
||||
$ {jobContext.deductible?.toFixed(2)}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
</PageHeader>
|
||||
|
||||
<Row>
|
||||
<Typography.Title level={4}>Information</Typography.Title>
|
||||
{
|
||||
// <Form.Item label='Estimate #'>
|
||||
// {getFieldDecorator("est_number", {
|
||||
// initialValue: jobContext.est_number
|
||||
// })(<Input name='est_number' readOnly onChange={handleChange} />)}
|
||||
// </Form.Item>
|
||||
}
|
||||
</Row>
|
||||
|
||||
<Row>
|
||||
<Typography.Title level={4}>Insurance Information</Typography.Title>
|
||||
<Form.Item label='Insurance Company'>
|
||||
{getFieldDecorator("est_co_nm", {
|
||||
initialValue: jobContext.est_co_nm
|
||||
})(<Input name='est_co_nm' onChange={handleChange} />)}
|
||||
</Form.Item>
|
||||
<Col span={8}>
|
||||
<Form.Item label='Estimator Last Name'>
|
||||
{getFieldDecorator("est_ct_ln", {
|
||||
initialValue: jobContext.est_ct_ln
|
||||
})(<Input name='est_ct_ln' onChange={handleChange} />)}
|
||||
</Form.Item>
|
||||
<Form.Item label='Estimator First Name'>
|
||||
{getFieldDecorator("est_ct_fn", {
|
||||
initialValue: jobContext.est_ct_fn
|
||||
})(<Input name='est_ct_fn' onChange={handleChange} />)}
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Form.Item label='Estimator Phone #'>
|
||||
{getFieldDecorator("est_ph1", {
|
||||
initialValue: jobContext.est_ph1
|
||||
})(
|
||||
<FormItemPhone
|
||||
customInput={Input}
|
||||
name='est_ph1'
|
||||
onValueChange={handleChange}
|
||||
/>
|
||||
)}
|
||||
</Form.Item>
|
||||
<Form.Item label='Estimator Email'>
|
||||
{getFieldDecorator("est_ea", {
|
||||
initialValue: jobContext.est_ea,
|
||||
rules: [
|
||||
{
|
||||
type: "email",
|
||||
message: "This is not a valid email address."
|
||||
}
|
||||
]
|
||||
})(<Input name='est_ea' onChange={handleChange} />)}
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form>
|
||||
</Content>
|
||||
);
|
||||
}
|
||||
|
||||
export default Form.create({ name: "JobTombstone" })(JobTombstone);
|
||||
@@ -0,0 +1,212 @@
|
||||
import { Button, Icon, Input, notification, Table } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
import OwnerFindModalContainer from "../owner-find-modal/owner-find-modal.container";
|
||||
export default function JobsAvailableComponent({
|
||||
loading,
|
||||
data,
|
||||
refetch,
|
||||
deleteJob,
|
||||
deleteAllNewJobs,
|
||||
onModalOk,
|
||||
onModalCancel,
|
||||
modalVisible,
|
||||
setModalVisible,
|
||||
selectedOwner,
|
||||
setSelectedOwner,
|
||||
loadEstData,
|
||||
estData
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [state, setState] = useState({
|
||||
sortedInfo: {},
|
||||
filteredInfo: { text: "" }
|
||||
});
|
||||
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t("jobs.fields.cieca_id"),
|
||||
dataIndex: "cieca_id",
|
||||
key: "cieca_id",
|
||||
//width: "8%",
|
||||
// onFilter: (value, record) => record.ro_number.includes(value),
|
||||
// filteredValue: state.filteredInfo.text || null,
|
||||
sorter: (a, b) => alphaSort(a, b),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "cieca_id" && state.sortedInfo.order
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.owner"),
|
||||
dataIndex: "ownr_name",
|
||||
key: "ownr_name",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
|
||||
//width: "25%",
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "ownr_name" && state.sortedInfo.order
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.vehicle"),
|
||||
dataIndex: "vehicle_info",
|
||||
key: "vehicle_info",
|
||||
sorter: (a, b) => alphaSort(a.vehicle_info, b.vehicle_info),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "vehicle_info" && state.sortedInfo.order
|
||||
//ellipsis: true
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.clm_no"),
|
||||
dataIndex: "clm_no",
|
||||
key: "clm_no",
|
||||
sorter: (a, b) => alphaSort(a.clm_no, b.clm_no),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order
|
||||
//width: "12%",
|
||||
//ellipsis: true
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.clm_total"),
|
||||
dataIndex: "clm_amt",
|
||||
key: "clm_amt",
|
||||
sorter: (a, b) => a.clm_amt - b.clm_amt,
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "clm_amt" && state.sortedInfo.order
|
||||
//width: "12%",
|
||||
//ellipsis: true
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.uploaded_by"),
|
||||
dataIndex: "uploaded_by",
|
||||
key: "uploaded_by",
|
||||
sorter: (a, b) => alphaSort(a.uploaded_by, b.uploaded_by),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "uploaded_by" && state.sortedInfo.order
|
||||
//width: "12%",
|
||||
//ellipsis: true
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.updated_at"),
|
||||
dataIndex: "updated_at",
|
||||
key: "updated_at",
|
||||
sorter: (a, b) => new Date(a.updated_at) - new Date(b.updated_at),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "updated_at" && state.sortedInfo.order,
|
||||
render: (text, record) => (
|
||||
<DateTimeFormatter>{record.updated_at}</DateTimeFormatter>
|
||||
)
|
||||
//width: "12%",
|
||||
//ellipsis: true
|
||||
},
|
||||
{
|
||||
title: t("general.labels.actions"),
|
||||
key: "actions",
|
||||
render: (text, record) => (
|
||||
<span>
|
||||
<Button
|
||||
onClick={() => {
|
||||
deleteJob({ variables: { id: record.id } }).then(r => {
|
||||
notification["success"]({
|
||||
message: t("jobs.successes.deleted")
|
||||
});
|
||||
refetch();
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Icon type="delete" />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
loadEstData({ variables: { id: record.id } });
|
||||
setModalVisible(true);
|
||||
}}
|
||||
>
|
||||
<Icon type="plus" />
|
||||
</Button>
|
||||
</span>
|
||||
)
|
||||
//width: "12%",
|
||||
//ellipsis: true
|
||||
}
|
||||
];
|
||||
|
||||
const owner =
|
||||
estData.data &&
|
||||
estData.data.available_jobs_by_pk &&
|
||||
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.data
|
||||
? estData.data.available_jobs_by_pk.est_data.owner.data
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<OwnerFindModalContainer
|
||||
loading={estData.loading}
|
||||
error={estData.error}
|
||||
owner={owner}
|
||||
selectedOwner={selectedOwner}
|
||||
setSelectedOwner={setSelectedOwner}
|
||||
visible={modalVisible}
|
||||
onOk={onModalOk}
|
||||
onCancel={onModalCancel}
|
||||
/>
|
||||
|
||||
<Table
|
||||
loading={loading}
|
||||
title={() => {
|
||||
return (
|
||||
<div>
|
||||
<Input.Search
|
||||
placeholder="Search..."
|
||||
onSearch={value => {
|
||||
console.log(value);
|
||||
}}
|
||||
enterButton
|
||||
/>
|
||||
<Button
|
||||
onClick={() => {
|
||||
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
|
||||
});
|
||||
});
|
||||
}}
|
||||
>
|
||||
Delete All
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
size="small"
|
||||
pagination={{ position: "top" }}
|
||||
columns={columns.map(item => ({ ...item }))}
|
||||
rowKey="id"
|
||||
dataSource={data && data.available_jobs}
|
||||
onChange={handleTableChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
import { notification } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useMutation, useQuery } from "react-apollo";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import { DELETE_ALL_AVAILABLE_NEW_JOBS, QUERY_AVAILABLE_NEW_JOBS } from "../../graphql/available-jobs.queries";
|
||||
import { INSERT_NEW_JOB } from "../../graphql/jobs.queries";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||
import JobsAvailableComponent from "./jobs-available-new.component";
|
||||
|
||||
export default withRouter(function JobsAvailableContainer({
|
||||
deleteJob,
|
||||
estDataLazyLoad,
|
||||
history
|
||||
}) {
|
||||
const { loading, error, data, refetch } = useQuery(QUERY_AVAILABLE_NEW_JOBS, {
|
||||
fetchPolicy: "network-only"
|
||||
});
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [selectedOwner, setSelectedOwner] = useState(null);
|
||||
const [insertLoading, setInsertLoading] = useState(false);
|
||||
const [deleteAllNewJobs] = useMutation(DELETE_ALL_AVAILABLE_NEW_JOBS);
|
||||
const [insertNewJob] = useMutation(INSERT_NEW_JOB);
|
||||
const [loadEstData, estData] = estDataLazyLoad;
|
||||
|
||||
const onModalOk = () => {
|
||||
setModalVisible(false);
|
||||
console.log("selectedOwner", selectedOwner);
|
||||
setInsertLoading(true);
|
||||
console.log(
|
||||
"logitest",
|
||||
estData.data &&
|
||||
estData.data.available_jobs_by_pk &&
|
||||
estData.data.available_jobs_by_pk.est_data
|
||||
);
|
||||
|
||||
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 {
|
||||
insertNewJob({
|
||||
variables: {
|
||||
job: selectedOwner
|
||||
? Object.assign(
|
||||
{},
|
||||
estData.data.available_jobs_by_pk.est_data,
|
||||
{ owner: null },
|
||||
{ ownerid: selectedOwner }
|
||||
)
|
||||
: estData.data.available_jobs_by_pk.est_data
|
||||
}
|
||||
})
|
||||
.then(r => {
|
||||
notification["success"]({
|
||||
message: t("jobs.successes.created"),
|
||||
onClick: () => {
|
||||
console.log("r", r);
|
||||
history.push(
|
||||
`/manage/jobs/${r.data.insert_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);
|
||||
setSelectedOwner(null);
|
||||
};
|
||||
|
||||
if (error) return <AlertComponent type="error" message={error.message} />;
|
||||
return (
|
||||
<LoadingSpinner
|
||||
loading={insertLoading}
|
||||
message={t("jobs.labels.creating_new_job")}
|
||||
>
|
||||
<JobsAvailableComponent
|
||||
loading={loading}
|
||||
data={data}
|
||||
refetch={refetch}
|
||||
deleteJob={deleteJob}
|
||||
deleteAllNewJobs={deleteAllNewJobs}
|
||||
insertNewJob={insertNewJob}
|
||||
estDataLazyLoad={estDataLazyLoad}
|
||||
onModalCancel={onModalCancel}
|
||||
onModalOk={onModalOk}
|
||||
modalVisible={modalVisible}
|
||||
setModalVisible={setModalVisible}
|
||||
selectedOwner={selectedOwner}
|
||||
setSelectedOwner={setSelectedOwner}
|
||||
loadEstData={loadEstData}
|
||||
estData={estData}
|
||||
/>
|
||||
</LoadingSpinner>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,193 @@
|
||||
import { Input, Table, Button, Icon, notification } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
export default function JobsAvailableSupplementComponent({
|
||||
loading,
|
||||
data,
|
||||
refetch,
|
||||
deleteJob,
|
||||
deleteAllNewJobs,
|
||||
estDataLazyLoad
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [state, setState] = useState({
|
||||
sortedInfo: {},
|
||||
filteredInfo: { text: "" }
|
||||
});
|
||||
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t("jobs.fields.cieca_id"),
|
||||
dataIndex: "cieca_id",
|
||||
key: "cieca_id",
|
||||
//width: "8%",
|
||||
// onFilter: (value, record) => record.ro_number.includes(value),
|
||||
// filteredValue: state.filteredInfo.text || null,
|
||||
sorter: (a, b) => alphaSort(a, b),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "cieca_id" && state.sortedInfo.order
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ro_number"),
|
||||
dataIndex: "job_id",
|
||||
key: "job_id",
|
||||
//width: "8%",
|
||||
// onFilter: (value, record) => record.ro_number.includes(value),
|
||||
// filteredValue: state.filteredInfo.text || null,
|
||||
sorter: (a, b) => alphaSort(a, b),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "cieca_id" && state.sortedInfo.order,
|
||||
render: (text, record) => <div>{record.job && record.job.ro_number}</div>
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.owner"),
|
||||
dataIndex: "ownr_name",
|
||||
key: "ownr_name",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
|
||||
//width: "25%",
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "ownr_name" && state.sortedInfo.order
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.vehicle"),
|
||||
dataIndex: "vehicle_info",
|
||||
key: "vehicle_info",
|
||||
sorter: (a, b) => alphaSort(a.vehicle_info, b.vehicle_info),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "vehicle_info" && state.sortedInfo.order
|
||||
//ellipsis: true
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.clm_no"),
|
||||
dataIndex: "clm_no",
|
||||
key: "clm_no",
|
||||
sorter: (a, b) => alphaSort(a.clm_no, b.clm_no),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order
|
||||
//width: "12%",
|
||||
//ellipsis: true
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.clm_total"),
|
||||
dataIndex: "clm_amt",
|
||||
key: "clm_amt",
|
||||
sorter: (a, b) => a.clm_amt - b.clm_amt,
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "clm_amt" && state.sortedInfo.order
|
||||
//width: "12%",
|
||||
//ellipsis: true
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.uploaded_by"),
|
||||
dataIndex: "uploaded_by",
|
||||
key: "uploaded_by",
|
||||
sorter: (a, b) => alphaSort(a.uploaded_by, b.uploaded_by),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "uploaded_by" && state.sortedInfo.order
|
||||
//width: "12%",
|
||||
//ellipsis: true
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.updated_at"),
|
||||
dataIndex: "updated_at",
|
||||
key: "updated_at",
|
||||
sorter: (a, b) => new Date(a.updated_at) - new Date(b.updated_at),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "updated_at" && state.sortedInfo.order,
|
||||
render: (text, record) => (
|
||||
<DateTimeFormatter>{record.updated_at}</DateTimeFormatter>
|
||||
)
|
||||
//width: "12%",
|
||||
//ellipsis: true
|
||||
},
|
||||
{
|
||||
title: t("general.labels.actions"),
|
||||
key: "actions",
|
||||
render: (text, record) => (
|
||||
<span>
|
||||
<Button
|
||||
onClick={() => {
|
||||
deleteJob({ variables: { id: record.id } }).then(r => {
|
||||
notification["success"]({
|
||||
message: t("jobs.successes.deleted")
|
||||
});
|
||||
refetch();
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Icon type="delete" />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
alert("Add");
|
||||
}}
|
||||
>
|
||||
<Icon type="plus" />
|
||||
</Button>
|
||||
</span>
|
||||
)
|
||||
//width: "12%",
|
||||
//ellipsis: true
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<Table
|
||||
loading={loading}
|
||||
title={() => {
|
||||
return (
|
||||
<div>
|
||||
<Input.Search
|
||||
placeholder="Search..."
|
||||
onSearch={value => {
|
||||
console.log(value);
|
||||
}}
|
||||
enterButton
|
||||
/>
|
||||
<Button
|
||||
onClick={() => {
|
||||
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
|
||||
});
|
||||
});
|
||||
}}
|
||||
>
|
||||
Delete All
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
size="small"
|
||||
pagination={{ position: "top" }}
|
||||
columns={columns.map(item => ({ ...item }))}
|
||||
rowKey="id"
|
||||
dataSource={data && data.available_jobs}
|
||||
onChange={handleTableChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import React from "react";
|
||||
import { useMutation, useQuery } from "react-apollo";
|
||||
import { DELETE_ALL_AVAILABLE_SUPPLEMENT_JOBS, QUERY_AVAILABLE_SUPPLEMENT_JOBS } from "../../graphql/available-jobs.queries";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import JobsAvailableSupplementComponent from "./jobs-available-supplement.component";
|
||||
|
||||
export default function JobsAvailableSupplementContainer({
|
||||
deleteJob,
|
||||
estDataLazyLoad
|
||||
}) {
|
||||
const { loading, error, data, refetch } = useQuery(
|
||||
QUERY_AVAILABLE_SUPPLEMENT_JOBS,
|
||||
{
|
||||
fetchPolicy: "network-only"
|
||||
}
|
||||
);
|
||||
const [deleteAllNewJobs] = useMutation(DELETE_ALL_AVAILABLE_SUPPLEMENT_JOBS);
|
||||
|
||||
if (error) return <AlertComponent type="error" message={error.message} />;
|
||||
return (
|
||||
<JobsAvailableSupplementComponent
|
||||
loading={loading}
|
||||
data={data}
|
||||
refetch={refetch}
|
||||
deleteJob={deleteJob}
|
||||
deleteAllNewJobs={deleteAllNewJobs}
|
||||
estDataLazyLoad={estDataLazyLoad}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import { Form, Input, Switch } from "antd";
|
||||
import React, { useContext } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import JobDetailFormContext from "../../pages/jobs-detail/jobs-detail.page.context";
|
||||
|
||||
export default function JobsDetailClaims({ job }) {
|
||||
const form = useContext(JobDetailFormContext);
|
||||
const { getFieldDecorator } = form;
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Form.Item label={t("jobs.fields.csr")}>
|
||||
{getFieldDecorator("csr", {
|
||||
initialValue: job.csr
|
||||
})(<Input name='csr' />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.loss_desc")}>
|
||||
{getFieldDecorator("loss_desc", {
|
||||
initialValue: job.loss_desc
|
||||
})(<Input name='loss_desc' />)}
|
||||
</Form.Item>
|
||||
TODO: How to handle different taxes and marking them as exempt?
|
||||
{
|
||||
// <Form.Item label={t("jobs.fields.exempt")}>
|
||||
// {getFieldDecorator("exempt", {
|
||||
// initialValue: job.exempt
|
||||
// })(<Input name='exempt' />)}
|
||||
// </Form.Item>
|
||||
}
|
||||
<Form.Item label={t("jobs.fields.ponumber")}>
|
||||
{getFieldDecorator("po_number", {
|
||||
initialValue: job.po_number
|
||||
})(<Input name='po_number' />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.unitnumber")}>
|
||||
{getFieldDecorator("unit_number", {
|
||||
initialValue: job.unit_number
|
||||
})(<Input name='unit_number' />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.specialcoveragepolicy")}>
|
||||
{getFieldDecorator("special_coverage_policy", {
|
||||
initialValue: job.special_coverage_policy,
|
||||
valuePropName: "checked"
|
||||
})(<Switch name='special_coverage_policy' />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.kmin")}>
|
||||
{getFieldDecorator("kmin", {
|
||||
initialValue: job.kmin
|
||||
})(<Input name='kmin' />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.kmout")}>
|
||||
{getFieldDecorator("kmout", {
|
||||
initialValue: job.kmout
|
||||
})(<Input name='kmout' />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.referralsource")}>
|
||||
{getFieldDecorator("referral_source", {
|
||||
initialValue: job.referral_source
|
||||
})(<Input name='referral_source' />)}
|
||||
</Form.Item>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { Form, Input, InputNumber } from "antd";
|
||||
import React, { useContext } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import JobDetailFormContext from "../../pages/jobs-detail/jobs-detail.page.context";
|
||||
|
||||
export default function JobsDetailFinancials({ job }) {
|
||||
const form = useContext(JobDetailFormContext);
|
||||
const { getFieldDecorator } = form;
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Form.Item label={t("jobs.fields.ded_amt")}>
|
||||
{getFieldDecorator("ded_amt", {
|
||||
initialValue: job.ded_amt
|
||||
})(<InputNumber name="ded_amt" />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.ded_status")}>
|
||||
{getFieldDecorator("ded_status", {
|
||||
initialValue: job.ded_status
|
||||
})(<Input name="ded_status" />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.depreciation_taxes")}>
|
||||
{getFieldDecorator("depreciation_taxes", {
|
||||
initialValue: job.depreciation_taxes
|
||||
})(<InputNumber name="depreciation_taxes" />)}
|
||||
</Form.Item>
|
||||
TODO: This is equivalent of GST payable.
|
||||
<Form.Item label={t("jobs.fields.federal_tax_payable")}>
|
||||
{getFieldDecorator("federal_tax_payable", {
|
||||
initialValue: job.federal_tax_payable
|
||||
})(<InputNumber name="federal_tax_payable" />)}
|
||||
</Form.Item>
|
||||
TODO: equivalent of other customer amount
|
||||
<Form.Item label={t("jobs.fields.other_amount_payable")}>
|
||||
{getFieldDecorator("other_amount_payable", {
|
||||
initialValue: job.other_amount_payable
|
||||
})(<InputNumber name="other_amount_payable" />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.towing_payable")}>
|
||||
{getFieldDecorator("towing_payable", {
|
||||
initialValue: job.towing_payable
|
||||
})(<InputNumber name="towing_payable" />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.storage_payable")}>
|
||||
{getFieldDecorator("storage_payable", {
|
||||
initialValue: job.storage_payable
|
||||
})(<InputNumber name="storage_payable" />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.adjustment_bottom_line")}>
|
||||
{getFieldDecorator("adjustment_bottom_line", {
|
||||
initialValue: job.adjustment_bottom_line
|
||||
})(<InputNumber name="adjustment_bottom_line" />)}
|
||||
</Form.Item>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
Checkbox,
|
||||
Descriptions,
|
||||
notification,
|
||||
PageHeader,
|
||||
Tag
|
||||
} from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Moment from "react-moment";
|
||||
import { Link } from "react-router-dom";
|
||||
import CarImage from "../../assets/car.svg";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
|
||||
export default function JobsDetailHeader({
|
||||
job,
|
||||
mutationConvertJob,
|
||||
refetch,
|
||||
getFieldDecorator
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const tombstoneTitle = (
|
||||
<div>
|
||||
<Avatar size="large" alt="Vehicle Image" src={CarImage} />
|
||||
{`${t("jobs.fields.ro_number")} ${
|
||||
job.ro_number ? job.ro_number : t("general.labels.na")
|
||||
}`}
|
||||
</div>
|
||||
);
|
||||
|
||||
const tombstoneSubtitle = (
|
||||
<div>
|
||||
<Tag color="red">
|
||||
{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>
|
||||
</div>
|
||||
);
|
||||
|
||||
const menuExtra = [
|
||||
<Button
|
||||
key="convert"
|
||||
type="dashed"
|
||||
disabled={job.converted}
|
||||
onClick={() => {
|
||||
mutationConvertJob({
|
||||
variables: { jobId: job.id }
|
||||
}).then(r => {
|
||||
refetch();
|
||||
|
||||
notification["success"]({
|
||||
message: t("jobs.successes.converted")
|
||||
});
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t("jobs.actions.convert")}
|
||||
</Button>,
|
||||
<Button type="primary" key="submit" htmlType="submit">
|
||||
{t("general.labels.save")}
|
||||
</Button>
|
||||
];
|
||||
|
||||
return (
|
||||
<PageHeader
|
||||
style={{
|
||||
border: "1px solid rgb(235, 237, 240)"
|
||||
}}
|
||||
title={tombstoneTitle}
|
||||
subTitle={tombstoneSubtitle}
|
||||
tags={
|
||||
<span key="job-status">
|
||||
{job.job_status ? (
|
||||
<Tag color="blue">{job.job_status.name}</Tag>
|
||||
) : null}
|
||||
</span>
|
||||
}
|
||||
extra={menuExtra}
|
||||
>
|
||||
<Descriptions size="small" column={5}>
|
||||
<Descriptions.Item label={t("jobs.fields.repairtotal")}>
|
||||
<CurrencyFormatter>{job.claim_total}</CurrencyFormatter>
|
||||
</Descriptions.Item>
|
||||
|
||||
<Descriptions.Item label={t("jobs.fields.customerowing")}>
|
||||
##NO BINDING YET##
|
||||
</Descriptions.Item>
|
||||
|
||||
<Descriptions.Item label={t("jobs.fields.specialcoveragepolicy")}>
|
||||
<Checkbox checked={job.special_coverage_policy} />
|
||||
</Descriptions.Item>
|
||||
|
||||
<Descriptions.Item label={t("jobs.fields.scheduled_completion")}>
|
||||
{job.scheduled_completion ? (
|
||||
<Moment format="MM/DD/YYYY">{job.scheduled_completion}</Moment>
|
||||
) : null}
|
||||
</Descriptions.Item>
|
||||
|
||||
<Descriptions.Item label={t("jobs.fields.servicecar")}>
|
||||
{job.service_car}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
</PageHeader>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
import { Divider, Form, Input, DatePicker } from "antd";
|
||||
import React, { useContext } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import JobDetailFormContext from "../../pages/jobs-detail/jobs-detail.page.context";
|
||||
import FormItemEmail from "../form-items-formatted/email-form-item.component";
|
||||
import FormItemPhone from "../form-items-formatted/phone-form-item.component";
|
||||
import moment from "moment";
|
||||
|
||||
export default function JobsDetailInsurance({ job }) {
|
||||
const form = useContext(JobDetailFormContext);
|
||||
const { getFieldDecorator, getFieldValue } = form;
|
||||
const { t } = useTranslation();
|
||||
|
||||
console.log("job.loss_date", job.loss_date);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Form.Item label={t("jobs.fields.ins_co_id")}>
|
||||
{getFieldDecorator("ins_co_id", {
|
||||
initialValue: job.ins_co_id
|
||||
})(<Input name="ins_co_id" />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.policy_no")}>
|
||||
{getFieldDecorator("policy_no", {
|
||||
initialValue: job.policy_no
|
||||
})(<Input name="policy_no" />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.clm_no")}>
|
||||
{getFieldDecorator("clm_no", {
|
||||
initialValue: job.clm_no
|
||||
})(<Input name="clm_no" />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.regie_number")}>
|
||||
{getFieldDecorator("regie_number", {
|
||||
initialValue: job.regie_number
|
||||
})(<Input name="regie_number" />)}
|
||||
</Form.Item>
|
||||
TODO: missing KOL field???
|
||||
<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.ins_co_nm")}>
|
||||
{getFieldDecorator("ins_co_nm", {
|
||||
initialValue: job.ins_co_nm
|
||||
})(<Input name="ins_co_nm" />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.ins_addr1")}>
|
||||
{getFieldDecorator("ins_addr1", {
|
||||
initialValue: job.ins_addr1
|
||||
})(<Input name="ins_addr1" />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.ins_city")}>
|
||||
{getFieldDecorator("ins_city", {
|
||||
initialValue: job.ins_city
|
||||
})(<Input name="ins_city" />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.ins_ct_ln")}>
|
||||
{getFieldDecorator("ins_ct_ln", {
|
||||
initialValue: job.ins_ct_ln
|
||||
})(<Input name="ins_ct_ln" />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.ins_ct_fn")}>
|
||||
{getFieldDecorator("ins_ct_fn", {
|
||||
initialValue: job.ins_ct_fn
|
||||
})(<Input name="ins_ct_fn" />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.ins_ph1")}>
|
||||
{getFieldDecorator("ins_ph1", {
|
||||
initialValue: job.ins_ph1
|
||||
})(<FormItemPhone customInput={Input} name="ins_ph1" />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.ins_ea")}>
|
||||
{getFieldDecorator("ins_ea", {
|
||||
initialValue: job.ins_ea,
|
||||
rules: [
|
||||
{
|
||||
type: "email",
|
||||
message: "This is not a valid email address."
|
||||
}
|
||||
]
|
||||
})(<FormItemEmail name="ins_ea" email={getFieldValue("ins_ea")} />)}
|
||||
</Form.Item>
|
||||
<Divider />
|
||||
Appraiser Info
|
||||
<Form.Item label={t("jobs.fields.est_co_nm")}>
|
||||
{getFieldDecorator("est_co_nm", {
|
||||
initialValue: job.est_co_nm
|
||||
})(<Input name="est_co_nm" />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.est_ct_fn")}>
|
||||
{getFieldDecorator("est_ct_fn", {
|
||||
initialValue: job.est_ct_fn
|
||||
})(<Input name="est_ct_fn" />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.est_ct_ln")}>
|
||||
{getFieldDecorator("est_ct_ln", {
|
||||
initialValue: job.est_ct_ln
|
||||
})(<Input name="est_ct_ln" />)}
|
||||
</Form.Item>
|
||||
TODO: Field is pay date but title is inspection date. Likely incorrect?
|
||||
<Form.Item label={t("jobs.fields.pay_date")}>
|
||||
{getFieldDecorator("pay_date", {
|
||||
initialValue: job.pay_date
|
||||
})(<Input name="pay_date" />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.est_ph1")}>
|
||||
{getFieldDecorator("est_ph1", {
|
||||
initialValue: job.est_ph1
|
||||
})(<Input name="est_ph1" />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.est_ea")}>
|
||||
{getFieldDecorator("est_ea", {
|
||||
initialValue: job.est_ea,
|
||||
rules: [
|
||||
{
|
||||
type: "email",
|
||||
message: "This is not a valid email address."
|
||||
}
|
||||
]
|
||||
})(<FormItemEmail name="est_ea" email={getFieldValue("est_ea")} />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.selling_dealer")}>
|
||||
{getFieldDecorator("selling_dealer", {
|
||||
initialValue: job.selling_dealer
|
||||
})(<Input name="selling_dealer" />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.servicing_dealer")}>
|
||||
{getFieldDecorator("servicing_dealer", {
|
||||
initialValue: job.servicing_dealer
|
||||
})(<Input name="servicing_dealer" />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.selling_dealer_contact")}>
|
||||
{getFieldDecorator("selling_dealer_contact", {
|
||||
initialValue: job.selling_dealer_contact
|
||||
})(<Input name="selling_dealer_contact" />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.servicing_dealer_contact")}>
|
||||
{getFieldDecorator("servicing_dealer_contact", {
|
||||
initialValue: job.servicing_dealer_contact
|
||||
})(<Input name="servicing_dealer_contact" />)}
|
||||
</Form.Item>
|
||||
TODO: Adding servicing/selling dealer contact info?
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
import { Icon, Modal, notification, Upload } from "antd";
|
||||
import axios from "axios";
|
||||
import React, { useState } from "react";
|
||||
import { useMutation } from "react-apollo";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Resizer from "react-image-file-resizer";
|
||||
import {
|
||||
INSERT_NEW_DOCUMENT,
|
||||
DELETE_DOCUMENT
|
||||
} from "../../graphql/documents.queries";
|
||||
import "./jobs-documents.styles.scss";
|
||||
import { generateCdnThumb } from "../../utils/DocHelpers";
|
||||
|
||||
function getBase64(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
reader.onload = () => resolve(reader.result);
|
||||
reader.onerror = error => reject(error);
|
||||
});
|
||||
}
|
||||
|
||||
function JobsDocumentsComponent({ shopId, jobId, loading, data, currentUser }) {
|
||||
const { t } = useTranslation();
|
||||
const [insertNewDocument] = useMutation(INSERT_NEW_DOCUMENT);
|
||||
const [deleteDocument] = useMutation(DELETE_DOCUMENT);
|
||||
|
||||
const [state, setState] = useState({
|
||||
previewVisible: false,
|
||||
previewImage: ""
|
||||
});
|
||||
|
||||
const [fileList, setFileList] = useState(
|
||||
data.reduce((acc, value) => {
|
||||
acc.push({
|
||||
uid: value.id,
|
||||
url: value.thumb_url,
|
||||
name: value.name,
|
||||
status: "done",
|
||||
full_url: value.url,
|
||||
key: value.key
|
||||
});
|
||||
return acc;
|
||||
}, [])
|
||||
);
|
||||
|
||||
const uploadToS3 = (
|
||||
fileName,
|
||||
fileType,
|
||||
file,
|
||||
onError,
|
||||
onSuccess,
|
||||
onProgress
|
||||
) => {
|
||||
axios
|
||||
.post("/sign_s3", {
|
||||
fileName,
|
||||
fileType
|
||||
})
|
||||
.then(response => {
|
||||
var returnData = response.data.data.returnData;
|
||||
var signedRequest = returnData.signedRequest;
|
||||
var url = returnData.url;
|
||||
setState({ ...state, url: url });
|
||||
// Put the fileType in the headers for the upload
|
||||
var options = {
|
||||
headers: {
|
||||
"Content-Type": fileType
|
||||
},
|
||||
onUploadProgress: e => {
|
||||
onProgress({ percent: (e.loaded / e.total) * 100 });
|
||||
}
|
||||
};
|
||||
|
||||
axios
|
||||
.put(signedRequest, file, options)
|
||||
.then(response => {
|
||||
console.log("response from axios", response);
|
||||
insertNewDocument({
|
||||
variables: {
|
||||
docInput: [
|
||||
{
|
||||
jobid: jobId,
|
||||
uploaded_by: currentUser.email,
|
||||
url,
|
||||
thumb_url: generateCdnThumb(fileName),
|
||||
key: fileName
|
||||
}
|
||||
]
|
||||
}
|
||||
}).then(r => {
|
||||
onSuccess({
|
||||
uid: r.data.insert_documents.returning[0].id,
|
||||
url: r.data.insert_documents.returning[0].thumb_url,
|
||||
name: r.data.insert_documents.returning[0].name,
|
||||
status: "done",
|
||||
full_url: r.data.insert_documents.returning[0].url,
|
||||
key: r.data.insert_documents.returning[0].key
|
||||
});
|
||||
notification["success"]({
|
||||
message: t("documents.successes.insert")
|
||||
});
|
||||
});
|
||||
|
||||
setState({ ...state, success: true });
|
||||
})
|
||||
.catch(error => {
|
||||
console.log("Error uploading to S3", error);
|
||||
onError(error);
|
||||
notification["error"]({
|
||||
message: t("documents.errors.insert") + JSON.stringify(error)
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.log("Outside Error here.", error);
|
||||
notification["error"]({
|
||||
message: t("documents.errors.getpresignurl") + JSON.stringify(error)
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleUpload = ev => {
|
||||
const { onError, onSuccess, onProgress } = ev;
|
||||
//If PDF, upload directly.
|
||||
//If JPEG, resize and upload.
|
||||
let key = `${shopId}/${jobId}/${ev.file.name}`;
|
||||
if (ev.file.type === "application/pdf") {
|
||||
console.log("It's a PDF.");
|
||||
uploadToS3(key, ev.file.type, ev.file, onError, onSuccess, onProgress);
|
||||
} else {
|
||||
Resizer.imageFileResizer(
|
||||
ev.file,
|
||||
3000,
|
||||
3000,
|
||||
"JPEG",
|
||||
75,
|
||||
0,
|
||||
uri => {
|
||||
let file = new File([uri], ev.file.name, {});
|
||||
file.uid = ev.file.uid;
|
||||
uploadToS3(key, file.type, file, onError, onSuccess, onProgress);
|
||||
},
|
||||
"blob"
|
||||
);
|
||||
}
|
||||
};
|
||||
const handleCancel = () => setState({ ...state, previewVisible: false });
|
||||
|
||||
const handlePreview = async file => {
|
||||
if (!file.full_url && !file.url) {
|
||||
file.preview = await getBase64(file.originFileObj);
|
||||
}
|
||||
|
||||
setState({
|
||||
...state,
|
||||
previewImage: file.full_url || file.url,
|
||||
previewVisible: true
|
||||
});
|
||||
};
|
||||
const handleChange = props => {
|
||||
const { event, fileList, file } = props;
|
||||
//Required to ensure that the state accurately reflects new data and that images can be deleted in feeded.
|
||||
if (!event) {
|
||||
//SPread the new file in where the old one was.
|
||||
const newFileList = fileList.map(i =>
|
||||
i.uid === file.uid ? Object.assign({}, i, file.response) : i
|
||||
);
|
||||
setFileList(newFileList);
|
||||
} else {
|
||||
setFileList(fileList);
|
||||
}
|
||||
};
|
||||
|
||||
const { previewVisible, previewImage } = state;
|
||||
|
||||
const handleRemove = file => {
|
||||
console.log("file", file);
|
||||
|
||||
//Remove the file on S3
|
||||
axios
|
||||
.post("/delete_s3", { fileName: file.key })
|
||||
.then(response => {
|
||||
//Delete the record in our database.
|
||||
if (response.status === 200) {
|
||||
deleteDocument({ variables: { id: file.uid } }).then(r => {
|
||||
notification["success"]({
|
||||
message: t("documents.successes.delete")
|
||||
});
|
||||
});
|
||||
} else {
|
||||
notification["error"]({
|
||||
message:
|
||||
1 +
|
||||
t("documents.errors.deletes3") +
|
||||
JSON.stringify(response.message)
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
notification["error"]({
|
||||
message: "2" + t("documents.errors.deletes3") + JSON.stringify(error)
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='clearfix'>
|
||||
<button
|
||||
onClick={() => {
|
||||
const imageRequest = JSON.stringify({
|
||||
bucket: process.env.REACT_APP_S3_BUCKET,
|
||||
key:
|
||||
"52b7357c-0edd-4c95-85c3-dfdbcdfad9ac/c8ca5761-681a-4bb3-ab76-34c447357be3/Invoice_353284489.pdf",
|
||||
edits: { format: "jpeg" }
|
||||
});
|
||||
const CloudFrontUrl = "https://d18fc493a0fm4o.cloudfront.net";
|
||||
const url = `${CloudFrontUrl}/${btoa(imageRequest)}`;
|
||||
console.log("url", url);
|
||||
}}>
|
||||
Test PDF
|
||||
</button>
|
||||
<Upload.Dragger
|
||||
customRequest={handleUpload}
|
||||
accept='.pdf,.jpg,.jpeg'
|
||||
listType='picture-card'
|
||||
fileList={fileList}
|
||||
multiple={true}
|
||||
onPreview={handlePreview}
|
||||
onRemove={handleRemove}
|
||||
onChange={handleChange}>
|
||||
<p className='ant-upload-drag-icon'>
|
||||
<Icon type='inbox' />
|
||||
</p>
|
||||
<p className='ant-upload-text'>
|
||||
Click or drag file to this area to upload
|
||||
</p>
|
||||
<p className='ant-upload-hint'>
|
||||
Support for a single or bulk upload. Strictly prohibit from uploading
|
||||
company data or other band files
|
||||
</p>
|
||||
</Upload.Dragger>
|
||||
<Modal visible={previewVisible} footer={null} onCancel={handleCancel}>
|
||||
<img alt='example' style={{ width: "100%" }} src={previewImage} />
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default JobsDocumentsComponent;
|
||||
@@ -4,7 +4,8 @@ import { QUERY_SHOP_ID } from "../../graphql/bodyshop.queries";
|
||||
import { GET_DOCUMENTS_BY_JOB } from "../../graphql/documents.queries";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||
import JobDocuments from "./jobs-documents.page";
|
||||
import JobDocuments from "./jobs-documents.component";
|
||||
import { GET_CURRENT_USER } from "../../graphql/local.queries";
|
||||
|
||||
export default function JobsDocumentsContainer({ jobId }) {
|
||||
const { loading, error, data } = useQuery(GET_DOCUMENTS_BY_JOB, {
|
||||
@@ -16,15 +17,22 @@ export default function JobsDocumentsContainer({ jobId }) {
|
||||
fetchPolicy: "network-only"
|
||||
});
|
||||
|
||||
if (loading || shopData.loading) return <LoadingSpinner />;
|
||||
if (error) return <AlertComponent type='error' message={error.message} />;
|
||||
if (shopData.error)
|
||||
return <AlertComponent type='error' message={shopData.error.message} />;
|
||||
const user = useQuery(GET_CURRENT_USER);
|
||||
|
||||
if (loading || shopData.loading || user.loading) return <LoadingSpinner />;
|
||||
if (error || shopData.error || user.error)
|
||||
return (
|
||||
<AlertComponent
|
||||
type='error'
|
||||
message={error.message || shopData.error.message || user.error.message}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<JobDocuments
|
||||
data={data.documents}
|
||||
jobId={jobId}
|
||||
currentUser={user.data.currentUser}
|
||||
shopId={
|
||||
shopData.data?.bodyshops[0]?.id
|
||||
? shopData.data?.bodyshops[0]?.id
|
||||
|
||||
@@ -1,204 +0,0 @@
|
||||
import { Button, Icon, Modal, notification, Upload } from "antd";
|
||||
import axios from "axios";
|
||||
import React, { useState } from "react";
|
||||
import { useMutation } from "react-apollo";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Resizer from "react-image-file-resizer";
|
||||
import { INSERT_NEW_DOCUMENT } from "../../graphql/documents.queries";
|
||||
import "./jobs-documents.styles.scss";
|
||||
|
||||
function getBase64(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
reader.onload = () => resolve(reader.result);
|
||||
reader.onerror = error => reject(error);
|
||||
});
|
||||
}
|
||||
|
||||
function JobsDocumentsComponent({ shopId, jobId, loading, data }) {
|
||||
const { t } = useTranslation();
|
||||
const [insertNewDocument] = useMutation(INSERT_NEW_DOCUMENT);
|
||||
|
||||
const [state, setState] = useState({
|
||||
previewVisible: false,
|
||||
previewImage: ""
|
||||
});
|
||||
|
||||
const [fileList, setFileList] = useState(
|
||||
data.reduce((acc, value) => {
|
||||
acc.push({
|
||||
uid: value.id,
|
||||
url: value.url,
|
||||
name: value.name,
|
||||
status: "done",
|
||||
thumUrl:
|
||||
"https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
|
||||
});
|
||||
return acc;
|
||||
}, [])
|
||||
);
|
||||
|
||||
const handleUpload = ev => {
|
||||
const { onError, onSuccess, onProgress } = ev;
|
||||
|
||||
Resizer.imageFileResizer(
|
||||
ev.file,
|
||||
3000,
|
||||
3000,
|
||||
"JPEG",
|
||||
75,
|
||||
0,
|
||||
uri => {
|
||||
let file = new File([uri], ev.file.name, {});
|
||||
file.uid = ev.file.uid;
|
||||
// Split the filename to get the name and type
|
||||
let fileName = file.name;
|
||||
let fileType = file.type;
|
||||
let key = `${shopId}/${jobId}/${fileName}`;
|
||||
//URL is using the proxy set in pacakges.json.
|
||||
axios
|
||||
.post("/sign_s3", {
|
||||
fileName: key,
|
||||
fileType
|
||||
})
|
||||
.then(response => {
|
||||
var returnData = response.data.data.returnData;
|
||||
var signedRequest = returnData.signedRequest;
|
||||
var url = returnData.url;
|
||||
setState({ ...state, url: url });
|
||||
// Put the fileType in the headers for the upload
|
||||
var options = {
|
||||
headers: {
|
||||
"Content-Type": fileType
|
||||
},
|
||||
onUploadProgress: e => {
|
||||
onProgress({ percent: (e.loaded / e.total) * 100 });
|
||||
}
|
||||
};
|
||||
|
||||
axios
|
||||
.put(signedRequest, file, options)
|
||||
.then(response => {
|
||||
onSuccess(response.body);
|
||||
insertNewDocument({
|
||||
variables: {
|
||||
docInput: [
|
||||
{
|
||||
jobid: jobId,
|
||||
uploaded_by: "patrick@bodyshop.app",
|
||||
url,
|
||||
thumb_url: url
|
||||
}
|
||||
]
|
||||
}
|
||||
}).then(r => {
|
||||
console.log(r);
|
||||
notification["success"]({
|
||||
message: t("documents.successes.insert")
|
||||
});
|
||||
});
|
||||
|
||||
setState({ ...state, success: true });
|
||||
})
|
||||
.catch(error => {
|
||||
console.log("Error uploading to S3", error);
|
||||
onError(error);
|
||||
notification["error"]({
|
||||
message: t("documents.errors.insert") + JSON.stringify(error)
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.log("Outside Error here.", error);
|
||||
notification["error"]({
|
||||
message:
|
||||
t("documents.errors.getpresignurl") + JSON.stringify(error)
|
||||
});
|
||||
});
|
||||
},
|
||||
"blob"
|
||||
);
|
||||
};
|
||||
|
||||
const handleCancel = () => setState({ ...state, previewVisible: false });
|
||||
|
||||
const handlePreview = async file => {
|
||||
if (!file.url && !file.preview) {
|
||||
file.preview = await getBase64(file.originFileObj);
|
||||
}
|
||||
|
||||
setState({
|
||||
...state,
|
||||
previewImage: file.url || file.preview,
|
||||
previewVisible: true
|
||||
});
|
||||
};
|
||||
const handleChange = props => {
|
||||
const { fileList } = props;
|
||||
console.log("New fileList", fileList);
|
||||
setFileList(fileList);
|
||||
};
|
||||
|
||||
const { previewVisible, previewImage } = state;
|
||||
// const uploadButton = (
|
||||
// <div>
|
||||
// <Icon type='plus' />
|
||||
// <div className='ant-upload-text'>{t("documents.labels.upload")}</div>
|
||||
// </div>
|
||||
// );
|
||||
|
||||
console.log(
|
||||
"process.env.REACT_APP_S3_BUCKET",
|
||||
process.env.REACT_APP_S3_BUCKET
|
||||
);
|
||||
const imageRequest = JSON.stringify({
|
||||
bucket: process.env.REACT_APP_S3_BUCKET,
|
||||
key:
|
||||
"52b7357c-0edd-4c95-85c3-dfdbcdfad9ac/f11e92a4-8a7d-4ec0-86ac-2f46b631e438/thumb-1920-459857.jpg",
|
||||
edits: {
|
||||
resize: {
|
||||
height: 100,
|
||||
width: 100
|
||||
}
|
||||
}
|
||||
});
|
||||
const CloudFrontUrl = "https://d18fc493a0fm4o.cloudfront.net";
|
||||
const url = `${CloudFrontUrl}/${btoa(imageRequest)}`;
|
||||
|
||||
return (
|
||||
<div className='clearfix'>
|
||||
<Button
|
||||
onClick={() => {
|
||||
console.log("btn click");
|
||||
console.log("data", data);
|
||||
}}>
|
||||
Test Request
|
||||
</Button>
|
||||
<img src={url} alt='test' />
|
||||
<Upload.Dragger
|
||||
customRequest={handleUpload}
|
||||
accept='.pdf,.jpg,.jpeg'
|
||||
listType='picture-card'
|
||||
fileList={fileList}
|
||||
multiple={true}
|
||||
onPreview={handlePreview}
|
||||
onChange={handleChange}>
|
||||
<p className='ant-upload-drag-icon'>
|
||||
<Icon type='inbox' />
|
||||
</p>
|
||||
<p className='ant-upload-text'>
|
||||
Click or drag file to this area to upload
|
||||
</p>
|
||||
<p className='ant-upload-hint'>
|
||||
Support for a single or bulk upload. Strictly prohibit from uploading
|
||||
company data or other band files
|
||||
</p>
|
||||
</Upload.Dragger>
|
||||
<Modal visible={previewVisible} footer={null} onCancel={handleCancel}>
|
||||
<img alt='example' style={{ width: "100%" }} src={previewImage} />
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default JobsDocumentsComponent;
|
||||
@@ -25,6 +25,7 @@ export default withRouter(function JobsList({
|
||||
title: t("jobs.fields.ro_number"),
|
||||
dataIndex: "ro_number",
|
||||
key: "ro_number",
|
||||
width: "8%",
|
||||
// onFilter: (value, record) => record.ro_number.includes(value),
|
||||
// filteredValue: state.filteredInfo.text || null,
|
||||
sorter: (a, b) => alphaSort(a, b),
|
||||
@@ -34,7 +35,7 @@ export default withRouter(function JobsList({
|
||||
render: (text, record) => (
|
||||
<span>
|
||||
<Link to={"/manage/jobs/" + record.id}>
|
||||
{record.ro_number ? record.ro_number : t("general.labels.na")}
|
||||
{record.ro_number ? record.ro_number : "EST-" + record.est_number}
|
||||
</Link>
|
||||
</span>
|
||||
)
|
||||
@@ -45,6 +46,7 @@ export default withRouter(function JobsList({
|
||||
key: "owner",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
|
||||
width: "25%",
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
|
||||
render: (text, record) => {
|
||||
@@ -64,6 +66,7 @@ export default withRouter(function JobsList({
|
||||
title: t("jobs.fields.phone1"),
|
||||
dataIndex: "ownr_ph1",
|
||||
key: "ownr_ph1",
|
||||
width: "12%",
|
||||
ellipsis: true,
|
||||
render: (text, record) => {
|
||||
return record.ownr_ph1 ? (
|
||||
@@ -86,12 +89,13 @@ export default withRouter(function JobsList({
|
||||
title: t("jobs.fields.status"),
|
||||
dataIndex: "status",
|
||||
key: "status",
|
||||
width: "10%",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => alphaSort(a, b),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
||||
render: (text, record) => {
|
||||
return record.job_status?.name ?? t("general.labels.na");
|
||||
return record.job_status?.name || t("general.labels.na");
|
||||
}
|
||||
},
|
||||
|
||||
@@ -99,6 +103,7 @@ export default withRouter(function JobsList({
|
||||
title: t("jobs.fields.vehicle"),
|
||||
dataIndex: "vehicle",
|
||||
key: "vehicle",
|
||||
width: "15%",
|
||||
ellipsis: true,
|
||||
render: (text, record) => {
|
||||
return record.vehicle ? (
|
||||
@@ -115,6 +120,7 @@ export default withRouter(function JobsList({
|
||||
title: t("vehicles.fields.plate_no"),
|
||||
dataIndex: "plate_no",
|
||||
key: "plate_no",
|
||||
width: "8%",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => alphaSort(a, b),
|
||||
sortOrder:
|
||||
@@ -131,6 +137,7 @@ export default withRouter(function JobsList({
|
||||
title: t("jobs.fields.clm_no"),
|
||||
dataIndex: "clm_no",
|
||||
key: "clm_no",
|
||||
width: "12%",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => alphaSort(a, b),
|
||||
sortOrder:
|
||||
@@ -147,11 +154,12 @@ export default withRouter(function JobsList({
|
||||
title: t("jobs.fields.clm_total"),
|
||||
dataIndex: "clm_total",
|
||||
key: "clm_total",
|
||||
sorter: (a, b) => {
|
||||
return a > b;
|
||||
},
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order,
|
||||
width: "8%",
|
||||
// sorter: (a, b) => {
|
||||
// return a > b;
|
||||
// },
|
||||
// sortOrder:
|
||||
// state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order,
|
||||
render: (text, record) => {
|
||||
return record.clm_total ? (
|
||||
<span>{record.clm_total}</span>
|
||||
@@ -164,6 +172,7 @@ export default withRouter(function JobsList({
|
||||
title: t("jobs.fields.owner_owing"),
|
||||
dataIndex: "owner_owing",
|
||||
key: "owner_owing",
|
||||
width: "8%",
|
||||
render: (text, record) => {
|
||||
return record.owner_owing ? (
|
||||
<span>{record.owner_owing}</span>
|
||||
@@ -202,7 +211,9 @@ export default withRouter(function JobsList({
|
||||
return (
|
||||
<Input.Search
|
||||
placeholder='Search...'
|
||||
onSearch={value => console.log(value)}
|
||||
onSearch={value => {
|
||||
console.log(value);
|
||||
}}
|
||||
enterButton
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import React from "react";
|
||||
|
||||
export default function JobsRatesComponent() {
|
||||
return <div>Jobs Rates Comp</div>;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import React from "react";
|
||||
import JobsRatesComponent from "./jobs-rates.component";
|
||||
|
||||
export default function JobsRatesContainer() {
|
||||
return <JobsRatesComponent />;
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import i18next from "i18next";
|
||||
import { Dropdown, Menu, Icon } from "antd";
|
||||
|
||||
export default function LanguageSelector() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleMenuClick = e => {
|
||||
i18next.changeLanguage(e.key, (err, t) => {
|
||||
if (err)
|
||||
return console.log("Error encountered when changing languages.", err);
|
||||
});
|
||||
};
|
||||
const menu = (
|
||||
<Menu onClick={handleMenuClick}>
|
||||
<Menu.Item key="en_us">{t("general.languages.english")}</Menu.Item>
|
||||
<Menu.Item key="fr">{t("general.languages.french")}</Menu.Item>
|
||||
<Menu.Item key="es">{t("general.languages.spanish")}</Menu.Item>
|
||||
</Menu>
|
||||
);
|
||||
return (
|
||||
<Dropdown overlay={menu}>
|
||||
<Icon type="global" />
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
@@ -2,6 +2,16 @@ import React from "react";
|
||||
import { Spin } from "antd";
|
||||
import "./loading-spinner.styles.scss";
|
||||
|
||||
export default function LoadingSpinner() {
|
||||
return <Spin className="loading-spinner" size="large" delay="500" />;
|
||||
export default function LoadingSpinner({ loading = true, message, ...props }) {
|
||||
return (
|
||||
<Spin
|
||||
spinning={loading}
|
||||
className="loading-spinner"
|
||||
size="large"
|
||||
//delay="500"
|
||||
tip={message ? message : null}
|
||||
>
|
||||
{props.children}
|
||||
</Spin>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -37,12 +37,12 @@ export default function NoteUpsertModalContainer({
|
||||
]
|
||||
}
|
||||
}).then(r => {
|
||||
refetch();
|
||||
changeVisibility(!visible);
|
||||
notification["success"]({
|
||||
message: t("notes.successes.create")
|
||||
});
|
||||
});
|
||||
refetch();
|
||||
changeVisibility(!visible);
|
||||
};
|
||||
|
||||
const updateExistingNote = () => {
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
import { Checkbox, Divider, Table } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import PhoneFormatter from "../../utils/PhoneFormatter";
|
||||
|
||||
export default function OwnerFindModalComponent({
|
||||
selectedOwner,
|
||||
setSelectedOwner,
|
||||
ownersListLoading,
|
||||
ownersList
|
||||
}) {
|
||||
//setSelectedOwner is used to set the record id of the owner to use for adding the job.
|
||||
const { t } = useTranslation();
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t("owners.fields.ownr_ln"),
|
||||
dataIndex: "ownr_ln",
|
||||
key: "ownr_ln"
|
||||
//width: "8%",
|
||||
// onFilter: (value, record) => record.ro_number.includes(value),
|
||||
// // filteredValue: state.filteredInfo.text || null,
|
||||
// sorter: (a, b) => alphaSort(a, b),
|
||||
// sortOrder:
|
||||
// state.sortedInfo.columnKey === "cieca_id" && state.sortedInfo.order
|
||||
},
|
||||
{
|
||||
title: t("owners.fields.ownr_fn"),
|
||||
dataIndex: "ownr_fn",
|
||||
key: "ownr_fn"
|
||||
// ellipsis: true,
|
||||
// sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
|
||||
// //width: "25%",
|
||||
// sortOrder:
|
||||
// state.sortedInfo.columnKey === "ownr_name" && state.sortedInfo.order
|
||||
},
|
||||
{
|
||||
title: t("owners.fields.ownr_addr1"),
|
||||
dataIndex: "ownr_addr1",
|
||||
key: "ownr_addr1"
|
||||
// sorter: (a, b) => alphaSort(a.vehicle_info, b.vehicle_info),
|
||||
// sortOrder:
|
||||
// state.sortedInfo.columnKey === "vehicle_info" && state.sortedInfo.order
|
||||
//ellipsis: true
|
||||
},
|
||||
{
|
||||
title: t("owners.fields.ownr_city"),
|
||||
dataIndex: "ownr_city",
|
||||
key: "ownr_city"
|
||||
// sorter: (a, b) => alphaSort(a.clm_no, b.clm_no),
|
||||
// sortOrder:
|
||||
// state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order
|
||||
//width: "12%",
|
||||
//ellipsis: true
|
||||
},
|
||||
{
|
||||
title: t("owners.fields.ownr_ea"),
|
||||
dataIndex: "ownr_ea",
|
||||
key: "ownr_ea"
|
||||
// sorter: (a, b) => a.clm_amt - b.clm_amt,
|
||||
// sortOrder:
|
||||
// state.sortedInfo.columnKey === "clm_amt" && state.sortedInfo.order
|
||||
//width: "12%",
|
||||
//ellipsis: true
|
||||
},
|
||||
{
|
||||
title: t("owners.fields.ownr_ph1"),
|
||||
dataIndex: "ownr_ph1",
|
||||
key: "ownr_ph1",
|
||||
render: (text, record) => (
|
||||
<PhoneFormatter>{record.ownr_ph1}</PhoneFormatter>
|
||||
)
|
||||
// sorter: (a, b) => alphaSort(a.uploaded_by, b.uploaded_by),
|
||||
// sortOrder:
|
||||
// state.sortedInfo.columnKey === "uploaded_by" && state.sortedInfo.order
|
||||
//width: "12%",
|
||||
//ellipsis: true
|
||||
}
|
||||
];
|
||||
|
||||
const handleOnRowClick = record => {
|
||||
if (record) {
|
||||
if (record.id) {
|
||||
setSelectedOwner(record.id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
setSelectedOwner(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Table
|
||||
title={() => t("owners.labels.existing_owners")}
|
||||
size="small"
|
||||
pagination={{ position: "bottom" }}
|
||||
columns={columns.map(item => ({ ...item }))}
|
||||
rowKey="id"
|
||||
loading={ownersListLoading}
|
||||
dataSource={ownersList}
|
||||
rowSelection={{
|
||||
onSelect: props => {
|
||||
setSelectedOwner(props.id);
|
||||
},
|
||||
type: "radio",
|
||||
selectedRowKeys: [selectedOwner]
|
||||
}}
|
||||
onRow={(record, rowIndex) => {
|
||||
return {
|
||||
onClick: event => {
|
||||
handleOnRowClick(record);
|
||||
}
|
||||
};
|
||||
}}
|
||||
/>
|
||||
<Divider />
|
||||
<Checkbox
|
||||
checked={selectedOwner ? false : true}
|
||||
onClick={() => setSelectedOwner(null)}
|
||||
>
|
||||
Create a new Owner record for this job.
|
||||
</Checkbox>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { Modal } from "antd";
|
||||
import React from "react";
|
||||
import { useQuery } from "react-apollo";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { QUERY_SEARCH_OWNER_BY_IDX } from "../../graphql/owners.queries";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||
import OwnerFindModalComponent from "./owner-find-modal.component";
|
||||
|
||||
export default function OwnerFindModalContainer({
|
||||
loading,
|
||||
error,
|
||||
owner,
|
||||
selectedOwner,
|
||||
setSelectedOwner,
|
||||
...modalProps
|
||||
}) {
|
||||
//use owner object to run query and find what possible owners there are.
|
||||
const { t } = useTranslation();
|
||||
|
||||
const ownersList = useQuery(QUERY_SEARCH_OWNER_BY_IDX, {
|
||||
variables: {
|
||||
search: owner
|
||||
? `${owner.ownr_fn} ${owner.ownr_ln} ${owner.ownr_addr1} ${owner.ownr_city} ${owner.ownr_zip} ${owner.ownr_ea} ${owner.ownr_ph1} ${owner.ownr_ph2}`
|
||||
: null
|
||||
},
|
||||
skip: !owner,
|
||||
fetchPolicy: "network-only"
|
||||
});
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t("owners.labels.existing_owners")}
|
||||
width={"80%"}
|
||||
{...modalProps}
|
||||
>
|
||||
{loading ? <LoadingSpinner /> : null}
|
||||
{error ? <AlertComponent message={error.message} type="error" /> : null}
|
||||
{owner ? (
|
||||
<OwnerFindModalComponent
|
||||
selectedOwner={selectedOwner}
|
||||
setSelectedOwner={setSelectedOwner}
|
||||
ownersListLoading={ownersList.loading}
|
||||
ownersList={
|
||||
ownersList.data && ownersList.data.search_owners
|
||||
? ownersList.data.search_owners
|
||||
: null
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +1,9 @@
|
||||
import React, { Component } from "react";
|
||||
//import { Redirect } from "react-router-dom";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import firebase from "../../firebase/firebase.utils";
|
||||
export default class SignOut extends Component {
|
||||
state = {
|
||||
redirect: false
|
||||
};
|
||||
|
||||
signOut = async () => {
|
||||
export default function SignoutComponent() {
|
||||
const signOut = async () => {
|
||||
try {
|
||||
await firebase.auth().signOut();
|
||||
// this.setState({
|
||||
@@ -17,17 +14,7 @@ export default class SignOut extends Component {
|
||||
}
|
||||
};
|
||||
|
||||
renderRedirect = () => {
|
||||
if (this.state.redirect) {
|
||||
//return <Redirect to="/signin" />;
|
||||
}
|
||||
};
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{this.renderRedirect()}
|
||||
<div onClick={this.signOut}>Sign Out</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const { t } = useTranslation();
|
||||
|
||||
return <div onClick={signOut}>{t("user.actions.signout")}</div>;
|
||||
}
|
||||
|
||||
@@ -63,11 +63,11 @@ export default function WhiteBoardCard({ metadata }) {
|
||||
<div>
|
||||
<Card
|
||||
title={
|
||||
(metadata.ro_number ?? metadata.est_number) +
|
||||
(metadata.ro_number || metadata.est_number) +
|
||||
" | " +
|
||||
(metadata.owner?.first_name ?? "") +
|
||||
(metadata.owner?.first_name || "") +
|
||||
" " +
|
||||
(metadata.owner?.last_name ?? "")
|
||||
(metadata.owner?.last_name || "")
|
||||
}
|
||||
style={{ width: 300, marginTop: 10 }}
|
||||
bodyStyle={{ padding: 10 }}
|
||||
@@ -87,15 +87,15 @@ export default function WhiteBoardCard({ metadata }) {
|
||||
<Col span={18}>
|
||||
<Row>
|
||||
<WrappedSpan>
|
||||
{metadata.vehicle?.v_model_yr ?? t("general.labels.na")}{" "}
|
||||
{metadata.vehicle?.v_make_desc ?? t("general.labels.na")}{" "}
|
||||
{metadata.vehicle?.v_model_desc ?? t("general.labels.na")}
|
||||
{metadata.vehicle?.v_model_yr || t("general.labels.na")}{" "}
|
||||
{metadata.vehicle?.v_make_desc || t("general.labels.na")}{" "}
|
||||
{metadata.vehicle?.v_model_desc || t("general.labels.na")}
|
||||
</WrappedSpan>
|
||||
</Row>
|
||||
{metadata.vehicle?.v_vin ? (
|
||||
<Row>
|
||||
<WrappedSpan>
|
||||
VIN: {metadata.vehicle?.v_vin ?? t("general.labels.na")}
|
||||
VIN: {metadata.vehicle?.v_vin || t("general.labels.na")}
|
||||
</WrappedSpan>
|
||||
</Row>
|
||||
) : null}
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
import firebase from "firebase/app";
|
||||
import "firebase/firestore";
|
||||
import "firebase/auth";
|
||||
import "firebase/database"
|
||||
|
||||
const config = {
|
||||
apiKey: "AIzaSyDV9MsSHZmpLtjoaTK_ObvjFaJ-nMSd2KA",
|
||||
authDomain: "bodyshop-dev-b1cb6.firebaseapp.com",
|
||||
databaseURL: "https://bodyshop-dev-b1cb6.firebaseio.com",
|
||||
projectId: "bodyshop-dev-b1cb6",
|
||||
storageBucket: "bodyshop-dev-b1cb6.appspot.com",
|
||||
messagingSenderId: "922785209028",
|
||||
appId: "1:922785209028:web:96e9df15401eee5d784791",
|
||||
measurementId: "G-2D5378VCHE"
|
||||
};
|
||||
import "firebase/database";
|
||||
|
||||
const config = JSON.parse(process.env.REACT_APP_FIREBASE_CONFIG);
|
||||
firebase.initializeApp(config);
|
||||
|
||||
export const createUserProfileDocument = async (userAuth, additionalData) => {
|
||||
//Needs to be redone to write to GQL database.
|
||||
//Needs to be redone to write to GQL database.
|
||||
console.log("userAuth from firebase Utils", userAuth);
|
||||
if (!userAuth) return;
|
||||
|
||||
|
||||
@@ -2,13 +2,13 @@ import { onError } from "apollo-link-error";
|
||||
import { Observable } from "apollo-link";
|
||||
import { auth } from "../firebase/firebase.utils";
|
||||
//https://stackoverflow.com/questions/57163454/refreshing-a-token-with-apollo-client-firebase-auth
|
||||
|
||||
const errorLink = onError(
|
||||
({ graphQLErrors, networkError, operation, forward }) => {
|
||||
let access_token = window.localStorage.getItem("token");
|
||||
console.log("graphQLErrors", graphQLErrors);
|
||||
console.log("networkError", networkError);
|
||||
console.log("operation", operation);
|
||||
console.log("forward", forward);
|
||||
// console.log("graphQLErrors", graphQLErrors);
|
||||
// console.log("networkError", networkError);
|
||||
// console.log("operation", operation);
|
||||
// console.log("forward", forward);
|
||||
|
||||
let expired = false;
|
||||
|
||||
@@ -26,45 +26,68 @@ const errorLink = onError(
|
||||
//User access token has expired
|
||||
//props.history.push("/network-error");
|
||||
console.log("We need a new token!");
|
||||
if (access_token && access_token !== "undefined") {
|
||||
// Let's refresh token through async request
|
||||
return new Observable(observer => {
|
||||
auth.currentUser
|
||||
.getIdToken(true)
|
||||
.then(function(idToken) {
|
||||
if (!idToken) {
|
||||
window.localStorage.removeItem("token");
|
||||
return console.log("Refresh token has expired");
|
||||
}
|
||||
console.log("Got a new token", idToken);
|
||||
window.localStorage.setItem("token", idToken);
|
||||
// Let's refresh token through async request
|
||||
|
||||
// reset the headers
|
||||
operation.setContext(({ headers = {} }) => ({
|
||||
headers: {
|
||||
// Re-add old headers
|
||||
...headers,
|
||||
// Switch out old access token for new one
|
||||
authorization: idToken ? `Bearer ${idToken}` : ""
|
||||
}
|
||||
}));
|
||||
auth.currentUser.getIdToken(true).then(token => {
|
||||
if (token) {
|
||||
console.log("Got the new token.", token);
|
||||
window.localStorage.setItem("token", token);
|
||||
operation.setContext(({ headers = {} }) => ({
|
||||
headers: {
|
||||
...headers,
|
||||
authorization: token ? `Bearer ${token}` : ""
|
||||
}
|
||||
}));
|
||||
|
||||
const subscriber = {
|
||||
next: observer.next.bind(observer),
|
||||
error: observer.error.bind(observer),
|
||||
complete: observer.complete.bind(observer)
|
||||
};
|
||||
console.log("About to resend the request.");
|
||||
// Retry last failed request
|
||||
forward(operation).subscribe(subscriber);
|
||||
})
|
||||
.catch(error => {
|
||||
// No refresh or client token available, we force user to login
|
||||
console.log("Hit an error.");
|
||||
observer.error(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
return new Observable(observer => {
|
||||
const subscriber = {
|
||||
next: observer.next.bind(observer),
|
||||
error: observer.error.bind(observer),
|
||||
complete: observer.complete.bind(observer)
|
||||
};
|
||||
console.log("About to resend the request.");
|
||||
// Retry last failed request
|
||||
forward(operation).subscribe(subscriber);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// return new Observable(observer => {
|
||||
// auth.currentUser
|
||||
// .getIdToken(true)
|
||||
// .then(function(idToken) {
|
||||
// if (!idToken) {
|
||||
// window.localStorage.removeItem("token");
|
||||
// return console.log("Refresh token has expired");
|
||||
// }
|
||||
// console.log("Got a new token", idToken);
|
||||
// window.localStorage.setItem("token", idToken);
|
||||
|
||||
// // reset the headers
|
||||
// operation.setContext(({ headers = {} }) => ({
|
||||
// headers: {
|
||||
// // Re-add old headers
|
||||
// ...headers,
|
||||
// // Switch out old access token for new one
|
||||
// authorization: idToken ? `Bearer ${idToken}` : ""
|
||||
// }
|
||||
// }));
|
||||
|
||||
// const subscriber = {
|
||||
// next: observer.next.bind(observer),
|
||||
// error: observer.error.bind(observer),
|
||||
// complete: observer.complete.bind(observer)
|
||||
// };
|
||||
// console.log("About to resend the request.");
|
||||
// // Retry last failed request
|
||||
// forward(operation).subscribe(subscriber);
|
||||
// })
|
||||
// .catch(error => {
|
||||
// // No refresh or client token available, we force user to login
|
||||
// console.log("Hit an error.");
|
||||
// observer.error(error);
|
||||
// });
|
||||
// });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
81
client/src/graphql/available-jobs.queries.js
Normal file
81
client/src/graphql/available-jobs.queries.js
Normal file
@@ -0,0 +1,81 @@
|
||||
import { gql } from "apollo-boost";
|
||||
|
||||
export const QUERY_AVAILABLE_NEW_JOBS = gql`
|
||||
query QUERY_AVAILABLE_NEW_JOBS {
|
||||
available_jobs(
|
||||
where: { issupplement: { _eq: false } }
|
||||
order_by: { updated_at: desc }
|
||||
) {
|
||||
cieca_id
|
||||
clm_amt
|
||||
clm_no
|
||||
created_at
|
||||
id
|
||||
issupplement
|
||||
ownr_name
|
||||
source_system
|
||||
supplement_number
|
||||
updated_at
|
||||
uploaded_by
|
||||
vehicle_info
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const QUERY_AVAILABLE_SUPPLEMENT_JOBS = gql`
|
||||
query QUERY_AVAILABLE_SUPPLEMENT_JOBS {
|
||||
available_jobs(
|
||||
where: { issupplement: { _eq: true } }
|
||||
order_by: { updated_at: desc }
|
||||
) {
|
||||
cieca_id
|
||||
clm_amt
|
||||
clm_no
|
||||
created_at
|
||||
id
|
||||
issupplement
|
||||
ownr_name
|
||||
source_system
|
||||
supplement_number
|
||||
updated_at
|
||||
uploaded_by
|
||||
vehicle_info
|
||||
job {
|
||||
ro_number
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const DELETE_AVAILABLE_JOB = gql`
|
||||
mutation DELETE_AVAILABLE_JOB($id: uuid) {
|
||||
delete_available_jobs(where: { id: { _eq: $id } }) {
|
||||
affected_rows
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const DELETE_ALL_AVAILABLE_NEW_JOBS = gql`
|
||||
mutation DELETE_ALL_AVAILABLE_NEW_JOBS {
|
||||
delete_available_jobs(where: { issupplement: { _eq: false } }) {
|
||||
affected_rows
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const DELETE_ALL_AVAILABLE_SUPPLEMENT_JOBS = gql`
|
||||
mutation DELETE_ALL_AVAILABLE_NEW_JOBS {
|
||||
delete_available_jobs(where: { issupplement: { _eq: true } }) {
|
||||
affected_rows
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const QUERY_AVAILABLE_NEW_JOBS_EST_DATA_BY_PK = gql`
|
||||
query QUERY_AVAILABLE_NEW_JOBS_EST_DATA_BY_PK($id: uuid!) {
|
||||
available_jobs_by_pk(id: $id) {
|
||||
id
|
||||
est_data
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -7,6 +7,7 @@ export const GET_DOCUMENTS_BY_JOB = gql`
|
||||
url
|
||||
thumb_url
|
||||
name
|
||||
key
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -14,6 +15,20 @@ export const GET_DOCUMENTS_BY_JOB = gql`
|
||||
export const INSERT_NEW_DOCUMENT = gql`
|
||||
mutation INSERT_NEW_DOCUMENT($docInput: [documents_insert_input!]!) {
|
||||
insert_documents(objects: $docInput) {
|
||||
returning {
|
||||
id
|
||||
url
|
||||
thumb_url
|
||||
name
|
||||
key
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const DELETE_DOCUMENT = gql`
|
||||
mutation DELETE_DOCUMENT($id: uuid) {
|
||||
delete_documents(where: { id: { _eq: $id } }) {
|
||||
returning {
|
||||
id
|
||||
}
|
||||
|
||||
@@ -3,5 +3,6 @@ export default {
|
||||
currentUser: null,
|
||||
selectedNavItem: "Home",
|
||||
recentItems: [],
|
||||
bodyShopData: null
|
||||
bodyShopData: null,
|
||||
language: "en_us"
|
||||
};
|
||||
|
||||
@@ -1,34 +1,7 @@
|
||||
import { gql } from "apollo-boost";
|
||||
|
||||
export const GET_ALL_OPEN_JOBS = gql`
|
||||
query GET_ALL_OPEN_JOBS {
|
||||
jobs {
|
||||
id
|
||||
est_number
|
||||
ro_number
|
||||
job_status {
|
||||
id
|
||||
name
|
||||
}
|
||||
scheduled_completion
|
||||
scheduled_delivery
|
||||
vehicle {
|
||||
id
|
||||
v_model_yr
|
||||
v_make_desc
|
||||
v_model_desc
|
||||
plate_no
|
||||
}
|
||||
owner {
|
||||
first_name
|
||||
last_name
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const SUBSCRIPTION_ALL_OPEN_JOBS = gql`
|
||||
subscription SUBSCRIPTION_ALL_OPEN_JOBS {
|
||||
export const QUERY_ALL_OPEN_JOBS = gql`
|
||||
query QUERY_ALL_OPEN_JOBS {
|
||||
jobs {
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
@@ -89,30 +62,6 @@ export const SUBSCRIPTION_ALL_OPEN_JOBS = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
export const QUERY_JOBS_IN_PRODUCTION = gql`
|
||||
query QUERY_JOBS_IN_PRODUCTION {
|
||||
jobs {
|
||||
id
|
||||
updated_at
|
||||
est_number
|
||||
ro_number
|
||||
scheduled_completion
|
||||
scheduled_delivery
|
||||
vehicle {
|
||||
id
|
||||
v_model_yr
|
||||
v_make_desc
|
||||
v_model_desc
|
||||
plate_no
|
||||
}
|
||||
owner {
|
||||
first_name
|
||||
last_name
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const SUBSCRIPTION_JOBS_IN_PRODUCTION = gql`
|
||||
subscription SUBSCRIPTION_JOBS_IN_PRODUCTION {
|
||||
job_status(
|
||||
@@ -149,36 +98,19 @@ export const SUBSCRIPTION_JOBS_IN_PRODUCTION = gql`
|
||||
export const GET_JOB_BY_PK = gql`
|
||||
query GET_JOB_BY_PK($id: uuid!) {
|
||||
jobs_by_pk(id: $id) {
|
||||
actual_completion
|
||||
actual_delivery
|
||||
actual_in
|
||||
created_at
|
||||
est_number
|
||||
id
|
||||
local_tax_rate
|
||||
owner {
|
||||
id
|
||||
first_name
|
||||
last_name
|
||||
phone
|
||||
}
|
||||
est_co_nm
|
||||
est_ph1
|
||||
est_ea
|
||||
est_ct_fn
|
||||
est_ct_ln
|
||||
regie_number
|
||||
ro_number
|
||||
scheduled_completion
|
||||
scheduled_in
|
||||
service_car
|
||||
csr
|
||||
loss_desc
|
||||
kmin
|
||||
kmout
|
||||
referral_source
|
||||
unit_number
|
||||
po_number
|
||||
special_coverage_policy
|
||||
scheduled_delivery
|
||||
job_status {
|
||||
id
|
||||
name
|
||||
}
|
||||
updated_at
|
||||
claim_total
|
||||
deductible
|
||||
converted
|
||||
est_number
|
||||
ro_number
|
||||
vehicle {
|
||||
id
|
||||
plate_no
|
||||
@@ -188,6 +120,62 @@ export const GET_JOB_BY_PK = gql`
|
||||
v_make_desc
|
||||
v_color
|
||||
}
|
||||
ins_co_id
|
||||
policy_no
|
||||
loss_date
|
||||
area_of_damage
|
||||
ins_co_nm
|
||||
ins_addr1
|
||||
ins_city
|
||||
ins_ct_ln
|
||||
ins_ct_fn
|
||||
ins_ea
|
||||
ins_ph1
|
||||
est_co_nm
|
||||
est_ct_fn
|
||||
est_ct_ln
|
||||
pay_date
|
||||
est_ph1
|
||||
est_ea
|
||||
selling_dealer
|
||||
servicing_dealer
|
||||
selling_dealer_contact
|
||||
servicing_dealer_contact
|
||||
regie_number
|
||||
scheduled_completion
|
||||
id
|
||||
ded_amt
|
||||
ded_status
|
||||
depreciation_taxes
|
||||
federal_tax_payable
|
||||
other_amount_payable
|
||||
towing_payable
|
||||
storage_payable
|
||||
adjustment_bottom_line
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
owner{
|
||||
id
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
}
|
||||
|
||||
joblines{
|
||||
id
|
||||
unq_seq
|
||||
line_ind
|
||||
line_desc
|
||||
part_type
|
||||
oem_partno
|
||||
db_price
|
||||
act_price
|
||||
part_qty
|
||||
mod_lbr_ty
|
||||
db_hrs
|
||||
mod_lb_hrs
|
||||
lbr_op
|
||||
lbr_amt
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -246,6 +234,10 @@ export const QUERY_JOB_CARD_DETAILS = gql`
|
||||
updated_at
|
||||
claim_total
|
||||
ded_amt
|
||||
documents(limit: 3, order_by: { created_at: desc }) {
|
||||
id
|
||||
thumb_url
|
||||
}
|
||||
vehicle {
|
||||
id
|
||||
plate_no
|
||||
@@ -282,3 +274,13 @@ export const CONVERT_JOB_TO_RO = gql`
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const INSERT_NEW_JOB = gql`
|
||||
mutation INSERT_JOB($job: [jobs_insert_input!]!) {
|
||||
insert_jobs(objects: $job) {
|
||||
returning {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
import { gql } from "apollo-boost";
|
||||
|
||||
export const SET_CURRENT_USER = gql`
|
||||
mutation SetCurrentUser($user: User!) {
|
||||
setCurrentUser(user: $user) @client {
|
||||
email
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const GET_CURRENT_USER = gql`
|
||||
query GET_CURRENT_USER {
|
||||
currentUser @client {
|
||||
@@ -26,16 +18,8 @@ export const GET_CURRENT_SELECTED_NAV_ITEM = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
export const GET_WHITE_BOARD_LEFT_SIDER_VISIBLE = gql`
|
||||
{
|
||||
whiteBoardLeftSiderVisible @client
|
||||
}
|
||||
`;
|
||||
|
||||
export const GET_BODYSHOP = gql`
|
||||
query LOCAL_GET_BODY_SHOP {
|
||||
bodyShopData @client {
|
||||
shopname
|
||||
}
|
||||
export const GET_LANGUAGE = gql`
|
||||
query GET_USER_LANGUAGE {
|
||||
language @client
|
||||
}
|
||||
`;
|
||||
|
||||
20
client/src/graphql/owners.queries.js
Normal file
20
client/src/graphql/owners.queries.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import { gql } from "apollo-boost";
|
||||
|
||||
export const QUERY_SEARCH_OWNER_BY_IDX = gql`
|
||||
query QUERY_SEARCH_OWNER_BY_IDX($search: String!) {
|
||||
search_owners(args: { search: $search }) {
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
ownr_addr1
|
||||
ownr_addr2
|
||||
ownr_city
|
||||
ownr_ctry
|
||||
ownr_ea
|
||||
ownr_st
|
||||
ownr_zip
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from "react";
|
||||
import JobsAvailableContainer from "../../components/jobs-available-new/jobs-available-new.container";
|
||||
import JobsAvailableSupplementContainer from "../../components/jobs-available-supplement/jobs-available-supplement.container";
|
||||
|
||||
export default function JobsAvailablePageComponent({
|
||||
deleteJob,
|
||||
estDataLazyLoad
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
Available New Jobs
|
||||
<JobsAvailableContainer
|
||||
deleteJob={deleteJob}
|
||||
estDataLazyLoad={estDataLazyLoad}
|
||||
/>
|
||||
Available Supplements (//TODO: LOGIC HAS NOT YET BEEN COPIED FROM OTHER COMPONENT)
|
||||
<JobsAvailableSupplementContainer
|
||||
deleteJob={deleteJob}
|
||||
estDataLazyLoad={estDataLazyLoad}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import React from "react";
|
||||
import { useMutation, useLazyQuery } from "react-apollo";
|
||||
import {
|
||||
DELETE_AVAILABLE_JOB,
|
||||
QUERY_AVAILABLE_NEW_JOBS_EST_DATA_BY_PK
|
||||
} from "../../graphql/available-jobs.queries";
|
||||
import JobsAvailablePageComponent from "./jobs-available.page.component";
|
||||
|
||||
export default function JobsAvailablePageContainer() {
|
||||
const [deleteJob] = useMutation(DELETE_AVAILABLE_JOB);
|
||||
|
||||
const estDataLazyLoad = useLazyQuery(
|
||||
QUERY_AVAILABLE_NEW_JOBS_EST_DATA_BY_PK,
|
||||
{
|
||||
fetchPolicy: "network-only"
|
||||
}
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
<JobsAvailablePageComponent
|
||||
deleteJob={deleteJob}
|
||||
estDataLazyLoad={estDataLazyLoad}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
173
client/src/pages/jobs-detail/jobs-detail.page.component.jsx
Normal file
173
client/src/pages/jobs-detail/jobs-detail.page.component.jsx
Normal file
@@ -0,0 +1,173 @@
|
||||
import { Alert, Button, Form, Icon, Tabs } from "antd";
|
||||
import React, { useContext } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
FaHardHat,
|
||||
FaInfo,
|
||||
FaRegStickyNote,
|
||||
FaShieldAlt
|
||||
} from "react-icons/fa";
|
||||
import JobsLines from '../../components/job-detail-lines/job-lines.component'
|
||||
import JobsDetailClaims from "../../components/jobs-detail-claims/jobs-detail-claims.component";
|
||||
import JobsDetailFinancials from "../../components/jobs-detail-financial/jobs-detail-financial.component";
|
||||
import JobsDetailHeader from "../../components/jobs-detail-header/jobs-detail-header.component";
|
||||
import JobsDetailInsurance from "../../components/jobs-detail-insurance/jobs-detail-insurance.component";
|
||||
import JobsDocumentsContainer from "../../components/jobs-documents/jobs-documents.container";
|
||||
import JobNotesContainer from "../../components/jobs-notes/jobs-notes.container";
|
||||
import JobDetailFormContext from "./jobs-detail.page.context";
|
||||
|
||||
export default function JobsDetailPage({
|
||||
job,
|
||||
mutationUpdateJob,
|
||||
mutationConvertJob,
|
||||
handleSubmit,
|
||||
refetch
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { isFieldsTouched, resetFields } = useContext(JobDetailFormContext);
|
||||
|
||||
const formItemLayout = {
|
||||
labelCol: {
|
||||
xs: { span: 12 },
|
||||
sm: { span: 5 }
|
||||
},
|
||||
wrapperCol: {
|
||||
xs: { span: 24 },
|
||||
sm: { span: 12 }
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit} {...formItemLayout}>
|
||||
<JobsDetailHeader
|
||||
job={job}
|
||||
mutationConvertJob={mutationConvertJob}
|
||||
refetch={refetch}
|
||||
/>
|
||||
|
||||
{isFieldsTouched() ? (
|
||||
<Alert
|
||||
message={
|
||||
<div>
|
||||
{t("general.messages.unsavedchanges")}
|
||||
<Button onClick={() => resetFields()}>
|
||||
{t("general.actions.reset")}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
closable
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<Tabs defaultActiveKey="claimdetail">
|
||||
<Tabs.TabPane
|
||||
tab={
|
||||
<span>
|
||||
<Icon component={FaInfo} />
|
||||
{t("menus.jobsdetail.claimdetail")}
|
||||
</span>
|
||||
}
|
||||
key="claimdetail"
|
||||
>
|
||||
<JobsDetailClaims job={job} />
|
||||
</Tabs.TabPane>
|
||||
|
||||
<Tabs.TabPane
|
||||
tab={
|
||||
<span>
|
||||
<Icon component={FaShieldAlt} />
|
||||
{t("menus.jobsdetail.insurance")}
|
||||
</span>
|
||||
}
|
||||
key="insurance"
|
||||
>
|
||||
<JobsDetailInsurance job={job} />
|
||||
</Tabs.TabPane>
|
||||
|
||||
<Tabs.TabPane
|
||||
tab={
|
||||
<span>
|
||||
<Icon type="bars" />
|
||||
{t("menus.jobsdetail.repairdata")}
|
||||
</span>
|
||||
}
|
||||
key="repairdata"
|
||||
>
|
||||
<JobsLines job={job} />
|
||||
</Tabs.TabPane>
|
||||
|
||||
<Tabs.TabPane
|
||||
tab={
|
||||
<span>
|
||||
<Icon type="dollar" />
|
||||
{t("menus.jobsdetail.financials")}
|
||||
</span>
|
||||
}
|
||||
key="financials"
|
||||
>
|
||||
<JobsDetailFinancials job={job} />
|
||||
</Tabs.TabPane>
|
||||
|
||||
<Tabs.TabPane
|
||||
tab={
|
||||
<span>
|
||||
<Icon type="tool" />
|
||||
{t("menus.jobsdetail.partssublet")}
|
||||
</span>
|
||||
}
|
||||
key="partssublet"
|
||||
>
|
||||
Partssublet
|
||||
</Tabs.TabPane>
|
||||
|
||||
<Tabs.TabPane
|
||||
tab={
|
||||
<span>
|
||||
<Icon component={FaHardHat} />
|
||||
{t("menus.jobsdetail.labor")}
|
||||
</span>
|
||||
}
|
||||
key="labor"
|
||||
>
|
||||
Labor
|
||||
</Tabs.TabPane>
|
||||
|
||||
<Tabs.TabPane
|
||||
tab={
|
||||
<span>
|
||||
<Icon type="calendar" />
|
||||
{t("menus.jobsdetail.dates")}
|
||||
</span>
|
||||
}
|
||||
key="dates"
|
||||
>
|
||||
Dates
|
||||
</Tabs.TabPane>
|
||||
|
||||
<Tabs.TabPane
|
||||
tab={
|
||||
<span>
|
||||
<Icon type="file-image" />
|
||||
{t("jobs.labels.documents")}
|
||||
</span>
|
||||
}
|
||||
key="#documents"
|
||||
>
|
||||
<JobsDocumentsContainer jobId={job.id} />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane
|
||||
tab={
|
||||
<span>
|
||||
<Icon component={FaRegStickyNote} />
|
||||
{t("jobs.labels.notes")}
|
||||
</span>
|
||||
}
|
||||
key="#notes"
|
||||
>
|
||||
<JobNotesContainer jobId={job.id} />
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
@@ -1,38 +1,76 @@
|
||||
import { useQuery } from "@apollo/react-hooks";
|
||||
import { Form, notification } from "antd";
|
||||
import React, { useEffect } from "react";
|
||||
import { useMutation, useQuery } from "react-apollo";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import AlertComponent from "../../components/alert/alert.component";
|
||||
import SpinComponent from "../../components/loading-spinner/loading-spinner.component";
|
||||
import { GET_JOB_BY_PK } from "../../graphql/jobs.queries";
|
||||
import JobsDetailPage from "./jobs-detail.page";
|
||||
import { CONVERT_JOB_TO_RO, GET_JOB_BY_PK, UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||
import JobsDetailPage from "./jobs-detail.page.component";
|
||||
import JobDetailFormContext from "./jobs-detail.page.context";
|
||||
|
||||
function JobsDetailPageContainer({ match, location }) {
|
||||
function JobsDetailPageContainer({ match, form }) {
|
||||
const { jobId } = match.params;
|
||||
const { hash } = location;
|
||||
const { t } = useTranslation();
|
||||
const { loading, error, data } = useQuery(GET_JOB_BY_PK, {
|
||||
|
||||
const { loading, error, data, refetch } = useQuery(GET_JOB_BY_PK, {
|
||||
variables: { id: jobId },
|
||||
fetchPolicy: "network-only"
|
||||
});
|
||||
const [mutationUpdateJob] = useMutation(UPDATE_JOB);
|
||||
const [mutationConvertJob] = useMutation(CONVERT_JOB_TO_RO);
|
||||
|
||||
useEffect(() => {
|
||||
document.title = loading
|
||||
? "..."
|
||||
: error
|
||||
? t("titles.app")
|
||||
: t("titles.jobsdetail", {
|
||||
ro_number: data.jobs_by_pk.ro_number
|
||||
});
|
||||
}, [loading, data, t]);
|
||||
}, [loading, data, t, error]);
|
||||
|
||||
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) {
|
||||
mutationUpdateJob({
|
||||
variables: { jobId: data.jobs_by_pk.id, job: values }
|
||||
}).then(r => {
|
||||
notification["success"]({
|
||||
message: t("jobs.successes.savetitle")
|
||||
});
|
||||
//TODO: Better way to reset the field decorators?
|
||||
refetch().then(r => form.resetFields());
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (loading) return <SpinComponent />;
|
||||
if (error) return <AlertComponent message={error.message} type='error' />;
|
||||
|
||||
return (
|
||||
<JobsDetailPage
|
||||
hash={hash ? hash.substring(1) : "#lines"}
|
||||
data={data}
|
||||
jobId={jobId}
|
||||
match={match}
|
||||
/>
|
||||
return data.jobs_by_pk ? (
|
||||
<JobDetailFormContext.Provider value={form}>
|
||||
<JobsDetailPage
|
||||
job={data.jobs_by_pk}
|
||||
mutationUpdateJob={mutationUpdateJob}
|
||||
mutationConvertJob={mutationConvertJob}
|
||||
handleSubmit={handleSubmit}
|
||||
getFieldDecorator={form.getFieldDecorator}
|
||||
refetch={refetch}
|
||||
/>
|
||||
</JobDetailFormContext.Provider>
|
||||
) : (
|
||||
<AlertComponent message={t("jobs.errors.noaccess")} type='error' />
|
||||
);
|
||||
}
|
||||
export default JobsDetailPageContainer;
|
||||
export default Form.create({ name: "JobsDetailPageContainer" })(
|
||||
JobsDetailPageContainer
|
||||
);
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import React from "react";
|
||||
const JobDetailFormContext = React.createContext(null);
|
||||
export default JobDetailFormContext;
|
||||
@@ -1,80 +0,0 @@
|
||||
import { Icon, Row, Tabs } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FaRegStickyNote } from "react-icons/fa";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import JobLinesContainer from "../../components/job-lines/job-lines.container.component";
|
||||
import JobTombstone from "../../components/job-tombstone/job-tombstone.component";
|
||||
import JobsDocumentsContainer from "../../components/jobs-documents/jobs-documents.container";
|
||||
import JobNotesContainer from "../../components/jobs-notes/jobs-notes.container";
|
||||
|
||||
function JobsDetailPage({ jobId, hash, data, match, history }) {
|
||||
const { t } = useTranslation();
|
||||
console.log("hash", hash);
|
||||
return (
|
||||
<div>
|
||||
<Row>
|
||||
<JobTombstone job={data.jobs_by_pk} />
|
||||
</Row>
|
||||
<Row>
|
||||
<Tabs
|
||||
defaultActiveKey={`#${hash}`}
|
||||
onChange={p => {
|
||||
history.push(p);
|
||||
}}>
|
||||
<Tabs.TabPane
|
||||
tab={
|
||||
<span>
|
||||
<Icon type='bars' />
|
||||
{t("jobs.labels.lines")}
|
||||
</span>
|
||||
}
|
||||
key='#lines'>
|
||||
<JobLinesContainer match={match} />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane
|
||||
tab={
|
||||
<span>
|
||||
<Icon type='dollar' />
|
||||
{t("jobs.labels.rates")}
|
||||
</span>
|
||||
}
|
||||
key='#rates'>
|
||||
Estimate Rates
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane
|
||||
tab={
|
||||
<span>
|
||||
<Icon type='tool1' />
|
||||
{t("jobs.labels.parts")}
|
||||
</span>
|
||||
}
|
||||
key='#parts'>
|
||||
Estimate Parts
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane
|
||||
tab={
|
||||
<span>
|
||||
<Icon type='file-image' />
|
||||
{t("jobs.labels.documents")}
|
||||
</span>
|
||||
}
|
||||
key='#documents'>
|
||||
<JobsDocumentsContainer jobId={jobId} />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane
|
||||
tab={
|
||||
<span>
|
||||
<Icon component={FaRegStickyNote} />
|
||||
{t("jobs.labels.notes")}
|
||||
</span>
|
||||
}
|
||||
key='#notes'>
|
||||
<JobNotesContainer jobId={jobId} />
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default withRouter(JobsDetailPage);
|
||||
@@ -1,15 +1,15 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useSubscription } from "@apollo/react-hooks";
|
||||
import { useQuery } from "@apollo/react-hooks";
|
||||
import AlertComponent from "../../components/alert/alert.component";
|
||||
import { Col } from "antd";
|
||||
import { SUBSCRIPTION_ALL_OPEN_JOBS } from "../../graphql/jobs.queries";
|
||||
import { QUERY_ALL_OPEN_JOBS } from "../../graphql/jobs.queries";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import JobsList from "../../components/jobs-list/jobs-list.component";
|
||||
import JobDetailCards from "../../components/job-detail-cards/job-detail-cards.component";
|
||||
|
||||
//TODO: Implement pagination for this.
|
||||
export default function JobsPage({ match, location }) {
|
||||
const { loading, error, data } = useSubscription(SUBSCRIPTION_ALL_OPEN_JOBS, {
|
||||
const { loading, error, data } = useQuery(QUERY_ALL_OPEN_JOBS, {
|
||||
fetchPolicy: "network-only"
|
||||
});
|
||||
const { t } = useTranslation();
|
||||
@@ -20,7 +20,6 @@ export default function JobsPage({ match, location }) {
|
||||
|
||||
const { hash } = location;
|
||||
const [selectedJob, setSelectedJob] = useState(hash ? hash.substr(1) : null);
|
||||
console.log("Jobs Page Render.");
|
||||
|
||||
if (error) return <AlertComponent message={error.message} type='error' />;
|
||||
return (
|
||||
|
||||
@@ -8,17 +8,21 @@ import HeaderContainer from "../../components/header/header.container";
|
||||
import FooterComponent from "../../components/footer/footer.component";
|
||||
import ErrorBoundary from "../../components/error-boundary/error-boundary.component";
|
||||
|
||||
import './manage.page.styles.scss'
|
||||
import "./manage.page.styles.scss";
|
||||
import ChatWindowContainer from "../../components/chat-window/chat-window.container";
|
||||
|
||||
const WhiteBoardPage = lazy(() => import("../white-board/white-board.page"));
|
||||
const JobsPage = lazy(() => import("../jobs/jobs.page"));
|
||||
const JobsDetailPage = lazy(() => import("../jobs-detail/jobs-detail.page.container"));
|
||||
const JobsDetailPage = lazy(() =>
|
||||
import("../jobs-detail/jobs-detail.page.container")
|
||||
);
|
||||
const ProfilePage = lazy(() => import("../profile/profile.container.page"));
|
||||
const JobsDocumentsPage = lazy(() =>
|
||||
import("../../components/jobs-documents/jobs-documents.container")
|
||||
);
|
||||
|
||||
|
||||
const JobsAvailablePage = lazy(() =>
|
||||
import("../jobs-available/jobs-available.page.container")
|
||||
);
|
||||
|
||||
const { Header, Content, Footer } = Layout;
|
||||
//This page will handle all routing for the entire application.
|
||||
@@ -35,13 +39,14 @@ export default function Manage({ match }) {
|
||||
<HeaderContainer />
|
||||
</Header>
|
||||
|
||||
<Content className="content-container">
|
||||
<Content className='content-container'>
|
||||
<ErrorBoundary>
|
||||
<Suspense
|
||||
fallback={<div>TODO: Suspended Loading in Manage Page...</div>}>
|
||||
<Route exact path={`${match.path}`} component={WhiteBoardPage} />
|
||||
|
||||
<Route exact path={`${match.path}/jobs`} component={JobsPage} />
|
||||
|
||||
<Route
|
||||
exact
|
||||
path={`${match.path}/jobs/:jobId`}
|
||||
@@ -57,11 +62,18 @@ export default function Manage({ match }) {
|
||||
path={`${match.path}/profile`}
|
||||
component={ProfilePage}
|
||||
/>
|
||||
|
||||
<Route
|
||||
exact
|
||||
path={`${match.path}/available`}
|
||||
component={JobsAvailablePage}
|
||||
/>
|
||||
</Suspense>
|
||||
</ErrorBoundary>
|
||||
</Content>
|
||||
|
||||
<Footer>
|
||||
<ChatWindowContainer />
|
||||
<FooterComponent />
|
||||
</Footer>
|
||||
<BackTop />
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { configure } from 'enzyme';
|
||||
import Adapter from 'enzyme-adapter-react-16';
|
||||
|
||||
configure({ adapter: new Adapter() });
|
||||
configure({ adapter: new Adapter() });
|
||||
@@ -2,18 +2,25 @@
|
||||
"translation": {
|
||||
"documents": {
|
||||
"errors": {
|
||||
"deletes3": "Error deleting document from storage. ",
|
||||
"getpresignurl": "Error obtaining presigned URL for document. ",
|
||||
"insert": "Unable to upload file."
|
||||
"insert": "Unable to upload file.",
|
||||
"nodocuments": "There are no documents."
|
||||
},
|
||||
"labels": {
|
||||
"upload": "Upload"
|
||||
},
|
||||
"successes": {
|
||||
"delete": "Document deleted successfully.",
|
||||
"insert": "Uploaded document successfully. "
|
||||
}
|
||||
},
|
||||
"general": {
|
||||
"actions": {
|
||||
"reset": "Reset to original."
|
||||
},
|
||||
"labels": {
|
||||
"actions": "Actions",
|
||||
"in": "In",
|
||||
"loading": "Loading...",
|
||||
"na": "N/A",
|
||||
@@ -25,17 +32,33 @@
|
||||
"english": "English",
|
||||
"french": "French",
|
||||
"spanish": "Spanish"
|
||||
},
|
||||
"messages": {
|
||||
"unsavedchanges": "You have unsaved changes."
|
||||
}
|
||||
},
|
||||
"joblines": {
|
||||
"fields": {
|
||||
"act_price": "Actual Price",
|
||||
"db_price": "Database Price",
|
||||
"line_desc": "Line Description",
|
||||
"part_type": "Part Type",
|
||||
"unq_seq": "Seq #"
|
||||
}
|
||||
},
|
||||
"jobs": {
|
||||
"actions": {
|
||||
"addDocuments": "Add Job Documents",
|
||||
"addNote": "Add Note",
|
||||
"convert": "Convert",
|
||||
"postInvoices": "Post Invoices",
|
||||
"printCenter": "Print Center"
|
||||
},
|
||||
"errors": {
|
||||
"creating": "Error encountered while creating job. {{error}}",
|
||||
"deleted": "Error deleting job.",
|
||||
"noaccess": "This job does not exist or you do not have access to it.",
|
||||
"nodates": "No dates specified for this job.",
|
||||
"nojobselected": "No job is selected.",
|
||||
"noowner": "No owner associated.",
|
||||
"novehicle": "No vehicle associated.",
|
||||
@@ -44,25 +67,83 @@
|
||||
"validationtitle": "Validation Error"
|
||||
},
|
||||
"fields": {
|
||||
"actual_completion": "Actual Completion",
|
||||
"actual_delivery": "Actual Delivery",
|
||||
"actual_in": "Actual In",
|
||||
"adjustment_bottom_line": "Adjustments",
|
||||
"cieca_id": "CIECA ID",
|
||||
"claim_total": "Claim Total",
|
||||
"clm_no": "Claim #",
|
||||
"clm_total": "Claim Total",
|
||||
"deductible": "Deductible",
|
||||
"csr": "Customer Service Rep.",
|
||||
"customerowing": "Customer Owing",
|
||||
"date_closed": "Closed",
|
||||
"date_estimated": "Date Estimated",
|
||||
"date_exported": "Exported",
|
||||
"date_invoiced": "Invoiced",
|
||||
"date_open": "Open",
|
||||
"date_scheduled": "Scheduled",
|
||||
"ded_amt": "Deductible",
|
||||
"ded_status": "Deductible Status",
|
||||
"depreciation_taxes": "Depreciation/Taxes",
|
||||
"est_addr1": "Appraiser Address",
|
||||
"est_co_nm": "Appraiser",
|
||||
"est_ct_fn": "Appraiser First Name",
|
||||
"est_ct_ln": "Appraiser Last Name",
|
||||
"est_ea": "Appraiser Email",
|
||||
"est_number": "Estimate Number",
|
||||
"est_ph1": "Appraiser Phone #",
|
||||
"federal_tax_payable": "Federal Tax Payable",
|
||||
"ins_addr1": "Insurance Co. Address",
|
||||
"ins_city": "Insurance City",
|
||||
"ins_co_id": "Insurance Co. ID",
|
||||
"ins_co_nm": "Insurance Company Name",
|
||||
"ins_ct_fn": "File Handler First Name",
|
||||
"ins_ct_ln": "File Handler Last Name",
|
||||
"ins_ea": "File Handler Email",
|
||||
"ins_ph1": "File Handler Phone #",
|
||||
"kmin": "Mileage In",
|
||||
"kmout": "Mileage Out",
|
||||
"loss_date": "Loss Date",
|
||||
"loss_desc": "Loss of Use",
|
||||
"other_amount_payable": "Other Amount Payable",
|
||||
"owner": "Owner",
|
||||
"owner_owing": "Cust. Owes",
|
||||
"ownr_ea": "Email",
|
||||
"pay_date": "Inspection Date",
|
||||
"phone1": "Phone 1",
|
||||
"phoneshort": "PH",
|
||||
"policy_no": "Policy #",
|
||||
"ponumber": "PO Number",
|
||||
"referralsource": "Referral Source",
|
||||
"regie_number": "Registration #",
|
||||
"repairtotal": "Repair Total",
|
||||
"ro_number": "RO #",
|
||||
"scheduled_completion": "Scheduled Completion",
|
||||
"scheduled_delivery": "Scheduled Delivery",
|
||||
"scheduled_in": "Scheduled In",
|
||||
"selling_dealer": "Selling Dealer",
|
||||
"selling_dealer_contact": "Selling Dealer Contact",
|
||||
"servicecar": "Service Car",
|
||||
"servicing_dealer": "Servicing Dealer",
|
||||
"servicing_dealer_contact": "Servicing Dealer Contact",
|
||||
"specialcoveragepolicy": "Special Coverage Policy",
|
||||
"status": "Job Status",
|
||||
"storage_payable": "Storage/PVRT",
|
||||
"towing_payable": "Towing Payable",
|
||||
"unitnumber": "Unit #",
|
||||
"updated_at": "Updated At",
|
||||
"uploaded_by": "Uploaded By",
|
||||
"vehicle": "Vehicle"
|
||||
},
|
||||
"labels": {
|
||||
"available_new_jobs": "",
|
||||
"cards": {
|
||||
"appraiser": "Appraiser",
|
||||
"customer": "Customer Information",
|
||||
"damage": "Area of Damage",
|
||||
"dates": "Dates",
|
||||
"documents": "Documents",
|
||||
"documents": "Recent Documents",
|
||||
"estimator": "Estimator",
|
||||
"filehandler": "File Handler",
|
||||
"insurance": "Insurance Details",
|
||||
@@ -71,7 +152,7 @@
|
||||
"totals": "Totals",
|
||||
"vehicle": "Vehicle"
|
||||
},
|
||||
"convert": "Convert",
|
||||
"creating_new_job": "Creating new job...",
|
||||
"documents": "Documents",
|
||||
"lines": "Estimate Lines",
|
||||
"notes": "Notes",
|
||||
@@ -80,7 +161,10 @@
|
||||
"vehicle_info": "Vehicle"
|
||||
},
|
||||
"successes": {
|
||||
"all_deleted": "{{count}} jobs deleted successfully.",
|
||||
"converted": "Job converted successfully.",
|
||||
"created": "Job created successfully. Click to view.",
|
||||
"deleted": "Job deleted successfully.",
|
||||
"save": "Record Saved",
|
||||
"savetitle": "Record saved successfully."
|
||||
}
|
||||
@@ -90,6 +174,21 @@
|
||||
"languageselector": "Language",
|
||||
"profile": "Profile"
|
||||
},
|
||||
"header": {
|
||||
"activejobs": "Active Jobs",
|
||||
"availablejobs": "Available Jobs",
|
||||
"home": "Home",
|
||||
"jobs": "Jobs"
|
||||
},
|
||||
"jobsdetail": {
|
||||
"claimdetail": "Claim Details",
|
||||
"dates": "Dates",
|
||||
"financials": "Financials",
|
||||
"insurance": "Insurance",
|
||||
"labor": "Labor",
|
||||
"partssublet": "Parts/Sublet",
|
||||
"repairdata": "Repair Data"
|
||||
},
|
||||
"profilesidebar": {
|
||||
"profile": "My Profile",
|
||||
"shops": "My Shops"
|
||||
@@ -113,11 +212,24 @@
|
||||
"newnoteplaceholder": "Add a note..."
|
||||
},
|
||||
"successes": {
|
||||
"created": "Note created successfully.",
|
||||
"create": "Note created successfully.",
|
||||
"deleted": "Note deleted successfully.",
|
||||
"updated": "Note updated successfully."
|
||||
}
|
||||
},
|
||||
"owners": {
|
||||
"fields": {
|
||||
"ownr_addr1": "Address",
|
||||
"ownr_city": "City",
|
||||
"ownr_ea": "Email",
|
||||
"ownr_fn": "First Name",
|
||||
"ownr_ln": "Last Name",
|
||||
"ownr_ph1": "Phone 1"
|
||||
},
|
||||
"labels": {
|
||||
"existing_owners": "Existing Owners"
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
"errors": {
|
||||
"state": "Error reading page state. Please refresh."
|
||||
@@ -130,6 +242,11 @@
|
||||
"jobsdocuments": "Job Documents {{ro_number}} | $t(titles.app)",
|
||||
"profile": "My Profile | $t(titles.app)"
|
||||
},
|
||||
"user": {
|
||||
"actions": {
|
||||
"signout": "Sign Out"
|
||||
}
|
||||
},
|
||||
"vehicles": {
|
||||
"fields": {
|
||||
"plate_no": "License Plate"
|
||||
|
||||
@@ -2,18 +2,25 @@
|
||||
"translation": {
|
||||
"documents": {
|
||||
"errors": {
|
||||
"deletes3": "Error al eliminar el documento del almacenamiento.",
|
||||
"getpresignurl": "Error al obtener la URL prescrita para el documento.",
|
||||
"insert": "Incapaz de cargar el archivo."
|
||||
"insert": "Incapaz de cargar el archivo.",
|
||||
"nodocuments": "No hay documentos"
|
||||
},
|
||||
"labels": {
|
||||
"upload": "Subir"
|
||||
},
|
||||
"successes": {
|
||||
"delete": "Documento eliminado con éxito.",
|
||||
"insert": "Documento cargado con éxito."
|
||||
}
|
||||
},
|
||||
"general": {
|
||||
"actions": {
|
||||
"reset": "Restablecer a original."
|
||||
},
|
||||
"labels": {
|
||||
"actions": "Comportamiento",
|
||||
"in": "en",
|
||||
"loading": "Cargando...",
|
||||
"na": "N / A",
|
||||
@@ -25,17 +32,33 @@
|
||||
"english": "Inglés",
|
||||
"french": "francés",
|
||||
"spanish": "español"
|
||||
},
|
||||
"messages": {
|
||||
"unsavedchanges": "Usted tiene cambios no guardados."
|
||||
}
|
||||
},
|
||||
"joblines": {
|
||||
"fields": {
|
||||
"act_price": "Precio actual",
|
||||
"db_price": "Precio de base de datos",
|
||||
"line_desc": "Descripción de línea",
|
||||
"part_type": "Tipo de parte",
|
||||
"unq_seq": "Seq #"
|
||||
}
|
||||
},
|
||||
"jobs": {
|
||||
"actions": {
|
||||
"addDocuments": "Agregar documentos de trabajo",
|
||||
"addNote": "Añadir la nota",
|
||||
"convert": "Convertir",
|
||||
"postInvoices": "Contabilizar facturas",
|
||||
"printCenter": "Centro de impresión"
|
||||
},
|
||||
"errors": {
|
||||
"creating": "",
|
||||
"deleted": "Error al eliminar el trabajo.",
|
||||
"noaccess": "Este trabajo no existe o no tiene acceso a él.",
|
||||
"nodates": "No hay fechas especificadas para este trabajo.",
|
||||
"nojobselected": "No hay trabajo seleccionado.",
|
||||
"noowner": "Ningún propietario asociado.",
|
||||
"novehicle": "No hay vehículo asociado.",
|
||||
@@ -44,25 +67,83 @@
|
||||
"validationtitle": "Error de validacion"
|
||||
},
|
||||
"fields": {
|
||||
"actual_completion": "Realización real",
|
||||
"actual_delivery": "Entrega real",
|
||||
"actual_in": "Real en",
|
||||
"adjustment_bottom_line": "Ajustes",
|
||||
"cieca_id": "CIECA ID",
|
||||
"claim_total": "Reclamar total",
|
||||
"clm_no": "Reclamación #",
|
||||
"clm_total": "Reclamar total",
|
||||
"deductible": "Deducible",
|
||||
"csr": "Representante de servicio al cliente.",
|
||||
"customerowing": "Cliente debido",
|
||||
"date_closed": "Cerrado",
|
||||
"date_estimated": "Fecha estimada",
|
||||
"date_exported": "Exportado",
|
||||
"date_invoiced": "Facturado",
|
||||
"date_open": "Abierto",
|
||||
"date_scheduled": "Programado",
|
||||
"ded_amt": "Deducible",
|
||||
"ded_status": "Estado deducible",
|
||||
"depreciation_taxes": "Depreciación / Impuestos",
|
||||
"est_addr1": "Dirección del tasador",
|
||||
"est_co_nm": "Tasador",
|
||||
"est_ct_fn": "Nombre del tasador",
|
||||
"est_ct_ln": "Apellido del tasador",
|
||||
"est_ea": "Correo electrónico del tasador",
|
||||
"est_number": "Numero Estimado",
|
||||
"est_ph1": "Número de teléfono del tasador",
|
||||
"federal_tax_payable": "Impuesto federal por pagar",
|
||||
"ins_addr1": "Dirección de Insurance Co.",
|
||||
"ins_city": "Ciudad de seguros",
|
||||
"ins_co_id": "ID de la compañía de seguros",
|
||||
"ins_co_nm": "Nombre de la compañía de seguros",
|
||||
"ins_ct_fn": "Nombre del controlador de archivos",
|
||||
"ins_ct_ln": "Apellido del manejador de archivos",
|
||||
"ins_ea": "Correo electrónico del controlador de archivos",
|
||||
"ins_ph1": "File Handler Phone #",
|
||||
"kmin": "Kilometraje en",
|
||||
"kmout": "Kilometraje",
|
||||
"loss_date": "Fecha de pérdida",
|
||||
"loss_desc": "Perdida de uso",
|
||||
"other_amount_payable": "Otra cantidad a pagar",
|
||||
"owner": "Propietario",
|
||||
"owner_owing": "Cust. Debe",
|
||||
"ownr_ea": "Email",
|
||||
"pay_date": "Fecha de inspección",
|
||||
"phone1": "Teléfono 1",
|
||||
"phoneshort": "PH",
|
||||
"policy_no": "Política #",
|
||||
"ponumber": "numero postal",
|
||||
"referralsource": "Fuente de referencia",
|
||||
"regie_number": "N. ° de registro",
|
||||
"repairtotal": "Reparación total",
|
||||
"ro_number": "RO #",
|
||||
"scheduled_completion": "Finalización programada",
|
||||
"scheduled_delivery": "Entrega programada",
|
||||
"scheduled_in": "Programado en",
|
||||
"selling_dealer": "Distribuidor vendedor",
|
||||
"selling_dealer_contact": "Contacto con el vendedor",
|
||||
"servicecar": "Auto de servicio",
|
||||
"servicing_dealer": "Distribuidor de servicio",
|
||||
"servicing_dealer_contact": "Servicio Contacto con el concesionario",
|
||||
"specialcoveragepolicy": "Política de cobertura especial",
|
||||
"status": "Estado del trabajo",
|
||||
"storage_payable": "Almacenamiento / PVRT",
|
||||
"towing_payable": "Remolque a pagar",
|
||||
"unitnumber": "Unidad #",
|
||||
"updated_at": "Actualizado en",
|
||||
"uploaded_by": "Subido por",
|
||||
"vehicle": "Vehículo"
|
||||
},
|
||||
"labels": {
|
||||
"available_new_jobs": "",
|
||||
"cards": {
|
||||
"appraiser": "Tasador",
|
||||
"customer": "Información al cliente",
|
||||
"damage": "Área de Daño",
|
||||
"dates": "fechas",
|
||||
"documents": "documentos",
|
||||
"documents": "Documentos recientes",
|
||||
"estimator": "Estimador",
|
||||
"filehandler": "File Handler",
|
||||
"insurance": "detalles del seguro",
|
||||
@@ -71,7 +152,7 @@
|
||||
"totals": "Totales",
|
||||
"vehicle": "Vehículo"
|
||||
},
|
||||
"convert": "Convertir",
|
||||
"creating_new_job": "Creando nuevo trabajo ...",
|
||||
"documents": "documentos",
|
||||
"lines": "Líneas estimadas",
|
||||
"notes": "Notas",
|
||||
@@ -80,7 +161,10 @@
|
||||
"vehicle_info": "Vehículo"
|
||||
},
|
||||
"successes": {
|
||||
"all_deleted": "{{count}} trabajos eliminados con éxito.",
|
||||
"converted": "Trabajo convertido con éxito.",
|
||||
"created": "Trabajo creado con éxito. Click para ver.",
|
||||
"deleted": "Trabajo eliminado con éxito.",
|
||||
"save": "Registro guardado",
|
||||
"savetitle": "Registro guardado con éxito."
|
||||
}
|
||||
@@ -90,6 +174,21 @@
|
||||
"languageselector": "idioma",
|
||||
"profile": "Perfil"
|
||||
},
|
||||
"header": {
|
||||
"activejobs": "Empleos activos",
|
||||
"availablejobs": "Trabajos disponibles",
|
||||
"home": "Casa",
|
||||
"jobs": "Trabajos"
|
||||
},
|
||||
"jobsdetail": {
|
||||
"claimdetail": "Detalles de la reclamación",
|
||||
"dates": "fechas",
|
||||
"financials": "finanzas",
|
||||
"insurance": "Seguro",
|
||||
"labor": "Labor",
|
||||
"partssublet": "Piezas / Subarrendamiento",
|
||||
"repairdata": "Datos de reparación"
|
||||
},
|
||||
"profilesidebar": {
|
||||
"profile": "Mi perfil",
|
||||
"shops": "Mis tiendas"
|
||||
@@ -113,11 +212,24 @@
|
||||
"newnoteplaceholder": "Agrega una nota..."
|
||||
},
|
||||
"successes": {
|
||||
"created": "Nota creada con éxito.",
|
||||
"create": "Nota creada con éxito.",
|
||||
"deleted": "Nota eliminada con éxito.",
|
||||
"updated": "Nota actualizada con éxito."
|
||||
}
|
||||
},
|
||||
"owners": {
|
||||
"fields": {
|
||||
"ownr_addr1": "Dirección",
|
||||
"ownr_city": "ciudad",
|
||||
"ownr_ea": "Email",
|
||||
"ownr_fn": "Nombre de pila",
|
||||
"ownr_ln": "Apellido",
|
||||
"ownr_ph1": ""
|
||||
},
|
||||
"labels": {
|
||||
"existing_owners": "Propietarios existentes"
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
"errors": {
|
||||
"state": "Error al leer el estado de la página. Porfavor refresca."
|
||||
@@ -130,6 +242,11 @@
|
||||
"jobsdocuments": "Documentos de trabajo {{ro_number}} | $ t (títulos.app)",
|
||||
"profile": "Mi perfil | $t(titles.app)"
|
||||
},
|
||||
"user": {
|
||||
"actions": {
|
||||
"signout": "desconectar"
|
||||
}
|
||||
},
|
||||
"vehicles": {
|
||||
"fields": {
|
||||
"plate_no": "Placa"
|
||||
|
||||
@@ -2,18 +2,25 @@
|
||||
"translation": {
|
||||
"documents": {
|
||||
"errors": {
|
||||
"deletes3": "Erreur lors de la suppression du document du stockage.",
|
||||
"getpresignurl": "Erreur lors de l'obtention de l'URL présignée pour le document.",
|
||||
"insert": "Incapable de télécharger le fichier."
|
||||
"insert": "Incapable de télécharger le fichier.",
|
||||
"nodocuments": "Il n'y a pas de documents."
|
||||
},
|
||||
"labels": {
|
||||
"upload": "Télécharger"
|
||||
},
|
||||
"successes": {
|
||||
"delete": "Le document a bien été supprimé.",
|
||||
"insert": "Document téléchargé avec succès."
|
||||
}
|
||||
},
|
||||
"general": {
|
||||
"actions": {
|
||||
"reset": "Rétablir l'original."
|
||||
},
|
||||
"labels": {
|
||||
"actions": "actes",
|
||||
"in": "dans",
|
||||
"loading": "Chargement...",
|
||||
"na": "N / A",
|
||||
@@ -25,17 +32,33 @@
|
||||
"english": "Anglais",
|
||||
"french": "Francais",
|
||||
"spanish": "Espanol"
|
||||
},
|
||||
"messages": {
|
||||
"unsavedchanges": "Vous avez des changements non enregistrés."
|
||||
}
|
||||
},
|
||||
"joblines": {
|
||||
"fields": {
|
||||
"act_price": "Prix actuel",
|
||||
"db_price": "Prix de la base de données",
|
||||
"line_desc": "Description de la ligne",
|
||||
"part_type": "Type de pièce",
|
||||
"unq_seq": "Seq #"
|
||||
}
|
||||
},
|
||||
"jobs": {
|
||||
"actions": {
|
||||
"addDocuments": "Ajouter des documents de travail",
|
||||
"addNote": "Ajouter une note",
|
||||
"convert": "Convertir",
|
||||
"postInvoices": "Poster des factures",
|
||||
"printCenter": "Centre d'impression"
|
||||
},
|
||||
"errors": {
|
||||
"creating": "",
|
||||
"deleted": "Erreur lors de la suppression du travail.",
|
||||
"noaccess": "Ce travail n'existe pas ou vous n'y avez pas accès.",
|
||||
"nodates": "Aucune date spécifiée pour ce travail.",
|
||||
"nojobselected": "Aucun travail n'est sélectionné.",
|
||||
"noowner": "Aucun propriétaire associé.",
|
||||
"novehicle": "Aucun véhicule associé.",
|
||||
@@ -44,25 +67,83 @@
|
||||
"validationtitle": "Erreur de validation"
|
||||
},
|
||||
"fields": {
|
||||
"actual_completion": "Achèvement réel",
|
||||
"actual_delivery": "Livraison réelle",
|
||||
"actual_in": "En réel",
|
||||
"adjustment_bottom_line": "Ajustements",
|
||||
"cieca_id": "CIECA ID",
|
||||
"claim_total": "Total réclamation",
|
||||
"clm_no": "Prétendre #",
|
||||
"clm_total": "Total réclamation",
|
||||
"deductible": "Déductible",
|
||||
"csr": "représentant du service à la clientèle",
|
||||
"customerowing": "Client propriétaire",
|
||||
"date_closed": "Fermé",
|
||||
"date_estimated": "Date estimée",
|
||||
"date_exported": "Exportés",
|
||||
"date_invoiced": "Facturé",
|
||||
"date_open": "Ouvrir",
|
||||
"date_scheduled": "Prévu",
|
||||
"ded_amt": "Déductible",
|
||||
"ded_status": "Statut de franchise",
|
||||
"depreciation_taxes": "Amortissement / taxes",
|
||||
"est_addr1": "Adresse de l'évaluateur",
|
||||
"est_co_nm": "Expert",
|
||||
"est_ct_fn": "Prénom de l'évaluateur",
|
||||
"est_ct_ln": "Nom de l'évaluateur",
|
||||
"est_ea": "Courriel de l'évaluateur",
|
||||
"est_number": "Numéro d'estimation",
|
||||
"est_ph1": "Numéro de téléphone de l'évaluateur",
|
||||
"federal_tax_payable": "Impôt fédéral à payer",
|
||||
"ins_addr1": "Adresse Insurance Co.",
|
||||
"ins_city": "Insurance City",
|
||||
"ins_co_id": "ID de la compagnie d'assurance",
|
||||
"ins_co_nm": "Nom de la compagnie d'assurance",
|
||||
"ins_ct_fn": "Prénom du gestionnaire de fichiers",
|
||||
"ins_ct_ln": "Nom du gestionnaire de fichiers",
|
||||
"ins_ea": "Courriel du gestionnaire de fichiers",
|
||||
"ins_ph1": "Numéro de téléphone du gestionnaire de fichiers",
|
||||
"kmin": "Kilométrage en",
|
||||
"kmout": "Kilométrage hors",
|
||||
"loss_date": "Date de perte",
|
||||
"loss_desc": "Perte d'usage",
|
||||
"other_amount_payable": "Autre montant à payer",
|
||||
"owner": "Propriétaire",
|
||||
"owner_owing": "Cust. Owes",
|
||||
"ownr_ea": "Email",
|
||||
"pay_date": "Date d'inspection",
|
||||
"phone1": "Téléphone 1",
|
||||
"phoneshort": "PH",
|
||||
"policy_no": "Politique #",
|
||||
"ponumber": "Numéro de bon de commande",
|
||||
"referralsource": "Source de référence",
|
||||
"regie_number": "Enregistrement #",
|
||||
"repairtotal": "Réparation totale",
|
||||
"ro_number": "RO #",
|
||||
"scheduled_completion": "Achèvement planifié",
|
||||
"scheduled_delivery": "Livraison programmée",
|
||||
"scheduled_in": "Planifié dans",
|
||||
"selling_dealer": "Revendeur vendeur",
|
||||
"selling_dealer_contact": "Contacter le revendeur",
|
||||
"servicecar": "Voiture de service",
|
||||
"servicing_dealer": "Concessionnaire",
|
||||
"servicing_dealer_contact": "Contacter le concessionnaire",
|
||||
"specialcoveragepolicy": "Politique de couverture spéciale",
|
||||
"status": "Statut de l'emploi",
|
||||
"storage_payable": "Stockage / PVRT",
|
||||
"towing_payable": "Remorquage à payer",
|
||||
"unitnumber": "Unité #",
|
||||
"updated_at": "Mis à jour à",
|
||||
"uploaded_by": "Telechargé par",
|
||||
"vehicle": "Véhicule"
|
||||
},
|
||||
"labels": {
|
||||
"available_new_jobs": "",
|
||||
"cards": {
|
||||
"appraiser": "Expert",
|
||||
"customer": "Informations client",
|
||||
"damage": "Zone de dommages",
|
||||
"dates": "Rendez-vous",
|
||||
"documents": "Les documents",
|
||||
"documents": "Documents récents",
|
||||
"estimator": "Estimateur",
|
||||
"filehandler": "Gestionnaire de fichiers",
|
||||
"insurance": "Détails de l'assurance",
|
||||
@@ -71,7 +152,7 @@
|
||||
"totals": "Totaux",
|
||||
"vehicle": "Véhicule"
|
||||
},
|
||||
"convert": "Convertir",
|
||||
"creating_new_job": "Création d'un nouvel emploi ...",
|
||||
"documents": "Les documents",
|
||||
"lines": "Estimer les lignes",
|
||||
"notes": "Remarques",
|
||||
@@ -80,7 +161,10 @@
|
||||
"vehicle_info": "Véhicule"
|
||||
},
|
||||
"successes": {
|
||||
"all_deleted": "{{count}} travaux supprimés avec succès.",
|
||||
"converted": "Travail converti avec succès.",
|
||||
"created": "Le travail a été créé avec succès. Clique pour voir.",
|
||||
"deleted": "Le travail a bien été supprimé.",
|
||||
"save": "Enregistrement enregistré",
|
||||
"savetitle": "Enregistrement enregistré avec succès."
|
||||
}
|
||||
@@ -90,6 +174,21 @@
|
||||
"languageselector": "La langue",
|
||||
"profile": "Profil"
|
||||
},
|
||||
"header": {
|
||||
"activejobs": "Emplois actifs",
|
||||
"availablejobs": "Emplois disponibles",
|
||||
"home": "Accueil",
|
||||
"jobs": "Emplois"
|
||||
},
|
||||
"jobsdetail": {
|
||||
"claimdetail": "Détails de la réclamation",
|
||||
"dates": "Rendez-vous",
|
||||
"financials": "Financiers",
|
||||
"insurance": "Assurance",
|
||||
"labor": "La main d'oeuvre",
|
||||
"partssublet": "Pièces / Sous-location",
|
||||
"repairdata": "Données de réparation"
|
||||
},
|
||||
"profilesidebar": {
|
||||
"profile": "Mon profil",
|
||||
"shops": "Mes boutiques"
|
||||
@@ -113,11 +212,24 @@
|
||||
"newnoteplaceholder": "Ajouter une note..."
|
||||
},
|
||||
"successes": {
|
||||
"created": "Remarque créée avec succès.",
|
||||
"create": "Remarque créée avec succès.",
|
||||
"deleted": "Remarque supprimée avec succès.",
|
||||
"updated": "Remarque mise à jour avec succès."
|
||||
}
|
||||
},
|
||||
"owners": {
|
||||
"fields": {
|
||||
"ownr_addr1": "Adresse",
|
||||
"ownr_city": "Ville",
|
||||
"ownr_ea": "Email",
|
||||
"ownr_fn": "Prénom",
|
||||
"ownr_ln": "Nom de famille",
|
||||
"ownr_ph1": ""
|
||||
},
|
||||
"labels": {
|
||||
"existing_owners": "Propriétaires existants"
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
"errors": {
|
||||
"state": "Erreur lors de la lecture de l'état de la page. Rafraichissez, s'il vous plait."
|
||||
@@ -130,6 +242,11 @@
|
||||
"jobsdocuments": "Documents de travail {{ro_number}} | $ t (titres.app)",
|
||||
"profile": "Mon profil | $t(titles.app)"
|
||||
},
|
||||
"user": {
|
||||
"actions": {
|
||||
"signout": "Déconnexion"
|
||||
}
|
||||
},
|
||||
"vehicles": {
|
||||
"fields": {
|
||||
"plate_no": "Plaque d'immatriculation"
|
||||
|
||||
13
client/src/utils/CurrencyFormatter.jsx
Normal file
13
client/src/utils/CurrencyFormatter.jsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from "react";
|
||||
import NumberFormat from "react-number-format";
|
||||
|
||||
export default function CurrencyFormatter(props) {
|
||||
return (
|
||||
<NumberFormat
|
||||
thousandSeparator={true}
|
||||
prefix={"$"}
|
||||
value={props.children}
|
||||
displayType={"text"}
|
||||
/>
|
||||
);
|
||||
}
|
||||
10
client/src/utils/DateFormatter.jsx
Normal file
10
client/src/utils/DateFormatter.jsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from "react";
|
||||
import Moment from "react-moment";
|
||||
|
||||
export function DateFormatter(props) {
|
||||
return <Moment format="MM/DD/YYYY">{props.children || ""}</Moment>;
|
||||
}
|
||||
|
||||
export function DateTimeFormatter(props) {
|
||||
return <Moment format="MM/DD/YYYY @ HH:mm">{props.children || ""}</Moment>;
|
||||
}
|
||||
13
client/src/utils/DocHelpers.js
Normal file
13
client/src/utils/DocHelpers.js
Normal file
@@ -0,0 +1,13 @@
|
||||
export const generateCdnThumb = key => {
|
||||
const imageRequest = JSON.stringify({
|
||||
bucket: process.env.REACT_APP_S3_BUCKET,
|
||||
key: key,
|
||||
edits: {
|
||||
resize: {
|
||||
height: 100,
|
||||
width: 100
|
||||
}
|
||||
}
|
||||
});
|
||||
return `${process.env.REACT_APP_S3_CDN}/${btoa(imageRequest)}`;
|
||||
};
|
||||
@@ -79,6 +79,15 @@
|
||||
"@apollo/react-hooks" "^3.1.3"
|
||||
tslib "^1.10.0"
|
||||
|
||||
"@apollo/react-testing@^3.1.3":
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@apollo/react-testing/-/react-testing-3.1.3.tgz#d8dd318f58fb6a404976bfa3f8a79e976a5c6562"
|
||||
integrity sha512-58R7gROl4TZMHm5kS76Nof9FfZhD703AU3SmJTA2f7naiMqC9Qd8pZ4oNCBafcab0SYN//UtWvLcluK5O7V/9g==
|
||||
dependencies:
|
||||
"@apollo/react-common" "^3.1.3"
|
||||
fast-json-stable-stringify "^2.0.0"
|
||||
tslib "^1.10.0"
|
||||
|
||||
"@babel/code-frame@7.5.5", "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.5.5":
|
||||
version "7.5.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.5.5.tgz#bc0782f6d69f7b7d49531219699b988f669a8f9d"
|
||||
@@ -11051,6 +11060,8 @@ rxjs@^6.4.0, rxjs@^6.5.3:
|
||||
version "6.5.4"
|
||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.4.tgz#e0777fe0d184cec7872df147f303572d414e211c"
|
||||
integrity sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||
version "5.1.2"
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
endpoint: https://bodyshop-dev-db.herokuapp.com
|
||||
#endpoint: https://bodyshop-staging-db.herokuapp.com/
|
||||
@@ -0,0 +1,3 @@
|
||||
- args:
|
||||
sql: ALTER TABLE "public"."documents" DROP COLUMN "key";
|
||||
type: run_sql
|
||||
@@ -0,0 +1,3 @@
|
||||
- args:
|
||||
sql: ALTER TABLE "public"."documents" ADD COLUMN "key" text NOT NULL DEFAULT '0';
|
||||
type: run_sql
|
||||
@@ -0,0 +1,36 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: documents
|
||||
schema: public
|
||||
type: drop_insert_permission
|
||||
- args:
|
||||
permission:
|
||||
check:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
columns:
|
||||
- name
|
||||
- thumb_url
|
||||
- uploaded_by
|
||||
- url
|
||||
- created_at
|
||||
- updated_at
|
||||
- id
|
||||
- jobid
|
||||
localPresets:
|
||||
- key: ""
|
||||
value: ""
|
||||
set: {}
|
||||
role: user
|
||||
table:
|
||||
name: documents
|
||||
schema: public
|
||||
type: create_insert_permission
|
||||
@@ -0,0 +1,37 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: documents
|
||||
schema: public
|
||||
type: drop_insert_permission
|
||||
- args:
|
||||
permission:
|
||||
check:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
columns:
|
||||
- created_at
|
||||
- id
|
||||
- jobid
|
||||
- key
|
||||
- name
|
||||
- thumb_url
|
||||
- updated_at
|
||||
- uploaded_by
|
||||
- url
|
||||
localPresets:
|
||||
- key: ""
|
||||
value: ""
|
||||
set: {}
|
||||
role: user
|
||||
table:
|
||||
name: documents
|
||||
schema: public
|
||||
type: create_insert_permission
|
||||
@@ -0,0 +1,34 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: documents
|
||||
schema: public
|
||||
type: drop_select_permission
|
||||
- args:
|
||||
permission:
|
||||
allow_aggregations: false
|
||||
columns:
|
||||
- name
|
||||
- thumb_url
|
||||
- uploaded_by
|
||||
- url
|
||||
- created_at
|
||||
- updated_at
|
||||
- id
|
||||
- jobid
|
||||
computed_fields: []
|
||||
filter:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
role: user
|
||||
table:
|
||||
name: documents
|
||||
schema: public
|
||||
type: create_select_permission
|
||||
@@ -0,0 +1,35 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: documents
|
||||
schema: public
|
||||
type: drop_select_permission
|
||||
- args:
|
||||
permission:
|
||||
allow_aggregations: false
|
||||
columns:
|
||||
- created_at
|
||||
- id
|
||||
- jobid
|
||||
- key
|
||||
- name
|
||||
- thumb_url
|
||||
- updated_at
|
||||
- uploaded_by
|
||||
- url
|
||||
computed_fields: []
|
||||
filter:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
role: user
|
||||
table:
|
||||
name: documents
|
||||
schema: public
|
||||
type: create_select_permission
|
||||
@@ -0,0 +1,36 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: documents
|
||||
schema: public
|
||||
type: drop_update_permission
|
||||
- args:
|
||||
permission:
|
||||
columns:
|
||||
- name
|
||||
- thumb_url
|
||||
- uploaded_by
|
||||
- url
|
||||
- created_at
|
||||
- updated_at
|
||||
- id
|
||||
- jobid
|
||||
filter:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
localPresets:
|
||||
- key: ""
|
||||
value: ""
|
||||
set: {}
|
||||
role: user
|
||||
table:
|
||||
name: documents
|
||||
schema: public
|
||||
type: create_update_permission
|
||||
@@ -0,0 +1,37 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: documents
|
||||
schema: public
|
||||
type: drop_update_permission
|
||||
- args:
|
||||
permission:
|
||||
columns:
|
||||
- created_at
|
||||
- id
|
||||
- jobid
|
||||
- key
|
||||
- name
|
||||
- thumb_url
|
||||
- updated_at
|
||||
- uploaded_by
|
||||
- url
|
||||
filter:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
localPresets:
|
||||
- key: ""
|
||||
value: ""
|
||||
set: {}
|
||||
role: user
|
||||
table:
|
||||
name: documents
|
||||
schema: public
|
||||
type: create_update_permission
|
||||
1
hasura/migrations/1579818641451_enable_pg_trm/down.yaml
Normal file
1
hasura/migrations/1579818641451_enable_pg_trm/down.yaml
Normal file
@@ -0,0 +1 @@
|
||||
[]
|
||||
4
hasura/migrations/1579818641451_enable_pg_trm/up.yaml
Normal file
4
hasura/migrations/1579818641451_enable_pg_trm/up.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
- args:
|
||||
cascade: true
|
||||
sql: "CREATE EXTENSION pg_trgm;\r\n"
|
||||
type: run_sql
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1,7 @@
|
||||
- args:
|
||||
cascade: true
|
||||
sql: "CREATE INDEX jobs_gin_idx ON jobs\r\nUSING GIN ((est_number || ' ' || ro_number
|
||||
\ || ' ' || clm_no || ' ' || ownr_ln || ' ' || ownr_fn || ' ' || ownr_ph1
|
||||
\r\n|| ' ' || ownr_ea \r\n|| ' ' || insd_ln \r\n|| ' ' || insd_fn \r\n|| ' '
|
||||
|| insd_ea \r\n|| ' ' || insd_ph1 ) gin_trgm_ops);"
|
||||
type: run_sql
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
16
hasura/migrations/1579819439525_search_jobs_function/up.yaml
Normal file
16
hasura/migrations/1579819439525_search_jobs_function/up.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
- args:
|
||||
cascade: true
|
||||
sql: "CREATE FUNCTION search_jobs(search text)\r\nRETURNS SETOF jobs AS $$\r\n
|
||||
\ SELECT *\r\n FROM jobs\r\n WHERE\r\n search <% (est_number ||
|
||||
' ' || ro_number || ' ' || clm_no || ' ' || ownr_ln || ' ' || ownr_fn ||
|
||||
' ' || ownr_ph1 \r\n|| ' ' || ownr_ea \r\n|| ' ' || insd_ln \r\n|| ' ' || insd_fn
|
||||
\r\n|| ' ' || insd_ea \r\n|| ' ' || insd_ph1 )\r\n ORDER BY\r\n similarity(search,
|
||||
(est_number || ' ' || ro_number || ' ' || clm_no || ' ' || ownr_ln || '
|
||||
' || ownr_fn || ' ' || ownr_ph1 \r\n|| ' ' || ownr_ea \r\n|| ' ' || insd_ln
|
||||
\r\n|| ' ' || insd_fn \r\n|| ' ' || insd_ea \r\n|| ' ' || insd_ph1 )) DESC\r\n
|
||||
\ LIMIT 50;\r\n$$ LANGUAGE sql STABLE;"
|
||||
type: run_sql
|
||||
- args:
|
||||
name: search_jobs
|
||||
schema: public
|
||||
type: track_function
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1,4 @@
|
||||
- args:
|
||||
cascade: true
|
||||
sql: 'drop index jobs_gin_idx '
|
||||
type: run_sql
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1,4 @@
|
||||
- args:
|
||||
cascade: true
|
||||
sql: drop function search_jobs
|
||||
type: run_sql
|
||||
1
hasura/migrations/1579821113991_new_jobs_index/down.yaml
Normal file
1
hasura/migrations/1579821113991_new_jobs_index/down.yaml
Normal file
@@ -0,0 +1 @@
|
||||
[]
|
||||
7
hasura/migrations/1579821113991_new_jobs_index/up.yaml
Normal file
7
hasura/migrations/1579821113991_new_jobs_index/up.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
- args:
|
||||
cascade: true
|
||||
sql: CREATE INDEX jobs_search_idx ON jobs USING gin (est_number gin_trgm_ops,
|
||||
ro_number gin_trgm_ops, clm_no gin_trgm_ops, ownr_ln gin_trgm_ops, ownr_fn gin_trgm_ops,
|
||||
ownr_ph1 gin_trgm_ops, ownr_ea gin_trgm_ops, insd_ln gin_trgm_ops, insd_fn gin_trgm_ops,
|
||||
insd_ea gin_trgm_ops, insd_ph1 gin_trgm_ops);
|
||||
type: run_sql
|
||||
1
hasura/migrations/1579823064149_drop_index/down.yaml
Normal file
1
hasura/migrations/1579823064149_drop_index/down.yaml
Normal file
@@ -0,0 +1 @@
|
||||
[]
|
||||
4
hasura/migrations/1579823064149_drop_index/up.yaml
Normal file
4
hasura/migrations/1579823064149_drop_index/up.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
- args:
|
||||
cascade: true
|
||||
sql: 'drop INDEX jobs_search_idx '
|
||||
type: run_sql
|
||||
@@ -0,0 +1,3 @@
|
||||
- args:
|
||||
sql: ALTER TABLE "public"."jobs" DROP COLUMN "search_idx_col";
|
||||
type: run_sql
|
||||
@@ -0,0 +1,3 @@
|
||||
- args:
|
||||
sql: ALTER TABLE "public"."jobs" ADD COLUMN "search_idx_col" tsvector NULL;
|
||||
type: run_sql
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
15
hasura/migrations/1579825076007_job_search_function/up.yaml
Normal file
15
hasura/migrations/1579825076007_job_search_function/up.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
- args:
|
||||
cascade: true
|
||||
sql: "CREATE FUNCTION search_jobs(search text)\r\nRETURNS SETOF jobs AS $$\r\n
|
||||
\ SELECT *\r\n FROM jobs\r\n WHERE\r\n ownr_fn ilike ('%' || search
|
||||
|| '%')\r\n OR ownr_ln ilike ('%' || search || '%')\r\n OR ro_number
|
||||
ilike ('%' || search || '%')\r\n OR est_number ilike ('%' || search ||
|
||||
'%')\r\n OR clm_no ilike ('%' || search || '%')\r\n OR ownr_ph1 ilike
|
||||
('%' || search || '%')\r\n OR ownr_ea ilike ('%' || search || '%')\r\n
|
||||
\ OR insd_ln ilike ('%' || search || '%')\r\n OR insd_fn ilike ('%'
|
||||
|| search || '%')\r\n$$ LANGUAGE sql STABLE;\r\n"
|
||||
type: run_sql
|
||||
- args:
|
||||
name: search_jobs
|
||||
schema: public
|
||||
type: track_function
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user