From a510ea903675327dbb1d752403efbc066fe29272 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Wed, 5 Nov 2025 08:48:47 -0700 Subject: [PATCH] Review card refactor (#20813) * Use the review card in event timeline popover * Show review title in review card --- web/src/components/card/ReviewCard.tsx | 59 ++++++++++++-------- web/src/components/timeline/DetailStream.tsx | 6 +- web/src/components/timeline/EventSegment.tsx | 17 ++---- web/src/hooks/use-event-segment-utils.ts | 8 +-- web/src/views/recording/RecordingView.tsx | 2 +- 5 files changed, 51 insertions(+), 41 deletions(-) diff --git a/web/src/components/card/ReviewCard.tsx b/web/src/components/card/ReviewCard.tsx index 6337ac4a9..8fc4024db 100644 --- a/web/src/components/card/ReviewCard.tsx +++ b/web/src/components/card/ReviewCard.tsx @@ -38,6 +38,7 @@ import { Button, buttonVariants } from "../ui/button"; import { Trans, useTranslation } from "react-i18next"; import { cn } from "@/lib/utils"; import { LuCircle } from "react-icons/lu"; +import { MdAutoAwesome } from "react-icons/md"; type ReviewCardProps = { event: ReviewSegment; @@ -164,29 +165,33 @@ export default function ReviewCard({
-
- <> - - {event.data.objects.map((object) => { - return getIconForLabel( - object, - "size-3 text-primary dark:text-white", - ); - })} - {event.data.audio.map((audio) => { - return getIconForLabel( - audio, - "size-3 text-primary dark:text-white", - ); - })} - +
+ +
+ {event.data.objects.map((object, idx) => ( +
+ {getIconForLabel(object, "size-3 text-white")} +
+ ))} + {event.data.audio.map((audio, idx) => ( +
+ {getIconForLabel(audio, "size-3 text-white")} +
+ ))} +
{formattedDate}
@@ -213,6 +218,14 @@ export default function ReviewCard({ dense />
+ {event.data.metadata?.title && ( +
+ + + {event.data.metadata.title} + +
+ )}
); diff --git a/web/src/components/timeline/DetailStream.tsx b/web/src/components/timeline/DetailStream.tsx index 4b152aadb..5b45de19f 100644 --- a/web/src/components/timeline/DetailStream.tsx +++ b/web/src/components/timeline/DetailStream.tsx @@ -22,6 +22,7 @@ import { LuChevronRight, LuSettings, } from "react-icons/lu"; +import { MdAutoAwesome } from "react-icons/md"; import { getTranslatedLabel } from "@/utils/i18n"; import EventMenu from "@/components/timeline/EventMenu"; import { FrigatePlusDialog } from "@/components/overlay/dialog/FrigatePlusDialog"; @@ -410,8 +411,9 @@ function ReviewGroup({
{review.data.metadata?.title && ( -
- {review.data.metadata.title} +
+ + {review.data.metadata.title}
)}
diff --git a/web/src/components/timeline/EventSegment.tsx b/web/src/components/timeline/EventSegment.tsx index e04841540..368e0fad5 100644 --- a/web/src/components/timeline/EventSegment.tsx +++ b/web/src/components/timeline/EventSegment.tsx @@ -1,4 +1,3 @@ -import { useApiHost } from "@/api"; import { useTimelineUtils } from "@/hooks/use-timeline-utils"; import { useEventSegmentUtils } from "@/hooks/use-event-segment-utils"; import { ReviewSegment, ReviewSeverity } from "@/types/review"; @@ -18,6 +17,7 @@ import { HoverCardPortal } from "@radix-ui/react-hover-card"; import scrollIntoView from "scroll-into-view-if-needed"; import { MinimapBounds, Tick, Timestamp } from "./segment-metadata"; import useTapUtils from "@/hooks/use-tap-utils"; +import ReviewCard from "../card/ReviewCard"; type EventSegmentProps = { events: ReviewSegment[]; @@ -54,7 +54,7 @@ export function EventSegment({ displaySeverityType, shouldShowRoundedCorners, getEventStart, - getEventThumbnail, + getEvent, } = useEventSegmentUtils(segmentDuration, events, severityType); const { alignStartDateToTimeline, alignEndDateToTimeline } = useTimelineUtils( @@ -87,13 +87,11 @@ export function EventSegment({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [getEventStart, segmentTime]); - const apiHost = useApiHost(); - const { handleTouchStart } = useTapUtils(); - const eventThumbnail = useMemo(() => { - return getEventThumbnail(segmentTime); - }, [getEventThumbnail, segmentTime]); + const segmentEvent = useMemo(() => { + return getEvent(segmentTime); + }, [getEvent, segmentTime]); const timestamp = useMemo(() => new Date(segmentTime * 1000), [segmentTime]); const segmentKey = useMemo( @@ -252,10 +250,7 @@ export function EventSegment({ className="w-[250px] rounded-lg p-2 md:rounded-2xl" side="left" > - + {segmentEvent && } diff --git a/web/src/hooks/use-event-segment-utils.ts b/web/src/hooks/use-event-segment-utils.ts index 3ecafcded..b105c3bf8 100644 --- a/web/src/hooks/use-event-segment-utils.ts +++ b/web/src/hooks/use-event-segment-utils.ts @@ -191,8 +191,8 @@ export const useEventSegmentUtils = ( [events, getSegmentStart, getSegmentEnd, severityType], ); - const getEventThumbnail = useCallback( - (time: number): string => { + const getEvent = useCallback( + (time: number): ReviewSegment | undefined => { const matchingEvent = events.find((event) => { return ( time >= getSegmentStart(event.start_time) && @@ -201,7 +201,7 @@ export const useEventSegmentUtils = ( ); }); - return matchingEvent?.thumb_path ?? ""; + return matchingEvent; }, [events, getSegmentStart, getSegmentEnd, severityType], ); @@ -214,6 +214,6 @@ export const useEventSegmentUtils = ( getReviewed, shouldShowRoundedCorners, getEventStart, - getEventThumbnail, + getEvent, }; }; diff --git a/web/src/views/recording/RecordingView.tsx b/web/src/views/recording/RecordingView.tsx index 149237b63..0c13876e8 100644 --- a/web/src/views/recording/RecordingView.tsx +++ b/web/src/views/recording/RecordingView.tsx @@ -974,7 +974,7 @@ function Timeline({ ? "w-[100px] flex-shrink-0" : timelineType == "detail" ? "min-w-[20rem] max-w-[30%] flex-shrink-0 flex-grow-0 basis-[30rem] md:min-w-[20rem] md:max-w-[25%] lg:min-w-[30rem] lg:max-w-[33%]" - : "w-60 flex-shrink-0", + : "w-80 flex-shrink-0", ) : cn( timelineType == "timeline"