mirror of
				https://github.com/blakeblackshear/frigate.git
				synced 2025-10-27 10:52:11 +01:00 
			
		
		
		
	Indicate on calendar which days have unreviewed activity (#10381)
This commit is contained in:
		
							parent
							
								
									3515361320
								
							
						
					
					
						commit
						8d4b9bc7ed
					
				| @ -10,8 +10,7 @@ import { | ||||
|   DropdownMenuSeparator, | ||||
|   DropdownMenuTrigger, | ||||
| } from "../ui/dropdown-menu"; | ||||
| import { Calendar } from "../ui/calendar"; | ||||
| import { ReviewFilter } from "@/types/review"; | ||||
| import { ReviewFilter, ReviewSummary } from "@/types/review"; | ||||
| import { getEndOfDayTimestamp } from "@/utils/dateUtil"; | ||||
| import { useFormattedTimestamp } from "@/hooks/use-date-utils"; | ||||
| import { FaCalendarAlt, FaFilter, FaVideo } from "react-icons/fa"; | ||||
| @ -20,15 +19,18 @@ import { Drawer, DrawerContent, DrawerTrigger } from "../ui/drawer"; | ||||
| import { Switch } from "../ui/switch"; | ||||
| import { Label } from "../ui/label"; | ||||
| import FilterCheckBox from "./FilterCheckBox"; | ||||
| import ReviewActivityCalendar from "../overlay/ReviewActivityCalendar"; | ||||
| 
 | ||||
| const ATTRIBUTES = ["amazon", "face", "fedex", "license_plate", "ups"]; | ||||
| 
 | ||||
| type ReviewFilterGroupProps = { | ||||
|   reviewSummary?: ReviewSummary; | ||||
|   filter?: ReviewFilter; | ||||
|   onUpdateFilter: (filter: ReviewFilter) => void; | ||||
| }; | ||||
| 
 | ||||
| export default function ReviewFilterGroup({ | ||||
|   reviewSummary, | ||||
|   filter, | ||||
|   onUpdateFilter, | ||||
| }: ReviewFilterGroupProps) { | ||||
| @ -102,6 +104,7 @@ export default function ReviewFilterGroup({ | ||||
|         }} | ||||
|       /> | ||||
|       <CalendarFilterButton | ||||
|         reviewSummary={reviewSummary} | ||||
|         day={ | ||||
|           filter?.after == undefined ? undefined : new Date(filter.after * 1000) | ||||
|         } | ||||
| @ -273,22 +276,17 @@ function CamerasFilterButton({ | ||||
| } | ||||
| 
 | ||||
| type CalendarFilterButtonProps = { | ||||
|   reviewSummary?: ReviewSummary; | ||||
|   day?: Date; | ||||
|   updateSelectedDay: (day?: Date) => void; | ||||
| }; | ||||
| function CalendarFilterButton({ | ||||
|   reviewSummary, | ||||
|   day, | ||||
|   updateSelectedDay, | ||||
| }: CalendarFilterButtonProps) { | ||||
|   const disabledDates = useMemo(() => { | ||||
|     const tomorrow = new Date(); | ||||
|     tomorrow.setHours(tomorrow.getHours() + 24, -1, 0, 0); | ||||
|     const future = new Date(); | ||||
|     future.setFullYear(tomorrow.getFullYear() + 10); | ||||
|     return { from: tomorrow, to: future }; | ||||
|   }, []); | ||||
|   const selectedDate = useFormattedTimestamp( | ||||
|     day == undefined ? 0 : day?.getTime() / 1000, | ||||
|     day == undefined ? 0 : day?.getTime() / 1000 + 1, | ||||
|     "%b %-d", | ||||
|   ); | ||||
| 
 | ||||
| @ -302,14 +300,10 @@ function CalendarFilterButton({ | ||||
|   ); | ||||
|   const content = ( | ||||
|     <> | ||||
|       <Calendar | ||||
|         mode="single" | ||||
|         disabled={disabledDates} | ||||
|         selected={day} | ||||
|         showOutsideDays={false} | ||||
|         onSelect={(day) => { | ||||
|           updateSelectedDay(day); | ||||
|         }} | ||||
|       <ReviewActivityCalendar | ||||
|         reviewSummary={reviewSummary} | ||||
|         selectedDay={day} | ||||
|         onSelect={updateSelectedDay} | ||||
|       /> | ||||
|       <DropdownMenuSeparator /> | ||||
|       <div className="p-2 flex justify-center items-center"> | ||||
|  | ||||
							
								
								
									
										78
									
								
								web/src/components/overlay/ReviewActivityCalendar.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								web/src/components/overlay/ReviewActivityCalendar.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,78 @@ | ||||
| import { ReviewSummary } from "@/types/review"; | ||||
| import { Calendar } from "../ui/calendar"; | ||||
| import { useMemo } from "react"; | ||||
| import { FaCircle } from "react-icons/fa"; | ||||
| 
 | ||||
| type ReviewActivityCalendarProps = { | ||||
|   reviewSummary?: ReviewSummary; | ||||
|   selectedDay?: Date; | ||||
|   onSelect: (day?: Date) => void; | ||||
| }; | ||||
| export default function ReviewActivityCalendar({ | ||||
|   reviewSummary, | ||||
|   selectedDay, | ||||
|   onSelect, | ||||
| }: ReviewActivityCalendarProps) { | ||||
|   const disabledDates = useMemo(() => { | ||||
|     const tomorrow = new Date(); | ||||
|     tomorrow.setHours(tomorrow.getHours() + 24, -1, 0, 0); | ||||
|     const future = new Date(); | ||||
|     future.setFullYear(tomorrow.getFullYear() + 10); | ||||
|     return { from: tomorrow, to: future }; | ||||
|   }, []); | ||||
| 
 | ||||
|   return ( | ||||
|     <Calendar | ||||
|       mode="single" | ||||
|       disabled={disabledDates} | ||||
|       showOutsideDays={false} | ||||
|       selected={selectedDay} | ||||
|       onSelect={onSelect} | ||||
|       components={{ | ||||
|         DayContent: (date) => ( | ||||
|           <ReviewActivityDay reviewSummary={reviewSummary} day={date.date} /> | ||||
|         ), | ||||
|       }} | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| type ReviewActivityDayProps = { | ||||
|   reviewSummary?: ReviewSummary; | ||||
|   day: Date; | ||||
| }; | ||||
| function ReviewActivityDay({ reviewSummary, day }: ReviewActivityDayProps) { | ||||
|   const dayActivity = useMemo(() => { | ||||
|     if (!reviewSummary) { | ||||
|       return "none"; | ||||
|     } | ||||
| 
 | ||||
|     const allActivity = | ||||
|       reviewSummary[ | ||||
|         `${day.getFullYear()}-${("0" + (day.getMonth() + 1)).slice(-2)}-${("0" + day.getDate()).slice(-2)}` | ||||
|       ]; | ||||
| 
 | ||||
|     if (!allActivity) { | ||||
|       return "none"; | ||||
|     } | ||||
| 
 | ||||
|     if (allActivity.total_alert > allActivity.reviewed_alert) { | ||||
|       return "alert"; | ||||
|     } else if (allActivity.total_detection > allActivity.reviewed_detection) { | ||||
|       return "detection"; | ||||
|     } else { | ||||
|       return "none"; | ||||
|     } | ||||
|   }, [reviewSummary, day]); | ||||
| 
 | ||||
|   return ( | ||||
|     <div className="flex flex-col justify-center items-center"> | ||||
|       {day.getDate()} | ||||
|       {dayActivity != "none" && ( | ||||
|         <FaCircle | ||||
|           className={`size-2 ${dayActivity == "alert" ? "fill-severity_alert" : "fill-severity_detection"}`} | ||||
|         /> | ||||
|       )} | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
| @ -238,7 +238,11 @@ export default function EventView({ | ||||
|         </ToggleGroup> | ||||
| 
 | ||||
|         {selectedReviews.length <= 0 ? ( | ||||
|           <ReviewFilterGroup filter={filter} onUpdateFilter={updateFilter} /> | ||||
|           <ReviewFilterGroup | ||||
|             reviewSummary={reviewSummary} | ||||
|             filter={filter} | ||||
|             onUpdateFilter={updateFilter} | ||||
|           /> | ||||
|         ) : ( | ||||
|           <ReviewActionGroup | ||||
|             selectedReviews={selectedReviews} | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user