diff --git a/web/src/components/Calender.jsx b/web/src/components/Calender.jsx
new file mode 100644
index 000000000..8d8c72e13
--- /dev/null
+++ b/web/src/components/Calender.jsx
@@ -0,0 +1,329 @@
+import { h } from 'preact';
+import { useEffect, useState, useCallback, useMemo, useRef } from 'preact/hooks';
+import ArrowRight from '../icons/ArrowRight';
+import ArrowRightDouble from '../icons/ArrowRightDouble';
+
+const todayTimestamp = new Date().setHours(0, 0, 0, 0).valueOf();
+
+const Calender = ({ onChange, calenderRef, close }) => {
+ const keyRef = useRef([]);
+
+ const date = new Date();
+ const year = date.getFullYear();
+ const month = date.getMonth();
+
+ const daysMap = useMemo(() => ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], []);
+ const monthMap = useMemo(
+ () => [
+ 'January',
+ 'February',
+ 'March',
+ 'April',
+ 'May',
+ 'June',
+ 'July',
+ 'August',
+ 'September',
+ 'October',
+ 'November',
+ 'December',
+ ],
+ []
+ );
+
+ const [state, setState] = useState({
+ getMonthDetails: [],
+ year,
+ month,
+ selectedDay: null,
+ timeRange: { before: null, after: null },
+ monthDetails: null,
+ });
+
+ const getNumberOfDays = useCallback((year, month) => {
+ return 40 - new Date(year, month, 40).getDate();
+ }, []);
+
+ const getDayDetails = useCallback(
+ (args) => {
+ const date = args.index - args.firstDay;
+ const day = args.index % 7;
+ let prevMonth = args.month - 1;
+ let prevYear = args.year;
+ if (prevMonth < 0) {
+ prevMonth = 11;
+ prevYear--;
+ }
+ const prevMonthNumberOfDays = getNumberOfDays(prevYear, prevMonth);
+ const _date = (date < 0 ? prevMonthNumberOfDays + date : date % args.numberOfDays) + 1;
+ const month = date < 0 ? -1 : date >= args.numberOfDays ? 1 : 0;
+ const timestamp = new Date(args.year, args.month, _date).getTime();
+ return {
+ date: _date,
+ day,
+ month,
+ timestamp,
+ dayString: daysMap[day],
+ };
+ },
+ [getNumberOfDays, daysMap]
+ );
+
+ const getMonthDetails = useCallback(
+ (year, month) => {
+ const firstDay = new Date(year, month).getDay();
+ const numberOfDays = getNumberOfDays(year, month);
+ const monthArray = [];
+ const rows = 6;
+ let currentDay = null;
+ let index = 0;
+ const cols = 7;
+
+ for (let row = 0; row < rows; row++) {
+ for (let col = 0; col < cols; col++) {
+ currentDay = getDayDetails({
+ index,
+ numberOfDays,
+ firstDay,
+ year,
+ month,
+ });
+ monthArray.push(currentDay);
+ index++;
+ }
+ }
+ return monthArray;
+ },
+ [getNumberOfDays, getDayDetails]
+ );
+
+ useEffect(() => {
+ setState((prev) => ({ ...prev, selectedDay: todayTimestamp, monthDetails: getMonthDetails(year, month) }));
+ }, [year, month, getMonthDetails]);
+
+ useEffect(() => {
+ // add refs for keyboard navigation
+ if (state.monthDetails) {
+ keyRef.current = keyRef.current.slice(0, state.monthDetails.length);
+ }
+ // set today date in focus for keyboard navigation
+ const todayDate = new Date(todayTimestamp).getDate();
+ keyRef.current.find((t) => t.tabIndex === todayDate)?.focus();
+ }, [state.monthDetails]);
+
+ const isCurrentDay = (day) => day.timestamp === todayTimestamp;
+
+ const isSelectedRange = useCallback(
+ (day) => {
+ if (!state.timeRange.after || !state.timeRange.before) return;
+
+ return day.timestamp < state.timeRange.before && day.timestamp >= state.timeRange.after;
+ },
+ [state.timeRange]
+ );
+
+ const isFirstDayInRange = useCallback(
+ (day) => {
+ if (isCurrentDay(day)) return;
+ return state.timeRange.after === day.timestamp;
+ },
+ [state.timeRange.after]
+ );
+
+ const isLastDayInRange = useCallback(
+ (day) => {
+ return state.timeRange.before === new Date(day.timestamp).setHours(24, 0, 0, 0);
+ },
+ [state.timeRange.before]
+ );
+
+ const getMonthStr = useCallback(
+ (month) => {
+ return monthMap[Math.max(Math.min(11, month), 0)] || 'Month';
+ },
+ [monthMap]
+ );
+
+ const onDateClick = (day) => {
+ 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,
+ timeRange,
+ selectedDay: day.timestamp,
+ }));
+
+ if (onChange) {
+ onChange(timeRange.after ? { before: timeRange.before / 1000, after: timeRange.after / 1000 } : ['all']);
+ }
+ };
+
+ const setYear = useCallback(
+ (offset) => {
+ const year = state.year + offset;
+ const month = state.month;
+ setState((prev) => {
+ return {
+ ...prev,
+ year,
+ monthDetails: getMonthDetails(year, month),
+ };
+ });
+ },
+ [state.year, state.month, getMonthDetails]
+ );
+
+ const setMonth = (offset) => {
+ let year = state.year;
+ let month = state.month + offset;
+ if (month === -1) {
+ month = 11;
+ year--;
+ } else if (month === 12) {
+ month = 0;
+ year++;
+ }
+ setState((prev) => {
+ return {
+ ...prev,
+ year,
+ month,
+ monthDetails: getMonthDetails(year, month),
+ };
+ });
+ };
+
+ const handleKeydown = (e, day, index) => {
+ if ((keyRef.current && e.key === 'Enter') || e.keyCode === 32) {
+ e.preventDefault();
+ day.month === 0 && onDateClick(day);
+ }
+ if (e.key === 'ArrowLeft') {
+ index > 0 && keyRef.current[index - 1].focus();
+ }
+ if (e.key === 'ArrowRight') {
+ index < 41 && keyRef.current[index + 1].focus();
+ }
+ if (e.key === 'ArrowUp') {
+ e.preventDefault();
+ index > 6 && keyRef.current[index - 7].focus();
+ }
+ if (e.key === 'ArrowDown') {
+ e.preventDefault();
+ index < 36 && keyRef.current[index + 7].focus();
+ }
+ if (e.key === 'Escape') {
+ close();
+ }
+ };
+
+ const renderCalendar = () => {
+ const days =
+ state.monthDetails &&
+ state.monthDetails.map((day, idx) => {
+ return (
+
onDateClick(day)}
+ onkeydown={(e) => handleKeydown(e, day, idx)}
+ ref={(ref) => (keyRef.current[idx] = ref)}
+ tabIndex={day.month === 0 ? day.date : null}
+ 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' : ''
+ }
+ ${isFirstDayInRange(day) ? ' rounded-l-xl ' : ''}
+ ${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={idx}
+ >
+
+ {day.date}
+
+
+ );
+ });
+
+ return (
+
+
+ {['SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT'].map((d, i) => (
+
+ {d}
+
+ ))}
+
+
{days}
+
+ );
+ };
+
+ return (
+
+
+
+
+
+
+
{state.year}
+
{getMonthStr(state.month)}
+
+
+
+
+
{renderCalendar()}
+
+
+ );
+};
+
+export default Calender;
diff --git a/web/src/components/DatePicker.jsx b/web/src/components/DatePicker.jsx
new file mode 100644
index 000000000..b751128ae
--- /dev/null
+++ b/web/src/components/DatePicker.jsx
@@ -0,0 +1,162 @@
+import { h } from 'preact';
+import { useCallback, useEffect, useState } from 'preact/hooks';
+
+export const DateFilterOptions = [
+ {
+ label: 'All',
+ value: ['all'],
+ },
+ {
+ label: 'Today',
+ value: {
+ //Before
+ before: new Date().setHours(24, 0, 0, 0) / 1000,
+ //After
+ after: new Date().setHours(0, 0, 0, 0) / 1000,
+ },
+ },
+ {
+ label: 'Yesterday',
+ value: {
+ //Before
+ before: new Date(new Date().setDate(new Date().getDate() - 1)).setHours(24, 0, 0, 0) / 1000,
+ //After
+ after: new Date(new Date().setDate(new Date().getDate() - 1)).setHours(0, 0, 0, 0) / 1000,
+ },
+ },
+ {
+ label: 'Last 7 Days',
+ value: {
+ //Before
+ before: new Date().setHours(24, 0, 0, 0) / 1000,
+ //After
+ after: new Date(new Date().setDate(new Date().getDate() - 7)).setHours(0, 0, 0, 0) / 1000,
+ },
+ },
+ {
+ label: 'This Month',
+ value: {
+ //Before
+ before: new Date().setHours(24, 0, 0, 0) / 1000,
+ //After
+ after: new Date(new Date().getFullYear(), new Date().getMonth(), 1).getTime() / 1000,
+ },
+ },
+ {
+ label: 'Last Month',
+ value: {
+ //Before
+ before: new Date(new Date().getFullYear(), new Date().getMonth(), 1).getTime() / 1000,
+ //After
+ after: new Date(new Date().getFullYear(), new Date().getMonth() - 1, 1).getTime() / 1000,
+ },
+ },
+ {
+ label: 'Custom Range',
+ value: 'custom_range',
+ },
+];
+
+export default function DatePicker({
+ helpText,
+ keyboardType = 'text',
+ inputRef,
+ label,
+ leadingIcon: LeadingIcon,
+ onBlur,
+ onChangeText,
+ onFocus,
+ readonly,
+ trailingIcon: TrailingIcon,
+ value: propValue = '',
+ ...props
+}) {
+ const [isFocused, setFocused] = useState(false);
+ const [value, setValue] = useState(propValue);
+
+ useEffect(() => {
+ if (propValue !== value) {
+ setValue(propValue);
+ }
+ }, [propValue, setValue, value]);
+
+ const handleFocus = useCallback(
+ (event) => {
+ setFocused(true);
+ onFocus && onFocus(event);
+ },
+ [onFocus]
+ );
+
+ const handleBlur = useCallback(
+ (event) => {
+ setFocused(false);
+ onBlur && onBlur(event);
+ },
+ [onBlur]
+ );
+
+ const handleChange = useCallback(
+ (event) => {
+ const { value } = event.target;
+ setValue(value);
+ onChangeText && onChangeText(value);
+ },
+ [onChangeText, setValue]
+ );
+
+ const onClick = (e) => {
+ props.onclick(e);
+ };
+ const labelMoved = isFocused || value !== '';
+
+ return (
+
+ {props.children}
+
+
+
+ {helpText ?
{helpText}
: null}
+
+ );
+}
diff --git a/web/src/components/RelativeModal.jsx b/web/src/components/RelativeModal.jsx
index 7dd1bf76e..5186cc8a6 100644
--- a/web/src/components/RelativeModal.jsx
+++ b/web/src/components/RelativeModal.jsx
@@ -27,7 +27,7 @@ export default function RelativeModal({
const handleKeydown = useCallback(
(event) => {
- const focusable = ref.current.querySelectorAll('[tabindex]');
+ const focusable = ref.current && ref.current.querySelectorAll('[tabindex]');
if (event.key === 'Tab' && focusable.length) {
if (event.shiftKey && document.activeElement === focusable[0]) {
focusable[focusable.length - 1].focus();
@@ -69,14 +69,15 @@ export default function RelativeModal({
let newTop = top;
let newLeft = left;
- // too far right
- if (newLeft + width + WINDOW_PADDING >= windowWidth - WINDOW_PADDING) {
- newLeft = windowWidth - width - WINDOW_PADDING;
- }
// too far left
- else if (left < WINDOW_PADDING) {
+ if (left < WINDOW_PADDING) {
newLeft = WINDOW_PADDING;
}
+ // too far right
+ else if (newLeft + width + WINDOW_PADDING >= windowWidth - WINDOW_PADDING) {
+ newLeft = windowWidth - width - WINDOW_PADDING;
+ }
+
// too close to bottom
if (top + menuHeight > windowHeight - WINDOW_PADDING + window.scrollY) {
newTop = WINDOW_PADDING;
diff --git a/web/src/components/Select.jsx b/web/src/components/Select.jsx
index 4b9fac7f5..4f7536dc6 100644
--- a/web/src/components/Select.jsx
+++ b/web/src/components/Select.jsx
@@ -3,74 +3,27 @@ import ArrowDropdown from '../icons/ArrowDropdown';
import ArrowDropup from '../icons/ArrowDropup';
import Menu, { MenuItem } from './Menu';
import TextField from './TextField';
+import DatePicker from './DatePicker';
+import Calender from './Calender';
import { useCallback, useEffect, useMemo, useRef, useState } from 'preact/hooks';
-export default function Select({ label, onChange, options: inputOptions = [], selected: propSelected }) {
+export default function Select({
+ type,
+ label,
+ onChange,
+ paramName,
+ options: inputOptions = [],
+ selected: propSelected,
+}) {
const options = useMemo(
() =>
typeof inputOptions[0] === 'string' ? inputOptions.map((opt) => ({ value: opt, label: opt })) : inputOptions,
[inputOptions]
);
+
const [showMenu, setShowMenu] = useState(false);
- const [selected, setSelected] = useState(
- Math.max(
- options.findIndex(({ value }) => value === propSelected),
- 0
- )
- );
- const [focused, setFocused] = useState(null);
-
- const ref = useRef(null);
-
- const handleSelect = useCallback(
- (value, label) => {
- setSelected(options.findIndex((opt) => opt.value === value));
- onChange && onChange(value, label);
- setShowMenu(false);
- },
- [onChange, options]
- );
-
- const handleClick = useCallback(() => {
- setShowMenu(true);
- }, [setShowMenu]);
-
- const handleKeydown = useCallback(
- (event) => {
- switch (event.key) {
- case 'Enter': {
- if (!showMenu) {
- setShowMenu(true);
- setFocused(selected);
- } else {
- setSelected(focused);
- onChange && onChange(options[focused].value, options[focused].label);
- setShowMenu(false);
- }
- break;
- }
-
- case 'ArrowDown': {
- const newIndex = focused + 1;
- newIndex < options.length && setFocused(newIndex);
- break;
- }
-
- case 'ArrowUp': {
- const newIndex = focused - 1;
- newIndex > -1 && setFocused(newIndex);
- break;
- }
-
- // no default
- }
- },
- [onChange, options, showMenu, setShowMenu, setFocused, focused, selected]
- );
-
- const handleDismiss = useCallback(() => {
- setShowMenu(false);
- }, [setShowMenu]);
+ const [selected, setSelected] = useState();
+ const [datePickerValue, setDatePickerValue] = useState();
// Reset the state if the prop value changes
useEffect(() => {
@@ -85,25 +38,219 @@ export default function Select({ label, onChange, options: inputOptions = [], se
// DO NOT include `selected`
}, [options, propSelected]); // eslint-disable-line react-hooks/exhaustive-deps
- return (
-
-
- {showMenu ? (
-
- ) : null}
-
+ useEffect(() => {
+ if (type === 'datepicker') {
+ if ('after' && 'before' in propSelected) {
+ if (!propSelected.before || !propSelected.after) return setDatePickerValue('all');
+
+ for (let i = 0; i < inputOptions.length; i++) {
+ if (
+ inputOptions[i].value &&
+ Object.entries(inputOptions[i].value).sort().toString() === Object.entries(propSelected).sort().toString()
+ ) {
+ setDatePickerValue(inputOptions[i]?.label);
+ break;
+ } else {
+ setDatePickerValue(
+ `${new Date(propSelected.after * 1000).toLocaleDateString()} -> ${new Date(
+ propSelected.before * 1000 - 1
+ ).toLocaleDateString()}`
+ );
+ }
+ }
+ }
+ }
+ if (type === 'dropdown') {
+ setSelected(
+ Math.max(
+ options.findIndex(({ value }) => Object.values(propSelected).includes(value)),
+ 0
+ )
+ );
+ }
+ }, [type, options, inputOptions, propSelected, setSelected]);
+
+ const [focused, setFocused] = useState(null);
+ const [showCalender, setShowCalender] = useState(false);
+ const calenderRef = useRef(null);
+ const ref = useRef(null);
+
+ const handleSelect = useCallback(
+ (value) => {
+ setSelected(options.findIndex(({ value }) => Object.values(propSelected).includes(value)));
+ setShowMenu(false);
+
+ //show calender date range picker
+ if (value === 'custom_range') return setShowCalender(true);
+ onChange && onChange(value);
+ },
+ [onChange, options, propSelected, setSelected]
);
+
+ const handleDateRange = useCallback(
+ (range) => {
+ onChange && onChange(range);
+ setShowMenu(false);
+ },
+ [onChange]
+ );
+
+ const handleClick = useCallback(() => {
+ setShowMenu(true);
+ }, [setShowMenu]);
+
+ const handleKeydownDatePicker = useCallback(
+ (event) => {
+ switch (event.key) {
+ case 'Enter': {
+ if (!showMenu) {
+ setShowMenu(true);
+ setFocused(selected);
+ } else {
+ setSelected(focused);
+ if (options[focused].value === 'custom_range') {
+ setShowMenu(false);
+ return setShowCalender(true);
+ }
+
+ onChange && onChange(options[focused].value);
+ setShowMenu(false);
+ }
+ break;
+ }
+
+ case 'ArrowDown': {
+ event.preventDefault();
+ const newIndex = focused + 1;
+ newIndex < options.length && setFocused(newIndex);
+ break;
+ }
+
+ case 'ArrowUp': {
+ event.preventDefault();
+ const newIndex = focused - 1;
+ newIndex > -1 && setFocused(newIndex);
+ break;
+ }
+
+ // no default
+ }
+ },
+ [onChange, options, showMenu, setShowMenu, setFocused, focused, selected]
+ );
+
+ const handleKeydown = useCallback(
+ (event) => {
+ switch (event.key) {
+ case 'Enter': {
+ if (!showMenu) {
+ setShowMenu(true);
+ setFocused(selected);
+ } else {
+ setSelected(focused);
+ onChange && onChange({ [paramName]: options[focused].value });
+ setShowMenu(false);
+ }
+ break;
+ }
+
+ case 'ArrowDown': {
+ event.preventDefault();
+ const newIndex = focused + 1;
+ newIndex < options.length && setFocused(newIndex);
+ break;
+ }
+
+ case 'ArrowUp': {
+ event.preventDefault();
+ const newIndex = focused - 1;
+ newIndex > -1 && setFocused(newIndex);
+ break;
+ }
+
+ // no default
+ }
+ },
+ [onChange, options, showMenu, setShowMenu, setFocused, focused, selected, paramName]
+ );
+
+ const handleDismiss = useCallback(() => {
+ setShowMenu(false);
+ }, [setShowMenu]);
+
+ const findDOMNodes = (component) => {
+ return (component && (component.base || (component.nodeType === 1 && component))) || null;
+ };
+
+ useEffect(() => {
+ const addBackDrop = (e) => {
+ if (showCalender && !findDOMNodes(calenderRef.current).contains(e.target)) {
+ setShowCalender(false);
+ }
+ };
+ window.addEventListener('click', addBackDrop);
+
+ return function cleanup() {
+ window.removeEventListener('click', addBackDrop);
+ };
+ }, [showCalender]);
+
+ switch (type) {
+ case 'datepicker':
+ return (
+
+
+ {showCalender && (
+
+ )}
+ {showMenu ? (
+
+ ) : null}
+
+ );
+
+ // case 'dropdown':
+ default:
+ return (
+
+
+ {showMenu ? (
+
+ ) : null}
+
+ );
+ }
}
diff --git a/web/src/components/__tests__/Select.test.jsx b/web/src/components/__tests__/Select.test.jsx
index 5425025cf..00c58d9e3 100644
--- a/web/src/components/__tests__/Select.test.jsx
+++ b/web/src/components/__tests__/Select.test.jsx
@@ -5,21 +5,40 @@ import { fireEvent, render, screen } from '@testing-library/preact';
describe('Select', () => {
test('on focus, shows a menu', async () => {
const handleChange = jest.fn();
- render();
+ render(
+
+ );
expect(screen.queryByRole('listbox')).not.toBeInTheDocument();
fireEvent.click(screen.getByRole('textbox'));
expect(screen.queryByRole('listbox')).toBeInTheDocument();
+ expect(screen.queryByRole('option', { name: 'all' })).toBeInTheDocument();
expect(screen.queryByRole('option', { name: 'tacos' })).toBeInTheDocument();
expect(screen.queryByRole('option', { name: 'burritos' })).toBeInTheDocument();
- fireEvent.click(screen.queryByRole('option', { name: 'burritos' }));
- expect(handleChange).toHaveBeenCalledWith('burritos', 'burritos');
+ fireEvent.click(screen.queryByRole('option', { name: 'tacos' }));
+ expect(handleChange).toHaveBeenCalledWith({ dinner: 'tacos' });
});
test('allows keyboard navigation', async () => {
const handleChange = jest.fn();
- render();
+ render(
+
+ );
expect(screen.queryByRole('listbox')).not.toBeInTheDocument();
const input = screen.getByRole('textbox');
@@ -29,6 +48,6 @@ describe('Select', () => {
fireEvent.keyDown(input, { key: 'ArrowDown', code: 'ArrowDown' });
fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });
- expect(handleChange).toHaveBeenCalledWith('burritos', 'burritos');
+ expect(handleChange).toHaveBeenCalledWith({ dinner: 'burritos' });
});
});
diff --git a/web/src/hooks/useSearchString.jsx b/web/src/hooks/useSearchString.jsx
index 1dde57dcc..b4fdaf3d0 100644
--- a/web/src/hooks/useSearchString.jsx
+++ b/web/src/hooks/useSearchString.jsx
@@ -18,7 +18,8 @@ export const useSearchString = (limit, searchParams) => {
const removeDefaultSearchKeys = useCallback((searchParams) => {
searchParams.delete('limit');
searchParams.delete('include_thumbnails');
- searchParams.delete('before');
+ // removed deletion of "before" as its used by DatePicker
+ // searchParams.delete('before');
}, []);
return { searchString, setSearchString, removeDefaultSearchKeys };
diff --git a/web/src/icons/ArrowLeft.jsx b/web/src/icons/ArrowLeft.jsx
new file mode 100644
index 000000000..6ff892695
--- /dev/null
+++ b/web/src/icons/ArrowLeft.jsx
@@ -0,0 +1,18 @@
+import { h } from 'preact';
+import { memo } from 'preact/compat';
+
+export function ArrowLeft({ className = '' }) {
+ return (
+
+ );
+}
+
+export default memo(ArrowLeft);
diff --git a/web/src/icons/ArrowRight.jsx b/web/src/icons/ArrowRight.jsx
new file mode 100644
index 000000000..455548887
--- /dev/null
+++ b/web/src/icons/ArrowRight.jsx
@@ -0,0 +1,12 @@
+import { h } from 'preact';
+import { memo } from 'preact/compat';
+
+export function ArrowRight({ className = '' }) {
+ return (
+
+ );
+}
+
+export default memo(ArrowRight);
diff --git a/web/src/icons/ArrowRightDouble.jsx b/web/src/icons/ArrowRightDouble.jsx
new file mode 100644
index 000000000..7487a4d5c
--- /dev/null
+++ b/web/src/icons/ArrowRightDouble.jsx
@@ -0,0 +1,12 @@
+import { h } from 'preact';
+import { memo } from 'preact/compat';
+
+export function ArrowRightDouble({ className = '' }) {
+ return (
+
+ );
+}
+
+export default memo(ArrowRightDouble);
diff --git a/web/src/routes/Events/components/filter.jsx b/web/src/routes/Events/components/filter.jsx
index 86d1bcd72..7428a1a03 100644
--- a/web/src/routes/Events/components/filter.jsx
+++ b/web/src/routes/Events/components/filter.jsx
@@ -1,31 +1,26 @@
import { h } from 'preact';
import Select from '../../../components/Select';
-import { useCallback, useMemo } from 'preact/hooks';
+import { useCallback } from 'preact/hooks';
-const Filter = ({ onChange, searchParams, paramName, options }) => {
+function Filter({ onChange, searchParams, paramName, options, ...rest }) {
const handleSelect = useCallback(
(key) => {
const newParams = new URLSearchParams(searchParams.toString());
- if (key !== 'all') {
- newParams.set(paramName, key);
- } else {
- newParams.delete(paramName);
- }
+ Object.keys(key).map((entries) => {
+ if (key[entries] !== 'all') {
+ newParams.set(entries, key[entries]);
+ } else {
+ paramName.map((p) => newParams.delete(p));
+ }
+ });
onChange(newParams);
},
[searchParams, paramName, onChange]
);
- const selectOptions = useMemo(() => ['all', ...options], [options]);
-
- return (
-
- );
-};
+ const obj = {};
+ paramName.map((name) => Object.assign(obj, { [name]: searchParams.get(name) }), [searchParams]);
+ return ;
+}
export default Filter;
diff --git a/web/src/routes/Events/components/filterable.jsx b/web/src/routes/Events/components/filterable.jsx
index b23e38eea..35018fadb 100644
--- a/web/src/routes/Events/components/filterable.jsx
+++ b/web/src/routes/Events/components/filterable.jsx
@@ -3,7 +3,13 @@ import { useCallback, useMemo } from 'preact/hooks';
import Link from '../../../components/Link';
import { route } from 'preact-router';
-const Filterable = ({ onFilter, pathname, searchParams, paramName, name, removeDefaultSearchKeys }) => {
+function Filterable({ onFilter, pathname, searchParams, paramName, name }) {
+ const removeDefaultSearchKeys = useCallback((searchParams) => {
+ searchParams.delete('limit');
+ searchParams.delete('include_thumbnails');
+ // searchParams.delete('before');
+ }, []);
+
const href = useMemo(() => {
const params = new URLSearchParams(searchParams.toString());
params.set(paramName, name);
@@ -27,6 +33,6 @@ const Filterable = ({ onFilter, pathname, searchParams, paramName, name, removeD
{name}
);
-};
+}
export default Filterable;
diff --git a/web/src/routes/Events/components/filters.jsx b/web/src/routes/Events/components/filters.jsx
index e08b4ea65..91913a6ac 100644
--- a/web/src/routes/Events/components/filters.jsx
+++ b/web/src/routes/Events/components/filters.jsx
@@ -1,11 +1,13 @@
import { h } from 'preact';
import Filter from './filter';
import { useConfig } from '../../../api';
-import { useMemo } from 'preact/hooks';
+import { useMemo, useState } from 'preact/hooks';
+import { DateFilterOptions } from '../../../components/DatePicker';
+import Button from '../../../components/Button';
const Filters = ({ onChange, searchParams }) => {
+ const [viewFilters, setViewFilters] = useState(false);
const { data } = useConfig();
-
const cameras = useMemo(() => Object.keys(data.cameras), [data]);
const zones = useMemo(
@@ -27,12 +29,52 @@ const Filters = ({ onChange, searchParams }) => {
}, data.objects?.track || [])
.filter((value, i, self) => self.indexOf(value) === i);
}, [data]);
-
return (
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
);
};