adding error alert
This commit is contained in:
parent
079af2e7fd
commit
e7098a1354
61
src/components/ErrorNotification.jsx
Normal file
61
src/components/ErrorNotification.jsx
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import { useEffect } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
|
||||
export default function ErrorNotification({ message, onClose, duration = 4000 }) {
|
||||
// Auto close setelah beberapa detik
|
||||
useEffect(() => {
|
||||
if (!message) return;
|
||||
const timer = setTimeout(() => {
|
||||
onClose && onClose();
|
||||
}, duration);
|
||||
return () => clearTimeout(timer);
|
||||
}, [message, onClose, duration]);
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{message && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -20 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className="fixed bottom-5 right-5 z-50 w-90 max-w-[90vw]"
|
||||
>
|
||||
<div className="flex items-start gap-3 bg-red-50 border border-red-200 rounded-lg p-4 shadow-lg">
|
||||
{/* Icon */}
|
||||
<div className="text-red-500 mt-0.5">
|
||||
<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="M12 9v3.75m0 3.75h.007v.007H12v-.007zM21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
{/* Text */}
|
||||
<div className="flex-1 text-sm text-red-800">
|
||||
<p className="font-semibold mb-1">Terjadi Kesalahan</p>
|
||||
<p className="leading-tight">{message}</p>
|
||||
</div>
|
||||
|
||||
{/* Close Button */}
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="text-red-500 hover:text-red-700 transition"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
);
|
||||
}
|
||||
|
|
@ -72,8 +72,9 @@ export function useUploadController() {
|
|||
}else{
|
||||
setLoading(false);
|
||||
}
|
||||
} finally {
|
||||
// setLoading(false);
|
||||
} catch(err){
|
||||
setLoading(false);
|
||||
throw err
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -84,8 +85,9 @@ export function useUploadController() {
|
|||
const res = await uploadPdf(selectedTable);
|
||||
dispatch(setResult(res));
|
||||
navigate("/admin/upload/validate");
|
||||
} finally {
|
||||
// setLoading(false);
|
||||
} catch(err){
|
||||
setLoading(false);
|
||||
throw err
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -100,8 +102,9 @@ export function useUploadController() {
|
|||
const res = await saveToDatabase(data);
|
||||
dispatch(setValidatedData(res));
|
||||
navigate("/admin/upload/success");
|
||||
} finally {
|
||||
// setLoading(false);
|
||||
} catch(err){
|
||||
setLoading(false);
|
||||
throw err
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -40,8 +40,7 @@ export async function uploadFile(file, page = null, sheet = null) {
|
|||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("Upload gagal:", error);
|
||||
throw error.response?.data || { message: "Gagal mengunggah file." };
|
||||
throw error.response?.data.detail || "Gagal proses file.";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -52,8 +51,7 @@ export async function uploadPdf(data) {
|
|||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("Gagal kirim JSON:", error);
|
||||
throw error.response?.data || { message: "Terjadi kesalahan." };
|
||||
throw error.response?.data.detail || { message: "Gagal proses file." };
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -65,7 +63,6 @@ export async function saveToDatabase(data) {
|
|||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("Gagal kirim JSON:", error);
|
||||
throw error.response?.data || { message: "Terjadi kesalahan." };
|
||||
throw error.response?.data.detail || { message: "Gagal upload data." };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { useUploadController } from "./controller_admin_upload";
|
||||
import { useDispatch } from "react-redux";
|
||||
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 { useEffect } from "react";
|
||||
import { reset } from "../../../store/slices/uploadSlice";
|
||||
|
||||
export default function ViewsAdminUploadStep1() {
|
||||
|
|
@ -26,6 +27,8 @@ export default function ViewsAdminUploadStep1() {
|
|||
handleUpload,
|
||||
handleNextPdf
|
||||
} = useUploadController();
|
||||
const [errorMsg, setErrorMsg] = useState("");
|
||||
const ext = file ? file.name.split(".").pop().toLowerCase() : "";
|
||||
|
||||
const handlePageSelection = (pages) => {
|
||||
console.log("Halaman PDF yang dipilih:", pages);
|
||||
|
|
@ -33,16 +36,34 @@ export default function ViewsAdminUploadStep1() {
|
|||
dispatch(setSelectedPages(pages));
|
||||
};
|
||||
|
||||
const ext = file ? file.name.split(".").pop().toLowerCase() : "";
|
||||
const handleProcess = async () => {
|
||||
try {
|
||||
await handleUpload();
|
||||
} catch (err) {
|
||||
setErrorMsg(err);
|
||||
}
|
||||
};
|
||||
|
||||
const handleProcessPdf = async () => {
|
||||
try {
|
||||
await handleNextPdf();
|
||||
} catch (err) {
|
||||
setErrorMsg(err);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(reset())
|
||||
}, [])
|
||||
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto py-10">
|
||||
|
||||
<ErrorNotification
|
||||
message={errorMsg}
|
||||
onClose={() => setErrorMsg("")}
|
||||
/>
|
||||
|
||||
<LoadingOverlay show={loading} text="Processing..." />
|
||||
|
||||
<div className="mb-6 flex justify-between items-center">
|
||||
|
|
@ -119,7 +140,7 @@ export default function ViewsAdminUploadStep1() {
|
|||
{/* Tombol Upload */}
|
||||
<div className={`mt-6 flex justify-center ${(result && result.file_type === ".pdf" && result.tables?.length > 1) ? 'hidden' : 'block' }`}>
|
||||
<button
|
||||
onClick={handleUpload}
|
||||
onClick={handleProcess}
|
||||
disabled={
|
||||
loading ||
|
||||
(result && result.file_type === ".pdf" && result.tables?.length > 1) ||
|
||||
|
|
@ -210,7 +231,7 @@ export default function ViewsAdminUploadStep1() {
|
|||
</ul>
|
||||
|
||||
<button
|
||||
onClick={handleNextPdf}
|
||||
onClick={handleProcessPdf}
|
||||
disabled={!selectedTable}
|
||||
className="w-full mt-4 px-5 py-2 bg-green-600 text-white rounded hover:bg-green-700 disabled:bg-gray-300"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ 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";
|
||||
|
||||
|
|
@ -18,6 +19,7 @@ export default function ViewsAdminUploadValidate() {
|
|||
const [showAlert, setShowAlert] = useState(false);
|
||||
const [alertMessage, setAlertMessage] = useState("");
|
||||
const [alertType, setAlertType] = useState("info");
|
||||
const [errorMsg, setErrorMsg] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const handleBeforeUnload = (e) => {
|
||||
|
|
@ -55,7 +57,7 @@ export default function ViewsAdminUploadValidate() {
|
|||
|
||||
if (!result) return <Navigate to="/admin/upload" />;
|
||||
|
||||
const handleUploadClick = () => {
|
||||
const handleUploadClick = async () => {
|
||||
if (!tableTitle.trim()) {
|
||||
setAlertMessage(
|
||||
"❗Judul tabel belum diisi. Silakan isi sebelum melanjutkan."
|
||||
|
|
@ -64,7 +66,13 @@ export default function ViewsAdminUploadValidate() {
|
|||
setShowAlert(true);
|
||||
return;
|
||||
}
|
||||
handleConfirmUpload();
|
||||
|
||||
// handleConfirmUpload();
|
||||
try {
|
||||
await handleConfirmUpload();
|
||||
} catch (err) {
|
||||
setErrorMsg(err);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -77,6 +85,11 @@ export default function ViewsAdminUploadValidate() {
|
|||
/>
|
||||
)}
|
||||
|
||||
<ErrorNotification
|
||||
message={errorMsg}
|
||||
onClose={() => setErrorMsg("")}
|
||||
/>
|
||||
|
||||
<LoadingOverlay show={loading} text="Upload to database..." />
|
||||
|
||||
<h1 className="text-2xl font-bold mb-4">✅ Validasi & Konfirmasi Data</h1>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user