@sneat/extension-eventus-shared 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (20) hide show
  1. package/fesm2022/sneat-extension-eventus-shared-bring-along-page.component-DHi-E8P1.mjs +153 -0
  2. package/fesm2022/sneat-extension-eventus-shared-bring-along-page.component-DHi-E8P1.mjs.map +1 -0
  3. package/fesm2022/sneat-extension-eventus-shared-create-event-page.component-Da39fu36.mjs +90 -0
  4. package/fesm2022/sneat-extension-eventus-shared-create-event-page.component-Da39fu36.mjs.map +1 -0
  5. package/fesm2022/sneat-extension-eventus-shared-edit-event-page.component-lhOpcqld.mjs +103 -0
  6. package/fesm2022/sneat-extension-eventus-shared-edit-event-page.component-lhOpcqld.mjs.map +1 -0
  7. package/fesm2022/sneat-extension-eventus-shared-event-page.component-CuOA0EU9.mjs +143 -0
  8. package/fesm2022/sneat-extension-eventus-shared-event-page.component-CuOA0EU9.mjs.map +1 -0
  9. package/fesm2022/sneat-extension-eventus-shared-events-list-page.component-DELSoZ2W.mjs +72 -0
  10. package/fesm2022/sneat-extension-eventus-shared-events-list-page.component-DELSoZ2W.mjs.map +1 -0
  11. package/fesm2022/sneat-extension-eventus-shared-invitees-page.component-DGG1LDd7.mjs +94 -0
  12. package/fesm2022/sneat-extension-eventus-shared-invitees-page.component-DGG1LDd7.mjs.map +1 -0
  13. package/fesm2022/sneat-extension-eventus-shared-responses-page.component-L9ayYgBN.mjs +78 -0
  14. package/fesm2022/sneat-extension-eventus-shared-responses-page.component-L9ayYgBN.mjs.map +1 -0
  15. package/fesm2022/sneat-extension-eventus-shared-share-links-page.component-BzZ6tFBc.mjs +92 -0
  16. package/fesm2022/sneat-extension-eventus-shared-share-links-page.component-BzZ6tFBc.mjs.map +1 -0
  17. package/fesm2022/sneat-extension-eventus-shared.mjs +270 -0
  18. package/fesm2022/sneat-extension-eventus-shared.mjs.map +1 -0
  19. package/package.json +43 -0
  20. package/types/sneat-extension-eventus-shared.d.ts +60 -0
@@ -0,0 +1,143 @@
1
+ import { DatePipe } from '@angular/common';
2
+ import * as i0 from '@angular/core';
3
+ import { inject, input, signal, Component } from '@angular/core';
4
+ import { Router } from '@angular/router';
5
+ import { AlertController, IonHeader, IonToolbar, IonTitle, IonButtons, IonBackButton, IonButton, IonContent, IonList, IonItem, IonLabel, IonNote, IonText, IonSpinner } from '@ionic/angular/standalone';
6
+ import { EVENT_SERVICE, extractErrorMessage } from '@sneat/extension-eventus-contract';
7
+
8
+ // Host screen showing a single event (GET) with host actions: edit (-> edit
9
+ // page, PUT) and cancel (POST .../cancel). (AC: host-creates-event, only-host-edits)
10
+ class EventPageComponent {
11
+ eventService = inject(EVENT_SERVICE);
12
+ router = inject(Router);
13
+ alertCtrl = inject(AlertController);
14
+ spaceType = input.required(...(ngDevMode ? [{ debugName: "spaceType" }] : /* istanbul ignore next */ []));
15
+ spaceID = input.required(...(ngDevMode ? [{ debugName: "spaceID" }] : /* istanbul ignore next */ []));
16
+ eventID = input.required(...(ngDevMode ? [{ debugName: "eventID" }] : /* istanbul ignore next */ []));
17
+ event = signal(undefined, ...(ngDevMode ? [{ debugName: "event" }] : /* istanbul ignore next */ []));
18
+ loading = signal(true, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
19
+ error = signal(undefined, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
20
+ cancelling = signal(false, ...(ngDevMode ? [{ debugName: "cancelling" }] : /* istanbul ignore next */ []));
21
+ ngOnInit() {
22
+ this.load();
23
+ }
24
+ load() {
25
+ this.loading.set(true);
26
+ this.error.set(undefined);
27
+ this.eventService.getEvent(this.spaceID(), this.eventID()).subscribe({
28
+ next: (event) => {
29
+ this.event.set(event);
30
+ this.loading.set(false);
31
+ },
32
+ error: (err) => {
33
+ this.loading.set(false);
34
+ this.error.set(extractErrorMessage(err, 'Failed to load event.'));
35
+ },
36
+ });
37
+ }
38
+ edit() {
39
+ void this.router.navigate([
40
+ '/space',
41
+ this.spaceType(),
42
+ this.spaceID(),
43
+ 'events',
44
+ this.eventID(),
45
+ 'edit',
46
+ ]);
47
+ }
48
+ manageInvitees() {
49
+ void this.router.navigate([
50
+ '/space',
51
+ this.spaceType(),
52
+ this.spaceID(),
53
+ 'events',
54
+ this.eventID(),
55
+ 'invitees',
56
+ ]);
57
+ }
58
+ shareLinks() {
59
+ void this.router.navigate([
60
+ '/space',
61
+ this.spaceType(),
62
+ this.spaceID(),
63
+ 'events',
64
+ this.eventID(),
65
+ 'share-links',
66
+ ]);
67
+ }
68
+ responses() {
69
+ void this.router.navigate([
70
+ '/space',
71
+ this.spaceType(),
72
+ this.spaceID(),
73
+ 'events',
74
+ this.eventID(),
75
+ 'responses',
76
+ ]);
77
+ }
78
+ bringAlong() {
79
+ void this.router.navigate([
80
+ '/space',
81
+ this.spaceType(),
82
+ this.spaceID(),
83
+ 'events',
84
+ this.eventID(),
85
+ 'bring-along',
86
+ ]);
87
+ }
88
+ async confirmCancel() {
89
+ const alert = await this.alertCtrl.create({
90
+ header: 'Cancel event?',
91
+ message: 'This marks the event as cancelled.',
92
+ buttons: [
93
+ { text: 'Keep', role: 'cancel' },
94
+ {
95
+ text: 'Cancel event',
96
+ role: 'destructive',
97
+ handler: () => {
98
+ this.cancelEvent();
99
+ },
100
+ },
101
+ ],
102
+ });
103
+ await alert.present();
104
+ }
105
+ cancelEvent() {
106
+ this.cancelling.set(true);
107
+ this.error.set(undefined);
108
+ this.eventService.cancelEvent(this.spaceID(), this.eventID()).subscribe({
109
+ next: (event) => {
110
+ this.event.set(event);
111
+ this.cancelling.set(false);
112
+ },
113
+ error: (err) => {
114
+ this.cancelling.set(false);
115
+ this.error.set(extractErrorMessage(err, 'Failed to cancel event.'));
116
+ },
117
+ });
118
+ }
119
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: EventPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
120
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: EventPageComponent, isStandalone: true, selector: "eventus-event-page", inputs: { spaceType: { classPropertyName: "spaceType", publicName: "spaceType", isSignal: true, isRequired: true, transformFunction: null }, spaceID: { classPropertyName: "spaceID", publicName: "spaceID", isSignal: true, isRequired: true, transformFunction: null }, eventID: { classPropertyName: "eventID", publicName: "eventID", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<ion-header>\n <ion-toolbar>\n <ion-buttons slot=\"start\">\n <ion-back-button defaultHref=\"/\"></ion-back-button>\n </ion-buttons>\n <ion-title>Event</ion-title>\n @if (event(); as ev) {\n <ion-buttons slot=\"end\">\n <ion-button (click)=\"edit()\">Edit</ion-button>\n </ion-buttons>\n }\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n @if (loading()) {\n <ion-spinner aria-label=\"Loading event\"></ion-spinner>\n } @else if (error() && !event()) {\n <ion-text color=\"danger\">{{ error() }}</ion-text>\n } @else if (event(); as ev) {\n <ion-list>\n <ion-item>\n <ion-label>\n <h2>{{ ev.title }}</h2>\n @if (ev.status === 'cancelled') {\n <ion-note color=\"danger\">Cancelled</ion-note>\n }\n </ion-label>\n </ion-item>\n <ion-item>\n <ion-label>\n <p>When</p>\n <h3>{{ ev.start | date: 'medium' }}</h3>\n </ion-label>\n </ion-item>\n <ion-item>\n <ion-label>\n <p>Where</p>\n <h3>{{ ev.location }}</h3>\n </ion-label>\n </ion-item>\n @if (ev.description) {\n <ion-item>\n <ion-label class=\"ion-text-wrap\">\n <p>Description</p>\n <h3>{{ ev.description }}</h3>\n </ion-label>\n </ion-item>\n }\n </ion-list>\n\n @if (error()) {\n <ion-text color=\"danger\">{{ error() }}</ion-text>\n }\n\n <ion-button\n expand=\"block\"\n fill=\"outline\"\n class=\"ion-margin-top\"\n (click)=\"manageInvitees()\"\n >\n Manage invitees\n </ion-button>\n\n <ion-button\n expand=\"block\"\n fill=\"outline\"\n class=\"ion-margin-top\"\n (click)=\"shareLinks()\"\n >\n Share links\n </ion-button>\n\n <ion-button\n expand=\"block\"\n fill=\"outline\"\n class=\"ion-margin-top\"\n (click)=\"responses()\"\n >\n Responses\n </ion-button>\n\n <ion-button\n expand=\"block\"\n fill=\"outline\"\n class=\"ion-margin-top\"\n (click)=\"bringAlong()\"\n >\n Bring along\n </ion-button>\n\n @if (ev.status === 'active') {\n <ion-button\n expand=\"block\"\n color=\"danger\"\n fill=\"outline\"\n class=\"ion-margin-top\"\n [disabled]=\"cancelling()\"\n (click)=\"confirmCancel()\"\n >\n Cancel event\n </ion-button>\n }\n }\n</ion-content>\n", dependencies: [{ kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonBackButton, selector: "ion-back-button" }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { kind: "component", type: IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "pipe", type: DatePipe, name: "date" }] });
121
+ }
122
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: EventPageComponent, decorators: [{
123
+ type: Component,
124
+ args: [{ selector: 'eventus-event-page', imports: [
125
+ DatePipe,
126
+ IonHeader,
127
+ IonToolbar,
128
+ IonTitle,
129
+ IonButtons,
130
+ IonBackButton,
131
+ IonButton,
132
+ IonContent,
133
+ IonList,
134
+ IonItem,
135
+ IonLabel,
136
+ IonNote,
137
+ IonText,
138
+ IonSpinner,
139
+ ], template: "<ion-header>\n <ion-toolbar>\n <ion-buttons slot=\"start\">\n <ion-back-button defaultHref=\"/\"></ion-back-button>\n </ion-buttons>\n <ion-title>Event</ion-title>\n @if (event(); as ev) {\n <ion-buttons slot=\"end\">\n <ion-button (click)=\"edit()\">Edit</ion-button>\n </ion-buttons>\n }\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n @if (loading()) {\n <ion-spinner aria-label=\"Loading event\"></ion-spinner>\n } @else if (error() && !event()) {\n <ion-text color=\"danger\">{{ error() }}</ion-text>\n } @else if (event(); as ev) {\n <ion-list>\n <ion-item>\n <ion-label>\n <h2>{{ ev.title }}</h2>\n @if (ev.status === 'cancelled') {\n <ion-note color=\"danger\">Cancelled</ion-note>\n }\n </ion-label>\n </ion-item>\n <ion-item>\n <ion-label>\n <p>When</p>\n <h3>{{ ev.start | date: 'medium' }}</h3>\n </ion-label>\n </ion-item>\n <ion-item>\n <ion-label>\n <p>Where</p>\n <h3>{{ ev.location }}</h3>\n </ion-label>\n </ion-item>\n @if (ev.description) {\n <ion-item>\n <ion-label class=\"ion-text-wrap\">\n <p>Description</p>\n <h3>{{ ev.description }}</h3>\n </ion-label>\n </ion-item>\n }\n </ion-list>\n\n @if (error()) {\n <ion-text color=\"danger\">{{ error() }}</ion-text>\n }\n\n <ion-button\n expand=\"block\"\n fill=\"outline\"\n class=\"ion-margin-top\"\n (click)=\"manageInvitees()\"\n >\n Manage invitees\n </ion-button>\n\n <ion-button\n expand=\"block\"\n fill=\"outline\"\n class=\"ion-margin-top\"\n (click)=\"shareLinks()\"\n >\n Share links\n </ion-button>\n\n <ion-button\n expand=\"block\"\n fill=\"outline\"\n class=\"ion-margin-top\"\n (click)=\"responses()\"\n >\n Responses\n </ion-button>\n\n <ion-button\n expand=\"block\"\n fill=\"outline\"\n class=\"ion-margin-top\"\n (click)=\"bringAlong()\"\n >\n Bring along\n </ion-button>\n\n @if (ev.status === 'active') {\n <ion-button\n expand=\"block\"\n color=\"danger\"\n fill=\"outline\"\n class=\"ion-margin-top\"\n [disabled]=\"cancelling()\"\n (click)=\"confirmCancel()\"\n >\n Cancel event\n </ion-button>\n }\n }\n</ion-content>\n" }]
140
+ }], propDecorators: { spaceType: [{ type: i0.Input, args: [{ isSignal: true, alias: "spaceType", required: true }] }], spaceID: [{ type: i0.Input, args: [{ isSignal: true, alias: "spaceID", required: true }] }], eventID: [{ type: i0.Input, args: [{ isSignal: true, alias: "eventID", required: true }] }] } });
141
+
142
+ export { EventPageComponent };
143
+ //# sourceMappingURL=sneat-extension-eventus-shared-event-page.component-CuOA0EU9.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sneat-extension-eventus-shared-event-page.component-CuOA0EU9.mjs","sources":["../../../../../../libs/extensions/eventus/shared/src/lib/pages/event/event-page.component.ts","../../../../../../libs/extensions/eventus/shared/src/lib/pages/event/event-page.component.html"],"sourcesContent":["import { DatePipe } from '@angular/common';\nimport { Component, OnInit, inject, input, signal } from '@angular/core';\nimport { Router } from '@angular/router';\nimport {\n AlertController,\n IonBackButton,\n IonButton,\n IonButtons,\n IonContent,\n IonHeader,\n IonItem,\n IonLabel,\n IonList,\n IonNote,\n IonSpinner,\n IonText,\n IonTitle,\n IonToolbar,\n} from '@ionic/angular/standalone';\nimport {\n EVENT_SERVICE,\n extractErrorMessage,\n IEvent,\n} from '@sneat/extension-eventus-contract';\n\n// Host screen showing a single event (GET) with host actions: edit (-> edit\n// page, PUT) and cancel (POST .../cancel). (AC: host-creates-event, only-host-edits)\n@Component({\n selector: 'eventus-event-page',\n templateUrl: './event-page.component.html',\n imports: [\n DatePipe,\n IonHeader,\n IonToolbar,\n IonTitle,\n IonButtons,\n IonBackButton,\n IonButton,\n IonContent,\n IonList,\n IonItem,\n IonLabel,\n IonNote,\n IonText,\n IonSpinner,\n ],\n})\nexport class EventPageComponent implements OnInit {\n private readonly eventService = inject(EVENT_SERVICE);\n private readonly router = inject(Router);\n private readonly alertCtrl = inject(AlertController);\n\n readonly spaceType = input.required<string>();\n readonly spaceID = input.required<string>();\n readonly eventID = input.required<string>();\n\n protected readonly event = signal<IEvent | undefined>(undefined);\n protected readonly loading = signal(true);\n protected readonly error = signal<string | undefined>(undefined);\n protected readonly cancelling = signal(false);\n\n ngOnInit(): void {\n this.load();\n }\n\n private load(): void {\n this.loading.set(true);\n this.error.set(undefined);\n this.eventService.getEvent(this.spaceID(), this.eventID()).subscribe({\n next: (event) => {\n this.event.set(event);\n this.loading.set(false);\n },\n error: (err: unknown) => {\n this.loading.set(false);\n this.error.set(extractErrorMessage(err, 'Failed to load event.'));\n },\n });\n }\n\n protected edit(): void {\n void this.router.navigate([\n '/space',\n this.spaceType(),\n this.spaceID(),\n 'events',\n this.eventID(),\n 'edit',\n ]);\n }\n\n protected manageInvitees(): void {\n void this.router.navigate([\n '/space',\n this.spaceType(),\n this.spaceID(),\n 'events',\n this.eventID(),\n 'invitees',\n ]);\n }\n\n protected shareLinks(): void {\n void this.router.navigate([\n '/space',\n this.spaceType(),\n this.spaceID(),\n 'events',\n this.eventID(),\n 'share-links',\n ]);\n }\n\n protected responses(): void {\n void this.router.navigate([\n '/space',\n this.spaceType(),\n this.spaceID(),\n 'events',\n this.eventID(),\n 'responses',\n ]);\n }\n\n protected bringAlong(): void {\n void this.router.navigate([\n '/space',\n this.spaceType(),\n this.spaceID(),\n 'events',\n this.eventID(),\n 'bring-along',\n ]);\n }\n\n protected async confirmCancel(): Promise<void> {\n const alert = await this.alertCtrl.create({\n header: 'Cancel event?',\n message: 'This marks the event as cancelled.',\n buttons: [\n { text: 'Keep', role: 'cancel' },\n {\n text: 'Cancel event',\n role: 'destructive',\n handler: () => {\n this.cancelEvent();\n },\n },\n ],\n });\n await alert.present();\n }\n\n private cancelEvent(): void {\n this.cancelling.set(true);\n this.error.set(undefined);\n this.eventService.cancelEvent(this.spaceID(), this.eventID()).subscribe({\n next: (event) => {\n this.event.set(event);\n this.cancelling.set(false);\n },\n error: (err: unknown) => {\n this.cancelling.set(false);\n this.error.set(extractErrorMessage(err, 'Failed to cancel event.'));\n },\n });\n }\n}\n","<ion-header>\n <ion-toolbar>\n <ion-buttons slot=\"start\">\n <ion-back-button defaultHref=\"/\"></ion-back-button>\n </ion-buttons>\n <ion-title>Event</ion-title>\n @if (event(); as ev) {\n <ion-buttons slot=\"end\">\n <ion-button (click)=\"edit()\">Edit</ion-button>\n </ion-buttons>\n }\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n @if (loading()) {\n <ion-spinner aria-label=\"Loading event\"></ion-spinner>\n } @else if (error() && !event()) {\n <ion-text color=\"danger\">{{ error() }}</ion-text>\n } @else if (event(); as ev) {\n <ion-list>\n <ion-item>\n <ion-label>\n <h2>{{ ev.title }}</h2>\n @if (ev.status === 'cancelled') {\n <ion-note color=\"danger\">Cancelled</ion-note>\n }\n </ion-label>\n </ion-item>\n <ion-item>\n <ion-label>\n <p>When</p>\n <h3>{{ ev.start | date: 'medium' }}</h3>\n </ion-label>\n </ion-item>\n <ion-item>\n <ion-label>\n <p>Where</p>\n <h3>{{ ev.location }}</h3>\n </ion-label>\n </ion-item>\n @if (ev.description) {\n <ion-item>\n <ion-label class=\"ion-text-wrap\">\n <p>Description</p>\n <h3>{{ ev.description }}</h3>\n </ion-label>\n </ion-item>\n }\n </ion-list>\n\n @if (error()) {\n <ion-text color=\"danger\">{{ error() }}</ion-text>\n }\n\n <ion-button\n expand=\"block\"\n fill=\"outline\"\n class=\"ion-margin-top\"\n (click)=\"manageInvitees()\"\n >\n Manage invitees\n </ion-button>\n\n <ion-button\n expand=\"block\"\n fill=\"outline\"\n class=\"ion-margin-top\"\n (click)=\"shareLinks()\"\n >\n Share links\n </ion-button>\n\n <ion-button\n expand=\"block\"\n fill=\"outline\"\n class=\"ion-margin-top\"\n (click)=\"responses()\"\n >\n Responses\n </ion-button>\n\n <ion-button\n expand=\"block\"\n fill=\"outline\"\n class=\"ion-margin-top\"\n (click)=\"bringAlong()\"\n >\n Bring along\n </ion-button>\n\n @if (ev.status === 'active') {\n <ion-button\n expand=\"block\"\n color=\"danger\"\n fill=\"outline\"\n class=\"ion-margin-top\"\n [disabled]=\"cancelling()\"\n (click)=\"confirmCancel()\"\n >\n Cancel event\n </ion-button>\n }\n }\n</ion-content>\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,72 @@
1
+ import { DatePipe } from '@angular/common';
2
+ import * as i0 from '@angular/core';
3
+ import { inject, DestroyRef, input, signal, Component } from '@angular/core';
4
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
5
+ import { RouterLink } from '@angular/router';
6
+ import { IonHeader, IonToolbar, IonTitle, IonButtons, IonMenuButton, IonButton, IonContent, IonList, IonItem, IonLabel, IonNote, IonText, IonSpinner } from '@ionic/angular/standalone';
7
+ import { EVENTUS_EVENT_SERVICE } from '@sneat/extension-eventus-contract';
8
+
9
+ // Host screen listing the space's events. An eventus event IS a single
10
+ // Calendarius happening that has an eventus overlay; events are read
11
+ // Firestore-direct (host/member view). Each row links to the event view page; a
12
+ // header action opens the create page. (AC: host-creates-event)
13
+ class EventsListPageComponent {
14
+ eventusEventService = inject(EVENTUS_EVENT_SERVICE);
15
+ destroyRef = inject(DestroyRef);
16
+ spaceType = input.required(...(ngDevMode ? [{ debugName: "spaceType" }] : /* istanbul ignore next */ []));
17
+ spaceID = input.required(...(ngDevMode ? [{ debugName: "spaceID" }] : /* istanbul ignore next */ []));
18
+ events = signal(undefined, ...(ngDevMode ? [{ debugName: "events" }] : /* istanbul ignore next */ []));
19
+ loading = signal(true, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
20
+ error = signal(undefined, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
21
+ ngOnInit() {
22
+ this.load();
23
+ }
24
+ load() {
25
+ this.loading.set(true);
26
+ this.error.set(undefined);
27
+ this.eventusEventService
28
+ .watchEvents(this.spaceID())
29
+ .pipe(takeUntilDestroyed(this.destroyRef))
30
+ .subscribe({
31
+ next: (events) => {
32
+ this.events.set(events);
33
+ this.loading.set(false);
34
+ },
35
+ error: () => {
36
+ this.loading.set(false);
37
+ this.error.set('Failed to load events.');
38
+ },
39
+ });
40
+ }
41
+ eventLink(eventID) {
42
+ return ['/space', this.spaceType(), this.spaceID(), 'events', eventID];
43
+ }
44
+ newEventLink() {
45
+ return ['/space', this.spaceType(), this.spaceID(), 'events', 'new'];
46
+ }
47
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: EventsListPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
48
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: EventsListPageComponent, isStandalone: true, selector: "eventus-events-list-page", inputs: { spaceType: { classPropertyName: "spaceType", publicName: "spaceType", isSignal: true, isRequired: true, transformFunction: null }, spaceID: { classPropertyName: "spaceID", publicName: "spaceID", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<ion-header>\n <ion-toolbar>\n <ion-buttons slot=\"start\">\n <ion-menu-button></ion-menu-button>\n </ion-buttons>\n <ion-title>Events</ion-title>\n <ion-buttons slot=\"end\">\n <ion-button [routerLink]=\"newEventLink()\">New event</ion-button>\n </ion-buttons>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n @if (loading()) {\n <ion-spinner aria-label=\"Loading events\"></ion-spinner>\n } @else if (error()) {\n <ion-text color=\"danger\">{{ error() }}</ion-text>\n } @else if (events(); as list) {\n @if (list.length === 0) {\n <ion-text>No events yet</ion-text>\n } @else {\n <ion-list>\n @for (ev of list; track ev.id) {\n <ion-item [routerLink]=\"eventLink(ev.id)\" tappable>\n <ion-label>\n <h2>{{ ev.title }}</h2>\n <p>{{ ev.start | date: 'medium' }}</p>\n </ion-label>\n @if (ev.happening.dbo?.status === 'canceled') {\n <ion-note slot=\"end\" color=\"danger\">Cancelled</ion-note>\n }\n </ion-item>\n }\n </ion-list>\n }\n }\n</ion-content>\n", dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonMenuButton, selector: "ion-menu-button", inputs: ["autoHide", "color", "disabled", "menu", "mode", "type"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { kind: "component", type: IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "pipe", type: DatePipe, name: "date" }] });
49
+ }
50
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: EventsListPageComponent, decorators: [{
51
+ type: Component,
52
+ args: [{ selector: 'eventus-events-list-page', imports: [
53
+ DatePipe,
54
+ RouterLink,
55
+ IonHeader,
56
+ IonToolbar,
57
+ IonTitle,
58
+ IonButtons,
59
+ IonMenuButton,
60
+ IonButton,
61
+ IonContent,
62
+ IonList,
63
+ IonItem,
64
+ IonLabel,
65
+ IonNote,
66
+ IonText,
67
+ IonSpinner,
68
+ ], template: "<ion-header>\n <ion-toolbar>\n <ion-buttons slot=\"start\">\n <ion-menu-button></ion-menu-button>\n </ion-buttons>\n <ion-title>Events</ion-title>\n <ion-buttons slot=\"end\">\n <ion-button [routerLink]=\"newEventLink()\">New event</ion-button>\n </ion-buttons>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n @if (loading()) {\n <ion-spinner aria-label=\"Loading events\"></ion-spinner>\n } @else if (error()) {\n <ion-text color=\"danger\">{{ error() }}</ion-text>\n } @else if (events(); as list) {\n @if (list.length === 0) {\n <ion-text>No events yet</ion-text>\n } @else {\n <ion-list>\n @for (ev of list; track ev.id) {\n <ion-item [routerLink]=\"eventLink(ev.id)\" tappable>\n <ion-label>\n <h2>{{ ev.title }}</h2>\n <p>{{ ev.start | date: 'medium' }}</p>\n </ion-label>\n @if (ev.happening.dbo?.status === 'canceled') {\n <ion-note slot=\"end\" color=\"danger\">Cancelled</ion-note>\n }\n </ion-item>\n }\n </ion-list>\n }\n }\n</ion-content>\n" }]
69
+ }], propDecorators: { spaceType: [{ type: i0.Input, args: [{ isSignal: true, alias: "spaceType", required: true }] }], spaceID: [{ type: i0.Input, args: [{ isSignal: true, alias: "spaceID", required: true }] }] } });
70
+
71
+ export { EventsListPageComponent };
72
+ //# sourceMappingURL=sneat-extension-eventus-shared-events-list-page.component-DELSoZ2W.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sneat-extension-eventus-shared-events-list-page.component-DELSoZ2W.mjs","sources":["../../../../../../libs/extensions/eventus/shared/src/lib/pages/events/events-list-page.component.ts","../../../../../../libs/extensions/eventus/shared/src/lib/pages/events/events-list-page.component.html"],"sourcesContent":["import { DatePipe } from '@angular/common';\nimport {\n Component,\n DestroyRef,\n OnInit,\n inject,\n input,\n signal,\n} from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { RouterLink } from '@angular/router';\nimport {\n IonButton,\n IonButtons,\n IonContent,\n IonHeader,\n IonItem,\n IonLabel,\n IonList,\n IonMenuButton,\n IonNote,\n IonSpinner,\n IonText,\n IonTitle,\n IonToolbar,\n} from '@ionic/angular/standalone';\nimport {\n EVENTUS_EVENT_SERVICE,\n IEventusEventListItem,\n} from '@sneat/extension-eventus-contract';\n\n// Host screen listing the space's events. An eventus event IS a single\n// Calendarius happening that has an eventus overlay; events are read\n// Firestore-direct (host/member view). Each row links to the event view page; a\n// header action opens the create page. (AC: host-creates-event)\n@Component({\n selector: 'eventus-events-list-page',\n templateUrl: './events-list-page.component.html',\n imports: [\n DatePipe,\n RouterLink,\n IonHeader,\n IonToolbar,\n IonTitle,\n IonButtons,\n IonMenuButton,\n IonButton,\n IonContent,\n IonList,\n IonItem,\n IonLabel,\n IonNote,\n IonText,\n IonSpinner,\n ],\n})\nexport class EventsListPageComponent implements OnInit {\n private readonly eventusEventService = inject(EVENTUS_EVENT_SERVICE);\n private readonly destroyRef = inject(DestroyRef);\n\n readonly spaceType = input.required<string>();\n readonly spaceID = input.required<string>();\n\n protected readonly events = signal<IEventusEventListItem[] | undefined>(\n undefined,\n );\n protected readonly loading = signal(true);\n protected readonly error = signal<string | undefined>(undefined);\n\n ngOnInit(): void {\n this.load();\n }\n\n private load(): void {\n this.loading.set(true);\n this.error.set(undefined);\n this.eventusEventService\n .watchEvents(this.spaceID())\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe({\n next: (events) => {\n this.events.set(events);\n this.loading.set(false);\n },\n error: () => {\n this.loading.set(false);\n this.error.set('Failed to load events.');\n },\n });\n }\n\n protected eventLink(eventID: string): unknown[] {\n return ['/space', this.spaceType(), this.spaceID(), 'events', eventID];\n }\n\n protected newEventLink(): unknown[] {\n return ['/space', this.spaceType(), this.spaceID(), 'events', 'new'];\n }\n}\n","<ion-header>\n <ion-toolbar>\n <ion-buttons slot=\"start\">\n <ion-menu-button></ion-menu-button>\n </ion-buttons>\n <ion-title>Events</ion-title>\n <ion-buttons slot=\"end\">\n <ion-button [routerLink]=\"newEventLink()\">New event</ion-button>\n </ion-buttons>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n @if (loading()) {\n <ion-spinner aria-label=\"Loading events\"></ion-spinner>\n } @else if (error()) {\n <ion-text color=\"danger\">{{ error() }}</ion-text>\n } @else if (events(); as list) {\n @if (list.length === 0) {\n <ion-text>No events yet</ion-text>\n } @else {\n <ion-list>\n @for (ev of list; track ev.id) {\n <ion-item [routerLink]=\"eventLink(ev.id)\" tappable>\n <ion-label>\n <h2>{{ ev.title }}</h2>\n <p>{{ ev.start | date: 'medium' }}</p>\n </ion-label>\n @if (ev.happening.dbo?.status === 'canceled') {\n <ion-note slot=\"end\" color=\"danger\">Cancelled</ion-note>\n }\n </ion-item>\n }\n </ion-list>\n }\n }\n</ion-content>\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,94 @@
1
+ import { DatePipe } from '@angular/common';
2
+ import * as i0 from '@angular/core';
3
+ import { inject, input, signal, Component } from '@angular/core';
4
+ import { IonHeader, IonToolbar, IonTitle, IonButtons, IonBackButton, IonButton, IonContent, IonList, IonListHeader, IonItem, IonLabel, IonNote, IonText, IonSpinner } from '@ionic/angular/standalone';
5
+ import { INVITATION_SERVICE, EVENTUS_INVITEE_SOURCE, extractErrorMessage } from '@sneat/extension-eventus-contract';
6
+
7
+ // Host screen to manage an event's invitees: pick an existing family or person
8
+ // (from the injected source) and record the invitation by id only — the host
9
+ // never types a name or contact detail. Also lists current invitations.
10
+ // (AC: invite-family-from-contacts)
11
+ class InviteesPageComponent {
12
+ invitationService = inject(INVITATION_SERVICE);
13
+ inviteeSource = inject(EVENTUS_INVITEE_SOURCE);
14
+ spaceID = input.required(...(ngDevMode ? [{ debugName: "spaceID" }] : /* istanbul ignore next */ []));
15
+ eventID = input.required(...(ngDevMode ? [{ debugName: "eventID" }] : /* istanbul ignore next */ []));
16
+ families = signal([], ...(ngDevMode ? [{ debugName: "families" }] : /* istanbul ignore next */ []));
17
+ persons = signal([], ...(ngDevMode ? [{ debugName: "persons" }] : /* istanbul ignore next */ []));
18
+ invitations = signal([], ...(ngDevMode ? [{ debugName: "invitations" }] : /* istanbul ignore next */ []));
19
+ loading = signal(true, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
20
+ submitting = signal(false, ...(ngDevMode ? [{ debugName: "submitting" }] : /* istanbul ignore next */ []));
21
+ error = signal(undefined, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
22
+ ngOnInit() {
23
+ this.inviteeSource.families(this.spaceID()).subscribe((f) => this.families.set(f));
24
+ this.inviteeSource.persons(this.spaceID()).subscribe((p) => this.persons.set(p));
25
+ this.loadInvitations();
26
+ }
27
+ loadInvitations() {
28
+ this.loading.set(true);
29
+ this.invitationService
30
+ .listInvitations(this.spaceID(), this.eventID())
31
+ .subscribe({
32
+ next: (invitations) => {
33
+ this.invitations.set(invitations);
34
+ this.loading.set(false);
35
+ },
36
+ error: (err) => {
37
+ this.loading.set(false);
38
+ this.error.set(extractErrorMessage(err, 'Failed to load invitations.'));
39
+ },
40
+ });
41
+ }
42
+ /** Invite a family by its space id — no name/detail is sent. */
43
+ inviteFamily(family) {
44
+ this.add({ inviteeType: 'family', familySpaceID: family.id });
45
+ }
46
+ /** Invite a person by their contact id — no name/detail is sent. */
47
+ invitePerson(person) {
48
+ this.add({ inviteeType: 'person', contactID: person.id });
49
+ }
50
+ add(request) {
51
+ if (this.submitting()) {
52
+ return;
53
+ }
54
+ this.submitting.set(true);
55
+ this.error.set(undefined);
56
+ this.invitationService
57
+ .addInvitee(this.spaceID(), this.eventID(), request)
58
+ .subscribe({
59
+ next: (invitation) => {
60
+ this.invitations.update((list) => [...list, invitation]);
61
+ this.submitting.set(false);
62
+ },
63
+ error: (err) => {
64
+ this.submitting.set(false);
65
+ this.error.set(extractErrorMessage(err, 'Failed to add invitee.'));
66
+ },
67
+ });
68
+ }
69
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: InviteesPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
70
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: InviteesPageComponent, isStandalone: true, selector: "eventus-invitees-page", inputs: { spaceID: { classPropertyName: "spaceID", publicName: "spaceID", isSignal: true, isRequired: true, transformFunction: null }, eventID: { classPropertyName: "eventID", publicName: "eventID", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<ion-header>\n <ion-toolbar>\n <ion-buttons slot=\"start\">\n <ion-back-button defaultHref=\"/\"></ion-back-button>\n </ion-buttons>\n <ion-title>Invitees</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n @if (error()) {\n <ion-text color=\"danger\">{{ error() }}</ion-text>\n }\n\n <ion-list>\n <ion-list-header>Invite a family</ion-list-header>\n @for (family of families(); track family.id) {\n <ion-item>\n <ion-label>{{ family.title }}</ion-label>\n <ion-button\n slot=\"end\"\n [disabled]=\"submitting()\"\n (click)=\"inviteFamily(family)\"\n >\n Invite\n </ion-button>\n </ion-item>\n } @empty {\n <ion-item>\n <ion-note>No families available.</ion-note>\n </ion-item>\n }\n </ion-list>\n\n <ion-list>\n <ion-list-header>Invite a person</ion-list-header>\n @for (person of persons(); track person.id) {\n <ion-item>\n <ion-label>{{ person.name }}</ion-label>\n <ion-button\n slot=\"end\"\n [disabled]=\"submitting()\"\n (click)=\"invitePerson(person)\"\n >\n Invite\n </ion-button>\n </ion-item>\n } @empty {\n <ion-item>\n <ion-note>No persons available.</ion-note>\n </ion-item>\n }\n </ion-list>\n\n <ion-list>\n <ion-list-header>Current invitees</ion-list-header>\n @if (loading()) {\n <ion-item>\n <ion-spinner aria-label=\"Loading invitations\"></ion-spinner>\n </ion-item>\n } @else {\n @for (invitation of invitations(); track invitation.id) {\n <ion-item>\n <ion-label>\n <h3>{{ invitation.inviteeType }}</h3>\n <ion-note>\n {{ invitation.familySpaceID ?? invitation.contactID }}\n </ion-note>\n <p>{{ invitation.createdAt | date: 'short' }}</p>\n </ion-label>\n </ion-item>\n } @empty {\n <ion-item>\n <ion-note>No invitees yet.</ion-note>\n </ion-item>\n }\n }\n </ion-list>\n</ion-content>\n", dependencies: [{ kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonBackButton, selector: "ion-back-button" }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: IonListHeader, selector: "ion-list-header", inputs: ["color", "lines", "mode"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { kind: "component", type: IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "pipe", type: DatePipe, name: "date" }] });
71
+ }
72
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: InviteesPageComponent, decorators: [{
73
+ type: Component,
74
+ args: [{ selector: 'eventus-invitees-page', imports: [
75
+ DatePipe,
76
+ IonHeader,
77
+ IonToolbar,
78
+ IonTitle,
79
+ IonButtons,
80
+ IonBackButton,
81
+ IonButton,
82
+ IonContent,
83
+ IonList,
84
+ IonListHeader,
85
+ IonItem,
86
+ IonLabel,
87
+ IonNote,
88
+ IonText,
89
+ IonSpinner,
90
+ ], template: "<ion-header>\n <ion-toolbar>\n <ion-buttons slot=\"start\">\n <ion-back-button defaultHref=\"/\"></ion-back-button>\n </ion-buttons>\n <ion-title>Invitees</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n @if (error()) {\n <ion-text color=\"danger\">{{ error() }}</ion-text>\n }\n\n <ion-list>\n <ion-list-header>Invite a family</ion-list-header>\n @for (family of families(); track family.id) {\n <ion-item>\n <ion-label>{{ family.title }}</ion-label>\n <ion-button\n slot=\"end\"\n [disabled]=\"submitting()\"\n (click)=\"inviteFamily(family)\"\n >\n Invite\n </ion-button>\n </ion-item>\n } @empty {\n <ion-item>\n <ion-note>No families available.</ion-note>\n </ion-item>\n }\n </ion-list>\n\n <ion-list>\n <ion-list-header>Invite a person</ion-list-header>\n @for (person of persons(); track person.id) {\n <ion-item>\n <ion-label>{{ person.name }}</ion-label>\n <ion-button\n slot=\"end\"\n [disabled]=\"submitting()\"\n (click)=\"invitePerson(person)\"\n >\n Invite\n </ion-button>\n </ion-item>\n } @empty {\n <ion-item>\n <ion-note>No persons available.</ion-note>\n </ion-item>\n }\n </ion-list>\n\n <ion-list>\n <ion-list-header>Current invitees</ion-list-header>\n @if (loading()) {\n <ion-item>\n <ion-spinner aria-label=\"Loading invitations\"></ion-spinner>\n </ion-item>\n } @else {\n @for (invitation of invitations(); track invitation.id) {\n <ion-item>\n <ion-label>\n <h3>{{ invitation.inviteeType }}</h3>\n <ion-note>\n {{ invitation.familySpaceID ?? invitation.contactID }}\n </ion-note>\n <p>{{ invitation.createdAt | date: 'short' }}</p>\n </ion-label>\n </ion-item>\n } @empty {\n <ion-item>\n <ion-note>No invitees yet.</ion-note>\n </ion-item>\n }\n }\n </ion-list>\n</ion-content>\n" }]
91
+ }], propDecorators: { spaceID: [{ type: i0.Input, args: [{ isSignal: true, alias: "spaceID", required: true }] }], eventID: [{ type: i0.Input, args: [{ isSignal: true, alias: "eventID", required: true }] }] } });
92
+
93
+ export { InviteesPageComponent };
94
+ //# sourceMappingURL=sneat-extension-eventus-shared-invitees-page.component-DGG1LDd7.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sneat-extension-eventus-shared-invitees-page.component-DGG1LDd7.mjs","sources":["../../../../../../libs/extensions/eventus/shared/src/lib/pages/invitees/invitees-page.component.ts","../../../../../../libs/extensions/eventus/shared/src/lib/pages/invitees/invitees-page.component.html"],"sourcesContent":["import { DatePipe } from '@angular/common';\nimport { Component, OnInit, inject, input, signal } from '@angular/core';\nimport {\n IonBackButton,\n IonButton,\n IonButtons,\n IonContent,\n IonHeader,\n IonItem,\n IonLabel,\n IonList,\n IonListHeader,\n IonNote,\n IonSpinner,\n IonText,\n IonTitle,\n IonToolbar,\n} from '@ionic/angular/standalone';\nimport {\n EVENTUS_INVITEE_SOURCE,\n extractErrorMessage,\n IInvitation,\n INVITATION_SERVICE,\n IInviteeFamily,\n IInviteePerson,\n InviteeType,\n} from '@sneat/extension-eventus-contract';\n\n// Host screen to manage an event's invitees: pick an existing family or person\n// (from the injected source) and record the invitation by id only — the host\n// never types a name or contact detail. Also lists current invitations.\n// (AC: invite-family-from-contacts)\n@Component({\n selector: 'eventus-invitees-page',\n templateUrl: './invitees-page.component.html',\n imports: [\n DatePipe,\n IonHeader,\n IonToolbar,\n IonTitle,\n IonButtons,\n IonBackButton,\n IonButton,\n IonContent,\n IonList,\n IonListHeader,\n IonItem,\n IonLabel,\n IonNote,\n IonText,\n IonSpinner,\n ],\n})\nexport class InviteesPageComponent implements OnInit {\n private readonly invitationService = inject(INVITATION_SERVICE);\n private readonly inviteeSource = inject(EVENTUS_INVITEE_SOURCE);\n\n readonly spaceID = input.required<string>();\n readonly eventID = input.required<string>();\n\n protected readonly families = signal<IInviteeFamily[]>([]);\n protected readonly persons = signal<IInviteePerson[]>([]);\n protected readonly invitations = signal<IInvitation[]>([]);\n\n protected readonly loading = signal(true);\n protected readonly submitting = signal(false);\n protected readonly error = signal<string | undefined>(undefined);\n\n ngOnInit(): void {\n this.inviteeSource.families(this.spaceID()).subscribe((f) => this.families.set(f));\n this.inviteeSource.persons(this.spaceID()).subscribe((p) => this.persons.set(p));\n this.loadInvitations();\n }\n\n private loadInvitations(): void {\n this.loading.set(true);\n this.invitationService\n .listInvitations(this.spaceID(), this.eventID())\n .subscribe({\n next: (invitations) => {\n this.invitations.set(invitations);\n this.loading.set(false);\n },\n error: (err: unknown) => {\n this.loading.set(false);\n this.error.set(extractErrorMessage(err, 'Failed to load invitations.'));\n },\n });\n }\n\n /** Invite a family by its space id — no name/detail is sent. */\n protected inviteFamily(family: IInviteeFamily): void {\n this.add({ inviteeType: 'family', familySpaceID: family.id });\n }\n\n /** Invite a person by their contact id — no name/detail is sent. */\n protected invitePerson(person: IInviteePerson): void {\n this.add({ inviteeType: 'person', contactID: person.id });\n }\n\n private add(request: {\n inviteeType: InviteeType;\n familySpaceID?: string;\n contactID?: string;\n }): void {\n if (this.submitting()) {\n return;\n }\n this.submitting.set(true);\n this.error.set(undefined);\n this.invitationService\n .addInvitee(this.spaceID(), this.eventID(), request)\n .subscribe({\n next: (invitation) => {\n this.invitations.update((list) => [...list, invitation]);\n this.submitting.set(false);\n },\n error: (err: unknown) => {\n this.submitting.set(false);\n this.error.set(extractErrorMessage(err, 'Failed to add invitee.'));\n },\n });\n }\n}\n","<ion-header>\n <ion-toolbar>\n <ion-buttons slot=\"start\">\n <ion-back-button defaultHref=\"/\"></ion-back-button>\n </ion-buttons>\n <ion-title>Invitees</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n @if (error()) {\n <ion-text color=\"danger\">{{ error() }}</ion-text>\n }\n\n <ion-list>\n <ion-list-header>Invite a family</ion-list-header>\n @for (family of families(); track family.id) {\n <ion-item>\n <ion-label>{{ family.title }}</ion-label>\n <ion-button\n slot=\"end\"\n [disabled]=\"submitting()\"\n (click)=\"inviteFamily(family)\"\n >\n Invite\n </ion-button>\n </ion-item>\n } @empty {\n <ion-item>\n <ion-note>No families available.</ion-note>\n </ion-item>\n }\n </ion-list>\n\n <ion-list>\n <ion-list-header>Invite a person</ion-list-header>\n @for (person of persons(); track person.id) {\n <ion-item>\n <ion-label>{{ person.name }}</ion-label>\n <ion-button\n slot=\"end\"\n [disabled]=\"submitting()\"\n (click)=\"invitePerson(person)\"\n >\n Invite\n </ion-button>\n </ion-item>\n } @empty {\n <ion-item>\n <ion-note>No persons available.</ion-note>\n </ion-item>\n }\n </ion-list>\n\n <ion-list>\n <ion-list-header>Current invitees</ion-list-header>\n @if (loading()) {\n <ion-item>\n <ion-spinner aria-label=\"Loading invitations\"></ion-spinner>\n </ion-item>\n } @else {\n @for (invitation of invitations(); track invitation.id) {\n <ion-item>\n <ion-label>\n <h3>{{ invitation.inviteeType }}</h3>\n <ion-note>\n {{ invitation.familySpaceID ?? invitation.contactID }}\n </ion-note>\n <p>{{ invitation.createdAt | date: 'short' }}</p>\n </ion-label>\n </ion-item>\n } @empty {\n <ion-item>\n <ion-note>No invitees yet.</ion-note>\n </ion-item>\n }\n }\n </ion-list>\n</ion-content>\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ import { DatePipe } from '@angular/common';
2
+ import * as i0 from '@angular/core';
3
+ import { inject, input, signal, computed, Component } from '@angular/core';
4
+ import { forkJoin } from 'rxjs';
5
+ import { IonHeader, IonToolbar, IonTitle, IonButtons, IonBackButton, IonContent, IonList, IonListHeader, IonItem, IonLabel, IonNote, IonText, IonSpinner } from '@ionic/angular/standalone';
6
+ import { INVITATION_SERVICE, RSVP_SERVICE, extractErrorMessage } from '@sneat/extension-eventus-contract';
7
+
8
+ // Host "responses" view for an event. Correlates the event's invitations
9
+ // (listInvitations) with its RSVPs (listRsvps) by `invitationID` to show which
10
+ // invitees responded and their current response; open-link responses (no
11
+ // invitationID) are listed separately by `selfIdentifiedName`. Because the host
12
+ // always sees the latest stored RSVP, a changed response is reflected here on
13
+ // reload. (AC: attribution-by-link-type, rsvp-can-be-changed)
14
+ class ResponsesPageComponent {
15
+ invitationService = inject(INVITATION_SERVICE);
16
+ rsvpService = inject(RSVP_SERVICE);
17
+ spaceID = input.required(...(ngDevMode ? [{ debugName: "spaceID" }] : /* istanbul ignore next */ []));
18
+ eventID = input.required(...(ngDevMode ? [{ debugName: "eventID" }] : /* istanbul ignore next */ []));
19
+ invitations = signal([], ...(ngDevMode ? [{ debugName: "invitations" }] : /* istanbul ignore next */ []));
20
+ rsvps = signal([], ...(ngDevMode ? [{ debugName: "rsvps" }] : /* istanbul ignore next */ []));
21
+ loading = signal(true, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
22
+ error = signal(undefined, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
23
+ /** Each invitation paired with the RSVP that attributes to it, if any. */
24
+ inviteeResponses = computed(() => {
25
+ const byInvitation = new Map();
26
+ for (const rsvp of this.rsvps()) {
27
+ if (rsvp.invitationID) {
28
+ byInvitation.set(rsvp.invitationID, rsvp);
29
+ }
30
+ }
31
+ return this.invitations().map((invitation) => ({
32
+ invitation,
33
+ rsvp: byInvitation.get(invitation.id),
34
+ }));
35
+ }, ...(ngDevMode ? [{ debugName: "inviteeResponses" }] : /* istanbul ignore next */ []));
36
+ /** Open-link responses (self-identified, no invitation attribution). */
37
+ openResponses = computed(() => this.rsvps().filter((rsvp) => !rsvp.invitationID), ...(ngDevMode ? [{ debugName: "openResponses" }] : /* istanbul ignore next */ []));
38
+ ngOnInit() {
39
+ forkJoin({
40
+ invitations: this.invitationService.listInvitations(this.spaceID(), this.eventID()),
41
+ rsvps: this.rsvpService.listRsvps(this.spaceID(), this.eventID()),
42
+ }).subscribe({
43
+ next: ({ invitations, rsvps }) => {
44
+ this.invitations.set(invitations);
45
+ this.rsvps.set(rsvps);
46
+ this.loading.set(false);
47
+ },
48
+ error: (err) => {
49
+ this.loading.set(false);
50
+ this.error.set(extractErrorMessage(err, 'Failed to load responses.'));
51
+ },
52
+ });
53
+ }
54
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ResponsesPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
55
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: ResponsesPageComponent, isStandalone: true, selector: "eventus-responses-page", inputs: { spaceID: { classPropertyName: "spaceID", publicName: "spaceID", isSignal: true, isRequired: true, transformFunction: null }, eventID: { classPropertyName: "eventID", publicName: "eventID", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<ion-header>\n <ion-toolbar>\n <ion-buttons slot=\"start\">\n <ion-back-button defaultHref=\"/\"></ion-back-button>\n </ion-buttons>\n <ion-title>Responses</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n @if (loading()) {\n <ion-spinner aria-label=\"Loading responses\"></ion-spinner>\n } @else if (error()) {\n <ion-text color=\"danger\">{{ error() }}</ion-text>\n } @else {\n <ion-list>\n <ion-list-header>Invitees</ion-list-header>\n @for (row of inviteeResponses(); track row.invitation.id) {\n <ion-item>\n <ion-label class=\"ion-text-wrap\">\n <h3>{{ row.invitation.inviteeType }}</h3>\n <ion-note>\n {{ row.invitation.familySpaceID ?? row.invitation.contactID }}\n </ion-note>\n @if (row.rsvp; as r) {\n <p>\n Responded: {{ r.status }} \u2014 {{ r.adults }} adult(s),\n {{ r.children }} child(ren)\n </p>\n @if (r.dietary) {\n <p>Dietary: {{ r.dietary }}</p>\n }\n @if (r.comment) {\n <p>{{ r.comment }}</p>\n }\n <p>{{ r.submittedAt | date: 'short' }}</p>\n } @else {\n <p>No response yet.</p>\n }\n </ion-label>\n </ion-item>\n } @empty {\n <ion-item>\n <ion-note>No invitees yet.</ion-note>\n </ion-item>\n }\n </ion-list>\n\n <ion-list>\n <ion-list-header>Open-link responses</ion-list-header>\n @for (r of openResponses(); track r.id) {\n <ion-item>\n <ion-label class=\"ion-text-wrap\">\n <h3>\n {{ r.selfIdentifiedName }}\n {{ r.selfIdentifiedFamilyName }}\n </h3>\n <p>\n {{ r.status }} \u2014 {{ r.adults }} adult(s), {{ r.children }}\n child(ren)\n </p>\n @if (r.dietary) {\n <p>Dietary: {{ r.dietary }}</p>\n }\n @if (r.comment) {\n <p>{{ r.comment }}</p>\n }\n <p>{{ r.submittedAt | date: 'short' }}</p>\n </ion-label>\n </ion-item>\n } @empty {\n <ion-item>\n <ion-note>No open-link responses.</ion-note>\n </ion-item>\n }\n </ion-list>\n }\n</ion-content>\n", dependencies: [{ kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonBackButton, selector: "ion-back-button" }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: IonListHeader, selector: "ion-list-header", inputs: ["color", "lines", "mode"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { kind: "component", type: IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "pipe", type: DatePipe, name: "date" }] });
56
+ }
57
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ResponsesPageComponent, decorators: [{
58
+ type: Component,
59
+ args: [{ selector: 'eventus-responses-page', imports: [
60
+ DatePipe,
61
+ IonHeader,
62
+ IonToolbar,
63
+ IonTitle,
64
+ IonButtons,
65
+ IonBackButton,
66
+ IonContent,
67
+ IonList,
68
+ IonListHeader,
69
+ IonItem,
70
+ IonLabel,
71
+ IonNote,
72
+ IonText,
73
+ IonSpinner,
74
+ ], template: "<ion-header>\n <ion-toolbar>\n <ion-buttons slot=\"start\">\n <ion-back-button defaultHref=\"/\"></ion-back-button>\n </ion-buttons>\n <ion-title>Responses</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n @if (loading()) {\n <ion-spinner aria-label=\"Loading responses\"></ion-spinner>\n } @else if (error()) {\n <ion-text color=\"danger\">{{ error() }}</ion-text>\n } @else {\n <ion-list>\n <ion-list-header>Invitees</ion-list-header>\n @for (row of inviteeResponses(); track row.invitation.id) {\n <ion-item>\n <ion-label class=\"ion-text-wrap\">\n <h3>{{ row.invitation.inviteeType }}</h3>\n <ion-note>\n {{ row.invitation.familySpaceID ?? row.invitation.contactID }}\n </ion-note>\n @if (row.rsvp; as r) {\n <p>\n Responded: {{ r.status }} \u2014 {{ r.adults }} adult(s),\n {{ r.children }} child(ren)\n </p>\n @if (r.dietary) {\n <p>Dietary: {{ r.dietary }}</p>\n }\n @if (r.comment) {\n <p>{{ r.comment }}</p>\n }\n <p>{{ r.submittedAt | date: 'short' }}</p>\n } @else {\n <p>No response yet.</p>\n }\n </ion-label>\n </ion-item>\n } @empty {\n <ion-item>\n <ion-note>No invitees yet.</ion-note>\n </ion-item>\n }\n </ion-list>\n\n <ion-list>\n <ion-list-header>Open-link responses</ion-list-header>\n @for (r of openResponses(); track r.id) {\n <ion-item>\n <ion-label class=\"ion-text-wrap\">\n <h3>\n {{ r.selfIdentifiedName }}\n {{ r.selfIdentifiedFamilyName }}\n </h3>\n <p>\n {{ r.status }} \u2014 {{ r.adults }} adult(s), {{ r.children }}\n child(ren)\n </p>\n @if (r.dietary) {\n <p>Dietary: {{ r.dietary }}</p>\n }\n @if (r.comment) {\n <p>{{ r.comment }}</p>\n }\n <p>{{ r.submittedAt | date: 'short' }}</p>\n </ion-label>\n </ion-item>\n } @empty {\n <ion-item>\n <ion-note>No open-link responses.</ion-note>\n </ion-item>\n }\n </ion-list>\n }\n</ion-content>\n" }]
75
+ }], propDecorators: { spaceID: [{ type: i0.Input, args: [{ isSignal: true, alias: "spaceID", required: true }] }], eventID: [{ type: i0.Input, args: [{ isSignal: true, alias: "eventID", required: true }] }] } });
76
+
77
+ export { ResponsesPageComponent };
78
+ //# sourceMappingURL=sneat-extension-eventus-shared-responses-page.component-L9ayYgBN.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sneat-extension-eventus-shared-responses-page.component-L9ayYgBN.mjs","sources":["../../../../../../libs/extensions/eventus/shared/src/lib/pages/responses/responses-page.component.ts","../../../../../../libs/extensions/eventus/shared/src/lib/pages/responses/responses-page.component.html"],"sourcesContent":["import { DatePipe } from '@angular/common';\nimport { Component, OnInit, computed, inject, input, signal } from '@angular/core';\nimport { forkJoin } from 'rxjs';\nimport {\n IonBackButton,\n IonButtons,\n IonContent,\n IonHeader,\n IonItem,\n IonLabel,\n IonList,\n IonListHeader,\n IonNote,\n IonSpinner,\n IonText,\n IonTitle,\n IonToolbar,\n} from '@ionic/angular/standalone';\nimport {\n extractErrorMessage,\n IInvitation,\n INVITATION_SERVICE,\n IRsvp,\n RSVP_SERVICE,\n} from '@sneat/extension-eventus-contract';\n\n/** An invitation row annotated with the invitee's current response (if any). */\ninterface IInviteeResponse {\n readonly invitation: IInvitation;\n readonly rsvp?: IRsvp;\n}\n\n// Host \"responses\" view for an event. Correlates the event's invitations\n// (listInvitations) with its RSVPs (listRsvps) by `invitationID` to show which\n// invitees responded and their current response; open-link responses (no\n// invitationID) are listed separately by `selfIdentifiedName`. Because the host\n// always sees the latest stored RSVP, a changed response is reflected here on\n// reload. (AC: attribution-by-link-type, rsvp-can-be-changed)\n@Component({\n selector: 'eventus-responses-page',\n templateUrl: './responses-page.component.html',\n imports: [\n DatePipe,\n IonHeader,\n IonToolbar,\n IonTitle,\n IonButtons,\n IonBackButton,\n IonContent,\n IonList,\n IonListHeader,\n IonItem,\n IonLabel,\n IonNote,\n IonText,\n IonSpinner,\n ],\n})\nexport class ResponsesPageComponent implements OnInit {\n private readonly invitationService = inject(INVITATION_SERVICE);\n private readonly rsvpService = inject(RSVP_SERVICE);\n\n readonly spaceID = input.required<string>();\n readonly eventID = input.required<string>();\n\n protected readonly invitations = signal<IInvitation[]>([]);\n protected readonly rsvps = signal<IRsvp[]>([]);\n protected readonly loading = signal(true);\n protected readonly error = signal<string | undefined>(undefined);\n\n /** Each invitation paired with the RSVP that attributes to it, if any. */\n protected readonly inviteeResponses = computed<IInviteeResponse[]>(() => {\n const byInvitation = new Map<string, IRsvp>();\n for (const rsvp of this.rsvps()) {\n if (rsvp.invitationID) {\n byInvitation.set(rsvp.invitationID, rsvp);\n }\n }\n return this.invitations().map((invitation) => ({\n invitation,\n rsvp: byInvitation.get(invitation.id),\n }));\n });\n\n /** Open-link responses (self-identified, no invitation attribution). */\n protected readonly openResponses = computed<IRsvp[]>(() =>\n this.rsvps().filter((rsvp) => !rsvp.invitationID),\n );\n\n ngOnInit(): void {\n forkJoin({\n invitations: this.invitationService.listInvitations(\n this.spaceID(),\n this.eventID(),\n ),\n rsvps: this.rsvpService.listRsvps(this.spaceID(), this.eventID()),\n }).subscribe({\n next: ({ invitations, rsvps }) => {\n this.invitations.set(invitations);\n this.rsvps.set(rsvps);\n this.loading.set(false);\n },\n error: (err: unknown) => {\n this.loading.set(false);\n this.error.set(extractErrorMessage(err, 'Failed to load responses.'));\n },\n });\n }\n}\n","<ion-header>\n <ion-toolbar>\n <ion-buttons slot=\"start\">\n <ion-back-button defaultHref=\"/\"></ion-back-button>\n </ion-buttons>\n <ion-title>Responses</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n @if (loading()) {\n <ion-spinner aria-label=\"Loading responses\"></ion-spinner>\n } @else if (error()) {\n <ion-text color=\"danger\">{{ error() }}</ion-text>\n } @else {\n <ion-list>\n <ion-list-header>Invitees</ion-list-header>\n @for (row of inviteeResponses(); track row.invitation.id) {\n <ion-item>\n <ion-label class=\"ion-text-wrap\">\n <h3>{{ row.invitation.inviteeType }}</h3>\n <ion-note>\n {{ row.invitation.familySpaceID ?? row.invitation.contactID }}\n </ion-note>\n @if (row.rsvp; as r) {\n <p>\n Responded: {{ r.status }} — {{ r.adults }} adult(s),\n {{ r.children }} child(ren)\n </p>\n @if (r.dietary) {\n <p>Dietary: {{ r.dietary }}</p>\n }\n @if (r.comment) {\n <p>{{ r.comment }}</p>\n }\n <p>{{ r.submittedAt | date: 'short' }}</p>\n } @else {\n <p>No response yet.</p>\n }\n </ion-label>\n </ion-item>\n } @empty {\n <ion-item>\n <ion-note>No invitees yet.</ion-note>\n </ion-item>\n }\n </ion-list>\n\n <ion-list>\n <ion-list-header>Open-link responses</ion-list-header>\n @for (r of openResponses(); track r.id) {\n <ion-item>\n <ion-label class=\"ion-text-wrap\">\n <h3>\n {{ r.selfIdentifiedName }}\n {{ r.selfIdentifiedFamilyName }}\n </h3>\n <p>\n {{ r.status }} — {{ r.adults }} adult(s), {{ r.children }}\n child(ren)\n </p>\n @if (r.dietary) {\n <p>Dietary: {{ r.dietary }}</p>\n }\n @if (r.comment) {\n <p>{{ r.comment }}</p>\n }\n <p>{{ r.submittedAt | date: 'short' }}</p>\n </ion-label>\n </ion-item>\n } @empty {\n <ion-item>\n <ion-note>No open-link responses.</ion-note>\n </ion-item>\n }\n </ion-list>\n }\n</ion-content>\n"],"names":[],"mappings":"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"}