@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 @AppLocalization.PageTitle("projects", "Projects")

@AppLocalization.PageTitle("projects", "Projects")

@(isAdmin ? AppLocalization.Text("project.manageAll", "Manage all projects.") : AppLocalization.Text("project.yourAssigned", "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 { }
@AppLocalization.Text("common.index", "No.") @AppLocalization.Text("field.name", "Name") @AppLocalization.Text("common.startDate", "Start Date") @AppLocalization.Text("common.endDate", "End Date") @AppLocalization.Text("field.created", "Created") @AppLocalization.Text("common.actions", "Actions")
@((currentPage - 1) * pageSize + entry.index + 1) @if (!string.IsNullOrEmpty(Content.GetProjectDescription(project))) {

@Content.GetProjectDescription(project)

}
@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 ? AppLocalization.Text("project.editProject", "Edit Project") : AppLocalization.Text("project.newProject", "New Project"))

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

@dateErrorMessage

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

@errorMessage

}
} @* ===== Confirm Dialog ===== *@ @code { private bool isLoading = true; private bool isAdmin = false; private bool canCreateProject = false; private bool canEditProject = false; private bool canDeleteProject = 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 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(); // 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? formNameMy; private string? formDescription; private string? formDescriptionMy; private bool burmeseGenerated; private bool englishGenerated; private bool burmeseNeedsReview; private bool englishNeedsReview; 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 = ""; private void MoveProjectNameToEnglish() { formName = formNameMy ?? string.Empty; } private void MoveProjectNameToBurmese() { formNameMy = formName; } private void MoveProjectDescriptionToEnglish() { formDescription = formDescriptionMy; } private void MoveProjectDescriptionToBurmese() { formDescriptionMy = formDescription; } private void MarkGenerated(bool english) { if (english) { englishGenerated = true; englishNeedsReview = true; } else { burmeseGenerated = true; burmeseNeedsReview = true; } } private Task GenerateProjectEnglishName(TranslationResult result) { formName = result.TranslatedText ?? string.Empty; MarkGenerated(true); return Task.CompletedTask; } private Task GenerateProjectBurmeseName(TranslationResult result) { formNameMy = result.TranslatedText; MarkGenerated(false); return Task.CompletedTask; } private Task GenerateProjectEnglishDescription(TranslationResult result) { formDescription = result.TranslatedText ?? string.Empty; MarkGenerated(true); return Task.CompletedTask; } private Task GenerateProjectBurmeseDescription(TranslationResult result) { formDescriptionMy = result.TranslatedText; MarkGenerated(false); return Task.CompletedTask; } 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"); } private void ClearValidation() { isNameError = false; nameErrorMessage = ""; isDateError = false; dateErrorMessage = ""; errorMessage = null; } private void OpenCreateModal() { isEditing = false; editingId = 0; formName = ""; formNameMy = null; formDescription = null; formDescriptionMy = null; burmeseGenerated = englishGenerated = burmeseNeedsReview = englishNeedsReview = false; 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; formNameMy = project.NameMy; formDescription = project.Description; formDescriptionMy = project.DescriptionMy; burmeseGenerated = englishGenerated = burmeseNeedsReview = englishNeedsReview = false; formStartDate = project.StartDate; formEndDate = project.EndDate; formBudgetedHours = project.BudgetedHours; ClearValidation(); showModal = true; } private void CloseModal() { showModal = false; ClearValidation(); } // ─── Assignment Modal ───────────────────────────────────────────────────── private void RequestSaveConfirmation() { ClearValidation(); bool hasError = false; if (string.IsNullOrWhiteSpace(formName) && string.IsNullOrWhiteSpace(formNameMy)) { isNameError = true; nameErrorMessage = AppLocalization.Text("validation.projectNameRequired", "Project name is required."); hasError = true; } if (formEndDate < formStartDate) { isDateError = true; dateErrorMessage = AppLocalization.Text("validation.endDateBeforeStart", "End date cannot be before start date."); hasError = true; } if (hasError) return; if (isEditing) { confirmTitle = AppLocalization.Text("project.updateTitle", "Update Project?"); confirmMessage = AppLocalization.Text("project.updateConfirm", "Are you sure you want to update this project?"); confirmButtonText = AppLocalization.Text("action.update", "Update"); confirmIcon = "pencil"; confirmIconBg = "bg-amber-50"; confirmIconText = "text-amber-500"; confirmButtonClass = "bg-violet-600 hover:bg-violet-700"; } else { confirmTitle = AppLocalization.Text("project.createTitle", "Create Project?"); confirmMessage = AppLocalization.Text("project.createConfirm", "Are you sure you want to create this project?"); confirmButtonText = AppLocalization.Text("action.create", "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 = AppLocalization.Text("project.deleteTitle", "Delete Project?"); confirmMessage = AppLocalization.Text("project.deleteConfirm", "Are you sure you want to delete this project? This action cannot be undone."); confirmButtonText = AppLocalization.Text("action.delete", "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) && string.IsNullOrWhiteSpace(formNameMy)) || formEndDate < formStartDate) { return; } isSaving = true; errorMessage = null; var client = ApiClient.CreateClient(); try { if (isEditing) { var dto = new UpdateProjectDto { Name = formName, NameMy = formNameMy, Description = formDescription, DescriptionMy = formDescriptionMy, 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, NameMy = formNameMy, Description = formDescription, DescriptionMy = formDescriptionMy, 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 = AppLocalization.Text("project.created", "Project created successfully!"); return; } CloseModal(); await LoadProjects(); successMessage = isEditing ? AppLocalization.Text("project.updated", "Project updated successfully!") : AppLocalization.Text("project.created", "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 ?? AppLocalization.Text("project.failedDelete", "Failed to delete project.")); return; } await LoadProjects(); if (currentPage > TotalPages && TotalPages > 0) currentPage = TotalPages; successMessage = AppLocalization.Text("project.deleted", "Project deleted successfully!"); } catch (Exception ex) { Console.WriteLine($"Delete error: {ex.Message}"); } } }