RO into IO merge as of 02/05/2024.

This commit is contained in:
Patrick Fic
2024-02-12 12:22:05 -08:00
211 changed files with 31134 additions and 25729 deletions

View File

@@ -171,7 +171,7 @@ export function CsiContainerPage({currentUser}) {
)}
<Layout.Footer>
{`Copyright ImEX.Online. Survey ID: ${surveyId}`}
{`Survey ID: ${surveyId}`}
</Layout.Footer>
</Layout>
);

View File

@@ -1,16 +1,17 @@
import React from "react";
import {Typography} from "antd";
import { Typography } from "antd";
import InstanceRenderMgr from "../../utils/instanceRenderMgr";
export default function AboutPage() {
return (
<div style={{textAlign: "center", margin: "1rem 0rem"}}>
<Typography.Title
level={2}
>{`ImEX Online V.${process.env.NODE_ENV}-${process.env.REACT_APP_GIT_SHA}`}</Typography.Title>
<Typography.Title level={4}>
&copy; 2019 - {new Date().getFullYear()} Snapt Software Inc. used under
license to ImEX Systems Inc.
</Typography.Title>
<div style={{ textAlign: "center", margin: "1rem 0rem" }}>
<Typography.Title level={2}>{`${InstanceRenderMgr({
imex: "ImEX Online",
rome: "Rome Online",
promanager: "ProManager",
})}Rome Online V.${process.env.NODE_ENV}-${
process.env.REACT_APP_GIT_SHA
}`}</Typography.Title>
<Typography.Title level={2}>Third Party Notices</Typography.Title>
<a href="/3rdparty-app.txt">
<Typography.Title level={4}>Application</Typography.Title>

View File

@@ -32,9 +32,9 @@ export default connect(mapStateToProps, mapDispatchToProps)(DmsContainer);
export const socket = SocketIO(
process.env.NODE_ENV === "production"
? process.env.REACT_APP_AXIOS_BASE_API_URL
: window.location.origin,
// "http://localhost:4000", // for dev testing,
? process.env.REACT_APP_AXIOS_BASE_API_URL
: window.location.origin,
// "http://localhost:4000", // for dev testing,
{
path: "/ws",
withCredentials: true,

View File

@@ -14,10 +14,10 @@ import JobsAdminDeleteIntake from "../../components/jobs-admin-delete-intake/job
import JobsAdminMarkReexport from "../../components/jobs-admin-mark-reexport/jobs-admin-mark-reexport.component";
import JobAdminOwnerReassociate
from "../../components/jobs-admin-owner-reassociate/jobs-admin-owner-reassociate.component";
import JobsAdminRemoveAR from "../../components/jobs-admin-remove-ar/jobs-admin-remove-ar.component";
import JobsAdminUnvoid from "../../components/jobs-admin-unvoid/jobs-admin-unvoid.component";
import JobAdminVehicleReassociate
from "../../components/jobs-admin-vehicle-reassociate/jobs-admin-vehicle-reassociate.component";
import JobsAdminRemoveAR from "../../components/jobs-admin-remove-ar/jobs-admin-remove-ar.component";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import NotFound from "../../components/not-found/not-found.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";

View File

@@ -56,7 +56,7 @@ export function JobsCloseComponent({job, bodyshop, jobRO, insertAuditTrail,}) {
const {t} = useTranslation();
const [form] = Form.useForm();
const client = useApolloClient();
// const history = useHistory();
// const history = useNavigate();
const [closeJob] = useMutation(UPDATE_JOB);
const [loading, setLoading] = useState(false);
@@ -119,7 +119,7 @@ export function JobsCloseComponent({job, bodyshop, jobRO, insertAuditTrail,}) {
jobid: job.id,
operation: AuditTrailMapping.jobinvoiced(),
});
// history.push(`/manage/jobs/${job.id}`);
// history(`/manage/jobs/${job.id}`);
} else {
setLoading(false);
notification["error"]({

View File

@@ -12,6 +12,7 @@ import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/applic
import {selectBodyshop} from "../../redux/user/user.selectors";
import JobsCreateComponent from "./jobs-create.component";
import JobCreateContext from "./jobs-create.context";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -168,96 +169,111 @@ function JobsCreateContainer({bodyshop, setBreadcrumbs, setSelectedHeader}) {
federal_tax_rate: bodyshop.bill_tax_rates.federal_tax_rate / 100,
state_tax_rate: bodyshop.bill_tax_rates.state_tax_rate / 100,
local_tax_rate: bodyshop.bill_tax_rates.local_tax_rate / 100,
parts_tax_rates: {
PAA: {
prt_type: "PAA",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
},
PAC: {
prt_type: "PAC",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
},
PAG: {
prt_type: "PAG",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
},
PAL: {
prt_type: "PAL",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
},
PAM: {
prt_type: "PAM",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
},
PAN: {
prt_type: "PAN",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
},
PAR: {
prt_type: "PAR",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
},
PAS: {
prt_type: "PAS",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
},
PASL: {
prt_type: "PASL",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
},
PAP: {
prt_type: "PAP",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
},
PAO: {
prt_type: "PAO",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
},
...InstanceRenderManager({imex: {
parts_tax_rates: {
PAA: {
prt_type: "PAA",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
},
PAC: {
prt_type: "PAC",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
},
PAG: {
prt_type: "PAG",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
},
PAL: {
prt_type: "PAL",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
},
PAM: {
prt_type: "PAM",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
},
PAN: {
prt_type: "PAN",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
},
PAR: {
prt_type: "PAR",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
},
PAS: {
prt_type: "PAS",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
},
PASL: {
prt_type: "PASL",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
},
PAP: {
prt_type: "PAP",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
},
PAO: {
prt_type: "PAO",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
},
},
rome: {
cieca_pft: {
...bodyshop.md_responsibility_centers.taxes.tax_ty1,
...bodyshop.md_responsibility_centers.taxes.tax_ty2,
...bodyshop.md_responsibility_centers.taxes.tax_ty3,
...bodyshop.md_responsibility_centers.taxes.tax_ty4,
...bodyshop.md_responsibility_centers.taxes.tax_ty5,
},
materials: bodyshop.md_responsibility_centers.cieca_pfm,
cieca_pfl: bodyshop.md_responsibility_centers.cieca_pfl,
parts_tax_rates: bodyshop.md_responsibility_centers.parts_tax_rates,
}
}})
}}
>
<JobsCreateComponent form={form}/>

View File

@@ -48,7 +48,9 @@ import {setModalContext} from "../../redux/modals/modals.actions";
import {selectBodyshop} from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import UndefinedToNull from "../../utils/undefinedtonull";
import {DateTimeFormat} from "./../../utils/DateFormatter";
import _ from "lodash";
import JobProfileDataWarning from "../../components/job-profile-data-warning/job-profile-data-warning.component";
import {DateTimeFormat} from "../../utils/DateFormatter";
import JobLifecycleComponent from "../../components/job-lifecycle/job-lifecycle.component";
const mapStateToProps = createStructuredSelector({
@@ -100,86 +102,121 @@ export function JobsDetailPage({
"category",
"referral_source",
]),
parts_tax_rates: {
...job.parts_tax_rates,
...values.parts_tax_rates,
},
// The union and spread is required to keep values coming in from the estimating system that aren't displayed.
parts_tax_rates: _.union(
Object.keys(job.parts_tax_rates),
Object.keys(values.parts_tax_rates)
).reduce((acc, val) => {
acc[val] = {
...job.parts_tax_rates[val],
...values.parts_tax_rates[val],
};
return acc;
}, {}),
materials: _.union(
Object.keys(job.materials),
Object.keys(values.materials)
).reduce((acc, val) => {
acc[val] = {
...job.materials[val],
...values.materials[val],
};
return acc;
}, {}),
cieca_pfl: _.union(
Object.keys(job.cieca_pfl),
Object.keys(values.cieca_pfl)
).reduce((acc, val) => {
acc[val] = {
...job.cieca_pfl[val],
...values.cieca_pfl[val],
};
return acc;
}, {}),
cieca_pfo: {...job.cieca_pfo, ...values.cieca_pfo},
},
},
});
const newTotals = await Axios.post("/job/totalsssu", {
id: job.id,
});
try {
const newTotals = await Axios.post("/job/totalsssu", {
id: job.id,
});
if (newTotals.status !== 200 || result.errors) {
if (newTotals.status !== 200 || result.errors) {
notification["error"]({
message: t("jobs.errors.totalscalc"),
});
} else {
notification["success"]({
message: t("jobs.successes.savetitle"),
});
const changedAuditFields = form.getFieldsValue(
[
"scheduled_in",
"actual_in",
"scheduled_completion",
"actual_completion",
"scheduled_delivery",
"actual_delivery",
"date_invoiced",
"ins_co_nm",
"ded_amt",
"ded_status",
"date_exported",
"special_coverage_policy",
"ca_gst_registrant",
"ca_bc_pvrt",
"scheduled_in",
"rate_la1",
"rate_la2",
"rate_la3",
"rate_la4",
"rate_laa",
"rate_lab",
"rate_lad",
"rate_lae",
"rate_laf",
"rate_lag",
"rate_lam",
"rate_lar",
"rate_las",
"rate_lau",
"rate_ma2s",
"rate_ma2t",
"rate_ma3s",
"rate_mabl",
"rate_macs",
"rate_mapa",
"rate_mahw",
"rate_mash",
"rate_matd",
],
(meta) => meta && meta.touched
);
Object.keys(changedAuditFields).forEach((key) => {
insertAuditTrail({
jobid: job.id,
operation: AuditTrailMapping.jobfieldchange(
key,
changedAuditFields[key] instanceof dayjs
? DateTimeFormat(changedAuditFields[key])
: changedAuditFields[key]
),
});
});
await refetch();
form.setFieldsValue(transformJobToForm(job));
form.resetFields();
}
} catch (error) {
notification["error"]({
message: t("jobs.errors.totalscalc"),
});
} else {
notification["success"]({
message: t("jobs.successes.savetitle"),
});
const changedAuditFields = form.getFieldsValue(
[
"scheduled_in",
"actual_in",
"scheduled_completion",
"actual_completion",
"scheduled_delivery",
"actual_delivery",
"date_invoiced",
"ins_co_nm",
"ded_amt",
"ded_status",
"date_exported",
"special_coverage_policy",
"ca_gst_registrant",
"ca_bc_pvrt",
"scheduled_in",
"rate_la1",
"rate_la2",
"rate_la3",
"rate_la4",
"rate_laa",
"rate_lab",
"rate_lad",
"rate_lae",
"rate_laf",
"rate_lag",
"rate_lam",
"rate_lar",
"rate_las",
"rate_lau",
"rate_ma2s",
"rate_ma2t",
"rate_ma3s",
"rate_mabl",
"rate_macs",
"rate_mapa",
"rate_mahw",
"rate_mash",
"rate_matd",
],
(meta) => meta && meta.touched
);
Object.keys(changedAuditFields).forEach((key) => {
insertAuditTrail({
jobid: job.id,
operation: AuditTrailMapping.jobfieldchange(
key,
changedAuditFields[key] instanceof dayjs
? DateTimeFormat(changedAuditFields[key])
: changedAuditFields[key]
),
});
});
await refetch();
form.setFieldsValue(transormJobToForm(job));
form.resetFields();
} finally {
setLoading(false);
}
setLoading(false);
};
const menuExtra = (
@@ -240,7 +277,7 @@ export function JobsDetailPage({
onFinish={handleFinish}
{...formItemLayout}
autoComplete={"off"}
initialValues={transormJobToForm(job)}
initialValues={transformJobToForm(job)}
>
<PageHeader
// onBack={() => window.history.back()}
@@ -249,6 +286,7 @@ export function JobsDetailPage({
/>
<JobsDetailHeader job={job}/>
<Divider type="horizontal"/>
<JobProfileDataWarning job={job}/>
<FormFieldsChanged form={form}/>
<Tabs
defaultActiveKey={search.tab}
@@ -305,7 +343,7 @@ export function JobsDetailPage({
key: 'lifecycle',
icon: <BarsOutlined/>,
label: t('menus.jobsdetail.lifecycle'),
children: <JobLifecycleComponent job={job} statuses={bodyshop.md_ro_statuses}/>,
children: <JobLifecycleComponent job={job} statuses={bodyshop.md_ro_statuses}/>
},
{
key: "dates",
@@ -345,10 +383,23 @@ export function JobsDetailPage({
export default connect(mapStateToProps, mapDispatchToProps)(JobsDetailPage);
const transormJobToForm = (job) => {
return {
...job,
loss_date: job.loss_date ? dayjs(job.loss_date) : null,
date_estimated: job.date_estimated ? dayjs(job.date_estimated) : null,
};
};
const transformJobToForm = (job) => {
const transformedJob = {...job};
transformedJob.parts_tax_rates = Object.keys(transformedJob.parts_tax_rates).reduce((acc, parttype) => {
acc[parttype] = Object.keys(transformedJob.parts_tax_rates[parttype]).reduce((innerAcc, key) => {
if (key.includes("tx_in")) {
innerAcc[key] = transformedJob.parts_tax_rates[parttype][key] === "Y" || transformedJob.parts_tax_rates[parttype][key] === true;
} else {
innerAcc[key] = transformedJob.parts_tax_rates[parttype][key];
}
return innerAcc;
}, {});
return acc;
}, {});
transformedJob.loss_date = transformedJob.loss_date ? dayjs(transformedJob.loss_date) : null;
transformedJob.date_estimated = transformedJob.date_estimated ? dayjs(transformedJob.date_estimated) : null;
return transformedJob;
};

View File

@@ -23,6 +23,7 @@ import * as Sentry from "@sentry/react";
import "./manage.page.styles.scss";
import UpdateAlert from "../../components/update-alert/update-alert.component";
import InstanceRenderManager from "../../utils/instanceRenderMgr.js";
const JobsPage = lazy(() => import("../jobs/jobs.page"));
@@ -93,6 +94,11 @@ const BillEnterModalContainer = lazy(() =>
const TimeTicketModalContainer = lazy(() =>
import("../../components/time-ticket-modal/time-ticket-modal.container")
);
const TimeTicketModalTask = lazy(() =>
import(
"../../components/time-ticket-task-modal/time-ticket-task-modal.container"
)
);
const PaymentModalContainer = lazy(() =>
import("../../components/payment-modal/payment-modal.container")
);
@@ -167,6 +173,9 @@ const DmsPayables = lazy(() =>
const ManageRootPage = lazy(() =>
import("../manage-root/manage-root.page.container")
);
const TtApprovals = lazy(() =>
import("../tt-approvals/tt-approvals.page.container")
);
const {Content, Footer} = Layout;
@@ -181,7 +190,7 @@ export function Manage({conflict, bodyshop}) {
useEffect(() => {
const widgetId = "IABVNO4scRKY11XBQkNr";
const widgetId = InstanceRenderManager({ imex:"IABVNO4scRKY11XBQkNr" ,rome: "mQdqARMzkZRUVugJ6TdS"}) ;
window.noticeable.render("widget", widgetId);
requestForToken().catch((error) => {
console.error(`Unable to request for token.`, error)
@@ -205,6 +214,7 @@ export function Manage({conflict, bodyshop}) {
<ReportCenterModal/>
<EmailOverlayContainer/>
<TimeTicketModalContainer/>
<TimeTicketModalTask/>
<PrintCenterModalContainer/>
<Routes>
<Route path='/_test' element={<TestComponent/>}/>
@@ -334,6 +344,7 @@ export function Manage({conflict, bodyshop}) {
path='/accounting/exportlogs'
element={<ExportLogs/>}
/>
<Route path='/ttapprovals' element={<TtApprovals/>}/>
<Route path='/partsqueue' element={<PartsQueue/>}/>
<Route path='/phonebook' element={<Phonebook/>}/>
@@ -387,7 +398,7 @@ export function Manage({conflict, bodyshop}) {
>
<div style={{display: "flex"}}>
<div>
{`ImEX Online ${
{`${t("titles.app")} ${
process.env.REACT_APP_GIT_SHA
} - ${preval`module.exports = new Date().toLocaleString("en-US", {timeZone: "America/Los_Angeles"});`}`}
</div>

View File

@@ -14,6 +14,8 @@ import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/applic
import {selectBodyshop} from "../../redux/user/user.selectors";
import ShopInfoUsersComponent from "../../components/shop-users/shop-users.component";
import ShopTeamsContainer from "../../components/shop-teams/shop-teams.container";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
@@ -42,33 +44,43 @@ export function ShopPage({bodyshop, setSelectedHeader, setBreadcrumbs}) {
if (!search.tab) history({search: "?tab=info"});
}, [history, search]);
const items = [
{
key: "info",
label: t("bodyshop.labels.shopinfo"),
children: <ShopInfoContainer/>,
},
{
key: "employees",
label: t("bodyshop.labels.employees"),
children: <ShopEmployeesContainer/>,
}];
if (bodyshop.md_tasks_presets.enable_tasks) {
items.push({
key: "teams",
label: t("bodyshop.labels.employee_teams"),
children: <ShopTeamsContainer/>
});
}
items.push({
key: "licensing",
label: t("bodyshop.labels.licensing"),
children: <ShopInfoUsersComponent/>,
},
{
key: "csiq",
label: t("bodyshop.labels.csiq"),
children: <ShopCsiConfig/>,
});
return (
<RbacWrapper action="shop:config">
<Tabs
activeKey={search.tab}
onChange={(key) => history({search: `?tab=${key}`})}
items={[
{
key: "info",
label: t("bodyshop.labels.shopinfo"),
children: <ShopInfoContainer/>,
},
{
key: "employees",
label: t("bodyshop.labels.employees"),
children: <ShopEmployeesContainer/>,
},
{
key: "licensing",
label: t("bodyshop.labels.licensing"),
children: <ShopInfoUsersComponent/>,
},
{
key: "csiq",
label: t("bodyshop.labels.csiq"),
children: <ShopCsiConfig/>,
},
]}
items={items}
/>
</RbacWrapper>
);

View File

@@ -0,0 +1,261 @@
import {useQuery} from "@apollo/client";
import React, {useState} from "react";
import {Button, Card, Input, Space, Table} from "antd";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {QUERY_JOBS_TECH_ASIGNED_TO_BY_TEAM} from "../../graphql/jobs.queries";
import {selectTechnician} from "../../redux/tech/tech.selectors";
import {selectBodyshop} from "../../redux/user/user.selectors";
import {alphaSort} from "../../utils/sorters";
import {SyncOutlined} from "@ant-design/icons";
import queryString from "query-string";
import {useTranslation} from "react-i18next";
import {useLocation, useNavigate} from "react-router-dom";
import {onlyUnique} from "../../utils/arrayHelper";
import AlertComponent from "../../components/alert/alert.component";
import OwnerNameDisplay from "../../components/owner-name-display/owner-name-display.component";
import {setModalContext} from "../../redux/modals/modals.actions";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
technician: selectTechnician,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setTimeTicketTaskContext: (context) =>
dispatch(setModalContext({context: context, modal: "timeTicketTask"})),
});
export function TechAssignedProdJobs({
setTimeTicketTaskContext,
technician,
bodyshop,
}) {
const {loading, error, data, refetch} = useQuery(
QUERY_JOBS_TECH_ASIGNED_TO_BY_TEAM,
{
variables: {
teamIds: bodyshop.employee_teams
.filter((et) =>
et.employee_team_members.find(
(etm) => etm.employeeid === technician.id
)
)
.map((et) => et.id),
},
}
);
const searchParams = queryString.parse(useLocation().search);
const {selected} = searchParams;
const [state, setState] = useState({
sortedInfo: {},
filteredInfo: {text: ""},
});
const {t} = useTranslation();
const history = useNavigate();
const [searchText, setSearchText] = useState("");
if (error) return <AlertComponent message={error.message} type="error"/>;
const jobs = data
? searchText === ""
? data.jobs
: data.jobs.filter(
(j) =>
(j.ro_number || "")
.toString()
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_fn || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_ln || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_co_nm || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) ||
(j.plate_no || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.v_model_desc || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.v_make_desc || "")
.toLowerCase()
.includes(searchText.toLowerCase())
)
: [];
const columns = [
{
title: t("jobs.fields.ro_number"),
dataIndex: "ro_number",
key: "ro_number",
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
sortOrder:
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
render: (text, record) => record.ro_number || t("general.labels.na"),
},
{
title: t("jobs.fields.owner"),
dataIndex: "owner",
key: "owner",
sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
sortOrder:
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
ellipsis: true,
render: (text, record) => (
<span>
<OwnerNameDisplay ownerObject={record}/>
</span>
),
},
{
title: t("jobs.fields.status"),
dataIndex: "status",
key: "status",
sorter: (a, b) => alphaSort(a.status, b.status),
sortOrder:
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
filters:
(jobs &&
jobs
.map((j) => j.status)
.filter(onlyUnique)
.map((s) => {
return {
text: s || "No Status*",
value: [s],
};
})) ||
[],
onFilter: (value, record) => value.includes(record.status),
render: (text, record) => {
return record.status || t("general.labels.na");
},
},
{
title: t("jobs.fields.vehicle"),
dataIndex: "vehicle",
key: "vehicle",
ellipsis: true,
render: (text, record) => (
<span>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
record.v_model_desc || ""
}`}</span>
),
},
{
title: t("vehicles.fields.plate_no"),
dataIndex: "plate_no",
key: "plate_no",
sorter: (a, b) => alphaSort(a.plate_no, b.plate_no),
sortOrder:
state.sortedInfo.columnKey === "plate_no" && state.sortedInfo.order,
render: (text, record) => {
return record.plate_no ? record.plate_no : "";
},
},
{
title: t("jobs.fields.clm_no"),
dataIndex: "clm_no",
key: "clm_no",
ellipsis: true,
sorter: (a, b) => alphaSort(a.clm_no, b.clm_no),
sortOrder:
state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order,
render: (text, record) => {
return record.clm_no ? (
<span>{record.clm_no}</span>
) : (
t("general.labels.unknown")
);
},
},
{
title: t("general.labels.actions"),
dataIndex: "actions",
key: "actions",
render: (text, record) => (
<Button
onClick={() => {
setTimeTicketTaskContext({
actions: {refetch: refetch},
context: {jobid: record.id},
});
}}
>
{t("timetickets.actions.claimtasks")}
</Button>
),
},
];
const handleTableChange = (pagination, filters, sorter) => {
setState({...state, filteredInfo: filters, sortedInfo: sorter});
};
const handleOnRowClick = (record) => {
if (record) {
if (record.id) {
history({
search: queryString.stringify({
...searchParams,
selected: record.id,
}),
});
}
}
};
return (
<Card
extra={
<Space wrap>
<Button onClick={() => refetch()}>
<SyncOutlined/>
</Button>
<Input.Search
placeholder={t("general.labels.search")}
onChange={(e) => {
setSearchText(e.target.value);
}}
value={searchText}
enterButton
/>
</Space>
}
>
<Table
loading={loading}
pagination={false}
columns={columns}
rowKey="id"
dataSource={jobs}
scroll={{x: true}}
rowSelection={{
onSelect: (record) => {
handleOnRowClick(record);
},
selectedRowKeys: [selected],
type: "radio",
}}
onChange={handleTableChange}
// onRow={(record, rowIndex) => {
// return {
// onClick: (event) => {
// handleOnRowClick(record);
// },
// };
// }}
/>
</Card>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(TechAssignedProdJobs);

View File

@@ -0,0 +1,137 @@
import {MinusCircleTwoTone, PlusCircleTwoTone, SyncOutlined,} from "@ant-design/icons";
import {useQuery} from "@apollo/client";
import {Button, Card, Space, Table} from "antd";
import queryString from "query-string";
import React from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {useLocation, useNavigate} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import AlertComponent from "../../components/alert/alert.component";
import PartsDispatchExpander from "../../components/parts-dispatch-expander/parts-dispatch-expander.component";
import {GET_UNACCEPTED_PARTS_DISPATCH} from "../../graphql/parts-dispatch.queries";
import {selectTechnician} from "../../redux/tech/tech.selectors";
import {selectBodyshop} from "../../redux/user/user.selectors";
import {alphaSort} from "../../utils/sorters";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
technician: selectTechnician,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({});
export function TechDispatchedParts({technician, bodyshop}) {
const searchParams = queryString.parse(useLocation().search);
const {page} = searchParams;
const {loading, error, data, refetch} = useQuery(
GET_UNACCEPTED_PARTS_DISPATCH,
{
variables: {
techId: technician.id,
offset: page ? (page - 1) * 25 : 0,
limit: 25,
},
}
);
const {t} = useTranslation();
const history = useNavigate();
if (error) return <AlertComponent message={error.message} type="error"/>;
const parts_dispatch = data?.parts_dispatch;
const columns = [
{
title: t("jobs.fields.ro_number"),
dataIndex: "job.ro_number",
key: "ro_number",
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
render: (text, record) => record.job.ro_number || t("general.labels.na"),
},
{
title: t("jobs.fields.status"),
dataIndex: "status",
key: "status",
sorter: (a, b) => alphaSort(a.status, b.status),
render: (text, record) => {
return record.job.status || t("general.labels.na");
},
},
{
title: t("jobs.fields.vehicle"),
dataIndex: "vehicle",
key: "vehicle",
ellipsis: true,
render: (text, record) => (
<span>{`${record.job.v_model_yr || ""} ${
record.job.v_make_desc || ""
} ${record.job.v_model_desc || ""}`}</span>
),
},
{
title: t("general.labels.actions"),
dataIndex: "actions",
key: "actions",
render: (text, record) => (
<Button onClick={() => {
}}>
{t("timetickets.actions.claimtasks")}
</Button>
),
},
];
const handleTableChange = (pagination, filters, sorter) => {
searchParams.page = pagination.current;
history({search: queryString.stringify(searchParams)});
};
return (
<Card
extra={
<Space wrap>
<Button onClick={() => refetch()}>
<SyncOutlined/>
</Button>
</Space>
}
>
<Table
loading={loading}
pagination={{
pageSize: 25,
current: parseInt(page || 1),
total: data ? data.parts_dispatch_aggregate.aggregate.count : 0,
showSizeChanger: false,
}}
columns={columns}
rowKey="id"
dataSource={parts_dispatch}
scroll={{x: true}}
onChange={handleTableChange}
expandable={{
expandedRowRender: (record) => (
<PartsDispatchExpander dispatch={record}/>
),
rowExpandable: (record) => true,
//expandRowByClick: true,
expandIcon: ({expanded, onExpand, record}) =>
expanded ? (
<MinusCircleTwoTone onClick={(e) => onExpand(record, e)}/>
) : (
<PlusCircleTwoTone onClick={(e) => onExpand(record, e)}/>
),
}}
/>
</Card>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(TechDispatchedParts);

View File

@@ -1,7 +1,6 @@
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import RbacWrapperComponent from "../../components/rbac-wrapper/rbac-wrapper.component";
import TechLookupJobsDrawer from "../../components/tech-lookup-jobs-drawer/tech-lookup-jobs-drawer.component";
import TechLookupJobsList from "../../components/tech-lookup-jobs-list/tech-lookup-jobs-list.component";
export default function TechLookupContainer() {
@@ -15,7 +14,6 @@ export default function TechLookupContainer() {
<div>
<RbacWrapperComponent action="jobs:list-active">
<TechLookupJobsList/>
<TechLookupJobsDrawer/>
</RbacWrapperComponent>
</div>
);

View File

@@ -9,6 +9,7 @@ import ErrorBoundary from "../../components/error-boundary/error-boundary.compon
import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import TechHeader from "../../components/tech-header/tech-header.component";
import TechLookupJobsDrawer from "../../components/tech-lookup-jobs-drawer/tech-lookup-jobs-drawer.component";
import TechSider from "../../components/tech-sider/tech-sider.component";
import UpdateAlert from "../../components/update-alert/update-alert.component";
import {selectTechnician} from "../../redux/tech/tech.selectors";
@@ -39,6 +40,17 @@ const TechJobClock = lazy(() =>
const TechShiftClock = lazy(() =>
import("../tech-shift-clock/tech-shift-clock.component")
);
const TimeTicketModalTask = lazy(() =>
import(
"../../components/time-ticket-task-modal/time-ticket-task-modal.container"
)
);
const TechAssignedProdJobs = lazy(() =>
import("../tech-assigned-prod-jobs/tech-assigned-prod-jobs.component")
);
const TechDispatchedParts = lazy(() =>
import("../tech-dispatched-parts/tech-dispatched-parts.page")
);
const {Content} = Layout;
@@ -69,6 +81,7 @@ export function TechPage({technician}) {
<Layout>
<UpdateAlert/>
<TechHeader/>
<TechLookupJobsDrawer/>
<Content className="tech-content-container">
<ErrorBoundary>
<Suspense
@@ -80,6 +93,7 @@ export function TechPage({technician}) {
<TimeTicketModalContainer/>
<EmailOverlayContainer/>
<PrintCenterModalContainer/>
<TimeTicketModalTask/>
<Routes>
<Route path='/login' element={<TechLogin/>}/>
<Route path='/joblookup' element={<TechLookup/>}/>
@@ -87,6 +101,9 @@ export function TechPage({technician}) {
<Route path='/jobclock' element={<TechJobClock/>}/>
<Route path='/shiftclock' element={<TechShiftClock/>}/>
<Route path='/board' element={<ProductionBoardPage/>}/>
<Route path='/assigned' element={<TechAssignedProdJobs/>}/>
<Route path='/dispatchedparts' element={<TechDispatchedParts/>}/>
</Routes>
</FeatureWrapper>
</Suspense>

View File

@@ -18,6 +18,7 @@ import {QUERY_TIME_TICKETS_IN_RANGE} from "../../graphql/timetickets.queries";
import TimeTicketsAttendanceTable
from "../../components/time-tickets-attendance-table/time-tickets-attendance-table.component";
import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import TimeTicketsCommit from "../../components/time-tickets-commit/time-tickets-commit.component";
const mapStateToProps = createStructuredSelector({});
@@ -73,6 +74,7 @@ export function TimeTicketsContainer({
<Space wrap>
<TimeTicketsAttendanceTable/>
<TimeTicketsPayrollTable/>
<TimeTicketsCommit timetickets={data ? data.timetickets : []}/>
<TimeTicketsDatesSelector/>
</Space>
}

View File

@@ -0,0 +1,40 @@
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import TtApprovalsList from "../../components/tt-approvals-list/tt-approvals-list.container";
import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import {selectBodyshop} from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function TtApprovalsPage({setBreadcrumbs, setSelectedHeader}) {
const {t} = useTranslation();
useEffect(() => {
document.title = t("titles.ttapprovals");
setSelectedHeader("ttapprovals");
setBreadcrumbs([
{
link: "/manage/ttapprovals",
label: t("titles.bc.ttapprovals"),
},
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
return (
<RbacWrapper action="ttapprovals:view">
<TtApprovalsList/>
</RbacWrapper>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(TtApprovalsPage);