Files
bodyshop/client/src/components/tt-approvals-list/tt-approvals-list.component.jsx
2026-03-12 12:19:34 -04:00

215 lines
7.4 KiB
JavaScript

import { SyncOutlined } from "@ant-design/icons";
import { Button, Card, Space, Tag } from "antd";
import ResponsiveTable from "../responsive-table/responsive-table.component";
import Dinero from "dinero.js";
import queryString from "query-string";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link, useLocation, useNavigate } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { selectAuthLevel, selectBodyshop } from "../../redux/user/user.selectors";
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
import { onlyUnique } from "../../utils/arrayHelper";
import { alphaSort, dateSort } from "../../utils/sorters";
import TtApproveButtonComponent from "../tt-approve-button/tt-approve-button.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
authLevel: selectAuthLevel
});
const mapDispatchToProps = () => ({});
export default connect(mapStateToProps, mapDispatchToProps)(TtApprovalsListComponent);
export function TtApprovalsListComponent({
bodyshop,
loading,
tt_approval_queue,
total,
refetch,
extra
}) {
const [state, setState] = useState({
sortedInfo: {},
filteredInfo: { text: "" }
});
const { t } = useTranslation();
const history = useNavigate();
const search = queryString.parse(useLocation().search);
const { page } = search;
const [selectedTickets, setSelectedTickets] = useState([]);
const columns = [
{
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: "employeeid",
key: "employeeid",
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:
tt_approval_queue
.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:
tt_approval_queue
.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)
},
{
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}>
<Space wrap>
{record.job.ro_number || "N/A"}
<Tag>{record.job.status}</Tag>
</Space>
</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
},
{
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)
},
{
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,
render: (text, record) => record.task_name || ""
},
{
title: t("timetickets.fields.clockon"),
dataIndex: "clockon",
key: "clockon",
render: (text, record) => <DateTimeFormatter>{record.clockon}</DateTimeFormatter>
},
{
title: "Pay",
dataIndex: "pay",
key: "pay",
render: (text, record) =>
Dinero({ amount: Math.round((record.rate || 0) * 100) })
.multiply(record.flat_rate ? record.productivehrs || 0 : record.actualhrs || 0)
.toFormat("$0.00")
}
];
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
search.page = pagination.current;
if (sorter && sorter.column && sorter.column.sortObject) {
search.searchObj = JSON.stringify(sorter.column.sortObject(sorter.order));
} else {
delete search.searchObj;
search.sortcolumn = sorter.order ? sorter.columnKey : null;
search.sortorder = sorter.order;
}
search.sort = JSON.stringify({ [sorter.columnKey]: sorter.order });
history({ search: queryString.stringify(search) });
};
return (
<Card
title={t("timetickets.labels.timetickets")}
extra={
<Space wrap>
{extra}
<TtApproveButtonComponent
selectedTickets={selectedTickets}
disabled={selectedTickets.length === 0}
completedCallback={setSelectedTickets}
refetch={refetch}
/>
<Button onClick={() => refetch()} icon={<SyncOutlined />} />
</Space>
}
>
<ResponsiveTable
loading={loading}
columns={columns}
mobileColumnKeys={["ro_number", "date", "employeeid", "cost_center", "task_name"]}
rowKey="id"
scroll={{
x: true
}}
pagination={{ placement: "top", pageSize: 25, current: parseInt(page || 1), total: total }}
dataSource={tt_approval_queue}
onChange={handleTableChange}
rowSelection={{
onSelectAll: (selected, selectedRows) => setSelectedTickets(selectedRows.map((i) => i.id)),
onSelect: (record, selected, selectedRows) => {
setSelectedTickets(selectedRows.map((i) => i.id));
},
selectedRowKeys: selectedTickets,
type: "checkbox"
}}
/>
</Card>
);
}