diff --git a/client/src/components/job-detail-cards/job-detail-cards.documents.component.jsx b/client/src/components/job-detail-cards/job-detail-cards.documents.component.jsx index 984a64ca0..786d8b598 100644 --- a/client/src/components/job-detail-cards/job-detail-cards.documents.component.jsx +++ b/client/src/components/job-detail-cards/job-detail-cards.documents.component.jsx @@ -1,13 +1,21 @@ import { Carousel } from "antd"; +import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component"; -import { GenerateThumbUrl } from "../jobs-documents-gallery/job-documents.utility"; +import { fetchImgproxyThumbnails } from "../jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.component"; import UpsellComponent, { upsellEnum } from "../upsell/upsell.component"; import CardTemplate from "./job-detail-cards.template.component"; export default function JobDetailCardsDocumentsComponent({ loading, data, bodyshop }) { const { t } = useTranslation(); const hasMediaAccess = HasFeatureAccess({ bodyshop, featureName: "media" }); + const [thumbnails, setThumbnails] = useState([]); + + useEffect(() => { + if (data?.id) { + fetchImgproxyThumbnails({ setStateCallback: setThumbnails, jobId: data.id, imagesOnly: true }); + } + }, [data?.id]); if (!data) return ( @@ -22,18 +30,19 @@ export default function JobDetailCardsDocumentsComponent({ loading, data, bodysh title={t("jobs.labels.cards.documents")} extraLink={`/manage/jobs/${data.id}?tab=documents`} > - {!hasMediaAccess && ( - - {data.documents.length > 0 ? ( + {!hasMediaAccess && } + {hasMediaAccess && ( + <> + {thumbnails.length > 0 ? ( - {data.documents.map((item) => ( - {item.name} + {thumbnails.map((item) => ( + {item.filename} ))} ) : (
{t("documents.errors.nodocuments")}
)} -
+ )} ); 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 884ac5ac3..badee7414 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,75 +1,24 @@ -import { useEffect, useMemo, useState, useCallback } from "react"; -import axios from "axios"; +import { useEffect, useState, useCallback } from "react"; import { useTranslation } from "react-i18next"; import LocalMediaGrid from "../jobs-documents-local-gallery/local-media-grid.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; +import { fetchImgproxyThumbnails } from "./jobs-documents-imgproxy-gallery.component"; 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]); + await fetchImgproxyThumbnails({ setStateCallback: setgalleryImages, jobId, imagesOnly: true }); + }, [jobId, setgalleryImages]); useEffect(() => { if (!jobId) return; setIsLoading(true); - fetchThumbnails() - .then(setRawMedia) - .catch(console.error) - .finally(() => setIsLoading(false)); + fetchThumbnails().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))); diff --git a/client/src/components/production-list-detail/production-list-detail.component.jsx b/client/src/components/production-list-detail/production-list-detail.component.jsx index 307e0409d..0bcb36a13 100644 --- a/client/src/components/production-list-detail/production-list-detail.component.jsx +++ b/client/src/components/production-list-detail/production-list-detail.component.jsx @@ -199,7 +199,7 @@ export function ProductionListDetail({ bodyshop, jobs, setPrintCenterContext, te {!bodyshop.uselocalmediaserver && ( <>
- + )}
diff --git a/server/job/job-totals-USA.js b/server/job/job-totals-USA.js index d1ff5abe6..7f9abb0a2 100644 --- a/server/job/job-totals-USA.js +++ b/server/job/job-totals-USA.js @@ -381,7 +381,12 @@ async function CalculateRatesTotals({ job, client }) { if (item.mod_lbr_ty) { //Check to see if it has 0 hours and a price instead. - if (item.lbr_op === "OP14" && item.act_price > 0 && (!item.part_type || item.mod_lb_hrs === 0) && !IsAdditionalCost(item)) { + if ( + item.lbr_op === "OP14" && + item.act_price > 0 && + (!item.part_type || item.mod_lb_hrs === 0) && + !IsAdditionalCost(item) + ) { //Scenario where SGI may pay out hours using a part price. if (!ret[item.mod_lbr_ty.toLowerCase()].total) { ret[item.mod_lbr_ty.toLowerCase()].base = Dinero(); @@ -943,9 +948,10 @@ function CalculateTaxesTotals(job, otherTotals) { amount: Math.round(stlTowing.t_amt * 100) }) ); + if (stlStorage) - taxableAmounts.TOW = taxableAmounts.TOW.add( - (taxableAmounts.TOW = Dinero({ + taxableAmounts.STOR = taxableAmounts.STOR.add( + (taxableAmounts.STOR = Dinero({ amount: Math.round(stlStorage.t_amt * 100) })) ); @@ -988,7 +994,7 @@ function CalculateTaxesTotals(job, otherTotals) { const pfo = job.cieca_pfo; Object.keys(taxableAmounts).map((key) => { try { - if (key.startsWith("PA")) { + if (key.startsWith("PA") && key !== "PAE") { const typeOfPart = key; // === "PAM" ? "PAC" : key; //At least one of these scenarios must be taxable. for (let tyCounter = 1; tyCounter <= 5; tyCounter++) {