274 lines
10 KiB
TypeScript
274 lines
10 KiB
TypeScript
"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>
|
||
);
|
||
} |