From a5f7ff308928d44bc77847e2cadf1fd2c19d0557 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Wed, 31 Dec 2025 09:25:12 -0800 Subject: [PATCH 1/5] IO-3491 QBO SALESTERMSREF Signed-off-by: Allan Carr --- server/accounting/qbo/qbo-payables.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server/accounting/qbo/qbo-payables.js b/server/accounting/qbo/qbo-payables.js index a21e8ebc0..2532fb5fc 100644 --- a/server/accounting/qbo/qbo-payables.js +++ b/server/accounting/qbo/qbo-payables.js @@ -283,6 +283,11 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor, bodyshop) VendorRef: { value: vendor.Id }, + ...(vendor.TermRef && { + SalesTermRef: { + value: vendor.TermRef.value + } + }), TxnDate: moment(bill.date) //.tz(bill.job.bodyshop.timezone) .format("YYYY-MM-DD"), From e3ab229ac596b60ad81eb7f132541d1b5560fcc9 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Wed, 31 Dec 2025 15:39:21 -0800 Subject: [PATCH 2/5] IO-3484 Shop Config Accounting Section Move Signed-off-by: Allan Carr --- .../shop-info/shop-info.general.component.jsx | 327 +----------------- ...p-info.responsibilitycenters.component.jsx | 297 +++++++++++++++- 2 files changed, 307 insertions(+), 317 deletions(-) diff --git a/client/src/components/shop-info/shop-info.general.component.jsx b/client/src/components/shop-info/shop-info.general.component.jsx index acf553bb5..01fbfe18b 100644 --- a/client/src/components/shop-info/shop-info.general.component.jsx +++ b/client/src/components/shop-info/shop-info.general.component.jsx @@ -1,12 +1,9 @@ import { DeleteFilled } from "@ant-design/icons"; -import { useSplitTreatments } from "@splitsoftware/splitio-react"; -import { Button, DatePicker, Form, Input, InputNumber, Radio, Select, Space, Switch } from "antd"; +import { Button, Form, Input, InputNumber, Select, Space, Switch } from "antd"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { selectBodyshop } from "../../redux/user/user.selectors"; -import DatePickerRanges from "../../utils/DatePickerRanges"; -import InstanceRenderManager from "../../utils/instanceRenderMgr"; import FeatureWrapper, { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component"; import CurrencyInput from "../form-items-formatted/currency-form-item.component"; import FormItemEmail from "../form-items-formatted/email-form-item.component"; @@ -26,14 +23,6 @@ export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoGeneral); export function ShopInfoGeneral({ form, bodyshop }) { const { t } = useTranslation(); - const { - treatments: { ClosingPeriod, ADPPayroll } - } = useSplitTreatments({ - attributes: {}, - names: ["ClosingPeriod", "ADPPayroll"], - splitKey: bodyshop?.imexshopid - }); - return (
@@ -143,299 +132,6 @@ export function ShopInfoGeneral({ form, bodyshop }) { - - {[ - ...(HasFeatureAccess({ featureName: "export", bodyshop }) - ? [ - - - , - InstanceRenderManager({ - imex: ( - - {() => ( - - - - )} - - ) - }), - - - , - - - 2 - 3 - - , - - {() => { - return ( - - - {t("bodyshop.labels.2tiername")} - {t("bodyshop.labels.2tiersource")} - - - ); - }} - , - - - , - - - - ] - : []), - - - , - - - , - InstanceRenderManager({ - imex: ( - - - - ) - }), - - - , - ...(HasFeatureAccess({ featureName: "bills", bodyshop }) - ? [ - InstanceRenderManager({ - imex: ( - - - - ) - }), - - - , - - - - ] - : []), - - - , - ...(HasFeatureAccess({ featureName: "export", bodyshop }) - ? [ - - {ReceivableCustomFieldSelect} - , - - {ReceivableCustomFieldSelect} - , - - {ReceivableCustomFieldSelect} - , - { - return { - required: getFieldValue("enforce_class"), - //message: t("general.validation.required"), - type: "array" - }; - } - ]} - > - - - ] - : []), - ...(ADPPayroll.treatment === "on" - ? [ - - - - ] - : []) - ] - : []), - - - - ]} - null}> {[ + + - VIN - Claim No. - Deductible Amount - -); diff --git a/client/src/components/shop-info/shop-info.responsibilitycenters.component.jsx b/client/src/components/shop-info/shop-info.responsibilitycenters.component.jsx index 6e7a4edfd..38437a9aa 100644 --- a/client/src/components/shop-info/shop-info.responsibilitycenters.component.jsx +++ b/client/src/components/shop-info/shop-info.responsibilitycenters.component.jsx @@ -1,6 +1,6 @@ import { DeleteFilled } from "@ant-design/icons"; import { useSplitTreatments } from "@splitsoftware/splitio-react"; -import { Button, Form, Input, InputNumber, Select, Space, Switch } from "antd"; +import { Button, DatePicker, Form, Input, InputNumber, Radio, Select, Space, Switch } from "antd"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; @@ -14,6 +14,7 @@ import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.c import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component"; import ShopInfoResponsibilitycentersTaxesComponent from "./shop-info.responsibilitycenters.taxes.component"; +import DatePickerRanges from "../../utils/DatePickerRanges"; const SelectorDiv = styled.div` .ant-form-item .ant-select { @@ -34,11 +35,11 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) { const { t } = useTranslation(); const { - treatments: { Qb_Multi_Ar, DmsAp } + treatments: { ClosingPeriod, ADPPayroll, Qb_Multi_Ar, DmsAp } } = useSplitTreatments({ attributes: {}, - names: ["Qb_Multi_Ar", "DmsAp"], - splitKey: bodyshop && bodyshop.imexshopid + names: ["ClosingPeriod", "ADPPayroll", "Qb_Multi_Ar", "DmsAp"], + splitKey: bodyshop?.imexshopid }); const [costOptions, setCostOptions] = useState([ @@ -58,6 +59,14 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) { setProfitOptions([...(form.getFieldValue(["md_responsibility_centers", "profits"]).map((i) => i && i.name) || [])]); }; + const ReceivableCustomFieldSelect = ( + + ); + return (
@@ -314,6 +323,286 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) { )} + + {[ + ...(HasFeatureAccess({ featureName: "export", bodyshop }) + ? [ + + + , + InstanceRenderManager({ + imex: ( + + {() => ( + + + + )} + + ) + }), + + + , + + + 2 + 3 + + , + + {() => { + return ( + + + {t("bodyshop.labels.2tiername")} + {t("bodyshop.labels.2tiersource")} + + + ); + }} + , + + + , + + + + ] + : []), + + + , + + + , + InstanceRenderManager({ + imex: ( + + + + ) + }), + + + , + ...(HasFeatureAccess({ featureName: "bills", bodyshop }) + ? [ + InstanceRenderManager({ + imex: ( + + + + ) + }), + + + , + + + + ] + : []), + + + , + + + , + ...(ClosingPeriod.treatment === "on" + ? [ + + + + ] + : []), + ...(ADPPayroll.treatment === "on" + ? [ + + + + ] + : []), + ...(ADPPayroll.treatment === "on" + ? [ + + + + ] + : []) + ] + : []), + + + + ]} + {HasFeatureAccess({ featureName: "export", bodyshop }) && ( <> From 531086630226a88d27acbf2db3148209e8fa3ff0 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Fri, 2 Jan 2026 13:40:50 -0800 Subject: [PATCH 3/5] IO-3484 Shop Config Acct Section Move Signed-off-by: Allan Carr --- ...p-info.responsibilitycenters.component.jsx | 589 +++++++++--------- 1 file changed, 294 insertions(+), 295 deletions(-) diff --git a/client/src/components/shop-info/shop-info.responsibilitycenters.component.jsx b/client/src/components/shop-info/shop-info.responsibilitycenters.component.jsx index 38437a9aa..40c5a229d 100644 --- a/client/src/components/shop-info/shop-info.responsibilitycenters.component.jsx +++ b/client/src/components/shop-info/shop-info.responsibilitycenters.component.jsx @@ -70,16 +70,302 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) { return (
+ + {[ + ...(HasFeatureAccess({ featureName: "export", bodyshop }) + ? !bodyshop.cdk_dealerid && !bodyshop.pbs_serialnumber + ? [ + + + , + InstanceRenderManager({ + imex: ( + + {() => ( + + + + )} + + ) + }), + + + , + + + 2 + 3 + + , + + {() => { + return ( + + + {t("bodyshop.labels.2tiername")} + {t("bodyshop.labels.2tiersource")} + + + ); + }} + , + + + , + + + , + + {ReceivableCustomFieldSelect} + , + + {ReceivableCustomFieldSelect} + , + + {ReceivableCustomFieldSelect} + , + { + return { + required: getFieldValue("enforce_class"), + //message: t("general.validation.required"), + type: "array" + }; + } + ]} + > + + , + + + , + InstanceRenderManager({ + imex: ( + + + + ) + }), + + + , + ...(HasFeatureAccess({ featureName: "bills", bodyshop }) + ? [ + InstanceRenderManager({ + imex: ( + + + + ) + }), + + + , + + + + ] + : []), + + + + ] + : []), + ...(ADPPayroll.treatment === "on" + ? [ + + + + ] + : []) + ] + : []) + ]} + {(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && ( <> - {bodyshop.cdk_dealerid && ( - {form.getFieldValue("cdk_dealerid")} - )} - {bodyshop.pbs_serialnumber && ( - - {form.getFieldValue("pbs_serialnumber")} - - )} + + {bodyshop.cdk_dealerid && ( + + {form.getFieldValue("cdk_dealerid")} + + )} + {bodyshop.pbs_serialnumber && ( + + {form.getFieldValue("pbs_serialnumber")} + + )} + )} - {bodyshop.pbs_serialnumber && ( - <> - - {form.getFieldValue("pbs_serialnumber")} - - - )} - - {[ - ...(HasFeatureAccess({ featureName: "export", bodyshop }) - ? [ - - - , - InstanceRenderManager({ - imex: ( - - {() => ( - - - - )} - - ) - }), - - - , - - - 2 - 3 - - , - - {() => { - return ( - - - {t("bodyshop.labels.2tiername")} - {t("bodyshop.labels.2tiersource")} - - - ); - }} - , - - - , - - - - ] - : []), - - - , - - - , - InstanceRenderManager({ - imex: ( - - - - ) - }), - - - , - ...(HasFeatureAccess({ featureName: "bills", bodyshop }) - ? [ - InstanceRenderManager({ - imex: ( - - - - ) - }), - - - , - - - - ] - : []), - - - , - - - , - ...(ClosingPeriod.treatment === "on" - ? [ - - - - ] - : []), - ...(ADPPayroll.treatment === "on" - ? [ - - - - ] - : []), - ...(ADPPayroll.treatment === "on" - ? [ - - - - ] - : []) - ] - : []), - - - - ]} - {HasFeatureAccess({ featureName: "export", bodyshop }) && ( <> From 05414d9177aba915be5ac09514857c829604d26a Mon Sep 17 00:00:00 2001 From: Dave Date: Fri, 2 Jan 2026 17:17:11 -0500 Subject: [PATCH 4/5] Revert "Merged in feature/IO-3484-Shop-Config-Account-Move (pull request #2769)" This reverts commit 0f5dd02d75881a6f46b07f73a2178f13544c340f, reversing changes made to 2eca0852849a9678c8c551927819cd668c5418b6. --- ...p-info.responsibilitycenters.component.jsx | 589 +++++++++--------- 1 file changed, 295 insertions(+), 294 deletions(-) diff --git a/client/src/components/shop-info/shop-info.responsibilitycenters.component.jsx b/client/src/components/shop-info/shop-info.responsibilitycenters.component.jsx index 40c5a229d..38437a9aa 100644 --- a/client/src/components/shop-info/shop-info.responsibilitycenters.component.jsx +++ b/client/src/components/shop-info/shop-info.responsibilitycenters.component.jsx @@ -70,302 +70,16 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) { return (
- - {[ - ...(HasFeatureAccess({ featureName: "export", bodyshop }) - ? !bodyshop.cdk_dealerid && !bodyshop.pbs_serialnumber - ? [ - - - , - InstanceRenderManager({ - imex: ( - - {() => ( - - - - )} - - ) - }), - - - , - - - 2 - 3 - - , - - {() => { - return ( - - - {t("bodyshop.labels.2tiername")} - {t("bodyshop.labels.2tiersource")} - - - ); - }} - , - - - , - - - , - - {ReceivableCustomFieldSelect} - , - - {ReceivableCustomFieldSelect} - , - - {ReceivableCustomFieldSelect} - , - { - return { - required: getFieldValue("enforce_class"), - //message: t("general.validation.required"), - type: "array" - }; - } - ]} - > - - , - - - , - InstanceRenderManager({ - imex: ( - - - - ) - }), - - - , - ...(HasFeatureAccess({ featureName: "bills", bodyshop }) - ? [ - InstanceRenderManager({ - imex: ( - - - - ) - }), - - - , - - - - ] - : []), - - - - ] - : []), - ...(ADPPayroll.treatment === "on" - ? [ - - - - ] - : []) - ] - : []) - ]} - {(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && ( <> - - {bodyshop.cdk_dealerid && ( - - {form.getFieldValue("cdk_dealerid")} - - )} - {bodyshop.pbs_serialnumber && ( - - {form.getFieldValue("pbs_serialnumber")} - - )} - + {bodyshop.cdk_dealerid && ( + {form.getFieldValue("cdk_dealerid")} + )} + {bodyshop.pbs_serialnumber && ( + + {form.getFieldValue("pbs_serialnumber")} + + )} )} + {bodyshop.pbs_serialnumber && ( + <> + + {form.getFieldValue("pbs_serialnumber")} + + + )} + + {[ + ...(HasFeatureAccess({ featureName: "export", bodyshop }) + ? [ + + + , + InstanceRenderManager({ + imex: ( + + {() => ( + + + + )} + + ) + }), + + + , + + + 2 + 3 + + , + + {() => { + return ( + + + {t("bodyshop.labels.2tiername")} + {t("bodyshop.labels.2tiersource")} + + + ); + }} + , + + + , + + + + ] + : []), + + + , + + + , + InstanceRenderManager({ + imex: ( + + + + ) + }), + + + , + ...(HasFeatureAccess({ featureName: "bills", bodyshop }) + ? [ + InstanceRenderManager({ + imex: ( + + + + ) + }), + + + , + + + + ] + : []), + + + , + + + , + ...(ClosingPeriod.treatment === "on" + ? [ + + + + ] + : []), + ...(ADPPayroll.treatment === "on" + ? [ + + + + ] + : []), + ...(ADPPayroll.treatment === "on" + ? [ + + + + ] + : []) + ] + : []), + + + + ]} + {HasFeatureAccess({ featureName: "export", bodyshop }) && ( <> From 4a22aeca4659aed7d9c8beaa41d7c31a7da48733 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Fri, 2 Jan 2026 14:51:13 -0800 Subject: [PATCH 5/5] IO-3431 Jobs Image Gallery Signed-off-by: Allan Carr --- ...s-documents-imgproxy-gallery.component.jsx | 38 ++---- ...ts-imgproxy-gallery.external.component.jsx | 125 +++++++++++++++--- ...jobs-documents-local-gallery.container.jsx | 52 +++----- .../local-media-grid.component.jsx | 27 +++- 4 files changed, 154 insertions(+), 88 deletions(-) diff --git a/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.component.jsx b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.component.jsx index 1c14eb486..f2f5fbfd5 100644 --- a/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.component.jsx +++ b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.component.jsx @@ -1,10 +1,9 @@ -import { EditFilled, FileExcelFilled, SyncOutlined } from "@ant-design/icons"; +import { EditFilled, SyncOutlined } from "@ant-design/icons"; import { Button, Card, Col, Row, Space } from "antd"; import axios from "axios"; import i18n from "i18next"; import { isFunction } from "lodash"; import { useCallback, useEffect, useState } from "react"; -import { Gallery } from "react-grid-gallery"; import { useTranslation } from "react-i18next"; import Lightbox from "react-image-lightbox"; import "react-image-lightbox/style.css"; @@ -18,19 +17,13 @@ import JobsDocumentsDownloadButton from "./jobs-document-imgproxy-gallery.downlo import JobsDocumentsGalleryReassign from "./jobs-document-imgproxy-gallery.reassign.component"; import JobsDocumentsDeleteButton from "./jobs-documents-imgproxy-gallery.delete.component"; import JobsDocumentsGallerySelectAllComponent from "./jobs-documents-imgproxy-gallery.selectall.component"; +import LocalMediaGrid from "../jobs-documents-local-gallery/local-media-grid.component"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop }); const mapDispatchToProps = () => ({}); -/* -################################################################################################ - Developer Note: - Known Technical Debt Item - Modifications to this code requires complementary changes to the Cloudinary code. Cloudinary code will be removed upon completed migration. -################################################################################################ -*/ function JobsDocumentsImgproxyComponent({ bodyshop, data, @@ -119,17 +112,12 @@ function JobsDocumentsImgproxyComponent({ )} - { setModalState({ open: true, index: index }); - // window.open( - // item.fullsize, - // "_blank", - // "toolbar=0,location=0,menubar=0" - // ); }} - onSelect={(index) => { + onToggle={(index) => { setGalleryImages({ ...galleryImages, images: galleryImages.images.map((g, idx) => @@ -137,30 +125,26 @@ function JobsDocumentsImgproxyComponent({ ) }); }} + minColumns={4} + expandHeight={true} /> - { - return { - backgroundImage: , - height: "100%", - width: "100%", - cursor: "pointer" - }; - }} onClick={(index) => { window.open(galleryImages.other[index].source, "_blank", "toolbar=0,location=0,menubar=0"); }} - onSelect={(index) => { + onToggle={(index) => { setGalleryImages({ ...galleryImages, other: galleryImages.other.map((g, idx) => (index === idx ? { ...g, isSelected: !g.isSelected } : g)) }); }} + minColumns={4} + expandHeight={true} /> @@ -221,6 +205,7 @@ export const fetchImgproxyThumbnails = async ({ setStateCallback, jobId, billId, width: 225, isSelected: false, key: value.key, + filename: value.key, extension: value.extension, id: value.id, type: value.type, @@ -259,6 +244,7 @@ export const fetchImgproxyThumbnails = async ({ setStateCallback, jobId, billId, isSelected: false, extension: value.extension, key: value.key, + filename: value.key, id: value.id, type: value.type, size: value.size diff --git a/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.external.component.jsx b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.external.component.jsx index 0458d3aac..884ac5ac3 100644 --- a/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.external.component.jsx +++ b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.external.component.jsx @@ -1,31 +1,112 @@ -import { useEffect } from "react"; -import { Gallery } from "react-grid-gallery"; -import { fetchImgproxyThumbnails } from "./jobs-documents-imgproxy-gallery.component"; +import { useEffect, useMemo, useState, useCallback } from "react"; +import axios from "axios"; +import { useTranslation } from "react-i18next"; +import LocalMediaGrid from "../jobs-documents-local-gallery/local-media-grid.component"; +import LoadingSpinner from "../loading-spinner/loading-spinner.component"; -/* -################################################################################################ - Developer Note: - Known Technical Debt Item - Modifications to this code requires complementary changes to the Cloudinary code. Cloudinary code will be removed upon completed migration. -################################################################################################ -*/ - -function JobsDocumentImgproxyGalleryExternal({ jobId, externalMediaState }) { +function JobsDocumentImgproxyGalleryExternal({ jobId, externalMediaState, context = "chat" }) { const [galleryImages, setgalleryImages] = externalMediaState; + const [rawMedia, setRawMedia] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const { t } = useTranslation(); + + const fetchThumbnails = useCallback(async () => { + const result = await axios.post("/media/imgproxy/thumbnails", { jobid: jobId }); + return result.data; + }, [jobId]); useEffect(() => { - if (jobId) fetchImgproxyThumbnails({ setStateCallback: setgalleryImages, jobId, imagesOnly: true }); - }, [jobId, setgalleryImages]); + if (!jobId) return; + setIsLoading(true); + fetchThumbnails() + .then(setRawMedia) + .catch(console.error) + .finally(() => setIsLoading(false)); + }, [jobId, fetchThumbnails]); + + const documents = useMemo(() => { + return rawMedia + .filter((v) => v.type?.startsWith("image")) + .map((v) => ({ + src: v.thumbnailUrl, + thumbnail: v.thumbnailUrl, + fullsize: v.originalUrl, + width: 225, + height: 225, + thumbnailWidth: 225, + thumbnailHeight: 225, + caption: v.key, + filename: v.key, + // additional properties if needed + key: v.key, + id: v.id, + type: v.type, + size: v.size, + extension: v.extension + })); + }, [rawMedia]); + + useEffect(() => { + const prevSelection = new Map(galleryImages.map((p) => [p.filename, p.isSelected])); + const nextImages = documents.map((d) => ({ ...d, isSelected: prevSelection.get(d.filename) || false })); + // Micro-optimization: if array length and each filename + selection flag match, skip creating a new array. + if (galleryImages.length === nextImages.length) { + let identical = true; + for (let i = 0; i < nextImages.length; i++) { + if ( + galleryImages[i].filename !== nextImages[i].filename || + galleryImages[i].isSelected !== nextImages[i].isSelected + ) { + identical = false; + break; + } + } + if (identical) { + setIsLoading(false); // ensure loading stops even on no-change + return; + } + } + setgalleryImages(nextImages); + setIsLoading(false); // stop loading after transform regardless of emptiness + }, [documents, setgalleryImages, galleryImages, jobId]); + + const handleToggle = useCallback( + (idx) => { + setgalleryImages((imgs) => imgs.map((g, gIdx) => (gIdx === idx ? { ...g, isSelected: !g.isSelected } : g))); + }, + [setgalleryImages] + ); + + const messageStyle = { textAlign: "center", padding: "1rem" }; + + if (!jobId) { + return ( +
+
No job selected.
+
+ ); + } return ( -
- { - setgalleryImages(galleryImages.map((g, idx) => (index === idx ? { ...g, isSelected: !g.isSelected } : g))); - }} - /> +
+ {isLoading && galleryImages.length === 0 && ( +
+ +
+ )} + {galleryImages.length > 0 && ( + + )} + {galleryImages.length > 0 && ( +
+ {`${t("general.labels.media")}: ${galleryImages.length}`} +
+ )}
); } diff --git a/client/src/components/jobs-documents-local-gallery/jobs-documents-local-gallery.container.jsx b/client/src/components/jobs-documents-local-gallery/jobs-documents-local-gallery.container.jsx index da94678a8..4ef9cb881 100644 --- a/client/src/components/jobs-documents-local-gallery/jobs-documents-local-gallery.container.jsx +++ b/client/src/components/jobs-documents-local-gallery/jobs-documents-local-gallery.container.jsx @@ -1,7 +1,6 @@ -import { EditFilled, FileExcelFilled, SyncOutlined } from "@ant-design/icons"; +import { EditFilled, SyncOutlined } from "@ant-design/icons"; import { Alert, Button, Card, Col, Row, Space } from "antd"; import { useEffect, useState } from "react"; -import { Gallery } from "react-grid-gallery"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; @@ -14,6 +13,7 @@ import JobsDocumentsLocalDeleteButton from "./jobs-documents-local-gallery.delet import JobsLocalGalleryDownloadButton from "./jobs-documents-local-gallery.download"; import JobsDocumentsLocalGalleryReassign from "./jobs-documents-local-gallery.reassign.component"; import JobsDocumentsLocalGallerySelectAllComponent from "./jobs-documents-local-gallery.selectall.component"; +import LocalMediaGrid from "./local-media-grid.component"; import Lightbox from "react-image-lightbox"; import "react-image-lightbox/style.css"; @@ -132,54 +132,34 @@ export function JobsDocumentsLocalGallery({ - + )} + { - toggleMediaSelected({ jobid: job.id, filename: image.filename }); - }} - {...(optimized && { - customControls: [ - - ] - })} onClick={(index) => { setModalState({ open: true, index: index }); - // const media = allMedia[job.id].find( - // (m) => m.optimized === item.src - // ); - - // window.open( - // media ? media.fullsize : item.fullsize, - // "_blank", - // "toolbar=0,location=0,menubar=0" - // ); }} + onToggle={(index) => { + toggleMediaSelected({ jobid: job.id, filename: jobMedia.images[index].filename }); + }} + minColumns={4} + expandHeight={true} /> - { - return { - backgroundImage: , - height: "100%", - width: "100%", - cursor: "pointer" - }; - }} onClick={(index) => { window.open(jobMedia.other[index].fullsize, "_blank", "toolbar=0,location=0,menubar=0"); }} - onSelect={(index, image) => { - toggleMediaSelected({ jobid: job.id, filename: image.filename }); + onToggle={(index) => { + toggleMediaSelected({ jobid: job.id, filename: jobMedia.other[index].filename }); }} + minColumns={4} + expandHeight={true} /> diff --git a/client/src/components/jobs-documents-local-gallery/local-media-grid.component.jsx b/client/src/components/jobs-documents-local-gallery/local-media-grid.component.jsx index 277351296..f4c671f4f 100644 --- a/client/src/components/jobs-documents-local-gallery/local-media-grid.component.jsx +++ b/client/src/components/jobs-documents-local-gallery/local-media-grid.component.jsx @@ -6,15 +6,18 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; * Props: * - images: Array<{ src, fullsize, filename?, isSelected? }> * - onToggle(index) + * - onClick(index) optional for viewing */ export function LocalMediaGrid({ images, onToggle, + onClick, thumbSize = 100, gap = 8, minColumns = 3, maxColumns = 12, - context = "default" + context = "default", + expandHeight = false }) { const containerRef = useRef(null); const [cols, setCols] = useState(() => { @@ -114,8 +117,7 @@ export function LocalMediaGrid({ display: "grid", gridTemplateColumns, gap, - maxHeight: 420, - overflowY: "auto", + ...(expandHeight ? {} : { maxHeight: 420, overflowY: "auto" }), overflowX: "hidden", padding: 4, justifyContent: justifyMode, @@ -131,7 +133,7 @@ export function LocalMediaGrid({ role="listitem" tabIndex={0} aria-label={img.filename || `image ${idx + 1}`} - onClick={() => onToggle(idx)} + onClick={() => onClick ? onClick(idx) : onToggle(idx)} onKeyDown={(e) => handleKeyDown(e, idx)} style={{ position: "relative", @@ -197,6 +199,23 @@ export function LocalMediaGrid({ }} /> )} + {onClick && ( + { + e.stopPropagation(); + onToggle(idx); + }} + onClick={(e) => e.stopPropagation()} + style={{ + position: 'absolute', + top: 5, + right: 5, + zIndex: 2 + }} + /> + )}
))} {/* No placeholders needed; layout uses auto-fit for non-chat or fixed columns for chat */}