From a5aeeb3d40d48505d6bdcc152f505a3e9b5c1f21 Mon Sep 17 00:00:00 2001 From: leedj312 Date: Sat, 28 Jun 2025 15:00:43 +0900 Subject: [PATCH] =?UTF-8?q?datemonthPicker=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/ui/date_month_picker/index.tsx | 82 ++++++++++++ .../src/ui/date_month_picker/mothPicker.tsx | 125 ++++++++++++++++++ .../business_components/src/ui/index.ts | 3 +- 3 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 packages/common/business_components/src/ui/date_month_picker/index.tsx create mode 100644 packages/common/business_components/src/ui/date_month_picker/mothPicker.tsx diff --git a/packages/common/business_components/src/ui/date_month_picker/index.tsx b/packages/common/business_components/src/ui/date_month_picker/index.tsx new file mode 100644 index 000000000..d318b561f --- /dev/null +++ b/packages/common/business_components/src/ui/date_month_picker/index.tsx @@ -0,0 +1,82 @@ +'use client'; + +import { cn } from "../../lib/utils"; +import { + Popover, + PopoverTrigger, + PopoverContent, + Button, +} from "@common/components/ui"; +import { ko } from "date-fns/locale"; +import { format } from "date-fns"; +import { useState } from "react"; +import { MonthPicker } from "./mothPicker"; +import { CalendarIcon } from "lucide-react"; + +type MonthPickerProps = { + field: { value: string; onChange: (date: string) => void }; // yyyyMM 문자열 + selectClass?: string; + readOnly?: boolean; +}; + +// yyyyMM → Date 객체 +const parseYYYYMM = (value: string) => { + const year = Number(value.substring(0, 4)); + const month = Number(value.substring(4, 6)) - 1; + return new Date(year, month); +}; + +// Date 객체 → yyyyMM 문자열 +const formatDateToYYYYMM = (date: Date) => { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, "0"); + return `${year}${month}`; +}; + +export const DateMonthPicker = ({ + field, + selectClass, + readOnly = false, +}: MonthPickerProps) => { + const [open, setOpen] = useState(false); + + const parsedDate = field?.value ? parseYYYYMM(field.value) : new Date(); + + const handleSelect = ( + date: Date | undefined, + shouldClosePopover?: boolean + ) => { + if (!readOnly && date) { + field.onChange(formatDateToYYYYMM(date)); + if (shouldClosePopover) setOpen(false); + } + }; + + return ( + + + + + + + + + ); +}; diff --git a/packages/common/business_components/src/ui/date_month_picker/mothPicker.tsx b/packages/common/business_components/src/ui/date_month_picker/mothPicker.tsx new file mode 100644 index 000000000..acf17fba4 --- /dev/null +++ b/packages/common/business_components/src/ui/date_month_picker/mothPicker.tsx @@ -0,0 +1,125 @@ +import { cn } from "../../lib/utils"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@common/components/ui"; +import { + eachMonthOfInterval, + endOfYear, + format, + isEqual, + isFuture, + parse, + startOfMonth, + startOfToday, +} from "date-fns"; +import { ko } from "date-fns/locale"; + +function getStartOfCurrentMonth() { + return startOfMonth(startOfToday()); +} + +export type MonthPickerProps = { + currentMonth: Date; + onMonthChange: ( + newMonth: Date | undefined, + shouldClosePopover?: boolean + ) => void; +}; + +export function MonthPicker({ currentMonth, onMonthChange }: MonthPickerProps) { + + const today = startOfToday(); +const currentYear = today.getFullYear(); + const yearFromProp = format(currentMonth, "yyyy"); +const selectedYear = parseInt(yearFromProp); + + // 현재 월의 시작 날짜 + const fullYearStart = parse(yearFromProp, "yyyy", new Date()); + + const yearEnd = + selectedYear === currentYear ? today : endOfYear(fullYearStart); + + + + + const yearRange = Array.from({ length: 21 }, (_, i) => currentYear - 20 + i); + const months = eachMonthOfInterval({ start: fullYearStart, end: yearEnd }); + + const getMonthButtonClass = (month: Date) => + cn( + "inline-flex h-9 w-16 items-center justify-center rounded-md p-0 text-sm font-normal ring-offset-white transition-colors hover:bg-slate-100 hover:text-slate-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-400 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 aria-selected:opacity-100 dark:ring-offset-slate-950 dark:hover:bg-slate-800 dark:hover:text-slate-50 dark:focus-visible:ring-slate-800", + isEqual(month, currentMonth) && + "bg-slate-900 text-slate-50 hover:bg-slate-900 hover:text-slate-50 focus:bg-slate-900 focus:text-slate-50 dark:bg-slate-50 dark:text-slate-900 dark:hover:bg-slate-50 dark:hover:text-slate-900 dark:focus:bg-slate-50 dark:focus:text-slate-900", + !isEqual(month, currentMonth) && + isEqual(month, getStartOfCurrentMonth()) && + "bg-slate-100 text-slate-900 dark:bg-slate-800 dark:text-slate-50" + ); + + return ( +
+
+
+ {/* 년도 셀렉트 */} +
+ +
+ + {/* 월 그리드 */} +
+ {months.map((month) => ( +
+ +
+ ))} +
+
+
+
+ ); +} diff --git a/packages/common/business_components/src/ui/index.ts b/packages/common/business_components/src/ui/index.ts index 19d966a53..fd1e6fe9e 100644 --- a/packages/common/business_components/src/ui/index.ts +++ b/packages/common/business_components/src/ui/index.ts @@ -20,4 +20,5 @@ export * from "./table"; export * from "./table_div"; export * from "./title"; export * from "./sdmInsertForm"; -export * from "./popover_ui" \ No newline at end of file +export * from "./popover_ui"; +export * from "./date_month_picker"; \ No newline at end of file