Merge branch 'dev-patrick' of https://bitbucket.org/snaptsoft/bodyshop into dev-patrick

This commit is contained in:
Patrick Fic
2020-04-13 20:48:52 -07:00
50 changed files with 1807 additions and 355 deletions

View File

@@ -1,4 +1,4 @@
<babeledit_project version="1.2" be_version="2.6.1">
<babeledit_project be_version="2.6.1" version="1.2">
<!--
BabelEdit project file
@@ -4807,6 +4807,27 @@
<folder_node>
<name>actions</name>
<children>
<concept_node>
<name>edit</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>receive</name>
<definition_loaded>false</definition_loaded>
@@ -5122,7 +5143,7 @@
<name>successes</name>
<children>
<concept_node>
<name>creating</name>
<name>created</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
@@ -5456,6 +5477,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>total</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>unq_seq</name>
<definition_loaded>false</definition_loaded>
@@ -8928,6 +8970,27 @@
<folder_node>
<name>header</name>
<children>
<concept_node>
<name>accounting</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>activejobs</name>
<definition_loaded>false</definition_loaded>
@@ -9075,6 +9138,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>enterinvoices</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>home</name>
<definition_loaded>false</definition_loaded>
@@ -9096,6 +9180,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>invoices</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>jobs</name>
<definition_loaded>false</definition_loaded>
@@ -10740,6 +10845,368 @@
</translation>
</translations>
</concept_node>
<folder_node>
<name>bc</name>
<children>
<concept_node>
<name>availablejobs</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>contracts</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>contracts-create</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>contracts-detail</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>courtesycars</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>courtesycars-detail</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>courtesycars-new</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>jobs</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>jobs-active</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>jobs-detail</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>jobs-new</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>owner-detail</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>owners</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>schedule</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>vehicle-details</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>vehicles</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
<concept_node>
<name>contracts</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>contracts-create</name>
<definition_loaded>false</definition_loaded>
@@ -10782,6 +11249,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>courtesycars</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>courtesycars-create</name>
<definition_loaded>false</definition_loaded>
@@ -10971,6 +11459,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>owners-detail</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>profile</name>
<definition_loaded>false</definition_loaded>

View File

@@ -28,6 +28,7 @@
"react-barcode": "^1.4.0",
"react-big-calendar": "^0.24.1",
"react-dom": "^16.13.1",
"react-ga": "^2.7.0",
"react-grid-gallery": "^0.5.5",
"react-grid-layout": "^0.18.3",
"react-html-email": "^3.0.0",

View File

@@ -0,0 +1,28 @@
import React from "react";
import { Breadcrumb } from "antd";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBreadcrumbs } from "../../redux/application/application.selectors";
import { Link } from "react-router-dom";
const mapStateToProps = createStructuredSelector({
breadcrumbs: selectBreadcrumbs,
});
export function BreadCrumbs({ breadcrumbs }) {
return (
<Breadcrumb>
<Breadcrumb.Item>Home</Breadcrumb.Item>
{breadcrumbs.map((item) =>
item.link ? (
<Breadcrumb.Item key={item.label}>
<Link to={item.link}>{item.label} </Link>
</Breadcrumb.Item>
) : (
<Breadcrumb.Item key={item.label}>{item.label}</Breadcrumb.Item>
)
)}
</Breadcrumb>
);
}
export default connect(mapStateToProps, null)(BreadCrumbs);

View File

@@ -2,13 +2,14 @@ import { UploadOutlined } from "@ant-design/icons";
import { Button, Upload } from "antd";
import React from "react";
export default function DocumentsUploadComponent({ handleUpload }) {
export default function DocumentsUploadComponent({ handleUpload, UploadRef }) {
return (
<div>
<Upload
multiple={true}
customRequest={handleUpload}
accept="audio/*,video/*,image/*"
ref={UploadRef}
>
<Button>
<UploadOutlined /> Click to Upload

View File

@@ -1,6 +1,6 @@
import { notification } from "antd";
import axios from "axios";
import React from "react";
import React, { useRef } from "react";
import { useMutation } from "@apollo/react-hooks";
import { useTranslation } from "react-i18next";
import Resizer from "react-image-file-resizer";
@@ -9,30 +9,29 @@ import { createStructuredSelector } from "reselect";
import { INSERT_NEW_DOCUMENT } from "../../graphql/documents.queries";
import {
selectBodyshop,
selectCurrentUser
selectCurrentUser,
} from "../../redux/user/user.selectors";
import { generateCdnThumb } from "../../utils/DocHelpers";
import DocumentsUploadComponent from "./documents-upload.component";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
bodyshop: selectBodyshop
bodyshop: selectBodyshop,
});
export default connect(
mapStateToProps,
null
)(function DocumentsUploadContainer({
export function DocumentsUploadContainer({
jobId,
invoiceId,
currentUser,
bodyshop,
callbackAfterUpload
callbackAfterUpload,
onChange,
}) {
const { t } = useTranslation();
const [insertNewDocument] = useMutation(INSERT_NEW_DOCUMENT);
const UploadRef = useRef(null);
const handleUpload = ev => {
const handleUpload = (ev) => {
const { onError, onSuccess, onProgress } = ev;
//If PDF, upload directly.
//If JPEG, resize and upload.
@@ -49,7 +48,7 @@ export default connect(
"PNG",
75,
0,
uri => {
(uri) => {
let file = new File([uri], ev.file.name, {});
file.uid = ev.file.uid;
uploadToS3(key, file.type, file, onError, onSuccess, onProgress);
@@ -69,9 +68,9 @@ export default connect(
axios
.post("/sign_s3", {
fileName,
fileType
fileType,
})
.then(response => {
.then((response) => {
var returnData = response.data.data.returnData;
var signedRequest = returnData.signedRequest;
var url = returnData.url;
@@ -79,16 +78,16 @@ export default connect(
// Put the fileType in the headers for the upload
var options = {
headers: {
"Content-Type": fileType
"Content-Type": fileType,
},
onUploadProgress: e => {
onUploadProgress: (e) => {
onProgress({ percent: (e.loaded / e.total) * 100 });
}
},
};
axios
.put(signedRequest, file, options)
.then(response => {
.then((response) => {
insertNewDocument({
variables: {
docInput: [
@@ -101,43 +100,55 @@ export default connect(
? "application/pdf"
: generateCdnThumb(fileName),
key: fileName,
invoiceid: invoiceId
}
]
}
}).then(r => {
invoiceid: invoiceId,
},
],
},
}).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
key: r.data.insert_documents.returning[0].key,
});
notification["success"]({
message: t("documents.successes.insert")
message: t("documents.successes.insert"),
});
if (callbackAfterUpload) {
callbackAfterUpload();
}
console.log("ref", UploadRef.current.state.fileList);
if (onChange) {
//Used in a form.
onChange(UploadRef.current.state.fileList);
}
});
})
.catch(error => {
.catch((error) => {
onError(error);
notification["error"]({
message: t("documents.errors.insert", {
message: JSON.stringify(error)
})
message: JSON.stringify(error),
}),
});
});
})
.catch(error => {
.catch((error) => {
notification["error"]({
message: t("documents.errors.getpresignurl", {
message: JSON.stringify(error)
})
message: JSON.stringify(error),
}),
});
});
};
return <DocumentsUploadComponent handleUpload={handleUpload} />;
});
return (
<DocumentsUploadComponent
handleUpload={handleUpload}
UploadRef={UploadRef}
/>
);
}
export default connect(mapStateToProps, null)(DocumentsUploadContainer);

View File

@@ -1,4 +1,12 @@
import Icon, { CarFilled, FileAddFilled, FileFilled, GlobalOutlined, HomeFilled, TeamOutlined } from "@ant-design/icons";
import Icon, {
CarFilled,
FileAddFilled,
FileFilled,
GlobalOutlined,
HomeFilled,
TeamOutlined,
DollarCircleFilled,
} from "@ant-design/icons";
import { Avatar, Col, Menu, Row } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
@@ -7,14 +15,26 @@ import { Link } from "react-router-dom";
import UserImage from "../../assets/User.svg";
import ManageSignInButton from "../manage-sign-in-button/manage-sign-in-button.component";
export default ({
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { setModalContext } from "../../redux/modals/modals.actions";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
});
const mapDispatchToProps = (dispatch) => ({
setInvoiceEnterContext: (context) =>
dispatch(setModalContext({ context: context, modal: "invoiceEnter" })),
});
function Header({
landingHeader,
selectedNavItem,
logo,
handleMenuClick,
currentUser,
signOutStart
}) => {
signOutStart,
setInvoiceEnterContext,
}) {
const { t } = useTranslation();
//TODO Add
@@ -159,6 +179,30 @@ export default ({
</Menu.Item>
</Menu.SubMenu>
<Menu.SubMenu
title={
<span>
<DollarCircleFilled />
<span>{t("menus.header.accounting")}</span>
</span>
}
>
<Menu.Item
key="enterinvoices"
onClick={() => {
setInvoiceEnterContext({
actions: {},
context: {},
});
}}
>
{t("menus.header.enterinvoices")}
</Menu.Item>
<Menu.Item key="invoices">
<Link to="/manage/invoices">{t("menus.header.invoices")}</Link>
</Menu.Item>
</Menu.SubMenu>
<Menu.SubMenu title={t("menus.header.shop")}>
<Menu.Item key="shop">
<Link to="/manage/shop">{t("menus.header.shop_config")}</Link>
@@ -217,4 +261,6 @@ export default ({
</Col>
</Row>
);
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Header);

View File

@@ -5,6 +5,7 @@ import CurrencyInput from "../form-items-formatted/currency-form-item.component"
import JobSearchSelect from "../job-search-select/job-search-select.component";
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
import InvoiceEnterModalLinesComponent from "./invoice-enter-modal.lines.component";
import DocumentsUploadContainer from "../documents-upload/documents-upload.container";
export default function InvoiceEnterModalComponent({
form,
@@ -12,7 +13,7 @@ export default function InvoiceEnterModalComponent({
vendorAutoCompleteOptions,
lineData,
responsibilityCenters,
loadLines
loadLines,
}) {
const { t } = useTranslation();
@@ -31,8 +32,8 @@ export default function InvoiceEnterModalComponent({
rules={[
{
required: true,
message: t("general.validation.required")
}
message: t("general.validation.required"),
},
]}
>
<JobSearchSelect
@@ -50,8 +51,8 @@ export default function InvoiceEnterModalComponent({
rules={[
{
required: true,
message: t("general.validation.required")
}
message: t("general.validation.required"),
},
]}
>
<VendorSearchSelect
@@ -67,8 +68,8 @@ export default function InvoiceEnterModalComponent({
rules={[
{
required: true,
message: t("general.validation.required")
}
message: t("general.validation.required"),
},
]}
>
<Input />
@@ -79,8 +80,8 @@ export default function InvoiceEnterModalComponent({
rules={[
{
required: true,
message: t("general.validation.required")
}
message: t("general.validation.required"),
},
]}
>
<DatePicker />
@@ -98,8 +99,8 @@ export default function InvoiceEnterModalComponent({
rules={[
{
required: true,
message: t("general.validation.required")
}
message: t("general.validation.required"),
},
]}
>
<CurrencyInput />
@@ -111,6 +112,20 @@ export default function InvoiceEnterModalComponent({
form={form}
responsibilityCenters={responsibilityCenters}
/>
<Form.Item
name="upload"
label="Upload"
>
<DocumentsUploadContainer jobId={form.getFieldValue("jobid")} />
</Form.Item>
<button
onClick={() => {
console.log(form.getFieldsValue());
}}
>
a
</button>
</div>
);
}

View File

@@ -1,6 +1,6 @@
import { useLazyQuery, useMutation, useQuery } from "@apollo/react-hooks";
import { Form, Modal, notification } from "antd";
import React, { useState } from "react";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
@@ -28,7 +28,6 @@ function InvoiceEnterModalContainer({
}) {
const [form] = Form.useForm();
const { t } = useTranslation();
const linesState = useState([]);
const [insertInvoice] = useMutation(INSERT_NEW_INVOICE);
@@ -67,6 +66,9 @@ function InvoiceEnterModalContainer({
notification["success"]({
message: t("invoices.successes.created"),
});
if (invoiceEnterModal.actions.refetch)
invoiceEnterModal.actions.refetch();
toggleModalVisible();
})
.catch((error) => {
@@ -82,8 +84,6 @@ function InvoiceEnterModalContainer({
toggleModalVisible();
};
console.log("invoiceEnterModal", invoiceEnterModal);
return (
<Modal
title={
@@ -116,7 +116,6 @@ function InvoiceEnterModalContainer({
VendorAutoCompleteData && VendorAutoCompleteData.vendors
}
loadLines={loadLines}
linesState={linesState}
lineData={lineData ? lineData.joblines : null}
responsibilityCenters={bodyshop.md_responsibility_centers || null}
/>

View File

@@ -1,60 +1,146 @@
import { Table } from "antd";
import { Button, Descriptions, Table } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { alphaSort } from "../../utils/sorters";
import { Link } from "react-router-dom";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter";
export default function InvoicesListTableComponent({ loading, invoices }) {
const [state, setState] = useState({
sortedInfo: {}
});
import { alphaSort } from "../../utils/sorters";
export default function InvoicesListTableComponent({
loading,
invoices,
selectedInvoice,
handleOnRowClick,
}) {
const { t } = useTranslation();
const [state, setState] = useState({
sortedInfo: {},
});
const columns = [
{
title: t("invoices.fields.vendorname"),
dataIndex: "vendorname",
key: "vendorname",
// onFilter: (value, record) => record.ro_number.includes(value),
// filteredValue: state.filteredInfo.text || null,
sorter: (a, b) => alphaSort(a.vendor.name, b.vendor.name),
sortOrder:
state.sortedInfo.columnKey === "vendorname" && state.sortedInfo.order,
//ellipsis: true,
render: (text, record) => <span>{record.vendor.name}</span>
render: (text, record) => <span>{record.vendor.name}</span>,
},
{
title: t("invoices.fields.invoice_number"),
dataIndex: "invoice_number",
key: "invoice_number",
// onFilter: (value, record) => record.ro_number.includes(value),
// filteredValue: state.filteredInfo.text || null,
sorter: (a, b) => alphaSort(a.invoice_number, b.invoice_number),
sortOrder:
state.sortedInfo.columnKey === "invoice_number" &&
state.sortedInfo.order
//ellipsis: true,
state.sortedInfo.order,
},
{
title: t("invoices.fields.date"),
dataIndex: "date",
key: "date",
// onFilter: (value, record) => record.ro_number.includes(value),
// filteredValue: state.filteredInfo.text || null,
sorter: (a, b) => a.date - b.date,
sortOrder:
state.sortedInfo.columnKey === "date" && state.sortedInfo.order,
//ellipsis: true,
render: (text, record) => <DateFormatter>{record.date}</DateFormatter>
}
render: (text, record) => <DateFormatter>{record.date}</DateFormatter>,
},
{
title: t("invoices.fields.total"),
dataIndex: "total",
key: "total",
sorter: (a, b) => a.total - b.total,
sortOrder:
state.sortedInfo.columnKey === "total" && state.sortedInfo.order,
render: (text, record) => (
<CurrencyFormatter>{record.total}</CurrencyFormatter>
),
},
{
title: t("general.labels.actions"),
dataIndex: "actions",
key: "actions",
render: (text, record) => (
<Link to={`/manage/invoices/${record.id}`}>
<Button>{t("invoices.actions.edit")}</Button>
</Link>
),
},
];
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
};
const rowExpander = record => (
<div style={{ margin: 0 }}>Invoice details</div>
);
const rowExpander = (record) => {
const columns = [
{
title: t("invoicelines.fields.line_desc"),
dataIndex: "line_desc",
key: "line_desc",
sorter: (a, b) => alphaSort(a.line_desc, b.line_desc),
sortOrder:
state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order,
},
{
title: t("invoicelines.fields.retail"),
dataIndex: "actual_price",
key: "actual_price",
sorter: (a, b) => a.actual_price - b.actual_price,
sortOrder:
state.sortedInfo.columnKey === "actual_price" &&
state.sortedInfo.order,
render: (text, record) => (
<CurrencyFormatter>{record.actual_price}</CurrencyFormatter>
),
},
{
title: t("invoicelines.fields.actual_cost"),
dataIndex: "actual_cost",
key: "actual_cost",
sorter: (a, b) => a.actual_cost - b.actual_cost,
sortOrder:
state.sortedInfo.columnKey === "actual_cost" &&
state.sortedInfo.order,
render: (text, record) => (
<CurrencyFormatter>{record.actual_cost}</CurrencyFormatter>
),
},
{
title: t("invoicelines.fields.cost_center"),
dataIndex: "cost_center",
key: "cost_center",
sorter: (a, b) => alphaSort(a.cost_center, b.cost_center),
sortOrder:
state.sortedInfo.columnKey === "cost_center" &&
state.sortedInfo.order,
},
];
return (
<div>
<Descriptions title="User Info">
<Descriptions.Item label="UserName">Zhou Maomao</Descriptions.Item>
<Descriptions.Item label="Telephone">1810000000</Descriptions.Item>
<Descriptions.Item label="Live">Hangzhou, Zhejiang</Descriptions.Item>
<Descriptions.Item label="Remark">empty</Descriptions.Item>
<Descriptions.Item label="Address">
No. 18, Wantang Road, Xihu District, Hangzhou, Zhejiang, China
</Descriptions.Item>
</Descriptions>
<Table
size="small"
pagination={{ position: "top", defaultPageSize: 25 }}
columns={columns.map((item) => ({ ...item }))}
rowKey="id"
dataSource={record.invoicelines}
/>
</div>
);
};
return (
<Table
@@ -62,10 +148,34 @@ export default function InvoicesListTableComponent({ loading, invoices }) {
size="small"
expandedRowRender={rowExpander}
pagination={{ position: "top", defaultPageSize: 25 }}
columns={columns.map(item => ({ ...item }))}
columns={columns.map((item) => ({ ...item }))}
rowKey="id"
dataSource={invoices}
onChange={handleTableChange}
expandable={{
expandedRowKeys: [selectedInvoice],
onExpand: (expanded, record) => {
handleOnRowClick(expanded ? record : null);
},
}}
rowSelection={{
onSelect: (record) => {
handleOnRowClick(record);
},
selectedRowKeys: [selectedInvoice],
type: "radio",
}}
onRow={(record, rowIndex) => {
return {
onClick: (event) => {
handleOnRowClick(record);
}, // click row
onDoubleClick: (event) => {}, // double click row
onContextMenu: (event) => {}, // right button click row
onMouseEnter: (event) => {}, // mouse enter row
onMouseLeave: (event) => {}, // mouse leave row
};
}}
/>
);
}

View File

@@ -11,9 +11,9 @@ import AllocationsBulkAssignmentContainer from "../allocations-bulk-assignment/a
import AllocationsEmployeeLabelContainer from "../allocations-employee-label/allocations-employee-label.container";
import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.container";
const mapDispatchToProps = dispatch => ({
setPartsOrderContext: context =>
dispatch(setModalContext({ context: context, modal: "partsOrder" }))
const mapDispatchToProps = (dispatch) => ({
setPartsOrderContext: (context) =>
dispatch(setModalContext({ context: context, modal: "partsOrder" })),
});
export function JobLinesComponent({
@@ -25,10 +25,10 @@ export function JobLinesComponent({
selectedLines,
setSelectedLines,
jobId,
setJobLineEditContext
setJobLineEditContext,
}) {
const [state, setState] = useState({
sortedInfo: {}
sortedInfo: {},
});
const { t } = useTranslation();
@@ -44,7 +44,7 @@ export function JobLinesComponent({
state.sortedInfo.columnKey === "unq_seq" && state.sortedInfo.order,
//ellipsis: true,
editable: true,
width: 75
width: 75,
},
{
title: t("joblines.fields.line_desc"),
@@ -55,7 +55,6 @@ export function JobLinesComponent({
state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order,
ellipsis: true,
editable: true,
width: "20%"
},
{
title: t("joblines.fields.oem_partno"),
@@ -70,12 +69,12 @@ export function JobLinesComponent({
state.sortedInfo.columnKey === "oem_partno" && state.sortedInfo.order,
ellipsis: true,
editable: true,
width: "10%",
render: (text, record) => (
<span>
{record.oem_partno ? record.oem_partno : record.op_code_desc}
</span>
)
),
},
{
title: t("joblines.fields.part_type"),
@@ -86,7 +85,6 @@ export function JobLinesComponent({
state.sortedInfo.columnKey === "part_type" && state.sortedInfo.order,
ellipsis: true,
editable: true,
width: "7%"
},
{
title: t("joblines.fields.line_ind"),
@@ -94,20 +92,7 @@ export function JobLinesComponent({
key: "line_ind",
sorter: (a, b) => alphaSort(a.line_ind, b.line_ind),
sortOrder:
state.sortedInfo.columnKey === "line_ind" && state.sortedInfo.order
},
{
title: t("joblines.fields.db_price"),
dataIndex: "db_price",
key: "db_price",
sorter: (a, b) => a.db_price - b.db_price,
sortOrder:
state.sortedInfo.columnKey === "db_price" && state.sortedInfo.order,
ellipsis: true,
width: "8%",
render: (text, record) => (
<CurrencyFormatter>{record.db_price}</CurrencyFormatter>
)
state.sortedInfo.columnKey === "line_ind" && state.sortedInfo.order,
},
{
title: t("joblines.fields.act_price"),
@@ -117,10 +102,31 @@ export function JobLinesComponent({
sortOrder:
state.sortedInfo.columnKey === "act_price" && state.sortedInfo.order,
ellipsis: true,
width: "8%",
render: (text, record) => (
<CurrencyFormatter>{record.act_price}</CurrencyFormatter>
)
),
},
{
title: t("joblines.fields.part_qty"),
dataIndex: "part_qty",
key: "part_qty",
ellipsis: true,
},
{
title: t("joblines.fields.total"),
dataIndex: "total",
key: "total",
sorter: (a, b) => a.act_price * a.part_qty - b.act_price * b.part_qty,
sortOrder:
state.sortedInfo.columnKey === "act_price" && state.sortedInfo.order,
ellipsis: true,
render: (text, record) => (
<CurrencyFormatter>
{record.act_price * record.part_qty}
</CurrencyFormatter>
),
},
{
title: t("joblines.fields.mod_lb_hrs"),
@@ -128,7 +134,7 @@ export function JobLinesComponent({
key: "mod_lb_hrs",
sorter: (a, b) => a.mod_lb_hrs - b.mod_lb_hrs,
sortOrder:
state.sortedInfo.columnKey === "mod_lb_hrs" && state.sortedInfo.order
state.sortedInfo.columnKey === "mod_lb_hrs" && state.sortedInfo.order,
},
{
title: t("joblines.fields.status"),
@@ -136,13 +142,13 @@ export function JobLinesComponent({
key: "status",
sorter: (a, b) => alphaSort(a.status, b.status),
sortOrder:
state.sortedInfo.columnKey === "status" && state.sortedInfo.order
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
},
{
title: t("allocations.fields.employee"),
dataIndex: "employee",
key: "employee",
width: "10%",
sorter: (a, b) =>
alphaSort(
a.allocations[0] &&
@@ -157,7 +163,7 @@ export function JobLinesComponent({
render: (text, record) => (
<span>
{record.allocations && record.allocations.length > 0
? record.allocations.map(item => (
? record.allocations.map((item) => (
<AllocationsEmployeeLabelContainer
key={item.id}
refetch={refetch}
@@ -172,7 +178,7 @@ export function JobLinesComponent({
hours={record.mod_lb_hrs}
/>
</span>
)
),
},
{
title: t("general.labels.actions"),
@@ -184,32 +190,21 @@ export function JobLinesComponent({
onClick={() => {
setJobLineEditContext({
actions: { refetch: refetch },
context: record
context: record,
});
}}
>
{t("general.actions.edit")}
</Button>
</span>
)
}
),
},
];
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
};
const formItemLayout = {
labelCol: {
xs: { span: 12 },
sm: { span: 5 }
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 12 }
}
};
return (
<div>
<PartsOrderModalContainer />
@@ -219,7 +214,7 @@ export function JobLinesComponent({
<div>
<Input.Search
placeholder={t("general.labels.search")}
onChange={e => {
onChange={(e) => {
e.preventDefault();
setSearchText(e.target.value);
}}
@@ -231,8 +226,8 @@ export function JobLinesComponent({
actions: { refetch: refetch },
context: {
jobId: jobId,
linesToOrder: selectedLines
}
linesToOrder: selectedLines,
},
});
}}
>
@@ -246,7 +241,7 @@ export function JobLinesComponent({
onClick={() => {
setJobLineEditContext({
actions: { refetch: refetch },
context: { jobid: jobId }
context: { jobid: jobId },
});
}}
>
@@ -255,13 +250,12 @@ export function JobLinesComponent({
</div>
);
}}
{...formItemLayout}
loading={loading}
size="small"
expandedRowRender={record => (
expandedRowRender={(record) => (
<div style={{ margin: 0 }}>
<strong>{t("parts_orders.labels.orderhistory")}</strong>
{record.parts_order_lines.map(item => (
{record.parts_order_lines.map((item) => (
<div key={item.id}>
{`${item.parts_order.order_number || ""} from `}
<Link to={`/manage/shop/vendors/${item.parts_order.vendor.id}`}>
@@ -279,9 +273,9 @@ export function JobLinesComponent({
setSelectedLines(selectedRows);
},
onSelect: (record, selected, selectedRows, nativeEvent) =>
setSelectedLines(selectedRows)
setSelectedLines(selectedRows),
}}
columns={columns.map(item => ({ ...item }))}
columns={columns.map((item) => ({ ...item }))}
rowKey="id"
dataSource={jobLines}
onChange={handleTableChange}

View File

@@ -1,8 +1,7 @@
import { Col, List, Row } from "antd";
import React from "react";
import { List, Row, Col } from "antd";
import { useTranslation } from "react-i18next";
export default function JobsTotalsTableComponent({ totals }) {
const { t } = useTranslation();
//const { t } = useTranslation();
if (!!!totals) return null;
return (

View File

@@ -33,19 +33,18 @@ function CalculateTaxesTotals(job, otherTotals) {
}
}, 0);
console.log("otherTotals", otherTotals);
console.log("job", job);
console.log("parts pst", statePartsTax);
console.log(
"pst on labor",
otherTotals.rates.rates_subtotal * (job.tax_lbr_rt || 0)
);
console.log(
"pst on mat",
(otherTotals.rates.paint_mat.total + otherTotals.rates.shop_mat.total) *
(job.tax_paint_mat_rt || 0)
);
// console.log("otherTotals", otherTotals);
// console.log("job", job);
// console.log("parts pst", statePartsTax);
// console.log(
// "pst on labor",
// otherTotals.rates.rates_subtotal * (job.tax_lbr_rt || 0)
// );
// console.log(
// "pst on mat",
// (otherTotals.rates.paint_mat.total + otherTotals.rates.shop_mat.total) *
// (job.tax_paint_mat_rt || 0)
// );
let ret = {
subtotal: subtotal,
@@ -158,19 +157,23 @@ function CalculateRatesTotals(ratesList, shoprates) {
},
rate_atp: {
rate: shoprates.rate_atp || 0,
hours: jobLines
.filter(
(item) =>
item.mod_lbr_ty !== "LA1" &&
item.mod_lbr_ty !== "LA2" &&
item.mod_lbr_ty !== "LA3" &&
item.mod_lbr_ty !== "LA4" &&
item.mod_lbr_ty !== "LAU" &&
item.mod_lbr_ty !== "LAG" &&
item.mod_lbr_ty !== "LAS" &&
item.mod_lbr_ty !== "LAA"
)
.reduce((acc, value) => acc + value.mod_lb_hrs, 0),
hours:
jobLines.filter((item) => item.line_desc.includes("ATS Amount"))
.length > 0
? jobLines
.filter(
(item) =>
item.mod_lbr_ty !== "LA1" &&
item.mod_lbr_ty !== "LA2" &&
item.mod_lbr_ty !== "LA3" &&
item.mod_lbr_ty !== "LA4" &&
item.mod_lbr_ty !== "LAU" &&
item.mod_lbr_ty !== "LAG" &&
item.mod_lbr_ty !== "LAS" &&
item.mod_lbr_ty !== "LAA"
)
.reduce((acc, value) => acc + value.mod_lb_hrs, 0)
: 0,
},
paint_mat: {
rate: ratesList.rate_mapa,
@@ -193,8 +196,8 @@ function CalculateRatesTotals(ratesList, shoprates) {
subtotal = subtotal + ret[property].hours * ret[property].rate;
if (
property !== "paint_mat" &&
property !== "shop_mat" &&
property !== "rate_atp"
property !== "shop_mat"
//&& property !== "rate_atp"
)
rates_subtotal =
rates_subtotal + ret[property].hours * ret[property].rate;

View File

@@ -1,25 +1,21 @@
import { Button } from "antd";
import React from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { setModalContext } from "../../redux/modals/modals.actions";
import InvoicesListTableComponent from "../invoices-list-table/invoices-list-table.component";
import AlertComponent from "../alert/alert.component";
import InvoicesListTableComponent from "../invoices-list-table/invoices-list-table.component";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
});
const mapDispatchToProps = (dispatch) => ({
setInvoiceEnterContext: (context) =>
dispatch(setModalContext({ context: context, modal: "invoiceEnter" })),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(function JobsDetailPliComponent({
export function JobsDetailPliComponent({
setInvoiceEnterContext,
job,
invoicesQuery,
handleOnRowClick,
selectedInvoice,
}) {
return (
<div>
@@ -40,8 +36,13 @@ export default connect(
) : null}
<InvoicesListTableComponent
loading={invoicesQuery.loading}
handleOnRowClick={handleOnRowClick}
selectedInvoice={selectedInvoice}
invoices={invoicesQuery.data ? invoicesQuery.data.invoices : null}
refetch={invoicesQuery.refetch}
/>
</div>
);
});
}
export default connect(null, mapDispatchToProps)(JobsDetailPliComponent);

View File

@@ -2,11 +2,35 @@ import React from "react";
import { useQuery } from "@apollo/react-hooks";
import JobsDetailPliComponent from "./jobs-detail-pli.component";
import { QUERY_INVOICES_BY_JOBID } from "../../graphql/invoices.queries";
import { useHistory, useLocation } from "react-router-dom";
import queryString from "query-string";
export default function JobsDetailPliContainer({ job }) {
const invoicesQuery = useQuery(QUERY_INVOICES_BY_JOBID, {
variables: { jobid: job.id },
fetchPolicy: "network-only"
});
return <JobsDetailPliComponent job={job} invoicesQuery={invoicesQuery} />;
const search = queryString.parse(useLocation().search);
const history = useHistory();
const handleOnRowClick = (record) => {
if (record) {
if (record.id) {
search.invoiceid = record.id;
history.push({ search: queryString.stringify(search) });
}
} else {
delete search.invoiceid;
history.push({ search: queryString.stringify(search) });
}
};
return (
<JobsDetailPliComponent
job={job}
invoicesQuery={invoicesQuery}
handleOnRowClick={handleOnRowClick}
selectedInvoice={search.invoiceid}
/>
);
}

View File

@@ -8,17 +8,20 @@ import PhoneFormatter from "../../utils/PhoneFormatter";
import { alphaSort } from "../../utils/sorters";
import StartChatButton from "../chat-open-button/chat-open-button.component";
import { useHistory } from "react-router-dom";
import queryString from "query-string";
export default withRouter(function JobsList({
searchTextState,
refetch,
loading,
jobs,
selectedJob
searchParams,
}) {
const { selected } = searchParams;
const [state, setState] = useState({
sortedInfo: {},
filteredInfo: { text: "" }
filteredInfo: { text: "" },
});
const { t } = useTranslation();
@@ -42,7 +45,7 @@ export default withRouter(function JobsList({
<Link to={"/manage/jobs/" + record.id}>
{record.ro_number ? record.ro_number : record.est_number}
</Link>
)
),
},
{
title: t("jobs.fields.owner"),
@@ -61,7 +64,7 @@ export default withRouter(function JobsList({
) : (
<span>{`${record.ownr_fn} ${record.ownr_ln}`}</span>
);
}
},
},
{
title: t("jobs.fields.ownr_ph1"),
@@ -76,7 +79,7 @@ export default withRouter(function JobsList({
<StartChatButton phone={record.ownr_ph1} />
</span>
) : null;
}
},
},
{
title: t("jobs.fields.status"),
@@ -89,7 +92,7 @@ export default withRouter(function JobsList({
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
render: (text, record) => {
return record.status || t("general.labels.na");
}
},
},
{
@@ -101,14 +104,16 @@ export default withRouter(function JobsList({
render: (text, record) => {
return record.vehicleid ? (
<Link to={"/manage/vehicles/" + record.vehicleid}>
{`${record.v_model_yr || ""} ${record.v_make_desc ||
""} ${record.v_model_desc || ""}`}
{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
record.v_model_desc || ""
}`}
</Link>
) : (
<span>{`${record.v_model_yr || ""} ${record.v_make_desc ||
""} ${record.v_model_desc || ""}`}</span>
<span>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
record.v_model_desc || ""
}`}</span>
);
}
},
},
{
title: t("vehicles.fields.plate_no"),
@@ -121,7 +126,7 @@ export default withRouter(function JobsList({
state.sortedInfo.columnKey === "plate_no" && state.sortedInfo.order,
render: (text, record) => {
return record.plate_no ? record.plate_no : "";
}
},
},
{
title: t("jobs.fields.clm_no"),
@@ -138,7 +143,7 @@ export default withRouter(function JobsList({
) : (
t("general.labels.unknown")
);
}
},
},
{
title: t("jobs.fields.clm_total"),
@@ -154,7 +159,7 @@ export default withRouter(function JobsList({
) : (
t("general.labels.unknown")
);
}
},
},
{
title: t("jobs.fields.owner_owing"),
@@ -167,18 +172,24 @@ export default withRouter(function JobsList({
) : (
t("general.labels.unknown")
);
}
}
},
},
];
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
};
const handleOnRowClick = record => {
const handleOnRowClick = (record) => {
if (record) {
if (record.id) {
history.push({ search: `selected=${record.id}` });
console.log("searchParams", searchParams);
history.push({
search: queryString.stringify({
...searchParams,
selected: record.id,
}),
});
}
}
};
@@ -195,7 +206,7 @@ export default withRouter(function JobsList({
</Button>
<Input.Search
placeholder={t("general.labels.search")}
onChange={e => {
onChange={(e) => {
setSearchText(e.target.value);
}}
enterButton
@@ -205,26 +216,26 @@ export default withRouter(function JobsList({
}}
size="small"
pagination={{ position: "top" }}
columns={columns.map(item => ({ ...item }))}
columns={columns.map((item) => ({ ...item }))}
rowKey="id"
dataSource={jobs}
rowSelection={{
onSelect: record => {
onSelect: (record) => {
handleOnRowClick(record);
},
selectedRowKeys: [selectedJob],
type: "radio"
selectedRowKeys: [selected],
type: "radio",
}}
onChange={handleTableChange}
onRow={(record, rowIndex) => {
return {
onClick: event => {
onClick: (event) => {
handleOnRowClick(record);
}, // click row
onDoubleClick: event => {}, // double click row
onContextMenu: event => {}, // right button click row
onMouseEnter: event => {}, // mouse enter row
onMouseLeave: event => {} // mouse leave row
onDoubleClick: (event) => {}, // double click row
onContextMenu: (event) => {}, // right button click row
onMouseEnter: (event) => {}, // mouse enter row
onMouseLeave: (event) => {}, // mouse leave row
};
}}
/>

View File

@@ -2,28 +2,30 @@ import firebase from "firebase/app";
import "firebase/firestore";
import "firebase/auth";
import "firebase/database";
import "firebase/analytics";
const config = JSON.parse(process.env.REACT_APP_FIREBASE_CONFIG);
firebase.initializeApp(config);
export const auth = firebase.auth();
export const firestore = firebase.firestore();
export const analytics = firebase.analytics();
export default firebase;
export const getCurrentUser = () => {
return new Promise((resolve, reject) => {
const unsubscribe = auth.onAuthStateChanged(userAuth => {
const unsubscribe = auth.onAuthStateChanged((userAuth) => {
unsubscribe();
resolve(userAuth);
}, reject);
});
};
export const updateCurrentUser = userDetails => {
export const updateCurrentUser = (userDetails) => {
return new Promise((resolve, reject) => {
const unsubscribe = auth.onAuthStateChanged(userAuth => {
console.log("userDetails", userDetails);
userAuth.updateProfile(userDetails).then(r => {
const unsubscribe = auth.onAuthStateChanged((userAuth) => {
userAuth.updateProfile(userDetails).then((r) => {
unsubscribe();
resolve(userAuth);
});

View File

@@ -10,6 +10,32 @@ export const INSERT_NEW_INVOICE = gql`
}
`;
export const QUERY_ALL_INVOICES_PAGINATED = gql`
query QUERY_ALL_INVOICES_PAGINATED($offset: Int, $limit: Int) {
invoices(offset: $offset, limit: $limit, order_by: { date: desc }) {
id
vendor {
id
name
}
total
invoice_number
date
job {
id
ro_number
}
invoicelines {
actual_price
actual_cost
cost_center
id
line_desc
}
}
}
`;
export const QUERY_INVOICES_BY_JOBID = gql`
query QUERY_INVOICES_BY_JOBID($jobid: uuid!) {
invoices(where: { jobid: { _eq: $jobid } }, order_by: { date: desc }) {
@@ -31,3 +57,31 @@ export const QUERY_INVOICES_BY_JOBID = gql`
}
}
`;
export const QUERY_INVOICE_BY_PK = gql`
query QUERY_INVOICE_BY_PK($invoiceid: uuid!) {
invoices_by_pk(id: $invoiceid) {
due_date
exported
exported_at
id
invoice_number
date
is_credit_memo
jobid
total
updated_at
vendor {
name
discount
}
invoicelines {
id
line_desc
actual_price
actual_cost
cost_center
}
}
}
`;

View File

@@ -435,9 +435,10 @@ export const QUERY_JOB_FINANCIALS = gql`
rate_matd
joblines {
id
line_desc
tax_part
prt_dsmk_p
prt_dsmk_m
prt_dsmk_p
prt_dsmk_m
mod_lbr_ty
act_price
mod_lb_hrs

View File

@@ -9,11 +9,16 @@ import { useTranslation } from "react-i18next";
import { INSERT_NEW_CONTRACT } from "../../graphql/cccontracts.queries";
import { useMutation } from "@apollo/react-hooks";
import { useHistory, useLocation } from "react-router-dom";
import { setBreadcrumbs } from "../../redux/application/application.actions";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
});
export function ContractCreatePageContainer({ bodyshop }) {
export function ContractCreatePageContainer({ bodyshop, setBreadcrumbs }) {
const [form] = Form.useForm();
const { t } = useTranslation();
const history = useHistory();
@@ -24,45 +29,56 @@ export function ContractCreatePageContainer({ bodyshop }) {
);
const [insertContract] = useMutation(INSERT_NEW_CONTRACT);
console.log("location", location);
console.log("test");
const handleFinish = values => {
const handleFinish = (values) => {
if (!!selectedCarState[0] && !!selectedJobState[0]) {
insertContract({
variables: {
contract: {
...values,
courtesycarid: selectedCarState[0],
jobid: selectedJobState[0]
}
}
jobid: selectedJobState[0],
},
},
})
.then(response => {
.then((response) => {
notification["success"]({
message: t("contracts.successes.saved")
message: t("contracts.successes.saved"),
});
history.push(
`/manage/courtesycars/contracts/${response.data.insert_cccontracts.returning[0].id}`
);
})
.catch(error =>
.catch((error) =>
notification["error"]({
message: t("contracts.errors.saving", {
error: JSON.stringify(error)
})
error: JSON.stringify(error),
}),
})
);
} else {
notification["error"]({
message: t("contracts.errors.selectjobandcar")
message: t("contracts.errors.selectjobandcar"),
});
}
};
useEffect(() => {
document.title = t("titles.contracts-create");
}, [t]);
setBreadcrumbs([
{ link: "/manage/courtesycars", label: t("titles.bc.courtesycars") },
{
link: "/manage/courtesycars/contracts",
label: t("titles.bc.contracts"),
},
{
link: "/manage/courtesycars/contracts/new",
label: t("titles.bc.contracts-create"),
},
]);
}, [t, setBreadcrumbs]);
return (
<Form form={form} autoComplete="no" onFinish={handleFinish}>
@@ -73,4 +89,7 @@ export function ContractCreatePageContainer({ bodyshop }) {
</Form>
);
}
export default connect(mapStateToProps, null)(ContractCreatePageContainer);
export default connect(
mapStateToProps,
mapDispatchToProps
)(ContractCreatePageContainer);

View File

@@ -3,23 +3,29 @@ import { Form, notification } from "antd";
import moment from "moment";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useParams } from "react-router-dom";
import AlertComponent from "../../components/alert/alert.component";
import CourtesyCarReturnModalContainer from "../../components/courtesy-car-return-modal/courtesy-car-return-modal.container";
import {
QUERY_CONTRACT_BY_PK,
UPDATE_CONTRACT
UPDATE_CONTRACT,
} from "../../graphql/cccontracts.queries";
import { setBreadcrumbs } from "../../redux/application/application.actions";
import ContractDetailPageComponent from "./contract-detail.page.component";
import CourtesyCarReturnModalContainer from "../../components/courtesy-car-return-modal/courtesy-car-return-modal.container";
export default function ContractDetailPageContainer() {
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
});
export function ContractDetailPageContainer({ setBreadcrumbs }) {
const { t } = useTranslation();
const [updateContract] = useMutation(UPDATE_CONTRACT);
const [form] = Form.useForm();
const { contractId } = useParams();
const { loading, error, data, refetch } = useQuery(QUERY_CONTRACT_BY_PK, {
variables: { id: contractId }
variables: { id: contractId },
});
useEffect(() => {
@@ -28,20 +34,34 @@ export default function ContractDetailPageContainer() {
: error
? t("titles.app")
: t("titles.contracts-detail", {
id: (data && data.cccontracts_by_pk.agreementnumber) || ""
id: (data && data.cccontracts_by_pk.agreementnumber) || "",
});
}, [t, data, error, loading]);
const handleFinish = values => {
setBreadcrumbs([
{ link: "/manage/courtesycars", label: t("titles.bc.courtesycars") },
{
link: "/manage/courtesycars/contracts",
label: t("titles.bc.contracts"),
},
{
link: "/manage/courtesycars/contracts/new",
label: t("titles.bc.contracts-detail", {
number: data.cccontracts_by_pk.agreementnumber || "",
}),
},
]);
}, [t, data, error, loading, setBreadcrumbs]);
const handleFinish = (values) => {
updateContract({
variables: { cccontract: { ...values }, contractId: contractId }
variables: { cccontract: { ...values }, contractId: contractId },
})
.then(response => {
.then((response) => {
notification["success"]({ message: t("contracts.successes.saved") });
})
.catch(error =>
.catch((error) =>
notification["error"]({
message: t("contracts.errors.saving", { error: error })
message: t("contracts.errors.saving", { error: error }),
})
);
};
@@ -76,7 +96,7 @@ export default function ContractDetailPageContainer() {
: null,
driver_dob: data.cccontracts_by_pk.driver_dob
? moment(data.cccontracts_by_pk.driver_dob)
: null
: null,
}
: {}
}
@@ -91,3 +111,4 @@ export default function ContractDetailPageContainer() {
</div>
);
}
export default connect(null, mapDispatchToProps)(ContractDetailPageContainer);

View File

@@ -1,11 +1,29 @@
import React from "react";
import ContractsPageComponent from "./contracts.page.component";
import { useQuery } from "@apollo/react-hooks";
import { QUERY_ACTIVE_CONTRACTS } from "../../graphql/cccontracts.queries";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import AlertComponent from "../../components/alert/alert.component";
import { QUERY_ACTIVE_CONTRACTS } from "../../graphql/cccontracts.queries";
import { setBreadcrumbs } from "../../redux/application/application.actions";
import ContractsPageComponent from "./contracts.page.component";
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
});
export default function ContractsPageContainer() {
export function ContractsPageContainer({ setBreadcrumbs }) {
const { loading, error, data } = useQuery(QUERY_ACTIVE_CONTRACTS);
const { t } = useTranslation();
useEffect(() => {
document.title = t("titles.contracts");
setBreadcrumbs([
{ link: "/manage/courtesycars", label: t("titles.bc.courtesycars") },
{
link: "/manage/courtesycars/contracts",
label: t("titles.bc.contracts"),
},
]);
}, [setBreadcrumbs, t]);
if (error) return <AlertComponent message={error.message} type="error" />;
return (
@@ -17,3 +35,4 @@ export default function ContractsPageContainer() {
</div>
);
}
export default connect(null, mapDispatchToProps)(ContractsPageContainer);

View File

@@ -8,33 +8,43 @@ import { INSERT_NEW_COURTESY_CAR } from "../../graphql/courtesy-car.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CourtesyCarCreateComponent from "./courtesy-car-create.page.component";
import { useHistory } from "react-router-dom";
import { setBreadcrumbs } from "../../redux/application/application.actions";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
bodyshop: selectBodyshop,
});
export function CourtesyCarCreateContainer({ bodyshop }) {
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
});
export function CourtesyCarCreateContainer({ bodyshop, setBreadcrumbs }) {
const [form] = Form.useForm();
const [insertCourtesyCar] = useMutation(INSERT_NEW_COURTESY_CAR);
const { t } = useTranslation();
const history = useHistory();
const handleFinish = values => {
const handleFinish = (values) => {
insertCourtesyCar({
variables: { courtesycar: { ...values, bodyshopid: bodyshop.id } }
variables: { courtesycar: { ...values, bodyshopid: bodyshop.id } },
})
.then(response => {
.then((response) => {
notification["success"]({ message: t("courtesycars.successes.saved") });
history.push(
`/manage/courtesycars/${response.data.insert_courtesycars.returning[0].id}`
);
})
.catch(error => console.log("error", error));
.catch((error) => console.log("error", error));
};
useEffect(() => {
document.title = t("titles.courtesycars-create");
}, [t]);
setBreadcrumbs([
{ link: "/manage/courtesycars", label: t("titles.bc.courtesycars") },
{
link: "/manage/courtesycars/new",
label: t("titles.bc.courtesycars-new"),
},
]);
}, [t, setBreadcrumbs]);
return (
<Form form={form} autoComplete="no" onFinish={handleFinish}>
@@ -42,4 +52,7 @@ export function CourtesyCarCreateContainer({ bodyshop }) {
</Form>
);
}
export default connect(mapStateToProps, null)(CourtesyCarCreateContainer);
export default connect(
mapStateToProps,
mapDispatchToProps
)(CourtesyCarCreateContainer);

View File

@@ -3,19 +3,24 @@ import { Form, notification } from "antd";
import moment from "moment";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useParams } from "react-router-dom";
import AlertComponent from "../../components/alert/alert.component";
import { QUERY_CC_BY_PK, UPDATE_CC } from "../../graphql/courtesy-car.queries";
import { setBreadcrumbs } from "../../redux/application/application.actions";
import CourtesyCarDetailPageComponent from "./courtesy-car-detail.page.component";
export default function CourtesyCarDetailPageContainer() {
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
});
export function CourtesyCarDetailPageContainer({ setBreadcrumbs }) {
const { t } = useTranslation();
const [insertCourtesyCar] = useMutation(UPDATE_CC);
const [form] = Form.useForm();
const { ccId } = useParams();
const { loading, error, data } = useQuery(QUERY_CC_BY_PK, {
variables: { id: ccId }
variables: { id: ccId },
});
useEffect(() => {
@@ -24,20 +29,31 @@ export default function CourtesyCarDetailPageContainer() {
: error
? t("titles.app")
: t("titles.courtesycars-detail", {
id: (data && data.courtesycars_by_pk.vin) || ""
id: (data && data.courtesycars_by_pk.fleet_number) || "",
});
}, [t, data, error, loading]);
setBreadcrumbs([
{ link: "/manage/courtesycars", label: t("titles.bc.courtesycars") },
{
link: `/manage/courtesycars/${
(data && data.courtesycars_by_pk.id) || ""
}`,
label: t("titles.bc.courtesycars-detail", {
number: (data && data.courtesycars_by_pk.fleetnumber) || "",
}),
},
]);
}, [t, data, error, loading, setBreadcrumbs]);
const handleFinish = values => {
const handleFinish = (values) => {
insertCourtesyCar({
variables: { cc: { ...values }, ccId: ccId }
variables: { cc: { ...values }, ccId: ccId },
})
.then(response => {
.then((response) => {
notification["success"]({ message: t("courtesycars.successes.saved") });
})
.catch(error =>
.catch((error) =>
notification["error"]({
message: t("courtesycars.errors.saving", { error: error })
message: t("courtesycars.errors.saving", { error: error }),
})
);
};
@@ -76,7 +92,7 @@ export default function CourtesyCarDetailPageContainer() {
: null,
insuranceexpires: data.courtesycars_by_pk.insuranceexpires
? moment(data.courtesycars_by_pk.insuranceexpires)
: null
: null,
}
: {}
}
@@ -87,3 +103,7 @@ export default function CourtesyCarDetailPageContainer() {
</Form>
);
}
export default connect(
null,
mapDispatchToProps
)(CourtesyCarDetailPageContainer);

View File

@@ -1,12 +1,26 @@
import React from "react";
import React, { useEffect } from "react";
import CourtesyCarsPageComponent from "./courtesy-cars.page.component";
import { useQuery } from "@apollo/react-hooks";
import AlertComponent from "../../components/alert/alert.component";
import { QUERY_ALL_CC } from "../../graphql/courtesy-car.queries";
import { setBreadcrumbs } from "../../redux/application/application.actions";
import { connect } from "react-redux";
import { useTranslation } from "react-i18next";
export default function CourtesyCarsPageContainer() {
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
});
export function CourtesyCarsPageContainer({ setBreadcrumbs }) {
const { loading, error, data } = useQuery(QUERY_ALL_CC);
const { t } = useTranslation();
useEffect(() => {
document.title = t("titles.courtesycars");
setBreadcrumbs([
{ link: "/manage/courtesycars", label: t("titles.bc.courtesycars") },
]);
}, [setBreadcrumbs, t]);
if (error) return <AlertComponent message={error.message} type="error" />;
return (
<CourtesyCarsPageComponent
@@ -15,3 +29,5 @@ export default function CourtesyCarsPageContainer() {
/>
);
}
export default connect(null, mapDispatchToProps)(CourtesyCarsPageContainer);

View File

@@ -0,0 +1,5 @@
import React from "react";
export default function InvoiceDetailPageComponent() {
return <div>Invoice Detail Page Component</div>;
}

View File

@@ -0,0 +1,27 @@
import React from "react";
import { useParams } from "react-router-dom";
import InvoiceDetailPageComponent from "./invoice-detail.page.component";
import { useQuery } from "@apollo/react-hooks";
import { QUERY_INVOICE_BY_PK } from "../../graphql/invoices.queries";
import AlertComponent from "../../components/alert/alert.component";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import { Form } from "antd";
export default function InvoiceDetailPageContainer() {
const { invoiceId } = useParams();
const [form] = Form.useForm();
const { loading, error, data } = useQuery(QUERY_INVOICE_BY_PK, {
variables: { invoiceid: invoiceId },
skip: !!!invoiceId,
});
if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />;
return (
<Form form={form} initialValues={data ? data.invoices_by_pk : {}}>
<InvoiceDetailPageComponent />
</Form>
);
}

View File

@@ -0,0 +1,188 @@
import { Button, Descriptions, Table } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter";
import { alphaSort } from "../../utils/sorters";
export default function InvoicesPageComponent({
loading,
invoices,
selectedInvoice,
handleFetchMore,
handleOnRowClick,
}) {
const { t } = useTranslation();
const [state, setState] = useState({
sortedInfo: {},
});
const columns = [
{
title: t("invoices.fields.vendorname"),
dataIndex: "vendorname",
key: "vendorname",
sorter: (a, b) => alphaSort(a.vendor.name, b.vendor.name),
sortOrder:
state.sortedInfo.columnKey === "vendorname" && state.sortedInfo.order,
render: (text, record) => <span>{record.vendor.name}</span>,
},
{
title: t("invoices.fields.invoice_number"),
dataIndex: "invoice_number",
key: "invoice_number",
sorter: (a, b) => alphaSort(a.invoice_number, b.invoice_number),
sortOrder:
state.sortedInfo.columnKey === "invoice_number" &&
state.sortedInfo.order,
},
{
title: t("invoices.fields.date"),
dataIndex: "date",
key: "date",
sorter: (a, b) => a.date - b.date,
sortOrder:
state.sortedInfo.columnKey === "date" && state.sortedInfo.order,
render: (text, record) => <DateFormatter>{record.date}</DateFormatter>,
},
{
title: t("invoices.fields.total"),
dataIndex: "total",
key: "total",
sorter: (a, b) => a.total - b.total,
sortOrder:
state.sortedInfo.columnKey === "total" && state.sortedInfo.order,
render: (text, record) => (
<CurrencyFormatter>{record.total}</CurrencyFormatter>
),
},
{
title: t("general.labels.actions"),
dataIndex: "actions",
key: "actions",
render: (text, record) => (
<Link to={`/manage/invoices/${record.id}`}>
<Button>{t("invoices.actions.edit")}</Button>
</Link>
),
},
];
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
};
const rowExpander = (record) => {
const columns = [
{
title: t("invoicelines.fields.line_desc"),
dataIndex: "line_desc",
key: "line_desc",
sorter: (a, b) => alphaSort(a.line_desc, b.line_desc),
sortOrder:
state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order,
},
{
title: t("invoicelines.fields.retail"),
dataIndex: "actual_price",
key: "actual_price",
sorter: (a, b) => a.actual_price - b.actual_price,
sortOrder:
state.sortedInfo.columnKey === "actual_price" &&
state.sortedInfo.order,
render: (text, record) => (
<CurrencyFormatter>{record.actual_price}</CurrencyFormatter>
),
},
{
title: t("invoicelines.fields.actual_cost"),
dataIndex: "actual_cost",
key: "actual_cost",
sorter: (a, b) => a.actual_cost - b.actual_cost,
sortOrder:
state.sortedInfo.columnKey === "actual_cost" &&
state.sortedInfo.order,
render: (text, record) => (
<CurrencyFormatter>{record.actual_cost}</CurrencyFormatter>
),
},
{
title: t("invoicelines.fields.cost_center"),
dataIndex: "cost_center",
key: "cost_center",
sorter: (a, b) => alphaSort(a.cost_center, b.cost_center),
sortOrder:
state.sortedInfo.columnKey === "cost_center" &&
state.sortedInfo.order,
},
];
return (
<div>
<Descriptions title="User Info">
<Descriptions.Item label="UserName">Zhou Maomao</Descriptions.Item>
<Descriptions.Item label="Telephone">1810000000</Descriptions.Item>
<Descriptions.Item label="Live">Hangzhou, Zhejiang</Descriptions.Item>
<Descriptions.Item label="Remark">empty</Descriptions.Item>
<Descriptions.Item label="Address">
No. 18, Wantang Road, Xihu District, Hangzhou, Zhejiang, China
</Descriptions.Item>
</Descriptions>
<Table
size="small"
pagination={{ position: "top", defaultPageSize: 25 }}
columns={columns.map((item) => ({ ...item }))}
rowKey="id"
dataSource={record.invoicelines}
/>
</div>
);
};
return (
<Table
loading={loading}
size="small"
expandedRowRender={rowExpander}
pagination={{
position: "top",
defaultPageSize: 1,
onChange: (page, pageSize) => {
handleOnRowClick(page * pageSize);
},
}}
columns={columns.map((item) => ({ ...item }))}
rowKey="id"
dataSource={invoices}
onChange={handleTableChange}
expandable={{
expandedRowKeys: [selectedInvoice],
onExpand: (expanded, record) => {
handleOnRowClick(expanded ? record : null);
},
}}
rowSelection={{
onSelect: (record) => {
handleOnRowClick(record);
},
selectedRowKeys: [selectedInvoice],
type: "radio",
}}
onRow={(record, rowIndex) => {
return {
onClick: (event) => {
handleOnRowClick(record);
}, // click row
onDoubleClick: (event) => {}, // double click row
onContextMenu: (event) => {}, // right button click row
onMouseEnter: (event) => {}, // mouse enter row
onMouseLeave: (event) => {}, // mouse leave row
};
}}
/>
);
}

View File

@@ -0,0 +1,57 @@
import { useQuery } from "@apollo/react-hooks";
import React from "react";
import { QUERY_ALL_INVOICES_PAGINATED } from "../../graphql/invoices.queries";
import InvoicesPageComponent from "./invoices.page.component";
import AlertComponent from "../../components/alert/alert.component";
import queryString from "query-string";
import { useHistory, useLocation } from "react-router-dom";
export default function InvoicesPageContainer() {
const { loading, error, data, fetchMore } = useQuery(
QUERY_ALL_INVOICES_PAGINATED,
{
variables: { offset: 0, limit: 1 },
}
);
const search = queryString.parse(useLocation().search);
const history = useHistory();
const handleOnRowClick = (record) => {
if (record) {
if (record.id) {
search.invoiceid = record.id;
history.push({ search: queryString.stringify(search) });
}
} else {
delete search.invoiceid;
history.push({ search: queryString.stringify(search) });
}
};
const handleFetchMore = (offset) => {
fetchMore({
variables: {
offset: offset,
},
updateQuery: (prev, { fetchMoreResult }) => {
if (!fetchMoreResult) return prev;
return Object.assign({}, prev, {
invoices: [...prev.invoices, ...fetchMoreResult.invoices],
});
},
});
};
if (error) return <AlertComponent message={error.message} type="error" />;
return (
<div>
<InvoicesPageComponent
loading={loading}
invoices={data ? data.invoices : null}
selectedInvoice={search.invoiceid}
handleFetchMore={handleFetchMore}
handleOnRowClick={handleOnRowClick}
/>
</div>
);
}

View File

@@ -2,12 +2,18 @@ import React, { useEffect } from "react";
import { useMutation, useLazyQuery } from "@apollo/react-hooks";
import {
DELETE_AVAILABLE_JOB,
QUERY_AVAILABLE_NEW_JOBS_EST_DATA_BY_PK
QUERY_AVAILABLE_NEW_JOBS_EST_DATA_BY_PK,
} from "../../graphql/available-jobs.queries";
import JobsAvailablePageComponent from "./jobs-available.page.component";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
export default function JobsAvailablePageContainer() {
import { setBreadcrumbs } from "../../redux/application/application.actions";
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
});
export function JobsAvailablePageContainer({ setBreadcrumbs }) {
const [deleteJob] = useMutation(DELETE_AVAILABLE_JOB);
const { t } = useTranslation();
@@ -15,7 +21,10 @@ export default function JobsAvailablePageContainer() {
useEffect(() => {
document.title = t("titles.jobsavailable");
}, [t]);
setBreadcrumbs([
{ link: "/manage/available", label: t("titles.bc.availablejobs") },
]);
}, [t, setBreadcrumbs]);
return (
<div>
@@ -26,3 +35,4 @@ export default function JobsAvailablePageContainer() {
</div>
);
}
export default connect(null, mapDispatchToProps)(JobsAvailablePageContainer);

View File

@@ -9,13 +9,15 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { useTranslation } from "react-i18next";
import { setBreadcrumbs } from "../../redux/application/application.actions";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
bodyshop: selectBodyshop,
});
export default connect(mapStateToProps, null)(JobsCreateContainer);
function JobsCreateContainer({ bodyshop }) {
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
});
function JobsCreateContainer({ bodyshop, setBreadcrumbs }) {
const { t } = useTranslation();
const contextState = useState({
vehicle: { new: false, search: "", selectedid: null, vehicleObj: null },
@@ -24,7 +26,7 @@ function JobsCreateContainer({ bodyshop }) {
created: false,
error: null,
newJobId: null,
newJobEstNum: null
newJobEstNum: null,
});
const [form] = Form.useForm();
const [state, setState] = contextState;
@@ -36,49 +38,56 @@ function JobsCreateContainer({ bodyshop }) {
useEffect(() => {
if (!!state.owner.selectedid) {
loadOwner({
variables: { id: state.owner.selectedid }
variables: { id: state.owner.selectedid },
});
}
}, [state.owner.selectedid, loadOwner]);
useEffect(() => {
document.title = t("titles.jobs-create");
}, [t]);
setBreadcrumbs([
{ link: "/manage/available", label: t("titles.bc.availablejobs") },
{
link: "/manage/jobs/new",
label: t("titles.bc.jobs-new"),
},
]);
}, [t, setBreadcrumbs]);
const runInsertJob = job => {
const runInsertJob = (job) => {
insertJob({ variables: { job: job } })
.then(resp => {
.then((resp) => {
setState({
...state,
created: true,
error: null,
newJobId: resp.data.insert_jobs.returning[0].id,
newJobEstNum: resp.data.insert_jobs.returning[0].est_number
newJobEstNum: resp.data.insert_jobs.returning[0].est_number,
});
})
.catch(error => {
.catch((error) => {
notification["error"]({
message: t("jobs.errors.creating", { error: error })
message: t("jobs.errors.creating", { error: error }),
});
setState({ ...state, error: error });
});
};
const handleFinish = values => {
const handleFinish = (values) => {
let job = Object.assign(
{},
values,
{
vehicle: state.vehicle.selectedid ? null : values.vehicle,
vehicleid: state.vehicle.selectedid || null
vehicleid: state.vehicle.selectedid || null,
},
{
owner: state.owner.selectedid ? null : values.owner,
ownerid: state.owner.selectedid || null
ownerid: state.owner.selectedid || null,
},
{
status: bodyshop.md_ro_statuses.default_imported || "Open*", //Pull from redux store.
shopid: bodyshop.id
shopid: bodyshop.id,
}
);
//TODO Logic to ensure the owner is actually fetched.
@@ -128,3 +137,7 @@ function JobsCreateContainer({ bodyshop }) {
</JobCreateContext.Provider>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(JobsCreateContainer);

View File

@@ -3,7 +3,7 @@ import Icon, {
CalendarFilled,
DollarCircleOutlined,
FileImageFilled,
ToolFilled
ToolFilled,
} from "@ant-design/icons";
import { Form, notification, Tabs } from "antd";
import moment from "moment";
@@ -14,7 +14,7 @@ import {
FaHistory,
FaInfo,
FaRegStickyNote,
FaShieldAlt
FaShieldAlt,
} from "react-icons/fa";
import { useHistory, useLocation } from "react-router-dom";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
@@ -57,9 +57,7 @@ const JobLineUpsertModalContainer = lazy(() =>
"../../components/job-lines-upsert-modal/job-lines-upsert-modal.container"
)
);
const EnterInvoiceModalContainer = lazy(() =>
import("../../components/invoice-enter-modal/invoice-enter-modal.container")
);
const JobsDetailPliContainer = lazy(() =>
import("../../components/jobs-detail-pli/jobs-detail-pli.container")
);
@@ -73,7 +71,7 @@ export default function JobsDetailPage({
mutationConvertJob,
handleSubmit,
refetch,
updateJobStatus
updateJobStatus,
}) {
const { t } = useTranslation();
const [form] = Form.useForm();
@@ -83,22 +81,22 @@ export default function JobsDetailPage({
const formItemLayout = {
labelCol: {
xs: { span: 12 },
sm: { span: 5 }
sm: { span: 5 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 12 }
}
sm: { span: 12 },
},
};
const handleFinish = values => {
const handleFinish = (values) => {
mutationUpdateJob({
variables: { jobId: job.id, job: values }
}).then(r => {
variables: { jobId: job.id, job: values },
}).then((r) => {
notification["success"]({
message: t("jobs.successes.savetitle")
message: t("jobs.successes.savetitle"),
});
refetch().then(r => form.resetFields());
refetch().then((r) => form.resetFields());
});
};
@@ -108,7 +106,6 @@ export default function JobsDetailPage({
>
<ScheduleJobModalContainer />
<JobLineUpsertModalContainer />
<EnterInvoiceModalContainer />
<Form
form={form}
@@ -143,7 +140,7 @@ export default function JobsDetailPage({
: null,
date_invoiced: job.date_invoiced ? moment(job.date_invoiced) : null,
date_closed: job.date_closed ? moment(job.date_closed) : null,
date_exported: job.date_exported ? moment(job.date_exported) : null
date_exported: job.date_exported ? moment(job.date_exported) : null,
}}
>
<JobsDetailHeader
@@ -155,7 +152,7 @@ export default function JobsDetailPage({
/>
<Tabs
defaultActiveKey={search.tab}
onChange={key => history.push({ search: `?tab=${key}` })}
onChange={(key) => history.push({ search: `?tab=${key}` })}
>
<Tabs.TabPane
tab={

View File

@@ -4,30 +4,41 @@ import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import AlertComponent from "../../components/alert/alert.component";
import SpinComponent from "../../components/loading-spinner/loading-spinner.component";
import { CONVERT_JOB_TO_RO, GET_JOB_BY_PK, UPDATE_JOB, UPDATE_JOB_STATUS } from "../../graphql/jobs.queries";
import {
CONVERT_JOB_TO_RO,
GET_JOB_BY_PK,
UPDATE_JOB,
UPDATE_JOB_STATUS,
} from "../../graphql/jobs.queries";
import JobsDetailPage from "./jobs-detail.page.component";
import { setBreadcrumbs } from "../../redux/application/application.actions";
import { connect } from "react-redux";
function JobsDetailPageContainer({ match }) {
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
});
function JobsDetailPageContainer({ match, setBreadcrumbs }) {
const { jobId } = match.params;
const { t } = useTranslation();
const { loading, error, data, refetch } = useQuery(GET_JOB_BY_PK, {
variables: { id: jobId },
fetchPolicy: "network-only"
fetchPolicy: "network-only",
});
const [mutationUpdateJob] = useMutation(UPDATE_JOB);
const [mutationConvertJob] = useMutation(CONVERT_JOB_TO_RO);
const [mutationUpdateJobstatus] = useMutation(UPDATE_JOB_STATUS);
const updateJobStatus = status => {
const updateJobStatus = (status) => {
mutationUpdateJobstatus({
variables: { jobId: jobId, status: status }
variables: { jobId: jobId, status: status },
})
.then(r => {
.then((r) => {
notification["success"]({ message: t("jobs.successes.save") });
refetch();
})
.catch(error => {
.catch((error) => {
notification[error]({ message: t("jobs.errors.saving") });
});
};
@@ -40,9 +51,23 @@ function JobsDetailPageContainer({ match }) {
: t("titles.jobsdetail", {
ro_number: data.jobs_by_pk.converted
? data.jobs_by_pk.ro_number
: `EST ${data.jobs_by_pk.est_number}`
: `EST ${data.jobs_by_pk.est_number}`,
});
}, [loading, data, t, error]);
setBreadcrumbs([
{ link: "/manage/jobs", label: t("titles.bc.jobs") },
{
link: `/manage/jobs/${jobId}`,
label: t("titles.bc.jobs-detail", {
number:
(data &&
(data.jobs_by_pk.converted
? data && data.jobs_by_pk.ro_number
: `EST ${data.jobs_by_pk.est_number}`)) ||
"",
}),
},
]);
}, [loading, data, t, error, setBreadcrumbs, jobId]);
if (loading) return <SpinComponent />;
if (error) return <AlertComponent message={error.message} type="error" />;
@@ -59,4 +84,4 @@ function JobsDetailPageContainer({ match }) {
<AlertComponent message={t("jobs.errors.noaccess")} type="error" />
);
}
export default JobsDetailPageContainer;
export default connect(null, mapDispatchToProps)(JobsDetailPageContainer);

View File

@@ -11,21 +11,28 @@ import JobsList from "../../components/jobs-list/jobs-list.component";
import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { setBreadcrumbs } from "../../redux/application/application.actions";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
bodyshop: selectBodyshop,
});
export function JobsPage({ location, bodyshop }) {
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
});
export function JobsPage({ location, bodyshop, setBreadcrumbs }) {
const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, {
variables: {
statuses: bodyshop.md_ro_statuses.open_statuses || ["Open"]
}
statuses: bodyshop.md_ro_statuses.open_statuses || ["Open"],
},
});
const { t } = useTranslation();
useEffect(() => {
document.title = t("titles.jobs");
}, [t]);
setBreadcrumbs([{ link: "/manage/jobs", label: t("titles.bc.jobs-active") }]);
}, [t, setBreadcrumbs]);
const search = queryString.parse(location.search);
const searchTextState = useState("");
@@ -39,14 +46,14 @@ export function JobsPage({ location, bodyshop }) {
searchTextState={searchTextState}
refetch={refetch}
loading={loading}
selectedJob={search.selected}
searchParams={search}
//setSelectedJob={setSelectedJob}
jobs={
data
? searchText === ""
? data.jobs
: data.jobs.filter(
j =>
(j) =>
(j.ro_number || "")
.toString()
.toLowerCase()
@@ -78,4 +85,4 @@ export function JobsPage({ location, bodyshop }) {
);
}
export default connect(mapStateToProps, null)(JobsPage);
export default connect(mapStateToProps, mapDispatchToProps)(JobsPage);

View File

@@ -1,11 +1,23 @@
import React from "react";
import DashboardGridComponent from "../../components/dashboard-grid/dashboard-grid.component";
import Test from "../../components/_test/test.component";
import { analytics } from "../../firebase/firebase.utils";
export default function ManageRootPageComponent() {
//const client = useApolloClient();
return (
<div>
<Test />
<button
onClick={() => {
analytics.logEvent("start_game", {
level: "10",
difficulty: "expert",
});
}}
>
Click me to start an event
</button>
<DashboardGridComponent />
{
// <SendEmailButton

View File

@@ -8,6 +8,7 @@ import FooterComponent from "../../components/footer/footer.component";
import HeaderContainer from "../../components/header/header.container";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import "./manage.page.styles.scss";
import BreadCrumbs from "../../components/breadcrumbs/breadcrumbs.component";
const ManageRootPage = lazy(() =>
import("../manage-root/manage-root.page.container")
@@ -64,6 +65,15 @@ const ContractDetailPage = lazy(() =>
const ContractsList = lazy(() =>
import("../contracts/contracts.page.container")
);
const InvoicesListPage = lazy(() =>
import("../invoices/invoices.page.container")
);
const InvoiceDetailPage = lazy(() =>
import("../invoice-detail/invoice-detail.page.container")
);
const EnterInvoiceModalContainer = lazy(() =>
import("../../components/invoice-enter-modal/invoice-enter-modal.container")
);
const { Header, Content, Footer } = Layout;
export default function Manage({ match }) {
@@ -89,6 +99,8 @@ export default function Manage({ match }) {
<LoadingSpinner message={t("general.labels.loadingapp")} />
}
>
<BreadCrumbs />
<EnterInvoiceModalContainer />
<EmailOverlayContainer />
<Route exact path={`${match.path}`} component={ManageRootPage} />
<Route exact path={`${match.path}/jobs`} component={JobsPage} />
@@ -154,6 +166,18 @@ export default function Manage({ match }) {
path={`${match.path}/vehicles/:vehId`}
component={VehiclesDetailContainer}
/>
<Route
exact
path={`${match.path}/invoices`}
component={InvoicesListPage}
/>
<Route
exact
path={`${match.path}/invoices/:invoiceId`}
component={InvoiceDetailPage}
/>
<Route
exact
path={`${match.path}/owners`}

View File

@@ -1,19 +1,49 @@
import React from "react";
import React, { useEffect } from "react";
import OwnersDetailComponent from "./owners-detail.page.component";
import { useTranslation } from "react-i18next";
import { useQuery } from "@apollo/react-hooks";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import AlertComponent from "../../components/alert/alert.component";
import { QUERY_OWNER_BY_ID } from "../../graphql/owners.queries";
export default function OwnersDetailContainer({ match }) {
import { connect } from "react-redux";
import { setBreadcrumbs } from "../../redux/application/application.actions";
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
});
export function OwnersDetailContainer({ match, setBreadcrumbs }) {
const { ownerId } = match.params;
const { t } = useTranslation();
const { loading, data, error, refetch } = useQuery(QUERY_OWNER_BY_ID, {
variables: { id: ownerId },
fetchPolicy: "network-only"
fetchPolicy: "network-only",
});
useEffect(() => {
document.title = t("titles.owners-detail", {
name: data
? `${data.owners_by_pk.ownr_fn || ""} ${
data.owners_by_pk.ownr_ln || ""
} ${data.owners_by_pk.ownr_co_nm || ""}`
: "",
});
setBreadcrumbs([
{ link: "/manage/owners", label: t("titles.bc.owners") },
{
link: `/manage/owners/${ownerId}`,
label: t("titles.bc.owner-detail", {
name: data
? `${data.owners_by_pk.ownr_fn || ""} ${
data.owners_by_pk.ownr_ln || ""
} ${data.owners_by_pk.ownr_co_nm || ""}`
: "",
}),
},
]);
}, [setBreadcrumbs, t, data, ownerId]);
if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />;
@@ -26,3 +56,4 @@ export default function OwnersDetailContainer({ match }) {
<AlertComponent message={t("owners.errors.noaccess")} type="error" />
);
}
export default connect(null, mapDispatchToProps)(OwnersDetailContainer);

View File

@@ -1,12 +1,20 @@
import React, { useEffect } from "react";
import OwnersPageComponent from "./owners.page.component";
import { useTranslation } from "react-i18next";
import { setBreadcrumbs } from "../../redux/application/application.actions";
import { connect } from "react-redux";
export default function OwnersPageContainer() {
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
});
export function OwnersPageContainer({ setBreadcrumbs }) {
const { t } = useTranslation();
useEffect(() => {
document.title = t("titles.owners");
}, [t]);
setBreadcrumbs([{ link: "/manage/owners", label: t("titles.bc.owners") }]);
}, [t, setBreadcrumbs]);
return <OwnersPageComponent />;
}
export default connect(null, mapDispatchToProps)(OwnersPageContainer);

View File

@@ -1,12 +1,22 @@
import React, { useEffect } from "react";
import SchedulePageComponent from "./schedule.page.component";
import { useTranslation } from "react-i18next";
export default function SchedulePageContainer() {
import { connect } from "react-redux";
import { setBreadcrumbs } from "../../redux/application/application.actions";
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
});
export function SchedulePageContainer({ setBreadcrumbs }) {
const { t } = useTranslation();
useEffect(() => {
document.title = t("titles.schedule");
}, [t]);
setBreadcrumbs([
{ link: "/manage/schedule", label: t("titles.bc.schedule") },
]);
}, [t, setBreadcrumbs]);
return <SchedulePageComponent />;
}
export default connect(null, mapDispatchToProps)(SchedulePageContainer);

View File

@@ -5,13 +5,19 @@ import { QUERY_VEHICLE_BY_ID } from "../../graphql/vehicles.queries";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import AlertComponent from "../../components/alert/alert.component";
import { useTranslation } from "react-i18next";
import { setBreadcrumbs } from "../../redux/application/application.actions";
import { connect } from "react-redux";
export default function VehicleDetailContainer({ match }) {
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
});
export function VehicleDetailContainer({ match, setBreadcrumbs }) {
const { vehId } = match.params;
const { t } = useTranslation();
const { loading, data, error, refetch } = useQuery(QUERY_VEHICLE_BY_ID, {
variables: { id: vehId },
fetchPolicy: "network-only"
fetchPolicy: "network-only",
});
useEffect(() => {
@@ -19,9 +25,21 @@ export default function VehicleDetailContainer({ match }) {
vehicle:
data && data.vehicles_by_pk
? `${data.vehicles_by_pk.v_model_yr} ${data.vehicles_by_pk.v_make_desc} ${data.vehicles_by_pk.v_model_desc}`
: ""
: "",
});
}, [t, data]);
setBreadcrumbs([
{ link: "/manage/vehicles", label: t("titles.bc.vehicles") },
{
link: `/manage/vehicles/${vehId}`,
label: t("titles.bc.vehicle-details", {
vehicle:
data && data.vehicles_by_pk
? `${data.vehicles_by_pk.v_model_yr} ${data.vehicles_by_pk.v_make_desc} ${data.vehicles_by_pk.v_model_desc}`
: "",
}),
},
]);
}, [t, data, setBreadcrumbs, vehId]);
if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />;
@@ -35,3 +53,4 @@ export default function VehicleDetailContainer({ match }) {
<AlertComponent message={t("vehicles.errors.noaccess")} type="error" />
);
}
export default connect(null, mapDispatchToProps)(VehicleDetailContainer);

View File

@@ -1,10 +1,23 @@
import React, { useEffect } from "react";
import VehiclesPageComponent from "./vehicles.page.component";
import { useTranslation } from "react-i18next";
export default function VehiclesPageContainer() {
import { connect } from "react-redux";
import { setBreadcrumbs } from "../../redux/application/application.actions";
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
});
export function VehiclesPageContainer({ setBreadcrumbs }) {
const { t } = useTranslation();
useEffect(() => {
document.title = t("titles.vehicles");
}, [t]);
setBreadcrumbs([
{ link: "/manage/vehicles", label: t("titles.bc.vehicles") },
]);
}, [t, setBreadcrumbs]);
return <VehiclesPageComponent />;
}
export default connect(null, mapDispatchToProps)(VehiclesPageContainer);

View File

@@ -1,10 +1,15 @@
import ApplicationActionTypes from "./application.types";
export const startLoading = () => ({
type: ApplicationActionTypes.START_LOADING
type: ApplicationActionTypes.START_LOADING,
});
export const endLoading = options => ({
export const endLoading = (options) => ({
type: ApplicationActionTypes.END_LOADING,
payload: options
payload: options,
});
export const setBreadcrumbs = (breadcrumbs) => ({
type: ApplicationActionTypes.SET_BREAD_CRUMBS,
payload: breadcrumbs,
});

View File

@@ -1,7 +1,8 @@
import ApplicationActionTypes from "./application.types";
const INITIAL_STATE = {
loading: false
loading: false,
breadcrumbs: [],
};
const applicationReducer = (state = INITIAL_STATE, action) => {
@@ -9,12 +10,17 @@ const applicationReducer = (state = INITIAL_STATE, action) => {
case ApplicationActionTypes.START_LOADING:
return {
...state,
loading: true
loading: true,
};
case ApplicationActionTypes.END_LOADING:
return {
...state,
loading: false
loading: false,
};
case ApplicationActionTypes.SET_BREAD_CRUMBS:
return {
...state,
breadcrumbs: action.payload,
};
default:
return state;

View File

@@ -1,8 +1,13 @@
import { createSelector } from "reselect";
const selectApplication = state => state.application;
const selectApplication = (state) => state.application;
export const selectLoading = createSelector(
[selectApplication],
application => application.loading
(application) => application.loading
);
export const selectBreadcrumbs = createSelector(
[selectApplication],
(application) => application.breadcrumbs
);

View File

@@ -1,5 +1,6 @@
const ApplicationActionTypes = {
START_LOADING: "START_LOADING",
END_LOADING: "END_LOADING"
END_LOADING: "END_LOADING",
SET_BREAD_CRUMBS: "SET_BREAD_CRUMBS",
};
export default ApplicationActionTypes;

View File

@@ -339,6 +339,7 @@
},
"invoices": {
"actions": {
"edit": "Edit",
"receive": "Receive Part"
},
"errors": {
@@ -361,7 +362,7 @@
"new": "New Invoice"
},
"successes": {
"creating": "Invoice added successfully."
"created": "Invoice added successfully."
}
},
"joblines": {
@@ -384,6 +385,7 @@
"part_qty": "Quantity",
"part_type": "Part Type",
"status": "Status",
"total": "Total",
"unq_seq": "Seq #"
},
"labels": {
@@ -574,6 +576,7 @@
"profile": "Profile"
},
"header": {
"accounting": "Accounting",
"activejobs": "Active Jobs",
"availablejobs": "Available Jobs",
"courtesycars": "Courtesy Cars",
@@ -581,7 +584,9 @@
"courtesycars-contracts": "Contracts",
"courtesycars-newcontract": "New Contract",
"customers": "Customers",
"enterinvoices": "Enter Invoices",
"home": "Home",
"invoices": "Invoices",
"jobs": "Jobs",
"owners": "Owners",
"schedule": "Schedule",
@@ -707,17 +712,38 @@
},
"titles": {
"app": "Bodyshop by ImEX Systems",
"bc": {
"availablejobs": "Available Jobs",
"contracts": "Contracts",
"contracts-create": "New Contract",
"contracts-detail": "Contract #{{number}}",
"courtesycars": "Courtesy Cars",
"courtesycars-detail": "Courtesy Car {{number}}",
"courtesycars-new": "New Courtesy Car",
"jobs": "Jobs",
"jobs-active": "Active Jobs",
"jobs-detail": "Job {{number}}",
"jobs-new": "Create a New Job",
"owner-detail": "{{name}}",
"owners": "Owners",
"schedule": "Schedule",
"vehicle-details": "Vehicle: {{vehicle}}",
"vehicles": "Vehicles"
},
"contracts": "Courtesy Car Contracts | $t(titles.app)",
"contracts-create": "New Contract | $t(titles.app)",
"contracts-detail": "Contract {{id}} | $t(titles.app)",
"courtesycars": "Courtesy Cars | $t(titles.app)",
"courtesycars-create": "New Courtesy Car | $t(titles.app)",
"courtesycars-detail": "Courtesy Car {{id}} | $t(titles.app)",
"jobs": "All Jobs | $t(titles.app)",
"jobs": "Active Jobs | $t(titles.app)",
"jobs-create": "Create a New Job | $t(titles.app)",
"jobsavailable": "Available Jobs | $t(titles.app)",
"jobsdetail": "Job {{ro_number}} | $t(titles.app)",
"jobsdocuments": "Job Documents {{ro_number}} | $t(titles.app)",
"manageroot": "Home | $t(titles.app)",
"owners": "All Owners | $t(titles.app)",
"owners-detail": "{{name}} | $t(titles.app)",
"profile": "My Profile | $t(titles.app)",
"schedule": "Schedule | $t(titles.app)",
"shop": "My Shop | $t(titles.app)",

View File

@@ -339,6 +339,7 @@
},
"invoices": {
"actions": {
"edit": "",
"receive": ""
},
"errors": {
@@ -361,7 +362,7 @@
"new": ""
},
"successes": {
"creating": ""
"created": ""
}
},
"joblines": {
@@ -384,6 +385,7 @@
"part_qty": "",
"part_type": "Tipo de parte",
"status": "Estado",
"total": "",
"unq_seq": "Seq #"
},
"labels": {
@@ -574,6 +576,7 @@
"profile": "Perfil"
},
"header": {
"accounting": "",
"activejobs": "Empleos activos",
"availablejobs": "Trabajos disponibles",
"courtesycars": "",
@@ -581,7 +584,9 @@
"courtesycars-contracts": "",
"courtesycars-newcontract": "",
"customers": "Clientes",
"enterinvoices": "",
"home": "Casa",
"invoices": "",
"jobs": "Trabajos",
"owners": "propietarios",
"schedule": "Programar",
@@ -707,8 +712,28 @@
},
"titles": {
"app": "Carrocería de ImEX Systems",
"bc": {
"availablejobs": "",
"contracts": "",
"contracts-create": "",
"contracts-detail": "",
"courtesycars": "",
"courtesycars-detail": "",
"courtesycars-new": "",
"jobs": "",
"jobs-active": "",
"jobs-detail": "",
"jobs-new": "",
"owner-detail": "",
"owners": "",
"schedule": "",
"vehicle-details": "",
"vehicles": ""
},
"contracts": "",
"contracts-create": "",
"contracts-detail": "",
"courtesycars": "",
"courtesycars-create": "",
"courtesycars-detail": "",
"jobs": "Todos los trabajos | $t(titles.app)",
@@ -718,6 +743,7 @@
"jobsdocuments": "Documentos de trabajo {{ro_number}} | $ t (títulos.app)",
"manageroot": "Casa | $t(titles.app)",
"owners": "Todos los propietarios | $t(titles.app)",
"owners-detail": "",
"profile": "Mi perfil | $t(titles.app)",
"schedule": "Horario | $t(titles.app)",
"shop": "Mi tienda | $t(titles.app)",

View File

@@ -339,6 +339,7 @@
},
"invoices": {
"actions": {
"edit": "",
"receive": ""
},
"errors": {
@@ -361,7 +362,7 @@
"new": ""
},
"successes": {
"creating": ""
"created": ""
}
},
"joblines": {
@@ -384,6 +385,7 @@
"part_qty": "",
"part_type": "Type de pièce",
"status": "Statut",
"total": "",
"unq_seq": "Seq #"
},
"labels": {
@@ -574,6 +576,7 @@
"profile": "Profil"
},
"header": {
"accounting": "",
"activejobs": "Emplois actifs",
"availablejobs": "Emplois disponibles",
"courtesycars": "",
@@ -581,7 +584,9 @@
"courtesycars-contracts": "",
"courtesycars-newcontract": "",
"customers": "Les clients",
"enterinvoices": "",
"home": "Accueil",
"invoices": "",
"jobs": "Emplois",
"owners": "Propriétaires",
"schedule": "Programme",
@@ -707,8 +712,28 @@
},
"titles": {
"app": "Carrosserie par ImEX Systems",
"bc": {
"availablejobs": "",
"contracts": "",
"contracts-create": "",
"contracts-detail": "",
"courtesycars": "",
"courtesycars-detail": "",
"courtesycars-new": "",
"jobs": "",
"jobs-active": "",
"jobs-detail": "",
"jobs-new": "",
"owner-detail": "",
"owners": "",
"schedule": "",
"vehicle-details": "",
"vehicles": ""
},
"contracts": "",
"contracts-create": "",
"contracts-detail": "",
"courtesycars": "",
"courtesycars-create": "",
"courtesycars-detail": "",
"jobs": "Tous les emplois | $t(titles.app)",
@@ -718,6 +743,7 @@
"jobsdocuments": "Documents de travail {{ro_number}} | $ t (titres.app)",
"manageroot": "Accueil | $t(titles.app)",
"owners": "Tous les propriétaires | $t(titles.app)",
"owners-detail": "",
"profile": "Mon profil | $t(titles.app)",
"schedule": "Horaire | $t(titles.app)",
"shop": "Mon magasin | $t(titles.app)",

View File

@@ -4,11 +4,11 @@ export default ({ component: Component, isAuthorized, ...rest }) => {
return (
<Route
{...rest}
render={props =>
render={(props) =>
isAuthorized === true ? (
<Component {...props} />
) : (
<Redirect to='/login' />
<Redirect to="/signin" />
)
}
/>

View File

@@ -11178,6 +11178,11 @@ react-error-overlay@^6.0.7:
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.7.tgz#1dcfb459ab671d53f660a991513cb2f0a0553108"
integrity sha512-TAv1KJFh3RhqxNvhzxj6LeT5NWklP6rDr2a0jaTfsZ5wSZWHOGeqQyejUp3xxLfPt2UpyJEcVQB/zyPcmonNFA==
react-ga@^2.7.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/react-ga/-/react-ga-2.7.0.tgz#24328f157f31e8cffbf4de74a3396536679d8d7c"
integrity sha512-AjC7UOZMvygrWTc2hKxTDvlMXEtbmA0IgJjmkhgmQQ3RkXrWR11xEagLGFGaNyaPnmg24oaIiaNPnEoftUhfXA==
react-grid-gallery@^0.5.5:
version "0.5.5"
resolved "https://registry.yarnpkg.com/react-grid-gallery/-/react-grid-gallery-0.5.5.tgz#1b3f3c23a190834e587ab613c96d53ec3af4f0a2"

View File

@@ -1,11 +0,0 @@
{
"compilerOptions": {
"target": "ES6",
"module": "commonjs",
"allowSyntheticDefaultImports": true
},
"exclude": [
"dist",
"node_modules"
]
}