[agent-pipeline] pipe-20260311052455-y968 round-4

This commit is contained in:
DDD1542 2026-03-11 14:50:02 +09:00
parent 834c52a2b2
commit f071777131
2 changed files with 123 additions and 51 deletions

View File

@ -1,75 +1,144 @@
"use client"; "use client";
import React from "react"; import React from "react";
import { ConfigPanelBuilderProps } from "./ConfigPanelTypes"; import { ConfigPanelBuilderProps, ConfigSectionDefinition } from "./ConfigPanelTypes";
import { ConfigSection } from "./ConfigSection"; import { ConfigSection } from "./ConfigSection";
import { ConfigField } from "./ConfigField"; import { ConfigField } from "./ConfigField";
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
function renderSections<T extends Record<string, any>>(
sections: ConfigSectionDefinition<T>[],
config: T,
onChange: (key: string, value: any) => void,
tableColumns?: any[],
) {
return sections.map((section) => {
if (section.condition && !section.condition(config)) {
return null;
}
const visibleFields = section.fields.filter(
(field) => !field.condition || field.condition(config),
);
if (visibleFields.length === 0) {
return null;
}
return (
<ConfigSection key={section.id} section={section}>
{visibleFields.map((field) => (
<ConfigField
key={field.key}
field={field}
value={(config as any)[field.key]}
onChange={onChange}
tableColumns={tableColumns}
/>
))}
</ConfigSection>
);
});
}
export function ConfigPanelBuilder<T extends Record<string, any>>({ export function ConfigPanelBuilder<T extends Record<string, any>>({
config, config,
onChange, onChange,
onConfigChange,
sections, sections,
presets, presets,
tableColumns, tableColumns,
children, children,
mode = "flat",
context,
}: ConfigPanelBuilderProps<T>) { }: ConfigPanelBuilderProps<T>) {
return ( const effectiveTableColumns = tableColumns || context?.tableColumns;
<div className="space-y-3">
{/* 프리셋 버튼 */} const presetSection = presets && presets.length > 0 && (
{presets && presets.length > 0 && ( <div className="border-b border-border/40 pb-2.5">
<div className="border-b pb-3"> <h4 className="mb-1.5 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">
<h4 className="mb-2 text-xs font-medium text-muted-foreground">
</h4>
</h4> <div className="flex flex-wrap gap-1">
<div className="flex flex-wrap gap-1"> {presets.map((preset, idx) => (
{presets.map((preset, idx) => ( <button
<button key={idx}
key={idx} onClick={() => {
onClick={() => { Object.entries(preset.values).forEach(([key, value]) => {
Object.entries(preset.values).forEach(([key, value]) => { onChange(key, value);
onChange(key, value); });
}); if (onConfigChange) {
}} onConfigChange({ ...config, ...preset.values } as Record<string, any>);
className="rounded-full bg-muted px-2.5 py-1 text-[10px] font-medium text-muted-foreground transition-colors hover:bg-primary hover:text-primary-foreground" }
title={preset.description} }}
> className="rounded border border-border bg-background px-2 py-0.5 text-[10px] font-medium text-muted-foreground transition-colors hover:border-primary hover:text-primary"
{preset.label} title={preset.description}
</button> >
))} {preset.label}
</div> </button>
))}
</div>
</div>
);
if (mode === "tabs") {
const groupMap = new Map<string, ConfigSectionDefinition<T>[]>();
const ungrouped: ConfigSectionDefinition<T>[] = [];
for (const section of sections) {
if (section.group) {
const existing = groupMap.get(section.group) || [];
existing.push(section);
groupMap.set(section.group, existing);
} else {
ungrouped.push(section);
}
}
const tabGroups = Array.from(groupMap.entries());
if (tabGroups.length === 0) {
return (
<div className="space-y-1">
{presetSection}
{renderSections(sections, config, onChange, effectiveTableColumns)}
{children}
</div> </div>
)} );
}
{/* 섹션 렌더링 */} const defaultTab = tabGroups[0]?.[0] || "general";
{sections.map((section) => {
if (section.condition && !section.condition(config)) {
return null;
}
const visibleFields = section.fields.filter( return (
(field) => !field.condition || field.condition(config), <div className="space-y-1">
); {presetSection}
if (visibleFields.length === 0) { {ungrouped.length > 0 && renderSections(ungrouped, config, onChange, effectiveTableColumns)}
return null;
}
return ( <Tabs defaultValue={defaultTab} className="w-full">
<ConfigSection key={section.id} section={section}> <TabsList className="h-7 w-full">
{visibleFields.map((field) => ( {tabGroups.map(([groupName]) => (
<ConfigField <TabsTrigger key={groupName} value={groupName} className="h-6 text-xs">
key={field.key} {groupName}
field={field} </TabsTrigger>
value={(config as any)[field.key]}
onChange={onChange}
tableColumns={tableColumns}
/>
))} ))}
</ConfigSection> </TabsList>
); {tabGroups.map(([groupName, groupSections]) => (
})} <TabsContent key={groupName} value={groupName} className="mt-1">
{renderSections(groupSections, config, onChange, effectiveTableColumns)}
</TabsContent>
))}
</Tabs>
{/* 커스텀 children */} {children}
</div>
);
}
return (
<div className="space-y-1">
{presetSection}
{renderSections(sections, config, onChange, effectiveTableColumns)}
{children} {children}
</div> </div>
); );

View File

@ -48,6 +48,7 @@ export interface ConfigSectionDefinition<T = any> {
export interface ConfigPanelBuilderProps<T = any> { export interface ConfigPanelBuilderProps<T = any> {
config: T; config: T;
onChange: (key: string, value: any) => void; onChange: (key: string, value: any) => void;
onConfigChange?: (config: Record<string, any>) => void;
sections: ConfigSectionDefinition<T>[]; sections: ConfigSectionDefinition<T>[];
presets?: Array<{ presets?: Array<{
label: string; label: string;
@ -56,6 +57,8 @@ export interface ConfigPanelBuilderProps<T = any> {
}>; }>;
tableColumns?: ConfigOption[]; tableColumns?: ConfigOption[];
children?: React.ReactNode; children?: React.ReactNode;
mode?: "flat" | "tabs";
context?: ConfigPanelContext;
} }
/** /**