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) => (
-
+ {thumbnails.map((item) => (
+
))}
) : (
{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++) {