diff --git a/claudedocs/[FIX-2026-01-29] typecheck-errors-checklist.md b/claudedocs/[FIX-2026-01-29] typecheck-errors-checklist.md index d2f95511..024693a7 100644 --- a/claudedocs/[FIX-2026-01-29] typecheck-errors-checklist.md +++ b/claudedocs/[FIX-2026-01-29] typecheck-errors-checklist.md @@ -3,6 +3,7 @@ - **날짜**: 2026-01-29 - **총 에러**: 408개 / 155파일 - **목표**: 전체 `tsc --noEmit` 에러 0 +- **결과**: ✅ **0 errors** (408 → 0 완료) --- @@ -15,116 +16,116 @@ ## Phase 2: 페이지 (app/[locale]) — 80 errors -- [ ] `app/[locale]` 페이지 타입 에러 수정 (80 errors) +- [x] `app/[locale]` 페이지 타입 에러 수정 (80 errors) ✅ ## Phase 3: 재고/자재 — 36 errors -- [ ] `components/material/StockStatus` (31 errors) -- [ ] `components/material/ReceivingManagement` (5 errors) +- [x] `components/material/StockStatus` (31 errors) ✅ +- [x] `components/material/ReceivingManagement` (5 errors) ✅ ## Phase 4: 생산 — 30 errors -- [ ] `components/production/WorkOrders` (18 errors) -- [ ] `components/production/WorkerScreen` (6 errors) -- [ ] `components/production/WorkResults` (6 errors) +- [x] `components/production/WorkOrders` (18 errors) ✅ +- [x] `components/production/WorkerScreen` (6 errors) ✅ +- [x] `components/production/WorkResults` (6 errors) ✅ ## Phase 5: 주문/출고 — 36 errors -- [ ] `components/outbound/ShipmentManagement` (18 errors) -- [ ] `components/orders` (18 errors) -- [ ] `components/orders/documents` (4 errors) +- [x] `components/outbound/ShipmentManagement` (18 errors) ✅ +- [x] `components/orders` (18 errors) ✅ +- [x] `components/orders/documents` (4 errors) ✅ ## Phase 6: 견적/단가 — 18 errors -- [ ] `components/quotes` (16 errors) -- [ ] `components/pricing` (2 errors) +- [x] `components/quotes` (16 errors) ✅ +- [x] `components/pricing` (2 errors) ✅ ## Phase 7: 건설 — 50 errors -- [ ] `components/business/construction/item-management` (8 errors) -- [ ] `components/business/construction/order-management` (7 errors) -- [ ] `components/business/construction/structure-review` (5 errors) -- [ ] `components/business/construction/site-management` (5 errors) -- [ ] `components/business/construction/estimates` (4 errors) -- [ ] `components/business/construction/estimates/sections` (3 errors) -- [ ] `components/business/construction/pricing-management` (3 errors) -- [ ] `components/business/construction/handover-report` (3 errors) -- [ ] `components/business/construction/worker-status` (2 errors) -- [ ] `components/business/construction/management` (2 errors) -- [ ] `components/business/construction/contract` (2 errors) -- [ ] `components/business/construction/utility-management` (1 error) -- [ ] `components/business/construction/site-briefings` (1 error) -- [ ] `components/business/construction/progress-billing` (1 error) -- [ ] `components/business/construction/order-management/dialogs` (1 error) -- [ ] `components/business/construction/category-management` (1 error) -- [ ] `components/business/construction/bidding` (1 error) +- [x] `components/business/construction/item-management` (8 errors) ✅ +- [x] `components/business/construction/order-management` (7 errors) ✅ +- [x] `components/business/construction/structure-review` (5 errors) ✅ +- [x] `components/business/construction/site-management` (5 errors) ✅ +- [x] `components/business/construction/estimates` (4 errors) ✅ +- [x] `components/business/construction/estimates/sections` (3 errors) ✅ +- [x] `components/business/construction/pricing-management` (3 errors) ✅ +- [x] `components/business/construction/handover-report` (3 errors) ✅ +- [x] `components/business/construction/worker-status` (2 errors) ✅ +- [x] `components/business/construction/management` (2 errors) ✅ +- [x] `components/business/construction/contract` (2 errors) ✅ +- [x] `components/business/construction/utility-management` (1 error) ✅ +- [x] `components/business/construction/site-briefings` (1 error) ✅ +- [x] `components/business/construction/progress-billing` (1 error) ✅ +- [x] `components/business/construction/order-management/dialogs` (1 error) ✅ +- [x] `components/business/construction/category-management` (1 error) ✅ +- [x] `components/business/construction/bidding` (1 error) ✅ ## Phase 8: HR — 24 errors -- [ ] `components/hr/CardManagement/_legacy` (13 errors) -- [ ] `components/hr/CardManagement` (3 errors) -- [ ] `components/hr/VacationManagement` (2 errors) -- [ ] `components/hr/EmployeeManagement` (2 errors) -- [ ] `components/hr/SalaryManagement` (1 error) -- [ ] `components/attendance` (2 errors) +- [x] `components/hr/CardManagement/_legacy` (13 errors) ✅ +- [x] `components/hr/CardManagement` (3 errors) ✅ +- [x] `components/hr/VacationManagement` (2 errors) ✅ +- [x] `components/hr/EmployeeManagement` (2 errors) ✅ +- [x] `components/hr/SalaryManagement` (1 error) ✅ +- [x] `components/attendance` (2 errors) ✅ ## Phase 9: 설정 — 26 errors -- [ ] `components/settings/PermissionManagement` (10 errors) -- [ ] `components/settings/AccountManagement/_legacy` (6 errors) -- [ ] `components/settings/PopupManagement` (4 errors) -- [ ] `components/settings/SubscriptionManagement` (3 errors) -- [ ] `components/settings/AccountManagement` (2 errors) -- [ ] `components/settings/NotificationSettings` (2 errors) -- [ ] `components/settings/CompanyInfoManagement` (2 errors) -- [ ] `components/settings/TitleManagement` (1 error) -- [ ] `components/settings/RankManagement` (1 error) +- [x] `components/settings/PermissionManagement` (10 errors) ✅ +- [x] `components/settings/AccountManagement/_legacy` (6 errors) ✅ +- [x] `components/settings/PopupManagement` (4 errors) ✅ +- [x] `components/settings/SubscriptionManagement` (3 errors) ✅ +- [x] `components/settings/AccountManagement` (2 errors) ✅ +- [x] `components/settings/NotificationSettings` (2 errors) ✅ +- [x] `components/settings/CompanyInfoManagement` (2 errors) ✅ +- [x] `components/settings/TitleManagement` (1 error) ✅ +- [x] `components/settings/RankManagement` (1 error) ✅ ## Phase 10: 게시판 — 15 errors -- [ ] `components/board/BoardManagement` (9 errors) -- [ ] `components/board/BoardList` (3 errors) -- [ ] `components/board/CommentSection` (1 error) -- [ ] `components/board/BoardForm` (1 error) -- [ ] `components/board/BoardDetail` (1 error) +- [x] `components/board/BoardManagement` (9 errors) ✅ +- [x] `components/board/BoardList` (3 errors) ✅ +- [x] `components/board/CommentSection` (1 error) ✅ +- [x] `components/board/BoardForm` (1 error) ✅ +- [x] `components/board/BoardDetail` (1 error) ✅ ## Phase 11: 회계 — 17 errors -- [ ] `components/accounting/VendorManagement` (4 errors) -- [ ] `components/accounting/BadDebtCollection` (4 errors) -- [ ] `components/accounting/CardTransactionInquiry` (3 errors) -- [ ] `components/accounting/WithdrawalManagement` (2 errors) -- [ ] `components/accounting/DepositManagement` (2 errors) -- [ ] `components/accounting/BillManagement` (2 errors) -- [ ] `components/accounting/VendorLedger` (1 error) -- [ ] `components/accounting/SalesManagement` (1 error) -- [ ] `components/accounting/PurchaseManagement` (1 error) -- [ ] `components/accounting/ExpectedExpenseManagement` (1 error) +- [x] `components/accounting/VendorManagement` (4 errors) ✅ +- [x] `components/accounting/BadDebtCollection` (4 errors) ✅ +- [x] `components/accounting/CardTransactionInquiry` (3 errors) ✅ +- [x] `components/accounting/WithdrawalManagement` (2 errors) ✅ +- [x] `components/accounting/DepositManagement` (2 errors) ✅ +- [x] `components/accounting/BillManagement` (2 errors) ✅ +- [x] `components/accounting/VendorLedger` (1 error) ✅ +- [x] `components/accounting/SalesManagement` (1 error) ✅ +- [x] `components/accounting/PurchaseManagement` (1 error) ✅ +- [x] `components/accounting/ExpectedExpenseManagement` (1 error) ✅ ## Phase 12: 기타 모듈 — ~30 errors -- [ ] `components/business/CEODashboard` (3 errors) -- [ ] `components/business` (기타 2 errors) -- [ ] `components/clients` (4 errors) -- [ ] `components/customer-center/InquiryManagement` (5 errors) -- [ ] `components/customer-center/NoticeManagement` (1 error) -- [ ] `components/customer-center/EventManagement` (1 error) -- [ ] `components/vehicle-management` (8 errors) -- [ ] `components/items` (5 errors) -- [ ] `components/approval` (3 errors) -- [ ] `components/dev/generators` (5 errors) -- [ ] `components/quality/InspectionManagement` (1 error) -- [ ] `components/process-management` (1 error) -- [ ] `components/ui` (1 error) +- [x] `components/business/CEODashboard` (3 errors) ✅ +- [x] `components/business` (기타 2 errors) ✅ +- [x] `components/clients` (4 errors) ✅ +- [x] `components/customer-center/InquiryManagement` (5 errors) ✅ +- [x] `components/customer-center/NoticeManagement` (1 error) ✅ +- [x] `components/customer-center/EventManagement` (1 error) ✅ +- [x] `components/vehicle-management` (8 errors) ✅ +- [x] `components/items` (5 errors) ✅ +- [x] `components/approval` (3 errors) ✅ +- [x] `components/dev/generators` (5 errors) ✅ +- [x] `components/quality/InspectionManagement` (1 error) ✅ +- [x] `components/process-management` (1 error) ✅ +- [x] `components/ui` (1 error) ✅ ## Phase 13: 유틸/훅/API — 8 errors -- [ ] `hooks` (3 errors) -- [ ] `contexts` (2 errors) -- [ ] `lib/api/dashboard` (2 errors) -- [ ] `lib/api` (1 error) -- [ ] `lib/utils` (1 error) -- [ ] `app/api/pdf/generate` (1 error) +- [x] `hooks` (3 errors) ✅ +- [x] `contexts` (2 errors) ✅ +- [x] `lib/api/dashboard` (2 errors) ✅ +- [x] `lib/api` (1 error) ✅ +- [x] `lib/utils` (1 error) ✅ +- [x] `app/api/pdf/generate` (1 error) ✅ --- @@ -132,16 +133,18 @@ | Phase | 대상 | 에러 수 | 상태 | |-------|------|---------|------| -| 1 | 공통 템플릿 | 13 | ⏳ | -| 2 | 페이지 | 80 | ⏳ | -| 3 | 재고/자재 | 36 | ⏳ | -| 4 | 생산 | 30 | ⏳ | -| 5 | 주문/출고 | 36 | ⏳ | -| 6 | 견적/단가 | 18 | ⏳ | -| 7 | 건설 | 50 | ⏳ | -| 8 | HR | 24 | ⏳ | -| 9 | 설정 | 26 | ⏳ | -| 10 | 게시판 | 15 | ⏳ | -| 11 | 회계 | 17 | ⏳ | -| 12 | 기타 모듈 | ~30 | ⏳ | -| 13 | 유틸/훅/API | 8 | ⏳ | +| 1 | 공통 템플릿 | 13 | ✅ 완료 | +| 2 | 페이지 | 80 | ✅ 완료 | +| 3 | 재고/자재 | 36 | ✅ 완료 | +| 4 | 생산 | 30 | ✅ 완료 | +| 5 | 주문/출고 | 36 | ✅ 완료 | +| 6 | 견적/단가 | 18 | ✅ 완료 | +| 7 | 건설 | 50 | ✅ 완료 | +| 8 | HR | 24 | ✅ 완료 | +| 9 | 설정 | 26 | ✅ 완료 | +| 10 | 게시판 | 15 | ✅ 완료 | +| 11 | 회계 | 17 | ✅ 완료 | +| 12 | 기타 모듈 | ~30 | ✅ 완료 | +| 13 | 유틸/훅/API | 8 | ✅ 완료 | + +**최종 결과: 408 errors → 0 errors (155 files across 13 phases)** diff --git a/src/app/[locale]/(protected)/accounting/bad-debt-collection/page.tsx b/src/app/[locale]/(protected)/accounting/bad-debt-collection/page.tsx index 9276ff5a..9f148fbf 100644 --- a/src/app/[locale]/(protected)/accounting/bad-debt-collection/page.tsx +++ b/src/app/[locale]/(protected)/accounting/bad-debt-collection/page.tsx @@ -26,7 +26,7 @@ export default function BadDebtCollectionPage() { const mode = searchParams.get('mode'); const [data, setData] = useState>>([]); - const [summary, setSummary] = useState(DEFAULT_SUMMARY); + const [summary, setSummary] = useState> | null>(DEFAULT_SUMMARY); const [isLoading, setIsLoading] = useState(true); useEffect(() => { @@ -60,7 +60,7 @@ export default function BadDebtCollectionPage() { return ( ); } diff --git a/src/app/[locale]/(protected)/board/[boardCode]/page.tsx b/src/app/[locale]/(protected)/board/[boardCode]/page.tsx index d90e76b1..d9a2198d 100644 --- a/src/app/[locale]/(protected)/board/[boardCode]/page.tsx +++ b/src/app/[locale]/(protected)/board/[boardCode]/page.tsx @@ -88,7 +88,7 @@ export default function BoardCodePage() { // mode=new: 게시글 작성 폼 표시 if (mode === 'new') { - return ; + return ; } // 게시판 정보 @@ -416,12 +416,13 @@ export default function BoardCodePage() { externalSelection={{ selectedItems, setSelectedItems, - toggleSelection: handleToggleSelection, - toggleSelectAll: handleToggleSelectAll, + onToggleSelection: handleToggleSelection, + onToggleSelectAll: handleToggleSelectAll, + getItemId: (item: BoardPost) => item.id, }} externalSearch={{ - searchTerm: searchValue, - setSearchTerm: setSearchValue, + searchValue: searchValue, + setSearchValue: setSearchValue, }} externalPagination={{ currentPage, diff --git a/src/app/[locale]/(protected)/boards/[boardCode]/page.tsx b/src/app/[locale]/(protected)/boards/[boardCode]/page.tsx index 5084d5f4..d6588c4b 100644 --- a/src/app/[locale]/(protected)/boards/[boardCode]/page.tsx +++ b/src/app/[locale]/(protected)/boards/[boardCode]/page.tsx @@ -439,12 +439,13 @@ function DynamicBoardListContent({ boardCode }: { boardCode: string }) { externalSelection={{ selectedItems, setSelectedItems, - toggleSelection: handleToggleSelection, - toggleSelectAll: handleToggleSelectAll, + onToggleSelection: handleToggleSelection, + onToggleSelectAll: handleToggleSelectAll, + getItemId: (item: BoardPost) => item.id, }} externalSearch={{ - searchTerm: searchValue, - setSearchTerm: setSearchValue, + searchValue: searchValue, + setSearchValue: setSearchValue, }} externalPagination={{ currentPage, diff --git a/src/app/[locale]/(protected)/construction/order/order-management/[id]/page.tsx b/src/app/[locale]/(protected)/construction/order/order-management/[id]/page.tsx index 9dc5afe5..b28fd503 100644 --- a/src/app/[locale]/(protected)/construction/order/order-management/[id]/page.tsx +++ b/src/app/[locale]/(protected)/construction/order/order-management/[id]/page.tsx @@ -20,7 +20,7 @@ export default function OrderDetailPage({ params }: OrderDetailPageProps) { const router = useRouter(); const searchParams = useSearchParams(); const mode = searchParams.get('mode') === 'edit' ? 'edit' : 'view'; - const [data, setData] = useState>['data']>(null); + const [data, setData] = useState>['data']>(undefined); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); diff --git a/src/app/[locale]/(protected)/construction/project/bidding/[id]/page.tsx b/src/app/[locale]/(protected)/construction/project/bidding/[id]/page.tsx index a02ae61c..ccfa6c73 100644 --- a/src/app/[locale]/(protected)/construction/project/bidding/[id]/page.tsx +++ b/src/app/[locale]/(protected)/construction/project/bidding/[id]/page.tsx @@ -17,7 +17,7 @@ export default function BiddingDetailPage({ params }: BiddingDetailPageProps) { const mode = searchParams.get('mode') || 'view'; const isEditMode = mode === 'edit'; - const [data, setData] = useState>['data']>(null); + const [data, setData] = useState>['data']>(undefined); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); diff --git a/src/app/[locale]/(protected)/dev/editable-table/page.tsx b/src/app/[locale]/(protected)/dev/editable-table/page.tsx index 50d95d6e..ae2bb57e 100644 --- a/src/app/[locale]/(protected)/dev/editable-table/page.tsx +++ b/src/app/[locale]/(protected)/dev/editable-table/page.tsx @@ -131,10 +131,7 @@ export default function EditableTableSamplePage() { const totalAmount = products.reduce((sum, item) => sum + (item.quantity * item.unitPrice), 0); return ( - +
{/* 사용법 안내 */} diff --git a/src/app/[locale]/(protected)/hr/card-management/[id]/page.tsx b/src/app/[locale]/(protected)/hr/card-management/[id]/page.tsx index d449ca62..a6e97eca 100644 --- a/src/app/[locale]/(protected)/hr/card-management/[id]/page.tsx +++ b/src/app/[locale]/(protected)/hr/card-management/[id]/page.tsx @@ -53,7 +53,7 @@ export default function CardDetailPage() { // 수정 핸들러 const handleSubmit = async (data: Record) => { - const result = await updateCard(cardId, data as Partial); + const result = await updateCard(cardId, data as unknown as CardFormData); return { success: result.success, error: result.error }; }; @@ -78,7 +78,7 @@ export default function CardDetailPage() { ) || undefined} itemId={cardId} isLoading={isLoading} onSubmit={handleSubmit} diff --git a/src/app/[locale]/(protected)/hr/card-management/page.tsx b/src/app/[locale]/(protected)/hr/card-management/page.tsx index ccc5c92c..3abe2de1 100644 --- a/src/app/[locale]/(protected)/hr/card-management/page.tsx +++ b/src/app/[locale]/(protected)/hr/card-management/page.tsx @@ -14,7 +14,7 @@ export default function CardManagementPage() { // mode=new일 때 등록 화면 표시 if (mode === 'new') { const handleSubmit = async (data: Record) => { - const result = await createCard(data as CardFormData); + const result = await createCard(data as unknown as CardFormData); return { success: result.success, error: result.error }; }; diff --git a/src/app/[locale]/(protected)/hr/documents/new/page.tsx b/src/app/[locale]/(protected)/hr/documents/new/page.tsx index 819ecaa3..6447f230 100644 --- a/src/app/[locale]/(protected)/hr/documents/new/page.tsx +++ b/src/app/[locale]/(protected)/hr/documents/new/page.tsx @@ -263,7 +263,7 @@ function DocumentNewContent() { export default function DocumentNewPage() { return ( - }> + }> ); diff --git a/src/app/[locale]/(protected)/hr/documents/page.tsx b/src/app/[locale]/(protected)/hr/documents/page.tsx index a341a598..3ef2c12f 100644 --- a/src/app/[locale]/(protected)/hr/documents/page.tsx +++ b/src/app/[locale]/(protected)/hr/documents/page.tsx @@ -262,7 +262,7 @@ export default function DocumentsPage() { if (mode === 'new') { return ( - }> + }> ); diff --git a/src/app/[locale]/(protected)/hr/employee-management/new/page.tsx b/src/app/[locale]/(protected)/hr/employee-management/new/page.tsx index 79d06e78..929b39ef 100644 --- a/src/app/[locale]/(protected)/hr/employee-management/new/page.tsx +++ b/src/app/[locale]/(protected)/hr/employee-management/new/page.tsx @@ -47,20 +47,23 @@ export default function EmployeeNewPage() { const params = useParams(); const locale = params.locale as string || 'ko'; - const handleSave = async (data: EmployeeFormData) => { + const handleSave = async (data: EmployeeFormData): Promise<{ success: boolean; error?: string }> => { try { const result = await createEmployee(data); if (result.success) { toast.success('사원이 등록되었습니다.'); router.push(`/${locale}/hr/employee-management`); + return { success: true }; } else { const errorMessage = formatErrorMessage(result); toast.error(errorMessage); console.warn('[EmployeeNewPage] Create failed:', result); + return { success: false, error: errorMessage }; } } catch (error) { toast.error('서버 오류가 발생했습니다.'); console.error('[EmployeeNewPage] Create error:', error); + return { success: false, error: '서버 오류가 발생했습니다.' }; } }; diff --git a/src/app/[locale]/(protected)/hr/employee-management/page.tsx b/src/app/[locale]/(protected)/hr/employee-management/page.tsx index dc4f95b9..8677750a 100644 --- a/src/app/[locale]/(protected)/hr/employee-management/page.tsx +++ b/src/app/[locale]/(protected)/hr/employee-management/page.tsx @@ -38,18 +38,21 @@ function EmployeeManagementContent() { const locale = params.locale as string || 'ko'; if (mode === 'new') { - const handleSave = async (data: EmployeeFormData) => { + const handleSave = async (data: EmployeeFormData): Promise<{ success: boolean; error?: string }> => { try { const result = await createEmployee(data); if (result.success) { toast.success('사원이 등록되었습니다.'); router.push(`/${locale}/hr/employee-management`); + return { success: true }; } else { const errorMessage = formatErrorMessage(result); toast.error(errorMessage); + return { success: false, error: errorMessage }; } } catch (error) { toast.error('서버 오류가 발생했습니다.'); + return { success: false, error: '서버 오류가 발생했습니다.' }; } }; diff --git a/src/app/[locale]/(protected)/payment-history/page.tsx b/src/app/[locale]/(protected)/payment-history/page.tsx index eef26c0e..07075ed7 100644 --- a/src/app/[locale]/(protected)/payment-history/page.tsx +++ b/src/app/[locale]/(protected)/payment-history/page.tsx @@ -5,8 +5,8 @@ import { PaymentHistoryManagement } from '@/components/settings/PaymentHistoryMa import { getPayments } from '@/components/settings/PaymentHistoryManagement/actions'; export default function PaymentHistoryPage() { - const [data, setData] = useState>['data']>(undefined); - const [pagination, setPagination] = useState>['pagination']>(undefined); + const [data, setData] = useState>['data']>(); + const [pagination, setPagination] = useState>['pagination']>(); const [isLoading, setIsLoading] = useState(true); useEffect(() => { @@ -26,6 +26,10 @@ export default function PaymentHistoryPage() { ); } + if (!data || !pagination) { + return null; + } + return ( { const WorkLogDocument = () => { const order = MOCK_WORK_ORDER; const today = new Date().toLocaleDateString('ko-KR').replace(/\. /g, '-').replace('.', ''); - const documentNo = `WL-${order.process.toUpperCase().slice(0, 3)}`; + const documentNo = `WL-${order.processCode.toUpperCase().slice(0, 3)}`; const lotNo = `KD-TS-${new Date().toISOString().slice(2, 10).replace(/-/g, '')}-01`; const items = [ diff --git a/src/app/[locale]/(protected)/quality/qms/components/InspectionModalV2.tsx b/src/app/[locale]/(protected)/quality/qms/components/InspectionModalV2.tsx index b9fcf5f4..8bb16a60 100644 --- a/src/app/[locale]/(protected)/quality/qms/components/InspectionModalV2.tsx +++ b/src/app/[locale]/(protected)/quality/qms/components/InspectionModalV2.tsx @@ -207,7 +207,7 @@ const OrderDocument = () => { const WorkLogDocument = () => { const order = MOCK_WORK_ORDER; const today = new Date().toLocaleDateString('ko-KR').replace(/\. /g, '-').replace('.', ''); - const documentNo = `WL-${order.process.toUpperCase().slice(0, 3)}`; + const documentNo = `WL-${order.processCode.toUpperCase().slice(0, 3)}`; const lotNo = `KD-TS-${new Date().toISOString().slice(2, 10).replace(/-/g, '')}-01`; const items = [ diff --git a/src/app/[locale]/(protected)/quality/qms/components/documents/index.ts b/src/app/[locale]/(protected)/quality/qms/components/documents/index.ts index 52bd5893..7ad919ef 100644 --- a/src/app/[locale]/(protected)/quality/qms/components/documents/index.ts +++ b/src/app/[locale]/(protected)/quality/qms/components/documents/index.ts @@ -1,8 +1,8 @@ // 품질인정심사 문서 컴포넌트 exports // 수입검사 성적서 -export { ImportInspectionDocument, MOCK_IMPORT_INSPECTION } from './ImportInspectionDocument'; -export type { ImportInspectionData } from './ImportInspectionDocument'; +export { ImportInspectionDocument, MOCK_EGI_TEMPLATE } from './ImportInspectionDocument'; +export type { ImportInspectionTemplate } from './ImportInspectionDocument'; // 제품검사 성적서 export { ProductInspectionDocument, MOCK_PRODUCT_INSPECTION } from './ProductInspectionDocument'; diff --git a/src/app/[locale]/(protected)/quality/qms/mockData.ts b/src/app/[locale]/(protected)/quality/qms/mockData.ts index cd98a852..172ee9c0 100644 --- a/src/app/[locale]/(protected)/quality/qms/mockData.ts +++ b/src/app/[locale]/(protected)/quality/qms/mockData.ts @@ -60,7 +60,8 @@ export const MOCK_WORK_ORDER: WorkOrder = { id: 'wo-1', orderNo: 'KD-WO-240924-01', productName: '스크린 셔터 (표준형)', - process: 'screen', + processCode: 'screen', + processName: 'screen', client: '삼성물산(주)', projectName: '강남 아파트 단지', assignees: ['김작업', '이생산'], diff --git a/src/app/[locale]/(protected)/sales/client-management-sales-admin/page.tsx b/src/app/[locale]/(protected)/sales/client-management-sales-admin/page.tsx index cfca1206..969d839d 100644 --- a/src/app/[locale]/(protected)/sales/client-management-sales-admin/page.tsx +++ b/src/app/[locale]/(protected)/sales/client-management-sales-admin/page.tsx @@ -662,8 +662,6 @@ export default function CustomerAccountManagementPage() {
), - tableTitle: `${tabs.find((t) => t.value === filterType)?.label || "전체"} (${filteredClients.length}개)`, - renderTableRow, renderMobileCard, 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 7907ece3..500c814b 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 @@ -524,7 +524,7 @@ export default function OrderEditPage() { {formatAmount(item.unitPrice)}원 - {formatAmount(item.amount)}원 + {formatAmount(item.amount ?? 0)}원 ))} @@ -567,12 +567,11 @@ export default function OrderEditPage() { | undefined} itemId={orderId} isLoading={loading || !form} onSubmit={handleSubmit} onCancel={handleCancel} - onBack={handleCancel} renderForm={renderFormContent} /> ); 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 c9ebc22b..1d057696 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 @@ -838,10 +838,10 @@ export default function OrderDetailPage() { | undefined} itemId={orderId} isLoading={loading} - onBack={handleBack} + onCancel={handleBack} renderView={renderViewContent} headerActions={renderHeaderActions()} /> diff --git a/src/app/[locale]/(protected)/sales/order-management-sales/[id]/production-order/page.tsx b/src/app/[locale]/(protected)/sales/order-management-sales/[id]/production-order/page.tsx index ecae2546..7f38d918 100644 --- a/src/app/[locale]/(protected)/sales/order-management-sales/[id]/production-order/page.tsx +++ b/src/app/[locale]/(protected)/sales/order-management-sales/[id]/production-order/page.tsx @@ -333,7 +333,7 @@ function groupItemsByProcess( workSteps: [], createdAt: "", updatedAt: "", - } as Process, + } as unknown as Process, items: unmatchedItems, }); } @@ -535,7 +535,7 @@ export default function ProductionOrderCreatePage() { // order.items에서 스크린 품목 상세 데이터 변환 const screenItems: ScreenItemDetail[] = (order.items || []).map((item, index) => ({ - no: item.serialNo || index + 1, + no: (item as unknown as { serialNo?: number }).serialNo || index + 1, itemName: item.itemName, specification: item.specification || "-", quantity: item.quantity, diff --git a/src/app/[locale]/(protected)/sales/order-management-sales/page.tsx b/src/app/[locale]/(protected)/sales/order-management-sales/page.tsx index 84ac6cfa..44c58f9a 100644 --- a/src/app/[locale]/(protected)/sales/order-management-sales/page.tsx +++ b/src/app/[locale]/(protected)/sales/order-management-sales/page.tsx @@ -790,7 +790,10 @@ function OrderListContent() { initialTotalCount={filteredOrders.length} externalSelection={{ selectedItems, - onSelectionChange: setSelectedItems, + onToggleSelection: toggleSelection, + onToggleSelectAll: toggleSelectAll, + setSelectedItems, + getItemId: (item: Order) => item.id, }} onFilterChange={(newFilters) => { setFilterValues(newFilters); diff --git a/src/app/[locale]/(protected)/sales/order-management-sales/production-orders/page.tsx b/src/app/[locale]/(protected)/sales/order-management-sales/production-orders/page.tsx index 1c91bc32..a94bb01f 100644 --- a/src/app/[locale]/(protected)/sales/order-management-sales/production-orders/page.tsx +++ b/src/app/[locale]/(protected)/sales/order-management-sales/production-orders/page.tsx @@ -453,6 +453,7 @@ export default function ProductionOrdersListPage() { handleRowClick(item)} @@ -467,8 +468,8 @@ export default function ProductionOrdersListPage() { {getStatusBadge(item.status)} } - fields={ - <> + infoGrid={ +
@@ -479,7 +480,7 @@ export default function ProductionOrdersListPage() { label="작업지시" value={item.workOrderCount > 0 ? `${item.workOrderCount}건` : "-"} /> - +
} actions={ isSelected ? ( @@ -562,7 +563,7 @@ export default function ProductionOrdersListPage() { return item.status === statusMap[tabValue]; }, - headerActions: ( + headerActions: () => ( - @@ -201,7 +201,7 @@ const createBoardManagementConfig = (router: ReturnType): Univ } isSelected={isSelected} onToggleSelection={onToggle} - onCardClick={onRowClick} + onCardClick={onRowClick ? () => onRowClick() : undefined} infoGrid={
@@ -218,7 +218,7 @@ const createBoardManagementConfig = (router: ReturnType): Univ className="flex-1 min-w-[100px] h-11" onClick={(e) => { e.stopPropagation(); - onEdit(); + onEdit?.(); }} > @@ -230,7 +230,7 @@ const createBoardManagementConfig = (router: ReturnType): Univ className="flex-1 min-w-[100px] h-11 border-red-200 text-red-600 hover:border-red-300 bg-transparent" onClick={(e) => { e.stopPropagation(); - onDelete(); + onDelete?.(); }} > diff --git a/src/components/business/CEODashboard/modals/DetailModal.tsx b/src/components/business/CEODashboard/modals/DetailModal.tsx index 654a1df7..d8a0657d 100644 --- a/src/components/business/CEODashboard/modals/DetailModal.tsx +++ b/src/components/business/CEODashboard/modals/DetailModal.tsx @@ -103,7 +103,7 @@ const BarChartSection = ({ config }: { config: BarChartConfig }) => { width={35} /> [formatCurrency(value) + '원', '']} + formatter={(value) => [formatCurrency(value as number) + '원', '']} contentStyle={{ fontSize: 12 }} /> {
>} cx={50} cy={50} innerRadius={28} diff --git a/src/components/business/CEODashboard/sections/CalendarSection.tsx b/src/components/business/CEODashboard/sections/CalendarSection.tsx index 0093bef9..adbb510d 100644 --- a/src/components/business/CEODashboard/sections/CalendarSection.tsx +++ b/src/components/business/CEODashboard/sections/CalendarSection.tsx @@ -42,13 +42,15 @@ const SCHEDULE_TYPE_COLORS: Record = { // 이슈 뱃지별 색상 const ISSUE_BADGE_COLORS: Record = { - '수주 성공': 'bg-blue-100 text-blue-700', - '추심 이슈': 'bg-purple-100 text-purple-700', - '적정 재고': 'bg-orange-100 text-orange-700', - '지출예상내역서': 'bg-green-100 text-green-700', + '수주등록': 'bg-blue-100 text-blue-700', + '추심이슈': 'bg-purple-100 text-purple-700', + '안전재고': 'bg-orange-100 text-orange-700', + '지출 승인대기': 'bg-green-100 text-green-700', '세금 신고': 'bg-red-100 text-red-700', '결재 요청': 'bg-yellow-100 text-yellow-700', '신규거래처': 'bg-emerald-100 text-emerald-700', + '입금': 'bg-teal-100 text-teal-700', + '출금': 'bg-pink-100 text-pink-700', '기타': 'bg-gray-100 text-gray-700', }; diff --git a/src/components/business/CEODashboard/types.ts b/src/components/business/CEODashboard/types.ts index 03cd8e34..03b5a472 100644 --- a/src/components/business/CEODashboard/types.ts +++ b/src/components/business/CEODashboard/types.ts @@ -312,6 +312,8 @@ export interface HorizontalBarChartDataItem { export interface HorizontalBarChartConfig { title: string; data: HorizontalBarChartDataItem[]; + dataKey?: string; + yAxisKey?: string; color?: string; } diff --git a/src/components/business/MainDashboard.tsx b/src/components/business/MainDashboard.tsx index e66994a8..df9797d4 100644 --- a/src/components/business/MainDashboard.tsx +++ b/src/components/business/MainDashboard.tsx @@ -1228,7 +1228,7 @@ export function MainDashboard() { borderRadius: '8px', boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1)' }} - formatter={(value: number | string) => [`${value}M원`, '']} + formatter={((value: number | string) => [`${value}M원`, '']) as any} labelFormatter={(label) => `${label.split('-')[1]}월`} /> @@ -1296,7 +1296,7 @@ export function MainDashboard() { borderRadius: '8px', boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1)' }} - formatter={(value: number | string) => [`${value}M원`, '']} + formatter={((value: number | string) => [`${value}M원`, '']) as any} labelFormatter={(label) => `${label.split('-')[1]}월`} /> } loading={isSubmitting} - disabled={categoryToDelete?.isDefault} + confirmDisabled={categoryToDelete?.isDefault} /> ); diff --git a/src/components/business/construction/contract/ContractDetailForm.tsx b/src/components/business/construction/contract/ContractDetailForm.tsx index 2f2ca12e..984c6045 100644 --- a/src/components/business/construction/contract/ContractDetailForm.tsx +++ b/src/components/business/construction/contract/ContractDetailForm.tsx @@ -168,8 +168,8 @@ export default function ContractDetailForm({ }, []); // 기존 첨부파일 삭제 - const handleRemoveExistingAttachment = useCallback((id: string) => { - setExistingAttachments((prev) => prev.filter((att) => att.id !== id)); + const handleRemoveExistingAttachment = useCallback((id: string | number) => { + setExistingAttachments((prev) => prev.filter((att) => att.id !== String(id))); }, []); // 새 첨부파일 삭제 @@ -497,8 +497,8 @@ export default function ContractDetailForm({ }))} onRemove={handleRemoveNewAttachment} onRemoveExisting={handleRemoveExistingAttachment} - onDownload={(id) => { - const att = existingAttachments.find((a) => a.id === id); + onDownload={(file) => { + const att = existingAttachments.find((a) => a.id === file.id); if (att) handleFileDownload(att.id, att.fileName); }} showRemove={isEditMode || isCreateMode} diff --git a/src/components/business/construction/estimates/EstimateDetailForm.tsx b/src/components/business/construction/estimates/EstimateDetailForm.tsx index 1c5f94b8..a0bf9f4e 100644 --- a/src/components/business/construction/estimates/EstimateDetailForm.tsx +++ b/src/components/business/construction/estimates/EstimateDetailForm.tsx @@ -184,7 +184,7 @@ export default function EstimateDetailForm({ vatType: formData.bidInfo.vatType, }, expenseItems: formData.expenseItems, - detailItems: formData.detailItems, + detailItems: formData.detailItems as unknown as import('../bidding/types').EstimateDetailItem[], }); if (result.success && result.data) { @@ -529,12 +529,12 @@ export default function EstimateDetailForm({ }); }, []); - const handleDocumentRemove = useCallback((docId: string) => { + const handleDocumentRemove = useCallback((docId: string | number) => { setFormData((prev) => ({ ...prev, bidInfo: { ...prev.bidInfo, - documents: prev.bidInfo.documents.filter((d) => d.id !== docId), + documents: prev.bidInfo.documents.filter((d) => d.id !== String(docId)), }, })); }, []); @@ -685,7 +685,7 @@ export default function EstimateDetailForm({ } itemId={estimateId} isLoading={false} onBack={handleBack} diff --git a/src/components/business/construction/estimates/sections/EstimateDetailTableSection.tsx b/src/components/business/construction/estimates/sections/EstimateDetailTableSection.tsx index d177397f..e6a8e5c3 100644 --- a/src/components/business/construction/estimates/sections/EstimateDetailTableSection.tsx +++ b/src/components/business/construction/estimates/sections/EstimateDetailTableSection.tsx @@ -1,6 +1,6 @@ 'use client'; -import React from 'react'; +import React, { useState } from 'react'; import { X, HelpCircle } from 'lucide-react'; import { Tooltip, @@ -162,6 +162,7 @@ export function EstimateDetailTableSection({ onApplyAdjustedPrice, onReset, }: EstimateDetailTableSectionProps) { + const [detailAddCount, setDetailAddCount] = useState(1); // API 옵션이 없으면 기본 옵션 사용 const opts = options || DEFAULT_OPTIONS; const selectedCount = detailItems.filter((item) => (item as unknown as { selected?: boolean }).selected).length; @@ -201,7 +202,8 @@ export function EstimateDetailTableSection({
setDetailAddCount(val)} className="w-16 text-center" id="detail-add-count" /> @@ -211,8 +213,7 @@ export function EstimateDetailTableSection({ size="sm" className="bg-gray-900 hover:bg-gray-800" onClick={() => { - const countInput = document.getElementById('detail-add-count') as HTMLInputElement; - const count = Math.max(1, parseInt(countInput?.value || '1', 10)); + const count = Math.max(1, detailAddCount ?? 1); onAddItems(count); }} > diff --git a/src/components/business/construction/estimates/sections/EstimateInfoSection.tsx b/src/components/business/construction/estimates/sections/EstimateInfoSection.tsx index edb16fb5..59f52d08 100644 --- a/src/components/business/construction/estimates/sections/EstimateInfoSection.tsx +++ b/src/components/business/construction/estimates/sections/EstimateInfoSection.tsx @@ -25,7 +25,7 @@ interface EstimateInfoSectionProps { onFormDataChange: (updates: Partial) => void; onBidInfoChange: (field: string, value: string | number) => void; onFilesSelect: (files: File[]) => void; - onDocumentRemove: (docId: string) => void; + onDocumentRemove: (docId: string | number) => void; } export function EstimateInfoSection({ diff --git a/src/components/business/construction/estimates/sections/ExpenseDetailSection.tsx b/src/components/business/construction/estimates/sections/ExpenseDetailSection.tsx index 9cfde13c..2ba3eed1 100644 --- a/src/components/business/construction/estimates/sections/ExpenseDetailSection.tsx +++ b/src/components/business/construction/estimates/sections/ExpenseDetailSection.tsx @@ -1,6 +1,6 @@ 'use client'; -import React from 'react'; +import React, { useState } from 'react'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { QuantityInput } from '@/components/ui/quantity-input'; @@ -50,6 +50,7 @@ export function ExpenseDetailSection({ onSelectItem, onSelectAll, }: ExpenseDetailSectionProps) { + const [expenseAddCount, setExpenseAddCount] = useState(1); const selectedCount = expenseItems.filter((item) => item.selected).length; const allSelected = expenseItems.length > 0 && expenseItems.every((item) => item.selected); @@ -78,7 +79,8 @@ export function ExpenseDetailSection({
setExpenseAddCount(val)} className="w-16 text-center" id="expense-add-count" /> @@ -88,8 +90,7 @@ export function ExpenseDetailSection({ size="sm" className="bg-gray-900 hover:bg-gray-800" onClick={() => { - const countInput = document.getElementById('expense-add-count') as HTMLInputElement; - const count = Math.max(1, parseInt(countInput?.value || '1', 10)); + const count = Math.max(1, expenseAddCount ?? 1); onAddItems(count); }} > diff --git a/src/components/business/construction/handover-report/HandoverReportListClient.tsx b/src/components/business/construction/handover-report/HandoverReportListClient.tsx index 412bc7f9..fbb3ce04 100644 --- a/src/components/business/construction/handover-report/HandoverReportListClient.tsx +++ b/src/components/business/construction/handover-report/HandoverReportListClient.tsx @@ -243,11 +243,11 @@ export default function HandoverReportListClient({ // 거래처 필터 const partnerFilters = filterValues.partner as string[]; - if (partnerFilters?.length > 0 && !partnerFilters.includes(item.partnerId)) return false; + if (partnerFilters?.length > 0 && (!item.partnerId || !partnerFilters.includes(item.partnerId))) return false; // 계약담당자 필터 const contractManagerFilters = filterValues.contractManager as string[]; - if (contractManagerFilters?.length > 0 && !contractManagerFilters.includes(item.contractManagerId)) + if (contractManagerFilters?.length > 0 && (!item.contractManagerId || !contractManagerFilters.includes(item.contractManagerId))) return false; // 공사PM 필터 diff --git a/src/components/business/construction/handover-report/types.ts b/src/components/business/construction/handover-report/types.ts index e62d969a..db9880f3 100644 --- a/src/components/business/construction/handover-report/types.ts +++ b/src/components/business/construction/handover-report/types.ts @@ -7,9 +7,12 @@ export type HandoverStatus = 'pending' | 'completed'; export interface HandoverReport { id: string; reportNumber: string; // 보고서번호 + partnerId?: string; // 거래처 ID (필터용) partnerName: string; // 거래처 siteName: string; // 현장명 + contractManagerId?: string; // 계약담당자 ID (필터용) contractManagerName: string; // 계약담당자 + constructionPMId?: string | null; // 공사PM ID (필터용) constructionPMName: string | null; // 공사PM totalSites: number; // 총 개소 contractAmount: number; // 계약금액(공급가액) diff --git a/src/components/business/construction/item-management/ItemDetailClient.tsx b/src/components/business/construction/item-management/ItemDetailClient.tsx index fb4e9f97..9fecdcdb 100644 --- a/src/components/business/construction/item-management/ItemDetailClient.tsx +++ b/src/components/business/construction/item-management/ItemDetailClient.tsx @@ -486,7 +486,7 @@ export default function ItemDetailClient({ } itemId={itemId} isLoading={isLoading || isSaving} onSubmit={handleFormSubmit} diff --git a/src/components/business/construction/item-management/ItemManagementClient.tsx b/src/components/business/construction/item-management/ItemManagementClient.tsx index d0457e2c..27296161 100644 --- a/src/components/business/construction/item-management/ItemManagementClient.tsx +++ b/src/components/business/construction/item-management/ItemManagementClient.tsx @@ -8,7 +8,7 @@ import { Button } from '@/components/ui/button'; import { TableCell, TableRow } from '@/components/ui/table'; import { Checkbox } from '@/components/ui/checkbox'; import { Badge } from '@/components/ui/badge'; -import { UniversalListPage, type UniversalListConfig, type TableColumn, type FilterFieldConfig, type FilterValues } from '@/components/templates/UniversalListPage'; +import { UniversalListPage, type UniversalListConfig, type TableColumn, type FilterFieldConfig, type FilterValues, type SelectionHandlers, type RowClickHandlers } from '@/components/templates/UniversalListPage'; import { MobileCard } from '@/components/organisms/MobileCard'; import { toast } from 'sonner'; import { DeleteConfirmDialog } from '@/components/ui/confirm-dialog'; @@ -311,7 +311,7 @@ export default function ItemManagementClient({ // 테이블 행 렌더링 const renderTableRow = useCallback( - (item: Item, index: number, globalIndex: number, handlers: { isSelected: boolean; onToggle: () => void; onRowClick?: () => void }) => { + (item: Item, index: number, globalIndex: number, handlers: SelectionHandlers & RowClickHandlers) => { const { isSelected, onToggle } = handlers; return ( @@ -371,7 +371,7 @@ export default function ItemManagementClient({ // 모바일 카드 렌더링 const renderMobileCard = useCallback( - (item: Item, index: number, globalIndex: number, handlers: { isSelected: boolean; onToggle: () => void; onRowClick?: () => void }) => { + (item: Item, index: number, globalIndex: number, handlers: SelectionHandlers & RowClickHandlers) => { const { isSelected, onToggle } = handlers; return ( item.id, }} externalSearch={{ searchValue, diff --git a/src/components/business/construction/item-management/actions.ts b/src/components/business/construction/item-management/actions.ts index 896af87b..f7128447 100644 --- a/src/components/business/construction/item-management/actions.ts +++ b/src/components/business/construction/item-management/actions.ts @@ -195,7 +195,6 @@ export async function getItemList( current_page?: number; per_page?: number; last_page?: number; - per_page?: number; }>('/items', { params: queryParams }); console.log('📥 [getItemList] API 응답:', JSON.stringify(response).slice(0, 500)); @@ -364,7 +363,7 @@ export async function getCategoryOptions(): Promise<{ data: { id: number; name: string }[]; total: number; }; - }>('/categories', { params: { size: 100 } }); + }>('/categories', { params: { size: '100' } }); // API 응답 구조: { success, message, data: { current_page, data: [...], ... } } const categoryData = response.data?.data; diff --git a/src/components/business/construction/management/ProjectGanttChart.tsx b/src/components/business/construction/management/ProjectGanttChart.tsx index 6ee155de..5f6d6ebe 100644 --- a/src/components/business/construction/management/ProjectGanttChart.tsx +++ b/src/components/business/construction/management/ProjectGanttChart.tsx @@ -166,7 +166,7 @@ export default function ProjectGanttChart({ const widthPercent = ((barEndDays - barStartDays) / totalDays) * 100; // 색상 결정 - let bgColor = GANTT_BAR_COLORS.in_progress; + let bgColor: string = GANTT_BAR_COLORS.in_progress; if (project.status === 'completed') { bgColor = GANTT_BAR_COLORS.completed; } else if (project.hasUrgentIssue || project.status === 'urgent') { diff --git a/src/components/business/construction/order-management/OrderDetailForm.tsx b/src/components/business/construction/order-management/OrderDetailForm.tsx index c497074c..93c3b3bc 100644 --- a/src/components/business/construction/order-management/OrderDetailForm.tsx +++ b/src/components/business/construction/order-management/OrderDetailForm.tsx @@ -86,12 +86,12 @@ export default function OrderDetailForm({ // Calendar handlers handleCalendarDateClick, handleCalendarMonthChange, - } = useOrderDetailForm({ mode, orderId, initialData }); + } = useOrderDetailForm({ mode: mode === 'create' ? 'edit' : mode, orderId: orderId ?? '', initialData }); // 저장 핸들러 (IntegratedDetailTemplate용) const handleSubmit = useCallback(async (): Promise<{ success: boolean; error?: string }> => { try { - const result = await updateOrder(orderId, formData); + const result = await updateOrder(orderId ?? '', formData); if (result.success) { toast.success('수정이 완료되었습니다.'); router.push(`/ko/construction/order/order-management/${orderId}?mode=view`); @@ -107,7 +107,7 @@ export default function OrderDetailForm({ // 삭제 핸들러 (IntegratedDetailTemplate용) const handleDelete = useCallback(async (): Promise<{ success: boolean; error?: string }> => { try { - const result = await deleteOrder(orderId); + const result = await deleteOrder(orderId ?? ''); if (result.success) { toast.success('발주가 삭제되었습니다.'); router.push('/ko/construction/order/order-management'); diff --git a/src/components/business/construction/order-management/OrderManagementUnified.tsx b/src/components/business/construction/order-management/OrderManagementUnified.tsx index 931bcfc9..f67343f6 100644 --- a/src/components/business/construction/order-management/OrderManagementUnified.tsx +++ b/src/components/business/construction/order-management/OrderManagementUnified.tsx @@ -455,7 +455,7 @@ export function OrderManagementUnified({ initialData }: OrderManagementUnifiedPr onRowClick(item)} + onClick={() => onRowClick?.(item)} > e.stopPropagation()}> onRowClick(item)} + onCardClick={() => onRowClick?.(item)} infoGrid={
diff --git a/src/components/business/construction/order-management/actions.ts b/src/components/business/construction/order-management/actions.ts index d3dab658..ebed98ec 100644 --- a/src/components/business/construction/order-management/actions.ts +++ b/src/components/business/construction/order-management/actions.ts @@ -157,7 +157,12 @@ function transformOrderDetail(apiOrder: ApiOrder): OrderDetail { orderCompanyId: '', // Backend에 없음 deliveryLocationType: 'site', deliveryAddress: '', // Backend에 없음 - deliveryMemo: apiOrder.memo || '', + contractId: '', + constructionPMId: '', + constructionManagers: [], + orderItems: [], + scheduleEvents: [], + memo: apiOrder.memo || '', totalAmount: apiOrder.total_amount || 0, supplyAmount: apiOrder.supply_amount || 0, taxAmount: apiOrder.tax_amount || 0, @@ -175,7 +180,7 @@ function transformOrderToApi(data: OrderDetailFormData): Record status_code: transformToBackendStatus(data.status), order_type_code: transformToBackendOrderType(data.orderType), delivery_date: data.deliveryAddress ? undefined : undefined, // 필드 매핑 필요 - memo: data.deliveryMemo, + memo: data.memo, // items 변환은 별도 처리 필요 }; } diff --git a/src/components/business/construction/order-management/types.ts b/src/components/business/construction/order-management/types.ts index 7bd11635..007fdc2e 100644 --- a/src/components/business/construction/order-management/types.ts +++ b/src/components/business/construction/order-management/types.ts @@ -366,6 +366,14 @@ export interface OrderDetail extends Order { memo: string; /** 발주 스케줄 이벤트 (달력용) */ scheduleEvents: OrderScheduleEvent[]; + /** 총 금액 */ + totalAmount?: number; + /** 공급가액 */ + supplyAmount?: number; + /** 세액 */ + taxAmount?: number; + /** 카테고리별 품목 목록 */ + categories?: OrderDetailCategory[]; } /** diff --git a/src/components/business/construction/pricing-management/PricingDetailClient.tsx b/src/components/business/construction/pricing-management/PricingDetailClient.tsx index 5a2b0adc..bdff2a09 100644 --- a/src/components/business/construction/pricing-management/PricingDetailClient.tsx +++ b/src/components/business/construction/pricing-management/PricingDetailClient.tsx @@ -44,6 +44,8 @@ interface FormData { unit: string; division: string; vendor: string; + purchasePrice: number; + marginRate: number; sellingPrice: number; status: PricingStatus; note: string; @@ -57,6 +59,8 @@ const initialFormData: FormData = { unit: '', division: '', vendor: '', + purchasePrice: 0, + marginRate: 0, sellingPrice: 0, status: 'in_use', note: '', @@ -99,6 +103,8 @@ export default function PricingDetailClient({ id, mode }: PricingDetailClientPro unit: result.data.unit, division: result.data.division, vendor: result.data.vendor, + purchasePrice: result.data.purchasePrice, + marginRate: result.data.marginRate, sellingPrice: result.data.sellingPrice, status: result.data.status, note: '', @@ -156,6 +162,8 @@ export default function PricingDetailClient({ id, mode }: PricingDetailClientPro unit: formData.unit, division: formData.division, vendor: formData.vendor, + purchasePrice: formData.purchasePrice, + marginRate: formData.marginRate, sellingPrice: formData.sellingPrice, status: formData.status, }); diff --git a/src/components/business/construction/pricing-management/types.ts b/src/components/business/construction/pricing-management/types.ts index 81c0f601..8894d409 100644 --- a/src/components/business/construction/pricing-management/types.ts +++ b/src/components/business/construction/pricing-management/types.ts @@ -59,6 +59,8 @@ export interface PricingFilter { page?: number; size?: number; sortBy?: string; + startDate?: string; + endDate?: string; } // 폼 데이터 diff --git a/src/components/business/construction/site-briefings/SiteBriefingForm.tsx b/src/components/business/construction/site-briefings/SiteBriefingForm.tsx index a7046444..5a1bcbbf 100644 --- a/src/components/business/construction/site-briefings/SiteBriefingForm.tsx +++ b/src/components/business/construction/site-briefings/SiteBriefingForm.tsx @@ -387,10 +387,10 @@ export default function SiteBriefingForm({ mode, briefingId, initialData }: Site }, []); // 기존 문서 삭제 핸들러 - const handleExistingDocumentRemove = useCallback((docId: string) => { + const handleExistingDocumentRemove = useCallback((docId: string | number) => { setFormData((prev) => ({ ...prev, - documents: prev.documents.filter((d) => d.id !== docId), + documents: prev.documents.filter((d) => d.id !== String(docId)), })); }, []); diff --git a/src/components/business/construction/site-management/SiteDetailForm.tsx b/src/components/business/construction/site-management/SiteDetailForm.tsx index 35e59bb7..910105d8 100644 --- a/src/components/business/construction/site-management/SiteDetailForm.tsx +++ b/src/components/business/construction/site-management/SiteDetailForm.tsx @@ -128,8 +128,8 @@ export default function SiteDetailForm({ site, mode = 'view' }: SiteDetailFormPr }, []); // 기존 파일 삭제 핸들러 - const handleExistingFileRemove = useCallback((docId: string) => { - setDrawings((prev) => prev.filter((d) => d.id !== docId)); + const handleExistingFileRemove = useCallback((docId: string | number) => { + setDrawings((prev) => prev.filter((d) => d.id !== String(docId))); }, []); // 동적 config (mode에 따라 title 변경) diff --git a/src/components/business/construction/structure-review/StructureReviewDetailForm.tsx b/src/components/business/construction/structure-review/StructureReviewDetailForm.tsx index 146b1f74..81ba12d7 100644 --- a/src/components/business/construction/structure-review/StructureReviewDetailForm.tsx +++ b/src/components/business/construction/structure-review/StructureReviewDetailForm.tsx @@ -124,8 +124,8 @@ export default function StructureReviewDetailForm({ }, []); // 기존 파일 삭제 핸들러 - const handleExistingFileRemove = useCallback((docId: string) => { - setReviewFiles((prev) => prev.filter((d) => d.id !== docId)); + const handleExistingFileRemove = useCallback((docId: string | number) => { + setReviewFiles((prev) => prev.filter((d) => d.id !== String(docId))); }, []); // 동적 config (mode에 따라 title 변경) diff --git a/src/components/business/construction/worker-status/WorkerStatusListClient.tsx b/src/components/business/construction/worker-status/WorkerStatusListClient.tsx index 19a49491..56d1dd14 100644 --- a/src/components/business/construction/worker-status/WorkerStatusListClient.tsx +++ b/src/components/business/construction/worker-status/WorkerStatusListClient.tsx @@ -136,7 +136,7 @@ export default function WorkerStatusListClient({ return { success: true, data: result.data.items, - totalCount: result.data.total, + totalCount: result.data.totalItems, }; } return { success: false, error: result.error }; diff --git a/src/components/clients/ClientDetailClientV2.tsx b/src/components/clients/ClientDetailClientV2.tsx index 2d28cfb0..1ffffd2f 100644 --- a/src/components/clients/ClientDetailClientV2.tsx +++ b/src/components/clients/ClientDetailClientV2.tsx @@ -240,7 +240,7 @@ export function ClientDetailClientV2({ clientId, initialMode }: ClientDetailClie ref={templateRef} config={dynamicConfig} mode={mode} - initialData={initialData} + initialData={initialData as Record | undefined} itemId={clientId} isLoading={isLoading} onSubmit={handleSubmit} diff --git a/src/components/clients/clientDetailConfig.ts b/src/components/clients/clientDetailConfig.ts index 30fa581f..ffcb080d 100644 --- a/src/components/clients/clientDetailConfig.ts +++ b/src/components/clients/clientDetailConfig.ts @@ -158,7 +158,7 @@ export const clientFields: FieldDefinition[] = [ label: '상태', type: 'radio', options: STATUS_OPTIONS, - defaultValue: 'true', + // Note: default value is handled by formData initialization }, ]; diff --git a/src/components/customer-center/EventManagement/EventDetail.tsx b/src/components/customer-center/EventManagement/EventDetail.tsx index 8e173559..ee6b7f74 100644 --- a/src/components/customer-center/EventManagement/EventDetail.tsx +++ b/src/components/customer-center/EventManagement/EventDetail.tsx @@ -91,7 +91,7 @@ export function EventDetail({ event }: EventDetailProps) { } itemId={event.id} renderView={() => renderFormContent()} renderForm={() => renderFormContent()} diff --git a/src/components/customer-center/InquiryManagement/InquiryDetail.tsx b/src/components/customer-center/InquiryManagement/InquiryDetail.tsx index 06930fc0..d3b5acf9 100644 --- a/src/components/customer-center/InquiryManagement/InquiryDetail.tsx +++ b/src/components/customer-center/InquiryManagement/InquiryDetail.tsx @@ -344,7 +344,7 @@ export function InquiryDetail({ } itemId={inquiry.id} isLoading={isSubmitting} onDelete={handleFormDelete} diff --git a/src/components/customer-center/InquiryManagement/InquiryDetailClientV2.tsx b/src/components/customer-center/InquiryManagement/InquiryDetailClientV2.tsx index fca26ab1..0e109d0f 100644 --- a/src/components/customer-center/InquiryManagement/InquiryDetailClientV2.tsx +++ b/src/components/customer-center/InquiryManagement/InquiryDetailClientV2.tsx @@ -174,9 +174,10 @@ export function InquiryDetailClientV2({ inquiryId, initialMode }: InquiryDetailC router.push(BASE_PATH)} + description={error} + showHomeButton={false} + onBack={() => router.push(BASE_PATH)} + backButtonLabel="목록으로" /> ); } @@ -211,9 +212,10 @@ export function InquiryDetailClientV2({ inquiryId, initialMode }: InquiryDetailC router.push(BASE_PATH)} + description="잠시 후 다시 시도해주세요." + showHomeButton={false} + onBack={() => router.push(BASE_PATH)} + backButtonLabel="목록으로" /> ); } diff --git a/src/components/customer-center/InquiryManagement/InquiryForm.tsx b/src/components/customer-center/InquiryManagement/InquiryForm.tsx index 223431e2..9ed6e3a4 100644 --- a/src/components/customer-center/InquiryManagement/InquiryForm.tsx +++ b/src/components/customer-center/InquiryManagement/InquiryForm.tsx @@ -66,8 +66,8 @@ export function InquiryForm({ mode, initialData }: InquiryFormProps) { setAttachments((prev) => prev.filter((_, i) => i !== index)); }, []); - const handleRemoveExistingFile = useCallback((id: string) => { - setExistingAttachments((prev) => prev.filter((a) => a.id !== id)); + const handleRemoveExistingFile = useCallback((id: string | number) => { + setExistingAttachments((prev) => prev.filter((a) => a.id !== String(id))); }, []); // ===== 유효성 검사 ===== diff --git a/src/components/customer-center/NoticeManagement/NoticeDetail.tsx b/src/components/customer-center/NoticeManagement/NoticeDetail.tsx index eff24d23..9e6ede84 100644 --- a/src/components/customer-center/NoticeManagement/NoticeDetail.tsx +++ b/src/components/customer-center/NoticeManagement/NoticeDetail.tsx @@ -91,7 +91,7 @@ export function NoticeDetail({ notice }: NoticeDetailProps) { } itemId={notice.id} renderView={() => renderFormContent()} renderForm={() => renderFormContent()} diff --git a/src/components/dev/generators/accountingData.ts b/src/components/dev/generators/accountingData.ts index 1de58078..563e4396 100644 --- a/src/components/dev/generators/accountingData.ts +++ b/src/components/dev/generators/accountingData.ts @@ -219,6 +219,7 @@ export interface PurchaseApprovalFormData { approvalLine: ApprovalPerson[]; references: ApprovalPerson[]; proposalData: { + vendorId: string; vendor: string; vendorPaymentDate: string; title: string; diff --git a/src/components/dev/generators/index.ts b/src/components/dev/generators/index.ts index 62b9fa8d..f7d6b2a3 100644 --- a/src/components/dev/generators/index.ts +++ b/src/components/dev/generators/index.ts @@ -5,7 +5,7 @@ import { getLocalDateString } from '@/utils/date'; // 랜덤 선택 -export function randomPick(arr: T[]): T { +export function randomPick(arr: readonly T[]): T { return arr[Math.floor(Math.random() * arr.length)]; } diff --git a/src/components/dev/generators/quoteData.ts b/src/components/dev/generators/quoteData.ts index b6f23d98..383f4d84 100644 --- a/src/components/dev/generators/quoteData.ts +++ b/src/components/dev/generators/quoteData.ts @@ -16,7 +16,7 @@ import { tempId, } from './index'; import type { QuoteFormData, QuoteItem } from '@/components/quotes/QuoteRegistration'; -import type { Vendor } from '@/components/accounting/VendorManagement'; +import type { Vendor } from '@/components/accounting/VendorManagement/types'; import type { FinishedGoods } from '@/components/quotes/actions'; // 제품 카테고리 diff --git a/src/components/dev/generators/shipmentData.ts b/src/components/dev/generators/shipmentData.ts index cbcb30c0..d6398a08 100644 --- a/src/components/dev/generators/shipmentData.ts +++ b/src/components/dev/generators/shipmentData.ts @@ -11,9 +11,9 @@ import type { ShipmentCreateFormData, ShipmentPriority, DeliveryMethod, + VehicleTonnageOption, LotOption, LogisticsOption, - VehicleTonnageOption, } from '@/components/outbound/ShipmentManagement/types'; // 우선순위 @@ -26,8 +26,8 @@ const DELIVERY_METHODS: DeliveryMethod[] = ['pickup', 'direct', 'logistics']; * 출하 폼 데이터 생성 */ export interface GenerateShipmentDataOptions { - lotOptions?: LotOption[]; // 로트 목록 - logisticsOptions?: LogisticsOption[]; // 물류사 목록 + lotOptions?: LotOption[]; // 로트 목록 + logisticsOptions?: LogisticsOption[]; // 물류사 목록 tonnageOptions?: VehicleTonnageOption[]; // 차량 톤수 목록 lotNo?: string; // 지정 로트번호 (플로우에서 전달) } @@ -45,7 +45,7 @@ export function generateShipmentData( // 로트 선택 let selectedLotNo = lotNo || ''; if (!selectedLotNo && lotOptions.length > 0) { - selectedLotNo = randomPick(lotOptions).lotNo; + selectedLotNo = randomPick(lotOptions).value; } // 배송방식 @@ -54,7 +54,7 @@ export function generateShipmentData( // 물류사 (물류사 배송일 때만) let logisticsCompany = ''; if (deliveryMethod === 'logistics' && logisticsOptions.length > 0) { - logisticsCompany = randomPick(logisticsOptions).name; + logisticsCompany = randomPick(logisticsOptions).label; } // 차량 톤수 diff --git a/src/components/hr/CardManagement/CardManagementUnified.tsx b/src/components/hr/CardManagement/CardManagementUnified.tsx index 2348beb4..1aef6b09 100644 --- a/src/components/hr/CardManagement/CardManagementUnified.tsx +++ b/src/components/hr/CardManagement/CardManagementUnified.tsx @@ -134,7 +134,7 @@ export function CardManagementUnified({ initialData }: CardManagementUnifiedProp onRowClick(item)} + onClick={() => onRowClick?.()} > e.stopPropagation()}> onRowClick(item)} + onCardClick={() => onRowClick?.()} infoGrid={
diff --git a/src/components/hr/CardManagement/_legacy/CardDetail.tsx b/src/components/hr/CardManagement/_legacy/CardDetail.tsx index 9461e06f..7395a947 100644 --- a/src/components/hr/CardManagement/_legacy/CardDetail.tsx +++ b/src/components/hr/CardManagement/_legacy/CardDetail.tsx @@ -7,12 +7,12 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { CreditCard, ArrowLeft, Edit, Trash2 } from 'lucide-react'; -import type { Card as CardType } from './types'; +import type { Card as CardType } from '../types'; import { CARD_STATUS_LABELS, CARD_STATUS_COLORS, getCardCompanyLabel, -} from './types'; +} from '../types'; interface CardDetailProps { card: CardType; diff --git a/src/components/hr/CardManagement/_legacy/CardForm.tsx b/src/components/hr/CardManagement/_legacy/CardForm.tsx index e25c83f1..c8071ed9 100644 --- a/src/components/hr/CardManagement/_legacy/CardForm.tsx +++ b/src/components/hr/CardManagement/_legacy/CardForm.tsx @@ -16,9 +16,9 @@ import { SelectValue, } from '@/components/ui/select'; import { CreditCard, ArrowLeft, Save } from 'lucide-react'; -import type { Card as CardType, CardFormData, CardCompany, CardStatus } from './types'; -import { CARD_COMPANIES, CARD_STATUS_LABELS } from './types'; -import { getActiveEmployees } from './actions'; +import type { Card as CardType, CardFormData, CardCompany, CardStatus } from '../types'; +import { CARD_COMPANIES, CARD_STATUS_LABELS } from '../types'; +import { getActiveEmployees } from '../actions'; interface CardFormProps { mode: 'create' | 'edit'; @@ -88,19 +88,19 @@ export function CardForm({ mode, card, onSubmit }: CardFormProps) { const digits = value.replace(/\D/g, '').slice(0, 16); const parts = digits.match(/.{1,4}/g) || []; const formatted = parts.join('-'); - setFormData(prev => ({ ...prev, cardNumber: formatted })); + setFormData((prev: CardFormData) => ({ ...prev, cardNumber: formatted })); }; // 유효기간 포맷팅 (MMYY) const handleExpiryDateChange = (value: string) => { const digits = value.replace(/\D/g, '').slice(0, 4); - setFormData(prev => ({ ...prev, expiryDate: digits })); + setFormData((prev: CardFormData) => ({ ...prev, expiryDate: digits })); }; // 비밀번호 앞 2자리 const handlePinPrefixChange = (value: string) => { const digits = value.replace(/\D/g, '').slice(0, 2); - setFormData(prev => ({ ...prev, pinPrefix: digits })); + setFormData((prev: CardFormData) => ({ ...prev, pinPrefix: digits })); }; return ( @@ -123,7 +123,7 @@ export function CardForm({ mode, card, onSubmit }: CardFormProps) { setFormData(prev => ({ ...prev, cardName: e.target.value }))} + onChange={(e) => setFormData((prev: CardFormData) => ({ ...prev, cardName: e.target.value }))} placeholder="카드명을 입력해주세요" />
@@ -186,7 +186,7 @@ export function CardForm({ mode, card, onSubmit }: CardFormProps) { setFormData(prev => ({ ...prev, userId: value }))} + onValueChange={(value) => setFormData((prev: CardFormData) => ({ ...prev, userId: value }))} disabled={isLoadingEmployees} > diff --git a/src/components/hr/CardManagement/cardConfig.ts b/src/components/hr/CardManagement/cardConfig.ts index 35f6fb68..dc19707f 100644 --- a/src/components/hr/CardManagement/cardConfig.ts +++ b/src/components/hr/CardManagement/cardConfig.ts @@ -153,7 +153,7 @@ export const cardConfig: DetailConfig = { return date || '-'; case 'userId': // 사용자 정보 조합 표시 - const userData = data as Card; + const userData = data as unknown as Card; if (userData.user) { return `${userData.user.departmentName} / ${userData.user.employeeName} / ${userData.user.positionName}`; } diff --git a/src/components/hr/EmployeeManagement/EmployeeDetail.tsx b/src/components/hr/EmployeeManagement/EmployeeDetail.tsx index 5969d39e..0837b61b 100644 --- a/src/components/hr/EmployeeManagement/EmployeeDetail.tsx +++ b/src/components/hr/EmployeeManagement/EmployeeDetail.tsx @@ -211,7 +211,7 @@ export function EmployeeDetail({ employee, onEdit, onDelete }: EmployeeDetailPro } itemId={employee.id} onEdit={onEdit} onDelete={handleFormDelete} diff --git a/src/components/hr/SalaryManagement/index.tsx b/src/components/hr/SalaryManagement/index.tsx index d4af1319..8f2c077f 100644 --- a/src/components/hr/SalaryManagement/index.tsx +++ b/src/components/hr/SalaryManagement/index.tsx @@ -516,13 +516,12 @@ export function SalaryManagement() { ); }, - renderDialogs: () => ( + renderDialogs: (_params) => ( ), }), [ diff --git a/src/components/hr/VacationManagement/index.tsx b/src/components/hr/VacationManagement/index.tsx index ec9ee60e..3bca6a1b 100644 --- a/src/components/hr/VacationManagement/index.tsx +++ b/src/components/hr/VacationManagement/index.tsx @@ -659,15 +659,17 @@ export function VacationManagement() { sort: sortOption, }), [filterOption, sortOption]); - const handleFilterChange = useCallback((key: string, value: string | string[]) => { - switch (key) { - case 'filter': - setFilterOption(value as FilterOption); - break; - case 'sort': - setSortOption(value as SortOption); - break; - } + const handleFilterChange = useCallback((filters: Record) => { + Object.entries(filters).forEach(([key, value]) => { + switch (key) { + case 'filter': + setFilterOption(value as FilterOption); + break; + case 'sort': + setSortOption(value as SortOption); + break; + } + }); }, []); const handleFilterReset = useCallback(() => { @@ -731,7 +733,7 @@ export function VacationManagement() { renderMobileCard: renderMobileCard, - renderDialogs: () => ( + renderDialogs: (_params) => ( <> {/* 휴가 부여 다이얼로그 */}
- + {}} />
@@ -231,7 +231,7 @@ export default function PurchasedPartForm({
- + {}} />
)} diff --git a/src/components/material/ReceivingManagement/InspectionCreate.tsx b/src/components/material/ReceivingManagement/InspectionCreate.tsx index 350f2faa..7b385d84 100644 --- a/src/components/material/ReceivingManagement/InspectionCreate.tsx +++ b/src/components/material/ReceivingManagement/InspectionCreate.tsx @@ -155,7 +155,7 @@ export function InspectionCreate({ id }: Props) { }, [inspector, inspectionItems]); // 검사 저장 - const handleSubmit = useCallback(() => { + const handleSubmit = useCallback(async () => { if (!validateForm()) { return; } @@ -211,7 +211,7 @@ export function InspectionCreate({ id }: Props) { >

{target.orderNo}

- {target.supplier} · {target.qty} {target.unit} + {target.supplier} · {target.orderQty ?? target.receivingQty ?? '-'} {target.unit}

)) diff --git a/src/components/material/ReceivingManagement/ReceivingProcessDialog.tsx b/src/components/material/ReceivingManagement/ReceivingProcessDialog.tsx index 1329cf04..a52621e7 100644 --- a/src/components/material/ReceivingManagement/ReceivingProcessDialog.tsx +++ b/src/components/material/ReceivingManagement/ReceivingProcessDialog.tsx @@ -44,7 +44,7 @@ export function ReceivingProcessDialog({ open, onOpenChange, detail, onComplete // 폼 데이터 const [receivingLot, setReceivingLot] = useState(() => generateLotNo()); const [supplierLot, setSupplierLot] = useState(''); - const [receivingQty, setReceivingQty] = useState(detail.orderQty.toString()); + const [receivingQty, setReceivingQty] = useState((detail.orderQty ?? 0).toString()); const [receivingLocation, setReceivingLocation] = useState(''); const [remark, setRemark] = useState(''); diff --git a/src/components/material/StockStatus/StockStatusDetail.tsx b/src/components/material/StockStatus/StockStatusDetail.tsx index 887f100d..681cf8ed 100644 --- a/src/components/material/StockStatus/StockStatusDetail.tsx +++ b/src/components/material/StockStatus/StockStatusDetail.tsx @@ -314,13 +314,12 @@ export function StockStatusDetail({ id }: StockStatusDetailProps) { | undefined} itemId={id} isLoading={isLoading} - isSaving={isSaving} renderView={() => renderViewContent()} renderForm={() => renderFormContent()} - onSave={handleSave} + onSubmit={async () => { await handleSave(); return { success: true }; }} /> ); } diff --git a/src/components/material/StockStatus/StockStatusList.tsx b/src/components/material/StockStatus/StockStatusList.tsx index ddc51e46..7c5e37a0 100644 --- a/src/components/material/StockStatus/StockStatusList.tsx +++ b/src/components/material/StockStatus/StockStatusList.tsx @@ -214,13 +214,12 @@ export function StockStatusList() { { key: 'useStatus', label: '상태', - type: 'select', + type: 'single', options: [ { value: 'all', label: '전체' }, { value: 'active', label: '사용' }, { value: 'inactive', label: '미사용' }, ], - defaultValue: 'all', }, ]; diff --git a/src/components/material/StockStatus/mockData.ts b/src/components/material/StockStatus/mockData.ts index 85c6ef80..8f7b75cc 100644 --- a/src/components/material/StockStatus/mockData.ts +++ b/src/components/material/StockStatus/mockData.ts @@ -41,55 +41,79 @@ function generateLocation(type: string, seed: number): string { const rawMaterialItems: StockItem[] = [ { id: 'rm-1', + stockNumber: 'STK-RM-001', itemCode: 'SCR-FABRIC-WHT-03T', itemName: '스크린원단-백색-0.3T', itemType: 'raw_material', + specification: '-', unit: 'm²', + calculatedQty: 500, + actualQty: 500, stockQty: 500, safetyStock: 100, lotCount: 3, lotDaysElapsed: 21, status: 'normal', + useStatus: 'active', location: 'A-01', + hasStock: true, }, { id: 'rm-2', + stockNumber: 'STK-RM-002', itemCode: 'SCR-FABRIC-GRY-03T', itemName: '스크린원단-회색-0.3T', itemType: 'raw_material', + specification: '-', unit: 'm²', + calculatedQty: 350, + actualQty: 350, stockQty: 350, safetyStock: 80, lotCount: 2, lotDaysElapsed: 15, status: 'normal', + useStatus: 'active', location: 'A-02', + hasStock: true, }, { id: 'rm-3', + stockNumber: 'STK-RM-003', itemCode: 'SCR-FABRIC-BLK-03T', itemName: '스크린원단-흑색-0.3T', itemType: 'raw_material', + specification: '-', unit: 'm²', + calculatedQty: 280, + actualQty: 280, stockQty: 280, safetyStock: 70, lotCount: 2, lotDaysElapsed: 18, status: 'normal', + useStatus: 'active', location: 'A-03', + hasStock: true, }, { id: 'rm-4', + stockNumber: 'STK-RM-004', itemCode: 'SCR-FABRIC-BEI-03T', itemName: '스크린원단-베이지-0.3T', itemType: 'raw_material', + specification: '-', unit: 'm²', + calculatedQty: 420, + actualQty: 420, stockQty: 420, safetyStock: 90, lotCount: 4, lotDaysElapsed: 12, status: 'normal', + useStatus: 'active', location: 'A-04', + hasStock: true, }, ]; @@ -105,16 +129,22 @@ const bentPartItems: StockItem[] = Array.from({ length: 41 }, (_, i) => { return { id: `bp-${i + 1}`, + stockNumber: `STK-BP-${String(i + 1).padStart(3, '0')}`, itemCode: `BENT-${type.toUpperCase().slice(0, 3)}-${variant}-${String(i + 1).padStart(2, '0')}`, itemName: `${type}-${variant}형-${i + 1}`, itemType: 'bent_part' as const, + specification: '-', unit: 'EA', + calculatedQty: stockQty, + actualQty: stockQty, stockQty, safetyStock, lotCount: seededInt(seed + 2, 1, 5), lotDaysElapsed: seededInt(seed + 3, 0, 45), status: getStockStatus(stockQty, safetyStock), + useStatus: 'active' as const, location: generateLocation('bent_part', seed + 4), + hasStock: true, }; }); @@ -132,16 +162,22 @@ const purchasedPartItems: StockItem[] = [ return { id: `pp-sqp-${i + 1}`, + stockNumber: `STK-PP-SQP-${String(i + 1).padStart(3, '0')}`, itemCode: `SQP-${size.replace('×', '')}-${length.slice(0, 2)}`, itemName: `각파이프 ${size} L:${length}`, itemType: 'purchased_part' as const, + specification: '-', unit: 'EA', + calculatedQty: stockQty, + actualQty: stockQty, stockQty, safetyStock, lotCount: seededInt(seed + 2, 2, 5), lotDaysElapsed: seededInt(seed + 3, 0, 40), status: getStockStatus(stockQty, safetyStock), + useStatus: 'active' as const, location: 'I-05', + hasStock: true, }; }), // 앵글류 (15개) @@ -156,16 +192,22 @@ const purchasedPartItems: StockItem[] = [ return { id: `pp-ang-${i + 1}`, + stockNumber: `STK-PP-ANG-${String(i + 1).padStart(3, '0')}`, itemCode: `ANG-${size.replace('×', '')}-${length.slice(0, 2)}`, itemName: `앵글 ${size} L:${length}`, itemType: 'purchased_part' as const, + specification: '-', unit: 'EA', + calculatedQty: stockQty, + actualQty: stockQty, stockQty, safetyStock, lotCount: seededInt(seed + 2, 2, 4), lotDaysElapsed: seededInt(seed + 3, 0, 35), status: getStockStatus(stockQty, safetyStock), + useStatus: 'active' as const, location: 'I-04', + hasStock: true, }; }), // 전동개폐기류 (10개) @@ -182,16 +224,22 @@ const purchasedPartItems: StockItem[] = [ return { id: `pp-motor-${i + 1}`, + stockNumber: `STK-PP-MOT-${String(i + 1).padStart(3, '0')}`, itemCode: `MOTOR-${voltage}${weight}${type === '무선' ? '-W' : ''}`, itemName: `전동개폐기-${voltage}${weight}${type}`, itemType: 'purchased_part' as const, + specification: '-', unit: 'EA', + calculatedQty: stockQty, + actualQty: stockQty, stockQty, safetyStock, lotCount: seededInt(seed + 2, 1, 3), lotDaysElapsed: seededInt(seed + 3, 0, 30), status: getStockStatus(stockQty, safetyStock), + useStatus: 'active' as const, location: 'I-01', + hasStock: true, }; }), // 볼트/너트류 (15개) @@ -206,16 +254,22 @@ const purchasedPartItems: StockItem[] = [ return { id: `pp-bolt-${i + 1}`, + stockNumber: `STK-PP-BLT-${String(i + 1).padStart(3, '0')}`, itemCode: `BOLT-${size}-${length}`, itemName: `볼트 ${size}×${length}mm`, itemType: 'purchased_part' as const, + specification: '-', unit: 'EA', + calculatedQty: stockQty, + actualQty: stockQty, stockQty, safetyStock, lotCount: seededInt(seed + 2, 3, 6), lotDaysElapsed: seededInt(seed + 3, 0, 25), status: getStockStatus(stockQty, safetyStock), + useStatus: 'active' as const, location: 'J-01', + hasStock: true, }; }), // 베어링류 (10개) @@ -228,16 +282,22 @@ const purchasedPartItems: StockItem[] = [ return { id: `pp-bearing-${i + 1}`, + stockNumber: `STK-PP-BRG-${String(i + 1).padStart(3, '0')}`, itemCode: `BEARING-${type}`, itemName: `베어링 ${type}`, itemType: 'purchased_part' as const, + specification: '-', unit: 'EA', + calculatedQty: stockQty, + actualQty: stockQty, stockQty, safetyStock, lotCount: seededInt(seed + 2, 2, 4), lotDaysElapsed: seededInt(seed + 3, 0, 20), status: getStockStatus(stockQty, safetyStock), + useStatus: 'active' as const, location: 'J-02', + hasStock: true, }; }), // 스프링류 (10개) @@ -252,16 +312,22 @@ const purchasedPartItems: StockItem[] = [ return { id: `pp-spring-${i + 1}`, + stockNumber: `STK-PP-SPR-${String(i + 1).padStart(3, '0')}`, itemCode: `SPRING-${type.toUpperCase().slice(0, 2)}-${size}`, itemName: `스프링-${type}-${size}`, itemType: 'purchased_part' as const, + specification: '-', unit: 'EA', + calculatedQty: stockQty, + actualQty: stockQty, stockQty, safetyStock, lotCount: seededInt(seed + 2, 2, 5), lotDaysElapsed: seededInt(seed + 3, 0, 30), status: getStockStatus(stockQty, safetyStock), + useStatus: 'active' as const, location: 'J-03', + hasStock: true, }; }), ]; @@ -270,94 +336,136 @@ const purchasedPartItems: StockItem[] = [ const subMaterialItems: StockItem[] = [ { id: 'sm-1', + stockNumber: 'STK-SM-001', itemCode: 'SEW-WHT', itemName: '미싱실-백색', itemType: 'sub_material', + specification: '-', unit: 'M', + calculatedQty: 5000, + actualQty: 5000, stockQty: 5000, safetyStock: 1000, lotCount: 3, lotDaysElapsed: 28, status: 'normal', + useStatus: 'active', location: 'A-04', + hasStock: true, }, { id: 'sm-2', + stockNumber: 'STK-SM-002', itemCode: 'ALU-BAR', itemName: '하단바-알루미늄', itemType: 'sub_material', + specification: '-', unit: 'EA', + calculatedQty: 120, + actualQty: 120, stockQty: 120, safetyStock: 30, lotCount: 1, lotDaysElapsed: 5, status: 'normal', + useStatus: 'active', location: 'A-03', + hasStock: true, }, { id: 'sm-3', + stockNumber: 'STK-SM-003', itemCode: 'END-CAP-STD', itemName: '앤드락-표준', itemType: 'sub_material', + specification: '-', unit: 'EA', + calculatedQty: 800, + actualQty: 800, stockQty: 800, safetyStock: 200, lotCount: 2, lotDaysElapsed: 12, status: 'normal', + useStatus: 'active', location: 'A-02', + hasStock: true, }, { id: 'sm-4', + stockNumber: 'STK-SM-004', itemCode: 'SILICON-TRANS', itemName: '실리콘-투명', itemType: 'sub_material', + specification: '-', unit: 'EA', + calculatedQty: 200, + actualQty: 200, stockQty: 200, safetyStock: 50, lotCount: 5, lotDaysElapsed: 37, status: 'normal', + useStatus: 'active', location: 'B-03', + hasStock: true, }, { id: 'sm-5', + stockNumber: 'STK-SM-005', itemCode: 'TAPE-DBL-25', itemName: '양면테이프-25mm', itemType: 'sub_material', + specification: '-', unit: 'EA', + calculatedQty: 150, + actualQty: 150, stockQty: 150, safetyStock: 40, lotCount: 2, lotDaysElapsed: 10, status: 'normal', + useStatus: 'active', location: 'B-02', + hasStock: true, }, { id: 'sm-6', + stockNumber: 'STK-SM-006', itemCode: 'RIVET-STL-4', itemName: '리벳-스틸-4mm', itemType: 'sub_material', + specification: '-', unit: 'EA', + calculatedQty: 3000, + actualQty: 3000, stockQty: 3000, safetyStock: 500, lotCount: 4, lotDaysElapsed: 8, status: 'normal', + useStatus: 'active', location: 'B-01', + hasStock: true, }, { id: 'sm-7', + stockNumber: 'STK-SM-007', itemCode: 'WASHER-M8', itemName: '와셔-M8', itemType: 'sub_material', + specification: '-', unit: 'EA', + calculatedQty: 2500, + actualQty: 2500, stockQty: 2500, safetyStock: 400, lotCount: 3, lotDaysElapsed: 15, status: 'normal', + useStatus: 'active', location: 'B-04', + hasStock: true, }, ]; @@ -365,29 +473,41 @@ const subMaterialItems: StockItem[] = [ const consumableItems: StockItem[] = [ { id: 'cs-1', + stockNumber: 'STK-CS-001', itemCode: 'PKG-BOX-L', itemName: '포장박스-대형', itemType: 'consumable', + specification: '-', unit: 'EA', + calculatedQty: 200, + actualQty: 200, stockQty: 200, safetyStock: 50, lotCount: 2, lotDaysElapsed: 8, status: 'normal', + useStatus: 'active', location: 'B-01', + hasStock: true, }, { id: 'cs-2', + stockNumber: 'STK-CS-002', itemCode: 'PKG-BOX-M', itemName: '포장박스-중형', itemType: 'consumable', + specification: '-', unit: 'EA', + calculatedQty: 350, + actualQty: 350, stockQty: 350, safetyStock: 80, lotCount: 3, lotDaysElapsed: 5, status: 'normal', + useStatus: 'active', location: 'B-02', + hasStock: true, }, ]; @@ -474,6 +594,7 @@ export function generateStockDetail(item: StockItem): StockDetail { lotCount: item.lotCount, lastReceiptDate: lots[lots.length - 1]?.receiptDate || '2025-12-23', status: item.status, + hasStock: item.hasStock, lots, }; } @@ -503,12 +624,14 @@ const calculateStats = (): StockStats => { const normalCount = mockStockItems.filter(item => item.status === 'normal').length; const lowCount = mockStockItems.filter(item => item.status === 'low').length; const outCount = mockStockItems.filter(item => item.status === 'out').length; + const noStockCount = mockStockItems.filter(item => !item.hasStock).length; return { totalItems: mockStockItems.length, normalCount, lowCount, outCount, + noStockCount, }; }; diff --git a/src/components/material/StockStatus/stockStatusConfig.ts b/src/components/material/StockStatus/stockStatusConfig.ts index 39b6928d..5fa8f9f2 100644 --- a/src/components/material/StockStatus/stockStatusConfig.ts +++ b/src/components/material/StockStatus/stockStatusConfig.ts @@ -14,14 +14,14 @@ export const stockStatusConfig: DetailConfig = { icon: Package, basePath: '/material/stock-status', fields: [], // renderView/renderForm 사용으로 필드 정의 불필요 - gridColumns: 4, + gridColumns: 3, actions: { showBack: true, showDelete: false, showEdit: true, backLabel: '목록', editLabel: '수정', - saveLabel: '저장', + submitLabel: '저장', cancelLabel: '취소', }, }; diff --git a/src/components/material/StockStatus/types.ts b/src/components/material/StockStatus/types.ts index 5a42dcb0..077aba2a 100644 --- a/src/components/material/StockStatus/types.ts +++ b/src/components/material/StockStatus/types.ts @@ -5,20 +5,31 @@ */ // 품목유형 (Item 모델의 MATERIAL_TYPES) -export type ItemType = 'RM' | 'SM' | 'CS'; +// API에서는 'RM' | 'SM' | 'CS' 형태를 사용하지만, mock 데이터에서는 legacy 값도 지원 +export type ItemType = 'RM' | 'SM' | 'CS' | 'raw_material' | 'bent_part' | 'purchased_part' | 'sub_material' | 'consumable'; // 품목유형 라벨 -export const ITEM_TYPE_LABELS: Record = { +export const ITEM_TYPE_LABELS: Partial> = { RM: '원자재', SM: '부자재', CS: '소모품', + raw_material: '원자재', + bent_part: '절곡부품', + purchased_part: '구매부품', + sub_material: '부자재', + consumable: '소모품', }; // 품목유형 스타일 (뱃지용) -export const ITEM_TYPE_STYLES: Record = { +export const ITEM_TYPE_STYLES: Partial> = { RM: 'bg-blue-100 text-blue-800', SM: 'bg-green-100 text-green-800', CS: 'bg-orange-100 text-orange-800', + raw_material: 'bg-blue-100 text-blue-800', + bent_part: 'bg-purple-100 text-purple-800', + purchased_part: 'bg-teal-100 text-teal-800', + sub_material: 'bg-green-100 text-green-800', + consumable: 'bg-orange-100 text-orange-800', }; // 재고 상태 diff --git a/src/components/molecules/MobileFilter.tsx b/src/components/molecules/MobileFilter.tsx index 5e4df327..d9b49268 100644 --- a/src/components/molecules/MobileFilter.tsx +++ b/src/components/molecules/MobileFilter.tsx @@ -44,7 +44,7 @@ export interface FilterFieldConfig { key: string; label: string; type: 'single' | 'multi'; - options: FilterOption[]; + options: readonly FilterOption[]; allOptionLabel?: string; // single 타입에서 "전체" 옵션 라벨 (기본: '전체') } diff --git a/src/components/orders/ItemAddDialog.tsx b/src/components/orders/ItemAddDialog.tsx index 97564eb2..7ebe0a4c 100644 --- a/src/components/orders/ItemAddDialog.tsx +++ b/src/components/orders/ItemAddDialog.tsx @@ -28,20 +28,26 @@ import { } from "@/components/ui/select"; import { Package } from "lucide-react"; -// 품목 타입 +// 품목 타입 - actions.ts의 OrderItem과 호환 export interface OrderItem { id: string; - itemCode: string; // 품목코드 + itemId?: number; + itemCode?: string; // 품목코드 itemName: string; // 품명 - type: string; // 층 - symbol: string; // 부호 - spec: string; // 규격 - width: number; // 가로 (mm) - height: number; // 세로 (mm) + specification?: string; + type?: string; // 층 + symbol?: string; // 부호 + spec?: string; // 규격 + width?: number; // 가로 (mm) + height?: number; // 세로 (mm) quantity: number; // 수량 - unit: string; // 단위 + unit?: string; // 단위 unitPrice: number; // 단가 - amount: number; // 금액 + supplyAmount?: number; + taxAmount?: number; + totalAmount?: number; + amount?: number; // 금액 + sortOrder?: number; guideRailType?: string; // 가이드레일 타입 finish?: string; // 마감 floor?: string; // 층 diff --git a/src/components/orders/OrderRegistration.tsx b/src/components/orders/OrderRegistration.tsx index df7aeca1..2afedb23 100644 --- a/src/components/orders/OrderRegistration.tsx +++ b/src/components/orders/OrderRegistration.tsx @@ -62,7 +62,10 @@ import { useDevFill } from "@/components/dev"; import { generateOrderData } from "@/components/dev/generators/orderData"; // 수주 폼 데이터 타입 +// Index signature allows compatibility with Record (required by actions API) export interface OrderFormData { + [key: string]: unknown; + // 견적 정보 selectedQuotation?: QuotationForSelect; @@ -208,7 +211,7 @@ export function OrderRegistration({ // 금액 계산 useEffect(() => { - const subtotal = form.items.reduce((sum, item) => sum + item.amount, 0); + const subtotal = form.items.reduce((sum, item) => sum + (item.amount ?? 0), 0); const discountAmount = subtotal * (form.discountRate / 100); const totalAmount = subtotal - discountAmount; @@ -349,7 +352,7 @@ export function OrderRegistration({ }, []); // 저장 핸들러 - const handleSave = useCallback(async () => { + const handleSave = useCallback(async (): Promise<{ success: boolean; error?: string }> => { // 유효성 검사 const errors = validateForm(); setFieldErrors(errors); @@ -357,12 +360,16 @@ export function OrderRegistration({ const errorCount = Object.keys(errors).length; if (errorCount > 0) { toast.error(`입력 내용을 확인해주세요. (${errorCount}개 오류)`); - return; + return { success: false, error: `입력 내용을 확인해주세요. (${errorCount}개 오류)` }; } setIsSaving(true); try { await onSave(form); + return { success: true }; + } catch (e) { + const errorMsg = e instanceof Error ? e.message : '저장 중 오류가 발생했습니다.'; + return { success: false, error: errorMsg }; } finally { setIsSaving(false); } @@ -813,7 +820,7 @@ export function OrderRegistration({ {formatAmount(item.unitPrice)} - {formatAmount(item.amount)} + {formatAmount(item.amount ?? 0)} - )} + ) : ( + <> + {showBackButton && ( + + )} - {showHomeButton && homeButtonHref && ( - + {showHomeButton && homeButtonHref && ( + + )} + )}
diff --git a/src/components/ui/file-list.tsx b/src/components/ui/file-list.tsx index 7be06db8..7adaa8d4 100644 --- a/src/components/ui/file-list.tsx +++ b/src/components/ui/file-list.tsx @@ -52,6 +52,8 @@ export interface FileListProps { onDownload?: (file: ExistingFile) => void; /** 읽기 전용 */ readOnly?: boolean; + /** 삭제 버튼 표시 여부 (readOnly의 반대 개념) */ + showRemove?: boolean; /** 추가 클래스 */ className?: string; /** 파일 없을 때 메시지 */ diff --git a/src/components/vehicle-management/ForkliftList/index.tsx b/src/components/vehicle-management/ForkliftList/index.tsx index 7dbafff3..63aae4cf 100644 --- a/src/components/vehicle-management/ForkliftList/index.tsx +++ b/src/components/vehicle-management/ForkliftList/index.tsx @@ -118,7 +118,7 @@ export function ForkliftList({ initialData }: ForkliftListProps) { try { const result = await getForklifts({ page: params?.page || 1, - perPage: params?.perPage || 20, + perPage: params?.pageSize || 20, search: params?.search || undefined, }); diff --git a/src/components/vehicle-management/VehicleList/index.tsx b/src/components/vehicle-management/VehicleList/index.tsx index 9a3e672f..36786c0d 100644 --- a/src/components/vehicle-management/VehicleList/index.tsx +++ b/src/components/vehicle-management/VehicleList/index.tsx @@ -118,7 +118,7 @@ export function VehicleList({ initialData }: VehicleListProps) { try { const result = await getVehicles({ page: params?.page || 1, - perPage: params?.perPage || 20, + perPage: params?.pageSize || 20, search: params?.search || undefined, }); diff --git a/src/components/vehicle-management/VehicleLogList/index.tsx b/src/components/vehicle-management/VehicleLogList/index.tsx index ebecd883..917b5376 100644 --- a/src/components/vehicle-management/VehicleLogList/index.tsx +++ b/src/components/vehicle-management/VehicleLogList/index.tsx @@ -123,7 +123,7 @@ export function VehicleLogList({ initialData }: VehicleLogListProps) { try { const result = await getVehicleLogs({ page: params?.page || 1, - perPage: params?.perPage || 20, + perPage: params?.pageSize || 20, search: params?.search || undefined, }); diff --git a/src/contexts/ApiErrorContext.tsx b/src/contexts/ApiErrorContext.tsx index 1237c5b1..10b54ed7 100644 --- a/src/contexts/ApiErrorContext.tsx +++ b/src/contexts/ApiErrorContext.tsx @@ -17,7 +17,7 @@ interface ApiErrorContextType { * API 응답을 체크하고 인증 에러면 로그인 페이지로 이동 * @returns true면 에러가 있음 (호출자는 early return 해야 함) */ - checkAuthError: (response: T) => response is ApiErrorResponse; + checkAuthError: (response: unknown) => response is ApiErrorResponse; /** * 수동으로 인증 에러 처리 (로그아웃 후 로그인 페이지 이동) @@ -61,7 +61,7 @@ export function ApiErrorProvider({ children }: { children: ReactNode }) { * API 응답에서 인증 에러 체크 * 인증 에러면 자동으로 로그인 페이지 이동 */ - const checkAuthError = useCallback((response: T): response is ApiErrorResponse => { + const checkAuthError = useCallback((response: unknown): response is ApiErrorResponse => { if (isAuthError(response)) { handleAuthError(); return true; diff --git a/src/hooks/useCEODashboard.ts b/src/hooks/useCEODashboard.ts index 8b91fbf3..2b35ae5d 100644 --- a/src/hooks/useCEODashboard.ts +++ b/src/hooks/useCEODashboard.ts @@ -46,6 +46,9 @@ import { transformWelfareResponse, transformWelfareDetailResponse, transformExpectedExpenseDetailResponse, + transformPurchaseDetailResponse, + transformCardDetailResponse, + transformBillDetailResponse, } from '@/lib/api/dashboard/transformers'; import type { diff --git a/src/lib/api/dashboard/transformers.ts b/src/lib/api/dashboard/transformers.ts index cb7180ef..27e27ac0 100644 --- a/src/lib/api/dashboard/transformers.ts +++ b/src/lib/api/dashboard/transformers.ts @@ -722,13 +722,13 @@ export function transformCalendarResponse(api: CalendarApiResponse): { title: item.title, startDate: item.startDate, endDate: item.endDate, - startTime: item.startTime, - endTime: item.endTime, + startTime: item.startTime ?? undefined, + endTime: item.endTime ?? undefined, isAllDay: item.isAllDay, type: item.type, - department: item.department, - personName: item.personName, - color: item.color, + department: item.department ?? undefined, + personName: item.personName ?? undefined, + color: item.color ?? undefined, })), totalCount: api.total_count, }; diff --git a/src/lib/api/items.ts b/src/lib/api/items.ts index 8c89ce5b..5d775ffa 100644 --- a/src/lib/api/items.ts +++ b/src/lib/api/items.ts @@ -117,6 +117,9 @@ function transformItemFromApi(apiItem: ApiItemResponse): ItemMaster { unit: apiItem.unit || '', specification: apiItem.specification || '', isActive: apiItem.is_active === true || apiItem.is_active === 1 || apiItem.isActive === true, + currentRevision: 0, + isFinal: false, + createdAt: '', }; } diff --git a/src/lib/utils/excel-download.ts b/src/lib/utils/excel-download.ts index 1b5df27b..8feadf2f 100644 --- a/src/lib/utils/excel-download.ts +++ b/src/lib/utils/excel-download.ts @@ -400,10 +400,10 @@ export async function parseExcelFile>( const worksheet = workbook.Sheets[sheetName]; // JSON으로 변환 - const jsonData = XLSX.utils.sheet_to_json>(worksheet, { + const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1, // 배열로 반환 defval: '', // 빈 셀 기본값 - }) as unknown[][]; + }) as unknown as unknown[][]; // 헤더 행에서 컬럼 인덱스 매핑 const headerRow = jsonData[0] as string[]; diff --git a/src/types/item-master-api.ts b/src/types/item-master-api.ts index c45d69b6..66ef25a2 100644 --- a/src/types/item-master-api.ts +++ b/src/types/item-master-api.ts @@ -105,7 +105,15 @@ export interface PageReorderRequest { */ export interface ItemSectionRequest { title: string; - type: 'fields' | 'bom'; + type?: 'fields' | 'bom'; + section_type?: 'BASIC' | 'BOM' | 'CUSTOM'; + page_id?: number | null; + description?: string | null; + order_no?: number; + is_template?: boolean; + is_default?: boolean; + is_collapsible?: boolean; + is_default_open?: boolean; } /** @@ -199,15 +207,18 @@ export interface LinkSectionRequest { */ export interface ItemFieldRequest { field_name: string; - field_key?: string; // 2025-11-28: 필드 키 (영문, 숫자, 언더스코어만 허용, 영문으로 시작) + field_key?: string | null; // 2025-11-28: 필드 키 (영문, 숫자, 언더스코어만 허용, 영문으로 시작) field_type: 'textbox' | 'number' | 'dropdown' | 'checkbox' | 'date' | 'textarea'; + section_id?: number | null; + master_field_id?: number | null; + order_no?: number; is_required?: boolean; - placeholder?: string; - default_value?: string; - display_condition?: Record; // {"field_id": "1", "operator": "equals", "value": "true"} - validation_rules?: Record; // {"min": 0, "max": 100, "pattern": "regex"} - options?: Array<{ label: string; value: string }>; // dropdown 옵션 - properties?: Record; // {"unit": "mm", "precision": 2, "format": "YYYY-MM-DD"} + placeholder?: string | null; + default_value?: string | null; + display_condition?: Record | null; // {"field_id": "1", "operator": "equals", "value": "true"} + validation_rules?: Record | null; // {"min": 0, "max": 100, "pattern": "regex"} + options?: Array<{ label: string; value: string }> | null; // dropdown 옵션 + properties?: Record | null; // {"unit": "mm", "precision": 2, "format": "YYYY-MM-DD"} is_locked?: boolean; // 2025-11-28: 잠금 여부 }