ContagemLigacoes/Program.cs
Giuliano Paschoalino ad6bd1f479 Melhorias em autenticação, tabelas e tratamento de erros
- Substituído `Console.ReadLine()` por `Thread.Sleep(3000)` após erro de autenticação para pausar execução.
- Tornada obrigatória a propriedade `Acao` na classe `Record`.
- Criada tabela `tableContReunioes` para exibir dados de reuniões.
- Adicionados contadores para reuniões agendadas, realizadas e canceladas.
- Implementada lógica para preencher `tableContReunioes` com dados calculados.
- Melhorado tratamento de exceções com `try-catch` para capturar erros.
- Alterada lógica de ordenação para priorizar data, ação e usuário.
- Adicionado painel com cabeçalho "Reuniões Agendadas e Realizadas".
- Ajustado valor padrão de `Acao` para "Desconhecida" em novos registros.
2025-07-21 16:26:38 -03:00

332 lines
15 KiB
C#

using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Numerics;
using System.Threading.Tasks;
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 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();
// 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));
// Montamos uma tabela com: Nome do usuário | UserID | Registros Hoje
var tableContLigacoes = new Table().Border(TableBorder.Rounded).Alignment(Justify.Center);
tableContLigacoes.Border(TableBorder.Rounded);
tableContLigacoes.AddColumn(new TableColumn("[green]Nome[/]").Centered());
tableContLigacoes.AddColumn(new TableColumn("[green]UserID[/]").Centered());
tableContLigacoes.AddColumn(new TableColumn("[green]Registros Hoje[/]").Centered());
// Montamos uma tabela com: Nome do usuário | UserID | Registros Hoje
var tableContReunioes = new Table().Border(TableBorder.Rounded).Alignment(Justify.Center);
tableContReunioes.Border(TableBorder.Rounded);
tableContReunioes.AddColumn(new TableColumn("[green]Nome[/]").Centered());
tableContReunioes.AddColumn(new TableColumn("[green]UserID[/]").Centered());
tableContReunioes.AddColumn(new TableColumn("[green]Agendadas[/]").Centered());
tableContReunioes.AddColumn(new TableColumn("[green]Realizadas[/]").Centered());
tableContReunioes.AddColumn(new TableColumn("[green]Canceladas[/]").Centered());
// Para cada usuário, rodar uma query COUNT(*) e adicionar linha na tabela
foreach (var (userId, nome) in _pipeUsers)
{
var userRecords = new List<Record>();
int count = 0;
int countR = 0;
int countA = 0;
int countC = 0;
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);
var reader = cmd.ExecuteReader();
using (reader)
{
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)
{
// Em caso de erro, exibimos zero e continuamos
count = 0;
AnsiConsole.MarkupLine($"\n[red]Erro ao consultar {nome}:[/] {ex.Message}");
}
});
count = userRecords.Where(x => (x.MovedAt > DateTime.Today && x.FieldID is not null)).ToList().Count;
countR = userRecords.Where(x => ((x.Acao ?? "").StartsWith("Realizou"))).ToList().Count;
countA = userRecords.Where(x => ((x.Acao ?? "").StartsWith("Agendou"))).ToList().Count;
countC = userRecords.Where(x => ((x.Acao ?? "").EndsWith("cancelada"))).ToList().Count;
tableContLigacoes.AddRow(nome, userId.ToString(), $"[bold yellow]{count}[/]");
tableContReunioes.AddRow(nome, userId.ToString(), $"[bold yellow]{countA}[/]", $"[bold yellow]{countR}[/]", $"[bold yellow]{countC}[/]");
records.AddRange(userRecords);
}
var panel = new Panel(new Align(tableContLigacoes, HorizontalAlignment.Center, VerticalAlignment.Top))
.Header("[blue bold] Ligações [/]", Justify.Center)
.Border(BoxBorder.Double)
.Expand();
AnsiConsole.Write(new Align(panel, HorizontalAlignment.Center, VerticalAlignment.Top));
panel = new Panel(new Align(tableContReunioes, HorizontalAlignment.Center, VerticalAlignment.Top))
.Header("[blue bold] Reuniões Agendadas e Realizadas [/]", Justify.Center)
.Border(BoxBorder.Double)
.Expand();
AnsiConsole.Write(new Align(panel, HorizontalAlignment.Center, VerticalAlignment.Top));
// Montamos uma tabela com: Nome do usuário | UserID | Registros Hoje
var tableReunioes = new Table().Border(TableBorder.Rounded).Alignment(Justify.Center);
tableReunioes.AddColumn(new TableColumn("[green]Data[/]").Centered());
tableReunioes.AddColumn(new TableColumn("[green]Status[/]").Centered());
tableReunioes.AddColumn(new TableColumn("[green]Empresa[/]").Centered());
tableReunioes.AddColumn(new TableColumn("[green]Card ID\n(Ctrl + click)[/]").Centered());
tableReunioes.AddColumn(new TableColumn("[green]Prospectante[/]").Centered());
records = [.. records.OrderBy(x => x.MovedAt.Date).OrderBy(x => x.Acao).OrderBy(y => y.User)];
// Para cada usuário, rodar uma query COUNT(*) e adicionar linha na tabela
foreach (var record in records)
{
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 ?? ""
);
}
}
panel = new Panel(new Align(tableReunioes, HorizontalAlignment.Center, VerticalAlignment.Top))
.Header("[blue bold] Reuniões no Mês [/]", Justify.Center)
.Border(BoxBorder.Double)
.Expand();
AnsiConsole.Write(new Align(panel, HorizontalAlignment.Center, VerticalAlignment.Top));
AnsiConsole.MarkupLine("\n[gray]Pressione [green]ENTER[/] para atualizar agora ou qualquer outra tecla para sair.[/]");
}
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
}