From b358f5d7b770d0901088d94b3bf7e4c05618155e Mon Sep 17 00:00:00 2001
From: chop
Date: Thu, 22 Jan 2026 21:51:37 +0100
Subject: [PATCH] feat: Implement delete confirmation modal and restrict delete
access to team leads
---
src/app/api/contracts/[id]/route.js | 4 +-
src/app/contracts/page.js | 293 +++++++++++++++++++++++-----
2 files changed, 247 insertions(+), 50 deletions(-)
diff --git a/src/app/api/contracts/[id]/route.js b/src/app/api/contracts/[id]/route.js
index 0dd9410..b5dcc55 100644
--- a/src/app/api/contracts/[id]/route.js
+++ b/src/app/api/contracts/[id]/route.js
@@ -1,6 +1,6 @@
import db from "@/lib/db";
import { NextResponse } from "next/server";
-import { withReadAuth, withUserAuth } from "@/lib/middleware/auth";
+import { withReadAuth, withTeamLeadAuth } from "@/lib/middleware/auth";
async function getContractHandler(req, { params }) {
const { id } = await params;
@@ -61,4 +61,4 @@ async function deleteContractHandler(req, { params }) {
// Protected routes - require authentication
export const GET = withReadAuth(getContractHandler);
-export const DELETE = withUserAuth(deleteContractHandler);
+export const DELETE = withTeamLeadAuth(deleteContractHandler);
diff --git a/src/app/contracts/page.js b/src/app/contracts/page.js
index f34a0cd..d2df1be 100644
--- a/src/app/contracts/page.js
+++ b/src/app/contracts/page.js
@@ -2,6 +2,7 @@
import { useEffect, useState } from "react";
import Link from "next/link";
+import { useSession } from "next-auth/react";
import { Card, CardHeader, CardContent } from "@/components/ui/Card";
import Button from "@/components/ui/Button";
import Badge from "@/components/ui/Badge";
@@ -15,6 +16,7 @@ import { useTranslation } from "@/lib/i18n";
export default function ContractsMainPage() {
const { t } = useTranslation();
+ const { data: session } = useSession();
const [contracts, setContracts] = useState([]);
const [loading, setLoading] = useState(true);
const [searchTerm, setSearchTerm] = useState("");
@@ -22,17 +24,27 @@ export default function ContractsMainPage() {
const [sortBy, setSortBy] = useState("date_signed");
const [sortOrder, setSortOrder] = useState("desc");
const [statusFilter, setStatusFilter] = useState("all");
+ const [showDeleteModal, setShowDeleteModal] = useState(false);
+ const [contractToDelete, setContractToDelete] = useState(null);
+ const [deleting, setDeleting] = useState(false);
+ const [error, setError] = useState(null);
+ const [success, setSuccess] = useState(null);
useEffect(() => {
async function fetchContracts() {
setLoading(true);
+ setError(null);
try {
const res = await fetch("/api/contracts");
+ if (!res.ok) {
+ throw new Error("Failed to fetch contracts");
+ }
const data = await res.json();
setContracts(data);
setFilteredContracts(data);
} catch (error) {
console.error("Error fetching contracts:", error);
+ setError("Nie udało się pobrać listy umów. Spróbuj ponownie później.");
} finally {
setLoading(false);
}
@@ -93,27 +105,6 @@ export default function ContractsMainPage() {
setFilteredContracts(filtered);
}, [searchTerm, contracts, sortBy, sortOrder, statusFilter]);
- async function handleDelete(id) {
- const confirmed = confirm("Czy na pewno chcesz usunąć tę umowę?");
- if (!confirmed) return;
-
- try {
- const res = await fetch(`/api/contracts/${id}`, {
- method: "DELETE",
- });
-
- if (res.ok) {
- setContracts(contracts.filter((c) => c.contract_id !== id));
- } else {
- alert("Błąd podczas usuwania umowy.");
- }
- } catch (error) {
- console.error("Error deleting contract:", error);
- alert("Błąd podczas usuwania umowy.");
- }
- }
-
- // Get contract statistics
const getContractStats = () => {
const currentDate = new Date();
const total = contracts.length;
@@ -148,25 +139,50 @@ export default function ContractsMainPage() {
}
};
- async function handleDelete(id) {
- const confirmed = confirm("Czy na pewno chcesz usunąć tę umowę?");
- if (!confirmed) return;
+ const initiateDelete = (contract) => {
+ setContractToDelete(contract);
+ setShowDeleteModal(true);
+ };
+
+ const handleDelete = async () => {
+ if (!contractToDelete) return;
+
+ setDeleting(true);
+ setError(null);
try {
- const res = await fetch(`/api/contracts/${id}`, {
+ const res = await fetch(`/api/contracts/${contractToDelete.contract_id}`, {
method: "DELETE",
});
+ const data = await res.json();
+
if (res.ok) {
- setContracts(contracts.filter((c) => c.contract_id !== id));
+ setContracts(contracts.filter((c) => c.contract_id !== contractToDelete.contract_id));
+ setSuccess(`Umowa "${contractToDelete.contract_number}" została usunięta.`);
+ setShowDeleteModal(false);
+ setContractToDelete(null);
+
+ // Auto-hide success message after 5 seconds
+ setTimeout(() => setSuccess(null), 5000);
} else {
- alert("Błąd podczas usuwania umowy.");
+ setError(data.error || "Nie udało się usunąć umowy.");
}
} catch (error) {
console.error("Error deleting contract:", error);
- alert("Błąd podczas usuwania umowy.");
+ setError("Wystąpił błąd podczas usuwania umowy. Spróbuj ponownie.");
+ } finally {
+ setDeleting(false);
}
- }
+ };
+
+ const cancelDelete = () => {
+ if (!deleting) {
+ setShowDeleteModal(false);
+ setContractToDelete(null);
+ setError(null);
+ }
+ };
const handleSearchChange = (e) => {
setSearchTerm(e.target.value);
@@ -264,6 +280,67 @@ export default function ContractsMainPage() {
{" "}
+
+ {/* Success Message */}
+ {success && (
+
+
+
+
+
+ )}
+
+ {/* Error Message */}
+ {error && (
+
+
+
+
+
+ )}
+
{/* Statistics Cards */}
@@ -573,26 +650,28 @@ export default function ContractsMainPage() {
Szczegóły
-
+
+ Usuń
+
+ )}
@@ -620,6 +699,124 @@ export default function ContractsMainPage() {
{" "}
)}
+
+ {/* Delete Confirmation Modal */}
+ {showDeleteModal && contractToDelete && (
+ e.target === e.currentTarget && !deleting && cancelDelete()}
+ >
+
+
+
+
+
+ Potwierdź usunięcie
+
+
+ {!deleting && (
+
+ )}
+
+
+
+
+ Czy na pewno chcesz usunąć umowę "{contractToDelete.contract_number}"
+ {contractToDelete.contract_name && (
+ <> — {contractToDelete.contract_name}>
+ )}?
+
+
+ Ta operacja jest nieodwracalna.
+
+ {contractToDelete.customer && (
+
+ Zleceniodawca: {contractToDelete.customer}
+
+ )}
+
+
+
+
+
+
+
+
+ )}
);
}