ContagemLigacoes/Program.cs
Giuliano Paschoalino 5e5618dd6d Refatoração para modularidade e melhorias visuais
- Código refatorado para maior modularidade e legibilidade, com criação de métodos auxiliares como `ExibirCabecalho`, `CriarTabelaLigacoes` e outros.
- Substituição de código inline por métodos reutilizáveis, reduzindo duplicação e centralizando lógica.
- Melhorias na exibição de dados com tabelas e painéis mais organizados.
- Lógica de consulta e contagem de registros encapsulada em métodos dedicados (`ObterRegistrosUsuario`, `ContarRegistros`).
- Introdução de formatação condicional para contagens e valores numéricos.
- Uso de LINQ para simplificar manipulação de coleções.
- Remoção de código redundante, dependências desnecessárias e variáveis globais fixas.
- Mensagens de erro mais informativas e consistentes.
- Adição de comentários explicativos e organização com `#region Helpers`.
- Funcionalidade principal mantida, com melhorias na experiência do usuário.
2025-07-21 17:41:49 -03:00

360 lines
15 KiB
C#

using System.Data.Common;
using System.Numerics;
using Npgsql;
using Spectre.Console;
class Program
{
static DateTime _endTime;
static bool _stop = false;
static bool _isAuthenticated = false;
static readonly string _connectionString = "Server=192.168.10.248;Port=5432;Database=pipefy_move_cards;User Id=postgres;Password=gds21;";
static readonly string _windowsID = Environment.UserName;
//static readonly string _windowsID = "comercial6";
static readonly List<(string actionId, string actionName)> _actionIds = // Tuplas de IDs e nomes das fases que queremos monitorar
[
new ("318642783-318906957", "Agendou reunião 1"),
new ("322784034-332649256", "Agendou reunião 2"),
new ("332659066-332653733", "Agendou reunião 3"),
new ("332659083-339300210", "Agendou reunião 4"),
new ("339300218-339300231", "Agendou reunião 5"),
new ("318906957-322784034", "Realizou reunião 1"),
new ("332649256-332659066", "Realizou reunião 2"),
new ("332653733-332659083", "Realizou reunião 3"),
new ("339300210-339300218", "Realizou reunião 4"),
new ("339300231-339300232", "Realizou reunião 5"),
new ("318906957-318642783", "Reunião cancelada"),
new ("332649256-322784034", "Reunião cancelada"),
new ("332653733-332659066", "Reunião cancelada"),
new ("339300210-332659083", "Reunião cancelada"),
new ("339300231-339300218", "Reunião cancelada"),
new ("325103205-325103208", "Agendou reunião 1"),
new ("325103212-332710299", "Agendou reunião 2"),
new ("332710321-332710322", "Agendou reunião 3"),
new ("332710362-339304168", "Agendou reunião 4"),
new ("339304193-339304215", "Agendou reunião 5"),
new ("325103208-325103212", "Realizou reunião 1"),
new ("332710299-332710321", "Realizou reunião 2"),
new ("332710322-332710362", "Realizou reunião 3"),
new ("339304168-339304193", "Realizou reunião 4"),
new ("339304215-339304218", "Realizou reunião 5"),
new ("325103208-325103205", "Reunião cancelada"),
new ("332710299-325103212", "Reunião cancelada"),
new ("332710322-332710321", "Reunião cancelada"),
new ("339304168-332710362", "Reunião cancelada"),
new ("339304215-339304193", "Reunião cancelada")
];
// Agora guardamos uma lista de tuplas (UserID, Nome)
static readonly List<(BigInteger UserId, string Nome)> _pipeUsers = [];
static void Main()
{
AnsiConsole.Clear();
// 1) Autenticação (pode retornar múltiplos usuários)
Autenticacao();
if (!_isAuthenticated)
{
AnsiConsole.MarkupLine("[bold red]Nenhum usuário encontrado ou erro na autenticação. Saindo...[/]");
Thread.Sleep(3000);
return;
}
// 2) Loop principal: atualizar dados e aguardar entrada
while (!_stop)
{
AtualizarDados();
AguardarEntrada();
}
AnsiConsole.MarkupLine("[bold yellow]Aplicação encerrada.[/]");
}
public class Record
{
public int? Id { get; set; }
public string? Action { get; set; }
public string? User { get; set; }
public string? FieldID { get; set; }
public int? From { get; set; }
public int? To { get; set; }
public int? CardID { get; set; }
public string? PipeSUID { get; set; }
public DateTime MovedAt { get; set; }
public required string Title { get; set; }
public string? AcaoID { get; set; }
public required string Acao { get; set; }
}
static void AtualizarDados()
{
var records = new List<Record>();
AnsiConsole.Clear();
ExibirCabecalho();
var tableContLigacoes = CriarTabelaLigacoes();
var tableContReunioes = CriarTabelaReunioes();
// Para cada usuário, rodar uma query COUNT(*) e adicionar linha na tabela
foreach (var (userId, nome) in _pipeUsers)
{
var userRecords = ObterRegistrosUsuario(userId, nome);
var (count, countA, countR, countC) = ContarRegistros(userRecords);
var format = ObterFormatoCor(count);
tableContLigacoes.AddRow(nome, userId.ToString(), $"{format} {count} [/]");
tableContReunioes.AddRow(nome, userId.ToString(), $"[yellow] {countA:#;-#;} [/]", $"[green] {countR:#;-#;} [/]", $"[red] {countC:#;-#;} [/]");
records.AddRange(userRecords);
}
ExibirPainel(tableContLigacoes, "[blue bold] Ligações [/]");
ExibirPainel(tableContReunioes, "[blue bold] Reuniões Agendadas e Realizadas [/]");
var tableReunioes = CriarTabelaDetalhesReunioes();
foreach (var record in records.OrderBy(x => x.User).ThenBy(x => x.Acao).ThenBy(x => x.MovedAt.Date))
{
if (record.Acao is not null)
{
tableReunioes.AddRow(
record.MovedAt.ToString("d"),
record.Acao ?? "Ação desconhecida",
record.Title ?? "",
$"[link=https://app.pipefy.com/open-cards/{record.CardID}]{record.CardID}[/]",
record.User ?? ""
);
}
}
ExibirPainel(tableReunioes, "[blue bold] Reuniões no Mês [/]");
AnsiConsole.MarkupLine("\n[gray]Pressione [green]ENTER[/] para atualizar agora ou qualquer outra tecla para sair.[/]");
}
static void ExibirCabecalho()
{
// Painel de cabeçalho que mostra quantos usuários foram carregados
var status = new Align(new Panel($"[bold]WindowsID[/]: {_windowsID} | [bold]Usuários encontrados[/]: {_pipeUsers.Count}"), HorizontalAlignment.Center, VerticalAlignment.Top);
var header = new Panel(status)
.Header("[yellow]Resumo Diário por Usuário[/]", Justify.Center)
.Expand();
AnsiConsole.Write(new Align(header, HorizontalAlignment.Center, VerticalAlignment.Top));
}
static Table CriarTabelaLigacoes()
{
var table = new Table().Border(TableBorder.Rounded).Alignment(Justify.Center);
table.AddColumn(new TableColumn("[green]Nome[/]").Centered());
table.AddColumn(new TableColumn("[green]UserID[/]").Centered());
table.AddColumn(new TableColumn("[green]Registros Hoje[/]").Centered());
return table;
}
static Table CriarTabelaReunioes()
{
var table = new Table().Border(TableBorder.Rounded).Alignment(Justify.Center);
table.AddColumn(new TableColumn("[white]Nome[/]").Centered());
table.AddColumn(new TableColumn("[white]UserID[/]").Centered());
table.AddColumn(new TableColumn("[yellow]Agendadas[/]").Centered());
table.AddColumn(new TableColumn("[green]Realizadas[/]").Centered());
table.AddColumn(new TableColumn("[red]Canceladas[/]").Centered());
return table;
}
static Table CriarTabelaDetalhesReunioes()
{
var table = new Table().Border(TableBorder.Rounded).Alignment(Justify.Center);
table.AddColumn(new TableColumn("[green]Data[/]").Centered());
table.AddColumn(new TableColumn("[green]Status[/]").Centered());
table.AddColumn(new TableColumn("[green]Empresa[/]").Centered());
table.AddColumn(new TableColumn("[green]Card ID\n(Ctrl + click)[/]").Centered());
table.AddColumn(new TableColumn("[green]Prospectante[/]").Centered());
return table;
}
static void ExibirPainel(Table table, string titulo)
{
var panel = new Panel(new Align(table, HorizontalAlignment.Center, VerticalAlignment.Top))
.Header(titulo, Justify.Center)
.Border(BoxBorder.Double)
.Expand();
AnsiConsole.Write(new Align(panel, HorizontalAlignment.Center, VerticalAlignment.Top));
}
static List<Record> ObterRegistrosUsuario(BigInteger userId, string nome)
{
var userRecords = new List<Record>();
AnsiConsole.Status()
.Spinner(Spinner.Known.Line)
.Start($"[blue]Obtendo registros de [yellow]{nome}[/]...[/]", ctx =>
{
const string query = @"
SELECT * FROM public.""ActionsHistory""
WHERE ""UserID"" = @pipeUser AND
""MovedAt"" >= date_trunc('month', CURRENT_DATE)";
try
{
using var conn = new NpgsqlConnection(_connectionString);
conn.Open();
using var cmd = new NpgsqlCommand(query, conn);
cmd.Parameters.AddWithValue("@pipeUser", userId);
using var reader = cmd.ExecuteReader();
while (reader.Read())
{
var temp = new Record
{
Id = ToIntOrNull(reader, "Id"),
Action = ToStringOrNull(reader, "Action"),
User = _pipeUsers.Find(u => u.UserId == ToIntOrNull(reader, "UserID")).Nome,
FieldID = ToStringOrNull(reader, "FieldID"),
From = ToIntOrNull(reader, "From"),
To = ToIntOrNull(reader, "To"),
CardID = ToIntOrNull(reader, "CardID"),
PipeSUID = ToStringOrNull(reader, "PipeSUID"),
MovedAt = (DateTime)(reader["MovedAt"] ?? DateTime.MinValue),
Title = ToStringOrNull(reader, "Title") ?? "-",
Acao = "Desconhecida"
};
temp.AcaoID = temp.From + "-" + temp.To;
temp.Acao = _actionIds.Find(p => p.actionId == temp.AcaoID).actionName;
userRecords.Add(temp);
}
}
catch (Exception ex)
{
AnsiConsole.MarkupLine($"\n[red]Erro ao consultar {nome}:[/] {ex.Message}");
}
});
return userRecords;
}
static (int count, int countA, int countR, int countC) ContarRegistros(List<Record> userRecords)
{
int count = userRecords.Count(x => x.MovedAt > DateTime.Today && x.FieldID is not null);
int countR = userRecords.Count(x => (x.Acao ?? "").StartsWith("Realizou"));
int countA = userRecords.Count(x => (x.Acao ?? "").StartsWith("Agendou"));
int countC = userRecords.Count(x => (x.Acao ?? "").EndsWith("cancelada"));
return (count, countA, countR, countC);
}
static string ObterFormatoCor(int count)
{
var format = "";
switch (count)
{
case int n when (n < 20):
format = "[red]";
break;
case int n when (n >= 20 && n < 40):
format = "[yellow]";
break;
case int n when (n >= 40 && n < 60):
format = "[blue]";
break;
case int n when (n >= 60):
format = "[white on green bold]";
break;
}
return format;
}
static void AguardarEntrada()
{
// Tempo de espera em segundos (ex.: 10 minutos = 600s)
int intervalSeconds = 600;
_endTime = DateTime.Now.AddSeconds(intervalSeconds);
// Escreve uma linha em branco para “reservar” o lugar do contador
AnsiConsole.WriteLine("");
// Começa o loop que vai atualizar a mesma linha a cada segundo
while (!_stop)
{
var remaining = _endTime - DateTime.Now;
if (remaining.TotalMilliseconds <= 0)
{
// Quando chegar a zero, exibe a mensagem de atualização automática
AnsiConsole.MarkupLine("\r[red]Atualização automática agora![/]");
Thread.Sleep(1000);
break;
}
// Monta o texto de “Próxima atualização em MM:SS”
AnsiConsole.Markup($"\r[blue]Próxima atualização em {remaining.Minutes:00}:{remaining.Seconds:00}[/]");
// Pequena pausa de 1 segundo
Thread.Sleep(1000);
// Se o usuário apertar qualquer tecla…
if (Console.KeyAvailable)
{
var key = Console.ReadKey(true);
// …e não for ENTER, sinalizamos para parar o loop e sair
if (key.Key != ConsoleKey.Enter)
_stop = true;
else
break;
}
}
// Garante que, ao sair do loop, o cursor fique embaixo do texto
Console.WriteLine("");
}
static void Autenticacao()
{
AnsiConsole.Status()
.Spinner(Spinner.Known.Dots)
.Start("[green]Autenticando usuário...[/]", ctx =>
{
const string query = @"
SELECT ""UserID"", ""Nome""
FROM public.""usuarios""
WHERE ""windowsID"" = @WindowsID";
try
{
using var conn = new NpgsqlConnection(_connectionString);
conn.Open();
using var cmd = new NpgsqlCommand(query, conn);
cmd.Parameters.AddWithValue("@WindowsID", _windowsID);
using var reader = cmd.ExecuteReader();
while (reader.Read())
{
var userId = BigInteger.Parse(reader["UserID"].ToString()!);
var nome = reader["Nome"].ToString()!;
_pipeUsers.Add((userId, nome));
}
if (_pipeUsers.Count > 0)
{
_isAuthenticated = true;
}
else
{
AnsiConsole.MarkupLine("[bold red]Nenhum usuário encontrado para este WindowsID.[/]");
_isAuthenticated = false;
}
}
catch (Exception ex)
{
AnsiConsole.MarkupLine($"\n[red]Erro na autenticação:[/] {ex.Message}");
_isAuthenticated = false;
}
});
}
#region Helpers
static int? ToIntOrNull(DbDataReader r, string col)
{
return r.IsDBNull(r.GetOrdinal(col))
? null
: Convert.ToInt32(r[col]); ;
}
static string? ToStringOrNull(DbDataReader r, string col)
{
return r.IsDBNull(r.GetOrdinal(col))
? null
: r.GetString(r.GetOrdinal(col));
}
#endregion
}