BOD-8 #comment Fixed up part of the vendor edit page. Refetch of list still oustanding.

This commit is contained in:
Patrick Fic
2020-03-10 14:21:21 -07:00
parent 024ddc2737
commit 8cb6bc6a24
11 changed files with 294 additions and 361 deletions

View File

@@ -8363,6 +8363,27 @@
<folder_node>
<name>fields</name>
<children>
<concept_node>
<name>am</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>city</name>
<definition_loaded>false</definition_loaded>
@@ -8531,6 +8552,48 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>lkq</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>make</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>name</name>
<definition_loaded>false</definition_loaded>
@@ -8552,6 +8615,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>oem</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>prompt_discount</name>
<definition_loaded>false</definition_loaded>

View File

@@ -1,135 +0,0 @@
import { Table } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort } from "../../utils/sorters";
import InvoiceAddLineButton from "../invoice-add-line-button/invoice-add-line-button.component";
export default function InvoiceEnterModalTableComponent({
lineData,
form,
vendor
}) {
const [state, setState] = useState({
sortedInfo: {}
});
const { t } = useTranslation();
const columns = [
{
title: t("joblines.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,
ellipsis: true
},
{
title: t("joblines.fields.oem_partno"),
dataIndex: "oem_partno",
key: "oem_partno",
sorter: (a, b) =>
alphaSort(
a.oem_partno ? a.oem_partno : a.op_code_desc,
b.oem_partno ? b.oem_partno : b.op_code_desc
),
sortOrder:
state.sortedInfo.columnKey === "oem_partno" && state.sortedInfo.order,
ellipsis: true,
render: (text, record) => (
<span>
{record.oem_partno ? record.oem_partno : record.op_code_desc}
</span>
)
},
{
title: t("joblines.fields.part_type"),
dataIndex: "part_type",
key: "part_type",
sorter: (a, b) => alphaSort(a.part_type, b.part_type),
sortOrder:
state.sortedInfo.columnKey === "part_type" && 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,
render: (text, record) => (
<CurrencyFormatter>{record.db_price}</CurrencyFormatter>
)
},
{
title: t("joblines.fields.act_price"),
dataIndex: "act_price",
key: "act_price",
sorter: (a, b) => a.act_price - b.act_price,
sortOrder:
state.sortedInfo.columnKey === "act_price" && state.sortedInfo.order,
render: (text, record) => (
<CurrencyFormatter>{record.act_price}</CurrencyFormatter>
)
},
{
title: t("joblines.fields.part_qty"),
dataIndex: "part_qty",
key: "part_qty",
sorter: (a, b) => a.part_qty - b.part_qty,
sortOrder:
state.sortedInfo.columnKey === "part_qty" && state.sortedInfo.order
},
{
title: t("joblines.fields.mod_lb_hrs"),
dataIndex: "mod_lb_hrs",
key: "mod_lb_hrs",
sorter: (a, b) => a.mod_lb_hrs - b.mod_lb_hrs,
sortOrder:
state.sortedInfo.columnKey === "mod_lb_hrs" && state.sortedInfo.order
},
{
title: t("invoices.labels.actions"),
dataIndex: "actions",
key: "actions",
render: (text, record) => (
<div>
<InvoiceAddLineButton
jobLine={record}
form={form}
discount={vendor ? vendor.discount : null}
disabled={vendor}
/>
</div>
)
}
];
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 (
<Table
{...formItemLayout}
size="small"
pagination={{ position: "top", defaultPageSize: 25 }}
columns={columns.map(item => ({ ...item }))}
rowKey="id"
dataSource={lineData}
onChange={handleTableChange}
/>
);
}

View File

@@ -1,54 +1,18 @@
import { Button, Checkbox, Col, Form, Input, InputNumber, Row } from "antd";
import { Button, Form, Input, InputNumber, Switch, Select } from "antd";
import React from "react";
import { MinusCircleOutlined, PlusCircleFilled } from "@ant-design/icons";
import { useTranslation } from "react-i18next";
import FormItemEmail from "../form-items-formatted/email-form-item.component";
import ResetForm from "../form-items-formatted/reset-form-item.component";
let id = 0;
export default function VendorsFormComponent({ form, vendor, handleDelete }) {
const {
getFieldDecorator,
isFieldsTouched,
getFieldValue,
resetFields
} = form;
getFieldDecorator("keys", {
initialValue: Array.isArray(vendor.favorite) ? vendor.favorite : []
});
const remove = k => {
// can use data-binding to get
const keys = form.getFieldValue("keys");
console.log("keys", keys);
// We need at least one passenger
if (keys.length === 1) {
return;
}
// can use data-binding to set
form.setFieldsValue({
keys: keys.filter(key => key !== k)
});
};
const add = props => {
console.log("props", props);
// can use data-binding to get
const keys = form.getFieldValue("keys");
console.log("keys", keys);
const nextKeys = keys.concat(id++);
// can use data-binding to set
// important! notify form to detect changes
form.setFieldsValue({
keys: nextKeys
});
};
import { DeleteFilled } from "@ant-design/icons";
export default function VendorsFormComponent({
form,
handleDelete,
responsibilityCenters
}) {
const { t } = useTranslation();
const { getFieldValue } = form;
return (
<div>
{isFieldsTouched() ? <ResetForm resetFields={resetFields} /> : null}
<Button htmlType="submit" type="primary">
{t("general.actions.save")}
</Button>
@@ -56,130 +20,143 @@ export default function VendorsFormComponent({ form, vendor, handleDelete }) {
{t("general.actions.delete")}
</Button>
{getFieldValue("keys").map((k, index) => (
<Form.Item required={false} key={k}>
{getFieldDecorator(`favorite[${k}].make`, {
validateTrigger: ["onChange", "onBlur"]
})(
<Input
placeholder="passenger name"
style={{ width: "60%", marginRight: 8 }}
/>
)}
{getFieldValue("keys").length > 1 ? (
<MinusCircleOutlined
className="dynamic-delete-button"
onClick={() => remove(k)}
/>
) : null}
<Form.Item label="Group">
{getFieldDecorator(`favorite[${k}].type`, {
initialValue: null
})(
<Checkbox.Group style={{ width: "100%" }}>
<Row>
<Col span={8}>
<Checkbox value="OEM">OEM</Checkbox>
</Col>
<Col span={8}>
<Checkbox value="LKQ">LKQ</Checkbox>
</Col>
<Col span={8}>
<Checkbox value="AM">AM</Checkbox>
</Col>
</Row>
</Checkbox.Group>
)}
</Form.Item>
</Form.Item>
))}
<Form.Item label={t("vendors.fields.favorite")}>
<Button type="dashed" onClick={add} style={{ width: "60%" }}>
<PlusCircleFilled /> Add field
</Button>
</Form.Item>
<Form.List name="favorite">
{(fields, { add, remove }) => {
return (
<div>
{fields.map((field, index) => (
<Form.Item key={field.key}>
<div style={{ display: "flex" }}>
<Form.Item
label={t("vendors.fields.make")}
key={`${index}make`}
name={[field.name, "make"]}
rules={[
{
required: true,
message: t("general.validation.required")
}
]}
>
<Input />
</Form.Item>
<Form.Item label={t("vendors.fields.zip")}>
{getFieldDecorator("zip", {
initialValue: vendor.zip
})(<Input name="zip" />)}
<Form.Item key={`${field.key}types`}>
<div style={{ display: "flex" }}>
<Form.Item
label={t("vendors.fields.oem")}
key={`${index}oem`}
name={[field.name, "type", "oem"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("vendors.fields.lkq")}
key={`${index}lkq`}
name={[field.name, "type", "lkq"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("vendors.fields.am")}
key={`${index}am`}
name={[field.name, "type", "am"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
</div>
</Form.Item>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
</div>
</Form.Item>
))}
<Form.Item>
<Button
type="dashed"
onClick={() => {
add();
}}
style={{ width: "100%" }}
>
{t("invoicelines.actions.newline")}
</Button>
</Form.Item>
</div>
);
}}
</Form.List>
<Form.Item label={t("vendors.fields.zip")} name="zip">
<Input />
</Form.Item>
<Form.Item label={t("vendors.fields.terms")}>
{getFieldDecorator("terms", {
initialValue: vendor.terms
})(<Input name="terms" />)}
<Form.Item label={t("vendors.fields.terms")} name="terms">
<Input />
</Form.Item>
<Form.Item label={t("vendors.fields.taxid")}>
{getFieldDecorator("taxid", {
initialValue: vendor.taxid
})(<Input name="taxid" />)}
<Form.Item label={t("vendors.fields.taxid")} name="taxid">
<Input />
</Form.Item>
<Form.Item label={t("vendors.fields.street1")}>
{getFieldDecorator("street1", {
initialValue: vendor.street1
})(<Input name="street1" />)}
<Form.Item label={t("vendors.fields.street1")} name="street1">
<Input />
</Form.Item>
<Form.Item label={t("vendors.fields.street2")}>
{getFieldDecorator("street2", {
initialValue: vendor.street2
})(<Input name="street2" />)}
<Form.Item label={t("vendors.fields.street2")} name="street2">
<Input />
</Form.Item>
<Form.Item label={t("vendors.fields.state")}>
{getFieldDecorator("state", {
initialValue: vendor.state
})(<Input name="state" />)}
<Form.Item label={t("vendors.fields.state")} name="state">
<Input />
</Form.Item>
<Form.Item label={t("vendors.fields.prompt_discount")}>
{getFieldDecorator("prompt_discount", {
initialValue: vendor.prompt_discount
})(<InputNumber name="prompt_discount" />)}
<Form.Item
label={t("vendors.fields.prompt_discount")}
name="prompt_discount"
>
<InputNumber />
</Form.Item>
<Form.Item label={t("vendors.fields.name")}>
{getFieldDecorator("name", {
initialValue: vendor.name
})(<Input name="name" />)}
<Form.Item label={t("vendors.fields.name")} name="name">
<Input />
</Form.Item>
<Form.Item label={t("vendors.fields.email")}>
{getFieldDecorator("email", {
initialValue: vendor.email,
rules: [
{
type: "email",
message: t("general.validation.invalidemail")
}
]
})(<FormItemEmail name="email" email={getFieldValue("email")} />)}
<Form.Item
label={t("vendors.fields.email")}
rules={[
{
type: "email",
message: t("general.validation.invalidemail")
}
]}
name="email"
>
<FormItemEmail email={getFieldValue("email")} />
</Form.Item>
<Form.Item label={t("vendors.fields.due_date")}>
{getFieldDecorator("due_date", {
initialValue: vendor.due_date
})(<InputNumber name="due_date" />)}
<Form.Item label={t("vendors.fields.due_date")} name="due_date">
<InputNumber />
</Form.Item>
<Form.Item label={t("vendors.fields.display_name")}>
{getFieldDecorator("display_name", {
initialValue: vendor.display_name
})(<Input name="display_name" />)}
<Form.Item label={t("vendors.fields.display_name")} name="display_name">
<Input />
</Form.Item>
<Form.Item label={t("vendors.fields.discount")}>
{getFieldDecorator("discount", {
initialValue: vendor.discount
})(<InputNumber name="discount" />)}
<Form.Item label={t("vendors.fields.discount")} name="discount">
<InputNumber />
</Form.Item>
<Form.Item label={t("vendors.fields.country")}>
{getFieldDecorator("country", {
initialValue: vendor.country
})(<Input name="country" />)}
<Form.Item label={t("vendors.fields.country")} name="country">
<Input />
</Form.Item>
<Form.Item label={t("vendors.fields.cost_center")}>
{getFieldDecorator("cost_center", {
initialValue: vendor.cost_center,
rules: [{ required: true, message: t("general.validation.required") }]
})(<Input name="cost_center" />)}
<Form.Item
label={t("vendors.fields.cost_center")}
rules={[{ required: true, message: t("general.validation.required") }]}
name="cost_center"
>
<Select style={{ width: "150px" }}>
{responsibilityCenters.costs.map(item => (
<Select.Option key={item}>{item}</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item label={t("vendors.fields.city")}>
{getFieldDecorator("city", {
initialValue: vendor.city
})(<Input name="city" />)}
<Form.Item label={t("vendors.fields.city")} name="city">
<Input />
</Form.Item>
</div>
);

View File

@@ -1,5 +1,5 @@
import { Form, notification } from "antd";
import React from "react";
import React, { useEffect } from "react";
import { useMutation, useQuery } from "react-apollo";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -7,31 +7,31 @@ import { createStructuredSelector } from "reselect";
import {
DELETE_VENDOR,
INSERT_NEW_VENDOR,
UPDATE_VENDOR,
QUERY_VENDOR_BY_ID
QUERY_VENDOR_BY_ID,
UPDATE_VENDOR
} from "../../graphql/vendors.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import VendorsFormComponent from "./vendors-form.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import AlertComponent from "../alert/alert.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import VendorsFormComponent from "./vendors-form.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
function VendorsFormContainer({ vendorId, refetch, bodyshop }) {
function VendorsFormContainer({ selectedVendor, refetch, bodyshop }) {
const [form] = Form.useForm();
const { t } = useTranslation();
const { loading, error, data } = useQuery(QUERY_VENDOR_BY_ID, {
variables: { id: vendorId },
variables: { id: (selectedVendor && selectedVendor.id) || null },
fetchPolicy: "network-only",
skip: !vendorId
skip: !selectedVendor
});
const [updateVendor] = useMutation(UPDATE_VENDOR);
const [insertvendor] = useMutation(INSERT_NEW_VENDOR);
const [deleteVendor] = useMutation(DELETE_VENDOR);
const handleDelete = () => {
deleteVendor({ variables: { id: vendorId } })
deleteVendor({ variables: { id: selectedVendor.id } })
.then(r => {
notification["success"]({
message: t("vendors.successes.deleted")
@@ -47,11 +47,10 @@ function VendorsFormContainer({ vendorId, refetch, bodyshop }) {
};
const handleFinish = values => {
delete values.keys;
if (vendorId) {
if (selectedVendor.id) {
//It's a vendor to update.
updateVendor({
variables: { id: vendorId, vendor: values }
variables: { id: selectedVendor.id, vendor: values }
})
.then(r => {
notification["success"]({
@@ -64,6 +63,7 @@ function VendorsFormContainer({ vendorId, refetch, bodyshop }) {
notification["error"]({
message: t("vendors.errors.saving")
});
console.log("error", error);
});
} else {
//It's a new vendor to insert.
@@ -85,15 +85,25 @@ function VendorsFormContainer({ vendorId, refetch, bodyshop }) {
}
};
useEffect(() => {
if (data) form.resetFields();
}, [data, form]);
if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />;
return (
<Form onFinish={handleFinish} form={form} autoComplete="new-password">
{data ? (
<Form
onFinish={handleFinish}
form={form}
autoComplete="new-password"
initialValues={data ? data.vendors_by_pk : null}
>
{selectedVendor ? (
<VendorsFormComponent
selec
form={form}
vendor={data ? data.vendors_by_pk : null}
handleDelete={handleDelete}
responsibilityCenters={bodyshop.md_responsibility_centers || null}
/>
) : (
t("vendors.labels.noneselected")

View File

@@ -1,4 +1,4 @@
import { Input, Table, Button } from "antd";
import { Button, Table } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { alphaSort } from "../../utils/sorters";
@@ -7,7 +7,6 @@ export default function VendorsListComponent({
setSelectedVendor,
handleNewVendor,
loading,
refetch,
handleOnRowClick,
vendors
}) {
@@ -56,47 +55,37 @@ export default function VendorsListComponent({
//TODO Implement search
return (
<div>
<Table
loading={loading}
title={() => {
return (
<div>
{" "}
<Input.Search
placeholder={t("general.labels.search")}
onSearch={value => {
console.log(value);
}}
enterButton
/>
<Button onClick={handleNewVendor}>
{t("vendors.actions.new")}
</Button>
</div>
);
}}
size="small"
pagination={{ position: "top" }}
columns={columns.map(item => ({ ...item }))}
rowKey="id"
onChange={handleTableChange}
dataSource={vendors}
rowSelection={{
onSelect: record => {
setSelectedVendor(record);
},
type: "radio",
selectedRowKeys: selectedVendor ? selectedVendor.id : null
}}
onRow={(record, rowIndex) => {
return {
onClick: event => {
handleOnRowClick(record);
}
};
}}
/>
</div>
<Table
loading={loading}
title={() => {
return (
<div>
<Button onClick={handleNewVendor}>
{t("vendors.actions.new")}
</Button>
</div>
);
}}
size="small"
pagination={{ position: "top" }}
columns={columns.map(item => ({ ...item }))}
rowKey="id"
onChange={handleTableChange}
dataSource={vendors}
rowSelection={{
onSelect: record => {
setSelectedVendor(record);
},
type: "radio",
selectedRowKeys: selectedVendor ? [selectedVendor.id] : null
}}
onRow={(record, rowIndex) => {
return {
onClick: event => {
handleOnRowClick(record);
}
};
}}
/>
);
}

View File

@@ -6,10 +6,9 @@ import VendorsListComponent from "./vendors-list.component";
export default function VendorsListContainer({ selectedVendorState }) {
const [selectedVendor, setSelectedVendor] = selectedVendorState;
const { loading, error, data, refetch } = useQuery(QUERY_ALL_VENDORS, {
const { loading, error, data } = useQuery(QUERY_ALL_VENDORS, {
fetchPolicy: "network-only"
});
const handleNewVendor = () => {
setSelectedVendor({});
};
@@ -17,12 +16,10 @@ export default function VendorsListContainer({ selectedVendorState }) {
const handleOnRowClick = record => {
if (record) {
setSelectedVendor(record);
return;
}
setSelectedVendor(null);
} else setSelectedVendor(null);
};
if (error) return <AlertComponent message={error.message} type='error' />;
if (error) return <AlertComponent message={error.message} type="error" />;
return (
<VendorsListComponent
selectedVendor={selectedVendor}
@@ -30,7 +27,6 @@ export default function VendorsListContainer({ selectedVendorState }) {
handleNewVendor={handleNewVendor}
handleOnRowClick={handleOnRowClick}
loading={loading}
refetch={refetch}
vendors={data ? data.vendors : null}
/>
);

View File

@@ -4,12 +4,11 @@ import VendorsFormContainer from "../../components/vendors-form/vendors-form.con
export default function ShopVendorPageComponent({ selectedVendorState }) {
//TODO Figure out how to handle the refresh list when saving form
return (
<div>
<VendorsListContainer selectedVendorState={selectedVendorState} />
<VendorsFormContainer
vendorId={selectedVendorState[0] ? selectedVendorState[0].id : null}
/>
<VendorsFormContainer selectedVendor={selectedVendorState[0]} />
</div>
);
}

View File

@@ -7,11 +7,12 @@ export default function ShopVendorPageContainer() {
useEffect(() => {
document.title = t("titles.shop_vendors");
}, [t]);
const fetchState = useState();
const selectedVendorState = useState();
return (
<div>
<ShopVendorPageComponent selectedVendorState={selectedVendorState} />
</div>
<ShopVendorPageComponent
selectedVendorState={selectedVendorState}
fetchState={fetchState}
/>
);
}

View File

@@ -570,6 +570,7 @@
"saving": "Error encountered while saving vendor. "
},
"fields": {
"am": "Aftermarket",
"city": "City",
"cost_center": "Cost Center",
"country": "Country",
@@ -578,7 +579,10 @@
"due_date": "Payment Due Date",
"email": "Contact Email",
"favorite": "Favorite?",
"lkq": "LKQ",
"make": "Make",
"name": "Vendor Name",
"oem": "OEM",
"prompt_discount": "Prompt Discount %",
"state": "State/Province",
"street1": "Street",

View File

@@ -570,6 +570,7 @@
"saving": "Se encontró un error al guardar el proveedor."
},
"fields": {
"am": "",
"city": "ciudad",
"cost_center": "Centro de costos",
"country": "País",
@@ -578,7 +579,10 @@
"due_date": "Fecha de vencimiento del pago",
"email": "Email de contacto",
"favorite": "¿Favorito?",
"lkq": "",
"make": "",
"name": "Nombre del vendedor",
"oem": "",
"prompt_discount": "Descuento pronto",
"state": "Provincia del estado",
"street1": "calle",

View File

@@ -570,6 +570,7 @@
"saving": "Erreur rencontrée lors de l'enregistrement du fournisseur."
},
"fields": {
"am": "",
"city": "Ville",
"cost_center": "Centre de coûts",
"country": "Pays",
@@ -578,7 +579,10 @@
"due_date": "Date limite de paiement",
"email": "Email du contact",
"favorite": "Préféré?",
"lkq": "",
"make": "",
"name": "Nom du vendeur",
"oem": "",
"prompt_discount": "Remise rapide%",
"state": "Etat / Province",
"street1": "rue",