Support multiple languages
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import React, { forwardRef } from 'react';
|
||||
import { clsx } from 'clsx';
|
||||
import { useLocation, Link } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { getBreadcrumbs, getRouteByPath } from '../../../router/routes.config';
|
||||
import {
|
||||
ChevronRight,
|
||||
@@ -94,7 +95,7 @@ export const Breadcrumbs = forwardRef<BreadcrumbsRef, BreadcrumbsProps>(({
|
||||
items: customItems,
|
||||
showHome = true,
|
||||
homePath = '/',
|
||||
homeLabel = 'Inicio',
|
||||
homeLabel,
|
||||
separator,
|
||||
maxItems = 5,
|
||||
truncateMiddle = true,
|
||||
@@ -103,9 +104,13 @@ export const Breadcrumbs = forwardRef<BreadcrumbsRef, BreadcrumbsProps>(({
|
||||
hiddenPaths = [],
|
||||
itemRenderer,
|
||||
}, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const location = useLocation();
|
||||
const containerRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
// Set default home label if not provided
|
||||
const resolvedHomeLabel = homeLabel || t('common:breadcrumbs.home', 'Inicio');
|
||||
|
||||
// Get breadcrumbs from router config or use custom items
|
||||
const routeBreadcrumbs = getBreadcrumbs(location.pathname);
|
||||
|
||||
@@ -115,11 +120,11 @@ export const Breadcrumbs = forwardRef<BreadcrumbsRef, BreadcrumbsProps>(({
|
||||
}
|
||||
|
||||
const items: BreadcrumbItem[] = [];
|
||||
|
||||
|
||||
// Add home if enabled
|
||||
if (showHome && location.pathname !== homePath) {
|
||||
items.push({
|
||||
label: homeLabel,
|
||||
label: resolvedHomeLabel,
|
||||
path: homePath,
|
||||
icon: Home,
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { forwardRef } from 'react';
|
||||
import { clsx } from 'clsx';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button } from '../../ui';
|
||||
import {
|
||||
Heart,
|
||||
@@ -150,15 +151,16 @@ export const Footer = forwardRef<FooterRef, FooterProps>(({
|
||||
compact = false,
|
||||
children,
|
||||
}, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const footerRef = React.useRef<HTMLDivElement>(null);
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
// Company info - full for public pages, minimal for internal
|
||||
const defaultCompanyInfo: CompanyInfo = compact ? {
|
||||
name: 'Panadería IA',
|
||||
name: t('common:app.name', 'Panadería IA'),
|
||||
} : {
|
||||
name: 'Panadería IA',
|
||||
description: 'Sistema inteligente de gestión para panaderías. Optimiza tu producción, inventario y ventas con inteligencia artificial.',
|
||||
name: t('common:app.name', 'Panadería IA'),
|
||||
description: t('common:footer.company_description', 'Sistema inteligente de gestión para panaderías. Optimiza tu producción, inventario y ventas con inteligencia artificial.'),
|
||||
email: 'contacto@panaderia-ia.com',
|
||||
website: 'https://panaderia-ia.com',
|
||||
};
|
||||
@@ -169,33 +171,33 @@ export const Footer = forwardRef<FooterRef, FooterProps>(({
|
||||
const defaultSections: FooterSection[] = compact ? [] : [
|
||||
{
|
||||
id: 'product',
|
||||
title: 'Producto',
|
||||
title: t('common:footer.sections.product', 'Producto'),
|
||||
links: [
|
||||
{ id: 'dashboard', label: 'Dashboard', href: '/dashboard' },
|
||||
{ id: 'inventory', label: 'Inventario', href: '/inventory' },
|
||||
{ id: 'production', label: 'Producción', href: '/production' },
|
||||
{ id: 'sales', label: 'Ventas', href: '/sales' },
|
||||
{ id: 'forecasting', label: 'Predicciones', href: '/forecasting' },
|
||||
{ id: 'dashboard', label: t('common:footer.links.dashboard', 'Dashboard'), href: '/dashboard' },
|
||||
{ id: 'inventory', label: t('common:footer.links.inventory', 'Inventario'), href: '/inventory' },
|
||||
{ id: 'production', label: t('common:footer.links.production', 'Producción'), href: '/production' },
|
||||
{ id: 'sales', label: t('common:footer.links.sales', 'Ventas'), href: '/sales' },
|
||||
{ id: 'forecasting', label: t('common:footer.links.forecasting', 'Predicciones'), href: '/forecasting' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'support',
|
||||
title: 'Soporte',
|
||||
title: t('common:footer.sections.support', 'Soporte'),
|
||||
links: [
|
||||
{ id: 'help', label: 'Centro de Ayuda', href: '/help', icon: HelpCircle },
|
||||
{ id: 'docs', label: 'Documentación', href: '/help/docs', icon: FileText },
|
||||
{ id: 'contact', label: 'Contacto', href: '/help/support', icon: MessageSquare },
|
||||
{ id: 'feedback', label: 'Feedback', href: '/help/feedback' },
|
||||
{ id: 'help', label: t('common:footer.links.help', 'Centro de Ayuda'), href: '/help', icon: HelpCircle },
|
||||
{ id: 'docs', label: t('common:footer.links.docs', 'Documentación'), href: '/help/docs', icon: FileText },
|
||||
{ id: 'contact', label: t('common:footer.links.contact', 'Contacto'), href: '/help/support', icon: MessageSquare },
|
||||
{ id: 'feedback', label: t('common:footer.links.feedback', 'Feedback'), href: '/help/feedback' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'company',
|
||||
title: 'Empresa',
|
||||
title: t('common:footer.sections.company', 'Empresa'),
|
||||
links: [
|
||||
{ id: 'about', label: 'Acerca de', href: '/about', external: true },
|
||||
{ id: 'blog', label: 'Blog', href: 'https://blog.panaderia-ia.com', external: true },
|
||||
{ id: 'careers', label: 'Carreras', href: 'https://careers.panaderia-ia.com', external: true },
|
||||
{ id: 'press', label: 'Prensa', href: '/press', external: true },
|
||||
{ id: 'about', label: t('common:footer.links.about', 'Acerca de'), href: '/about', external: true },
|
||||
{ id: 'blog', label: t('common:footer.links.blog', 'Blog'), href: 'https://blog.panaderia-ia.com', external: true },
|
||||
{ id: 'careers', label: t('common:footer.links.careers', 'Carreras'), href: 'https://careers.panaderia-ia.com', external: true },
|
||||
{ id: 'press', label: t('common:footer.links.press', 'Prensa'), href: '/press', external: true },
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -206,19 +208,19 @@ export const Footer = forwardRef<FooterRef, FooterProps>(({
|
||||
const defaultSocialLinks: SocialLink[] = compact ? [] : [
|
||||
{
|
||||
id: 'twitter',
|
||||
label: 'Twitter',
|
||||
label: t('common:footer.social_labels.twitter', 'Twitter'),
|
||||
href: 'https://twitter.com/panaderia-ia',
|
||||
icon: Twitter,
|
||||
},
|
||||
{
|
||||
id: 'linkedin',
|
||||
label: 'LinkedIn',
|
||||
label: t('common:footer.social_labels.linkedin', 'LinkedIn'),
|
||||
href: 'https://linkedin.com/company/panaderia-ia',
|
||||
icon: Linkedin,
|
||||
},
|
||||
{
|
||||
id: 'github',
|
||||
label: 'GitHub',
|
||||
label: t('common:footer.social_labels.github', 'GitHub'),
|
||||
href: 'https://github.com/panaderia-ia',
|
||||
icon: Github,
|
||||
},
|
||||
@@ -228,9 +230,9 @@ export const Footer = forwardRef<FooterRef, FooterProps>(({
|
||||
|
||||
// Privacy links
|
||||
const privacyLinks: FooterLink[] = [
|
||||
{ id: 'privacy', label: 'Privacidad', href: '/privacy', icon: Shield },
|
||||
{ id: 'terms', label: 'Términos', href: '/terms', icon: FileText },
|
||||
{ id: 'cookies', label: 'Cookies', href: '/cookies' },
|
||||
{ id: 'privacy', label: t('common:footer.links.privacy', 'Privacidad'), href: '/privacy', icon: Shield },
|
||||
{ id: 'terms', label: t('common:footer.links.terms', 'Términos'), href: '/terms', icon: FileText },
|
||||
{ id: 'cookies', label: t('common:footer.links.cookies', 'Cookies'), href: '/cookies' },
|
||||
];
|
||||
|
||||
// Scroll into view
|
||||
@@ -375,7 +377,7 @@ export const Footer = forwardRef<FooterRef, FooterProps>(({
|
||||
{socialLinksToShow.length > 0 && (
|
||||
<div className="border-t border-[var(--border-primary)] pt-6 mb-6">
|
||||
<div className="flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
<p className="text-sm text-[var(--text-secondary)]">Síguenos en redes sociales</p>
|
||||
<p className="text-sm text-[var(--text-secondary)]">{t('common:footer.social_follow', 'Síguenos en redes sociales')}</p>
|
||||
<div className="flex items-center gap-2">
|
||||
{socialLinksToShow.map((social) => renderSocialLink(social))}
|
||||
</div>
|
||||
@@ -404,13 +406,13 @@ export const Footer = forwardRef<FooterRef, FooterProps>(({
|
||||
to="/privacy"
|
||||
className="text-[var(--text-tertiary)] hover:text-[var(--text-primary)] transition-colors duration-200"
|
||||
>
|
||||
Privacidad
|
||||
{t('common:footer.links.privacy', 'Privacidad')}
|
||||
</Link>
|
||||
<Link
|
||||
to="/terms"
|
||||
className="text-[var(--text-tertiary)] hover:text-[var(--text-primary)] transition-colors duration-200"
|
||||
>
|
||||
Términos
|
||||
{t('common:footer.links.terms', 'Términos')}
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -148,7 +148,7 @@ export const Header = forwardRef<HeaderRef, HeaderProps>(({
|
||||
className
|
||||
)}
|
||||
role="banner"
|
||||
aria-label="Navegación principal"
|
||||
aria-label={t('common:header.main_navigation', 'Navegación principal')}
|
||||
>
|
||||
{/* Left section */}
|
||||
<div className="flex items-center gap-2 sm:gap-4 flex-1 min-w-0 h-full">
|
||||
@@ -158,7 +158,7 @@ export const Header = forwardRef<HeaderRef, HeaderProps>(({
|
||||
size="sm"
|
||||
onClick={onMenuClick}
|
||||
className="lg:hidden w-10 h-10 p-0 flex items-center justify-center hover:bg-[var(--bg-secondary)] active:scale-95 transition-all duration-150"
|
||||
aria-label="Abrir menú de navegación"
|
||||
aria-label={t('common:header.open_menu', 'Abrir menú de navegación')}
|
||||
>
|
||||
<Menu className="h-5 w-5 text-[var(--text-primary)]" />
|
||||
</Button>
|
||||
@@ -176,7 +176,7 @@ export const Header = forwardRef<HeaderRef, HeaderProps>(({
|
||||
'self-center',
|
||||
sidebarCollapsed ? 'lg:block' : 'lg:hidden xl:block'
|
||||
)}>
|
||||
Panadería IA
|
||||
{t('common:app.name', 'Panadería IA')}
|
||||
</h1>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { forwardRef } from 'react';
|
||||
import { clsx } from 'clsx';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Button, ThemeToggle } from '../../ui';
|
||||
import { CompactLanguageSelector } from '../../ui/LanguageSelector';
|
||||
|
||||
export interface PublicHeaderProps {
|
||||
className?: string;
|
||||
@@ -17,6 +18,10 @@ export interface PublicHeaderProps {
|
||||
* Show authentication buttons (login/register)
|
||||
*/
|
||||
showAuthButtons?: boolean;
|
||||
/**
|
||||
* Show language selector
|
||||
*/
|
||||
showLanguageSelector?: boolean;
|
||||
/**
|
||||
* Custom navigation items
|
||||
*/
|
||||
@@ -53,6 +58,7 @@ export const PublicHeader = forwardRef<PublicHeaderRef, PublicHeaderProps>(({
|
||||
logo,
|
||||
showThemeToggle = true,
|
||||
showAuthButtons = true,
|
||||
showLanguageSelector = true,
|
||||
navigationItems = [],
|
||||
variant = 'default',
|
||||
}, ref) => {
|
||||
@@ -149,6 +155,11 @@ export const PublicHeader = forwardRef<PublicHeaderRef, PublicHeaderProps>(({
|
||||
|
||||
{/* Right side actions */}
|
||||
<div className="flex items-center gap-3">
|
||||
{/* Language selector */}
|
||||
{showLanguageSelector && (
|
||||
<CompactLanguageSelector className="hidden sm:flex" />
|
||||
)}
|
||||
|
||||
{/* Theme toggle */}
|
||||
{showThemeToggle && (
|
||||
<ThemeToggle
|
||||
@@ -162,8 +173,8 @@ export const PublicHeader = forwardRef<PublicHeaderRef, PublicHeaderProps>(({
|
||||
{showAuthButtons && (
|
||||
<div className="flex items-center gap-2">
|
||||
<Link to="/login">
|
||||
<Button
|
||||
variant="ghost"
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="hidden sm:inline-flex"
|
||||
>
|
||||
@@ -171,7 +182,7 @@ export const PublicHeader = forwardRef<PublicHeaderRef, PublicHeaderProps>(({
|
||||
</Button>
|
||||
</Link>
|
||||
<Link to="/register">
|
||||
<Button
|
||||
<Button
|
||||
size="sm"
|
||||
className="bg-[var(--color-primary)] hover:bg-[var(--color-primary-dark)] text-white"
|
||||
>
|
||||
@@ -219,7 +230,17 @@ export const PublicHeader = forwardRef<PublicHeaderRef, PublicHeaderProps>(({
|
||||
{renderNavLink(item)}
|
||||
</div>
|
||||
))}
|
||||
|
||||
|
||||
{/* Mobile language selector */}
|
||||
{showLanguageSelector && (
|
||||
<div className="py-2 border-b border-[var(--border-primary)] sm:hidden">
|
||||
<div className="text-sm font-medium text-[var(--text-secondary)] mb-2">
|
||||
Idioma
|
||||
</div>
|
||||
<CompactLanguageSelector className="w-full" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Mobile auth buttons */}
|
||||
{showAuthButtons && (
|
||||
<div className="flex flex-col gap-2 pt-4 sm:hidden">
|
||||
|
||||
@@ -21,6 +21,7 @@ export interface PublicLayoutProps {
|
||||
headerProps?: {
|
||||
showThemeToggle?: boolean;
|
||||
showAuthButtons?: boolean;
|
||||
showLanguageSelector?: boolean;
|
||||
navigationItems?: Array<{
|
||||
id: string;
|
||||
label: string;
|
||||
|
||||
@@ -732,7 +732,7 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
|
||||
isCollapsed ? 'justify-center p-2 h-10 w-10 mx-auto rounded-lg' : 'p-4 gap-3',
|
||||
isProfileMenuOpen && 'bg-[var(--bg-secondary)]'
|
||||
)}
|
||||
aria-label="Menú de perfil"
|
||||
aria-label={t('common:profile.profile_menu', 'Menú de perfil')}
|
||||
aria-expanded={isProfileMenuOpen}
|
||||
aria-haspopup="true"
|
||||
>
|
||||
@@ -767,25 +767,25 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
|
||||
<div className="py-1">
|
||||
<button
|
||||
onClick={() => {
|
||||
navigate('/app/settings/profile');
|
||||
navigate('/app/settings/personal-info');
|
||||
setIsProfileMenuOpen(false);
|
||||
if (onClose) onClose();
|
||||
}}
|
||||
className="w-full px-4 py-2 text-left text-sm flex items-center gap-3 hover:bg-[var(--bg-secondary)] transition-colors"
|
||||
>
|
||||
<User className="h-4 w-4" />
|
||||
Perfil
|
||||
{t('common:profile.my_profile', 'Mi perfil')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
navigate('/app/settings');
|
||||
navigate('/app/settings/organizations');
|
||||
setIsProfileMenuOpen(false);
|
||||
if (onClose) onClose();
|
||||
}}
|
||||
className="w-full px-4 py-2 text-left text-sm flex items-center gap-3 hover:bg-[var(--bg-secondary)] transition-colors"
|
||||
>
|
||||
<Settings className="h-4 w-4" />
|
||||
Configuración
|
||||
<Factory className="h-4 w-4" />
|
||||
{t('common:profile.my_locations', 'Mis Locales')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -795,7 +795,7 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
|
||||
className="w-full px-4 py-2 text-left text-sm flex items-center gap-3 hover:bg-[var(--bg-secondary)] transition-colors text-[var(--color-error)]"
|
||||
>
|
||||
<LogOut className="h-4 w-4" />
|
||||
Cerrar Sesión
|
||||
{t('common:profile.logout', 'Cerrar Sesión')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -852,7 +852,7 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
|
||||
PI
|
||||
</div>
|
||||
<h2 className="text-lg font-semibold text-[var(--text-primary)]">
|
||||
Panadería IA
|
||||
{t('common:app.name', 'Panadería IA')}
|
||||
</h2>
|
||||
</div>
|
||||
<Button
|
||||
@@ -860,7 +860,7 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
|
||||
size="sm"
|
||||
onClick={onClose}
|
||||
className="p-2 hover:bg-[var(--bg-secondary)]"
|
||||
aria-label="Cerrar navegación"
|
||||
aria-label={t('common:profile.close_navigation', 'Cerrar navegación')}
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</Button>
|
||||
@@ -931,7 +931,7 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
|
||||
'hover:bg-[var(--bg-secondary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]/20',
|
||||
isProfileMenuOpen && 'bg-[var(--bg-secondary)]'
|
||||
)}
|
||||
aria-label="Menú de perfil"
|
||||
aria-label={t('common:profile.profile_menu', 'Menú de perfil')}
|
||||
aria-expanded={isProfileMenuOpen}
|
||||
aria-haspopup="true"
|
||||
>
|
||||
@@ -966,7 +966,7 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
|
||||
className="w-full px-4 py-2 text-left text-sm flex items-center gap-3 hover:bg-[var(--bg-tertiary)] transition-colors"
|
||||
>
|
||||
<User className="h-4 w-4" />
|
||||
Perfil
|
||||
{t('common:profile.profile', 'Perfil')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
@@ -977,7 +977,7 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
|
||||
className="w-full px-4 py-2 text-left text-sm flex items-center gap-3 hover:bg-[var(--bg-tertiary)] transition-colors"
|
||||
>
|
||||
<Settings className="h-4 w-4" />
|
||||
Configuración
|
||||
{t('common:profile.settings', 'Configuración')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -987,7 +987,7 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
|
||||
className="w-full px-4 py-2 text-left text-sm flex items-center gap-3 hover:bg-[var(--bg-tertiary)] transition-colors text-[var(--color-error)]"
|
||||
>
|
||||
<LogOut className="h-4 w-4" />
|
||||
Cerrar Sesión
|
||||
{t('common:profile.logout', 'Cerrar Sesión')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user