diff --git a/App.xaml.cs b/App.xaml.cs
index 55142af..5595912 100644
--- a/App.xaml.cs
+++ b/App.xaml.cs
@@ -40,6 +40,7 @@ public partial class App : Application
var configService = new ConfigService();
var employeeLookupService = new EmployeeLookupService(configService);
+ var employeePhotoService = new EmployeePhotoService(configService);
var menuLookupService = new MenuLookupService(configService);
var mealScheduleService = new ProductionMealScheduleService(configService);
var mealSessionResolver = new DbMealSessionResolver(mealScheduleService);
@@ -55,7 +56,7 @@ public partial class App : Application
navigationService = new NavigationService(
session,
() => new AdminLoginViewModel(authService, session, navigationService, configService, adminAuditService),
- () => new ScannerDashboardViewModel(rfidService, navigationService, session, configService, menuLookupService),
+ () => new ScannerDashboardViewModel(rfidService, navigationService, session, configService, menuLookupService, employeePhotoService),
() => new MainDashboardViewModel(navigationService, rfidService, configService, session),
() => new AdminSettingsAuthViewModel(authService, session, navigationService, configService),
() => new SettingsViewModel(configService, navigationService, adminAuditService, syncService),
diff --git a/Data/AppDbContext.cs b/Data/AppDbContext.cs
index fb50f4c..f5294fe 100644
--- a/Data/AppDbContext.cs
+++ b/Data/AppDbContext.cs
@@ -163,6 +163,13 @@ public class AppDbContext : DbContext
cmd.ExecuteNonQuery();
}
+ if (columns.Count > 0 && !columns.Contains("ParentDocumentId", StringComparer.OrdinalIgnoreCase))
+ {
+ using var cmd = conn.CreateCommand();
+ cmd.CommandText = "ALTER TABLE lunch_order_transactions ADD COLUMN ParentDocumentId TEXT DEFAULT ''";
+ cmd.ExecuteNonQuery();
+ }
+
if (columns.Count > 0 && !columns.Contains("EmployeeId", StringComparer.OrdinalIgnoreCase))
{
using var cmd = conn.CreateCommand();
diff --git a/Models/HrmsEmployeeInfo.cs b/Models/HrmsEmployeeInfo.cs
index 7d64b3c..cde2f73 100644
--- a/Models/HrmsEmployeeInfo.cs
+++ b/Models/HrmsEmployeeInfo.cs
@@ -5,6 +5,9 @@ namespace UtopiaCanteenSystem.Models;
///
public class HrmsEmployeeInfo
{
+ /// Employee document id (employee.id); used to fetch photo from hrms.employee_photo.
+ public string ParentDocumentId { get; set; } = string.Empty;
+ /// Employee serial number (employee.serial_number).
public string EmployeeId { get; set; } = string.Empty;
public string FirstName { get; set; } = string.Empty;
public string MiddleName { get; set; } = string.Empty;
diff --git a/Models/OrderHistoryItem.cs b/Models/OrderHistoryItem.cs
index 551925a..ae1d2a8 100644
--- a/Models/OrderHistoryItem.cs
+++ b/Models/OrderHistoryItem.cs
@@ -1,3 +1,5 @@
+using System.Windows.Media;
+
namespace UtopiaCanteenSystem.Models;
///
@@ -5,6 +7,8 @@ namespace UtopiaCanteenSystem.Models;
///
public class OrderHistoryItem
{
+ /// HRMS employee document id (employee.id) for this order; used to load photo.
+ public string ParentDocumentId { get; set; } = string.Empty;
public string EmployeeId { get; set; } = string.Empty;
public string EmployeeName { get; set; } = string.Empty;
public string Department { get; set; } = string.Empty;
@@ -18,6 +22,8 @@ public class OrderHistoryItem
public string RelativeDateLabel { get; set; } = string.Empty;
/// Time only, e.g. "09:54 AM".
public string TimeDisplay { get; set; } = string.Empty;
- /// Optional avatar path; null = show placeholder.
- public string? AvatarPath { get; set; }
+ /// Optional employee photo for this row; null = show placeholder.
+ public ImageSource? EmployeePhoto { get; set; }
+ /// True when EmployeePhoto is not null.
+ public bool HasEmployeePhoto => EmployeePhoto != null;
}
diff --git a/Models/ScanRecord.cs b/Models/ScanRecord.cs
index c33030b..9cd1577 100644
--- a/Models/ScanRecord.cs
+++ b/Models/ScanRecord.cs
@@ -18,6 +18,8 @@ public class ScanRecord
public string DeviceId { get; set; } = string.Empty;
/// IP address of the device at scan time.
public string IpAddress { get; set; } = string.Empty;
+ /// HRMS employee document id (employee.id) at scan time; used to load photo from hrms.employee_photo.
+ public string ParentDocumentId { get; set; } = string.Empty;
/// HRMS employee ID (serial_number) at scan time, when lookup succeeded.
public string EmployeeId { get; set; } = string.Empty;
/// Full name (first + middle) from HRMS at scan time, when lookup succeeded.
diff --git a/Properties/PublishProfiles/ClickOnceProfile.pubxml b/Properties/PublishProfiles/ClickOnceProfile.pubxml
index 83446e0..e5fcd31 100644
--- a/Properties/PublishProfiles/ClickOnceProfile.pubxml
+++ b/Properties/PublishProfiles/ClickOnceProfile.pubxml
@@ -2,7 +2,7 @@
- 11
+ 13
1.0.0.*
True
Release
@@ -33,6 +33,6 @@
False
\\FileServer\Edata\Deployments-by-DotNet-Team\UtopiaCanteenSystem\
Publish.html
- True|2026-02-12T06:25:40.9881813Z||;True|2026-02-12T11:22:02.6537306+05:00||;True|2026-02-12T10:57:16.2775876+05:00||;True|2026-02-12T10:50:58.6968682+05:00||;False|2026-02-12T10:49:27.1867616+05:00||;True|2026-02-12T10:44:55.6022990+05:00||;True|2026-02-11T17:00:04.2786466+05:00||;True|2026-02-11T16:54:37.5052808+05:00||;True|2026-02-11T16:39:26.1892892+05:00||;True|2026-02-11T16:30:57.2420414+05:00||;True|2026-02-11T16:20:44.4749529+05:00||;
+ False|2026-03-03T06:08:36.2813025Z||;False|2026-03-03T11:07:21.6156279+05:00||;True|2026-02-18T16:44:11.3509970+05:00||;True|2026-02-12T11:25:40.9881813+05:00||;True|2026-02-12T11:22:02.6537306+05:00||;True|2026-02-12T10:57:16.2775876+05:00||;True|2026-02-12T10:50:58.6968682+05:00||;False|2026-02-12T10:49:27.1867616+05:00||;True|2026-02-12T10:44:55.6022990+05:00||;True|2026-02-11T17:00:04.2786466+05:00||;True|2026-02-11T16:54:37.5052808+05:00||;True|2026-02-11T16:39:26.1892892+05:00||;True|2026-02-11T16:30:57.2420414+05:00||;True|2026-02-11T16:20:44.4749529+05:00||;
\ No newline at end of file
diff --git a/Services/EmployeeLookupService.cs b/Services/EmployeeLookupService.cs
index 4b613f0..4cee5b1 100644
--- a/Services/EmployeeLookupService.cs
+++ b/Services/EmployeeLookupService.cs
@@ -26,9 +26,10 @@ public class EmployeeLookupService : IEmployeeLookupService
const string sql = @"
SELECT
+ e.id AS parent_document_id,
e.serial_number AS employee_id,
- e.first_name,
- e.middle_name,
+ e.concatenated_name AS first_name,
+ '' AS middle_name,
e.department_id,
d.title AS department_title,
d.department_type,
@@ -51,13 +52,14 @@ public class EmployeeLookupService : IEmployeeLookupService
return new HrmsEmployeeInfo
{
- EmployeeId = GetString(reader, 0),
- FirstName = GetString(reader, 1),
- MiddleName = GetString(reader, 2),
- DepartmentId = GetString(reader, 3),
- DepartmentTitle = GetString(reader, 4),
- DepartmentType = GetString(reader, 5),
- LocationSiteId = GetString(reader, 6)
+ ParentDocumentId = GetString(reader, 0),
+ EmployeeId = GetString(reader, 1),
+ FirstName = GetString(reader, 2),
+ MiddleName = GetString(reader, 3),
+ DepartmentId = GetString(reader, 4),
+ DepartmentTitle = GetString(reader, 5),
+ DepartmentType = GetString(reader, 6),
+ LocationSiteId = GetString(reader, 7)
};
}
diff --git a/Services/EmployeePhotoService.cs b/Services/EmployeePhotoService.cs
new file mode 100644
index 0000000..4272d80
--- /dev/null
+++ b/Services/EmployeePhotoService.cs
@@ -0,0 +1,114 @@
+using System.Collections.Concurrent;
+using System.Text;
+using MySqlConnector;
+
+namespace UtopiaCanteenSystem.Services;
+
+///
+/// Fetches employee photo from hrms.employee_photo by parent_document_id (employee document id from lookup).
+/// Handles data URI (data:image/...;base64,...) or raw image bytes. Caches by key to avoid repeated DB calls.
+///
+public class EmployeePhotoService : IEmployeePhotoService
+{
+ private readonly IConfigService _configService;
+ private readonly ConcurrentDictionary _cache = new(StringComparer.OrdinalIgnoreCase);
+ private const int MaxCacheEntries = 50;
+
+ public EmployeePhotoService(IConfigService configService)
+ {
+ _configService = configService;
+ }
+
+ ///
+ public async Task GetPhotoBytesAsync(string parentDocumentId, CancellationToken cancellationToken = default)
+ {
+ if (string.IsNullOrWhiteSpace(parentDocumentId))
+ return null;
+
+ var key = parentDocumentId.Trim();
+ if (_cache.TryGetValue(key, out var cached))
+ return cached;
+
+ var connectionString = _configService.GetHrmsLookupConnectionString();
+ if (string.IsNullOrWhiteSpace(connectionString))
+ return null;
+
+ try
+ {
+ await using var conn = new MySqlConnection(connectionString);
+ await conn.OpenAsync(cancellationToken).ConfigureAwait(false);
+
+ // employee_photo.employee_id stores the employee document id (parent_document_id from employee lookup)
+ const string sql = "SELECT photo_blob FROM hrms.employee_photo WHERE employee_id = @parentDocumentId LIMIT 1";
+ await using var cmd = new MySqlCommand(sql, conn);
+ cmd.Parameters.AddWithValue("@parentDocumentId", key);
+
+ await using var reader = await cmd.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false);
+ if (!await reader.ReadAsync(cancellationToken).ConfigureAwait(false))
+ return null;
+
+ if (reader.IsDBNull(0))
+ return null;
+
+ byte[] blob;
+ try
+ {
+ var len = reader.GetBytes(0, 0, null, 0, 0);
+ blob = new byte[len];
+ reader.GetBytes(0, 0, blob, 0, (int)len);
+ }
+ catch
+ {
+ return null;
+ }
+
+ var imageBytes = DecodeBlobToImageBytes(blob);
+ if (imageBytes == null || imageBytes.Length == 0)
+ return null;
+
+ // Cache (evict old if needed)
+ while (_cache.Count >= MaxCacheEntries && _cache.Count > 0)
+ {
+ var first = _cache.Keys.FirstOrDefault();
+ if (first != null)
+ _cache.TryRemove(first, out _);
+ else
+ break;
+ }
+ _cache[key] = imageBytes;
+ return imageBytes;
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ ///
+ /// Converts blob to image bytes: if blob is UTF-8 "data:image...;base64,<payload>", decodes base64; else treats as raw image.
+ ///
+ internal static byte[]? DecodeBlobToImageBytes(byte[] blob)
+ {
+ if (blob == null || blob.Length == 0)
+ return null;
+
+ try
+ {
+ var str = Encoding.UTF8.GetString(blob);
+ if (str.StartsWith("data:image", StringComparison.OrdinalIgnoreCase))
+ {
+ var commaIndex = str.IndexOf(',');
+ if (commaIndex < 0)
+ return null;
+ var base64 = str.Substring(commaIndex + 1).Trim();
+ return Convert.FromBase64String(base64);
+ }
+
+ return blob;
+ }
+ catch
+ {
+ return null;
+ }
+ }
+}
diff --git a/Services/IEmployeePhotoService.cs b/Services/IEmployeePhotoService.cs
new file mode 100644
index 0000000..2014f35
--- /dev/null
+++ b/Services/IEmployeePhotoService.cs
@@ -0,0 +1,14 @@
+namespace UtopiaCanteenSystem.Services;
+
+///
+/// Fetches employee profile photo from hrms.employee_photo by parent_document_id (employee document id from RFID lookup).
+/// Photo blob may be a data URI (data:image/...;base64,...) or raw image bytes.
+///
+public interface IEmployeePhotoService
+{
+ ///
+ /// Gets photo bytes for the given parent_document_id (employee.id from lookup), or null if not found / decode failed.
+ /// May return cached bytes for the same key.
+ ///
+ Task GetPhotoBytesAsync(string parentDocumentId, CancellationToken cancellationToken = default);
+}
diff --git a/Services/RfidService.cs b/Services/RfidService.cs
index 276f377..b2ccc40 100644
--- a/Services/RfidService.cs
+++ b/Services/RfidService.cs
@@ -108,6 +108,7 @@ public class RfidService : IRfidService
SiteId = siteId,
DeviceId = _configService.GetDeviceId(),
IpAddress = GetLocalIpAddress(),
+ ParentDocumentId = employee.ParentDocumentId ?? string.Empty,
EmployeeId = employee.EmployeeId ?? string.Empty,
EmployeeName = string.IsNullOrEmpty(fullName) ? string.Empty : fullName,
Department = employee.DepartmentTitle ?? string.Empty,
diff --git a/ViewModels/MealSchedulesViewModel.cs b/ViewModels/MealSchedulesViewModel.cs
index 6f56c0b..fe4333e 100644
--- a/ViewModels/MealSchedulesViewModel.cs
+++ b/ViewModels/MealSchedulesViewModel.cs
@@ -195,10 +195,18 @@ public partial class MealSchedulesViewModel : ObservableObject
}
}
+ /// Selects a schedule for editing (used by Actions column Edit button).
[RelayCommand]
- private async Task Delete()
+ private void SelectSchedule(MealSchedule? schedule)
{
- if (SelectedSchedule == null)
+ SelectedSchedule = schedule;
+ }
+
+ [RelayCommand]
+ private async Task Delete(MealSchedule? row)
+ {
+ var toDelete = row ?? SelectedSchedule;
+ if (toDelete == null)
{
Message = "Select a schedule to delete.";
IsError = true;
@@ -208,7 +216,7 @@ public partial class MealSchedulesViewModel : ObservableObject
IsError = false;
try
{
- await _mealScheduleService.DeleteAsync(SelectedSchedule.Id).ConfigureAwait(true);
+ await _mealScheduleService.DeleteAsync(toDelete.Id).ConfigureAwait(true);
Message = "Schedule deleted from production.";
SelectedSchedule = null;
await LoadSchedulesAsync().ConfigureAwait(true);
diff --git a/ViewModels/ScannerDashboardViewModel.cs b/ViewModels/ScannerDashboardViewModel.cs
index 43cfb69..a249a74 100644
--- a/ViewModels/ScannerDashboardViewModel.cs
+++ b/ViewModels/ScannerDashboardViewModel.cs
@@ -5,6 +5,8 @@ using System.Text;
using DebounceTimer = System.Timers.Timer;
using Microsoft.Win32;
using System.Windows;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
using System.Windows.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
@@ -138,6 +140,13 @@ public partial class ScannerDashboardViewModel : ObservableObject
[ObservableProperty]
private string? _employeeProfileImagePath;
+ /// Employee photo from hrms.employee_photo (in-memory); null = show placeholder.
+ [ObservableProperty]
+ [NotifyPropertyChangedFor(nameof(HasEmployeeProfileImage))]
+ private ImageSource? _employeeProfileImage;
+
+ public bool HasEmployeeProfileImage => EmployeeProfileImage != null;
+
/// Last 4 orders for the Order History card (newest first).
public ObservableCollection OrderHistory { get; } = new();
@@ -154,19 +163,22 @@ public partial class ScannerDashboardViewModel : ObservableObject
private bool _isOrderHistoryModalOpen;
private readonly IMenuLookupService _menuLookupService;
+ private readonly IEmployeePhotoService _employeePhotoService;
public ScannerDashboardViewModel(
IRfidService rfidService,
INavigationService navigation,
AppSession session,
IConfigService configService,
- IMenuLookupService menuLookupService)
+ IMenuLookupService menuLookupService,
+ IEmployeePhotoService employeePhotoService)
{
_rfidService = rfidService;
_navigation = navigation;
_session = session;
_configService = configService;
_menuLookupService = menuLookupService;
+ _employeePhotoService = employeePhotoService;
_uiDispatcher = Application.Current?.Dispatcher ?? Dispatcher.CurrentDispatcher;
// Debounce auto-submit (keyboard wedge scanners).
@@ -503,6 +515,8 @@ public partial class ScannerDashboardViewModel : ObservableObject
// Load menu using site from employee_rfid_tag.location_site_id (not config)
if (int.TryParse(info.LocationSiteId?.Trim(), out var siteFromRfid))
_ = LoadMenuForSiteAsync(siteFromRfid, result.MealSession);
+ // Load employee photo from hrms.employee_photo by parent_document_id (employee document id)
+ _ = LoadEmployeePhotoAsync(info.ParentDocumentId);
}
// Refresh dashboard stats and order history
_ = RefreshDashboardAfterSuccessfulScanAsync(cardId);
@@ -575,6 +589,7 @@ public partial class ScannerDashboardViewModel : ObservableObject
EmployeeDepartment = "—";
EmployeeDepartmentType = "—";
EmployeeOrderItem = "—";
+ EmployeeProfileImage = null;
return;
}
@@ -591,6 +606,53 @@ public partial class ScannerDashboardViewModel : ObservableObject
EmployeeOrderItem = last.MealItems;
else
EmployeeOrderItem = "—";
+ // Load photo for last scanned employee (e.g. on app restart) using parent_document_id
+ _ = LoadEmployeePhotoAsync(string.IsNullOrWhiteSpace(last.ParentDocumentId) ? null : last.ParentDocumentId);
+ }
+
+ ///
+ /// Loads employee photo from hrms.employee_photo by parent_document_id and sets EmployeeProfileImage on the UI thread.
+ /// Fails gracefully: no photo or decode error leaves placeholder (null).
+ ///
+ private async Task LoadEmployeePhotoAsync(string? parentDocumentId)
+ {
+ if (string.IsNullOrWhiteSpace(parentDocumentId))
+ {
+ _uiDispatcher.Invoke(() => EmployeeProfileImage = null);
+ return;
+ }
+
+ try
+ {
+ var bytes = await _employeePhotoService.GetPhotoBytesAsync(parentDocumentId.Trim()).ConfigureAwait(false);
+ _uiDispatcher.Invoke(() =>
+ {
+ if (bytes == null || bytes.Length == 0)
+ {
+ EmployeeProfileImage = null;
+ return;
+ }
+ try
+ {
+ using var ms = new MemoryStream(bytes);
+ var bmp = new BitmapImage();
+ bmp.BeginInit();
+ bmp.CacheOption = BitmapCacheOption.OnLoad;
+ bmp.StreamSource = ms;
+ bmp.EndInit();
+ bmp.Freeze();
+ EmployeeProfileImage = bmp;
+ }
+ catch
+ {
+ EmployeeProfileImage = null;
+ }
+ });
+ }
+ catch
+ {
+ _uiDispatcher.Invoke(() => EmployeeProfileImage = null);
+ }
}
private void LoadOrderHistory()
@@ -602,9 +664,11 @@ public partial class ScannerDashboardViewModel : ObservableObject
var local = r.ScanTime.ToLocalTime();
var employeeId = !string.IsNullOrWhiteSpace(r.EmployeeId) ? r.EmployeeId : "—";
var employeeName = !string.IsNullOrWhiteSpace(r.EmployeeName) ? r.EmployeeName : "—";
+ var photo = CreateImageSourceFromPhoto(r.ParentDocumentId);
OrderHistory.Add(new OrderHistoryItem
{
+ ParentDocumentId = r.ParentDocumentId ?? string.Empty,
EmployeeId = employeeId,
EmployeeName = employeeName,
Department = string.IsNullOrWhiteSpace(r.Department) ? "—" : r.Department,
@@ -615,7 +679,8 @@ public partial class ScannerDashboardViewModel : ObservableObject
: (!string.IsNullOrWhiteSpace(r.MealLabel) ? r.MealLabel : "—"),
TotalPrice = r.TotalPrice > 0 ? r.TotalPrice : 0,
TimeDisplay = local.ToString("hh:mm tt"),
- RelativeDateLabel = GetRelativeDateLabel(r.ScanTime)
+ RelativeDateLabel = GetRelativeDateLabel(r.ScanTime),
+ EmployeePhoto = photo
});
}
@@ -631,9 +696,11 @@ public partial class ScannerDashboardViewModel : ObservableObject
var local = r.ScanTime.ToLocalTime();
var employeeId = !string.IsNullOrWhiteSpace(r.EmployeeId) ? r.EmployeeId : "—";
var employeeName = !string.IsNullOrWhiteSpace(r.EmployeeName) ? r.EmployeeName : "—";
+ var photo = CreateImageSourceFromPhoto(r.ParentDocumentId);
TodayOrderHistory.Add(new OrderHistoryItem
{
+ ParentDocumentId = r.ParentDocumentId ?? string.Empty,
EmployeeId = employeeId,
EmployeeName = employeeName,
Department = string.IsNullOrWhiteSpace(r.Department) ? "—" : r.Department,
@@ -644,11 +711,45 @@ public partial class ScannerDashboardViewModel : ObservableObject
: (!string.IsNullOrWhiteSpace(r.MealLabel) ? r.MealLabel : "—"),
TotalPrice = r.TotalPrice > 0 ? r.TotalPrice : 0,
TimeDisplay = local.ToString("hh:mm tt"),
- RelativeDateLabel = GetRelativeDateLabel(r.ScanTime)
+ RelativeDateLabel = GetRelativeDateLabel(r.ScanTime),
+ EmployeePhoto = photo
});
}
}
+ ///
+ /// Helper to synchronously create an ImageSource for order history rows using cached employee photos.
+ /// Returns null on any failure and does not throw.
+ ///
+ private ImageSource? CreateImageSourceFromPhoto(string? parentDocumentId)
+ {
+ try
+ {
+ if (string.IsNullOrWhiteSpace(parentDocumentId))
+ return null;
+
+ var bytes = _employeePhotoService.GetPhotoBytesAsync(parentDocumentId.Trim())
+ .ConfigureAwait(false)
+ .GetAwaiter()
+ .GetResult();
+ if (bytes == null || bytes.Length == 0)
+ return null;
+
+ using var ms = new MemoryStream(bytes);
+ var bmp = new BitmapImage();
+ bmp.BeginInit();
+ bmp.CacheOption = BitmapCacheOption.OnLoad;
+ bmp.StreamSource = ms;
+ bmp.EndInit();
+ bmp.Freeze();
+ return bmp;
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
[RelayCommand]
private void OpenOrderHistoryModal()
{
diff --git a/Views/MealSchedulesView.xaml b/Views/MealSchedulesView.xaml
index d5fd1c7..3948fa0 100644
--- a/Views/MealSchedulesView.xaml
+++ b/Views/MealSchedulesView.xaml
@@ -4,114 +4,577 @@
xmlns:converters="clr-namespace:UtopiaCanteenSystem.Converters">
-
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/Views/ScannerDashboardView.xaml b/Views/ScannerDashboardView.xaml
index 5bb278e..3e2d7dd 100644
--- a/Views/ScannerDashboardView.xaml
+++ b/Views/ScannerDashboardView.xaml
@@ -165,15 +165,26 @@
-
+
-
+
-
+
+
+
+
+
@@ -491,7 +502,31 @@
-
+
+
+
+
+
+
+
+
+
+
+
+