api / public /js /api.js
Tataaicoltd's picture
deploy: taboard API (overwrite)
30a3fac verified
Raw History Blame Contribute Delete
3.39 kB
// Thin fetch wrapper around the backend API + shared UI helpers.
const API = {
base: '/api',
async request(method, path, body) {
const opts = { method, headers: {} };
if (body !== undefined) {
opts.headers['Content-Type'] = 'application/json';
opts.body = JSON.stringify(body);
}
const res = await fetch(this.base + path, opts);
if (res.status === 204) return null;
const data = await res.json().catch(() => ({}));
if (!res.ok) throw new Error(data.error || `Request failed (${res.status})`);
return data;
},
// Projects
listProjects() { return this.request('GET', '/projects'); },
getProject(id) { return this.request('GET', `/projects/${id}`); },
createProject(p) { return this.request('POST', '/projects', p); },
updateProject(id, p) { return this.request('PUT', `/projects/${id}`, p); },
deleteProject(id) { return this.request('DELETE', `/projects/${id}`); },
// Tickets
listTickets(params = {}) {
const qs = new URLSearchParams(params).toString();
return this.request('GET', `/tickets${qs ? '?' + qs : ''}`);
},
getTicket(id) { return this.request('GET', `/tickets/${id}`); },
createTicket(t) { return this.request('POST', '/tickets', t); },
updateTicket(id, t) { return this.request('PUT', `/tickets/${id}`, t); },
deleteTicket(id) { return this.request('DELETE', `/tickets/${id}`); },
deleteTicketImage(id, url) { return this.request('DELETE', `/tickets/${id}/images`, { url }); },
async uploadImages(id, files) {
const fd = new FormData();
for (const f of files) fd.append('images', f);
const res = await fetch(`${this.base}/tickets/${id}/images`, { method: 'POST', body: fd });
const data = await res.json().catch(() => ({}));
if (!res.ok) throw new Error(data.error || 'Upload failed');
return data;
}
};
// ---------- UI helpers ----------
const UI = {
el(html) {
const t = document.createElement('template');
t.innerHTML = html.trim();
return t.content.firstElementChild;
},
esc(s) {
return String(s ?? '').replace(/[&<>"']/g, (c) => ({
'&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'
})[c]);
},
fmtDate(v) {
if (!v) return '—';
const d = new Date(v);
if (isNaN(d)) return UI.esc(v);
return d.toLocaleString();
},
toast(msg, isError = false) {
const t = document.getElementById('toast');
t.textContent = msg;
t.className = 'toast' + (isError ? ' error' : '');
t.hidden = false;
clearTimeout(this._toastTimer);
this._toastTimer = setTimeout(() => (t.hidden = true), 2600);
},
openModal(html, wide = false) {
const overlay = document.getElementById('modal-overlay');
const modal = document.getElementById('modal');
modal.className = 'modal' + (wide ? ' wide' : '');
modal.innerHTML = '';
modal.appendChild(typeof html === 'string' ? UI.el(html) : html);
overlay.hidden = false;
return modal;
},
closeModal() {
document.getElementById('modal-overlay').hidden = true;
},
lightbox(url) {
const lb = UI.el(`<div class="lightbox"><img src="${UI.esc(url)}" alt="screenshot" /></div>`);
lb.addEventListener('click', () => lb.remove());
document.body.appendChild(lb);
}
};
// Close modal on overlay click
document.addEventListener('click', (e) => {
if (e.target.id === 'modal-overlay') UI.closeModal();
});