- 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>
268 lines
7.9 KiB
TypeScript
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);
|
|
}
|
|
`;
|