From 3e19218382b08b0950e534a222b535f59b57a4bc Mon Sep 17 00:00:00 2001 From: kjs Date: Tue, 3 Feb 2026 11:02:13 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=ED=85=8C=EC=9D=B4=EB=B8=94=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20=ED=8E=98=EC=9D=B4=EC=A7=80=EC=9D=98=20?= =?UTF-8?q?=EC=BB=AC=EB=9F=BC=20=EC=84=A4=EC=A0=95=20=EC=A0=80=EC=9E=A5=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 컬럼의 detailSettings을 동적으로 계산하여 다양한 입력 타입에 맞는 설정을 포함하도록 수정하였습니다. - Numbering, Entity, Code 타입에 대한 추가 설정을 detailSettings에 포함시켜 데이터 저장 시 유연성을 높였습니다. - V2Input 컴포넌트에서 채번 규칙 ID를 formData에 저장하는 기능을 추가하여 데이터 처리의 일관성을 강화하였습니다. - 레이아웃 변환 함수에 메타데이터를 포함하여 레이아웃 정보를 더욱 풍부하게 하였습니다. --- .../admin/systemMng/tableMngList/page.tsx | 87 ++++++++++++++++--- frontend/components/v2/V2Input.tsx | 11 +++ .../components/v2-input/V2InputRenderer.tsx | 8 +- frontend/lib/utils/layoutV2Converter.ts | 4 + 4 files changed, 97 insertions(+), 13 deletions(-) diff --git a/frontend/app/(main)/admin/systemMng/tableMngList/page.tsx b/frontend/app/(main)/admin/systemMng/tableMngList/page.tsx index 73e5d282..17c52897 100644 --- a/frontend/app/(main)/admin/systemMng/tableMngList/page.tsx +++ b/frontend/app/(main)/admin/systemMng/tableMngList/page.tsx @@ -773,18 +773,81 @@ export default function TableManagementPage() { // 2. 모든 컬럼 설정 저장 if (columns.length > 0) { - const columnSettings = columns.map((column) => ({ - columnName: column.columnName, // 실제 DB 컬럼명 (변경 불가) - columnLabel: column.displayName, // 사용자가 입력한 표시명 - inputType: column.inputType || "text", - detailSettings: column.detailSettings || "", - description: column.description || "", - codeCategory: column.codeCategory || "", - codeValue: column.codeValue || "", - referenceTable: column.referenceTable || "", - referenceColumn: column.referenceColumn || "", - displayColumn: column.displayColumn || "", // 🎯 Entity 조인에서 표시할 컬럼명 - })); + const columnSettings = columns.map((column) => { + // detailSettings 계산 + let finalDetailSettings = column.detailSettings || ""; + + // 🆕 Numbering 타입인 경우 numberingRuleId를 detailSettings에 포함 + if (column.inputType === "numbering" && column.numberingRuleId) { + let existingSettings: Record = {}; + if (typeof finalDetailSettings === "string" && finalDetailSettings.trim().startsWith("{")) { + try { + existingSettings = JSON.parse(finalDetailSettings); + } catch { + existingSettings = {}; + } + } + const numberingSettings = { + ...existingSettings, + numberingRuleId: column.numberingRuleId, + }; + finalDetailSettings = JSON.stringify(numberingSettings); + console.log("🔧 전체저장 - Numbering 설정 JSON 생성:", { + columnName: column.columnName, + numberingRuleId: column.numberingRuleId, + finalDetailSettings, + }); + } + + // 🆕 Entity 타입인 경우 detailSettings에 엔티티 설정 포함 + if (column.inputType === "entity" && column.referenceTable) { + let existingSettings: Record = {}; + if (typeof finalDetailSettings === "string" && finalDetailSettings.trim().startsWith("{")) { + try { + existingSettings = JSON.parse(finalDetailSettings); + } catch { + existingSettings = {}; + } + } + const entitySettings = { + ...existingSettings, + entityTable: column.referenceTable, + entityCodeColumn: column.referenceColumn || "id", + entityLabelColumn: column.displayColumn || "name", + }; + finalDetailSettings = JSON.stringify(entitySettings); + } + + // 🆕 Code 타입인 경우 hierarchyRole을 detailSettings에 포함 + if (column.inputType === "code" && column.hierarchyRole) { + let existingSettings: Record = {}; + if (typeof finalDetailSettings === "string" && finalDetailSettings.trim().startsWith("{")) { + try { + existingSettings = JSON.parse(finalDetailSettings); + } catch { + existingSettings = {}; + } + } + const codeSettings = { + ...existingSettings, + hierarchyRole: column.hierarchyRole, + }; + finalDetailSettings = JSON.stringify(codeSettings); + } + + return { + columnName: column.columnName, // 실제 DB 컬럼명 (변경 불가) + columnLabel: column.displayName, // 사용자가 입력한 표시명 + inputType: column.inputType || "text", + detailSettings: finalDetailSettings, + description: column.description || "", + codeCategory: column.codeCategory || "", + codeValue: column.codeValue || "", + referenceTable: column.referenceTable || "", + referenceColumn: column.referenceColumn || "", + displayColumn: column.displayColumn || "", // 🎯 Entity 조인에서 표시할 컬럼명 + }; + }); // console.log("저장할 전체 설정:", { tableLabel, tableDescription, columnSettings }); diff --git a/frontend/components/v2/V2Input.tsx b/frontend/components/v2/V2Input.tsx index be4dd83e..edaefffb 100644 --- a/frontend/components/v2/V2Input.tsx +++ b/frontend/components/v2/V2Input.tsx @@ -334,6 +334,8 @@ export const V2Input = forwardRef((props, ref) => // formData 추출 (채번규칙 날짜 컬럼 기준 생성 시 사용) const formData = (props as any).formData || {}; const columnName = (props as any).columnName; + // onFormDataChange 추출 (채번 규칙 ID를 formData에 저장하기 위함) + const onFormDataChange = (props as any).onFormDataChange; // config가 없으면 기본값 사용 const config = (configProp || { type: "text" }) as V2InputConfig & { @@ -526,6 +528,15 @@ export const V2Input = forwardRef((props, ref) => try { const parsed = JSON.parse(targetColumn.detailSettings); numberingRuleIdRef.current = parsed.numberingRuleId || null; + + // 🆕 채번 규칙 ID를 formData에 저장 (저장 시 allocateCode 호출을 위해) + if (parsed.numberingRuleId && onFormDataChange && columnName) { + onFormDataChange(`${columnName}_numberingRuleId`, parsed.numberingRuleId); + console.log("🔧 채번 규칙 ID를 formData에 저장:", { + key: `${columnName}_numberingRuleId`, + value: parsed.numberingRuleId, + }); + } } catch { // JSON 파싱 실패 } diff --git a/frontend/lib/registry/components/v2-input/V2InputRenderer.tsx b/frontend/lib/registry/components/v2-input/V2InputRenderer.tsx index 1afc2075..52a230fa 100644 --- a/frontend/lib/registry/components/v2-input/V2InputRenderer.tsx +++ b/frontend/lib/registry/components/v2-input/V2InputRenderer.tsx @@ -30,15 +30,21 @@ export class V2InputRenderer extends AutoRegisteringComponentRenderer { } }; + // 라벨: style.labelText 우선, 없으면 component.label 사용 + // style.labelDisplay가 false면 라벨 숨김 + const style = component.style || {}; + const effectiveLabel = style.labelDisplay === false ? undefined : (style.labelText || component.label); + return ( Date: Tue, 3 Feb 2026 11:57:13 +0900 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=9C=84=EC=A0=AF=20=EB=B0=8F=20=EB=AF=B8=EB=94=94=EC=96=B4=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=95=84=EC=9B=83=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ImageWidget 컴포넌트에 size 및 style props를 추가하여 유연한 크기 조정 및 스타일 적용이 가능하도록 수정하였습니다. - 이미지 표시 및 업로드 영역의 레이아웃을 개선하여, 부모 컨테이너의 크기를 기반으로 동적으로 조정되도록 하였습니다. - V2Media 컴포넌트의 구조를 변경하여, 전체 높이를 유지하고 이미지 미리보기 영역의 flex 속성을 조정하여 일관된 사용자 경험을 제공하도록 하였습니다. - 관련된 CSS 클래스를 업데이트하여 반응형 디자인을 강화하였습니다. --- .../screen/widgets/types/ImageWidget.tsx | 26 +++++++++++++------ frontend/components/v2/V2Media.tsx | 14 +++++----- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/frontend/components/screen/widgets/types/ImageWidget.tsx b/frontend/components/screen/widgets/types/ImageWidget.tsx index 5c81ca9c..fdcb1f27 100644 --- a/frontend/components/screen/widgets/types/ImageWidget.tsx +++ b/frontend/components/screen/widgets/types/ImageWidget.tsx @@ -9,15 +9,17 @@ import { WidgetComponent } from "@/types/screen"; import { toast } from "sonner"; import { apiClient, getFullImageUrl } from "@/lib/api/client"; -export const ImageWidget: React.FC = ({ +export const ImageWidget: React.FC = ({ component, value, onChange, readonly = false, - isDesignMode = false // 디자인 모드 여부 + isDesignMode = false, // 디자인 모드 여부 + size, // props로 전달된 size + style: propStyle, // props로 전달된 style }) => { const widget = component as WidgetComponent; - const { required, style } = widget; + const { required, style: widgetStyle } = widget; const fileInputRef = useRef(null); const [uploading, setUploading] = useState(false); @@ -25,8 +27,16 @@ export const ImageWidget: React.FC = ({ const rawImageUrl = value || widget.value || ""; const imageUrl = rawImageUrl ? getFullImageUrl(rawImageUrl) : ""; - // style에서 width, height 제거 (부모 컨테이너 크기 사용) - const filteredStyle = style ? { ...style, width: undefined, height: undefined } : {}; + // 🔧 컴포넌트 크기를 명시적으로 적용 (props.size 우선, 없으면 style에서 가져옴) + const effectiveSize = size || (widget as any).size || {}; + const effectiveStyle = propStyle || widgetStyle || {}; + const containerStyle: React.CSSProperties = { + width: effectiveSize.width ? `${effectiveSize.width}px` : effectiveStyle?.width || "100%", + height: effectiveSize.height ? `${effectiveSize.height}px` : effectiveStyle?.height || "100%", + }; + + // style에서 width, height 제거 (내부 요소용) + const filteredStyle = effectiveStyle ? { ...effectiveStyle, width: undefined, height: undefined } : {}; // 파일 선택 처리 const handleFileSelect = () => { @@ -120,11 +130,11 @@ export const ImageWidget: React.FC = ({ }; return ( -
+
{imageUrl ? ( // 이미지 표시 모드
= ({ ) : ( // 업로드 영역
+
{/* 이미지 미리보기 */} {preview && images.length > 0 && (
{images.map((src, index) => ( -
+
{`이미지