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 && ( +
+ + + +
+

{success}

+
+ +
+ )} + + {/* Error Message */} + {error && ( +
+ + + +
+

{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} +

+ )} +
+ +
+ + +
+
+
+ )} ); }