/** * Account-List Component (Alpine.js) * * Used in: accounts.html * * Features: * - Paginated table of accounts * - Search by name (?q=) * - Filter by industry (?industry=) and size (?size=) * - "+ New Account" modal that POSTs to /api/v1/accounts/ * - Row click → /accounts-detail.html?id=… * * Backend: GET /api/v1/accounts/?skip=&limit=&q=&industry=&size=&owner_id= * POST /api/v1/accounts/ → AccountOut * * R-5: x-text only. All user-controlled strings go through x-text or * textContent (never x-html / innerHTML). */ import { api } from '/js/api.js'; export function accountList() { return { // State accounts: [], total: 0, skip: 0, limit: 20, q: '', industry: '', size: '', ownerId: '', loading: false, error: null, // Create-modal state showCreate: false, createForm: this._initCreateForm(), createError: '', creating: false, // Lookup values (could be hard-coded; kept here for clarity) industries: ['technology', 'finance', 'healthcare', 'retail', 'manufacturing', 'other'], sizes: ['startup', 'smb', 'midmarket', 'enterprise'], async init() { await this.load(); }, async load() { this.loading = true; this.error = null; try { const params = new URLSearchParams(); params.set('skip', String(this.skip)); params.set('limit', String(this.limit)); if (this.q) params.set('q', this.q); if (this.industry) params.set('industry', this.industry); if (this.size) params.set('size', this.size); if (this.ownerId) params.set('owner_id', this.ownerId); const res = await api.get(`/accounts/?${params.toString()}`); this.accounts = Array.isArray(res) ? res : (res && res.items) || []; this.total = (res && res.total) || this.accounts.length; } catch (err) { this.error = err.message || 'Fehler beim Laden der Accounts.'; this._notify('error', this.error); } finally { this.loading = false; } }, async search() { this.skip = 0; await this.load(); }, async nextPage() { if (this.skip + this.limit < this.total) { this.skip += this.limit; await this.load(); } }, async prevPage() { if (this.skip - this.limit >= 0) { this.skip -= this.limit; await this.load(); } }, get pageInfo() { const from = this.total === 0 ? 0 : this.skip + 1; const to = Math.min(this.skip + this.limit, this.total); return `${from}–${to} von ${this.total}`; }, // === Create modal === openCreate() { this.createForm = this._initCreateForm(); this.createError = ''; this.showCreate = true; }, closeCreate() { this.showCreate = false; }, async submitCreate() { this.creating = true; this.createError = ''; try { const payload = { name: this.createForm.name, }; if (this.createForm.website) payload.website = this.createForm.website; if (this.createForm.industry) payload.industry = this.createForm.industry; if (this.createForm.size) payload.size = this.createForm.size; await api.post('/accounts/', payload); this._notify('success', 'Account angelegt.'); this.showCreate = false; await this.load(); } catch (err) { this.createError = err.message || 'Fehler beim Anlegen.'; } finally { this.creating = false; } }, // === Internals === _initCreateForm() { return { name: '', website: '', industry: '', size: '' }; }, _notify(type, message) { if (window.Alpine) { Alpine.store('notifications').push(message, type); } }, }; } if (typeof window !== 'undefined') { document.addEventListener('alpine:init', () => { if (window.Alpine) { window.Alpine.data('accountList', accountList); } }); } export default accountList;