@page "/projects" @page "/projects/all" @using Microsoft.AspNetCore.Authorization @using Microsoft.AspNetCore.Components.Authorization @rendermode @(new InteractiveServerRenderMode(prerender: false)) @attribute [Microsoft.AspNetCore.Authorization.Authorize] @inject ApiClientService ApiClient @inject NavigationManager Navigation @inject IJSRuntime JS @inject AuthenticationStateProvider AuthStateProvider @inject MenuAuthorizationService MenuAuthorization Projects

Projects

@(isAdmin ? "Manage all projects." : "Your assigned projects.")

@if (canCreateProject) { }
@if (isLoading) { } else {
@if (PagedProjects.Any()) { @foreach (var entry in PagedProjects.Select((project, index) => (project, index))) { var project = entry.project; } } else { }
No. Name Start Date End Date Created Actions
@((currentPage - 1) * pageSize + entry.index + 1) @if (!string.IsNullOrEmpty(project.Description)) {

@project.Description

}
@DisplayFormats.Date(project.StartDate) @DisplayFormats.Date(project.EndDate) @DisplayFormats.Date(project.CreatedAt)
@if (canEditProject || canDeleteProject) { @if (canEditProject) { } @if (canDeleteProject) { } }
}
@* ===== Create / Edit Modal ===== *@ @if (showModal) {

@(isEditing ? "Edit Project" : "New Project")

@if (isNameError) { }
@if (isNameError) {

@nameErrorMessage

}
@if (isDateError) { }
@if (isDateError) { }
@if (isDateError) {

@dateErrorMessage

} @if (!string.IsNullOrEmpty(errorMessage)) {

@errorMessage

}
} @* ===== Assign Members Modal ===== *@ @if (showAssignModal) {

Assign Members

Project: @assigningProjectName

Select users to assign to this project. They will only see this project's tasks when they log in.

@if (isLoadingUsers) {
} else {
@foreach (var user in allUsers) { var isChecked = selectedUserIds.Contains(user.Id); }

@selectedUserIds.Count user(s) selected

} @if (!string.IsNullOrEmpty(assignError)) {

@assignError

} @if (!string.IsNullOrEmpty(assignSuccess)) {

@assignSuccess

}
} @* ===== Confirm Dialog ===== *@ @code { private bool isLoading = true; private bool isAdmin = false; private bool canCreateProject = false; private bool canEditProject = false; private bool canDeleteProject = false; private bool canAssignProject = false; private long currentUserId = 0; private bool showModal = false; private bool isEditing = false; private bool isSaving = false; private string? errorMessage; // Confirm dialog state private bool showConfirmDialog = false; private bool showAssignConfirmDialog = false; private string? successMessage; private string confirmTitle = ""; private string confirmMessage = ""; private string confirmButtonText = ""; private string confirmIcon = ""; private string confirmIconBg = ""; private string confirmIconText = ""; private string confirmButtonClass = ""; private Func? confirmAction; private List projects = new(); // Assignment modal state private bool showAssignModal = false; private long assigningProjectId = 0; private string assigningProjectName = ""; private List allUsers = new(); private HashSet selectedUserIds = new(); private bool isLoadingUsers = false; private bool isSavingAssign = false; private string? assignError; private string? assignSuccess; // Search state private string searchInput = ""; // Pagination state private int currentPage = 1; private int pageSize = 10; private int totalCount = 0; private int totalPages = 0; private int TotalPages => totalPages; private int TotalCount => totalCount; private IEnumerable PagedProjects => projects; // Form fields private long editingId; private string formName = ""; private string? formDescription; private DateTime formStartDate = DateTime.Today; private DateTime formEndDate = DateTime.Today.AddMonths(1); private int? formBudgetedHours; // Validation states private bool isNameError; private string nameErrorMessage = ""; private bool isDateError; private string dateErrorMessage = ""; protected override async Task OnInitializedAsync() { var authState = await AuthStateProvider.GetAuthenticationStateAsync(); var user = authState.User; isAdmin = user.IsInRole("Admin"); var idStr = user.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value; if (idStr != null) long.TryParse(idStr, out currentUserId); await LoadProjectAccessAsync(user); await LoadProjects(); } protected override async Task OnAfterRenderAsync(bool firstRender) { await JS.InvokeVoidAsync("initIcons"); } private async Task LoadProjects() { isLoading = true; var client = ApiClient.CreateClient(); try { var url = BuildProjectsUrl(); var result = await client.GetFromJsonAsync>(url, Serialization.CaseInsensitive); projects = result?.Items ?? new(); totalCount = result?.TotalCount ?? 0; totalPages = result?.TotalPages ?? 0; currentPage = result?.Page ?? currentPage; pageSize = result?.PageSize ?? pageSize; } catch (Exception ex) { Console.WriteLine($"Load projects error: {ex.Message}"); } finally { isLoading = false; } } private string BuildProjectsUrl() { var query = new List { $"page={currentPage}", $"limit={pageSize}" }; if (!string.IsNullOrWhiteSpace(searchInput)) { query.Add($"search={Uri.EscapeDataString(searchInput.Trim())}"); } return $"Project?{string.Join("&", query)}"; } private void NavigateToKanban(long projectId) { Navigation.NavigateTo($"/projects/{projectId}/tasks"); } private async Task HandlePageChanged(int page) { currentPage = page; await LoadProjects(); } private async Task HandlePageSizeChanged(int size) { pageSize = size; currentPage = 1; await LoadProjects(); } private async Task ApplySearch() { currentPage = 1; await LoadProjects(); } private async Task ResetSearch() { searchInput = ""; currentPage = 1; await LoadProjects(); } private async Task LoadProjectAccessAsync(System.Security.Claims.ClaimsPrincipal user) { canCreateProject = isAdmin || await MenuAuthorization.HasAccessCodeAsync(user, "Projects_Create"); canEditProject = isAdmin || await MenuAuthorization.HasAccessCodeAsync(user, "Projects_Update"); canDeleteProject = isAdmin || await MenuAuthorization.HasAccessCodeAsync(user, "Projects_Delete"); canAssignProject = isAdmin || await MenuAuthorization.HasAccessCodeAsync(user, "Projects_Update"); } private void ClearValidation() { isNameError = false; nameErrorMessage = ""; isDateError = false; dateErrorMessage = ""; errorMessage = null; } private void OpenCreateModal() { isEditing = false; editingId = 0; formName = ""; formDescription = null; formStartDate = DateTime.Today; formEndDate = DateTime.Today.AddMonths(1); formBudgetedHours = null; ClearValidation(); showModal = true; } private void OpenEditModal(ProjectDto project) { isEditing = true; editingId = project.Id; formName = project.Name; formDescription = project.Description; formStartDate = project.StartDate; formEndDate = project.EndDate; formBudgetedHours = project.BudgetedHours; ClearValidation(); showModal = true; } private void CloseModal() { showModal = false; ClearValidation(); } // ─── Assignment Modal ───────────────────────────────────────────────────── private async Task OpenAssignModal(ProjectDto project) { assigningProjectId = project.Id; assigningProjectName = project.Name; assignError = null; assignSuccess = null; isLoadingUsers = true; showAssignModal = true; StateHasChanged(); var client = ApiClient.CreateClient(); try { allUsers = await client.GetFromJsonAsync>("User", Serialization.CaseInsensitive) ?? new(); var membersResult = await client.GetFromJsonAsync>>($"Project/{project.Id}/members", Serialization.CaseInsensitive); selectedUserIds = new HashSet(membersResult?.Value?.Select(u => u.Id) ?? Enumerable.Empty()); } catch (Exception ex) { Console.WriteLine($"Load assign modal error: {ex.Message}"); } finally { isLoadingUsers = false; StateHasChanged(); } } private void CloseAssignModal() { showAssignModal = false; assignError = null; assignSuccess = null; } private void ToggleUserSelection(long userId, bool isChecked) { if (isChecked) selectedUserIds.Add(userId); else selectedUserIds.Remove(userId); } private void RequestAssignConfirmation() { showAssignConfirmDialog = true; } private void CloseAssignConfirmDialog() { showAssignConfirmDialog = false; } private async Task ConfirmSaveAssignment() { showAssignConfirmDialog = false; await SaveAssignment(); } private async Task SaveAssignment() { isSavingAssign = true; assignError = null; assignSuccess = null; var client = ApiClient.CreateClient(); try { var dto = new AssignMembersDto { UserIds = selectedUserIds.ToList() }; var response = await client.PostAsJsonAsync($"Project/{assigningProjectId}/members", dto); if (response.IsSuccessStatusCode) { assignSuccess = "Members assigned successfully!"; successMessage = "Assignment saved successfully!"; } else { assignError = "Failed to save assignment. Please try again."; } } catch (Exception ex) { assignError = ex.Message; } finally { isSavingAssign = false; } } private void RequestSaveConfirmation() { ClearValidation(); bool hasError = false; if (string.IsNullOrWhiteSpace(formName)) { isNameError = true; nameErrorMessage = "Project name is required."; hasError = true; } if (formEndDate < formStartDate) { isDateError = true; dateErrorMessage = "End date cannot be before start date."; hasError = true; } if (hasError) return; if (isEditing) { confirmTitle = "Update Project?"; confirmMessage = "Are you sure you want to update this project?"; confirmButtonText = "Update"; confirmIcon = "pencil"; confirmIconBg = "bg-amber-50"; confirmIconText = "text-amber-500"; confirmButtonClass = "bg-violet-600 hover:bg-violet-700"; } else { confirmTitle = "Create Project?"; confirmMessage = "Are you sure you want to create this project?"; confirmButtonText = "Create"; confirmIcon = "plus-circle"; confirmIconBg = "bg-emerald-50"; confirmIconText = "text-emerald-500"; confirmButtonClass = "bg-violet-600 hover:bg-violet-700"; } confirmAction = SaveProject; showConfirmDialog = true; } private void RequestDeleteConfirmation(long id) { confirmTitle = "Delete Project?"; confirmMessage = "Are you sure you want to delete this project? This action cannot be undone."; confirmButtonText = "Delete"; confirmIcon = "trash-2"; confirmIconBg = "bg-rose-50"; confirmIconText = "text-rose-500"; confirmButtonClass = "bg-violet-600 hover:bg-violet-700"; confirmAction = () => DeleteProject(id); showConfirmDialog = true; } private async Task HandleConfirmAction() { showConfirmDialog = false; if (confirmAction != null) await confirmAction(); } private void CloseConfirmDialog() { showConfirmDialog = false; } private async Task SaveProject() { if (string.IsNullOrWhiteSpace(formName) || formEndDate < formStartDate) { return; } isSaving = true; errorMessage = null; var client = ApiClient.CreateClient(); try { if (isEditing) { var dto = new UpdateProjectDto { Name = formName, Description = formDescription, StartDate = formStartDate, EndDate = formEndDate, BudgetedHours = formBudgetedHours }; var response = await client.PutAsJsonAsync($"Project/{editingId}", dto); var res = await response.Content.ReadFromJsonAsync(Serialization.CaseInsensitive); if (res == null || !res.IsSuccess) { errorMessage = res?.ErrorMessage ?? ResultMessages.FailedToUpdateProject; return; } } else { var dto = new CreateProjectDto { Name = formName, Description = formDescription, StartDate = formStartDate, EndDate = formEndDate, CreatedById = currentUserId, BudgetedHours = formBudgetedHours }; var response = await client.PostAsJsonAsync("Project", dto); var res = await response.Content.ReadFromJsonAsync>(Serialization.CaseInsensitive); if (res == null || !res.IsSuccess) { errorMessage = res?.ErrorMessage ?? ResultMessages.FailedToCreateProject; return; } // After creating, reload await LoadProjects(); CloseModal(); successMessage = "Project created successfully!"; return; } CloseModal(); await LoadProjects(); successMessage = isEditing ? "Project updated successfully!" : "Project created successfully!"; } catch (Exception ex) { errorMessage = ex.Message; } finally { isSaving = false; } } private async Task DeleteProject(long id) { var client = ApiClient.CreateClient(); try { var response = await client.DeleteAsync($"Project/{id}"); var res = await response.Content.ReadFromJsonAsync(Serialization.CaseInsensitive); if (res == null || !res.IsSuccess) { await JS.InvokeVoidAsync("alert", res?.ErrorMessage ?? "Failed to delete project."); return; } await LoadProjects(); if (currentPage > TotalPages && TotalPages > 0) currentPage = TotalPages; successMessage = "Project deleted successfully!"; } catch (Exception ex) { Console.WriteLine($"Delete error: {ex.Message}"); } } }