diff --git a/web/src/components/audio/AudioLevelGraph.tsx b/web/src/components/audio/AudioLevelGraph.tsx index 4f0e75722..74c3ce0e6 100644 --- a/web/src/components/audio/AudioLevelGraph.tsx +++ b/web/src/components/audio/AudioLevelGraph.tsx @@ -8,6 +8,7 @@ import { formatUnixTimestampToDateTime } from "@/utils/dateUtil"; import useSWR from "swr"; import { FrigateConfig } from "@/types/frigateConfig"; import { useTranslation } from "react-i18next"; +import { useTimeFormat } from "@/hooks/use-date-utils"; const GRAPH_COLORS = ["#3b82f6", "#ef4444"]; // RMS, dBFS @@ -72,7 +73,7 @@ export function AudioLevelGraph({ cameraName }: AudioLevelGraphProps) { return [last.rms, last.dBFS]; }, [audioData]); - const timeFormat = config?.ui.time_format === "24hour" ? "24hour" : "12hour"; + const timeFormat = useTimeFormat(config); const formatString = useMemo( () => t(`time.formattedTimestampHourMinuteSecond.${timeFormat}`, { diff --git a/web/src/components/button/DownloadVideoButton.tsx b/web/src/components/button/DownloadVideoButton.tsx index 607458af4..93a8e1d8a 100644 --- a/web/src/components/button/DownloadVideoButton.tsx +++ b/web/src/components/button/DownloadVideoButton.tsx @@ -7,6 +7,7 @@ import { useTranslation } from "react-i18next"; import useSWR from "swr"; import { FrigateConfig } from "@/types/frigateConfig"; import { useDateLocale } from "@/hooks/use-date-locale"; +import { useTimeFormat } from "@/hooks/use-date-utils"; import { useMemo } from "react"; type DownloadVideoButtonProps = { @@ -26,7 +27,7 @@ export function DownloadVideoButton({ const { data: config } = useSWR("config"); const locale = useDateLocale(); - const timeFormat = config?.ui.time_format === "24hour" ? "24hour" : "12hour"; + const timeFormat = useTimeFormat(config); const format = useMemo(() => { return t(`time.formattedTimestampFilename.${timeFormat}`, { ns: "common" }); }, [t, timeFormat]); diff --git a/web/src/components/card/ReviewCard.tsx b/web/src/components/card/ReviewCard.tsx index 6b8b6bb52..bf256ab5e 100644 --- a/web/src/components/card/ReviewCard.tsx +++ b/web/src/components/card/ReviewCard.tsx @@ -1,5 +1,5 @@ import { baseUrl } from "@/api/baseUrl"; -import { useFormattedTimestamp } from "@/hooks/use-date-utils"; +import { useFormattedTimestamp, use24HourTime } from "@/hooks/use-date-utils"; import { FrigateConfig } from "@/types/frigateConfig"; import { REVIEW_PADDING, ReviewSegment } from "@/types/review"; import { getIconForLabel } from "@/utils/iconUtil"; @@ -55,9 +55,10 @@ export default function ReviewCard({ const { t } = useTranslation(["components/dialog"]); const { data: config } = useSWR("config"); const [imgRef, imgLoaded, onImgLoad] = useImageLoaded(); + const is24Hour = use24HourTime(config); const formattedDate = useFormattedTimestamp( event.start_time, - config?.ui.time_format == "24hour" + is24Hour ? t("time.formattedTimestampHourMinute.24hour", { ns: "common" }) : t("time.formattedTimestampHourMinute.12hour", { ns: "common" }), config?.ui.timezone, diff --git a/web/src/components/card/SearchThumbnailFooter.tsx b/web/src/components/card/SearchThumbnailFooter.tsx index 808ad2831..1087a53fb 100644 --- a/web/src/components/card/SearchThumbnailFooter.tsx +++ b/web/src/components/card/SearchThumbnailFooter.tsx @@ -1,7 +1,7 @@ import TimeAgo from "../dynamic/TimeAgo"; import useSWR from "swr"; import { FrigateConfig } from "@/types/frigateConfig"; -import { useFormattedTimestamp } from "@/hooks/use-date-utils"; +import { useFormattedTimestamp, use24HourTime } from "@/hooks/use-date-utils"; import { SearchResult } from "@/types/search"; import ActivityIndicator from "../indicators/activity-indicator"; import SearchResultActions from "../menu/SearchResultActions"; @@ -29,9 +29,10 @@ export default function SearchThumbnailFooter({ const { data: config } = useSWR("config"); // date + const is24Hour = use24HourTime(config); const formattedDate = useFormattedTimestamp( searchResult.start_time, - config?.ui.time_format == "24hour" + is24Hour ? t("time.formattedTimestampMonthDayHourMinute.24hour", { ns: "common" }) : t("time.formattedTimestampMonthDayHourMinute.12hour", { ns: "common" }), config?.ui.timezone, diff --git a/web/src/components/config-form/sectionExtras/NotificationsSettingsExtras.tsx b/web/src/components/config-form/sectionExtras/NotificationsSettingsExtras.tsx index 0f186e105..b97c90448 100644 --- a/web/src/components/config-form/sectionExtras/NotificationsSettingsExtras.tsx +++ b/web/src/components/config-form/sectionExtras/NotificationsSettingsExtras.tsx @@ -43,6 +43,7 @@ import { SelectItem, } from "@/components/ui/select"; import { formatUnixTimestampToDateTime } from "@/utils/dateUtil"; +import { use24HourTime } from "@/hooks/use-date-utils"; import FilterSwitch from "@/components/filter/FilterSwitch"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Trans, useTranslation } from "react-i18next"; @@ -752,6 +753,7 @@ export function CameraNotificationSwitch({ }; const locale = useDateLocale(); + const is24Hour = use24HourTime(config); const formatSuspendedUntil = (timestamp: string) => { if (timestamp === "0") return t("time.untilForRestart", { ns: "common" }); @@ -760,14 +762,13 @@ export function CameraNotificationSwitch({ time_style: "medium", date_style: "medium", timezone: config?.ui.timezone, - date_format: - config?.ui.time_format == "24hour" - ? t("time.formattedTimestampMonthDayHourMinute.24hour", { - ns: "common", - }) - : t("time.formattedTimestampMonthDayHourMinute.12hour", { - ns: "common", - }), + date_format: is24Hour + ? t("time.formattedTimestampMonthDayHourMinute.24hour", { + ns: "common", + }) + : t("time.formattedTimestampMonthDayHourMinute.12hour", { + ns: "common", + }), locale: locale, }); return t("time.untilForTime", { ns: "common", time }); diff --git a/web/src/components/graph/LineGraph.tsx b/web/src/components/graph/LineGraph.tsx index cf2114a9c..b571bdfc2 100644 --- a/web/src/components/graph/LineGraph.tsx +++ b/web/src/components/graph/LineGraph.tsx @@ -8,6 +8,7 @@ import { isMobileOnly } from "react-device-detect"; import { useTranslation } from "react-i18next"; import { MdCircle } from "react-icons/md"; import useSWR from "swr"; +import { useTimeFormat } from "@/hooks/use-date-utils"; const GRAPH_COLORS = ["#5C7CFA", "#ED5CFA", "#FAD75C"]; @@ -48,7 +49,7 @@ export function CameraLineGraph({ const locale = useDateLocale(); - const timeFormat = config?.ui.time_format === "24hour" ? "24hour" : "12hour"; + const timeFormat = useTimeFormat(config); const format = useMemo(() => { return t(`time.formattedTimestampHourMinute.${timeFormat}`, { ns: "common", @@ -203,7 +204,7 @@ export function EventsPerSecondsLineGraph({ const locale = useDateLocale(); const { t } = useTranslation(["common"]); - const timeFormat = config?.ui.time_format === "24hour" ? "24hour" : "12hour"; + const timeFormat = useTimeFormat(config); const format = useMemo(() => { return t(`time.formattedTimestampHourMinute.${timeFormat}`, { ns: "common", diff --git a/web/src/components/graph/SystemGraph.tsx b/web/src/components/graph/SystemGraph.tsx index bf13417d0..c1ee47f71 100644 --- a/web/src/components/graph/SystemGraph.tsx +++ b/web/src/components/graph/SystemGraph.tsx @@ -8,6 +8,7 @@ import Chart from "react-apexcharts"; import { isMobileOnly } from "react-device-detect"; import { useTranslation } from "react-i18next"; import useSWR from "swr"; +import { useTimeFormat } from "@/hooks/use-date-utils"; type ThresholdBarGraphProps = { graphId: string; @@ -53,7 +54,7 @@ export function ThresholdBarGraph({ const locale = useDateLocale(); const { t } = useTranslation(["common"]); - const timeFormat = config?.ui.time_format === "24hour" ? "24hour" : "12hour"; + const timeFormat = useTimeFormat(config); const format = useMemo(() => { return t(`time.formattedTimestampHourMinute.${timeFormat}`, { ns: "common", diff --git a/web/src/components/input/InputWithTags.tsx b/web/src/components/input/InputWithTags.tsx index 70f1cd0c9..771f65f26 100755 --- a/web/src/components/input/InputWithTags.tsx +++ b/web/src/components/input/InputWithTags.tsx @@ -50,6 +50,7 @@ import { import { toast } from "sonner"; import useSWR from "swr"; import { FrigateConfig } from "@/types/frigateConfig"; +import { use24HourTime } from "@/hooks/use-date-utils"; import { MdImageSearch } from "react-icons/md"; import { useTranslation } from "react-i18next"; import { getTranslatedLabel } from "@/utils/i18n"; @@ -80,6 +81,8 @@ export default function InputWithTags({ const { data: config } = useSWR("config", { revalidateOnFocus: false, }); + const is24Hour = use24HourTime(config); + const resolvedTimeFormat = is24Hour ? "24hour" : ("12hour" as const); const allAudioListenLabels = useMemo>(() => { if (!config) { @@ -431,12 +434,8 @@ export default function InputWithTags({ const [startTime, endTime] = (filterValues as string) .replace("-", ",") .split(","); - return `${ - config?.ui.time_format === "24hour" - ? startTime - : convertTo12Hour(startTime) - } - ${ - config?.ui.time_format === "24hour" ? endTime : convertTo12Hour(endTime) + return `${is24Hour ? startTime : convertTo12Hour(startTime)} - ${ + is24Hour ? endTime : convertTo12Hour(endTime) }`; } else if (filterType === "min_score" || filterType === "max_score") { return Math.round(Number(filterValues) * 100).toString() + "%"; @@ -478,7 +477,7 @@ export default function InputWithTags({ (filterType === "time_range" && isValidTimeRange( trimmedValue.replace("-", ","), - config?.ui.time_format, + resolvedTimeFormat, )) || ((filterType === "min_score" || filterType === "max_score") && !isNaN(Number(trimmedValue)) && @@ -495,7 +494,7 @@ export default function InputWithTags({ ? trimmedValue .replace("-", ",") .split(",") - .map((time) => to24Hour(time.trim(), config?.ui.time_format)) + .map((time) => to24Hour(time.trim(), resolvedTimeFormat)) .join(",") : trimmedValue, ); @@ -511,7 +510,7 @@ export default function InputWithTags({ setCurrentFilterType(null); } }, - [allSuggestions, createFilter, config], + [allSuggestions, createFilter, resolvedTimeFormat], ); const handleInputChange = useCallback( @@ -598,7 +597,7 @@ export default function InputWithTags({ suggestion = suggestion .replace("-", ",") .split(",") - .map((time) => to24Hour(time.trim(), config?.ui.time_format)) + .map((time) => to24Hour(time.trim(), resolvedTimeFormat)) .join(","); } createFilter(currentFilterType, suggestion); @@ -627,7 +626,7 @@ export default function InputWithTags({ inputRef.current?.focus(); }, - [createFilter, currentFilterType, allSuggestions, config], + [createFilter, currentFilterType, allSuggestions, resolvedTimeFormat], ); const handleSearch = useCallback( @@ -779,10 +778,7 @@ export default function InputWithTags({
  • {t("filter.tips.desc.step5", { - exampleTime: - config?.ui.time_format == "24hour" - ? "15:00-16:00" - : "3:00PM-4:00PM", + exampleTime: is24Hour ? "15:00-16:00" : "3:00PM-4:00PM", })}
  • {t("filter.tips.desc.step6")}
  • diff --git a/web/src/components/menu/LiveContextMenu.tsx b/web/src/components/menu/LiveContextMenu.tsx index 671506712..8ed78e348 100644 --- a/web/src/components/menu/LiveContextMenu.tsx +++ b/web/src/components/menu/LiveContextMenu.tsx @@ -46,6 +46,7 @@ import { } from "@/api/ws"; import { useTranslation } from "react-i18next"; import { useDateLocale } from "@/hooks/use-date-locale"; +import { use24HourTime } from "@/hooks/use-date-utils"; import { useIsAdmin } from "@/hooks/use-is-admin"; import { CameraNameLabel } from "../camera/FriendlyNameLabel"; import { LiveStreamMetadata } from "@/types/live"; @@ -247,6 +248,8 @@ export default function LiveContextMenu({ const locale = useDateLocale(); + const is24Hour = use24HourTime(config); + const formatSuspendedUntil = (timestamp: string) => { // Some languages require a change in word order if (timestamp === "0") return t("time.untilForRestart", { ns: "common" }); @@ -255,14 +258,13 @@ export default function LiveContextMenu({ time_style: "medium", date_style: "medium", timezone: config?.ui.timezone, - date_format: - config?.ui.time_format == "24hour" - ? t("time.formattedTimestampMonthDayHourMinute.24hour", { - ns: "common", - }) - : t("time.formattedTimestampMonthDayHourMinute.12hour", { - ns: "common", - }), + date_format: is24Hour + ? t("time.formattedTimestampMonthDayHourMinute.24hour", { + ns: "common", + }) + : t("time.formattedTimestampMonthDayHourMinute.12hour", { + ns: "common", + }), locale: locale, }); return t("time.untilForTime", { ns: "common", time }); diff --git a/web/src/components/overlay/CustomTimeSelector.tsx b/web/src/components/overlay/CustomTimeSelector.tsx index 0d9a4d052..7948a8b5b 100644 --- a/web/src/components/overlay/CustomTimeSelector.tsx +++ b/web/src/components/overlay/CustomTimeSelector.tsx @@ -3,7 +3,7 @@ import { Button } from "../ui/button"; import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover"; import { SelectSeparator } from "../ui/select"; import { TimeRange } from "@/types/timeline"; -import { useFormattedTimestamp } from "@/hooks/use-date-utils"; +import { useFormattedTimestamp, use24HourTime } from "@/hooks/use-date-utils"; import { getUTCOffset } from "@/utils/dateUtil"; import { TimezoneAwareCalendar } from "./ReviewActivityCalendar"; import { FaArrowRight, FaCalendarAlt } from "react-icons/fa"; @@ -69,16 +69,18 @@ export function CustomTimeSelector({ return time; }, [range, latestTime, timezoneOffset, localTimeOffset]); + const is24Hour = use24HourTime(config); + const formattedStart = useFormattedTimestamp( startTime, - config?.ui.time_format == "24hour" + is24Hour ? t("time.formattedTimestamp.24hour") : t("time.formattedTimestamp.12hour"), ); const formattedEnd = useFormattedTimestamp( endTime, - config?.ui.time_format == "24hour" + is24Hour ? t("time.formattedTimestamp.24hour") : t("time.formattedTimestamp.12hour"), ); diff --git a/web/src/components/overlay/detail/SearchDetailDialog.tsx b/web/src/components/overlay/detail/SearchDetailDialog.tsx index db4e08e6f..c23b916a5 100644 --- a/web/src/components/overlay/detail/SearchDetailDialog.tsx +++ b/web/src/components/overlay/detail/SearchDetailDialog.tsx @@ -2,7 +2,7 @@ import { isDesktop, isIOS, isMobile, isSafari } from "react-device-detect"; import { SearchResult } from "@/types/search"; import useSWR from "swr"; import { FrigateConfig } from "@/types/frigateConfig"; -import { useFormattedTimestamp } from "@/hooks/use-date-utils"; +import { useFormattedTimestamp, use24HourTime } from "@/hooks/use-date-utils"; import { getIconForLabel } from "@/utils/iconUtil"; import { useApiHost } from "@/api"; import { Button } from "../../ui/button"; @@ -769,9 +769,10 @@ function ObjectDetailsTab({ setShowNavigationButtons, ]); + const is24Hour = use24HourTime(config); const formattedDate = useFormattedTimestamp( search?.start_time ?? 0, - config?.ui.time_format == "24hour" + is24Hour ? t("time.formattedTimestampMonthDayYearHourMinute.24hour", { ns: "common", }) diff --git a/web/src/components/overlay/detail/TrackingDetails.tsx b/web/src/components/overlay/detail/TrackingDetails.tsx index 881a02db7..00d09ec4f 100644 --- a/web/src/components/overlay/detail/TrackingDetails.tsx +++ b/web/src/components/overlay/detail/TrackingDetails.tsx @@ -7,6 +7,7 @@ import ActivityIndicator from "@/components/indicators/activity-indicator"; import { TrackingDetailsSequence } from "@/types/timeline"; import { FrigateConfig } from "@/types/frigateConfig"; import { formatUnixTimestampToDateTime } from "@/utils/dateUtil"; +import { use24HourTime } from "@/hooks/use-date-utils"; import { getIconForLabel } from "@/utils/iconUtil"; import { LuCircle, LuFolderX } from "react-icons/lu"; import { cn } from "@/lib/utils"; @@ -428,17 +429,18 @@ export function TrackingDetails({ [annotationOffset, displaySource, timestampToVideoTime], ); + const is24Hour = use24HourTime(config); + const formattedStart = config ? formatUnixTimestampToDateTime(event.start_time ?? 0, { timezone: config.ui.timezone, - date_format: - config.ui.time_format == "24hour" - ? t("time.formattedTimestamp.24hour", { - ns: "common", - }) - : t("time.formattedTimestamp.12hour", { - ns: "common", - }), + date_format: is24Hour + ? t("time.formattedTimestamp.24hour", { + ns: "common", + }) + : t("time.formattedTimestamp.12hour", { + ns: "common", + }), time_style: "medium", date_style: "medium", }) @@ -448,14 +450,13 @@ export function TrackingDetails({ config && event.end_time != null ? formatUnixTimestampToDateTime(event.end_time, { timezone: config.ui.timezone, - date_format: - config.ui.time_format == "24hour" - ? t("time.formattedTimestamp.24hour", { - ns: "common", - }) - : t("time.formattedTimestamp.12hour", { - ns: "common", - }), + date_format: is24Hour + ? t("time.formattedTimestamp.24hour", { + ns: "common", + }) + : t("time.formattedTimestamp.12hour", { + ns: "common", + }), time_style: "medium", date_style: "medium", }) @@ -917,24 +918,25 @@ function LifecycleIconRow({ [effectiveTime, item.timestamp], ); + const is24Hour = use24HourTime(config); + const formattedEventTimestamp = useMemo( () => config ? formatUnixTimestampToDateTime(item.timestamp ?? 0, { timezone: config.ui.timezone, - date_format: - config.ui.time_format == "24hour" - ? t("time.formattedTimestampHourMinuteSecond.24hour", { - ns: "common", - }) - : t("time.formattedTimestampHourMinuteSecond.12hour", { - ns: "common", - }), + date_format: is24Hour + ? t("time.formattedTimestampHourMinuteSecond.24hour", { + ns: "common", + }) + : t("time.formattedTimestampHourMinuteSecond.12hour", { + ns: "common", + }), time_style: "medium", date_style: "medium", }) : "", - [config, item.timestamp, t], + [config, is24Hour, item.timestamp, t], ); const ratio = useMemo( diff --git a/web/src/components/player/PreviewThumbnailPlayer.tsx b/web/src/components/player/PreviewThumbnailPlayer.tsx index 53d2e0ceb..21aa41a43 100644 --- a/web/src/components/player/PreviewThumbnailPlayer.tsx +++ b/web/src/components/player/PreviewThumbnailPlayer.tsx @@ -12,7 +12,7 @@ import useSWR from "swr"; import { FrigateConfig } from "@/types/frigateConfig"; import { isIOS, isMobile, isSafari } from "react-device-detect"; import Chip from "@/components/indicators/Chip"; -import { useFormattedTimestamp } from "@/hooks/use-date-utils"; +import { useFormattedTimestamp, use24HourTime } from "@/hooks/use-date-utils"; import useImageLoaded from "@/hooks/use-image-loaded"; import { useSwipeable } from "react-swipeable"; import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip"; @@ -174,9 +174,10 @@ export default function PreviewThumbnailPlayer({ // date + const is24Hour = use24HourTime(config); const formattedDate = useFormattedTimestamp( review.start_time, - config?.ui.time_format == "24hour" + is24Hour ? t("time.formattedTimestampMonthDayHourMinute.24hour", { ns: "common" }) : t("time.formattedTimestampMonthDayHourMinute.12hour", { ns: "common" }), config?.ui?.timezone, diff --git a/web/src/components/timeline/DetailStream.tsx b/web/src/components/timeline/DetailStream.tsx index baa665f50..36df5e9f8 100644 --- a/web/src/components/timeline/DetailStream.tsx +++ b/web/src/components/timeline/DetailStream.tsx @@ -8,6 +8,7 @@ import { formatUnixTimestampToDateTime, getDurationFromTimestamps, } from "@/utils/dateUtil"; +import { use24HourTime } from "@/hooks/use-date-utils"; import { useTranslation } from "react-i18next"; import AnnotationOffsetSlider from "@/components/overlay/detail/AnnotationOffsetSlider"; import { FrigateConfig } from "@/types/frigateConfig"; @@ -398,12 +399,12 @@ function ReviewGroup({ } }, [isActive, alwaysExpandActive]); + const is24Hour = use24HourTime(config); const displayTime = formatUnixTimestampToDateTime(start, { timezone: config.ui.timezone, - date_format: - config.ui.time_format == "24hour" - ? t("time.formattedTimestampHourMinuteSecond.24hour", { ns: "common" }) - : t("time.formattedTimestampHourMinuteSecond.12hour", { ns: "common" }), + date_format: is24Hour + ? t("time.formattedTimestampHourMinuteSecond.24hour", { ns: "common" }) + : t("time.formattedTimestampHourMinuteSecond.12hour", { ns: "common" }), time_style: "medium", date_style: "medium", }); @@ -787,17 +788,17 @@ function LifecycleItem({ ); }, [config, item]); + const is24Hour = use24HourTime(config); const formattedEventTimestamp = config ? formatUnixTimestampToDateTime(item?.timestamp ?? 0, { timezone: config.ui.timezone, - date_format: - config.ui.time_format == "24hour" - ? t("time.formattedTimestampHourMinuteSecond.24hour", { - ns: "common", - }) - : t("time.formattedTimestampHourMinuteSecond.12hour", { - ns: "common", - }), + date_format: is24Hour + ? t("time.formattedTimestampHourMinuteSecond.24hour", { + ns: "common", + }) + : t("time.formattedTimestampHourMinuteSecond.12hour", { + ns: "common", + }), time_style: "medium", date_style: "medium", }) diff --git a/web/src/components/timeline/segment-metadata.tsx b/web/src/components/timeline/segment-metadata.tsx index 70e44b40b..5c674cac2 100644 --- a/web/src/components/timeline/segment-metadata.tsx +++ b/web/src/components/timeline/segment-metadata.tsx @@ -1,4 +1,4 @@ -import { useFormattedTimestamp } from "@/hooks/use-date-utils"; +import { useFormattedTimestamp, useTimeFormat } from "@/hooks/use-date-utils"; import { FrigateConfig } from "@/types/frigateConfig"; import { useMemo } from "react"; import { useTranslation } from "react-i18next"; @@ -36,7 +36,7 @@ export function MinimapBounds({ }: MinimapSegmentProps) { const { data: config } = useSWR("config"); const { t } = useTranslation(["common"]); - const timeFormat = config?.ui.time_format === "24hour" ? "24hour" : "12hour"; + const timeFormat = useTimeFormat(config); const formatKey = dense ? `time.formattedTimestampHourMinute.${timeFormat}` @@ -104,7 +104,7 @@ export function Timestamp({ const { t } = useTranslation(["common"]); const { data: config } = useSWR("config"); - const timeFormat = config?.ui.time_format === "24hour" ? "24hour" : "12hour"; + const timeFormat = useTimeFormat(config); const format = t(`time.formattedTimestampHourMinute.${timeFormat}`); const formattedTimestamp = useFormattedTimestamp( diff --git a/web/src/hooks/use-date-utils.ts b/web/src/hooks/use-date-utils.ts index dbe3085d4..329266b6b 100644 --- a/web/src/hooks/use-date-utils.ts +++ b/web/src/hooks/use-date-utils.ts @@ -84,6 +84,18 @@ export function use24HourTime(config: FrigateConfig | undefined) { }, [config, localeUses24HourTime]); } +/** + * Returns the resolved time format key ("24hour" | "12hour") based on config + * and browser locale. Use this instead of checking config.ui.time_format directly + * to correctly handle the "browser" setting. + */ +export function useTimeFormat( + config: FrigateConfig | undefined, +): "24hour" | "12hour" { + const is24Hour = use24HourTime(config); + return is24Hour ? "24hour" : "12hour"; +} + export function useFormattedHour( config: FrigateConfig | undefined, time: string, // hour is assumed to be in 24 hour format per the Date object diff --git a/web/src/hooks/use-draggable-element.ts b/web/src/hooks/use-draggable-element.ts index 9103a8a37..bf43901e8 100644 --- a/web/src/hooks/use-draggable-element.ts +++ b/web/src/hooks/use-draggable-element.ts @@ -4,6 +4,7 @@ import { FrigateConfig } from "@/types/frigateConfig"; import useSWR from "swr"; import { formatUnixTimestampToDateTime } from "@/utils/dateUtil"; import { useDateLocale } from "./use-date-locale"; +import { useTimeFormat } from "./use-date-utils"; import { useTranslation } from "react-i18next"; import useUserInteraction from "./use-user-interaction"; @@ -168,7 +169,7 @@ function useDraggableElement({ const { t } = useTranslation(["common"]); const locale = useDateLocale(); - const timeFormat = config?.ui.time_format === "24hour" ? "24hour" : "12hour"; + const timeFormat = useTimeFormat(config); const format = useMemo(() => { const formatKey = `time.${ segmentDuration < 60 && !dense diff --git a/web/src/views/events/MotionPreviewsPane.tsx b/web/src/views/events/MotionPreviewsPane.tsx index 6b5b262ce..37cf9e261 100644 --- a/web/src/views/events/MotionPreviewsPane.tsx +++ b/web/src/views/events/MotionPreviewsPane.tsx @@ -19,7 +19,7 @@ import { useResizeObserver } from "@/hooks/resize-observer"; import { Skeleton } from "@/components/ui/skeleton"; import ActivityIndicator from "@/components/indicators/activity-indicator"; import TimeAgo from "@/components/dynamic/TimeAgo"; -import { useFormattedTimestamp } from "@/hooks/use-date-utils"; +import { useFormattedTimestamp, use24HourTime } from "@/hooks/use-date-utils"; import { FrigateConfig } from "@/types/frigateConfig"; const MOTION_HEATMAP_GRID_SIZE = 16; @@ -165,9 +165,10 @@ function MotionPreviewClip({ const [fallbackFrameIndex, setFallbackFrameIndex] = useState(0); const [fallbackFramesReady, setFallbackFramesReady] = useState(false); + const is24Hour = use24HourTime(config); const formattedDate = useFormattedTimestamp( range.start_time, - config?.ui.time_format == "24hour" + is24Hour ? t("time.formattedTimestampMonthDayHourMinute.24hour", { ns: "common", }) diff --git a/web/src/views/motion-search/MotionSearchDialog.tsx b/web/src/views/motion-search/MotionSearchDialog.tsx index 5f31f1d07..cff49b4b1 100644 --- a/web/src/views/motion-search/MotionSearchDialog.tsx +++ b/web/src/views/motion-search/MotionSearchDialog.tsx @@ -43,8 +43,9 @@ import { TimezoneAwareCalendar } from "@/components/overlay/ReviewActivityCalend import { useApiHost } from "@/api"; import { useResizeObserver } from "@/hooks/resize-observer"; -import { useFormattedTimestamp } from "@/hooks/use-date-utils"; +import { useFormattedTimestamp, use24HourTime } from "@/hooks/use-date-utils"; import { getUTCOffset } from "@/utils/dateUtil"; +import useSWR from "swr"; import { cn } from "@/lib/utils"; import MotionSearchROICanvas from "./MotionSearchROICanvas"; import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch"; @@ -452,7 +453,6 @@ export default function MotionSearchDialog({ range={searchRange} setRange={setSearchRange} defaultRange={defaultRange} - timeFormat={config.ui?.time_format} timezone={timezone} /> @@ -476,7 +476,6 @@ type SearchRangeSelectorProps = { range?: TimeRange; setRange: React.Dispatch>; defaultRange: TimeRange; - timeFormat?: "browser" | "12hour" | "24hour"; timezone?: string; }; @@ -484,7 +483,6 @@ function SearchRangeSelector({ range, setRange, defaultRange, - timeFormat, timezone, }: SearchRangeSelectorProps) { const { t } = useTranslation(["views/motionSearch", "common"]); @@ -527,15 +525,18 @@ function SearchRangeSelector({ return time; }, [range, defaultRange, timezoneOffset, localTimeOffset]); + const { data: config } = useSWR("config"); + const is24Hour = use24HourTime(config); + const formattedStart = useFormattedTimestamp( startTime, - timeFormat === "24hour" + is24Hour ? t("time.formattedTimestamp.24hour", { ns: "common" }) : t("time.formattedTimestamp.12hour", { ns: "common" }), ); const formattedEnd = useFormattedTimestamp( endTime, - timeFormat === "24hour" + is24Hour ? t("time.formattedTimestamp.24hour", { ns: "common" }) : t("time.formattedTimestamp.12hour", { ns: "common" }), ); diff --git a/web/src/views/motion-search/MotionSearchView.tsx b/web/src/views/motion-search/MotionSearchView.tsx index 122e05985..961c1065c 100644 --- a/web/src/views/motion-search/MotionSearchView.tsx +++ b/web/src/views/motion-search/MotionSearchView.tsx @@ -51,7 +51,7 @@ import { RecordingSegment, } from "@/types/record"; import { VideoResolutionType } from "@/types/live"; -import { useFormattedTimestamp } from "@/hooks/use-date-utils"; +import { useFormattedTimestamp, use24HourTime } from "@/hooks/use-date-utils"; import MotionSearchROICanvas from "./MotionSearchROICanvas"; import MotionSearchDialog from "./MotionSearchDialog"; import { IoMdArrowRoundBack } from "react-icons/io"; @@ -94,12 +94,13 @@ export default function MotionSearchView({ ]); const navigate = useNavigate(); + const is24Hour = use24HourTime(config); const resultTimestampFormat = useMemo( () => - config.ui?.time_format === "24hour" + is24Hour ? t("time.formattedTimestamp.24hour", { ns: "common" }) : t("time.formattedTimestamp.12hour", { ns: "common" }), - [config.ui?.time_format, t], + [is24Hour, t], ); // Refs diff --git a/web/src/views/search/SearchView.tsx b/web/src/views/search/SearchView.tsx index 0251c66e2..0becce1ca 100644 --- a/web/src/views/search/SearchView.tsx +++ b/web/src/views/search/SearchView.tsx @@ -31,6 +31,7 @@ import Chip from "@/components/indicators/Chip"; import { TooltipPortal } from "@radix-ui/react-tooltip"; import SearchActionGroup from "@/components/filter/SearchActionGroup"; import { Trans, useTranslation } from "react-i18next"; +import { use24HourTime } from "@/hooks/use-date-utils"; import { useNavigate } from "react-router-dom"; import { useAllowedCameras } from "@/hooks/use-allowed-cameras"; @@ -77,6 +78,7 @@ export default function SearchView({ const { data: config } = useSWR("config", { revalidateOnFocus: false, }); + const is24Hour = use24HourTime(config); const navigate = useNavigate(); const { data: exploreEvents } = useSWR( @@ -191,10 +193,7 @@ export default function SearchView({ sub_labels: allSubLabels, ...(hasCustomClassificationModels && { attributes: allAttributes }), search_type: ["thumbnail", "description"] as SearchSource[], - time_range: - config?.ui.time_format == "24hour" - ? ["00:00-23:59"] - : ["12:00AM-11:59PM"], + time_range: is24Hour ? ["00:00-23:59"] : ["12:00AM-11:59PM"], before: [formatDateToLocaleString()], after: [formatDateToLocaleString(-5)], min_score: ["50"], @@ -209,6 +208,7 @@ export default function SearchView({ }), [ config, + is24Hour, allLabels, allZones, allSubLabels, diff --git a/web/src/views/settings/TriggerView.tsx b/web/src/views/settings/TriggerView.tsx index 5d2e03b4c..359665ee3 100644 --- a/web/src/views/settings/TriggerView.tsx +++ b/web/src/views/settings/TriggerView.tsx @@ -39,6 +39,7 @@ import { Trigger, TriggerAction, TriggerType } from "@/types/trigger"; import { useSearchEffect } from "@/hooks/use-overlay-state"; import { cn } from "@/lib/utils"; import { formatUnixTimestampToDateTime } from "@/utils/dateUtil"; +import { use24HourTime } from "@/hooks/use-date-utils"; import { Link } from "react-router-dom"; import { useTriggers } from "@/api/ws"; import { useCameraFriendlyName } from "@/hooks/use-camera-friendly-name"; @@ -89,6 +90,7 @@ export default function TriggerView({ const { t } = useTranslation("views/settings"); const { data: config, mutate: updateConfig } = useSWR("config"); + const is24Hour = use24HourTime(config); const { data: trigger_status, mutate } = useSWR( config?.cameras[selectedCamera]?.semantic_search?.triggers && Object.keys(config.cameras[selectedCamera].semantic_search.triggers) @@ -581,20 +583,19 @@ export default function TriggerView({ ?.last_triggered, { timezone: config.ui.timezone, - date_format: - config.ui.time_format == "24hour" - ? t( - "time.formattedTimestamp2.24hour", - { - ns: "common", - }, - ) - : t( - "time.formattedTimestamp2.12hour", - { - ns: "common", - }, - ), + date_format: is24Hour + ? t( + "time.formattedTimestamp2.24hour", + { + ns: "common", + }, + ) + : t( + "time.formattedTimestamp2.12hour", + { + ns: "common", + }, + ), time_style: "medium", date_style: "medium", }, @@ -742,20 +743,19 @@ export default function TriggerView({ ?.last_triggered, { timezone: config.ui.timezone, - date_format: - config.ui.time_format == "24hour" - ? t( - "time.formattedTimestamp2.24hour", - { - ns: "common", - }, - ) - : t( - "time.formattedTimestamp2.12hour", - { - ns: "common", - }, - ), + date_format: is24Hour + ? t( + "time.formattedTimestamp2.24hour", + { + ns: "common", + }, + ) + : t( + "time.formattedTimestamp2.12hour", + { + ns: "common", + }, + ), time_style: "medium", date_style: "medium", }, diff --git a/web/src/views/system/StorageMetrics.tsx b/web/src/views/system/StorageMetrics.tsx index 84afe20c8..00855b948 100644 --- a/web/src/views/system/StorageMetrics.tsx +++ b/web/src/views/system/StorageMetrics.tsx @@ -10,7 +10,11 @@ import { import useSWR from "swr"; import { CiCircleAlert } from "react-icons/ci"; import { FrigateConfig } from "@/types/frigateConfig"; -import { useFormattedTimestamp, useTimezone } from "@/hooks/use-date-utils"; +import { + useFormattedTimestamp, + useTimeFormat, + useTimezone, +} from "@/hooks/use-date-utils"; import { RecordingsSummary } from "@/types/review"; import { useTranslation } from "react-i18next"; import { TZDate } from "react-day-picker"; @@ -81,7 +85,7 @@ export default function StorageMetrics({ : null; }, [recordingsSummary, timezone]); - const timeFormat = config?.ui.time_format === "24hour" ? "24hour" : "12hour"; + const timeFormat = useTimeFormat(config); const format = useMemo(() => { return t(`time.formattedTimestampMonthDayYear.${timeFormat}`, { ns: "common",