add all task that i have done today
This commit is contained in:
parent
bcd1f06635
commit
96ecb499b9
|
|
@ -65,7 +65,7 @@ export function InstantScheduleModal({ isOpen, onClose, adminName }: Props) {
|
||||||
<form action={formAction} className="p-6 flex flex-col gap-5">
|
<form action={formAction} className="p-6 flex flex-col gap-5">
|
||||||
<input type="hidden" name="edited_by" value={adminName} />
|
<input type="hidden" name="edited_by" value={adminName} />
|
||||||
<input type="hidden" name="jam_mulai" value="08:00" />
|
<input type="hidden" name="jam_mulai" value="08:00" />
|
||||||
<input type="hidden" name="jam_selesai" value="11:00" />
|
<input type="hidden" name="jam_selesai" value="10:00" />
|
||||||
|
|
||||||
<div className="p-3 bg-blue-50 border border-blue-200 rounded-xl flex gap-x-2">
|
<div className="p-3 bg-blue-50 border border-blue-200 rounded-xl flex gap-x-2">
|
||||||
<AlertCircle className="w-4 h-4 text-blue-600 flex-shrink-0" />
|
<AlertCircle className="w-4 h-4 text-blue-600 flex-shrink-0" />
|
||||||
|
|
@ -74,7 +74,7 @@ export function InstantScheduleModal({ isOpen, onClose, adminName }: Props) {
|
||||||
Penjadwalan harus dilakukan minimal 1 hari sebelumnya.
|
Penjadwalan harus dilakukan minimal 1 hari sebelumnya.
|
||||||
</p>
|
</p>
|
||||||
<p className="text-[10px] text-blue-800 leading-relaxed font-bold uppercase tracking-tight">
|
<p className="text-[10px] text-blue-800 leading-relaxed font-bold uppercase tracking-tight">
|
||||||
Sesi tetap: 08:00 - 11:00 WIB (Selingan 1 Jam).
|
Sesi: 08-10, 11-13, 14-16 (Selingan 1 Jam).
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -104,7 +104,7 @@ export function InstantScheduleModal({ isOpen, onClose, adminName }: Props) {
|
||||||
</div>
|
</div>
|
||||||
<div className="h-0.5 w-4 bg-gray-300"></div>
|
<div className="h-0.5 w-4 bg-gray-300"></div>
|
||||||
<div className="px-3 py-1.5 bg-black text-white text-sm font-black rounded-lg">
|
<div className="px-3 py-1.5 bg-black text-white text-sm font-black rounded-lg">
|
||||||
11:00
|
10:00
|
||||||
</div>
|
</div>
|
||||||
<span className="text-[10px] font-black text-gray-400 uppercase tracking-widest ml-auto">Fixed Slot</span>
|
<span className="text-[10px] font-black text-gray-400 uppercase tracking-widest ml-auto">Fixed Slot</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -222,97 +222,101 @@ export function JadwalTable({ data, userName }: Props) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Table */}
|
{/* Table */}
|
||||||
<div className={`overflow-hidden rounded-2xl border-2 border-black shadow-[8px_8px_0px_0px_rgba(0,0,0,1)] transition-colors ${showHistory ? 'bg-slate-50' : 'bg-white'}`}>
|
<div className={`rounded-2xl border-2 border-black shadow-[8px_8px_0px_0px_rgba(0,0,0,1)] transition-colors ${showHistory ? 'bg-slate-50' : 'bg-white'} overflow-hidden`}>
|
||||||
<table className="w-full text-left border-collapse">
|
<div className="overflow-x-auto">
|
||||||
<thead className={`${showHistory ? 'bg-purple-600' : 'bg-black'} text-white transition-colors`}>
|
<div className="min-w-[1000px] md:min-w-full">
|
||||||
<tr>
|
<table className="w-full text-left border-collapse">
|
||||||
<th className="px-6 py-5 text-[10px] font-black uppercase tracking-widest text-center w-20">No</th>
|
<thead className={`${showHistory ? 'bg-purple-600' : 'bg-black'} text-white transition-colors`}>
|
||||||
<th className="px-6 py-5 text-[10px] font-black uppercase tracking-widest">Waktu & Sesi</th>
|
<tr>
|
||||||
<th className="px-6 py-5 text-[10px] font-black uppercase tracking-widest">Detail Posyandu</th>
|
<th className="px-6 py-5 text-[10px] font-black uppercase tracking-widest text-center w-20">No</th>
|
||||||
<th className="px-6 py-5 text-[10px] font-black uppercase tracking-widest">Oleh Admin</th>
|
<th className="px-6 py-5 text-[10px] font-black uppercase tracking-widest">Waktu & Sesi</th>
|
||||||
<th className="px-6 py-5 text-[10px] font-black uppercase tracking-widest text-center">Aksi</th>
|
<th className="px-6 py-5 text-[10px] font-black uppercase tracking-widest">Detail Posyandu</th>
|
||||||
</tr>
|
<th className="px-6 py-5 text-[10px] font-black uppercase tracking-widest">Oleh Admin</th>
|
||||||
</thead>
|
<th className="px-6 py-5 text-[10px] font-black uppercase tracking-widest text-center">Aksi</th>
|
||||||
<tbody className={`${showHistory ? 'bg-slate-50 divide-purple-100' : 'bg-white divide-gray-100'} divide-y-2`}>
|
|
||||||
{filteredData.length === 0 ? (
|
|
||||||
<tr>
|
|
||||||
<td colSpan={5} className="px-6 py-24 text-center">
|
|
||||||
<div className="flex flex-col items-center gap-4 text-gray-300">
|
|
||||||
<div className="p-4 bg-gray-50 rounded-full">
|
|
||||||
<Calendar className="w-12 h-12 opacity-20" />
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-1">
|
|
||||||
<p className="font-black text-gray-400">Belum ada jadwal ditemukan</p>
|
|
||||||
<p className="text-xs font-semibold">Gunakan Penjadwalan Instan untuk membuat jadwal otomatis.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
) : (
|
|
||||||
filteredData.map((j, idx) => (
|
|
||||||
<tr key={j.id} className={`${showHistory ? 'hover:bg-purple-100/50' : 'hover:bg-red-50/30'} transition-colors group`}>
|
|
||||||
<td className={`px-6 py-6 text-center font-black ${showHistory ? 'text-purple-300' : 'text-gray-300'} group-hover:text-red-300 text-lg`}>
|
|
||||||
{idx + 1}
|
|
||||||
</td>
|
|
||||||
<td className="px-6 py-6">
|
|
||||||
<div className="flex flex-col gap-1">
|
|
||||||
<div className={`flex items-center gap-2 ${showHistory ? 'text-purple-600' : 'text-red-600'} font-black`}>
|
|
||||||
<Clock className="w-4 h-4" />
|
|
||||||
<span className="text-lg">{j.jam_mulai.slice(0, 5)} - {j.jam_selesai.slice(0, 5)}</span>
|
|
||||||
</div>
|
|
||||||
<span className="text-[10px] font-bold uppercase text-gray-400 tracking-widest">
|
|
||||||
{new Date(j.tanggal).toLocaleDateString('id-ID', { weekday: 'long', day: 'numeric', month: 'long' })}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td className="px-6 py-6">
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<span className={`font-black text-base ${showHistory ? 'text-purple-900' : 'text-gray-900'} group-hover:text-red-600 transition-colors uppercase tracking-tight`}>
|
|
||||||
{j.detail_posyandu.nama_posyandu}
|
|
||||||
</span>
|
|
||||||
<div className="flex items-center gap-2 text-xs text-gray-500 font-semibold mt-1">
|
|
||||||
<Building2 className="w-3.5 h-3.5" />
|
|
||||||
<span className="line-clamp-1">{j.detail_posyandu.alamat}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td className="px-6 py-6">
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<div className={`w-8 h-8 rounded-full ${showHistory ? 'bg-purple-100' : 'bg-gray-100'} border-2 border-white shadow-sm flex items-center justify-center text-[10px] font-black group-hover:bg-red-100 transition-colors`}>
|
|
||||||
{j.diedit_oleh.replace('[HISTORY] ', '').charAt(0).toUpperCase()}
|
|
||||||
</div>
|
|
||||||
<span className="text-xs font-bold text-gray-700">{j.diedit_oleh.replace('[HISTORY] ', '')}</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td className="px-6 py-6">
|
|
||||||
<div className="flex items-center justify-center gap-3">
|
|
||||||
<CetakPDFJadwal jadwal={j} currentAdmin={userName} />
|
|
||||||
<button
|
|
||||||
onClick={() => handleEditJadwal(j)}
|
|
||||||
className="p-2 text-gray-400 hover:text-black hover:bg-gray-100 rounded-lg transition-all"
|
|
||||||
title="Edit"
|
|
||||||
>
|
|
||||||
<Edit3 className="w-4 h-4" />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => handleDeleteJadwal(j.id, j.detail_posyandu.nama_posyandu)}
|
|
||||||
disabled={isDeleting === j.id}
|
|
||||||
className="p-2 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded-lg transition-all disabled:opacity-50"
|
|
||||||
title="Hapus"
|
|
||||||
>
|
|
||||||
{isDeleting === j.id ? (
|
|
||||||
<Loader2 className="w-4 h-4 animate-spin" />
|
|
||||||
) : (
|
|
||||||
<Trash2 className="w-4 h-4" />
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
))
|
</thead>
|
||||||
)}
|
<tbody className={`${showHistory ? 'bg-slate-50 divide-purple-100' : 'bg-white divide-gray-100'} divide-y-2`}>
|
||||||
</tbody>
|
{filteredData.length === 0 ? (
|
||||||
</table>
|
<tr>
|
||||||
|
<td colSpan={5} className="px-6 py-24 text-center">
|
||||||
|
<div className="flex flex-col items-center gap-4 text-gray-300">
|
||||||
|
<div className="p-4 bg-gray-50 rounded-full">
|
||||||
|
<Calendar className="w-12 h-12 opacity-20" />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<p className="font-black text-gray-400">Belum ada jadwal ditemukan</p>
|
||||||
|
<p className="text-xs font-semibold">Gunakan Penjadwalan Instan untuk membuat jadwal otomatis.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
) : (
|
||||||
|
filteredData.map((j, idx) => (
|
||||||
|
<tr key={j.id} className={`${showHistory ? 'hover:bg-purple-100/50' : 'hover:bg-red-50/30'} transition-colors group`}>
|
||||||
|
<td className={`px-6 py-6 text-center font-black ${showHistory ? 'text-purple-300' : 'text-gray-300'} group-hover:text-red-300 text-lg`}>
|
||||||
|
{idx + 1}
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-6">
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<div className={`flex items-center gap-2 ${showHistory ? 'text-purple-600' : 'text-red-600'} font-black`}>
|
||||||
|
<Clock className="w-4 h-4" />
|
||||||
|
<span className="text-lg">{j.jam_mulai.slice(0, 5)} - {j.jam_selesai.slice(0, 5)}</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-[10px] font-bold uppercase text-gray-400 tracking-widest">
|
||||||
|
{new Date(j.tanggal).toLocaleDateString('id-ID', { weekday: 'long', day: 'numeric', month: 'long' })}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-6">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className={`font-black text-base ${showHistory ? 'text-purple-900' : 'text-gray-900'} group-hover:text-red-600 transition-colors uppercase tracking-tight`}>
|
||||||
|
{j.detail_posyandu.nama_posyandu}
|
||||||
|
</span>
|
||||||
|
<div className="flex items-center gap-2 text-xs text-gray-500 font-semibold mt-1">
|
||||||
|
<Building2 className="w-3.5 h-3.5" />
|
||||||
|
<span className="line-clamp-1">{j.detail_posyandu.alamat}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-6">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className={`w-8 h-8 rounded-full ${showHistory ? 'bg-purple-100' : 'bg-gray-100'} border-2 border-white shadow-sm flex items-center justify-center text-[10px] font-black group-hover:bg-red-100 transition-colors`}>
|
||||||
|
{j.diedit_oleh.replace('[HISTORY] ', '').charAt(0).toUpperCase()}
|
||||||
|
</div>
|
||||||
|
<span className="text-xs font-bold text-gray-700">{j.diedit_oleh.replace('[HISTORY] ', '')}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-6">
|
||||||
|
<div className="flex items-center justify-center gap-3">
|
||||||
|
<CetakPDFJadwal jadwal={j} currentAdmin={userName} />
|
||||||
|
<button
|
||||||
|
onClick={() => handleEditJadwal(j)}
|
||||||
|
className="p-2 text-gray-400 hover:text-black hover:bg-gray-100 rounded-lg transition-all"
|
||||||
|
title="Edit"
|
||||||
|
>
|
||||||
|
<Edit3 className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => handleDeleteJadwal(j.id, j.detail_posyandu.nama_posyandu)}
|
||||||
|
disabled={isDeleting === j.id}
|
||||||
|
className="p-2 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded-lg transition-all disabled:opacity-50"
|
||||||
|
title="Hapus"
|
||||||
|
>
|
||||||
|
{isDeleting === j.id ? (
|
||||||
|
<Loader2 className="w-4 h-4 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<Trash2 className="w-4 h-4" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<InstantScheduleModal
|
<InstantScheduleModal
|
||||||
|
|
|
||||||
|
|
@ -64,72 +64,76 @@ export default async function KelolaAkunPenggunaPage() {
|
||||||
|
|
||||||
{/* Table */}
|
{/* Table */}
|
||||||
<div className="rounded-xl border-2 border-black shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] overflow-hidden">
|
<div className="rounded-xl border-2 border-black shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] overflow-hidden">
|
||||||
{/* Table Header */}
|
<div className="overflow-x-auto">
|
||||||
<div className="grid grid-cols-[2fr_2fr_1.5fr_1.5fr_auto] bg-black text-white px-6 py-4 text-xs font-bold uppercase tracking-widest">
|
<div className="min-w-[800px] md:min-w-full">
|
||||||
<span>Nama Orang Tua</span>
|
{/* Table Header */}
|
||||||
<span>Nama Anak</span>
|
<div className="grid grid-cols-[2fr_2fr_1.5fr_1.5fr_auto] bg-black text-white px-6 py-4 text-xs font-bold uppercase tracking-widest">
|
||||||
<span>No. WhatsApp</span>
|
<span>Nama Orang Tua</span>
|
||||||
<span>Username</span>
|
<span>Nama Anak</span>
|
||||||
<span className="text-center">Aksi</span>
|
<span>No. WhatsApp</span>
|
||||||
</div>
|
<span>Username</span>
|
||||||
|
<span className="text-center">Aksi</span>
|
||||||
{/* Table Rows */}
|
|
||||||
{!pengguna || pengguna.length === 0 ? (
|
|
||||||
<div className="flex flex-col items-center justify-center py-16 gap-3 text-gray-400">
|
|
||||||
<Users className="w-12 h-12 opacity-30" />
|
|
||||||
<p className="font-semibold">Belum ada pengguna terdaftar</p>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
pengguna.map((user, idx) => (
|
|
||||||
<div
|
|
||||||
key={user.id}
|
|
||||||
className={`grid grid-cols-[2fr_2fr_1.5fr_1.5fr_auto] items-center px-6 py-4 border-b border-gray-100 hover:bg-gray-50 transition-colors ${idx % 2 === 0 ? 'bg-white' : 'bg-gray-50/50'}`}
|
|
||||||
>
|
|
||||||
{/* Nama Orang Tua */}
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<div className="w-9 h-9 rounded-full bg-blue-100 border border-blue-200 flex items-center justify-center text-blue-700 text-xs font-bold flex-shrink-0">
|
|
||||||
{user.nama_orang_tua?.charAt(0).toUpperCase() ?? '?'}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="font-semibold text-sm leading-none">{user.nama_orang_tua ?? '-'}</p>
|
|
||||||
<p className="text-[10px] text-gray-400 mt-0.5">
|
|
||||||
{user.created_at ? new Date(user.created_at).toLocaleDateString('id-ID', { day: '2-digit', month: 'short', year: 'numeric' }) : '-'}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Nama Anak */}
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Baby className="w-3.5 h-3.5 text-gray-400 flex-shrink-0" />
|
|
||||||
<span className="text-sm truncate">{user.nama_anak ?? '-'}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* No WA */}
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Phone className="w-3.5 h-3.5 text-gray-400 flex-shrink-0" />
|
|
||||||
<span className="text-sm text-gray-600">{user.no_whatsapp ?? '-'}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Username */}
|
|
||||||
<div>
|
|
||||||
<span className="inline-block bg-gray-100 border border-gray-200 text-gray-700 text-xs font-mono px-2 py-1 rounded">
|
|
||||||
@{user.username}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Aksi */}
|
|
||||||
<div className="flex justify-center">
|
|
||||||
<Link
|
|
||||||
href={`/dashboard/manajemen-akun/pengguna/${user.id}`}
|
|
||||||
className="flex items-center gap-1.5 px-4 py-2 bg-black text-white text-xs font-bold rounded-lg hover:bg-gray-800 transition-all shadow-[2px_2px_0px_0px_rgba(0,0,0,0.3)] hover:shadow-none hover:translate-x-[1px] hover:translate-y-[1px]"
|
|
||||||
>
|
|
||||||
<Eye className="w-3.5 h-3.5" />
|
|
||||||
Detail
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
))
|
|
||||||
)}
|
{/* Table Rows */}
|
||||||
|
{!pengguna || pengguna.length === 0 ? (
|
||||||
|
<div className="flex flex-col items-center justify-center py-16 gap-3 text-gray-400 bg-white">
|
||||||
|
<Users className="w-12 h-12 opacity-30" />
|
||||||
|
<p className="font-semibold">Belum ada pengguna terdaftar</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
pengguna.map((user, idx) => (
|
||||||
|
<div
|
||||||
|
key={user.id}
|
||||||
|
className={`grid grid-cols-[2fr_2fr_1.5fr_1.5fr_auto] items-center px-6 py-4 border-b border-gray-100 hover:bg-gray-50 transition-colors ${idx % 2 === 0 ? 'bg-white' : 'bg-gray-50/50'}`}
|
||||||
|
>
|
||||||
|
{/* Nama Orang Tua */}
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="w-9 h-9 rounded-full bg-blue-100 border border-blue-200 flex items-center justify-center text-blue-700 text-xs font-bold flex-shrink-0">
|
||||||
|
{user.nama_orang_tua?.charAt(0).toUpperCase() ?? '?'}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="font-semibold text-sm leading-none">{user.nama_orang_tua ?? '-'}</p>
|
||||||
|
<p className="text-[10px] text-gray-400 mt-0.5">
|
||||||
|
{user.created_at ? new Date(user.created_at).toLocaleDateString('id-ID', { day: '2-digit', month: 'short', year: 'numeric' }) : '-'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Nama Anak */}
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Baby className="w-3.5 h-3.5 text-gray-400 flex-shrink-0" />
|
||||||
|
<span className="text-sm truncate">{user.nama_anak ?? '-'}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* No WA */}
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Phone className="w-3.5 h-3.5 text-gray-400 flex-shrink-0" />
|
||||||
|
<span className="text-sm text-gray-600">{user.no_whatsapp ?? '-'}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Username */}
|
||||||
|
<div>
|
||||||
|
<span className="inline-block bg-gray-100 border border-gray-200 text-gray-700 text-xs font-mono px-2 py-1 rounded">
|
||||||
|
@{user.username}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Aksi */}
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<Link
|
||||||
|
href={`/dashboard/manajemen-akun/pengguna/${user.id}`}
|
||||||
|
className="flex items-center gap-1.5 px-4 py-2 bg-black text-white text-xs font-bold rounded-lg hover:bg-gray-800 transition-all shadow-[2px_2px_0px_0px_rgba(0,0,0,0.3)] hover:shadow-none hover:translate-x-[1px] hover:translate-y-[1px]"
|
||||||
|
>
|
||||||
|
<Eye className="w-3.5 h-3.5" />
|
||||||
|
Detail
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Footer note */}
|
{/* Footer note */}
|
||||||
|
|
|
||||||
|
|
@ -96,124 +96,128 @@ export function ManajemenPosyanduTable({ data }: Props) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Table */}
|
{/* Table */}
|
||||||
<div className="overflow-hidden rounded-2xl border-2 border-black shadow-[8px_8px_0px_0px_rgba(0,0,0,1)]">
|
<div className="rounded-2xl border-2 border-black shadow-[8px_8px_0px_0px_rgba(0,0,0,1)] overflow-hidden">
|
||||||
<table className="w-full text-left border-collapse">
|
<div className="overflow-x-auto">
|
||||||
<thead className="bg-black text-white">
|
<div className="min-w-[900px] md:min-w-full">
|
||||||
<tr>
|
<table className="w-full text-left border-collapse">
|
||||||
<th className="px-6 py-4 text-[10px] font-bold uppercase tracking-widest text-center w-16">No</th>
|
<thead className="bg-black text-white">
|
||||||
<th className="px-6 py-4 text-[10px] font-bold uppercase tracking-widest">Informasi Posyandu</th>
|
<tr>
|
||||||
<th className="px-6 py-4 text-[10px] font-bold uppercase tracking-widest">Petugas & Kontak</th>
|
<th className="px-6 py-4 text-[10px] font-bold uppercase tracking-widest text-center w-16">No</th>
|
||||||
<th className="px-6 py-4 text-[10px] font-bold uppercase tracking-widest text-center">Lokasi</th>
|
<th className="px-6 py-4 text-[10px] font-bold uppercase tracking-widest">Informasi Posyandu</th>
|
||||||
<th className="px-6 py-4 text-[10px] font-bold uppercase tracking-widest text-center w-40">Aksi</th>
|
<th className="px-6 py-4 text-[10px] font-bold uppercase tracking-widest">Petugas & Kontak</th>
|
||||||
</tr>
|
<th className="px-6 py-4 text-[10px] font-bold uppercase tracking-widest text-center">Lokasi</th>
|
||||||
</thead>
|
<th className="px-6 py-4 text-[10px] font-bold uppercase tracking-widest text-center w-40">Aksi</th>
|
||||||
<tbody className="bg-white divide-y divide-gray-100">
|
|
||||||
{filteredData.length === 0 ? (
|
|
||||||
<tr>
|
|
||||||
<td colSpan={5} className="px-6 py-20 text-center">
|
|
||||||
<div className="flex flex-col items-center gap-2 text-gray-400">
|
|
||||||
<Building2 className="w-12 h-12 opacity-20" />
|
|
||||||
<p className="font-bold">Tidak ada data posyandu ditemukan</p>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
) : (
|
|
||||||
filteredData.map((p, idx) => (
|
|
||||||
<tr key={p.id} className="hover:bg-purple-50/30 transition-colors group">
|
|
||||||
<td className="px-6 py-5 text-center font-black text-gray-300 group-hover:text-purple-300">
|
|
||||||
{idx + 1}
|
|
||||||
</td>
|
|
||||||
<td className="px-6 py-5">
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<span className="font-black text-base">{p.nama_posyandu}</span>
|
|
||||||
<div className="flex items-center gap-1.5 text-gray-500 text-xs mt-1">
|
|
||||||
<MapPin className="w-3 h-3 flex-shrink-0" />
|
|
||||||
<span className="line-clamp-1">{p.alamat}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td className="px-6 py-5">
|
|
||||||
<div className="flex flex-col gap-2 max-w-[250px]">
|
|
||||||
{p.petugas && p.petugas.length > 0 ? (
|
|
||||||
p.petugas.map((petugas, i) => (
|
|
||||||
<div key={i} className="flex flex-col border-l-2 border-purple-100 pl-3 py-0.5">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span className="text-sm font-bold text-gray-700 line-clamp-1">
|
|
||||||
{petugas.nama_petugas}
|
|
||||||
</span>
|
|
||||||
{petugas.jabatan && (
|
|
||||||
<span className="text-[10px] px-1.5 py-0.5 bg-purple-50 text-purple-600 rounded font-black uppercase tracking-tighter">
|
|
||||||
{petugas.jabatan}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{petugas.nomor_hp && (
|
|
||||||
<div className="flex items-center gap-1.5 text-[10px] text-gray-400">
|
|
||||||
<Phone className="w-2.5 h-2.5" />
|
|
||||||
<span>{petugas.nomor_hp}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<div className="flex items-center gap-2 text-gray-400 italic text-xs">
|
|
||||||
<User className="w-3 h-3" />
|
|
||||||
<span>Belum ada petugas</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td className="px-6 py-5 text-center">
|
|
||||||
{p.link_google_maps ? (
|
|
||||||
<a
|
|
||||||
href={p.link_google_maps}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="inline-flex items-center gap-1.5 px-3 py-1.5 bg-blue-50 text-blue-600 rounded-lg text-xs font-bold border border-blue-100 hover:bg-blue-100 transition-colors"
|
|
||||||
>
|
|
||||||
<ExternalLink className="w-3 h-3" />
|
|
||||||
Cek Maps
|
|
||||||
</a>
|
|
||||||
) : (
|
|
||||||
<span className="text-xs text-gray-300 italic">Belum diset</span>
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
<td className="px-6 py-5">
|
|
||||||
<div className="flex items-center justify-center gap-2">
|
|
||||||
<button
|
|
||||||
onClick={() => handleEdit(p)}
|
|
||||||
className="p-2 text-gray-400 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition-all"
|
|
||||||
title="Edit"
|
|
||||||
>
|
|
||||||
<Edit3 className="w-4 h-4" />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => handleDelete(p.id, p.nama_posyandu)}
|
|
||||||
disabled={isDeleting === p.id}
|
|
||||||
className="p-2 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded-lg transition-all disabled:opacity-50"
|
|
||||||
title="Hapus"
|
|
||||||
>
|
|
||||||
{isDeleting === p.id ? (
|
|
||||||
<Loader2 className="w-4 h-4 animate-spin" />
|
|
||||||
) : (
|
|
||||||
<Trash2 className="w-4 h-4" />
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
<div className="w-px h-10 bg-gray-100 mx-1"></div>
|
|
||||||
<button
|
|
||||||
onClick={() => router.push(`/dashboard/manajemen-posyandu/review/${p.id}`)}
|
|
||||||
className="flex items-center gap-1.5 px-3 py-1.5 bg-black text-white rounded-lg text-xs font-black shadow-[3px_3px_0px_0px_rgba(147,51,234,0.5)] hover:shadow-none hover:translate-x-[1px] hover:translate-y-[1px] transition-all"
|
|
||||||
>
|
|
||||||
<Eye className="w-3.5 h-3.5" />
|
|
||||||
REVIEW
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
))
|
</thead>
|
||||||
)}
|
<tbody className="bg-white divide-y divide-gray-100">
|
||||||
</tbody>
|
{filteredData.length === 0 ? (
|
||||||
</table>
|
<tr>
|
||||||
|
<td colSpan={5} className="px-6 py-20 text-center">
|
||||||
|
<div className="flex flex-col items-center gap-2 text-gray-400">
|
||||||
|
<Building2 className="w-12 h-12 opacity-20" />
|
||||||
|
<p className="font-bold">Tidak ada data posyandu ditemukan</p>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
) : (
|
||||||
|
filteredData.map((p, idx) => (
|
||||||
|
<tr key={p.id} className="hover:bg-purple-50/30 transition-colors group">
|
||||||
|
<td className="px-6 py-5 text-center font-black text-gray-300 group-hover:text-purple-300">
|
||||||
|
{idx + 1}
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-5">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className="font-black text-base">{p.nama_posyandu}</span>
|
||||||
|
<div className="flex items-center gap-1.5 text-gray-500 text-xs mt-1">
|
||||||
|
<MapPin className="w-3 h-3 flex-shrink-0" />
|
||||||
|
<span className="line-clamp-1">{p.alamat}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-5">
|
||||||
|
<div className="flex flex-col gap-2 max-w-[250px]">
|
||||||
|
{p.petugas && p.petugas.length > 0 ? (
|
||||||
|
p.petugas.map((petugas, i) => (
|
||||||
|
<div key={i} className="flex flex-col border-l-2 border-purple-100 pl-3 py-0.5">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-sm font-bold text-gray-700 line-clamp-1">
|
||||||
|
{petugas.nama_petugas}
|
||||||
|
</span>
|
||||||
|
{petugas.jabatan && (
|
||||||
|
<span className="text-[10px] px-1.5 py-0.5 bg-purple-50 text-purple-600 rounded font-black uppercase tracking-tighter">
|
||||||
|
{petugas.jabatan}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{petugas.nomor_hp && (
|
||||||
|
<div className="flex items-center gap-1.5 text-[10px] text-gray-400">
|
||||||
|
<Phone className="w-2.5 h-2.5" />
|
||||||
|
<span>{petugas.nomor_hp}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center gap-2 text-gray-400 italic text-xs">
|
||||||
|
<User className="w-3 h-3" />
|
||||||
|
<span>Belum ada petugas</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-5 text-center">
|
||||||
|
{p.link_google_maps ? (
|
||||||
|
<a
|
||||||
|
href={p.link_google_maps}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="inline-flex items-center gap-1.5 px-3 py-1.5 bg-blue-50 text-blue-600 rounded-lg text-xs font-bold border border-blue-100 hover:bg-blue-100 transition-colors"
|
||||||
|
>
|
||||||
|
<ExternalLink className="w-3 h-3" />
|
||||||
|
Cek Maps
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<span className="text-xs text-gray-300 italic">Belum diset</span>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-5">
|
||||||
|
<div className="flex items-center justify-center gap-2">
|
||||||
|
<button
|
||||||
|
onClick={() => handleEdit(p)}
|
||||||
|
className="p-2 text-gray-400 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition-all"
|
||||||
|
title="Edit"
|
||||||
|
>
|
||||||
|
<Edit3 className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => handleDelete(p.id, p.nama_posyandu)}
|
||||||
|
disabled={isDeleting === p.id}
|
||||||
|
className="p-2 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded-lg transition-all disabled:opacity-50"
|
||||||
|
title="Hapus"
|
||||||
|
>
|
||||||
|
{isDeleting === p.id ? (
|
||||||
|
<Loader2 className="w-4 h-4 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<Trash2 className="w-4 h-4" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
<div className="w-px h-10 bg-gray-100 mx-1"></div>
|
||||||
|
<button
|
||||||
|
onClick={() => router.push(`/dashboard/manajemen-posyandu/review/${p.id}`)}
|
||||||
|
className="flex items-center gap-1.5 px-3 py-1.5 bg-black text-white rounded-lg text-xs font-black shadow-[3px_3px_0px_0px_rgba(147,51,234,0.5)] hover:shadow-none hover:translate-x-[1px] hover:translate-y-[1px] transition-all"
|
||||||
|
>
|
||||||
|
<Eye className="w-3.5 h-3.5" />
|
||||||
|
REVIEW
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isModalOpen && (
|
{isModalOpen && (
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ export default async function DashboardPage() {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-white font-sans text-black flex flex-col">
|
<div className="min-h-screen bg-white font-sans text-black flex flex-col">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<header className="flex justify-between items-center px-8 py-6 border-b border-gray-100">
|
<header className="flex justify-between items-center px-4 md:px-8 py-4 md:py-6 border-b border-gray-100">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="p-2 rounded-full border border-black flex items-center justify-center">
|
<div className="p-2 rounded-full border border-black flex items-center justify-center">
|
||||||
<Activity className="h-5 w-5 text-black" />
|
<Activity className="h-5 w-5 text-black" />
|
||||||
|
|
@ -46,12 +46,12 @@ export default async function DashboardPage() {
|
||||||
<LogoutButton />
|
<LogoutButton />
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main className="p-8 max-w-6xl mx-auto">
|
<main className="p-4 md:p-8 max-w-6xl mx-auto w-full">
|
||||||
{/* Main Single Frame */}
|
{/* Main Single Frame */}
|
||||||
<div className="bg-white rounded-xl border border-black shadow-[8px_8px_0px_0px_rgba(0,0,0,1)] overflow-hidden">
|
<div className="bg-white rounded-xl border border-black shadow-[8px_8px_0px_0px_rgba(0,0,0,1)] overflow-hidden">
|
||||||
|
|
||||||
{/* Top Section - Welcome & Clock */}
|
{/* Top Section - Welcome & Clock */}
|
||||||
<div className="p-8 border-b border-black flex flex-col md:flex-row justify-between items-start md:items-center">
|
<div className="p-6 md:p-8 border-b border-black flex flex-col md:flex-row justify-between items-start md:items-center">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-2xl font-bold flex items-center gap-2 mb-2">
|
<h2 className="text-2xl font-bold flex items-center gap-2 mb-2">
|
||||||
<Activity className="h-6 w-6" />
|
<Activity className="h-6 w-6" />
|
||||||
|
|
|
||||||
|
|
@ -240,45 +240,49 @@ export function StuntingChart({ data, availableYears }: Props) {
|
||||||
<p className="font-semibold text-gray-400">Tidak ada data untuk tahun {selectedYear}</p>
|
<p className="font-semibold text-gray-400">Tidak ada data untuk tahun {selectedYear}</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<ResponsiveContainer width="100%" height={320}>
|
<div className="overflow-x-auto pb-2 -mx-2 px-2">
|
||||||
{chartType === 'bar' ? (
|
<div className="min-w-[700px] md:min-w-full">
|
||||||
<BarChart data={chartData} margin={{ top: 4, right: 8, left: -10, bottom: 0 }} barCategoryGap="30%">
|
<ResponsiveContainer width="100%" height={350}>
|
||||||
<CartesianGrid strokeDasharray="3 3" stroke="#f0f0f0" />
|
{chartType === 'bar' ? (
|
||||||
<XAxis dataKey="month" tick={{ fontSize: 11, fontWeight: 600 }} axisLine={false} tickLine={false} />
|
<BarChart data={chartData} margin={{ top: 20, right: 10, left: -20, bottom: 0 }} barCategoryGap="25%">
|
||||||
<YAxis tick={{ fontSize: 11 }} axisLine={false} tickLine={false} allowDecimals={false} />
|
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#f0f0f0" />
|
||||||
<Tooltip content={<CustomTooltip />} />
|
<XAxis dataKey="month" tick={{ fontSize: 11, fontWeight: 700 }} axisLine={false} tickLine={false} />
|
||||||
<Legend
|
<YAxis tick={{ fontSize: 11 }} axisLine={false} tickLine={false} allowDecimals={false} />
|
||||||
wrapperStyle={{ fontSize: 11, fontWeight: 600, paddingTop: 12 }}
|
<Tooltip content={<CustomTooltip />} cursor={{ fill: '#f8fafc' }} />
|
||||||
formatter={(val) => val === 'stunting' ? 'Stunting' : 'Normal'}
|
<Legend
|
||||||
/>
|
wrapperStyle={{ fontSize: 12, fontWeight: 700, paddingTop: 20 }}
|
||||||
<Bar dataKey="stunting" fill="#ef4444" radius={[4, 4, 0, 0]} />
|
formatter={(val) => val === 'stunting' ? 'Stunting' : 'Normal'}
|
||||||
<Bar dataKey="normal" fill="#10b981" radius={[4, 4, 0, 0]} />
|
/>
|
||||||
</BarChart>
|
<Bar dataKey="stunting" fill="#ef4444" radius={[4, 4, 0, 0]} />
|
||||||
) : (
|
<Bar dataKey="normal" fill="#10b981" radius={[4, 4, 0, 0]} />
|
||||||
<AreaChart data={chartData} margin={{ top: 4, right: 8, left: -10, bottom: 0 }}>
|
</BarChart>
|
||||||
<defs>
|
) : (
|
||||||
<linearGradient id="stuntingGrad" x1="0" y1="0" x2="0" y2="1">
|
<AreaChart data={chartData} margin={{ top: 20, right: 10, left: -20, bottom: 0 }}>
|
||||||
<stop offset="5%" stopColor="#ef4444" stopOpacity={0.25} />
|
<defs>
|
||||||
<stop offset="95%" stopColor="#ef4444" stopOpacity={0} />
|
<linearGradient id="stuntingGrad" x1="0" y1="0" x2="0" y2="1">
|
||||||
</linearGradient>
|
<stop offset="5%" stopColor="#ef4444" stopOpacity={0.25} />
|
||||||
<linearGradient id="normalGrad" x1="0" y1="0" x2="0" y2="1">
|
<stop offset="95%" stopColor="#ef4444" stopOpacity={0} />
|
||||||
<stop offset="5%" stopColor="#10b981" stopOpacity={0.25} />
|
</linearGradient>
|
||||||
<stop offset="95%" stopColor="#10b981" stopOpacity={0} />
|
<linearGradient id="normalGrad" x1="0" y1="0" x2="0" y2="1">
|
||||||
</linearGradient>
|
<stop offset="5%" stopColor="#10b981" stopOpacity={0.25} />
|
||||||
</defs>
|
<stop offset="95%" stopColor="#10b981" stopOpacity={0} />
|
||||||
<CartesianGrid strokeDasharray="3 3" stroke="#f0f0f0" />
|
</linearGradient>
|
||||||
<XAxis dataKey="month" tick={{ fontSize: 11, fontWeight: 600 }} axisLine={false} tickLine={false} />
|
</defs>
|
||||||
<YAxis tick={{ fontSize: 11 }} axisLine={false} tickLine={false} allowDecimals={false} />
|
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#f0f0f0" />
|
||||||
<Tooltip content={<CustomTooltip />} />
|
<XAxis dataKey="month" tick={{ fontSize: 11, fontWeight: 700 }} axisLine={false} tickLine={false} />
|
||||||
<Legend
|
<YAxis tick={{ fontSize: 11 }} axisLine={false} tickLine={false} allowDecimals={false} />
|
||||||
wrapperStyle={{ fontSize: 11, fontWeight: 600, paddingTop: 12 }}
|
<Tooltip content={<CustomTooltip />} />
|
||||||
formatter={(val) => val === 'stunting' ? 'Stunting' : 'Normal'}
|
<Legend
|
||||||
/>
|
wrapperStyle={{ fontSize: 12, fontWeight: 700, paddingTop: 20 }}
|
||||||
<Area type="monotone" dataKey="normal" stroke="#10b981" strokeWidth={2.5} fill="url(#normalGrad)" dot={{ r: 4, fill: '#10b981' }} />
|
formatter={(val) => val === 'stunting' ? 'Stunting' : 'Normal'}
|
||||||
<Area type="monotone" dataKey="stunting" stroke="#ef4444" strokeWidth={2.5} fill="url(#stuntingGrad)" dot={{ r: 4, fill: '#ef4444' }} />
|
/>
|
||||||
</AreaChart>
|
<Area type="monotone" dataKey="normal" stroke="#10b981" strokeWidth={3} fill="url(#normalGrad)" dot={{ r: 4, fill: '#10b981', strokeWidth: 2, stroke: 'white' }} />
|
||||||
)}
|
<Area type="monotone" dataKey="stunting" stroke="#ef4444" strokeWidth={3} fill="url(#stuntingGrad)" dot={{ r: 4, fill: '#ef4444', strokeWidth: 2, stroke: 'white' }} />
|
||||||
</ResponsiveContainer>
|
</AreaChart>
|
||||||
|
)}
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -295,63 +299,66 @@ export function StuntingChart({ data, availableYears }: Props) {
|
||||||
<p className="text-sm">Tidak ada data untuk ditampilkan</p>
|
<p className="text-sm">Tidak ada data untuk ditampilkan</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<ResponsiveContainer width="100%" height={220}>
|
<div className="overflow-x-auto pb-2 -mx-2 px-2">
|
||||||
<LineChart data={chartData} margin={{ top: 8, right: 16, left: -10, bottom: 0 }}>
|
<div className="min-w-[700px] md:min-w-full">
|
||||||
<defs>
|
<ResponsiveContainer width="100%" height={260}>
|
||||||
<linearGradient id="prevalensiGrad" x1="0" y1="0" x2="0" y2="1">
|
<LineChart data={chartData} margin={{ top: 10, right: 20, left: -20, bottom: 0 }}>
|
||||||
<stop offset="5%" stopColor="#f59e0b" stopOpacity={0.15} />
|
<defs>
|
||||||
<stop offset="95%" stopColor="#f59e0b" stopOpacity={0} />
|
<linearGradient id="prevalensiGrad" x1="0" y1="0" x2="0" y2="1">
|
||||||
</linearGradient>
|
<stop offset="5%" stopColor="#f59e0b" stopOpacity={0.15} />
|
||||||
</defs>
|
<stop offset="95%" stopColor="#f59e0b" stopOpacity={0} />
|
||||||
<CartesianGrid strokeDasharray="3 3" stroke="#fde68a" />
|
</linearGradient>
|
||||||
<XAxis dataKey="month" tick={{ fontSize: 11, fontWeight: 600 }} axisLine={false} tickLine={false} />
|
</defs>
|
||||||
<YAxis
|
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#fde68a" />
|
||||||
tick={{ fontSize: 11 }}
|
<XAxis dataKey="month" tick={{ fontSize: 11, fontWeight: 700 }} axisLine={false} tickLine={false} />
|
||||||
axisLine={false}
|
<YAxis
|
||||||
tickLine={false}
|
tick={{ fontSize: 11 }}
|
||||||
tickFormatter={(v) => `${v}%`}
|
axisLine={false}
|
||||||
domain={[0, 100]}
|
tickLine={false}
|
||||||
/>
|
tickFormatter={(v) => `${v}%`}
|
||||||
<Tooltip
|
domain={[0, 100]}
|
||||||
content={({ active, payload, label }) => {
|
|
||||||
if (active && payload && payload.length) {
|
|
||||||
const val = payload[0]?.value as number
|
|
||||||
return (
|
|
||||||
<div className="bg-white border-2 border-amber-400 rounded-xl shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] px-4 py-3 text-sm">
|
|
||||||
<p className="font-bold text-black mb-1">{label}</p>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span className="w-2.5 h-2.5 rounded-full bg-amber-400 inline-block" />
|
|
||||||
<span>Prevalensi:</span>
|
|
||||||
<span className={`font-bold ${val >= 20 ? 'text-red-600' : 'text-emerald-600'}`}>{val}%</span>
|
|
||||||
</div>
|
|
||||||
<p className="text-[10px] text-gray-400 mt-1">
|
|
||||||
{val >= 20 ? '⚠️ Di atas ambang batas (20%)' : '✓ Di bawah ambang batas (20%)'}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{/* Reference line 20% */}
|
|
||||||
<Line
|
|
||||||
type="monotone"
|
|
||||||
dataKey="prevalensi"
|
|
||||||
stroke="#f59e0b"
|
|
||||||
strokeWidth={3}
|
|
||||||
dot={({ cx, cy, payload }) => (
|
|
||||||
<circle
|
|
||||||
key={`dot-${payload.month}`}
|
|
||||||
cx={cx} cy={cy} r={5}
|
|
||||||
fill={payload.prevalensi >= 20 ? '#ef4444' : '#10b981'}
|
|
||||||
stroke="white"
|
|
||||||
strokeWidth={2}
|
|
||||||
/>
|
/>
|
||||||
)}
|
<Tooltip
|
||||||
activeDot={{ r: 7, stroke: '#f59e0b', strokeWidth: 2 }}
|
content={({ active, payload, label }) => {
|
||||||
/>
|
if (active && payload && payload.length) {
|
||||||
</LineChart>
|
const val = payload[0]?.value as number
|
||||||
</ResponsiveContainer>
|
return (
|
||||||
|
<div className="bg-white border-2 border-amber-400 rounded-xl shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] px-4 py-3 text-sm">
|
||||||
|
<p className="font-bold text-black mb-1">{label} {selectedYear}</p>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="w-2.5 h-2.5 rounded-full bg-amber-400 inline-block" />
|
||||||
|
<span>Prevalensi:</span>
|
||||||
|
<span className={`font-bold ${val >= 20 ? 'text-red-600' : 'text-emerald-600'}`}>{val}%</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-[10px] text-gray-400 mt-1 uppercase font-bold tracking-tight">
|
||||||
|
{val >= 20 ? '⚠️ Di atas ambang batas (20%)' : '✓ Di bawah ambang batas (20%)'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Line
|
||||||
|
type="monotone"
|
||||||
|
dataKey="prevalensi"
|
||||||
|
stroke="#f59e0b"
|
||||||
|
strokeWidth={4}
|
||||||
|
dot={({ cx, cy, payload }) => (
|
||||||
|
<circle
|
||||||
|
key={`dot-${payload.month}`}
|
||||||
|
cx={cx} cy={cy} r={6}
|
||||||
|
fill={payload.prevalensi >= 20 ? '#ef4444' : '#10b981'}
|
||||||
|
stroke="white"
|
||||||
|
strokeWidth={2}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
activeDot={{ r: 8, stroke: '#f59e0b', strokeWidth: 2 }}
|
||||||
|
/>
|
||||||
|
</LineChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
<p className="text-[10px] text-amber-500 mt-2 text-center">
|
<p className="text-[10px] text-amber-500 mt-2 text-center">
|
||||||
● Merah = prevalensi ≥ 20% (tinggi) | ● Hijau = prevalensi < 20% (aman)
|
● Merah = prevalensi ≥ 20% (tinggi) | ● Hijau = prevalensi < 20% (aman)
|
||||||
|
|
|
||||||
12
app/page.tsx
12
app/page.tsx
|
|
@ -28,7 +28,7 @@ export default function LoginPage() {
|
||||||
</div>
|
</div>
|
||||||
<h1 className="text-2xl font-bold tracking-tight">HealthPortal</h1>
|
<h1 className="text-2xl font-bold tracking-tight">HealthPortal</h1>
|
||||||
</div>
|
</div>
|
||||||
|
<p className="text-gray-400 text-sm tracking-widest uppercase">SISTEM INFORMASI KESEHATAN</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 className="text-4xl font-bold mb-8">Panduan Login</h2>
|
<h2 className="text-4xl font-bold mb-8">Panduan Login</h2>
|
||||||
|
|
@ -84,8 +84,8 @@ export default function LoginPage() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right Side - Login Form */}
|
{/* Right Side - Login Form */}
|
||||||
<div className="w-full lg:w-1/2 bg-white text-black flex flex-col items-center justify-center p-8 relative">
|
<div className="w-full lg:w-1/2 bg-white text-black flex flex-col items-center justify-center p-4 md:p-8 relative">
|
||||||
<div className="w-full max-w-md bg-white p-8 rounded-2xl shadow-[0_20px_50px_rgba(0,0,0,0.1)] border border-gray-100 relative z-10 mb-10">
|
<div className="w-full max-w-md bg-white p-6 md:p-8 rounded-2xl shadow-[0_20px_50px_rgba(0,0,0,0.1)] border border-gray-100 relative z-10 mb-10">
|
||||||
{/* Decorative Elements for Card style */}
|
{/* Decorative Elements for Card style */}
|
||||||
<div className="absolute top-0 left-0 w-full h-2 bg-black rounded-t-2xl"></div>
|
<div className="absolute top-0 left-0 w-full h-2 bg-black rounded-t-2xl"></div>
|
||||||
|
|
||||||
|
|
@ -169,11 +169,11 @@ export default function LoginPage() {
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-6 text-xs font-bold text-gray-400 uppercase tracking-widest mt-auto mb-4">
|
<div className="flex flex-wrap justify-center gap-4 md:gap-6 text-xs font-bold text-gray-400 uppercase tracking-widest mt-auto mb-4 px-4 text-center">
|
||||||
<a href="#" className="hover:text-black transition-colors">Privasi</a>
|
<a href="#" className="hover:text-black transition-colors">Privasi</a>
|
||||||
<span className="text-gray-300">•</span>
|
<span className="hidden md:inline text-gray-300">•</span>
|
||||||
<a href="#" className="hover:text-black transition-colors">Ketentuan</a>
|
<a href="#" className="hover:text-black transition-colors">Ketentuan</a>
|
||||||
<span className="text-gray-300">•</span>
|
<span className="hidden md:inline text-gray-300">•</span>
|
||||||
<a href="#" className="hover:text-black transition-colors">Bantuan</a>
|
<a href="#" className="hover:text-black transition-colors">Bantuan</a>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[10px] text-gray-300 mb-6">
|
<div className="text-[10px] text-gray-300 mb-6">
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { MapPin, Phone, User, ExternalLink, Map as MapIcon, Star, Send, Loader2, Building2 } from 'lucide-react'
|
import { MapPin, Phone, User, ExternalLink, Map as MapIcon, Star, Send, Loader2, Building2, Calendar, Clock } from 'lucide-react'
|
||||||
import { supabase } from '@/lib/supabase'
|
import { supabase } from '@/lib/supabase'
|
||||||
import { submitReview } from '../action-review'
|
import { submitReview } from '../action-review'
|
||||||
import { showSwal } from '@/lib/swal'
|
import { showSwal } from '@/lib/swal'
|
||||||
|
|
@ -19,6 +19,12 @@ interface Posyandu {
|
||||||
nomor_hp: string | null
|
nomor_hp: string | null
|
||||||
jabatan: string | null
|
jabatan: string | null
|
||||||
}[]
|
}[]
|
||||||
|
jadwal?: {
|
||||||
|
id: string
|
||||||
|
tanggal: string
|
||||||
|
jam_mulai: string
|
||||||
|
jam_selesai: string
|
||||||
|
}[]
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Review {
|
interface Review {
|
||||||
|
|
@ -119,6 +125,79 @@ export default function PosyanduDetailClient({ data, userId }: Props) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Jadwal Pelaksanaan Card */}
|
||||||
|
<div className="bg-white rounded-3xl border-2 border-black shadow-[8px_8px_0px_0px_rgba(239,68,68,0.2)] overflow-hidden">
|
||||||
|
<div className="p-6 border-b border-gray-100 flex items-center justify-between">
|
||||||
|
<h3 className="text-base font-black flex items-center gap-3 lowercase">
|
||||||
|
<Calendar className="w-5 h-5 text-red-600" />
|
||||||
|
Jadwal Pelaksanaan
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div className="p-0">
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<div className="min-w-[500px] md:min-w-full">
|
||||||
|
<table className="w-full text-left border-collapse">
|
||||||
|
<thead className="bg-gray-50">
|
||||||
|
<tr>
|
||||||
|
<th className="px-6 py-4 text-[10px] font-black uppercase tracking-widest text-gray-400">Hari / Tanggal</th>
|
||||||
|
<th className="px-6 py-4 text-[10px] font-black uppercase tracking-widest text-gray-400">Waktu</th>
|
||||||
|
<th className="px-6 py-4 text-[10px] font-black uppercase tracking-widest text-gray-400">Status</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y divide-gray-100 italic">
|
||||||
|
{data.jadwal && data.jadwal.length > 0 ? (
|
||||||
|
data.jadwal.map((j) => {
|
||||||
|
const scheduleDate = new Date(j.tanggal)
|
||||||
|
const today = new Date()
|
||||||
|
today.setHours(0, 0, 0, 0)
|
||||||
|
const isUpcoming = scheduleDate >= today
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr key={j.id} className="hover:bg-gray-50 transition-colors">
|
||||||
|
<td className="px-6 py-4">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className="font-bold text-gray-900">
|
||||||
|
{scheduleDate.toLocaleDateString('id-ID', { weekday: 'long' })}
|
||||||
|
</span>
|
||||||
|
<span className="text-xs text-gray-500 font-semibold">
|
||||||
|
{scheduleDate.toLocaleDateString('id-ID', { day: 'numeric', month: 'long', year: 'numeric' })}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-4">
|
||||||
|
<div className="flex items-center gap-2 text-red-600 font-black">
|
||||||
|
<Clock className="w-3.5 h-3.5" />
|
||||||
|
<span className="text-sm">{j.jam_mulai.slice(0, 5)} - {j.jam_selesai.slice(0, 5)}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-4">
|
||||||
|
{isUpcoming ? (
|
||||||
|
<span className="px-2 py-1 bg-emerald-50 text-emerald-600 rounded text-[10px] font-black uppercase tracking-widest border border-emerald-100">
|
||||||
|
Mendatang
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="px-2 py-1 bg-gray-50 text-gray-400 rounded text-[10px] font-black uppercase tracking-widest border border-gray-100">
|
||||||
|
Selesai
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={3} className="px-6 py-10 text-center text-gray-400 italic text-xs font-semibold">
|
||||||
|
Belum ada jadwal pelaksanaan yang terdaftar.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Petugas Card */}
|
{/* Petugas Card */}
|
||||||
<div className="bg-white rounded-3xl border-2 border-black shadow-[8px_8px_0px_0px_rgba(147,51,234,0.3)] overflow-hidden">
|
<div className="bg-white rounded-3xl border-2 border-black shadow-[8px_8px_0px_0px_rgba(147,51,234,0.3)] overflow-hidden">
|
||||||
<div className="p-6 border-b border-gray-100 flex items-center justify-between">
|
<div className="p-6 border-b border-gray-100 flex items-center justify-between">
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,11 @@ export default async function PosyanduDetailPage({ params }: Props) {
|
||||||
.from('detail_posyandu')
|
.from('detail_posyandu')
|
||||||
.select(`
|
.select(`
|
||||||
*,
|
*,
|
||||||
petugas:petugas_posyandu_lokal(*)
|
petugas:petugas_posyandu_lokal(*),
|
||||||
|
jadwal:jadwal_posyandu(*)
|
||||||
`)
|
`)
|
||||||
.eq('id', id)
|
.eq('id', id)
|
||||||
|
.order('tanggal', { foreignTable: 'jadwal_posyandu', ascending: false })
|
||||||
.single()
|
.single()
|
||||||
|
|
||||||
if (error || !posyandu) {
|
if (error || !posyandu) {
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ export default async function UserDashboardPage() {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-white font-sans text-black flex flex-col">
|
<div className="min-h-screen bg-white font-sans text-black flex flex-col">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<header className="flex justify-between items-center px-8 py-6 border-b border-gray-100">
|
<header className="flex justify-between items-center px-4 md:px-8 py-4 md:py-6 border-b border-gray-100">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="p-2 rounded-full border border-black flex items-center justify-center">
|
<div className="p-2 rounded-full border border-black flex items-center justify-center">
|
||||||
<Activity className="h-5 w-5 text-black" />
|
<Activity className="h-5 w-5 text-black" />
|
||||||
|
|
@ -66,12 +66,12 @@ export default async function UserDashboardPage() {
|
||||||
<LogoutButton />
|
<LogoutButton />
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main className="p-8 max-w-6xl mx-auto">
|
<main className="p-4 md:p-8 max-w-6xl mx-auto w-full">
|
||||||
{/* Main Single Frame */}
|
{/* Main Single Frame */}
|
||||||
<div className="bg-white rounded-xl border border-black shadow-[8px_8px_0px_0px_rgba(0,0,0,1)] overflow-hidden">
|
<div className="bg-white rounded-xl border border-black shadow-[8px_8px_0px_0px_rgba(0,0,0,1)] overflow-hidden">
|
||||||
|
|
||||||
{/* Top Section - Welcome & Clock */}
|
{/* Top Section - Welcome & Clock */}
|
||||||
<div className="p-8 border-b border-black flex flex-col md:flex-row justify-between items-start md:items-center">
|
<div className="p-6 md:p-8 border-b border-black flex flex-col md:flex-row justify-between items-start md:items-center">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-2xl font-bold flex items-center gap-2 mb-2">
|
<h2 className="text-2xl font-bold flex items-center gap-2 mb-2">
|
||||||
<Activity className="h-6 w-6" />
|
<Activity className="h-6 w-6" />
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ export default async function UserPerkembanganPage() {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-white font-sans text-black flex flex-col">
|
<div className="min-h-screen bg-white font-sans text-black flex flex-col">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<header className="flex justify-between items-center px-8 py-6 border-b border-gray-100 bg-white">
|
<header className="flex justify-between items-center px-4 md:px-8 py-4 md:py-6 border-b border-gray-100 bg-white">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<Link href="/user-dashboard" className="group flex items-center gap-2 text-sm font-bold hover:text-gray-600 transition-colors">
|
<Link href="/user-dashboard" className="group flex items-center gap-2 text-sm font-bold hover:text-gray-600 transition-colors">
|
||||||
<div className="p-2.5 rounded-full border-2 border-black flex items-center justify-center group-hover:bg-black group-hover:text-white transition-all shadow-sm">
|
<div className="p-2.5 rounded-full border-2 border-black flex items-center justify-center group-hover:bg-black group-hover:text-white transition-all shadow-sm">
|
||||||
|
|
@ -114,10 +114,10 @@ export default async function UserPerkembanganPage() {
|
||||||
<LogoutButton />
|
<LogoutButton />
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main className="p-8 max-w-6xl mx-auto flex-1 w-full flex flex-col gap-10">
|
<main className="p-4 md:p-8 max-w-6xl mx-auto flex-1 w-full flex flex-col gap-6 md:gap-10">
|
||||||
|
|
||||||
{/* Hero Profile Section */}
|
{/* Hero Profile Section */}
|
||||||
<section className="bg-black text-white rounded-3xl p-8 lg:p-10 flex flex-col lg:flex-row items-center lg:items-end justify-between gap-8 shadow-[12px_12px_0px_0px_rgba(0,0,0,0.1)] relative overflow-hidden">
|
<section className="bg-black text-white rounded-3xl p-6 md:p-8 lg:p-10 flex flex-col lg:flex-row items-center lg:items-end justify-between gap-8 shadow-[12px_12px_0px_0px_rgba(0,0,0,0.1)] relative overflow-hidden">
|
||||||
{/* Background Decorative Element */}
|
{/* Background Decorative Element */}
|
||||||
<div className="absolute top-0 right-0 w-64 h-64 bg-white/5 rounded-full -mr-32 -mt-32 blur-3xl pointer-events-none" />
|
<div className="absolute top-0 right-0 w-64 h-64 bg-white/5 rounded-full -mr-32 -mt-32 blur-3xl pointer-events-none" />
|
||||||
|
|
||||||
|
|
@ -181,7 +181,7 @@ export default async function UserPerkembanganPage() {
|
||||||
|
|
||||||
{/* Chart Column */}
|
{/* Chart Column */}
|
||||||
<div className="lg:col-span-8">
|
<div className="lg:col-span-8">
|
||||||
<div className="bg-white rounded-3xl border-2 border-black p-8 shadow-[8px_8px_0px_0px_rgba(0,0,0,0.05)] h-full">
|
<div className="bg-white rounded-3xl border-2 border-black p-4 md:p-8 shadow-[8px_8px_0px_0px_rgba(0,0,0,0.05)] h-full">
|
||||||
<GrowthChart data={hasilData ?? []} />
|
<GrowthChart data={hasilData ?? []} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -190,7 +190,7 @@ export default async function UserPerkembanganPage() {
|
||||||
{/* Bottom Row: Full Width Table */}
|
{/* Bottom Row: Full Width Table */}
|
||||||
<div className="flex flex-col gap-6">
|
<div className="flex flex-col gap-6">
|
||||||
<div className="bg-white rounded-3xl border-2 border-gray-100 p-1">
|
<div className="bg-white rounded-3xl border-2 border-gray-100 p-1">
|
||||||
<div className="p-6 md:p-8">
|
<div className="p-4 md:p-8">
|
||||||
<StuntingTable data={hasilData ?? []} pengguna={pengguna} />
|
<StuntingTable data={hasilData ?? []} pengguna={pengguna} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -210,39 +210,43 @@ export function StuntingChart({ data, availableYears }: Props) {
|
||||||
<p className="font-bold">Data belum tersedia untuk tahun {selectedYear}</p>
|
<p className="font-bold">Data belum tersedia untuk tahun {selectedYear}</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<ResponsiveContainer width="100%" height={320}>
|
<div className="overflow-x-auto pb-2 -mx-2 px-2">
|
||||||
{chartType === 'bar' ? (
|
<div className="min-w-[700px] md:min-w-full">
|
||||||
<BarChart data={chartData} margin={{ top: 10, right: 10, left: -20, bottom: 0 }}>
|
<ResponsiveContainer width="100%" height={350}>
|
||||||
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#f1f5f9" />
|
{chartType === 'bar' ? (
|
||||||
<XAxis dataKey="month" axisLine={false} tickLine={false} tick={{ fontSize: 11, fontWeight: 700 }} />
|
<BarChart data={chartData} margin={{ top: 20, right: 10, left: -20, bottom: 0 }}>
|
||||||
<YAxis axisLine={false} tickLine={false} tick={{ fontSize: 11 }} />
|
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#f1f5f9" />
|
||||||
<Tooltip content={<CustomTooltip />} cursor={{ fill: '#f8fafc' }} />
|
<XAxis dataKey="month" axisLine={false} tickLine={false} tick={{ fontSize: 11, fontWeight: 700 }} />
|
||||||
<Legend wrapperStyle={{ paddingTop: 20, fontSize: 12, fontWeight: 800 }} iconType="circle" />
|
<YAxis axisLine={false} tickLine={false} tick={{ fontSize: 11 }} />
|
||||||
<Bar dataKey="stunting" name="Stunting" fill="#ef4444" radius={[4, 4, 0, 0]} />
|
<Tooltip content={<CustomTooltip />} cursor={{ fill: '#f8fafc' }} />
|
||||||
<Bar dataKey="normal" name="Normal" fill="#10b981" radius={[4, 4, 0, 0]} />
|
<Legend wrapperStyle={{ paddingTop: 20, fontSize: 12, fontWeight: 800 }} iconType="circle" />
|
||||||
</BarChart>
|
<Bar dataKey="stunting" name="Stunting" fill="#ef4444" radius={[4, 4, 0, 0]} />
|
||||||
) : (
|
<Bar dataKey="normal" name="Normal" fill="#10b981" radius={[4, 4, 0, 0]} />
|
||||||
<AreaChart data={chartData} margin={{ top: 10, right: 10, left: -20, bottom: 0 }}>
|
</BarChart>
|
||||||
<defs>
|
) : (
|
||||||
<linearGradient id="colorStunting" x1="0" y1="0" x2="0" y2="1">
|
<AreaChart data={chartData} margin={{ top: 20, right: 10, left: -20, bottom: 0 }}>
|
||||||
<stop offset="5%" stopColor="#ef4444" stopOpacity={0.1} />
|
<defs>
|
||||||
<stop offset="95%" stopColor="#ef4444" stopOpacity={0} />
|
<linearGradient id="colorStunting" x1="0" y1="0" x2="0" y2="1">
|
||||||
</linearGradient>
|
<stop offset="5%" stopColor="#ef4444" stopOpacity={0.1} />
|
||||||
<linearGradient id="colorNormal" x1="0" y1="0" x2="0" y2="1">
|
<stop offset="95%" stopColor="#ef4444" stopOpacity={0} />
|
||||||
<stop offset="5%" stopColor="#10b981" stopOpacity={0.1} />
|
</linearGradient>
|
||||||
<stop offset="95%" stopColor="#10b981" stopOpacity={0} />
|
<linearGradient id="colorNormal" x1="0" y1="0" x2="0" y2="1">
|
||||||
</linearGradient>
|
<stop offset="5%" stopColor="#10b981" stopOpacity={0.1} />
|
||||||
</defs>
|
<stop offset="95%" stopColor="#10b981" stopOpacity={0} />
|
||||||
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#f1f5f9" />
|
</linearGradient>
|
||||||
<XAxis dataKey="month" axisLine={false} tickLine={false} tick={{ fontSize: 11, fontWeight: 700 }} />
|
</defs>
|
||||||
<YAxis axisLine={false} tickLine={false} tick={{ fontSize: 11 }} />
|
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#f1f5f9" />
|
||||||
<Tooltip content={<CustomTooltip />} />
|
<XAxis dataKey="month" axisLine={false} tickLine={false} tick={{ fontSize: 11, fontWeight: 700 }} />
|
||||||
<Legend wrapperStyle={{ paddingTop: 20, fontSize: 12, fontWeight: 800 }} iconType="circle" />
|
<YAxis axisLine={false} tickLine={false} tick={{ fontSize: 11 }} />
|
||||||
<Area type="monotone" dataKey="stunting" name="Stunting" stroke="#ef4444" strokeWidth={3} fillOpacity={1} fill="url(#colorStunting)" />
|
<Tooltip content={<CustomTooltip />} />
|
||||||
<Area type="monotone" dataKey="normal" name="Normal" stroke="#10b981" strokeWidth={3} fillOpacity={1} fill="url(#colorNormal)" />
|
<Legend wrapperStyle={{ paddingTop: 20, fontSize: 12, fontWeight: 800 }} iconType="circle" />
|
||||||
</AreaChart>
|
<Area type="monotone" dataKey="stunting" name="Stunting" stroke="#ef4444" strokeWidth={3} fillOpacity={1} fill="url(#colorStunting)" dot={{ r: 4, fill: '#ef4444', strokeWidth: 2, stroke: 'white' }} />
|
||||||
)}
|
<Area type="monotone" dataKey="normal" name="Normal" stroke="#10b981" strokeWidth={3} fillOpacity={1} fill="url(#colorNormal)" dot={{ r: 4, fill: '#10b981', strokeWidth: 2, stroke: 'white' }} />
|
||||||
</ResponsiveContainer>
|
</AreaChart>
|
||||||
|
)}
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -258,48 +262,53 @@ export function StuntingChart({ data, availableYears }: Props) {
|
||||||
<p className="text-sm font-bold opacity-30">Belum ada data prevalensi</p>
|
<p className="text-sm font-bold opacity-30">Belum ada data prevalensi</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<ResponsiveContainer width="100%" height={240}>
|
<div className="overflow-x-auto pb-2 -mx-2 px-2">
|
||||||
<LineChart data={chartData} margin={{ top: 10, right: 20, left: -20, bottom: 0 }}>
|
<div className="min-w-[700px] md:min-w-full">
|
||||||
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#fef3c7" />
|
<ResponsiveContainer width="100%" height={260}>
|
||||||
<XAxis dataKey="month" axisLine={false} tickLine={false} tick={{ fontSize: 11, fontWeight: 700 }} />
|
<LineChart data={chartData} margin={{ top: 10, right: 20, left: -20, bottom: 0 }}>
|
||||||
<YAxis axisLine={false} tickLine={false} tick={{ fontSize: 11 }} domain={[0, 100]} />
|
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#fef3c7" />
|
||||||
<Tooltip
|
<XAxis dataKey="month" axisLine={false} tickLine={false} tick={{ fontSize: 11, fontWeight: 700 }} />
|
||||||
content={({ active, payload, label }) => {
|
<YAxis axisLine={false} tickLine={false} tick={{ fontSize: 11 }} domain={[0, 100]} />
|
||||||
if (active && payload && payload.length) {
|
<Tooltip
|
||||||
const val = payload[0].value as number
|
content={({ active, payload, label }) => {
|
||||||
return (
|
if (active && payload && payload.length) {
|
||||||
<div className="bg-white border-2 border-amber-500 rounded-xl shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] px-4 py-3 text-sm">
|
const val = payload[0].value as number
|
||||||
<p className="font-black text-black mb-1">{label} {selectedYear}</p>
|
return (
|
||||||
<div className="flex items-center gap-2">
|
<div className="bg-white border-2 border-amber-500 rounded-xl shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] px-4 py-3 text-sm">
|
||||||
<span className={`w-3 h-3 rounded-full ${val >= 20 ? 'bg-red-500' : 'bg-emerald-500'}`} />
|
<p className="font-black text-black mb-1">{label} {selectedYear}</p>
|
||||||
<span className="font-black text-lg">{val}%</span>
|
<div className="flex items-center gap-2">
|
||||||
</div>
|
<span className={`w-3 h-3 rounded-full ${val >= 20 ? 'bg-red-500' : 'bg-emerald-500'}`} />
|
||||||
<p className="text-[10px] font-bold mt-1 uppercase tracking-tight">
|
<span className="font-black text-lg">{val}%</span>
|
||||||
{val >= 20 ? '🛑 Tinggi' : '✅ Aman'}
|
</div>
|
||||||
</p>
|
<p className="text-[10px] font-bold mt-1 uppercase tracking-tight">
|
||||||
</div>
|
{val >= 20 ? '🛑 Tinggi' : '✅ Aman'}
|
||||||
)
|
</p>
|
||||||
}
|
</div>
|
||||||
return null
|
)
|
||||||
}}
|
}
|
||||||
/>
|
return null
|
||||||
<Line
|
}}
|
||||||
type="monotone"
|
|
||||||
dataKey="prevalensi"
|
|
||||||
stroke="#f59e0b"
|
|
||||||
strokeWidth={4}
|
|
||||||
dot={({ cx, cy, payload }) => (
|
|
||||||
<circle
|
|
||||||
key={`dot-${payload.month}`}
|
|
||||||
cx={cx} cy={cy} r={6}
|
|
||||||
fill={payload.prevalensi >= 20 ? '#ef4444' : '#10b981'}
|
|
||||||
stroke="white"
|
|
||||||
strokeWidth={2}
|
|
||||||
/>
|
/>
|
||||||
)}
|
<Line
|
||||||
/>
|
type="monotone"
|
||||||
</LineChart>
|
dataKey="prevalensi"
|
||||||
</ResponsiveContainer>
|
stroke="#f59e0b"
|
||||||
|
strokeWidth={4}
|
||||||
|
dot={({ cx, cy, payload }) => (
|
||||||
|
<circle
|
||||||
|
key={`dot-${payload.month}`}
|
||||||
|
cx={cx} cy={cy} r={6}
|
||||||
|
fill={payload.prevalensi >= 20 ? '#ef4444' : '#10b981'}
|
||||||
|
stroke="white"
|
||||||
|
strokeWidth={2}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
activeDot={{ r: 8, stroke: '#f59e0b', strokeWidth: 2 }}
|
||||||
|
/>
|
||||||
|
</LineChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ export function DashboardFooter() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<footer className="bg-black text-white py-8 px-8 mt-auto w-full">
|
<footer className="bg-black text-white py-6 md:py-8 px-4 md:px-8 mt-auto w-full">
|
||||||
<div className="max-w-6xl mx-auto flex flex-col items-center gap-4 text-center">
|
<div className="max-w-6xl mx-auto flex flex-col items-center gap-4 text-center">
|
||||||
|
|
||||||
<div className="flex flex-col items-center gap-2">
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
|
|
||||||
|
|
@ -36,13 +36,13 @@ export function FeatureCard({ title, description, icon: Icon, href, color, class
|
||||||
colorStyles[color]
|
colorStyles[color]
|
||||||
)}>
|
)}>
|
||||||
<div className="flex flex-col items-center text-center h-full">
|
<div className="flex flex-col items-center text-center h-full">
|
||||||
<div className={cn("w-16 h-16 rounded-full flex items-center justify-center mb-4 border-2 transition-transform group-hover:scale-110", iconBgStyles[color])}>
|
<div className={cn("w-12 h-12 sm:w-16 sm:h-16 rounded-full flex items-center justify-center mb-4 border-2 transition-transform group-hover:scale-110", iconBgStyles[color])}>
|
||||||
<Icon className="w-8 h-8" />
|
<Icon className="w-6 h-6 sm:w-8 sm:h-8" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 className="text-xl font-bold mb-2 text-black">{title}</h3>
|
<h3 className="text-lg sm:text-xl font-bold mb-2 text-black">{title}</h3>
|
||||||
{description && (
|
{description && (
|
||||||
<p className="text-gray-500 text-sm leading-relaxed">{description}</p>
|
<p className="text-gray-500 text-xs sm:text-sm leading-relaxed">{description}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue