Continued development

This commit is contained in:
c0d3.m0nk3y 2026-05-08 19:22:00 -04:00
parent 289657a7d5
commit 691a999bae
12 changed files with 1502 additions and 116 deletions

View File

@ -225,44 +225,6 @@ namespace trakker.Data
return result; return result;
} }
/// <summary>
/// Executes arbitrary, ad-hoc SQL against the database inside a transaction.
/// </summary>
/// <param name="sql">A SQL statement to execute. The caller is responsible for
/// ensuring the SQL is safe and properly parameterized to avoid SQL injection.</param>
/// <remarks>
/// 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.
/// </remarks>
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;
}
}
} }
} }

View File

@ -25,5 +25,44 @@ namespace trakker.Data
conn.Open(); conn.Open();
return conn; return conn;
} }
/// <summary>
/// Executes arbitrary, ad-hoc SQL against the database inside a transaction.
/// </summary>
/// <param name="sql">A SQL statement to execute. The caller is responsible for
/// ensuring the SQL is safe and properly parameterized to avoid SQL injection.</param>
/// <remarks>
/// 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.
/// </remarks>
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;
}
}
} }
} }

232
Data/ProjectData.cs Normal file
View File

@ -0,0 +1,232 @@
using System.ComponentModel;
using trakker.Models;
namespace trakker.Data
{
/// <summary>
/// Provides data access methods for the <see cref="Models.Project"/> entity.
/// This class encapsulates database operations such as upsert, delete and ad-hoc
/// SQL execution for projects. It inherits from <see cref="DataAccess"/> which
/// provides connection management.
/// </summary>
internal class ProjectData(string connectionString) : DataAccess(connectionString)
{
public BindingList<Project> Get(string? projectId = null)
{
var results = new BindingList<Project>();
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;
}
/// <summary>
/// Inserts a new project record or updates an existing one (upsert) using
/// the provided <paramref name="project"/> model. This method executes
/// a single SQL statement inside a transaction and will commit on
/// success or roll back on failure.
/// </summary>
/// <param name="project">The <see cref="Client"/> model to insert or update. Must not be null.</param>
/// <remarks>
/// The SQL statement uses an ON CONFLICT clause to perform the update when a
/// matching <c>project_id</c> already exists. Parameter names correspond to the
/// project model property names.
/// </remarks>
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;
}
}
/// <summary>
/// Deletes the project with the specified <paramref name="projectId"/> from the
/// database.
/// </summary>
/// <param name="projectId">The identifier of the project to delete.</param>
/// <returns>An optional integer representing any scalar value returned by the
/// command executed after deletion (if applicable). May be null.</returns>
/// <remarks>
/// 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.
/// </remarks>
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;
}
}
}

View File

@ -34,16 +34,20 @@
MainForm_StatusStrip = new StatusStrip(); MainForm_StatusStrip = new StatusStrip();
tabControlMainForm = new TabControl(); tabControlMainForm = new TabControl();
MainForm_TabPage1 = new TabPage(); MainForm_TabPage1 = new TabPage();
button1 = new Button();
MainForm_TabPage2 = new TabPage(); MainForm_TabPage2 = new TabPage();
tableLayoutPanel1Tab2 = new TableLayoutPanel(); tableLayoutPanelClients1 = new TableLayoutPanel();
dataGridViewClients = new DataGridView(); dataGridViewClients = new DataGridView();
MainForm_TabPage3 = new TabPage();
tableLayoutPanelProjects1 = new TableLayoutPanel();
dataGridViewProjects = new DataGridView();
MainForm_MenuStrip.SuspendLayout(); MainForm_MenuStrip.SuspendLayout();
tabControlMainForm.SuspendLayout(); tabControlMainForm.SuspendLayout();
MainForm_TabPage1.SuspendLayout();
MainForm_TabPage2.SuspendLayout(); MainForm_TabPage2.SuspendLayout();
tableLayoutPanel1Tab2.SuspendLayout(); tableLayoutPanelClients1.SuspendLayout();
((System.ComponentModel.ISupportInitialize)dataGridViewClients).BeginInit(); ((System.ComponentModel.ISupportInitialize)dataGridViewClients).BeginInit();
MainForm_TabPage3.SuspendLayout();
tableLayoutPanelProjects1.SuspendLayout();
((System.ComponentModel.ISupportInitialize)dataGridViewProjects).BeginInit();
SuspendLayout(); SuspendLayout();
// //
// MainForm_MenuStrip // MainForm_MenuStrip
@ -52,7 +56,7 @@
MainForm_MenuStrip.Items.AddRange(new ToolStripItem[] { fileToolStripMenuItem }); MainForm_MenuStrip.Items.AddRange(new ToolStripItem[] { fileToolStripMenuItem });
MainForm_MenuStrip.Location = new Point(0, 0); MainForm_MenuStrip.Location = new Point(0, 0);
MainForm_MenuStrip.Name = "MainForm_MenuStrip"; 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.TabIndex = 0;
MainForm_MenuStrip.Text = "menuStrip1"; MainForm_MenuStrip.Text = "menuStrip1";
// //
@ -73,9 +77,9 @@
// MainForm_StatusStrip // MainForm_StatusStrip
// //
MainForm_StatusStrip.ImageScalingSize = new Size(32, 32); 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.Name = "MainForm_StatusStrip";
MainForm_StatusStrip.Size = new Size(1343, 22); MainForm_StatusStrip.Size = new Size(1878, 22);
MainForm_StatusStrip.TabIndex = 1; MainForm_StatusStrip.TabIndex = 1;
MainForm_StatusStrip.Text = "MainForm_StatusStrip"; MainForm_StatusStrip.Text = "MainForm_StatusStrip";
// //
@ -83,37 +87,27 @@
// //
tabControlMainForm.Controls.Add(MainForm_TabPage1); tabControlMainForm.Controls.Add(MainForm_TabPage1);
tabControlMainForm.Controls.Add(MainForm_TabPage2); tabControlMainForm.Controls.Add(MainForm_TabPage2);
tabControlMainForm.Controls.Add(MainForm_TabPage3);
tabControlMainForm.Dock = DockStyle.Fill; tabControlMainForm.Dock = DockStyle.Fill;
tabControlMainForm.Location = new Point(0, 40); tabControlMainForm.Location = new Point(0, 40);
tabControlMainForm.Name = "tabControlMainForm"; tabControlMainForm.Name = "tabControlMainForm";
tabControlMainForm.SelectedIndex = 0; tabControlMainForm.SelectedIndex = 0;
tabControlMainForm.Size = new Size(1343, 943); tabControlMainForm.Size = new Size(1878, 1020);
tabControlMainForm.TabIndex = 2; tabControlMainForm.TabIndex = 2;
// //
// MainForm_TabPage1 // MainForm_TabPage1
// //
MainForm_TabPage1.Controls.Add(button1);
MainForm_TabPage1.Location = new Point(8, 46); MainForm_TabPage1.Location = new Point(8, 46);
MainForm_TabPage1.Name = "MainForm_TabPage1"; MainForm_TabPage1.Name = "MainForm_TabPage1";
MainForm_TabPage1.Padding = new Padding(3); 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.TabIndex = 0;
MainForm_TabPage1.Text = "Tab 1"; MainForm_TabPage1.Text = "Tab 1";
MainForm_TabPage1.UseVisualStyleBackColor = true; 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
// //
MainForm_TabPage2.Controls.Add(tableLayoutPanel1Tab2); MainForm_TabPage2.Controls.Add(tableLayoutPanelClients1);
MainForm_TabPage2.Location = new Point(8, 46); MainForm_TabPage2.Location = new Point(8, 46);
MainForm_TabPage2.Name = "MainForm_TabPage2"; MainForm_TabPage2.Name = "MainForm_TabPage2";
MainForm_TabPage2.Padding = new Padding(3); MainForm_TabPage2.Padding = new Padding(3);
@ -122,20 +116,20 @@
MainForm_TabPage2.Text = "Tab 2"; MainForm_TabPage2.Text = "Tab 2";
MainForm_TabPage2.UseVisualStyleBackColor = true; MainForm_TabPage2.UseVisualStyleBackColor = true;
// //
// tableLayoutPanel1Tab2 // tableLayoutPanelClients1
// //
tableLayoutPanel1Tab2.ColumnCount = 1; tableLayoutPanelClients1.ColumnCount = 1;
tableLayoutPanel1Tab2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F)); tableLayoutPanelClients1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
tableLayoutPanel1Tab2.Controls.Add(dataGridViewClients, 0, 1); tableLayoutPanelClients1.Controls.Add(dataGridViewClients, 0, 1);
tableLayoutPanel1Tab2.Dock = DockStyle.Fill; tableLayoutPanelClients1.Dock = DockStyle.Fill;
tableLayoutPanel1Tab2.Location = new Point(3, 3); tableLayoutPanelClients1.Location = new Point(3, 3);
tableLayoutPanel1Tab2.Name = "tableLayoutPanel1Tab2"; tableLayoutPanelClients1.Name = "tableLayoutPanelClients1";
tableLayoutPanel1Tab2.RowCount = 3; tableLayoutPanelClients1.RowCount = 3;
tableLayoutPanel1Tab2.RowStyles.Add(new RowStyle(SizeType.Absolute, 1F)); tableLayoutPanelClients1.RowStyles.Add(new RowStyle(SizeType.Absolute, 1F));
tableLayoutPanel1Tab2.RowStyles.Add(new RowStyle(SizeType.Percent, 100F)); tableLayoutPanelClients1.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
tableLayoutPanel1Tab2.RowStyles.Add(new RowStyle(SizeType.Absolute, 200F)); tableLayoutPanelClients1.RowStyles.Add(new RowStyle(SizeType.Absolute, 200F));
tableLayoutPanel1Tab2.Size = new Size(1321, 883); tableLayoutPanelClients1.Size = new Size(1321, 883);
tableLayoutPanel1Tab2.TabIndex = 0; tableLayoutPanelClients1.TabIndex = 0;
// //
// dataGridViewClients // dataGridViewClients
// //
@ -150,11 +144,49 @@
dataGridViewClients.Size = new Size(1315, 676); dataGridViewClients.Size = new Size(1315, 676);
dataGridViewClients.TabIndex = 0; 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 // MainForm
// //
AutoScaleDimensions = new SizeF(13F, 32F); AutoScaleDimensions = new SizeF(13F, 32F);
AutoScaleMode = AutoScaleMode.Font; AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(1343, 1005); ClientSize = new Size(1878, 1082);
Controls.Add(tabControlMainForm); Controls.Add(tabControlMainForm);
Controls.Add(MainForm_StatusStrip); Controls.Add(MainForm_StatusStrip);
Controls.Add(MainForm_MenuStrip); Controls.Add(MainForm_MenuStrip);
@ -164,10 +196,12 @@
MainForm_MenuStrip.ResumeLayout(false); MainForm_MenuStrip.ResumeLayout(false);
MainForm_MenuStrip.PerformLayout(); MainForm_MenuStrip.PerformLayout();
tabControlMainForm.ResumeLayout(false); tabControlMainForm.ResumeLayout(false);
MainForm_TabPage1.ResumeLayout(false);
MainForm_TabPage2.ResumeLayout(false); MainForm_TabPage2.ResumeLayout(false);
tableLayoutPanel1Tab2.ResumeLayout(false); tableLayoutPanelClients1.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)dataGridViewClients).EndInit(); ((System.ComponentModel.ISupportInitialize)dataGridViewClients).EndInit();
MainForm_TabPage3.ResumeLayout(false);
tableLayoutPanelProjects1.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)dataGridViewProjects).EndInit();
ResumeLayout(false); ResumeLayout(false);
PerformLayout(); PerformLayout();
} }
@ -181,8 +215,10 @@
private TabPage MainForm_TabPage2; private TabPage MainForm_TabPage2;
private ToolStripMenuItem fileToolStripMenuItem; private ToolStripMenuItem fileToolStripMenuItem;
private ToolStripMenuItem MainForm_Exit_MenuItem; private ToolStripMenuItem MainForm_Exit_MenuItem;
private Button button1; private TableLayoutPanel tableLayoutPanelClients1;
private TableLayoutPanel tableLayoutPanel1Tab2;
private DataGridView dataGridViewClients; private DataGridView dataGridViewClients;
private TabPage MainForm_TabPage3;
private TableLayoutPanel tableLayoutPanelProjects1;
private DataGridView dataGridViewProjects;
} }
} }

View File

@ -36,6 +36,7 @@ namespace trakker
tabControlMainForm.TabPages[0].Text = " Home "; tabControlMainForm.TabPages[0].Text = " Home ";
tabControlMainForm.TabPages[1].Text = " Clients "; tabControlMainForm.TabPages[1].Text = " Clients ";
tabControlMainForm.TabPages[2].Text = " Projects ";
_ctrl = new Services.MainCtrl(this, connectionString); _ctrl = new Services.MainCtrl(this, connectionString);
} }
@ -58,11 +59,11 @@ namespace trakker
dataGridViewClients.AutoGenerateColumns = false; dataGridViewClients.AutoGenerateColumns = false;
dataGridViewClients.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill; dataGridViewClients.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
dataGridViewClients.BackgroundColor = Color.White; dataGridViewClients.BackgroundColor = Color.White;
dataGridViewClients.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
dataGridViewClients.RowHeadersVisible = false;
dataGridViewClients.ColumnHeadersVisible = true; dataGridViewClients.ColumnHeadersVisible = true;
dataGridViewClients.MultiSelect = false;
dataGridViewClients.DataSource = clients; dataGridViewClients.DataSource = clients;
dataGridViewClients.MultiSelect = false;
dataGridViewClients.RowHeadersVisible = false;
dataGridViewClients.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
dataGridViewClients.Columns.Clear(); dataGridViewClients.Columns.Clear();
{ {
@ -146,23 +147,160 @@ namespace trakker
}; };
} }
public void FillDataGridViewProjects(BindingSource projects)
private void button1_Click(object sender, EventArgs e)
{ {
var dialog = new ClientForm(new Client()); dataGridViewProjects.DataSource = projects;
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);
}
}
} }
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);
// }
// }
//}
} }
} }

435
Forms/ProjectForm.Designer.cs generated Normal file
View File

@ -0,0 +1,435 @@
namespace trakker.Forms
{
partial class ProjectForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
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;
}
}

120
Forms/ProjectForm.cs Normal file
View File

@ -0,0 +1,120 @@
using System.ComponentModel;
using trakker.Models;
namespace trakker.Forms
{
/// <summary>
/// Form used to view and edit a <see cref="Project"/> model. Fields on the form
/// are data-bound to the provided project instance and basic validation is
/// performed on required fields.
/// </summary>
public partial class ProjectForm : Form
{
/// <summary>
/// The project instance being edited by this form.
/// </summary>
private readonly Project _project;
private BindingList<Client>? _clients;
/// <summary>
/// Binding source that connects the project model to the form controls.
/// </summary>
private BindingSource bindingSource = new BindingSource();
/// <summary>
/// Error provider used to display validation errors next to input controls.
/// </summary>
private ErrorProvider errorProvider = new ErrorProvider();
/// <summary>
/// Initializes a new instance of the <see cref="ProjectForm"/> class bound to
/// the provided <paramref name="project"/>. Sets up data bindings for all
/// visible input controls and configures dialog button behavior.
/// </summary>
/// <param name="project">The <see cref="Project"/> instance to edit. Must not be null.</param>
/// <param name="clients">The list of <see cref="Client"/> instances to select from. Must not be null.</param>
public ProjectForm(Project project, BindingList<Client> 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;
}
/// <summary>
/// Gets the <see cref="Project"/> instance edited by the form.
/// </summary>
public Project Project { get => _project; private set { } }
public BindingList<Client>? Clients { get => _clients; set { _clients = value; } }
/// <summary>
/// Validates the Name field. If the name is empty or whitespace, an error is set
/// on the <see cref="errorProvider"/> and the event is canceled to prevent the
/// form from closing.
/// </summary>
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, "");
}
}
/// <summary>
/// Validates the Email field. If the email is empty or whitespace, an error is set
/// on the <see cref="errorProvider"/> and the event is canceled to prevent the
/// form from closing. Note: this validation only checks presence, not format.
/// </summary>
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, "");
//}
}
}
}

120
Forms/ProjectForm.resx Normal file
View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -11,5 +11,7 @@ namespace trakker.Interfaces
internal interface IMainForm internal interface IMainForm
{ {
void InitDataGridViewClients(BindingList<Client> clients); void InitDataGridViewClients(BindingList<Client> clients);
void InitDataGridViewProjects();
void FillDataGridViewProjects(BindingSource projects);
} }
} }

View File

@ -1,37 +1,31 @@
using System; 
using System.ComponentModel.DataAnnotations;
namespace trakker.Models namespace trakker.Models
{ {
public class Project public class Project
{ {
[Key]
public string ProjectId { get; set; } = string.Empty; public string ProjectId { get; set; } = string.Empty;
[Required]
public string ClientId { get; set; } = string.Empty; public string ClientId { get; set; } = string.Empty;
[Required] public string ProjectCode { get; set; } = string.Empty;
[MaxLength(200)]
public string Name { get; set; } = string.Empty;
[MaxLength(1000)] public string ClientName { get; set; } = string.Empty;
public string? Description { get; set; }
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 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; } public decimal? HourlyRate { get; set; }
[MaxLength(2000)] public string? Notes { get; set; } = string.Empty;
public string? Notes { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow; public DateTime CreatedAt { get; set; } = DateTime.UtcNow;

View File

@ -20,13 +20,36 @@ namespace trakker.Services
_connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString)); _connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
LoadClients(); LoadClients();
_view.InitDataGridViewProjects();
LoadProjects();
}
public BindingList<Client> GetClients()
{
var dbo = new Data.ClientData(_connectionString);
return dbo.Get();
} }
internal void LoadClients() internal void LoadClients()
{ {
// Implement logic to load clients from the database using _connectionString var clients = GetClients();
var dbo = new Data.ClientData(_connectionString);
var clients = dbo.Get();
_view.InitDataGridViewClients(clients); _view.InitDataGridViewClients(clients);
} }
public BindingList<Project> 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 });
}
} }
} }

285
backup.txt Normal file
View File

@ -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;
/// <summary>
/// Initializes a new instance of the <see cref="MainForm"/> class.
/// Sets up the form's controls and event handlers by calling
/// <see cref="InitializeComponent"/> which is generated by the designer.
/// </summary>
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);
}
/// <summary>
/// Handles the Click event of the Exit menu item. When invoked, this
/// method terminates the application.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">Event data associated with the click event.</param>
private void MainForm_Exit_MenuItem_Click(object sender, EventArgs e)
{
Application.Exit();
}
public void InitDataGridViewClients(BindingList<Client> 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<Project> 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);
// }
// }
//}
}
}