diff --git a/src/components/quality/InspectionManagement/InspectionDetail.tsx b/src/components/quality/InspectionManagement/InspectionDetail.tsx
index 4190874e..61b37ccf 100644
--- a/src/components/quality/InspectionManagement/InspectionDetail.tsx
+++ b/src/components/quality/InspectionManagement/InspectionDetail.tsx
@@ -27,6 +27,7 @@ import { Badge } from '@/components/ui/badge';
import { getPresetStyle } from '@/lib/utils/status-config';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
+import { DatePicker } from '@/components/ui/date-picker';
import { Label } from '@/components/ui/label';
import {
Table,
@@ -932,26 +933,23 @@ export function InspectionDetail({ id }: InspectionDetailProps) {
검사방문요청일
- updateNested('scheduleInfo', 'visitRequestDate', e.target.value)}
+ onChange={(date) => updateNested('scheduleInfo', 'visitRequestDate', date)}
/>
검사시작일
- updateNested('scheduleInfo', 'startDate', e.target.value)}
+ onChange={(date) => updateNested('scheduleInfo', 'startDate', date)}
/>
검사종료일
- updateNested('scheduleInfo', 'endDate', e.target.value)}
+ onChange={(date) => updateNested('scheduleInfo', 'endDate', date)}
/>
diff --git a/src/components/quotes/QuoteRegistration.tsx b/src/components/quotes/QuoteRegistration.tsx
index 16ed2b7b..11ab70cf 100644
--- a/src/components/quotes/QuoteRegistration.tsx
+++ b/src/components/quotes/QuoteRegistration.tsx
@@ -10,6 +10,7 @@
import { useState, useEffect, useMemo, useCallback } from "react";
import { Input } from "../ui/input";
+import { DatePicker } from "../ui/date-picker";
import { Textarea } from "../ui/textarea";
import { QuantityInput } from "../ui/quantity-input";
import { CurrencyInput } from "../ui/currency-input";
@@ -671,12 +672,9 @@ export function QuoteRegistration({
>
-
@@ -752,11 +750,9 @@ export function QuoteRegistration({
- handleFieldChange("dueDate", e.target.value)}
+ onChange={(date) => handleFieldChange("dueDate", date)}
/>
diff --git a/src/components/quotes/QuoteRegistrationV2.tsx b/src/components/quotes/QuoteRegistrationV2.tsx
index 833ec07c..3d8e145b 100644
--- a/src/components/quotes/QuoteRegistrationV2.tsx
+++ b/src/components/quotes/QuoteRegistrationV2.tsx
@@ -17,6 +17,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "../ui/card";
import { Button } from "../ui/button";
import { Badge } from "../ui/badge";
import { Input } from "../ui/input";
+import { DatePicker } from "../ui/date-picker";
import { Textarea } from "../ui/textarea";
import { PhoneInput } from "../ui/phone-input";
import {
@@ -754,10 +755,9 @@ export function QuoteRegistrationV2({
접수일
- handleFieldChange("registrationDate", e.target.value)}
+ onChange={(date) => handleFieldChange("registrationDate", date)}
disabled={isViewMode}
/>
diff --git a/src/components/settings/PopupManagement/PopupForm.tsx b/src/components/settings/PopupManagement/PopupForm.tsx
index 4224e431..c7f1d541 100644
--- a/src/components/settings/PopupManagement/PopupForm.tsx
+++ b/src/components/settings/PopupManagement/PopupForm.tsx
@@ -17,6 +17,7 @@ import { PageLayout } from '@/components/organisms/PageLayout';
import { PageHeader } from '@/components/organisms/PageHeader';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
+import { DatePicker } from '@/components/ui/date-picker';
import { Label } from '@/components/ui/label';
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
import {
@@ -189,17 +190,15 @@ export function PopupForm({ mode, initialData }: PopupFormProps) {
기간
*
- setStartDate(e.target.value)}
+ onChange={(date) => setStartDate(date)}
className={errors.startDate ? 'border-red-500' : ''}
/>
~
- setEndDate(e.target.value)}
+ onChange={(date) => setEndDate(date)}
className={errors.endDate ? 'border-red-500' : ''}
/>
diff --git a/src/components/templates/IntegratedDetailTemplate/FieldInput.tsx b/src/components/templates/IntegratedDetailTemplate/FieldInput.tsx
index 103bbc77..6b537427 100644
--- a/src/components/templates/IntegratedDetailTemplate/FieldInput.tsx
+++ b/src/components/templates/IntegratedDetailTemplate/FieldInput.tsx
@@ -27,6 +27,7 @@ import { AccountNumberInput } from '@/components/ui/account-number-input';
import { CurrencyInput } from '@/components/ui/currency-input';
import { QuantityInput } from '@/components/ui/quantity-input';
import { NumberInput } from '@/components/ui/number-input';
+import { DatePicker } from '@/components/ui/date-picker';
import { cn } from '@/lib/utils';
import { formatPhoneNumber, formatBusinessNumber, formatCardNumber, formatAccountNumber, formatNumber } from '@/lib/formatters';
import type { FieldDefinition, DetailMode, FieldOption } from './types';
@@ -291,11 +292,9 @@ function renderFormField(
case 'date':
return (
-
onChange(e.target.value)}
+ onChange={(date) => onChange(date)}
disabled={disabled}
className={cn(hasError && 'border-destructive')}
/>
diff --git a/src/components/templates/IntegratedDetailTemplate/FieldRenderer.tsx b/src/components/templates/IntegratedDetailTemplate/FieldRenderer.tsx
index b86ea9e8..420103a3 100644
--- a/src/components/templates/IntegratedDetailTemplate/FieldRenderer.tsx
+++ b/src/components/templates/IntegratedDetailTemplate/FieldRenderer.tsx
@@ -21,6 +21,7 @@ import { BusinessNumberInput } from '@/components/ui/business-number-input';
import { PersonalNumberInput } from '@/components/ui/personal-number-input';
import { CurrencyInput } from '@/components/ui/currency-input';
import { QuantityInput } from '@/components/ui/quantity-input';
+import { DatePicker } from '@/components/ui/date-picker';
import { cn } from '@/lib/utils';
import { formatPhoneNumber, formatBusinessNumber, formatNumber } from '@/lib/formatters';
import type { FieldDefinition, DetailMode, FieldOption } from './types';
@@ -297,11 +298,9 @@ function renderFormField(
case 'date':
return (
-
onChange(e.target.value)}
+ onChange={(date) => onChange(date)}
disabled={disabled}
className={cn(error && 'border-red-500')}
/>
diff --git a/src/components/ui/calendar.tsx b/src/components/ui/calendar.tsx
index 870e2953..d0b8aa48 100644
--- a/src/components/ui/calendar.tsx
+++ b/src/components/ui/calendar.tsx
@@ -5,9 +5,10 @@ import { ChevronLeft, ChevronRight } from "lucide-react";
import { DayPicker } from "react-day-picker";
import { format, getDay } from "date-fns";
+import { ko } from "date-fns/locale";
import { cn } from "./utils";
import { buttonVariants } from "./button";
-import { isHoliday } from "@/constants/calendarEvents";
+import { isHoliday, isTaxDeadline } from "@/constants/calendarEvents";
// 토요일 체크 함수 (react-day-picker modifier용)
const saturdayMatcher = (date: Date) => {
@@ -27,6 +28,12 @@ const holidayMatcher = (date: Date) => {
return isHoliday(dateStr);
};
+// 세금신고일 체크 함수 (react-day-picker modifier용)
+const taxDeadlineMatcher = (date: Date) => {
+ const dateStr = format(date, "yyyy-MM-dd");
+ return isTaxDeadline(dateStr);
+};
+
function Calendar({
className,
classNames,
@@ -54,6 +61,7 @@ function Calendar({
saturday: saturdayMatcher,
sunday: sundayMatcher,
holiday: holidayMatcher,
+ taxDeadline: taxDeadlineMatcher,
...modifiers,
};
@@ -61,6 +69,7 @@ function Calendar({
saturday: "text-blue-500 font-semibold",
sunday: "text-red-500 font-semibold",
holiday: "text-red-500 font-semibold",
+ taxDeadline: "text-green-600 font-semibold",
...modifiersClassNames,
};
@@ -119,6 +128,7 @@ function Calendar({
return
;
},
}}
+ locale={ko}
{...props}
/>
);
diff --git a/src/components/ui/date-picker.tsx b/src/components/ui/date-picker.tsx
new file mode 100644
index 00000000..7616cd30
--- /dev/null
+++ b/src/components/ui/date-picker.tsx
@@ -0,0 +1,278 @@
+"use client";
+
+import * as React from "react";
+import { format, parse, isValid } from "date-fns";
+import { ko } from "date-fns/locale";
+import { CalendarIcon, ChevronLeft, ChevronRight } from "lucide-react";
+
+import { cn } from "./utils";
+import { Button } from "./button";
+import { Calendar } from "./calendar";
+import { Popover, PopoverContent, PopoverTrigger } from "./popover";
+
+interface DatePickerProps {
+ /** 선택된 날짜 (YYYY-MM-DD 형식 문자열) */
+ value?: string;
+ /** 날짜 변경 핸들러 */
+ onChange?: (date: string) => void;
+ /** 플레이스홀더 텍스트 */
+ placeholder?: string;
+ /** 비활성화 여부 */
+ disabled?: boolean;
+ /** 추가 className */
+ className?: string;
+ /** 트리거 버튼 크기 */
+ size?: "default" | "sm" | "lg";
+ /** 날짜 표시 형식 (date-fns format) */
+ displayFormat?: string;
+ /** 최소 선택 가능 날짜 */
+ minDate?: Date;
+ /** 최대 선택 가능 날짜 */
+ maxDate?: Date;
+ /** 팝오버 정렬 (start, center, end) */
+ align?: "start" | "center" | "end";
+ /** 팝오버 위치 (top, right, bottom, left) */
+ side?: "top" | "right" | "bottom" | "left";
+ /** 팝오버 사이드 오프셋 */
+ sideOffset?: number;
+}
+
+const MONTH_LABELS = [
+ "1월", "2월", "3월", "4월", "5월", "6월",
+ "7월", "8월", "9월", "10월", "11월", "12월",
+];
+
+/**
+ * DatePicker 컴포넌트
+ *
+ * 공휴일/주말 색상 표시가 포함된 커스텀 날짜 선택기
+ * 기존 input[type="date"]를 대체하여 사용
+ *
+ * @example
+ * // 기본 사용
+ *
+ *
+ * // 형식 지정
+ *
+ */
+function DatePicker({
+ value,
+ onChange,
+ placeholder = "날짜 선택",
+ disabled = false,
+ className,
+ size = "default",
+ displayFormat = "yyyy-MM-dd",
+ minDate,
+ maxDate,
+ align = "center",
+ side = "bottom",
+ sideOffset = 8,
+}: DatePickerProps) {
+ const [open, setOpen] = React.useState(false);
+ const [displayMonth, setDisplayMonth] = React.useState
();
+ const [showMonthPicker, setShowMonthPicker] = React.useState(false);
+ const [pickerYear, setPickerYear] = React.useState(new Date().getFullYear());
+
+ // 문자열 → Date 변환
+ const selectedDate = React.useMemo(() => {
+ if (!value) return undefined;
+ const parsed = parse(value, "yyyy-MM-dd", new Date());
+ return isValid(parsed) ? parsed : undefined;
+ }, [value]);
+
+ // 팝오버 열릴 때 리셋
+ React.useEffect(() => {
+ if (open) {
+ const date = selectedDate ?? new Date();
+ setDisplayMonth(date);
+ setPickerYear(date.getFullYear());
+ setShowMonthPicker(false);
+ }
+ }, [open, selectedDate]);
+
+ // 날짜 선택 핸들러
+ const handleSelect = React.useCallback(
+ (date: Date | undefined) => {
+ if (date && onChange) {
+ onChange(format(date, "yyyy-MM-dd"));
+ }
+ setOpen(false);
+ },
+ [onChange]
+ );
+
+ // 연월 피커에서 월 선택
+ const handleMonthSelect = React.useCallback(
+ (month: number) => {
+ setDisplayMonth(new Date(pickerYear, month, 1));
+ setShowMonthPicker(false);
+ },
+ [pickerYear]
+ );
+
+ // 연월 텍스트 클릭 → 연월 피커 열기
+ const handleCaptionClick = React.useCallback(() => {
+ setPickerYear(displayMonth?.getFullYear() ?? new Date().getFullYear());
+ setShowMonthPicker(true);
+ }, [displayMonth]);
+
+ // 오늘로 이동
+ const handleGoToToday = React.useCallback(() => {
+ const today = new Date();
+ setDisplayMonth(today);
+ setShowMonthPicker(false);
+ }, []);
+
+ // 표시 텍스트
+ const displayText = React.useMemo(() => {
+ if (!selectedDate) return placeholder;
+ return format(selectedDate, displayFormat, { locale: ko });
+ }, [selectedDate, displayFormat, placeholder]);
+
+ // 버튼 크기 스타일
+ const sizeClasses = {
+ default: "h-10 px-3",
+ sm: "h-8 px-2 text-sm",
+ lg: "h-12 px-4 text-base",
+ };
+
+ return (
+
+
+
+
+ {displayText}
+
+
+
+
+ {showMonthPicker ? (
+ /* 연월 선택 피커 */
+
+ {/* 연도 네비게이션 */}
+
+ setPickerYear((y) => y - 1)}
+ className="h-7 w-7 flex items-center justify-center rounded-md hover:bg-accent transition-colors"
+ >
+
+
+ {pickerYear}년
+ setPickerYear((y) => y + 1)}
+ className="h-7 w-7 flex items-center justify-center rounded-md hover:bg-accent transition-colors"
+ >
+
+
+
+ {/* 월 그리드 (4행 3열) */}
+
+ {MONTH_LABELS.map((label, i) => {
+ const isActive =
+ displayMonth?.getMonth() === i &&
+ displayMonth?.getFullYear() === pickerYear;
+ return (
+ handleMonthSelect(i)}
+ className={cn(
+ "h-9 rounded-lg text-sm font-medium transition-colors",
+ "hover:bg-accent hover:text-accent-foreground",
+ isActive &&
+ "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground"
+ )}
+ >
+ {label}
+
+ );
+ })}
+
+ {/* 오늘 버튼 */}
+
+ 오늘
+
+
+ ) : (
+ /* 달력 뷰 */
+
+ {/* 연월 텍스트 클릭 영역 (화살표 사이) */}
+
+ {
+ if (minDate && date < minDate) return true;
+ if (maxDate && date > maxDate) return true;
+ return false;
+ }}
+ month={displayMonth}
+ onMonthChange={setDisplayMonth}
+ classNames={{
+ months: "flex flex-col relative",
+ month: "relative",
+ month_caption: "flex justify-center items-center h-10",
+ caption_label: "text-sm font-medium",
+ nav: "absolute top-0 left-1 right-1 h-10 flex items-center justify-between z-10",
+ button_previous: cn(
+ "h-7 w-7 bg-transparent p-0 hover:bg-accent hover:text-accent-foreground rounded-md flex items-center justify-center"
+ ),
+ button_next: cn(
+ "h-7 w-7 bg-transparent p-0 hover:bg-accent hover:text-accent-foreground rounded-md flex items-center justify-center"
+ ),
+ month_grid: "w-full border-collapse space-y-1",
+ weekdays: "flex",
+ weekday:
+ "text-muted-foreground rounded-md w-9 font-normal text-[0.8rem] text-center",
+ week: "flex w-full mt-2",
+ day: "h-9 w-9 text-center text-sm p-0 relative",
+ day_button: cn(
+ "h-9 w-9 p-0 font-normal rounded-md hover:bg-accent hover:text-accent-foreground flex items-center justify-center"
+ ),
+ selected:
+ "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground rounded-lg",
+ today: "bg-orange-100 text-orange-600 font-bold rounded-lg",
+ outside: "text-muted-foreground/40",
+ disabled: "opacity-50 cursor-not-allowed",
+ hidden: "invisible",
+ }}
+ />
+ {/* 오늘 버튼 */}
+
+ 오늘
+
+
+ )}
+
+
+
+ );
+}
+
+export { DatePicker };
+export type { DatePickerProps };
diff --git a/src/constants/calendarEvents.ts b/src/constants/calendarEvents.ts
index 47634e17..53191dc6 100644
--- a/src/constants/calendarEvents.ts
+++ b/src/constants/calendarEvents.ts
@@ -25,6 +25,7 @@ export const HOLIDAYS_2026: CalendarEvent[] = [
{ date: '2026-05-05', name: '어린이날', type: 'holiday' },
{ date: '2026-05-24', name: '부처님오신날', type: 'holiday' },
{ date: '2026-06-06', name: '현충일', type: 'holiday' },
+ { date: '2026-07-17', name: '제헌절', type: 'holiday' },
{ date: '2026-08-15', name: '광복절', type: 'holiday' },
{ date: '2026-08-17', name: '대체공휴일(광복절)', type: 'holiday' }, // 8/15가 토요일
{ date: '2026-09-24', name: '추석 연휴', type: 'holiday' },
diff --git a/src/types/process.ts b/src/types/process.ts
index 963d54dd..f6acfdab 100644
--- a/src/types/process.ts
+++ b/src/types/process.ts
@@ -76,6 +76,9 @@ export interface Process {
// 생산일자 사용여부 (신규 필드 - 백엔드 미준비)
useProductionDate?: boolean;
+ // 구분 (신규 필드 - 공정명에 따라 옵션 변경)
+ processCategory?: string;
+
// 단계 목록 (신규 필드 - 백엔드 미준비)
steps?: ProcessStep[];
@@ -130,6 +133,21 @@ export const PROCESS_TYPE_OPTIONS: { value: ProcessType; label: string }[] = [
{ value: '조립', label: '조립' },
];
+// 공정명별 구분(카테고리) 옵션 매핑
+export const PROCESS_CATEGORY_OPTIONS: Record = {
+ '스크린': [
+ { value: '없음', label: '없음' },
+ ],
+ '슬릿': [
+ { value: '슬릿', label: '슬릿' },
+ { value: '조인트바', label: '조인트바' },
+ ],
+ '절곡': [
+ { value: '철판', label: '철판' },
+ { value: '제곡풍', label: '제곡풍' },
+ ],
+};
+
// ============================================================================
// 공정 단계 (Process Step) 타입 정의
// ============================================================================