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
|
|
@ -0,0 +1,183 @@
|
|||
import React, { useState } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import {
|
||||
AlertOctagon, AlertTriangle, Info, ShieldCheck, AlertCircle, Cpu, Layers, Target, Plus,
|
||||
} from 'lucide-react-native';
|
||||
import type { LucideIcon } from 'lucide-react-native';
|
||||
import {
|
||||
HeadlineCard, FindingCard, SectionDivider, StatBlock, ChipsRow, Chip, CollapseButton,
|
||||
} from '../editorial';
|
||||
import type { FindingAccent } from '../../theme/colors';
|
||||
import type { V3TechniquesResult, TechniqueDefinition } from '../../types/analysis';
|
||||
import { formatTechniqueName } from '../../utils/formatTechniqueName';
|
||||
import { localized, useTranslation, smoothEnums } from '../../i18n';
|
||||
|
||||
const DEFAULT_VISIBLE = 5;
|
||||
|
||||
const scoreAccent = (s: number) =>
|
||||
s >= 70 ? '#ef4444' : s >= 40 ? '#f97316' : s >= 20 ? '#eab308' : '#22c55e';
|
||||
|
||||
const scoreIcon = (s: number): LucideIcon => {
|
||||
if (s >= 70) return AlertOctagon;
|
||||
if (s >= 40) return AlertTriangle;
|
||||
if (s >= 20) return Info;
|
||||
return ShieldCheck;
|
||||
};
|
||||
|
||||
const scoreCategory = (s: number, isRo: boolean): string => {
|
||||
if (s >= 70) return isRo ? 'Critic' : 'Critical';
|
||||
if (s >= 40) return isRo ? 'Ridicat' : 'High';
|
||||
if (s >= 20) return isRo ? 'Mediu' : 'Medium';
|
||||
return isRo ? 'Scăzut' : 'Low';
|
||||
};
|
||||
|
||||
const techIcon = (name: string): LucideIcon => {
|
||||
const dim = (name?.split('.')[0] || '').toUpperCase();
|
||||
if (dim === 'D3') return Cpu;
|
||||
if (dim === 'D5') return Layers;
|
||||
if (dim === 'D8') return Target;
|
||||
return AlertCircle;
|
||||
};
|
||||
|
||||
const techAccent = (severity: number): FindingAccent => {
|
||||
if (severity >= 70) return 'critical';
|
||||
if (severity >= 40) return 'warning';
|
||||
return 'info';
|
||||
};
|
||||
|
||||
const buildTldr = (r: V3TechniquesResult, isRo: boolean): string => {
|
||||
const techCount = r.techniques_detected?.length ?? 0;
|
||||
const dimCount = r.dimensions_affected?.length ?? 0;
|
||||
const dims = (r.dimensions_affected ?? []).join(' · ');
|
||||
if (techCount === 0) {
|
||||
return isRo
|
||||
? 'Nicio tehnică de manipulare detectată în acest conținut.'
|
||||
: 'No manipulation techniques detected in this content.';
|
||||
}
|
||||
return isRo
|
||||
? `${techCount} ${techCount === 1 ? 'tehnică' : 'tehnici'} de manipulare detectate în ${dimCount} ${dimCount === 1 ? 'dimensiune' : 'dimensiuni'}${dims ? ` (${dims})` : ''}.`
|
||||
: `${techCount} manipulation ${techCount === 1 ? 'technique' : 'techniques'} detected across ${dimCount} ${dimCount === 1 ? 'dimension' : 'dimensions'}${dims ? ` (${dims})` : ''}.`;
|
||||
};
|
||||
|
||||
interface Props {
|
||||
result: V3TechniquesResult;
|
||||
defMap?: Map<string, TechniqueDefinition>;
|
||||
}
|
||||
|
||||
export default function TechniquesResults({ result, defMap }: Props) {
|
||||
const { language } = useTranslation();
|
||||
const isRo = language === 'ro';
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
const score = result.manipulation_score ?? 0;
|
||||
const accent = scoreAccent(score);
|
||||
const Icon = scoreIcon(score);
|
||||
const techCount = result.techniques_detected?.length ?? 0;
|
||||
const dimCount = result.dimensions_affected?.length ?? 0;
|
||||
const techs = result.techniques_detected ?? [];
|
||||
const visible = expanded ? techs.length : Math.min(DEFAULT_VISIBLE, techs.length);
|
||||
const shown = techs.slice(0, visible);
|
||||
const remaining = techs.length - visible;
|
||||
const warningFlags: string[] = (result.coupling_context as any)?.for_claims?.warning_flags ?? [];
|
||||
|
||||
return (
|
||||
<View>
|
||||
<HeadlineCard
|
||||
accent={accent}
|
||||
score={score}
|
||||
scoreLabel={isRo ? '/ 100 SCOR' : '/ 100 SCORE'}
|
||||
eyebrow={isRo ? 'Tehnici de manipulare' : 'Manipulation techniques'}
|
||||
icon={Icon}
|
||||
category={scoreCategory(score, isRo)}
|
||||
descriptor={
|
||||
dimCount > 0
|
||||
? `${techCount} ${isRo ? (techCount === 1 ? 'tehnică' : 'tehnici') : (techCount === 1 ? 'technique' : 'techniques')} · ${dimCount} ${isRo ? (dimCount === 1 ? 'dimensiune' : 'dimensiuni') : (dimCount === 1 ? 'dimension' : 'dimensions')}`
|
||||
: `${techCount} ${isRo ? (techCount === 1 ? 'tehnică' : 'tehnici') : (techCount === 1 ? 'technique' : 'techniques')}`
|
||||
}
|
||||
tldr={buildTldr(result, isRo)}
|
||||
/>
|
||||
|
||||
{(score > 0 || dimCount > 0) && (
|
||||
<StatBlock>
|
||||
<ChipsRow>
|
||||
{(result.dimensions_affected ?? []).map(dim => (
|
||||
<Chip key={dim} variant="info">{dim}</Chip>
|
||||
))}
|
||||
{techCount > 0 && (
|
||||
<Chip variant="violet">{techCount} {isRo ? 'tehnici detectate' : 'techniques detected'}</Chip>
|
||||
)}
|
||||
</ChipsRow>
|
||||
</StatBlock>
|
||||
)}
|
||||
|
||||
{techCount > 0 && (
|
||||
<>
|
||||
<SectionDivider
|
||||
label={isRo ? 'Tehnici de manipulare' : 'Manipulation techniques'}
|
||||
count={`${techCount} ${isRo ? 'detectate' : 'detected'}${dimCount > 0 ? ` · ${(result.dimensions_affected ?? []).join(' · ')}` : ''}`}
|
||||
/>
|
||||
{shown.map((tech, i) => {
|
||||
const sev = tech.severity ?? (tech.intensity * 33);
|
||||
const rawName = localized(tech, 'name') || `Technique #${tech.technique_id}`;
|
||||
const def = defMap?.get(rawName);
|
||||
const dimName = localized(tech, 'dimension_name', 'dimension');
|
||||
const subdimName = localized(tech, 'subdimension_name', 'subdimension');
|
||||
const eyebrowParts = [
|
||||
(tech.name?.split('.')[0] || '').toUpperCase() || undefined,
|
||||
dimName ? (subdimName ? `${dimName} · ${subdimName}` : dimName) : undefined,
|
||||
tech.severity != null ? `${isRo ? 'severitate' : 'severity'} ${tech.severity}` : undefined,
|
||||
].filter(Boolean) as string[];
|
||||
return (
|
||||
<FindingCard
|
||||
key={i}
|
||||
icon={techIcon(tech.name || '')}
|
||||
accent={techAccent(sev)}
|
||||
eyebrow={eyebrowParts.join(' · ')}
|
||||
headline={formatTechniqueName(rawName)}
|
||||
quote={smoothEnums(tech.evidence) || smoothEnums(def ? localized(def, 'description') : '') || undefined}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{remaining > 0 && (
|
||||
<CollapseButton
|
||||
onPress={() => setExpanded(true)}
|
||||
expanded={false}
|
||||
leading={<Plus size={16} color="rgba(255,255,255,0.6)" strokeWidth={2} />}
|
||||
label={
|
||||
isRo
|
||||
? `Vezi celelalte ${remaining} ${remaining === 1 ? 'tehnică' : 'tehnici'}`
|
||||
: `Show ${remaining} more ${remaining === 1 ? 'technique' : 'techniques'}`
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{expanded && techs.length > DEFAULT_VISIBLE && (
|
||||
<CollapseButton
|
||||
onPress={() => setExpanded(false)}
|
||||
expanded={true}
|
||||
label={isRo ? 'Ascunde' : 'Hide'}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{techCount === 0 && (
|
||||
<FindingCard
|
||||
icon={ShieldCheck}
|
||||
accent="success"
|
||||
eyebrow={isRo ? 'Conținut curat' : 'Clean content'}
|
||||
headline={isRo ? 'Nicio tehnică de manipulare detectată' : 'No manipulation techniques detected'}
|
||||
/>
|
||||
)}
|
||||
|
||||
{warningFlags.length > 0 && (
|
||||
<FindingCard
|
||||
icon={AlertTriangle}
|
||||
accent="warning"
|
||||
eyebrow={isRo ? 'Semnale adiționale' : 'Additional signals'}
|
||||
headline={isRo ? 'Indicatori de manipulare la screening' : 'Manipulation signals at screening'}
|
||||
quote={warningFlags.map(f => f.replace(/_/g, ' ')).join(' · ')}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue