diff --git a/frontend/src/components/Calendar/CalendarView.tsx b/frontend/src/components/Calendar/CalendarView.tsx index 68a720e..793533d 100644 --- a/frontend/src/components/Calendar/CalendarView.tsx +++ b/frontend/src/components/Calendar/CalendarView.tsx @@ -16,76 +16,50 @@ export function CalendarView({ events }: CalendarViewProps) { (e) => !e.start.startsWith(today) && !e.start.startsWith(tomorrow) ); - const formatDate = (dateStr: string) => { - return new Date(dateStr).toLocaleDateString("es-MX", { - weekday: "long", - month: "long", - day: "numeric", - }); - }; - - const hasEvents = events.length > 0; + const fmt = (d: string) => + new Date(d).toLocaleDateString("es-MX", { weekday: "long", month: "long", day: "numeric" }); return (
-
- +
+ {events.length} eventos esta semana
- -
- {!hasEvents ? ( -
- πŸ“… -

- Sin eventos programados -

-

- Los prΓ³ximos eventos del calendario de Odoo aparecerΓ‘n aquΓ­ -

+
+ {events.length === 0 ? ( +
+ πŸ“… +

Sin eventos programados

+

Los eventos de Odoo aparecerΓ‘n aquΓ­

) : ( -
+
-

Hoy

-

{formatDate(today)}

- {todayEvents.length === 0 ? ( -

Sin eventos

- ) : ( -
- {todayEvents.map((e) => )} -
- )} +

Hoy

+

{fmt(today)}

+ {todayEvents.length === 0 + ?

Sin eventos

+ :
{todayEvents.map((e) => )}
}
-

MaΓ±ana

-

{formatDate(tomorrow)}

- {tomorrowEvents.length === 0 ? ( -

Sin eventos

- ) : ( -
- {tomorrowEvents.map((e) => )} -
- )} +

MaΓ±ana

+

{fmt(tomorrow)}

+ {tomorrowEvents.length === 0 + ?

Sin eventos

+ :
{tomorrowEvents.map((e) => )}
}
-

Esta semana

-

PrΓ³ximos dΓ­as

- {laterEvents.length === 0 ? ( -

Sin eventos

- ) : ( -
- {laterEvents.map((e) => ( -
- - {formatDate(e.start)} - +

Esta semana

+

PrΓ³ximos dΓ­as

+ {laterEvents.length === 0 + ?

Sin eventos

+ :
{laterEvents.map((e) => ( +
+ {fmt(e.start)} {e.name}
- ))} -
- )} + ))}
}
)} diff --git a/frontend/src/components/Layout/Header.tsx b/frontend/src/components/Layout/Header.tsx index 9293092..2a5bf12 100644 --- a/frontend/src/components/Layout/Header.tsx +++ b/frontend/src/components/Layout/Header.tsx @@ -27,25 +27,18 @@ export function Header({ viewName, connected }: HeaderProps) { }); return ( -
-
-

- Consultoria AS -

+
+
+

Consultoria AS

| - {viewName} + {viewName}
-
- {dateStr} - {timeStr} -
- - - {connected ? "Conectado" : "Sin conexiΓ³n"} - -
+
+ {dateStr} + {timeStr} +
); diff --git a/frontend/src/components/Tasks/KanbanBoard.tsx b/frontend/src/components/Tasks/KanbanBoard.tsx index 862e988..77d75a0 100644 --- a/frontend/src/components/Tasks/KanbanBoard.tsx +++ b/frontend/src/components/Tasks/KanbanBoard.tsx @@ -5,57 +5,37 @@ interface KanbanBoardProps { } const STAGE_COLORS = [ - "bg-blue-500", - "bg-emerald-500", - "bg-amber-500", - "bg-purple-500", - "bg-rose-500", - "bg-cyan-500", - "bg-orange-500", - "bg-teal-500", + "bg-blue-500", "bg-emerald-500", "bg-amber-500", "bg-purple-500", + "bg-rose-500", "bg-cyan-500", "bg-orange-500", "bg-teal-500", ]; function ProjectRow({ project }: { project: Project }) { const stages = Object.entries(project.stages); const totalTasks = stages.reduce((sum, [, tasks]) => sum + tasks.length, 0); - if (totalTasks === 0) return null; return ( -
-
-

+
+
+

{project.name}

- - {totalTasks} - + {totalTasks}
- -
- {stages.map(([stageName, tasks], i) => { +
+ {stages.map(([name, tasks], i) => { const pct = (tasks.length / totalTasks) * 100; if (pct < 1) return null; return ( -
+
); })}
- -
- {stages.map(([stageName, tasks], i) => ( - - - {stageName} +
+ {stages.map(([name, tasks], i) => ( + + + {name} {tasks.length} ))} @@ -65,33 +45,27 @@ function ProjectRow({ project }: { project: Project }) { } export function KanbanBoard({ projects }: KanbanBoardProps) { - const activeProjects = projects.filter((p) => { - const total = Object.values(p.stages).reduce((s, t) => s + t.length, 0); - return total > 0; - }); - - const totalTasks = activeProjects.reduce( - (sum, p) => sum + Object.values(p.stages).reduce((s, t) => s + t.length, 0), - 0 + const active = projects.filter((p) => + Object.values(p.stages).reduce((s, t) => s + t.length, 0) > 0 + ); + const total = active.reduce( + (sum, p) => sum + Object.values(p.stages).reduce((s, t) => s + t.length, 0), 0 ); return (
-
- - {activeProjects.length} proyectos +
+ + {active.length} proyectos | - - {totalTasks} tareas + + {total} tareas
- -
-
- {activeProjects.map((project) => ( - - ))} +
+
+ {active.map((p) => )}
diff --git a/frontend/src/components/Topology/NetworkGraph.tsx b/frontend/src/components/Topology/NetworkGraph.tsx index ad75435..bea4c1c 100644 --- a/frontend/src/components/Topology/NetworkGraph.tsx +++ b/frontend/src/components/Topology/NetworkGraph.tsx @@ -11,171 +11,137 @@ const ICON_MAP: Record = { phone: "πŸ“ž", camera: "πŸ“·", device: "πŸ“±", }; -/* ── Compact VM pill ─────────────────────────────────────── */ function VmPill({ node }: { node: NetworkNode }) { - const isUp = node.status === "up"; - const dotColor = isUp ? "bg-success" : "bg-danger"; - + const dot = node.status === "up" ? "bg-success" : "bg-danger"; return ( -
- - {node.name} +
+ + {node.name}
); } -/* ── Infrastructure card ─────────────────────────────────── */ function InfraCard({ node }: { node: NetworkNode }) { const [showPass, setShowPass] = useState(false); const isUp = node.status === "up"; - const borderColor = isUp ? "border-success/40" : "border-danger/40"; - const bgColor = isUp ? "bg-success-dim" : "bg-danger-dim"; - const dotColor = isUp ? "bg-success" : "bg-danger"; + const border = isUp ? "border-success/30" : "border-danger/30"; + const bg = isUp ? "bg-success-dim" : "bg-danger-dim"; + const dot = isUp ? "bg-success" : "bg-danger"; return (
setShowPass((p) => !p)} >
- {ICON_MAP[node.icon] || "πŸ“¦"} -
-

{node.name}

-

{node.ip}

+ {ICON_MAP[node.icon] || "πŸ“¦"} +
+

{node.name}

+

{node.ip}

- +
{showPass && node.username && ( -

+

{node.username} - {node.password && / {node.password}} + {node.password && / {node.password}}

)} {showPass && node.public_url && ( -

{node.public_url}

+

{node.public_url}

)}
); } -/* ── Proxmox server column ───────────────────────────────── */ -function ProxmoxColumn({ server, vms }: { server: NetworkNode; vms: NetworkNode[] }) { - const upCount = vms.filter((v) => v.status === "up").length; - +function ProxmoxCol({ server, vms }: { server: NetworkNode; vms: NetworkNode[] }) { + const up = vms.filter((v) => v.status === "up").length; return ( -
+
+
-
- - {upCount}/{vms.length} activos - -
- {vms.map((vm) => ( - - ))} +
+ {up}/{vms.length} +
+ {vms.map((vm) => )}
); } -/* ── Vertical line ───────────────────────────────────────── */ -function VLine() { - return
; -} - -/* ── Main topology ───────────────────────────────────────── */ export function NetworkGraph({ nodes }: NetworkGraphProps) { - const findByName = (name: string) => nodes.find((n) => n.name === name); - const modem = findByName("Router Telmex"); - const firewall = findByName("Firewall OPNsense"); - const switchNode = findByName("Switch Cisco"); + const find = (name: string) => nodes.find((n) => n.name === name); + const modem = find("Router Telmex"); + const firewall = find("Firewall OPNsense"); + const sw = find("Switch Cisco"); - const proxmoxServers = nodes.filter((n) => n.type === "proxmox"); - const otherDevices = nodes.filter( - (n) => - !n.type && - n.name !== "Router Telmex" && - n.name !== "Firewall OPNsense" && - n.name !== "Switch Cisco" - ); - - const vmsByParent = new Map(); - for (const node of nodes) { - if ((node.type === "vm" || node.type === "ct") && node.parent) { - const list = vmsByParent.get(node.parent) || []; - list.push(node); - vmsByParent.set(node.parent, list); + const pve = nodes.filter((n) => n.type === "proxmox"); + const vms = new Map(); + for (const n of nodes) { + if ((n.type === "vm" || n.type === "ct") && n.parent) { + const list = vms.get(n.parent) || []; + list.push(n); + vms.set(n.parent, list); } } + const other = nodes.filter( + (n) => !n.type && !["Router Telmex", "Firewall OPNsense", "Switch Cisco"].includes(n.name) + ); - const onlineCount = nodes.filter((n) => n.status === "up").length; - const total = nodes.length; + const online = nodes.filter((n) => n.status === "up").length; + const vmTotal = nodes.filter((n) => n.type === "vm" || n.type === "ct").length; return (
- {/* Summary bar */} -
-
- - - {onlineCount} online - -
-
- - - {total - onlineCount} offline - -
+ {/* Stats */} +
+ + + {online} + online + + + + {nodes.length - online} + offline + | - - {proxmoxServers.length} Proxmox Β·{" "} - - {nodes.filter((n) => n.type === "vm" || n.type === "ct").length} - {" "} - VMs/CTs + + {pve.length} Proxmox Β·{" "} + {vmTotal} VMs/CTs
- {/* Diagram */} -
- {/* Top chain: Modem β†’ Firewall β†’ Switch */} -
+ {/* Tree */} +
+ {/* Top chain */} +
{modem && } - β†’ + β†’ {firewall && } - β†’ - {switchNode && } + β†’ + {sw && }
- - - {/* Horizontal branch line */} -
+ {/* Branch down */} +
+
{/* Proxmox columns */} -
- {proxmoxServers.map((server) => { - const vms = vmsByParent.get(server.name) || []; - return ( -
-
- -
- ); - })} +
+ {pve.map((s) => ( + + ))}
- {/* Other devices */} - {otherDevices.length > 0 && ( -
-

+ {/* Others */} + {other.length > 0 && ( +

+

Otros dispositivos

-
- {otherDevices.map((node) => ( - - ))} +
+ {other.map((n) => )}
)} diff --git a/frontend/src/index.css b/frontend/src/index.css index 4c66f66..308d25d 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -21,7 +21,8 @@ } html { - font-size: clamp(14px, 1.15vw, 24px); + /* Scales with viewport: ~14px at 1920w, ~16px at 3840w, min 10px */ + font-size: clamp(10px, 0.75vw, 16px); } body { @@ -35,12 +36,12 @@ body { } ::-webkit-scrollbar { - width: 6px; + width: 4px; } ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background: var(--color-border); - border-radius: 3px; + border-radius: 2px; }