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>
This commit is contained in:
commit
cec967f953
321 changed files with 80506 additions and 0 deletions
268
web/src/components/Sidebar/Sidebar.tsx
Normal file
268
web/src/components/Sidebar/Sidebar.tsx
Normal file
|
|
@ -0,0 +1,268 @@
|
|||
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);
|
||||
}
|
||||
`;
|
||||
Loading…
Add table
Add a link
Reference in a new issue