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:
@@ -173,7 +173,7 @@ export default function AdminUsuariosPage() {
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full sm:w-[250px]">
|
||||
<div className="w-[250px]">
|
||||
<Select value={filterTenant} onValueChange={setFilterTenant}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Filtrar por empresa" />
|
||||
@@ -337,7 +337,7 @@ export default function AdminUsuariosPage() {
|
||||
value={editingUser.role}
|
||||
onValueChange={(v) => setEditingUser({ ...editingUser, role: v as any })}
|
||||
>
|
||||
<SelectTrigger className="h-8 w-full sm:w-[140px]">
|
||||
<SelectTrigger className="h-8 w-[140px]">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
|
||||
@@ -240,7 +240,7 @@ export default function DiscrepanciaRegimenPage() {
|
||||
type="date"
|
||||
value={fechaDesde}
|
||||
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 className="flex items-center gap-2">
|
||||
@@ -249,7 +249,7 @@ export default function DiscrepanciaRegimenPage() {
|
||||
type="date"
|
||||
value={fechaHasta}
|
||||
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 className="flex items-center gap-2">
|
||||
|
||||
@@ -222,7 +222,7 @@ export default function TipoRelacionSospechosaPage() {
|
||||
type="date"
|
||||
value={fechaDesde}
|
||||
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 className="flex items-center gap-2">
|
||||
@@ -231,7 +231,7 @@ export default function TipoRelacionSospechosaPage() {
|
||||
type="date"
|
||||
value={fechaHasta}
|
||||
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 className="flex items-center gap-2">
|
||||
|
||||
@@ -302,9 +302,8 @@ export default function CalendarioPage() {
|
||||
</div>
|
||||
<div className="grid grid-cols-7 gap-1">
|
||||
{['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">
|
||||
<span className="md:hidden">{d.charAt(0)}</span>
|
||||
<span className="hidden md:inline">{d}</span>
|
||||
<div key={d} className="text-center text-sm font-medium text-muted-foreground py-2">
|
||||
{d}
|
||||
</div>
|
||||
))}
|
||||
{days.map((day, i) => {
|
||||
@@ -314,14 +313,14 @@ export default function CalendarioPage() {
|
||||
<div
|
||||
key={i}
|
||||
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',
|
||||
isToday && 'ring-2 ring-primary'
|
||||
)}
|
||||
>
|
||||
{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">
|
||||
{dayEventos.slice(0, 2).map((e, idx) => {
|
||||
const Icon = tipoIcons[e.tipo] || Calendar;
|
||||
|
||||
@@ -967,7 +967,7 @@ export default function CfdiPage() {
|
||||
<Card>
|
||||
<CardContent className="p-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
|
||||
placeholder="Buscar por UUID, RFC o nombre..."
|
||||
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" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
|
||||
@@ -205,9 +205,9 @@ export default function ClientesPage() {
|
||||
<Calendar className="h-5 w-5 text-muted-foreground" />
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<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>
|
||||
<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>
|
||||
<Button onClick={() => setShowForm(true)}>
|
||||
@@ -369,7 +369,7 @@ export default function ClientesPage() {
|
||||
onClick={() => setUsuariosTenantId(null)}
|
||||
>
|
||||
<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()}
|
||||
>
|
||||
<div className="p-6 border-b flex items-center justify-between">
|
||||
|
||||
@@ -67,7 +67,7 @@ export function AddonsDialog({
|
||||
|
||||
return (
|
||||
<Dialog open={!!target} onOpenChange={(o) => !o && onClose()}>
|
||||
<DialogContent className="max-w-[95vw] md:max-w-2xl">
|
||||
<DialogContent className="max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2"><Sparkles className="h-5 w-5 text-amber-500" /> Add-ons — {target?.nombre}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -379,11 +379,11 @@ function DeclaracionesTab() {
|
||||
<div className="flex flex-wrap items-end gap-3 mb-4 pb-3 border-b">
|
||||
<div className="flex items-center gap-2">
|
||||
<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 className="flex items-center gap-2">
|
||||
<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>
|
||||
|
||||
@@ -552,7 +552,7 @@ function UploadDialog({ onClose }: { onClose: () => void }) {
|
||||
|
||||
return (
|
||||
<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>
|
||||
<DialogTitle>Subir declaración</DialogTitle>
|
||||
<DialogDescription>
|
||||
|
||||
@@ -812,7 +812,7 @@ export default function FacturacionPage() {
|
||||
{/* Modal de búsqueda de conceptos previos */}
|
||||
{showConceptoSearch && (
|
||||
<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">
|
||||
<h3 className="text-lg font-semibold">Buscar Concepto en Facturas</h3>
|
||||
<Button variant="ghost" size="icon" onClick={() => setShowConceptoSearch(false)}>
|
||||
|
||||
@@ -9,7 +9,6 @@ import { Sidebar } from '@/components/layouts/sidebar';
|
||||
import { TopNav } from '@/components/layouts/topnav';
|
||||
import { SidebarCompact } from '@/components/layouts/sidebar-compact';
|
||||
import { SidebarFloating } from '@/components/layouts/sidebar-floating';
|
||||
import { MobileNav } from '@/components/layouts/mobile-nav';
|
||||
import { SubscriptionBanner } from '@/components/subscription-banner';
|
||||
import { cn } from '@horux/shared-ui';
|
||||
|
||||
@@ -46,7 +45,7 @@ export default function DashboardLayout({
|
||||
}
|
||||
|
||||
// Render layout based on theme
|
||||
const renderDesktopNavigation = () => {
|
||||
const renderNavigation = () => {
|
||||
switch (layout) {
|
||||
case 'topnav':
|
||||
return <TopNav />;
|
||||
@@ -63,14 +62,14 @@ export default function DashboardLayout({
|
||||
const getContentClasses = () => {
|
||||
switch (layout) {
|
||||
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':
|
||||
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':
|
||||
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':
|
||||
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',
|
||||
layout === 'sidebar-floating' && 'bg-gradient-to-br from-background via-background to-muted/20'
|
||||
)}>
|
||||
{/* Mobile header with hamburger menu */}
|
||||
<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">
|
||||
<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())}>
|
||||
{renderNavigation()}
|
||||
<div className={getContentClasses()}>
|
||||
<SubscriptionBanner />
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -103,7 +103,7 @@ export function EstadoResultadosDrillDownModal({
|
||||
|
||||
return (
|
||||
<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>
|
||||
<div className="flex items-center gap-2">
|
||||
{selectedRfc && (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Metadata, Viewport } from 'next';
|
||||
import type { Metadata } from 'next';
|
||||
import { Inter } from 'next/font/google';
|
||||
import './globals.css';
|
||||
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',
|
||||
};
|
||||
|
||||
export const viewport: Viewport = {
|
||||
width: 'device-width',
|
||||
initialScale: 1,
|
||||
viewportFit: 'cover',
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
|
||||
@@ -192,7 +192,7 @@ export function CfdiViewerModal({ cfdi, open, onClose }: CfdiViewerModalProps) {
|
||||
|
||||
return (
|
||||
<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>
|
||||
<div className="flex items-center justify-between">
|
||||
<DialogTitle>Vista de Factura</DialogTitle>
|
||||
|
||||
@@ -52,10 +52,10 @@ export function ContribuyenteSelector() {
|
||||
<div className="contribuyente-selector relative">
|
||||
<button
|
||||
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" />
|
||||
<span className="hidden max-w-[180px] truncate sm:inline">
|
||||
<span className="max-w-[180px] truncate">
|
||||
{selected ? selected.nombre : 'Todos los RFCs'}
|
||||
</span>
|
||||
<ChevronDown className={cn('h-4 w-4 transition-transform', open && 'rotate-180')} />
|
||||
|
||||
@@ -32,30 +32,24 @@ export function Header({ title, children }: HeaderProps) {
|
||||
};
|
||||
|
||||
return (
|
||||
<header className="sticky top-0 z-30 border-b bg-background/95 backdrop-blur">
|
||||
<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">
|
||||
{/* Title section */}
|
||||
<div className="flex items-center gap-4 min-w-0">
|
||||
<h1 className="text-lg font-semibold truncate md:text-xl">{title}</h1>
|
||||
{children}
|
||||
</div>
|
||||
<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 items-center gap-4 min-w-0">
|
||||
<h1 className="text-xl font-semibold whitespace-nowrap">{title}</h1>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
{/* Actions section */}
|
||||
<div className="flex flex-wrap items-center gap-2 md:gap-3">
|
||||
<div className="flex flex-1 items-center gap-2 min-w-0 md:flex-initial md:gap-3">
|
||||
<ContribuyenteSelector />
|
||||
<MembershipSwitcher />
|
||||
<TenantSelector />
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={cycleTheme}
|
||||
title={`Tema: ${themes[theme].name}`}
|
||||
>
|
||||
{themeIcons[theme]}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<ContribuyenteSelector />
|
||||
<MembershipSwitcher />
|
||||
<TenantSelector />
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={cycleTheme}
|
||||
title={`Tema: ${themes[theme].name}`}
|
||||
>
|
||||
{themeIcons[theme]}
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -65,10 +65,10 @@ export function MembershipSwitcher() {
|
||||
<button
|
||||
onClick={() => setOpen(!open)}
|
||||
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" />
|
||||
<span className="hidden max-w-[180px] truncate sm:inline">
|
||||
<span className="max-w-[180px] truncate">
|
||||
{activeTenant?.nombre || user?.tenantName}
|
||||
</span>
|
||||
{switching
|
||||
|
||||
@@ -53,14 +53,14 @@ export function TenantSelector() {
|
||||
<button
|
||||
onClick={() => setOpen(!open)}
|
||||
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
|
||||
? 'bg-primary/10 text-primary border border-primary/30'
|
||||
: 'hover:bg-accent'
|
||||
)}
|
||||
>
|
||||
<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 && (
|
||||
<span
|
||||
role="button"
|
||||
|
||||
@@ -38,7 +38,7 @@ const DialogContent = React.forwardRef<
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
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
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -5,6 +5,5 @@ export * from './input';
|
||||
export * from './label';
|
||||
export * from './popover';
|
||||
export * from './select';
|
||||
export * from './sheet';
|
||||
export * from './sortable-header';
|
||||
export * from './tabs';
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
Reference in New Issue
Block a user