fix: Improve number and slider input handling in V2Input and SplitPanelLayoutComponent

- Enhanced V2Input to convert string values from the database to numbers for number and slider inputs, ensuring correct display and functionality.
- Updated primary key retrieval logic in SplitPanelLayoutComponent to prioritize actual DB id values, improving data integrity.
- Simplified primary key handling by removing unnecessary checks and ensuring consistent usage of id fields.
- Improved user feedback in the SplitPanelLayoutComponent with clearer console logs for save operations and item selections.
This commit is contained in:
kjs 2026-02-12 16:07:36 +09:00
parent 505930b3ec
commit d0ebb82f90
2 changed files with 47 additions and 20 deletions

View File

@ -771,9 +771,15 @@ export const V2Input = forwardRef<HTMLDivElement, V2InputProps>((props, ref) =>
); );
case "number": case "number":
// DB에서 문자열("325")로 반환되는 경우도 숫자로 변환하여 표시
const numValue = typeof displayValue === "number"
? displayValue
: (displayValue !== undefined && displayValue !== null && displayValue !== "" && !isNaN(Number(displayValue)))
? Number(displayValue)
: undefined;
return ( return (
<NumberInput <NumberInput
value={typeof displayValue === "number" ? displayValue : undefined} value={numValue}
onChange={(v) => { onChange={(v) => {
setAutoGeneratedValue(null); setAutoGeneratedValue(null);
onChange?.(v ?? 0); onChange?.(v ?? 0);
@ -802,9 +808,15 @@ export const V2Input = forwardRef<HTMLDivElement, V2InputProps>((props, ref) =>
); );
case "slider": case "slider":
// DB에서 문자열로 반환되는 경우도 숫자로 변환
const sliderValue = typeof displayValue === "number"
? displayValue
: (displayValue !== undefined && displayValue !== null && displayValue !== "" && !isNaN(Number(displayValue)))
? Number(displayValue)
: (config.min ?? 0);
return ( return (
<SliderInput <SliderInput
value={typeof displayValue === "number" ? displayValue : (config.min ?? 0)} value={sliderValue}
onChange={(v) => { onChange={(v) => {
setAutoGeneratedValue(null); setAutoGeneratedValue(null);
onChange?.(v); onChange?.(v);

View File

@ -2105,22 +2105,16 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
const editButtonConfig = componentConfig.leftPanel?.editButton; const editButtonConfig = componentConfig.leftPanel?.editButton;
if (editButtonConfig?.mode === "modal" && editButtonConfig?.modalScreenId) { if (editButtonConfig?.mode === "modal" && editButtonConfig?.modalScreenId) {
const leftTableName = componentConfig.leftPanel?.tableName || ""; const leftTableName = componentConfig.leftPanel?.tableName || "";
const sourceColumn = componentConfig.leftPanel?.itemAddConfig?.sourceColumn || "id";
// Primary Key 찾기 // Primary Key 찾기 - 실제 DB의 id 컬럼 값을 우선 사용
let primaryKeyName = sourceColumn; let primaryKeyValue = item.id || item.ID;
let primaryKeyValue = item[sourceColumn];
if (primaryKeyValue === undefined || primaryKeyValue === null) { if (primaryKeyValue === undefined || primaryKeyValue === null) {
if (item.id !== undefined && item.id !== null) { // id가 없으면 sourceColumn 시도, 마지막으로 첫 번째 키
primaryKeyName = "id"; const sourceColumn = componentConfig.leftPanel?.itemAddConfig?.sourceColumn || "id";
primaryKeyValue = item.id; primaryKeyValue = item[sourceColumn];
} else if (item.ID !== undefined && item.ID !== null) { if (primaryKeyValue === undefined || primaryKeyValue === null) {
primaryKeyName = "ID";
primaryKeyValue = item.ID;
} else {
const firstKey = Object.keys(item)[0]; const firstKey = Object.keys(item)[0];
primaryKeyName = firstKey;
primaryKeyValue = item[firstKey]; primaryKeyValue = item[firstKey];
} }
} }
@ -2147,7 +2141,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
console.log("✅ [SplitPanel] 좌측 수정 모달 화면 열기:", { console.log("✅ [SplitPanel] 좌측 수정 모달 화면 열기:", {
screenId: editButtonConfig.modalScreenId, screenId: editButtonConfig.modalScreenId,
tableName: leftTableName, tableName: leftTableName,
primaryKeyName,
primaryKeyValue, primaryKeyValue,
}); });
return; return;
@ -2282,9 +2275,8 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
return; return;
} }
// Primary Key 찾기 // Primary Key 찾기 - 실제 DB의 id 컬럼 값을 사용 (sourceColumn은 관계 연결용이므로 PK로 사용하지 않음)
const sourceColumn = componentConfig.leftPanel?.itemAddConfig?.sourceColumn || "id"; const primaryKey = selectedLeftItem.id || selectedLeftItem.ID;
const primaryKey = selectedLeftItem[sourceColumn] || selectedLeftItem.id || selectedLeftItem.ID;
if (!primaryKey) { if (!primaryKey) {
toast({ toast({
@ -2307,7 +2299,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
cleanData.company_code = companyCode; cleanData.company_code = companyCode;
} }
console.log("📝 [SplitPanel] 커스텀 우측 패널 저장:", { tableName, sourceColumn, primaryKey, data: cleanData }); console.log("📝 [SplitPanel] 커스텀 우측 패널 저장:", { tableName, primaryKey, data: cleanData });
const response = await dataApi.updateRecord(tableName, primaryKey, cleanData); const response = await dataApi.updateRecord(tableName, primaryKey, cleanData);
@ -2529,7 +2521,8 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
if (deleteModalPanel === "left") { if (deleteModalPanel === "left") {
loadLeftData(); loadLeftData();
// 삭제된 항목이 선택되어 있었으면 선택 해제 // 삭제된 항목이 선택되어 있었으면 선택 해제
if (selectedLeftItem && selectedLeftItem[sourceColumn] === primaryKey) { const deletedId = deleteModalItem?.id || deleteModalItem?.ID;
if (selectedLeftItem && (selectedLeftItem.id === deletedId || selectedLeftItem.ID === deletedId)) {
setSelectedLeftItem(null); setSelectedLeftItem(null);
setRightData(null); setRightData(null);
} }
@ -2968,6 +2961,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
const displayHeight = isResizingComp && resizeSize ? resizeSize.height : (comp.size?.height || 100); const displayHeight = isResizingComp && resizeSize ? resizeSize.height : (comp.size?.height || 100);
// 컴포넌트 데이터를 DynamicComponentRenderer 형식으로 변환 // 컴포넌트 데이터를 DynamicComponentRenderer 형식으로 변환
// componentConfig의 주요 속성을 최상위로 펼침 (일반 화면의 overrides 플래트닝과 동일)
const componentData = { const componentData = {
id: comp.id, id: comp.id,
type: "component" as const, type: "component" as const,
@ -2977,6 +2971,11 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
size: { width: displayWidth, height: displayHeight }, size: { width: displayWidth, height: displayHeight },
componentConfig: comp.componentConfig || {}, componentConfig: comp.componentConfig || {},
style: comp.style || {}, style: comp.style || {},
// 파일 업로드/미디어 등이 component.tableName, component.columnName을 직접 참조하므로 펼침
tableName: comp.componentConfig?.tableName,
columnName: comp.componentConfig?.columnName,
webType: comp.componentConfig?.webType,
inputType: comp.inputType || comp.componentConfig?.inputType,
}; };
if (isDesignMode) { if (isDesignMode) {
@ -3981,6 +3980,15 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
})() })()
) : componentConfig.rightPanel?.displayMode === "custom" ? ( ) : componentConfig.rightPanel?.displayMode === "custom" ? (
// 🆕 커스텀 모드: 패널 안에 자유롭게 컴포넌트 배치 // 🆕 커스텀 모드: 패널 안에 자유롭게 컴포넌트 배치
// 실행 모드에서 좌측 미선택 시 안내 메시지 표시
!isDesignMode && !selectedLeftItem ? (
<div className="flex h-full items-center justify-center">
<div className="text-muted-foreground text-center text-sm">
<p className="mb-2"> </p>
<p className="text-xs"> </p>
</div>
</div>
) : (
<div <div
className="relative h-full w-full" className="relative h-full w-full"
data-split-panel-container="true" data-split-panel-container="true"
@ -4002,6 +4010,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
const displayHeight = isResizingComp && resizeSize ? resizeSize.height : (comp.size?.height || 100); const displayHeight = isResizingComp && resizeSize ? resizeSize.height : (comp.size?.height || 100);
// 컴포넌트 데이터를 DynamicComponentRenderer 형식으로 변환 // 컴포넌트 데이터를 DynamicComponentRenderer 형식으로 변환
// componentConfig의 주요 속성을 최상위로 펼침 (일반 화면의 overrides 플래트닝과 동일)
const componentData = { const componentData = {
id: comp.id, id: comp.id,
type: "component" as const, type: "component" as const,
@ -4011,6 +4020,11 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
size: { width: displayWidth, height: displayHeight }, size: { width: displayWidth, height: displayHeight },
componentConfig: comp.componentConfig || {}, componentConfig: comp.componentConfig || {},
style: comp.style || {}, style: comp.style || {},
// 파일 업로드/미디어 등이 component.tableName, component.columnName을 직접 참조하므로 펼침
tableName: comp.componentConfig?.tableName,
columnName: comp.componentConfig?.columnName,
webType: comp.componentConfig?.webType,
inputType: comp.inputType || comp.componentConfig?.inputType,
}; };
if (isDesignMode) { if (isDesignMode) {
@ -4201,6 +4215,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
</div> </div>
)} )}
</div> </div>
)
) : isLoadingRight ? ( ) : isLoadingRight ? (
// 로딩 중 // 로딩 중
<div className="flex h-full items-center justify-center"> <div className="flex h-full items-center justify-center">