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>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
components = new System.ComponentModel.Container();
|
||||
MainForm_MenuStrip = new MenuStrip();
|
||||
fileToolStripMenuItem = new ToolStripMenuItem();
|
||||
MainForm_Exit_MenuItem = new ToolStripMenuItem();
|
||||
|
|
@ -40,9 +41,16 @@
|
|||
MainForm_TabPage3 = new TabPage();
|
||||
tableLayoutPanelProjects1 = new TableLayoutPanel();
|
||||
dataGridViewProjects = new DataGridView();
|
||||
tableLayoutPanelProjects2 = new TableLayoutPanel();
|
||||
groupBoxProjectTasks = new GroupBox();
|
||||
dataGridViewProjectTasks = new DataGridView();
|
||||
MainForm_TabPage4 = new TabPage();
|
||||
tableLayoutPanelTasks1 = new TableLayoutPanel();
|
||||
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();
|
||||
tabControlMainForm.SuspendLayout();
|
||||
MainForm_TabPage2.SuspendLayout();
|
||||
|
|
@ -51,9 +59,12 @@
|
|||
MainForm_TabPage3.SuspendLayout();
|
||||
tableLayoutPanelProjects1.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)dataGridViewProjects).BeginInit();
|
||||
tableLayoutPanelProjects2.SuspendLayout();
|
||||
groupBoxProjectTasks.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)dataGridViewProjectTasks).BeginInit();
|
||||
MainForm_TabPage4.SuspendLayout();
|
||||
tableLayoutPanelTasks1.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)splitContainerTasks1).BeginInit();
|
||||
splitContainerTasks1.Panel1.SuspendLayout();
|
||||
splitContainerTasks1.SuspendLayout();
|
||||
contextMenuStripTreeviewTasks.SuspendLayout();
|
||||
SuspendLayout();
|
||||
//
|
||||
// MainForm_MenuStrip
|
||||
|
|
@ -94,6 +105,7 @@
|
|||
tabControlMainForm.Controls.Add(MainForm_TabPage1);
|
||||
tabControlMainForm.Controls.Add(MainForm_TabPage2);
|
||||
tabControlMainForm.Controls.Add(MainForm_TabPage3);
|
||||
tabControlMainForm.Controls.Add(MainForm_TabPage4);
|
||||
tabControlMainForm.Dock = DockStyle.Fill;
|
||||
tabControlMainForm.Location = new Point(0, 40);
|
||||
tabControlMainForm.Name = "tabControlMainForm";
|
||||
|
|
@ -165,14 +177,13 @@
|
|||
tableLayoutPanelProjects1.ColumnCount = 1;
|
||||
tableLayoutPanelProjects1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
|
||||
tableLayoutPanelProjects1.Controls.Add(dataGridViewProjects, 0, 1);
|
||||
tableLayoutPanelProjects1.Controls.Add(tableLayoutPanelProjects2, 0, 2);
|
||||
tableLayoutPanelProjects1.Dock = DockStyle.Fill;
|
||||
tableLayoutPanelProjects1.Location = new Point(0, 0);
|
||||
tableLayoutPanelProjects1.Name = "tableLayoutPanelProjects1";
|
||||
tableLayoutPanelProjects1.RowCount = 3;
|
||||
tableLayoutPanelProjects1.RowStyles.Add(new RowStyle(SizeType.Absolute, 1F));
|
||||
tableLayoutPanelProjects1.RowStyles.Add(new RowStyle(SizeType.Percent, 50F));
|
||||
tableLayoutPanelProjects1.RowStyles.Add(new RowStyle(SizeType.Percent, 50F));
|
||||
tableLayoutPanelProjects1.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
|
||||
tableLayoutPanelProjects1.RowStyles.Add(new RowStyle(SizeType.Percent, 0F));
|
||||
tableLayoutPanelProjects1.Size = new Size(1862, 966);
|
||||
tableLayoutPanelProjects1.TabIndex = 1;
|
||||
//
|
||||
|
|
@ -186,47 +197,95 @@
|
|||
dataGridViewProjects.Name = "dataGridViewProjects";
|
||||
dataGridViewProjects.ReadOnly = true;
|
||||
dataGridViewProjects.RowHeadersWidth = 82;
|
||||
dataGridViewProjects.Size = new Size(1856, 476);
|
||||
dataGridViewProjects.Size = new Size(1856, 959);
|
||||
dataGridViewProjects.TabIndex = 0;
|
||||
//
|
||||
// tableLayoutPanelProjects2
|
||||
// MainForm_TabPage4
|
||||
//
|
||||
tableLayoutPanelProjects2.ColumnCount = 3;
|
||||
tableLayoutPanelProjects2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 0F));
|
||||
tableLayoutPanelProjects2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
|
||||
tableLayoutPanelProjects2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 0F));
|
||||
tableLayoutPanelProjects2.Controls.Add(groupBoxProjectTasks, 1, 0);
|
||||
tableLayoutPanelProjects2.Dock = DockStyle.Fill;
|
||||
tableLayoutPanelProjects2.Location = new Point(3, 486);
|
||||
tableLayoutPanelProjects2.Name = "tableLayoutPanelProjects2";
|
||||
tableLayoutPanelProjects2.RowCount = 1;
|
||||
tableLayoutPanelProjects2.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
|
||||
tableLayoutPanelProjects2.Size = new Size(1856, 477);
|
||||
tableLayoutPanelProjects2.TabIndex = 1;
|
||||
MainForm_TabPage4.Controls.Add(tableLayoutPanelTasks1);
|
||||
MainForm_TabPage4.Location = new Point(8, 46);
|
||||
MainForm_TabPage4.Name = "MainForm_TabPage4";
|
||||
MainForm_TabPage4.Size = new Size(1862, 966);
|
||||
MainForm_TabPage4.TabIndex = 3;
|
||||
MainForm_TabPage4.Text = "Tab 4";
|
||||
MainForm_TabPage4.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// groupBoxProjectTasks
|
||||
// tableLayoutPanelTasks1
|
||||
//
|
||||
groupBoxProjectTasks.Controls.Add(dataGridViewProjectTasks);
|
||||
groupBoxProjectTasks.Dock = DockStyle.Fill;
|
||||
groupBoxProjectTasks.Location = new Point(3, 3);
|
||||
groupBoxProjectTasks.Name = "groupBoxProjectTasks";
|
||||
groupBoxProjectTasks.Size = new Size(1850, 471);
|
||||
groupBoxProjectTasks.TabIndex = 0;
|
||||
groupBoxProjectTasks.TabStop = false;
|
||||
groupBoxProjectTasks.Text = "Tasks";
|
||||
tableLayoutPanelTasks1.ColumnCount = 1;
|
||||
tableLayoutPanelTasks1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
|
||||
tableLayoutPanelTasks1.Controls.Add(splitContainerTasks1, 0, 1);
|
||||
tableLayoutPanelTasks1.Dock = DockStyle.Fill;
|
||||
tableLayoutPanelTasks1.Location = new Point(0, 0);
|
||||
tableLayoutPanelTasks1.Name = "tableLayoutPanelTasks1";
|
||||
tableLayoutPanelTasks1.RowCount = 3;
|
||||
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;
|
||||
dataGridViewProjectTasks.AllowUserToDeleteRows = false;
|
||||
dataGridViewProjectTasks.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
dataGridViewProjectTasks.Dock = DockStyle.Fill;
|
||||
dataGridViewProjectTasks.Location = new Point(3, 35);
|
||||
dataGridViewProjectTasks.Name = "dataGridViewProjectTasks";
|
||||
dataGridViewProjectTasks.ReadOnly = true;
|
||||
dataGridViewProjectTasks.RowHeadersWidth = 82;
|
||||
dataGridViewProjectTasks.Size = new Size(1844, 433);
|
||||
dataGridViewProjectTasks.TabIndex = 0;
|
||||
splitContainerTasks1.Dock = DockStyle.Fill;
|
||||
splitContainerTasks1.Location = new Point(3, 3);
|
||||
splitContainerTasks1.Name = "splitContainerTasks1";
|
||||
//
|
||||
// splitContainerTasks1.Panel1
|
||||
//
|
||||
splitContainerTasks1.Panel1.Controls.Add(treeViewTasks1);
|
||||
splitContainerTasks1.Size = new Size(1856, 960);
|
||||
splitContainerTasks1.SplitterDistance = 618;
|
||||
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
|
||||
//
|
||||
|
|
@ -248,9 +307,12 @@
|
|||
MainForm_TabPage3.ResumeLayout(false);
|
||||
tableLayoutPanelProjects1.ResumeLayout(false);
|
||||
((System.ComponentModel.ISupportInitialize)dataGridViewProjects).EndInit();
|
||||
tableLayoutPanelProjects2.ResumeLayout(false);
|
||||
groupBoxProjectTasks.ResumeLayout(false);
|
||||
((System.ComponentModel.ISupportInitialize)dataGridViewProjectTasks).EndInit();
|
||||
MainForm_TabPage4.ResumeLayout(false);
|
||||
tableLayoutPanelTasks1.ResumeLayout(false);
|
||||
splitContainerTasks1.Panel1.ResumeLayout(false);
|
||||
((System.ComponentModel.ISupportInitialize)splitContainerTasks1).EndInit();
|
||||
splitContainerTasks1.ResumeLayout(false);
|
||||
contextMenuStripTreeviewTasks.ResumeLayout(false);
|
||||
ResumeLayout(false);
|
||||
PerformLayout();
|
||||
}
|
||||
|
|
@ -269,8 +331,15 @@
|
|||
private TabPage MainForm_TabPage3;
|
||||
private TableLayoutPanel tableLayoutPanelProjects1;
|
||||
private DataGridView dataGridViewProjects;
|
||||
private TableLayoutPanel tableLayoutPanelProjects2;
|
||||
private GroupBox groupBoxProjectTasks;
|
||||
private DataGridView dataGridViewProjectTasks;
|
||||
private TabPage MainForm_TabPage4;
|
||||
private TableLayoutPanel tableLayoutPanelTasks1;
|
||||
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 string connectionString = string.Empty;
|
||||
readonly MainCtrl _ctrl;
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MainForm"/> class.
|
||||
|
|
@ -37,7 +37,8 @@ namespace trakker
|
|||
tabControlMainForm.TabPages[0].Text = " Home ";
|
||||
tabControlMainForm.TabPages[1].Text = " Clients ";
|
||||
tabControlMainForm.TabPages[2].Text = " Projects ";
|
||||
|
||||
tabControlMainForm.TabPages[3].Text = " Tasks ";
|
||||
|
||||
_ctrl = new Services.MainCtrl(this, connectionString);
|
||||
}
|
||||
|
||||
|
|
@ -131,7 +132,7 @@ namespace trakker
|
|||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
dataGridViewClients.SelectionChanged += (s, e) =>
|
||||
{
|
||||
|
|
@ -274,7 +275,7 @@ namespace trakker
|
|||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
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)
|
||||
//{
|
||||
|
|
|
|||
|
|
@ -123,6 +123,9 @@
|
|||
<metadata name="MainForm_StatusStrip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>16, 55</value>
|
||||
</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">
|
||||
<value>162</value>
|
||||
</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 InitDataGridViewProjects();
|
||||
void FillDataGridViewProjects(BindingSource projects);
|
||||
void FillTreeViewTasks(List<TaskFS> items);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
namespace trakker.Models
|
||||
{
|
||||
internal class LOV
|
||||
public class LOV
|
||||
{
|
||||
public LOV(string value, string display)
|
||||
{
|
||||
Value = value;
|
||||
Display = display;
|
||||
}
|
||||
|
||||
public string Value { 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 trakker.Models;
|
||||
using trakker.Interfaces;
|
||||
using trakker.Data;
|
||||
|
||||
namespace trakker.Services
|
||||
{
|
||||
|
|
@ -55,6 +56,12 @@ namespace trakker.Services
|
|||
var x = project.Status;
|
||||
}
|
||||
_view.FillDataGridViewProjects(new BindingSource { DataSource = projects });
|
||||
LoadTasks();
|
||||
}
|
||||
internal void LoadTasks()
|
||||
{
|
||||
var dbo = new TaskData(_connectionString);
|
||||
_view.FillTreeViewTasks(dbo.GetFS());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue