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

119 lines
4.0 KiB
TypeScript
Raw Normal View History

interface Group {
id: string;
name: string;
}
interface GroupMember {
id: string;
group_id: string;
member_type: 'user' | 'group';
member_id: string;
}
class GroupEditor extends HTMLElement {
connectedCallback(): void {
this.render();
}
async render(): Promise<void> {
const res = await fetch('/api/groups');
const groups = await res.json() as Group[];
this.innerHTML = `
<div class="group-editor">
<h2>Groups</h2>
<form class="create-form">
<input name="name" placeholder="New group name" required>
<button type="submit">Create</button>
</form>
<ul class="group-list">
${groups.map(g => `
<li data-id="${g.id}">
<div class="group-header">
<strong>${g.name}</strong>
<button class="btn-members" data-id="${g.id}">Members</button>
<button class="btn-delete" data-id="${g.id}">Delete</button>
</div>
<div class="members-panel" data-group="${g.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/groups', {
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 () => {
if (!confirm('Delete group?')) return;
await fetch(`/api/groups/${(btn as HTMLElement).dataset.id}`, { method: 'DELETE' });
this.render();
});
});
this.querySelectorAll('.btn-members').forEach(btn => {
btn.addEventListener('click', () => this.toggleMembers((btn as HTMLElement).dataset.id!));
});
}
async toggleMembers(groupId: string): Promise<void> {
const panel = this.querySelector(`.members-panel[data-group="${groupId}"]`) as HTMLElement;
if (panel.style.display !== 'none') { panel.style.display = 'none'; return; }
panel.style.display = 'block';
await this.loadMembers(groupId, panel);
}
async loadMembers(groupId: string, panel: HTMLElement): Promise<void> {
const res = await fetch(`/api/groups/${groupId}/members`);
const members = await res.json() as GroupMember[];
panel.innerHTML = `
<ul class="member-list">
${members.map(m => `
<li>${m.member_type}: ${m.member_id}
<button class="btn-remove-member" data-gid="${groupId}" 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>
<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/groups/${el.dataset.gid}/members/${el.dataset.mid}`, { method: 'DELETE' });
await this.loadMembers(groupId, 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/groups/${groupId}/members`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
await this.loadMembers(groupId, panel);
});
}
}
customElements.define('group-editor', GroupEditor);