File size: 4,656 Bytes
30a3fac
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// 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); }
  }
};