Project Details Page
This commit is contained in:
425
OnProfNext.Client/Pages/Projects/ProjectDetails.razor
Normal file
425
OnProfNext.Client/Pages/Projects/ProjectDetails.razor
Normal file
@@ -0,0 +1,425 @@
|
||||
@page "/projects/{id:int}"
|
||||
@using OnProfNext.Client.Services
|
||||
@inject ProjectApiService ProjectService
|
||||
@inject NavigationManager Nav
|
||||
@inject OrderApiService OrderService
|
||||
@inject UserApiService UserService
|
||||
|
||||
@using OnProfNext.Shared.Models.DTOs
|
||||
|
||||
<h3 class="mb-4 d-flex justify-content-between align-items-center">
|
||||
<span class="text-primary">📁 Projekt-Details</span>
|
||||
<button class="btn btn-outline-secondary" @onclick="GoBack">
|
||||
<i class="bi bi-arrow-left"></i> Zurück
|
||||
</button>
|
||||
</h3>
|
||||
|
||||
@if (isLoading)
|
||||
{
|
||||
<div class="text-center mt-5">
|
||||
<div class="spinner-border text-primary" role="status"></div>
|
||||
<p class="mt-3 text-secondary">Projekt wird geladen...</p>
|
||||
</div>
|
||||
}
|
||||
else if (errorMessage is not null)
|
||||
{
|
||||
<div class="alert alert-danger">@errorMessage</div>
|
||||
}
|
||||
else if (project is null)
|
||||
{
|
||||
<div class="alert alert-warning">Projekt nicht gefunden.</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<!-- HEADER -->
|
||||
<div class="card shadow-sm border-0 mb-4">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<h3 class="text-primary mb-1">@project.ProjectName</h3>
|
||||
<span class="badge bg-@GetStatusColor(project.Status)">
|
||||
@project.Status
|
||||
</span>
|
||||
<p class="mt-3 text-muted">@project.Description</p>
|
||||
</div>
|
||||
<div class="text-end">
|
||||
<small class="text-muted d-block">
|
||||
<i class="bi bi-calendar"></i>
|
||||
@project.StartDate.ToString("dd.MM.yyyy")
|
||||
@if (project.EndDate is not null)
|
||||
{
|
||||
<span> – @project.EndDate?.ToString("dd.MM.yyyy")</span>
|
||||
}
|
||||
</small>
|
||||
<small class="text-muted d-block mt-1">
|
||||
<i class="bi bi-building"></i> Mandant: @project.MandantId
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (project.ProjectManagers?.Any() == true)
|
||||
{
|
||||
<div class="mt-3">
|
||||
<h6 class="fw-bold text-secondary">Projektleiter</h6>
|
||||
@foreach (var manager in project.ProjectManagers)
|
||||
{
|
||||
<span class="badge bg-primary me-1">@manager.FullName</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TABS -->
|
||||
<ul class="nav nav-tabs mb-3">
|
||||
@foreach (var tab in tabs)
|
||||
{
|
||||
<li class="nav-item">
|
||||
<button class="nav-link @(activeTab == tab.Key ? "active" : "")"
|
||||
@onclick="() => SetActiveTab(tab.Key)">
|
||||
<i class="@tab.Icon me-1"></i> @tab.Title
|
||||
</button>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
|
||||
<div class="tab-content p-3 border rounded bg-white shadow-sm">
|
||||
@switch (activeTab)
|
||||
{
|
||||
case "overview":
|
||||
<div>
|
||||
<h5>Projektübersicht</h5>
|
||||
<p>Hier kommt später ein Überblick über Fortschritt, KPIs und Aktivitäten.</p>
|
||||
</div>
|
||||
break;
|
||||
|
||||
case "tasks":
|
||||
<div>
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h5 class="m-0">Aufträge</h5>
|
||||
<button class="btn btn-success btn-sm" @onclick="ShowAddOrderModal">
|
||||
<i class="bi bi-plus-circle"></i> Neuer Auftrag
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@if (orders is null)
|
||||
{
|
||||
<div class="text-center text-muted mt-3">
|
||||
<div class="spinner-border spinner-border-sm text-primary"></div>
|
||||
<p>Aufträge werden geladen...</p>
|
||||
</div>
|
||||
}
|
||||
else if (!orders.Any())
|
||||
{
|
||||
<div class="alert alert-info text-center">Keine Aufträge vorhanden.</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<table class="table table-hover align-middle">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Nr.</th>
|
||||
<th>Titel</th>
|
||||
<th>Status</th>
|
||||
<th>Plan (h)</th>
|
||||
<th>Ist (h)</th>
|
||||
<th>Mitarbeiter</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var o in orders)
|
||||
{
|
||||
<tr>
|
||||
<td>@o.Auftragsnummer</td>
|
||||
<td>@o.Titel</td>
|
||||
<td>
|
||||
<span class="badge bg-@GetStatusColor(o.Status)">@o.Status</span>
|
||||
</td>
|
||||
<td>@o.Planstunden</td>
|
||||
<td>@o.Iststunden</td>
|
||||
<td>
|
||||
<!-- Mitarbeiter-Zuordnung (später) -->
|
||||
<span class="text-muted small">noch offen</span>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<button class="btn btn-outline-danger btn-sm" @onclick="() => DeleteOrder(o.Id)">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (showAddOrderModal)
|
||||
{
|
||||
<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">Neuen Auftrag anlegen</h5>
|
||||
<button type="button" class="btn-close btn-close-white" @onclick="CloseAddOrderModal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<EditForm Model="newOrder" OnValidSubmit="CreateOrderAsync">
|
||||
<DataAnnotationsValidator />
|
||||
<ValidationSummary />
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Auftragsnummer</label>
|
||||
<InputText class="form-control" @bind-Value="newOrder.Auftragsnummer" />
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Titel</label>
|
||||
<InputText class="form-control" @bind-Value="newOrder.Titel" />
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Planstunden</label>
|
||||
<InputNumber class="form-control" @bind-Value="newOrder.Planstunden" />
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Status</label>
|
||||
<InputSelect class="form-select" @bind-Value="newOrder.Status">
|
||||
<option value="Geplant">Geplant</option>
|
||||
<option value="In Arbeit">In Arbeit</option>
|
||||
<option value="Abgeschlossen">Abgeschlossen</option>
|
||||
</InputSelect>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Mitarbeiter zuweisen</label>
|
||||
<InputText class="form-control mb-2" @bind-Value="searchText" placeholder="Mitarbeiter suchen..." />
|
||||
<div class="list-group" style="max-height: 150px; overflow-y: auto;">
|
||||
@foreach (var u in FilteredUsers)
|
||||
{
|
||||
<label class="list-group-item small">
|
||||
<input type="checkbox"
|
||||
value="@u.Id"
|
||||
checked="@(selectedUserIds.Contains(u.Id))"
|
||||
@onchange="(e => ToggleUser(u.Id, (bool)e.Value!))" />
|
||||
@u.FirstName @u.LastName (@u.Username)
|
||||
</label>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-end">
|
||||
<button type="button" class="btn btn-secondary me-2" @onclick="CloseAddOrderModal">Abbrechen</button>
|
||||
<button type="submit" class="btn btn-success">Speichern</button>
|
||||
</div>
|
||||
</EditForm>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case "bookings":
|
||||
<div>
|
||||
<h5>Buchungen</h5>
|
||||
<p>Zeiterfassungen und Plan/Ist-Vergleiche werden hier erscheinen.</p>
|
||||
</div>
|
||||
break;
|
||||
|
||||
case "members":
|
||||
<div>
|
||||
<h5>Mitarbeiter</h5>
|
||||
<p>Teamzuordnung, Rollenverwaltung, People Picker.</p>
|
||||
</div>
|
||||
break;
|
||||
|
||||
case "documents":
|
||||
<div>
|
||||
<h5>Dokumente</h5>
|
||||
<p>Dateiupload, SharePoint-Verknüpfung, Projektreports.</p>
|
||||
</div>
|
||||
break;
|
||||
|
||||
case "comments":
|
||||
<div>
|
||||
<h5>Kommentare</h5>
|
||||
<p>Chat/Kommentarbereich für Projektkommunikation.</p>
|
||||
</div>
|
||||
break;
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter] public int id { get; set; }
|
||||
|
||||
private ProjectDto? project;
|
||||
private string? errorMessage;
|
||||
private bool isLoading = true;
|
||||
|
||||
// Tabs
|
||||
private string activeTab = "overview";
|
||||
private readonly List<(string Key, string Title, string Icon)> tabs = new()
|
||||
{
|
||||
("overview", "Übersicht", "bi bi-house"),
|
||||
("tasks", "Aufträge", "bi bi-list-task"),
|
||||
("bookings", "Buchungen", "bi bi-clock-history"),
|
||||
("members", "Mitarbeiter", "bi bi-people"),
|
||||
("documents", "Dokumente", "bi bi-folder2-open"),
|
||||
("comments", "Kommentare", "bi bi-chat-left-text")
|
||||
};
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadProjectAsync();
|
||||
|
||||
var result = await OrderService.GetOrdersByProjectAsync(1);
|
||||
Console.WriteLine($"Orders loaded: {result.Data?.Count}");
|
||||
|
||||
}
|
||||
|
||||
private async Task LoadProjectAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await ProjectService.GetProjectAsync(id);
|
||||
if (!result.Success)
|
||||
{
|
||||
errorMessage = result.Error;
|
||||
return;
|
||||
}
|
||||
project = result.Data;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorMessage = $"Fehler beim Laden: {ex.Message}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetStatusColor(string? status) => status switch
|
||||
{
|
||||
"Geplant" => "secondary",
|
||||
"In Arbeit" => "info",
|
||||
"Abgeschlossen" => "success",
|
||||
_ => "light"
|
||||
};
|
||||
|
||||
private void SetActiveTab(string key) => activeTab = key;
|
||||
private void GoBack() => Nav.NavigateTo("/projects");
|
||||
|
||||
|
||||
|
||||
private List<OrderDto>? orders;
|
||||
private bool showAddOrderModal = false;
|
||||
private OrderDto newOrder = new();
|
||||
private List<UserDto> users = new();
|
||||
private string searchText = "";
|
||||
private List<int> selectedUserIds = new();
|
||||
|
||||
private IEnumerable<UserDto> FilteredUsers => string.IsNullOrWhiteSpace(searchText)
|
||||
? users
|
||||
: users.Where(u => $"{u.FirstName} {u.LastName} {u.Username}".Contains(searchText, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
private void ToggleUser(int id, bool selected)
|
||||
{
|
||||
if (selected)
|
||||
{
|
||||
selectedUserIds.Add(id);
|
||||
}
|
||||
else
|
||||
{
|
||||
selectedUserIds.Remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowAddOrderModal()
|
||||
{
|
||||
newOrder = new OrderDto
|
||||
{
|
||||
ProjectId = project!.Id,
|
||||
Status = "Geplant",
|
||||
MandantId = project.MandantId
|
||||
};
|
||||
|
||||
selectedUserIds.Clear();
|
||||
searchText = "";
|
||||
showAddOrderModal = true;
|
||||
}
|
||||
|
||||
private void CloseAddOrderModal() => showAddOrderModal = false;
|
||||
|
||||
private async Task LoadOrderAsync()
|
||||
{
|
||||
var result = await OrderService.GetOrdersByProjectAsync(project!.Id);
|
||||
if(result.Success)
|
||||
{
|
||||
orders = result.Data;
|
||||
}
|
||||
else
|
||||
{
|
||||
errorMessage = result.Error;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CreateOrderAsync()
|
||||
{
|
||||
var result = await OrderService.CreateOrderAsync(newOrder);
|
||||
if(!result.Success)
|
||||
{
|
||||
errorMessage = result.Error;
|
||||
return;
|
||||
}
|
||||
|
||||
CloseAddOrderModal();
|
||||
await LoadOrderAsync();
|
||||
}
|
||||
|
||||
private async Task DeleteOrder(int id)
|
||||
{
|
||||
var result = await OrderService.DeleteOrderAsync(id);
|
||||
if (result.Success)
|
||||
{
|
||||
await LoadOrderAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
errorMessage = result.Error;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
<style>
|
||||
.nav-tabs .nav-link {
|
||||
color: #495057;
|
||||
border: none;
|
||||
border-bottom: 3px solid transparent;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-link.active {
|
||||
color: #0d6efd;
|
||||
font-weight: 600;
|
||||
border-color: #0d6efd;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
|
||||
.list-group-item {
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
</style>
|
||||
331
OnProfNext.Client/Pages/Projects/Projects.razor
Normal file
331
OnProfNext.Client/Pages/Projects/Projects.razor
Normal file
@@ -0,0 +1,331 @@
|
||||
@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<User> users = new();
|
||||
private string searchText = "";
|
||||
|
||||
private IEnumerable<User> 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>
|
||||
Reference in New Issue
Block a user