205 lines
11 KiB
TypeScript
205 lines
11 KiB
TypeScript
import { cookies } from 'next/headers'
|
|
import { redirect } from 'next/navigation'
|
|
import { supabase } from '@/lib/supabase'
|
|
import { LogoutButton } from '@/components/logout-button'
|
|
import { ArrowLeft, User, MapPin, Phone, Baby, Calendar, Mars, Venus } from 'lucide-react'
|
|
import Link from 'next/link'
|
|
import { StuntingTable } from './StuntingTable'
|
|
import { GrowthChart } from './GrowthChart'
|
|
|
|
function ReadField({
|
|
icon,
|
|
label,
|
|
value,
|
|
accent,
|
|
}: {
|
|
icon: React.ReactNode
|
|
label: string
|
|
value: string | null | undefined
|
|
accent?: 'blue' | 'pink'
|
|
}) {
|
|
return (
|
|
<div className="flex flex-col gap-2 p-5 rounded-2xl border-2 border-gray-100 bg-gray-50/30 hover:border-black transition-all group">
|
|
<div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-gray-400 group-hover:text-black transition-colors">
|
|
<span className="opacity-50 group-hover:opacity-100">{icon}</span>
|
|
{label}
|
|
</div>
|
|
<p className={`text-base font-black ${accent === 'blue' ? 'text-blue-600' : accent === 'pink' ? 'text-pink-600' : 'text-black'}`}>
|
|
{value || <span className="text-gray-300 font-bold italic opacity-50">Belum diisi</span>}
|
|
</p>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default async function UserPerkembanganPage() {
|
|
const cookieStore = await cookies()
|
|
const sessionCookie = cookieStore.get('user_session')
|
|
if (!sessionCookie) redirect('/')
|
|
|
|
const session = JSON.parse(sessionCookie.value)
|
|
if (session.role !== 'user' && session.role !== 'admin') redirect('/dashboard')
|
|
|
|
// Fetch this specific user's child data
|
|
const { data: pengguna, error } = await supabase
|
|
.from('akun_balita')
|
|
.select('id, nama_orang_tua, alamat, no_whatsapp, nama_anak, jenis_kelamin, tanggal_lahir, username, password')
|
|
.eq('id', session.id)
|
|
.single()
|
|
|
|
if (error || !pengguna) {
|
|
return (
|
|
<div className="min-h-screen flex flex-col items-center justify-center gap-4 bg-white text-black p-8 text-center">
|
|
<div className="w-20 h-20 rounded-full bg-red-50 flex items-center justify-center text-red-500 mb-2 border-2 border-red-100">
|
|
<User className="w-10 h-10" />
|
|
</div>
|
|
<h2 className="text-2xl font-black uppercase tracking-tight">Data Tidak Ditemukan</h2>
|
|
<p className="max-w-xs text-gray-500 text-sm font-medium">Maaf, kami tidak dapat menemukan profil balita Anda. Silakan hubungi admin di posyandu terdekat.</p>
|
|
<Link href="/user-dashboard" className="px-6 py-3 bg-black text-white text-xs font-black rounded-xl uppercase tracking-widest shadow-[4px_4px_0px_0px_rgba(0,0,0,0.1)]">Kembali ke Dashboard</Link>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// Fetch measurement history
|
|
const { data: hasilData } = await supabase
|
|
.from('hasil_stunting_balita')
|
|
.select('id, tinggi_badan, berat_badan, status_stunting, pesan_ai, tanggal_upload, nama_posyandu')
|
|
.eq('id_balita', pengguna.id)
|
|
.order('tanggal_upload', { ascending: false })
|
|
|
|
const formatDate = (d: string | null) => {
|
|
if (!d) return null
|
|
return new Date(d).toLocaleDateString('id-ID', {
|
|
day: 'numeric', month: 'long', year: 'numeric'
|
|
})
|
|
}
|
|
|
|
const isLaki = pengguna.jenis_kelamin?.toLowerCase().includes('laki')
|
|
|
|
const calculateAge = (birthDateStr: string | null) => {
|
|
if (!birthDateStr) return null
|
|
const birthDate = new Date(birthDateStr)
|
|
const today = new Date()
|
|
|
|
let months = (today.getFullYear() - birthDate.getFullYear()) * 12 + (today.getMonth() - birthDate.getMonth())
|
|
let days = today.getDate() - birthDate.getDate()
|
|
|
|
if (days < 0) {
|
|
months -= 1
|
|
const lastMonth = new Date(today.getFullYear(), today.getMonth(), 0)
|
|
days += lastMonth.getDate()
|
|
}
|
|
|
|
return { months, days }
|
|
}
|
|
|
|
const age = calculateAge(pengguna.tanggal_lahir)
|
|
|
|
return (
|
|
<div className="min-h-screen bg-white font-sans text-black flex flex-col">
|
|
{/* Header */}
|
|
<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">
|
|
<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">
|
|
<ArrowLeft className="h-4 w-4" />
|
|
</div>
|
|
<span className="hidden md:block uppercase tracking-widest text-[10px] font-black">Dashboard</span>
|
|
</Link>
|
|
<div className="h-8 w-px bg-gray-200 mx-1 hidden md:block"></div>
|
|
<div className="flex flex-col justify-center">
|
|
<h1 className="text-xl font-black leading-none uppercase tracking-tight">Perkembangan Anak</h1>
|
|
<p className="text-[10px] text-gray-400 font-bold tracking-widest uppercase mt-1">Laporan Rutin Posyandu</p>
|
|
</div>
|
|
</div>
|
|
<LogoutButton />
|
|
</header>
|
|
|
|
<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 */}
|
|
<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 */}
|
|
<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="flex flex-col lg:flex-row items-center lg:items-center gap-6 relative z-10 w-full lg:w-auto">
|
|
<div className="w-24 h-24 rounded-full bg-white text-black flex items-center justify-center text-4xl font-black border-4 border-gray-800 shadow-xl overflow-hidden shrink-0">
|
|
{pengguna.nama_anak?.charAt(0).toUpperCase() ?? '?'}
|
|
</div>
|
|
<div className="text-center lg:text-left">
|
|
<h2 className="text-3xl lg:text-4xl font-black tracking-tight mb-2">{pengguna.nama_anak}</h2>
|
|
<div className="flex flex-wrap items-center justify-center lg:justify-start gap-2">
|
|
<span className={`flex items-center gap-1.5 px-3 py-1 rounded-full text-[10px] font-black uppercase tracking-widest border border-white/20 bg-white/10 ${isLaki ? 'text-blue-200' : 'text-pink-200'}`}>
|
|
{isLaki ? <Mars className="w-3 h-3" /> : <Venus className="w-3 h-3" />}
|
|
{pengguna.jenis_kelamin}
|
|
</span>
|
|
<span className="flex items-center gap-1.5 px-3 py-1 rounded-full text-[10px] font-black uppercase tracking-widest border border-white/20 bg-white/10 text-gray-300">
|
|
<Calendar className="w-3 h-3" />
|
|
Lahir {formatDate(pengguna.tanggal_lahir)}
|
|
</span>
|
|
{age && (
|
|
<span className="flex items-center gap-1.5 px-3 py-1 rounded-full text-[10px] font-black uppercase tracking-widest border border-emerald-400/30 bg-emerald-400/10 text-emerald-400">
|
|
<Baby className="w-3 h-3" />
|
|
Umur {age.months} Bulan {age.days} Hari
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex gap-4 w-full lg:w-auto">
|
|
<div className="flex-1 lg:flex-none bg-white/10 border border-white/20 rounded-2xl p-4 text-center min-w-[100px]">
|
|
<p className="text-[10px] font-black uppercase text-gray-400 tracking-tighter mb-1">Total Cek</p>
|
|
<p className="text-2xl font-black">{hasilData?.length ?? 0}</p>
|
|
</div>
|
|
<div className="flex-1 lg:flex-none bg-white/10 border border-white/20 rounded-2xl p-4 text-center min-w-[100px]">
|
|
<p className="text-[10px] font-black uppercase text-gray-400 tracking-tighter mb-1">Status</p>
|
|
<p className="text-2xl font-black text-emerald-400">AKTIF</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<div className="flex flex-col gap-10">
|
|
{/* Top Row: Info & Chart */}
|
|
<div className="grid grid-cols-1 lg:grid-cols-12 gap-10">
|
|
{/* Info Column */}
|
|
<div className="lg:col-span-4 flex flex-col gap-6">
|
|
<div className="flex items-center gap-2">
|
|
<User className="w-4 h-4 text-black" />
|
|
<h3 className="text-xs font-black uppercase tracking-[0.2em] text-black">Informasi Keluarga</h3>
|
|
</div>
|
|
<div className="flex flex-col gap-4">
|
|
<ReadField icon={<User className="w-3.5 h-3.5" />} label="Nama Ibu / Orang Tua" value={pengguna.nama_orang_tua} />
|
|
<ReadField icon={<MapPin className="w-3.5 h-3.5" />} label="Alamat Domisili" value={pengguna.alamat} />
|
|
<ReadField icon={<Phone className="w-3.5 h-3.5" />} label="Kontak WhatsApp" value={pengguna.no_whatsapp} />
|
|
</div>
|
|
|
|
<div className="p-6 rounded-3xl bg-amber-50 border-2 border-amber-100">
|
|
<h4 className="text-xs font-black uppercase tracking-widest text-amber-700 mb-2 italic">Tips Sehat ✨</h4>
|
|
<p className="text-[11px] text-amber-900 leading-relaxed font-medium">Pastikan si kecil mendapatkan asupan gizi seimbang dan rutin mengikuti pemeriksaan di posyandu setiap bulan.</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Chart Column */}
|
|
<div className="lg:col-span-8">
|
|
<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 ?? []} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Bottom Row: Full Width Table */}
|
|
<div className="flex flex-col gap-6">
|
|
<div className="bg-white rounded-3xl border-2 border-gray-100 p-1">
|
|
<div className="p-4 md:p-8">
|
|
<StuntingTable data={hasilData ?? []} pengguna={pengguna} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
|
|
</div>
|
|
)
|
|
}
|