From 83437e76dd7d9c0395e91136893bef161be6ae36 Mon Sep 17 00:00:00 2001
From: syc0123
Date: Fri, 27 Feb 2026 18:11:59 +0900
Subject: [PATCH] feat: Enhance form validation and modal handling in various
components
- Added `isInModal` prop to `ScreenModal` and `InteractiveScreenViewerDynamic` for improved modal context awareness.
- Implemented `isFieldEmpty` and `checkAllRequiredFieldsFilled` utility functions to validate required fields in forms.
- Updated `SaveModal` and `ButtonPrimaryComponent` to disable save actions when required fields are missing, enhancing user feedback.
- Introduced error messages for required fields in modals to guide users in completing necessary inputs.
Made-with: Cursor
---
frontend/components/common/ScreenModal.tsx | 2 +
.../screen/InteractiveScreenViewerDynamic.tsx | 47 +++++++++++-
frontend/components/screen/SaveModal.tsx | 10 ++-
.../button-primary/ButtonPrimaryComponent.tsx | 13 +++-
.../ButtonPrimaryComponent.tsx | 22 +++---
frontend/lib/utils/formValidation.ts | 73 +++++++++++++++++++
6 files changed, 151 insertions(+), 16 deletions(-)
diff --git a/frontend/components/common/ScreenModal.tsx b/frontend/components/common/ScreenModal.tsx
index 47cb7549..a57b5ed4 100644
--- a/frontend/components/common/ScreenModal.tsx
+++ b/frontend/components/common/ScreenModal.tsx
@@ -1218,6 +1218,7 @@ export const ScreenModal: React.FC = ({ className }) => {
userId={userId}
userName={userName}
companyCode={user?.companyCode}
+ isInModal={true}
/>
);
});
@@ -1261,6 +1262,7 @@ export const ScreenModal: React.FC = ({ className }) => {
userId={userId}
userName={userName}
companyCode={user?.companyCode}
+ isInModal={true}
/>
);
})}
diff --git a/frontend/components/screen/InteractiveScreenViewerDynamic.tsx b/frontend/components/screen/InteractiveScreenViewerDynamic.tsx
index a35c5ed2..249f92eb 100644
--- a/frontend/components/screen/InteractiveScreenViewerDynamic.tsx
+++ b/frontend/components/screen/InteractiveScreenViewerDynamic.tsx
@@ -14,6 +14,7 @@ import { DynamicWebTypeRenderer } from "@/lib/registry";
import { DynamicComponentRenderer } from "@/lib/registry/DynamicComponentRenderer";
import { filterDOMProps } from "@/lib/utils/domPropsFilter";
import { isFileComponent, isDataTableComponent, isButtonComponent } from "@/lib/utils/componentTypeUtils";
+import { isFieldEmpty } from "@/lib/utils/formValidation";
import { FlowButtonGroup } from "./widgets/FlowButtonGroup";
import { FlowVisibilityConfig } from "@/types/control-management";
import { findAllButtonGroups } from "@/lib/utils/flowButtonGroupUtils";
@@ -447,6 +448,7 @@ export const InteractiveScreenViewerDynamic: React.FC {
// buttonActions.ts가 이미 처리함
}}
+ isInModal={isInModal}
// 탭 관련 정보 전달
parentTabId={parentTabId}
parentTabsComponentId={parentTabsComponentId}
@@ -956,7 +958,7 @@ export const InteractiveScreenViewerDynamic: React.FC {
const compType = (component as any).componentType || "";
const isSplitLine = type === "component" && compType === "v2-split-line";
@@ -1204,7 +1242,7 @@ export const InteractiveScreenViewerDynamic: React.FC 0 ? "visible" : undefined),
+ overflow: (isSplitActive && adjustedW < origW) ? "hidden" : ((labelOffset > 0 || showRequiredError) ? "visible" : undefined),
willChange: canvasSplit.isDragging && isSplitActive ? "left, width" as const : undefined,
transition: isSplitActive
? (canvasSplit.isDragging ? "none" : "left 0.15s ease-out, width 0.15s ease-out")
@@ -1317,6 +1355,11 @@ export const InteractiveScreenViewerDynamic: React.FC
+ 필수 입력 항목입니다
+
+ )}
{/* 팝업 화면 렌더링 */}
diff --git a/frontend/components/screen/SaveModal.tsx b/frontend/components/screen/SaveModal.tsx
index 1c848d6a..18c9127b 100644
--- a/frontend/components/screen/SaveModal.tsx
+++ b/frontend/components/screen/SaveModal.tsx
@@ -11,6 +11,7 @@ import { screenApi } from "@/lib/api/screen";
import { dynamicFormApi, DynamicFormData } from "@/lib/api/dynamicForm";
import { ComponentData } from "@/lib/types/screen";
import { useAuth } from "@/hooks/useAuth";
+import { checkAllRequiredFieldsFilled } from "@/lib/utils/formValidation";
interface SaveModalProps {
isOpen: boolean;
@@ -304,6 +305,7 @@ export const SaveModal: React.FC = ({
};
const dynamicSize = calculateDynamicSize();
+ const isRequiredFieldsMissing = !checkAllRequiredFieldsFilled(components, formData);
return (