331 lines
12 KiB
Plaintext
331 lines
12 KiB
Plaintext
@page "/projects"
|
||
@using OnProfNext.Client.Services
|
||
@using OnProfNext.Shared.Models
|
||
@using OnProfNext.Shared.Models.DTOs
|
||
@inject ProjectApiService ProjectService
|
||
@inject UserApiService UserService
|
||
@inject NavigationManager Nav
|
||
|
||
<h3 class="mb-4 text-primary d-flex justify-content-between align-items-center">
|
||
<span>📁 Projektübersicht</span>
|
||
<button class="btn btn-success" @onclick="ShowAddModal">
|
||
<i class="bi bi-plus-circle"></i> Neues Projekt
|
||
</button>
|
||
</h3>
|
||
|
||
@if (errorMessage is not null)
|
||
{
|
||
<div class="alert alert-danger">@errorMessage</div>
|
||
}
|
||
else if (projects is null)
|
||
{
|
||
<div class="text-center mt-5">
|
||
<div class="spinner-border text-primary" role="status"></div>
|
||
<p class="mt-3 text-secondary">Projekte werden geladen...</p>
|
||
</div>
|
||
}
|
||
else if (!projects.Any())
|
||
{
|
||
<div class="alert alert-info text-center">
|
||
Es sind aktuell keine Projekte vorhanden.
|
||
</div>
|
||
}
|
||
else
|
||
{
|
||
@foreach (var group in projects.GroupBy(p => p.Status))
|
||
{
|
||
<h5 class="mt-4 mb-3 fw-bold text-secondary">
|
||
<i class="bi bi-kanban"></i> @group.Key
|
||
</h5>
|
||
|
||
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-3">
|
||
@foreach (var p in group)
|
||
{
|
||
<div class="col">
|
||
<div class="card shadow-sm h-100 border-0 position-relative card-hover"
|
||
style="cursor: pointer;" @onclick="@(() => OpenProjectDetails(p.Id))"
|
||
>
|
||
<button class="btn btn-sm btn-outline-danger position-absolute top-0 end-0 m-2"
|
||
title="Projekt löschen"
|
||
@onclick="() => ConfirmDelete(p)">
|
||
<i class="bi bi-trash"></i>
|
||
</button>
|
||
|
||
<div class="card-body">
|
||
<h5 class="card-title text-primary">@p.ProjectName</h5>
|
||
@if (p.ProjectManagers?.Any() == true)
|
||
{
|
||
<div class="mb-2">
|
||
@foreach (var manager in p.ProjectManagers)
|
||
{
|
||
<span class="badge bg-primary me-1">@manager.FullName</span>
|
||
}
|
||
</div>
|
||
}
|
||
else
|
||
{
|
||
<p class="text-muted small mb-2"><i class="bi bi-person-circle"></i> Keine Projektleiter</p>
|
||
}
|
||
<p class="card-text">@p.Description</p>
|
||
</div>
|
||
<div class="card-footer bg-white border-0 text-end small text-muted">
|
||
<i class="bi bi-calendar"></i>
|
||
@p.StartDate.ToString("dd.MM.yyyy")
|
||
@if (p.EndDate is not null)
|
||
{
|
||
<span> – @p.EndDate?.ToString("dd.MM.yyyy")</span>
|
||
}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
}
|
||
</div>
|
||
}
|
||
}
|
||
|
||
<!-- Neues Projekt Modal -->
|
||
@if (showAddModal)
|
||
{
|
||
<div class="modal fade show d-block"
|
||
tabindex="-1"
|
||
style="background-color: rgba(0,0,0,0.5);">
|
||
<div class="modal-dialog modal-dialog-centered"
|
||
@onclick:stopPropagation>
|
||
<div class="modal-content border-0 shadow">
|
||
<div class="modal-header bg-primary text-white">
|
||
<h5 class="modal-title">Neues Projekt anlegen</h5>
|
||
<button type="button" class="btn-close btn-close-white" @onclick="CloseAddModal"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<EditForm Model="newProject" OnValidSubmit="CreateProjectAsync">
|
||
<DataAnnotationsValidator />
|
||
<ValidationSummary />
|
||
|
||
<div class="mb-3">
|
||
<label class="form-label">Projektname</label>
|
||
<InputText class="form-control" @bind-Value="newProject.ProjectName" />
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label class="form-label">Beschreibung</label>
|
||
<InputTextArea class="form-control" rows="3" @bind-Value="newProject.Description" />
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label class="form-label">Projektleiter</label>
|
||
<InputText class="form-control mb-2" @bind-Value="searchText" placeholder="Projektleiter suchen..." />
|
||
<div class="list-group" style="max-height: 150px; overflow-y: auto;">
|
||
@if (!FilteredUsers.Any())
|
||
{
|
||
<div class="list-group-item text-muted">Keine Benutzer gefunden</div>
|
||
}
|
||
else
|
||
{
|
||
@foreach (var user in FilteredUsers)
|
||
{
|
||
<label class="list-group-item">
|
||
<input type="checkbox"
|
||
value="@user.Id"
|
||
checked="@(newProject.ProjectManagerIds.Contains(user.Id))"
|
||
@onchange="@(e => ToggleProjectManager(user.Id, e.Value != null && (bool)e.Value))" />
|
||
@user.FirstName @user.LastName (@user.Username)
|
||
</label>
|
||
}
|
||
}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label class="form-label">Status</label>
|
||
<InputSelect class="form-select" @bind-Value="newProject.Status">
|
||
<option value="Geplant">Geplant</option>
|
||
<option value="In Arbeit">In Arbeit</option>
|
||
<option value="Abgeschlossen">Abgeschlossen</option>
|
||
</InputSelect>
|
||
</div>
|
||
|
||
<div class="d-flex justify-content-end">
|
||
<button type="button" class="btn btn-secondary me-2" @onclick="CloseAddModal">Abbrechen</button>
|
||
<button type="submit" class="btn btn-success">Speichern</button>
|
||
</div>
|
||
</EditForm>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
}
|
||
|
||
@if (showDeleteModal)
|
||
{
|
||
<div class="modal fade show d-block" tabindex="-1"
|
||
style="background-color: rgba(0,0,0,0.5);">
|
||
<div class="modal-dialog modal-dialog-centered" @onclick:stopPropagation>
|
||
<div class="modal-content border-0 shadow">
|
||
<div class="modal-header bg-danger text-white">
|
||
<h5 class="modal-title">Projekt löschen</h5>
|
||
<button type="button" class="btn-close btn-close-white" @onclick="CloseDeleteModal"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p>Möchten Sie das Projekt <strong>@projectToDelete?.ProjectName</strong> wirklich löschen?</p>
|
||
<p class="text-muted small">Dieser Vorgang kann nicht rückgängig gemacht werden.</p>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-secondary" @onclick="CloseDeleteModal">Abbrechen</button>
|
||
<button class="btn btn-danger" @onclick="DeleteProjectAsync">Löschen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
}
|
||
|
||
|
||
@code {
|
||
private List<ProjectDto>? projects;
|
||
private string? errorMessage;
|
||
private bool showAddModal = false;
|
||
private ProjectCreateDto newProject = new();
|
||
private List<UserDto> users = new();
|
||
private string searchText = "";
|
||
|
||
private IEnumerable<UserDto> FilteredUsers => string.IsNullOrWhiteSpace(searchText)
|
||
? users
|
||
: users.Where(u => $"{u.FirstName} {u.LastName} {u.Username}"
|
||
.Contains(searchText, StringComparison.OrdinalIgnoreCase));
|
||
|
||
protected override async Task OnInitializedAsync()
|
||
{
|
||
await LoadProjectsAsync();
|
||
var userResult = await UserService.GetUsersAsync();
|
||
if (userResult.Success && userResult.Data != null)
|
||
users = userResult.Data;
|
||
}
|
||
|
||
private async Task LoadProjectsAsync()
|
||
{
|
||
var result = await ProjectService.GetProjectsAsync();
|
||
if (!result.Success)
|
||
{
|
||
errorMessage = result.Error;
|
||
return;
|
||
}
|
||
projects = result.Data?
|
||
.OrderBy(p => p.Status)
|
||
.ThenByDescending(p => p.StartDate)
|
||
.ToList();
|
||
}
|
||
|
||
private void ShowAddModal()
|
||
{
|
||
newProject = new ProjectCreateDto { Status = "Geplant", MandantId = 1 };
|
||
searchText = "";
|
||
showAddModal = true;
|
||
}
|
||
|
||
private void CloseAddModal()
|
||
{
|
||
showAddModal = false;
|
||
}
|
||
|
||
private void ToggleProjectManager(int userId, bool isChecked)
|
||
{
|
||
if (isChecked)
|
||
{
|
||
if (!newProject.ProjectManagerIds.Contains(userId))
|
||
newProject.ProjectManagerIds.Add(userId);
|
||
}
|
||
else
|
||
{
|
||
newProject.ProjectManagerIds.Remove(userId);
|
||
}
|
||
}
|
||
|
||
private async Task CreateProjectAsync()
|
||
{
|
||
var result = await ProjectService.CreateProjectAsync(newProject);
|
||
if (!result.Success)
|
||
{
|
||
errorMessage = result.Error;
|
||
return;
|
||
}
|
||
CloseAddModal();
|
||
await LoadProjectsAsync();
|
||
}
|
||
|
||
|
||
|
||
private bool showDeleteModal = false;
|
||
private ProjectDto? projectToDelete;
|
||
|
||
private void ConfirmDelete(ProjectDto project)
|
||
{
|
||
projectToDelete = project;
|
||
showDeleteModal = true;
|
||
}
|
||
|
||
private void CloseDeleteModal()
|
||
{
|
||
showDeleteModal = false;
|
||
projectToDelete = null;
|
||
}
|
||
|
||
private async Task DeleteProjectAsync()
|
||
{
|
||
if(projectToDelete is null)
|
||
return;
|
||
|
||
var result = await ProjectService.DeleteProjectAsync(projectToDelete.Id);
|
||
if(!result.Success)
|
||
{
|
||
errorMessage = result.Error;
|
||
return;
|
||
}
|
||
|
||
CloseDeleteModal();
|
||
await LoadProjectsAsync();
|
||
|
||
}
|
||
|
||
private void OpenProjectDetails(int projectId)
|
||
{
|
||
Nav.NavigateTo($"/projects/{projectId}");
|
||
}
|
||
|
||
}
|
||
|
||
<style>
|
||
.card {
|
||
transition: all 0.15s ease-in-out;
|
||
border-radius: 0.75rem;
|
||
}
|
||
|
||
.card:hover {
|
||
transform: scale(1.02);
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||
}
|
||
|
||
.modal-content {
|
||
border-radius: 0.75rem;
|
||
}
|
||
|
||
.modal-header {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.list-group-item {
|
||
padding: 0.5rem 1rem;
|
||
}
|
||
|
||
.list-group-item input[type="checkbox"] {
|
||
margin-right: 0.5rem;
|
||
}
|
||
|
||
.btn-outline-danger {
|
||
border: none;
|
||
color: #dc3545;
|
||
}
|
||
|
||
.btn-outline-danger:hover {
|
||
color: white;
|
||
background-color: #dc3545;
|
||
}
|
||
</style> |