Added my shop page and ability to add, edit and delete employees.
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
import { Tag, Popover } from "antd";
|
||||
import React from "react";
|
||||
import Barcode from "react-barcode";
|
||||
import { useTranslation } from "react-i18next";
|
||||
export default function BarcodePopupComponent({ value }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div>
|
||||
<Popover
|
||||
content={
|
||||
<Barcode
|
||||
value={value}
|
||||
background="transparent"
|
||||
displayValue={false}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Tag>{t("general.labels.barcode")}</Tag>
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -72,6 +72,12 @@ export default ({
|
||||
</Menu.Item>
|
||||
</Menu.SubMenu>
|
||||
|
||||
<Menu.Item key="shop">
|
||||
<Link to="/manage/shop">
|
||||
{t("menus.header.shop")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
|
||||
<Menu.SubMenu
|
||||
title={
|
||||
<div>
|
||||
|
||||
@@ -20,6 +20,7 @@ import { createStructuredSelector } from "reselect";
|
||||
import CarImage from "../../assets/car.svg";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import BarcodePopup from "../barcode-popup/barcode-popup.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
@@ -49,32 +50,6 @@ export default connect(
|
||||
</div>
|
||||
);
|
||||
|
||||
const tombstoneSubtitle = (
|
||||
<div>
|
||||
<Tag color="red">
|
||||
{job.owner ? (
|
||||
<Link to={`/manage/owners/${job.owner.id}`}>
|
||||
{`${job.ownr_co_nm || ""}${job.ownr_fn || ""} ${job.ownr_ln || ""}`}
|
||||
</Link>
|
||||
) : (
|
||||
t("jobs.errors.noowner")
|
||||
)}
|
||||
</Tag>
|
||||
|
||||
<Tag color="green">
|
||||
{job.vehicle ? (
|
||||
<Link to={`/manage/vehicles/${job.vehicle.id}`}>
|
||||
{job.vehicle.v_model_yr || t("general.labels.na")}{" "}
|
||||
{job.vehicle.v_make_desc || t("general.labels.na")}{" "}
|
||||
{job.vehicle.v_model_desc || t("general.labels.na")} |{" "}
|
||||
{job.vehicle.plate_no || t("general.labels.na")} |{" "}
|
||||
{job.vehicle.v_vin || t("general.labels.na")}
|
||||
</Link>
|
||||
) : null}
|
||||
</Tag>
|
||||
</div>
|
||||
);
|
||||
|
||||
const statusmenu = (
|
||||
<Menu
|
||||
onClick={e => {
|
||||
@@ -137,10 +112,32 @@ export default connect(
|
||||
border: "1px solid rgb(235, 237, 240)"
|
||||
}}
|
||||
title={tombstoneTitle}
|
||||
subTitle={tombstoneSubtitle}
|
||||
//subTitle={tombstoneSubtitle}
|
||||
tags={
|
||||
<span key="job-status">
|
||||
{job.status ? <Tag color="blue">{job.status}</Tag> : null}
|
||||
<Tag color="red">
|
||||
{job.owner ? (
|
||||
<Link to={`/manage/owners/${job.owner.id}`}>
|
||||
{`${job.ownr_co_nm || ""}${job.ownr_fn || ""} ${job.ownr_ln ||
|
||||
""}`}
|
||||
</Link>
|
||||
) : (
|
||||
t("jobs.errors.noowner")
|
||||
)}
|
||||
</Tag>
|
||||
<Tag color="green">
|
||||
{job.vehicle ? (
|
||||
<Link to={`/manage/vehicles/${job.vehicle.id}`}>
|
||||
{job.vehicle.v_model_yr || t("general.labels.na")}{" "}
|
||||
{job.vehicle.v_make_desc || t("general.labels.na")}{" "}
|
||||
{job.vehicle.v_model_desc || t("general.labels.na")} |{" "}
|
||||
{job.vehicle.plate_no || t("general.labels.na")} |{" "}
|
||||
{job.vehicle.v_vin || t("general.labels.na")}
|
||||
</Link>
|
||||
) : null}
|
||||
</Tag>
|
||||
<BarcodePopup value={job.id} />
|
||||
</span>
|
||||
}
|
||||
extra={menuExtra}
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
import { Button, DatePicker, Form, Input, InputNumber, Switch } from "antd";
|
||||
import moment from "moment";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function ShopEmployeesFormComponent({
|
||||
form,
|
||||
selectedEmployee,
|
||||
handleSubmit
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { getFieldDecorator } = form;
|
||||
if (!selectedEmployee) return "//TODO No employee selected.";
|
||||
return (
|
||||
<Form onSubmit={handleSubmit} autoComplete={"off"}>
|
||||
<Button type="primary" htmlType="submit">
|
||||
{t("general.actions.save")}
|
||||
</Button>
|
||||
|
||||
<Form.Item label={t("employees.fields.first_name")}>
|
||||
{getFieldDecorator("first_name", {
|
||||
initialValue: selectedEmployee.first_name,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: t("general.validation.required")
|
||||
}
|
||||
]
|
||||
})(<Input name="first_name" />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("employees.fields.last_name")}>
|
||||
{getFieldDecorator("last_name", {
|
||||
initialValue: selectedEmployee.last_name,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: t("general.validation.required")
|
||||
}
|
||||
]
|
||||
})(<Input name="last_name" />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("employees.fields.employee_number")}>
|
||||
{getFieldDecorator("employee_number", {
|
||||
initialValue: selectedEmployee.employee_number,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: t("general.validation.required")
|
||||
}
|
||||
]
|
||||
})(<Input name="employee_number" />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("employees.fields.active")}>
|
||||
{getFieldDecorator("active", {
|
||||
initialValue: selectedEmployee.active,
|
||||
valuePropName: "checked"
|
||||
})(<Switch name="active" />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("employees.fields.flat_rate")}>
|
||||
{getFieldDecorator("flat_rate", {
|
||||
initialValue: selectedEmployee.flat_rate,
|
||||
valuePropName: "checked"
|
||||
})(<Switch name="active" />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("employees.fields.hire_date")}>
|
||||
{getFieldDecorator("hire_date", {
|
||||
initialValue: selectedEmployee.hire_date
|
||||
? moment(selectedEmployee.hire_date)
|
||||
: null,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: t("general.validation.required")
|
||||
}
|
||||
]
|
||||
})(<DatePicker name="hire_date" />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("employees.fields.termination_date")}>
|
||||
{getFieldDecorator("termination_date", {
|
||||
initialValue: selectedEmployee.termination_date
|
||||
? moment(selectedEmployee.termination_date)
|
||||
: null
|
||||
})(<DatePicker name="termination_date" />)}
|
||||
</Form.Item>
|
||||
{
|
||||
//TODO Make this a picklist.
|
||||
}
|
||||
<Form.Item label={t("employees.fields.cost_center")}>
|
||||
{getFieldDecorator("cost_center", {
|
||||
initialValue: selectedEmployee.cost_center,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: t("general.validation.required")
|
||||
}
|
||||
]
|
||||
})(<Input name="cost_center" />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("employees.fields.base_rate")}>
|
||||
{getFieldDecorator("base_rate", {
|
||||
initialValue: selectedEmployee.base_rate,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: t("general.validation.required")
|
||||
}
|
||||
]
|
||||
})(<InputNumber name="base_rate" />)}
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { Button, List } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
export default function ShopEmployeesListComponent({
|
||||
loading,
|
||||
employees,
|
||||
setSelectedEmployee,
|
||||
handleDelete
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
setSelectedEmployee({});
|
||||
}}
|
||||
>
|
||||
{t("employees.actions.new")}
|
||||
</Button>
|
||||
<List
|
||||
loading={loading}
|
||||
itemLayout="horizontal"
|
||||
dataSource={employees}
|
||||
renderItem={item => (
|
||||
<List.Item
|
||||
actions={[
|
||||
<Button key="edit" onClick={() => setSelectedEmployee(item)}>
|
||||
{t("general.actions.edit")}
|
||||
</Button>,
|
||||
<Button key="delete" onClick={() => handleDelete(item.id)}>
|
||||
{t("general.actions.delete")}
|
||||
</Button>
|
||||
]}
|
||||
>
|
||||
<List.Item.Meta
|
||||
title={`${item.first_name || ""} ${item.last_name ||
|
||||
""} | ${item.employee_number || ""}`}
|
||||
description={
|
||||
<span>
|
||||
{item.cost_center} @{" "}
|
||||
<CurrencyFormatter>{item.base_rate}</CurrencyFormatter>
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Col, Row } from "antd";
|
||||
import React from "react";
|
||||
import ShopEmployeesFormComponent from "./shop-employees-form.component";
|
||||
import ShopEmployeesListComponent from "./shop-employees-list.component";
|
||||
export default function ShopEmployeeComponent({
|
||||
form,
|
||||
loading,
|
||||
employees,
|
||||
employeeState,
|
||||
handleSubmit,
|
||||
handleDelete
|
||||
}) {
|
||||
const [selectedEmployee, setSelectedEmployee] = employeeState;
|
||||
return (
|
||||
<Row>
|
||||
<Col span={6}>
|
||||
<ShopEmployeesListComponent
|
||||
employees={employees}
|
||||
loading={loading}
|
||||
setSelectedEmployee={setSelectedEmployee}
|
||||
handleDelete={handleDelete}
|
||||
/>
|
||||
</Col>
|
||||
|
||||
<Col span={18}>
|
||||
<ShopEmployeesFormComponent
|
||||
handleSubmit={handleSubmit}
|
||||
form={form}
|
||||
selectedEmployee={selectedEmployee}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import { Form, notification } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useMutation, useQuery } from "react-apollo";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { DELETE_EMPLOYEE, INSERT_EMPLOYEES, QUERY_EMPLOYEES, UPDATE_EMPLOYEE } from "../../graphql/employees.queries";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import ShopEmployeeComponent from "./shop-employees.component";
|
||||
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
|
||||
function ShopEmployeesContainer({ form, bodyshop }) {
|
||||
const { t } = useTranslation();
|
||||
const employeeState = useState(null);
|
||||
const { loading, error, data, refetch } = useQuery(QUERY_EMPLOYEES, {
|
||||
fetchPolicy: "network-only"
|
||||
});
|
||||
|
||||
const [updateEmployee] = useMutation(UPDATE_EMPLOYEE);
|
||||
const [insertEmployees] = useMutation(INSERT_EMPLOYEES);
|
||||
const [deleteEmployee] = useMutation(DELETE_EMPLOYEE);
|
||||
const handleDelete = id => {
|
||||
deleteEmployee({ variables: { id: id } })
|
||||
.then(r => {
|
||||
notification["success"]({
|
||||
message: t("employees.successes.delete")
|
||||
});
|
||||
//TODO: Better way to reset the field decorators?
|
||||
employeeState[1](null);
|
||||
refetch().then(r => form.resetFields());
|
||||
})
|
||||
.catch(error => {
|
||||
notification["error"]({
|
||||
message: t("employees.errors.delete")
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = e => {
|
||||
e.preventDefault();
|
||||
|
||||
form.validateFieldsAndScroll((err, values) => {
|
||||
if (err) {
|
||||
notification["error"]({
|
||||
message: t("employees.errors.validationtitle"),
|
||||
description: t("employees.errors.validation")
|
||||
});
|
||||
}
|
||||
if (!err) {
|
||||
if (employeeState[0].id) {
|
||||
//Update a record.
|
||||
updateEmployee({
|
||||
variables: { id: employeeState[0].id, employee: values }
|
||||
})
|
||||
.then(r => {
|
||||
notification["success"]({
|
||||
message: t("employees.successes.save")
|
||||
});
|
||||
//TODO: Better way to reset the field decorators?
|
||||
employeeState[1](null);
|
||||
refetch().then(r => form.resetFields());
|
||||
})
|
||||
.catch(error => {
|
||||
notification["error"]({
|
||||
message: t("employees.errors.save")
|
||||
});
|
||||
});
|
||||
} else {
|
||||
//New record, insert it.
|
||||
insertEmployees({
|
||||
variables: { employees: [{ ...values, shopid: bodyshop.id }] }
|
||||
}).then(r => {
|
||||
notification["success"]({
|
||||
message: t("employees.successes.save")
|
||||
});
|
||||
//TODO: Better way to reset the field decorators?
|
||||
employeeState[1](null);
|
||||
refetch()
|
||||
.then(r => form.resetFields())
|
||||
.catch(error => {
|
||||
notification["error"]({
|
||||
message: t("employees.errors.save")
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
|
||||
return (
|
||||
<ShopEmployeeComponent
|
||||
handleSubmit={handleSubmit}
|
||||
handleDelete={handleDelete}
|
||||
form={form}
|
||||
loading={loading}
|
||||
employeeState={employeeState}
|
||||
employees={data ? data.employees : []}
|
||||
/>
|
||||
);
|
||||
}
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
null
|
||||
)(Form.create({ name: "ShopEmployeesContainer" })(ShopEmployeesContainer));
|
||||
@@ -1,9 +1,8 @@
|
||||
import { Input, 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 PhoneFormatter from "../../utils/PhoneFormatter";
|
||||
import { Table, Icon, Input } from "antd";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
|
||||
export default function VehiclesListComponent({ loading, vehicles, refetch }) {
|
||||
const [state, setState] = useState({
|
||||
|
||||
Reference in New Issue
Block a user