Compare commits

...

2 Commits

Author SHA1 Message Date
c0d3.m0nk3y 222a37c0de Continued development 2026-05-05 14:55:25 -04:00
c0d3.m0nk3y 9f0303fbef Created ClientForm UI 2026-05-04 19:11:25 -04:00
16 changed files with 1135 additions and 0 deletions

1
DBVERSION Normal file
View File

@ -0,0 +1 @@
1.0.0

416
Forms/ClientForm.Designer.cs generated Normal file
View File

@ -0,0 +1,416 @@
namespace trakker.Forms
{
partial class ClientForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
groupBoxNewClient = new GroupBox();
tableLayoutPanel1 = new TableLayoutPanel();
tableLayoutPanel2 = new TableLayoutPanel();
labelName = new Label();
labelCompany = new Label();
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();
textBoxAddressStreet = new TextBox();
groupBoxNotes = new GroupBox();
richTextBoxNotes = new RichTextBox();
tableLayoutPanel3 = new TableLayoutPanel();
buttonOkay = new Button();
buttonCancel = new Button();
labelCreatedUpdatedDT = new Label();
groupBoxNewClient.SuspendLayout();
tableLayoutPanel1.SuspendLayout();
tableLayoutPanel2.SuspendLayout();
tableLayoutPanel4.SuspendLayout();
tableLayoutPanel5.SuspendLayout();
groupBoxNotes.SuspendLayout();
tableLayoutPanel3.SuspendLayout();
SuspendLayout();
//
// groupBoxNewClient
//
groupBoxNewClient.Controls.Add(tableLayoutPanel1);
groupBoxNewClient.Dock = DockStyle.Fill;
groupBoxNewClient.Location = new Point(0, 0);
groupBoxNewClient.Name = "groupBoxNewClient";
groupBoxNewClient.Size = new Size(1128, 665);
groupBoxNewClient.TabIndex = 0;
groupBoxNewClient.TabStop = false;
groupBoxNewClient.Text = "New Client";
//
// tableLayoutPanel1
//
tableLayoutPanel1.ColumnCount = 1;
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
tableLayoutPanel1.Controls.Add(tableLayoutPanel2, 0, 0);
tableLayoutPanel1.Controls.Add(groupBoxNotes, 0, 1);
tableLayoutPanel1.Controls.Add(tableLayoutPanel3, 0, 2);
tableLayoutPanel1.Dock = DockStyle.Fill;
tableLayoutPanel1.Location = new Point(3, 35);
tableLayoutPanel1.Name = "tableLayoutPanel1";
tableLayoutPanel1.RowCount = 3;
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Absolute, 200F));
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Absolute, 75F));
tableLayoutPanel1.Size = new Size(1122, 627);
tableLayoutPanel1.TabIndex = 0;
//
// tableLayoutPanel2
//
tableLayoutPanel2.ColumnCount = 2;
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 150F));
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
tableLayoutPanel2.Controls.Add(labelName, 0, 0);
tableLayoutPanel2.Controls.Add(labelCompany, 0, 1);
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.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;
//
// labelName
//
labelName.AutoSize = true;
labelName.Dock = DockStyle.Fill;
labelName.Location = new Point(3, 0);
labelName.Name = "labelName";
labelName.Size = new Size(144, 50);
labelName.TabIndex = 0;
labelName.Text = "Name *";
//
// labelCompany
//
labelCompany.AutoSize = true;
labelCompany.Dock = DockStyle.Fill;
labelCompany.Location = new Point(3, 50);
labelCompany.Name = "labelCompany";
labelCompany.Size = new Size(144, 50);
labelCompany.TabIndex = 1;
labelCompany.Text = "Company";
//
// labelEmail
//
labelEmail.AutoSize = true;
labelEmail.Dock = DockStyle.Fill;
labelEmail.Location = new Point(3, 100);
labelEmail.Name = "labelEmail";
labelEmail.Size = new Size(144, 50);
labelEmail.TabIndex = 2;
labelEmail.Text = "Email *";
//
// labelPhone
//
labelPhone.AutoSize = true;
labelPhone.Dock = DockStyle.Fill;
labelPhone.Location = new Point(3, 150);
labelPhone.Name = "labelPhone";
labelPhone.Size = new Size(144, 50);
labelPhone.TabIndex = 3;
labelPhone.Text = "Phone";
//
// labelAddress
//
labelAddress.AutoSize = true;
labelAddress.Dock = DockStyle.Fill;
labelAddress.Location = new Point(3, 200);
labelAddress.Name = "labelAddress";
labelAddress.Size = new Size(144, 100);
labelAddress.TabIndex = 4;
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.Location = new Point(153, 3);
textBoxName.Name = "textBoxName";
textBoxName.PlaceholderText = "Nancy Thompson";
textBoxName.Size = new Size(960, 39);
textBoxName.TabIndex = 6;
textBoxName.Validating += textBoxName_Validating;
//
// textBoxCompany
//
textBoxCompany.Dock = DockStyle.Fill;
textBoxCompany.Location = new Point(153, 53);
textBoxCompany.Name = "textBoxCompany";
textBoxCompany.Size = new Size(960, 39);
textBoxCompany.TabIndex = 7;
//
// textBoxEmail
//
textBoxEmail.Dock = DockStyle.Fill;
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;
//
// maskedTextBox_Phone
//
maskedTextBox_Phone.Dock = DockStyle.Fill;
maskedTextBox_Phone.Location = new Point(153, 153);
maskedTextBox_Phone.Mask = "(999) 000-0000";
maskedTextBox_Phone.Name = "maskedTextBox_Phone";
maskedTextBox_Phone.Size = new Size(960, 39);
maskedTextBox_Phone.TabIndex = 12;
//
// tableLayoutPanel4
//
tableLayoutPanel4.ColumnCount = 1;
tableLayoutPanel4.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
tableLayoutPanel4.Controls.Add(tableLayoutPanel5, 0, 1);
tableLayoutPanel4.Controls.Add(textBoxAddressStreet, 0, 0);
tableLayoutPanel4.Dock = DockStyle.Fill;
tableLayoutPanel4.Location = new Point(153, 203);
tableLayoutPanel4.Name = "tableLayoutPanel4";
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;
//
// tableLayoutPanel5
//
tableLayoutPanel5.ColumnCount = 3;
tableLayoutPanel5.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 70F));
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(comboBoxAddressState, 1, 0);
tableLayoutPanel5.Controls.Add(maskedTextBoxAddressZipcode, 2, 0);
tableLayoutPanel5.Dock = DockStyle.Fill;
tableLayoutPanel5.Location = new Point(3, 50);
tableLayoutPanel5.Name = "tableLayoutPanel5";
tableLayoutPanel5.RowCount = 1;
tableLayoutPanel5.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
tableLayoutPanel5.Size = new Size(954, 41);
tableLayoutPanel5.TabIndex = 0;
//
// textBoxAddressCity
//
textBoxAddressCity.Dock = DockStyle.Fill;
textBoxAddressCity.Location = new Point(3, 3);
textBoxAddressCity.Name = "textBoxAddressCity";
textBoxAddressCity.PlaceholderText = "Springwood";
textBoxAddressCity.Size = new Size(661, 39);
textBoxAddressCity.TabIndex = 0;
//
// comboBoxAddressState
//
comboBoxAddressState.Dock = DockStyle.Fill;
comboBoxAddressState.FormattingEnabled = true;
comboBoxAddressState.Location = new Point(670, 3);
comboBoxAddressState.Name = "comboBoxAddressState";
comboBoxAddressState.Size = new Size(137, 40);
comboBoxAddressState.TabIndex = 1;
//
// maskedTextBoxAddressZipcode
//
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;
//
// textBoxAddressStreet
//
textBoxAddressStreet.Dock = DockStyle.Fill;
textBoxAddressStreet.Location = new Point(3, 3);
textBoxAddressStreet.Name = "textBoxAddressStreet";
textBoxAddressStreet.PlaceholderText = "1428 Elm Street";
textBoxAddressStreet.Size = new Size(954, 39);
textBoxAddressStreet.TabIndex = 1;
//
// groupBoxNotes
//
groupBoxNotes.Controls.Add(richTextBoxNotes);
groupBoxNotes.Dock = DockStyle.Fill;
groupBoxNotes.Location = new Point(3, 355);
groupBoxNotes.Name = "groupBoxNotes";
groupBoxNotes.Size = new Size(1116, 194);
groupBoxNotes.TabIndex = 1;
groupBoxNotes.TabStop = false;
groupBoxNotes.Text = "Notes";
//
// richTextBoxNotes
//
richTextBoxNotes.Dock = DockStyle.Fill;
richTextBoxNotes.Location = new Point(3, 35);
richTextBoxNotes.Name = "richTextBoxNotes";
richTextBoxNotes.Size = new Size(1110, 156);
richTextBoxNotes.TabIndex = 0;
richTextBoxNotes.Text = "";
//
// tableLayoutPanel3
//
tableLayoutPanel3.ColumnCount = 3;
tableLayoutPanel3.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
tableLayoutPanel3.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 200F));
tableLayoutPanel3.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 200F));
tableLayoutPanel3.Controls.Add(buttonOkay, 1, 0);
tableLayoutPanel3.Controls.Add(buttonCancel, 2, 0);
tableLayoutPanel3.Controls.Add(labelCreatedUpdatedDT, 0, 0);
tableLayoutPanel3.Dock = DockStyle.Fill;
tableLayoutPanel3.Location = new Point(3, 555);
tableLayoutPanel3.Name = "tableLayoutPanel3";
tableLayoutPanel3.RowCount = 1;
tableLayoutPanel3.RowStyles.Add(new RowStyle(SizeType.Absolute, 75F));
tableLayoutPanel3.Size = new Size(1116, 69);
tableLayoutPanel3.TabIndex = 2;
//
// buttonOkay
//
buttonOkay.Dock = DockStyle.Fill;
buttonOkay.Location = new Point(719, 3);
buttonOkay.Margin = new Padding(3, 3, 3, 15);
buttonOkay.Name = "buttonOkay";
buttonOkay.Size = new Size(194, 57);
buttonOkay.TabIndex = 0;
buttonOkay.Text = "Okay";
buttonOkay.UseVisualStyleBackColor = true;
//
// buttonCancel
//
buttonCancel.Dock = DockStyle.Fill;
buttonCancel.Location = new Point(919, 3);
buttonCancel.Margin = new Padding(3, 3, 3, 15);
buttonCancel.Name = "buttonCancel";
buttonCancel.Size = new Size(194, 57);
buttonCancel.TabIndex = 1;
buttonCancel.Text = "Cancel";
buttonCancel.UseVisualStyleBackColor = true;
//
// labelCreatedUpdatedDT
//
labelCreatedUpdatedDT.AutoSize = true;
labelCreatedUpdatedDT.Dock = DockStyle.Fill;
labelCreatedUpdatedDT.Location = new Point(3, 0);
labelCreatedUpdatedDT.Name = "labelCreatedUpdatedDT";
labelCreatedUpdatedDT.Size = new Size(710, 75);
labelCreatedUpdatedDT.TabIndex = 2;
labelCreatedUpdatedDT.Text = "Created: yyyy-mm-dd, Updated: yyyy-mm-dd";
//
// ClientForm
//
AutoScaleDimensions = new SizeF(13F, 32F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(1128, 665);
Controls.Add(groupBoxNewClient);
Name = "ClientForm";
Text = "Client";
groupBoxNewClient.ResumeLayout(false);
tableLayoutPanel1.ResumeLayout(false);
tableLayoutPanel2.ResumeLayout(false);
tableLayoutPanel2.PerformLayout();
tableLayoutPanel4.ResumeLayout(false);
tableLayoutPanel4.PerformLayout();
tableLayoutPanel5.ResumeLayout(false);
tableLayoutPanel5.PerformLayout();
groupBoxNotes.ResumeLayout(false);
tableLayoutPanel3.ResumeLayout(false);
tableLayoutPanel3.PerformLayout();
ResumeLayout(false);
}
#endregion
private GroupBox groupBoxNewClient;
private TableLayoutPanel tableLayoutPanel1;
private TableLayoutPanel tableLayoutPanel2;
private Label labelName;
private Label labelCompany;
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;
private Button buttonCancel;
private Label labelCreatedUpdatedDT;
private MaskedTextBox maskedTextBox_Phone;
private TableLayoutPanel tableLayoutPanel4;
private TableLayoutPanel tableLayoutPanel5;
private TextBox textBoxAddressCity;
private ComboBox comboBoxAddressState;
private MaskedTextBox maskedTextBoxAddressZipcode;
private TextBox textBoxAddressStreet;
}
}

42
Forms/ClientForm.cs Normal file
View File

@ -0,0 +1,42 @@
using System.Reflection.Metadata.Ecma335;
using trakker.Models;
namespace trakker.Forms
{
public partial class ClientForm : Form
{
private readonly Client _client;
private BindingSource bindingSource = new BindingSource();
private ErrorProvider errorProvider = new ErrorProvider();
public ClientForm(Client client)
{
_client = client;
InitializeComponent();
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);
richTextBoxNotes.DataBindings.Add("Text", bindingSource, "Notes", true);
}
public Client Client { get => _client; private set { } }
private void textBoxName_Validating(object sender, System.ComponentModel.CancelEventArgs e)
{
if (string.IsNullOrWhiteSpace(textBoxName.Text))
{
errorProvider.SetError(textBoxName, "Name is required.");
e.Cancel = true;
}
else
{
errorProvider.SetError(textBoxName, "");
}
}
}
}

120
Forms/ClientForm.resx Normal file
View File

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

149
Forms/MainForm.Designer.cs generated Normal file
View File

@ -0,0 +1,149 @@
namespace trakker
{
partial class MainForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
MainForm_MenuStrip = new MenuStrip();
fileToolStripMenuItem = new ToolStripMenuItem();
MainForm_Exit_MenuItem = new ToolStripMenuItem();
MainForm_StatusStrip = new StatusStrip();
MainForm_TabControl = new TabControl();
MainForm_TabPage1 = new TabPage();
MainForm_TabPage2 = new TabPage();
button1 = new Button();
MainForm_MenuStrip.SuspendLayout();
MainForm_TabControl.SuspendLayout();
MainForm_TabPage1.SuspendLayout();
SuspendLayout();
//
// MainForm_MenuStrip
//
MainForm_MenuStrip.ImageScalingSize = new Size(32, 32);
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.TabIndex = 0;
MainForm_MenuStrip.Text = "menuStrip1";
//
// fileToolStripMenuItem
//
fileToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { MainForm_Exit_MenuItem });
fileToolStripMenuItem.Name = "fileToolStripMenuItem";
fileToolStripMenuItem.Size = new Size(71, 36);
fileToolStripMenuItem.Text = "File";
//
// MainForm_Exit_MenuItem
//
MainForm_Exit_MenuItem.Name = "MainForm_Exit_MenuItem";
MainForm_Exit_MenuItem.Size = new Size(184, 44);
MainForm_Exit_MenuItem.Text = "Exit";
MainForm_Exit_MenuItem.Click += MainForm_Exit_MenuItem_Click;
//
// MainForm_StatusStrip
//
MainForm_StatusStrip.ImageScalingSize = new Size(32, 32);
MainForm_StatusStrip.Location = new Point(0, 1074);
MainForm_StatusStrip.Name = "MainForm_StatusStrip";
MainForm_StatusStrip.Size = new Size(1696, 22);
MainForm_StatusStrip.TabIndex = 1;
MainForm_StatusStrip.Text = "MainForm_StatusStrip";
//
// MainForm_TabControl
//
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;
//
// MainForm_TabPage1
//
MainForm_TabPage1.Controls.Add(button1);
MainForm_TabPage1.Location = new Point(8, 46);
MainForm_TabPage1.Name = "MainForm_TabPage1";
MainForm_TabPage1.Padding = new Padding(3);
MainForm_TabPage1.Size = new Size(1680, 980);
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);
button1.Name = "button1";
button1.Size = new Size(150, 46);
button1.TabIndex = 0;
button1.Text = "button1";
button1.UseVisualStyleBackColor = true;
button1.Click += button1_Click;
//
// MainForm
//
AutoScaleDimensions = new SizeF(13F, 32F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(1696, 1096);
Controls.Add(MainForm_TabControl);
Controls.Add(MainForm_StatusStrip);
Controls.Add(MainForm_MenuStrip);
MainMenuStrip = MainForm_MenuStrip;
Name = "MainForm";
Text = "MainForm";
MainForm_MenuStrip.ResumeLayout(false);
MainForm_MenuStrip.PerformLayout();
MainForm_TabControl.ResumeLayout(false);
MainForm_TabPage1.ResumeLayout(false);
ResumeLayout(false);
PerformLayout();
}
#endregion
private MenuStrip MainForm_MenuStrip;
private StatusStrip MainForm_StatusStrip;
private TabControl MainForm_TabControl;
private TabPage MainForm_TabPage1;
private TabPage MainForm_TabPage2;
private ToolStripMenuItem fileToolStripMenuItem;
private ToolStripMenuItem MainForm_Exit_MenuItem;
private Button button1;
}
}

41
Forms/MainForm.cs Normal file
View File

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

129
Forms/MainForm.resx Normal file
View File

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

44
Models/Client.cs Normal file
View File

@ -0,0 +1,44 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace trakker.Models
{
public class Client
{
[Key]
public string ClientId { get; set; } = string.Empty;
[Required]
[MaxLength(200)]
public string Name { get; set; } = string.Empty;
[MaxLength(200)]
public string? Company { get; set; }
[EmailAddress]
[MaxLength(255)]
public string? Email { get; set; }
[Phone]
[MaxLength(50)]
public string? Phone { get; set; }
[MaxLength(500)]
public string? Address { get; set; }
[MaxLength(2000)]
public string? Notes { get; set; }
public bool IsActive { get; set; } = true;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
// Optional: Helper method for updating timestamp
public void UpdateTimestamp()
{
UpdatedAt = DateTime.UtcNow;
}
}
}

57
Models/Project.cs Normal file
View File

@ -0,0 +1,57 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace trakker.Models
{
public class Project
{
[Key]
public string ProjectId { get; set; } = string.Empty;
[Required]
public string ClientId { get; set; } = string.Empty;
[Required]
[MaxLength(200)]
public string Name { get; set; } = string.Empty;
[MaxLength(1000)]
public string? Description { get; set; }
public DateOnly? StartDate { get; set; }
public DateOnly? EndDate { get; set; }
[Range(0, double.MaxValue)]
public decimal Budget { get; set; } = 0;
public ProjectStatus Status { get; set; } = ProjectStatus.Active;
[Range(0, double.MaxValue)]
public decimal? HourlyRate { get; set; }
[MaxLength(2000)]
public string? Notes { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
// Optional navigation property (for Entity Framework Core)
public Client? Client { get; set; }
// Helper method
public void UpdateTimestamp()
{
UpdatedAt = DateTime.UtcNow;
}
}
public enum ProjectStatus
{
Active = 0,
OnHold = 1,
Completed = 2,
Cancelled = 3
}
}

55
Models/TimeEntry.cs Normal file
View File

@ -0,0 +1,55 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace trakker.Models
{
public class TimeEntry
{
[Key]
public string EntryId { get; set; } = string.Empty;
[Required]
public string ProjectId { get; set; } = string.Empty;
[Required]
public string ClientId { get; set; } = string.Empty;
[Required]
public DateOnly WorkDate { get; set; }
public TimeOnly? StartTime { get; set; }
public TimeOnly? EndTime { get; set; }
[Required]
[Range(0.01, double.MaxValue)]
public decimal Hours { get; set; }
[MaxLength(1000)]
public string? Description { get; set; }
[Range(0, double.MaxValue)]
public decimal? Rate { get; set; }
// Computed column (mirrors the GENERATED ALWAYS AS clause)
public decimal Amount => (Rate ?? 0) * Hours;
public bool Billed { get; set; } = false;
public DateOnly? BilledDate { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
// Optional navigation properties (great for EF Core)
public Project? Project { get; set; }
public Client? Client { get; set; }
// Helper method
public void UpdateTimestamp()
{
UpdatedAt = DateTime.UtcNow;
}
}
}

17
Program.cs Normal file
View File

@ -0,0 +1,17 @@
namespace trakker
{
internal static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
// To customize application configuration such as set high DPI settings or default font,
// see https://aka.ms/applicationconfiguration.
ApplicationConfiguration.Initialize();
Application.Run(new MainForm());
}
}
}

7
commit.sh Normal file
View File

@ -0,0 +1,7 @@
#!/bin/bash
MSG="$1"
if [[ $MSG == "" ]]; then
MSG="Continued development"
fi
git add . && git commit -m "$MSG" && git push

21
deploy.sh Normal file
View File

@ -0,0 +1,21 @@
#!/bin/bash
BIN="../../utils/io.cdmnky.utils.replace.exe"
TS=$(date '+%Y.%m.%d.%H%M%S');
DBVERSION=`cat DBVERSION`
TRGT="./Forms/MainForm.cs"
cp $TRGT ./backup.txt
cp $TRGT ./wrk.txt
$BIN ./wrk.txt --search "[YYYY.MM.DD]" --replace $TS
$BIN ./wrk.txt --search "[N.N.N]" --replace $DBVERSION
# backup original exe before replacing...
EXEC="/c/Users/xboxo/Documents/Shared/Bin/newcle.us/newcle.us.exe"
BACK="/c/Users/xboxo/Documents/Shared/Bin/newcle.us/newcle.us-$TS.exe"
mv $EXEC $BACK
mv ./wrk.txt $TRGT
dotnet.exe build -c Release && cp ./bin/Release/net8.0-windows/newcle.us.* ~/Documents/Shared/Bin/newcle.us/
cp ./backup.txt $TRGT

17
refresh.sh Normal file
View File

@ -0,0 +1,17 @@
#!/bin/bash
TS=$(date '+%Y.%m.%d.%H%M%S');
PROD="/c/Users/xboxo/Documents/Shared/Bin/newcle.us/core.db"
DVLP="/c/Users/xboxo/source/repos/newcle.us/win/bin/Debug/net8.0-windows/core.db"
BKUP="/c/Users/xboxo/source/repos/newcle.us/win/bin/Debug/net8.0-windows/core-$TS.db"
echo "Prod... $PROD"
echo "Dvlp... $DVLP"
echo "Bkup... $BKUP"
echo "Backup current DVLP database..."
mv $DVLP $BKUP
echo "Copy PROD database to DVLP"
cp $PROD $DVLP
echo "Done"

16
trakker.csproj Normal file
View File

@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<Folder Include="Interfaces\" />
<Folder Include="Services\" />
</ItemGroup>
</Project>

3
trakker.slnx Normal file
View File

@ -0,0 +1,3 @@
<Solution>
<Project Path="trakker.csproj" />
</Solution>