IO-3431 Fix Document in Drawer from Production Board
Signed-off-by: Allan Carr <allan@imexsystems.ca>
This commit is contained in:
@@ -1,13 +1,21 @@
|
|||||||
import { Carousel } from "antd";
|
import { Carousel } from "antd";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
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 UpsellComponent, { upsellEnum } from "../upsell/upsell.component";
|
||||||
import CardTemplate from "./job-detail-cards.template.component";
|
import CardTemplate from "./job-detail-cards.template.component";
|
||||||
|
|
||||||
export default function JobDetailCardsDocumentsComponent({ loading, data, bodyshop }) {
|
export default function JobDetailCardsDocumentsComponent({ loading, data, bodyshop }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const hasMediaAccess = HasFeatureAccess({ bodyshop, featureName: "media" });
|
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)
|
if (!data)
|
||||||
return (
|
return (
|
||||||
@@ -22,18 +30,19 @@ export default function JobDetailCardsDocumentsComponent({ loading, data, bodysh
|
|||||||
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`}
|
||||||
>
|
>
|
||||||
{!hasMediaAccess && (
|
{!hasMediaAccess && <UpsellComponent disableMask upsell={upsellEnum().media.general} />}
|
||||||
<UpsellComponent disableMask upsell={upsellEnum().media.general}>
|
{hasMediaAccess && (
|
||||||
{data.documents.length > 0 ? (
|
<>
|
||||||
|
{thumbnails.length > 0 ? (
|
||||||
<Carousel autoplay>
|
<Carousel autoplay>
|
||||||
{data.documents.map((item) => (
|
{thumbnails.map((item) => (
|
||||||
<img key={item.id} src={GenerateThumbUrl(item)} alt={item.name} />
|
<img key={item.id} src={item.src} alt={item.filename} />
|
||||||
))}
|
))}
|
||||||
</Carousel>
|
</Carousel>
|
||||||
) : (
|
) : (
|
||||||
<div>{t("documents.errors.nodocuments")}</div>
|
<div>{t("documents.errors.nodocuments")}</div>
|
||||||
)}
|
)}
|
||||||
</UpsellComponent>
|
</>
|
||||||
)}
|
)}
|
||||||
</CardTemplate>
|
</CardTemplate>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,75 +1,24 @@
|
|||||||
import { useEffect, useMemo, useState, useCallback } from "react";
|
import { useEffect, useState, useCallback } from "react";
|
||||||
import axios from "axios";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import LocalMediaGrid from "../jobs-documents-local-gallery/local-media-grid.component";
|
import LocalMediaGrid from "../jobs-documents-local-gallery/local-media-grid.component";
|
||||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||||
|
import { fetchImgproxyThumbnails } from "./jobs-documents-imgproxy-gallery.component";
|
||||||
|
|
||||||
function JobsDocumentImgproxyGalleryExternal({ jobId, externalMediaState, context = "chat" }) {
|
function JobsDocumentImgproxyGalleryExternal({ jobId, externalMediaState, context = "chat" }) {
|
||||||
const [galleryImages, setgalleryImages] = externalMediaState;
|
const [galleryImages, setgalleryImages] = externalMediaState;
|
||||||
const [rawMedia, setRawMedia] = useState([]);
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const fetchThumbnails = useCallback(async () => {
|
const fetchThumbnails = useCallback(async () => {
|
||||||
const result = await axios.post("/media/imgproxy/thumbnails", { jobid: jobId });
|
await fetchImgproxyThumbnails({ setStateCallback: setgalleryImages, jobId, imagesOnly: true });
|
||||||
return result.data;
|
}, [jobId, setgalleryImages]);
|
||||||
}, [jobId]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!jobId) return;
|
if (!jobId) return;
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
fetchThumbnails()
|
fetchThumbnails().finally(() => setIsLoading(false));
|
||||||
.then(setRawMedia)
|
|
||||||
.catch(console.error)
|
|
||||||
.finally(() => setIsLoading(false));
|
|
||||||
}, [jobId, fetchThumbnails]);
|
}, [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(
|
const handleToggle = useCallback(
|
||||||
(idx) => {
|
(idx) => {
|
||||||
setgalleryImages((imgs) => imgs.map((g, gIdx) => (gIdx === idx ? { ...g, isSelected: !g.isSelected } : g)));
|
setgalleryImages((imgs) => imgs.map((g, gIdx) => (gIdx === idx ? { ...g, isSelected: !g.isSelected } : g)));
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ export function ProductionListDetail({ bodyshop, jobs, setPrintCenterContext, te
|
|||||||
{!bodyshop.uselocalmediaserver && (
|
{!bodyshop.uselocalmediaserver && (
|
||||||
<>
|
<>
|
||||||
<div style={{ height: "8px" }} />
|
<div style={{ height: "8px" }} />
|
||||||
<JobDetailCardsDocumentsComponent loading={loading} data={data ? data.jobs_by_pk : null} />
|
<JobDetailCardsDocumentsComponent loading={loading} data={data ? data.jobs_by_pk : null} bodyshop={bodyshop} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user