[agent-pipeline] pipe-20260311071246-rhvz round-1

This commit is contained in:
DDD1542 2026-03-11 16:17:32 +09:00
parent 0eba11e047
commit d4f2a3cf04
1 changed files with 219 additions and 205 deletions

View File

@ -328,11 +328,13 @@ export const V2PropertiesPanel: React.FC<V2PropertiesPanelProps> = ({
const isInputField = inputFieldTypes.includes(componentType); const isInputField = inputFieldTypes.includes(componentType);
return ( return (
<div className="space-y-2">
{/* 너비 + 높이 (같은 행) */}
<div className="grid grid-cols-2 gap-2">
<div className="space-y-1"> <div className="space-y-1">
<Label className="text-xs"> (px)</Label> {/* DIMENSIONS 섹션 */}
<div className="border-b border-border/50 pb-3 mb-3">
<h4 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground py-2">DIMENSIONS</h4>
<div className="flex gap-2">
<div className="flex-1">
<Label className="text-[10px] text-muted-foreground"></Label>
<Input <Input
type="number" type="number"
min={10} min={10}
@ -362,11 +364,11 @@ export const V2PropertiesPanel: React.FC<V2PropertiesPanelProps> = ({
} }
}} }}
placeholder="100" placeholder="100"
className="h-6 w-full px-2 py-0 text-xs" className="h-7 text-xs"
/> />
</div> </div>
<div className="space-y-1"> <div className="flex-1">
<Label className="text-xs"></Label> <Label className="text-[10px] text-muted-foreground"></Label>
<Input <Input
type="number" type="number"
value={localHeight} value={localHeight}
@ -394,59 +396,122 @@ export const V2PropertiesPanel: React.FC<V2PropertiesPanelProps> = ({
}} }}
step={1} step={1}
placeholder="10" placeholder="10"
className="h-6 w-full px-2 py-0 text-xs" className="h-7 text-xs"
/> />
</div> </div>
</div> </div>
{/* Title (group/area) */}
{(selectedComponent.type === "group" || selectedComponent.type === "area") && (
<div className="space-y-1">
<Label className="text-xs"></Label>
<Input
value={group.title || area.title || ""}
onChange={(e) => handleUpdate("title", e.target.value)}
placeholder="제목"
className="h-6 w-full px-2 py-0 text-xs"
/>
</div>
)}
{/* Description (area만) */}
{selectedComponent.type === "area" && (
<div className="space-y-1">
<Label className="text-xs"></Label>
<Input
value={area.description || ""}
onChange={(e) => handleUpdate("description", e.target.value)}
placeholder="설명"
className="h-6 w-full px-2 py-0 text-xs"
/>
</div>
)}
{/* Z-Index */} {/* Z-Index */}
<div className="space-y-1"> <div className="flex items-center justify-between py-1.5">
<Label className="text-xs">Z-Index</Label> <span className="text-xs text-muted-foreground">Z-Index</span>
<div className="w-[140px]">
<Input <Input
type="number" type="number"
step="1" step="1"
value={currentPosition.z || 1} value={currentPosition.z || 1}
onChange={(e) => handleUpdate("position.z", parseInt(e.target.value) || 1)} onChange={(e) => handleUpdate("position.z", parseInt(e.target.value) || 1)}
className="h-6 w-full px-2 py-0 text-xs" className="h-7 text-xs"
/> />
</div> </div>
</div>
</div>
{/* 라벨 스타일 - 입력 필드에서만 표시 */} {/* Title (group/area) */}
{(selectedComponent.type === "group" || selectedComponent.type === "area") && (
<div className="border-b border-border/50 pb-3 mb-3">
<h4 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground py-2">CONTENT</h4>
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"></span>
<div className="w-[140px]">
<Input
value={group.title || area.title || ""}
onChange={(e) => handleUpdate("title", e.target.value)}
placeholder="제목"
className="h-7 text-xs"
/>
</div>
</div>
{selectedComponent.type === "area" && (
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"></span>
<div className="w-[140px]">
<Input
value={area.description || ""}
onChange={(e) => handleUpdate("description", e.target.value)}
placeholder="설명"
className="h-7 text-xs"
/>
</div>
</div>
)}
</div>
)}
{/* OPTIONS 섹션 */}
<div className="border-b border-border/50 pb-3 mb-3">
<h4 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground py-2">OPTIONS</h4>
{(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 (
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground">
{isNotNull && <span className="text-muted-foreground/60 ml-1">(NOT NULL)</span>}
</span>
<Checkbox
checked={isNotNull || widget.required === true || selectedComponent.componentConfig?.required === true}
onCheckedChange={(checked) => {
if (isNotNull) return;
handleUpdate("required", checked);
handleUpdate("componentConfig.required", checked);
}}
disabled={!!isNotNull}
className="h-4 w-4"
/>
</div>
);
})()}
{(isInputField || widget.readonly !== undefined) && (
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"></span>
<Checkbox
checked={widget.readonly === true || selectedComponent.componentConfig?.readonly === true}
onCheckedChange={(checked) => {
handleUpdate("readonly", checked);
handleUpdate("componentConfig.readonly", checked);
}}
className="h-4 w-4"
/>
</div>
)}
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"></span>
<Checkbox
checked={selectedComponent.hidden === true || selectedComponent.componentConfig?.hidden === true}
onCheckedChange={(checked) => {
handleUpdate("hidden", checked);
handleUpdate("componentConfig.hidden", checked);
}}
className="h-4 w-4"
/>
</div>
</div>
{/* LABEL 섹션 - 입력 필드에서만 표시 */}
{isInputField && ( {isInputField && (
<Collapsible> <Collapsible>
<CollapsibleTrigger className="flex w-full items-center justify-between rounded-lg bg-slate-50 p-2 text-xs font-medium hover:bg-slate-100"> <CollapsibleTrigger className="flex w-full items-center justify-between py-0.5 text-left">
<span className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">LABEL</span>
<ChevronDown className="h-3.5 w-3.5" /> <ChevronDown className="h-3 w-3 shrink-0 text-muted-foreground/50" />
</CollapsibleTrigger> </CollapsibleTrigger>
<CollapsibleContent className="mt-2 space-y-2"> <CollapsibleContent className="mt-1.5 space-y-1">
<div className="space-y-1"> {/* 라벨 텍스트 */}
<Label className="text-xs"> </Label> <div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"></span>
<div className="w-[140px]">
<Input <Input
value={ value={
selectedComponent.style?.labelText !== undefined selectedComponent.style?.labelText !== undefined
@ -455,20 +520,22 @@ export const V2PropertiesPanel: React.FC<V2PropertiesPanelProps> = ({
} }
onChange={(e) => { onChange={(e) => {
handleUpdate("style.labelText", e.target.value); handleUpdate("style.labelText", e.target.value);
handleUpdate("label", e.target.value); // label도 함께 업데이트 handleUpdate("label", e.target.value);
}} }}
placeholder="라벨을 입력하세요 (비우면 라벨 없음)" placeholder="라벨"
className="h-6 w-full px-2 py-0 text-xs" className="h-7 text-xs"
/> />
</div> </div>
<div className="grid grid-cols-2 gap-2"> </div>
<div className="space-y-1"> {/* 위치 + 간격 */}
<Label className="text-xs"></Label> <div className="flex gap-2">
<div className="flex-1">
<Label className="text-[10px] text-muted-foreground"></Label>
<Select <Select
value={selectedComponent.style?.labelPosition || "top"} value={selectedComponent.style?.labelPosition || "top"}
onValueChange={(value) => handleUpdate("style.labelPosition", value)} onValueChange={(value) => handleUpdate("style.labelPosition", value)}
> >
<SelectTrigger className="h-6 text-xs"> <SelectTrigger className="h-7 text-xs">
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@ -479,8 +546,8 @@ export const V2PropertiesPanel: React.FC<V2PropertiesPanelProps> = ({
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
<div className="space-y-1"> <div className="flex-1">
<Label className="text-xs"></Label> <Label className="text-[10px] text-muted-foreground"></Label>
<Input <Input
value={ value={
(selectedComponent.style?.labelPosition === "left" || selectedComponent.style?.labelPosition === "right") (selectedComponent.style?.labelPosition === "left" || selectedComponent.style?.labelPosition === "right")
@ -495,21 +562,22 @@ export const V2PropertiesPanel: React.FC<V2PropertiesPanelProps> = ({
handleUpdate("style.labelMarginBottom", e.target.value); handleUpdate("style.labelMarginBottom", e.target.value);
} }
}} }}
className="h-6 w-full px-2 py-0 text-xs" className="h-7 text-xs"
/> />
</div> </div>
</div> </div>
<div className="grid grid-cols-2 gap-2"> {/* 크기 + 색상 */}
<div className="space-y-1"> <div className="flex gap-2">
<Label className="text-xs"></Label> <div className="flex-1">
<Label className="text-[10px] text-muted-foreground"></Label>
<Input <Input
value={selectedComponent.style?.labelFontSize || "12px"} value={selectedComponent.style?.labelFontSize || "12px"}
onChange={(e) => handleUpdate("style.labelFontSize", e.target.value)} onChange={(e) => handleUpdate("style.labelFontSize", e.target.value)}
className="h-6 w-full px-2 py-0 text-xs" className="h-7 text-xs"
/> />
</div> </div>
<div className="space-y-1"> <div className="flex-1">
<Label className="text-xs"></Label> <Label className="text-[10px] text-muted-foreground"></Label>
<ColorPickerWithTransparent <ColorPickerWithTransparent
value={selectedComponent.style?.labelColor} value={selectedComponent.style?.labelColor}
onChange={(value) => handleUpdate("style.labelColor", value)} onChange={(value) => handleUpdate("style.labelColor", value)}
@ -518,14 +586,15 @@ export const V2PropertiesPanel: React.FC<V2PropertiesPanelProps> = ({
/> />
</div> </div>
</div> </div>
<div className="grid grid-cols-2 gap-2"> {/* 굵기 */}
<div className="space-y-1"> <div className="flex items-center justify-between py-1.5">
<Label className="text-xs"></Label> <span className="text-xs text-muted-foreground"></span>
<div className="w-[140px]">
<Select <Select
value={selectedComponent.style?.labelFontWeight || "500"} value={selectedComponent.style?.labelFontWeight || "500"}
onValueChange={(value) => handleUpdate("style.labelFontWeight", value)} onValueChange={(value) => handleUpdate("style.labelFontWeight", value)}
> >
<SelectTrigger className="h-6 text-xs"> <SelectTrigger className="h-7 text-xs">
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@ -536,15 +605,16 @@ export const V2PropertiesPanel: React.FC<V2PropertiesPanelProps> = ({
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
<div className="flex items-center space-x-2 pt-5"> </div>
{/* 표시 */}
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"></span>
<Checkbox <Checkbox
checked={selectedComponent.style?.labelDisplay === true || selectedComponent.labelDisplay === true} checked={selectedComponent.style?.labelDisplay === true || selectedComponent.labelDisplay === true}
onCheckedChange={(checked) => { onCheckedChange={(checked) => {
const boolValue = checked === true; const boolValue = checked === true;
// 🔧 "필수"처럼 직접 경로로 업데이트! (style 객체 전체 덮어쓰기 방지)
handleUpdate("style.labelDisplay", boolValue); handleUpdate("style.labelDisplay", boolValue);
handleUpdate("labelDisplay", boolValue); handleUpdate("labelDisplay", boolValue);
// labelText도 설정 (처음 켤 때 라벨 텍스트가 없을 수 있음)
if (boolValue && !selectedComponent.style?.labelText) { if (boolValue && !selectedComponent.style?.labelText) {
const labelValue = selectedComponent.label || selectedComponent.componentConfig?.label || ""; const labelValue = selectedComponent.label || selectedComponent.componentConfig?.label || "";
if (labelValue) { if (labelValue) {
@ -554,66 +624,10 @@ export const V2PropertiesPanel: React.FC<V2PropertiesPanelProps> = ({
}} }}
className="h-4 w-4" className="h-4 w-4"
/> />
<Label className="text-xs"></Label>
</div>
</div> </div>
</CollapsibleContent> </CollapsibleContent>
</Collapsible> </Collapsible>
)} )}
{/* 옵션 - 입력 필드에서는 항상 표시, 기타 컴포넌트는 속성이 정의된 경우만 표시 */}
<div className="grid grid-cols-2 gap-2">
{(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 (
<div className="flex items-center space-x-2">
<Checkbox
checked={isNotNull || widget.required === true || selectedComponent.componentConfig?.required === true}
onCheckedChange={(checked) => {
if (isNotNull) return;
handleUpdate("required", checked);
handleUpdate("componentConfig.required", checked);
}}
disabled={!!isNotNull}
className="h-4 w-4"
/>
<Label className="text-xs">
{isNotNull && <span className="text-muted-foreground ml-1">(NOT NULL)</span>}
</Label>
</div>
);
})()}
{(isInputField || widget.readonly !== undefined) && (
<div className="flex items-center space-x-2">
<Checkbox
checked={widget.readonly === true || selectedComponent.componentConfig?.readonly === true}
onCheckedChange={(checked) => {
handleUpdate("readonly", checked);
handleUpdate("componentConfig.readonly", checked);
}}
className="h-4 w-4"
/>
<Label className="text-xs"></Label>
</div>
)}
{/* 숨김 옵션 - 모든 컴포넌트에서 표시 */}
<div className="flex items-center space-x-2">
<Checkbox
checked={selectedComponent.hidden === true || selectedComponent.componentConfig?.hidden === true}
onCheckedChange={(checked) => {
handleUpdate("hidden", checked);
handleUpdate("componentConfig.hidden", checked);
}}
className="h-4 w-4"
/>
<Label className="text-xs"></Label>
</div>
</div>
</div> </div>
); );
}; };