From b64a62dc473c8b4741c23c1692d2a9ab06845235 Mon Sep 17 00:00:00 2001 From: JOYCEQL <1449239013@qq.com> Date: Thu, 12 Feb 2026 14:01:21 +0800 Subject: [PATCH] fix: update date format --- src/components/ui/unified-date-input.tsx | 63 +++--- .../ui/unified-date-range-input.tsx | 194 +++++++++--------- src/lib/utils.ts | 2 +- 3 files changed, 134 insertions(+), 125 deletions(-) diff --git a/src/components/ui/unified-date-input.tsx b/src/components/ui/unified-date-input.tsx index ebbc861..aa3aab7 100644 --- a/src/components/ui/unified-date-input.tsx +++ b/src/components/ui/unified-date-input.tsx @@ -1,9 +1,9 @@ "use client"; import { DateInput } from "@heroui/date-input"; +import { HeroUIProvider } from "@heroui/react"; import { CalendarDate, parseDate } from "@internationalized/date"; -import { useMemo } from "react"; -import { useTranslations } from "next-intl"; +import { useState } from "react"; interface UnifiedDateInputProps { value: string; @@ -18,52 +18,53 @@ export function UnifiedDateInput({ value, onChange, label, - placeholder, isRequired, className, }: UnifiedDateInputProps) { - const t = useTranslations("common"); - - // Parse string "YYYY-MM" to CalendarDate - // We use useMemo to calculate the initial value only once or when key changes - // actually, we want to respect value updates if they are completely different (e.g. item switch) - // but ignore them if they are just caused by our own null update. - // Easiest robust way for now: Uncontrolled with defaultValue. - // Assuming the component is remounted when switching items (verified by AnimatePresence in parents). - const initialDate = useMemo(() => { - if (!value) return null; + const parseValue = (input: string): CalendarDate | null => { + if (!input) return null; try { - const dateStr = value.length === 7 ? `${value}-01` : value; - return parseDate(dateStr); - } catch (e) { + let normalized = input.replace(/[./]/g, "-"); + if (normalized.length === 7) normalized = `${normalized}-01`; + return parseDate(normalized); + } catch { return null; } - }, []); // Empty dependency array to treat as mount-time config + }; + + const [selectedDate, setSelectedDate] = useState(() => + parseValue(value) + ); const handleDateChange = (date: CalendarDate | null) => { + setSelectedDate(date); if (!date) { onChange(""); return; } const month = date.month.toString().padStart(2, "0"); - onChange(`${date.year}-${month}`); + onChange(`${date.year}/${month}`); }; return (
- + + +
); } diff --git a/src/components/ui/unified-date-range-input.tsx b/src/components/ui/unified-date-range-input.tsx index ba0e69b..2455d42 100644 --- a/src/components/ui/unified-date-range-input.tsx +++ b/src/components/ui/unified-date-range-input.tsx @@ -1,9 +1,9 @@ "use client"; import { DateInput } from "@heroui/date-input"; +import { HeroUIProvider } from "@heroui/react"; import { CalendarDate, parseDate } from "@internationalized/date"; -import { useMemo, useState, useEffect } from "react"; -import { useTranslations } from "next-intl"; +import { useState } from "react"; import { cn } from "@/lib/utils"; interface UnifiedDateRangeInputProps { @@ -14,120 +14,128 @@ interface UnifiedDateRangeInputProps { className?: string; } +const SEPARATOR = " - "; +const PRESENT_VALUES = new Set(["至今", "Present", "Now"]); + +const parsePart = (part: string): CalendarDate | null => { + if (!part) return null; + const cleanPart = part.trim(); + if (PRESENT_VALUES.has(cleanPart)) return null; + + try { + let isoStr = cleanPart.replace(/[./]/g, "-"); + if (isoStr.length === 7) isoStr += "-01"; + if (isoStr.length === 4) isoStr += "-01-01"; + return parseDate(isoStr); + } catch { + return null; + } +}; + +const parseRange = (rangeValue: string) => { + if (!rangeValue) return { start: null, end: null }; + + let startStr = ""; + let endStr = ""; + + if (rangeValue.includes(SEPARATOR)) { + [startStr, endStr] = rangeValue.split(SEPARATOR); + } else { + const match = rangeValue.match(/^([^\s]+)\s*(?:-|–|—)\s*([^\s]+)$/); + if (match) { + startStr = match[1]; + endStr = match[2]; + } else { + startStr = rangeValue; + } + } + + return { start: parsePart(startStr), end: parsePart(endStr) }; +}; + export function UnifiedDateRangeInput({ value, onChange, - label, className, }: UnifiedDateRangeInputProps) { - // Parsing logic - // Expected formats: "YYYY.MM - YYYY.MM" - const t = useTranslations("common"); - - // Helper to normalize separator - const SEPARATOR = " - "; - - // Helper to parse a single date string part - const parsePart = (part: string): CalendarDate | null => { - if (!part) return null; - const cleanPart = part.trim(); - // Legacy support: if value has "Present", treat as null/empty for end date input? - // Or just let it fail parse and show empty. - if (["至今", "Present", "Now"].includes(cleanPart)) return null; - - try { - // Replace dots with dashes for standard parsing - let isoStr = cleanPart.replace(/\./g, "-"); - if (isoStr.length === 7) isoStr += "-01"; // YYYY-MM -> YYYY-MM-DD - if (isoStr.length === 4) isoStr += "-01-01"; // YYYY -> YYYY-MM-DD - return parseDate(isoStr); - } catch (e) { - return null; - } - }; - - // Deconstruct value into state - const { start, end } = useMemo(() => { - if (!value) return { start: null, end: null }; - - // ... logic ... - const parts = value.split("-").map(s => s.trim()); - let startStr = "", endStr = ""; - - if (value.includes(" - ")) { - [startStr, endStr] = value.split(" - "); - } else { - const match = value.match(/^([^\s]+)\s*(?:-|–|—)\s*([^\s]+)$/); - if (match) { - startStr = match[1]; - endStr = match[2]; - } else { - startStr = value; - } - } - - const start = parsePart(startStr); - // If endStr was "Present", parsePart returns null, which is what we want (empty input) - const end = parsePart(endStr); - - return { start, end }; - }, []); // Empty dependency array for uncontrolled initialization + const [range, setRange] = useState<{ start: CalendarDate | null; end: CalendarDate | null }>( + () => parseRange(value) + ); const updateValue = (newStart: CalendarDate | null, newEnd: CalendarDate | null) => { - // Format: YYYY.MM - const format = (d: CalendarDate) => `${d.year}.${d.month.toString().padStart(2, "0")}`; - + const format = (d: CalendarDate) => + `${d.year}/${d.month.toString().padStart(2, "0")}`; + const startStr = newStart ? format(newStart) : ""; const endStr = newEnd ? format(newEnd) : ""; if (!startStr && !endStr) { - onChange(""); - return; + onChange(""); + return; } if (startStr && !endStr) { - // If end is empty, maybe keep separator? "YYYY.MM - " - // Or just "YYYY.MM"? Context usually implies " - Present" if missing? - // But user removed "Present" option. - // Let's keep separator " - " to denote range started. - onChange(`${startStr}${SEPARATOR}`); - return; + onChange(`${startStr}${SEPARATOR}`); + return; } onChange(`${startStr}${SEPARATOR}${endStr}`); }; + const handleStartChange = (newStart: CalendarDate | null) => { + setRange((prev) => { + const next = { start: newStart, end: prev.end }; + updateValue(next.start, next.end); + return next; + }); + }; + + const handleEndChange = (newEnd: CalendarDate | null) => { + setRange((prev) => { + const next = { start: prev.start, end: newEnd }; + updateValue(next.start, next.end); + return next; + }); + }; + return (
+
-
- updateValue(d, end)} - variant="bordered" - granularity="month" - aria-label="Start Date" - classNames={{ - inputWrapper: "bg-background hover:bg-muted/20 h-9 min-h-0 py-0 px-3 shadow-sm ring-1 ring-inset ring-input border-0", - innerWrapper: "pb-0", - }} - /> -
- - -
- updateValue(start, d)} - variant="bordered" - granularity="month" - aria-label="End Date" - classNames={{ - inputWrapper: "bg-background hover:bg-muted/20 h-9 min-h-0 py-0 px-3 shadow-sm ring-1 ring-inset ring-input border-0", - innerWrapper: "pb-0", - }} - /> -
+
+ +
+ - +
+ +
+
); + } diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 84f8c3f..2012016 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -46,7 +46,7 @@ export function formatDateString(dateStr: string | undefined, locale: string = " try { if (locale === "zh" || locale === "zh-CN") { - return `${date.getUTCFullYear()}.${String(date.getUTCMonth() + 1).padStart(2, "0")}`; + return `${date.getUTCFullYear()}/${String(date.getUTCMonth() + 1).padStart(2, "0")}`; } const formatter = new Intl.DateTimeFormat(locale, { year: 'numeric',