|
|
Can you improve the visual appearance of my application while ensuring its compatibility with my server-side data : <!-- Agenda.html --> <style> #agendaPanel .slide-in { animation: slideInLeft 0.3s forwards; } #agendaPanel > div { max-width: 100rem; width:100%; } #agendaPanel h2 { font-size: 1.875rem; } #agenda-grid { /* enlarge columns so event cards are wider */ min-width: 84rem; display: grid; grid-template-columns: repeat(7, minmax(12rem, 1fr)); border-top: 1px solid #374151; height: 100%; } #agenda-grid > div { height: 100%; } .day-body { position: relative; flex: 1 1 auto; min-height: 15rem; display: flex; flex-direction: column; align-items: stretch; padding: 0.25rem; /* remove useless horizontal grid lines */ background-image: none; } .agenda-event { position: relative; margin: 0.75rem 0; background-color: #60a5fa; border-radius: 0.25rem; padding: 0.75rem; padding-bottom: 1.5rem; font-size: 1rem; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); cursor: pointer; aspect-ratio: 1 / 1; display: flex; flex-direction: column; overflow: hidden; border: 2px solid rgba(0,0,0,0.4); } .agenda-event-title { font-weight: bold; margin-bottom: 0.25rem; text-align: center; } .agenda-event-desc { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; } .agenda-time { position: absolute; bottom: 0.25rem; right: 0.25rem; background-color: rgba(30, 64, 175, 0.8); color: #fff; font-size: 0.875rem; padding: 0.125rem 0.5rem; border-radius: 0.25rem; } </style> <div id="agendaPanel" class="fixed inset-0 z-50 hidden"> <div class="modal-backdrop"></div> <div class="fixed inset-y-0 left-0 bg-white shadow-xl slide-in flex flex-col"> <div class="flex items-center justify-between bg-blue-600 px-6 py-4"> <h2 class="text-white text-3xl">Agenda</h2> <button id="closeAgenda" class="text-white text-2xl"> <span class="material-icons">close</span> </button> </div> <div class="flex-1 overflow-auto"> <div class="h-full flex flex-col bg-gray-900 text-gray-100"> <div class="flex items-center justify-between px-4 py-2 bg-gray-800"> <button id="agenda-prev" class="text-white"><span class="material-icons">chevron_left</span></button> <h3 id="agenda-date" class="flex-1 text-center text-lg font-medium"></h3> <button id="agenda-next" class="text-white"><span class="material-icons">chevron_right</span></button> </div> <div id="agenda-scroll" class="relative flex-1 overflow-y-auto"> <div id="agenda-grid"></div> <button id="agenda-add" class="absolute bottom-4 right-4 w-10 h-10 rounded-full bg-blue-600 text-white flex items-center justify-center shadow-lg"><span class="material-icons">add</span></button> </div> </div> </div> </div> </div> <div id="agendaEventModal" class="fixed inset-0 z-50 hidden"> <div class="modal-backdrop"></div> <div class="fixed inset-0 flex items-center justify-center p-4"> <div class="bg-white rounded-lg shadow-xl w-full max-w-md p-6"> <h3 id="agendaModalTitle" class="text-lg font-medium mb-4">Nouvel événement</h3> <div class="space-y-4"> <div><label class="block text-sm">Titre</label><input id="agenda-event-title" type="text" class="w-full border rounded px-2 py-1"></div> <div><label class="block text-sm">Date</label><input id="agenda-event-date" type="date" class="w-full border rounded px-2 py-1"></div> <div class="grid grid-cols-2 gap-4"> <div><label class="block text-sm">Début</label><input id="agenda-event-start" type="time" class="w-full border rounded px-2 py-1"></div> <div><label class="block text-sm">Fin</label><input id="agenda-event-end" type="time" class="w-full border rounded px-2 py-1"></div> </div> <div><label class="block text-sm">Description</label><textarea id="agenda-event-desc" rows="3" class="w-full border rounded px-2 py-1"></textarea></div> <div> <label class="block text-sm mb-1">Couleur</label> <div id="agenda-color-picker" class="flex space-x-2"></div> </div> </div> <div class="mt-6 flex justify-end space-x-2"> <button id="cancelAgendaEvent" class="px-4 py-2 border rounded">Annuler</button> <button id="deleteAgendaEvent" class="px-4 py-2 bg-red-600 text-white rounded hidden">Supprimer l'événement</button> <button id="saveAgendaEvent" class="px-4 py-2 bg-blue-600 text-white rounded">Enregistrer</button> </div> </div> </div> </div> <script> let agendaWeekStart = ''; let agendaEditingId = null; const agendaColors = ['#60a5fa','#34d399','#f87171','#a78bfa','#fbbf24','#f472b6','#38bdf8']; let agendaSelectedColor = agendaColors[0]; // Attach initAgenda to the global window object so it is accessible when the // sidebar button callback tries to invoke it. This avoids "initAgenda is not // defined" errors that can happen if the function is not evaluated in the // global scope when the page is embedded inside the Apps Script iframe. window.initAgenda = function(){ agendaWeekStart = formatDateISO(getWeekStart(new Date())); buildAgendaGrid(); updateAgendaDate(); loadAgenda(); document.getElementById('agenda-prev').onclick = () => changeAgendaDate(-1); document.getElementById('agenda-next').onclick = () => changeAgendaDate(1); document.getElementById('agenda-add').onclick = () => openAgendaModal(false,{date:agendaWeekStart,start:'08:00',end:'09:00'}); document.getElementById('cancelAgendaEvent').onclick = () => document.getElementById('agendaEventModal').classList.add('hidden'); document.getElementById('saveAgendaEvent').onclick = saveAgendaEvent; document.getElementById('deleteAgendaEvent').onclick = deleteAgendaEvent; const picker=document.getElementById('agenda-color-picker'); picker.innerHTML=''; agendaColors.forEach(c=>{ const s=document.createElement('span'); s.className='w-6 h-6 rounded-full border cursor-pointer'; s.style.backgroundColor=c; s.dataset.color=c; s.addEventListener('click',()=>{agendaSelectedColor=c;updateColorPicker();}); picker.appendChild(s); }); }; function buildAgendaGrid(){ const grid=document.getElementById('agenda-grid'); grid.innerHTML=''; const dates=getWeekDates(agendaWeekStart); dates.forEach(d=>{ const col=document.createElement('div'); col.className='border-l border-r border-gray-700 flex flex-col'; const head=document.createElement('div'); head.className='sticky top-0 bg-gray-800 text-center py-1 border-b border-gray-700 border-l border-r'; const dt=new Date(d); head.textContent=dt.toLocaleDateString('fr-FR',{weekday:'long',day:'2-digit',month:'2-digit'}); col.appendChild(head); const body=document.createElement('div'); body.className='day-body'; body.dataset.date=d; col.appendChild(body); grid.appendChild(col); }); } function updateAgendaDate(){ const start=new Date(agendaWeekStart); const end=new Date(start); end.setDate(start.getDate()+6); const startStr=start.toLocaleDateString('fr-FR',{day:'2-digit',month:'2-digit'}); const endStr=end.toLocaleDateString('fr-FR',{day:'2-digit',month:'2-digit',year:'numeric'}); document.getElementById('agenda-date').textContent=`Semaine du ${startStr} au ${endStr}`; } function changeAgendaDate(delta){ const d=new Date(agendaWeekStart); d.setDate(d.getDate()+delta*7); agendaWeekStart=formatDateISO(d); buildAgendaGrid(); updateAgendaDate(); loadAgenda(); } function loadAgenda(){ const start=agendaWeekStart; const end=formatDateISO(addDays(start,6)); google.script.run.withSuccessHandler(renderAgenda).getAgendaRange(start,end); } function renderAgenda(list){ if(!Array.isArray(list)) list = []; const grid=document.getElementById('agenda-grid'); if(!grid) return; grid.querySelectorAll('.agenda-event').forEach(e=>e.remove()); list.forEach(ev=>{ const container=grid.querySelector(`.day-body[data-date="${ev.date}"]`); if(!container) return; const div=document.createElement('div'); div.className='agenda-event text-sm'; div.style.backgroundColor = ev.color || agendaColors[0]; const time=document.createElement('span'); time.className='agenda-time'; time.textContent=`${ev.start} à ${ev.end}`; div.appendChild(time); const title=document.createElement('div'); title.className='agenda-event-title'; title.textContent=ev.title; div.appendChild(title); const desc=document.createElement('div'); desc.className='agenda-event-desc'; desc.textContent=ev.description||''; div.appendChild(desc); div.onclick=()=>openAgendaModal(true,ev); container.prepend(div); }); } function openAgendaModal(isEdit,data){ agendaEditingId=isEdit?data.row:null; document.getElementById('agendaModalTitle').textContent=isEdit?'Modifier l\u00e9v\u00e9nement':'Nouvel \u00e9v\u00e9nement'; document.getElementById('agenda-event-title').value=data.title||''; document.getElementById('agenda-event-date').value=data.date||agendaWeekStart; document.getElementById('agenda-event-start').value=data.start||''; document.getElementById('agenda-event-end').value=data.end||''; document.getElementById('agenda-event-desc').value=data.description||''; agendaSelectedColor=data.color||agendaColors[0]; updateColorPicker(); document.getElementById('deleteAgendaEvent').classList.toggle('hidden',!isEdit); document.getElementById('agendaEventModal').classList.remove('hidden'); } function saveAgendaEvent(){ const obj={ row: agendaEditingId, date: document.getElementById('agenda-event-date').value, title: document.getElementById('agenda-event-title').value.trim(), start: document.getElementById('agenda-event-start').value, end: document.getElementById('agenda-event-end').value, description: document.getElementById('agenda-event-desc').value.trim(), color: agendaSelectedColor }; const afterSave=()=>{ document.getElementById('agendaEventModal').classList.add('hidden'); const start=formatDateISO(getWeekStart(obj.date)); if(start!==agendaWeekStart){ agendaWeekStart=start; buildAgendaGrid(); updateAgendaDate(); } loadAgenda(); }; const runner=google.script.run.withSuccessHandler(afterSave); if(agendaEditingId) runner.updateAgendaItem(obj); else runner.createAgendaItem(obj); } function deleteAgendaEvent(){ if(!agendaEditingId) return; if(!confirm('Supprimer cet événement ?')) return; google.script.run.withSuccessHandler(()=>{document.getElementById('agendaEventModal').classList.add('hidden');loadAgenda();}).deleteAgendaItem(agendaEditingId); } function updateColorPicker(){ document.querySelectorAll('#agenda-color-picker span').forEach(s=>{ const active = s.dataset.color===agendaSelectedColor; s.classList.toggle('ring',active); s.classList.toggle('ring-2',active); s.classList.toggle('ring-offset-2',active); }); } function formatDateISO(d){ if(d===undefined||d===null||d==='') return ''; let dt; if(d instanceof Date){ dt=d; }else if(typeof d==='number'){ dt=new Date(Math.round((d-25569)*86400000)); }else{ dt=new Date(d); } if(isNaN(dt)) return ''; const y=dt.getFullYear(); const m=String(dt.getMonth()+1).padStart(2,'0'); const day=String(dt.getDate()).padStart(2,'0'); return `${y}-${m}-${day}`; } function getWeekStart(date){ const d=new Date(date); const day=d.getDay(); const diff=day===0?-6:1-day; d.setDate(d.getDate()+diff); d.setHours(0,0,0,0); return d; } function getWeekDates(start){ const dates=[]; const d=new Date(start); for(let i=0;i<7;i++){ dates.push(formatDateISO(addDays(d,i))); } return dates; } function addDays(date,n){ const d=new Date(date); d.setDate(d.getDate()+n); return d; } // Setup open/close handlers when the sidebar button is clicked (function(){ const openBtn = document.getElementById('sidebar-agenda'); if(openBtn){ openBtn.addEventListener('click', () => { activateSidebarButton('sidebar-agenda'); if(typeof window.initAgenda === 'function') window.initAgenda(); document.getElementById('agendaPanel').classList.remove('hidden'); }); } document.getElementById('closeAgenda').addEventListener('click', () => { document.getElementById('agendaPanel').classList.add('hidden'); }); document.getElementById('agendaPanel').addEventListener('click', e => { if(e.target.id === 'agendaPanel') e.target.classList.add('hidden'); }); })(); </script> |