feat: Implement file upload and management features in ProjectViewPage

This commit is contained in:
2025-09-24 21:55:44 +02:00
parent 0f451555d3
commit 96333ecced
7 changed files with 490 additions and 15 deletions

View File

@@ -4,8 +4,9 @@ import { withAdminAuth } from "@/lib/middleware/auth";
// GET: Get user by ID (admin only)
async function getUserHandler(req, { params }) {
const { id } = await params;
try {
const user = getUserById(params.id);
const user = getUserById(id);
if (!user) {
return NextResponse.json(
@@ -29,9 +30,10 @@ async function getUserHandler(req, { params }) {
// PUT: Update user (admin only)
async function updateUserHandler(req, { params }) {
const { id } = await params;
try {
const data = await req.json();
const userId = params.id;
const userId = id;
// Prevent admin from deactivating themselves
if (data.is_active === false && userId === req.user.id) {
@@ -92,8 +94,9 @@ async function updateUserHandler(req, { params }) {
// DELETE: Delete user (admin only)
async function deleteUserHandler(req, { params }) {
const { id } = await params;
try {
const userId = params.id;
const userId = id;
// Prevent admin from deleting themselves
if (userId === req.user.id) {

View File

@@ -6,9 +6,9 @@ import path from "path";
import db from "@/lib/db";
export async function GET(request, { params }) {
try {
const fileId = params.fileId;
const { fileId } = await params;
try {
// Get file info from database
const file = db.prepare(`
SELECT * FROM file_attachments WHERE file_id = ?
@@ -53,10 +53,94 @@ export async function GET(request, { params }) {
}
}
export async function DELETE(request, { params }) {
export async function PUT(request, { params }) {
const { fileId } = await params;
try {
const fileId = params.fileId;
const body = await request.json();
const { description, original_filename } = body;
// Validate input
if (description !== undefined && typeof description !== 'string') {
return NextResponse.json(
{ error: "Description must be a string" },
{ status: 400 }
);
}
if (original_filename !== undefined && typeof original_filename !== 'string') {
return NextResponse.json(
{ error: "Original filename must be a string" },
{ status: 400 }
);
}
// Check if file exists
const existingFile = db.prepare(`
SELECT * FROM file_attachments WHERE file_id = ?
`).get(parseInt(fileId));
if (!existingFile) {
return NextResponse.json(
{ error: "File not found" },
{ status: 404 }
);
}
// Build update query
const updates = [];
const values = [];
if (description !== undefined) {
updates.push('description = ?');
values.push(description);
}
if (original_filename !== undefined) {
updates.push('original_filename = ?');
values.push(original_filename);
}
if (updates.length === 0) {
return NextResponse.json(
{ error: "No valid fields to update" },
{ status: 400 }
);
}
values.push(parseInt(fileId));
const result = db.prepare(`
UPDATE file_attachments
SET ${updates.join(', ')}
WHERE file_id = ?
`).run(...values);
if (result.changes === 0) {
return NextResponse.json(
{ error: "File not found" },
{ status: 404 }
);
}
// Get updated file
const updatedFile = db.prepare(`
SELECT * FROM file_attachments WHERE file_id = ?
`).get(parseInt(fileId));
return NextResponse.json(updatedFile);
} catch (error) {
console.error("Error updating file:", error);
return NextResponse.json(
{ error: "Failed to update file" },
{ status: 500 }
);
}
}
export async function DELETE(request, { params }) {
const { fileId } = await params;
try {
// Get file info from database
const file = db.prepare(`
SELECT * FROM file_attachments WHERE file_id = ?

View File

@@ -110,7 +110,7 @@ async function createNoteHandler(req) {
}
async function deleteNoteHandler(req, { params }) {
const { id } = params;
const { id } = await params;
// Get note data before deletion for audit log
const note = db.prepare("SELECT * FROM notes WHERE note_id = ?").get(id);
@@ -137,7 +137,8 @@ async function deleteNoteHandler(req, { params }) {
}
async function updateNoteHandler(req, { params }) {
const noteId = params.id;
const { id } = await params;
const noteId = id;
const { note } = await req.json();
if (!note || !noteId) {

View File

@@ -4,10 +4,11 @@ import { withReadAuth, withUserAuth } from "@/lib/middleware/auth";
// GET: Get a specific task template
async function getTaskHandler(req, { params }) {
const { id } = await params;
try {
const template = db
.prepare("SELECT * FROM tasks WHERE task_id = ? AND is_standard = 1")
.get(params.id);
.get(id);
if (!template) {
return NextResponse.json(
@@ -27,6 +28,7 @@ async function getTaskHandler(req, { params }) {
// PUT: Update a task template
async function updateTaskHandler(req, { params }) {
const { id } = await params;
try {
const { name, max_wait_days, description } = await req.json();
@@ -40,7 +42,7 @@ async function updateTaskHandler(req, { params }) {
SET name = ?, max_wait_days = ?, description = ?
WHERE task_id = ? AND is_standard = 1`
)
.run(name, max_wait_days || 0, description || null, params.id);
.run(name, max_wait_days || 0, description || null, id);
if (result.changes === 0) {
return NextResponse.json(
@@ -60,10 +62,11 @@ async function updateTaskHandler(req, { params }) {
// DELETE: Delete a task template
async function deleteTaskHandler(req, { params }) {
const { id } = await params;
try {
const result = db
.prepare("DELETE FROM tasks WHERE task_id = ? AND is_standard = 1")
.run(params.id);
.run(id);
if (result.changes === 0) {
return NextResponse.json(

View File

@@ -16,6 +16,8 @@ import PageContainer from "@/components/ui/PageContainer";
import PageHeader from "@/components/ui/PageHeader";
import ProjectStatusDropdown from "@/components/ProjectStatusDropdown";
import ClientProjectMap from "@/components/ui/ClientProjectMap";
import FileUploadBox from "@/components/FileUploadBox";
import FileItem from "@/components/FileItem";
export default function ProjectViewPage() {
const params = useParams();
@@ -23,6 +25,7 @@ export default function ProjectViewPage() {
const [project, setProject] = useState(null);
const [notes, setNotes] = useState([]);
const [loading, setLoading] = useState(true);
const [uploadedFiles, setUploadedFiles] = useState([]);
// Helper function to add a new note to the list
const addNote = (newNote) => {
@@ -40,6 +43,39 @@ export default function ProjectViewPage() {
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
)
);
};
useEffect(() => {
const fetchData = async () => {
if (!params.id) return;
@@ -56,8 +92,13 @@ export default function ProjectViewPage() {
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);
@@ -168,7 +209,7 @@ export default function ProjectViewPage() {
<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"
className="w-4 h-4 mr-1"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
@@ -229,7 +270,7 @@ export default function ProjectViewPage() {
className="w-4 h-4 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
@@ -493,7 +534,7 @@ export default function ProjectViewPage() {
<Link href="/projects" className="block">
<Button
variant="outline"
size="sm"
size="sm"
className="w-full justify-start"
>
<svg
@@ -536,6 +577,31 @@ export default function ProjectViewPage() {
</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 */}