LSFE/LSFE.MobApp/ViewModels/Coverage/MarketProductVM.cs
2026-07-30 10:31:04 +08:00

484 lines
19 KiB
C#

using LSFE.MobApp.Contracts;
using LSFE.MobApp.Entities.Doctor;
using LSFE.MobApp.Entities.Planning;
using LSFE.MobApp.Helpers;
using LSFE.MobApp.Models;
using LSFE.MobApp.Pages.CoveragePlan;
using LSFE.MobApp.Services;
using LSFE.MobApp.Services.Coverage;
using LSFE.MobApp.ViewModels.Common;
using LSFE.MobApp.Views.CoveragePlan;
using Microsoft.Extensions.Configuration;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Windows.Input;
namespace LSFE.MobApp.ViewModels.Coverage
{
public class MarketProductVM : BaseDoctorVM
{
#region Service and Helper
private readonly IFileDownloadService _fileDownloadService;
#endregion
#region Repository
private readonly Repository<ProductTransaction> _productTransRepository;
private readonly Repository<InventoryTransaction> _inventoryTransRepository;
private readonly Repository<MRRawPlan> _mrRawPlanRepository;
#endregion
public ObservableCollection<ProductMaterial> Materials { get; set; }
public ObservableCollection<SamplingItem> Samplings { get; set; }
private readonly Doctor _doctor;
public MarketProductVM(Doctor doctor, Repository<Doctor> doctorRepository,
IFileDownloadService fileDownloadService,
Repository<ProductTransaction> productTransRepository,
Repository<InventoryTransaction> inventoryTransRepository,
Repository<MRRawPlan> mrRawPlanRepository,
ISyncService syncService, IConfiguration configuration, AddressService addressService)
: base(doctorRepository, syncService, addressService, configuration)
{
_doctor = doctor;
_fileDownloadService = fileDownloadService;
_productTransRepository = productTransRepository;
_inventoryTransRepository = inventoryTransRepository;
_mrRawPlanRepository = mrRawPlanRepository;
Materials = new ObservableCollection<ProductMaterial>();
Samplings = new ObservableCollection<SamplingItem>();
CloseCommand = new Command(async () => await Close());
CopyEmailCommand = new Command(async () => await CopyEmail());
CallPhoneCommand = new Command(async () => await CallPhone());
DownloadMaterialCommand = new Command<ProductMaterial>(async (material) => await DownloadMaterial(material));
OpenMaterialCommand = new Command<ProductMaterial>(async (material) => await OpenMaterial(material));
DownloadAllCommand = new Command(async () => await DownloadAllMaterials());
CheckAllMaterialsCommand = new Command(CheckAllMaterials);
CheckAllSamplingCommand = new Command(CheckAllSampling);
DoctorSignInCommand = new Command(async () => await ProcessDoctorSignIn());
_ = LoadMaterials();
_ = LoadSamplings();
}
#region Status Properties - Using Helper (DRY!)
public string StatusText => DoctorStatusHelper.GetStatusText(_doctor.Status);
public Color StatusBackgroundColor => DoctorStatusHelper.GetStatusBackgroundColor(_doctor.Status);
public Color StatusTextColor => DoctorStatusHelper.GetStatusTextColor(_doctor.Status);
#endregion
/// <summary>
/// This method is automatically called when status updates are broadcast
/// </summary>
protected override async void OnDoctorStatusUpdated(int doctorId)
{
// Only update if it's this doctor
if (_doctor.DoctorId == doctorId)
{
var updated = await RefreshDoctorFromRepositoryAsync(doctorId);
if (updated != null)
{
_doctor.Status = updated.Status;
// Notify UI to refresh
OnPropertyChanged(nameof(StatusText));
OnPropertyChanged(nameof(StatusBackgroundColor));
OnPropertyChanged(nameof(StatusTextColor));
}
}
}
private bool _isLoading;
public bool IsLoading
{
get => _isLoading;
set
{
_isLoading = value;
OnPropertyChanged();
}
}
#region Dr Information
public string FullName => $"Dr. {_doctor.FirstName} {_doctor.MiddleInitial} {_doctor.LastName}".Replace(" ", " ").Trim();
public string EmailAddress => _doctor.EmailAddress ?? "N/A";
public string PhoneNo => _doctor.PhoneNo ?? "N/A";
public string LicenseNo => _doctor.LicenseNo ?? "N/A";
public string Specialization => _doctor.SpecializationName ?? "General Practice";
public string InstitutionName => _doctor.InstitutionName ?? "No Institution Assigned";
public string Address => _doctor.Address ?? "No address provided";
public string MaxVisit => $"{_doctor.MaxVisit} doctors";
public string BirthDateFormatted => _doctor.BirthDate.ToString("MMMM dd, yyyy");
public string CreatedDateFormatted => _doctor.CreatedDate.ToString("MMMM dd, yyyy");
public bool HasInstitution => !string.IsNullOrEmpty(_doctor.InstitutionName);
public bool HasAddress => !string.IsNullOrEmpty(_doctor.Address);
#endregion
#region Styles
public static string GetIconEmoji(int iconEmojiId)
{
return iconEmojiId switch
{
1 => "💊",
2 => "🍎",
3 => "🍌",
4 => "🍓",
5 => "🍌",
6 => "💉",
_ => "🧪"
};
}
#endregion
#region Command
public ICommand CloseCommand { get; }
public ICommand CopyEmailCommand { get; }
public ICommand CallPhoneCommand { get; }
public ICommand DownloadMaterialCommand { get; }
public ICommand OpenMaterialCommand { get; }
public ICommand DownloadAllCommand { get; }
public ICommand CheckAllMaterialsCommand { get; }
public ICommand CheckAllSamplingCommand { get; }
public ICommand DoctorSignInCommand { get; }
#endregion
#region Method
private async Task ProcessDoctorSignIn()
{
try
{
IsLoading = true;
if (_doctor.Status == 2)
{
await UtilityVM.ShowAlert(
"Warning:",
$"Failed to process the signature. The visitation for Dr. {_doctor.FirstName} {_doctor.LastName} has already been completed."
);
return;
}
// Get all checked materials
var checkedMaterials = Materials.Where(m => m.IsChecked).ToList();
// Get all checked sampling items
var checkedSamplings = Samplings.Where(s => s.IsChecked).ToList();
if (!checkedMaterials.Any() && !checkedSamplings.Any())
{
await Application.Current.MainPage.DisplayAlert(
"No Selection",
"Please select at least one material or sampling item.",
"OK");
return;
}
// Build summary message
var vm = new ConfirmSelectionVM(
FullName,
Specialization,
checkedMaterials,
checkedSamplings,
async () =>
{
var signatureVM = new DoctorSignatureVM(
_doctor,
_addressService,
_syncService,
_mrRawPlanRepository,
_doctorRepository,
_configuration,
checkedMaterials,
checkedSamplings,
OnSignatureCompleted
);
var signaturePage = new DoctorSignaturePage(signatureVM);
await Application.Current.MainPage.Navigation.PushAsync(signaturePage);
});
await Application.Current.MainPage.Navigation.PushModalAsync(
new ConfirmSelectionModal(vm)
);
}
catch (Exception ex)
{
await Application.Current.MainPage.DisplayAlert("Error", $"Failed to process: {ex.Message}", "OK");
}
finally
{
IsLoading = false;
}
}
private async Task LoadSamplings()
{
try
{
Samplings.Clear();
var sampleData = await _inventoryTransRepository.GetAllAsync();
var today = DateTime.Today;
var filtered = sampleData.Where(i => i.DoctorId == _doctor.DoctorId &&
i.AppointmentDate >= today &&
i.AppointmentDate < today.AddDays(1));
foreach (var item in filtered)
{
Samplings.Add(new SamplingItem
{
InventoryTransId = item.InventoryTransId,
Description = item.Description,
Qty = item.QtyIn,
IconEmoji = GetIconEmoji(item.IconEmojiId),
IsChecked = false
});
}
}
catch (Exception ex)
{
Debug.WriteLine($"Failed to load samplings: {ex.Message}");
}
}
private async Task DownloadMaterial(ProductMaterial material)
{
if (material == null || material.IsDownloading) return;
try
{
material.IsDownloading = true;
await _fileDownloadService.DownloadFileAsync(material.FileUrl, material.ProductLink, CancellationToken.None);
material.IsDownloaded = true;
material.IsDownloading = false;
await Application.Current.MainPage.DisplayAlert("Success", $"{material.ProductName} downloaded successfully!", "OK");
}
catch (Exception ex)
{
material.IsDownloading = false;
await Application.Current.MainPage.DisplayAlert("Error", $"Failed to download: {ex.Message}", "OK");
}
}
private async Task OpenMaterial(ProductMaterial material)
{
try
{
if (!material.IsDownloaded)
{
var download = await Application.Current.MainPage.DisplayAlert(
"Not Downloaded",
"This material needs to be downloaded first. Download now?",
"Yes", "No");
if (download)
{
await DownloadMaterial(material);
}
return;
}
var localPath = _fileDownloadService.GetLocalFilePath(material.ProductLink);
if (File.Exists(localPath))
{
await Launcher.OpenAsync(new OpenFileRequest
{
File = new ReadOnlyFile(localPath)
});
}
else
{
await Application.Current.MainPage.DisplayAlert("Error", "File not found. Please re-download.", "OK");
material.IsDownloaded = false;
}
}
catch (Exception ex)
{
await Application.Current.MainPage.DisplayAlert("Error", $"Failed to open file: {ex.Message}", "OK");
}
}
private async Task DownloadMaterials(ProductMaterial material)
{
if (material == null || material.IsDownloading) return;
try
{
material.IsDownloading = true;
await _fileDownloadService.DownloadFileAsync(material.FileUrl, material.ProductLink, CancellationToken.None);
material.IsDownloaded = true;
material.IsDownloading = false;
}
catch (Exception ex)
{
material.IsDownloading = false;
await Application.Current.MainPage.DisplayAlert("Error", $"Failed to download: {ex.Message}", "OK");
}
}
private async Task DownloadAllMaterials()
{
try
{
await LoadMaterials();
var allTransactions = await _productTransRepository.GetAllAsync();
foreach (var product in allTransactions)
{
var endpoint = _configuration["ApiSettings:Endpoints:GetProductFiles"];
var fileUrl = $"{endpoint}{Uri.EscapeDataString(product.ProductLink)}";
var material = new ProductMaterial
{
ProductTransId = product.ProductTransId,
ProductId = product.ProductId,
ProductName = product.ProductName,
ProductLink = product.ProductLink,
FileUrl = fileUrl,
FileExtension = Path.GetExtension(product.ProductLink)
};
if (!await _fileDownloadService.IsFileDownloadedAsync(product.ProductLink))
{
await DownloadMaterials(material);
}
}
await Application.Current.MainPage.DisplayAlert("Success", "All materials downloaded!", "OK");
}
catch (Exception ex)
{
await Application.Current.MainPage.DisplayAlert("Error", $"Failed to download all: {ex.Message}", "OK");
}
}
private async Task LoadMaterials()
{
IsLoading = true;
try
{
await TrySyncAsync();
Materials.Clear();
// Broadcast to ALL ViewModels - ONE LINE!
DoctorStatusHelper.BroadcastStatusUpdate(_doctor.DoctorId);
var transactions = await _productTransRepository.GetAllAsync();
var displayList = transactions
.Where(p => p.DoctorId == _doctor.DoctorId && p.MRRawPlanId < _doctor.MRRawPlanId)
.ToList();
// Load materials in parallel
var materialTasks = displayList.Select(async product =>
{
var fileExtension = Path.GetExtension(product.ProductLink);
var endpoint = _configuration["ApiSettings:Endpoints:GetProductFiles"];
var fileUrl = $"{endpoint}{Uri.EscapeDataString(product.ProductLink)}";
var isDownloaded = await _fileDownloadService.IsFileDownloadedAsync(product.ProductLink);
return new ProductMaterial
{
ProductTransId = product.ProductTransId,
ProductId = product.ProductId,
ProductName = product.ProductName,
ProductLink = product.ProductLink,
FileUrl = fileUrl,
FileExtension = fileExtension,
IsDownloaded = isDownloaded,
IsChecked = false
};
});
var materials = await Task.WhenAll(materialTasks);
foreach (var m in materials)
Materials.Add(m);
}
finally
{
IsLoading = false;
}
}
private async Task TrySyncAsync()
{
try
{
await _syncService.SyncPullProductsAsync();
await _syncService.SyncPullSamplesAsync();
}
catch (Exception ex)
{
Debug.WriteLine($"Sync failed: {ex.Message}");
}
}
private void CheckAllMaterials()
{
bool allChecked = Materials.All(m => m.IsChecked);
foreach (var material in Materials)
{
material.IsChecked = !allChecked;
}
}
private void CheckAllSampling()
{
bool allChecked = Samplings.All(s => s.IsChecked);
foreach (var sampling in Samplings)
{
sampling.IsChecked = !allChecked;
}
}
private async Task Close()
{
await Application.Current.MainPage.Navigation.PopAsync();
}
private async Task CopyEmail()
{
if (!string.IsNullOrEmpty(_doctor.EmailAddress))
{
await Clipboard.SetTextAsync(_doctor.EmailAddress);
await Application.Current.MainPage.DisplayAlert("Copied", "Email address copied to clipboard", "OK");
}
}
private async Task CallPhone()
{
if (!string.IsNullOrEmpty(_doctor.PhoneNo))
{
try
{
if (PhoneDialer.Default.IsSupported)
{
PhoneDialer.Default.Open(_doctor.PhoneNo);
}
else
{
await Clipboard.SetTextAsync(_doctor.PhoneNo);
await Application.Current.MainPage.DisplayAlert("Phone Number", "Phone number copied to clipboard", "OK");
}
}
catch (Exception ex)
{
await Application.Current.MainPage.DisplayAlert("Error", $"Unable to dial: {ex.Message}", "OK");
}
}
}
private void OnSignatureCompleted(byte[] signatureBytes, string signatureFilePath)
{
// Handle the completed signature here
// You can save to database, upload to server, etc.
// Example: Save the transaction record
// var transaction = new DoctorTransaction
// {
// DoctorId = _doctor.DoctorId,
// SignatureFilePath = signatureFilePath,
// TransactionDate = DateTime.Now,
// Materials = checkedMaterials,
// Samplings = checkedSamplings
// };
// await _repository.SaveAsync(transaction);
}
#endregion
}
}