- 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>
283 lines
12 KiB
TypeScript
283 lines
12 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 { Textarea } from '@/components/ui/textarea';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { FileText, Package, Check, X } from 'lucide-react';
|
|
import type { SectionTemplate } from '@/contexts/ItemMasterContext';
|
|
|
|
interface SectionDialogProps {
|
|
isSectionDialogOpen: boolean;
|
|
setIsSectionDialogOpen: (open: boolean) => void;
|
|
newSectionType: 'fields' | 'bom';
|
|
setNewSectionType: (type: 'fields' | 'bom') => void;
|
|
newSectionTitle: string;
|
|
setNewSectionTitle: (title: string) => void;
|
|
newSectionDescription: string;
|
|
setNewSectionDescription: (description: string) => void;
|
|
handleAddSection: () => void;
|
|
// 템플릿 선택 관련 props
|
|
sectionInputMode: 'custom' | 'template';
|
|
setSectionInputMode: (mode: 'custom' | 'template') => void;
|
|
sectionTemplates: SectionTemplate[];
|
|
selectedTemplateId: number | null;
|
|
setSelectedTemplateId: (id: number | null) => void;
|
|
handleLinkTemplate: (template: SectionTemplate) => void;
|
|
}
|
|
|
|
export function SectionDialog({
|
|
isSectionDialogOpen,
|
|
setIsSectionDialogOpen,
|
|
newSectionType,
|
|
setNewSectionType,
|
|
newSectionTitle,
|
|
setNewSectionTitle,
|
|
newSectionDescription,
|
|
setNewSectionDescription,
|
|
handleAddSection,
|
|
sectionInputMode,
|
|
setSectionInputMode,
|
|
sectionTemplates,
|
|
selectedTemplateId,
|
|
setSelectedTemplateId,
|
|
handleLinkTemplate,
|
|
}: SectionDialogProps) {
|
|
const handleClose = () => {
|
|
setIsSectionDialogOpen(false);
|
|
setNewSectionType('fields');
|
|
setNewSectionTitle('');
|
|
setNewSectionDescription('');
|
|
setSectionInputMode('custom');
|
|
setSelectedTemplateId(null);
|
|
};
|
|
|
|
// 템플릿 선택 시 폼에 값 채우기
|
|
const handleSelectTemplate = (template: SectionTemplate) => {
|
|
setSelectedTemplateId(template.id);
|
|
setNewSectionTitle(template.template_name);
|
|
setNewSectionDescription(template.description || '');
|
|
setNewSectionType(template.section_type === 'BOM' ? 'bom' : 'fields');
|
|
};
|
|
|
|
return (
|
|
<Dialog open={isSectionDialogOpen} onOpenChange={(open) => {
|
|
if (!open) handleClose();
|
|
else setIsSectionDialogOpen(open);
|
|
}}>
|
|
<DialogContent className="max-w-[95vw] sm:max-w-[600px] max-h-[90vh] flex flex-col p-0">
|
|
<DialogHeader className="sticky top-0 bg-white z-10 px-6 py-4 border-b">
|
|
<DialogTitle>섹션 추가</DialogTitle>
|
|
<DialogDescription>
|
|
새로운 섹션을 추가하거나 기존 템플릿을 연결합니다.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<div className="flex-1 overflow-y-auto px-6 py-4 space-y-4">
|
|
{/* 입력 모드 선택 */}
|
|
<div className="flex gap-2 p-1 bg-gray-100 rounded">
|
|
<Button
|
|
variant={sectionInputMode === 'custom' ? 'default' : 'ghost'}
|
|
size="sm"
|
|
onClick={() => {
|
|
setSectionInputMode('custom');
|
|
setSelectedTemplateId(null);
|
|
}}
|
|
className="flex-1"
|
|
>
|
|
직접 입력
|
|
</Button>
|
|
<Button
|
|
variant={sectionInputMode === 'template' ? 'default' : 'ghost'}
|
|
size="sm"
|
|
onClick={() => setSectionInputMode('template')}
|
|
className="flex-1"
|
|
>
|
|
템플릿 선택
|
|
</Button>
|
|
</div>
|
|
|
|
{/* 템플릿 목록 */}
|
|
{sectionInputMode === 'template' && (
|
|
<div className="border rounded p-3 space-y-2 max-h-[300px] overflow-y-auto">
|
|
<div className="flex items-center justify-between mb-2">
|
|
<Label>섹션 템플릿 목록</Label>
|
|
</div>
|
|
{sectionTemplates.length === 0 ? (
|
|
<p className="text-sm text-muted-foreground text-center py-4">
|
|
등록된 섹션 템플릿이 없습니다.<br/>
|
|
섹션 탭에서 템플릿을 먼저 등록해주세요.
|
|
</p>
|
|
) : (
|
|
<div className="space-y-2">
|
|
{sectionTemplates.map(template => (
|
|
<div
|
|
key={template.id}
|
|
className={`p-3 border rounded cursor-pointer transition-colors ${
|
|
selectedTemplateId === template.id
|
|
? 'bg-blue-50 border-blue-300'
|
|
: 'hover:bg-gray-50'
|
|
}`}
|
|
onClick={() => handleSelectTemplate(template)}
|
|
>
|
|
<div className="flex items-start justify-between">
|
|
<div className="flex-1">
|
|
<div className="flex items-center gap-2">
|
|
{template.section_type === 'BOM' ? (
|
|
<Package className="h-4 w-4 text-orange-500" />
|
|
) : (
|
|
<FileText className="h-4 w-4 text-blue-500" />
|
|
)}
|
|
<span className="font-medium">{template.template_name}</span>
|
|
<Badge variant="outline" className="text-xs">
|
|
{template.section_type === 'BOM' ? '모듈(BOM)' : '일반'}
|
|
</Badge>
|
|
</div>
|
|
{template.description && (
|
|
<p className="text-xs text-muted-foreground mt-1">{template.description}</p>
|
|
)}
|
|
{template.fields && template.fields.length > 0 && (
|
|
<p className="text-xs text-blue-600 mt-1">
|
|
{template.fields.length}개 항목 포함
|
|
</p>
|
|
)}
|
|
</div>
|
|
{selectedTemplateId === template.id && (
|
|
<Check className="h-5 w-5 text-blue-600" />
|
|
)}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{/* 직접 입력 폼 또는 선택된 템플릿 정보 표시 */}
|
|
{(sectionInputMode === 'custom' || selectedTemplateId) && (
|
|
<>
|
|
{/* 섹션 유형 선택 - 템플릿 선택 시 비활성화 */}
|
|
<div>
|
|
<Label className="mb-3 block">섹션 유형 *</Label>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
{/* 일반 섹션 */}
|
|
<div
|
|
onClick={() => {
|
|
if (sectionInputMode === 'custom') setNewSectionType('fields');
|
|
}}
|
|
className={`flex items-center gap-3 p-4 border rounded-lg transition-all ${
|
|
sectionInputMode === 'template' ? 'opacity-60 cursor-not-allowed' : 'cursor-pointer'
|
|
} ${
|
|
newSectionType === 'fields'
|
|
? 'border-blue-500 bg-blue-50 ring-2 ring-blue-500'
|
|
: 'border-gray-200 hover:border-gray-300'
|
|
}`}
|
|
>
|
|
<FileText className={`h-5 w-5 ${newSectionType === 'fields' ? 'text-blue-600' : 'text-gray-400'}`} />
|
|
<div className="flex-1">
|
|
<div className={`font-medium ${newSectionType === 'fields' ? 'text-blue-900' : 'text-gray-900'}`}>
|
|
일반 섹션
|
|
</div>
|
|
<div className="text-xs text-gray-500">필드 항목 관리</div>
|
|
</div>
|
|
{newSectionType === 'fields' && (
|
|
<Check className="h-5 w-5 text-blue-600" />
|
|
)}
|
|
</div>
|
|
{/* BOM 섹션 */}
|
|
<div
|
|
onClick={() => {
|
|
if (sectionInputMode === 'custom') setNewSectionType('bom');
|
|
}}
|
|
className={`flex items-center gap-3 p-4 border rounded-lg transition-all ${
|
|
sectionInputMode === 'template' ? 'opacity-60 cursor-not-allowed' : 'cursor-pointer'
|
|
} ${
|
|
newSectionType === 'bom'
|
|
? 'border-blue-500 bg-blue-50 ring-2 ring-blue-500'
|
|
: 'border-gray-200 hover:border-gray-300'
|
|
}`}
|
|
>
|
|
<Package className={`h-5 w-5 ${newSectionType === 'bom' ? 'text-blue-600' : 'text-gray-400'}`} />
|
|
<div className="flex-1">
|
|
<div className={`font-medium ${newSectionType === 'bom' ? 'text-blue-900' : 'text-gray-900'}`}>
|
|
모듈 섹션 (BOM)
|
|
</div>
|
|
<div className="text-xs text-gray-500">자재명세서 관리</div>
|
|
</div>
|
|
{newSectionType === 'bom' && (
|
|
<Check className="h-5 w-5 text-blue-600" />
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<Label>섹션 제목 *</Label>
|
|
<Input
|
|
value={newSectionTitle}
|
|
onChange={(e) => setNewSectionTitle(e.target.value)}
|
|
placeholder={newSectionType === 'bom' ? '예: BOM 구성' : '예: 기본 정보'}
|
|
disabled={sectionInputMode === 'template'}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<Label>설명 (선택)</Label>
|
|
<Textarea
|
|
value={newSectionDescription}
|
|
onChange={(e) => setNewSectionDescription(e.target.value)}
|
|
placeholder="섹션에 대한 설명"
|
|
disabled={sectionInputMode === 'template'}
|
|
/>
|
|
</div>
|
|
|
|
{sectionInputMode === 'template' && selectedTemplateId && (
|
|
<div className="bg-green-50 p-3 rounded-md border border-green-200">
|
|
<p className="text-sm text-green-700">
|
|
<strong>템플릿 연결:</strong> 선택한 템플릿을 페이지에 연결합니다.
|
|
템플릿에 포함된 항목들도 함께 추가됩니다.
|
|
</p>
|
|
</div>
|
|
)}
|
|
|
|
{newSectionType === 'bom' && sectionInputMode === 'custom' && (
|
|
<div className="bg-blue-50 p-3 rounded-md">
|
|
<p className="text-sm text-blue-700">
|
|
<strong>BOM 섹션:</strong> 자재명세서(BOM) 관리를 위한 전용 섹션입니다.
|
|
부품 구성, 수량, 단가 등을 관리할 수 있습니다.
|
|
</p>
|
|
</div>
|
|
)}
|
|
</>
|
|
)}
|
|
</div>
|
|
|
|
<DialogFooter className="shrink-0 bg-white z-10 px-6 py-4 border-t flex-col sm:flex-row gap-2">
|
|
<Button variant="outline" onClick={handleClose} className="w-full sm:w-auto">
|
|
취소
|
|
</Button>
|
|
{sectionInputMode === 'template' && selectedTemplateId ? (
|
|
<Button
|
|
onClick={() => {
|
|
const template = sectionTemplates.find(t => t.id === selectedTemplateId);
|
|
if (template) handleLinkTemplate(template);
|
|
}}
|
|
className="w-full sm:w-auto"
|
|
>
|
|
템플릿 연결
|
|
</Button>
|
|
) : (
|
|
<Button
|
|
onClick={handleAddSection}
|
|
className="w-full sm:w-auto"
|
|
disabled={sectionInputMode === 'template' && !selectedTemplateId}
|
|
>
|
|
추가
|
|
</Button>
|
|
)}
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
} |