Continued development
This commit is contained in:
parent
222a37c0de
commit
289657a7d5
|
|
@ -0,0 +1,268 @@
|
|||
using System.ComponentModel;
|
||||
using trakker.Models;
|
||||
|
||||
namespace trakker.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides data access methods for the <see cref="Models.Client"/> entity.
|
||||
/// This class encapsulates database operations such as upsert, delete and ad-hoc
|
||||
/// SQL execution for clients. It inherits from <see cref="DataAccess"/> which
|
||||
/// provides connection management.
|
||||
/// </summary>
|
||||
internal class ClientData(string connectionString) : DataAccess(connectionString)
|
||||
{
|
||||
|
||||
public BindingList<Client> Get(string? clientId = null)
|
||||
{
|
||||
var results = new BindingList<Client>();
|
||||
|
||||
string whereClause = "1 = 1";
|
||||
if (clientId != null)
|
||||
{
|
||||
whereClause = "client_id = $client_id";
|
||||
}
|
||||
|
||||
string sql = $@"
|
||||
SELECT
|
||||
client_id,
|
||||
name,
|
||||
company,
|
||||
email,
|
||||
phone,
|
||||
address_street,
|
||||
address_city,
|
||||
address_state,
|
||||
address_postal,
|
||||
notes,
|
||||
is_active
|
||||
FROM
|
||||
clients
|
||||
WHERE
|
||||
{whereClause}
|
||||
ORDER BY
|
||||
name ASC
|
||||
;
|
||||
";
|
||||
|
||||
|
||||
using var conn = OpenConnection();
|
||||
using var cmd = conn.CreateCommand();
|
||||
cmd.CommandText = sql;
|
||||
|
||||
if (clientId != null)
|
||||
{
|
||||
cmd.Parameters.AddWithValue("$client_id", clientId);
|
||||
}
|
||||
using var reader = cmd.ExecuteReader();
|
||||
|
||||
var _var1 = reader.GetOrdinal("client_id");
|
||||
var _var2 = reader.GetOrdinal("name");
|
||||
var _var3 = reader.GetOrdinal("company");
|
||||
var _var4 = reader.GetOrdinal("email");
|
||||
var _var5 = reader.GetOrdinal("phone");
|
||||
var _var6 = reader.GetOrdinal("address_street");
|
||||
var _var7 = reader.GetOrdinal("address_city");
|
||||
var _var8 = reader.GetOrdinal("address_state");
|
||||
var _var9 = reader.GetOrdinal("address_postal");
|
||||
var _var10 = reader.GetOrdinal("notes");
|
||||
var _var11 = reader.GetOrdinal("is_active");
|
||||
|
||||
while (reader.Read())
|
||||
{
|
||||
results.Add(new Client
|
||||
{
|
||||
ClientId = reader.GetString(_var1),
|
||||
Name = reader.GetString(_var2),
|
||||
Company = reader.GetString(_var3),
|
||||
Email = reader.GetString(_var4),
|
||||
Phone = reader.GetString(_var5),
|
||||
AddressStreet = reader.GetString(_var6),
|
||||
AddressCity = reader.GetString(_var7),
|
||||
AddressState = reader.GetString(_var8),
|
||||
AddressPostal = reader.GetString(_var9),
|
||||
Notes = reader.GetString(_var10),
|
||||
IsActive = reader.GetString(_var11),
|
||||
});
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts a new client record or updates an existing one (upsert) using
|
||||
/// the provided <paramref name="client"/> model. This method executes
|
||||
/// a single SQL statement inside a transaction and will commit on
|
||||
/// success or roll back on failure.
|
||||
/// </summary>
|
||||
/// <param name="client">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>client_id</c> already exists. Parameter names correspond to the
|
||||
/// client model property names.
|
||||
/// </remarks>
|
||||
public void Upsert(Client client)
|
||||
{
|
||||
const string sql = @"
|
||||
INSERT INTO clients (
|
||||
client_id,
|
||||
name,
|
||||
company,
|
||||
email,
|
||||
phone,
|
||||
address_street,
|
||||
address_city,
|
||||
address_state,
|
||||
address_postal,
|
||||
notes,
|
||||
is_active
|
||||
)
|
||||
VALUES (
|
||||
$client_id,
|
||||
$name,
|
||||
$company,
|
||||
$email,
|
||||
$phone,
|
||||
$address_street,
|
||||
$address_city,
|
||||
$address_state,
|
||||
$address_postal,
|
||||
$notes,
|
||||
$is_active
|
||||
)
|
||||
ON CONFLICT(client_id) DO UPDATE SET
|
||||
name = excluded.name,
|
||||
company = excluded.company,
|
||||
email = excluded.email,
|
||||
phone = excluded.phone,
|
||||
address_street = excluded.address_street,
|
||||
address_city = excluded.address_city,
|
||||
address_state = excluded.address_state,
|
||||
address_postal = excluded.address_postal,
|
||||
notes = excluded.notes,
|
||||
is_active = excluded.is_active,
|
||||
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("$client_id", client.ClientId);
|
||||
cmd.Parameters.AddWithValue("$name", client.Name);
|
||||
cmd.Parameters.AddWithValue("$company", client.Company);
|
||||
cmd.Parameters.AddWithValue("$email", client.Email);
|
||||
cmd.Parameters.AddWithValue("$phone", client.Phone);
|
||||
cmd.Parameters.AddWithValue("$address_street", client.AddressStreet);
|
||||
cmd.Parameters.AddWithValue("$address_city", client.AddressCity);
|
||||
cmd.Parameters.AddWithValue("$address_state", client.AddressState);
|
||||
cmd.Parameters.AddWithValue("$address_postal", client.AddressPostal);
|
||||
cmd.Parameters.AddWithValue("$notes", client.Notes);
|
||||
cmd.Parameters.AddWithValue("$is_active", client.IsActive);
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
tx.Commit();
|
||||
}
|
||||
catch
|
||||
{
|
||||
tx.Rollback();
|
||||
throw;
|
||||
}
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// Deletes the client with the specified <paramref name="clientId"/> from the
|
||||
/// database.
|
||||
/// </summary>
|
||||
/// <param name="clientId">The identifier of the client 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 clientId)
|
||||
{
|
||||
const string sql = @"
|
||||
DELETE FROM
|
||||
clients
|
||||
WHERE
|
||||
client_id = $client_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("$client_id", clientId);
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
using var idCmd = conn.CreateCommand();
|
||||
idCmd.Transaction = tx;
|
||||
result = (int?)idCmd.ExecuteScalar() ;
|
||||
|
||||
tx.Commit();
|
||||
}
|
||||
catch
|
||||
{
|
||||
tx.Rollback();
|
||||
throw;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
using Microsoft.Data.Sqlite;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace trakker.Data
|
||||
{
|
||||
public class DataAccess(string connectionString)
|
||||
{
|
||||
private readonly string _connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new, unopened SqliteConnection.
|
||||
/// </summary>
|
||||
protected SqliteConnection CreateConnection() => new(_connectionString);
|
||||
|
||||
/// <summary>
|
||||
/// Opens and returns a SqliteConnection (synchronous).
|
||||
/// </summary>
|
||||
protected SqliteConnection OpenConnection()
|
||||
{
|
||||
var conn = CreateConnection();
|
||||
conn.Open();
|
||||
return conn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -36,17 +36,15 @@
|
|||
labelEmail = new Label();
|
||||
labelPhone = new Label();
|
||||
labelAddress = new Label();
|
||||
labelIsActive = new Label();
|
||||
textBoxName = new TextBox();
|
||||
textBoxCompany = new TextBox();
|
||||
textBoxEmail = new TextBox();
|
||||
comboBoxIsActive = new ComboBox();
|
||||
maskedTextBox_Phone = new MaskedTextBox();
|
||||
tableLayoutPanel4 = new TableLayoutPanel();
|
||||
tableLayoutPanel5 = new TableLayoutPanel();
|
||||
textBoxAddressCity = new TextBox();
|
||||
comboBoxAddressState = new ComboBox();
|
||||
maskedTextBoxAddressZipcode = new MaskedTextBox();
|
||||
maskedTextBoxAddressPostal = new MaskedTextBox();
|
||||
textBoxAddressStreet = new TextBox();
|
||||
groupBoxNotes = new GroupBox();
|
||||
richTextBoxNotes = new RichTextBox();
|
||||
|
|
@ -86,7 +84,7 @@
|
|||
tableLayoutPanel1.Name = "tableLayoutPanel1";
|
||||
tableLayoutPanel1.RowCount = 3;
|
||||
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
|
||||
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Absolute, 200F));
|
||||
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Absolute, 242F));
|
||||
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Absolute, 75F));
|
||||
tableLayoutPanel1.Size = new Size(1122, 627);
|
||||
tableLayoutPanel1.TabIndex = 0;
|
||||
|
|
@ -101,25 +99,22 @@
|
|||
tableLayoutPanel2.Controls.Add(labelEmail, 0, 2);
|
||||
tableLayoutPanel2.Controls.Add(labelPhone, 0, 3);
|
||||
tableLayoutPanel2.Controls.Add(labelAddress, 0, 4);
|
||||
tableLayoutPanel2.Controls.Add(labelIsActive, 0, 5);
|
||||
tableLayoutPanel2.Controls.Add(textBoxName, 1, 0);
|
||||
tableLayoutPanel2.Controls.Add(textBoxCompany, 1, 1);
|
||||
tableLayoutPanel2.Controls.Add(textBoxEmail, 1, 2);
|
||||
tableLayoutPanel2.Controls.Add(comboBoxIsActive, 1, 5);
|
||||
tableLayoutPanel2.Controls.Add(maskedTextBox_Phone, 1, 3);
|
||||
tableLayoutPanel2.Controls.Add(tableLayoutPanel4, 1, 4);
|
||||
tableLayoutPanel2.Dock = DockStyle.Fill;
|
||||
tableLayoutPanel2.Location = new Point(3, 3);
|
||||
tableLayoutPanel2.Name = "tableLayoutPanel2";
|
||||
tableLayoutPanel2.RowCount = 6;
|
||||
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, 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.Size = new Size(1116, 346);
|
||||
tableLayoutPanel2.TabIndex = 0;
|
||||
tableLayoutPanel2.Size = new Size(1116, 304);
|
||||
tableLayoutPanel2.TabIndex = 1;
|
||||
//
|
||||
// labelName
|
||||
//
|
||||
|
|
@ -138,7 +133,7 @@
|
|||
labelCompany.Location = new Point(3, 50);
|
||||
labelCompany.Name = "labelCompany";
|
||||
labelCompany.Size = new Size(144, 50);
|
||||
labelCompany.TabIndex = 1;
|
||||
labelCompany.TabIndex = 0;
|
||||
labelCompany.Text = "Company";
|
||||
//
|
||||
// labelEmail
|
||||
|
|
@ -148,7 +143,7 @@
|
|||
labelEmail.Location = new Point(3, 100);
|
||||
labelEmail.Name = "labelEmail";
|
||||
labelEmail.Size = new Size(144, 50);
|
||||
labelEmail.TabIndex = 2;
|
||||
labelEmail.TabIndex = 0;
|
||||
labelEmail.Text = "Email *";
|
||||
//
|
||||
// labelPhone
|
||||
|
|
@ -158,7 +153,7 @@
|
|||
labelPhone.Location = new Point(3, 150);
|
||||
labelPhone.Name = "labelPhone";
|
||||
labelPhone.Size = new Size(144, 50);
|
||||
labelPhone.TabIndex = 3;
|
||||
labelPhone.TabIndex = 0;
|
||||
labelPhone.Text = "Phone";
|
||||
//
|
||||
// labelAddress
|
||||
|
|
@ -167,28 +162,18 @@
|
|||
labelAddress.Dock = DockStyle.Fill;
|
||||
labelAddress.Location = new Point(3, 200);
|
||||
labelAddress.Name = "labelAddress";
|
||||
labelAddress.Size = new Size(144, 100);
|
||||
labelAddress.TabIndex = 4;
|
||||
labelAddress.Size = new Size(144, 104);
|
||||
labelAddress.TabIndex = 0;
|
||||
labelAddress.Text = "Address";
|
||||
//
|
||||
// labelIsActive
|
||||
//
|
||||
labelIsActive.AutoSize = true;
|
||||
labelIsActive.Dock = DockStyle.Fill;
|
||||
labelIsActive.Location = new Point(3, 300);
|
||||
labelIsActive.Name = "labelIsActive";
|
||||
labelIsActive.Size = new Size(144, 50);
|
||||
labelIsActive.TabIndex = 5;
|
||||
labelIsActive.Text = "Is Active?";
|
||||
//
|
||||
// textBoxName
|
||||
//
|
||||
textBoxName.Dock = DockStyle.Fill;
|
||||
textBoxName.Dock = DockStyle.Left;
|
||||
textBoxName.Location = new Point(153, 3);
|
||||
textBoxName.Name = "textBoxName";
|
||||
textBoxName.PlaceholderText = "Nancy Thompson";
|
||||
textBoxName.Size = new Size(960, 39);
|
||||
textBoxName.TabIndex = 6;
|
||||
textBoxName.Size = new Size(916, 39);
|
||||
textBoxName.TabIndex = 1;
|
||||
textBoxName.Validating += textBoxName_Validating;
|
||||
//
|
||||
// textBoxCompany
|
||||
|
|
@ -196,25 +181,19 @@
|
|||
textBoxCompany.Dock = DockStyle.Fill;
|
||||
textBoxCompany.Location = new Point(153, 53);
|
||||
textBoxCompany.Name = "textBoxCompany";
|
||||
textBoxCompany.PlaceholderText = "Acme Corporation";
|
||||
textBoxCompany.Size = new Size(960, 39);
|
||||
textBoxCompany.TabIndex = 7;
|
||||
textBoxCompany.TabIndex = 2;
|
||||
//
|
||||
// textBoxEmail
|
||||
//
|
||||
textBoxEmail.Dock = DockStyle.Fill;
|
||||
textBoxEmail.Dock = DockStyle.Left;
|
||||
textBoxEmail.Location = new Point(153, 103);
|
||||
textBoxEmail.Name = "textBoxEmail";
|
||||
textBoxEmail.Size = new Size(960, 39);
|
||||
textBoxEmail.TabIndex = 8;
|
||||
//
|
||||
// comboBoxIsActive
|
||||
//
|
||||
comboBoxIsActive.FormattingEnabled = true;
|
||||
comboBoxIsActive.Items.AddRange(new object[] { "True", "False" });
|
||||
comboBoxIsActive.Location = new Point(153, 303);
|
||||
comboBoxIsActive.Name = "comboBoxIsActive";
|
||||
comboBoxIsActive.Size = new Size(147, 40);
|
||||
comboBoxIsActive.TabIndex = 11;
|
||||
textBoxEmail.PlaceholderText = "username@domain.com";
|
||||
textBoxEmail.Size = new Size(916, 39);
|
||||
textBoxEmail.TabIndex = 3;
|
||||
textBoxEmail.Validating += textBoxEmail_Validating;
|
||||
//
|
||||
// maskedTextBox_Phone
|
||||
//
|
||||
|
|
@ -223,7 +202,7 @@
|
|||
maskedTextBox_Phone.Mask = "(999) 000-0000";
|
||||
maskedTextBox_Phone.Name = "maskedTextBox_Phone";
|
||||
maskedTextBox_Phone.Size = new Size(960, 39);
|
||||
maskedTextBox_Phone.TabIndex = 12;
|
||||
maskedTextBox_Phone.TabIndex = 4;
|
||||
//
|
||||
// tableLayoutPanel4
|
||||
//
|
||||
|
|
@ -237,8 +216,8 @@
|
|||
tableLayoutPanel4.RowCount = 2;
|
||||
tableLayoutPanel4.RowStyles.Add(new RowStyle(SizeType.Percent, 50F));
|
||||
tableLayoutPanel4.RowStyles.Add(new RowStyle(SizeType.Percent, 50F));
|
||||
tableLayoutPanel4.Size = new Size(960, 94);
|
||||
tableLayoutPanel4.TabIndex = 13;
|
||||
tableLayoutPanel4.Size = new Size(960, 98);
|
||||
tableLayoutPanel4.TabIndex = 5;
|
||||
//
|
||||
// tableLayoutPanel5
|
||||
//
|
||||
|
|
@ -248,14 +227,14 @@
|
|||
tableLayoutPanel5.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 15F));
|
||||
tableLayoutPanel5.Controls.Add(textBoxAddressCity, 0, 0);
|
||||
tableLayoutPanel5.Controls.Add(comboBoxAddressState, 1, 0);
|
||||
tableLayoutPanel5.Controls.Add(maskedTextBoxAddressZipcode, 2, 0);
|
||||
tableLayoutPanel5.Controls.Add(maskedTextBoxAddressPostal, 2, 0);
|
||||
tableLayoutPanel5.Dock = DockStyle.Fill;
|
||||
tableLayoutPanel5.Location = new Point(3, 50);
|
||||
tableLayoutPanel5.Location = new Point(3, 52);
|
||||
tableLayoutPanel5.Name = "tableLayoutPanel5";
|
||||
tableLayoutPanel5.RowCount = 1;
|
||||
tableLayoutPanel5.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
|
||||
tableLayoutPanel5.Size = new Size(954, 41);
|
||||
tableLayoutPanel5.TabIndex = 0;
|
||||
tableLayoutPanel5.Size = new Size(954, 43);
|
||||
tableLayoutPanel5.TabIndex = 6;
|
||||
//
|
||||
// textBoxAddressCity
|
||||
//
|
||||
|
|
@ -264,7 +243,7 @@
|
|||
textBoxAddressCity.Name = "textBoxAddressCity";
|
||||
textBoxAddressCity.PlaceholderText = "Springwood";
|
||||
textBoxAddressCity.Size = new Size(661, 39);
|
||||
textBoxAddressCity.TabIndex = 0;
|
||||
textBoxAddressCity.TabIndex = 1;
|
||||
//
|
||||
// comboBoxAddressState
|
||||
//
|
||||
|
|
@ -273,16 +252,16 @@
|
|||
comboBoxAddressState.Location = new Point(670, 3);
|
||||
comboBoxAddressState.Name = "comboBoxAddressState";
|
||||
comboBoxAddressState.Size = new Size(137, 40);
|
||||
comboBoxAddressState.TabIndex = 1;
|
||||
comboBoxAddressState.TabIndex = 2;
|
||||
//
|
||||
// maskedTextBoxAddressZipcode
|
||||
// maskedTextBoxAddressPostal
|
||||
//
|
||||
maskedTextBoxAddressZipcode.Dock = DockStyle.Fill;
|
||||
maskedTextBoxAddressZipcode.Location = new Point(813, 3);
|
||||
maskedTextBoxAddressZipcode.Mask = "00000";
|
||||
maskedTextBoxAddressZipcode.Name = "maskedTextBoxAddressZipcode";
|
||||
maskedTextBoxAddressZipcode.Size = new Size(138, 39);
|
||||
maskedTextBoxAddressZipcode.TabIndex = 2;
|
||||
maskedTextBoxAddressPostal.Dock = DockStyle.Fill;
|
||||
maskedTextBoxAddressPostal.Location = new Point(813, 3);
|
||||
maskedTextBoxAddressPostal.Mask = "00000";
|
||||
maskedTextBoxAddressPostal.Name = "maskedTextBoxAddressPostal";
|
||||
maskedTextBoxAddressPostal.Size = new Size(138, 39);
|
||||
maskedTextBoxAddressPostal.TabIndex = 3;
|
||||
//
|
||||
// textBoxAddressStreet
|
||||
//
|
||||
|
|
@ -297,10 +276,10 @@
|
|||
//
|
||||
groupBoxNotes.Controls.Add(richTextBoxNotes);
|
||||
groupBoxNotes.Dock = DockStyle.Fill;
|
||||
groupBoxNotes.Location = new Point(3, 355);
|
||||
groupBoxNotes.Location = new Point(3, 313);
|
||||
groupBoxNotes.Name = "groupBoxNotes";
|
||||
groupBoxNotes.Size = new Size(1116, 194);
|
||||
groupBoxNotes.TabIndex = 1;
|
||||
groupBoxNotes.Size = new Size(1116, 236);
|
||||
groupBoxNotes.TabIndex = 0;
|
||||
groupBoxNotes.TabStop = false;
|
||||
groupBoxNotes.Text = "Notes";
|
||||
//
|
||||
|
|
@ -309,8 +288,8 @@
|
|||
richTextBoxNotes.Dock = DockStyle.Fill;
|
||||
richTextBoxNotes.Location = new Point(3, 35);
|
||||
richTextBoxNotes.Name = "richTextBoxNotes";
|
||||
richTextBoxNotes.Size = new Size(1110, 156);
|
||||
richTextBoxNotes.TabIndex = 0;
|
||||
richTextBoxNotes.Size = new Size(1110, 198);
|
||||
richTextBoxNotes.TabIndex = 9;
|
||||
richTextBoxNotes.Text = "";
|
||||
//
|
||||
// tableLayoutPanel3
|
||||
|
|
@ -337,7 +316,7 @@
|
|||
buttonOkay.Margin = new Padding(3, 3, 3, 15);
|
||||
buttonOkay.Name = "buttonOkay";
|
||||
buttonOkay.Size = new Size(194, 57);
|
||||
buttonOkay.TabIndex = 0;
|
||||
buttonOkay.TabIndex = 10;
|
||||
buttonOkay.Text = "Okay";
|
||||
buttonOkay.UseVisualStyleBackColor = true;
|
||||
//
|
||||
|
|
@ -348,7 +327,8 @@
|
|||
buttonCancel.Margin = new Padding(3, 3, 3, 15);
|
||||
buttonCancel.Name = "buttonCancel";
|
||||
buttonCancel.Size = new Size(194, 57);
|
||||
buttonCancel.TabIndex = 1;
|
||||
buttonCancel.TabIndex = 99;
|
||||
buttonCancel.TabStop = false;
|
||||
buttonCancel.Text = "Cancel";
|
||||
buttonCancel.UseVisualStyleBackColor = true;
|
||||
//
|
||||
|
|
@ -369,7 +349,6 @@
|
|||
ClientSize = new Size(1128, 665);
|
||||
Controls.Add(groupBoxNewClient);
|
||||
Name = "ClientForm";
|
||||
Text = "Client";
|
||||
groupBoxNewClient.ResumeLayout(false);
|
||||
tableLayoutPanel1.ResumeLayout(false);
|
||||
tableLayoutPanel2.ResumeLayout(false);
|
||||
|
|
@ -394,12 +373,10 @@
|
|||
private Label labelEmail;
|
||||
private Label labelPhone;
|
||||
private Label labelAddress;
|
||||
private Label labelIsActive;
|
||||
private GroupBox groupBoxNotes;
|
||||
private TextBox textBoxName;
|
||||
private TextBox textBoxCompany;
|
||||
private TextBox textBoxEmail;
|
||||
private ComboBox comboBoxIsActive;
|
||||
private RichTextBox richTextBoxNotes;
|
||||
private TableLayoutPanel tableLayoutPanel3;
|
||||
private Button buttonOkay;
|
||||
|
|
@ -410,7 +387,7 @@
|
|||
private TableLayoutPanel tableLayoutPanel5;
|
||||
private TextBox textBoxAddressCity;
|
||||
private ComboBox comboBoxAddressState;
|
||||
private MaskedTextBox maskedTextBoxAddressZipcode;
|
||||
private MaskedTextBox maskedTextBoxAddressPostal;
|
||||
private TextBox textBoxAddressStreet;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +1,76 @@
|
|||
using System.Reflection.Metadata.Ecma335;
|
||||
using trakker.Models;
|
||||
using trakker.Models;
|
||||
|
||||
namespace trakker.Forms
|
||||
{
|
||||
/// <summary>
|
||||
/// Form used to view and edit a <see cref="Client"/> model. Fields on the form
|
||||
/// are data-bound to the provided client instance and basic validation is
|
||||
/// performed on required fields.
|
||||
/// </summary>
|
||||
public partial class ClientForm : Form
|
||||
{
|
||||
/// <summary>
|
||||
/// The client instance being edited by this form.
|
||||
/// </summary>
|
||||
private readonly Client _client;
|
||||
|
||||
/// <summary>
|
||||
/// Binding source that connects the client 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="ClientForm"/> class bound to
|
||||
/// the provided <paramref name="client"/>. Sets up data bindings for all
|
||||
/// visible input controls and configures dialog button behavior.
|
||||
/// </summary>
|
||||
/// <param name="client">The <see cref="Client"/> instance to edit. Must not be null.</param>
|
||||
public ClientForm(Client client)
|
||||
{
|
||||
_client = client;
|
||||
InitializeComponent();
|
||||
|
||||
// Bind model properties to controls so the UI reflects and updates the model.
|
||||
bindingSource.DataSource = _client;
|
||||
textBoxName.DataBindings.Add("Text", bindingSource, "Name", true);
|
||||
textBoxCompany.DataBindings.Add("Text", bindingSource, "Company", true);
|
||||
textBoxEmail.DataBindings.Add("Text", bindingSource, "Email", true);
|
||||
maskedTextBox_Phone.DataBindings.Add("Text", bindingSource, "Phone", true);
|
||||
//richTextBoxAddress.DataBindings.Add("Text", bindingSource, "Address", true);
|
||||
comboBoxIsActive.DataBindings.Add("Text", bindingSource, "IsActive", true);
|
||||
textBoxAddressStreet.DataBindings.Add("Text", bindingSource, "AddressStreet", true);
|
||||
textBoxAddressCity.DataBindings.Add("Text", bindingSource, "AddressCity", true);
|
||||
comboBoxAddressState.DataBindings.Add("Text", bindingSource, "AddressState", true);
|
||||
maskedTextBoxAddressPostal.DataBindings.Add("Text", bindingSource, "AddressPostal", true);
|
||||
richTextBoxNotes.DataBindings.Add("Text", bindingSource, "Notes", true);
|
||||
|
||||
// Configure dialog buttons and window behavior.
|
||||
buttonOkay.DialogResult = DialogResult.OK;
|
||||
buttonCancel.DialogResult = DialogResult.Cancel;
|
||||
this.CancelButton = CancelButton;
|
||||
this.StartPosition = FormStartPosition.CenterParent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="Client"/> instance edited by the form.
|
||||
/// </summary>
|
||||
public Client Client { get => _client; private set { } }
|
||||
|
||||
/// <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
|
||||
|
|
@ -38,5 +78,25 @@ namespace trakker.Forms
|
|||
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, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,13 +32,18 @@
|
|||
fileToolStripMenuItem = new ToolStripMenuItem();
|
||||
MainForm_Exit_MenuItem = new ToolStripMenuItem();
|
||||
MainForm_StatusStrip = new StatusStrip();
|
||||
MainForm_TabControl = new TabControl();
|
||||
tabControlMainForm = new TabControl();
|
||||
MainForm_TabPage1 = new TabPage();
|
||||
MainForm_TabPage2 = new TabPage();
|
||||
button1 = new Button();
|
||||
MainForm_TabPage2 = new TabPage();
|
||||
tableLayoutPanel1Tab2 = new TableLayoutPanel();
|
||||
dataGridViewClients = new DataGridView();
|
||||
MainForm_MenuStrip.SuspendLayout();
|
||||
MainForm_TabControl.SuspendLayout();
|
||||
tabControlMainForm.SuspendLayout();
|
||||
MainForm_TabPage1.SuspendLayout();
|
||||
MainForm_TabPage2.SuspendLayout();
|
||||
tableLayoutPanel1Tab2.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)dataGridViewClients).BeginInit();
|
||||
SuspendLayout();
|
||||
//
|
||||
// MainForm_MenuStrip
|
||||
|
|
@ -47,7 +52,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(1696, 40);
|
||||
MainForm_MenuStrip.Size = new Size(1343, 40);
|
||||
MainForm_MenuStrip.TabIndex = 0;
|
||||
MainForm_MenuStrip.Text = "menuStrip1";
|
||||
//
|
||||
|
|
@ -68,22 +73,22 @@
|
|||
// MainForm_StatusStrip
|
||||
//
|
||||
MainForm_StatusStrip.ImageScalingSize = new Size(32, 32);
|
||||
MainForm_StatusStrip.Location = new Point(0, 1074);
|
||||
MainForm_StatusStrip.Location = new Point(0, 983);
|
||||
MainForm_StatusStrip.Name = "MainForm_StatusStrip";
|
||||
MainForm_StatusStrip.Size = new Size(1696, 22);
|
||||
MainForm_StatusStrip.Size = new Size(1343, 22);
|
||||
MainForm_StatusStrip.TabIndex = 1;
|
||||
MainForm_StatusStrip.Text = "MainForm_StatusStrip";
|
||||
//
|
||||
// MainForm_TabControl
|
||||
// tabControlMainForm
|
||||
//
|
||||
MainForm_TabControl.Controls.Add(MainForm_TabPage1);
|
||||
MainForm_TabControl.Controls.Add(MainForm_TabPage2);
|
||||
MainForm_TabControl.Dock = DockStyle.Fill;
|
||||
MainForm_TabControl.Location = new Point(0, 40);
|
||||
MainForm_TabControl.Name = "MainForm_TabControl";
|
||||
MainForm_TabControl.SelectedIndex = 0;
|
||||
MainForm_TabControl.Size = new Size(1696, 1034);
|
||||
MainForm_TabControl.TabIndex = 2;
|
||||
tabControlMainForm.Controls.Add(MainForm_TabPage1);
|
||||
tabControlMainForm.Controls.Add(MainForm_TabPage2);
|
||||
tabControlMainForm.Dock = DockStyle.Fill;
|
||||
tabControlMainForm.Location = new Point(0, 40);
|
||||
tabControlMainForm.Name = "tabControlMainForm";
|
||||
tabControlMainForm.SelectedIndex = 0;
|
||||
tabControlMainForm.Size = new Size(1343, 943);
|
||||
tabControlMainForm.TabIndex = 2;
|
||||
//
|
||||
// MainForm_TabPage1
|
||||
//
|
||||
|
|
@ -91,21 +96,11 @@
|
|||
MainForm_TabPage1.Location = new Point(8, 46);
|
||||
MainForm_TabPage1.Name = "MainForm_TabPage1";
|
||||
MainForm_TabPage1.Padding = new Padding(3);
|
||||
MainForm_TabPage1.Size = new Size(1680, 980);
|
||||
MainForm_TabPage1.Size = new Size(1327, 889);
|
||||
MainForm_TabPage1.TabIndex = 0;
|
||||
MainForm_TabPage1.Text = "Tab 1";
|
||||
MainForm_TabPage1.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// MainForm_TabPage2
|
||||
//
|
||||
MainForm_TabPage2.Location = new Point(8, 46);
|
||||
MainForm_TabPage2.Name = "MainForm_TabPage2";
|
||||
MainForm_TabPage2.Padding = new Padding(3);
|
||||
MainForm_TabPage2.Size = new Size(1680, 1015);
|
||||
MainForm_TabPage2.TabIndex = 1;
|
||||
MainForm_TabPage2.Text = "Tab 2";
|
||||
MainForm_TabPage2.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// button1
|
||||
//
|
||||
button1.Location = new Point(39, 47);
|
||||
|
|
@ -116,12 +111,51 @@
|
|||
button1.UseVisualStyleBackColor = true;
|
||||
button1.Click += button1_Click;
|
||||
//
|
||||
// MainForm_TabPage2
|
||||
//
|
||||
MainForm_TabPage2.Controls.Add(tableLayoutPanel1Tab2);
|
||||
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.TabIndex = 1;
|
||||
MainForm_TabPage2.Text = "Tab 2";
|
||||
MainForm_TabPage2.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// tableLayoutPanel1Tab2
|
||||
//
|
||||
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;
|
||||
//
|
||||
// dataGridViewClients
|
||||
//
|
||||
dataGridViewClients.AllowUserToAddRows = false;
|
||||
dataGridViewClients.AllowUserToDeleteRows = false;
|
||||
dataGridViewClients.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
dataGridViewClients.Dock = DockStyle.Fill;
|
||||
dataGridViewClients.Location = new Point(3, 4);
|
||||
dataGridViewClients.Name = "dataGridViewClients";
|
||||
dataGridViewClients.ReadOnly = true;
|
||||
dataGridViewClients.RowHeadersWidth = 82;
|
||||
dataGridViewClients.Size = new Size(1315, 676);
|
||||
dataGridViewClients.TabIndex = 0;
|
||||
//
|
||||
// MainForm
|
||||
//
|
||||
AutoScaleDimensions = new SizeF(13F, 32F);
|
||||
AutoScaleMode = AutoScaleMode.Font;
|
||||
ClientSize = new Size(1696, 1096);
|
||||
Controls.Add(MainForm_TabControl);
|
||||
ClientSize = new Size(1343, 1005);
|
||||
Controls.Add(tabControlMainForm);
|
||||
Controls.Add(MainForm_StatusStrip);
|
||||
Controls.Add(MainForm_MenuStrip);
|
||||
MainMenuStrip = MainForm_MenuStrip;
|
||||
|
|
@ -129,8 +163,11 @@
|
|||
Text = "MainForm";
|
||||
MainForm_MenuStrip.ResumeLayout(false);
|
||||
MainForm_MenuStrip.PerformLayout();
|
||||
MainForm_TabControl.ResumeLayout(false);
|
||||
tabControlMainForm.ResumeLayout(false);
|
||||
MainForm_TabPage1.ResumeLayout(false);
|
||||
MainForm_TabPage2.ResumeLayout(false);
|
||||
tableLayoutPanel1Tab2.ResumeLayout(false);
|
||||
((System.ComponentModel.ISupportInitialize)dataGridViewClients).EndInit();
|
||||
ResumeLayout(false);
|
||||
PerformLayout();
|
||||
}
|
||||
|
|
@ -139,11 +176,13 @@
|
|||
|
||||
private MenuStrip MainForm_MenuStrip;
|
||||
private StatusStrip MainForm_StatusStrip;
|
||||
private TabControl MainForm_TabControl;
|
||||
private TabControl tabControlMainForm;
|
||||
private TabPage MainForm_TabPage1;
|
||||
private TabPage MainForm_TabPage2;
|
||||
private ToolStripMenuItem fileToolStripMenuItem;
|
||||
private ToolStripMenuItem MainForm_Exit_MenuItem;
|
||||
private Button button1;
|
||||
private TableLayoutPanel tableLayoutPanel1Tab2;
|
||||
private DataGridView dataGridViewClients;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,19 @@
|
|||
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
|
||||
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.
|
||||
|
|
@ -13,8 +22,22 @@ namespace trakker
|
|||
/// </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 ";
|
||||
|
||||
_ctrl = new Services.MainCtrl(this, connectionString);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -28,14 +51,118 @@ namespace trakker
|
|||
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 );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
private void button1_Click(object sender, EventArgs e)
|
||||
{
|
||||
Client client = new Client();
|
||||
client.ClientId = Guid.NewGuid().ToString();
|
||||
var dialog = new ClientForm(client);
|
||||
dialog.ShowDialog(this);
|
||||
client = dialog.Client;
|
||||
MessageBox.Show($"Client Name: {client.Name}\nCompany: {client.Company}\nEmail: {client.Email}\nPhone: {client.Phone}\nAddress: {client.Address}\nIs Active: {client.IsActive}\nNotes: {client.Notes}");
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using trakker.Models;
|
||||
|
||||
namespace trakker.Interfaces
|
||||
{
|
||||
internal interface IMainForm
|
||||
{
|
||||
void InitDataGridViewClients(BindingList<Client> clients);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,39 +3,104 @@ using System.ComponentModel.DataAnnotations;
|
|||
|
||||
namespace trakker.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a client record in the application.
|
||||
/// Contains contact information, address fields, and audit timestamps.
|
||||
/// Validation attributes decorate properties to enforce basic constraints.
|
||||
/// </summary>
|
||||
public class Client
|
||||
{
|
||||
public Client()
|
||||
{
|
||||
this.ClientId = Guid.NewGuid().ToString();
|
||||
this.IsActive = "y";
|
||||
}
|
||||
/// <summary>
|
||||
/// Primary identifier for the client. This maps to the database <c>client_id</c>.
|
||||
/// Marked with <see cref="Key"/> to indicate the primary key.
|
||||
/// </summary>
|
||||
[Key]
|
||||
public string ClientId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The client's full name. This field is required and has a maximum length of 200 characters.
|
||||
/// </summary>
|
||||
[Required]
|
||||
[MaxLength(200)]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Optional company name associated with the client. Maximum length 200 characters.
|
||||
/// </summary>
|
||||
[MaxLength(200)]
|
||||
public string? Company { get; set; }
|
||||
public string? Company { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Optional email address for the client. Validated with <see cref="EmailAddressAttribute"/> and
|
||||
/// constrained to 255 characters.
|
||||
/// </summary>
|
||||
[EmailAddress]
|
||||
[MaxLength(255)]
|
||||
public string? Email { get; set; }
|
||||
public string? Email { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Optional phone number for the client. Validated with <see cref="PhoneAttribute"/> and
|
||||
/// constrained to 50 characters.
|
||||
/// </summary>
|
||||
[Phone]
|
||||
[MaxLength(50)]
|
||||
public string? Phone { get; set; }
|
||||
public string? Phone { get; set; } = string.Empty;
|
||||
|
||||
[MaxLength(500)]
|
||||
public string? Address { get; set; }
|
||||
/// <summary>
|
||||
/// Street address for the client. Optional and limited to 100 characters.
|
||||
/// </summary>
|
||||
[MaxLength(100)]
|
||||
public string? AddressStreet { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// City portion of the client's address. Optional and limited to 100 characters.
|
||||
/// </summary>
|
||||
[MaxLength(100)]
|
||||
public string? AddressCity { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// State portion of the client's address. Stored as a 2-character code (e.g., US state abbreviation).
|
||||
/// </summary>
|
||||
[MaxLength(2)]
|
||||
public string? AddressState { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Postal code portion of the client's address. Limited to 5 characters in this model.
|
||||
/// </summary>
|
||||
[MaxLength(5)]
|
||||
public string? AddressPostal { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Free-form notes about the client. Optional and limited to 2000 characters.
|
||||
/// </summary>
|
||||
[MaxLength(2000)]
|
||||
public string? Notes { get; set; }
|
||||
public string? Notes { get; set; } = string.Empty;
|
||||
|
||||
public bool IsActive { get; set; } = true;
|
||||
/// <summary>
|
||||
/// Indicates whether the client is active. Defaults to <c>true</c>.
|
||||
/// </summary>
|
||||
public string IsActive { get; set; } = "y";
|
||||
|
||||
/// <summary>
|
||||
/// UTC timestamp for when the record was created. Defaults to <see cref="DateTime.UtcNow"/>.
|
||||
/// </summary>
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
/// <summary>
|
||||
/// UTC timestamp for when the record was last updated. Updated by application code
|
||||
/// when changes are made.
|
||||
/// </summary>
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
// Optional: Helper method for updating timestamp
|
||||
/// <summary>
|
||||
/// Updates the <see cref="UpdatedAt"/> timestamp to the current UTC time. Call this
|
||||
/// before persisting changes to ensure the audit timestamp is accurate.
|
||||
/// </summary>
|
||||
public void UpdateTimestamp()
|
||||
{
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
using SQLitePCL;
|
||||
|
||||
namespace trakker
|
||||
{
|
||||
internal static class Program
|
||||
|
|
@ -8,6 +10,9 @@ namespace trakker
|
|||
[STAThread]
|
||||
static void Main()
|
||||
{
|
||||
// Initialize SQLite early
|
||||
Batteries_V2.Init(); // ← Modern version (recommended)
|
||||
|
||||
// To customize application configuration such as set high DPI settings or default font,
|
||||
// see https://aka.ms/applicationconfiguration.
|
||||
ApplicationConfiguration.Initialize();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using trakker.Models;
|
||||
using trakker.Interfaces;
|
||||
|
||||
namespace trakker.Services
|
||||
{
|
||||
internal class MainCtrl
|
||||
{
|
||||
private readonly string _connectionString;
|
||||
private readonly IMainForm _view;
|
||||
|
||||
public MainCtrl(IMainForm view, string? connectionString)
|
||||
{
|
||||
_view = view ?? throw new ArgumentNullException(nameof(view));
|
||||
_connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
|
||||
|
||||
LoadClients();
|
||||
}
|
||||
internal void LoadClients()
|
||||
{
|
||||
// Implement logic to load clients from the database using _connectionString
|
||||
var dbo = new Data.ClientData(_connectionString);
|
||||
var clients = dbo.Get();
|
||||
_view.InitDataGridViewClients(clients);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,8 +9,8 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Interfaces\" />
|
||||
<Folder Include="Services\" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="10.0.7" />
|
||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="3.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Loading…
Reference in New Issue