diff --git a/web/.eslintrc b/web/.eslintrc index da39b9cdc..cf638d350 100644 --- a/web/.eslintrc +++ b/web/.eslintrc @@ -15,7 +15,8 @@ "rules": { "indent": ["error", 2, { "SwitchCase": 1 }], "comma-dangle": ["error", { "objects": "always-multiline", "arrays": "always-multiline" }], - "no-unused-vars": ["error", { "argsIgnorePattern": "^_" }] + "no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], + "no-console": "error" }, "overrides": [ { diff --git a/web/src/components/Calendar.jsx b/web/src/components/Calendar.jsx index bde7399f0..3525ce0f1 100644 --- a/web/src/components/Calendar.jsx +++ b/web/src/components/Calendar.jsx @@ -5,7 +5,7 @@ import ArrowRightDouble from '../icons/ArrowRightDouble'; const todayTimestamp = new Date().setHours(0, 0, 0, 0).valueOf(); -const Calendar = ({ onChange, calendarRef, close }) => { +const Calendar = ({ onChange, calendarRef, close, dateRange }) => { const keyRef = useRef([]); const date = new Date(); @@ -36,7 +36,7 @@ const Calendar = ({ onChange, calendarRef, close }) => { year, month, selectedDay: null, - timeRange: { before: null, after: null }, + timeRange: dateRange, monthDetails: null, }); diff --git a/web/src/routes/Events.jsx b/web/src/routes/Events.jsx index 1a40044e4..02d6a5822 100644 --- a/web/src/routes/Events.jsx +++ b/web/src/routes/Events.jsx @@ -42,53 +42,55 @@ export default function Events({ path, ...props }) { label: props.label ?? 'all', zone: props.zone ?? 'all', }); + const [state, setState] = useState({ + showDownloadMenu: null, + showDatePicker: null, + showCalendar: null, + }); const [viewEvent, setViewEvent] = useState(); const [downloadEvent, setDownloadEvent] = useState({ id: null, has_clip: false, has_snapshot: false }); - const [showDownloadMenu, setShowDownloadMenu] = useState(); - const [showDatePicker, setShowDatePicker] = useState(); - const [showCalendar, setShowCalendar] = useState(); - const eventsFetcher = (path, params) => { + const eventsFetcher = useCallback((path, params) => { params = { ...params, include_thumbnails: 0, limit: API_LIMIT }; return axios.get(path, { params }).then((res) => res.data); - }; + }, []); - const getKey = (index, prevData) => { - if (index > 0) { - const lastDate = prevData[prevData.length - 1].start_time; - const pagedParams = { ...searchParams, before: lastDate }; - return ['events', pagedParams]; - } + const getKey = useCallback( + (index, prevData) => { + if (index > 0) { + const lastDate = prevData[prevData.length - 1].start_time; + const pagedParams = { ...searchParams, before: lastDate }; + return ['events', pagedParams]; + } - return ['events', searchParams]; - }; + return ['events', searchParams]; + }, + [searchParams] + ); const { data: eventPages, mutate, size, setSize, isValidating } = useSWRInfinite(getKey, eventsFetcher); const { data: config } = useSWR('config'); - const cameras = useMemo(() => Object.keys(config?.cameras || {}), [config]); - - const zones = useMemo( - () => - Object.values(config?.cameras || {}) + const filterValues = useMemo( + () => ({ + cameras: Object.keys(config?.cameras || {}), + zones: Object.values(config?.cameras || {}) .reduce((memo, camera) => { memo = memo.concat(Object.keys(camera?.zones || {})); return memo; }, []) .filter((value, i, self) => self.indexOf(value) === i), + labels: Object.values(config?.cameras || {}) + .reduce((memo, camera) => { + memo = memo.concat(camera?.objects?.track || []); + return memo; + }, config?.objects?.track || []) + .filter((value, i, self) => self.indexOf(value) === i), + }), [config] ); - const labels = useMemo(() => { - return Object.values(config?.cameras || {}) - .reduce((memo, camera) => { - memo = memo.concat(camera?.objects?.track || []); - return memo; - }, config?.objects?.track || []) - .filter((value, i, self) => self.indexOf(value) === i); - }, [config]); - const onSave = async (e, eventId, save) => { e.stopPropagation(); let response; @@ -118,16 +120,15 @@ export default function Events({ path, ...props }) { e.stopPropagation(); setDownloadEvent((_prev) => ({ id: event.id, has_clip: event.has_clip, has_snapshot: event.has_snapshot })); downloadButton.current = e.target; - setShowDownloadMenu(true); + setState({ ...state, showDownloadMenu: true }); }; const handleSelectDateRange = useCallback( (dates) => { - console.log(dates); setSearchParams({ ...searchParams, before: dates.before, after: dates.after }); - setShowDatePicker(false); + setState({ ...state, showDatePicker: false }); }, - [searchParams, setSearchParams, setShowDatePicker] + [searchParams, setSearchParams, state, setState] ); const onFilter = useCallback( @@ -166,7 +167,7 @@ export default function Events({ path, ...props }) { [size, setSize, isValidating, isDone] ); - if (!eventPages || !config) { + if (!config) { return ; } @@ -180,7 +181,7 @@ export default function Events({ path, ...props }) { onChange={(e) => onFilter('camera', e.target.value)} > - {cameras.map((item) => ( + {filterValues.cameras.map((item) => ( @@ -192,7 +193,7 @@ export default function Events({ path, ...props }) { onChange={(e) => onFilter('label', e.target.value)} > - {labels.map((item) => ( + {filterValues.labels.map((item) => ( @@ -204,18 +205,21 @@ export default function Events({ path, ...props }) { onChange={(e) => onFilter('zone', e.target.value)} > - {zones.map((item) => ( + {filterValues.zones.map((item) => ( ))}
- setShowDatePicker(true)} /> + setState({ ...state, showDatePicker: true })} + />
- {showDownloadMenu && ( - setShowDownloadMenu(false)} relativeTo={downloadButton}> + {state.showDownloadMenu && ( + setState({ ...state, showDownloadMenu: false })} relativeTo={downloadButton}> {downloadEvent.has_snapshot && ( )} - {showDatePicker && ( - setShowDatePicker(false)} relativeTo={datePicker}> + {state.showDatePicker && ( + setState({ ...state, setShowDatePicker: false })} + relativeTo={datePicker} + > { - setShowCalendar(true); - setShowDatePicker(false); + setState({ ...state, showCalendar: true, showDatePicker: false }); }} /> )} - {showCalendar && ( - setShowCalendar(false)} relativeTo={datePicker}> - setShowCalendar(false)} /> + {state.showCalendar && ( + setState({ ...state, showCalendar: false })} + relativeTo={datePicker} + > + setState({ ...state, showCalendar: false })} + /> )}
- {eventPages.map((page, i) => { - const lastPage = eventPages.length === i + 1; - return page.map((event, j) => { - const lastEvent = lastPage && page.length === j + 1; - return ( - -
(viewEvent === event.id ? setViewEvent(null) : setViewEvent(event.id))} - > + {eventPages ? ( + eventPages.map((page, i) => { + const lastPage = eventPages.length === i + 1; + return page.map((event, j) => { + const lastEvent = lastPage && page.length === j + 1; + return ( +
(viewEvent === event.id ? setViewEvent(null) : setViewEvent(event.id))} > - onSave(e, event.id, !event.retain_indefinitely)} - fill={event.retain_indefinitely ? 'currentColor' : 'none'} - /> - {event.end_time ? null : ( -
- In progress -
- )} -
-
-
-
- {event.label} ({(event.top_score * 100).toFixed(0)}%) -
-
- {new Date(event.start_time * 1000).toLocaleDateString()}{' '} - {new Date(event.start_time * 1000).toLocaleTimeString()} -
-
- - {event.camera} -
-
- - {event.zones.join(',')} -
-
-
- onDelete(e, event.id)} /> - - onDownloadClick(e, event)} +
+ onSave(e, event.id, !event.retain_indefinitely)} + fill={event.retain_indefinitely ? 'currentColor' : 'none'} /> -
-
-
- {viewEvent !== event.id ? null : ( -
-
- {event.has_clip ? ( - <> - Clip - {}} - /> - - ) : ( -
-
- {event.has_snapshot ? 'Best Image' : 'Thumbnail'} - {`${event.label} -
+ {event.end_time ? null : ( +
+ In progress
)}
+
+
+
+ {event.label} ({(event.top_score * 100).toFixed(0)}%) +
+
+ {new Date(event.start_time * 1000).toLocaleDateString()}{' '} + {new Date(event.start_time * 1000).toLocaleTimeString()} +
+
+ + {event.camera} +
+
+ + {event.zones.join(',')} +
+
+
+ onDelete(e, event.id)} /> + + onDownloadClick(e, event)} + /> +
+
- )} - - ); - }); - })} + {viewEvent !== event.id ? null : ( +
+
+ {event.has_clip ? ( + <> + Clip + {}} + /> + + ) : ( +
+
+ {event.has_snapshot ? 'Best Image' : 'Thumbnail'} + {`${event.label} +
+
+ )} +
+
+ )} + + ); + }); + }) + ) : ( + + )}
{isDone ? null : }