IO-3624 Polish employee and team config layouts

This commit is contained in:
Dave
2026-03-24 12:50:11 -04:00
parent bcb693f03c
commit fd712da4a3
12 changed files with 533 additions and 405 deletions

View File

@@ -1,7 +1,7 @@
import { DeleteFilled } from "@ant-design/icons";
import { useApolloClient, useMutation, useQuery } from "@apollo/client/react";
import { useTreatmentsWithConfig } from "@splitsoftware/splitio-react";
import { Button, Card, Form, Input, InputNumber, Select, Space, Switch } from "antd";
import { Button, Card, Col, Form, Input, InputNumber, Row, Select, Space, Switch } from "antd";
import ResponsiveTable from "../responsive-table/responsive-table.component";
import { useForm } from "antd/es/form/Form";
import queryString from "query-string";
@@ -30,6 +30,7 @@ import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.c
import { getFormListItemTitle } from "../form-list-move-arrows/form-list-item-title.utils";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import ShopEmployeeAddVacation from "./shop-employees-add-vacation.component";
import FormItemEmail from "../form-items-formatted/email-form-item.component.jsx";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
@@ -42,6 +43,14 @@ export function ShopEmployeesFormComponent({ bodyshop }) {
const { t } = useTranslation();
const [form] = useForm();
const employeeRates = Form.useWatch(["rates"], form) || [];
const employeeOptionsColProps = {
xs: 24,
sm: 12,
md: 8,
lg: { flex: "0 0 320px" },
xl: { flex: "0 0 280px" },
xxl: { flex: "0 0 380px" }
};
const history = useNavigate();
const search = queryString.parse(useLocation().search);
const [deleteVacation] = useMutation(DELETE_VACATION);
@@ -177,237 +186,280 @@ export function ShopEmployeesFormComponent({ bodyshop }) {
}
>
<Form onFinish={handleFinish} autoComplete={"off"} layout="vertical" form={form}>
<LayoutFormRow>
<Form.Item
name="first_name"
label={t("employees.fields.first_name")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Input />
</Form.Item>
<Form.Item
label={t("employees.fields.last_name")}
name="last_name"
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Input />
</Form.Item>
<Form.Item
name="employee_number"
label={t("employees.fields.employee_number")}
validateTrigger="onBlur"
hasFeedback
rules={[
{
required: true
//message: t("general.validation.required"),
},
() => ({
async validator(rule, value) {
if (value) {
const response = await client.query({
query: CHECK_EMPLOYEE_NUMBER,
variables: {
employeenumber: value
}
});
if (response.data.employees_aggregate.aggregate.count === 0) {
return Promise.resolve();
} else if (
response.data.employees_aggregate.nodes.length === 1 &&
response.data.employees_aggregate.nodes[0].id === form.getFieldValue("id")
) {
return Promise.resolve();
<LayoutFormRow title={t("bodyshop.labels.employee_options")}>
<div style={{ display: "grid", rowGap: 16 }}>
<Row gutter={[16, 16]} wrap>
<Col {...employeeOptionsColProps}>
<Form.Item
name="first_name"
label={t("employees.fields.first_name")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
return Promise.reject(t("employees.validation.unique_employee_number"));
} else {
return Promise.resolve();
}
}
})
]}
>
<Input />
</Form.Item>
<Form.Item
label={t("employees.fields.pin")}
name="pin"
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Input />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow>
<Form.Item label={t("employees.fields.active")} valuePropName="checked" name="active">
<Switch />
</Form.Item>
<Form.Item label={t("employees.fields.flat_rate")} name="flat_rate" valuePropName="checked">
<Switch />
</Form.Item>
<Form.Item
name="hire_date"
label={t("employees.fields.hire_date")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<DateTimePicker isDateOnly />
</Form.Item>
<Form.Item label={t("employees.fields.termination_date")} name="termination_date">
<DateTimePicker isDateOnly />
</Form.Item>
<Form.Item
label={t("employees.fields.user_email")}
name="user_email"
validateTrigger="onBlur"
rules={[
({ getFieldValue }) => ({
async validator(rule, value) {
const user_email = getFieldValue("user_email");
if (user_email && value) {
const response = await client.query({
query: QUERY_USERS_BY_EMAIL,
variables: {
email: user_email
}
});
if (response.data.users.length === 1) {
return Promise.resolve();
}
return Promise.reject(t("bodyshop.validation.useremailmustexist"));
} else {
return Promise.resolve();
}
}
})
]}
>
<Input />
</Form.Item>
<Form.Item label={t("employees.fields.external_id")} name="external_id">
<Input />
</Form.Item>
</LayoutFormRow>
<Form.List name={["rates"]}>
{(fields, { add, remove, move }) => {
return (
<div>
{fields.map((field, index) => {
const employeeRate = employeeRates[field.name] || {};
return (
<Form.Item key={field.key} style={{ padding: 0, margin: 2 }}>
<LayoutFormRow
grow
title={getFormListItemTitle(t("employees.fields.cost_center"), index, employeeRate.cost_center)}
extra={
<Space align="center" size="small">
<Button
type="text"
icon={<DeleteFilled />}
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
orientation="horizontal"
/>
</Space>
}
>
<Form.Item
label={t("employees.fields.cost_center")}
key={`${index}`}
name={[field.name, "cost_center"]}
valuePropName="value"
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Select
options={[
{ value: "timetickets.labels.shift", label: t("timetickets.labels.shift") },
...(bodyshop.cdk_dealerid ||
bodyshop.pbs_serialnumber ||
bodyshop.rr_dealerid ||
Enhanced_Payroll.treatment === "on"
? CiecaSelect(false, true)
: bodyshop.md_responsibility_centers.costs.map((c) => ({
value: c.name,
label: c.name
})))
]}
/>
</Form.Item>
<Form.Item
label={t("employees.fields.rate")}
key={`${index}`}
name={[field.name, "rate"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber min={0} precision={2} />
</Form.Item>
</LayoutFormRow>
</Form.Item>
);
})}
<Form.Item>
<Button
type="dashed"
onClick={() => {
add();
}}
style={{ width: "100%" }}
id="add-employee-rate-button"
>
<span id="new-employee-rate">{t("employees.actions.newrate")}</span>
</Button>
]}
>
<Input />
</Form.Item>
</div>
);
}}
</Form.List>
</Col>
<Col {...employeeOptionsColProps}>
<Form.Item
label={t("employees.fields.last_name")}
name="last_name"
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Input />
</Form.Item>
</Col>
<Col {...employeeOptionsColProps}>
<Form.Item
name="employee_number"
label={t("employees.fields.employee_number")}
validateTrigger="onBlur"
hasFeedback
rules={[
{
required: true
//message: t("general.validation.required"),
},
() => ({
async validator(rule, value) {
if (value) {
const response = await client.query({
query: CHECK_EMPLOYEE_NUMBER,
variables: {
employeenumber: value
}
});
if (response.data.employees_aggregate.aggregate.count === 0) {
return Promise.resolve();
} else if (
response.data.employees_aggregate.nodes.length === 1 &&
response.data.employees_aggregate.nodes[0].id === form.getFieldValue("id")
) {
return Promise.resolve();
}
return Promise.reject(t("employees.validation.unique_employee_number"));
} else {
return Promise.resolve();
}
}
})
]}
>
<Input />
</Form.Item>
</Col>
<Col {...employeeOptionsColProps}>
<Form.Item
label={t("employees.fields.pin")}
name="pin"
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Input />
</Form.Item>
</Col>
</Row>
<Row gutter={[16, 16]} wrap>
<Col {...employeeOptionsColProps}>
<Form.Item label={t("employees.fields.active")} valuePropName="checked" name="active">
<Switch />
</Form.Item>
</Col>
<Col {...employeeOptionsColProps}>
<Form.Item label={t("employees.fields.flat_rate")} name="flat_rate" valuePropName="checked">
<Switch />
</Form.Item>
</Col>
<Col {...employeeOptionsColProps}>
<Form.Item
name="hire_date"
label={t("employees.fields.hire_date")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<DateTimePicker isDateOnly />
</Form.Item>
</Col>
<Col {...employeeOptionsColProps}>
<Form.Item label={t("employees.fields.termination_date")} name="termination_date">
<DateTimePicker isDateOnly />
</Form.Item>
</Col>
<Col {...employeeOptionsColProps}>
<Form.Item
label={t("employees.fields.user_email")}
name="user_email"
validateTrigger="onBlur"
rules={[
({ getFieldValue }) => ({
async validator(rule, value) {
const user_email = getFieldValue("user_email");
if (user_email && value) {
const response = await client.query({
query: QUERY_USERS_BY_EMAIL,
variables: {
email: user_email
}
});
if (response.data.users.length === 1) {
return Promise.resolve();
}
return Promise.reject(t("bodyshop.validation.useremailmustexist"));
} else {
return Promise.resolve();
}
}
})
]}
>
<FormItemEmail />
</Form.Item>
</Col>
<Col {...employeeOptionsColProps}>
<Form.Item label={t("employees.fields.external_id")} name="external_id">
<Input />
</Form.Item>
</Col>
</Row>
</div>
</LayoutFormRow>
<LayoutFormRow title={t("bodyshop.labels.employee_rates")}>
<Form.List name={["rates"]}>
{(fields, { add, remove, move }) => {
return (
<div>
{fields.map((field, index) => {
const employeeRate = employeeRates[field.name] || {};
return (
<Form.Item key={field.key} style={{ padding: 0, margin: 2 }}>
<LayoutFormRow
grow
title={getFormListItemTitle(
t("employees.fields.cost_center"),
index,
employeeRate.cost_center
)}
extra={
<Space align="center" size="small">
<Button
type="text"
icon={<DeleteFilled />}
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
orientation="horizontal"
/>
</Space>
}
>
<Form.Item
label={t("employees.fields.cost_center")}
key={`${index}`}
name={[field.name, "cost_center"]}
valuePropName="value"
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Select
options={[
{ value: "timetickets.labels.shift", label: t("timetickets.labels.shift") },
...(bodyshop.cdk_dealerid ||
bodyshop.pbs_serialnumber ||
bodyshop.rr_dealerid ||
Enhanced_Payroll.treatment === "on"
? CiecaSelect(false, true)
: bodyshop.md_responsibility_centers.costs.map((c) => ({
value: c.name,
label: c.name
})))
]}
/>
</Form.Item>
<Form.Item
label={t("employees.fields.rate")}
key={`${index}`}
name={[field.name, "rate"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber min={0} precision={2} />
</Form.Item>
</LayoutFormRow>
</Form.Item>
);
})}
<Form.Item>
<Button
type="dashed"
onClick={() => {
add();
}}
style={{ width: "100%" }}
id="add-employee-rate-button"
>
<span id="new-employee-rate">{t("employees.actions.newrate")}</span>
</Button>
</Form.Item>
</div>
);
}}
</Form.List>
</LayoutFormRow>
</Form>
<ResponsiveTable
title={() => <ShopEmployeeAddVacation employee={data && data.employees_by_pk} />}
columns={columns}
mobileColumnKeys={["start", "length", "actions"]}
rowKey={"id"}
dataSource={data?.employees_by_pk?.employee_vacations ?? []}
/>
<LayoutFormRow title={t("bodyshop.labels.employee_vacation")}>
<div>
<ResponsiveTable
columns={columns}
mobileColumnKeys={["start", "length", "actions"]}
rowKey={"id"}
dataSource={data?.employees_by_pk?.employee_vacations ?? []}
pagination={false}
/>
<div style={{ marginTop: 12 }}>
<ShopEmployeeAddVacation
employee={data && data.employees_by_pk}
buttonProps={{
type: "dashed",
block: true
}}
/>
</div>
</div>
</LayoutFormRow>
</Card>
);
}