BOD-34 Basics of print center => redux setup, container/base component.

This commit is contained in:
Patrick Fic
2020-04-28 09:04:47 -07:00
parent 0e12ae35c9
commit b47767e86c
15 changed files with 335 additions and 79 deletions

View File

@@ -11357,6 +11357,37 @@
</folder_node> </folder_node>
</children> </children>
</folder_node> </folder_node>
<folder_node>
<name>printcenter</name>
<children>
<folder_node>
<name>labels</name>
<children>
<concept_node>
<name>title</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
</children>
</folder_node>
<folder_node> <folder_node>
<name>production</name> <name>production</name>
<children> <children>

View File

@@ -1,6 +1,6 @@
{ {
"name": "bodyshop", "name": "bodyshop",
"version": "0.1.0", "version": "0.1.0001",
"private": true, "private": true,
"proxy": "https://localhost:5000", "proxy": "https://localhost:5000",
"dependencies": { "dependencies": {

View File

@@ -2,11 +2,11 @@ import {
EditFilled, EditFilled,
FileImageFilled, FileImageFilled,
PrinterFilled, PrinterFilled,
ShoppingFilled ShoppingFilled,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { useQuery } from "@apollo/react-hooks"; import { useQuery } from "@apollo/react-hooks";
import { Button, PageHeader, Tag } from "antd"; import { Button, PageHeader, Tag } from "antd";
import React, { useState } from "react"; import React 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";
@@ -15,7 +15,6 @@ import { setModalContext } from "../../redux/modals/modals.actions";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import NoteUpsertModal from "../note-upsert-modal/note-upsert-modal.container"; import NoteUpsertModal from "../note-upsert-modal/note-upsert-modal.container";
import ScheduleJobModalContainer from "../schedule-job-modal/schedule-job-modal.container";
import JobDetailCardsCustomerComponent from "./job-detail-cards.customer.component"; import JobDetailCardsCustomerComponent from "./job-detail-cards.customer.component";
import JobDetailCardsDamageComponent from "./job-detail-cards.damage.component"; import JobDetailCardsDamageComponent from "./job-detail-cards.damage.component";
import JobDetailCardsDatesComponent from "./job-detail-cards.dates.component"; import JobDetailCardsDatesComponent from "./job-detail-cards.dates.component";
@@ -26,46 +25,44 @@ import JobDetailCardsPartsComponent from "./job-detail-cards.parts.component";
import "./job-detail-cards.styles.scss"; import "./job-detail-cards.styles.scss";
import JobDetailCardsTotalsComponent from "./job-detail-cards.totals.component"; import JobDetailCardsTotalsComponent from "./job-detail-cards.totals.component";
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = (dispatch) => ({
setInvoiceEnterContext: context => setInvoiceEnterContext: (context) =>
dispatch(setModalContext({ context: context, modal: "invoiceEnter" })), dispatch(setModalContext({ context: context, modal: "invoiceEnter" })),
setNoteUpsertContext: context => setNoteUpsertContext: (context) =>
dispatch(setModalContext({ context: context, modal: "noteUpsert" })) dispatch(setModalContext({ context: context, modal: "noteUpsert" })),
setPrintCenterContext: (context) =>
dispatch(setModalContext({ context: context, modal: "printCenter" })),
}); });
export function JobDetailCards({ export function JobDetailCards({
selectedJob, selectedJob,
setInvoiceEnterContext, setInvoiceEnterContext,
setNoteUpsertContext setNoteUpsertContext,
setPrintCenterContext,
}) { }) {
const { loading, error, data, refetch } = useQuery(QUERY_JOB_CARD_DETAILS, { const { loading, error, data, refetch } = useQuery(QUERY_JOB_CARD_DETAILS, {
fetchPolicy: "network-only", fetchPolicy: "network-only",
variables: { id: selectedJob }, variables: { id: selectedJob },
skip: !selectedJob skip: !selectedJob,
}); });
const scheduleModalState = useState(false);
const { t } = useTranslation(); const { t } = useTranslation();
if (!selectedJob) { if (!selectedJob) {
return <div>{t("jobs.errors.nojobselected")}</div>; return <div>{t("jobs.errors.nojobselected")}</div>;
} }
if (loading) return <LoadingSpinner />; if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />; if (error) return <AlertComponent message={error.message} type='error' />;
return ( return (
<div className="job-cards-container"> <div className='job-cards-container'>
<NoteUpsertModal /> <NoteUpsertModal />
<ScheduleJobModalContainer
scheduleModalState={scheduleModalState}
jobId={data.jobs_by_pk.id}
refetch={refetch}
/>
<PageHeader <PageHeader
ghost={false} ghost={false}
tags={ tags={
<span key="job-status"> <span key='job-status'>
{data.jobs_by_pk.status ? ( {data.jobs_by_pk.status ? (
<Tag color="blue">{data.jobs_by_pk.status}</Tag> <Tag color='blue'>{data.jobs_by_pk.status}</Tag>
) : null} ) : null}
</span> </span>
} }
@@ -84,89 +81,66 @@ export function JobDetailCards({
} }
extra={[ extra={[
<Button <Button
key="schedule" key='schedule'
//TODO Enabled logic based on status. //TODO Enabled logic based on status.
onClick={() => { onClick={() => {}}>
scheduleModalState[1](true);
}}
>
{t("jobs.actions.schedule")} {t("jobs.actions.schedule")}
</Button>, </Button>,
<Link <Link
key="documents" key='documents'
to={`/manage/jobs/${data.jobs_by_pk.id}?documents`} to={`/manage/jobs/${data.jobs_by_pk.id}?documents`}>
>
<Button> <Button>
<FileImageFilled /> <FileImageFilled />
{t("jobs.actions.addDocuments")} {t("jobs.actions.addDocuments")}
</Button> </Button>
</Link>, </Link>,
<Button key="printing"> <Button
onClick={() => {
setPrintCenterContext({
actions: { refetch: refetch },
context: {
id: data.jobs_by_pk.id,
type: "job",
},
});
}}
key='printing'>
<PrinterFilled /> <PrinterFilled />
{t("jobs.actions.printCenter")} {t("jobs.actions.printCenter")}
</Button>, </Button>,
<Button <Button
key="notes" key='notes'
actiontype="addNote" actiontype='addNote'
onClick={() => { onClick={() => {
setNoteUpsertContext({ setNoteUpsertContext({
actions: { refetch: refetch }, actions: { refetch: refetch },
context: { context: {
jobId: data.jobs_by_pk.id jobId: data.jobs_by_pk.id,
} },
}); });
}} }}>
>
<EditFilled /> <EditFilled />
{t("jobs.actions.addNote")} {t("jobs.actions.addNote")}
</Button>, </Button>,
<Button <Button
key="postinvoices" key='postinvoices'
onClick={() => { onClick={() => {
setInvoiceEnterContext({ setInvoiceEnterContext({
actions: { refetch: refetch }, actions: { refetch: refetch },
context: { context: {
job: data.jobs_by_pk job: data.jobs_by_pk,
} },
}); });
}} }}>
>
<ShoppingFilled /> <ShoppingFilled />
{t("jobs.actions.postInvoices")} {t("jobs.actions.postInvoices")}
</Button> </Button>,
]} ]}>
> <section className='job-cards'>
{
// loading ? (
// <LoadingSkeleton />
// ) : (
// <Descriptions size='small' column={3}>
// <Descriptions.Item label='Created'>Lili Qu</Descriptions.Item>
// <Descriptions.Item label='Association'>421421</Descriptions.Item>
// <Descriptions.Item label='Creation Time'>
// 2017-01-10
// </Descriptions.Item>
// <Descriptions.Item label='Effective Time'>
// 2017-10-10
// </Descriptions.Item>
// <Descriptions.Item label='Remarks'>
// Gonghu Road, Xihu District, Hangzhou, Zhejiang, China
// </Descriptions.Item>
// </Descriptions>
// )
}
<section className="job-cards">
<JobDetailCardsCustomerComponent <JobDetailCardsCustomerComponent
loading={loading} loading={loading}
data={data ? data.jobs_by_pk : null} data={data ? data.jobs_by_pk : null}
/> />
{
// <JobDetailCardsVehicleComponent
// loading={loading}
// data={data ? data.jobs_by_pk : null}
// />
}
<JobDetailCardsInsuranceComponent <JobDetailCardsInsuranceComponent
loading={loading} loading={loading}

View File

@@ -0,0 +1,8 @@
import React from "react";
import { useTranslation } from "react-i18next";
export default function PrintCenterModalComponent({ context }) {
const { t } = useTranslation();
const { type, id } = context;
return <div>{`${type} - ${id}`}</div>;
}

View File

@@ -0,0 +1,54 @@
import { Modal } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { setEmailOptions } from "../../redux/email/email.actions";
import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectPrintCenter } from "../../redux/modals/modals.selectors";
import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import PrintCenterModalComponent from "./print-center-modal.component";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
bodyshop: selectBodyshop,
printCenterModal: selectPrintCenter,
});
const mapDispatchToProps = (dispatch) => ({
setEmailOptions: (e) => dispatch(setEmailOptions(e)),
toggleModalVisible: () => dispatch(toggleModalVisible("printCenter")),
});
export function PrintCenterModalContainer({
printCenterModal,
toggleModalVisible,
currentUser,
bodyshop,
setEmailOptions,
}) {
const { t } = useTranslation();
const { visible, context, actions } = printCenterModal;
// const { jobId, linesToOrder } = context;
// const { refetch } = actions;
return (
<Modal
visible={visible}
onCancel={() => toggleModalVisible()}
width='90%'
title={t("printcenter.labels.title")}
destroyOnClose>
<PrintCenterModalComponent context={context} />
</Modal>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(PrintCenterModalContainer);

View File

@@ -9,6 +9,7 @@ import HeaderContainer from "../../components/header/header.container";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import "./manage.page.styles.scss"; import "./manage.page.styles.scss";
import BreadCrumbs from "../../components/breadcrumbs/breadcrumbs.component"; import BreadCrumbs from "../../components/breadcrumbs/breadcrumbs.component";
import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container";
const ManageRootPage = lazy(() => const ManageRootPage = lazy(() =>
import("../manage-root/manage-root.page.container") import("../manage-root/manage-root.page.container")
@@ -99,19 +100,18 @@ export default function Manage({ match }) {
</Header> </Header>
<Layout> <Layout>
<Content <Content
className="content-container" className='content-container'
style={{ padding: "0em 4em 4em" }} style={{ padding: "0em 4em 4em" }}>
>
<ErrorBoundary> <ErrorBoundary>
<Suspense <Suspense
fallback={ fallback={
<LoadingSpinner message={t("general.labels.loadingapp")} /> <LoadingSpinner message={t("general.labels.loadingapp")} />
} }>
>
<BreadCrumbs /> <BreadCrumbs />
<EnterInvoiceModalContainer /> <EnterInvoiceModalContainer />
<EmailOverlayContainer /> <EmailOverlayContainer />
<TimeTicketModalContainer /> <TimeTicketModalContainer />
<PrintCenterModalContainer />
<Route exact path={`${match.path}`} component={ManageRootPage} /> <Route exact path={`${match.path}`} component={ManageRootPage} />
<Route exact path={`${match.path}/jobs`} component={JobsPage} /> <Route exact path={`${match.path}/jobs`} component={JobsPage} />
<Switch> <Switch>
@@ -177,10 +177,10 @@ export default function Manage({ match }) {
component={ProductionListPage} component={ProductionListPage}
/> />
<Route <Route
exact exact
path={`${match.path}/production/board`} path={`${match.path}/production/board`}
component={ProductionBoardPage} component={ProductionBoardPage}
/> />
<Route <Route
exact exact
path={`${match.path}/vehicles/:vehId`} path={`${match.path}/vehicles/:vehId`}

View File

@@ -16,6 +16,7 @@ const INITIAL_STATE = {
schedule: { ...baseModal }, schedule: { ...baseModal },
partsOrder: { ...baseModal }, partsOrder: { ...baseModal },
timeTicket: { ...baseModal }, timeTicket: { ...baseModal },
printCenter: {...baseModal}
}; };
const modalsReducer = (state = INITIAL_STATE, action) => { const modalsReducer = (state = INITIAL_STATE, action) => {

View File

@@ -35,4 +35,7 @@ export const selectPartsOrder = createSelector(
export const selectTimeTicket = createSelector( export const selectTimeTicket = createSelector(
[selectModals], [selectModals],
modals => modals.timeTicket modals => modals.timeTicket
);export const selectPrintCenter = createSelector(
[selectModals],
modals => modals.printCenter
); );

View File

@@ -732,6 +732,11 @@
"created": "Parts order created successfully. " "created": "Parts order created successfully. "
} }
}, },
"printcenter": {
"labels": {
"title": "Print Center"
}
},
"production": { "production": {
"actions": { "actions": {
"addcolumns": "Add Columns", "addcolumns": "Add Columns",

View File

@@ -732,6 +732,11 @@
"created": "Pedido de piezas creado con éxito." "created": "Pedido de piezas creado con éxito."
} }
}, },
"printcenter": {
"labels": {
"title": ""
}
},
"production": { "production": {
"actions": { "actions": {
"addcolumns": "", "addcolumns": "",

View File

@@ -732,6 +732,11 @@
"created": "Commande de pièces créée avec succès." "created": "Commande de pièces créée avec succès."
} }
}, },
"printcenter": {
"labels": {
"title": ""
}
},
"production": { "production": {
"actions": { "actions": {
"addcolumns": "", "addcolumns": "",

View File

@@ -0,0 +1,11 @@
query EMAIL_APPOINTMENT_CONFIRMATION($id: uuid!) {
appointments_by_pk(id: $id) {
start
title
job {
ownr_fn
ownr_ln
ownr_ea
}
}
}

View File

@@ -0,0 +1,8 @@
<div style="font-family: Arial, Helvetica, sans-serif;">
<p style="text-align: center;">Hello {{appointments_by_pk.job.ownr_fn}},</p>
<p style="text-align: center;">
This is a reminder that you have an appointment at
{{appointments_by_pk.start}} to bring your car in for repair. Please email
us at {{bodyshop.email}} if you can't make it.&nbsp;
</p>
</div>

View File

@@ -0,0 +1,29 @@
query REPORT_QUERY_PARTS_ORDER_BY_PK($id: uuid!) {
parts_orders_by_pk(id: $id) {
job {
id
vehicle {
id
v_model_desc
v_make_desc
v_model_yr
v_vin
}
ro_number
est_number
}
id
deliver_by
parts_order_lines {
id
db_price
act_price
line_desc
line_remarks
oem_partno
status
}
status
user_email
}
}

View File

@@ -0,0 +1,122 @@
<div style="font-family: Arial, Helvetica, sans-serif;">
<p style="text-align: center;">
<span><strong>Deliver By: {{parts_orders_by_pk.deliver_by}}</strong></span>
</p>
<table
style="
border-collapse: collapse;
width: 85%;
height: 86px;
border-color: initial;
border-style: solid;
margin-left: auto;
margin-right: auto;
"
>
<tbody>
<tr style="height: 22px;">
<td
style="
width: 33.7389%;
text-align: center;
padding: 7px;
height: 22px;
"
>
<strong>Line Description</strong>
</td>
<td
style="
width: 22.0208%;
text-align: center;
padding-top: 7px;
padding-right: 7px;
padding-bottom: 7px;
height: 22px;
"
>
<strong>Part #</strong>
</td>
<td
style="
width: 12.1897%;
text-align: center;
padding: 7px;
height: 22px;
"
>
<strong>Price</strong>
</td>
<td
style="
width: 32.0506%;
text-align: center;
padding: 7px;
height: 22px;
"
>
<strong>Line Remarks</strong>
</td>
</tr>
<tr style="height: 21px; display: none;">
<td style="height: 21px; display: none; width: 33.7389%;">
<span>{{#each parts_orders_by_pk.parts_order_lines}}</span>
</td>
</tr>
<tr style="height: 22px;">
<td
style="
width: 33.7389%;
text-align: center;
padding: 7px;
height: 22px;
border-style: solid;
border-width: 1px;
"
>
<span>{{this.line_desc}}</span>
</td>
<td
style="
width: 22.0208%;
text-align: center;
padding: 7px;
height: 22px;
border-style: solid;
border-width: 1px;
"
>
<span>{{this.oem_partno}}</span>
</td>
<td
style="
width: 12.1897%;
text-align: center;
padding: 7px;
height: 22px;
border-style: solid;
border-width: 1px;
"
>
<span>${{this.act_price}}</span>
</td>
<td
style="
width: 32.0506%;
text-align: center;
padding: 7px;
height: 22px;
border-style: solid;
border-width: 1px;
"
>
<span>{{this.line_remarks}}</span>
</td>
</tr>
<tr style="height: 21px; display: none;">
<td style="height: 21px; width: 33.7389%;"><span>{{/each}}</span></td>
</tr>
</tbody>
</table>
<p>Order Placed by {{parts_orders_by_pk.user_email}}.</p>
</div>