using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; using System.Windows.Forms; using System.Globalization; namespace UI { // ═══════════════════════════════════════════════════════════════════════ // 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 _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 } } }