api / public /js /projects.js
Tataaicoltd's picture
deploy: taboard API (overwrite)
30a3fac verified
Raw History Blame Contribute Delete
4.66 kB
// Projects view — CRUD list styled like a Jira projects page.
const ProjectsView = {
async render(container) {
container.innerHTML = `
<div class="page-head">
<div>
<h1 class="page-title">Projects</h1>
<div class="page-sub">Manage your projects and their structure</div>
</div>
<button class="btn btn-primary" id="new-project">+ Create project</button>
</div>
<div id="projects-body"><div class="spinner">Loading…</div></div>
`;
container.querySelector('#new-project').addEventListener('click', () => this.openForm());
await this.load();
},
async load() {
const body = document.getElementById('projects-body');
try {
const projects = await API.listProjects();
if (!projects.length) {
body.innerHTML = `
<div class="empty-state">
<h3>No projects yet</h3>
<p>Create your first project to get started.</p>
</div>`;
return;
}
body.innerHTML = `
<div class="table-wrap">
<table class="data">
<thead>
<tr>
<th style="width:60px">ID</th>
<th>Name</th>
<th>Note</th>
<th>Created</th>
<th style="width:140px;text-align:right">Actions</th>
</tr>
</thead>
<tbody>
${projects.map((p) => this.row(p)).join('')}
</tbody>
</table>
</div>`;
body.querySelectorAll('[data-edit]').forEach((b) =>
b.addEventListener('click', () => this.openForm(Number(b.dataset.edit)))
);
body.querySelectorAll('[data-del]').forEach((b) =>
b.addEventListener('click', () => this.remove(Number(b.dataset.del)))
);
} catch (e) {
body.innerHTML = `<div class="empty-state"><h3>Failed to load</h3><p>${UI.esc(e.message)}</p></div>`;
}
},
row(p) {
return `
<tr>
<td class="muted mono">${p.id}</td>
<td><strong>${UI.esc(p.name)}</strong></td>
<td class="muted">${UI.esc(p.note || '—')}</td>
<td class="muted">${UI.fmtDate(p.creatid)}</td>
<td>
<div class="row-actions">
<button class="btn btn-subtle btn-sm" data-edit="${p.id}">Edit</button>
<button class="btn btn-danger btn-sm" data-del="${p.id}">Delete</button>
</div>
</td>
</tr>`;
},
async openForm(id) {
let p = { name: '', note: '', struc: {} };
if (id) {
try { p = await API.getProject(id); } catch (e) { return UI.toast(e.message, true); }
}
const strucText = typeof p.struc === 'object' ? JSON.stringify(p.struc ?? {}, null, 2) : (p.struc || '');
const modal = UI.openModal(`
<div>
<h2 class="modal-title">${id ? 'Edit project' : 'Create project'}</h2>
<div class="field">
<label>Name *</label>
<input type="text" id="f-name" value="${UI.esc(p.name)}" placeholder="Project name" />
</div>
<div class="field">
<label>Note</label>
<textarea id="f-note" placeholder="Short description">${UI.esc(p.note || '')}</textarea>
</div>
<div class="field">
<label>Structure (JSON)</label>
<textarea id="f-struc" class="mono" placeholder='{ "columns": ["todo","doing","done"] }'>${UI.esc(strucText)}</textarea>
</div>
<div class="modal-actions">
<button class="btn btn-subtle" id="f-cancel">Cancel</button>
<button class="btn btn-primary" id="f-save">${id ? 'Save' : 'Create'}</button>
</div>
</div>
`);
modal.querySelector('#f-cancel').addEventListener('click', UI.closeModal);
modal.querySelector('#f-save').addEventListener('click', async () => {
const name = modal.querySelector('#f-name').value.trim();
if (!name) return UI.toast('Name is required', true);
const payload = {
name,
note: modal.querySelector('#f-note').value.trim(),
struc: modal.querySelector('#f-struc').value.trim()
};
try {
if (id) await API.updateProject(id, payload);
else await API.createProject(payload);
UI.closeModal();
UI.toast(id ? 'Project updated' : 'Project created');
this.load();
} catch (e) { UI.toast(e.message, true); }
});
},
async remove(id) {
if (!confirm('Delete this project? Tickets linked to it will be unlinked.')) return;
try {
await API.deleteProject(id);
UI.toast('Project deleted');
this.load();
} catch (e) { UI.toast(e.message, true); }
}
};