461 lines
19 KiB
C#
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();
|
|
}
|
|
}
|
|
}
|