feat(UniversalFormModal): 수정 모드 INSERT/UPDATE/DELETE 지원

_groupedData를 테이블 섹션에 초기화하여 기존 품목 표시
originalGroupedData로 원본 데이터 보관하여 변경 추적
handleUniversalFormModalTableSectionSave()에 INSERT/UPDATE/DELETE 분기 로직 구현
EditModal, ConditionalSectionViewer에서 UniversalFormModal 감지 시 onSave 미전달
저장 완료 후 closeEditModal 이벤트 발생
This commit is contained in:
SeongHyun Kim
2025-12-19 16:08:27 +09:00
parent 9fb94da493
commit a1b05b8982
5 changed files with 262 additions and 56 deletions

View File

@@ -150,46 +150,54 @@ export function ConditionalSectionViewer({
/* 실행 모드: 실제 화면 렌더링 */
<div className="w-full">
{/* 화면 크기만큼의 절대 위치 캔버스 */}
<div
className="relative mx-auto"
style={{
width: screenResolution?.width ? `${screenResolution.width}px` : "100%",
height: screenResolution?.height ? `${screenResolution.height}px` : "auto",
minHeight: "200px",
}}
>
{components.map((component) => {
const { position = { x: 0, y: 0, z: 1 }, size = { width: 200, height: 40 } } = component;
return (
<div
key={component.id}
className="absolute"
style={{
left: position.x || 0,
top: position.y || 0,
width: size.width || 200,
height: size.height || 40,
zIndex: position.z || 1,
}}
>
<DynamicComponentRenderer
component={component}
isInteractive={true}
screenId={screenInfo?.id}
tableName={screenInfo?.tableName}
userId={userId}
userName={userName}
companyCode={user?.companyCode}
formData={enhancedFormData}
onFormDataChange={onFormDataChange}
groupedData={groupedData}
onSave={onSave}
/>
</div>
);
})}
</div>
{/* UniversalFormModal이 있으면 onSave 전달하지 않음 (자체 저장 로직 사용) */}
{(() => {
const hasUniversalFormModal = components.some(
(c) => c.componentType === "universal-form-modal"
);
return (
<div
className="relative mx-auto"
style={{
width: screenResolution?.width ? `${screenResolution.width}px` : "100%",
height: screenResolution?.height ? `${screenResolution.height}px` : "auto",
minHeight: "200px",
}}
>
{components.map((component) => {
const { position = { x: 0, y: 0, z: 1 }, size = { width: 200, height: 40 } } = component;
return (
<div
key={component.id}
className="absolute"
style={{
left: position.x || 0,
top: position.y || 0,
width: size.width || 200,
height: size.height || 40,
zIndex: position.z || 1,
}}
>
<DynamicComponentRenderer
component={component}
isInteractive={true}
screenId={screenInfo?.id}
tableName={screenInfo?.tableName}
userId={userId}
userName={userName}
companyCode={user?.companyCode}
formData={enhancedFormData}
onFormDataChange={onFormDataChange}
groupedData={groupedData}
onSave={hasUniversalFormModal ? undefined : onSave}
/>
</div>
);
})}
</div>
);
})()}
</div>
)}
</>

View File

@@ -339,6 +339,27 @@ export function TableSectionRenderer({
// 날짜 일괄 적용 완료 플래그 (컬럼별로 한 번만 적용)
const [batchAppliedFields, setBatchAppliedFields] = useState<Set<string>>(new Set());
// 초기 데이터 로드 완료 플래그 (무한 루프 방지)
const initialDataLoadedRef = React.useRef(false);
// formData에서 초기 테이블 데이터 로드 (수정 모드에서 _groupedData 표시)
useEffect(() => {
// 이미 초기화되었으면 스킵
if (initialDataLoadedRef.current) return;
const tableSectionKey = `_tableSection_${sectionId}`;
const initialData = formData[tableSectionKey];
if (Array.isArray(initialData) && initialData.length > 0) {
console.log("[TableSectionRenderer] 초기 데이터 로드:", {
sectionId,
itemCount: initialData.length,
});
setTableData(initialData);
initialDataLoadedRef.current = true;
}
}, [sectionId, formData]);
// RepeaterColumnConfig로 변환
const columns: RepeaterColumnConfig[] = (tableConfig.columns || []).map(convertToRepeaterColumn);

View File

@@ -195,6 +195,10 @@ export function UniversalFormModalComponent({
// 로딩 상태
const [saving, setSaving] = useState(false);
// 🆕 수정 모드: 원본 그룹 데이터 (INSERT/UPDATE/DELETE 추적용)
const [originalGroupedData, setOriginalGroupedData] = useState<any[]>([]);
const groupedDataInitializedRef = useRef(false);
// 삭제 확인 다이얼로그
const [deleteDialog, setDeleteDialog] = useState<{
open: boolean;
@@ -304,6 +308,12 @@ export function UniversalFormModalComponent({
console.log(`[UniversalFormModal] 반복 섹션 병합: ${sectionKey}`, items);
}
}
// 🆕 수정 모드: 원본 그룹 데이터 전달 (UPDATE/DELETE 추적용)
if (originalGroupedData.length > 0) {
event.detail.formData._originalGroupedData = originalGroupedData;
console.log(`[UniversalFormModal] 원본 그룹 데이터 병합: ${originalGroupedData.length}`);
}
};
window.addEventListener("beforeFormSave", handleBeforeFormSave as EventListener);
@@ -311,7 +321,37 @@ export function UniversalFormModalComponent({
return () => {
window.removeEventListener("beforeFormSave", handleBeforeFormSave as EventListener);
};
}, [formData, repeatSections, config.sections]);
}, [formData, repeatSections, config.sections, originalGroupedData]);
// 🆕 수정 모드: _groupedData가 있으면 테이블 섹션 초기화
useEffect(() => {
if (!_groupedData || _groupedData.length === 0) return;
if (groupedDataInitializedRef.current) return; // 이미 초기화됨
// 테이블 타입 섹션 찾기
const tableSection = config.sections.find((s) => s.type === "table");
if (!tableSection) {
console.log("[UniversalFormModal] 테이블 섹션 없음 - _groupedData 무시");
return;
}
console.log("[UniversalFormModal] 수정 모드 - 테이블 섹션 초기화:", {
sectionId: tableSection.id,
itemCount: _groupedData.length,
});
// 원본 데이터 저장 (수정/삭제 추적용)
setOriginalGroupedData(JSON.parse(JSON.stringify(_groupedData)));
// 테이블 섹션 데이터 설정
const tableSectionKey = `_tableSection_${tableSection.id}`;
setFormData((prev) => ({
...prev,
[tableSectionKey]: _groupedData,
}));
groupedDataInitializedRef.current = true;
}, [_groupedData, config.sections]);
// 필드 레벨 linkedFieldGroup 데이터 로드
useEffect(() => {