import "./app.scss"
import { initEntrepriseSearch } from "./modules/entreprise-search.js"
// Membre / Super Admin : mutuellement exclusif
document.addEventListener('DOMContentLoaded', () => {
const memberCheckbox = document.querySelector('input[value="siteconseil_member"]');
const adminCheckbox = document.querySelector('input[value="siteconseil_admin"]');
const otherGroupCheckboxes = () =>
[...document.querySelectorAll('input[name="groups[]"]')].filter(cb => cb !== memberCheckbox);
if (memberCheckbox && adminCheckbox) {
memberCheckbox.addEventListener('change', () => {
/* istanbul ignore next */ if (memberCheckbox.checked) otherGroupCheckboxes().forEach(cb => { cb.checked = false; });
});
adminCheckbox.addEventListener('change', () => {
if (!adminCheckbox.checked) return;
memberCheckbox.checked = false;
otherGroupCheckboxes().forEach(cb => { cb.checked = true; });
});
}
// Stats period selector
const periodSelect = document.getElementById('stats-period-select');
const customRange = document.getElementById('stats-custom-range');
if (periodSelect && customRange) {
periodSelect.addEventListener('change', () => {
customRange.classList.toggle('hidden', periodSelect.value !== 'custom');
});
}
// data-confirm — modal glassmorphism custom
const confirmModal = document.createElement('div');
confirmModal.id = 'confirm-modal';
confirmModal.className = 'hidden fixed inset-0 z-[100] flex items-center justify-center';
confirmModal.innerHTML = `
`;
document.body.appendChild(confirmModal);
let pendingForm = null;
const confirmMessage = document.getElementById('confirm-message');
const confirmCancel = document.getElementById('confirm-cancel');
const confirmOk = document.getElementById('confirm-ok');
const confirmOverlay = document.getElementById('confirm-overlay');
const closeConfirm = () => { confirmModal.classList.add('hidden'); pendingForm = null; };
confirmCancel.addEventListener('click', closeConfirm);
confirmOverlay.addEventListener('click', closeConfirm);
document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && pendingForm) closeConfirm(); });
confirmOk.addEventListener('click', () => {
if (pendingForm) {
confirmModal.classList.add('hidden');
pendingForm.removeAttribute('data-confirm');
pendingForm.requestSubmit();
}
});
document.querySelectorAll('form[data-confirm]').forEach(form => {
form.addEventListener('submit', (e) => {
if (form.dataset.confirm) {
e.preventDefault();
pendingForm = form;
confirmMessage.textContent = form.dataset.confirm;
confirmModal.classList.remove('hidden');
}
});
});
// Sidebar dropdown toggle
document.querySelectorAll('.sidebar-dropdown-btn').forEach(btn => {
btn.addEventListener('click', () => {
const menu = btn.nextElementSibling;
const arrow = btn.querySelector('.sidebar-dropdown-arrow');
menu?.classList.toggle('hidden');
arrow?.classList.toggle('rotate-180');
});
});
// Mobile sidebar toggle
const sidebarToggle = document.getElementById('admin-sidebar-toggle');
const sidebar = document.getElementById('admin-sidebar');
const overlay = document.getElementById('admin-overlay');
if (sidebarToggle && sidebar && overlay) {
sidebarToggle.addEventListener('click', () => {
sidebar.classList.toggle('open');
});
overlay.addEventListener('click', () => {
sidebar.classList.remove('open');
});
}
// Mobile menu toggle (public)
const mobileMenuBtn = document.getElementById('mobile-menu-btn');
const mobileMenu = document.getElementById('mobile-menu');
const menuIconOpen = document.getElementById('menu-icon-open');
const menuIconClose = document.getElementById('menu-icon-close');
if (mobileMenuBtn && mobileMenu) {
mobileMenuBtn.addEventListener('click', () => {
mobileMenu.classList.toggle('hidden');
menuIconOpen?.classList.toggle('hidden');
menuIconClose?.classList.toggle('hidden');
});
}
// Cookie banner
const cookieBanner = document.getElementById('cookie-banner');
const cookieAccept = document.getElementById('cookie-accept');
const cookieRefuse = document.getElementById('cookie-refuse');
if (cookieBanner && !localStorage.getItem('cookie_consent')) {
cookieBanner.classList.remove('hidden');
}
cookieAccept?.addEventListener('click', () => {
localStorage.setItem('cookie_consent', 'accepted');
cookieBanner?.classList.add('hidden');
});
cookieRefuse?.addEventListener('click', () => {
localStorage.setItem('cookie_consent', 'refused');
cookieBanner?.classList.add('hidden');
});
// Search (customers & revendeurs)
const renderHit = (h, linkPrefix) => {
const id = h.customerId || h.id;
const name = h.fqdn || h.name || h.fullName || h.raisonSociale || (h.firstName + ' ' + h.lastName);
const sub = h.customerName ? `${h.customerName}` : (h.email ? `${h.email}` : '');
return `
${name}
${sub}
${h.codeRevendeur ? `${h.codeRevendeur}` : ''}
`;
};
const performSearch = async (searchUrl, linkPrefix, results, q) => {
const resp = await fetch(`${searchUrl}?q=${encodeURIComponent(q)}`);
const hits = await resp.json();
results.innerHTML = hits.length === 0
? 'Aucun resultat.
'
: hits.map(h => renderHit(h, linkPrefix)).join('');
results.classList.remove('hidden');
};
const setupSearch = (inputId, resultsId, searchUrl, linkPrefix) => {
const input = document.getElementById(inputId);
const results = document.getElementById(resultsId);
if (!input || !results) return;
let debounce;
input.addEventListener('input', () => {
clearTimeout(debounce);
const q = input.value.trim();
if (q.length < 2) {
results.classList.add('hidden');
results.innerHTML = '';
return;
}
debounce = setTimeout(() => performSearch(searchUrl, linkPrefix, results, q), 300);
});
document.addEventListener('click', (e) => {
/* istanbul ignore next */ if (!results.contains(e.target) && e.target !== input) {
results.classList.add('hidden');
}
});
};
setupSearch('search-customers', 'search-results', '/admin/clients/search', '/admin/clients/');
setupSearch('search-revendeurs', 'search-results-revendeurs', '/admin/revendeurs/search', '/admin/revendeurs/');
setupSearch('search-ndd', 'search-ndd-results', '/admin/services/ndd/search', '/admin/clients/');
setupSearch('search-websites', 'search-websites-results', '/admin/services/esyweb/search', '/admin/clients/');
// Tarif tabs
const tarifTabs = document.getElementById('tarif-tabs');
if (tarifTabs) {
const active = 'px-6 py-3 font-bold uppercase text-sm tracking-wider glass-dark text-white border border-white/20 border-b-0 cursor-pointer';
const inactive = 'px-6 py-3 font-bold uppercase text-sm tracking-wider glass text-gray-700 border border-gray-200 border-b-0 cursor-pointer hover:bg-white/80 transition-all';
tarifTabs.querySelectorAll('[data-tab]').forEach(btn => {
btn.addEventListener('click', () => {
const tab = btn.dataset.tab;
tarifTabs.querySelectorAll('[data-tab]').forEach(b => {
b.className = b.dataset.tab === tab ? active : inactive;
});
document.querySelectorAll('[id^="content-"]').forEach(el => {
/* istanbul ignore next */ if (el.closest('#tarif-tabs')) return;
el.classList.toggle('hidden', el.id !== 'content-' + tab);
});
});
});
}
// Recherche entreprise (page creation client)
initEntrepriseSearch();
// Recherche globale (navbar admin)
const globalInput = document.getElementById('global-search');
const globalResults = document.getElementById('global-search-results');
if (globalInput && globalResults) {
const typeIcons = {
client: '👤',
ndd: '🌐',
site: '💻',
contact: '👥',
revendeur: '🏢',
};
const typeLabels = {
client: 'Client',
ndd: 'NDD',
site: 'Site',
contact: 'Contact',
revendeur: 'Revendeur',
};
let globalDebounce;
globalInput.addEventListener('input', () => {
clearTimeout(globalDebounce);
const q = globalInput.value.trim();
if (q.length < 2) {
globalResults.classList.add('hidden');
globalResults.innerHTML = '';
return;
}
globalDebounce = setTimeout(async () => {
const resp = await fetch('/admin/global-search?q=' + encodeURIComponent(q));
const hits = await resp.json();
if (hits.length === 0) {
globalResults.innerHTML = 'Aucun resultat.
';
} else {
globalResults.innerHTML = hits.map(h =>
`
${typeIcons[h.type] || ''}
${h.label}
${h.sub ? `${h.sub}` : ''}
${typeLabels[h.type] || h.type}
`
).join('');
}
globalResults.classList.remove('hidden');
}, 250);
});
document.addEventListener('click', (e) => {
/* istanbul ignore next */ if (!globalResults.contains(e.target) && e.target !== globalInput) {
globalResults.classList.add('hidden');
}
});
globalInput.addEventListener('keydown', (e) => {
if (e.key === 'Escape') globalResults.classList.add('hidden');
});
}
// ──────── Tab search devis / avis ────────
initTabSearch('search-devis', 'search-devis-results');
initTabSearch('search-adverts', 'search-adverts-results');
// ──────── Devis lines repeater + drag & drop ────────
initDevisLines();
// ──────── Devis process : toggle formulaire de refus ────────
const refuseBtn = document.getElementById('refuse-toggle-btn');
const refuseForm = document.getElementById('refuse-form');
if (refuseBtn && refuseForm) {
refuseBtn.addEventListener('click', () => refuseForm.classList.toggle('hidden'));
}
});
function initTabSearch(inputId, resultsId) {
const input = document.getElementById(inputId);
const results = document.getElementById(resultsId);
if (!input || !results) return;
const url = input.dataset.url;
let timeout = null;
const stateLabels = {
created: 'Cree', send: 'Envoye', accepted: 'Accepte', refused: 'Refuse', cancel: 'Annule'
};
const stateColors = {
created: 'bg-yellow-100 text-yellow-800', send: 'bg-blue-500/20 text-blue-700',
accepted: 'bg-green-500/20 text-green-700', refused: 'bg-red-500/20 text-red-700',
cancel: 'bg-gray-100 text-gray-600'
};
input.addEventListener('input', () => {
clearTimeout(timeout);
const q = input.value.trim();
if (q.length < 2) { results.classList.add('hidden'); return; }
timeout = setTimeout(async () => {
const resp = await fetch(url + '?q=' + encodeURIComponent(q));
const hits = await resp.json();
if (hits.length === 0) {
results.innerHTML = 'Aucun resultat.
';
} else {
results.innerHTML = hits.map(h =>
`
${h.numOrder}
${h.customerName || ''}
${h.totalTtc || '0.00'} €
${stateLabels[h.state] || h.state}
`
).join('');
}
results.classList.remove('hidden');
}, 250);
});
document.addEventListener('click', (e) => {
if (!results.contains(e.target) && e.target !== input) results.classList.add('hidden');
});
input.addEventListener('keydown', (e) => {
if (e.key === 'Escape') results.classList.add('hidden');
});
}
function initDevisLines() {
const container = document.getElementById('lines-container');
const addBtn = document.getElementById('add-line-btn');
const tplEl = document.getElementById('line-template');
const totalEl = document.getElementById('total-ht');
if (!container || !addBtn || !tplEl || !totalEl) return;
const template = tplEl.innerHTML;
let counter = 0;
function renumber() {
container.querySelectorAll('.line-row').forEach((row, idx) => {
row.querySelector('.line-pos').textContent = '#' + (idx + 1);
row.querySelector('.line-pos-input').value = idx;
});
}
function recalc() {
let total = 0;
container.querySelectorAll('.line-price').forEach(input => {
const v = parseFloat(input.value);
if (!isNaN(v)) total += v;
});
totalEl.textContent = total.toFixed(2) + ' EUR';
}
function addLine() {
const html = template.replaceAll('__INDEX__', counter);
const wrapper = document.createElement('div');
wrapper.innerHTML = html.trim();
const node = wrapper.firstChild;
container.appendChild(node);
counter++;
renumber();
recalc();
}
container.addEventListener('click', e => {
if (e.target.classList.contains('remove-line-btn')) {
e.target.closest('.line-row').remove();
renumber();
recalc();
}
});
container.addEventListener('input', e => {
if (e.target.classList.contains('line-price')) recalc();
});
addBtn.addEventListener('click', () => addLine());
// Boutons prestations rapides : ajoute une ligne pre-remplie
document.querySelectorAll('.quick-price-btn').forEach(btn => {
btn.addEventListener('click', () => {
addLine();
const lastRow = container.querySelector('.line-row:last-child');
if (!lastRow) return;
lastRow.querySelector('input[name$="[title]"]').value = btn.dataset.title || '';
lastRow.querySelector('textarea[name$="[description]"]').value = btn.dataset.description || '';
const priceInput = lastRow.querySelector('.line-price');
priceInput.value = btn.dataset.price || '0.00';
recalc();
});
});
// Drag & drop reordering
let draggedRow = null;
container.addEventListener('dragstart', e => {
const row = e.target.closest('.line-row');
if (!row) return;
draggedRow = row;
row.classList.add('dragging');
e.dataTransfer.effectAllowed = 'move';
});
container.addEventListener('dragend', e => {
const row = e.target.closest('.line-row');
if (row) row.classList.remove('dragging');
container.querySelectorAll('.line-row').forEach(r => r.classList.remove('drag-over'));
draggedRow = null;
renumber();
});
container.addEventListener('dragover', e => {
e.preventDefault();
const target = e.target.closest('.line-row');
if (!target || target === draggedRow) return;
container.querySelectorAll('.line-row').forEach(r => r.classList.remove('drag-over'));
target.classList.add('drag-over');
});
container.addEventListener('drop', e => {
e.preventDefault();
const target = e.target.closest('.line-row');
if (!target || !draggedRow || target === draggedRow) return;
const rows = Array.from(container.querySelectorAll('.line-row'));
const draggedIdx = rows.indexOf(draggedRow);
const targetIdx = rows.indexOf(target);
if (draggedIdx < targetIdx) {
target.after(draggedRow);
} else {
target.before(draggedRow);
}
target.classList.remove('drag-over');
renumber();
});
// Prefill en mode edition
const initial = container.dataset.initialLines;
if (initial) {
try {
const arr = JSON.parse(initial);
arr.sort((a, b) => (a.pos || 0) - (b.pos || 0));
arr.forEach(l => {
addLine();
const row = container.querySelector('.line-row:last-child');
if (!row) return;
row.querySelector('input[name$="[title]"]').value = l.title || '';
row.querySelector('textarea[name$="[description]"]').value = l.description || '';
row.querySelector('.line-price').value = l.priceHt || '0.00';
});
recalc();
} catch (e) { /* ignore */ }
}
}