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();
|
labelEmail = new Label();
|
||||||
labelPhone = new Label();
|
labelPhone = new Label();
|
||||||
labelAddress = new Label();
|
labelAddress = new Label();
|
||||||
labelIsActive = new Label();
|
|
||||||
textBoxName = new TextBox();
|
textBoxName = new TextBox();
|
||||||
textBoxCompany = new TextBox();
|
textBoxCompany = new TextBox();
|
||||||
textBoxEmail = new TextBox();
|
textBoxEmail = new TextBox();
|
||||||
comboBoxIsActive = new ComboBox();
|
|
||||||
maskedTextBox_Phone = new MaskedTextBox();
|
maskedTextBox_Phone = new MaskedTextBox();
|
||||||
tableLayoutPanel4 = new TableLayoutPanel();
|
tableLayoutPanel4 = new TableLayoutPanel();
|
||||||
tableLayoutPanel5 = new TableLayoutPanel();
|
tableLayoutPanel5 = new TableLayoutPanel();
|
||||||
textBoxAddressCity = new TextBox();
|
textBoxAddressCity = new TextBox();
|
||||||
comboBoxAddressState = new ComboBox();
|
comboBoxAddressState = new ComboBox();
|
||||||
maskedTextBoxAddressZipcode = new MaskedTextBox();
|
maskedTextBoxAddressPostal = new MaskedTextBox();
|
||||||
textBoxAddressStreet = new TextBox();
|
textBoxAddressStreet = new TextBox();
|
||||||
groupBoxNotes = new GroupBox();
|
groupBoxNotes = new GroupBox();
|
||||||
richTextBoxNotes = new RichTextBox();
|
richTextBoxNotes = new RichTextBox();
|
||||||
|
|
@ -86,7 +84,7 @@
|
||||||
tableLayoutPanel1.Name = "tableLayoutPanel1";
|
tableLayoutPanel1.Name = "tableLayoutPanel1";
|
||||||
tableLayoutPanel1.RowCount = 3;
|
tableLayoutPanel1.RowCount = 3;
|
||||||
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
|
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.RowStyles.Add(new RowStyle(SizeType.Absolute, 75F));
|
||||||
tableLayoutPanel1.Size = new Size(1122, 627);
|
tableLayoutPanel1.Size = new Size(1122, 627);
|
||||||
tableLayoutPanel1.TabIndex = 0;
|
tableLayoutPanel1.TabIndex = 0;
|
||||||
|
|
@ -101,25 +99,22 @@
|
||||||
tableLayoutPanel2.Controls.Add(labelEmail, 0, 2);
|
tableLayoutPanel2.Controls.Add(labelEmail, 0, 2);
|
||||||
tableLayoutPanel2.Controls.Add(labelPhone, 0, 3);
|
tableLayoutPanel2.Controls.Add(labelPhone, 0, 3);
|
||||||
tableLayoutPanel2.Controls.Add(labelAddress, 0, 4);
|
tableLayoutPanel2.Controls.Add(labelAddress, 0, 4);
|
||||||
tableLayoutPanel2.Controls.Add(labelIsActive, 0, 5);
|
|
||||||
tableLayoutPanel2.Controls.Add(textBoxName, 1, 0);
|
tableLayoutPanel2.Controls.Add(textBoxName, 1, 0);
|
||||||
tableLayoutPanel2.Controls.Add(textBoxCompany, 1, 1);
|
tableLayoutPanel2.Controls.Add(textBoxCompany, 1, 1);
|
||||||
tableLayoutPanel2.Controls.Add(textBoxEmail, 1, 2);
|
tableLayoutPanel2.Controls.Add(textBoxEmail, 1, 2);
|
||||||
tableLayoutPanel2.Controls.Add(comboBoxIsActive, 1, 5);
|
|
||||||
tableLayoutPanel2.Controls.Add(maskedTextBox_Phone, 1, 3);
|
tableLayoutPanel2.Controls.Add(maskedTextBox_Phone, 1, 3);
|
||||||
tableLayoutPanel2.Controls.Add(tableLayoutPanel4, 1, 4);
|
tableLayoutPanel2.Controls.Add(tableLayoutPanel4, 1, 4);
|
||||||
tableLayoutPanel2.Dock = DockStyle.Fill;
|
tableLayoutPanel2.Dock = DockStyle.Fill;
|
||||||
tableLayoutPanel2.Location = new Point(3, 3);
|
tableLayoutPanel2.Location = new Point(3, 3);
|
||||||
tableLayoutPanel2.Name = "tableLayoutPanel2";
|
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, 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, 100F));
|
||||||
tableLayoutPanel2.RowStyles.Add(new RowStyle(SizeType.Absolute, 50F));
|
tableLayoutPanel2.Size = new Size(1116, 304);
|
||||||
tableLayoutPanel2.Size = new Size(1116, 346);
|
tableLayoutPanel2.TabIndex = 1;
|
||||||
tableLayoutPanel2.TabIndex = 0;
|
|
||||||
//
|
//
|
||||||
// labelName
|
// labelName
|
||||||
//
|
//
|
||||||
|
|
@ -138,7 +133,7 @@
|
||||||
labelCompany.Location = new Point(3, 50);
|
labelCompany.Location = new Point(3, 50);
|
||||||
labelCompany.Name = "labelCompany";
|
labelCompany.Name = "labelCompany";
|
||||||
labelCompany.Size = new Size(144, 50);
|
labelCompany.Size = new Size(144, 50);
|
||||||
labelCompany.TabIndex = 1;
|
labelCompany.TabIndex = 0;
|
||||||
labelCompany.Text = "Company";
|
labelCompany.Text = "Company";
|
||||||
//
|
//
|
||||||
// labelEmail
|
// labelEmail
|
||||||
|
|
@ -148,7 +143,7 @@
|
||||||
labelEmail.Location = new Point(3, 100);
|
labelEmail.Location = new Point(3, 100);
|
||||||
labelEmail.Name = "labelEmail";
|
labelEmail.Name = "labelEmail";
|
||||||
labelEmail.Size = new Size(144, 50);
|
labelEmail.Size = new Size(144, 50);
|
||||||
labelEmail.TabIndex = 2;
|
labelEmail.TabIndex = 0;
|
||||||
labelEmail.Text = "Email *";
|
labelEmail.Text = "Email *";
|
||||||
//
|
//
|
||||||
// labelPhone
|
// labelPhone
|
||||||
|
|
@ -158,7 +153,7 @@
|
||||||
labelPhone.Location = new Point(3, 150);
|
labelPhone.Location = new Point(3, 150);
|
||||||
labelPhone.Name = "labelPhone";
|
labelPhone.Name = "labelPhone";
|
||||||
labelPhone.Size = new Size(144, 50);
|
labelPhone.Size = new Size(144, 50);
|
||||||
labelPhone.TabIndex = 3;
|
labelPhone.TabIndex = 0;
|
||||||
labelPhone.Text = "Phone";
|
labelPhone.Text = "Phone";
|
||||||
//
|
//
|
||||||
// labelAddress
|
// labelAddress
|
||||||
|
|
@ -167,28 +162,18 @@
|
||||||
labelAddress.Dock = DockStyle.Fill;
|
labelAddress.Dock = DockStyle.Fill;
|
||||||
labelAddress.Location = new Point(3, 200);
|
labelAddress.Location = new Point(3, 200);
|
||||||
labelAddress.Name = "labelAddress";
|
labelAddress.Name = "labelAddress";
|
||||||
labelAddress.Size = new Size(144, 100);
|
labelAddress.Size = new Size(144, 104);
|
||||||
labelAddress.TabIndex = 4;
|
labelAddress.TabIndex = 0;
|
||||||
labelAddress.Text = "Address";
|
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
|
||||||
//
|
//
|
||||||
textBoxName.Dock = DockStyle.Fill;
|
textBoxName.Dock = DockStyle.Left;
|
||||||
textBoxName.Location = new Point(153, 3);
|
textBoxName.Location = new Point(153, 3);
|
||||||
textBoxName.Name = "textBoxName";
|
textBoxName.Name = "textBoxName";
|
||||||
textBoxName.PlaceholderText = "Nancy Thompson";
|
textBoxName.PlaceholderText = "Nancy Thompson";
|
||||||
textBoxName.Size = new Size(960, 39);
|
textBoxName.Size = new Size(916, 39);
|
||||||
textBoxName.TabIndex = 6;
|
textBoxName.TabIndex = 1;
|
||||||
textBoxName.Validating += textBoxName_Validating;
|
textBoxName.Validating += textBoxName_Validating;
|
||||||
//
|
//
|
||||||
// textBoxCompany
|
// textBoxCompany
|
||||||
|
|
@ -196,25 +181,19 @@
|
||||||
textBoxCompany.Dock = DockStyle.Fill;
|
textBoxCompany.Dock = DockStyle.Fill;
|
||||||
textBoxCompany.Location = new Point(153, 53);
|
textBoxCompany.Location = new Point(153, 53);
|
||||||
textBoxCompany.Name = "textBoxCompany";
|
textBoxCompany.Name = "textBoxCompany";
|
||||||
|
textBoxCompany.PlaceholderText = "Acme Corporation";
|
||||||
textBoxCompany.Size = new Size(960, 39);
|
textBoxCompany.Size = new Size(960, 39);
|
||||||
textBoxCompany.TabIndex = 7;
|
textBoxCompany.TabIndex = 2;
|
||||||
//
|
//
|
||||||
// textBoxEmail
|
// textBoxEmail
|
||||||
//
|
//
|
||||||
textBoxEmail.Dock = DockStyle.Fill;
|
textBoxEmail.Dock = DockStyle.Left;
|
||||||
textBoxEmail.Location = new Point(153, 103);
|
textBoxEmail.Location = new Point(153, 103);
|
||||||
textBoxEmail.Name = "textBoxEmail";
|
textBoxEmail.Name = "textBoxEmail";
|
||||||
textBoxEmail.Size = new Size(960, 39);
|
textBoxEmail.PlaceholderText = "username@domain.com";
|
||||||
textBoxEmail.TabIndex = 8;
|
textBoxEmail.Size = new Size(916, 39);
|
||||||
//
|
textBoxEmail.TabIndex = 3;
|
||||||
// comboBoxIsActive
|
textBoxEmail.Validating += textBoxEmail_Validating;
|
||||||
//
|
|
||||||
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;
|
|
||||||
//
|
//
|
||||||
// maskedTextBox_Phone
|
// maskedTextBox_Phone
|
||||||
//
|
//
|
||||||
|
|
@ -223,7 +202,7 @@
|
||||||
maskedTextBox_Phone.Mask = "(999) 000-0000";
|
maskedTextBox_Phone.Mask = "(999) 000-0000";
|
||||||
maskedTextBox_Phone.Name = "maskedTextBox_Phone";
|
maskedTextBox_Phone.Name = "maskedTextBox_Phone";
|
||||||
maskedTextBox_Phone.Size = new Size(960, 39);
|
maskedTextBox_Phone.Size = new Size(960, 39);
|
||||||
maskedTextBox_Phone.TabIndex = 12;
|
maskedTextBox_Phone.TabIndex = 4;
|
||||||
//
|
//
|
||||||
// tableLayoutPanel4
|
// tableLayoutPanel4
|
||||||
//
|
//
|
||||||
|
|
@ -237,8 +216,8 @@
|
||||||
tableLayoutPanel4.RowCount = 2;
|
tableLayoutPanel4.RowCount = 2;
|
||||||
tableLayoutPanel4.RowStyles.Add(new RowStyle(SizeType.Percent, 50F));
|
tableLayoutPanel4.RowStyles.Add(new RowStyle(SizeType.Percent, 50F));
|
||||||
tableLayoutPanel4.RowStyles.Add(new RowStyle(SizeType.Percent, 50F));
|
tableLayoutPanel4.RowStyles.Add(new RowStyle(SizeType.Percent, 50F));
|
||||||
tableLayoutPanel4.Size = new Size(960, 94);
|
tableLayoutPanel4.Size = new Size(960, 98);
|
||||||
tableLayoutPanel4.TabIndex = 13;
|
tableLayoutPanel4.TabIndex = 5;
|
||||||
//
|
//
|
||||||
// tableLayoutPanel5
|
// tableLayoutPanel5
|
||||||
//
|
//
|
||||||
|
|
@ -248,14 +227,14 @@
|
||||||
tableLayoutPanel5.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 15F));
|
tableLayoutPanel5.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 15F));
|
||||||
tableLayoutPanel5.Controls.Add(textBoxAddressCity, 0, 0);
|
tableLayoutPanel5.Controls.Add(textBoxAddressCity, 0, 0);
|
||||||
tableLayoutPanel5.Controls.Add(comboBoxAddressState, 1, 0);
|
tableLayoutPanel5.Controls.Add(comboBoxAddressState, 1, 0);
|
||||||
tableLayoutPanel5.Controls.Add(maskedTextBoxAddressZipcode, 2, 0);
|
tableLayoutPanel5.Controls.Add(maskedTextBoxAddressPostal, 2, 0);
|
||||||
tableLayoutPanel5.Dock = DockStyle.Fill;
|
tableLayoutPanel5.Dock = DockStyle.Fill;
|
||||||
tableLayoutPanel5.Location = new Point(3, 50);
|
tableLayoutPanel5.Location = new Point(3, 52);
|
||||||
tableLayoutPanel5.Name = "tableLayoutPanel5";
|
tableLayoutPanel5.Name = "tableLayoutPanel5";
|
||||||
tableLayoutPanel5.RowCount = 1;
|
tableLayoutPanel5.RowCount = 1;
|
||||||
tableLayoutPanel5.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
|
tableLayoutPanel5.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
|
||||||
tableLayoutPanel5.Size = new Size(954, 41);
|
tableLayoutPanel5.Size = new Size(954, 43);
|
||||||
tableLayoutPanel5.TabIndex = 0;
|
tableLayoutPanel5.TabIndex = 6;
|
||||||
//
|
//
|
||||||
// textBoxAddressCity
|
// textBoxAddressCity
|
||||||
//
|
//
|
||||||
|
|
@ -264,7 +243,7 @@
|
||||||
textBoxAddressCity.Name = "textBoxAddressCity";
|
textBoxAddressCity.Name = "textBoxAddressCity";
|
||||||
textBoxAddressCity.PlaceholderText = "Springwood";
|
textBoxAddressCity.PlaceholderText = "Springwood";
|
||||||
textBoxAddressCity.Size = new Size(661, 39);
|
textBoxAddressCity.Size = new Size(661, 39);
|
||||||
textBoxAddressCity.TabIndex = 0;
|
textBoxAddressCity.TabIndex = 1;
|
||||||
//
|
//
|
||||||
// comboBoxAddressState
|
// comboBoxAddressState
|
||||||
//
|
//
|
||||||
|
|
@ -273,16 +252,16 @@
|
||||||
comboBoxAddressState.Location = new Point(670, 3);
|
comboBoxAddressState.Location = new Point(670, 3);
|
||||||
comboBoxAddressState.Name = "comboBoxAddressState";
|
comboBoxAddressState.Name = "comboBoxAddressState";
|
||||||
comboBoxAddressState.Size = new Size(137, 40);
|
comboBoxAddressState.Size = new Size(137, 40);
|
||||||
comboBoxAddressState.TabIndex = 1;
|
comboBoxAddressState.TabIndex = 2;
|
||||||
//
|
//
|
||||||
// maskedTextBoxAddressZipcode
|
// maskedTextBoxAddressPostal
|
||||||
//
|
//
|
||||||
maskedTextBoxAddressZipcode.Dock = DockStyle.Fill;
|
maskedTextBoxAddressPostal.Dock = DockStyle.Fill;
|
||||||
maskedTextBoxAddressZipcode.Location = new Point(813, 3);
|
maskedTextBoxAddressPostal.Location = new Point(813, 3);
|
||||||
maskedTextBoxAddressZipcode.Mask = "00000";
|
maskedTextBoxAddressPostal.Mask = "00000";
|
||||||
maskedTextBoxAddressZipcode.Name = "maskedTextBoxAddressZipcode";
|
maskedTextBoxAddressPostal.Name = "maskedTextBoxAddressPostal";
|
||||||
maskedTextBoxAddressZipcode.Size = new Size(138, 39);
|
maskedTextBoxAddressPostal.Size = new Size(138, 39);
|
||||||
maskedTextBoxAddressZipcode.TabIndex = 2;
|
maskedTextBoxAddressPostal.TabIndex = 3;
|
||||||
//
|
//
|
||||||
// textBoxAddressStreet
|
// textBoxAddressStreet
|
||||||
//
|
//
|
||||||
|
|
@ -297,10 +276,10 @@
|
||||||
//
|
//
|
||||||
groupBoxNotes.Controls.Add(richTextBoxNotes);
|
groupBoxNotes.Controls.Add(richTextBoxNotes);
|
||||||
groupBoxNotes.Dock = DockStyle.Fill;
|
groupBoxNotes.Dock = DockStyle.Fill;
|
||||||
groupBoxNotes.Location = new Point(3, 355);
|
groupBoxNotes.Location = new Point(3, 313);
|
||||||
groupBoxNotes.Name = "groupBoxNotes";
|
groupBoxNotes.Name = "groupBoxNotes";
|
||||||
groupBoxNotes.Size = new Size(1116, 194);
|
groupBoxNotes.Size = new Size(1116, 236);
|
||||||
groupBoxNotes.TabIndex = 1;
|
groupBoxNotes.TabIndex = 0;
|
||||||
groupBoxNotes.TabStop = false;
|
groupBoxNotes.TabStop = false;
|
||||||
groupBoxNotes.Text = "Notes";
|
groupBoxNotes.Text = "Notes";
|
||||||
//
|
//
|
||||||
|
|
@ -309,8 +288,8 @@
|
||||||
richTextBoxNotes.Dock = DockStyle.Fill;
|
richTextBoxNotes.Dock = DockStyle.Fill;
|
||||||
richTextBoxNotes.Location = new Point(3, 35);
|
richTextBoxNotes.Location = new Point(3, 35);
|
||||||
richTextBoxNotes.Name = "richTextBoxNotes";
|
richTextBoxNotes.Name = "richTextBoxNotes";
|
||||||
richTextBoxNotes.Size = new Size(1110, 156);
|
richTextBoxNotes.Size = new Size(1110, 198);
|
||||||
richTextBoxNotes.TabIndex = 0;
|
richTextBoxNotes.TabIndex = 9;
|
||||||
richTextBoxNotes.Text = "";
|
richTextBoxNotes.Text = "";
|
||||||
//
|
//
|
||||||
// tableLayoutPanel3
|
// tableLayoutPanel3
|
||||||
|
|
@ -337,7 +316,7 @@
|
||||||
buttonOkay.Margin = new Padding(3, 3, 3, 15);
|
buttonOkay.Margin = new Padding(3, 3, 3, 15);
|
||||||
buttonOkay.Name = "buttonOkay";
|
buttonOkay.Name = "buttonOkay";
|
||||||
buttonOkay.Size = new Size(194, 57);
|
buttonOkay.Size = new Size(194, 57);
|
||||||
buttonOkay.TabIndex = 0;
|
buttonOkay.TabIndex = 10;
|
||||||
buttonOkay.Text = "Okay";
|
buttonOkay.Text = "Okay";
|
||||||
buttonOkay.UseVisualStyleBackColor = true;
|
buttonOkay.UseVisualStyleBackColor = true;
|
||||||
//
|
//
|
||||||
|
|
@ -348,7 +327,8 @@
|
||||||
buttonCancel.Margin = new Padding(3, 3, 3, 15);
|
buttonCancel.Margin = new Padding(3, 3, 3, 15);
|
||||||
buttonCancel.Name = "buttonCancel";
|
buttonCancel.Name = "buttonCancel";
|
||||||
buttonCancel.Size = new Size(194, 57);
|
buttonCancel.Size = new Size(194, 57);
|
||||||
buttonCancel.TabIndex = 1;
|
buttonCancel.TabIndex = 99;
|
||||||
|
buttonCancel.TabStop = false;
|
||||||
buttonCancel.Text = "Cancel";
|
buttonCancel.Text = "Cancel";
|
||||||
buttonCancel.UseVisualStyleBackColor = true;
|
buttonCancel.UseVisualStyleBackColor = true;
|
||||||
//
|
//
|
||||||
|
|
@ -369,7 +349,6 @@
|
||||||
ClientSize = new Size(1128, 665);
|
ClientSize = new Size(1128, 665);
|
||||||
Controls.Add(groupBoxNewClient);
|
Controls.Add(groupBoxNewClient);
|
||||||
Name = "ClientForm";
|
Name = "ClientForm";
|
||||||
Text = "Client";
|
|
||||||
groupBoxNewClient.ResumeLayout(false);
|
groupBoxNewClient.ResumeLayout(false);
|
||||||
tableLayoutPanel1.ResumeLayout(false);
|
tableLayoutPanel1.ResumeLayout(false);
|
||||||
tableLayoutPanel2.ResumeLayout(false);
|
tableLayoutPanel2.ResumeLayout(false);
|
||||||
|
|
@ -394,12 +373,10 @@
|
||||||
private Label labelEmail;
|
private Label labelEmail;
|
||||||
private Label labelPhone;
|
private Label labelPhone;
|
||||||
private Label labelAddress;
|
private Label labelAddress;
|
||||||
private Label labelIsActive;
|
|
||||||
private GroupBox groupBoxNotes;
|
private GroupBox groupBoxNotes;
|
||||||
private TextBox textBoxName;
|
private TextBox textBoxName;
|
||||||
private TextBox textBoxCompany;
|
private TextBox textBoxCompany;
|
||||||
private TextBox textBoxEmail;
|
private TextBox textBoxEmail;
|
||||||
private ComboBox comboBoxIsActive;
|
|
||||||
private RichTextBox richTextBoxNotes;
|
private RichTextBox richTextBoxNotes;
|
||||||
private TableLayoutPanel tableLayoutPanel3;
|
private TableLayoutPanel tableLayoutPanel3;
|
||||||
private Button buttonOkay;
|
private Button buttonOkay;
|
||||||
|
|
@ -410,7 +387,7 @@
|
||||||
private TableLayoutPanel tableLayoutPanel5;
|
private TableLayoutPanel tableLayoutPanel5;
|
||||||
private TextBox textBoxAddressCity;
|
private TextBox textBoxAddressCity;
|
||||||
private ComboBox comboBoxAddressState;
|
private ComboBox comboBoxAddressState;
|
||||||
private MaskedTextBox maskedTextBoxAddressZipcode;
|
private MaskedTextBox maskedTextBoxAddressPostal;
|
||||||
private TextBox textBoxAddressStreet;
|
private TextBox textBoxAddressStreet;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,36 +1,76 @@
|
||||||
using System.Reflection.Metadata.Ecma335;
|
using trakker.Models;
|
||||||
using trakker.Models;
|
|
||||||
|
|
||||||
namespace trakker.Forms
|
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
|
public partial class ClientForm : Form
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The client instance being edited by this form.
|
||||||
|
/// </summary>
|
||||||
private readonly Client _client;
|
private readonly Client _client;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Binding source that connects the client model to the form controls.
|
||||||
|
/// </summary>
|
||||||
private BindingSource bindingSource = new BindingSource();
|
private BindingSource bindingSource = new BindingSource();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Error provider used to display validation errors next to input controls.
|
||||||
|
/// </summary>
|
||||||
private ErrorProvider errorProvider = new ErrorProvider();
|
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)
|
public ClientForm(Client client)
|
||||||
{
|
{
|
||||||
_client = client;
|
_client = client;
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
|
// Bind model properties to controls so the UI reflects and updates the model.
|
||||||
bindingSource.DataSource = _client;
|
bindingSource.DataSource = _client;
|
||||||
textBoxName.DataBindings.Add("Text", bindingSource, "Name", true);
|
textBoxName.DataBindings.Add("Text", bindingSource, "Name", true);
|
||||||
textBoxCompany.DataBindings.Add("Text", bindingSource, "Company", true);
|
textBoxCompany.DataBindings.Add("Text", bindingSource, "Company", true);
|
||||||
textBoxEmail.DataBindings.Add("Text", bindingSource, "Email", true);
|
textBoxEmail.DataBindings.Add("Text", bindingSource, "Email", true);
|
||||||
maskedTextBox_Phone.DataBindings.Add("Text", bindingSource, "Phone", true);
|
maskedTextBox_Phone.DataBindings.Add("Text", bindingSource, "Phone", true);
|
||||||
//richTextBoxAddress.DataBindings.Add("Text", bindingSource, "Address", true);
|
textBoxAddressStreet.DataBindings.Add("Text", bindingSource, "AddressStreet", true);
|
||||||
comboBoxIsActive.DataBindings.Add("Text", bindingSource, "IsActive", 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);
|
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 { } }
|
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)
|
private void textBoxName_Validating(object sender, System.ComponentModel.CancelEventArgs e)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(textBoxName.Text))
|
if (string.IsNullOrWhiteSpace(textBoxName.Text))
|
||||||
{
|
{
|
||||||
errorProvider.SetError(textBoxName, "Name is required.");
|
errorProvider.SetError(textBoxName, "Name is required.");
|
||||||
|
errorProvider.SetIconAlignment(textBoxName, ErrorIconAlignment.MiddleRight);
|
||||||
|
errorProvider.SetIconPadding(textBoxName, 2);
|
||||||
e.Cancel = true;
|
e.Cancel = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -38,5 +78,25 @@ namespace trakker.Forms
|
||||||
errorProvider.SetError(textBoxName, "");
|
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();
|
fileToolStripMenuItem = new ToolStripMenuItem();
|
||||||
MainForm_Exit_MenuItem = new ToolStripMenuItem();
|
MainForm_Exit_MenuItem = new ToolStripMenuItem();
|
||||||
MainForm_StatusStrip = new StatusStrip();
|
MainForm_StatusStrip = new StatusStrip();
|
||||||
MainForm_TabControl = new TabControl();
|
tabControlMainForm = new TabControl();
|
||||||
MainForm_TabPage1 = new TabPage();
|
MainForm_TabPage1 = new TabPage();
|
||||||
MainForm_TabPage2 = new TabPage();
|
|
||||||
button1 = new Button();
|
button1 = new Button();
|
||||||
|
MainForm_TabPage2 = new TabPage();
|
||||||
|
tableLayoutPanel1Tab2 = new TableLayoutPanel();
|
||||||
|
dataGridViewClients = new DataGridView();
|
||||||
MainForm_MenuStrip.SuspendLayout();
|
MainForm_MenuStrip.SuspendLayout();
|
||||||
MainForm_TabControl.SuspendLayout();
|
tabControlMainForm.SuspendLayout();
|
||||||
MainForm_TabPage1.SuspendLayout();
|
MainForm_TabPage1.SuspendLayout();
|
||||||
|
MainForm_TabPage2.SuspendLayout();
|
||||||
|
tableLayoutPanel1Tab2.SuspendLayout();
|
||||||
|
((System.ComponentModel.ISupportInitialize)dataGridViewClients).BeginInit();
|
||||||
SuspendLayout();
|
SuspendLayout();
|
||||||
//
|
//
|
||||||
// MainForm_MenuStrip
|
// MainForm_MenuStrip
|
||||||
|
|
@ -47,7 +52,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(1696, 40);
|
MainForm_MenuStrip.Size = new Size(1343, 40);
|
||||||
MainForm_MenuStrip.TabIndex = 0;
|
MainForm_MenuStrip.TabIndex = 0;
|
||||||
MainForm_MenuStrip.Text = "menuStrip1";
|
MainForm_MenuStrip.Text = "menuStrip1";
|
||||||
//
|
//
|
||||||
|
|
@ -68,22 +73,22 @@
|
||||||
// MainForm_StatusStrip
|
// MainForm_StatusStrip
|
||||||
//
|
//
|
||||||
MainForm_StatusStrip.ImageScalingSize = new Size(32, 32);
|
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.Name = "MainForm_StatusStrip";
|
||||||
MainForm_StatusStrip.Size = new Size(1696, 22);
|
MainForm_StatusStrip.Size = new Size(1343, 22);
|
||||||
MainForm_StatusStrip.TabIndex = 1;
|
MainForm_StatusStrip.TabIndex = 1;
|
||||||
MainForm_StatusStrip.Text = "MainForm_StatusStrip";
|
MainForm_StatusStrip.Text = "MainForm_StatusStrip";
|
||||||
//
|
//
|
||||||
// MainForm_TabControl
|
// tabControlMainForm
|
||||||
//
|
//
|
||||||
MainForm_TabControl.Controls.Add(MainForm_TabPage1);
|
tabControlMainForm.Controls.Add(MainForm_TabPage1);
|
||||||
MainForm_TabControl.Controls.Add(MainForm_TabPage2);
|
tabControlMainForm.Controls.Add(MainForm_TabPage2);
|
||||||
MainForm_TabControl.Dock = DockStyle.Fill;
|
tabControlMainForm.Dock = DockStyle.Fill;
|
||||||
MainForm_TabControl.Location = new Point(0, 40);
|
tabControlMainForm.Location = new Point(0, 40);
|
||||||
MainForm_TabControl.Name = "MainForm_TabControl";
|
tabControlMainForm.Name = "tabControlMainForm";
|
||||||
MainForm_TabControl.SelectedIndex = 0;
|
tabControlMainForm.SelectedIndex = 0;
|
||||||
MainForm_TabControl.Size = new Size(1696, 1034);
|
tabControlMainForm.Size = new Size(1343, 943);
|
||||||
MainForm_TabControl.TabIndex = 2;
|
tabControlMainForm.TabIndex = 2;
|
||||||
//
|
//
|
||||||
// MainForm_TabPage1
|
// MainForm_TabPage1
|
||||||
//
|
//
|
||||||
|
|
@ -91,21 +96,11 @@
|
||||||
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(1680, 980);
|
MainForm_TabPage1.Size = new Size(1327, 889);
|
||||||
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;
|
||||||
//
|
//
|
||||||
// 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
|
||||||
//
|
//
|
||||||
button1.Location = new Point(39, 47);
|
button1.Location = new Point(39, 47);
|
||||||
|
|
@ -116,12 +111,51 @@
|
||||||
button1.UseVisualStyleBackColor = true;
|
button1.UseVisualStyleBackColor = true;
|
||||||
button1.Click += button1_Click;
|
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
|
// MainForm
|
||||||
//
|
//
|
||||||
AutoScaleDimensions = new SizeF(13F, 32F);
|
AutoScaleDimensions = new SizeF(13F, 32F);
|
||||||
AutoScaleMode = AutoScaleMode.Font;
|
AutoScaleMode = AutoScaleMode.Font;
|
||||||
ClientSize = new Size(1696, 1096);
|
ClientSize = new Size(1343, 1005);
|
||||||
Controls.Add(MainForm_TabControl);
|
Controls.Add(tabControlMainForm);
|
||||||
Controls.Add(MainForm_StatusStrip);
|
Controls.Add(MainForm_StatusStrip);
|
||||||
Controls.Add(MainForm_MenuStrip);
|
Controls.Add(MainForm_MenuStrip);
|
||||||
MainMenuStrip = MainForm_MenuStrip;
|
MainMenuStrip = MainForm_MenuStrip;
|
||||||
|
|
@ -129,8 +163,11 @@
|
||||||
Text = "MainForm";
|
Text = "MainForm";
|
||||||
MainForm_MenuStrip.ResumeLayout(false);
|
MainForm_MenuStrip.ResumeLayout(false);
|
||||||
MainForm_MenuStrip.PerformLayout();
|
MainForm_MenuStrip.PerformLayout();
|
||||||
MainForm_TabControl.ResumeLayout(false);
|
tabControlMainForm.ResumeLayout(false);
|
||||||
MainForm_TabPage1.ResumeLayout(false);
|
MainForm_TabPage1.ResumeLayout(false);
|
||||||
|
MainForm_TabPage2.ResumeLayout(false);
|
||||||
|
tableLayoutPanel1Tab2.ResumeLayout(false);
|
||||||
|
((System.ComponentModel.ISupportInitialize)dataGridViewClients).EndInit();
|
||||||
ResumeLayout(false);
|
ResumeLayout(false);
|
||||||
PerformLayout();
|
PerformLayout();
|
||||||
}
|
}
|
||||||
|
|
@ -139,11 +176,13 @@
|
||||||
|
|
||||||
private MenuStrip MainForm_MenuStrip;
|
private MenuStrip MainForm_MenuStrip;
|
||||||
private StatusStrip MainForm_StatusStrip;
|
private StatusStrip MainForm_StatusStrip;
|
||||||
private TabControl MainForm_TabControl;
|
private TabControl tabControlMainForm;
|
||||||
private TabPage MainForm_TabPage1;
|
private TabPage MainForm_TabPage1;
|
||||||
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 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.Forms;
|
||||||
|
using trakker.Interfaces;
|
||||||
using trakker.Models;
|
using trakker.Models;
|
||||||
|
using trakker.Services;
|
||||||
|
|
||||||
namespace trakker
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="MainForm"/> class.
|
/// Initializes a new instance of the <see cref="MainForm"/> class.
|
||||||
|
|
@ -13,8 +22,22 @@ namespace trakker
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MainForm()
|
public MainForm()
|
||||||
{
|
{
|
||||||
|
|
||||||
InitializeComponent();
|
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>
|
/// <summary>
|
||||||
|
|
@ -28,14 +51,118 @@ namespace trakker
|
||||||
Application.Exit();
|
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)
|
private void button1_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
Client client = new Client();
|
var dialog = new ClientForm(new Client());
|
||||||
client.ClientId = Guid.NewGuid().ToString();
|
if (dialog.ShowDialog(this) == DialogResult.OK)
|
||||||
var dialog = new ClientForm(client);
|
{
|
||||||
dialog.ShowDialog(this);
|
Client client = dialog.Client;
|
||||||
client = dialog.Client;
|
ClientData clientData = new ClientData(connectionString);
|
||||||
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}");
|
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
|
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 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]
|
[Key]
|
||||||
public string ClientId { get; set; } = string.Empty;
|
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]
|
[Required]
|
||||||
[MaxLength(200)]
|
[MaxLength(200)]
|
||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optional company name associated with the client. Maximum length 200 characters.
|
||||||
|
/// </summary>
|
||||||
[MaxLength(200)]
|
[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]
|
[EmailAddress]
|
||||||
[MaxLength(255)]
|
[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]
|
[Phone]
|
||||||
[MaxLength(50)]
|
[MaxLength(50)]
|
||||||
public string? Phone { get; set; }
|
public string? Phone { get; set; } = string.Empty;
|
||||||
|
|
||||||
[MaxLength(500)]
|
/// <summary>
|
||||||
public string? Address { get; set; }
|
/// 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)]
|
[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;
|
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;
|
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()
|
public void UpdateTimestamp()
|
||||||
{
|
{
|
||||||
UpdatedAt = DateTime.UtcNow;
|
UpdatedAt = DateTime.UtcNow;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
using SQLitePCL;
|
||||||
|
|
||||||
namespace trakker
|
namespace trakker
|
||||||
{
|
{
|
||||||
internal static class Program
|
internal static class Program
|
||||||
|
|
@ -8,6 +10,9 @@ namespace trakker
|
||||||
[STAThread]
|
[STAThread]
|
||||||
static void Main()
|
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,
|
// To customize application configuration such as set high DPI settings or default font,
|
||||||
// see https://aka.ms/applicationconfiguration.
|
// see https://aka.ms/applicationconfiguration.
|
||||||
ApplicationConfiguration.Initialize();
|
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>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Interfaces\" />
|
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="10.0.7" />
|
||||||
<Folder Include="Services\" />
|
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="3.0.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
Loading…
Reference in New Issue