diff --git a/frontend/components/screen/panels/V2PropertiesPanel.tsx b/frontend/components/screen/panels/V2PropertiesPanel.tsx index e09acd6b..8ee116f9 100644 --- a/frontend/components/screen/panels/V2PropertiesPanel.tsx +++ b/frontend/components/screen/panels/V2PropertiesPanel.tsx @@ -328,147 +328,214 @@ export const V2PropertiesPanel: React.FC = ({ const isInputField = inputFieldTypes.includes(componentType); return ( -
- {/* 너비 + 높이 (같은 행) */} -
-
- - { - setLocalWidth(e.target.value); - }} - onBlur={(e) => { - const value = parseInt(e.target.value) || 0; - if (value >= 10) { - const snappedValue = Math.round(value / 10) * 10; - handleUpdate("size.width", snappedValue); - setLocalWidth(String(snappedValue)); - } - }} - onKeyDown={(e) => { - if (e.key === "Enter") { - const value = parseInt(e.currentTarget.value) || 0; +
+ {/* DIMENSIONS 섹션 */} +
+

DIMENSIONS

+
+
+ + { + setLocalWidth(e.target.value); + }} + onBlur={(e) => { + const value = parseInt(e.target.value) || 0; if (value >= 10) { const snappedValue = Math.round(value / 10) * 10; handleUpdate("size.width", snappedValue); setLocalWidth(String(snappedValue)); } - e.currentTarget.blur(); - } - }} - placeholder="100" - className="h-6 w-full px-2 py-0 text-xs" - /> -
-
- - { - setLocalHeight(e.target.value); - }} - onBlur={(e) => { - const value = parseInt(e.target.value) || 0; - if (value >= 10) { - const snappedValue = Math.round(value / 10) * 10; - handleUpdate("size.height", snappedValue); - setLocalHeight(String(snappedValue)); - } - }} - onKeyDown={(e) => { - if (e.key === "Enter") { - const value = parseInt(e.currentTarget.value) || 0; + }} + onKeyDown={(e) => { + if (e.key === "Enter") { + const value = parseInt(e.currentTarget.value) || 0; + if (value >= 10) { + const snappedValue = Math.round(value / 10) * 10; + handleUpdate("size.width", snappedValue); + setLocalWidth(String(snappedValue)); + } + e.currentTarget.blur(); + } + }} + placeholder="100" + className="h-7 text-xs" + /> +
+
+ + { + setLocalHeight(e.target.value); + }} + onBlur={(e) => { + const value = parseInt(e.target.value) || 0; if (value >= 10) { const snappedValue = Math.round(value / 10) * 10; handleUpdate("size.height", snappedValue); setLocalHeight(String(snappedValue)); } - e.currentTarget.blur(); - } - }} - step={1} - placeholder="10" - className="h-6 w-full px-2 py-0 text-xs" - /> + }} + onKeyDown={(e) => { + if (e.key === "Enter") { + const value = parseInt(e.currentTarget.value) || 0; + if (value >= 10) { + const snappedValue = Math.round(value / 10) * 10; + handleUpdate("size.height", snappedValue); + setLocalHeight(String(snappedValue)); + } + e.currentTarget.blur(); + } + }} + step={1} + placeholder="10" + className="h-7 text-xs" + /> +
+
+ {/* Z-Index */} +
+ Z-Index +
+ handleUpdate("position.z", parseInt(e.target.value) || 1)} + className="h-7 text-xs" + /> +
{/* Title (group/area) */} {(selectedComponent.type === "group" || selectedComponent.type === "area") && ( -
- - handleUpdate("title", e.target.value)} - placeholder="제목" - className="h-6 w-full px-2 py-0 text-xs" - /> -
- )} - - {/* Description (area만) */} - {selectedComponent.type === "area" && ( -
- - handleUpdate("description", e.target.value)} - placeholder="설명" - className="h-6 w-full px-2 py-0 text-xs" - /> -
- )} - - {/* Z-Index */} -
- - handleUpdate("position.z", parseInt(e.target.value) || 1)} - className="h-6 w-full px-2 py-0 text-xs" - /> -
- - {/* 라벨 스타일 - 입력 필드에서만 표시 */} - {isInputField && ( - - - 라벨 스타일 - - - -
- +
+

CONTENT

+
+ 제목 +
{ - handleUpdate("style.labelText", e.target.value); - handleUpdate("label", e.target.value); // label도 함께 업데이트 - }} - placeholder="라벨을 입력하세요 (비우면 라벨 없음)" - className="h-6 w-full px-2 py-0 text-xs" + value={group.title || area.title || ""} + onChange={(e) => handleUpdate("title", e.target.value)} + placeholder="제목" + className="h-7 text-xs" />
-
-
- +
+ {selectedComponent.type === "area" && ( +
+ 설명 +
+ handleUpdate("description", e.target.value)} + placeholder="설명" + className="h-7 text-xs" + /> +
+
+ )} +
+ )} + + {/* OPTIONS 섹션 */} +
+

OPTIONS

+ {(isInputField || widget.required !== undefined) && (() => { + const colName = widget.columnName || selectedComponent?.columnName; + const colMeta = colName ? currentTable?.columns?.find( + (c: any) => (c.columnName || c.column_name || "").toLowerCase() === colName.toLowerCase() + ) : null; + const isNotNull = colMeta && ((colMeta as any).isNullable === "NO" || (colMeta as any).isNullable === "N" || (colMeta as any).is_nullable === "NO" || (colMeta as any).is_nullable === "N"); + return ( +
+ + 필수 + {isNotNull && (NOT NULL)} + + { + if (isNotNull) return; + handleUpdate("required", checked); + handleUpdate("componentConfig.required", checked); + }} + disabled={!!isNotNull} + className="h-4 w-4" + /> +
+ ); + })()} + {(isInputField || widget.readonly !== undefined) && ( +
+ 읽기전용 + { + handleUpdate("readonly", checked); + handleUpdate("componentConfig.readonly", checked); + }} + className="h-4 w-4" + /> +
+ )} +
+ 숨김 + { + handleUpdate("hidden", checked); + handleUpdate("componentConfig.hidden", checked); + }} + className="h-4 w-4" + /> +
+
+ + {/* LABEL 섹션 - 입력 필드에서만 표시 */} + {isInputField && ( + + + LABEL + + + + {/* 라벨 텍스트 */} +
+ 텍스트 +
+ { + handleUpdate("style.labelText", e.target.value); + handleUpdate("label", e.target.value); + }} + placeholder="라벨" + className="h-7 text-xs" + /> +
+
+ {/* 위치 + 간격 */} +
+
+
-
- +
+ = ({ handleUpdate("style.labelMarginBottom", e.target.value); } }} - className="h-6 w-full px-2 py-0 text-xs" + className="h-7 text-xs" />
-
-
- + {/* 크기 + 색상 */} +
+
+ handleUpdate("style.labelFontSize", e.target.value)} - className="h-6 w-full px-2 py-0 text-xs" + className="h-7 text-xs" />
-
- +
+ handleUpdate("style.labelColor", value)} @@ -518,14 +586,15 @@ export const V2PropertiesPanel: React.FC = ({ />
-
-
- + {/* 굵기 */} +
+ 굵기 +
-
- { - const boolValue = checked === true; - // 🔧 "필수"처럼 직접 경로로 업데이트! (style 객체 전체 덮어쓰기 방지) - handleUpdate("style.labelDisplay", boolValue); - handleUpdate("labelDisplay", boolValue); - // labelText도 설정 (처음 켤 때 라벨 텍스트가 없을 수 있음) - if (boolValue && !selectedComponent.style?.labelText) { - const labelValue = selectedComponent.label || selectedComponent.componentConfig?.label || ""; - if (labelValue) { - handleUpdate("style.labelText", labelValue); - } +
+ {/* 표시 */} +
+ 표시 + { + const boolValue = checked === true; + handleUpdate("style.labelDisplay", boolValue); + handleUpdate("labelDisplay", boolValue); + if (boolValue && !selectedComponent.style?.labelText) { + const labelValue = selectedComponent.label || selectedComponent.componentConfig?.label || ""; + if (labelValue) { + handleUpdate("style.labelText", labelValue); } - }} - className="h-4 w-4" - /> - -
+ } + }} + className="h-4 w-4" + />
)} - - {/* 옵션 - 입력 필드에서는 항상 표시, 기타 컴포넌트는 속성이 정의된 경우만 표시 */} -
- {(isInputField || widget.required !== undefined) && (() => { - const colName = widget.columnName || selectedComponent?.columnName; - const colMeta = colName ? currentTable?.columns?.find( - (c: any) => (c.columnName || c.column_name || "").toLowerCase() === colName.toLowerCase() - ) : null; - const isNotNull = colMeta && ((colMeta as any).isNullable === "NO" || (colMeta as any).isNullable === "N" || (colMeta as any).is_nullable === "NO" || (colMeta as any).is_nullable === "N"); - return ( -
- { - if (isNotNull) return; - handleUpdate("required", checked); - handleUpdate("componentConfig.required", checked); - }} - disabled={!!isNotNull} - className="h-4 w-4" - /> - -
- ); - })()} - {(isInputField || widget.readonly !== undefined) && ( -
- { - handleUpdate("readonly", checked); - handleUpdate("componentConfig.readonly", checked); - }} - className="h-4 w-4" - /> - -
- )} - {/* 숨김 옵션 - 모든 컴포넌트에서 표시 */} -
- { - handleUpdate("hidden", checked); - handleUpdate("componentConfig.hidden", checked); - }} - className="h-4 w-4" - /> - -
-
); };