TKK_E32231405/app/user-dashboard/lokasi-posyandu/[id]/PosyanduDetailClient.tsx

350 lines
20 KiB
TypeScript

'use client'
import { useState, useEffect } from 'react'
import { MapPin, Phone, User, ExternalLink, Map as MapIcon, Star, Send, Loader2, Building2, Calendar, Clock } from 'lucide-react'
import { supabase } from '@/lib/supabase'
import { submitReview } from '../action-review'
import { showSwal } from '@/lib/swal'
interface Posyandu {
id: string
nama_posyandu: string
alamat: string
kontak: string | null
latitude: number | null
longitude: number | null
link_google_maps: string | null
petugas?: {
nama_petugas: string
nomor_hp: string | null
jabatan: string | null
}[]
jadwal?: {
id: string
tanggal: string
jam_mulai: string
jam_selesai: string
}[]
}
interface Review {
id: string
rating: number
ulasan: string
nama_pengulas: string
created_at: string
}
interface Props {
data: Posyandu
userId: string
}
export default function PosyanduDetailClient({ data, userId }: Props) {
const [reviews, setReviews] = useState<Review[]>([])
const [isLoadingReviews, setIsLoadingReviews] = useState(true)
const [rating, setRating] = useState(5)
const [comment, setComment] = useState('')
const [isSubmitting, setIsSubmitting] = useState(false)
useEffect(() => {
fetchReviews()
}, [data.id])
async function fetchReviews() {
setIsLoadingReviews(true)
const { data: reviewData, error } = await supabase
.from('ulasan_posyandu')
.select('*')
.eq('posyandu_id', data.id)
.order('created_at', { ascending: false })
if (!error && reviewData) {
setReviews(reviewData as any)
}
setIsLoadingReviews(false)
}
async function handleSubmit(e: React.FormEvent) {
e.preventDefault()
if (!comment.trim()) {
showSwal.error('Ops!', 'Komentar tidak boleh kosong.')
return
}
setIsSubmitting(true)
const result = await submitReview({
posyandu_id: data.id,
nama_pengulas: 'Orang Tua', // Or fetch the actual name if available in session
rating,
comment: comment
})
if (result.success) {
showSwal.success('Berhasil!', 'Review Anda telah terkirim.')
setComment('')
setRating(5)
fetchReviews()
} else {
showSwal.error('Gagal!', result.error || 'Terjadi kesalahan saat mengirim ulasan.')
}
setIsSubmitting(false)
}
const mapSrc = `https://maps.google.com/maps?q=${encodeURIComponent(data.alamat)}&z=15&output=embed`
return (
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Left Column - Information */}
<div className="lg:col-span-2 flex flex-col gap-8">
{/* Basic Info Card */}
<div className="bg-white rounded-3xl border-2 border-black shadow-[8px_8px_0px_0px_rgba(0,0,0,1)] overflow-hidden">
<div className="p-8 bg-purple-600 border-b-2 border-black">
<h3 className="text-2xl font-black text-white flex items-center gap-3 uppercase">
<Building2 className="w-7 h-7" />
Informasi Utama
</h3>
</div>
<div className="p-8 flex flex-col gap-6">
<div className="flex flex-col gap-2">
<span className="text-[10px] font-black uppercase text-gray-400 tracking-widest flex items-center gap-2">
<MapPin className="w-3 h-3" />
Alamat Posyandu
</span>
<p className="text-lg font-bold leading-relaxed">{data.alamat}</p>
</div>
{data.kontak && (
<div className="flex flex-col gap-2">
<span className="text-[10px] font-black uppercase text-gray-400 tracking-widest flex items-center gap-2">
<Phone className="w-3 h-3" />
Kontak Layanan
</span>
<p className="text-lg font-bold">{data.kontak}</p>
</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 */}
<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">
<h3 className="text-base font-black flex items-center gap-3">
<User className="w-5 h-5 text-purple-600" />
Petugas Bertugas
</h3>
<span className="text-xs font-bold text-purple-600 bg-purple-50 px-3 py-1 rounded-full border border-purple-100 uppercase tracking-widest">
{data.petugas?.length || 0} Orang
</span>
</div>
<div className="divide-y divide-gray-100">
{data.petugas && data.petugas.length > 0 ? (
data.petugas.map((pt: any, i) => (
<div key={i} className="p-6 flex items-center justify-between hover:bg-gray-50/50 transition-all">
<div className="flex items-center gap-4">
<div className="w-12 h-12 rounded-xl bg-purple-100 text-purple-600 flex items-center justify-center font-black text-xl">
{pt.nama_petugas?.[0]}
</div>
<div>
<p className="font-bold text-gray-900">{pt.nama_petugas}</p>
<p className="text-xs text-gray-500 font-semibold uppercase tracking-wider">{pt.jabatan || 'Anggota'}</p>
</div>
</div>
{pt.nomor_hp && (
<div className="flex items-center gap-1.5 px-3 py-1.5 bg-emerald-50 text-emerald-600 rounded-lg text-xs font-bold">
<Phone className="w-3 h-3" />
{pt.nomor_hp}
</div>
)}
</div>
))
) : (
<div className="p-12 text-center text-gray-400 italic text-sm font-semibold">
Belum ada data petugas terdaftar di posyandu ini.
</div>
)}
</div>
</div>
{/* Reviews List */}
<div className="flex flex-col gap-6 mt-4">
<h3 className="font-black text-xl flex items-center justify-between">
<span className="flex items-center gap-2">
<Star className="w-6 h-6 text-yellow-500" />
Ulasan Pengguna
</span>
<span className="text-xs font-bold bg-gray-100 px-3 py-1 rounded-full text-gray-500">{reviews.length} REVIEW</span>
</h3>
<div className="grid grid-cols-1 gap-4">
{isLoadingReviews ? (
<div className="flex flex-col items-center py-12 gap-2 text-gray-300">
<Loader2 className="w-10 h-10 animate-spin" />
<p className="text-sm font-bold">Memuat ulasan masyarakat...</p>
</div>
) : reviews.length > 0 ? (
reviews.map((rev) => (
<div key={rev.id} className="p-6 bg-white border-2 border-black rounded-2xl shadow-[4px_4px_0px_0px_rgba(0,0,0,0.05)] flex flex-col gap-3">
<div className="flex justify-between items-center">
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-blue-100 text-blue-600 rounded-full flex items-center justify-center text-[10px] font-black">
{rev.nama_pengulas?.[0] || 'A'}
</div>
<p className="text-sm font-black text-gray-900">{rev.nama_pengulas || 'Orang Tua'}</p>
</div>
<div className="flex gap-0.5">
{[...Array(5)].map((_, i) => (
<Star key={i} className={`w-3 h-3 ${i < rev.rating ? 'text-yellow-400 fill-current' : 'text-gray-200'}`} />
))}
</div>
</div>
<p className="text-gray-600 font-semibold italic">"{rev.ulasan}"</p>
<p className="text-[10px] text-gray-400 font-black uppercase tracking-widest">{new Date(rev.created_at).toLocaleDateString('id-ID', { day: 'numeric', month: 'long', year: 'numeric' })}</p>
</div>
))
) : (
<div className="py-20 text-center text-gray-300 flex flex-col items-center gap-4 border-4 border-dashed border-gray-100 rounded-[32px]">
<Star className="w-12 h-12 opacity-10" />
<p className="font-bold italic">Belum ada ulasan untuk lokasi ini.</p>
</div>
)}
</div>
</div>
</div>
{/* Right Column - Map & Review Form */}
<div className="flex flex-col gap-8">
{/* Map Card */}
<div className="bg-white rounded-3xl border-2 border-black shadow-[8px_8px_0px_0px_rgba(59,130,246,0.2)] overflow-hidden">
<div className="p-6 border-b border-gray-100 flex items-center justify-between bg-blue-50/30">
<h3 className="text-base font-black flex items-center gap-3">
<MapIcon className="w-5 h-5 text-blue-600" />
Lokasi Maps
</h3>
{data.link_google_maps && (
<a href={data.link_google_maps} target="_blank" rel="noopener noreferrer" className="p-2 bg-white text-blue-600 border border-blue-200 rounded-lg hover:shadow-md transition-all">
<ExternalLink className="w-4 h-4" />
</a>
)}
</div>
<div className="aspect-square w-full bg-gray-100 relative">
<iframe width="100%" height="100%" style={{ border: 0 }} loading="lazy" allowFullScreen src={mapSrc}></iframe>
</div>
</div>
{/* Review Form Card */}
<div className="bg-black text-white rounded-[32px] p-8 flex flex-col gap-6 shadow-[12px_12px_0px_0px_rgba(147,51,234,0.4)] relative overflow-hidden">
<div className="z-10">
<h3 className="text-xl font-black text-purple-400 uppercase tracking-tight mb-2">Berikan Ulasan</h3>
<p className="text-xs text-gray-400 font-semibold mb-6">Ceritakan pengalaman Bapak/Ibu mengenai pelayanan di posyandu ini.</p>
<form onSubmit={handleSubmit} className="flex flex-col gap-5">
<div className="flex gap-3">
{[1, 2, 3, 4, 5].map((star) => (
<button
key={star}
type="button"
onClick={() => setRating(star)}
className={`transition-all duration-200 ${rating >= star ? 'text-yellow-400 scale-125 drop-shadow-[0_0_8px_rgba(250,204,21,0.5)]' : 'text-gray-700 hover:text-gray-500'}`}
>
<Star className="w-8 h-8 fill-current" />
</button>
))}
</div>
<textarea
value={comment}
onChange={(e) => setComment(e.target.value)}
placeholder="Tuliskan ulasan anda disini..."
className="w-full bg-[#111] border-2 border-gray-800 rounded-2xl p-4 text-sm font-semibold outline-none focus:border-purple-500 transition-all min-h-[120px] resize-none"
></textarea>
<button
type="submit"
disabled={isSubmitting}
className="w-full bg-purple-600 hover:bg-purple-700 text-white font-black py-4 rounded-2xl flex items-center justify-center gap-3 transition-all active:scale-95 disabled:opacity-50"
>
{isSubmitting ? <Loader2 className="w-5 h-5 animate-spin" /> : <Send className="w-5 h-5" />}
KIRIM ULASAN SEKARANG
</button>
</form>
</div>
{/* Background decoration */}
<div className="absolute -right-10 -bottom-10 w-40 h-40 bg-purple-600/10 rounded-full blur-3xl"></div>
</div>
</div>
</div>
)
}