add loader overlay
This commit is contained in:
parent
d85beefd8b
commit
1462a1ff54
49
package-lock.json
generated
49
package-lock.json
generated
|
|
@ -11,6 +11,7 @@
|
||||||
"@reduxjs/toolkit": "^2.9.2",
|
"@reduxjs/toolkit": "^2.9.2",
|
||||||
"@tailwindcss/vite": "^4.1.16",
|
"@tailwindcss/vite": "^4.1.16",
|
||||||
"axios": "^1.13.0",
|
"axios": "^1.13.0",
|
||||||
|
"framer-motion": "^12.23.24",
|
||||||
"jwt-decode": "^4.0.0",
|
"jwt-decode": "^4.0.0",
|
||||||
"pdfjs-dist": "^5.4.394",
|
"pdfjs-dist": "^5.4.394",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
|
|
@ -2489,6 +2490,33 @@
|
||||||
"node": ">=0.8"
|
"node": ">=0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/framer-motion": {
|
||||||
|
"version": "12.23.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.24.tgz",
|
||||||
|
"integrity": "sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"motion-dom": "^12.23.23",
|
||||||
|
"motion-utils": "^12.23.6",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/is-prop-valid": "*",
|
||||||
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@emotion/is-prop-valid": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fsevents": {
|
"node_modules/fsevents": {
|
||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
|
|
@ -3125,6 +3153,21 @@
|
||||||
"node": "*"
|
"node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/motion-dom": {
|
||||||
|
"version": "12.23.23",
|
||||||
|
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz",
|
||||||
|
"integrity": "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"motion-utils": "^12.23.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/motion-utils": {
|
||||||
|
"version": "12.23.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz",
|
||||||
|
"integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
|
@ -3648,6 +3691,12 @@
|
||||||
"url": "https://github.com/sponsors/SuperchupuDev"
|
"url": "https://github.com/sponsors/SuperchupuDev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tslib": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
|
"license": "0BSD"
|
||||||
|
},
|
||||||
"node_modules/type-check": {
|
"node_modules/type-check": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
"@reduxjs/toolkit": "^2.9.2",
|
"@reduxjs/toolkit": "^2.9.2",
|
||||||
"@tailwindcss/vite": "^4.1.16",
|
"@tailwindcss/vite": "^4.1.16",
|
||||||
"axios": "^1.13.0",
|
"axios": "^1.13.0",
|
||||||
|
"framer-motion": "^12.23.24",
|
||||||
"jwt-decode": "^4.0.0",
|
"jwt-decode": "^4.0.0",
|
||||||
"pdfjs-dist": "^5.4.394",
|
"pdfjs-dist": "^5.4.394",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
|
|
|
||||||
43
src/components/LoadingOverlay.jsx
Normal file
43
src/components/LoadingOverlay.jsx
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
|
||||||
|
export default function LoadingOverlay({ show = false, text = "Memproses data..." }) {
|
||||||
|
if (!show) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 z-50 flex flex-col items-center justify-center bg-white/30">
|
||||||
|
{/* Animated Dots */}
|
||||||
|
<motion.div
|
||||||
|
className="flex gap-3 mb-4"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ duration: 0.6 }}
|
||||||
|
>
|
||||||
|
{[0, 1, 2].map((i) => (
|
||||||
|
<motion.span
|
||||||
|
key={i}
|
||||||
|
className="w-4 h-4 bg-blue-600 rounded-full"
|
||||||
|
animate={{
|
||||||
|
y: [0, -8, 0],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
repeat: Infinity,
|
||||||
|
duration: 0.8,
|
||||||
|
delay: i * 0.2,
|
||||||
|
ease: "easeInOut",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Text with subtle fade animation */}
|
||||||
|
<motion.p
|
||||||
|
className="text-gray-700 text-sm font-medium"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: [0.3, 1, 0.3] }}
|
||||||
|
transition={{ duration: 1.5, repeat: Infinity }}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</motion.p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -11,7 +11,7 @@ export default function Notification({ message, type = "info", onClose }) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
onClose();
|
onClose();
|
||||||
}, 3000); // auto close 3 detik
|
}, 2000); // auto close 3 detik
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}, [onClose]);
|
}, [onClose]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -64,15 +64,16 @@ export function useUploadController() {
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
console.log(selectedPages);
|
|
||||||
const res = await uploadFile(file, selectedPages, selectedSheet);
|
const res = await uploadFile(file, selectedPages, selectedSheet);
|
||||||
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)) {
|
||||||
navigate("/admin/upload/validate");
|
navigate("/admin/upload/validate");
|
||||||
|
}else{
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
// setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -84,7 +85,7 @@ export function useUploadController() {
|
||||||
dispatch(setResult(res));
|
dispatch(setResult(res));
|
||||||
navigate("/admin/upload/validate");
|
navigate("/admin/upload/validate");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
// setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -100,7 +101,7 @@ export function useUploadController() {
|
||||||
dispatch(setValidatedData(res));
|
dispatch(setValidatedData(res));
|
||||||
navigate("/admin/upload/success");
|
navigate("/admin/upload/success");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
// setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ export async function uploadFile(file, page = null, sheet = null) {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await api.post("/upload", formData, {
|
const response = await api.post("/upload/file", formData, {
|
||||||
headers: { "Content-Type": "multipart/form-data" },
|
headers: { "Content-Type": "multipart/form-data" },
|
||||||
});
|
});
|
||||||
return response.data;
|
return response.data;
|
||||||
|
|
@ -47,7 +47,7 @@ export async function uploadFile(file, page = null, sheet = null) {
|
||||||
|
|
||||||
export async function uploadPdf(data) {
|
export async function uploadPdf(data) {
|
||||||
try {
|
try {
|
||||||
const response = await api.post("/process-pdf", data, {
|
const response = await api.post("/upload/process-pdf", data, {
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
});
|
});
|
||||||
return response.data;
|
return response.data;
|
||||||
|
|
@ -60,7 +60,7 @@ export async function uploadPdf(data) {
|
||||||
export async function saveToDatabase(data) {
|
export async function saveToDatabase(data) {
|
||||||
console.log("send:", 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" },
|
||||||
});
|
});
|
||||||
return response.data;
|
return response.data;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
import { useUploadController } from "./controller_admin_upload";
|
import { useUploadController } from "./controller_admin_upload";
|
||||||
import { useDispatch } from "react-redux";
|
import { useDispatch } from "react-redux";
|
||||||
|
import LoadingOverlay from "../../../components/LoadingOverlay";
|
||||||
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 { useEffect } from "react";
|
||||||
|
import { reset } from "../../../store/slices/uploadSlice";
|
||||||
|
|
||||||
export default function ViewsAdminUploadStep1() {
|
export default function ViewsAdminUploadStep1() {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
@ -32,8 +35,16 @@ export default function ViewsAdminUploadStep1() {
|
||||||
|
|
||||||
const ext = file ? file.name.split(".").pop().toLowerCase() : "";
|
const ext = file ? file.name.split(".").pop().toLowerCase() : "";
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(reset())
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-4xl mx-auto py-10">
|
<div className="max-w-4xl mx-auto py-10">
|
||||||
|
|
||||||
|
<LoadingOverlay show={loading} text="Processing..." />
|
||||||
|
|
||||||
<div className="mb-6 flex justify-between items-center">
|
<div className="mb-6 flex justify-between items-center">
|
||||||
<h1 className="text-2xl font-bold text-gray-800">Upload Data</h1>
|
<h1 className="text-2xl font-bold text-gray-800">Upload Data</h1>
|
||||||
<p className="text-lg text-gray-600">
|
<p className="text-lg text-gray-600">
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
import { useUploadController } from "./controller_admin_upload";
|
import { useUploadController } from "./controller_admin_upload";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { useState, useEffect } from "react";
|
import LoadingOverlay from "../../../components/LoadingOverlay";
|
||||||
import Notification from "../../../components/Notification";
|
import Notification from "../../../components/Notification";
|
||||||
import { Navigate } from "react-router-dom";
|
import { Navigate } from "react-router-dom";
|
||||||
import FilePreview from "../../../components/upload/FilePreview";
|
import FilePreview from "../../../components/upload/FilePreview";
|
||||||
|
|
@ -66,9 +67,9 @@ export default function ViewsAdminUploadValidate() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setAlertMessage("Mengunggah ke database...");
|
// setAlertMessage("Mengunggah ke database...");
|
||||||
setAlertType("info");
|
// setAlertType("info");
|
||||||
setShowAlert(true);
|
// setShowAlert(true);
|
||||||
handleConfirmUpload();
|
handleConfirmUpload();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -82,6 +83,8 @@ export default function ViewsAdminUploadValidate() {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<LoadingOverlay show={loading} text="Upload to database..." />
|
||||||
|
|
||||||
<h1 className="text-2xl font-bold mb-4">✅ Validasi & Konfirmasi Data</h1>
|
<h1 className="text-2xl font-bold mb-4">✅ Validasi & Konfirmasi Data</h1>
|
||||||
|
|
||||||
<div className="w-full mx-auto mt-6">
|
<div className="w-full mx-auto mt-6">
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,8 @@ const uploadSlice = createSlice({
|
||||||
state.file = null;
|
state.file = null;
|
||||||
state.result = null;
|
state.result = null;
|
||||||
state.validatedData = null;
|
state.validatedData = null;
|
||||||
|
state.pdfPageCount = null;
|
||||||
|
state.selectedPages = null;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user