feature/IO-3214-Job-Status-Card-Extension - Complete

This commit is contained in:
Dave Richer
2025-05-06 17:08:46 -04:00
parent b7456cecd4
commit 110fad2abc
12 changed files with 127 additions and 50 deletions

View File

@@ -106,7 +106,12 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) {
<Form.Item label={t("jobs.fields.date_open")} name="date_open"> <Form.Item label={t("jobs.fields.date_open")} name="date_open">
<DateTimePicker /> <DateTimePicker />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.estimate_sent_approval")} name="estimate_sent_approval">
<DateTimePicker />
</Form.Item>
<Form.Item label={t("jobs.fields.estimate_approved")} name="estimate_approved">
<DateTimePicker />
</Form.Item>
<Form.Item label={t("jobs.fields.date_scheduled")} name="date_scheduled"> <Form.Item label={t("jobs.fields.date_scheduled")} name="date_scheduled">
<DateTimePicker /> <DateTimePicker />
</Form.Item> </Form.Item>

View File

@@ -7,6 +7,7 @@ import { selectJobReadOnly } from "../../redux/application/application.selectors
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component"; import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
import FormRow from "../layout-form-row/layout-form-row.component"; import FormRow from "../layout-form-row/layout-form-row.component";
import dayjs from "../../utils/day";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly, jobRO: selectJobReadOnly,
@@ -40,6 +41,20 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) {
<Form.Item label={t("jobs.fields.date_rentalresp")} name="date_rentalresp"> <Form.Item label={t("jobs.fields.date_rentalresp")} name="date_rentalresp">
<DateTimePicker disabled={jobRO} /> <DateTimePicker disabled={jobRO} />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.estimate_sent_approval")} name="estimate_sent_approval">
<DateTimePicker
disabled={true}
value={job.estimate_sent_approval ? dayjs(job.estimate_sent_approval) : null}
placeholder={t("general.labels.na")}
/>
</Form.Item>
<Form.Item label={t("jobs.fields.estimate_approved")} name="estimate_approved">
<DateTimePicker
disabled={true}
value={job.estimate_approved ? dayjs(job.estimate_approved) : null}
placeholder={t("general.labels.na")}
/>
</Form.Item>
</FormRow> </FormRow>
<FormRow header={t("jobs.forms.scheddates")}> <FormRow header={t("jobs.forms.scheddates")}>
@@ -76,21 +91,15 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) {
<DateTimePicker disabled={jobRO} /> <DateTimePicker disabled={jobRO} />
</Form.Item> </Form.Item>
<Form.Item shouldUpdate> <Form.Item shouldUpdate>
{() => { {() => (
return ( <Form.Item
<Form.Item label={t("jobs.fields.actual_completion")}
label={t("jobs.fields.actual_completion")} name="actual_completion"
name="actual_completion" rules={[{ required: jobInPostProduction }]}
rules={[ >
{ <DateTimePicker disabled={jobRO} />
required: jobInPostProduction </Form.Item>
} )}
]}
>
<DateTimePicker disabled={jobRO} />
</Form.Item>
);
}}
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.scheduled_delivery")} name="scheduled_delivery"> <Form.Item label={t("jobs.fields.scheduled_delivery")} name="scheduled_delivery">
<DateTimePicker disabled={jobRO} /> <DateTimePicker disabled={jobRO} />
@@ -103,15 +112,12 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) {
<Form.Item label={t("jobs.fields.date_invoiced")} name="date_invoiced"> <Form.Item label={t("jobs.fields.date_invoiced")} name="date_invoiced">
<DateTimePicker disabled={true || jobRO} /> <DateTimePicker disabled={true || jobRO} />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.date_exported")} name="date_exported"> <Form.Item label={t("jobs.fields.date_exported")} name="date_exported">
<DateTimePicker disabled={true || jobRO} /> <DateTimePicker disabled={true || jobRO} />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.date_void")} name="date_void"> <Form.Item label={t("jobs.fields.date_void")} name="date_void">
<DateTimePicker disabled={true || jobRO} /> <DateTimePicker disabled={true || jobRO} />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.date_lost_sale")} name="date_lost_sale"> <Form.Item label={t("jobs.fields.date_lost_sale")} name="date_lost_sale">
<DateTimePicker disabled={true || jobRO} /> <DateTimePicker disabled={true || jobRO} />
</Form.Item> </Form.Item>

View File

@@ -1,13 +1,15 @@
import { BranchesOutlined, ExclamationCircleFilled, PauseCircleOutlined, WarningFilled } from "@ant-design/icons"; import { BranchesOutlined, ExclamationCircleFilled, PauseCircleOutlined, WarningFilled } from "@ant-design/icons";
import { Card, Col, Divider, Row, Space, Tag, Tooltip } from "antd"; import { Card, Checkbox, Col, Divider, Row, Space, Tag, Tooltip } from "antd";
import React, { useState } from "react"; import { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { useMutation } from "@apollo/client";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateTimeFormatter } from "../../utils/DateFormatter"; import { DateTimeFormatter } from "../../utils/DateFormatter";
import dayjs from "../../utils/day"; import dayjs from "../../utils/day";
@@ -22,6 +24,7 @@ import ProductionListColumnComment from "../production-list-columns/production-l
import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component"; import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component";
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component"; import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
import "./jobs-detail-header.styles.scss"; import "./jobs-detail-header.styles.scss";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly, jobRO: selectJobReadOnly,
@@ -29,41 +32,55 @@ const mapStateToProps = createStructuredSelector({
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setPrintCenterContext: (context) => dispatch(setModalContext({ context: context, modal: "printCenter" })) setPrintCenterContext: (context) =>
dispatch(
setModalContext({
context: context,
modal: "printCenter"
})
)
}); });
const colSpan = { const colSpan = {
xs: { xs: { span: 24 },
span: 24 sm: { span: 24 },
}, md: { span: 12 },
sm: { lg: { span: 6 },
span: 24 xl: { span: 6 }
},
md: {
span: 12
},
lg: {
span: 6
},
xl: {
span: 6
}
}; };
export function JobsDetailHeader({ job, bodyshop, disabled }) { export function JobsDetailHeader({ job, bodyshop, disabled }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { notification } = useNotification();
const [notesClamped, setNotesClamped] = useState(true); const [notesClamped, setNotesClamped] = useState(true);
const vehicleTitle = `${job.v_model_yr || ""} ${job.v_color || ""} const [updateJob] = useMutation(UPDATE_JOB);
${job.v_make_desc || ""} const vehicleTitle =
${job.v_model_desc || ""}`.trim(); `${job.v_model_yr || ""} ${job.v_color || ""} ${job.v_make_desc || ""} ${job.v_model_desc || ""}`.trim();
const bodyHrs = job.joblines.filter((j) => j.mod_lbr_ty !== "LAR").reduce((acc, val) => acc + val.mod_lb_hrs, 0); const bodyHrs = job.joblines.filter((j) => j.mod_lbr_ty !== "LAR").reduce((acc, val) => acc + val.mod_lb_hrs, 0);
const refinishHrs = job.joblines const refinishHrs = job.joblines
.filter((line) => line.mod_lbr_ty === "LAR") .filter((line) => line.mod_lbr_ty === "LAR")
.reduce((acc, val) => acc + val.mod_lb_hrs, 0); .reduce((acc, val) => acc + val.mod_lb_hrs, 0);
const ownerTitle = OwnerNameDisplayFunction(job).trim(); const ownerTitle = OwnerNameDisplayFunction(job).trim();
// Handle checkbox changes
const handleCheckboxChange = async (field, checked) => {
const value = checked ? dayjs().toISOString() : null;
try {
await updateJob({
variables: {
jobId: job.id,
job: { [field]: value }
},
refetchQueries: ["GET_JOB_BY_PK"],
awaitRefetchQueries: true
});
} catch (error) {
notification.error({
message: t("jobs.errors.saving", { error: error.message })
});
}
};
return ( return (
<Row gutter={[16, 16]} style={{ alignItems: "stretch" }}> <Row gutter={[16, 16]} style={{ alignItems: "stretch" }}>
<Col {...colSpan}> <Col {...colSpan}>
@@ -72,11 +89,7 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
<DataLabel label={t("jobs.fields.status")}> <DataLabel label={t("jobs.fields.status")}>
<Space wrap> <Space wrap>
{job.status} {job.status}
{job.inproduction && ( {job.inproduction && <Tag color="#f50">{t("jobs.labels.inproduction")}</Tag>}
<Tag color="#f50" key="production">
{t("jobs.labels.inproduction")}
</Tag>
)}
{job.suspended && <PauseCircleOutlined style={{ color: "orangered" }} />} {job.suspended && <PauseCircleOutlined style={{ color: "orangered" }} />}
{job.iouparent && ( {job.iouparent && (
<Link to={`/manage/jobs/${job.iouparent}`}> <Link to={`/manage/jobs/${job.iouparent}`}>
@@ -110,7 +123,6 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
<span style={{ margin: "0rem .5rem" }}>/</span> <span style={{ margin: "0rem .5rem" }}>/</span>
<CurrencyFormatter>{job.owner_owing}</CurrencyFormatter> <CurrencyFormatter>{job.owner_owing}</CurrencyFormatter>
</DataLabel> </DataLabel>
<DataLabel label={t("jobs.fields.alt_transport")}> <DataLabel label={t("jobs.fields.alt_transport")}>
{job.alt_transport} {job.alt_transport}
<JobAltTransportChange job={job} /> <JobAltTransportChange job={job} />
@@ -127,11 +139,27 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
))} ))}
</DataLabel> </DataLabel>
)} )}
<DataLabel label={t("jobs.fields.production_vars.note")}> <DataLabel label={t("jobs.fields.production_vars.note")}>
<ProductionListColumnProductionNote record={job} /> <ProductionListColumnProductionNote record={job} />
</DataLabel> </DataLabel>
<DataLabel label={t("jobs.fields.estimate_sent_approval")}>
<Checkbox
checked={!!job.estimate_sent_approval}
onChange={(e) => handleCheckboxChange("estimate_sent_approval", e.target.checked)}
disabled={disabled}
>
{t("jobs.labels.sent")}
</Checkbox>
</DataLabel>
<DataLabel label={t("jobs.fields.estimate_approved")}>
<Checkbox
checked={!!job.estimate_approved}
onChange={(e) => handleCheckboxChange("estimate_approved", e.target.checked)}
disabled={disabled}
>
{t("jobs.labels.approved")}
</Checkbox>
</DataLabel>
<Space wrap> <Space wrap>
{job.special_coverage_policy && ( {job.special_coverage_policy && (
<Tag color="tomato"> <Tag color="tomato">

View File

@@ -685,6 +685,8 @@ export const GET_JOB_BY_PK = gql`
scheduled_delivery scheduled_delivery
scheduled_in scheduled_in
selling_dealer selling_dealer
estimate_approved
estimate_sent_approval
selling_dealer_contact selling_dealer_contact
servicing_dealer servicing_dealer
servicing_dealer_contact servicing_dealer_contact
@@ -929,6 +931,8 @@ export const QUERY_JOB_CARD_DETAILS = gql`
date_exported date_exported
date_repairstarted date_repairstarted
date_scheduled date_scheduled
estimate_sent_approval
estimate_approved
date_estimated date_estimated
employee_body_rel { employee_body_rel {
id id
@@ -1077,6 +1081,8 @@ export const UPDATE_JOB = gql`
date_repairstarted date_repairstarted
date_void date_void
date_lost_sale date_lost_sale
estimate_sent_approval
estimate_approved
} }
} }
} }
@@ -2431,6 +2437,8 @@ export const QUERY_PARTS_QUEUE_CARD_DETAILS = gql`
plate_st plate_st
po_number po_number
production_vars production_vars
estimate_sent_approval
estimate_approved
ro_number ro_number
scheduled_completion scheduled_completion
scheduled_delivery scheduled_delivery

View File

@@ -1650,6 +1650,8 @@
"adjustment_bottom_line": "Adjustments", "adjustment_bottom_line": "Adjustments",
"adjustmenthours": "Adjustment Hours", "adjustmenthours": "Adjustment Hours",
"alt_transport": "Alt. Trans.", "alt_transport": "Alt. Trans.",
"estimate_sent_approval": "Estimate Sent for Approval",
"estimate_approved": "Estimate Approved",
"area_of_damage_impact": { "area_of_damage_impact": {
"10": "Left Front Side", "10": "Left Front Side",
"11": "Left Front Corner", "11": "Left Front Corner",
@@ -1955,6 +1957,8 @@
"scheddates": "Schedule Dates" "scheddates": "Schedule Dates"
}, },
"labels": { "labels": {
"sent": "",
"approved": "",
"accountsreceivable": "Accounts Receivable", "accountsreceivable": "Accounts Receivable",
"act_price_ppc": "New Part Price", "act_price_ppc": "New Part Price",
"actual_completion_inferred": "$t(jobs.fields.actual_completion) inferred using $t(jobs.fields.scheduled_completion).", "actual_completion_inferred": "$t(jobs.fields.actual_completion) inferred using $t(jobs.fields.scheduled_completion).",

View File

@@ -1642,6 +1642,8 @@
"voiding": "" "voiding": ""
}, },
"fields": { "fields": {
"estimate_sent_approval": "",
"estimate_approved": "",
"active_tasks": "", "active_tasks": "",
"actual_completion": "Realización real", "actual_completion": "Realización real",
"actual_delivery": "Entrega real", "actual_delivery": "Entrega real",
@@ -1955,6 +1957,8 @@
"scheddates": "" "scheddates": ""
}, },
"labels": { "labels": {
"sent": "",
"approved": "",
"accountsreceivable": "", "accountsreceivable": "",
"act_price_ppc": "", "act_price_ppc": "",
"actual_completion_inferred": "", "actual_completion_inferred": "",

View File

@@ -1642,6 +1642,8 @@
"voiding": "" "voiding": ""
}, },
"fields": { "fields": {
"estimate_sent_approval": "",
"estimate_approved": "",
"active_tasks": "", "active_tasks": "",
"actual_completion": "Achèvement réel", "actual_completion": "Achèvement réel",
"actual_delivery": "Livraison réelle", "actual_delivery": "Livraison réelle",
@@ -1955,6 +1957,8 @@
"scheddates": "" "scheddates": ""
}, },
"labels": { "labels": {
"sent": "",
"approved": "",
"accountsreceivable": "", "accountsreceivable": "",
"act_price_ppc": "", "act_price_ppc": "",
"actual_completion_inferred": "", "actual_completion_inferred": "",

View File

@@ -3702,6 +3702,8 @@
- est_ph1 - est_ph1
- est_st - est_st
- est_zip - est_zip
- estimate_approved
- estimate_sent_approval
- federal_tax_rate - federal_tax_rate
- flat_rate_ats - flat_rate_ats
- g_bett_amt - g_bett_amt
@@ -3976,6 +3978,8 @@
- est_ph1 - est_ph1
- est_st - est_st
- est_zip - est_zip
- estimate_approved
- estimate_sent_approval
- federal_tax_rate - federal_tax_rate
- flat_rate_ats - flat_rate_ats
- g_bett_amt - g_bett_amt
@@ -4262,6 +4266,8 @@
- est_ph1 - est_ph1
- est_st - est_st
- est_zip - est_zip
- estimate_approved
- estimate_sent_approval
- federal_tax_rate - federal_tax_rate
- flat_rate_ats - flat_rate_ats
- g_bett_amt - g_bett_amt

View File

@@ -0,0 +1,4 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "public"."jobs" add column "estimate_sent_approval" timestamptz
-- null;

View File

@@ -0,0 +1,2 @@
alter table "public"."jobs" add column "estimate_sent_approval" timestamptz
null;

View File

@@ -0,0 +1,4 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "public"."jobs" add column "estimate_approved" timestamptz
-- null;

View File

@@ -0,0 +1,2 @@
alter table "public"."jobs" add column "estimate_approved" timestamptz
null;