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 = `
`;
// 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 ? `
${user.work_state === "check_in" ? "Check Out" : "Check In"}
` : ``}
Work Records
Time Corrections
${(states && states.length) ? `
Last 5 Check-ins/Check-outs
` : ''}
`;
// 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 = `
My Corrections `;
if (window.currentUser && window.currentUser.role === 'super_admin') {
tabHeaders += `All corrections `;
}
tabHeaders += `
`;
container.innerHTML = `
${tabHeaders}
Time Corrections
Add Correction
`;
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
Add Correction
`;
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 = `
Correction submitted!
`;
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 ? `
` : ''}
${(window.currentUser && window.currentUser.role === 'super_admin') ? `${tc.resolved ? 'Unresolve' : 'Accept'} ` : ''}
${tc.resolved ? '' : `Edit Delete `}
${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 ? `
` : ''}
${tc.description || ''}
${(window.currentUser && window.currentUser.role === 'super_admin') ? `${tc.resolved ? 'Unresolve' : 'Accept'} ` : ''}
${tc.resolved ? '' : `Edit Delete `}
`).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
Add Correction
My 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 = `
${data.map(tc => `
${tc.shift_start ? new Date(tc.shift_start).toLocaleString() : ''} - ${tc.shift_end ? new Date(tc.shift_end).toLocaleString() : ''}
${tc.description || ''}
Delete
`).join('')}
`;
// 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);