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

513 lines
20 KiB
C#

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using LSFE.MobApp.Contracts;
using LSFE.MobApp.Entities.Account;
using LSFE.MobApp.Entities.Doctor;
using LSFE.MobApp.Entities.Location;
using LSFE.MobApp.Models;
using LSFE.MobApp.Services;
using LSFE.Infrastructure.Entities.Maintenance;
using Microsoft.Extensions.Logging;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace LSFE.MobApp.ViewModels
{
public partial class MainPageVM : ObservableObject, INotifyPropertyChanged
{
private readonly Repository<User> _userRepository;
private readonly Repository<Institution> _institutionRepository;
private readonly Repository<Doctor> _doctorRepository;
private readonly IDatabaseService _databaseService;
private readonly ILogger<MainPageVM> _logger;
private readonly ISyncService _syncService; // Add sync service
private readonly IApiClientFactory _apiClientFactory; // Add API client factory
private DateTime _currentMonthStart;
private string _selectedTask = "REMOTE, TASK 1";
private bool _isLeftPanelVisible = true;
private int _currentCycle;
private bool _isLoading;
private bool _isSyncing; // Add sync status
private string _statusMessage = string.Empty; // Add status message
public ObservableCollection<Institution> Institutions { get; set; } = new();
public ObservableCollection<Entities.Doctor.Doctor> AllDoctors { get; set; } = new();
// Add property to track selected doctor
[ObservableProperty]
private Doctor selectedDoctor;
// Add property to control which view is shown
[ObservableProperty]
private bool isCalendarViewVisible = true;
// Property to control doctor view visibility
public bool IsDoctorViewVisible => !isCalendarViewVisible;
// Command to toggle institution expansion
[RelayCommand]
private void ToggleInstitution(Institution institution)
{
if (institution != null)
{
institution.IsExpanded = !institution.IsExpanded;
}
}
public bool IsSyncing
{
get => _isSyncing;
set => SetProperty(ref _isSyncing, value);
}
public string StatusMessage
{
get => _statusMessage;
set => SetProperty(ref _statusMessage, value);
}
// Add this property for header visibility
private bool _isHeaderVisible = true;
public bool IsHeaderVisible
{
get => _isHeaderVisible;
set
{
if (_isHeaderVisible != value)
{
_isHeaderVisible = value;
OnPropertyChanged(nameof(IsHeaderVisible));
}
}
}
public string SelectedTask
{
get => _selectedTask;
set => SetProperty(ref _selectedTask, value);
}
public DateTime CurrentMonthStart
{
get => _currentMonthStart;
set
{
if (SetProperty(ref _currentMonthStart, value))
{
OnPropertyChanged(nameof(CycleText));
OnPropertyChanged(nameof(CurrentMonthText));
// LoadCalendarMonth();
}
}
}
public bool IsLeftPanelVisible
{
get => _isLeftPanelVisible;
set
{
if (SetProperty(ref _isLeftPanelVisible, value))
{
OnPropertyChanged(nameof(LeftPanelWidth));
OnPropertyChanged(nameof(LeftPanelVisibility));
}
}
}
public int CurrentCycle
{
get => _currentCycle;
set
{
if (SetProperty(ref _currentCycle, value))
{
OnPropertyChanged(nameof(CycleText));
}
}
}
public bool IsLoading
{
get => _isLoading;
set => SetProperty(ref _isLoading, value);
}
public string CycleText => $"Cycle {CurrentCycle}";
public string CurrentMonthText => CurrentMonthStart.ToString("MMMM yyyy");
public GridLength LeftPanelWidth => IsLeftPanelVisible ? new GridLength(250, GridUnitType.Absolute) : new GridLength(0, GridUnitType.Absolute);
public bool LeftPanelVisibility => IsLeftPanelVisible;
public MainPageVM(
Repository<User> userRepository,
Repository<Doctor> doctorRepository,
Repository<Institution> institutionRepository,
IDatabaseService databaseService,
ILogger<MainPageVM> logger,
ISyncService syncService, // Add sync service injection
IApiClientFactory apiClientFactory) // Add API client factory injection
{
_userRepository = userRepository;
_doctorRepository = doctorRepository;
_institutionRepository = institutionRepository;
_databaseService = databaseService;
_logger = logger;
_syncService = syncService;
_apiClientFactory = apiClientFactory;
//InitializePlottingNavigationItems();
// Initialize with current month
CurrentMonthStart = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1);
CurrentCycle = DateTime.Today.Month;
// Load data asynchronously
_ = Task.Run(async () => await InitializeDataAsync());
}
[RelayCommand]
private async Task SyncFromServerAsync()
{
if (IsSyncing) return;
try
{
IsSyncing = true;
StatusMessage = "Fetching data from server...";
// Create API client using the exact API entity type
var apiClient = _apiClientFactory.CreateClient<Institutions>("institutions");
// Call your Web API endpoint
var serverInstitutions = await apiClient.GetAllAsync();
if (serverInstitutions?.Any() == true)
{
StatusMessage = $"Received {serverInstitutions.Count} institutions from server";
_logger.LogInformation($"Successfully fetched {serverInstitutions.Count} institutions from server");
// Log sample data to verify API response
foreach (var institution in serverInstitutions.Take(3))
{
_logger.LogInformation($"API Data: ID={institution.InstitutionId}, Name={institution.InstitutionName}, Territory={institution.TerritoryId}, Active={institution.IsActive}");
}
// Process the data
await ProcessServerDataAsync(serverInstitutions);
StatusMessage = "Server data processed successfully!";
}
else
{
StatusMessage = "No data received from server";
_logger.LogWarning("No institutions received from server");
}
// Clear status message after delay
await Task.Delay(3000);
StatusMessage = string.Empty;
}
catch (HttpRequestException httpEx)
{
_logger.LogError(httpEx, "Network error while fetching from server");
StatusMessage = "Network error: Check your connection";
}
catch (Exception ex)
{
_logger.LogError(ex, "Error fetching data from server");
StatusMessage = $"Server error: {ex.Message}";
}
finally
{
IsSyncing = false;
// Clear error messages after delay
if (!string.IsNullOrEmpty(StatusMessage))
{
await Task.Delay(5000);
StatusMessage = string.Empty;
}
}
}
// Option 1: Use InsertAsync directly instead of SaveAsync
private async Task ProcessServerDataAsync(List<Institutions> serverInstitutions)
{
try
{
_logger.LogInformation($"=== PROCESSING SERVER DATA ===");
_logger.LogInformation($"Received {serverInstitutions.Count} institutions from server");
int newCount = 0;
int updatedCount = 0;
int errorCount = 0;
foreach (var serverInstitution in serverInstitutions)
{
try
{
// Find existing local record by InstitutionId
var existingLocal = await _institutionRepository.FirstOrDefaultAsync(x => x.InstitutionId == serverInstitution.InstitutionId);
if (existingLocal == null)
{
// Create new local record with DEFAULT GUID initially
var newInstitution = new Institution
{
Id = default(Guid), // Use default GUID for SaveAsync to work
InstitutionId = serverInstitution.InstitutionId,
InstitutionName = serverInstitution.InstitutionName,
TerritoryId = serverInstitution.TerritoryId,
IsActive = serverInstitution.IsActive,
LastSyncedAt = DateTime.UtcNow,
NeedsSync = false,
CreatedDate = DateTime.UtcNow
};
_logger.LogInformation($"Creating: {newInstitution.InstitutionName} (ServerID: {newInstitution.InstitutionId})");
// Method 1: Use SaveAsync with default GUID
var result = await _institutionRepository.SaveAsync(newInstitution);
_logger.LogInformation($"Save result: {result}");
if (result > 0)
{
newCount++;
_logger.LogInformation($"✅ Successfully created: {newInstitution.InstitutionName}");
}
else
{
_logger.LogWarning($"❌ SaveAsync failed, trying direct insert...");
// Method 2: Try direct insert as backup
//try
//{
// newInstitution.Id = Guid.NewGuid(); // Set proper GUID for direct insert
// // Use direct SQLite insert
// var insertQuery = @"
//INSERT INTO Institutions (Id, InstitutionId, InstitutionName, TerritoryId, IsActive, CreatedDate, LastSyncedAt, NeedsSync)
//VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
// await _institutionRepository.QueryAsync<object>(insertQuery,
// newInstitution.Id.ToString(),
// newInstitution.InstitutionId,
// newInstitution.InstitutionName,
// newInstitution.TerritoryId,
// newInstitution.IsActive ? 1 : 0,
// newInstitution.CreatedDate.ToString("yyyy-MM-dd HH:mm:ss"),
// newInstitution.LastSyncedAt.ToString("yyyy-MM-dd HH:mm:ss"),
// newInstitution.NeedsSync ? 1 : 0
// );
// newCount++;
// _logger.LogInformation($"✅ Direct insert successful: {newInstitution.InstitutionName}");
//}
//catch (Exception insertEx)
//{
// _logger.LogError(insertEx, $"❌ Direct insert also failed: {newInstitution.InstitutionName}");
// errorCount++;
//}
}
// Verify the record was saved
var verification = await _institutionRepository.FirstOrDefaultAsync(x => x.InstitutionId == serverInstitution.InstitutionId);
if (verification != null)
{
_logger.LogInformation($"✅ Verified in database: {verification.InstitutionName} with GUID: {verification.Id}");
}
else
{
_logger.LogWarning($"❌ Could not verify saved record for {newInstitution.InstitutionName}");
}
}
else
{
// Update existing record
var oldName = existingLocal.InstitutionName;
existingLocal.InstitutionName = serverInstitution.InstitutionName;
existingLocal.TerritoryId = serverInstitution.TerritoryId;
existingLocal.IsActive = serverInstitution.IsActive;
existingLocal.LastSyncedAt = DateTime.UtcNow;
existingLocal.NeedsSync = false;
var result = await _institutionRepository.UpdateAsync(existingLocal);
if (result > 0)
{
updatedCount++;
_logger.LogInformation($"✅ Updated: {oldName} -> {existingLocal.InstitutionName}");
}
else
{
_logger.LogWarning($"❌ Failed to update: {existingLocal.InstitutionName}");
errorCount++;
}
}
}
catch (Exception itemEx)
{
_logger.LogError(itemEx, $"Error processing institution {serverInstitution.InstitutionId}: {serverInstitution.InstitutionName}");
errorCount++;
}
}
_logger.LogInformation($"=== SYNC SUMMARY ===");
_logger.LogInformation($"New: {newCount}, Updated: {updatedCount}, Errors: {errorCount}");
// Final verification - count records in database
var finalCount = (await _institutionRepository.GetAllAsync()).Count;
_logger.LogInformation($"Total records in database after sync: {finalCount}");
// Refresh the UI data
await LoadDataFromDatabaseAsync();
StatusMessage = $"Processed: {newCount} new, {updatedCount} updated, {errorCount} errors. Total DB records: {finalCount}";
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing server data");
throw;
}
}
private async Task InitializeDataAsync()
{
try
{
IsLoading = true;
await _databaseService.InitializeAsync();
await _institutionRepository.GetActiveAsync();
await LoadDataFromDatabaseAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error initializing data");
}
finally
{
IsLoading = false;
}
}
private async Task LoadDataFromDatabaseAsync()
{
try
{
// Load institutions from database
var institutions = await _institutionRepository.GetActiveAsync();
var users = await _doctorRepository.GetActiveAsync();
// Update UI on main thread
MainThread.BeginInvokeOnMainThread(() =>
{
// Clear existing collections
Institutions.Clear();
AllDoctors.Clear();
// Add all users to AllUsers collection
foreach (var user in users)
{
// Set institution name for display
var institution = institutions.FirstOrDefault(i => i.InstitutionId == user.InstitutionId);
if (institution != null)
{
user.InstitutionName = institution.InstitutionName;
}
AllDoctors.Add(user);
}
// Group users by institution and add to Institutions collection
foreach (var institution in institutions)
{
var institutionUsers = users.Where(u => u.InstitutionId == institution.InstitutionId).ToList();
institution.Doctors.Clear();
foreach (var user in institutionUsers)
{
institution.Doctors.Add(user);
}
Institutions.Add(institution);
}
_logger.LogInformation($"Loaded {institutions.Count} institutions and {users.Count} users from database");
});
}
catch (Exception ex)
{
_logger.LogError(ex, "Error loading data from database");
throw;
}
}
// Updated RefreshDataAsync with sync functionality
[RelayCommand]
private async Task RefreshDataAsync()
{
if (IsSyncing) return; // Prevent multiple sync operations
try
{
IsSyncing = true;
StatusMessage = "Syncing with server...";
// First try to sync with server
var syncSuccess = await _syncService.SyncPullCoveragePlanTodayAsync();
if (syncSuccess)
{
StatusMessage = "Sync successful! Loading updated data...";
_logger.LogInformation("Data sync completed successfully");
}
else
{
StatusMessage = "Sync failed, loading local data...";
_logger.LogWarning("Data sync failed, falling back to local data");
}
// Load updated local data
await LoadDataFromDatabaseAsync();
StatusMessage = syncSuccess ? "Data refreshed successfully!" : "Loaded local data";
// Clear status message after delay
await Task.Delay(3000);
StatusMessage = string.Empty;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error refreshing data");
StatusMessage = $"Error: {ex.Message}";
// Clear error message after delay
await Task.Delay(5000);
StatusMessage = string.Empty;
}
finally
{
IsSyncing = false;
}
}
[RelayCommand]
private void ToggleLeftPanel()
{
IsLeftPanelVisible = !IsLeftPanelVisible;
}
[RelayCommand]
private void PreviousMonth()
{
CurrentMonthStart = CurrentMonthStart.AddMonths(-1);
}
[RelayCommand]
private void NextMonth()
{
CurrentMonthStart = CurrentMonthStart.AddMonths(1);
}
}
}