fix: enlarge cards and show full info for TV readability

NodeCard:
- Icon 4xl, name text-xl bold, IP text-lg mono
- Show credentials and URL always (no hidden sections)
- Password hidden with click-to-reveal (dots → text)
- Status dot 4x4, rounded-2xl cards, more padding
- Grid columns min 520px (was 320px)

KanbanBoard:
- Grid 2 columns (was 3) for bigger project cards
- Name text-2xl, progress bar h-5 (was h-3)
- Stage chips text-base with larger count (was text-xs)
- More padding and gaps throughout

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-15 11:08:34 +00:00
parent c7f2d650c4
commit e9e6c871ab
3 changed files with 61 additions and 59 deletions

View File

@@ -22,19 +22,19 @@ function ProjectRow({ project }: { project: Project }) {
if (totalTasks === 0) return null;
return (
<div className="bg-bg-card border border-border rounded-xl p-5 flex flex-col gap-3">
<div className="bg-bg-card border border-border rounded-2xl p-7 flex flex-col gap-4">
{/* Project header */}
<div className="flex items-center justify-between">
<h3 className="text-lg font-bold text-text-primary truncate pr-4">
<div className="flex items-center justify-between gap-4">
<h3 className="text-2xl font-bold text-text-primary leading-snug">
{project.name}
</h3>
<span className="text-sm font-mono text-text-muted shrink-0">
<span className="text-lg font-mono text-text-secondary shrink-0">
{totalTasks} tareas
</span>
</div>
{/* Progress bar */}
<div className="flex h-3 rounded-full overflow-hidden bg-bg-primary">
<div className="flex h-5 rounded-full overflow-hidden bg-bg-primary">
{stages.map(([stageName, tasks], i) => {
const pct = (tasks.length / totalTasks) * 100;
if (pct < 1) return null;
@@ -50,17 +50,17 @@ function ProjectRow({ project }: { project: Project }) {
</div>
{/* Stage chips */}
<div className="flex flex-wrap gap-2">
<div className="flex flex-wrap gap-3">
{stages.map(([stageName, tasks], i) => (
<span
key={stageName}
className="inline-flex items-center gap-1.5 text-xs text-text-secondary bg-bg-secondary rounded-md px-2.5 py-1"
className="inline-flex items-center gap-2 text-base text-text-secondary bg-bg-secondary rounded-lg px-4 py-2"
>
<span
className={`w-2 h-2 rounded-full ${STAGE_COLORS[i % STAGE_COLORS.length]}`}
className={`w-3 h-3 rounded-full shrink-0 ${STAGE_COLORS[i % STAGE_COLORS.length]}`}
/>
<span className="truncate max-w-[180px]">{stageName}</span>
<span className="font-mono font-bold text-text-primary">{tasks.length}</span>
<span className="leading-tight">{stageName}</span>
<span className="font-mono font-bold text-text-primary text-lg">{tasks.length}</span>
</span>
))}
</div>
@@ -82,20 +82,20 @@ export function KanbanBoard({ projects }: KanbanBoardProps) {
return (
<div className="flex flex-col h-full">
{/* Summary bar */}
<div className="flex items-center gap-8 px-16 py-4 bg-bg-secondary border-b border-border">
<span className="text-lg text-text-secondary">
<div className="flex items-center gap-10 px-16 py-5 bg-bg-secondary border-b border-border">
<span className="text-xl text-text-secondary">
<span className="font-bold text-text-primary">{activeProjects.length}</span> proyectos
activos
</span>
<span className="text-text-muted">|</span>
<span className="text-lg text-text-secondary">
<span className="text-border-light text-2xl">|</span>
<span className="text-xl text-text-secondary">
<span className="font-bold text-text-primary">{totalTasks}</span> tareas totales
</span>
</div>
{/* Projects grid */}
<div className="flex-1 overflow-y-auto px-16 py-6">
<div className="grid grid-cols-3 gap-4">
<div className="flex-1 overflow-y-auto px-16 py-8">
<div className="grid grid-cols-2 gap-6">
{activeProjects.map((project) => (
<ProjectRow key={project.id} project={project} />
))}

View File

@@ -28,7 +28,6 @@ function categorizeNodes(nodes: NetworkNode[]) {
}
}
// Any remaining nodes
const remaining = nodes.filter((n) => !assigned.has(n.ip));
if (remaining.length > 0) {
categorized.push({ label: "Otros", nodes: remaining });
@@ -45,37 +44,37 @@ export function NetworkGraph({ nodes }: NetworkGraphProps) {
return (
<div className="flex flex-col h-full">
{/* Summary bar */}
<div className="flex items-center gap-8 px-16 py-4 bg-bg-secondary border-b border-border">
<div className="flex items-center gap-10 px-16 py-5 bg-bg-secondary border-b border-border">
<div className="flex items-center gap-3">
<span className="w-3 h-3 rounded-full bg-success" />
<span className="text-lg text-text-secondary">
<span className="w-4 h-4 rounded-full bg-success" />
<span className="text-xl text-text-secondary">
<span className="font-bold text-text-primary">{onlineCount}</span> online
</span>
</div>
<div className="flex items-center gap-3">
<span className="w-3 h-3 rounded-full bg-danger" />
<span className="text-lg text-text-secondary">
<span className="w-4 h-4 rounded-full bg-danger" />
<span className="text-xl text-text-secondary">
<span className="font-bold text-text-primary">{total - onlineCount}</span> offline
</span>
</div>
<span className="text-text-muted">|</span>
<span className="text-lg text-text-secondary">
<span className="text-border-light text-2xl">|</span>
<span className="text-xl text-text-secondary">
<span className="font-bold text-text-primary">{total}</span> dispositivos
</span>
</div>
{/* Scrollable grid */}
<div className="flex-1 overflow-y-auto px-16 py-6 space-y-6">
<div className="flex-1 overflow-y-auto px-16 py-8 space-y-8">
{categories.map((cat) => (
<section key={cat.label}>
<h3 className="text-sm font-bold text-text-muted uppercase tracking-widest mb-3">
<h3 className="text-lg font-bold text-text-muted uppercase tracking-widest mb-4">
{cat.label}
<span className="ml-2 text-text-muted font-normal">({cat.nodes.length})</span>
<span className="ml-3 font-normal">({cat.nodes.length})</span>
</h3>
<div
className="grid gap-3"
className="grid gap-4"
style={{
gridTemplateColumns: "repeat(auto-fill, minmax(320px, 1fr))",
gridTemplateColumns: "repeat(auto-fill, minmax(520px, 1fr))",
}}
>
{cat.nodes.map((node) => (

View File

@@ -20,7 +20,7 @@ interface NodeCardProps {
}
export function NodeCard({ node }: NodeCardProps) {
const [expanded, setExpanded] = useState(false);
const [showPassword, setShowPassword] = useState(false);
const isUp = node.status === "up";
const statusBg = isUp ? "bg-success-dim" : "bg-danger-dim";
@@ -29,42 +29,45 @@ export function NodeCard({ node }: NodeCardProps) {
return (
<div
className={`relative ${statusBg} border ${statusBorder} rounded-lg px-4 py-3 cursor-pointer transition-all duration-200 hover:bg-bg-card-hover`}
onClick={() => setExpanded((prev) => !prev)}
className={`${statusBg} border ${statusBorder} rounded-2xl px-6 py-5 cursor-pointer transition-all duration-200 hover:bg-bg-card-hover`}
onClick={() => setShowPassword((prev) => !prev)}
>
<div className="flex items-center gap-3">
<span className={`w-2.5 h-2.5 rounded-full ${statusDot} shrink-0`} />
<span className="text-xl shrink-0">{ICON_MAP[node.icon] || "📦"}</span>
<div className="min-w-0 flex-1">
<p className="text-base font-semibold text-text-primary truncate leading-tight">
{/* Row 1: Icon + Name + Status */}
<div className="flex items-center gap-4 mb-2">
<span className="text-4xl shrink-0">{ICON_MAP[node.icon] || "📦"}</span>
<div className="flex-1 min-w-0">
<p className="text-xl font-bold text-text-primary leading-snug">
{node.name}
</p>
<p className="text-sm font-mono text-text-secondary leading-tight">{node.ip}</p>
</div>
{node.public_url && (
<span className="text-xs text-accent truncate max-w-[180px] shrink-0 hidden 2xl:block">
{node.public_url.replace(/^https?:\/\//, "")}
</span>
)}
<span className={`w-4 h-4 rounded-full ${statusDot} shrink-0`} />
</div>
{expanded && (node.username || node.public_url) && (
<div className="mt-2 pt-2 border-t border-border-light text-sm space-y-1">
{node.username && (
<p className="text-text-secondary">
<span className="text-text-muted">User:</span> {node.username}
{node.password && (
<span className="ml-2">
<span className="text-text-muted">Pass:</span>{" "}
<span className="font-mono text-warning">{node.password}</span>
</span>
)}
</p>
{/* Row 2: IP */}
<p className="text-lg font-mono text-text-secondary mb-1">{node.ip}</p>
{/* Row 3: Credentials */}
{node.username && (
<p className="text-base text-text-secondary">
<span className="text-text-muted">Usuario:</span>{" "}
<span className="text-text-primary">{node.username}</span>
{node.password && (
<>
{" · "}
<span className="text-text-muted">Clave:</span>{" "}
<span className="font-mono text-warning">
{showPassword ? node.password : "••••••"}
</span>
</>
)}
{node.public_url && (
<p className="text-accent truncate">{node.public_url}</p>
)}
</div>
</p>
)}
{/* Row 4: Public URL */}
{node.public_url && (
<p className="text-base text-accent mt-1">
{node.public_url}
</p>
)}
</div>
);