Chat interno 1-a-1 entre usuarios de la plataforma (addon skeen_chat + pagina Chat con badge de no leidos)

This commit is contained in:
2026-08-28 08:23:56 +00:00
parent d47c054ffc
commit 681c0a9173
14 changed files with 607 additions and 2 deletions

View File

@@ -35,6 +35,7 @@ import GlobalSearch from './GlobalSearch';
import ThemePicker from './ThemePicker';
import NotificationBell from './NotificationBell';
import { useAuth, ROLE_LABELS } from '../lib/auth';
import { useChatUnread } from '../lib/chat';
import { useQuickLinks } from '../lib/quicklinks';
import { odooApi } from '../services/odoo';
@@ -114,6 +115,7 @@ const menuTree: MenuEntry[] = [
{
label: 'Comunicación', icon: MessageSquare,
children: [
{ label: 'Chat', href: '/chat' },
{ label: 'Cumpleañeros', href: '/cumpleanos' },
{ label: 'Mensajes', href: '/wacrm/messages' },
],
@@ -143,6 +145,7 @@ export const ClasicoLayout: FC<LayoutProps> = ({ children, title: _title, subtit
const navigate = useNavigate();
const { user, canSee, hasRole, logout } = useAuth();
const [sidebarOpen, setSidebarOpen] = useState(false);
const chatUnread = useChatUnread();
const visibleLeaves = useMemo(
() => menuLeaves.filter((it) => canSee(it.href)),
@@ -383,7 +386,12 @@ export const ClasicoLayout: FC<LayoutProps> = ({ children, title: _title, subtit
>
<Icon size={16} />
<span>{entry.label}</span>
<ChevronDown size={14} className={`ml-auto transition-transform ${open ? '' : '-rotate-90'}`} />
{entry.children?.some((c) => c.href === '/chat') && chatUnread > 0 && (
<span className="ml-auto inline-flex items-center justify-center min-w-[18px] h-[18px] px-1 rounded-full bg-brand-coral text-white text-[10px] font-bold">
{chatUnread}
</span>
)}
<ChevronDown size={14} className={`${entry.children?.some((c) => c.href === '/chat') && chatUnread > 0 ? '' : 'ml-auto'} transition-transform ${open ? '' : '-rotate-90'}`} />
</button>
{open && (
<ul style={{ backgroundColor: 'rgba(0,0,0,0.15)' }}>
@@ -405,6 +413,11 @@ export const ClasicoLayout: FC<LayoutProps> = ({ children, title: _title, subtit
>
<span className="inline-block w-1.5 h-1.5 rounded-full mr-2 shrink-0" style={{ backgroundColor: active ? 'var(--sidebar-accent)' : 'var(--sidebar-text)' }} />
<span>{child.label}</span>
{child.href === '/chat' && chatUnread > 0 && (
<span className="ml-auto inline-flex items-center justify-center min-w-[18px] h-[18px] px-1 rounded-full bg-brand-coral text-white text-[10px] font-bold">
{chatUnread}
</span>
)}
</Link>
<button
type="button"

View File

@@ -31,6 +31,7 @@ import {
Star,
Pencil,
ChevronUp,
MessageCircle,
} from 'lucide-react';
import { Button, Modal, Input } from './ui';
import QuickLinksDock, { QuickLinksModePicker } from './QuickLinksDock';
@@ -38,6 +39,7 @@ import GlobalSearch from './GlobalSearch';
import ThemePicker from './ThemePicker';
import NotificationBell from './NotificationBell';
import { useAuth } from '../lib/auth';
import { useChatUnread } from '../lib/chat';
import { useQuickLinks } from '../lib/quicklinks';
import { odooApi } from '../services/odoo';
@@ -85,6 +87,7 @@ const menuSections = [
items: [
{ label: 'Reportes', icon: BarChart3, href: '/reportes' },
{ label: 'Comisiones', icon: Percent, href: '/reportes?tab=comisiones' },
{ label: 'Chat', icon: MessageCircle, href: '/chat' },
{ label: 'Cumpleañeros', icon: Cake, href: '/cumpleanos' },
{ label: 'Configuración', icon: Settings, href: '/configuracion' },
],
@@ -104,6 +107,7 @@ export const HomeNestLayout: FC<LayoutProps> = ({ children, title: _title, subti
const { user, canSee, hasRole, logout } = useAuth();
const [menuOpen, setMenuOpen] = useState(false);
const [expanded, setExpanded] = useState<string[]>(['Clínica']);
const chatUnread = useChatUnread();
const visibleSections = useMemo(
() =>
@@ -287,6 +291,11 @@ export const HomeNestLayout: FC<LayoutProps> = ({ children, title: _title, subti
onMouseLeave={(e) => e.currentTarget.style.backgroundColor = 'transparent'}
>
<Icon size={16} /> {item.label}
{item.href === '/chat' && chatUnread > 0 && (
<span className="ml-auto inline-flex items-center justify-center min-w-[18px] h-[18px] px-1 rounded-full bg-brand-coral text-white text-[10px] font-bold">
{chatUnread}
</span>
)}
</Link>
);
})}
@@ -458,6 +467,11 @@ export const HomeNestLayout: FC<LayoutProps> = ({ children, title: _title, subti
>
<Icon size={16} />
{item.label}
{item.href === '/chat' && chatUnread > 0 && (
<span className="ml-auto inline-flex items-center justify-center min-w-[18px] h-[18px] px-1 rounded-full bg-brand-coral text-white text-[10px] font-bold">
{chatUnread}
</span>
)}
</Link>
<button
type="button"
@@ -556,6 +570,11 @@ export const HomeNestLayout: FC<LayoutProps> = ({ children, title: _title, subti
}}
>
<Icon size={18} /> {item.label}
{item.href === '/chat' && chatUnread > 0 && (
<span className="ml-auto inline-flex items-center justify-center min-w-[18px] h-[18px] px-1 rounded-full bg-brand-coral text-white text-[10px] font-bold">
{chatUnread}
</span>
)}
</Link>
);
})}

View File

@@ -31,6 +31,7 @@ import {
Pencil,
ChevronUp,
ChevronDown,
MessageCircle,
} from 'lucide-react';
import { Button, Modal, Input } from './ui';
import QuickLinksDock, { QuickLinksModePicker } from './QuickLinksDock';
@@ -38,6 +39,7 @@ import GlobalSearch from './GlobalSearch';
import ThemePicker from './ThemePicker';
import NotificationBell from './NotificationBell';
import { useAuth, ROLE_LABELS } from '../lib/auth';
import { useChatUnread } from '../lib/chat';
import { useQuickLinks } from '../lib/quicklinks';
import { odooApi } from '../services/odoo';
@@ -91,6 +93,7 @@ const menuGroups = [
{
label: 'Comunicación',
items: [
{ label: 'Chat', icon: MessageCircle, href: '/chat' },
{ label: 'Cumpleañeros', icon: Cake, href: '/cumpleanos' },
],
},
@@ -108,6 +111,7 @@ export const SkeenLayout: FC<LayoutProps> = ({ children, title, subtitle }) => {
const navigate = useNavigate();
const { user, canSee, hasRole, logout } = useAuth();
const [sidebarOpen, setSidebarOpen] = useState(false);
const chatUnread = useChatUnread();
const visibleGroups = useMemo(
() =>
@@ -293,7 +297,12 @@ export const SkeenLayout: FC<LayoutProps> = ({ children, title, subtitle }) => {
>
<Icon size={18} />
<span>{item.label}</span>
{active && <ChevronRight size={14} className="ml-auto" />}
{item.href === '/chat' && chatUnread > 0 && (
<span className="ml-auto inline-flex items-center justify-center min-w-[18px] h-[18px] px-1 rounded-full bg-brand-coral text-white text-[10px] font-bold">
{chatUnread}
</span>
)}
{active && <ChevronRight size={14} className={item.href === '/chat' && chatUnread > 0 ? '' : 'ml-auto'} />}
</Link>
<button
type="button"