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

@@ -41,7 +41,29 @@ export default function initializeDatabase() {
name TEXT NOT NULL,
max_wait_days INTEGER DEFAULT 0,
is_standard INTEGER NOT NULL DEFAULT 0
); -- Table: project_tasks
);
-- Table: task_sets
CREATE TABLE IF NOT EXISTS task_sets (
set_id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
description TEXT,
task_category TEXT CHECK(task_category IN ('design', 'construction')) NOT NULL,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
);
-- Table: task_set_templates
CREATE TABLE IF NOT EXISTS task_set_templates (
set_id INTEGER NOT NULL,
task_template_id INTEGER NOT NULL,
sort_order INTEGER DEFAULT 0,
PRIMARY KEY (set_id, task_template_id),
FOREIGN KEY (set_id) REFERENCES task_sets(set_id) ON DELETE CASCADE,
FOREIGN KEY (task_template_id) REFERENCES tasks(task_id)
);
-- Table: project_tasks
CREATE TABLE IF NOT EXISTS project_tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_id INTEGER NOT NULL,
@@ -342,6 +364,24 @@ export default function initializeDatabase() {
console.warn("Migration warning:", e.message);
}
// Migration: Rename project_type to task_category in task_sets
try {
// Check if the old column exists and rename it
const tableInfo = db.prepare("PRAGMA table_info(task_sets)").all();
const hasOldColumn = tableInfo.some(col => col.name === 'project_type');
const hasNewColumn = tableInfo.some(col => col.name === 'task_category');
if (hasOldColumn && !hasNewColumn) {
// Rename the column
db.exec(`
ALTER TABLE task_sets RENAME COLUMN project_type TO task_category;
`);
console.log("✅ Renamed project_type to task_category in task_sets");
}
} catch (e) {
console.warn("Migration warning:", e.message);
}
// Generic file attachments table
db.exec(`
CREATE TABLE IF NOT EXISTS file_attachments (

View File

@@ -413,3 +413,192 @@ export function updateProjectTask(taskId, updates, userId = null) {
return result;
}
// ===== TASK SETS =====
// Get all task sets
export function getAllTaskSets() {
const taskSets = db
.prepare("SELECT * FROM task_sets ORDER BY name ASC")
.all();
// Add templates to each task set
return taskSets.map(taskSet => {
const templates = db
.prepare(`
SELECT
tst.sort_order,
t.task_id,
t.name,
t.max_wait_days,
t.description
FROM task_set_templates tst
JOIN tasks t ON tst.task_template_id = t.task_id
WHERE tst.set_id = ?
ORDER BY tst.sort_order ASC
`)
.all(taskSet.set_id);
return { ...taskSet, templates };
});
}
// Get task sets by task category
export function getTaskSetsByTaskCategory(taskCategory) {
const taskSets = db
.prepare("SELECT * FROM task_sets WHERE task_category = ? ORDER BY name ASC")
.all(taskCategory);
// Add templates to each task set
return taskSets.map(taskSet => {
const templates = db
.prepare(`
SELECT
tst.sort_order,
t.task_id,
t.name,
t.max_wait_days,
t.description
FROM task_set_templates tst
JOIN tasks t ON tst.task_template_id = t.task_id
WHERE tst.set_id = ?
ORDER BY tst.sort_order ASC
`)
.all(taskSet.set_id);
return { ...taskSet, templates };
});
}
// Get task set by ID with templates
export function getTaskSetById(setId) {
const taskSet = db
.prepare("SELECT * FROM task_sets WHERE set_id = ?")
.get(setId);
if (taskSet) {
const templates = db
.prepare(`
SELECT
tst.sort_order,
t.task_id,
t.name,
t.max_wait_days,
t.description
FROM task_set_templates tst
JOIN tasks t ON tst.task_template_id = t.task_id
WHERE tst.set_id = ?
ORDER BY tst.sort_order ASC
`)
.all(setId);
return { ...taskSet, templates };
}
return null;
}
// Create a new task set
export function createTaskSet(data) {
const result = db
.prepare(`
INSERT INTO task_sets (name, description, task_category, created_at, updated_at)
VALUES (?, ?, ?, datetime('now', 'localtime'), datetime('now', 'localtime'))
`)
.run(data.name, data.description || null, data.task_category);
return result.lastInsertRowid;
}
// Update a task set
export function updateTaskSet(setId, data) {
const fields = [];
const values = [];
if (data.name !== undefined) {
fields.push("name = ?");
values.push(data.name);
}
if (data.description !== undefined) {
fields.push("description = ?");
values.push(data.description || null);
}
if (data.task_category !== undefined) {
fields.push("task_category = ?");
values.push(data.task_category);
}
fields.push("updated_at = datetime('now', 'localtime')");
values.push(setId);
const stmt = db.prepare(`
UPDATE task_sets
SET ${fields.join(", ")}
WHERE set_id = ?
`);
return stmt.run(...values);
}
// Delete a task set
export function deleteTaskSet(setId) {
// Delete task set templates first (cascade should handle this, but being explicit)
db.prepare("DELETE FROM task_set_templates WHERE set_id = ?").run(setId);
// Delete the task set
return db.prepare("DELETE FROM task_sets WHERE set_id = ?").run(setId);
}
// Add task template to set
export function addTaskTemplateToSet(setId, taskTemplateId, sortOrder = 0) {
return db
.prepare(`
INSERT OR REPLACE INTO task_set_templates (set_id, task_template_id, sort_order)
VALUES (?, ?, ?)
`)
.run(setId, taskTemplateId, sortOrder);
}
// Remove task template from set
export function removeTaskTemplateFromSet(setId, taskTemplateId) {
return db
.prepare(`
DELETE FROM task_set_templates
WHERE set_id = ? AND task_template_id = ?
`)
.run(setId, taskTemplateId);
}
// Apply task set to project (bulk create project tasks)
export function applyTaskSetToProject(setId, projectId, userId = null) {
// Get the task set with templates
const taskSet = getTaskSetById(setId);
if (!taskSet) {
throw new Error(`Task set with ID ${setId} not found`);
}
const createdTasks = [];
const language = getUserLanguage();
// Create project tasks for each template in the set
for (const template of taskSet.templates) {
const result = createProjectTask({
project_id: projectId,
task_template_id: template.task_id,
status: "pending",
priority: "normal",
created_by: userId,
assigned_to: null, // Will be assigned based on user role logic in createProjectTask
});
createdTasks.push(result.lastInsertRowid);
// Add system note for task set application
const logMessage = `${serverT("Task added from set", language)} "${taskSet.name}"`;
addNoteToTask(result.lastInsertRowid, logMessage, true, userId);
}
return createdTasks;
}