+
{label}
{InstanceRenderMgr({
imex: calculating ? : LoadComponent,
@@ -216,6 +184,6 @@ export function ScheduleCalendarHeaderComponent({
);
-}
+});
export default connect(mapStateToProps, mapDispatchToProps)(ScheduleCalendarHeaderComponent);
diff --git a/client/src/components/schedule-calendar-wrapper/schedule-calendar-util.js b/client/src/components/schedule-calendar-wrapper/schedule-calendar-util.js
index 554c837db..234b73495 100644
--- a/client/src/components/schedule-calendar-wrapper/schedule-calendar-util.js
+++ b/client/src/components/schedule-calendar-wrapper/schedule-calendar-util.js
@@ -1,29 +1,28 @@
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");
- }
+// Predefine range calculation functions for each view
+const viewRanges = {
+ day: (date) => ({
+ start: date.startOf("day"),
+ end: date.endOf("day")
+ }),
+ week: (date) => ({
+ start: date.startOf("week"),
+ end: date.endOf("week")
+ }),
+ month: (date) => ({
+ // Adjusting for adjacent weeks
+ start: date.startOf("month").subtract(7, "day"),
+ end: date.endOf("month").add(7, "day")
+ }),
+ agenda: (date) => ({
+ start: date.startOf("day"),
+ end: date.endOf("day").add(1, "month")
+ })
+};
- return { start, end };
+export function getRange(dateParam = new Date(), viewParam = "week") {
+ const date = dayjs(dateParam);
+ const view = viewRanges[viewParam] ? viewParam : "week";
+ return viewRanges[view](date);
}
diff --git a/client/src/components/schedule-calendar-wrapper/scheduler-calendar-wrapper.component.jsx b/client/src/components/schedule-calendar-wrapper/scheduler-calendar-wrapper.component.jsx
index fc79cd4a9..a261daf6a 100644
--- a/client/src/components/schedule-calendar-wrapper/scheduler-calendar-wrapper.component.jsx
+++ b/client/src/components/schedule-calendar-wrapper/scheduler-calendar-wrapper.component.jsx
@@ -1,6 +1,6 @@
import dayjs from "../../utils/day";
import queryString from "query-string";
-import React from "react";
+import React, { useCallback, useMemo } from "react";
import { Calendar, dayjsLocalizer } from "react-big-calendar";
import { connect } from "react-redux";
import { Link, useLocation, useNavigate } from "react-router-dom";
@@ -19,9 +19,10 @@ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
problemJobs: selectProblemJobs
});
+
const localizer = dayjsLocalizer(dayjs);
-export function ScheduleCalendarWrapperComponent({
+export const ScheduleCalendarWrapperComponent = React.memo(function ScheduleCalendarWrapperComponent({
bodyshop,
problemJobs,
data,
@@ -31,23 +32,71 @@ export function ScheduleCalendarWrapperComponent({
date,
...otherProps
}) {
- const search = queryString.parse(useLocation().search);
- const history = useNavigate();
+ const location = useLocation();
+ const search = useMemo(() => queryString.parse(location.search), [location.search]);
+ const navigate = 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 = useMemo(() => {
+ return new Date(date || dayjs(search.date).toDate() || Date.now());
+ }, [date, search.date]);
+
+ const minTime = useMemo(() => {
+ return bodyshop.schedule_start_time ? new Date(bodyshop.schedule_start_time) : new Date("2020-01-01T06:00:00");
+ }, [bodyshop.schedule_start_time]);
+
+ const maxTime = useMemo(() => {
+ return bodyshop.schedule_end_time ? new Date(bodyshop.schedule_end_time) : new Date("2020-01-01T20:00:00");
+ }, [bodyshop.schedule_end_time]);
+
+ const handleEventPropStyles = useCallback(
+ (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" : ""}`
+ };
+ },
+ [search.view, defaultView]
+ );
+
+ const eventComponent = useCallback(
+ (e) =>
,
+ [bodyshop, refetch]
+ );
+
+ const headerComponent = useCallback(
+ (p) =>
,
+ [data, refetch]
+ );
+
+ const onNavigate = useCallback(
+ (date, view, action) => {
+ const newSearch = { ...search, date: date.toISOString().substr(0, 10) };
+ navigate({ search: queryString.stringify(newSearch) });
+ },
+ [search, navigate]
+ );
+
+ const onView = useCallback(
+ (view) => {
+ const newSearch = { ...search, view };
+ navigate({ search: queryString.stringify(newSearch) });
+ },
+ [search, navigate]
+ );
+
+ const onRangeChange = useCallback(
+ (range) => {
+ if (setDateRangeCallback) setDateRangeCallback(range);
+ },
+ [setDateRangeCallback]
+ );
return (
<>
@@ -109,32 +158,23 @@ export function ScheduleCalendarWrapperComponent({
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) });
- }}
+ onNavigate={onNavigate}
+ onRangeChange={onRangeChange}
+ onView={onView}
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")}
+ min={minTime}
+ max={maxTime}
eventPropGetter={handleEventPropStyles}
components={{
- event: (e) => Event({ bodyshop: bodyshop, event: e.event, refetch: refetch }),
- header: (p) =>
+ event: eventComponent,
+ header: headerComponent
}}
{...otherProps}
/>
>
);
-}
+});
export default connect(mapStateToProps, null)(ScheduleCalendarWrapperComponent);
diff --git a/client/src/components/schedule-calendar/schedule-calendar.component.jsx b/client/src/components/schedule-calendar/schedule-calendar.component.jsx
index 476094dd8..7382dc08f 100644
--- a/client/src/components/schedule-calendar/schedule-calendar.component.jsx
+++ b/client/src/components/schedule-calendar/schedule-calendar.component.jsx
@@ -1,8 +1,8 @@
import { SyncOutlined } from "@ant-design/icons";
import { Button, Card, Checkbox, Col, Row, Select, Space } from "antd";
import { PageHeader } from "@ant-design/pro-layout";
-import { t } from "i18next";
-import React, { useMemo } from "react";
+import React, { useCallback, useMemo } from "react";
+import { useTranslation } from "react-i18next";
import useLocalStorage from "../../utils/useLocalStorage";
import ScheduleAtsSummary from "../schedule-ats-summary/schedule-ats-summary.component";
import ScheduleCalendarWrapperComponent from "../schedule-calendar-wrapper/scheduler-calendar-wrapper.component";
@@ -18,19 +18,17 @@ import _ from "lodash";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
-const mapDispatchToProps = (dispatch) => ({
- //setUserLanguage: language => dispatch(setUserLanguage(language))
-});
-export default connect(mapStateToProps, mapDispatchToProps)(ScheduleCalendarComponent);
-export function ScheduleCalendarComponent({ data, refetch, bodyshop }) {
+const ScheduleCalendarComponent = React.memo(function ScheduleCalendarComponent({ data, refetch, bodyshop }) {
+ const { t } = useTranslation();
+
const [filter, setFilter] = useLocalStorage("filter_events", {
intake: true,
manual: true,
employeevacation: true,
ins_co_nm: null
});
- const [estimatorsFilter, setEstimatiorsFilter] = useLocalStorage("estimators", []);
+ const [estimatorsFilter, setEstimatorsFilter] = useLocalStorage("estimators", []);
const estimators = useMemo(() => {
return _.uniq([
@@ -48,7 +46,7 @@ export function ScheduleCalendarComponent({ data, refetch, bodyshop }) {
d.__typename === "appointments"
? estimatorsFilter.length === 0
? true
- : !!estimatorsFilter.find((e) => e === `${d.job?.est_ct_fn || ""} ${d.job?.est_ct_ln || ""}`.trim())
+ : estimatorsFilter.includes(`${d.job?.est_ct_fn || ""} ${d.job?.est_ct_ln || ""}`.trim())
: true;
return (
@@ -62,6 +60,70 @@ export function ScheduleCalendarComponent({ data, refetch, bodyshop }) {
});
}, [data, filter, estimatorsFilter]);
+ const estimatorsOptions = useMemo(() => {
+ return estimators.map((e) => ({
+ label: e,
+ value: e
+ }));
+ }, [estimators]);
+
+ const insCoNmOptions = useMemo(() => {
+ return bodyshop.md_ins_cos.map((i) => ({
+ label: i.name,
+ value: i.name
+ }));
+ }, [bodyshop.md_ins_cos]);
+
+ const handleEstimatorsFilterChange = useCallback(
+ (e) => {
+ setEstimatorsFilter(e);
+ },
+ [setEstimatorsFilter]
+ );
+
+ const handleEstimatorsFilterClear = useCallback(() => {
+ setEstimatorsFilter([]);
+ }, [setEstimatorsFilter]);
+
+ const handleInsCoNmFilterChange = useCallback(
+ (e) => {
+ setFilter((prevFilter) => ({ ...prevFilter, ins_co_nm: e }));
+ },
+ [setFilter]
+ );
+
+ const handleInsCoNmFilterClear = useCallback(() => {
+ setFilter((prevFilter) => ({ ...prevFilter, ins_co_nm: [] }));
+ }, [setFilter]);
+
+ const handleIntakeFilterChange = useCallback(
+ (e) => {
+ const checked = e.target.checked;
+ setFilter((prevFilter) => ({ ...prevFilter, intake: checked }));
+ },
+ [setFilter]
+ );
+
+ const handleManualFilterChange = useCallback(
+ (e) => {
+ const checked = e.target.checked;
+ setFilter((prevFilter) => ({ ...prevFilter, manual: checked }));
+ },
+ [setFilter]
+ );
+
+ const handleEmployeeVacationFilterChange = useCallback(
+ (e) => {
+ const checked = e.target.checked;
+ setFilter((prevFilter) => ({ ...prevFilter, employeevacation: checked }));
+ },
+ [setFilter]
+ );
+
+ const handleRefetch = useCallback(() => {
+ refetch();
+ }, [refetch]);
+
return (
@@ -76,65 +138,35 @@ export function ScheduleCalendarComponent({ data, refetch, bodyshop }) {
mode="multiple"
placeholder={t("schedule.labels.estimators")}
allowClear
- onClear={() => setEstimatiorsFilter([])}
- value={[...estimatorsFilter]}
- onChange={(e) => {
- setEstimatiorsFilter(e);
- }}
- options={estimators.map((e) => ({
- label: e,
- value: e
- }))}
+ onClear={handleEstimatorsFilterClear}
+ value={estimatorsFilter}
+ onChange={handleEstimatorsFilterChange}
+ options={estimatorsOptions}
/>
);
-}
+});
+
+export default connect(mapStateToProps)(ScheduleCalendarComponent);
diff --git a/client/src/components/schedule-calendar/schedule-calendar.container.jsx b/client/src/components/schedule-calendar/schedule-calendar.container.jsx
index 8bf50ca7f..b13a049c8 100644
--- a/client/src/components/schedule-calendar/schedule-calendar.container.jsx
+++ b/client/src/components/schedule-calendar/schedule-calendar.container.jsx
@@ -15,56 +15,65 @@ import dayjs from "../../utils/day";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
});
+
const mapDispatchToProps = (dispatch) => ({
calculateScheduleLoad: (endDate) => dispatch(calculateScheduleLoad(endDate))
});
-export function ScheduleCalendarContainer({ calculateScheduleLoad }) {
- const search = queryString.parse(useLocation().search);
+const ScheduleCalendarContainer = React.memo(function ScheduleCalendarContainer({ calculateScheduleLoad }) {
+ const location = useLocation();
+ const search = useMemo(() => queryString.parse(location.search), [location.search]);
const { date, view } = search;
const range = useMemo(() => getRange(date, view), [date, view]);
- const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_APPOINTMENTS, {
- variables: {
+ const queryVariables = useMemo(
+ () => ({
start: range.start.toDate(),
end: range.end.toDate(),
startd: range.start,
endd: range.end
- },
- skip: !!!range.start || !!!range.end,
+ }),
+ [range]
+ );
+
+ const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_APPOINTMENTS, {
+ variables: queryVariables,
+ skip: !range.start || !range.end,
fetchPolicy: "network-only",
nextFetchPolicy: "network-only"
});
useEffect(() => {
- if (data && range.end) calculateScheduleLoad(range.end);
- }, [data, range, calculateScheduleLoad]);
+ if (data && range.end) {
+ calculateScheduleLoad(range.end);
+ }
+ }, [data, range.end, calculateScheduleLoad]);
- if (loading) return
;
- if (error) return
;
- let normalizedData = [
- ...data.appointments.map((e) => {
- //Required because Hasura returns a string instead of a date object.
- return Object.assign({}, e, { start: new Date(e.start) }, { end: new Date(e.end) });
- }),
- ...data.employee_vacation.map((e) => {
- //Required because Hasura returns a string instead of a date object.
- return {
+ const normalizedData = useMemo(() => {
+ if (!data) return [];
+ return [
+ ...data.appointments.map((e) => ({
...e,
- title: `${
- (e.employee.first_name && e.employee.first_name.substr(0, 1)) || ""
- } ${e.employee.last_name || ""} OUT`,
+ start: new Date(e.start),
+ end: new Date(e.end)
+ })),
+ ...data.employee_vacation.map((e) => ({
+ ...e,
+ title: `${e.employee.first_name?.[0] || ""} ${e.employee.last_name || ""} OUT`,
color: "red",
start: dayjs(e.start).startOf("day").toDate(),
end: dayjs(e.end).startOf("day").toDate(),
allDay: true,
vacation: true
- };
- })
- ];
+ }))
+ ];
+ }, [data]);
- return
;
-}
+ if (loading) return
;
+ if (error) return
;
+
+ return
;
+});
export default connect(mapStateToProps, mapDispatchToProps)(ScheduleCalendarContainer);
diff --git a/client/src/components/schedule-day-view/schedule-day-view.component.jsx b/client/src/components/schedule-day-view/schedule-day-view.component.jsx
index 0200fe4c1..c0f086f2c 100644
--- a/client/src/components/schedule-day-view/schedule-day-view.component.jsx
+++ b/client/src/components/schedule-day-view/schedule-day-view.component.jsx
@@ -2,9 +2,14 @@ import React from "react";
import { useTranslation } from "react-i18next";
import ScheduleCalendarWrapperComponent from "../schedule-calendar-wrapper/scheduler-calendar-wrapper.component";
-export default function ScheduleDayViewComponent({ data, day }) {
+const ScheduleDayViewComponent = React.memo(function ScheduleDayViewComponent({ data, day }) {
const { t } = useTranslation();
- if (data)
- return
;
- else return
{t("appointments.labels.nodateselected")}
;
-}
+
+ if (data) {
+ return
;
+ } else {
+ return
{t("appointments.labels.nodateselected")}
;
+ }
+});
+
+export default ScheduleDayViewComponent;
diff --git a/client/src/components/schedule-day-view/schedule-day-view.container.jsx b/client/src/components/schedule-day-view/schedule-day-view.container.jsx
index 5b7d6d152..9d375a1ff 100644
--- a/client/src/components/schedule-day-view/schedule-day-view.container.jsx
+++ b/client/src/components/schedule-day-view/schedule-day-view.container.jsx
@@ -1,4 +1,4 @@
-import React from "react";
+import React, { useMemo } from "react";
import ScheduleDayViewComponent from "./schedule-day-view.component";
import { useQuery } from "@apollo/client";
import { QUERY_APPOINTMENT_BY_DATE } from "../../graphql/appointments.queries";
@@ -6,45 +6,59 @@ import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import dayjs from "../../utils/day";
import { useTranslation } from "react-i18next";
-export default function ScheduleDayViewContainer({ day }) {
+const ScheduleDayViewContainer = React.memo(function ScheduleDayViewContainer({ day }) {
+ const { t } = useTranslation();
+
+ // Memoize dayjs computations
+ const dayjsDay = useMemo(() => dayjs(day), [day]);
+
+ // Memoize query variables
+ const queryVariables = useMemo(
+ () => ({
+ start: dayjsDay.startOf("day").toISOString(),
+ end: dayjsDay.endOf("day").toISOString(),
+ startd: dayjsDay.startOf("day").format("YYYY-MM-DD"),
+ endd: dayjsDay.add(1, "day").format("YYYY-MM-DD")
+ }),
+ [dayjsDay]
+ );
+
+ // Use the useQuery hook
const { loading, error, data } = useQuery(QUERY_APPOINTMENT_BY_DATE, {
- variables: {
- start: dayjs(day).startOf("day"),
- end: dayjs(day).endOf("day"),
- startd: dayjs(day).startOf("day").format("YYYY-MM-DD"),
- endd: dayjs(day).add(1, "day").format("YYYY-MM-DD")
- },
- skip: !dayjs(day).isValid(),
+ variables: queryVariables,
+ skip: !dayjsDay.isValid(),
fetchPolicy: "network-only",
nextFetchPolicy: "network-only"
});
- const { t } = useTranslation();
+
+ // Memoize normalizedData
+ const normalizedData = useMemo(() => {
+ if (!data) return [];
+
+ const appointments = data.appointments.map((e) => ({
+ ...e,
+ start: new Date(e.start),
+ end: new Date(e.end)
+ }));
+
+ const vacations = data.employee_vacation.map((e) => ({
+ ...e,
+ title: `${e.employee.first_name?.[0] || ""} ${e.employee.last_name || ""} OUT`,
+ color: "red",
+ start: dayjs(e.start).startOf("day").toDate(),
+ end: dayjs(e.end).startOf("day").toDate(),
+ vacation: true
+ }));
+
+ return [...appointments, ...vacations];
+ }, [data]);
+
+ // Handle conditional rendering
if (!day) return
{t("appointments.labels.nodateselected")}
;
if (loading) return
;
if (error) return
{error.message}
;
- let normalizedData;
- if (data) {
- normalizedData = [
- ...data.appointments.map((e) => {
- //Required becuase Hasura returns a string instead of a date object.
- return Object.assign({}, e, { start: new Date(e.start) }, { end: new Date(e.end) });
- }),
- ...data.employee_vacation.map((e) => {
- //Required becuase Hasura returns a string instead of a date object.
- return {
- ...e,
- title: `${
- (e.employee.first_name && e.employee.first_name.substr(0, 1)) || ""
- } ${e.employee.last_name || ""} OUT`,
- color: "red",
- start: dayjs(e.start).startOf("day").toDate(),
- end: dayjs(e.end).startOf("day").toDate(),
- vacation: true
- };
- })
- ];
- }
+ return
;
+});
- return
;
-}
+export default ScheduleDayViewContainer;
diff --git a/client/src/components/schedule-existing-appointments-list/schedule-existing-appointments-list.component.jsx b/client/src/components/schedule-existing-appointments-list/schedule-existing-appointments-list.component.jsx
index 41d563c35..b8f7968ba 100644
--- a/client/src/components/schedule-existing-appointments-list/schedule-existing-appointments-list.component.jsx
+++ b/client/src/components/schedule-existing-appointments-list/schedule-existing-appointments-list.component.jsx
@@ -1,38 +1,43 @@
-import React from "react";
+import React, { useMemo } from "react";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import AlertComponent from "../alert/alert.component";
import { Timeline } from "antd";
import { useTranslation } from "react-i18next";
import { DateTimeFormatter } from "../../utils/DateFormatter";
-export default function ScheduleExistingAppointmentsList({ existingAppointments }) {
+const ScheduleExistingAppointmentsList = React.memo(function ScheduleExistingAppointmentsList({
+ existingAppointments
+}) {
const { t } = useTranslation();
- if (existingAppointments.loading) return
;
- if (existingAppointments.error) return
;
+ const { loading, error, data } = existingAppointments;
+
+ const items = useMemo(() => {
+ if (!data) return [];
+ return data.appointments.map((item) => ({
+ key: item.id,
+ color: item.canceled ? "red" : item.arrived ? "green" : "blue",
+ children: (
+ <>
+ {item.canceled
+ ? t("appointments.labels.cancelledappointment")
+ : item.arrived
+ ? t("appointments.labels.arrivedon")
+ : t("appointments.labels.scheduledfor")}
+
{item.start}
+ >
+ )
+ }));
+ }, [data, t]);
+
+ if (loading) return
;
+ if (error) return
;
return (
{t("appointments.labels.priorappointments")}
- ({
- key: item.id,
- color: item.canceled ? "red" : item.arrived ? "green" : "blue",
- children: (
- <>
- {item.canceled
- ? t("appointments.labels.cancelledappointment")
- : item.arrived
- ? t("appointments.labels.arrivedon")
- : t("appointments.labels.scheduledfor")}
- {item.start}
- >
- )
- }))
- : []
- }
- />
+
);
-}
+});
+
+export default ScheduleExistingAppointmentsList;
diff --git a/client/src/components/schedule-job-modal/schedule-job-modal.component.jsx b/client/src/components/schedule-job-modal/schedule-job-modal.component.jsx
index ea942e5d2..5efb41b42 100644
--- a/client/src/components/schedule-job-modal/schedule-job-modal.component.jsx
+++ b/client/src/components/schedule-job-modal/schedule-job-modal.component.jsx
@@ -1,7 +1,7 @@
import { Button, Col, Form, Input, Row, Select, Space, Switch, Typography } from "antd";
import axios from "axios";
import dayjs from "../../utils/day";
-import React, { useState } from "react";
+import React, { useCallback, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
@@ -19,12 +19,12 @@ import InstanceRenderManager from "../../utils/instanceRenderMgr";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
+
const mapDispatchToProps = (dispatch) => ({
- //setUserLanguage: language => dispatch(setUserLanguage(language))
calculateScheduleLoad: (endDate) => dispatch(calculateScheduleLoad(endDate))
});
-export function ScheduleJobModalComponent({
+const ScheduleJobModalComponent = React.memo(function ScheduleJobModalComponent({
bodyshop,
form,
existingAppointments,
@@ -36,7 +36,7 @@ export function ScheduleJobModalComponent({
const [loading, setLoading] = useState(false);
const [smartOptions, setSmartOptions] = useState([]);
- const handleSmartScheduling = async () => {
+ const handleSmartScheduling = useCallback(async () => {
setLoading(true);
try {
const response = await axios.post("/scheduling/job", {
@@ -48,21 +48,66 @@ export function ScheduleJobModalComponent({
} finally {
setLoading(false);
}
- };
+ }, [jobId]);
- const handleDateBlur = () => {
+ const handleDateBlur = useCallback(() => {
const values = form.getFieldsValue();
if (lbrHrsData) {
const totalHours =
- lbrHrsData.jobs_by_pk.labhrs.aggregate.sum.mod_lb_hrs + lbrHrsData.jobs_by_pk.larhrs.aggregate.sum.mod_lb_hrs;
+ (lbrHrsData.jobs_by_pk.labhrs.aggregate.sum.mod_lb_hrs || 0) +
+ (lbrHrsData.jobs_by_pk.larhrs.aggregate.sum.mod_lb_hrs || 0);
if (values.start && !values.scheduled_completion)
form.setFieldsValue({
scheduled_completion: dayjs(values.start).businessDaysAdd(totalHours / bodyshop.target_touchtime, "day")
});
}
- };
+ }, [form, lbrHrsData, bodyshop.target_touchtime]);
+
+ const colorOptions = useMemo(() => {
+ return (
+ bodyshop.appt_colors &&
+ bodyshop.appt_colors.map((color) => (
+
+ {color.label}
+
+ ))
+ );
+ }, [bodyshop.appt_colors]);
+
+ const altTransportOptions = useMemo(() => {
+ return (
+ bodyshop.appt_alt_transport &&
+ bodyshop.appt_alt_transport.map((alt) => (
+
+ {alt}
+
+ ))
+ );
+ }, [bodyshop.appt_alt_transport]);
+
+ const smartOptionsButtons = useMemo(() => {
+ return smartOptions.map((d, idx) => (
+
{
+ const ssDate = dayjs(d);
+ if (ssDate.isBefore(dayjs())) {
+ form.setFieldsValue({ start: dayjs() });
+ } else {
+ form.setFieldsValue({
+ start: dayjs(d).add(8, "hour")
+ });
+ }
+ handleDateBlur();
+ }}
+ >
+ {d}
+
+ ));
+ }, [smartOptions, form, handleDateBlur]);
return (
@@ -80,7 +125,6 @@ export function ScheduleJobModalComponent({
rules={[
{
required: true
- //message: t("general.validation.required"),
}
]}
>
@@ -92,7 +136,6 @@ export function ScheduleJobModalComponent({
rules={[
{
required: true
- //message: t("general.validation.required"),
}
]}
>
@@ -107,25 +150,7 @@ export function ScheduleJobModalComponent({
{t("appointments.actions.calculate")}
- {smartOptions.map((d, idx) => (
- {
- const ssDate = dayjs(d);
- if (ssDate.isBefore(dayjs())) {
- form.setFieldsValue({ start: dayjs() });
- } else {
- form.setFieldsValue({
- start: dayjs(d).add(8, "hour")
- });
- }
- handleDateBlur();
- }}
- >
- {d}
-
- ))}
+ {smartOptionsButtons}
>
),
@@ -144,20 +169,10 @@ export function ScheduleJobModalComponent({
-
+
-
+
@@ -183,6 +198,6 @@ export function ScheduleJobModalComponent({
);
-}
+});
export default connect(mapStateToProps, mapDispatchToProps)(ScheduleJobModalComponent);
diff --git a/client/src/components/schedule-job-modal/schedule-job-modal.container.jsx b/client/src/components/schedule-job-modal/schedule-job-modal.container.jsx
index eac29d274..c22d43f3f 100644
--- a/client/src/components/schedule-job-modal/schedule-job-modal.container.jsx
+++ b/client/src/components/schedule-job-modal/schedule-job-modal.container.jsx
@@ -1,7 +1,7 @@
import { useMutation, useQuery } from "@apollo/client";
import { Form, Modal, notification } from "antd";
import dayjs from "../../utils/day";
-import React, { useEffect, useState } from "react";
+import React, { useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
@@ -27,13 +27,21 @@ const mapStateToProps = createStructuredSelector({
scheduleModal: selectSchedule,
currentUser: selectCurrentUser
});
+
const mapDispatchToProps = (dispatch) => ({
toggleModalVisible: () => dispatch(toggleModalVisible("schedule")),
setEmailOptions: (e) => dispatch(setEmailOptions(e)),
- insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
+ insertAuditTrail: ({ jobid, operation, type }) =>
+ dispatch(
+ insertAuditTrail({
+ jobid,
+ operation,
+ type
+ })
+ )
});
-export function ScheduleJobModalContainer({
+const ScheduleJobModalContainer = React.memo(function ScheduleJobModalContainer({
scheduleModal,
bodyshop,
toggleModalVisible,
@@ -43,168 +51,186 @@ export function ScheduleJobModalContainer({
}) {
const { open, context, actions } = scheduleModal;
const { jobId, job, previousEvent } = context;
-
const { refetch } = actions;
const [form] = Form.useForm();
+ const { t } = useTranslation();
+ const [loading, setLoading] = useState(false);
const { data: lbrHrsData } = useQuery(QUERY_LBR_HRS_BY_PK, {
- variables: { id: job && job.id },
- skip: !job || !job.id,
+ variables: { id: job?.id },
+ skip: !job?.id,
fetchPolicy: "network-only",
nextFetchPolicy: "network-only"
});
- const [loading, setLoading] = useState(false);
const [cancelAppointment] = useMutation(CANCEL_APPOINTMENT_BY_ID);
const [insertAppointment] = useMutation(INSERT_APPOINTMENT);
const [updateJobStatus] = useMutation(UPDATE_JOBS);
- useEffect(() => {
- if (job) form.resetFields();
- }, [job, form]);
-
- const { t } = useTranslation();
-
const existingAppointments = useQuery(QUERY_APPOINTMENTS_BY_JOBID, {
variables: { jobid: jobId },
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
- skip: !open || !!!jobId
+ skip: !open || !jobId
});
useEffect(() => {
- if (
- existingAppointments.data &&
- existingAppointments.data.appointments.length > 0 &&
- !existingAppointments.data.appointments[0].canceled
- ) {
- form.setFieldsValue({
- color: existingAppointments.data.appointments[0].color,
+ if (job) form.resetFields();
+ }, [job, form]);
- note: existingAppointments.data.appointments[0].note
+ useEffect(() => {
+ const appointments = existingAppointments.data?.appointments;
+ if (appointments?.length && !appointments[0].canceled) {
+ form.setFieldsValue({
+ color: appointments[0].color,
+ note: appointments[0].note
});
}
}, [existingAppointments.data, form]);
- const handleFinish = async (values) => {
- logImEXEvent("schedule_new_appointment");
+ const handleFinish = useCallback(
+ async (values) => {
+ logImEXEvent("schedule_new_appointment");
+ setLoading(true);
- setLoading(true);
- if (!!previousEvent) {
- const cancelAppt = await cancelAppointment({
- variables: { appid: previousEvent }
- });
-
- if (!!cancelAppt.errors) {
- notification["error"]({
- message: t("appointments.errors.canceling", {
- message: JSON.stringify(cancelAppt.errors)
- })
+ if (previousEvent) {
+ const cancelAppt = await cancelAppointment({
+ variables: { appid: previousEvent }
});
- return;
- }
- notification["success"]({
- message: t("appointments.successes.canceled")
- });
- }
-
- if (existingAppointments.data.appointments.length > 0) {
- await Promise.all(
- existingAppointments.data.appointments.map((app) => {
- return cancelAppointment({
- variables: { appid: app.id }
+ if (cancelAppt.errors) {
+ notification.error({
+ message: t("appointments.errors.canceling", {
+ message: JSON.stringify(cancelAppt.errors)
+ })
});
- })
- );
- }
+ return;
+ }
- const appt = await insertAppointment({
- variables: {
- app: {
- jobid: jobId,
- bodyshopid: bodyshop.id,
- start: dayjs(values.start),
- end: dayjs(values.start).add(bodyshop.appt_length || 60, "minute"),
- color: values.color,
- note: values.note,
- created_by: currentUser.email
- },
- jobId: jobId,
- altTransport: values.alt_transport
+ notification.success({
+ message: t("appointments.successes.canceled")
+ });
}
- });
- if (!appt.errors) {
- insertAuditTrail({
- jobid: job.id,
- operation: AuditTrailMapping.appointmentinsert(DateTimeFormat(values.start)),
- type: "appointmentinsert"
- });
- }
+ const existingApps = existingAppointments.data?.appointments || [];
+ if (existingApps.length > 0) {
+ await Promise.all(
+ existingApps.map((app) =>
+ cancelAppointment({
+ variables: { appid: app.id }
+ })
+ )
+ );
+ }
- if (!!appt.errors) {
- notification["error"]({
- message: t("appointments.errors.saving", {
- message: JSON.stringify(appt.errors)
- })
- });
- return;
- }
- notification["success"]({
- message: t("appointments.successes.created")
- });
- if (jobId) {
- const jobUpdate = await updateJobStatus({
+ const appt = await insertAppointment({
variables: {
- jobIds: [jobId],
- fields: {
- status: bodyshop.md_ro_statuses.default_scheduled,
- date_scheduled: new Date(),
- scheduled_in: values.start,
- scheduled_completion: values.scheduled_completion,
- lost_sale_reason: null,
- date_lost_sale: null
- }
+ app: {
+ jobid: jobId,
+ bodyshopid: bodyshop.id,
+ start: dayjs(values.start),
+ end: dayjs(values.start).add(bodyshop.appt_length || 60, "minute"),
+ color: values.color,
+ note: values.note,
+ created_by: currentUser.email
+ },
+ jobId: jobId,
+ altTransport: values.alt_transport
}
});
- if (!!jobUpdate.errors) {
- notification["error"]({
+ if (!appt.errors) {
+ insertAuditTrail({
+ jobid: job.id,
+ operation: AuditTrailMapping.appointmentinsert(DateTimeFormat(values.start)),
+ type: "appointmentinsert"
+ });
+ } else {
+ notification.error({
message: t("appointments.errors.saving", {
- message: JSON.stringify(jobUpdate.errors)
+ message: JSON.stringify(appt.errors)
})
});
return;
}
- }
- setLoading(false);
- toggleModalVisible();
- if (values.notifyCustomer) {
- setEmailOptions({
- jobid: jobId,
- messageOptions: {
- to: [values.email],
- replyTo: bodyshop.email,
- subject: TemplateList("appointment").appointment_confirmation.subject
- },
- template: {
- name: TemplateList("appointment").appointment_confirmation.key,
- variables: {
- id: appt.data.insert_appointments.returning[0].id
- }
- }
+
+ notification.success({
+ message: t("appointments.successes.created")
});
- }
- if (refetch) refetch();
- };
+
+ if (jobId) {
+ const jobUpdate = await updateJobStatus({
+ variables: {
+ jobIds: [jobId],
+ fields: {
+ status: bodyshop.md_ro_statuses.default_scheduled,
+ date_scheduled: new Date(),
+ scheduled_in: values.start,
+ scheduled_completion: values.scheduled_completion,
+ lost_sale_reason: null,
+ date_lost_sale: null
+ }
+ }
+ });
+
+ if (jobUpdate.errors) {
+ notification.error({
+ message: t("appointments.errors.saving", {
+ message: JSON.stringify(jobUpdate.errors)
+ })
+ });
+ return;
+ }
+ }
+
+ if (values.notifyCustomer) {
+ setEmailOptions({
+ jobid: jobId,
+ messageOptions: {
+ to: [values.email],
+ replyTo: bodyshop.email,
+ subject: TemplateList("appointment").appointment_confirmation.subject
+ },
+ template: {
+ name: TemplateList("appointment").appointment_confirmation.key,
+ variables: {
+ id: appt.data.insert_appointments.returning[0].id
+ }
+ }
+ });
+ }
+
+ if (refetch) refetch();
+ toggleModalVisible();
+ setLoading(false);
+ },
+ [
+ t,
+ previousEvent,
+ cancelAppointment,
+ existingAppointments.data,
+ insertAppointment,
+ jobId,
+ bodyshop.id,
+ bodyshop.appt_length,
+ currentUser.email,
+ insertAuditTrail,
+ job,
+ updateJobStatus,
+ bodyshop.md_ro_statuses.default_scheduled,
+ setEmailOptions,
+ refetch,
+ toggleModalVisible,
+ bodyshop.email
+ ]
+ );
return (
toggleModalVisible()}
+ onCancel={toggleModalVisible}
onOk={() => form.submit()}
- width={"90%"}
+ width="90%"
maskClosable={false}
destroyOnClose
okButtonProps={{
@@ -217,10 +243,9 @@ export function ScheduleJobModalContainer({
layout="vertical"
onFinish={handleFinish}
initialValues={{
- notifyCustomer: !!(job && job.ownr_ea),
- email: (job && job.ownr_ea) || "",
+ notifyCustomer: !!job?.ownr_ea,
+ email: job?.ownr_ea || "",
start: null,
- // smartDates: [],
scheduled_completion: null,
color: context.color,
alt_transport: context.alt_transport,
@@ -236,6 +261,6 @@ export function ScheduleJobModalContainer({
);
-}
+});
export default connect(mapStateToProps, mapDispatchToProps)(ScheduleJobModalContainer);
diff --git a/client/src/components/schedule-manual-event/schedule-manual-event.component.jsx b/client/src/components/schedule-manual-event/schedule-manual-event.component.jsx
index e6bb2896d..b795015b8 100644
--- a/client/src/components/schedule-manual-event/schedule-manual-event.component.jsx
+++ b/client/src/components/schedule-manual-event/schedule-manual-event.component.jsx
@@ -1,7 +1,7 @@
import { useMutation } from "@apollo/client";
import { Button, Card, Form, Input, Popover, Select, Space } from "antd";
import dayjs from "../../utils/day";
-import React, { useEffect, useState } from "react";
+import React, { useCallback, useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
@@ -13,142 +13,143 @@ import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
-const mapDispatchToProps = (dispatch) => ({
- //setUserLanguage: language => dispatch(setUserLanguage(language))
-});
-export default connect(mapStateToProps, mapDispatchToProps)(ScheduleManualEvent);
-export function ScheduleManualEvent({ bodyshop, event }) {
+const ScheduleManualEvent = React.memo(function ScheduleManualEvent({ bodyshop, event }) {
const { t } = useTranslation();
- const [insertAppointment] = useMutation(INSERT_MANUAL_APPT);
- const [updateAppointment] = useMutation(UPDATE_APPOINTMENT);
+ const [insertAppointment] = useMutation(INSERT_MANUAL_APPT, {
+ refetchQueries: ["QUERY_ALL_ACTIVE_APPOINTMENTS"]
+ });
+ const [updateAppointment] = useMutation(UPDATE_APPOINTMENT, {
+ refetchQueries: ["QUERY_ALL_ACTIVE_APPOINTMENTS"]
+ });
const [loading, setLoading] = useState(false);
const [form] = Form.useForm();
const [visibility, setVisibility] = useState(false);
- // const [callQuery, { loading: entryLoading, data: entryData }] = useLazyQuery(
- // QUERY_SCOREBOARD_ENTRY
- // );
+
+ const handleFinish = useCallback(
+ async (values) => {
+ logImEXEvent("schedule_manual_event");
+ setLoading(true);
+ try {
+ if (event && event.id) {
+ await updateAppointment({
+ variables: { appid: event.id, app: values }
+ });
+ } else {
+ await insertAppointment({
+ variables: {
+ apt: {
+ ...values,
+ isintake: false,
+ bodyshopid: bodyshop.id
+ }
+ }
+ });
+ }
+ form.resetFields();
+ setVisibility(false);
+ } catch (error) {
+ console.error(error);
+ } finally {
+ setLoading(false);
+ }
+ },
+ [event, updateAppointment, insertAppointment, bodyshop.id, form]
+ );
+
+ const handleClick = useCallback(() => {
+ setVisibility(true);
+ }, []);
useEffect(() => {
if (visibility && event) {
form.setFieldsValue(event);
+ } else if (!visibility) {
+ form.resetFields();
}
}, [visibility, form, event]);
- const handleFinish = async (values) => {
- logImEXEvent("schedule_manual_event");
-
- setLoading(true);
- try {
- if (event && event.id) {
- updateAppointment({
- variables: { appid: event.id, app: values },
- refetchQueries: ["QUERY_ALL_ACTIVE_APPOINTMENTS"]
- });
- } else {
- insertAppointment({
- variables: {
- apt: { ...values, isintake: false, bodyshopid: bodyshop.id }
- },
- refetchQueries: ["QUERY_ALL_ACTIVE_APPOINTMENTS"]
- });
- }
- form.resetFields();
- setVisibility(false);
- } catch (error) {
- console.log(error);
- } finally {
- setLoading(false);
- }
- };
+ const colorOptions = useMemo(() => {
+ return bodyshop.appt_colors.map((col, idx) => (
+
+ {col.label}
+
+ ));
+ }, [bodyshop.appt_colors]);
const overlay = (
-
-
-
-
-
-
-
-
-
-
- ({
- async validator(rule, value) {
- if (value) {
- const { start } = form.getFieldsValue();
- if (dayjs(start).isAfter(dayjs(value))) {
- return Promise.reject(t("employees.labels.endmustbeafterstart"));
- } else {
- return Promise.resolve();
- }
+
+
+
+
+
+
+
+
+
+ ({
+ validator(rule, value) {
+ if (value) {
+ const start = form.getFieldValue("start");
+ if (dayjs(start).isAfter(dayjs(value))) {
+ return Promise.reject(t("employees.labels.endmustbeafterstart"));
} else {
return Promise.resolve();
}
+ } else {
+ return Promise.resolve();
}
- })
- ]}
- >
-
-
-
-
-
-
-
-
- {t("general.actions.save")}
-
- setVisibility(false)}>{t("general.actions.cancel")}
-
-
-
+ }
+ })
+ ]}
+ >
+
+
+
+
+
+
+
+ {t("general.actions.save")}
+
+ setVisibility(false)}>{t("general.actions.cancel")}
+
+
);
- const handleClick = (e) => {
- setVisibility(true);
- };
-
return (
-
+
{event ? t("appointments.actions.reschedule") : t("appointments.labels.manualevent")}
);
-}
+});
+
+export default connect(mapStateToProps)(ScheduleManualEvent);
diff --git a/client/src/components/schedule-production-list/schedule-production-list.component.jsx b/client/src/components/schedule-production-list/schedule-production-list.component.jsx
index b4e5dcf31..a91b6122f 100644
--- a/client/src/components/schedule-production-list/schedule-production-list.component.jsx
+++ b/client/src/components/schedule-production-list/schedule-production-list.component.jsx
@@ -1,6 +1,6 @@
import { DownOutlined } from "@ant-design/icons";
import { Button, Card, Popover } from "antd";
-import React from "react";
+import React, { useCallback } from "react";
import { useLazyQuery } from "@apollo/client";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
@@ -11,52 +11,52 @@ import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import "./schedule-production-list.styles.scss";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
-export default function ScheduleProductionList() {
+const ScheduleProductionList = React.memo(function ScheduleProductionList() {
const { t } = useTranslation();
const [callQuery, { loading, error, data }] = useLazyQuery(QUERY_JOBS_IN_PRODUCTION);
- const content = () => {
+ const content = useCallback(() => {
return (
e.stopPropagation()} className="jobs-in-production-table">
- {loading ?
: null}
- {error ?
: null}
- {data ? (
+ {loading &&
}
+ {error &&
}
+ {data && data.jobs && (
- {data && data.jobs
- ? data.jobs.map((j) => (
-
- |
- {j.ro_number}
- |
-
-
- |
- {`${j.v_model_yr || ""} ${j.v_make_desc || ""} ${j.v_model_desc || ""}`} |
- {`${j.labhrs.aggregate.sum.mod_lb_hrs || "0"} / ${
- j.larhrs.aggregate.sum.mod_lb_hrs || "0"
- }`} |
-
- {j.scheduled_completion}
- |
-
- ))
- : null}
+ {data.jobs.map((j) => (
+
+ |
+ {j.ro_number}
+ |
+
+
+ |
+ {`${j.v_model_yr || ""} ${j.v_make_desc || ""} ${j.v_model_desc || ""}`} |
+ {`${j.labhrs.aggregate.sum.mod_lb_hrs || "0"} / ${
+ j.larhrs.aggregate.sum.mod_lb_hrs || "0"
+ }`} |
+
+ {j.scheduled_completion}
+ |
+
+ ))}
- ) : null}
+ )}
);
- };
+ }, [loading, error, data]);
return (
- callQuery()}>
+
{t("appointments.labels.inproduction")}
);
-}
+});
+
+export default ScheduleProductionList;
diff --git a/client/src/components/schedule-verify-integrity/schedule-verify-integrity.component.jsx b/client/src/components/schedule-verify-integrity/schedule-verify-integrity.component.jsx
index 83693df19..a1fd85dab 100644
--- a/client/src/components/schedule-verify-integrity/schedule-verify-integrity.component.jsx
+++ b/client/src/components/schedule-verify-integrity/schedule-verify-integrity.component.jsx
@@ -1,7 +1,7 @@
import { useApolloClient } from "@apollo/client";
import { Button } from "antd";
import dayjs from "../../utils/day";
-import React, { useState } from "react";
+import React, { useCallback, useState } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { QUERY_SCHEDULE_LOAD_DATA } from "../../graphql/appointments.queries";
@@ -10,49 +10,46 @@ import { selectCurrentUser } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser
});
-const mapDispatchToProps = (dispatch) => ({
- //setUserLanguage: language => dispatch(setUserLanguage(language))
-});
-export default connect(mapStateToProps, mapDispatchToProps)(ScheduleVerifyIntegrity);
-export function ScheduleVerifyIntegrity({ currentUser }) {
+const ScheduleVerifyIntegrity = React.memo(function ScheduleVerifyIntegrity({ currentUser }) {
const [loading, setLoading] = useState(false);
-
const client = useApolloClient();
- const handleVerify = async () => {
+
+ const handleVerify = useCallback(async () => {
setLoading(true);
- const {
- data: { arrJobs, compJobs, prodJobs }
- } = await client.query({
- query: QUERY_SCHEDULE_LOAD_DATA,
- variables: { start: dayjs(), end: dayjs().add(180, "day") }
- });
+ try {
+ const {
+ data: { arrJobs, compJobs, prodJobs }
+ } = await client.query({
+ query: QUERY_SCHEDULE_LOAD_DATA,
+ variables: { start: dayjs(), end: dayjs().add(180, "day") }
+ });
- //check that the leaving jobs are either in the arriving list, or in production.
- const issues = [];
+ // Check that the completing jobs are either in production or arriving within the next 180 days.
+ const issues = compJobs.filter((j) => {
+ const inProdJobs = prodJobs.some((p) => p.id === j.id);
+ const inArrJobs = arrJobs.some((p) => p.id === j.id);
+ return !(inProdJobs || inArrJobs);
+ });
- compJobs.forEach((j) => {
- const inProdJobs = prodJobs.find((p) => p.id === j.id);
- const inArrJobs = arrJobs.find((p) => p.id === j.id);
+ console.log("The following completing jobs are not in production or arriving within the next 180 days:", issues);
+ } catch (error) {
+ console.error("Error verifying schedule integrity:", error);
+ } finally {
+ setLoading(false);
+ }
+ }, [client]);
- if (!(inProdJobs || inArrJobs)) {
- // NOT FOUND!
- issues.push(j);
- }
- });
- console.log(
- "The following completing jobs are not in production, or are arriving within the next 180 days. ",
- issues
- );
+ // TODO: A Global helper with developer emails
+ if (currentUser.email !== "patrick@imex.prod") {
+ return null;
+ }
- setLoading(false);
- };
+ return (
+
+ Developer Use Only - Verify Schedule Integrity
+
+ );
+});
- if (currentUser.email === "patrick@imex.prod")
- return (
-
- Developer Use Only - Verify Schedule Integrity
-
- );
- else return null;
-}
+export default connect(mapStateToProps)(ScheduleVerifyIntegrity);