|
|
|
import { useState } from 'preact/hooks'
|
|
|
|
import { JSX } from 'preact/jsx-runtime'
|
|
|
|
import { ProblemId, Solution as SolutionModel, SolutionId, SolutionStatus, UserId } from '../../shared/model'
|
|
|
|
import { prependBaseUrl } from '../../shared/utils'
|
|
|
|
import { server } from '../api'
|
|
|
|
import { useLoggedInUser } from '../hooks/useCurrentUser'
|
|
|
|
import { Markdown } from './Markdown'
|
|
|
|
|
|
|
|
const STATUS_SELECT_OPTIONS: Record<SolutionStatus, JSX.Element> = {
|
|
|
|
['pending']: <div class="pending">In attesa di correzione</div>,
|
|
|
|
['correct']: <div class="correct">Corretta</div>,
|
|
|
|
['wrong']: <div class="wrong">Sbagliata</div>,
|
|
|
|
}
|
|
|
|
|
|
|
|
type Props = {
|
|
|
|
id: SolutionId
|
|
|
|
createdAt: string
|
|
|
|
sentBy?: UserId
|
|
|
|
forProblem: ProblemId
|
|
|
|
content: string
|
|
|
|
status?: SolutionStatus
|
|
|
|
visible?: boolean
|
|
|
|
adminControls: boolean
|
|
|
|
setSolution?: (solutionFn: (prev: SolutionModel) => SolutionModel) => void
|
|
|
|
refreshSolution?: () => void
|
|
|
|
}
|
|
|
|
|
|
|
|
export const Solution = ({
|
|
|
|
id,
|
|
|
|
createdAt,
|
|
|
|
sentBy,
|
|
|
|
forProblem,
|
|
|
|
content,
|
|
|
|
status,
|
|
|
|
visible,
|
|
|
|
adminControls,
|
|
|
|
setSolution,
|
|
|
|
refreshSolution,
|
|
|
|
}: Props) => {
|
|
|
|
const [user] = useLoggedInUser()
|
|
|
|
|
|
|
|
const markAsCorrect = async () => {
|
|
|
|
setSolution?.(prevSolution => ({ ...prevSolution, status: 'correct' }))
|
|
|
|
|
|
|
|
await server.patch(`/api/solution/${id}`, {
|
|
|
|
status: 'correct',
|
|
|
|
})
|
|
|
|
|
|
|
|
refreshSolution?.()
|
|
|
|
}
|
|
|
|
|
|
|
|
const markAsWrong = async () => {
|
|
|
|
setSolution?.(prevSolution => ({ ...prevSolution, status: 'wrong' }))
|
|
|
|
|
|
|
|
await server.patch(`/api/solution/${id}`, {
|
|
|
|
status: 'wrong',
|
|
|
|
})
|
|
|
|
|
|
|
|
refreshSolution?.()
|
|
|
|
}
|
|
|
|
|
|
|
|
const changeVisibility = async () => {
|
|
|
|
setSolution?.(prevSolution => ({ ...prevSolution, visible: !visible }))
|
|
|
|
|
|
|
|
await server.patch(`/api/solution/${id}`, {
|
|
|
|
visible: !visible,
|
|
|
|
})
|
|
|
|
|
|
|
|
refreshSolution?.()
|
|
|
|
}
|
|
|
|
|
|
|
|
const deleteSolution = async () => {
|
|
|
|
if (confirm('Sei proprio sicuro di voler eliminare questa soluzione?')) {
|
|
|
|
await server.delete(`/api/solution/${id}`)
|
|
|
|
refreshSolution?.()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const [viewRaw, setViewRaw] = useState<boolean>(false)
|
|
|
|
|
|
|
|
const toggleViewRaw = () => {
|
|
|
|
setViewRaw(prev => !prev)
|
|
|
|
}
|
|
|
|
|
|
|
|
const d = new Date(createdAt)
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div class={['solution', status].join(' ')}>
|
|
|
|
<div class="solution-header">
|
|
|
|
<div>
|
|
|
|
<span title={id} class="dotted">
|
|
|
|
Soluzione
|
|
|
|
</span>
|
|
|
|
{sentBy && (
|
|
|
|
<>
|
|
|
|
{' '}
|
|
|
|
di <a href={prependBaseUrl(`/u/${sentBy}`)}>@{sentBy}</a>
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
{forProblem && (
|
|
|
|
<>
|
|
|
|
{' '}
|
|
|
|
per il <a href={prependBaseUrl(`/problem/${forProblem}`)}>Problema {forProblem}</a>
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
{!isNaN(d as any) && (
|
|
|
|
<>
|
|
|
|
{' del '}
|
|
|
|
<span title={!isNaN(d as any) ? d.toISOString() : undefined} class="dotted">
|
|
|
|
{d.getFullYear()}/{d.getMonth().toString().padStart(2, '0')}/{d.getDate().toString().padStart(2, '0')}{' '}
|
|
|
|
{d.getHours().toString().padStart(2, '0')}:{d.getMinutes().toString().padStart(2, '0')}
|
|
|
|
</span>
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="solution-content">
|
|
|
|
{viewRaw ? (
|
|
|
|
<pre>
|
|
|
|
<code>{content}</code>
|
|
|
|
</pre>
|
|
|
|
) : (
|
|
|
|
<Markdown source={content} />
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
{status && (
|
|
|
|
<div class="solution-footer">
|
|
|
|
<div class="row">
|
|
|
|
<div class="status-label">{STATUS_SELECT_OPTIONS[status]}</div>
|
|
|
|
{adminControls && (
|
|
|
|
<>
|
|
|
|
<button title="Segna come corretta" disabled={status === 'correct'} class="icon" onClick={markAsCorrect}>
|
|
|
|
<span class="material-symbols-outlined correct">check_circle</span>
|
|
|
|
</button>
|
|
|
|
<button title="Segna come sbagliata" disabled={status === 'wrong'} class="icon" onClick={markAsWrong}>
|
|
|
|
<span class="material-symbols-outlined wrong">cancel</span>
|
|
|
|
</button>
|
|
|
|
{status !== 'pending' && (
|
|
|
|
<button
|
|
|
|
title={visible ? 'Nascondi al pubblico' : 'Rendi visibile al pubblico'}
|
|
|
|
class="icon"
|
|
|
|
onClick={changeVisibility}
|
|
|
|
>
|
|
|
|
<span class="material-symbols-outlined">{visible ? 'visibility' : 'visibility_off'}</span>
|
|
|
|
</button>
|
|
|
|
)}
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
{user && (user.role === 'admin' || sentBy === user.id) && (
|
|
|
|
<>
|
|
|
|
<button class="icon" title="Elimina questa soluzione" onClick={() => deleteSolution()}>
|
|
|
|
<span class="material-symbols-outlined wrong">delete</span>
|
|
|
|
</button>
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
<div class="vr"></div>
|
|
|
|
<button
|
|
|
|
class="icon"
|
|
|
|
onClick={toggleViewRaw}
|
|
|
|
title={!viewRaw ? 'Mostra markdown grezzo' : 'Mostra testo matematicoso'}
|
|
|
|
>
|
|
|
|
<span class="material-symbols-outlined">{!viewRaw ? 'data_object' : 'functions'}</span>
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|