Continued development

This commit is contained in:
c0d3.m0nk3y 2026-05-12 09:40:36 -04:00
parent 9119eca1b4
commit 74b01247ed
14 changed files with 1684 additions and 46 deletions

View File

@ -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),

View File

@ -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 == "/")
{ {

View File

@ -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

147
Forms/TextAreaForm.Designer.cs generated Normal file
View File

@ -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;
}
}

63
Forms/TextAreaForm.cs Normal file
View File

@ -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");
}
}
}

1256
Forms/TextAreaForm.resx Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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);

View File

@ -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();
} }

View File

@ -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 });
} }

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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
);
}
}
}

View File

@ -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}");
}
}
}
}

View File

@ -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);
}
}
}