"use client"; /* eslint-disable @typescript-eslint/no-explicit-any, react-hooks/set-state-in-effect */ import { useState, useEffect, useRef } from "react"; import { toast } from "sonner"; import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { cn } from "@/lib/utils"; import type { UserMarker } from "@/components/leaflet-map"; import { TimedUndoAction } from "@/components/time-undo-action"; import { EV_TYPES, EV_STATUSES, GAS_BRANDS, REPAIR_SPECIALITIES, GAS_TYPES_BY_BRAND, } from "@/components/gas-station/station-placement-dialog"; /* ─── Helpers ───────────────────────────────────── */ function Field({ label, children, className }: { label: string; children: React.ReactNode; className?: string }) { return (
{label} {children}
); } function ChipSelector({ options, value, onChange, }: { options: { value: string; label: string; color: string }[]; value: string; onChange: (v: string) => void; }) { return (
{options.map((opt) => { const isSelected = value === opt.value; return ( ); })}
); } function MultiChipSelector({ options, values, onChange, color, }: { options: string[]; values: string[]; onChange: (v: string[]) => void; color: string; }) { return (
{options.map((opt) => { const isSelected = values.includes(opt); return ( ); })}
); } /* ─── Edit Dialog ───────────────────────────────── */ interface StationEditDialogProps { open: boolean; onOpenChange: (open: boolean) => void; marker: UserMarker | null; onUpdated: (updated: UserMarker) => void; onDeleted: (deleted: UserMarker) => void; } export function StationEditDialog({ open, onOpenChange, marker, onUpdated, onDeleted, }: StationEditDialogProps) { const [category, setCategory] = useState("gas-pump"); const [name, setName] = useState(""); // Form fields for EV Charger const [evType, setEvType] = useState("DC Fast"); const [evStatus, setEvStatus] = useState("Active"); // Form fields for Gas Station const [gasBrand, setGasBrand] = useState("Pertamina"); const [gasHours, setGasHours] = useState("24 Jam"); const [selectedGasTypes, setSelectedGasTypes] = useState([]); // Form fields for Repair Shop const [repairSpeciality, setRepairSpeciality] = useState("Umum"); const [repairStatus, setRepairStatus] = useState("Buka 08:00 - 17:00"); // Timed Delete State const DELETE_SECONDS = 5; const TOAST_ID = "station-delete"; const [deleteActive, setDeleteActive] = useState(false); const [deleteCountdown, setDeleteCountdown] = useState(DELETE_SECONDS); const pendingDeleteMarker = useRef(null); // Fetching and saving state const [isLoading, setIsLoading] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); const [isDeleting, setIsDeleting] = useState(false); const [error, setError] = useState(null); const BACKEND_URL = process.env.NEXT_PUBLIC_BACKEND_URL ?? "http://localhost:4000"; // Synchronize when marker opens and load latest from DB useEffect(() => { if (!open || !marker) return; setDeleteActive(false); setDeleteCountdown(DELETE_SECONDS); pendingDeleteMarker.current = null; toast.dismiss(TOAST_ID); setError(null); const dbId = marker.id.split("-").slice(1).join("-"); const token = localStorage.getItem("auth_token"); const headers: Record = token ? { Authorization: `Bearer ${token}` } : {}; setIsLoading(true); fetch(`${BACKEND_URL}/gas-station/markers/${dbId}`, { headers }) .then((r) => { if (!r.ok) throw new Error("Gagal memuat data dari server"); return r.json(); }) .then((row) => { setCategory(row.marker_category); setName(row.name); if (row.marker_category === "charging-station") { setEvType(row.sub_type ?? "DC Fast"); setEvStatus(row.status ?? "Active"); } else if (row.marker_category === "gas-pump") { setGasBrand(row.brand ?? "Pertamina"); setGasHours(row.operating_hours ?? "24 Jam"); setSelectedGasTypes(row.gas_types ?? GAS_TYPES_BY_BRAND[row.brand ?? "Pertamina"] ?? []); } else if (row.marker_category === "wrench") { setRepairSpeciality(row.sub_type ?? "Umum"); setRepairStatus(row.status ?? "Buka 08:00 - 17:00"); } }) .catch((err) => { console.error(err); setError("Gagal memuat data terbaru, menggunakan data lokal."); // Fallback setCategory(marker.type); setName(marker.name); const meta = marker.meta; if (marker.type === "charging-station") { setEvType(meta?.poi_type ?? "DC Fast"); setEvStatus(meta?.notes?.replace("Status: ", "") ?? "Active"); } else if (marker.type === "gas-pump") { setGasBrand(meta?.poi_type ?? "Pertamina"); setGasHours(meta?.notes?.replace("Jam: ", "") ?? "24 Jam"); setSelectedGasTypes(meta?.gas_types ?? GAS_TYPES_BY_BRAND[meta?.poi_type ?? "Pertamina"] ?? []); } else if (marker.type === "wrench") { setRepairSpeciality(meta?.poi_type ?? "Umum"); setRepairStatus(meta?.notes ?? "Buka 08:00 - 17:00"); } }) .finally(() => setIsLoading(false)); }, [open, marker]); async function handleDelete() { const target = pendingDeleteMarker.current; if (!target) return; const dbId = target.id.split("-").slice(1).join("-"); const token = localStorage.getItem("auth_token"); const headers: Record = token ? { Authorization: `Bearer ${token}` } : {}; setIsDeleting(true); setError(null); try { const res = await fetch(`${BACKEND_URL}/gas-station/markers/${dbId}`, { method: "DELETE", headers, }); if (!res.ok && res.status !== 204) { throw new Error("Gagal menghapus lokasi dari server"); } pendingDeleteMarker.current = null; onDeleted(target); onOpenChange(false); } catch (err: unknown) { setError(err instanceof Error ? err.message : "Terjadi kesalahan saat menghapus"); toast.error(err instanceof Error ? err.message : "Gagal menghapus lokasi"); } finally { setIsDeleting(false); } } // Countdown tick useEffect(() => { if (!deleteActive) return; const id = setInterval(() => setDeleteCountdown((p) => Math.max(0, p - 1)), 1000); return () => clearInterval(id); }, [deleteActive]); // Fire delete when countdown hits 0 useEffect(() => { if (!deleteActive || deleteCountdown > 0) return; setDeleteActive(false); setDeleteCountdown(DELETE_SECONDS); toast.dismiss(TOAST_ID); handleDelete(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [deleteActive, deleteCountdown]); // Toast showing countdown useEffect(() => { if (!deleteActive || !pendingDeleteMarker.current) { toast.dismiss(TOAST_ID); return; } const mName = pendingDeleteMarker.current.name; const pct = (deleteCountdown / DELETE_SECONDS) * 100; toast.custom( () => (

Menghapus lokasi

"{mName}" — dalam {deleteCountdown} detik

), { id: TOAST_ID, duration: Infinity, position: "bottom-left" }, ); // eslint-disable-next-line react-hooks/exhaustive-deps }, [deleteActive, deleteCountdown]); function toggleDelete() { setDeleteActive((prev) => { if (!prev) { pendingDeleteMarker.current = marker; setDeleteCountdown(DELETE_SECONDS); } else { pendingDeleteMarker.current = null; toast.dismiss(TOAST_ID); } return !prev; }); } async function handleSubmit(e: React.FormEvent) { e.preventDefault(); if (!marker) return; setIsSubmitting(true); setError(null); let finalPoiType = ""; let finalNotes = ""; const brand = category === "gas-pump" ? gasBrand : null; const sub_type = category === "charging-station" ? evType : (category === "wrench" ? repairSpeciality : null); const gas_types = category === "gas-pump" ? selectedGasTypes : null; const operating_hours = category === "gas-pump" ? gasHours : null; const status = category === "charging-station" ? evStatus : (category === "wrench" ? repairStatus : null); if (category === "charging-station") { finalPoiType = evType; finalNotes = `Status: ${evStatus}`; } else if (category === "gas-pump") { finalPoiType = gasBrand; finalNotes = `Jam: ${gasHours}`; } else if (category === "wrench") { finalPoiType = repairSpeciality; finalNotes = repairStatus; } try { const dbId = marker.id.split("-").slice(1).join("-"); const token = localStorage.getItem("auth_token"); const headers = { "Content-Type": "application/json", ...(token ? { Authorization: `Bearer ${token}` } : {}), }; const res = await fetch(`${BACKEND_URL}/gas-station/markers/${dbId}`, { method: "PUT", headers, body: JSON.stringify({ name: name.trim() || marker.name, marker_category: category, brand, sub_type, gas_types, operating_hours, status, notes: null, }), }); if (!res.ok) { const data = await res.json().catch(() => ({})); throw new Error(data.error ?? "Gagal menyimpan perubahan"); } const saved = await res.json(); const updated: UserMarker = { ...marker, type: category, name: saved.name, meta: { ...marker.meta, poi_type: finalPoiType, notes: finalNotes, gas_types: category === "gas-pump" ? selectedGasTypes : undefined, }, }; onUpdated(updated); onOpenChange(false); } catch (err: unknown) { setError(err instanceof Error ? err.message : "Terjadi kesalahan"); } finally { setIsSubmitting(false); } } const categoryLabels: Record = { "charging-station": "Edit EV Charger", "gas-pump": "Edit Gas Station", wrench: "Edit Bengkel", }; const titleLabel = categoryLabels[category] || "Edit Lokasi"; return ( { if (!v) onOpenChange(false); }}>
{/* Header */} {titleLabel}

{marker ? `${marker.lat.toFixed(5)}, ${marker.lng.toFixed(5)}` : ""}

{/* Body */}
{isLoading ? (

Memuat data…

) : ( <> {/* Category Selector */} {/* Location Name */} setName(e.target.value)} required className="bg-muted/50 border border-border focus-visible:border-ring" /> {/* Dynamic Fields: EV Charger */} {category === "charging-station" && ( <> )} {/* Dynamic Fields: Gas Station */} {category === "gas-pump" && ( <> { setGasBrand(brand); setSelectedGasTypes(GAS_TYPES_BY_BRAND[brand] ?? []); }} /> b.value === gasBrand)?.color ?? "#ff3b1f"} /> setGasHours(e.target.value)} required className="bg-muted/50 border border-border focus-visible:border-ring" />
{["24 Jam", "06:00 - 22:00", "07:00 - 21:00"].map((h) => ( ))}
)} {/* Dynamic Fields: Repair Shop */} {category === "wrench" && ( <> setRepairStatus(e.target.value)} required className="bg-muted/50 border border-border focus-visible:border-ring" />
{["Buka 24 Jam", "Buka 08:00 - 17:00", "Tutup"].map((s) => ( ))}
)} )} {error && (

{error}

)}
{/* Footer */}
); }