diff --git a/claudedocs/[IMPL-2026-02-06] datepicker-migration-checklist.md b/claudedocs/[IMPL-2026-02-06] datepicker-migration-checklist.md new file mode 100644 index 00000000..d118a260 --- /dev/null +++ b/claudedocs/[IMPL-2026-02-06] datepicker-migration-checklist.md @@ -0,0 +1,106 @@ +# DatePicker 전체 교체 계획서 + +> `input type="date"` → `DatePicker` 컴포넌트 교체 +> 작성일: 2026-02-06 + +## 개요 +- 기존 브라우저 기본 `input type="date"`를 공통 `DatePicker` 컴포넌트로 교체 +- 공휴일/토요일/일요일 색상, 연월 직접 선택, 오늘 버튼 등 통일된 UX 제공 + +## 교체 패턴 +```tsx +// Before + setValue(e.target.value)} /> + +// After +import { DatePicker } from '@/components/ui/date-picker'; + +``` + +--- + +## Phase 1: 공통 템플릿 (우선 - 파급 효과 큼) ✅ 완료 +- [x] `molecules/DateRangeSelector.tsx` (4곳) +- [x] `molecules/FormField.tsx` (1곳) +- [x] `templates/IntegratedDetailTemplate/FieldRenderer.tsx` (1곳) +- [x] `templates/IntegratedDetailTemplate/FieldInput.tsx` (1곳) +- [x] `items/DynamicItemForm/fields/DateField.tsx` (1곳) + +## Phase 2: 회계 ✅ 완료 +- [x] `accounting/DepositManagement/DepositDetail.tsx` (1곳) +- [x] `accounting/WithdrawalManagement/WithdrawalDetail.tsx` (1곳) +- [x] `accounting/BadDebtCollection/BadDebtDetail.tsx` (2곳) +- [x] `accounting/DailyReport/index.tsx` (1곳) +- [x] `accounting/PurchaseManagement/PurchaseDetailModal.tsx` (1곳) +- [x] `accounting/PurchaseManagement/PurchaseDetail.tsx` (1곳) +- [x] `accounting/BillManagement/BillDetailV2.tsx` (3곳) +- [x] `accounting/BillManagement/BillDetail.tsx` (3곳) +- [x] `accounting/SalesManagement/SalesDetail.tsx` (1곳) + +## Phase 3: 건설/현장 ✅ 완료 +- [x] `construction/bidding/BiddingDetailForm.tsx` (5곳) +- [x] `construction/site-briefings/SiteBriefingForm.tsx` (2곳) +- [x] `construction/issue-management/IssueDetailForm.tsx` (2곳) +- [x] `construction/structure-review/StructureReviewDetailForm.tsx` (2곳) +- [x] `construction/contract/ContractDetailForm.tsx` (3곳) +- [x] `construction/management/ConstructionDetailClient.tsx` (1곳) +- [x] `construction/management/ProjectEndDialog.tsx` (2곳) +- [x] `construction/estimates/sections/EstimateInfoSection.tsx` (3곳) +- [x] `construction/order-management/cards/ConstructionDetailCard.tsx` (2곳) +- [x] `construction/order-management/tables/OrderDetailItemTable.tsx` (5곳) +- [x] `construction/handover-report/HandoverReportDetailForm.tsx` (3곳) + +## Phase 4: 주문/견적/단가 ✅ 완료 +- [x] `quotes/QuoteRegistrationV2.tsx` (1곳) +- [x] `quotes/QuoteRegistration.tsx` (2곳) +- [x] `orders/OrderSalesDetailEdit.tsx` (2곳) +- [x] `orders/OrderRegistration.tsx` (2곳) +- [x] `pricing/PricingFormClient.tsx` (2곳) +- [x] `pricing-distribution/PriceDistributionDetail.tsx` (1곳) + +## Phase 5: 인사 ✅ 완료 +- [x] `hr/VacationManagement/VacationRequestDialog.tsx` (2곳) +- [x] `hr/VacationManagement/VacationGrantDialog.tsx` (1곳) +- [x] `hr/EmployeeManagement/EmployeeDialog.tsx` (1곳) +- [x] `hr/EmployeeManagement/EmployeeForm.tsx` (2곳) + +## Phase 6: 품질 ✅ 완료 +- [x] `quality/InspectionManagement/InspectionCreate.tsx` (3곳) +- [x] `quality/InspectionManagement/InspectionDetail.tsx` (3곳) + +## Phase 7: 생산/자재/출고 ✅ 완료 +- [x] `production/WorkOrders/WorkOrderCreate.tsx` (1곳) +- [x] `production/WorkOrders/WorkOrderEdit.tsx` (1곳) +- [x] `production/WorkerScreen/index.tsx` (1곳) +- [x] `material/ReceivingManagement/InspectionCreate.tsx` (1곳) +- [x] `material/ReceivingManagement/ReceivingDetail.tsx` (2곳) +- [x] `outbound/ShipmentManagement/ShipmentCreate.tsx` (2곳) +- [x] `outbound/ShipmentManagement/ShipmentEdit.tsx` (2곳) + +## Phase 8: 결재/설정/기타 ✅ 완료 +- [x] `approval/DocumentCreate/ProposalForm.tsx` (1곳) +- [x] `approval/DocumentCreate/ExpenseReportForm.tsx` (2곳) +- [x] `settings/PopupManagement/PopupForm.tsx` (2곳) +- [x] `items/ItemForm/forms/ProductForm.tsx` (2곳) +- [x] `app/sales/order-management-sales/[id]/edit/page.tsx` (2곳) + +## 제외 대상 +- ~~`hr/documents/page.tsx`~~ - 메뉴 미연결 페이지 +- ~~`hr/documents/new/page.tsx`~~ - 메뉴 미연결 페이지 +- ~~`CEODashboard/TodayIssueSection.tsx`~~ - 이미 적용 완료 +- ~~`CEODashboard/ScheduleDetailModal.tsx`~~ - 이미 적용 완료 + +--- + +## 진행 상황 +| Phase | 파일 수 | 완료 | 상태 | +|-------|---------|------|------| +| 1. 공통 템플릿 | 5 | 5 | ✅ 완료 | +| 2. 회계 | 9 | 9 | ✅ 완료 | +| 3. 건설/현장 | 11 | 11 | ✅ 완료 | +| 4. 주문/견적/단가 | 6 | 6 | ✅ 완료 | +| 5. 인사 | 4 | 4 | ✅ 완료 | +| 6. 품질 | 2 | 2 | ✅ 완료 | +| 7. 생산/자재/출고 | 7 | 7 | ✅ 완료 | +| 8. 결재/설정/기타 | 5 | 5 | ✅ 완료 | +| **합계** | **49** | **49** | ✅ 전체 완료 | diff --git a/src/app/[locale]/(protected)/sales/order-management-sales/[id]/edit/page.tsx b/src/app/[locale]/(protected)/sales/order-management-sales/[id]/edit/page.tsx index 500c814b..cd11ea39 100644 --- a/src/app/[locale]/(protected)/sales/order-management-sales/[id]/edit/page.tsx +++ b/src/app/[locale]/(protected)/sales/order-management-sales/[id]/edit/page.tsx @@ -12,6 +12,7 @@ import { useState, useEffect, useCallback } from "react"; import { useRouter, useParams } from "next/navigation"; import { Input } from "@/components/ui/input"; +import { DatePicker } from "@/components/ui/date-picker"; import { Textarea } from "@/components/ui/textarea"; import { PhoneInput } from "@/components/ui/phone-input"; import { Checkbox } from "@/components/ui/checkbox"; @@ -322,11 +323,10 @@ export default function OrderEditPage() {
- - setForm({ ...form, expectedShipDate: e.target.value }) + onChange={(date) => + setForm({ ...form, expectedShipDate: date }) } disabled={form.expectedShipDateUndecided} className="flex-1" @@ -358,11 +358,10 @@ export default function OrderEditPage() { - - setForm({ ...form, deliveryRequestDate: e.target.value }) + onChange={(date) => + setForm({ ...form, deliveryRequestDate: date }) } />
diff --git a/src/app/[locale]/(protected)/sales/order-management-sales/[id]/page.tsx b/src/app/[locale]/(protected)/sales/order-management-sales/[id]/page.tsx index ad299191..431b7842 100644 --- a/src/app/[locale]/(protected)/sales/order-management-sales/[id]/page.tsx +++ b/src/app/[locale]/(protected)/sales/order-management-sales/[id]/page.tsx @@ -75,7 +75,6 @@ import { } from "@/components/orders"; import { sendSalesOrderNotification } from "@/lib/actions/fcm"; import { OrderSalesDetailEdit } from "@/components/orders/OrderSalesDetailEdit"; -import { getDepartmentTree, type DepartmentRecord } from "@/components/hr/DepartmentManagement/actions"; /** * 수량 포맷 함수 @@ -159,8 +158,6 @@ export default function OrderDetailPage() { const [isProductionDialogOpen, setIsProductionDialogOpen] = useState(false); const [isCreatingProduction, setIsCreatingProduction] = useState(false); const [productionPriority, setProductionPriority] = useState<"normal" | "high" | "urgent">("normal"); - const [productionDepartmentId, setProductionDepartmentId] = useState(""); - const [departments, setDepartments] = useState([]); const [productionMemo, setProductionMemo] = useState(""); // 생산지시 완료 알림 모달 상태 const [isProductionSuccessDialogOpen, setIsProductionSuccessDialogOpen] = useState(false); @@ -208,45 +205,11 @@ export default function OrderDetailPage() { router.push(`/sales/order-management-sales/${orderId}?mode=edit`); }; - // 부서 트리를 평탄화 - const flattenDepartments = (depts: DepartmentRecord[]): DepartmentRecord[] => { - const result: DepartmentRecord[] = []; - const traverse = (list: DepartmentRecord[]) => { - for (const dept of list) { - result.push(dept); - if (dept.children?.length) traverse(dept.children); - } - }; - traverse(depts); - return result; - }; - - const handleProductionOrder = async () => { + const handleProductionOrder = () => { // 생산지시 생성 모달 열기 setProductionPriority("normal"); setProductionMemo(""); - setProductionDepartmentId(""); setIsProductionDialogOpen(true); - - // 부서 목록 로드 - if (departments.length === 0) { - const result = await getDepartmentTree(); - if (result.success && result.data) { - const flatList = flattenDepartments(result.data); - setDepartments(flatList); - // 디폴트: 생산부서 - const defaultDept = flatList.find(d => d.name.includes("생산")); - if (defaultDept) { - setProductionDepartmentId(String(defaultDept.id)); - } - } - } else { - // 이미 로드된 경우 디폴트 설정 - const defaultDept = departments.find(d => d.name.includes("생산")); - if (defaultDept) { - setProductionDepartmentId(String(defaultDept.id)); - } - } }; // 생산지시 확정 처리 @@ -256,7 +219,6 @@ export default function OrderDetailPage() { try { const result = await createProductionOrder(order.id, { priority: productionPriority, - departmentId: productionDepartmentId ? Number(productionDepartmentId) : undefined, memo: productionMemo || undefined, }); if (result.success && result.data) { @@ -1295,26 +1257,6 @@ export default function OrderDetailPage() {
- {/* 부서 */} -
- - -
- {/* 비고 */}
diff --git a/src/components/accounting/BadDebtCollection/BadDebtDetail.tsx b/src/components/accounting/BadDebtCollection/BadDebtDetail.tsx index abb2badd..bc189426 100644 --- a/src/components/accounting/BadDebtCollection/BadDebtDetail.tsx +++ b/src/components/accounting/BadDebtCollection/BadDebtDetail.tsx @@ -12,6 +12,7 @@ import { format } from 'date-fns'; import { Plus, X, FileText, Receipt, CreditCard, Upload, Download, Trash2 } from 'lucide-react'; 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 { PhoneInput } from '@/components/ui/phone-input'; import { BusinessNumberInput } from '@/components/ui/business-number-input'; @@ -836,23 +837,19 @@ export function BadDebtDetail({ mode, recordId, initialData }: BadDebtDetailProp {/* 악성채권 발생일 */}
- handleChange('occurrenceDate', e.target.value)} + onChange={(date) => handleChange('occurrenceDate', date)} disabled={isViewMode} - className="bg-white" />
{/* 악성채권 종료일 */}
- handleChange('endDate', e.target.value || null)} + onChange={(date) => handleChange('endDate', date || null)} disabled={isViewMode} - className="bg-white" placeholder="-" />
diff --git a/src/components/accounting/BillManagement/BillDetail.tsx b/src/components/accounting/BillManagement/BillDetail.tsx index 285a3e1f..009ef295 100644 --- a/src/components/accounting/BillManagement/BillDetail.tsx +++ b/src/components/accounting/BillManagement/BillDetail.tsx @@ -5,6 +5,7 @@ import { useRouter } from 'next/navigation'; import { Plus, X } from 'lucide-react'; 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 { CurrencyInput } from '@/components/ui/currency-input'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; @@ -301,11 +302,9 @@ export function BillDetail({ billId, mode }: BillDetailProps) { - setIssueDate(e.target.value)} + onChange={setIssueDate} disabled={isViewMode} />
@@ -315,11 +314,9 @@ export function BillDetail({ billId, mode }: BillDetailProps) { - setMaturityDate(e.target.value)} + onChange={setMaturityDate} disabled={isViewMode} /> @@ -399,12 +396,10 @@ export function BillDetail({ billId, mode }: BillDetailProps) { {index + 1} - handleUpdateInstallment(inst.id, 'date', e.target.value)} + onChange={(date) => handleUpdateInstallment(inst.id, 'date', date)} disabled={isViewMode} - className="w-full" /> diff --git a/src/components/accounting/BillManagement/BillDetailV2.tsx b/src/components/accounting/BillManagement/BillDetailV2.tsx index e1c08e4e..d63b5674 100644 --- a/src/components/accounting/BillManagement/BillDetailV2.tsx +++ b/src/components/accounting/BillManagement/BillDetailV2.tsx @@ -5,6 +5,7 @@ import { useRouter } from 'next/navigation'; import { Plus, X } from 'lucide-react'; 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 { CurrencyInput } from '@/components/ui/currency-input'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; @@ -369,11 +370,9 @@ export function BillDetailV2({ billId, mode }: BillDetailProps) { - updateField('issueDate', e.target.value)} + onChange={(date) => updateField('issueDate', date)} disabled={isViewMode} /> @@ -383,11 +382,9 @@ export function BillDetailV2({ billId, mode }: BillDetailProps) { - updateField('maturityDate', e.target.value)} + onChange={(date) => updateField('maturityDate', date)} disabled={isViewMode} /> @@ -471,12 +468,10 @@ export function BillDetailV2({ billId, mode }: BillDetailProps) { {index + 1} - handleUpdateInstallment(inst.id, 'date', e.target.value)} + onChange={(date) => handleUpdateInstallment(inst.id, 'date', date)} disabled={isViewMode} - className="w-full" /> diff --git a/src/components/accounting/DailyReport/index.tsx b/src/components/accounting/DailyReport/index.tsx index 466d8511..6e5d857f 100644 --- a/src/components/accounting/DailyReport/index.tsx +++ b/src/components/accounting/DailyReport/index.tsx @@ -15,7 +15,7 @@ import { TableRow, TableFooter, } from '@/components/ui/table'; -import { Input } from '@/components/ui/input'; +import { DatePicker } from '@/components/ui/date-picker'; import { PageLayout } from '@/components/organisms/PageLayout'; import { PageHeader } from '@/components/organisms/PageHeader'; import { Badge } from '@/components/ui/badge'; @@ -197,11 +197,11 @@ export function DailyReport({ initialNoteReceivables = [], initialDailyAccounts
조회 일자 - setSelectedDate(e.target.value)} - className="w-[160px]" + onChange={setSelectedDate} + className="w-[170px]" + size="sm" />
diff --git a/src/components/accounting/DepositManagement/DepositDetail.tsx b/src/components/accounting/DepositManagement/DepositDetail.tsx index 823b8ad9..ed28a8ae 100644 --- a/src/components/accounting/DepositManagement/DepositDetail.tsx +++ b/src/components/accounting/DepositManagement/DepositDetail.tsx @@ -8,6 +8,7 @@ import { } from 'lucide-react'; 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 { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { @@ -214,11 +215,8 @@ export function DepositDetail({ depositId, mode }: DepositDetailProps) { {/* 입금일 */}
- diff --git a/src/components/accounting/PurchaseManagement/PurchaseDetail.tsx b/src/components/accounting/PurchaseManagement/PurchaseDetail.tsx index d4df6a27..f33ffbd6 100644 --- a/src/components/accounting/PurchaseManagement/PurchaseDetail.tsx +++ b/src/components/accounting/PurchaseManagement/PurchaseDetail.tsx @@ -4,6 +4,7 @@ import { useState, useCallback, useMemo, useEffect } from 'react'; import { format } from 'date-fns'; 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 { Switch } from '@/components/ui/switch'; import { Badge } from '@/components/ui/badge'; @@ -380,10 +381,9 @@ export function PurchaseDetail({ purchaseId, mode }: PurchaseDetailProps) { {/* 매입일 */}
- setPurchaseDate(e.target.value)} + onChange={setPurchaseDate} disabled={isViewMode} />
diff --git a/src/components/accounting/PurchaseManagement/PurchaseDetailModal.tsx b/src/components/accounting/PurchaseManagement/PurchaseDetailModal.tsx index 2d0493c0..53317453 100644 --- a/src/components/accounting/PurchaseManagement/PurchaseDetailModal.tsx +++ b/src/components/accounting/PurchaseManagement/PurchaseDetailModal.tsx @@ -9,6 +9,7 @@ import { } from '@/components/ui/dialog'; 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 { QuantityInput } from '@/components/ui/quantity-input'; import { CurrencyInput } from '@/components/ui/currency-input'; @@ -199,10 +200,9 @@ export function PurchaseDetailModal({ {/* 매입일자 */}
- handleFieldChange('purchaseDate', e.target.value)} + onChange={(date) => handleFieldChange('purchaseDate', date)} />
diff --git a/src/components/accounting/SalesManagement/SalesDetail.tsx b/src/components/accounting/SalesManagement/SalesDetail.tsx index 9c882785..f923ca46 100644 --- a/src/components/accounting/SalesManagement/SalesDetail.tsx +++ b/src/components/accounting/SalesManagement/SalesDetail.tsx @@ -10,6 +10,7 @@ import { } from 'lucide-react'; 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 { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Switch } from '@/components/ui/switch'; @@ -296,11 +297,9 @@ export function SalesDetail({ mode, salesId }: SalesDetailProps) { {/* 매출일 */}
- setSalesDate(e.target.value)} + onChange={setSalesDate} disabled={isViewMode} />
diff --git a/src/components/accounting/WithdrawalManagement/WithdrawalDetail.tsx b/src/components/accounting/WithdrawalManagement/WithdrawalDetail.tsx index e157a5a1..34d6819d 100644 --- a/src/components/accounting/WithdrawalManagement/WithdrawalDetail.tsx +++ b/src/components/accounting/WithdrawalManagement/WithdrawalDetail.tsx @@ -8,6 +8,7 @@ import { } from 'lucide-react'; 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 { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { @@ -214,11 +215,8 @@ export function WithdrawalDetail({ withdrawalId, mode }: WithdrawalDetailProps) {/* 출금일 */}
- diff --git a/src/components/approval/DocumentCreate/ExpenseReportForm.tsx b/src/components/approval/DocumentCreate/ExpenseReportForm.tsx index 9027a61b..bd15c0df 100644 --- a/src/components/approval/DocumentCreate/ExpenseReportForm.tsx +++ b/src/components/approval/DocumentCreate/ExpenseReportForm.tsx @@ -2,6 +2,7 @@ import { Plus, X } from 'lucide-react'; import { Input } from '@/components/ui/input'; +import { DatePicker } from '@/components/ui/date-picker'; import { Label } from '@/components/ui/label'; import { Button } from '@/components/ui/button'; import { CurrencyInput } from '@/components/ui/currency-input'; @@ -83,21 +84,17 @@ export function ExpenseReportForm({ data, onChange }: ExpenseReportFormProps) {
- onChange({ ...data, requestDate: e.target.value })} + onChange={(date) => onChange({ ...data, requestDate: date })} />
- onChange({ ...data, paymentDate: e.target.value })} + onChange={(date) => onChange({ ...data, paymentDate: date })} />
diff --git a/src/components/approval/DocumentCreate/ProposalForm.tsx b/src/components/approval/DocumentCreate/ProposalForm.tsx index 19ee8c78..ff83373c 100644 --- a/src/components/approval/DocumentCreate/ProposalForm.tsx +++ b/src/components/approval/DocumentCreate/ProposalForm.tsx @@ -3,6 +3,7 @@ import { useEffect, useState } from 'react'; import { Mic } from 'lucide-react'; import { Input } from '@/components/ui/input'; +import { DatePicker } from '@/components/ui/date-picker'; import { Label } from '@/components/ui/label'; import { Button } from '@/components/ui/button'; import { CurrencyInput } from '@/components/ui/currency-input'; @@ -109,11 +110,9 @@ export function ProposalForm({ data, onChange }: ProposalFormProps) {
- onChange({ ...data, vendorPaymentDate: e.target.value })} + onChange={(date) => onChange({ ...data, vendorPaymentDate: date })} />
diff --git a/src/components/business/CEODashboard/modals/ScheduleDetailModal.tsx b/src/components/business/CEODashboard/modals/ScheduleDetailModal.tsx index 75acdbf3..048d462c 100644 --- a/src/components/business/CEODashboard/modals/ScheduleDetailModal.tsx +++ b/src/components/business/CEODashboard/modals/ScheduleDetailModal.tsx @@ -6,6 +6,7 @@ import { Input } from '@/components/ui/input'; import { Textarea } from '@/components/ui/textarea'; import { Checkbox } from '@/components/ui/checkbox'; import { TimePicker } from '@/components/ui/time-picker'; +import { DatePicker } from '@/components/ui/date-picker'; import { Dialog, DialogContent, @@ -172,17 +173,17 @@ export function ScheduleDetailModal({ 기간
- handleFieldChange('startDate', e.target.value)} + onChange={(value) => handleFieldChange('startDate', value)} + size="sm" className="flex-1" /> ~ - handleFieldChange('endDate', e.target.value)} + onChange={(value) => handleFieldChange('endDate', value)} + size="sm" className="flex-1" />
diff --git a/src/components/business/CEODashboard/sections/TodayIssueSection.tsx b/src/components/business/CEODashboard/sections/TodayIssueSection.tsx index cca05e51..f19db36e 100644 --- a/src/components/business/CEODashboard/sections/TodayIssueSection.tsx +++ b/src/components/business/CEODashboard/sections/TodayIssueSection.tsx @@ -6,7 +6,7 @@ import { Card, CardContent } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'; -import { Input } from '@/components/ui/input'; +import { DatePicker } from '@/components/ui/date-picker'; import { Select, SelectContent, @@ -204,9 +204,8 @@ export function TodayIssueSection({ items }: TodayIssueSectionProps) { const yesterday = useMemo(() => getYesterday(), []); const isNextDisabled = isSameDay(pastDate, yesterday); - // 날짜 input 직접 선택 - const handleDateInputChange = useCallback((e: React.ChangeEvent) => { - const value = e.target.value; // yyyy-MM-dd + // DatePicker 날짜 선택 + const handleDatePickerChange = useCallback((value: string) => { if (!value) return; const selected = new Date(value + 'T00:00:00'); if (isNaN(selected.getTime())) return; @@ -296,24 +295,27 @@ export function TodayIssueSection({ items }: TodayIssueSectionProps) { {activeTab === 'past' && (
-
@@ -210,21 +210,15 @@ export default function BiddingDetailForm({ /> ) : ( <> - - handleFieldChange('constructionStartDate', e.target.value) - } + onChange={(date) => handleFieldChange('constructionStartDate', date)} className="flex-1" /> ~ - - handleFieldChange('constructionEndDate', e.target.value) - } + onChange={(date) => handleFieldChange('constructionEndDate', date)} className="flex-1" /> @@ -313,10 +307,9 @@ export default function BiddingDetailForm({ className="bg-muted" /> ) : ( - handleFieldChange('submissionDate', e.target.value)} + onChange={(date) => handleFieldChange('submissionDate', date)} /> )}
@@ -331,10 +324,9 @@ export default function BiddingDetailForm({ className="bg-muted" /> ) : ( - handleFieldChange('confirmDate', e.target.value)} + onChange={(date) => handleFieldChange('confirmDate', date)} /> )} diff --git a/src/components/business/construction/contract/ContractDetailForm.tsx b/src/components/business/construction/contract/ContractDetailForm.tsx index 762f3547..f48154e6 100644 --- a/src/components/business/construction/contract/ContractDetailForm.tsx +++ b/src/components/business/construction/contract/ContractDetailForm.tsx @@ -8,6 +8,7 @@ import { FileDropzone } from '@/components/ui/file-dropzone'; import { FileList, type NewFile, type ExistingFile } from '@/components/ui/file-list'; 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 { Textarea } from '@/components/ui/textarea'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; @@ -297,10 +298,9 @@ export default function ContractDetailForm({ {/* 계약일자 */}
- handleFieldChange('contractDate', e.target.value)} + onChange={(date) => handleFieldChange('contractDate', date)} disabled={isViewMode} />
@@ -319,17 +319,15 @@ export default function ContractDetailForm({
- handleFieldChange('contractStartDate', e.target.value)} + onChange={(date) => handleFieldChange('contractStartDate', date)} disabled={isViewMode} /> ~ - handleFieldChange('contractEndDate', e.target.value)} + onChange={(date) => handleFieldChange('contractEndDate', date)} disabled={isViewMode} />
diff --git a/src/components/business/construction/estimates/sections/EstimateInfoSection.tsx b/src/components/business/construction/estimates/sections/EstimateInfoSection.tsx index 59f52d08..a8a45a38 100644 --- a/src/components/business/construction/estimates/sections/EstimateInfoSection.tsx +++ b/src/components/business/construction/estimates/sections/EstimateInfoSection.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { Input } from '@/components/ui/input'; +import { DatePicker } from '@/components/ui/date-picker'; import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; @@ -162,12 +163,10 @@ export function EstimateInfoSection({
- onBidInfoChange('bidDate', e.target.value)} + onChange={(date) => onBidInfoChange('bidDate', date)} disabled={isViewMode} - className={isViewMode ? 'bg-gray-50' : 'bg-white'} />
@@ -182,20 +181,16 @@ export function EstimateInfoSection({
- onBidInfoChange('constructionStartDate', e.target.value)} + onChange={(date) => onBidInfoChange('constructionStartDate', date)} disabled={isViewMode} - className={isViewMode ? 'bg-gray-50' : 'bg-white'} /> ~ - onBidInfoChange('constructionEndDate', e.target.value)} + onChange={(date) => onBidInfoChange('constructionEndDate', date)} disabled={isViewMode} - className={isViewMode ? 'bg-gray-50' : 'bg-white'} />
diff --git a/src/components/business/construction/handover-report/HandoverReportDetailForm.tsx b/src/components/business/construction/handover-report/HandoverReportDetailForm.tsx index fcd9026b..bb881ddc 100644 --- a/src/components/business/construction/handover-report/HandoverReportDetailForm.tsx +++ b/src/components/business/construction/handover-report/HandoverReportDetailForm.tsx @@ -5,6 +5,7 @@ import { useRouter } from 'next/navigation'; import { Plus, X, Eye, Stamp } from 'lucide-react'; 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 { Textarea } from '@/components/ui/textarea'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; @@ -268,10 +269,9 @@ export default function HandoverReportDetailForm({ {/* 계약일자 */}
- handleFieldChange('contractDate', e.target.value)} + onChange={(date) => handleFieldChange('contractDate', date)} disabled={isViewMode} />
@@ -290,17 +290,15 @@ export default function HandoverReportDetailForm({
- handleFieldChange('contractStartDate', e.target.value)} + onChange={(date) => handleFieldChange('contractStartDate', date)} disabled={isViewMode} /> ~ - handleFieldChange('contractEndDate', e.target.value)} + onChange={(date) => handleFieldChange('contractEndDate', date)} disabled={isViewMode} />
diff --git a/src/components/business/construction/issue-management/IssueDetailForm.tsx b/src/components/business/construction/issue-management/IssueDetailForm.tsx index 6b256803..bce5bc61 100644 --- a/src/components/business/construction/issue-management/IssueDetailForm.tsx +++ b/src/components/business/construction/issue-management/IssueDetailForm.tsx @@ -6,6 +6,7 @@ import { getTodayString } from '@/utils/date'; import { Mic, X, Upload } from 'lucide-react'; 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 { Textarea } from '@/components/ui/textarea'; import { @@ -406,11 +407,9 @@ export default function IssueDetailForm({ issue, mode = 'view' }: IssueDetailFor {/* 이슈보고일 */}
- handleInputChange('reportDate')({ target: { value: date } } as React.ChangeEvent)} disabled={isReadOnly} />
@@ -418,11 +417,9 @@ export default function IssueDetailForm({ issue, mode = 'view' }: IssueDetailFor {/* 이슈해결일 */}
- handleInputChange('resolvedDate')({ target: { value: date } } as React.ChangeEvent)} disabled={isReadOnly} />
diff --git a/src/components/business/construction/management/ConstructionDetailClient.tsx b/src/components/business/construction/management/ConstructionDetailClient.tsx index f6f9886b..6271b694 100644 --- a/src/components/business/construction/management/ConstructionDetailClient.tsx +++ b/src/components/business/construction/management/ConstructionDetailClient.tsx @@ -6,6 +6,7 @@ import { getTodayString, formatDate } from '@/utils/date'; import { Plus, Trash2, FileText, Upload, X } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; +import { DatePicker } from '@/components/ui/date-picker'; import { Textarea } from '@/components/ui/textarea'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { @@ -399,13 +400,11 @@ export default function ConstructionDetailClient({ id, mode }: ConstructionDetai {isViewMode ? ( {worker.workDate || '-'} ) : ( - - handleWorkerInfoChange(worker.id, 'workDate', e.target.value) + onChange={(date) => + handleWorkerInfoChange(worker.id, 'workDate', date) } - className="w-full" /> )} diff --git a/src/components/business/construction/management/ProjectEndDialog.tsx b/src/components/business/construction/management/ProjectEndDialog.tsx index 0a67ea98..347ec880 100644 --- a/src/components/business/construction/management/ProjectEndDialog.tsx +++ b/src/components/business/construction/management/ProjectEndDialog.tsx @@ -10,6 +10,7 @@ import { } from '@/components/ui/dialog'; 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 { Textarea } from '@/components/ui/textarea'; import { @@ -112,9 +113,7 @@ export default function ProjectEndDialog({ {/* 결선작업일 - 읽기전용 */}
- - - setFormData((prev) => ({ ...prev, completionDate: e.target.value })) + onChange={(date) => + setFormData((prev) => ({ ...prev, completionDate: date })) } />
diff --git a/src/components/business/construction/order-management/cards/ConstructionDetailCard.tsx b/src/components/business/construction/order-management/cards/ConstructionDetailCard.tsx index ec89d139..e08aa789 100644 --- a/src/components/business/construction/order-management/cards/ConstructionDetailCard.tsx +++ b/src/components/business/construction/order-management/cards/ConstructionDetailCard.tsx @@ -2,6 +2,7 @@ import { Label } from '@/components/ui/label'; import { Input } from '@/components/ui/input'; +import { DatePicker } from '@/components/ui/date-picker'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Select, @@ -60,19 +61,16 @@ export function ConstructionDetailCard({ 시공완료일
- onFieldChange('constructionStartDate', e.target.value)} + onChange={(date) => onFieldChange('constructionStartDate', date)} disabled={isViewMode} - required className="flex-1" /> ~ - onFieldChange('constructionEndDate', e.target.value)} + onChange={(date) => onFieldChange('constructionEndDate', date)} disabled={isViewMode} className="flex-1" /> diff --git a/src/components/business/construction/order-management/tables/OrderDetailItemTable.tsx b/src/components/business/construction/order-management/tables/OrderDetailItemTable.tsx index 8c8ca5ac..62fd83d3 100644 --- a/src/components/business/construction/order-management/tables/OrderDetailItemTable.tsx +++ b/src/components/business/construction/order-management/tables/OrderDetailItemTable.tsx @@ -3,6 +3,7 @@ import { Plus, Trash2, Image as ImageIcon } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; +import { DatePicker } from '@/components/ui/date-picker'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { NumberInput } from '@/components/ui/number-input'; import { QuantityInput } from '@/components/ui/quantity-input'; @@ -219,13 +220,12 @@ export function OrderDetailItemTable({ {isEditMode ? ( - - onItemChange(item.id, 'constructionStartDate', e.target.value) + onChange={(date) => + onItemChange(item.id, 'constructionStartDate', date) } - className="h-8" + size="sm" /> ) : ( item.constructionStartDate || '-' @@ -233,13 +233,12 @@ export function OrderDetailItemTable({ {isEditMode ? ( - - onItemChange(item.id, 'constructionEndDate', e.target.value) + onChange={(date) => + onItemChange(item.id, 'constructionEndDate', date) } - className="h-8" + size="sm" /> ) : ( item.constructionEndDate || '-' @@ -360,13 +359,12 @@ export function OrderDetailItemTable({ {isEditMode ? ( - - onItemChange(item.id, 'orderDate', e.target.value) + onChange={(date) => + onItemChange(item.id, 'orderDate', date) } - className="h-8" + size="sm" /> ) : ( item.orderDate || '-' @@ -374,13 +372,12 @@ export function OrderDetailItemTable({ {isEditMode ? ( - - onItemChange(item.id, 'plannedDeliveryDate', e.target.value) + onChange={(date) => + onItemChange(item.id, 'plannedDeliveryDate', date) } - className="h-8" + size="sm" /> ) : ( item.plannedDeliveryDate || '-' @@ -388,13 +385,12 @@ export function OrderDetailItemTable({ {isEditMode ? ( - - onItemChange(item.id, 'actualDeliveryDate', e.target.value) + onChange={(date) => + onItemChange(item.id, 'actualDeliveryDate', date) } - className="h-8" + size="sm" /> ) : ( item.actualDeliveryDate || '-' diff --git a/src/components/business/construction/site-briefings/SiteBriefingForm.tsx b/src/components/business/construction/site-briefings/SiteBriefingForm.tsx index 5a1bcbbf..f5ad0b69 100644 --- a/src/components/business/construction/site-briefings/SiteBriefingForm.tsx +++ b/src/components/business/construction/site-briefings/SiteBriefingForm.tsx @@ -7,6 +7,7 @@ import { FileDropzone } from '@/components/ui/file-dropzone'; import { FileList, type NewFile, type ExistingFile } from '@/components/ui/file-list'; 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 { Textarea } from '@/components/ui/textarea'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; @@ -751,20 +752,16 @@ export default function SiteBriefingForm({ mode, briefingId, initialData }: Site
- handleChange('constructionStartDate', e.target.value)} + onChange={(date) => handleChange('constructionStartDate', date)} disabled={isViewMode} - className="bg-white" /> ~ - handleChange('constructionEndDate', e.target.value)} + onChange={(date) => handleChange('constructionEndDate', date)} disabled={isViewMode} - className="bg-white" />
diff --git a/src/components/business/construction/structure-review/StructureReviewDetailForm.tsx b/src/components/business/construction/structure-review/StructureReviewDetailForm.tsx index 81ba12d7..d438c7d3 100644 --- a/src/components/business/construction/structure-review/StructureReviewDetailForm.tsx +++ b/src/components/business/construction/structure-review/StructureReviewDetailForm.tsx @@ -11,6 +11,7 @@ import { FileDropzone } from '@/components/ui/file-dropzone'; import { FileList, type NewFile, type ExistingFile } from '@/components/ui/file-list'; 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 { Select, @@ -310,11 +311,9 @@ export default function StructureReviewDetailForm({ {/* 구조검토 의뢰일 */}
- handleInputChange('requestDate')({ target: { value: date } } as React.ChangeEvent)} disabled={isViewMode} />
@@ -322,11 +321,9 @@ export default function StructureReviewDetailForm({ {/* 구조검토 완료일 */}
- handleInputChange('completionDate')({ target: { value: date } } as React.ChangeEvent)} disabled={isViewMode} />
diff --git a/src/components/hr/EmployeeManagement/EmployeeDialog.tsx b/src/components/hr/EmployeeManagement/EmployeeDialog.tsx index f985bfa7..d6f124b5 100644 --- a/src/components/hr/EmployeeManagement/EmployeeDialog.tsx +++ b/src/components/hr/EmployeeManagement/EmployeeDialog.tsx @@ -11,6 +11,7 @@ import { } from '@/components/ui/dialog'; 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 { CurrencyInput } from '@/components/ui/currency-input'; import { PhoneInput } from '@/components/ui/phone-input'; @@ -348,11 +349,9 @@ export function EmployeeDialog({ {fieldSettings.showHireDate && (
- handleChange('hireDate', e.target.value)} + onChange={(date) => handleChange('hireDate', date)} disabled={isViewMode} />
diff --git a/src/components/hr/EmployeeManagement/EmployeeForm.tsx b/src/components/hr/EmployeeManagement/EmployeeForm.tsx index 149442ad..295c5529 100644 --- a/src/components/hr/EmployeeManagement/EmployeeForm.tsx +++ b/src/components/hr/EmployeeManagement/EmployeeForm.tsx @@ -14,6 +14,7 @@ import { employeeCreateConfig, employeeEditConfig, employeeConfig } from './empl import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; 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 { CurrencyInput } from '@/components/ui/currency-input'; import { PhoneInput } from '@/components/ui/phone-input'; @@ -686,11 +687,9 @@ export function EmployeeForm({ {fieldSettings.showHireDate && (
- handleChange('hireDate', e.target.value)} + onChange={(date) => handleChange('hireDate', date)} disabled={isViewMode} />
@@ -887,11 +886,9 @@ export function EmployeeForm({ {fieldSettings.showResignationDate && (
- handleChange('resignationDate', e.target.value)} + onChange={(date) => handleChange('resignationDate', date)} disabled={isViewMode} />
diff --git a/src/components/hr/VacationManagement/VacationGrantDialog.tsx b/src/components/hr/VacationManagement/VacationGrantDialog.tsx index 7fe3f8e9..d2fdd0aa 100644 --- a/src/components/hr/VacationManagement/VacationGrantDialog.tsx +++ b/src/components/hr/VacationManagement/VacationGrantDialog.tsx @@ -12,6 +12,7 @@ import { } from '@/components/ui/dialog'; 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 { Textarea } from '@/components/ui/textarea'; import { QuantityInput } from '@/components/ui/quantity-input'; @@ -156,11 +157,9 @@ export function VacationGrantDialog({ {/* 부여일 */}
- setFormData(prev => ({ ...prev, grantDate: e.target.value }))} + onChange={(date) => setFormData(prev => ({ ...prev, grantDate: date }))} />
diff --git a/src/components/hr/VacationManagement/VacationRequestDialog.tsx b/src/components/hr/VacationManagement/VacationRequestDialog.tsx index 0e292f31..8d11f66c 100644 --- a/src/components/hr/VacationManagement/VacationRequestDialog.tsx +++ b/src/components/hr/VacationManagement/VacationRequestDialog.tsx @@ -12,6 +12,7 @@ import { } from '@/components/ui/dialog'; 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 { Select, @@ -165,23 +166,19 @@ export function VacationRequestDialog({ {/* 시작일 */}
- setFormData(prev => ({ ...prev, startDate: e.target.value }))} + onChange={(date) => setFormData(prev => ({ ...prev, startDate: date }))} />
{/* 종료일 */}
- setFormData(prev => ({ ...prev, endDate: e.target.value }))} + minDate={formData.startDate ? new Date(formData.startDate) : undefined} + onChange={(date) => setFormData(prev => ({ ...prev, endDate: date }))} />
diff --git a/src/components/items/DynamicItemForm/fields/DateField.tsx b/src/components/items/DynamicItemForm/fields/DateField.tsx index 130e4cb7..6ad430ba 100644 --- a/src/components/items/DynamicItemForm/fields/DateField.tsx +++ b/src/components/items/DynamicItemForm/fields/DateField.tsx @@ -6,7 +6,7 @@ 'use client'; import { Label } from '@/components/ui/label'; -import { Input } from '@/components/ui/input'; +import { DatePicker } from '@/components/ui/date-picker'; import type { DynamicFieldRendererProps } from '../types'; export function DateField({ @@ -25,11 +25,9 @@ export function DateField({ {field.field_name} {field.is_required && *} - onChange(e.target.value)} + onChange={(date) => onChange(date)} disabled={disabled} className={error ? 'border-red-500' : ''} /> diff --git a/src/components/items/ItemForm/forms/ProductForm.tsx b/src/components/items/ItemForm/forms/ProductForm.tsx index 3f59398e..ab1fcd63 100644 --- a/src/components/items/ItemForm/forms/ProductForm.tsx +++ b/src/components/items/ItemForm/forms/ProductForm.tsx @@ -3,6 +3,7 @@ */ import { Input } from '@/components/ui/input'; +import { DatePicker } from '@/components/ui/date-picker'; import { Label } from '@/components/ui/label'; import { Checkbox } from '@/components/ui/checkbox'; import { Textarea } from '@/components/ui/textarea'; @@ -186,6 +187,8 @@ export function ProductCertificationSection({ setCertificationFile, isSubmitting, register, + setValue, + getValues, }: Pick) { return (
@@ -221,20 +226,18 @@ export function ProductCertificationSection({
- setValue('certificationStartDate', date)} disabled={isSubmitting} />
- setValue('certificationEndDate', date)} disabled={isSubmitting} />
diff --git a/src/components/items/ItemForm/index.tsx b/src/components/items/ItemForm/index.tsx index 59a489a5..be414ab5 100644 --- a/src/components/items/ItemForm/index.tsx +++ b/src/components/items/ItemForm/index.tsx @@ -348,6 +348,8 @@ export default function ItemForm({ mode, initialData, onSubmit }: ItemFormProps) setCertificationFile={setCertificationFile} isSubmitting={isSubmitting} register={register} + setValue={setValue} + getValues={getValues} /> )} diff --git a/src/components/material/ReceivingManagement/InspectionCreate.tsx b/src/components/material/ReceivingManagement/InspectionCreate.tsx index 7b385d84..94e44825 100644 --- a/src/components/material/ReceivingManagement/InspectionCreate.tsx +++ b/src/components/material/ReceivingManagement/InspectionCreate.tsx @@ -12,13 +12,14 @@ import { useState, useCallback, useMemo, useEffect } from 'react'; import { useRouter } from 'next/navigation'; -import { Calendar } from 'lucide-react'; + import { getTodayString } from '@/utils/date'; import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate'; import { materialInspectionCreateConfig } from './inspectionConfig'; import { ContentSkeleton } from '@/components/ui/skeleton'; 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 { Textarea } from '@/components/ui/textarea'; import { @@ -251,15 +252,10 @@ export function InspectionCreate({ id }: Props) {
-
- setInspectionDate(e.target.value)} - className="pr-10" - /> - -
+ setInspectionDate(date)} + />
@@ -649,17 +647,16 @@ export function ReceivingDetail({ id, mode = 'view' }: Props) { {idx + 1} - { + onChange={(date) => { setAdjustments((prev) => prev.map((a) => - a.id === adj.id ? { ...a, adjustmentDate: e.target.value } : a + a.id === adj.id ? { ...a, adjustmentDate: date } : a ) ); }} - className="h-8 text-sm" + size="sm" /> diff --git a/src/components/molecules/DateRangeSelector.tsx b/src/components/molecules/DateRangeSelector.tsx index 8e819a64..3f174c30 100644 --- a/src/components/molecules/DateRangeSelector.tsx +++ b/src/components/molecules/DateRangeSelector.tsx @@ -2,8 +2,8 @@ import { ReactNode, useCallback } from 'react'; import { format, startOfYear, endOfYear, subMonths, startOfMonth, endOfMonth, subDays } from 'date-fns'; -import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; +import { DatePicker } from '@/components/ui/date-picker'; import { ScrollableButtonGroup } from '@/components/atoms/ScrollableButtonGroup'; /** @@ -152,18 +152,20 @@ export function DateRangeSelector({ {/* 날짜 범위 선택 */} {!hideDateInputs && (
- onStartDateChange(e.target.value)} - className="w-[165px]" + onChange={onStartDateChange} + className="w-[170px]" + size="sm" + displayFormat="yyyy년 MM월 dd일" /> ~ - onEndDateChange(e.target.value)} - className="w-[165px]" + onChange={onEndDateChange} + className="w-[170px]" + size="sm" + displayFormat="yyyy년 MM월 dd일" />
)} @@ -186,18 +188,20 @@ export function DateRangeSelector({ {/* 날짜 범위 선택 */} {!hideDateInputs && (
- onStartDateChange(e.target.value)} - className="w-[165px]" + onChange={onStartDateChange} + className="w-[170px]" + size="sm" + displayFormat="yyyy년 MM월 dd일" /> ~ - onEndDateChange(e.target.value)} - className="w-[165px]" + onChange={onEndDateChange} + className="w-[170px]" + size="sm" + displayFormat="yyyy년 MM월 dd일" />
)} diff --git a/src/components/molecules/FormField.tsx b/src/components/molecules/FormField.tsx index 55d25008..3f5afd9f 100644 --- a/src/components/molecules/FormField.tsx +++ b/src/components/molecules/FormField.tsx @@ -14,6 +14,7 @@ import { PersonalNumberInput } from "../ui/personal-number-input"; import { NumberInput } from "../ui/number-input"; import { CurrencyInput } from "../ui/currency-input"; import { QuantityInput } from "../ui/quantity-input"; +import { DatePicker } from "../ui/date-picker"; export type FormFieldType = | 'text' @@ -166,11 +167,9 @@ export function FormField({ case 'date': return ( - onChange?.(e.target.value)} + onChange?.(date)} disabled={disabled} className={`${error ? 'border-red-500' : ''} ${inputClassName}`} /> diff --git a/src/components/orders/OrderRegistration.tsx b/src/components/orders/OrderRegistration.tsx index 13a25e7d..01a7481c 100644 --- a/src/components/orders/OrderRegistration.tsx +++ b/src/components/orders/OrderRegistration.tsx @@ -15,6 +15,7 @@ import { useState, useEffect, useCallback, useMemo } from "react"; import { useDaumPostcode } from "@/hooks/useDaumPostcode"; import { useClientList } from "@/hooks/useClientList"; import { Input } from "@/components/ui/input"; +import { DatePicker } from "@/components/ui/date-picker"; import { Textarea } from "@/components/ui/textarea"; import { Button } from "@/components/ui/button"; import { QuantityInput } from "@/components/ui/quantity-input"; @@ -730,13 +731,12 @@ export function OrderRegistration({ - { + onChange={(date) => { setForm((prev) => ({ ...prev, - deliveryRequestDate: e.target.value, + deliveryRequestDate: date, })); clearFieldError("deliveryRequestDate"); }} @@ -750,13 +750,12 @@ export function OrderRegistration({
- + onChange={(date) => setForm((prev) => ({ ...prev, - expectedShipDate: e.target.value, + expectedShipDate: date, })) } disabled={form.expectedShipDateUndecided} diff --git a/src/components/orders/OrderSalesDetailEdit.tsx b/src/components/orders/OrderSalesDetailEdit.tsx index 222ac13a..525cad7e 100644 --- a/src/components/orders/OrderSalesDetailEdit.tsx +++ b/src/components/orders/OrderSalesDetailEdit.tsx @@ -13,6 +13,7 @@ import { useState, useEffect, useMemo, useCallback } from "react"; import { useRouter } from "next/navigation"; import { Input } from "@/components/ui/input"; +import { DatePicker } from "@/components/ui/date-picker"; import { Textarea } from "@/components/ui/textarea"; import { PhoneInput } from "@/components/ui/phone-input"; import { Checkbox } from "@/components/ui/checkbox"; @@ -414,22 +415,20 @@ export function OrderSalesDetailEdit({ orderId }: OrderSalesDetailEditProps) { - - setForm({ ...form, deliveryRequestDate: e.target.value }) + onChange={(date) => + setForm({ ...form, deliveryRequestDate: date }) } />
- - setForm({ ...form, expectedShipDate: e.target.value }) + onChange={(date) => + setForm({ ...form, expectedShipDate: date }) } disabled={form.expectedShipDateUndecided} /> diff --git a/src/components/outbound/ShipmentManagement/ShipmentCreate.tsx b/src/components/outbound/ShipmentManagement/ShipmentCreate.tsx index fd4cd3d0..48f679ea 100644 --- a/src/components/outbound/ShipmentManagement/ShipmentCreate.tsx +++ b/src/components/outbound/ShipmentManagement/ShipmentCreate.tsx @@ -10,6 +10,7 @@ import { useRouter } from 'next/navigation'; import { Plus, X as XIcon, ChevronDown, Search } from 'lucide-react'; import { getTodayString } from '@/utils/date'; import { Input } from '@/components/ui/input'; +import { DatePicker } from '@/components/ui/date-picker'; import { Label } from '@/components/ui/label'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; @@ -426,19 +427,17 @@ export function ShipmentCreate() {
- handleInputChange('scheduledDate', e.target.value)} + onChange={(date) => handleInputChange('scheduledDate', date)} disabled={isSubmitting} />
- handleInputChange('shipmentDate', e.target.value)} + onChange={(date) => handleInputChange('shipmentDate', date)} disabled={isSubmitting} />
diff --git a/src/components/outbound/ShipmentManagement/ShipmentEdit.tsx b/src/components/outbound/ShipmentManagement/ShipmentEdit.tsx index 48ad2c6b..950b40bd 100644 --- a/src/components/outbound/ShipmentManagement/ShipmentEdit.tsx +++ b/src/components/outbound/ShipmentManagement/ShipmentEdit.tsx @@ -9,6 +9,7 @@ import { useState, useCallback, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import { Plus, X as XIcon, ChevronDown, Search } from 'lucide-react'; import { Input } from '@/components/ui/input'; +import { DatePicker } from '@/components/ui/date-picker'; import { Label } from '@/components/ui/label'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; @@ -423,19 +424,17 @@ export function ShipmentEdit({ id }: ShipmentEditProps) {
- handleInputChange('scheduledDate', e.target.value)} + onChange={(date) => handleInputChange('scheduledDate', date)} disabled={isSubmitting} />
- handleInputChange('shipmentDate', e.target.value)} + onChange={(date) => handleInputChange('shipmentDate', date)} disabled={isSubmitting} />
diff --git a/src/components/pricing-distribution/PriceDistributionDetail.tsx b/src/components/pricing-distribution/PriceDistributionDetail.tsx index a7f9352e..d1ce6370 100644 --- a/src/components/pricing-distribution/PriceDistributionDetail.tsx +++ b/src/components/pricing-distribution/PriceDistributionDetail.tsx @@ -15,6 +15,7 @@ import { Button } from '@/components/ui/button'; import { useMenuStore } from '@/store/menuStore'; 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 { Badge } from '@/components/ui/badge'; import { getPresetStyle } from '@/lib/utils/status-config'; @@ -304,11 +305,10 @@ export function PriceDistributionDetail({ id, mode: propMode }: Props) {
{isEditMode ? ( - handleChange('effectiveDate', e.target.value)} - className="h-8 text-sm" + onChange={(date) => handleChange('effectiveDate', date)} + size="sm" /> ) : (

diff --git a/src/components/pricing/PricingFormClient.tsx b/src/components/pricing/PricingFormClient.tsx index eeebdcee..197ecad8 100644 --- a/src/components/pricing/PricingFormClient.tsx +++ b/src/components/pricing/PricingFormClient.tsx @@ -29,6 +29,7 @@ import { import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; 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 { Textarea } from '@/components/ui/textarea'; import { CurrencyInput } from '@/components/ui/currency-input'; @@ -426,11 +427,10 @@ export function PricingFormClient({ - { - setEffectiveDate(e.target.value); + onChange={(date) => { + setEffectiveDate(date); setErrors((prev) => { const n = {...prev}; delete n.effectiveDate; return n; }); }} className={errors.effectiveDate ? 'border-red-500' : ''} @@ -457,10 +457,9 @@ export function PricingFormClient({

- setReceiveDate(e.target.value)} + onChange={(date) => setReceiveDate(date)} />
diff --git a/src/components/process-management/ProcessDetail.tsx b/src/components/process-management/ProcessDetail.tsx index 54ccd8ad..a3f77d1e 100644 --- a/src/components/process-management/ProcessDetail.tsx +++ b/src/components/process-management/ProcessDetail.tsx @@ -137,14 +137,15 @@ export function ProcessDetail({ process }: ProcessDetailProps) { 기본 정보 -
+ {/* Row 1: 공정번호 | 공정명 | 담당부서 | 담당자 */} +
공정번호
{process.processCode}
-
공정형
- {process.processType} +
공정명
+
{process.processName}
담당부서
@@ -154,6 +155,13 @@ export function ProcessDetail({ process }: ProcessDetailProps) {
담당자
{process.manager || '-'}
+
+ {/* Row 2: 구분 | 생산일자 | 상태 */} +
+
+
구분
+
{process.processCategory || '없음'}
+
생산일자
@@ -188,7 +196,7 @@ export function ProcessDetail({ process }: ProcessDetailProps) { {itemCount}개
diff --git a/src/components/process-management/ProcessForm.tsx b/src/components/process-management/ProcessForm.tsx index d727824b..550e102f 100644 --- a/src/components/process-management/ProcessForm.tsx +++ b/src/components/process-management/ProcessForm.tsx @@ -11,7 +11,7 @@ * 제거된 섹션: 자동분류규칙, 작업정보, 설명 */ -import { useState, useCallback, useEffect, useRef } from 'react'; +import { useState, useCallback, useEffect, useRef, useMemo } from 'react'; import { useRouter } from 'next/navigation'; import { Plus, GripVertical, Trash2, Package } from 'lucide-react'; import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate'; @@ -28,11 +28,10 @@ import { SelectTrigger, SelectValue, } from '@/components/ui/select'; -import { Switch } from '@/components/ui/switch'; import { RuleModal } from './RuleModal'; import { toast } from 'sonner'; import type { Process, ClassificationRule, ProcessType, ProcessStep } from '@/types/process'; -import { PROCESS_TYPE_OPTIONS } from '@/types/process'; +import { PROCESS_TYPE_OPTIONS, PROCESS_CATEGORY_OPTIONS } from '@/types/process'; import { createProcess, updateProcess, @@ -57,6 +56,9 @@ export function ProcessForm({ mode, initialData }: ProcessFormProps) { ); const [department, setDepartment] = useState(initialData?.department || ''); const [manager, setManager] = useState(initialData?.manager || ''); + const [processCategory, setProcessCategory] = useState( + initialData?.processCategory || '' + ); const [useProductionDate, setUseProductionDate] = useState( initialData?.useProductionDate ?? false ); @@ -86,6 +88,24 @@ export function ProcessForm({ mode, initialData }: ProcessFormProps) { const [dragIndex, setDragIndex] = useState(null); const [dragOverIndex, setDragOverIndex] = useState(null); + // 공정명에 따른 구분 옵션 계산 + const categoryOptions = useMemo(() => { + const name = processName.trim(); + for (const [key, options] of Object.entries(PROCESS_CATEGORY_OPTIONS)) { + if (name.includes(key)) return options; + } + return [{ value: '없음', label: '없음' }]; + }, [processName]); + + // 공정명 변경 시 구분 값 리셋 + useEffect(() => { + if (categoryOptions.length === 0) { + setProcessCategory(''); + } else if (processCategory && !categoryOptions.find(o => o.value === processCategory)) { + setProcessCategory(''); + } + }, [categoryOptions]); // eslint-disable-line react-hooks/exhaustive-deps + // 품목 개수 계산 const itemCount = classificationRules .filter((r) => r.registrationType === 'individual') @@ -227,6 +247,7 @@ export function ProcessForm({ mode, initialData }: ProcessFormProps) { const formData = { processName: processName.trim(), processType, + processCategory: processCategory || undefined, department, classificationRules: classificationRules.map((rule) => ({ registrationType: rule.registrationType, @@ -288,7 +309,18 @@ export function ProcessForm({ mode, initialData }: ProcessFormProps) { 기본 정보 -
+ {/* Row 1: 공정번호(수정시) | 공정명 | 담당부서 | 담당자 */} +
+ {isEdit && initialData?.processCode && ( +
+ + +
+ )}
-
- - -
+ + + + + {categoryOptions.map((opt) => ( + + {opt.label} + + ))} + + +
-
- - - {useProductionDate ? '사용' : '미사용'} - -
+
@@ -399,7 +438,7 @@ export function ProcessForm({ mode, initialData }: ProcessFormProps) { size="sm" onClick={() => setRuleModalOpen(true)} > - 품목 선택 + 공정 품목 선택
@@ -553,12 +592,15 @@ export function ProcessForm({ mode, initialData }: ProcessFormProps) { onAdd={handleSaveRule} editRule={editingRule} processId={initialData?.id} + processName={processName} /> ), [ processName, processType, + processCategory, + categoryOptions, department, manager, useProductionDate, diff --git a/src/components/process-management/ProcessListClient.tsx b/src/components/process-management/ProcessListClient.tsx index d5e403ab..eada0796 100644 --- a/src/components/process-management/ProcessListClient.tsx +++ b/src/components/process-management/ProcessListClient.tsx @@ -236,7 +236,6 @@ export default function ProcessListClient({ initialData = [], initialStats }: Pr { key: 'processCode', label: '공정번호', className: 'w-[120px]' }, { key: 'processName', label: '공정명', className: 'min-w-[200px]' }, { key: 'department', label: '담당부서', className: 'w-[120px]' }, - { key: 'steps', label: '단계', className: 'w-[80px] text-center' }, { key: 'items', label: '품목', className: 'w-[80px] text-center' }, { key: 'status', label: '상태', className: 'w-[80px] text-center' }, ], @@ -329,7 +328,6 @@ export default function ProcessListClient({ initialData = [], initialStats }: Pr {process.processCode} {process.processName} {process.department} - {process.steps?.length ?? 0} {itemCount > 0 ? itemCount : '-'} e.stopPropagation()}> - 0 ? `${itemCount}개` : '-'} />
} diff --git a/src/components/process-management/RuleModal.tsx b/src/components/process-management/RuleModal.tsx index ce54882a..ed95d772 100644 --- a/src/components/process-management/RuleModal.tsx +++ b/src/components/process-management/RuleModal.tsx @@ -11,10 +11,6 @@ import { } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; -import { QuantityInput } from '@/components/ui/quantity-input'; -import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; -import { Switch } from '@/components/ui/switch'; import { Checkbox } from '@/components/ui/checkbox'; import { Select, @@ -31,17 +27,33 @@ import { TableHeader, TableRow, } from '@/components/ui/table'; -import { Search, Package } from 'lucide-react'; -import type { - ClassificationRule, - RuleRegistrationType, - RuleType, - MatchingType, -} from '@/types/process'; -import { RULE_TYPE_OPTIONS, MATCHING_TYPE_OPTIONS } from '@/types/process'; +import { Search } from 'lucide-react'; +import type { ClassificationRule } from '@/types/process'; +import { PROCESS_CATEGORY_OPTIONS } from '@/types/process'; -// 품목 유형 기본 옵션 (전체) -const DEFAULT_ITEM_TYPE_OPTION = { value: 'all', label: '전체' }; +// 공정 필터 옵션 +const PROCESS_FILTER_OPTIONS = [ + { value: 'all', label: '전체' }, + { value: '스크린', label: '스크린' }, + { value: '슬릿', label: '슬릿' }, + { value: '절곡', label: '절곡' }, +]; + +// 공정 필터에 따른 구분 필터 옵션 +function getCategoryFilterOptions(processFilter: string): { value: string; label: string }[] { + if (processFilter === 'all') { + return [{ value: 'all', label: '전체' }]; + } + const categories = PROCESS_CATEGORY_OPTIONS[processFilter]; + if (!categories || categories.length === 0) { + return [{ value: 'all', label: '전체' }]; + } + // 스크린의 경우 '없음'만 있으므로 전체만 표시 + if (categories.length === 1 && categories[0].value === '없음') { + return [{ value: 'all', label: '전체' }]; + } + return [{ value: 'all', label: '전체' }, ...categories]; +} interface RuleModalProps { open: boolean; @@ -50,44 +62,40 @@ interface RuleModalProps { editRule?: ClassificationRule; /** 현재 공정 ID (다른 공정에 이미 배정된 품목 제외용) */ processId?: string; + /** 현재 공정명 (하단 안내 문구용) */ + processName?: string; } -export function RuleModal({ open, onOpenChange, onAdd, editRule, processId }: RuleModalProps) { - // 공통 상태 - const [registrationType, setRegistrationType] = useState( - editRule?.registrationType || 'pattern' - ); - const [description, setDescription] = useState(editRule?.description || ''); - - // 패턴 규칙용 상태 - const [ruleType, setRuleType] = useState(editRule?.ruleType || '품목코드'); - const [matchingType, setMatchingType] = useState( - editRule?.matchingType || 'startsWith' - ); - const [conditionValue, setConditionValue] = useState(editRule?.conditionValue || ''); - const [priority, setPriority] = useState(editRule?.priority || 10); - const [isActive, setIsActive] = useState(editRule?.isActive ?? true); - - // 개별 품목용 상태 +export function RuleModal({ open, onOpenChange, onAdd, editRule, processId, processName }: RuleModalProps) { + // 검색/필터 상태 const [searchKeyword, setSearchKeyword] = useState(''); const [selectedItemType, setSelectedItemType] = useState('all'); const [selectedItemIds, setSelectedItemIds] = useState>(new Set()); + // 공정/구분 필터 상태 + const [processFilter, setProcessFilter] = useState('all'); + const [categoryFilter, setCategoryFilter] = useState('all'); + // 품목 목록 API 상태 const [itemList, setItemList] = useState([]); const [isItemsLoading, setIsItemsLoading] = useState(false); // 품목 유형 옵션 (common_codes에서 동적 조회) - const [itemTypeOptions, setItemTypeOptions] = useState>([DEFAULT_ITEM_TYPE_OPTION]); + const [itemTypeOptions, setItemTypeOptions] = useState>([ + { value: 'all', label: '전체' }, + ]); - // 품목 목록 로드 (debounced) + // 구분 필터 옵션 (공정 필터에 따라 변경) + const categoryFilterOptions = getCategoryFilterOptions(processFilter); + + // 품목 목록 로드 const loadItems = useCallback(async (q?: string, itemType?: string) => { setIsItemsLoading(true); const items = await getItemList({ q: q || undefined, itemType: itemType === 'all' ? undefined : itemType, - size: 1000, // 전체 품목 조회 - excludeProcessId: processId, // 다른 공정에 이미 배정된 품목 제외 + size: 1000, + excludeProcessId: processId, }); setItemList(items); setIsItemsLoading(false); @@ -96,21 +104,14 @@ export function RuleModal({ open, onOpenChange, onAdd, editRule, processId }: Ru // 검색어 유효성 검사 함수 const isValidSearchKeyword = (keyword: string): boolean => { if (!keyword || keyword.trim() === '') return false; - const trimmed = keyword.trim(); - // 한글이 포함되어 있으면 1자 이상 const hasKorean = /[가-힣]/.test(trimmed); if (hasKorean) return trimmed.length >= 1; - - // 영어/숫자만 있으면 2자 이상 return trimmed.length >= 2; }; - // 검색어/품목유형 변경 시 API 호출 (debounce) + // 검색어 변경 시 API 호출 (debounce) useEffect(() => { - if (registrationType !== 'individual') return; - - // 검색어 유효성 검사 - 유효하지 않으면 빈 목록 if (!isValidSearchKeyword(searchKeyword)) { setItemList([]); return; @@ -121,30 +122,40 @@ export function RuleModal({ open, onOpenChange, onAdd, editRule, processId }: Ru }, 300); return () => clearTimeout(timer); - }, [searchKeyword, selectedItemType, registrationType, loadItems]); + }, [searchKeyword, selectedItemType, loadItems]); - // 품목유형 변경 시 검색어가 유효하면 재검색 - useEffect(() => { - if (registrationType !== 'individual') return; - if (!isValidSearchKeyword(searchKeyword)) return; - - loadItems(searchKeyword, selectedItemType); - }, [selectedItemType]); - - // 모달 열릴 때 품목 목록 초기화 + 품목유형 옵션 로드 + // 모달 열릴 때 초기화 useEffect(() => { if (open) { - // 품목유형 옵션 로드 (common_codes에서 동적 조회) + // 품목유형 옵션 로드 getItemTypeOptions().then((options) => { - setItemTypeOptions([DEFAULT_ITEM_TYPE_OPTION, ...options]); + setItemTypeOptions([{ value: 'all', label: '전체' }, ...options]); }); - if (registrationType === 'individual') { - setItemList([]); - setSearchKeyword(''); + if (editRule) { + // 수정 모드: 기존 선택된 품목 ID 설정 + if (editRule.registrationType === 'individual') { + const ids = editRule.conditionValue.split(',').filter(Boolean); + setSelectedItemIds(new Set(ids)); + } else { + setSelectedItemIds(new Set()); + } + } else { + setSelectedItemIds(new Set()); } + + setSearchKeyword(''); + setSelectedItemType('all'); + setProcessFilter('all'); + setCategoryFilter('all'); + setItemList([]); } - }, [open, registrationType]); + }, [open, editRule]); + + // 공정 필터 변경 시 구분 필터 리셋 + useEffect(() => { + setCategoryFilter('all'); + }, [processFilter]); // 체크박스 토글 const handleToggleItem = (id: string) => { @@ -159,363 +170,174 @@ export function RuleModal({ open, onOpenChange, onAdd, editRule, processId }: Ru }); }; - // 전체 선택 - const handleSelectAll = () => { - const allIds = itemList.map((item) => item.id); - setSelectedItemIds(new Set(allIds)); - }; - - // 초기화 - const handleResetSelection = () => { - setSelectedItemIds(new Set()); - }; - - // 모달 열릴 때 초기화 또는 수정 데이터 로드 - useEffect(() => { - if (open) { - if (editRule) { - // 수정 모드: 기존 데이터 로드 - setRegistrationType(editRule.registrationType); - setDescription(editRule.description || ''); - setRuleType(editRule.ruleType); - setMatchingType(editRule.matchingType); - setConditionValue(editRule.conditionValue); - setPriority(editRule.priority); - setIsActive(editRule.isActive); - setSearchKeyword(''); - setSelectedItemType('all'); - - // 개별 품목인 경우 선택된 품목 ID 설정 - if (editRule.registrationType === 'individual') { - const ids = editRule.conditionValue.split(',').filter(Boolean); - setSelectedItemIds(new Set(ids)); - } else { - setSelectedItemIds(new Set()); - } - } else { - // 추가 모드: 초기화 (개별 품목을 디폴트로) - setRegistrationType('individual'); - setDescription(''); - setRuleType('품목코드'); - setMatchingType('startsWith'); - setConditionValue(''); - setPriority(10); - setIsActive(true); - setSearchKeyword(''); - setSelectedItemType('all'); - setSelectedItemIds(new Set()); - } - } - }, [open, editRule]); - + // 저장 const handleSubmit = () => { - if (registrationType === 'pattern') { - if (!conditionValue.trim()) { - alert('조건 값을 입력해주세요.'); - return; - } - } else { - if (selectedItemIds.size === 0) { - alert('품목을 최소 1개 이상 선택해주세요.'); - return; - } + if (selectedItemIds.size === 0) { + alert('품목을 최소 1개 이상 선택해주세요.'); + return; } - // 개별 품목의 경우 conditionValue에 품목코드들을 저장 - const finalConditionValue = - registrationType === 'individual' - ? Array.from(selectedItemIds).join(',') - : conditionValue.trim(); + const finalConditionValue = Array.from(selectedItemIds).join(','); onAdd({ - registrationType, - ruleType: registrationType === 'individual' ? '품목코드' : ruleType, - matchingType: registrationType === 'individual' ? 'equals' : matchingType, + registrationType: 'individual', + ruleType: '품목코드', + matchingType: 'equals', conditionValue: finalConditionValue, - priority: registrationType === 'individual' ? 10 : priority, - description: description.trim() || undefined, - isActive: registrationType === 'individual' ? true : isActive, + priority: 10, + description: undefined, + isActive: true, }); - // Reset form - setRegistrationType('pattern'); - setDescription(''); - setRuleType('품목코드'); - setMatchingType('startsWith'); - setConditionValue(''); - setPriority(10); - setIsActive(true); + // Reset setSearchKeyword(''); setSelectedItemType('all'); setSelectedItemIds(new Set()); + setProcessFilter('all'); + setCategoryFilter('all'); onOpenChange(false); }; return ( - + - {editRule ? '규칙 수정' : '규칙 추가'} + 공정 품목 선택
- {/* 등록 방식 */} -
- - setRegistrationType(v as RuleRegistrationType)} - > -
- - -
-
- - -
-
+ {/* 검색 입력 */} +
+ + setSearchKeyword(e.target.value)} + placeholder="품목코드 또는 품목명으로 검색..." + className="pl-9" + />
- {/* 패턴 규칙 UI */} - {registrationType === 'pattern' && ( - <> - {/* 규칙 유형 */} -
- - -
+ {/* 카운트 + 필터 행 */} +
+
+ {isItemsLoading ? ( + '로딩 중...' + ) : ( + <> + 총 {itemList.length}건{' '} + {selectedItemIds.size > 0 && ( + + {selectedItemIds.size}건 선택됨 + + )} + + )} +
+
+ + +
+
- {/* 매칭 방식 */} -
- - -
- - {/* 조건 값 */} -
- -
- setConditionValue(e.target.value)} - placeholder="예: SCR-, E-, STEEL-" - className="flex-1" - /> - -
-

- Enter 키를 누르거나 검색 버튼을 클릭하세요 -

-
- - {/* 우선순위 - 패턴 규칙에서만 표시 */} -
- - setPriority(value ?? 1)} - min={1} - max={100} - className="w-24" - /> -

낮을수록 먼저 적용됩니다

-
- - {/* 설명 - 패턴 규칙 */} -
- - setDescription(e.target.value)} - placeholder="규칙에 대한 설명" - /> -
- - {/* 활성 상태 - 패턴 규칙에서만 표시 */} -
- - -
- - )} - - {/* 개별 품목 UI - 기획서 기준 */} - {registrationType === 'individual' && ( - <> - {/* 설명 (선택) */} -
- - setDescription(e.target.value)} - placeholder="이 품목 그룹에 대한 설명" - /> -
- - {/* 품목 검색 + 품목 유형 필터 */} -
-
- -
- - setSearchKeyword(e.target.value)} - placeholder="품목코드 또는 품목명으로 검색..." - className="pl-9" - /> -
-
-
- - -
-
- - {/* 품목 목록 헤더 */} -
-
- - - {isItemsLoading ? ( - '로딩 중...' - ) : ( - <>품목 목록 ({itemList.length}개) | 선택됨 ({selectedItemIds.size}개) - )} - -
-
- - | - -
-
- - {/* 품목 테이블 */} -
- - - - - 품목코드 - 품목명 - 품목유형 + {/* 품목 테이블 */} +
+
+ + + + 품목유형 + 품목코드 + 품목명 + {/* TODO: API에서 process_name, process_category 필드 지원 후 실제 데이터 표시 */} + 공정 + 구분 + + + + {isItemsLoading ? ( + + + 품목 목록을 불러오는 중... + + + ) : itemList.length === 0 ? ( + + + {searchKeyword.trim() === '' + ? '품목을 검색해주세요 (한글 1자 이상, 영문 2자 이상)' + : '검색 결과가 없습니다'} + + + ) : ( + itemList.map((item) => ( + handleToggleItem(item.id)} + > + + handleToggleItem(item.id)} + onClick={(e) => e.stopPropagation()} + /> + + {item.type} + {item.code} + {item.fullName} + {/* TODO: API 지원 후 item.processName / item.processCategory 표시 */} + - + - - - - {isItemsLoading ? ( - - - 품목 목록을 불러오는 중... - - - ) : itemList.length === 0 ? ( - - - {searchKeyword.trim() === '' - ? '품목을 검색해주세요 (한글 1자 이상, 영문 2자 이상)' - : '검색 결과가 없습니다'} - - - ) : ( - itemList.map((item) => ( - handleToggleItem(item.id)} - > - - handleToggleItem(item.id)} - onClick={(e) => e.stopPropagation()} - /> - - {item.code} - {item.fullName} - {item.type} - - )) - )} - -
-
+ )) + )} + + +
- {/* 안내 문구 */} -

- 이 공정에 배정할 품목을 선택하세요. 다른 공정에 이미 배정된 품목은 표시되지 않습니다. -

- - )} + {/* 안내 문구 */} +

+ 선택 후 저장하시면 선택한 품목들이{' '} + + {processName || '해당'} + {' '} + 공정으로 변경됩니다. +

- +
diff --git a/src/components/process-management/StepForm.tsx b/src/components/process-management/StepForm.tsx index 9898dc18..7f69e8ff 100644 --- a/src/components/process-management/StepForm.tsx +++ b/src/components/process-management/StepForm.tsx @@ -85,7 +85,7 @@ export function StepForm({ mode, processId, initialData }: StepFormProps) { initialData?.needsApproval ? '필요' : '불필요' ); const [needsInspection, setNeedsInspection] = useState( - initialData?.needsInspection ? '필요' : '불필요' + initialData?.needsInspection ? '사용' : '미사용' ); const [isActive, setIsActive] = useState( initialData?.isActive !== false ? '사용' : '미사용' @@ -115,8 +115,8 @@ export function StepForm({ mode, processId, initialData }: StepFormProps) { const [isLoading, setIsLoading] = useState(false); - // 검사여부가 "필요"인지 확인 - const isInspectionEnabled = needsInspection === '필요'; + // 검사여부가 "사용"인지 확인 + const isInspectionEnabled = needsInspection === '사용'; // 제출 const handleSubmit = async (): Promise<{ success: boolean; error?: string }> => { @@ -130,7 +130,7 @@ export function StepForm({ mode, processId, initialData }: StepFormProps) { stepName: stepName.trim(), isRequired: isRequired === '필수', needsApproval: needsApproval === '필요', - needsInspection: needsInspection === '필요', + needsInspection: needsInspection === '사용', isActive: isActive === '사용', order: initialData?.order || 0, connectionType, @@ -232,8 +232,8 @@ export function StepForm({ mode, processId, initialData }: StepFormProps) { - 필요 - 불필요 + 사용 + 미사용
@@ -293,28 +293,6 @@ export function StepForm({ mode, processId, initialData }: StepFormProps) {
- {/* 검사여부가 "필요"일 때 버튼 표시 */} - {isInspectionEnabled && ( - <> - - - - )}
@@ -374,6 +352,29 @@ export function StepForm({ mode, processId, initialData }: StepFormProps) { onCancel={handleCancel} onSubmit={handleSubmit} renderForm={renderFormContent} + headerActions={ + isInspectionEnabled ? ( + <> + + + + ) : undefined + } /> {/* 검사 설정 모달 */} diff --git a/src/components/process-management/actions.ts b/src/components/process-management/actions.ts index 1715318f..215d7284 100644 --- a/src/components/process-management/actions.ts +++ b/src/components/process-management/actions.ts @@ -614,6 +614,9 @@ export interface ItemOption { id: string; fullName: string; type: string; + // TODO: API 응답에 process_name, process_category 필드 추가 후 활성화 + processName?: string; + processCategory?: string; } interface GetItemListParams { @@ -627,6 +630,10 @@ interface GetItemListParams { /** * 품목 목록 조회 (분류 규칙용) * - excludeProcessId: 다른 공정에 이미 배정된 품목 제외 (중복 방지) + * + * TODO: 백엔드 API 수정 요청 + * - 응답에 process_name, process_category 필드 추가 필요 (공정 품목 선택 팝업에서 공정/구분 컬럼 표시용) + * - 파라미터에 processName, processCategory 필터 추가 필요 (공정/구분 필터링용) */ export async function getItemList(params?: GetItemListParams): Promise { try { diff --git a/src/components/production/WorkOrders/WorkOrderCreate.tsx b/src/components/production/WorkOrders/WorkOrderCreate.tsx index 251940fe..1af4ea99 100644 --- a/src/components/production/WorkOrders/WorkOrderCreate.tsx +++ b/src/components/production/WorkOrders/WorkOrderCreate.tsx @@ -11,6 +11,7 @@ import { useRouter } from 'next/navigation'; import { ArrowLeft, FileText, X, Edit2, Loader2 } from 'lucide-react'; 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 { Textarea } from '@/components/ui/textarea'; import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; @@ -455,11 +456,9 @@ export function WorkOrderCreate() {
- setFormData({ ...formData, shipmentDate: e.target.value })} - className="bg-white" + onChange={(date) => setFormData({ ...formData, shipmentDate: date })} />
diff --git a/src/components/production/WorkOrders/WorkOrderDetail.tsx b/src/components/production/WorkOrders/WorkOrderDetail.tsx index d30b378b..79ba1d56 100644 --- a/src/components/production/WorkOrders/WorkOrderDetail.tsx +++ b/src/components/production/WorkOrders/WorkOrderDetail.tsx @@ -369,7 +369,7 @@ export function WorkOrderDetail({ orderId }: WorkOrderDetailProps) {

기본 정보

- {/* 1행: 작업번호 | 수주일 | 공정구분 | 로트번호 */} + {/* 1행: 작업번호 | 수주일 | 공정 | 구분 */}

작업번호

{order.workOrderNo}

@@ -379,15 +379,19 @@ export function WorkOrderDetail({ orderId }: WorkOrderDetailProps) {

{order.salesOrderDate || '-'}

-

공정구분

+

공정

{order.processName}

+
+

구분

+

-

+
+ + {/* 2행: 로트번호 | 수주처 | 현장명 | 수주 담당자 */}

로트번호

{order.lotNo}

- - {/* 2행: 수주처 | 현장명 | 수주 담당자 | 담당자 연락처 */}

수주처

{order.client}

@@ -400,12 +404,12 @@ export function WorkOrderDetail({ orderId }: WorkOrderDetailProps) {

수주 담당자

-

+ + {/* 3행: 담당자 연락처 | 출고예정일 | 틀수 | 우선순위 */}

담당자 연락처

-

- - {/* 3행: 출고예정일 | 틀수 | 우선순위 | 부서 */}

출고예정일

{order.shipmentDate || '-'}

@@ -418,12 +422,12 @@ export function WorkOrderDetail({ orderId }: WorkOrderDetailProps) {

우선순위

{order.priorityLabel || '-'}

+ + {/* 4행: 부서 | 생산 담당자 | 상태 | 비고 */}

부서

{order.department || '-'}

- - {/* 4행: 생산 담당자 | 상태 | 비고 (colspan 2) */}

생산 담당자

@@ -438,7 +442,7 @@ export function WorkOrderDetail({ orderId }: WorkOrderDetailProps) { {WORK_ORDER_STATUS_LABELS[order.status]}

-
+

비고

{order.note || '-'}

diff --git a/src/components/production/WorkOrders/WorkOrderEdit.tsx b/src/components/production/WorkOrders/WorkOrderEdit.tsx index 11b93a4a..9788a28f 100644 --- a/src/components/production/WorkOrders/WorkOrderEdit.tsx +++ b/src/components/production/WorkOrders/WorkOrderEdit.tsx @@ -11,6 +11,7 @@ import { useState, useEffect, useCallback } from 'react'; import { useRouter } from 'next/navigation'; import { Pencil, Trash2 } from 'lucide-react'; import { Input } from '@/components/ui/input'; +import { DatePicker } from '@/components/ui/date-picker'; import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; import { Button } from '@/components/ui/button'; @@ -365,7 +366,7 @@ export function WorkOrderEdit({ orderId }: WorkOrderEditProps) {

기본 정보

- {/* 1행: 작업번호(읽기) | 수주일(읽기) | 공정구분(셀렉트) | 로트번호(읽기) */} + {/* 1행: 작업번호(읽기) | 수주일(읽기) | 공정(셀렉트) | 구분(읽기) */}
@@ -375,7 +376,7 @@ export function WorkOrderEdit({ orderId }: WorkOrderEditProps) {
- +
+
+ + +
+ + {/* 2행: 로트번호(읽기) | 수주처(읽기) | 현장명(입력) | 수주 담당자(읽기) */}
- - {/* 2행: 수주처(읽기) | 현장명(입력) | 수주 담당자(읽기) | 담당자 연락처(읽기) */}
@@ -415,19 +420,17 @@ export function WorkOrderEdit({ orderId }: WorkOrderEditProps) {
+ + {/* 3행: 담당자 연락처(읽기) | 출고예정일(입력) | 틀수(읽기) | 우선순위(셀렉트) */}
- - {/* 3행: 출고예정일(입력) | 틀수(읽기) | 우선순위(셀렉트) | 부서(읽기) */}
- setFormData({ ...formData, scheduledDate: e.target.value })} - className="bg-white" + onChange={(date) => setFormData({ ...formData, scheduledDate: date })} />
@@ -452,12 +455,12 @@ export function WorkOrderEdit({ orderId }: WorkOrderEditProps) {
+ + {/* 4행: 부서(읽기) | 생산 담당자(선택) | 상태(읽기) | 비고(입력) */}
- - {/* 4행: 생산 담당자(선택) | 상태(읽기) | 비고(입력, colspan 2) */}
상태
-
+