Files
panel/src/app/projects/[id]/page.js

993 lines
30 KiB
JavaScript

"use client";
import { useState, useEffect } from "react";
import { useParams } from "next/navigation";
import { useSession } from "next-auth/react";
import NoteForm from "@/components/NoteForm";
import ProjectTasksSection from "@/components/ProjectTasksSection";
import FieldWithHistory from "@/components/FieldWithHistory";
import { Card, CardHeader, CardContent } from "@/components/ui/Card";
import Button from "@/components/ui/Button";
import Badge from "@/components/ui/Badge";
import Link from "next/link";
import { differenceInCalendarDays, parseISO } from "date-fns";
import { formatDate, formatCoordinates } from "@/lib/utils";
import PageContainer from "@/components/ui/PageContainer";
import PageHeader from "@/components/ui/PageHeader";
import ProjectStatusDropdown from "@/components/ProjectStatusDropdown";
import ProjectAssigneeDropdown from "@/components/ProjectAssigneeDropdown";
import ClientProjectMap from "@/components/ui/ClientProjectMap";
import FileUploadBox from "@/components/FileUploadBox";
import FileItem from "@/components/FileItem";
import proj4 from "proj4";
export default function ProjectViewPage() {
const params = useParams();
const { data: session } = useSession();
const [project, setProject] = useState(null);
const [notes, setNotes] = useState([]);
const [loading, setLoading] = useState(true);
const [uploadedFiles, setUploadedFiles] = useState([]);
const [editingNoteId, setEditingNoteId] = useState(null);
const [editText, setEditText] = useState('');
// Helper function to parse note text with links
const parseNoteText = (text) => {
const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
const parts = [];
let lastIndex = 0;
let match;
while ((match = linkRegex.exec(text)) !== null) {
// Add text before the link
if (match.index > lastIndex) {
parts.push(text.slice(lastIndex, match.index));
}
// Add the link
parts.push(
<a
key={match.index}
href={match[2]}
className="text-blue-600 hover:text-blue-800 underline"
target="_blank"
rel="noopener noreferrer"
>
{match[1]}
</a>
);
lastIndex = match.index + match[0].length;
}
// Add remaining text
if (lastIndex < text.length) {
parts.push(text.slice(lastIndex));
}
return parts.length > 0 ? parts : text;
};
// Helper function to add a new note to the list
const addNote = (newNote) => {
setNotes(prevNotes => [newNote, ...prevNotes]);
};
// Helper function to check if user can modify a note (edit or delete)
const canModifyNote = (note) => {
if (!session?.user) return false;
// Admins can modify any note
if (session.user.role === 'admin') return true;
// Users can modify their own notes
return note.created_by === session.user.id;
};
// Helper function to handle file upload
const handleFileUploaded = (newFile) => {
setUploadedFiles(prevFiles => [newFile, ...prevFiles]);
};
// Helper function to handle file deletion
const handleFileDelete = async (fileId) => {
if (confirm('Czy na pewno chcesz usunąć ten plik?')) {
try {
const res = await fetch(`/api/files/${fileId}`, {
method: 'DELETE',
});
if (res.ok) {
setUploadedFiles(prevFiles => prevFiles.filter(file => file.file_id !== fileId));
} else {
alert('Błąd podczas usuwania pliku');
}
} catch (error) {
console.error('Error deleting file:', error);
alert('Błąd podczas usuwania pliku');
}
}
};
// Helper function to handle file update (edit)
const handleFileUpdate = async (updatedFile) => {
setUploadedFiles(prevFiles =>
prevFiles.map(file =>
file.file_id === updatedFile.file_id ? updatedFile : file
)
);
};
// Helper function to save edited note
const handleSaveNote = async (noteId) => {
try {
const res = await fetch(`/api/notes/${noteId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ note: editText }),
});
if (res.ok) {
// Update the note in local state
setNotes(prevNotes =>
prevNotes.map(note =>
note.note_id === noteId ? { ...note, note: editText, edited_at: new Date().toISOString() } : note
)
);
setEditingNoteId(null);
setEditText('');
} else {
alert('Błąd podczas aktualizacji notatki');
}
} catch (error) {
console.error('Error updating note:', error);
alert('Błąd podczas aktualizacji notatki');
}
};
useEffect(() => {
const fetchData = async () => {
if (!params.id) return;
try {
// Fetch project data
const projectRes = await fetch(`/api/projects/${params.id}`);
if (!projectRes.ok) {
throw new Error('Project not found');
}
const projectData = await projectRes.json();
// Fetch notes data
const notesRes = await fetch(`/api/notes?project_id=${params.id}`);
const notesData = notesRes.ok ? await notesRes.json() : [];
// Fetch files data
const filesRes = await fetch(`/api/files?entityType=project&entityId=${params.id}`);
const filesData = filesRes.ok ? await filesRes.json() : [];
setProject(projectData);
setNotes(notesData);
setUploadedFiles(filesData);
} catch (error) {
console.error('Error fetching data:', error);
setProject(null);
setNotes([]);
} finally {
setLoading(false);
}
};
fetchData();
}, [params.id]);
useEffect(() => {
if (project?.project_name) {
document.title = `${project.project_name} - Panel`;
} else {
document.title = 'Panel';
}
}, [project]);
if (loading) {
return (
<PageContainer>
<div className="flex items-center justify-center py-12">
<div className="text-gray-500">Loading...</div>
</div>
</PageContainer>
);
}
if (!project) {
return (
<PageContainer>
<Card>
<CardContent className="text-center py-8">
<p className="text-red-600 text-lg">Projekt nie został znaleziony.</p>
<Link href="/projects" className="mt-4 inline-block">
<Button variant="primary">Powrót do projektów</Button>
</Link>
</CardContent>
</Card>
</PageContainer>
);
}
const daysRemaining = project.finish_date
? differenceInCalendarDays(parseISO(project.finish_date), new Date())
: null;
const getDeadlineVariant = (days) => {
if (days < 0) return "danger";
if (days <= 7) return "warning";
return "success";
};
return (
<PageContainer>
{/* Mobile: Full-width title, Desktop: Standard PageHeader */}
<div className="block sm:hidden mb-6">
{/* Mobile Layout */}
<div className="space-y-4">
{/* Full-width title */}
<div className="w-full">
<h1 className="text-2xl font-bold text-gray-900 break-words">
{project.project_name}
</h1>
<p className="text-sm text-gray-600 mt-1">
{project.city} {project.address} {project.project_number}
</p>
</div>
{/* Mobile action bar */}
<div className="flex flex-col space-y-3">
{/* Status and deadline badges */}
<div className="flex items-center gap-2 flex-wrap">
<ProjectStatusDropdown project={project} size="sm" />
{daysRemaining !== null && (
<Badge variant={getDeadlineVariant(daysRemaining)} size="sm" className="text-xs">
{daysRemaining === 0
? "Termin dzisiaj"
: daysRemaining > 0
? `${daysRemaining} dni pozostało`
: `${Math.abs(daysRemaining)} dni po terminie`}
</Badge>
)}
</div>
{/* Action buttons - full width */}
<div className="flex gap-2 w-full">
<Link href="/projects" className="flex-1">
<Button variant="outline" size="sm" className="w-full text-xs">
<svg
className="w-4 h-4 mr-1"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M15 19l-7-7 7-7"
/>
</svg>
Powrót
</Button>
</Link>
<Link href={`/projects/${params.id}/edit`} className="flex-1">
<Button variant="primary" size="sm" className="w-full text-xs">
<svg
className="w-4 h-4 mr-1"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
/>
</svg>
Edytuj
</Button>
</Link>
</div>
</div>
</div>
</div>
{/* Desktop: Standard PageHeader */}
<div className="hidden sm:block">
<PageHeader
title={project.project_name}
description={`${project.city}${project.address}${project.project_number}`}
action={
<div className="flex items-center gap-3">
<ProjectStatusDropdown project={project} size="sm" />
{daysRemaining !== null && (
<Badge variant={getDeadlineVariant(daysRemaining)} size="md">
{daysRemaining === 0
? "Termin dzisiaj"
: daysRemaining > 0
? `${daysRemaining} dni pozostało`
: `${Math.abs(daysRemaining)} dni po terminie`}
</Badge>
)}
<Link href="/projects">
<Button variant="outline" size="sm">
<svg
className="w-4 h-4 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M15 19l-7-7 7-7"
/>
</svg>
Powrót do projektów
</Button>
</Link>
<Link href={`/projects/${params.id}/edit`}>
<Button variant="primary">
<svg
className="w-4 h-4 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
/>
</svg>
Edytuj projekt
</Button>
</Link>
</div>
}
/>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
{/* Main Project Information */}
<div className="lg:col-span-2 space-y-6">
<Card>
<CardHeader>
{" "}
<div className="flex items-center justify-between">
<h2 className="text-xl font-semibold text-gray-900">
Informacje o projekcie
</h2>
<Badge
variant={
project.project_type === "design"
? "secondary"
: project.project_type === "construction"
? "primary"
: project.project_type === "design+construction"
? "success"
: "default"
}
size="sm"
>
{project.project_type === "design"
? "Projektowanie (P)"
: project.project_type === "construction"
? "Budowa (B)"
: project.project_type === "design+construction"
? "Projektowanie + Budowa (P+B)"
: "Nieznany"}
</Badge>
</div>
</CardHeader>
<CardContent className="space-y-6">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<span className="text-sm font-medium text-gray-500 block mb-1">
Lokalizacja
</span>
<p className="text-gray-900 font-medium">
{project.city || "N/A"}
</p>
</div>
<div>
<span className="text-sm font-medium text-gray-500 block mb-1">
Adres
</span>
<p className="text-gray-900 font-medium">
{project.address || "N/A"}
</p>
</div>
<div>
<span className="text-sm font-medium text-gray-500 block mb-1">
Działka
</span>
<p className="text-gray-900 font-medium">
{project.plot || "N/A"}
</p>
</div>
<div>
<span className="text-sm font-medium text-gray-500 block mb-1">
Jednostka ewidencyjna
</span>
<p className="text-gray-900 font-medium">
{project.district || "N/A"}
</p>
</div>
<div>
<span className="text-sm font-medium text-gray-500 block mb-1">
Obręb
</span>
<p className="text-gray-900 font-medium">
{project.unit || "N/A"}
</p>
</div>{" "}
<FieldWithHistory
tableName="projects"
recordId={project.project_id}
fieldName="finish_date"
currentValue={project.finish_date}
label="Termin zakończenia"
/>
{project.completion_date && (
<div>
<span className="text-sm font-medium text-gray-500 block mb-1">
Data zakończenia projektu
</span>
<p className="text-gray-900 font-medium">
{formatDate(project.completion_date)}
</p>
</div>
)}
<div>
<span className="text-sm font-medium text-gray-500 block mb-1">
WP
</span>
<p className="text-gray-900 font-medium">
{project.wp || "N/A"}
</p>
</div>
<div>
<span className="text-sm font-medium text-gray-500 block mb-1">
Numer inwestycji
</span>
<p className="text-gray-900 font-medium">
{project.investment_number || "N/A"}
</p>
</div>
{session?.user?.role === 'team_lead' && project.wartosc_zlecenia && (
<FieldWithHistory
tableName="projects"
recordId={project.project_id}
fieldName="wartosc_zlecenia"
currentValue={project.wartosc_zlecenia}
displayValue={parseFloat(project.wartosc_zlecenia).toLocaleString('pl-PL', {
style: 'currency',
currency: 'PLN'
})}
label="Wartość zlecenia"
/>
)}
</div>
{project.contact && (
<div className="border-t pt-4">
<span className="text-sm font-medium text-gray-500 block mb-1">
Kontakt
</span>
<p className="text-gray-900 font-medium">{project.contact}</p>
</div>
)}
{project.coordinates && (
<div className="border-t pt-4">
<span className="text-sm font-medium text-gray-500 block mb-1">
Współrzędne
</span>
<div className="flex items-center gap-2">
<p className="text-gray-900 font-medium font-mono text-sm">
{formatCoordinates(project.coordinates)}
</p>
<a
href={`https://www.google.com/maps/place/${project.coordinates}`}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:text-blue-800 transition-colors"
title="Otwórz w Google Maps"
>
<svg
className="w-5 h-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"
/>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"
/>
</svg>
</a>
<a
href={(() => {
// Define EPSG:2180 projection (Poland CS92)
proj4.defs("EPSG:2180", "+proj=tmerc +lat_0=0 +lon_0=19 +k=0.9993 +x_0=500000 +y_0=-5300000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs");
const [lat, lng] = project.coordinates.split(',').map(c => parseFloat(c.trim()));
// Convert WGS84 to EPSG:2180
const [x, y] = proj4('EPSG:4326', 'EPSG:2180', [lng, lat]);
// Create bbox with ~100m offset in each direction
const offset = 100;
const bbox = `${x - offset},${y - offset},${x + offset},${y + offset}`;
return `https://mapy.geoportal.gov.pl/imap/Imgp_2.html?gpmap=gp0&bbox=${bbox}&variant=KATASTER`;
})()}
target="_blank"
rel="noopener noreferrer"
className="text-green-600 hover:text-green-800 transition-colors"
title="Otwórz w Geoportal"
>
<svg
className="w-5 h-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-1.447-.894L15 4m0 13V4m0 0L9 7"
/>
</svg>
</a>
</div>
</div>
)} {project.notes && (
<div className="border-t pt-4">
<span className="text-sm font-medium text-gray-500 block mb-1">
Notes
</span>
<p className="text-gray-900">{project.notes}</p>
</div>
)}
</CardContent>
</Card>
{/* Contract Details */}
<Card>
<CardHeader>
<h2 className="text-xl font-semibold text-gray-900">
Szczegóły umowy
</h2>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<span className="text-sm font-medium text-gray-500 block mb-1">
Numer umowy
</span>
<p className="text-gray-900 font-medium">
{project.contract_number || "N/A"}
</p>
</div>
<div>
<span className="text-sm font-medium text-gray-500 block mb-1">
Numer umowy klienta
</span>
<p className="text-gray-900 font-medium">
{project.customer_contract_number ? (
<Link
href={`/contracts/${project.contract_id}`}
className="text-inherit hover:text-inherit no-underline"
>
{project.customer_contract_number}
</Link>
) : (
"N/A"
)}
</p>
</div>
<div>
<span className="text-sm font-medium text-gray-500 block mb-1">
Klient
</span>
<p className="text-gray-900 font-medium">
{project.customer || "N/A"}
</p>
</div>
<div>
<span className="text-sm font-medium text-gray-500 block mb-1">
Inwestor
</span>
<p className="text-gray-900 font-medium">
{project.investor || "N/A"}
</p>
</div>
</div>
</CardContent>
</Card>
</div>
{/* Status Sidebar */}
<div className="space-y-6">
<Card>
<CardHeader>
<h2 className="text-lg font-semibold text-gray-900">
Status projektu
</h2>
</CardHeader>
<CardContent className="space-y-4">
{" "}
<div>
<span className="text-sm font-medium text-gray-500 block mb-2">
Aktualny status
</span>
<ProjectStatusDropdown project={project} size="md" />
</div>
<div className="border-t pt-4">
<span className="text-sm font-medium text-gray-500 block mb-2">
Przypisany do
</span>
<ProjectAssigneeDropdown project={project} size="md" />
</div>
{daysRemaining !== null && (
<div className="border-t pt-4">
<span className="text-sm font-medium text-gray-500 block mb-2">
Harmonogram
</span>
<div className="text-center">
<Badge
variant={getDeadlineVariant(daysRemaining)}
size="lg"
>
{daysRemaining === 0
? "Termin dzisiaj"
: daysRemaining > 0
? `${daysRemaining} dni pozostało`
: `${Math.abs(daysRemaining)} dni po terminie`}
</Badge>
</div>
</div>
)}
</CardContent>
</Card>
{/* Quick Actions */}
<Card>
<CardHeader>
<h2 className="text-lg font-semibold text-gray-900">
Szybkie akcje
</h2>
</CardHeader>
<CardContent className="space-y-3">
<Link href={`/projects/${params.id}/edit`} className="block">
<Button
variant="outline"
size="sm"
className="w-full justify-start"
>
<svg
className="w-4 h-4 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
/>
</svg>
Edytuj projekt
</Button>
</Link>{" "}
<Link href="/projects" className="block">
<Button
variant="outline"
size="sm"
className="w-full justify-start"
>
<svg
className="w-4 h-4 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M15 19l-7-7 7-7"
/>
</svg>
Powrót do projektów
</Button>
</Link>
<Link href="/projects/map" className="block">
<Button
variant="outline"
size="sm"
className="w-full justify-start"
>
<svg
className="w-4 h-4 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-1.447-.894L15 4m0 13V4m0 0L9 7"
/>
</svg>
Zobacz wszystkie na mapie
</Button>
</Link>
</CardContent>
</Card>
{/* File Upload */}
<Card>
<CardHeader>
<h2 className="text-lg font-semibold text-gray-900">
Załączniki
</h2>
</CardHeader>
<CardContent className="space-y-4">
<FileUploadBox projectId={params.id} onFileUploaded={handleFileUploaded} />
{uploadedFiles.length > 0 && (
<div className="space-y-2">
<h3 className="text-sm font-medium text-gray-700">Przesłane pliki:</h3>
{uploadedFiles.map((file) => (
<FileItem
key={file.file_id}
file={file}
onDelete={handleFileDelete}
onUpdate={handleFileUpdate}
/>
))}
</div>
)}
</CardContent>
</Card>
</div>
</div>
{/* Project Location Map */}
{project.coordinates && (
<div className="mb-8">
{" "}
<Card>
<CardHeader>
{" "}
<div className="flex items-center justify-between">
<h2 className="text-xl font-semibold text-gray-900">
Lokalizacja projektu
</h2>
{project.coordinates && (
<Link
href={`/projects/map?lat=${project.coordinates
.split(",")[0]
.trim()}&lng=${project.coordinates
.split(",")[1]
.trim()}&zoom=16`}
>
<Button variant="outline" size="sm">
<svg
className="w-4 h-4 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-1.447-.894L15 4m0 13V4m0 0L9 7"
/>
</svg>
Zobacz na pełnej mapie
</Button>
</Link>
)}
</div>
</CardHeader>
<CardContent>
<ClientProjectMap
coordinates={project.coordinates}
projectName={project.project_name}
projectStatus={project.project_status}
showLayerControl={true}
mapHeight="h-80"
defaultLayer="Polish Geoportal Orthophoto"
showOverlays={false}
/>
</CardContent>
</Card>
</div>
)}
{/* Project Tasks Section */}
<div className="mb-8">
<ProjectTasksSection projectId={params.id} />
</div>
{/* Notes Section */}
<Card>
<CardHeader>
<h2 className="text-xl font-semibold text-gray-900">Notatki</h2>
</CardHeader>
<CardContent>
<div className="mb-6">
<NoteForm projectId={params.id} onNoteAdded={addNote} />
</div>
{notes.length === 0 ? (
<div className="text-center py-12">
<div className="text-gray-400 mb-4">
<svg
className="w-12 h-12 mx-auto"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={1.5}
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
/>
</svg>
</div>
<h3 className="text-lg font-medium text-gray-900 mb-2">
Brak notatek
</h3>
<p className="text-gray-500">
Dodaj swoją pierwszą notatkę używając formularza powyżej.
</p>
</div>
) : (
<div className="space-y-4">
{notes.map((n) => (
<div
key={n.note_id}
className="border border-gray-200 p-4 rounded-lg bg-gray-50 hover:bg-gray-100 transition-colors group"
>
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">
<span className="text-sm font-medium text-gray-500">
{formatDate(n.note_date, { includeTime: true })}
</span>
{n.created_by_name && (
<span className="px-2 py-1 text-xs bg-blue-100 text-blue-700 rounded-full font-medium">
{n.created_by_name}
</span>
)}
{n.edited_at && (
<span className="text-xs text-gray-400 italic">
edytowane
</span>
)}
</div>
{canModifyNote(n) && (
<div className="flex gap-1">
<button
onClick={() => {
setEditingNoteId(n.note_id);
setEditText(n.note);
}}
className="opacity-0 group-hover:opacity-100 transition-opacity p-1 text-gray-400 hover:text-blue-500 hover:bg-blue-50 rounded"
title="Edytuj notatkę"
>
<svg
className="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
/>
</svg>
</button>
<button
onClick={async () => {
if (confirm('Czy na pewno chcesz usunąć tę notatkę?')) {
try {
const res = await fetch(`/api/notes/${n.note_id}`, {
method: 'DELETE',
});
if (res.ok) {
// Remove the note from local state instead of full page reload
setNotes(prevNotes => prevNotes.filter(note => note.note_id !== n.note_id));
} else {
alert('Błąd podczas usuwania notatki');
}
} catch (error) {
console.error('Error deleting note:', error);
alert('Błąd podczas usuwania notatki');
}
}
}}
className="opacity-0 group-hover:opacity-100 transition-opacity p-1 text-gray-400 hover:text-red-500 hover:bg-red-50 rounded"
title="Usuń notatkę"
>
<svg
className="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</div>
)}
</div>
{editingNoteId === n.note_id ? (
<div className="space-y-2">
<textarea
value={editText}
onChange={(e) => setEditText(e.target.value)}
className="w-full p-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
rows={3}
placeholder="Wpisz notatkę..."
/>
<div className="flex gap-2">
<Button
onClick={() => handleSaveNote(n.note_id)}
variant="primary"
size="sm"
>
Zapisz
</Button>
<Button
onClick={() => {
setEditingNoteId(null);
setEditText('');
}}
variant="outline"
size="sm"
>
Anuluj
</Button>
</div>
</div>
) : (
<div className="text-gray-900 leading-relaxed">
{parseNoteText(n.note)}
</div>
)}
</div>
))}
</div>
)}
</CardContent>
</Card>
</PageContainer>
);
}