@sneat/extension-debtus-ui 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/fesm2022/sneat-extension-debtus-ui-debtus-contact-details-page.component-ygSD0Q-C.mjs +101 -0
  2. package/fesm2022/sneat-extension-debtus-ui-debtus-contact-details-page.component-ygSD0Q-C.mjs.map +1 -0
  3. package/fesm2022/{sneat-extension-debtus-ui-debtus-contacts-page.component-DYtFeNsM.mjs → sneat-extension-debtus-ui-debtus-contacts-page.component-B2SicqpG.mjs} +5 -5
  4. package/fesm2022/sneat-extension-debtus-ui-debtus-contacts-page.component-B2SicqpG.mjs.map +1 -0
  5. package/fesm2022/sneat-extension-debtus-ui-debtus-home-page.component-Dd5-oQdz.mjs +95 -0
  6. package/fesm2022/sneat-extension-debtus-ui-debtus-home-page.component-Dd5-oQdz.mjs.map +1 -0
  7. package/fesm2022/{sneat-extension-debtus-ui-new-transfer-page.component-CT-6X_0e.mjs → sneat-extension-debtus-ui-new-transfer-page.component-YaGgYqKB.mjs} +16 -12
  8. package/fesm2022/sneat-extension-debtus-ui-new-transfer-page.component-YaGgYqKB.mjs.map +1 -0
  9. package/fesm2022/sneat-extension-debtus-ui-settle-up-page.component-48l8YFn4.mjs +162 -0
  10. package/fesm2022/sneat-extension-debtus-ui-settle-up-page.component-48l8YFn4.mjs.map +1 -0
  11. package/fesm2022/sneat-extension-debtus-ui-source-obligations-page.component-6E9_pHCq.mjs +423 -0
  12. package/fesm2022/sneat-extension-debtus-ui-source-obligations-page.component-6E9_pHCq.mjs.map +1 -0
  13. package/fesm2022/sneat-extension-debtus-ui-transfer-details-page.component-B6iQS1YJ.mjs +105 -0
  14. package/fesm2022/sneat-extension-debtus-ui-transfer-details-page.component-B6iQS1YJ.mjs.map +1 -0
  15. package/fesm2022/sneat-extension-debtus-ui.mjs +14 -36
  16. package/fesm2022/sneat-extension-debtus-ui.mjs.map +1 -1
  17. package/package.json +4 -2
  18. package/types/sneat-extension-debtus-ui.d.ts +1 -6
  19. package/fesm2022/sneat-extension-debtus-ui-debtus-contact-details-page.component-DIAVE0D6.mjs +0 -106
  20. package/fesm2022/sneat-extension-debtus-ui-debtus-contact-details-page.component-DIAVE0D6.mjs.map +0 -1
  21. package/fesm2022/sneat-extension-debtus-ui-debtus-contacts-page.component-DYtFeNsM.mjs.map +0 -1
  22. package/fesm2022/sneat-extension-debtus-ui-debtus-home-page.component-BCNMpirX.mjs +0 -97
  23. package/fesm2022/sneat-extension-debtus-ui-debtus-home-page.component-BCNMpirX.mjs.map +0 -1
  24. package/fesm2022/sneat-extension-debtus-ui-new-transfer-page.component-CT-6X_0e.mjs.map +0 -1
  25. package/fesm2022/sneat-extension-debtus-ui-settle-up-page.component-CUoNSuAE.mjs +0 -163
  26. package/fesm2022/sneat-extension-debtus-ui-settle-up-page.component-CUoNSuAE.mjs.map +0 -1
  27. package/fesm2022/sneat-extension-debtus-ui-transfer-details-page.component-uo5lP557.mjs +0 -97
  28. package/fesm2022/sneat-extension-debtus-ui-transfer-details-page.component-uo5lP557.mjs.map +0 -1
@@ -0,0 +1,101 @@
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 { IonHeader, IonToolbar, IonButtons, IonBackButton, IonTitle, IonContent, IonCard, IonCardHeader, IonCardTitle, IonCardContent, IonList, IonItem, IonLabel, IonNote, IonBadge, IonButton, IonIcon, IonSpinner } from '@ionic/angular';
6
+ import { DEBTUS_SERVICE, formatAmount, formatSignedBalance } from '@sneat/extension-debtus-contract';
7
+ import { SpacePageBaseComponent, SpaceComponentBaseParams } from '@sneat/space-components';
8
+ import { SpaceServiceModule } from '@sneat/space-services';
9
+ import { ClassName } from '@sneat/ui';
10
+ import { combineLatest, switchMap } from 'rxjs';
11
+
12
+ // Contact detail — balance with one counterparty plus their transfer history.
13
+ // Actions mirror the bot: record a lend/borrow, remind, and settle up.
14
+ class DebtusContactDetailsPageComponent extends SpacePageBaseComponent {
15
+ debtusService = inject(DEBTUS_SERVICE);
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
+ openTransfer(transferID) {
63
+ this.spaceNav.navigateForwardToSpacePage(this.space, `transfer/${transferID}`);
64
+ }
65
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: DebtusContactDetailsPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
66
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: DebtusContactDetailsPageComponent, isStandalone: true, selector: "sneat-debtus-contact-details-page", providers: [
67
+ { provide: ClassName, useValue: 'DebtusContactDetailsPageComponent' },
68
+ SpaceComponentBaseParams,
69
+ ], 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 @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\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: 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: "ngmodule", type: SpaceServiceModule }, { kind: "pipe", type: DatePipe, name: "date" }] });
70
+ }
71
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: DebtusContactDetailsPageComponent, decorators: [{
72
+ type: Component,
73
+ args: [{ selector: 'sneat-debtus-contact-details-page', imports: [
74
+ IonHeader,
75
+ IonToolbar,
76
+ IonButtons,
77
+ IonBackButton,
78
+ IonTitle,
79
+ IonContent,
80
+ IonCard,
81
+ IonCardHeader,
82
+ IonCardTitle,
83
+ IonCardContent,
84
+ IonList,
85
+ IonItem,
86
+ IonLabel,
87
+ IonNote,
88
+ IonBadge,
89
+ IonButton,
90
+ IonIcon,
91
+ IonSpinner,
92
+ DatePipe,
93
+ SpaceServiceModule,
94
+ ], providers: [
95
+ { provide: ClassName, useValue: 'DebtusContactDetailsPageComponent' },
96
+ SpaceComponentBaseParams,
97
+ ], 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 @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\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" }]
98
+ }], ctorParameters: () => [] });
99
+
100
+ export { DebtusContactDetailsPageComponent };
101
+ //# sourceMappingURL=sneat-extension-debtus-ui-debtus-contact-details-page.component-ygSD0Q-C.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sneat-extension-debtus-ui-debtus-contact-details-page.component-ygSD0Q-C.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} from '@ionic/angular';\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 { SpaceServiceModule } from '@sneat/space-services';\nimport { ClassName } from '@sneat/ui';\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 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 SpaceServiceModule,\n ],\n providers: [\n { provide: ClassName, useValue: 'DebtusContactDetailsPageComponent' },\n SpaceComponentBaseParams,\n ],\n})\nexport class DebtusContactDetailsPageComponent 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 $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 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 @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\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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"}
@@ -2,9 +2,9 @@ import * as i0 from '@angular/core';
2
2
  import { inject, signal, Component } from '@angular/core';
3
3
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
4
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
5
  import { DEBTUS_SERVICE, formatSignedBalance } from '@sneat/extension-debtus-contract';
7
6
  import { SpacePageBaseComponent, SpaceComponentBaseParams } from '@sneat/space-components';
7
+ import { SpaceServiceModule } from '@sneat/space-services';
8
8
  import { ClassName } from '@sneat/ui';
9
9
  import { switchMap } from 'rxjs';
10
10
 
@@ -47,12 +47,11 @@ class DebtusContactsPageComponent extends SpacePageBaseComponent {
47
47
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: DebtusContactsPageComponent, isStandalone: true, selector: "sneat-debtus-contacts-page", providers: [
48
48
  { provide: ClassName, useValue: 'DebtusContactsPageComponent' },
49
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"] }] });
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 @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: 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"] }, { kind: "ngmodule", type: SpaceServiceModule }] });
51
51
  }
52
52
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: DebtusContactsPageComponent, decorators: [{
53
53
  type: Component,
54
54
  args: [{ selector: 'sneat-debtus-contacts-page', imports: [
55
- DemoDataBannerComponent,
56
55
  IonHeader,
57
56
  IonToolbar,
58
57
  IonButtons,
@@ -64,11 +63,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
64
63
  IonLabel,
65
64
  IonNote,
66
65
  IonSpinner,
66
+ SpaceServiceModule,
67
67
  ], providers: [
68
68
  { provide: ClassName, useValue: 'DebtusContactsPageComponent' },
69
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" }]
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 @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
71
  }], ctorParameters: () => [] });
72
72
 
73
73
  export { DebtusContactsPageComponent };
74
- //# sourceMappingURL=sneat-extension-debtus-ui-debtus-contacts-page.component-DYtFeNsM.mjs.map
74
+ //# sourceMappingURL=sneat-extension-debtus-ui-debtus-contacts-page.component-B2SicqpG.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sneat-extension-debtus-ui-debtus-contacts-page.component-B2SicqpG.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 {\n DEBTUS_SERVICE,\n IContactBalance,\n formatSignedBalance,\n} from '@sneat/extension-debtus-contract';\nimport {\n SpaceComponentBaseParams,\n SpacePageBaseComponent,\n} from '@sneat/space-components';\nimport { SpaceServiceModule } from '@sneat/space-services';\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 IonHeader,\n IonToolbar,\n IonButtons,\n IonBackButton,\n IonTitle,\n IonContent,\n IonList,\n IonItem,\n IonLabel,\n IonNote,\n IonSpinner,\n SpaceServiceModule,\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 @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,95 @@
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 { DEBTUS_SERVICE, formatAmount, formatSignedBalance, isZeroBalance, summarizeBalances } from '@sneat/extension-debtus-contract';
6
+ import { SpacePageBaseComponent, SpacePageTitleComponent, SpaceComponentBaseParams } from '@sneat/space-components';
7
+ import { SpaceServiceModule } from '@sneat/space-services';
8
+ import { ClassName } from '@sneat/ui';
9
+ import { switchMap } from 'rxjs';
10
+
11
+ // Home / balances screen — the debtus entry point. Mirrors the bot's
12
+ // `debts_balance` view: a net summary of who owes the user and whom the user
13
+ // owes, plus quick actions into contacts and creating a transfer.
14
+ class DebtusHomePageComponent extends SpacePageBaseComponent {
15
+ debtusService = inject(DEBTUS_SERVICE);
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
+ $contacts = signal([], /* @ts-ignore */
21
+ ...(ngDevMode ? [{ debugName: "$contacts" }] : /* istanbul ignore next */ []));
22
+ $summary = signal({
23
+ theyOweYou: [],
24
+ youOwe: [],
25
+ }, /* @ts-ignore */
26
+ ...(ngDevMode ? [{ debugName: "$summary" }] : /* istanbul ignore next */ []));
27
+ formatAmount = formatAmount;
28
+ formatSignedBalance = formatSignedBalance;
29
+ constructor() {
30
+ super();
31
+ this.spaceIDChanged$
32
+ .pipe(switchMap((spaceID) => {
33
+ this.$loading.set(true);
34
+ this.$error.set(undefined);
35
+ return this.debtusService.getContactBalances(spaceID ?? '');
36
+ }), takeUntilDestroyed())
37
+ .subscribe({
38
+ next: (contacts) => {
39
+ const active = contacts.filter((c) => !isZeroBalance(c.balance));
40
+ this.$contacts.set(active);
41
+ this.$summary.set(summarizeBalances(contacts));
42
+ this.$loading.set(false);
43
+ },
44
+ error: (err) => {
45
+ this.$error.set('Failed to load balances');
46
+ this.$loading.set(false);
47
+ this.errorLogger.logError(err, 'Failed to load contact balances');
48
+ },
49
+ });
50
+ }
51
+ goContacts() {
52
+ this.spaceNav.navigateForwardToSpacePage(this.space, 'debtus-contacts');
53
+ }
54
+ goNewTransfer() {
55
+ this.spaceNav.navigateForwardToSpacePage(this.space, 'new-transfer');
56
+ }
57
+ goContact(contactID) {
58
+ this.spaceNav.navigateForwardToSpacePage(this.space, `debtus-contact/${contactID}`);
59
+ }
60
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: DebtusHomePageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
61
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: DebtusHomePageComponent, isStandalone: true, selector: "sneat-debtus-home-page", providers: [
62
+ { provide: ClassName, useValue: 'DebtusHomePageComponent' },
63
+ SpaceComponentBaseParams,
64
+ ], 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 <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: 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"] }] });
65
+ }
66
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: DebtusHomePageComponent, decorators: [{
67
+ type: Component,
68
+ args: [{ selector: 'sneat-debtus-home-page', imports: [
69
+ SpacePageTitleComponent,
70
+ SpaceServiceModule,
71
+ IonHeader,
72
+ IonToolbar,
73
+ IonButtons,
74
+ IonBackButton,
75
+ IonContent,
76
+ IonCard,
77
+ IonCardHeader,
78
+ IonCardTitle,
79
+ IonCardContent,
80
+ IonList,
81
+ IonItem,
82
+ IonLabel,
83
+ IonNote,
84
+ IonBadge,
85
+ IonButton,
86
+ IonIcon,
87
+ IonSpinner,
88
+ ], providers: [
89
+ { provide: ClassName, useValue: 'DebtusHomePageComponent' },
90
+ SpaceComponentBaseParams,
91
+ ], 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 <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" }]
92
+ }], ctorParameters: () => [] });
93
+
94
+ export { DebtusHomePageComponent };
95
+ //# sourceMappingURL=sneat-extension-debtus-ui-debtus-home-page.component-Dd5-oQdz.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sneat-extension-debtus-ui-debtus-home-page.component-Dd5-oQdz.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 {\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 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 <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":";;;;;;;;;;AAuCA;AACA;AACA;AA8BM,MAAO,uBAAwB,SAAQ,sBAAsB,CAAA;AAChD,IAAA,aAAa,GAAG,MAAM,CAAC,cAAc,CAAC;IAEpC,QAAQ,GAAG,MAAM,CAAC,IAAI;iFAAC;IACvB,MAAM,GAAG,MAAM,CAAqB,SAAS;+EAAC;IAC9C,SAAS,GAAG,MAAM,CAAoB,EAAE;kFAAC;IACzC,QAAQ,GAAG,MAAM,CAAkB;AACpD,QAAA,UAAU,EAAE,EAAE;AACd,QAAA,MAAM,EAAE,EAAE;AACX,KAAA;iFAAC;IAEiB,YAAY,GAAG,YAAY;IAC3B,mBAAmB,GAAG,mBAAmB;AAE5D,IAAA,WAAA,GAAA;AACE,QAAA,KAAK,EAAE;AACP,QAAA,IAAI,CAAC;AACF,aAAA,IAAI,CACH,SAAS,CAAC,CAAC,OAAO,KAAI;AACpB,YAAA,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;AACvB,YAAA,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC;YAC1B,OAAO,IAAI,CAAC,aAAa,CAAC,kBAAkB,CAAC,OAAO,IAAI,EAAE,CAAC;AAC7D,QAAA,CAAC,CAAC,EACF,kBAAkB,EAAE;AAErB,aAAA,SAAS,CAAC;AACT,YAAA,IAAI,EAAE,CAAC,QAAQ,KAAI;AACjB,gBAAA,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;AAChE,gBAAA,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC;gBAC1B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;AAC9C,gBAAA,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC;YAC1B,CAAC;AACD,YAAA,KAAK,EAAE,CAAC,GAAG,KAAI;AACb,gBAAA,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,yBAAyB,CAAC;AAC1C,gBAAA,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC;gBACxB,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,GAAG,EAAE,iCAAiC,CAAC;YACnE,CAAC;AACF,SAAA,CAAC;IACN;IAEU,UAAU,GAAA;QAClB,IAAI,CAAC,QAAQ,CAAC,0BAA0B,CAAC,IAAI,CAAC,KAAK,EAAE,iBAAiB,CAAC;IACzE;IAEU,aAAa,GAAA;QACrB,IAAI,CAAC,QAAQ,CAAC,0BAA0B,CAAC,IAAI,CAAC,KAAK,EAAE,cAAc,CAAC;IACtE;AAEU,IAAA,SAAS,CAAC,SAAiB,EAAA;AACnC,QAAA,IAAI,CAAC,QAAQ,CAAC,0BAA0B,CACtC,IAAI,CAAC,KAAK,EACV,CAAA,eAAA,EAAkB,SAAS,CAAA,CAAE,CAC9B;IACH;uGArDW,uBAAuB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAvB,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,uBAAuB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,wBAAA,EAAA,SAAA,EALvB;AACT,YAAA,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,yBAAyB,EAAE;YAC3D,wBAAwB;AACzB,SAAA,EAAA,eAAA,EAAA,IAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,ECrEH,suGAsGA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EDxDI,uBAAuB,EAAA,QAAA,EAAA,wBAAA,EAAA,MAAA,EAAA,CAAA,MAAA,EAAA,cAAA,EAAA,OAAA,EAAA,mBAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EACvB,kBAAkB,+BAClB,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,UAAU,EAAA,QAAA,EAAA,aAAA,EAAA,MAAA,EAAA,CAAA,UAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EACV,aAAa,4DACb,UAAU,EAAA,QAAA,EAAA,aAAA,EAAA,MAAA,EAAA,CAAA,OAAA,EAAA,oBAAA,EAAA,iBAAA,EAAA,YAAA,EAAA,cAAA,EAAA,SAAA,EAAA,SAAA,CAAA,EAAA,OAAA,EAAA,CAAA,gBAAA,EAAA,WAAA,EAAA,cAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EACV,OAAO,EAAA,QAAA,EAAA,UAAA,EAAA,MAAA,EAAA,CAAA,QAAA,EAAA,OAAA,EAAA,UAAA,EAAA,UAAA,EAAA,MAAA,EAAA,MAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,iBAAA,EAAA,QAAA,EAAA,MAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EACP,aAAa,EAAA,QAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,CAAA,OAAA,EAAA,MAAA,EAAA,aAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EACb,YAAY,sFACZ,cAAc,EAAA,QAAA,EAAA,kBAAA,EAAA,MAAA,EAAA,CAAA,MAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EACd,OAAO,EAAA,QAAA,EAAA,UAAA,EAAA,MAAA,EAAA,CAAA,OAAA,EAAA,OAAA,EAAA,MAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EACP,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,gFACP,QAAQ,EAAA,QAAA,EAAA,WAAA,EAAA,MAAA,EAAA,CAAA,OAAA,EAAA,MAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EACR,SAAS,EAAA,QAAA,EAAA,YAAA,EAAA,MAAA,EAAA,CAAA,YAAA,EAAA,OAAA,EAAA,UAAA,EAAA,UAAA,EAAA,QAAA,EAAA,MAAA,EAAA,MAAA,EAAA,MAAA,EAAA,MAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,iBAAA,EAAA,OAAA,EAAA,MAAA,EAAA,QAAA,EAAA,QAAA,EAAA,MAAA,CAAA,EAAA,OAAA,EAAA,CAAA,UAAA,EAAA,SAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EACT,OAAO,2JACP,UAAU,EAAA,QAAA,EAAA,aAAA,EAAA,MAAA,EAAA,CAAA,OAAA,EAAA,UAAA,EAAA,MAAA,EAAA,QAAA,CAAA,EAAA,CAAA,EAAA,CAAA;;2FAOD,uBAAuB,EAAA,UAAA,EAAA,CAAA;kBA7BnC,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,wBAAwB,EAAA,OAAA,EAEzB;wBACP,uBAAuB;wBACvB,kBAAkB;wBAClB,SAAS;wBACT,UAAU;wBACV,UAAU;wBACV,aAAa;wBACb,UAAU;wBACV,OAAO;wBACP,aAAa;wBACb,YAAY;wBACZ,cAAc;wBACd,OAAO;wBACP,OAAO;wBACP,QAAQ;wBACR,OAAO;wBACP,QAAQ;wBACR,SAAS;wBACT,OAAO;wBACP,UAAU;qBACX,EAAA,SAAA,EACU;AACT,wBAAA,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,yBAAyB,EAAE;wBAC3D,wBAAwB;AACzB,qBAAA,EAAA,QAAA,EAAA,suGAAA,EAAA;;;;;"}
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, signal, Component } from '@angular/core';
2
+ import { inject, ChangeDetectorRef, signal, Component } from '@angular/core';
3
3
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
4
4
  import * as i1 from '@angular/forms';
5
5
  import { FormControl, Validators, FormGroup, ReactiveFormsModule } from '@angular/forms';
@@ -7,8 +7,9 @@ import { ToastController, IonHeader, IonToolbar, IonButtons, IonBackButton, IonT
7
7
  import { ContactInputComponent } from '@sneat/extension-contactus-ui';
8
8
  import { DEBTUS_SERVICE } from '@sneat/extension-debtus-contract';
9
9
  import { SpacePageBaseComponent, SpaceComponentBaseParams } from '@sneat/space-components';
10
+ import { SpaceServiceModule } from '@sneat/space-services';
10
11
  import { ClassName } from '@sneat/ui';
11
- import { first } from 'rxjs';
12
+ import { first, startWith, filter } from 'rxjs';
12
13
 
13
14
  // Create transfer — the primary write flow. Mirrors the bot's lend/borrow
14
15
  // wizard: direction, counterparty (a contactus space contact), amount +
@@ -16,6 +17,7 @@ import { first } from 'rxjs';
16
17
  // so the counterparty is a real space contact, not a debtus-private person.
17
18
  class NewTransferPageComponent extends SpacePageBaseComponent {
18
19
  debtusService = inject(DEBTUS_SERVICE);
20
+ changeDetector = inject(ChangeDetectorRef);
19
21
  toastController = inject(ToastController);
20
22
  $submitting = signal(false, /* @ts-ignore */
21
23
  ...(ngDevMode ? [{ debugName: "$submitting" }] : /* istanbul ignore next */ []));
@@ -59,11 +61,14 @@ class NewTransferPageComponent extends SpacePageBaseComponent {
59
61
  this.prefilledContactID = contactID;
60
62
  // Prefill the name field from the known balance so the user sees who
61
63
  // they're recording against even before the contactus picker loads.
62
- this.spaceIDChanged$.pipe(first()).subscribe((spaceID) => {
64
+ this.spaceIDChanged$.pipe(startWith(this.$spaceID()), filter((spaceID) => !!spaceID), first()).subscribe((spaceID) => {
63
65
  this.debtusService
64
- .getContactBalance(spaceID ?? '', contactID)
66
+ .getContactBalance(spaceID, contactID)
65
67
  .pipe(first())
66
- .subscribe((c) => this.counterpartyName.setValue(c.title));
68
+ .subscribe((c) => {
69
+ this.counterpartyName.setValue(c.title);
70
+ this.changeDetector.markForCheck();
71
+ });
67
72
  });
68
73
  }
69
74
  },
@@ -132,11 +137,9 @@ class NewTransferPageComponent extends SpacePageBaseComponent {
132
137
  this.debtusService.createTransfer(request).subscribe({
133
138
  next: (resp) => {
134
139
  this.$submitting.set(false);
135
- // Navigation default: go to the created transfer's detail, replaceUrl
136
- // so Back doesn't reopen the filled form. The created transfer is
137
- // handed over via router state — transfer reads are not wired to the
138
- // live backend yet, so the details page must not fabricate a receipt
139
- // from fixtures.
140
+ // Go to the persisted transfer's detail and replace the filled form in
141
+ // history. Router state avoids an immediate duplicate read; a reload
142
+ // retrieves the same transfer through the Debtus API.
140
143
  this.spaceNav.navigateForwardToSpacePage(this.space, `transfer/${resp.transfer.id}`, { replaceUrl: true, state: { transfer: resp.transfer } });
141
144
  },
142
145
  error: (err) => {
@@ -160,7 +163,7 @@ class NewTransferPageComponent extends SpacePageBaseComponent {
160
163
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: NewTransferPageComponent, isStandalone: true, selector: "sneat-debtus-new-transfer-page", providers: [
161
164
  { provide: ClassName, useValue: 'NewTransferPageComponent' },
162
165
  SpaceComponentBaseParams,
163
- ], 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>New transfer</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"cardy\">\n <form [formGroup]=\"form\" (ngSubmit)=\"submit()\">\n <ion-card>\n <ion-card-content>\n <ion-segment [formControl]=\"direction\">\n <ion-segment-button value=\"lend\">\n <ion-label>I lent</ion-label>\n </ion-segment-button>\n <ion-segment-button value=\"borrow\">\n <ion-label>I borrowed</ion-label>\n </ion-segment-button>\n </ion-segment>\n <ion-note class=\"ion-padding-start\" color=\"medium\">\n {{\n direction.value === 'lend'\n ? 'You gave money \u2014 they owe you.'\n : 'You got money \u2014 you owe them.'\n }}\n </ion-note>\n </ion-card-content>\n </ion-card>\n\n <ion-card>\n <ion-card-content>\n <!-- Reuse the contactus space-contact picker. The counterparty is a\n real contactus contact, not a debtus-private person. -->\n <sneat-contact-input\n [space]=\"space\"\n label=\"Counterparty\"\n [canChangeContact]=\"true\"\n (contactChange)=\"onContactChanged($event)\"\n />\n @if (prefilledContactID) {\n <ion-note class=\"ion-padding-start\" color=\"medium\">\n Recording against\n {{ counterpartyName.value || prefilledContactID }}\n </ion-note>\n }\n <!-- Fable refactoring: the \"Or new counterparty name\" free-text input\n is disabled \u2014 a name-only counterparty always 400s server-side\n (POST create-transfer requires a real contact id; see\n resolveCounterparty() in the component). Re-enable once a\n create-contact-then-transfer flow exists. Kept per the no-delete\n policy:\n <ion-item>\n <ion-label position=\"stacked\">\n Or new counterparty name\n </ion-label>\n <ion-input\n [formControl]=\"counterpartyName\"\n placeholder=\"e.g. Alex\"\n autocapitalize=\"words\"\n />\n </ion-item>\n -->\n </ion-card-content>\n </ion-card>\n\n <ion-card>\n <ion-card-content>\n <ion-item>\n <ion-label>Currency</ion-label>\n <ion-select [formControl]=\"currency\" interface=\"popover\">\n @for (c of currencies; track c) {\n <ion-select-option [value]=\"c\">{{ c }}</ion-select-option>\n }\n </ion-select>\n </ion-item>\n <ion-item>\n <ion-label position=\"stacked\">Amount</ion-label>\n <ion-input\n type=\"number\"\n inputmode=\"decimal\"\n [formControl]=\"amount\"\n placeholder=\"0.00\"\n />\n </ion-item>\n @if (amount.touched && amount.invalid) {\n <ion-note color=\"danger\" class=\"ion-padding-start\">\n Enter an amount greater than zero.\n </ion-note>\n }\n <ion-item>\n <ion-label position=\"stacked\">Note (optional)</ion-label>\n <ion-textarea\n [formControl]=\"note\"\n placeholder=\"What is it for?\"\n [autoGrow]=\"true\"\n />\n </ion-item>\n </ion-card-content>\n </ion-card>\n\n <div class=\"ion-padding\">\n <ion-button\n expand=\"block\"\n type=\"submit\"\n [disabled]=\"$submitting() || form.invalid\"\n >\n @if ($submitting()) {\n <ion-spinner name=\"dots\" slot=\"start\" />\n Saving\u2026\n } @else {\n Record transfer\n }\n </ion-button>\n </div>\n </form>\n</ion-content>\n", dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { 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: IonCardContent, selector: "ion-card-content", inputs: ["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: IonInput, selector: "ion-input", inputs: ["accept", "autocapitalize", "autocomplete", "autocorrect", "autofocus", "clearInput", "clearOnEdit", "color", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "max", "maxlength", "min", "minlength", "mode", "multiple", "name", "pattern", "placeholder", "readonly", "required", "shape", "size", "spellcheck", "step", "type", "value"] }, { kind: "component", type: IonTextarea, selector: "ion-textarea", inputs: ["autoGrow", "autocapitalize", "autofocus", "clearOnEdit", "color", "cols", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "maxlength", "minlength", "mode", "name", "placeholder", "readonly", "required", "rows", "shape", "spellcheck", "value", "wrap"] }, { kind: "component", type: IonSelect, selector: "ion-select", inputs: ["cancelText", "color", "compareWith", "disabled", "errorText", "expandedIcon", "fill", "helperText", "interface", "interfaceOptions", "justify", "label", "labelPlacement", "mode", "multiple", "name", "okText", "placeholder", "selectedText", "shape", "toggleIcon", "value"] }, { kind: "component", type: IonSelectOption, selector: "ion-select-option", inputs: ["description", "disabled", "justify", "labelPlacement", "mode", "value"] }, { kind: "component", type: IonSegment, selector: "ion-segment", inputs: ["color", "disabled", "mode", "scrollable", "selectOnFocus", "swipeGesture", "value"] }, { kind: "component", type: IonSegmentButton, selector: "ion-segment-button", inputs: ["contentId", "disabled", "layout", "mode", "type", "value"] }, { 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: IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: ContactInputComponent, selector: "sneat-contact-input", inputs: ["space", "disabled", "canChangeContact", "canReset", "readonly", "label", "labelPosition", "contactRole", "contactType", "subLabel", "parentType", "parentRole", "parentContact", "deleting", "contact"], outputs: ["contactChange"] }] });
166
+ ], 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>New transfer</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"cardy\">\n <form [formGroup]=\"form\" (ngSubmit)=\"submit()\">\n <ion-card>\n <ion-card-content>\n <ion-segment [formControl]=\"direction\">\n <ion-segment-button value=\"lend\">\n <ion-label>I lent</ion-label>\n </ion-segment-button>\n <ion-segment-button value=\"borrow\">\n <ion-label>I borrowed</ion-label>\n </ion-segment-button>\n </ion-segment>\n <ion-note class=\"ion-padding-start\" color=\"medium\">\n {{\n direction.value === 'lend'\n ? 'You gave money \u2014 they owe you.'\n : 'You got money \u2014 you owe them.'\n }}\n </ion-note>\n </ion-card-content>\n </ion-card>\n\n <ion-card>\n <ion-card-content>\n <!-- Reuse the contactus space-contact picker. The counterparty is a\n real contactus contact, not a debtus-private person. -->\n <sneat-contact-input\n [space]=\"space\"\n label=\"Counterparty\"\n [canChangeContact]=\"true\"\n (contactChange)=\"onContactChanged($event)\"\n />\n @if (prefilledContactID) {\n <ion-note class=\"ion-padding-start\" color=\"medium\">\n Recording against\n {{ counterpartyName.value || prefilledContactID }}\n </ion-note>\n }\n <!-- Fable refactoring: the \"Or new counterparty name\" free-text input\n is disabled \u2014 a name-only counterparty always 400s server-side\n (POST create-transfer requires a real contact id; see\n resolveCounterparty() in the component). Re-enable once a\n create-contact-then-transfer flow exists. Kept per the no-delete\n policy:\n <ion-item>\n <ion-label position=\"stacked\">\n Or new counterparty name\n </ion-label>\n <ion-input\n [formControl]=\"counterpartyName\"\n placeholder=\"e.g. Alex\"\n autocapitalize=\"words\"\n />\n </ion-item>\n -->\n </ion-card-content>\n </ion-card>\n\n <ion-card>\n <ion-card-content>\n <ion-item>\n <ion-label>Currency</ion-label>\n <ion-select [formControl]=\"currency\" interface=\"popover\">\n @for (c of currencies; track c) {\n <ion-select-option [value]=\"c\">{{ c }}</ion-select-option>\n }\n </ion-select>\n </ion-item>\n <ion-item>\n <ion-label position=\"stacked\">Amount</ion-label>\n <ion-input\n type=\"number\"\n inputmode=\"decimal\"\n [formControl]=\"amount\"\n placeholder=\"0.00\"\n />\n </ion-item>\n @if (amount.touched && amount.invalid) {\n <ion-note color=\"danger\" class=\"ion-padding-start\">\n Enter an amount greater than zero.\n </ion-note>\n }\n <ion-item>\n <ion-label position=\"stacked\">Note (optional)</ion-label>\n <ion-textarea\n [formControl]=\"note\"\n placeholder=\"What is it for?\"\n [autoGrow]=\"true\"\n />\n </ion-item>\n </ion-card-content>\n </ion-card>\n\n <div class=\"ion-padding\">\n <ion-button\n expand=\"block\"\n type=\"submit\"\n [disabled]=\"$submitting() || form.invalid\"\n >\n @if ($submitting()) {\n <ion-spinner name=\"dots\" slot=\"start\" />\n Saving\u2026\n } @else {\n Record transfer\n }\n </ion-button>\n </div>\n </form>\n</ion-content>\n", dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { 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: IonCardContent, selector: "ion-card-content", inputs: ["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: IonInput, selector: "ion-input", inputs: ["accept", "autocapitalize", "autocomplete", "autocorrect", "autofocus", "clearInput", "clearOnEdit", "color", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "max", "maxlength", "min", "minlength", "mode", "multiple", "name", "pattern", "placeholder", "readonly", "required", "shape", "size", "spellcheck", "step", "type", "value"] }, { kind: "component", type: IonTextarea, selector: "ion-textarea", inputs: ["autoGrow", "autocapitalize", "autofocus", "clearOnEdit", "color", "cols", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "maxlength", "minlength", "mode", "name", "placeholder", "readonly", "required", "rows", "shape", "spellcheck", "value", "wrap"] }, { kind: "component", type: IonSelect, selector: "ion-select", inputs: ["cancelText", "color", "compareWith", "disabled", "errorText", "expandedIcon", "fill", "helperText", "interface", "interfaceOptions", "justify", "label", "labelPlacement", "mode", "multiple", "name", "okText", "placeholder", "selectedText", "shape", "toggleIcon", "value"] }, { kind: "component", type: IonSelectOption, selector: "ion-select-option", inputs: ["description", "disabled", "justify", "labelPlacement", "mode", "value"] }, { kind: "component", type: IonSegment, selector: "ion-segment", inputs: ["color", "disabled", "mode", "scrollable", "selectOnFocus", "swipeGesture", "value"] }, { kind: "component", type: IonSegmentButton, selector: "ion-segment-button", inputs: ["contentId", "disabled", "layout", "mode", "type", "value"] }, { 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: IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: ContactInputComponent, selector: "sneat-contact-input", inputs: ["space", "disabled", "canChangeContact", "canReset", "readonly", "label", "labelPosition", "contactRole", "contactType", "subLabel", "parentType", "parentRole", "parentContact", "deleting", "contact"], outputs: ["contactChange"] }, { kind: "ngmodule", type: SpaceServiceModule }] });
164
167
  }
165
168
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: NewTransferPageComponent, decorators: [{
166
169
  type: Component,
@@ -186,6 +189,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
186
189
  IonNote,
187
190
  IonSpinner,
188
191
  ContactInputComponent,
192
+ SpaceServiceModule,
189
193
  ], providers: [
190
194
  { provide: ClassName, useValue: 'NewTransferPageComponent' },
191
195
  SpaceComponentBaseParams,
@@ -193,4 +197,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
193
197
  }], ctorParameters: () => [] });
194
198
 
195
199
  export { NewTransferPageComponent };
196
- //# sourceMappingURL=sneat-extension-debtus-ui-new-transfer-page.component-CT-6X_0e.mjs.map
200
+ //# sourceMappingURL=sneat-extension-debtus-ui-new-transfer-page.component-YaGgYqKB.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sneat-extension-debtus-ui-new-transfer-page.component-YaGgYqKB.mjs","sources":["../../../../../../libs/extensions/debtus/ui/src/lib/pages/new-transfer/new-transfer-page.component.ts","../../../../../../libs/extensions/debtus/ui/src/lib/pages/new-transfer/new-transfer-page.component.html"],"sourcesContent":["import { ChangeDetectorRef, Component, inject, signal } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport {\n FormControl,\n FormGroup,\n ReactiveFormsModule,\n Validators,\n} from '@angular/forms';\nimport {\n IonBackButton,\n IonButton,\n IonButtons,\n IonCard,\n IonCardContent,\n IonContent,\n IonHeader,\n IonInput,\n IonItem,\n IonLabel,\n IonNote,\n IonSegment,\n IonSegmentButton,\n IonSelect,\n IonSelectOption,\n IonSpinner,\n IonTextarea,\n IonTitle,\n IonToolbar,\n ToastController,\n} from '@ionic/angular';\nimport { IContactContext } from '@sneat/extension-contactus-contract';\nimport { ContactInputComponent } from '@sneat/extension-contactus-ui';\nimport {\n CurrencyCode,\n DEBTUS_SERVICE,\n DebtDirection,\n ICreateTransferRequest,\n} from '@sneat/extension-debtus-contract';\nimport {\n SpaceComponentBaseParams,\n SpacePageBaseComponent,\n} from '@sneat/space-components';\nimport { SpaceServiceModule } from '@sneat/space-services';\nimport { ClassName } from '@sneat/ui';\nimport { filter, first, startWith } from 'rxjs';\n\n// Create transfer — the primary write flow. Mirrors the bot's lend/borrow\n// wizard: direction, counterparty (a contactus space contact), amount +\n// currency, optional note. Reuses the contactus `sneat-contact-input` picker\n// so the counterparty is a real space contact, not a debtus-private person.\n@Component({\n selector: 'sneat-debtus-new-transfer-page',\n templateUrl: './new-transfer-page.component.html',\n imports: [\n ReactiveFormsModule,\n IonHeader,\n IonToolbar,\n IonButtons,\n IonBackButton,\n IonTitle,\n IonContent,\n IonCard,\n IonCardContent,\n IonItem,\n IonLabel,\n IonInput,\n IonTextarea,\n IonSelect,\n IonSelectOption,\n IonSegment,\n IonSegmentButton,\n IonButton,\n IonNote,\n IonSpinner,\n ContactInputComponent,\n SpaceServiceModule,\n ],\n providers: [\n { provide: ClassName, useValue: 'NewTransferPageComponent' },\n SpaceComponentBaseParams,\n ],\n})\nexport class NewTransferPageComponent extends SpacePageBaseComponent {\n private readonly debtusService = inject(DEBTUS_SERVICE);\n private readonly changeDetector = inject(ChangeDetectorRef);\n private readonly toastController = inject(ToastController);\n\n protected readonly $submitting = signal(false);\n protected readonly $pickedContact = signal<IContactContext | undefined>(\n undefined,\n );\n /** Set when arriving from a contact detail page (?contactID=…). */\n protected prefilledContactID?: string;\n\n protected readonly direction = new FormControl<DebtDirection>('lend', {\n nonNullable: true,\n });\n protected readonly currency = new FormControl<CurrencyCode>('EUR', {\n nonNullable: true,\n });\n protected readonly amount = new FormControl<number | null>(null, [\n Validators.required,\n Validators.min(0.01),\n ]);\n protected readonly counterpartyName = new FormControl<string>('', {\n nonNullable: true,\n });\n protected readonly note = new FormControl<string>('', { nonNullable: true });\n\n protected readonly form = new FormGroup({\n direction: this.direction,\n currency: this.currency,\n amount: this.amount,\n counterpartyName: this.counterpartyName,\n note: this.note,\n });\n\n protected readonly currencies: CurrencyCode[] = ['EUR', 'USD'];\n\n constructor() {\n super();\n this.$defaultBackUrlSpacePath.set('debts');\n this.route.queryParamMap.pipe(first(), takeUntilDestroyed()).subscribe({\n next: (params) => {\n const dir = params.get('direction');\n if (dir === 'lend' || dir === 'borrow') {\n this.direction.setValue(dir);\n }\n const contactID = params.get('contactID');\n if (contactID) {\n this.prefilledContactID = contactID;\n // Prefill the name field from the known balance so the user sees who\n // they're recording against even before the contactus picker loads.\n this.spaceIDChanged$.pipe(\n startWith(this.$spaceID()),\n filter((spaceID): spaceID is string => !!spaceID),\n first(),\n ).subscribe((spaceID) => {\n this.debtusService\n .getContactBalance(spaceID, contactID)\n .pipe(first())\n .subscribe((c) => {\n this.counterpartyName.setValue(c.title);\n this.changeDetector.markForCheck();\n });\n });\n }\n },\n });\n }\n\n protected onContactChanged(contact: IContactContext | undefined): void {\n this.$pickedContact.set(contact);\n if (contact) {\n // A picked contact supersedes a typed name / prefilled id.\n this.prefilledContactID = undefined;\n this.counterpartyName.setValue(contact.brief?.title ?? '');\n }\n }\n\n private resolveCounterparty(): { contactID: string; title: string } | null {\n const picked = this.$pickedContact();\n if (picked) {\n return { contactID: picked.id, title: picked.brief?.title ?? picked.id };\n }\n if (this.prefilledContactID) {\n return {\n contactID: this.prefilledContactID,\n title: this.counterpartyName.value,\n };\n }\n // Fable refactoring: the \"new counterparty by name\" path is disabled —\n // the assumption that \"the backend create flow resolves/creates it\" was\n // wrong: POST create-transfer validation requires toContactID/fromContactID\n // (facade4debtus CreateTransferRequest.Validate), so an empty contactID\n // always 400s. Until a create-contact-then-transfer flow is wired, users\n // must pick an existing space contact (the picker above supports adding\n // one). Original branch kept per the no-delete policy:\n // const name = this.counterpartyName.value.trim();\n // if (name) {\n // // New counterparty by name (mirrors the bot's \"new counterparty\"\n // // step). No contactID yet — the backend create flow\n // // resolves/creates it.\n // return { contactID: '', title: name };\n // }\n return null;\n }\n\n protected submit(): void {\n this.form.markAllAsTouched();\n const amount = this.amount.value;\n if (!amount || amount <= 0) {\n return;\n }\n const counterparty = this.resolveCounterparty();\n if (!counterparty) {\n this.showToast(\n 'Pick a counterparty contact. To record against someone new, add them as a space contact first.',\n 'danger',\n );\n return;\n }\n const spaceID = this.$spaceID();\n if (!spaceID) {\n return;\n }\n const request: ICreateTransferRequest = {\n spaceID,\n direction: this.direction.value,\n amount: { currency: this.currency.value, value: amount },\n contactID: counterparty.contactID,\n contactTitle: counterparty.title,\n note: this.note.value.trim() || undefined,\n counterpartySpaceID: this.$pickedContact()?.space?.id,\n };\n\n this.$submitting.set(true);\n this.debtusService.createTransfer(request).subscribe({\n next: (resp) => {\n this.$submitting.set(false);\n // Go to the persisted transfer's detail and replace the filled form in\n // history. Router state avoids an immediate duplicate read; a reload\n // retrieves the same transfer through the Debtus API.\n this.spaceNav.navigateForwardToSpacePage(\n this.space,\n `transfer/${resp.transfer.id}`,\n { replaceUrl: true, state: { transfer: resp.transfer } },\n );\n },\n error: (err) => {\n this.$submitting.set(false);\n this.errorLogger.logError(err, 'Failed to create transfer', {\n show: false,\n });\n this.showToast('Failed to record transfer. Please try again.', 'danger');\n },\n });\n }\n\n private async showToast(message: string, color: string): Promise<void> {\n const toast = await this.toastController.create({\n message,\n duration: 3000,\n color,\n });\n await toast.present();\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>New transfer</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"cardy\">\n <form [formGroup]=\"form\" (ngSubmit)=\"submit()\">\n <ion-card>\n <ion-card-content>\n <ion-segment [formControl]=\"direction\">\n <ion-segment-button value=\"lend\">\n <ion-label>I lent</ion-label>\n </ion-segment-button>\n <ion-segment-button value=\"borrow\">\n <ion-label>I borrowed</ion-label>\n </ion-segment-button>\n </ion-segment>\n <ion-note class=\"ion-padding-start\" color=\"medium\">\n {{\n direction.value === 'lend'\n ? 'You gave money — they owe you.'\n : 'You got money — you owe them.'\n }}\n </ion-note>\n </ion-card-content>\n </ion-card>\n\n <ion-card>\n <ion-card-content>\n <!-- Reuse the contactus space-contact picker. The counterparty is a\n real contactus contact, not a debtus-private person. -->\n <sneat-contact-input\n [space]=\"space\"\n label=\"Counterparty\"\n [canChangeContact]=\"true\"\n (contactChange)=\"onContactChanged($event)\"\n />\n @if (prefilledContactID) {\n <ion-note class=\"ion-padding-start\" color=\"medium\">\n Recording against\n {{ counterpartyName.value || prefilledContactID }}\n </ion-note>\n }\n <!-- Fable refactoring: the \"Or new counterparty name\" free-text input\n is disabled — a name-only counterparty always 400s server-side\n (POST create-transfer requires a real contact id; see\n resolveCounterparty() in the component). Re-enable once a\n create-contact-then-transfer flow exists. Kept per the no-delete\n policy:\n <ion-item>\n <ion-label position=\"stacked\">\n Or new counterparty name\n </ion-label>\n <ion-input\n [formControl]=\"counterpartyName\"\n placeholder=\"e.g. Alex\"\n autocapitalize=\"words\"\n />\n </ion-item>\n -->\n </ion-card-content>\n </ion-card>\n\n <ion-card>\n <ion-card-content>\n <ion-item>\n <ion-label>Currency</ion-label>\n <ion-select [formControl]=\"currency\" interface=\"popover\">\n @for (c of currencies; track c) {\n <ion-select-option [value]=\"c\">{{ c }}</ion-select-option>\n }\n </ion-select>\n </ion-item>\n <ion-item>\n <ion-label position=\"stacked\">Amount</ion-label>\n <ion-input\n type=\"number\"\n inputmode=\"decimal\"\n [formControl]=\"amount\"\n placeholder=\"0.00\"\n />\n </ion-item>\n @if (amount.touched && amount.invalid) {\n <ion-note color=\"danger\" class=\"ion-padding-start\">\n Enter an amount greater than zero.\n </ion-note>\n }\n <ion-item>\n <ion-label position=\"stacked\">Note (optional)</ion-label>\n <ion-textarea\n [formControl]=\"note\"\n placeholder=\"What is it for?\"\n [autoGrow]=\"true\"\n />\n </ion-item>\n </ion-card-content>\n </ion-card>\n\n <div class=\"ion-padding\">\n <ion-button\n expand=\"block\"\n type=\"submit\"\n [disabled]=\"$submitting() || form.invalid\"\n >\n @if ($submitting()) {\n <ion-spinner name=\"dots\" slot=\"start\" />\n Saving…\n } @else {\n Record transfer\n }\n </ion-button>\n </div>\n </form>\n</ion-content>\n"],"names":[],"mappings":"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"}