fish-station/Content.Client/_Sunrise/MentorHelp/MentorHelpControl.xaml.cs
Copilot 878167c937
Вернуть команду adminwho в общий доступ и реализовать UI с правильным client-server взаимодействием (#2918)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: VigersRay <60344369+VigersRay@users.noreply.github.com>
Co-authored-by: Vigers Ray <vigersray@gmail.com>
2025-09-09 22:06:57 +03:00

434 lines
16 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Linq;
using Content.Client.Administration.Systems;
using Content.Client.Administration.UI.Bwoink;
using Content.Shared._Sunrise.MentorHelp;
using Content.Shared.Administration;
using Content.Shared.Database;
using Robust.Client.AutoGenerated;
using Robust.Client.State;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Network;
using Robust.Shared.Timing;
namespace Content.Client._Sunrise.MentorHelp
{
/// <summary>
/// Main control for mentor help interface
/// </summary>
[GenerateTypedNameReferences]
public sealed partial class MentorHelpControl : Control
{
[Dependency] private readonly IUserInterfaceManager _ui = default!;
[Dependency] private readonly IGameTiming _timing = default!;
private MentorHelpSystem? _mentorHelpSystem;
private NetUserId _ownerUserId;
private bool _hasMentorPermissions;
private List<MentorHelpTicketData> _tickets = new();
private MentorHelpTicketData? _selectedTicket;
private Dictionary<int, List<MentorHelpMessageData>> _ticketMessages = new();
private readonly Dictionary<int, TicketEntryControl> _openTicketControls = new();
private readonly Dictionary<int, TicketEntryControl> _closedTicketControls = new();
private enum ViewState
{
TicketsList,
TicketView
}
private MentorHelpNewTicketDialog? _newTicketDialog;
private int? _pendingOpenTicketId;
public MentorHelpControl()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
// Wire up button events
NewTicketButton.OnPressed += _ => OpenNewTicketDialog();
StatisticsButton.OnPressed += _ => _ui.GetUIController<MentorHelpStatisticsUIController>().ToggleStatistics();
BackToListButton.OnPressed += _ => SwitchState(ViewState.TicketsList);
// Wire up ticket action buttons
ClaimButton.OnPressed += _ => ClaimTicket();
UnassignButton.OnPressed += _ => UnassignTicket();
CloseTicketButton.OnPressed += _ => CloseTicket();
// Handle enter key in reply input
ReplyInput.OnTextEntered += _ => SendReply();
// Setup tab container like in AdminMenuWindow
TicketsTabContainer.SetTabTitle(0, Loc.GetString("mentor-help-tab-open"));
TicketsTabContainer.SetTabTitle(1, Loc.GetString("mentor-help-tab-closed"));
// Handle tab changes to load closed tickets when needed
TicketsTabContainer.OnTabChanged += OnTabChanged;
AdminWhoButton.OnPressed += _ =>
{
var ctrl = _ui.GetUIController<AdminWhoUIController>();
ctrl.Toggle();
};
// Set initial state
SwitchState(ViewState.TicketsList);
}
/// <summary>
/// Попытаться открыть тикет с указанным id. Если тикет ещё не загружен, запросит список тикетов
/// и откроет тикет когда список придёт.
/// </summary>
public void TryOpenTicket(int ticketId)
{
// If we already have the ticket loaded, select it immediately
var ticket = _tickets.FirstOrDefault(t => t.Id == ticketId);
if (ticket != null)
{
OnTicketSelected(ticket);
return;
}
// Otherwise, remember to open when the list arrives and request tickets from server
_pendingOpenTicketId = ticketId;
_mentorHelpSystem?.RequestTickets(onlyMine: !_hasMentorPermissions);
}
public void Initialize(MentorHelpSystem? mentorHelpSystem, NetUserId ownerUserId, bool hasMentorPermissions)
{
_mentorHelpSystem = mentorHelpSystem;
_ownerUserId = ownerUserId;
_hasMentorPermissions = hasMentorPermissions;
// Show/hide buttons based on permissions
StatisticsButton.Visible = _hasMentorPermissions;
UpdateTicketActionButtons();
}
private void OnTabChanged(int tabIndex)
{
// When switching to closed tickets tab, request all tickets including closed ones
if (tabIndex == 1 && _hasMentorPermissions)
{
_mentorHelpSystem?.RequestTickets(onlyMine: false);
}
else if (tabIndex == 1 && !_hasMentorPermissions)
{
_mentorHelpSystem?.RequestTickets(onlyMine: true);
}
}
// State switching like in LobbyGui
private void SwitchState(ViewState state)
{
DefaultState.Visible = false;
TicketViewState.Visible = false;
BackToListButton.Visible = false;
switch (state)
{
case ViewState.TicketsList:
DefaultState.Visible = true;
_selectedTicket = null;
break;
case ViewState.TicketView:
TicketViewState.Visible = true;
BackToListButton.Visible = true;
UpdateTicketHeader();
UpdateTicketActionButtons();
break;
}
}
public void UpdateTicketsList(List<MentorHelpTicketData> tickets)
{
_tickets = tickets;
RefreshTicketsList();
// If we were asked to open a specific ticket once the list arrives, do it now
if (_pendingOpenTicketId.HasValue)
{
var id = _pendingOpenTicketId.Value;
_pendingOpenTicketId = null;
var ticket = _tickets.FirstOrDefault(t => t.Id == id);
if (ticket != null)
{
OnTicketSelected(ticket);
}
}
}
// Refresh tickets like in ContributorsTop
private void RefreshTicketsList()
{
var openTickets = _tickets.Where(t => t.Status != MentorHelpTicketStatus.Closed).ToList();
var closedTickets = _tickets.Where(t => t.Status == MentorHelpTicketStatus.Closed).ToList();
// Filter for players to show only their tickets
if (!_hasMentorPermissions)
{
openTickets = openTickets.Where(t => t.PlayerId == _ownerUserId).ToList();
closedTickets = closedTickets.Where(t => t.PlayerId == _ownerUserId).ToList();
}
// Update open tickets
RefreshTicketTab(openTickets, _openTicketControls, OpenTicketsList);
// Update closed tickets
RefreshTicketTab(closedTickets, _closedTicketControls, ClosedTicketsList);
}
private void RefreshTicketTab(List<MentorHelpTicketData> tickets, Dictionary<int, TicketEntryControl> controls, BoxContainer container)
{
var sortedTickets = tickets.OrderByDescending(t => t.UpdatedAt).ToList();
// Remove tickets that no longer exist
var toRemove = controls.Keys.Where(id => !sortedTickets.Any(t => t.Id == id)).ToList();
foreach (var id in toRemove)
{
if (controls.TryGetValue(id, out var control))
{
container.RemoveChild(control);
controls.Remove(id);
}
}
// Add or update existing tickets
foreach (var ticket in sortedTickets)
{
if (controls.TryGetValue(ticket.Id, out var existingControl))
{
existingControl.UpdateData(ticket);
}
else
{
var control = new TicketEntryControl();
control.UpdateData(ticket);
control.OnTicketSelected += OnTicketSelected;
controls[ticket.Id] = control;
container.AddChild(control);
}
}
}
private void OnTicketSelected(MentorHelpTicketData ticket)
{
_selectedTicket = ticket;
SwitchState(ViewState.TicketView);
// Clear previous messages first
MessagesContainer.RemoveAllChildren();
// Request messages for this ticket
_mentorHelpSystem?.RequestTicketMessages(ticket.Id);
}
public void UpdateTicketMessages(int ticketId, List<MentorHelpMessageData> messages)
{
_ticketMessages[ticketId] = messages;
if (_selectedTicket?.Id == ticketId && TicketViewState.Visible)
{
DisplayTicketMessages(messages);
}
}
public void UpdateTicket(MentorHelpTicketData ticket)
{
// Update ticket in our list
var index = _tickets.FindIndex(t => t.Id == ticket.Id);
if (index >= 0)
{
_tickets[index] = ticket;
}
else
{
_tickets.Add(ticket);
}
// Update selected ticket if it's the one that changed
if (_selectedTicket?.Id == ticket.Id)
{
_selectedTicket = ticket;
if (TicketViewState.Visible)
{
UpdateTicketHeader();
UpdateTicketActionButtons();
}
}
// If admin closed the ticket while player was viewing it, return to list
if (ticket.Status == MentorHelpTicketStatus.Closed &&
_selectedTicket?.Id == ticket.Id &&
!_hasMentorPermissions &&
TicketViewState.Visible)
{
SwitchState(ViewState.TicketsList);
}
// Refresh the list to show updated status
RefreshTicketsList();
}
private void UpdateTicketHeader()
{
if (_selectedTicket == null)
return;
// Отдельные метки для ID и темы (чтобы занимали меньше места и были выровнены)
TicketIdLabel.Text = $"#{_selectedTicket.Id}";
TicketSubjectLabel.Text = _selectedTicket.Subject;
// Компактная строка: статус | назначен | создано
TicketStatus.Text = Loc.GetString("mentor-help-status-label", ("status", GetStatusText(_selectedTicket.Status)));
TicketAssigned.Text = Loc.GetString("mentor-help-assigned-label",
("assigned", _selectedTicket.AssignedToName ?? Loc.GetString("mentor-help-unassigned")));
TicketCreated.Text = Loc.GetString("mentor-help-created-label",
("created", _selectedTicket.CreatedAt.ToString("dd.MM.yyyy HH:mm")));
}
private void UpdateTicketActionButtons()
{
if (_selectedTicket == null)
{
ReplyPanel.Visible = false;
return;
}
var canReply = _selectedTicket.Status != MentorHelpTicketStatus.Closed;
var isAssignedToMe = _selectedTicket.AssignedToUserId == _ownerUserId;
var isOpen = _selectedTicket.Status != MentorHelpTicketStatus.Closed;
// Hide reply panel for closed tickets
ReplyPanel.Visible = canReply;
if (_hasMentorPermissions)
{
ClaimButton.Visible = isOpen && !isAssignedToMe;
UnassignButton.Visible = isOpen && isAssignedToMe;
CloseTicketButton.Visible = isOpen;
}
else
{
ClaimButton.Visible = false;
UnassignButton.Visible = false;
CloseTicketButton.Visible = isOpen; // Players can close their own tickets
}
}
private string GetStatusText(MentorHelpTicketStatus status)
{
return status switch
{
MentorHelpTicketStatus.Open => Loc.GetString("mentor-help-status-open"),
MentorHelpTicketStatus.Assigned => Loc.GetString("mentor-help-status-assigned"),
MentorHelpTicketStatus.AwaitingResponse => Loc.GetString("mentor-help-status-awaiting"),
MentorHelpTicketStatus.Closed => Loc.GetString("mentor-help-status-closed"),
_ => Loc.GetString("mentor-help-status-unknown")
};
}
private void DisplayTicketMessages(List<MentorHelpMessageData> messages)
{
MessagesContainer.RemoveAllChildren();
DateTime? lastDate = null;
foreach (var message in messages.OrderBy(m => m.SentAt))
{
// Ensure we show a date header when the day changes
var sentAt = message.SentAt;
var sentDate = sentAt.Date;
if (lastDate == null || lastDate.Value != sentDate)
{
lastDate = sentDate;
var dateLabel = new RichTextLabel
{
Text = $"[center=\"{sentAt:dd.MM.yyyy}\"]",
HorizontalExpand = true
};
MessagesContainer.AddChild(dateLabel);
}
var messageBox = new PanelContainer
{
StyleClasses = { "PanelColorMedium" },
HorizontalExpand = true,
Margin = new Thickness(0, 2)
};
var vbox = new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Vertical,
HorizontalExpand = true
};
// Format: [HH:mm] Author: message
var content = new RichTextLabel
{
Text = $"[bold]{sentAt:HH:mm}[/bold] {message.FormattedSender}: {message.Message}",
HorizontalExpand = true
};
vbox.AddChild(content);
messageBox.AddChild(vbox);
MessagesContainer.AddChild(messageBox);
}
}
private void OpenNewTicketDialog()
{
if (_newTicketDialog != null)
{
_newTicketDialog.Close();
_newTicketDialog = null;
return;
}
_newTicketDialog = new MentorHelpNewTicketDialog();
_newTicketDialog.OnTicketCreated += (subject, message) =>
{
_newTicketDialog?.Close();
_mentorHelpSystem?.CreateTicket(subject, message);
};
_newTicketDialog.OnClose += () => _newTicketDialog = null;
_newTicketDialog.OpenCentered();
}
private void SendReply()
{
if (_selectedTicket == null || string.IsNullOrWhiteSpace(ReplyInput.Text))
return;
_mentorHelpSystem?.ReplyToTicket(_selectedTicket.Id, ReplyInput.Text.Trim());
ReplyInput.Text = string.Empty;
}
private void ClaimTicket()
{
if (_selectedTicket == null)
return;
_mentorHelpSystem?.ClaimTicket(_selectedTicket.Id);
}
private void UnassignTicket()
{
if (_selectedTicket == null)
return;
_mentorHelpSystem?.UnassignTicket(_selectedTicket.Id);
}
private void CloseTicket()
{
if (_selectedTicket == null)
return;
_mentorHelpSystem?.CloseTicket(_selectedTicket.Id);
}
}
}