TKK_E32231405/app/dashboard/kelola-data/[id]/HasilStuntingTable.tsx

191 lines
9.1 KiB
TypeScript

'use client'
import { useState, useMemo } from 'react'
import { Activity, ChevronDown } from 'lucide-react'
import { CetakPDFButton } from './CetakPDFButton'
interface HasilStunting {
id: number
tinggi_badan: number | null
berat_badan: number | null
z_score: number | null
status_stunting: boolean | null
pesan_ai: string | null
tanggal_upload: string | null
nama_posyandu: string | null
}
interface Pengguna {
nama_orang_tua: string
alamat: string | null
nama_anak: string
jenis_kelamin: string | null
tanggal_lahir: string | null
}
interface Props {
data: HasilStunting[]
pengguna: Pengguna
}
const START_YEAR = 2026
export function HasilStuntingTable({ data, pengguna }: Props) {
const availableYears = useMemo(() => {
const currentYear = new Date().getFullYear()
const dataYears = Array.from(
new Set(data.map(d => d.tanggal_upload ? new Date(d.tanggal_upload).getFullYear() : null).filter(Boolean))
) as number[]
const maxYear = Math.max(currentYear, ...dataYears, START_YEAR)
return Array.from(
new Set([
...Array.from({ length: maxYear - START_YEAR + 1 }, (_, i) => START_YEAR + i),
...dataYears,
])
).filter(y => y >= START_YEAR).sort((a, b) => a - b)
}, [data])
const [selectedYear, setSelectedYear] = useState<number>(availableYears[0] ?? START_YEAR)
const filtered = useMemo(() => {
return data.filter(d => {
if (!d.tanggal_upload) return false
return new Date(d.tanggal_upload).getFullYear() === selectedYear
})
}, [data, selectedYear])
const formatDate = (d: string | null) => {
if (!d) return '-'
return new Date(d).toLocaleDateString('id-ID', {
day: 'numeric', month: 'short', year: 'numeric'
})
}
return (
<div className="flex flex-col gap-4">
{/* Section Header + Filter */}
<div className="flex items-center justify-between flex-wrap gap-3">
<div className="flex items-center gap-2">
<Activity className="w-4 h-4 text-gray-500" />
<p className="text-xs font-bold uppercase tracking-widest text-gray-500">Riwayat Hasil Pengukuran</p>
</div>
<div className="flex items-center gap-2">
<span className="text-xs font-bold uppercase tracking-widest text-gray-400">Periode:</span>
<div className="relative">
<select
value={selectedYear}
onChange={e => setSelectedYear(Number(e.target.value))}
className="appearance-none border-2 border-black rounded-lg pl-3 pr-8 py-1.5 text-sm font-bold bg-white focus:outline-none shadow-[3px_3px_0px_0px_rgba(0,0,0,1)] cursor-pointer"
>
{availableYears.map(year => (
<option key={year} value={year}>{year}</option>
))}
</select>
<ChevronDown className="absolute right-2 top-1/2 -translate-y-1/2 w-3.5 h-3.5 pointer-events-none text-gray-500" />
</div>
<span className="text-xs text-gray-400 font-semibold">{filtered.length} data</span>
</div>
</div>
{/* Table */}
<div className="rounded-xl border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] overflow-hidden">
{/* Header */}
<div className="grid grid-cols-[36px_88px_88px_88px_100px_1fr_108px_110px_90px] bg-black text-white px-4 py-3 text-[10px] font-bold uppercase tracking-widest">
<span className="text-center text-gray-500">#</span>
<span className="text-center">Panjang</span>
<span className="text-center">Berat</span>
<span className="text-center">Z-Score</span>
<span className="text-center">Status</span>
<span>Pesan AI</span>
<span className="text-center">Posyandu</span>
<span className="text-center">Tgl Upload</span>
<span className="text-center">Aksi</span>
</div>
{filtered.length === 0 ? (
<div className="flex flex-col items-center justify-center py-12 gap-2 text-gray-300">
<Activity className="w-10 h-10 opacity-30" />
<p className="text-sm text-gray-400 font-semibold">Tidak ada data untuk tahun {selectedYear}</p>
</div>
) : (
filtered.map((row, idx) => {
const isStunting = row.status_stunting === true
return (
<div
key={row.id}
className={`grid grid-cols-[36px_88px_88px_88px_100px_1fr_108px_110px_90px] items-center px-4 py-3 border-b border-gray-100 text-sm transition-colors ${idx % 2 === 0 ? 'bg-white' : 'bg-gray-50/40'} hover:bg-orange-50/30`}
>
{/* Index */}
<div className="flex justify-center">
<span className="text-xs text-gray-400 font-bold">{idx + 1}</span>
</div>
{/* Panjang Badan */}
<div className="text-center">
<span className="font-bold">{row.tinggi_badan ?? '-'}</span>
{row.tinggi_badan && <span className="text-xs text-gray-400 ml-0.5">cm</span>}
</div>
{/* Berat Badan */}
<div className="text-center">
<span className="font-bold">{row.berat_badan ?? '-'}</span>
{row.berat_badan && <span className="text-xs text-gray-400 ml-0.5">kg</span>}
</div>
{/* Z-Score */}
<div className="text-center">
<span className="font-bold">{row.z_score ?? '-'}</span>
{row.z_score !== null && <span className="text-xs text-gray-400 ml-0.5">SD</span>}
</div>
{/* Status Stunting */}
<div className="flex justify-center">
{row.status_stunting === null ? (
<span className="text-xs text-gray-300"></span>
) : (
<span className={`inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-bold border ${isStunting
? 'bg-red-50 border-red-200 text-red-700'
: 'bg-emerald-50 border-emerald-200 text-emerald-700'
}`}>
{isStunting ? '⚠ Stunting' : '✓ Normal'}
</span>
)}
</div>
{/* Pesan AI — truncated */}
<div className="pr-2">
{row.pesan_ai ? (
<p className="text-xs text-gray-600 leading-relaxed">
{row.pesan_ai.length > 80
? row.pesan_ai.slice(0, 80) + '..........'
: row.pesan_ai}
</p>
) : (
<span className="text-xs text-gray-300 italic">Tidak ada pesan</span>
)}
</div>
{/* Nama Posyandu */}
<div className="text-center">
<span className="text-xs text-gray-600">{row.nama_posyandu ?? '-'}</span>
</div>
{/* Tanggal Upload */}
<div className="text-center">
<span className="text-xs text-gray-500">{formatDate(row.tanggal_upload)}</span>
</div>
{/* Aksi: Cetak PDF */}
<div className="flex justify-center">
<CetakPDFButton row={row} allData={data} pengguna={pengguna} />
</div>
</div>
)
})
)}
</div>
</div>
)
}