Files
bodyshop/client/src/components/time-ticket-list/time-ticket-list.component.jsx
Allan Carr 91e2e7931b Merged in feature/IO-2894-Modify-Shift-Memo (pull request #1671)
Feature/IO-2894 Modify Shift Memo
2024-08-29 22:00:14 +00:00

388 lines
13 KiB
JavaScript

import { EditFilled, SyncOutlined } from "@ant-design/icons";
import { useSplitTreatments } from "@splitsoftware/splitio-react";
import { Button, Card, Checkbox, Space, Table } from "antd";
import React, { useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectAuthLevel, selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
import { onlyUnique } from "../../utils/arrayHelper";
import dayjs from "../../utils/day";
import { alphaSort, dateSort } from "../../utils/sorters";
import RbacWrapper, { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
import TimeTicketEnterButton from "../time-ticket-enter-button/time-ticket-enter-button.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
authLevel: selectAuthLevel,
currentUser: selectCurrentUser
});
const mapDispatchToProps = (dispatch) => ({
setTimeTicketTaskContext: (context) => dispatch(setModalContext({ context: context, modal: "timeTicketTask" }))
});
export default connect(mapStateToProps, mapDispatchToProps)(TimeTicketList);
export function TimeTicketList({
bodyshop,
setTimeTicketTaskContext,
authLevel,
currentUser,
disabled,
loading,
timetickets,
refetch,
techConsole,
jobId,
extra
}) {
const [state, setState] = useState({
sortedInfo: {},
filteredInfo: { text: "" }
});
const { t } = useTranslation();
const {
treatments: { Enhanced_Payroll }
} = useSplitTreatments({
attributes: {},
names: ["Enhanced_Payroll"],
splitKey: bodyshop.imexshopid
});
const totals = useMemo(() => {
if (timetickets)
return timetickets.reduce(
(acc, val) => {
acc.productivehrs = acc.productivehrs + val.productivehrs;
acc.actualhrs = acc.actualhrs + val.actualhrs;
return acc;
},
{ productivehrs: 0, actualhrs: 0 }
);
return { productivehrs: 0, actualhrs: 0 };
}, [timetickets]);
const columns = [
...(Enhanced_Payroll.treatment === "on"
? [
{
title: t("timetickets.fields.committed"),
dataIndex: "committed_at",
key: "committed_at",
render: (text, record) => <Checkbox disabled checked={record.committed_at} />
}
]
: []),
{
title: t("timetickets.fields.date"),
dataIndex: "date",
key: "date",
sorter: (a, b) => dateSort(a.date, b.date),
sortOrder: state.sortedInfo.columnKey === "date" && state.sortedInfo.order,
render: (text, record) => <DateFormatter>{record.date}</DateFormatter>
},
{
title: t("timetickets.fields.employee"),
dataIndex: "employee",
key: "employee",
sorter: (a, b) => alphaSort(a.employee.last_name, b.employee.last_name),
sortOrder: state.sortedInfo.columnKey === "employee" && state.sortedInfo.order,
render: (text, record) => `${record.employee.first_name} ${record.employee.last_name}`,
filters:
timetickets
.map((l) => l.employeeid)
.filter(onlyUnique)
.map((s) => {
return {
text: (() => {
const emp = bodyshop.employees.find((e) => e.id === s);
return `${emp?.first_name} ${emp?.last_name}`;
})(), //
value: [s]
};
}) || [],
onFilter: (value, record) => value.includes(record.employeeid)
},
{
title: t("timetickets.fields.cost_center"),
dataIndex: "cost_center",
key: "cost_center",
sorter: (a, b) => alphaSort(a.cost_center, b.cost_center),
render: (text, record) =>
record.cost_center === "timetickets.labels.shift" ? t(record.cost_center) : record.cost_center,
sortOrder: state.sortedInfo.columnKey === "cost_center" && state.sortedInfo.order,
filters:
timetickets
.map((l) => l.cost_center)
.filter(onlyUnique)
.map((s) => {
return {
text: s === "timetickets.labels.shift" ? t(s) : s, //|| "No Status*",
value: [s]
};
}) || [],
onFilter: (value, record) => value.includes(record.cost_center)
},
...(jobId
? []
: [
{
title: t("jobs.fields.ro_number"),
dataIndex: "ro_number",
key: "ro_number",
sorter: (a, b) => alphaSort(a.job && a.job.ro_number, b.job && b.job.ro_number),
sortOrder: state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
render: (text, record) =>
record.job && <Link to={"/manage/jobs/" + record.job.id}>{record.job.ro_number || "N/A"}</Link>
}
]),
{
title: t("timetickets.fields.productivehrs"),
dataIndex: "productivehrs",
key: "productivehrs",
sorter: (a, b) => a.productivehrs - b.productivehrs,
sortOrder: state.sortedInfo.columnKey === "productivehrs" && state.sortedInfo.order
},
...(Enhanced_Payroll.treatment === "on"
? []
: [
{
title: t("timetickets.fields.actualhrs"),
dataIndex: "actualhrs",
key: "actualhrs",
sorter: (a, b) => a.actualhrs - b.actualhrs,
sortOrder: state.sortedInfo.columnKey === "actualhrs" && state.sortedInfo.order
}
]),
{
title: t("timetickets.fields.memo"),
dataIndex: "memo",
key: "memo",
sorter: (a, b) => alphaSort(a.memo, b.memo),
sortOrder: state.sortedInfo.columnKey === "memo" && state.sortedInfo.order,
render: (text, record) => (record.memo.startsWith("timetickets.labels") ? t(record.memo) : record.memo)
},
...(Enhanced_Payroll.treatment === "on"
? [
{
title: t("timetickets.fields.task_name"),
dataIndex: "task_name",
key: "task_name",
sorter: (a, b) => alphaSort(a.task_name, b.task_name),
sortOrder: state.sortedInfo.columnKey === "task_name" && state.sortedInfo.order
}
]
: []),
...(Enhanced_Payroll.treatment === "on"
? []
: [
{
title: t("timetickets.fields.clockon"),
dataIndex: "clockon",
key: "clockon",
render: (text, record) => <DateTimeFormatter>{record.clockon}</DateTimeFormatter>
},
{
title: t("timetickets.fields.clockoff"),
dataIndex: "clockoff",
key: "clockoff",
render: (text, record) => <DateTimeFormatter>{record.clockoff}</DateTimeFormatter>
},
{
title: t("timetickets.fields.clockhours"),
dataIndex: "clockhours",
key: "clockhours",
render: (text, record) => {
if (record.clockoff && record.clockon)
return <div>{dayjs(record.clockoff).diff(dayjs(record.clockon), "hour", true).toFixed(2)}</div>;
else {
return null;
}
}
}
]),
{
title: t("timetickets.fields.created_by"),
dataIndex: "created_by",
key: "created_by",
sorter: (a, b) => alphaSort(a.created_by, b.created_by),
sortOrder: state.sortedInfo.columnKey === "created_by" && state.sortedInfo.order,
render: (text, record) => record.created_by
},
// {
// title: "Pay",
// dataIndex: "pay",
// key: "pay",
// render: (text, record) =>
// Dinero({ amount: Math.round(record.rate * 100) })
// .multiply(record.flat_rate ? record.productivehrs : record.actualhrs)
// .toFormat("$0.00"),
// },
{
title: t("general.labels.actions"),
dataIndex: "actions",
key: "actions",
render: (text, record) => (
<Space wrap>
{techConsole && (
<TimeTicketEnterButton
actions={{ refetch }}
context={{ id: record.id, timeticket: record }}
disabled={!record.job || disabled}
>
<EditFilled />
</TimeTicketEnterButton>
)}
{!techConsole && (
<RbacWrapper
action="timetickets:edit"
noauth={() => {
return <div />;
}}
>
<TimeTicketEnterButton
actions={{ refetch }}
context={{
id: record.id,
timeticket: record
}}
disabled={
record.ciecacode
? record.committed_at
? HasRbacAccess({
bodyshop,
authLevel: authLevel,
action: "timetickets:editcommitted"
}) &&
HasRbacAccess({
bodyshop,
authLevel: authLevel,
action: "timetickets:edit"
})
: HasRbacAccess({
bodyshop,
authLevel: authLevel,
action: "timetickets:edit"
})
: record.committed_at
? HasRbacAccess({
bodyshop,
authLevel: authLevel,
action: "timetickets:editcommitted"
}) &&
HasRbacAccess({
bodyshop,
authLevel: authLevel,
action: "timetickets:shiftedit"
})
: HasRbacAccess({
bodyshop,
authLevel: authLevel,
action: "timetickets:shiftedit"
})
? disabled
: !record.jobid
}
>
<EditFilled />
</TimeTicketEnterButton>
</RbacWrapper>
)}
</Space>
)
}
];
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
};
return (
<Card
title={t("timetickets.labels.timetickets")}
extra={
<Space wrap>
{jobId && bodyshop.md_tasks_presets.enable_tasks && (
<Button
disabled={disabled}
onClick={() => {
setTimeTicketTaskContext({
actions: { refetch: refetch },
context: { jobid: jobId }
});
}}
>
{t("timetickets.actions.claimtasks")}
</Button>
)}
{jobId &&
(techConsole ? null : (
<TimeTicketEnterButton
actions={{ refetch }}
context={{
jobId: jobId,
created_by: currentUser.displayName
? currentUser.email.concat(" | ", currentUser.displayName)
: currentUser.email
}}
disabled={disabled}
>
{t("timetickets.actions.enter")}
</TimeTicketEnterButton>
))}
{extra}
<Button
onClick={async () => {
refetch();
}}
>
<SyncOutlined />
</Button>
</Space>
}
>
<Table
loading={loading}
columns={columns}
rowKey="id"
scroll={{
x: true
}}
dataSource={timetickets}
onChange={handleTableChange}
summary={() => {
if (Enhanced_Payroll.treatment === "on") return null;
if (Enhanced_Payroll.treatment === "off")
return (
<Table.Summary.Row>
<Table.Summary.Cell>{t("general.labels.totals")}</Table.Summary.Cell>
<Table.Summary.Cell />
<Table.Summary.Cell />
<Table.Summary.Cell />
<Table.Summary.Cell>{totals.productivehrs.toFixed(1)}</Table.Summary.Cell>
<Table.Summary.Cell>{totals.actualhrs.toFixed(1)}</Table.Summary.Cell>
<Table.Summary.Cell>
{totals.actualhrs === 0 || !totals.actualhrs
? "∞"
: `${((totals.productivehrs / totals.actualhrs) * 100).toFixed(
2
)}% ${t("timetickets.labels.efficiency")}`}
</Table.Summary.Cell>
<Table.Summary.Cell />
<Table.Summary.Cell />
<Table.Summary.Cell />
</Table.Summary.Row>
);
}}
/>
</Card>
);
}