From b4e0dd6d656c46ccd73a6d7e8710d11c8613d7c7 Mon Sep 17 00:00:00 2001 From: vladtechno Date: Wed, 20 May 2026 13:11:04 +1000 Subject: [PATCH] =?UTF-8?q?inspection-review:=20TT-=D1=84=D0=BE=D1=82?= =?UTF-8?q?=D0=BE=20=D0=BF=D0=BE=D0=B2=D1=80=D0=B5=D0=B6=D0=B4=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B9,=20=D1=81=D1=87=D1=91=D1=82=D1=87=D0=B8=D0=BA=20?= =?UTF-8?q?=D0=B8=20lightbox=20=D0=B4=D0=BB=D1=8F=20=D0=B2=D1=81=D0=B5?= =?UTF-8?q?=D1=85=20=D0=BC=D0=B0=D1=80=D0=BA=D0=B5=D1=80=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Photo counter теперь учитывает уникальные tt_image_id из markers, не только ins.photos + photo_id. На осмотрах, где почти все маркеры пришли из Element carry-over, счётчик показывал 1 вместо 15+ — теперь корректно. 2. В галерее «Фото» добавлена секция карточек для TT-image carry-over (label «из Element»). Картинки тянутся через ttImageUrl(hash), кликабельны. 3. Lightbox state поменялся с `number | null` на дискриминированный union { kind: 'photo', id } | { kind: 'tt', ttId }. Полноразмерное превью выбирает между AuthImg (наши MinIO-фото) и обычным (Element). 4. Превью маркеров в секции «Метки» теперь кликабельны (раньше только грид с основными фото открывал lightbox). Работает и для photo_id, и для tt_image_id маркеров. Backend-зависимость: MarkerOut.tt_image_id (Pydantic) и MechanicDamageMarker.tt_image_id (ORM) добавлены в PremiumCRM коммитом d405e17 на crm.pptaxi.ru — без них фронт получает tt_image_id=undefined. --- mechanic-pwa/frontend/src/api/inspections.ts | 2 + .../frontend/src/pages/InspectionReview.tsx | 126 +++++++++++++++++- 2 files changed, 123 insertions(+), 5 deletions(-) diff --git a/mechanic-pwa/frontend/src/api/inspections.ts b/mechanic-pwa/frontend/src/api/inspections.ts index b933f09..722909c 100644 --- a/mechanic-pwa/frontend/src/api/inspections.ts +++ b/mechanic-pwa/frontend/src/api/inspections.ts @@ -66,6 +66,8 @@ export interface InspectionDetail { photos: PhotoSummary[]; markers: MarkerSummary[]; mileage?: number | null; + /** ФИО механика, выполнившего осмотр (lookup users.full_name по performed_by). */ + inspector_name?: string | null; } export async function createInspection( diff --git a/mechanic-pwa/frontend/src/pages/InspectionReview.tsx b/mechanic-pwa/frontend/src/pages/InspectionReview.tsx index b8ad5fe..b9047da 100644 --- a/mechanic-pwa/frontend/src/pages/InspectionReview.tsx +++ b/mechanic-pwa/frontend/src/pages/InspectionReview.tsx @@ -1,3 +1,4 @@ +import { useState } from "react"; import { useParams, useNavigate } from "react-router-dom"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { toast } from "sonner"; @@ -114,6 +115,12 @@ export default function InspectionReview() { staleTime: Infinity, }); + // Lightbox: либо ID нашего фото в MinIO (через AuthImg), либо vendor TT + // image hash (через ttImageUrl). + const [preview, setPreview] = useState< + { kind: "photo"; id: number } | { kind: "tt"; ttId: string } | null + >(null); + if (isLoading) return
Загружаю…
; if (isError || !ins) return
Осмотр не найден.
; @@ -122,6 +129,25 @@ export default function InspectionReview() { const photosById = new Map(); for (const p of ins.photos) photosById.set(p.id, p); + // Все уникальные фото осмотра: основные + photo_id из маркеров (наши локальные + // фото повреждений) + tt_image_id из маркеров (carry-over из Element). Без + // tt_image_id counter недоучитывал импортированные дефекты — видно на + // осмотрах, где почти все маркеры пришли из вендора. + const markerPhotoIds = ins.markers + .map((m: MarkerSummary) => m.photo_id) + .filter((id): id is number => id != null); + const markerTtIds = ins.markers + .map((m: MarkerSummary) => m.tt_image_id) + .filter((id): id is string => !!id); + const uniquePhotoIds = Array.from( + new Set([...ins.photos.map((p: PhotoSummary) => p.id), ...markerPhotoIds]) + ); + const uniqueTtIds = Array.from(new Set(markerTtIds)); + const totalPhotoCount = uniquePhotoIds.length + uniqueTtIds.length; + // Дополнительные фото-карточки в галерее: photo_id маркеров, которых нет в + // основном списке (`ins.photos` — это side-photos осмотра). + const extraMarkerPhotos = markerPhotoIds.filter((id) => !photosById.has(id)); + return (
+ {ins.inspector_name && ( +
+ Механик: + {ins.inspector_name} +
+ )}
Начат: {new Date(ins.started_at).toLocaleString("ru-RU")} @@ -179,14 +211,18 @@ export default function InspectionReview() {

- Фото ({ins.photos.length}) + Фото ({totalPhotoCount})

- {ins.photos.length === 0 ? ( + {totalPhotoCount === 0 ? (
Нет фото.
) : (
{ins.photos.map((p: PhotoSummary) => ( - + setPreview({ kind: "photo", id: p.id })} + > {p.side}
))} + {/* Фото-маркеров, которых нет в основном списке (наши локальные) */} + {extraMarkerPhotos.map((id) => ( + setPreview({ kind: "photo", id })} + > + +
повреждение
+
+ ))} + {/* TT-фото carry-over дефектов из Element */} + {uniqueTtIds.map((ttId) => ( + setPreview({ kind: "tt", ttId })} + > + повреждение (Element) +
из Element
+
+ ))}
)}
@@ -249,7 +316,12 @@ export default function InspectionReview() { )} {m.photo_id ? ( -
+
+ setPreview({ kind: "photo", id: m.photo_id as number }) + } + >
) : m.tt_image_id ? ( -
+
+ setPreview({ kind: "tt", ttId: m.tt_image_id as string }) + } + > Фото из Element Mechanic
)} + + {/* Lightbox preview — click photo to enlarge, click background to close. + Соответствует UX в TtStateDetail (импортированные из Element осмотры). */} + {preview != null && ( +
setPreview(null)} + > +
+ +
+
e.stopPropagation()} + > + {preview.kind === "photo" ? ( + + ) : ( + Просмотр (Element) + )} +
+
+ )}
); }