update admin page
This commit is contained in:
parent
60d39a36e6
commit
8921e2a118
|
|
@ -9,9 +9,22 @@ import {
|
||||||
DropdownMenuSeparator
|
DropdownMenuSeparator
|
||||||
} from "../../../components/ui/dropdown-menu";
|
} from "../../../components/ui/dropdown-menu";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
export default function ViewsAdminHome() {
|
export default function ViewsAdminHome() {
|
||||||
const { datasets, loading, errorMsg } = useAdminHomeController();
|
const { datasets, loading, errorMsg } = useAdminHomeController();
|
||||||
|
const [filter, setFilter] = useState("ALL");
|
||||||
|
const counts = {
|
||||||
|
ALL: datasets.length,
|
||||||
|
CLEANSING: datasets.filter(d => d.process === "CLEANSING").length,
|
||||||
|
ERROR: datasets.filter(d => d.process === "ERROR").length,
|
||||||
|
FINISHED: datasets.filter(d => d.process === "FINISHED").length,
|
||||||
|
TESTING: datasets.filter(d => d.process === "TESTING").length,
|
||||||
|
};
|
||||||
|
const filteredData =
|
||||||
|
filter === "ALL"
|
||||||
|
? datasets
|
||||||
|
: datasets.filter(d => d.process === filter);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-6xl mx-auto py-10">
|
<div className="max-w-6xl mx-auto py-10">
|
||||||
|
|
@ -29,8 +42,83 @@ export default function ViewsAdminHome() {
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-3 mb-6">
|
||||||
|
|
||||||
|
{/* ALL */}
|
||||||
|
<button
|
||||||
|
onClick={() => setFilter("ALL")}
|
||||||
|
className={`
|
||||||
|
px-4 py-1 rounded-sm text-sm font-medium border transition
|
||||||
|
${filter === "ALL"
|
||||||
|
? "bg-blue-200 text-blue-700 border-blue-200"
|
||||||
|
: "bg-white text-blue-700 border-blue-700 hover:bg-blue-200 cursor-pointer"
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
ALL ({counts.ALL})
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* CLEANSING */}
|
||||||
|
<button
|
||||||
|
onClick={() => setFilter("CLEANSING")}
|
||||||
|
className={`
|
||||||
|
px-4 py-1 rounded-sm text-sm font-medium border transition
|
||||||
|
${filter === "CLEANSING"
|
||||||
|
? "bg-yellow-200 text-yellow-700 border-yellow-200"
|
||||||
|
: "bg-white text-yellow-400 border-yellow-400 hover:bg-yellow-200 hover:text-yellow-700 cursor-pointer"
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
CLEANSING ({counts.CLEANSING})
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* ERROR */}
|
||||||
|
<button
|
||||||
|
onClick={() => setFilter("ERROR")}
|
||||||
|
className={`
|
||||||
|
px-4 py-1 rounded-sm text-sm font-medium border transition
|
||||||
|
${filter === "ERROR"
|
||||||
|
? "bg-red-200 text-red-500 border-red-200"
|
||||||
|
: "bg-white text-red-500 border-red-500 hover:bg-red-200 cursor-pointer"
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
ERROR ({counts.ERROR})
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* FINISHED */}
|
||||||
|
<button
|
||||||
|
onClick={() => setFilter("FINISHED")}
|
||||||
|
className={`
|
||||||
|
px-4 py-1 rounded-sm text-sm font-medium border transition
|
||||||
|
${filter === "FINISHED"
|
||||||
|
? "bg-green-200 text-green-500 border-green-200"
|
||||||
|
: "bg-white text-green-500 border-green-500 hover:bg-green-200 cursor-pointer"
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
FINISHED ({counts.FINISHED})
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* TESTING */}
|
||||||
|
<button
|
||||||
|
onClick={() => setFilter("TESTING")}
|
||||||
|
className={`
|
||||||
|
px-4 py-1 rounded-sm text-sm font-medium border transition
|
||||||
|
${filter === "TESTING"
|
||||||
|
? "bg-gray-300 text-gray-700 border-gray-300"
|
||||||
|
: "bg-white text-gray-700 border-gray-700 hover:bg-gray-300"
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
TESTING ({counts.TESTING})
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{/* Empty State */}
|
{/* Empty State */}
|
||||||
{datasets.length === 0 && !loading && (
|
{filteredData.length === 0 && !loading && (
|
||||||
<p className="text-gray-500 text-center mt-10">
|
<p className="text-gray-500 text-center mt-10">
|
||||||
Belum ada metadata dataset yang tersimpan.
|
Belum ada metadata dataset yang tersimpan.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -38,7 +126,7 @@ export default function ViewsAdminHome() {
|
||||||
|
|
||||||
{/* CARD LIST */}
|
{/* CARD LIST */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
{datasets.map((item) => (
|
{filteredData.map((item) => (
|
||||||
<div
|
<div
|
||||||
key={item.id}
|
key={item.id}
|
||||||
className="bg-white border border-gray-200 rounded-xl shadow-sm p-6 hover:shadow-md transition"
|
className="bg-white border border-gray-200 rounded-xl shadow-sm p-6 hover:shadow-md transition"
|
||||||
|
|
@ -51,12 +139,14 @@ export default function ViewsAdminHome() {
|
||||||
{/* STATUS BADGE */}
|
{/* STATUS BADGE */}
|
||||||
<span
|
<span
|
||||||
className={`text-xs px-2 py-1 rounded-full ${
|
className={`text-xs px-2 py-1 rounded-full ${
|
||||||
item.dataset_status === "completed"
|
item.process === "FINISHED"
|
||||||
? "bg-green-100 text-green-700"
|
? "bg-green-100 text-green-700"
|
||||||
: "bg-yellow-100 text-yellow-700"
|
: item.process === "CLEANSING"
|
||||||
|
? "bg-yellow-100 text-yellow-700"
|
||||||
|
: "bg-red-100 text-red-700"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{item.dataset_status}
|
{item.process}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -71,11 +161,6 @@ export default function ViewsAdminHome() {
|
||||||
{item.table_title}
|
{item.table_title}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
|
||||||
<span className="font-medium">Kategori:</span>{" "}
|
|
||||||
{item.topic_category}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<span className="font-medium">Organisasi:</span>{" "}
|
<span className="font-medium">Organisasi:</span>{" "}
|
||||||
{item.organization_name}
|
{item.organization_name}
|
||||||
|
|
@ -134,9 +219,9 @@ export default function ViewsAdminHome() {
|
||||||
{/* MORE MENU */}
|
{/* MORE MENU */}
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger>
|
<DropdownMenuTrigger>
|
||||||
<button className="p-2 rounded-full hover:bg-gray-200">
|
<div className="p-2 rounded-full hover:bg-gray-200">
|
||||||
⋮
|
⋮
|
||||||
</button>
|
</div>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
|
|
||||||
<DropdownMenuContent className="w-40">
|
<DropdownMenuContent className="w-40">
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { setFile, setResult, setValidatedData, setPdfPageCount, setSelectedPages } from "../../../store/slices/uploadSlice";
|
import { setFile, setResult, setValidatedData, setPdfPageCount, setSelectedPages } from "../../../store/slices/uploadSlice";
|
||||||
import { uploadFile, uploadPdf, saveToDatabase } from "./service_admin_upload";
|
import { uploadFile, uploadPdf, saveToDatabase, getStyles } from "./service_admin_upload";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import * as pdfjsLib from "pdfjs-dist";
|
import * as pdfjsLib from "pdfjs-dist";
|
||||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
||||||
|
|
@ -14,7 +14,7 @@ import * as XLSX from 'xlsx';
|
||||||
export function useUploadController() {
|
export function useUploadController() {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { file, result, pdfPageCount, selectedPages } = useSelector((state) => state.upload);
|
const { file, fileDesc, result, pdfPageCount, selectedPages } = useSelector((state) => state.upload);
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [selectedTable, setSelectedTable] = useState(null);
|
const [selectedTable, setSelectedTable] = useState(null);
|
||||||
|
|
@ -24,6 +24,11 @@ export function useUploadController() {
|
||||||
const [selectedSheet, setSelectedSheet] = useState(null);
|
const [selectedSheet, setSelectedSheet] = useState(null);
|
||||||
const [sheetCount, setSheetCount] = useState(null);
|
const [sheetCount, setSheetCount] = useState(null);
|
||||||
const [sheetNames, setSheetNames] = useState([]);
|
const [sheetNames, setSheetNames] = useState([]);
|
||||||
|
|
||||||
|
// const [filedesc, setFileDesc] = useState(null);
|
||||||
|
|
||||||
|
const [geosStyle, setGeosStyle] = useState([]);
|
||||||
|
const [fileReady, setFileReady] = useState(false);
|
||||||
|
|
||||||
// 🔹 handle drop file
|
// 🔹 handle drop file
|
||||||
const handleFileSelect = async (f) => {
|
const handleFileSelect = async (f) => {
|
||||||
|
|
@ -60,7 +65,8 @@ export function useUploadController() {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
navigate("/admin/upload/pdf");
|
// navigate("/admin/upload/pdf");
|
||||||
|
setFileReady(true);
|
||||||
}
|
}
|
||||||
else if (ext === "xlsx" || ext === "xls") {
|
else if (ext === "xlsx" || ext === "xls") {
|
||||||
const data = await f.arrayBuffer();
|
const data = await f.arrayBuffer();
|
||||||
|
|
@ -81,7 +87,7 @@ export function useUploadController() {
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const res = await uploadFile(file, selectedPages, selectedSheet);
|
const res = await uploadFile(file, selectedPages, selectedSheet, fileDesc);
|
||||||
dispatch(setResult(res));
|
dispatch(setResult(res));
|
||||||
|
|
||||||
if (res.file_type !== ".pdf" || (res.file_type === ".pdf" && !res.tables)) {
|
if (res.file_type !== ".pdf" || (res.file_type === ".pdf" && !res.tables)) {
|
||||||
|
|
@ -99,7 +105,7 @@ export function useUploadController() {
|
||||||
if (!selectedTable) return;
|
if (!selectedTable) return;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const res = await uploadPdf(selectedTable);
|
const res = await uploadPdf(selectedTable, file.name, fileDesc);
|
||||||
dispatch(setResult(res));
|
dispatch(setResult(res));
|
||||||
navigate("/admin/upload/validate");
|
navigate("/admin/upload/validate");
|
||||||
} catch(err){
|
} catch(err){
|
||||||
|
|
@ -108,14 +114,15 @@ export function useUploadController() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleConfirmUpload = async (metadata) => {
|
const handleConfirmUpload = async (metadata, style) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const data = {
|
const data = {
|
||||||
title: metadata.title,
|
title: metadata.title,
|
||||||
columns: result.columns,
|
columns: result.columns,
|
||||||
rows: result.preview,
|
rows: result.preview,
|
||||||
author: metadata
|
author: metadata,
|
||||||
|
style: style
|
||||||
};
|
};
|
||||||
const res = await saveToDatabase(data);
|
const res = await saveToDatabase(data);
|
||||||
dispatch(setValidatedData(res));
|
dispatch(setValidatedData(res));
|
||||||
|
|
@ -126,6 +133,15 @@ export function useUploadController() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleGetStyles = async () => {
|
||||||
|
try {
|
||||||
|
const data = await getStyles();
|
||||||
|
setGeosStyle(data.styles);
|
||||||
|
} catch (err) {
|
||||||
|
setErrorMsg(err?.message || "Terjadi kesalahan saat memuat data.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
loading,
|
loading,
|
||||||
file,
|
file,
|
||||||
|
|
@ -145,5 +161,7 @@ export function useUploadController() {
|
||||||
handleUpload,
|
handleUpload,
|
||||||
handleNextPdf,
|
handleNextPdf,
|
||||||
handleConfirmUpload,
|
handleConfirmUpload,
|
||||||
|
handleGetStyles,
|
||||||
|
geosStyle
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
||||||
export function usePdfViewerController() {
|
export function usePdfViewerController() {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { file, selectedPages } = useSelector((state) => state.upload);
|
const { file, fileDesc } = useSelector((state) => state.upload);
|
||||||
|
|
||||||
const [pages, setPages] = useState([]);
|
const [pages, setPages] = useState([]);
|
||||||
const [selectedPagesLocal, setSelectedPagesLocal] = useState([]);
|
const [selectedPagesLocal, setSelectedPagesLocal] = useState([]);
|
||||||
|
|
@ -73,7 +73,7 @@ export function usePdfViewerController() {
|
||||||
if (selectedPagesLocal.length === 0) return;
|
if (selectedPagesLocal.length === 0) return;
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const res = await uploadFile(file, selectedPagesLocal);
|
const res = await uploadFile(file, selectedPagesLocal, null, fileDesc);
|
||||||
dispatch(setResult(res));
|
dispatch(setResult(res));
|
||||||
|
|
||||||
if (!res.tables) {
|
if (!res.tables) {
|
||||||
|
|
|
||||||
|
|
@ -26,13 +26,15 @@
|
||||||
|
|
||||||
import api from "../../../services/api";
|
import api from "../../../services/api";
|
||||||
|
|
||||||
export async function uploadFile(file, page = null, sheet = null) {
|
export async function uploadFile(file, page = null, sheet = null, file_desc) {
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("file", file);
|
formData.append("file", file);
|
||||||
formData.append("page", page);
|
formData.append("page", page);
|
||||||
if (sheet != null) {
|
if (sheet != null) {
|
||||||
formData.append("sheet", sheet);
|
formData.append("sheet", sheet);
|
||||||
}
|
}
|
||||||
|
formData.append("file_desc", file_desc);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await api.post("/upload/file", formData, {
|
const response = await api.post("/upload/file", formData, {
|
||||||
|
|
@ -44,9 +46,14 @@ export async function uploadFile(file, page = null, sheet = null) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function uploadPdf(data) {
|
export async function uploadPdf(data, fileName, fileDesc) {
|
||||||
|
const payload = {
|
||||||
|
...data,
|
||||||
|
fileName,
|
||||||
|
fileDesc
|
||||||
|
};
|
||||||
try {
|
try {
|
||||||
const response = await api.post("/upload/process-pdf", data, {
|
const response = await api.post(`/upload/process-pdf`, payload, {
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
});
|
});
|
||||||
return response.data.data;
|
return response.data.data;
|
||||||
|
|
@ -56,7 +63,6 @@ export async function uploadPdf(data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function saveToDatabase(data) {
|
export async function saveToDatabase(data) {
|
||||||
console.log("send:", data);
|
|
||||||
try {
|
try {
|
||||||
const response = await api.post("/upload/to-postgis", data, {
|
const response = await api.post("/upload/to-postgis", data, {
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
|
|
@ -66,3 +72,27 @@ export async function saveToDatabase(data) {
|
||||||
throw error.response?.data.detail.message || { message: "Gagal upload data." };
|
throw error.response?.data.detail.message || { message: "Gagal upload data." };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export async function getStyles() {
|
||||||
|
try {
|
||||||
|
const res = await api.get("/dataset/styles");
|
||||||
|
return res.data || [];
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Fetch datasets error:", err);
|
||||||
|
throw err.response?.data || err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export async function getStylesFile(styleName) {
|
||||||
|
try {
|
||||||
|
const res = await api.get(`/dataset/styles/${styleName}`);
|
||||||
|
return res.data?.data || [];
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Fetch datasets error:", err);
|
||||||
|
throw err.response?.data || err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ export function useTablePickerController() {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const { result } = useSelector((state) => state.upload); // result dari BE upload PDF
|
const { result, file, fileDesc } = useSelector((state) => state.upload); // result dari BE upload PDF
|
||||||
const [selectedTable, setSelectedTableLocal] = useState(
|
const [selectedTable, setSelectedTableLocal] = useState(
|
||||||
result?.tables?.[0] || null
|
result?.tables?.[0] || null
|
||||||
);
|
);
|
||||||
|
|
@ -22,7 +22,8 @@ export function useTablePickerController() {
|
||||||
if (!selectedTable) return;
|
if (!selectedTable) return;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const res = await uploadPdf(selectedTable);
|
console.log('pdf', selectedTable);
|
||||||
|
const res = await uploadPdf(selectedTable, file.name, fileDesc);
|
||||||
dispatch(setResult(res));
|
dispatch(setResult(res));
|
||||||
navigate("/admin/upload/validate");
|
navigate("/admin/upload/validate");
|
||||||
} catch(err){
|
} catch(err){
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { useEffect, useState, useRef } from "react";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { Navigate } from "react-router-dom";
|
import { Navigate } from "react-router-dom";
|
||||||
|
|
@ -13,14 +14,110 @@ export default function ViewsAdminUploadSuccess() {
|
||||||
MultiPolygon: "🗾",
|
MultiPolygon: "🗾",
|
||||||
GeometryCollection: "🧩",
|
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 = {
|
||||||
|
upload: "done",
|
||||||
|
cleansing: "pending",
|
||||||
|
publish_geoserver: "pending",
|
||||||
|
done: "pending",
|
||||||
|
};
|
||||||
|
|
||||||
|
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) => {
|
||||||
|
if (status === "running") return <Spinner />;
|
||||||
|
if (status === "done") return "✔";
|
||||||
|
if (status === "error") return "❌";
|
||||||
|
return "⬜";
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const [stepStatus, setStepStatus] = useState(INITIAL_STEP_STATUS);
|
||||||
|
const wsRef = useRef(null);
|
||||||
|
|
||||||
if (!validatedData) {
|
if (!validatedData) {
|
||||||
return <Navigate to="/admin/upload" />;
|
return <Navigate to="/admin/upload" />;
|
||||||
}else{
|
|
||||||
console.log('success', validatedData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!validatedData?.job_id) return;
|
||||||
|
|
||||||
|
const wsUrl = `ws://localhost:8000/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) => {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ws.onerror = (err) => {
|
||||||
|
console.error("WS error", err);
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onclose = () => {
|
||||||
|
console.log("WS closed");
|
||||||
|
};
|
||||||
|
|
||||||
|
// 🔥 CLEANUP: ketika pindah halaman
|
||||||
|
return () => {
|
||||||
|
ws.close();
|
||||||
|
};
|
||||||
|
}, [validatedData?.job_id]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-4xl mx-auto py-20 text-center">
|
<div className="max-w-4xl mx-auto text-center">
|
||||||
<h1 className="text-3xl font-bold text-green-600 mb-4">✅ Upload Berhasil!</h1>
|
<h1 className="text-3xl font-bold text-green-600 mb-4">✅ Upload Berhasil!</h1>
|
||||||
<p className="text-gray-700 mb-8">
|
<p className="text-gray-700 mb-8">
|
||||||
Data Anda berhasil disimpan ke database.
|
Data Anda berhasil disimpan ke database.
|
||||||
|
|
@ -93,16 +190,53 @@ export default function ViewsAdminUploadSuccess() {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{validatedData.message && (
|
{/* {validatedData.message && (
|
||||||
<div className="bg-green-50 border border-green-200 px-5 py-4 rounded-lg mt-4">
|
<div className="bg-green-50 border border-green-200 px-5 py-4 rounded-lg mt-4">
|
||||||
<p className="w-full text-center text-green-700 font-semibold">
|
<p className="w-full text-center text-green-700 font-semibold">
|
||||||
{/* {validatedData.message} */}
|
Data sedang diproses <br />
|
||||||
Datasets berhasil di upload
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
)} */}
|
||||||
|
{validatedData.message && (
|
||||||
|
<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>
|
</div>
|
||||||
|
|
||||||
|
<p className="mt-3 text-center text-gray-500">
|
||||||
|
Sistem sedang melakukan cleansing data dan publikasi ke GeoServer dan GeoNetwork.<br />
|
||||||
|
Anda tidak perlu menunggu di halaman ini.
|
||||||
|
</p>
|
||||||
|
|
||||||
{/* Metadata Section */}
|
{/* Metadata Section */}
|
||||||
{validatedData.metadata && (
|
{validatedData.metadata && (
|
||||||
<div className="mt-8 relative z-10">
|
<div className="mt-8 relative z-10">
|
||||||
|
|
@ -114,13 +248,20 @@ export default function ViewsAdminUploadSuccess() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col w-full items-center ">
|
||||||
<Link
|
<Link
|
||||||
to="/admin/upload"
|
to="/admin/home"
|
||||||
className="bg-blue-600 text-white px-6 py-2 rounded hover:bg-blue-700 transition"
|
className="w-fit bg-blue-600 text-white px-6 py-3 rounded-lg hover:bg-blue-700 transition"
|
||||||
>
|
>
|
||||||
Kembali ke Dashboard
|
Kembali ke Dashboard
|
||||||
</Link>
|
</Link>
|
||||||
|
<Link
|
||||||
|
to="/admin/upload"
|
||||||
|
className="w-fit mt-3 text-gray-500 px-6 py-2 hover:text-gray-600 transition"
|
||||||
|
>
|
||||||
|
Upload data lagi
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,17 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useUploadController } from "./controller_admin_upload";
|
import { useUploadController } from "./controller_admin_upload";
|
||||||
import { useDispatch } from "react-redux";
|
import { useDispatch } from "react-redux";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
import LoadingOverlay from "../../../components/LoadingOverlay";
|
import LoadingOverlay from "../../../components/LoadingOverlay";
|
||||||
import ErrorNotification from "../../../components/ErrorNotification";
|
import ErrorNotification from "../../../components/ErrorNotification";
|
||||||
import FileDropzone from "../../../components/FileDropzone";
|
import FileDropzone from "../../../components/FileDropzone";
|
||||||
import PdfPageSelector from "../../../components/PdfPageSelector";
|
import PdfPageSelector from "../../../components/PdfPageSelector";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { reset } from "../../../store/slices/uploadSlice";
|
import { reset } from "../../../store/slices/uploadSlice";
|
||||||
|
import { setFileDesc } from "../../../store/slices/uploadSlice";
|
||||||
|
|
||||||
export default function ViewsAdminUploadStep1() {
|
export default function ViewsAdminUploadStep1() {
|
||||||
|
const navigate = useNavigate();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const {
|
const {
|
||||||
loading,
|
loading,
|
||||||
|
|
@ -37,10 +40,14 @@ export default function ViewsAdminUploadStep1() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleProcess = async () => {
|
const handleProcess = async () => {
|
||||||
try {
|
if (ext === 'pdf') {
|
||||||
await handleUpload();
|
navigate("/admin/upload/pdf");
|
||||||
} catch (err) {
|
} else {
|
||||||
setErrorMsg(err);
|
try {
|
||||||
|
await handleUpload();
|
||||||
|
} catch (err) {
|
||||||
|
setErrorMsg(err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -80,7 +87,7 @@ export default function ViewsAdminUploadStep1() {
|
||||||
{/* {!file && (
|
{/* {!file && (
|
||||||
)} */}
|
)} */}
|
||||||
|
|
||||||
{file && ext!= 'pdf' && (
|
{file && (
|
||||||
<div className="mt-6 border border-gray-200 bg-white rounded-xl p-6 shadow-sm">
|
<div className="mt-6 border border-gray-200 bg-white rounded-xl p-6 shadow-sm">
|
||||||
{/* Info File */}
|
{/* Info File */}
|
||||||
<div className="">
|
<div className="">
|
||||||
|
|
@ -138,7 +145,19 @@ export default function ViewsAdminUploadStep1() {
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<div className="mt-6 ">
|
||||||
|
<label htmlFor="fileDesc" className="block text-sm font-semibold text-gray-700 mb-1">
|
||||||
|
Deskripsi Singkat File<span className="text-red-500">*</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="fileDesc"
|
||||||
|
name="fileDesc"
|
||||||
|
type='text'
|
||||||
|
onChange={(e) => dispatch(setFileDesc(e.target.value))}
|
||||||
|
className={`w-full border border-gray-300 rounded-md p-2 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition bg-white border-red-500 ring-1 ring-red-400`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Tombol Upload */}
|
{/* Tombol Upload */}
|
||||||
<div className={`mt-6 flex justify-center ${(result && result.file_type === ".pdf" && result.tables?.length > 1) ? 'hidden' : 'block' }`}>
|
<div className={`mt-6 flex justify-center ${(result && result.file_type === ".pdf" && result.tables?.length > 1) ? 'hidden' : 'block' }`}>
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -1,162 +1,3 @@
|
||||||
// import { useState, useEffect } from "react";
|
|
||||||
// import { useUploadController } from "./controller_admin_upload";
|
|
||||||
// import { useSelector } from "react-redux";
|
|
||||||
// import LoadingOverlay from "../../../components/LoadingOverlay";
|
|
||||||
// import Notification from "../../../components/Notification";
|
|
||||||
// import ErrorNotification from "../../../components/ErrorNotification";
|
|
||||||
// import { Navigate } from "react-router-dom";
|
|
||||||
// import FilePreview from "../../../components/upload/FilePreview";
|
|
||||||
// import MetadataForm from "../../../components/MetaDataForm";
|
|
||||||
|
|
||||||
// export default function ViewsAdminUploadValidate() {
|
|
||||||
// const { result, file } = useSelector((state) => state.upload);
|
|
||||||
// const {
|
|
||||||
// loading,
|
|
||||||
// tableTitle,
|
|
||||||
// setTableTitle,
|
|
||||||
// handleConfirmUpload,
|
|
||||||
// } = useUploadController();
|
|
||||||
|
|
||||||
// const [showAlert, setShowAlert] = useState(false);
|
|
||||||
// const [alertMessage, setAlertMessage] = useState("");
|
|
||||||
// const [alertType, setAlertType] = useState("info");
|
|
||||||
// const [errorMsg, setErrorMsg] = useState("");
|
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// const handleBeforeUnload = (e) => {
|
|
||||||
// if (result && !loading) {
|
|
||||||
// e.preventDefault();
|
|
||||||
// e.returnValue =
|
|
||||||
// "Data upload Anda belum disimpan. Jika Anda meninggalkan halaman ini, data akan hilang.";
|
|
||||||
// return e.returnValue;
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// window.addEventListener("beforeunload", handleBeforeUnload);
|
|
||||||
// return () => window.removeEventListener("beforeunload", handleBeforeUnload);
|
|
||||||
// }, [result, loading]);
|
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// const handleNavigation = (e) => {
|
|
||||||
// if (result && !loading) {
|
|
||||||
// const confirmLeave = window.confirm(
|
|
||||||
// "Data upload Anda belum disimpan. Jika Anda meninggalkan halaman ini, data akan hilang."
|
|
||||||
// );
|
|
||||||
// if (!confirmLeave) {
|
|
||||||
// e.preventDefault();
|
|
||||||
// window.history.pushState(null, "", window.location.href); // tetap di halaman
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// window.addEventListener("popstate", handleNavigation);
|
|
||||||
// return () => window.removeEventListener("popstate", handleNavigation);
|
|
||||||
// }, [result, loading]);
|
|
||||||
|
|
||||||
// const handleMetadataChange = (data) => {
|
|
||||||
// console.log("Metadata Updated:", data);
|
|
||||||
// };
|
|
||||||
|
|
||||||
// // if (!result)
|
|
||||||
// // return <div className="text-center mt-10">Data belum diupload.</div>;
|
|
||||||
|
|
||||||
// if (!result) return <Navigate to="/admin/upload" />;
|
|
||||||
|
|
||||||
// const handleUploadClick = async () => {
|
|
||||||
// if (!tableTitle.trim()) {
|
|
||||||
// setAlertMessage(
|
|
||||||
// "❗Judul tabel belum diisi. Silakan isi sebelum melanjutkan."
|
|
||||||
// );
|
|
||||||
// setAlertType("error");
|
|
||||||
// setShowAlert(true);
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // handleConfirmUpload();
|
|
||||||
// try {
|
|
||||||
// await handleConfirmUpload();
|
|
||||||
// } catch (err) {
|
|
||||||
// setErrorMsg(err);
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// return (
|
|
||||||
// <div className="max-w-4xl mx-auto py-10">
|
|
||||||
// {showAlert && (
|
|
||||||
// <Notification
|
|
||||||
// message={alertMessage}
|
|
||||||
// type={alertType}
|
|
||||||
// onClose={() => setShowAlert(false)}
|
|
||||||
// />
|
|
||||||
// )}
|
|
||||||
|
|
||||||
// <ErrorNotification
|
|
||||||
// message={errorMsg}
|
|
||||||
// onClose={() => setErrorMsg("")}
|
|
||||||
// />
|
|
||||||
|
|
||||||
// <LoadingOverlay show={loading} text="Upload to database..." />
|
|
||||||
|
|
||||||
// <h1 className="text-2xl font-bold mb-4">✅ Validasi & Konfirmasi Data</h1>
|
|
||||||
|
|
||||||
// <MetadataForm onChange={handleMetadataChange} />
|
|
||||||
|
|
||||||
// <div className="w-full mx-auto mt-6">
|
|
||||||
// <label
|
|
||||||
// htmlFor="tableTitle"
|
|
||||||
// className="block text-sm font-medium text-gray-700 mb-2"
|
|
||||||
// >
|
|
||||||
// Judul Tabel
|
|
||||||
// </label>
|
|
||||||
// <input
|
|
||||||
// id="tableTitle"
|
|
||||||
// type="text"
|
|
||||||
// value={tableTitle}
|
|
||||||
// onChange={(e) => setTableTitle(e.target.value)}
|
|
||||||
// placeholder="Masukkan judul tabel..."
|
|
||||||
// className={`w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 ${
|
|
||||||
// !tableTitle ? "border-red-400" : ""
|
|
||||||
// }`}
|
|
||||||
// />
|
|
||||||
// <small className="text-gray-500">
|
|
||||||
// Judul akan dijadikan nama tabel pada database
|
|
||||||
// </small>
|
|
||||||
// </div>
|
|
||||||
|
|
||||||
// {result && <FilePreview result={result} />}
|
|
||||||
|
|
||||||
// <div className="mt-6 flex justify-between">
|
|
||||||
// <button
|
|
||||||
// onClick={() => history.back()}
|
|
||||||
// className="px-5 py-2 text-blue-600 hover:underline"
|
|
||||||
// >
|
|
||||||
// ← Kembali
|
|
||||||
// </button>
|
|
||||||
|
|
||||||
// <button
|
|
||||||
// onClick={handleUploadClick}
|
|
||||||
// disabled={loading}
|
|
||||||
// className="px-5 py-2 bg-green-600 text-white rounded hover:bg-green-700 disabled:bg-gray-400"
|
|
||||||
// >
|
|
||||||
// {loading ? "Mengunggah..." : "Upload ke Database"}
|
|
||||||
// </button>
|
|
||||||
// </div>
|
|
||||||
// </div>
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// <div className="bg-white border rounded-xl shadow-sm p-6 mt-4">
|
|
||||||
// <FilePreview result={result} />
|
|
||||||
// </div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useUploadController } from "./controller_admin_upload";
|
import { useUploadController } from "./controller_admin_upload";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
|
|
@ -167,6 +8,11 @@ import Notification from "../../../components/Notification";
|
||||||
import ErrorNotification from "../../../components/ErrorNotification";
|
import ErrorNotification from "../../../components/ErrorNotification";
|
||||||
import MetadataForm from "../../../components/MetaDataForm";
|
import MetadataForm from "../../../components/MetaDataForm";
|
||||||
import FilePreview from "../../../components/upload/FilePreview";
|
import FilePreview from "../../../components/upload/FilePreview";
|
||||||
|
import ConfirmDialog from "../../../components/common/ConfirmDialog"
|
||||||
|
|
||||||
|
import GeoPreview from "@/components/layers_preview/GeoPreview";
|
||||||
|
import StylingLayers from "@/components/layers_style/StylingLayers";
|
||||||
|
import SpatialStylePreview from "@/components/layers_style/StylePreview";
|
||||||
|
|
||||||
// shadcn accordion (pastikan path sesuai proyekmu)
|
// shadcn accordion (pastikan path sesuai proyekmu)
|
||||||
import {
|
import {
|
||||||
|
|
@ -175,6 +21,14 @@ import {
|
||||||
AccordionTrigger,
|
AccordionTrigger,
|
||||||
AccordionContent,
|
AccordionContent,
|
||||||
} from "../../../components/ui/accordion";
|
} from "../../../components/ui/accordion";
|
||||||
|
import {
|
||||||
|
Sheet,
|
||||||
|
SheetTrigger,
|
||||||
|
SheetContent,
|
||||||
|
SheetHeader,
|
||||||
|
SheetTitle,
|
||||||
|
SheetDescription,
|
||||||
|
} from "../../../components/ui/sheet";
|
||||||
|
|
||||||
export default function ViewsAdminUploadValidate() {
|
export default function ViewsAdminUploadValidate() {
|
||||||
const { result } = useSelector((state) => state.upload);
|
const { result } = useSelector((state) => state.upload);
|
||||||
|
|
@ -183,13 +37,20 @@ export default function ViewsAdminUploadValidate() {
|
||||||
tableTitle,
|
tableTitle,
|
||||||
setTableTitle,
|
setTableTitle,
|
||||||
handleConfirmUpload,
|
handleConfirmUpload,
|
||||||
|
handleGetStyles,
|
||||||
|
geosStyle
|
||||||
} = useUploadController();
|
} = useUploadController();
|
||||||
|
|
||||||
|
const [styleConfig, setStyleConfig] = useState(null);
|
||||||
|
|
||||||
const [errorMsg, setErrorMsg] = useState("");
|
const [errorMsg, setErrorMsg] = useState("");
|
||||||
const [showAlert, setShowAlert] = useState(false);
|
const [showAlert, setShowAlert] = useState(false);
|
||||||
const [alertMessage, setAlertMessage] = useState("");
|
const [alertMessage, setAlertMessage] = useState("");
|
||||||
const [alertType, setAlertType] = useState("info");
|
const [alertType, setAlertType] = useState("info");
|
||||||
|
|
||||||
|
const [openSheet, setOpenSheet] = useState(false);
|
||||||
|
const [showStylePreview, setShowStylePreview] = useState(false);
|
||||||
|
|
||||||
// Local state: index tabel yg dipilih (default 0)
|
// Local state: index tabel yg dipilih (default 0)
|
||||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||||
// Metadata form state is emitted via onChange from MetadataForm; simpan jika perlu
|
// Metadata form state is emitted via onChange from MetadataForm; simpan jika perlu
|
||||||
|
|
@ -200,6 +61,11 @@ export default function ViewsAdminUploadValidate() {
|
||||||
|
|
||||||
// Keep selectedIndex valid ketika result berubah
|
// Keep selectedIndex valid ketika result berubah
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
console.log('review', result);
|
||||||
|
|
||||||
|
handleGetStyles()
|
||||||
|
setTimeout(() => setShowStylePreview(true), 500);
|
||||||
|
|
||||||
if (!result || !result.tables || result.tables.length === 0) {
|
if (!result || !result.tables || result.tables.length === 0) {
|
||||||
setSelectedIndex(0);
|
setSelectedIndex(0);
|
||||||
return;
|
return;
|
||||||
|
|
@ -213,6 +79,15 @@ export default function ViewsAdminUploadValidate() {
|
||||||
});
|
});
|
||||||
}, [result]);
|
}, [result]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (openSheet) {
|
||||||
|
const timer = setTimeout(() => setShowStylePreview(true), 600);
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
} else {
|
||||||
|
setShowStylePreview(false);
|
||||||
|
}
|
||||||
|
}, [openSheet]);
|
||||||
|
|
||||||
const handleUploadClick = async () => {
|
const handleUploadClick = async () => {
|
||||||
if (!tableTitle || !tableTitle.trim()) {
|
if (!tableTitle || !tableTitle.trim()) {
|
||||||
setAlertMessage("❗Judul tabel belum diisi. Silakan isi sebelum melanjutkan.");
|
setAlertMessage("❗Judul tabel belum diisi. Silakan isi sebelum melanjutkan.");
|
||||||
|
|
@ -221,7 +96,7 @@ export default function ViewsAdminUploadValidate() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await handleConfirmUpload(metadata);
|
await handleConfirmUpload(metadata, styleConfig.sldContent);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// tangani error dari controller/service
|
// tangani error dari controller/service
|
||||||
const message =
|
const message =
|
||||||
|
|
@ -234,8 +109,16 @@ export default function ViewsAdminUploadValidate() {
|
||||||
|
|
||||||
const selectedTable = result.tables?.[selectedIndex] || null;
|
const selectedTable = result.tables?.[selectedIndex] || null;
|
||||||
|
|
||||||
|
const handleStyleSubmit = (config) => {
|
||||||
|
setStyleConfig(config)
|
||||||
|
console.log("Konfigurasi styling:", config);
|
||||||
|
// Kirim ke backend → generate SLD otomatis → publish ke GeoServer
|
||||||
|
};
|
||||||
|
|
||||||
|
const [index, setIndex] = useState(0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="py-10">
|
<div className="p-0 h-[calc(100vh-(57px+48px))] overflow-hidden">
|
||||||
{/* Alerts */}
|
{/* Alerts */}
|
||||||
{showAlert && (
|
{showAlert && (
|
||||||
<Notification
|
<Notification
|
||||||
|
|
@ -247,66 +130,158 @@ export default function ViewsAdminUploadValidate() {
|
||||||
<ErrorNotification message={errorMsg} onClose={() => setErrorMsg("")} />
|
<ErrorNotification message={errorMsg} onClose={() => setErrorMsg("")} />
|
||||||
<LoadingOverlay show={loading} text="Upload to database..." />
|
<LoadingOverlay show={loading} text="Upload to database..." />
|
||||||
|
|
||||||
<h1 className="text-2xl font-bold mb-4">✅ Validasi & Konfirmasi Data</h1>
|
<div
|
||||||
|
className="h-full w-full transition-transform duration-700 ease-in-out"
|
||||||
|
style={{ transform: `translateY(-${index * 100}vh)` }}
|
||||||
|
>
|
||||||
|
<div className="w-full h-full">
|
||||||
|
<h1 className="text-2xl font-bold mb-4">✅ Validasi & Konfirmasi Data</h1>
|
||||||
|
|
||||||
{/* SINGLE ACCORDION */}
|
{/* <GeoPreview features={result.preview} /> */}
|
||||||
<Accordion type="single" collapsible defaultValue="validate-panel" className="w-full">
|
|
||||||
<AccordionItem value="validate-panel" className="bg-white rounded-xl border shadow-sm px-3 mb-4">
|
|
||||||
<AccordionTrigger className="text-lg font-semibold">
|
|
||||||
📄 Dataset 1
|
|
||||||
</AccordionTrigger>
|
|
||||||
|
|
||||||
<AccordionContent>
|
{/* SINGLE ACCORDION */}
|
||||||
<div className="mt-4 grid grid-cols-1 lg:grid-cols-12 gap-8">
|
<Accordion type="single" collapsible defaultValue="validate-panel" className="w-full">
|
||||||
{/* LEFT: tabel preview (6 kolom pada layout 12) */}
|
<AccordionItem value="validate-panel" className="bg-white rounded-xl border shadow-sm px-3 mb-4">
|
||||||
<div className="lg:col-span-6 col-span-1 min-w-0">
|
<AccordionTrigger className="text-lg font-semibold">
|
||||||
<h3 className="text-xl font-semibold mb-3">🧾 Cuplikan Data</h3>
|
📄 {result.file_name}
|
||||||
<div className="mb-3">
|
</AccordionTrigger>
|
||||||
<div className="flex gap-2 min-w-0">
|
|
||||||
<FilePreview result={result} />
|
<AccordionContent>
|
||||||
|
<div className="mt-4 grid grid-cols-1 lg:grid-cols-12 gap-8">
|
||||||
|
{/* LEFT: tabel preview (6 kolom pada layout 12) */}
|
||||||
|
<div className="lg:col-span-8 col-span-1 min-w-0">
|
||||||
|
<h3 className="text-xl font-semibold mb-3">🧾 Cuplikan Data</h3>
|
||||||
|
<div className="mb-3">
|
||||||
|
<div className="flex gap-2 min-w-0">
|
||||||
|
<FilePreview result={result} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* RIGHT: metadata form (6 kolom) */}
|
||||||
|
<div className="lg:col-span-4 col-span-1">
|
||||||
|
<div className="mb-3 flex justify-between items-center">
|
||||||
|
<h3 className="text-xl font-semibold mb-0">🧾 Info dataset</h3>
|
||||||
|
<span className="text-gray-500 italic">AI Generate</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* MetadataForm menyimpan hasil ke parent via onChange */}
|
||||||
|
<MetadataForm
|
||||||
|
initialValues={result.metadata_suggest}
|
||||||
|
onChange={(data) => setMetadata(data)}
|
||||||
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* RIGHT: metadata form (6 kolom) */}
|
{/* ACTIONS di bawah accordion content */}
|
||||||
<div className="lg:col-span-6 col-span-1">
|
<div className="mt-6 flex justify-between">
|
||||||
<h3 className="text-xl font-semibold mb-3">🧾 Metadata</h3>
|
<button
|
||||||
|
onClick={() => history.back()}
|
||||||
|
className="px-5 py-2 text-blue-600 hover:underline"
|
||||||
|
>
|
||||||
|
← Kembali
|
||||||
|
</button>
|
||||||
|
|
||||||
{/* MetadataForm menyimpan hasil ke parent via onChange */}
|
<div className="flex items-center gap-3">
|
||||||
<MetadataForm onChange={(data) => setMetadata(data)}/>
|
{/* optional: show metadata summary brief */}
|
||||||
|
{metadata && (
|
||||||
</div>
|
<div className="text-xs text-gray-600">
|
||||||
</div>
|
Metadata siap — preview: <span className="font-medium">{metadata.title || "-"}</span>
|
||||||
|
</div>
|
||||||
{/* ACTIONS di bawah accordion content */}
|
)}
|
||||||
<div className="mt-6 flex justify-between">
|
{/* <button
|
||||||
<button
|
onClick={handleGetStyles}
|
||||||
onClick={() => history.back()}
|
className="px-5 py-2 bg-yellow-600 text-white rounded hover:bg-yellow-700"
|
||||||
className="px-5 py-2 text-blue-600 hover:underline"
|
></button> */}
|
||||||
>
|
<button
|
||||||
← Kembali
|
onClick={() => setIndex(1)}
|
||||||
</button>
|
className="px-5 py-2 bg-blue-600 text-white rounded"
|
||||||
|
>
|
||||||
<div className="flex items-center gap-3">
|
Selanjutnya ↓
|
||||||
{/* optional: show metadata summary brief */}
|
</button>
|
||||||
{metadata && (
|
{/* <button
|
||||||
<div className="text-xs text-gray-600">
|
onClick={handleUploadClick}
|
||||||
Metadata siap — preview: <span className="font-medium">{metadata.title || "-"}</span>
|
disabled={loading}
|
||||||
|
className="px-5 py-2 bg-green-600 text-white rounded hover:bg-green-700 disabled:bg-gray-400"
|
||||||
|
>
|
||||||
|
{loading ? "Mengunggah..." : "Upload ke Database"} ↓
|
||||||
|
</button> */}
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-[81px] pt-6 h-full w-full flex flex-wrap items-stretch">
|
||||||
|
<h2 className="w-full mb-2 text-xl font-bold">Preview Style</h2>
|
||||||
|
<div className="w-[60%] h-[calc(100%-68px)]">
|
||||||
|
{showStylePreview &&
|
||||||
|
<SpatialStylePreview data={result.preview} geometryType={result.geometry_type} styleConfig={styleConfig}/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div className="w-[40%] h-[calc(100%-68px)]">
|
||||||
|
<StylingLayers data={result.preview} geometryType={result.geometry_type} onSubmit={handleStyleSubmit} geosStyle={geosStyle}/>
|
||||||
|
</div>
|
||||||
|
<div className="mt-3 w-full h-fit flex gap-1">
|
||||||
|
<button
|
||||||
|
onClick={() => setIndex(0)}
|
||||||
|
className="w-[60%] px-4 py-2 bg-blue-600 text-white rounded"
|
||||||
|
>
|
||||||
|
Kembali ↑
|
||||||
|
</button>
|
||||||
|
{/* <button
|
||||||
|
onClick={handleUploadClick}
|
||||||
|
disabled={loading}
|
||||||
|
className="w-[40%] px-5 py-2 bg-green-600 text-white rounded hover:bg-green-700 disabled:bg-gray-400"
|
||||||
|
>
|
||||||
|
{loading ? "Mengunggah..." : "Upload ke Database"}
|
||||||
|
</button> */}
|
||||||
|
<ConfirmDialog
|
||||||
|
title="Upload ke database?"
|
||||||
|
description="Pastikan data sudah benar sebelum diunggah."
|
||||||
|
confirmText="Ya, Upload"
|
||||||
|
cancelText="Batal"
|
||||||
|
onConfirm={handleUploadClick}
|
||||||
|
trigger={
|
||||||
<button
|
<button
|
||||||
onClick={handleUploadClick}
|
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
className="px-5 py-2 bg-green-600 text-white rounded hover:bg-green-700 disabled:bg-gray-400"
|
className="w-[40%] px-5 py-2 bg-green-600 text-white rounded hover:bg-green-700 disabled:bg-gray-400"
|
||||||
>
|
>
|
||||||
{loading ? "Mengunggah..." : "Upload ke Database"}
|
{loading ? "Mengunggah..." : "Upload ke Database"}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* <Sheet open={openSheet} onOpenChange={setOpenSheet}>
|
||||||
|
|
||||||
|
<SheetContent
|
||||||
|
side="bottom"
|
||||||
|
className="h-[90vh] overflow-hidden p-0"
|
||||||
|
>
|
||||||
|
<SheetHeader className="px-4 py-2 border-b">
|
||||||
|
<SheetTitle>Style Editor</SheetTitle>
|
||||||
|
<SheetDescription>
|
||||||
|
Edit your preview and styling layers.
|
||||||
|
</SheetDescription>
|
||||||
|
</SheetHeader>
|
||||||
|
|
||||||
|
<div className="flex h-full w-full">
|
||||||
|
<div className="w-[70%] h-full border-r overflow-auto">
|
||||||
|
{showStylePreview && <SpatialStylePreview data={result.preview} geometryType={result.geometry_type} styleConfig={styleConfig}/>}
|
||||||
</div>
|
</div>
|
||||||
</AccordionContent>
|
|
||||||
</AccordionItem>
|
<div className="w-[30%] h-full overflow-auto pb-20">
|
||||||
</Accordion>
|
<StylingLayers data={result.preview} geometryType={result.geometry_type} onSubmit={handleStyleSubmit} geosStyle={geosStyle}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</SheetContent>
|
||||||
|
</Sheet> */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user