@sneat/extension-debtus-ui 0.1.0
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/README.md +4 -0
- package/fesm2022/sneat-extension-debtus-ui-debtus-contact-details-page.component-DIAVE0D6.mjs +106 -0
- package/fesm2022/sneat-extension-debtus-ui-debtus-contact-details-page.component-DIAVE0D6.mjs.map +1 -0
- package/fesm2022/sneat-extension-debtus-ui-debtus-contacts-page.component-DYtFeNsM.mjs +74 -0
- package/fesm2022/sneat-extension-debtus-ui-debtus-contacts-page.component-DYtFeNsM.mjs.map +1 -0
- package/fesm2022/sneat-extension-debtus-ui-debtus-home-page.component-BCNMpirX.mjs +97 -0
- package/fesm2022/sneat-extension-debtus-ui-debtus-home-page.component-BCNMpirX.mjs.map +1 -0
- package/fesm2022/sneat-extension-debtus-ui-new-transfer-page.component-CT-6X_0e.mjs +196 -0
- package/fesm2022/sneat-extension-debtus-ui-new-transfer-page.component-CT-6X_0e.mjs.map +1 -0
- package/fesm2022/sneat-extension-debtus-ui-settle-up-page.component-CUoNSuAE.mjs +163 -0
- package/fesm2022/sneat-extension-debtus-ui-settle-up-page.component-CUoNSuAE.mjs.map +1 -0
- package/fesm2022/sneat-extension-debtus-ui-transfer-details-page.component-uo5lP557.mjs +97 -0
- package/fesm2022/sneat-extension-debtus-ui-transfer-details-page.component-uo5lP557.mjs.map +1 -0
- package/fesm2022/sneat-extension-debtus-ui.mjs +153 -0
- package/fesm2022/sneat-extension-debtus-ui.mjs.map +1 -0
- package/package.json +36 -0
- package/types/sneat-extension-debtus-ui.d.ts +37 -0
package/README.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { DatePipe } from '@angular/common';
|
|
2
|
+
import * as i0 from '@angular/core';
|
|
3
|
+
import { inject, signal, Component } from '@angular/core';
|
|
4
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
5
|
+
import { ToastController, IonHeader, IonToolbar, IonButtons, IonBackButton, IonTitle, IonContent, IonCard, IonCardHeader, IonCardTitle, IonCardContent, IonList, IonItem, IonLabel, IonNote, IonBadge, IonButton, IonIcon, IonSpinner } from '@ionic/angular';
|
|
6
|
+
import { DemoDataBannerComponent } from './sneat-extension-debtus-ui.mjs';
|
|
7
|
+
import { DEBTUS_SERVICE, formatAmount, formatSignedBalance } from '@sneat/extension-debtus-contract';
|
|
8
|
+
import { SpacePageBaseComponent, SpaceComponentBaseParams } from '@sneat/space-components';
|
|
9
|
+
import { combineLatest, switchMap } from 'rxjs';
|
|
10
|
+
|
|
11
|
+
// Contact detail — balance with one counterparty plus their transfer history.
|
|
12
|
+
// Actions mirror the bot: record a lend/borrow, remind, and settle up.
|
|
13
|
+
class DebtusContactDetailsPageComponent extends SpacePageBaseComponent {
|
|
14
|
+
debtusService = inject(DEBTUS_SERVICE);
|
|
15
|
+
toastController = inject(ToastController);
|
|
16
|
+
$loading = signal(true, /* @ts-ignore */
|
|
17
|
+
...(ngDevMode ? [{ debugName: "$loading" }] : /* istanbul ignore next */ []));
|
|
18
|
+
$error = signal(undefined, /* @ts-ignore */
|
|
19
|
+
...(ngDevMode ? [{ debugName: "$error" }] : /* istanbul ignore next */ []));
|
|
20
|
+
$contact = signal(undefined, /* @ts-ignore */
|
|
21
|
+
...(ngDevMode ? [{ debugName: "$contact" }] : /* istanbul ignore next */ []));
|
|
22
|
+
$transfers = signal([], /* @ts-ignore */
|
|
23
|
+
...(ngDevMode ? [{ debugName: "$transfers" }] : /* istanbul ignore next */ []));
|
|
24
|
+
contactID = '';
|
|
25
|
+
formatAmount = formatAmount;
|
|
26
|
+
formatSignedBalance = formatSignedBalance;
|
|
27
|
+
constructor() {
|
|
28
|
+
super();
|
|
29
|
+
this.$defaultBackUrlSpacePath.set('debtus-contacts');
|
|
30
|
+
combineLatest([this.spaceIDChanged$, this.route.paramMap])
|
|
31
|
+
.pipe(switchMap(([spaceID, params]) => {
|
|
32
|
+
this.contactID = params.get('contactID') ?? '';
|
|
33
|
+
this.$loading.set(true);
|
|
34
|
+
this.$error.set(undefined);
|
|
35
|
+
return combineLatest([
|
|
36
|
+
this.debtusService.getContactBalance(spaceID ?? '', this.contactID),
|
|
37
|
+
this.debtusService.getTransfers(spaceID ?? '', this.contactID),
|
|
38
|
+
]);
|
|
39
|
+
}), takeUntilDestroyed())
|
|
40
|
+
.subscribe({
|
|
41
|
+
next: ([contact, transfers]) => {
|
|
42
|
+
this.$contact.set(contact);
|
|
43
|
+
this.$transfers.set(transfers);
|
|
44
|
+
this.$loading.set(false);
|
|
45
|
+
},
|
|
46
|
+
error: (err) => {
|
|
47
|
+
this.$error.set('Failed to load contact');
|
|
48
|
+
this.$loading.set(false);
|
|
49
|
+
this.errorLogger.logError(err, 'Failed to load debtus contact');
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
recordLend() {
|
|
54
|
+
this.spaceNav.navigateForwardToSpacePage(this.space, `new-transfer?direction=lend&contactID=${this.contactID}`);
|
|
55
|
+
}
|
|
56
|
+
recordBorrow() {
|
|
57
|
+
this.spaceNav.navigateForwardToSpacePage(this.space, `new-transfer?direction=borrow&contactID=${this.contactID}`);
|
|
58
|
+
}
|
|
59
|
+
settleUp() {
|
|
60
|
+
this.spaceNav.navigateForwardToSpacePage(this.space, `settle-up?contactID=${this.contactID}`);
|
|
61
|
+
}
|
|
62
|
+
async remind() {
|
|
63
|
+
// Fable: prototype demo data — no wired reminder-send HTTP endpoint yet.
|
|
64
|
+
// The bot schedules reminders via a background cron; here we just confirm
|
|
65
|
+
// the intent. SWAP: call a POST reminder endpoint once available.
|
|
66
|
+
const toast = await this.toastController.create({
|
|
67
|
+
message: `Reminder queued for ${this.$contact()?.title ?? 'contact'} (demo).`,
|
|
68
|
+
duration: 2500,
|
|
69
|
+
color: 'medium',
|
|
70
|
+
});
|
|
71
|
+
await toast.present();
|
|
72
|
+
}
|
|
73
|
+
openTransfer(transferID) {
|
|
74
|
+
this.spaceNav.navigateForwardToSpacePage(this.space, `transfer/${transferID}`);
|
|
75
|
+
}
|
|
76
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: DebtusContactDetailsPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
77
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: DebtusContactDetailsPageComponent, isStandalone: true, selector: "sneat-debtus-contact-details-page", providers: [SpaceComponentBaseParams], usesInheritance: true, ngImport: i0, template: "<ion-header>\n <ion-toolbar color=\"light\">\n <ion-buttons slot=\"start\">\n <ion-back-button [defaultHref]=\"$defaultBackUrl()\" />\n </ion-buttons>\n <ion-title>{{ $contact()?.title ?? 'Contact' }}</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"cardy\">\n <!-- Reads on this page are still demo fixtures (see DebtusService). -->\n <sneat-debtus-demo-data-banner />\n\n @if ($loading()) {\n <div class=\"ion-padding ion-text-center\">\n <ion-spinner name=\"dots\" />\n </div>\n } @else if ($error()) {\n <div class=\"ion-padding\">\n <ion-note color=\"danger\">{{ $error() }}</ion-note>\n </div>\n } @else {\n <ion-card>\n <ion-card-header>\n <ion-card-title>Balance</ion-card-title>\n </ion-card-header>\n <ion-card-content>\n {{ formatSignedBalance($contact()?.balance ?? {}) }}\n @if ($contact()?.counterpartySpaceID) {\n <p>\n <ion-note color=\"medium\">\n Cross-space loan \u2014 counterparty is in space\n {{ $contact()?.counterpartySpaceID }}.\n </ion-note>\n </p>\n }\n </ion-card-content>\n </ion-card>\n\n <ion-card>\n <ion-card-content>\n <ion-button expand=\"block\" (click)=\"recordLend()\">\n <ion-icon name=\"arrow-up-outline\" slot=\"start\" />\n Record what I lent\n </ion-button>\n <ion-button expand=\"block\" fill=\"outline\" (click)=\"recordBorrow()\">\n <ion-icon name=\"arrow-down-outline\" slot=\"start\" />\n Record what I borrowed\n </ion-button>\n <ion-button expand=\"block\" fill=\"clear\" (click)=\"remind()\">\n <ion-icon name=\"notifications-outline\" slot=\"start\" />\n Remind\n </ion-button>\n <ion-button\n expand=\"block\"\n color=\"success\"\n fill=\"outline\"\n (click)=\"settleUp()\"\n >\n <ion-icon name=\"checkmark-done-outline\" slot=\"start\" />\n Settle up\n </ion-button>\n </ion-card-content>\n </ion-card>\n\n <ion-card>\n <ion-card-header>\n <ion-card-title>History</ion-card-title>\n </ion-card-header>\n @if ($transfers().length) {\n <ion-list>\n @for (t of $transfers(); track t.id) {\n <ion-item button (click)=\"openTransfer(t.id)\">\n <ion-label>\n <h2>\n {{ t.direction === 'lend' ? 'You lent' : 'You borrowed' }}\n @if (t.isReturn) { (return) }\n </h2>\n <p>{{ t.note || (t.created | date: 'mediumDate') }}</p>\n </ion-label>\n <ion-badge\n slot=\"end\"\n [color]=\"t.direction === 'lend' ? 'success' : 'warning'\"\n >\n {{ formatAmount(t.amount) }}\n </ion-badge>\n </ion-item>\n }\n </ion-list>\n } @else {\n <ion-card-content>\n <ion-note>No transfers with this contact yet.</ion-note>\n </ion-card-content>\n }\n </ion-card>\n }\n</ion-content>\n", dependencies: [{ kind: "component", type: DemoDataBannerComponent, selector: "sneat-debtus-demo-data-banner" }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonBackButton, selector: "ion-back-button" }, { kind: "component", type: IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"], outputs: ["ionScrollStart", "ionScroll", "ionScrollEnd"] }, { kind: "component", type: IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonCardHeader, selector: "ion-card-header", inputs: ["color", "mode", "translucent"] }, { kind: "component", type: IonCardTitle, selector: "ion-card-title", inputs: ["color", "mode"] }, { kind: "component", type: IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { 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: IonBadge, selector: "ion-badge", inputs: ["color", "mode"] }, { 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"], outputs: ["ionFocus", "ionBlur"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "pipe", type: DatePipe, name: "date" }] });
|
|
78
|
+
}
|
|
79
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: DebtusContactDetailsPageComponent, decorators: [{
|
|
80
|
+
type: Component,
|
|
81
|
+
args: [{ selector: 'sneat-debtus-contact-details-page', imports: [
|
|
82
|
+
DemoDataBannerComponent,
|
|
83
|
+
IonHeader,
|
|
84
|
+
IonToolbar,
|
|
85
|
+
IonButtons,
|
|
86
|
+
IonBackButton,
|
|
87
|
+
IonTitle,
|
|
88
|
+
IonContent,
|
|
89
|
+
IonCard,
|
|
90
|
+
IonCardHeader,
|
|
91
|
+
IonCardTitle,
|
|
92
|
+
IonCardContent,
|
|
93
|
+
IonList,
|
|
94
|
+
IonItem,
|
|
95
|
+
IonLabel,
|
|
96
|
+
IonNote,
|
|
97
|
+
IonBadge,
|
|
98
|
+
IonButton,
|
|
99
|
+
IonIcon,
|
|
100
|
+
IonSpinner,
|
|
101
|
+
DatePipe,
|
|
102
|
+
], providers: [SpaceComponentBaseParams], template: "<ion-header>\n <ion-toolbar color=\"light\">\n <ion-buttons slot=\"start\">\n <ion-back-button [defaultHref]=\"$defaultBackUrl()\" />\n </ion-buttons>\n <ion-title>{{ $contact()?.title ?? 'Contact' }}</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"cardy\">\n <!-- Reads on this page are still demo fixtures (see DebtusService). -->\n <sneat-debtus-demo-data-banner />\n\n @if ($loading()) {\n <div class=\"ion-padding ion-text-center\">\n <ion-spinner name=\"dots\" />\n </div>\n } @else if ($error()) {\n <div class=\"ion-padding\">\n <ion-note color=\"danger\">{{ $error() }}</ion-note>\n </div>\n } @else {\n <ion-card>\n <ion-card-header>\n <ion-card-title>Balance</ion-card-title>\n </ion-card-header>\n <ion-card-content>\n {{ formatSignedBalance($contact()?.balance ?? {}) }}\n @if ($contact()?.counterpartySpaceID) {\n <p>\n <ion-note color=\"medium\">\n Cross-space loan \u2014 counterparty is in space\n {{ $contact()?.counterpartySpaceID }}.\n </ion-note>\n </p>\n }\n </ion-card-content>\n </ion-card>\n\n <ion-card>\n <ion-card-content>\n <ion-button expand=\"block\" (click)=\"recordLend()\">\n <ion-icon name=\"arrow-up-outline\" slot=\"start\" />\n Record what I lent\n </ion-button>\n <ion-button expand=\"block\" fill=\"outline\" (click)=\"recordBorrow()\">\n <ion-icon name=\"arrow-down-outline\" slot=\"start\" />\n Record what I borrowed\n </ion-button>\n <ion-button expand=\"block\" fill=\"clear\" (click)=\"remind()\">\n <ion-icon name=\"notifications-outline\" slot=\"start\" />\n Remind\n </ion-button>\n <ion-button\n expand=\"block\"\n color=\"success\"\n fill=\"outline\"\n (click)=\"settleUp()\"\n >\n <ion-icon name=\"checkmark-done-outline\" slot=\"start\" />\n Settle up\n </ion-button>\n </ion-card-content>\n </ion-card>\n\n <ion-card>\n <ion-card-header>\n <ion-card-title>History</ion-card-title>\n </ion-card-header>\n @if ($transfers().length) {\n <ion-list>\n @for (t of $transfers(); track t.id) {\n <ion-item button (click)=\"openTransfer(t.id)\">\n <ion-label>\n <h2>\n {{ t.direction === 'lend' ? 'You lent' : 'You borrowed' }}\n @if (t.isReturn) { (return) }\n </h2>\n <p>{{ t.note || (t.created | date: 'mediumDate') }}</p>\n </ion-label>\n <ion-badge\n slot=\"end\"\n [color]=\"t.direction === 'lend' ? 'success' : 'warning'\"\n >\n {{ formatAmount(t.amount) }}\n </ion-badge>\n </ion-item>\n }\n </ion-list>\n } @else {\n <ion-card-content>\n <ion-note>No transfers with this contact yet.</ion-note>\n </ion-card-content>\n }\n </ion-card>\n }\n</ion-content>\n" }]
|
|
103
|
+
}], ctorParameters: () => [] });
|
|
104
|
+
|
|
105
|
+
export { DebtusContactDetailsPageComponent };
|
|
106
|
+
//# sourceMappingURL=sneat-extension-debtus-ui-debtus-contact-details-page.component-DIAVE0D6.mjs.map
|
package/fesm2022/sneat-extension-debtus-ui-debtus-contact-details-page.component-DIAVE0D6.mjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sneat-extension-debtus-ui-debtus-contact-details-page.component-DIAVE0D6.mjs","sources":["../../../../../../libs/extensions/debtus/ui/src/lib/pages/debtus-contact-details/debtus-contact-details-page.component.ts","../../../../../../libs/extensions/debtus/ui/src/lib/pages/debtus-contact-details/debtus-contact-details-page.component.html"],"sourcesContent":["import { DatePipe } from '@angular/common';\nimport { Component, inject, signal } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport {\n IonBackButton,\n IonBadge,\n IonButton,\n IonButtons,\n IonCard,\n IonCardContent,\n IonCardHeader,\n IonCardTitle,\n IonContent,\n IonHeader,\n IonIcon,\n IonItem,\n IonLabel,\n IonList,\n IonNote,\n IonSpinner,\n IonTitle,\n IonToolbar,\n ToastController,\n} from '@ionic/angular';\nimport { DemoDataBannerComponent } from '../../components/demo-data-banner/demo-data-banner.component';\nimport {\n DEBTUS_SERVICE,\n IContactBalance,\n IDebtusTransfer,\n formatAmount,\n formatSignedBalance,\n} from '@sneat/extension-debtus-contract';\nimport {\n SpaceComponentBaseParams,\n SpacePageBaseComponent,\n} from '@sneat/space-components';\nimport { combineLatest, switchMap } from 'rxjs';\n\n// Contact detail — balance with one counterparty plus their transfer history.\n// Actions mirror the bot: record a lend/borrow, remind, and settle up.\n@Component({\n selector: 'sneat-debtus-contact-details-page',\n templateUrl: './debtus-contact-details-page.component.html',\n imports: [\n DemoDataBannerComponent,\n IonHeader,\n IonToolbar,\n IonButtons,\n IonBackButton,\n IonTitle,\n IonContent,\n IonCard,\n IonCardHeader,\n IonCardTitle,\n IonCardContent,\n IonList,\n IonItem,\n IonLabel,\n IonNote,\n IonBadge,\n IonButton,\n IonIcon,\n IonSpinner,\n DatePipe,\n ],\n providers: [SpaceComponentBaseParams],\n})\nexport class DebtusContactDetailsPageComponent extends SpacePageBaseComponent {\n private readonly debtusService = inject(DEBTUS_SERVICE);\n private readonly toastController = inject(ToastController);\n\n protected readonly $loading = signal(true);\n protected readonly $error = signal<string | undefined>(undefined);\n protected readonly $contact = signal<IContactBalance | undefined>(undefined);\n protected readonly $transfers = signal<IDebtusTransfer[]>([]);\n protected contactID = '';\n\n protected readonly formatAmount = formatAmount;\n protected readonly formatSignedBalance = formatSignedBalance;\n\n constructor() {\n super();\n this.$defaultBackUrlSpacePath.set('debtus-contacts');\n combineLatest([this.spaceIDChanged$, this.route.paramMap])\n .pipe(\n switchMap(([spaceID, params]) => {\n this.contactID = params.get('contactID') ?? '';\n this.$loading.set(true);\n this.$error.set(undefined);\n return combineLatest([\n this.debtusService.getContactBalance(\n spaceID ?? '',\n this.contactID,\n ),\n this.debtusService.getTransfers(spaceID ?? '', this.contactID),\n ]);\n }),\n takeUntilDestroyed(),\n )\n .subscribe({\n next: ([contact, transfers]) => {\n this.$contact.set(contact);\n this.$transfers.set(transfers);\n this.$loading.set(false);\n },\n error: (err) => {\n this.$error.set('Failed to load contact');\n this.$loading.set(false);\n this.errorLogger.logError(err, 'Failed to load debtus contact');\n },\n });\n }\n\n protected recordLend(): void {\n this.spaceNav.navigateForwardToSpacePage(\n this.space,\n `new-transfer?direction=lend&contactID=${this.contactID}`,\n );\n }\n\n protected recordBorrow(): void {\n this.spaceNav.navigateForwardToSpacePage(\n this.space,\n `new-transfer?direction=borrow&contactID=${this.contactID}`,\n );\n }\n\n protected settleUp(): void {\n this.spaceNav.navigateForwardToSpacePage(\n this.space,\n `settle-up?contactID=${this.contactID}`,\n );\n }\n\n protected async remind(): Promise<void> {\n // Fable: prototype demo data — no wired reminder-send HTTP endpoint yet.\n // The bot schedules reminders via a background cron; here we just confirm\n // the intent. SWAP: call a POST reminder endpoint once available.\n const toast = await this.toastController.create({\n message: `Reminder queued for ${this.$contact()?.title ?? 'contact'} (demo).`,\n duration: 2500,\n color: 'medium',\n });\n await toast.present();\n }\n\n protected openTransfer(transferID: string): void {\n this.spaceNav.navigateForwardToSpacePage(\n this.space,\n `transfer/${transferID}`,\n );\n }\n}\n","<ion-header>\n <ion-toolbar color=\"light\">\n <ion-buttons slot=\"start\">\n <ion-back-button [defaultHref]=\"$defaultBackUrl()\" />\n </ion-buttons>\n <ion-title>{{ $contact()?.title ?? 'Contact' }}</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"cardy\">\n <!-- Reads on this page are still demo fixtures (see DebtusService). -->\n <sneat-debtus-demo-data-banner />\n\n @if ($loading()) {\n <div class=\"ion-padding ion-text-center\">\n <ion-spinner name=\"dots\" />\n </div>\n } @else if ($error()) {\n <div class=\"ion-padding\">\n <ion-note color=\"danger\">{{ $error() }}</ion-note>\n </div>\n } @else {\n <ion-card>\n <ion-card-header>\n <ion-card-title>Balance</ion-card-title>\n </ion-card-header>\n <ion-card-content>\n {{ formatSignedBalance($contact()?.balance ?? {}) }}\n @if ($contact()?.counterpartySpaceID) {\n <p>\n <ion-note color=\"medium\">\n Cross-space loan — counterparty is in space\n {{ $contact()?.counterpartySpaceID }}.\n </ion-note>\n </p>\n }\n </ion-card-content>\n </ion-card>\n\n <ion-card>\n <ion-card-content>\n <ion-button expand=\"block\" (click)=\"recordLend()\">\n <ion-icon name=\"arrow-up-outline\" slot=\"start\" />\n Record what I lent\n </ion-button>\n <ion-button expand=\"block\" fill=\"outline\" (click)=\"recordBorrow()\">\n <ion-icon name=\"arrow-down-outline\" slot=\"start\" />\n Record what I borrowed\n </ion-button>\n <ion-button expand=\"block\" fill=\"clear\" (click)=\"remind()\">\n <ion-icon name=\"notifications-outline\" slot=\"start\" />\n Remind\n </ion-button>\n <ion-button\n expand=\"block\"\n color=\"success\"\n fill=\"outline\"\n (click)=\"settleUp()\"\n >\n <ion-icon name=\"checkmark-done-outline\" slot=\"start\" />\n Settle up\n </ion-button>\n </ion-card-content>\n </ion-card>\n\n <ion-card>\n <ion-card-header>\n <ion-card-title>History</ion-card-title>\n </ion-card-header>\n @if ($transfers().length) {\n <ion-list>\n @for (t of $transfers(); track t.id) {\n <ion-item button (click)=\"openTransfer(t.id)\">\n <ion-label>\n <h2>\n {{ t.direction === 'lend' ? 'You lent' : 'You borrowed' }}\n @if (t.isReturn) { (return) }\n </h2>\n <p>{{ t.note || (t.created | date: 'mediumDate') }}</p>\n </ion-label>\n <ion-badge\n slot=\"end\"\n [color]=\"t.direction === 'lend' ? 'success' : 'warning'\"\n >\n {{ formatAmount(t.amount) }}\n </ion-badge>\n </ion-item>\n }\n </ion-list>\n } @else {\n <ion-card-content>\n <ion-note>No transfers with this contact yet.</ion-note>\n </ion-card-content>\n }\n </ion-card>\n }\n</ion-content>\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, signal, Component } from '@angular/core';
|
|
3
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
4
|
+
import { IonHeader, IonToolbar, IonButtons, IonBackButton, IonTitle, IonContent, IonList, IonItem, IonLabel, IonNote, IonSpinner } from '@ionic/angular';
|
|
5
|
+
import { DemoDataBannerComponent } from './sneat-extension-debtus-ui.mjs';
|
|
6
|
+
import { DEBTUS_SERVICE, formatSignedBalance } from '@sneat/extension-debtus-contract';
|
|
7
|
+
import { SpacePageBaseComponent, SpaceComponentBaseParams } from '@sneat/space-components';
|
|
8
|
+
import { ClassName } from '@sneat/ui';
|
|
9
|
+
import { switchMap } from 'rxjs';
|
|
10
|
+
|
|
11
|
+
// Contacts list — counterparties (contactus space contacts) with their net
|
|
12
|
+
// balance. Tap a row to open the per-contact detail.
|
|
13
|
+
class DebtusContactsPageComponent extends SpacePageBaseComponent {
|
|
14
|
+
debtusService = inject(DEBTUS_SERVICE);
|
|
15
|
+
$loading = signal(true, /* @ts-ignore */
|
|
16
|
+
...(ngDevMode ? [{ debugName: "$loading" }] : /* istanbul ignore next */ []));
|
|
17
|
+
$error = signal(undefined, /* @ts-ignore */
|
|
18
|
+
...(ngDevMode ? [{ debugName: "$error" }] : /* istanbul ignore next */ []));
|
|
19
|
+
$contacts = signal([], /* @ts-ignore */
|
|
20
|
+
...(ngDevMode ? [{ debugName: "$contacts" }] : /* istanbul ignore next */ []));
|
|
21
|
+
formatSignedBalance = formatSignedBalance;
|
|
22
|
+
constructor() {
|
|
23
|
+
super();
|
|
24
|
+
this.$defaultBackUrlSpacePath.set('debts');
|
|
25
|
+
this.spaceIDChanged$
|
|
26
|
+
.pipe(switchMap((spaceID) => {
|
|
27
|
+
this.$loading.set(true);
|
|
28
|
+
this.$error.set(undefined);
|
|
29
|
+
return this.debtusService.getContactBalances(spaceID ?? '');
|
|
30
|
+
}), takeUntilDestroyed())
|
|
31
|
+
.subscribe({
|
|
32
|
+
next: (contacts) => {
|
|
33
|
+
this.$contacts.set(contacts);
|
|
34
|
+
this.$loading.set(false);
|
|
35
|
+
},
|
|
36
|
+
error: (err) => {
|
|
37
|
+
this.$error.set('Failed to load contacts');
|
|
38
|
+
this.$loading.set(false);
|
|
39
|
+
this.errorLogger.logError(err, 'Failed to load debtus contacts');
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
openContact(contactID) {
|
|
44
|
+
this.spaceNav.navigateForwardToSpacePage(this.space, `debtus-contact/${contactID}`);
|
|
45
|
+
}
|
|
46
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: DebtusContactsPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
47
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: DebtusContactsPageComponent, isStandalone: true, selector: "sneat-debtus-contacts-page", providers: [
|
|
48
|
+
{ provide: ClassName, useValue: 'DebtusContactsPageComponent' },
|
|
49
|
+
SpaceComponentBaseParams,
|
|
50
|
+
], usesInheritance: true, ngImport: i0, template: "<ion-header>\n <ion-toolbar color=\"light\">\n <ion-buttons slot=\"start\">\n <ion-back-button [defaultHref]=\"$defaultBackUrl()\" />\n </ion-buttons>\n <ion-title>Contacts</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"cardy\">\n <!-- Reads on this page are still demo fixtures (see DebtusService). -->\n <sneat-debtus-demo-data-banner />\n\n @if ($loading()) {\n <div class=\"ion-padding ion-text-center\">\n <ion-spinner name=\"dots\" />\n </div>\n } @else if ($error()) {\n <div class=\"ion-padding\">\n <ion-note color=\"danger\">{{ $error() }}</ion-note>\n </div>\n } @else if ($contacts().length) {\n <ion-list>\n @for (c of $contacts(); track c.contactID) {\n <ion-item button (click)=\"openContact(c.contactID)\">\n <ion-label>\n <h2>{{ c.title }}</h2>\n <p>{{ formatSignedBalance(c.balance) }}</p>\n @if (c.counterpartySpaceID) {\n <ion-note color=\"medium\"\n >cross-space \u00B7 {{ c.counterpartySpaceID }}</ion-note\n >\n }\n </ion-label>\n </ion-item>\n }\n </ion-list>\n } @else {\n <div class=\"ion-padding ion-text-center\">\n <ion-note>No contacts with debts yet.</ion-note>\n </div>\n }\n</ion-content>\n", dependencies: [{ kind: "component", type: DemoDataBannerComponent, selector: "sneat-debtus-demo-data-banner" }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonBackButton, selector: "ion-back-button" }, { kind: "component", type: IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"], outputs: ["ionScrollStart", "ionScroll", "ionScrollEnd"] }, { 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: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }] });
|
|
51
|
+
}
|
|
52
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: DebtusContactsPageComponent, decorators: [{
|
|
53
|
+
type: Component,
|
|
54
|
+
args: [{ selector: 'sneat-debtus-contacts-page', imports: [
|
|
55
|
+
DemoDataBannerComponent,
|
|
56
|
+
IonHeader,
|
|
57
|
+
IonToolbar,
|
|
58
|
+
IonButtons,
|
|
59
|
+
IonBackButton,
|
|
60
|
+
IonTitle,
|
|
61
|
+
IonContent,
|
|
62
|
+
IonList,
|
|
63
|
+
IonItem,
|
|
64
|
+
IonLabel,
|
|
65
|
+
IonNote,
|
|
66
|
+
IonSpinner,
|
|
67
|
+
], providers: [
|
|
68
|
+
{ provide: ClassName, useValue: 'DebtusContactsPageComponent' },
|
|
69
|
+
SpaceComponentBaseParams,
|
|
70
|
+
], template: "<ion-header>\n <ion-toolbar color=\"light\">\n <ion-buttons slot=\"start\">\n <ion-back-button [defaultHref]=\"$defaultBackUrl()\" />\n </ion-buttons>\n <ion-title>Contacts</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"cardy\">\n <!-- Reads on this page are still demo fixtures (see DebtusService). -->\n <sneat-debtus-demo-data-banner />\n\n @if ($loading()) {\n <div class=\"ion-padding ion-text-center\">\n <ion-spinner name=\"dots\" />\n </div>\n } @else if ($error()) {\n <div class=\"ion-padding\">\n <ion-note color=\"danger\">{{ $error() }}</ion-note>\n </div>\n } @else if ($contacts().length) {\n <ion-list>\n @for (c of $contacts(); track c.contactID) {\n <ion-item button (click)=\"openContact(c.contactID)\">\n <ion-label>\n <h2>{{ c.title }}</h2>\n <p>{{ formatSignedBalance(c.balance) }}</p>\n @if (c.counterpartySpaceID) {\n <ion-note color=\"medium\"\n >cross-space \u00B7 {{ c.counterpartySpaceID }}</ion-note\n >\n }\n </ion-label>\n </ion-item>\n }\n </ion-list>\n } @else {\n <div class=\"ion-padding ion-text-center\">\n <ion-note>No contacts with debts yet.</ion-note>\n </div>\n }\n</ion-content>\n" }]
|
|
71
|
+
}], ctorParameters: () => [] });
|
|
72
|
+
|
|
73
|
+
export { DebtusContactsPageComponent };
|
|
74
|
+
//# sourceMappingURL=sneat-extension-debtus-ui-debtus-contacts-page.component-DYtFeNsM.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sneat-extension-debtus-ui-debtus-contacts-page.component-DYtFeNsM.mjs","sources":["../../../../../../libs/extensions/debtus/ui/src/lib/pages/debtus-contacts/debtus-contacts-page.component.ts","../../../../../../libs/extensions/debtus/ui/src/lib/pages/debtus-contacts/debtus-contacts-page.component.html"],"sourcesContent":["import { Component, inject, signal } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport {\n IonBackButton,\n IonButtons,\n IonContent,\n IonHeader,\n IonItem,\n IonLabel,\n IonList,\n IonNote,\n IonSpinner,\n IonTitle,\n IonToolbar,\n} from '@ionic/angular';\nimport { DemoDataBannerComponent } from '../../components/demo-data-banner/demo-data-banner.component';\nimport {\n DEBTUS_SERVICE,\n IContactBalance,\n formatSignedBalance,\n} from '@sneat/extension-debtus-contract';\nimport {\n SpaceComponentBaseParams,\n SpacePageBaseComponent,\n} from '@sneat/space-components';\nimport { ClassName } from '@sneat/ui';\nimport { switchMap } from 'rxjs';\n\n// Contacts list — counterparties (contactus space contacts) with their net\n// balance. Tap a row to open the per-contact detail.\n@Component({\n selector: 'sneat-debtus-contacts-page',\n templateUrl: './debtus-contacts-page.component.html',\n imports: [\n DemoDataBannerComponent,\n IonHeader,\n IonToolbar,\n IonButtons,\n IonBackButton,\n IonTitle,\n IonContent,\n IonList,\n IonItem,\n IonLabel,\n IonNote,\n IonSpinner,\n ],\n providers: [\n { provide: ClassName, useValue: 'DebtusContactsPageComponent' },\n SpaceComponentBaseParams,\n ],\n})\nexport class DebtusContactsPageComponent extends SpacePageBaseComponent {\n private readonly debtusService = inject(DEBTUS_SERVICE);\n\n protected readonly $loading = signal(true);\n protected readonly $error = signal<string | undefined>(undefined);\n protected readonly $contacts = signal<IContactBalance[]>([]);\n\n protected readonly formatSignedBalance = formatSignedBalance;\n\n constructor() {\n super();\n this.$defaultBackUrlSpacePath.set('debts');\n this.spaceIDChanged$\n .pipe(\n switchMap((spaceID) => {\n this.$loading.set(true);\n this.$error.set(undefined);\n return this.debtusService.getContactBalances(spaceID ?? '');\n }),\n takeUntilDestroyed(),\n )\n .subscribe({\n next: (contacts) => {\n this.$contacts.set(contacts);\n this.$loading.set(false);\n },\n error: (err) => {\n this.$error.set('Failed to load contacts');\n this.$loading.set(false);\n this.errorLogger.logError(err, 'Failed to load debtus contacts');\n },\n });\n }\n\n protected openContact(contactID: string): void {\n this.spaceNav.navigateForwardToSpacePage(\n this.space,\n `debtus-contact/${contactID}`,\n );\n }\n}\n","<ion-header>\n <ion-toolbar color=\"light\">\n <ion-buttons slot=\"start\">\n <ion-back-button [defaultHref]=\"$defaultBackUrl()\" />\n </ion-buttons>\n <ion-title>Contacts</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"cardy\">\n <!-- Reads on this page are still demo fixtures (see DebtusService). -->\n <sneat-debtus-demo-data-banner />\n\n @if ($loading()) {\n <div class=\"ion-padding ion-text-center\">\n <ion-spinner name=\"dots\" />\n </div>\n } @else if ($error()) {\n <div class=\"ion-padding\">\n <ion-note color=\"danger\">{{ $error() }}</ion-note>\n </div>\n } @else if ($contacts().length) {\n <ion-list>\n @for (c of $contacts(); track c.contactID) {\n <ion-item button (click)=\"openContact(c.contactID)\">\n <ion-label>\n <h2>{{ c.title }}</h2>\n <p>{{ formatSignedBalance(c.balance) }}</p>\n @if (c.counterpartySpaceID) {\n <ion-note color=\"medium\"\n >cross-space · {{ c.counterpartySpaceID }}</ion-note\n >\n }\n </ion-label>\n </ion-item>\n }\n </ion-list>\n } @else {\n <div class=\"ion-padding ion-text-center\">\n <ion-note>No contacts with debts yet.</ion-note>\n </div>\n }\n</ion-content>\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, signal, Component } from '@angular/core';
|
|
3
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
4
|
+
import { IonHeader, IonToolbar, IonButtons, IonBackButton, IonContent, IonCard, IonCardHeader, IonCardTitle, IonCardContent, IonList, IonItem, IonLabel, IonNote, IonBadge, IonButton, IonIcon, IonSpinner } from '@ionic/angular';
|
|
5
|
+
import { DemoDataBannerComponent } from './sneat-extension-debtus-ui.mjs';
|
|
6
|
+
import { DEBTUS_SERVICE, formatAmount, formatSignedBalance, isZeroBalance, summarizeBalances } from '@sneat/extension-debtus-contract';
|
|
7
|
+
import { SpacePageBaseComponent, SpacePageTitleComponent, SpaceComponentBaseParams } from '@sneat/space-components';
|
|
8
|
+
import { SpaceServiceModule } from '@sneat/space-services';
|
|
9
|
+
import { ClassName } from '@sneat/ui';
|
|
10
|
+
import { switchMap } from 'rxjs';
|
|
11
|
+
|
|
12
|
+
// Home / balances screen — the debtus entry point. Mirrors the bot's
|
|
13
|
+
// `debts_balance` view: a net summary of who owes the user and whom the user
|
|
14
|
+
// owes, plus quick actions into contacts and creating a transfer.
|
|
15
|
+
class DebtusHomePageComponent extends SpacePageBaseComponent {
|
|
16
|
+
debtusService = inject(DEBTUS_SERVICE);
|
|
17
|
+
$loading = signal(true, /* @ts-ignore */
|
|
18
|
+
...(ngDevMode ? [{ debugName: "$loading" }] : /* istanbul ignore next */ []));
|
|
19
|
+
$error = signal(undefined, /* @ts-ignore */
|
|
20
|
+
...(ngDevMode ? [{ debugName: "$error" }] : /* istanbul ignore next */ []));
|
|
21
|
+
$contacts = signal([], /* @ts-ignore */
|
|
22
|
+
...(ngDevMode ? [{ debugName: "$contacts" }] : /* istanbul ignore next */ []));
|
|
23
|
+
$summary = signal({
|
|
24
|
+
theyOweYou: [],
|
|
25
|
+
youOwe: [],
|
|
26
|
+
}, /* @ts-ignore */
|
|
27
|
+
...(ngDevMode ? [{ debugName: "$summary" }] : /* istanbul ignore next */ []));
|
|
28
|
+
formatAmount = formatAmount;
|
|
29
|
+
formatSignedBalance = formatSignedBalance;
|
|
30
|
+
constructor() {
|
|
31
|
+
super();
|
|
32
|
+
this.spaceIDChanged$
|
|
33
|
+
.pipe(switchMap((spaceID) => {
|
|
34
|
+
this.$loading.set(true);
|
|
35
|
+
this.$error.set(undefined);
|
|
36
|
+
return this.debtusService.getContactBalances(spaceID ?? '');
|
|
37
|
+
}), takeUntilDestroyed())
|
|
38
|
+
.subscribe({
|
|
39
|
+
next: (contacts) => {
|
|
40
|
+
const active = contacts.filter((c) => !isZeroBalance(c.balance));
|
|
41
|
+
this.$contacts.set(active);
|
|
42
|
+
this.$summary.set(summarizeBalances(contacts));
|
|
43
|
+
this.$loading.set(false);
|
|
44
|
+
},
|
|
45
|
+
error: (err) => {
|
|
46
|
+
this.$error.set('Failed to load balances');
|
|
47
|
+
this.$loading.set(false);
|
|
48
|
+
this.errorLogger.logError(err, 'Failed to load contact balances');
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
goContacts() {
|
|
53
|
+
this.spaceNav.navigateForwardToSpacePage(this.space, 'debtus-contacts');
|
|
54
|
+
}
|
|
55
|
+
goNewTransfer() {
|
|
56
|
+
this.spaceNav.navigateForwardToSpacePage(this.space, 'new-transfer');
|
|
57
|
+
}
|
|
58
|
+
goContact(contactID) {
|
|
59
|
+
this.spaceNav.navigateForwardToSpacePage(this.space, `debtus-contact/${contactID}`);
|
|
60
|
+
}
|
|
61
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: DebtusHomePageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
62
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: DebtusHomePageComponent, isStandalone: true, selector: "sneat-debtus-home-page", providers: [
|
|
63
|
+
{ provide: ClassName, useValue: 'DebtusHomePageComponent' },
|
|
64
|
+
SpaceComponentBaseParams,
|
|
65
|
+
], usesInheritance: true, ngImport: i0, template: "<ion-header>\n <ion-toolbar color=\"light\" class=\"with-back-button with-end-buttons\">\n <ion-buttons slot=\"start\">\n <!-- This is the top page of a space, so \"back\" leaves the space and\n returns to the app home (the spaces list). -->\n <ion-back-button defaultHref=\"/\" />\n </ion-buttons>\n <sneat-space-page-title\n [space]=\"space\"\n generalTitle=\"Debts\"\n icon=\"\uD83D\uDCB8\"\n [titlesBySpaceType]=\"{\n personal: 'Personal debts',\n family: 'Family debts',\n }\"\n />\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"cardy\">\n <!-- Reads on this page are still demo fixtures (see DebtusService). -->\n <sneat-debtus-demo-data-banner />\n\n <ion-card>\n <ion-card-header>\n <ion-card-title>Balance</ion-card-title>\n </ion-card-header>\n <ion-card-content>\n @if ($loading()) {\n <ion-spinner name=\"dots\" />\n } @else if ($error()) {\n <ion-note color=\"danger\">{{ $error() }}</ion-note>\n } @else {\n <ion-list lines=\"none\">\n <ion-item>\n <ion-label>They owe you</ion-label>\n @if ($summary().theyOweYou.length) {\n @for (a of $summary().theyOweYou; track a.currency) {\n <ion-badge slot=\"end\" color=\"success\">{{\n formatAmount(a)\n }}</ion-badge>\n }\n } @else {\n <ion-note slot=\"end\">\u2014</ion-note>\n }\n </ion-item>\n <ion-item>\n <ion-label>You owe</ion-label>\n @if ($summary().youOwe.length) {\n @for (a of $summary().youOwe; track a.currency) {\n <ion-badge slot=\"end\" color=\"warning\">{{\n formatAmount(a)\n }}</ion-badge>\n }\n } @else {\n <ion-note slot=\"end\">\u2014</ion-note>\n }\n </ion-item>\n </ion-list>\n }\n </ion-card-content>\n </ion-card>\n\n <ion-card>\n <ion-card-content>\n <ion-button expand=\"block\" (click)=\"goNewTransfer()\">\n <ion-icon name=\"add\" slot=\"start\" />\n New transfer\n </ion-button>\n <ion-button expand=\"block\" fill=\"outline\" (click)=\"goContacts()\">\n <ion-icon name=\"people-outline\" slot=\"start\" />\n Contacts\n </ion-button>\n </ion-card-content>\n </ion-card>\n\n @if (!$loading() && !$error()) {\n <ion-card>\n <ion-card-header>\n <ion-card-title>By contact</ion-card-title>\n </ion-card-header>\n @if ($contacts().length) {\n <ion-list>\n @for (c of $contacts(); track c.contactID) {\n <ion-item button (click)=\"goContact(c.contactID)\">\n <ion-label>\n <h2>{{ c.title }}</h2>\n <p>{{ formatSignedBalance(c.balance) }}</p>\n @if (c.counterpartySpaceID) {\n <ion-note color=\"medium\">\n cross-space \u00B7 {{ c.counterpartySpaceID }}\n </ion-note>\n }\n </ion-label>\n </ion-item>\n }\n </ion-list>\n } @else {\n <ion-card-content>\n <ion-note>Everyone is settled up. \uD83C\uDF89</ion-note>\n </ion-card-content>\n }\n </ion-card>\n }\n</ion-content>\n", dependencies: [{ kind: "component", type: DemoDataBannerComponent, selector: "sneat-debtus-demo-data-banner" }, { kind: "component", type: SpacePageTitleComponent, selector: "sneat-space-page-title", inputs: ["icon", "generalTitle", "space", "titlesBySpaceType"] }, { kind: "ngmodule", type: SpaceServiceModule }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { 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"], outputs: ["ionScrollStart", "ionScroll", "ionScrollEnd"] }, { kind: "component", type: IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonCardHeader, selector: "ion-card-header", inputs: ["color", "mode", "translucent"] }, { kind: "component", type: IonCardTitle, selector: "ion-card-title", inputs: ["color", "mode"] }, { kind: "component", type: IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { 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: IonBadge, selector: "ion-badge", inputs: ["color", "mode"] }, { 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"], outputs: ["ionFocus", "ionBlur"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }] });
|
|
66
|
+
}
|
|
67
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: DebtusHomePageComponent, decorators: [{
|
|
68
|
+
type: Component,
|
|
69
|
+
args: [{ selector: 'sneat-debtus-home-page', imports: [
|
|
70
|
+
DemoDataBannerComponent,
|
|
71
|
+
SpacePageTitleComponent,
|
|
72
|
+
SpaceServiceModule,
|
|
73
|
+
IonHeader,
|
|
74
|
+
IonToolbar,
|
|
75
|
+
IonButtons,
|
|
76
|
+
IonBackButton,
|
|
77
|
+
IonContent,
|
|
78
|
+
IonCard,
|
|
79
|
+
IonCardHeader,
|
|
80
|
+
IonCardTitle,
|
|
81
|
+
IonCardContent,
|
|
82
|
+
IonList,
|
|
83
|
+
IonItem,
|
|
84
|
+
IonLabel,
|
|
85
|
+
IonNote,
|
|
86
|
+
IonBadge,
|
|
87
|
+
IonButton,
|
|
88
|
+
IonIcon,
|
|
89
|
+
IonSpinner,
|
|
90
|
+
], providers: [
|
|
91
|
+
{ provide: ClassName, useValue: 'DebtusHomePageComponent' },
|
|
92
|
+
SpaceComponentBaseParams,
|
|
93
|
+
], template: "<ion-header>\n <ion-toolbar color=\"light\" class=\"with-back-button with-end-buttons\">\n <ion-buttons slot=\"start\">\n <!-- This is the top page of a space, so \"back\" leaves the space and\n returns to the app home (the spaces list). -->\n <ion-back-button defaultHref=\"/\" />\n </ion-buttons>\n <sneat-space-page-title\n [space]=\"space\"\n generalTitle=\"Debts\"\n icon=\"\uD83D\uDCB8\"\n [titlesBySpaceType]=\"{\n personal: 'Personal debts',\n family: 'Family debts',\n }\"\n />\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"cardy\">\n <!-- Reads on this page are still demo fixtures (see DebtusService). -->\n <sneat-debtus-demo-data-banner />\n\n <ion-card>\n <ion-card-header>\n <ion-card-title>Balance</ion-card-title>\n </ion-card-header>\n <ion-card-content>\n @if ($loading()) {\n <ion-spinner name=\"dots\" />\n } @else if ($error()) {\n <ion-note color=\"danger\">{{ $error() }}</ion-note>\n } @else {\n <ion-list lines=\"none\">\n <ion-item>\n <ion-label>They owe you</ion-label>\n @if ($summary().theyOweYou.length) {\n @for (a of $summary().theyOweYou; track a.currency) {\n <ion-badge slot=\"end\" color=\"success\">{{\n formatAmount(a)\n }}</ion-badge>\n }\n } @else {\n <ion-note slot=\"end\">\u2014</ion-note>\n }\n </ion-item>\n <ion-item>\n <ion-label>You owe</ion-label>\n @if ($summary().youOwe.length) {\n @for (a of $summary().youOwe; track a.currency) {\n <ion-badge slot=\"end\" color=\"warning\">{{\n formatAmount(a)\n }}</ion-badge>\n }\n } @else {\n <ion-note slot=\"end\">\u2014</ion-note>\n }\n </ion-item>\n </ion-list>\n }\n </ion-card-content>\n </ion-card>\n\n <ion-card>\n <ion-card-content>\n <ion-button expand=\"block\" (click)=\"goNewTransfer()\">\n <ion-icon name=\"add\" slot=\"start\" />\n New transfer\n </ion-button>\n <ion-button expand=\"block\" fill=\"outline\" (click)=\"goContacts()\">\n <ion-icon name=\"people-outline\" slot=\"start\" />\n Contacts\n </ion-button>\n </ion-card-content>\n </ion-card>\n\n @if (!$loading() && !$error()) {\n <ion-card>\n <ion-card-header>\n <ion-card-title>By contact</ion-card-title>\n </ion-card-header>\n @if ($contacts().length) {\n <ion-list>\n @for (c of $contacts(); track c.contactID) {\n <ion-item button (click)=\"goContact(c.contactID)\">\n <ion-label>\n <h2>{{ c.title }}</h2>\n <p>{{ formatSignedBalance(c.balance) }}</p>\n @if (c.counterpartySpaceID) {\n <ion-note color=\"medium\">\n cross-space \u00B7 {{ c.counterpartySpaceID }}\n </ion-note>\n }\n </ion-label>\n </ion-item>\n }\n </ion-list>\n } @else {\n <ion-card-content>\n <ion-note>Everyone is settled up. \uD83C\uDF89</ion-note>\n </ion-card-content>\n }\n </ion-card>\n }\n</ion-content>\n" }]
|
|
94
|
+
}], ctorParameters: () => [] });
|
|
95
|
+
|
|
96
|
+
export { DebtusHomePageComponent };
|
|
97
|
+
//# sourceMappingURL=sneat-extension-debtus-ui-debtus-home-page.component-BCNMpirX.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sneat-extension-debtus-ui-debtus-home-page.component-BCNMpirX.mjs","sources":["../../../../../../libs/extensions/debtus/ui/src/lib/pages/debtus-home/debtus-home-page.component.ts","../../../../../../libs/extensions/debtus/ui/src/lib/pages/debtus-home/debtus-home-page.component.html"],"sourcesContent":["import { Component, inject, signal } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport {\n IonBackButton,\n IonBadge,\n IonButton,\n IonButtons,\n IonCard,\n IonCardContent,\n IonCardHeader,\n IonCardTitle,\n IonContent,\n IonHeader,\n IonIcon,\n IonItem,\n IonLabel,\n IonList,\n IonNote,\n IonSpinner,\n IonToolbar,\n} from '@ionic/angular';\nimport { DemoDataBannerComponent } from '../../components/demo-data-banner/demo-data-banner.component';\nimport {\n DEBTUS_SERVICE,\n IBalanceSummary,\n IContactBalance,\n formatAmount,\n formatSignedBalance,\n isZeroBalance,\n summarizeBalances,\n} from '@sneat/extension-debtus-contract';\nimport {\n SpaceComponentBaseParams,\n SpacePageBaseComponent,\n SpacePageTitleComponent,\n} from '@sneat/space-components';\nimport { SpaceServiceModule } from '@sneat/space-services';\nimport { ClassName } from '@sneat/ui';\nimport { switchMap } from 'rxjs';\n\n// Home / balances screen — the debtus entry point. Mirrors the bot's\n// `debts_balance` view: a net summary of who owes the user and whom the user\n// owes, plus quick actions into contacts and creating a transfer.\n@Component({\n selector: 'sneat-debtus-home-page',\n templateUrl: './debtus-home-page.component.html',\n imports: [\n DemoDataBannerComponent,\n SpacePageTitleComponent,\n SpaceServiceModule,\n IonHeader,\n IonToolbar,\n IonButtons,\n IonBackButton,\n IonContent,\n IonCard,\n IonCardHeader,\n IonCardTitle,\n IonCardContent,\n IonList,\n IonItem,\n IonLabel,\n IonNote,\n IonBadge,\n IonButton,\n IonIcon,\n IonSpinner,\n ],\n providers: [\n { provide: ClassName, useValue: 'DebtusHomePageComponent' },\n SpaceComponentBaseParams,\n ],\n})\nexport class DebtusHomePageComponent extends SpacePageBaseComponent {\n private readonly debtusService = inject(DEBTUS_SERVICE);\n\n protected readonly $loading = signal(true);\n protected readonly $error = signal<string | undefined>(undefined);\n protected readonly $contacts = signal<IContactBalance[]>([]);\n protected readonly $summary = signal<IBalanceSummary>({\n theyOweYou: [],\n youOwe: [],\n });\n\n protected readonly formatAmount = formatAmount;\n protected readonly formatSignedBalance = formatSignedBalance;\n\n constructor() {\n super();\n this.spaceIDChanged$\n .pipe(\n switchMap((spaceID) => {\n this.$loading.set(true);\n this.$error.set(undefined);\n return this.debtusService.getContactBalances(spaceID ?? '');\n }),\n takeUntilDestroyed(),\n )\n .subscribe({\n next: (contacts) => {\n const active = contacts.filter((c) => !isZeroBalance(c.balance));\n this.$contacts.set(active);\n this.$summary.set(summarizeBalances(contacts));\n this.$loading.set(false);\n },\n error: (err) => {\n this.$error.set('Failed to load balances');\n this.$loading.set(false);\n this.errorLogger.logError(err, 'Failed to load contact balances');\n },\n });\n }\n\n protected goContacts(): void {\n this.spaceNav.navigateForwardToSpacePage(this.space, 'debtus-contacts');\n }\n\n protected goNewTransfer(): void {\n this.spaceNav.navigateForwardToSpacePage(this.space, 'new-transfer');\n }\n\n protected goContact(contactID: string): void {\n this.spaceNav.navigateForwardToSpacePage(\n this.space,\n `debtus-contact/${contactID}`,\n );\n }\n}\n","<ion-header>\n <ion-toolbar color=\"light\" class=\"with-back-button with-end-buttons\">\n <ion-buttons slot=\"start\">\n <!-- This is the top page of a space, so \"back\" leaves the space and\n returns to the app home (the spaces list). -->\n <ion-back-button defaultHref=\"/\" />\n </ion-buttons>\n <sneat-space-page-title\n [space]=\"space\"\n generalTitle=\"Debts\"\n icon=\"💸\"\n [titlesBySpaceType]=\"{\n personal: 'Personal debts',\n family: 'Family debts',\n }\"\n />\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"cardy\">\n <!-- Reads on this page are still demo fixtures (see DebtusService). -->\n <sneat-debtus-demo-data-banner />\n\n <ion-card>\n <ion-card-header>\n <ion-card-title>Balance</ion-card-title>\n </ion-card-header>\n <ion-card-content>\n @if ($loading()) {\n <ion-spinner name=\"dots\" />\n } @else if ($error()) {\n <ion-note color=\"danger\">{{ $error() }}</ion-note>\n } @else {\n <ion-list lines=\"none\">\n <ion-item>\n <ion-label>They owe you</ion-label>\n @if ($summary().theyOweYou.length) {\n @for (a of $summary().theyOweYou; track a.currency) {\n <ion-badge slot=\"end\" color=\"success\">{{\n formatAmount(a)\n }}</ion-badge>\n }\n } @else {\n <ion-note slot=\"end\">—</ion-note>\n }\n </ion-item>\n <ion-item>\n <ion-label>You owe</ion-label>\n @if ($summary().youOwe.length) {\n @for (a of $summary().youOwe; track a.currency) {\n <ion-badge slot=\"end\" color=\"warning\">{{\n formatAmount(a)\n }}</ion-badge>\n }\n } @else {\n <ion-note slot=\"end\">—</ion-note>\n }\n </ion-item>\n </ion-list>\n }\n </ion-card-content>\n </ion-card>\n\n <ion-card>\n <ion-card-content>\n <ion-button expand=\"block\" (click)=\"goNewTransfer()\">\n <ion-icon name=\"add\" slot=\"start\" />\n New transfer\n </ion-button>\n <ion-button expand=\"block\" fill=\"outline\" (click)=\"goContacts()\">\n <ion-icon name=\"people-outline\" slot=\"start\" />\n Contacts\n </ion-button>\n </ion-card-content>\n </ion-card>\n\n @if (!$loading() && !$error()) {\n <ion-card>\n <ion-card-header>\n <ion-card-title>By contact</ion-card-title>\n </ion-card-header>\n @if ($contacts().length) {\n <ion-list>\n @for (c of $contacts(); track c.contactID) {\n <ion-item button (click)=\"goContact(c.contactID)\">\n <ion-label>\n <h2>{{ c.title }}</h2>\n <p>{{ formatSignedBalance(c.balance) }}</p>\n @if (c.counterpartySpaceID) {\n <ion-note color=\"medium\">\n cross-space · {{ c.counterpartySpaceID }}\n </ion-note>\n }\n </ion-label>\n </ion-item>\n }\n </ion-list>\n } @else {\n <ion-card-content>\n <ion-note>Everyone is settled up. 🎉</ion-note>\n </ion-card-content>\n }\n </ion-card>\n }\n</ion-content>\n"],"names":[],"mappings":"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"}
|