513 lines
20 KiB
C#
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);
|
|
}
|
|
}
|
|
} |