Files
bodyshop/client/src/components/time-ticket-list/time-ticket-list.component.jsx

339 lines
10 KiB
JavaScript

import { EditFilled } from "@ant-design/icons";
import { Button, Card, Space, Table } from "antd";
import Dinero from "dinero.js";
import moment from "moment";
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,
} from "../../redux/user/user.selectors";
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
import { onlyUnique } from "../../utils/arrayHelper";
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,
});
const mapDispatchToProps = (dispatch) => ({
setTimeTicketTaskContext: (context) =>
dispatch(setModalContext({ context: context, modal: "timeTicketTask" })),
});
export default connect(mapStateToProps, mapDispatchToProps)(TimeTicketList);
export function TimeTicketList({
bodyshop,
setTimeTicketTaskContext,
authLevel,
disabled,
loading,
timetickets,
refetch,
techConsole,
jobId,
extra,
}) {
const [state, setState] = useState({
sortedInfo: {},
filteredInfo: { text: "" },
});
const { t } = useTranslation();
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 = [
{
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),
},
{
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,
},
{
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.clockon || record.clockoff ? t(record.memo) : record.memo,
},
{
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>
{moment(record.clockoff)
.diff(moment(record.clockon), "hours", true)
.toFixed(2)}
</div>
);
else {
return null;
}
},
},
{
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={
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>
{
// <TimeTicketListTeamPay
// actions={{ refetch }}
// context={{ jobId: jobId }}
// />
}
<Button
onClick={() => {
setTimeTicketTaskContext();
setTimeTicketTaskContext({
actions: {},
context: { jobid: jobId },
});
}}
>
{t("timetickets.actions.claimtasks")}
</Button>
{jobId &&
(techConsole ? null : (
<TimeTicketEnterButton
actions={{ refetch }}
context={{ jobId: jobId }}
disabled={disabled}
>
{t("timetickets.actions.enter")}
</TimeTicketEnterButton>
))}
{extra}
</Space>
}
>
<Table
loading={loading}
columns={columns}
rowKey="id"
scroll={{
x: true,
}}
dataSource={timetickets}
onChange={handleTableChange}
summary={() => {
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>
);
}