diff --git a/Data/LOVData.cs b/Data/LOVData.cs new file mode 100644 index 0000000..f682c2d --- /dev/null +++ b/Data/LOVData.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using trakker.Models; + +namespace trakker.Data +{ + internal class LOVData(string connectionString) : DataAccess(connectionString) + { + public BindingList Get(string source) + { + var results = new BindingList(); + + string sql = $@" + SELECT + value, + display + FROM + lov + WHERE + source = $source + ORDER BY + sort ASC, display ASC + ; + "; + + using var conn = OpenConnection(); + using var cmd = conn.CreateCommand(); + cmd.CommandText = sql; + + cmd.Parameters.AddWithValue("$source", source); + using var reader = cmd.ExecuteReader(); + + var _var1 = reader.GetOrdinal("value"); + var _var2 = reader.GetOrdinal("display"); + + while (reader.Read()) + { + results.Add(new LOV(reader.GetString(_var1), reader.GetString(_var2))); + } + + return results; + } + + } +} diff --git a/Data/ProjectData.cs b/Data/ProjectData.cs index 589ffe4..28b46d7 100644 --- a/Data/ProjectData.cs +++ b/Data/ProjectData.cs @@ -33,14 +33,16 @@ namespace trakker.Data p.end_date, p.budget, p.status, + l.display AS status_name, 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} + JOIN (SELECT value, display FROM lov WHERE source = 'project.status') l ON p.status = l.value + WHERE + {whereClause} ORDER BY p.start_date DESC, p.name ASC; ; "; @@ -65,11 +67,11 @@ namespace trakker.Data 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"); - + var _var11 = reader.GetOrdinal("status_name"); + var _var12 = reader.GetOrdinal("hourly_rate"); + var _var13 = reader.GetOrdinal("notes"); + var _var14 = reader.GetOrdinal("created_at"); + var _var15 = reader.GetOrdinal("updated_at"); while (reader.Read()) { results.Add(new Project @@ -84,10 +86,11 @@ namespace trakker.Data 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), + StatusName = reader.GetString(_var11), + HourlyRate = reader.GetDecimal(_var12), + Notes = reader.GetString(_var13), + CreatedAt = reader.GetDateTime(_var14), + UpdatedAt = reader.GetDateTime(_var15), }); } diff --git a/Forms/ClientForm.Designer.cs b/Forms/ClientForm.Designer.cs index f95df90..8563873 100644 --- a/Forms/ClientForm.Designer.cs +++ b/Forms/ClientForm.Designer.cs @@ -70,7 +70,7 @@ groupBoxNewClient.Size = new Size(1128, 665); groupBoxNewClient.TabIndex = 0; groupBoxNewClient.TabStop = false; - groupBoxNewClient.Text = "New Client"; + groupBoxNewClient.Text = "Client"; // // tableLayoutPanel1 // diff --git a/Forms/ClientForm.cs b/Forms/ClientForm.cs index c242bef..2666eef 100644 --- a/Forms/ClientForm.cs +++ b/Forms/ClientForm.cs @@ -30,11 +30,14 @@ namespace trakker.Forms /// visible input controls and configures dialog button behavior. /// /// The instance to edit. Must not be null. - public ClientForm(Client client) + /// The binding source for state values. Must not be null. + public ClientForm(Client client, BindingSource states) { _client = client; InitializeComponent(); + Text = "Client Details"; + // Bind model properties to controls so the UI reflects and updates the model. bindingSource.DataSource = _client; textBoxName.DataBindings.Add("Text", bindingSource, "Name", true); @@ -43,7 +46,10 @@ namespace trakker.Forms maskedTextBox_Phone.DataBindings.Add("Text", bindingSource, "Phone", true); textBoxAddressStreet.DataBindings.Add("Text", bindingSource, "AddressStreet", true); textBoxAddressCity.DataBindings.Add("Text", bindingSource, "AddressCity", true); - comboBoxAddressState.DataBindings.Add("Text", bindingSource, "AddressState", true); + comboBoxAddressState.DataSource = states; + comboBoxAddressState.DisplayMember = "Display"; + comboBoxAddressState.ValueMember = "Value"; + comboBoxAddressState.DataBindings.Add("SelectedValue", bindingSource, "AddressState", true); maskedTextBoxAddressPostal.DataBindings.Add("Text", bindingSource, "AddressPostal", true); richTextBoxNotes.DataBindings.Add("Text", bindingSource, "Notes", true); diff --git a/Forms/MainForm.Designer.cs b/Forms/MainForm.Designer.cs index 0634acb..e7497bb 100644 --- a/Forms/MainForm.Designer.cs +++ b/Forms/MainForm.Designer.cs @@ -40,6 +40,9 @@ MainForm_TabPage3 = new TabPage(); tableLayoutPanelProjects1 = new TableLayoutPanel(); dataGridViewProjects = new DataGridView(); + tableLayoutPanelProjects2 = new TableLayoutPanel(); + groupBoxProjectTasks = new GroupBox(); + dataGridViewProjectTasks = new DataGridView(); MainForm_MenuStrip.SuspendLayout(); tabControlMainForm.SuspendLayout(); MainForm_TabPage2.SuspendLayout(); @@ -48,6 +51,9 @@ MainForm_TabPage3.SuspendLayout(); tableLayoutPanelProjects1.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)dataGridViewProjects).BeginInit(); + tableLayoutPanelProjects2.SuspendLayout(); + groupBoxProjectTasks.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)dataGridViewProjectTasks).BeginInit(); SuspendLayout(); // // MainForm_MenuStrip @@ -111,7 +117,7 @@ MainForm_TabPage2.Location = new Point(8, 46); MainForm_TabPage2.Name = "MainForm_TabPage2"; MainForm_TabPage2.Padding = new Padding(3); - MainForm_TabPage2.Size = new Size(1327, 889); + MainForm_TabPage2.Size = new Size(1862, 966); MainForm_TabPage2.TabIndex = 1; MainForm_TabPage2.Text = "Tab 2"; MainForm_TabPage2.UseVisualStyleBackColor = true; @@ -127,8 +133,8 @@ 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.RowStyles.Add(new RowStyle(SizeType.Percent, 0F)); + tableLayoutPanelClients1.Size = new Size(1856, 960); tableLayoutPanelClients1.TabIndex = 0; // // dataGridViewClients @@ -141,7 +147,7 @@ dataGridViewClients.Name = "dataGridViewClients"; dataGridViewClients.ReadOnly = true; dataGridViewClients.RowHeadersWidth = 82; - dataGridViewClients.Size = new Size(1315, 676); + dataGridViewClients.Size = new Size(1850, 953); dataGridViewClients.TabIndex = 0; // // MainForm_TabPage3 @@ -149,7 +155,7 @@ 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.Size = new Size(1862, 966); MainForm_TabPage3.TabIndex = 2; MainForm_TabPage3.Text = "Tab 3"; MainForm_TabPage3.UseVisualStyleBackColor = true; @@ -159,14 +165,15 @@ tableLayoutPanelProjects1.ColumnCount = 1; tableLayoutPanelProjects1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F)); tableLayoutPanelProjects1.Controls.Add(dataGridViewProjects, 0, 1); + tableLayoutPanelProjects1.Controls.Add(tableLayoutPanelProjects2, 0, 2); 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.RowStyles.Add(new RowStyle(SizeType.Percent, 50F)); + tableLayoutPanelProjects1.RowStyles.Add(new RowStyle(SizeType.Percent, 50F)); + tableLayoutPanelProjects1.Size = new Size(1862, 966); tableLayoutPanelProjects1.TabIndex = 1; // // dataGridViewProjects @@ -179,9 +186,48 @@ dataGridViewProjects.Name = "dataGridViewProjects"; dataGridViewProjects.ReadOnly = true; dataGridViewProjects.RowHeadersWidth = 82; - dataGridViewProjects.Size = new Size(1321, 682); + dataGridViewProjects.Size = new Size(1856, 476); dataGridViewProjects.TabIndex = 0; // + // tableLayoutPanelProjects2 + // + tableLayoutPanelProjects2.ColumnCount = 3; + tableLayoutPanelProjects2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 0F)); + tableLayoutPanelProjects2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F)); + tableLayoutPanelProjects2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 0F)); + tableLayoutPanelProjects2.Controls.Add(groupBoxProjectTasks, 1, 0); + tableLayoutPanelProjects2.Dock = DockStyle.Fill; + tableLayoutPanelProjects2.Location = new Point(3, 486); + tableLayoutPanelProjects2.Name = "tableLayoutPanelProjects2"; + tableLayoutPanelProjects2.RowCount = 1; + tableLayoutPanelProjects2.RowStyles.Add(new RowStyle(SizeType.Percent, 100F)); + tableLayoutPanelProjects2.Size = new Size(1856, 477); + tableLayoutPanelProjects2.TabIndex = 1; + // + // groupBoxProjectTasks + // + groupBoxProjectTasks.Controls.Add(dataGridViewProjectTasks); + groupBoxProjectTasks.Dock = DockStyle.Fill; + groupBoxProjectTasks.Location = new Point(3, 3); + groupBoxProjectTasks.Name = "groupBoxProjectTasks"; + groupBoxProjectTasks.Size = new Size(1850, 471); + groupBoxProjectTasks.TabIndex = 0; + groupBoxProjectTasks.TabStop = false; + groupBoxProjectTasks.Text = "Tasks"; + // + // dataGridViewProjectTasks + // + dataGridViewProjectTasks.AllowUserToAddRows = false; + dataGridViewProjectTasks.AllowUserToDeleteRows = false; + dataGridViewProjectTasks.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize; + dataGridViewProjectTasks.Dock = DockStyle.Fill; + dataGridViewProjectTasks.Location = new Point(3, 35); + dataGridViewProjectTasks.Name = "dataGridViewProjectTasks"; + dataGridViewProjectTasks.ReadOnly = true; + dataGridViewProjectTasks.RowHeadersWidth = 82; + dataGridViewProjectTasks.Size = new Size(1844, 433); + dataGridViewProjectTasks.TabIndex = 0; + // // MainForm // AutoScaleDimensions = new SizeF(13F, 32F); @@ -202,6 +248,9 @@ MainForm_TabPage3.ResumeLayout(false); tableLayoutPanelProjects1.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)dataGridViewProjects).EndInit(); + tableLayoutPanelProjects2.ResumeLayout(false); + groupBoxProjectTasks.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)dataGridViewProjectTasks).EndInit(); ResumeLayout(false); PerformLayout(); } @@ -220,5 +269,8 @@ private TabPage MainForm_TabPage3; private TableLayoutPanel tableLayoutPanelProjects1; private DataGridView dataGridViewProjects; + private TableLayoutPanel tableLayoutPanelProjects2; + private GroupBox groupBoxProjectTasks; + private DataGridView dataGridViewProjectTasks; } } diff --git a/Forms/MainForm.cs b/Forms/MainForm.cs index 7aba030..5a1ab74 100644 --- a/Forms/MainForm.cs +++ b/Forms/MainForm.cs @@ -114,7 +114,7 @@ namespace trakker var selectedClient = dataGridViewClients.SelectedRows[0].DataBoundItem as Client; if (selectedClient != null) { - var dialog = new ClientForm(selectedClient); + var dialog = new ClientForm(selectedClient, _ctrl.GetLOV("state")); if (dialog.ShowDialog(this) == DialogResult.OK) { Client client = dialog.Client; @@ -236,7 +236,7 @@ namespace trakker var textColumn = new DataGridViewTextBoxColumn { AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill, - DataPropertyName = "Status", + DataPropertyName = "StatusName", Name = "Status", Visible = true, }; @@ -250,7 +250,7 @@ namespace trakker var selectedProject = dataGridViewProjects.SelectedRows[0].DataBoundItem as Project; if (selectedProject != null) { - var dialog = new ProjectForm(selectedProject, _ctrl.GetClients()); + var dialog = new ProjectForm(selectedProject, _ctrl.GetClients(), _ctrl.GetLOV("project.status")); if (dialog.ShowDialog(this) == DialogResult.OK) { Project project = dialog.Project; diff --git a/Forms/ProjectForm.Designer.cs b/Forms/ProjectForm.Designer.cs index 1adefbd..ba03356 100644 --- a/Forms/ProjectForm.Designer.cs +++ b/Forms/ProjectForm.Designer.cs @@ -73,7 +73,7 @@ groupBoxNewClient.Size = new Size(1144, 660); groupBoxNewClient.TabIndex = 1; groupBoxNewClient.TabStop = false; - groupBoxNewClient.Text = "New Client"; + groupBoxNewClient.Text = "Project"; // // tableLayoutPanel1 // diff --git a/Forms/ProjectForm.cs b/Forms/ProjectForm.cs index cb462c8..3d1155d 100644 --- a/Forms/ProjectForm.cs +++ b/Forms/ProjectForm.cs @@ -34,12 +34,15 @@ namespace trakker.Forms /// /// 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) + /// The binding source for status values. Must not be null. + public ProjectForm(Project project, BindingList clients, BindingSource status) { _project = project; _clients = clients; InitializeComponent(); + Text = "Project Details"; + comboBoxClient.DataSource = _clients; comboBoxClient.DisplayMember = "Name"; comboBoxClient.ValueMember = "ClientId"; @@ -58,7 +61,10 @@ namespace trakker.Forms 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); + comboBoxStatus.DataSource = status; + comboBoxStatus.DisplayMember = "Display"; + comboBoxStatus.ValueMember = "Value"; + comboBoxStatus.DataBindings.Add("SelectedValue", bindingSource, "Status", true); richTextBoxNotes.DataBindings.Add("Text", bindingSource, "Notes", true); // Configure dialog buttons and window behavior. diff --git a/Models/LOV.cs b/Models/LOV.cs new file mode 100644 index 0000000..e9b4db5 --- /dev/null +++ b/Models/LOV.cs @@ -0,0 +1,13 @@ +namespace trakker.Models +{ + internal class LOV + { + public LOV(string value, string display) + { + Value = value; + Display = display; + } + public string Value { get; set; } + public string Display { get; set; } + } +} diff --git a/Models/Project.cs b/Models/Project.cs index ed26ea1..23ded6f 100644 --- a/Models/Project.cs +++ b/Models/Project.cs @@ -23,6 +23,8 @@ namespace trakker.Models public string Status { get; set; } = string.Empty; + public string StatusName { get; set; } = string.Empty; + public decimal? HourlyRate { get; set; } public string? Notes { get; set; } = string.Empty; diff --git a/Services/MainCtrl.cs b/Services/MainCtrl.cs index 187aa79..ffe968e 100644 --- a/Services/MainCtrl.cs +++ b/Services/MainCtrl.cs @@ -25,6 +25,11 @@ namespace trakker.Services LoadProjects(); } + public BindingSource GetLOV(string source) + { + var dbo = new Data.LOVData(_connectionString); + return new BindingSource { DataSource = dbo.Get(source) }; + } public BindingList GetClients() { var dbo = new Data.ClientData(_connectionString);