SGI/Dashboard/DashboardMain.xaml.cs

461 lines
19 KiB
C#

using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Security.Cryptography.Xml;
using System.Windows;
using System.Windows.Input;
using UI.Database;
namespace UI.Dashboard
{
// ─────────────────────────────────────────
// MODEL
// ─────────────────────────────────────────
public class PessoaViewModel
{
public int Id { get; set; }
public string NomeCompleto { get; set; } = "";
public string Email { get; set; } = "";
public string Telefone { get; set; } = "";
public string Cidade { get; set; } = "";
public string Cargo { get; set; } = "";
public DateTime? DataNascimento { get; set; }
public string DataNascFormatada =>
DataNascimento.HasValue ? DataNascimento.Value.ToString("dd/MM/yyyy") : "-";
public string Iniciais
{
get
{
var p = NomeCompleto.Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (p.Length >= 2) return $"{p[0][0]}{p[^1][0]}".ToUpper();
return NomeCompleto.Length >= 2 ? NomeCompleto[..2].ToUpper() : NomeCompleto.ToUpper();
}
}
private static readonly (string Bg, string Fg)[] Paleta =
{
("#1A3A6E","#60A5FA"), ("#3A1A3A","#C084FC"), ("#1A3A20","#4ADE80"),
("#3A2A10","#FCD34D"), ("#1A2A3A","#38BDF8"), ("#3A1A20","#FB7185"),
("#1A3A35","#34D399"),
};
public string AvatarBackground => Paleta[Math.Abs(Id) % Paleta.Length].Bg;
public string AvatarForeground => Paleta[Math.Abs(Id) % Paleta.Length].Fg;
}
// ─────────────────────────────────────────
// MODEL DA PAGINACAO
// ─────────────────────────────────────────
public class PaginaItem
{
public string Label { get; set; } = "";
public bool IsActive { get; set; }
public string FontWeight => IsActive ? "Bold" : "Normal";
}
// ─────────────────────────────────────────
// REPOSITORIO
// ─────────────────────────────────────────
public class PessoaRepository
{
private readonly string _connectionString;
public PessoaRepository(string connectionString)
{
_connectionString = connectionString;
}
public (List<PessoaViewModel> Itens, int TotalRegistros) Buscar(
string filtro, int pagina, int itensPorPagina)
{
using var conn = new SqlConnection(_connectionString);
conn.Open();
var filtroParam = string.IsNullOrWhiteSpace(filtro) ? "%" : $"%{filtro}%";
var offset = (pagina - 1) * itensPorPagina;
int total;
using (var cmdCount = new SqlCommand(@"
SELECT COUNT(*)
FROM Pessoas
WHERE @filtro = '%'
OR NomeCompleto LIKE @filtro
OR Email LIKE @filtro
OR Telefone LIKE @filtro", conn))
{
cmdCount.Parameters.AddWithValue("@filtro", filtroParam);
total = (int)cmdCount.ExecuteScalar();
}
var itens = new List<PessoaViewModel>();
using (var cmd = new SqlCommand(@"
SELECT Id, NomeCompleto, Email, Telefone, Cidade, Cargo, DataNascimento
FROM Pessoas
WHERE @filtro = '%'
OR NomeCompleto LIKE @filtro
OR Email LIKE @filtro
OR Cidade LIKE @filtro
OR Cargo LIKE @filtro
ORDER BY NomeCompleto
OFFSET @offset ROWS FETCH NEXT @qtd ROWS ONLY", conn))
{
cmd.Parameters.AddWithValue("@filtro", filtroParam);
cmd.Parameters.AddWithValue("@offset", offset);
cmd.Parameters.AddWithValue("@qtd", itensPorPagina);
using var reader = cmd.ExecuteReader();
while (reader.Read())
{
itens.Add(new PessoaViewModel
{
Id = reader.GetInt32(0),
NomeCompleto = reader.IsDBNull(1) ? "" : reader.GetString(1),
Email = reader.IsDBNull(2) ? "" : reader.GetString(2),
Telefone = reader.IsDBNull(3) ? "" : reader.GetString(3),
Cidade = reader.IsDBNull(4) ? "" : reader.GetString(4),
Cargo = reader.IsDBNull(5) ? "" : reader.GetString(5),
DataNascimento = reader.IsDBNull(6) ? null : reader.GetDateTime(6)
});
}
}
return (itens, total);
}
}
// ─────────────────────────────────────────
// CODE-BEHIND
// ─────────────────────────────────────────
public partial class DashboardMain : Window
{
private PessoaRepository? _repo;
private int _paginaAtual = 1;
private int _itensPorPagina = 10;
private int _totalRegistros = 0;
private string _filtroAtual = "";
// Controles da tabela — resolvidos via FindName no OnLoaded
private System.Windows.Controls.Border? _loadingPanel;
private System.Windows.Controls.Border? _emptyPanel;
private System.Windows.Controls.ItemsControl? _listaPessoas;
private System.Windows.Controls.ItemsControl? _paginacaoPanel;
private System.Windows.Controls.Button? _btnAnterior;
private System.Windows.Controls.Button? _btnProximo;
private System.Windows.Controls.TextBlock? _runPaginaInfo;
// ✅ Barras do gráfico de Distribuição por Sexo
private System.Windows.Controls.Border? _barMasc;
private System.Windows.Controls.Border? _barFem;
private System.Windows.Controls.Border? _barOutros;
// ─────────────────────────────────────────
// CONSTRUTOR
// ─────────────────────────────────────────
public DashboardMain()
{
InitializeComponent();
Loaded += OnLoaded;
this.lblCadHoje.Text = ObterCadastrosHoje().ToString();
this.CarregarResumo();
}
// ─────────────────────────────────────────
// QUERIES — inalteradas em relação ao original
// ─────────────────────────────────────────
public int ObterCadastrosHoje()
{
using var conn = new SqlConnection(DadosConexao.ObterStringConexao());
conn.Open();
using var cmd = new SqlCommand(
"SELECT TotalHoje FROM vw_CadastrosHoje", conn);
var resultado = cmd.ExecuteScalar();
return resultado != null ? Convert.ToInt32(resultado) : 0;
}
public (string total, string crescimento) ObterResumoCadastros()
{
using var conn = new SqlConnection(DadosConexao.ObterStringConexao());
conn.Open();
using var cmd = new SqlCommand(
"SELECT TotalCadastros, CrescimentoPercentual FROM vw_ResumoCadastros", conn);
using var reader = cmd.ExecuteReader();
if (reader.Read())
{
int total = reader.GetInt32(0);
decimal crescimento = reader.GetDecimal(1);
return (total.ToString("N0"), $"↑ {Math.Round(crescimento)}%");
}
return ("0", "0%");
}
public (string ativos, string inativos) ObterStatusPessoas()
{
using var conn = new SqlConnection(DadosConexao.ObterStringConexao());
conn.Open();
using var cmd = new SqlCommand(
"SELECT Ativos, Inativos FROM vw_StatusPessoas", conn);
using var reader = cmd.ExecuteReader();
if (reader.Read())
{
int ativos = reader.GetInt32(0);
int inativos = reader.GetInt32(1);
return (ativos.ToString("N0"), inativos.ToString("N0"));
}
return ("0", "0");
}
// ✅ Query original preservada — apenas 3 colunas, sem alterar a view
public (string masc, string fem, string outros) ObterDistribuicaoSexo()
{
using var conn = new SqlConnection(DadosConexao.ObterStringConexao());
conn.Open();
using var cmd = new SqlCommand(
"SELECT PercentMasculino, PercentFeminino, PercentOutros FROM vw_DistribuicaoSexo",
conn);
using var reader = cmd.ExecuteReader();
if (reader.Read())
{
decimal masc = reader.GetDecimal(0);
decimal fem = reader.GetDecimal(1);
decimal outros = reader.GetDecimal(2);
return (
$"{Math.Round(masc)}%",
$"{Math.Round(fem)}%",
$"{Math.Round(outros)}%"
);
}
return ("0%", "0%", "0%");
}
// ─────────────────────────────────────────
// CARREGA RESUMO (cards + gráfico)
// ─────────────────────────────────────────
private void CarregarResumo()
{
var resumo = ObterResumoCadastros();
var status = ObterStatusPessoas();
var sexo = ObterDistribuicaoSexo();
TxtTotalCadastros.Text = resumo.total;
TxtCrescimento.Text = resumo.crescimento;
TxtAtivos.Text = status.ativos;
TxtInativos.Text = status.inativos;
// Percentuais nos TextBlocks do gráfico
TxtMasc.Text = sexo.masc;
TxtFem.Text = sexo.fem;
TxtOutros.Text = sexo.outros;
// Tenta atualizar as barras — se Loaded ainda não rodou,
// os campos serão null e a chamada é no-op.
// O OnLoaded chama novamente para garantir.
AtualizarBarrasGrafico(sexo.masc, sexo.fem, sexo.outros);
}
// ─────────────────────────────────────────
// GRÁFICO DE BARRAS — DISTRIBUIÇÃO POR SEXO
// ─────────────────────────────────────────
/// <summary>
/// Recebe as strings de percentual ("52%", "43%", "5%")
/// e ajusta a largura de cada barra proporcionalmente.
/// </summary>
private void AtualizarBarrasGrafico(string mascPct, string femPct, string outrosPct)
{
const double BarMaxWidth = 250; // px — ajuste se o card mudar de tamanho
const double BarMinWidth = 4; // evita barra invisível quando valor é 0
double m = ParsePct(mascPct);
double f = ParsePct(femPct);
double o = ParsePct(outrosPct);
// Normaliza caso a soma não seja exatamente 100
double soma = m + f + o;
if (soma > 0) { m = m / soma * 100; f = f / soma * 100; o = o / soma * 100; }
if (_barMasc != null) _barMasc.Width = Math.Max(BarMinWidth, m / 100 * BarMaxWidth);
if (_barFem != null) _barFem.Width = Math.Max(BarMinWidth, f / 100 * BarMaxWidth);
if (_barOutros != null) _barOutros.Width = Math.Max(BarMinWidth, o / 100 * BarMaxWidth);
}
/// <summary>Converte "52%" → 52.0 com segurança.</summary>
private static double ParsePct(string pct)
{
var limpo = pct.Replace("%", "").Replace(",", ".").Trim();
return double.TryParse(limpo,
System.Globalization.NumberStyles.Any,
System.Globalization.CultureInfo.InvariantCulture,
out double v) ? v : 0;
}
// ─────────────────────────────────────────
// LOADED
// ─────────────────────────────────────────
private void OnLoaded(object sender, RoutedEventArgs e)
{
// Controles da tabela
_loadingPanel = FindName("LoadingPanel") as System.Windows.Controls.Border;
_emptyPanel = FindName("EmptyPanel") as System.Windows.Controls.Border;
_listaPessoas = FindName("ListaPessoas") as System.Windows.Controls.ItemsControl;
_paginacaoPanel = FindName("PaginacaoPanel") as System.Windows.Controls.ItemsControl;
_btnAnterior = FindName("BtnAnterior") as System.Windows.Controls.Button;
_btnProximo = FindName("BtnProximo") as System.Windows.Controls.Button;
_runPaginaInfo = FindName("RunPaginaInfo") as System.Windows.Controls.TextBlock;
// ✅ Barras do gráfico
_barMasc = FindName("BarMasc") as System.Windows.Controls.Border;
_barFem = FindName("BarFem") as System.Windows.Controls.Border;
_barOutros = FindName("BarOutros") as System.Windows.Controls.Border;
// Aplica as barras agora que os controles estão resolvidos
AtualizarBarrasGrafico(TxtMasc.Text, TxtFem.Text, TxtOutros.Text);
// Inicializa repositório e carrega a tabela
try
{
string conexao = DadosConexao.ObterStringConexao();
if (string.IsNullOrWhiteSpace(conexao))
{
MessageBox.Show(
"String de conexão não configurada.\nVerifique DadosConexao.ObterStringConexao().",
"Configuração", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
_repo = new PessoaRepository(conexao);
CarregarPessoas();
}
catch (Exception ex)
{
MessageBox.Show($"Erro ao inicializar conexão:\n{ex.Message}",
"Erro", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
// ─────────────────────────────────────────
// JANELA
// ─────────────────────────────────────────
private void Window_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left) DragMove();
}
private void BtnFechar_Click(object sender, RoutedEventArgs e)
{
Application.Current.Shutdown();
}
// ─────────────────────────────────────────
// TABELA / PAGINAÇÃO
// ─────────────────────────────────────────
private void CarregarPessoas()
{
if (_repo == null) return;
if (_loadingPanel != null) _loadingPanel.Visibility = Visibility.Visible;
if (_emptyPanel != null) _emptyPanel.Visibility = Visibility.Collapsed;
if (_listaPessoas != null) _listaPessoas.ItemsSource = null;
try
{
var (itens, total) = _repo.Buscar(_filtroAtual, _paginaAtual, _itensPorPagina);
_totalRegistros = total;
if (itens.Count == 0)
{
if (_emptyPanel != null) _emptyPanel.Visibility = Visibility.Visible;
}
else
{
if (_listaPessoas != null) _listaPessoas.ItemsSource = itens;
}
AtualizarPaginacao();
AtualizarInfoRegistros();
}
catch (Exception ex)
{
MessageBox.Show($"Erro ao carregar pessoas:\n{ex.Message}",
"Erro", MessageBoxButton.OK, MessageBoxImage.Error);
}
finally
{
if (_loadingPanel != null) _loadingPanel.Visibility = Visibility.Collapsed;
}
}
private void AtualizarPaginacao()
{
int totalPaginas = (int)Math.Ceiling(_totalRegistros / (double)_itensPorPagina);
if (totalPaginas < 1) totalPaginas = 1;
int inicio = Math.Max(1, _paginaAtual - 2);
int fim = Math.Min(totalPaginas, inicio + 4);
inicio = Math.Max(1, fim - 4);
var paginas = new List<PaginaItem>();
if (inicio > 1)
paginas.Add(new PaginaItem { Label = "..." });
for (int i = inicio; i <= fim; i++)
paginas.Add(new PaginaItem { Label = i.ToString(), IsActive = i == _paginaAtual });
if (fim < totalPaginas)
paginas.Add(new PaginaItem { Label = "..." });
if (_paginacaoPanel != null) _paginacaoPanel.ItemsSource = paginas;
if (_btnAnterior != null) _btnAnterior.IsEnabled = _paginaAtual > 1;
if (_btnProximo != null) _btnProximo.IsEnabled = _paginaAtual < totalPaginas;
}
private void AtualizarInfoRegistros()
{
if (_runPaginaInfo == null) return;
int ini = (_paginaAtual - 1) * _itensPorPagina + 1;
int fim = Math.Min(_paginaAtual * _itensPorPagina, _totalRegistros);
_runPaginaInfo.Text = _totalRegistros == 0
? "0 registros"
: $"{ini}-{fim} de {_totalRegistros:N0}";
}
private void BtnAnterior_Click(object sender, RoutedEventArgs e)
{
if (_paginaAtual > 1) { _paginaAtual--; CarregarPessoas(); }
}
private void BtnProximo_Click(object sender, RoutedEventArgs e)
{
int totalPaginas = (int)Math.Ceiling(_totalRegistros / (double)_itensPorPagina);
if (_paginaAtual < totalPaginas) { _paginaAtual++; CarregarPessoas(); }
}
private void CmbItensPorPagina_SelectionChanged(object sender,
System.Windows.Controls.SelectionChangedEventArgs e)
{
if (CmbItensPorPagina?.SelectedItem is System.Windows.Controls.ComboBoxItem item
&& int.TryParse(item.Content?.ToString(), out int qtd))
{
_itensPorPagina = qtd;
_paginaAtual = 1;
CarregarPessoas();
}
}
private void BtnNovoCadastro_Click(object sender, RoutedEventArgs e)
{
var cp = new UI.Cadastro.FrmCadastroClientes();
cp.ShowDialog();
}
}
}