Continued development
This commit is contained in:
parent
94e532986c
commit
9119eca1b4
117
Data/TaskData.cs
117
Data/TaskData.cs
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
// 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)
|
||||
//{
|
||||
// Project project = dialog.Project;
|
||||
// ProjectData projectData = new ProjectData(connectionString);
|
||||
// try
|
||||
// {
|
||||
// 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 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();
|
||||
}
|
||||
|
||||
//private void button1_Click(object sender, EventArgs e)
|
||||
//{
|
||||
// var dialog = new ClientForm(new Client());
|
||||
// if (dialog.ShowDialog(this) == DialogResult.OK)
|
||||
// {
|
||||
// Client client = dialog.Client;
|
||||
// ClientData clientData = new ClientData(connectionString);
|
||||
// try
|
||||
// {
|
||||
// clientData.Upsert(client);
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// MessageBox.Show($"Error saving client: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue