import { Alert, Button, Card, Col, Row, Space, Table, Typography } from "antd"; import { SyncOutlined } from "@ant-design/icons"; import axios from "axios"; import _ from "lodash"; import React, { useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { selectTechnician } from "../../redux/tech/tech.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; import "./labor-allocations-table.styles.scss"; import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component"; import UpsellComponent, { upsellEnum } from "../upsell/upsell.component"; import LockWrapperComponent from "../lock-wrapper/lock-wrapper.component"; import { useNotification } from "../../contexts/Notifications/notificationContext.jsx"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, technician: selectTechnician }); export function PayrollLaborAllocationsTable({ jobId, joblines, timetickets, bodyshop, adjustments, technician, refetch, showWarning, warningCallback }) { const { t } = useTranslation(); const [totals, setTotals] = useState([]); const [state, setState] = useState({ sortedInfo: { columnKey: "cost_center", field: "cost_center", order: "ascend" }, filteredInfo: {} }); const notification = useNotification(); useEffect(() => { async function CalculateTotals() { const { data } = await axios.post("/payroll/calculatelabor", { jobid: jobId }); setTotals(data); } if (!!joblines && !!timetickets && !!bodyshop) { CalculateTotals(); } if (!jobId) setTotals([]); }, [joblines, timetickets, bodyshop, adjustments, jobId]); const convertedLines = useMemo(() => joblines && joblines.filter((j) => j.convertedtolbr), [joblines]); const columns = [ { title: t("timetickets.fields.employee"), dataIndex: "employeeid", key: "employeeid", render: (text, record) => { if (record.employeeid === undefined) { return {t("timetickets.labels.unassigned")}; } const emp = bodyshop.employees.find((e) => e.id === record.employeeid); return `${emp?.first_name} ${emp?.last_name}`; } }, { title: t("joblines.fields.mod_lbr_ty"), dataIndex: "mod_lbr_ty", key: "mod_lbr_ty", render: (text, record) => record.employeeid === undefined ? ( {t("timetickets.labels.unassigned")} ) : ( t(`joblines.fields.lbr_types.${record.mod_lbr_ty?.toUpperCase()}`) ) }, // { // title: t("timetickets.fields.rate"), // dataIndex: "rate", // key: "rate", // }, { title: t("jobs.labels.hrs_total"), dataIndex: "expectedHours", key: "expectedHours", sorter: (a, b) => a.expectedHours - b.expectedHours, sortOrder: state.sortedInfo.columnKey === "expectedHours" && state.sortedInfo.order, render: (text, record) => record.expectedHours.toFixed(5) }, { title: t("jobs.labels.hrs_claimed"), dataIndex: "claimedHours", key: "claimedHours", sorter: (a, b) => a.claimedHours - b.claimedHours, sortOrder: state.sortedInfo.columnKey === "claimedHours" && state.sortedInfo.order, render: (text, record) => record.claimedHours && record.claimedHours.toFixed(5) }, { title: t("jobs.labels.difference"), dataIndex: "difference", key: "difference", sorter: (a, b) => a.difference - b.difference, sortOrder: state.sortedInfo.columnKey === "difference" && state.sortedInfo.order, render: (text, record) => { const difference = _.round(record.expectedHours - record.claimedHours, 5); return ( = 0 ? "green" : "red" }} > {difference} ); } } ]; const convertedTableCols = [ { title: t("joblines.fields.line_desc"), dataIndex: "line_desc", key: "line_desc", ellipsis: true }, { title: t("joblines.fields.op_code_desc"), dataIndex: "op_code_desc", key: "op_code_desc", ellipsis: true, render: (text, record) => `${record.op_code_desc || ""}${record.alt_partm ? ` ${record.alt_partm}` : ""}` }, { title: t("joblines.fields.act_price"), dataIndex: "act_price", key: "act_price", ellipsis: true, render: (text, record) => ( <> {record.db_ref === "900510" || record.db_ref === "900511" ? record.prt_dsmk_m : record.act_price} {record.prt_dsmk_p && record.prt_dsmk_p !== 0 ? ( {`(${record.prt_dsmk_p}%)`} ) : ( <> )} ) }, { title: t("joblines.fields.part_qty"), dataIndex: "part_qty", key: "part_qty" }, { title: t("joblines.fields.mod_lbr_ty"), dataIndex: "conv_mod_lbr_ty", key: "conv_mod_lbr_ty", render: (text, record) => record.convertedtolbr_data && record.convertedtolbr_data.mod_lbr_ty }, { title: t("joblines.fields.mod_lb_hrs"), dataIndex: "conv_mod_lb_hrs", key: "conv_mod_lb_hrs", render: (text, record) => record.convertedtolbr_data && record.convertedtolbr_data.mod_lb_hrs && record.convertedtolbr_data.mod_lb_hrs.toFixed(5) } ]; const handleTableChange = (pagination, filters, sorter) => { setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); }; const summary = totals && totals.reduce( (acc, val) => { acc.hrs_total += val.expectedHours; acc.hrs_claimed += val.claimedHours; // acc.adjustments += val.adjustments; acc.difference += val.expectedHours - val.claimedHours; return acc; }, { hrs_total: 0, hrs_claimed: 0, adjustments: 0, difference: 0 } ); if (summary.difference !== 0 && typeof warningCallback === "function") { warningCallback({ key: "labor", warning: t("jobs.labels.outstandinghours") }); } const hasTimeTicketAccess = HasFeatureAccess({ bodyshop, featureName: "timetickets" }); return ( } > `${record.employeeid} ${record.mod_lbr_ty}`} pagination={false} onChange={handleTableChange} dataSource={hasTimeTicketAccess ? totals : []} scroll={{ x: true }} locale={{ ...(!hasTimeTicketAccess && { emptyText: ( ) }) }} summary={() => ( {t("general.labels.totals")} {summary.hrs_total.toFixed(5)} {summary.hrs_claimed.toFixed(5)} {summary.difference.toFixed(5)} )} /> {convertedLines && convertedLines.length > 0 && (
)} {showWarning && summary.difference !== 0 && ( )} ); } export default connect(mapStateToProps, null)(PayrollLaborAllocationsTable);