231 lines
8.5 KiB
JavaScript
231 lines
8.5 KiB
JavaScript
import Icon from "@ant-design/icons";
|
|
import { Card, Popover, Space } from "antd";
|
|
import { groupBy } from "lodash";
|
|
import dayjs from "../../utils/day";
|
|
|
|
import { useMemo } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { MdFileDownload, MdFileUpload } from "react-icons/md";
|
|
import { connect } from "react-redux";
|
|
import { Link } from "react-router-dom";
|
|
import { createStructuredSelector } from "reselect";
|
|
import { selectScheduleLoad, selectScheduleLoadCalculating } from "../../redux/application/application.selectors";
|
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
|
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
|
import { default as BlurWrapper, default as BlurWrapperComponent } from "../feature-wrapper/blur-wrapper.component";
|
|
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
|
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
|
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
|
import ScheduleBlockDay from "../schedule-block-day/schedule-block-day.component";
|
|
import UpsellComponent, { upsellEnum } from "../upsell/upsell.component";
|
|
import ScheduleCalendarHeaderGraph from "./schedule-calendar-header-graph.component";
|
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
|
|
|
const mapStateToProps = createStructuredSelector({
|
|
bodyshop: selectBodyshop,
|
|
load: selectScheduleLoad,
|
|
calculating: selectScheduleLoadCalculating
|
|
});
|
|
|
|
const mapDispatchToProps = () => ({});
|
|
|
|
export function ScheduleCalendarHeaderComponent({ bodyshop, label, refetch, date, load, calculating, events }) {
|
|
const ATSToday = useMemo(() => {
|
|
if (!events) return [];
|
|
return groupBy(
|
|
events.filter((e) => !e.vacation && e.isintake && dayjs(date).isSame(dayjs(e.start), "day")),
|
|
"job.alt_transport"
|
|
);
|
|
}, [events, date]);
|
|
|
|
const isDayBlocked = useMemo(() => {
|
|
if (!events) return [];
|
|
return events && events.filter((e) => dayjs(date).isSame(dayjs(e.start), "day") && e.block);
|
|
}, [events, date]);
|
|
|
|
const { t } = useTranslation();
|
|
const loadData = load[date.toISOString().substr(0, 10)];
|
|
const hasSmartSchedulingAccess = HasFeatureAccess({ featureName: "smartscheduling", bodyshop });
|
|
|
|
const jobsOutPopup = () => (
|
|
<div onClick={(e) => e.stopPropagation()}>
|
|
<table>
|
|
<tbody>
|
|
{loadData && loadData.allJobsOut ? (
|
|
loadData.allJobsOut.map((j) => (
|
|
<BlurWrapperComponent key={j.id} featureName="smartscheduling">
|
|
<tr>
|
|
<td style={{ padding: "2.5px" }}>
|
|
<Link to={`/manage/jobs/${j.id}`}>{j.ro_number}</Link> ({j.status})
|
|
</td>
|
|
<td style={{ padding: "2.5px" }}>
|
|
<OwnerNameDisplay ownerObject={j} />
|
|
</td>
|
|
<td style={{ padding: "2.5px" }}>
|
|
{`(${j.labhrs?.aggregate?.sum?.mod_lb_hrs?.toFixed(1) || 0}/${
|
|
j.larhrs?.aggregate?.sum?.mod_lb_hrs?.toFixed(1) || 0
|
|
}/${(j.labhrs.aggregate?.sum?.mod_lb_hrs + j.larhrs.aggregate?.sum?.mod_lb_hrs).toFixed(
|
|
1
|
|
)} ${t("general.labels.hours")})`}
|
|
</td>
|
|
<td style={{ padding: "2.5px" }}>
|
|
<DateTimeFormatter>{j.scheduled_completion}</DateTimeFormatter>
|
|
</td>
|
|
</tr>
|
|
</BlurWrapperComponent>
|
|
))
|
|
) : (
|
|
<tr>
|
|
<BlurWrapperComponent featureName="smartscheduling">
|
|
<td style={{ padding: "2.5px" }}>{t("appointments.labels.nocompletingjobs")}</td>
|
|
</BlurWrapperComponent>
|
|
</tr>
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
{!hasSmartSchedulingAccess && (
|
|
<Card style={{ maxWidth: "30rem" }}>
|
|
<UpsellComponent size="small" upsell={upsellEnum().smartscheduling.hrsdelta} />
|
|
</Card>
|
|
)}
|
|
</div>
|
|
);
|
|
|
|
const jobsInPopup = () => (
|
|
<div onClick={(e) => e.stopPropagation()}>
|
|
<table>
|
|
<tbody>
|
|
{loadData && loadData.allJobsIn ? (
|
|
loadData.allJobsIn.map((j) => (
|
|
<BlurWrapperComponent key={j.id} featureName="smartscheduling">
|
|
<tr>
|
|
<td style={{ padding: "2.5px" }}>
|
|
<Link to={`/manage/jobs/${j.id}`}>{j.ro_number}</Link>
|
|
</td>
|
|
<td style={{ padding: "2.5px" }}>
|
|
<OwnerNameDisplay ownerObject={j} />
|
|
</td>
|
|
<td style={{ padding: "2.5px" }}>
|
|
{`(${j.labhrs?.aggregate?.sum.mod_lb_hrs?.toFixed(1) || 0}/${
|
|
j.larhrs?.aggregate?.sum?.mod_lb_hrs?.toFixed(1) || 0
|
|
}/${(j.labhrs?.aggregate?.sum?.mod_lb_hrs + j.larhrs?.aggregate?.sum?.mod_lb_hrs).toFixed(
|
|
1
|
|
)} ${t("general.labels.hours")})`}
|
|
</td>
|
|
<td style={{ padding: "2.5px" }}>
|
|
<DateTimeFormatter>{j.scheduled_in}</DateTimeFormatter>
|
|
</td>
|
|
</tr>
|
|
</BlurWrapperComponent>
|
|
))
|
|
) : (
|
|
<tr>
|
|
<BlurWrapperComponent featureName="smartscheduling">
|
|
<td style={{ padding: "2.5px" }}>{t("appointments.labels.noarrivingjobs")}</td>
|
|
</BlurWrapperComponent>
|
|
</tr>
|
|
)}
|
|
{!hasSmartSchedulingAccess && (
|
|
<Card style={{ maxWidth: "30rem" }}>
|
|
<UpsellComponent size="small" upsell={upsellEnum().smartscheduling.hrsdelta} />
|
|
</Card>
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
);
|
|
|
|
const LoadComponent = loadData ? (
|
|
<div>
|
|
<Space align="center">
|
|
<Popover
|
|
placement={"bottom"}
|
|
content={jobsInPopup}
|
|
trigger="hover"
|
|
title={t("appointments.labels.arrivingjobs")}
|
|
onOpenChange={(open) => open && logImEXEvent("schedule_popover_arriving_jobs", {})}
|
|
>
|
|
<Space size="small">
|
|
<Icon component={MdFileDownload} style={{ color: "green" }} />
|
|
<BlurWrapper featureName="smartscheduling">
|
|
<span>
|
|
{`${(loadData.allHoursInBody || 0) && loadData.allHoursInBody.toFixed(1)}/${
|
|
(loadData.allHoursInRefinish || 0) && loadData.allHoursInRefinish.toFixed(1)
|
|
}/${(loadData.allHoursIn || 0) && loadData.allHoursIn.toFixed(1)}`}
|
|
</span>
|
|
</BlurWrapper>
|
|
</Space>
|
|
</Popover>
|
|
<Popover
|
|
placement={"bottom"}
|
|
content={jobsOutPopup}
|
|
trigger="hover"
|
|
title={t("appointments.labels.completingjobs")}
|
|
onOpenChange={(open) => open && logImEXEvent("schedule_popover_departing_jobs", {})}
|
|
>
|
|
<Space size="small">
|
|
<Icon component={MdFileUpload} style={{ color: "red" }} />
|
|
<BlurWrapper featureName="smartscheduling">
|
|
<span>{(loadData.allHoursOut || 0) && loadData.allHoursOut.toFixed(1)}</span>
|
|
</BlurWrapper>
|
|
</Space>
|
|
</Popover>
|
|
<ScheduleCalendarHeaderGraph loadData={loadData} />
|
|
</Space>
|
|
|
|
<div>
|
|
<ul style={{ listStyleType: "none", columns: "2 auto", padding: 0 }}>
|
|
{Object.keys(ATSToday).map((key, idx) => (
|
|
<li key={idx}>{`${key === "null" || key === "undefined" ? "N/A" : key}: ${ATSToday[key].length}`}</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
) : null;
|
|
|
|
const isShopOpen = (date) => {
|
|
let day;
|
|
switch (dayjs(date).day()) {
|
|
case 0:
|
|
day = "sunday";
|
|
break;
|
|
case 1:
|
|
day = "monday";
|
|
break;
|
|
case 2:
|
|
day = "tuesday";
|
|
break;
|
|
case 3:
|
|
day = "wednesday";
|
|
break;
|
|
case 4:
|
|
day = "thursday";
|
|
break;
|
|
case 5:
|
|
day = "friday";
|
|
break;
|
|
case 6:
|
|
day = "saturday";
|
|
break;
|
|
default:
|
|
day = "sunday";
|
|
break;
|
|
}
|
|
|
|
return bodyshop.workingdays[day];
|
|
};
|
|
|
|
return (
|
|
<div className="imex-calendar-load">
|
|
<ScheduleBlockDay alreadyBlocked={isDayBlocked.length > 0} date={date} refetch={refetch}>
|
|
<div style={{ color: isShopOpen(date) ? "" : "tomato" }}>
|
|
{label}
|
|
{calculating ? <LoadingSkeleton /> : LoadComponent}
|
|
</div>
|
|
</ScheduleBlockDay>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default connect(mapStateToProps, mapDispatchToProps)(ScheduleCalendarHeaderComponent);
|