console.log("window.Telegram:", window.Telegram); console.log("window.Telegram.WebApp:", window.Telegram && window.Telegram.WebApp); let tg = null; if (window.Telegram && window.Telegram.WebApp) { tg = window.Telegram.WebApp; tg.ready(); // Debug: log all available init data console.log("tg.initData:", tg.initData); console.log("tg.initDataUnsafe:", tg.initDataUnsafe); if (tg.initDataUnsafe && tg.initDataUnsafe.user) { console.log("tg.initDataUnsafe.user:", tg.initDataUnsafe.user); } } else { console.warn("Telegram WebApp API not found. Will attempt fallback methods."); } const apiBase = "/api"; function getNativeMapLink(location) { if (!location) return null; const [lat, lng] = location.split(','); const ua = navigator.userAgent || navigator.vendor || window.opera; const isIOS = /iPad|iPhone|iPod/.test(ua); // Use 'q' for Apple Maps to show a pin, and 'q' for Google Maps for a marker if (isIOS) { return `https://maps.apple.com/?q=${lat},${lng}`; } else { return `https://maps.google.com/?q=${lat},${lng}`; } } // MiniMapWithAddress component for static Google map thumbnail + Google reverse geocoded address // Usage: MiniMapWithAddress({ location, container, adress, adress_image }) async function MiniMapWithAddress({ location, container, adress, adress_image }) { console.log('[MiniMapWithAddress] called with', { location, container }); if (!location || !container) return; const [lat, lng] = location.split(','); // Insert loading placeholder container.innerHTML = `
Map
`; // Use cached image/address if available let address = adress || ''; let imageUrl = adress_image || ''; const imgEl = container.querySelector('img'); if (imageUrl) { imgEl.src = imageUrl; imgEl.onerror = () => { imgEl.style.display = 'none'; if (imgEl.nextElementSibling) imgEl.nextElementSibling.textContent = 'Map failed to load'; }; } else if (location) { // Only call backend if no cached image try { const [lat, lng] = location.split(','); const imgResp = await fetch(`/api/maps/staticmap?lat=${lat}&lng=${lng}&zoom=15&size=100x60`); if (imgResp.ok) { const blob = await imgResp.blob(); const imgUrl = URL.createObjectURL(blob); imgEl.src = imgUrl; imgEl.onerror = () => { imgEl.style.display = 'none'; if (imgEl.nextElementSibling) imgEl.nextElementSibling.textContent = 'Map failed to load'; }; } else { imgEl.style.display = 'none'; } } catch (err) { imgEl.style.display = 'none'; } } if (!address && location) { // Only call backend if no cached address try { const [lat, lng] = location.split(','); const resp = await fetch(`/api/maps/reverse_geocode?lat=${lat}&lng=${lng}`); if (resp.ok) { const data = await resp.json(); if (data.results && data.results.length > 0) { address = data.results[0].formatted_address; } } } catch (err) {} } // Update address in DOM const miniMapDiv = container.querySelector('div'); if (miniMapDiv) { let span = miniMapDiv.querySelector('span'); if (!span) { span = document.createElement('span'); span.className = 'text-xs text-gray-600 mt-1'; miniMapDiv.appendChild(span); } span.textContent = address ? address : 'Address not found'; } } // Helper to load all minimaps after render function loadMiniMaps() { document.querySelectorAll('.minimap[data-location]').forEach(div => { MiniMapWithAddress({ location: div.dataset.location, container: div, adress: div.dataset.adress || '', adress_image: div.dataset.adress_image || '' }); }); } function renderUserAndWorkStates(user, states) { const container = document.getElementById("companies"); if (!container) return; // Defensive: If user is null/undefined, show error and return if (!user || !user.telegram_id) { container.innerHTML = `
User not found. Please check your Telegram login or contact support.
`; return; } // Clear container before rendering container.innerHTML = ""; console.log('[DEBUG] renderUserAndWorkStates received user:', user); // Tabbed interface: Work State, Add Correction, My Corrections container.innerHTML = `
${window.currentUser.role === 'super_admin' ? `
${window.currentUser.role}
Telegram ID: ${user.telegram_id}
` : ''}

Hello, ${user.first_name} ${user.last_name || ''}!

Your work state: ${(user.work_state && user.work_state !== "") ? user.work_state.replace('_', ' ') : "unknown"}
${user.remote_chek_in ? ` ` : ``}
${(states && states.length) ? `

Last 5 Check-ins/Check-outs

    ${states.map(state => `
  • ${new Date(state.created_at).toLocaleString()} ${state.work_state.replace('_', ' ')}
    ${state.location ? `
    ` : ''}
  • `).join('')}
` : ''}
`; // After rendering, attach minimaps for each location setTimeout(() => { loadMiniMaps(); }, 0); // Tab logic const tabWorkRecords = container.querySelector('#tab-workrecords'); const tabCorrections = container.querySelector('#tab-corrections'); const tabAllCorrections = container.querySelector('#tab-allcorrections'); const paneWorkRecords = container.querySelector('#tab-pane-workrecords'); const paneCorrections = container.querySelector('#tab-pane-corrections'); const paneAllCorrections = container.querySelector('#tab-pane-allcorrections'); tabWorkRecords.onclick = () => { tabWorkRecords.classList.add('text-blue-700','border-blue-600'); tabWorkRecords.classList.remove('text-gray-600','border-transparent'); tabCorrections.classList.remove('text-blue-700','border-blue-600'); tabCorrections.classList.add('text-gray-600','border-transparent'); if (tabAllCorrections) tabAllCorrections.classList.remove('text-blue-700','border-blue-600'), tabAllCorrections.classList.add('text-gray-600','border-transparent'); paneWorkRecords.style.display = ''; paneCorrections.style.display = 'none'; if (paneAllCorrections) paneAllCorrections.style.display = 'none'; }; tabCorrections.onclick = () => { tabCorrections.classList.add('text-blue-700','border-blue-600'); tabCorrections.classList.remove('text-gray-600','border-transparent'); tabWorkRecords.classList.remove('text-blue-700','border-blue-600'); tabWorkRecords.classList.add('text-gray-600','border-transparent'); if (tabAllCorrections) tabAllCorrections.classList.remove('text-blue-700','border-blue-600'), tabAllCorrections.classList.add('text-gray-600','border-transparent'); paneWorkRecords.style.display = 'none'; paneCorrections.style.display = ''; if (paneAllCorrections) paneAllCorrections.style.display = 'none'; renderTimeCorrectionsTab(user.uuid, paneCorrections); }; if (tabAllCorrections) { tabAllCorrections.onclick = () => { tabAllCorrections.classList.add('text-blue-700','border-blue-600'); tabAllCorrections.classList.remove('text-gray-600','border-transparent'); tabWorkRecords.classList.remove('text-blue-700','border-blue-600'); tabWorkRecords.classList.add('text-gray-600','border-transparent'); tabCorrections.classList.remove('text-blue-700','border-blue-600'); tabCorrections.classList.add('text-gray-600','border-transparent'); paneWorkRecords.style.display = 'none'; paneCorrections.style.display = 'none'; paneAllCorrections.style.display = ''; // Load all corrections (no uuid filter) paneAllCorrections.innerHTML = '
'; loadCorrectionsListTab(null, paneAllCorrections.querySelector('#allCorrectionsListContainer')); }; } // Default to Work Records tab tabWorkRecords.onclick(); // Attach work state button logic const btn = container.querySelector("#toggleWorkStateBtn"); if (btn) { const btnClone = btn.cloneNode(true); btn.parentNode.replaceChild(btnClone, btn); btnClone.disabled = false; btnClone.onclick = async () => { btnClone.disabled = true; btnClone.classList.add('opacity-60', 'cursor-not-allowed'); const newState = user.work_state === "check_in" ? "check_out" : "check_in"; let location = null; let locationDenied = false; if (navigator.geolocation) { try { location = await new Promise((resolve, reject) => { navigator.geolocation.getCurrentPosition( pos => resolve(`${pos.coords.latitude},${pos.coords.longitude}`), err => { locationDenied = true; resolve(null); }, { enableHighAccuracy: true, timeout: 5000, maximumAge: 0 } ); }); } catch (e) { location = null; } } else { location = null; } if (locationDenied) { alert("Location permission denied. Your check-in/out will be recorded without location."); } try { await fetch(`${apiBase}/personal/update_work_state`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ telegram_id: user.telegram_id, work_state: newState, location, source_of_data: 'telegram' }) }); } finally { loadUser(); } }; } } // Helper to render the Add Correction and My Corrections panes into the tabbed UI function renderTimeCorrectionsTab(uuid, container) { // Render tab headers let tabHeaders = `
`; if (window.currentUser && window.currentUser.role === 'super_admin') { tabHeaders += ``; } tabHeaders += `
`; container.innerHTML = ` ${tabHeaders}

Time Corrections

`; const btnShowForm = container.querySelector('#showAddCorrectionForm'); const formContainer = container.querySelector('#addCorrectionFormContainer'); const paneMy = container.querySelector('#tab-pane-my-corrections'); const paneAll = container.querySelector('#tab-pane-all-corrections'); // Tab switching const tabMy = container.querySelector('#tab-my-corrections'); const tabAll = container.querySelector('#tab-all-corrections'); tabMy.onclick = () => { tabMy.classList.add('text-blue-700','border-blue-600'); tabMy.classList.remove('text-gray-600','border-transparent'); if (tabAll) { tabAll.classList.remove('text-blue-700','border-blue-600'); tabAll.classList.add('text-gray-600','border-transparent'); paneAll.style.display = 'none'; } paneMy.style.display = ''; paneMy.innerHTML = `

Time Corrections

`; const btnShowForm = paneMy.querySelector('#showAddCorrectionForm'); const formContainer = paneMy.querySelector('#addCorrectionFormContainer'); btnShowForm.onclick = () => { btnShowForm.style.display = 'none'; formContainer.style.display = ''; renderAddCorrectionForm(uuid, formContainer, paneMy.querySelector('#correctionsListContainer'), btnShowForm); }; loadCorrectionsListTab(uuid, paneMy.querySelector('#correctionsListContainer')); }; if (tabAll) { tabAll.onclick = () => { tabAll.classList.add('text-blue-700','border-blue-600'); tabAll.classList.remove('text-gray-600','border-transparent'); tabMy.classList.remove('text-blue-700','border-blue-600'); tabMy.classList.add('text-gray-600','border-transparent'); paneMy.style.display = 'none'; paneAll.style.display = ''; paneAll.innerHTML = '
'; loadCorrectionsListTab(null, paneAll.querySelector('#allCorrectionsListContainer')); }; } // Default: show My Corrections tabMy.onclick(); btnShowForm.onclick = () => { btnShowForm.style.display = 'none'; formContainer.style.display = ''; renderAddCorrectionForm(uuid, formContainer, paneMy.querySelector('#correctionsListContainer'), btnShowForm); }; } function renderAddCorrectionForm(uuid, formContainer, listContainer, btnShowForm) { formContainer.innerHTML = `
`; const addCorrectionForm = formContainer.querySelector('#timeCorrectionForm'); const successMsg = formContainer.querySelector('#addCorrectionSuccess'); const cancelBtn = formContainer.querySelector('#cancelAddCorrection'); if (cancelBtn && btnShowForm) { cancelBtn.onclick = (e) => { e.preventDefault(); formContainer.style.display = 'none'; btnShowForm.style.display = ''; }; } addCorrectionForm.onsubmit = async (e) => { e.preventDefault(); const fd = new FormData(addCorrectionForm); const payload = { uuid, shift_start: fd.get('shift_start'), shift_end: fd.get('shift_end'), description: fd.get('description'), telegram_id: (window.currentUser && window.currentUser.telegram_id) || null, first_name: (window.currentUser && window.currentUser.first_name) || '', last_name: (window.currentUser && window.currentUser.last_name) || '', resolved: false }; addCorrectionForm.querySelector('button[type="submit"]').disabled = true; try { const res = await fetch(`${apiBase}/time_correction_from_users`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (!res.ok) throw new Error('Failed to submit correction'); addCorrectionForm.reset(); successMsg.classList.remove('hidden'); setTimeout(() => { successMsg.classList.add('hidden'); formContainer.style.display = 'none'; btnShowForm.style.display = ''; loadCorrectionsListTab(uuid, listContainer); }, 1200); } catch (err) { alert('Error: ' + err.message); } finally { addCorrectionForm.querySelector('button[type="submit"]').disabled = false; } }; } // Helper to render the list of corrections in the My Corrections tab async function loadCorrectionsListTab(uuid, listPane) { listPane.innerHTML = `
Loading corrections...
`; try { // For 'All corrections', omit uuid param let url = `${apiBase}/time_correction_from_users?limit=20`; if (uuid !== undefined && uuid !== null && uuid !== '' && uuid !== 'null') { url = `${apiBase}/time_correction_from_users?uuid=${uuid}&limit=5`; } const res = await fetch(url); if (!res.ok) { const text = await res.text(); console.error('Failed to fetch corrections:', res.status, text); throw new Error('Failed to fetch corrections'); } const corrections = await res.json(); if (!Array.isArray(corrections) || corrections.length === 0) { listPane.innerHTML = `
No corrections found.
`; return; } // If viewing all corrections (uuid is null), show a table with names if (!uuid) { listPane.innerHTML = `
${corrections.map(tc => `
${tc.first_name || ''} ${tc.last_name || ''}
${tc.shift_start ? new Date(tc.shift_start).toLocaleString() : ''} - ${tc.shift_end ? new Date(tc.shift_end).toLocaleString() : ''}
Total: ${(() => { const ms = new Date(tc.shift_end).getTime() - new Date(tc.shift_start).getTime(); if (isNaN(ms) || ms <= 0) return '--'; const h = Math.floor(ms / 3600000); const m = Math.floor((ms % 3600000) / 60000); return `${h}h ${m}m`; })()}
${tc.adress ? `
📍 ${tc.adress}
` : ''} ${tc.adress_image ? `address map` : ''}
${(window.currentUser && window.currentUser.role === 'super_admin') ? `` : ''} ${tc.resolved ? '' : ``}
${tc.description || ''}
`).join('')}
`; } else { listPane.innerHTML = `
${corrections.map(tc => `
${new Date(tc.shift_start).toLocaleString()} - ${new Date(tc.shift_end).toLocaleString()}
total time: ${(() => { const ms = new Date(tc.shift_end).getTime() - new Date(tc.shift_start).getTime(); if (isNaN(ms) || ms <= 0) return '--'; const h = Math.floor(ms / 3600000); const m = Math.floor((ms % 3600000) / 60000); return `${h}h ${m}m`; })()}
${tc.adress ? `
📍 ${tc.adress}
` : ''} ${tc.adress_image ? `address map` : ''}
${tc.description || ''}
${(window.currentUser && window.currentUser.role === 'super_admin') ? `` : ''} ${tc.resolved ? '' : ``}
`).join('')}
`; } // Attach accept handlers (super_admin) listPane.querySelectorAll('.accept-tc-btn').forEach(btn => { btn.onclick = async () => { const id = btn.dataset.id; const tc = corrections.find(c => c.id == id); const newResolved = !tc.resolved; btn.disabled = true; try { const res = await fetch(`${apiBase}/time_correction_from_users/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...tc, resolved: newResolved }) }); if (!res.ok) throw new Error('Failed to update resolved state'); await loadCorrectionsListTab(uuid, listPane); } catch (err) { alert('Accept error: ' + err.message); } finally { btn.disabled = false; } }; }); // Attach edit handlers listPane.querySelectorAll('.edit-tc-btn').forEach(btn => { btn.onclick = () => { const id = btn.dataset.id; const tc = corrections.find(c => c.id == id); const formContainer = listPane.querySelector(`#edit-tc-form-${id}`); if (!formContainer) return; // Hide all other edit forms and show only this one listPane.querySelectorAll('.edit-tc-form-container').forEach(f => { f.style.display = 'none'; }); listPane.querySelectorAll('.edit-tc-btn').forEach(b => { b.style.display = ''; }); formContainer.innerHTML = `
`; formContainer.style.display = ''; btn.style.display = 'none'; // Cancel button formContainer.querySelector('.cancel-edit-tc-btn').onclick = () => { formContainer.style.display = 'none'; btn.style.display = ''; }; // Submit handler formContainer.querySelector('.edit-tc-form').onsubmit = async (e) => { e.preventDefault(); const fd = new FormData(e.target); const payload = { shift_start: fd.get('shift_start'), shift_end: fd.get('shift_end'), description: fd.get('description'), resolved: tc.resolved }; formContainer.querySelector('button[type="submit"]').disabled = true; try { const res = await fetch(`${apiBase}/time_correction_from_users/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...tc, ...payload }) }); if (!res.ok) throw new Error('Failed to update correction'); formContainer.style.display = 'none'; btn.style.display = ''; await loadCorrectionsListTab(uuid, listPane); } catch (err) { alert('Edit error: ' + err.message); } finally { formContainer.querySelector('button[type="submit"]').disabled = false; } }; }; }); // Attach delete handlers listPane.querySelectorAll('.delete-tc-btn').forEach(btn => { btn.onclick = async () => { if (!confirm('Delete this correction?')) return; btn.disabled = true; try { const delRes = await fetch(`${apiBase}/time_correction_from_users/${btn.dataset.id}`, { method: 'DELETE' }); if (!delRes.ok) throw new Error('Delete failed'); await loadCorrectionsListTab(uuid, listPane); } catch (err) { alert('Delete error: ' + err.message); } finally { btn.disabled = false; } }; }); } catch (e) { listPane.innerHTML = '
Failed to load corrections.
'; } } // Use the CDN version of @telegram-apps/sdk, loaded in index.html // Access retrieveLaunchParams via window.TelegramAppsSdk function getInitDataFromUrl() { // Try both search and hash for tgWebAppData/initData let data = null; if (window.location.search && window.location.search.includes('=')) { const params = new URLSearchParams(window.location.search); data = params.get('initData') || params.get('tgWebAppData'); } if (!data && window.location.hash) { const hash = window.location.hash.substring(1); // remove '#' if (hash.includes('=')) { const hashParams = new URLSearchParams(hash); data = hashParams.get('tgWebAppData') || hashParams.get('initData'); } if (!data) { // Sometimes hash is not query params, but a single string // e.g. #tgWebAppData=....&tgWebAppVersion=... const match = hash.match(/tgWebAppData=([^&]+)/); if (match) data = decodeURIComponent(match[1]); } } return data; } function parseUserFromTgWebAppData(tgWebAppData) { // tgWebAppData is a query string, e.g. query_id=...&user=%7B...%7D&...&hash=... if (!tgWebAppData || !tgWebAppData.includes('=')) return null; try { const params = new URLSearchParams(tgWebAppData); let user = params.get('user'); if (user) { user = JSON.parse(decodeURIComponent(user)); // Ensure telegram_id is present for backend compatibility if (!user.telegram_id && user.id) { user.telegram_id = String(user.id); } return user; } } catch (e) { console.warn('Failed to parse user from tgWebAppData:', e); } return null; } async function loadUser() { let telegram_id = null; let initData = null; let fallbackUser = null; // Try @telegram-apps/sdk first try { const sdkParams = window.TelegramAppsSdk.retrieveLaunchParams(); console.log('[SDK] retrieveLaunchParams:', sdkParams); const { initData: sdkInitData } = sdkParams; if (sdkInitData && sdkInitData.user && sdkInitData.user.id) { telegram_id = sdkInitData.user.id; } } catch (e) { console.warn('Failed to use TelegramAppsSdk:', e); } // Try window.Telegram.WebApp if (!telegram_id && window.Telegram && window.Telegram.WebApp && window.Telegram.WebApp.initDataUnsafe && window.Telegram.WebApp.initDataUnsafe.user) { telegram_id = window.Telegram.WebApp.initDataUnsafe.user.id; } // Fallback: try parsing from URL hash if (!telegram_id) { const tgWebAppData = getInitDataFromUrl(); fallbackUser = parseUserFromTgWebAppData(tgWebAppData); if (fallbackUser && fallbackUser.telegram_id) { telegram_id = fallbackUser.telegram_id; } } if (!telegram_id) { document.getElementById("data").innerHTML = "Error: Could not determine your Telegram ID. Please launch this app from Telegram."; const companiesDiv = document.getElementById("companies"); if (companiesDiv) companiesDiv.innerHTML = ""; return; } // Query backend for user info try { const res = await fetch(`${apiBase}/personal/by_telegram_id?telegram_id=${telegram_id}`); if (res.ok) { const user = await res.json(); let states = []; const wsRes = await fetch(`${apiBase}/personal/${user.uuid}/last_work_states`); if (wsRes.ok) { states = await wsRes.json(); } console.log('[DEBUG] User object from backend:', user); window.currentUser = user; renderUserAndWorkStates(user, states); } else if (res.status === 404) { document.getElementById("data").innerHTML = "User not found: Your Telegram ID is not registered. Please contact admin."; const companiesDiv = document.getElementById("companies"); if (companiesDiv) companiesDiv.innerHTML = ""; } else { document.getElementById("data").innerHTML = `Backend error: ${res.status}`; const companiesDiv = document.getElementById("companies"); if (companiesDiv) companiesDiv.innerHTML = ""; } } catch (e) { document.getElementById("data").innerHTML = `Connection error: ${e}`; const companiesDiv = document.getElementById("companies"); if (companiesDiv) companiesDiv.innerHTML = ""; return; } } // --- Time Corrections Component --- async function renderTimeCorrections(uuid) { const containerId = "time-corrections"; let container = document.getElementById(containerId); if (!container) { container = document.createElement("div"); container.id = containerId; document.getElementById("companies").after(container); } container.innerHTML = `

Time Corrections

`; // Tab logic const tabAdd = container.querySelector('#tc-tab-add'); const tabList = container.querySelector('#tc-tab-list'); const paneAdd = container.querySelector('#tc-add-pane'); const paneList = container.querySelector('#tc-list-pane'); tabAdd.onclick = () => { tabAdd.classList.add('text-blue-700','border-blue-600'); tabAdd.classList.remove('text-gray-600','border-transparent'); tabList.classList.remove('text-blue-700','border-blue-600'); tabList.classList.add('text-gray-600','border-transparent'); paneAdd.style.display = ''; paneList.style.display = 'none'; }; tabList.onclick = () => { tabList.classList.add('text-blue-700','border-blue-600'); tabList.classList.remove('text-gray-600','border-transparent'); tabAdd.classList.remove('text-blue-700','border-blue-600'); tabAdd.classList.add('text-gray-600','border-transparent'); paneAdd.style.display = 'none'; paneList.style.display = ''; }; // Always show Add first paneAdd.style.display = ''; paneList.style.display = 'none'; // Debug log console.log('[UI] Time Corrections tabs rendered'); // Handle form submit const tabCorrectionForm = container.querySelector('#timeCorrectionForm'); tabCorrectionForm.onsubmit = async (e) => { e.preventDefault(); const fd = new FormData(tabCorrectionForm); const payload = { uuid, shift_start: fd.get('shift_start'), shift_end: fd.get('shift_end'), description: fd.get('description') }; tabCorrectionForm.querySelector('button[type="submit"]').disabled = true; try { const res = await fetch(`${apiBase}/time_correction_from_users`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (!res.ok) throw new Error('Failed to submit correction'); tabCorrectionForm.reset(); await loadCorrectionsList(uuid); // Switch to list tab after add tabList.click(); } catch (err) { alert('Error: ' + err.message); } finally { tabCorrectionForm.querySelector('button[type="submit"]').disabled = false; } }; await loadCorrectionsList(uuid); // Show list tab if user clicks tabList.onclick(); // END FORM HANDLER PATCH correctionForm.onsubmit = async (e) => { e.preventDefault(); const fd = new FormData(form); const payload = { uuid, shift_start: fd.get('shift_start'), shift_end: fd.get('shift_end'), description: fd.get('description') }; form.querySelector('button[type="submit"]').disabled = true; try { const res = await fetch(`${apiBase}/time_correction_from_users`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (!res.ok) throw new Error('Failed to submit correction'); form.reset(); await loadCorrectionsList(uuid); } catch (err) { alert('Error: ' + err.message); } finally { form.querySelector('button[type="submit"]').disabled = false; } }; await loadCorrectionsList(uuid); } async function loadCorrectionsList(uuid) { const listSection = document.getElementById('tc-list-section'); listSection.innerHTML = '
Loading corrections...
'; try { const res = await fetch(`${apiBase}/time_correction_from_users?order=created_at.desc&limit=5`); let data = []; if (res.ok) { data = await res.json(); // Filter by uuid (since backend may not support uuid param yet) data = data.filter(tc => tc.uuid === uuid).slice(0, 5); } if (!data.length) { listSection.innerHTML = '
No corrections found.
'; return; } listSection.innerHTML = ``; // Attach delete handlers listSection.querySelectorAll('.delete-tc-btn').forEach(btn => { btn.onclick = async () => { if (!confirm('Delete this correction?')) return; btn.disabled = true; try { const delRes = await fetch(`${apiBase}/time_correction_from_users/${btn.dataset.id}`, { method: 'DELETE' }); if (!delRes.ok) throw new Error('Delete failed'); await loadCorrectionsList(uuid); } catch (err) { alert('Delete error: ' + err.message); } finally { btn.disabled = false; } }; }); } catch (e) { listSection.innerHTML = '
Failed to load corrections.
'; } } // --- END Time Corrections Component --- document.addEventListener("DOMContentLoaded", loadUser);