using Microsoft.Data.Sqlite;
using System.ComponentModel;
using trakker.Data;
using trakker.Forms;
using trakker.Interfaces;
using trakker.Models;
using trakker.Services;
namespace trakker
{
public partial class MainForm : Form, IMainForm
{
//private readonly string _dbversion = "[N.N.N]";
private string connectionString = string.Empty;
readonly MainCtrl _ctrl;
///
/// Initializes a new instance of the class.
/// Sets up the form's controls and event handlers by calling
/// which is generated by the designer.
///
public MainForm()
{
InitializeComponent();
// build connection string that will be used for database connections
// ------------------------------------------------------------------------
var dbPath = Path.Combine(AppContext.BaseDirectory, "trakker.db");
connectionString = new SqliteConnectionStringBuilder
{
DataSource = dbPath,
Mode = SqliteOpenMode.ReadWriteCreate,
Cache = SqliteCacheMode.Shared
}.ToString();
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);
}
///
/// Handles the Click event of the Exit menu item. When invoked, this
/// method terminates the application.
///
/// The source of the event.
/// Event data associated with the click event.
private void MainForm_Exit_MenuItem_Click(object sender, EventArgs e)
{
Application.Exit();
}
public void InitDataGridViewClients(BindingList clients)
{
dataGridViewClients.AllowUserToAddRows = true;
dataGridViewClients.AllowUserToDeleteRows = true;
dataGridViewClients.AutoGenerateColumns = false;
dataGridViewClients.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
dataGridViewClients.BackgroundColor = Color.White;
dataGridViewClients.ColumnHeadersVisible = true;
dataGridViewClients.DataSource = clients;
dataGridViewClients.MultiSelect = false;
dataGridViewClients.RowHeadersVisible = false;
dataGridViewClients.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
dataGridViewClients.Columns.Clear();
{
var textColumn = new DataGridViewTextBoxColumn
{
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill,
DataPropertyName = "Name",
Name = "Name",
Visible = true,
};
dataGridViewClients.Columns.Add(textColumn);
}
{
var textColumn = new DataGridViewTextBoxColumn
{
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill,
DataPropertyName = "Company",
Name = "Company",
Visible = true,
};
dataGridViewClients.Columns.Add(textColumn);
}
{
var textColumn = new DataGridViewTextBoxColumn
{
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill,
DataPropertyName = "Email",
Name = "Email",
Visible = true,
};
dataGridViewClients.Columns.Add(textColumn);
}
{
var textColumn = new DataGridViewTextBoxColumn
{
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill,
DataPropertyName = "Phone",
Name = "Phone",
Visible = true,
};
dataGridViewClients.Columns.Add(textColumn);
}
dataGridViewClients.DoubleClick += (s, e) =>
{
if (dataGridViewClients.SelectedRows.Count > 0)
{
var selectedClient = dataGridViewClients.SelectedRows[0].DataBoundItem as Client;
if (selectedClient != null)
{
var dialog = new ClientForm(selectedClient, _ctrl.GetLOV("state"));
if (dialog.ShowDialog(this) == DialogResult.OK)
{
Client client = dialog.Client;
ClientData clientData = new ClientData(connectionString);
try
{
clientData.Upsert(client);
dataGridViewClients.Refresh(); // Refresh the DataGridView to reflect changes
}
catch (Exception ex)
{
MessageBox.Show($"Error saving client: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
}
};
dataGridViewClients.SelectionChanged += (s, e) =>
{
if (dataGridViewClients.SelectedRows.Count > 0)
{
var selectedClient = dataGridViewClients.SelectedRows[0].DataBoundItem as Client;
if (selectedClient != null)
{
// Handle the selected client as needed
// MessageBox.Show($"Selected Client: {selectedClient.AddressStreet}", "Client Selected", MessageBoxButtons.OK, MessageBoxIcon.Information );
}
}
};
}
public void FillDataGridViewProjects(BindingSource projects)
{
dataGridViewProjects.DataSource = projects;
}
public void InitDataGridViewProjects()
{
dataGridViewProjects.AllowUserToAddRows = true;
dataGridViewProjects.AllowUserToDeleteRows = true;
dataGridViewProjects.AutoGenerateColumns = false;
dataGridViewProjects.BackgroundColor = Color.White;
dataGridViewProjects.ColumnHeadersVisible = true;
dataGridViewProjects.MultiSelect = false;
dataGridViewProjects.RowHeadersVisible = false;
dataGridViewProjects.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
dataGridViewProjects.Columns.Clear();
{
var textColumn = new DataGridViewTextBoxColumn
{
AutoSizeMode = DataGridViewAutoSizeColumnMode.None,
DataPropertyName = "ProjectName",
Name = "Project Name",
Visible = true,
Width = 350,
};
dataGridViewProjects.Columns.Add(textColumn);
}
{
var textColumn = new DataGridViewTextBoxColumn
{
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill,
DataPropertyName = "ClientName",
Name = "Client Name",
Visible = true,
};
dataGridViewProjects.Columns.Add(textColumn);
}
{
var textColumn = new DataGridViewTextBoxColumn
{
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill,
DataPropertyName = "StartDate",
DefaultCellStyle = { Format = "MM/dd/yyyy" },
Name = "Start Date",
Visible = true,
};
dataGridViewProjects.Columns.Add(textColumn);
}
{
var textColumn = new DataGridViewTextBoxColumn
{
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill,
DataPropertyName = "EndDate",
DefaultCellStyle = { Format = "MM/dd/yyyy" },
Name = "End Date",
Visible = true,
};
dataGridViewProjects.Columns.Add(textColumn);
}
{
var textColumn = new DataGridViewTextBoxColumn
{
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill,
DataPropertyName = "HourlyRate",
Name = "Hourly Rate",
Visible = true,
};
textColumn.DefaultCellStyle.Format = "$#,##0.00";
textColumn.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
textColumn.HeaderCell.Style.Alignment = DataGridViewContentAlignment.MiddleRight;
dataGridViewProjects.Columns.Add(textColumn);
}
{
var textColumn = new DataGridViewTextBoxColumn
{
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill,
DataPropertyName = "Budget",
Name = "Budget",
Visible = true,
};
textColumn.DefaultCellStyle.Format = "$#,##0.00";
textColumn.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
textColumn.HeaderCell.Style.Alignment = DataGridViewContentAlignment.MiddleRight;
dataGridViewProjects.Columns.Add(textColumn);
}
{
var textColumn = new DataGridViewTextBoxColumn
{
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill,
DataPropertyName = "StatusName",
Name = "Status",
Visible = true,
};
dataGridViewProjects.Columns.Add(textColumn);
}
dataGridViewProjects.DoubleClick += (s, e) =>
{
if (dataGridViewProjects.SelectedRows.Count > 0)
{
var selectedIdx = dataGridViewProjects.SelectedRows[0].Index;
var selectedProject = dataGridViewProjects.SelectedRows[0].DataBoundItem as Project;
if (selectedProject != null)
{
var dialog = new ProjectForm(selectedProject, _ctrl.GetClients(), _ctrl.GetLOV("project.status"));
if (dialog.ShowDialog(this) == DialogResult.OK)
{
Project project = dialog.Project;
ProjectData projectData = new ProjectData(connectionString);
try
{
projectData.Upsert(project);
_ctrl.LoadProjects(); // Reload projects to update the DataGridView with any changes
dataGridViewProjects.ClearSelection();
if (selectedIdx >= 0 && selectedIdx < dataGridViewProjects.Rows.Count)
{
dataGridViewProjects.Rows[selectedIdx].Selected = true;
}
}
catch (Exception ex)
{
MessageBox.Show($"Error saving project: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
return;
}
}
};
dataGridViewClients.SelectionChanged += (s, e) =>
{
if (dataGridViewClients.SelectedRows.Count > 0)
{
var selectedClient = dataGridViewClients.SelectedRows[0].DataBoundItem as Client;
if (selectedClient != null)
{
// Handle the selected client as needed
// MessageBox.Show($"Selected Client: {selectedClient.AddressStreet}", "Client Selected", MessageBoxButtons.OK, MessageBoxIcon.Information );
}
}
};
}
public void InitTreeViewTasks()
{
// Basic TreeView configuration
treeViewTasks1.BeginUpdate();
treeViewTasks1.Nodes.Clear();
treeViewTasks1.ShowLines = true;
treeViewTasks1.ShowRootLines = true;
treeViewTasks1.HideSelection = false;
treeViewTasks1.EndUpdate();
// When a tree node is clicked, fetch and show notebooks for that folder
treeViewTasks1.NodeMouseClick += (sender, e) =>
{
TaskFS? selectedNode = (TaskFS?)e.Node.Tag ?? new TaskFS();
if (selectedNode?.Parent == "/" || selectedNode?.GUID == "/")
{
_ctrl.LoadTasksRecursive(selectedNode.ProjectId, trakker.Models.Task.RecursiveRoot.PARENT_TASK_ID); // Load all tasks for the project when root node is clicked
return;
}
_ctrl.LoadTasksRecursive(selectedNode?.GUID ?? "/", trakker.Models.Task.RecursiveRoot.TASK_ID); // Load all tasks for the project when root node is clicked
};
}
public void FillTreeViewTasks(List 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>();
foreach (var it in items)
{
var key = NormalizeKey(it.Parent);
if (!lookup.TryGetValue(key, out var list))
{
list = new List();
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)
{
}
public void InitDataGridViewProjectTasks()
{
dataGridViewProjectTasks.AllowUserToAddRows = false;
dataGridViewProjectTasks.AllowUserToDeleteRows = false;
dataGridViewProjectTasks.AutoGenerateColumns = false;
dataGridViewProjectTasks.BackgroundColor = Color.White;
dataGridViewProjectTasks.ColumnHeadersDefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleLeft;
dataGridViewProjectTasks.ColumnHeadersDefaultCellStyle.WrapMode = DataGridViewTriState.False;
dataGridViewProjectTasks.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
dataGridViewProjectTasks.ColumnHeadersVisible = true;
dataGridViewProjectTasks.MultiSelect = false;
dataGridViewProjectTasks.ReadOnly = true;
dataGridViewProjectTasks.RowHeadersVisible = false;
dataGridViewProjectTasks.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
// Then add your columns...
dataGridViewProjectTasks.Columns.Clear();
{
var textColumn = new DataGridViewTextBoxColumn
{
AutoSizeMode = DataGridViewAutoSizeColumnMode.None,
DataPropertyName = "Title",
Name = "Title",
Visible = true,
Width = 350,
};
dataGridViewProjectTasks.Columns.Add(textColumn);
}
// 1. Status - Most important for quick visual scanning
{
var textColumn = new DataGridViewTextBoxColumn
{
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill,
DataPropertyName = "Status",
Name = "Status",
Visible = true,
};
dataGridViewProjectTasks.Columns.Add(textColumn);
}
// 2. Priority - Right after status
{
var textColumn = new DataGridViewTextBoxColumn
{
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill,
DataPropertyName = "Priority",
Name = "Priority",
Visible = true,
};
dataGridViewProjectTasks.Columns.Add(textColumn);
}
// 3. Due Date - Time sensitivity
{
var textColumn = new DataGridViewTextBoxColumn
{
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill,
DataPropertyName = "DueDate",
DefaultCellStyle = { Format = "MM/dd/yyyy" },
Name = "Due Date",
Visible = true,
};
dataGridViewProjectTasks.Columns.Add(textColumn);
}
// 4. Estimated Hours
{
var textColumn = new DataGridViewTextBoxColumn
{
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill,
DataPropertyName = "EstimatedHours",
Name = "Est Hours",
Visible = true,
};
textColumn.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
textColumn.HeaderCell.Style.Alignment = DataGridViewContentAlignment.MiddleRight;
dataGridViewProjectTasks.Columns.Add(textColumn);
}
// 5. Actual Hours
{
var textColumn = new DataGridViewTextBoxColumn
{
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill,
DataPropertyName = "ActualHours",
Name = "Act Hours",
Visible = true,
};
textColumn.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
textColumn.HeaderCell.Style.Alignment = DataGridViewContentAlignment.MiddleRight;
dataGridViewProjectTasks.Columns.Add(textColumn);
}
// 6. Hourly Rate - Financial info at the end
{
var textColumn = new DataGridViewTextBoxColumn
{
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill,
DataPropertyName = "HourlyRate",
Name = "Hourly Rate",
Visible = true,
};
textColumn.DefaultCellStyle.Format = "$#,##0.00";
textColumn.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
textColumn.HeaderCell.Style.Alignment = DataGridViewContentAlignment.MiddleRight;
dataGridViewProjectTasks.Columns.Add(textColumn);
}
dataGridViewProjectTasks.Click += (s, e) =>
{
if (dataGridViewProjectTasks.SelectedRows.Count > 0)
{
var selectedIdx = dataGridViewProjectTasks.SelectedRows[0].Index;
var selectedProject = dataGridViewProjectTasks.SelectedRows[0].DataBoundItem as Project;
if (selectedProject != null)
{
//var dialog = new ProjectForm(selectedProject, _ctrl.GetClients(), _ctrl.GetLOV("project.status"));
//if (dialog.ShowDialog(this) == DialogResult.OK)
//{
// Project project = dialog.Project;
// ProjectData projectData = new ProjectData(connectionString);
// try
// {
// projectData.Upsert(project);
// _ctrl.LoadProjects(); // Reload projects to update the DataGridView with any changes
// dataGridViewProjectTasks.ClearSelection();
// if (selectedIdx >= 0 && selectedIdx < dataGridViewProjectTasks.Rows.Count)
// {
// dataGridViewProjectTasks.Rows[selectedIdx].Selected = true;
// }
// }
// catch (Exception ex)
// {
// MessageBox.Show($"Error saving project: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
// }
//}
//return;
}
}
};
//dataGridViewClients.SelectionChanged += (s, e) =>
//{
// if (dataGridViewClients.SelectedRows.Count > 0)
// {
// var selectedClient = dataGridViewClients.SelectedRows[0].DataBoundItem as Client;
// if (selectedClient != null)
// {
// // Handle the selected client as needed
// // MessageBox.Show($"Selected Client: {selectedClient.AddressStreet}", "Client Selected", MessageBoxButtons.OK, MessageBoxIcon.Information );
// }
// }
//};
}
public void FillDataGridViewProjectTasks(BindingSource tasks)
{
dataGridViewProjectTasks.DataSource = tasks;
dataGridViewProjectTasks.Refresh();
}
}
}