feat: Add user tracking to project tasks and notes
- Implemented user tracking columns in project_tasks and notes tables. - Added created_by and assigned_to fields to project_tasks. - Introduced created_by field and is_system flag in notes. - Updated API endpoints to handle user tracking during task and note creation. - Enhanced database initialization to include new columns and indexes. - Created utility functions to fetch users for task assignment. - Updated front-end components to display user information for tasks and notes. - Added tests for project-tasks API endpoints to verify functionality.
This commit is contained in:
@@ -196,11 +196,72 @@ export default function initializeDatabase() {
|
||||
// Column already exists, ignore error
|
||||
}
|
||||
|
||||
// Add foreign key indexes for performance
|
||||
// Migration: Add user tracking columns to project_tasks table
|
||||
try {
|
||||
db.exec(`
|
||||
CREATE INDEX IF NOT EXISTS idx_projects_created_by ON projects(created_by);
|
||||
CREATE INDEX IF NOT EXISTS idx_projects_assigned_to ON projects(assigned_to);
|
||||
ALTER TABLE project_tasks ADD COLUMN created_by TEXT;
|
||||
`);
|
||||
} catch (e) {
|
||||
// Column already exists, ignore error
|
||||
}
|
||||
|
||||
try {
|
||||
db.exec(`
|
||||
ALTER TABLE project_tasks ADD COLUMN assigned_to TEXT;
|
||||
`);
|
||||
} catch (e) {
|
||||
// Column already exists, ignore error
|
||||
}
|
||||
|
||||
try {
|
||||
db.exec(`
|
||||
ALTER TABLE project_tasks ADD COLUMN created_at TEXT;
|
||||
`);
|
||||
} catch (e) {
|
||||
// Column already exists, ignore error
|
||||
}
|
||||
|
||||
try {
|
||||
db.exec(`
|
||||
ALTER TABLE project_tasks ADD COLUMN updated_at TEXT;
|
||||
`);
|
||||
} catch (e) {
|
||||
// Column already exists, ignore error
|
||||
}
|
||||
|
||||
// Create indexes for project_tasks user tracking
|
||||
try {
|
||||
db.exec(`
|
||||
CREATE INDEX IF NOT EXISTS idx_project_tasks_created_by ON project_tasks(created_by);
|
||||
CREATE INDEX IF NOT EXISTS idx_project_tasks_assigned_to ON project_tasks(assigned_to);
|
||||
`);
|
||||
} catch (e) {
|
||||
// Index already exists, ignore error
|
||||
}
|
||||
|
||||
// Migration: Add user tracking columns to notes table
|
||||
try {
|
||||
db.exec(`
|
||||
ALTER TABLE notes ADD COLUMN created_by TEXT;
|
||||
`);
|
||||
} catch (e) {
|
||||
// Column already exists, ignore error
|
||||
}
|
||||
|
||||
try {
|
||||
db.exec(`
|
||||
ALTER TABLE notes ADD COLUMN is_system INTEGER DEFAULT 0;
|
||||
`);
|
||||
} catch (e) {
|
||||
// Column already exists, ignore error
|
||||
}
|
||||
|
||||
// Create indexes for notes user tracking
|
||||
try {
|
||||
db.exec(`
|
||||
CREATE INDEX IF NOT EXISTS idx_notes_created_by ON notes(created_by);
|
||||
CREATE INDEX IF NOT EXISTS idx_notes_project_id ON notes(project_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_notes_task_id ON notes(task_id);
|
||||
`);
|
||||
} catch (e) {
|
||||
// Index already exists, ignore error
|
||||
|
||||
@@ -2,29 +2,100 @@ import db from "../db.js";
|
||||
|
||||
export function getNotesByProjectId(project_id) {
|
||||
return db
|
||||
.prepare(`SELECT * FROM notes WHERE project_id = ? ORDER BY note_date DESC`)
|
||||
.prepare(
|
||||
`
|
||||
SELECT n.*,
|
||||
u.name as created_by_name,
|
||||
u.email as created_by_email
|
||||
FROM notes n
|
||||
LEFT JOIN users u ON n.created_by = u.id
|
||||
WHERE n.project_id = ?
|
||||
ORDER BY n.note_date DESC
|
||||
`
|
||||
)
|
||||
.all(project_id);
|
||||
}
|
||||
|
||||
export function addNoteToProject(project_id, note) {
|
||||
db.prepare(`INSERT INTO notes (project_id, note) VALUES (?, ?)`).run(
|
||||
project_id,
|
||||
note
|
||||
);
|
||||
export function addNoteToProject(project_id, note, created_by = null) {
|
||||
db.prepare(
|
||||
`
|
||||
INSERT INTO notes (project_id, note, created_by, note_date)
|
||||
VALUES (?, ?, ?, CURRENT_TIMESTAMP)
|
||||
`
|
||||
).run(project_id, note, created_by);
|
||||
}
|
||||
|
||||
export function getNotesByTaskId(task_id) {
|
||||
return db
|
||||
.prepare(`SELECT * FROM notes WHERE task_id = ? ORDER BY note_date DESC`)
|
||||
.prepare(
|
||||
`
|
||||
SELECT n.*,
|
||||
u.name as created_by_name,
|
||||
u.email as created_by_email
|
||||
FROM notes n
|
||||
LEFT JOIN users u ON n.created_by = u.id
|
||||
WHERE n.task_id = ?
|
||||
ORDER BY n.note_date DESC
|
||||
`
|
||||
)
|
||||
.all(task_id);
|
||||
}
|
||||
|
||||
export function addNoteToTask(task_id, note, is_system = false) {
|
||||
export function addNoteToTask(
|
||||
task_id,
|
||||
note,
|
||||
is_system = false,
|
||||
created_by = null
|
||||
) {
|
||||
db.prepare(
|
||||
`INSERT INTO notes (task_id, note, is_system) VALUES (?, ?, ?)`
|
||||
).run(task_id, note, is_system ? 1 : 0);
|
||||
`INSERT INTO notes (task_id, note, is_system, created_by, note_date)
|
||||
VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)`
|
||||
).run(task_id, note, is_system ? 1 : 0, created_by);
|
||||
}
|
||||
|
||||
export function deleteNote(note_id) {
|
||||
db.prepare(`DELETE FROM notes WHERE note_id = ?`).run(note_id);
|
||||
}
|
||||
|
||||
// Get all notes with user information (for admin/reporting purposes)
|
||||
export function getAllNotesWithUsers() {
|
||||
return db
|
||||
.prepare(
|
||||
`
|
||||
SELECT n.*,
|
||||
u.name as created_by_name,
|
||||
u.email as created_by_email,
|
||||
p.project_name,
|
||||
COALESCE(pt.custom_task_name, t.name) as task_name
|
||||
FROM notes n
|
||||
LEFT JOIN users u ON n.created_by = u.id
|
||||
LEFT JOIN projects p ON n.project_id = p.project_id
|
||||
LEFT JOIN project_tasks pt ON n.task_id = pt.id
|
||||
LEFT JOIN tasks t ON pt.task_template_id = t.task_id
|
||||
ORDER BY n.note_date DESC
|
||||
`
|
||||
)
|
||||
.all();
|
||||
}
|
||||
|
||||
// Get notes created by a specific user
|
||||
export function getNotesByCreator(userId) {
|
||||
return db
|
||||
.prepare(
|
||||
`
|
||||
SELECT n.*,
|
||||
u.name as created_by_name,
|
||||
u.email as created_by_email,
|
||||
p.project_name,
|
||||
COALESCE(pt.custom_task_name, t.name) as task_name
|
||||
FROM notes n
|
||||
LEFT JOIN users u ON n.created_by = u.id
|
||||
LEFT JOIN projects p ON n.project_id = p.project_id
|
||||
LEFT JOIN project_tasks pt ON n.task_id = pt.id
|
||||
LEFT JOIN tasks t ON pt.task_template_id = t.task_id
|
||||
WHERE n.created_by = ?
|
||||
ORDER BY n.note_date DESC
|
||||
`
|
||||
)
|
||||
.all(userId);
|
||||
}
|
||||
|
||||
@@ -222,9 +222,13 @@ export function getNotesForProject(projectId) {
|
||||
return db
|
||||
.prepare(
|
||||
`
|
||||
SELECT * FROM notes
|
||||
WHERE project_id = ?
|
||||
ORDER BY note_date DESC
|
||||
SELECT n.*,
|
||||
u.name as created_by_name,
|
||||
u.email as created_by_email
|
||||
FROM notes n
|
||||
LEFT JOIN users u ON n.created_by = u.id
|
||||
WHERE n.project_id = ?
|
||||
ORDER BY n.note_date DESC
|
||||
`
|
||||
)
|
||||
.all(projectId);
|
||||
|
||||
@@ -27,10 +27,16 @@ export function getAllProjectTasks() {
|
||||
p.plot,
|
||||
p.city,
|
||||
p.address,
|
||||
p.finish_date
|
||||
p.finish_date,
|
||||
creator.name as created_by_name,
|
||||
creator.email as created_by_email,
|
||||
assignee.name as assigned_to_name,
|
||||
assignee.email as assigned_to_email
|
||||
FROM project_tasks pt
|
||||
LEFT JOIN tasks t ON pt.task_template_id = t.task_id
|
||||
LEFT JOIN projects p ON pt.project_id = p.project_id
|
||||
LEFT JOIN users creator ON pt.created_by = creator.id
|
||||
LEFT JOIN users assignee ON pt.assigned_to = assignee.id
|
||||
ORDER BY pt.date_added DESC
|
||||
`
|
||||
)
|
||||
@@ -50,9 +56,15 @@ export function getProjectTasks(projectId) {
|
||||
CASE
|
||||
WHEN pt.task_template_id IS NOT NULL THEN 'template'
|
||||
ELSE 'custom'
|
||||
END as task_type
|
||||
END as task_type,
|
||||
creator.name as created_by_name,
|
||||
creator.email as created_by_email,
|
||||
assignee.name as assigned_to_name,
|
||||
assignee.email as assigned_to_email
|
||||
FROM project_tasks pt
|
||||
LEFT JOIN tasks t ON pt.task_template_id = t.task_id
|
||||
LEFT JOIN users creator ON pt.created_by = creator.id
|
||||
LEFT JOIN users assignee ON pt.assigned_to = assignee.id
|
||||
WHERE pt.project_id = ?
|
||||
ORDER BY pt.date_added DESC
|
||||
`
|
||||
@@ -68,14 +80,19 @@ export function createProjectTask(data) {
|
||||
if (data.task_template_id) {
|
||||
// Creating from template - explicitly set custom_max_wait_days to NULL so COALESCE uses template value
|
||||
const stmt = db.prepare(`
|
||||
INSERT INTO project_tasks (project_id, task_template_id, custom_max_wait_days, status, priority)
|
||||
VALUES (?, ?, NULL, ?, ?)
|
||||
INSERT INTO project_tasks (
|
||||
project_id, task_template_id, custom_max_wait_days, status, priority,
|
||||
created_by, assigned_to, created_at, updated_at
|
||||
)
|
||||
VALUES (?, ?, NULL, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||
`);
|
||||
result = stmt.run(
|
||||
data.project_id,
|
||||
data.task_template_id,
|
||||
data.status || "pending",
|
||||
data.priority || "normal"
|
||||
data.priority || "normal",
|
||||
data.created_by || null,
|
||||
data.assigned_to || null
|
||||
);
|
||||
|
||||
// Get the template name for the log
|
||||
@@ -85,8 +102,11 @@ export function createProjectTask(data) {
|
||||
} else {
|
||||
// Creating custom task
|
||||
const stmt = db.prepare(`
|
||||
INSERT INTO project_tasks (project_id, custom_task_name, custom_max_wait_days, custom_description, status, priority)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
INSERT INTO project_tasks (
|
||||
project_id, custom_task_name, custom_max_wait_days, custom_description,
|
||||
status, priority, created_by, assigned_to, created_at, updated_at
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||
`);
|
||||
result = stmt.run(
|
||||
data.project_id,
|
||||
@@ -94,7 +114,9 @@ export function createProjectTask(data) {
|
||||
data.custom_max_wait_days || 0,
|
||||
data.custom_description || "",
|
||||
data.status || "pending",
|
||||
data.priority || "normal"
|
||||
data.priority || "normal",
|
||||
data.created_by || null,
|
||||
data.assigned_to || null
|
||||
);
|
||||
|
||||
taskName = data.custom_task_name;
|
||||
@@ -105,14 +127,14 @@ export function createProjectTask(data) {
|
||||
const priority = data.priority || "normal";
|
||||
const status = data.status || "pending";
|
||||
const logMessage = `Task "${taskName}" created with priority: ${priority}, status: ${status}`;
|
||||
addNoteToTask(result.lastInsertRowid, logMessage, true);
|
||||
addNoteToTask(result.lastInsertRowid, logMessage, true, data.created_by);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Update project task status
|
||||
export function updateProjectTaskStatus(taskId, status) {
|
||||
export function updateProjectTaskStatus(taskId, status, userId = null) {
|
||||
// First get the current task details for logging
|
||||
const getCurrentTask = db.prepare(`
|
||||
SELECT
|
||||
@@ -136,7 +158,7 @@ export function updateProjectTaskStatus(taskId, status) {
|
||||
// Starting a task - set date_started
|
||||
stmt = db.prepare(`
|
||||
UPDATE project_tasks
|
||||
SET status = ?, date_started = CURRENT_TIMESTAMP
|
||||
SET status = ?, date_started = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ?
|
||||
`);
|
||||
result = stmt.run(status, taskId);
|
||||
@@ -144,7 +166,7 @@ export function updateProjectTaskStatus(taskId, status) {
|
||||
// Completing a task - set date_completed
|
||||
stmt = db.prepare(`
|
||||
UPDATE project_tasks
|
||||
SET status = ?, date_completed = CURRENT_TIMESTAMP
|
||||
SET status = ?, date_completed = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ?
|
||||
`);
|
||||
result = stmt.run(status, taskId);
|
||||
@@ -152,7 +174,7 @@ export function updateProjectTaskStatus(taskId, status) {
|
||||
// Just updating status without changing timestamps
|
||||
stmt = db.prepare(`
|
||||
UPDATE project_tasks
|
||||
SET status = ?
|
||||
SET status = ?, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ?
|
||||
`);
|
||||
result = stmt.run(status, taskId);
|
||||
@@ -162,7 +184,7 @@ export function updateProjectTaskStatus(taskId, status) {
|
||||
if (result.changes > 0 && oldStatus !== status) {
|
||||
const taskName = currentTask.task_name || "Unknown task";
|
||||
const logMessage = `Status changed from "${oldStatus}" to "${status}"`;
|
||||
addNoteToTask(taskId, logMessage, true);
|
||||
addNoteToTask(taskId, logMessage, true, userId);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -173,3 +195,99 @@ export function deleteProjectTask(taskId) {
|
||||
const stmt = db.prepare("DELETE FROM project_tasks WHERE id = ?");
|
||||
return stmt.run(taskId);
|
||||
}
|
||||
|
||||
// Get project tasks assigned to a specific user
|
||||
export function getProjectTasksByAssignedUser(userId) {
|
||||
return db
|
||||
.prepare(
|
||||
`
|
||||
SELECT
|
||||
pt.*,
|
||||
COALESCE(pt.custom_task_name, t.name) as task_name,
|
||||
COALESCE(pt.custom_max_wait_days, t.max_wait_days) as max_wait_days,
|
||||
COALESCE(pt.custom_description, t.description) as description,
|
||||
CASE
|
||||
WHEN pt.task_template_id IS NOT NULL THEN 'template'
|
||||
ELSE 'custom'
|
||||
END as task_type,
|
||||
p.project_name,
|
||||
p.wp,
|
||||
p.plot,
|
||||
p.city,
|
||||
p.address,
|
||||
p.finish_date,
|
||||
creator.name as created_by_name,
|
||||
creator.email as created_by_email,
|
||||
assignee.name as assigned_to_name,
|
||||
assignee.email as assigned_to_email
|
||||
FROM project_tasks pt
|
||||
LEFT JOIN tasks t ON pt.task_template_id = t.task_id
|
||||
LEFT JOIN projects p ON pt.project_id = p.project_id
|
||||
LEFT JOIN users creator ON pt.created_by = creator.id
|
||||
LEFT JOIN users assignee ON pt.assigned_to = assignee.id
|
||||
WHERE pt.assigned_to = ?
|
||||
ORDER BY pt.date_added DESC
|
||||
`
|
||||
)
|
||||
.all(userId);
|
||||
}
|
||||
|
||||
// Get project tasks created by a specific user
|
||||
export function getProjectTasksByCreator(userId) {
|
||||
return db
|
||||
.prepare(
|
||||
`
|
||||
SELECT
|
||||
pt.*,
|
||||
COALESCE(pt.custom_task_name, t.name) as task_name,
|
||||
COALESCE(pt.custom_max_wait_days, t.max_wait_days) as max_wait_days,
|
||||
COALESCE(pt.custom_description, t.description) as description,
|
||||
CASE
|
||||
WHEN pt.task_template_id IS NOT NULL THEN 'template'
|
||||
ELSE 'custom'
|
||||
END as task_type,
|
||||
p.project_name,
|
||||
p.wp,
|
||||
p.plot,
|
||||
p.city,
|
||||
p.address,
|
||||
p.finish_date,
|
||||
creator.name as created_by_name,
|
||||
creator.email as created_by_email,
|
||||
assignee.name as assigned_to_name,
|
||||
assignee.email as assigned_to_email
|
||||
FROM project_tasks pt
|
||||
LEFT JOIN tasks t ON pt.task_template_id = t.task_id
|
||||
LEFT JOIN projects p ON pt.project_id = p.project_id
|
||||
LEFT JOIN users creator ON pt.created_by = creator.id
|
||||
LEFT JOIN users assignee ON pt.assigned_to = assignee.id
|
||||
WHERE pt.created_by = ?
|
||||
ORDER BY pt.date_added DESC
|
||||
`
|
||||
)
|
||||
.all(userId);
|
||||
}
|
||||
|
||||
// Update project task assignment
|
||||
export function updateProjectTaskAssignment(taskId, assignedToUserId) {
|
||||
const stmt = db.prepare(`
|
||||
UPDATE project_tasks
|
||||
SET assigned_to = ?, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ?
|
||||
`);
|
||||
return stmt.run(assignedToUserId, taskId);
|
||||
}
|
||||
|
||||
// Get active users for task assignment (same as projects)
|
||||
export function getAllUsersForTaskAssignment() {
|
||||
return db
|
||||
.prepare(
|
||||
`
|
||||
SELECT id, name, email, role
|
||||
FROM users
|
||||
WHERE is_active = 1
|
||||
ORDER BY name ASC
|
||||
`
|
||||
)
|
||||
.all();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user