From 7335d1790015e42372933e4a9bfc22afe8c86f3a Mon Sep 17 00:00:00 2001 From: chop Date: Thu, 22 Jan 2026 22:03:25 +0100 Subject: [PATCH] feat: Add contract editing functionality with form handling and translations --- src/app/api/contracts/[id]/route.js | 76 ++++++++++++++- src/app/contracts/[id]/edit/page.js | 139 ++++++++++++++++++++++++++++ src/app/contracts/[id]/page.js | 18 ++++ src/components/ContractForm.js | 49 +++++++--- src/lib/i18n.js | 46 ++++++++- 5 files changed, 315 insertions(+), 13 deletions(-) create mode 100644 src/app/contracts/[id]/edit/page.js diff --git a/src/app/api/contracts/[id]/route.js b/src/app/api/contracts/[id]/route.js index b5dcc55..d0f98c6 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, withTeamLeadAuth } from "@/lib/middleware/auth"; +import { withReadAuth, withTeamLeadAuth, withUserAuth } from "@/lib/middleware/auth"; async function getContractHandler(req, { params }) { const { id } = await params; @@ -21,6 +21,79 @@ async function getContractHandler(req, { params }) { return NextResponse.json(contract); } +async function updateContractHandler(req, { params }) { + const { id } = await params; + + try { + const body = await req.json(); + const { + contract_number, + contract_name, + customer_contract_number, + customer, + investor, + date_signed, + finish_date, + } = body; + + // Check if contract exists + const existingContract = db + .prepare("SELECT * FROM contracts WHERE contract_id = ?") + .get(id); + + if (!existingContract) { + return NextResponse.json( + { error: "Contract not found" }, + { status: 404 } + ); + } + + // Update the contract + const result = db + .prepare( + `UPDATE contracts + SET contract_number = ?, + contract_name = ?, + customer_contract_number = ?, + customer = ?, + investor = ?, + date_signed = ?, + finish_date = ? + WHERE contract_id = ?` + ) + .run( + contract_number, + contract_name || null, + customer_contract_number || null, + customer || null, + investor || null, + date_signed || null, + finish_date || null, + id + ); + + if (result.changes === 0) { + return NextResponse.json( + { error: "Failed to update contract" }, + { status: 500 } + ); + } + + // Fetch and return the updated contract + const updatedContract = db + .prepare("SELECT * FROM contracts WHERE contract_id = ?") + .get(id); + + return NextResponse.json(updatedContract); + } catch (error) { + console.error("Error updating contract:", error); + return NextResponse.json( + { error: "Internal server error" }, + { status: 500 } + ); + } +} + async function deleteContractHandler(req, { params }) { const { id } = params; @@ -61,4 +134,5 @@ async function deleteContractHandler(req, { params }) { // Protected routes - require authentication export const GET = withReadAuth(getContractHandler); +export const PUT = withUserAuth(updateContractHandler); export const DELETE = withTeamLeadAuth(deleteContractHandler); diff --git a/src/app/contracts/[id]/edit/page.js b/src/app/contracts/[id]/edit/page.js new file mode 100644 index 0000000..fe0d6cc --- /dev/null +++ b/src/app/contracts/[id]/edit/page.js @@ -0,0 +1,139 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { useParams } from "next/navigation"; +import ContractForm from "@/components/ContractForm"; +import PageContainer from "@/components/ui/PageContainer"; +import PageHeader from "@/components/ui/PageHeader"; +import Button from "@/components/ui/Button"; +import Link from "next/link"; +import { LoadingState } from "@/components/ui/States"; +import { useTranslation } from "@/lib/i18n"; + +export default function EditContractPage() { + const params = useParams(); + const id = params.id; + const [contract, setContract] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const { t } = useTranslation(); + + useEffect(() => { + async function fetchContract() { + setLoading(true); + setError(null); + try { + const res = await fetch(`/api/contracts/${id}`); + + if (!res.ok) { + throw new Error("Failed to fetch contract"); + } + + const data = await res.json(); + setContract(data); + } catch (error) { + console.error("Error fetching contract:", error); + setError("Nie udało się pobrać danych umowy."); + } finally { + setLoading(false); + } + } + + if (id) { + fetchContract(); + } + }, [id]); + + if (loading) { + return ( + + + + + ); + } + + if (error || !contract) { + return ( + + +
+ + + +
+

+ {error || "Nie znaleziono umowy."} +

+
+
+ + + +
+ ); + } + + return ( + + + + + } + /> +
+ +
+
+ ); +} diff --git a/src/app/contracts/[id]/page.js b/src/app/contracts/[id]/page.js index b0977d0..87ed93a 100644 --- a/src/app/contracts/[id]/page.js +++ b/src/app/contracts/[id]/page.js @@ -113,6 +113,24 @@ export default function ContractDetailsPage() { {t('contracts.backToContracts')} + + + diff --git a/src/lib/i18n.js b/src/lib/i18n.js index e003336..61e5c95 100644 --- a/src/lib/i18n.js +++ b/src/lib/i18n.js @@ -287,6 +287,11 @@ const translations = { contract: "Umowa", newContract: "Nowa umowa", editContract: "Edytuj umowę", + editContractDescription: "Edycja szczegółów umowy", + editing: "Edycja umowy", + backToContract: "Powrót do umowy", + updateContract: "Zaktualizuj umowę", + failedToUpdateContract: "Nie udało się zaktualizować umowy. Sprawdź dane i spróbuj ponownie.", deleteContract: "Usuń umowę", contractNumber: "Numer umowy", contractName: "Nazwa umowy", @@ -940,8 +945,14 @@ const translations = { contracts: { title: "Contracts", subtitle: "Manage your contracts and agreements", + contract: "Contract", newContract: "New Contract", editContract: "Edit Contract", + editContractDescription: "Edit contract details", + editing: "Editing contract", + backToContract: "Back to Contract", + updateContract: "Update Contract", + failedToUpdateContract: "Failed to update contract. Please check your data and try again.", deleteContract: "Delete Contract", contractNumber: "Contract Number", contractName: "Contract Name", @@ -968,7 +979,40 @@ const translations = { signedOn: "Signed:", finishOn: "Finish:", customerLabel: "Customer:", - investorLabel: "Investor:" + investorLabel: "Investor:", + loadingContractDetails: "Loading contract details...", + contractNotFound: "Contract not found.", + backToContracts: "Back to Contracts", + addProject: "Add Project", + contractInformation: "Contract Information", + summary: "Summary", + projectsCount: "Projects Count", + projects: "projects", + contractStatus: "Contract Status", + contractDocuments: "Contract Documents", + uploadDocument: "Upload Document", + associatedProjects: "Associated Projects", + noProjectsYet: "No projects yet", + getStartedMessage: "Get started by creating the first project for this contract", + createFirstProject: "Create First Project", + viewDetails: "View Details", + createNewContract: "Create New Contract", + addNewContractDescription: "Add a new contract to your portfolio", + contractDetails: "Contract Details", + failedToCreateContract: "Failed to create contract. Please check your data and try again.", + uploadDocumentTitle: "Upload Document", + descriptionOptional: "Description (optional)", + descriptionPlaceholder: "Short description of the document...", + uploading: "Uploading...", + dropFilesHere: "Drop files here or click to browse", + supportedFiles: "PDF, DOC, XLS, Images up to 10MB", + chooseFile: "Choose File", + failedToUploadFile: "Failed to upload file", + loadingFiles: "Loading files...", + noDocumentsUploaded: "No documents uploaded", + download: "Download", + confirmDeleteFile: "Are you sure you want to delete this file?", + failedToDeleteFile: "Failed to delete file" }, tasks: {