- Settings (#/settings): API tokens (mint/list/revoke), Agents list, Orthos Mode placeholder - Per-space Projects: Void-1-style expandable cards — inline status, ↻ Research (Eithan stub), Edit/New modal, Delete-with-confirm; migration 019 adds research_status/notes/timestamps; POST /api/projects/:id/research stub; GET /api/agent-tokens list - Global +1 font bump; themed scrollbars; larger/bolder themed topbar Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
46 lines
2.2 KiB
JavaScript
46 lines
2.2 KiB
JavaScript
// Create / edit a project. Minimal modal: name, description, status.
|
|
import { el } from '../dom.js';
|
|
import { api } from '../api.js';
|
|
|
|
const STATUSES = ['idea', 'active', 'paused', 'done', 'abandoned'];
|
|
const slugify = (s) => (s.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '').slice(0, 50) || 'project');
|
|
|
|
export function openProjectModal(spaceId, project, onSaved) {
|
|
const editing = !!project;
|
|
const name = el('input', { class: 'pm-input', value: project?.name || '', placeholder: 'Project name' });
|
|
const desc = el('textarea', { class: 'pm-input', rows: 3, placeholder: 'Description (optional)' });
|
|
desc.value = project?.description || '';
|
|
const status = el('select', { class: 'pm-input' }, STATUSES.map(s => el('option', { value: s, selected: s === (project?.status || 'active') }, s)));
|
|
const errEl = el('div', { class: 'err', style: { minHeight: '15px' } }, '');
|
|
|
|
async function save() {
|
|
const nm = name.value.trim();
|
|
if (!nm) { errEl.textContent = 'Name required'; return; }
|
|
try {
|
|
if (editing) {
|
|
await api.patch('/api/projects/' + project.id, { name: nm, description: desc.value.trim() || null, status: status.value });
|
|
} else {
|
|
await api.post(`/api/spaces/${spaceId}/projects`, {
|
|
slug: slugify(nm) + '-' + Date.now().toString(36).slice(-4),
|
|
name: nm, description: desc.value.trim() || undefined, status: status.value
|
|
});
|
|
}
|
|
modal.remove();
|
|
onSaved && onSaved();
|
|
} catch (e) { errEl.textContent = 'Save failed: ' + e.message; }
|
|
}
|
|
|
|
const modal = el('div', { class: 'rev-overlay', onclick: (e) => { if (e.target === modal) modal.remove(); } },
|
|
el('div', { class: 'pm-modal' },
|
|
el('div', { class: 'pm-head' }, editing ? 'Edit project' : 'New project'),
|
|
el('label', { class: 'pm-label' }, 'Name'), name,
|
|
el('label', { class: 'pm-label' }, 'Description'), desc,
|
|
el('label', { class: 'pm-label' }, 'Status'), status,
|
|
errEl,
|
|
el('div', { class: 'pm-foot' },
|
|
el('button', { class: 'ghost', onclick: () => modal.remove() }, 'Cancel'),
|
|
el('button', { class: 'primary', onclick: save }, editing ? 'Save' : 'Create'))));
|
|
document.body.appendChild(modal);
|
|
name.focus();
|
|
}
|