update form promote user,add tabel executive
continuous-integration/drone/push Build is passing Details

This commit is contained in:
Anang Yusman 2026-04-14 15:51:47 +08:00
parent a7234c8b46
commit c46fdf1c5d
29 changed files with 539 additions and 39 deletions

View File

@ -0,0 +1,13 @@
import DashboardNavbar from "@/components/dashboard/dashboard-navbar";
import CoordinatorTable from "@/components/table/coordinator-table";
export default function CoordinatorPage() {
return (
<div className="min-h-screen bg-[#f8f9fa]">
<DashboardNavbar />
<div className="p-6">
<CoordinatorTable />
</div>
</div>
);
}

View File

@ -14,6 +14,7 @@ interface DialogCampaignDetailProps {
isOpen: boolean; isOpen: boolean;
onClose: () => void; onClose: () => void;
data?: { data?: {
deskripsi: string;
durasi: string; durasi: string;
media: string; media: string;
tujuan: string; tujuan: string;
@ -46,6 +47,11 @@ export default function DialogCampaignDetail({
{/* Detail Info */} {/* Detail Info */}
<div className="space-y-2 text-sm"> <div className="space-y-2 text-sm">
<p>
<span className="text-gray-500">Deskripsi</span>
<br />
<span className="font-semibold">{data.deskripsi}</span>
</p>
<p> <p>
<span className="text-gray-500">Durasi</span> <span className="text-gray-500">Durasi</span>
<br /> <br />

View File

@ -24,6 +24,8 @@ import { id } from "date-fns/locale";
import { Progress } from "../ui/progress"; import { Progress } from "../ui/progress";
import DialogMediaOnline from "../dialog/media-online"; import DialogMediaOnline from "../dialog/media-online";
import DialogMediaSosial from "../dialog/media-sosial"; import DialogMediaSosial from "../dialog/media-sosial";
import MapVideotron from "../global/maps";
import dynamic from "next/dynamic";
export default function FormCampaign() { export default function FormCampaign() {
const [startDate, setStartDate] = useState<Date | undefined>(undefined); const [startDate, setStartDate] = useState<Date | undefined>(undefined);
@ -34,6 +36,10 @@ export default function FormCampaign() {
const [media, setMedia] = useState("Media Online"); const [media, setMedia] = useState("Media Online");
const [isDialogOpen, setIsDialogOpen] = useState(false); const [isDialogOpen, setIsDialogOpen] = useState(false);
const MapVideotron = dynamic(() => import("../global/maps"), {
ssr: false,
});
// contoh data pilihan media online (bisa diganti sesuai kebutuhan) // contoh data pilihan media online (bisa diganti sesuai kebutuhan)
const mediaOnlineList = [ const mediaOnlineList = [
"Tribrata News Mabes", "Tribrata News Mabes",
@ -44,10 +50,13 @@ export default function FormCampaign() {
]; ];
const [selectedMediaOnline, setSelectedMediaOnline] = useState<string[]>([]); 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) => { const toggleMediaOnline = (item: string) => {
setSelectedMediaOnline((prev) => setSelectedMediaOnline((prev) =>
prev.includes(item) ? prev.filter((m) => m !== item) : [...prev, item] prev.includes(item) ? prev.filter((m) => m !== item) : [...prev, item],
); );
}; };
@ -73,7 +82,7 @@ export default function FormCampaign() {
// ✅ Simulasi upload progress // ✅ Simulasi upload progress
const simulateUpload = ( const simulateUpload = (
fileList: { file: File; progress: number; uploaded: boolean }[] fileList: { file: File; progress: number; uploaded: boolean }[],
) => { ) => {
fileList.forEach((fileObj) => { fileList.forEach((fileObj) => {
let progress = 0; let progress = 0;
@ -83,8 +92,8 @@ export default function FormCampaign() {
prev.map((f) => prev.map((f) =>
f.file === fileObj.file f.file === fileObj.file
? { ...f, progress, uploaded: progress >= 100 } ? { ...f, progress, uploaded: progress >= 100 }
: f : f,
) ),
); );
if (progress >= 100) clearInterval(interval); if (progress >= 100) clearInterval(interval);
}, 300); }, 300);
@ -214,17 +223,11 @@ export default function FormCampaign() {
Tambahkan Media Sosial Tambahkan Media Sosial
</Button> </Button>
)} )}
{media === "Videotron" && ( {media === "Videotron" && (
<Button <div className="mt-4 space-y-4">
variant="outline" {/* Map tampil */}
size="sm" <MapVideotron />
className="mt-4" </div>
onClick={() => setIsDialogOpen(true)}
>
<Plus className="h-4 w-4 mr-2" />
Tambahkan Videotron
</Button>
)} )}
{/* 🧩 Komponen DialogMediaOnline dipanggil di sini */} {/* 🧩 Komponen DialogMediaOnline dipanggil di sini */}
@ -325,28 +328,332 @@ export default function FormCampaign() {
</RadioGroup> </RadioGroup>
{available === "Yes" ? ( {available === "Yes" ? (
// ✅ Jika user pilih "Yes" → tampil upload file // ✅ Upload
<div className="space-y-2"> <div className="space-y-2">
<Label className="text-sm font-medium">Upload File</Label> <Label className="text-sm font-medium">Upload File</Label>
<div className="flex items-center gap-2"> <Button
<Button variant="outline"
variant="outline" size="sm"
size="sm" onClick={() => setIsUploadOpen(true)}
onClick={() => setIsUploadOpen(true)} >
> <Plus className="h-4 w-4 mr-2" />
<Plus className="h-4 w-4 mr-2" /> Upload File
Upload File </Button>
</Button>
</div>
</div> </div>
) : ( ) : (
// ✅ Jika user pilih "Tidak" → tampil textarea deskripsi // ❌ Tidak → tampil pilihan konten
<div className="space-y-2"> <div className="space-y-4">
<Label className="text-sm font-medium">Deskripsi Promote</Label> {/* Pilih Jenis Konten */}
<textarea <div>
placeholder="Tulis deskripsi promote..." <p className="text-sm font-medium mb-2">Pilih Jenis Konten</p>
className="w-full min-h-[100px] p-3 border rounded-md text-sm resize-none focus:outline-none focus:ring-2 focus:ring-primary"
/> <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> </div>
)} )}
</section> </section>

View File

@ -12,7 +12,6 @@ import { useRouter } from "next/navigation";
import Footer from "../landing-page/footer"; import Footer from "../landing-page/footer";
import Navbar from "../landing-page/navbar"; import Navbar from "../landing-page/navbar";
// ✅ Dummy user data
const users = [ const users = [
{ {
nrp: "1001", nrp: "1001",
@ -26,14 +25,19 @@ const users = [
}, },
{ {
nrp: "1003", nrp: "1003",
password: "super123", password: "executive123",
role: "supervisor", role: "executive",
}, },
{ {
nrp: "1004", nrp: "1004",
password: "approve123", password: "approve123",
role: "approver", role: "approver",
}, },
{
nrp: "1005",
password: "coordinator123",
role: "kordinator",
},
]; ];
export default function Login() { export default function Login() {
@ -66,12 +70,15 @@ export default function Login() {
case "user": case "user":
router.push("/dashboard/user"); router.push("/dashboard/user");
break; break;
case "supervisor": case "executive":
router.push("/dashboard/supervisor"); router.push("/dashboard/supervisor");
break; break;
case "approver": case "approver":
router.push("/dashboard/approver"); router.push("/dashboard/approver");
break; break;
case "kordinator":
router.push("/dashboard/coordinator");
break;
default: default:
router.push("/"); router.push("/");
} }

View File

@ -0,0 +1,13 @@
"use client";
export default function MapVideotron() {
return (
<div className="mt-4 h-[400px] w-full rounded-lg overflow-hidden border">
<iframe
src="https://www.google.com/maps?q=Jakarta&output=embed"
className="w-full h-full border-0"
loading="lazy"
/>
</div>
);
}

View File

@ -0,0 +1,122 @@
"use client";
import Link from "next/link";
import { useState } from "react";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Button } from "@/components/ui/button";
import { Eye } from "lucide-react";
import DialogCampaignDetail from "../dialog/campaign-detail";
export default function CoordinatorTable() {
const data = [
{
durasi: "22/08/2025 - 22/08/2026",
media: "Media Online",
tujuan: "Sosialisasi",
materi: "Tersedia",
description:
"Lorem ipsum dolor sit amet consectetur. Tempor mi scelerisque enim semper sed nibh.",
status: "Selesai",
},
{
durasi: "22/08/2025 - 22/08/2026",
media: "Media Sosial",
tujuan: "Sosialisasi",
materi: "Tersedia",
description:
"Ultricies pellentesque ullamcorper mattis pellentesque. Amet eu ut.",
status: "Selesai",
},
];
const [selectedRow, setSelectedRow] = useState<any>(null);
const [isDialogOpen, setIsDialogOpen] = useState(false);
const openDetail = (row: any) => {
setSelectedRow(row);
setIsDialogOpen(true);
};
return (
<>
<div className="bg-white shadow rounded-lg p-4">
<div className="overflow-x-auto">
<Table>
<TableHeader>
<TableRow>
<TableHead>Durasi</TableHead>
<TableHead>Media</TableHead>
<TableHead>Tujuan</TableHead>
<TableHead>Materi</TableHead>
<TableHead>Deskripsi Promote</TableHead>
<TableHead>Status</TableHead>
<TableHead>Tindakan</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data.map((row, i) => (
<TableRow key={i}>
<TableCell>{row.durasi}</TableCell>
<TableCell className="font-medium">{row.media}</TableCell>
<TableCell>{row.tujuan}</TableCell>
<TableCell>{row.materi}</TableCell>
<TableCell className="min-w-[200px] text-gray-600">
{row.description}
</TableCell>
<TableCell>
<span className="bg-green-100 text-green-700 px-3 py-1 rounded-full text-xs font-medium">
{row.status}
</span>
</TableCell>
<TableCell>
<Button
variant="link"
className="text-blue-600 p-0 h-auto font-medium flex items-center gap-1"
onClick={() => openDetail(row)}
>
<Eye className="h-4 w-4" />
Lihat
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
{/* Pagination */}
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-center mt-4 text-sm text-gray-500 gap-2">
<div className="flex items-center">
<span>Rows per page:</span>
<select className="ml-2 border rounded px-1 py-0.5">
<option>6</option>
<option>12</option>
</select>
</div>
<div className="flex items-center justify-between sm:justify-end gap-2">
<span>11 of 1</span>
<div className="flex gap-1">
<button className="text-gray-400 hover:text-gray-600"></button>
<button className="text-gray-400 hover:text-gray-600"></button>
</div>
</div>
</div>
</div>
<DialogCampaignDetail
isOpen={isDialogOpen}
onClose={() => setIsDialogOpen(false)}
data={selectedRow}
/>
</>
);
}

36
package-lock.json generated
View File

@ -25,12 +25,14 @@
"clsx": "^2.1.1", "clsx": "^2.1.1",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"leaflet": "^1.9.4",
"lucide-react": "^0.552.0", "lucide-react": "^0.552.0",
"next": "16.0.1", "next": "16.0.1",
"react": "19.2.0", "react": "19.2.0",
"react-apexcharts": "^1.8.0", "react-apexcharts": "^1.8.0",
"react-day-picker": "^9.11.1", "react-day-picker": "^9.11.1",
"react-dom": "19.2.0", "react-dom": "19.2.0",
"react-leaflet": "^4.2.1",
"recharts": "^3.8.1", "recharts": "^3.8.1",
"tailwind-merge": "^3.3.1" "tailwind-merge": "^3.3.1"
}, },
@ -1936,6 +1938,16 @@
"resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz",
"integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==" "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="
}, },
"node_modules/@react-leaflet/core": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-2.1.0.tgz",
"integrity": "sha512-Qk7Pfu8BSarKGqILj4x7bCSZ1pjuAPZ+qmRwH5S7mDS91VSbVVsJSrW4qA+GPrro8t69gFYVMWb1Zc4yFmPiVg==",
"peerDependencies": {
"leaflet": "^1.9.0",
"react": "^18.0.0",
"react-dom": "^18.0.0"
}
},
"node_modules/@reduxjs/toolkit": { "node_modules/@reduxjs/toolkit": {
"version": "2.11.2", "version": "2.11.2",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz", "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz",
@ -2196,7 +2208,7 @@
"version": "19.2.2", "version": "19.2.2",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
"devOptional": true, "dev": true,
"dependencies": { "dependencies": {
"csstype": "^3.0.2" "csstype": "^3.0.2"
} }
@ -2205,7 +2217,7 @@
"version": "19.2.2", "version": "19.2.2",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.2.tgz", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.2.tgz",
"integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==", "integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==",
"devOptional": true, "dev": true,
"peerDependencies": { "peerDependencies": {
"@types/react": "^19.2.0" "@types/react": "^19.2.0"
} }
@ -2431,7 +2443,7 @@
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"devOptional": true "dev": true
}, },
"node_modules/d3-array": { "node_modules/d3-array": {
"version": "3.2.4", "version": "3.2.4",
@ -2847,6 +2859,11 @@
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
}, },
"node_modules/leaflet": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA=="
},
"node_modules/lightningcss": { "node_modules/lightningcss": {
"version": "1.30.2", "version": "1.30.2",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz",
@ -3169,6 +3186,19 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
}, },
"node_modules/react-leaflet": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-4.2.1.tgz",
"integrity": "sha512-p9chkvhcKrWn/H/1FFeVSqLdReGwn2qmiobOQGO3BifX+/vV/39qhY8dGqbdcPh1e6jxh/QHriLXr7a4eLFK4Q==",
"dependencies": {
"@react-leaflet/core": "^2.1.0"
},
"peerDependencies": {
"leaflet": "^1.9.0",
"react": "^18.0.0",
"react-dom": "^18.0.0"
}
},
"node_modules/react-redux": { "node_modules/react-redux": {
"version": "9.2.0", "version": "9.2.0",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",

View File

@ -25,12 +25,14 @@
"clsx": "^2.1.1", "clsx": "^2.1.1",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"leaflet": "^1.9.4",
"lucide-react": "^0.552.0", "lucide-react": "^0.552.0",
"next": "16.0.1", "next": "16.0.1",
"react": "19.2.0", "react": "19.2.0",
"react-apexcharts": "^1.8.0", "react-apexcharts": "^1.8.0",
"react-day-picker": "^9.11.1", "react-day-picker": "^9.11.1",
"react-dom": "19.2.0", "react-dom": "19.2.0",
"react-leaflet": "^4.2.1",
"recharts": "^3.8.1", "recharts": "^3.8.1",
"tailwind-merge": "^3.3.1" "tailwind-merge": "^3.3.1"
}, },

BIN
public/agatha1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

BIN
public/agatha2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
public/ai1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1003 KiB

BIN
public/ai2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

BIN
public/ai3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

BIN
public/ai4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 989 KiB

BIN
public/m1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

BIN
public/m2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
public/m3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

BIN
public/m4.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

BIN
public/meme1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

BIN
public/meme2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

BIN
public/meme3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

BIN
public/meme4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
public/sl1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

BIN
public/sl2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

BIN
public/sl3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

BIN
public/sl4.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

BIN
public/ts1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

BIN
public/ts2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

BIN
public/ts3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB