netgescon-master/netgescon-laravel/resources/views/components/layout/header/notifications.blade.php

315 lines
9.9 KiB
PHP

{{--
========================================
NOTIFICHE HEADER
========================================
Componente notifiche con badge contatore,
dropdown e gestione real-time.
Props:
- $maxVisible (int): Max notifiche visibili nel dropdown
- $showBadge (bool): Mostra badge contatore
- $realTime (bool): Abilita aggiornamenti real-time
Autore: NetGesCon Development Team
Data: 2024
========================================
--}}
@props([
'maxVisible' => 10,
'showBadge' => true,
'realTime' => true
])
@php
// Recupera notifiche utente (sostituire con logica reale)
$notifications = collect([
(object)[
'id' => 1,
'type' => 'ticket',
'title' => 'Nuovo ticket aperto',
'message' => 'Ticket #123 - Problema ascensore',
'icon' => 'fas fa-ticket-alt',
'color' => 'warning',
'time' => '2 min fa',
'read' => false,
'url' => '#'
],
(object)[
'id' => 2,
'type' => 'system',
'title' => 'Backup completato',
'message' => 'Backup automatico database eseguito con successo',
'icon' => 'fas fa-database',
'color' => 'success',
'time' => '1 ora fa',
'read' => false,
'url' => '#'
]
]);
$unreadCount = $notifications->where('read', false)->count();
@endphp
<div class="netgescon-notifications dropdown">
{{-- Pulsante notifiche --}}
<button class="btn btn-outline-secondary position-relative"
type="button"
data-bs-toggle="dropdown"
aria-expanded="false"
aria-label="Notifiche ({{ $unreadCount }} non lette)"
title="Notifiche">
<i class="fas fa-bell"></i>
{{-- Badge contatore --}}
@if($showBadge && $unreadCount > 0)
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
{{ $unreadCount > 99 ? '99+' : $unreadCount }}
<span class="visually-hidden">notifiche non lette</span>
</span>
@endif
</button>
{{-- Dropdown notifiche --}}
<div class="dropdown-menu dropdown-menu-end notifications-dropdown">
{{-- Header --}}
<div class="dropdown-header d-flex justify-content-between align-items-center">
<h6 class="mb-0">Notifiche</h6>
@if($unreadCount > 0)
<button type="button" class="btn btn-sm btn-link p-0" id="markAllRead">
<small>Segna tutte lette</small>
</button>
@endif
</div>
{{-- Lista notifiche --}}
@if($notifications->count() > 0)
<div class="notifications-list">
@foreach($notifications->take($maxVisible) as $notification)
<a href="{{ $notification->url }}"
class="dropdown-item notification-item {{ !$notification->read ? 'unread' : '' }}"
data-notification-id="{{ $notification->id }}">
<div class="d-flex align-items-start">
<div class="notification-icon me-3">
<i class="{{ $notification->icon }} text-{{ $notification->color }}"></i>
</div>
<div class="notification-content flex-grow-1">
<div class="notification-title fw-semibold">{{ $notification->title }}</div>
<div class="notification-message text-muted small">{{ $notification->message }}</div>
<div class="notification-time text-muted small">
<i class="fas fa-clock me-1"></i>{{ $notification->time }}
</div>
</div>
@if(!$notification->read)
<div class="notification-unread">
<span class="badge bg-primary rounded-circle">&nbsp;</span>
</div>
@endif
</div>
</a>
@endforeach
</div>
{{-- Footer --}}
@if($notifications->count() > $maxVisible)
<div class="dropdown-divider"></div>
<a href="#" class="dropdown-item text-center text-primary" title="Notifiche (in sviluppo)">
<i class="fas fa-eye me-1"></i>Vedi tutte le notifiche
</a>
@endif
@else
{{-- Nessuna notifica --}}
<div class="dropdown-item-text text-center py-4">
<i class="fas fa-bell-slash fa-2x text-muted mb-2"></i>
<div class="text-muted">Nessuna notifica</div>
</div>
@endif
</div>
</div>
{{-- CSS per notifiche --}}
@push('styles')
<style>
.notifications-dropdown {
width: 350px;
max-height: 500px;
overflow-y: auto;
border: none;
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
}
.notification-item {
padding: 1rem;
border-bottom: 1px solid var(--bs-border-color);
transition: all 0.2s ease;
}
.notification-item:hover {
background-color: var(--bs-light);
}
.notification-item.unread {
background-color: rgba(0, 123, 255, 0.05);
border-left: 3px solid var(--bs-primary);
}
.notification-item:last-child {
border-bottom: none;
}
.notification-icon {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background-color: var(--bs-light);
border-radius: 50%;
}
.notification-title {
line-height: 1.3;
margin-bottom: 0.25rem;
}
.notification-message {
line-height: 1.4;
margin-bottom: 0.25rem;
}
.notification-unread .badge {
width: 8px;
height: 8px;
padding: 0;
}
/* Tema scuro */
.dark .notifications-dropdown {
background-color: var(--bs-dark);
border-color: var(--bs-gray-700);
}
.dark .notification-item:hover {
background-color: var(--bs-gray-800);
}
.dark .notification-item.unread {
background-color: rgba(0, 123, 255, 0.1);
}
.dark .notification-icon {
background-color: var(--bs-gray-700);
}
/* Responsive */
@media (max-width: 768px) {
.notifications-dropdown {
width: 300px;
}
}
</style>
@endpush
{{-- JavaScript per notifiche --}}
@push('scripts')
<script>
document.addEventListener('DOMContentLoaded', function() {
// Segna tutte le notifiche come lette
const markAllReadBtn = document.getElementById('markAllRead');
if (markAllReadBtn) {
markAllReadBtn.addEventListener('click', function() {
fetch('#', {
method: 'POST',
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
'Content-Type': 'application/json',
},
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Rimuovi badge e classi unread
document.querySelector('.badge.bg-danger')?.remove();
document.querySelectorAll('.notification-item.unread').forEach(item => {
item.classList.remove('unread');
item.querySelector('.notification-unread')?.remove();
});
this.remove();
}
})
.catch(error => console.error('Errore:', error));
});
}
// Segna singola notifica come letta al click
document.querySelectorAll('.notification-item').forEach(item => {
item.addEventListener('click', function() {
const notificationId = this.dataset.notificationId;
if (this.classList.contains('unread')) {
fetch(`#`, {
method: 'POST',
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
'Content-Type': 'application/json',
},
body: JSON.stringify({ id: notificationId })
})
.then(response => response.json())
.then(data => {
if (data.success) {
this.classList.remove('unread');
this.querySelector('.notification-unread')?.remove();
// Aggiorna contatore
const badge = document.querySelector('.badge.bg-danger');
if (badge) {
const count = parseInt(badge.textContent) - 1;
if (count <= 0) {
badge.remove();
} else {
badge.textContent = count > 99 ? '99+' : count;
}
}
}
})
.catch(error => console.error('Errore:', error));
}
});
});
@if($realTime)
// Aggiornamenti real-time (WebSocket o polling)
function checkNewNotifications() {
fetch('#', {
headers: {
'X-Requested-With': 'XMLHttpRequest',
}
})
.then(response => response.json())
.then(data => {
if (data.hasNew) {
// Aggiorna badge o ricarica dropdown
location.reload(); // Temporaneo - implementare aggiornamento dinamico
}
})
.catch(error => console.error('Errore check notifiche:', error));
}
// Check ogni 30 secondi
setInterval(checkNewNotifications, 30000);
@endif
});
</script>
@endpush