477 lines
16 KiB
TypeScript
477 lines
16 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";
|
|||
|
|
|
|||
|
|
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);
|
|||
|
|
|
|||
|
|
// 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 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" && (
|
|||
|
|
<Button
|
|||
|
|
variant="outline"
|
|||
|
|
size="sm"
|
|||
|
|
className="mt-4"
|
|||
|
|
onClick={() => setIsDialogOpen(true)}
|
|||
|
|
>
|
|||
|
|
<Plus className="h-4 w-4 mr-2" />
|
|||
|
|
Tambahkan Videotron
|
|||
|
|
</Button>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{/* 🧩 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" ? (
|
|||
|
|
// ✅ Jika user pilih "Yes" → tampil upload file
|
|||
|
|
<div className="space-y-2">
|
|||
|
|
<Label className="text-sm font-medium">Upload File</Label>
|
|||
|
|
<div className="flex items-center gap-2">
|
|||
|
|
<Button
|
|||
|
|
variant="outline"
|
|||
|
|
size="sm"
|
|||
|
|
onClick={() => setIsUploadOpen(true)}
|
|||
|
|
>
|
|||
|
|
<Plus className="h-4 w-4 mr-2" />
|
|||
|
|
Upload File
|
|||
|
|
</Button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
) : (
|
|||
|
|
// ✅ Jika user pilih "Tidak" → tampil textarea deskripsi
|
|||
|
|
<div className="space-y-2">
|
|||
|
|
<Label className="text-sm font-medium">Deskripsi Promote</Label>
|
|||
|
|
<textarea
|
|||
|
|
placeholder="Tulis deskripsi promote..."
|
|||
|
|
className="w-full min-h-[100px] p-3 border rounded-md text-sm resize-none focus:outline-none focus:ring-2 focus:ring-primary"
|
|||
|
|
/>
|
|||
|
|
</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>
|
|||
|
|
);
|
|||
|
|
}
|