mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-14 23:25:25 +03:00
better date handling
This commit is contained in:
parent
eb6d2c8983
commit
89f8f69948
@ -5,8 +5,8 @@ import {
|
|||||||
LuImage,
|
LuImage,
|
||||||
LuChevronDown,
|
LuChevronDown,
|
||||||
LuChevronUp,
|
LuChevronUp,
|
||||||
LuSave,
|
|
||||||
LuTrash2,
|
LuTrash2,
|
||||||
|
LuStar,
|
||||||
} from "react-icons/lu";
|
} from "react-icons/lu";
|
||||||
import {
|
import {
|
||||||
FilterType,
|
FilterType,
|
||||||
@ -28,23 +28,8 @@ import { TooltipPortal } from "@radix-ui/react-tooltip";
|
|||||||
import { usePersistence } from "@/hooks/use-persistence";
|
import { usePersistence } from "@/hooks/use-persistence";
|
||||||
import { SaveSearchDialog } from "./SaveSearchDialog";
|
import { SaveSearchDialog } from "./SaveSearchDialog";
|
||||||
import { DeleteSearchDialog } from "./DeleteSearchDialog";
|
import { DeleteSearchDialog } from "./DeleteSearchDialog";
|
||||||
|
import { convertLocalDateToTimestamp } from "@/utils/dateUtil";
|
||||||
const convertMMDDYYToTimestamp = (dateString: string): number => {
|
import { toast } from "sonner";
|
||||||
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[];
|
|
||||||
|
|
||||||
type InputWithTagsProps = {
|
type InputWithTagsProps = {
|
||||||
filters: SearchFilter;
|
filters: SearchFilter;
|
||||||
@ -76,7 +61,7 @@ export default function InputWithTags({
|
|||||||
|
|
||||||
const [searchHistory, setSearchHistory, searchHistoryLoaded] = usePersistence<
|
const [searchHistory, setSearchHistory, searchHistoryLoaded] = usePersistence<
|
||||||
SavedSearchQuery[]
|
SavedSearchQuery[]
|
||||||
>("frigate-search-history", emptyObject);
|
>("frigate-search-history");
|
||||||
|
|
||||||
const [isSaveDialogOpen, setIsSaveDialogOpen] = useState(false);
|
const [isSaveDialogOpen, setIsSaveDialogOpen] = useState(false);
|
||||||
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
|
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
|
||||||
@ -198,9 +183,39 @@ export default function InputWithTags({
|
|||||||
switch (type) {
|
switch (type) {
|
||||||
case "before":
|
case "before":
|
||||||
case "after":
|
case "after":
|
||||||
timestamp = convertMMDDYYToTimestamp(value);
|
timestamp = convertLocalDateToTimestamp(value);
|
||||||
if (timestamp > 0) {
|
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;
|
break;
|
||||||
case "search_type":
|
case "search_type":
|
||||||
@ -247,7 +262,7 @@ export default function InputWithTags({
|
|||||||
trimmedValue,
|
trimmedValue,
|
||||||
) ||
|
) ||
|
||||||
((filterType === "before" || filterType === "after") &&
|
((filterType === "before" || filterType === "after") &&
|
||||||
trimmedValue.match(/^\d{6}$/))
|
trimmedValue.match(/^\d{8}$/))
|
||||||
) {
|
) {
|
||||||
createFilter(filterType, trimmedValue);
|
createFilter(filterType, trimmedValue);
|
||||||
setInputValue((prev) => {
|
setInputValue((prev) => {
|
||||||
@ -295,7 +310,7 @@ export default function InputWithTags({
|
|||||||
|
|
||||||
if (filterType === "before" || filterType === "after") {
|
if (filterType === "before" || filterType === "after") {
|
||||||
// For before and after, we don't need to update suggestions
|
// For before and after, we don't need to update suggestions
|
||||||
if (filterValue.match(/^\d{6}$/)) {
|
if (filterValue.match(/^\d{8}$/)) {
|
||||||
handleFilterCreation(filterType, filterValue);
|
handleFilterCreation(filterType, filterValue);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -414,7 +429,11 @@ export default function InputWithTags({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Command shouldFilter={false} ref={commandRef} className="rounded-md">
|
<Command
|
||||||
|
shouldFilter={false}
|
||||||
|
ref={commandRef}
|
||||||
|
className="rounded-md border"
|
||||||
|
>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<CommandInput
|
<CommandInput
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
@ -444,7 +463,7 @@ export default function InputWithTags({
|
|||||||
{(search || Object.keys(filters).length > 0) && (
|
{(search || Object.keys(filters).length > 0) && (
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger>
|
||||||
<LuSave
|
<LuStar
|
||||||
className="size-4 cursor-pointer text-secondary-foreground"
|
className="size-4 cursor-pointer text-secondary-foreground"
|
||||||
onClick={handleSetSearchHistory}
|
onClick={handleSetSearchHistory}
|
||||||
/>
|
/>
|
||||||
@ -550,7 +569,13 @@ export default function InputWithTags({
|
|||||||
>
|
>
|
||||||
{filterType}:
|
{filterType}:
|
||||||
{filterType === "before" || filterType === "after"
|
{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}
|
: filterValues}
|
||||||
<button
|
<button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
@ -573,8 +598,9 @@ export default function InputWithTags({
|
|||||||
|
|
||||||
{!currentFilterType &&
|
{!currentFilterType &&
|
||||||
!inputValue &&
|
!inputValue &&
|
||||||
|
searchHistoryLoaded &&
|
||||||
(searchHistory?.length ?? 0) > 0 && (
|
(searchHistory?.length ?? 0) > 0 && (
|
||||||
<CommandGroup heading="Previous Searches">
|
<CommandGroup heading="Saved Searches">
|
||||||
{searchHistory?.map((suggestion, index) => (
|
{searchHistory?.map((suggestion, index) => (
|
||||||
<CommandItem
|
<CommandItem
|
||||||
key={index}
|
key={index}
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import {
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { isMobile } from "react-device-detect";
|
||||||
|
|
||||||
type SaveSearchDialogProps = {
|
type SaveSearchDialogProps = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@ -34,7 +35,13 @@ export function SaveSearchDialog({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||||
<DialogContent>
|
<DialogContent
|
||||||
|
onOpenAutoFocus={(e) => {
|
||||||
|
if (isMobile) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Save Search</DialogTitle>
|
<DialogTitle>Save Search</DialogTitle>
|
||||||
<DialogDescription className="sr-only">
|
<DialogDescription className="sr-only">
|
||||||
@ -43,14 +50,19 @@ export function SaveSearchDialog({
|
|||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<Input
|
<Input
|
||||||
value={searchName}
|
value={searchName}
|
||||||
|
className="text-md"
|
||||||
onChange={(e) => setSearchName(e.target.value)}
|
onChange={(e) => setSearchName(e.target.value)}
|
||||||
placeholder="Enter a name for your search"
|
placeholder="Enter a name for your search"
|
||||||
/>
|
/>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button onClick={onClose} variant="select">
|
<Button onClick={onClose}>Cancel</Button>
|
||||||
Cancel
|
<Button
|
||||||
|
onClick={handleSave}
|
||||||
|
variant="select"
|
||||||
|
className="mb-2 md:mb-0"
|
||||||
|
>
|
||||||
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handleSave}>Save</Button>
|
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@ -296,3 +296,63 @@ export function isCurrentHour(timestamp: number) {
|
|||||||
|
|
||||||
return timestamp > now.getTime() / 1000;
|
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;
|
||||||
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user