better date handling

This commit is contained in:
Josh Hawkins 2024-09-18 11:47:48 -05:00
parent eb6d2c8983
commit 89f8f69948
3 changed files with 129 additions and 31 deletions

View File

@ -5,8 +5,8 @@ import {
LuImage,
LuChevronDown,
LuChevronUp,
LuSave,
LuTrash2,
LuStar,
} from "react-icons/lu";
import {
FilterType,
@ -28,23 +28,8 @@ import { TooltipPortal } from "@radix-ui/react-tooltip";
import { usePersistence } from "@/hooks/use-persistence";
import { SaveSearchDialog } from "./SaveSearchDialog";
import { DeleteSearchDialog } from "./DeleteSearchDialog";
const convertMMDDYYToTimestamp = (dateString: string): number => {
const match = dateString.match(/^(\d{2})(\d{2})(\d{2})$/);
if (!match) return 0;
const [, month, day, year] = match;
const date = new Date(`20${year}-${month}-${day}T00:00:00Z`);
return date.getTime();
};
const emptyObject = Object.freeze([
{
name: "",
search: "",
filter: undefined,
},
]) as SavedSearchQuery[];
import { convertLocalDateToTimestamp } from "@/utils/dateUtil";
import { toast } from "sonner";
type InputWithTagsProps = {
filters: SearchFilter;
@ -76,7 +61,7 @@ export default function InputWithTags({
const [searchHistory, setSearchHistory, searchHistoryLoaded] = usePersistence<
SavedSearchQuery[]
>("frigate-search-history", emptyObject);
>("frigate-search-history");
const [isSaveDialogOpen, setIsSaveDialogOpen] = useState(false);
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
@ -198,9 +183,39 @@ export default function InputWithTags({
switch (type) {
case "before":
case "after":
timestamp = convertMMDDYYToTimestamp(value);
timestamp = convertLocalDateToTimestamp(value);
if (timestamp > 0) {
newFilters[type] = timestamp;
// Check for conflicts with existing before/after filters
if (
type === "before" &&
filters.after &&
timestamp <= filters.after * 1000
) {
toast.error(
"The 'before' date must be later than the 'after' date.",
{
position: "top-center",
},
);
return;
}
if (
type === "after" &&
filters.before &&
timestamp >= filters.before * 1000
) {
toast.error(
"The 'after' date must be earlier than the 'before' date.",
{
position: "top-center",
},
);
return;
}
if (type === "before") {
timestamp -= 1;
}
newFilters[type] = timestamp / 1000;
}
break;
case "search_type":
@ -247,7 +262,7 @@ export default function InputWithTags({
trimmedValue,
) ||
((filterType === "before" || filterType === "after") &&
trimmedValue.match(/^\d{6}$/))
trimmedValue.match(/^\d{8}$/))
) {
createFilter(filterType, trimmedValue);
setInputValue((prev) => {
@ -295,7 +310,7 @@ export default function InputWithTags({
if (filterType === "before" || filterType === "after") {
// For before and after, we don't need to update suggestions
if (filterValue.match(/^\d{6}$/)) {
if (filterValue.match(/^\d{8}$/)) {
handleFilterCreation(filterType, filterValue);
}
} else {
@ -414,7 +429,11 @@ export default function InputWithTags({
return (
<>
<Command shouldFilter={false} ref={commandRef} className="rounded-md">
<Command
shouldFilter={false}
ref={commandRef}
className="rounded-md border"
>
<div className="relative">
<CommandInput
ref={inputRef}
@ -444,7 +463,7 @@ export default function InputWithTags({
{(search || Object.keys(filters).length > 0) && (
<Tooltip>
<TooltipTrigger>
<LuSave
<LuStar
className="size-4 cursor-pointer text-secondary-foreground"
onClick={handleSetSearchHistory}
/>
@ -550,7 +569,13 @@ export default function InputWithTags({
>
{filterType}:
{filterType === "before" || filterType === "after"
? new Date(filterValues as number).toLocaleDateString()
? new Date(
(filterType === "before"
? (filterValues as number) + 1
: (filterValues as number)) * 1000,
).toLocaleDateString(
window.navigator?.language || "en-US",
)
: filterValues}
<button
onClick={() =>
@ -573,8 +598,9 @@ export default function InputWithTags({
{!currentFilterType &&
!inputValue &&
searchHistoryLoaded &&
(searchHistory?.length ?? 0) > 0 && (
<CommandGroup heading="Previous Searches">
<CommandGroup heading="Saved Searches">
{searchHistory?.map((suggestion, index) => (
<CommandItem
key={index}

View File

@ -10,6 +10,7 @@ import {
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { useState } from "react";
import { isMobile } from "react-device-detect";
type SaveSearchDialogProps = {
isOpen: boolean;
@ -34,7 +35,13 @@ export function SaveSearchDialog({
return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent>
<DialogContent
onOpenAutoFocus={(e) => {
if (isMobile) {
e.preventDefault();
}
}}
>
<DialogHeader>
<DialogTitle>Save Search</DialogTitle>
<DialogDescription className="sr-only">
@ -43,14 +50,19 @@ export function SaveSearchDialog({
</DialogHeader>
<Input
value={searchName}
className="text-md"
onChange={(e) => setSearchName(e.target.value)}
placeholder="Enter a name for your search"
/>
<DialogFooter>
<Button onClick={onClose} variant="select">
Cancel
<Button onClick={onClose}>Cancel</Button>
<Button
onClick={handleSave}
variant="select"
className="mb-2 md:mb-0"
>
Save
</Button>
<Button onClick={handleSave}>Save</Button>
</DialogFooter>
</DialogContent>
</Dialog>

View File

@ -296,3 +296,63 @@ export function isCurrentHour(timestamp: number) {
return timestamp > now.getTime() / 1000;
}
export const convertLocalDateToTimestamp = (dateString: string): number => {
// Ensure the date string is in the correct format (8 digits)
if (!/^\d{8}$/.test(dateString)) {
return 0;
}
// Determine the local date format
const format = new Intl.DateTimeFormat()
.formatToParts(new Date())
.reduce((acc, part) => {
if (part.type === "day") acc.push("D");
if (part.type === "month") acc.push("M");
if (part.type === "year") acc.push("Y");
return acc;
}, [] as string[])
.join("");
let day: string, month: string, year: string;
// Parse the date string according to the detected format
switch (format) {
case "DMY":
[day, month, year] = [
dateString.slice(0, 2),
dateString.slice(2, 4),
dateString.slice(4),
];
break;
case "MDY":
[month, day, year] = [
dateString.slice(0, 2),
dateString.slice(2, 4),
dateString.slice(4),
];
break;
case "YMD":
[year, month, day] = [
dateString.slice(0, 2),
dateString.slice(2, 4),
dateString.slice(4),
];
break;
default:
return 0;
}
// Create a Date object based on the local timezone
const localDate = new Date(`${year}-${month}-${day}T00:00:00`);
// Check if the date is valid
if (isNaN(localDate.getTime())) {
return 0;
}
// Convert local date to UTC timestamp
const timestamp = localDate.getTime();
return timestamp;
};