Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 177 additions & 0 deletions src/portal/CloudHealthOffice.Portal/Pages/AddEditUserDialog.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
@using CloudHealthOffice.Portal.Services
@inject HttpClient Http
@inject IConfiguration Configuration

<MudDialog>
<TitleContent>
<MudText Typo="Typo.h6">
<MudIcon Icon="@(IsEdit ? Icons.Material.Filled.Edit : Icons.Material.Filled.PersonAdd)" Class="mr-2" />
@(IsEdit ? "Edit User" : "Add User")
</MudText>
</TitleContent>
<DialogContent>
<MudTextField @bind-Value="_email" Label="Email" Required="true" RequiredError="Email is required"
InputType="InputType.Email" Disabled="@IsEdit" Class="mb-3" />
<MudTextField @bind-Value="_displayName" Label="Display Name" Required="true"
RequiredError="Display name is required" Class="mb-3" />
<MudStack Row="true" Spacing="2" Class="mb-3">
<MudTextField @bind-Value="_firstName" Label="First Name" />
<MudTextField @bind-Value="_lastName" Label="Last Name" />
</MudStack>
<MudSelect T="string" Label="Department" @bind-Value="_department" Class="mb-3">
<MudSelectItem Value="@("Claims")">Claims</MudSelectItem>
<MudSelectItem Value="@("Utilization Management")">Utilization Management</MudSelectItem>
<MudSelectItem Value="@("Member Services")">Member Services</MudSelectItem>
<MudSelectItem Value="@("Enrollment")">Enrollment</MudSelectItem>
<MudSelectItem Value="@("Provider Relations")">Provider Relations</MudSelectItem>
<MudSelectItem Value="@("Finance")">Finance</MudSelectItem>
<MudSelectItem Value="@("Compliance")">Compliance</MudSelectItem>
<MudSelectItem Value="@("Administration")">Administration</MudSelectItem>
</MudSelect>
<MudSelect T="string" Label="Roles" MultiSelection="true" @bind-SelectedValues="_selectedRoles" Class="mb-3">
@foreach (var role in AvailableRoles)
{
<MudSelectItem T="string" Value="@role">@FormatRoleName(role)</MudSelectItem>
}
</MudSelect>

@if (!string.IsNullOrEmpty(_errorMessage))
{
<MudAlert Severity="Severity.Error" Class="mt-2">@_errorMessage</MudAlert>
}
</DialogContent>
<DialogActions>
<MudButton OnClick="Cancel">Cancel</MudButton>
<MudButton Color="Color.Primary" Variant="Variant.Filled" OnClick="Submit" Disabled="_saving">
@if (_saving) { <MudProgressCircular Size="Size.Small" Indeterminate="true" Class="mr-2" /> }
@(IsEdit ? "Save Changes" : "Add User")
</MudButton>
</DialogActions>
</MudDialog>

@code {
[CascadingParameter] MudDialogInstance MudDialog { get; set; } = null!;
[Parameter] public string? TenantId { get; set; }
[Parameter] public UserManagement.UserListItem? ExistingUser { get; set; }
[Parameter] public List<string> AvailableRoles { get; set; } = new();

private string _email = string.Empty;
private string _displayName = string.Empty;
private string _firstName = string.Empty;
private string _lastName = string.Empty;
private string _department = string.Empty;
private IEnumerable<string> _selectedRoles = new List<string>();
private string? _errorMessage;
private bool _saving;

private bool IsEdit => ExistingUser != null;

protected override void OnInitialized()
{
if (ExistingUser != null)
{
_email = ExistingUser.Email;
_displayName = ExistingUser.DisplayName;
_firstName = ExistingUser.FirstName;
_lastName = ExistingUser.LastName;
_department = ExistingUser.Department;
Comment on lines +71 to +77
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In edit mode, only Email/DisplayName/Department/Roles are copied from ExistingUser; first/last name fields remain empty. Since the update request later includes first/last name, this can unintentionally clear those values. Consider loading first/last name for edits (e.g., include them in the list item or fetch the user by ID).

Copilot uses AI. Check for mistakes.
_selectedRoles = ExistingUser.Roles;
}
}

private async Task Submit()
{
_errorMessage = null;

if (string.IsNullOrWhiteSpace(_email) || string.IsNullOrWhiteSpace(_displayName))
{
_errorMessage = "Email and Display Name are required.";
return;
}

if (!_selectedRoles.Any())
{
_errorMessage = "At least one role must be selected.";
return;
}

if (string.IsNullOrEmpty(TenantId))
{
_errorMessage = "Tenant context is not available. Please refresh and try again.";
return;
}

_saving = true;

try
{
var baseUrl = Configuration["Services:TenantService"] ?? "http://tenant-service.cho-svcs/api";

if (IsEdit)
{
var response = await Http.PutAsJsonAsync(
$"{baseUrl}/v1/tenants/{TenantId}/users/{ExistingUser!.Id}",
Comment on lines +112 to +113
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TenantId is nullable but is interpolated into the tenant-service URL. Add a submit-time guard that blocks saving and shows a clear error when TenantId is null/empty to avoid generating invalid endpoints.

Copilot uses AI. Check for mistakes.
new
{
DisplayName = _displayName,
FirstName = _firstName,
LastName = _lastName,
Roles = _selectedRoles.ToList(),
Department = _department
Comment on lines +116 to +120
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PUT payload always includes FirstName/LastName, even when the edit form didn’t populate them (and the user didn’t change them). This risks wiping existing values to empty strings; prefer sending null for unchanged fields or only including properties the user actually edited.

Copilot uses AI. Check for mistakes.
});

if (!response.IsSuccessStatusCode)
{
_errorMessage = $"Failed to update user: {response.ReasonPhrase}";
return;
}
}
else
{
var response = await Http.PostAsJsonAsync(
$"{baseUrl}/v1/tenants/{TenantId}/users",
new
{
Email = _email,
DisplayName = _displayName,
FirstName = _firstName,
LastName = _lastName,
Roles = _selectedRoles.ToList(),
Department = _department
});

if (!response.IsSuccessStatusCode)
{
var body = await response.Content.ReadAsStringAsync();
_errorMessage = $"Failed to add user: {body}";
return;
}
}

MudDialog.Close(DialogResult.Ok(true));
}
catch (Exception ex)
{
_errorMessage = $"Error: {ex.Message}";
}
finally
{
_saving = false;
}
}

private void Cancel() => MudDialog.Cancel();

private string FormatRoleName(string role) => role switch
{
"ClaimsExaminer" => "Claims Examiner",
"ClaimsSupervisor" => "Claims Supervisor",
"MemberServices" => "Member Services",
"EnrollmentSpecialist" => "Enrollment Specialist",
"UMCoordinator" => "UM Coordinator",
"ProviderRelations" => "Provider Relations",
"ComplianceOfficer" => "Compliance Officer",
"TenantAdmin" => "Tenant Admin",
_ => role
};
}
2 changes: 2 additions & 0 deletions src/portal/CloudHealthOffice.Portal/Pages/Appeals.razor
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

<PageTitle>Appeals - Cloud Health Office</PageTitle>

<PermissionGate Permission="appeals:read" RoleName="UMCoordinator">
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="mt-4">
<MudText Typo="Typo.h3" GutterBottom="true">
<MudIcon Icon="@Icons.Material.Filled.Balance" Class="mr-2" />
Expand Down Expand Up @@ -274,6 +275,7 @@
</MudAlert>
}
</MudContainer>
</PermissionGate>

@code {
private bool _loadingSummary = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

<PageTitle>Authorizations - Cloud Health Office</PageTitle>

<PermissionGate Permission="authorizations:read" RoleName="UMCoordinator">
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="mt-4">
<MudText Typo="Typo.h3" GutterBottom="true">
<MudIcon Icon="@Icons.Material.Filled.FactCheck" Class="mr-2" />
Expand Down Expand Up @@ -214,6 +215,7 @@
</MudPaper>
}
</MudContainer>
</PermissionGate>

@code {
private List<AuthorizationSummary> _allAuthorizations = new();
Expand Down
2 changes: 2 additions & 0 deletions src/portal/CloudHealthOffice.Portal/Pages/BenefitPlans.razor
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

<PageTitle>Benefit Plans - Cloud Health Office</PageTitle>

<PermissionGate>
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="mt-4">
<MudText Typo="Typo.h3" GutterBottom="true">
<MudIcon Icon="@Icons.Material.Filled.HealthAndSafety" Class="mr-2" />
Expand Down Expand Up @@ -172,6 +173,7 @@
</LoadingContent>
</MudTable>
</MudContainer>
</PermissionGate>

@code {
private List<BenefitPlanListItem> _allPlans = new();
Expand Down
2 changes: 2 additions & 0 deletions src/portal/CloudHealthOffice.Portal/Pages/Claims.razor
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

<PageTitle>Claims - Cloud Health Office</PageTitle>

<PermissionGate Permission="claims:read,claims:work" RoleName="ClaimsExaminer">
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="mt-4">
<MudText Typo="Typo.h3" GutterBottom="true">
<MudIcon Icon="@Icons.Material.Filled.Receipt" Class="mr-2" />
Expand Down Expand Up @@ -360,6 +361,7 @@
</MudAlert>
}
</MudContainer>
</PermissionGate>

@code {
private ClaimSearchRequest _searchRequest = new();
Expand Down
24 changes: 19 additions & 5 deletions src/portal/CloudHealthOffice.Portal/Pages/Dashboard.razor
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,31 @@
@inject IClaimsService ClaimsService
@inject CloudHealthOffice.Portal.Services.IAuthorizationService AuthorizationService
@inject ITenantContextService TenantContextService
@inject IUserContextService UserContextService

<PageTitle>Dashboard - Cloud Health Office</PageTitle>

<PermissionGate>
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="mt-4">

@* ── Top Bar: Welcome + Tenant ── *@
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center" Class="mb-4">
<MudStack Spacing="0">
<MudText Typo="Typo.h4">
Welcome back@(tenantContext != null ? $", {tenantContext.TenantName}" : "")
</MudText>
<MudText Typo="Typo.body2" Color="Color.Secondary">
@DateTime.Now.ToString("dddd, MMMM d, yyyy")
Welcome back@(_userContext != null ? $", {_userContext.DisplayName}" : tenantContext != null ? $", {tenantContext.TenantName}" : "")
</MudText>
@if (_userContext != null)
{
<MudText Typo="Typo.body2" Color="Color.Secondary">
@_userContext.PrimaryRoleDisplayName — @DateTime.Now.ToString("dddd, MMMM d, yyyy")
</MudText>
}
else
{
<MudText Typo="Typo.body2" Color="Color.Secondary">
@DateTime.Now.ToString("dddd, MMMM d, yyyy")
</MudText>
}
</MudStack>
@if (tenantContext != null)
{
Expand Down Expand Up @@ -367,12 +378,14 @@
</MudCard>

</MudContainer>
</PermissionGate>

@code {
private DashboardMetrics? metrics;
private List<ClaimSummary>? recentClaims;
private List<AuthorizationSummary>? recentAuthorizations;
private TenantContext? tenantContext;
private UserContext? _userContext;
private OperationalAlerts? _alerts;
private EdiVolumeSummary? _ediVolume;
private bool _loading = true;
Expand All @@ -398,10 +411,11 @@
try
{
tenantContext = await TenantContextService.GetCurrentTenantContextAsync();
_userContext = await UserContextService.GetCurrentUserAsync();
}
catch (Exception ex)
{
Logger.LogWarning(ex, "Failed to load tenant context");
Logger.LogWarning(ex, "Failed to load tenant/user context");
}

try
Expand Down
2 changes: 2 additions & 0 deletions src/portal/CloudHealthOffice.Portal/Pages/Eligibility.razor
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

<PageTitle>Eligibility Verification - Cloud Health Office</PageTitle>

<PermissionGate Permission="eligibility:check" RoleName="MemberServices">
<MudContainer MaxWidth="MaxWidth.Large" Class="mt-4">
<MudText Typo="Typo.h3" GutterBottom="true">
<MudIcon Icon="@Icons.Material.Filled.VerifiedUser" Class="mr-2" />
Expand Down Expand Up @@ -216,6 +217,7 @@
</MudItem>
</MudGrid>
</MudContainer>
</PermissionGate>

@code {
private MudForm? _form;
Expand Down Expand Up @@ -243,7 +245,7 @@
serviceType = _request.ServiceType
});
}
catch (Exception ex)

Check warning on line 248 in src/portal/CloudHealthOffice.Portal/Pages/Eligibility.razor

View workflow job for this annotation

GitHub Actions / .NET Unit Tests

The variable 'ex' is declared but never used
{
// Show error snackbar
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

<PageTitle>Enrollment Operations - Cloud Health Office</PageTitle>

<PermissionGate Permission="enrollment:read" RoleName="EnrollmentSpecialist">
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="mt-4">
<MudText Typo="Typo.h3" GutterBottom="true">
<MudIcon Icon="@Icons.Material.Filled.UploadFile" Class="mr-2" />
Expand Down Expand Up @@ -214,6 +215,7 @@
50% { opacity: 0.5; }
}
</style>
</PermissionGate>

@code {
private bool _loading = true;
Expand Down
2 changes: 2 additions & 0 deletions src/portal/CloudHealthOffice.Portal/Pages/Members.razor
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

<PageTitle>Members - Cloud Health Office</PageTitle>

<PermissionGate Permission="members:read" RoleName="MemberServices">
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="mt-4">
<MudText Typo="Typo.h3" GutterBottom="true">
<MudIcon Icon="@Icons.Material.Filled.People" Class="mr-2" />
Expand Down Expand Up @@ -143,6 +144,7 @@
</MudPaper>
}
</MudContainer>
</PermissionGate>

@code {
private string _memberIdSearch = string.Empty;
Expand Down
2 changes: 2 additions & 0 deletions src/portal/CloudHealthOffice.Portal/Pages/PaymentRuns.razor
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

<PageTitle>Payment Runs — Cloud Health Office</PageTitle>

<PermissionGate Permission="payments:read" RoleName="Finance">
<MudText Typo="Typo.h4" Class="mb-1" Style="color: #00ffff; font-weight: 700; letter-spacing: -0.5px;">
<MudIcon Icon="@Icons.Material.Filled.Payments" Class="mr-2" Style="vertical-align: middle;" />
Payment Runs
Expand Down Expand Up @@ -206,6 +207,7 @@
50% { opacity: 0.5; }
}
</style>
</PermissionGate>

@code {
private List<PaymentRunSummary> _runs = new();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

<PageTitle>Premium Billing — Cloud Health Office</PageTitle>

<PermissionGate Permission="billing:read" RoleName="Finance">
<MudText Typo="Typo.h4" Class="mb-1" Style="color: #00ffff; font-weight: 700; letter-spacing: -0.5px;">
<MudIcon Icon="@Icons.Material.Filled.RequestQuote" Class="mr-2" Style="vertical-align: middle;" />
Premium Billing
Expand Down Expand Up @@ -346,6 +347,7 @@
</MudTabPanel>

</MudTabs>
</PermissionGate>

@code {
private int _activeTab = 0;
Expand Down
Loading
Loading