Spaces:
Running
Running
Download public/js/projects.js from Tataaicoltd/api: direct link, hf CLI and curl.
- Browser
- Download file 4.66 kB
-
https://huggingface.co/spaces/Tataaicoltd/api/resolve/main/public/js/projects.js
- Command line
-
hf download hf://spaces/Tataaicoltd/api/public/js/projects.js
-
curl -L -o projects.js https://huggingface.co/spaces/Tataaicoltd/api/resolve/main/public/js/projects.js
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); } | |
| } | |
| }; | |