<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Divisore di Gruppi Dinamico</title>
<!-- Carica Tailwind CSS per uno stile moderno e responsive -->
<script src="https://cdn.tailwindcss.com"></script>
<style>
/* Stili personalizzati per la tipografia e l'aspetto generale */
body {
font-family: 'Inter', sans-serif;
background-color: #f7f9fb;
}
.group-card {
min-height: 150px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.06);
transition: transform 0.2s, box-shadow 0.2s;
}
.group-card:hover {
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.05);
transform: translateY(-2px);
}
</style>
</head>
<body class="p-4 sm:p-8">
<div class="max-w-4xl mx-auto">
<h1 class="text-3xl sm:text-4xl font-extrabold text-gray-800 mb-2">Generatore di Gruppi per Lezioni</h1>
<p class="text-gray-600 mb-6 sm:mb-8">
Gestisci la lista dei partecipanti, segna le presenze e genera i gruppi casuali con un clic.
</p>
<!-- Sezione di configurazione -->
<div class="bg-white p-6 rounded-xl shadow-lg mb-8 border border-gray-200">
<h2 class="text-2xl font-bold text-gray-700 mb-4 border-b pb-2">Configurazione Lezione</h2>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- Colonna 1: Numero di Gruppi -->
<div class="col-span-1">
<label for="numGroupsInput" class="block text-sm font-medium text-gray-700 mb-2">Numero di Gruppi Desiderato</label>
<input type="number" id="numGroupsInput" min="1" value="4"
class="w-full p-2 border border-gray-300 rounded-lg focus:ring-indigo-500 focus:border-indigo-500 shadow-sm"
onchange="saveSettings()">
</div>
<!-- Colonna 2 & 3: Lista Master e Nomi Gruppi -->
<div class="col-span-2 grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label for="groupNamesInput" class="block text-sm font-medium text-gray-700 mb-2">Nomi dei Gruppi (separati da virgola)</label>
<input type="text" id="groupNamesInput"
class="w-full p-2 border border-gray-300 rounded-lg focus:ring-indigo-500 focus:border-indigo-500 shadow-sm"
value="Squadra Alfa, I Pionieri, Le Sentinelle, Gruppo Zenith"
onchange="saveSettings()">
<p class="text-xs text-gray-500 mt-1">Se ci sono più gruppi che nomi, verranno usati i numeri.</p>
</div>
<div>
<label for="masterListArea" class="block text-sm font-medium text-gray-700 mb-2">Lista Master (un nome per riga)</label>
<textarea id="masterListArea" rows="4"
class="w-full p-2 border border-gray-300 rounded-lg focus:ring-indigo-500 focus:border-indigo-500 shadow-sm"
onchange="updateAttendanceAndSave()">Aurora
Fulvia
Eleonora
Lucia B.
Edoardo
Giorgia
Esra
Giovanni
Andrea
Mattia
Riccardo
Jacopo
Kamela
Lucioa Lol.
Fabio
Donatella
Rachid
Alessandra
Dimitri
Marta
Laura
Filippo</textarea>
</div>
</div>
</div>
<!-- Sezione Presenze -->
<div class="mt-6 border-t pt-4">
<h3 class="text-lg font-bold text-gray-700 mb-3">Segna Presenze (<span id="attendeeCount">0</span> persone presenti)</h3>
<div id="attendanceContainer" class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-3">
<!-- Checkbox delle presenze verranno inserite qui dal JS -->
</div>
</div>
</div>
<!-- Pulsante di generazione -->
<button id="shuffleButton"
class="w-full sm:w-auto px-8 py-3 bg-indigo-600 text-white font-bold text-lg rounded-xl hover:bg-indigo-700 transition duration-150 transform hover:scale-[1.01] focus:outline-none focus:ring-4 focus:ring-indigo-500 focus:ring-opacity-50 active:bg-indigo-800 shadow-lg mb-8">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 inline-block mr-2 align-text-bottom" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
Mescola e Genera Gruppi
</button>
<!-- Area di visualizzazione dei gruppi -->
<div id="groupsContainer" class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- I risultati dei gruppi saranno inseriti qui -->
</div>
<div id="statusMessage" class="mt-6 text-center text-gray-500 italic">
Configura la lista e il numero di gruppi, quindi clicca sul pulsante.
</div>
</div>
<script type="text/javascript">
// Costanti per le chiavi di localStorage
const STORAGE_KEY_PEOPLE = 'group_shuffler_people';
const STORAGE_KEY_GROUP_NAMES = 'group_shuffler_group_names';
const STORAGE_KEY_NUM_GROUPS = 'group_shuffler_num_groups';
const button = document.getElementById('shuffleButton');
const groupsContainer = document.getElementById('groupsContainer');
const statusMessage = document.getElementById('statusMessage');
const masterListArea = document.getElementById('masterListArea');
const numGroupsInput = document.getElementById('numGroupsInput');
const groupNamesInput = document.getElementById('groupNamesInput');
const attendanceContainer = document.getElementById('attendanceContainer');
const attendeeCountSpan = document.getElementById('attendeeCount');
/**
* Salva le impostazioni correnti (lista master, nomi gruppi, numero gruppi) in localStorage.
*/
function saveSettings() {
// Salva la lista master (un nome per riga)
localStorage.setItem(STORAGE_KEY_PEOPLE, masterListArea.value);
// Salva i nomi dei gruppi
localStorage.setItem(STORAGE_KEY_GROUP_NAMES, groupNamesInput.value);
// Salva il numero di gruppi
localStorage.setItem(STORAGE_KEY_NUM_GROUPS, numGroupsInput.value);
}
/**
* Carica le impostazioni da localStorage all'avvio.
*/
function loadSettings() {
const savedPeople = localStorage.getItem(STORAGE_KEY_PEOPLE);
const savedGroupNames = localStorage.getItem(STORAGE_KEY_GROUP_NAMES);
const savedNumGroups = localStorage.getItem(STORAGE_KEY_NUM_GROUPS);
if (savedPeople) {
masterListArea.value = savedPeople;
}
if (savedGroupNames) {
groupNamesInput.value = savedGroupNames;
}
if (savedNumGroups) {
numGroupsInput.value = savedNumGroups;
}
// Renderizza la lista delle presenze dopo aver caricato la lista master
renderAttendanceList();
}
/**
* Renderizza la lista di checkbox per segnare le presenze.
*/
function renderAttendanceList() {
const people = getMasterList();
attendanceContainer.innerHTML = '';
if (people.length === 0) {
attendanceContainer.innerHTML = '<p class="text-sm text-gray-500 italic col-span-full">Inserisci i nomi nella lista master per segnare le presenze.</p>';
attendeeCountSpan.textContent = '0';
return;
}
people.forEach(person => {
const isChecked = true; // Di default, tutti sono presenti all'inizio
const html = `
<div class="flex items-center">
<input id="person-${person}" type="checkbox" value="${person}" ${isChecked ? 'checked' : ''}
class="h-4 w-4 text-indigo-600 border-gray-300 rounded cursor-pointer"
onchange="updateAttendeeCount()">
<label for="person-${person}" class="ml-2 text-sm text-gray-700 whitespace-nowrap overflow-hidden text-ellipsis cursor-pointer" title="${person}">
${person}
</label>
</div>
`;
attendanceContainer.insertAdjacentHTML('beforeend', html);
});
updateAttendeeCount();
}
/**
* Aggiorna il conteggio delle persone presenti.
*/
function updateAttendeeCount() {
const attendees = getAttendees();
attendeeCountSpan.textContent = attendees.length.toString();
}
/**
* Funzione di utility per estrarre la lista master dall'area di testo.
* @returns {Array<string>} La lista dei nomi senza righe vuote.
*/
function getMasterList() {
return masterListArea.value
.split('\n')
.map(name => name.trim())
.filter(name => name.length > 0);
}
/**
* Aggiorna la lista di presenze e salva le impostazioni. Chiamata su cambio della Master List.
*/
function updateAttendanceAndSave() {
saveSettings();
renderAttendanceList();
}
/**
* Ottiene l'elenco dei partecipanti selezionati dalle checkbox.
* @returns {Array<string>} I nomi delle persone presenti.
*/
function getAttendees() {
const checkboxes = attendanceContainer.querySelectorAll('input[type="checkbox"]:checked');
return Array.from(checkboxes).map(checkbox => checkbox.value);
}
/**
* Ottiene l'elenco dei nomi dei gruppi.
* @returns {Array<string>} La lista dei nomi dei gruppi.
*/
function getGroupNames() {
return groupNamesInput.value
.split(',')
.map(name => name.trim())
.filter(name => name.length > 0);
}
/**
* Implementa l'algoritmo Fisher-Yates per mescolare un array in modo casuale.
* @param {Array<string>} array L'array da mescolare.
* @returns {Array<string>} L'array mescolato.
*/
function shuffleArray(array) {
const shuffled = [...array];
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
}
return shuffled;
}
/**
* Divide un array mescolato in un numero flessibile di gruppi, cercando l'equilibrio.
* @param {Array<string>} shuffledPeople L'array di persone mescolate.
* @param {number} numGroups Il numero di gruppi desiderato.
* @returns {Array<Array<string>>} Un array contenente i gruppi.
*/
function divideIntoGroups(shuffledPeople, numGroups) {
const P = shuffledPeople.length; // Persone
const G = numGroups; // Gruppi
if (P === 0 || G <= 0) return [];
// Calcolo la dimensione base del gruppo (parte intera)
const S = Math.floor(P / G);
// Calcolo il resto (numero di gruppi che avranno S+1 persone)
const R = P % G;
const groups = [];
let currentIndex = 0;
for (let i = 0; i < G; i++) {
// Se i < R, il gruppo avrà dimensione S+1, altrimenti S
const size = i < R ? S + 1 : S;
if (size > 0) {
const group = shuffledPeople.slice(currentIndex, currentIndex + size);
groups.push(group);
currentIndex += size;
}
}
return groups;
}
/**
* Genera e visualizza i gruppi.
*/
function generateAndDisplayGroups() {
const attendees = getAttendees();
const numGroups = parseInt(numGroupsInput.value, 10);
const groupNames = getGroupNames();
groupsContainer.innerHTML = '';
if (attendees.length < numGroups) {
groupsContainer.innerHTML = `<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-xl col-span-full" role="alert"><p class="font-bold">Attenzione!</p><p class="text-sm">Il numero di persone presenti (${attendees.length}) è inferiore al numero di gruppi desiderato (${numGroups}). Riduci il numero di gruppi o aggiungi persone.</p></div>`;
statusMessage.classList.add('hidden');
return;
}
if (numGroups <= 0) {
groupsContainer.innerHTML = `<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-xl col-span-full" role="alert"><p class="font-bold">Errore!</p><p class="text-sm">Il numero di gruppi deve essere maggiore di zero.</p></div>`;
statusMessage.classList.add('hidden');
return;
}
// 1. Mescola le persone presenti
const shuffledPeople = shuffleArray(attendees);
// 2. Dividi nei gruppi
const groups = divideIntoGroups(shuffledPeople, numGroups);
// 3. Mescola i nomi dei gruppi (per assegnarli in modo casuale)
const shuffledNames = shuffleArray(groupNames);
// 4. Pulisci il contenitore e mostra i risultati
statusMessage.classList.add('hidden');
groups.forEach((group, index) => {
let groupName = `Gruppo ${index + 1}`;
// Assegna un nome casuale se disponibile
if (shuffledNames[index]) {
groupName = shuffledNames[index];
}
const membersList = group.map(person =>
// Stile per ogni membro
`<span class="inline-block bg-indigo-100 text-indigo-800 text-sm font-medium mr-2 mb-2 px-3 py-1 rounded-full border border-indigo-200 whitespace-nowrap">${person}</span>`
).join('');
// Crea la card HTML per il gruppo
const groupCardHtml = `
<div class="group-card bg-white p-5 rounded-xl border border-gray-200">
<h2 class="text-xl font-bold text-indigo-700 mb-3">${groupName}</h2>
<p class="text-sm font-medium text-gray-500 mb-3">Membri: ${group.length} persone</p>
<div>
${membersList}
</div>
</div>
`;
groupsContainer.insertAdjacentHTML('beforeend', groupCardHtml);
});
}
// --- Inizializzazione ---
// Aggiungi il listener al pulsante
button.addEventListener('click', generateAndDisplayGroups);
// Carica le impostazioni e renderizza la lista di presenze all'avvio
document.addEventListener('DOMContentLoaded', () => {
loadSettings();
generateAndDisplayGroups(); // Genera una prima divisione all'avvio
});
</script>
</body>
</html>