Files
brief_ai/frontend-BriefAI/src/components/MagicCard.tsx
T
2026-05-05 10:24:46 +02:00

142 lines
4.2 KiB
TypeScript

import type { ReactNode } from 'react'
import { useState } from 'react'
import { sendFeedback } from '../services/feedbackService'
type MagicCardProps = {
articleId?: string
source: string
timeAgo: string
sentiment: 'Positivo' | 'Negativo' | 'Neutrale'
title: string
summary: string
tags: string[]
entities: string[]
}
// Card notizia: mostra fonte, sentiment, testo, tag, entità e azioni rapide.
function MagicCard({ articleId, source, timeAgo, sentiment, title, summary, tags, entities }: MagicCardProps) {
const [voted, setVoted] = useState<1 | -1 | null>(null)
const handleVote = (vote: 1 | -1) => {
if (voted !== null) return
setVoted(vote)
if (articleId) sendFeedback(articleId, vote)
}
return (
<article className="magic-card">
{/* Testata card: fonte e badge del sentiment allineati ai lati opposti. */}
<header className="magic-card-header">
<div>
<p className="magic-card-source">
{source} {timeAgo}
</p>
</div>
<span className={`sentiment-badge ${sentiment.toLowerCase()}`}>{sentiment}</span>
</header>
<h3 className="magic-card-title">{title}</h3>
<p className="magic-card-summary">{summary}</p>
{/* Tag tematici: servono a classificare velocemente la notizia. */}
<div className="magic-card-tags">
{tags.map((tag) => (
<span key={tag} className="tag-chip">
{tag}
</span>
))}
</div>
{/* Entità rilevate: evidenziano i nomi chiave citati nella notizia. */}
<div className="magic-card-entities">
<span className="entities-label">Entità:</span>
<div className="chip-wrap">
{entities.map((entity) => (
<span key={entity} className="entity-chip">
{entity}
</span>
))}
</div>
</div>
{/* Azioni rapide: pulsanti iconici per interagire con la card. */}
<footer className="magic-card-actions" aria-label="Azioni notizia">
<ActionButton
label="Mi piace"
tone="like"
onClick={() => handleVote(1)}
disabled={voted !== null}
style={{ opacity: voted === -1 ? 0.4 : 1 }}
>
<ThumbUpIcon />
</ActionButton>
<ActionButton
label="Non mi piace"
tone="dislike"
onClick={() => handleVote(-1)}
disabled={voted !== null}
style={{ opacity: voted === 1 ? 0.4 : 1 }}
>
<ThumbDownIcon />
</ActionButton>
<ActionButton label="Salva" tone="save">
<BookmarkIcon />
</ActionButton>
<ActionButton label="Condividi" tone="share">
<ShareIcon />
</ActionButton>
</footer>
</article>
)
}
// Piccolo bottone riutilizzabile per mantenere coerente il footer azioni.
function ActionButton({ label, tone, children, onClick, disabled, style }: { label: string; tone: string; children: ReactNode; onClick?: () => void; disabled?: boolean; style?: React.CSSProperties }) {
return (
<button
type="button"
className={`action-button ${tone}`}
aria-label={label}
onClick={onClick}
disabled={disabled}
style={style}
>
{children}
</button>
)
}
function ThumbUpIcon() {
return (
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M7 11v10H4V11h3Zm3 10h7.5a2.5 2.5 0 0 0 2.45-2.02l1.03-5.48A2.5 2.5 0 0 0 18.53 10H13V5.5a2.5 2.5 0 0 0-5 0V11L10 21Z" />
</svg>
)
}
function ThumbDownIcon() {
return (
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M7 13V3H4v10h3Zm3-10h7.5a2.5 2.5 0 0 1 2.45 2.02l1.03 5.48A2.5 2.5 0 0 1 18.53 14H13v4.5a2.5 2.5 0 0 1-5 0V13L10 3Z" />
</svg>
)
}
function BookmarkIcon() {
return (
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M6 3a2 2 0 0 0-2 2v16l8-4 8 4V5a2 2 0 0 0-2-2H6Z" />
</svg>
)
}
function ShareIcon() {
return (
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M18 8a3 3 0 1 0-2.82-4h-.01A3 3 0 0 0 18 8ZM6 15a3 3 0 1 0 0 6 3 3 0 0 0 0-6Zm12-4a3 3 0 0 0-2.83 2H15L9 10V8.8A3 3 0 1 0 7.4 10l5.2 2.6v.4a3 3 0 1 0 1.4 2.4V14l5.16-2.58A3 3 0 0 0 18 11Z" />
</svg>
)
}
export default MagicCard