@sneat/extension-debtus-ui 0.2.0 → 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.
- package/fesm2022/sneat-extension-debtus-ui-debtus-contact-details-page.component-ygSD0Q-C.mjs +101 -0
- package/fesm2022/sneat-extension-debtus-ui-debtus-contact-details-page.component-ygSD0Q-C.mjs.map +1 -0
- package/fesm2022/{sneat-extension-debtus-ui-debtus-contacts-page.component-DYtFeNsM.mjs → sneat-extension-debtus-ui-debtus-contacts-page.component-B2SicqpG.mjs} +5 -5
- package/fesm2022/sneat-extension-debtus-ui-debtus-contacts-page.component-B2SicqpG.mjs.map +1 -0
- package/fesm2022/sneat-extension-debtus-ui-debtus-home-page.component-Dd5-oQdz.mjs +95 -0
- package/fesm2022/sneat-extension-debtus-ui-debtus-home-page.component-Dd5-oQdz.mjs.map +1 -0
- 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
- package/fesm2022/sneat-extension-debtus-ui-new-transfer-page.component-YaGgYqKB.mjs.map +1 -0
- package/fesm2022/sneat-extension-debtus-ui-settle-up-page.component-48l8YFn4.mjs +162 -0
- package/fesm2022/sneat-extension-debtus-ui-settle-up-page.component-48l8YFn4.mjs.map +1 -0
- package/fesm2022/sneat-extension-debtus-ui-source-obligations-page.component-6E9_pHCq.mjs +423 -0
- package/fesm2022/sneat-extension-debtus-ui-source-obligations-page.component-6E9_pHCq.mjs.map +1 -0
- package/fesm2022/sneat-extension-debtus-ui-transfer-details-page.component-B6iQS1YJ.mjs +105 -0
- package/fesm2022/sneat-extension-debtus-ui-transfer-details-page.component-B6iQS1YJ.mjs.map +1 -0
- package/fesm2022/sneat-extension-debtus-ui.mjs +14 -36
- package/fesm2022/sneat-extension-debtus-ui.mjs.map +1 -1
- package/package.json +5 -3
- package/types/sneat-extension-debtus-ui.d.ts +1 -6
- package/fesm2022/sneat-extension-debtus-ui-debtus-contact-details-page.component-DIAVE0D6.mjs +0 -106
- package/fesm2022/sneat-extension-debtus-ui-debtus-contact-details-page.component-DIAVE0D6.mjs.map +0 -1
- package/fesm2022/sneat-extension-debtus-ui-debtus-contacts-page.component-DYtFeNsM.mjs.map +0 -1
- package/fesm2022/sneat-extension-debtus-ui-debtus-home-page.component-BCNMpirX.mjs +0 -97
- package/fesm2022/sneat-extension-debtus-ui-debtus-home-page.component-BCNMpirX.mjs.map +0 -1
- package/fesm2022/sneat-extension-debtus-ui-new-transfer-page.component-CT-6X_0e.mjs.map +0 -1
- package/fesm2022/sneat-extension-debtus-ui-settle-up-page.component-CUoNSuAE.mjs +0 -163
- package/fesm2022/sneat-extension-debtus-ui-settle-up-page.component-CUoNSuAE.mjs.map +0 -1
- package/fesm2022/sneat-extension-debtus-ui-transfer-details-page.component-uo5lP557.mjs +0 -97
- package/fesm2022/sneat-extension-debtus-ui-transfer-details-page.component-uo5lP557.mjs.map +0 -1
|
@@ -0,0 +1,162 @@
|
|
|
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 * as i1 from '@angular/forms';
|
|
5
|
+
import { FormControl, Validators, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
|
6
|
+
import { ToastController, IonHeader, IonToolbar, IonButtons, IonBackButton, IonTitle, IonContent, IonCard, IonCardHeader, IonCardTitle, IonCardContent, IonItem, IonLabel, IonInput, IonSelect, IonSelectOption, IonButton, IonNote, IonSpinner } from '@ionic/angular';
|
|
7
|
+
import { DEBTUS_SERVICE, formatSignedBalance, round2, settleDirectionForBalance } from '@sneat/extension-debtus-contract';
|
|
8
|
+
import { SpacePageBaseComponent, SpaceComponentBaseParams } from '@sneat/space-components';
|
|
9
|
+
import { SpaceServiceModule } from '@sneat/space-services';
|
|
10
|
+
import { ClassName } from '@sneat/ui';
|
|
11
|
+
import { combineLatest, switchMap } from 'rxjs';
|
|
12
|
+
|
|
13
|
+
// Settle up — records a settling (return) transfer against a counterparty
|
|
14
|
+
// balance. Mirrors the bot's "Returned fully/partially" flow: the backend nets
|
|
15
|
+
// the return against outstanding transfers.
|
|
16
|
+
class SettleUpPageComponent extends SpacePageBaseComponent {
|
|
17
|
+
debtusService = inject(DEBTUS_SERVICE);
|
|
18
|
+
toastController = inject(ToastController);
|
|
19
|
+
$loading = signal(true, /* @ts-ignore */
|
|
20
|
+
...(ngDevMode ? [{ debugName: "$loading" }] : /* istanbul ignore next */ []));
|
|
21
|
+
$submitting = signal(false, /* @ts-ignore */
|
|
22
|
+
...(ngDevMode ? [{ debugName: "$submitting" }] : /* istanbul ignore next */ []));
|
|
23
|
+
$error = signal(undefined, /* @ts-ignore */
|
|
24
|
+
...(ngDevMode ? [{ debugName: "$error" }] : /* istanbul ignore next */ []));
|
|
25
|
+
$contact = signal(undefined, /* @ts-ignore */
|
|
26
|
+
...(ngDevMode ? [{ debugName: "$contact" }] : /* istanbul ignore next */ []));
|
|
27
|
+
contactID = '';
|
|
28
|
+
formatSignedBalance = formatSignedBalance;
|
|
29
|
+
currency = new FormControl('EUR', {
|
|
30
|
+
nonNullable: true,
|
|
31
|
+
});
|
|
32
|
+
amount = new FormControl(null, [
|
|
33
|
+
Validators.required,
|
|
34
|
+
Validators.min(0.01),
|
|
35
|
+
]);
|
|
36
|
+
form = new FormGroup({
|
|
37
|
+
currency: this.currency,
|
|
38
|
+
amount: this.amount,
|
|
39
|
+
});
|
|
40
|
+
currencies = ['EUR', 'USD'];
|
|
41
|
+
constructor() {
|
|
42
|
+
super();
|
|
43
|
+
combineLatest([this.spaceIDChanged$, this.route.queryParamMap])
|
|
44
|
+
.pipe(switchMap(([spaceID, params]) => {
|
|
45
|
+
this.contactID = params.get('contactID') ?? '';
|
|
46
|
+
// Back should return to the contact detail this settle-up was
|
|
47
|
+
// opened from, not a fixed page.
|
|
48
|
+
this.$defaultBackUrlSpacePath.set(`debtus-contact/${this.contactID}`);
|
|
49
|
+
this.$loading.set(true);
|
|
50
|
+
this.$error.set(undefined);
|
|
51
|
+
return this.debtusService.getContactBalance(spaceID ?? '', this.contactID);
|
|
52
|
+
}), takeUntilDestroyed())
|
|
53
|
+
.subscribe({
|
|
54
|
+
next: (contact) => {
|
|
55
|
+
this.$contact.set(contact);
|
|
56
|
+
// Default the settle amount/currency to the outstanding balance.
|
|
57
|
+
const entries = Object.entries(contact.balance).filter(([, v]) => v && round2(v) !== 0);
|
|
58
|
+
if (entries.length) {
|
|
59
|
+
const [cur, val] = entries[0];
|
|
60
|
+
this.currency.setValue(cur);
|
|
61
|
+
this.amount.setValue(Math.abs(round2(val)));
|
|
62
|
+
}
|
|
63
|
+
this.$loading.set(false);
|
|
64
|
+
},
|
|
65
|
+
error: (err) => {
|
|
66
|
+
this.$loading.set(false);
|
|
67
|
+
this.$error.set('Failed to load contact');
|
|
68
|
+
this.errorLogger.logError(err, 'Failed to load contact for settle');
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
submit() {
|
|
73
|
+
this.form.markAllAsTouched();
|
|
74
|
+
const amount = this.amount.value;
|
|
75
|
+
if (!amount || amount <= 0) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const spaceID = this.$spaceID();
|
|
79
|
+
if (!spaceID || !this.contactID) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
// Mirrors the bot's "Is it returned in full?" confirmation step
|
|
83
|
+
// (askIfReturnedInFull in transfer_return.go) before recording the
|
|
84
|
+
// settling transfer — this is a financial record, not a reversible
|
|
85
|
+
// toggle, so it needs an explicit yes.
|
|
86
|
+
const title = this.$contact()?.title ?? 'this contact';
|
|
87
|
+
if (!confirm(`Record settlement of ${this.currency.value} ${amount.toFixed(2)} with ${title}?`)) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
// This page shows the signed balance, so it (not the service) derives the
|
|
91
|
+
// direction that moves that balance toward zero for the chosen currency.
|
|
92
|
+
// The selected direction is part of the user's explicit settlement intent.
|
|
93
|
+
const balanceValue = this.$contact()?.balance[this.currency.value] ?? 0;
|
|
94
|
+
const request = {
|
|
95
|
+
spaceID,
|
|
96
|
+
contactID: this.contactID,
|
|
97
|
+
contactTitle: this.$contact()?.title,
|
|
98
|
+
amount: { currency: this.currency.value, value: amount },
|
|
99
|
+
counterpartySpaceID: this.$contact()?.counterpartySpaceID,
|
|
100
|
+
direction: settleDirectionForBalance(balanceValue),
|
|
101
|
+
};
|
|
102
|
+
this.$submitting.set(true);
|
|
103
|
+
this.debtusService.settleUp(request).subscribe({
|
|
104
|
+
next: (resp) => {
|
|
105
|
+
this.$submitting.set(false);
|
|
106
|
+
// Hand the created transfer to the details page via router state —
|
|
107
|
+
// transfer reads are not wired to the live backend yet, so the details
|
|
108
|
+
// The backend response is the persisted receipt shown to the user.
|
|
109
|
+
this.spaceNav.navigateForwardToSpacePage(this.space, `transfer/${resp.transfer.id}`, { replaceUrl: true, state: { transfer: resp.transfer } });
|
|
110
|
+
},
|
|
111
|
+
error: (err) => {
|
|
112
|
+
this.$submitting.set(false);
|
|
113
|
+
this.errorLogger.logError(err, 'Failed to settle up');
|
|
114
|
+
this.showToast('Failed to settle up. Please try again.');
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
async showToast(message) {
|
|
119
|
+
const toast = await this.toastController.create({
|
|
120
|
+
message,
|
|
121
|
+
duration: 3000,
|
|
122
|
+
color: 'danger',
|
|
123
|
+
});
|
|
124
|
+
await toast.present();
|
|
125
|
+
}
|
|
126
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SettleUpPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
127
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: SettleUpPageComponent, isStandalone: true, selector: "sneat-debtus-settle-up-page", providers: [
|
|
128
|
+
{ provide: ClassName, useValue: 'SettleUpPageComponent' },
|
|
129
|
+
SpaceComponentBaseParams,
|
|
130
|
+
], 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>Settle up</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>{{ $contact()?.title ?? 'Contact' }}</ion-card-title>\n </ion-card-header>\n <ion-card-content>\n Current balance: {{ formatSignedBalance($contact()?.balance ?? {}) }}\n </ion-card-content>\n </ion-card>\n\n <form [formGroup]=\"form\" (ngSubmit)=\"submit()\">\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 to settle</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-note class=\"ion-padding\" color=\"medium\">\n Records a settling (return) transfer. The balance is netted against\n outstanding transfers.\n </ion-note>\n </ion-card-content>\n </ion-card>\n\n <div class=\"ion-padding\">\n <ion-button\n expand=\"block\"\n color=\"success\"\n type=\"submit\"\n [disabled]=\"$submitting() || form.invalid\"\n >\n @if ($submitting()) {\n <ion-spinner name=\"dots\" slot=\"start\" />\n Settling\u2026\n } @else {\n Record settlement\n }\n </ion-button>\n </div>\n </form>\n }\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: 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: 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: 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: 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: "ngmodule", type: SpaceServiceModule }] });
|
|
131
|
+
}
|
|
132
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SettleUpPageComponent, decorators: [{
|
|
133
|
+
type: Component,
|
|
134
|
+
args: [{ selector: 'sneat-debtus-settle-up-page', imports: [
|
|
135
|
+
ReactiveFormsModule,
|
|
136
|
+
IonHeader,
|
|
137
|
+
IonToolbar,
|
|
138
|
+
IonButtons,
|
|
139
|
+
IonBackButton,
|
|
140
|
+
IonTitle,
|
|
141
|
+
IonContent,
|
|
142
|
+
IonCard,
|
|
143
|
+
IonCardHeader,
|
|
144
|
+
IonCardTitle,
|
|
145
|
+
IonCardContent,
|
|
146
|
+
IonItem,
|
|
147
|
+
IonLabel,
|
|
148
|
+
IonInput,
|
|
149
|
+
IonSelect,
|
|
150
|
+
IonSelectOption,
|
|
151
|
+
IonButton,
|
|
152
|
+
IonNote,
|
|
153
|
+
IonSpinner,
|
|
154
|
+
SpaceServiceModule,
|
|
155
|
+
], providers: [
|
|
156
|
+
{ provide: ClassName, useValue: 'SettleUpPageComponent' },
|
|
157
|
+
SpaceComponentBaseParams,
|
|
158
|
+
], 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>Settle up</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>{{ $contact()?.title ?? 'Contact' }}</ion-card-title>\n </ion-card-header>\n <ion-card-content>\n Current balance: {{ formatSignedBalance($contact()?.balance ?? {}) }}\n </ion-card-content>\n </ion-card>\n\n <form [formGroup]=\"form\" (ngSubmit)=\"submit()\">\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 to settle</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-note class=\"ion-padding\" color=\"medium\">\n Records a settling (return) transfer. The balance is netted against\n outstanding transfers.\n </ion-note>\n </ion-card-content>\n </ion-card>\n\n <div class=\"ion-padding\">\n <ion-button\n expand=\"block\"\n color=\"success\"\n type=\"submit\"\n [disabled]=\"$submitting() || form.invalid\"\n >\n @if ($submitting()) {\n <ion-spinner name=\"dots\" slot=\"start\" />\n Settling\u2026\n } @else {\n Record settlement\n }\n </ion-button>\n </div>\n </form>\n }\n</ion-content>\n" }]
|
|
159
|
+
}], ctorParameters: () => [] });
|
|
160
|
+
|
|
161
|
+
export { SettleUpPageComponent };
|
|
162
|
+
//# sourceMappingURL=sneat-extension-debtus-ui-settle-up-page.component-48l8YFn4.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sneat-extension-debtus-ui-settle-up-page.component-48l8YFn4.mjs","sources":["../../../../../../libs/extensions/debtus/ui/src/lib/pages/settle-up/settle-up-page.component.ts","../../../../../../libs/extensions/debtus/ui/src/lib/pages/settle-up/settle-up-page.component.html"],"sourcesContent":["import { 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 IonCardHeader,\n IonCardTitle,\n IonContent,\n IonHeader,\n IonInput,\n IonItem,\n IonLabel,\n IonNote,\n IonSelect,\n IonSelectOption,\n IonSpinner,\n IonTitle,\n IonToolbar,\n ToastController,\n} from '@ionic/angular';\nimport {\n CurrencyCode,\n DEBTUS_SERVICE,\n IContactBalance,\n ISettleUpRequest,\n formatSignedBalance,\n round2,\n settleDirectionForBalance,\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// Settle up — records a settling (return) transfer against a counterparty\n// balance. Mirrors the bot's \"Returned fully/partially\" flow: the backend nets\n// the return against outstanding transfers.\n@Component({\n selector: 'sneat-debtus-settle-up-page',\n templateUrl: './settle-up-page.component.html',\n imports: [\n ReactiveFormsModule,\n IonHeader,\n IonToolbar,\n IonButtons,\n IonBackButton,\n IonTitle,\n IonContent,\n IonCard,\n IonCardHeader,\n IonCardTitle,\n IonCardContent,\n IonItem,\n IonLabel,\n IonInput,\n IonSelect,\n IonSelectOption,\n IonButton,\n IonNote,\n IonSpinner,\n SpaceServiceModule,\n ],\n providers: [\n { provide: ClassName, useValue: 'SettleUpPageComponent' },\n SpaceComponentBaseParams,\n ],\n})\nexport class SettleUpPageComponent 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 $submitting = signal(false);\n protected readonly $error = signal<string | undefined>(undefined);\n protected readonly $contact = signal<IContactBalance | undefined>(undefined);\n protected contactID = '';\n\n protected readonly formatSignedBalance = formatSignedBalance;\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 form = new FormGroup({\n currency: this.currency,\n amount: this.amount,\n });\n\n protected readonly currencies: CurrencyCode[] = ['EUR', 'USD'];\n\n constructor() {\n super();\n combineLatest([this.spaceIDChanged$, this.route.queryParamMap])\n .pipe(\n switchMap(([spaceID, params]) => {\n this.contactID = params.get('contactID') ?? '';\n // Back should return to the contact detail this settle-up was\n // opened from, not a fixed page.\n this.$defaultBackUrlSpacePath.set(\n `debtus-contact/${this.contactID}`,\n );\n this.$loading.set(true);\n this.$error.set(undefined);\n return this.debtusService.getContactBalance(\n spaceID ?? '',\n this.contactID,\n );\n }),\n takeUntilDestroyed(),\n )\n .subscribe({\n next: (contact) => {\n this.$contact.set(contact);\n // Default the settle amount/currency to the outstanding balance.\n const entries = Object.entries(contact.balance).filter(\n ([, v]) => v && round2(v) !== 0,\n );\n if (entries.length) {\n const [cur, val] = entries[0];\n this.currency.setValue(cur as CurrencyCode);\n this.amount.setValue(Math.abs(round2(val as number)));\n }\n this.$loading.set(false);\n },\n error: (err) => {\n this.$loading.set(false);\n this.$error.set('Failed to load contact');\n this.errorLogger.logError(err, 'Failed to load contact for settle');\n },\n });\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 spaceID = this.$spaceID();\n if (!spaceID || !this.contactID) {\n return;\n }\n // Mirrors the bot's \"Is it returned in full?\" confirmation step\n // (askIfReturnedInFull in transfer_return.go) before recording the\n // settling transfer — this is a financial record, not a reversible\n // toggle, so it needs an explicit yes.\n const title = this.$contact()?.title ?? 'this contact';\n if (\n !confirm(\n `Record settlement of ${this.currency.value} ${amount.toFixed(2)} with ${title}?`,\n )\n ) {\n return;\n }\n // This page shows the signed balance, so it (not the service) derives the\n // direction that moves that balance toward zero for the chosen currency.\n // The selected direction is part of the user's explicit settlement intent.\n const balanceValue = this.$contact()?.balance[this.currency.value] ?? 0;\n const request: ISettleUpRequest = {\n spaceID,\n contactID: this.contactID,\n contactTitle: this.$contact()?.title,\n amount: { currency: this.currency.value, value: amount },\n counterpartySpaceID: this.$contact()?.counterpartySpaceID,\n direction: settleDirectionForBalance(balanceValue),\n };\n this.$submitting.set(true);\n this.debtusService.settleUp(request).subscribe({\n next: (resp) => {\n this.$submitting.set(false);\n // Hand the created transfer to the details page via router state —\n // transfer reads are not wired to the live backend yet, so the details\n // The backend response is the persisted receipt shown to the user.\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 settle up');\n this.showToast('Failed to settle up. Please try again.');\n },\n });\n }\n\n private async showToast(message: string): Promise<void> {\n const toast = await this.toastController.create({\n message,\n duration: 3000,\n color: 'danger',\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>Settle up</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>{{ $contact()?.title ?? 'Contact' }}</ion-card-title>\n </ion-card-header>\n <ion-card-content>\n Current balance: {{ formatSignedBalance($contact()?.balance ?? {}) }}\n </ion-card-content>\n </ion-card>\n\n <form [formGroup]=\"form\" (ngSubmit)=\"submit()\">\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 to settle</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-note class=\"ion-padding\" color=\"medium\">\n Records a settling (return) transfer. The balance is netted against\n outstanding transfers.\n </ion-note>\n </ion-card-content>\n </ion-card>\n\n <div class=\"ion-padding\">\n <ion-button\n expand=\"block\"\n color=\"success\"\n type=\"submit\"\n [disabled]=\"$submitting() || form.invalid\"\n >\n @if ($submitting()) {\n <ion-spinner name=\"dots\" slot=\"start\" />\n Settling…\n } @else {\n Record settlement\n }\n </ion-button>\n </div>\n </form>\n }\n</ion-content>\n"],"names":[],"mappings":"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"}
|