api요청정보 수정
This commit is contained in:
parent
bfc0c3fc39
commit
addff4769b
|
|
@ -12,6 +12,7 @@ import { Button } from "@/components/ui/button";
|
|||
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useFlowEditorStore } from "@/lib/stores/flowEditorStore";
|
||||
import { tableTypeApi } from "@/lib/api/screen";
|
||||
|
|
@ -34,10 +35,10 @@ export function TableSourceProperties({ nodeId, data }: TableSourcePropertiesPro
|
|||
|
||||
const [displayName, setDisplayName] = useState(data.displayName || data.tableName);
|
||||
const [tableName, setTableName] = useState(data.tableName);
|
||||
|
||||
|
||||
// 🆕 데이터 소스 타입 (기본값: context-data)
|
||||
const [dataSourceType, setDataSourceType] = useState<"context-data" | "table-all">(
|
||||
(data as any).dataSourceType || "context-data"
|
||||
(data as any).dataSourceType || "context-data",
|
||||
);
|
||||
|
||||
// 테이블 선택 관련 상태
|
||||
|
|
@ -167,171 +168,168 @@ export function TableSourceProperties({ nodeId, data }: TableSourcePropertiesPro
|
|||
|
||||
return (
|
||||
<div className="space-y-4 p-4 pb-8">
|
||||
{/* 기본 정보 */}
|
||||
<div>
|
||||
<h3 className="mb-3 text-sm font-semibold">기본 정보</h3>
|
||||
{/* 기본 정보 */}
|
||||
<div>
|
||||
<h3 className="mb-3 text-sm font-semibold">기본 정보</h3>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<Label htmlFor="displayName" className="text-xs">
|
||||
표시 이름
|
||||
</Label>
|
||||
<Input
|
||||
id="displayName"
|
||||
value={displayName}
|
||||
onChange={(e) => handleDisplayNameChange(e.target.value)}
|
||||
className="mt-1"
|
||||
placeholder="노드 표시 이름"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<Label htmlFor="displayName" className="text-xs">
|
||||
표시 이름
|
||||
</Label>
|
||||
<Input
|
||||
id="displayName"
|
||||
value={displayName}
|
||||
onChange={(e) => handleDisplayNameChange(e.target.value)}
|
||||
className="mt-1"
|
||||
placeholder="노드 표시 이름"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 테이블 선택 Combobox */}
|
||||
<div>
|
||||
<Label className="text-xs">테이블 선택</Label>
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={open}
|
||||
className="mt-1 w-full justify-between"
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? (
|
||||
<span className="text-muted-foreground">로딩 중...</span>
|
||||
) : tableName ? (
|
||||
<span className="truncate">{selectedTableLabel}</span>
|
||||
) : (
|
||||
<span className="text-muted-foreground">테이블을 선택하세요</span>
|
||||
)}
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[320px] p-0" align="start">
|
||||
<Command>
|
||||
<CommandInput placeholder="테이블 검색..." className="h-9" />
|
||||
<CommandList>
|
||||
<CommandEmpty>검색 결과가 없습니다.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
<ScrollArea className="h-[300px]">
|
||||
{tables.map((table) => (
|
||||
<CommandItem
|
||||
key={table.tableName}
|
||||
value={`${table.label} ${table.tableName} ${table.description}`}
|
||||
onSelect={() => handleTableSelect(table.tableName)}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
tableName === table.tableName ? "opacity-100" : "opacity-0",
|
||||
)}
|
||||
/>
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">{table.label}</span>
|
||||
{table.label !== table.tableName && (
|
||||
<span className="text-muted-foreground text-xs">{table.tableName}</span>
|
||||
)}
|
||||
{table.description && (
|
||||
<span className="text-muted-foreground text-xs">{table.description}</span>
|
||||
)}
|
||||
</div>
|
||||
</CommandItem>
|
||||
))}
|
||||
</ScrollArea>
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
{tableName && selectedTableLabel !== tableName && (
|
||||
<p className="text-muted-foreground mt-1 text-xs">
|
||||
실제 테이블명: <code className="rounded bg-gray-100 px-1 py-0.5">{tableName}</code>
|
||||
</p>
|
||||
{/* 테이블 선택 Combobox */}
|
||||
<div>
|
||||
<Label className="text-xs">테이블 선택</Label>
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={open}
|
||||
className="mt-1 w-full justify-between"
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? (
|
||||
<span className="text-muted-foreground">로딩 중...</span>
|
||||
) : tableName ? (
|
||||
<span className="truncate">{selectedTableLabel}</span>
|
||||
) : (
|
||||
<span className="text-muted-foreground">테이블을 선택하세요</span>
|
||||
)}
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[320px] p-0" align="start">
|
||||
<Command>
|
||||
<CommandInput placeholder="테이블 검색..." className="h-9" />
|
||||
<CommandList>
|
||||
<CommandEmpty>검색 결과가 없습니다.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
<ScrollArea className="h-[300px]">
|
||||
{tables.map((table) => (
|
||||
<CommandItem
|
||||
key={table.tableName}
|
||||
value={`${table.label} ${table.tableName} ${table.description}`}
|
||||
onSelect={() => handleTableSelect(table.tableName)}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
tableName === table.tableName ? "opacity-100" : "opacity-0",
|
||||
)}
|
||||
/>
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">{table.label}</span>
|
||||
{table.label !== table.tableName && (
|
||||
<span className="text-muted-foreground text-xs">{table.tableName}</span>
|
||||
)}
|
||||
{table.description && (
|
||||
<span className="text-muted-foreground text-xs">{table.description}</span>
|
||||
)}
|
||||
</div>
|
||||
</CommandItem>
|
||||
))}
|
||||
</ScrollArea>
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
{tableName && selectedTableLabel !== tableName && (
|
||||
<p className="text-muted-foreground mt-1 text-xs">
|
||||
실제 테이블명: <code className="rounded bg-gray-100 px-1 py-0.5">{tableName}</code>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 🆕 데이터 소스 설정 */}
|
||||
<div>
|
||||
<h3 className="mb-3 text-sm font-semibold">데이터 소스 설정</h3>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<Label className="text-xs">데이터 소스 타입</Label>
|
||||
<Select value={dataSourceType} onValueChange={handleDataSourceTypeChange}>
|
||||
<SelectTrigger className="mt-1">
|
||||
<SelectValue placeholder="데이터 소스 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="context-data">
|
||||
<div className="flex items-center gap-2">
|
||||
<FileText className="h-4 w-4" />
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">컨텍스트 데이터</span>
|
||||
<span className="text-muted-foreground text-xs">
|
||||
버튼에서 전달된 데이터 사용 (폼, 선택 항목 등)
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</SelectItem>
|
||||
<SelectItem value="table-all">
|
||||
<div className="flex items-center gap-2">
|
||||
<Table className="h-4 w-4" />
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">테이블 전체 데이터</span>
|
||||
<span className="text-muted-foreground text-xs">선택한 테이블의 모든 행 조회 (페이징 무관)</span>
|
||||
</div>
|
||||
</div>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
{/* 설명 텍스트 */}
|
||||
<div className="mt-2 rounded bg-blue-50 p-3 text-xs text-blue-700">
|
||||
{dataSourceType === "context-data" ? (
|
||||
<>
|
||||
<p className="mb-1 font-medium">💡 컨텍스트 데이터 모드</p>
|
||||
<p>버튼 실행 시 전달된 데이터(폼 데이터, 테이블 선택 항목 등)를 사용합니다.</p>
|
||||
<p className="mt-1 text-blue-600">• 폼 데이터: 1개 레코드</p>
|
||||
<p className="text-blue-600">• 테이블 선택: N개 레코드</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p className="mb-1 font-medium">📊 테이블 전체 데이터 모드</p>
|
||||
<p>선택한 테이블의 **모든 행**을 직접 조회합니다.</p>
|
||||
<p className="mt-1 font-medium text-orange-600">⚠️ 대량 데이터 시 성능 주의</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 🆕 데이터 소스 설정 */}
|
||||
<div>
|
||||
<h3 className="mb-3 text-sm font-semibold">데이터 소스 설정</h3>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<Label className="text-xs">데이터 소스 타입</Label>
|
||||
<Select value={dataSourceType} onValueChange={handleDataSourceTypeChange}>
|
||||
<SelectTrigger className="mt-1">
|
||||
<SelectValue placeholder="데이터 소스 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="context-data">
|
||||
<div className="flex items-center gap-2">
|
||||
<FileText className="h-4 w-4" />
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">컨텍스트 데이터</span>
|
||||
<span className="text-muted-foreground text-xs">
|
||||
버튼에서 전달된 데이터 사용 (폼, 선택 항목 등)
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</SelectItem>
|
||||
<SelectItem value="table-all">
|
||||
<div className="flex items-center gap-2">
|
||||
<Table className="h-4 w-4" />
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">테이블 전체 데이터</span>
|
||||
<span className="text-muted-foreground text-xs">
|
||||
선택한 테이블의 모든 행 조회 (페이징 무관)
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
{/* 설명 텍스트 */}
|
||||
<div className="mt-2 rounded bg-blue-50 p-3 text-xs text-blue-700">
|
||||
{dataSourceType === "context-data" ? (
|
||||
<>
|
||||
<p className="font-medium mb-1">💡 컨텍스트 데이터 모드</p>
|
||||
<p>버튼 실행 시 전달된 데이터(폼 데이터, 테이블 선택 항목 등)를 사용합니다.</p>
|
||||
<p className="mt-1 text-blue-600">• 폼 데이터: 1개 레코드</p>
|
||||
<p className="text-blue-600">• 테이블 선택: N개 레코드</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p className="font-medium mb-1">📊 테이블 전체 데이터 모드</p>
|
||||
<p>선택한 테이블의 **모든 행**을 직접 조회합니다.</p>
|
||||
<p className="mt-1 text-orange-600 font-medium">⚠️ 대량 데이터 시 성능 주의</p>
|
||||
</>
|
||||
)}
|
||||
{/* 필드 정보 */}
|
||||
<div>
|
||||
<h3 className="mb-3 text-sm font-semibold">
|
||||
출력 필드 {data.fields && data.fields.length > 0 && `(${data.fields.length}개)`}
|
||||
</h3>
|
||||
{data.fields && data.fields.length > 0 ? (
|
||||
<div className="max-h-[300px] space-y-1 overflow-y-auto rounded border bg-gray-50 p-2">
|
||||
{data.fields.map((field) => (
|
||||
<div key={field.name} className="flex items-center justify-between rounded bg-white px-2 py-1.5 text-xs">
|
||||
<span className="truncate font-mono text-gray-700" title={field.name}>
|
||||
{field.name}
|
||||
</span>
|
||||
<span className="ml-2 shrink-0 text-gray-400">{field.type}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 필드 정보 */}
|
||||
<div>
|
||||
<h3 className="mb-3 text-sm font-semibold">
|
||||
출력 필드 {data.fields && data.fields.length > 0 && `(${data.fields.length}개)`}
|
||||
</h3>
|
||||
{data.fields && data.fields.length > 0 ? (
|
||||
<div className="max-h-[300px] space-y-1 overflow-y-auto rounded border bg-gray-50 p-2">
|
||||
{data.fields.map((field) => (
|
||||
<div key={field.name} className="flex items-center justify-between rounded bg-white px-2 py-1.5 text-xs">
|
||||
<span className="truncate font-mono text-gray-700" title={field.name}>
|
||||
{field.name}
|
||||
</span>
|
||||
<span className="ml-2 shrink-0 text-gray-400">{field.type}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="rounded border p-4 text-center text-xs text-gray-400">필드 정보가 없습니다</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
) : (
|
||||
<div className="rounded border p-4 text-center text-xs text-gray-400">필드 정보가 없습니다</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import { Input } from "@/components/ui/input";
|
|||
import { Workflow, Info, CheckCircle, XCircle, Loader2, ArrowRight, ArrowDown } from "lucide-react";
|
||||
import { ComponentData } from "@/types/screen";
|
||||
import { FlowVisibilityConfig } from "@/types/control-management";
|
||||
import { getFlowById } from "@/lib/api/flow";
|
||||
import { getFlowById, getFlowSteps } from "@/lib/api/flow";
|
||||
import type { FlowDefinition, FlowStep } from "@/types/flow";
|
||||
import { toast } from "sonner";
|
||||
|
||||
|
|
@ -25,7 +25,7 @@ interface FlowVisibilityConfigPanelProps {
|
|||
|
||||
/**
|
||||
* 플로우 단계별 버튼 표시 설정 패널
|
||||
*
|
||||
*
|
||||
* 플로우 위젯이 화면에 있을 때, 버튼이 특정 플로우 단계에서만 표시되도록 설정할 수 있습니다.
|
||||
*/
|
||||
export const FlowVisibilityConfigPanel: React.FC<FlowVisibilityConfigPanelProps> = ({
|
||||
|
|
@ -40,8 +40,7 @@ export const FlowVisibilityConfigPanel: React.FC<FlowVisibilityConfigPanelProps>
|
|||
const flowWidgets = useMemo(() => {
|
||||
return allComponents.filter((comp) => {
|
||||
const isFlowWidget =
|
||||
comp.type === "flow" ||
|
||||
(comp.type === "component" && (comp as any).componentConfig?.type === "flow-widget");
|
||||
comp.type === "flow" || (comp.type === "component" && (comp as any).componentConfig?.type === "flow-widget");
|
||||
return isFlowWidget;
|
||||
});
|
||||
}, [allComponents]);
|
||||
|
|
@ -49,23 +48,23 @@ export const FlowVisibilityConfigPanel: React.FC<FlowVisibilityConfigPanelProps>
|
|||
// State
|
||||
const [enabled, setEnabled] = useState(currentConfig?.enabled || false);
|
||||
const [selectedFlowComponentId, setSelectedFlowComponentId] = useState<string | null>(
|
||||
currentConfig?.targetFlowComponentId || null
|
||||
currentConfig?.targetFlowComponentId || null,
|
||||
);
|
||||
const [mode, setMode] = useState<"whitelist" | "blacklist" | "all">(currentConfig?.mode || "whitelist");
|
||||
const [visibleSteps, setVisibleSteps] = useState<number[]>(currentConfig?.visibleSteps || []);
|
||||
const [hiddenSteps, setHiddenSteps] = useState<number[]>(currentConfig?.hiddenSteps || []);
|
||||
const [layoutBehavior, setLayoutBehavior] = useState<"preserve-position" | "auto-compact">(
|
||||
currentConfig?.layoutBehavior || "auto-compact"
|
||||
currentConfig?.layoutBehavior || "auto-compact",
|
||||
);
|
||||
|
||||
// 🆕 그룹 설정 (auto-compact 모드에서만 사용)
|
||||
const [groupId, setGroupId] = useState<string>(currentConfig?.groupId || `group-${Date.now()}`);
|
||||
const [groupDirection, setGroupDirection] = useState<"horizontal" | "vertical">(
|
||||
currentConfig?.groupDirection || "horizontal"
|
||||
currentConfig?.groupDirection || "horizontal",
|
||||
);
|
||||
const [groupGap, setGroupGap] = useState<number>(currentConfig?.groupGap ?? 8);
|
||||
const [groupAlign, setGroupAlign] = useState<"start" | "center" | "end" | "space-between" | "space-around">(
|
||||
currentConfig?.groupAlign || "start"
|
||||
currentConfig?.groupAlign || "start",
|
||||
);
|
||||
|
||||
// 선택된 플로우의 스텝 목록
|
||||
|
|
@ -127,13 +126,12 @@ export const FlowVisibilityConfigPanel: React.FC<FlowVisibilityConfigPanelProps>
|
|||
setFlowInfo(flowResponse.data);
|
||||
|
||||
// 스텝 목록 조회
|
||||
const stepsResponse = await fetch(`/api/flow/definitions/${flowId}/steps`);
|
||||
if (!stepsResponse.ok) {
|
||||
const stepsResponse = await getFlowSteps(flowId);
|
||||
if (!stepsResponse.success) {
|
||||
throw new Error("스텝 목록을 불러올 수 없습니다");
|
||||
}
|
||||
const stepsData = await stepsResponse.json();
|
||||
if (stepsData.success && stepsData.data) {
|
||||
const sortedSteps = stepsData.data.sort((a: FlowStep, b: FlowStep) => a.stepOrder - b.stepOrder);
|
||||
if (stepsResponse.data) {
|
||||
const sortedSteps = stepsResponse.data.sort((a: FlowStep, b: FlowStep) => a.stepOrder - b.stepOrder);
|
||||
setFlowSteps(sortedSteps);
|
||||
}
|
||||
} catch (error: any) {
|
||||
|
|
@ -346,12 +344,10 @@ export const FlowVisibilityConfigPanel: React.FC<FlowVisibilityConfigPanelProps>
|
|||
</div>
|
||||
|
||||
{/* 스텝 체크박스 목록 */}
|
||||
<div className="space-y-2 rounded-lg border bg-muted/30 p-3">
|
||||
<div className="bg-muted/30 space-y-2 rounded-lg border p-3">
|
||||
{flowSteps.map((step) => {
|
||||
const isChecked =
|
||||
mode === "whitelist"
|
||||
? visibleSteps.includes(step.id)
|
||||
: hiddenSteps.includes(step.id);
|
||||
mode === "whitelist" ? visibleSteps.includes(step.id) : hiddenSteps.includes(step.id);
|
||||
|
||||
return (
|
||||
<div key={step.id} className="flex items-center gap-2">
|
||||
|
|
@ -366,7 +362,9 @@ export const FlowVisibilityConfigPanel: React.FC<FlowVisibilityConfigPanelProps>
|
|||
</Badge>
|
||||
<span>{step.stepName}</span>
|
||||
{isChecked && (
|
||||
<CheckCircle className={`ml-auto h-4 w-4 ${mode === "whitelist" ? "text-green-500" : "text-red-500"}`} />
|
||||
<CheckCircle
|
||||
className={`ml-auto h-4 w-4 ${mode === "whitelist" ? "text-green-500" : "text-red-500"}`}
|
||||
/>
|
||||
)}
|
||||
</Label>
|
||||
</div>
|
||||
|
|
@ -403,14 +401,12 @@ export const FlowVisibilityConfigPanel: React.FC<FlowVisibilityConfigPanelProps>
|
|||
|
||||
{/* 🆕 그룹 설정 (auto-compact 모드일 때만 표시) */}
|
||||
{layoutBehavior === "auto-compact" && (
|
||||
<div className="rounded-lg border border-blue-200 bg-blue-50 p-4 space-y-4">
|
||||
<div className="space-y-4 rounded-lg border border-blue-200 bg-blue-50 p-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
그룹 설정
|
||||
</Badge>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
같은 그룹 ID를 가진 버튼들이 자동으로 정렬됩니다
|
||||
</p>
|
||||
<p className="text-muted-foreground text-xs">같은 그룹 ID를 가진 버튼들이 자동으로 정렬됩니다</p>
|
||||
</div>
|
||||
|
||||
{/* 그룹 ID */}
|
||||
|
|
@ -425,7 +421,7 @@ export const FlowVisibilityConfigPanel: React.FC<FlowVisibilityConfigPanelProps>
|
|||
placeholder="group-1"
|
||||
className="h-8 text-xs sm:h-9 sm:text-sm"
|
||||
/>
|
||||
<p className="text-[10px] text-muted-foreground">
|
||||
<p className="text-muted-foreground text-[10px]">
|
||||
같은 그룹 ID를 가진 버튼들이 하나의 그룹으로 묶입니다
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -577,4 +573,3 @@ export const FlowVisibilityConfigPanel: React.FC<FlowVisibilityConfigPanelProps>
|
|||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,14 @@ import { FlowComponent } from "@/types/screen-management";
|
|||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { AlertCircle, Loader2, ChevronUp, History } from "lucide-react";
|
||||
import { getFlowById, getAllStepCounts, getStepDataList, getFlowAuditLogs } from "@/lib/api/flow";
|
||||
import {
|
||||
getFlowById,
|
||||
getAllStepCounts,
|
||||
getStepDataList,
|
||||
getFlowAuditLogs,
|
||||
getFlowSteps,
|
||||
getFlowConnections,
|
||||
} from "@/lib/api/flow";
|
||||
import type { FlowDefinition, FlowStep, FlowAuditLog } from "@/types/flow";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
|
|
@ -162,22 +169,18 @@ export function FlowWidget({
|
|||
setFlowData(flowResponse.data);
|
||||
|
||||
// 스텝 목록 조회
|
||||
const stepsResponse = await fetch(`/api/flow/definitions/${flowId}/steps`);
|
||||
if (!stepsResponse.ok) {
|
||||
const stepsResponse = await getFlowSteps(flowId);
|
||||
if (!stepsResponse.success) {
|
||||
throw new Error("스텝 목록을 불러올 수 없습니다");
|
||||
}
|
||||
const stepsData = await stepsResponse.json();
|
||||
if (stepsData.success && stepsData.data) {
|
||||
const sortedSteps = stepsData.data.sort((a: FlowStep, b: FlowStep) => a.stepOrder - b.stepOrder);
|
||||
if (stepsResponse.data) {
|
||||
const sortedSteps = stepsResponse.data.sort((a: FlowStep, b: FlowStep) => a.stepOrder - b.stepOrder);
|
||||
setSteps(sortedSteps);
|
||||
|
||||
// 연결 정보 조회
|
||||
const connectionsResponse = await fetch(`/api/flow/connections/${flowId}`);
|
||||
if (connectionsResponse.ok) {
|
||||
const connectionsData = await connectionsResponse.json();
|
||||
if (connectionsData.success && connectionsData.data) {
|
||||
setConnections(connectionsData.data);
|
||||
}
|
||||
const connectionsResponse = await getFlowConnections(flowId);
|
||||
if (connectionsResponse.success && connectionsResponse.data) {
|
||||
setConnections(connectionsResponse.data);
|
||||
}
|
||||
|
||||
// 스텝별 데이터 건수 조회
|
||||
|
|
|
|||
|
|
@ -19,7 +19,33 @@ import {
|
|||
ApiResponse,
|
||||
} from "@/types/flow";
|
||||
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL || "/api";
|
||||
// API URL 동적 설정
|
||||
const getApiBaseUrl = (): string => {
|
||||
// 1. 환경변수가 있으면 우선 사용
|
||||
if (process.env.NEXT_PUBLIC_API_URL) {
|
||||
return process.env.NEXT_PUBLIC_API_URL;
|
||||
}
|
||||
|
||||
// 2. 클라이언트 사이드에서 동적 설정
|
||||
if (typeof window !== "undefined") {
|
||||
const currentHost = window.location.hostname;
|
||||
|
||||
// 프로덕션 환경: v1.vexplor.com → api.vexplor.com
|
||||
if (currentHost === "v1.vexplor.com") {
|
||||
return "https://api.vexplor.com/api";
|
||||
}
|
||||
|
||||
// 로컬 개발환경
|
||||
if (currentHost === "localhost" || currentHost === "127.0.0.1") {
|
||||
return "http://localhost:8080/api";
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 기본값
|
||||
return "/api";
|
||||
};
|
||||
|
||||
const API_BASE = getApiBaseUrl();
|
||||
|
||||
// 토큰 가져오기
|
||||
function getAuthToken(): string | null {
|
||||
|
|
|
|||
Loading…
Reference in New Issue