netgescon-day0/resources/views/mobile/assemblea/convocazione-mobile.blade.php

363 lines
21 KiB
PHP

<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NetGescon Convocazione & Votazione</title>
<meta name="csrf-token" content="{{ csrf_token() }}">
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Google Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<!-- FontAwesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
body {
font-family: 'Outfit', sans-serif;
background: radial-gradient(circle at top, #1e293b 0%, #0f172a 100%);
}
.glass-card {
background: rgba(30, 41, 59, 0.7);
backdrop-filter: blur(16px);
border: 1px solid rgba(255, 255, 255, 0.08);
}
</style>
</head>
<body class="min-h-screen text-slate-100 flex flex-col justify-between">
<!-- Header -->
<header class="glass-card sticky top-0 z-50 border-b border-slate-800 px-4 py-3 flex items-center justify-between">
<div class="flex items-center space-x-2">
<div class="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center shadow-lg shadow-blue-500/20">
<i class="fas fa-building text-white text-sm"></i>
</div>
<div>
<h1 class="text-xs font-semibold tracking-wider text-blue-400 uppercase">NetGescon Mobile</h1>
<p class="text-sm font-bold truncate max-w-[200px]">{{ $assemblea->stabile->denominazione }}</p>
</div>
</div>
<div class="text-xxs px-2 py-1 rounded-full bg-slate-800 text-slate-400 font-semibold border border-slate-700">
Unit: {{ $convocazione->unitaImmobiliare->interno ?: $convocazione->unitaImmobiliare->codice_unita }}
</div>
</header>
<!-- Main Content -->
<main class="flex-grow p-4 space-y-6">
<!-- Welcome Card -->
<div class="glass-card rounded-2xl p-5 shadow-xl space-y-4">
<div class="flex justify-between items-start">
<div>
<h2 class="text-lg font-bold text-white">Gentile {{ $convocazione->soggetto->nome_completo ?? 'Condomino' }}</h2>
<p class="text-xs text-slate-400">Sei collegato alla gestione digitale dell'assemblea.</p>
</div>
<span class="px-2 py-1 text-[10px] font-bold uppercase rounded bg-indigo-500/20 text-indigo-300 border border-indigo-500/30">
{{ $convocazione->ruolo === 'I' ? 'Inquilino' : 'Proprietario' }}
</span>
</div>
<!-- Receipt Confirmation Box -->
<div id="receipt-box" class="p-3.5 rounded-xl bg-slate-900/60 border border-slate-800 flex items-center justify-between gap-3">
<div class="flex items-center space-x-3">
<div id="receipt-icon" class="w-9 h-9 rounded-lg flex items-center justify-center {{ $convocazione->ricezione_confermata_at ? 'bg-emerald-500/20 text-emerald-400 border border-emerald-500/30' : 'bg-amber-500/20 text-amber-400 border border-amber-500/30' }}">
<i class="fas {{ $convocazione->ricezione_confermata_at ? 'fa-check-double' : 'fa-envelope-open' }}"></i>
</div>
<div>
<p class="text-xs font-semibold text-slate-300">Ricezione convocazione</p>
<p id="receipt-status" class="text-[10px] text-slate-400">
{{ $convocazione->ricezione_confermata_at ? 'Firma apposta il ' . $convocazione->ricezione_confermata_at->format('d/m/Y H:i') : 'Firma elettronica richiesta' }}
</p>
</div>
</div>
@if(!$convocazione->ricezione_confermata_at)
<button onclick="confirmReceipt()" id="receipt-btn" class="px-3 py-1.5 bg-blue-600 hover:bg-blue-500 text-white rounded-lg text-xs font-semibold shadow-lg shadow-blue-500/10 active:scale-95 transition-all">
Firma
</button>
@endif
</div>
</div>
<!-- Assembly Info Panel -->
<div class="glass-card rounded-2xl p-5 shadow-xl space-y-4">
<h3 class="text-sm font-bold text-slate-300 uppercase tracking-wider flex items-center"><i class="fas fa-calendar-day mr-2 text-blue-500"></i>Dettagli convocazione</h3>
<div class="grid grid-cols-2 gap-4 text-xs">
<div class="bg-slate-900/40 p-3 rounded-xl border border-slate-800/80">
<p class="text-slate-400 mb-1">1ª Convocazione</p>
<p class="font-bold text-white">{{ $assemblea->data_prima_convocazione ? $assemblea->data_prima_convocazione->format('d/m/Y H:i') : 'n/d' }}</p>
</div>
<div class="bg-slate-900/40 p-3 rounded-xl border border-slate-800/80">
<p class="text-slate-400 mb-1">2ª Convocazione</p>
<p class="font-bold text-white">{{ $assemblea->data_seconda_convocazione ? $assemblea->data_seconda_convocazione->format('d/m/Y H:i') : 'n/d' }}</p>
</div>
</div>
<div class="bg-slate-900/40 p-3 rounded-xl border border-slate-800/80 text-xs">
<p class="text-slate-400 mb-1">Sede e Luogo</p>
<p class="font-semibold text-white flex items-center"><i class="fas fa-map-marker-alt mr-1.5 text-red-400"></i>{{ $assemblea->luogo ?: 'Presso i locali condominiali / Telematica' }}</p>
</div>
</div>
<!-- Real-Time Voting Box -->
<div id="live-voting-panel" class="p-5 rounded-2xl bg-gradient-to-br from-indigo-900/50 to-purple-900/30 border border-indigo-500/20 shadow-2xl relative overflow-hidden hidden">
<!-- Pulsante decorativo pulsante -->
<div class="absolute -top-12 -right-12 w-28 h-28 bg-indigo-500/10 rounded-full blur-xl animate-pulse"></div>
<div class="flex items-center justify-between mb-4">
<div class="flex items-center space-x-2">
<span class="flex h-2.5 w-2.5 relative">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-400 opacity-75"></span>
<span class="relative inline-flex rounded-full h-2.5 w-2.5 bg-red-500"></span>
</span>
<h3 class="text-sm font-bold tracking-wider text-indigo-300 uppercase">Votazione in corso</h3>
</div>
<span class="px-2 py-0.5 rounded-full bg-indigo-500/20 text-indigo-300 text-[10px] font-semibold border border-indigo-400/20">LIVE</span>
</div>
<div id="live-odg-details" class="space-y-4">
<div>
<h4 id="live-odg-title" class="text-md font-bold text-white leading-snug"></h4>
<p id="live-odg-desc" class="text-xs text-slate-300 mt-1.5 leading-relaxed"></p>
</div>
<!-- Bottoni di voto -->
<div class="space-y-3 pt-2">
<!-- Votazione propria -->
<div id="own-vote-section" class="space-y-2">
<p class="text-[11px] font-semibold text-slate-400 uppercase tracking-wider">Il tuo voto (Int. {{ $convocazione->unitaImmobiliare->interno }})</p>
<div class="grid grid-cols-3 gap-2">
<button onclick="submitVote('favorevole')" class="py-2.5 rounded-xl border border-emerald-500/20 bg-emerald-500/10 hover:bg-emerald-500/20 text-emerald-400 text-xs font-bold active:scale-95 transition-all flex flex-col items-center justify-center gap-1">
<i class="fas fa-thumbs-up"></i> Favorevole
</button>
<button onclick="submitVote('contrario')" class="py-2.5 rounded-xl border border-rose-500/20 bg-rose-500/10 hover:bg-rose-500/20 text-rose-400 text-xs font-bold active:scale-95 transition-all flex flex-col items-center justify-center gap-1">
<i class="fas fa-thumbs-down"></i> Contrario
</button>
<button onclick="submitVote('astenuto')" class="py-2.5 rounded-xl border border-slate-500/20 bg-slate-500/10 hover:bg-slate-500/20 text-slate-400 text-xs font-bold active:scale-95 transition-all flex flex-col items-center justify-center gap-1">
<i class="fas fa-ban"></i> Astenuto
</button>
</div>
</div>
<!-- Sezione deleghe -->
@if($deleghe->isNotEmpty())
<div id="deleghe-vote-section" class="mt-4 pt-3 border-t border-slate-800 space-y-3">
<p class="text-[11px] font-semibold text-slate-400 uppercase tracking-wider">Voti delegati in delega</p>
@foreach($deleghe as $delega)
<div class="p-2.5 rounded-xl bg-slate-900/60 border border-slate-800 space-y-2">
<div class="flex justify-between items-center text-[10px] text-slate-300">
<span>Int. {{ $delega->unitaImmobiliare->interno }} - {{ $delega->soggetto->nome_completo }}</span>
<span class="text-indigo-400 font-semibold">Delega</span>
</div>
<div class="grid grid-cols-3 gap-2">
<button onclick="submitVote('favorevole', {{ $delega->unita_immobiliare_id }}, {{ $delega->soggetto_id }}, this)" class="py-1.5 rounded-lg border border-emerald-500/20 bg-emerald-500/10 text-emerald-400 text-[11px] font-semibold active:scale-95 transition-all">
Favorevole
</button>
<button onclick="submitVote('contrario', {{ $delega->unita_immobiliare_id }}, {{ $delega->soggetto_id }}, this)" class="py-1.5 rounded-lg border border-rose-500/20 bg-rose-500/10 text-rose-400 text-[11px] font-semibold active:scale-95 transition-all">
Contrario
</button>
<button onclick="submitVote('astenuto', {{ $delega->unita_immobiliare_id }}, {{ $delega->soggetto_id }}, this)" class="py-1.5 rounded-lg border border-slate-500/20 bg-slate-500/10 text-slate-400 text-[11px] font-semibold active:scale-95 transition-all">
Astenuto
</button>
</div>
</div>
@endforeach
</div>
@endif
</div>
</div>
</div>
<!-- Ordine del giorno & Allegati -->
<div class="glass-card rounded-2xl p-5 shadow-xl space-y-4">
<h3 class="text-sm font-bold text-slate-300 uppercase tracking-wider flex items-center"><i class="fas fa-list-ol mr-2 text-blue-500"></i>Ordine del Giorno (OdG)</h3>
<div class="space-y-3">
@foreach($odg as $punto)
<div class="p-3.5 rounded-xl bg-slate-900/40 border border-slate-800 space-y-2">
<div class="flex items-start justify-between gap-3">
<div class="flex items-start space-x-2">
<span class="w-5 h-5 rounded-full bg-slate-800 flex items-center justify-center text-xxs font-bold text-slate-300 mt-0.5">
{{ $punto->numero_punto }}
</span>
<div>
<h4 class="text-xs font-bold text-white leading-snug">{{ $punto->titolo }}</h4>
<p class="text-xxs text-slate-400 mt-1 leading-relaxed">{{ $punto->descrizione }}</p>
</div>
</div>
</div>
<div class="flex flex-wrap items-center justify-between gap-2 pt-2 border-t border-slate-800/80 text-[10px]">
@if($punto->articolo_legge)
<span class="text-blue-400"><i class="fas fa-gavel mr-1"></i>Rif: {{ $punto->articolo_legge }}</span>
@endif
@if($punto->tabellaMillesimale)
<span class="text-slate-400"><i class="fas fa-calculator mr-1"></i>Tab: {{ $punto->tabellaMillesimale->denominazione }}</span>
@endif
</div>
</div>
@endforeach
</div>
</div>
</main>
<!-- Toast Notification -->
<div id="toast" class="fixed bottom-4 left-4 right-4 p-4 rounded-xl bg-slate-900 border border-slate-800 shadow-2xl flex items-center justify-between text-xs transition-all transform translate-y-24 opacity-0 z-50">
<span id="toast-message" class="text-slate-300"></span>
<button onclick="hideToast()" class="text-slate-400 hover:text-white"><i class="fas fa-times"></i></button>
</div>
<!-- Footer -->
<footer class="text-center py-4 text-xxs text-slate-500 border-t border-slate-800 glass-card">
NetGescon © 2026 - Piattaforma Gestione Assemblee Condominiali
</footer>
<!-- Polling & Event Javascript -->
<script>
const token = "{{ $convocazione->token_accesso }}";
let activeOdgId = null;
function showToast(message, isSuccess = true) {
const toast = document.getElementById('toast');
const msgEl = document.getElementById('toast-message');
msgEl.textContent = message;
if (isSuccess) {
toast.classList.remove('border-red-500/20');
toast.classList.add('border-emerald-500/20');
} else {
toast.classList.remove('border-emerald-500/20');
toast.classList.add('border-red-500/20');
}
toast.classList.remove('translate-y-24', 'opacity-0');
setTimeout(hideToast, 4000);
}
function hideToast() {
document.getElementById('toast').classList.add('translate-y-24', 'opacity-0');
}
function confirmReceipt() {
const btn = document.getElementById('receipt-btn');
btn.disabled = true;
btn.innerHTML = '<i class="fas fa-spinner animate-spin"></i>';
fetch(`/public/assemblea/${token}/ricezione`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
}
})
.then(res => res.json())
.then(data => {
if (data.success) {
showToast('Firma elettronica inserita con successo!');
btn.remove();
document.getElementById('receipt-status').textContent = 'Firma apposta il ' + data.ricezione_confermata_at;
document.getElementById('receipt-icon').className = 'w-9 h-9 rounded-lg flex items-center justify-center bg-emerald-500/20 text-emerald-400 border border-emerald-500/30';
document.getElementById('receipt-icon').innerHTML = '<i class="fas fa-check-double"></i>';
} else {
showToast(data.message || 'Errore durante la firma.', false);
btn.disabled = false;
btn.textContent = 'Firma';
}
})
.catch(err => {
console.error(err);
showToast('Errore di rete. Riprova.', false);
btn.disabled = false;
btn.textContent = 'Firma';
});
}
function submitVote(voteStr, unitaId = null, soggettoId = null, btnEl = null) {
if (btnEl) {
// Disable sister buttons for the same delegation
const container = btnEl.closest('div');
container.querySelectorAll('button').forEach(b => {
b.classList.remove('bg-emerald-500/30', 'bg-rose-500/30', 'bg-slate-500/30', 'border-emerald-500/40', 'border-rose-500/40', 'border-slate-500/40');
});
}
fetch(`/public/assemblea/${token}/vota`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
},
body: JSON.stringify({
ordine_giorno_id: activeOdgId,
voto: voteStr,
unita_immobiliare_id: unitaId,
soggetto_id: soggettoId
})
})
.then(res => res.json())
.then(data => {
if (data.success) {
showToast('Voto inviato con successo!');
if (btnEl) {
// Highlight selected button
let colorClass = voteStr === 'favorevole' ? 'bg-emerald-500/30 border-emerald-500/40' : (voteStr === 'contrario' ? 'bg-rose-500/30 border-rose-500/40' : 'bg-slate-500/30 border-slate-500/40');
btnEl.className.split(' ').forEach(c => {
if (c.startsWith('bg-') || c.startsWith('border-')) {
btnEl.classList.remove(c);
}
});
colorClass.split(' ').forEach(c => btnEl.classList.add(c));
} else {
// Highlight own vote section
const container = document.getElementById('own-vote-section');
container.querySelectorAll('button').forEach(b => {
b.classList.remove('bg-emerald-500/30', 'bg-rose-500/30', 'bg-slate-500/30');
if (b.onclick.toString().includes(voteStr)) {
let bg = voteStr === 'favorevole' ? 'bg-emerald-500/30' : (voteStr === 'contrario' ? 'bg-rose-500/30' : 'bg-slate-500/30');
b.classList.add(bg);
}
});
}
} else {
showToast(data.message || 'Errore nell\'invio del voto.', false);
}
})
.catch(err => {
console.error(err);
showToast('Errore di rete. Riprova.', false);
});
}
function pollActiveVoting() {
fetch(`/public/assemblea/${token}/poll`)
.then(res => res.json())
.then(data => {
const livePanel = document.getElementById('live-voting-panel');
if (data.attivo_odg_id) {
if (activeOdgId !== data.attivo_odg_id) {
activeOdgId = data.attivo_odg_id;
document.getElementById('live-odg-title').textContent = `${data.punto.numero}. ${data.punto.titolo}`;
document.getElementById('live-odg-desc').textContent = data.punto.descrizione;
// Reset selections in buttons
document.querySelectorAll('#live-voting-panel button').forEach(b => {
b.classList.remove('bg-emerald-500/30', 'bg-rose-500/30', 'bg-slate-500/30', 'border-emerald-500/40', 'border-rose-500/40', 'border-slate-500/40');
});
livePanel.classList.remove('hidden');
showToast('Nuova votazione aperta!');
}
} else {
activeOdgId = null;
livePanel.classList.add('hidden');
}
})
.catch(err => console.error('Polling error:', err));
}
// Avvia polling ogni 3 secondi
setInterval(pollActiveVoting, 3000);
pollActiveVoting();
</script>
</body>
</html>