Revert "feat(ui): make dashboard responsive for iPhone and mobile devices"

This reverts commit d3b326e.

The deployment caused reports of blank screens and 400 errors. Reverting to restore stable state while investigating root cause.
This commit is contained in:
Horux Dev
2026-06-13 20:16:04 +00:00
parent d3b326e78c
commit 66d68c652c
21 changed files with 54 additions and 365 deletions

View File

@@ -173,7 +173,7 @@ export default function AdminUsuariosPage() {
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchTerm(e.target.value)}
/> />
</div> </div>
<div className="w-full sm:w-[250px]"> <div className="w-[250px]">
<Select value={filterTenant} onValueChange={setFilterTenant}> <Select value={filterTenant} onValueChange={setFilterTenant}>
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder="Filtrar por empresa" /> <SelectValue placeholder="Filtrar por empresa" />
@@ -337,7 +337,7 @@ export default function AdminUsuariosPage() {
value={editingUser.role} value={editingUser.role}
onValueChange={(v) => setEditingUser({ ...editingUser, role: v as any })} onValueChange={(v) => setEditingUser({ ...editingUser, role: v as any })}
> >
<SelectTrigger className="h-8 w-full sm:w-[140px]"> <SelectTrigger className="h-8 w-[140px]">
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>

View File

@@ -240,7 +240,7 @@ export default function DiscrepanciaRegimenPage() {
type="date" type="date"
value={fechaDesde} value={fechaDesde}
onChange={e => setFechaDesde(e.target.value)} onChange={e => setFechaDesde(e.target.value)}
className="h-8 w-full sm:w-[150px] text-sm" className="h-8 w-[150px] text-sm"
/> />
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@@ -249,7 +249,7 @@ export default function DiscrepanciaRegimenPage() {
type="date" type="date"
value={fechaHasta} value={fechaHasta}
onChange={e => setFechaHasta(e.target.value)} onChange={e => setFechaHasta(e.target.value)}
className="h-8 w-full sm:w-[150px] text-sm" className="h-8 w-[150px] text-sm"
/> />
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">

View File

@@ -222,7 +222,7 @@ export default function TipoRelacionSospechosaPage() {
type="date" type="date"
value={fechaDesde} value={fechaDesde}
onChange={e => setFechaDesde(e.target.value)} onChange={e => setFechaDesde(e.target.value)}
className="h-8 w-full sm:w-[150px] text-sm" className="h-8 w-[150px] text-sm"
/> />
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@@ -231,7 +231,7 @@ export default function TipoRelacionSospechosaPage() {
type="date" type="date"
value={fechaHasta} value={fechaHasta}
onChange={e => setFechaHasta(e.target.value)} onChange={e => setFechaHasta(e.target.value)}
className="h-8 w-full sm:w-[150px] text-sm" className="h-8 w-[150px] text-sm"
/> />
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">

View File

@@ -302,9 +302,8 @@ export default function CalendarioPage() {
</div> </div>
<div className="grid grid-cols-7 gap-1"> <div className="grid grid-cols-7 gap-1">
{['Dom', 'Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb'].map(d => ( {['Dom', 'Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb'].map(d => (
<div key={d} className="text-center text-xs md:text-sm font-medium text-muted-foreground py-1 md:py-2"> <div key={d} className="text-center text-sm font-medium text-muted-foreground py-2">
<span className="md:hidden">{d.charAt(0)}</span> {d}
<span className="hidden md:inline">{d}</span>
</div> </div>
))} ))}
{days.map((day, i) => { {days.map((day, i) => {
@@ -314,14 +313,14 @@ export default function CalendarioPage() {
<div <div
key={i} key={i}
className={cn( className={cn(
'min-h-[60px] md:min-h-[80px] p-0.5 md:p-1 border rounded-md', 'min-h-[80px] p-1 border rounded-md',
day ? 'bg-background' : 'bg-muted/30', day ? 'bg-background' : 'bg-muted/30',
isToday && 'ring-2 ring-primary' isToday && 'ring-2 ring-primary'
)} )}
> >
{day && ( {day && (
<> <>
<div className={cn('text-xs md:text-sm font-medium', isToday && 'text-primary')}>{day}</div> <div className={cn('text-sm font-medium', isToday && 'text-primary')}>{day}</div>
<div className="space-y-1 mt-1"> <div className="space-y-1 mt-1">
{dayEventos.slice(0, 2).map((e, idx) => { {dayEventos.slice(0, 2).map((e, idx) => {
const Icon = tipoIcons[e.tipo] || Calendar; const Icon = tipoIcons[e.tipo] || Calendar;

View File

@@ -967,7 +967,7 @@ export default function CfdiPage() {
<Card> <Card>
<CardContent className="p-4"> <CardContent className="p-4">
<div className="flex flex-wrap gap-4"> <div className="flex flex-wrap gap-4">
<div className="flex gap-2 w-full sm:w-[300px]"> <div className="flex gap-2 w-[300px]">
<Input <Input
placeholder="Buscar por UUID, RFC o nombre..." placeholder="Buscar por UUID, RFC o nombre..."
value={searchTerm} value={searchTerm}
@@ -1012,7 +1012,7 @@ export default function CfdiPage() {
}) })
} }
> >
<SelectTrigger className="w-full sm:w-[200px]"> <SelectTrigger className="w-[200px]">
<SelectValue placeholder="Tipo de Comprobante" /> <SelectValue placeholder="Tipo de Comprobante" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>

View File

@@ -205,9 +205,9 @@ export default function ClientesPage() {
<Calendar className="h-5 w-5 text-muted-foreground" /> <Calendar className="h-5 w-5 text-muted-foreground" />
<div className="flex items-center gap-2 text-sm"> <div className="flex items-center gap-2 text-sm">
<span className="text-muted-foreground">Periodo:</span> <span className="text-muted-foreground">Periodo:</span>
<Input type="date" value={from} onChange={(e) => setFrom(e.target.value)} className="w-full sm:w-[150px]" /> <Input type="date" value={from} onChange={(e) => setFrom(e.target.value)} className="w-[150px]" />
<span className="text-muted-foreground">a</span> <span className="text-muted-foreground">a</span>
<Input type="date" value={to} onChange={(e) => setTo(e.target.value)} className="w-full sm:w-[150px]" /> <Input type="date" value={to} onChange={(e) => setTo(e.target.value)} className="w-[150px]" />
</div> </div>
</div> </div>
<Button onClick={() => setShowForm(true)}> <Button onClick={() => setShowForm(true)}>
@@ -369,7 +369,7 @@ export default function ClientesPage() {
onClick={() => setUsuariosTenantId(null)} onClick={() => setUsuariosTenantId(null)}
> >
<div <div
className="bg-background rounded-lg max-w-[95vw] md:max-w-2xl w-full max-h-[80vh] overflow-y-auto" className="bg-background rounded-lg max-w-2xl w-full max-h-[80vh] overflow-y-auto"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
<div className="p-6 border-b flex items-center justify-between"> <div className="p-6 border-b flex items-center justify-between">

View File

@@ -67,7 +67,7 @@ export function AddonsDialog({
return ( return (
<Dialog open={!!target} onOpenChange={(o) => !o && onClose()}> <Dialog open={!!target} onOpenChange={(o) => !o && onClose()}>
<DialogContent className="max-w-[95vw] md:max-w-2xl"> <DialogContent className="max-w-2xl">
<DialogHeader> <DialogHeader>
<DialogTitle className="flex items-center gap-2"><Sparkles className="h-5 w-5 text-amber-500" /> Add-ons {target?.nombre}</DialogTitle> <DialogTitle className="flex items-center gap-2"><Sparkles className="h-5 w-5 text-amber-500" /> Add-ons {target?.nombre}</DialogTitle>
</DialogHeader> </DialogHeader>

View File

@@ -379,11 +379,11 @@ function DeclaracionesTab() {
<div className="flex flex-wrap items-end gap-3 mb-4 pb-3 border-b"> <div className="flex flex-wrap items-end gap-3 mb-4 pb-3 border-b">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<label className="text-xs text-muted-foreground">Desde</label> <label className="text-xs text-muted-foreground">Desde</label>
<Input type="date" value={fechaDesde} onChange={(e) => setFechaDesde(e.target.value)} className="h-8 w-full sm:w-[150px] text-sm" /> <Input type="date" value={fechaDesde} onChange={(e) => setFechaDesde(e.target.value)} className="h-8 w-[150px] text-sm" />
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<label className="text-xs text-muted-foreground">Hasta</label> <label className="text-xs text-muted-foreground">Hasta</label>
<Input type="date" value={fechaHasta} onChange={(e) => setFechaHasta(e.target.value)} className="h-8 w-full sm:w-[150px] text-sm" /> <Input type="date" value={fechaHasta} onChange={(e) => setFechaHasta(e.target.value)} className="h-8 w-[150px] text-sm" />
</div> </div>
</div> </div>
@@ -552,7 +552,7 @@ function UploadDialog({ onClose }: { onClose: () => void }) {
return ( return (
<Dialog open onOpenChange={(o) => { if (!o) onClose(); }}> <Dialog open onOpenChange={(o) => { if (!o) onClose(); }}>
<DialogContent className="max-w-[95vw] md:max-w-2xl max-h-[90vh] overflow-y-auto"> <DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
<DialogHeader> <DialogHeader>
<DialogTitle>Subir declaración</DialogTitle> <DialogTitle>Subir declaración</DialogTitle>
<DialogDescription> <DialogDescription>

View File

@@ -812,7 +812,7 @@ export default function FacturacionPage() {
{/* Modal de búsqueda de conceptos previos */} {/* Modal de búsqueda de conceptos previos */}
{showConceptoSearch && ( {showConceptoSearch && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"> <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
<div className="bg-white dark:bg-zinc-900 rounded-xl shadow-2xl w-full max-w-[95vw] md:max-w-3xl max-h-[80vh] flex flex-col m-4"> <div className="bg-white dark:bg-zinc-900 rounded-xl shadow-2xl w-full max-w-3xl max-h-[80vh] flex flex-col m-4">
<div className="flex items-center justify-between p-4 border-b"> <div className="flex items-center justify-between p-4 border-b">
<h3 className="text-lg font-semibold">Buscar Concepto en Facturas</h3> <h3 className="text-lg font-semibold">Buscar Concepto en Facturas</h3>
<Button variant="ghost" size="icon" onClick={() => setShowConceptoSearch(false)}> <Button variant="ghost" size="icon" onClick={() => setShowConceptoSearch(false)}>

View File

@@ -9,7 +9,6 @@ import { Sidebar } from '@/components/layouts/sidebar';
import { TopNav } from '@/components/layouts/topnav'; import { TopNav } from '@/components/layouts/topnav';
import { SidebarCompact } from '@/components/layouts/sidebar-compact'; import { SidebarCompact } from '@/components/layouts/sidebar-compact';
import { SidebarFloating } from '@/components/layouts/sidebar-floating'; import { SidebarFloating } from '@/components/layouts/sidebar-floating';
import { MobileNav } from '@/components/layouts/mobile-nav';
import { SubscriptionBanner } from '@/components/subscription-banner'; import { SubscriptionBanner } from '@/components/subscription-banner';
import { cn } from '@horux/shared-ui'; import { cn } from '@horux/shared-ui';
@@ -46,7 +45,7 @@ export default function DashboardLayout({
} }
// Render layout based on theme // Render layout based on theme
const renderDesktopNavigation = () => { const renderNavigation = () => {
switch (layout) { switch (layout) {
case 'topnav': case 'topnav':
return <TopNav />; return <TopNav />;
@@ -63,14 +62,14 @@ export default function DashboardLayout({
const getContentClasses = () => { const getContentClasses = () => {
switch (layout) { switch (layout) {
case 'topnav': case 'topnav':
return 'pt-16 px-4 md:px-6'; // Top padding for fixed top nav return 'pt-16'; // Top padding for fixed top nav
case 'sidebar-compact': case 'sidebar-compact':
return 'md:pl-16 px-4 md:px-0'; // Small left padding for compact sidebar on desktop return 'pl-16'; // Small left padding for compact sidebar
case 'sidebar-floating': case 'sidebar-floating':
return 'px-4 py-4 md:pl-72 md:pr-4 md:py-4'; // Padding for floating sidebar on desktop return 'pl-72 pr-4 py-4'; // Padding for floating sidebar
case 'sidebar-standard': case 'sidebar-standard':
default: default:
return 'px-4 md:pl-64 md:px-0'; // Standard sidebar width on desktop return 'pl-64'; // Standard sidebar width
} }
}; };
@@ -79,20 +78,8 @@ export default function DashboardLayout({
'min-h-screen bg-background', 'min-h-screen bg-background',
layout === 'sidebar-floating' && 'bg-gradient-to-br from-background via-background to-muted/20' layout === 'sidebar-floating' && 'bg-gradient-to-br from-background via-background to-muted/20'
)}> )}>
{/* Mobile header with hamburger menu */} {renderNavigation()}
<header className="md:hidden fixed top-0 left-0 right-0 z-40 h-14 border-b bg-card px-4 flex items-center justify-between"> <div className={getContentClasses()}>
<MobileNav />
<span className="font-bold text-lg">Horux</span>
<div className="w-10" />
</header>
{/* Desktop navigation - hidden on mobile */}
<div className="hidden md:block">
{renderDesktopNavigation()}
</div>
{/* Main content */}
<div className={cn('pt-14 md:pt-0', getContentClasses())}>
<SubscriptionBanner /> <SubscriptionBanner />
{children} {children}
</div> </div>

View File

@@ -103,7 +103,7 @@ export function EstadoResultadosDrillDownModal({
return ( return (
<Dialog open onOpenChange={(open) => !open && onClose()}> <Dialog open onOpenChange={(open) => !open && onClose()}>
<DialogContent className="max-w-[95vw] md:max-w-4xl max-h-[90vh] overflow-y-auto"> <DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<DialogHeader> <DialogHeader>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{selectedRfc && ( {selectedRfc && (

View File

@@ -1,4 +1,4 @@
import type { Metadata, Viewport } from 'next'; import type { Metadata } from 'next';
import { Inter } from 'next/font/google'; import { Inter } from 'next/font/google';
import './globals.css'; import './globals.css';
import { ThemeProvider } from '@/components/providers/theme-provider'; import { ThemeProvider } from '@/components/providers/theme-provider';
@@ -11,12 +11,6 @@ export const metadata: Metadata = {
description: 'Plataforma de análisis financiero y gestión fiscal para empresas mexicanas', description: 'Plataforma de análisis financiero y gestión fiscal para empresas mexicanas',
}; };
export const viewport: Viewport = {
width: 'device-width',
initialScale: 1,
viewportFit: 'cover',
};
export default function RootLayout({ export default function RootLayout({
children, children,
}: { }: {

View File

@@ -192,7 +192,7 @@ export function CfdiViewerModal({ cfdi, open, onClose }: CfdiViewerModalProps) {
return ( return (
<Dialog open={open} onOpenChange={(isOpen) => !isOpen && onClose()}> <Dialog open={open} onOpenChange={(isOpen) => !isOpen && onClose()}>
<DialogContent className="max-w-[95vw] md:max-w-4xl max-h-[90vh] overflow-y-auto"> <DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<DialogHeader> <DialogHeader>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<DialogTitle>Vista de Factura</DialogTitle> <DialogTitle>Vista de Factura</DialogTitle>

View File

@@ -52,10 +52,10 @@ export function ContribuyenteSelector() {
<div className="contribuyente-selector relative"> <div className="contribuyente-selector relative">
<button <button
onClick={() => setOpen(!open)} onClick={() => setOpen(!open)}
className="flex items-center gap-2 rounded-lg px-2 py-2 text-sm font-medium hover:bg-accent transition-colors sm:px-3" className="flex items-center gap-2 rounded-lg px-3 py-2 text-sm font-medium hover:bg-accent transition-colors"
> >
<Building2 className="h-4 w-4" /> <Building2 className="h-4 w-4" />
<span className="hidden max-w-[180px] truncate sm:inline"> <span className="max-w-[180px] truncate">
{selected ? selected.nombre : 'Todos los RFCs'} {selected ? selected.nombre : 'Todos los RFCs'}
</span> </span>
<ChevronDown className={cn('h-4 w-4 transition-transform', open && 'rotate-180')} /> <ChevronDown className={cn('h-4 w-4 transition-transform', open && 'rotate-180')} />

View File

@@ -32,30 +32,24 @@ export function Header({ title, children }: HeaderProps) {
}; };
return ( return (
<header className="sticky top-0 z-30 border-b bg-background/95 backdrop-blur"> <header className="sticky top-0 z-30 flex h-16 items-center justify-between border-b bg-background/95 backdrop-blur px-6">
<div className="flex h-auto min-h-16 flex-col gap-3 px-4 py-3 md:flex-row md:items-center md:justify-between md:px-6 md:py-0"> <div className="flex items-center gap-4 min-w-0">
{/* Title section */} <h1 className="text-xl font-semibold whitespace-nowrap">{title}</h1>
<div className="flex items-center gap-4 min-w-0"> {children}
<h1 className="text-lg font-semibold truncate md:text-xl">{title}</h1> </div>
{children}
</div>
{/* Actions section */} <div className="flex items-center gap-3">
<div className="flex flex-wrap items-center gap-2 md:gap-3"> <ContribuyenteSelector />
<div className="flex flex-1 items-center gap-2 min-w-0 md:flex-initial md:gap-3"> <MembershipSwitcher />
<ContribuyenteSelector /> <TenantSelector />
<MembershipSwitcher /> <Button
<TenantSelector /> variant="ghost"
</div> size="icon"
<Button onClick={cycleTheme}
variant="ghost" title={`Tema: ${themes[theme].name}`}
size="icon" >
onClick={cycleTheme} {themeIcons[theme]}
title={`Tema: ${themes[theme].name}`} </Button>
>
{themeIcons[theme]}
</Button>
</div>
</div> </div>
</header> </header>
); );

View File

@@ -1,181 +0,0 @@
'use client';
import Link from 'next/link';
import Image from 'next/image';
import { usePathname } from 'next/navigation';
import { useState } from 'react';
import { cn, Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from '@horux/shared-ui';
import {
LayoutDashboard,
FileText,
Calculator,
Settings,
LogOut,
BarChart3,
Calendar,
Bell,
Users,
Building2,
Scale,
Send,
ListChecks,
FileCheck,
ClipboardList,
CreditCard,
CheckSquare2,
UserCog,
Shield,
FileWarning,
Rocket,
Menu,
} from 'lucide-react';
import { useAuthStore } from '@/stores/auth-store';
import { logout } from '@/lib/api/auth';
import { useNavGate } from '@/lib/hooks/use-nav-gate';
import { useRouter } from 'next/navigation';
import { hasDespachoFeature, isGlobalAdminRfc, type DespachoPlan } from '@horux/shared';
interface NavItem {
name: string;
href: string;
icon: typeof LayoutDashboard;
feature?: string;
roles?: string[];
}
const navigation: NavItem[] = [
{ name: 'Despacho', href: '/despachos', icon: ListChecks, roles: ['owner', 'cfo', 'contador', 'auxiliar', 'supervisor'] },
{ name: 'Dashboard', href: '/dashboard', icon: LayoutDashboard, roles: ['owner', 'cfo', 'contador', 'auxiliar', 'supervisor', 'cliente'] },
{ name: 'CFDI', href: '/cfdi', icon: FileText },
{ name: 'Impuestos', href: '/impuestos', icon: Calculator },
{ name: 'Reportes', href: '/reportes', icon: BarChart3, feature: 'reportes', roles: ['owner', 'cfo', 'supervisor', 'cliente'] },
{ name: 'Conciliacion', href: '/conciliacion', icon: Scale, feature: 'conciliacion' },
{ name: 'Calendario', href: '/calendario', icon: Calendar, feature: 'calendario' },
{ name: 'Alertas', href: '/alertas', icon: Bell, feature: 'alertas' },
{ name: 'Facturación', href: '/facturacion', icon: Send, roles: ['owner', 'cfo', 'contador', 'auxiliar', 'supervisor'] },
{ name: 'Documentos', href: '/documentos', icon: FileCheck, feature: 'documentos' },
{ name: 'Carteras', href: '/carteras', icon: ClipboardList, roles: ['supervisor', 'auxiliar'] },
{ name: 'Contribuyentes', href: '/contribuyentes', icon: Building2, roles: ['owner', 'cfo', 'supervisor', 'contador', 'auxiliar'] },
{ name: 'Usuarios', href: '/usuarios', icon: Users, roles: ['owner', 'cfo', 'supervisor', 'auxiliar'] },
{ name: 'Tareas', href: '/tareas', icon: CheckSquare2, roles: ['owner', 'cfo', 'contador', 'auxiliar', 'supervisor'] },
{ name: 'Planes', href: '/configuracion/planes-despacho', icon: CreditCard, roles: ['owner', 'cfo'] },
{ name: 'Configuracion', href: '/configuracion', icon: Settings, roles: ['owner', 'cfo', 'supervisor', 'auxiliar', 'cliente'] },
];
const adminNavigation: NavItem[] = [
{ name: 'Clientes', href: '/clientes', icon: Building2 },
{ name: 'Admin Usuarios', href: '/admin/usuarios', icon: UserCog },
{ name: 'Staff', href: '/admin/staff', icon: Shield },
{ name: 'Audit Log', href: '/admin/audit-log', icon: FileWarning },
];
export function MobileNav() {
const pathname = usePathname();
const router = useRouter();
const { user, logout: clearAuth } = useAuthStore();
const [open, setOpen] = useState(false);
const handleLogout = async () => {
try {
await logout();
} catch {
// Ignore errors
} finally {
clearAuth();
router.push('/login');
}
};
const plan = (user?.plan || 'trial') as DespachoPlan;
const role = user?.role || 'visor';
const navGate = useNavGate();
const filteredNav = navigation.filter((item) => {
if (item.feature && !hasDespachoFeature(plan, item.feature)) return false;
if (item.roles && !item.roles.includes(role)) return false;
if (!navGate.isAllowed(item.href)) return false;
return true;
});
const isGlobalAdmin = isGlobalAdminRfc(user?.tenantRfc, role, user?.platformRoles);
const allNavigation = isGlobalAdmin
? [...filteredNav.slice(0, -1), ...adminNavigation, filteredNav[filteredNav.length - 1]]
: filteredNav;
const handleLinkClick = () => {
setOpen(false);
};
return (
<Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger asChild>
<button
type="button"
className="md:hidden inline-flex h-10 w-10 items-center justify-center rounded-md border border-input bg-background text-sm font-medium ring-offset-background transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
aria-label="Abrir menú de navegación"
>
<Menu className="h-5 w-5" />
</button>
</SheetTrigger>
<SheetContent side="left" className="flex w-[280px] flex-col p-0 sm:max-w-[280px]">
{/* Logo */}
<SheetHeader className="border-b px-4 py-4">
<SheetTitle asChild>
<Link href="/dashboard" onClick={handleLinkClick} className="flex items-center gap-2">
<Image
src="/logo.jpg"
alt="Horux Despachos"
width={32}
height={32}
className="rounded-full"
/>
<div className="flex flex-col leading-tight">
<span className="font-bold text-xl">Horux</span>
<span className="text-xs text-muted-foreground -mt-1">Despachos</span>
</div>
</Link>
</SheetTitle>
</SheetHeader>
{/* Navigation */}
<nav className="flex-1 space-y-1 overflow-y-auto px-3 py-4">
{allNavigation.map((item) => {
const isActive = pathname === item.href || pathname.startsWith(`${item.href}/`);
return (
<Link
key={item.name}
href={item.href}
onClick={handleLinkClick}
className={cn(
'flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors',
isActive
? 'bg-primary text-primary-foreground'
: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
)}
>
<item.icon className="h-5 w-5 flex-shrink-0" />
{item.name}
</Link>
);
})}
</nav>
{/* User & Logout */}
<div className="border-t p-4">
{!isGlobalAdmin && (
<div className="mb-3 px-3">
<p className="text-sm font-medium">{user?.nombre}</p>
<p className="text-xs text-muted-foreground">{user?.email}</p>
</div>
)}
<button
onClick={handleLogout}
className="flex w-full items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium text-muted-foreground hover:bg-destructive hover:text-destructive-foreground transition-colors"
>
<LogOut className="h-5 w-5" />
Cerrar sesión
</button>
</div>
</SheetContent>
</Sheet>
);
}

View File

@@ -65,10 +65,10 @@ export function MembershipSwitcher() {
<button <button
onClick={() => setOpen(!open)} onClick={() => setOpen(!open)}
disabled={switching} disabled={switching}
className="flex items-center gap-2 rounded-lg px-2 py-2 text-sm font-medium hover:bg-accent transition-colors disabled:opacity-50 sm:px-3" className="flex items-center gap-2 rounded-lg px-3 py-2 text-sm font-medium hover:bg-accent transition-colors disabled:opacity-50"
> >
<Building2 className="h-4 w-4" /> <Building2 className="h-4 w-4" />
<span className="hidden max-w-[180px] truncate sm:inline"> <span className="max-w-[180px] truncate">
{activeTenant?.nombre || user?.tenantName} {activeTenant?.nombre || user?.tenantName}
</span> </span>
{switching {switching

View File

@@ -53,14 +53,14 @@ export function TenantSelector() {
<button <button
onClick={() => setOpen(!open)} onClick={() => setOpen(!open)}
className={cn( className={cn(
'flex items-center gap-2 rounded-lg px-2 py-2 text-sm font-medium transition-colors sm:px-3', 'flex items-center gap-2 rounded-lg px-3 py-2 text-sm font-medium transition-colors',
isViewingOther isViewingOther
? 'bg-primary/10 text-primary border border-primary/30' ? 'bg-primary/10 text-primary border border-primary/30'
: 'hover:bg-accent' : 'hover:bg-accent'
)} )}
> >
<Building className="h-4 w-4" /> <Building className="h-4 w-4" />
<span className="hidden max-w-[150px] truncate sm:inline">{displayName}</span> <span className="max-w-[150px] truncate">{displayName}</span>
{isViewingOther && ( {isViewingOther && (
<span <span
role="button" role="button"

View File

@@ -38,7 +38,7 @@ const DialogContent = React.forwardRef<
<DialogPrimitive.Content <DialogPrimitive.Content
ref={ref} ref={ref}
className={cn( className={cn(
'fixed left-[50%] top-[50%] z-50 grid w-[calc(100%-2rem)] max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] rounded-lg', 'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
className className
)} )}
{...props} {...props}

View File

@@ -5,6 +5,5 @@ export * from './input';
export * from './label'; export * from './label';
export * from './popover'; export * from './popover';
export * from './select'; export * from './select';
export * from './sheet';
export * from './sortable-header'; export * from './sortable-header';
export * from './tabs'; export * from './tabs';

View File

@@ -1,103 +0,0 @@
'use client';
import * as React from 'react';
import * as DialogPrimitive from '@radix-ui/react-dialog';
import { X } from 'lucide-react';
import { cn } from '../lib/cn';
const Sheet = DialogPrimitive.Root;
const SheetTrigger = DialogPrimitive.Trigger;
const SheetClose = DialogPrimitive.Close;
const SheetPortal = DialogPrimitive.Portal;
const SheetOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
className
)}
{...props}
/>
));
SheetOverlay.displayName = DialogPrimitive.Overlay.displayName;
interface SheetContentProps
extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> {
side?: 'left' | 'right';
}
const SheetContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
SheetContentProps
>(({ className, children, side = 'left', ...props }, ref) => (
<SheetPortal>
<SheetOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
'fixed z-50 gap-4 border bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500',
side === 'left' &&
'inset-y-0 left-0 h-full w-3/4 data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm',
side === 'right' &&
'inset-y-0 right-0 h-full w-3/4 data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm',
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</SheetPortal>
));
SheetContent.displayName = DialogPrimitive.Content.displayName;
const SheetHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
'flex flex-col space-y-1.5 text-left',
className
)}
{...props}
/>
);
SheetHeader.displayName = 'SheetHeader';
const SheetTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
'text-lg font-semibold leading-none tracking-tight',
className
)}
{...props}
/>
));
SheetTitle.displayName = DialogPrimitive.Title.displayName;
export {
Sheet,
SheetPortal,
SheetOverlay,
SheetTrigger,
SheetClose,
SheetContent,
SheetHeader,
SheetTitle,
};