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
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@

namespace Bezalu.ProjectReporting.API.Functions;

public class ProjectCompletionReportFunction(
ILogger<ProjectCompletionReportFunction> logger,
public class ReportFunction(
ILogger<ReportFunction> logger,
IProjectReportingService reportingService)
{
[Function("GenerateProjectCompletionReport")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public async Task<List<ProjectListItem>> GetActiveProjectsAsync(CancellationToke

// Query ConnectWise for projects with "Active" status
Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

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

The comment on line 24 states "Query ConnectWise for projects with 'Active' status" but the code now queries for 'In Progress' status. The comment should be updated to reflect the actual query being performed.

Suggested change
// Query ConnectWise for projects with "Active" status
// Query ConnectWise for projects with status name containing "In Progress"

Copilot uses AI. Check for mistakes.
var projects = await connectWiseClient.GetListAsync<CWProject>(
"project/projects?conditions=status/name='Active'&orderBy=name",
"project/projects?conditions=status/name contains 'In Progress'&orderBy=name",
Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

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

The query filter syntax may be incorrect. In ConnectWise API conditions, the 'contains' operator is typically used with the 'like' keyword or as 'status/name like "%In Progress%"'. The current syntax 'status/name contains 'In Progress'' might not work as intended. Please verify this syntax against the ConnectWise API documentation, or consider using the equality operator with the exact status name instead.

Suggested change
"project/projects?conditions=status/name contains 'In Progress'&orderBy=name",
"project/projects?conditions=status/name=\"In Progress\"&orderBy=name",

Copilot uses AI. Check for mistakes.
cancellationToken) ?? new List<CWProject>();

return projects
Expand Down
68 changes: 56 additions & 12 deletions Bezalu.ProjectReporting.Web/Pages/Home.razor
Original file line number Diff line number Diff line change
@@ -1,18 +1,33 @@
@page "/"
@using System.Net
@inject HttpClient Http
@inject NavigationManager Nav
@inject IJSRuntime JS
@using Markdig

<PageTitle>Project Reporting</PageTitle>

@if (ShowLoginPrompt)
{
<FluentCard Style="padding:0.5rem 1rem; margin-bottom:1rem;">
<FluentStack Orientation="Orientation.Horizontal" Gap="10" VerticalAlignment="VerticalAlignment.Center">
<FluentIcon Value="@(new Icons.Regular.Size20.LockClosed())" />
<FluentStack>
<strong>@LoginPromptText</strong>
<span>Sign in again to continue.</span>
</FluentStack>
<FluentButton Appearance="Appearance.Accent" OnClick="TriggerLogin">Sign in</FluentButton>
</FluentStack>
</FluentCard>
}

@if (Report is null)
{
<FluentCard Style="padding:1rem; margin-bottom:1rem;">
<h2>Select a Project</h2>
@if (IsLoadingProjects)
{
<FluentProgressRing/>
<FluentProgressRing />
<p>Loading projects...</p>
}
else if (!string.IsNullOrEmpty(ErrorMessage))
Expand All @@ -31,8 +46,8 @@
<PropertyColumn Property="@(p => p.Manager)" Title="Manager" />
<PropertyColumn Property="@(p => p.Company)" Title="Company" />
<TemplateColumn Title="Actions">
<FluentButton Appearance="Appearance.Accent"
OnClick="@(() => GenerateReport(context.ProjectId))"
<FluentButton Appearance="Appearance.Accent"
OnClick="@(() => GenerateReport(context.ProjectId))"
Disabled="IsGeneratingReport">
@(IsGeneratingReport && GeneratingProjectId == context.ProjectId ? "Generating..." : "Generate Report")
</FluentButton>
Expand Down Expand Up @@ -82,10 +97,10 @@ else
else
{
<FluentDataGrid Items="@Report.Phases.AsQueryable()" GenerateFooter="false">
<PropertyColumn Property="@(p => p.PhaseName)" Title="Name"/>
<PropertyColumn Property="@(p => p.Status)" Title="Status"/>
<PropertyColumn Property="@(p => p.EstimatedHours)" Title="Est Hrs"/>
<PropertyColumn Property="@(p => p.ActualHours)" Title="Actual Hrs"/>
<PropertyColumn Property="@(p => p.PhaseName)" Title="Name" />
<PropertyColumn Property="@(p => p.Status)" Title="Status" />
<PropertyColumn Property="@(p => p.EstimatedHours)" Title="Est Hrs" />
<PropertyColumn Property="@(p => p.ActualHours)" Title="Actual Hrs" />
</FluentDataGrid>
}
</FluentTab>
Expand All @@ -97,11 +112,11 @@ else
else
{
<FluentDataGrid Items="@Report.Tickets.AsQueryable()" GenerateFooter="false">
<PropertyColumn Property="@(t => t.TicketNumber)" Title="Number"/>
<PropertyColumn Property="@(t => t.Summary)" Title="Summary"/>
<PropertyColumn Property="@(t => t.Status)" Title="Status"/>
<PropertyColumn Property="@(t => t.EstimatedHours)" Title="Est Hrs"/>
<PropertyColumn Property="@(t => t.ActualHours)" Title="Actual Hrs"/>
<PropertyColumn Property="@(t => t.TicketNumber)" Title="Number" />
<PropertyColumn Property="@(t => t.Summary)" Title="Summary" />
<PropertyColumn Property="@(t => t.Status)" Title="Status" />
<PropertyColumn Property="@(t => t.EstimatedHours)" Title="Est Hrs" />
<PropertyColumn Property="@(t => t.ActualHours)" Title="Actual Hrs" />
</FluentDataGrid>
}
</FluentTab>
Expand All @@ -116,6 +131,8 @@ else
bool IsGeneratingReport;
int GeneratingProjectId;
bool IsPdfLoading;
bool ShowLoginPrompt;
string LoginPromptText = "Please sign in to continue.";
string? ErrorMessage;
string AiSummaryHtml => Report?.AiGeneratedSummary is null ? string.Empty : Markdown.ToHtml(Report.AiGeneratedSummary);

Expand All @@ -131,6 +148,11 @@ else
try
{
var response = await Http.GetAsync("api/projects");
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
HandleUnauthorized("Please sign in to load projects.");
return;
}
Comment on lines +151 to +155
Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

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

When handling unauthorized responses in LoadProjects, the loading state 'IsLoadingProjects' is not reset before returning. This will leave the UI in a loading state indefinitely. The 'finally' block won't execute because of the early return. Consider setting 'IsLoadingProjects = false' before the return statement, or restructure the code to ensure the finally block always executes.

Copilot uses AI. Check for mistakes.
if (!response.IsSuccessStatusCode)
{
ErrorMessage = $"Failed to load projects: {response.StatusCode}";
Expand Down Expand Up @@ -158,6 +180,11 @@ else
{
var req = new ProjectCompletionReportRequest { ProjectId = projectId };
var httpResp = await Http.PostAsJsonAsync("api/reports/project-completion", req);
if (httpResp.StatusCode == HttpStatusCode.Unauthorized)
{
HandleUnauthorized("Please sign in to generate a report.");
return;
}
Comment on lines +183 to +187
Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

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

When handling unauthorized responses in GenerateReport, the loading state 'IsGeneratingReport' is not reset before returning. This will leave the UI in a loading state indefinitely. The 'finally' block won't execute because of the early return. Consider setting 'IsGeneratingReport = false' before the return statement, or restructure the code to ensure the finally block always executes.

Copilot uses AI. Check for mistakes.
if (!httpResp.IsSuccessStatusCode)
{
ErrorMessage = $"Failed to generate report: {httpResp.StatusCode}";
Expand Down Expand Up @@ -191,6 +218,11 @@ else
try
{
var httpResp = await Http.PostAsJsonAsync("api/reports/project-completion/pdf", Report);
if (httpResp.StatusCode == HttpStatusCode.Unauthorized)
{
HandleUnauthorized("Please sign in to download the PDF.");
return;
}
Comment on lines +221 to +225
Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

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

When handling unauthorized responses in DownloadPdf, the loading state 'IsPdfLoading' is not reset before returning. This will leave the UI in a loading state indefinitely. The 'finally' block won't execute because of the early return. Consider setting 'IsPdfLoading = false' before the return statement, or restructure the code to ensure the finally block always executes.

Copilot uses AI. Check for mistakes.
if (!httpResp.IsSuccessStatusCode)
{
ErrorMessage = $"Failed to generate PDF: {httpResp.StatusCode}";
Expand All @@ -210,4 +242,16 @@ else
IsPdfLoading = false;
}
}

void HandleUnauthorized(string message)
{
LoginPromptText = message;
ShowLoginPrompt = true;
ErrorMessage = null;
}

void TriggerLogin()
{
Nav.NavigateTo("/login?post_login_redirect_uri=.referrer", forceLoad: true);
Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

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

The query parameter 'post_login_redirect_uri=.referrer' appears to use '.referrer' as a literal value, but this may not be recognized by Azure Static Web Apps authentication. Typically, you would use '.referrer' as a special placeholder that Azure Static Web Apps replaces with the referring URL, but this should be verified. If '.referrer' is not a recognized placeholder, consider using the actual current page URL or removing the query parameter to rely on the default redirect behavior defined in staticwebapp.config.json.

Suggested change
Nav.NavigateTo("/login?post_login_redirect_uri=.referrer", forceLoad: true);
var currentUri = new Uri(Nav.Uri);
var redirectPath = currentUri.PathAndQuery + currentUri.Fragment;
var encodedRedirect = Uri.EscapeDataString(redirectPath);
Nav.NavigateTo($"/login?post_login_redirect_uri={encodedRedirect}", forceLoad: true);

Copilot uses AI. Check for mistakes.
}
}
8 changes: 4 additions & 4 deletions Bezalu.ProjectReporting.Web/staticwebapp.config.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"routes": [
{
"route": "/api/*",
"allowedRoles": ["authenticated"]
},
{
"route": "/login",
"rewrite": "/.auth/login/aad"
},
{
"route": "/*",
"allowedRoles": ["authenticated"]
}
],
"navigationFallback": {
Expand Down
Loading