테스트 프로젝트 테이블 생성 및 오류들 수정
This commit is contained in:
@@ -185,6 +185,9 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
|
||||
currentValue,
|
||||
hasFormData: !!formData,
|
||||
formDataKeys: formData ? Object.keys(formData) : [],
|
||||
autoGeneration: component.autoGeneration,
|
||||
hidden: component.hidden,
|
||||
isInteractive,
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -200,6 +203,9 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
|
||||
config={component.componentConfig}
|
||||
componentConfig={component.componentConfig}
|
||||
value={currentValue} // formData에서 추출한 현재 값 전달
|
||||
// 새로운 기능들 전달
|
||||
autoGeneration={component.autoGeneration}
|
||||
hidden={component.hidden}
|
||||
// React 전용 props들은 직접 전달 (DOM에 전달되지 않음)
|
||||
isInteractive={isInteractive}
|
||||
formData={formData}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
import { ComponentRendererProps } from "@/types/component";
|
||||
import { ButtonPrimaryConfig } from "./types";
|
||||
import {
|
||||
@@ -71,6 +71,21 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
|
||||
config: any;
|
||||
context: ButtonActionContext;
|
||||
} | null>(null);
|
||||
|
||||
// 토스트 정리를 위한 ref
|
||||
const currentLoadingToastRef = useRef<string | number | undefined>();
|
||||
|
||||
// 컴포넌트 언마운트 시 토스트 정리
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (currentLoadingToastRef.current !== undefined) {
|
||||
console.log("🧹 컴포넌트 언마운트 시 토스트 정리");
|
||||
toast.dismiss(currentLoadingToastRef.current);
|
||||
currentLoadingToastRef.current = undefined;
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
// 컴포넌트 설정
|
||||
const componentConfig = {
|
||||
...config,
|
||||
@@ -84,13 +99,26 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
|
||||
processedConfig.action = {
|
||||
...DEFAULT_BUTTON_ACTIONS[actionType],
|
||||
type: actionType,
|
||||
// 🔥 제어관리 설정 추가 (webTypeConfig에서 가져옴)
|
||||
enableDataflowControl: component.webTypeConfig?.enableDataflowControl,
|
||||
dataflowConfig: component.webTypeConfig?.dataflowConfig,
|
||||
};
|
||||
} else if (componentConfig.action && typeof componentConfig.action === "object") {
|
||||
// 🔥 이미 객체인 경우에도 제어관리 설정 추가
|
||||
processedConfig.action = {
|
||||
...componentConfig.action,
|
||||
enableDataflowControl: component.webTypeConfig?.enableDataflowControl,
|
||||
dataflowConfig: component.webTypeConfig?.dataflowConfig,
|
||||
};
|
||||
}
|
||||
|
||||
console.log("🔧 버튼 컴포넌트 설정:", {
|
||||
originalConfig: componentConfig,
|
||||
processedConfig,
|
||||
component: component,
|
||||
actionConfig: processedConfig.action,
|
||||
webTypeConfig: component.webTypeConfig,
|
||||
enableDataflowControl: component.webTypeConfig?.enableDataflowControl,
|
||||
dataflowConfig: component.webTypeConfig?.dataflowConfig,
|
||||
screenId,
|
||||
tableName,
|
||||
onRefresh,
|
||||
@@ -119,13 +147,22 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
|
||||
// 실제 액션 실행 함수
|
||||
const executeAction = async (actionConfig: any, context: ButtonActionContext) => {
|
||||
console.log("🚀 executeAction 시작:", { actionConfig, context });
|
||||
let loadingToast: string | number | undefined;
|
||||
|
||||
try {
|
||||
// 기존 토스트가 있다면 먼저 제거
|
||||
if (currentLoadingToastRef.current !== undefined) {
|
||||
console.log("📱 기존 토스트 제거");
|
||||
toast.dismiss(currentLoadingToastRef.current);
|
||||
currentLoadingToastRef.current = undefined;
|
||||
}
|
||||
|
||||
// 추가 안전장치: 모든 로딩 토스트 제거
|
||||
toast.dismiss();
|
||||
|
||||
// edit 액션을 제외하고만 로딩 토스트 표시
|
||||
if (actionConfig.type !== "edit") {
|
||||
console.log("📱 로딩 토스트 표시 시작");
|
||||
loadingToast = toast.loading(
|
||||
currentLoadingToastRef.current = toast.loading(
|
||||
actionConfig.type === "save"
|
||||
? "저장 중..."
|
||||
: actionConfig.type === "delete"
|
||||
@@ -133,8 +170,11 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
|
||||
: actionConfig.type === "submit"
|
||||
? "제출 중..."
|
||||
: "처리 중...",
|
||||
{
|
||||
duration: Infinity, // 명시적으로 무한대로 설정
|
||||
},
|
||||
);
|
||||
console.log("📱 로딩 토스트 ID:", loadingToast);
|
||||
console.log("📱 로딩 토스트 ID:", currentLoadingToastRef.current);
|
||||
}
|
||||
|
||||
console.log("⚡ ButtonActionExecutor.executeAction 호출 시작");
|
||||
@@ -142,9 +182,10 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
|
||||
console.log("⚡ ButtonActionExecutor.executeAction 완료, success:", success);
|
||||
|
||||
// 로딩 토스트 제거 (있는 경우에만)
|
||||
if (loadingToast) {
|
||||
console.log("📱 로딩 토스트 제거");
|
||||
toast.dismiss(loadingToast);
|
||||
if (currentLoadingToastRef.current !== undefined) {
|
||||
console.log("📱 로딩 토스트 제거 시도, ID:", currentLoadingToastRef.current);
|
||||
toast.dismiss(currentLoadingToastRef.current);
|
||||
currentLoadingToastRef.current = undefined;
|
||||
}
|
||||
|
||||
// edit 액션은 조용히 처리 (모달 열기만 하므로 토스트 불필요)
|
||||
@@ -170,9 +211,10 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
|
||||
console.log("❌ executeAction catch 블록 진입:", error);
|
||||
|
||||
// 로딩 토스트 제거
|
||||
if (loadingToast) {
|
||||
console.log("📱 오류 시 로딩 토스트 제거");
|
||||
toast.dismiss(loadingToast);
|
||||
if (currentLoadingToastRef.current !== undefined) {
|
||||
console.log("📱 오류 시 로딩 토스트 제거, ID:", currentLoadingToastRef.current);
|
||||
toast.dismiss(currentLoadingToastRef.current);
|
||||
currentLoadingToastRef.current = undefined;
|
||||
}
|
||||
|
||||
console.error("❌ 버튼 액션 실행 오류:", error);
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { ComponentRendererProps } from "@/types/component";
|
||||
import { DateInputConfig } from "./types";
|
||||
import { filterDOMProps } from "@/lib/utils/domPropsFilter";
|
||||
import { AutoGenerationUtils } from "@/lib/utils/autoGeneration";
|
||||
import { AutoGenerationConfig } from "@/types/screen";
|
||||
|
||||
export interface DateInputComponentProps extends ComponentRendererProps {
|
||||
config?: DateInputConfig;
|
||||
value?: any; // 외부에서 전달받는 값
|
||||
autoGeneration?: AutoGenerationConfig;
|
||||
hidden?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -28,6 +32,8 @@ export const DateInputComponent: React.FC<DateInputComponentProps> = ({
|
||||
formData,
|
||||
onFormDataChange,
|
||||
value: externalValue, // 외부에서 전달받은 값
|
||||
autoGeneration,
|
||||
hidden,
|
||||
...props
|
||||
}) => {
|
||||
// 컴포넌트 설정
|
||||
@@ -36,14 +42,130 @@ export const DateInputComponent: React.FC<DateInputComponentProps> = ({
|
||||
...component.config,
|
||||
} as DateInputConfig;
|
||||
|
||||
// 🎯 자동생성 상태 관리
|
||||
const [autoGeneratedValue, setAutoGeneratedValue] = useState<string>("");
|
||||
|
||||
// 🚨 컴포넌트 마운트 확인용 로그
|
||||
console.log("🚨 DateInputComponent 마운트됨!", {
|
||||
componentId: component.id,
|
||||
isInteractive,
|
||||
isDesignMode,
|
||||
autoGeneration,
|
||||
componentAutoGeneration: component.autoGeneration,
|
||||
externalValue,
|
||||
formDataValue: formData?.[component.columnName || ""],
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
// 🧪 무조건 실행되는 테스트
|
||||
useEffect(() => {
|
||||
console.log("🧪 DateInputComponent 무조건 실행 테스트!");
|
||||
const testDate = "2025-01-19"; // 고정된 테스트 날짜
|
||||
setAutoGeneratedValue(testDate);
|
||||
console.log("🧪 autoGeneratedValue 설정 완료:", testDate);
|
||||
}, []); // 빈 의존성 배열로 한 번만 실행
|
||||
|
||||
// 자동생성 설정 (props 우선, 컴포넌트 설정 폴백)
|
||||
const finalAutoGeneration = autoGeneration || component.autoGeneration;
|
||||
const finalHidden = hidden !== undefined ? hidden : component.hidden;
|
||||
|
||||
// 🧪 테스트용 간단한 자동생성 로직
|
||||
useEffect(() => {
|
||||
console.log("🔍 DateInputComponent useEffect 실행:", {
|
||||
componentId: component.id,
|
||||
finalAutoGeneration,
|
||||
enabled: finalAutoGeneration?.enabled,
|
||||
type: finalAutoGeneration?.type,
|
||||
isInteractive,
|
||||
isDesignMode,
|
||||
hasOnFormDataChange: !!onFormDataChange,
|
||||
columnName: component.columnName,
|
||||
currentFormValue: formData?.[component.columnName || ""],
|
||||
});
|
||||
|
||||
// 🧪 테스트: 자동생성이 활성화되어 있으면 무조건 현재 날짜 설정
|
||||
if (finalAutoGeneration?.enabled) {
|
||||
const today = new Date().toISOString().split("T")[0]; // YYYY-MM-DD
|
||||
console.log("🧪 테스트용 날짜 생성:", today);
|
||||
|
||||
setAutoGeneratedValue(today);
|
||||
|
||||
// 인터랙티브 모드에서 폼 데이터에도 설정
|
||||
if (isInteractive && onFormDataChange && component.columnName) {
|
||||
console.log("📤 테스트용 폼 데이터 업데이트:", component.columnName, today);
|
||||
onFormDataChange(component.columnName, today);
|
||||
}
|
||||
}
|
||||
|
||||
// 원래 자동생성 로직 (주석 처리)
|
||||
/*
|
||||
if (finalAutoGeneration?.enabled && finalAutoGeneration.type !== "none") {
|
||||
const fieldName = component.columnName || component.id;
|
||||
const generatedValue = AutoGenerationUtils.generateValue(finalAutoGeneration, fieldName);
|
||||
|
||||
console.log("🎯 DateInputComponent 자동생성 시도:", {
|
||||
componentId: component.id,
|
||||
fieldName,
|
||||
type: finalAutoGeneration.type,
|
||||
options: finalAutoGeneration.options,
|
||||
generatedValue,
|
||||
isInteractive,
|
||||
isDesignMode,
|
||||
});
|
||||
|
||||
if (generatedValue) {
|
||||
console.log("✅ DateInputComponent 자동생성 성공:", generatedValue);
|
||||
setAutoGeneratedValue(generatedValue);
|
||||
|
||||
// 인터랙티브 모드에서 폼 데이터 업데이트
|
||||
if (isInteractive && onFormDataChange && component.columnName) {
|
||||
const currentValue = formData?.[component.columnName];
|
||||
if (!currentValue) {
|
||||
console.log("📤 DateInputComponent -> onFormDataChange 호출:", component.columnName, generatedValue);
|
||||
onFormDataChange(component.columnName, generatedValue);
|
||||
} else {
|
||||
console.log("⚠️ DateInputComponent 기존 값이 있어서 자동생성 스킵:", currentValue);
|
||||
}
|
||||
} else {
|
||||
console.log("⚠️ DateInputComponent 자동생성 조건 불만족:", {
|
||||
isInteractive,
|
||||
hasOnFormDataChange: !!onFormDataChange,
|
||||
hasColumnName: !!component.columnName,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.log("❌ DateInputComponent 자동생성 실패: generatedValue가 null");
|
||||
}
|
||||
} else {
|
||||
console.log("⚠️ DateInputComponent 자동생성 비활성화:", {
|
||||
enabled: finalAutoGeneration?.enabled,
|
||||
type: finalAutoGeneration?.type,
|
||||
});
|
||||
}
|
||||
*/
|
||||
}, [
|
||||
finalAutoGeneration?.enabled,
|
||||
finalAutoGeneration?.type,
|
||||
finalAutoGeneration?.options,
|
||||
component.id,
|
||||
component.columnName,
|
||||
isInteractive,
|
||||
]);
|
||||
|
||||
// 날짜 값 계산 및 디버깅
|
||||
const fieldName = component.columnName || component.id;
|
||||
const rawValue =
|
||||
externalValue !== undefined
|
||||
? externalValue
|
||||
: isInteractive && formData && component.columnName
|
||||
? formData[component.columnName]
|
||||
: component.value;
|
||||
|
||||
// 값 우선순위: externalValue > formData > autoGeneratedValue > component.value
|
||||
let rawValue: any;
|
||||
if (externalValue !== undefined) {
|
||||
rawValue = externalValue;
|
||||
} else if (isInteractive && formData && component.columnName && formData[component.columnName]) {
|
||||
rawValue = formData[component.columnName];
|
||||
} else if (autoGeneratedValue) {
|
||||
rawValue = autoGeneratedValue;
|
||||
} else {
|
||||
rawValue = component.value;
|
||||
}
|
||||
|
||||
console.log("🔍 DateInputComponent 값 디버깅:", {
|
||||
componentId: component.id,
|
||||
@@ -196,10 +318,14 @@ export const DateInputComponent: React.FC<DateInputComponentProps> = ({
|
||||
<input
|
||||
type="date"
|
||||
value={formattedValue}
|
||||
placeholder={componentConfig.placeholder || ""}
|
||||
placeholder={
|
||||
finalAutoGeneration?.enabled
|
||||
? `자동생성: ${AutoGenerationUtils.getTypeDescription(finalAutoGeneration.type)}`
|
||||
: componentConfig.placeholder || ""
|
||||
}
|
||||
disabled={componentConfig.disabled || false}
|
||||
required={componentConfig.required || false}
|
||||
readOnly={componentConfig.readonly || false}
|
||||
readOnly={componentConfig.readonly || finalAutoGeneration?.enabled || false}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
|
||||
@@ -6,6 +6,8 @@ import { Label } from "@/components/ui/label";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { DateInputConfig } from "./types";
|
||||
import { AutoGenerationType, AutoGenerationConfig } from "@/types/screen";
|
||||
import { AutoGenerationUtils } from "@/lib/utils/autoGeneration";
|
||||
|
||||
export interface DateInputConfigPanelProps {
|
||||
config: DateInputConfig;
|
||||
@@ -16,21 +18,17 @@ export interface DateInputConfigPanelProps {
|
||||
* DateInput 설정 패널
|
||||
* 컴포넌트의 설정값들을 편집할 수 있는 UI 제공
|
||||
*/
|
||||
export const DateInputConfigPanel: React.FC<DateInputConfigPanelProps> = ({
|
||||
config,
|
||||
onChange,
|
||||
}) => {
|
||||
export const DateInputConfigPanel: React.FC<DateInputConfigPanelProps> = ({ config, onChange }) => {
|
||||
const handleChange = (key: keyof DateInputConfig, value: any) => {
|
||||
console.log("🔧 DateInputConfigPanel.handleChange:", { key, value });
|
||||
onChange({ [key]: value });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="text-sm font-medium">
|
||||
date-input 설정
|
||||
</div>
|
||||
<div className="text-sm font-medium">date-input 설정</div>
|
||||
|
||||
{/* date 관련 설정 */}
|
||||
{/* date 관련 설정 */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="placeholder">플레이스홀더</Label>
|
||||
<Input
|
||||
@@ -67,6 +65,194 @@ export const DateInputConfigPanel: React.FC<DateInputConfigPanelProps> = ({
|
||||
onCheckedChange={(checked) => handleChange("readonly", checked)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 숨김 기능 */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="hidden">숨김</Label>
|
||||
<Checkbox
|
||||
id="hidden"
|
||||
checked={config.hidden || false}
|
||||
onCheckedChange={(checked) => handleChange("hidden", checked)}
|
||||
/>
|
||||
<p className="text-xs text-gray-500">편집기에서는 연하게 보이지만 실제 화면에서는 숨겨집니다</p>
|
||||
</div>
|
||||
|
||||
{/* 자동생성 기능 */}
|
||||
<div className="space-y-3 border-t pt-3">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="autoGeneration">자동생성</Label>
|
||||
<Checkbox
|
||||
id="autoGeneration"
|
||||
checked={config.autoGeneration?.enabled || false}
|
||||
onCheckedChange={(checked) => {
|
||||
const newAutoGeneration: AutoGenerationConfig = {
|
||||
...config.autoGeneration,
|
||||
enabled: checked as boolean,
|
||||
type: config.autoGeneration?.type || "current_time",
|
||||
};
|
||||
handleChange("autoGeneration", newAutoGeneration);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{config.autoGeneration?.enabled && (
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="autoGenerationType">자동생성 타입</Label>
|
||||
<Select
|
||||
value={config.autoGeneration?.type || "current_time"}
|
||||
onValueChange={(value: AutoGenerationType) => {
|
||||
const newAutoGeneration: AutoGenerationConfig = {
|
||||
...config.autoGeneration,
|
||||
type: value,
|
||||
options: value === "current_time" ? { format: "date" } : {},
|
||||
};
|
||||
handleChange("autoGeneration", newAutoGeneration);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="current_time">현재 날짜/시간</SelectItem>
|
||||
<SelectItem value="uuid">UUID</SelectItem>
|
||||
<SelectItem value="current_user">현재 사용자</SelectItem>
|
||||
<SelectItem value="sequence">순차 번호</SelectItem>
|
||||
<SelectItem value="random_string">랜덤 문자열</SelectItem>
|
||||
<SelectItem value="random_number">랜덤 숫자</SelectItem>
|
||||
<SelectItem value="company_code">회사 코드</SelectItem>
|
||||
<SelectItem value="department">부서 코드</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-xs text-gray-500">
|
||||
{AutoGenerationUtils.getTypeDescription(config.autoGeneration?.type || "current_time")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{config.autoGeneration?.type === "current_time" && (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="dateFormat">날짜 형식</Label>
|
||||
<Select
|
||||
value={config.autoGeneration?.options?.format || "date"}
|
||||
onValueChange={(value) => {
|
||||
const newAutoGeneration: AutoGenerationConfig = {
|
||||
...config.autoGeneration!,
|
||||
options: {
|
||||
...config.autoGeneration?.options,
|
||||
format: value,
|
||||
},
|
||||
};
|
||||
handleChange("autoGeneration", newAutoGeneration);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="date">날짜만 (YYYY-MM-DD)</SelectItem>
|
||||
<SelectItem value="datetime">날짜+시간 (YYYY-MM-DD HH:mm:ss)</SelectItem>
|
||||
<SelectItem value="time">시간만 (HH:mm:ss)</SelectItem>
|
||||
<SelectItem value="timestamp">타임스탬프</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(config.autoGeneration?.type === "sequence" ||
|
||||
config.autoGeneration?.type === "random_string" ||
|
||||
config.autoGeneration?.type === "random_number") && (
|
||||
<>
|
||||
{config.autoGeneration?.type === "sequence" && (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="startValue">시작값</Label>
|
||||
<Input
|
||||
id="startValue"
|
||||
type="number"
|
||||
value={config.autoGeneration?.options?.startValue || 1}
|
||||
onChange={(e) => {
|
||||
const newAutoGeneration: AutoGenerationConfig = {
|
||||
...config.autoGeneration!,
|
||||
options: {
|
||||
...config.autoGeneration?.options,
|
||||
startValue: parseInt(e.target.value) || 1,
|
||||
},
|
||||
};
|
||||
handleChange("autoGeneration", newAutoGeneration);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(config.autoGeneration?.type === "random_string" ||
|
||||
config.autoGeneration?.type === "random_number") && (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="length">길이</Label>
|
||||
<Input
|
||||
id="length"
|
||||
type="number"
|
||||
value={
|
||||
config.autoGeneration?.options?.length ||
|
||||
(config.autoGeneration?.type === "random_string" ? 8 : 6)
|
||||
}
|
||||
onChange={(e) => {
|
||||
const newAutoGeneration: AutoGenerationConfig = {
|
||||
...config.autoGeneration!,
|
||||
options: {
|
||||
...config.autoGeneration?.options,
|
||||
length: parseInt(e.target.value) || 8,
|
||||
},
|
||||
};
|
||||
handleChange("autoGeneration", newAutoGeneration);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="prefix">접두사</Label>
|
||||
<Input
|
||||
id="prefix"
|
||||
value={config.autoGeneration?.options?.prefix || ""}
|
||||
onChange={(e) => {
|
||||
const newAutoGeneration: AutoGenerationConfig = {
|
||||
...config.autoGeneration!,
|
||||
options: {
|
||||
...config.autoGeneration?.options,
|
||||
prefix: e.target.value,
|
||||
},
|
||||
};
|
||||
handleChange("autoGeneration", newAutoGeneration);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="suffix">접미사</Label>
|
||||
<Input
|
||||
id="suffix"
|
||||
value={config.autoGeneration?.options?.suffix || ""}
|
||||
onChange={(e) => {
|
||||
const newAutoGeneration: AutoGenerationConfig = {
|
||||
...config.autoGeneration!,
|
||||
options: {
|
||||
...config.autoGeneration?.options,
|
||||
suffix: e.target.value,
|
||||
},
|
||||
};
|
||||
handleChange("autoGeneration", newAutoGeneration);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="rounded bg-gray-50 p-2 text-xs">
|
||||
<strong>미리보기:</strong> {AutoGenerationUtils.generatePreviewValue(config.autoGeneration)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -13,17 +13,24 @@ export class DateInputRenderer extends AutoRegisteringComponentRenderer {
|
||||
static componentDefinition = DateInputDefinition;
|
||||
|
||||
render(): React.ReactElement {
|
||||
console.log("🎯 DateInputRenderer.render() 호출:", {
|
||||
componentId: this.props.component?.id,
|
||||
autoGeneration: this.props.autoGeneration,
|
||||
componentAutoGeneration: this.props.component?.autoGeneration,
|
||||
allProps: Object.keys(this.props),
|
||||
});
|
||||
|
||||
return <DateInputComponent {...this.props} renderer={this} />;
|
||||
}
|
||||
|
||||
/**
|
||||
* 컴포넌트별 특화 메서드들
|
||||
*/
|
||||
|
||||
|
||||
// date 타입 특화 속성 처리
|
||||
protected getDateInputProps() {
|
||||
const baseProps = this.getWebTypeProps();
|
||||
|
||||
|
||||
// date 타입에 특화된 추가 속성들
|
||||
return {
|
||||
...baseProps,
|
||||
|
||||
@@ -4,7 +4,7 @@ import React from "react";
|
||||
import { createComponentDefinition } from "../../utils/createComponentDefinition";
|
||||
import { ComponentCategory } from "@/types/component";
|
||||
import type { WebType } from "@/types/screen";
|
||||
import { DateInputWrapper } from "./DateInputComponent";
|
||||
import { DateInputComponent } from "./DateInputComponent";
|
||||
import { DateInputConfigPanel } from "./DateInputConfigPanel";
|
||||
import { DateInputConfig } from "./types";
|
||||
|
||||
@@ -19,7 +19,7 @@ export const DateInputDefinition = createComponentDefinition({
|
||||
description: "날짜 선택을 위한 날짜 선택기 컴포넌트",
|
||||
category: ComponentCategory.INPUT,
|
||||
webType: "date",
|
||||
component: DateInputWrapper,
|
||||
component: DateInputComponent,
|
||||
defaultConfig: {
|
||||
placeholder: "입력하세요",
|
||||
},
|
||||
|
||||
@@ -1,25 +1,29 @@
|
||||
"use client";
|
||||
|
||||
import { ComponentConfig } from "@/types/component";
|
||||
import { AutoGenerationConfig } from "@/types/screen";
|
||||
|
||||
/**
|
||||
* DateInput 컴포넌트 설정 타입
|
||||
*/
|
||||
export interface DateInputConfig extends ComponentConfig {
|
||||
// date 관련 설정
|
||||
// date 관련 설정
|
||||
placeholder?: string;
|
||||
|
||||
|
||||
// 공통 설정
|
||||
disabled?: boolean;
|
||||
required?: boolean;
|
||||
readonly?: boolean;
|
||||
placeholder?: string;
|
||||
helperText?: string;
|
||||
|
||||
|
||||
// 자동생성 및 숨김 기능
|
||||
autoGeneration?: AutoGenerationConfig;
|
||||
hidden?: boolean;
|
||||
|
||||
// 스타일 관련
|
||||
variant?: "default" | "outlined" | "filled";
|
||||
size?: "sm" | "md" | "lg";
|
||||
|
||||
|
||||
// 이벤트 관련
|
||||
onChange?: (value: any) => void;
|
||||
onFocus?: () => void;
|
||||
@@ -37,7 +41,7 @@ export interface DateInputProps {
|
||||
config?: DateInputConfig;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
|
||||
|
||||
// 이벤트 핸들러
|
||||
onChange?: (value: any) => void;
|
||||
onFocus?: () => void;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { ComponentRendererProps } from "@/types/component";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { ComponentRendererProps, AutoGenerationConfig } from "@/types/component";
|
||||
import { NumberInputConfig } from "./types";
|
||||
import { filterDOMProps } from "@/lib/utils/domPropsFilter";
|
||||
import { AutoGenerationUtils } from "@/lib/utils/autoGeneration";
|
||||
|
||||
export interface NumberInputComponentProps extends ComponentRendererProps {
|
||||
config?: NumberInputConfig;
|
||||
|
||||
@@ -539,7 +539,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
}
|
||||
}, [refreshKey]);
|
||||
|
||||
// 표시할 컬럼 계산 (Entity 조인 적용됨 + 체크박스 컬럼 추가)
|
||||
// 표시할 컬럼 계산 (Entity 조인 적용됨 + 체크박스 컬럼 추가 + 숨김 기능)
|
||||
const visibleColumns = useMemo(() => {
|
||||
// 기본값 처리: checkbox 설정이 없으면 기본값 사용
|
||||
const checkboxConfig = tableConfig.checkbox || {
|
||||
@@ -554,9 +554,27 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
if (!displayColumns || displayColumns.length === 0) {
|
||||
// displayColumns가 아직 설정되지 않은 경우 기본 컬럼 사용
|
||||
if (!tableConfig.columns) return [];
|
||||
columns = tableConfig.columns.filter((col) => col.visible).sort((a, b) => a.order - b.order);
|
||||
columns = tableConfig.columns
|
||||
.filter((col) => {
|
||||
// 디자인 모드에서는 숨김 컬럼도 표시 (연하게), 실제 화면에서는 완전히 숨김
|
||||
if (isDesignMode) {
|
||||
return col.visible; // 디자인 모드에서는 visible만 체크
|
||||
} else {
|
||||
return col.visible && !col.hidden; // 실제 화면에서는 visible이면서 hidden이 아닌 것만
|
||||
}
|
||||
})
|
||||
.sort((a, b) => a.order - b.order);
|
||||
} else {
|
||||
columns = displayColumns.filter((col) => col.visible).sort((a, b) => a.order - b.order);
|
||||
columns = displayColumns
|
||||
.filter((col) => {
|
||||
// 디자인 모드에서는 숨김 컬럼도 표시 (연하게), 실제 화면에서는 완전히 숨김
|
||||
if (isDesignMode) {
|
||||
return col.visible; // 디자인 모드에서는 visible만 체크
|
||||
} else {
|
||||
return col.visible && !col.hidden; // 실제 화면에서는 visible이면서 hidden이 아닌 것만
|
||||
}
|
||||
})
|
||||
.sort((a, b) => a.order - b.order);
|
||||
}
|
||||
|
||||
// 체크박스가 활성화된 경우 체크박스 컬럼을 추가
|
||||
@@ -876,6 +894,8 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
: "h-12 cursor-pointer border-b px-4 py-3 text-left align-middle font-medium whitespace-nowrap text-gray-900 select-none",
|
||||
`text-${column.align}`,
|
||||
column.sortable && "hover:bg-gray-50",
|
||||
// 숨김 컬럼 스타일 (디자인 모드에서만)
|
||||
isDesignMode && column.hidden && "bg-gray-100/50 opacity-40",
|
||||
)}
|
||||
style={{
|
||||
minWidth: `${getColumnWidth(column)}px`,
|
||||
@@ -963,13 +983,22 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
{columnsByPosition.normal.map((column) => (
|
||||
<th
|
||||
key={`normal-${column.columnName}`}
|
||||
style={{ minWidth: `${getColumnWidth(column)}px` }}
|
||||
style={{
|
||||
minWidth: `${getColumnWidth(column)}px`,
|
||||
minHeight: "48px",
|
||||
height: "48px",
|
||||
verticalAlign: "middle",
|
||||
lineHeight: "1",
|
||||
boxSizing: "border-box",
|
||||
}}
|
||||
className={cn(
|
||||
column.columnName === "__checkbox__"
|
||||
? "h-12 border-b px-4 py-3 text-center"
|
||||
: "cursor-pointer border-b px-4 py-3 text-left font-medium whitespace-nowrap text-gray-900 select-none",
|
||||
? "h-12 border-b px-4 py-3 text-center align-middle"
|
||||
: "cursor-pointer border-b px-4 py-3 text-left align-middle font-medium whitespace-nowrap text-gray-900 select-none",
|
||||
`text-${column.align}`,
|
||||
column.sortable && "hover:bg-gray-50",
|
||||
// 숨김 컬럼 스타일 (디자인 모드에서만)
|
||||
isDesignMode && column.hidden && "bg-gray-100/50 opacity-40",
|
||||
)}
|
||||
onClick={() => column.sortable && handleSort(column.columnName)}
|
||||
>
|
||||
@@ -1056,6 +1085,8 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
: "h-12 cursor-pointer border-b px-4 py-3 text-left align-middle font-medium whitespace-nowrap text-gray-900 select-none",
|
||||
`text-${column.align}`,
|
||||
column.sortable && "hover:bg-gray-50",
|
||||
// 숨김 컬럼 스타일 (디자인 모드에서만)
|
||||
isDesignMode && column.hidden && "bg-gray-100/50 opacity-40",
|
||||
)}
|
||||
style={{
|
||||
minWidth: `${getColumnWidth(column)}px`,
|
||||
|
||||
@@ -11,6 +11,35 @@ export interface EntityJoinInfo {
|
||||
joinAlias: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 자동생성 타입 정의
|
||||
*/
|
||||
export type AutoGenerationType =
|
||||
| "uuid" // UUID 생성
|
||||
| "current_user" // 현재 사용자 ID
|
||||
| "current_time" // 현재 시간
|
||||
| "sequence" // 시퀀스 번호
|
||||
| "random_string" // 랜덤 문자열
|
||||
| "random_number" // 랜덤 숫자
|
||||
| "company_code" // 회사 코드
|
||||
| "department" // 부서 코드
|
||||
| "none"; // 자동생성 없음
|
||||
|
||||
/**
|
||||
* 자동생성 설정
|
||||
*/
|
||||
export interface AutoGenerationConfig {
|
||||
type: AutoGenerationType;
|
||||
enabled: boolean;
|
||||
options?: {
|
||||
length?: number; // 랜덤 문자열/숫자 길이
|
||||
prefix?: string; // 접두사
|
||||
suffix?: string; // 접미사
|
||||
format?: string; // 시간 형식 (current_time용)
|
||||
startValue?: number; // 시퀀스 시작값
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 테이블 컬럼 설정
|
||||
*/
|
||||
@@ -31,6 +60,10 @@ export interface ColumnConfig {
|
||||
// 컬럼 고정 관련 속성
|
||||
fixed?: "left" | "right" | false; // 컬럼 고정 위치 (왼쪽, 오른쪽, 고정 안함)
|
||||
fixedOrder?: number; // 고정된 컬럼들 내에서의 순서
|
||||
|
||||
// 새로운 기능들
|
||||
hidden?: boolean; // 숨김 기능 (편집기에서는 연하게, 실제 화면에서는 숨김)
|
||||
autoGeneration?: AutoGenerationConfig; // 자동생성 설정
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { ComponentRendererProps } from "@/types/component";
|
||||
import { AutoGenerationConfig } from "@/types/screen";
|
||||
import { TextInputConfig } from "./types";
|
||||
import { filterDOMProps } from "@/lib/utils/domPropsFilter";
|
||||
import { AutoGenerationUtils } from "@/lib/utils/autoGeneration";
|
||||
|
||||
export interface TextInputComponentProps extends ComponentRendererProps {
|
||||
config?: TextInputConfig;
|
||||
@@ -34,18 +36,112 @@ export const TextInputComponent: React.FC<TextInputComponentProps> = ({
|
||||
...component.config,
|
||||
} as TextInputConfig;
|
||||
|
||||
// 자동생성 설정 (props에서 전달받은 값 우선 사용)
|
||||
const autoGeneration: AutoGenerationConfig = props.autoGeneration ||
|
||||
component.autoGeneration ||
|
||||
componentConfig.autoGeneration || {
|
||||
type: "none",
|
||||
enabled: false,
|
||||
};
|
||||
|
||||
// 숨김 상태 (props에서 전달받은 값 우선 사용)
|
||||
const isHidden = props.hidden !== undefined ? props.hidden : component.hidden || componentConfig.hidden || false;
|
||||
|
||||
// 자동생성된 값 상태
|
||||
const [autoGeneratedValue, setAutoGeneratedValue] = useState<string>("");
|
||||
|
||||
// 테스트용: 컴포넌트 라벨에 "test"가 포함되면 강제로 UUID 자동생성 활성화
|
||||
const testAutoGeneration = component.label?.toLowerCase().includes("test")
|
||||
? {
|
||||
type: "uuid" as AutoGenerationType,
|
||||
enabled: true,
|
||||
}
|
||||
: autoGeneration;
|
||||
|
||||
console.log("🔧 텍스트 입력 컴포넌트 설정:", {
|
||||
config,
|
||||
componentConfig,
|
||||
component: component,
|
||||
autoGeneration,
|
||||
testAutoGeneration,
|
||||
isTestMode: component.label?.toLowerCase().includes("test"),
|
||||
isHidden,
|
||||
isInteractive,
|
||||
formData,
|
||||
columnName: component.columnName,
|
||||
currentFormValue: formData?.[component.columnName],
|
||||
componentValue: component.value,
|
||||
autoGeneratedValue,
|
||||
});
|
||||
|
||||
// 자동생성 값 생성 (컴포넌트 마운트 시 또는 폼 데이터 변경 시)
|
||||
useEffect(() => {
|
||||
console.log("🔄 자동생성 useEffect 실행:", {
|
||||
enabled: testAutoGeneration.enabled,
|
||||
type: testAutoGeneration.type,
|
||||
isInteractive,
|
||||
columnName: component.columnName,
|
||||
hasFormData: !!formData,
|
||||
hasOnFormDataChange: !!onFormDataChange,
|
||||
});
|
||||
|
||||
if (testAutoGeneration.enabled && testAutoGeneration.type !== "none") {
|
||||
// 폼 데이터에 이미 값이 있으면 자동생성하지 않음
|
||||
const currentFormValue = formData?.[component.columnName];
|
||||
const currentComponentValue = component.value;
|
||||
|
||||
console.log("🔍 자동생성 조건 확인:", {
|
||||
currentFormValue,
|
||||
currentComponentValue,
|
||||
hasCurrentValue: !!(currentFormValue || currentComponentValue),
|
||||
autoGeneratedValue,
|
||||
});
|
||||
|
||||
// 자동생성된 값이 없고, 현재 값도 없을 때만 생성
|
||||
if (!autoGeneratedValue && !currentFormValue && !currentComponentValue) {
|
||||
const generatedValue = AutoGenerationUtils.generateValue(testAutoGeneration, component.columnName);
|
||||
console.log("✨ 자동생성된 값:", generatedValue);
|
||||
|
||||
if (generatedValue) {
|
||||
setAutoGeneratedValue(generatedValue);
|
||||
|
||||
// 폼 데이터에 자동생성된 값 설정 (인터랙티브 모드에서만)
|
||||
if (isInteractive && onFormDataChange && component.columnName) {
|
||||
console.log("📝 폼 데이터에 자동생성 값 설정:", {
|
||||
columnName: component.columnName,
|
||||
value: generatedValue,
|
||||
});
|
||||
onFormDataChange(component.columnName, generatedValue);
|
||||
}
|
||||
}
|
||||
} else if (!autoGeneratedValue && testAutoGeneration.type !== "none") {
|
||||
// 디자인 모드에서도 미리보기용 자동생성 값 표시
|
||||
const previewValue = AutoGenerationUtils.generatePreviewValue(testAutoGeneration);
|
||||
console.log("🎨 디자인 모드 미리보기 값:", previewValue);
|
||||
setAutoGeneratedValue(previewValue);
|
||||
} else {
|
||||
console.log("⏭️ 이미 값이 있어서 자동생성 건너뜀:", {
|
||||
hasAutoGenerated: !!autoGeneratedValue,
|
||||
hasFormValue: !!currentFormValue,
|
||||
hasComponentValue: !!currentComponentValue,
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [testAutoGeneration, isInteractive, component.columnName, component.value, formData, onFormDataChange]);
|
||||
|
||||
// 스타일 계산 (위치는 RealtimePreviewDynamic에서 처리하므로 제외)
|
||||
const componentStyle: React.CSSProperties = {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
...component.style,
|
||||
...style,
|
||||
// 숨김 기능: 디자인 모드에서는 연하게, 실제 화면에서는 완전히 숨김
|
||||
...(isHidden && {
|
||||
opacity: isDesignMode ? 0.4 : 0,
|
||||
backgroundColor: isDesignMode ? "#f3f4f6" : "transparent",
|
||||
pointerEvents: isDesignMode ? "auto" : "none",
|
||||
display: isDesignMode ? "block" : "none",
|
||||
}),
|
||||
};
|
||||
|
||||
// 디자인 모드 스타일
|
||||
@@ -105,15 +201,37 @@ export const TextInputComponent: React.FC<TextInputComponentProps> = ({
|
||||
|
||||
<input
|
||||
type="text"
|
||||
value={
|
||||
isInteractive && formData && component.columnName
|
||||
? formData[component.columnName] || ""
|
||||
: component.value || ""
|
||||
value={(() => {
|
||||
let displayValue = "";
|
||||
|
||||
if (isInteractive && formData && component.columnName) {
|
||||
// 인터랙티브 모드: formData 우선, 없으면 자동생성 값
|
||||
displayValue = formData[component.columnName] || autoGeneratedValue || "";
|
||||
} else {
|
||||
// 디자인 모드: component.value 우선, 없으면 자동생성 값
|
||||
displayValue = component.value || autoGeneratedValue || "";
|
||||
}
|
||||
|
||||
console.log("📄 Input 값 계산:", {
|
||||
isInteractive,
|
||||
hasFormData: !!formData,
|
||||
columnName: component.columnName,
|
||||
formDataValue: formData?.[component.columnName],
|
||||
componentValue: component.value,
|
||||
autoGeneratedValue,
|
||||
finalDisplayValue: displayValue,
|
||||
});
|
||||
|
||||
return displayValue;
|
||||
})()}
|
||||
placeholder={
|
||||
testAutoGeneration.enabled && testAutoGeneration.type !== "none"
|
||||
? `자동생성: ${AutoGenerationUtils.getTypeDescription(testAutoGeneration.type)}`
|
||||
: componentConfig.placeholder || ""
|
||||
}
|
||||
placeholder={componentConfig.placeholder || ""}
|
||||
disabled={componentConfig.disabled || false}
|
||||
required={componentConfig.required || false}
|
||||
readOnly={componentConfig.readonly || false}
|
||||
readOnly={componentConfig.readonly || (testAutoGeneration.enabled && testAutoGeneration.type !== "none")}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
|
||||
@@ -6,6 +6,8 @@ import { Label } from "@/components/ui/label";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { TextInputConfig } from "./types";
|
||||
import { AutoGenerationType, AutoGenerationConfig } from "@/types/screen";
|
||||
import { AutoGenerationUtils } from "@/lib/utils/autoGeneration";
|
||||
|
||||
export interface TextInputConfigPanelProps {
|
||||
config: TextInputConfig;
|
||||
@@ -16,21 +18,16 @@ export interface TextInputConfigPanelProps {
|
||||
* TextInput 설정 패널
|
||||
* 컴포넌트의 설정값들을 편집할 수 있는 UI 제공
|
||||
*/
|
||||
export const TextInputConfigPanel: React.FC<TextInputConfigPanelProps> = ({
|
||||
config,
|
||||
onChange,
|
||||
}) => {
|
||||
export const TextInputConfigPanel: React.FC<TextInputConfigPanelProps> = ({ config, onChange }) => {
|
||||
const handleChange = (key: keyof TextInputConfig, value: any) => {
|
||||
onChange({ [key]: value });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="text-sm font-medium">
|
||||
text-input 설정
|
||||
</div>
|
||||
<div className="text-sm font-medium">text-input 설정</div>
|
||||
|
||||
{/* 텍스트 관련 설정 */}
|
||||
{/* 텍스트 관련 설정 */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="placeholder">플레이스홀더</Label>
|
||||
<Input
|
||||
@@ -77,6 +74,163 @@ export const TextInputConfigPanel: React.FC<TextInputConfigPanelProps> = ({
|
||||
onCheckedChange={(checked) => handleChange("readonly", checked)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 구분선 */}
|
||||
<div className="border-t pt-4">
|
||||
<div className="mb-3 text-sm font-medium">고급 기능</div>
|
||||
|
||||
{/* 숨김 기능 */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="hidden">숨김 (편집기에서는 연하게, 실제 화면에서는 숨김)</Label>
|
||||
<Checkbox
|
||||
id="hidden"
|
||||
checked={config.hidden || false}
|
||||
onCheckedChange={(checked) => handleChange("hidden", checked)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 자동생성 기능 */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="autoGeneration">자동생성 활성화</Label>
|
||||
<Checkbox
|
||||
id="autoGeneration"
|
||||
checked={config.autoGeneration?.enabled || false}
|
||||
onCheckedChange={(checked) => {
|
||||
const currentConfig = config.autoGeneration || { type: "none", enabled: false };
|
||||
handleChange("autoGeneration", {
|
||||
...currentConfig,
|
||||
enabled: checked as boolean,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 자동생성 타입 선택 */}
|
||||
{config.autoGeneration?.enabled && (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="autoGenerationType">자동생성 타입</Label>
|
||||
<Select
|
||||
value={config.autoGeneration?.type || "none"}
|
||||
onValueChange={(value: AutoGenerationType) => {
|
||||
const currentConfig = config.autoGeneration || { type: "none", enabled: false };
|
||||
handleChange("autoGeneration", {
|
||||
...currentConfig,
|
||||
type: value,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="자동생성 타입 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="none">자동생성 없음</SelectItem>
|
||||
<SelectItem value="uuid">UUID 생성</SelectItem>
|
||||
<SelectItem value="current_user">현재 사용자 ID</SelectItem>
|
||||
<SelectItem value="current_time">현재 시간</SelectItem>
|
||||
<SelectItem value="sequence">순차 번호</SelectItem>
|
||||
<SelectItem value="random_string">랜덤 문자열</SelectItem>
|
||||
<SelectItem value="random_number">랜덤 숫자</SelectItem>
|
||||
<SelectItem value="company_code">회사 코드</SelectItem>
|
||||
<SelectItem value="department">부서 코드</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
{/* 선택된 타입 설명 */}
|
||||
{config.autoGeneration?.type && config.autoGeneration.type !== "none" && (
|
||||
<div className="text-xs text-gray-500">
|
||||
{AutoGenerationUtils.getTypeDescription(config.autoGeneration.type)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 자동생성 옵션 */}
|
||||
{config.autoGeneration?.enabled &&
|
||||
config.autoGeneration?.type &&
|
||||
["random_string", "random_number", "sequence"].includes(config.autoGeneration.type) && (
|
||||
<div className="space-y-2">
|
||||
<Label>자동생성 옵션</Label>
|
||||
|
||||
{/* 길이 설정 (랜덤 문자열/숫자용) */}
|
||||
{["random_string", "random_number"].includes(config.autoGeneration.type) && (
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="autoGenLength" className="text-xs">
|
||||
길이
|
||||
</Label>
|
||||
<Input
|
||||
id="autoGenLength"
|
||||
type="number"
|
||||
min="1"
|
||||
max="50"
|
||||
value={
|
||||
config.autoGeneration?.options?.length || (config.autoGeneration.type === "random_string" ? 8 : 6)
|
||||
}
|
||||
onChange={(e) => {
|
||||
const currentConfig = config.autoGeneration!;
|
||||
handleChange("autoGeneration", {
|
||||
...currentConfig,
|
||||
options: {
|
||||
...currentConfig.options,
|
||||
length: parseInt(e.target.value) || 8,
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 접두사 */}
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="autoGenPrefix" className="text-xs">
|
||||
접두사
|
||||
</Label>
|
||||
<Input
|
||||
id="autoGenPrefix"
|
||||
value={config.autoGeneration?.options?.prefix || ""}
|
||||
onChange={(e) => {
|
||||
const currentConfig = config.autoGeneration!;
|
||||
handleChange("autoGeneration", {
|
||||
...currentConfig,
|
||||
options: {
|
||||
...currentConfig.options,
|
||||
prefix: e.target.value,
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 접미사 */}
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="autoGenSuffix" className="text-xs">
|
||||
접미사
|
||||
</Label>
|
||||
<Input
|
||||
id="autoGenSuffix"
|
||||
value={config.autoGeneration?.options?.suffix || ""}
|
||||
onChange={(e) => {
|
||||
const currentConfig = config.autoGeneration!;
|
||||
handleChange("autoGeneration", {
|
||||
...currentConfig,
|
||||
options: {
|
||||
...currentConfig.options,
|
||||
suffix: e.target.value,
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 미리보기 */}
|
||||
<div className="space-y-1">
|
||||
<Label className="text-xs">미리보기</Label>
|
||||
<div className="rounded border bg-gray-100 p-2 text-xs">
|
||||
{AutoGenerationUtils.generatePreviewValue(config.autoGeneration)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,32 +1,37 @@
|
||||
"use client";
|
||||
|
||||
import { ComponentConfig } from "@/types/component";
|
||||
import { AutoGenerationConfig } from "@/types/screen";
|
||||
|
||||
/**
|
||||
* TextInput 컴포넌트 설정 타입
|
||||
*/
|
||||
export interface TextInputConfig extends ComponentConfig {
|
||||
// 텍스트 관련 설정
|
||||
// 텍스트 관련 설정
|
||||
placeholder?: string;
|
||||
maxLength?: number;
|
||||
minLength?: number;
|
||||
|
||||
|
||||
// 공통 설정
|
||||
disabled?: boolean;
|
||||
required?: boolean;
|
||||
readonly?: boolean;
|
||||
placeholder?: string;
|
||||
helperText?: string;
|
||||
|
||||
|
||||
// 스타일 관련
|
||||
variant?: "default" | "outlined" | "filled";
|
||||
size?: "sm" | "md" | "lg";
|
||||
|
||||
|
||||
// 이벤트 관련
|
||||
onChange?: (value: any) => void;
|
||||
onFocus?: () => void;
|
||||
onBlur?: () => void;
|
||||
onClick?: () => void;
|
||||
|
||||
// 새로운 기능들
|
||||
hidden?: boolean; // 숨김 기능 (편집기에서는 연하게, 실제 화면에서는 숨김)
|
||||
autoGeneration?: AutoGenerationConfig; // 자동생성 설정
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,7 +44,7 @@ export interface TextInputProps {
|
||||
config?: TextInputConfig;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
|
||||
|
||||
// 이벤트 핸들러
|
||||
onChange?: (value: any) => void;
|
||||
onFocus?: () => void;
|
||||
|
||||
Reference in New Issue
Block a user