LevelOS/LevelOS/Dashboards/Home/DashboardPanel.cs

369 lines
16 KiB
C#

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using System.Globalization;
namespace LevelOS
{
// ═══════════════════════════════════════════════════════════════════════
// DashboardPanel — painel principal com KPI cards + tabela de OS
// ═══════════════════════════════════════════════════════════════════════
public class DashboardPanel : UserControl
{
// ── Paleta ─────────────────────────────────────────────────────────
private static readonly Color Surface = Color.FromArgb(255, 255, 255);
private static readonly Color Surface2 = Color.FromArgb(248, 250, 252);
private static readonly Color Border = Color.FromArgb(226, 232, 240);
private static readonly Color TextPri = Color.FromArgb(15, 23, 42);
private static readonly Color TextSec = Color.FromArgb(71, 85, 105);
private static readonly Color TextMuted = Color.FromArgb(148, 163, 184);
private static readonly Color Blue = Color.FromArgb(37, 99, 235);
private static readonly Color BlueLight = Color.FromArgb(219, 234, 254);
private static readonly Color Green = Color.FromArgb(22, 163, 74);
private static readonly Color GreenL = Color.FromArgb(220, 252, 231);
private static readonly Color Amber = Color.FromArgb(217, 119, 6);
private static readonly Color AmberL = Color.FromArgb(254, 243, 199);
private static readonly Color Red = Color.FromArgb(220, 38, 38);
private static readonly Color RedL = Color.FromArgb(254, 226, 226);
private static readonly Color Orange = Color.FromArgb(234, 88, 12);
private static readonly Color OrangeL = Color.FromArgb(255, 237, 213);
// ── Dados de OS ────────────────────────────────────────────────────
private readonly List<OsRow> _ordens = new()
{
new OsRow("#0001", "João Silva", "Notebook Dell", OsStatus.EmAndamento, "07/04"),
new OsRow("#0002", "Maria Souza", "PC Gamer", OsStatus.Concluida, "07/04"),
new OsRow("#0003", "Carlos Mota", "Impressora HP", OsStatus.Aguardando, "08/04"),
new OsRow("#0004", "Ana Lima", "iPhone 14", OsStatus.EmAndamento, "08/04"),
new OsRow("#0005", "Pedro Rocha", "Monitor LG", OsStatus.Pendente, "09/04"),
};
// ── KPI cards ──────────────────────────────────────────────────────
// CORRIGIDO: instanciação posicional explícita (sem parâmetros nomeados)
private readonly KpiCard[] _kpis;
// ── Layout ─────────────────────────────────────────────────────────
private const int Pad = 24;
private const int KpiH = 110;
private const int KpiGap = 14;
private const int CardRad = 10;
private const int TopbarH = 56;
private const int RowH = 38;
private readonly Font _fLabel;
private readonly Font _fValue;
private readonly Font _fDelta;
private readonly Font _fHead;
private readonly Font _fCell;
private readonly Font _fColHead;
private readonly Font _fMono;
private readonly Font _fBtn;
private readonly Font _fTopTitle;
private readonly Font _fTopSub;
private Rectangle _btnNovaOS;
private int _hoverRow = -1;
public DashboardPanel()
{
// CORRIGIDO: inicialização dos campos Font no construtor
// (evita o aviso "campo não anulável precisa conter valor não nulo")
_fLabel = new Font("Segoe UI", 8.5f);
_fValue = new Font("Segoe UI Semibold", 18f);
_fDelta = new Font("Segoe UI", 9f);
_fHead = new Font("Segoe UI Semibold", 10.5f);
_fCell = new Font("Segoe UI", 10f);
_fColHead = new Font("Segoe UI", 8f);
_fMono = new Font("Consolas", 9.5f);
_fBtn = new Font("Segoe UI Semibold", 10f);
_fTopTitle = new Font("Segoe UI Semibold", 13f);
_fTopSub = new Font("Segoe UI", 9f);
// CORRIGIDO: KpiCard instanciado com construtor posicional explícito
_kpis = new[]
{
new KpiCard("OS Abertas", "12", "↑ 4 essa semana", true, BlueLight, Blue),
new KpiCard("OS Concluídas", "45", "↑ 12% vs mês", true, GreenL, Green),
new KpiCard("Faturamento", "22.5k", "↑ R$ 2.100", true, AmberL, Amber),
new KpiCard("Pendências", "3", "↓ aguardando", false, RedL, Red),
};
BackColor = Surface2;
DoubleBuffered = true;
SetStyle(ControlStyles.AllPaintingInWmPaint |
ControlStyles.OptimizedDoubleBuffer |
ControlStyles.ResizeRedraw, true);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_fLabel.Dispose(); _fValue.Dispose(); _fDelta.Dispose();
_fHead.Dispose(); _fCell.Dispose(); _fColHead.Dispose();
_fMono.Dispose(); _fBtn.Dispose(); _fTopTitle.Dispose();
_fTopSub.Dispose();
}
base.Dispose(disposing);
}
// ── Paint ──────────────────────────────────────────────────────────
protected override void OnPaint(PaintEventArgs e)
{
var g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
DrawTopbar(g);
int y = TopbarH + Pad;
DrawKpiRow(g, y);
y += KpiH + 20;
DrawOsCard(g, y);
}
// ── Topbar ─────────────────────────────────────────────────────────
private void DrawTopbar(Graphics g)
{
g.FillRectangle(new SolidBrush(Surface), 0, 0, Width, TopbarH);
g.DrawLine(new Pen(Border, 1), 0, TopbarH, Width, TopbarH);
g.DrawString("Home", _fTopTitle, new SolidBrush(TextPri), Pad, 12);
//g.DrawString("Quinta, 09 de abril de 2026", _fTopSub,
g.DrawString(
DateTime.Now.ToString("dd 'de' MMMM 'de' yyyy", new CultureInfo("pt-BR")), _fTopSub,
new SolidBrush(TextMuted), Pad, 32);
int bw = 110, bh = 32;
int bx = Width - Pad - bw;
int by = (TopbarH - bh) / 2;
_btnNovaOS = new Rectangle(bx, by, bw, bh);
FillRoundRect(g, new SolidBrush(Blue), _btnNovaOS, 6);
var sf = new StringFormat
{
Alignment = StringAlignment.Center,
LineAlignment = StringAlignment.Center
};
g.DrawString("+ Nova OS", _fBtn, new SolidBrush(Color.White), _btnNovaOS, sf);
}
// ── KPI Row ────────────────────────────────────────────────────────
private void DrawKpiRow(Graphics g, int y)
{
int totalGap = KpiGap * (_kpis.Length - 1);
int kpiW = (Width - Pad * 2 - totalGap) / _kpis.Length;
for (int i = 0; i < _kpis.Length; i++)
{
int x = Pad + i * (kpiW + KpiGap);
DrawKpiCard(g, _kpis[i], new Rectangle(x, y, kpiW, KpiH));
}
}
private void DrawKpiCard(Graphics g, KpiCard kpi, Rectangle r)
{
FillRoundRect(g, new SolidBrush(Surface), r, CardRad);
DrawRoundBorder(g, r, CardRad, Border);
int dotSize = 36;
var dotRect = new Rectangle(r.Right - 16 - dotSize, r.Y + 16, dotSize, dotSize);
FillRoundRect(g, new SolidBrush(kpi.DotBg), dotRect, 8);
g.DrawString(kpi.Label, _fLabel, new SolidBrush(TextMuted), r.X + 16, r.Y + 18);
g.DrawString(kpi.Value, _fValue, new SolidBrush(TextPri), r.X + 14, r.Y + 36);
var deltaColor = kpi.DeltaUp ? Green : Red;
g.DrawString(kpi.Delta, _fDelta, new SolidBrush(deltaColor), r.X + 16, r.Y + 76);
}
// ── OS Card ────────────────────────────────────────────────────────
private void DrawOsCard(Graphics g, int y)
{
string[] cols = { "OS", "Cliente", "Equipamento", "Status", "Data" };
float[] colW = { 0.08f, 0.22f, 0.28f, 0.22f, 0.10f };
int cardH = 28 + RowH + RowH * _ordens.Count + 16;
var card = new Rectangle(Pad, y, Width - Pad * 2, cardH);
FillRoundRect(g, new SolidBrush(Surface), card, CardRad);
DrawRoundBorder(g, card, CardRad, Border);
g.DrawString("Ordens de Serviço Recentes", _fHead,
new SolidBrush(TextPri), card.X + 18, card.Y + 16);
int tableY = card.Y + 44;
g.DrawLine(new Pen(Border, 0.5f), card.X, tableY, card.Right, tableY);
int cx = card.X;
for (int c = 0; c < cols.Length; c++)
{
int cw = (int)(card.Width * colW[c]);
var sf = new StringFormat { LineAlignment = StringAlignment.Center };
g.DrawString(cols[c].ToUpperInvariant(), _fColHead,
new SolidBrush(TextMuted), cx + 10, tableY + 2, sf);
cx += cw;
}
for (int i = 0; i < _ordens.Count; i++)
{
int ry = tableY + RowH + i * RowH;
var row = _ordens[i];
if (i == _hoverRow)
g.FillRectangle(new SolidBrush(Surface2),
card.X + 1, ry, card.Width - 2, RowH);
g.DrawLine(new Pen(Border, 0.5f), card.X, ry, card.Right, ry);
cx = card.X;
int[] cws = GetColWidths(card.Width, colW);
g.DrawString(row.OS, _fMono,
new SolidBrush(TextMuted), cx + 10, ry + (RowH - 14) / 2);
cx += cws[0];
g.DrawString(row.Cliente, _fCell,
new SolidBrush(TextPri), cx + 8, ry + (RowH - 14) / 2);
cx += cws[1];
g.DrawString(row.Equipamento, _fCell,
new SolidBrush(TextSec), cx + 8, ry + (RowH - 14) / 2);
cx += cws[2];
DrawBadge(g, row.Status, cx + 8, ry + (RowH - 20) / 2);
cx += cws[3];
g.DrawString(row.Data, _fCell,
new SolidBrush(TextMuted), cx + 8, ry + (RowH - 14) / 2);
}
}
private static int[] GetColWidths(int total, float[] ratios)
{
var ws = new int[ratios.Length];
for (int i = 0; i < ratios.Length; i++)
ws[i] = (int)(total * ratios[i]);
return ws;
}
private void DrawBadge(Graphics g, OsStatus status, int x, int y)
{
var (label, bg, fg) = status switch
{
OsStatus.Concluida => ("Concluída", GreenL, Color.FromArgb(22, 101, 52)),
OsStatus.EmAndamento => ("Em andamento", AmberL, Color.FromArgb(146, 64, 14)),
OsStatus.Aguardando => ("Aguardando", BlueLight, Color.FromArgb(30, 64, 175)),
OsStatus.Pendente => ("Pendente", RedL, Color.FromArgb(153, 27, 27)),
_ => ("—", Border, TextMuted)
};
var sz = g.MeasureString(label, _fDelta);
int bw = (int)sz.Width + 16;
int bh = 20;
var badge = new Rectangle(x, y, bw, bh);
FillRoundRect(g, new SolidBrush(bg), badge, 10);
var sf = new StringFormat
{
Alignment = StringAlignment.Center,
LineAlignment = StringAlignment.Center
};
g.DrawString(label, _fDelta, new SolidBrush(fg), badge, sf);
}
// ── Mouse ──────────────────────────────────────────────────────────
protected override void OnMouseMove(MouseEventArgs e)
{
int row = HitTestRow(e.Y);
if (row != _hoverRow) { _hoverRow = row; Invalidate(); }
Cursor = _btnNovaOS.Contains(e.Location) ? Cursors.Hand : Cursors.Default;
}
protected override void OnMouseLeave(EventArgs e)
{
_hoverRow = -1;
Invalidate();
}
private int HitTestRow(int mouseY)
{
int tableY = TopbarH + Pad + KpiH + 20 + 44 + RowH;
for (int i = 0; i < _ordens.Count; i++)
{
int ry = tableY + i * RowH;
if (mouseY >= ry && mouseY < ry + RowH) return i;
}
return -1;
}
// ── GDI Helpers ────────────────────────────────────────────────────
private static void FillRoundRect(Graphics g, Brush brush, Rectangle r, int radius)
{
using var path = RoundRectPath(r, radius);
g.FillPath(brush, path);
}
private static void DrawRoundBorder(Graphics g, Rectangle r, int radius, Color color)
{
using var path = RoundRectPath(r, radius);
g.DrawPath(new Pen(color, 0.5f), path);
}
private static GraphicsPath RoundRectPath(Rectangle r, int radius)
{
var path = new GraphicsPath();
path.AddArc(r.X, r.Y, radius * 2, radius * 2, 180, 90);
path.AddArc(r.Right - radius * 2, r.Y, radius * 2, radius * 2, 270, 90);
path.AddArc(r.Right - radius * 2, r.Bottom - radius * 2, radius * 2, radius * 2, 0, 90);
path.AddArc(r.X, r.Bottom - radius * 2, radius * 2, radius * 2, 90, 90);
path.CloseFigure();
return path;
}
// ── Modelos ────────────────────────────────────────────────────────
// CORRIGIDO: usando classes simples em vez de records posicionais
// para compatibilidade com qualquer versão do compilador .NET 6+.
private class OsRow
{
public string OS { get; }
public string Cliente { get; }
public string Equipamento { get; }
public OsStatus Status { get; }
public string Data { get; }
public OsRow(string os, string cliente, string equipamento, OsStatus status, string data)
{
OS = os;
Cliente = cliente;
Equipamento = equipamento;
Status = status;
Data = data;
}
}
private class KpiCard
{
public string Label { get; }
public string Value { get; }
public string Delta { get; }
public bool DeltaUp { get; }
public Color DotBg { get; }
public Color DotFg { get; }
public KpiCard(string label, string value, string delta, bool deltaUp, Color dotBg, Color dotFg)
{
Label = label;
Value = value;
Delta = delta;
DeltaUp = deltaUp;
DotBg = dotBg;
DotFg = dotFg;
}
}
private enum OsStatus { Concluida, EmAndamento, Aguardando, Pendente }
}
}