@@ -1,92 +1,86 @@
|
||||
import { RadarChartOutlined } from "@ant-design/icons";
|
||||
import { Popover, Space } from "antd";
|
||||
import React, { useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
Legend,
|
||||
PolarAngleAxis,
|
||||
PolarGrid,
|
||||
PolarRadiusAxis,
|
||||
Radar,
|
||||
RadarChart,
|
||||
Tooltip,
|
||||
} from "recharts";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import {RadarChartOutlined} from "@ant-design/icons";
|
||||
import {Popover, Space} from "antd";
|
||||
import React, {useMemo} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {Legend, PolarAngleAxis, PolarGrid, PolarRadiusAxis, Radar, RadarChart, Tooltip,} from "recharts";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
|
||||
export function ScheduleCalendarHeaderGraph({ bodyshop, loadData }) {
|
||||
const { ssbuckets } = bodyshop;
|
||||
const { t } = useTranslation();
|
||||
const data = useMemo(() => {
|
||||
return (
|
||||
(loadData &&
|
||||
loadData.expectedLoad &&
|
||||
Object.keys(loadData.expectedLoad).map((key) => {
|
||||
const metadataBucket = ssbuckets.filter((b) => b.id === key)[0];
|
||||
export function ScheduleCalendarHeaderGraph({bodyshop, loadData}) {
|
||||
const {ssbuckets} = bodyshop;
|
||||
const {t} = useTranslation();
|
||||
const data = useMemo(() => {
|
||||
return (
|
||||
(loadData &&
|
||||
loadData.expectedLoad &&
|
||||
Object.keys(loadData.expectedLoad).map((key) => {
|
||||
const metadataBucket = ssbuckets.filter((b) => b.id === key)[0];
|
||||
|
||||
return {
|
||||
bucket: loadData.expectedLoad[key].label,
|
||||
current: loadData.expectedLoad[key].count,
|
||||
target: metadataBucket && metadataBucket.target,
|
||||
};
|
||||
})) ||
|
||||
[]
|
||||
return {
|
||||
bucket: loadData.expectedLoad[key].label,
|
||||
current: loadData.expectedLoad[key].count,
|
||||
target: metadataBucket && metadataBucket.target,
|
||||
};
|
||||
})) ||
|
||||
[]
|
||||
);
|
||||
}, [loadData, ssbuckets]);
|
||||
|
||||
const popContent = (
|
||||
<div>
|
||||
<Space>
|
||||
{t("appointments.labels.expectedprodhrs")}
|
||||
<strong>{loadData?.expectedHours?.toFixed(1)}</strong>
|
||||
{t("appointments.labels.expectedjobs")}
|
||||
<strong>{loadData?.expectedJobCount}</strong>
|
||||
</Space>
|
||||
<RadarChart
|
||||
// cx={300}
|
||||
// cy={250}
|
||||
// outerRadius={150}
|
||||
width={800}
|
||||
height={600}
|
||||
data={data}
|
||||
>
|
||||
<PolarGrid/>
|
||||
<PolarAngleAxis dataKey="bucket"/>
|
||||
<PolarRadiusAxis angle={90}/>
|
||||
<Radar
|
||||
name="Ideal Load"
|
||||
dataKey="target"
|
||||
stroke="darkgreen"
|
||||
fill="white"
|
||||
fillOpacity={0}
|
||||
/>
|
||||
<Radar
|
||||
name="EOD Load"
|
||||
dataKey="current"
|
||||
stroke="dodgerblue"
|
||||
fill="dodgerblue"
|
||||
fillOpacity={0.6}
|
||||
/>
|
||||
<Tooltip/>
|
||||
<Legend/>
|
||||
</RadarChart>
|
||||
</div>
|
||||
);
|
||||
}, [loadData, ssbuckets]);
|
||||
|
||||
const popContent = (
|
||||
<div>
|
||||
<Space>
|
||||
{t("appointments.labels.expectedprodhrs")}
|
||||
<strong>{loadData?.expectedHours?.toFixed(1)}</strong>
|
||||
{t("appointments.labels.expectedjobs")}
|
||||
<strong>{loadData?.expectedJobCount}</strong>
|
||||
</Space>
|
||||
<RadarChart
|
||||
// cx={300}
|
||||
// cy={250}
|
||||
// outerRadius={150}
|
||||
width={800}
|
||||
height={600}
|
||||
data={data}
|
||||
>
|
||||
<PolarGrid />
|
||||
<PolarAngleAxis dataKey="bucket" />
|
||||
<PolarRadiusAxis angle={90} />
|
||||
<Radar
|
||||
name="Ideal Load"
|
||||
dataKey="target"
|
||||
stroke="darkgreen"
|
||||
fill="white"
|
||||
fillOpacity={0}
|
||||
/>
|
||||
<Radar
|
||||
name="EOD Load"
|
||||
dataKey="current"
|
||||
stroke="dodgerblue"
|
||||
fill="dodgerblue"
|
||||
fillOpacity={0.6}
|
||||
/>
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
</RadarChart>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover trigger="hover" placement="bottom" content={popContent}>
|
||||
<RadarChartOutlined />
|
||||
</Popover>
|
||||
);
|
||||
return (
|
||||
<Popover trigger="hover" placement="bottom" content={popContent}>
|
||||
<RadarChartOutlined/>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ScheduleCalendarHeaderGraph);
|
||||
|
||||
@@ -1,222 +1,221 @@
|
||||
import Icon from "@ant-design/icons";
|
||||
import { Popover } from "antd";
|
||||
import {Popover} from "antd";
|
||||
import _ from "lodash";
|
||||
import dayjs from "../../utils/day";
|
||||
|
||||
import React, { 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 React, {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 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 ScheduleCalendarHeaderGraph from "./schedule-calendar-header-graph.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
load: selectScheduleLoad,
|
||||
calculating: selectScheduleLoadCalculating,
|
||||
bodyshop: selectBodyshop,
|
||||
load: selectScheduleLoad,
|
||||
calculating: selectScheduleLoadCalculating,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({});
|
||||
|
||||
export function ScheduleCalendarHeaderComponent({
|
||||
bodyshop,
|
||||
label,
|
||||
refetch,
|
||||
date,
|
||||
load,
|
||||
calculating,
|
||||
events,
|
||||
...otherProps
|
||||
}) {
|
||||
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]);
|
||||
bodyshop,
|
||||
label,
|
||||
refetch,
|
||||
date,
|
||||
load,
|
||||
calculating,
|
||||
events,
|
||||
...otherProps
|
||||
}) {
|
||||
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 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 {t} = useTranslation();
|
||||
const loadData = load[date.toISOString().substr(0, 10)];
|
||||
|
||||
const jobsOutPopup = () => (
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<table>
|
||||
<tbody>
|
||||
{loadData && loadData.allJobsOut ? (
|
||||
loadData.allJobsOut.map((j) => (
|
||||
<tr key={j.id}>
|
||||
<td>
|
||||
<Link to={`/manage/jobs/${j.id}`}>{j.ro_number}</Link>
|
||||
</td>
|
||||
<td>
|
||||
<OwnerNameDisplay ownerObject={j} />
|
||||
</td>
|
||||
<td>
|
||||
{`(${(
|
||||
j.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
j.larhrs.aggregate.sum.mod_lb_hrs
|
||||
).toFixed(1)} ${t("general.labels.hours")})`}
|
||||
</td>
|
||||
<td>
|
||||
<DateTimeFormatter>
|
||||
{j.scheduled_completion}
|
||||
</DateTimeFormatter>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td>{t("appointments.labels.nocompletingjobs")}</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
|
||||
const jobsInPopup = () => (
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<table>
|
||||
<tbody>
|
||||
{loadData && loadData.allJobsIn ? (
|
||||
loadData.allJobsIn.map((j) => (
|
||||
<tr key={j.id}>
|
||||
<td>
|
||||
<Link to={`/manage/jobs/${j.id}`}>{j.ro_number}</Link>
|
||||
{j.status}
|
||||
</td>
|
||||
<td>
|
||||
<OwnerNameDisplay ownerObject={j} />
|
||||
</td>
|
||||
<td>
|
||||
{`(${(
|
||||
j.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
j.larhrs.aggregate.sum.mod_lb_hrs
|
||||
).toFixed(1)} ${t("general.labels.hours")})`}
|
||||
</td>
|
||||
<td>
|
||||
<DateTimeFormatter>{j.scheduled_in}</DateTimeFormatter>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td>{t("appointments.labels.noarrivingjobs")}</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
|
||||
const LoadComponent = loadData ? (
|
||||
<div>
|
||||
<div className="imex-flex-row imex-flex-row__flex-space-around">
|
||||
<Popover
|
||||
placement={"bottom"}
|
||||
content={jobsInPopup}
|
||||
trigger="hover"
|
||||
title={t("appointments.labels.arrivingjobs")}
|
||||
>
|
||||
<Icon component={MdFileDownload} style={{ color: "green" }} />
|
||||
{(loadData.allHoursIn || 0) && loadData.allHoursIn.toFixed(2)}
|
||||
</Popover>
|
||||
<Popover
|
||||
placement={"bottom"}
|
||||
content={jobsOutPopup}
|
||||
trigger="hover"
|
||||
title={t("appointments.labels.completingjobs")}
|
||||
>
|
||||
<Icon component={MdFileUpload} style={{ color: "red" }} />
|
||||
{(loadData.allHoursOut || 0) && loadData.allHoursOut.toFixed(2)}
|
||||
</Popover>
|
||||
<ScheduleCalendarHeaderGraph loadData={loadData} />
|
||||
</div>
|
||||
<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}
|
||||
const jobsOutPopup = () => (
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<table>
|
||||
<tbody>
|
||||
{loadData && loadData.allJobsOut ? (
|
||||
loadData.allJobsOut.map((j) => (
|
||||
<tr key={j.id}>
|
||||
<td>
|
||||
<Link to={`/manage/jobs/${j.id}`}>{j.ro_number}</Link>
|
||||
</td>
|
||||
<td>
|
||||
<OwnerNameDisplay ownerObject={j}/>
|
||||
</td>
|
||||
<td>
|
||||
{`(${(
|
||||
j.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
j.larhrs.aggregate.sum.mod_lb_hrs
|
||||
).toFixed(1)} ${t("general.labels.hours")})`}
|
||||
</td>
|
||||
<td>
|
||||
<DateTimeFormatter>
|
||||
{j.scheduled_completion}
|
||||
</DateTimeFormatter>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td>{t("appointments.labels.nocompletingjobs")}</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</ScheduleBlockDay>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
|
||||
const jobsInPopup = () => (
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<table>
|
||||
<tbody>
|
||||
{loadData && loadData.allJobsIn ? (
|
||||
loadData.allJobsIn.map((j) => (
|
||||
<tr key={j.id}>
|
||||
<td>
|
||||
<Link to={`/manage/jobs/${j.id}`}>{j.ro_number}</Link>
|
||||
{j.status}
|
||||
</td>
|
||||
<td>
|
||||
<OwnerNameDisplay ownerObject={j}/>
|
||||
</td>
|
||||
<td>
|
||||
{`(${(
|
||||
j.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
j.larhrs.aggregate.sum.mod_lb_hrs
|
||||
).toFixed(1)} ${t("general.labels.hours")})`}
|
||||
</td>
|
||||
<td>
|
||||
<DateTimeFormatter>{j.scheduled_in}</DateTimeFormatter>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td>{t("appointments.labels.noarrivingjobs")}</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
|
||||
const LoadComponent = loadData ? (
|
||||
<div>
|
||||
<div className="imex-flex-row imex-flex-row__flex-space-around">
|
||||
<Popover
|
||||
placement={"bottom"}
|
||||
content={jobsInPopup}
|
||||
trigger="hover"
|
||||
title={t("appointments.labels.arrivingjobs")}
|
||||
>
|
||||
<Icon component={MdFileDownload} style={{color: "green"}}/>
|
||||
{(loadData.allHoursIn || 0) && loadData.allHoursIn.toFixed(2)}
|
||||
</Popover>
|
||||
<Popover
|
||||
placement={"bottom"}
|
||||
content={jobsOutPopup}
|
||||
trigger="hover"
|
||||
title={t("appointments.labels.completingjobs")}
|
||||
>
|
||||
<Icon component={MdFileUpload} style={{color: "red"}}/>
|
||||
{(loadData.allHoursOut || 0) && loadData.allHoursOut.toFixed(2)}
|
||||
</Popover>
|
||||
<ScheduleCalendarHeaderGraph loadData={loadData}/>
|
||||
</div>
|
||||
<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
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ScheduleCalendarHeaderComponent);
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
import dayjs from "../../utils/day";
|
||||
|
||||
export function getRange(dateParam, viewParam) {
|
||||
let start, end;
|
||||
let date = dateParam || new Date();
|
||||
let view = viewParam || "week";
|
||||
// if view is day: from dayjs(date).startOf('day') to dayjs(date).endOf('day');
|
||||
if (view === "day") {
|
||||
start = dayjs(date).startOf("day");
|
||||
end = dayjs(date).endOf("day");
|
||||
}
|
||||
// if view is week: from dayjs(date).startOf('isoWeek') to dayjs(date).endOf('isoWeek');
|
||||
else if (view === "week") {
|
||||
start = dayjs(date).startOf("week");
|
||||
end = dayjs(date).endOf("week");
|
||||
}
|
||||
//if view is month: from dayjs(date).startOf('month').subtract(7, 'day') to dayjs(date).endOf('month').add(7, 'day'); i do additional 7 days math because you can see adjacent weeks on month view (that is the way how i generate my recurrent events for the Big Calendar, but if you need only start-end of month - just remove that math);
|
||||
else if (view === "month") {
|
||||
start = dayjs(date).startOf("month").subtract(7, "day");
|
||||
end = dayjs(date).endOf("month").add(7, "day");
|
||||
}
|
||||
// if view is agenda: from dayjs(date).startOf('day') to dayjs(date).endOf('day').add(1, 'month');
|
||||
else if (view === "agenda") {
|
||||
start = dayjs(date).startOf("day");
|
||||
end = dayjs(date).endOf("day").add(1, "month");
|
||||
}
|
||||
let start, end;
|
||||
let date = dateParam || new Date();
|
||||
let view = viewParam || "week";
|
||||
// if view is day: from dayjs(date).startOf('day') to dayjs(date).endOf('day');
|
||||
if (view === "day") {
|
||||
start = dayjs(date).startOf("day");
|
||||
end = dayjs(date).endOf("day");
|
||||
}
|
||||
// if view is week: from dayjs(date).startOf('isoWeek') to dayjs(date).endOf('isoWeek');
|
||||
else if (view === "week") {
|
||||
start = dayjs(date).startOf("week");
|
||||
end = dayjs(date).endOf("week");
|
||||
}
|
||||
//if view is month: from dayjs(date).startOf('month').subtract(7, 'day') to dayjs(date).endOf('month').add(7, 'day'); i do additional 7 days math because you can see adjacent weeks on month view (that is the way how i generate my recurrent events for the Big Calendar, but if you need only start-end of month - just remove that math);
|
||||
else if (view === "month") {
|
||||
start = dayjs(date).startOf("month").subtract(7, "day");
|
||||
end = dayjs(date).endOf("month").add(7, "day");
|
||||
}
|
||||
// if view is agenda: from dayjs(date).startOf('day') to dayjs(date).endOf('day').add(1, 'month');
|
||||
else if (view === "agenda") {
|
||||
start = dayjs(date).startOf("day");
|
||||
end = dayjs(date).endOf("day").add(1, "month");
|
||||
}
|
||||
|
||||
return { start, end };
|
||||
return {start, end};
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
.rbc-time-header-cell-single-day {
|
||||
display: unset;
|
||||
}
|
||||
|
||||
.rbc-time-view .rbc-allday-cell {
|
||||
height: unset;
|
||||
}
|
||||
|
||||
.rbc-month-row{
|
||||
.rbc-month-row {
|
||||
overflow: unset !important;
|
||||
}
|
||||
|
||||
// .rbc-row-content {
|
||||
// display: none;
|
||||
// }
|
||||
@@ -31,6 +33,7 @@
|
||||
.rbc-event.rbc-selected {
|
||||
background-color: slategrey;
|
||||
}
|
||||
|
||||
.imex-calendar-load {
|
||||
max-width: 12rem;
|
||||
position: relative;
|
||||
|
||||
@@ -1,161 +1,161 @@
|
||||
import dayjs from "../../utils/day";
|
||||
import queryString from "query-string";
|
||||
import React from "react";
|
||||
import { Calendar, dayjsLocalizer } from "react-big-calendar";
|
||||
import { connect } from "react-redux";
|
||||
import { useNavigate, useLocation } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import {Calendar, dayjsLocalizer} from "react-big-calendar";
|
||||
import {connect} from "react-redux";
|
||||
import {Link, useLocation, useNavigate} from "react-router-dom";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
import Event from "../job-at-change/schedule-event.container";
|
||||
import HeaderComponent from "./schedule-calendar-header.component";
|
||||
import "./schedule-calendar.styles.scss";
|
||||
import JobDetailCards from "../job-detail-cards/job-detail-cards.component";
|
||||
import { selectProblemJobs } from "../../redux/application/application.selectors";
|
||||
import {selectProblemJobs} from "../../redux/application/application.selectors";
|
||||
import {Alert, Collapse, Space} from "antd";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import {Trans, useTranslation} from "react-i18next";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
problemJobs: selectProblemJobs,
|
||||
bodyshop: selectBodyshop,
|
||||
problemJobs: selectProblemJobs,
|
||||
});
|
||||
const localizer = dayjsLocalizer(dayjs);
|
||||
|
||||
export function ScheduleCalendarWrapperComponent({
|
||||
bodyshop,
|
||||
problemJobs,
|
||||
data,
|
||||
refetch,
|
||||
defaultView,
|
||||
setDateRangeCallback,
|
||||
date,
|
||||
...otherProps
|
||||
}) {
|
||||
const search = queryString.parse(useLocation().search);
|
||||
const history = useNavigate();
|
||||
const { t } = useTranslation();
|
||||
const handleEventPropStyles = (event, start, end, isSelected) => {
|
||||
return {
|
||||
...(event.color && !((search.view || defaultView) === "agenda")
|
||||
? {
|
||||
style: {
|
||||
backgroundColor:
|
||||
event.color && event.color.hex ? event.color.hex : event.color,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
className: `${event.arrived ? "imex-event-arrived" : ""} ${
|
||||
event.block ? "imex-event-block" : ""
|
||||
}`,
|
||||
bodyshop,
|
||||
problemJobs,
|
||||
data,
|
||||
refetch,
|
||||
defaultView,
|
||||
setDateRangeCallback,
|
||||
date,
|
||||
...otherProps
|
||||
}) {
|
||||
const search = queryString.parse(useLocation().search);
|
||||
const history = useNavigate();
|
||||
const {t} = useTranslation();
|
||||
const handleEventPropStyles = (event, start, end, isSelected) => {
|
||||
return {
|
||||
...(event.color && !((search.view || defaultView) === "agenda")
|
||||
? {
|
||||
style: {
|
||||
backgroundColor:
|
||||
event.color && event.color.hex ? event.color.hex : event.color,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
className: `${event.arrived ? "imex-event-arrived" : ""} ${
|
||||
event.block ? "imex-event-block" : ""
|
||||
}`,
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
const selectedDate = new Date(date || dayjs(search.date) || Date.now());
|
||||
const selectedDate = new Date(date || dayjs(search.date) || Date.now());
|
||||
|
||||
return (
|
||||
<>
|
||||
<JobDetailCards />
|
||||
{problemJobs && problemJobs.length > 2 ? (
|
||||
<Collapse style={{marginBottom: '5px'}}>
|
||||
<Collapse.Panel
|
||||
key="1"
|
||||
header={
|
||||
<span style={{ color: "tomato" }}>
|
||||
return (
|
||||
<>
|
||||
<JobDetailCards/>
|
||||
{problemJobs && problemJobs.length > 2 ? (
|
||||
<Collapse style={{marginBottom: '5px'}}>
|
||||
<Collapse.Panel
|
||||
key="1"
|
||||
header={
|
||||
<span style={{color: "tomato"}}>
|
||||
{t("appointments.labels.severalerrorsfound")}
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
{problemJobs.map((problem) => (
|
||||
<Alert
|
||||
key={problem.id}
|
||||
type="error"
|
||||
message={
|
||||
<Trans
|
||||
i18nKey="appointments.labels.dataconsistency"
|
||||
components={[
|
||||
<Link
|
||||
to={`/manage/jobs/${problem.id}`}
|
||||
target="_blank"
|
||||
/>,
|
||||
]}
|
||||
values={{
|
||||
ro_number: problem.ro_number,
|
||||
code: problem.code,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<Space direction="vertical" style={{width: '100%'}}>
|
||||
{problemJobs.map((problem) => (
|
||||
<Alert
|
||||
key={problem.id}
|
||||
type="error"
|
||||
message={
|
||||
<Trans
|
||||
i18nKey="appointments.labels.dataconsistency"
|
||||
components={[
|
||||
<Link
|
||||
to={`/manage/jobs/${problem.id}`}
|
||||
target="_blank"
|
||||
/>,
|
||||
]}
|
||||
values={{
|
||||
ro_number: problem.ro_number,
|
||||
code: problem.code,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</Space>
|
||||
|
||||
</Collapse.Panel>
|
||||
</Collapse>
|
||||
) : (
|
||||
<Space direction="vertical" style={{ width: '100%', marginBottom: '5px' }}>
|
||||
{
|
||||
problemJobs.map((problem) => (
|
||||
<Alert
|
||||
key={problem.id}
|
||||
type="error"
|
||||
message={
|
||||
<Trans
|
||||
i18nKey="appointments.labels.dataconsistency"
|
||||
components={[
|
||||
<Link to={`/manage/jobs/${problem.id}`} target="_blank"/>,
|
||||
]}
|
||||
values={{
|
||||
ro_number: problem.ro_number,
|
||||
code: problem.code,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</Space>
|
||||
)}
|
||||
</Collapse.Panel>
|
||||
</Collapse>
|
||||
) : (
|
||||
<Space direction="vertical" style={{width: '100%', marginBottom: '5px'}}>
|
||||
{
|
||||
problemJobs.map((problem) => (
|
||||
<Alert
|
||||
key={problem.id}
|
||||
type="error"
|
||||
message={
|
||||
<Trans
|
||||
i18nKey="appointments.labels.dataconsistency"
|
||||
components={[
|
||||
<Link to={`/manage/jobs/${problem.id}`} target="_blank"/>,
|
||||
]}
|
||||
values={{
|
||||
ro_number: problem.ro_number,
|
||||
code: problem.code,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</Space>
|
||||
)}
|
||||
|
||||
<Calendar
|
||||
events={data}
|
||||
defaultView={search.view || defaultView || "week"}
|
||||
date={selectedDate}
|
||||
onNavigate={(date, view, action) => {
|
||||
search.date = date.toISOString().substr(0, 10);
|
||||
history({ search: queryString.stringify(search) });
|
||||
}}
|
||||
onRangeChange={(start, end) => {
|
||||
if (setDateRangeCallback) setDateRangeCallback({ start, end });
|
||||
}}
|
||||
onView={(view) => {
|
||||
search.view = view;
|
||||
history({ search: queryString.stringify(search) });
|
||||
}}
|
||||
step={15}
|
||||
// timeslots={1}
|
||||
showMultiDayTimes
|
||||
localizer={localizer}
|
||||
min={
|
||||
bodyshop.schedule_start_time
|
||||
? new Date(bodyshop.schedule_start_time)
|
||||
: new Date("2020-01-01T06:00:00")
|
||||
}
|
||||
max={
|
||||
bodyshop.schedule_end_time
|
||||
? new Date(bodyshop.schedule_end_time)
|
||||
: new Date("2020-01-01T20:00:00")
|
||||
}
|
||||
eventPropGetter={handleEventPropStyles}
|
||||
components={{
|
||||
event: (e) =>
|
||||
Event({ bodyshop: bodyshop, event: e.event, refetch: refetch }),
|
||||
header: (p) => (
|
||||
<HeaderComponent {...p} events={data} refetch={refetch} />
|
||||
),
|
||||
}}
|
||||
{...otherProps}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
<Calendar
|
||||
events={data}
|
||||
defaultView={search.view || defaultView || "week"}
|
||||
date={selectedDate}
|
||||
onNavigate={(date, view, action) => {
|
||||
search.date = date.toISOString().substr(0, 10);
|
||||
history({search: queryString.stringify(search)});
|
||||
}}
|
||||
onRangeChange={(start, end) => {
|
||||
if (setDateRangeCallback) setDateRangeCallback({start, end});
|
||||
}}
|
||||
onView={(view) => {
|
||||
search.view = view;
|
||||
history({search: queryString.stringify(search)});
|
||||
}}
|
||||
step={15}
|
||||
// timeslots={1}
|
||||
showMultiDayTimes
|
||||
localizer={localizer}
|
||||
min={
|
||||
bodyshop.schedule_start_time
|
||||
? new Date(bodyshop.schedule_start_time)
|
||||
: new Date("2020-01-01T06:00:00")
|
||||
}
|
||||
max={
|
||||
bodyshop.schedule_end_time
|
||||
? new Date(bodyshop.schedule_end_time)
|
||||
: new Date("2020-01-01T20:00:00")
|
||||
}
|
||||
eventPropGetter={handleEventPropStyles}
|
||||
components={{
|
||||
event: (e) =>
|
||||
Event({bodyshop: bodyshop, event: e.event, refetch: refetch}),
|
||||
header: (p) => (
|
||||
<HeaderComponent {...p} events={data} refetch={refetch}/>
|
||||
),
|
||||
}}
|
||||
{...otherProps}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, null)(ScheduleCalendarWrapperComponent);
|
||||
|
||||
Reference in New Issue
Block a user