From 6e2b838539d168f740cecb85912ff99c6eaee911 Mon Sep 17 00:00:00 2001 From: Jeremy Hayes Date: Sun, 12 Apr 2026 18:01:59 -0500 Subject: [PATCH] Removed large files FiX --- .vscode/launch.json | 26 + .vscode/tasks.json | 41 + App.xaml | 28 +- App.xaml.cs | 245 ++-- App.xaml.cs.md | 110 ++ App.xaml.md | 79 ++ AppLogger.cs | 105 ++ AppShell.xaml | 62 +- AppShell.xaml.cs | 152 ++- BadgePrinterPlatforms/Windows/App.xbf | Bin 0 -> 677 bytes CompanyPage.xaml | 280 ++-- CompanyPage.xaml.cs | 355 +++-- Converters/IsNotNullConverter.cs | 26 + Documentation/AppCS.md | 49 + Documentation/AppLoggerCS.md | 82 ++ Documentation/AppShellCS.md | 60 + Documentation/EmployeeFormPageCS.md | 73 + Documentation/EmployeePage.cs.md | 81 ++ Documentation/MainPageCS.md | 15 + Documentation/MauiProgramCS.md | 36 + Documentation/SettingsPageCS.md | 71 + Documentation/companyPageCS.md | 69 + EmployeeFormPage.xaml | 174 +++ EmployeeFormPage.xaml.cs | 219 +++ EmployeePage.xaml | 466 ++++--- EmployeePage.xaml.cs | 1425 +++++++++++++++----- FrymasterBadgeApp.csproj | 106 +- MainPage.xaml | 4 +- MauiProgram.cs | 93 +- Platforms/Windows/App.xaml.cs | 8 +- README.md | 164 +++ Resources/AppIcon/appicon.svg | 115 +- Resources/Fonts/LibreBarcode39-Regular.ttf | Bin 0 -> 14828 bytes Resources/Raw/Guest.jpg | Bin 0 -> 33666 bytes Resources/Raw/WelBilt.jpg | Bin 0 -> 19969 bytes Resources/Raw/appsettings.json | 2 +- Resources/Raw/appsettings.json.bac | 11 + Resources/Styles/Colors.xaml | 54 +- Resources/Styles/Styles.xaml | 53 +- Services/PrinterService.cs | 287 ++-- Services/SqlService.cs | 99 +- SettingsPage.xaml | 130 ++ SettingsPage.xaml.cs | 230 ++++ app.js | 295 ++++ 44 files changed, 4596 insertions(+), 1384 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json create mode 100644 App.xaml.cs.md create mode 100644 App.xaml.md create mode 100644 AppLogger.cs create mode 100644 BadgePrinterPlatforms/Windows/App.xbf create mode 100644 Converters/IsNotNullConverter.cs create mode 100644 Documentation/AppCS.md create mode 100644 Documentation/AppLoggerCS.md create mode 100644 Documentation/AppShellCS.md create mode 100644 Documentation/EmployeeFormPageCS.md create mode 100644 Documentation/EmployeePage.cs.md create mode 100644 Documentation/MainPageCS.md create mode 100644 Documentation/MauiProgramCS.md create mode 100644 Documentation/SettingsPageCS.md create mode 100644 Documentation/companyPageCS.md create mode 100644 EmployeeFormPage.xaml create mode 100644 EmployeeFormPage.xaml.cs create mode 100644 README.md create mode 100644 Resources/Fonts/LibreBarcode39-Regular.ttf create mode 100644 Resources/Raw/Guest.jpg create mode 100644 Resources/Raw/WelBilt.jpg create mode 100644 Resources/Raw/appsettings.json.bac create mode 100644 SettingsPage.xaml create mode 100644 SettingsPage.xaml.cs create mode 100644 app.js diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..6ef2bb5 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,26 @@ +{ + "version": "0.2.0", + "configurations": [ + { + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/bin/Debug/net9.0-windows10.0.19041.0/win-x64/FrymasterBadgeApp.dll", + "args": [], + "cwd": "${workspaceFolder}", + // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..53bfb34 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,41 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/FrymasterBadgeApp.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary;ForceNoAlign" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/FrymasterBadgeApp.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary;ForceNoAlign" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "--project", + "${workspaceFolder}/FrymasterBadgeApp.csproj" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/App.xaml b/App.xaml index 953d5f5..cd544a8 100644 --- a/App.xaml +++ b/App.xaml @@ -1,15 +1,17 @@ - + - - - - - - - - + x:Class="FrymasterBadgeApp.App" + xmlns="http://schemas.microsoft.com/dotnet/2021/maui" + xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" + xmlns:local="clr-namespace:FrymasterBadgeApp"> + + + + + + + + + + diff --git a/App.xaml.cs b/App.xaml.cs index 3117ddd..4766af0 100644 --- a/App.xaml.cs +++ b/App.xaml.cs @@ -1,176 +1,99 @@ -using System.Diagnostics; -using FrymasterBadgeApp.Services; -using Microsoft.Maui.Controls; -using Microsoft.Maui.Controls.PlatformConfiguration.WindowsSpecific; +namespace FrymasterBadgeApp; -namespace FrymasterBadgeApp; - -public partial class App : Microsoft.Maui.Controls.Application +public partial class App : Application { - private readonly SqlService _db; - private readonly PrinterService _printerService; - private Microsoft.Maui.Controls.TabbedPage _rootTabbedPage; + private readonly IServiceProvider _serviceProvider; + private const string ThemePrefKey = "AppTheme"; // Must match SettingsPage - public App(SqlService db, PrinterService printerService) + public App(IServiceProvider serviceProvider) { - 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() - .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"); - + AppLogger.Info("App: Constructor - Internal setup beginning"); 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"); - }); + InitializeComponent(); + ApplySavedTheme(); // Set the theme before resolving Shell + AppLogger.Info("App: InitializeComponent and Theme application successful"); } catch (Exception ex) { - Debug.WriteLine($"LoadTabsAsync: EXCEPTION - {ex.Message}"); - Debug.WriteLine(ex.StackTrace); + AppLogger.Error("App: FATAL XAML ERROR", ex); + throw; + } + _serviceProvider = serviceProvider; + } + +/// +/// Applies the saved theme preference to the app. +/// If the preference could not be applied, a warning is logged. +/// + private void ApplySavedTheme() + { + try + { + var pref = Microsoft.Maui.Storage.Preferences.Default.Get(ThemePrefKey, "System"); + AppLogger.Info($"App: Applying saved theme preference: {pref}"); + + Application.Current.UserAppTheme = pref switch + { + "Light" => AppTheme.Light, + "Dark" => AppTheme.Dark, + _ => AppTheme.Unspecified + }; + } + catch (Exception ex) + { + AppLogger.Warn($"App: Failed to apply theme preference: {ex.Message}"); } } +/// +/// Creates a new instance of the required AppShell service and +/// opens a new window with the specified content. +/// protected override Window CreateWindow(IActivationState? activationState) { - Debug.WriteLine("CreateWindow called"); - return new Window(MainPage!); + AppLogger.Info("App: CreateWindow entered"); + try + { + var shell = _serviceProvider.GetRequiredService(); + AppLogger.Info("App: AppShell resolved successfully"); + return new Window(shell); + } + catch (Exception ex) + { + AppLogger.Error("CRITICAL STARTUP ERROR: Dependency Resolution Failed", ex); + + return new Window( + new ContentPage + { + Content = new VerticalStackLayout + { + VerticalOptions = LayoutOptions.Center, + Padding = 20, + Children = + { + new Label + { + Text = "Startup Failed", + FontAttributes = FontAttributes.Bold, + HorizontalTextAlignment = TextAlignment.Center, + FontSize = 20 + }, + new Label + { + Text = ex.Message, + HorizontalTextAlignment = TextAlignment.Center, + TextColor = Colors.Red, + }, + new Label + { + Text = "Check log for details.", + HorizontalTextAlignment = TextAlignment.Center, + }, + } + } + } + ); + } } -} +} \ No newline at end of file diff --git a/App.xaml.cs.md b/App.xaml.cs.md new file mode 100644 index 0000000..22e0a5e --- /dev/null +++ b/App.xaml.cs.md @@ -0,0 +1,110 @@ +# App Class Documentation + +## Overview + +The `App` class is the main entry point for the **FrymasterBadgeApp** .NET MAUI application. It inherits from `Microsoft.Maui.Controls.Application` and is responsible for: + +- Initializing the application components +- Applying the user's saved theme preference +- Setting up dependency injection +- Creating the main application window with robust error handling + +## Namespace + +```csharp +namespace FrymasterBadgeApp; +``` + +## Class Definition + +```csharp +public partial class App : Application +``` + +## Fields + +| Field | Type | Description | +|------------------|-------------------|-----------| +| `_serviceProvider` | `IServiceProvider` | Holds the dependency injection container used to resolve services like `AppShell`. | +| `ThemePrefKey` | `const string` | Constant key used to store and retrieve the app theme preference. Must match the key used in `SettingsPage`. | + +## Constructor + +```csharp +public App(IServiceProvider serviceProvider) +``` + +### Purpose +Initializes the application, applies the saved theme, and stores the service provider for later use. + +### Behavior +1. Logs the start of constructor execution. +2. Calls `InitializeComponent()` to load XAML-defined UI elements. +3. Applies the saved user theme preference via `ApplySavedTheme()`. +4. Stores the injected `IServiceProvider`. +5. Catches and logs any fatal XAML initialization errors. + +## Methods + +### ApplySavedTheme() + +```csharp +private void ApplySavedTheme() +``` + +**Summary**: +Applies the user's previously saved theme preference (Light, Dark, or System) to the application. + +**Behavior**: +- Retrieves the theme preference from `Preferences.Default` using the key `"AppTheme"`. +- Defaults to `"System"` if no preference is found. +- Maps the string preference to the corresponding `AppTheme` enum value. +- Sets `Application.Current.UserAppTheme`. +- Logs the applied theme or any failure (as a warning). + +### CreateWindow(IActivationState? activationState) + +```csharp +protected override Window CreateWindow(IActivationState? activationState) +``` + +**Summary**: +Overrides the default window creation to provide proper dependency injection support and robust error handling. + +**Behavior**: +- Logs entry into the method. +- Resolves `AppShell` using the injected `IServiceProvider`. +- Returns a new `Window` containing the resolved `AppShell`. +- If dependency resolution fails (e.g., missing service registration), a user-friendly error page is displayed instead of crashing the app. +- The fallback error page shows: + - "Startup Failed" title + - The exception message in red + - A note to check the log for details + +## Key Features + +- **Theme Persistence**: Automatically restores the user's last chosen theme on startup. +- **Dependency Injection Ready**: Uses `IServiceProvider` to resolve the main `AppShell`. +- **Graceful Error Handling**: Critical startup failures are caught and presented in a clean error screen rather than a hard crash. +- **Comprehensive Logging**: Uses `AppLogger` for all major initialization steps and errors. + +## Dependencies + +- `Microsoft.Maui.Controls.Application` +- `Microsoft.Maui.Storage.Preferences` +- `IServiceProvider` (from Microsoft.Extensions.DependencyInjection) +- `AppShell` +- `AppLogger` (custom logging service) + +## Related Files + +- `SettingsPage.xaml.cs` — Uses the same `ThemePrefKey` constant +- `AppShell.xaml` — Main navigation shell resolved in `CreateWindow` +- `MauiProgram.cs` — Where services are registered and `App` is configured + +## Best Practices Implemented + +- Separation of theme logic into a dedicated method +- Defensive programming with try-catch blocks around critical startup operations +- Meaningful logging at each stage of initialization +- Fallback UI for startup failures to improve user experience \ No newline at end of file diff --git a/App.xaml.md b/App.xaml.md new file mode 100644 index 0000000..fd7f240 --- /dev/null +++ b/App.xaml.md @@ -0,0 +1,79 @@ +# App.xaml Documentation + +## Overview + +This is the main XAML file for the **FrymasterBadgeApp** .NET MAUI application. It defines the root `Application` object and sets up global resources that are available throughout the entire app. + +## File Information + +- **File**: `App.xaml` +- **Class**: `FrymasterBadgeApp.App` (partial class, paired with `App.xaml.cs`) +- **Purpose**: Application-level configuration and resource management + +## XAML Content + +\`\`\`xaml + + + + + + + + + + + + + +\`\`\` + +## Key Elements + +### Root Element + +- **``** — Defines the application root. +- **`x:Class="FrymasterBadgeApp.App"`** — Links this XAML file to the partial C# class `App` in `App.xaml.cs`. + +### XML Namespaces + +| Namespace | Prefix | Description | +|-----------|--------|-----------| +| `http://schemas.microsoft.com/dotnet/2021/maui` | (default) | Standard .NET MAUI namespace | +| `http://schemas.microsoft.com/winfx/2009/xaml` | `x` | XAML language namespace | +| `clr-namespace:FrymasterBadgeApp` | `local` | Local application namespace (currently unused in this file) | + +### Resources + +The file defines **application-wide resources** inside ``: + +- A `ResourceDictionary` that merges two external style files: + - **`Resources/Styles/Colors.xaml`** — Contains color definitions and brushes used across the app. + - **`Resources/Styles/Styles.xaml`** — Contains control styles, implicit styles, and other visual resources. + +## Purpose & Role + +- Serves as the central location for app-level resources. +- Ensures consistent theming and styling across all pages and controls. +- Loads color and style definitions early in the application lifecycle. +- Works together with `App.xaml.cs` (the code-behind) which handles logic such as theme application and window creation. + +## Related Files + +- **`App.xaml.cs`** — Code-behind containing constructor, theme logic, and `CreateWindow` override. +- **`Resources/Styles/Colors.xaml`** — Color palette and resource definitions. +- **`Resources/Styles/Styles.xaml`** — Global styles and control templates. +- **`MauiProgram.cs`** — Service registration and app startup configuration. + +## Best Practices Demonstrated + +- Clean separation of concerns: XAML for resources, C# for logic. +- Use of `MergedDictionaries` for modular and maintainable styling. +- Proper namespace declarations for future extensibility. +- Standard .NET MAUI application structure. + +This file is loaded automatically when the application starts, before any pages are displayed. \ No newline at end of file diff --git a/AppLogger.cs b/AppLogger.cs new file mode 100644 index 0000000..9d93a55 --- /dev/null +++ b/AppLogger.cs @@ -0,0 +1,105 @@ +using System; +using System.Diagnostics; +using System.IO; + +namespace FrymasterBadgeApp; + +public static class AppLogger +{ + private static readonly string LogDirectory = Environment.GetFolderPath( + Environment.SpecialFolder.LocalApplicationData + ); + private static readonly string BaseLogName = "FrymasterBadgeApp"; + private static readonly long MaxFileSizeBytes = 5 * 1024 * 1024; // 5MB + private static readonly object _lock = new(); + + static AppLogger() + { + Log("DEBUG", "AppLogger initialized"); + } + + private static string GetCurrentLogPath() + { + string date = DateTime.Now.ToString("yyyy-MM-dd"); + return Path.Combine(LogDirectory, $"{BaseLogName}_{date}.log"); + } + + public static void Info(string message) => Log("INFO", message); + + public static void Warn(string message) => Log("WARN", message); + + public static void Warning(string message) => Log("WARNING", message); + + /// + /// Logs an error to the application log, including the given message. + /// The message to be logged.The exception associated with the error, or null if none. + public static void Error(string message, Exception? ex = null) + { + string full = ex != null ? $"{message}\nException: {ex}" : message; + Log("ERROR", full); + } + + /// + /// Logs a a instructions to the application log, including the given message. + /// The message to be logged. + public static void Debug(string message) + { +#if DEBUG + Log("DEBUG", message); +#endif + } + + /// + /// Logs a message to the application log, including the given message. + /// + private static void Log(string level, string message) + { + string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"); + string logLine = $"[{timestamp}] [{level}] {message}"; + + lock (_lock) + { + string logPath = GetCurrentLogPath(); + + // Rotate if too large + if (File.Exists(logPath) && new FileInfo(logPath).Length > MaxFileSizeBytes) + { + string backup = logPath + ".old"; + if (File.Exists(backup)) + File.Delete(backup); + File.Move(logPath, backup); + } + + try + { + File.AppendAllText(logPath, logLine + Environment.NewLine); + } + catch (Exception ex) + { + Console.WriteLine($"Log file write failed: {ex.Message}"); + } + + Console.WriteLine(logLine); + System.Diagnostics.Debug.WriteLine(logLine); + } + } + + /// + /// Deletes old log files that are older than the given number of days. + /// + public static void CleanupOldLogs(int daysToKeep = 30) + { + var cutoff = DateTime.Now.AddDays(-daysToKeep); + foreach (var file in Directory.GetFiles(LogDirectory, $"{BaseLogName}_*.log")) + { + if (File.GetCreationTime(file) < cutoff) + { + try + { + File.Delete(file); + } + catch { } + } + } + } +} diff --git a/AppShell.xaml b/AppShell.xaml index f4f9882..1fbc7e6 100644 --- a/AppShell.xaml +++ b/AppShell.xaml @@ -1,15 +1,53 @@ - + x:Class="FrymasterBadgeApp.AppShell" + xmlns="http://schemas.microsoft.com/dotnet/2021/maui" + xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" + xmlns:pages="clr-namespace:FrymasterBadgeApp" + x:Name="thisShell" + Shell.FlyoutBehavior="Disabled" + + Shell.BackgroundColor="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource BgDark}}" + + Shell.ForegroundColor="White" + Shell.TitleColor="White" + + Shell.TabBarBackgroundColor="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource CardDark}}" + Shell.TabBarForegroundColor="{StaticResource Primary}" + Shell.TabBarTitleColor="{StaticResource Primary}" + Shell.TabBarUnselectedColor="{StaticResource Gray500}" + Shell.UnselectedColor="{StaticResource Gray500}"> - - + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AppShell.xaml.cs b/AppShell.xaml.cs index bd96a17..bacaac1 100644 --- a/AppShell.xaml.cs +++ b/AppShell.xaml.cs @@ -1,5 +1,6 @@ +using System.Diagnostics; using FrymasterBadgeApp.Services; -using Microsoft.Maui.Controls; +using Microsoft.Maui.Storage; namespace FrymasterBadgeApp; @@ -7,66 +8,151 @@ public partial class AppShell : Shell { private readonly SqlService _db; private readonly PrinterService _printerService; + private readonly IServiceProvider _serviceProvider; + private bool _isInitialized = false; - public AppShell(SqlService db, PrinterService printerService) +/// +/// AppShell is a class that contains the initialization code for the app. It is responsible for +/// loading all companies in the background and then switching to the main thread to +/// update the UI. +/// + public AppShell(SqlService db, PrinterService printerService, IServiceProvider serviceProvider) { InitializeComponent(); _db = db; _printerService = printerService; + _serviceProvider = serviceProvider; - _ = LoadCompaniesAsync(); + // Register pages that are NOT part of the TabBar permanently + Routing.RegisterRoute(nameof(SettingsPage), typeof(SettingsPage)); + Routing.RegisterRoute(nameof(CompanyPage), typeof(CompanyPage)); + Routing.RegisterRoute(nameof(EmployeePage), typeof(EmployeePage)); } + protected override async void OnAppearing() + { + base.OnAppearing(); + + // Using a small delay or ensuring initialization happens once + if (!_isInitialized) + { + _isInitialized = true; + await LoadCompaniesAsync(); + } + } + +/// +/// Loads all companies in the background and then switches to the main thread to update the UI. +/// private async Task LoadCompaniesAsync() { try { + // 1. Fetch data in background + AppLogger.Debug("AppShell: Fetching companies"); var companies = await Task.Run(() => _db.Query("SELECT * FROM dbo.Companies", null)); - await MainThread.InvokeOnMainThreadAsync(() => + // 2. Switch to Main Thread for UI changes + await MainThread.InvokeOnMainThreadAsync(async () => { - Items.Clear(); + // Clear the "Loading" item + MainTabBar.Items.Clear(); if (companies == null || !companies.Any()) { - CurrentItem = SetupTab; - return; + MainTabBar.Items.Add( + new ShellContent + { + Title = "Setup", + Route = "InitialSetup", + ContentTemplate = new DataTemplate(() => + _serviceProvider.GetRequiredService() + ), + } + ); } - - foreach (var company in companies) + else { - string companyName = company.GetValueOrDefault("Name")?.ToString() ?? "Unknown"; - - var employeePage = new EmployeePage(_db, _printerService, company) + foreach (var company in companies) { - 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); + var companyId = company.GetValueOrDefault("ID")?.ToString() ?? "0"; + MainTabBar.Items.Add( + new ShellContent + { + Title = company.GetValueOrDefault("Name")?.ToString() ?? "Unknown", + Route = $"EmployeePage_{companyId}", + ContentTemplate = new DataTemplate(() => + new EmployeePage(_db, _printerService, company) + ), + } + ); + } } + // 3. Navigate away from the Loading page to the first real tab if (MainTabBar.Items.Count > 0) - CurrentItem = MainTabBar.Items[0]; + { + var targetRoute = MainTabBar.Items[0].Route; + // The "///" is critical—it tells Shell to rebuild the navigation stack + await Shell.Current.GoToAsync($"///{targetRoute}"); + } }); } catch (Exception ex) { - await MainThread.InvokeOnMainThreadAsync(async () => - await DisplayAlert("Error", $"Failed to load: {ex.Message}", "OK") - ); + AppLogger.Error("AppShell: Error loading companies", ex); + // Fallback: If DB fails, at least show the Company page + await Shell.Current.GoToAsync($"///InitialSetup"); } } + +/// +/// Navigates to CompanyPage as a sub-page (pushed onto the stack). + public async void OnManageCompaniesClicked(object sender, EventArgs e) + { + // Navigates to CompanyPage as a sub-page (pushed onto the stack) + await Shell.Current.GoToAsync(nameof(CompanyPage)); + } + + private async void OnSettingsClicked(object sender, EventArgs e) => + await Shell.Current.GoToAsync(nameof(SettingsPage)); + +/// +/// Open a simple action sheet to choose theme globally +/// +/// The object that triggered the event +/// The event arguments + private async void OnThemeClicked(object sender, EventArgs e) + { + // Open a simple action sheet to choose theme globally + string action = await Shell.Current.DisplayActionSheet("Select Theme", "Cancel", null, "System", "Light", "Dark"); + if (action == "Cancel" || string.IsNullOrEmpty(action)) + return; + + switch (action) + { + case "Light": + Application.Current.UserAppTheme = AppTheme.Light; + Preferences.Default.Set("AppTheme", "Light"); + break; + case "Dark": + Application.Current.UserAppTheme = AppTheme.Dark; + Preferences.Default.Set("AppTheme", "Dark"); + break; + default: + Application.Current.UserAppTheme = AppTheme.Unspecified; + Preferences.Default.Set("AppTheme", "System"); + break; + } + } + + private void OnExitClicked(object sender, EventArgs e) => Application.Current?.Quit(); + +/// +/// Open a simple action sheet to show the app's about page +/// +/// The object that triggered the event +/// The event arguments + private async void OnAboutClicked(object sender, EventArgs e) => + await DisplayAlert("About", "Frymaster Badge App v1.0", "OK"); } diff --git a/BadgePrinterPlatforms/Windows/App.xbf b/BadgePrinterPlatforms/Windows/App.xbf new file mode 100644 index 0000000000000000000000000000000000000000..86ad98d76f9c5ba3199c35189fdc73cec9d2489e GIT binary patch literal 677 zcmcIiJx{|h5PfZc0XBrhfQrP9(tO%ATb%stB<# zaF%s<&+oo+xzPrvec%lM6?(iaua$S}P(Gagbb8McDf><*auP@#>pV+>QI?NHs*I2- zN|ID(N|;Opu`+oWh0>%GEkmWEAU5MT&odbbxyxLN_YnS<=Zo{mUe`IN6=~+IY&vWW?aFzf~?ocGyVH`~8KDvfCv^Wdj_*c1 H2F|_!C=_D( literal 0 HcmV?d00001 diff --git a/CompanyPage.xaml b/CompanyPage.xaml index 0f3a2b2..354a22f 100644 --- a/CompanyPage.xaml +++ b/CompanyPage.xaml @@ -1,125 +1,169 @@ - - - - - - + x:Class="FrymasterBadgeApp.CompanyPage" + xmlns="http://schemas.microsoft.com/dotnet/2021/maui" + xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" + Title="Company Settings"> - - - - - public App() - { - this.InitializeComponent(); - } + public App() + { + this.InitializeComponent(); + } protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); } diff --git a/README.md b/README.md new file mode 100644 index 0000000..878d238 --- /dev/null +++ b/README.md @@ -0,0 +1,164 @@ +# Frymaster Badge App + +A cross-platform **.NET MAUI** application for managing employee records, generating customizable ID badges, handling company profiles, and printing via local desktop printers. Features dynamic tab navigation, photo capture/cropping, barcode generation, and robust file-based logging. + +--- + +## 📋 Prerequisites + +| Requirement | Details | +|-------------|---------| +| **SDK** | `.NET 8.0` or later | +| **Workload** | `maui` workload installed (`dotnet workload install maui`) | +| **IDE** | Visual Studio 2022 (v17.8+) with MAUI, or VS Code + C# Dev Kit | +| **Database** | SQL Server (2019/2022/Express/Azure SQL) | +| **Platform** | Windows 10/11 (recommended for printing & file pickers) | + +> ⚠️ **Note:** Printer APIs (`System.Drawing.Printing`) and folder/file pickers used in this app are desktop-optimized. Mobile targets may require platform-specific fallbacks. + +--- + +## 🗄️ Database Setup + +1. Create a database named `Frymaster` in your SQL Server instance. +2. Run the following schema to create the required tables (matches application queries): + +```sql +CREATE TABLE dbo.Companies ( + ID INT IDENTITY(1,1) PRIMARY KEY, + Name NVARCHAR(100) NOT NULL, + Address NVARCHAR(200), + City NVARCHAR(100), + State NVARCHAR(50), + Zip NVARCHAR(20), + Logo NVARCHAR(255) +); + +CREATE TABLE dbo.tblData ( + Data1 NVARCHAR(50) PRIMARY KEY, -- Employee Record Number + Data2 NVARCHAR(100), -- First Name + LastName NVARCHAR(100), + Data3 NVARCHAR(100), -- Display Name + Data4 NVARCHAR(100), -- Additional Info + Data5 DATETIME, -- Issue/Start Date + Data7 NVARCHAR(10), -- Medical Indicator 1 (True/False) + Data8 NVARCHAR(10), -- Medical Indicator 2 (1/0 or True/False) + Active NVARCHAR(10), -- YES/NO + Data9 NVARCHAR(50), -- Badge Number + picCode NVARCHAR(255), -- Photo filename reference + cropX FLOAT DEFAULT 0, + cropY FLOAT DEFAULT 0, + cropScale FLOAT DEFAULT 1.0, + BadgeType NVARCHAR(50) DEFAULT 'OFFICE', -- OFFICE, GUEST, PLANT, MAINTENANCE, VENDOR + ToPrint BIT DEFAULT 0, + CdsPrinted BIT DEFAULT 0 +); +``` + +--- + +## ⚙️ Configuration + +1. Create `appsettings.json` inside `Resources/Raw/` +2. Set its **Build Action** to `MauiAsset` +3. Add your connection string: + +```json +{ + "ConnectionStrings": { + "DefaultConnection": "Server=127.0.0.1;Database=Frymaster;User Id=sa;Password=YourSecurePassword;TrustServerCertificate=True;" + } +} +``` + +> 🔒 **Security Note:** The app contains a hardcoded fallback connection string in `MauiProgram.cs`. **Never deploy to production without providing `appsettings.json`**, as the fallback uses plain-text credentials and will log a warning. + +--- + +## 🛠️ Building & Running + +### ✅ Using CLI +```bash +# Restore dependencies +dotnet restore + +# Build +dotnet build -c Release + +# Run (Windows Desktop default) +dotnet run -f net8.0-windows10.0.19041.0 +``` + +### ✅ Using Visual Studio +1. Open `FrymasterBadgeApp.csproj` or `.sln` +2. Select your target framework in the toolbar (e.g., `net8.0-windows10.0.19041.0`) +3. Press `F5` or click ▶ Run +4. The app will automatically restore NuGet packages and launch. + +--- + +## 🚀 First Run & Navigation Flow + +1. **Initial Setup Tab**: If no companies exist in the database, the app shows a `Setup` tab. Click it to add your first company via `CompanyPage`. +2. **Dynamic Tabs**: Once companies are added, each gets its own tab in the bottom `TabBar`. Tabs are generated dynamically at runtime. +3. **Employee Management**: Click a company tab → select/search employees → view/edit badge preview → capture/update photos → print. +4. **Settings**: Use the top-right `⚙️` icon to configure storage paths (`C:\FrymasterData\...` defaults) and UI theme (`System`/`Light`/`Dark`). +5. **Company Management**: Use the `🏢` icon in `AppShell` to manage company records and upload logos. + +--- + +## 📂 Storage & Paths + +| Setting | Default | Notes | +|---------|---------|-------| +| Photos | `C:\FrymasterData\photos` | Stores employee JPGs (`{recordNum}_{ticks}.jpg`) | +| Logos | `C:\FrymasterData\logos` | Stores company logo images | +| Images | `C:\FrymasterData\images` | Stores guest/default badge assets | +| Logs | `%LOCALAPPDATA%` | Rotates daily, max 5MB/file, 30-day auto-cleanup | + +> 🌍 **Cross-Platform Note:** The `C:\...` defaults are Windows-specific. On macOS/Linux, change paths immediately via the **Settings Page** to avoid `UnauthorizedAccessException` or missing directory errors. + +--- + +## 🐛 Troubleshooting + +| Issue | Solution | +|-------|----------| +| `SqlService` connection fails | Verify SQL Server is running, check `appsettings.json`, ensure `TrustServerCertificate=True` | +| App crashes on startup | Check `FrymasterBadgeApp_*.log` in `%LOCALAPPDATA%` for DI or XAML errors | +| Missing tabs / "Loading..." stuck | Ensure at least one company exists in `dbo.Companies` | +| Print fails / `PrinterSettings` empty | Ensure desktop target is selected. Mobile platforms don't expose `InstalledPrinters` | +| `FolderPicker` or `Toast` crashes | Wrapped in try/catch. Intentionally disables auto-navigation after save to prevent Shell crashes | +| Missing barcode font | Ensure `LibreBarcode39-Regular.ttf` is in `Resources/Fonts` with `Build Action: MauiFont` | + +--- + +## 📜 Logging & Diagnostics + +- **Location:** `%LOCALAPPDATA%\FrymasterBadgeApp_YYYY-MM-DD.log` +- **Rotation:** Files >5MB are renamed to `.old` (previous `.old` is deleted) +- **Cleanup:** `AppLogger.CleanupOldLogs()` deletes logs older than 30 days +- **Viewing:** Open with any text editor or run: + ```powershell + Get-ChildItem $env:LOCALAPPDATA\FrymasterBadgeApp_*.log | Sort-Object LastWriteTime -Descending | Select-Object -First 1 | Invoke-Item + ``` + +--- + +## 🤝 Contributing + +1. Fork the repository +2. Create a feature branch (`git checkout -b feature/your-feature`) +3. Commit changes (`git commit -m "Add your feature"`) +4. Push to the branch (`git push origin feature/your-feature`) +5. Open a Pull Request + +--- + +## 📄 License + +Property of Frymaster + +--- + +> 💡 **Tip:** Run `dotnet workload restore` before first build if you encounter missing MAUI SDK errors. Ensure your `.csproj` targets a supported Windows SDK version (`net8.0-windows10.0.19041.0` or higher). \ No newline at end of file diff --git a/Resources/AppIcon/appicon.svg b/Resources/AppIcon/appicon.svg index 9d63b65..103e01f 100644 --- a/Resources/AppIcon/appicon.svg +++ b/Resources/AppIcon/appicon.svg @@ -1,4 +1,113 @@ - - - \ No newline at end of file + + + + + + + + + + + + + + diff --git a/Resources/Fonts/LibreBarcode39-Regular.ttf b/Resources/Fonts/LibreBarcode39-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..73fd0acc7dbdf721b0ec6b7c53753285cc0a7472 GIT binary patch literal 14828 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 literal 0 HcmV?d00001 diff --git a/Resources/Raw/Guest.jpg b/Resources/Raw/Guest.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5ab55ea98113f9b6083e88c016f3cd69416d6942 GIT binary patch literal 33666 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%0A286VQ;2G=2Bej^y8 zY08cHRfrgbX2_sfD$`5IP5>dEZnVY)-R*mLDE|yZ5#({Fe&=z|tXzEk_wwg3<#mf{ zh+x}|4;%-kiRZ!`@k0BJW;n(MxaE8vN_%zpZv~@!fQJ19Q6JLm+>%Ae`En0&m0x%$ zuun2F^_$=lX+mgi+(H-(aaD)}_XnKm@PW%$nn(j!?0ZHhAl7C#GsZx_vs+Xp7ny<_>;k(#3s7p`N3wbDsld2UO(_jT%(e^h)g zuzT01)2gc(RIH~;&i{kHq$L^@!17 z&elxi{Gv4ook~F`4QE|u1rP_S`l^&AD29OUH^9G~;2^%gB#|b>{}Mj*FdY%PO)HeO z!13fr+mV)zV2qZUnS1Iu>6t??yN8G*k<`95|Le7-C?#NJwlK|j!LfP-=sIbLGIE>y z0yzKGbZ))cU(-kU(0`F+SQmhsivBDNI-H(gO@&q^fT<_xDuQud0y_mc#yr%{2B=TV z=0pu&;&8n6$cXO z*dZO%)RiEqK72;hDRw@KTXC)6h=~MNC>Sr4%{nw``nkzA%=6ldXY}^&*x?zG~ZGfSU{wu6gbkg(4nPF#ZczkQgC+I@LI zPdlo>)Go2|VKHa>Jfkd@VZkJSn7U8l>>?{XDDK$$A}4vt_!ac?yNv9`luhQOu8kR1 zR4e&g5AI1>T{yGuM<6z!t!AwDGRtz3#M!3LNQ$L4S0ocQICtr}KK#Po6_kGGb4{T8 zrj>>IgT!+pi2gAdftfRM8&_6 zFaBTINC+JVjFA!_!rnfl*f|g{YlLj;Po_Q|rgyU}tF<5AEy8Bt|9~s}9}qMd4`^~3 zgrv3_dhhs-NBds3$@OV&ouBL~9v^$w`@U@wTkhj~eALOyZLwrkNlNc0gPpOu{i{YW zxddQ{#5DFgbCRwG)&P;ayLBGnd;lIEZB`9~$G$prK zyp_Q5L@$zzBUKhh#S-F>vBa9n6TIts`mWkHJr;$ZKq zph(LTPp}*p28t=7M+qR#O%vHsq&I`}t2<1WVV1GZe&9x;U9kzsGeU$b;O%GLft=q9 z;#_+t;BTpa6FXd3nlr?(y8BDv|3=5aVSzVL0j0z`os^loK+SG_Xw5!NN&EyV9|78d zEvF0E6=|^L^j}IXS$w4N>C@o$fmgulV-vWN9SkyXi{%zegee?F7T%DM&E3-0`dJg% z^pS^BQDI^cMkWvSCj0B6=D)kjl@A46%@WJm#z~|f0~vOpb%jAmkRw>Oh!f-?z?uDx z;s3SD^0oP2^W)^$$MA^TnGWEl_tK9NkaHdW5@Uo=SGb+@!yMWM9DoL9#pLQ=k&G*4 zHVBDEO>iB+E&jr-7v26rwK0= zUZ=4y!M?DzZac9b`#t=Tq8Pd-mfg78!bnFJkOx;cZTgah-+H<o zFCDRn<*Hn~nMt#R$^Pj@$P2i3#2)g3fG+`?=mPn_V5JZ=<gsv9N2ROc!)+5?=yEwKe&dIHN zg^@!`DN#W568$|6y}U>)bWN7G9lKi^}&9CHMU^oxY| zQSvo2`K=NodtjxdS;&JOFyzwfZ7{%_i0tqVL`z>L#?3peb} zU2qOKsOZn{m^YVvz=pjHyvxKk-z1zPH;D@zYe4<&bwE-M@Mcaj#%#4sbQho-2@J5d zX?7H4on1$g)6XCiq#lk|7H;!!4`xAs^q2dEpKlw>EytSd>q#I04RH4fM?^48Wpq_{ zVo?>W*!TGP{FwFm?LgzbLsH3c3$)vZ4Z#**tLC>J>lKQns!5F>tDAc~p)k&^>|pqg z3cLQ1W9S)?Fo>%)v+1^1X`fYGC6-Z z^c@XqAUTmRAJo?ct;k|cDh3vgrcM1>e|^;i+`NaP&Q(E6{?=OlPUEt2{tIGuEje~W zVo~Fh690z3{U1rd0Hgpv3?-I~{0}5x{C`ToAn)$f2{BdFt2Q_Y9GN;3`)kts56eWk zxCtx`Dtkz!&)L<9tcP=ts`4Mjz&ArTZ15K#~j5TX=mLPUBE9i)XS9TKYagc1TN z&f#~z-#2&eH*4n3nl-a#{&)e`V!ejsywBOs-uv07`3sdftB2V z4iuw<$M9uB@V3g#7PcTounrIdv2^WzEHx9}x1GS0dABTiGdG@AW%oKlX&nz%1!)BO zGLG1|JeXxpFYrS>N)(}pzx;Ipal2S2{MMAGfFXv~;`zQ%O~i&cI|yUe@B_QMz0`=+tje7x?R@&xU32yo448@f1C+v5cYjBy#LCso!m!{tSOe) zc%QwFs-2mBqk-WU&Xjv!X_ZDgRjV&nJMJQV0by9jzuw3B?w=2xVKEP_Y!L2{`sr9fNvt?j;NS|2a~nN&kGd_8aj8}(MA?}~KDZGNe*?U$}R zKBySMphrO)Kzv6lk$#LACVHFo8FrM8_4=9l`>m7uCEaqi6?O%+j_+1uKc3hN&uyDO zk>|*oRiO{F@`C1{-7Agv6RezJU-{2%7=9{2*kxk?YKUC_N0^5OFs#Tv0@h$2O_-kR z1Y5%hSCkpN`N|#l2!2Q$Ba2ooW$5{HVt>eN;Kz$z5XcYEa@147?Zc>2<6nvH-5VYG zXuv-k(|1sw%%)HlZVi}R34q+u#N?6_Tx1Ma^+B5-1XrKP^zF`=BjC#yvrg52KVNz; zP}{^HS~7rp*}NBN7)&g%T^HQyy}7EHyxL!;jw>N>l|KRotAli8U8r#kvdhI|zn1>k z)kq+FboRAFDcXNHp!he02T;D6E)ltMbySHx z3-RS^aakzz+}BgkXi_7EZnqS)B@Yy=&;T9I(Cb zEM^EX1ysnW=#hBF`z_ZW#mM=4J7z8M3&u|XiW+Jp->X_|&p+NI!k{+c7|NK~I)h3J zGpr}zyG2h%=l0+>z<~LX8t71B^+&D}wyEuee@|`yj>h!L;XM4H%b>nFwy^Y)*y-wskbSGn-TuSs(~H?J<6;9;85Mf zUI2#YY_=-KnKdH|h8I%V!t9&O9soh;W}DE182%9#D50=7X=HfenPfyQb{+|}5UVMm z1v7=Vh-wkl@s%BIS+%&0`gg#NEHgf&^by#RiyrO8zc>2BQAAk#iZQFF7H*5jp_P)B zgWKa-e@)WeZU)0yFqnAs-ktTnpSHpnZ?VxB9TcjvK|d9fiLDkZa9f1-bJpCkcX!-~ zi!NCgAtYYG)m)ivkZGaHsZ<-qZ1hbMulc94dzD#Zz0f;g@1|#LZBNB;{JU$%B(iXM z0Y%0RC6f+kkg$hNz3s-PIQqVS9E8&!Mns_H+9m}$dzH#G|hr= z5B#pCI80%aLZ7*F*Y5CmMuW(w`#U!5-$n%r(!`D>=@$bi0SzTkPOmd&F&eiC4K zrof@=MJ7Y6k`!eBcQ)mK51mQkqTu^NnHJzRVVq_zgKt4h`C=}INZMTg}4%l7? zvS^V6Ug|-doI~oU%&Pson22r{ar$=+j+L3y$Nqx_Y6 zu;=|t{>pQ;jUWQqlKG78PcC<{KCA_QU_OiD;r*OKu}frxR- zPtO7?vpn3g>TWhH=UZq>;Wh=D;JhrZ+HYDXVR!#<#K8e48i4jXVlZ8E!w)x2Ac?(F z6&TnvnWXeyV2;(w{LFA?b3dcXFPJY!lta!pZp`zS*&p_9V%67T3Tr9&j%KDM>vg4k zS`~b-sG2QV9N*M!zRZ9d*lcL#lj>SWo6H{m;V87h-(n@g{a@kumMcGKXzQT> z$)x1fjJs_f?%l-B-lR7G+PX&#&-D4pUepElekXARTLWnLo`huBPx?16_)WsUXnyrT zz_^OrRBA)98|KGYrwZ`-GMn+Q2$Qx*=4}=c=o^ny*#d&}9b$W0#<@a|xs(K(mg~o) z9V*5H$h?oLi=(+U%0tZ;&(Pcs&KbB|Lb&uKLsR{5K>%|(WtIc(F>WH7P1M~abQD;O z0cDas7zYTZ9LFncpV7y67 zdo>lgbZnJ-0kb2qwO(K8r_tg!#?W8Gzs&EBHwLOT za2(*Z8TJbyqxXujV6%l-NEGj>v}hS1w9ia`a^$lq zDNE7LkYkug^lokQQkp%x`xa0>Ze?CHYzY3oB;4!F-j9I7iDjZzO~_u?dpC&8{V{c; z_O}>{DgO}p9+|JGA&lo4J>ONjApQ& zYaqY=@?>rEZhv1T5#d$je~8bJN2y+|j4r zx^9+hxNgWE75KiRZ%cvHL4`Q;#=J)sDmW z6+|frubwC<_I%){*YYvAgO_E4f4^X4#|81 z8oQ}WfjjY+q0fgozfAKsO551rTGLQ3 z>J}kIQYH%*qHWe6L>spIFV@K7JN`Ah-um8-JNKo>yg-{#(_?UeU3k_Wy4f zHYmXpmZkleKBSOrk2IJL{I%1+^GfAMe>yK~6kiJ;ENx^!Y4h|Fw{EWx*R)2~Q#U0* z4}i(mr}!+&s7YfR)@vPx4Npc(A#uO0U%WG`JekL^iGHSZFaR)59pkM+x<&EA-6 zoOHZm48YeTY+3m*tliKU z4^eJ@dG{Rut6=9lS;1I4z0r7lP2;kv)tLth@8{(* z{8-pSby!jo4y3edW11?@5`x@L8TNi=H*`}Tje0cLoWKs=Y>k9$%^)L6ZNC>37;}Ec z=Ym*Qf`lY>rhXAx<$1{G18n~_k8~Hf%xG(}6V6D?bS5{p(P?wvjZS3#BYqP~iBq=n z@?G?p94?5Uw8}5vdRS6ywtjXW%n#c#`Oos6%}e}ymbY){F5PUU^FQw(src_YNYFUI z$>emv0bx$US)DB7ludS0vn5UZS~;x;=g}v}G4m+jMm?i=K=>hI0l^d~t8tmo@0T1e zNJ_VHBX6$ng>HG)6WLw(hKS%8tYgQ0H<~6UgPe*YXBsWaw@H#NPA6p5V31jAdw_s~ zXLDyw$kVm3P7px{MeV8jaapcK?i8w2VYgi8`oQ_L-}}&6mNN_CR8wuF4w7yMw7EWT-Bm1Rs%o_?ZXSUZu?^UR9a`I|Vn zExN?%5C7W+FvUK{v;p9>3K?9)K1IE>GP`AxSYp>Od(wTwCfqkKm$YO3BFDQ}#zjmg zOq`X05j3L{YWI1cAIh-x|56riQ(PE%LHg(MECsIQz2|l*Cax2DGa2%3&71P5H3BO3 znJ=chf5hFKHt#y)r75@~&jj2MErF`Fut*83xkFbsiND^baV3YN&p9XvWKMA zCV4GU_1=qW`L03hSqhqmN{Eh2>c}{0et4{+ikCrE3Y;RMR*p zpqdKa3}9UdVF#r%@l2_hYn(kW9_CFV7(OPWY$oi!Ri0+0R!Yrfw3-KpdYnmuFNkJ< z-fN=xo98UD$4pse@4nNO?~sx3-K}YDp&2Je*!EW#r!{H#AsqSav%-nGLQtd7h0C+Z z5v7(LzR`Se`^kZ?pyZ2K$td#N3e_bRHu<^(zO%1wnC>82(Iza zuQJKCX4t~JRywQaCP3?$cV+WO#XY+HH*Adzb%P9En-29tyr)kikB2CQMZVjoE4<0;BaLXT> zkMpZwn$8xQ_BQ_R%|kYKg@rwSmb|r7(GYP(Q{e9Vza7_tWGux98t6uiq3xsNl4_yx z9ui?E)13Xil-lfCJVPrNIs&1~h*Bdvkgo19sTrv%kR`J>i?ipzta-<`*}LJU%ZG8p z_U3UJTwHm&elx2?N|?owwBVyI1Dl$j#;x>Z@(`N{&5wwHL7T*FDD6e=_AZ2=XD(Bp zK{Ac3C`P&K#?V{D8XNH{9DQC|D^rU^pOcIBxw2-HE_pmursE><2`dur=60CfY~&Xp zXo;=)an9#_SnMrs(@!4)ZXXgo)N?=MXYb?8*ya`$(0{Ok{Sqet^}fs{^4uUsb6c{h0_qrjgzNRF+Xwn ziKz@}v~?l%#A{g}+w*pPhPh+;Ru;LtoMHO|AxMzwtY7i9omm0f-U_h*nXRMvD!4cu!4||WUZTp>Fm7oj(Q$VQ zpC5Q#BL{81xN$X&iZ7@19cX!#rM<4r#!?`W6QL@}ya_o3Ny9JYg+g}}6UV;yfu+_jy;CLGAfFJ17LB;!@dc{;Z;^o>isusuCo%4uhvk1_^9Zg`8?CYRVrw7RTiCo*N?Bc;*pvCNY;U(a zgBtTbvPb3=Mrhg+Uh-wqI47ojNU^-oH9_fOY1gNdOYc1%6Lj2MiMd;q*Xo6R^942T z_SlOncf!>d=ll#zu?H%p9I!kZqJHC$15OvVtfJDsZl=9w-%8Fpr?HzOQjg7RO2Lne9N>UfXdb*#e`d#6hSA@!qrO#A4C zu7MX=dwPzxQKGSJLD7UnYDMHdOuCI-N@ip7XC&w>nN~X`DhYy)^q3_(NMdNuzsqf8S zIy;MGGA8$H?K&_@*!{(l!pl%+^8m_Zw?qW_LV_C!`%1`2CHPC7uSxpdra`Aa97GY0 z-Es0u?;;amOBXQyjbf)X5DW~zN}Sb-MZt&{s>pa2-}oqPe?u@s;>4gl*)4g2i6a5W zYUkWkr3(6XAxM-Bu1l0Im6>udNB3FlGxI%+a=FT2y%G!3e(DokFW3F}7>0fI5)#GT zk8uT!=`|e_EJ{BMMFNWB#v(D5Y4bgd3}#g2KQOKac@*~+a9eDsbRBEH8ca_%sXZ*F zw7ZcnYP0>?+i|764;`Ms?y_cFVvxA&WjL4k>9t4Rj>gl7qSCTToZL*k+I7XV>Qi=+ zjyztTbFDs7EM0T=m*+FfG%t_~>rh*_&Ka0!jURL=%j+hPV&V|A8Gsn8@cD|fo`cZ? zM7K4OWUke>h=oPWO;B+UYN%;wGJL#W7p2bud4%0KFCP5Hk!D$)~H^li*rz>stf9Z%U(^LF% zE~;!l_)|3Dq_5`KKs28Z85wkB5;3^+<$zAMyZ%QFHD~+uAY<}Z$>cLNoNv8spo#Q0 zgT6+l&Ll7{^^lw^CK6!?FR!WY4}UnO{9Lvi{H}ZBg};aKh`PAC7XIOY#jrf!)t**E{t8S( z!by2!c`0N0rxs3)tDc{?EAA_Y9QVfQ-Q{%m`=n58Fg!glQ}ae=*W4qS6?xK37{aRK znEDUTH|vbx;LWtcY6j0Go3xfR5(d#AETn&E@k3W1vwbdfM^h5vI_&JWy>I)H&dGUf zd#Wqj7_z@*%CQSdbF!!80{>GCoBI|6ST!r$*$s?;;}Wwbko04=Wm=2_x=QvgeIag> zR_Z$d2dYdn#wt>G;bTouo3V@>y=?y!LTLa&4(K1F0xqHxjWxJ~{)`GEorYMF|SbJ~TWj}t<#1jk=reD_E z{a`Mx<;0pNRYv=4c!9wUvv4P?w#lJ9-@U(P7K|uN<^y`;ME2F|tLK+@&?U`(O%210 zR0gDicCaZv=;}Wl_riTjzQQiv;{R4EK7ipj4b6;G9XYwW=s7o90R6{)o*%h1i!%7WHI1bcwG8K7+1<$bW!R-6KkAtb8F;N%4MR7)dQRplAt_lV zE17EdZ2>^?JKKF9x0{~q&@A#PwtDA~*9*2Zw%-agD%Zi$PU&LiMIC)3W0cGJNIC*} zXc!V(FywKnhYtgfWzLh)SFx06dE?FG`HL#-8+@H?t>3#b4&W90x_nCKViNW9;Er9x?S=DUKIf|X^K?!V39&r1s&#xOq#jpd5tJ1w1iyMwx(2|Gwejj*0 zY2Y{J1nGca+wIgr3?h(HO4k<3utiV()R@ra9gqP4&)#w~WoTCk04|d%B!vASv=lmO z{G?^>ycHC88^W4EJ$WceVVE-Kdr7{dMCAbX!@l_~39O*uOv}_{Hw$hw{qfBmqfF6L ztFk?eyS*&lLbxbzG9H*3U*c|6{2fbYBpV8>dWxV5i{`>}`JQ=4+~FMhIKOb~f0je{ zBw{;<4m4(nNZ(`s$j|3|7;YCH9c^ZMKR}TiKQluspo^SZP-Z-$7gdGv4bd^v*hM$U zxm_}|8@tM#QdrApifZ&d1GBcc@gZnFugUXGAeM@-oGIO9%K&hOqBDB`DP`OTH1?An=y&^F8a{i?EcO~ zrWvz`X&hV^cU!t^U;jG(Zm9k_MVT#0zE|N+PLJV2**ES^PaNwp(0nQ!2$G!c?D&L` zp{myz{N561ZRKiy_N(ZX#ufeur>>iGWm@i5f3X_uqpTu=BZQxMjuO}0XFk=Tla=zi zDb6-svb`iq{P6joldlspRqxCEY|A_1ZLau!;I}RP%(!%VJ+u%?(L(a*lA-id^0k4b z!9R<8kCygYNX8zVUhzqCYY>W{)cYx$0x(hzNdvb(989h_c_B=PLc{HewR`>K=iYPQ z9tZecP0t1n&UzD%toA&HE}>$W)dJBOFbs$NlrPy0!U_eF$r?D|Ik46b8QfROBL}S5 zDLt}LBJ#?~vONH-e&K}`EC~!eFdKK6G=BQXI64S$6bB5h=A{}c+mxGQ9~)<6zJ2uU zeBUTmgTSra+gq;A=Spmr$8Ep533f1nl8i_!DA2c7nCM15tRz-G+7kZ1%Wl34e zqRxyHgDRok*qL7%W*OJMT2MYdhcERn{=D?_R0YPk(IR_>nH~fgLM#WHsuVJEe%7$TNW_==-zfE!}G=;bRI0RzdulCg4By zNACnC5ph<9hURDUTACX#J(WN{)^YW4Rjcq9?nZ5 z_*v($b3yWaN8UY4rZ9(gpS^JWPRx@j0RyYfsXzfkB^QAm7Krzn4*{$iLsBmDK~+fd zb-FhpA13PU`e0vWbUcB~ki3d-!-ZC=2Gn>d68JaXsz=KN@0n!Qy$0gIdJxWrQDMB7Z9Q|>)tE&fnr zjzhIb!LpjAMYfS-@moLP4SKVHF^_k}l{vLF8d7R-?bYNhx z2Ip(`@cygh;apB?&Z>D&j9#9XO?Xln80A%|=7Bo_JLd&l_VAS%x;yzPcBsg8?9s?% z_In@pMtrPRK^X?db->P`nlxiTyM&J6Jgaw7!IC&2?+@j5f$e4rQgAQM-m1U{uEo2Y zlj19CUrD;FcwnyXFl+(!4Y-V*-+xM(k{w{6A0&MXAi%d3pXHsbJQ|iF6qan_SgtaT z%q`(uy5_;T1k|aVw^!juSRD9$V41u}3&zu)vw|w6yj_3Es~fnSDd5}1_u*N*iADN5 zKfe59O6-BfxHUyIqL-SVn7EHYdsYVKZ zEh7KMxYV6qwxp;EvQ&f9({#0=FMoX{s~i^8sylV$DQ>BEu7zq_^+Nk2@Pl-v)%*k- z5qMbvqE#L2T8BpA_&T*0IqLhG%GpQz`aM=6mclI2>s!@Xg?lwmIE$ZP^=<-M*2--3Z{l$KJLWj3GLCRkg4b9su0EEn z;LUa0n$lMxfPJ1pCg=KD1&)1o4KDxv49sa(<{A~&V>qo@cruM$}4qSVHPiE;63 z4P!fG-I$i74DlR$N{_Q$Z~kSyTU9)=9^Ah}iW! zb-xTvM>VVNf4lfpO8keUb#HF8t)Z$QbB^6d$SaBCa>~z)zmm%-zw}tCD0rR9 zV`1q8PJZHSGP^5yQ1er=#GG;bIzP|&&CcLn)TP2Tz$tD*ch{?mX2^dtv{(kD$1Ylg zs^^SyGxIvNPC~pZ#qQm;9rIS_2?cE>xxh%fYV)fM(odi{P#c+ok`t{MAAFx0OVzZf0Ir*Pg7p`F@ho+Bgj>37|Z-E2Ck-Dr&Oh71~MM%Cf+a1m?2i$pvi z`{ph*3E2Li{W#q?V~*p23H*k0!bBV}5K8?^Ups&D(8UGk>?peAmDf?dA3x=toB9~} zL|eHNLGlT*)i9AAw;j{h%-y0vc8kHp??kxx=}*s)X6lDT6Zi#v-YcdrTR``-W&6z> zek{g5xx0b?40nUjRj}g`!IkoJ?BjIE;}jHIuupEPTzAjh`dRt&cJEq*Z5+8nZzA9_ zLI@5Q={i?eC=F~(FFZg#4LmFnUie}>@K+RNwdPUN5t?^_u!rnbGQ7-9?Cfmu+K6QO zy%|V&+PXRV@wK_3>`JWX%*^^l6MWIY@(P34L!?Uve;lLeVgpySe@ze4NRN9T5=y|G z8%+`2#^IFtDm7#cp2HzgxMT4H@djn_rJBoYzOK{9t{jmkWvVV)C~xkOE-K7cBeV`N zMu=SsYpjIR-3yV7?Of+}XEf+&c89TE*s==YcB&^SiXlApF zNrR$`tjWhKdluO8=)?4%(`RxkS7n#7hilIoW@v@O0w7msWNbACIAYGWcUOuf2Wa>-JgPkyv13T8j}DD9xeURJo) zQbl>h$ML=I9!xAAt(cgM-)ADz9~~X-zou-{iZtKUtDRUXpw!l>_(DSRSz4OE&X?#` zOocNcZtke#>ssMew${IIpi;1db3MS8_H70{LgErfYxOkRaIw8V=Nn9JZXHDRlc7pG z=zY)$V(15q=#O{hy~D}BDzQ}L;?r))vx-lA@Aq|&eFt$!$rD_Q^Vw!7_Ql}4bQGoQ zYM%1c0?aBcv$1*z<;qOAeyw|yCxAEPTw!@@cbLN3nZnpHfAZYAU6htQgAda*dJ>e7 zBzEV!-yaChxfK3D>3iNd(<^~R&l6=Q9-O0vTt23CamS|sA+D{%6t9=($AU)>bghXW zZWmYA>U25&J-6T$W@(W7t<+=ukId(==vGWSG7tL#3;bU%OYH;DJV`^pm%*#t!su$0?yaLk=y@N^LN3mD^64?t{csyHM(*Wortw%gAqy90Kdf?E zwDTP5sLOL-OZ?G6NfeVaO~kXLi*jmbh28~_Sf!}w$iqI zBWSE86?vKCi7sXkTP+0+H9N@yV&y{?T)&rWOl=JPgtC&th7sRK7Z_-CpWH5W_VU3lFtXPd_>E7y%=5lFd z!U2aBAH)Sj=f_Fp4XgI>UQ2Bg;iGS_;0KEJL%&G#MtgmGik-&T)@i^`|MEB^3thLF z^O2Vf2e@pSaD;tg$RX}*%e35LT7}P^JciZg2j_#g#XObQiSWm z<29*I{LFR^wyot@(ORDP4XP6OObUv^Z*Rp^bWM?t-jJTV5n(`(--Tb?^ z{F2L?FMh7IrT6Z|o-kSxMwA<9+8d@!FJBnTNU;{q(liBpYQQ#GwsZR`!G?hwF^@4W zd!L-B#i&qzg^g$P|6Y0~tJf)ad|v=(`hCyhUF5#KB~8amL(eqms(*G=wnM9`Fh$cC zXWGCaA;AI4>5F$G2hHige!1Z4J$Jj(%{Ab%Dy)BNhD5A}aI=XL9C;MHKT|!0j;y6| zZkU++6^CBx=P0v@dG}pK{b1`2?USXMGUgxS3Ix+mvdp5@&Xbn8Q@{d#;IzGp|NZ3y zUp&F!WgcbWT{H;b!GfSw#zShjyJ#rvsXHNi9%XnOFzfVzOBV~S1`?Q}u?*?z@$_|0 z(jrF3v<14*NIfx|@BHaCPN{9;Giye#WFG0>X(>Q8{#;^xGA83SWi4g-G`^S$s`D6# zKnZ~76$h4!Qt(wb%#NXXAT0EF9P`^t=4TY?7AgkV8*mo<2e78tUz#F=`~kq)8c+iOU?;$y76fpDH8$|0vmgU-ga7k^=PFl^AC0RP#wzhJgJFe>VTp_W!yq^7rT_&GmKpYTvEr1u3a zZOwyPfH53-!P_UqFG~KLUqDcpiRwx{UR6HG*F@DBYJJf9!g0UAAoI8gKZiJ*Gd^)4 zJ_f$3a2P+2G13qjdLh&=%3B^8dOj@D5NV?Dm!}(o_1~AZ6kx^?zW#>xCr(EwX$M8<|ic_TH#A{GDkfD?X^J`q6|qJqN1 zsI5Su7-~Ong7tY zvDSay)L83xC;z)n!~S{&)B{-iyVgJz%Ku+D{+(a)59IlUfpCifam5-1jsqO*?7u(Y z!3lo2c5-oXa&qx-bMM&6$HT|R%frjdFCe^|UqDEJmv{G`-9jRwVq#)^g5nZ;L?whp z#YBHUgpC8dhm&g;7uPOPeqMgj|K)?#3<&LHYvGXRU{e6th1fWR*jQ}<1T^nl;Ck?T zN%-5r#t!yz2lvihJiK6o>fHc48wUqFC&%wzgRK+6?*UFBF5$h}M|X(Wd2=gV6xB(- z@pz}=v8s1s_I)HJ-E$EsyLk49OGrv7tEleVucik*bXecO@c0SSlV;`?mJVkeot#}< z-F$rg`~w1mf+M4%FU25Z<1SxGy_$9{JtH^o=B?Xz?%vCPQdm^{w50Ue^Ovt)SJ%|O zd0W@g+ScCD`M#_BOaH*&5aH|a$ka4(=KGJ?x%mb1>aVqR$_90F>$hBN0LNd&`nzP? z7*tKRKW)U_vxor~z> z*`uU4rA+=U+FvC5*91%X-;(U_g8f6T5rB__4IDfUApj0A9G+zC1O6<3>flcd{E2}- zG4Lk_{?9ScMKHGYo}&JW+^gfGAkpE$0S+({Q1NO!f<1US7Ln&YYuz!jjrXoJoHO%_KPxb;(p8- zExL7_XWx^g|EfiYZe7tiH72~kASD!Uo?!tZasYj?Wdd=>t}Zt|-MCB1;9N*Gl?6P% zufc6y!_6L_GCL8>0w|8eLv^>cKe@R{);=5l6dI1YEALB?o<^q)jbz#0P41}<8Cmf4 z$^TkAu*b1AV*iqcY-OnX(d2h>xw= z8}$lIbs!GYAWmNCfbhv4h@LAZ!!jOlUt$54*CP8cwDu*L6zE!-WFSjH#GwWGDD4(g)1J)jVwShf3OzcVIZof{*dvs zB*~z{{O7s1p16$33aEhLP~+3WtVsL5pnSv5)X;T_`!n3oABLStTCZj8pq|1DiMn=a zcYlU@6k^WjJlcxEIlUg(igb3BZ#;ESQm?YtRad+jQXNXkJm41I#;fq*dS>}>C#J)t z^OuKU@YeC;gx4#=^Dh>p9G9e0Z?;C*a$L1n{{ay~N=?|Mci!9y8=S4#gW5-ReBM+z z<-U17XZg!W?Wgd=wE5n(g@cK6FXP!m!xAS9PcE$0WgOR#B z6AKM?TabkXf2YHJ(9^usrPrgb;hIIXs+M95;cGmh4u68#%yP6yPIXnlvjT%+?{9N3 z5u~N8?zCu3x&)v6bzea{FPfJrsbk3lnaz#fUq%R)4BS^5zRTU>z5ZT_6xglJ2~rP* z1(3y)5^PHYoAi?D7Ees$4+LGh!uw{o5nGR$tl_WZcjao1*(x{duvRx`B)%;?44TZ% zD+wtZuAc~dDA*UQo=XiUySL!?yoL@YXp|p)HhMPpT6Vg(D%JORH{_gpMTLX4r<_1M$BzrM0yjNiW3RoW_nfJ!RCnhuxZj{Pj|< zeegnBOT-y!_M4w3i&i#ovc`_6sCH{UCdCKj|0u+cS9C^py1f*ETNn#Y$#S&h>+O0p zY!(`>7owWK!}q4pbytikE^clynJP5BcRKtEQ?5><&U~5qk@!YC_cA=?GL?|%5gu86 zUhF%34@?l3&ap6}qh$DKv-rw|S~=#YmGMTm43QVm$mwL`+mT(I@B6H!#gO05K9fBv zRv1z`DG)M_(nwIa+~M>k*zlZkQ_W?AxmpfLbR$D>cKO#SeBhTA<=6G;6+gYt<(kU3 z?w8FDe)!R?Y8kO3+;~M?VV3EFU&Y1Z@|o1-vso5PdYfh;C8q z30s=lQ5}eV8`-HNPfNq8cfP>&QJ+ExHHo^9ne#U+vw%}GM9$SqHQEaygyM`;Yq>0D zT+bWG!EP-v*_a@2D$Q>1}9zk6YwKE08Ru3=7(mbfMlu&Zz zJ#X)+PZ0awbt4C8_1MG%nfDdxyO+=EcSERRiIE6B^t&S{QS3Dqz@`<=>%v!uOY@V; zG7{H3*D-uEK6GynBU0=_?V%zYdL2Xu-P%u&VFAa|($EGhfLcfEnS{5y!1U;9y~ggs zrhK)%ETHJ!E@khB$Qx|NlLw*HP{boF$qJW|fXr(ak1QB@m{EUm*v}D?hl*}ASC%g1 zi41cJm{S%#bNJ3h7dP8Tqid6?QWi36jvPwNV{dH=X9P!jC_e^K>td&D3o2q0SSBVX6t9Wao6xTIKa$ zcdyNV*Lr`K1tgVL?y2{&IW#C^Q-mvo`=Wi}BgfDq$D8YNo|U?|2jw?CM21#sEb~fq z-{Q+UEF3GzUGSuF9De{knLi=SoeG0`A|s|x-!9nuT3cnKCB1Mnk7FRK0#)J+HDB%S zv^I!5IfXDleVwRw>|7cy3gj&cqQq6!KDU{1AHVbUx&pmt^?Kyn>w?d4g(2io3Xxg_ zQ)Tu_jk zn#r8fc$6MjU(aQ?na_AYR!uHvN})a)mZUBn41J9gvKj&1x*7VZFEC2%nl~8nJ-K$6N6aSODEKp_&EogmhxpR2K16tvf6ry^twN zz`*CM31|{R3nac~N*o0N1Nd!Yj@hmw4RlLBd6u!OZiUJFodrw>vVc@5v1dypdV((g zfd#bnbpf;Y*0ty+@J{sFK4i=<(0XJRGUc#D{FbP((I#kl+6f;Jq^AwBAn3#DSwPAr znwk%vvPS-5UKs;t0@o_23cZLU20u`3%uf?yP{C|C_nNKv!lg??;NDXPc#^%K6p~}wHof6+$W-W#=~2r8mDd?TS)*fi-6>x$=p0!_n4{VZq0D3& zl-=Al9ie^Ot zJDQSAttX0i*z!}2>_|hIK7P+F)GRIr`~TW~Kuf-3R`EI8`xV3`0x^6E$^uj!Cm~xa z7r|-!%6aLQwM%IuPtmtqU+qlA(qGt_iUMF^+ngdRGI<<`2rl{wvTa&LgZiVM(4vJ} zCn2l-*!Xe>ogH9M|5(aJkJ&$J5_gwrg6@i>X0iYuxk46DZ0{`T?H&2t>v&KA=N)H< zgXUAIRz}z(+)9Lq66y07>T;WAhgrbC)vzX7u>e6^@&toNPL@6kf@r=ETrC{v5lWs+ zK@#~?_B*!=<`kpF@BGK7oU_)-_T#h?)XUx6TIy#CLL`ND$K{>`D&2C-FA2nMz{BwY zEFdQizRW?|8f~jP6B@KHl4~q0OLF;#?3sc|($Sy%f%J>y^@)~=D^2^zeyOj;@8nzG zdz-=gU^DC}2Y-ry!7@S){Z0*6Vi=RNL~o^d9}wYOuICI`6X&^Bf-QA`fy6jX02 zBOW10mf`Ez-OSr)np5qwouxH5uBfW2A4=>Zi?_X^9&Uyy5-PlNre{y}&o<=jPv5z8 z<2GB>=UuX8znXumsNs?zJ>zdmTU^29pjS=l(JbJsRS!dWka{*QKf}(x@k5#Fo^n(r zvlCK1aSa`$wVRr0JM`L9*DCW=O-A5&bJyDsy6gf!j4vV_2|rfo!mpTS7+EwS70=9y zV8q#S=UOxc#@&6dwjNMRGfkt!uzuT-ax6RnZ+)_FBcAnhbtiNumRT1*+r4- zxSQLpO}sc=boSW5`byfkVCJd*aOi#q`&;)8ERWk4evoZMEPPlGnxvPuu@ zUAnGU&i#ysA08mrLdpG^cw^fwbK4Af^eTd`j@|>VnH3f2$)<3o3TC^Un&y`v#x(5t zghOE8^QeT5iQUEqQ#35k+(Gw+xGQ^dtwd}a#lG!V)I52_WN}T$VvoeQ+1YDJue9fY zg%GmCWB^F}eaP764RJZ?p}i?4{(S{zFK((~pb-|M%^SFpkVVI)mv3s$Ir?W8DqZ6A z*&>*n$a6B>U(^3~bp?YCnh>rm4uWCAM$FQ*gn!2GB7X552>ZFb>~dh^R7SHIpyWf% zpf^uutYa^u_k_`-e+11y0k0YscVg9r(O=0KYoi`mzW7;b}2}Ry{s2GU)5y5E%_c7!@%k>&~5n- z?_{iJ^CuYilmrR|8YY%z?R~pB_~Xr)$)XdTd)gJ@@g~po%Js&2kRTEjPyY|xR*vuJ z(Kkd3QX1pZTc(}!dn@I{TACk~8$rfieQi`BVv}cQeaK?wtvM`6%td9VSH*_S#V<{e zy@i*Y?8{5IY&oSCOGpoH!4yWUoP^^(J3n)>{L*1I0;w`fw_dpl9TS^~bp2M*%`tEw zGfVLNV|aF=Dq`W7;NFkIw z2Qu7?YDcuXA%1ObbUrld)5-9g!P+~FIMDr`Z&*Nlt+A7Eex}8`$$3o#LejFrjfeTk zv$AG^vFFZu69I<78B%eioSx2_?AYKvqaNwnGOfw`WW~VAv+R{=8u$>SU53@7@2qO< zYf?LeEp_h-jph?h%Q>2&&oFCDJnEW9jkJA3(5SvzYgam2crp*iSs3lGpl)+NX28NF zWrJx9n&8S%dKqc41G0K(5xvfUGqZvj?O<#K12WRZ`)D<~AvuknT{po%+p>T{sfMge zeeg>pJh6T2I132V+4m%4)pmkC=|Y6Qp4|~hAgTjVqf+vCF&~|5926HG<^FWHOYmX- zdm2jcXj#noF(l_|$$%XE#3CtLwlLaH+12JKH>VWi4Pc(5Nxk+FM_rN0FB%|n~ z(M#cbTMysTLx$fCM%W0?p^cDf*L&aD6!=}u$hiMHcl3S~Gj3vKp~ngY$rrc;PetiG zWvd1o?Gq!BKlDT8s>^6$A-e>mRHeM{2;?eU1|@4tDxAirCur$!o)7zWta-PbLd;&t z<=2?*3T6*NM2UKwoY^X;>g-!vU3aFQN5^f9>FP|j4_QP|)gUbx1zIb1clqR|3S>Cu zr-4zD2CvZ9v-&->YSKP^=!Ezukrb<3vo&76zHcqLZHr1ak8kzP95mSponiqs*fs>! zY2p<=y^?tEapS=5pxfn*F(0m@$<6EcX*~2Za1k&BF(wwK{43go5A8b^P^rk#tvYa* zBC?XdS;*wML6#Y(7PgG%mK*irFeXJE`&0qgXcN;!(0# z0j$4xyglmP{H?RGxUmXew`#Inddb3&PHc@cg*C0p@{wHk zazWQ43enOmUt7;NcjdSe;J+DjiYpjcjX3@OQ+f0|1Li(t?KC>o*e7xtzuRl^m)rPJ8Nqm!Us5+Cj%-b$9M>5> z%H2z$;vXx;sL9(nFAa^j26W$w7aM3i zHXvJ8P*j(F1z7;o0y@iQ!+v|rzY9u&9>)T5XUXEs4ihsl8eLRjJDi@u?1iz*Uq4wU zuDIxmmc!4yrJJ&VDvTWq`0`9WuQz)6c&k+V(~6>TEAMA+=E}8o$u$jOh6hcuo;Yx$QRNhYPYoPESz>oae;p!uJ^gZSZ0&a}d`v!e9p{hy%Kunzr_e1&mbpIZpV3!<(1=iec_@1!Lo#Ie0J~0>*z#P&b?$QHMgTroaf`OXy$1fWNRK zM>Ci`SmF0d5bDWwp-x7;g;%!MMVdJa=xU^cEWaxJG&rO&Piv3G>yoybz(_`lM-@ce z*pf|NcAsw?ljPjKPC?U!7nznT>F^~{_}baZ1uU zRwoh<$Ji(G$G!<>1msKnR6rJ+-d5#umGN_tdvtlg`SS!gf<6_3?I%OcT5|GtJdNle zNLJrmMM744NRx5SCxSN&zFTeD=E9_q*>nx!7>DD@VM@sR;ro3St*9%61y1`SveLD8 zH=6ytdoTAAzV~D|L6B)vL(hi{DfgjSkFMw5xH}_hTqv+}8gJUfkIX{iroyjQ!wy^d zUz=D_eUEl5c6Qe@bn}#bhyscDyAut{s;MY_7i327YxoG|;%6ny_u&I(-tt?dr}$H4 zGid65@ypU~RMX-`@;OeHn(=ySpzOmE8&5 zQl&{Vhtr8W%jtr|vTBUy5rzJB*Zb@wfgA7R&5a<_D?MK452-L`da8(;ycC(?+pdB! zu}7nRWh{?HjRn;X57?Q;rA;e|g?VjKwdDk(#u!;5!;@r!Gt6{+u2xex+La7Bib!Fzg4= zL=e|v4z0R*SaCkLcez`*e_^M-0{)RD9mS|eiGl0}QXPBtN@wvRo~&D~&lWMekpawc z%s5+1Mj4cuJ^Kbi=MYR?>oQY)L2ggu1GADvN_*k>SH}W*m$Pv%e?pRQ9E``Pz4&(9 zY&i|`{)){oPf<4N)+h4Xe4_&O7wQL@%{tM-7gt|l?OjPoL4S&;M`o(?Wv##U46 zfe9b3z8Lcc7q|j4K*F@&G9x0SzWsn|~E(^!>muKrL-!qwbkQYRf2o@c<9 zi&5DGsB8R4lwpwB7~vV6-X(3SsK_p9!710#ga@rmaDt8PPP*ezf>EqtYsumv{)0M) zfZKqJ&$nl2+Klr}=GO^+{FDo`*K7Za3@y!;)yPse;NTMjRASju(Ji`qXVjR@idkdM zYR9ydd+D#2fp^O^L5>@V?qMFJV%wHgsG2{AL*Gsw!73rw zx=Z2qK@+cLTX&%j($A4PQ*T(gkBxEHhkXdc8sl@?&zii)8R2^IRAoZT)2oCw?Cvm- z9lB?$wf7hRX?JN6ijo_8MVpF`rr*vat^|e1-YP#tDm8z6RBE3Dz?P(*Lj^6e61oFH z;Q{@JKL7f-X&6ZEx*+5`jFd|Fnro%l+iw&&Z&@#lpUy`{bY_QbSdeZtR)6|lj2|g> zzn^-A^P9MQt(VJ1FEqjr{qA73wDsX>Li$X7T}q!o){&r@Gn|`!B%RpGO(XTf6t!iQ z2sr6ycs62f*B3CB6d()v!x^fK1+=(6LH7oL-a&H_3y^|O9q5D4>6UqL z^_eYjpuw9Y-s+|HlAKskT|SaJpfj2oa_fG?^B3odB}&c%(YIK@E`JbQM<2pLgyqo# zS-_Q7Cs@Fv&sdOaSumk_VKz&H%3{93glX<&5R}TaX>P{VO8jqI^Qq#Sc<+IsezS|b zQMsyoC3B`IMbsDUDxygq{WW;;cN!$r?2?bWZ#4b|f&S1r3BB0Z$;|zPcj_K z*Eu*JkyTr!>?d*=GuaTqU?FD%{z{T8E9SepJns%ZcCdfA&Hxgn%ucCTg zd|T8lvdispgXcUpQ*4V1Br8u@fGb&WK7a*Ohy0@8kKiY7PAtOqG7f%@IgqmP43Q@yLLyJkNqIRxQ;GZL0v7O5x zWmnlCYcbm!kFx-a@kY4@A_a6F^5Us?SirgQ$6z3M#X9T?C17ej5kw(p9Z1jWr)_Cm zf(!|DA`cDD+yw^llw;bk6duUdLFZ`O)CYP@CDa3OKSp!RU*7UBY|4;>(5z5j6e-bf zN!g!1=P}BP7hc(lea*3cb)r;pZ=SLCW>5FU0t2yc2^!S?DZ#9K!wD+FIb(zOUZ~8{ znIQk%yJGNvMGz-ePHVYx2#0#?>|4{C41Ub0ux zHJr&0tlGQ!C*7hq;mPdvbs?qEYOV5FLiPlmpojW#Us|0~1H<>^uiMN{ZgN?0p3Qmm zrBkt0!9x0_`4yd)4|ba!>svHM7SU*Eiep-l}nEHrsp zw7>3IrbChEN{=^I z1vaG@pvo;aa3K38MnuE}%pnt?GRn$kf?N_6xlTt-g1Ah#~g=!h+qqytpsGxw2azy&=Lo2CySkoyy2f|}0AiV(|Fx?}t5U2TgcYf#T+T0;z z&q*`;BGk#}TDQL4)AaKpsb$dYiOgMoM6z*;pcpmu@wZEoQj^*q}Pgl30%yvja#PN_<1qj;1aBcfk8=QQdz*O zmDJb6dv{NGwlWH1EsGW7 zb(fKl`}dqA3cV_qj+}-SWiZ;QVx%8}U>9~ZK&P&q;ePqr)}&^#;p1Tg7%iP%HUm$> zlWY*X>BiKGN7Os*hU00^gWNox&Mw{d_m5QqHqj`k1pn1Vx=0dl8w&Ap5XSd^Az}w{axy95jpP@&rCOc z21EC*bdVW5E^CEstP)v3HW+^RCka=A6APs4N5`$ zQ)6m0w#{}+szDo!D`~>1gK}R?T5~-y+5d5mUScJBb6J%^30cB#ZwnzU!mnZ1phzDk z(*sR@Zc-GVrc@7cN4?AHp=nZ2yxIewm&wC!hOKX6Xf|#i0%%9+M{06f7fk4UTam zBC!?&W@!nw3ThtjeI~fZd&R7hyI-8%G4&uxp9t-dELIur+h<^^xQekJ)Y1!?!$r~F zE`=*7bzTfrG*j?BJ9RTfUkL^6MYj;hwG06;KEdQy1v7$W3s=E;=t@7%cZrH?RgQe8 zbwQAs0cwBym^sl&KO7gLfbMP$SGC0!I=kTcz>L@c!7R>^7fK-6&xDNlHj-Cuj zz=8sE0W;({V)&K}xW?0^z&^A)&vVm7tlA85*O(&69HP^pR{8Iw!e5Tewp{cTM$ zDFmm%Fx+%Tz?<>(10NTpg@mq41aK0-e86@UR*0F8MjqaU@cI7FD*tCaZeh1;e7l@E zNNyz>W0C2S8upkC=x~4WWhLbHSue1|4*bh-V_34OG8wT5e3+W>%|PgoYbC zRCu~u*85&fJQ-jnmuhu3V^)1xaQjrU3IbW$OY)+Jd>uJFP4O~Z_e7XvTCYSiBXBnD zVuWqgD3~uU|3Tx<`ALDi+PrRxuUQhobT>UXG5pp@NV5C0N^93TM@BI;i!FdMY3(G-r_Zl@ahoVa8ZKkl|Tz_ zjn^VMMjRfM7N2L`GEY}g{r;FhelxMXRp(iX~ErI!3AuDir40v9$k!ErG*mSs<}b~BDDt1ipj~J0xW|K zbuC{+3_=RFRMV?5d>;nRhebCpV>VaH61NV#tYzGy(wVbJd?j4-M z&^9l!fR57HBoGt-WP$K!yne>7d=+3$FT{pbDv>lkSm>=v%JvLHl<($RY(Q6W+o9hy zkvSz$ihU(I!SA;EaZzyUZ4{z9Wg_60+m5Wm)}s-T{rbzUHg=b)D)hU&W~WpC$(z`Z z-qu&g6>Qb)Qus7>n9wOzSzQ@o2rCSY@uT=+Y=f+>eiN`+v&(q<83e?My~;`>um&*Z znzi(=##~`?ppAZne$UYiSto-Y)uUDfw)(e=w#|FM!$X?e!DI?{NQN5>TrdQ|$ntyy z&rg_mLJ&Fa-6zB{Q+!CJ>VVn(^PLGIf@WEyID(*Q#M9}Yn76oNw^i;Jd#TyQKdrpC z6;9Hk&`H=f#T|F1XD}`KquwVzZ@#+M_<+&ce|3Ekai&!-M(Dr|!!GHpvd8Zg2c+l_ zTo_fF=7F4@yz*ki-3xu58fznMfj>7_$h=qU((^*U=XA)I;;ax$Y%H}Vaca=;4(_HE z7(`HvOTgNimE^-cL-NThZD@YFc5m?@-MY+u-pO1*%73#+|MP%Xd8aH_LLym^$>Vx% zT)Y;s1Bvn_geiRd5`0CXLi)|=l8v-C#)79qro-jvC&{X9%a^Qu+O`@$;}SMoKTjMu z`(W7=qNepSNj>h(do%Okp1WNMCY{89p1aHi-lwamhvH?ldvl2DwDuK2`gv-F*0lyB z?~4H;l&G3q_Xj)P`YP=@gsD5UEMZLc(lr$$cH2ZmB-+lE?-9Pu!Oy25z5348b8MJP zOu70sA+PA*iC-UQgUzIF`@@n1s@Lx{W2mYt=?M{`>j>#C)zmeoR$fzJ{1hzhTIcb+ zyrumu51t#D%DlZDn$o>L{(PvZSbzPR`>P-Dok%SY^3UoWc~cC{S;qkteHV?w1BqR0 zV4C6E1Ur86;h`Fp2`6e^RBc|C;oR+TS~V>9J#3>730=pw+FmITrJ6qB*KHXwmO8H_ zc*s%u>D{fay_xwSIs@0MMOEXbCmDlTHH0{Jix0X<^V*?B{m6VV3Exzmi|b(lR@cFe zWF4razoytcJ6rH6<66{D<=OC-trLXD?joKqhQopNSV4}QivyG$&s0@?y}cR_Ko7e$ z+&otsI;@z63#US83e-iS?+Z`&5wtfo@tQ^%;xW&MMXBiP%;V!tAE`n#4yx_cyPClS z7cv!xIHjL`GgBXQ-yVN|uUO_YkFam$yVR#-ce3h4lG+nKwd7vF(7D)Z$9Of2NcPxh z3v_7Ydt%(PInqBGDo3r~7BEBGZ9!y6Wp49D^*~;^3 zHx*mxP~+({o4VWJuLyA@v|E3+EkhH&fpXe){8iY$<`nj~MQ1ymuG*VVSjio0Ib zRi!u;?K!1sX{NT@rJyxJo^FZ?CM<8XVc5SWoFZvfPi32=~OOs`CP%!zU&`UF*LqOzFTBqFrfnd3m6{p-n5 zs|K&SIHQR{nP^dm$rSf{PWyaf{d`hS*nhNrA@&1XwWRZ!et0U|y+f+K>DH4Vg4EN( z3!~?1s>PdIz(RNlf8Z}@a1oaQGtgzH|+;7og<^M2$6AS%@eg|vX z{ppgwyR5ya0i!LZ^DKR!09HLeAqXa{)6l$1BFM;=A*VZMXFNah{o+`V?NSFfptr|C z#qx`H)|VF4C>nhvM=@?Cocb>J*=7R90uvq?>JDxH>T>^QyZ^dBbo_Jd{v5kMG4Lk_ O{=~rl8yJAIhW{IGRf=5z literal 0 HcmV?d00001 diff --git a/Resources/Raw/appsettings.json b/Resources/Raw/appsettings.json index 9e623d5..2e6a282 100644 --- a/Resources/Raw/appsettings.json +++ b/Resources/Raw/appsettings.json @@ -1,6 +1,6 @@ { "ConnectionStrings": { - "Default": "Server=ZABEASTPORTABLE\\SQLEXPRESS01;Database=FrymasterBadges;Trusted_Connection=True;TrustServerCertificate=True;" + "DefaultConnection": "Data Source=FRYDB003V\\FRYSQL1;Initial Catalog=HRBadge;Integrated Security=True;Persist Security Info=False;Pooling=False;MultipleActiveResultSets=False;Encrypt=False;TrustServerCertificate=False;Application Name='Badge Printer';Command Timeout=0" }, "Logging": { "LogLevel": { diff --git a/Resources/Raw/appsettings.json.bac b/Resources/Raw/appsettings.json.bac new file mode 100644 index 0000000..1a10879 --- /dev/null +++ b/Resources/Raw/appsettings.json.bac @@ -0,0 +1,11 @@ +{ + "ConnectionStrings": { + "DefaultConnection": "Server=ZABEASTPORTABLE\\SQLEXPRESS01;Database=FrymasterBadges;Trusted_Connection=True;TrustServerCertificate=True;" + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} \ No newline at end of file diff --git a/Resources/Styles/Colors.xaml b/Resources/Styles/Colors.xaml index 7ac8968..cad76e0 100644 --- a/Resources/Styles/Colors.xaml +++ b/Resources/Styles/Colors.xaml @@ -1,27 +1,33 @@ - - - #2563EB - #1E40AF - #64748B - #F1F5F9 + - #10B981 - #EF4444 - #F59E0B - #3B82F6 - #F9FAFB - #111827 - #000000 - #FFFFFF - #F3F4F6 - #E5E7EB - #6B7280 - #111827 + #2563EB + #64748B + #10B981 + #EF4444 + #3ABFF8 + #FFFFFF + #000000 + #F3F4F6 + #6B7280 + #2D3748 - #FFFFFF - #2563EB - + #F9FAFB + #111827 + #FFFFFF + #E5E7EB + #F0F0F0 + + #0F172A + #FFFFFF + #111827 + #374151 + #1F2937 + + #F9FAFB + #FFFFFF + #E5E7EB + #111827 + + \ No newline at end of file diff --git a/Resources/Styles/Styles.xaml b/Resources/Styles/Styles.xaml index 00db308..9501f65 100644 --- a/Resources/Styles/Styles.xaml +++ b/Resources/Styles/Styles.xaml @@ -1,32 +1,31 @@ - - - + - + - + + + + + + + - diff --git a/Services/PrinterService.cs b/Services/PrinterService.cs index 1eac8d2..d5c638f 100644 --- a/Services/PrinterService.cs +++ b/Services/PrinterService.cs @@ -1,234 +1,107 @@ -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; +using System.Drawing; +// We move the usings outside the IF for the Language Server, +// but keep the SupportedOSPlatform attribute to satisfy the linker. +using System.Drawing.Printing; +using System.IO; +using System.Runtime.Versioning; + +[assembly: SupportedOSPlatform("windows")] namespace FrymasterBadgeApp.Services; +[SupportedOSPlatform("windows")] public class PrinterService { - public async Task PrintBadge( - Dictionary emp, - Dictionary comp, - string ip - ) + public PrinterService() { + AppLogger.Info("PrinterService: Constructor started."); 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; + var test = System.Drawing.Color.White; + AppLogger.Info("PrinterService: GDI+ Compatibility check passed."); } catch (Exception ex) { - System.Diagnostics.Debug.WriteLine($"Print Engine Error: {ex.Message}"); - return false; + AppLogger.Error("PrinterService: GDI+ Compatibility check FAILED.", ex); } } - private SKBitmap GenerateFrontSide( - Dictionary emp, - Dictionary comp - ) + public void PrintBase64Badge(string frontBase64, string backBase64, string printerName) { - 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 emp, - Dictionary 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> DiscoverPrinters() - { - return await Task.Run(() => - { - List 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; + // Wrap the actual logic in the WINDOWS check so it doesn't try to compile on other platforms +#if WINDOWS try { - connection.Open(); - cardPrinter = ZebraCardPrinterFactory.GetInstance(connection); - using ZebraCardGraphics g = new ZebraCardGraphics(cardPrinter); - List info = new(); - if (!string.IsNullOrEmpty(front)) + AppLogger.Info($"PrinterService: Attempting to print to {printerName}"); + + using (PrintDocument pd = new PrintDocument()) { - g.DrawImage( - Convert.FromBase64String(front), - 0, - 0, - 0, - 0, - (Zebra.Sdk.Card.Graphics.Enumerations.RotationType)0 - ); - info.Add( - new Zebra.Sdk.Card.Containers.GraphicsInfo + pd.PrinterSettings.PrinterName = printerName; + pd.DefaultPageSettings.Landscape = true; + pd.DefaultPageSettings.Margins = new Margins(0, 0, 0, 0); + + bool frontPrinted = false; + + pd.PrintPage += (sender, e) => + { + try { - Side = Zebra.Sdk.Card.Enumerations.CardSide.Front, - PrintType = Zebra.Sdk.Card.Enumerations.PrintType.Color, - GraphicData = g.CreateImage(), + string currentB64 = !frontPrinted ? frontBase64 : backBase64; + + using (var ms = new MemoryStream(Convert.FromBase64String(currentB64))) + using (var img = System.Drawing.Image.FromStream(ms)) + { + if (img.Height > img.Width) + { + e.Graphics.TranslateTransform(e.PageBounds.Width, 0); + e.Graphics.RotateTransform(90); + e.Graphics.DrawImage( + img, + 0, + 0, + e.PageBounds.Height, + e.PageBounds.Width + ); + } + else + { + e.Graphics.DrawImage( + img, + 0, + 0, + e.PageBounds.Width, + e.PageBounds.Height + ); + } + } + + if (!frontPrinted && !string.IsNullOrEmpty(backBase64)) + { + frontPrinted = true; + e.HasMorePages = true; + } + else + { + e.HasMorePages = false; + } } - ); + catch (Exception ex) + { + AppLogger.Error("PrinterService: Error during PrintPage event", ex); + e.HasMorePages = false; + } + }; + + pd.Print(); + AppLogger.Info("PrinterService: Print job sent to spooler."); } - cardPrinter.Print(1, info); - return (true, ""); } catch (Exception ex) { - return (false, ex.Message); - } - finally - { - cardPrinter?.Destroy(); - if (connection.Connected) - connection.Close(); + AppLogger.Error("PrinterService: Critical failure in PrintBase64Badge", ex); } +#else + AppLogger.Warn("PrinterService: PrintBase64Badge called on non-Windows platform."); +#endif } } - -// Fixed missing classes and helpers -public class InternalStatusDiscoveryHandler : DiscoveryHandler -{ - public List 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 dict, string key) => - dict.TryGetValue(key, out var val) && val != null ? val.ToString()! : ""; -} diff --git a/Services/SqlService.cs b/Services/SqlService.cs index f5f42ca..67573a7 100644 --- a/Services/SqlService.cs +++ b/Services/SqlService.cs @@ -8,43 +8,94 @@ public class SqlService { private readonly string _connectionString; - public SqlService(IConfiguration configuration) + // Change constructor to accept string instead of IConfiguration + public SqlService(string connectionString) { - // Pulls the "Default" string from your JSON structure - _connectionString = - configuration.GetConnectionString("Default") - ?? throw new Exception("Connection string 'Default' not found in appsettings.json"); + _connectionString = connectionString; + + if (string.IsNullOrEmpty(_connectionString)) + { + AppLogger.Warn("SqlService: Initialized with an EMPTY connection string."); + } } public List> Query(string sql, SqlParameter[]? parameters = null) { var rows = new List>(); - 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()) + if (string.IsNullOrEmpty(_connectionString)) { - var row = new Dictionary(); - for (int i = 0; i < reader.FieldCount; i++) - { - row[reader.GetName(i)] = reader.GetValue(i); - } - rows.Add(row); + AppLogger.Warn( + $"SqlService: Skipping Query because connection string is missing. SQL: {sql}" + ); + return rows; } + + try + { + using var conn = new SqlConnection(_connectionString); + using var cmd = new SqlCommand(sql, conn); + + if (parameters != null) + cmd.Parameters.AddRange(parameters); + + AppLogger.Debug($"SqlService: Executing Query: {sql}"); + conn.Open(); + + using var reader = cmd.ExecuteReader(); + while (reader.Read()) + { + var row = new Dictionary(); + for (int i = 0; i < reader.FieldCount; i++) + { + row[reader.GetName(i)] = reader.GetValue(i); + } + rows.Add(row); + } + AppLogger.Info($"SqlService: Query returned {rows.Count} rows."); + } + catch (Exception ex) + { + AppLogger.Error($"SqlService: Query execution failed for: {sql}", ex); + // We re-throw here because this happens after the app has already started + throw; + } + 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(); + if (string.IsNullOrEmpty(_connectionString)) + { + AppLogger.Warn($"SqlService: Skipping Execute because connection string is missing."); + return; + } + + try + { + using var conn = new SqlConnection(_connectionString); + using var cmd = new SqlCommand(sql, conn); + + if (parameters != null) + { + // Clear any previous ownership just in case + cmd.Parameters.Clear(); + cmd.Parameters.AddRange(parameters); + } + + AppLogger.Debug($"SqlService: Executing Command: {sql}"); + conn.Open(); + int affected = cmd.ExecuteNonQuery(); + AppLogger.Info($"SqlService: Execute finished. Rows affected: {affected}"); + + // IMPORTANT: Clear parameters after execution so they can be reused if needed + cmd.Parameters.Clear(); + } + catch (Exception ex) + { + AppLogger.Error($"SqlService: Execute failed for: {sql}", ex); + throw; // This will trigger the DisplayAlert in your Page + } } } diff --git a/SettingsPage.xaml b/SettingsPage.xaml new file mode 100644 index 0000000..a0b9d6e --- /dev/null +++ b/SettingsPage.xaml @@ -0,0 +1,130 @@ + + + + + +