Lorenzo Pollutri 2 тижднів тому
коміт
a2eaec4c71
3 змінених файлів з 406 додано та 0 видалено
  1. +0
    -0
      README.md
  2. +406
    -0
      index.html
  3. BIN
      mappa.jpg

+ 406
- 0
index.html Переглянути файл

@@ -0,0 +1,406 @@
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8" />
<title>Consiglio Comunale</title>
<style>
body { font-family: Arial, sans-serif; background: #f0f2f5; padding: 20px; }
.container { background: #fff; padding: 30px; border-radius: 12px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); max-width: 1000px; margin: auto; }
h1 { text-align: center; color: #333; }
.input-group { display: flex; justify-content: center; align-items: center; margin-bottom: 20px; flex-wrap: wrap; }
label { font-size: 18px; margin-right: 10px; }
input[type="number"], input[type="file"], input[type="text"] { width: 250px; padding: 12px; font-size: 18px; border: 1px solid #ccc; border-radius: 8px; margin: 5px; }
button { padding: 12px 24px; font-size: 16px; border: none; border-radius: 8px; cursor: pointer; margin: 5px; }
#confirm { background-color: #28a745; color: white; }
#start { background-color: #007bff; color: white; }
#configure { background-color: #17a2b8; color: white; }
#mapConfig { background-color: #fd7e14; color: white; }
#stop { background-color: #dc3545; color: white; }
#exportLog, #clearLog, #exportXML, #importXML { background-color: #6c757d; color: white; }
#buttons, #config-buttons { margin-top: 20px; text-align: center; }
#buttons button { background-color: #6f42c1; color: white; margin: 5px; position: relative; }
#currentSpeaker { margin-top: 20px; font-size: 24px; font-weight: bold; border: 2px solid #007bff; padding: 20px; height: 100px; background: #e9ecef; display: flex; align-items: center; justify-content: center; border-radius: 8px; }
#mapContainer { position: relative; width: 800px; height: 600px; background-image: url('mappa.jpg'); background-size: cover; background-position: center; border: 2px solid #ccc; margin: 20px auto; }
.map-button { position: absolute; padding: 10px 15px; background-color: #007bff; color: white; border: none; border-radius: 8px; user-select: none; }
.hidden { display: none; }
#saveMapBtn { display: block; margin: 20px auto; }
button.clicked-feedback { animation: buttonFlash 0.3s ease-in-out; }
@keyframes buttonFlash {
0% { transform: scale(1); filter: brightness(1); }
50% { transform: scale(1.1); filter: brightness(1.5); }
100% { transform: scale(1); filter: brightness(1); }
}
</style>
</head>
<body>
<div class="container">
<h1>Consiglio Comunale</h1>

<div class="input-group">
<label for="numPartecipanti">Numero partecipanti:</label>
<input type="number" id="numPartecipanti" min="1" />
<button id="confirm">Conferma</button>
<button id="importXML">Importa Partecipanti (XML)</button>
<input type="file" id="fileInput" accept=".xml" style="display:none">
</div>

<div class="input-group hidden" id="sessionControls">
<button id="start">Avvia</button>
<button id="configure">Assegna nomi</button>
<button id="mapConfig">Configura mappa</button>
<button id="stop" class="hidden">Termina</button>
<button id="exportLog">Esporta Log</button>
<button id="clearLog">Pulisci Log</button>
<button id="exportXML" class="hidden">Esporta Elenco Partecipanti (XML)</button>
</div>

<div id="buttons" class="hidden"></div>
<div id="config-buttons" class="hidden"></div>
<div id="mapContainer" class="hidden"></div>
<button id="saveMapBtn" class="hidden">Salva Mappa</button>

<h2 style="text-align: center;">Chi sta parlando:</h2>
<div id="currentSpeaker">Nessuno</div>

<script>
let buttonLabels = [];
let buttonCoords = [];
let sessionLog = '';
let sessionStart = null;

let sessionState = {
active: false,
startTime: null,
currentSpeaker: 'Nessuno',
mapVisible: false
};

const exportXMLBtn = document.getElementById('exportXML');
const saveBtn = document.getElementById('saveMapBtn');
const map = document.getElementById('mapContainer');

function giveButtonFeedback(btn) {
btn.classList.add('clicked-feedback');
setTimeout(() => btn.classList.remove('clicked-feedback'), 300);
}

function applyButtonFeedback() {
document.querySelectorAll('button').forEach(btn => {
btn.addEventListener('click', () => giveButtonFeedback(btn));
});
}

function saveSessionLog() {
localStorage.setItem('sessionLog', sessionLog);
}

function saveSessionData() {
localStorage.setItem('buttonLabels', JSON.stringify(buttonLabels));
localStorage.setItem('buttonCoords', JSON.stringify(buttonCoords));
localStorage.setItem('sessionState', JSON.stringify(sessionState));
}

function logSpeech(index) {
const now = new Date();
const time = sessionStart ? new Date(now - sessionStart).toISOString().substr(11, 8) : now.toLocaleTimeString();
const entry = `[${time}] Parla: ${buttonLabels[index]}\n`;
sessionLog += entry;
sessionState.currentSpeaker = buttonLabels[index];
saveSessionLog();
saveSessionData();
document.getElementById('currentSpeaker').textContent = buttonLabels[index];
}

function createButtons() {
const div = document.getElementById('buttons');
div.innerHTML = '<hr style="margin: 20px 0;">';
buttonLabels.forEach((name, i) => {
const btn = document.createElement('button');
btn.textContent = name;
btn.addEventListener('click', () => logSpeech(i));
div.appendChild(btn);
});
div.classList.remove('hidden');
applyButtonFeedback();
saveSessionData();
}

function renderMapConfig() {
if (buttonLabels.length === 0) return;

map.innerHTML = '';
map.classList.remove('hidden');
saveBtn.classList.remove('hidden');
saveBtn.style.display = 'block';

const cols = Math.ceil(Math.sqrt(buttonLabels.length));
const spacingX = map.clientWidth / (cols + 1);
const spacingY = map.clientHeight / (cols + 1);
buttonLabels.forEach((name, i) => {
const x = buttonCoords[i].x;
const y = buttonCoords[i].y;
const b = document.createElement('button');
b.textContent = name;
b.className = 'map-button';
b.style.left = `${x}px`;
b.style.top = `${y}px`;
b.setAttribute('data-index', i);
b.onmousedown = startDrag;
map.appendChild(b);
});
}

function renderMapView() {
if (buttonLabels.length === 0) return;

map.innerHTML = '';
map.classList.remove('hidden');
saveBtn.classList.add('hidden');

buttonLabels.forEach((name, i) => {
const b = document.createElement('button');
b.textContent = name;
b.className = 'map-button';
b.style.left = `${buttonCoords[i].x}px`;
b.style.top = `${buttonCoords[i].y}px`;
b.style.cursor = 'pointer';
b.onclick = () => {
giveButtonFeedback(b);
logSpeech(i);
};
map.appendChild(b);
});
}
document.getElementById('confirm').addEventListener('click', () => {
const n = parseInt(document.getElementById('numPartecipanti').value);
if (!n || n < 1) return alert('Inserisci un numero valido');
const gruppi = ['Centro', 'Sinistra', 'Destra'];
buttonLabels = Array.from({ length: n }, (_, i) => {
const gruppo = gruppi[i % gruppi.length];
const numero = Math.floor(i / gruppi.length) + 1;
return `${gruppo} ${numero}`;
});
buttonCoords = Array.from({ length: n }, () => ({ x: 0, y: 0 }));
createButtons();
document.getElementById('sessionControls').classList.remove('hidden');
});

document.getElementById('start').addEventListener('click', () => {
if (!sessionStart) {
sessionStart = new Date();
sessionState.active = true;
sessionState.startTime = sessionStart.getTime();
sessionLog += `[00:00:00] Sessione avviata (${sessionStart.toLocaleString('it-IT')})\n`;
saveSessionLog();
saveSessionData();
document.getElementById('stop').classList.remove('hidden');
document.getElementById('start').classList.add('hidden');
document.getElementById('configure').classList.add('hidden');
}
});

document.getElementById('stop').addEventListener('click', () => {
if (sessionStart) {
const end = new Date();
const duration = new Date(end - sessionStart).toISOString().substr(11, 8);
sessionLog += `[${duration}] Sessione terminata (${end.toLocaleString('it-IT')})\n`;
sessionState.active = false;
sessionState.startTime = null;
saveSessionLog();
saveSessionData();
document.getElementById('stop').classList.add('hidden');
document.getElementById('start').classList.remove('hidden');
document.getElementById('configure').classList.remove('hidden');
}
});
document.getElementById('configure').addEventListener('click', () => {
const configDiv = document.getElementById('config-buttons');
configDiv.innerHTML = '';
saveBtn.classList.add('hidden');

buttonLabels.forEach((name, i) => {
const input = document.createElement('input');
input.type = 'text';
input.value = name;
input.dataset.index = i;
input.style.margin = '5px';
input.style.padding = '8px';
configDiv.appendChild(input);
});

const saveBtnNames = document.createElement('button');
saveBtnNames.textContent = 'Salva Nomi';
saveBtnNames.style.backgroundColor = '#28a745';
saveBtnNames.style.color = 'white';
saveBtnNames.style.marginTop = '20px';
saveBtnNames.style.display = 'block';
saveBtnNames.onclick = () => {
configDiv.querySelectorAll('input').forEach(input => {
const idx = parseInt(input.dataset.index);
buttonLabels[idx] = input.value;
});
saveSessionData();
configDiv.classList.add('hidden');
createButtons();
};

configDiv.appendChild(saveBtnNames);
configDiv.classList.remove('hidden');
});


document.getElementById('clearLog').addEventListener('click', () => {
if (confirm('Sei sicuro di voler cancellare il log e i dati?')) {
localStorage.clear();
location.reload();
}
});

document.getElementById('exportLog').addEventListener('click', () => {
const blob = new Blob([sessionLog], { type: 'text/plain' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = `log_consiglio_${new Date().toLocaleDateString('it-IT').replace(/\//g, '-')}.txt`;
link.click();
});

exportXMLBtn.addEventListener('click', () => {
let xml = '<?xml version="1.0" encoding="UTF-8"?><participants>';
buttonLabels.forEach((name, i) => {
const { x, y } = buttonCoords[i] || { x: 0, y: 0 };
xml += `<participant><name>${name}</name><x>${x}</x><y>${y}</y></participant>`;
});
xml += '</participants>';
const blob = new Blob([xml], { type: 'application/xml' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = `partecipanti_consiglio_${new Date().toLocaleDateString('it-IT').replace(/\//g, '-')}.xml`;
link.click();
});

document.getElementById('importXML').addEventListener('click', () => document.getElementById('fileInput').click());

document.getElementById('fileInput').addEventListener('change', e => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = evt => {
const xml = new DOMParser().parseFromString(evt.target.result, 'application/xml');
const participants = xml.getElementsByTagName('participant');
buttonLabels = [];
buttonCoords = [];
for (const p of participants) {
buttonLabels.push(p.getElementsByTagName('name')[0].textContent);
buttonCoords.push({
x: parseInt(p.getElementsByTagName('x')[0]?.textContent || 0),
y: parseInt(p.getElementsByTagName('y')[0]?.textContent || 0)
});
}
createButtons();
document.getElementById('sessionControls').classList.remove('hidden');
};
reader.readAsText(file);
});

let dragEl = null, offsetX = 0, offsetY = 0;
function startDrag(e) {
dragEl = e.target;
offsetX = e.offsetX;
offsetY = e.offsetY;
document.onmousemove = drag;
document.onmouseup = stopDrag;
}

function drag(e) {
if (!dragEl) return;
const rect = map.getBoundingClientRect();
const x = e.clientX - rect.left - offsetX;
const y = e.clientY - rect.top - offsetY;
dragEl.style.left = `${x}px`;
dragEl.style.top = `${y}px`;
}

function stopDrag() {
if (dragEl) {
const i = dragEl.dataset.index;
buttonCoords[i] = {
x: parseInt(dragEl.style.left),
y: parseInt(dragEl.style.top)
};
dragEl = null;
document.onmousemove = null;
document.onmouseup = null;
saveSessionData();
}
}

document.getElementById('mapConfig').addEventListener('click', () => {
sessionState.mapVisible = true;
saveSessionData();
renderMapConfig();
});

saveBtn.addEventListener('click', (event) => {
if (map.classList.contains('hidden')) return;
giveButtonFeedback(event.currentTarget);

document.querySelectorAll('.map-button').forEach(b => {
b.onmousedown = null;
b.style.cursor = 'pointer';
b.onclick = () => {
giveButtonFeedback(b);
logSpeech(b.dataset.index);
};
});

sessionState.mapVisible = true;
saveSessionData();

saveBtn.style.display = 'none';
saveBtn.classList.add('hidden');
exportXMLBtn.classList.remove('hidden');
});

window.addEventListener('load', () => {
map.classList.add('hidden');
saveBtn.classList.add('hidden');
saveBtn.style.display = 'none';

const savedLog = localStorage.getItem('sessionLog');
if (savedLog) sessionLog = savedLog;

const savedLabels = localStorage.getItem('buttonLabels');
const savedCoords = localStorage.getItem('buttonCoords');
const savedState = localStorage.getItem('sessionState');

if (savedLabels && savedCoords) {
buttonLabels = JSON.parse(savedLabels);
buttonCoords = JSON.parse(savedCoords);
createButtons();
document.getElementById('sessionControls').classList.remove('hidden');
}

if (savedState) {
sessionState = JSON.parse(savedState);
if (sessionState.active && sessionState.startTime) {
sessionStart = new Date(sessionState.startTime);
document.getElementById('start').classList.add('hidden');
document.getElementById('stop').classList.remove('hidden');
document.getElementById('configure').classList.add('hidden');
}
document.getElementById('currentSpeaker').textContent = sessionState.currentSpeaker || 'Nessuno';

if (sessionState.mapVisible) {
renderMapView();
}
}

applyButtonFeedback();
document.getElementById('numPartecipanti').value = '';
});
</script>
</div>
</body>
</html>


BIN
mappa.jpg Переглянути файл

Перед Після
Ширина: 1076  |  Висота: 927  |  Розмір: 9.8 KiB

Завантаження…
Відмінити
Зберегти