[feat]: Item Master 데이터 관리 기능 구현 및 타입 에러 수정
- ItemMasterDataManagement 컴포넌트 구조화 (tabs, dialogs, components 분리) - HierarchyTab 타입 에러 수정 (BOMItem section_id, updated_at 추가) - API 클라이언트 구현 (item-master.ts, 13개 엔드포인트) - ItemMasterContext 구현 (상태 관리 및 데이터 흐름) - 백엔드 요구사항 문서 작성 (CORS 설정, API 스펙 등) - SSR 호환성 수정 (navigator API typeof window 체크) - 미사용 변수 ESLint 에러 해결 - Context 리팩토링 (AuthContext, RootProvider 추가) - API 유틸리티 추가 (error-handler, logger, transformers) 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,262 @@
|
||||
'use client';
|
||||
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
|
||||
const INPUT_TYPE_OPTIONS = [
|
||||
{ value: 'textbox', label: '텍스트 입력' },
|
||||
{ value: 'number', label: '숫자 입력' },
|
||||
{ value: 'dropdown', label: '드롭다운' },
|
||||
{ value: 'checkbox', label: '체크박스' },
|
||||
{ value: 'date', label: '날짜' },
|
||||
{ value: 'textarea', label: '긴 텍스트' },
|
||||
];
|
||||
|
||||
interface MasterFieldDialogProps {
|
||||
isMasterFieldDialogOpen: boolean;
|
||||
setIsMasterFieldDialogOpen: (open: boolean) => void;
|
||||
editingMasterFieldId: number | null;
|
||||
setEditingMasterFieldId: (id: number | null) => void;
|
||||
newMasterFieldName: string;
|
||||
setNewMasterFieldName: (name: string) => void;
|
||||
newMasterFieldKey: string;
|
||||
setNewMasterFieldKey: (key: string) => void;
|
||||
newMasterFieldInputType: 'textbox' | 'number' | 'dropdown' | 'checkbox' | 'date' | 'textarea';
|
||||
setNewMasterFieldInputType: (type: any) => void;
|
||||
newMasterFieldRequired: boolean;
|
||||
setNewMasterFieldRequired: (required: boolean) => void;
|
||||
newMasterFieldCategory: string;
|
||||
setNewMasterFieldCategory: (category: string) => void;
|
||||
newMasterFieldDescription: string;
|
||||
setNewMasterFieldDescription: (description: string) => void;
|
||||
newMasterFieldOptions: string;
|
||||
setNewMasterFieldOptions: (options: string) => void;
|
||||
newMasterFieldAttributeType: 'custom' | 'unit' | 'material' | 'surface';
|
||||
setNewMasterFieldAttributeType: (type: 'custom' | 'unit' | 'material' | 'surface') => void;
|
||||
newMasterFieldMultiColumn: boolean;
|
||||
setNewMasterFieldMultiColumn: (multi: boolean) => void;
|
||||
newMasterFieldColumnCount: number;
|
||||
setNewMasterFieldColumnCount: (count: number) => void;
|
||||
newMasterFieldColumnNames: string[];
|
||||
setNewMasterFieldColumnNames: (names: string[]) => void;
|
||||
handleUpdateMasterField: () => void;
|
||||
handleAddMasterField: () => void;
|
||||
}
|
||||
|
||||
export function MasterFieldDialog({
|
||||
isMasterFieldDialogOpen,
|
||||
setIsMasterFieldDialogOpen,
|
||||
editingMasterFieldId,
|
||||
setEditingMasterFieldId,
|
||||
newMasterFieldName,
|
||||
setNewMasterFieldName,
|
||||
newMasterFieldKey,
|
||||
setNewMasterFieldKey,
|
||||
newMasterFieldInputType,
|
||||
setNewMasterFieldInputType,
|
||||
newMasterFieldRequired,
|
||||
setNewMasterFieldRequired,
|
||||
newMasterFieldCategory,
|
||||
setNewMasterFieldCategory,
|
||||
newMasterFieldDescription,
|
||||
setNewMasterFieldDescription,
|
||||
newMasterFieldOptions,
|
||||
setNewMasterFieldOptions,
|
||||
newMasterFieldAttributeType,
|
||||
setNewMasterFieldAttributeType,
|
||||
newMasterFieldMultiColumn,
|
||||
setNewMasterFieldMultiColumn,
|
||||
newMasterFieldColumnCount,
|
||||
setNewMasterFieldColumnCount,
|
||||
newMasterFieldColumnNames,
|
||||
setNewMasterFieldColumnNames,
|
||||
handleUpdateMasterField,
|
||||
handleAddMasterField,
|
||||
}: MasterFieldDialogProps) {
|
||||
return (
|
||||
<Dialog open={isMasterFieldDialogOpen} onOpenChange={(open) => {
|
||||
setIsMasterFieldDialogOpen(open);
|
||||
if (!open) {
|
||||
setEditingMasterFieldId(null);
|
||||
setNewMasterFieldName('');
|
||||
setNewMasterFieldKey('');
|
||||
setNewMasterFieldInputType('textbox');
|
||||
setNewMasterFieldRequired(false);
|
||||
setNewMasterFieldCategory('공통');
|
||||
setNewMasterFieldDescription('');
|
||||
setNewMasterFieldOptions('');
|
||||
setNewMasterFieldAttributeType('custom');
|
||||
setNewMasterFieldMultiColumn(false);
|
||||
setNewMasterFieldColumnCount(2);
|
||||
setNewMasterFieldColumnNames(['컬럼1', '컬럼2']);
|
||||
}
|
||||
}}>
|
||||
<DialogContent className="max-w-[95vw] sm:max-w-2xl max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{editingMasterFieldId ? '마스터 항목 수정' : '마스터 항목 추가'}</DialogTitle>
|
||||
<DialogDescription>
|
||||
여러 섹션에서 재사용할 수 있는 항목 템플릿을 생성합니다
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label>항목명 *</Label>
|
||||
<Input
|
||||
value={newMasterFieldName}
|
||||
onChange={(e) => setNewMasterFieldName(e.target.value)}
|
||||
placeholder="예: 품목명"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label>필드 키 *</Label>
|
||||
<Input
|
||||
value={newMasterFieldKey}
|
||||
onChange={(e) => setNewMasterFieldKey(e.target.value)}
|
||||
placeholder="예: itemName"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label>입력방식 *</Label>
|
||||
<Select value={newMasterFieldInputType} onValueChange={(v: any) => setNewMasterFieldInputType(v)}>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{INPUT_TYPE_OPTIONS.map(opt => (
|
||||
<SelectItem key={opt.value} value={opt.value}>{opt.label}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch checked={newMasterFieldRequired} onCheckedChange={setNewMasterFieldRequired} />
|
||||
<Label>필수 항목</Label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label>설명</Label>
|
||||
<Textarea
|
||||
value={newMasterFieldDescription}
|
||||
onChange={(e) => setNewMasterFieldDescription(e.target.value)}
|
||||
placeholder="항목에 대한 설명"
|
||||
/>
|
||||
<p className="text-xs text-gray-500 mt-1">* 제품 유형에 따라 품목 분류를 표시 [필수]</p>
|
||||
</div>
|
||||
|
||||
{(newMasterFieldInputType === 'textbox' || newMasterFieldInputType === 'textarea') && (
|
||||
<div className="space-y-4 p-4 border rounded-lg bg-gray-50">
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch
|
||||
checked={newMasterFieldMultiColumn}
|
||||
onCheckedChange={(checked) => {
|
||||
setNewMasterFieldMultiColumn(checked);
|
||||
if (!checked) {
|
||||
setNewMasterFieldColumnCount(2);
|
||||
setNewMasterFieldColumnNames(['컬럼1', '컬럼2']);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Label>다중 컬럼 사용</Label>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500">
|
||||
활성화하면 한 항목에 여러 개의 값을 입력받을 수 있습니다 (예: 규격 - 가로, 세로, 높이)
|
||||
</p>
|
||||
|
||||
{newMasterFieldMultiColumn && (
|
||||
<div className="space-y-4 pt-4 border-t">
|
||||
<div>
|
||||
<Label>컬럼 개수</Label>
|
||||
<Input
|
||||
type="number"
|
||||
min="2"
|
||||
max="10"
|
||||
value={newMasterFieldColumnCount}
|
||||
onChange={(e) => {
|
||||
const count = parseInt(e.target.value) || 2;
|
||||
setNewMasterFieldColumnCount(count);
|
||||
// 컬럼 개수에 맞게 이름 배열 조정
|
||||
const newNames = Array.from({ length: count }, (_, i) =>
|
||||
newMasterFieldColumnNames[i] || `컬럼${i + 1}`
|
||||
);
|
||||
setNewMasterFieldColumnNames(newNames);
|
||||
}}
|
||||
placeholder="컬럼 개수 (2~10)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label>컬럼 이름 설정</Label>
|
||||
<div className="space-y-2 mt-2">
|
||||
{Array.from({ length: newMasterFieldColumnCount }, (_, i) => (
|
||||
<Input
|
||||
key={i}
|
||||
value={newMasterFieldColumnNames[i] || ''}
|
||||
onChange={(e) => {
|
||||
const newNames = [...newMasterFieldColumnNames];
|
||||
newNames[i] = e.target.value;
|
||||
setNewMasterFieldColumnNames(newNames);
|
||||
}}
|
||||
placeholder={`${i + 1}번째 컬럼 이름`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mt-2">
|
||||
예시: 가로, 세로, 높이 / 최소값, 최대값 / 상한, 하한
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{newMasterFieldInputType === 'dropdown' && (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<Label>드롭다운 옵션</Label>
|
||||
{newMasterFieldAttributeType !== 'custom' && (
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
{newMasterFieldAttributeType === 'unit' ? '단위' :
|
||||
newMasterFieldAttributeType === 'material' ? '재질' : '표면처리'} 연동
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<Textarea
|
||||
value={newMasterFieldOptions}
|
||||
onChange={(e) => setNewMasterFieldOptions(e.target.value)}
|
||||
placeholder="제품,부품,원자재 (쉼표로 구분)"
|
||||
disabled={newMasterFieldAttributeType !== 'custom'}
|
||||
className="min-h-[80px]"
|
||||
/>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
{newMasterFieldAttributeType === 'custom'
|
||||
? '쉼표(,)로 구분하여 입력하세요'
|
||||
: '속성 탭에서 옵션을 추가/삭제하면 자동으로 반영됩니다'
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setIsMasterFieldDialogOpen(false)}>취소</Button>
|
||||
<Button onClick={editingMasterFieldId ? handleUpdateMasterField : handleAddMasterField}>
|
||||
{editingMasterFieldId ? '수정' : '추가'}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user