369 lines
16 KiB
C#
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 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<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 }
|
|
}
|
|
}
|