feat(WEB): 자재/출고/생산/품질/단가 기능 대폭 개선 및 신규 페이지 추가

자재관리:
- 입고관리 재고조정 다이얼로그 신규 추가, 상세/목록 기능 확장
- 재고현황 컴포넌트 리팩토링

출고관리:
- 출하관리 생성/수정/목록/상세 개선
- 차량배차관리 상세/수정/목록 기능 보강

생산관리:
- 작업지시서 WIP 생산 모달 신규 추가
- 벤딩WIP/슬랫조인트바 검사 콘텐츠 신규 추가
- 작업자화면 기능 대폭 확장 (카드/목록 개선)
- 검사성적서 모달 개선

품질관리:
- 실적보고서 관리 페이지 신규 추가
- 검사관리 문서/타입/목데이터 개선

단가관리:
- 단가배포 페이지 및 컴포넌트 신규 추가
- 단가표 관리 페이지 및 컴포넌트 신규 추가

공통:
- 권한 시스템 추가 개선 (PermissionContext, usePermission, PermissionGuard)
- 메뉴 폴링 훅 개선, 레이아웃 수정
- 모바일 줌/패닝 CSS 수정
- locale 유틸 추가

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-02-04 12:46:19 +09:00
parent 17c16028b1
commit c1b63b850a
70 changed files with 6832 additions and 384 deletions

View File

@@ -4,13 +4,12 @@
* 재고현황 상세/수정 페이지
*
* 기획서 기준:
* - 기본 정보: 재번호, 품목코드, 품목명, 규격, 단위, 계산 재고량 (읽기 전용)
* - 수정 가능: 실제 재고량, 안전재고, 상태 (사용/미사용)
* - 기본 정보: 재번호, 품목코드, 품목유형, 품목명, 규격, 단위, 재고량 (읽기 전용)
* - 수정 가능: 안전재고, 상태 (사용/미사용)
*/
import { useState, useCallback, useEffect } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import { Loader2 } from 'lucide-react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
@@ -25,7 +24,8 @@ import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetai
import { stockStatusConfig } from './stockStatusConfig';
import { ServerErrorPage } from '@/components/common/ServerErrorPage';
import { getStockById, updateStock } from './actions';
import { USE_STATUS_LABELS } from './types';
import { USE_STATUS_LABELS, ITEM_TYPE_LABELS } from './types';
import type { ItemType } from './types';
import { isNextRedirectError } from '@/lib/utils/redirect-error';
import { toast } from 'sonner';
@@ -38,11 +38,11 @@ interface StockDetailData {
id: string;
stockNumber: string;
itemCode: string;
itemType: ItemType;
itemName: string;
specification: string;
unit: string;
calculatedQty: number;
actualQty: number;
safetyStock: number;
useStatus: 'active' | 'inactive';
}
@@ -59,11 +59,9 @@ export function StockStatusDetail({ id }: StockStatusDetailProps) {
// 폼 데이터 (수정 모드용)
const [formData, setFormData] = useState<{
actualQty: number;
safetyStock: number;
useStatus: 'active' | 'inactive';
}>({
actualQty: 0,
safetyStock: 0,
useStatus: 'active',
});
@@ -86,17 +84,16 @@ export function StockStatusDetail({ id }: StockStatusDetailProps) {
id: data.id,
stockNumber: data.id, // stockNumber가 없으면 id 사용
itemCode: data.itemCode,
itemType: (data.itemType || 'RM') as ItemType,
itemName: data.itemName,
specification: data.specification || '-',
unit: data.unit,
calculatedQty: data.currentStock, // 계산 재고량
actualQty: data.currentStock, // 실제 재고량 (별도 필드 없으면 currentStock 사용)
calculatedQty: data.currentStock, // 재고량
safetyStock: data.safetyStock,
useStatus: data.status === null ? 'active' : 'active', // 기본값
};
setDetail(detailData);
setFormData({
actualQty: detailData.actualQty,
safetyStock: detailData.safetyStock,
useStatus: detailData.useStatus,
});
@@ -140,7 +137,6 @@ export function StockStatusDetail({ id }: StockStatusDetailProps) {
prev
? {
...prev,
actualQty: formData.actualQty,
safetyStock: formData.safetyStock,
useStatus: formData.useStatus,
}
@@ -189,19 +185,19 @@ export function StockStatusDetail({ id }: StockStatusDetailProps) {
</CardHeader>
<CardContent>
<div className="space-y-6">
{/* Row 1: 재번호, 품목코드, 품목명, 규격 */}
{/* Row 1: 재번호, 품목코드, 품목유형, 품목명 */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
{renderReadOnlyField('재번호', detail.stockNumber)}
{renderReadOnlyField('재번호', detail.stockNumber)}
{renderReadOnlyField('품목코드', detail.itemCode)}
{renderReadOnlyField('품목유형', ITEM_TYPE_LABELS[detail.itemType] || '-')}
{renderReadOnlyField('품목명', detail.itemName)}
{renderReadOnlyField('규격', detail.specification)}
</div>
{/* Row 2: 단위, 계산 재고량, 실제 재고량, 안전재고 */}
{/* Row 2: 규격, 단위, 재고량, 안전재고 */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
{renderReadOnlyField('규격', detail.specification)}
{renderReadOnlyField('단위', detail.unit)}
{renderReadOnlyField('계산 재고량', detail.calculatedQty)}
{renderReadOnlyField('실제 재고량', detail.actualQty)}
{renderReadOnlyField('재고량', detail.calculatedQty)}
{renderReadOnlyField('안전재고', detail.safetyStock)}
</div>
@@ -226,33 +222,19 @@ export function StockStatusDetail({ id }: StockStatusDetailProps) {
</CardHeader>
<CardContent>
<div className="space-y-6">
{/* Row 1: 재번호, 품목코드, 품목명, 규격 (읽기 전용) */}
{/* Row 1: 재번호, 품목코드, 품목유형, 품목명 (읽기 전용) */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
{renderReadOnlyField('재번호', detail.stockNumber, true)}
{renderReadOnlyField('재번호', detail.stockNumber, true)}
{renderReadOnlyField('품목코드', detail.itemCode, true)}
{renderReadOnlyField('품목유형', ITEM_TYPE_LABELS[detail.itemType] || '-', true)}
{renderReadOnlyField('품목명', detail.itemName, true)}
{renderReadOnlyField('규격', detail.specification, true)}
</div>
{/* Row 2: 단위, 계산 재고량 (읽기 전용) + 실제 재고량, 안전재고 (수정 가능) */}
{/* Row 2: 규격, 단위, 재고량 (읽기 전용) + 안전재고 (수정 가능) */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
{renderReadOnlyField('규격', detail.specification, true)}
{renderReadOnlyField('단위', detail.unit, true)}
{renderReadOnlyField('계산 재고량', detail.calculatedQty, true)}
{/* 실제 재고량 (수정 가능) */}
<div>
<Label htmlFor="actualQty" className="text-sm text-muted-foreground">
</Label>
<Input
id="actualQty"
type="number"
value={formData.actualQty}
onChange={(e) => handleInputChange('actualQty', e.target.value)}
className="mt-1.5 border-gray-300 focus:border-blue-500 focus:ring-blue-500"
min={0}
/>
</div>
{renderReadOnlyField('재고량', detail.calculatedQty, true)}
{/* 안전재고 (수정 가능) */}
<div>
@@ -322,4 +304,4 @@ export function StockStatusDetail({ id }: StockStatusDetailProps) {
onSubmit={async () => { await handleSave(); return { success: true }; }}
/>
);
}
}