- 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>
200 lines
7.3 KiB
TypeScript
200 lines
7.3 KiB
TypeScript
'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>
|
|
);
|
|
}
|