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:
File diff suppressed because it is too large
Load Diff
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user