Snapshot of main at 9f39a6e1e (2026-11-02)

This commit is contained in:
Ronald Huynen committed 2026-11-02 10:41:47 +01:00
commit 6ccf1792d7
2580 files changed
+1040202

No files matched your search

+159
View File
@@ -0,0 +1,159 @@
// SNAPSHOT
// Profile switch banner
// The meta tag approach fails because Livewire navigate does a full document replace,
// destroying all observers/listeners. Instead we use localStorage as the handoff:
// 1. Server renders meta[name="profile-switch-data"] with JSON
// 2. A tiny IIFE in <head> (non-deferred, runs immediately) reads the meta,
// writes to localStorage, and shows the banner.
// This file just exports the show function for the head script to call.
window.__showProfileSwitchBanner = function(data) {
const banner = document.getElementById('profile-switch-banner');
if (!banner) return;
const img = document.getElementById('profile-switch-banner-photo');
if (img) img.style.display = 'none';
const roleEl = document.getElementById('profile-switch-banner-role');
if (roleEl) { roleEl.style.display = 'none'; roleEl.textContent = ''; }
if (data.photo && img) { img.src = data.photo; img.alt = data.name || ''; img.style.display = 'block'; }
const nameEl = document.getElementById('profile-switch-banner-name');
if (nameEl) nameEl.textContent = data.name || '';
if (roleEl && data.roleLabel) { roleEl.textContent = data.roleLabel; roleEl.style.display = 'block'; }
banner.style.opacity = '1';
banner.style.transition = '';
banner.style.display = 'flex';
banner.addEventListener('click', () => { banner.style.display = 'none'; }, { once: true });
setTimeout(() => {
banner.style.transition = 'opacity 0.4s';
banner.style.opacity = '0';
setTimeout(() => { banner.style.display = 'none'; banner.style.opacity = ''; banner.style.transition = ''; }, 400);
}, 3000);
};
import './bootstrap';
import { createPopper } from "@popperjs/core";
import focus from "@alpinejs/focus";
import tagifyMin from "@yaireo/tagify";
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
import { Chart, registerables } from 'chart.js';
import './presence-tracker.js';
import './return-ratio-chart.js';
import './account-balances-chart.js';
import './stats-charts.js';
// Cytoscape wordt lazy geladen binnen deze module (zie stats-network.js).
import './stats-network.js';
import './report-balance-distribution-chart.js';
Alpine.plugin(focus);
// Register Chart.js globally
Chart.register(...registerables);
window.Chart = Chart;
window.createPopper = createPopper;
window.Tagify = tagifyMin; // Needs to be loaded after Alpine
// Lazy-load JSZip (used by backup restore chunked upload)
window.loadJSZip = () => import('jszip').then(m => m.default);
// Alpine component for the call tag picker (livewire:calls.call-skill-input)
// Registered via Alpine.data() so Alpine can resolve it when Livewire morphs in new DOM nodes.
document.addEventListener('alpine:init', () => {
Alpine.data('callTagPicker', () => ({
callTagify: null,
init() {
const input = this.$refs.callTagsInput;
const suggestions = JSON.parse(input.dataset.suggestions || '[]');
this.callTagify = new Tagify(input, {
maxTags: 1,
whitelist: suggestions,
enforceWhiteList: false,
backspace: false,
editTags: false,
addTagOn: ['enter', 'tab', 'blur'],
addTagOnBlur: true,
dropdown: {
maxItems: 10,
enabled: 2,
closeOnSelect: true,
highlightFirst: true,
},
});
// Pre-populate with initial value from server (e.g. edit modal)
const initialTags = this.$wire.get('tagsArray');
if (initialTags && initialTags !== '[]') {
this.callTagify.loadOriginalValues(initialTags);
}
this.callTagify.on('change', () => {
// Read from the actual input element — e.target can be a #text node in Tagify
const rawValue = this.$refs.callTagsInput.value;
const tags = JSON.parse(rawValue || '[]');
if (tags.length === 0) {
this.$wire.set('tagsArray', '[]', false);
this.$wire.call('notifyTagCleared');
return;
}
const tag = tags[0];
if (tag.tag_id) {
// Known tag — sync and notify parent
this.$wire.set('tagsArray', rawValue, false);
this.$wire.call('notifyTagSelected', tag.tag_id);
} else {
// Unknown tag — open creation modal (no set() to avoid re-render race)
this.$wire.call('openNewTagModal', tag.value);
}
});
// Server asks us to remove the pending unconfirmed tag (cancel modal)
window.addEventListener('removeLastCallTag', () => {
if (this.callTagify && this.callTagify.value.length > 0) {
this.callTagify.removeTag(
this.callTagify.value[this.callTagify.value.length - 1].value
);
}
});
// Server pushes a new tagsArray after createTag — reload Tagify with colored badge
Livewire.on('callTagifyReload', (data) => {
this.callTagify.loadOriginalValues(data.tagsArray);
});
}
}));
});
window.Pusher = Pusher;
// Get the current locale from the URL (e.g., /en/, /nl/, etc.)
const getLocalePrefix = () => {
const pathParts = window.location.pathname.split('/').filter(Boolean);
const locales = ['en', 'nl', 'de', 'es', 'fr'];
if (pathParts.length > 0 && locales.includes(pathParts[0])) {
return `/${pathParts[0]}`;
}
return '/en'; // Default to English if no locale found
};
window.Echo = new Echo({
broadcaster: 'reverb',
key: import.meta.env.VITE_REVERB_APP_KEY,
wsHost: import.meta.env.VITE_REVERB_HOST,
wsPort: import.meta.env.VITE_REVERB_PORT ?? 80,
wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
wsPath: import.meta.env.VITE_REVERB_PATH ?? "/",
forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? "https") === "https",
enabledTransports: ['ws', 'wss'],
authEndpoint: `${getLocalePrefix()}/broadcasting/auth`,
auth: {
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content')
}
}
});