Upload app/webui/components/account-list.js
This commit is contained in:
@@ -0,0 +1,149 @@
|
|||||||
|
/**
|
||||||
|
* 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;
|
||||||
Reference in New Issue
Block a user