Continued development

This commit is contained in:
c0d3.m0nk3y 2026-05-11 23:07:00 -04:00
parent 94e532986c
commit 9119eca1b4
6 changed files with 367 additions and 18 deletions

View File

@ -11,6 +11,123 @@ namespace trakker.Data
/// </summary>
internal class TaskData(string connectionString) : DataAccess(connectionString)
{
public BindingList<trakker.Models.Task> GetRecursive(string? id = null, trakker.Models.Task.RecursiveRoot root = trakker.Models.Task.RecursiveRoot.TASK_ID)
{
var results = new BindingList<trakker.Models.Task>();
string whereClause = "1 = 1";
switch(root)
{
case trakker.Models.Task.RecursiveRoot.TASK_ID:
if (id != null)
{
whereClause = "task_id = $id";
}
break;
case trakker.Models.Task.RecursiveRoot.PARENT_TASK_ID:
if (id != null)
{
whereClause = "parent_task_id = $id";
}
break;
}
string sql = $@"
WITH RECURSIVE TaskHierarchy AS (
-- Anchor: starting task(s)
SELECT
task_id,
project_id,
title,
description,
status,
priority,
due_date,
estimated_hours,
actual_hours,
hourly_rate,
parent_task_id,
created_at,
updated_at,
0 AS level,
title AS path
FROM tasks
WHERE
{whereClause}
UNION ALL
-- Recursive part: get children
SELECT
t.task_id,
t.project_id,
t.title,
t.description,
t.status,
t.priority,
t.due_date,
t.estimated_hours,
t.actual_hours,
t.hourly_rate,
t.parent_task_id,
t.created_at,
t.updated_at,
th.level + 1,
th.path || ' > ' || t.title
FROM tasks t
JOIN TaskHierarchy th ON t.parent_task_id = th.task_id
)
SELECT *
FROM TaskHierarchy
ORDER BY path
;
";
using var conn = OpenConnection();
using var cmd = conn.CreateCommand();
cmd.CommandText = sql;
if (id != null)
{
cmd.Parameters.AddWithValue("$id", id);
}
using var reader = cmd.ExecuteReader();
var _var1 = reader.GetOrdinal("task_id");
var _var2 = reader.GetOrdinal("project_id");
var _var3 = reader.GetOrdinal("title");
var _var4 = reader.GetOrdinal("description");
var _var5 = reader.GetOrdinal("status");
var _var6 = reader.GetOrdinal("priority");
var _var7 = reader.GetOrdinal("due_date");
var _var8 = reader.GetOrdinal("estimated_hours");
var _var9 = reader.GetOrdinal("actual_hours");
var _var10 = reader.GetOrdinal("hourly_rate");
var _var11 = reader.GetOrdinal("parent_task_id");
var _var12 = reader.GetOrdinal("created_at");
var _var13 = reader.GetOrdinal("updated_at");
while (reader.Read())
{
results.Add(new trakker.Models.Task
{
TaskId = reader.GetString(_var1),
ProjectId = reader.GetString(_var2),
Title = reader.GetString(_var3),
Description = reader.GetString(_var4),
Status = reader.GetString(_var5),
Priority = reader.GetString(_var6),
DueDate = reader.IsDBNull(_var7) ? null : reader.GetDateTime(_var7),
EstimatedHours = reader.IsDBNull(_var8) ? null : reader.GetDouble(_var8),
ActualHours = reader.IsDBNull(_var9) ? null : reader.GetDouble(_var9),
HourlyRate = reader.IsDBNull(_var10) ? null : reader.GetDouble(_var10),
ParentTaskId = reader.IsDBNull(_var11) ? null : reader.GetString(_var11),
CreatedAt = reader.IsDBNull(_var12) ? null : reader.GetDateTime(_var12),
UpdatedAt = reader.IsDBNull(_var13) ? null : reader.GetDateTime(_var13),
});
}
return results;
}
public trakker.Models.Task Get(string? taskId = null)
{

View File

@ -51,6 +51,8 @@
deleteThisTaskSubtaskToolStripMenuItem = new ToolStripMenuItem();
toolStripSeparator1 = new ToolStripSeparator();
addACommentToolStripMenuItem = new ToolStripMenuItem();
splitContainerTasks2 = new SplitContainer();
dataGridViewProjectTasks = new DataGridView();
MainForm_MenuStrip.SuspendLayout();
tabControlMainForm.SuspendLayout();
MainForm_TabPage2.SuspendLayout();
@ -63,8 +65,13 @@
tableLayoutPanelTasks1.SuspendLayout();
((System.ComponentModel.ISupportInitialize)splitContainerTasks1).BeginInit();
splitContainerTasks1.Panel1.SuspendLayout();
splitContainerTasks1.Panel2.SuspendLayout();
splitContainerTasks1.SuspendLayout();
contextMenuStripTreeviewTasks.SuspendLayout();
((System.ComponentModel.ISupportInitialize)splitContainerTasks2).BeginInit();
splitContainerTasks2.Panel1.SuspendLayout();
splitContainerTasks2.SuspendLayout();
((System.ComponentModel.ISupportInitialize)dataGridViewProjectTasks).BeginInit();
SuspendLayout();
//
// MainForm_MenuStrip
@ -234,6 +241,10 @@
// splitContainerTasks1.Panel1
//
splitContainerTasks1.Panel1.Controls.Add(treeViewTasks1);
//
// splitContainerTasks1.Panel2
//
splitContainerTasks1.Panel2.Controls.Add(splitContainerTasks2);
splitContainerTasks1.Size = new Size(1856, 960);
splitContainerTasks1.SplitterDistance = 618;
splitContainerTasks1.TabIndex = 0;
@ -252,7 +263,7 @@
contextMenuStripTreeviewTasks.ImageScalingSize = new Size(32, 32);
contextMenuStripTreeviewTasks.Items.AddRange(new ToolStripItem[] { addTaskSubtaskToolStripMenuItem, editThisTaskSubtaskToolStripMenuItem, deleteThisTaskSubtaskToolStripMenuItem, toolStripSeparator1, addACommentToolStripMenuItem });
contextMenuStripTreeviewTasks.Name = "contextMenuStripTreeviewTasks";
contextMenuStripTreeviewTasks.Size = new Size(371, 206);
contextMenuStripTreeviewTasks.Size = new Size(371, 162);
//
// addTaskSubtaskToolStripMenuItem
//
@ -287,6 +298,33 @@
addACommentToolStripMenuItem.Text = "Add a comment";
addACommentToolStripMenuItem.Click += addACommentToolStripMenuItem_Click;
//
// splitContainerTasks2
//
splitContainerTasks2.Dock = DockStyle.Fill;
splitContainerTasks2.Location = new Point(0, 0);
splitContainerTasks2.Name = "splitContainerTasks2";
splitContainerTasks2.Orientation = Orientation.Horizontal;
//
// splitContainerTasks2.Panel1
//
splitContainerTasks2.Panel1.Controls.Add(dataGridViewProjectTasks);
splitContainerTasks2.Size = new Size(1234, 960);
splitContainerTasks2.SplitterDistance = 411;
splitContainerTasks2.TabIndex = 0;
//
// dataGridViewProjectTasks
//
dataGridViewProjectTasks.AllowUserToAddRows = false;
dataGridViewProjectTasks.AllowUserToDeleteRows = false;
dataGridViewProjectTasks.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
dataGridViewProjectTasks.Dock = DockStyle.Fill;
dataGridViewProjectTasks.Location = new Point(0, 0);
dataGridViewProjectTasks.Name = "dataGridViewProjectTasks";
dataGridViewProjectTasks.ReadOnly = true;
dataGridViewProjectTasks.RowHeadersWidth = 82;
dataGridViewProjectTasks.Size = new Size(1234, 411);
dataGridViewProjectTasks.TabIndex = 0;
//
// MainForm
//
AutoScaleDimensions = new SizeF(13F, 32F);
@ -310,9 +348,14 @@
MainForm_TabPage4.ResumeLayout(false);
tableLayoutPanelTasks1.ResumeLayout(false);
splitContainerTasks1.Panel1.ResumeLayout(false);
splitContainerTasks1.Panel2.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)splitContainerTasks1).EndInit();
splitContainerTasks1.ResumeLayout(false);
contextMenuStripTreeviewTasks.ResumeLayout(false);
splitContainerTasks2.Panel1.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)splitContainerTasks2).EndInit();
splitContainerTasks2.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)dataGridViewProjectTasks).EndInit();
ResumeLayout(false);
PerformLayout();
}
@ -341,5 +384,7 @@
private ToolStripMenuItem deleteThisTaskSubtaskToolStripMenuItem;
private ToolStripSeparator toolStripSeparator1;
private ToolStripMenuItem addACommentToolStripMenuItem;
private SplitContainer splitContainerTasks2;
private DataGridView dataGridViewProjectTasks;
}
}

View File

@ -290,6 +290,30 @@ namespace trakker
}
};
}
public void InitTreeViewTasks()
{
// Basic TreeView configuration
treeViewTasks1.BeginUpdate();
treeViewTasks1.Nodes.Clear();
treeViewTasks1.ShowLines = true;
treeViewTasks1.ShowRootLines = true;
treeViewTasks1.HideSelection = false;
treeViewTasks1.EndUpdate();
// When a tree node is clicked, fetch and show notebooks for that folder
treeViewTasks1.NodeMouseClick += (sender, e) =>
{
TaskFS? selectedNode = (TaskFS?)e.Node.Tag ?? new TaskFS();
if (selectedNode?.Parent == "/" || selectedNode?.GUID == "/")
{
_ctrl.LoadTasksRecursive(selectedNode.ProjectId, trakker.Models.Task.RecursiveRoot.PARENT_TASK_ID); // Load all tasks for the project when root node is clicked
return;
}
_ctrl.LoadTasksRecursive(selectedNode?.GUID ?? "/", trakker.Models.Task.RecursiveRoot.TASK_ID); // Load all tasks for the project when root node is clicked
};
}
public void FillTreeViewTasks(List<TaskFS> items)
{
@ -454,24 +478,166 @@ namespace trakker
{
}
public void InitDataGridViewProjectTasks()
{
dataGridViewProjectTasks.AllowUserToAddRows = false;
dataGridViewProjectTasks.AllowUserToDeleteRows = false;
dataGridViewProjectTasks.AutoGenerateColumns = false;
dataGridViewProjectTasks.BackgroundColor = Color.White;
dataGridViewProjectTasks.ColumnHeadersDefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleLeft;
dataGridViewProjectTasks.ColumnHeadersDefaultCellStyle.WrapMode = DataGridViewTriState.False;
dataGridViewProjectTasks.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
dataGridViewProjectTasks.ColumnHeadersVisible = true;
dataGridViewProjectTasks.MultiSelect = false;
dataGridViewProjectTasks.ReadOnly = true;
dataGridViewProjectTasks.RowHeadersVisible = false;
dataGridViewProjectTasks.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
// Then add your columns...
dataGridViewProjectTasks.Columns.Clear();
{
var textColumn = new DataGridViewTextBoxColumn
{
AutoSizeMode = DataGridViewAutoSizeColumnMode.None,
DataPropertyName = "Title",
Name = "Title",
Visible = true,
Width = 350,
};
dataGridViewProjectTasks.Columns.Add(textColumn);
}
// 1. Status - Most important for quick visual scanning
{
var textColumn = new DataGridViewTextBoxColumn
{
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill,
DataPropertyName = "Status",
Name = "Status",
Visible = true,
};
dataGridViewProjectTasks.Columns.Add(textColumn);
}
//private void button1_Click(object sender, EventArgs e)
//{
// var dialog = new ClientForm(new Client());
// 2. Priority - Right after status
{
var textColumn = new DataGridViewTextBoxColumn
{
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill,
DataPropertyName = "Priority",
Name = "Priority",
Visible = true,
};
dataGridViewProjectTasks.Columns.Add(textColumn);
}
// 3. Due Date - Time sensitivity
{
var textColumn = new DataGridViewTextBoxColumn
{
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill,
DataPropertyName = "DueDate",
DefaultCellStyle = { Format = "MM/dd/yyyy" },
Name = "Due Date",
Visible = true,
};
dataGridViewProjectTasks.Columns.Add(textColumn);
}
// 4. Estimated Hours
{
var textColumn = new DataGridViewTextBoxColumn
{
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill,
DataPropertyName = "EstimatedHours",
Name = "Est Hours",
Visible = true,
};
textColumn.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
textColumn.HeaderCell.Style.Alignment = DataGridViewContentAlignment.MiddleRight;
dataGridViewProjectTasks.Columns.Add(textColumn);
}
// 5. Actual Hours
{
var textColumn = new DataGridViewTextBoxColumn
{
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill,
DataPropertyName = "ActualHours",
Name = "Act Hours",
Visible = true,
};
textColumn.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
textColumn.HeaderCell.Style.Alignment = DataGridViewContentAlignment.MiddleRight;
dataGridViewProjectTasks.Columns.Add(textColumn);
}
// 6. Hourly Rate - Financial info at the end
{
var textColumn = new DataGridViewTextBoxColumn
{
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill,
DataPropertyName = "HourlyRate",
Name = "Hourly Rate",
Visible = true,
};
textColumn.DefaultCellStyle.Format = "$#,##0.00";
textColumn.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
textColumn.HeaderCell.Style.Alignment = DataGridViewContentAlignment.MiddleRight;
dataGridViewProjectTasks.Columns.Add(textColumn);
}
dataGridViewProjectTasks.Click += (s, e) =>
{
if (dataGridViewProjectTasks.SelectedRows.Count > 0)
{
var selectedIdx = dataGridViewProjectTasks.SelectedRows[0].Index;
var selectedProject = dataGridViewProjectTasks.SelectedRows[0].DataBoundItem as Project;
if (selectedProject != null)
{
//var dialog = new ProjectForm(selectedProject, _ctrl.GetClients(), _ctrl.GetLOV("project.status"));
//if (dialog.ShowDialog(this) == DialogResult.OK)
//{
// Client client = dialog.Client;
// ClientData clientData = new ClientData(connectionString);
// Project project = dialog.Project;
// ProjectData projectData = new ProjectData(connectionString);
// try
// {
// clientData.Upsert(client);
// projectData.Upsert(project);
// _ctrl.LoadProjects(); // Reload projects to update the DataGridView with any changes
// dataGridViewProjectTasks.ClearSelection();
// if (selectedIdx >= 0 && selectedIdx < dataGridViewProjectTasks.Rows.Count)
// {
// dataGridViewProjectTasks.Rows[selectedIdx].Selected = true;
// }
// }
// catch (Exception ex)
// {
// MessageBox.Show($"Error saving client: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
// }
// MessageBox.Show($"Error saving project: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
// }
//}
//return;
}
}
};
//dataGridViewClients.SelectionChanged += (s, e) =>
//{
// if (dataGridViewClients.SelectedRows.Count > 0)
// {
// var selectedClient = dataGridViewClients.SelectedRows[0].DataBoundItem as Client;
// if (selectedClient != null)
// {
// // Handle the selected client as needed
// // MessageBox.Show($"Selected Client: {selectedClient.AddressStreet}", "Client Selected", MessageBoxButtons.OK, MessageBoxIcon.Information );
// }
// }
//};
}
public void FillDataGridViewProjectTasks(BindingSource tasks)
{
dataGridViewProjectTasks.DataSource = tasks;
dataGridViewProjectTasks.Refresh();
}
}
}

View File

@ -14,5 +14,8 @@ namespace trakker.Interfaces
void InitDataGridViewProjects();
void FillDataGridViewProjects(BindingSource projects);
void FillTreeViewTasks(List<TaskFS> items);
void InitTreeViewTasks();
void InitDataGridViewProjectTasks();
void FillDataGridViewProjectTasks(BindingSource tasks);
}
}

View File

@ -1,7 +1,14 @@
namespace trakker.Models
{
public class Task
{
public enum RecursiveRoot
{
TASK_ID = 0,
PARENT_TASK_ID = 1
}
public Task()
{
TaskId = Guid.NewGuid().ToString();

View File

@ -22,7 +22,10 @@ namespace trakker.Services
LoadClients();
_view.InitDataGridViewProjectTasks();
_view.InitDataGridViewProjects();
_view.InitTreeViewTasks();
LoadProjects();
}
@ -63,5 +66,13 @@ namespace trakker.Services
var dbo = new TaskData(_connectionString);
_view.FillTreeViewTasks(dbo.GetFS());
}
internal void LoadTasksRecursive(string id, trakker.Models.Task.RecursiveRoot root)
{
var dbo = new TaskData(_connectionString);
BindingList<trakker.Models.Task> tasks = dbo.GetRecursive(id, root);
_view.FillDataGridViewProjectTasks(new BindingSource { DataSource = tasks });
}
}
}