142 lines
4.2 KiB
TypeScript
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
|