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",
|
||||
"@tailwindcss/vite": "^4.1.16",
|
||||
"axios": "^1.13.0",
|
||||
"framer-motion": "^12.23.24",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"pdfjs-dist": "^5.4.394",
|
||||
"react": "^19.1.1",
|
||||
|
|
@ -2489,6 +2490,33 @@
|
|||
"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": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
|
|
@ -3125,6 +3153,21 @@
|
|||
"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": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
|
|
@ -3648,6 +3691,12 @@
|
|||
"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": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
"@reduxjs/toolkit": "^2.9.2",
|
||||
"@tailwindcss/vite": "^4.1.16",
|
||||
"axios": "^1.13.0",
|
||||
"framer-motion": "^12.23.24",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"pdfjs-dist": "^5.4.394",
|
||||
"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(() => {
|
||||
const timer = setTimeout(() => {
|
||||
onClose();
|
||||
}, 3000); // auto close 3 detik
|
||||
}, 2000); // auto close 3 detik
|
||||
return () => clearTimeout(timer);
|
||||
}, [onClose]);
|
||||
|
||||
|
|
|
|||
|
|
@ -64,15 +64,16 @@ export function useUploadController() {
|
|||
if (!file) return;
|
||||
setLoading(true);
|
||||
try {
|
||||
console.log(selectedPages);
|
||||
const res = await uploadFile(file, selectedPages, selectedSheet);
|
||||
dispatch(setResult(res));
|
||||
|
||||
if (res.file_type !== ".pdf" || (res.file_type === ".pdf" && !res.tables)) {
|
||||
navigate("/admin/upload/validate");
|
||||
}else{
|
||||
setLoading(false);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
// setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -84,7 +85,7 @@ export function useUploadController() {
|
|||
dispatch(setResult(res));
|
||||
navigate("/admin/upload/validate");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
// setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -100,7 +101,7 @@ export function useUploadController() {
|
|||
dispatch(setValidatedData(res));
|
||||
navigate("/admin/upload/success");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
// setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ export async function uploadFile(file, page = null, sheet = null) {
|
|||
}
|
||||
|
||||
try {
|
||||
const response = await api.post("/upload", formData, {
|
||||
const response = await api.post("/upload/file", formData, {
|
||||
headers: { "Content-Type": "multipart/form-data" },
|
||||
});
|
||||
return response.data;
|
||||
|
|
@ -47,7 +47,7 @@ export async function uploadFile(file, page = null, sheet = null) {
|
|||
|
||||
export async function uploadPdf(data) {
|
||||
try {
|
||||
const response = await api.post("/process-pdf", data, {
|
||||
const response = await api.post("/upload/process-pdf", data, {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
return response.data;
|
||||
|
|
@ -60,7 +60,7 @@ export async function uploadPdf(data) {
|
|||
export async function saveToDatabase(data) {
|
||||
console.log("send:", data);
|
||||
try {
|
||||
const response = await api.post("/upload_to_postgis", data, {
|
||||
const response = await api.post("/upload/to-postgis", data, {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
return response.data;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
import { useUploadController } from "./controller_admin_upload";
|
||||
import { useDispatch } from "react-redux";
|
||||
import LoadingOverlay from "../../../components/LoadingOverlay";
|
||||
import FileDropzone from "../../../components/FileDropzone";
|
||||
import PdfPageSelector from "../../../components/PdfPageSelector";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useEffect } from "react";
|
||||
import { reset } from "../../../store/slices/uploadSlice";
|
||||
|
||||
export default function ViewsAdminUploadStep1() {
|
||||
const dispatch = useDispatch();
|
||||
|
|
@ -32,8 +35,16 @@ export default function ViewsAdminUploadStep1() {
|
|||
|
||||
const ext = file ? file.name.split(".").pop().toLowerCase() : "";
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(reset())
|
||||
}, [])
|
||||
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto py-10">
|
||||
|
||||
<LoadingOverlay show={loading} text="Processing..." />
|
||||
|
||||
<div className="mb-6 flex justify-between items-center">
|
||||
<h1 className="text-2xl font-bold text-gray-800">Upload Data</h1>
|
||||
<p className="text-lg text-gray-600">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { useState, useEffect } from "react";
|
||||
import { useUploadController } from "./controller_admin_upload";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useState, useEffect } from "react";
|
||||
import LoadingOverlay from "../../../components/LoadingOverlay";
|
||||
import Notification from "../../../components/Notification";
|
||||
import { Navigate } from "react-router-dom";
|
||||
import FilePreview from "../../../components/upload/FilePreview";
|
||||
|
|
@ -66,9 +67,9 @@ export default function ViewsAdminUploadValidate() {
|
|||
return;
|
||||
}
|
||||
|
||||
setAlertMessage("Mengunggah ke database...");
|
||||
setAlertType("info");
|
||||
setShowAlert(true);
|
||||
// setAlertMessage("Mengunggah ke database...");
|
||||
// setAlertType("info");
|
||||
// setShowAlert(true);
|
||||
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>
|
||||
|
||||
<div className="w-full mx-auto mt-6">
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ const uploadSlice = createSlice({
|
|||
state.file = null;
|
||||
state.result = null;
|
||||
state.validatedData = null;
|
||||
state.pdfPageCount = null;
|
||||
state.selectedPages = null;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user