style changes

This commit is contained in:
2025-06-25 18:23:21 +02:00
parent 4c19ffdd9f
commit bbad670baa
6 changed files with 819 additions and 492 deletions
+185 -195
View File
@@ -186,7 +186,7 @@ const CORSI_DISPONIBILI: Corso[] = [
{ nome: 'Ultrafiltri e metodi non-standard', anno: 'M', cfu: 6 },
]
function MediaPesataApp() {
export function MediaPesataApp() {
// Funzioni per localStorage
const loadFromStorage = () => {
try {
@@ -220,13 +220,22 @@ function MediaPesataApp() {
}
// Inizializzazione con dati salvati
const initialData = loadFromStorage()
const [tipoStudente, setTipoStudente] = useState<TipoStudente>(initialData.tipoStudente)
const [corsiSelezionati, setCorsiSelezionati] = useState<CorsoSelezionato[]>(initialData.corsiSelezionati)
// const initialData = loadFromStorage()
const [tipoStudente, setTipoStudente] = useState<TipoStudente>('triennale')
const [corsiSelezionati, setCorsiSelezionati] = useState<CorsoSelezionato[]>([])
const [showCustomForm, setShowCustomForm] = useState(false)
const [customCorso, setCustomCorso] = useState<CorsoCustom>({ nome: '', cfu: 0 })
const [sezioniAperte, setSezioniAperte] = useState<Record<string, boolean>>(initialData.sezioniAperte)
const [mostraRisultati, setMostraRisultati] = useState(initialData.mostraRisultati)
const [sezioniAperte, setSezioniAperte] = useState<Record<string, boolean>>({})
const [mostraRisultati, setMostraRisultati] = useState(false)
// Load data from localStorage on mount
useEffect(() => {
const initialData = loadFromStorage()
setTipoStudente(initialData.tipoStudente)
setCorsiSelezionati(initialData.corsiSelezionati)
setSezioniAperte(initialData.sezioniAperte)
setMostraRisultati(initialData.mostraRisultati)
}, [])
// Salva automaticamente quando cambiano i dati importanti
useEffect(() => {
@@ -482,15 +491,6 @@ function MediaPesataApp() {
}
const cambiaTipoStudente = (nuovoTipo: TipoStudente) => {
if (corsiSelezionati.length > 0 && nuovoTipo !== tipoStudente) {
if (
!confirm(
'Cambiando il tipo di studente, alcune materie potrebbero non essere più disponibili. Continuare?',
)
) {
return
}
}
setTipoStudente(nuovoTipo)
}
@@ -501,217 +501,207 @@ function MediaPesataApp() {
const cfuError = totaleCfu > maxCfu
return (
<div className="media-pesata-app">
<div class="media-pesata-app">
{/* Selezione tipo studente */}
<div className="student-type-selector">
<h2>Corso di Laurea</h2>
<div className="radio-group">
<label>
<input
type="radio"
value="triennale"
checked={tipoStudente === 'triennale'}
onChange={e => cambiaTipoStudente((e.target as HTMLInputElement).value as TipoStudente)}
/>
Triennale
</label>
<label>
<input
type="radio"
value="magistrale"
checked={tipoStudente === 'magistrale'}
onChange={e => cambiaTipoStudente((e.target as HTMLInputElement).value as TipoStudente)}
/>
Magistrale
</label>
<div class="card student-type-switcher wide">
<div class="grid-center">
<h2>Corso di Laurea</h2>
<div class="compound-button">
<button
class={tipoStudente === 'triennale' ? 'active' : ''}
onClick={() => cambiaTipoStudente('triennale')}
>
Triennale
</button>
<button
class={tipoStudente === 'magistrale' ? 'active' : ''}
onClick={() => cambiaTipoStudente('magistrale')}
>
Magistrale
</button>
</div>
</div>
</div>
{/* Counter CFU */}
<div className={`cfu-counter ${cfuError ? 'error' : ''}`}>
<div class={`cfu-counter wide ${cfuError ? 'error' : ''}`}>
<h3>
CFU Totali: {totaleCfu}/{maxCfu}
{tipoStudente === 'triennale' ? ' (+9 tesi)' : ' (+27 tesi)'}
</h3>
{cfuError && <p className="error-text">⚠️ Hai superato il limite di CFU consentiti!</p>}
{cfuError && <p class="error-text">⚠️ Hai superato il limite di CFU consentiti!</p>}
</div>
<div className="main-content">
{/* Sezione selezione corsi */}
<div className="course-selection">
{/* Sezione selezione corsi */}
<div class="card">
<div class="title">
<h2>Seleziona Materie</h2>
</div>
{Object.entries(gruppiCorsi).map(([categoria, corsi]) => (
<div key={categoria} className="course-category">
<button className="category-header" onClick={() => toggleSezione(categoria)}>
<h3>{categoria}</h3>
<span className={`toggle-icon ${sezioniAperte[categoria] ? 'expanded' : ''}`}>▶</span>
</button>
{Object.entries(gruppiCorsi).map(([categoria, corsi]) => (
<div key={categoria} class="course-category">
<button onClick={() => toggleSezione(categoria)}>
<div class="h-flex">
{categoria}
<div class="spacer"></div>
<span class={`toggle-icon ${sezioniAperte[categoria] ? 'expanded' : ''}`}>▶</span>
</div>
</button>
{sezioniAperte[categoria] && (
<div className="course-grid">
{corsi.map((corso, index) => (
<button
key={index}
className="course-button"
onClick={() => aggiungiCorso(corso)}
disabled={corsiSelezionati.some(c => c.nome === corso.nome)}
>
<span className="course-name">{corso.nome}</span>
<span className="course-cfu">{corso.cfu} CFU</span>
</button>
))}
</div>
)}
</div>
))}
{/* Form per materia custom */}
<div className="custom-course">
<h3>Materia Personalizzata</h3>
{!showCustomForm ? (
<button onClick={() => setShowCustomForm(true)} className="add-custom-btn">
+ Aggiungi Materia Personalizzata
</button>
) : (
<div className="custom-form">
<input
type="text"
placeholder="Nome materia"
value={customCorso.nome}
onChange={e =>
setCustomCorso({ ...customCorso, nome: (e.target as HTMLInputElement).value })
}
/>
<input
type="number"
placeholder="CFU"
min="1"
max="30"
step="1"
value={customCorso.cfu || ''}
onChange={e =>
setCustomCorso({
...customCorso,
cfu: parseInt((e.target as HTMLInputElement).value) || 0,
})
}
/>
<button onClick={aggiungiCorsoCustom} className="confirm-btn">
Aggiungi
</button>
<button onClick={() => setShowCustomForm(false)} className="cancel-btn">
Annulla
</button>
{sezioniAperte[categoria] && (
<div class="course-grid">
{corsi.map((corso, index) => (
<button
key={index}
class="course-button"
onClick={() => aggiungiCorso(corso)}
disabled={corsiSelezionati.some(c => c.nome === corso.nome)}
>
<span class="course-name">{corso.nome}</span>
<span class="course-cfu">{corso.cfu} CFU</span>
</button>
))}
</div>
)}
</div>
</div>
))}
{/* Sezione lista corsi selezionati */}
<div className="selected-courses">
<div className="section-header">
<h2>Materie Selezionate</h2>
{corsiSelezionati.length > 0 && (
<button onClick={resetTutto} className="reset-btn">
🗑️ Cancella Tutto
</button>
)}
</div>
{corsiSelezionati.length === 0 ? (
<p className="no-courses">Nessuna materia selezionata</p>
{/* Form per materia custom */}
<div class="custom-course">
<h3>Materia Personalizzata</h3>
{!showCustomForm ? (
<button onClick={() => setShowCustomForm(true)}>+ Aggiungi Materia Personalizzata</button>
) : (
<div className="courses-list">
{corsiSelezionati.map(corso => (
<div key={corso.id} className="course-item">
<div className="course-info">
<span className="course-name">{corso.nome}</span>
<span className="course-cfu">{corso.cfu} CFU</span>
{corso.passFailOnly && <span className="pass-fail-badge">Pass/Fail</span>}
</div>
{!corso.passFailOnly && (
<div className="course-grade">
<input
type="number"
placeholder="Voto"
min="18"
max="30"
step="1"
value={corso.voto || ''}
onChange={e =>
aggiornaVoto(
corso.id,
parseInt((e.target as HTMLInputElement).value) || null,
)
}
/>
<label className={`lode-checkbox ${corso.voto !== 30 ? 'disabled' : ''}`}>
<input
type="checkbox"
checked={corso.lode}
disabled={corso.voto !== 30}
onChange={e =>
aggiornaLode(corso.id, (e.target as HTMLInputElement).checked)
}
/>
Lode
</label>
</div>
)}
<button
onClick={() => rimuoviCorso(corso.id)}
className="remove-btn"
title="Rimuovi materia"
>
×
</button>
</div>
))}
<div class="custom-form">
<input
type="text"
placeholder="Nome materia"
value={customCorso.nome}
onChange={e =>
setCustomCorso({ ...customCorso, nome: (e.target as HTMLInputElement).value })
}
/>
<input
type="number"
placeholder="CFU"
min="1"
max="30"
step="1"
value={customCorso.cfu || ''}
onChange={e =>
setCustomCorso({
...customCorso,
cfu: parseInt((e.target as HTMLInputElement).value) || 0,
})
}
/>
<button onClick={aggiungiCorsoCustom}>Aggiungi</button>
<button onClick={() => setShowCustomForm(false)}>Annulla</button>
</div>
)}
</div>
</div>
{/* Sezione lista corsi selezionati */}
<div class="card">
<div class="h-flex">
<div class="title">
<h2>Materie Selezionate</h2>
</div>
<div class="spacer"></div>
{corsiSelezionati.length > 0 && <button onClick={resetTutto}>🗑️ Cancella Tutto</button>}
</div>
{corsiSelezionati.length === 0 ? (
<p>Nessuna materia selezionata</p>
) : (
<div class="courses-list">
{corsiSelezionati.map(corso => (
<div key={corso.id} class="course-item">
<span class="course-name">{corso.nome}</span>
<span class="course-cfu">{corso.cfu} CFU</span>
{!corso.passFailOnly && (
<div class="course-grade tall">
<input
type="number"
placeholder="Voto"
min="18"
max="30"
step="1"
value={corso.voto || ''}
onChange={e =>
aggiornaVoto(
corso.id,
parseInt((e.target as HTMLInputElement).value) || null,
)
}
/>
<label class={`lode-checkbox ${corso.voto !== 30 ? 'disabled' : ''}`}>
<input
class="star"
type="checkbox"
checked={corso.lode}
disabled={corso.voto !== 30}
onChange={e =>
aggiornaLode(corso.id, (e.target as HTMLInputElement).checked)
}
/>
Lode
</label>
</div>
)}
<div class="actions tall">
<button
class="icon remove-btn"
onClick={() => rimuoviCorso(corso.id)}
title="Rimuovi materia"
>
×
</button>
</div>
</div>
))}
</div>
)}
</div>
{/* Pulsante Calcola */}
{corsiSelezionati.length > 0 && (
<div className="calculate-section">
<button onClick={calcolaMedia} className="calculate-btn">
🧮 Calcola Media e Voto di Laurea
</button>
<div class="calculate-section wide">
<button onClick={calcolaMedia}>🧮 Calcola Media e Voto di Laurea</button>
</div>
)}
{/* Risultati */}
{risultati && mostraRisultati && (
<div className="results">
<div class="results wide">
<h2>Risultati</h2>
{risultati.errore ? (
<div className="error-message">
<div class="error-message">
<p>⚠️ {risultati.errore}</p>
</div>
) : (
<div className="results-grid">
<div className="result-item">
<span className="label">Media Pesata:</span>
<span className="value">{risultati.mediaPesata}</span>
<div class="results-grid">
<div class="result-item">
<span class="label">Media Pesata:</span>
<span class="value">{risultati.mediaPesata}</span>
</div>
<div className="result-item">
<span className="label">Bonus Lodi:</span>
<span className="value">+{risultati.bonusLodi}</span>
<div class="result-item">
<span class="label">Bonus Lodi:</span>
<span class="value">+{risultati.bonusLodi}</span>
</div>
<div className="result-item highlight">
<span className="label">Voto di Ammissione:</span>
<span className="value">{risultati.votoAmmissione}</span>
<div class="result-item highlight">
<span class="label">Voto di Ammissione:</span>
<span class="value">{risultati.votoAmmissione}</span>
</div>
<div className="result-item highlight">
<span className="label">Massimo Voto Di Laurea Possibile:</span>
<span className="value">
<div class="result-item highlight">
<span class="label">Massimo Voto Di Laurea Possibile:</span>
<span class="value">
{risultati.massimoVotoLaurea}
{risultati.conLode && <span className="lode-badge">(+lode)</span>}
{risultati.conLode && <span class="lode-badge">(+lode)</span>}
</span>
</div>
</div>
@@ -720,9 +710,9 @@ function MediaPesataApp() {
)}
{/* Nota informativa */}
<div className="info-section">
<div class="card wide">
<h3>📋 Come viene calcolata la media</h3>
<div className="info-content">
<div class="info-content">
<h4>Regole di esclusione CFU:</h4>
<ul>
<li>
@@ -763,11 +753,11 @@ function MediaPesataApp() {
}
// Funzione per inizializzare l'app
export function initMediaPesataApp() {
const container = document.getElementById('media-pesata-app')
if (container) {
render(<MediaPesataApp />, container)
}
}
// export function initMediaPesataApp() {
// const container = document.getElementById('media-pesata-app')
// if (container) {
// render(<MediaPesataApp />, container)
// }
// }
export default MediaPesataApp
// export default MediaPesataApp