From 691a999baebf7a805956c08d4dcefc90b0c47a8d Mon Sep 17 00:00:00 2001 From: "c0d3.m0nk3y" Date: Fri, 8 May 2026 19:22:00 -0400 Subject: [PATCH] Continued development --- Data/ClientData.cs | 38 --- Data/DataAccess.cs | 39 +++ Data/ProjectData.cs | 232 ++++++++++++++++++ Forms/MainForm.Designer.cs | 114 ++++++--- Forms/MainForm.cs | 176 ++++++++++++-- Forms/ProjectForm.Designer.cs | 435 ++++++++++++++++++++++++++++++++++ Forms/ProjectForm.cs | 120 ++++++++++ Forms/ProjectForm.resx | 120 ++++++++++ Interfaces/IMainForm.cs | 2 + Models/Project.cs | 28 +-- Services/MainCtrl.cs | 29 ++- backup.txt | 285 ++++++++++++++++++++++ 12 files changed, 1502 insertions(+), 116 deletions(-) create mode 100644 Data/ProjectData.cs create mode 100644 Forms/ProjectForm.Designer.cs create mode 100644 Forms/ProjectForm.cs create mode 100644 Forms/ProjectForm.resx create mode 100644 backup.txt diff --git a/Data/ClientData.cs b/Data/ClientData.cs index ffbc6a5..a00e964 100644 --- a/Data/ClientData.cs +++ b/Data/ClientData.cs @@ -225,44 +225,6 @@ namespace trakker.Data return result; } - /// - /// Executes arbitrary, ad-hoc SQL against the database inside a transaction. - /// - /// A SQL statement to execute. The caller is responsible for - /// ensuring the SQL is safe and properly parameterized to avoid SQL injection. - /// - /// This method is intended for one-off maintenance or administrative commands. - /// It does not return any result; if a scalar value is produced by the SQL, - /// the current implementation captures it but does not expose it to the caller. - /// - public void Adhoc(string sql) - { - using var conn = OpenConnection(); - using var tx = conn.BeginTransaction(); - - int? result = 0; - try - { - using (var cmd = conn.CreateCommand()) - { - cmd.Transaction = tx; - cmd.CommandText = sql; - cmd.ExecuteNonQuery(); - } - - using var idCmd = conn.CreateCommand(); - idCmd.Transaction = tx; - result = (int?)idCmd.ExecuteScalar() ; - - tx.Commit(); - } - catch - { - tx.Rollback(); - throw; - } - - } } } diff --git a/Data/DataAccess.cs b/Data/DataAccess.cs index 2c02f52..f111997 100644 --- a/Data/DataAccess.cs +++ b/Data/DataAccess.cs @@ -25,5 +25,44 @@ namespace trakker.Data conn.Open(); return conn; } + + /// + /// Executes arbitrary, ad-hoc SQL against the database inside a transaction. + /// + /// A SQL statement to execute. The caller is responsible for + /// ensuring the SQL is safe and properly parameterized to avoid SQL injection. + /// + /// This method is intended for one-off maintenance or administrative commands. + /// It does not return any result; if a scalar value is produced by the SQL, + /// the current implementation captures it but does not expose it to the caller. + /// + public void Adhoc(string sql) + { + using var conn = OpenConnection(); + using var tx = conn.BeginTransaction(); + + int? result = 0; + try + { + using (var cmd = conn.CreateCommand()) + { + cmd.Transaction = tx; + cmd.CommandText = sql; + cmd.ExecuteNonQuery(); + } + + using var idCmd = conn.CreateCommand(); + idCmd.Transaction = tx; + result = (int?)idCmd.ExecuteScalar() ; + + tx.Commit(); + } + catch + { + tx.Rollback(); + throw; + } + + } } } diff --git a/Data/ProjectData.cs b/Data/ProjectData.cs new file mode 100644 index 0000000..589ffe4 --- /dev/null +++ b/Data/ProjectData.cs @@ -0,0 +1,232 @@ +using System.ComponentModel; +using trakker.Models; + +namespace trakker.Data +{ + /// + /// Provides data access methods for the entity. + /// This class encapsulates database operations such as upsert, delete and ad-hoc + /// SQL execution for projects. It inherits from which + /// provides connection management. + /// + internal class ProjectData(string connectionString) : DataAccess(connectionString) + { + public BindingList Get(string? projectId = null) + { + var results = new BindingList(); + + string whereClause = "1 = 1"; + if (projectId != null) + { + whereClause = "p.project_id = $project_id"; + } + + string sql = $@" + SELECT + p.project_id, + p.client_id, + p.project_code, + c.name AS client_name, + p.name AS project_name, + p.description, + p.start_date, + p.end_date, + p.budget, + p.status, + p.hourly_rate, + p.notes, + p.created_at, + p.updated_at + FROM projects p + LEFT JOIN clients c ON p.client_id = c.client_id + WHERE p.status = 'active' + AND {whereClause} + ORDER BY p.start_date DESC, p.name ASC; + ; + "; + + using var conn = OpenConnection(); + using var cmd = conn.CreateCommand(); + cmd.CommandText = sql; + + if (projectId != null) + { + cmd.Parameters.AddWithValue("$project_id", projectId); + } + using var reader = cmd.ExecuteReader(); + + var _var1 = reader.GetOrdinal("project_id"); + var _var2 = reader.GetOrdinal("client_id"); + var _var3 = reader.GetOrdinal("project_code"); + var _var4 = reader.GetOrdinal("client_name"); + var _var5 = reader.GetOrdinal("project_name"); + var _var6 = reader.GetOrdinal("description"); + var _var7 = reader.GetOrdinal("start_date"); + var _var8 = reader.GetOrdinal("end_date"); + var _var9 = reader.GetOrdinal("budget"); + var _var10 = reader.GetOrdinal("status"); + var _var11 = reader.GetOrdinal("hourly_rate"); + var _var12 = reader.GetOrdinal("notes"); + var _var13 = reader.GetOrdinal("created_at"); + var _var14 = reader.GetOrdinal("updated_at"); + + while (reader.Read()) + { + results.Add(new Project + { + ProjectId = reader.GetString(_var1), + ClientId = reader.GetString(_var2), + ProjectCode = reader.GetString(_var3), + ClientName = reader.GetString(_var4), + ProjectName = reader.GetString(_var5), + Description = reader.GetString(_var6), + StartDate = reader.IsDBNull(_var7) ? null : reader.GetDateTime(_var7), + EndDate = reader.IsDBNull(_var8) ? null : reader.GetDateTime(_var8), + Budget = reader.GetDecimal(_var9), + Status = reader.GetString(_var10), + HourlyRate = reader.GetDecimal(_var11), + Notes = reader.GetString(_var12), + CreatedAt = reader.GetDateTime(_var13), + UpdatedAt = reader.GetDateTime(_var14), + }); + } + + return results; + } + + /// + /// Inserts a new project record or updates an existing one (upsert) using + /// the provided model. This method executes + /// a single SQL statement inside a transaction and will commit on + /// success or roll back on failure. + /// + /// The model to insert or update. Must not be null. + /// + /// The SQL statement uses an ON CONFLICT clause to perform the update when a + /// matching project_id already exists. Parameter names correspond to the + /// project model property names. + /// + public void Upsert(Project project) + { + const string sql = @" + INSERT INTO projects ( + project_id, + client_id, + name, + description, + start_date, + end_date, + budget, + status, + hourly_rate, + notes + ) + VALUES ( + $project_id, + $client_id, + $name, + $description, + $start_date, + $end_date, + $budget, + $status, + $hourly_rate, + $notes + ) + ON CONFLICT (project_id) DO UPDATE SET + client_id = excluded.client_id, + name = excluded.name, + description = excluded.description, + start_date = excluded.start_date, + end_date = excluded.end_date, + budget = excluded.budget, + status = excluded.status, + hourly_rate = excluded.hourly_rate, + notes = excluded.notes, + updated_at = CURRENT_TIMESTAMP; + "; + + using var conn = OpenConnection(); + using var tx = conn.BeginTransaction(); + + try + { + using (var cmd = conn.CreateCommand()) + { + cmd.Transaction = tx; + cmd.CommandText = sql; + cmd.Parameters.AddWithValue("$project_id", project.ProjectId); + cmd.Parameters.AddWithValue("$client_id", project.ClientId); + cmd.Parameters.AddWithValue("$name", project.ProjectName); + cmd.Parameters.AddWithValue("$description", project.Description); + cmd.Parameters.AddWithValue("$start_date", project.StartDate); + cmd.Parameters.AddWithValue("$end_date", project.EndDate); + cmd.Parameters.AddWithValue("$budget", project.Budget); + cmd.Parameters.AddWithValue("$status", project.Status); + cmd.Parameters.AddWithValue("$hourly_rate", project.HourlyRate); + cmd.Parameters.AddWithValue("$notes", project.Notes); + cmd.ExecuteNonQuery(); + } + + tx.Commit(); + } + catch + { + tx.Rollback(); + throw; + } + + } + /// + /// Deletes the project with the specified from the + /// database. + /// + /// The identifier of the project to delete. + /// An optional integer representing any scalar value returned by the + /// command executed after deletion (if applicable). May be null. + /// + /// The method executes within a transaction. The current implementation attempts + /// to read a scalar value after the delete; that value depends on surrounding + /// database triggers or commands and may be null. + /// + public int? Delete(string projectId) + { + const string sql = @" + DELETE FROM + projects + WHERE + project_id = $project_id + ; + "; + + using var conn = OpenConnection(); + using var tx = conn.BeginTransaction(); + + int? result = 0; + try + { + using (var cmd = conn.CreateCommand()) + { + cmd.Transaction = tx; + cmd.CommandText = sql; + cmd.Parameters.AddWithValue("$project_id", projectId); + cmd.ExecuteNonQuery(); + } + + using var idCmd = conn.CreateCommand(); + idCmd.Transaction = tx; + result = (int?)idCmd.ExecuteScalar() ; + + tx.Commit(); + } + catch + { + tx.Rollback(); + throw; + } + + return result; + } + + } +} diff --git a/Forms/MainForm.Designer.cs b/Forms/MainForm.Designer.cs index 5e6deed..0634acb 100644 --- a/Forms/MainForm.Designer.cs +++ b/Forms/MainForm.Designer.cs @@ -34,16 +34,20 @@ MainForm_StatusStrip = new StatusStrip(); tabControlMainForm = new TabControl(); MainForm_TabPage1 = new TabPage(); - button1 = new Button(); MainForm_TabPage2 = new TabPage(); - tableLayoutPanel1Tab2 = new TableLayoutPanel(); + tableLayoutPanelClients1 = new TableLayoutPanel(); dataGridViewClients = new DataGridView(); + MainForm_TabPage3 = new TabPage(); + tableLayoutPanelProjects1 = new TableLayoutPanel(); + dataGridViewProjects = new DataGridView(); MainForm_MenuStrip.SuspendLayout(); tabControlMainForm.SuspendLayout(); - MainForm_TabPage1.SuspendLayout(); MainForm_TabPage2.SuspendLayout(); - tableLayoutPanel1Tab2.SuspendLayout(); + tableLayoutPanelClients1.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)dataGridViewClients).BeginInit(); + MainForm_TabPage3.SuspendLayout(); + tableLayoutPanelProjects1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)dataGridViewProjects).BeginInit(); SuspendLayout(); // // MainForm_MenuStrip @@ -52,7 +56,7 @@ MainForm_MenuStrip.Items.AddRange(new ToolStripItem[] { fileToolStripMenuItem }); MainForm_MenuStrip.Location = new Point(0, 0); MainForm_MenuStrip.Name = "MainForm_MenuStrip"; - MainForm_MenuStrip.Size = new Size(1343, 40); + MainForm_MenuStrip.Size = new Size(1878, 40); MainForm_MenuStrip.TabIndex = 0; MainForm_MenuStrip.Text = "menuStrip1"; // @@ -73,9 +77,9 @@ // MainForm_StatusStrip // MainForm_StatusStrip.ImageScalingSize = new Size(32, 32); - MainForm_StatusStrip.Location = new Point(0, 983); + MainForm_StatusStrip.Location = new Point(0, 1060); MainForm_StatusStrip.Name = "MainForm_StatusStrip"; - MainForm_StatusStrip.Size = new Size(1343, 22); + MainForm_StatusStrip.Size = new Size(1878, 22); MainForm_StatusStrip.TabIndex = 1; MainForm_StatusStrip.Text = "MainForm_StatusStrip"; // @@ -83,37 +87,27 @@ // tabControlMainForm.Controls.Add(MainForm_TabPage1); tabControlMainForm.Controls.Add(MainForm_TabPage2); + tabControlMainForm.Controls.Add(MainForm_TabPage3); tabControlMainForm.Dock = DockStyle.Fill; tabControlMainForm.Location = new Point(0, 40); tabControlMainForm.Name = "tabControlMainForm"; tabControlMainForm.SelectedIndex = 0; - tabControlMainForm.Size = new Size(1343, 943); + tabControlMainForm.Size = new Size(1878, 1020); tabControlMainForm.TabIndex = 2; // // MainForm_TabPage1 // - MainForm_TabPage1.Controls.Add(button1); MainForm_TabPage1.Location = new Point(8, 46); MainForm_TabPage1.Name = "MainForm_TabPage1"; MainForm_TabPage1.Padding = new Padding(3); - MainForm_TabPage1.Size = new Size(1327, 889); + MainForm_TabPage1.Size = new Size(1862, 966); MainForm_TabPage1.TabIndex = 0; MainForm_TabPage1.Text = "Tab 1"; MainForm_TabPage1.UseVisualStyleBackColor = true; // - // button1 - // - button1.Location = new Point(39, 47); - button1.Name = "button1"; - button1.Size = new Size(150, 46); - button1.TabIndex = 0; - button1.Text = "button1"; - button1.UseVisualStyleBackColor = true; - button1.Click += button1_Click; - // // MainForm_TabPage2 // - MainForm_TabPage2.Controls.Add(tableLayoutPanel1Tab2); + MainForm_TabPage2.Controls.Add(tableLayoutPanelClients1); MainForm_TabPage2.Location = new Point(8, 46); MainForm_TabPage2.Name = "MainForm_TabPage2"; MainForm_TabPage2.Padding = new Padding(3); @@ -122,20 +116,20 @@ MainForm_TabPage2.Text = "Tab 2"; MainForm_TabPage2.UseVisualStyleBackColor = true; // - // tableLayoutPanel1Tab2 + // tableLayoutPanelClients1 // - tableLayoutPanel1Tab2.ColumnCount = 1; - tableLayoutPanel1Tab2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F)); - tableLayoutPanel1Tab2.Controls.Add(dataGridViewClients, 0, 1); - tableLayoutPanel1Tab2.Dock = DockStyle.Fill; - tableLayoutPanel1Tab2.Location = new Point(3, 3); - tableLayoutPanel1Tab2.Name = "tableLayoutPanel1Tab2"; - tableLayoutPanel1Tab2.RowCount = 3; - tableLayoutPanel1Tab2.RowStyles.Add(new RowStyle(SizeType.Absolute, 1F)); - tableLayoutPanel1Tab2.RowStyles.Add(new RowStyle(SizeType.Percent, 100F)); - tableLayoutPanel1Tab2.RowStyles.Add(new RowStyle(SizeType.Absolute, 200F)); - tableLayoutPanel1Tab2.Size = new Size(1321, 883); - tableLayoutPanel1Tab2.TabIndex = 0; + tableLayoutPanelClients1.ColumnCount = 1; + tableLayoutPanelClients1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F)); + tableLayoutPanelClients1.Controls.Add(dataGridViewClients, 0, 1); + tableLayoutPanelClients1.Dock = DockStyle.Fill; + tableLayoutPanelClients1.Location = new Point(3, 3); + tableLayoutPanelClients1.Name = "tableLayoutPanelClients1"; + tableLayoutPanelClients1.RowCount = 3; + tableLayoutPanelClients1.RowStyles.Add(new RowStyle(SizeType.Absolute, 1F)); + tableLayoutPanelClients1.RowStyles.Add(new RowStyle(SizeType.Percent, 100F)); + tableLayoutPanelClients1.RowStyles.Add(new RowStyle(SizeType.Absolute, 200F)); + tableLayoutPanelClients1.Size = new Size(1321, 883); + tableLayoutPanelClients1.TabIndex = 0; // // dataGridViewClients // @@ -150,11 +144,49 @@ dataGridViewClients.Size = new Size(1315, 676); dataGridViewClients.TabIndex = 0; // + // MainForm_TabPage3 + // + MainForm_TabPage3.Controls.Add(tableLayoutPanelProjects1); + MainForm_TabPage3.Location = new Point(8, 46); + MainForm_TabPage3.Name = "MainForm_TabPage3"; + MainForm_TabPage3.Size = new Size(1327, 889); + MainForm_TabPage3.TabIndex = 2; + MainForm_TabPage3.Text = "Tab 3"; + MainForm_TabPage3.UseVisualStyleBackColor = true; + // + // tableLayoutPanelProjects1 + // + tableLayoutPanelProjects1.ColumnCount = 1; + tableLayoutPanelProjects1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F)); + tableLayoutPanelProjects1.Controls.Add(dataGridViewProjects, 0, 1); + tableLayoutPanelProjects1.Dock = DockStyle.Fill; + tableLayoutPanelProjects1.Location = new Point(0, 0); + tableLayoutPanelProjects1.Name = "tableLayoutPanelProjects1"; + tableLayoutPanelProjects1.RowCount = 3; + tableLayoutPanelProjects1.RowStyles.Add(new RowStyle(SizeType.Absolute, 1F)); + tableLayoutPanelProjects1.RowStyles.Add(new RowStyle(SizeType.Percent, 100F)); + tableLayoutPanelProjects1.RowStyles.Add(new RowStyle(SizeType.Absolute, 200F)); + tableLayoutPanelProjects1.Size = new Size(1327, 889); + tableLayoutPanelProjects1.TabIndex = 1; + // + // dataGridViewProjects + // + dataGridViewProjects.AllowUserToAddRows = false; + dataGridViewProjects.AllowUserToDeleteRows = false; + dataGridViewProjects.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize; + dataGridViewProjects.Dock = DockStyle.Fill; + dataGridViewProjects.Location = new Point(3, 4); + dataGridViewProjects.Name = "dataGridViewProjects"; + dataGridViewProjects.ReadOnly = true; + dataGridViewProjects.RowHeadersWidth = 82; + dataGridViewProjects.Size = new Size(1321, 682); + dataGridViewProjects.TabIndex = 0; + // // MainForm // AutoScaleDimensions = new SizeF(13F, 32F); AutoScaleMode = AutoScaleMode.Font; - ClientSize = new Size(1343, 1005); + ClientSize = new Size(1878, 1082); Controls.Add(tabControlMainForm); Controls.Add(MainForm_StatusStrip); Controls.Add(MainForm_MenuStrip); @@ -164,10 +196,12 @@ MainForm_MenuStrip.ResumeLayout(false); MainForm_MenuStrip.PerformLayout(); tabControlMainForm.ResumeLayout(false); - MainForm_TabPage1.ResumeLayout(false); MainForm_TabPage2.ResumeLayout(false); - tableLayoutPanel1Tab2.ResumeLayout(false); + tableLayoutPanelClients1.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)dataGridViewClients).EndInit(); + MainForm_TabPage3.ResumeLayout(false); + tableLayoutPanelProjects1.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)dataGridViewProjects).EndInit(); ResumeLayout(false); PerformLayout(); } @@ -181,8 +215,10 @@ private TabPage MainForm_TabPage2; private ToolStripMenuItem fileToolStripMenuItem; private ToolStripMenuItem MainForm_Exit_MenuItem; - private Button button1; - private TableLayoutPanel tableLayoutPanel1Tab2; + private TableLayoutPanel tableLayoutPanelClients1; private DataGridView dataGridViewClients; + private TabPage MainForm_TabPage3; + private TableLayoutPanel tableLayoutPanelProjects1; + private DataGridView dataGridViewProjects; } } diff --git a/Forms/MainForm.cs b/Forms/MainForm.cs index b2f2ba0..7aba030 100644 --- a/Forms/MainForm.cs +++ b/Forms/MainForm.cs @@ -36,6 +36,7 @@ namespace trakker tabControlMainForm.TabPages[0].Text = " Home "; tabControlMainForm.TabPages[1].Text = " Clients "; + tabControlMainForm.TabPages[2].Text = " Projects "; _ctrl = new Services.MainCtrl(this, connectionString); } @@ -58,11 +59,11 @@ namespace trakker dataGridViewClients.AutoGenerateColumns = false; dataGridViewClients.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill; dataGridViewClients.BackgroundColor = Color.White; - dataGridViewClients.SelectionMode = DataGridViewSelectionMode.FullRowSelect; - dataGridViewClients.RowHeadersVisible = false; dataGridViewClients.ColumnHeadersVisible = true; - dataGridViewClients.MultiSelect = false; dataGridViewClients.DataSource = clients; + dataGridViewClients.MultiSelect = false; + dataGridViewClients.RowHeadersVisible = false; + dataGridViewClients.SelectionMode = DataGridViewSelectionMode.FullRowSelect; dataGridViewClients.Columns.Clear(); { @@ -146,23 +147,160 @@ namespace trakker }; } - - private void button1_Click(object sender, EventArgs e) + public void FillDataGridViewProjects(BindingSource projects) { - 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); - } - } + 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 = "Status", + Name = "Status", + Visible = true, + }; + dataGridViewProjects.Columns.Add(textColumn); + } + + dataGridViewProjects.DoubleClick += (s, e) => + { + if (dataGridViewProjects.SelectedRows.Count > 0) + { + var selectedProject = dataGridViewProjects.SelectedRows[0].DataBoundItem as Project; + if (selectedProject != null) + { + var dialog = new ProjectForm(selectedProject, _ctrl.GetClients()); + 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 + } + 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 ); + } + } + }; + + } + + //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); + // } + // } + //} } } diff --git a/Forms/ProjectForm.Designer.cs b/Forms/ProjectForm.Designer.cs new file mode 100644 index 0000000..1adefbd --- /dev/null +++ b/Forms/ProjectForm.Designer.cs @@ -0,0 +1,435 @@ +namespace trakker.Forms +{ + partial class ProjectForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + groupBoxNewClient = new GroupBox(); + tableLayoutPanel1 = new TableLayoutPanel(); + tableLayoutPanel2 = new TableLayoutPanel(); + labelClient = new Label(); + labelName = new Label(); + labelDescription = new Label(); + labelStartDate = new Label(); + labelHourlyRate = new Label(); + textBoxName = new TextBox(); + comboBoxClient = new ComboBox(); + tableLayoutPanel6 = new TableLayoutPanel(); + dateTimePickerStartDate = new DateTimePicker(); + dateTimePickerEndDate = new DateTimePicker(); + labelEndDate = new Label(); + richTextBoxDescription = new RichTextBox(); + tableLayoutPanel4 = new TableLayoutPanel(); + labelBudget = new Label(); + labelStatus = new Label(); + comboBoxStatus = new ComboBox(); + textBoxHourlyRate = new TextBox(); + textBoxBudget = new TextBox(); + groupBoxNotes = new GroupBox(); + richTextBoxNotes = new RichTextBox(); + tableLayoutPanel3 = new TableLayoutPanel(); + buttonOkay = new Button(); + buttonCancel = new Button(); + labelCreatedUpdatedDT = new Label(); + groupBoxNewClient.SuspendLayout(); + tableLayoutPanel1.SuspendLayout(); + tableLayoutPanel2.SuspendLayout(); + tableLayoutPanel6.SuspendLayout(); + tableLayoutPanel4.SuspendLayout(); + groupBoxNotes.SuspendLayout(); + tableLayoutPanel3.SuspendLayout(); + SuspendLayout(); + // + // groupBoxNewClient + // + groupBoxNewClient.Controls.Add(tableLayoutPanel1); + groupBoxNewClient.Dock = DockStyle.Fill; + groupBoxNewClient.Location = new Point(0, 0); + groupBoxNewClient.Name = "groupBoxNewClient"; + groupBoxNewClient.Size = new Size(1144, 660); + groupBoxNewClient.TabIndex = 1; + groupBoxNewClient.TabStop = false; + groupBoxNewClient.Text = "New Client"; + // + // tableLayoutPanel1 + // + tableLayoutPanel1.ColumnCount = 1; + tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F)); + tableLayoutPanel1.Controls.Add(tableLayoutPanel2, 0, 0); + tableLayoutPanel1.Controls.Add(groupBoxNotes, 0, 1); + tableLayoutPanel1.Controls.Add(tableLayoutPanel3, 0, 2); + tableLayoutPanel1.Dock = DockStyle.Fill; + tableLayoutPanel1.Location = new Point(3, 35); + tableLayoutPanel1.Name = "tableLayoutPanel1"; + tableLayoutPanel1.RowCount = 3; + tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 100F)); + tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Absolute, 242F)); + tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Absolute, 75F)); + tableLayoutPanel1.Size = new Size(1138, 622); + tableLayoutPanel1.TabIndex = 0; + // + // tableLayoutPanel2 + // + tableLayoutPanel2.ColumnCount = 2; + tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 150F)); + tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F)); + tableLayoutPanel2.Controls.Add(labelClient, 0, 0); + tableLayoutPanel2.Controls.Add(labelName, 0, 1); + tableLayoutPanel2.Controls.Add(labelDescription, 0, 2); + tableLayoutPanel2.Controls.Add(labelStartDate, 0, 3); + tableLayoutPanel2.Controls.Add(labelHourlyRate, 0, 4); + tableLayoutPanel2.Controls.Add(textBoxName, 1, 1); + tableLayoutPanel2.Controls.Add(comboBoxClient, 1, 0); + tableLayoutPanel2.Controls.Add(tableLayoutPanel6, 1, 3); + tableLayoutPanel2.Controls.Add(richTextBoxDescription, 1, 2); + tableLayoutPanel2.Controls.Add(tableLayoutPanel4, 1, 4); + tableLayoutPanel2.Dock = DockStyle.Fill; + tableLayoutPanel2.Location = new Point(3, 3); + tableLayoutPanel2.Name = "tableLayoutPanel2"; + tableLayoutPanel2.RowCount = 5; + tableLayoutPanel2.RowStyles.Add(new RowStyle(SizeType.Absolute, 50F)); + tableLayoutPanel2.RowStyles.Add(new RowStyle(SizeType.Absolute, 50F)); + tableLayoutPanel2.RowStyles.Add(new RowStyle(SizeType.Absolute, 100F)); + tableLayoutPanel2.RowStyles.Add(new RowStyle(SizeType.Absolute, 50F)); + tableLayoutPanel2.RowStyles.Add(new RowStyle(SizeType.Absolute, 50F)); + tableLayoutPanel2.Size = new Size(1132, 299); + tableLayoutPanel2.TabIndex = 1; + // + // labelClient + // + labelClient.AutoSize = true; + labelClient.Dock = DockStyle.Fill; + labelClient.Location = new Point(3, 0); + labelClient.Name = "labelClient"; + labelClient.Size = new Size(144, 50); + labelClient.TabIndex = 0; + labelClient.Text = "Client *"; + // + // labelName + // + labelName.AutoSize = true; + labelName.Dock = DockStyle.Fill; + labelName.Location = new Point(3, 50); + labelName.Name = "labelName"; + labelName.Size = new Size(144, 50); + labelName.TabIndex = 0; + labelName.Text = "Name *"; + // + // labelDescription + // + labelDescription.AutoSize = true; + labelDescription.Dock = DockStyle.Fill; + labelDescription.Location = new Point(3, 100); + labelDescription.Name = "labelDescription"; + labelDescription.Size = new Size(144, 100); + labelDescription.TabIndex = 0; + labelDescription.Text = "Description"; + // + // labelStartDate + // + labelStartDate.AutoSize = true; + labelStartDate.Dock = DockStyle.Fill; + labelStartDate.Location = new Point(3, 200); + labelStartDate.Name = "labelStartDate"; + labelStartDate.Size = new Size(144, 50); + labelStartDate.TabIndex = 0; + labelStartDate.Text = "Start Date"; + // + // labelHourlyRate + // + labelHourlyRate.AutoSize = true; + labelHourlyRate.Dock = DockStyle.Fill; + labelHourlyRate.Location = new Point(3, 250); + labelHourlyRate.Name = "labelHourlyRate"; + labelHourlyRate.Size = new Size(144, 50); + labelHourlyRate.TabIndex = 0; + labelHourlyRate.Text = "Hourly Rate"; + // + // textBoxName + // + textBoxName.Dock = DockStyle.Left; + textBoxName.Location = new Point(153, 53); + textBoxName.Name = "textBoxName"; + textBoxName.PlaceholderText = "Project X"; + textBoxName.Size = new Size(913, 39); + textBoxName.TabIndex = 2; + textBoxName.Validating += textBoxName_Validating; + // + // comboBoxClient + // + comboBoxClient.Dock = DockStyle.Fill; + comboBoxClient.FlatStyle = FlatStyle.Flat; + comboBoxClient.FormattingEnabled = true; + comboBoxClient.Location = new Point(153, 3); + comboBoxClient.Name = "comboBoxClient"; + comboBoxClient.Size = new Size(976, 40); + comboBoxClient.TabIndex = 1; + // + // tableLayoutPanel6 + // + tableLayoutPanel6.ColumnCount = 3; + tableLayoutPanel6.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 40F)); + tableLayoutPanel6.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 20F)); + tableLayoutPanel6.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 40F)); + tableLayoutPanel6.Controls.Add(dateTimePickerStartDate, 0, 0); + tableLayoutPanel6.Controls.Add(dateTimePickerEndDate, 2, 0); + tableLayoutPanel6.Controls.Add(labelEndDate, 1, 0); + tableLayoutPanel6.Dock = DockStyle.Fill; + tableLayoutPanel6.Location = new Point(153, 203); + tableLayoutPanel6.Name = "tableLayoutPanel6"; + tableLayoutPanel6.RowCount = 1; + tableLayoutPanel6.RowStyles.Add(new RowStyle(SizeType.Percent, 100F)); + tableLayoutPanel6.Size = new Size(976, 44); + tableLayoutPanel6.TabIndex = 7; + // + // dateTimePickerStartDate + // + dateTimePickerStartDate.Format = DateTimePickerFormat.Short; + dateTimePickerStartDate.Location = new Point(3, 3); + dateTimePickerStartDate.Name = "dateTimePickerStartDate"; + dateTimePickerStartDate.Size = new Size(384, 39); + dateTimePickerStartDate.TabIndex = 0; + dateTimePickerStartDate.TabStop = false; + // + // dateTimePickerEndDate + // + dateTimePickerEndDate.Format = DateTimePickerFormat.Short; + dateTimePickerEndDate.Location = new Point(588, 3); + dateTimePickerEndDate.Name = "dateTimePickerEndDate"; + dateTimePickerEndDate.Size = new Size(385, 39); + dateTimePickerEndDate.TabIndex = 1; + dateTimePickerEndDate.TabStop = false; + // + // labelEndDate + // + labelEndDate.AutoSize = true; + labelEndDate.Dock = DockStyle.Fill; + labelEndDate.Location = new Point(393, 0); + labelEndDate.Name = "labelEndDate"; + labelEndDate.Size = new Size(189, 44); + labelEndDate.TabIndex = 2; + labelEndDate.Text = "End Date"; + labelEndDate.TextAlign = ContentAlignment.TopCenter; + // + // richTextBoxDescription + // + richTextBoxDescription.Dock = DockStyle.Fill; + richTextBoxDescription.Location = new Point(153, 103); + richTextBoxDescription.Name = "richTextBoxDescription"; + richTextBoxDescription.Size = new Size(976, 94); + richTextBoxDescription.TabIndex = 3; + richTextBoxDescription.Text = ""; + // + // tableLayoutPanel4 + // + tableLayoutPanel4.ColumnCount = 5; + tableLayoutPanel4.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 20F)); + tableLayoutPanel4.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 20F)); + tableLayoutPanel4.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 20F)); + tableLayoutPanel4.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 20F)); + tableLayoutPanel4.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 20F)); + tableLayoutPanel4.Controls.Add(labelBudget, 1, 0); + tableLayoutPanel4.Controls.Add(labelStatus, 3, 0); + tableLayoutPanel4.Controls.Add(comboBoxStatus, 4, 0); + tableLayoutPanel4.Controls.Add(textBoxHourlyRate, 0, 0); + tableLayoutPanel4.Controls.Add(textBoxBudget, 2, 0); + tableLayoutPanel4.Dock = DockStyle.Fill; + tableLayoutPanel4.Location = new Point(153, 253); + tableLayoutPanel4.Name = "tableLayoutPanel4"; + tableLayoutPanel4.RowCount = 1; + tableLayoutPanel4.RowStyles.Add(new RowStyle(SizeType.Percent, 100F)); + tableLayoutPanel4.Size = new Size(976, 44); + tableLayoutPanel4.TabIndex = 9; + // + // labelBudget + // + labelBudget.AutoSize = true; + labelBudget.Dock = DockStyle.Fill; + labelBudget.Location = new Point(198, 0); + labelBudget.Name = "labelBudget"; + labelBudget.Size = new Size(189, 44); + labelBudget.TabIndex = 0; + labelBudget.Text = "Budget"; + labelBudget.TextAlign = ContentAlignment.TopCenter; + // + // labelStatus + // + labelStatus.AutoSize = true; + labelStatus.Dock = DockStyle.Fill; + labelStatus.Location = new Point(588, 0); + labelStatus.Name = "labelStatus"; + labelStatus.Size = new Size(189, 44); + labelStatus.TabIndex = 1; + labelStatus.Text = "Status"; + labelStatus.TextAlign = ContentAlignment.TopCenter; + // + // comboBoxStatus + // + comboBoxStatus.FlatStyle = FlatStyle.Flat; + comboBoxStatus.FormattingEnabled = true; + comboBoxStatus.Location = new Point(783, 3); + comboBoxStatus.Name = "comboBoxStatus"; + comboBoxStatus.Size = new Size(190, 40); + comboBoxStatus.TabIndex = 6; + // + // textBoxHourlyRate + // + textBoxHourlyRate.Location = new Point(3, 3); + textBoxHourlyRate.Name = "textBoxHourlyRate"; + textBoxHourlyRate.Size = new Size(189, 39); + textBoxHourlyRate.TabIndex = 7; + // + // textBoxBudget + // + textBoxBudget.Location = new Point(393, 3); + textBoxBudget.Name = "textBoxBudget"; + textBoxBudget.Size = new Size(189, 39); + textBoxBudget.TabIndex = 8; + // + // groupBoxNotes + // + groupBoxNotes.Controls.Add(richTextBoxNotes); + groupBoxNotes.Dock = DockStyle.Fill; + groupBoxNotes.Location = new Point(3, 308); + groupBoxNotes.Name = "groupBoxNotes"; + groupBoxNotes.Size = new Size(1132, 236); + groupBoxNotes.TabIndex = 0; + groupBoxNotes.TabStop = false; + groupBoxNotes.Text = "Notes"; + // + // richTextBoxNotes + // + richTextBoxNotes.Dock = DockStyle.Fill; + richTextBoxNotes.Location = new Point(3, 35); + richTextBoxNotes.Name = "richTextBoxNotes"; + richTextBoxNotes.Size = new Size(1126, 198); + richTextBoxNotes.TabIndex = 7; + richTextBoxNotes.Text = ""; + // + // tableLayoutPanel3 + // + tableLayoutPanel3.ColumnCount = 3; + tableLayoutPanel3.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F)); + tableLayoutPanel3.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 200F)); + tableLayoutPanel3.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 200F)); + tableLayoutPanel3.Controls.Add(buttonOkay, 1, 0); + tableLayoutPanel3.Controls.Add(buttonCancel, 2, 0); + tableLayoutPanel3.Controls.Add(labelCreatedUpdatedDT, 0, 0); + tableLayoutPanel3.Dock = DockStyle.Fill; + tableLayoutPanel3.Location = new Point(3, 550); + tableLayoutPanel3.Name = "tableLayoutPanel3"; + tableLayoutPanel3.RowCount = 1; + tableLayoutPanel3.RowStyles.Add(new RowStyle(SizeType.Absolute, 75F)); + tableLayoutPanel3.Size = new Size(1132, 69); + tableLayoutPanel3.TabIndex = 2; + // + // buttonOkay + // + buttonOkay.Dock = DockStyle.Fill; + buttonOkay.Location = new Point(735, 3); + buttonOkay.Margin = new Padding(3, 3, 3, 15); + buttonOkay.Name = "buttonOkay"; + buttonOkay.Size = new Size(194, 57); + buttonOkay.TabIndex = 8; + buttonOkay.Text = "Okay"; + buttonOkay.UseVisualStyleBackColor = true; + // + // buttonCancel + // + buttonCancel.Dock = DockStyle.Fill; + buttonCancel.Location = new Point(935, 3); + buttonCancel.Margin = new Padding(3, 3, 3, 15); + buttonCancel.Name = "buttonCancel"; + buttonCancel.Size = new Size(194, 57); + buttonCancel.TabIndex = 99; + buttonCancel.TabStop = false; + buttonCancel.Text = "Cancel"; + buttonCancel.UseVisualStyleBackColor = true; + // + // labelCreatedUpdatedDT + // + labelCreatedUpdatedDT.AutoSize = true; + labelCreatedUpdatedDT.Dock = DockStyle.Fill; + labelCreatedUpdatedDT.Location = new Point(3, 0); + labelCreatedUpdatedDT.Name = "labelCreatedUpdatedDT"; + labelCreatedUpdatedDT.Size = new Size(726, 75); + labelCreatedUpdatedDT.TabIndex = 2; + labelCreatedUpdatedDT.Text = "Created: yyyy-mm-dd, Updated: yyyy-mm-dd"; + // + // ProjectForm + // + AutoScaleDimensions = new SizeF(13F, 32F); + AutoScaleMode = AutoScaleMode.Font; + ClientSize = new Size(1144, 660); + Controls.Add(groupBoxNewClient); + Name = "ProjectForm"; + Text = "ProjectForm"; + groupBoxNewClient.ResumeLayout(false); + tableLayoutPanel1.ResumeLayout(false); + tableLayoutPanel2.ResumeLayout(false); + tableLayoutPanel2.PerformLayout(); + tableLayoutPanel6.ResumeLayout(false); + tableLayoutPanel6.PerformLayout(); + tableLayoutPanel4.ResumeLayout(false); + tableLayoutPanel4.PerformLayout(); + groupBoxNotes.ResumeLayout(false); + tableLayoutPanel3.ResumeLayout(false); + tableLayoutPanel3.PerformLayout(); + ResumeLayout(false); + } + + #endregion + + private GroupBox groupBoxNewClient; + private TableLayoutPanel tableLayoutPanel1; + private TableLayoutPanel tableLayoutPanel2; + private Label labelClient; + private Label labelName; + private Label labelDescription; + private Label labelStartDate; + private Label labelHourlyRate; + private TextBox textBoxName; + private GroupBox groupBoxNotes; + private RichTextBox richTextBoxNotes; + private TableLayoutPanel tableLayoutPanel3; + private Button buttonOkay; + private Button buttonCancel; + private Label labelCreatedUpdatedDT; + private ComboBox comboBoxClient; + private TableLayoutPanel tableLayoutPanel6; + private DateTimePicker dateTimePickerStartDate; + private DateTimePicker dateTimePickerEndDate; + private Label labelEndDate; + private RichTextBox richTextBoxDescription; + private TableLayoutPanel tableLayoutPanel4; + private Label labelBudget; + private Label labelStatus; + private ComboBox comboBoxStatus; + private TextBox textBoxHourlyRate; + private TextBox textBoxBudget; + } +} \ No newline at end of file diff --git a/Forms/ProjectForm.cs b/Forms/ProjectForm.cs new file mode 100644 index 0000000..cb462c8 --- /dev/null +++ b/Forms/ProjectForm.cs @@ -0,0 +1,120 @@ +using System.ComponentModel; +using trakker.Models; + +namespace trakker.Forms +{ + /// + /// Form used to view and edit a model. Fields on the form + /// are data-bound to the provided project instance and basic validation is + /// performed on required fields. + /// + public partial class ProjectForm : Form + { + /// + /// The project instance being edited by this form. + /// + private readonly Project _project; + + private BindingList? _clients; + + /// + /// Binding source that connects the project model to the form controls. + /// + private BindingSource bindingSource = new BindingSource(); + + /// + /// Error provider used to display validation errors next to input controls. + /// + private ErrorProvider errorProvider = new ErrorProvider(); + + /// + /// Initializes a new instance of the class bound to + /// the provided . Sets up data bindings for all + /// visible input controls and configures dialog button behavior. + /// + /// The instance to edit. Must not be null. + /// The list of instances to select from. Must not be null. + public ProjectForm(Project project, BindingList clients) + { + _project = project; + _clients = clients; + InitializeComponent(); + + comboBoxClient.DataSource = _clients; + comboBoxClient.DisplayMember = "Name"; + comboBoxClient.ValueMember = "ClientId"; + + dateTimePickerStartDate.Format = DateTimePickerFormat.Custom; + dateTimePickerStartDate.CustomFormat = "yyyy-MM-dd"; + dateTimePickerEndDate.Format = DateTimePickerFormat.Custom; + dateTimePickerEndDate.CustomFormat = "yyyy-MM-dd"; + + // Bind model properties to controls so the UI reflects and updates the model. + bindingSource.DataSource = _project; + comboBoxClient.DataBindings.Add("SelectedValue", bindingSource, "ClientId", true); + textBoxName.DataBindings.Add("Text", bindingSource, "ProjectName", true); + richTextBoxDescription.DataBindings.Add("Text", bindingSource, "Description", true); + dateTimePickerStartDate.DataBindings.Add("Value", bindingSource, "StartDate", true); + dateTimePickerEndDate.DataBindings.Add("Value", bindingSource, "EndDate", true); + textBoxHourlyRate.DataBindings.Add("Text", bindingSource, "HourlyRate", true); + textBoxBudget.DataBindings.Add("Text", bindingSource, "Budget", true); + comboBoxStatus.DataBindings.Add("ValueMember", bindingSource, "Status", true); + richTextBoxNotes.DataBindings.Add("Text", bindingSource, "Notes", true); + + // Configure dialog buttons and window behavior. + buttonOkay.DialogResult = DialogResult.OK; + buttonCancel.DialogResult = DialogResult.Cancel; + this.CancelButton = buttonCancel; + this.StartPosition = FormStartPosition.CenterParent; + } + + /// + /// Gets the instance edited by the form. + /// + public Project Project { get => _project; private set { } } + + public BindingList? Clients { get => _clients; set { _clients = value; } } + + /// + /// Validates the Name field. If the name is empty or whitespace, an error is set + /// on the and the event is canceled to prevent the + /// form from closing. + /// + private void textBoxName_Validating(object sender, System.ComponentModel.CancelEventArgs e) + { + if (string.IsNullOrWhiteSpace(textBoxName.Text)) + { + errorProvider.SetError(textBoxName, "Name is required."); + errorProvider.SetIconAlignment(textBoxName, ErrorIconAlignment.MiddleRight); + errorProvider.SetIconPadding(textBoxName, 2); + e.Cancel = true; + } + else + { + errorProvider.SetError(textBoxName, ""); + } + } + + /// + /// Validates the Email field. If the email is empty or whitespace, an error is set + /// on the and the event is canceled to prevent the + /// form from closing. Note: this validation only checks presence, not format. + /// + private void textBoxEmail_Validating(object sender, System.ComponentModel.CancelEventArgs e) + { + //if (string.IsNullOrWhiteSpace(textBoxEmail.Text)) + //{ + // errorProvider.SetError(textBoxEmail, "Email is required."); + // errorProvider.SetIconAlignment(textBoxEmail, ErrorIconAlignment.MiddleRight); + // errorProvider.SetIconPadding(textBoxEmail, 2); + // e.Cancel = true; + //} + //else + //{ + // errorProvider.SetError(textBoxEmail, ""); + //} + } + + } +} + diff --git a/Forms/ProjectForm.resx b/Forms/ProjectForm.resx new file mode 100644 index 0000000..8b2ff64 --- /dev/null +++ b/Forms/ProjectForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Interfaces/IMainForm.cs b/Interfaces/IMainForm.cs index 4f5c57c..d537a2d 100644 --- a/Interfaces/IMainForm.cs +++ b/Interfaces/IMainForm.cs @@ -11,5 +11,7 @@ namespace trakker.Interfaces internal interface IMainForm { void InitDataGridViewClients(BindingList clients); + void InitDataGridViewProjects(); + void FillDataGridViewProjects(BindingSource projects); } } diff --git a/Models/Project.cs b/Models/Project.cs index 4a308cf..ed26ea1 100644 --- a/Models/Project.cs +++ b/Models/Project.cs @@ -1,37 +1,31 @@ -using System; -using System.ComponentModel.DataAnnotations; - + namespace trakker.Models { public class Project { - [Key] public string ProjectId { get; set; } = string.Empty; - [Required] public string ClientId { get; set; } = string.Empty; - [Required] - [MaxLength(200)] - public string Name { get; set; } = string.Empty; + public string ProjectCode { get; set; } = string.Empty; - [MaxLength(1000)] - public string? Description { get; set; } + public string ClientName { get; set; } = string.Empty; - public DateOnly? StartDate { get; set; } + public string ProjectName { get; set; } = string.Empty; - public DateOnly? EndDate { get; set; } + public string? Description { get; set; } = string.Empty; + + public DateTime? StartDate { get; set; } + + public DateTime? EndDate { get; set; } - [Range(0, double.MaxValue)] public decimal Budget { get; set; } = 0; - public ProjectStatus Status { get; set; } = ProjectStatus.Active; + public string Status { get; set; } = string.Empty; - [Range(0, double.MaxValue)] public decimal? HourlyRate { get; set; } - [MaxLength(2000)] - public string? Notes { get; set; } + public string? Notes { get; set; } = string.Empty; public DateTime CreatedAt { get; set; } = DateTime.UtcNow; diff --git a/Services/MainCtrl.cs b/Services/MainCtrl.cs index 01a672a..187aa79 100644 --- a/Services/MainCtrl.cs +++ b/Services/MainCtrl.cs @@ -20,13 +20,36 @@ namespace trakker.Services _connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString)); LoadClients(); + + _view.InitDataGridViewProjects(); + LoadProjects(); + } + + public BindingList GetClients() + { + var dbo = new Data.ClientData(_connectionString); + return dbo.Get(); } internal void LoadClients() { - // Implement logic to load clients from the database using _connectionString - var dbo = new Data.ClientData(_connectionString); - var clients = dbo.Get(); + var clients = GetClients(); _view.InitDataGridViewClients(clients); } + + public BindingList GetProjects() + { + var dbo = new Data.ProjectData(_connectionString); + return dbo.Get(); + } + + internal void LoadProjects() + { + var projects = GetProjects(); + foreach (var project in projects) + { + var x = project.Status; + } + _view.FillDataGridViewProjects(new BindingSource { DataSource = projects }); + } } } diff --git a/backup.txt b/backup.txt new file mode 100644 index 0000000..9923c00 --- /dev/null +++ b/backup.txt @@ -0,0 +1,285 @@ +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 "; + + _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.SelectionMode = DataGridViewSelectionMode.FullRowSelect; + dataGridViewClients.RowHeadersVisible = false; + dataGridViewClients.ColumnHeadersVisible = true; + dataGridViewClients.MultiSelect = false; + dataGridViewClients.DataSource = clients; + + 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); + 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 InitDataGridViewProjects(BindingList projects) + { + dataGridViewProjects.AllowUserToAddRows = true; + dataGridViewProjects.AllowUserToDeleteRows = true; + dataGridViewProjects.AutoGenerateColumns = false; + dataGridViewProjects.BackgroundColor = Color.White; + dataGridViewProjects.SelectionMode = DataGridViewSelectionMode.FullRowSelect; + dataGridViewProjects.RowHeadersVisible = false; + dataGridViewProjects.ColumnHeadersVisible = true; + dataGridViewProjects.MultiSelect = false; + dataGridViewProjects.DataSource = projects; + dataGridViewProjects.Columns.Clear(); + { + var textColumn = new DataGridViewTextBoxColumn + { + AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill, + DataPropertyName = "ProjectName", + Name = "Project Name", + Visible = true, + }; + 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", + Name = "Start Date", + Visible = true, + }; + dataGridViewProjects.Columns.Add(textColumn); + } + { + var textColumn = new DataGridViewTextBoxColumn + { + AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill, + DataPropertyName = "EndDate", + Name = "End Date", + Visible = true, + }; + dataGridViewProjects.Columns.Add(textColumn); + } + { + var textColumn = new DataGridViewTextBoxColumn + { + AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill, + DataPropertyName = "Status", + Name = "Status", + Visible = true, + }; + 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); + } + + dataGridViewProjects.DoubleClick += (s, e) => + { + if (dataGridViewProjects.SelectedRows.Count > 0) + { + var selectedProject = dataGridViewProjects.SelectedRows[0].DataBoundItem as Project; + if (selectedProject != null) + { + var dialog = new ProjectForm(selectedProject); + if (dialog.ShowDialog(this) == DialogResult.OK) + { + // Project project = dialog.Project; + // ProjectData projectData = new ProjectData(connectionString); + // try + // { + // projectData.Upsert(project); + // dataGridViewProjects.Refresh(); // Refresh the DataGridView to reflect changes + // } + // catch (Exception ex) + // { + // MessageBox.Show($"Error saving project: {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 ); + } + } + }; + + } + + //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); + // } + // } + //} + } +}