"use client"; import { useState } from "react"; import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import type { UserMarker } from "@/components/leaflet-map"; import { FuzzyResultStep } from "@/components/poverty/fuzzy-result-step"; const BACKEND_URL = process.env.NEXT_PUBLIC_BACKEND_URL ?? "http://localhost:4000"; const MARKER_LABELS: Record = { marker: "Warga Miskin", mosque: "Masjid", church: "Gereja", synagogue: "Sinagog", clinic: "Klinik", "food-bank": "Bank Makanan", school: "Sekolah", }; interface PendingClick { lat: number; lng: number; } interface MarkerPlacementDialogProps { open: boolean; onOpenChange: (open: boolean) => void; markerType: string; pendingClick: PendingClick | null; onConfirm: (marker: UserMarker) => void; } /* ─── Adaptive Slider ─────────────────────────────────────── */ function AdaptiveSlider({ value, onChange, hint, }: { value: number; onChange: (v: number) => void; hint: string; }) { const pct = ((value - 1) / 9) * 100; const color = value <= 3 ? "#ef4444" : value <= 6 ? "#f59e0b" : "#10b981"; return (
onChange(Number(e.target.value))} style={{ background: `linear-gradient(to right, ${color} 0%, ${color} ${pct}%, var(--border) ${pct}%, var(--border) 100%)`, }} className=" flex-1 h-[3px] cursor-pointer appearance-none rounded-full outline-none [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-white [&::-webkit-slider-thumb]:shadow [&::-webkit-slider-thumb]:border [&::-webkit-slider-thumb]:border-border [&::-moz-range-thumb]:w-3 [&::-moz-range-thumb]:h-3 [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:bg-white [&::-moz-range-thumb]:border [&::-moz-range-thumb]:border-border " /> {value}

{hint}

); } /* ─── Field wrapper ───────────────────────────────────────── */ function Field({ label, children, className, }: { label: string; children: React.ReactNode; className?: string; }) { return (
{label} {children}
); } /* ─── Main dialog ─────────────────────────────────────────── */ export function MarkerPlacementDialog({ open, onOpenChange, markerType, pendingClick, onConfirm, }: MarkerPlacementDialogProps) { const isHousehold = markerType === "marker"; const [headName, setHeadName] = useState(""); const [familyCount, setFamilyCount] = useState(3); const [penghasilan, setPenghasilan] = useState(1500000); const [kondisiRumah, setKondisiRumah] = useState(5); const [kepemilikanAset, setKepemilikanAset] = useState(5); const [notes, setNotes] = useState(""); const [placeName, setPlaceName] = useState(""); const [isSubmitting, setIsSubmitting] = useState(false); const [error, setError] = useState(null); const [step, setStep] = useState<"form" | "result">("form"); const [savedMarker, setSavedMarker] = useState(null); function reset() { setHeadName(""); setFamilyCount(3); setPenghasilan(1500000); setKondisiRumah(5); setKepemilikanAset(5); setNotes(""); setPlaceName(""); setError(null); setStep("form"); setSavedMarker(null); } async function handleSubmit(e: React.FormEvent) { e.preventDefault(); if (!pendingClick) return; setIsSubmitting(true); setError(null); try { if (isHousehold) { const token = localStorage.getItem("auth_token"); const res = await fetch(`${BACKEND_URL}/households`, { method: "POST", headers: { "Content-Type": "application/json", ...(token ? { Authorization: `Bearer ${token}` } : {}), }, body: JSON.stringify({ head_name: headName, family_count: familyCount, latitude: pendingClick.lat, longitude: pendingClick.lng, notes: notes || null, penghasilan, kondisi_rumah: kondisiRumah, kepemilikan_aset: kepemilikanAset, marker_type: "marker", }), }); if (!res.ok) { const data = await res.json().catch(() => ({})); throw new Error(data.error ?? "Gagal menyimpan data"); } const saved = await res.json(); const marker: UserMarker = { id: `hh-${saved.id}`, lat: pendingClick.lat, lng: pendingClick.lng, type: "marker", name: saved.head_name, meta: { poverty_level: saved.poverty_level, family_count: saved.family_count, penghasilan: saved.penghasilan, fuzzy_label: saved.fuzzy_label ?? undefined, fuzzy_score: saved.fuzzy_score != null ? parseFloat(saved.fuzzy_score) : undefined, fuzzy_detail: saved.fuzzy_detail ?? undefined, notes: saved.notes ?? undefined, }, }; setSavedMarker(marker); setStep("result"); setIsSubmitting(false); return; } else { const token = localStorage.getItem("auth_token"); const name = placeName.trim() || MARKER_LABELS[markerType] || markerType; const res = await fetch(`${BACKEND_URL}/poi`, { method: "POST", headers: { "Content-Type": "application/json", ...(token ? { Authorization: `Bearer ${token}` } : {}), }, body: JSON.stringify({ name, poi_type: markerType, latitude: pendingClick.lat, longitude: pendingClick.lng, }), }); if (!res.ok) { const data = await res.json().catch(() => ({})); throw new Error(data.error ?? "Gagal menyimpan data"); } const saved = await res.json(); onConfirm({ id: `poi-${saved.id}`, lat: pendingClick.lat, lng: pendingClick.lng, type: markerType, name, meta: { poi_type: markerType, notes: saved.notes ?? undefined }, }); } reset(); onOpenChange(false); } catch (err: unknown) { setError(err instanceof Error ? err.message : "Terjadi kesalahan"); } finally { setIsSubmitting(false); } } function handleCancel() { reset(); onOpenChange(false); } return ( { if (!v) handleCancel(); }}> {/* ── Fuzzy Result Step ── */} {step === "result" && savedMarker && ( { onConfirm(savedMarker); reset(); onOpenChange(false); }} /> )} {/* ── Form Step ── */} {step === "form" && (
{/* ── Header ── */} {isHousehold ? "Tambah Data Warga Miskin" : `Tambah ${MARKER_LABELS[markerType] ?? markerType}`} {pendingClick && (

{pendingClick.lat.toFixed(6)}, {pendingClick.lng.toFixed(6)}

)}
{/* ── Body ── */}
{isHousehold ? ( <> {/* Row 1: Nama KK */} setHeadName(e.target.value)} required className="bg-muted/50 border border-border focus-visible:border-ring" /> {/* Row 2: Penghasilan + Tanggungan */}
Rp { const clean = e.target.value.replace(/\D/g, ""); setPenghasilan(clean ? parseInt(clean, 10) : 0); }} className="pl-9 bg-muted/50 border border-border focus-visible:border-ring" />
setFamilyCount(Number(e.target.value))} className="bg-muted/50 border border-border focus-visible:border-ring" />
{/* Row 3: Sliders */}
{/* Row 4: Catatan */}