Merged in feature/IO-3020-IO-3036-imex-lite-rome-lite (pull request #2012)

feature/IO-3020-IO-3036-imex-lite-rome-lite

Approved-by: Patrick Fic
This commit is contained in:
Patrick Fic
2024-12-11 17:45:25 +00:00
8 changed files with 258 additions and 142 deletions

79
.gitattributes vendored
View File

@@ -1 +1,80 @@
# Ensure all text files use LF for line endings
* text eol=lf * text eol=lf
# Binary files should not be modified by Git
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.webp binary
*.svg binary
# Fonts
*.woff binary
*.woff2 binary
*.ttf binary
*.otf binary
*.eot binary
# Videos
*.mp4 binary
*.mov binary
*.avi binary
*.mkv binary
*.webm binary
# Audio
*.mp3 binary
*.wav binary
*.ogg binary
*.flac binary
# Archives and compressed files
*.zip binary
*.gz binary
*.tar binary
*.7z binary
*.rar binary
# PDF and documents
*.pdf binary
*.doc binary
*.docx binary
*.xls binary
*.xlsx binary
*.ppt binary
*.pptx binary
# Exclude JSON and other data files from text processing, if necessary
*.json text
*.xml text
*.csv text
# Scripts and code files should maintain LF endings
*.js text eol=lf
*.jsx text eol=lf
*.ts text eol=lf
*.tsx text eol=lf
*.css text eol=lf
*.scss text eol=lf
*.html text eol=lf
*.yml text eol=lf
*.yaml text eol=lf
*.md text eol=lf
*.sh text eol=lf
*.py text eol=lf
*.rb text eol=lf
*.java text eol=lf
*.php text eol=lf
# Git configuration files
.gitattributes text eol=lf
.gitignore text eol=lf
*.gitattributes text eol=lf
# Exclude some other potential binary files
*.db binary
*.sqlite binary
*.exe binary
*.dll binary

View File

@@ -47,7 +47,7 @@ export function JobDetailCards({ bodyshop, setPrintCenterContext }) {
md: "100%", md: "100%",
lg: "75%", lg: "75%",
xl: "75%", xl: "75%",
xxl: "60%" xxl: "75%"
}; };
const drawerPercentage = selectedBreakpoint ? bpoints[selectedBreakpoint[0]] : "100%"; const drawerPercentage = selectedBreakpoint ? bpoints[selectedBreakpoint[0]] : "100%";

View File

@@ -3,6 +3,7 @@ import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { GenerateThumbUrl } from "../jobs-documents-gallery/job-documents.utility"; import { GenerateThumbUrl } from "../jobs-documents-gallery/job-documents.utility";
import CardTemplate from "./job-detail-cards.template.component"; import CardTemplate from "./job-detail-cards.template.component";
import UpsellComponent, { upsellEnum } from "../upsell/upsell.component";
export default function JobDetailCardsDocumentsComponent({ loading, data }) { export default function JobDetailCardsDocumentsComponent({ loading, data }) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -20,15 +21,17 @@ export default function JobDetailCardsDocumentsComponent({ loading, data }) {
title={t("jobs.labels.cards.documents")} title={t("jobs.labels.cards.documents")}
extraLink={`/manage/jobs/${data.id}?tab=documents`} extraLink={`/manage/jobs/${data.id}?tab=documents`}
> >
{data.documents.length > 0 ? ( <UpsellComponent disableMask upsell={upsellEnum().media.general}>
<Carousel autoplay> {data.documents.length > 0 ? (
{data.documents.map((item) => ( <Carousel autoplay>
<img key={item.id} src={GenerateThumbUrl(item)} alt={item.name} /> {data.documents.map((item) => (
))} <img key={item.id} src={GenerateThumbUrl(item)} alt={item.name} />
</Carousel> ))}
) : ( </Carousel>
<div>{t("documents.errors.nodocuments")}</div> ) : (
)} <div>{t("documents.errors.nodocuments")}</div>
)}
</UpsellComponent>
</CardTemplate> </CardTemplate>
); );
} }

View File

@@ -892,16 +892,16 @@ export function JobsDetailHeaderActions({
key: "postbills", key: "postbills",
id: "job-actions-postbills", id: "job-actions-postbills",
disabled: !job.converted, disabled: !job.converted,
label: <LockerWrapperComponent featureName="bill">{t("jobs.actions.postbills")}</LockerWrapperComponent>, label: <LockerWrapperComponent featureName="bills">{t("jobs.actions.postbills")}</LockerWrapperComponent>,
onClick: () => { onClick: () => {
logImEXEvent("job_header_enter_bills"); logImEXEvent("job_header_enter_bills");
HasFeatureAccess({ featureName: "bills", bodyshop }) &&
setBillEnterContext({ setBillEnterContext({
actions: { refetch: refetch }, actions: { refetch: refetch },
context: { context: {
job: job job: job
} }
}); });
} }
}, },

View File

@@ -133,7 +133,7 @@ function JobsDocumentsComponent({
{!hasMediaAccess && ( {!hasMediaAccess && (
<Col span={24}> <Col span={24}>
<Card> <Card>
<UpsellComponent upsell={upsellEnum().media.general} /> <UpsellComponent disableMask upsell={upsellEnum().media.general} />
</Card> </Card>
</Col> </Col>
)} )}

View File

@@ -1,5 +1,5 @@
import { FileExcelFilled, SyncOutlined } from "@ant-design/icons"; import { FileExcelFilled, SyncOutlined } from "@ant-design/icons";
import { Alert, Button, Card, Space } from "antd"; import { Alert, Button, Card, Col, Row, Space } from "antd";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { Gallery } from "react-grid-gallery"; import { Gallery } from "react-grid-gallery";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -18,6 +18,7 @@ import JobsDocumentsLocalGallerySelectAllComponent from "./jobs-documents-local-
import Lightbox from "react-image-lightbox"; import Lightbox from "react-image-lightbox";
import "react-image-lightbox/style.css"; import "react-image-lightbox/style.css";
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component"; import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
import UpsellComponent, { upsellEnum } from "../upsell/upsell.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -88,95 +89,117 @@ export function JobsDocumentsLocalGallery({
const hasMediaAccess = HasFeatureAccess({ bodyshop, featureName: "media" }); const hasMediaAccess = HasFeatureAccess({ bodyshop, featureName: "media" });
return ( return (
<div> <div>
<Space wrap> <Row gutter={[16, 16]}>
<Button <Col span={24}>
onClick={() => { <Space wrap>
if (job) { <Button
if (invoice_number) { onClick={() => {
getBillMedia({ jobid: job.id, invoice_number }); if (job) {
} else { if (invoice_number) {
getJobMedia(job.id); getBillMedia({ jobid: job.id, invoice_number });
} } else {
} getJobMedia(job.id);
}} }
> }
<SyncOutlined /> }}
</Button> >
<a href={CreateExplorerLinkForJob({ jobid: job.id })}> <SyncOutlined />
<Button>{t("documents.labels.openinexplorer")}</Button> </Button>
</a> <a href={CreateExplorerLinkForJob({ jobid: job.id })}>
<JobsDocumentsLocalGalleryReassign jobid={job.id} /> <Button>{t("documents.labels.openinexplorer")}</Button>
<JobsDocumentsLocalGallerySelectAllComponent jobid={job.id} /> </a>
<JobsLocalGalleryDownloadButton job={job} /> <JobsDocumentsLocalGalleryReassign jobid={job.id} />
<JobsDocumentsLocalDeleteButton jobid={job.id} /> <JobsDocumentsLocalGallerySelectAllComponent jobid={job.id} />
</Space> <JobsLocalGalleryDownloadButton job={job} />
<Card> <JobsDocumentsLocalDeleteButton jobid={job.id} />
<DocumentsLocalUploadComponent job={job} invoice_number={invoice_number} vendorid={vendorid} allowAllTypes /> </Space>
</Card> </Col>
<Card title={t("jobs.labels.documents-images")}> {!hasMediaAccess && (
<Gallery <Col span={24}>
images={jobMedia.images} <Card>
onSelect={(index, image) => { <UpsellComponent disableMask upsell={upsellEnum().media.general} />
toggleMediaSelected({ jobid: job.id, filename: image.filename }); </Card>
}} </Col>
{...(optimized && { )}
customControls: [ <Col span={24}>
<Alert style={{ margin: "4px" }} message={t("documents.labels.optimizedimage")} type="success" /> <Card>
] <DocumentsLocalUploadComponent
})} job={job}
onClick={(index) => { invoice_number={invoice_number}
setModalState({ open: true, index: index }); vendorid={vendorid}
// const media = allMedia[job.id].find( allowAllTypes
// (m) => m.optimized === item.src />
// ); </Card>
</Col>
<Col span={24}>
<Card title={t("jobs.labels.documents-images")}>
<Gallery
images={jobMedia.images}
onSelect={(index, image) => {
toggleMediaSelected({ jobid: job.id, filename: image.filename });
}}
{...(optimized && {
customControls: [
<Alert style={{ margin: "4px" }} message={t("documents.labels.optimizedimage")} type="success" />
]
})}
onClick={(index) => {
setModalState({ open: true, index: index });
// const media = allMedia[job.id].find(
// (m) => m.optimized === item.src
// );
// window.open( // window.open(
// media ? media.fullsize : item.fullsize, // media ? media.fullsize : item.fullsize,
// "_blank", // "_blank",
// "toolbar=0,location=0,menubar=0" // "toolbar=0,location=0,menubar=0"
// ); // );
}} }}
/> />
</Card> </Card>
<Card title={t("jobs.labels.documents-other")}> </Col>
<Gallery <Col span={24}>
images={jobMedia.other} <Card title={t("jobs.labels.documents-other")}>
thumbnailStyle={() => { <Gallery
return { images={jobMedia.other}
backgroundImage: <FileExcelFilled />, thumbnailStyle={() => {
height: "100%", return {
width: "100%", backgroundImage: <FileExcelFilled />,
cursor: "pointer" height: "100%",
}; width: "100%",
}} cursor: "pointer"
onClick={(index) => { };
window.open(jobMedia.other[index].fullsize, "_blank", "toolbar=0,location=0,menubar=0"); }}
}} onClick={(index) => {
onSelect={(index, image) => { window.open(jobMedia.other[index].fullsize, "_blank", "toolbar=0,location=0,menubar=0");
toggleMediaSelected({ jobid: job.id, filename: image.filename }); }}
}} onSelect={(index, image) => {
/> toggleMediaSelected({ jobid: job.id, filename: image.filename });
</Card> }}
{modalState.open && ( />
<Lightbox </Card>
mainSrc={jobMedia.images[modalState.index].fullsize} </Col>
nextSrc={jobMedia.images[(modalState.index + 1) % jobMedia.images.length].fullsize} {modalState.open && (
prevSrc={jobMedia.images[(modalState.index + jobMedia.images.length - 1) % jobMedia.images.length].fullsize} <Lightbox
onCloseRequest={() => setModalState({ open: false, index: 0 })} mainSrc={jobMedia.images[modalState.index].fullsize}
onMovePrevRequest={() => nextSrc={jobMedia.images[(modalState.index + 1) % jobMedia.images.length].fullsize}
setModalState({ prevSrc={jobMedia.images[(modalState.index + jobMedia.images.length - 1) % jobMedia.images.length].fullsize}
...modalState, onCloseRequest={() => setModalState({ open: false, index: 0 })}
index: (modalState.index + jobMedia.images.length - 1) % jobMedia.images.length onMovePrevRequest={() =>
}) setModalState({
} ...modalState,
onMoveNextRequest={() => index: (modalState.index + jobMedia.images.length - 1) % jobMedia.images.length
setModalState({ })
...modalState, }
index: (modalState.index + 1) % jobMedia.images.length onMoveNextRequest={() =>
}) setModalState({
} ...modalState,
/> index: (modalState.index + 1) % jobMedia.images.length
)} })
}
/>
)}
</Row>
</div> </div>
); );
} }

View File

@@ -1,4 +1,4 @@
import { Alert, Form, InputNumber, Switch } from "antd"; import { Alert, Form, Switch } from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component";

View File

@@ -1,5 +1,5 @@
import { useQuery } from "@apollo/client"; import { useQuery } from "@apollo/client";
import { Col, Row, Typography } from "antd"; import { Card, Col, Row, Typography } from "antd";
import dayjs from "../../utils/day"; import dayjs from "../../utils/day";
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -14,6 +14,8 @@ import { QUERY_JOB_CHECKLISTS } from "../../graphql/jobs.queries";
import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import InstanceRenderManager from "../../utils/instanceRenderMgr"; import InstanceRenderManager from "../../utils/instanceRenderMgr";
import FeatureWrapperComponent from "../../components/feature-wrapper/feature-wrapper.component";
import UpsellComponent, { upsellEnum } from "../../components/upsell/upsell.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser //currentUser: selectCurrentUser
@@ -73,38 +75,47 @@ export function JobsChecklistViewContainer({ bodyshop, setBreadcrumbs, setSelect
); );
return ( return (
<RbacWrapper action="jobs:checklist-view"> <FeatureWrapperComponent
<Row gutter={[16, 16]}> featureName="checklist"
<Col span={12}> noauth={
<Typography.Title level={4}>{t("jobs.labels.intakechecklist")}</Typography.Title> <Card>
{data.jobs_by_pk.intakechecklist && data.jobs_by_pk.intakechecklist.form && ( <UpsellComponent upsell={upsellEnum().checklist.general} />
<> </Card>
<JobChecklistForm }
formItems={data.jobs_by_pk.intakechecklist && data.jobs_by_pk.intakechecklist.form} >
type="intake" <RbacWrapper action="jobs:checklist-view">
job={data.jobs_by_pk} <Row gutter={[16, 16]}>
readOnly <Col span={12}>
/> <Typography.Title level={4}>{t("jobs.labels.intakechecklist")}</Typography.Title>
<CompletedBy checklist={data.jobs_by_pk.intakechecklist} /> {data.jobs_by_pk.intakechecklist && data.jobs_by_pk.intakechecklist.form && (
</> <>
)} <JobChecklistForm
</Col> formItems={data.jobs_by_pk.intakechecklist && data.jobs_by_pk.intakechecklist.form}
<Col span={12}> type="intake"
<Typography.Title level={4}>{t("jobs.labels.deliverchecklist")}</Typography.Title> job={data.jobs_by_pk}
{data.jobs_by_pk.deliverchecklist && data.jobs_by_pk.deliverchecklist.form && ( readOnly
<> />
<JobChecklistForm <CompletedBy checklist={data.jobs_by_pk.intakechecklist} />
formItems={data.jobs_by_pk.deliverchecklist && data.jobs_by_pk.deliverchecklist.form} </>
type="deliver" )}
job={data.jobs_by_pk} </Col>
readOnly <Col span={12}>
/> <Typography.Title level={4}>{t("jobs.labels.deliverchecklist")}</Typography.Title>
<CompletedBy checklist={data.jobs_by_pk.deliverchecklist} /> {data.jobs_by_pk.deliverchecklist && data.jobs_by_pk.deliverchecklist.form && (
</> <>
)} <JobChecklistForm
</Col> formItems={data.jobs_by_pk.deliverchecklist && data.jobs_by_pk.deliverchecklist.form}
</Row> type="deliver"
</RbacWrapper> job={data.jobs_by_pk}
readOnly
/>
<CompletedBy checklist={data.jobs_by_pk.deliverchecklist} />
</>
)}
</Col>
</Row>
</RbacWrapper>
</FeatureWrapperComponent>
); );
} }