Started implementing admin ticket ui. Cleaned up some stuff

This commit is contained in:
Marcel Baumgartner 2023-11-09 01:56:14 +01:00
parent f5501f77fe
commit 332937f964
11 changed files with 634 additions and 25 deletions

View file

@ -1,5 +1,6 @@
using Microsoft.EntityFrameworkCore;
using Moonlight.App.Database.Entities.Tickets;
using Moonlight.App.Database.Enums;
using Moonlight.App.Event;
using Moonlight.App.Event.Args;
using Moonlight.App.Extensions;
@ -50,7 +51,31 @@ public class TicketChatService
return Task.CompletedTask;
}
public Task Stop()
public async Task Update(bool open, TicketPriority priority) // Updated and syncs ticket states to all listeners
{
if (Ticket.Open != open)
{
Ticket.Open = open;
if(open)
await SendSystemMessage("Ticket has been opened");
else
await SendSystemMessage("Ticket has been closed");
}
if (Ticket.Priority != priority)
{
Ticket.Priority = priority;
await SendSystemMessage($"Ticket priority to {priority}");
}
TicketRepository.Update(Ticket);
await Events.OnTicketUpdated.InvokeAsync(Ticket);
}
public Task Stop() // Clear cache and stop listeners
{
Events.OnTicketMessage -= OnTicketMessage;
Events.OnTicketUpdated -= OnTicketUpdated;
@ -60,7 +85,24 @@ public class TicketChatService
return Task.CompletedTask;
}
public async Task SendMessage(string content, Stream? attachmentStream = null, string? attachmentName = null)
#region Sending
public async Task SendSystemMessage(string content) // use this to send a message shown in a seperator
{
// Build the message model
var message = new TicketMessage()
{
Content = content,
Attachment = null,
CreatedAt = DateTime.UtcNow,
Sender = null,
IsSupport = IsSupporter
};
await SyncMessage(message);
}
public async Task SendMessage(string content, Stream? attachmentStream = null, string? attachmentName = null) // Regular send method
{
if(string.IsNullOrEmpty(content))
return;
@ -87,6 +129,11 @@ public class TicketChatService
IsSupport = IsSupporter
};
await SyncMessage(message);
}
private async Task SyncMessage(TicketMessage message) // Use this function to save and sync function to others
{
// Save ticket to the db
var t = TicketRepository
.Get()
@ -103,6 +150,8 @@ public class TicketChatService
});
}
#endregion
// Event handlers
private async void OnTicketUpdated(object? _, Ticket ticket)
{

View file

@ -47,6 +47,17 @@
<ItemGroup>
<_ContentIncludedByDefault Remove="storage\config.json" />
<_ContentIncludedByDefault Remove="Shared\Components\Partials\TicketPopup\LiveChatCreate.razor" />
<_ContentIncludedByDefault Remove="Shared\Components\Partials\TicketPopup\LiveChatMain.razor" />
<_ContentIncludedByDefault Remove="Shared\Components\Partials\TicketPopup\LiveChatOverview.razor" />
<_ContentIncludedByDefault Remove="Shared\Components\Partials\TicketPopup\LiveChatView.razor" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="Shared\Components\TicketPopup\LiveChatCreate.razor" />
<AdditionalFiles Include="Shared\Components\TicketPopup\LiveChatMain.razor" />
<AdditionalFiles Include="Shared\Components\TicketPopup\LiveChatOverview.razor" />
<AdditionalFiles Include="Shared\Components\TicketPopup\LiveChatView.razor" />
</ItemGroup>
</Project>

View file

@ -23,6 +23,7 @@ Directory.CreateDirectory(PathBuilder.Dir("storage", "logs"));
var logConfig = new LoggerConfiguration();
logConfig = logConfig.Enrich.FromLogContext()
.MinimumLevel.Debug()
.WriteTo.Console(
outputTemplate:
"{Timestamp:HH:mm:ss} [{Level:u3}] {SourceContext} {Message:lj}{NewLine}{Exception}");

View file

@ -106,6 +106,17 @@
</a>
</div>
<div class="menu-item">
<a class="menu-link " href="/admin/tickets">
<span class="menu-icon">
<i class="bx bx-sm bx-support"></i>
</span>
<span class="menu-title">
Tickets
</span>
</a>
</div>
<div class="menu-item">
<a class="menu-link " href="/admin/sys">
<span class="menu-icon">

View file

@ -11,7 +11,7 @@
<div class="card-header">
<span class="card-title fs-5">Create a new ticket</span>
<div class="card-toolbar">
<button @onclick="() => LiveChatMain.SetViewIndex(1)" class="btn btn-rounded-circle btn-icon">
<button @onclick="() => TicketPopupMain.SetViewIndex(1)" class="btn btn-rounded-circle btn-icon">
<i class="bx bx-sm bx-chevron-left"></i>
</button>
</div>
@ -49,7 +49,7 @@
@code
{
[CascadingParameter]
public LiveChatMain LiveChatMain { get; set; }
public TicketPopupMain TicketPopupMain { get; set; }
private Service[] Services;
private CreateTicketForm Form = new();
@ -77,6 +77,6 @@
Form.Service
);
await LiveChatMain.OpenTicket(ticket);
await TicketPopupMain.OpenTicket(ticket);
}
}

View file

@ -12,15 +12,15 @@
<div class="card border border-2 border-warning" style="pointer-events: all; height: 70vh">
@if (ViewIndex == 1)
{
<LiveChatOverview />
<TicketPopupOverview />
}
else if (ViewIndex == 2)
{
<LiveChatView />
<TicketPopupView />
}
else if (ViewIndex == 3)
{
<LiveChatCreate />
<TicketPopupCreate />
}
</div>
}

View file

@ -11,7 +11,7 @@
<div class="card-header">
<span class="card-title fs-5">Your tickets</span>
<div class="card-toolbar">
<button @onclick="() => LiveChatMain.SetViewIndex(0)" class="btn btn-rounded-circle btn-icon">
<button @onclick="() => TicketPopupMain.SetViewIndex(0)" class="btn btn-rounded-circle btn-icon">
<i class="bx bx-sm bx-x"></i>
</button>
</div>
@ -19,7 +19,7 @@
<div class="card-body pt-5">
<div class="scroll-y me-n5 pe-5" style="height: 50vh; width: 40vh; display: flex; flex-direction: column;">
<div class="d-flex flex-stack py-2 justify-content-center">
<h3 class="align-middle text-center">Need help? Create a <a @onclick="() => LiveChatMain.SetViewIndex(3)" @onclick:preventDefault href="#" class="text-primary">ticket</a></h3>
<h3 class="align-middle text-center">Need help? Create a <a @onclick="() => TicketPopupMain.SetViewIndex(3)" @onclick:preventDefault href="#" class="text-primary">ticket</a></h3>
</div>
<div class="d-flex flex-stack py-4">
@ -30,7 +30,7 @@
{
foreach (var ticket in Tickets)
{
<a href="#" @onclick="() => LiveChatMain.OpenTicket(ticket)" @onclick:preventDefault class="d-flex flex-stack py-4">
<a href="#" @onclick="() => TicketPopupMain.OpenTicket(ticket)" @onclick:preventDefault class="d-flex flex-stack py-4">
<div class="d-flex align-items-center">
<div>
<a href="#" class="fs-5 fw-bold text-gray-900 text-hover-primary mb-2">@(ticket.Name)</a>
@ -54,7 +54,7 @@
@code
{
[CascadingParameter]
public LiveChatMain LiveChatMain { get; set; }
public TicketPopupMain TicketPopupMain { get; set; }
private Ticket[] Tickets;

View file

@ -8,7 +8,7 @@
<div class="card-header">
<span class="card-title fs-5">@(HasStarted ? TicketService.Chat.Ticket.Name : "Loading")</span>
<div class="card-toolbar">
<button @onclick="() => LiveChatMain.SetViewIndex(1)" class="btn btn-rounded-circle btn-icon">
<button @onclick="() => TicketPopupMain.SetViewIndex(1)" class="btn btn-rounded-circle btn-icon">
<i class="bx bx-sm bx-chevron-left"></i>
</button>
</div>
@ -66,7 +66,7 @@
<div class="card-footer">
<div class="row">
<div class="input-group">
<textarea @bind="MyMessageContent" class="form-control form-control-solid-bg rounded-end me-3" placeholder="Type a message" style="height: 1vh"></textarea>
<textarea @bind="MessageContent" class="form-control form-control-solid-bg rounded-end me-3" placeholder="Type a message" style="height: 1vh"></textarea>
<ChatFileSelect @ref="FileSelect"/>
<WButton OnClick="SendMessage" CssClasses="ms-2 btn btn-icon btn-bg-light btn-color-white">
<i class="bx bx-sm bx-send"></i>
@ -78,12 +78,12 @@
@code
{
[CascadingParameter]
public LiveChatMain LiveChatMain { get; set; }
private ChatFileSelect FileSelect;
public TicketPopupMain TicketPopupMain { get; set; }
private bool HasStarted = false;
private string MyMessageContent = "";
private ChatFileSelect FileSelect;
private string MessageContent = "";
private async Task Load(LazyLoader lazyLoader)
{
@ -91,7 +91,7 @@
// Initialize chat service and start it
TicketService.Chat.OnUpdate = OnUpdate;
await TicketService.Chat.Start(LiveChatMain.CurrentTicket);
await TicketService.Chat.Start(TicketPopupMain.CurrentTicket);
// Let the ui know that we are ready
HasStarted = true;
@ -105,18 +105,18 @@
private async Task SendMessage()
{
if (string.IsNullOrEmpty(MyMessageContent) && FileSelect.SelectedFile == null)
if (string.IsNullOrEmpty(MessageContent) && FileSelect.SelectedFile == null)
return;
if (!HasStarted)
return;
if (FileSelect.SelectedFile == null)
await TicketService.Chat.SendMessage(MyMessageContent);
await TicketService.Chat.SendMessage(MessageContent);
else
{
await TicketService.Chat.SendMessage(
string.IsNullOrEmpty(MyMessageContent) ? $"Upload of {FileSelect.SelectedFile.Name}" : MyMessageContent,
string.IsNullOrEmpty(MessageContent) ? $"Upload of {FileSelect.SelectedFile.Name}" : MessageContent,
FileSelect.SelectedFile.OpenReadStream(1024 * 1024 * 5),
FileSelect.SelectedFile.Name
);
@ -124,7 +124,7 @@
await FileSelect.RemoveSelection();
}
MyMessageContent = "";
MessageContent = "";
await InvokeAsync(StateHasChanged);
}

View file

@ -1,4 +1,4 @@
@using Moonlight.Shared.Components.Partials.LiveChat
@using Moonlight.Shared.Components.TicketPopup
<div class="d-flex flex-column flex-root app-root">
<div class="app-page flex-column flex-column-fluid">
@ -11,7 +11,7 @@
<div class="app-container container-fluid">
@ChildContent
<LiveChatMain />
<TicketPopupMain />
</div>
</div>
</div>

View file

@ -0,0 +1,265 @@
@page "/admin/tickets"
@using Moonlight.App.Extensions.Attributes
@using Moonlight.App.Models.Enums
@using Moonlight.App.Repositories
@using Moonlight.App.Database.Entities.Tickets
@using BlazorTable
@using Microsoft.EntityFrameworkCore
@using Moonlight.App.Database.Enums
@using Moonlight.App.Event
@using Moonlight.App.Event.Args
@attribute [RequirePermission(Permission.AdminTickets)]
@implements IDisposable
@inject Repository<Ticket> TicketRepository
<div class="row mb-5">
<LazyLoader @ref="StatisticsLazyLoader" Load="LoadStatistics" ShowAsCard="true">
<div class="col-md-4 col-12">
<div class="card">
<div class="card-body">
<div class="row align-items-center gx-0">
<div class="col">
<h6 class="text-uppercase text-muted mb-2">
<TL>Total Tickets</TL>
</h6>
<span class="h2 mb-0">
@(TotalTicketsCount)
</span>
</div>
<div class="col-auto">
<span class="h2 text-muted mb-0">
<i class="text-primary bx bx-purchase-tag bx-lg"></i>
</span>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-4 col-12">
<div class="card">
<div class="card-body">
<div class="row align-items-center gx-0">
<div class="col">
<h6 class="text-uppercase text-muted mb-2">
<TL>Pending tickets</TL>
</h6>
<span class="h2 mb-0">
@(PendingTicketsCount)
</span>
</div>
<div class="col-auto">
<span class="h2 text-muted mb-">
<i class="text-primary bx bx-hourglass bx-lg"></i>
</span>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-4 col-12">
<div class="card">
<div class="card-body">
<div class="row align-items-center gx-0">
<div class="col">
<h6 class="text-uppercase text-muted mb-2">
<TL>Closed tickets</TL>
</h6>
<span class="h2 mb-0">
@(ClosedTicketsCount)
</span>
</div>
<div class="col-auto">
<span class="h2 text-muted mb-">
<i class="text-primary bx bx-lock bx-lg"></i>
</span>
</div>
</div>
</div>
</div>
</div>
</LazyLoader>
</div>
<div class="card">
<div class="card-header">
<span class="card-title">
<TL>Ticket overview</TL>
</span>
<div class="card-toolbar">
<div class="btn-group">
<WButton Text="Overview" CssClasses="btn-secondary" OnClick="() => UpdateFilter(0)" />
<WButton Text="Closed tickets" CssClasses="btn-secondary" OnClick="() => UpdateFilter(1)" />
</div>
</div>
</div>
<div class="card-body">
<LazyLoader @ref="TicketLazyLoader" Load="LoadTickets" ShowAsCard="true">
<div class="table-responsive">
<Table TableItem="Ticket" Items="AllTickets" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
<Column TableItem="Ticket" Title="Id" Field="@(x => x.Id)" Filterable="true" Sortable="true"/>
<Column TableItem="Ticket" Title="Name" Field="@(x => x.Name)" Filterable="true" Sortable="false">
<Template>
<a href="/admin/tickets/view/@(context.Id)">@(context.Name)</a>
</Template>
</Column>
<Column TableItem="Ticket" Title="User" Field="@(x => x.Id)" Filterable="false" Sortable="false">
<Template>
<a href="/admin/users/view/@(context.Creator.Id)">@(context.Creator.Username)</a>
</Template>
</Column>
<Column TableItem="Ticket" Title="Created at" Field="@(x => x.CreatedAt)" Filterable="true" Sortable="true">
<Template>
<span>@(Formatter.FormatDate(context.CreatedAt))</span>
</Template>
</Column>
<Column TableItem="Ticket" Title="Priority" Field="@(x => x.Priority)" Filterable="true" Sortable="true">
<Template>
@switch (context.Priority)
{
case TicketPriority.Low:
<span class="badge bg-success">@(context.Priority)</span>
break;
case TicketPriority.Medium:
<span class="badge bg-primary">@(context.Priority)</span>
break;
case TicketPriority.High:
<span class="badge bg-warning">@(context.Priority)</span>
break;
case TicketPriority.Critical:
<span class="badge bg-danger">@(context.Priority)</span>
break;
}
</Template>
</Column>
<Column TableItem="Ticket" Title="Status" Field="@(x => x.Open)" Filterable="true" Sortable="true">
<Template>
@if (context.Open)
{
<span class="badge bg-success">Open</span>
}
else
{
<span class="badge bg-danger">Closed</span>
}
</Template>
</Column>
<Pager AlwaysShow="true" ShowPageNumber="true" ShowTotalCount="true"/>
</Table>
</div>
</LazyLoader>
</div>
</div>
@code
{
// Lazy loaders
private LazyLoader TicketLazyLoader;
private LazyLoader StatisticsLazyLoader;
// Statistics
private int TotalTicketsCount;
private int ClosedTicketsCount;
private int PendingTicketsCount;
// Data
private int Filter = 0;
private Ticket[] AllTickets;
protected override void OnInitialized()
{
Events.OnTicketCreated += OnTicketCreated;
Events.OnTicketUpdated += OnTicketUpdated;
Events.OnTicketMessage += OnTicketMessage;
}
#region Lazyloaders
private Task LoadStatistics(LazyLoader lazyLoader)
{
TotalTicketsCount = TicketRepository
.Get()
.Count();
ClosedTicketsCount = TicketRepository
.Get()
.Count(x => !x.Open);
PendingTicketsCount = TicketRepository
.Get()
.Where(x => x.Open)
.Count(x => x.Messages.All(x => !x.IsSupport));
return Task.CompletedTask;
}
private Task LoadTickets(LazyLoader lazyLoader)
{
if (Filter == 0)
{
AllTickets = TicketRepository
.Get()
.Include(x => x.Creator)
.Include(x => x.Service)
.ThenInclude(x => x.Product)
.Where(x => x.Open)
.ToArray();
}
else if (Filter == 1)
{
AllTickets = TicketRepository
.Get()
.Include(x => x.Creator)
.Include(x => x.Service)
.ThenInclude(x => x.Product)
.Where(x => !x.Open)
.ToArray();
}
return Task.CompletedTask;
}
#endregion
private async Task UpdateFilter(int filter)
{
Filter = filter;
await TicketLazyLoader.Reload();
}
#region Events
private async void OnTicketMessage(object? sender, TicketMessageEventArgs message)
{
if(!message.TicketMessage.IsSupport) // Only update if support has sent messages as the pending tickets depend on that
return;
await StatisticsLazyLoader.Reload();
await TicketLazyLoader.Reload();
}
private async void OnTicketUpdated(object? o, Ticket e)
{
await StatisticsLazyLoader.Reload();
await TicketLazyLoader.Reload();
}
private async void OnTicketCreated(object? o, Ticket e)
{
await StatisticsLazyLoader.Reload();
await TicketLazyLoader.Reload();
}
#endregion
public void Dispose() // Unsubscribe to events
{
Events.OnTicketCreated -= OnTicketCreated;
Events.OnTicketUpdated -= OnTicketUpdated;
Events.OnTicketMessage -= OnTicketMessage;
}
}

View file

@ -0,0 +1,272 @@
@page "/admin/tickets/view/{Id:int}"
@using Moonlight.App.Extensions.Attributes
@using Moonlight.App.Models.Enums
@using Moonlight.App.Repositories
@using Moonlight.App.Services.Ticketing
@using Moonlight.App.Database.Entities.Tickets
@using System.Text.RegularExpressions
@using Microsoft.EntityFrameworkCore
@using Moonlight.App.Database.Enums
@attribute [RequirePermission(Permission.AdminTickets)]
@implements IDisposable
@inject TicketService TicketService
@inject ToastService ToastService
@inject Repository<Ticket> TicketRepository
<LazyLoader Load="LoadTicket" ShowAsCard="true">
@if (Ticket == null)
{
<NotFoundAlert/>
}
else
{
<div class="row">
<div class="col-md-3 col-12 mb-5">
<div class="card">
<div class="card-header">
<ul class="nav nav-stretch nav-line-tabs nav-line-tabs-2x border-transparent fs-5 fw-bold">
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(0 == 0 ? "active" : "")" href="/account">
Request
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(0 == 1 ? "active" : "")" href="/account/security">
Details
</a>
</li>
</ul>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-borderless align-middle mb-0 fs-5">
<tbody>
<tr>
<th>
<span>Ticket ID</span>
</th>
<td>@(Ticket.Id)</td>
</tr>
<tr>
<th>
<span>User</span>
</th>
<td>
<a href="/admin/users/view/@(Ticket.Creator.Id)">@(Ticket.Creator.Username)</a>
</td>
</tr>
<tr>
<th>
<span>Service</span>
</th>
<td>
@if (Ticket.Service == null)
{
<span>None</span>
}
else
{
<a href="/service/@(Ticket.Service.Id)">@(Ticket.Service.Nickname ?? $"Service {Ticket.Service.Id}")</a>
}
</td>
</tr>
<tr>
<th>
<span>Status</span>
</th>
<td>
<div class="form-check">
<input class="form-check-input" type="checkbox" @bind="EditOpen"/>
</div>
</td>
</tr>
<tr>
<th>
<span>Priority</span>
</th>
<td>
<SmartEnumSelect @bind-Value="EditPriority"/>
</td>
</tr>
<tr>
<th>
<span>Created at</span>
</th>
<td>
<span>@(Formatter.FormatDate(Ticket.CreatedAt))</span>
</td>
</tr>
<tr>
<th></th>
<td>
<WButton OnClick="Save" Text="Save" CssClasses="btn-primary" />
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="col-md-9 col-12">
<div class="card">
<div class="card-body bg-black p-8">
<LazyLoader Load="LoadChatClient">
<div class="scroll-y" style="display: flex; flex-direction: column-reverse; height: 70vh">
@foreach (var message in TicketService.Chat.Messages.OrderByDescending(x => x.CreatedAt))
{
var orientation = message.IsSupport ? "end" : "start";
if (message.Sender != null)
{
<div class="d-flex justify-content-@(orientation) mb-10 ">
<div class="d-flex flex-column align-items-@(orientation)">
<div class="d-flex align-items-center mb-2">
<div class="symbol symbol-35px symbol-circle ">
<img alt="Avatar" src="/api/bucket/avatars/@(message.Sender.Avatar)">
</div>
<div class="ms-3">
<div class="fs-5 fw-bold text-gray-900 me-1">@(message.Sender.Username)</div>
<span class="text-muted fs-7 mb-1">@(Formatter.FormatAgoFromDateTime(message.CreatedAt))</span>
</div>
</div>
<div class="p-5 rounded bg-light-@(message.IsSupport ? "info" : "primary") text-dark fw-semibold mw-lg-400px text-@(orientation)">
@(Formatter.FormatLineBreaks(message.Content))
@if (message.Attachment != null)
{
<div class="mt-3">
@if (Regex.IsMatch(message.Attachment, @"\.(jpg|jpeg|png|gif|bmp)$"))
{
<img src="/api/bucket/ticketAttachments/@(message.Attachment)" class="img-fluid" alt="Attachment"/>
}
else
{
<a href="/api/bucket/ticketAttachments/@(message.Attachment)" target="_blank" class="btn btn-secondary">
<i class="me-2 bx bx-download"></i> @(message.Attachment)
</a>
}
</div>
}
</div>
</div>
</div>
}
else
{
<div class="separator separator-content my-15">@(message.Content)</div>
}
}
</div>
</LazyLoader>
</div>
<div class="card-footer">
<div class="row">
<div class="input-group">
<textarea @bind="MessageContent" class="form-control form-control-solid-bg rounded-end me-3" placeholder="Type a message" style="height: 1vh"></textarea>
<ChatFileSelect @ref="FileSelect"/>
<WButton OnClick="SendMessage" CssClasses="ms-2 btn btn-icon btn-bg-light btn-color-white">
<i class="bx bx-sm bx-send"></i>
</WButton>
</div>
</div>
</div>
</div>
</div>
</div>
}
</LazyLoader>
@code
{
[Parameter]
public int Id { get; set; }
private Ticket? Ticket;
private bool HasStarted = false;
// Message compose cache
private ChatFileSelect FileSelect;
private string MessageContent = "";
// Edit cache
private bool EditOpen;
private TicketPriority EditPriority;
private Task LoadTicket(LazyLoader _)
{
Ticket = TicketRepository
.Get()
.Include(x => x.Creator)
.Include(x => x.Service)
.FirstOrDefault(x => x.Id == Id);
return Task.CompletedTask;
}
private async Task LoadChatClient(LazyLoader lazyLoader)
{
if (Ticket != null)
{
await lazyLoader.SetText("Starting chat client");
TicketService.Chat.OnUpdate += OnUpdate;
await TicketService.Chat.Start(Ticket, true);
EditOpen = TicketService.Chat.Ticket.Open;
EditPriority = TicketService.Chat.Ticket.Priority;
HasStarted = true;
await InvokeAsync(StateHasChanged);
}
}
private async Task SendMessage()
{
if (string.IsNullOrEmpty(MessageContent) && FileSelect.SelectedFile == null)
return;
if (!HasStarted)
return;
if (FileSelect.SelectedFile == null)
await TicketService.Chat.SendMessage(MessageContent);
else
{
await TicketService.Chat.SendMessage(
string.IsNullOrEmpty(MessageContent) ? $"Upload of {FileSelect.SelectedFile.Name}" : MessageContent,
FileSelect.SelectedFile.OpenReadStream(1024 * 1024 * 5),
FileSelect.SelectedFile.Name
);
await FileSelect.RemoveSelection();
}
MessageContent = "";
await InvokeAsync(StateHasChanged);
}
private async Task Save()
{
await TicketService.Chat.Update(EditOpen, EditPriority);
await ToastService.Success("Successfully updated ticket");
}
private async Task OnUpdate()
{
// Overwrite current cached data
EditOpen = TicketService.Chat.Ticket.Open;
EditPriority = TicketService.Chat.Ticket.Priority;
await InvokeAsync(StateHasChanged);
}
public async void Dispose()
{
await TicketService.Chat.Stop();
}
}