- Merge client update into test-beta
Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
@@ -1,15 +1,15 @@
|
||||
import { PrinterFilled } from "@ant-design/icons";
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { Button, Card, Col, Divider, Drawer, Grid, Row, Space } from "antd";
|
||||
import {PrinterFilled} from "@ant-design/icons";
|
||||
import {useQuery} from "@apollo/client";
|
||||
import {Button, Card, Col, Divider, Drawer, Grid, Row, Space} from "antd";
|
||||
import queryString from "query-string";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Link, useHistory, useLocation } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { QUERY_JOB_CARD_DETAILS } from "../../graphql/jobs.queries";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {Link, useLocation, useNavigate} from "react-router-dom";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {QUERY_JOB_CARD_DETAILS} from "../../graphql/jobs.queries";
|
||||
import {setModalContext} from "../../redux/modals/modals.actions";
|
||||
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import JobSyncButton from "../job-sync-button/job-sync-button.component";
|
||||
import JobsDetailHeader from "../jobs-detail-header/jobs-detail-header.component";
|
||||
@@ -23,150 +23,151 @@ import JobDetailCardsPartsComponent from "./job-detail-cards.parts.component";
|
||||
import JobDetailCardsTotalsComponent from "./job-detail-cards.totals.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setPrintCenterContext: (context) =>
|
||||
dispatch(setModalContext({ context: context, modal: "printCenter" })),
|
||||
setPrintCenterContext: (context) =>
|
||||
dispatch(setModalContext({context: context, modal: "printCenter"})),
|
||||
});
|
||||
|
||||
const span = {
|
||||
lg: { span: 24 },
|
||||
xl: { span: 12 },
|
||||
xxl: { span: 8 },
|
||||
lg: {span: 24},
|
||||
xl: {span: 12},
|
||||
xxl: {span: 8},
|
||||
};
|
||||
|
||||
export function JobDetailCards({ bodyshop, setPrintCenterContext }) {
|
||||
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
|
||||
.filter((screen) => !!screen[1])
|
||||
.slice(-1)[0];
|
||||
export function JobDetailCards({bodyshop, setPrintCenterContext}) {
|
||||
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
|
||||
.filter((screen) => !!screen[1])
|
||||
.slice(-1)[0];
|
||||
|
||||
const bpoints = {
|
||||
xs: "100%",
|
||||
sm: "100%",
|
||||
md: "100%",
|
||||
lg: "75%",
|
||||
xl: "75%",
|
||||
xxl: "60%",
|
||||
};
|
||||
const drawerPercentage = selectedBreakpoint
|
||||
? bpoints[selectedBreakpoint[0]]
|
||||
: "100%";
|
||||
const bpoints = {
|
||||
xs: "100%",
|
||||
sm: "100%",
|
||||
md: "100%",
|
||||
lg: "75%",
|
||||
xl: "75%",
|
||||
xxl: "60%",
|
||||
};
|
||||
const drawerPercentage = selectedBreakpoint
|
||||
? bpoints[selectedBreakpoint[0]]
|
||||
: "100%";
|
||||
|
||||
const searchParams = queryString.parse(useLocation().search);
|
||||
const { selected } = searchParams;
|
||||
const history = useHistory();
|
||||
const { loading, error, data, refetch } = useQuery(QUERY_JOB_CARD_DETAILS, {
|
||||
variables: { id: selected },
|
||||
skip: !selected,
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
});
|
||||
|
||||
const { t } = useTranslation();
|
||||
const handleDrawerClose = () => {
|
||||
delete searchParams.selected;
|
||||
history.push({
|
||||
search: queryString.stringify({
|
||||
...searchParams,
|
||||
}),
|
||||
const searchParams = queryString.parse(useLocation().search);
|
||||
const {selected} = searchParams;
|
||||
const history = useNavigate();
|
||||
const {loading, error, data, refetch} = useQuery(QUERY_JOB_CARD_DETAILS, {
|
||||
variables: {id: selected},
|
||||
skip: !selected,
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
visible={!!selected}
|
||||
destroyOnClose
|
||||
width={drawerPercentage}
|
||||
placement="right"
|
||||
onClose={handleDrawerClose}
|
||||
>
|
||||
{loading ? <LoadingSpinner /> : null}
|
||||
{error ? <AlertComponent message={error.message} type="error" /> : null}
|
||||
{data ? (
|
||||
<Card
|
||||
title={
|
||||
<Link to={`/manage/jobs/${data.jobs_by_pk.id}`}>
|
||||
{data.jobs_by_pk.ro_number || t("general.labels.na")}
|
||||
</Link>
|
||||
}
|
||||
extra={
|
||||
<Space wrap>
|
||||
<JobSyncButton job={data.jobs_by_pk} />
|
||||
const {t} = useTranslation();
|
||||
const handleDrawerClose = () => {
|
||||
delete searchParams.selected;
|
||||
history({
|
||||
search: queryString.stringify({
|
||||
...searchParams,
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
<Button
|
||||
onClick={() => {
|
||||
setPrintCenterContext({
|
||||
actions: { refetch: refetch },
|
||||
context: {
|
||||
id: data.jobs_by_pk.id,
|
||||
job: data.jobs_by_pk,
|
||||
type: "job",
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
<PrinterFilled />
|
||||
{t("jobs.actions.printCenter")}
|
||||
</Button>
|
||||
<Link to={`/manage/jobs/${data.jobs_by_pk.id}?tab=repairdata`}>
|
||||
<Button>{t("parts.actions.order")}</Button>
|
||||
</Link>
|
||||
</Space>
|
||||
}
|
||||
return (
|
||||
<Drawer
|
||||
open={!!selected}
|
||||
destroyOnClose
|
||||
width={drawerPercentage}
|
||||
placement="right"
|
||||
onClose={handleDrawerClose}
|
||||
>
|
||||
<JobsDetailHeader job={data ? data.jobs_by_pk : null} />
|
||||
<Divider type="horizontal" />
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col {...span}>
|
||||
<JobDetailCardsInsuranceComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...span}>
|
||||
<JobDetailCardsTotalsComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...span}>
|
||||
<JobDetailCardsDatesComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...span}>
|
||||
<JobDetailCardsNotesComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Col>
|
||||
{!bodyshop.uselocalmediaserver && (
|
||||
<Col {...span}>
|
||||
<JobDetailCardsDocumentsComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Col>
|
||||
)}
|
||||
<Col {...span}>
|
||||
<JobDetailCardsDamageComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<JobDetailCardsPartsComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
) : null}
|
||||
</Drawer>
|
||||
);
|
||||
{loading ? <LoadingSpinner/> : null}
|
||||
{error ? <AlertComponent message={error.message} type="error"/> : null}
|
||||
{data ? (
|
||||
<Card
|
||||
title={
|
||||
<Link to={`/manage/jobs/${data.jobs_by_pk.id}`}>
|
||||
{data.jobs_by_pk.ro_number || t("general.labels.na")}
|
||||
</Link>
|
||||
}
|
||||
extra={
|
||||
<Space wrap>
|
||||
<JobSyncButton job={data.jobs_by_pk}/>
|
||||
|
||||
<Button
|
||||
onClick={() => {
|
||||
setPrintCenterContext({
|
||||
actions: {refetch: refetch},
|
||||
context: {
|
||||
id: data.jobs_by_pk.id,
|
||||
job: data.jobs_by_pk,
|
||||
type: "job",
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
<PrinterFilled/>
|
||||
{t("jobs.actions.printCenter")}
|
||||
</Button>
|
||||
<Link to={`/manage/jobs/${data.jobs_by_pk.id}?tab=repairdata`}>
|
||||
<Button>{t("parts.actions.order")}</Button>
|
||||
</Link>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<JobsDetailHeader job={data ? data.jobs_by_pk : null}/>
|
||||
<Divider type="horizontal"/>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col {...span}>
|
||||
<JobDetailCardsInsuranceComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...span}>
|
||||
<JobDetailCardsTotalsComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...span}>
|
||||
<JobDetailCardsDatesComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...span}>
|
||||
<JobDetailCardsNotesComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Col>
|
||||
{!bodyshop.uselocalmediaserver && (
|
||||
<Col {...span}>
|
||||
<JobDetailCardsDocumentsComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Col>
|
||||
)}
|
||||
<Col {...span}>
|
||||
<JobDetailCardsDamageComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<JobDetailCardsPartsComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
) : null}
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(JobDetailCards);
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import CardTemplate from "./job-detail-cards.template.component";
|
||||
import Car from "../job-damage-visual/job-damage-visual.component";
|
||||
|
||||
export default function JobDetailCardsDamageComponent({ loading, data }) {
|
||||
const { t } = useTranslation();
|
||||
const { area_of_damage } = data;
|
||||
return (
|
||||
<CardTemplate loading={loading} title={t("jobs.labels.cards.damage")}>
|
||||
{area_of_damage ? (
|
||||
<Car dmg1={area_of_damage.impact1} dmg2={area_of_damage.impact2} />
|
||||
) : (
|
||||
t("jobs.errors.nodamage")
|
||||
)}
|
||||
</CardTemplate>
|
||||
);
|
||||
export default function JobDetailCardsDamageComponent({loading, data}) {
|
||||
const {t} = useTranslation();
|
||||
const {area_of_damage} = data;
|
||||
return (
|
||||
<CardTemplate loading={loading} title={t("jobs.labels.cards.damage")}>
|
||||
{area_of_damage ? (
|
||||
<Car dmg1={area_of_damage.impact1} dmg2={area_of_damage.impact2}/>
|
||||
) : (
|
||||
t("jobs.errors.nodamage")
|
||||
)}
|
||||
</CardTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,119 +1,188 @@
|
||||
import { Timeline } from "antd";
|
||||
import {Timeline} from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {DateTimeFormatter} from "../../utils/DateFormatter";
|
||||
import CardTemplate from "./job-detail-cards.template.component";
|
||||
|
||||
export default function JobDetailCardsDatesComponent({ loading, data }) {
|
||||
const { t } = useTranslation();
|
||||
export default function JobDetailCardsDatesComponent({loading, data}) {
|
||||
const {t} = useTranslation();
|
||||
|
||||
return (
|
||||
<CardTemplate loading={loading} title={t("jobs.labels.cards.dates")}>
|
||||
{data ? (
|
||||
<Timeline>
|
||||
{!(
|
||||
data.actual_in ||
|
||||
data.scheduled_completion ||
|
||||
data.scheduled_in ||
|
||||
data.actual_completion ||
|
||||
data.scheduled_delivery ||
|
||||
data.actual_delivery ||
|
||||
data.date_estimated ||
|
||||
data.date_open ||
|
||||
data.date_scheduled ||
|
||||
data.date_invoiced ||
|
||||
data.date_exported
|
||||
) ? (
|
||||
<div>{t("jobs.errors.nodates")}</div>
|
||||
) : null}
|
||||
{data.date_last_contacted ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.date_last_contacted")}: </label>
|
||||
<DateTimeFormatter>{data.date_last_contacted}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
{data.date_open ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.date_open")}: </label>
|
||||
<DateTimeFormatter>{data.date_open}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.date_estimated ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.date_estimated")}: </label>
|
||||
<DateTimeFormatter>{data.date_estimated}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.date_scheduled ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.date_scheduled")}: </label>
|
||||
<DateTimeFormatter>{data.date_scheduled}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.scheduled_in ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.scheduled_in")}: </label>
|
||||
<DateTimeFormatter>{data.scheduled_in}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
{data.actual_in ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.actual_in")}: </label>
|
||||
<DateTimeFormatter>{data.actual_in}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
{data.date_repairstarted ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.date_repairstarted")}: </label>
|
||||
<DateTimeFormatter>{data.date_repairstarted}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
{data.scheduled_completion ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.scheduled_completion")}: </label>
|
||||
<DateTimeFormatter>{data.scheduled_completion}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.actual_completion ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.actual_completion")}: </label>
|
||||
<DateTimeFormatter>{data.actual_completion}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.scheduled_delivery ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.scheduled_delivery")}: </label>
|
||||
<DateTimeFormatter>{data.scheduled_delivery}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.actual_delivery ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.actual_delivery")}: </label>
|
||||
<DateTimeFormatter>{data.actual_delivery}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.date_invoiced ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.date_invoiced")}: </label>
|
||||
<DateTimeFormatter>{data.date_invoiced}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.date_exported ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.date_exported")}: </label>
|
||||
<DateTimeFormatter>{data.date_exported}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
</Timeline>
|
||||
) : null}
|
||||
</CardTemplate>
|
||||
);
|
||||
return (
|
||||
<CardTemplate loading={loading} title={t("jobs.labels.cards.dates")}>
|
||||
{data ? (
|
||||
<Timeline
|
||||
items={[
|
||||
...(data.date_last_contacted
|
||||
? [
|
||||
{
|
||||
key: "date_last_contacted",
|
||||
children: (
|
||||
<>
|
||||
<label>{t("jobs.fields.date_last_contacted")}: </label>
|
||||
<DateTimeFormatter>{data.date_last_contacted}</DateTimeFormatter>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(data.date_open
|
||||
? [
|
||||
{
|
||||
key: "date_open",
|
||||
children: (
|
||||
<>
|
||||
<label>{t("jobs.fields.date_open")}: </label>
|
||||
<DateTimeFormatter>{data.date_open}</DateTimeFormatter>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(data.date_estimated
|
||||
? [
|
||||
{
|
||||
key: "date_estimated",
|
||||
children: (
|
||||
<>
|
||||
<label>{t("jobs.fields.date_estimated")}: </label>
|
||||
<DateTimeFormatter>{data.date_estimated}</DateTimeFormatter>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(data.date_scheduled
|
||||
? [
|
||||
{
|
||||
key: "date_scheduled",
|
||||
children: (
|
||||
<>
|
||||
<label>{t("jobs.fields.date_scheduled")}: </label>
|
||||
<DateTimeFormatter>{data.date_scheduled}</DateTimeFormatter>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(data.scheduled_in
|
||||
? [
|
||||
{
|
||||
key: "scheduled_in",
|
||||
children: (
|
||||
<>
|
||||
<label>{t("jobs.fields.scheduled_in")}: </label>
|
||||
<DateTimeFormatter>{data.scheduled_in}</DateTimeFormatter>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(data.actual_in
|
||||
? [
|
||||
{
|
||||
key: "actual_in",
|
||||
children: (
|
||||
<>
|
||||
<label>{t("jobs.fields.actual_in")}: </label>
|
||||
<DateTimeFormatter>{data.actual_in}</DateTimeFormatter>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(data.date_repairstarted
|
||||
? [
|
||||
{
|
||||
key: "date_repairstarted",
|
||||
children: (
|
||||
<>
|
||||
<label>{t("jobs.fields.date_repairstarted")}: </label>
|
||||
<DateTimeFormatter>{data.date_repairstarted}</DateTimeFormatter>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(data.scheduled_completion
|
||||
? [
|
||||
{
|
||||
key: "scheduled_completion",
|
||||
children: (
|
||||
<>
|
||||
<label>{t("jobs.fields.scheduled_completion")}: </label>
|
||||
<DateTimeFormatter>{data.scheduled_completion}</DateTimeFormatter>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(data.actual_completion
|
||||
? [
|
||||
{
|
||||
key: "actual_completion",
|
||||
children: (
|
||||
<>
|
||||
<label>{t("jobs.fields.actual_completion")}: </label>
|
||||
<DateTimeFormatter>{data.actual_completion}</DateTimeFormatter>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(data.scheduled_delivery
|
||||
? [
|
||||
{
|
||||
key: "scheduled_delivery",
|
||||
children: (
|
||||
<>
|
||||
<label>{t("jobs.fields.scheduled_delivery")}: </label>
|
||||
<DateTimeFormatter>{data.scheduled_delivery}</DateTimeFormatter>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(data.actual_delivery
|
||||
? [
|
||||
{
|
||||
key: "actual_delivery",
|
||||
children: (
|
||||
<>
|
||||
<label>{t("jobs.fields.actual_delivery")}: </label>
|
||||
<DateTimeFormatter>{data.actual_delivery}</DateTimeFormatter>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(data.date_invoiced
|
||||
? [
|
||||
{
|
||||
key: "date_invoiced",
|
||||
children: (
|
||||
<>
|
||||
<label>{t("jobs.fields.date_invoiced")}: </label>
|
||||
<DateTimeFormatter>{data.date_invoiced}</DateTimeFormatter>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(data.date_exported
|
||||
? [
|
||||
{
|
||||
key: "date_exported",
|
||||
children: (
|
||||
<>
|
||||
<label>{t("jobs.fields.date_exported")}: </label>
|
||||
<DateTimeFormatter>{data.date_exported}</DateTimeFormatter>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
]}
|
||||
/>) : null}
|
||||
</CardTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,33 +1,34 @@
|
||||
import { Carousel } from "antd";
|
||||
import {Carousel} from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { GenerateThumbUrl } from "../jobs-documents-gallery/job-documents.utility";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {GenerateThumbUrl} from "../jobs-documents-gallery/job-documents.utility";
|
||||
import CardTemplate from "./job-detail-cards.template.component";
|
||||
export default function JobDetailCardsDocumentsComponent({ loading, data }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!data)
|
||||
export default function JobDetailCardsDocumentsComponent({loading, data}) {
|
||||
const {t} = useTranslation();
|
||||
|
||||
if (!data)
|
||||
return (
|
||||
<CardTemplate loading={loading} title={t("jobs.labels.cards.documents")}>
|
||||
null
|
||||
</CardTemplate>
|
||||
);
|
||||
|
||||
return (
|
||||
<CardTemplate loading={loading} title={t("jobs.labels.cards.documents")}>
|
||||
null
|
||||
</CardTemplate>
|
||||
<CardTemplate
|
||||
loading={loading}
|
||||
title={t("jobs.labels.cards.documents")}
|
||||
extraLink={`/manage/jobs/${data.id}?tab=documents`}
|
||||
>
|
||||
{data.documents.length > 0 ? (
|
||||
<Carousel autoplay>
|
||||
{data.documents.map((item) => (
|
||||
<img key={item.id} src={GenerateThumbUrl(item)} alt={item.name}/>
|
||||
))}
|
||||
</Carousel>
|
||||
) : (
|
||||
<div>{t("documents.errors.nodocuments")}</div>
|
||||
)}
|
||||
</CardTemplate>
|
||||
);
|
||||
|
||||
return (
|
||||
<CardTemplate
|
||||
loading={loading}
|
||||
title={t("jobs.labels.cards.documents")}
|
||||
extraLink={`/manage/jobs/${data.id}?tab=documents`}
|
||||
>
|
||||
{data.documents.length > 0 ? (
|
||||
<Carousel autoplay>
|
||||
{data.documents.map((item) => (
|
||||
<img key={item.id} src={GenerateThumbUrl(item)} alt={item.name} />
|
||||
))}
|
||||
</Carousel>
|
||||
) : (
|
||||
<div>{t("documents.errors.nodocuments")}</div>
|
||||
)}
|
||||
</CardTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import DataLabel from "../data-label/data-label.component";
|
||||
import CardTemplate from "./job-detail-cards.template.component";
|
||||
export default function JobDetailCardsInsuranceComponent({ loading, data }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<CardTemplate loading={loading} title={t("jobs.labels.cards.insurance")}>
|
||||
{data ? (
|
||||
<span>
|
||||
export default function JobDetailCardsInsuranceComponent({loading, data}) {
|
||||
const {t} = useTranslation();
|
||||
|
||||
return (
|
||||
<CardTemplate loading={loading} title={t("jobs.labels.cards.insurance")}>
|
||||
{data ? (
|
||||
<span>
|
||||
<DataLabel label={t("jobs.fields.ins_co_nm")} hideIfNull>
|
||||
{data.ins_co_nm}
|
||||
</DataLabel>
|
||||
@@ -16,29 +17,29 @@ export default function JobDetailCardsInsuranceComponent({ loading, data }) {
|
||||
{data.clm_no}
|
||||
</DataLabel>
|
||||
<DataLabel
|
||||
label={t("jobs.labels.cards.filehandler")}
|
||||
visible={data.ins_ct_fn && data.ins_ct_ln}>
|
||||
label={t("jobs.labels.cards.filehandler")}
|
||||
open={data.ins_ct_fn && data.ins_ct_ln}>
|
||||
{data.ins_ea ? (
|
||||
<a href={`mailto:${data.ins_ea}`}>
|
||||
<div>{`${data.ins_ct_fn || ""} ${data.ins_ct_ln || ""}`}</div>
|
||||
</a>
|
||||
<a href={`mailto:${data.ins_ea}`}>
|
||||
<div>{`${data.ins_ct_fn || ""} ${data.ins_ct_ln || ""}`}</div>
|
||||
</a>
|
||||
) : (
|
||||
<div>{`${data.ins_ct_fn || ""} ${data.ins_ct_ln || ""}`}</div>
|
||||
<div>{`${data.ins_ct_fn || ""} ${data.ins_ct_ln || ""}`}</div>
|
||||
)}
|
||||
</DataLabel>
|
||||
<DataLabel
|
||||
label={t("jobs.labels.cards.estimator")}
|
||||
visible={data.est_ct_fn && data.est_ct_ln}>
|
||||
label={t("jobs.labels.cards.estimator")}
|
||||
open={data.est_ct_fn && data.est_ct_ln}>
|
||||
{data.ins_ea ? (
|
||||
<a href={`mailto:${data.est_ea}`}>
|
||||
<div>{`${data.est_ct_fn || ""} ${data.est_ct_ln || ""}`}</div>
|
||||
</a>
|
||||
<a href={`mailto:${data.est_ea}`}>
|
||||
<div>{`${data.est_ct_fn || ""} ${data.est_ct_ln || ""}`}</div>
|
||||
</a>
|
||||
) : (
|
||||
<div>{`${data.est_ct_fn || ""} ${data.est_ct_ln || ""}`}</div>
|
||||
<div>{`${data.est_ct_fn || ""} ${data.est_ct_ln || ""}`}</div>
|
||||
)}
|
||||
</DataLabel>
|
||||
</span>
|
||||
) : null}
|
||||
</CardTemplate>
|
||||
);
|
||||
) : null}
|
||||
</CardTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import { List } from "antd";
|
||||
import {
|
||||
WarningFilled,
|
||||
EyeInvisibleFilled,
|
||||
AuditOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import {List} from "antd";
|
||||
import {AuditOutlined, EyeInvisibleFilled, WarningFilled,} from "@ant-design/icons";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import CardTemplate from "./job-detail-cards.template.component";
|
||||
import styled from "styled-components";
|
||||
|
||||
@@ -14,33 +10,33 @@ const Container = styled.div`
|
||||
overflow-y: auto;
|
||||
`;
|
||||
|
||||
export default function JobDetailCardsNotesComponent({ loading, data }) {
|
||||
const { t } = useTranslation();
|
||||
export default function JobDetailCardsNotesComponent({loading, data}) {
|
||||
const {t} = useTranslation();
|
||||
|
||||
return (
|
||||
<CardTemplate
|
||||
loading={loading}
|
||||
title={t("jobs.labels.cards.notes")}
|
||||
extraLink={`/manage/jobs/${data.id}?tab=notes`}
|
||||
>
|
||||
{data ? (
|
||||
<Container>
|
||||
<List
|
||||
bordered
|
||||
dataSource={data.notes}
|
||||
renderItem={(item) => (
|
||||
<List.Item style={{ whiteSpace: "pre-line" }}>
|
||||
{item.critical ? (
|
||||
<EyeInvisibleFilled style={{ margin: 4, color: "red" }} />
|
||||
) : null}
|
||||
{item.private ? <WarningFilled style={{ margin: 4 }} /> : null}
|
||||
{item.audit ? <AuditOutlined style={{ margin: 4 }} /> : null}
|
||||
{item.text}
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</Container>
|
||||
) : null}
|
||||
</CardTemplate>
|
||||
);
|
||||
return (
|
||||
<CardTemplate
|
||||
loading={loading}
|
||||
title={t("jobs.labels.cards.notes")}
|
||||
extraLink={`/manage/jobs/${data.id}?tab=notes`}
|
||||
>
|
||||
{data ? (
|
||||
<Container>
|
||||
<List
|
||||
bordered
|
||||
dataSource={data.notes}
|
||||
renderItem={(item) => (
|
||||
<List.Item style={{whiteSpace: "pre-line"}}>
|
||||
{item.critical ? (
|
||||
<EyeInvisibleFilled style={{margin: 4, color: "red"}}/>
|
||||
) : null}
|
||||
{item.private ? <WarningFilled style={{margin: 4}}/> : null}
|
||||
{item.audit ? <AuditOutlined style={{margin: 4}}/> : null}
|
||||
{item.text}
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</Container>
|
||||
) : null}
|
||||
</CardTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,120 +1,121 @@
|
||||
import { Table } from "antd";
|
||||
import {Table} from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import JobLineNotePopup from "../job-line-note-popup/job-line-note-popup.component";
|
||||
import PartsStatusPie from "../parts-status-pie/parts-status-pie.component";
|
||||
import CardTemplate from "./job-detail-cards.template.component";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import { onlyUnique } from "../../utils/arrayHelper";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
import {connect} from "react-redux";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {selectJobReadOnly} from "../../redux/application/application.selectors";
|
||||
import {onlyUnique} from "../../utils/arrayHelper";
|
||||
import {alphaSort} from "../../utils/sorters";
|
||||
import JobLineLocationPopup from "../job-line-location-popup/job-line-location-popup.component";
|
||||
import JobLineStatusPopup from "../job-line-status-popup/job-line-status-popup.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
jobRO: selectJobReadOnly,
|
||||
//currentUser: selectCurrentUser
|
||||
jobRO: selectJobReadOnly,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(JobDetailCardsPartsComponent);
|
||||
|
||||
export function JobDetailCardsPartsComponent({ loading, data, jobRO }) {
|
||||
const { t } = useTranslation();
|
||||
const { joblines_status } = data;
|
||||
export function JobDetailCardsPartsComponent({loading, data, jobRO}) {
|
||||
const {t} = useTranslation();
|
||||
const {joblines_status} = data;
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t("joblines.fields.line_desc"),
|
||||
dataIndex: "line_desc",
|
||||
fixed: "left",
|
||||
key: "line_desc",
|
||||
sorter: (a, b) => alphaSort(a.line_desc, b.line_desc),
|
||||
onCell: (record) => ({
|
||||
className: record.manual_line && "job-line-manual",
|
||||
style: {
|
||||
...(record.critical ? { boxShadow: " -.5em 0 0 #FFC107" } : {}),
|
||||
const columns = [
|
||||
{
|
||||
title: t("joblines.fields.line_desc"),
|
||||
dataIndex: "line_desc",
|
||||
fixed: "left",
|
||||
key: "line_desc",
|
||||
sorter: (a, b) => alphaSort(a.line_desc, b.line_desc),
|
||||
onCell: (record) => ({
|
||||
className: record.manual_line && "job-line-manual",
|
||||
style: {
|
||||
...(record.critical ? {boxShadow: " -.5em 0 0 #FFC107"} : {}),
|
||||
},
|
||||
}),
|
||||
width: "30%",
|
||||
ellipsis: true,
|
||||
},
|
||||
}),
|
||||
width: "30%",
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.part_type"),
|
||||
dataIndex: "part_type",
|
||||
key: "part_type",
|
||||
width: "15%",
|
||||
sorter: (a, b) =>
|
||||
alphaSort(
|
||||
t(`joblines.fields.part_types.${a.part_type}`),
|
||||
t(`joblines.fields.part_types.${b.part_type}`)
|
||||
),
|
||||
render: (text, record) =>
|
||||
record.part_type
|
||||
? t(`joblines.fields.part_types.${record.part_type}`)
|
||||
: null,
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.part_qty"),
|
||||
dataIndex: "part_qty",
|
||||
key: "part_qty",
|
||||
width: "10%",
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.notes"),
|
||||
dataIndex: "notes",
|
||||
key: "notes",
|
||||
render: (text, record) => (
|
||||
<JobLineNotePopup disabled={jobRO} jobline={record} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.location"),
|
||||
dataIndex: "location",
|
||||
key: "location",
|
||||
sorter: (a, b) => alphaSort(a.location, b.location),
|
||||
render: (text, record) => (
|
||||
<JobLineLocationPopup jobline={record} disabled={jobRO} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.status"),
|
||||
dataIndex: "status",
|
||||
key: "status",
|
||||
sorter: (a, b) => alphaSort(a.status, b.status),
|
||||
filters:
|
||||
(data &&
|
||||
data.joblines
|
||||
?.map((l) => l.status)
|
||||
.filter(onlyUnique)
|
||||
.map((s) => {
|
||||
return {
|
||||
text: s || "No Status*",
|
||||
value: [s],
|
||||
};
|
||||
})) ||
|
||||
[],
|
||||
onFilter: (value, record) => value.includes(record.status),
|
||||
render: (text, record) => (
|
||||
<JobLineStatusPopup jobline={record} disabled={jobRO} />
|
||||
),
|
||||
},
|
||||
];
|
||||
return (
|
||||
<div>
|
||||
<CardTemplate loading={loading} title={t("jobs.labels.cards.parts")}>
|
||||
<PartsStatusPie joblines_status={joblines_status} />
|
||||
<Table
|
||||
key="id"
|
||||
columns={columns}
|
||||
dataSource={data ? data.joblines : []}
|
||||
/>
|
||||
</CardTemplate>
|
||||
</div>
|
||||
);
|
||||
{
|
||||
title: t("joblines.fields.part_type"),
|
||||
dataIndex: "part_type",
|
||||
key: "part_type",
|
||||
width: "15%",
|
||||
sorter: (a, b) =>
|
||||
alphaSort(
|
||||
t(`joblines.fields.part_types.${a.part_type}`),
|
||||
t(`joblines.fields.part_types.${b.part_type}`)
|
||||
),
|
||||
render: (text, record) =>
|
||||
record.part_type
|
||||
? t(`joblines.fields.part_types.${record.part_type}`)
|
||||
: null,
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.part_qty"),
|
||||
dataIndex: "part_qty",
|
||||
key: "part_qty",
|
||||
width: "10%",
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.notes"),
|
||||
dataIndex: "notes",
|
||||
key: "notes",
|
||||
render: (text, record) => (
|
||||
<JobLineNotePopup disabled={jobRO} jobline={record}/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.location"),
|
||||
dataIndex: "location",
|
||||
key: "location",
|
||||
sorter: (a, b) => alphaSort(a.location, b.location),
|
||||
render: (text, record) => (
|
||||
<JobLineLocationPopup jobline={record} disabled={jobRO}/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.status"),
|
||||
dataIndex: "status",
|
||||
key: "status",
|
||||
sorter: (a, b) => alphaSort(a.status, b.status),
|
||||
filters:
|
||||
(data &&
|
||||
data.joblines
|
||||
?.map((l) => l.status)
|
||||
.filter(onlyUnique)
|
||||
.map((s) => {
|
||||
return {
|
||||
text: s || "No Status*",
|
||||
value: [s],
|
||||
};
|
||||
})) ||
|
||||
[],
|
||||
onFilter: (value, record) => value.includes(record.status),
|
||||
render: (text, record) => (
|
||||
<JobLineStatusPopup jobline={record} disabled={jobRO}/>
|
||||
),
|
||||
},
|
||||
];
|
||||
return (
|
||||
<div>
|
||||
<CardTemplate loading={loading} title={t("jobs.labels.cards.parts")}>
|
||||
<PartsStatusPie joblines_status={joblines_status}/>
|
||||
<Table
|
||||
key="id"
|
||||
columns={columns}
|
||||
dataSource={data ? data.joblines : []}
|
||||
/>
|
||||
</CardTemplate>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,25 +1,47 @@
|
||||
import {Card} from "antd";
|
||||
import React from "react";
|
||||
import { Card } from "antd";
|
||||
import { Link } from "react-router-dom";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {Link} from "react-router-dom";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {selectTechnician} from "../../redux/tech/tech.selectors";
|
||||
|
||||
export default function JobDetailCardTemplate({
|
||||
loading,
|
||||
title,
|
||||
extraLink,
|
||||
...otherProps
|
||||
}) {
|
||||
let extra;
|
||||
if (extraLink) extra = { extra: <Link to={extraLink}>More</Link> };
|
||||
return (
|
||||
<Card
|
||||
size="small"
|
||||
className="job-card"
|
||||
title={title}
|
||||
loading={loading}
|
||||
style={{ height: "100%" }}
|
||||
{...extra}
|
||||
>
|
||||
{otherProps.children}
|
||||
</Card>
|
||||
);
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
technician: selectTechnician,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(JobDetailCardTemplate);
|
||||
|
||||
export function JobDetailCardTemplate({
|
||||
loading,
|
||||
title,
|
||||
extraLink,
|
||||
technician,
|
||||
...otherProps
|
||||
}) {
|
||||
const {t} = useTranslation();
|
||||
|
||||
let extra;
|
||||
if (extraLink && !technician)
|
||||
extra = {
|
||||
extra: <Link to={extraLink}>{t("jobs.labels.cards.more")}</Link>,
|
||||
};
|
||||
return (
|
||||
<Card
|
||||
size="small"
|
||||
className="job-card"
|
||||
title={title}
|
||||
loading={loading}
|
||||
style={{height: "100%"}}
|
||||
{...extra}
|
||||
>
|
||||
{otherProps.children}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,37 +1,37 @@
|
||||
import { Statistic } from "antd";
|
||||
import {Statistic} from "antd";
|
||||
import Dinero from "dinero.js";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import CardTemplate from "./job-detail-cards.template.component";
|
||||
|
||||
export default function JobDetailCardsTotalsComponent({ loading, data }) {
|
||||
const { t } = useTranslation();
|
||||
export default function JobDetailCardsTotalsComponent({loading, data}) {
|
||||
const {t} = useTranslation();
|
||||
|
||||
return (
|
||||
<CardTemplate loading={loading} title={t("jobs.labels.cards.totals")}>
|
||||
{data.job_totals ? (
|
||||
<div className="imex-flex-row imex-flex-row__flex-space-around">
|
||||
<Statistic
|
||||
className="imex-flex-row__margin-large"
|
||||
title={t("jobs.labels.total_repairs")}
|
||||
value={Dinero(data.job_totals.totals.total_repairs).toFormat()}
|
||||
/>
|
||||
<Statistic
|
||||
className="imex-flex-row__margin-large"
|
||||
title={t("jobs.fields.ded_amt")}
|
||||
value={Dinero({
|
||||
amount: Math.round((data.ded_amt || 0) * 100),
|
||||
}).toFormat()}
|
||||
/>
|
||||
<Statistic
|
||||
className="imex-flex-row__margin-large"
|
||||
title={t("jobs.labels.net_repairs")}
|
||||
value={Dinero(data.job_totals.totals.net_repairs).toFormat()}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
t("jobs.errors.nofinancial")
|
||||
)}
|
||||
</CardTemplate>
|
||||
);
|
||||
return (
|
||||
<CardTemplate loading={loading} title={t("jobs.labels.cards.totals")}>
|
||||
{data.job_totals ? (
|
||||
<div className="imex-flex-row imex-flex-row__flex-space-around">
|
||||
<Statistic
|
||||
className="imex-flex-row__margin-large"
|
||||
title={t("jobs.labels.total_repairs")}
|
||||
value={Dinero(data.job_totals.totals.total_repairs).toFormat()}
|
||||
/>
|
||||
<Statistic
|
||||
className="imex-flex-row__margin-large"
|
||||
title={t("jobs.fields.ded_amt")}
|
||||
value={Dinero({
|
||||
amount: Math.round((data.ded_amt || 0) * 100),
|
||||
}).toFormat()}
|
||||
/>
|
||||
<Statistic
|
||||
className="imex-flex-row__margin-large"
|
||||
title={t("jobs.labels.net_repairs")}
|
||||
value={Dinero(data.job_totals.totals.net_repairs).toFormat()}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
t("jobs.errors.nofinancial")
|
||||
)}
|
||||
</CardTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import CardTemplate from "./job-detail-cards.template.component";
|
||||
|
||||
export default function JobDetailCardsVehicleComponent({ loading, data }) {
|
||||
const { t } = useTranslation();
|
||||
export default function JobDetailCardsVehicleComponent({loading, data}) {
|
||||
const {t} = useTranslation();
|
||||
|
||||
return (
|
||||
<CardTemplate
|
||||
loading={loading}
|
||||
title={t("jobs.labels.cards.vehicle")}
|
||||
extraLink={data.vehicle ? `/manage/vehicles/${data.vehicle.id}` : null}
|
||||
>
|
||||
{data ? (
|
||||
<span>
|
||||
return (
|
||||
<CardTemplate
|
||||
loading={loading}
|
||||
title={t("jobs.labels.cards.vehicle")}
|
||||
extraLink={data.vehicle ? `/manage/vehicles/${data.vehicle.id}` : null}
|
||||
>
|
||||
{data ? (
|
||||
<span>
|
||||
{` ${data.vehicle?.v_model_yr || t("general.labels.na")} ${
|
||||
data.vehicle?.v_make_desc || t("general.labels.na")
|
||||
data.vehicle?.v_make_desc || t("general.labels.na")
|
||||
} ${data.vehicle?.v_model_desc || t("general.labels.na")}`}
|
||||
</span>
|
||||
) : null}
|
||||
</CardTemplate>
|
||||
);
|
||||
) : null}
|
||||
</CardTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user