784 lines
28 KiB
TypeScript
784 lines
28 KiB
TypeScript
"use client";
|
||
|
||
import { useState } from "react";
|
||
import { Button } from "@/components/ui/button";
|
||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||
import { Label } from "@/components/ui/label";
|
||
import { Calendar } from "@/components/ui/calendar";
|
||
import {
|
||
Popover,
|
||
PopoverContent,
|
||
PopoverTrigger,
|
||
} from "@/components/ui/popover";
|
||
import {
|
||
Dialog,
|
||
DialogContent,
|
||
DialogHeader,
|
||
DialogTitle,
|
||
DialogFooter,
|
||
} from "@/components/ui/dialog";
|
||
import { Input } from "@/components/ui/input";
|
||
import { CalendarIcon, Plus, Trash2 } from "lucide-react";
|
||
import { format } from "date-fns";
|
||
import { id } from "date-fns/locale";
|
||
import { Progress } from "../ui/progress";
|
||
import DialogMediaOnline from "../dialog/media-online";
|
||
import DialogMediaSosial from "../dialog/media-sosial";
|
||
import MapVideotron from "../global/maps";
|
||
import dynamic from "next/dynamic";
|
||
|
||
export default function FormCampaign() {
|
||
const [startDate, setStartDate] = useState<Date | undefined>(undefined);
|
||
const [endDate, setEndDate] = useState<Date | undefined>(undefined);
|
||
const [goal, setGoal] = useState("Publikasi");
|
||
const [available, setAvailable] = useState("Yes");
|
||
const [isUploadOpen, setIsUploadOpen] = useState(false);
|
||
const [media, setMedia] = useState("Media Online");
|
||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||
|
||
const MapVideotron = dynamic(() => import("../global/maps"), {
|
||
ssr: false,
|
||
});
|
||
|
||
// contoh data pilihan media online (bisa diganti sesuai kebutuhan)
|
||
const mediaOnlineList = [
|
||
"Tribrata News Mabes",
|
||
"Tribrata News Polda Aceh",
|
||
"Tribrata News Polda Jawa Timur",
|
||
"Tribrata News Polda Jawa Tengah",
|
||
"Tribrata News Polda Jawa Barat",
|
||
];
|
||
|
||
const [selectedMediaOnline, setSelectedMediaOnline] = useState<string[]>([]);
|
||
const [contentType, setContentType] = useState("Meme");
|
||
const [talkshowType, setTalkshowType] = useState("Renjani Nyrah");
|
||
const [musicType, setMusicType] = useState("Sendrasena");
|
||
|
||
const toggleMediaOnline = (item: string) => {
|
||
setSelectedMediaOnline((prev) =>
|
||
prev.includes(item) ? prev.filter((m) => m !== item) : [...prev, item],
|
||
);
|
||
};
|
||
|
||
const [files, setFiles] = useState<
|
||
{ file: File; progress: number; uploaded: boolean }[]
|
||
>([]);
|
||
const [url, setUrl] = useState("");
|
||
|
||
// ✅ Upload dari file input
|
||
const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||
const selectedFiles = e.target.files;
|
||
if (!selectedFiles) return;
|
||
|
||
const newFiles = Array.from(selectedFiles).map((f) => ({
|
||
file: f,
|
||
progress: 0,
|
||
uploaded: false,
|
||
}));
|
||
|
||
setFiles((prev) => [...prev, ...newFiles]);
|
||
simulateUpload(newFiles);
|
||
};
|
||
|
||
// ✅ Simulasi upload progress
|
||
const simulateUpload = (
|
||
fileList: { file: File; progress: number; uploaded: boolean }[],
|
||
) => {
|
||
fileList.forEach((fileObj) => {
|
||
let progress = 0;
|
||
const interval = setInterval(() => {
|
||
progress += 10;
|
||
setFiles((prev) =>
|
||
prev.map((f) =>
|
||
f.file === fileObj.file
|
||
? { ...f, progress, uploaded: progress >= 100 }
|
||
: f,
|
||
),
|
||
);
|
||
if (progress >= 100) clearInterval(interval);
|
||
}, 300);
|
||
});
|
||
};
|
||
|
||
// ✅ Upload dari URL
|
||
const handleUploadFromUrl = () => {
|
||
if (!url.trim()) return;
|
||
const fakeFile = {
|
||
file: new File([], url.split("/").pop() || "file_from_url.jpg"),
|
||
progress: 100,
|
||
uploaded: true,
|
||
};
|
||
setFiles((prev) => [...prev, fakeFile]);
|
||
setUrl("");
|
||
};
|
||
|
||
// ✅ Hapus file
|
||
const removeFile = (index: number) => {
|
||
setFiles((prev) => prev.filter((_, i) => i !== index));
|
||
};
|
||
|
||
return (
|
||
<div className="bg-white rounded-2xl shadow-sm p-6 space-y-8">
|
||
{/* Langkah 1 */}
|
||
<section className="border-b pb-6">
|
||
<h2 className="font-semibold mb-4">Langkah 1</h2>
|
||
<p className="text-sm font-medium mb-2">Pilih Durasi</p>
|
||
<div className="flex flex-wrap gap-4">
|
||
<div>
|
||
<Label className="text-sm">Dari Tanggal</Label>
|
||
<Popover>
|
||
<PopoverTrigger asChild>
|
||
<Button
|
||
variant="outline"
|
||
className="w-[200px] justify-start text-left font-normal"
|
||
>
|
||
<CalendarIcon className="mr-2 h-4 w-4" />
|
||
{startDate
|
||
? format(startDate, "dd MMMM yyyy", { locale: id })
|
||
: "Pilih tanggal"}
|
||
</Button>
|
||
</PopoverTrigger>
|
||
<PopoverContent className="p-0" align="start">
|
||
<Calendar
|
||
mode="single"
|
||
selected={startDate}
|
||
onSelect={setStartDate}
|
||
locale={id}
|
||
/>
|
||
</PopoverContent>
|
||
</Popover>
|
||
</div>
|
||
<div>
|
||
<Label className="text-sm">Sampai Tanggal</Label>
|
||
<Popover>
|
||
<PopoverTrigger asChild>
|
||
<Button
|
||
variant="outline"
|
||
className="w-[200px] justify-start text-left font-normal"
|
||
>
|
||
<CalendarIcon className="mr-2 h-4 w-4" />
|
||
{endDate
|
||
? format(endDate, "dd MMMM yyyy", { locale: id })
|
||
: "Pilih tanggal"}
|
||
</Button>
|
||
</PopoverTrigger>
|
||
<PopoverContent className="p-0" align="start">
|
||
<Calendar
|
||
mode="single"
|
||
selected={endDate}
|
||
onSelect={setEndDate}
|
||
locale={id}
|
||
/>
|
||
</PopoverContent>
|
||
</Popover>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section className="border-b pb-6">
|
||
<h2 className="font-semibold mb-4">Langkah 2</h2>
|
||
<p className="text-sm font-medium mb-2">Pilih Media</p>
|
||
|
||
<RadioGroup
|
||
value={media}
|
||
onValueChange={setMedia}
|
||
className="flex flex-wrap gap-4"
|
||
>
|
||
{[
|
||
"Media Online",
|
||
"Media Sosial",
|
||
"Videotron",
|
||
"Radio Polri",
|
||
"TV Polri",
|
||
"Majalah Digital",
|
||
].map((m) => (
|
||
<div key={m} className="flex items-center space-x-2">
|
||
<RadioGroupItem value={m} id={m} />
|
||
<Label htmlFor={m}>{m}</Label>
|
||
</div>
|
||
))}
|
||
</RadioGroup>
|
||
|
||
{/* Tombol muncul sesuai media terpilih */}
|
||
{media === "Media Online" && (
|
||
<Button
|
||
variant="outline"
|
||
size="sm"
|
||
className="mt-4"
|
||
onClick={() => setIsDialogOpen(true)}
|
||
>
|
||
<Plus className="h-4 w-4 mr-2" />
|
||
Tambahkan Media Online
|
||
</Button>
|
||
)}
|
||
|
||
{media === "Media Sosial" && (
|
||
<Button
|
||
variant="outline"
|
||
size="sm"
|
||
className="mt-4"
|
||
onClick={() => setIsDialogOpen(true)}
|
||
>
|
||
<Plus className="h-4 w-4 mr-2" />
|
||
Tambahkan Media Sosial
|
||
</Button>
|
||
)}
|
||
{media === "Videotron" && (
|
||
<div className="mt-4 space-y-4">
|
||
{/* Map tampil */}
|
||
<MapVideotron />
|
||
</div>
|
||
)}
|
||
|
||
{/* 🧩 Komponen DialogMediaOnline dipanggil di sini */}
|
||
{media === "Media Online" && (
|
||
<DialogMediaOnline
|
||
isOpen={isDialogOpen}
|
||
onClose={() => setIsDialogOpen(false)}
|
||
/>
|
||
)}
|
||
{media === "Media Sosial" && (
|
||
<DialogMediaSosial
|
||
isOpen={isDialogOpen}
|
||
onClose={() => setIsDialogOpen(false)}
|
||
/>
|
||
)}
|
||
{media === "Videotron" && (
|
||
<DialogMediaSosial
|
||
isOpen={isDialogOpen}
|
||
onClose={() => setIsDialogOpen(false)}
|
||
/>
|
||
)}
|
||
{media === "Radio Polri" && (
|
||
<div className="mt-4 space-y-3">
|
||
{[
|
||
"Pagi pukul 06:00 – 07:00",
|
||
"Siang pukul 12:00 – 13:00",
|
||
"Sore pukul 16:00 – 17:00",
|
||
"Malam pukul 20:00 – 21:00",
|
||
].map((time) => (
|
||
<div key={time} className="flex items-center space-x-2">
|
||
<input
|
||
type="checkbox"
|
||
id={time}
|
||
value={time}
|
||
className="w-4 h-4 border border-purple-600 text-purple-600 focus:ring-2 focus:ring-purple-500 rounded"
|
||
/>
|
||
<Label htmlFor={time} className="text-sm">
|
||
{time}
|
||
</Label>
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
{media === "TV Polri" && (
|
||
<div className="mt-4 space-y-3">
|
||
{[
|
||
"Pagi pukul 06:00 – 07:00",
|
||
"Siang pukul 12:00 – 13:00",
|
||
"Sore pukul 16:00 – 17:00",
|
||
"Malam pukul 20:00 – 21:00",
|
||
].map((time) => (
|
||
<div key={time} className="flex items-center space-x-2">
|
||
<input
|
||
type="checkbox"
|
||
id={time}
|
||
value={time}
|
||
className="w-4 h-4 border border-purple-600 text-purple-600 focus:ring-2 focus:ring-purple-500 rounded"
|
||
/>
|
||
<Label htmlFor={time} className="text-sm">
|
||
{time}
|
||
</Label>
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
</section>
|
||
|
||
{/* Langkah 3 */}
|
||
<section className="border-b pb-6">
|
||
<h2 className="font-semibold mb-4">Langkah 3</h2>
|
||
<p className="text-sm font-medium mb-2">Tujuan</p>
|
||
<RadioGroup value={goal} onValueChange={setGoal} className="flex gap-4">
|
||
{["Publikasi", "Sosialisasi"].map((g) => (
|
||
<div key={g} className="flex items-center space-x-2">
|
||
<RadioGroupItem value={g} id={g} />
|
||
<Label htmlFor={g}>{g}</Label>
|
||
</div>
|
||
))}
|
||
</RadioGroup>
|
||
</section>
|
||
|
||
{/* Langkah 4 */}
|
||
<section>
|
||
<h2 className="font-semibold mb-4">Langkah 4</h2>
|
||
<p className="text-sm font-medium mb-2">Materi Promote Tersedia</p>
|
||
|
||
<RadioGroup
|
||
value={available}
|
||
onValueChange={setAvailable}
|
||
className="flex gap-4 mb-4"
|
||
>
|
||
{["Yes", "Tidak"].map((a) => (
|
||
<div key={a} className="flex items-center space-x-2">
|
||
<RadioGroupItem value={a} id={a} />
|
||
<Label htmlFor={a}>{a}</Label>
|
||
</div>
|
||
))}
|
||
</RadioGroup>
|
||
|
||
{available === "Yes" ? (
|
||
// ✅ Upload
|
||
<div className="space-y-2">
|
||
<Label className="text-sm font-medium">Upload File</Label>
|
||
<Button
|
||
variant="outline"
|
||
size="sm"
|
||
onClick={() => setIsUploadOpen(true)}
|
||
>
|
||
<Plus className="h-4 w-4 mr-2" />
|
||
Upload File
|
||
</Button>
|
||
</div>
|
||
) : (
|
||
// ❌ Tidak → tampil pilihan konten
|
||
<div className="space-y-4">
|
||
{/* Pilih Jenis Konten */}
|
||
<div>
|
||
<p className="text-sm font-medium mb-2">Pilih Jenis Konten</p>
|
||
|
||
<RadioGroup
|
||
value={contentType}
|
||
onValueChange={setContentType}
|
||
className="flex flex-wrap gap-4"
|
||
>
|
||
{["Meme", "AI Influencer", "Talkshow", "Musik"].map((item) => (
|
||
<div key={item} className="flex items-center space-x-2">
|
||
<RadioGroupItem value={item} id={item} />
|
||
<Label htmlFor={item}>{item}</Label>
|
||
</div>
|
||
))}
|
||
</RadioGroup>
|
||
</div>
|
||
|
||
{/* List Konten (Dummy) */}
|
||
{/* List Konten */}
|
||
<div>
|
||
<p className="text-sm font-medium mb-2">Pilih {contentType}</p>
|
||
|
||
{/* 🔥 TAMPILAN KHUSUS MEME */}
|
||
{contentType === "Meme" && (
|
||
<div className="space-y-4">
|
||
{/* Search */}
|
||
<div className="relative">
|
||
<input
|
||
type="text"
|
||
placeholder="Cari Meme..."
|
||
className="w-full pl-10 pr-4 py-2 border rounded-full text-sm"
|
||
/>
|
||
<span className="absolute left-3 top-2.5 text-gray-400 text-sm">
|
||
🔍
|
||
</span>
|
||
</div>
|
||
|
||
{/* Grid Meme */}
|
||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4">
|
||
{[
|
||
{
|
||
img: "/meme1.png",
|
||
title:
|
||
"Respons Cepat Lakukan Sterilisasi Ancaman Bom di Depok",
|
||
},
|
||
{
|
||
img: "/meme2.png",
|
||
title: "Upacara Penutupan Pendidikan Anggota Polri",
|
||
},
|
||
{
|
||
img: "/meme3.png",
|
||
title: "Pantau Situasi Kamtibmas Malam Natal",
|
||
},
|
||
{
|
||
img: "/meme4.png",
|
||
title: "Program SPPG Bersama Penerima Manfaat",
|
||
},
|
||
].map((item, i) => (
|
||
<div
|
||
key={i}
|
||
className="bg-white border rounded-xl overflow-hidden shadow-sm hover:shadow-md"
|
||
>
|
||
<div className="relative">
|
||
<img
|
||
src={item.img}
|
||
className="w-full h-40 object-cover"
|
||
/>
|
||
|
||
<input
|
||
type="checkbox"
|
||
className="absolute top-2 left-2 w-4 h-4"
|
||
/>
|
||
|
||
<div className="absolute bottom-2 right-2 text-white text-xs bg-black/50 px-1 rounded">
|
||
⛶
|
||
</div>
|
||
</div>
|
||
|
||
<div className="p-2 text-xs line-clamp-2">
|
||
{item.title}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{contentType === "AI Influencer" && (
|
||
<div className="space-y-4">
|
||
{/* Search */}
|
||
<div className="relative">
|
||
<input
|
||
type="text"
|
||
placeholder="Cari AI Influencer..."
|
||
className="w-full pl-10 pr-4 py-2 border rounded-full text-sm"
|
||
/>
|
||
<span className="absolute left-3 top-2.5 text-gray-400 text-sm">
|
||
🔍
|
||
</span>
|
||
</div>
|
||
|
||
{/* Grid AI */}
|
||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4">
|
||
{[
|
||
{ img: "/ai1.png", name: "Revandra Jaya" },
|
||
{ img: "/ai2.png", name: "Fudhalina Adisty" },
|
||
{ img: "/ai3.png", name: "Adriyan Pratama" },
|
||
{ img: "/ai4.png", name: "Nadhya Wijaya" },
|
||
].map((item, i) => (
|
||
<div
|
||
key={i}
|
||
className="relative rounded-xl overflow-hidden shadow-sm hover:shadow-md"
|
||
>
|
||
{/* Image */}
|
||
<img
|
||
src={item.img}
|
||
className="w-full h-48 object-cover"
|
||
/>
|
||
|
||
{/* Overlay bawah */}
|
||
<div className="absolute bottom-0 left-0 right-0 bg-white/80 backdrop-blur px-3 py-2 flex items-center gap-2">
|
||
<input type="checkbox" className="w-4 h-4" />
|
||
<span className="text-xs font-medium">
|
||
{item.name}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{/* Footer */}
|
||
<div className="flex justify-end items-center text-xs text-gray-500 gap-4">
|
||
<span>Rows per page: 1</span>
|
||
<span>1-1 of 1</span>
|
||
<div className="flex gap-2">
|
||
<button>{"<"}</button>
|
||
<button>{">"}</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
{contentType === "Talkshow" && (
|
||
<div className="space-y-4">
|
||
{/* Select Talkshow */}
|
||
<div>
|
||
<p className="text-sm font-medium mb-2">Pilih Talkshow</p>
|
||
|
||
<select
|
||
value={talkshowType}
|
||
onChange={(e) => setTalkshowType(e.target.value)}
|
||
className="border rounded-md px-3 py-2 text-sm"
|
||
>
|
||
<option value="Renjani Nyrah">Renjani Nyrah</option>
|
||
<option value="Agatha Bicara">Agatha Bicara</option>
|
||
</select>
|
||
</div>
|
||
|
||
{/* Konten berdasarkan pilihan */}
|
||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
|
||
{(talkshowType === "Renjani Nyrah"
|
||
? [
|
||
{
|
||
img: "/ts1.jpg",
|
||
title: "Renjani Nyrah - Napak Tilas di Museum",
|
||
},
|
||
{
|
||
img: "/ts2.jpg",
|
||
title: "Renjani Nyrah - Kata Kata Baru",
|
||
},
|
||
{
|
||
img: "/ts3.jpg",
|
||
title: "Renjani Nyrah - Pestapora 2025",
|
||
},
|
||
]
|
||
: [
|
||
{
|
||
img: "/agatha1.jpg",
|
||
title: "Agatha Bicara - Kerja DPR Itu Apa?",
|
||
},
|
||
{
|
||
img: "/agatha1.jpg",
|
||
title: "Agatha Bicara - Kerja DPR Itu Apa?",
|
||
},
|
||
{
|
||
img: "/agatha2.png",
|
||
title: "Agatha Bicara - Penghargaan dari Presiden",
|
||
},
|
||
]
|
||
).map((item, i) => (
|
||
<div
|
||
key={i}
|
||
className="bg-white border rounded-xl overflow-hidden shadow-sm hover:shadow-md"
|
||
>
|
||
{/* Image */}
|
||
<div className="relative">
|
||
<img
|
||
src={item.img}
|
||
className="w-full h-40 object-cover"
|
||
/>
|
||
|
||
{/* Checkbox */}
|
||
<input
|
||
type="checkbox"
|
||
className="absolute top-2 left-2 w-4 h-4"
|
||
/>
|
||
|
||
{/* Expand icon */}
|
||
<div className="absolute bottom-2 right-2 text-white text-xs bg-black/50 px-1 rounded">
|
||
⛶
|
||
</div>
|
||
</div>
|
||
|
||
{/* Title */}
|
||
<div className="p-2 text-xs line-clamp-2">
|
||
{item.title}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
{contentType === "Musik" && (
|
||
<div className="space-y-4">
|
||
{/* Dropdown Musik */}
|
||
<div>
|
||
<p className="text-sm font-medium mb-2">Pilih Musik</p>
|
||
|
||
<select
|
||
value={musicType}
|
||
onChange={(e) => setMusicType(e.target.value)}
|
||
className="border rounded-md px-3 py-2 text-sm"
|
||
>
|
||
<option value="Sendrasena">Sendrasena</option>
|
||
<option value="Selara Luna">Selara Luna</option>
|
||
</select>
|
||
</div>
|
||
|
||
{/* Grid Musik */}
|
||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4">
|
||
{(musicType === "Sendrasena"
|
||
? [
|
||
{
|
||
img: "/m1.jpg",
|
||
title: "Sendrasena - Track 1",
|
||
},
|
||
{
|
||
img: "/m2.jpg",
|
||
title: "Sendrasena - Track 2",
|
||
},
|
||
{
|
||
img: "/m3.jpg",
|
||
title: "Sendrasena - Track 3",
|
||
},
|
||
{
|
||
img: "/m4.jpg",
|
||
title: "Sendrasena - Track 4",
|
||
},
|
||
]
|
||
: [
|
||
{
|
||
img: "/sl1.jpg",
|
||
title: "Selara Luna - Track 1",
|
||
},
|
||
{
|
||
img: "/sl2.jpg",
|
||
title: "Selara Luna - Track 2",
|
||
},
|
||
{
|
||
img: "/sl3.jpg",
|
||
title: "Selara Luna - Track 3",
|
||
},
|
||
{
|
||
img: "/sl4.jpg",
|
||
title: "Selara Luna - Track 4",
|
||
},
|
||
]
|
||
).map((item, i) => (
|
||
<div
|
||
key={i}
|
||
className="bg-white border rounded-xl overflow-hidden shadow-sm hover:shadow-md"
|
||
>
|
||
{/* Cover */}
|
||
<div className="relative">
|
||
<img
|
||
src={item.img}
|
||
className="w-full h-40 object-cover"
|
||
/>
|
||
|
||
{/* Checkbox */}
|
||
<input
|
||
type="checkbox"
|
||
className="absolute top-2 left-2 w-4 h-4"
|
||
/>
|
||
|
||
{/* Play icon (biar beda dari talkshow 😄) */}
|
||
<div className="absolute bottom-2 right-2 text-white text-xs bg-black/50 px-2 py-1 rounded">
|
||
▶
|
||
</div>
|
||
</div>
|
||
|
||
{/* Title */}
|
||
<div className="p-2 text-xs line-clamp-2">
|
||
{item.title}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
)}
|
||
</section>
|
||
|
||
<div className="pt-6">
|
||
<Button className="w-[120px]" size="lg">
|
||
Submit
|
||
</Button>
|
||
</div>
|
||
|
||
{/* Modal Upload */}
|
||
<Dialog open={isUploadOpen} onOpenChange={setIsUploadOpen}>
|
||
<DialogContent className="max-w-lg w-[90vw] sm:w-full">
|
||
<DialogHeader>
|
||
<DialogTitle>Unggah Berkas</DialogTitle>
|
||
<p className="text-sm text-muted-foreground">
|
||
Pilih berkas dan unggah dengan aman untuk melanjutkan.
|
||
</p>
|
||
</DialogHeader>
|
||
|
||
{/* === Upload Section === */}
|
||
<div className="border-2 border-dashed rounded-lg p-6 flex flex-col items-center justify-center text-center space-y-2 w-full">
|
||
<p className="text-sm text-muted-foreground">
|
||
Seret dan jatuhkan berkas Anda
|
||
</p>
|
||
<p className="text-xs text-muted-foreground">
|
||
Format .PNG, .JPG, dan .JPEG hingga 50MB
|
||
</p>
|
||
<label htmlFor="fileInput">
|
||
<Button
|
||
variant="outline"
|
||
size="sm"
|
||
className="mt-2 cursor-pointer"
|
||
>
|
||
Pilih Berkas
|
||
</Button>
|
||
<Input
|
||
id="fileInput"
|
||
type="file"
|
||
multiple
|
||
className="hidden"
|
||
onChange={handleFileUpload}
|
||
/>
|
||
</label>
|
||
</div>
|
||
|
||
{/* === Upload via URL === */}
|
||
<div className="flex flex-col sm:flex-row items-center gap-2 mt-4 w-full">
|
||
<Input
|
||
type="url"
|
||
placeholder="Tambahkan URL berkas"
|
||
value={url}
|
||
onChange={(e) => setUrl(e.target.value)}
|
||
className="flex-1"
|
||
/>
|
||
<Button onClick={handleUploadFromUrl} className="w-full sm:w-auto">
|
||
Unggah
|
||
</Button>
|
||
</div>
|
||
|
||
{/* === Uploaded Files === */}
|
||
{files.length > 0 && (
|
||
<div className="mt-5 space-y-3 max-h-[60vh] overflow-y-auto">
|
||
<h4 className="text-sm font-semibold">Uploaded Files</h4>
|
||
<div className="space-y-2">
|
||
{files.map((f, i) => (
|
||
<div
|
||
key={i}
|
||
className="border rounded-lg p-3 flex flex-wrap sm:flex-nowrap justify-between items-start sm:items-center gap-3"
|
||
>
|
||
<div className="flex flex-col min-w-0 flex-1">
|
||
<p className="text-sm font-medium truncate max-w-[250px]">
|
||
{f.file.name}
|
||
</p>
|
||
<p className="text-xs text-muted-foreground">
|
||
{(f.file.size / (1024 * 1024)).toFixed(1)}MB •{" "}
|
||
{f.uploaded ? (
|
||
<span className="text-green-600 font-medium">
|
||
Uploaded Successfully
|
||
</span>
|
||
) : (
|
||
<span className="text-blue-600 font-medium">
|
||
{f.progress}% • Uploading...
|
||
</span>
|
||
)}
|
||
</p>
|
||
{!f.uploaded && (
|
||
<Progress
|
||
value={f.progress}
|
||
className="h-1 mt-1 w-full bg-gray-200"
|
||
/>
|
||
)}
|
||
</div>
|
||
<Button
|
||
variant="ghost"
|
||
size="icon"
|
||
onClick={() => removeFile(i)}
|
||
className="shrink-0"
|
||
>
|
||
<Trash2 className="h-4 w-4 text-muted-foreground" />
|
||
</Button>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
<DialogFooter className="mt-4 flex flex-col sm:flex-row justify-end gap-2 sm:gap-4">
|
||
<Button
|
||
variant="outline"
|
||
onClick={() => setIsUploadOpen(false)}
|
||
className="w-full sm:w-auto"
|
||
>
|
||
Batal
|
||
</Button>
|
||
<Button
|
||
onClick={() => setIsUploadOpen(false)}
|
||
className="w-full sm:w-auto"
|
||
>
|
||
Lampirkan Berkas
|
||
</Button>
|
||
</DialogFooter>
|
||
</DialogContent>
|
||
</Dialog>
|
||
</div>
|
||
);
|
||
}
|