Prima versione della web app BriefAI
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
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
|
||||
Reference in New Issue
Block a user