선택항목 상게입력 컴포넌트 구현
This commit is contained in:
@@ -60,6 +60,9 @@ interface RealtimePreviewProps {
|
||||
sortBy?: string;
|
||||
sortOrder?: "asc" | "desc";
|
||||
columnOrder?: string[];
|
||||
|
||||
// 🆕 조건부 컨테이너 높이 변화 콜백
|
||||
onHeightChange?: (componentId: string, newHeight: number) => void;
|
||||
}
|
||||
|
||||
// 동적 위젯 타입 아이콘 (레지스트리에서 조회)
|
||||
@@ -123,6 +126,7 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
|
||||
onFlowRefresh,
|
||||
formData,
|
||||
onFormDataChange,
|
||||
onHeightChange, // 🆕 조건부 컨테이너 높이 변화 콜백
|
||||
}) => {
|
||||
const [actualHeight, setActualHeight] = React.useState<number | null>(null);
|
||||
const contentRef = React.useRef<HTMLDivElement>(null);
|
||||
@@ -225,6 +229,12 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
|
||||
};
|
||||
|
||||
const getHeight = () => {
|
||||
// 🆕 조건부 컨테이너는 높이를 자동으로 설정 (내용물에 따라 자동 조정)
|
||||
const isConditionalContainer = (component as any).componentType === "conditional-container";
|
||||
if (isConditionalContainer && !isDesignMode) {
|
||||
return "auto"; // 런타임에서는 내용물 높이에 맞춤
|
||||
}
|
||||
|
||||
// 플로우 위젯의 경우 측정된 높이 사용
|
||||
const isFlowWidget = component.type === "component" && (component as any).componentType === "flow-widget";
|
||||
if (isFlowWidget && actualHeight) {
|
||||
@@ -325,7 +335,12 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
|
||||
(contentRef as any).current = node;
|
||||
}
|
||||
}}
|
||||
className={`${component.type === "component" && (component as any).componentType === "flow-widget" ? "h-auto" : "h-full"} overflow-visible`}
|
||||
className={`${
|
||||
(component.type === "component" && (component as any).componentType === "flow-widget") ||
|
||||
((component as any).componentType === "conditional-container" && !isDesignMode)
|
||||
? "h-auto"
|
||||
: "h-full"
|
||||
} overflow-visible`}
|
||||
style={{ width: "100%", maxWidth: "100%" }}
|
||||
>
|
||||
<DynamicComponentRenderer
|
||||
@@ -361,6 +376,7 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
|
||||
sortBy={sortBy}
|
||||
sortOrder={sortOrder}
|
||||
columnOrder={columnOrder}
|
||||
onHeightChange={onHeightChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -274,6 +274,7 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
|
||||
<SelectItem value="edit">편집</SelectItem>
|
||||
<SelectItem value="copy">복사 (품목코드 초기화)</SelectItem>
|
||||
<SelectItem value="navigate">페이지 이동</SelectItem>
|
||||
<SelectItem value="openModalWithData">데이터 전달 + 모달 열기 🆕</SelectItem>
|
||||
<SelectItem value="modal">모달 열기</SelectItem>
|
||||
<SelectItem value="control">제어 흐름</SelectItem>
|
||||
<SelectItem value="view_table_history">테이블 이력 보기</SelectItem>
|
||||
@@ -409,6 +410,136 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 🆕 데이터 전달 + 모달 열기 액션 설정 */}
|
||||
{component.componentConfig?.action?.type === "openModalWithData" && (
|
||||
<div className="mt-4 space-y-4 rounded-lg border bg-blue-50 p-4 dark:bg-blue-950/20">
|
||||
<h4 className="text-sm font-medium text-foreground">데이터 전달 + 모달 설정</h4>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
TableList에서 선택된 데이터를 다음 모달로 전달합니다
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="data-source-id">데이터 소스 ID</Label>
|
||||
<Input
|
||||
id="data-source-id"
|
||||
placeholder="예: item_info (테이블명과 동일하게 입력)"
|
||||
value={component.componentConfig?.action?.dataSourceId || ""}
|
||||
onChange={(e) => {
|
||||
onUpdateProperty("componentConfig.action.dataSourceId", e.target.value);
|
||||
}}
|
||||
/>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
TableList에서 데이터를 저장한 ID와 동일해야 합니다 (보통 테이블명)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="modal-title-with-data">모달 제목</Label>
|
||||
<Input
|
||||
id="modal-title-with-data"
|
||||
placeholder="예: 상세 정보 입력"
|
||||
value={localInputs.modalTitle}
|
||||
onChange={(e) => {
|
||||
const newValue = e.target.value;
|
||||
setLocalInputs((prev) => ({ ...prev, modalTitle: newValue }));
|
||||
onUpdateProperty("componentConfig.action.modalTitle", newValue);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="modal-size-with-data">모달 크기</Label>
|
||||
<Select
|
||||
value={component.componentConfig?.action?.modalSize || "lg"}
|
||||
onValueChange={(value) => {
|
||||
onUpdateProperty("componentConfig.action.modalSize", value);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="모달 크기 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="sm">작음 (Small)</SelectItem>
|
||||
<SelectItem value="md">보통 (Medium)</SelectItem>
|
||||
<SelectItem value="lg">큼 (Large) - 권장</SelectItem>
|
||||
<SelectItem value="xl">매우 큼 (Extra Large)</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="target-screen-with-data">대상 화면 선택</Label>
|
||||
<Popover open={modalScreenOpen} onOpenChange={setModalScreenOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={modalScreenOpen}
|
||||
className="h-6 w-full justify-between px-2 py-0"
|
||||
style={{ fontSize: "12px" }}
|
||||
disabled={screensLoading}
|
||||
>
|
||||
{config.action?.targetScreenId
|
||||
? screens.find((screen) => screen.id === parseInt(config.action?.targetScreenId))?.name ||
|
||||
"화면을 선택하세요..."
|
||||
: "화면을 선택하세요..."}
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="p-0" align="start" style={{ width: "var(--radix-popover-trigger-width)" }}>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex items-center border-b px-3 py-2">
|
||||
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
<Input
|
||||
placeholder="화면 검색..."
|
||||
value={modalSearchTerm}
|
||||
onChange={(e) => setModalSearchTerm(e.target.value)}
|
||||
className="border-0 p-0 focus-visible:ring-0"
|
||||
/>
|
||||
</div>
|
||||
<div className="max-h-[200px] overflow-auto">
|
||||
{(() => {
|
||||
const filteredScreens = filterScreens(modalSearchTerm);
|
||||
if (screensLoading) {
|
||||
return <div className="p-3 text-sm text-muted-foreground">화면 목록을 불러오는 중...</div>;
|
||||
}
|
||||
if (filteredScreens.length === 0) {
|
||||
return <div className="p-3 text-sm text-muted-foreground">검색 결과가 없습니다.</div>;
|
||||
}
|
||||
return filteredScreens.map((screen, index) => (
|
||||
<div
|
||||
key={`modal-data-screen-${screen.id}-${index}`}
|
||||
className="flex cursor-pointer items-center px-3 py-2 hover:bg-muted"
|
||||
onClick={() => {
|
||||
onUpdateProperty("componentConfig.action.targetScreenId", screen.id);
|
||||
setModalScreenOpen(false);
|
||||
setModalSearchTerm("");
|
||||
}}
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
parseInt(config.action?.targetScreenId) === screen.id ? "opacity-100" : "opacity-0",
|
||||
)}
|
||||
/>
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">{screen.name}</span>
|
||||
{screen.description && <span className="text-xs text-muted-foreground">{screen.description}</span>}
|
||||
</div>
|
||||
</div>
|
||||
));
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
SelectedItemsDetailInput 컴포넌트가 있는 화면을 선택하세요
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 수정 액션 설정 */}
|
||||
{(component.componentConfig?.action?.type || "save") === "edit" && (
|
||||
<div className="mt-4 space-y-4 rounded-lg border bg-success/10 p-4">
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Plus, X, GripVertical, ChevronDown, ChevronUp } from "lucide-react";
|
||||
import { RepeaterFieldGroupConfig, RepeaterData, RepeaterItemData, RepeaterFieldDefinition } from "@/types/repeater";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
Reference in New Issue
Block a user