didi-lot3-frontend/web/src/components/Sidebar/Sidebar.tsx
Top Clossers cec967f953 Livrare Lot 3 (Frontend): aplicație web, aplicație mobilă Android, extensie browser
- Surse complete web (React/Vite) + mobil (React Native/Expo) + extensie (MV3)
- Documentație de livrare: ghid utilizare, matrice trasabilitate cerințe, raport testare furnizor
- Artefacte binare: imagine Docker didi-frontend:lot3-1.0, APK, extensie v3.2.6 + SHA256SUMS
- Configurare adresă platformă externalizată (build args / .env / config.js)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 12:40:40 +03:00

268 lines
7.9 KiB
TypeScript

import React, { useState } from 'react';
import styled from '@emotion/styled';
import { useTranslation } from 'react-i18next';
import { typography, spacing, media } from '../../theme';
export type SidebarSection = 'pipeline' | 'agent' | 'didi' | 'source' | 'claim' | 'aitamper' | 'history' | 'settings';
interface SidebarProps {
activeSection: SidebarSection;
onSectionChange: (section: SidebarSection) => void;
onLogout: () => void;
isOpen?: boolean;
onClose?: () => void;
}
export const Sidebar: React.FC<SidebarProps> = ({
activeSection,
onSectionChange,
onLogout,
isOpen,
}) => {
const { t } = useTranslation();
const [skillsExpanded, setSkillsExpanded] = useState(true);
const handleSkillsClick = () => {
setSkillsExpanded(!skillsExpanded);
};
const isSkillSection = (section: SidebarSection) =>
section === 'didi' || section === 'source' || section === 'claim' || section === 'aitamper';
return (
<SidebarContainer isOpen={isOpen}>
<SidebarContent>
{/* Full Analysis (Pipeline) */}
<MenuSection>
<MenuItem
active={activeSection === 'pipeline'}
onClick={() => onSectionChange('pipeline')}
>
<MenuLabel>{t('sidebar.fullAnalysis')}</MenuLabel>
</MenuItem>
</MenuSection>
{/* DIDI Skills Section with Submenu */}
<MenuSection>
<MenuItem
active={isSkillSection(activeSection)}
onClick={handleSkillsClick}
aria-expanded={skillsExpanded}
aria-controls="skills-submenu"
>
<MenuLabel>{t('sidebar.didiSkills')}</MenuLabel>
<ExpandIcon expanded={skillsExpanded}>
{skillsExpanded ? '▼' : '▶'}
</ExpandIcon>
</MenuItem>
{skillsExpanded && (
<Submenu id="skills-submenu" role="menu">
<SubmenuItem
active={activeSection === 'didi'}
onClick={() => onSectionChange('didi')}
role="menuitem"
>
<SubmenuLabel>{t('sidebar.manipulationTechniques')}</SubmenuLabel>
</SubmenuItem>
<SubmenuItem
active={activeSection === 'source'}
onClick={() => onSectionChange('source')}
role="menuitem"
>
<SubmenuLabel>{t('sidebar.sourceAssessment')}</SubmenuLabel>
</SubmenuItem>
<SubmenuItem
active={activeSection === 'claim'}
onClick={() => onSectionChange('claim')}
role="menuitem"
>
<SubmenuLabel>{t('sidebar.claimAnalysis')}</SubmenuLabel>
</SubmenuItem>
<SubmenuItem
active={activeSection === 'aitamper'}
onClick={() => onSectionChange('aitamper')}
role="menuitem"
>
<SubmenuLabel>{t('sidebar.aiTamper')}</SubmenuLabel>
</SubmenuItem>
</Submenu>
)}
</MenuSection>
{/* History Section */}
<MenuSection>
<MenuItem
active={activeSection === 'history'}
onClick={() => onSectionChange('history')}
>
<MenuLabel>{t('sidebar.history')}</MenuLabel>
</MenuItem>
</MenuSection>
{/* Settings Section */}
<MenuSection>
<MenuItem
active={activeSection === 'settings'}
onClick={() => onSectionChange('settings')}
>
<MenuLabel>{t('sidebar.settings')}</MenuLabel>
</MenuItem>
</MenuSection>
{/* Logout Button - Bottom */}
<LogoutSection>
<LogoutButton onClick={onLogout}>
<MenuLabel>{t('sidebar.logout')}</MenuLabel>
</LogoutButton>
</LogoutSection>
</SidebarContent>
</SidebarContainer>
);
};
// Styled Components
const SidebarContainer = styled.aside<{ isOpen?: boolean }>`
position: fixed;
left: 0;
top: 80px;
bottom: 0;
width: 280px;
background: var(--bg-surface);
border-right: 1px solid var(--border-subtle);
z-index: 8;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
${media.maxTablet} {
top: 70px;
z-index: 9;
transform: translateX(${p => p.isOpen ? '0' : '-100%'});
box-shadow: ${p => p.isOpen ? 'var(--shadow-lg)' : 'none'};
}
`;
const SidebarContent = styled.nav`
display: flex;
flex-direction: column;
padding: ${spacing.xl}px ${spacing.md}px;
height: 100%;
overflow-y: auto;
overflow-x: hidden;
&::-webkit-scrollbar { width: 6px; }
&::-webkit-scrollbar-track { background: transparent; }
&::-webkit-scrollbar-thumb { background: var(--border-default); border-radius: 3px; }
&::-webkit-scrollbar-thumb:hover { background: var(--border-strong); }
`;
const MenuSection = styled.div`
margin-bottom: ${spacing.sm}px;
`;
const MenuItem = styled.button<{ active?: boolean }>`
display: flex;
align-items: center;
gap: ${spacing.md}px;
width: 100%;
padding: ${spacing.sm}px ${spacing.md}px;
min-height: 44px;
background: ${props => props.active ? 'var(--accent-subtle)' : 'transparent'};
border: none;
border-radius: 6px;
box-shadow: ${props => props.active ? 'inset 2px 0 0 0 var(--accent)' : 'none'};
color: ${props => props.active ? 'var(--accent-text)' : 'var(--fg-secondary)'};
font-family: ${typography.fontFamily.primary};
font-size: ${typography.fontSize.base};
font-weight: ${props => props.active ? typography.fontWeight.semibold : typography.fontWeight.medium};
cursor: pointer;
transition: background-color 0.15s ease, color 0.15s ease;
text-align: left;
&:hover {
background: ${props => props.active ? 'var(--accent-subtle)' : 'var(--bg-hover)'};
color: ${props => props.active ? 'var(--accent-text)' : 'var(--fg-primary)'};
}
`;
const MenuLabel = styled.span`flex: 1;`;
const ExpandIcon = styled.span<{ expanded: boolean }>`
font-size: ${typography.fontSize.sm};
color: var(--fg-muted);
transition: transform 0.3s ease;
flex-shrink: 0;
`;
const Submenu = styled.div`
margin-top: ${spacing.xs}px;
margin-left: ${spacing.md}px;
`;
const SubmenuItem = styled.button<{ active?: boolean }>`
display: flex;
align-items: center;
gap: ${spacing.sm}px;
width: 100%;
padding: 12px ${spacing.md}px;
min-height: 44px;
background: ${props => props.active ? 'var(--accent-subtle)' : 'transparent'};
border: none;
border-radius: 4px;
color: ${props => props.active ? 'var(--accent-text)' : 'var(--fg-secondary)'};
font-family: ${typography.fontFamily.primary};
font-size: ${typography.fontSize.sm};
font-weight: ${props => props.active ? typography.fontWeight.medium : typography.fontWeight.normal};
cursor: pointer;
transition: background-color 0.15s ease, color 0.15s ease;
text-align: left;
margin-bottom: ${spacing.xs}px;
&:hover {
background: ${props => props.active ? 'var(--accent-subtle)' : 'var(--bg-hover)'};
color: ${props => props.active ? 'var(--accent-text)' : 'var(--fg-primary)'};
}
`;
const SubmenuLabel = styled.span`flex: 1;`;
const LogoutSection = styled.div`
margin-top: auto;
padding-top: ${spacing.lg}px;
border-top: 1px solid var(--border-subtle);
`;
const LogoutButton = styled.button`
display: flex;
align-items: center;
gap: ${spacing.md}px;
width: 100%;
padding: ${spacing.sm}px ${spacing.md}px;
min-height: 44px;
background: transparent;
border: none;
border-radius: 6px;
color: rgba(255, 92, 92, 0.8);
font-family: ${typography.fontFamily.primary};
font-size: ${typography.fontSize.base};
font-weight: ${typography.fontWeight.medium};
cursor: pointer;
transition: background-color 0.15s ease, color 0.15s ease;
text-align: left;
[data-theme="light"] & {
color: rgba(220, 38, 38, 0.8);
}
&:hover {
background: rgba(255, 92, 92, 0.08);
color: rgba(255, 92, 92, 1);
}
[data-theme="light"] &:hover {
background: rgba(220, 38, 38, 0.06);
color: rgba(220, 38, 38, 1);
}
`;