diff --git a/src/web/src/components/layout/AppHeader.tsx b/src/web/src/components/layout/AppHeader.tsx new file mode 100644 index 0000000..4d868b9 --- /dev/null +++ b/src/web/src/components/layout/AppHeader.tsx @@ -0,0 +1,97 @@ +import { Menu, LogOut, User } from 'lucide-react' +import { useNavigate } from 'react-router-dom' +import { + Sheet, + SheetContent, + SheetHeader, + SheetTitle, + SheetTrigger, +} from '@/components/ui/sheet' +import { Button } from '@/components/ui/button' +import { Avatar, AvatarFallback } from '@/components/ui/avatar' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu' +import { ThemeToggle } from '@/components/layout/ThemeToggle' +import { SidebarNav } from '@/components/layout/AppSidebar' +import { useAuthStore } from '@/stores/authStore' + +export function AppHeader() { + const navigate = useNavigate() + const user = useAuthStore((s) => s.user) + const logout = useAuthStore((s) => s.logout) + + const initials = user?.nombre + ? user.nombre + .split(' ') + .slice(0, 2) + .map((n) => n[0]) + .join('') + .toUpperCase() + : 'U' + + function handleLogout() { + logout() + void navigate('/login') + } + + return ( +
+ {/* Mobile sidebar trigger */} + + + + + + + Navegación + + + + + + {/* Spacer */} +
+ + {/* Right actions */} + + + + + + + + + + Mi perfil + + + + + Cerrar sesión + + + +
+ ) +} diff --git a/src/web/src/components/layout/AppSidebar.tsx b/src/web/src/components/layout/AppSidebar.tsx new file mode 100644 index 0000000..120d22d --- /dev/null +++ b/src/web/src/components/layout/AppSidebar.tsx @@ -0,0 +1,85 @@ +import { Link, useLocation } from 'react-router-dom' +import { + LayoutDashboard, + ShoppingCart, + Calculator, + Zap, + Settings, +} from 'lucide-react' +import { cn } from '@/lib/utils' +import { Badge } from '@/components/ui/badge' + +interface NavItem { + label: string + href: string + icon: React.ElementType + disabled?: boolean +} + +const navItems: NavItem[] = [ + { label: 'Dashboard', href: '/', icon: LayoutDashboard }, + { label: 'Ventas', href: '/ventas', icon: ShoppingCart, disabled: true }, + { label: 'Tasación', href: '/tasacion', icon: Calculator, disabled: true }, + { + label: 'Integraciones', + href: '/integraciones', + icon: Zap, + disabled: true, + }, + { + label: 'Administración', + href: '/administracion', + icon: Settings, + disabled: true, + }, +] + +export function SidebarNav() { + const { pathname } = useLocation() + + return ( + + ) +} diff --git a/src/web/src/components/layout/ThemeToggle.tsx b/src/web/src/components/layout/ThemeToggle.tsx new file mode 100644 index 0000000..66de9a4 --- /dev/null +++ b/src/web/src/components/layout/ThemeToggle.tsx @@ -0,0 +1,30 @@ +import { Moon, Sun } from 'lucide-react' +import { Button } from '@/components/ui/button' +import { useTheme } from '@/hooks/useTheme' + +export function ThemeToggle() { + const { resolvedTheme, setTheme } = useTheme() + + function toggle() { + setTheme(resolvedTheme === 'dark' ? 'light' : 'dark') + } + + return ( + + ) +} diff --git a/src/web/src/hooks/useTheme.ts b/src/web/src/hooks/useTheme.ts new file mode 100644 index 0000000..d05fc8c --- /dev/null +++ b/src/web/src/hooks/useTheme.ts @@ -0,0 +1,42 @@ +import { useEffect, useState } from 'react' + +export type Theme = 'light' | 'dark' | 'system' + +const STORAGE_KEY = 'theme' + +function getSystemTheme(): 'light' | 'dark' { + return window.matchMedia('(prefers-color-scheme: dark)').matches + ? 'dark' + : 'light' +} + +function applyTheme(theme: Theme) { + const resolved = theme === 'system' ? getSystemTheme() : theme + const root = document.documentElement + root.classList.remove('light', 'dark') + root.classList.add(resolved) +} + +export function useTheme() { + const [theme, setThemeState] = useState(() => { + const stored = localStorage.getItem(STORAGE_KEY) as Theme | null + if (stored === 'light' || stored === 'dark' || stored === 'system') { + return stored + } + return 'system' + }) + + const resolvedTheme: 'light' | 'dark' = + theme === 'system' ? getSystemTheme() : theme + + useEffect(() => { + applyTheme(theme) + }, [theme]) + + function setTheme(next: Theme) { + localStorage.setItem(STORAGE_KEY, next) + setThemeState(next) + } + + return { theme, setTheme, resolvedTheme } +} diff --git a/src/web/src/layouts/ProtectedLayout.tsx b/src/web/src/layouts/ProtectedLayout.tsx index 8d572ca..377047d 100644 --- a/src/web/src/layouts/ProtectedLayout.tsx +++ b/src/web/src/layouts/ProtectedLayout.tsx @@ -1,4 +1,6 @@ import type { ReactNode } from 'react' +import { AppHeader } from '@/components/layout/AppHeader' +import { SidebarNav } from '@/components/layout/AppSidebar' interface ProtectedLayoutProps { children: ReactNode @@ -6,8 +8,17 @@ interface ProtectedLayoutProps { export function ProtectedLayout({ children }: ProtectedLayoutProps) { return ( -
- {children} +
+ {/* Desktop sidebar */} +
+ +
+ + {/* Main column */} +
+ +
{children}
+
) } diff --git a/src/web/src/layouts/PublicLayout.tsx b/src/web/src/layouts/PublicLayout.tsx index 0526116..11731a8 100644 --- a/src/web/src/layouts/PublicLayout.tsx +++ b/src/web/src/layouts/PublicLayout.tsx @@ -6,7 +6,7 @@ interface PublicLayoutProps { export function PublicLayout({ children }: PublicLayoutProps) { return ( -
+
{children}
) diff --git a/src/web/src/pages/HomePage.tsx b/src/web/src/pages/HomePage.tsx index 452ea85..f41bf41 100644 --- a/src/web/src/pages/HomePage.tsx +++ b/src/web/src/pages/HomePage.tsx @@ -1,8 +1,109 @@ +import { + TrendingUp, + Calculator, + Zap, + Settings, + LayoutDashboard, +} from 'lucide-react' +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from '@/components/ui/card' +import { Badge } from '@/components/ui/badge' +import { useAuthStore } from '@/stores/authStore' + +interface ModuleCard { + title: string + description: string + icon: React.ElementType + available: boolean +} + +const modules: ModuleCard[] = [ + { + title: 'Dashboard', + description: 'Vista general del sistema y métricas principales.', + icon: LayoutDashboard, + available: true, + }, + { + title: 'Ventas', + description: 'Gestión de ventas, presupuestos y seguimiento de clientes.', + icon: TrendingUp, + available: false, + }, + { + title: 'Tasación', + description: 'Herramientas de valuación y tasación de propiedades.', + icon: Calculator, + available: false, + }, + { + title: 'Integraciones', + description: 'Conectores con portales inmobiliarios y servicios externos.', + icon: Zap, + available: false, + }, + { + title: 'Administración', + description: 'Gestión de usuarios, roles y configuración del sistema.', + icon: Settings, + available: false, + }, +] + export function HomePage() { + const user = useAuthStore((s) => s.user) + return ( -
-

Dashboard

-

Bienvenido al SIG-CM2.

+
+ {/* Welcome */} +
+

+ Panel principal +

+

+ {user?.nombre + ? `Bienvenido, ${user.nombre}. Seleccioná un módulo para comenzar.` + : 'Bienvenido al SIG-CM 2.0. Seleccioná un módulo para comenzar.'} +

+
+ + {/* Module grid */} +
+ {modules.map((mod) => { + const Icon = mod.icon + return ( + + +
+ + {mod.title} +
+
+ + {mod.description} + + + {mod.available ? ( + Disponible + ) : ( + Próximamente + )} + +
+ ) + })} +
) }