diff --git a/web/src/components/input/InputWithTags.tsx b/web/src/components/input/InputWithTags.tsx index 062a38d13..9d4632885 100644 --- a/web/src/components/input/InputWithTags.tsx +++ b/web/src/components/input/InputWithTags.tsx @@ -38,6 +38,7 @@ import { convertTo12Hour, getIntlDateFormat, isValidTimeRange, + to24Hour, } from "@/utils/dateUtil"; import { toast } from "sonner"; import useSWR from "swr"; @@ -191,7 +192,8 @@ export default function InputWithTags({ if ( allSuggestions[type as FilterType]?.includes(value) || type == "before" || - type == "after" + type == "after" || + type == "time_range" ) { const newFilters = { ...filters }; let timestamp = 0; @@ -235,23 +237,6 @@ export default function InputWithTags({ } break; case "time_range": - if (!value.includes(",")) { - toast.error( - "The correct format is after,before. Example: 15:00,18:00.", - { - position: "top-center", - }, - ); - return; - } - - if (!isValidTimeRange(value)) { - toast.error("Time range is not valid.", { - position: "top-center", - }); - return; - } - newFilters[type] = value; break; case "search_type": @@ -299,7 +284,9 @@ export default function InputWithTags({ : (filterValues as number)) * 1000, ).toLocaleDateString(window.navigator?.language || "en-US"); } else if (filterType === "time_range") { - const [startTime, endTime] = (filterValues as string).split(","); + const [startTime, endTime] = (filterValues as string) + .replace("-", ",") + .split(","); return `${ config?.ui.time_format === "24hour" ? startTime @@ -320,9 +307,23 @@ export default function InputWithTags({ if ( allSuggestions[filterType]?.includes(trimmedValue) || ((filterType === "before" || filterType === "after") && - trimmedValue.match(/^\d{8}$/)) + trimmedValue.match(/^\d{8}$/)) || + (filterType === "time_range" && + isValidTimeRange( + trimmedValue.replace("-", ","), + config?.ui.time_format, + )) ) { - createFilter(filterType, trimmedValue); + createFilter( + filterType, + filterType === "time_range" + ? trimmedValue + .replace("-", ",") + .split(",") + .map((time) => to24Hour(time.trim(), config?.ui.time_format)) + .join(",") + : trimmedValue, + ); setInputValue((prev) => { const regex = new RegExp( `${filterType}:${filterValue.trim()}[,\\s]*`, @@ -335,7 +336,7 @@ export default function InputWithTags({ setCurrentFilterType(null); } }, - [allSuggestions, createFilter], + [allSuggestions, createFilter, config], ); const handleInputChange = useCallback( @@ -362,18 +363,11 @@ export default function InputWithTags({ if (filterType in allSuggestions) { setCurrentFilterType(filterType); - if (filterType === "before" || filterType === "after") { - // For before and after, we don't need to update suggestions - if (filterValue.match(/^\d{8}$/)) { - handleFilterCreation(filterType, filterValue); - } - } else { - updateSuggestions(filterValue, filterType); + updateSuggestions(filterValue, filterType); - // Check if the last character is a space or comma - if (isLastCharSpaceOrComma) { - handleFilterCreation(filterType, filterValue); - } + // Check if the last character is a space or comma + if (isLastCharSpaceOrComma) { + handleFilterCreation(filterType, filterValue); } } else { resetSuggestions(value); @@ -413,6 +407,13 @@ export default function InputWithTags({ (suggestion: string) => { if (currentFilterType) { // Apply the selected suggestion to the current filter type + if (currentFilterType == "time_range") { + suggestion = suggestion + .replace("-", ",") + .split(",") + .map((time) => to24Hour(time.trim(), config?.ui.time_format)) + .join(","); + } createFilter(currentFilterType, suggestion); setInputValue((prev) => { const regex = new RegExp(`${currentFilterType}:[^\\s,]*`, "g"); @@ -439,7 +440,7 @@ export default function InputWithTags({ inputRef.current?.focus(); }, - [createFilter, currentFilterType, allSuggestions], + [createFilter, currentFilterType, allSuggestions, config], ); const handleSearch = useCallback( @@ -565,7 +566,7 @@ export default function InputWithTags({

How to use text filters

Filters help you narrow down your search results. Here's how - to use them: + to use them in the input field:

@@ -587,6 +598,7 @@ export default function InputWithTags({ Example:{" "} cameras:front_door label:person before:01012024 + time_range:3:00PM-4:00PM

diff --git a/web/src/utils/dateUtil.ts b/web/src/utils/dateUtil.ts index 9e0b25c5c..dc5589884 100644 --- a/web/src/utils/dateUtil.ts +++ b/web/src/utils/dateUtil.ts @@ -387,22 +387,54 @@ export function formatDateToLocaleString(daysOffset: number = 0): string { .replace(/[^\d]/g, ""); } -export function isValidTimeRange(rangeString: string): boolean { - const range = rangeString.split(","); +export function to24Hour( + time: string, + time_format: "12hour" | "24hour" | "browser" = "24hour", +): string { + const is24HourFormat = time_format === "24hour"; + if (is24HourFormat) return time; + + const [timePart, ampm] = time.split(/([AP]M)/i); + + if (!timePart || !ampm) { + throw new Error(`Invalid time format: ${time}`); + } + + let hours = Number(timePart.split(":")[0]); + const minutes = Number(timePart.split(":")[1]); + + if (ampm.toUpperCase() === "PM" && hours !== 12) hours += 12; + if (ampm.toUpperCase() === "AM" && hours === 12) hours = 0; + + return `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}`; +} + +export function isValidTimeRange( + rangeString: string, + time_format?: "12hour" | "24hour" | "browser", +): boolean { + const range = rangeString.split(","); if (range.length !== 2) { return false; } + const is24HourFormat = time_format === "24hour"; + const toMinutes = (time: string): number => { - const [h, m] = time.split(":").map(Number); + const [h, m] = to24Hour(time, time_format).split(":").map(Number); return h * 60 + m; }; - const isValidTime = (time: string): boolean => - /^(?:([01]\d|2[0-3]):([0-5]\d)|24:00)$/.test(time); + const isValidTime = (time: string): boolean => { + if (is24HourFormat) { + return /^(?:([01]\d|2[0-3]):([0-5]\d)|24:00)$/.test(time); + } else { + return /^(0?[1-9]|1[0-2]):[0-5][0-9](A|P)M$/i.test(time); + } + }; - const [startTime, endTime] = range; + const [startTime, endTime] = range.map((t) => t.trim()); return ( isValidTime(startTime) && diff --git a/web/src/views/search/SearchView.tsx b/web/src/views/search/SearchView.tsx index 0a41163f6..28f9ed579 100644 --- a/web/src/views/search/SearchView.tsx +++ b/web/src/views/search/SearchView.tsx @@ -121,7 +121,10 @@ export default function SearchView({ zones: Object.values(allZones || {}), sub_labels: allSubLabels, search_type: ["thumbnail", "description"] as SearchSource[], - time_range: ["00:00,24:00"], + time_range: + config?.ui.time_format == "24hour" + ? ["00:00-23:59"] + : ["12:00AM-11:59PM"], before: [formatDateToLocaleString()], after: [formatDateToLocaleString(-5)], }),