From 2d9b80971c9ae8b0df971ed26139052c4d86f6c9 Mon Sep 17 00:00:00 2001 From: kaushik Date: Thu, 23 Jul 2026 10:27:10 +0530 Subject: [PATCH] anton screenshot --- src/api/activityFeedsService.ts | 8 ++ src/pages/audit-session/AuditSession.tsx | 97 ++++++++++++++++++++++-- 2 files changed, 100 insertions(+), 5 deletions(-) diff --git a/src/api/activityFeedsService.ts b/src/api/activityFeedsService.ts index 3a9ad8e..e5ae248 100644 --- a/src/api/activityFeedsService.ts +++ b/src/api/activityFeedsService.ts @@ -93,6 +93,14 @@ export const activityFeedsService = { return response; }, + // Manual test-frame capture: extract the frame at `timestamp` (seconds) from + // the record's video, store it as a new image, and repoint Frame_Test at it. + // Returns { frame_test } - the new relative path to display/use. + captureFrame: async (payload: { site_id: number | string; anomaly_id: number; table: 'audits' | 'rectification'; timestamp: number }) => { + const response = await axiosClient.post('/api/audit/auditor/anomaly/capture-frame', payload); + return response; + }, + getOrganizationSites: async (org_id: string) => { const response = await axiosClient.get('/api/audit/Master/site', { params: { org_id } diff --git a/src/pages/audit-session/AuditSession.tsx b/src/pages/audit-session/AuditSession.tsx index 921e357..0025133 100644 --- a/src/pages/audit-session/AuditSession.tsx +++ b/src/pages/audit-session/AuditSession.tsx @@ -104,6 +104,10 @@ export const AuditSession: React.FC = () => { const [currentIndex, setCurrentIndex] = useState(0); const [loading, setLoading] = useState(true); const [carouselIndex, setCarouselIndex] = useState(0); + // Manual test-frame capture: ref to read the playing video's currentTime, + // and a flag for the in-flight extraction round-trip. + const videoRef = useRef(null); + const [capturing, setCapturing] = useState(false); // Form states const [selectedCategory, setSelectedCategory] = useState(null); @@ -564,6 +568,77 @@ export const AuditSession: React.FC = () => { return items; }, [currentAnomaly]); + // Grab the frame currently showing in the video and make it the record's + // Frame_Test. The backend extracts it server-side, stores it as a NEW file, + // and repoints Frame_Test (original preserved). We mirror that into local + // state so the carousel shows the new frame immediately - everything else + // (audit → archive → anomaly row) already reads Frame_Test. + const handleCaptureFrame = useCallback(async () => { + const v = videoRef.current; + if (!v || !currentAnomaly) return; + const timestamp = v.currentTime; + const toast = useToastStore.getState().showToast; + setCapturing(true); + try { + const res: any = await activityFeedsService.captureFrame({ + site_id: currentAnomaly.site_id, + anomaly_id: currentAnomaly.id, + table: isRectActive ? 'rectification' : 'audits', + timestamp, + }); + const newFrame = res?.frame_test; + if (!newFrame) throw new Error('No frame returned'); + const newExtra = res?.extra_image; + + // Index the new frame will occupy in the carousel: it replaces the + // current test frame in place (or is appended). Switch to it explicitly - + // the auditor clicks while viewing the video, and we don't want to rely + // on the [currentAnomaly] effect to move off the video item. + const oldImages: any[] = Array.isArray(currentAnomaly.images) ? currentAnomaly.images : []; + let targetIdx = 0; + if (oldImages.length > 0) { + const found = oldImages.findIndex((i: any) => i.selected || i.src === currentAnomaly.Frame_Test); + targetIdx = found >= 0 ? found : oldImages.length; + } + + // Repoint the record everywhere it's cached. Frame_Test drives the CDN + // URL; Extra_Image drives the carousel; images is the built carousel. + const patchOne = (a: any) => { + if (a.id !== currentAnomaly.id) return a; + const updated: any = { ...a, Frame_Test: newFrame, Extra_Image: newExtra ?? a.Extra_Image }; + if (Array.isArray(a.images) && a.images.length > 0) { + let replaced = false; + updated.images = a.images.map((img: any) => { + if (img.selected || img.src === a.Frame_Test) { replaced = true; return { ...img, src: newFrame, selected: true }; } + return { ...img, selected: false }; + }); + if (!replaced) updated.images = [...a.images.map((i: any) => ({ ...i, selected: false })), { src: newFrame, selected: true }]; + } else { + updated.images = [{ src: newFrame, selected: true }]; + } + return updated; + }; + + // Session state first, then the PERSISTED feed-tab caches (both anomalies + // AND anomaliesCopy) - otherwise the feed / re-opening the record shows + // the stale pre-capture image (same sync as handleSaveAssetEdit). Only + // same-mode tabs (audits vs rectification ids can collide). + setAnomalies((prev: any[]) => prev.map(patchOne)); + const feed = useFeedStore.getState(); + feed.setTabs(feed.tabs.map((t: any) => + !!t.isRectificationTab === isRectActive + ? { ...t, anomalies: (t.anomalies || []).map(patchOne), anomaliesCopy: (t.anomaliesCopy || []).map(patchOne) } + : t + )); + setCarouselIndex(targetIdx); + toast('Frame captured — now the test image for this record', 'success'); + } catch (e: any) { + toast(e?.response?.data?.error || e?.message || 'Frame capture failed', 'error'); + } finally { + setCapturing(false); + } + }, [currentAnomaly, isRectActive]); + // Single navigation entry point (arrows, Enter-advance, progress-tile // clicks). If the target record has a staged verdict, pre-fill the form // with it so it can be reviewed/overwritten before Bulk Submit. @@ -1656,11 +1731,23 @@ export const AuditSession: React.FC = () => { {mediaItems.length > 0 ? ( <> {mediaItems[carouselIndex]?.type === 'video' ? ( -