[agent-pipeline] pipe-20260311122226-4dkx round-4

This commit is contained in:
DDD1542 2026-03-11 21:40:33 +09:00
parent a36bcceef3
commit eaa893a01a
1 changed files with 248 additions and 116 deletions

View File

@ -2,14 +2,54 @@
/** /**
* V2Media * V2Media
* . * UX: 미디어 -> -> -> ()
*/ */
import React from "react"; import React, { useState } from "react";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch";
import { Checkbox } from "@/components/ui/checkbox"; import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import {
FileText,
Image,
Video,
Music,
Settings,
ChevronDown,
} from "lucide-react";
import { cn } from "@/lib/utils";
// ─── 미디어 타입 카드 정의 ───
const MEDIA_TYPE_CARDS = [
{
value: "file",
icon: FileText,
title: "파일",
description: "일반 파일을 업로드해요",
},
{
value: "image",
icon: Image,
title: "이미지",
description: "사진이나 그림을 올려요",
},
{
value: "video",
icon: Video,
title: "비디오",
description: "동영상을 업로드해요",
},
{
value: "audio",
icon: Music,
title: "오디오",
description: "음악이나 녹음을 올려요",
},
] as const;
interface V2MediaConfigPanelProps { interface V2MediaConfigPanelProps {
config: Record<string, any>; config: Record<string, any>;
@ -20,133 +60,150 @@ export const V2MediaConfigPanel: React.FC<V2MediaConfigPanelProps> = ({
config, config,
onChange, onChange,
}) => { }) => {
const [advancedOpen, setAdvancedOpen] = useState(false);
const updateConfig = (field: string, value: any) => { const updateConfig = (field: string, value: any) => {
onChange({ ...config, [field]: value }); onChange({ ...config, [field]: value });
}; };
const currentMediaType = config.mediaType || config.type || "image";
const isImageType = currentMediaType === "image";
const isPlayerType = currentMediaType === "video" || currentMediaType === "audio";
return ( return (
<div className="space-y-1"> <div className="space-y-4">
{/* MEDIA TYPE 섹션 */} {/* ─── 1단계: 미디어 타입 선택 (카드) ─── */}
<div className="border-b border-border/50 pb-3 mb-3"> <div className="space-y-2">
<h4 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground py-2">MEDIA TYPE</h4> <p className="text-sm font-medium"> ?</p>
<div className="flex items-center justify-between py-1.5"> <div className="grid grid-cols-2 gap-2">
<span className="text-xs text-muted-foreground"> </span> {MEDIA_TYPE_CARDS.map((card) => {
<div className="w-[140px]"> const Icon = card.icon;
<Select const isSelected = currentMediaType === card.value;
value={config.mediaType || config.type || "image"} return (
onValueChange={(value) => updateConfig("mediaType", value)} <button
> key={card.value}
<SelectTrigger className="h-7 text-xs"> type="button"
<SelectValue placeholder="타입 선택" /> onClick={() => updateConfig("mediaType", card.value)}
</SelectTrigger> className={cn(
<SelectContent> "flex flex-col items-center justify-center rounded-lg border p-3 text-center transition-all min-h-[80px]",
<SelectItem value="file"></SelectItem> isSelected
<SelectItem value="image"></SelectItem> ? "border-primary bg-primary/5 ring-1 ring-primary/20"
<SelectItem value="video"></SelectItem> : "border-border hover:border-primary/50 hover:bg-muted/50",
<SelectItem value="audio"></SelectItem> )}
</SelectContent> >
</Select> <Icon className="h-5 w-5 mb-1.5 text-primary" />
</div> <span className="text-xs font-medium leading-tight">
{card.title}
</span>
<span className="text-[10px] text-muted-foreground leading-tight mt-0.5">
{card.description}
</span>
</button>
);
})}
</div> </div>
</div> </div>
{/* FILE SETTINGS 섹션 */} {/* ─── 2단계: 기본 설정 ─── */}
<div className="border-b border-border/50 pb-3 mb-3"> <div className="rounded-lg border bg-muted/30 p-4 space-y-3">
<h4 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground py-2">FILE SETTINGS</h4> <span className="text-sm font-medium"> </span>
<div className="flex items-center justify-between py-1.5"> <div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"> </span> <span className="text-xs text-muted-foreground"> </span>
<div className="w-[140px]"> <Input
<Input value={config.accept || ""}
value={config.accept || ""} onChange={(e) => updateConfig("accept", e.target.value)}
onChange={(e) => updateConfig("accept", e.target.value)} placeholder=".jpg,.png,.pdf"
placeholder=".jpg,.png,.pdf" className="h-8 w-[180px] text-sm"
className="h-7 text-xs" />
/>
</div>
</div>
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"> (MB)</span>
<div className="w-[140px]">
<Input
type="number"
value={config.maxSize || ""}
onChange={(e) => updateConfig("maxSize", e.target.value ? Number(e.target.value) : undefined)}
placeholder="10"
min="1"
className="h-7 text-xs"
/>
</div>
</div>
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"> </span>
<div className="w-[140px]">
<Input
type="number"
value={config.maxFiles || ""}
onChange={(e) => updateConfig("maxFiles", e.target.value ? Number(e.target.value) : undefined)}
placeholder="제한 없음"
min="1"
className="h-7 text-xs"
/>
</div>
</div> </div>
</div> </div>
{/* OPTIONS 섹션 */} {/* ─── 3단계: 업로드 옵션 (Switch + 설명) ─── */}
<div className="border-b border-border/50 pb-3 mb-3"> <div className="space-y-2">
<h4 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground py-2">OPTIONS</h4> <div className="flex items-center justify-between py-1">
<div className="flex items-center justify-between py-1.5"> <div>
<span className="text-xs text-muted-foreground"> </span> <p className="text-sm"> </p>
<Checkbox <p className="text-[11px] text-muted-foreground">
</p>
</div>
<Switch
checked={config.multiple || false} checked={config.multiple || false}
onCheckedChange={(checked) => updateConfig("multiple", checked)} onCheckedChange={(checked) => updateConfig("multiple", checked)}
/> />
</div> </div>
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"> </span> <div className="flex items-center justify-between py-1">
<Checkbox <div>
<p className="text-sm"></p>
<p className="text-[11px] text-muted-foreground">
</p>
</div>
<Switch
checked={config.preview !== false} checked={config.preview !== false}
onCheckedChange={(checked) => updateConfig("preview", checked)} onCheckedChange={(checked) => updateConfig("preview", checked)}
/> />
</div> </div>
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"> </span> <div className="flex items-center justify-between py-1">
<Checkbox <div>
<p className="text-sm"> </p>
<p className="text-[11px] text-muted-foreground">
</p>
</div>
<Switch
checked={config.dragDrop !== false} checked={config.dragDrop !== false}
onCheckedChange={(checked) => updateConfig("dragDrop", checked)} onCheckedChange={(checked) => updateConfig("dragDrop", checked)}
/> />
</div> </div>
</div> </div>
{/* IMAGE SETTINGS 섹션 - 이미지 타입 전용 */} {/* ─── 4단계: 타입별 설정 ─── */}
{config.mediaType === "image" && (
<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">IMAGE SETTINGS</h4> {isImageType && (
<div className="flex gap-2"> <div className="space-y-3">
<div className="flex-1"> <div className="rounded-lg border bg-muted/30 p-4 space-y-3">
<Label className="text-[10px] text-muted-foreground"> (px)</Label> <div className="flex items-center gap-2">
<Input <Image className="h-4 w-4 text-primary" />
type="number" <span className="text-sm font-medium"> </span>
value={config.maxWidth || ""}
onChange={(e) => updateConfig("maxWidth", e.target.value ? Number(e.target.value) : undefined)}
placeholder="자동"
className="h-7 text-xs"
/>
</div> </div>
<div className="flex-1">
<Label className="text-[10px] text-muted-foreground"> (px)</Label> <div className="flex gap-2">
<Input <div className="flex-1">
type="number" <span className="text-xs text-muted-foreground"> (px)</span>
value={config.maxHeight || ""} <Input
onChange={(e) => updateConfig("maxHeight", e.target.value ? Number(e.target.value) : undefined)} type="number"
placeholder="자동" value={config.maxWidth || ""}
className="h-7 text-xs" onChange={(e) => updateConfig("maxWidth", e.target.value ? Number(e.target.value) : undefined)}
/> placeholder="자동"
className="mt-1 h-8 text-sm"
/>
</div>
<div className="flex-1">
<span className="text-xs text-muted-foreground"> (px)</span>
<Input
type="number"
value={config.maxHeight || ""}
onChange={(e) => updateConfig("maxHeight", e.target.value ? Number(e.target.value) : undefined)}
placeholder="자동"
className="mt-1 h-8 text-sm"
/>
</div>
</div> </div>
</div> </div>
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"> </span> <div className="flex items-center justify-between py-1">
<Checkbox <div>
<p className="text-sm"> </p>
<p className="text-[11px] text-muted-foreground">
</p>
</div>
<Switch
checked={config.crop || false} checked={config.crop || false}
onCheckedChange={(checked) => updateConfig("crop", checked)} onCheckedChange={(checked) => updateConfig("crop", checked)}
/> />
@ -154,33 +211,108 @@ export const V2MediaConfigPanel: React.FC<V2MediaConfigPanelProps> = ({
</div> </div>
)} )}
{/* PLAYER SETTINGS 섹션 - 비디오/오디오 전용 */} {/* 비디오/오디오 타입: 플레이어 설정 */}
{(config.mediaType === "video" || config.mediaType === "audio") && ( {isPlayerType && (
<div className="border-b border-border/50 pb-3 mb-3"> <div className="space-y-2">
<h4 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground py-2">PLAYER SETTINGS</h4> <div className="rounded-lg border bg-muted/30 p-4">
<div className="flex items-center justify-between py-1.5"> <div className="flex items-center gap-2">
<span className="text-xs text-muted-foreground"> </span> {currentMediaType === "video" ? (
<Checkbox <Video className="h-4 w-4 text-primary" />
) : (
<Music className="h-4 w-4 text-primary" />
)}
<span className="text-sm font-medium"> </span>
</div>
</div>
<div className="flex items-center justify-between py-1">
<div>
<p className="text-sm"> </p>
<p className="text-[11px] text-muted-foreground">
</p>
</div>
<Switch
checked={config.autoplay || false} checked={config.autoplay || false}
onCheckedChange={(checked) => updateConfig("autoplay", checked)} onCheckedChange={(checked) => updateConfig("autoplay", checked)}
/> />
</div> </div>
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"> </span> <div className="flex items-center justify-between py-1">
<Checkbox <div>
<p className="text-sm"> </p>
<p className="text-[11px] text-muted-foreground">
, ,
</p>
</div>
<Switch
checked={config.controls !== false} checked={config.controls !== false}
onCheckedChange={(checked) => updateConfig("controls", checked)} onCheckedChange={(checked) => updateConfig("controls", checked)}
/> />
</div> </div>
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"> </span> <div className="flex items-center justify-between py-1">
<Checkbox <div>
<p className="text-sm"> </p>
<p className="text-[11px] text-muted-foreground">
</p>
</div>
<Switch
checked={config.loop || false} checked={config.loop || false}
onCheckedChange={(checked) => updateConfig("loop", checked)} onCheckedChange={(checked) => updateConfig("loop", checked)}
/> />
</div> </div>
</div> </div>
)} )}
{/* ─── 5단계: 고급 설정 (기본 접혀있음) ─── */}
<Collapsible open={advancedOpen} onOpenChange={setAdvancedOpen}>
<CollapsibleTrigger asChild>
<button
type="button"
className="flex w-full items-center justify-between rounded-lg border bg-muted/30 px-4 py-2.5 text-left transition-colors hover:bg-muted/50"
>
<div className="flex items-center gap-2">
<Settings className="h-4 w-4 text-muted-foreground" />
<span className="text-sm font-medium"> </span>
</div>
<ChevronDown
className={cn(
"h-4 w-4 text-muted-foreground transition-transform duration-200",
advancedOpen && "rotate-180",
)}
/>
</button>
</CollapsibleTrigger>
<CollapsibleContent>
<div className="rounded-b-lg border border-t-0 p-4 space-y-3">
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"> (MB)</span>
<Input
type="number"
value={config.maxSize || ""}
onChange={(e) => updateConfig("maxSize", e.target.value ? Number(e.target.value) : undefined)}
placeholder="10"
min="1"
className="h-8 w-[180px] text-sm"
/>
</div>
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"> </span>
<Input
type="number"
value={config.maxFiles || ""}
onChange={(e) => updateConfig("maxFiles", e.target.value ? Number(e.target.value) : undefined)}
placeholder="제한 없음"
min="1"
className="h-8 w-[180px] text-sm"
/>
</div>
</div>
</CollapsibleContent>
</Collapsible>
</div> </div>
); );
}; };