diff --git a/web/src/components/Calender.jsx b/web/src/components/Calender.jsx index e6639cba1..c0cf865ab 100644 --- a/web/src/components/Calender.jsx +++ b/web/src/components/Calender.jsx @@ -35,8 +35,7 @@ const Calender = ({ onChange, calenderRef }) => { year, month, selectedDay: null, - selectedRange: { before: null, after: null }, - selectedRangeBefore: false, + timeRange: { before: null, after: null }, monthDetails: null, }); const getNumberOfDays = useCallback((year, month) => { @@ -104,42 +103,55 @@ const Calender = ({ onChange, calenderRef }) => { return day.timestamp === todayTimestamp; }; - const isSelectedDay = (day) => { - return day.timestamp === state.selectedDay; - }; const isSelectedRange = (day) => { - if (!state.selectedRange.after || !state.selectedRange.before) return; + if (!state.timeRange.after || !state.timeRange.before) return; - return day.timestamp < state.selectedRange.before * 1000 && day.timestamp >= state.selectedRange.after * 1000; + return day.timestamp < state.timeRange.before && day.timestamp >= state.timeRange.after; }; const isFirstDayInRange = (day) => { if (isCurrentDay(day)) return; - return state.selectedRange.after * 1000 === day.timestamp; + return state.timeRange.after === day.timestamp; }; const isLastDayInRange = (day) => { - if (state.selectedRange.after * 1000 === todayTimestamp) return; - return state.selectedRange.before * 1000 === day.timestamp + 86400000; + return state.timeRange.before === new Date(day.timestamp).setHours(24, 0, 0, 0); }; const getMonthStr = (month) => monthMap[Math.max(Math.min(11, month), 0)] || 'Month'; const onDateClick = (day) => { - const range = { - selectedRange: state.selectedRangeBefore - ? { ...state.selectedRange, before: new Date(day.timestamp).setHours(24, 0, 0, 0) / 1000 } - : { ...state.selectedRange, after: day.timestamp / 1000 }, - }; + const { before, after } = state.timeRange; + let timeRange = { before: null, after: null }; + + // user has selected a date < after, reset values + if (after === null || day.timestamp < after) { + timeRange = { before: new Date(day.timestamp).setHours(24, 0, 0, 0), after: day.timestamp }; + } + + // user has selected a date > after + if (after !== null && before !== new Date(day.timestamp).setHours(24, 0, 0, 0) && day.timestamp > after) { + timeRange = { + after, + before: + day.timestamp >= todayTimestamp + ? new Date(todayTimestamp).setHours(24, 0, 0, 0) + : new Date(day.timestamp).setHours(24, 0, 0, 0), + }; + } + + // reset values + if (before === new Date(day.timestamp).setHours(24, 0, 0, 0)) { + timeRange = { before: null, after: null }; + } setState((prev) => ({ ...prev, - ...range, + timeRange, selectedDay: day.timestamp, - selectedRangeBefore: !state.selectedRangeBefore, })); if (onChange) { - onChange({ before: range.selectedRange.before, after: range.selectedRange.after }); + onChange(timeRange.after ? { before: timeRange.before / 1000, after: timeRange.after / 1000 } : ['all']); } }; @@ -185,9 +197,8 @@ const Calender = ({ onChange, calenderRef }) => { className={`h-12 w-12 float-left flex flex-shrink justify-center items-center cursor-pointer ${ day.month !== 0 ? ' opacity-50 bg-gray-700 dark:bg-gray-700 pointer-events-none' : '' } - ${isSelectedDay(day) ? 'bg-gray-100 dark:hover:bg-gray-100' : ''} ${isFirstDayInRange(day) ? ' rounded-l-xl ' : ''} - ${isSelectedRange(day) ? ' bg-blue-500 dark:hover:bg-blue-600' : ''} + ${isSelectedRange(day) ? ' bg-blue-600 dark:hover:bg-blue-600' : ''} ${isLastDayInRange(day) ? ' rounded-r-xl ' : ''} ${isCurrentDay(day) && !isLastDayInRange(day) ? 'rounded-full bg-gray-100 dark:hover:bg-gray-100 ' : ''}`} key={index} diff --git a/web/src/routes/Events.jsx b/web/src/routes/Events.jsx deleted file mode 100644 index 70816fe00..000000000 --- a/web/src/routes/Events.jsx +++ /dev/null @@ -1,316 +0,0 @@ -import { h, Fragment } from 'preact'; -import ActivityIndicator from '../components/ActivityIndicator'; -import Heading from '../components/Heading'; -import Link from '../components/Link'; -import Select from '../components/Select'; -import produce from 'immer'; -import { route } from 'preact-router'; -import { useIntersectionObserver } from '../hooks'; -import { FetchStatus, useApiHost, useConfig, useEvents } from '../api'; -import { Table, Thead, Tbody, Tfoot, Th, Tr, Td } from '../components/Table'; -import { useCallback, useEffect, useMemo, useReducer, useState } from 'preact/hooks'; -import { DateFilterOptions } from '../components/DatePicker'; - -const API_LIMIT = 25; - -const initialState = Object.freeze({ events: [], reachedEnd: false, searchStrings: {} }); -const reducer = (state = initialState, action) => { - switch (action.type) { - case 'APPEND_EVENTS': { - const { - meta: { searchString }, - payload, - } = action; - - return produce(state, (draftState) => { - draftState.searchStrings[searchString] = true; - draftState.events.push(...payload); - }); - } - - case 'REACHED_END': { - const { - meta: { searchString }, - } = action; - return produce(state, (draftState) => { - draftState.reachedEnd = true; - draftState.searchStrings[searchString] = true; - }); - } - - case 'RESET': - return initialState; - - default: - return state; - } -}; - -const defaultSearchString = (limit) => `include_thumbnails=0&limit=${limit}`; -function removeDefaultSearchKeys(searchParams) { - searchParams.delete('limit'); - searchParams.delete('include_thumbnails'); - // searchParams.delete('before'); -} - -export default function Events({ path: pathname, limit = API_LIMIT } = {}) { - const apiHost = useApiHost(); - const [{ events, reachedEnd, searchStrings }, dispatch] = useReducer(reducer, initialState); - const { searchParams: initialSearchParams } = new URL(window.location); - const [searchString, setSearchString] = useState(`${defaultSearchString(limit)}&${initialSearchParams.toString()}`); - const { data, status, deleted } = useEvents(searchString); - - useEffect(() => { - if (data && !(searchString in searchStrings)) { - dispatch({ type: 'APPEND_EVENTS', payload: data, meta: { searchString } }); - } - - if (data && Array.isArray(data) && data.length + deleted < limit) { - dispatch({ type: 'REACHED_END', meta: { searchString } }); - } - }, [data, limit, searchString, searchStrings, deleted]); - - const [entry, setIntersectNode] = useIntersectionObserver(); - - useEffect(() => { - if (entry && entry.isIntersecting) { - const { startTime } = entry.target.dataset; - const { searchParams } = new URL(window.location); - searchParams.set('before', parseFloat(startTime) - 0.0001); - - setSearchString(`${defaultSearchString(limit)}&${searchParams.toString()}`); - } - }, [entry, limit]); - - const lastCellRef = useCallback( - (node) => { - if (node !== null && !reachedEnd) { - setIntersectNode(node); - } - }, - [setIntersectNode, reachedEnd] - ); - - const handleFilter = useCallback( - (searchParams) => { - dispatch({ type: 'RESET' }); - removeDefaultSearchKeys(searchParams); - setSearchString(`${defaultSearchString(limit)}&${searchParams.toString()}`); - route(`${pathname}?${searchParams.toString()}`); - }, - [limit, pathname, setSearchString] - ); - - const searchParams = useMemo(() => new URLSearchParams(searchString), [searchString]); - - return ( -
| - | Camera | -Label | -Score | -Zones | -Date | -Start | -End | -
|---|---|---|---|---|---|---|---|
|
-
- |
-
- |
-
- |
- {(score * 100).toFixed(2)}% | -
-
|
- {start.toLocaleDateString()} | -{start.toLocaleTimeString()} | -{end.toLocaleTimeString()} | -
|
- {status === FetchStatus.LOADING ? |
- |||||||