import { useEffect, useMemo, useRef, useState } from 'preact/hooks'; import { animated, useTransition } from '@react-spring/web'; import { api } from '../../services/apiClient'; function getBrowserApiBase() { const apiUrl = import.meta.env.PUBLIC_API_URL || ''; if (typeof window === 'undefined') { return apiUrl || 'http://localhost:8001/api/'; } try { if (apiUrl) { const parsed = new URL(apiUrl); const internalHosts = new Set(['backend', 'localhost', '127.0.0.1']); const isInternalDockerHost = parsed.hostname === 'backend'; if (!isInternalDockerHost) { return parsed.toString(); } } } catch { // fallback is handled below } return `${window.location.protocol}//${window.location.hostname}:8001/api/`; } export function resolveMediaUrl(pathOrUrl) { if (!pathOrUrl || typeof window === 'undefined') return ''; let finalSrc = String(pathOrUrl); const apiUrl = getBrowserApiBase(); const baseUrl = apiUrl.replace(/api\/?$/, ''); if (/^https?:\/\//i.test(finalSrc)) { if (finalSrc.includes('backend:8000')) { return finalSrc.replace('http://backend:8000/', baseUrl); } return finalSrc; } if (finalSrc.startsWith('/')) { return `${baseUrl.replace(/\/$/, '')}${finalSrc}`; } return new URL(finalSrc, apiUrl).toString(); } function isProtectedMediaUrl(url) { return typeof url === 'string' && /\/api\/fleet\//i.test(url); } function revokeObjectUrls(entries = {}) { Object.values(entries).forEach((url) => { if (typeof url === 'string' && url.startsWith('blob:')) { window.URL.revokeObjectURL(url); } }); } export function useAuthenticatedMediaSources(mediaPaths = []) { const [authenticatedSources, setAuthenticatedSources] = useState({}); const authenticatedSourcesRef = useRef({}); const resolvedMediaPaths = useMemo(() => { const unique = new Set(); if (!Array.isArray(mediaPaths)) { return []; } mediaPaths.forEach((item) => { const resolved = resolveMediaUrl(item); if (resolved) { unique.add(resolved); } }); return Array.from(unique); }, [mediaPaths]); const resolvedMediaPathsKey = useMemo(() => resolvedMediaPaths.join('|'), [resolvedMediaPaths]); useEffect(() => { authenticatedSourcesRef.current = authenticatedSources; }, [authenticatedSources]); useEffect(() => { let cancelled = false; const protectedPaths = resolvedMediaPaths.filter((url) => isProtectedMediaUrl(url)); if (protectedPaths.length === 0) { setAuthenticatedSources((previous) => { revokeObjectUrls(previous); return {}; }); return undefined; } (async () => { const nextSources = {}; const createdObjectUrls = []; const fetched = await Promise.all( protectedPaths.map(async (url) => { try { const blob = await api.get(url, { responseType: 'blob' }); const blobUrl = window.URL.createObjectURL(blob); createdObjectUrls.push(blobUrl); return [url, blobUrl]; } catch { return null; } }) ); fetched.forEach((entry) => { if (!entry) return; const [url, blobUrl] = entry; nextSources[url] = blobUrl; }); if (cancelled) { createdObjectUrls.forEach((blobUrl) => window.URL.revokeObjectURL(blobUrl)); return; } setAuthenticatedSources((previous) => { revokeObjectUrls(previous); return nextSources; }); })(); return () => { cancelled = true; }; }, [resolvedMediaPathsKey]); useEffect(() => () => { revokeObjectUrls(authenticatedSourcesRef.current); }, []); const getAuthenticatedMediaSrc = (pathOrUrl) => { const resolved = resolveMediaUrl(pathOrUrl); if (!resolved) { return ''; } return authenticatedSources[resolved] || resolved; }; return { getAuthenticatedMediaSrc, }; } export default function WorkOrderImageCarousel({ images = [], editMode = false, emptyLabel = 'Nema priložene tehničke dokumentacije' }) { const normalizedImages = useMemo(() => ( Array.isArray(images) ? images.filter(Boolean) : (typeof images === 'string' && images ? [images] : []) ), [images]); const [currentImgIdx, setCurrentImgIdx] = useState(0); const { getAuthenticatedMediaSrc } = useAuthenticatedMediaSources(normalizedImages); useEffect(() => { setCurrentImgIdx(0); }, [normalizedImages.length]); useEffect(() => { if (editMode || normalizedImages.length <= 1) return undefined; const interval = window.setInterval(() => { setCurrentImgIdx((prev) => (prev + 1) % normalizedImages.length); }, 3000); return () => window.clearInterval(interval); }, [normalizedImages.length, editMode]); if (normalizedImages.length === 0) { return (