rojects/source/components/project-editor/project-editor.ts

142 lines
4.8 KiB
TypeScript

import { showConfirmDialog } from '../confirm-dialog/confirm-dialog.js';
interface Project {
id: string;
name: string;
owner_id: string;
}
interface ProjectMember {
id: string;
project_id: string;
member_type: 'user' | 'group';
member_id: string;
role: string;
}
class ProjectEditor extends HTMLElement {
connectedCallback(): void {
this.render();
}
async render(): Promise<void> {
const res = await fetch('/api/projects');
const projects = await res.json() as Project[];
this.innerHTML = `
<div class="project-editor">
<h2>Projects</h2>
<form class="create-form">
<input name="name" placeholder="New project name" required>
<button type="submit">Create</button>
</form>
<ul class="project-list">
${projects.map(p => `
<li data-id="${p.id}">
<div class="project-header">
<strong>${p.name}</strong>
<a class="btn-edit" href="/editor.html?project=${p.id}&name=${encodeURIComponent(p.name)}">Edit</a>
<button class="btn-members" data-id="${p.id}">Members</button>
<button class="btn-delete" data-id="${p.id}" data-name="${p.name}">Delete</button>
</div>
<div class="members-panel" data-project="${p.id}" style="display:none"></div>
</li>
`).join('')}
</ul>
</div>
`;
this.querySelector('.create-form')!.addEventListener('submit', async (e: Event) => {
e.preventDefault();
const form = e.target as HTMLFormElement;
const name = (form.elements.namedItem('name') as HTMLInputElement).value.trim();
const res = await fetch('/api/projects', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name })
});
if (res.ok) this.render();
});
this.querySelectorAll('.btn-delete').forEach(btn => {
btn.addEventListener('click', async ( e ) => {
console.log( "Clicking:", e );
const el = btn as HTMLElement;
const ok = await showConfirmDialog({
icon: '🗑',
title: 'Delete Project',
message: `Delete "${el.dataset.name}"? This cannot be undone.`,
confirmLabel: 'Delete',
cancelLabel: 'Cancel',
danger: true
});
console.log( "Result:", ok );
if (!ok) return;
await fetch(`/api/projects/${el.dataset.id}`, { method: 'DELETE' });
this.render();
});
});
this.querySelectorAll('.btn-members').forEach(btn => {
btn.addEventListener('click', () => this.toggleMembers((btn as HTMLElement).dataset.id!));
});
}
async toggleMembers(projectId: string): Promise<void> {
const panel = this.querySelector(`.members-panel[data-project="${projectId}"]`) as HTMLElement;
if (panel.style.display !== 'none') { panel.style.display = 'none'; return; }
panel.style.display = 'block';
await this.loadMembers(projectId, panel);
}
async loadMembers(projectId: string, panel: HTMLElement): Promise<void> {
const res = await fetch(`/api/projects/${projectId}/members`);
const members = await res.json() as ProjectMember[];
panel.innerHTML = `
<ul class="member-list">
${members.map(m => `
<li>${m.member_type}: ${m.member_id}${m.role}
<button class="btn-remove-member" data-pid="${projectId}" data-mid="${m.id}">Remove</button>
</li>
`).join('') || '<li class="empty">No members</li>'}
</ul>
<form class="add-member-form">
<select name="member_type">
<option value="user">User ID</option>
<option value="group">Group ID</option>
</select>
<input name="member_id" type="number" placeholder="ID" required>
<select name="role">
<option value="viewer">Viewer</option>
<option value="editor">Editor</option>
<option value="admin">Admin</option>
</select>
<button type="submit">Add</button>
</form>
`;
panel.querySelectorAll('.btn-remove-member').forEach(btn => {
btn.addEventListener('click', async () => {
const el = btn as HTMLElement;
await fetch(`/api/projects/${el.dataset.pid}/members/${el.dataset.mid}`, { method: 'DELETE' });
await this.loadMembers(projectId, panel);
});
});
(panel.querySelector('.add-member-form') as HTMLFormElement).addEventListener('submit', async (e: Event) => {
e.preventDefault();
const data = Object.fromEntries(new FormData(e.target as HTMLFormElement));
await fetch(`/api/projects/${projectId}/members`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
await this.loadMembers(projectId, panel);
});
}
}
customElements.define('project-editor', ProjectEditor);