Merged in test-beta (pull request #1186)

Test beta
This commit is contained in:
Dave Richer
2024-01-18 20:34:37 +00:00
10 changed files with 224 additions and 94 deletions

View File

@@ -1,15 +1,37 @@
import React from "react";
import { Card } from "antd"; import { Card } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { selectTechnician } from "../../redux/tech/tech.selectors";
export default function JobDetailCardTemplate({ const mapStateToProps = createStructuredSelector({
technician: selectTechnician,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(JobDetailCardTemplate);
export function JobDetailCardTemplate({
loading, loading,
title, title,
extraLink, extraLink,
technician,
...otherProps ...otherProps
}) { }) {
const { t } = useTranslation();
let extra; let extra;
if (extraLink) extra = { extra: <Link to={extraLink}>More</Link> }; if (extraLink && !technician)
extra = {
extra: <Link to={extraLink}>{t("jobs.labels.cards.more")}</Link>,
};
return ( return (
<Card <Card
size="small" size="small"

View File

@@ -15,6 +15,7 @@ import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateTimeFormatter } from "../../utils/DateFormatter"; import { DateTimeFormatter } from "../../utils/DateFormatter";
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import ChatOpenButton from "../chat-open-button/chat-open-button.component"; import ChatOpenButton from "../chat-open-button/chat-open-button.component";
import DataLabel from "../data-label/data-label.component"; import DataLabel from "../data-label/data-label.component";
import JobAltTransportChange from "../job-at-change/job-at-change.component"; import JobAltTransportChange from "../job-at-change/job-at-change.component";
@@ -160,19 +161,35 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
<Card <Card
style={{ height: "100%" }} style={{ height: "100%" }}
title={ title={
<Link to={disabled ? "#" : `/manage/owners/${job.owner.id}`}> disabled ? (
{ownerTitle.length > 0 <>
? ownerTitle {ownerTitle.length > 0
: t("owner.labels.noownerinfo")} ? ownerTitle
</Link> : t("owner.labels.noownerinfo")}
</>
) : (
<Link to={`/manage/owners/${job.owner.id}`}>
{ownerTitle.length > 0
? ownerTitle
: t("owner.labels.noownerinfo")}
</Link>
)
} }
> >
<div> <div>
<DataLabel key="2" label={t("jobs.fields.ownr_ph1")}> <DataLabel key="2" label={t("jobs.fields.ownr_ph1")}>
<ChatOpenButton phone={job.ownr_ph1} jobid={job.id} /> {disabled ? (
<PhoneNumberFormatter>{job.ownr_ph1}</PhoneNumberFormatter>
) : (
<ChatOpenButton phone={job.ownr_ph1} jobid={job.id} />
)}
</DataLabel> </DataLabel>
<DataLabel key="22" label={t("jobs.fields.ownr_ph2")}> <DataLabel key="22" label={t("jobs.fields.ownr_ph2")}>
<ChatOpenButton phone={job.ownr_ph2} jobid={job.id} /> {disabled ? (
<PhoneNumberFormatter>{job.ownr_ph2}</PhoneNumberFormatter>
) : (
<ChatOpenButton phone={job.ownr_ph2} jobid={job.id} />
)}
</DataLabel> </DataLabel>
<DataLabel key="3" label={t("owners.fields.address")}> <DataLabel key="3" label={t("owners.fields.address")}>
{`${job.ownr_addr1 || ""} ${job.ownr_addr2 || ""} ${ {`${job.ownr_addr1 || ""} ${job.ownr_addr2 || ""} ${
@@ -180,7 +197,11 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
} ${job.ownr_st || ""} ${job.ownr_zip || ""}`} } ${job.ownr_st || ""} ${job.ownr_zip || ""}`}
</DataLabel> </DataLabel>
<DataLabel key="4" label={t("owners.fields.ownr_ea")}> <DataLabel key="4" label={t("owners.fields.ownr_ea")}>
{job.ownr_ea || ""} {disabled ? (
<>{job.ownr_ea || ""}</>
) : job.ownr_ea ? (
<a href={`mailto:${job.ownr_ea}`}>{job.ownr_ea}</a>
) : null}
</DataLabel> </DataLabel>
{job.owner?.tax_number && ( {job.owner?.tax_number && (
<DataLabel key="5" label={t("owners.fields.tax_number")}> <DataLabel key="5" label={t("owners.fields.tax_number")}>
@@ -195,17 +216,19 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
style={{ height: "100%" }} style={{ height: "100%" }}
title={ title={
job.vehicle ? ( job.vehicle ? (
<Link disabled ? (
to={ <>
disabled {vehicleTitle.length > 0
? "#" ? vehicleTitle
: job.vehicle && `/manage/vehicles/${job.vehicle.id}` : t("vehicles.labels.novehinfo")}{" "}
} </>
> ) : (
{vehicleTitle.length > 0 <Link to={job.vehicle && `/manage/vehicles/${job.vehicle.id}`}>
? vehicleTitle {vehicleTitle.length > 0
: t("vehicles.labels.novehinfo")} ? vehicleTitle
</Link> : t("vehicles.labels.novehinfo")}
</Link>
)
) : ( ) : (
<span></span> <span></span>
) )
@@ -223,7 +246,9 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
</VehicleVinDisplay> </VehicleVinDisplay>
{bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid ? ( {bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid ? (
job.v_vin?.length !== 17 ? ( job.v_vin?.length !== 17 ? (
<WarningFilled style={{ color: "tomato", marginLeft: ".3rem" }} /> <WarningFilled
style={{ color: "tomato", marginLeft: ".3rem" }}
/>
) : null ) : null
) : null} ) : null}
</DataLabel> </DataLabel>
@@ -231,7 +256,7 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
{job.regie_number || t("general.labels.na")} {job.regie_number || t("general.labels.na")}
</DataLabel> </DataLabel>
<DataLabel label={t("jobs.labels.relatedros")}> <DataLabel label={t("jobs.labels.relatedros")}>
<JobsRelatedRos jobid={job.id} job={job} /> <JobsRelatedRos jobid={job.id} job={job} disabled={disabled} />
</DataLabel> </DataLabel>
{job.vehicle && job.vehicle.notes && ( {job.vehicle && job.vehicle.notes && (
<DataLabel <DataLabel

View File

@@ -2,7 +2,7 @@ import { Space, Tag } from "antd";
import React from "react"; import React from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
export default function JobsRelatedRos({ jobid, job }) { export default function JobsRelatedRos({ jobid, job, disabled }) {
if (!(job && job.vehicle && job.vehicle.jobs)) return null; if (!(job && job.vehicle && job.vehicle.jobs)) return null;
return ( return (
<Space wrap> <Space wrap>
@@ -10,9 +10,15 @@ export default function JobsRelatedRos({ jobid, job }) {
.filter((j) => j.id !== job.id) .filter((j) => j.id !== job.id)
.map((j) => ( .map((j) => (
<Tag key={j.id}> <Tag key={j.id}>
<Link to={`/manage/jobs/${j?.id}`}>{`${j.ro_number || "N/A"}${ {disabled ? (
j.clm_no ? ` | ${j.clm_no}` : "" <>{`${j.ro_number || "N/A"}${j.clm_no ? ` | ${j.clm_no}` : ""}${
}${j.status ? ` | ${j.status}` : ""}`}</Link> j.status ? ` | ${j.status}` : ""
}`}</>
) : (
<Link to={`/manage/jobs/${j?.id}`}>{`${j.ro_number || "N/A"}${
j.clm_no ? ` | ${j.clm_no}` : ""
}${j.status ? ` | ${j.status}` : ""}`}</Link>
)}
</Tag> </Tag>
))} ))}
</Space> </Space>

View File

@@ -76,7 +76,14 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
dataIndex: "ownr", dataIndex: "ownr",
key: "ownr", key: "ownr",
ellipsis: true, ellipsis: true,
render: (text, record) => <OwnerNameDisplay ownerObject={record} />, render: (text, record) =>
technician ? (
<OwnerNameDisplay ownerObject={record} />
) : (
<Link to={`/manage/owners/${record.ownerid}`}>
<OwnerNameDisplay ownerObject={record} />
</Link>
),
sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln), sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
sortOrder: sortOrder:
state.sortedInfo.columnKey === "ownr" && state.sortedInfo.order, state.sortedInfo.columnKey === "ownr" && state.sortedInfo.order,
@@ -93,13 +100,18 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
), ),
sortOrder: sortOrder:
state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order, state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order,
render: (text, record) => ( render: (text, record) =>
<Link to={`/manage/vehicles/${record.vehicleid}`}>{`${ technician ? (
record.v_model_yr || "" <>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
} ${record.v_make_desc || ""} ${record.v_model_desc || ""} ${ record.v_model_desc || ""
record.v_color || "" } ${record.v_color || ""} ${record.plate_no || ""}`}</>
} ${record.plate_no || ""}`}</Link> ) : (
), <Link to={`/manage/vehicles/${record.vehicleid}`}>{`${
record.v_model_yr || ""
} ${record.v_make_desc || ""} ${record.v_model_desc || ""} ${
record.v_color || ""
} ${record.plate_no || ""}`}</Link>
),
}, },
{ {
title: i18n.t("jobs.fields.actual_in"), title: i18n.t("jobs.fields.actual_in"),

View File

@@ -14,12 +14,14 @@ import { selectTechnician } from "../../redux/tech/tech.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter"; import { DateFormatter } from "../../utils/DateFormatter";
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import StartChatButton from "../chat-open-button/chat-open-button.component"; import StartChatButton from "../chat-open-button/chat-open-button.component";
import JobAtChange from "../job-at-change/job-at-change.component"; import JobAtChange from "../job-at-change/job-at-change.component";
import JobDetailCardsDocumentsComponent from "../job-detail-cards/job-detail-cards.documents.component"; import JobDetailCardsDocumentsComponent from "../job-detail-cards/job-detail-cards.documents.component";
import JobDetailCardsNotesComponent from "../job-detail-cards/job-detail-cards.notes.component"; import JobDetailCardsNotesComponent from "../job-detail-cards/job-detail-cards.notes.component";
import JobDetailCardsPartsComponent from "../job-detail-cards/job-detail-cards.parts.component"; import JobDetailCardsPartsComponent from "../job-detail-cards/job-detail-cards.parts.component";
import CardTemplate from "../job-detail-cards/job-detail-cards.template.component";
import JobEmployeeAssignments from "../job-employee-assignments/job-employee-assignments.container"; import JobEmployeeAssignments from "../job-employee-assignments/job-employee-assignments.container";
import ScoreboardAddButton from "../job-scoreboard-add-button/job-scoreboard-add-button.component"; import ScoreboardAddButton from "../job-scoreboard-add-button/job-scoreboard-add-button.component";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
@@ -104,65 +106,85 @@ export function ProductionListDetail({
{error && <AlertComponent error={JSON.stringify(error)} />} {error && <AlertComponent error={JSON.stringify(error)} />}
{!loading && data && ( {!loading && data && (
<div> <div>
<JobEmployeeAssignments job={data.jobs_by_pk} refetch={refetch} /> <Space direction="vertical">
<Descriptions bordered column={1}> <CardTemplate
<Descriptions.Item label={t("jobs.fields.ro_number")}> title={t("jobs.labels.employeeassignments")}
{theJob.ro_number || ""} loading={loading}
</Descriptions.Item> >
<Descriptions.Item label={t("jobs.fields.alt_transport")}> <JobEmployeeAssignments job={data.jobs_by_pk} refetch={refetch} />
<Space> </CardTemplate>
{data.jobs_by_pk.alt_transport || ""} <Descriptions bordered column={1}>
<JobAtChange event={{ job: data.jobs_by_pk }} /> <Descriptions.Item label={t("jobs.fields.ro_number")}>
</Space> {theJob.ro_number || ""}
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label={t("jobs.fields.clm_no")}> <Descriptions.Item label={t("jobs.fields.alt_transport")}>
{theJob.clm_no || ""} <Space>
</Descriptions.Item> {data.jobs_by_pk.alt_transport || ""}
<Descriptions.Item label={t("jobs.fields.ins_co_nm")}> <JobAtChange job={data.jobs_by_pk} />
{theJob.ins_co_nm || ""} </Space>
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label={t("jobs.fields.owner")}> <Descriptions.Item label={t("jobs.fields.clm_no")}>
<OwnerNameDisplay ownerObject={theJob} /> {theJob.clm_no || ""}
<StartChatButton </Descriptions.Item>
phone={data.jobs_by_pk.ownr_ph1} <Descriptions.Item label={t("jobs.fields.ins_co_nm")}>
jobid={data.jobs_by_pk.id} {theJob.ins_co_nm || ""}
/> </Descriptions.Item>
<StartChatButton <Descriptions.Item label={t("jobs.fields.owner")}>
phone={data.jobs_by_pk.ownr_ph2} <Space>
jobid={data.jobs_by_pk.id} <OwnerNameDisplay ownerObject={theJob} />
/> {!technician ? (
</Descriptions.Item> <>
<Descriptions.Item label={t("jobs.fields.vehicle")}> <StartChatButton
{`${theJob.v_model_yr || ""} ${theJob.v_color || ""} ${ phone={data.jobs_by_pk.ownr_ph1}
theJob.v_make_desc || "" jobid={data.jobs_by_pk.id}
} ${theJob.v_model_desc || ""}`} />
</Descriptions.Item> <StartChatButton
<Descriptions.Item label={t("jobs.fields.clm_total")}> phone={data.jobs_by_pk.ownr_ph2}
<CurrencyFormatter>{theJob.clm_total}</CurrencyFormatter> jobid={data.jobs_by_pk.id}
</Descriptions.Item> />
<Descriptions.Item label={t("jobs.fields.actual_in")}> </>
<DateFormatter>{theJob.actual_in}</DateFormatter> ) : (
</Descriptions.Item> <>
<Descriptions.Item label={t("jobs.fields.scheduled_completion")}> <PhoneNumberFormatter>
<DateFormatter>{theJob.scheduled_completion}</DateFormatter> {data.jobs_by_pk.ownr_ph1}
</Descriptions.Item> </PhoneNumberFormatter>
</Descriptions> <PhoneNumberFormatter>
{data.jobs_by_pk.ownr_ph2}
<JobDetailCardsPartsComponent </PhoneNumberFormatter>
loading={loading} </>
data={data ? data.jobs_by_pk : null} )}
/> </Space>
</Descriptions.Item>
<JobDetailCardsNotesComponent <Descriptions.Item label={t("jobs.fields.vehicle")}>
loading={loading} {`${theJob.v_model_yr || ""} ${theJob.v_color || ""} ${
data={data ? data.jobs_by_pk : null} theJob.v_make_desc || ""
/> } ${theJob.v_model_desc || ""}`}
{!bodyshop.uselocalmediaserver && ( </Descriptions.Item>
<JobDetailCardsDocumentsComponent <Descriptions.Item label={t("jobs.fields.clm_total")}>
<CurrencyFormatter>{theJob.clm_total}</CurrencyFormatter>
</Descriptions.Item>
<Descriptions.Item label={t("jobs.fields.actual_in")}>
<DateFormatter>{theJob.actual_in}</DateFormatter>
</Descriptions.Item>
<Descriptions.Item label={t("jobs.fields.scheduled_completion")}>
<DateFormatter>{theJob.scheduled_completion}</DateFormatter>
</Descriptions.Item>
</Descriptions>
<JobDetailCardsPartsComponent
loading={loading} loading={loading}
data={data ? data.jobs_by_pk : null} data={data ? data.jobs_by_pk : null}
/> />
)} <JobDetailCardsNotesComponent
loading={loading}
data={data ? data.jobs_by_pk : null}
/>
{!bodyshop.uselocalmediaserver && (
<JobDetailCardsDocumentsComponent
loading={loading}
data={data ? data.jobs_by_pk : null}
/>
)}
</Space>
</div> </div>
)} )}
</Drawer> </Drawer>

View File

@@ -5,7 +5,7 @@ export const QUERY_ALL_ACTIVE_JOBS_PAGINATED = gql`
$offset: Int $offset: Int
$limit: Int $limit: Int
$order: [jobs_order_by!] $order: [jobs_order_by!]
$statuses: [String!]!, $statuses: [String!]!
$isConverted: Boolean $isConverted: Boolean
) { ) {
jobs( jobs(
@@ -120,7 +120,9 @@ export const QUERY_PARTS_QUEUE = gql`
} }
} }
jobs( jobs(
where: { _and: [{ status: { _in: $statuses }, converted: { _eq: true } }] } where: {
_and: [{ status: { _in: $statuses }, converted: { _eq: true } }]
}
offset: $offset offset: $offset
limit: $limit limit: $limit
order_by: $order order_by: $order
@@ -336,6 +338,7 @@ export const QUERY_JOBS_IN_PRODUCTION = gql`
category category
iouparent iouparent
ro_number ro_number
ownerid
ownr_fn ownr_fn
ownr_ln ownr_ln
ownr_co_nm ownr_co_nm

View File

@@ -1704,6 +1704,7 @@
"estimator": "Estimator", "estimator": "Estimator",
"filehandler": "File Handler", "filehandler": "File Handler",
"insurance": "Insurance Details", "insurance": "Insurance Details",
"more": "More",
"notes": "Notes", "notes": "Notes",
"parts": "Parts", "parts": "Parts",
"totals": "Totals", "totals": "Totals",

View File

@@ -1704,6 +1704,7 @@
"estimator": "Estimador", "estimator": "Estimador",
"filehandler": "File Handler", "filehandler": "File Handler",
"insurance": "detalles del seguro", "insurance": "detalles del seguro",
"more": "Más",
"notes": "Notas", "notes": "Notas",
"parts": "Partes", "parts": "Partes",
"totals": "Totales", "totals": "Totales",

View File

@@ -1704,6 +1704,7 @@
"estimator": "Estimateur", "estimator": "Estimateur",
"filehandler": "Gestionnaire de fichiers", "filehandler": "Gestionnaire de fichiers",
"insurance": "Détails de l'assurance", "insurance": "Détails de l'assurance",
"more": "Plus",
"notes": "Remarques", "notes": "Remarques",
"parts": "les pièces", "parts": "les pièces",
"totals": "Totaux", "totals": "Totaux",

View File

@@ -0,0 +1,37 @@
export const BETA_KEY = 'betaSwitchImex';
export const checkBeta = () => {
const cookie = document.cookie.split('; ').find(row => row.startsWith(BETA_KEY));
return cookie ? cookie.split('=')[1] === 'true' : false;
}
export const setBeta = (value) => {
const domain = window.location.hostname.split('.').slice(-2).join('.');
document.cookie = `${BETA_KEY}=${value}; path=/; domain=.${domain}`;
}
export const handleBeta = () => {
// If the current host name does not start with beta or test, then we don't need to do anything.
if (window.location.hostname.startsWith('localhost')) {
console.log('Not on beta or test, so no need to handle beta.');
return;
}
const isBeta = checkBeta();
const currentHostName = window.location.hostname;
// Beta is enabled, but the current host name does start with beta.
if (isBeta && !currentHostName.startsWith('beta')) {
window.location.href = `${window.location.protocol}//beta.${currentHostName}${window.location.pathname}${window.location.search}${window.location.hash}`;
window.reload();
}
// Beta is not enabled, but the current host name does start with beta.
else if (!isBeta && currentHostName.startsWith('beta')) {
window.location.href = `${window.location.protocol}//${currentHostName.replace('beta.', '')}${window.location.pathname}${window.location.search}${window.location.hash}`;
window.reload();
}
}
export default handleBeta;