271 lines
14 KiB
TypeScript
271 lines
14 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useEffect } from 'react'
|
|
import { MapPin, Phone, User, ExternalLink, Map as MapIcon, Star, Send, Loader2, Building2 } 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
|
|
}[]
|
|
}
|
|
|
|
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>
|
|
|
|
{/* 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>
|
|
)
|
|
}
|