@unbrained/pm-web 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +107 -0
- package/dist/auth.js +20 -0
- package/dist/auth.js.map +1 -0
- package/dist/crypto.js +42 -0
- package/dist/crypto.js.map +1 -0
- package/dist/db.js +111 -0
- package/dist/db.js.map +1 -0
- package/dist/index.js +88 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/auth.js +16 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/routes/admin.js +207 -0
- package/dist/routes/admin.js.map +1 -0
- package/dist/routes/auth.js +163 -0
- package/dist/routes/auth.js.map +1 -0
- package/dist/routes/github.js +354 -0
- package/dist/routes/github.js.map +1 -0
- package/dist/routes/groups.js +180 -0
- package/dist/routes/groups.js.map +1 -0
- package/dist/routes/pm.js +2446 -0
- package/dist/routes/pm.js.map +1 -0
- package/dist/routes/projects.js +151 -0
- package/dist/routes/projects.js.map +1 -0
- package/dist/routes/sharing.js +155 -0
- package/dist/routes/sharing.js.map +1 -0
- package/dist/server.js +64 -0
- package/dist/server.js.map +1 -0
- package/dist/services/pm-runner.js +190 -0
- package/dist/services/pm-runner.js.map +1 -0
- package/dist/services/sse.js +111 -0
- package/dist/services/sse.js.map +1 -0
- package/manifest.json +15 -0
- package/package.json +111 -0
- package/public/icons/icon-192.png +0 -0
- package/public/icons/icon-512.png +0 -0
- package/public/index.html +265 -0
- package/public/manifest.json +66 -0
- package/public/src/api.js +28 -0
- package/public/src/api.js.map +1 -0
- package/public/src/api.ts +29 -0
- package/public/src/app.js +926 -0
- package/public/src/app.js.map +1 -0
- package/public/src/app.ts +929 -0
- package/public/src/components/modals.js +62 -0
- package/public/src/components/modals.js.map +1 -0
- package/public/src/components/modals.ts +73 -0
- package/public/src/components/toast.js +10 -0
- package/public/src/components/toast.js.map +1 -0
- package/public/src/components/toast.ts +13 -0
- package/public/src/constants.js +30 -0
- package/public/src/constants.js.map +1 -0
- package/public/src/constants.ts +41 -0
- package/public/src/state.js +15 -0
- package/public/src/state.js.map +1 -0
- package/public/src/state.ts +19 -0
- package/public/src/types.js +5 -0
- package/public/src/types.js.map +1 -0
- package/public/src/types.ts +253 -0
- package/public/src/utils.js +57 -0
- package/public/src/utils.js.map +1 -0
- package/public/src/utils.ts +56 -0
- package/public/src/views/activity.js +47 -0
- package/public/src/views/activity.js.map +1 -0
- package/public/src/views/activity.ts +41 -0
- package/public/src/views/admin.js +435 -0
- package/public/src/views/admin.js.map +1 -0
- package/public/src/views/admin.ts +504 -0
- package/public/src/views/auth.js +81 -0
- package/public/src/views/auth.js.map +1 -0
- package/public/src/views/auth.ts +74 -0
- package/public/src/views/calendar.js +133 -0
- package/public/src/views/calendar.js.map +1 -0
- package/public/src/views/calendar.ts +129 -0
- package/public/src/views/comments-audit.js +109 -0
- package/public/src/views/comments-audit.js.map +1 -0
- package/public/src/views/comments-audit.ts +108 -0
- package/public/src/views/config.js +322 -0
- package/public/src/views/config.js.map +1 -0
- package/public/src/views/config.ts +344 -0
- package/public/src/views/context.js +98 -0
- package/public/src/views/context.js.map +1 -0
- package/public/src/views/context.ts +100 -0
- package/public/src/views/create.js +293 -0
- package/public/src/views/create.js.map +1 -0
- package/public/src/views/create.ts +246 -0
- package/public/src/views/dedupe.js +51 -0
- package/public/src/views/dedupe.js.map +1 -0
- package/public/src/views/dedupe.ts +43 -0
- package/public/src/views/export.js +300 -0
- package/public/src/views/export.js.map +1 -0
- package/public/src/views/export.ts +274 -0
- package/public/src/views/github.js +360 -0
- package/public/src/views/github.js.map +1 -0
- package/public/src/views/github.ts +308 -0
- package/public/src/views/graph-canvas.js +1986 -0
- package/public/src/views/graph-canvas.js.map +1 -0
- package/public/src/views/graph-canvas.ts +2218 -0
- package/public/src/views/graph.js +1824 -0
- package/public/src/views/graph.js.map +1 -0
- package/public/src/views/graph.ts +1891 -0
- package/public/src/views/groups.js +186 -0
- package/public/src/views/groups.js.map +1 -0
- package/public/src/views/groups.ts +172 -0
- package/public/src/views/guide.js +151 -0
- package/public/src/views/guide.js.map +1 -0
- package/public/src/views/guide.ts +162 -0
- package/public/src/views/health.js +105 -0
- package/public/src/views/health.js.map +1 -0
- package/public/src/views/health.ts +102 -0
- package/public/src/views/items.js +1306 -0
- package/public/src/views/items.js.map +1 -0
- package/public/src/views/items.ts +1196 -0
- package/public/src/views/normalize.js +67 -0
- package/public/src/views/normalize.js.map +1 -0
- package/public/src/views/normalize.ts +58 -0
- package/public/src/views/plan.js +454 -0
- package/public/src/views/plan.js.map +1 -0
- package/public/src/views/plan.ts +496 -0
- package/public/src/views/projects.js +204 -0
- package/public/src/views/projects.js.map +1 -0
- package/public/src/views/projects.ts +196 -0
- package/public/src/views/router.js +227 -0
- package/public/src/views/router.js.map +1 -0
- package/public/src/views/router.ts +188 -0
- package/public/src/views/search.js +103 -0
- package/public/src/views/search.js.map +1 -0
- package/public/src/views/search.ts +94 -0
- package/public/src/views/settings.js +272 -0
- package/public/src/views/settings.js.map +1 -0
- package/public/src/views/settings.ts +190 -0
- package/public/src/views/shared.js +49 -0
- package/public/src/views/shared.js.map +1 -0
- package/public/src/views/shared.ts +49 -0
- package/public/src/views/sharing.js +152 -0
- package/public/src/views/sharing.js.map +1 -0
- package/public/src/views/sharing.ts +139 -0
- package/public/src/views/stats.js +92 -0
- package/public/src/views/stats.js.map +1 -0
- package/public/src/views/stats.ts +88 -0
- package/public/src/views/templates.js +117 -0
- package/public/src/views/templates.js.map +1 -0
- package/public/src/views/templates.ts +113 -0
- package/public/src/views/validate.js +54 -0
- package/public/src/views/validate.js.map +1 -0
- package/public/src/views/validate.ts +48 -0
- package/public/styles.css +2231 -0
- package/public/sw.js +318 -0
- package/public/tsconfig.json +20 -0
- package/sql/schema.sql +105 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2
|
+
// CREATE ITEM VIEW
|
|
3
|
+
// ═══════════════════════════════════════════════════════════════
|
|
4
|
+
import { state } from '../state.js';
|
|
5
|
+
import { api } from '../api.js';
|
|
6
|
+
import { escHtml } from '../utils.js';
|
|
7
|
+
import { toast } from '../components/toast.js';
|
|
8
|
+
import { getTypes, TYPE_ICONS, PRIORITY_LABELS } from '../constants.js';
|
|
9
|
+
import { showView } from './router.js';
|
|
10
|
+
import { loadItemsBadge } from './projects.js';
|
|
11
|
+
import { openItemDetail } from './items.js';
|
|
12
|
+
export function renderCreateView() {
|
|
13
|
+
const el = document.getElementById('content-create');
|
|
14
|
+
if (!el)
|
|
15
|
+
return;
|
|
16
|
+
if (!state.currentProject) {
|
|
17
|
+
el.innerHTML = '<div class="empty-state"><div class="empty-state-text">No project selected</div></div>';
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
el.innerHTML = `
|
|
21
|
+
<div class="page-header">
|
|
22
|
+
<div><div class="page-title">Create Item</div><div class="page-subtitle">Add a new item to ${escHtml(state.currentProject.name)}</div></div>
|
|
23
|
+
</div>
|
|
24
|
+
<div class="card" style="max-width:620px">
|
|
25
|
+
<div class="card-body">
|
|
26
|
+
<form id="create-item-form" onsubmit="window.__app.submitCreateItem(event)">
|
|
27
|
+
<div class="form-group">
|
|
28
|
+
<label class="form-label">Title *</label>
|
|
29
|
+
<input class="form-input" id="ci-title" type="text" placeholder="Brief description of the item" required autofocus>
|
|
30
|
+
</div>
|
|
31
|
+
<div class="two-col">
|
|
32
|
+
<div class="form-group">
|
|
33
|
+
<label class="form-label">Type</label>
|
|
34
|
+
<select class="form-select" id="ci-type">
|
|
35
|
+
${getTypes(state.schema).map(t => `<option value="${t}"${t === 'Task' ? ' selected' : ''}>${TYPE_ICONS[t] || ''} ${t}</option>`).join('')}
|
|
36
|
+
</select>
|
|
37
|
+
</div>
|
|
38
|
+
<div class="form-group">
|
|
39
|
+
<label class="form-label">Priority</label>
|
|
40
|
+
<select class="form-select" id="ci-priority">
|
|
41
|
+
${[0, 1, 2, 3, 4].map(p => `<option value="${p}"${p === 2 ? ' selected' : ''}>P${p}: ${PRIORITY_LABELS[p]}</option>`).join('')}
|
|
42
|
+
</select>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
<div class="form-group">
|
|
46
|
+
<label class="form-label">Description</label>
|
|
47
|
+
<textarea class="form-textarea" id="ci-desc" placeholder="Detailed description (optional)" rows="5"></textarea>
|
|
48
|
+
</div>
|
|
49
|
+
<div class="two-col">
|
|
50
|
+
<div class="form-group">
|
|
51
|
+
<label class="form-label">Tags</label>
|
|
52
|
+
<input class="form-input" id="ci-tags" type="text" placeholder="comma, separated, tags">
|
|
53
|
+
</div>
|
|
54
|
+
<div class="form-group">
|
|
55
|
+
<label class="form-label">Parent Item ID</label>
|
|
56
|
+
<input class="form-input" id="ci-parent" type="text" placeholder="${escHtml(state.currentProject.prefix)}-1">
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
<div class="two-col">
|
|
60
|
+
<div class="form-group">
|
|
61
|
+
<label class="form-label">Deadline</label>
|
|
62
|
+
<input class="form-input" id="ci-deadline" type="text" placeholder="+1d, +1w, 2026-06-01">
|
|
63
|
+
</div>
|
|
64
|
+
<div class="form-group">
|
|
65
|
+
<label class="form-label">Assignee</label>
|
|
66
|
+
<input class="form-input" id="ci-assignee" type="text" placeholder="username or email">
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
<div class="two-col">
|
|
70
|
+
<div class="form-group">
|
|
71
|
+
<label class="form-label">Sprint</label>
|
|
72
|
+
<input class="form-input" id="ci-sprint" type="text" placeholder="sprint-1">
|
|
73
|
+
</div>
|
|
74
|
+
<div class="form-group">
|
|
75
|
+
<label class="form-label">Release</label>
|
|
76
|
+
<input class="form-input" id="ci-release" type="text" placeholder="v1.0.0">
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
<div class="form-group">
|
|
80
|
+
<label class="form-label">Estimated Minutes</label>
|
|
81
|
+
<input class="form-input" id="ci-estimate" type="number" min="1" placeholder="e.g. 60">
|
|
82
|
+
</div>
|
|
83
|
+
<div class="form-group">
|
|
84
|
+
<label class="form-label">Acceptance Criteria</label>
|
|
85
|
+
<textarea class="form-textarea" id="ci-acceptance-criteria" placeholder="What conditions must be met?" rows="2"></textarea>
|
|
86
|
+
</div>
|
|
87
|
+
<details style="margin-top:8px;margin-bottom:4px">
|
|
88
|
+
<summary style="cursor:pointer;color:var(--text-secondary);font-size:13px;padding:4px 0;user-select:none">▸ Advanced fields</summary>
|
|
89
|
+
<div style="margin-top:12px;display:flex;flex-direction:column;gap:0">
|
|
90
|
+
<div class="form-group">
|
|
91
|
+
<label class="form-label">Body (Markdown)</label>
|
|
92
|
+
<textarea class="form-textarea" id="ci-body" placeholder="Full body / notes in markdown" rows="3"></textarea>
|
|
93
|
+
</div>
|
|
94
|
+
<div class="two-col">
|
|
95
|
+
<div class="form-group">
|
|
96
|
+
<label class="form-label">Reporter</label>
|
|
97
|
+
<input class="form-input" id="ci-reporter" type="text" placeholder="who reported this">
|
|
98
|
+
</div>
|
|
99
|
+
<div class="form-group">
|
|
100
|
+
<label class="form-label">Component</label>
|
|
101
|
+
<input class="form-input" id="ci-component" type="text" placeholder="e.g. auth, api, ui">
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
<div class="two-col">
|
|
105
|
+
<div class="form-group">
|
|
106
|
+
<label class="form-label">Severity</label>
|
|
107
|
+
<select class="form-select" id="ci-severity">
|
|
108
|
+
<option value="">— none —</option>
|
|
109
|
+
<option value="critical">Critical</option>
|
|
110
|
+
<option value="high">High</option>
|
|
111
|
+
<option value="medium">Medium</option>
|
|
112
|
+
<option value="low">Low</option>
|
|
113
|
+
</select>
|
|
114
|
+
</div>
|
|
115
|
+
<div class="form-group">
|
|
116
|
+
<label class="form-label">Risk</label>
|
|
117
|
+
<select class="form-select" id="ci-risk">
|
|
118
|
+
<option value="">— none —</option>
|
|
119
|
+
<option value="high">High</option>
|
|
120
|
+
<option value="medium">Medium</option>
|
|
121
|
+
<option value="low">Low</option>
|
|
122
|
+
</select>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
<div class="form-group">
|
|
126
|
+
<label class="form-label">Goal / Objective</label>
|
|
127
|
+
<input class="form-input" id="ci-goal" type="text" placeholder="What business goal does this serve?">
|
|
128
|
+
</div>
|
|
129
|
+
<div class="two-col">
|
|
130
|
+
<div class="form-group">
|
|
131
|
+
<label class="form-label">Environment</label>
|
|
132
|
+
<input class="form-input" id="ci-environment" type="text" placeholder="e.g. production, staging">
|
|
133
|
+
</div>
|
|
134
|
+
<div class="form-group">
|
|
135
|
+
<label class="form-label">Blocked By</label>
|
|
136
|
+
<input class="form-input" id="ci-blocked-by" type="text" placeholder="item ID blocking this">
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
<div class="two-col">
|
|
140
|
+
<div class="form-group">
|
|
141
|
+
<label class="form-label">Repro Steps</label>
|
|
142
|
+
<textarea class="form-textarea" id="ci-repro-steps" placeholder="Steps to reproduce" rows="2"></textarea>
|
|
143
|
+
</div>
|
|
144
|
+
<div class="form-group">
|
|
145
|
+
<label class="form-label">Expected Result</label>
|
|
146
|
+
<textarea class="form-textarea" id="ci-expected-result" placeholder="What should happen" rows="2"></textarea>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
<div class="form-group">
|
|
150
|
+
<label class="form-label">Blocked Reason</label>
|
|
151
|
+
<textarea class="form-textarea" id="ci-blocked-reason" placeholder="Why is this item blocked? (optional)" rows="2"></textarea>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
</details>
|
|
155
|
+
<div class="form-error" id="ci-error" style="display:none"></div>
|
|
156
|
+
<div style="display:flex;gap:8px;margin-top:8px;flex-wrap:wrap">
|
|
157
|
+
<button type="submit" class="btn btn-primary" id="ci-submit"><span>Create Item</span></button>
|
|
158
|
+
<button type="button" class="btn btn-secondary" id="ci-submit-open" onclick="window.__app.submitCreateItemAndOpen(event)"><span>Create & Open</span></button>
|
|
159
|
+
<button type="button" class="btn btn-ghost" onclick="window.__app.showView('items')">Cancel</button>
|
|
160
|
+
</div>
|
|
161
|
+
</form>
|
|
162
|
+
</div>
|
|
163
|
+
</div>`;
|
|
164
|
+
}
|
|
165
|
+
export async function submitCreateItemAndOpen(e) {
|
|
166
|
+
e.preventDefault();
|
|
167
|
+
await submitCreateItem(e, true);
|
|
168
|
+
}
|
|
169
|
+
export async function submitCreateItem(e, openAfter = false) {
|
|
170
|
+
e.preventDefault();
|
|
171
|
+
const val = (id) => document.getElementById(id)?.value?.trim() || '';
|
|
172
|
+
const title = val('ci-title');
|
|
173
|
+
const type = val('ci-type');
|
|
174
|
+
const priority = val('ci-priority');
|
|
175
|
+
const description = val('ci-desc');
|
|
176
|
+
const tags = val('ci-tags');
|
|
177
|
+
const parent = val('ci-parent');
|
|
178
|
+
const deadline = val('ci-deadline');
|
|
179
|
+
const assignee = val('ci-assignee');
|
|
180
|
+
const sprint = val('ci-sprint');
|
|
181
|
+
const release = val('ci-release');
|
|
182
|
+
const estimate = val('ci-estimate');
|
|
183
|
+
const acceptanceCriteria = val('ci-acceptance-criteria');
|
|
184
|
+
const body = val('ci-body');
|
|
185
|
+
const reporter = val('ci-reporter');
|
|
186
|
+
const component = val('ci-component');
|
|
187
|
+
const severity = val('ci-severity');
|
|
188
|
+
const risk = val('ci-risk');
|
|
189
|
+
const goal = val('ci-goal');
|
|
190
|
+
const environment = val('ci-environment');
|
|
191
|
+
const blockedBy = val('ci-blocked-by');
|
|
192
|
+
const reproSteps = val('ci-repro-steps');
|
|
193
|
+
const expectedResult = val('ci-expected-result');
|
|
194
|
+
const blockedReason = val('ci-blocked-reason');
|
|
195
|
+
const errEl = document.getElementById('ci-error');
|
|
196
|
+
const btn = document.getElementById('ci-submit');
|
|
197
|
+
if (errEl)
|
|
198
|
+
errEl.style.display = 'none';
|
|
199
|
+
if (!title) {
|
|
200
|
+
if (errEl) {
|
|
201
|
+
errEl.textContent = 'Title is required';
|
|
202
|
+
errEl.style.display = 'block';
|
|
203
|
+
}
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const btnOpen = document.getElementById('ci-submit-open');
|
|
207
|
+
if (btn) {
|
|
208
|
+
btn.disabled = true;
|
|
209
|
+
const sp = btn.querySelector('span');
|
|
210
|
+
if (sp)
|
|
211
|
+
sp.textContent = 'Creating…';
|
|
212
|
+
}
|
|
213
|
+
if (btnOpen)
|
|
214
|
+
btnOpen.disabled = true;
|
|
215
|
+
try {
|
|
216
|
+
const bodyData = { title, type, priority };
|
|
217
|
+
if (description)
|
|
218
|
+
bodyData.description = description;
|
|
219
|
+
if (tags)
|
|
220
|
+
bodyData.tags = tags;
|
|
221
|
+
if (parent)
|
|
222
|
+
bodyData.parent = parent;
|
|
223
|
+
if (deadline)
|
|
224
|
+
bodyData.deadline = deadline;
|
|
225
|
+
if (assignee)
|
|
226
|
+
bodyData.assignee = assignee;
|
|
227
|
+
if (sprint)
|
|
228
|
+
bodyData.sprint = sprint;
|
|
229
|
+
if (release)
|
|
230
|
+
bodyData.release = release;
|
|
231
|
+
if (estimate)
|
|
232
|
+
bodyData.estimate = estimate;
|
|
233
|
+
if (acceptanceCriteria)
|
|
234
|
+
bodyData.acceptanceCriteria = acceptanceCriteria;
|
|
235
|
+
if (body)
|
|
236
|
+
bodyData.body = body;
|
|
237
|
+
if (reporter)
|
|
238
|
+
bodyData.reporter = reporter;
|
|
239
|
+
if (component)
|
|
240
|
+
bodyData.component = component;
|
|
241
|
+
if (severity)
|
|
242
|
+
bodyData.severity = severity;
|
|
243
|
+
if (risk)
|
|
244
|
+
bodyData.risk = risk;
|
|
245
|
+
if (goal)
|
|
246
|
+
bodyData.goal = goal;
|
|
247
|
+
if (environment)
|
|
248
|
+
bodyData.environment = environment;
|
|
249
|
+
if (blockedBy)
|
|
250
|
+
bodyData['blocked-by'] = blockedBy;
|
|
251
|
+
if (reproSteps)
|
|
252
|
+
bodyData['repro-steps'] = reproSteps;
|
|
253
|
+
if (expectedResult)
|
|
254
|
+
bodyData['expected-result'] = expectedResult;
|
|
255
|
+
if (blockedReason)
|
|
256
|
+
bodyData['blocked-reason'] = blockedReason;
|
|
257
|
+
const data = await api('POST', `/projects/${state.currentProject.id}/pm/create`, bodyData);
|
|
258
|
+
const newId = data.item?.id || data.id || '';
|
|
259
|
+
toast(`Created ${newId || 'item'}!`, 'success');
|
|
260
|
+
const form = document.getElementById('create-item-form');
|
|
261
|
+
if (form)
|
|
262
|
+
form.reset();
|
|
263
|
+
const typeEl = document.getElementById('ci-type');
|
|
264
|
+
if (typeEl)
|
|
265
|
+
typeEl.value = 'Task';
|
|
266
|
+
const priEl = document.getElementById('ci-priority');
|
|
267
|
+
if (priEl)
|
|
268
|
+
priEl.value = '2';
|
|
269
|
+
loadItemsBadge();
|
|
270
|
+
if (openAfter && newId) {
|
|
271
|
+
showView('items');
|
|
272
|
+
openItemDetail(newId);
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
showView('items');
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
catch (err) {
|
|
279
|
+
if (errEl) {
|
|
280
|
+
errEl.textContent = err instanceof Error ? err.message : String(err);
|
|
281
|
+
errEl.style.display = 'block';
|
|
282
|
+
}
|
|
283
|
+
if (btn) {
|
|
284
|
+
btn.disabled = false;
|
|
285
|
+
const sp = btn.querySelector('span');
|
|
286
|
+
if (sp)
|
|
287
|
+
sp.textContent = 'Create Item';
|
|
288
|
+
}
|
|
289
|
+
if (btnOpen)
|
|
290
|
+
btnOpen.disabled = false;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
//# sourceMappingURL=create.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create.js","sourceRoot":"","sources":["create.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2
|
+
// CREATE ITEM VIEW
|
|
3
|
+
// ═══════════════════════════════════════════════════════════════
|
|
4
|
+
import { state } from '../state.js';
|
|
5
|
+
import { api } from '../api.js';
|
|
6
|
+
import { escHtml } from '../utils.js';
|
|
7
|
+
import { toast } from '../components/toast.js';
|
|
8
|
+
import { getTypes, TYPE_ICONS, PRIORITY_LABELS } from '../constants.js';
|
|
9
|
+
import { showView } from './router.js';
|
|
10
|
+
import { loadItemsBadge } from './projects.js';
|
|
11
|
+
import { openItemDetail } from './items.js';
|
|
12
|
+
|
|
13
|
+
export function renderCreateView(): void {
|
|
14
|
+
const el = document.getElementById('content-create');
|
|
15
|
+
if (!el) return;
|
|
16
|
+
if (!state.currentProject) { el.innerHTML = '<div class="empty-state"><div class="empty-state-text">No project selected</div></div>'; return; }
|
|
17
|
+
el.innerHTML = `
|
|
18
|
+
<div class="page-header">
|
|
19
|
+
<div><div class="page-title">Create Item</div><div class="page-subtitle">Add a new item to ${escHtml(state.currentProject.name)}</div></div>
|
|
20
|
+
</div>
|
|
21
|
+
<div class="card" style="max-width:620px">
|
|
22
|
+
<div class="card-body">
|
|
23
|
+
<form id="create-item-form" onsubmit="window.__app.submitCreateItem(event)">
|
|
24
|
+
<div class="form-group">
|
|
25
|
+
<label class="form-label">Title *</label>
|
|
26
|
+
<input class="form-input" id="ci-title" type="text" placeholder="Brief description of the item" required autofocus>
|
|
27
|
+
</div>
|
|
28
|
+
<div class="two-col">
|
|
29
|
+
<div class="form-group">
|
|
30
|
+
<label class="form-label">Type</label>
|
|
31
|
+
<select class="form-select" id="ci-type">
|
|
32
|
+
${getTypes(state.schema).map(t=>`<option value="${t}"${t==='Task'?' selected':''}>${TYPE_ICONS[t]||''} ${t}</option>`).join('')}
|
|
33
|
+
</select>
|
|
34
|
+
</div>
|
|
35
|
+
<div class="form-group">
|
|
36
|
+
<label class="form-label">Priority</label>
|
|
37
|
+
<select class="form-select" id="ci-priority">
|
|
38
|
+
${[0,1,2,3,4].map(p=>`<option value="${p}"${p===2?' selected':''}>P${p}: ${PRIORITY_LABELS[p]}</option>`).join('')}
|
|
39
|
+
</select>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
<div class="form-group">
|
|
43
|
+
<label class="form-label">Description</label>
|
|
44
|
+
<textarea class="form-textarea" id="ci-desc" placeholder="Detailed description (optional)" rows="5"></textarea>
|
|
45
|
+
</div>
|
|
46
|
+
<div class="two-col">
|
|
47
|
+
<div class="form-group">
|
|
48
|
+
<label class="form-label">Tags</label>
|
|
49
|
+
<input class="form-input" id="ci-tags" type="text" placeholder="comma, separated, tags">
|
|
50
|
+
</div>
|
|
51
|
+
<div class="form-group">
|
|
52
|
+
<label class="form-label">Parent Item ID</label>
|
|
53
|
+
<input class="form-input" id="ci-parent" type="text" placeholder="${escHtml(state.currentProject.prefix)}-1">
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
<div class="two-col">
|
|
57
|
+
<div class="form-group">
|
|
58
|
+
<label class="form-label">Deadline</label>
|
|
59
|
+
<input class="form-input" id="ci-deadline" type="text" placeholder="+1d, +1w, 2026-06-01">
|
|
60
|
+
</div>
|
|
61
|
+
<div class="form-group">
|
|
62
|
+
<label class="form-label">Assignee</label>
|
|
63
|
+
<input class="form-input" id="ci-assignee" type="text" placeholder="username or email">
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
<div class="two-col">
|
|
67
|
+
<div class="form-group">
|
|
68
|
+
<label class="form-label">Sprint</label>
|
|
69
|
+
<input class="form-input" id="ci-sprint" type="text" placeholder="sprint-1">
|
|
70
|
+
</div>
|
|
71
|
+
<div class="form-group">
|
|
72
|
+
<label class="form-label">Release</label>
|
|
73
|
+
<input class="form-input" id="ci-release" type="text" placeholder="v1.0.0">
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
<div class="form-group">
|
|
77
|
+
<label class="form-label">Estimated Minutes</label>
|
|
78
|
+
<input class="form-input" id="ci-estimate" type="number" min="1" placeholder="e.g. 60">
|
|
79
|
+
</div>
|
|
80
|
+
<div class="form-group">
|
|
81
|
+
<label class="form-label">Acceptance Criteria</label>
|
|
82
|
+
<textarea class="form-textarea" id="ci-acceptance-criteria" placeholder="What conditions must be met?" rows="2"></textarea>
|
|
83
|
+
</div>
|
|
84
|
+
<details style="margin-top:8px;margin-bottom:4px">
|
|
85
|
+
<summary style="cursor:pointer;color:var(--text-secondary);font-size:13px;padding:4px 0;user-select:none">▸ Advanced fields</summary>
|
|
86
|
+
<div style="margin-top:12px;display:flex;flex-direction:column;gap:0">
|
|
87
|
+
<div class="form-group">
|
|
88
|
+
<label class="form-label">Body (Markdown)</label>
|
|
89
|
+
<textarea class="form-textarea" id="ci-body" placeholder="Full body / notes in markdown" rows="3"></textarea>
|
|
90
|
+
</div>
|
|
91
|
+
<div class="two-col">
|
|
92
|
+
<div class="form-group">
|
|
93
|
+
<label class="form-label">Reporter</label>
|
|
94
|
+
<input class="form-input" id="ci-reporter" type="text" placeholder="who reported this">
|
|
95
|
+
</div>
|
|
96
|
+
<div class="form-group">
|
|
97
|
+
<label class="form-label">Component</label>
|
|
98
|
+
<input class="form-input" id="ci-component" type="text" placeholder="e.g. auth, api, ui">
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
<div class="two-col">
|
|
102
|
+
<div class="form-group">
|
|
103
|
+
<label class="form-label">Severity</label>
|
|
104
|
+
<select class="form-select" id="ci-severity">
|
|
105
|
+
<option value="">— none —</option>
|
|
106
|
+
<option value="critical">Critical</option>
|
|
107
|
+
<option value="high">High</option>
|
|
108
|
+
<option value="medium">Medium</option>
|
|
109
|
+
<option value="low">Low</option>
|
|
110
|
+
</select>
|
|
111
|
+
</div>
|
|
112
|
+
<div class="form-group">
|
|
113
|
+
<label class="form-label">Risk</label>
|
|
114
|
+
<select class="form-select" id="ci-risk">
|
|
115
|
+
<option value="">— none —</option>
|
|
116
|
+
<option value="high">High</option>
|
|
117
|
+
<option value="medium">Medium</option>
|
|
118
|
+
<option value="low">Low</option>
|
|
119
|
+
</select>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
<div class="form-group">
|
|
123
|
+
<label class="form-label">Goal / Objective</label>
|
|
124
|
+
<input class="form-input" id="ci-goal" type="text" placeholder="What business goal does this serve?">
|
|
125
|
+
</div>
|
|
126
|
+
<div class="two-col">
|
|
127
|
+
<div class="form-group">
|
|
128
|
+
<label class="form-label">Environment</label>
|
|
129
|
+
<input class="form-input" id="ci-environment" type="text" placeholder="e.g. production, staging">
|
|
130
|
+
</div>
|
|
131
|
+
<div class="form-group">
|
|
132
|
+
<label class="form-label">Blocked By</label>
|
|
133
|
+
<input class="form-input" id="ci-blocked-by" type="text" placeholder="item ID blocking this">
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
<div class="two-col">
|
|
137
|
+
<div class="form-group">
|
|
138
|
+
<label class="form-label">Repro Steps</label>
|
|
139
|
+
<textarea class="form-textarea" id="ci-repro-steps" placeholder="Steps to reproduce" rows="2"></textarea>
|
|
140
|
+
</div>
|
|
141
|
+
<div class="form-group">
|
|
142
|
+
<label class="form-label">Expected Result</label>
|
|
143
|
+
<textarea class="form-textarea" id="ci-expected-result" placeholder="What should happen" rows="2"></textarea>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
<div class="form-group">
|
|
147
|
+
<label class="form-label">Blocked Reason</label>
|
|
148
|
+
<textarea class="form-textarea" id="ci-blocked-reason" placeholder="Why is this item blocked? (optional)" rows="2"></textarea>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
</details>
|
|
152
|
+
<div class="form-error" id="ci-error" style="display:none"></div>
|
|
153
|
+
<div style="display:flex;gap:8px;margin-top:8px;flex-wrap:wrap">
|
|
154
|
+
<button type="submit" class="btn btn-primary" id="ci-submit"><span>Create Item</span></button>
|
|
155
|
+
<button type="button" class="btn btn-secondary" id="ci-submit-open" onclick="window.__app.submitCreateItemAndOpen(event)"><span>Create & Open</span></button>
|
|
156
|
+
<button type="button" class="btn btn-ghost" onclick="window.__app.showView('items')">Cancel</button>
|
|
157
|
+
</div>
|
|
158
|
+
</form>
|
|
159
|
+
</div>
|
|
160
|
+
</div>`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export async function submitCreateItemAndOpen(e: Event): Promise<void> {
|
|
164
|
+
e.preventDefault();
|
|
165
|
+
await submitCreateItem(e, true);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export async function submitCreateItem(e: Event, openAfter = false): Promise<void> {
|
|
169
|
+
e.preventDefault();
|
|
170
|
+
const val = (id: string) => (document.getElementById(id) as HTMLInputElement | HTMLTextAreaElement | null)?.value?.trim() || '';
|
|
171
|
+
const title = val('ci-title');
|
|
172
|
+
const type = val('ci-type');
|
|
173
|
+
const priority = val('ci-priority');
|
|
174
|
+
const description = val('ci-desc');
|
|
175
|
+
const tags = val('ci-tags');
|
|
176
|
+
const parent = val('ci-parent');
|
|
177
|
+
const deadline = val('ci-deadline');
|
|
178
|
+
const assignee = val('ci-assignee');
|
|
179
|
+
const sprint = val('ci-sprint');
|
|
180
|
+
const release = val('ci-release');
|
|
181
|
+
const estimate = val('ci-estimate');
|
|
182
|
+
const acceptanceCriteria = val('ci-acceptance-criteria');
|
|
183
|
+
const body = val('ci-body');
|
|
184
|
+
const reporter = val('ci-reporter');
|
|
185
|
+
const component = val('ci-component');
|
|
186
|
+
const severity = val('ci-severity');
|
|
187
|
+
const risk = val('ci-risk');
|
|
188
|
+
const goal = val('ci-goal');
|
|
189
|
+
const environment = val('ci-environment');
|
|
190
|
+
const blockedBy = val('ci-blocked-by');
|
|
191
|
+
const reproSteps = val('ci-repro-steps');
|
|
192
|
+
const expectedResult = val('ci-expected-result');
|
|
193
|
+
const blockedReason = val('ci-blocked-reason');
|
|
194
|
+
const errEl = document.getElementById('ci-error') as HTMLElement | null;
|
|
195
|
+
const btn = document.getElementById('ci-submit') as HTMLButtonElement | null;
|
|
196
|
+
if (errEl) errEl.style.display = 'none';
|
|
197
|
+
|
|
198
|
+
if (!title) { if (errEl) { errEl.textContent = 'Title is required'; errEl.style.display='block'; } return; }
|
|
199
|
+
|
|
200
|
+
const btnOpen = document.getElementById('ci-submit-open') as HTMLButtonElement | null;
|
|
201
|
+
if (btn) { btn.disabled = true; const sp = btn.querySelector('span'); if (sp) sp.textContent = 'Creating…'; }
|
|
202
|
+
if (btnOpen) btnOpen.disabled = true;
|
|
203
|
+
try {
|
|
204
|
+
const bodyData: Record<string, string> = {title,type,priority};
|
|
205
|
+
if (description) bodyData.description = description;
|
|
206
|
+
if (tags) bodyData.tags = tags;
|
|
207
|
+
if (parent) bodyData.parent = parent;
|
|
208
|
+
if (deadline) bodyData.deadline = deadline;
|
|
209
|
+
if (assignee) bodyData.assignee = assignee;
|
|
210
|
+
if (sprint) bodyData.sprint = sprint;
|
|
211
|
+
if (release) bodyData.release = release;
|
|
212
|
+
if (estimate) bodyData.estimate = estimate;
|
|
213
|
+
if (acceptanceCriteria) bodyData.acceptanceCriteria = acceptanceCriteria;
|
|
214
|
+
if (body) bodyData.body = body;
|
|
215
|
+
if (reporter) bodyData.reporter = reporter;
|
|
216
|
+
if (component) bodyData.component = component;
|
|
217
|
+
if (severity) bodyData.severity = severity;
|
|
218
|
+
if (risk) bodyData.risk = risk;
|
|
219
|
+
if (goal) bodyData.goal = goal;
|
|
220
|
+
if (environment) bodyData.environment = environment;
|
|
221
|
+
if (blockedBy) bodyData['blocked-by'] = blockedBy;
|
|
222
|
+
if (reproSteps) bodyData['repro-steps'] = reproSteps;
|
|
223
|
+
if (expectedResult) bodyData['expected-result'] = expectedResult;
|
|
224
|
+
if (blockedReason) bodyData['blocked-reason'] = blockedReason;
|
|
225
|
+
const data = await api('POST',`/projects/${state.currentProject!.id}/pm/create`,bodyData);
|
|
226
|
+
const newId: string = (data as any).item?.id || (data as any).id || '';
|
|
227
|
+
toast(`Created ${newId || 'item'}!`,'success');
|
|
228
|
+
const form = document.getElementById('create-item-form') as HTMLFormElement | null;
|
|
229
|
+
if (form) form.reset();
|
|
230
|
+
const typeEl = document.getElementById('ci-type') as HTMLSelectElement | null;
|
|
231
|
+
if (typeEl) typeEl.value = 'Task';
|
|
232
|
+
const priEl = document.getElementById('ci-priority') as HTMLSelectElement | null;
|
|
233
|
+
if (priEl) priEl.value = '2';
|
|
234
|
+
loadItemsBadge();
|
|
235
|
+
if (openAfter && newId) {
|
|
236
|
+
showView('items');
|
|
237
|
+
openItemDetail(newId);
|
|
238
|
+
} else {
|
|
239
|
+
showView('items');
|
|
240
|
+
}
|
|
241
|
+
} catch(err: unknown) {
|
|
242
|
+
if (errEl) { errEl.textContent = err instanceof Error ? err.message : String(err); errEl.style.display = 'block'; }
|
|
243
|
+
if (btn) { btn.disabled = false; const sp = btn.querySelector('span'); if (sp) sp.textContent = 'Create Item'; }
|
|
244
|
+
if (btnOpen) btnOpen.disabled = false;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2
|
+
// DEDUPE AUDIT VIEW
|
|
3
|
+
// ═══════════════════════════════════════════════════════════════
|
|
4
|
+
import { state } from '../state.js';
|
|
5
|
+
import { api } from '../api.js';
|
|
6
|
+
import { escHtml, typeIcon, statusBadge } from '../utils.js';
|
|
7
|
+
export async function renderDedupeAuditView() {
|
|
8
|
+
const el = document.getElementById('content-dedupe');
|
|
9
|
+
if (!el)
|
|
10
|
+
return;
|
|
11
|
+
if (!state.currentProject) {
|
|
12
|
+
el.innerHTML = '<div class="empty-state"><div class="empty-state-text">No project selected</div></div>';
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
el.innerHTML = `
|
|
16
|
+
<div class="page-header">
|
|
17
|
+
<div><div class="page-title">Dedupe Audit</div><div class="page-subtitle">Find potential duplicate items in ${escHtml(state.currentProject.name)}</div></div>
|
|
18
|
+
<div class="page-actions"><button class="btn btn-secondary btn-sm" onclick="window.__app.renderDedupeAuditView()">↺ Refresh</button></div>
|
|
19
|
+
</div>
|
|
20
|
+
<div id="dedupe-content"><div class="loading-state"><div class="loading-spinner"></div></div></div>`;
|
|
21
|
+
try {
|
|
22
|
+
const data = await api('GET', `/projects/${state.currentProject.id}/pm/dedupe-audit`);
|
|
23
|
+
const groups = data.groups || data.duplicates || [];
|
|
24
|
+
const el2 = document.getElementById('dedupe-content');
|
|
25
|
+
if (!el2)
|
|
26
|
+
return;
|
|
27
|
+
if (groups.length === 0) {
|
|
28
|
+
el2.innerHTML = `<div class="card"><div class="card-body"><div style="color:var(--status-closed);font-size:13px">✓ No potential duplicates found — project looks clean!</div></div></div>`;
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
el2.innerHTML = groups.map((g, i) => `
|
|
32
|
+
<div class="card" style="margin-bottom:12px">
|
|
33
|
+
<div class="card-header"><div class="card-title">Potential Duplicate Group ${i + 1} ${g.score !== undefined ? `<span style="font-size:11px;color:var(--text-muted)">· ${Math.round((g.score || 0) * 100)}% similarity</span>` : ''}</div></div>
|
|
34
|
+
<div class="card-body">
|
|
35
|
+
${(g.items || []).map((item) => `
|
|
36
|
+
<div class="item-row" onclick="window.__app.openItemDetail('${escHtml(item.id || item)}')" style="cursor:pointer">
|
|
37
|
+
${typeIcon(item.type || '')} <span class="item-id">${escHtml(item.id || item)}</span>
|
|
38
|
+
<span class="item-title">${escHtml(item.title || '')}</span>
|
|
39
|
+
<div class="item-meta">${statusBadge(item.status || 'draft')}</div>
|
|
40
|
+
</div>`).join('')}
|
|
41
|
+
</div>
|
|
42
|
+
</div>`).join('') || `<div class="card"><div class="card-body"><pre style="font-size:12px;color:var(--text-secondary);white-space:pre-wrap">${escHtml(JSON.stringify(data, null, 2))}</pre></div></div>`;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
const el2 = document.getElementById('dedupe-content');
|
|
47
|
+
if (el2)
|
|
48
|
+
el2.innerHTML = `<div class="empty-state"><div class="empty-state-text">Error: ${escHtml(err instanceof Error ? err.message : String(err))}</div></div>`;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=dedupe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dedupe.js","sourceRoot":"","sources":["dedupe.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,oBAAoB;AACpB,kEAAkE;AAClE,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAChC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE7D,MAAM,CAAC,KAAK,UAAU,qBAAqB;IACzC,MAAM,EAAE,GAAG,QAAQ,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAC;IACrD,IAAI,CAAC,EAAE;QAAE,OAAO;IAChB,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE,CAAC;QAAC,EAAE,CAAC,SAAS,GAAG,wFAAwF,CAAC;QAAC,OAAO;IAAC,CAAC;IAC/I,EAAE,CAAC,SAAS,GAAG;;oHAEmG,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,IAAI,CAAC;;;wGAG9C,CAAC;IACvG,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,CAAC,cAAc,CAAC,EAAE,kBAAkB,CAAC,CAAC;QACtF,MAAM,MAAM,GAAI,IAAY,CAAC,MAAM,IAAK,IAAY,CAAC,UAAU,IAAI,EAAE,CAAC;QACtE,MAAM,GAAG,GAAG,QAAQ,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAC;QACtD,IAAI,CAAC,GAAG;YAAE,OAAO;QACjB,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxB,GAAG,CAAC,SAAS,GAAG,0KAA0K,CAAC;QAC7L,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,CAAS,EAAE,EAAE,CAAC;;uFAE+B,CAAC,GAAC,CAAC,IAAI,CAAC,CAAC,KAAK,KAAG,SAAS,CAAA,CAAC,CAAA,0DAA0D,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,IAAE,CAAC,CAAC,GAAC,GAAG,CAAC,qBAAqB,CAAA,CAAC,CAAA,EAAE;;cAElN,CAAC,CAAC,CAAC,KAAK,IAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,IAAS,EAAC,EAAE,CAAA;4EAC+B,OAAO,CAAC,IAAI,CAAC,EAAE,IAAE,IAAI,CAAC;kBAChF,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAE,EAAE,CAAC,0BAA0B,OAAO,CAAC,IAAI,CAAC,EAAE,IAAE,IAAI,CAAC;2CAC9C,OAAO,CAAC,IAAI,CAAC,KAAK,IAAE,EAAE,CAAC;yCACzB,WAAW,CAAC,IAAI,CAAC,MAAM,IAAE,OAAO,CAAC;qBACrD,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;;eAEhB,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,yHAAyH,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAC,IAAI,EAAC,CAAC,CAAC,CAAC,oBAAoB,CAAC;QAC3M,CAAC;IACH,CAAC;IAAC,OAAM,GAAY,EAAE,CAAC;QACrB,MAAM,GAAG,GAAG,QAAQ,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAC;QACtD,IAAI,GAAG;YAAE,GAAG,CAAC,SAAS,GAAG,iEAAiE,OAAO,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,cAAc,CAAC;IACpK,CAAC;AACH,CAAC"}
|