```
✨ feat(Security/Listener): Utilise APP_ENV pour le mode dev. ✨ feat(assets/admin.js): Ajoute le composant PlaningLogestics. ✨ feat(assets/libs): Crée composant PlaningLogestics pour calendrier. ➕ feat(package.json): Ajoute les dépendances FullCalendar. ➕ feat(templates/base.twig): Ajoute lien vers le planing de réservation. ➕ feat(src/Controller): Crée controller Reservation pour le planning. ➕ feat(templates): Crée template pour le planning de réservation. ```
This commit is contained in:
@@ -9,6 +9,7 @@ import { initTomSelect } from "./libs/initTomSelect.js";
|
||||
import { SearchProduct, SearchOptions } from "./libs/SearchProduct.js";
|
||||
import { SearchProductDevis, SearchOptionsDevis } from "./libs/SearchProductDevis.js";
|
||||
import { SearchProductFormule, SearchOptionsFormule } from "./libs/SearchProductFormule.js";
|
||||
import PlaningLogestics from "./libs/PlaningLogestics.js";
|
||||
|
||||
// --- CONFIGURATION SENTRY ---
|
||||
Sentry.init({
|
||||
@@ -30,6 +31,7 @@ const registerCustomElements = () => {
|
||||
{ name: 'search-product', class: SearchProduct, extends: 'button' },
|
||||
{ name: 'search-productformule', class: SearchProductFormule, extends: 'button' },
|
||||
{ name: 'search-optionsformule', class: SearchOptionsFormule, extends: 'button' },
|
||||
{ name: 'planing-logestics', class: PlaningLogestics },
|
||||
{ name: 'search-options', class: SearchOptions, extends: 'button' },
|
||||
{ name: 'search-productdevis', class: SearchProductDevis, extends: 'button' },
|
||||
{ name: 'search-optionsdevis', class: SearchOptionsDevis, extends: 'button' },
|
||||
@@ -38,7 +40,12 @@ const registerCustomElements = () => {
|
||||
|
||||
elements.forEach(el => {
|
||||
if (!customElements.get(el.name)) {
|
||||
customElements.define(el.name, el.class, { extends: el.extends });
|
||||
if(el.extends != undefined) {
|
||||
customElements.define(el.name, el.class, { extends: el.extends });
|
||||
} else {
|
||||
customElements.define(el.name, el.class);
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
319
assets/libs/PlaningLogestics.js
Normal file
319
assets/libs/PlaningLogestics.js
Normal file
@@ -0,0 +1,319 @@
|
||||
import { Calendar } from '@fullcalendar/core';
|
||||
import dayGridPlugin from '@fullcalendar/daygrid';
|
||||
import timeGridPlugin from '@fullcalendar/timegrid';
|
||||
import interactionPlugin from '@fullcalendar/interaction';
|
||||
import frLocale from '@fullcalendar/core/locales/fr';
|
||||
|
||||
export default class PlaningLogestics extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.calendar = null;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.render();
|
||||
this.initCalendar();
|
||||
}
|
||||
|
||||
render() {
|
||||
this.innerHTML = `
|
||||
<div class="p-4 md:p-8 bg-gray-50 dark:bg-gray-950 min-h-screen transition-colors duration-300 font-sans">
|
||||
<div class="mx-auto space-y-6">
|
||||
|
||||
<!-- En-tête Statistiques -->
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<div class="bg-white dark:bg-gray-900 p-5 rounded-2xl shadow-sm border border-gray-100 dark:border-gray-800 flex items-center gap-4">
|
||||
<div class="p-3 bg-indigo-100 dark:bg-indigo-900/30 rounded-xl text-indigo-600">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 5l7 7-7 7M5 5l7 7-7 7"></path></svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-[10px] font-bold text-gray-400 uppercase tracking-widest">Départs Jour</p>
|
||||
<p class="text-2xl font-black text-gray-900 dark:text-white" id="stat-departs">0</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white dark:bg-gray-900 p-5 rounded-2xl shadow-sm border border-gray-100 dark:border-gray-800 flex items-center gap-4">
|
||||
<div class="p-3 bg-emerald-100 dark:bg-emerald-900/30 rounded-xl text-emerald-600">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 19l-7-7 7-7M19 19l-7-7 7-7"></path></svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-[10px] font-bold text-gray-400 uppercase tracking-widest">Retours Jour</p>
|
||||
<p class="text-2xl font-black text-gray-900 dark:text-white" id="stat-retours">0</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Calendrier Principal -->
|
||||
<div class="bg-white dark:bg-gray-900 p-2 md:p-6 rounded-3xl shadow-sm border border-gray-100 dark:border-gray-800">
|
||||
<div id="calendar-root"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modale de Détails -->
|
||||
<div id="calendar-modal" class="fixed inset-0 z-[100] hidden items-center justify-center p-4">
|
||||
<div class="absolute inset-0 bg-gray-900/60 backdrop-blur-sm modal-overlay"></div>
|
||||
<div class="relative bg-white dark:bg-gray-900 rounded-3xl shadow-2xl w-full max-w-lg overflow-hidden border border-gray-100 dark:border-gray-800 animate-in fade-in zoom-in duration-200">
|
||||
<div class="p-6 md:p-8">
|
||||
<div class="flex justify-between items-start mb-4">
|
||||
<span id="modal-contract-number" class="px-3 py-1 bg-gray-100 dark:bg-gray-800 text-gray-500 dark:text-gray-400 rounded-lg text-[10px] font-black uppercase tracking-tighter border border-gray-200 dark:border-gray-700"></span>
|
||||
<button class="close-modal text-gray-400 hover:text-gray-600 dark:hover:text-white transition-colors">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Badges Statut -->
|
||||
<div class="flex flex-wrap gap-2 mb-6" id="modal-status-container"></div>
|
||||
|
||||
<div class="mb-6">
|
||||
<h2 id="modal-title" class="text-2xl font-extrabold text-gray-900 dark:text-white leading-tight mb-1"></h2>
|
||||
<p id="modal-client" class="text-indigo-600 dark:text-indigo-400 font-bold text-lg"></p>
|
||||
</div>
|
||||
|
||||
<!-- Informations de contact -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
|
||||
<a id="link-phone" href="#" class="flex items-center gap-3 p-3 bg-gray-50 dark:bg-gray-800 rounded-xl hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors group">
|
||||
<div class="text-indigo-500 group-hover:scale-110 transition-transform"><svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"></path></svg></div>
|
||||
<span id="modal-phone" class="text-sm font-medium text-gray-700 dark:text-gray-300"></span>
|
||||
</a>
|
||||
<a id="link-email" href="#" class="flex items-center gap-3 p-3 bg-gray-50 dark:bg-gray-800 rounded-xl hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors group">
|
||||
<div class="text-indigo-500 group-hover:scale-110 transition-transform"><svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path></svg></div>
|
||||
<span id="modal-email" class="text-sm font-medium text-gray-700 dark:text-gray-300 truncate"></span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Adresse -->
|
||||
<div class="mb-6 p-4 bg-gray-50 dark:bg-gray-800 rounded-2xl border border-gray-100 dark:border-gray-700">
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="text-emerald-500 mt-0.5"><svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"></path></svg></div>
|
||||
<div>
|
||||
<span class="block text-[10px] font-bold text-gray-400 uppercase tracking-widest">Lieu de l'événement</span>
|
||||
<span id="modal-adresse" class="text-sm text-gray-700 dark:text-gray-300 font-semibold leading-snug"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dates -->
|
||||
<div class="grid grid-cols-2 gap-6 py-4 border-t border-gray-100 dark:border-gray-800">
|
||||
<div>
|
||||
<span class="block text-[10px] font-bold text-gray-400 uppercase tracking-widest mb-1">Départ / Enlèvement</span>
|
||||
<span id="modal-start" class="text-sm text-gray-900 dark:text-white font-bold"></span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="block text-[10px] font-bold text-gray-400 uppercase tracking-widest mb-1">Retour prévu</span>
|
||||
<span id="modal-end" class="text-sm text-gray-900 dark:text-white font-bold"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="p-6 bg-gray-50 dark:bg-gray-800/50 flex gap-3">
|
||||
<a id="modal-link-contrat" href="#" target="_blank" class="flex-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 text-gray-700 dark:text-gray-200 py-4 rounded-2xl font-bold text-sm hover:bg-gray-50 dark:hover:bg-gray-700 transition-all flex items-center justify-center gap-2 shadow-sm">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path></svg>
|
||||
Voir Contrat
|
||||
</a>
|
||||
<button class="flex-1 bg-indigo-600 text-white py-4 rounded-2xl font-bold text-sm hover:bg-indigo-700 shadow-lg shadow-indigo-500/20 transition-all flex items-center justify-center gap-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"></path></svg>
|
||||
Éditer
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
:host {
|
||||
--fc-border-color: #f1f5f9;
|
||||
--fc-button-bg-color: #ffffff;
|
||||
--fc-button-border-color: #e2e8f0;
|
||||
--fc-button-text-color: #64748b;
|
||||
--fc-today-bg-color: rgba(79, 70, 229, 0.05);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:host {
|
||||
--fc-border-color: #1f2937;
|
||||
--fc-button-bg-color: #111827;
|
||||
--fc-button-border-color: #374151;
|
||||
--fc-button-text-color: #9ca3af;
|
||||
--fc-today-bg-color: rgba(99, 102, 241, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
#calendar-root { min-height: 750px; }
|
||||
|
||||
.fc-toolbar-title { font-weight: 900 !important; color: inherit; font-size: 1.25rem !important; }
|
||||
.fc-col-header-cell { padding: 12px 0 !important; text-transform: uppercase; font-size: 0.7rem; letter-spacing: 0.1em; color: #94a3b8; }
|
||||
|
||||
.fc .fc-button-primary {
|
||||
background: var(--fc-button-bg-color) !important;
|
||||
color: var(--fc-button-text-color) !important;
|
||||
border: 1px solid var(--fc-button-border-color) !important;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.05) !important;
|
||||
border-radius: 12px !important;
|
||||
font-weight: 700 !important;
|
||||
text-transform: capitalize !important;
|
||||
padding: 8px 16px !important;
|
||||
}
|
||||
|
||||
.fc .fc-button-active {
|
||||
background: #4f46e5 !important;
|
||||
color: white !important;
|
||||
border-color: #4f46e5 !important;
|
||||
}
|
||||
|
||||
.fc-event {
|
||||
border: none !important;
|
||||
border-radius: 10px !important;
|
||||
padding: 4px !important;
|
||||
margin: 1px 2px !important;
|
||||
transition: all 0.2s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
.fc-event:hover { transform: translateY(-1px); filter: brightness(1.1); box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1); }
|
||||
|
||||
.fc-event-main-frame { padding: 4px; }
|
||||
.status-indicators { display: flex; gap: 3px; margin-top: 4px; }
|
||||
.status-dot { width: 14px; height: 14px; border-radius: 4px; display: flex; align-items: center; justify-content: center; }
|
||||
|
||||
.dark .fc-theme-standard td, .dark .fc-theme-standard th { border-color: #1f2937 !important; }
|
||||
</style>
|
||||
`;
|
||||
|
||||
this.querySelector('.modal-overlay').addEventListener('click', () => this.hideModal());
|
||||
this.querySelector('.close-modal').addEventListener('click', () => this.hideModal());
|
||||
}
|
||||
|
||||
initCalendar() {
|
||||
const calendarEl = this.querySelector('#calendar-root');
|
||||
|
||||
// Données exemples intégrant contractNumber
|
||||
/*const events = [
|
||||
{
|
||||
title: 'Retrait Pack Mariage',
|
||||
start: '2026-01-30T14:00:00',
|
||||
end: '2026-02-01T12:00:00',
|
||||
backgroundColor: '#10b981',
|
||||
extendedProps: {
|
||||
contractNumber: 'CTR-2026-001',
|
||||
client: 'M. et Mme. Lefebvre',
|
||||
clientEmail: 'lefebvre.m@gmail.com',
|
||||
clientPhone: '06 12 34 56 78',
|
||||
eventAdresse: '15 Rue de la Paix, 75002 Paris',
|
||||
linkContrat: 'https://admin.votre-erp.com/contrats/12345',
|
||||
caution: true,
|
||||
acompte: true,
|
||||
solde: true
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Livraison Podium Sonorisé',
|
||||
start: '2026-01-29T08:00:00',
|
||||
end: '2026-01-29T18:00:00',
|
||||
backgroundColor: '#4f46e5',
|
||||
extendedProps: {
|
||||
contractNumber: 'CTR-2026-042',
|
||||
client: 'Mairie de Nanterre',
|
||||
clientEmail: 'logistique@nanterre.fr',
|
||||
clientPhone: '01 47 29 50 00',
|
||||
eventAdresse: 'Place Gabriel Péri, 92000 Nanterre',
|
||||
linkContrat: 'https://admin.votre-erp.com/contrats/98765',
|
||||
caution: true,
|
||||
acompte: true,
|
||||
solde: false
|
||||
}
|
||||
}
|
||||
];*/
|
||||
const events = {};
|
||||
|
||||
this.calendar = new Calendar(calendarEl, {
|
||||
plugins: [ dayGridPlugin, timeGridPlugin, interactionPlugin ],
|
||||
initialView: 'dayGridMonth',
|
||||
locale: frLocale,
|
||||
headerToolbar: {
|
||||
left: 'prev,next today',
|
||||
center: 'title',
|
||||
right: 'dayGridMonth,timeGridWeek'
|
||||
},
|
||||
events: events,
|
||||
eventContent: (arg) => {
|
||||
const { caution, acompte, solde } = arg.event.extendedProps;
|
||||
return {
|
||||
html: `
|
||||
<div class="fc-event-main-frame">
|
||||
<div class="text-[11px] font-bold leading-tight line-clamp-1">${arg.event.title}</div>
|
||||
<div class="status-indicators">
|
||||
<div class="status-dot" style="background:${caution ? '#f59e0b' : '#4b5563'}; opacity:${caution ? 1 : 0.3}"><svg class="w-2.5 h-2.5 text-white" fill="currentColor" viewBox="0 0 20 20"><path d="M4 4a2 2 0 00-2 2v1h16V6a2 2 0 00-2-2H4z"></path><path d="M2 9h16v5a2 2 0 01-2 2H4a2 2 0 01-2-2V9z"></path></svg></div>
|
||||
<div class="status-dot" style="background:${acompte ? '#3b82f6' : '#4b5563'}; opacity:${acompte ? 1 : 0.3}"><svg class="w-2.5 h-2.5 text-white" fill="currentColor" viewBox="0 0 20 20"><path d="M4 4a2 2 0 00-2 2v4a2 2 0 002 2V6h10a2 2 0 00-2-2H4z"></path></svg></div>
|
||||
<div class="status-dot" style="background:${solde ? '#10b981' : '#4b5563'}; opacity:${solde ? 1 : 0.3}"><svg class="w-2.5 h-2.5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7"></path></svg></div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
};
|
||||
},
|
||||
eventClick: (info) => this.showEventDetails(info.event)
|
||||
});
|
||||
|
||||
this.calendar.render();
|
||||
this.updateStats(events);
|
||||
}
|
||||
|
||||
updateStats(events) {
|
||||
const todayStr = new Date().toISOString().split('T')[0];
|
||||
const departs = events.filter(e => e.start.split('T')[0] === todayStr).length;
|
||||
const retours = events.filter(e => e.end && e.end.split('T')[0] === todayStr).length;
|
||||
|
||||
this.querySelector('#stat-departs').innerText = departs;
|
||||
this.querySelector('#stat-retours').innerText = retours;
|
||||
}
|
||||
|
||||
showEventDetails(event) {
|
||||
const modal = this.querySelector('#calendar-modal');
|
||||
const props = event.extendedProps;
|
||||
|
||||
// Mise à jour des informations contractuelles
|
||||
this.querySelector('#modal-contract-number').innerText = props.contractNumber || 'SANS NUMÉRO';
|
||||
this.querySelector('#modal-title').innerText = event.title;
|
||||
this.querySelector('#modal-client').innerText = props.client;
|
||||
this.querySelector('#modal-email').innerText = props.clientEmail || 'Non renseigné';
|
||||
this.querySelector('#modal-phone').innerText = props.clientPhone || 'Non renseigné';
|
||||
this.querySelector('#modal-adresse').innerText = props.eventAdresse || 'Adresse non spécifiée';
|
||||
|
||||
// Formatage des dates
|
||||
const dateOptions = { day: 'numeric', month: 'long', year: 'numeric', hour: '2-digit', minute: '2-digit' };
|
||||
this.querySelector('#modal-start').innerText = event.start.toLocaleString('fr-FR', dateOptions);
|
||||
this.querySelector('#modal-end').innerText = event.end ? event.end.toLocaleString('fr-FR', dateOptions) : '--';
|
||||
|
||||
// Mise à jour des liens interactifs
|
||||
this.querySelector('#link-phone').href = `tel:${props.clientPhone}`;
|
||||
this.querySelector('#link-email').href = `mailto:${props.clientEmail}`;
|
||||
this.querySelector('#modal-link-contrat').href = props.linkContrat || '#';
|
||||
|
||||
// Couleur thématique
|
||||
this.querySelector('#modal-contract-number').style.borderColor = event.backgroundColor;
|
||||
this.querySelector('#modal-contract-number').style.color = event.backgroundColor;
|
||||
|
||||
// Génération dynamique des badges de statut
|
||||
const statusContainer = this.querySelector('#modal-status-container');
|
||||
const statusList = [
|
||||
{ label: 'Caution', val: props.caution, color: 'orange', icon: 'M4 4a2 2 0 00-2 2v1h16V6a2 2 0 00-2-2H4z' },
|
||||
{ label: 'Acompte', val: props.acompte, color: 'blue', icon: 'M4 4a2 2 0 002 2V6h10a2 2 0 00-2-2H4z' },
|
||||
{ label: 'Solde', val: props.solde, color: 'emerald', icon: 'M5 13l4 4L19 7' }
|
||||
];
|
||||
|
||||
statusContainer.innerHTML = statusList.map(s => `
|
||||
<span class="px-2.5 py-1 rounded-lg text-[10px] font-bold uppercase tracking-wider flex items-center gap-1.5 ${s.val ? `bg-${s.color}-50 text-${s.color}-700 dark:bg-${s.color}-900/20 dark:text-${s.color}-400 border border-${s.color}-100 dark:border-${s.color}-800` : 'bg-gray-50 text-gray-400 dark:bg-gray-800/50 dark:text-gray-600 border border-transparent'}">
|
||||
<div class="w-1.5 h-1.5 rounded-full ${s.val ? `bg-${s.color}-500` : 'bg-gray-400'}"></div>
|
||||
${s.label}
|
||||
</span>
|
||||
`).join('');
|
||||
|
||||
modal.classList.remove('hidden');
|
||||
modal.classList.add('flex');
|
||||
}
|
||||
|
||||
hideModal() {
|
||||
const modal = this.querySelector('#calendar-modal');
|
||||
modal.classList.add('hidden');
|
||||
modal.classList.remove('flex');
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,10 @@
|
||||
"vite": "^7.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fullcalendar/core": "^6.1.20",
|
||||
"@fullcalendar/daygrid": "^6.1.20",
|
||||
"@fullcalendar/interaction": "^6.1.20",
|
||||
"@fullcalendar/timegrid": "^6.1.20",
|
||||
"@grafikart/drop-files-element": "^1.0.9",
|
||||
"@hotwired/turbo": "^8.0.20",
|
||||
"@preact/preset-vite": "^2.10.2",
|
||||
|
||||
65
src/Controller/Dashboard/ReservationController.php
Normal file
65
src/Controller/Dashboard/ReservationController.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\Dashboard;
|
||||
|
||||
use App\Entity\AuditLog;
|
||||
use App\Logger\AppLogger;
|
||||
use App\Repository\AccountRepository;
|
||||
use App\Repository\AuditLogRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Knp\Component\Pager\PaginatorInterface;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
|
||||
class ReservationController extends AbstractController
|
||||
{
|
||||
|
||||
#[Route(path: '/crm/reservation', name: 'app_crm_reservation', methods: ['GET'])]
|
||||
public function crmReservation(AppLogger $appLogger): Response
|
||||
{
|
||||
$appLogger->record('VIEW', 'Affichage liste des reservations');
|
||||
return $this->render('dashboard/reservation.twig');
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint pour alimenter le calendrier en JSON
|
||||
*/
|
||||
#[Route(path: '/crm/reservation/data', name: 'app_crm_reservation_data', methods: ['GET'])]
|
||||
public function getReservationData(): JsonResponse
|
||||
{
|
||||
/*
|
||||
$reservations = $repository->findAll(); // Adaptez avec vos critères de dates
|
||||
|
||||
$events = [];
|
||||
foreach ($reservations as $res) {
|
||||
$events[] = [
|
||||
'title' => $res->getLabel(), // ou $res->getProduit()
|
||||
'start' => $res->getStartAt()->format(\DateTimeInterface::ATOM),
|
||||
'end' => $res->getEndAt()->format(\DateTimeInterface::ATOM),
|
||||
'backgroundColor' => $res->getStatusColor(), // Méthode personnalisée
|
||||
'extendedProps' => [
|
||||
'contractNumber' => $res->getContractNumber(), // Le champ que vous avez demandé
|
||||
'client' => $res->getClientName(),
|
||||
'clientEmail' => $res->getClientEmail(),
|
||||
'clientPhone' => $res->getClientPhone(),
|
||||
'eventAdresse' => $res->getAdresse(),
|
||||
'linkContrat' => $this->generateUrl('app_crm_reservation_data', ['id' => $res->getId()]),
|
||||
'caution' => $res->isCautionReceived(),
|
||||
'acompte' => $res->isAcomptePaid(),
|
||||
'solde' => $res->isSoldePaid(),
|
||||
]
|
||||
];
|
||||
}*/
|
||||
$events =[];
|
||||
return new JsonResponse($events);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Event\ResponseEvent;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Parameter;
|
||||
|
||||
#[AsEventListener(event: ResponseEvent::class, method: 'onResponse')]
|
||||
class RedirecListener
|
||||
@@ -14,10 +13,9 @@ class RedirecListener
|
||||
private bool $isDev;
|
||||
|
||||
public function __construct(
|
||||
#[Parameter('kernel.debug')] bool $debug
|
||||
) {
|
||||
// On considère être en dev si le mode debug de Symfony est activé
|
||||
$this->isDev = $debug;
|
||||
$this->isDev = $_ENV['APP_ENV'] == "dev";
|
||||
}
|
||||
|
||||
public function onResponse(ResponseEvent $event): void
|
||||
|
||||
@@ -41,8 +41,9 @@
|
||||
{% import _self as menu %}
|
||||
|
||||
{{ menu.nav_link(path('app_crm'), 'Dashboard', '<path d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path>', 'app_crm') }}
|
||||
{{ menu.nav_link(path('app_crm_product'), 'Produits', '<path d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>', 'app_clients') }}
|
||||
{{ menu.nav_link(path('app_crm_formules'), 'Formules', '<path d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>', 'app_clients') }}
|
||||
{{ menu.nav_link(path('app_crm_reservation'), 'Planing de réservation', '<path d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>', 'app_crm_reservation') }}
|
||||
{{ menu.nav_link(path('app_crm_product'), 'Produits', '<path d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>', 'app_crm_product') }}
|
||||
{{ menu.nav_link(path('app_crm_formules'), 'Formules', '<path d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>', 'app_crm_formules') }}
|
||||
{# {{ menu.nav_link(path('app_crm_contrats'), 'Contrat de location', '<path d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>', 'app_clients') }}#}
|
||||
{# {{ menu.nav_link(path('app_crm_facture'), 'Facture', '<path d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>', 'app_clients') }}#}
|
||||
{# {{ menu.nav_link(path('app_crm_devis'), 'Devis', '<path d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>', 'app_clients') }}#}
|
||||
|
||||
7
templates/dashboard/reservation.twig
Normal file
7
templates/dashboard/reservation.twig
Normal file
@@ -0,0 +1,7 @@
|
||||
{% extends 'dashboard/base.twig' %}
|
||||
|
||||
{% block title %}Planning des Réservations{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<planing-logestics></planing-logestics>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user