satupeta-main/app/(modules)/admin/mapset-upload/_components/step-5-success.tsx

274 lines
10 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useEffect, useState, useRef } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useUploadContext } from "../_context/upload-context";
import { Button } from "@/shared/components/ui/button"; // Sesuaikan dengan UI kit Anda
// Pastikan Anda memiliki variabel env atau konstanta untuk WS_URL
// Jika belum ada, bisa ganti sementara dengan string hardcoded atau process.env
const WS_URL = process.env.NEXT_PUBLIC_WS_URL || "ws://localhost:8000";
export default function StepSuccess() {
const { state, reset } = useUploadContext();
const { validatedData } = state;
const router = useRouter();
const geomIcons: Record<string, string> = {
Point: "📍",
MultiPoint: "🔹",
LineString: "📏",
MultiLineString: "🛣️",
Polygon: "⬛",
MultiPolygon: "🗾",
GeometryCollection: "🧩",
};
const PROCESS_STEPS = [
{ key: "upload", label: "Upload data" },
{ key: "cleansing", label: "Cleansing data" },
{ key: "publish_geoserver", label: "Publish GeoServer" },
{ key: "done", label: "Publish GeoNetwork" },
];
const INITIAL_STEP_STATUS: Record<string, "pending" | "running" | "done" | "error"> = {
upload: "done",
cleansing: "pending",
publish_geoserver: "pending",
done: "pending",
};
const [stepStatus, setStepStatus] = useState(INITIAL_STEP_STATUS);
const wsRef = useRef<WebSocket | null>(null);
// Jika user refresh halaman dan data hilang, kembalikan ke awal
useEffect(() => {
if (!validatedData) {
router.replace("/admin/mapset-upload?step=VALIDATE");
}
}, [validatedData, router]);
// WebSocket Logic
// useEffect(() => {
// if (!validatedData?.job_id || validatedData.job_status === "done") return;
// // Construct WS URL
// const wsUrl = `${WS_URL}/ws/job/${validatedData.job_id}`;
// const ws = new WebSocket(wsUrl);
// wsRef.current = ws;
// ws.onopen = () => {
// console.log("WS connected:", validatedData.job_id);
// };
// ws.onmessage = (event) => {
// try {
// const data = JSON.parse(event.data);
// const finishedStep = data.step;
// setStepStatus((prev) => {
// const updated = { ...prev };
// const stepIndex = PROCESS_STEPS.findIndex((s) => s.key === finishedStep);
// // 1⃣ step yang dikirim WS → DONE
// if (stepIndex >= 0) {
// updated[finishedStep] = "done";
// }
// // 2⃣ step setelahnya → RUNNING
// const nextStep = PROCESS_STEPS[stepIndex + 1];
// if (nextStep) {
// updated[nextStep.key] = "running";
// }
// // 3⃣ step setelah itu → PENDING
// PROCESS_STEPS.slice(stepIndex + 2).forEach((s) => {
// if (updated[s.key] !== "done") {
// updated[s.key] = "pending";
// }
// });
// return updated;
// });
// // 🔥 AUTO CLOSE WS JIKA SELESAI
// if (finishedStep === "done") {
// setTimeout(() => {
// wsRef.current?.close();
// }, 2000);
// }
// } catch (e) {
// console.error("Error parsing WS message", e);
// }
// };
// ws.onerror = (err) => {
// console.error("WS error", err);
// };
// return () => {
// ws.close();
// };
// }, [validatedData]);
// Render Helpers
const Spinner = () => (
<span className="inline-block w-4 h-4 border-2 border-blue-500 border-t-transparent rounded-full animate-spin" />
);
const renderIcon = (status: string) => {
if (status === "running") return <Spinner />;
if (status === "done") return "✔";
if (status === "error") return "❌";
return "⬜";
};
if (!validatedData){ return null;}else{console.log("val", validatedData);} // Prevent render if redirecting
return (
<div className="max-w-4xl mx-auto text-center py-10">
<h1 className="text-3xl font-bold text-green-600 mb-4"> Upload Berhasil!</h1>
<p className="text-gray-700 mb-8">
Data Anda berhasil disimpan ke database.
</p>
<div className="relative border border-gray-200 bg-gradient-to-b from-white to-gray-50 rounded-2xl shadow-md p-8 mb-10 text-left overflow-hidden">
{/* Background Accents */}
<div className="absolute top-0 right-0 w-32 h-32 bg-green-100 rounded-full blur-3xl opacity-50 pointer-events-none"></div>
<div className="absolute bottom-0 left-0 w-32 h-32 bg-blue-100 rounded-full blur-3xl opacity-50 pointer-events-none"></div>
<div className="flex items-center gap-3 mb-6 relative z-10">
<div className="p-2 bg-green-100 text-green-600 rounded-full shadow-inner">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth="2"
stroke="currentColor"
className="w-6 h-6"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M4.5 12.75l6 6 9-13.5"
/>
</svg>
</div>
<h2 className="text-2xl font-bold text-gray-800 tracking-tight">
Ringkasan Hasil Upload
</h2>
</div>
<div className="space-y-4 relative z-10">
{validatedData.data.table_name && (
<div className="flex justify-between items-center bg-gray-50 px-4 py-3 rounded-lg border border-gray-200 hover:shadow-sm transition">
<span className="text-gray-600 font-medium">📁 Nama Tabel</span>
<span className="text-gray-900 font-semibold">{validatedData.data.table_name}</span>
</div>
)}
{validatedData.data.total_rows && (
<div className="flex justify-between items-center bg-gray-50 px-4 py-3 rounded-lg border border-gray-200 hover:shadow-sm transition">
<span className="text-gray-600 font-medium">📊 Jumlah Baris</span>
<span className="text-gray-900 font-semibold">
{validatedData.data.total_rows.toLocaleString()} data
</span>
</div>
)}
{validatedData.data.geometry_type && (
<div className="flex justify-between items-center bg-gray-50 px-4 py-3 rounded-lg border border-gray-200 hover:shadow-sm transition">
<span className="text-gray-600 font-medium">🧭 Jenis Geometry</span>
<span className="text-gray-900 font-semibold">
{Array.isArray(validatedData.data.geometry_type)
? validatedData.data.geometry_type.map((g: string) => `${geomIcons[g] || "❓"} ${g}`).join(", ")
: validatedData.data.geometry_type
}
</span>
</div>
)}
{validatedData.data.upload_time && (
<div className="flex justify-between items-center bg-gray-50 px-4 py-3 rounded-lg border border-gray-200 hover:shadow-sm transition">
<span className="text-gray-600 font-medium">🕒 Waktu Upload</span>
<span className="text-gray-900 font-semibold">
{new Date(validatedData.data.upload_time).toLocaleString("id-ID", {
dateStyle: "full",
timeStyle: "short",
})}
</span>
</div>
)}
{/* PROGRESS STEPS (WS LIVE) */}
{(validatedData.data.message && validatedData.data.job_status !== "done") && (
<div className="border border-gray-200 rounded-lg mt-4 overflow-hidden">
{PROCESS_STEPS.map((step) => (
<div
key={step.key}
className={`px-4 flex items-center gap-3 text-sm py-3 border-b border-gray-200 ${
stepStatus[step.key] === "done"
? "bg-green-50"
: stepStatus[step.key] === "running"
? "bg-blue-50"
: "bg-gray-50"
}`}
>
<span className="w-5 flex justify-center">
{renderIcon(stepStatus[step.key] || "-")}
</span>
<span
className={
stepStatus[step.key] === "done"
? "text-green-600 font-medium"
: stepStatus[step.key] === "running"
? "text-blue-600 font-medium"
: "text-gray-500"
}
>
{step.label}
</span>
</div>
))}
</div>
)}
</div>
{(validatedData.data.job_status !== "done") && (
<p className="mt-3 text-center text-gray-500 text-sm">
Sistem sedang melakukan cleansing data dan publikasi ke GeoServer dan GeoNetwork.<br />
Anda tidak perlu menunggu di halaman ini.
</p>
)}
{/* Metadata Section JSON View */}
{validatedData.data.metadata && (
<div className="mt-8 relative z-10">
<h3 className="text-sm font-semibold text-gray-600 mb-2">Metadata</h3>
<div className="bg-slate-900 text-slate-100 text-xs rounded-lg overflow-auto shadow-inner p-4 max-h-60 font-mono">
<pre>{JSON.stringify(validatedData.data.metadata, null, 2)}</pre>
</div>
</div>
)}
</div>
<div className="flex flex-col w-full items-center gap-3">
{/* Tombol ke Dashboard (Next.js Link) */}
<Link href="/admin/mapset">
<Button className="w-fit bg-blue-600 hover:bg-blue-700 px-8 py-6 text-lg shadow-lg shadow-blue-200">
Kembali ke Dashboard
</Button>
</Link>
{/* Tombol Upload Lagi (Reset Context) */}
<button
onClick={reset}
className="text-gray-500 hover:text-gray-700 text-sm font-medium hover:underline transition"
>
Upload data lagi
</button>
</div>
</div>
);
}