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{
|
}else{
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
} finally {
|
} catch(err){
|
||||||
// setLoading(false);
|
setLoading(false);
|
||||||
|
throw err
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -84,8 +85,9 @@ export function useUploadController() {
|
||||||
const res = await uploadPdf(selectedTable);
|
const res = await uploadPdf(selectedTable);
|
||||||
dispatch(setResult(res));
|
dispatch(setResult(res));
|
||||||
navigate("/admin/upload/validate");
|
navigate("/admin/upload/validate");
|
||||||
} finally {
|
} catch(err){
|
||||||
// setLoading(false);
|
setLoading(false);
|
||||||
|
throw err
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -100,8 +102,9 @@ export function useUploadController() {
|
||||||
const res = await saveToDatabase(data);
|
const res = await saveToDatabase(data);
|
||||||
dispatch(setValidatedData(res));
|
dispatch(setValidatedData(res));
|
||||||
navigate("/admin/upload/success");
|
navigate("/admin/upload/success");
|
||||||
} finally {
|
} catch(err){
|
||||||
// setLoading(false);
|
setLoading(false);
|
||||||
|
throw err
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,8 +40,7 @@ export async function uploadFile(file, page = null, sheet = null) {
|
||||||
});
|
});
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Upload gagal:", error);
|
throw error.response?.data.detail || "Gagal proses file.";
|
||||||
throw error.response?.data || { message: "Gagal mengunggah file." };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,8 +51,7 @@ export async function uploadPdf(data) {
|
||||||
});
|
});
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Gagal kirim JSON:", error);
|
throw error.response?.data.detail || { message: "Gagal proses file." };
|
||||||
throw error.response?.data || { message: "Terjadi kesalahan." };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -65,7 +63,6 @@ export async function saveToDatabase(data) {
|
||||||
});
|
});
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Gagal kirim JSON:", error);
|
throw error.response?.data.detail || { message: "Gagal upload data." };
|
||||||
throw error.response?.data || { message: "Terjadi kesalahan." };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
|
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 LoadingOverlay from "../../../components/LoadingOverlay";
|
import LoadingOverlay from "../../../components/LoadingOverlay";
|
||||||
|
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 { useEffect } from "react";
|
|
||||||
import { reset } from "../../../store/slices/uploadSlice";
|
import { reset } from "../../../store/slices/uploadSlice";
|
||||||
|
|
||||||
export default function ViewsAdminUploadStep1() {
|
export default function ViewsAdminUploadStep1() {
|
||||||
|
|
@ -26,6 +27,8 @@ export default function ViewsAdminUploadStep1() {
|
||||||
handleUpload,
|
handleUpload,
|
||||||
handleNextPdf
|
handleNextPdf
|
||||||
} = useUploadController();
|
} = useUploadController();
|
||||||
|
const [errorMsg, setErrorMsg] = useState("");
|
||||||
|
const ext = file ? file.name.split(".").pop().toLowerCase() : "";
|
||||||
|
|
||||||
const handlePageSelection = (pages) => {
|
const handlePageSelection = (pages) => {
|
||||||
console.log("Halaman PDF yang dipilih:", pages);
|
console.log("Halaman PDF yang dipilih:", pages);
|
||||||
|
|
@ -33,16 +36,34 @@ export default function ViewsAdminUploadStep1() {
|
||||||
dispatch(setSelectedPages(pages));
|
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(() => {
|
useEffect(() => {
|
||||||
dispatch(reset())
|
dispatch(reset())
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-4xl mx-auto py-10">
|
<div className="max-w-4xl mx-auto py-10">
|
||||||
|
|
||||||
|
<ErrorNotification
|
||||||
|
message={errorMsg}
|
||||||
|
onClose={() => setErrorMsg("")}
|
||||||
|
/>
|
||||||
|
|
||||||
<LoadingOverlay show={loading} text="Processing..." />
|
<LoadingOverlay show={loading} text="Processing..." />
|
||||||
|
|
||||||
<div className="mb-6 flex justify-between items-center">
|
<div className="mb-6 flex justify-between items-center">
|
||||||
|
|
@ -119,7 +140,7 @@ export default function ViewsAdminUploadStep1() {
|
||||||
{/* 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
|
||||||
onClick={handleUpload}
|
onClick={handleProcess}
|
||||||
disabled={
|
disabled={
|
||||||
loading ||
|
loading ||
|
||||||
(result && result.file_type === ".pdf" && result.tables?.length > 1) ||
|
(result && result.file_type === ".pdf" && result.tables?.length > 1) ||
|
||||||
|
|
@ -210,7 +231,7 @@ export default function ViewsAdminUploadStep1() {
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={handleNextPdf}
|
onClick={handleProcessPdf}
|
||||||
disabled={!selectedTable}
|
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"
|
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 { useSelector } from "react-redux";
|
||||||
import LoadingOverlay from "../../../components/LoadingOverlay";
|
import LoadingOverlay from "../../../components/LoadingOverlay";
|
||||||
import Notification from "../../../components/Notification";
|
import Notification from "../../../components/Notification";
|
||||||
|
import ErrorNotification from "../../../components/ErrorNotification";
|
||||||
import { Navigate } from "react-router-dom";
|
import { Navigate } from "react-router-dom";
|
||||||
import FilePreview from "../../../components/upload/FilePreview";
|
import FilePreview from "../../../components/upload/FilePreview";
|
||||||
|
|
||||||
|
|
@ -18,6 +19,7 @@ export default function ViewsAdminUploadValidate() {
|
||||||
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 [errorMsg, setErrorMsg] = useState("");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleBeforeUnload = (e) => {
|
const handleBeforeUnload = (e) => {
|
||||||
|
|
@ -55,7 +57,7 @@ export default function ViewsAdminUploadValidate() {
|
||||||
|
|
||||||
if (!result) return <Navigate to="/admin/upload" />;
|
if (!result) return <Navigate to="/admin/upload" />;
|
||||||
|
|
||||||
const handleUploadClick = () => {
|
const handleUploadClick = async () => {
|
||||||
if (!tableTitle.trim()) {
|
if (!tableTitle.trim()) {
|
||||||
setAlertMessage(
|
setAlertMessage(
|
||||||
"❗Judul tabel belum diisi. Silakan isi sebelum melanjutkan."
|
"❗Judul tabel belum diisi. Silakan isi sebelum melanjutkan."
|
||||||
|
|
@ -64,7 +66,13 @@ export default function ViewsAdminUploadValidate() {
|
||||||
setShowAlert(true);
|
setShowAlert(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
handleConfirmUpload();
|
|
||||||
|
// handleConfirmUpload();
|
||||||
|
try {
|
||||||
|
await handleConfirmUpload();
|
||||||
|
} catch (err) {
|
||||||
|
setErrorMsg(err);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -77,6 +85,11 @@ export default function ViewsAdminUploadValidate() {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<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>
|
<h1 className="text-2xl font-bold mb-4">✅ Validasi & Konfirmasi Data</h1>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user