Continued development
This commit is contained in:
parent
3491593c07
commit
94e532986c
|
|
@ -0,0 +1,385 @@
|
||||||
|
using System.ComponentModel;
|
||||||
|
using trakker.Models;
|
||||||
|
|
||||||
|
namespace trakker.Data
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides data access methods for the <see cref="Models.Task"/> entity.
|
||||||
|
/// This class encapsulates database operations such as upsert, delete and ad-hoc
|
||||||
|
/// SQL execution for tasks. It inherits from <see cref="DataAccess"/> which
|
||||||
|
/// provides connection management.
|
||||||
|
/// </summary>
|
||||||
|
internal class TaskData(string connectionString) : DataAccess(connectionString)
|
||||||
|
{
|
||||||
|
|
||||||
|
public trakker.Models.Task Get(string? taskId = null)
|
||||||
|
{
|
||||||
|
var results = new List<trakker.Models.Task>();
|
||||||
|
|
||||||
|
string whereClause = "1 = 1";
|
||||||
|
if (taskId != null)
|
||||||
|
{
|
||||||
|
whereClause = "task_id = $task_id";
|
||||||
|
}
|
||||||
|
|
||||||
|
string sql = $@"
|
||||||
|
SELECT
|
||||||
|
task_id,
|
||||||
|
project_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
status,
|
||||||
|
priority,
|
||||||
|
due_date,
|
||||||
|
estimated_hours,
|
||||||
|
actual_hours,
|
||||||
|
hourly_rate,
|
||||||
|
parent_task_id,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
FROM tasks
|
||||||
|
WHERE
|
||||||
|
{whereClause}
|
||||||
|
ORDER BY
|
||||||
|
priority DESC,
|
||||||
|
due_date ASC,
|
||||||
|
created_at DESC
|
||||||
|
;
|
||||||
|
";
|
||||||
|
|
||||||
|
|
||||||
|
using var conn = OpenConnection();
|
||||||
|
using var cmd = conn.CreateCommand();
|
||||||
|
cmd.CommandText = sql;
|
||||||
|
|
||||||
|
if (taskId != null)
|
||||||
|
{
|
||||||
|
cmd.Parameters.AddWithValue("$task_id", taskId);
|
||||||
|
}
|
||||||
|
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.FirstOrDefault() ?? new trakker.Models.Task();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inserts a new task record or updates an existing one (upsert) using
|
||||||
|
/// the provided <paramref name="task"/> model. This method executes
|
||||||
|
/// a single SQL statement inside a transaction and will commit on
|
||||||
|
/// success or roll back on failure.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="task">The <see cref="Task"/> model to insert or update. Must not be null.</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// The SQL statement uses an ON CONFLICT clause to perform the update when a
|
||||||
|
/// matching <c>task_id</c> already exists. Parameter names correspond to the
|
||||||
|
/// task model property names.
|
||||||
|
/// </remarks>
|
||||||
|
public void Upsert(trakker.Models.Task task)
|
||||||
|
{
|
||||||
|
const string sql = @"
|
||||||
|
INSERT INTO tasks (
|
||||||
|
task_id,
|
||||||
|
project_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
status,
|
||||||
|
priority,
|
||||||
|
due_date,
|
||||||
|
estimated_hours,
|
||||||
|
actual_hours,
|
||||||
|
hourly_rate,
|
||||||
|
parent_task_id
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
$task_id,
|
||||||
|
$project_id,
|
||||||
|
$title,
|
||||||
|
$description,
|
||||||
|
$status,
|
||||||
|
$priority,
|
||||||
|
$due_date,
|
||||||
|
$estimated_hours,
|
||||||
|
$actual_hours,
|
||||||
|
$hourly_rate,
|
||||||
|
$parent_task_id
|
||||||
|
)
|
||||||
|
ON CONFLICT(task_id) DO UPDATE SET
|
||||||
|
project_id = excluded.project_id,
|
||||||
|
title = excluded.title,
|
||||||
|
description = excluded.description,
|
||||||
|
status = excluded.status,
|
||||||
|
priority = excluded.priority,
|
||||||
|
due_date = excluded.due_date,
|
||||||
|
estimated_hours = excluded.estimated_hours,
|
||||||
|
actual_hours = excluded.actual_hours,
|
||||||
|
hourly_rate = excluded.hourly_rate,
|
||||||
|
parent_task_id = excluded.parent_task_id,
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
|
;
|
||||||
|
";
|
||||||
|
|
||||||
|
using var conn = OpenConnection();
|
||||||
|
using var tx = conn.BeginTransaction();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var cmd = conn.CreateCommand())
|
||||||
|
{
|
||||||
|
cmd.Transaction = tx;
|
||||||
|
cmd.CommandText = sql;
|
||||||
|
cmd.Parameters.AddWithValue("$task_id", task.TaskId);
|
||||||
|
cmd.Parameters.AddWithValue("$project_id", task.ProjectId);
|
||||||
|
cmd.Parameters.AddWithValue("$title", task.Title);
|
||||||
|
cmd.Parameters.AddWithValue("$description", task.Description);
|
||||||
|
cmd.Parameters.AddWithValue("$status", task.Status);
|
||||||
|
cmd.Parameters.AddWithValue("$priority", task.Priority);
|
||||||
|
cmd.Parameters.AddWithValue("$due_date", task.DueDate);
|
||||||
|
cmd.Parameters.AddWithValue("$estimated_hours", task.EstimatedHours);
|
||||||
|
cmd.Parameters.AddWithValue("$actual_hours", task.ActualHours);
|
||||||
|
cmd.Parameters.AddWithValue("$hourly_rate", task.HourlyRate);
|
||||||
|
cmd.Parameters.AddWithValue("$parent_task_id", task.ParentTaskId);
|
||||||
|
cmd.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.Commit();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
tx.Rollback();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Deletes the task with the specified <paramref name="taskId"/> from the
|
||||||
|
/// database.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="taskId">The identifier of the task to delete.</param>
|
||||||
|
/// <returns>An optional integer representing any scalar value returned by the
|
||||||
|
/// command executed after deletion (if applicable). May be null.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// The method executes within a transaction. The current implementation attempts
|
||||||
|
/// to read a scalar value after the delete; that value depends on surrounding
|
||||||
|
/// database triggers or commands and may be null.
|
||||||
|
/// </remarks>
|
||||||
|
public int? Delete(string taskId)
|
||||||
|
{
|
||||||
|
const string sql = @"
|
||||||
|
DELETE FROM
|
||||||
|
tasks
|
||||||
|
WHERE
|
||||||
|
task_id = $task_id
|
||||||
|
;
|
||||||
|
";
|
||||||
|
|
||||||
|
using var conn = OpenConnection();
|
||||||
|
using var tx = conn.BeginTransaction();
|
||||||
|
|
||||||
|
int? result = 0;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var cmd = conn.CreateCommand())
|
||||||
|
{
|
||||||
|
cmd.Transaction = tx;
|
||||||
|
cmd.CommandText = sql;
|
||||||
|
cmd.Parameters.AddWithValue("$task_id", taskId);
|
||||||
|
cmd.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
using var idCmd = conn.CreateCommand();
|
||||||
|
idCmd.Transaction = tx;
|
||||||
|
result = (int?)idCmd.ExecuteScalar() ;
|
||||||
|
|
||||||
|
tx.Commit();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
tx.Rollback();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
public List<TaskFS> GetFS()
|
||||||
|
{
|
||||||
|
var results = new List<TaskFS>();
|
||||||
|
|
||||||
|
string sql = $@"
|
||||||
|
SELECT
|
||||||
|
a.project_id AS guid,
|
||||||
|
a.name AS node,
|
||||||
|
'/' AS parent,
|
||||||
|
a.hourly_rate,
|
||||||
|
a.project_id
|
||||||
|
FROM
|
||||||
|
projects a
|
||||||
|
|
||||||
|
UNION
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
b.task_id AS guid,
|
||||||
|
b.title AS node,
|
||||||
|
b.parent_task_id AS parent,
|
||||||
|
b.hourly_rate,
|
||||||
|
b.project_id
|
||||||
|
FROM
|
||||||
|
tasks b
|
||||||
|
;
|
||||||
|
";
|
||||||
|
|
||||||
|
|
||||||
|
using var conn = OpenConnection();
|
||||||
|
using var cmd = conn.CreateCommand();
|
||||||
|
cmd.CommandText = sql;
|
||||||
|
|
||||||
|
using var reader = cmd.ExecuteReader();
|
||||||
|
|
||||||
|
var _var1 = reader.GetOrdinal("guid");
|
||||||
|
var _var2 = reader.GetOrdinal("node");
|
||||||
|
var _var3 = reader.GetOrdinal("parent");
|
||||||
|
var _var4 = reader.GetOrdinal("hourly_rate");
|
||||||
|
var _var5 = reader.GetOrdinal("project_id");
|
||||||
|
|
||||||
|
while (reader.Read())
|
||||||
|
{
|
||||||
|
results.Add(new TaskFS
|
||||||
|
{
|
||||||
|
GUID = reader.GetString(_var1),
|
||||||
|
Node = reader.GetString(_var2),
|
||||||
|
Parent = reader.GetString(_var3),
|
||||||
|
HourlyRate = reader.IsDBNull(_var4) ? (double?)null : reader.GetDouble(_var4),
|
||||||
|
ProjectId = reader.GetString(_var5)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
public void SaveComment(TaskComment taskComment)
|
||||||
|
{
|
||||||
|
const string sql = @"
|
||||||
|
INSERT INTO task_comments (
|
||||||
|
task_comment_id,
|
||||||
|
task_id,
|
||||||
|
comment
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
$task_comment_id,
|
||||||
|
$task_id,
|
||||||
|
$comment
|
||||||
|
)
|
||||||
|
ON CONFLICT(task_comment_id) DO UPDATE SET
|
||||||
|
task_id = excluded.task_id,
|
||||||
|
comment = excluded.comment
|
||||||
|
;
|
||||||
|
";
|
||||||
|
|
||||||
|
using var conn = OpenConnection();
|
||||||
|
using var tx = conn.BeginTransaction();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var cmd = conn.CreateCommand())
|
||||||
|
{
|
||||||
|
cmd.Transaction = tx;
|
||||||
|
cmd.CommandText = sql;
|
||||||
|
cmd.Parameters.AddWithValue("$task_comment_id", taskComment.TaskCommentId);
|
||||||
|
cmd.Parameters.AddWithValue("$task_id", taskComment.TaskId);
|
||||||
|
cmd.Parameters.AddWithValue("$comment", taskComment.Comment);
|
||||||
|
cmd.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.Commit();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
tx.Rollback();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
public List<TaskComment> GetComments(string taskId)
|
||||||
|
{
|
||||||
|
var results = new List<TaskComment>();
|
||||||
|
|
||||||
|
string whereClause = "1 = 1";
|
||||||
|
if (taskId != null)
|
||||||
|
{
|
||||||
|
whereClause = "task_id = $task_id";
|
||||||
|
}
|
||||||
|
|
||||||
|
string sql = $@"
|
||||||
|
SELECT
|
||||||
|
task_comment_id,
|
||||||
|
task_id,
|
||||||
|
comment,
|
||||||
|
created_at
|
||||||
|
FROM
|
||||||
|
task_comments
|
||||||
|
WHERE
|
||||||
|
{whereClause}
|
||||||
|
ORDER BY
|
||||||
|
created_at DESC
|
||||||
|
;
|
||||||
|
";
|
||||||
|
|
||||||
|
|
||||||
|
using var conn = OpenConnection();
|
||||||
|
using var cmd = conn.CreateCommand();
|
||||||
|
cmd.CommandText = sql;
|
||||||
|
|
||||||
|
using var reader = cmd.ExecuteReader();
|
||||||
|
|
||||||
|
var _var1 = reader.GetOrdinal("task_comment_id");
|
||||||
|
var _var2 = reader.GetOrdinal("task_id");
|
||||||
|
var _var3 = reader.GetOrdinal("comment");
|
||||||
|
var _var4 = reader.GetOrdinal("created_at");
|
||||||
|
while (reader.Read())
|
||||||
|
{
|
||||||
|
results.Add(new TaskComment
|
||||||
|
{
|
||||||
|
TaskCommentId = reader.GetString(_var1),
|
||||||
|
TaskId = reader.GetString(_var2),
|
||||||
|
Comment = reader.GetString(_var3),
|
||||||
|
CreatedAt = reader.IsDBNull(_var4) ? (DateTime?)null : reader.GetDateTime(_var4)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -28,6 +28,7 @@
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void InitializeComponent()
|
private void InitializeComponent()
|
||||||
{
|
{
|
||||||
|
components = new System.ComponentModel.Container();
|
||||||
MainForm_MenuStrip = new MenuStrip();
|
MainForm_MenuStrip = new MenuStrip();
|
||||||
fileToolStripMenuItem = new ToolStripMenuItem();
|
fileToolStripMenuItem = new ToolStripMenuItem();
|
||||||
MainForm_Exit_MenuItem = new ToolStripMenuItem();
|
MainForm_Exit_MenuItem = new ToolStripMenuItem();
|
||||||
|
|
@ -40,9 +41,16 @@
|
||||||
MainForm_TabPage3 = new TabPage();
|
MainForm_TabPage3 = new TabPage();
|
||||||
tableLayoutPanelProjects1 = new TableLayoutPanel();
|
tableLayoutPanelProjects1 = new TableLayoutPanel();
|
||||||
dataGridViewProjects = new DataGridView();
|
dataGridViewProjects = new DataGridView();
|
||||||
tableLayoutPanelProjects2 = new TableLayoutPanel();
|
MainForm_TabPage4 = new TabPage();
|
||||||
groupBoxProjectTasks = new GroupBox();
|
tableLayoutPanelTasks1 = new TableLayoutPanel();
|
||||||
dataGridViewProjectTasks = new DataGridView();
|
splitContainerTasks1 = new SplitContainer();
|
||||||
|
treeViewTasks1 = new TreeView();
|
||||||
|
contextMenuStripTreeviewTasks = new ContextMenuStrip(components);
|
||||||
|
addTaskSubtaskToolStripMenuItem = new ToolStripMenuItem();
|
||||||
|
editThisTaskSubtaskToolStripMenuItem = new ToolStripMenuItem();
|
||||||
|
deleteThisTaskSubtaskToolStripMenuItem = new ToolStripMenuItem();
|
||||||
|
toolStripSeparator1 = new ToolStripSeparator();
|
||||||
|
addACommentToolStripMenuItem = new ToolStripMenuItem();
|
||||||
MainForm_MenuStrip.SuspendLayout();
|
MainForm_MenuStrip.SuspendLayout();
|
||||||
tabControlMainForm.SuspendLayout();
|
tabControlMainForm.SuspendLayout();
|
||||||
MainForm_TabPage2.SuspendLayout();
|
MainForm_TabPage2.SuspendLayout();
|
||||||
|
|
@ -51,9 +59,12 @@
|
||||||
MainForm_TabPage3.SuspendLayout();
|
MainForm_TabPage3.SuspendLayout();
|
||||||
tableLayoutPanelProjects1.SuspendLayout();
|
tableLayoutPanelProjects1.SuspendLayout();
|
||||||
((System.ComponentModel.ISupportInitialize)dataGridViewProjects).BeginInit();
|
((System.ComponentModel.ISupportInitialize)dataGridViewProjects).BeginInit();
|
||||||
tableLayoutPanelProjects2.SuspendLayout();
|
MainForm_TabPage4.SuspendLayout();
|
||||||
groupBoxProjectTasks.SuspendLayout();
|
tableLayoutPanelTasks1.SuspendLayout();
|
||||||
((System.ComponentModel.ISupportInitialize)dataGridViewProjectTasks).BeginInit();
|
((System.ComponentModel.ISupportInitialize)splitContainerTasks1).BeginInit();
|
||||||
|
splitContainerTasks1.Panel1.SuspendLayout();
|
||||||
|
splitContainerTasks1.SuspendLayout();
|
||||||
|
contextMenuStripTreeviewTasks.SuspendLayout();
|
||||||
SuspendLayout();
|
SuspendLayout();
|
||||||
//
|
//
|
||||||
// MainForm_MenuStrip
|
// MainForm_MenuStrip
|
||||||
|
|
@ -94,6 +105,7 @@
|
||||||
tabControlMainForm.Controls.Add(MainForm_TabPage1);
|
tabControlMainForm.Controls.Add(MainForm_TabPage1);
|
||||||
tabControlMainForm.Controls.Add(MainForm_TabPage2);
|
tabControlMainForm.Controls.Add(MainForm_TabPage2);
|
||||||
tabControlMainForm.Controls.Add(MainForm_TabPage3);
|
tabControlMainForm.Controls.Add(MainForm_TabPage3);
|
||||||
|
tabControlMainForm.Controls.Add(MainForm_TabPage4);
|
||||||
tabControlMainForm.Dock = DockStyle.Fill;
|
tabControlMainForm.Dock = DockStyle.Fill;
|
||||||
tabControlMainForm.Location = new Point(0, 40);
|
tabControlMainForm.Location = new Point(0, 40);
|
||||||
tabControlMainForm.Name = "tabControlMainForm";
|
tabControlMainForm.Name = "tabControlMainForm";
|
||||||
|
|
@ -165,14 +177,13 @@
|
||||||
tableLayoutPanelProjects1.ColumnCount = 1;
|
tableLayoutPanelProjects1.ColumnCount = 1;
|
||||||
tableLayoutPanelProjects1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
|
tableLayoutPanelProjects1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
|
||||||
tableLayoutPanelProjects1.Controls.Add(dataGridViewProjects, 0, 1);
|
tableLayoutPanelProjects1.Controls.Add(dataGridViewProjects, 0, 1);
|
||||||
tableLayoutPanelProjects1.Controls.Add(tableLayoutPanelProjects2, 0, 2);
|
|
||||||
tableLayoutPanelProjects1.Dock = DockStyle.Fill;
|
tableLayoutPanelProjects1.Dock = DockStyle.Fill;
|
||||||
tableLayoutPanelProjects1.Location = new Point(0, 0);
|
tableLayoutPanelProjects1.Location = new Point(0, 0);
|
||||||
tableLayoutPanelProjects1.Name = "tableLayoutPanelProjects1";
|
tableLayoutPanelProjects1.Name = "tableLayoutPanelProjects1";
|
||||||
tableLayoutPanelProjects1.RowCount = 3;
|
tableLayoutPanelProjects1.RowCount = 3;
|
||||||
tableLayoutPanelProjects1.RowStyles.Add(new RowStyle(SizeType.Absolute, 1F));
|
tableLayoutPanelProjects1.RowStyles.Add(new RowStyle(SizeType.Absolute, 1F));
|
||||||
tableLayoutPanelProjects1.RowStyles.Add(new RowStyle(SizeType.Percent, 50F));
|
tableLayoutPanelProjects1.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
|
||||||
tableLayoutPanelProjects1.RowStyles.Add(new RowStyle(SizeType.Percent, 50F));
|
tableLayoutPanelProjects1.RowStyles.Add(new RowStyle(SizeType.Percent, 0F));
|
||||||
tableLayoutPanelProjects1.Size = new Size(1862, 966);
|
tableLayoutPanelProjects1.Size = new Size(1862, 966);
|
||||||
tableLayoutPanelProjects1.TabIndex = 1;
|
tableLayoutPanelProjects1.TabIndex = 1;
|
||||||
//
|
//
|
||||||
|
|
@ -186,47 +197,95 @@
|
||||||
dataGridViewProjects.Name = "dataGridViewProjects";
|
dataGridViewProjects.Name = "dataGridViewProjects";
|
||||||
dataGridViewProjects.ReadOnly = true;
|
dataGridViewProjects.ReadOnly = true;
|
||||||
dataGridViewProjects.RowHeadersWidth = 82;
|
dataGridViewProjects.RowHeadersWidth = 82;
|
||||||
dataGridViewProjects.Size = new Size(1856, 476);
|
dataGridViewProjects.Size = new Size(1856, 959);
|
||||||
dataGridViewProjects.TabIndex = 0;
|
dataGridViewProjects.TabIndex = 0;
|
||||||
//
|
//
|
||||||
// tableLayoutPanelProjects2
|
// MainForm_TabPage4
|
||||||
//
|
//
|
||||||
tableLayoutPanelProjects2.ColumnCount = 3;
|
MainForm_TabPage4.Controls.Add(tableLayoutPanelTasks1);
|
||||||
tableLayoutPanelProjects2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 0F));
|
MainForm_TabPage4.Location = new Point(8, 46);
|
||||||
tableLayoutPanelProjects2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
|
MainForm_TabPage4.Name = "MainForm_TabPage4";
|
||||||
tableLayoutPanelProjects2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 0F));
|
MainForm_TabPage4.Size = new Size(1862, 966);
|
||||||
tableLayoutPanelProjects2.Controls.Add(groupBoxProjectTasks, 1, 0);
|
MainForm_TabPage4.TabIndex = 3;
|
||||||
tableLayoutPanelProjects2.Dock = DockStyle.Fill;
|
MainForm_TabPage4.Text = "Tab 4";
|
||||||
tableLayoutPanelProjects2.Location = new Point(3, 486);
|
MainForm_TabPage4.UseVisualStyleBackColor = true;
|
||||||
tableLayoutPanelProjects2.Name = "tableLayoutPanelProjects2";
|
|
||||||
tableLayoutPanelProjects2.RowCount = 1;
|
|
||||||
tableLayoutPanelProjects2.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
|
|
||||||
tableLayoutPanelProjects2.Size = new Size(1856, 477);
|
|
||||||
tableLayoutPanelProjects2.TabIndex = 1;
|
|
||||||
//
|
//
|
||||||
// groupBoxProjectTasks
|
// tableLayoutPanelTasks1
|
||||||
//
|
//
|
||||||
groupBoxProjectTasks.Controls.Add(dataGridViewProjectTasks);
|
tableLayoutPanelTasks1.ColumnCount = 1;
|
||||||
groupBoxProjectTasks.Dock = DockStyle.Fill;
|
tableLayoutPanelTasks1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
|
||||||
groupBoxProjectTasks.Location = new Point(3, 3);
|
tableLayoutPanelTasks1.Controls.Add(splitContainerTasks1, 0, 1);
|
||||||
groupBoxProjectTasks.Name = "groupBoxProjectTasks";
|
tableLayoutPanelTasks1.Dock = DockStyle.Fill;
|
||||||
groupBoxProjectTasks.Size = new Size(1850, 471);
|
tableLayoutPanelTasks1.Location = new Point(0, 0);
|
||||||
groupBoxProjectTasks.TabIndex = 0;
|
tableLayoutPanelTasks1.Name = "tableLayoutPanelTasks1";
|
||||||
groupBoxProjectTasks.TabStop = false;
|
tableLayoutPanelTasks1.RowCount = 3;
|
||||||
groupBoxProjectTasks.Text = "Tasks";
|
tableLayoutPanelTasks1.RowStyles.Add(new RowStyle(SizeType.Percent, 0F));
|
||||||
|
tableLayoutPanelTasks1.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
|
||||||
|
tableLayoutPanelTasks1.RowStyles.Add(new RowStyle(SizeType.Percent, 0F));
|
||||||
|
tableLayoutPanelTasks1.Size = new Size(1862, 966);
|
||||||
|
tableLayoutPanelTasks1.TabIndex = 0;
|
||||||
//
|
//
|
||||||
// dataGridViewProjectTasks
|
// splitContainerTasks1
|
||||||
//
|
//
|
||||||
dataGridViewProjectTasks.AllowUserToAddRows = false;
|
splitContainerTasks1.Dock = DockStyle.Fill;
|
||||||
dataGridViewProjectTasks.AllowUserToDeleteRows = false;
|
splitContainerTasks1.Location = new Point(3, 3);
|
||||||
dataGridViewProjectTasks.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
splitContainerTasks1.Name = "splitContainerTasks1";
|
||||||
dataGridViewProjectTasks.Dock = DockStyle.Fill;
|
//
|
||||||
dataGridViewProjectTasks.Location = new Point(3, 35);
|
// splitContainerTasks1.Panel1
|
||||||
dataGridViewProjectTasks.Name = "dataGridViewProjectTasks";
|
//
|
||||||
dataGridViewProjectTasks.ReadOnly = true;
|
splitContainerTasks1.Panel1.Controls.Add(treeViewTasks1);
|
||||||
dataGridViewProjectTasks.RowHeadersWidth = 82;
|
splitContainerTasks1.Size = new Size(1856, 960);
|
||||||
dataGridViewProjectTasks.Size = new Size(1844, 433);
|
splitContainerTasks1.SplitterDistance = 618;
|
||||||
dataGridViewProjectTasks.TabIndex = 0;
|
splitContainerTasks1.TabIndex = 0;
|
||||||
|
//
|
||||||
|
// treeViewTasks1
|
||||||
|
//
|
||||||
|
treeViewTasks1.ContextMenuStrip = contextMenuStripTreeviewTasks;
|
||||||
|
treeViewTasks1.Dock = DockStyle.Fill;
|
||||||
|
treeViewTasks1.Location = new Point(0, 0);
|
||||||
|
treeViewTasks1.Name = "treeViewTasks1";
|
||||||
|
treeViewTasks1.Size = new Size(618, 960);
|
||||||
|
treeViewTasks1.TabIndex = 0;
|
||||||
|
//
|
||||||
|
// contextMenuStripTreeviewTasks
|
||||||
|
//
|
||||||
|
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);
|
||||||
|
//
|
||||||
|
// addTaskSubtaskToolStripMenuItem
|
||||||
|
//
|
||||||
|
addTaskSubtaskToolStripMenuItem.Name = "addTaskSubtaskToolStripMenuItem";
|
||||||
|
addTaskSubtaskToolStripMenuItem.Size = new Size(370, 38);
|
||||||
|
addTaskSubtaskToolStripMenuItem.Text = "Add Task / Sub-task";
|
||||||
|
addTaskSubtaskToolStripMenuItem.Click += addTaskSubtaskToolStripMenuItem_Click;
|
||||||
|
//
|
||||||
|
// editThisTaskSubtaskToolStripMenuItem
|
||||||
|
//
|
||||||
|
editThisTaskSubtaskToolStripMenuItem.Name = "editThisTaskSubtaskToolStripMenuItem";
|
||||||
|
editThisTaskSubtaskToolStripMenuItem.Size = new Size(370, 38);
|
||||||
|
editThisTaskSubtaskToolStripMenuItem.Text = "Edit this Task / Sub-task";
|
||||||
|
editThisTaskSubtaskToolStripMenuItem.Click += editThisTaskSubtaskToolStripMenuItem_Click;
|
||||||
|
//
|
||||||
|
// deleteThisTaskSubtaskToolStripMenuItem
|
||||||
|
//
|
||||||
|
deleteThisTaskSubtaskToolStripMenuItem.Name = "deleteThisTaskSubtaskToolStripMenuItem";
|
||||||
|
deleteThisTaskSubtaskToolStripMenuItem.Size = new Size(370, 38);
|
||||||
|
deleteThisTaskSubtaskToolStripMenuItem.Text = "Delete this Task / Sub-task";
|
||||||
|
deleteThisTaskSubtaskToolStripMenuItem.Click += deleteThisTaskSubtaskToolStripMenuItem_Click;
|
||||||
|
//
|
||||||
|
// toolStripSeparator1
|
||||||
|
//
|
||||||
|
toolStripSeparator1.Name = "toolStripSeparator1";
|
||||||
|
toolStripSeparator1.Size = new Size(367, 6);
|
||||||
|
//
|
||||||
|
// addACommentToolStripMenuItem
|
||||||
|
//
|
||||||
|
addACommentToolStripMenuItem.Name = "addACommentToolStripMenuItem";
|
||||||
|
addACommentToolStripMenuItem.Size = new Size(370, 38);
|
||||||
|
addACommentToolStripMenuItem.Text = "Add a comment";
|
||||||
|
addACommentToolStripMenuItem.Click += addACommentToolStripMenuItem_Click;
|
||||||
//
|
//
|
||||||
// MainForm
|
// MainForm
|
||||||
//
|
//
|
||||||
|
|
@ -248,9 +307,12 @@
|
||||||
MainForm_TabPage3.ResumeLayout(false);
|
MainForm_TabPage3.ResumeLayout(false);
|
||||||
tableLayoutPanelProjects1.ResumeLayout(false);
|
tableLayoutPanelProjects1.ResumeLayout(false);
|
||||||
((System.ComponentModel.ISupportInitialize)dataGridViewProjects).EndInit();
|
((System.ComponentModel.ISupportInitialize)dataGridViewProjects).EndInit();
|
||||||
tableLayoutPanelProjects2.ResumeLayout(false);
|
MainForm_TabPage4.ResumeLayout(false);
|
||||||
groupBoxProjectTasks.ResumeLayout(false);
|
tableLayoutPanelTasks1.ResumeLayout(false);
|
||||||
((System.ComponentModel.ISupportInitialize)dataGridViewProjectTasks).EndInit();
|
splitContainerTasks1.Panel1.ResumeLayout(false);
|
||||||
|
((System.ComponentModel.ISupportInitialize)splitContainerTasks1).EndInit();
|
||||||
|
splitContainerTasks1.ResumeLayout(false);
|
||||||
|
contextMenuStripTreeviewTasks.ResumeLayout(false);
|
||||||
ResumeLayout(false);
|
ResumeLayout(false);
|
||||||
PerformLayout();
|
PerformLayout();
|
||||||
}
|
}
|
||||||
|
|
@ -269,8 +331,15 @@
|
||||||
private TabPage MainForm_TabPage3;
|
private TabPage MainForm_TabPage3;
|
||||||
private TableLayoutPanel tableLayoutPanelProjects1;
|
private TableLayoutPanel tableLayoutPanelProjects1;
|
||||||
private DataGridView dataGridViewProjects;
|
private DataGridView dataGridViewProjects;
|
||||||
private TableLayoutPanel tableLayoutPanelProjects2;
|
private TabPage MainForm_TabPage4;
|
||||||
private GroupBox groupBoxProjectTasks;
|
private TableLayoutPanel tableLayoutPanelTasks1;
|
||||||
private DataGridView dataGridViewProjectTasks;
|
private SplitContainer splitContainerTasks1;
|
||||||
|
private TreeView treeViewTasks1;
|
||||||
|
private ContextMenuStrip contextMenuStripTreeviewTasks;
|
||||||
|
private ToolStripMenuItem addTaskSubtaskToolStripMenuItem;
|
||||||
|
private ToolStripMenuItem editThisTaskSubtaskToolStripMenuItem;
|
||||||
|
private ToolStripMenuItem deleteThisTaskSubtaskToolStripMenuItem;
|
||||||
|
private ToolStripSeparator toolStripSeparator1;
|
||||||
|
private ToolStripMenuItem addACommentToolStripMenuItem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ namespace trakker
|
||||||
//private readonly string _dbversion = "[N.N.N]";
|
//private readonly string _dbversion = "[N.N.N]";
|
||||||
private string connectionString = string.Empty;
|
private string connectionString = string.Empty;
|
||||||
readonly MainCtrl _ctrl;
|
readonly MainCtrl _ctrl;
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="MainForm"/> class.
|
/// Initializes a new instance of the <see cref="MainForm"/> class.
|
||||||
|
|
@ -37,7 +37,8 @@ namespace trakker
|
||||||
tabControlMainForm.TabPages[0].Text = " Home ";
|
tabControlMainForm.TabPages[0].Text = " Home ";
|
||||||
tabControlMainForm.TabPages[1].Text = " Clients ";
|
tabControlMainForm.TabPages[1].Text = " Clients ";
|
||||||
tabControlMainForm.TabPages[2].Text = " Projects ";
|
tabControlMainForm.TabPages[2].Text = " Projects ";
|
||||||
|
tabControlMainForm.TabPages[3].Text = " Tasks ";
|
||||||
|
|
||||||
_ctrl = new Services.MainCtrl(this, connectionString);
|
_ctrl = new Services.MainCtrl(this, connectionString);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -131,7 +132,7 @@ namespace trakker
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
dataGridViewClients.SelectionChanged += (s, e) =>
|
dataGridViewClients.SelectionChanged += (s, e) =>
|
||||||
{
|
{
|
||||||
|
|
@ -274,7 +275,7 @@ namespace trakker
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
dataGridViewClients.SelectionChanged += (s, e) =>
|
dataGridViewClients.SelectionChanged += (s, e) =>
|
||||||
{
|
{
|
||||||
|
|
@ -290,6 +291,170 @@ namespace trakker
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
public void FillTreeViewTasks(List<TaskFS> items)
|
||||||
|
{
|
||||||
|
if (items == null) return;
|
||||||
|
|
||||||
|
treeViewTasks1.BeginUpdate(); // Improves performance for large trees
|
||||||
|
treeViewTasks1.Nodes.Clear();
|
||||||
|
|
||||||
|
// Root node
|
||||||
|
TaskFS root = new();
|
||||||
|
root.GUID = "/";
|
||||||
|
root.Node = "/Projects";
|
||||||
|
root.Parent = string.Empty;
|
||||||
|
TreeNode rootNode = new TreeNode(root.Node) { Tag = root };
|
||||||
|
treeViewTasks1.Nodes.Add(rootNode);
|
||||||
|
|
||||||
|
// Normalize keys so null/empty parents map to root key "/"
|
||||||
|
static string NormalizeKey(string? k) => string.IsNullOrWhiteSpace(k) ? "/" : k!.Trim();
|
||||||
|
|
||||||
|
// Build lookup: parentKey -> list of children
|
||||||
|
var lookup = new Dictionary<string, List<TaskFS>>();
|
||||||
|
foreach (var it in items)
|
||||||
|
{
|
||||||
|
var key = NormalizeKey(it.Parent);
|
||||||
|
if (!lookup.TryGetValue(key, out var list))
|
||||||
|
{
|
||||||
|
list = new List<TaskFS>();
|
||||||
|
lookup[key] = list;
|
||||||
|
}
|
||||||
|
list.Add(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
var keys = lookup.Keys.ToList();
|
||||||
|
// Recursive adder: attach child nodes to a parent TreeNode.
|
||||||
|
// Uses normalized keys so null/empty parents become "/".
|
||||||
|
void AddChildren(TreeNode parentNode, string parentKey)
|
||||||
|
{
|
||||||
|
if (!lookup.TryGetValue(parentKey, out var children)) return;
|
||||||
|
// Optional: sort children by Node text for deterministic order
|
||||||
|
foreach (var child in children.OrderBy(c => c.Node))
|
||||||
|
{
|
||||||
|
var text = string.IsNullOrWhiteSpace(child.Node) ? "(unnamed)" : child.Node!.Trim();
|
||||||
|
var tn = new TreeNode(text) { Tag = child };
|
||||||
|
//tn.Expand();
|
||||||
|
parentNode.Nodes.Add(tn);
|
||||||
|
|
||||||
|
// Recurse using this child's node text as the parent key
|
||||||
|
AddChildren(tn, NormalizeKey(child.GUID));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// // Start recursion from root key
|
||||||
|
AddChildren(rootNode, "/");
|
||||||
|
|
||||||
|
//rootNode.ExpandAll();
|
||||||
|
rootNode.Expand(); // Expand first level for better UX
|
||||||
|
treeViewTasks1.EndUpdate(); // End the update
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addTaskSubtaskToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (treeViewTasks1.SelectedNode == null) return;
|
||||||
|
TaskFS? selectedNode = (TaskFS?)treeViewTasks1.SelectedNode.Tag ?? new TaskFS();
|
||||||
|
if (selectedNode?.Parent == "")
|
||||||
|
{
|
||||||
|
MessageBox.Show("Cannot add tasks to root node", "Add Task", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
string taskId = Guid.NewGuid().ToString();
|
||||||
|
trakker.Models.Task task = new()
|
||||||
|
{
|
||||||
|
TaskId = taskId,
|
||||||
|
Title = "New Task",
|
||||||
|
Description = string.Empty,
|
||||||
|
ParentTaskId = selectedNode?.GUID == "/" ? null : selectedNode?.GUID,
|
||||||
|
HourlyRate = selectedNode?.HourlyRate,
|
||||||
|
ProjectId = selectedNode?.ProjectId, // Root node has no project
|
||||||
|
};
|
||||||
|
TaskForm dialog = new TaskForm(task, _ctrl.GetLOV("task.status"), _ctrl.GetLOV("task.priority"));
|
||||||
|
DialogResult result = dialog.ShowDialog(this);
|
||||||
|
if (result == DialogResult.OK)
|
||||||
|
{
|
||||||
|
task = dialog.Task;
|
||||||
|
//MessageBox.Show(task.ToString(), "Task Details", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||||
|
TaskData taskData = new TaskData(connectionString);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
taskData.Upsert(task);
|
||||||
|
_ctrl.LoadTasks(); // Reload tasks to update the DataGridView with any changes
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show($"Error saving task: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void editThisTaskSubtaskToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (treeViewTasks1.SelectedNode == null) return;
|
||||||
|
TaskFS? selectedTask = treeViewTasks1.SelectedNode.Tag as TaskFS;
|
||||||
|
//MessageBox.Show(selectedTask != null ? selectedTask.ToString() : "No task selected", "Edit Task", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||||
|
if (selectedTask?.Parent == "/" || selectedTask?.GUID == "/")
|
||||||
|
{
|
||||||
|
MessageBox.Show("Cannot edit root node", "Edit Task", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TaskData taskData = new TaskData(connectionString);
|
||||||
|
trakker.Models.Task task = taskData.Get(selectedTask?.GUID);
|
||||||
|
TaskForm dialog = new TaskForm(task, _ctrl.GetLOV("task.status"), _ctrl.GetLOV("task.priority"));
|
||||||
|
DialogResult result = dialog.ShowDialog(this);
|
||||||
|
if (result == DialogResult.OK)
|
||||||
|
{
|
||||||
|
task = dialog.Task;
|
||||||
|
//MessageBox.Show(task.ToString(), "Task Details", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
taskData.Upsert(task);
|
||||||
|
_ctrl.LoadTasks(); // Reload tasks to update the DataGridView with any changes
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show($"Error saving task: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteThisTaskSubtaskToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (treeViewTasks1.SelectedNode == null) return;
|
||||||
|
TaskFS? selectedTask = treeViewTasks1.SelectedNode.Tag as TaskFS;
|
||||||
|
//MessageBox.Show(selectedTask != null ? selectedTask.ToString() : "No task selected", "Edit Task", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||||
|
if (selectedTask?.Parent == "/" || selectedTask?.GUID == "/")
|
||||||
|
{
|
||||||
|
MessageBox.Show("Cannot delete root node", "Delete Task", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (treeViewTasks1.SelectedNode.Nodes.Count > 0)
|
||||||
|
{
|
||||||
|
MessageBox.Show("Cannot delete a task with subtasks", "Delete Task", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TaskData taskData = new TaskData(connectionString);
|
||||||
|
DialogResult result = MessageBox.Show("Are you sure you want to delete this task?", "Delete Task", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
|
||||||
|
if (result == DialogResult.Yes)
|
||||||
|
{
|
||||||
|
//MessageBox.Show(task.ToString(), "Task Details", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
taskData.Delete(selectedTask?.GUID ?? string.Empty);
|
||||||
|
treeViewTasks1.SelectedNode.Remove();
|
||||||
|
//_ctrl.LoadTasks(); // Reload tasks to update the DataGridView with any changes
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show($"Error deleting task: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addACommentToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//private void button1_Click(object sender, EventArgs e)
|
//private void button1_Click(object sender, EventArgs e)
|
||||||
//{
|
//{
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,9 @@
|
||||||
<metadata name="MainForm_StatusStrip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
<metadata name="MainForm_StatusStrip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||||
<value>16, 55</value>
|
<value>16, 55</value>
|
||||||
</metadata>
|
</metadata>
|
||||||
|
<metadata name="contextMenuStripTreeviewTasks.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||||
|
<value>358, 12</value>
|
||||||
|
</metadata>
|
||||||
<metadata name="$this.TrayHeight" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
<metadata name="$this.TrayHeight" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||||
<value>162</value>
|
<value>162</value>
|
||||||
</metadata>
|
</metadata>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,411 @@
|
||||||
|
namespace trakker.Forms
|
||||||
|
{
|
||||||
|
partial class TaskForm
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Required designer variable.
|
||||||
|
/// </summary>
|
||||||
|
private System.ComponentModel.IContainer components = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clean up any resources being used.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing && (components != null))
|
||||||
|
{
|
||||||
|
components.Dispose();
|
||||||
|
}
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Windows Form Designer generated code
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Required method for Designer support - do not modify
|
||||||
|
/// the contents of this method with the code editor.
|
||||||
|
/// </summary>
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
groupBoxNewTask = new GroupBox();
|
||||||
|
tableLayoutPanel1 = new TableLayoutPanel();
|
||||||
|
tableLayoutPanel3 = new TableLayoutPanel();
|
||||||
|
buttonOkay = new Button();
|
||||||
|
buttonCancel = new Button();
|
||||||
|
labelCreatedUpdatedDT = new Label();
|
||||||
|
tableLayoutPanel2 = new TableLayoutPanel();
|
||||||
|
tableLayoutPanel4 = new TableLayoutPanel();
|
||||||
|
dateTimePickerDueDate = new DateTimePicker();
|
||||||
|
comboBoxStatus = new ComboBox();
|
||||||
|
labelPriority = new Label();
|
||||||
|
comboBoxPriority = new ComboBox();
|
||||||
|
labelDueDate = new Label();
|
||||||
|
labelStatus = new Label();
|
||||||
|
richTextBoxDescription = new RichTextBox();
|
||||||
|
labelDescription = new Label();
|
||||||
|
textBoxTitle = new TextBox();
|
||||||
|
labelTitle = new Label();
|
||||||
|
labelHoursEst = new Label();
|
||||||
|
tableLayoutPanel6 = new TableLayoutPanel();
|
||||||
|
labelHoursActual = new Label();
|
||||||
|
textBoxHoursEst = new TextBox();
|
||||||
|
textBoxHoursActual = new TextBox();
|
||||||
|
labelRate = new Label();
|
||||||
|
textBoxRate = new TextBox();
|
||||||
|
groupBoxNewTask.SuspendLayout();
|
||||||
|
tableLayoutPanel1.SuspendLayout();
|
||||||
|
tableLayoutPanel3.SuspendLayout();
|
||||||
|
tableLayoutPanel2.SuspendLayout();
|
||||||
|
tableLayoutPanel4.SuspendLayout();
|
||||||
|
tableLayoutPanel6.SuspendLayout();
|
||||||
|
SuspendLayout();
|
||||||
|
//
|
||||||
|
// groupBoxNewTask
|
||||||
|
//
|
||||||
|
groupBoxNewTask.Controls.Add(tableLayoutPanel1);
|
||||||
|
groupBoxNewTask.Dock = DockStyle.Fill;
|
||||||
|
groupBoxNewTask.Location = new Point(0, 0);
|
||||||
|
groupBoxNewTask.Name = "groupBoxNewTask";
|
||||||
|
groupBoxNewTask.Size = new Size(1181, 372);
|
||||||
|
groupBoxNewTask.TabIndex = 2;
|
||||||
|
groupBoxNewTask.TabStop = false;
|
||||||
|
groupBoxNewTask.Text = "Task";
|
||||||
|
//
|
||||||
|
// tableLayoutPanel1
|
||||||
|
//
|
||||||
|
tableLayoutPanel1.ColumnCount = 1;
|
||||||
|
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
|
||||||
|
tableLayoutPanel1.Controls.Add(tableLayoutPanel3, 0, 1);
|
||||||
|
tableLayoutPanel1.Controls.Add(tableLayoutPanel2, 0, 0);
|
||||||
|
tableLayoutPanel1.Dock = DockStyle.Fill;
|
||||||
|
tableLayoutPanel1.Location = new Point(3, 35);
|
||||||
|
tableLayoutPanel1.Name = "tableLayoutPanel1";
|
||||||
|
tableLayoutPanel1.RowCount = 2;
|
||||||
|
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
|
||||||
|
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Absolute, 75F));
|
||||||
|
tableLayoutPanel1.Size = new Size(1175, 334);
|
||||||
|
tableLayoutPanel1.TabIndex = 0;
|
||||||
|
//
|
||||||
|
// tableLayoutPanel3
|
||||||
|
//
|
||||||
|
tableLayoutPanel3.ColumnCount = 3;
|
||||||
|
tableLayoutPanel3.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
|
||||||
|
tableLayoutPanel3.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 200F));
|
||||||
|
tableLayoutPanel3.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 200F));
|
||||||
|
tableLayoutPanel3.Controls.Add(buttonOkay, 1, 0);
|
||||||
|
tableLayoutPanel3.Controls.Add(buttonCancel, 2, 0);
|
||||||
|
tableLayoutPanel3.Controls.Add(labelCreatedUpdatedDT, 0, 0);
|
||||||
|
tableLayoutPanel3.Dock = DockStyle.Fill;
|
||||||
|
tableLayoutPanel3.Location = new Point(3, 262);
|
||||||
|
tableLayoutPanel3.Name = "tableLayoutPanel3";
|
||||||
|
tableLayoutPanel3.RowCount = 1;
|
||||||
|
tableLayoutPanel3.RowStyles.Add(new RowStyle(SizeType.Absolute, 50F));
|
||||||
|
tableLayoutPanel3.Size = new Size(1169, 69);
|
||||||
|
tableLayoutPanel3.TabIndex = 99;
|
||||||
|
//
|
||||||
|
// buttonOkay
|
||||||
|
//
|
||||||
|
buttonOkay.Dock = DockStyle.Fill;
|
||||||
|
buttonOkay.Location = new Point(772, 3);
|
||||||
|
buttonOkay.Margin = new Padding(3, 3, 3, 15);
|
||||||
|
buttonOkay.Name = "buttonOkay";
|
||||||
|
buttonOkay.Size = new Size(194, 51);
|
||||||
|
buttonOkay.TabIndex = 9;
|
||||||
|
buttonOkay.Text = "Okay";
|
||||||
|
buttonOkay.UseVisualStyleBackColor = true;
|
||||||
|
//
|
||||||
|
// buttonCancel
|
||||||
|
//
|
||||||
|
buttonCancel.Dock = DockStyle.Fill;
|
||||||
|
buttonCancel.Location = new Point(972, 3);
|
||||||
|
buttonCancel.Margin = new Padding(3, 3, 3, 15);
|
||||||
|
buttonCancel.Name = "buttonCancel";
|
||||||
|
buttonCancel.Size = new Size(194, 51);
|
||||||
|
buttonCancel.TabIndex = 99;
|
||||||
|
buttonCancel.TabStop = false;
|
||||||
|
buttonCancel.Text = "Cancel";
|
||||||
|
buttonCancel.UseVisualStyleBackColor = true;
|
||||||
|
//
|
||||||
|
// labelCreatedUpdatedDT
|
||||||
|
//
|
||||||
|
labelCreatedUpdatedDT.AutoSize = true;
|
||||||
|
labelCreatedUpdatedDT.Dock = DockStyle.Fill;
|
||||||
|
labelCreatedUpdatedDT.Location = new Point(3, 0);
|
||||||
|
labelCreatedUpdatedDT.Name = "labelCreatedUpdatedDT";
|
||||||
|
labelCreatedUpdatedDT.Size = new Size(763, 69);
|
||||||
|
labelCreatedUpdatedDT.TabIndex = 2;
|
||||||
|
labelCreatedUpdatedDT.Text = "Created: yyyy-mm-dd, Updated: yyyy-mm-dd";
|
||||||
|
//
|
||||||
|
// tableLayoutPanel2
|
||||||
|
//
|
||||||
|
tableLayoutPanel2.ColumnCount = 2;
|
||||||
|
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 175F));
|
||||||
|
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
|
||||||
|
tableLayoutPanel2.Controls.Add(tableLayoutPanel4, 1, 2);
|
||||||
|
tableLayoutPanel2.Controls.Add(labelStatus, 0, 2);
|
||||||
|
tableLayoutPanel2.Controls.Add(richTextBoxDescription, 1, 1);
|
||||||
|
tableLayoutPanel2.Controls.Add(labelDescription, 0, 1);
|
||||||
|
tableLayoutPanel2.Controls.Add(textBoxTitle, 1, 0);
|
||||||
|
tableLayoutPanel2.Controls.Add(labelTitle, 0, 0);
|
||||||
|
tableLayoutPanel2.Controls.Add(labelHoursEst, 0, 3);
|
||||||
|
tableLayoutPanel2.Controls.Add(tableLayoutPanel6, 1, 3);
|
||||||
|
tableLayoutPanel2.Dock = DockStyle.Fill;
|
||||||
|
tableLayoutPanel2.Location = new Point(3, 3);
|
||||||
|
tableLayoutPanel2.Name = "tableLayoutPanel2";
|
||||||
|
tableLayoutPanel2.RowCount = 4;
|
||||||
|
tableLayoutPanel2.RowStyles.Add(new RowStyle(SizeType.Absolute, 50F));
|
||||||
|
tableLayoutPanel2.RowStyles.Add(new RowStyle(SizeType.Absolute, 100F));
|
||||||
|
tableLayoutPanel2.RowStyles.Add(new RowStyle(SizeType.Absolute, 50F));
|
||||||
|
tableLayoutPanel2.RowStyles.Add(new RowStyle(SizeType.Absolute, 50F));
|
||||||
|
tableLayoutPanel2.RowStyles.Add(new RowStyle(SizeType.Absolute, 20F));
|
||||||
|
tableLayoutPanel2.Size = new Size(1169, 253);
|
||||||
|
tableLayoutPanel2.TabIndex = 1;
|
||||||
|
//
|
||||||
|
// tableLayoutPanel4
|
||||||
|
//
|
||||||
|
tableLayoutPanel4.ColumnCount = 5;
|
||||||
|
tableLayoutPanel4.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 20F));
|
||||||
|
tableLayoutPanel4.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 20F));
|
||||||
|
tableLayoutPanel4.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 20F));
|
||||||
|
tableLayoutPanel4.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 20F));
|
||||||
|
tableLayoutPanel4.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 20F));
|
||||||
|
tableLayoutPanel4.Controls.Add(dateTimePickerDueDate, 4, 0);
|
||||||
|
tableLayoutPanel4.Controls.Add(comboBoxStatus, 0, 0);
|
||||||
|
tableLayoutPanel4.Controls.Add(labelPriority, 1, 0);
|
||||||
|
tableLayoutPanel4.Controls.Add(comboBoxPriority, 2, 0);
|
||||||
|
tableLayoutPanel4.Controls.Add(labelDueDate, 3, 0);
|
||||||
|
tableLayoutPanel4.Dock = DockStyle.Fill;
|
||||||
|
tableLayoutPanel4.Location = new Point(178, 153);
|
||||||
|
tableLayoutPanel4.Name = "tableLayoutPanel4";
|
||||||
|
tableLayoutPanel4.RowCount = 1;
|
||||||
|
tableLayoutPanel4.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
|
||||||
|
tableLayoutPanel4.Size = new Size(988, 44);
|
||||||
|
tableLayoutPanel4.TabIndex = 99;
|
||||||
|
//
|
||||||
|
// dateTimePickerDueDate
|
||||||
|
//
|
||||||
|
dateTimePickerDueDate.Format = DateTimePickerFormat.Short;
|
||||||
|
dateTimePickerDueDate.Location = new Point(791, 3);
|
||||||
|
dateTimePickerDueDate.Name = "dateTimePickerDueDate";
|
||||||
|
dateTimePickerDueDate.Size = new Size(194, 39);
|
||||||
|
dateTimePickerDueDate.TabIndex = 5;
|
||||||
|
//
|
||||||
|
// comboBoxStatus
|
||||||
|
//
|
||||||
|
comboBoxStatus.FlatStyle = FlatStyle.Flat;
|
||||||
|
comboBoxStatus.FormattingEnabled = true;
|
||||||
|
comboBoxStatus.Location = new Point(3, 3);
|
||||||
|
comboBoxStatus.Name = "comboBoxStatus";
|
||||||
|
comboBoxStatus.Size = new Size(190, 40);
|
||||||
|
comboBoxStatus.TabIndex = 3;
|
||||||
|
//
|
||||||
|
// labelPriority
|
||||||
|
//
|
||||||
|
labelPriority.AutoSize = true;
|
||||||
|
labelPriority.Dock = DockStyle.Fill;
|
||||||
|
labelPriority.Location = new Point(200, 0);
|
||||||
|
labelPriority.Name = "labelPriority";
|
||||||
|
labelPriority.Size = new Size(191, 44);
|
||||||
|
labelPriority.TabIndex = 0;
|
||||||
|
labelPriority.Text = "Priority";
|
||||||
|
labelPriority.TextAlign = ContentAlignment.TopCenter;
|
||||||
|
//
|
||||||
|
// comboBoxPriority
|
||||||
|
//
|
||||||
|
comboBoxPriority.FormattingEnabled = true;
|
||||||
|
comboBoxPriority.Location = new Point(397, 3);
|
||||||
|
comboBoxPriority.Name = "comboBoxPriority";
|
||||||
|
comboBoxPriority.Size = new Size(191, 40);
|
||||||
|
comboBoxPriority.TabIndex = 4;
|
||||||
|
//
|
||||||
|
// labelDueDate
|
||||||
|
//
|
||||||
|
labelDueDate.AutoSize = true;
|
||||||
|
labelDueDate.Dock = DockStyle.Fill;
|
||||||
|
labelDueDate.Location = new Point(594, 0);
|
||||||
|
labelDueDate.Name = "labelDueDate";
|
||||||
|
labelDueDate.Size = new Size(191, 44);
|
||||||
|
labelDueDate.TabIndex = 11;
|
||||||
|
labelDueDate.Text = "Due Date";
|
||||||
|
labelDueDate.TextAlign = ContentAlignment.TopCenter;
|
||||||
|
//
|
||||||
|
// labelStatus
|
||||||
|
//
|
||||||
|
labelStatus.AutoSize = true;
|
||||||
|
labelStatus.Dock = DockStyle.Fill;
|
||||||
|
labelStatus.Location = new Point(3, 150);
|
||||||
|
labelStatus.Name = "labelStatus";
|
||||||
|
labelStatus.Size = new Size(169, 50);
|
||||||
|
labelStatus.TabIndex = 14;
|
||||||
|
labelStatus.Text = "Status";
|
||||||
|
//
|
||||||
|
// richTextBoxDescription
|
||||||
|
//
|
||||||
|
richTextBoxDescription.Dock = DockStyle.Fill;
|
||||||
|
richTextBoxDescription.Location = new Point(178, 53);
|
||||||
|
richTextBoxDescription.Name = "richTextBoxDescription";
|
||||||
|
richTextBoxDescription.Size = new Size(988, 94);
|
||||||
|
richTextBoxDescription.TabIndex = 2;
|
||||||
|
richTextBoxDescription.Text = "";
|
||||||
|
//
|
||||||
|
// labelDescription
|
||||||
|
//
|
||||||
|
labelDescription.AutoSize = true;
|
||||||
|
labelDescription.Dock = DockStyle.Fill;
|
||||||
|
labelDescription.Location = new Point(3, 50);
|
||||||
|
labelDescription.Name = "labelDescription";
|
||||||
|
labelDescription.Size = new Size(169, 100);
|
||||||
|
labelDescription.TabIndex = 12;
|
||||||
|
labelDescription.Text = "Description";
|
||||||
|
//
|
||||||
|
// textBoxTitle
|
||||||
|
//
|
||||||
|
textBoxTitle.Dock = DockStyle.Fill;
|
||||||
|
textBoxTitle.Location = new Point(178, 3);
|
||||||
|
textBoxTitle.Margin = new Padding(3, 3, 52, 3);
|
||||||
|
textBoxTitle.Name = "textBoxTitle";
|
||||||
|
textBoxTitle.PlaceholderText = "Task name";
|
||||||
|
textBoxTitle.Size = new Size(939, 39);
|
||||||
|
textBoxTitle.TabIndex = 1;
|
||||||
|
textBoxTitle.Validating += textBoxTitle_Validating;
|
||||||
|
//
|
||||||
|
// labelTitle
|
||||||
|
//
|
||||||
|
labelTitle.AutoSize = true;
|
||||||
|
labelTitle.Dock = DockStyle.Fill;
|
||||||
|
labelTitle.Location = new Point(3, 0);
|
||||||
|
labelTitle.Name = "labelTitle";
|
||||||
|
labelTitle.Size = new Size(169, 50);
|
||||||
|
labelTitle.TabIndex = 10;
|
||||||
|
labelTitle.Text = "Name *";
|
||||||
|
//
|
||||||
|
// labelHoursEst
|
||||||
|
//
|
||||||
|
labelHoursEst.AutoSize = true;
|
||||||
|
labelHoursEst.Dock = DockStyle.Fill;
|
||||||
|
labelHoursEst.Location = new Point(3, 200);
|
||||||
|
labelHoursEst.Name = "labelHoursEst";
|
||||||
|
labelHoursEst.Size = new Size(169, 53);
|
||||||
|
labelHoursEst.TabIndex = 0;
|
||||||
|
labelHoursEst.Text = "Estimate Hrs";
|
||||||
|
//
|
||||||
|
// tableLayoutPanel6
|
||||||
|
//
|
||||||
|
tableLayoutPanel6.ColumnCount = 5;
|
||||||
|
tableLayoutPanel6.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 20F));
|
||||||
|
tableLayoutPanel6.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 20F));
|
||||||
|
tableLayoutPanel6.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 20F));
|
||||||
|
tableLayoutPanel6.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 20F));
|
||||||
|
tableLayoutPanel6.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 20F));
|
||||||
|
tableLayoutPanel6.Controls.Add(labelHoursActual, 1, 0);
|
||||||
|
tableLayoutPanel6.Controls.Add(textBoxHoursEst, 0, 0);
|
||||||
|
tableLayoutPanel6.Controls.Add(textBoxHoursActual, 2, 0);
|
||||||
|
tableLayoutPanel6.Controls.Add(labelRate, 3, 0);
|
||||||
|
tableLayoutPanel6.Controls.Add(textBoxRate, 4, 0);
|
||||||
|
tableLayoutPanel6.Dock = DockStyle.Fill;
|
||||||
|
tableLayoutPanel6.Location = new Point(178, 203);
|
||||||
|
tableLayoutPanel6.Name = "tableLayoutPanel6";
|
||||||
|
tableLayoutPanel6.RowCount = 1;
|
||||||
|
tableLayoutPanel6.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
|
||||||
|
tableLayoutPanel6.Size = new Size(988, 47);
|
||||||
|
tableLayoutPanel6.TabIndex = 99;
|
||||||
|
//
|
||||||
|
// labelHoursActual
|
||||||
|
//
|
||||||
|
labelHoursActual.AutoSize = true;
|
||||||
|
labelHoursActual.Dock = DockStyle.Fill;
|
||||||
|
labelHoursActual.Location = new Point(200, 0);
|
||||||
|
labelHoursActual.Name = "labelHoursActual";
|
||||||
|
labelHoursActual.Size = new Size(191, 47);
|
||||||
|
labelHoursActual.TabIndex = 2;
|
||||||
|
labelHoursActual.Text = "Actual Hrs";
|
||||||
|
labelHoursActual.TextAlign = ContentAlignment.TopCenter;
|
||||||
|
//
|
||||||
|
// textBoxHoursEst
|
||||||
|
//
|
||||||
|
textBoxHoursEst.Dock = DockStyle.Fill;
|
||||||
|
textBoxHoursEst.Location = new Point(3, 3);
|
||||||
|
textBoxHoursEst.Name = "textBoxHoursEst";
|
||||||
|
textBoxHoursEst.Size = new Size(191, 39);
|
||||||
|
textBoxHoursEst.TabIndex = 6;
|
||||||
|
//
|
||||||
|
// textBoxHoursActual
|
||||||
|
//
|
||||||
|
textBoxHoursActual.Dock = DockStyle.Fill;
|
||||||
|
textBoxHoursActual.Location = new Point(397, 3);
|
||||||
|
textBoxHoursActual.Name = "textBoxHoursActual";
|
||||||
|
textBoxHoursActual.Size = new Size(191, 39);
|
||||||
|
textBoxHoursActual.TabIndex = 6;
|
||||||
|
//
|
||||||
|
// labelRate
|
||||||
|
//
|
||||||
|
labelRate.AutoSize = true;
|
||||||
|
labelRate.Dock = DockStyle.Fill;
|
||||||
|
labelRate.Location = new Point(594, 0);
|
||||||
|
labelRate.Name = "labelRate";
|
||||||
|
labelRate.Size = new Size(191, 47);
|
||||||
|
labelRate.TabIndex = 5;
|
||||||
|
labelRate.Text = "Rate";
|
||||||
|
labelRate.TextAlign = ContentAlignment.TopCenter;
|
||||||
|
//
|
||||||
|
// textBoxRate
|
||||||
|
//
|
||||||
|
textBoxRate.Dock = DockStyle.Fill;
|
||||||
|
textBoxRate.Location = new Point(791, 3);
|
||||||
|
textBoxRate.Name = "textBoxRate";
|
||||||
|
textBoxRate.Size = new Size(194, 39);
|
||||||
|
textBoxRate.TabIndex = 8;
|
||||||
|
//
|
||||||
|
// TaskForm
|
||||||
|
//
|
||||||
|
AutoScaleDimensions = new SizeF(13F, 32F);
|
||||||
|
AutoScaleMode = AutoScaleMode.Font;
|
||||||
|
ClientSize = new Size(1181, 372);
|
||||||
|
Controls.Add(groupBoxNewTask);
|
||||||
|
Name = "TaskForm";
|
||||||
|
Text = "TaskForm";
|
||||||
|
groupBoxNewTask.ResumeLayout(false);
|
||||||
|
tableLayoutPanel1.ResumeLayout(false);
|
||||||
|
tableLayoutPanel3.ResumeLayout(false);
|
||||||
|
tableLayoutPanel3.PerformLayout();
|
||||||
|
tableLayoutPanel2.ResumeLayout(false);
|
||||||
|
tableLayoutPanel2.PerformLayout();
|
||||||
|
tableLayoutPanel4.ResumeLayout(false);
|
||||||
|
tableLayoutPanel4.PerformLayout();
|
||||||
|
tableLayoutPanel6.ResumeLayout(false);
|
||||||
|
tableLayoutPanel6.PerformLayout();
|
||||||
|
ResumeLayout(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private GroupBox groupBoxNewTask;
|
||||||
|
private TableLayoutPanel tableLayoutPanel1;
|
||||||
|
private TableLayoutPanel tableLayoutPanel2;
|
||||||
|
private Label labelClient;
|
||||||
|
private Label labelTitle;
|
||||||
|
private Label labelDescription;
|
||||||
|
private Label labelHoursEst;
|
||||||
|
private TextBox textBoxTitle;
|
||||||
|
private ComboBox comboBoxClient;
|
||||||
|
private TableLayoutPanel tableLayoutPanel6;
|
||||||
|
private Label labelHoursActual;
|
||||||
|
private RichTextBox richTextBoxDescription;
|
||||||
|
private TableLayoutPanel tableLayoutPanel4;
|
||||||
|
private ComboBox comboBoxStatus;
|
||||||
|
private Label labelPriority;
|
||||||
|
private Label labelStatus;
|
||||||
|
private DateTimePicker dateTimePickerDueDate;
|
||||||
|
private ComboBox comboBoxPriority;
|
||||||
|
private Label labelDueDate;
|
||||||
|
private TextBox textBoxHoursEst;
|
||||||
|
private TextBox textBoxHoursActual;
|
||||||
|
private TableLayoutPanel tableLayoutPanel3;
|
||||||
|
private Button buttonOkay;
|
||||||
|
private Button buttonCancel;
|
||||||
|
private Label labelCreatedUpdatedDT;
|
||||||
|
private Label labelRate;
|
||||||
|
private TextBox textBoxRate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,114 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Data;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using trakker.Models;
|
||||||
|
|
||||||
|
namespace trakker.Forms
|
||||||
|
{
|
||||||
|
|
||||||
|
public partial class TaskForm : Form
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The task instance being edited by this form.
|
||||||
|
/// </summary>
|
||||||
|
private readonly trakker.Models.Task _task;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Binding source that connects the task status to the form controls.
|
||||||
|
/// </summary>
|
||||||
|
private BindingSource? _status;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Binding source that connects the task priority to the form controls.
|
||||||
|
/// </summary>
|
||||||
|
private BindingSource? _priority;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Binding source that connects the task model to the form controls.
|
||||||
|
/// </summary>
|
||||||
|
private BindingSource bindingSource = new BindingSource();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Error provider used to display validation errors next to input controls.
|
||||||
|
/// </summary>
|
||||||
|
private ErrorProvider errorProvider = new ErrorProvider();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ProjectForm"/> class bound to
|
||||||
|
/// the provided <paramref name="project"/>. Sets up data bindings for all
|
||||||
|
/// visible input controls and configures dialog button behavior.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="task">The <see cref="Task"/> instance to edit. Must not be null.</param>
|
||||||
|
/// <param name="status">The binding source for status values. Must not be null.</param>
|
||||||
|
/// <param name="priority">The binding source for priority values. Must not be null.</param>
|
||||||
|
public TaskForm(trakker.Models.Task task, BindingSource status, BindingSource priority)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
_task = task ?? throw new ArgumentNullException(nameof(task));
|
||||||
|
_status = status ?? throw new ArgumentNullException(nameof(status));
|
||||||
|
_priority = priority ?? throw new ArgumentNullException(nameof(priority));
|
||||||
|
|
||||||
|
Text = "Task Details";
|
||||||
|
|
||||||
|
comboBoxStatus.DataSource = _status;
|
||||||
|
comboBoxStatus.DisplayMember = "Display";
|
||||||
|
comboBoxStatus.ValueMember = "Value";
|
||||||
|
|
||||||
|
comboBoxPriority.DataSource = _priority;
|
||||||
|
comboBoxPriority.DisplayMember = "Display";
|
||||||
|
comboBoxPriority.ValueMember = "Value";
|
||||||
|
|
||||||
|
dateTimePickerDueDate.Format = DateTimePickerFormat.Custom;
|
||||||
|
dateTimePickerDueDate.CustomFormat = "yyyy-MM-dd";
|
||||||
|
|
||||||
|
// // Bind model properties to controls so the UI reflects and updates the model.
|
||||||
|
bindingSource.DataSource = _task;
|
||||||
|
textBoxTitle.DataBindings.Add("Text", bindingSource, "Title", true);
|
||||||
|
richTextBoxDescription.DataBindings.Add("Text", bindingSource, "Description", true);
|
||||||
|
dateTimePickerDueDate.DataBindings.Add("Value", bindingSource, "DueDate", true);
|
||||||
|
textBoxHoursEst.DataBindings.Add("Text", bindingSource, "EstimatedHours", true);
|
||||||
|
textBoxHoursActual.DataBindings.Add("Text", bindingSource, "ActualHours", true);
|
||||||
|
textBoxRate.DataBindings.Add("Text", bindingSource, "HourlyRate", true);
|
||||||
|
comboBoxStatus.DataBindings.Add("SelectedValue", bindingSource, "Status", true);
|
||||||
|
comboBoxPriority.DataBindings.Add("SelectedValue", bindingSource, "Priority", true);
|
||||||
|
|
||||||
|
// Configure dialog buttons and window behavior.
|
||||||
|
buttonOkay.DialogResult = DialogResult.OK;
|
||||||
|
buttonCancel.DialogResult = DialogResult.Cancel;
|
||||||
|
this.CancelButton = buttonCancel;
|
||||||
|
this.StartPosition = FormStartPosition.CenterParent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the <see cref="Task"/> instance edited by the form.
|
||||||
|
/// </summary>
|
||||||
|
public trakker.Models.Task Task { get => _task; private set { } }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validates the Name field. If the name is empty or whitespace, an error is set
|
||||||
|
/// on the <see cref="errorProvider"/> and the event is canceled to prevent the
|
||||||
|
/// form from closing.
|
||||||
|
/// </summary>
|
||||||
|
private void textBoxTitle_Validating(object sender, System.ComponentModel.CancelEventArgs e)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(textBoxTitle.Text))
|
||||||
|
{
|
||||||
|
errorProvider.SetError(textBoxTitle, "Title is required.");
|
||||||
|
errorProvider.SetIconAlignment(textBoxTitle, ErrorIconAlignment.MiddleRight);
|
||||||
|
errorProvider.SetIconPadding(textBoxTitle, 2);
|
||||||
|
e.Cancel = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
errorProvider.SetError(textBoxTitle, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,120 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
</root>
|
||||||
|
|
@ -13,5 +13,6 @@ namespace trakker.Interfaces
|
||||||
void InitDataGridViewClients(BindingList<Client> clients);
|
void InitDataGridViewClients(BindingList<Client> clients);
|
||||||
void InitDataGridViewProjects();
|
void InitDataGridViewProjects();
|
||||||
void FillDataGridViewProjects(BindingSource projects);
|
void FillDataGridViewProjects(BindingSource projects);
|
||||||
|
void FillTreeViewTasks(List<TaskFS> items);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
namespace trakker.Models
|
namespace trakker.Models
|
||||||
{
|
{
|
||||||
internal class LOV
|
public class LOV
|
||||||
{
|
{
|
||||||
public LOV(string value, string display)
|
public LOV(string value, string display)
|
||||||
{
|
{
|
||||||
Value = value;
|
Value = value;
|
||||||
Display = display;
|
Display = display;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Value { get; set; }
|
public string Value { get; set; }
|
||||||
public string Display { get; set; }
|
public string Display { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
namespace trakker.Models
|
||||||
|
{
|
||||||
|
public class Task
|
||||||
|
{
|
||||||
|
public Task()
|
||||||
|
{
|
||||||
|
TaskId = Guid.NewGuid().ToString();
|
||||||
|
DueDate = DateTime.Now;
|
||||||
|
CreatedAt = DateTime.Now;
|
||||||
|
UpdatedAt = DateTime.Now;
|
||||||
|
}
|
||||||
|
public string? TaskId { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string? ProjectId { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string? Title { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string? Description { get; set; }
|
||||||
|
|
||||||
|
public string Status { get; set; } = "todo";
|
||||||
|
|
||||||
|
public string Priority { get; set; } = "medium";
|
||||||
|
|
||||||
|
public DateTime? DueDate { get; set; }
|
||||||
|
|
||||||
|
public double? EstimatedHours { get; set; } = 0;
|
||||||
|
|
||||||
|
public double? ActualHours { get; set; } = 0;
|
||||||
|
|
||||||
|
public double? HourlyRate { get; set; } = 0;
|
||||||
|
|
||||||
|
public string? ParentTaskId { get; set; }
|
||||||
|
|
||||||
|
public DateTime? CreatedAt { get; set; }
|
||||||
|
|
||||||
|
public DateTime? UpdatedAt { get; set; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return @$"
|
||||||
|
TaskId: {TaskId}
|
||||||
|
ProjectId: {ProjectId}
|
||||||
|
Title: {Title}
|
||||||
|
Description: {Description}
|
||||||
|
Status: {Status}
|
||||||
|
Priority: {Priority}
|
||||||
|
DueDate: {DueDate}
|
||||||
|
EstimatedHours: {EstimatedHours}
|
||||||
|
ActualHours: {ActualHours}
|
||||||
|
HourlyRate: {HourlyRate}
|
||||||
|
ParentTaskId: {ParentTaskId}
|
||||||
|
CreatedAt: {CreatedAt}
|
||||||
|
UpdatedAt: {UpdatedAt}
|
||||||
|
";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TaskFS
|
||||||
|
{
|
||||||
|
public string GUID { get; set; } = string.Empty;
|
||||||
|
public string Node { get; set; } = string.Empty;
|
||||||
|
public string Parent { get; set; } = string.Empty;
|
||||||
|
public double? HourlyRate { get; set; } = 0;
|
||||||
|
public string ProjectId { get; set; } = string.Empty;
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return @$"
|
||||||
|
GUID: {GUID}
|
||||||
|
Node: {Node}
|
||||||
|
Parent: {Parent}
|
||||||
|
HourlyRate: {HourlyRate}
|
||||||
|
ProjectId: {ProjectId}
|
||||||
|
";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TaskComment
|
||||||
|
{
|
||||||
|
public TaskComment()
|
||||||
|
{
|
||||||
|
TaskCommentId = Guid.NewGuid().ToString();
|
||||||
|
}
|
||||||
|
public string? TaskCommentId { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string? TaskId { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string? Comment { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public DateTime? CreatedAt { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using trakker.Models;
|
using trakker.Models;
|
||||||
using trakker.Interfaces;
|
using trakker.Interfaces;
|
||||||
|
using trakker.Data;
|
||||||
|
|
||||||
namespace trakker.Services
|
namespace trakker.Services
|
||||||
{
|
{
|
||||||
|
|
@ -55,6 +56,12 @@ namespace trakker.Services
|
||||||
var x = project.Status;
|
var x = project.Status;
|
||||||
}
|
}
|
||||||
_view.FillDataGridViewProjects(new BindingSource { DataSource = projects });
|
_view.FillDataGridViewProjects(new BindingSource { DataSource = projects });
|
||||||
|
LoadTasks();
|
||||||
|
}
|
||||||
|
internal void LoadTasks()
|
||||||
|
{
|
||||||
|
var dbo = new TaskData(_connectionString);
|
||||||
|
_view.FillTreeViewTasks(dbo.GetFS());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue