diff --git a/src/pages/admin/home/views_admin_home.jsx b/src/pages/admin/home/views_admin_home.jsx
index 28c1b7d..d203740 100644
--- a/src/pages/admin/home/views_admin_home.jsx
+++ b/src/pages/admin/home/views_admin_home.jsx
@@ -9,9 +9,22 @@ import {
DropdownMenuSeparator
} from "../../../components/ui/dropdown-menu";
import { Link } from "react-router-dom";
+import { useState } from "react";
export default function ViewsAdminHome() {
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 (
- {datasets.map((item) => (
+ {filteredData.map((item) => (
- {item.dataset_status}
+ {item.process}
@@ -71,11 +161,6 @@ export default function ViewsAdminHome() {
{item.table_title}
-
- Kategori:{" "}
- {item.topic_category}
-
-
Organisasi:{" "}
{item.organization_name}
@@ -134,9 +219,9 @@ export default function ViewsAdminHome() {
{/* MORE MENU */}
-
diff --git a/src/pages/admin/upload/controller_admin_upload.jsx b/src/pages/admin/upload/controller_admin_upload.jsx
index 7126e10..0c2fc78 100644
--- a/src/pages/admin/upload/controller_admin_upload.jsx
+++ b/src/pages/admin/upload/controller_admin_upload.jsx
@@ -1,7 +1,7 @@
import { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
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 * as pdfjsLib from "pdfjs-dist";
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
@@ -14,7 +14,7 @@ import * as XLSX from 'xlsx';
export function useUploadController() {
const dispatch = useDispatch();
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 [selectedTable, setSelectedTable] = useState(null);
@@ -24,6 +24,11 @@ export function useUploadController() {
const [selectedSheet, setSelectedSheet] = useState(null);
const [sheetCount, setSheetCount] = useState(null);
const [sheetNames, setSheetNames] = useState([]);
+
+ // const [filedesc, setFileDesc] = useState(null);
+
+ const [geosStyle, setGeosStyle] = useState([]);
+ const [fileReady, setFileReady] = useState(false);
// ๐น handle drop file
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") {
const data = await f.arrayBuffer();
@@ -81,7 +87,7 @@ export function useUploadController() {
if (!file) return;
setLoading(true);
try {
- const res = await uploadFile(file, selectedPages, selectedSheet);
+ const res = await uploadFile(file, selectedPages, selectedSheet, fileDesc);
dispatch(setResult(res));
if (res.file_type !== ".pdf" || (res.file_type === ".pdf" && !res.tables)) {
@@ -99,7 +105,7 @@ export function useUploadController() {
if (!selectedTable) return;
setLoading(true);
try {
- const res = await uploadPdf(selectedTable);
+ const res = await uploadPdf(selectedTable, file.name, fileDesc);
dispatch(setResult(res));
navigate("/admin/upload/validate");
} catch(err){
@@ -108,14 +114,15 @@ export function useUploadController() {
}
};
- const handleConfirmUpload = async (metadata) => {
+ const handleConfirmUpload = async (metadata, style) => {
setLoading(true);
try {
const data = {
title: metadata.title,
columns: result.columns,
rows: result.preview,
- author: metadata
+ author: metadata,
+ style: style
};
const res = await saveToDatabase(data);
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 {
loading,
file,
@@ -145,5 +161,7 @@ export function useUploadController() {
handleUpload,
handleNextPdf,
handleConfirmUpload,
+ handleGetStyles,
+ geosStyle
};
}
diff --git a/src/pages/admin/upload/pdf_viewer/controller_pdf_viewer.jsx b/src/pages/admin/upload/pdf_viewer/controller_pdf_viewer.jsx
index 18621bd..cde9c5c 100644
--- a/src/pages/admin/upload/pdf_viewer/controller_pdf_viewer.jsx
+++ b/src/pages/admin/upload/pdf_viewer/controller_pdf_viewer.jsx
@@ -13,7 +13,7 @@ pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
export function usePdfViewerController() {
const dispatch = useDispatch();
const navigate = useNavigate();
- const { file, selectedPages } = useSelector((state) => state.upload);
+ const { file, fileDesc } = useSelector((state) => state.upload);
const [pages, setPages] = useState([]);
const [selectedPagesLocal, setSelectedPagesLocal] = useState([]);
@@ -73,7 +73,7 @@ export function usePdfViewerController() {
if (selectedPagesLocal.length === 0) return;
try {
setLoading(true);
- const res = await uploadFile(file, selectedPagesLocal);
+ const res = await uploadFile(file, selectedPagesLocal, null, fileDesc);
dispatch(setResult(res));
if (!res.tables) {
diff --git a/src/pages/admin/upload/service_admin_upload.jsx b/src/pages/admin/upload/service_admin_upload.jsx
index 48687ed..c1b45fa 100644
--- a/src/pages/admin/upload/service_admin_upload.jsx
+++ b/src/pages/admin/upload/service_admin_upload.jsx
@@ -26,13 +26,15 @@
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();
formData.append("file", file);
formData.append("page", page);
if (sheet != null) {
formData.append("sheet", sheet);
}
+ formData.append("file_desc", file_desc);
try {
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 {
- const response = await api.post("/upload/process-pdf", data, {
+ const response = await api.post(`/upload/process-pdf`, payload, {
headers: { "Content-Type": "application/json" },
});
return response.data.data;
@@ -56,7 +63,6 @@ export async function uploadPdf(data) {
}
export async function saveToDatabase(data) {
- console.log("send:", data);
try {
const response = await api.post("/upload/to-postgis", data, {
headers: { "Content-Type": "application/json" },
@@ -66,3 +72,27 @@ export async function saveToDatabase(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;
+ }
+}
diff --git a/src/pages/admin/upload/table_picker/controller_admin_table_picker.jsx b/src/pages/admin/upload/table_picker/controller_admin_table_picker.jsx
index c6406da..d83a328 100644
--- a/src/pages/admin/upload/table_picker/controller_admin_table_picker.jsx
+++ b/src/pages/admin/upload/table_picker/controller_admin_table_picker.jsx
@@ -8,7 +8,7 @@ export function useTablePickerController() {
const dispatch = useDispatch();
const navigate = useNavigate();
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(
result?.tables?.[0] || null
);
@@ -22,7 +22,8 @@ export function useTablePickerController() {
if (!selectedTable) return;
setLoading(true);
try {
- const res = await uploadPdf(selectedTable);
+ console.log('pdf', selectedTable);
+ const res = await uploadPdf(selectedTable, file.name, fileDesc);
dispatch(setResult(res));
navigate("/admin/upload/validate");
} catch(err){
diff --git a/src/pages/admin/upload/views_admin_success_upload.jsx b/src/pages/admin/upload/views_admin_success_upload.jsx
index d0fc52e..3a6040f 100644
--- a/src/pages/admin/upload/views_admin_success_upload.jsx
+++ b/src/pages/admin/upload/views_admin_success_upload.jsx
@@ -1,3 +1,4 @@
+import { useEffect, useState, useRef } from "react";
import { useSelector } from "react-redux";
import { Link } from "react-router-dom";
import { Navigate } from "react-router-dom";
@@ -13,14 +14,110 @@ export default function ViewsAdminUploadSuccess() {
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 = {
+ upload: "done",
+ cleansing: "pending",
+ publish_geoserver: "pending",
+ done: "pending",
+ };
+
+ const Spinner = () => (
+
+ );
+ const renderIcon = (status) => {
+ if (status === "running") return ;
+ if (status === "done") return "โ";
+ if (status === "error") return "โ";
+ return "โฌ";
+ };
+
+
+ const [stepStatus, setStepStatus] = useState(INITIAL_STEP_STATUS);
+ const wsRef = useRef(null);
+
if (!validatedData) {
return ;
- }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 (
-
+
โ
Upload Berhasil!
Data Anda berhasil disimpan ke database.
@@ -93,16 +190,53 @@ export default function ViewsAdminUploadSuccess() {
)}
- {validatedData.message && (
+ {/* {validatedData.message && (
- {/* {validatedData.message} */}
- Datasets berhasil di upload
+ Data sedang diproses
+ )} */}
+ {validatedData.message && (
+
+ {PROCESS_STEPS.map((step) => (
+
+
+
+ {renderIcon(stepStatus[step.key] || "-")}
+
+
+
+ {step.label}
+
+
+ ))}
+
)}
+
+ Sistem sedang melakukan cleansing data dan publikasi ke GeoServer dan GeoNetwork.
+ Anda tidak perlu menunggu di halaman ini.
+
+
{/* Metadata Section */}
{validatedData.metadata && (
@@ -114,13 +248,20 @@ export default function ViewsAdminUploadSuccess() {
)}
-
-
- Kembali ke Dashboard
-
+
+
+ Kembali ke Dashboard
+
+
+ Upload data lagi
+
+
);
}
diff --git a/src/pages/admin/upload/views_admin_upload.jsx b/src/pages/admin/upload/views_admin_upload.jsx
index f922db0..4b08440 100644
--- a/src/pages/admin/upload/views_admin_upload.jsx
+++ b/src/pages/admin/upload/views_admin_upload.jsx
@@ -1,14 +1,17 @@
import { useEffect, useState } from "react";
import { useUploadController } from "./controller_admin_upload";
import { useDispatch } from "react-redux";
+import { useNavigate } from "react-router-dom";
import LoadingOverlay from "../../../components/LoadingOverlay";
import ErrorNotification from "../../../components/ErrorNotification";
import FileDropzone from "../../../components/FileDropzone";
import PdfPageSelector from "../../../components/PdfPageSelector";
import { Link } from "react-router-dom";
import { reset } from "../../../store/slices/uploadSlice";
+import { setFileDesc } from "../../../store/slices/uploadSlice";
export default function ViewsAdminUploadStep1() {
+ const navigate = useNavigate();
const dispatch = useDispatch();
const {
loading,
@@ -37,10 +40,14 @@ export default function ViewsAdminUploadStep1() {
};
const handleProcess = async () => {
- try {
- await handleUpload();
- } catch (err) {
- setErrorMsg(err);
+ if (ext === 'pdf') {
+ navigate("/admin/upload/pdf");
+ } else {
+ try {
+ await handleUpload();
+ } catch (err) {
+ setErrorMsg(err);
+ }
}
};
@@ -80,7 +87,7 @@ export default function ViewsAdminUploadStep1() {
{/* {!file && (
)} */}
- {file && ext!= 'pdf' && (
+ {file && (