Continued development
This commit is contained in:
parent
9119eca1b4
commit
74b01247ed
|
|
@ -4,27 +4,27 @@ using trakker.Models;
|
||||||
namespace trakker.Data
|
namespace trakker.Data
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides data access methods for the <see cref="Models.Task"/> entity.
|
/// Provides data access methods for the <see cref="Models.PTask"/> entity.
|
||||||
/// This class encapsulates database operations such as upsert, delete and ad-hoc
|
/// This class encapsulates database operations such as upsert, delete and ad-hoc
|
||||||
/// SQL execution for tasks. It inherits from <see cref="DataAccess"/> which
|
/// SQL execution for tasks. It inherits from <see cref="DataAccess"/> which
|
||||||
/// provides connection management.
|
/// provides connection management.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class TaskData(string connectionString) : DataAccess(connectionString)
|
internal class TaskData(string connectionString) : DataAccess(connectionString)
|
||||||
{
|
{
|
||||||
public BindingList<trakker.Models.Task> GetRecursive(string? id = null, trakker.Models.Task.RecursiveRoot root = trakker.Models.Task.RecursiveRoot.TASK_ID)
|
public BindingList<PTask> GetRecursive(string? id = null, PTask.RecursiveRoot root = PTask.RecursiveRoot.TASK_ID)
|
||||||
{
|
{
|
||||||
var results = new BindingList<trakker.Models.Task>();
|
var results = new BindingList<PTask>();
|
||||||
|
|
||||||
string whereClause = "1 = 1";
|
string whereClause = "1 = 1";
|
||||||
switch(root)
|
switch(root)
|
||||||
{
|
{
|
||||||
case trakker.Models.Task.RecursiveRoot.TASK_ID:
|
case PTask.RecursiveRoot.TASK_ID:
|
||||||
if (id != null)
|
if (id != null)
|
||||||
{
|
{
|
||||||
whereClause = "task_id = $id";
|
whereClause = "task_id = $id";
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case trakker.Models.Task.RecursiveRoot.PARENT_TASK_ID:
|
case PTask.RecursiveRoot.PARENT_TASK_ID:
|
||||||
if (id != null)
|
if (id != null)
|
||||||
{
|
{
|
||||||
whereClause = "parent_task_id = $id";
|
whereClause = "parent_task_id = $id";
|
||||||
|
|
@ -108,7 +108,7 @@ namespace trakker.Data
|
||||||
|
|
||||||
while (reader.Read())
|
while (reader.Read())
|
||||||
{
|
{
|
||||||
results.Add(new trakker.Models.Task
|
results.Add(new PTask
|
||||||
{
|
{
|
||||||
TaskId = reader.GetString(_var1),
|
TaskId = reader.GetString(_var1),
|
||||||
ProjectId = reader.GetString(_var2),
|
ProjectId = reader.GetString(_var2),
|
||||||
|
|
@ -129,9 +129,9 @@ namespace trakker.Data
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
public trakker.Models.Task Get(string? taskId = null)
|
public PTask Get(string? taskId = null)
|
||||||
{
|
{
|
||||||
var results = new List<trakker.Models.Task>();
|
var results = new List<PTask>();
|
||||||
|
|
||||||
string whereClause = "1 = 1";
|
string whereClause = "1 = 1";
|
||||||
if (taskId != null)
|
if (taskId != null)
|
||||||
|
|
@ -191,7 +191,7 @@ namespace trakker.Data
|
||||||
|
|
||||||
while (reader.Read())
|
while (reader.Read())
|
||||||
{
|
{
|
||||||
results.Add(new trakker.Models.Task
|
results.Add(new PTask
|
||||||
{
|
{
|
||||||
TaskId = reader.GetString(_var1),
|
TaskId = reader.GetString(_var1),
|
||||||
ProjectId = reader.GetString(_var2),
|
ProjectId = reader.GetString(_var2),
|
||||||
|
|
@ -209,7 +209,7 @@ namespace trakker.Data
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return results.FirstOrDefault() ?? new trakker.Models.Task();
|
return results.FirstOrDefault() ?? new PTask();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -218,13 +218,13 @@ namespace trakker.Data
|
||||||
/// a single SQL statement inside a transaction and will commit on
|
/// a single SQL statement inside a transaction and will commit on
|
||||||
/// success or roll back on failure.
|
/// success or roll back on failure.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="task">The <see cref="Task"/> model to insert or update. Must not be null.</param>
|
/// <param name="task">The <see cref="PTask"/> model to insert or update. Must not be null.</param>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// The SQL statement uses an ON CONFLICT clause to perform the update when a
|
/// 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
|
/// matching <c>task_id</c> already exists. Parameter names correspond to the
|
||||||
/// task model property names.
|
/// task model property names.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public void Upsert(trakker.Models.Task task)
|
public void Upsert(PTask task)
|
||||||
{
|
{
|
||||||
const string sql = @"
|
const string sql = @"
|
||||||
INSERT INTO tasks (
|
INSERT INTO tasks (
|
||||||
|
|
@ -350,9 +350,9 @@ namespace trakker.Data
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
public List<TaskFS> GetFS()
|
public List<PTaskFS> GetFS()
|
||||||
{
|
{
|
||||||
var results = new List<TaskFS>();
|
var results = new List<PTaskFS>();
|
||||||
|
|
||||||
string sql = $@"
|
string sql = $@"
|
||||||
SELECT
|
SELECT
|
||||||
|
|
@ -392,7 +392,7 @@ namespace trakker.Data
|
||||||
|
|
||||||
while (reader.Read())
|
while (reader.Read())
|
||||||
{
|
{
|
||||||
results.Add(new TaskFS
|
results.Add(new PTaskFS
|
||||||
{
|
{
|
||||||
GUID = reader.GetString(_var1),
|
GUID = reader.GetString(_var1),
|
||||||
Node = reader.GetString(_var2),
|
Node = reader.GetString(_var2),
|
||||||
|
|
@ -404,7 +404,7 @@ namespace trakker.Data
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
public void SaveComment(TaskComment taskComment)
|
public void SaveComment(PTaskComment taskComment)
|
||||||
{
|
{
|
||||||
const string sql = @"
|
const string sql = @"
|
||||||
INSERT INTO task_comments (
|
INSERT INTO task_comments (
|
||||||
|
|
@ -447,9 +447,9 @@ namespace trakker.Data
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
public List<TaskComment> GetComments(string taskId)
|
public List<PTaskComment> GetComments(string taskId)
|
||||||
{
|
{
|
||||||
var results = new List<TaskComment>();
|
var results = new List<PTaskComment>();
|
||||||
|
|
||||||
string whereClause = "1 = 1";
|
string whereClause = "1 = 1";
|
||||||
if (taskId != null)
|
if (taskId != null)
|
||||||
|
|
@ -485,7 +485,7 @@ namespace trakker.Data
|
||||||
var _var4 = reader.GetOrdinal("created_at");
|
var _var4 = reader.GetOrdinal("created_at");
|
||||||
while (reader.Read())
|
while (reader.Read())
|
||||||
{
|
{
|
||||||
results.Add(new TaskComment
|
results.Add(new PTaskComment
|
||||||
{
|
{
|
||||||
TaskCommentId = reader.GetString(_var1),
|
TaskCommentId = reader.GetString(_var1),
|
||||||
TaskId = reader.GetString(_var2),
|
TaskId = reader.GetString(_var2),
|
||||||
|
|
|
||||||
|
|
@ -305,17 +305,17 @@ namespace trakker
|
||||||
// When a tree node is clicked, fetch and show notebooks for that folder
|
// When a tree node is clicked, fetch and show notebooks for that folder
|
||||||
treeViewTasks1.NodeMouseClick += (sender, e) =>
|
treeViewTasks1.NodeMouseClick += (sender, e) =>
|
||||||
{
|
{
|
||||||
TaskFS? selectedNode = (TaskFS?)e.Node.Tag ?? new TaskFS();
|
PTaskFS? selectedNode = (PTaskFS?)e.Node.Tag ?? new PTaskFS();
|
||||||
if (selectedNode?.Parent == "/" || selectedNode?.GUID == "/")
|
if (selectedNode?.Parent == "/" || selectedNode?.GUID == "/")
|
||||||
{
|
{
|
||||||
_ctrl.LoadTasksRecursive(selectedNode.ProjectId, trakker.Models.Task.RecursiveRoot.PARENT_TASK_ID); // Load all tasks for the project when root node is clicked
|
_ctrl.LoadTasksRecursive(selectedNode.ProjectId, PTask.RecursiveRoot.PARENT_TASK_ID); // Load all tasks for the project when root node is clicked
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_ctrl.LoadTasksRecursive(selectedNode?.GUID ?? "/", trakker.Models.Task.RecursiveRoot.TASK_ID); // Load all tasks for the project when root node is clicked
|
_ctrl.LoadTasksRecursive(selectedNode?.GUID ?? "/", PTask.RecursiveRoot.TASK_ID); // Load all tasks for the project when root node is clicked
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
public void FillTreeViewTasks(List<TaskFS> items)
|
public void FillTreeViewTasks(List<PTaskFS> items)
|
||||||
{
|
{
|
||||||
if (items == null) return;
|
if (items == null) return;
|
||||||
|
|
||||||
|
|
@ -323,7 +323,7 @@ namespace trakker
|
||||||
treeViewTasks1.Nodes.Clear();
|
treeViewTasks1.Nodes.Clear();
|
||||||
|
|
||||||
// Root node
|
// Root node
|
||||||
TaskFS root = new();
|
PTaskFS root = new();
|
||||||
root.GUID = "/";
|
root.GUID = "/";
|
||||||
root.Node = "/Projects";
|
root.Node = "/Projects";
|
||||||
root.Parent = string.Empty;
|
root.Parent = string.Empty;
|
||||||
|
|
@ -334,13 +334,13 @@ namespace trakker
|
||||||
static string NormalizeKey(string? k) => string.IsNullOrWhiteSpace(k) ? "/" : k!.Trim();
|
static string NormalizeKey(string? k) => string.IsNullOrWhiteSpace(k) ? "/" : k!.Trim();
|
||||||
|
|
||||||
// Build lookup: parentKey -> list of children
|
// Build lookup: parentKey -> list of children
|
||||||
var lookup = new Dictionary<string, List<TaskFS>>();
|
var lookup = new Dictionary<string, List<PTaskFS>>();
|
||||||
foreach (var it in items)
|
foreach (var it in items)
|
||||||
{
|
{
|
||||||
var key = NormalizeKey(it.Parent);
|
var key = NormalizeKey(it.Parent);
|
||||||
if (!lookup.TryGetValue(key, out var list))
|
if (!lookup.TryGetValue(key, out var list))
|
||||||
{
|
{
|
||||||
list = new List<TaskFS>();
|
list = new List<PTaskFS>();
|
||||||
lookup[key] = list;
|
lookup[key] = list;
|
||||||
}
|
}
|
||||||
list.Add(it);
|
list.Add(it);
|
||||||
|
|
@ -376,14 +376,14 @@ namespace trakker
|
||||||
private void addTaskSubtaskToolStripMenuItem_Click(object sender, EventArgs e)
|
private void addTaskSubtaskToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (treeViewTasks1.SelectedNode == null) return;
|
if (treeViewTasks1.SelectedNode == null) return;
|
||||||
TaskFS? selectedNode = (TaskFS?)treeViewTasks1.SelectedNode.Tag ?? new TaskFS();
|
PTaskFS? selectedNode = (PTaskFS?)treeViewTasks1.SelectedNode.Tag ?? new PTaskFS();
|
||||||
if (selectedNode?.Parent == "")
|
if (selectedNode?.Parent == "")
|
||||||
{
|
{
|
||||||
MessageBox.Show("Cannot add tasks to root node", "Add Task", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
MessageBox.Show("Cannot add tasks to root node", "Add Task", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
string taskId = Guid.NewGuid().ToString();
|
string taskId = Guid.NewGuid().ToString();
|
||||||
trakker.Models.Task task = new()
|
PTask task = new()
|
||||||
{
|
{
|
||||||
TaskId = taskId,
|
TaskId = taskId,
|
||||||
Title = "New Task",
|
Title = "New Task",
|
||||||
|
|
@ -397,12 +397,12 @@ namespace trakker
|
||||||
if (result == DialogResult.OK)
|
if (result == DialogResult.OK)
|
||||||
{
|
{
|
||||||
task = dialog.Task;
|
task = dialog.Task;
|
||||||
//MessageBox.Show(task.ToString(), "Task Details", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
|
||||||
TaskData taskData = new TaskData(connectionString);
|
TaskData taskData = new TaskData(connectionString);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
taskData.Upsert(task);
|
taskData.Upsert(task);
|
||||||
_ctrl.LoadTasks(); // Reload tasks to update the DataGridView with any changes
|
treeViewTasks1.SelectedNode.Nodes.Add(new TreeNode(task.Title ?? "") { Tag = new PTaskFS { GUID = task.TaskId ?? "", Node = task.Title ?? "", Parent = task.ParentTaskId ?? "", HourlyRate = task.HourlyRate, ProjectId = task.ProjectId ?? "" } });
|
||||||
|
//_ctrl.LoadTasks();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
@ -414,15 +414,14 @@ namespace trakker
|
||||||
private void editThisTaskSubtaskToolStripMenuItem_Click(object sender, EventArgs e)
|
private void editThisTaskSubtaskToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (treeViewTasks1.SelectedNode == null) return;
|
if (treeViewTasks1.SelectedNode == null) return;
|
||||||
TaskFS? selectedTask = treeViewTasks1.SelectedNode.Tag as TaskFS;
|
PTaskFS? selectedTask = treeViewTasks1.SelectedNode.Tag as PTaskFS;
|
||||||
//MessageBox.Show(selectedTask != null ? selectedTask.ToString() : "No task selected", "Edit Task", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
|
||||||
if (selectedTask?.Parent == "/" || selectedTask?.GUID == "/")
|
if (selectedTask?.Parent == "/" || selectedTask?.GUID == "/")
|
||||||
{
|
{
|
||||||
MessageBox.Show("Cannot edit root node", "Edit Task", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
MessageBox.Show("Cannot edit root node", "Edit Task", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
TaskData taskData = new TaskData(connectionString);
|
TaskData taskData = new TaskData(connectionString);
|
||||||
trakker.Models.Task task = taskData.Get(selectedTask?.GUID);
|
PTask task = taskData.Get(selectedTask?.GUID);
|
||||||
TaskForm dialog = new TaskForm(task, _ctrl.GetLOV("task.status"), _ctrl.GetLOV("task.priority"));
|
TaskForm dialog = new TaskForm(task, _ctrl.GetLOV("task.status"), _ctrl.GetLOV("task.priority"));
|
||||||
DialogResult result = dialog.ShowDialog(this);
|
DialogResult result = dialog.ShowDialog(this);
|
||||||
if (result == DialogResult.OK)
|
if (result == DialogResult.OK)
|
||||||
|
|
@ -432,7 +431,8 @@ namespace trakker
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
taskData.Upsert(task);
|
taskData.Upsert(task);
|
||||||
_ctrl.LoadTasks(); // Reload tasks to update the DataGridView with any changes
|
treeViewTasks1.SelectedNode.Text = task.Title ?? ""; // Update the node text in the tree view
|
||||||
|
_ctrl.LoadTasksRecursive(selectedTask!.GUID, PTask.RecursiveRoot.TASK_ID); // Reload tasks to update the tree view with any changes
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
@ -444,7 +444,7 @@ namespace trakker
|
||||||
private void deleteThisTaskSubtaskToolStripMenuItem_Click(object sender, EventArgs e)
|
private void deleteThisTaskSubtaskToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (treeViewTasks1.SelectedNode == null) return;
|
if (treeViewTasks1.SelectedNode == null) return;
|
||||||
TaskFS? selectedTask = treeViewTasks1.SelectedNode.Tag as TaskFS;
|
PTaskFS? selectedTask = treeViewTasks1.SelectedNode.Tag as PTaskFS;
|
||||||
//MessageBox.Show(selectedTask != null ? selectedTask.ToString() : "No task selected", "Edit Task", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
//MessageBox.Show(selectedTask != null ? selectedTask.ToString() : "No task selected", "Edit Task", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||||
if (selectedTask?.Parent == "/" || selectedTask?.GUID == "/")
|
if (selectedTask?.Parent == "/" || selectedTask?.GUID == "/")
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ namespace trakker.Forms
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The task instance being edited by this form.
|
/// The task instance being edited by this form.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly trakker.Models.Task _task;
|
private readonly trakker.Models.PTask _task;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Binding source that connects the task status to the form controls.
|
/// Binding source that connects the task status to the form controls.
|
||||||
|
|
@ -47,7 +47,7 @@ namespace trakker.Forms
|
||||||
/// <param name="task">The <see cref="Task"/> instance to edit. Must not be null.</param>
|
/// <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="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>
|
/// <param name="priority">The binding source for priority values. Must not be null.</param>
|
||||||
public TaskForm(trakker.Models.Task task, BindingSource status, BindingSource priority)
|
public TaskForm(trakker.Models.PTask task, BindingSource status, BindingSource priority)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
_task = task ?? throw new ArgumentNullException(nameof(task));
|
_task = task ?? throw new ArgumentNullException(nameof(task));
|
||||||
|
|
@ -88,7 +88,7 @@ namespace trakker.Forms
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the <see cref="Task"/> instance edited by the form.
|
/// Gets the <see cref="Task"/> instance edited by the form.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public trakker.Models.Task Task { get => _task; private set { } }
|
public trakker.Models.PTask Task { get => _task; private set { } }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Validates the Name field. If the name is empty or whitespace, an error is set
|
/// Validates the Name field. If the name is empty or whitespace, an error is set
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,147 @@
|
||||||
|
namespace newcle.us.Forms
|
||||||
|
{
|
||||||
|
partial class TextAreaForm
|
||||||
|
{
|
||||||
|
/// <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()
|
||||||
|
{
|
||||||
|
components = new System.ComponentModel.Container();
|
||||||
|
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(TextAreaForm));
|
||||||
|
TextEdit_TableLayoutPanel1 = new TableLayoutPanel();
|
||||||
|
Content_RichTextBox = new RichTextBox();
|
||||||
|
TextEdit_TableLayoutPanel2 = new TableLayoutPanel();
|
||||||
|
Okay_Button = new Button();
|
||||||
|
Cancel_Button = new Button();
|
||||||
|
TextEdit_ContextMenuStrip = new ContextMenuStrip(components);
|
||||||
|
copyToClipboardToolStripMenuItem = new ToolStripMenuItem();
|
||||||
|
TextEdit_TableLayoutPanel1.SuspendLayout();
|
||||||
|
TextEdit_TableLayoutPanel2.SuspendLayout();
|
||||||
|
TextEdit_ContextMenuStrip.SuspendLayout();
|
||||||
|
SuspendLayout();
|
||||||
|
//
|
||||||
|
// TextEdit_TableLayoutPanel1
|
||||||
|
//
|
||||||
|
TextEdit_TableLayoutPanel1.ColumnCount = 1;
|
||||||
|
TextEdit_TableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
|
||||||
|
TextEdit_TableLayoutPanel1.Controls.Add(Content_RichTextBox, 0, 0);
|
||||||
|
TextEdit_TableLayoutPanel1.Controls.Add(TextEdit_TableLayoutPanel2, 0, 1);
|
||||||
|
TextEdit_TableLayoutPanel1.Dock = DockStyle.Fill;
|
||||||
|
TextEdit_TableLayoutPanel1.Location = new Point(0, 0);
|
||||||
|
TextEdit_TableLayoutPanel1.Name = "TextEdit_TableLayoutPanel1";
|
||||||
|
TextEdit_TableLayoutPanel1.RowCount = 2;
|
||||||
|
TextEdit_TableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
|
||||||
|
TextEdit_TableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Absolute, 75F));
|
||||||
|
TextEdit_TableLayoutPanel1.Size = new Size(1206, 777);
|
||||||
|
TextEdit_TableLayoutPanel1.TabIndex = 0;
|
||||||
|
//
|
||||||
|
// Content_RichTextBox
|
||||||
|
//
|
||||||
|
Content_RichTextBox.ContextMenuStrip = TextEdit_ContextMenuStrip;
|
||||||
|
Content_RichTextBox.Dock = DockStyle.Fill;
|
||||||
|
Content_RichTextBox.Font = new Font("Cascadia Code", 10.125F, FontStyle.Regular, GraphicsUnit.Point, 0);
|
||||||
|
Content_RichTextBox.Location = new Point(3, 3);
|
||||||
|
Content_RichTextBox.Name = "Content_RichTextBox";
|
||||||
|
Content_RichTextBox.Size = new Size(1200, 696);
|
||||||
|
Content_RichTextBox.TabIndex = 0;
|
||||||
|
Content_RichTextBox.Text = "";
|
||||||
|
Content_RichTextBox.LinkClicked += Content_RichTextBox_LinkClicked;
|
||||||
|
//
|
||||||
|
// TextEdit_TableLayoutPanel2
|
||||||
|
//
|
||||||
|
TextEdit_TableLayoutPanel2.ColumnCount = 3;
|
||||||
|
TextEdit_TableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
|
||||||
|
TextEdit_TableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 225F));
|
||||||
|
TextEdit_TableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 225F));
|
||||||
|
TextEdit_TableLayoutPanel2.Controls.Add(Okay_Button, 1, 0);
|
||||||
|
TextEdit_TableLayoutPanel2.Controls.Add(Cancel_Button, 2, 0);
|
||||||
|
TextEdit_TableLayoutPanel2.Dock = DockStyle.Fill;
|
||||||
|
TextEdit_TableLayoutPanel2.Location = new Point(3, 705);
|
||||||
|
TextEdit_TableLayoutPanel2.Name = "TextEdit_TableLayoutPanel2";
|
||||||
|
TextEdit_TableLayoutPanel2.RowCount = 1;
|
||||||
|
TextEdit_TableLayoutPanel2.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
|
||||||
|
TextEdit_TableLayoutPanel2.Size = new Size(1200, 69);
|
||||||
|
TextEdit_TableLayoutPanel2.TabIndex = 1;
|
||||||
|
//
|
||||||
|
// Okay_Button
|
||||||
|
//
|
||||||
|
Okay_Button.Dock = DockStyle.Fill;
|
||||||
|
Okay_Button.Location = new Point(753, 3);
|
||||||
|
Okay_Button.Name = "Okay_Button";
|
||||||
|
Okay_Button.Size = new Size(219, 63);
|
||||||
|
Okay_Button.TabIndex = 0;
|
||||||
|
Okay_Button.Text = "Okay";
|
||||||
|
Okay_Button.UseVisualStyleBackColor = true;
|
||||||
|
//
|
||||||
|
// Cancel_Button
|
||||||
|
//
|
||||||
|
Cancel_Button.Dock = DockStyle.Fill;
|
||||||
|
Cancel_Button.Location = new Point(978, 3);
|
||||||
|
Cancel_Button.Name = "Cancel_Button";
|
||||||
|
Cancel_Button.Size = new Size(219, 63);
|
||||||
|
Cancel_Button.TabIndex = 1;
|
||||||
|
Cancel_Button.Text = "Cancel";
|
||||||
|
Cancel_Button.UseVisualStyleBackColor = true;
|
||||||
|
//
|
||||||
|
// TextEdit_ContextMenuStrip
|
||||||
|
//
|
||||||
|
TextEdit_ContextMenuStrip.ImageScalingSize = new Size(32, 32);
|
||||||
|
TextEdit_ContextMenuStrip.Items.AddRange(new ToolStripItem[] { copyToClipboardToolStripMenuItem });
|
||||||
|
TextEdit_ContextMenuStrip.Name = "TextEdit_ContextMenuStrip";
|
||||||
|
TextEdit_ContextMenuStrip.Size = new Size(283, 42);
|
||||||
|
//
|
||||||
|
// copyToClipboardToolStripMenuItem
|
||||||
|
//
|
||||||
|
copyToClipboardToolStripMenuItem.Name = "copyToClipboardToolStripMenuItem";
|
||||||
|
copyToClipboardToolStripMenuItem.Size = new Size(282, 38);
|
||||||
|
copyToClipboardToolStripMenuItem.Text = "Copy to Clipboard";
|
||||||
|
copyToClipboardToolStripMenuItem.Click += copyToClipboardToolStripMenuItem_Click;
|
||||||
|
//
|
||||||
|
// TextEdit
|
||||||
|
//
|
||||||
|
AutoScaleDimensions = new SizeF(13F, 32F);
|
||||||
|
AutoScaleMode = AutoScaleMode.Font;
|
||||||
|
ClientSize = new Size(1206, 777);
|
||||||
|
Controls.Add(TextEdit_TableLayoutPanel1);
|
||||||
|
Icon = (Icon)resources.GetObject("$this.Icon");
|
||||||
|
Name = "TextEdit";
|
||||||
|
Text = "Edit";
|
||||||
|
TextEdit_TableLayoutPanel1.ResumeLayout(false);
|
||||||
|
TextEdit_TableLayoutPanel2.ResumeLayout(false);
|
||||||
|
TextEdit_ContextMenuStrip.ResumeLayout(false);
|
||||||
|
ResumeLayout(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private TableLayoutPanel TextEdit_TableLayoutPanel1;
|
||||||
|
private RichTextBox Content_RichTextBox;
|
||||||
|
private TableLayoutPanel TextEdit_TableLayoutPanel2;
|
||||||
|
private Button Okay_Button;
|
||||||
|
private Button Cancel_Button;
|
||||||
|
private ContextMenuStrip TextEdit_ContextMenuStrip;
|
||||||
|
private ToolStripMenuItem copyToClipboardToolStripMenuItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
using newcle.us.Utilities;
|
||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
|
namespace newcle.us.Forms
|
||||||
|
{
|
||||||
|
public partial class TextAreaForm : Form
|
||||||
|
{
|
||||||
|
public TextAreaForm() : this("Edit Content") { }
|
||||||
|
public TextAreaForm(string formTitle)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
// Set DialogResult on buttons
|
||||||
|
Okay_Button.DialogResult = DialogResult.OK;
|
||||||
|
Cancel_Button.DialogResult = DialogResult.Cancel;
|
||||||
|
|
||||||
|
// Optional: Set default Accept/Cancel buttons
|
||||||
|
//this.AcceptButton = Okay_Button;
|
||||||
|
this.CancelButton = CancelButton;
|
||||||
|
this.StartPosition = FormStartPosition.CenterParent;
|
||||||
|
this.Text = formTitle;
|
||||||
|
|
||||||
|
Content_RichTextBox.Text = string.Empty;
|
||||||
|
Content_RichTextBox.BackColor = Color.White;
|
||||||
|
}
|
||||||
|
|
||||||
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||||
|
public string? Content { get { return Content_RichTextBox.Text; } set { Content_RichTextBox.Text = value; } }
|
||||||
|
|
||||||
|
public void ReadOnly()
|
||||||
|
{
|
||||||
|
Content_RichTextBox.ReadOnly = true;
|
||||||
|
Okay_Button.Visible = false;
|
||||||
|
Cancel_Button.Text = "Okay";
|
||||||
|
Text = string.Format("{0} [readonly]", Text);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Content_RichTextBox_LinkClicked(object sender, LinkClickedEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Open the URL in the default browser
|
||||||
|
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = e.LinkText,
|
||||||
|
UseShellExecute = true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
DialogExtensions.GenericError(String.Format($"Failed to open link: {ex.Message}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copyToClipboardToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
Clipboard.SetText(Content ?? "");
|
||||||
|
DialogExtensions.GenericSuccess("Content successfully copied to clipboard");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -13,7 +13,7 @@ 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);
|
void FillTreeViewTasks(List<PTaskFS> items);
|
||||||
void InitTreeViewTasks();
|
void InitTreeViewTasks();
|
||||||
void InitDataGridViewProjectTasks();
|
void InitDataGridViewProjectTasks();
|
||||||
void FillDataGridViewProjectTasks(BindingSource tasks);
|
void FillDataGridViewProjectTasks(BindingSource tasks);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
namespace trakker.Models
|
namespace trakker.Models
|
||||||
{
|
{
|
||||||
|
|
||||||
public class Task
|
public class PTask
|
||||||
{
|
{
|
||||||
public enum RecursiveRoot
|
public enum RecursiveRoot
|
||||||
{
|
{
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
PARENT_TASK_ID = 1
|
PARENT_TASK_ID = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task()
|
public PTask()
|
||||||
{
|
{
|
||||||
TaskId = Guid.NewGuid().ToString();
|
TaskId = Guid.NewGuid().ToString();
|
||||||
DueDate = DateTime.Now;
|
DueDate = DateTime.Now;
|
||||||
|
|
@ -63,7 +63,7 @@
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TaskFS
|
public class PTaskFS
|
||||||
{
|
{
|
||||||
public string GUID { get; set; } = string.Empty;
|
public string GUID { get; set; } = string.Empty;
|
||||||
public string Node { get; set; } = string.Empty;
|
public string Node { get; set; } = string.Empty;
|
||||||
|
|
@ -83,9 +83,9 @@
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TaskComment
|
public class PTaskComment
|
||||||
{
|
{
|
||||||
public TaskComment()
|
public PTaskComment()
|
||||||
{
|
{
|
||||||
TaskCommentId = Guid.NewGuid().ToString();
|
TaskCommentId = Guid.NewGuid().ToString();
|
||||||
}
|
}
|
||||||
|
|
@ -66,10 +66,10 @@ namespace trakker.Services
|
||||||
var dbo = new TaskData(_connectionString);
|
var dbo = new TaskData(_connectionString);
|
||||||
_view.FillTreeViewTasks(dbo.GetFS());
|
_view.FillTreeViewTasks(dbo.GetFS());
|
||||||
}
|
}
|
||||||
internal void LoadTasksRecursive(string id, trakker.Models.Task.RecursiveRoot root)
|
internal void LoadTasksRecursive(string id, PTask.RecursiveRoot root)
|
||||||
{
|
{
|
||||||
var dbo = new TaskData(_connectionString);
|
var dbo = new TaskData(_connectionString);
|
||||||
BindingList<trakker.Models.Task> tasks = dbo.GetRecursive(id, root);
|
BindingList<PTask> tasks = dbo.GetRecursive(id, root);
|
||||||
_view.FillDataGridViewProjectTasks(new BindingSource { DataSource = tasks });
|
_view.FillDataGridViewProjectTasks(new BindingSource { DataSource = tasks });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace newcle.us.Utilities
|
||||||
|
{
|
||||||
|
public static class CurrencyExtensions
|
||||||
|
{
|
||||||
|
const int labelWidth = 35;
|
||||||
|
const int valueWidth = 20;
|
||||||
|
|
||||||
|
public static string PrintLine(string label, double amount)
|
||||||
|
{
|
||||||
|
string formatted = amount.ToString("C2", CultureInfo.GetCultureInfo("en-US"));
|
||||||
|
return String.Format($"{label,-labelWidth}{formatted,valueWidth}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string PrintSeparator(int width = 60, char ch = '-')
|
||||||
|
{
|
||||||
|
return new string(ch, width);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
namespace newcle.us.Utilities
|
||||||
|
{
|
||||||
|
internal class DateTimeExtensions
|
||||||
|
{
|
||||||
|
public static DateTime EndOfWeek(DateTime dt, DayOfWeek startOfWeek = DayOfWeek.Monday)
|
||||||
|
{
|
||||||
|
// Normalize to date (ignore time for week math)
|
||||||
|
var date = dt.Date;
|
||||||
|
|
||||||
|
// Compute offset from the configured startOfWeek
|
||||||
|
int diff = (7 + (date.DayOfWeek - startOfWeek)) % 7;
|
||||||
|
|
||||||
|
// Start of this week
|
||||||
|
var start = date.AddDays(-diff);
|
||||||
|
|
||||||
|
// End of this week: start + 6 days, then set to end-of-day
|
||||||
|
var endDate = start.AddDays(4);
|
||||||
|
|
||||||
|
// End-of-day with max precision (DateTime has 100-ns ticks)
|
||||||
|
return endDate.Date.AddDays(1).AddTicks(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
namespace newcle.us.Utilities
|
||||||
|
{
|
||||||
|
internal class DialogExtensions
|
||||||
|
{
|
||||||
|
public static DialogResult DeleteYesNo()
|
||||||
|
{
|
||||||
|
return MessageBox.Show(
|
||||||
|
"Are you sure you want to delete this record?",
|
||||||
|
"Delete confirmation",
|
||||||
|
MessageBoxButtons.YesNo,
|
||||||
|
MessageBoxIcon.Question,
|
||||||
|
MessageBoxDefaultButton.Button2
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
public static DialogResult GenericError(string message)
|
||||||
|
{
|
||||||
|
return GenericError(string.Empty, message);
|
||||||
|
}
|
||||||
|
public static DialogResult GenericError(string func, string message)
|
||||||
|
{
|
||||||
|
string title = "Error";
|
||||||
|
if (func != null)
|
||||||
|
{
|
||||||
|
title += String.Format(" - {0}", func);
|
||||||
|
}
|
||||||
|
return MessageBox.Show(
|
||||||
|
message,
|
||||||
|
title,
|
||||||
|
MessageBoxButtons.OK,
|
||||||
|
MessageBoxIcon.Error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
public static DialogResult GenericSuccess(string message)
|
||||||
|
{
|
||||||
|
return MessageBox.Show(
|
||||||
|
message,
|
||||||
|
"Success",
|
||||||
|
MessageBoxButtons.OK,
|
||||||
|
MessageBoxIcon.Information
|
||||||
|
);
|
||||||
|
}
|
||||||
|
public static DialogResult GenericInformational(string message)
|
||||||
|
{
|
||||||
|
return MessageBox.Show(
|
||||||
|
message,
|
||||||
|
"Information",
|
||||||
|
MessageBoxButtons.OK,
|
||||||
|
MessageBoxIcon.Information
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace newcle.us.Utilities
|
||||||
|
{
|
||||||
|
public class FileExtensions
|
||||||
|
{
|
||||||
|
public static string CreateTempFile(string content)
|
||||||
|
{
|
||||||
|
// Best practice: random name + .txt extension for gvim
|
||||||
|
string tempPath = Path.Combine(Path.GetTempPath(),
|
||||||
|
Path.GetRandomFileName() + ".txt");
|
||||||
|
|
||||||
|
File.WriteAllText(tempPath, content);
|
||||||
|
return tempPath;
|
||||||
|
}
|
||||||
|
public static bool EditFile(string filePath, string editor)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var startInfo = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = editor,
|
||||||
|
Arguments = $"\"{filePath}\"", // Quote the path
|
||||||
|
UseShellExecute = true,
|
||||||
|
WindowStyle = ProcessWindowStyle.Normal
|
||||||
|
};
|
||||||
|
|
||||||
|
using var process = Process.Start(startInfo);
|
||||||
|
if (process == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
process.WaitForExit(); // ← This blocks until gvim is closed
|
||||||
|
return process.ExitCode == 0; // Usually 0 on normal exit
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show($"Failed to launch gvim:\n{ex.Message}", "Error");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static void SafeDeleteTempFile(string path)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (File.Exists(path))
|
||||||
|
File.Delete(path);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Log but don't crash
|
||||||
|
Debug.WriteLine($"Failed to delete temp file: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
namespace newcle.us.Utilities
|
||||||
|
{
|
||||||
|
public class StringExtensions
|
||||||
|
{
|
||||||
|
public static string FormatRow(object[] values, int[] widths)
|
||||||
|
{
|
||||||
|
string format = "|";
|
||||||
|
for (int i = 0; i < values.Length; i++)
|
||||||
|
{
|
||||||
|
format += $" {{{i},-{widths[i]}}} |";
|
||||||
|
}
|
||||||
|
return String.Format(format, values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue