Criado seção de histórico

This commit is contained in:
Diego Freitas 2026-03-19 23:03:07 -03:00
parent 523d6924c3
commit 51c9f51f4f
22 changed files with 1275 additions and 52 deletions

View File

@ -403,7 +403,7 @@ namespace AgroBase.Models.Modules
RPM_SP = RpmCorrigido; RPM_SP = RpmCorrigido;
FatorCompensacaoCurvasAnterior = FatorCompensacaoCurvas; FatorCompensacaoCurvasAnterior = FatorCompensacaoCurvas;
Variaveis.OperacaoEmAndamento.SimulacaoRpmControle = Variaveis.OperacaoEmAndamento.Controle.PercentualVelocidadeSP; Variaveis.OperacaoEmAndamento.SimulacaoRpmControle = _Controle.PercentualVelocidadeSP;
//Console.WriteLine($"RPM Atualizado para {Variaveis.OperacaoEmAndamento.SimulacaoRpmControle}"); //Console.WriteLine($"RPM Atualizado para {Variaveis.OperacaoEmAndamento.SimulacaoRpmControle}");
switch (DirecaoAtual) switch (DirecaoAtual)

View File

@ -3,6 +3,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Net;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Net.Sockets; using System.Net.Sockets;
using System.Threading; using System.Threading;
@ -615,4 +616,132 @@ public class EthernetService
public class StatusRedeInfo
{
public bool AdaptadorEncontrado { get; set; }
public bool AdaptadorOperacional { get; set; }
public bool CaboConectado { get; set; }
public bool IpValido { get; set; }
public bool GatewayValido { get; set; }
public string NomeInterface { get; set; } = "";
public string Ip { get; set; } = "";
public string Mascara { get; set; } = "";
public string Gateway { get; set; } = "";
public bool RedeOk { get; set; }
public string Detalhes { get; set; } = "";
}
public static StatusRedeInfo ObterStatusRede(string nomeInterface = null, bool exigirGateway = false)
{
var info = new StatusRedeInfo();
try
{
var interfaces = NetworkInterface.GetAllNetworkInterfaces()
.Where(ni =>
ni.NetworkInterfaceType != NetworkInterfaceType.Loopback &&
ni.NetworkInterfaceType != NetworkInterfaceType.Tunnel &&
ni.Description.IndexOf("virtual", StringComparison.OrdinalIgnoreCase) < 0 &&
ni.Name.IndexOf("virtual", StringComparison.OrdinalIgnoreCase) < 0)
.ToList();
NetworkInterface niSelecionada = null;
if (!string.IsNullOrWhiteSpace(nomeInterface))
{
niSelecionada = interfaces.FirstOrDefault(ni =>
ni.Name.Equals(nomeInterface, StringComparison.OrdinalIgnoreCase) ||
ni.Description.Equals(nomeInterface, StringComparison.OrdinalIgnoreCase) ||
ni.Name.IndexOf(nomeInterface, StringComparison.OrdinalIgnoreCase) >= 0 ||
ni.Description.IndexOf(nomeInterface, StringComparison.OrdinalIgnoreCase) >= 0);
}
else
{
niSelecionada = interfaces
.Where(ni =>
ni.NetworkInterfaceType == NetworkInterfaceType.Ethernet ||
ni.NetworkInterfaceType == NetworkInterfaceType.GigabitEthernet ||
ni.Description.IndexOf("Ethernet", StringComparison.OrdinalIgnoreCase) >= 0 ||
ni.Name.IndexOf("Ethernet", StringComparison.OrdinalIgnoreCase) >= 0)
.OrderByDescending(ni => ni.OperationalStatus == OperationalStatus.Up)
.FirstOrDefault();
}
if (niSelecionada == null)
{
info.Detalhes = "Nenhum adaptador de rede compatível encontrado.";
return info;
}
info.AdaptadorEncontrado = true;
info.NomeInterface = niSelecionada.Name;
info.AdaptadorOperacional = niSelecionada.OperationalStatus == OperationalStatus.Up;
info.CaboConectado = info.AdaptadorOperacional;
var props = niSelecionada.GetIPProperties();
var ipv4 = props.UnicastAddresses
.FirstOrDefault(a => a.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork);
var gateway = props.GatewayAddresses
.FirstOrDefault(g => g.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork);
if (ipv4 != null)
{
info.Ip = ipv4.Address.ToString();
info.Mascara = ipv4.IPv4Mask?.ToString() ?? "";
info.IpValido = IpEhValido(info.Ip);
}
if (gateway != null)
{
info.Gateway = gateway.Address.ToString();
info.GatewayValido = IpEhValido(info.Gateway);
}
info.RedeOk =
info.AdaptadorEncontrado &&
info.CaboConectado &&
info.IpValido &&
(!exigirGateway || info.GatewayValido);
info.Detalhes =
$"{info.NomeInterface}\n" +
$"Cabo: {(info.CaboConectado ? "Conectado" : "Desconectado")}\n" +
$"IP: {(!string.IsNullOrWhiteSpace(info.Ip) ? info.Ip : "Não definido")}\n" +
$"Máscara: {(!string.IsNullOrWhiteSpace(info.Mascara) ? info.Mascara : "Não definida")}\n" +
$"Gateway: {(!string.IsNullOrWhiteSpace(info.Gateway) ? info.Gateway : "Não definido")}\n" +
$"Rede OK: {(info.RedeOk ? "Sim" : "Não")}";
}
catch (Exception ex)
{
info.RedeOk = false;
info.Detalhes = "Erro ao obter status da rede: " + ex.Message;
}
return info;
}
private static bool IpEhValido(string ip)
{
if (string.IsNullOrWhiteSpace(ip))
return false;
if (!IPAddress.TryParse(ip, out var endereco))
return false;
if (IPAddress.Any.Equals(endereco))
return false;
// APIPA: geralmente indica ausência de DHCP/conectividade útil
if (ip.StartsWith("169.254."))
return false;
return true;
}
} }

View File

@ -16,7 +16,7 @@
<ComboBox x:Name="CmbBaseMap" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,10,10,0" Width="189" SelectionChanged="OnBaseMapChanged"/> <ComboBox x:Name="CmbBaseMap" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,10,10,0" Width="189" SelectionChanged="OnBaseMapChanged"/>
<Button Content="Carregar Mapa" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0,0,10,10" Padding="8,4" Background="#2ECC71" Foreground="White" FontWeight="Bold" Click="OnLoadMapClicked"/> <Button Content="Carregar Mapa" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0,0,10,10" Padding="8,4" Background="#2ECC71" Foreground="White" FontWeight="Bold" Click="OnLoadMapClicked" Visibility="Collapsed"/>
<!--<Button Content="Centralizar" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0,0,10,10" Padding="8,4" Background="#2ECC71" Foreground="White" FontWeight="Bold" Click="OnCenterAreaClicked"/>--> <!--<Button Content="Centralizar" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0,0,10,10" Padding="8,4" Background="#2ECC71" Foreground="White" FontWeight="Bold" Click="OnCenterAreaClicked"/>-->
</Grid> </Grid>
</UserControl> </UserControl>

View File

@ -930,6 +930,14 @@ namespace OperationControl.Controls
} }
} }
public void LimparRastrosRover(string? roverId, bool limparPredicao = true)
{
if (string.IsNullOrWhiteSpace(roverId))
return;
markers?.ClearMarkerTrail(roverId, limparPredicao);
}
} }
public class MapMarkerManager public class MapMarkerManager
@ -1089,6 +1097,19 @@ namespace OperationControl.Controls
UpdateFeatures(); UpdateFeatures();
} }
public void ClearMarkerTrail(string id, bool clearPrediction = true)
{
if (string.IsNullOrWhiteSpace(id)) return;
var marker = _features.FirstOrDefault(x => x.Id == id);
if (marker == null) return;
marker.ClearTrajectory(clearPrediction);
_map.Refresh();
}
public class MapMarkerDictionaryModel public class MapMarkerDictionaryModel
{ {
public void CreateTrajectory(MapControl _map) public void CreateTrajectory(MapControl _map)
@ -1344,6 +1365,34 @@ namespace OperationControl.Controls
} }
public void ClearTrajectory(bool clearPrediction = true)
{
_trajFeatures.Clear();
if (_trajLayer != null)
{
_trajLayer.DataSource = new MemoryProvider(_trajFeatures);
_trajLayer.DataHasChanged();
}
if (clearPrediction)
{
_predFeatures.Clear();
if (_predLayer != null)
{
_predLayer.DataSource = new MemoryProvider(_predFeatures);
_predLayer.DataHasChanged();
}
}
// Reseta a posição base do rastro para a posição atual do rover,
// evitando que ao voltar a andar ele desenhe uma linha gigante do passado.
var (x, y) = Mapsui.Projections.SphericalMercator.FromLonLat(Lon, Lat);
Position = new Coordinate(x, y);
}
#region TRAJETORIA #region TRAJETORIA
private MemoryProvider _trajProvider; private MemoryProvider _trajProvider;

View File

@ -30,7 +30,8 @@ namespace OperationControl.Models
? $"{Modulo} ({Mod_ID})" ? $"{Modulo} ({Mod_ID})"
: Modulo.ToString(); : Modulo.ToString();
return $"[{Rover_ID}] {mod} — {ResumoSeveridade}"; //return $"[{Rover_ID}] {mod} — {ResumoSeveridade}";
return $"{mod} — {ResumoSeveridade}";
} }
} }

View File

@ -204,7 +204,7 @@ namespace OperationControl.Models
{ {
System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() => System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{ {
(Dock?.DataContext as DockWindowViewModel)?.AdicionarAlerta(roverId, modulo, severidade, mensagem, modId); Dock?._vm?.AdicionarAlerta(roverId, modulo, severidade, mensagem, modId);
})); }));
} }
@ -212,7 +212,7 @@ namespace OperationControl.Models
{ {
System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() => System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{ {
(Dock?.DataContext as DockWindowViewModel)?.RemoverAlerta(roverId, modulo, severidade, modId); Dock?._vm?.RemoverAlerta(roverId, modulo, severidade, modId);
})); }));
} }

View File

@ -5,6 +5,7 @@ using OperationControl.Services;
using AgroMonitor; using AgroMonitor;
using Application = System.Windows.Application; using Application = System.Windows.Application;
using AgroBase.Models.Operacoes; using AgroBase.Models.Operacoes;
using OperationControl.Windows;
namespace OperationControl.Models namespace OperationControl.Models
{ {
@ -15,6 +16,8 @@ namespace OperationControl.Models
public static UdpReliableChannel UdpChannel; public static UdpReliableChannel UdpChannel;
public static ManualControlSender ControlSenderDir; public static ManualControlSender ControlSenderDir;
public static ManualControlSender ControlSenderMov; public static ManualControlSender ControlSenderMov;
public static AppShell? Shell => ((App)Application.Current)?.Shell;
public static DockWindow? Dock => Shell?.Dock;
public static void MostrarLog(string message) public static void MostrarLog(string message)
{ {
@ -163,11 +166,11 @@ namespace OperationControl.Models
rover.UltimoContato = DateTime.Now; rover.UltimoContato = DateTime.Now;
} }
} }
((App)Application.Current).Shell.Main?.AtualizarDadosTela_Telemetria(device_id); //((App)Application.Current).Shell.Main?.AtualizarDadosTela_Telemetria(device_id);
Application.Current.Dispatcher.BeginInvoke(new Action(() => Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{ {
((App)Application.Current).Shell.Dock?._vm?.AtualizarDadosTela(rover); Variaveis.Dock?._vm?.AtualizarDadosTela(rover);
})); }));
} }
catch (Exception ex) catch (Exception ex)
@ -195,9 +198,9 @@ namespace OperationControl.Models
RoversNaRede[index] = obj; RoversNaRede[index] = obj;
} }
} }
((App)Application.Current).Shell.Main?.AtualizarDadosTela_ParametrosOperacao(obj.Controle); //((App)Application.Current).Shell.Main?.AtualizarDadosTela_ParametrosOperacao(obj.Controle);
((App)Application.Current).Shell.Main?.MAP?.CarregarDadosMapa(obj.Mapa, obj.RuasPercorrer); //((App)Application.Current).Shell.Main?.MAP?.CarregarDadosMapa(obj.Mapa, obj.RuasPercorrer);
((App)Application.Current).Shell.Dock?._vm?.AtualizarParametrosRover(obj); Variaveis.Dock?._vm?.AtualizarParametrosRover(obj);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -211,7 +214,7 @@ namespace OperationControl.Models
{ {
dispositivos_ids = dispositivos_ids.Union(RoversNaRede.Select(x => x.RoverId).ToList()).ToList(); dispositivos_ids = dispositivos_ids.Union(RoversNaRede.Select(x => x.RoverId).ToList()).ToList();
} }
((App)Application.Current).Shell.Main?.AtualizarListaDispositivos(dispositivos_ids); //((App)Application.Current).Shell.Main?.AtualizarListaDispositivos(dispositivos_ids);
RequisitarParametrosOperacao(); RequisitarParametrosOperacao();
} }
@ -244,24 +247,24 @@ namespace OperationControl.Models
rover.DadosLeitura.Momento = DateTime.Now; rover.DadosLeitura.Momento = DateTime.Now;
if (rover.DadosLeitura.Operacao == null) rover.DadosLeitura.Operacao = new OperacaoParametrosDadosOperacaoModel(); if (rover.DadosLeitura.Operacao == null) rover.DadosLeitura.Operacao = new OperacaoParametrosDadosOperacaoModel();
rover.DadosLeitura.Operacao.Status = AgroBase.Models.Enums.StatusOperacao.Erro; rover.DadosLeitura.Operacao.Status = AgroBase.Models.Enums.StatusOperacao.Erro;
((App)Application.Current)?.Shell?.Main?.AtualizarDadosTela_Telemetria(rover.RoverId); //((App)Application.Current)?.Shell?.Main?.AtualizarDadosTela_Telemetria(rover.RoverId);
((App)Application.Current)?.Shell?.Main?.AdicionarAlerta(rover.RoverId, AgroBase.Models.Enums.T_Code.Ipb, SeveridadeAlerta.Critical, "Perda de comunicação com o equipamento!"); //((App)Application.Current)?.Shell?.Main?.AdicionarAlerta(rover.RoverId, AgroBase.Models.Enums.T_Code.Ipb, SeveridadeAlerta.Critical, "Perda de comunicação com o equipamento!");
((App)Application.Current)?.Shell?.AdicionarAlertaDock(rover.RoverId, AgroBase.Models.Enums.T_Code.Ipb, SeveridadeAlerta.Critical, "Perda de comunicação com o equipamento!"); Variaveis.Shell?.AdicionarAlertaDock(rover.RoverId, AgroBase.Models.Enums.T_Code.Ipb, SeveridadeAlerta.Critical, "Perda de comunicação com o equipamento!");
((App)Application.Current)?.Shell?.Dock?._vm?._viewOperacaoCenter?.Mapa?.markers?.UpdateMarkerInfo(rover.RoverId, status: AgroBase.Models.Enums.StatusOperacao.Erro); Variaveis.Dock?._vm?._viewOperacaoCenter?.Mapa?.markers?.UpdateMarkerInfo(rover.RoverId, status: AgroBase.Models.Enums.StatusOperacao.Erro);
} }
else else
{ {
((App)Application.Current)?.Shell?.Main?.RemoverAlerta(rover.RoverId, AgroBase.Models.Enums.T_Code.Ipb); //((App)Application.Current)?.Shell?.Main?.RemoverAlerta(rover.RoverId, AgroBase.Models.Enums.T_Code.Ipb);
((App)Application.Current)?.Shell?.RemoverAlertaDock(rover.RoverId, AgroBase.Models.Enums.T_Code.Ipb); Variaveis.Shell?.RemoverAlertaDock(rover.RoverId, AgroBase.Models.Enums.T_Code.Ipb);
} }
((App)Application.Current)?.Shell?.Dock?._vm?.AtualizarBarraSuperior(rover); Variaveis.Shell?.Dock?._vm?.AtualizarBarraSuperior(rover);
} }
Application.Current?.Dispatcher?.BeginInvoke(new Action(() => Application.Current?.Dispatcher?.BeginInvoke(new Action(() =>
{ {
((App)Application.Current)?.Shell?.Dock?._vm?.AtualizarListaRovers(rovers_atual); Variaveis.Shell?.Dock?._vm?.AtualizarListaRovers(rovers_atual);
})); }));
} }
finally finally

View File

@ -52,6 +52,9 @@
<Compile Update="Views\Operacao\Monitoramento\DiagnosticoView.xaml.cs"> <Compile Update="Views\Operacao\Monitoramento\DiagnosticoView.xaml.cs">
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
<Compile Update="Views\Operacao\Monitoramento\HistoricoView.xaml.cs">
<SubType>Code</SubType>
</Compile>
<Compile Update="Views\Operacao\Monitoramento\MonitoramentoView.xaml.cs"> <Compile Update="Views\Operacao\Monitoramento\MonitoramentoView.xaml.cs">
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
@ -138,6 +141,9 @@
<Page Update="Views\Operacao\Monitoramento\DiagnosticoView.xaml"> <Page Update="Views\Operacao\Monitoramento\DiagnosticoView.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
</Page> </Page>
<Page Update="Views\Operacao\Monitoramento\HistoricoView.xaml">
<SubType>Designer</SubType>
</Page>
<Page Update="Views\Operacao\Monitoramento\MonitoramentoView.xaml"> <Page Update="Views\Operacao\Monitoramento\MonitoramentoView.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
</Page> </Page>

View File

@ -368,8 +368,7 @@ namespace OperationControl.Services
} }
Models.Variaveis.MostrarLog(mensagem); Models.Variaveis.MostrarLog(mensagem);
((App)Application.Current)?.Shell?.Dock?._vm?._viewPreparacaoMapaBottom?._vm?.FinalizarFixacao(sucesso, mensagem); Models.Variaveis.Dock?._vm?.FinalizarFixacaoBase(sucesso, mensagem);
((App)Application.Current)?.Shell?.Dock?._vm?.FinalizarFixacaoBase(sucesso, mensagem);
} }
private async Task ConfigurarModuloBase(string porta_usb = "com3", string porta_saida = "com2", int tempo_fixacao = 60) private async Task ConfigurarModuloBase(string porta_usb = "com3", string porta_saida = "com2", int tempo_fixacao = 60)
@ -1223,9 +1222,9 @@ namespace OperationControl.Services
{ {
try try
{ {
((App)Application.Current).Shell.Main?.AtualizarDadosTela_Telemetria(VariaveisControleOperacao.BaseMarkerID); //((App)Application.Current).Shell.Main?.AtualizarDadosTela_Telemetria(VariaveisControleOperacao.BaseMarkerID);
((App)Application.Current).Shell.Dock?._vm?.AtualizarDadosGnss(UltimaLeitura); Models.Variaveis.Dock?._vm?.AtualizarDadosGnss(UltimaLeitura);
} }
catch (Exception exUi) catch (Exception exUi)
{ {

View File

@ -248,21 +248,17 @@ namespace OperationControl.ViewModels
{ {
try try
{ {
// 1) Escolhe a interface “ativa” ou a que você usa pra ponte IP var interfaces = EthernetService.ObterNomesInterfaces()
// Se você já tem isso fixo, melhor ainda: use sempre a mesma. .Where(x => x.Contains(AppShell.Mock ? "Ativa" : "Ethernet"))
var interfaces = EthernetService.ObterNomesInterfaces().Where(x => x.Contains("Ativa")).Select(x => x.Split('(')[0].Trim()).ToList(); .Select(x => x.Split('(')[0].Trim())
var iface = interfaces[0]; // <-- se tiver .ToList();
// ou se você já sabe o nome: var iface = "Ethernet 2";
var ip = EthernetService.ObterIpAtual(iface); string? iface = interfaces.FirstOrDefault();
var sub = EthernetService.ObterSubnetMaskAtual(iface);
var gw = EthernetService.ObterIpGateway(iface);
DetalhesRede = $"{iface}\nIP: {ip}\nMáscara: {sub}\nGateway: {gw}"; var status = EthernetService.ObterStatusRede(iface, exigirGateway: false);
// 2) Define RedeOk do jeito que faz sentido pra você: RedeOk = status.RedeOk;
// exemplo simples: tem IP válido + gateway válido DetalhesRede = status.Detalhes;
RedeOk = !string.IsNullOrWhiteSpace(ip) && ip != "0.0.0.0";
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -0,0 +1,397 @@
using OperationControl.Models;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Data;
namespace OperationControl.ViewModels.Views.Operacao.Monitoramento
{
public class HistoricoViewModel : INotifyPropertyChanged
{
public HistoricoViewModel()
{
EventosView = CollectionViewSource.GetDefaultView(Eventos);
EventosView.Filter = FiltrarEvento;
_ordenacaoSelecionada = "Mais recentes";
AplicarOrdenacao();
AtualizarFiltros();
}
private List<MotivoTopBarModel> _eventosTopBarAnteriores = new();
private ObservableCollection<HistoricoOperacaoItemModel> _eventos = new();
public ObservableCollection<HistoricoOperacaoItemModel> Eventos
{
get => _eventos;
set
{
_eventos = value;
OnPropertyChanged(nameof(Eventos));
EventosView = CollectionViewSource.GetDefaultView(Eventos);
EventosView.Filter = FiltrarEvento;
AplicarOrdenacao();
OnPropertyChanged(nameof(EventosView));
OnPropertyChanged(nameof(QuantidadeEventos));
OnPropertyChanged(nameof(QuantidadeEventosFiltrados));
}
}
private ICollectionView _eventosView;
public ICollectionView EventosView
{
get => _eventosView;
set
{
_eventosView = value;
OnPropertyChanged(nameof(EventosView));
OnPropertyChanged(nameof(QuantidadeEventosFiltrados));
}
}
public int QuantidadeEventos => Eventos?.Count ?? 0;
public int QuantidadeEventosFiltrados
{
get
{
if (EventosView == null) return 0;
return EventosView.Cast<object>().Count();
}
}
private string _textoBusca;
public string TextoBusca
{
get => _textoBusca;
set
{
if (_textoBusca == value) return;
_textoBusca = value;
OnPropertyChanged(nameof(TextoBusca));
AtualizarFiltros();
}
}
private string _roverIdSelecionado;
public string RoverIdSelecionado
{
get => _roverIdSelecionado;
set
{
if (_roverIdSelecionado == value) return;
_roverIdSelecionado = value;
OnPropertyChanged(nameof(RoverIdSelecionado));
AtualizarFiltros();
}
}
private string _tipoTransicaoSelecionado = "Todos";
public string TipoTransicaoSelecionado
{
get => _tipoTransicaoSelecionado;
set
{
if (_tipoTransicaoSelecionado == value) return;
_tipoTransicaoSelecionado = value;
OnPropertyChanged(nameof(TipoTransicaoSelecionado));
AtualizarFiltros();
}
}
private string _statusSelecionado = "Todos";
public string StatusSelecionado
{
get => _statusSelecionado;
set
{
if (_statusSelecionado == value) return;
_statusSelecionado = value;
OnPropertyChanged(nameof(StatusSelecionado));
AtualizarFiltros();
}
}
private string _fonteSelecionada = "Todas";
public string FonteSelecionada
{
get => _fonteSelecionada;
set
{
if (_fonteSelecionada == value) return;
_fonteSelecionada = value;
OnPropertyChanged(nameof(FonteSelecionada));
AtualizarFiltros();
}
}
private string _ordenacaoSelecionada;
public string OrdenacaoSelecionada
{
get => _ordenacaoSelecionada;
set
{
if (_ordenacaoSelecionada == value) return;
_ordenacaoSelecionada = value;
OnPropertyChanged(nameof(OrdenacaoSelecionada));
AplicarOrdenacao();
}
}
public List<string> TiposTransicaoDisponiveis { get; } = new()
{
"Todos",
"INICIO",
"FIM",
"MUDANCA"
};
public List<string> StatusDisponiveis { get; } = new()
{
"Todos",
"Falha",
"Alerta",
"Desconectado",
"Operante",
"Conectado"
};
public List<string> FontesDisponiveis { get; } = new()
{
"Todas",
"Normal",
"Comunicacao",
"Emergencia",
"Pausa",
"Controle",
"Trajetoria",
"ModuloCritico",
"NCP",
"AlertaModulo"
};
public List<string> OrdenacoesDisponiveis { get; } = new()
{
"Mais recentes",
"Mais antigos",
"Título (A-Z)",
"Fonte (A-Z)",
"Status"
};
private bool FiltrarEvento(object obj)
{
if (obj is not HistoricoOperacaoItemModel ev)
return false;
// filtro padrão por rover selecionado
if (!string.IsNullOrWhiteSpace(RoverIdSelecionado) &&
!string.Equals(ev.RoverId, RoverIdSelecionado, StringComparison.OrdinalIgnoreCase))
return false;
if (!string.IsNullOrWhiteSpace(TipoTransicaoSelecionado) &&
TipoTransicaoSelecionado != "Todos" &&
!string.Equals(ev.TipoTransicao, TipoTransicaoSelecionado, StringComparison.OrdinalIgnoreCase))
return false;
if (!string.IsNullOrWhiteSpace(StatusSelecionado) &&
StatusSelecionado != "Todos" &&
!string.Equals(ev.StatusVisual.ToString(), StatusSelecionado, StringComparison.OrdinalIgnoreCase))
return false;
if (!string.IsNullOrWhiteSpace(FonteSelecionada) &&
FonteSelecionada != "Todas" &&
!string.Equals(ev.Fonte, FonteSelecionada, StringComparison.OrdinalIgnoreCase))
return false;
if (!string.IsNullOrWhiteSpace(TextoBusca))
{
var busca = TextoBusca.Trim();
bool encontrou =
(ev.Titulo?.Contains(busca, StringComparison.OrdinalIgnoreCase) ?? false) ||
(ev.Descricao?.Contains(busca, StringComparison.OrdinalIgnoreCase) ?? false) ||
(ev.Fonte?.Contains(busca, StringComparison.OrdinalIgnoreCase) ?? false) ||
(ev.RoverId?.Contains(busca, StringComparison.OrdinalIgnoreCase) ?? false) ||
(ev.TipoTransicao?.Contains(busca, StringComparison.OrdinalIgnoreCase) ?? false) ||
(ev.Modulo?.ToString().Contains(busca, StringComparison.OrdinalIgnoreCase) ?? false);
if (!encontrou)
return false;
}
return true;
}
public void AtualizarFiltros()
{
EventosView?.Refresh();
OnPropertyChanged(nameof(QuantidadeEventosFiltrados));
}
public void AplicarOrdenacao()
{
if (EventosView == null) return;
using (EventosView.DeferRefresh())
{
EventosView.SortDescriptions.Clear();
switch (OrdenacaoSelecionada)
{
case "Mais antigos":
EventosView.SortDescriptions.Add(new SortDescription(nameof(HistoricoOperacaoItemModel.DataHora), ListSortDirection.Ascending));
break;
case "Título (A-Z)":
EventosView.SortDescriptions.Add(new SortDescription(nameof(HistoricoOperacaoItemModel.Titulo), ListSortDirection.Ascending));
EventosView.SortDescriptions.Add(new SortDescription(nameof(HistoricoOperacaoItemModel.DataHora), ListSortDirection.Descending));
break;
case "Fonte (A-Z)":
EventosView.SortDescriptions.Add(new SortDescription(nameof(HistoricoOperacaoItemModel.Fonte), ListSortDirection.Ascending));
EventosView.SortDescriptions.Add(new SortDescription(nameof(HistoricoOperacaoItemModel.DataHora), ListSortDirection.Descending));
break;
case "Status":
EventosView.SortDescriptions.Add(new SortDescription(nameof(HistoricoOperacaoItemModel.StatusVisual), ListSortDirection.Ascending));
EventosView.SortDescriptions.Add(new SortDescription(nameof(HistoricoOperacaoItemModel.DataHora), ListSortDirection.Descending));
break;
default:
EventosView.SortDescriptions.Add(new SortDescription(nameof(HistoricoOperacaoItemModel.DataHora), ListSortDirection.Descending));
break;
}
}
OnPropertyChanged(nameof(QuantidadeEventosFiltrados));
}
public void LimparBusca()
{
TextoBusca = string.Empty;
}
public void ResetarFiltros()
{
RoverIdSelecionado = VariaveisControleOperacao.SelectedRoverId;
TextoBusca = string.Empty;
TipoTransicaoSelecionado = "Todos";
StatusSelecionado = "Todos";
FonteSelecionada = "Todas";
OrdenacaoSelecionada = "Mais recentes";
AtualizarFiltros();
}
public void CompararERegistrarMudancasEventos(string roverId, List<MotivoTopBarModel> eventosAtuais)
{
string Chave(MotivoTopBarModel e) =>
$"{e.Fonte}|{e.Titulo}|{e.Descricao}|{e.Modulo}|{e.StatusVisual}";
var atuais = eventosAtuais?
.GroupBy(Chave)
.ToDictionary(g => g.Key, g => g.First())
?? new Dictionary<string, MotivoTopBarModel>();
var anteriores = _eventosTopBarAnteriores?
.GroupBy(Chave)
.ToDictionary(g => g.Key, g => g.First())
?? new Dictionary<string, MotivoTopBarModel>();
foreach (var chave in anteriores.Keys.Except(atuais.Keys))
RegistrarLogEvento(roverId, "FIM", anteriores[chave]);
foreach (var chave in atuais.Keys.Except(anteriores.Keys))
RegistrarLogEvento(roverId, "INICIO", atuais[chave]);
_eventosTopBarAnteriores = eventosAtuais?
.Select(x => new MotivoTopBarModel
{
Prioridade = x.Prioridade,
Fonte = x.Fonte,
Titulo = x.Titulo,
Descricao = x.Descricao,
StatusVisual = x.StatusVisual,
Modulo = x.Modulo
})
.ToList() ?? new List<MotivoTopBarModel>();
_roverIdSelecionado = VariaveisControleOperacao.SelectedRoverId;
OnPropertyChanged(nameof(QuantidadeEventos));
OnPropertyChanged(nameof(QuantidadeEventosFiltrados));
OnPropertyChanged(nameof(RoverIdSelecionado));
}
private void RegistrarLogEvento(string roverId, string tipoTransicao, MotivoTopBarModel ev)
{
if (ev == null) return;
var item = new HistoricoOperacaoItemModel
{
DataHora = DateTime.Now,
RoverId = roverId,
Tipo = TipoHistoricoOperacao.Operacao,
TipoTransicao = tipoTransicao,
Fonte = ev.Fonte,
Titulo = ev.Titulo,
Descricao = ev.Descricao,
StatusVisual = ev.StatusVisual,
Modulo = ev.Modulo
};
System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
Eventos.Insert(0, item);
if (Eventos.Count > 2000)
Eventos.RemoveAt(Eventos.Count - 1);
AtualizarFiltros();
OnPropertyChanged(nameof(QuantidadeEventos));
OnPropertyChanged(nameof(QuantidadeEventosFiltrados));
}));
}
public void RegistrarHistoricoAlerta(string tipoTransicao, AlertaModel alerta)
{
if (alerta == null) return;
var item = new HistoricoOperacaoItemModel
{
DataHora = DateTime.Now,
RoverId = alerta.Rover_ID,
Tipo = TipoHistoricoOperacao.Alerta,
TipoTransicao = tipoTransicao,
Fonte = "AlertaModulo",
Titulo = alerta.Titulo,
Descricao = alerta.Mensagem,
SeveridadeAlerta = alerta.Severidade,
Modulo = alerta.Modulo,
ModId = alerta.Mod_ID
};
System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
Eventos.Insert(0, item);
if (Eventos.Count > 2000)
Eventos.RemoveAt(Eventos.Count - 1);
AtualizarFiltros();
OnPropertyChanged(nameof(QuantidadeEventos));
OnPropertyChanged(nameof(QuantidadeEventosFiltrados));
}));
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string nome)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nome));
}
}
}

View File

@ -1,4 +1,5 @@
using OperationControl.Models; using OperationControl.Models;
using OperationControl.ViewModels.Views.Operacao.Monitoramento;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.ComponentModel; using System.ComponentModel;
using System.Windows.Threading; using System.Windows.Threading;
@ -8,6 +9,7 @@ namespace OperationControl.ViewModels.Views.Operacao
{ {
public class OperacaoBottomViewModel : INotifyPropertyChanged public class OperacaoBottomViewModel : INotifyPropertyChanged
{ {
private HistoricoViewModel? historicoVm => Variaveis.Dock?._vm?._viewOperacaoCenter?.Historico?._vm;
public ObservableCollection<AlertaModel> AlertasAtivos { get; } = new(); public ObservableCollection<AlertaModel> AlertasAtivos { get; } = new();
private AlertaModel _toastAlerta; private AlertaModel _toastAlerta;
@ -87,11 +89,21 @@ namespace OperationControl.ViewModels.Views.Operacao
AlertasAtivos.Add(alerta); AlertasAtivos.Add(alerta);
MostrarToast(alerta); MostrarToast(alerta);
historicoVm?.RegistrarHistoricoAlerta("INICIO", alerta);
} }
else else
{ {
bool mudouMensagem = existente.Mensagem != mensagem;
bool mudouSeveridade = existente.Severidade != severidade;
existente.Mensagem = mensagem; existente.Mensagem = mensagem;
existente.Severidade = severidade; existente.Severidade = severidade;
if (mudouMensagem || mudouSeveridade)
{
historicoVm?.RegistrarHistoricoAlerta("MUDANCA", existente);
}
} }
})); }));
} }
@ -107,7 +119,10 @@ namespace OperationControl.ViewModels.Views.Operacao
(severidade != null ? x.Severidade == severidade : true)); (severidade != null ? x.Severidade == severidade : true));
if (alerta != null) if (alerta != null)
{
historicoVm?.RegistrarHistoricoAlerta("FIM", alerta);
AlertasAtivos.Remove(alerta); AlertasAtivos.Remove(alerta);
}
})); }));
} }
@ -266,6 +281,8 @@ namespace OperationControl.ViewModels.Views.Operacao
{ {
var alertaRemover = mapaAtual[chave]; var alertaRemover = mapaAtual[chave];
historicoVm?.RegistrarHistoricoAlerta("FIM", alertaRemover);
AlertasAtivos.Remove(alertaRemover); AlertasAtivos.Remove(alertaRemover);
if (ToastAlerta == alertaRemover) if (ToastAlerta == alertaRemover)
@ -284,6 +301,8 @@ namespace OperationControl.ViewModels.Views.Operacao
esperado.CriadoEm = agora; esperado.CriadoEm = agora;
AlertasAtivos.Add(esperado); AlertasAtivos.Add(esperado);
MostrarToast(esperado); MostrarToast(esperado);
historicoVm?.RegistrarHistoricoAlerta("INICIO", esperado);
} }
else else
{ {
@ -296,11 +315,11 @@ namespace OperationControl.ViewModels.Views.Operacao
{ {
existente.Mensagem = esperado.Mensagem; existente.Mensagem = esperado.Mensagem;
existente.Severidade = esperado.Severidade; existente.Severidade = esperado.Severidade;
// Se quiser que Timestamp represente "última mudança", mantém isso:
existente.AtualizadoEm = agora; existente.AtualizadoEm = agora;
MostrarToast(existente); MostrarToast(existente);
historicoVm?.RegistrarHistoricoAlerta("MUDANCA", existente);
} }
} }
} }

View File

@ -41,6 +41,7 @@ namespace OperationControl.ViewModels.Views.Operacao
OnPropertyChanged(nameof(ExibirMapa)); OnPropertyChanged(nameof(ExibirMapa));
OnPropertyChanged(nameof(ExibirDiagnostico)); OnPropertyChanged(nameof(ExibirDiagnostico));
OnPropertyChanged(nameof(ExibirMonitoramento)); OnPropertyChanged(nameof(ExibirMonitoramento));
OnPropertyChanged(nameof(ExibirHistorico));
OnPropertyChanged(nameof(TituloCentro)); OnPropertyChanged(nameof(TituloCentro));
OnPropertyChanged(nameof(SubtituloCentro)); OnPropertyChanged(nameof(SubtituloCentro));
} }
@ -49,6 +50,7 @@ namespace OperationControl.ViewModels.Views.Operacao
public bool ExibirMapa => ConteudoAtual == OperacaoCenterConteudo.Mapa; public bool ExibirMapa => ConteudoAtual == OperacaoCenterConteudo.Mapa;
public bool ExibirDiagnostico => ConteudoAtual == OperacaoCenterConteudo.Diagnostico; public bool ExibirDiagnostico => ConteudoAtual == OperacaoCenterConteudo.Diagnostico;
public bool ExibirMonitoramento => ConteudoAtual == OperacaoCenterConteudo.Monitoramento; public bool ExibirMonitoramento => ConteudoAtual == OperacaoCenterConteudo.Monitoramento;
public bool ExibirHistorico => ConteudoAtual == OperacaoCenterConteudo.Historico;
public string TituloCentro public string TituloCentro
{ {
@ -101,6 +103,7 @@ namespace OperationControl.ViewModels.Views.Operacao
SecaoRightBar.Controle => OperacaoCenterConteudo.Monitoramento, SecaoRightBar.Controle => OperacaoCenterConteudo.Monitoramento,
SecaoRightBar.Parametrizacao => OperacaoCenterConteudo.Mapa, SecaoRightBar.Parametrizacao => OperacaoCenterConteudo.Mapa,
SecaoRightBar.Menu => OperacaoCenterConteudo.Monitoramento, SecaoRightBar.Menu => OperacaoCenterConteudo.Monitoramento,
SecaoRightBar.Historico => OperacaoCenterConteudo.Historico,
_ => OperacaoCenterConteudo.Mapa _ => OperacaoCenterConteudo.Mapa
}; };
@ -142,6 +145,7 @@ namespace OperationControl.ViewModels.Views.Operacao
{ {
Mapa = 1, Mapa = 1,
Diagnostico = 2, Diagnostico = 2,
Monitoramento = 3 Monitoramento = 3,
Historico = 4,
} }
} }

View File

@ -338,6 +338,7 @@ namespace OperationControl.ViewModels.Views.Operacao
{ {
Task.Run(async () => Task.Run(async () =>
{ {
Variaveis.Dock?._vm?._viewOperacaoCenter?.Mapa?.LimparRastrosRover(VariaveisControleOperacao.RoverEmFoco?.RoverId);
if ((dadosRover?.Operacao?.Status ?? StatusOperacao.NaoIniciado) == StatusOperacao.Concluido) if ((dadosRover?.Operacao?.Status ?? StatusOperacao.NaoIniciado) == StatusOperacao.Concluido)
{ {
VariaveisControleOperacao.EnviarComandoIniciarOperacao(false, false); VariaveisControleOperacao.EnviarComandoIniciarOperacao(false, false);

View File

@ -99,7 +99,7 @@ namespace OperationControl.ViewModels.Views.Operacao
} }
} }
public bool EmMenu => SecaoAtual == SecaoRightBar.Menu || SecaoAtual == SecaoRightBar.Diagnostico; public bool EmMenu => new List<SecaoRightBar>() { SecaoRightBar.Menu, SecaoRightBar.Diagnostico, SecaoRightBar.Historico }.Contains(SecaoAtual);
public bool EmResumo => SecaoAtual == SecaoRightBar.Resumo; public bool EmResumo => SecaoAtual == SecaoRightBar.Resumo;
public bool EmParametrizacao => SecaoAtual == SecaoRightBar.Parametrizacao; public bool EmParametrizacao => SecaoAtual == SecaoRightBar.Parametrizacao;
public bool EmControle => SecaoAtual == SecaoRightBar.Controle; public bool EmControle => SecaoAtual == SecaoRightBar.Controle;
@ -264,7 +264,8 @@ namespace OperationControl.ViewModels.Views.Operacao
Resumo = 2, Resumo = 2,
Diagnostico = 3, Diagnostico = 3,
Parametrizacao = 4, Parametrizacao = 4,
Controle = 5 Controle = 5,
Historico = 6,
} }
} }

View File

@ -5,6 +5,7 @@ using Brushes = System.Windows.Media.Brushes;
using ColorConverter = System.Windows.Media.ColorConverter; using ColorConverter = System.Windows.Media.ColorConverter;
using Color = System.Windows.Media.Color; using Color = System.Windows.Media.Color;
using static AgroBase.Models.Enums; using static AgroBase.Models.Enums;
using OperationControl.Models;
namespace OperationControl.ViewModels.Views.Operacao namespace OperationControl.ViewModels.Views.Operacao
{ {
@ -173,4 +174,159 @@ namespace OperationControl.ViewModels.Views.Operacao
public StatusModulo StatusVisual { get; set; } public StatusModulo StatusVisual { get; set; }
public T_Code? Modulo { get; set; } public T_Code? Modulo { get; set; }
} }
public class LogEventoOperacaoModel
{
public DateTime DataHora { get; set; }
public string RoverId { get; set; }
public string TipoTransicao { get; set; } // INICIO / FIM / MUDANCA
public string Fonte { get; set; }
public string Titulo { get; set; }
public string Descricao { get; set; }
public StatusModulo StatusVisual { get; set; }
public T_Code? Modulo { get; set; }
public string Hora => DataHora.ToString("HH:mm:ss");
public string DataHoraCompleta => DataHora.ToString("dd/MM/yyyy HH:mm:ss");
public string Subtitulo
{
get
{
var moduloTxt = Modulo?.ToString();
if (!string.IsNullOrWhiteSpace(moduloTxt))
return $"{Fonte} • {moduloTxt} • {RoverId}";
return $"{Fonte} • {RoverId}";
}
}
public Brush CorAccent
{
get
{
return StatusVisual switch
{
StatusModulo.Falha => Brushes.IndianRed,
StatusModulo.Desconectado => Brushes.DarkRed,
StatusModulo.Alerta => Brushes.Goldenrod,
StatusModulo.Operante => Brushes.ForestGreen,
StatusModulo.Conectado => Brushes.SteelBlue,
_ => Brushes.Gray
};
}
}
public Brush CorBadge
{
get
{
return TipoTransicao switch
{
"INICIO" => Brushes.OrangeRed,
"FIM" => Brushes.SeaGreen,
"MUDANCA" => Brushes.SteelBlue,
_ => Brushes.DimGray
};
}
}
}
public class HistoricoOperacaoItemModel
{
public DateTime DataHora { get; set; }
public string RoverId { get; set; }
public TipoHistoricoOperacao Tipo { get; set; } // Operacao ou Alerta
public string TipoTransicao { get; set; } // INICIO / FIM / MUDANCA
public string Fonte { get; set; } // ex: Controle, Trajetoria, AlertaModulo
public string Titulo { get; set; }
public string Descricao { get; set; }
public StatusModulo? StatusVisual { get; set; } // para eventos operacionais
public SeveridadeAlerta? SeveridadeAlerta { get; set; } // para alertas
public T_Code? Modulo { get; set; }
public string? ModId { get; set; }
public string Hora => DataHora.ToString("HH:mm:ss");
public string DataHoraCompleta => DataHora.ToString("dd/MM/yyyy HH:mm:ss");
public string Subtitulo
{
get
{
var partes = new List<string>();
partes.Add(Tipo == TipoHistoricoOperacao.Alerta ? "Alerta" : "Operação");
if (!string.IsNullOrWhiteSpace(Fonte))
partes.Add(Fonte);
if (Modulo.HasValue)
partes.Add(Modulo.Value.ToString());
if (!string.IsNullOrWhiteSpace(ModId))
partes.Add(ModId);
if (!string.IsNullOrWhiteSpace(RoverId))
partes.Add(RoverId);
return string.Join(" • ", partes);
}
}
public Brush CorAccent
{
get
{
if (Tipo == TipoHistoricoOperacao.Alerta)
{
return SeveridadeAlerta switch
{
Models.SeveridadeAlerta.Warning => Brushes.Goldenrod,
Models.SeveridadeAlerta.Error => Brushes.IndianRed,
Models.SeveridadeAlerta.Critical => Brushes.DarkRed,
_ => Brushes.SteelBlue
};
}
return StatusVisual switch
{
StatusModulo.Falha => Brushes.IndianRed,
StatusModulo.Desconectado => Brushes.DarkRed,
StatusModulo.Alerta => Brushes.Goldenrod,
StatusModulo.Operante => Brushes.ForestGreen,
StatusModulo.Conectado => Brushes.SteelBlue,
_ => Brushes.Gray
};
}
}
public Brush CorBadge
{
get
{
return TipoTransicao switch
{
"INICIO" => Brushes.OrangeRed,
"FIM" => Brushes.SeaGreen,
"MUDANCA" => Brushes.SteelBlue,
_ => Brushes.DimGray
};
}
}
}
public enum TipoHistoricoOperacao
{
Operacao = 0,
Alerta = 1
}
} }

View File

@ -374,6 +374,9 @@ namespace OperationControl.ViewModels
double tempoSemResposta = (DateTime.Now - rover.UltimoContato).TotalSeconds; double tempoSemResposta = (DateTime.Now - rover.UltimoContato).TotalSeconds;
bool semComunicacao = !AppShell.Mock && tempoSemResposta >= VariaveisControleOperacao.TempoRoverVivo; bool semComunicacao = !AppShell.Mock && tempoSemResposta >= VariaveisControleOperacao.TempoRoverVivo;
// ============================
// 0) Perda de comunicação
// ============================
if (semComunicacao) if (semComunicacao)
{ {
eventos.Add(new MotivoTopBarModel eventos.Add(new MotivoTopBarModel
@ -386,11 +389,11 @@ namespace OperationControl.ViewModels
}); });
} }
// ============================
// 0) NCP desconectado
// ============================
if (obj.ModulosSaude?.All(x => (x?.status ?? StatusModulo.Desconectado) == StatusModulo.Desconectado) ?? false) if (obj.ModulosSaude?.All(x => (x?.status ?? StatusModulo.Desconectado) == StatusModulo.Desconectado) ?? false)
{ {
// ============================
// 0) NCP desconectado
// ============================
eventos.Add(new MotivoTopBarModel eventos.Add(new MotivoTopBarModel
{ {
Prioridade = 1100, Prioridade = 1100,
@ -484,6 +487,14 @@ namespace OperationControl.ViewModels
(x?.Utilizar ?? false) && (x?.Utilizar ?? false) &&
(x?.Mandatorio ?? false) (x?.Mandatorio ?? false)
) ?? false) ) ?? false)
) ||
(
(mod?.status ?? StatusModulo.Desconectado) == StatusModulo.Alerta &&
(rover?.ModulosMandatorios?.Any(x =>
(x?.Dispositivo ?? T_Code.Vzo) == (mod?.modulo ?? T_Code.Vzo) &&
(x?.Utilizar ?? false) &&
(x?.Mandatorio ?? false)
) ?? false)
) )
) )
.ToList() ?? new List<AgroBase.Models.Operadores.ManagerWorkerMessageResponseModulosPendentesModel>(); .ToList() ?? new List<AgroBase.Models.Operadores.ManagerWorkerMessageResponseModulosPendentesModel>();
@ -500,8 +511,32 @@ namespace OperationControl.ViewModels
(x?.Utilizar ?? false) && (x?.Utilizar ?? false) &&
(x?.Mandatorio ?? false) (x?.Mandatorio ?? false)
) ?? false); ) ?? false);
bool moduloMandatorioAlerta =
(modulo?.status ?? StatusModulo.Desconectado) == StatusModulo.Alerta &&
(rover?.ModulosMandatorios?.Any(x =>
(x?.Dispositivo ?? T_Code.Vzo) == (modulo?.modulo ?? T_Code.Vzo) &&
(x?.Utilizar ?? false) &&
(x?.Mandatorio ?? false)
) ?? false);
// Caso 1: condições operacionais severas // Caso 1: módulo mandatório não operante
if (moduloMandatorioNaoOperante)
{
var statusTexto = (modulo?.status ?? StatusModulo.Desconectado).ToString();
string motivos = string.Join("; ", modulo?.motivos ?? new List<string>() { "Desconhecido" });
eventos.Add(new MotivoTopBarModel
{
Prioridade = 620,
Fonte = "ModuloCritico",
Titulo = "MÓDULO MANDATÓRIO NÃO OPERACIONAL",
Descricao = $"{nomeModulo}: módulo mandatório em estado '{statusTexto}': {motivos}",
StatusVisual = StatusModulo.Falha,
Modulo = modulo?.modulo
});
}
// Caso 2: condições operacionais severas
if (temCondicaoSevera) if (temCondicaoSevera)
{ {
var descricoesCriticas = modulo?.condicoes_operacionais? var descricoesCriticas = modulo?.condicoes_operacionais?
@ -515,7 +550,7 @@ namespace OperationControl.ViewModels
{ {
eventos.Add(new MotivoTopBarModel eventos.Add(new MotivoTopBarModel
{ {
Prioridade = 600, Prioridade = 610,
Fonte = "ModuloCritico", Fonte = "ModuloCritico",
Titulo = "CONDIÇÕES OPERACIONAIS CRÍTICAS", Titulo = "CONDIÇÕES OPERACIONAIS CRÍTICAS",
Descricao = $"{nomeModulo}: {desc}", Descricao = $"{nomeModulo}: {desc}",
@ -525,18 +560,19 @@ namespace OperationControl.ViewModels
} }
} }
// Caso 2: módulo mandatório não operante // Caso 3: módulo mandatório em alerta
if (moduloMandatorioNaoOperante) if (moduloMandatorioAlerta)
{ {
var statusTexto = (modulo?.status ?? StatusModulo.Desconectado).ToString(); var statusTexto = (modulo?.status ?? StatusModulo.Desconectado).ToString();
string motivos = string.Join("; ", modulo?.motivos ?? new List<string>() { "Desconhecido" });
eventos.Add(new MotivoTopBarModel eventos.Add(new MotivoTopBarModel
{ {
Prioridade = 610, Prioridade = 600,
Fonte = "ModuloCritico", Fonte = "ModuloCritico",
Titulo = "MÓDULO MANDATÓRIO NÃO OPERACIONAL", Titulo = "MÓDULO MANDATÓRIO EM ALERTA",
Descricao = $"{nomeModulo}: módulo mandatório em estado '{statusTexto}'", Descricao = $"{nomeModulo}: módulo mandatório em estado '{statusTexto}': {motivos}",
StatusVisual = StatusModulo.Falha, StatusVisual = StatusModulo.Alerta,
Modulo = modulo?.modulo Modulo = modulo?.modulo
}); });
} }
@ -565,6 +601,20 @@ namespace OperationControl.ViewModels
.ThenBy(x => x.Fonte) .ThenBy(x => x.Fonte)
.ToList(); .ToList();
if (!eventosOrdenados.Any())
{
eventosOrdenados.Add(new MotivoTopBarModel
{
Prioridade = 0,
Fonte = "Normal",
Titulo = "ROVER OPERANDO NORMALMENTE",
Descricao = "Trajetória, controle e operação em condição normal.",
StatusVisual = obj.StatusRover == StatusModulo.Desconectado
? StatusModulo.Desconectado
: StatusModulo.Operante
});
}
// ============================ // ============================
// 7) Monta visual final // 7) Monta visual final
// ============================ // ============================
@ -627,6 +677,8 @@ namespace OperationControl.ViewModels
resumo = (obj.Operacao?.Status ?? StatusOperacao.NaoIniciado).ToString(); resumo = (obj.Operacao?.Status ?? StatusOperacao.NaoIniciado).ToString();
} }
_viewOperacaoCenter?.Historico?._vm?.CompararERegistrarMudancasEventos(rover.RoverId, eventosOrdenados);
// Segurança extra: evita linha vazia // Segurança extra: evita linha vazia
if (string.IsNullOrWhiteSpace(linha2)) if (string.IsNullOrWhiteSpace(linha2))
linha2 = "Sem detalhes adicionais."; linha2 = "Sem detalhes adicionais.";

View File

@ -0,0 +1,382 @@
<UserControl x:Class="OperationControl.Views.Operacao.Monitoramento.HistoricoView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:OperationControl.Views.Operacao.Monitoramento"
mc:Ignorable="d"
d:DesignHeight="900"
d:DesignWidth="1600">
<UserControl.Resources>
<!-- Campo base escuro -->
<Style x:Key="DarkInputTextBoxStyle" TargetType="TextBox">
<Setter Property="Height" Value="36"/>
<Setter Property="Padding" Value="10,6"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="Background" Value="#111315"/>
<Setter Property="BorderBrush" Value="#323842"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="CaretBrush" Value="White"/>
<Setter Property="FontSize" Value="13"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TextBox">
<Border x:Name="Bd"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="10">
<ScrollViewer x:Name="PART_ContentHost"
Margin="0"
VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsKeyboardFocused" Value="True">
<Setter TargetName="Bd" Property="BorderBrush" Value="#4C89FF"/>
<Setter TargetName="Bd" Property="Background" Value="#14181D"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Bd" Property="BorderBrush" Value="#46505C"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="Bd" Property="Opacity" Value="0.65"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- ComboBox escuro -->
<Style x:Key="DarkComboBoxStyle" TargetType="ComboBox">
<Setter Property="Height" Value="36"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="Background" Value="#111315"/>
<Setter Property="BorderBrush" Value="#323842"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="FontSize" Value="13"/>
<Setter Property="Padding" Value="10,6"/>
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="ScrollViewer.CanContentScroll" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ComboBox">
<Grid>
<Border x:Name="Bd"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="10"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="36"/>
</Grid.ColumnDefinitions>
<!-- texto selecionado -->
<ContentPresenter Grid.Column="0"
Margin="12,0,8,0"
VerticalAlignment="Center"
HorizontalAlignment="Left"
RecognizesAccessKey="True"
Content="{TemplateBinding SelectionBoxItem}"
ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}"/>
<!-- seta -->
<Border Grid.Column="1"
Background="Transparent">
<Path Width="10"
Height="6"
Stretch="Fill"
Fill="#C8D0DA"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Data="M 0 0 L 10 0 L 5 6 Z"/>
</Border>
<!-- área clicável -->
<ToggleButton Grid.ColumnSpan="2"
Background="Transparent"
BorderThickness="0"
Focusable="False"
ClickMode="Press"
IsChecked="{Binding IsDropDownOpen, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}">
<ToggleButton.Template>
<ControlTemplate TargetType="ToggleButton">
<Border Background="Transparent"/>
</ControlTemplate>
</ToggleButton.Template>
</ToggleButton>
</Grid>
<Popup x:Name="Popup"
Placement="Bottom"
AllowsTransparency="True"
Focusable="False"
StaysOpen="False"
IsOpen="{TemplateBinding IsDropDownOpen}"
PopupAnimation="Fade">
<Border Margin="0,4,0,0"
Background="#1A1D21"
BorderBrush="#323842"
BorderThickness="1"
CornerRadius="10"
MinWidth="{Binding ActualWidth, RelativeSource={RelativeSource TemplatedParent}}">
<ScrollViewer MaxHeight="320"
CanContentScroll="True">
<ItemsPresenter KeyboardNavigation.DirectionalNavigation="Contained"/>
</ScrollViewer>
</Border>
</Popup>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter TargetName="Bd" Property="BorderBrush" Value="#4C89FF"/>
<Setter TargetName="Bd" Property="Background" Value="#14181D"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Bd" Property="BorderBrush" Value="#46505C"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="Bd" Property="Opacity" Value="0.65"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="ComboBoxItem">
<Setter Property="Foreground" Value="White"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Padding" Value="10,6"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ComboBoxItem">
<Border x:Name="ItemBorder"
Background="{TemplateBinding Background}"
Padding="{TemplateBinding Padding}">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsHighlighted" Value="True">
<Setter TargetName="ItemBorder" Property="Background" Value="#2A313A"/>
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="ItemBorder" Property="Background" Value="#314256"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
<!-- Botão secundário, caso use depois -->
<Style x:Key="DarkSmallButtonStyle" TargetType="Button">
<Setter Property="Height" Value="36"/>
<Setter Property="Padding" Value="14,6"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="Background" Value="#20252B"/>
<Setter Property="BorderBrush" Value="#323842"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="FontSize" Value="13"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border x:Name="Bd"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="10">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Bd" Property="Background" Value="#2A3138"/>
<Setter TargetName="Bd" Property="BorderBrush" Value="#46505C"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="Bd" Property="Background" Value="#151A1F"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="Bd" Property="Opacity" Value="0.65"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid Background="#111315">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- CABEÇALHO -->
<StackPanel Grid.Row="0" Margin="16,16,16,0">
<Border Padding="18" CornerRadius="14" Background="#1A1D21" BorderBrush="#2A2E35" BorderThickness="1">
<StackPanel>
<DockPanel LastChildFill="False">
<StackPanel DockPanel.Dock="Left">
<TextBlock Text="Histórico operacional" FontSize="22" FontWeight="SemiBold" Foreground="White"/>
<TextBlock Margin="0,6,0,0" FontSize="13" Foreground="#B8C0CC">
<Run Text="Exibindo "/>
<Run Text="{Binding QuantidadeEventosFiltrados, Mode=OneWay}"/>
<Run Text=" eventos filtrados de "/>
<Run Text="{Binding QuantidadeEventos, Mode=OneWay}"/>
<Run Text=" eventos"/>
</TextBlock>
</StackPanel>
</DockPanel>
<StackPanel Margin="0,16,0,0">
<TextBlock Text="Busca e filtros" Margin="2,0,0,10" FontSize="12" Foreground="#8F99A5"/>
<WrapPanel>
<TextBox Width="260" Margin="0,0,10,10" Text="{Binding TextoBusca, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource DarkInputTextBoxStyle}" ToolTip="Buscar por título, descrição, fonte, módulo..." />
<ComboBox Width="150" Margin="0,0,10,10" ItemsSource="{Binding TiposTransicaoDisponiveis}" SelectedItem="{Binding TipoTransicaoSelecionado}" Style="{StaticResource DarkComboBoxStyle}" />
<ComboBox Width="150" Margin="0,0,10,10" ItemsSource="{Binding StatusDisponiveis}" SelectedItem="{Binding StatusSelecionado}" Style="{StaticResource DarkComboBoxStyle}" />
<ComboBox Width="170" Margin="0,0,10,10" ItemsSource="{Binding FontesDisponiveis}" SelectedItem="{Binding FonteSelecionada}" Style="{StaticResource DarkComboBoxStyle}" />
<ComboBox Width="160" Margin="0,0,10,10" ItemsSource="{Binding OrdenacoesDisponiveis}" SelectedItem="{Binding OrdenacaoSelecionada}" Style="{StaticResource DarkComboBoxStyle}" />
<Border Background="#111315" BorderBrush="#323842" BorderThickness="1" CornerRadius="10" Height="36" Padding="12,6" Margin="0,0,10,10">
<TextBlock VerticalAlignment="Center" Foreground="#B8C0CC" FontSize="12" Text="{Binding RoverIdSelecionado, StringFormat=Rover: {0}}" />
</Border>
</WrapPanel>
</StackPanel>
</StackPanel>
</Border>
</StackPanel>
<!-- LISTA -->
<Border Grid.Row="1"
Margin="16,0,16,16"
Padding="8"
CornerRadius="14"
Background="#16191D"
BorderBrush="#2A2E35"
BorderThickness="1">
<ListBox ItemsSource="{Binding EventosView}"
Background="Transparent"
BorderThickness="0"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Padding" Value="0"/>
<Setter Property="Margin" Value="0,0,0,10"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Focusable" Value="False"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Border Background="#1D2126"
CornerRadius="12"
BorderBrush="#2B3138"
BorderThickness="1"
Padding="0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="6"/>
<ColumnDefinition Width="110"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="110"/>
</Grid.ColumnDefinitions>
<!-- barra lateral -->
<Border Grid.Column="0"
Background="{Binding CorAccent}"
CornerRadius="12,0,0,12"/>
<!-- hora -->
<StackPanel Grid.Column="1"
VerticalAlignment="Center"
Margin="16,14,10,14">
<TextBlock Text="{Binding Hora}"
FontSize="20"
FontWeight="Bold"
Foreground="White"
HorizontalAlignment="Left"/>
<TextBlock Text="{Binding DataHoraCompleta}"
Margin="0,4,0,0"
FontSize="11"
Foreground="#9DA7B3"
TextWrapping="Wrap"/>
</StackPanel>
<!-- conteúdo -->
<StackPanel Grid.Column="2"
Margin="8,14,16,14">
<TextBlock Text="{Binding Titulo}"
FontSize="16"
FontWeight="SemiBold"
Foreground="White"
TextWrapping="Wrap"/>
<TextBlock Text="{Binding Descricao}"
Margin="0,8,0,0"
FontSize="13"
Foreground="#D0D7E2"
TextWrapping="Wrap"/>
<TextBlock Text="{Binding Subtitulo}"
Margin="0,10,0,0"
FontSize="12"
Foreground="#8F99A5"
TextWrapping="Wrap"/>
</StackPanel>
<!-- badge -->
<StackPanel Grid.Column="3"
Margin="0,14,14,14"
VerticalAlignment="Top"
HorizontalAlignment="Right">
<Border Background="{Binding CorBadge}"
CornerRadius="10"
Padding="10,4">
<TextBlock Text="{Binding TipoTransicao}"
Foreground="White"
FontSize="11"
FontWeight="SemiBold"
HorizontalAlignment="Center"/>
</Border>
</StackPanel>
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Border>
</Grid>
</UserControl>

View File

@ -0,0 +1,20 @@
using OperationControl.ViewModels.Views.Operacao.Monitoramento;
namespace OperationControl.Views.Operacao.Monitoramento
{
/// <summary>
/// Interação lógica para HistoricoView.xam
/// </summary>
public partial class HistoricoView : System.Windows.Controls.UserControl
{
public HistoricoViewModel _vm;
public HistoricoView()
{
InitializeComponent();
_vm = new HistoricoViewModel();
DataContext = _vm;
}
}
}

View File

@ -33,5 +33,10 @@
<views:MonitoramentoView x:Name="areaMonitor"/> <views:MonitoramentoView x:Name="areaMonitor"/>
</Grid> </Grid>
<!-- HSITORICO -->
<Grid Visibility="{Binding ExibirHistorico, Converter={StaticResource BoolToVisibilityConverter}}">
<views:HistoricoView x:Name="areaHistorico"/>
</Grid>
</Grid> </Grid>
</UserControl> </UserControl>

View File

@ -13,6 +13,7 @@ namespace OperationControl.Views.Operacao
public MapViewControl Mapa => _vm?.ConteudoAtual == OperacaoCenterConteudo.Mapa ? areaMapa : _vm?.ConteudoAtual == OperacaoCenterConteudo.Monitoramento ? Monitoramento?.MapaMonitoramento : null; public MapViewControl Mapa => _vm?.ConteudoAtual == OperacaoCenterConteudo.Mapa ? areaMapa : _vm?.ConteudoAtual == OperacaoCenterConteudo.Monitoramento ? Monitoramento?.MapaMonitoramento : null;
public MonitoramentoView Monitoramento => areaMonitor; public MonitoramentoView Monitoramento => areaMonitor;
public DiagnosticoView Diagnostico => areaDiagnostico; public DiagnosticoView Diagnostico => areaDiagnostico;
public HistoricoView Historico => areaHistorico;
public OperacaoCenterView() public OperacaoCenterView()
{ {

View File

@ -210,6 +210,8 @@
<Button Content="Controle ▶" Style="{StaticResource MenuButtonStyle}" Command="{Binding SelecionarSecaoCommand}" CommandParameter="Controle"/> <Button Content="Controle ▶" Style="{StaticResource MenuButtonStyle}" Command="{Binding SelecionarSecaoCommand}" CommandParameter="Controle"/>
<Button Content="Resumo ▶" Style="{StaticResource MenuButtonStyle}" Command="{Binding SelecionarSecaoCommand}" CommandParameter="Resumo"/> <Button Content="Resumo ▶" Style="{StaticResource MenuButtonStyle}" Command="{Binding SelecionarSecaoCommand}" CommandParameter="Resumo"/>
<Button Content="Histórico" Style="{StaticResource MenuButtonStyle}" Command="{Binding SelecionarSecaoCommand}" CommandParameter="Historico"/>
</StackPanel> </StackPanel>
<!-- RESUMO --> <!-- RESUMO -->