fix: 페이지 삭제 시 섹션 동기화 및 코드 정리

- 페이지 삭제 시 독립 섹션 목록 갱신 추가 (독립 엔티티 아키텍처)
- ItemForm 컴포넌트 분리 완료 (1607→415줄, 74% 감소)
- ItemMasterDataManagement 중복 코드 제거 (getInputTypeLabel 헬퍼)
- 문서 업데이트 (realtime-sync-fixes.md)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
byeongcheolryu
2025-11-28 15:25:33 +09:00
parent 65a8510c0b
commit 9d0cb073ba
16 changed files with 1462 additions and 1435 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -30,7 +30,6 @@ import {
transformPagesResponse,
transformSectionsResponse,
transformSectionTemplatesResponse,
transformMasterFieldsResponse,
transformFieldsResponse,
transformCustomTabsResponse,
transformUnitOptionsResponse,
@@ -78,41 +77,40 @@ const INPUT_TYPE_OPTIONS = [
{ value: 'textarea', label: '텍스트영역' }
];
// 입력 타입 라벨 변환 헬퍼 함수 (중복 코드 제거)
const getInputTypeLabel = (inputType: string | undefined): string => {
const labels: Record<string, string> = {
textbox: '텍스트박스',
number: '숫자',
dropdown: '드롭다운',
checkbox: '체크박스',
date: '날짜',
textarea: '텍스트영역',
};
return labels[inputType || ''] || '텍스트박스';
};
export function ItemMasterDataManagement() {
const {
itemPages,
loadItemPages,
addItemPage: _addItemPage,
updateItemPage,
deleteItemPage,
addSectionToPage: _addSectionToPage,
updateSection,
deleteSection,
addFieldToSection: _addFieldToSection,
updateField: _updateField,
deleteField: _deleteField,
reorderFields,
itemMasterFields,
loadItemMasterFields,
addItemMasterField: _addItemMasterField,
updateItemMasterField: _updateItemMasterField,
deleteItemMasterField: _deleteItemMasterField,
sectionTemplates,
loadSectionTemplates,
addSectionTemplate: _addSectionTemplate,
updateSectionTemplate: _updateSectionTemplate,
deleteSectionTemplate: _deleteSectionTemplate,
resetAllData,
tenantId: _tenantId,
// 2025-11-26 추가: 독립 엔티티 관리
independentSections,
loadIndependentSections,
independentFields: _independentFields,
loadIndependentFields,
refreshIndependentSections,
refreshIndependentFields,
linkSectionToPage,
unlinkSectionFromPage: _unlinkSectionFromPage,
linkFieldToSection,
unlinkFieldFromSection,
getSectionUsage,
@@ -133,7 +131,7 @@ export function ItemMasterDataManagement() {
pageCount: itemPages.length,
pages: itemPages.map(p => ({
id: p.id,
name: p.name,
name: p.page_name,
sectionsCount: p.sections.length,
sections: p.sections.map(s => ({
id: s.id,
@@ -160,8 +158,7 @@ export function ItemMasterDataManagement() {
isPageDialogOpen, setIsPageDialogOpen,
newPageName, setNewPageName, newPageItemType, setNewPageItemType,
editingPathPageId, setEditingPathPageId, editingAbsolutePath, setEditingAbsolutePath,
handleAddPage, handleDuplicatePage, handleDeletePage: _handleDeletePage,
handleUpdatePageName: _handleUpdatePageName, handleUpdateAbsolutePath: _handleUpdateAbsolutePath,
handleAddPage, handleDuplicatePage,
} = pageManagement;
const {
@@ -173,10 +170,9 @@ export function ItemMasterDataManagement() {
newSectionType, setNewSectionType,
sectionInputMode, setSectionInputMode,
selectedSectionTemplateId, setSelectedSectionTemplateId,
expandedSections: _expandedSections, setExpandedSections: _setExpandedSections,
handleAddSection, handleLinkTemplate,
handleEditSectionTitle, handleSaveSectionTitle,
handleUnlinkSection, handleDeleteSection: _handleDeleteSection, toggleSection: _toggleSection,
handleUnlinkSection,
} = sectionManagement;
const {
@@ -202,7 +198,7 @@ export function ItemMasterDataManagement() {
newFieldConditionFields, setNewFieldConditionFields,
newFieldConditionSections, setNewFieldConditionSections,
tempConditionValue, setTempConditionValue,
handleAddField, handleEditField, handleDeleteField: _handleDeleteField,
handleAddField, handleEditField,
} = fieldManagement;
const {
@@ -864,15 +860,7 @@ export function ItemMasterDataManagement() {
{unitOptions.map((option) => {
const columns = attributeColumns['units'] || [];
const hasColumns = columns.length > 0 && option.columnValues;
const inputTypeLabel =
option.inputType === 'textbox' ? '텍스트박스' :
option.inputType === 'number' ? '숫자' :
option.inputType === 'dropdown' ? '드롭다운' :
option.inputType === 'checkbox' ? '체크박스' :
option.inputType === 'date' ? '날짜' :
option.inputType === 'textarea' ? '텍스트영역' :
'텍스트박스';
return (
<div key={option.id} className="p-4 border rounded hover:bg-gray-50 transition-colors">
<div className="flex items-start justify-between gap-3">
@@ -880,7 +868,7 @@ export function ItemMasterDataManagement() {
<div className="flex items-center gap-2">
<span className="font-medium text-base">{option.label}</span>
{option.inputType && (
<Badge variant="outline" className="text-xs">{inputTypeLabel}</Badge>
<Badge variant="outline" className="text-xs">{getInputTypeLabel(option.inputType)}</Badge>
)}
{option.required && (
<Badge variant="destructive" className="text-xs"></Badge>
@@ -966,15 +954,7 @@ export function ItemMasterDataManagement() {
{materialOptions.map((option) => {
const columns = attributeColumns['materials'] || [];
const hasColumns = columns.length > 0 && option.columnValues;
const inputTypeLabel =
option.inputType === 'textbox' ? '텍스트박스' :
option.inputType === 'number' ? '숫자' :
option.inputType === 'dropdown' ? '드롭다운' :
option.inputType === 'checkbox' ? '체크박스' :
option.inputType === 'date' ? '날짜' :
option.inputType === 'textarea' ? '텍스트영역' :
'텍스트박스';
return (
<div key={option.id} className="p-4 border rounded hover:bg-gray-50 transition-colors">
<div className="flex items-start justify-between gap-3">
@@ -982,7 +962,7 @@ export function ItemMasterDataManagement() {
<div className="flex items-center gap-2">
<span className="font-medium text-base">{option.label}</span>
{option.inputType && (
<Badge variant="outline" className="text-xs">{inputTypeLabel}</Badge>
<Badge variant="outline" className="text-xs">{getInputTypeLabel(option.inputType)}</Badge>
)}
{option.required && (
<Badge variant="destructive" className="text-xs"></Badge>
@@ -1068,15 +1048,8 @@ export function ItemMasterDataManagement() {
{surfaceTreatmentOptions.map((option) => {
const columns = attributeColumns['surface'] || [];
const hasColumns = columns.length > 0 && option.columnValues;
const inputTypeLabel =
option.inputType === 'textbox' ? '텍스트박스' :
option.inputType === 'number' ? '숫자' :
option.inputType === 'dropdown' ? '드롭다운' :
option.inputType === 'checkbox' ? '체크박스' :
option.inputType === 'date' ? '날짜' :
option.inputType === 'textarea' ? '텍스트영역' :
'텍스트박스';
const inputTypeLabel = getInputTypeLabel(option.inputType);
return (
<div key={option.id} className="p-4 border rounded hover:bg-gray-50 transition-colors">
<div className="flex items-start justify-between gap-3">
@@ -1173,15 +1146,8 @@ export function ItemMasterDataManagement() {
<div className="space-y-3">
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
{propertiesArray.map((property: any) => {
const inputTypeLabel =
property.type === 'textbox' ? '텍스트박스' :
property.type === 'number' ? '숫자' :
property.type === 'dropdown' ? '드롭다운' :
property.type === 'checkbox' ? '체크박스' :
property.type === 'date' ? '날짜' :
property.type === 'textarea' ? '텍스트영역' :
'텍스트박스';
const inputTypeLabel = getInputTypeLabel(property.type);
return (
<div key={property.id} className="p-4 border rounded hover:bg-gray-50 transition-colors">
<div className="flex items-start justify-between gap-3">
@@ -1283,15 +1249,8 @@ export function ItemMasterDataManagement() {
{currentOptions.map((option) => {
const columns = attributeColumns[currentTabKey] || [];
const hasColumns = columns.length > 0 && option.columnValues;
const inputTypeLabel =
option.inputType === 'textbox' ? '텍스트박스' :
option.inputType === 'number' ? '숫자' :
option.inputType === 'dropdown' ? '드롭다운' :
option.inputType === 'checkbox' ? '체크박스' :
option.inputType === 'date' ? '날짜' :
option.inputType === 'textarea' ? '텍스트영역' :
'텍스트박스';
const inputTypeLabel = getInputTypeLabel(option.inputType);
return (
<div key={option.id} className="p-4 border rounded hover:bg-gray-50 transition-colors">
<div className="flex items-start justify-between gap-3">

View File

@@ -5,7 +5,7 @@ import { Badge } from '@/components/ui/badge';
import {
GripVertical,
Edit,
X
Unlink
} from 'lucide-react';
// 입력방식 옵션 (ItemMasterDataManagement에서 사용하는 상수)
@@ -111,8 +111,9 @@ export function DraggableField({ field, index, moveField, onDelete, onEdit }: Dr
size="sm"
variant="ghost"
onClick={onDelete}
title="섹션에서 연결 해제"
>
<X className="h-4 w-4 text-red-500" />
<Unlink className="h-4 w-4 text-orange-500" />
</Button>
</div>
</div>

View File

@@ -8,7 +8,7 @@ import {
Edit,
Check,
X,
Trash2
Unlink
} from 'lucide-react';
interface DraggableSectionProps {
@@ -120,7 +120,7 @@ export function DraggableSection({
onClick={onDelete}
title="페이지에서 연결 해제"
>
<X className="h-4 w-4 text-gray-500" />
<Unlink className="h-4 w-4 text-orange-500" />
</Button>
</div>
</div>

View File

@@ -17,7 +17,7 @@ import {
import { type ItemType } from '@/types/item';
interface ItemTypeSelectProps {
value?: ItemType;
value?: ItemType | '';
onChange: (value: ItemType) => void;
disabled?: boolean;
required?: boolean;
@@ -56,7 +56,7 @@ export default function ItemTypeSelect({
)}
<Select
value={value}
value={value || undefined}
onValueChange={onChange}
disabled={disabled}
>