From 681a7cad309c295404ed642744a21e7694511404 Mon Sep 17 00:00:00 2001 From: Leopoldadmin Date: Thu, 4 Jun 2026 00:06:28 +0000 Subject: [PATCH] Upload app/webui/components/activity-list.js --- app/webui/components/activity-list.js | 206 ++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 app/webui/components/activity-list.js diff --git a/app/webui/components/activity-list.js b/app/webui/components/activity-list.js new file mode 100644 index 0000000..c333a93 --- /dev/null +++ b/app/webui/components/activity-list.js @@ -0,0 +1,206 @@ +/** + * Activity-List Component (Alpine.js) + * + * Used in: activities.html + * + * Features: + * - Paginated table of activities + * - Filter by type, owner, overdue, completed + * - "+ New Activity" modal that POSTs to /api/v1/activities/ + * - Row click opens a detail modal with a "Complete" button + * + * Backend: GET /api/v1/activities/?skip=&limit=&type=&owner_id=&overdue=&completed= + * POST /api/v1/activities/ + * PATCH /api/v1/activities/{id}/complete + * + * R-5: x-text only. Even activity bodies go through x-text, never x-html. + */ + +import { api } from '/js/api.js'; + +export const ACTIVITY_TYPES = ['call', 'email', 'meeting', 'task', 'note']; + +export const TYPE_BADGE = { + call: 'badge-blue', + email: 'badge-yellow', + meeting: 'badge-green', + task: 'badge-gray', + note: 'badge-gray', +}; + +export function activityList() { + return { + // State + activities: [], + total: 0, + skip: 0, + limit: 20, + type: '', + ownerId: '', + overdue: '', // '', 'true', 'false' + completed: '', // '', 'true', 'false' + loading: false, + error: null, + + // Create-modal state + showCreate: false, + createForm: this._initForm(), + createError: '', + creating: false, + + // Detail-modal state + showDetail: false, + selected: null, + completing: false, + + // Lookups + types: ACTIVITY_TYPES.slice(), + + 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.type) params.set('type', this.type); + if (this.ownerId) params.set('owner_id', this.ownerId); + if (this.overdue) params.set('overdue', this.overdue); + if (this.completed) params.set('completed', this.completed); + + const res = await api.get(`/activities/?${params.toString()}`); + this.activities = Array.isArray(res) ? res : (res && res.items) || []; + this.total = (res && res.total) || this.activities.length; + } catch (err) { + this.error = err.message || 'Fehler beim Laden der Activities.'; + this._notify('error', this.error); + } finally { + this.loading = false; + } + }, + + async applyFilters() { + 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}`; + }, + + // === Row helpers === + isOverdue(a) { + if (a.completed_at) return false; + if (!a.due_date) return false; + return new Date(a.due_date) < new Date(); + }, + isCompleted(a) { return !!a.completed_at; }, + typeBadge(t) { return TYPE_BADGE[t] || 'badge-gray'; }, + + formatDateTime(iso) { + if (!iso) return ''; + try { return new Date(iso).toLocaleString('de-DE'); } + catch { return iso; } + }, + + // === Create modal === + openCreate() { + this.createForm = this._initForm(); + this.createError = ''; + this.showCreate = true; + }, + closeCreate() { this.showCreate = false; }, + async submitCreate() { + this.creating = true; + this.createError = ''; + try { + const payload = { + type: this.createForm.type, + subject: this.createForm.subject, + }; + if (this.createForm.body) payload.body = this.createForm.body; + if (this.createForm.due_date) payload.due_date = this.createForm.due_date; + if (this.createForm.account_id) payload.account_id = Number(this.createForm.account_id); + if (this.createForm.contact_id) payload.contact_id = Number(this.createForm.contact_id); + if (this.createForm.deal_id) payload.deal_id = Number(this.createForm.deal_id); + + await api.post('/activities/', payload); + this._notify('success', 'Activity angelegt.'); + this.showCreate = false; + await this.load(); + } catch (err) { + this.createError = err.message || 'Fehler beim Anlegen.'; + } finally { + this.creating = false; + } + }, + + // === Detail / Complete === + openDetail(a) { + this.selected = a; + this.showDetail = true; + }, + closeDetail() { + this.showDetail = false; + this.selected = null; + }, + async completeSelected() { + if (!this.selected) return; + this.completing = true; + try { + await api.patch(`/activities/${this.selected.id}/complete`, { outcome: '' }); + this._notify('success', 'Activity abgeschlossen.'); + this.closeDetail(); + await this.load(); + } catch (err) { + this._notify('error', err.message || 'Fehler beim Abschließen.'); + } finally { + this.completing = false; + } + }, + + // === Internals === + _initForm() { + return { + type: 'task', + subject: '', + body: '', + due_date: '', + account_id: '', + contact_id: '', + deal_id: '', + }; + }, + _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('activityList', activityList); + } + }); +} + +export default activityList;