Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 | 2x 2x 12x 2x 12x 2x 12x 12x 12x 12x 2x 1x 1x | /**
* TopicSelectionCard (Epic 14, Story 14.F.3)
*
* A single topic in the overlay's State-A grid, folding the old right-hand
* detail panel onto the card: title, staleness chip + colour-coded left border,
* category, last-used + usage count, inline similarity warning, and the
* Select / Edit / Delete actions. A too-recent (red) topic shows "Select
* anyway…" — the override/justification dialog lives in the overlay
* (driven by useTopicSelection). No backend (NFR9).
*/
import React from 'react';
import { Box, Button, Card, CardContent, Chip, Stack, Typography } from '@mui/material';
import { Warning as WarningIcon } from '@mui/icons-material';
import { useTranslation } from 'react-i18next';
import type { Topic } from '@/types/topic.types';
import { RECENT_STALENESS } from './useTopicSelection';
export interface TopicSelectionCardProps {
topic: Topic;
hasHighSimilarity: boolean;
isPending: boolean;
onSelect: (topic: Topic) => void;
onEdit: (topic: Topic) => void;
onDelete: (topic: Topic) => void;
}
const CATEGORY_KEY: Record<string, string> = {
technical: 'technical',
management: 'management',
soft_skills: 'softSkills',
industry_trends: 'industryTrends',
tools_platforms: 'toolsPlatforms',
};
const zoneColor = (zone: Topic['colorZone']): 'error' | 'warning' | 'success' =>
zone === 'red' ? 'error' : zone === 'yellow' ? 'warning' : 'success';
const borderColor = (zone: Topic['colorZone']): string =>
zone === 'red' ? 'error.main' : zone === 'yellow' ? 'warning.main' : 'success.main';
export const TopicSelectionCard: React.FC<TopicSelectionCardProps> = ({
topic,
hasHighSimilarity,
isPending,
onSelect,
onEdit,
onDelete,
}) => {
const { t } = useTranslation(['events', 'organizer', 'common']);
const isRecent = topic.stalenessScore < RECENT_STALENESS;
const canDelete = !topic.usageCount || topic.usageCount === 0;
return (
<Card
variant="outlined"
data-testid={`topic-card-${topic.topicCode}`}
sx={{ borderLeft: 4, borderLeftColor: borderColor(topic.colorZone), height: '100%' }}
>
<CardContent>
<Stack spacing={1.5}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', gap: 1 }}>
<Typography variant="subtitle1" fontWeight={600}>
{topic.title}
</Typography>
<Chip
label={`${topic.stalenessScore}%`}
size="small"
color={zoneColor(topic.colorZone)}
data-testid={`topic-card-staleness-${topic.topicCode}`}
/>
</Box>
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap', alignItems: 'center' }}>
<Chip
label={t(
`organizer:topicBacklog.filters.categories.${CATEGORY_KEY[topic.category] ?? topic.category}`,
topic.category
)}
size="small"
variant="outlined"
/>
{topic.usageCount !== undefined && (
<Typography variant="caption" color="text.secondary">
{t('organizer:topicBacklog.details.usageCount', 'Usage Count')}: {topic.usageCount}
</Typography>
)}
{topic.lastUsedDate && (
<Typography variant="caption" color="text.secondary">
{t('organizer:topicBacklog.details.lastUsed', 'Last Used')}:{' '}
{new Date(topic.lastUsedDate).toLocaleDateString()}
</Typography>
)}
</Box>
{hasHighSimilarity && (
<Box
sx={{ display: 'flex', alignItems: 'center', gap: 0.5, color: 'warning.main' }}
data-testid={`topic-card-similarity-${topic.topicCode}`}
>
<WarningIcon fontSize="small" />
<Typography variant="caption">
{t('eventPage.topicOverlay.similarityWarning', 'Similar topic detected')}
</Typography>
</Box>
)}
<Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap>
<Button
variant="contained"
size="small"
color={isRecent ? 'warning' : 'primary'}
onClick={() => onSelect(topic)}
disabled={isPending}
data-testid={`topic-card-select-${topic.topicCode}`}
>
{isRecent
? t('eventPage.topicOverlay.selectAnyway', 'Select anyway…')
: t('organizer:topicBacklog.details.selectButton', 'Select for Event')}
</Button>
<Button
variant="outlined"
size="small"
onClick={() => onEdit(topic)}
disabled={isPending}
data-testid={`topic-card-edit-${topic.topicCode}`}
>
{t('organizer:topicBacklog.details.editButton', 'Edit')}
</Button>
<Button
variant="outlined"
size="small"
color="error"
onClick={() => onDelete(topic)}
disabled={isPending || !canDelete}
data-testid={`topic-card-delete-${topic.topicCode}`}
>
{t('organizer:topicBacklog.details.deleteButton', 'Delete')}
</Button>
</Stack>
</Stack>
</CardContent>
</Card>
);
};
export default TopicSelectionCard;
|