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 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) { } //private void button1_Click(object sender, EventArgs e) //{ // var dialog = new ClientForm(new Client()); // if (dialog.ShowDialog(this) == DialogResult.OK) // { // Client client = dialog.Client; // ClientData clientData = new ClientData(connectionString); // try // { // clientData.Upsert(client); // } // catch (Exception ex) // { // MessageBox.Show($"Error saving client: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); // } // } //} } }