feat: add task sets functionality with CRUD operations and UI integration

- Implemented NewTaskSetPage for creating task sets with templates.
- Created TaskSetsPage for listing and filtering task sets.
- Enhanced TaskTemplatesPage with navigation to task sets.
- Updated ProjectTaskForm to support task set selection.
- Modified PageHeader to support multiple action buttons.
- Initialized database with task_sets and task_set_templates tables.
- Added queries for task sets including creation, retrieval, and deletion.
- Implemented applyTaskSetToProject function for bulk task creation.
- Added test script for verifying task sets functionality.
This commit is contained in:
2025-10-07 21:58:08 +02:00
parent e19172d2bb
commit 952caf10d1
14 changed files with 1838 additions and 77 deletions

View File

@@ -0,0 +1,35 @@
import { applyTaskSetToProject } from "@/lib/queries/tasks";
import { NextResponse } from "next/server";
import { withUserAuth } from "@/lib/middleware/auth";
// POST: Apply a task set to a project (bulk create project tasks)
async function applyTaskSetHandler(req, { params }) {
try {
const { id } = await params;
const { project_id } = await req.json();
if (!project_id) {
return NextResponse.json(
{ error: "project_id is required" },
{ status: 400 }
);
}
const createdTaskIds = applyTaskSetToProject(id, project_id, req.user?.id || null);
return NextResponse.json({
success: true,
message: `Task set applied successfully. Created ${createdTaskIds.length} tasks.`,
createdTaskIds
});
} catch (error) {
console.error("Error applying task set:", error);
return NextResponse.json(
{ error: "Failed to apply task set", details: error.message },
{ status: 500 }
);
}
}
// Protected route - require authentication
export const POST = withUserAuth(applyTaskSetHandler);

View File

@@ -0,0 +1,130 @@
import {
getTaskSetById,
updateTaskSet,
deleteTaskSet,
addTaskTemplateToSet,
removeTaskTemplateFromSet,
} from "@/lib/queries/tasks";
import { NextResponse } from "next/server";
import { withReadAuth, withUserAuth } from "@/lib/middleware/auth";
import initializeDatabase from "@/lib/init-db";
// GET: Get a specific task set with its templates
async function getTaskSetHandler(req, { params }) {
initializeDatabase();
try {
const { id } = await params;
const taskSet = getTaskSetById(id);
if (!taskSet) {
return NextResponse.json(
{ error: "Task set not found" },
{ status: 404 }
);
}
return NextResponse.json(taskSet);
} catch (error) {
console.error("Error fetching task set:", error);
return NextResponse.json(
{ error: "Failed to fetch task set" },
{ status: 500 }
);
}
}
// PUT: Update a task set
async function updateTaskSetHandler(req, { params }) {
initializeDatabase();
try {
const { id } = await params;
const updates = await req.json();
// Validate required fields
if (updates.name !== undefined && !updates.name.trim()) {
return NextResponse.json(
{ error: "Name cannot be empty" },
{ status: 400 }
);
}
if (updates.task_category !== undefined) {
const validTypes = ["design", "construction"];
if (!validTypes.includes(updates.task_category)) {
return NextResponse.json(
{ error: "Invalid task_category. Must be one of: design, construction" },
{ status: 400 }
);
}
}
// Handle template updates
if (updates.templates !== undefined) {
// Clear existing templates
// Note: This is a simple implementation. In a real app, you might want to handle this more efficiently
const currentSet = getTaskSetById(id);
if (currentSet) {
for (const template of currentSet.templates) {
removeTaskTemplateFromSet(id, template.task_id);
}
}
// Add new templates
if (Array.isArray(updates.templates)) {
for (let i = 0; i < updates.templates.length; i++) {
const template = updates.templates[i];
addTaskTemplateToSet(id, template.task_id, i);
}
}
// Remove templates from updates object so it doesn't interfere with task set update
delete updates.templates;
}
const result = updateTaskSet(id, updates);
if (result.changes === 0) {
return NextResponse.json(
{ error: "Task set not found" },
{ status: 404 }
);
}
return NextResponse.json({ success: true });
} catch (error) {
console.error("Error updating task set:", error);
return NextResponse.json(
{ error: "Failed to update task set", details: error.message },
{ status: 500 }
);
}
}
// DELETE: Delete a task set
async function deleteTaskSetHandler(req, { params }) {
initializeDatabase();
try {
const { id } = await params;
const result = deleteTaskSet(id);
if (result.changes === 0) {
return NextResponse.json(
{ error: "Task set not found" },
{ status: 404 }
);
}
return NextResponse.json({ success: true });
} catch (error) {
console.error("Error deleting task set:", error);
return NextResponse.json(
{ error: "Failed to delete task set", details: error.message },
{ status: 500 }
);
}
}
// Protected routes - require authentication
export const GET = withReadAuth(getTaskSetHandler);
export const PUT = withUserAuth(updateTaskSetHandler);
export const DELETE = withUserAuth(deleteTaskSetHandler);

View File

@@ -0,0 +1,60 @@
import {
getAllTaskSets,
getTaskSetsByProjectType,
createTaskSet,
} from "@/lib/queries/tasks";
import { NextResponse } from "next/server";
import { withReadAuth, withUserAuth } from "@/lib/middleware/auth";
import initializeDatabase from "@/lib/init-db";
// GET: Get all task sets or filter by task category
async function getTaskSetsHandler(req) {
initializeDatabase();
const { searchParams } = new URL(req.url);
const taskCategory = searchParams.get("task_category");
if (taskCategory) {
const taskSets = getTaskSetsByTaskCategory(taskCategory);
return NextResponse.json(taskSets);
} else {
const taskSets = getAllTaskSets();
return NextResponse.json(taskSets);
}
}
// POST: Create a new task set
async function createTaskSetHandler(req) {
initializeDatabase();
try {
const data = await req.json();
if (!data.name || !data.task_category) {
return NextResponse.json(
{ error: "Name and task_category are required" },
{ status: 400 }
);
}
// Validate task_category
const validTypes = ["design", "construction"];
if (!validTypes.includes(data.task_category)) {
return NextResponse.json(
{ error: "Invalid task_category. Must be one of: design, construction" },
{ status: 400 }
);
}
const setId = createTaskSet(data);
return NextResponse.json({ success: true, id: setId });
} catch (error) {
console.error("Error creating task set:", error);
return NextResponse.json(
{ error: "Failed to create task set", details: error.message },
{ status: 500 }
);
}
}
// Protected routes - require authentication
export const GET = withReadAuth(getTaskSetsHandler);
export const POST = withUserAuth(createTaskSetHandler);