Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
Dave Richer
2023-12-14 13:20:43 -05:00
parent a67fb3576c
commit 83e4fb3dc4
19 changed files with 623 additions and 594 deletions

View File

@@ -1,26 +1,16 @@
import Icon, { UploadOutlined } from "@ant-design/icons";
import { useApolloClient } from "@apollo/client";
import { useTreatments } from "@splitsoftware/splitio-react";
import {
Alert,
Divider,
Form,
Input,
Select,
Space,
Statistic,
Switch,
Upload,
} from "antd";
import Icon, {UploadOutlined} from "@ant-design/icons";
import {useApolloClient} from "@apollo/client";
import {useTreatments} from "@splitsoftware/splitio-react";
import {Alert, Divider, Form, Input, Select, Space, Statistic, Switch, Upload,} from "antd";
import dayjs from "../../utils/day";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { MdOpenInNew } from "react-icons/md";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { CHECK_BILL_INVOICE_NUMBER } from "../../graphql/bills.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import React, {useEffect, useState} from "react";
import {useTranslation} from "react-i18next";
import {MdOpenInNew} from "react-icons/md";
import {connect} from "react-redux";
import {Link} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import {CHECK_BILL_INVOICE_NUMBER} from "../../graphql/bills.queries";
import {selectBodyshop} from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component";
import BillFormLinesExtended from "../bill-form-lines-extended/bill-form-lines-extended.component";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
@@ -30,7 +20,7 @@ import JobSearchSelect from "../job-search-select/job-search-select.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
import BillFormLines from "./bill-form.lines.component";
import { CalculateBillTotal } from "./bill-form.totals.utility";
import {CalculateBillTotal} from "./bill-form.totals.utility";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -51,16 +41,16 @@ export function BillFormComponent({
loadOutstandingReturns,
loadInventory,
preferredMake,
}) {
const { t } = useTranslation();
}) {
const {t} = useTranslation();
const client = useApolloClient();
const [discount, setDiscount] = useState(0);
const { Extended_Bill_Posting } = useTreatments(
const {Extended_Bill_Posting} = useTreatments(
["Extended_Bill_Posting"],
{},
bodyshop.imexshopid
);
const { ClosingPeriod } = useTreatments(
const {ClosingPeriod} = useTreatments(
["ClosingPeriod"],
{},
bodyshop.imexshopid
@@ -95,7 +85,7 @@ export function BillFormComponent({
}
const jobId = form.getFieldValue("jobid");
if (jobId) {
loadLines({ variables: { id: jobId } });
loadLines({variables: {id: jobId}});
if (form.getFieldValue("is_credit_memo") && vendorId && !billEdit) {
loadOutstandingReturns({
variables: {
@@ -122,13 +112,13 @@ export function BillFormComponent({
return (
<div>
<FormFieldsChanged form={form} />
<FormFieldsChanged form={form}/>
<Form.Item
style={{ display: "none" }}
style={{display: "none"}}
name="isinhouse"
valuePropName="checked"
>
<Switch />
<Switch/>
</Form.Item>
<LayoutFormRow grow>
<Form.Item
@@ -147,7 +137,7 @@ export function BillFormComponent({
notExported={false}
onBlur={() => {
if (form.getFieldValue("jobid") !== null) {
loadLines({ variables: { id: form.getFieldValue("jobid") } });
loadLines({variables: {id: form.getFieldValue("jobid")}});
if (form.getFieldValue("vendorid") !== null) {
loadOutstandingReturns({
variables: {
@@ -169,7 +159,7 @@ export function BillFormComponent({
required: true,
//message: t("general.validation.required"),
},
({ getFieldValue }) => ({
({getFieldValue}) => ({
validator(rule, value) {
if (
value &&
@@ -208,7 +198,7 @@ export function BillFormComponent({
>
<Space>
{iou.ro_number}
<Icon component={MdOpenInNew} />
<Icon component={MdOpenInNew}/>
</Space>
</Link>
</Space>
@@ -226,7 +216,7 @@ export function BillFormComponent({
required: true,
//message: t("general.validation.required"),
},
({ getFieldValue }) => ({
({getFieldValue}) => ({
async validator(rule, value) {
const vendorid = getFieldValue("vendorid");
if (vendorid && value) {
@@ -257,7 +247,7 @@ export function BillFormComponent({
}),
]}
>
<Input disabled={disabled || disableInvNumber} />
<Input disabled={disabled || disableInvNumber}/>
</Form.Item>
<Form.Item
label={t("bills.fields.date")}
@@ -267,7 +257,7 @@ export function BillFormComponent({
required: true,
//message: t("general.validation.required"),
},
({ getFieldValue }) => ({
({getFieldValue}) => ({
validator(rule, value) {
if (
ClosingPeriod.treatment === "on" &&
@@ -300,14 +290,14 @@ export function BillFormComponent({
}),
]}
>
<FormDatePicker disabled={disabled} />
<FormDatePicker disabled={disabled}/>
</Form.Item>
<Form.Item
label={t("bills.fields.is_credit_memo")}
name="is_credit_memo"
valuePropName="checked"
rules={[
({ getFieldValue }) => ({
({getFieldValue}) => ({
validator(rule, value) {
if (
value === true &&
@@ -339,7 +329,7 @@ export function BillFormComponent({
}),
]}
>
<Switch />
<Switch/>
</Form.Item>
<Form.Item
label={t("bills.fields.total")}
@@ -351,11 +341,11 @@ export function BillFormComponent({
},
]}
>
<CurrencyInput min={0} disabled={disabled} />
<CurrencyInput min={0} disabled={disabled}/>
</Form.Item>
{!billEdit && (
<Form.Item label={t("bills.fields.allpartslocation")} name="location">
<Select style={{ width: "10rem" }} disabled={disabled} allowClear>
<Select style={{width: "10rem"}} disabled={disabled} allowClear>
{bodyshop.md_parts_locations.map((loc, idx) => (
<Select.Option key={idx} value={loc}>
{loc}
@@ -371,21 +361,21 @@ export function BillFormComponent({
label={t("bills.fields.federal_tax_rate")}
name="federal_tax_rate"
>
<CurrencyInput min={0} disabled={disabled} />
<CurrencyInput min={0} disabled={disabled}/>
</Form.Item>
<Form.Item
span={3}
label={t("bills.fields.state_tax_rate")}
name="state_tax_rate"
>
<CurrencyInput min={0} disabled={disabled} />
<CurrencyInput min={0} disabled={disabled}/>
</Form.Item>
<Form.Item
span={3}
label={t("bills.fields.local_tax_rate")}
name="local_tax_rate"
>
<CurrencyInput min={0} />
<CurrencyInput min={0}/>
</Form.Item>
<Form.Item shouldUpdate span={15}>
{() => {
@@ -485,7 +475,7 @@ export function BillFormComponent({
<Form.Item
name="upload"
label="Upload"
style={{ display: billEdit ? "none" : null }}
style={{display: billEdit ? "none" : null}}
valuePropName="fileList"
getValueFromEvent={(e) => {
if (Array.isArray(e)) {
@@ -502,7 +492,7 @@ export function BillFormComponent({
>
<>
<p className="ant-upload-drag-icon">
<UploadOutlined />
<UploadOutlined/>
</p>
<p className="ant-upload-text">
Click or drag files to this area to upload.

View File

@@ -14,10 +14,10 @@ export function getRange(dateParam, viewParam) {
start = dayjs(date).startOf("week");
end = dayjs(date).endOf("week");
}
//if view is month: from dayjs(date).startOf('month').subtract(7, 'days') to dayjs(date).endOf('month').add(7, 'days'); 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);
//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, "days");
end = dayjs(date).endOf("month").add(7, "days");
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") {

View File

@@ -201,7 +201,7 @@ export function ScheduleJobModalComponent({
{() => {
const values = form.getFieldsValue();
if (values.start) {
calculateScheduleLoad(dayjs(values.start).add(3, "days"));
calculateScheduleLoad(dayjs(values.start).add(3, "day"));
}
return (
<div className="schedule-job-modal">

View File

@@ -27,7 +27,7 @@ export function ScheduleVerifyIntegrity({ currentUser }) {
data: { arrJobs, compJobs, prodJobs },
} = await client.query({
query: QUERY_SCHEDULE_LOAD_DATA,
variables: { start: dayjs(), end: dayjs().add(180, "days") },
variables: { start: dayjs(), end: dayjs().add(180, "day") },
});
//check that the leaving jobs are either in the arriving list, or in production.

View File

@@ -17,7 +17,7 @@ const mapDispatchToProps = (dispatch) => ({
const rowGutter = [16, 16];
const statSpans = { xs: 24, sm: 3 };
export function ScoreboardTimeticketsTargetsTable({ bodyshop }) {
export function ScoreboardTimeTicketsTargetsTable({ bodyshop }) {
const { t } = useTranslation();
return (
@@ -282,4 +282,4 @@ export function ScoreboardTimeticketsTargetsTable({ bodyshop }) {
export default connect(
mapStateToProps,
mapDispatchToProps
)(ScoreboardTimeticketsTargetsTable);
)(ScoreboardTimeTicketsTargetsTable);

View File

@@ -17,7 +17,7 @@ export default function ScoreboardTimeTickets() {
const { start, end } = searchParams;
const startDate = start
? dayjs(start)
: dayjs().startOf("week").subtract(7, "days");
: dayjs().startOf("week").subtract(7, "day");
const endDate = end ? dayjs(end) : dayjs().endOf("week");
const fixedPeriods = useMemo(() => {

View File

@@ -16,7 +16,9 @@ const mapStateToProps = createStructuredSelector({
export function TechClockInComponent({ form, bodyshop, technician }) {
const { t } = useTranslation();
const emps = bodyshop.employees.filter((e) => e.id === technician.id)[0];
return (
<div>
<LayoutFormRow grow noDivider>

View File

@@ -46,6 +46,16 @@ export function TechClockInContainer({
(e) => e.id === (technician && technician.id)
)[0];
const TechForm = () => {
if (technician) {
return <Form form={form} layout="vertical" onFinish={handleFinish} >
<TechClockInComponent form={form} />
</Form>
} else {
return <div />
}
}
const handleFinish = async (values) => {
setLoading(true);
const theTime = (await axios.post("/utils/time")).data;
@@ -146,9 +156,7 @@ export function TechClockInContainer({
</Space>
}
>
<Form form={form} layout="vertical" onFinish={handleFinish}>
<TechClockInComponent form={form} />
</Form>
<TechForm />
</Card>
);
}

View File

@@ -1,18 +1,18 @@
import { Card, List, Typography } from "antd";
import {Card, List, Typography} from "antd";
import React from "react";
import { useQuery } from "@apollo/client";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { QUERY_ACTIVE_TIME_TICKETS } from "../../graphql/timetickets.queries";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import {useQuery} from "@apollo/client";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {Link} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import {QUERY_ACTIVE_TIME_TICKETS} from "../../graphql/timetickets.queries";
import {selectTechnician} from "../../redux/tech/tech.selectors";
import {DateTimeFormatter} from "../../utils/DateFormatter";
import AlertComponent from "../alert/alert.component";
import DataLabel from "../data-label/data-label.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import TechClockOffButton from "../tech-job-clock-out-button/tech-job-clock-out-button.component";
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
import {OwnerNameDisplayFunction} from "../owner-name-display/owner-name-display.component";
const mapStateToProps = createStructuredSelector({
technician: selectTechnician,
@@ -21,26 +21,26 @@ const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export function TechClockedInList({ technician }) {
const { loading, error, data, refetch } = useQuery(
export function TechClockedInList({technician}) {
const {loading, error, data, refetch} = useQuery(
QUERY_ACTIVE_TIME_TICKETS,
{
variables: {
employeeId: technician.id,
employeeId: technician?.id,
},
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
skip: !technician,
}
);
const { t } = useTranslation();
if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />;
const {t} = useTranslation();
if (loading) return <LoadingSpinner/>;
if (error) return <AlertComponent message={error.message} type="error"/>;
return (
<div>
{data.timetickets.length > 0 ? (
{data?.timetickets?.length > 0 ? (
<div>
<Typography.Title level={2}>
{t("timetickets.labels.alreadyclockedon")}
@@ -97,4 +97,5 @@ export function TechClockedInList({ technician }) {
</div>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(TechClockedInList);

View File

@@ -30,8 +30,9 @@ const TechJobStatistics = ({ technician }) => {
end: endDate.format("YYYY-MM-DD"),
fixedStart: dayjs().startOf("month").format("YYYY-MM-DD"),
fixedEnd: dayjs().endOf("month").format("YYYY-MM-DD"),
employeeid: technician.id,
employeeid: technician?.id,
},
skip: !technician,
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
}

View File

@@ -33,7 +33,8 @@ export function TechLogin({
const navigate = useNavigate();
const handleFinish = (values) => {
techLoginStart(values);
// Remap these because EmployeeID form name has previously been used in the project
techLoginStart({pin: values.pin, employeeid: values.techEmployeeId});
};
useEffect(() => {
@@ -49,7 +50,8 @@ export function TechLogin({
>
<Form.Item
label={t("tech.fields.employeeid")}
name="employeeid"
name="techEmployeeId"
rules={[
{
required: true,

View File

@@ -37,7 +37,7 @@ export default function TimeTicketsDatesSelector() {
return (
<DatePicker.RangePicker
defaultValue={[
start ? dayjs(start) : dayjs().startOf("week").subtract(7, "days"),
start ? dayjs(start) : dayjs().startOf("week").subtract(7, "day"),
end ? dayjs(end) : dayjs().endOf("week"),
]}
format="MM/DD/YYYY"

View File

@@ -23,7 +23,7 @@ export default function TimeTicketsAttendanceTable() {
variables: {
start: start
? start
: dayjs().startOf("week").subtract(7, "days").format("YYYY-MM-DD"),
: dayjs().startOf("week").subtract(7, "day").format("YYYY-MM-DD"),
end: end ? end : dayjs().endOf("week").format("YYYY-MM-DD"),
},
},

View File

@@ -22,7 +22,7 @@ export default function TimeTicketsPayrollTable() {
variables: {
start: start
? start
: dayjs().startOf("week").subtract(7, "days").format("YYYY-MM-DD"),
: dayjs().startOf("week").subtract(7, "day").format("YYYY-MM-DD"),
end: end ? end : dayjs().endOf("week").format("YYYY-MM-DD"),
},
},

View File

@@ -18,20 +18,36 @@ import {
} from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
/**
* Mapping state to props
*/
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
/**
* Mapping dispatch to props
*/
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
/**
* ScoreboardContainer component
* @param {Object} props - The props that were defined by the caller of this component.
* @param {Function} props.setBreadcrumbs - Function to set breadcrumbs.
* @param {Function} props.setSelectedHeader - Function to set selected header.
*/
export function ScoreboardContainer({ setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation();
const searchParams = queryString.parse(useLocation().search);
const { tab } = searchParams;
const history = useNavigate();
/**
* useEffect hook to set document title, selected header and breadcrumbs
*/
useEffect(() => {
document.title = t("titles.scoreboard");
setSelectedHeader("scoreboard");
@@ -43,6 +59,9 @@ export function ScoreboardContainer({ setBreadcrumbs, setSelectedHeader }) {
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
/**
* Render the component
*/
return (
<FeatureWrapper featureName="scoreboard">
<RbacWrapper action="scoreboard:view">
@@ -97,6 +116,10 @@ export function ScoreboardContainer({ setBreadcrumbs, setSelectedHeader }) {
</FeatureWrapper>
);
}
/**
* Connecting the component to Redux store
*/
export default connect(
mapStateToProps,
mapDispatchToProps

View File

@@ -37,7 +37,7 @@ export function TimeTicketsContainer({
const startDate = start
? dayjs(start)
: dayjs().startOf("week").subtract(7, "days");
: dayjs().startOf("week").subtract(7, "day");
const endDate = end ? dayjs(end) : dayjs().endOf("week");
const { loading, error, data } = useQuery(QUERY_TIME_TICKETS_IN_RANGE, {

View File

@@ -209,9 +209,9 @@ export function* calculateScheduleLoad({ payload: end }) {
const range = Math.round(dayjs.duration(end.diff(today)).asDays()) + 1;
for (var day = 0; day < range; day++) {
const current = dayjs(today).add(day, "days").format("yyyy-MM-DD");
const current = dayjs(today).add(day, "day").format("yyyy-MM-DD");
const prev = dayjs(today)
.add(day - 1, "days")
.add(day - 1, "day")
.format("yyyy-MM-DD");
if (!!!load[current]) {
load[current] = {};

View File

@@ -1,10 +1,10 @@
import dayjs from "./day";
const range = {
Today: [dayjs(), dayjs()],
"Last 14 days": [dayjs().subtract(14, "days"), dayjs()],
"Last 7 days": [dayjs().subtract(7, "days"), dayjs()],
"Next 7 days": [dayjs(), dayjs().add(7, "days")],
"Next 14 days": [dayjs(), dayjs().add(14, "days")],
"Last 14 days": [dayjs().subtract(14, "day"), dayjs()],
"Last 7 days": [dayjs().subtract(7, "day"), dayjs()],
"Next 7 days": [dayjs(), dayjs().add(7, "day")],
"Next 14 days": [dayjs(), dayjs().add(14, "day")],
"Last Month": [
dayjs().startOf("month").subtract(1, "month"),
dayjs().startOf("month").subtract(1, "month").endOf("month"),
@@ -15,13 +15,13 @@ const range = {
dayjs().startOf("month").add(1, "month").endOf("month"),
],
"Last Quarter": [
dayjs().startOf("quarter").subtract(1, "quarters"),
dayjs().startOf("quarter").subtract(1, "quarter"),
dayjs().startOf("quarter").subtract(1, "day"),
],
"This Quarter": [
dayjs().startOf("quarter"),
dayjs().startOf("quarter").add(1, "quarter").subtract(1, "day"),
],
"Last 90 Days": [dayjs().add(-90, "days"), dayjs()],
"Last 90 Days": [dayjs().add(-90, "day"), dayjs()],
};
export default range;

View File

@@ -1,4 +1,5 @@
import dayjs from 'dayjs';
import dayjsBusinessDays from "dayjs-business-days2";
import isSameOrAfter from "dayjs/plugin/isSameOrAfter";
import updateLocale from 'dayjs/plugin/updateLocale';
@@ -30,6 +31,7 @@ import isYesterday from 'dayjs/plugin/isYesterday';
import objectSupport from 'dayjs/plugin/objectSupport';
import toArray from 'dayjs/plugin/toArray';
import toObject from 'dayjs/plugin/toObject';
// import badMutable from 'dayjs/plugin/badMutable';
// import preParsePostFormat from 'dayjs/plugin/preParsePostFormat';
@@ -42,7 +44,6 @@ dayjs.extend(isYesterday);
dayjs.extend(isTomorrow);
dayjs.extend(isToday);
dayjs.extend(localeData);
dayjs.extend(relativeTime);
dayjs.extend(quarterOfYear);
dayjs.extend(localizedFormat);
// dayjs.extend(preParsePostFormat); // TODO: This should not be needed
@@ -57,6 +58,7 @@ dayjs.extend(calendar);
dayjs.extend(arraySupport);
dayjs.extend(advancedFormat);
dayjs.extend(duration);
dayjs.extend(relativeTime);
dayjs.extend(pluralGetSet);
dayjs.extend(customParseFormat);
dayjs.extend(utc);