Compare commits
2 Commits
eb273efe61
...
2b14a8a417
| Author | SHA1 | Date | |
|---|---|---|---|
| 2b14a8a417 | |||
| a154b25415 |
48
frontend/src/components/ConfirmationModal.tsx
Normal file
48
frontend/src/components/ConfirmationModal.tsx
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { Dialog, DialogPanel, DialogTitle } from "@headlessui/react";
|
||||||
|
import StatsStyling from "../styles/stats_styling";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
open: boolean;
|
||||||
|
title: string;
|
||||||
|
message: string;
|
||||||
|
confirmLabel?: string;
|
||||||
|
cancelLabel?: string;
|
||||||
|
loading?: boolean;
|
||||||
|
onConfirm: () => void;
|
||||||
|
onCancel: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StatsStyling;
|
||||||
|
|
||||||
|
export default function ConfirmationModal({
|
||||||
|
open,
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
confirmLabel = "Confirm",
|
||||||
|
cancelLabel = "Cancel",
|
||||||
|
loading = false,
|
||||||
|
onConfirm,
|
||||||
|
onCancel,
|
||||||
|
}: Props) {
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onClose={onCancel} style={styles.modalRoot}>
|
||||||
|
<div style={styles.modalBackdrop} />
|
||||||
|
|
||||||
|
<div style={styles.modalContainer}>
|
||||||
|
<DialogPanel style={{ ...styles.card, ...styles.modalPanel }}>
|
||||||
|
<DialogTitle style={styles.sectionTitle}>{title}</DialogTitle>
|
||||||
|
<p style={styles.sectionSubtitle}>{message}</p>
|
||||||
|
|
||||||
|
<div style={{ display: "flex", justifyContent: "flex-end", gap: 8 }}>
|
||||||
|
<button type="button" onClick={onCancel} style={styles.buttonSecondary} disabled={loading}>
|
||||||
|
{cancelLabel}
|
||||||
|
</button>
|
||||||
|
<button type="button" onClick={onConfirm} style={styles.buttonDanger} disabled={loading}>
|
||||||
|
{loading ? "Deleting..." : confirmLabel}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</DialogPanel>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import StatsStyling from "../styles/stats_styling";
|
|||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { useEffect, useMemo, useState, type FormEvent } from "react";
|
import { useEffect, useMemo, useState, type FormEvent } from "react";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import ConfirmationModal from "../components/ConfirmationModal";
|
||||||
|
|
||||||
const API_BASE_URL = import.meta.env.VITE_BACKEND_URL;
|
const API_BASE_URL = import.meta.env.VITE_BACKEND_URL;
|
||||||
const styles = StatsStyling;
|
const styles = StatsStyling;
|
||||||
@@ -19,16 +20,11 @@ const DatasetEditPage = () => {
|
|||||||
const [statusMessage, setStatusMessage] = useState("");
|
const [statusMessage, setStatusMessage] = useState("");
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
|
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||||
const [hasError, setHasError] = useState(false);
|
const [hasError, setHasError] = useState(false);
|
||||||
|
|
||||||
const [datasetName, setDatasetName] = useState("");
|
const [datasetName, setDatasetName] = useState("");
|
||||||
|
|
||||||
const token = localStorage.getItem("access_token");
|
|
||||||
if (!token) {
|
|
||||||
setHasError(true);
|
|
||||||
setStatusMessage("You must be signed in to save changes.");
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!Number.isInteger(parsedDatasetId) || parsedDatasetId <= 0) {
|
if (!Number.isInteger(parsedDatasetId) || parsedDatasetId <= 0) {
|
||||||
setHasError(true);
|
setHasError(true);
|
||||||
@@ -108,21 +104,37 @@ const DatasetEditPage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const deleteDataset = async () => {
|
const deleteDataset = async () => {
|
||||||
try{
|
const deleteToken = localStorage.getItem("access_token");
|
||||||
|
if (!deleteToken) {
|
||||||
|
setHasError(true);
|
||||||
|
setStatusMessage("You must be signed in to delete datasets.");
|
||||||
|
setIsDeleteModalOpen(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setIsDeleting(true);
|
||||||
|
setHasError(false);
|
||||||
|
setStatusMessage("");
|
||||||
|
|
||||||
await axios.delete(
|
await axios.delete(
|
||||||
`${API_BASE_URL}/dataset/${parsedDatasetId}`,
|
`${API_BASE_URL}/dataset/${parsedDatasetId}`,
|
||||||
{ headers: { Authorization: `Bearer ${token}` } }
|
{ headers: { Authorization: `Bearer ${deleteToken}` } }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
setIsDeleteModalOpen(false);
|
||||||
navigate("/datasets", { replace: true });
|
navigate("/datasets", { replace: true });
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
setHasError(true);
|
setHasError(true);
|
||||||
if (axios.isAxiosError(error)) {
|
if (axios.isAxiosError(error)) {
|
||||||
setStatusMessage(String(error.response?.data?.error || error.message || "Save failed."));
|
setStatusMessage(String(error.response?.data?.error || error.message || "Delete failed."));
|
||||||
} else {
|
} else {
|
||||||
setStatusMessage("Save failed due to an unexpected error.");
|
setStatusMessage("Delete failed due to an unexpected error.");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
setIsDeleting(false);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={styles.page}>
|
<div style={styles.page}>
|
||||||
@@ -159,8 +171,8 @@ const DatasetEditPage = () => {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
style={styles.buttonDanger}
|
style={styles.buttonDanger}
|
||||||
onClick={deleteDataset}
|
onClick={() => setIsDeleteModalOpen(true)}
|
||||||
disabled={isSaving}
|
disabled={isSaving || isDeleting}
|
||||||
>
|
>
|
||||||
Delete Dataset
|
Delete Dataset
|
||||||
</button>
|
</button>
|
||||||
@@ -169,14 +181,14 @@ const DatasetEditPage = () => {
|
|||||||
type="button"
|
type="button"
|
||||||
style={styles.buttonSecondary}
|
style={styles.buttonSecondary}
|
||||||
onClick={() => navigate("/datasets")}
|
onClick={() => navigate("/datasets")}
|
||||||
disabled={isSaving}
|
disabled={isSaving || isDeleting}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
style={{ ...styles.buttonPrimary, opacity: loading || isSaving ? 0.75 : 1 }}
|
style={{ ...styles.buttonPrimary, opacity: loading || isSaving ? 0.75 : 1 }}
|
||||||
disabled={loading || isSaving}
|
disabled={loading || isSaving || isDeleting}
|
||||||
>
|
>
|
||||||
{isSaving ? "Saving..." : "Save"}
|
{isSaving ? "Saving..." : "Save"}
|
||||||
</button>
|
</button>
|
||||||
@@ -186,6 +198,17 @@ const DatasetEditPage = () => {
|
|||||||
: statusMessage}
|
: statusMessage}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<ConfirmationModal
|
||||||
|
open={isDeleteModalOpen}
|
||||||
|
title="Delete Dataset"
|
||||||
|
message={`Are you sure you want to delete "${datasetName || "this dataset"}"? This action cannot be undone.`}
|
||||||
|
confirmLabel="Delete"
|
||||||
|
cancelLabel="Keep Dataset"
|
||||||
|
loading={isDeleting}
|
||||||
|
onCancel={() => setIsDeleteModalOpen(false)}
|
||||||
|
onConfirm={deleteDataset}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -38,9 +38,13 @@ class PostgresConnector:
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
def execute_batch(self, query, values):
|
def execute_batch(self, query, values):
|
||||||
|
try:
|
||||||
with self.connection.cursor(cursor_factory=RealDictCursor) as cursor:
|
with self.connection.cursor(cursor_factory=RealDictCursor) as cursor:
|
||||||
execute_batch(cursor, query, values)
|
execute_batch(cursor, query, values)
|
||||||
self.connection.commit()
|
self.connection.commit()
|
||||||
|
except Exception:
|
||||||
|
self.connection.rollback()
|
||||||
|
raise
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
if self.connection:
|
if self.connection:
|
||||||
|
|||||||
Reference in New Issue
Block a user