BOD-16 BOD-17 Added Contract detail pages + custom form components for courtesy cars.

This commit is contained in:
Patrick Fic
2020-03-31 16:32:18 -07:00
parent 72f4d31b05
commit 1c02c063b9
21 changed files with 626 additions and 34 deletions

View File

@@ -1981,6 +1981,27 @@
<folder_node>
<name>errors</name>
<children>
<concept_node>
<name>saving</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>selectjobandcar</name>
<definition_loaded>false</definition_loaded>
@@ -2550,7 +2571,7 @@
<name>courtesycars</name>
<children>
<folder_node>
<name>erorrs</name>
<name>errors</name>
<children>
<concept_node>
<name>saving</name>
@@ -3001,7 +3022,206 @@
</children>
</folder_node>
<folder_node>
<name>statuses</name>
<name>labels</name>
<children>
<folder_node>
<name>fuel</name>
<children>
<concept_node>
<name>12</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>14</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>18</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>34</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>38</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>58</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>78</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>empty</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>full</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
</children>
</folder_node>
<folder_node>
<name>status</name>
<children>
<concept_node>
<name>in</name>
@@ -3713,6 +3933,27 @@
<folder_node>
<name>actions</name>
<children>
<concept_node>
<name>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>delete</name>
<definition_loaded>false</definition_loaded>
@@ -10190,6 +10431,27 @@
</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-create</name>
<definition_loaded>false</definition_loaded>

View File

@@ -92,7 +92,7 @@ export default function ContractsCarsComponent({
.includes(state.search.toLowerCase()) ||
(cc.plate || "").toLowerCase().includes(state.search.toLowerCase())
);
console.log("filteredData", filteredData);
return (
<Table
loading={loading}

View File

@@ -160,7 +160,7 @@ export default function ContractsJobsComponent({
.includes(state.search.toLowerCase())
);
console.log("filteredData", filteredData);
return (
<Table
loading={loading}

View File

@@ -1,18 +1,20 @@
import React, { useState } from "react";
import React, { useState, useEffect } from "react";
import { Select } from "antd";
import { useTranslation } from "react-i18next";
const { Option } = Select;
const ContractStatusComponent = ({ value = "", onChange }) => {
const [option, setOption] = useState("contracts.status.new");
const ContractStatusComponent = ({
value = "contracts.status.new",
onChange
}) => {
const [option, setOption] = useState(value);
const { t } = useTranslation();
const onChangeSelect = newOption => {
setOption(newOption);
useEffect(() => {
if (onChange) {
onChange(newOption);
onChange(option);
}
};
}, [option, onChange]);
return (
<Select
@@ -20,7 +22,7 @@ const ContractStatusComponent = ({ value = "", onChange }) => {
style={{
width: 100
}}
onChange={onChangeSelect}
onChange={setOption}
>
<Option value="contracts.status.new">{t("contracts.status.new")}</Option>
<Option value="contracts.status.out">{t("contracts.status.out")}</Option>

View File

@@ -0,0 +1,5 @@
import React from "react";
export default function CourtesyCarContractListComponent({ contracts }) {
return <div>List</div>;
}

View File

@@ -2,6 +2,9 @@ import React from "react";
import { Form, Input, InputNumber, DatePicker, Button } from "antd";
import { useTranslation } from "react-i18next";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import CourtesyCarStatus from "../courtesy-car-status-select/courtesy-car-status-select.component";
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component";
export default function CourtesyCarCreateFormComponent() {
const { t } = useTranslation();
return (
@@ -121,7 +124,7 @@ export default function CourtesyCarCreateFormComponent() {
}
]}
>
<Input />
<CourtesyCarStatus />
</Form.Item>
<Form.Item
label={t("courtesycars.fields.nextservicekm")}
@@ -163,7 +166,7 @@ export default function CourtesyCarCreateFormComponent() {
}
]}
>
<Input />
<CourtesyCarFuelSlider />
</Form.Item>
<Form.Item
label={t("courtesycars.fields.registrationexpires")}

View File

@@ -0,0 +1,46 @@
import { Slider } from "antd";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
const CourtesyCarFuelComponent = ({ value = 100, onChange }) => {
const [option, setOption] = useState(value);
const { t } = useTranslation();
useEffect(() => {
if (onChange) {
onChange(option);
}
}, [option, onChange]);
const marks = {
0: {
style: {
color: "#f50"
},
label: t("courtesycars.labels.fuel.empty")
},
13: t("courtesycars.labels.fuel.18"),
25: t("courtesycars.labels.fuel.14"),
38: t("courtesycars.labels.fuel.38"),
50: t("courtesycars.labels.fuel.12"),
63: t("courtesycars.labels.fuel.58"),
75: t("courtesycars.labels.fuel.34"),
88: t("courtesycars.labels.fuel.78"),
100: {
style: {
color: "#008000"
},
label: <strong>{t("courtesycars.labels.fuel.full")}</strong>
}
};
return (
<Slider
marks={marks}
defaultValue={value}
onChange={setOption}
step={null}
/>
);
};
export default CourtesyCarFuelComponent;

View File

@@ -0,0 +1,39 @@
import React, { useState, useEffect } from "react";
import { Select } from "antd";
import { useTranslation } from "react-i18next";
const { Option } = Select;
const CourtesyCarStatusComponent = ({
value = "courtesycars.status.in",
onChange
}) => {
const [option, setOption] = useState(value);
const { t } = useTranslation();
useEffect(() => {
if (onChange) {
onChange(option);
}
}, [option, onChange]);
return (
<Select
value={option}
style={{
width: 100
}}
onChange={setOption}
>
<Option value="courtesycars.status.in">
{t("courtesycars.status.in")}
</Option>
<Option value="courtesycars.status.inservice">
{t("courtesycars.status.inservice")}
</Option>
<Option value="courtesycars.status.out">
{t("courtesycars.status.out")}
</Option>
</Select>
);
};
export default CourtesyCarStatusComponent;

View File

@@ -9,7 +9,6 @@ export default function JobsList({ loading, courtesycars }) {
sortedInfo: {},
filteredInfo: { text: "" }
});
console.log("courtesycars", courtesycars);
const { t } = useTranslation();
@@ -38,7 +37,8 @@ export default function JobsList({ loading, courtesycars }) {
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,
render: (text, record) => t(record.status)
},
{
title: t("courtesycars.fields.year"),

View File

@@ -9,3 +9,52 @@ export const INSERT_NEW_CONTRACT = gql`
}
}
`;
export const UPDATE_CONTRACT = gql`
mutation UPDATE_CONTRACT(
$contractId: uuid!
$cccontract: cccontracts_set_input!
) {
update_cccontracts(
where: { id: { _eq: $contractId } }
_set: $cccontract
) {
returning {
id
}
}
}
`;
export const QUERY_CONTRACT_BY_PK = gql`
query QUERY_CONTRACT_BY_PK($id: uuid!) {
cccontracts_by_pk(id: $id) {
actualreturn
agreementnumber
cc_cardholder
cc_expiry
cc_num
courtesycarid
driver_addr1
driver_city
driver_addr2
driver_dlexpiry
driver_dlnumber
driver_dlst
driver_dob
driver_fn
driver_ln
driver_ph1
driver_state
driver_zip
id
jobid
kmend
kmstart
scheduledreturn
start
status
updated_at
}
}
`;

View File

@@ -14,7 +14,7 @@ export const INSERT_NEW_COURTESY_CAR = gql`
export const QUERY_AVAILABLE_CC = gql`
query QUERY_AVAILABLE_CC {
courtesycars(where: {serviceenddate: {_is_null: true}}) {
courtesycars(where: { serviceenddate: { _is_null: true } }) {
color
dailycost
damage
@@ -85,6 +85,20 @@ export const QUERY_CC_BY_PK = gql`
status
vin
year
cccontracts {
status
start
scheduledreturn
kmstart
kmend
driver_ln
driver_fn
job {
ro_number
est_number
id
}
}
}
}
`;

View File

@@ -8,6 +8,7 @@ import { Form, notification } from "antd";
import { useTranslation } from "react-i18next";
import { INSERT_NEW_CONTRACT } from "../../graphql/cccontracts.queries";
import { useMutation } from "@apollo/react-hooks";
import { useHistory } from "react-router-dom";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
@@ -17,8 +18,8 @@ export function ContractCreatePageContainer({ bodyshop }) {
const { t } = useTranslation();
const selectedCarState = useState(null);
const selectedJobState = useState(null);
const [insertContract] = useMutation(INSERT_NEW_CONTRACT);
const history = useHistory();
const handleFinish = values => {
if (!!selectedCarState[0] && !!selectedJobState[0]) {
@@ -35,8 +36,18 @@ export function ContractCreatePageContainer({ bodyshop }) {
notification["success"]({
message: t("contracts.successes.saved")
});
history.push(
`/manage/courtesycars/contracts/${response.data.insert_cccontracts.returning[0].id}`
);
})
.catch(error => console.log("error", error));
.catch(error =>
notification["error"]({
message: t("contracts.errors.saving", {
error: JSON.stringify(error)
})
})
);
} else {
notification["error"]({
message: t("contracts.errors.selectjobandcar")
@@ -50,7 +61,6 @@ export function ContractCreatePageContainer({ bodyshop }) {
return (
<Form form={form} autoComplete="no" onFinish={handleFinish}>
<button onClick={() => console.log(form.getFieldsValue())}>t</button>
<ContractCreatePageComponent
selectedJobState={selectedJobState}
selectedCarState={selectedCarState}

View File

@@ -0,0 +1,16 @@
import React from "react";
import ContractFormComponent from "../../components/contract-form/contract-form.component";
import { useTranslation } from "react-i18next";
import { Button } from "antd";
export default function ContractDetailPage() {
const { t } = useTranslation();
return (
<div>
<Button type="primary" htmlType="submit">
{t("general.actions.save")}
</Button>
SOME SORT OF HEADER INFORMATION HERE.
<ContractFormComponent />
</div>
);
}

View File

@@ -0,0 +1,84 @@
import { useMutation, useQuery } from "@apollo/react-hooks";
import { Form, notification } from "antd";
import moment from "moment";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { useParams } from "react-router-dom";
import AlertComponent from "../../components/alert/alert.component";
import {
QUERY_CONTRACT_BY_PK,
UPDATE_CONTRACT
} from "../../graphql/cccontracts.queries";
import ContractDetailPageComponent from "./contract-detail.page.component";
export default function ContractDetailPageContainer() {
const { t } = useTranslation();
const [updateContract] = useMutation(UPDATE_CONTRACT);
const [form] = Form.useForm();
const { contractId } = useParams();
const { loading, error, data } = useQuery(QUERY_CONTRACT_BY_PK, {
variables: { id: contractId }
});
useEffect(() => {
document.title = loading
? t("titles.app")
: error
? t("titles.app")
: t("titles.contracts-detail", {
id: (data && data.cccontracts_by_pk.id) || ""
});
}, [t, data, error, loading]);
const handleFinish = values => {
updateContract({
variables: { cccontract: { ...values }, contractId: contractId }
})
.then(response => {
notification["success"]({ message: t("contracts.successess.saved") });
})
.catch(error =>
notification["error"]({
message: t("contracts.errors.saving", { error: error })
})
);
};
useEffect(() => {
if (data) form.resetFields();
}, [data, form]);
if (error) return <AlertComponent message={error.message} type="error" />;
return (
<Form
form={form}
autoComplete="no"
onFinish={handleFinish}
initialValues={
data
? {
...data.cccontracts_by_pk,
start: data.cccontracts_by_pk.start
? moment(data.cccontracts_by_pk.start)
: null,
scheduledreturn: data.cccontracts_by_pk.scheduledreturn
? moment(data.cccontracts_by_pk.scheduledreturn)
: null,
actualreturn: data.cccontracts_by_pk.actualreturn
? moment(data.cccontracts_by_pk.actualreturn)
: null,
driver_dlexpiry: data.cccontracts_by_pk.driver_dlexpiry
? moment(data.cccontracts_by_pk.driver_dlexpiry)
: null,
driver_dob: data.cccontracts_by_pk.driver_dob
? moment(data.cccontracts_by_pk.driver_dob)
: null
}
: {}
}
>
<ContractDetailPageComponent />
</Form>
);
}

View File

@@ -7,6 +7,7 @@ import { createStructuredSelector } from "reselect";
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";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
@@ -16,13 +17,17 @@ export function CourtesyCarCreateContainer({ bodyshop }) {
const [form] = Form.useForm();
const [insertCourtesyCar] = useMutation(INSERT_NEW_COURTESY_CAR);
const { t } = useTranslation();
const history = useHistory();
const handleFinish = values => {
insertCourtesyCar({
variables: { courtesycar: { ...values, bodyshopid: bodyshop.id } }
})
.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));
};

View File

@@ -1,6 +1,12 @@
import React from "react";
import CourtesyCarCreateFormComponent from "../../components/courtesy-car-form/courtesy-car-form.component";
import CourtesyCarContractListComponent from "../../components/courtesy-car-contract-list/courtesy-car-contract-list.component";
export default function CourtesyCarDetailPageComponent() {
return <CourtesyCarCreateFormComponent />;
export default function CourtesyCarDetailPageComponent({ contracts }) {
return (
<div>
<CourtesyCarCreateFormComponent />
<CourtesyCarContractListComponent contracts={contracts} />
</div>
);
}

View File

@@ -11,7 +11,7 @@ export default function JobsAvailablePageComponent({
const { t } = useTranslation();
return (
<div>
<Link to="/manage/jobs/create">
<Link to="/manage/jobs/new">
<Button>{t("jobs.actions.manualnew")}</Button>
</Link>

View File

@@ -58,6 +58,9 @@ const CourtesyCarsPage = lazy(() =>
const ContractCreatePage = lazy(() =>
import("../contract-create/contract-create.page.container")
);
const ContractDetailPage = lazy(() =>
import("../contract-detail/contract-detail.page.container")
);
const { Header, Content, Footer } = Layout;
@@ -90,7 +93,7 @@ export default function Manage({ match }) {
<Switch>
<Route
exact
path={`${match.path}/jobs/create`}
path={`${match.path}/jobs/new`}
component={JobsCreateContainerPage}
/>
<Route
@@ -107,7 +110,7 @@ export default function Manage({ match }) {
<Switch>
<Route
exact
path={`${match.path}/courtesycars/create`}
path={`${match.path}/courtesycars/new`}
component={CourtesyCarCreateContainer}
/>
@@ -125,7 +128,7 @@ export default function Manage({ match }) {
<Route
exact
path={`${match.path}/courtesycars/contracts/:contractId`}
component={() => <div>cc contract id</div>}
component={ContractDetailPage}
/>
<Route

View File

@@ -141,6 +141,7 @@
},
"contracts": {
"errors": {
"saving": "Error saving contract. {{error}}",
"selectjobandcar": "Please ensure both a car and job are selected."
},
"fields": {
@@ -168,7 +169,7 @@
},
"status": {
"new": "New Contract",
"out": "Current",
"out": "Out",
"returned": "Returned"
},
"successess": {
@@ -176,7 +177,7 @@
}
},
"courtesycars": {
"erorrs": {
"errors": {
"saving": "Error saving courtesy card. {{error}}"
},
"fields": {
@@ -201,7 +202,20 @@
"vin": "VIN",
"year": "Year"
},
"statuses": {
"labels": {
"fuel": {
"12": "1/2",
"14": "1/4",
"18": "1/8",
"34": "3/4",
"38": "3/8",
"58": "5/8",
"78": "7/8",
"empty": "Empty",
"full": "Full"
}
},
"status": {
"in": "Available",
"inservice": "In Service",
"out": "Rented"
@@ -264,6 +278,7 @@
},
"general": {
"actions": {
"create": "Create",
"delete": "Delete",
"edit": "Edit",
"reset": "Reset to original.",
@@ -672,6 +687,7 @@
"titles": {
"app": "Bodyshop by ImEX Systems",
"contracts-create": "New Contract | $t(titles.app)",
"contracts-detail": "Contract {{id}} | $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)",

View File

@@ -141,6 +141,7 @@
},
"contracts": {
"errors": {
"saving": "",
"selectjobandcar": ""
},
"fields": {
@@ -176,7 +177,7 @@
}
},
"courtesycars": {
"erorrs": {
"errors": {
"saving": ""
},
"fields": {
@@ -201,7 +202,20 @@
"vin": "",
"year": ""
},
"statuses": {
"labels": {
"fuel": {
"12": "",
"14": "",
"18": "",
"34": "",
"38": "",
"58": "",
"78": "",
"empty": "",
"full": ""
}
},
"status": {
"in": "",
"inservice": "",
"out": ""
@@ -264,6 +278,7 @@
},
"general": {
"actions": {
"create": "",
"delete": "Borrar",
"edit": "Editar",
"reset": "Restablecer a original.",
@@ -672,6 +687,7 @@
"titles": {
"app": "Carrocería de ImEX Systems",
"contracts-create": "",
"contracts-detail": "",
"courtesycars-create": "",
"courtesycars-detail": "",
"jobs": "Todos los trabajos | $t(titles.app)",

View File

@@ -141,6 +141,7 @@
},
"contracts": {
"errors": {
"saving": "",
"selectjobandcar": ""
},
"fields": {
@@ -176,7 +177,7 @@
}
},
"courtesycars": {
"erorrs": {
"errors": {
"saving": ""
},
"fields": {
@@ -201,7 +202,20 @@
"vin": "",
"year": ""
},
"statuses": {
"labels": {
"fuel": {
"12": "",
"14": "",
"18": "",
"34": "",
"38": "",
"58": "",
"78": "",
"empty": "",
"full": ""
}
},
"status": {
"in": "",
"inservice": "",
"out": ""
@@ -264,6 +278,7 @@
},
"general": {
"actions": {
"create": "",
"delete": "Effacer",
"edit": "modifier",
"reset": "Rétablir l'original.",
@@ -672,6 +687,7 @@
"titles": {
"app": "Carrosserie par ImEX Systems",
"contracts-create": "",
"contracts-detail": "",
"courtesycars-create": "",
"courtesycars-detail": "",
"jobs": "Tous les emplois | $t(titles.app)",