Static catalog and view state for the police MDT UI. Defines the tab and app records, the tab lists for the desktop, tablet and car shells, and an MdtState class holding per-window UI state (search boxes, open forms, filters, pagination) plus a debounce helper for host queries and a hash used to detect state changes.
namespace HorizonRP;
/// <summary>
/// Un écran du MDT (Mobile Data Terminal de l'UCPD) : son identifiant, son nom, son icône, sa
/// phrase d'aide. Les trois habillages (poste du commissariat, tablette, ordinateur de bord)
/// montrent les mêmes écrans, dessinés par <see cref="MdtView"/>.
/// </summary>
public sealed record MdtTab( string Id, string Name, string Icon, string Hint );
/// <summary>
/// Une application du poste du commissariat : une fenêtre, un ou plusieurs écrans du MDT (l'appli
/// « MDT » réunit l'accueil, les citoyens, les véhicules et le central 911 dans ses onglets).
/// </summary>
public sealed record MdtApp( string Id, string Name, string Icon, string Color, string[] Tabs );
/// <summary>Les écrans et les applications du MDT, lus par les trois habillages.</summary>
public static class MdtCatalog
{
// Des propriétés, pas des champs : un tableau statique garderait son ancienne valeur après un
// rechargement à chaud dans l'éditeur.
public static MdtTab[] Tabs => new MdtTab[]
{
new( "accueil", "Accueil", "dashboard", "Votre service et les unités en un coup d'œil" ),
new( "appels", "Appels 911", "emergency", "Les appels d'urgence en cours" ),
new( "lecteur", "Lecteur de plaques", "document_scanner", "Lecture automatique des plaques, avant et arrière" ),
new( "citoyens", "Citoyens", "badge", "Recherche, fiche, casier, amendes et rapports" ),
new( "casiers", "Casiers et rapports", "folder_shared", "Le casier judiciaire d'un citoyen, et les rapports à verser" ),
new( "vehicules", "Véhicules", "directions_car", "Interroger une plaque, signaler un véhicule" ),
new( "avis", "Avis de recherche", "person_search", "Les personnes recherchées par l'UCPD" ),
new( "historique", "Historique 911", "history", "Tous les appels reçus par le central, consultables" ),
new( "scelles", "Scellés", "inventory_2", "Le registre des pièces saisies sous scellé" ),
new( "code", "Code pénal", "gavel", "Infractions, amendes et peines d'Union City" ),
new( "effectifs", "Effectifs", "groups", "Les agents de l'UCPD, par grade" ),
};
public static MdtTab Tab( string id ) => Tabs.FirstOrDefault( x => x.Id == id ) ?? Tabs[0];
/// <summary>La tablette à pied : tout sauf le lecteur de plaques (il est monté sur les voitures) et l'appli des casiers (la fiche du citoyen les a).</summary>
public static string[] TabletTabs => new[] { "accueil", "appels", "citoyens", "vehicules", "avis", "historique", "scelles", "code", "effectifs" };
/// <summary>L'ordinateur de bord : le lecteur de plaques en plus, les appels en tête.</summary>
public static string[] CarTabs => new[] { "accueil", "appels", "lecteur", "citoyens", "vehicules", "avis", "historique", "scelles", "code", "effectifs" };
/// <summary>Le poste du commissariat : une appli par fenêtre.</summary>
public static MdtApp[] Apps => new MdtApp[]
{
new( "mdt", "MDT", "local_police", "#2f6bff", new[] { "accueil", "citoyens", "vehicules", "appels" } ),
new( "casiers", "Casiers et rapports", "folder_shared", "#7c3aed", new[] { "casiers" } ),
new( "avis", "Avis de recherche", "person_search", "#dc2626", new[] { "avis" } ),
new( "historique", "Historique 911", "history", "#ea580c", new[] { "historique" } ),
new( "scelles", "Scellés", "inventory_2", "#0d9488", new[] { "scelles" } ),
new( "code", "Code pénal", "gavel", "#ca8a04", new[] { "code" } ),
new( "effectifs", "Effectifs", "groups", "#0891b2", new[] { "effectifs" } ),
};
public static MdtApp App( string id ) => Apps.FirstOrDefault( x => x.Id == id );
/// <summary>L'appli du poste qui montre cet écran.</summary>
public static MdtApp AppOfTab( string tab ) => Apps.FirstOrDefault( x => x.Tabs.Contains( tab ) ) ?? Apps[0];
}
/// <summary>
/// Ce qu'un écran du MDT a en cours (recherche tapée, fiche ouverte, formulaire, filtres) : un par
/// fenêtre sur le poste du commissariat, un seul sur la tablette et l'ordinateur de bord. Rien
/// n'est décidé ici : c'est la mémoire de l'affichage.
/// </summary>
public sealed class MdtState
{
/// <summary>« pc » (poste du commissariat), « tablette » (à pied) ou « bord » (dans un véhicule).</summary>
public string Shell { get; set; } = "tablette";
public string Tab { get; set; } = "accueil";
/// <summary>Monte quand l'habillage s'ouvre : l'écran reprend des nouvelles du fichier.</summary>
public int RefreshSerial { get; set; }
/// <summary>L'action destructrice qui attend un second clic (« avis:12 », « ligne:4 »…).</summary>
public string Confirm { get; set; }
// Citoyens et casiers
public string SearchQuery { get; set; } = "";
public string SearchedFor { get; set; }
public ulong FileId { get; set; }
/// <summary>Le formulaire ouvert sur la fiche : « rapport », « avertissement », « avis », ou null.</summary>
public string Mode { get; set; }
public string DraftTitle { get; set; } = "";
public string DraftNotes { get; set; } = "";
public string DraftLocation { get; set; } = "";
public bool DraftDangerous { get; set; }
public List<string> DraftCharges { get; } = new();
public string ChargeQuery { get; set; } = "";
// Véhicules et lecteur de plaques
public string PlateQuery { get; set; } = "";
public string PlateFor { get; set; }
public string FlagDraft { get; set; } = "";
// Avis de recherche
public long WarrantId { get; set; }
// Code pénal
public string PenalQuery { get; set; } = "";
public string PenalCategory { get; set; }
// Effectifs
public string RosterEdit { get; set; }
public int RosterGrade { get; set; }
public string RosterCallsign { get; set; } = "";
// Appels 911
public long CallId { get; set; }
// Historique 911
public string HistService { get; set; } = "police";
public string HistStatus { get; set; } = "";
public string HistQuery { get; set; } = "";
public string HistApplied { get; set; } = "";
public int HistPage { get; set; } = 1;
public long HistId { get; set; }
// Scellés
public string EvStatus { get; set; } = "stocke";
public string EvQuery { get; set; } = "";
public string EvApplied { get; set; } = "";
public int EvPage { get; set; } = 1;
public long EvId { get; set; }
public string EvAction { get; set; }
public string EvNotes { get; set; } = "";
/// <summary>Les questions déjà posées à l'hôte, et quand : une page manquante se redemande, sans rafale.</summary>
public Dictionary<string, RealTimeSince> Asked { get; } = new();
/// <summary>Vrai si <paramref name="key"/> n'a pas été demandée depuis <paramref name="seconds"/> s (et la note demandée).</summary>
public bool ShouldAsk( string key, float seconds = 4f )
{
if ( Asked.TryGetValue( key, out var since ) && since < seconds )
return false;
Asked[key] = 0;
return true;
}
public int Hash() => HashCode.Combine(
HashCode.Combine( Shell, Tab, RefreshSerial, Confirm, SearchQuery, SearchedFor, FileId, Mode ),
HashCode.Combine( DraftTitle, DraftNotes, DraftLocation, DraftDangerous, DraftCharges.Count, ChargeQuery ),
HashCode.Combine( PlateQuery, PlateFor, FlagDraft, WarrantId, PenalQuery, PenalCategory ),
HashCode.Combine( RosterEdit, RosterGrade, RosterCallsign, CallId ),
HashCode.Combine( HistService, HistStatus, HistQuery, HistApplied, HistPage, HistId ),
HashCode.Combine( EvStatus, EvQuery, EvApplied, EvPage, EvId, EvAction, EvNotes ) );
}