[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
* .
* UX: 미디어 -> -> -> ()
*/
import React from "react";
import { Label } from "@/components/ui/label";
import React, { useState } from "react";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Checkbox } from "@/components/ui/checkbox";
import { Switch } from "@/components/ui/switch";
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 {
config: Record<string, any>;
@ -20,133 +60,150 @@ export const V2MediaConfigPanel: React.FC<V2MediaConfigPanelProps> = ({
config,
onChange,
}) => {
const [advancedOpen, setAdvancedOpen] = useState(false);
const updateConfig = (field: string, value: any) => {
onChange({ ...config, [field]: value });
};
const currentMediaType = config.mediaType || config.type || "image";
const isImageType = currentMediaType === "image";
const isPlayerType = currentMediaType === "video" || currentMediaType === "audio";
return (
<div className="space-y-1">
{/* MEDIA TYPE 섹션 */}
<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">MEDIA TYPE</h4>
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"> </span>
<div className="w-[140px]">
<Select
value={config.mediaType || config.type || "image"}
onValueChange={(value) => updateConfig("mediaType", value)}
>
<SelectTrigger className="h-7 text-xs">
<SelectValue placeholder="타입 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="file"></SelectItem>
<SelectItem value="image"></SelectItem>
<SelectItem value="video"></SelectItem>
<SelectItem value="audio"></SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-4">
{/* ─── 1단계: 미디어 타입 선택 (카드) ─── */}
<div className="space-y-2">
<p className="text-sm font-medium"> ?</p>
<div className="grid grid-cols-2 gap-2">
{MEDIA_TYPE_CARDS.map((card) => {
const Icon = card.icon;
const isSelected = currentMediaType === card.value;
return (
<button
key={card.value}
type="button"
onClick={() => updateConfig("mediaType", card.value)}
className={cn(
"flex flex-col items-center justify-center rounded-lg border p-3 text-center transition-all min-h-[80px]",
isSelected
? "border-primary bg-primary/5 ring-1 ring-primary/20"
: "border-border hover:border-primary/50 hover:bg-muted/50",
)}
>
<Icon className="h-5 w-5 mb-1.5 text-primary" />
<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>
{/* FILE SETTINGS 섹션 */}
<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">FILE SETTINGS</h4>
{/* ─── 2단계: 기본 설정 ─── */}
<div className="rounded-lg border bg-muted/30 p-4 space-y-3">
<span className="text-sm font-medium"> </span>
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"> </span>
<div className="w-[140px]">
<Input
value={config.accept || ""}
onChange={(e) => updateConfig("accept", e.target.value)}
placeholder=".jpg,.png,.pdf"
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>
<span className="text-xs text-muted-foreground"> </span>
<Input
value={config.accept || ""}
onChange={(e) => updateConfig("accept", e.target.value)}
placeholder=".jpg,.png,.pdf"
className="h-8 w-[180px] text-sm"
/>
</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>
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"> </span>
<Checkbox
{/* ─── 3단계: 업로드 옵션 (Switch + 설명) ─── */}
<div className="space-y-2">
<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.multiple || false}
onCheckedChange={(checked) => updateConfig("multiple", checked)}
/>
</div>
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"> </span>
<Checkbox
<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.preview !== false}
onCheckedChange={(checked) => updateConfig("preview", checked)}
/>
</div>
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"> </span>
<Checkbox
<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.dragDrop !== false}
onCheckedChange={(checked) => updateConfig("dragDrop", checked)}
/>
</div>
</div>
{/* IMAGE SETTINGS 섹션 - 이미지 타입 전용 */}
{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>
<div className="flex gap-2">
<div className="flex-1">
<Label className="text-[10px] text-muted-foreground"> (px)</Label>
<Input
type="number"
value={config.maxWidth || ""}
onChange={(e) => updateConfig("maxWidth", e.target.value ? Number(e.target.value) : undefined)}
placeholder="자동"
className="h-7 text-xs"
/>
{/* ─── 4단계: 타입별 설정 ─── */}
{/* 이미지 타입: 크기 제한 + 자르기 */}
{isImageType && (
<div className="space-y-3">
<div className="rounded-lg border bg-muted/30 p-4 space-y-3">
<div className="flex items-center gap-2">
<Image className="h-4 w-4 text-primary" />
<span className="text-sm font-medium"> </span>
</div>
<div className="flex-1">
<Label className="text-[10px] text-muted-foreground"> (px)</Label>
<Input
type="number"
value={config.maxHeight || ""}
onChange={(e) => updateConfig("maxHeight", e.target.value ? Number(e.target.value) : undefined)}
placeholder="자동"
className="h-7 text-xs"
/>
<div className="flex gap-2">
<div className="flex-1">
<span className="text-xs text-muted-foreground"> (px)</span>
<Input
type="number"
value={config.maxWidth || ""}
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 className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"> </span>
<Checkbox
<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.crop || false}
onCheckedChange={(checked) => updateConfig("crop", checked)}
/>
@ -154,33 +211,108 @@ export const V2MediaConfigPanel: React.FC<V2MediaConfigPanelProps> = ({
</div>
)}
{/* PLAYER SETTINGS 섹션 - 비디오/오디오 전용 */}
{(config.mediaType === "video" || config.mediaType === "audio") && (
<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">PLAYER SETTINGS</h4>
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"> </span>
<Checkbox
{/* 비디오/오디오 타입: 플레이어 설정 */}
{isPlayerType && (
<div className="space-y-2">
<div className="rounded-lg border bg-muted/30 p-4">
<div className="flex items-center gap-2">
{currentMediaType === "video" ? (
<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}
onCheckedChange={(checked) => updateConfig("autoplay", checked)}
/>
</div>
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"> </span>
<Checkbox
<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.controls !== false}
onCheckedChange={(checked) => updateConfig("controls", checked)}
/>
</div>
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"> </span>
<Checkbox
<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.loop || false}
onCheckedChange={(checked) => updateConfig("loop", checked)}
/>
</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>
);
};