- Real-time updates (Laravel Reverb): live operator queue, live ticket chat/detail updates for operator and client, periodic fallback refresh with a visible countdown as a backstop for dropped websocket connections. - SLA automation rules (Admin > Automatyzacja SLA): act on a ticket after N minutes of customer silence (change priority/status/team/assignee), evaluated every 15 minutes, reusing TicketService's own setters so automated changes get the same history/notification/broadcast a manual change would. - New notification: every operator on a matching team gets notified when a new ticket lands in one of their subcategories. - BookStack knowledge-base sidebar now also shown on the client's own ticket view (previously operator-only); suggestions everywhere now load in after first paint instead of blocking it. - Client ticket view: shows assigned operator + team; page widened to match the operator's. - Notification bell shows unread only; read notifications disappear instead of just dimming. - Stats dashboard: sectioned layout, new breakdowns (by subcategory, CSAT by team/operator, top clients, client x subcategory cross-tab). - Mobile: nav dropdowns (theme/notifications/profile) now expand full width instead of overflowing off-screen below 640px. - Fixed two bugs that silently disabled all real-time updates (missing CSRF header on Echo's private-channel auth; a script-load-order race that could miss the livewire:init event) and the mariadb healthcheck (world-writable credentials file on this stack's NFS mount). - Assorted test-suite fixes (roles virtual attribute needs the roles table seeded; a few missing seeds/wrong assertions found along the way). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
38 lines
1.7 KiB
PHP
38 lines
1.7 KiB
PHP
<div
|
|
x-data="{
|
|
theme: localStorage.getItem('theme') || 'auto',
|
|
open: false,
|
|
icon() { return this.theme === 'light' ? 'light_mode' : this.theme === 'dark' ? 'dark_mode' : 'brightness_auto' },
|
|
apply(t) {
|
|
this.theme = t
|
|
localStorage.setItem('theme', t)
|
|
let systemDark = window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
document.documentElement.setAttribute('data-theme', t === 'auto' ? (systemDark ? 'dark' : 'light') : t)
|
|
this.open = false
|
|
}
|
|
}"
|
|
@click.outside="open = false"
|
|
class="nav-dropdown-wrap"
|
|
style="position:relative;display:inline-block"
|
|
>
|
|
<button type="button" class="btn btn-secondary btn-icon" @click="open = !open">
|
|
<span class="material-symbols-outlined" x-text="icon()"></span>
|
|
</button>
|
|
<div
|
|
x-show="open"
|
|
x-cloak
|
|
class="nav-dropdown"
|
|
style="position:absolute;top:100%;right:0;margin-top:6px;background:var(--color-surface);border:1px solid var(--color-divider);border-radius:8px;box-shadow:var(--shadow-md);min-width:150px;overflow:hidden;z-index:20"
|
|
>
|
|
<button type="button" class="theme-toggle-option" @click="apply('light')">
|
|
<span class="material-symbols-outlined" style="font-size:16px">light_mode</span>Jasny
|
|
</button>
|
|
<button type="button" class="theme-toggle-option" @click="apply('dark')">
|
|
<span class="material-symbols-outlined" style="font-size:16px">dark_mode</span>Ciemny
|
|
</button>
|
|
<button type="button" class="theme-toggle-option" @click="apply('auto')">
|
|
<span class="material-symbols-outlined" style="font-size:16px">brightness_auto</span>Auto
|
|
</button>
|
|
</div>
|
|
</div>
|