first commit

master
Jeremy Hayes 2025-12-22 15:32:00 -06:00
commit de280dab2d
71 changed files with 28098 additions and 0 deletions

389
.gitignore vendored Normal file
View File

@ -0,0 +1,389 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
.nuspec/
.buildtasks/
templatesTest/
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
/src/Controls/tests/TestCases.HostApp/MauiProgram.user.cs
CustomAgentLogsTmp/
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
Generated/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
tools/**
!tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
.DS_Store
# Android Studio
.gradle/
.idea/
local.properties
# Directory Build overrides for local setups
Directory.Build.Override.props
# Only the "snapshots" directory should be added to Git, not the "snapshots-diff" directory
snapshots-diff/
#install of dotnet version
/.dotnet
.dotnet
temp
.packages
/src/Templates/.tempTemplateOutput
# TypeScript source map files (generated artifacts)
# Note: CSS map files in templates (e.g., bootstrap) are intentionally tracked
*.js.map

15
App.xaml Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8" ?>
<Application
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="FrymasterBadgeApp.App"
>
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Styles/Colors.xaml" />
<ResourceDictionary Source="Resources/Styles/Styles.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>

176
App.xaml.cs Normal file
View File

@ -0,0 +1,176 @@
using System.Diagnostics;
using FrymasterBadgeApp.Services;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.PlatformConfiguration.WindowsSpecific;
namespace FrymasterBadgeApp;
public partial class App : Microsoft.Maui.Controls.Application
{
private readonly SqlService _db;
private readonly PrinterService _printerService;
private Microsoft.Maui.Controls.TabbedPage _rootTabbedPage;
public App(SqlService db, PrinterService printerService)
{
InitializeComponent();
_db = db;
_printerService = printerService;
Debug.WriteLine("=== APP STARTUP ===");
_rootTabbedPage = new Microsoft.Maui.Controls.TabbedPage
{
Title = "Frymaster Badge System",
BarBackgroundColor = Colors.SlateGray,
BarTextColor = Colors.White,
};
_rootTabbedPage
.On<Microsoft.Maui.Controls.PlatformConfiguration.Windows>()
.SetHeaderIconsEnabled(false);
var loadingPage = new Microsoft.Maui.Controls.ContentPage
{
Title = "Loading",
BackgroundColor = Colors.DarkSlateBlue,
Content = new StackLayout
{
VerticalOptions = LayoutOptions.Center,
HorizontalOptions = LayoutOptions.Center,
Children =
{
new ActivityIndicator
{
IsRunning = true,
Color = Colors.White,
Scale = 2,
},
new Microsoft.Maui.Controls.Label
{
Text = "Loading companies...",
TextColor = Colors.White,
FontSize = 18,
},
},
},
};
_rootTabbedPage.Children.Add(loadingPage);
MainPage = _rootTabbedPage;
Debug.WriteLine("App: Resources loaded, loading page added, TabbedPage set as MainPage");
}
protected override async void OnStart()
{
base.OnStart();
Debug.WriteLine("App: OnStart - calling LoadTabsAsync");
await LoadTabsAsync();
}
private async Task LoadTabsAsync()
{
Debug.WriteLine("LoadTabsAsync: ENTERED");
try
{
Debug.WriteLine("LoadTabsAsync: Querying database for companies...");
var companies = await Task.Run(() => _db.Query("SELECT * FROM dbo.Companies", null));
Debug.WriteLine($"LoadTabsAsync: Query returned {companies?.Count ?? 0} companies");
await MainThread.InvokeOnMainThreadAsync(async () =>
{
Debug.WriteLine("LoadTabsAsync: On main thread - delay");
await Task.Delay(200);
Debug.WriteLine(
$"LoadTabsAsync: Current children count before clear: {_rootTabbedPage.Children.Count}"
);
_rootTabbedPage.Children.Clear();
Debug.WriteLine("LoadTabsAsync: Children cleared");
if (companies == null || !companies.Any())
{
Debug.WriteLine("LoadTabsAsync: No companies - adding setup page");
var setupPage = new Microsoft.Maui.Controls.ContentPage
{
Title = "Setup",
BackgroundColor = Colors.Purple,
Content = new StackLayout
{
VerticalOptions = LayoutOptions.Center,
HorizontalOptions = LayoutOptions.Center,
Children =
{
new Microsoft.Maui.Controls.Label
{
Text = "No companies found - click to manage",
TextColor = Colors.White,
FontSize = 24,
},
new Microsoft.Maui.Controls.Button
{
Text = "Manage Companies",
Command = new Command(async () =>
await Microsoft.Maui.Controls.Application.Current.MainPage.Navigation.PushAsync(
new CompanyPage(_db)
)
),
},
},
},
};
_rootTabbedPage.Children.Add(setupPage);
Debug.WriteLine("LoadTabsAsync: Setup page added");
return;
}
Debug.WriteLine($"LoadTabsAsync: Adding {companies.Count} real tabs");
int added = 0;
foreach (var company in companies)
{
string companyName = company.GetValueOrDefault("Name")?.ToString() ?? "Unknown";
Debug.WriteLine($"LoadTabsAsync: Processing company '{companyName}'");
var employeePage = new EmployeePage(_db, _printerService, company);
employeePage.Title = companyName;
employeePage.BackgroundColor = Colors.LightBlue; // Temporary for visibility
employeePage.ToolbarItems.Add(
new ToolbarItem
{
Text = "Manage",
Command = new Command(async () =>
await employeePage.Navigation.PushAsync(new CompanyPage(_db))
),
}
);
_rootTabbedPage.Children.Add(employeePage);
added++;
Debug.WriteLine(
$"LoadTabsAsync: Added tab '{companyName}' ({added}/{companies.Count})"
);
}
Debug.WriteLine($"LoadTabsAsync: Finished - {added} tabs added");
});
}
catch (Exception ex)
{
Debug.WriteLine($"LoadTabsAsync: EXCEPTION - {ex.Message}");
Debug.WriteLine(ex.StackTrace);
}
}
protected override Window CreateWindow(IActivationState? activationState)
{
Debug.WriteLine("CreateWindow called");
return new Window(MainPage!);
}
}

15
AppShell.xaml Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8" ?>
<Shell
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:pages="clr-namespace:FrymasterBadgeApp"
x:Class="FrymasterBadgeApp.AppShell"
>
<TabBar x:Name="MainTabBar" />
<ShellContent
x:Name="SetupTab"
Title="Setup"
ContentTemplate="{DataTemplate pages:CompanyPage}"
/>
</Shell>

72
AppShell.xaml.cs Normal file
View File

@ -0,0 +1,72 @@
using FrymasterBadgeApp.Services;
using Microsoft.Maui.Controls;
namespace FrymasterBadgeApp;
public partial class AppShell : Shell
{
private readonly SqlService _db;
private readonly PrinterService _printerService;
public AppShell(SqlService db, PrinterService printerService)
{
InitializeComponent();
_db = db;
_printerService = printerService;
_ = LoadCompaniesAsync();
}
private async Task LoadCompaniesAsync()
{
try
{
var companies = await Task.Run(() => _db.Query("SELECT * FROM dbo.Companies", null));
await MainThread.InvokeOnMainThreadAsync(() =>
{
Items.Clear();
if (companies == null || !companies.Any())
{
CurrentItem = SetupTab;
return;
}
foreach (var company in companies)
{
string companyName = company.GetValueOrDefault("Name")?.ToString() ?? "Unknown";
var employeePage = new EmployeePage(_db, _printerService, company)
{
Title = companyName,
};
var manageBtn = new ToolbarItem
{
Text = "Manage Companies",
Order = ToolbarItemOrder.Primary,
Command = new Command(async () =>
await employeePage.Navigation.PushAsync(new CompanyPage(_db))
),
};
employeePage.ToolbarItems.Add(manageBtn);
var tab = new Tab { Title = companyName };
tab.Items.Add(new ShellContent { Content = employeePage });
MainTabBar.Items.Add(tab);
}
if (MainTabBar.Items.Count > 0)
CurrentItem = MainTabBar.Items[0];
});
}
catch (Exception ex)
{
await MainThread.InvokeOnMainThreadAsync(async () =>
await DisplayAlert("Error", $"Failed to load: {ex.Message}", "OK")
);
}
}
}

125
CompanyPage.xaml Normal file
View File

@ -0,0 +1,125 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="FrymasterBadgeApp.CompanyPage"
Title="Manage Companies"
BackgroundColor="#f1f5f9"
>
<Grid ColumnDefinitions="350,*" Padding="20">
<!-- Left: Company List -->
<Frame
BackgroundColor="White"
BorderColor="#e2e8f0"
CornerRadius="12"
HasShadow="True"
Padding="0"
>
<VerticalStackLayout>
<Label
Text="Companies"
FontSize="18"
FontAttributes="Bold"
Margin="15"
TextColor="#1e293b"
/>
<CollectionView
x:Name="CompanyList"
SelectionMode="Single"
SelectionChanged="OnCompanySelected"
Margin="10"
>
<CollectionView.ItemTemplate>
<DataTemplate>
<Frame BackgroundColor="#f8fafc" CornerRadius="8" Padding="12" Margin="5">
<Label Text="{Binding [Name]}" FontSize="16" TextColor="#1e293b" />
</Frame>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</VerticalStackLayout>
</Frame>
<!-- Right: Edit Form -->
<ScrollView Grid.Column="1" Margin="30,0,0,0">
<VerticalStackLayout Spacing="25" HorizontalOptions="Start" WidthRequest="700">
<Button
Text="+ Add New Company"
Clicked="OnAddNewClicked"
BackgroundColor="#10b981"
TextColor="White"
FontAttributes="Bold"
CornerRadius="12"
HeightRequest="50"
/>
<Frame BackgroundColor="White" CornerRadius="16" HasShadow="True" Padding="30">
<VerticalStackLayout Spacing="20">
<Label Text="Company Logo" FontSize="16" FontAttributes="Bold" TextColor="#1e293b" />
<HorizontalStackLayout HorizontalOptions="Center" Spacing="20">
<Frame BackgroundColor="#f1f5f9" CornerRadius="8" Padding="0">
<Image
x:Name="LogoPreview"
HeightRequest="120"
WidthRequest="240"
Aspect="AspectFit"
/>
</Frame>
</HorizontalStackLayout>
<Button
Text="Choose Logo Image"
Clicked="OnSelectLogoClicked"
BackgroundColor="#64748b"
TextColor="White"
CornerRadius="10"
/>
<VerticalStackLayout Spacing="15">
<Label Text="Company Name" TextColor="#475569" />
<Entry x:Name="CompName" Placeholder="Enter name" />
<Label Text="Street Address" TextColor="#475569" />
<Entry x:Name="CompAddress" Placeholder="Enter address" />
<Grid ColumnDefinitions="*,100,150" ColumnSpacing="15">
<VerticalStackLayout Grid.Column="0">
<Label Text="City" TextColor="#475569" />
<Entry x:Name="CompCity" Placeholder="City" />
</VerticalStackLayout>
<VerticalStackLayout Grid.Column="1">
<Label Text="State" TextColor="#475569" />
<Entry x:Name="CompState" Placeholder="ST" />
</VerticalStackLayout>
<VerticalStackLayout Grid.Column="2">
<Label Text="Zip" TextColor="#475569" />
<Entry x:Name="CompZip" Placeholder="Zip" />
</VerticalStackLayout>
</Grid>
</VerticalStackLayout>
<Grid ColumnDefinitions="*,*" ColumnSpacing="20">
<Button
Text="Save Company"
Clicked="OnSaveClicked"
BackgroundColor="#2563eb"
TextColor="White"
FontAttributes="Bold"
CornerRadius="12"
/>
<Button
Text="Delete Company"
Clicked="OnDeleteClicked"
BackgroundColor="#ef4444"
TextColor="White"
FontAttributes="Bold"
CornerRadius="12"
/>
</Grid>
</VerticalStackLayout>
</Frame>
</VerticalStackLayout>
</ScrollView>
</Grid>
</ContentPage>

109
CompanyPage.xaml.cs Normal file
View File

@ -0,0 +1,109 @@
using FrymasterBadgeApp.Services;
using Microsoft.Data.SqlClient;
namespace FrymasterBadgeApp;
public partial class CompanyPage : ContentPage
{
private readonly SqlService _db;
private Dictionary<string, object>? _selectedCompany;
private string _tempLogoPath = "";
public CompanyPage(SqlService db)
{
InitializeComponent();
_db = db;
LoadCompanies();
}
private string GetLogoDirectory() =>
Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
"FrymasterBadgeApp",
"Logos"
);
private void LoadCompanies() =>
CompanyList.ItemsSource = _db.Query("SELECT * FROM Companies", null);
private void OnCompanySelected(object sender, SelectedItemChangedEventArgs e)
{
_selectedCompany = e.SelectedItem as Dictionary<string, object>;
if (_selectedCompany == null)
return;
CompName.Text = _selectedCompany["Name"]?.ToString();
CompAddress.Text = _selectedCompany["Address"]?.ToString();
_tempLogoPath = _selectedCompany.GetValueOrDefault("logo")?.ToString() ?? "";
LogoPreview.Source = File.Exists(_tempLogoPath)
? ImageSource.FromFile(_tempLogoPath)
: null;
}
private async void OnSelectLogoClicked(object sender, EventArgs e)
{
var result = await FilePicker.Default.PickAsync(
new PickOptions { PickerTitle = "Select Logo", FileTypes = FilePickerFileType.Images }
);
if (result != null)
{
_tempLogoPath = result.FullPath;
LogoPreview.Source = ImageSource.FromFile(_tempLogoPath);
}
}
private void OnSaveClicked(object sender, EventArgs e)
{
string logoDir = GetLogoDirectory();
if (!Directory.Exists(logoDir))
Directory.CreateDirectory(logoDir);
string finalLogoPath = _tempLogoPath;
if (!string.IsNullOrEmpty(_tempLogoPath) && !_tempLogoPath.Contains(logoDir))
{
string dest = Path.Combine(logoDir, Path.GetFileName(_tempLogoPath));
File.Copy(_tempLogoPath, dest, true);
finalLogoPath = dest;
}
if (_selectedCompany == null)
{
_db.Execute(
"INSERT INTO Companies (Name, Address, logo) VALUES (@n, @a, @l)",
new SqlParameter[]
{
new("@n", CompName.Text),
new("@a", CompAddress.Text),
new("@l", finalLogoPath),
}
);
}
else
{
_db.Execute(
"UPDATE Companies SET Name=@n, Address=@a, logo=@l WHERE ID=@id",
new SqlParameter[]
{
new("@n", CompName.Text),
new("@a", CompAddress.Text),
new("@l", finalLogoPath),
new("@id", _selectedCompany["ID"]),
}
);
}
LoadCompanies();
}
private void OnAddNewClicked(object sender, EventArgs e)
{
_selectedCompany = null;
CompName.Text = CompAddress.Text = "";
LogoPreview.Source = null;
}
private void OnDeleteClicked(
object sender,
EventArgs e
) { /* Delete logic */
}
}

View File

@ -0,0 +1,17 @@
@inherits LayoutComponentBase
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<main>
<div class="top-row px-4">
<a href="https://learn.microsoft.com/aspnet/core/" target="_blank">About</a>
</div>
<article class="content px-4">
@Body
</article>
</main>
</div>

View File

@ -0,0 +1,77 @@
.page {
position: relative;
display: flex;
flex-direction: column;
}
main {
flex: 1;
}
.sidebar {
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
}
.top-row {
background-color: #f7f7f7;
border-bottom: 1px solid #d6d5d5;
justify-content: flex-end;
height: 3.5rem;
display: flex;
align-items: center;
}
.top-row ::deep a, .top-row ::deep .btn-link {
white-space: nowrap;
margin-left: 1.5rem;
text-decoration: none;
}
.top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
text-decoration: underline;
}
.top-row ::deep a:first-child {
overflow: hidden;
text-overflow: ellipsis;
}
@media (max-width: 640.98px) {
.top-row {
justify-content: space-between;
}
.top-row ::deep a, .top-row ::deep .btn-link {
margin-left: 0;
}
}
@media (min-width: 641px) {
.page {
flex-direction: row;
}
.sidebar {
width: 250px;
height: 100vh;
position: sticky;
top: 0;
}
.top-row {
position: sticky;
top: 0;
z-index: 1;
}
.top-row.auth ::deep a:first-child {
flex: 1;
text-align: right;
width: 0;
}
.top-row, article {
padding-left: 2rem !important;
padding-right: 1.5rem !important;
}
}

View File

@ -0,0 +1,27 @@
<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="">FrymasterBadgeApp</a>
</div>
</div>
<input type="checkbox" title="Navigation menu" class="navbar-toggler" />
<div class="nav-scrollable" onclick="document.querySelector('.navbar-toggler').click()">
<nav class="flex-column">
<div class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="counter">
<span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Counter
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="weather">
<span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Weather
</NavLink>
</div>
</nav>
</div>

View File

@ -0,0 +1,101 @@
.navbar-toggler {
appearance: none;
cursor: pointer;
width: 3.5rem;
height: 2.5rem;
color: white;
position: absolute;
top: 0.5rem;
right: 1rem;
border: 1px solid rgba(255, 255, 255, 0.1);
background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") no-repeat center/1.75rem rgba(255, 255, 255, 0.1);
}
.navbar-toggler:checked {
background-color: rgba(255, 255, 255, 0.5);
}
.top-row {
height: 3.5rem;
background-color: rgba(0,0,0,0.4);
}
.navbar-brand {
font-size: 1.1rem;
}
.bi {
display: inline-block;
position: relative;
width: 1.25rem;
height: 1.25rem;
margin-right: 0.75rem;
top: -1px;
background-size: cover;
}
.bi-house-door-fill-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E");
}
.bi-plus-square-fill-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E");
}
.bi-list-nested-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
}
.nav-item {
font-size: 0.9rem;
padding-bottom: 0.5rem;
}
.nav-item:first-of-type {
padding-top: 1rem;
}
.nav-item:last-of-type {
padding-bottom: 1rem;
}
.nav-item ::deep a {
color: #d7d7d7;
border-radius: 4px;
height: 3rem;
display: flex;
align-items: center;
line-height: 3rem;
}
.nav-item ::deep a.active {
background-color: rgba(255,255,255,0.37);
color: white;
}
.nav-item ::deep a:hover {
background-color: rgba(255,255,255,0.1);
color: white;
}
.nav-scrollable {
display: none;
}
.navbar-toggler:checked ~ .nav-scrollable {
display: block;
}
@media (min-width: 641px) {
.navbar-toggler {
display: none;
}
.nav-scrollable {
/* Never collapse the sidebar for wide screens */
display: block;
/* Allow sidebar to scroll for tall menus */
height: calc(100vh - 3.5rem);
overflow-y: auto;
}
}

View File

@ -0,0 +1,16 @@
@page "/counter"
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}

View File

@ -0,0 +1,5 @@
@page "/"
<h1>Hello, world!</h1>
Welcome to your new app.

View File

@ -0,0 +1,61 @@
@page "/weather"
<h1>Weather</h1>
<p>This component demonstrates showing data.</p>
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
@code {
private WeatherForecast[]? forecasts;
protected override async Task OnInitializedAsync()
{
// Simulate asynchronous loading to demonstrate a loading indicator
await Task.Delay(500);
var startDate = DateOnly.FromDateTime(DateTime.Now);
var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };
forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
}).ToArray();
}
private class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public string? Summary { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
}

6
Components/Routes.razor Normal file
View File

@ -0,0 +1,6 @@
<Router AppAssembly="@typeof(MauiProgram).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
</Router>

View File

@ -0,0 +1,9 @@
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using FrymasterBadgeApp
@using FrymasterBadgeApp.Components

241
EmployeePage.xaml Normal file
View File

@ -0,0 +1,241 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="FrymasterBadgeApp.EmployeePage"
BackgroundColor="#0f172a"
>
<Grid RowDefinitions="80,*" ColumnDefinitions="400,*">
<!-- Header -->
<Border Grid.ColumnSpan="2" BackgroundColor="#1e293b" StrokeThickness="0" Padding="20,0">
<Grid ColumnDefinitions="Auto,*">
<Label
Text="FRYMASTER BADGE SYSTEM"
FontSize="20"
FontAttributes="Bold"
TextColor="White"
VerticalOptions="Center"
/>
<SearchBar
Grid.Column="1"
x:Name="EmployeeSearchBar"
Placeholder="Search employees..."
TextColor="White"
BackgroundColor="#334155"
PlaceholderColor="#94a3b8"
Margin="10,0"
VerticalOptions="Center"
TextChanged="OnSearchTextChanged"
/>
</Grid>
</Border>
<!-- Left: Employee List -->
<Border Grid.Row="1" BackgroundColor="#1e293b" StrokeThickness="0">
<CollectionView
x:Name="EmployeeList"
SelectionMode="Single"
SelectionChanged="OnSelectionChanged"
BackgroundColor="Transparent"
Margin="10"
>
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical" ItemSpacing="8" />
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<Border
BackgroundColor="#334155"
Padding="15"
StrokeShape="RoundRectangle 8"
Shadow="Shadow 0 4 8 0.2 Black"
>
<Label
Text="{Binding [Data2]}"
TextColor="White"
FontSize="16"
FontAttributes="Bold"
/>
</Border>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</Border>
<!-- Right: Preview & Controls -->
<ScrollView Grid.Row="1" Grid.Column="1" Padding="40,20">
<VerticalStackLayout Spacing="25" HorizontalOptions="Center">
<!-- Company Logo (used in badge preview) -->
<Image
x:Name="PreviewLogoFront"
HeightRequest="60"
Aspect="AspectFit"
HorizontalOptions="Center"
/>
<!-- Badge Type & Guest Selector -->
<Border
BackgroundColor="#1e293b"
Padding="20"
StrokeShape="RoundRectangle 12"
Shadow="Shadow 0 4 10 0.3 Black"
>
<VerticalStackLayout Spacing="15">
<HorizontalStackLayout Spacing="15" HorizontalOptions="Center">
<Label
Text="Badge Type"
TextColor="White"
FontAttributes="Bold"
FontSize="16"
VerticalOptions="Center"
/>
<Picker
x:Name="BadgeTypePicker"
Title="Select type"
WidthRequest="250"
TextColor="White"
BackgroundColor="#334155"
SelectedIndexChanged="OnBadgeTypeChanged"
>
<Picker.Items>
<x:String>Office</x:String>
<x:String>Plant</x:String>
<x:String>Maintenance</x:String>
<x:String>Guest</x:String>
<x:String>Visitor</x:String>
<x:String>Vendor</x:String>
</Picker.Items>
</Picker>
</HorizontalStackLayout>
<HorizontalStackLayout
x:Name="GuestImageSelector"
IsVisible="False"
Spacing="15"
HorizontalOptions="Center"
>
<Label
Text="Guest Image"
TextColor="White"
FontAttributes="Bold"
FontSize="16"
VerticalOptions="Center"
/>
<Picker
x:Name="GuestImagePicker"
Title="Select image"
WidthRequest="250"
TextColor="White"
BackgroundColor="#334155"
SelectedIndexChanged="OnGuestImageChanged"
>
<Picker.Items>
<x:String>Guest1.png</x:String>
<x:String>Guest2.png</x:String>
<x:String>Guest3.png</x:String>
</Picker.Items>
</Picker>
</HorizontalStackLayout>
</VerticalStackLayout>
</Border>
<!-- Badge Preview (Front + Back side-by-side) -->
<Border
x:Name="PreviewFrame"
BackgroundColor="Transparent"
Padding="30"
StrokeShape="RoundRectangle 20"
Shadow="Shadow 0 8 20 0.4 Black"
HorizontalOptions="Center"
>
<!-- Content dynamically set in code-behind -->
</Border>
<!-- Action Buttons -->
<HorizontalStackLayout Spacing="20" HorizontalOptions="Center">
<Button
Text="📷 Edit / Capture Photo"
Clicked="OnTakePhotoClicked"
BackgroundColor="#3b82f6"
TextColor="White"
FontAttributes="Bold"
CornerRadius="12"
HeightRequest="55"
WidthRequest="220"
/>
<Button
Text="🖨️ Print Badge"
Clicked="OnPrintClicked"
BackgroundColor="#6366f1"
TextColor="White"
FontAttributes="Bold"
CornerRadius="12"
HeightRequest="55"
WidthRequest="220"
/>
</HorizontalStackLayout>
</VerticalStackLayout>
</ScrollView>
</Grid>
<!-- Photo Editor Overlay -->
<Grid x:Name="PhotoEditorOverlay" IsVisible="False" BackgroundColor="#CC000000">
<Border
BackgroundColor="#1e293b"
Padding="30"
StrokeShape="RoundRectangle 20"
Shadow="Shadow 0 8 20 0.4 Black"
HorizontalOptions="Center"
VerticalOptions="Center"
WidthRequest="500"
>
<VerticalStackLayout Spacing="25">
<Label
Text="Adjust Photo"
FontSize="20"
FontAttributes="Bold"
TextColor="White"
HorizontalTextAlignment="Center"
/>
<Border
BackgroundColor="Black"
StrokeShape="RoundRectangle 12"
Padding="0"
IsClippedToBounds="True"
HeightRequest="300"
WidthRequest="300"
>
<Image x:Name="EditorPhotoPreview" Aspect="AspectFill">
<Image.GestureRecognizers>
<PanGestureRecognizer PanUpdated="OnPanUpdated" />
</Image.GestureRecognizers>
</Image>
</Border>
<VerticalStackLayout Spacing="8">
<Label Text="Zoom" TextColor="#cbd5e1" HorizontalTextAlignment="Center" />
<Slider x:Name="ZoomSlider" Minimum="1" Maximum="4" Value="1" />
</VerticalStackLayout>
<Grid ColumnDefinitions="*,*" ColumnSpacing="20">
<Button
Text="Cancel"
Clicked="OnCancelPhoto"
BackgroundColor="#ef4444"
TextColor="White"
CornerRadius="10"
/>
<Button
Grid.Column="1"
Text="Save Crop"
Clicked="OnApplyPhoto"
BackgroundColor="#22c55e"
TextColor="White"
CornerRadius="10"
/>
</Grid>
</VerticalStackLayout>
</Border>
</Grid>
</ContentPage>

571
EmployeePage.xaml.cs Normal file
View File

@ -0,0 +1,571 @@
using System.IO; // Explicitly included for Path
using FrymasterBadgeApp.Services;
using Microsoft.Data.SqlClient;
using Microsoft.Maui.Controls.Shapes; // For RoundRectangle
using Microsoft.Maui.Graphics;
namespace FrymasterBadgeApp;
public partial class EmployeePage : ContentPage
{
private readonly SqlService _db;
private readonly PrinterService _printerService;
private Dictionary<string, object>? _selectedEmployee;
private readonly Dictionary<string, object> _currentCompany;
private string _tempCapturePath = "";
private double _editX = 0;
private double _editY = 0;
private string _currentBadgeType = "Office";
private string _currentGuestImage = "Guest1.png";
public EmployeePage(
SqlService db,
PrinterService printerService,
Dictionary<string, object> company
)
{
InitializeComponent();
_db = db;
_printerService = printerService;
_currentCompany = company;
EditorPhotoPreview.SetBinding(
Image.ScaleProperty,
new Binding("Value", source: ZoomSlider)
);
GuestImagePicker.Items.Add("Guest1.png");
GuestImagePicker.Items.Add("Guest2.png");
GuestImagePicker.Items.Add("Guest3.png");
GuestImagePicker.SelectedIndex = 0;
GuestImagePicker.SelectedIndexChanged += OnGuestImageChanged;
BadgeTypePicker.SelectedIndexChanged += OnBadgeTypeChanged;
LoadCompanyLogo();
LoadEmployees();
}
private string GetPhotoDir()
{
string path = @"C:\FrymasterData\photos";
if (!Directory.Exists(path))
Directory.CreateDirectory(path);
return path;
}
private void LoadCompanyLogo()
{
string logoPath = _currentCompany.GetValueOrDefault("logo")?.ToString() ?? "";
if (!string.IsNullOrEmpty(logoPath) && File.Exists(logoPath))
{
PreviewLogoFront.Source = ImageSource.FromFile(logoPath);
}
}
private void LoadEmployees()
{
string sql =
"SELECT *, ISNULL(BadgeType, 'Office') AS BadgeType FROM dbo.tblData ORDER BY Data2";
var results = _db.Query(sql, null);
MainThread.BeginInvokeOnMainThread(() => EmployeeList.ItemsSource = results);
}
private void OnSearchTextChanged(object sender, TextChangedEventArgs e)
{
string search = $"%{e.NewTextValue}%";
string sql =
@"SELECT *, ISNULL(BadgeType, 'Office') AS BadgeType
FROM dbo.tblData
WHERE Data2 LIKE @s OR Data1 LIKE @s
ORDER BY Data2";
var results = _db.Query(sql, new[] { new SqlParameter("@s", search) });
MainThread.BeginInvokeOnMainThread(() =>
{
EmployeeList.SelectedItem = null;
EmployeeList.ItemsSource = results;
});
}
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
_selectedEmployee = e.CurrentSelection.FirstOrDefault() as Dictionary<string, object>;
if (_selectedEmployee == null)
{
ClearBadgePreview();
return;
}
_currentBadgeType =
_selectedEmployee.GetValueOrDefault("BadgeType")?.ToString() ?? "Office";
BadgeTypePicker.SelectedItem = _currentBadgeType;
GuestImageSelector.IsVisible = _currentBadgeType == "Guest";
RenderBadgePreview(_selectedEmployee);
}
private void OnBadgeTypeChanged(object sender, EventArgs e)
{
if (_selectedEmployee == null)
return;
_currentBadgeType = BadgeTypePicker.SelectedItem?.ToString() ?? "Office";
GuestImageSelector.IsVisible = _currentBadgeType == "Guest";
string sql = "UPDATE dbo.tblData SET BadgeType = @type WHERE Data1 = @id";
_db.Execute(
sql,
new[]
{
new SqlParameter("@type", _currentBadgeType),
new SqlParameter("@id", _selectedEmployee["Data1"]),
}
);
_selectedEmployee["BadgeType"] = _currentBadgeType;
RenderBadgePreview(_selectedEmployee);
}
private void OnGuestImageChanged(object sender, EventArgs e)
{
_currentGuestImage = GuestImagePicker.SelectedItem?.ToString() ?? "Guest1.png";
if (_selectedEmployee != null && _currentBadgeType == "Guest")
RenderBadgePreview(_selectedEmployee);
}
private void ClearBadgePreview()
{
PreviewFrame.Content = null;
}
private void RenderBadgePreview(Dictionary<string, object> emp)
{
string firstName = (emp.GetValueOrDefault("Data8")?.ToString() ?? "").Trim();
string lastName = (emp.GetValueOrDefault("Data7")?.ToString() ?? "").Trim();
string recordNum = (emp.GetValueOrDefault("Data1")?.ToString() ?? "").Trim();
string department = (emp.GetValueOrDefault("Data9")?.ToString() ?? "").Trim();
string title = (emp.GetValueOrDefault("Data10")?.ToString() ?? "").Trim();
string data5 = (emp.GetValueOrDefault("Data5")?.ToString() ?? "").Trim();
string fullName = $"{firstName} {lastName}".Trim();
string barcode = $"*{recordNum}*";
string picCode = emp.GetValueOrDefault("picCode")?.ToString() ?? "";
string photoPath = string.IsNullOrEmpty(picCode)
? ""
: System.IO.Path.Combine(GetPhotoDir(), $"{picCode}.jpg"); // Fully qualified
double scale = SafeToDouble(emp.GetValueOrDefault("cropScale"), 1.0);
double tx = SafeToDouble(emp.GetValueOrDefault("cropX"), 0.0);
double ty = SafeToDouble(emp.GetValueOrDefault("cropY"), 0.0);
bool medicalBlue = false;
bool medicalRed = false;
var mainLayout = new HorizontalStackLayout
{
Spacing = 60,
Padding = 20,
HorizontalOptions = LayoutOptions.Center,
};
// FRONT BADGE
var frontLayout = new VerticalStackLayout { Spacing = 15 };
Color frontBg = Colors.White;
Color frontBorderColor = Colors.Gray;
switch (_currentBadgeType)
{
case "Plant":
frontBg = Color.FromArgb("#e8f5e8");
frontBorderColor = Colors.Green;
break;
case "Maintenance":
frontBg = Color.FromArgb("#fff3e0");
frontBorderColor = Colors.Orange;
break;
case "Guest":
frontBg = Color.FromArgb("#f3e8ff");
frontBorderColor = Colors.Purple;
break;
case "Visitor":
case "Vendor":
frontBg = Color.FromArgb("#ffebee");
frontBorderColor = Colors.Red;
break;
}
var frontBorder = new Border
{
Stroke = frontBorderColor,
StrokeThickness = 4,
BackgroundColor = frontBg,
Padding = 25,
WidthRequest = 380,
StrokeShape = new RoundRectangle { CornerRadius = 20 },
Shadow = new Shadow
{
Brush = SolidColorBrush.Black,
Offset = new Point(0, 4),
Radius = 10f,
Opacity = 0.3f,
},
Content = frontLayout,
};
frontLayout.Children.Add(
new Image
{
Source = PreviewLogoFront.Source,
HeightRequest = 70,
HorizontalOptions = LayoutOptions.Center,
Aspect = Aspect.AspectFit,
}
);
if (_currentBadgeType == "Guest")
{
frontLayout.Children.Add(
new Image
{
Source = ImageSource.FromResource(
$"FrymasterBadgeApp.Resources.Images.{_currentGuestImage}"
),
HeightRequest = 200,
WidthRequest = 200,
Aspect = Aspect.AspectFit,
}
);
}
else if (File.Exists(photoPath))
{
var photoImg = new Image
{
Source = ImageSource.FromFile(photoPath),
HeightRequest = 200,
WidthRequest = 200,
Aspect = Aspect.AspectFill,
Scale = scale,
TranslationX = tx,
TranslationY = ty,
};
frontLayout.Children.Add(photoImg);
}
else
{
frontLayout.Children.Add(
new Label
{
Text = "No Photo",
FontSize = 18,
TextColor = Colors.Gray,
HorizontalTextAlignment = TextAlignment.Center,
}
);
}
frontLayout.Children.Add(
new Label
{
Text = fullName,
FontSize = 24,
FontAttributes = FontAttributes.Bold,
HorizontalTextAlignment = TextAlignment.Center,
TextColor = Colors.Black,
}
);
if (!string.IsNullOrEmpty(title))
frontLayout.Children.Add(
new Label
{
Text = title,
FontSize = 18,
HorizontalTextAlignment = TextAlignment.Center,
TextColor = Colors.Black,
}
);
if (!string.IsNullOrEmpty(department))
frontLayout.Children.Add(
new Label
{
Text = department,
FontSize = 16,
HorizontalTextAlignment = TextAlignment.Center,
TextColor = Colors.Gray,
}
);
mainLayout.Children.Add(frontBorder);
// BACK BADGE
var backLayout = new VerticalStackLayout { Spacing = 12 };
var backBorder = new Border
{
Stroke = Colors.Gray,
StrokeThickness = 4,
BackgroundColor = Colors.White,
Padding = 25,
WidthRequest = 380,
StrokeShape = new RoundRectangle { CornerRadius = 20 },
Shadow = new Shadow
{
Brush = SolidColorBrush.Black,
Offset = new Point(0, 4),
Radius = 10f,
Opacity = 0.3f,
},
Content = backLayout,
};
backLayout.Children.Add(
new Image
{
Source = PreviewLogoFront.Source,
HeightRequest = 50,
HorizontalOptions = LayoutOptions.Center,
Aspect = Aspect.AspectFit,
}
);
backLayout.Children.Add(
new Label
{
Text =
$"This badge is the property of {_currentCompany.GetValueOrDefault("Name")} L.L.C.",
FontSize = 14,
FontAttributes = FontAttributes.Bold,
HorizontalTextAlignment = TextAlignment.Center,
TextColor = Colors.Black,
}
);
backLayout.Children.Add(
new Label
{
Text = $"ID: {recordNum}",
FontSize = 16,
TextColor = Colors.Black,
}
);
backLayout.Children.Add(
new Label
{
Text = title,
FontSize = 16,
TextColor = Colors.Black,
}
);
backLayout.Children.Add(
new Label
{
Text = data5,
FontSize = 16,
TextColor = Colors.Black,
}
);
string address = _currentCompany.GetValueOrDefault("Address")?.ToString() ?? "";
if (!string.IsNullOrEmpty(address))
backLayout.Children.Add(
new Label
{
Text = address,
FontSize = 14,
TextColor = Colors.Black,
}
);
var locationParts = new[]
{
_currentCompany.GetValueOrDefault("City")?.ToString(),
_currentCompany.GetValueOrDefault("State")?.ToString(),
_currentCompany.GetValueOrDefault("Zip")?.ToString(),
}
.Where(p => !string.IsNullOrEmpty(p))
.ToArray();
if (locationParts.Length > 0)
backLayout.Children.Add(
new Label
{
Text = string.Join(", ", locationParts),
FontSize = 14,
TextColor = Colors.Black,
}
);
string phone = _currentCompany.GetValueOrDefault("Phone")?.ToString() ?? "";
if (!string.IsNullOrEmpty(phone))
backLayout.Children.Add(
new Label
{
Text = phone,
FontSize = 14,
TextColor = Colors.Black,
}
);
if (medicalBlue || medicalRed)
{
var medStack = new HorizontalStackLayout
{
HorizontalOptions = LayoutOptions.Center,
Spacing = 10,
};
if (medicalBlue)
medStack.Children.Add(
new Label
{
Text = "🩺",
FontSize = 36,
TextColor = Colors.Blue,
}
);
if (medicalRed)
medStack.Children.Add(
new Label
{
Text = "🩺",
FontSize = 36,
TextColor = Colors.Red,
}
);
backLayout.Children.Add(medStack);
}
if (_currentBadgeType == "Office" || _currentBadgeType == "Plant")
{
backLayout.Children.Add(
new Label
{
Text = barcode,
FontFamily = "BarcodeFont",
FontSize = 50,
HorizontalTextAlignment = TextAlignment.Center,
TextColor = Colors.Black,
}
);
}
mainLayout.Children.Add(backBorder);
PreviewFrame.Content = mainLayout;
}
private async void OnTakePhotoClicked(object sender, EventArgs e)
{
if (_selectedEmployee == null)
return;
try
{
var photo = await MediaPicker.Default.CapturePhotoAsync();
if (photo != null)
{
_tempCapturePath = System.IO.Path.Combine(
FileSystem.CacheDirectory,
"temp_editor.jpg"
); // Fully qualified
using var source = await photo.OpenReadAsync();
using var dest = File.OpenWrite(_tempCapturePath);
await source.CopyToAsync(dest);
EditorPhotoPreview.Source = ImageSource.FromFile(_tempCapturePath);
_editX = _editY = 0;
EditorPhotoPreview.TranslationX = EditorPhotoPreview.TranslationY = 0;
ZoomSlider.Value = 1;
PhotoEditorOverlay.IsVisible = true;
}
}
catch (Exception ex)
{
await DisplayAlert("Camera Error", ex.Message, "OK");
}
}
private void OnPanUpdated(object sender, PanUpdatedEventArgs e)
{
if (e.StatusType == GestureStatus.Running)
{
EditorPhotoPreview.TranslationX = _editX + e.TotalX;
EditorPhotoPreview.TranslationY = _editY + e.TotalY;
}
else if (e.StatusType == GestureStatus.Completed)
{
_editX = EditorPhotoPreview.TranslationX;
_editY = EditorPhotoPreview.TranslationY;
}
}
private async void OnApplyPhoto(object sender, EventArgs e)
{
if (_selectedEmployee == null)
return;
try
{
string firstName = (
_selectedEmployee.GetValueOrDefault("Data8")?.ToString() ?? ""
).Trim();
string lastName = (
_selectedEmployee.GetValueOrDefault("Data7")?.ToString() ?? ""
).Trim();
string recordNum = (
_selectedEmployee.GetValueOrDefault("Data1")?.ToString() ?? ""
).Trim();
string picCode = $"{lastName}{recordNum}{firstName}{lastName}";
string finalPath = System.IO.Path.Combine(GetPhotoDir(), $"{picCode}.jpg"); // Fully qualified
File.Copy(_tempCapturePath, finalPath, true);
string sql =
@"UPDATE dbo.tblData SET picCode = @pic, cropScale = @s, cropX = @x, cropY = @y WHERE Data1 = @id";
_db.Execute(
sql,
new[]
{
new SqlParameter("@pic", picCode),
new SqlParameter("@s", ZoomSlider.Value),
new SqlParameter("@x", EditorPhotoPreview.TranslationX),
new SqlParameter("@y", EditorPhotoPreview.TranslationY),
new SqlParameter("@id", recordNum),
}
);
_selectedEmployee["picCode"] = picCode;
_selectedEmployee["cropScale"] = ZoomSlider.Value;
_selectedEmployee["cropX"] = EditorPhotoPreview.TranslationX;
_selectedEmployee["cropY"] = EditorPhotoPreview.TranslationY;
RenderBadgePreview(_selectedEmployee);
PhotoEditorOverlay.IsVisible = false;
}
catch (Exception ex)
{
await DisplayAlert("Save Error", ex.Message, "OK");
}
}
private void OnCancelPhoto(object sender, EventArgs e) => PhotoEditorOverlay.IsVisible = false;
private double SafeToDouble(object? value, double defaultValue)
{
if (value == null || value == DBNull.Value)
return defaultValue;
return double.TryParse(value.ToString(), out double d) ? d : defaultValue;
}
private void OnPrintClicked(object sender, EventArgs e)
{
if (_selectedEmployee != null)
_printerService.PrintBadge(_selectedEmployee, _currentCompany, _currentBadgeType);
}
}

85
FrymasterBadgeApp.csproj Normal file
View File

@ -0,0 +1,85 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net9.0-windows10.0.19041.0</TargetFrameworks>
<Platforms>x64</Platforms>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<OutputType>WinExe</OutputType>
<RootNamespace>FrymasterBadgeApp</RootNamespace>
<UseMaui>true</UseMaui>
<SingleProject>true</SingleProject>
<ImplicitUsings>enable</ImplicitUsings>
<EnableDefaultCssItems>false</EnableDefaultCssItems>
<Nullable>enable</Nullable>
<ApplicationTitle>FrymasterBadgeApp</ApplicationTitle>
<ApplicationId>com.c4b3r.frymasterbadgeapp</ApplicationId>
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
<ApplicationVersion>1</ApplicationVersion>
<WindowsPackageType>None</WindowsPackageType>
<SupportedOSPlatformVersion
Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'"
>10.0.17763.0</SupportedOSPlatformVersion
>
<TargetPlatformMinVersion
Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'"
>10.0.17763.0</TargetPlatformMinVersion
>
<NoWarn>$(NoWarn);MSB3277;NU1605</NoWarn>
</PropertyGroup>
<ItemGroup>
<MauiIcon
Include="Resources\AppIcon\appicon.svg"
ForegroundFile="Resources\AppIcon\appiconfg.svg"
Color="#512BD4"
/>
<MauiSplashScreen Include="Resources\Splash\splash.png" Color="#512BD4" BaseSize="128,128" />
<MauiImage Include="Resources\Images\*" />
<MauiImage Update="Resources\Images\dotnet_bot.svg" BaseSize="168,208" />
<MauiFont Include="Resources\Fonts\*" />
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
<EmbeddedResource Include="Resources\Raw\appsettings.json" />
</ItemGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'net9.0-windows10.0.19041.0'">
<MauiEnableXamlC>false</MauiEnableXamlC>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.1" />
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="9.0.0" />
<PackageReference Include="System.Drawing.Common" Version="9.0.1" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Lextm.SharpSnmpLib" Version="12.5.6" />
<PackageReference Include="SkiaSharp" Version="3.116.1" />
<PackageReference Include="SkiaSharp.Views.Desktop.Common" Version="3.116.1" />
<PackageReference Include="Neodynamic.SDK.BarcodeCore" Version="6.0.24.513" />
</ItemGroup>
<ItemGroup>
<Reference Include="SdkApi.Card.Core">
<HintPath>Libs\zebra\Sdk\SdkApi.Card.Core.dll</HintPath>
</Reference>
<Reference Include="SdkApi.Card.Desktop">
<HintPath>Libs\zebra\Sdk\SdkApi.Card.Desktop.dll</HintPath>
</Reference>
<Reference Include="SdkApi.Core">
<HintPath>Libs\zebra\Sdk\SdkApi.Core.dll</HintPath>
</Reference>
<Reference Include="SdkApi.Desktop">
<HintPath>Libs\zebra\Sdk\SdkApi.Desktop.dll</HintPath>
</Reference>
<Reference Include="SdkApi.Desktop.Usb">
<HintPath>Libs\zebra\Sdk\SdkApi.Desktop.Usb.dll</HintPath>
</Reference>
<Content Include="Libs\zebra\Sdk\*.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Visible>false</Visible>
</Content>
</ItemGroup>
</Project>

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,384 @@
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v8.0",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETCoreApp,Version=v8.0": {
"SdkApi.Card.Desktop/3.01.332": {
"dependencies": {
"Lextm.SharpSnmpLib": "12.5.2",
"Microsoft.Extensions.DependencyModel": "6.0.0",
"Microsoft.Windows.CsWinRT": "2.0.4",
"Neodynamic.SDK.BarcodeCore": "6.0.24.513",
"Newtonsoft.Json": "13.0.3",
"Portable.BouncyCastle": "1.8.5.2",
"SkiaSharp": "2.88.8",
"SkiaSharp.Views.Desktop.Common": "2.88.8",
"System.Drawing.Common": "8.0.1",
"SdkApi.Card.Core": "3.1.332.0",
"SdkApi.Core": "3.1.332.0",
"SdkApi.Desktop": "3.1.332.0",
"SdkApi.Desktop.Usb": "3.1.332.0",
"runtimepack.Microsoft.Windows.SDK.NET.Ref": "10.0.17763.44"
},
"runtime": {
"SdkApi.Card.Desktop.dll": {}
}
},
"runtimepack.Microsoft.Windows.SDK.NET.Ref/10.0.17763.44": {
"runtime": {
"Microsoft.Windows.SDK.NET.dll": {
"assemblyVersion": "10.0.17763.38",
"fileVersion": "10.0.17763.43"
},
"WinRT.Runtime.dll": {
"assemblyVersion": "2.1.0.0",
"fileVersion": "2.1.2.47512"
}
}
},
"Lextm.SharpSnmpLib/12.5.2": {
"runtime": {
"lib/net6.0/SharpSnmpLib.dll": {
"assemblyVersion": "12.5.2.0",
"fileVersion": "12.5.2.0"
}
}
},
"Microsoft.Extensions.DependencyModel/6.0.0": {
"dependencies": {
"System.Buffers": "4.5.1",
"System.Memory": "4.5.4",
"System.Runtime.CompilerServices.Unsafe": "6.0.0",
"System.Text.Encodings.Web": "6.0.0",
"System.Text.Json": "6.0.0"
},
"runtime": {
"lib/netstandard2.0/Microsoft.Extensions.DependencyModel.dll": {
"assemblyVersion": "6.0.0.0",
"fileVersion": "6.0.21.52210"
}
}
},
"Microsoft.NETCore.Platforms/2.0.0": {},
"Microsoft.Win32.SystemEvents/8.0.0": {},
"Microsoft.Windows.CsWinRT/2.0.4": {
"runtime": {
"lib/net6.0/WinRT.Runtime.dll": {
"assemblyVersion": "2.0.0.0",
"fileVersion": "2.0.4.44211"
}
}
},
"Neodynamic.SDK.BarcodeCore/6.0.24.513": {
"dependencies": {
"SkiaSharp": "2.88.8",
"System.Text.Encoding.CodePages": "4.4.0"
},
"runtime": {
"lib/netstandard2.0/Neodynamic.SDK.BarcodeCore.dll": {
"assemblyVersion": "6.0.0.0",
"fileVersion": "6.0.24.513"
}
}
},
"Newtonsoft.Json/13.0.3": {
"runtime": {
"lib/net6.0/Newtonsoft.Json.dll": {
"assemblyVersion": "13.0.0.0",
"fileVersion": "13.0.3.27908"
}
}
},
"Portable.BouncyCastle/1.8.5.2": {
"runtime": {
"lib/netstandard2.0/BouncyCastle.Crypto.dll": {
"assemblyVersion": "1.8.5.0",
"fileVersion": "1.8.5.2"
}
}
},
"SkiaSharp/2.88.8": {
"dependencies": {
"SkiaSharp.NativeAssets.Win32": "2.88.8",
"SkiaSharp.NativeAssets.macOS": "2.88.8"
},
"runtime": {
"lib/net6.0/SkiaSharp.dll": {
"assemblyVersion": "2.88.0.0",
"fileVersion": "2.88.8.0"
}
}
},
"SkiaSharp.NativeAssets.macOS/2.88.8": {
"runtimeTargets": {
"runtimes/osx/native/libSkiaSharp.dylib": {
"rid": "osx",
"assetType": "native",
"fileVersion": "0.0.0.0"
}
}
},
"SkiaSharp.NativeAssets.Win32/2.88.8": {
"runtimeTargets": {
"runtimes/win-arm64/native/libSkiaSharp.dll": {
"rid": "win-arm64",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/win-x64/native/libSkiaSharp.dll": {
"rid": "win-x64",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/win-x86/native/libSkiaSharp.dll": {
"rid": "win-x86",
"assetType": "native",
"fileVersion": "0.0.0.0"
}
}
},
"SkiaSharp.Views.Desktop.Common/2.88.8": {
"dependencies": {
"SkiaSharp": "2.88.8",
"System.Drawing.Common": "8.0.1"
},
"runtime": {
"lib/netcoreapp3.1/SkiaSharp.Views.Desktop.Common.dll": {
"assemblyVersion": "2.88.0.0",
"fileVersion": "2.88.8.0"
}
}
},
"System.Buffers/4.5.1": {},
"System.Drawing.Common/8.0.1": {
"dependencies": {
"Microsoft.Win32.SystemEvents": "8.0.0"
},
"runtime": {
"lib/net8.0/System.Drawing.Common.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.123.58006"
}
}
},
"System.Memory/4.5.4": {},
"System.Runtime.CompilerServices.Unsafe/6.0.0": {},
"System.Text.Encoding.CodePages/4.4.0": {
"dependencies": {
"Microsoft.NETCore.Platforms": "2.0.0"
}
},
"System.Text.Encodings.Web/6.0.0": {
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
}
},
"System.Text.Json/6.0.0": {
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "6.0.0",
"System.Text.Encodings.Web": "6.0.0"
}
},
"SdkApi.Card.Core/3.1.332.0": {
"runtime": {
"SdkApi.Card.Core.dll": {
"assemblyVersion": "3.1.332.0",
"fileVersion": "3.1.332.0"
}
}
},
"SdkApi.Core/3.1.332.0": {
"runtime": {
"SdkApi.Core.dll": {
"assemblyVersion": "3.1.332.0",
"fileVersion": "3.1.332.0"
}
}
},
"SdkApi.Desktop/3.1.332.0": {
"runtime": {
"SdkApi.Desktop.dll": {
"assemblyVersion": "3.1.332.0",
"fileVersion": "3.1.332.0"
}
}
},
"SdkApi.Desktop.Usb/3.1.332.0": {
"runtime": {
"SdkApi.Desktop.Usb.dll": {
"assemblyVersion": "3.1.332.0",
"fileVersion": "3.1.332.0"
}
}
}
}
},
"libraries": {
"SdkApi.Card.Desktop/3.01.332": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"runtimepack.Microsoft.Windows.SDK.NET.Ref/10.0.17763.44": {
"type": "runtimepack",
"serviceable": false,
"sha512": ""
},
"Lextm.SharpSnmpLib/12.5.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-rWPyrB7klKxbuM/2wAQBgFk71XpERJt6uSRI27tCpMePidqbRD5whfFCXMeO0MlhUwqBmTZn7PoL6YnQLdZy1Q==",
"path": "lextm.sharpsnmplib/12.5.2",
"hashPath": "lextm.sharpsnmplib.12.5.2.nupkg.sha512"
},
"Microsoft.Extensions.DependencyModel/6.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-TD5QHg98m3+QhgEV1YVoNMl5KtBw/4rjfxLHO0e/YV9bPUBDKntApP4xdrVtGgCeQZHVfC2EXIGsdpRNrr87Pg==",
"path": "microsoft.extensions.dependencymodel/6.0.0",
"hashPath": "microsoft.extensions.dependencymodel.6.0.0.nupkg.sha512"
},
"Microsoft.NETCore.Platforms/2.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-VdLJOCXhZaEMY7Hm2GKiULmn7IEPFE4XC5LPSfBVCUIA8YLZVh846gtfBJalsPQF2PlzdD7ecX7DZEulJ402ZQ==",
"path": "microsoft.netcore.platforms/2.0.0",
"hashPath": "microsoft.netcore.platforms.2.0.0.nupkg.sha512"
},
"Microsoft.Win32.SystemEvents/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-9opKRyOKMCi2xJ7Bj7kxtZ1r9vbzosMvRrdEhVhDz8j8MoBGgB+WmC94yH839NPH+BclAjtQ/pyagvi/8gDLkw==",
"path": "microsoft.win32.systemevents/8.0.0",
"hashPath": "microsoft.win32.systemevents.8.0.0.nupkg.sha512"
},
"Microsoft.Windows.CsWinRT/2.0.4": {
"type": "package",
"serviceable": true,
"sha512": "sha512-ULRbWAcqUG2jLZRGpMGOOL1SokFDcWlTLCjKUXVzQSnh/TR+7vsvet+eICRT0JIN4W2XRkJOBhxjFUEmlmN3rA==",
"path": "microsoft.windows.cswinrt/2.0.4",
"hashPath": "microsoft.windows.cswinrt.2.0.4.nupkg.sha512"
},
"Neodynamic.SDK.BarcodeCore/6.0.24.513": {
"type": "package",
"serviceable": true,
"sha512": "sha512-iNs7iD/lOnGE6qnG0v2bVn4/B4TWV3081fdW2bQjhr8Dj9ASLS+TVO8FUuvDz1ktTbtlhvKMFmsQdDgUwcNLyw==",
"path": "neodynamic.sdk.barcodecore/6.0.24.513",
"hashPath": "neodynamic.sdk.barcodecore.6.0.24.513.nupkg.sha512"
},
"Newtonsoft.Json/13.0.3": {
"type": "package",
"serviceable": true,
"sha512": "sha512-HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==",
"path": "newtonsoft.json/13.0.3",
"hashPath": "newtonsoft.json.13.0.3.nupkg.sha512"
},
"Portable.BouncyCastle/1.8.5.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-iLfdr+T33QrDpsvcKRbMVk0PnjqE4hcrbgc6IH+A1BdPAThZlMB9s5TtFANhVVtdS+0oN3yIivgXRPRET7zuZw==",
"path": "portable.bouncycastle/1.8.5.2",
"hashPath": "portable.bouncycastle.1.8.5.2.nupkg.sha512"
},
"SkiaSharp/2.88.8": {
"type": "package",
"serviceable": true,
"sha512": "sha512-bRkp3uKp5ZI8gXYQT57uKwil1uobb2p8c69n7v5evlB/2JNcMAXVcw9DZAP5Ig3WSvgzGm2YSn27UVeOi05NlA==",
"path": "skiasharp/2.88.8",
"hashPath": "skiasharp.2.88.8.nupkg.sha512"
},
"SkiaSharp.NativeAssets.macOS/2.88.8": {
"type": "package",
"serviceable": true,
"sha512": "sha512-6Kn5TSkKlfyS6azWHF3Jk2sW5C4jCE5uSshM/5AbfFrR+5n6qM5XEnz9h4VaVl7LTxBvHvMkuPb/3bpbq0vxTw==",
"path": "skiasharp.nativeassets.macos/2.88.8",
"hashPath": "skiasharp.nativeassets.macos.2.88.8.nupkg.sha512"
},
"SkiaSharp.NativeAssets.Win32/2.88.8": {
"type": "package",
"serviceable": true,
"sha512": "sha512-O9QXoWEXA+6cweR4h3BOnwMz+pO9vL9mXdjLrpDd0w1QzCgWmLQBxa1VgySDITiH7nQndrDG1h6937zm9pLj1Q==",
"path": "skiasharp.nativeassets.win32/2.88.8",
"hashPath": "skiasharp.nativeassets.win32.2.88.8.nupkg.sha512"
},
"SkiaSharp.Views.Desktop.Common/2.88.8": {
"type": "package",
"serviceable": true,
"sha512": "sha512-SBP7byk9a5gmzHriOTC6BjykqJ5rCTqXIo/RLNszPaKwDs8naCZGUM9GZmreshPkpsyez3Ci4sm3+eleuSGgVQ==",
"path": "skiasharp.views.desktop.common/2.88.8",
"hashPath": "skiasharp.views.desktop.common.2.88.8.nupkg.sha512"
},
"System.Buffers/4.5.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==",
"path": "system.buffers/4.5.1",
"hashPath": "system.buffers.4.5.1.nupkg.sha512"
},
"System.Drawing.Common/8.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-x0rAZECxIGx/YVjN28YRdpqka0+H7YMN9741FUDzipXPDzesd60gef/LI0ZCOcYSDsacTLTHvMAvxHG+TjbNNQ==",
"path": "system.drawing.common/8.0.1",
"hashPath": "system.drawing.common.8.0.1.nupkg.sha512"
},
"System.Memory/4.5.4": {
"type": "package",
"serviceable": true,
"sha512": "sha512-1MbJTHS1lZ4bS4FmsJjnuGJOu88ZzTT2rLvrhW7Ygic+pC0NWA+3hgAen0HRdsocuQXCkUTdFn9yHJJhsijDXw==",
"path": "system.memory/4.5.4",
"hashPath": "system.memory.4.5.4.nupkg.sha512"
},
"System.Runtime.CompilerServices.Unsafe/6.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==",
"path": "system.runtime.compilerservices.unsafe/6.0.0",
"hashPath": "system.runtime.compilerservices.unsafe.6.0.0.nupkg.sha512"
},
"System.Text.Encoding.CodePages/4.4.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-6JX7ZdaceBiLKLkYt8zJcp4xTJd1uYyXXEkPw6mnlUIjh1gZPIVKPtRXPmY5kLf6DwZmf5YLwR3QUrRonl7l0A==",
"path": "system.text.encoding.codepages/4.4.0",
"hashPath": "system.text.encoding.codepages.4.4.0.nupkg.sha512"
},
"System.Text.Encodings.Web/6.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Vg8eB5Tawm1IFqj4TVK1czJX89rhFxJo9ELqc/Eiq0eXy13RK00eubyU6TJE6y+GQXjyV5gSfiewDUZjQgSE0w==",
"path": "system.text.encodings.web/6.0.0",
"hashPath": "system.text.encodings.web.6.0.0.nupkg.sha512"
},
"System.Text.Json/6.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-zaJsHfESQvJ11vbXnNlkrR46IaMULk/gHxYsJphzSF+07kTjPHv+Oc14w6QEOfo3Q4hqLJgStUaYB9DBl0TmWg==",
"path": "system.text.json/6.0.0",
"hashPath": "system.text.json.6.0.0.nupkg.sha512"
},
"SdkApi.Card.Core/3.1.332.0": {
"type": "reference",
"serviceable": false,
"sha512": ""
},
"SdkApi.Core/3.1.332.0": {
"type": "reference",
"serviceable": false,
"sha512": ""
},
"SdkApi.Desktop/3.1.332.0": {
"type": "reference",
"serviceable": false,
"sha512": ""
},
"SdkApi.Desktop.Usb/3.1.332.0": {
"type": "reference",
"serviceable": false,
"sha512": ""
}
}
}

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

11
MainPage.xaml Normal file
View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="FrymasterBadgeApp.MainPage"
>
<VerticalStackLayout VerticalOptions="Center" HorizontalOptions="Center" Spacing="20">
<Label Text="Frymaster Badge System" FontSize="24" FontAttributes="Bold" TextColor="#1F2937" />
<Label Text="Select a tab below to begin." TextColor="Gray" />
</VerticalStackLayout>
</ContentPage>

11
MainPage.xaml.cs Normal file
View File

@ -0,0 +1,11 @@
using FrymasterBadgeApp.Services;
namespace FrymasterBadgeApp;
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
}

58
MauiProgram.cs Normal file
View File

@ -0,0 +1,58 @@
using System.Reflection;
using FrymasterBadgeApp.Services;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace FrymasterBadgeApp;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
fonts.AddFont("LibreBarcode128-Regular.ttf", "BarcodeFont");
});
// --- Simplified Configuration Loading ---
// Instead of GetManifestResourceStream, we use the simpler FileSystem approach
// if the file is marked as a MauiAsset.
try
{
var configPath = "appsettings.json";
// Note: MauiAssets in Resources/Raw are accessed at the root of the app package
using var stream = FileSystem.OpenAppPackageFileAsync(configPath).Result;
if (stream != null)
{
var config = new ConfigurationBuilder().AddJsonStream(stream).Build();
builder.Configuration.AddConfiguration(config);
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"CONFIG WARNING: {ex.Message}");
// Don't 'throw' here unless it's truly fatal,
// otherwise the app won't even start.
}
// 1. Register Services
builder.Services.AddSingleton<SqlService>();
builder.Services.AddSingleton<PrinterService>();
// 2. Register Pages (Crucial for constructor injection)
builder.Services.AddTransient<MainPage>();
builder.Services.AddTransient<CompanyPage>();
builder.Services.AddTransient<EmployeePage>();
#if DEBUG
builder.Logging.AddDebug();
#endif
return builder.Build();
}
}

20
Models/CompanyModel.cs Normal file
View File

@ -0,0 +1,20 @@
// Models/CompanyModel.cs
namespace FrymasterBadgeApp.Models
{
public class CompanyModel
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Address { get; set; } = string.Empty;
public string City { get; set; } = string.Empty;
public string State { get; set; } = string.Empty;
public string Zip { get; set; } = string.Empty;
public string Phone { get; set; } = string.Empty;
// This maps to the "LogoPath" column in your DB
public string LogoPath { get; set; } = string.Empty;
// Helper property to check if a logo exists locally
public bool HasLogo => !string.IsNullOrEmpty(LogoPath) && File.Exists(LogoPath);
}
}

View File

@ -0,0 +1,15 @@
// Models/PrintImageRequest.cs
namespace FrymasterBadgeApp.Models
{
public class PrintImageRequest
{
// The fully rendered front of the badge (Photo + Name + Logo)
public string FrontBase64 { get; set; } = string.Empty;
// Any instructions for the back (often a static disclaimer)
public string BackBase64 { get; set; } = string.Empty;
// The IP Address or Name of the printer
public string Printer { get; set; } = string.Empty;
}
}

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true"></application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
</manifest>

View File

@ -0,0 +1,10 @@
using Android.App;
using Android.Content.PM;
using Android.OS;
namespace FrymasterBadgeApp;
[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
public class MainActivity : MauiAppCompatActivity
{
}

View File

@ -0,0 +1,15 @@
using Android.App;
using Android.Runtime;
namespace FrymasterBadgeApp;
[Application]
public class MainApplication : MauiApplication
{
public MainApplication(IntPtr handle, JniHandleOwnership ownership)
: base(handle, ownership)
{
}
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
}

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#512BD4</color>
<color name="colorPrimaryDark">#2B0B98</color>
<color name="colorAccent">#2B0B98</color>
</resources>

View File

@ -0,0 +1,9 @@
using Foundation;
namespace FrymasterBadgeApp;
[Register("AppDelegate")]
public class AppDelegate : MauiUIApplicationDelegate
{
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
}

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<!-- See https://aka.ms/maui-publish-app-store#add-entitlements for more information about adding entitlements.-->
<dict>
<!-- App Sandbox must be enabled to distribute a MacCatalyst app through the Mac App Store. -->
<key>com.apple.security.app-sandbox</key>
<true/>
<!-- When App Sandbox is enabled, this value is required to open outgoing network connections. -->
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- The Mac App Store requires you specify if the app uses encryption. -->
<!-- Please consult https://developer.apple.com/documentation/bundleresources/information_property_list/itsappusesnonexemptencryption -->
<!-- <key>ITSAppUsesNonExemptEncryption</key> -->
<!-- Please indicate <true/> or <false/> here. -->
<!-- Specify the category for your app here. -->
<!-- Please consult https://developer.apple.com/documentation/bundleresources/information_property_list/lsapplicationcategorytype -->
<!-- <key>LSApplicationCategoryType</key> -->
<!-- <string>public.app-category.YOUR-CATEGORY-HERE</string> -->
<key>UIDeviceFamily</key>
<array>
<integer>2</integer>
</array>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>arm64</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>XSAppIconAssets</key>
<string>Assets.xcassets/appicon.appiconset</string>
</dict>
</plist>

View File

@ -0,0 +1,15 @@
using ObjCRuntime;
using UIKit;
namespace FrymasterBadgeApp;
public class Program
{
// This is the main entry point of the application.
static void Main(string[] args)
{
// if you want to use a different Application Delegate class from "AppDelegate"
// you can specify it here.
UIApplication.Main(args, null, typeof(AppDelegate));
}
}

16
Platforms/Tizen/Main.cs Normal file
View File

@ -0,0 +1,16 @@
using System;
using Microsoft.Maui;
using Microsoft.Maui.Hosting;
namespace FrymasterBadgeApp;
class Program : MauiApplication
{
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
static void Main(string[] args)
{
var app = new Program();
app.Run(args);
}
}

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="maui-application-id-placeholder" version="0.0.0" api-version="9" xmlns="http://tizen.org/ns/packages">
<profile name="common" />
<ui-application appid="maui-application-id-placeholder" exec="FrymasterBadgeApp.dll" multiple="false" nodisplay="false" taskmanage="true" type="dotnet" launch_mode="single">
<label>maui-application-title-placeholder</label>
<icon>maui-appicon-placeholder</icon>
<metadata key="http://tizen.org/metadata/prefer_dotnet_aot" value="true" />
</ui-application>
<shortcut-list />
<privileges>
<privilege>http://tizen.org/privilege/internet</privilege>
</privileges>
<dependencies />
<provides-appdefined-privileges />
</manifest>

View File

@ -0,0 +1,7 @@
<maui:MauiWinUIApplication
x:Class="FrymasterBadgeApp.WinUI.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:maui="using:Microsoft.Maui"
xmlns:local="using:FrymasterBadgeApp.WinUI"
></maui:MauiWinUIApplication>

View File

@ -0,0 +1,24 @@
using Microsoft.UI.Xaml;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace FrymasterBadgeApp.WinUI;
/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
public partial class App : MauiWinUIApplication
{
/// <summary>
/// Initializes the singleton application object. This is the first line of authored code
/// executed, and as such is the logical equivalent of main() or WinMain().
/// </summary>
public App()
{
this.InitializeComponent();
}
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
}

View File

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap rescap">
<Identity Name="maui-package-name-placeholder" Publisher="CN=User Name" Version="0.0.0.0" />
<mp:PhoneIdentity PhoneProductId="6BFAE85A-04AF-446D-AD36-131E90DCA257" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
<Properties>
<DisplayName>$placeholder$</DisplayName>
<PublisherDisplayName>User Name</PublisherDisplayName>
<Logo>$placeholder$.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
</Dependencies>
<Resources>
<Resource Language="x-generate" />
</Resources>
<Applications>
<Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="$targetentrypoint$">
<uap:VisualElements
DisplayName="$placeholder$"
Description="$placeholder$"
Square150x150Logo="$placeholder$.png"
Square44x44Logo="$placeholder$.png"
BackgroundColor="transparent">
<uap:DefaultTile Square71x71Logo="$placeholder$.png" Wide310x150Logo="$placeholder$.png" Square310x310Logo="$placeholder$.png" />
<uap:SplashScreen Image="$placeholder$.png" />
</uap:VisualElements>
</Application>
</Applications>
<Capabilities>
<rescap:Capability Name="runFullTrust" />
<DeviceCapability Name="webcam" />
</Capabilities>
</Package>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="FrymasterBadgeApp.WinUI.app"/>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<!-- The combination of below two tags have the following effect:
1) Per-Monitor for >= Windows 10 Anniversary Update
2) System < Windows 10 Anniversary Update
-->
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
</windowsSettings>
</application>
</assembly>

View File

@ -0,0 +1,9 @@
using Foundation;
namespace FrymasterBadgeApp;
[Register("AppDelegate")]
public class AppDelegate : MauiUIApplicationDelegate
{
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
}

32
Platforms/iOS/Info.plist Normal file
View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIDeviceFamily</key>
<array>
<integer>1</integer>
<integer>2</integer>
</array>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>arm64</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>XSAppIconAssets</key>
<string>Assets.xcassets/appicon.appiconset</string>
</dict>
</plist>

15
Platforms/iOS/Program.cs Normal file
View File

@ -0,0 +1,15 @@
using ObjCRuntime;
using UIKit;
namespace FrymasterBadgeApp;
public class Program
{
// This is the main entry point of the application.
static void Main(string[] args)
{
// if you want to use a different Application Delegate class from "AppDelegate"
// you can specify it here.
UIApplication.Main(args, null, typeof(AppDelegate));
}
}

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
This is the minimum required version of the Apple Privacy Manifest for .NET MAUI apps.
The contents below are needed because of APIs that are used in the .NET framework and .NET MAUI SDK.
You are responsible for adding extra entries as needed for your application.
More information: https://aka.ms/maui-privacy-manifest
-->
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>35F9.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>E174.1</string>
</array>
</dict>
<!--
The entry below is only needed when you're using the Preferences API in your app.
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict> -->
</array>
</dict>
</plist>

View File

@ -0,0 +1,8 @@
{
"profiles": {
"Windows Machine": {
"commandName": "Project",
"nativeDebugging": false
}
}
}

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="456" height="456" fill="#512BD4" />
</svg>

After

Width:  |  Height:  |  Size: 228 B

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="m 105.50037,281.60863 c -2.70293,0 -5.00091,-0.90042 -6.893127,-2.70209 -1.892214,-1.84778 -2.837901,-4.04181 -2.837901,-6.58209 0,-2.58722 0.945687,-4.80389 2.837901,-6.65167 1.892217,-1.84778 4.190197,-2.77167 6.893127,-2.77167 2.74819,0 5.06798,0.92389 6.96019,2.77167 1.93749,1.84778 2.90581,4.06445 2.90581,6.65167 0,2.54028 -0.96832,4.73431 -2.90581,6.58209 -1.89221,1.80167 -4.212,2.70209 -6.96019,2.70209 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
<path d="M 213.56111,280.08446 H 195.99044 L 149.69953,207.0544 c -1.17121,-1.84778 -2.14037,-3.76515 -2.90581,-5.75126 h -0.40578 c 0.36051,2.12528 0.54076,6.67515 0.54076,13.6496 v 65.13172 h -15.54349 v -99.36009 h 18.71925 l 44.7374,71.29798 c 1.89222,2.95695 3.1087,4.98917 3.64945,6.09751 h 0.26996 c -0.45021,-2.6325 -0.67573,-7.09015 -0.67573,-13.37293 v -64.02256 h 15.47557 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
<path d="m 289.25134,280.08446 h -54.40052 v -99.36009 h 52.23835 v 13.99669 h -36.15411 v 28.13085 h 33.31621 v 13.9271 h -33.31621 v 29.37835 h 38.31628 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
<path d="M 366.56466,194.72106 H 338.7222 v 85.3634 h -16.08423 v -85.3634 h -27.77455 v -13.99669 h 71.70124 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,11 @@
{
"ConnectionStrings": {
"Default": "Server=ZABEASTPORTABLE\\SQLEXPRESS01;Database=FrymasterBadges;Trusted_Connection=True;TrustServerCertificate=True;"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

BIN
Resources/Splash/Splash.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8" ?>
<?xaml-comp compile="true" ?>
<ResourceDictionary
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
>
<Color x:Key="Primary">#2563EB</Color>
<Color x:Key="PrimaryDark">#1E40AF</Color>
<Color x:Key="Secondary">#64748B</Color>
<Color x:Key="Tertiary">#F1F5F9</Color>
<Color x:Key="Success">#10B981</Color>
<Color x:Key="Danger">#EF4444</Color>
<Color x:Key="Warning">#F59E0B</Color>
<Color x:Key="Info">#3B82F6</Color>
<Color x:Key="PageBgLight">#F9FAFB</Color>
<Color x:Key="PageBgDark">#111827</Color>
<Color x:Key="Black">#000000</Color>
<Color x:Key="White">#FFFFFF</Color>
<Color x:Key="Gray100">#F3F4F6</Color>
<Color x:Key="Gray200">#E5E7EB</Color>
<Color x:Key="Gray500">#6B7280</Color>
<Color x:Key="Gray900">#111827</Color>
<Color x:Key="AppBackgroundColor">#FFFFFF</Color>
<Color x:Key="NavigationPrimary">#2563EB</Color>
</ResourceDictionary>

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8" ?>
<?xaml-comp compile="true" ?>
<ResourceDictionary
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
>
<Style TargetType="Label">
<Setter Property="TextColor" Value="{StaticResource Gray900}" />
<Setter Property="FontFamily" Value="OpenSansRegular" />
</Style>
<Style x:Key="CardFrame" TargetType="Frame">
<Setter Property="BackgroundColor" Value="{StaticResource White}" />
<Setter Property="BorderColor" Value="{StaticResource Gray200}" />
<Setter Property="CornerRadius" Value="8" />
<Setter Property="HasShadow" Value="True" />
<Setter Property="Padding" Value="15" />
</Style>
<Style x:Key="EntryStyle" TargetType="Entry">
<Setter Property="TextColor" Value="{StaticResource Gray900}" />
<Setter Property="PlaceholderColor" Value="{StaticResource Gray500}" />
<Setter Property="FontSize" Value="14" />
<Setter Property="BackgroundColor" Value="{StaticResource Gray100}" />
<Setter Property="Margin" Value="0,5" />
</Style>
<Style TargetType="NavigationPage">
<Setter Property="BarBackgroundColor" Value="{StaticResource Primary}" />
<Setter Property="BarTextColor" Value="{StaticResource White}" />
</Style>
</ResourceDictionary>

234
Services/PrinterService.cs Normal file
View File

@ -0,0 +1,234 @@
using System.Text;
using SkiaSharp;
using Zebra.Sdk.Card.Containers;
using Zebra.Sdk.Card.Enumerations;
using Zebra.Sdk.Card.Graphics;
using Zebra.Sdk.Card.Printer;
using Zebra.Sdk.Comm;
using Zebra.Sdk.Printer.Discovery;
namespace FrymasterBadgeApp.Services;
public class PrinterService
{
public async Task<bool> PrintBadge(
Dictionary<string, object> emp,
Dictionary<string, object> comp,
string ip
)
{
try
{
using var frontBitmap = GenerateFrontSide(emp, comp);
using var backBitmap = GenerateBackSide(emp, comp);
string frontBase64 = ConvertBitmapToBase64(frontBitmap);
string backBase64 = ConvertBitmapToBase64(backBitmap);
var (ok, error) = await PrintBadgeImages(frontBase64, backBase64, ip);
return ok;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Print Engine Error: {ex.Message}");
return false;
}
}
private SKBitmap GenerateFrontSide(
Dictionary<string, object> emp,
Dictionary<string, object> comp
)
{
var bitmap = new SKBitmap(648, 1016);
using var canvas = new SKCanvas(bitmap);
canvas.Clear(SKColors.White);
using var paint = new SKPaint { IsAntialias = true };
using var font = new SKFont(SKTypeface.FromFamilyName("Arial"), 40);
// 1. Draw Logo
string logoPath = comp.InternalSafeGet("logo");
if (File.Exists(logoPath))
{
using var logo = SKBitmap.Decode(logoPath);
canvas.DrawBitmap(logo, SKRect.Create(224, 40, 200, 80));
}
// 2. Draw Photo
string photoPath = emp.InternalSafeGet("ImagePath");
if (File.Exists(photoPath))
{
using var photo = SKBitmap.Decode(photoPath);
canvas.DrawBitmap(photo, SKRect.Create(149, 160, 350, 350));
}
// 3. Draw First Name (Red)
paint.Color = SKColors.Red;
font.Size = 70;
font.Embolden = true;
string firstName = emp.InternalSafeGet("Data2").Split(' ')[0].ToUpper();
canvas.DrawText(firstName, 324, 600, SKTextAlign.Center, font, paint);
// 4. Draw Full Name
paint.Color = SKColors.Black;
font.Size = 40;
font.Embolden = false;
canvas.DrawText(emp.InternalSafeGet("Data2"), 324, 660, SKTextAlign.Center, font, paint);
// 5. Draw Barcode Text
font.Size = 80;
canvas.DrawText(
$"*{emp.InternalSafeGet("Data9")}*",
324,
850,
SKTextAlign.Center,
font,
paint
);
return bitmap;
}
private SKBitmap GenerateBackSide(
Dictionary<string, object> emp,
Dictionary<string, object> comp
)
{
var bitmap = new SKBitmap(1016, 648);
using var canvas = new SKCanvas(bitmap);
canvas.Clear(SKColors.White);
using var paint = new SKPaint { IsAntialias = true, Color = SKColors.Black };
using var font = new SKFont(SKTypeface.FromFamilyName("Arial"), 25);
canvas.DrawText(
$"This badge is the property of {comp.InternalSafeGet("Name")} L.L.C.",
508,
100,
SKTextAlign.Center,
font,
paint
);
canvas.DrawText(emp.InternalSafeGet("Data1"), 100, 200, SKTextAlign.Left, font, paint);
canvas.DrawText(emp.InternalSafeGet("Data10"), 100, 240, SKTextAlign.Left, font, paint);
canvas.DrawText(comp.InternalSafeGet("Address"), 500, 200, SKTextAlign.Left, font, paint);
canvas.DrawText(
$"{comp.InternalSafeGet("City")}, {comp.InternalSafeGet("State")} {comp.InternalSafeGet("Zip")}",
500,
240,
SKTextAlign.Left,
font,
paint
);
font.Size = 80;
canvas.DrawText(
$"*{emp.InternalSafeGet("Data9")}*",
508,
500,
SKTextAlign.Center,
font,
paint
);
return bitmap;
}
private string ConvertBitmapToBase64(SKBitmap bitmap)
{
using var image = SKImage.FromBitmap(bitmap);
using var data = image.Encode(SKEncodedImageFormat.Png, 100);
return Convert.ToBase64String(data.ToArray());
}
public async Task<List<string>> DiscoverPrinters()
{
return await Task.Run(() =>
{
List<string> list = new();
var handler = new InternalStatusDiscoveryHandler();
NetworkDiscoverer.FindPrinters(handler);
int wait = 0;
while (!handler.IsDiscoveryComplete && wait < 5000)
{
Thread.Sleep(200);
wait += 200;
}
foreach (var p in handler.DiscoveredPrinters)
list.Add(p.Address);
return list;
});
}
public async Task<(bool ok, string error)> PrintBadgeImages(
string front,
string back,
string ip
)
{
if (string.IsNullOrEmpty(ip))
return (false, "No IP");
Connection connection = new TcpConnection(ip, TcpConnection.DEFAULT_ZPL_TCP_PORT);
Zebra.Sdk.Card.Printer.ZebraCardPrinter? cardPrinter = null;
try
{
connection.Open();
cardPrinter = ZebraCardPrinterFactory.GetInstance(connection);
using ZebraCardGraphics g = new ZebraCardGraphics(cardPrinter);
List<Zebra.Sdk.Card.Containers.GraphicsInfo> info = new();
if (!string.IsNullOrEmpty(front))
{
g.DrawImage(
Convert.FromBase64String(front),
0,
0,
0,
0,
(Zebra.Sdk.Card.Graphics.Enumerations.RotationType)0
);
info.Add(
new Zebra.Sdk.Card.Containers.GraphicsInfo
{
Side = Zebra.Sdk.Card.Enumerations.CardSide.Front,
PrintType = Zebra.Sdk.Card.Enumerations.PrintType.Color,
GraphicData = g.CreateImage(),
}
);
}
cardPrinter.Print(1, info);
return (true, "");
}
catch (Exception ex)
{
return (false, ex.Message);
}
finally
{
cardPrinter?.Destroy();
if (connection.Connected)
connection.Close();
}
}
}
// Fixed missing classes and helpers
public class InternalStatusDiscoveryHandler : DiscoveryHandler
{
public List<DiscoveredPrinter> DiscoveredPrinters { get; } = new();
public bool IsDiscoveryComplete { get; private set; } = false;
public void FoundPrinter(DiscoveredPrinter printer) => DiscoveredPrinters.Add(printer);
public void DiscoveryFinished() => IsDiscoveryComplete = true;
public void DiscoveryError(string message) => IsDiscoveryComplete = true;
}
internal static class PrinterExtensions
{
public static string InternalSafeGet(this Dictionary<string, object> dict, string key) =>
dict.TryGetValue(key, out var val) && val != null ? val.ToString()! : "";
}

50
Services/SqlService.cs Normal file
View File

@ -0,0 +1,50 @@
using System.Data;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Configuration;
namespace FrymasterBadgeApp.Services;
public class SqlService
{
private readonly string _connectionString;
public SqlService(IConfiguration configuration)
{
// Pulls the "Default" string from your JSON structure
_connectionString =
configuration.GetConnectionString("Default")
?? throw new Exception("Connection string 'Default' not found in appsettings.json");
}
public List<Dictionary<string, object>> Query(string sql, SqlParameter[]? parameters = null)
{
var rows = new List<Dictionary<string, object>>();
using var conn = new SqlConnection(_connectionString);
using var cmd = new SqlCommand(sql, conn);
if (parameters != null)
cmd.Parameters.AddRange(parameters);
conn.Open();
using var reader = cmd.ExecuteReader();
while (reader.Read())
{
var row = new Dictionary<string, object>();
for (int i = 0; i < reader.FieldCount; i++)
{
row[reader.GetName(i)] = reader.GetValue(i);
}
rows.Add(row);
}
return rows;
}
public void Execute(string sql, SqlParameter[]? parameters = null)
{
using var conn = new SqlConnection(_connectionString);
using var cmd = new SqlCommand(sql, conn);
if (parameters != null)
cmd.Parameters.AddRange(parameters);
conn.Open();
cmd.ExecuteNonQuery();
}
}

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

23
wwwroot/index.html Normal file
View File

@ -0,0 +1,23 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Badge Maker</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Libre+Barcode+128&display=swap" rel="stylesheet">
<script type="module" crossorigin src="/assets/index-CNw6GJoD.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DnBq_fk4.css">
</head>
<body>
<div id="root"></div>
<script src="_framework/blazor.webview.js"></script>
</body>
</html>

BIN
wwwroot/no-photo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

1
wwwroot/vite.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB