[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,199 @@
|
||||
'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 { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Plus, Trash2 } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import type { OptionColumn } from '../types';
|
||||
|
||||
interface AttributeSubTab {
|
||||
id: string;
|
||||
label: string;
|
||||
key: string;
|
||||
order: number;
|
||||
isDefault?: boolean;
|
||||
}
|
||||
|
||||
interface ColumnManageDialogProps {
|
||||
isOpen: boolean;
|
||||
setIsOpen: (open: boolean) => void;
|
||||
managingColumnType: string | null;
|
||||
attributeSubTabs: AttributeSubTab[];
|
||||
attributeColumns: Record<string, OptionColumn[]>;
|
||||
setAttributeColumns: React.Dispatch<React.SetStateAction<Record<string, OptionColumn[]>>>;
|
||||
newColumnName: string;
|
||||
setNewColumnName: (name: string) => void;
|
||||
newColumnKey: string;
|
||||
setNewColumnKey: (key: string) => void;
|
||||
newColumnType: 'text' | 'number';
|
||||
setNewColumnType: (type: 'text' | 'number') => void;
|
||||
newColumnRequired: boolean;
|
||||
setNewColumnRequired: (required: boolean) => void;
|
||||
}
|
||||
|
||||
export function ColumnManageDialog({
|
||||
isOpen,
|
||||
setIsOpen,
|
||||
managingColumnType,
|
||||
attributeSubTabs,
|
||||
attributeColumns,
|
||||
setAttributeColumns,
|
||||
newColumnName,
|
||||
setNewColumnName,
|
||||
newColumnKey,
|
||||
setNewColumnKey,
|
||||
newColumnType,
|
||||
setNewColumnType,
|
||||
newColumnRequired,
|
||||
setNewColumnRequired,
|
||||
}: ColumnManageDialogProps) {
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={(open) => {
|
||||
setIsOpen(open);
|
||||
if (!open) {
|
||||
setNewColumnName('');
|
||||
setNewColumnKey('');
|
||||
setNewColumnType('text');
|
||||
setNewColumnRequired(false);
|
||||
}
|
||||
}}>
|
||||
<DialogContent className="max-w-3xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>칼럼 관리</DialogTitle>
|
||||
<DialogDescription>
|
||||
{managingColumnType === 'units' && '단위'}
|
||||
{managingColumnType === 'materials' && '재질'}
|
||||
{managingColumnType === 'surface' && '표면처리'}
|
||||
{managingColumnType && !['units', 'materials', 'surface'].includes(managingColumnType) &&
|
||||
(attributeSubTabs.find(t => t.key === managingColumnType)?.label || '속성')}
|
||||
{' '}에 추가 칼럼을 설정합니다 (예: 규격 안에 속성/값/단위 나누기)
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4">
|
||||
{/* 기존 칼럼 목록 */}
|
||||
{managingColumnType && attributeColumns[managingColumnType]?.length > 0 && (
|
||||
<div className="border rounded-lg p-4">
|
||||
<h4 className="font-medium mb-3">설정된 칼럼</h4>
|
||||
<div className="space-y-2">
|
||||
{attributeColumns[managingColumnType].map((column, idx) => (
|
||||
<div key={column.id} className="flex items-center justify-between p-3 bg-gray-50 rounded">
|
||||
<div className="flex items-center gap-3">
|
||||
<Badge variant="outline">{idx + 1}</Badge>
|
||||
<div>
|
||||
<p className="font-medium">{column.name}</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
키: {column.key} | 타입: {column.type === 'text' ? '텍스트' : '숫자'}
|
||||
{column.required && ' | 필수'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
if (managingColumnType) {
|
||||
setAttributeColumns(prev => ({
|
||||
...prev,
|
||||
[managingColumnType]: prev[managingColumnType]?.filter(c => c.id !== column.id) || []
|
||||
}));
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Trash2 className="w-4 h-4 text-red-500" />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 새 칼럼 추가 폼 */}
|
||||
<div className="border rounded-lg p-4 space-y-3">
|
||||
<h4 className="font-medium">새 칼럼 추가</h4>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<Label>칼럼명 *</Label>
|
||||
<Input
|
||||
value={newColumnName}
|
||||
onChange={(e) => setNewColumnName(e.target.value)}
|
||||
placeholder="예: 속성, 값, 단위"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label>키 (영문) *</Label>
|
||||
<Input
|
||||
value={newColumnKey}
|
||||
onChange={(e) => setNewColumnKey(e.target.value)}
|
||||
placeholder="예: property, value, unit"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label>타입</Label>
|
||||
<Select value={newColumnType} onValueChange={(value: 'text' | 'number') => setNewColumnType(value)}>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="text">텍스트</SelectItem>
|
||||
<SelectItem value="number">숫자</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 pt-6">
|
||||
<Switch
|
||||
checked={newColumnRequired}
|
||||
onCheckedChange={setNewColumnRequired}
|
||||
/>
|
||||
<Label>필수 항목</Label>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
size="sm"
|
||||
className="w-full"
|
||||
onClick={() => {
|
||||
if (!newColumnName.trim() || !newColumnKey.trim()) {
|
||||
toast.error('칼럼명과 키를 입력해주세요');
|
||||
return;
|
||||
}
|
||||
|
||||
if (managingColumnType) {
|
||||
const newColumn: OptionColumn = {
|
||||
id: `col-${Date.now()}`,
|
||||
name: newColumnName,
|
||||
key: newColumnKey,
|
||||
type: newColumnType,
|
||||
required: newColumnRequired
|
||||
};
|
||||
|
||||
setAttributeColumns(prev => ({
|
||||
...prev,
|
||||
[managingColumnType]: [...(prev[managingColumnType] || []), newColumn]
|
||||
}));
|
||||
|
||||
// 입력 필드 초기화
|
||||
setNewColumnName('');
|
||||
setNewColumnKey('');
|
||||
setNewColumnType('text');
|
||||
setNewColumnRequired(false);
|
||||
|
||||
toast.success('칼럼이 추가되었습니다');
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
칼럼 추가
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button onClick={() => setIsOpen(false)}>완료</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user