feat: API 프록시 추가 및 품목기준관리 기능 개선
- HttpOnly 쿠키 기반 API 프록시 라우트 추가 (/api/proxy/[...path]) - 품목기준관리 컴포넌트 개선 (섹션, 필드, 다이얼로그) - ItemMasterContext API 연동 강화 - mock-data 제거 및 실제 API 연동 - 문서 명명규칙 정리 ([TYPE-DATE] 형식) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,9 @@ 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';
|
||||
import { Check, X } from 'lucide-react';
|
||||
import type { ItemMasterField } from '@/contexts/ItemMasterContext';
|
||||
|
||||
const INPUT_TYPE_OPTIONS = [
|
||||
{ value: 'textbox', label: '텍스트 입력' },
|
||||
@@ -41,6 +44,14 @@ interface TemplateFieldDialogProps {
|
||||
templateFieldColumnNames: string[];
|
||||
setTemplateFieldColumnNames: (names: string[]) => void;
|
||||
handleAddTemplateField: () => void;
|
||||
// 마스터 항목 관련 props
|
||||
itemMasterFields?: ItemMasterField[];
|
||||
templateFieldInputMode?: 'custom' | 'master';
|
||||
setTemplateFieldInputMode?: (mode: 'custom' | 'master') => void;
|
||||
showMasterFieldList?: boolean;
|
||||
setShowMasterFieldList?: (show: boolean) => void;
|
||||
selectedMasterFieldId?: string;
|
||||
setSelectedMasterFieldId?: (id: string) => void;
|
||||
}
|
||||
|
||||
export function TemplateFieldDialog({
|
||||
@@ -67,31 +78,151 @@ export function TemplateFieldDialog({
|
||||
templateFieldColumnNames,
|
||||
setTemplateFieldColumnNames,
|
||||
handleAddTemplateField,
|
||||
// 마스터 항목 관련 props (optional)
|
||||
itemMasterFields = [],
|
||||
templateFieldInputMode = 'custom',
|
||||
setTemplateFieldInputMode,
|
||||
showMasterFieldList = false,
|
||||
setShowMasterFieldList,
|
||||
selectedMasterFieldId = '',
|
||||
setSelectedMasterFieldId,
|
||||
}: TemplateFieldDialogProps) {
|
||||
const handleClose = () => {
|
||||
setIsTemplateFieldDialogOpen(false);
|
||||
setEditingTemplateFieldId(null);
|
||||
setTemplateFieldName('');
|
||||
setTemplateFieldKey('');
|
||||
setTemplateFieldInputType('textbox');
|
||||
setTemplateFieldRequired(false);
|
||||
setTemplateFieldOptions('');
|
||||
setTemplateFieldDescription('');
|
||||
setTemplateFieldMultiColumn(false);
|
||||
setTemplateFieldColumnCount(2);
|
||||
setTemplateFieldColumnNames(['컬럼1', '컬럼2']);
|
||||
// 마스터 항목 관련 상태 초기화
|
||||
setTemplateFieldInputMode?.('custom');
|
||||
setShowMasterFieldList?.(false);
|
||||
setSelectedMasterFieldId?.('');
|
||||
};
|
||||
|
||||
const handleSelectMasterField = (field: ItemMasterField) => {
|
||||
setSelectedMasterFieldId?.(String(field.id));
|
||||
setTemplateFieldName(field.field_name);
|
||||
setTemplateFieldKey(field.id.toString());
|
||||
setTemplateFieldInputType(field.field_type);
|
||||
setTemplateFieldRequired(field.properties?.required || false);
|
||||
setTemplateFieldDescription(field.description || '');
|
||||
// options는 {label, value}[] 배열이므로 label만 추출
|
||||
setTemplateFieldOptions(field.options?.map(opt => opt.label).join(', ') || '');
|
||||
if (field.properties?.multiColumn && field.properties?.columnNames) {
|
||||
setTemplateFieldMultiColumn(true);
|
||||
setTemplateFieldColumnCount(field.properties.columnNames.length);
|
||||
setTemplateFieldColumnNames(field.properties.columnNames);
|
||||
} else {
|
||||
setTemplateFieldMultiColumn(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isTemplateFieldDialogOpen} onOpenChange={(open) => {
|
||||
setIsTemplateFieldDialogOpen(open);
|
||||
if (!open) {
|
||||
setEditingTemplateFieldId(null);
|
||||
setTemplateFieldName('');
|
||||
setTemplateFieldKey('');
|
||||
setTemplateFieldInputType('textbox');
|
||||
setTemplateFieldRequired(false);
|
||||
setTemplateFieldOptions('');
|
||||
setTemplateFieldDescription('');
|
||||
setTemplateFieldMultiColumn(false);
|
||||
setTemplateFieldColumnCount(2);
|
||||
setTemplateFieldColumnNames(['컬럼1', '컬럼2']);
|
||||
}
|
||||
}}>
|
||||
<Dialog open={isTemplateFieldDialogOpen} onOpenChange={handleClose}>
|
||||
<DialogContent className="max-w-[95vw] sm:max-w-2xl max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{editingTemplateFieldId ? '항목 수정' : '항목 추가'}</DialogTitle>
|
||||
<DialogDescription>
|
||||
섹션에 포함될 항목을 설정합니다
|
||||
재사용 가능한 마스터 항목을 선택하거나 직접 입력하세요
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4">
|
||||
{/* 입력 모드 선택 (편집 시에는 표시 안 함) */}
|
||||
{!editingTemplateFieldId && setTemplateFieldInputMode && (
|
||||
<div className="flex gap-2 p-1 bg-gray-100 rounded">
|
||||
<Button
|
||||
variant={templateFieldInputMode === 'custom' ? 'default' : 'ghost'}
|
||||
size="sm"
|
||||
onClick={() => setTemplateFieldInputMode('custom')}
|
||||
className="flex-1"
|
||||
>
|
||||
직접 입력
|
||||
</Button>
|
||||
<Button
|
||||
variant={templateFieldInputMode === 'master' ? 'default' : 'ghost'}
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setTemplateFieldInputMode('master');
|
||||
setShowMasterFieldList?.(true);
|
||||
}}
|
||||
className="flex-1"
|
||||
>
|
||||
마스터 항목 선택
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 마스터 항목 목록 */}
|
||||
{templateFieldInputMode === 'master' && !editingTemplateFieldId && showMasterFieldList && (
|
||||
<div className="border rounded p-3 space-y-2 max-h-[400px] overflow-y-auto">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<Label>마스터 항목 목록</Label>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setShowMasterFieldList?.(false)}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
{itemMasterFields.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground text-center py-4">
|
||||
등록된 마스터 항목이 없습니다
|
||||
</p>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{itemMasterFields.map(field => (
|
||||
<div
|
||||
key={field.id}
|
||||
className={`p-3 border rounded cursor-pointer transition-colors ${
|
||||
selectedMasterFieldId === String(field.id)
|
||||
? 'bg-blue-50 border-blue-300'
|
||||
: 'hover:bg-gray-50'
|
||||
}`}
|
||||
onClick={() => handleSelectMasterField(field)}
|
||||
>
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium">{field.field_name}</span>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{INPUT_TYPE_OPTIONS.find(o => o.value === field.field_type)?.label || field.field_type}
|
||||
</Badge>
|
||||
{field.properties?.required && (
|
||||
<Badge variant="destructive" className="text-xs">필수</Badge>
|
||||
)}
|
||||
</div>
|
||||
{field.description && (
|
||||
<p className="text-xs text-muted-foreground mt-1">{field.description}</p>
|
||||
)}
|
||||
{field.category && (
|
||||
<div className="flex gap-1 mt-1">
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
{field.category}
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{selectedMasterFieldId === String(field.id) && (
|
||||
<Check className="h-5 w-5 text-blue-600" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 직접 입력 폼 */}
|
||||
{(templateFieldInputMode === 'custom' || editingTemplateFieldId || !setTemplateFieldInputMode) && (
|
||||
<>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label>항목명 *</Label>
|
||||
@@ -199,6 +330,8 @@ export function TemplateFieldDialog({
|
||||
<Switch checked={templateFieldRequired} onCheckedChange={setTemplateFieldRequired} />
|
||||
<Label>필수 항목</Label>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setIsTemplateFieldDialogOpen(false)}>취소</Button>
|
||||
|
||||
Reference in New Issue
Block a user