Wartestatus für Tickets;
This commit is contained in:
@@ -16,9 +16,23 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="ticket-list">
|
<div class="ticket-list">
|
||||||
|
@{
|
||||||
|
bool dividerShown = false;
|
||||||
|
}
|
||||||
@foreach (var ticket in TicketService.GetPendingTickets())
|
@foreach (var ticket in TicketService.GetPendingTickets())
|
||||||
{
|
{
|
||||||
<div class="ticket-card priority-@ticket.Priority.ToString().ToLower()">
|
// Sobald das erste wartende Ticket auftaucht und der Trenner noch nicht da ist:
|
||||||
|
@if (ticket.IsWaitingForFeedback && !dividerShown)
|
||||||
|
{
|
||||||
|
<div class="waiting-separator">
|
||||||
|
<hr />
|
||||||
|
<span>Wartende Tickets</span>
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
dividerShown = true; // Trenner wurde gezeichnet, nicht nochmal anzeigen
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="ticket-card priority-@ticket.Priority.ToString().ToLower() @(ticket.IsWaitingForFeedback ? "is-waiting" : "")">
|
||||||
<div class="ticket-header" @onclick="() => GoToDetails(ticket.Id)">
|
<div class="ticket-header" @onclick="() => GoToDetails(ticket.Id)">
|
||||||
<span class="prio-tag">@ticket.Priority</span>
|
<span class="prio-tag">@ticket.Priority</span>
|
||||||
<h5>@ticket.Title</h5>
|
<h5>@ticket.Title</h5>
|
||||||
@@ -33,9 +47,18 @@
|
|||||||
<span class="comments-info">
|
<span class="comments-info">
|
||||||
<i class="bi bi-chat-left-text"></i> @ticket.Comments.Count Kommentare
|
<i class="bi bi-chat-left-text"></i> @ticket.Comments.Count Kommentare
|
||||||
</span>
|
</span>
|
||||||
<button class="btn btn-sm btn-outline-success" @onclick="() => Complete(ticket.Id)">
|
<div class="ticket-actions">
|
||||||
Erledigen
|
<button class="btn btn-sm @(ticket.IsWaitingForFeedback ? "btn-secondary" : "btn-outline-warning")"
|
||||||
</button>
|
@onclick="() => ToggleWaitingStatus(ticket)"
|
||||||
|
@onclick:stopPropagation="true">
|
||||||
|
@(ticket.IsWaitingForFeedback ? "Warten aufheben" : "Auf Rückmeldung warten")
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-outline-success"
|
||||||
|
@onclick="() => Complete(ticket.Id)"
|
||||||
|
@onclick:stopPropagation="true">
|
||||||
|
Erledigen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -57,7 +80,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Complete(Guid id) => TicketService.CompleteTicket(id);
|
private void ToggleWaitingStatus(Ticket ticket)
|
||||||
|
{
|
||||||
|
// Status umkehren
|
||||||
|
ticket.IsWaitingForFeedback = !ticket.IsWaitingForFeedback;
|
||||||
|
|
||||||
|
// In der Datenbank speichern
|
||||||
|
TicketService.UpdateTicket(ticket);
|
||||||
|
|
||||||
|
// UI explizit anweisen, sich neu zu sortieren
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Complete(Guid id)
|
||||||
|
{
|
||||||
|
TicketService.CompleteTicket(id);
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
private void GoToDetails(Guid id) => Nav.NavigateTo($"/ticket/edit/{id}");
|
private void GoToDetails(Guid id) => Nav.NavigateTo($"/ticket/edit/{id}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,10 +107,38 @@
|
|||||||
background: #f8f9fa; border-radius: 8px; padding: 1rem; margin-bottom: 1rem;
|
background: #f8f9fa; border-radius: 8px; padding: 1rem; margin-bottom: 1rem;
|
||||||
border-left: 5px solid #ccc; cursor: pointer; transition: transform 0.1s;
|
border-left: 5px solid #ccc; cursor: pointer; transition: transform 0.1s;
|
||||||
}
|
}
|
||||||
|
.is-waiting {
|
||||||
|
opacity: 0.6;
|
||||||
|
background-color: #e9ecef;
|
||||||
|
border-left-color: #6c757d !important; /* Überschreibt die Prio-Farbe mit Grau */
|
||||||
|
}
|
||||||
|
.ticket-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
.ticket-card:hover { transform: scale(1.01); }
|
.ticket-card:hover { transform: scale(1.01); }
|
||||||
.priority-critical { border-left-color: #dc3545; }
|
.priority-critical { border-left-color: #dc3545; }
|
||||||
.priority-high { border-left-color: #fd7e14; }
|
.priority-high { border-left-color: #fd7e14; }
|
||||||
.ticket-header { display: flex; justify-content: space-between; align-items: center; }
|
.ticket-header { display: flex; justify-content: space-between; align-items: center; }
|
||||||
.prio-tag { font-size: 0.7rem; font-weight: bold; text-transform: uppercase; }
|
.prio-tag { font-size: 0.7rem; font-weight: bold; text-transform: uppercase; }
|
||||||
.ticket-footer { display: flex; justify-content: space-between; align-items: center; margin-top: 1rem; }
|
.ticket-footer { display: flex; justify-content: space-between; align-items: center; margin-top: 1rem; }
|
||||||
|
.waiting-separator {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
margin: 2.5rem 0 1.5rem 0;
|
||||||
|
color: #6c757d;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waiting-separator hr {
|
||||||
|
flex-grow: 1;
|
||||||
|
border: none;
|
||||||
|
border-top: 2px dashed #dee2e6;
|
||||||
|
margin: 0 1rem;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -35,10 +35,14 @@
|
|||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 d-flex align-items-end">
|
<div class="col-md-6 d-flex align-items-end gap-4">
|
||||||
<div class="form-check mb-2">
|
<div class="form-check mb-2">
|
||||||
|
<input class="form-check-input" type="checkbox" id="isWaiting" @bind="ticket.IsWaitingForFeedback">
|
||||||
|
<label class="form-check-label text-warning" for="isWaiting">Wartet auf Rückmeldung</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check mb-2">
|
||||||
<input class="form-check-input" type="checkbox" id="isDone" @bind="ticket.IsCompleted">
|
<input class="form-check-input" type="checkbox" id="isDone" @bind="ticket.IsCompleted">
|
||||||
<label class="form-check-label" for="isDone">Als erledigt markieren</label>
|
<label class="form-check-label text-success" for="isDone">Als erledigt markieren</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using TodoTicketApp.Data;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace TodoTicketApp.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(AppDbContext))]
|
||||||
|
[Migration("20260527090007_AddWaitingStatus")]
|
||||||
|
partial class AddWaitingStatus
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "10.0.8");
|
||||||
|
|
||||||
|
modelBuilder.Entity("TodoTicketApp.Models.Ticket", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.PrimitiveCollection<string>("AttachmentNames")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("IsCompleted")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("IsWaitingForFeedback")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Priority")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Tickets");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("TodoTicketApp.Models.TicketComment", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Text")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<Guid>("TicketId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("TicketId");
|
||||||
|
|
||||||
|
b.ToTable("Comments");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("TodoTicketApp.Models.TicketComment", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("TodoTicketApp.Models.Ticket", null)
|
||||||
|
.WithMany("Comments")
|
||||||
|
.HasForeignKey("TicketId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("TodoTicketApp.Models.Ticket", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Comments");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace TodoTicketApp.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddWaitingStatus : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "IsWaitingForFeedback",
|
||||||
|
table: "Tickets",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "IsWaitingForFeedback",
|
||||||
|
table: "Tickets");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,6 +37,9 @@ namespace TodoTicketApp.Migrations
|
|||||||
b.Property<bool>("IsCompleted")
|
b.Property<bool>("IsCompleted")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("IsWaitingForFeedback")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<int>("Priority")
|
b.Property<int>("Priority")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ public class Ticket
|
|||||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||||
public TicketPriority Priority { get; set; } = TicketPriority.Medium;
|
public TicketPriority Priority { get; set; } = TicketPriority.Medium;
|
||||||
public bool IsCompleted { get; set; } = false;
|
public bool IsCompleted { get; set; } = false;
|
||||||
|
public bool IsWaitingForFeedback { get; set; } = false;
|
||||||
|
|
||||||
// Relationen
|
// Relationen
|
||||||
public List<TicketComment> Comments { get; set; } = new();
|
public List<TicketComment> Comments { get; set; } = new();
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ public class SqliteTicketService : ITicketService
|
|||||||
return _context.Tickets
|
return _context.Tickets
|
||||||
.Include(t => t.Comments)
|
.Include(t => t.Comments)
|
||||||
.Where(t => !t.IsCompleted)
|
.Where(t => !t.IsCompleted)
|
||||||
.OrderByDescending(t => t.Priority)
|
.OrderBy(t => t.IsWaitingForFeedback)
|
||||||
|
.ThenByDescending(t => t.Priority)
|
||||||
.ThenBy(t => t.CreatedAt)
|
.ThenBy(t => t.CreatedAt)
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user