@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.
- package/fesm2022/sneat-extension-eventus-shared-bring-along-page.component-DHi-E8P1.mjs +153 -0
- package/fesm2022/sneat-extension-eventus-shared-bring-along-page.component-DHi-E8P1.mjs.map +1 -0
- package/fesm2022/sneat-extension-eventus-shared-create-event-page.component-Da39fu36.mjs +90 -0
- package/fesm2022/sneat-extension-eventus-shared-create-event-page.component-Da39fu36.mjs.map +1 -0
- package/fesm2022/sneat-extension-eventus-shared-edit-event-page.component-lhOpcqld.mjs +103 -0
- package/fesm2022/sneat-extension-eventus-shared-edit-event-page.component-lhOpcqld.mjs.map +1 -0
- package/fesm2022/sneat-extension-eventus-shared-event-page.component-CuOA0EU9.mjs +143 -0
- package/fesm2022/sneat-extension-eventus-shared-event-page.component-CuOA0EU9.mjs.map +1 -0
- package/fesm2022/sneat-extension-eventus-shared-events-list-page.component-DELSoZ2W.mjs +72 -0
- package/fesm2022/sneat-extension-eventus-shared-events-list-page.component-DELSoZ2W.mjs.map +1 -0
- package/fesm2022/sneat-extension-eventus-shared-invitees-page.component-DGG1LDd7.mjs +94 -0
- package/fesm2022/sneat-extension-eventus-shared-invitees-page.component-DGG1LDd7.mjs.map +1 -0
- package/fesm2022/sneat-extension-eventus-shared-responses-page.component-L9ayYgBN.mjs +78 -0
- package/fesm2022/sneat-extension-eventus-shared-responses-page.component-L9ayYgBN.mjs.map +1 -0
- package/fesm2022/sneat-extension-eventus-shared-share-links-page.component-BzZ6tFBc.mjs +92 -0
- package/fesm2022/sneat-extension-eventus-shared-share-links-page.component-BzZ6tFBc.mjs.map +1 -0
- package/fesm2022/sneat-extension-eventus-shared.mjs +270 -0
- package/fesm2022/sneat-extension-eventus-shared.mjs.map +1 -0
- package/package.json +43 -0
- 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":";;;;;;;AAgCA;AACA;AACA;AACA;AACA;AACA;MAqBa,sBAAsB,CAAA;AAChB,IAAA,iBAAiB,GAAG,MAAM,CAAC,kBAAkB,CAAC;AAC9C,IAAA,WAAW,GAAG,MAAM,CAAC,YAAY,CAAC;AAE1C,IAAA,OAAO,GAAG,KAAK,CAAC,QAAQ,6EAAU;AAClC,IAAA,OAAO,GAAG,KAAK,CAAC,QAAQ,6EAAU;AAExB,IAAA,WAAW,GAAG,MAAM,CAAgB,EAAE,kFAAC;AACvC,IAAA,KAAK,GAAG,MAAM,CAAU,EAAE,4EAAC;AAC3B,IAAA,OAAO,GAAG,MAAM,CAAC,IAAI,8EAAC;AACtB,IAAA,KAAK,GAAG,MAAM,CAAqB,SAAS,4EAAC;;AAG7C,IAAA,gBAAgB,GAAG,QAAQ,CAAqB,MAAK;AACtE,QAAA,MAAM,YAAY,GAAG,IAAI,GAAG,EAAiB;QAC7C,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,EAAE;AAC/B,YAAA,IAAI,IAAI,CAAC,YAAY,EAAE;gBACrB,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC;YAC3C;QACF;AACA,QAAA,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC,GAAG,CAAC,CAAC,UAAU,MAAM;YAC7C,UAAU;YACV,IAAI,EAAE,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;AACtC,SAAA,CAAC,CAAC;AACL,IAAA,CAAC,uFAAC;;IAGiB,aAAa,GAAG,QAAQ,CAAU,MACnD,IAAI,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,EAAA,IAAA,SAAA,GAAA,CAAA,EAAA,SAAA,EAAA,eAAA,EAAA,CAAA,8BAAA,EAAA,CAAA,CAClD;IAED,QAAQ,GAAA;AACN,QAAA,QAAQ,CAAC;AACP,YAAA,WAAW,EAAE,IAAI,CAAC,iBAAiB,CAAC,eAAe,CACjD,IAAI,CAAC,OAAO,EAAE,EACd,IAAI,CAAC,OAAO,EAAE,CACf;AACD,YAAA,KAAK,EAAE,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC;SAClE,CAAC,CAAC,SAAS,CAAC;YACX,IAAI,EAAE,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,KAAI;AAC/B,gBAAA,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,WAAW,CAAC;AACjC,gBAAA,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC;AACrB,gBAAA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;YACzB,CAAC;AACD,YAAA,KAAK,EAAE,CAAC,GAAY,KAAI;AACtB,gBAAA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;AACvB,gBAAA,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,mBAAmB,CAAC,GAAG,EAAE,2BAA2B,CAAC,CAAC;YACvE,CAAC;AACF,SAAA,CAAC;IACJ;uGAjDW,sBAAsB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAtB,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,sBAAsB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,wBAAA,EAAA,MAAA,EAAA,EAAA,OAAA,EAAA,EAAA,iBAAA,EAAA,SAAA,EAAA,UAAA,EAAA,SAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,OAAA,EAAA,EAAA,iBAAA,EAAA,SAAA,EAAA,UAAA,EAAA,SAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EC1DnC,y3EA8EA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EDnCI,SAAS,EAAA,QAAA,EAAA,YAAA,EAAA,MAAA,EAAA,CAAA,UAAA,EAAA,MAAA,EAAA,aAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EACT,UAAU,EAAA,QAAA,EAAA,aAAA,EAAA,MAAA,EAAA,CAAA,OAAA,EAAA,MAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EACV,QAAQ,EAAA,QAAA,EAAA,WAAA,EAAA,MAAA,EAAA,CAAA,OAAA,EAAA,MAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EACR,UAAU,EAAA,QAAA,EAAA,aAAA,EAAA,MAAA,EAAA,CAAA,UAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EACV,aAAa,EAAA,QAAA,EAAA,iBAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EACb,UAAU,EAAA,QAAA,EAAA,aAAA,EAAA,MAAA,EAAA,CAAA,OAAA,EAAA,oBAAA,EAAA,iBAAA,EAAA,YAAA,EAAA,cAAA,EAAA,SAAA,EAAA,SAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EACV,OAAO,EAAA,QAAA,EAAA,UAAA,EAAA,MAAA,EAAA,CAAA,OAAA,EAAA,OAAA,EAAA,MAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EACP,aAAa,EAAA,QAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,CAAA,OAAA,EAAA,OAAA,EAAA,MAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EACb,OAAO,EAAA,QAAA,EAAA,UAAA,EAAA,MAAA,EAAA,CAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,YAAA,EAAA,UAAA,EAAA,UAAA,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,iBAAA,EAAA,QAAA,EAAA,MAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EACP,QAAQ,EAAA,QAAA,EAAA,WAAA,EAAA,MAAA,EAAA,CAAA,OAAA,EAAA,MAAA,EAAA,UAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EACR,OAAO,EAAA,QAAA,EAAA,UAAA,EAAA,MAAA,EAAA,CAAA,OAAA,EAAA,MAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EACP,OAAO,EAAA,QAAA,EAAA,UAAA,EAAA,MAAA,EAAA,CAAA,OAAA,EAAA,MAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EACP,UAAU,oGAbV,QAAQ,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,EAAA,CAAA;;2FAgBC,sBAAsB,EAAA,UAAA,EAAA,CAAA;kBApBlC,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,wBAAwB,EAAA,OAAA,EAEzB;wBACP,QAAQ;wBACR,SAAS;wBACT,UAAU;wBACV,QAAQ;wBACR,UAAU;wBACV,aAAa;wBACb,UAAU;wBACV,OAAO;wBACP,aAAa;wBACb,OAAO;wBACP,QAAQ;wBACR,OAAO;wBACP,OAAO;wBACP,UAAU;AACX,qBAAA,EAAA,QAAA,EAAA,y3EAAA,EAAA;;;;;"}
|