@rolatech/angular-billing 20.2.6-beta.1 → 20.2.7-beta.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.
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { input, output, Component, ViewEncapsulation, inject, signal, model } from '@angular/core';
|
|
2
|
+
import { input, output, Component, ViewEncapsulation, inject, signal, model, computed, Injectable } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/common';
|
|
4
4
|
import { CommonModule, DatePipe, KeyValuePipe } from '@angular/common';
|
|
5
5
|
import * as i1$2 from '@angular/forms';
|
|
@@ -19,9 +19,9 @@ import * as i7 from '@angular/material/select';
|
|
|
19
19
|
import { MatSelectModule } from '@angular/material/select';
|
|
20
20
|
import * as i1$1 from '@angular/router';
|
|
21
21
|
import { RouterModule, RouterLink } from '@angular/router';
|
|
22
|
-
import { Skeleton, BaseComponent, ContainerComponent, TabsComponent, TabComponent, ToolbarComponent, ListComponent, EmptyComponent, FilterComponent, MaterialModule } from '@rolatech/angular-components';
|
|
22
|
+
import { Skeleton, BaseComponent, ContainerComponent, TabsComponent, TabComponent, ToolbarComponent, ListComponent, EmptyComponent, FilterComponent, EnumSelect, ConfirmationDialogComponent, MaterialModule } from '@rolatech/angular-components';
|
|
23
23
|
import { PricePipe } from '@rolatech/angular-common';
|
|
24
|
-
import { InvoiceService, PaymentService } from '@rolatech/angular-services';
|
|
24
|
+
import { InvoiceService, PaymentService, EnumApiClient } from '@rolatech/angular-services';
|
|
25
25
|
import { Title } from '@angular/platform-browser';
|
|
26
26
|
import * as i8 from '@angular/material/paginator';
|
|
27
27
|
import { MatPaginatorModule } from '@angular/material/paginator';
|
|
@@ -30,12 +30,32 @@ import * as i9 from '@angular/material/card';
|
|
|
30
30
|
import { MatCardModule } from '@angular/material/card';
|
|
31
31
|
import * as i8$1 from '@angular/material/divider';
|
|
32
32
|
import { MatDividerModule } from '@angular/material/divider';
|
|
33
|
-
import { MomentDateAdapter } from '@angular/material-moment-adapter';
|
|
34
33
|
import * as i3$1 from '@angular/material/menu';
|
|
35
34
|
import { MatMenuModule } from '@angular/material/menu';
|
|
35
|
+
import { MomentDateAdapter } from '@angular/material-moment-adapter';
|
|
36
|
+
import { finalize as finalize$1 } from 'rxjs/operators';
|
|
37
|
+
import * as i1$3 from '@angular/cdk/drag-drop';
|
|
38
|
+
import { DragDropModule } from '@angular/cdk/drag-drop';
|
|
39
|
+
import { MatDialog } from '@angular/material/dialog';
|
|
36
40
|
|
|
37
41
|
const billingRoutes = [];
|
|
38
42
|
|
|
43
|
+
var InvoiceVatMode;
|
|
44
|
+
(function (InvoiceVatMode) {
|
|
45
|
+
InvoiceVatMode["EXCLUSIVE"] = "EXCLUSIVE";
|
|
46
|
+
InvoiceVatMode["INCLUSIVE"] = "INCLUSIVE";
|
|
47
|
+
})(InvoiceVatMode || (InvoiceVatMode = {}));
|
|
48
|
+
var InvoiceLineType;
|
|
49
|
+
(function (InvoiceLineType) {
|
|
50
|
+
InvoiceLineType["PROPERTY_RENT"] = "PROPERTY_RENT";
|
|
51
|
+
InvoiceLineType["PROPERTY_SALE"] = "PROPERTY_SALE";
|
|
52
|
+
InvoiceLineType["HOLDING_DEPOSIT"] = "HOLDING_DEPOSIT";
|
|
53
|
+
InvoiceLineType["SECURITY_DEPOSIT"] = "SECURITY_DEPOSIT";
|
|
54
|
+
InvoiceLineType["DISCOUNT"] = "DISCOUNT";
|
|
55
|
+
InvoiceLineType["FEE"] = "FEE";
|
|
56
|
+
InvoiceLineType["ADJUSTMENT"] = "ADJUSTMENT";
|
|
57
|
+
InvoiceLineType["CUSTOM"] = "CUSTOM";
|
|
58
|
+
})(InvoiceLineType || (InvoiceLineType = {}));
|
|
39
59
|
var InvoiceType;
|
|
40
60
|
(function (InvoiceType) {
|
|
41
61
|
InvoiceType["FIRST_PAYMENT"] = "First Payment";
|
|
@@ -45,6 +65,7 @@ var InvoiceType;
|
|
|
45
65
|
var InvoiceStatus;
|
|
46
66
|
(function (InvoiceStatus) {
|
|
47
67
|
InvoiceStatus["DRAFT"] = "Draft";
|
|
68
|
+
InvoiceStatus["SENT"] = "Sent";
|
|
48
69
|
InvoiceStatus["CREATED"] = "Created";
|
|
49
70
|
InvoiceStatus["ISSUED"] = "Issued";
|
|
50
71
|
InvoiceStatus["PARTIALLY_PAID"] = "Partially paid";
|
|
@@ -75,10 +96,10 @@ class InvoiceItem {
|
|
|
75
96
|
return 'bg-gray-100 text-gray-700';
|
|
76
97
|
}
|
|
77
98
|
}
|
|
78
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.
|
|
79
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.
|
|
99
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceItem, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
100
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.6", type: InvoiceItem, isStandalone: true, selector: "rolatech-invoice-item", inputs: { invoice: { classPropertyName: "invoice", publicName: "invoice", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { download: "download" }, ngImport: i0, template: "<div class=\"flex w-full justify-between p-3 cursor-pointer hover:bg-[--rt-raised-background]\">\n <div class=\"flex items-center\">\n <div class=\"w-[180px] max-w-[180px]\">{{invoice().id}}</div>\n </div>\n <div class=\"flex items-center gap-3\">\n <div>{{invoice().total | price}}</div>\n </div>\n <div class=\"flex items-center gap-3\">\n <div\n class=\"inline-flex items-center rounded-full px-3 py-1 text-xs font-medium\"\n [ngClass]=\"statusBadgeClass(invoice().status)\"\n >\n {{status[invoice().status]}}\n </div>\n <div class=\"w-[100px]\">{{invoice().createdAt | date:'dd/MM/yyyy':'Europe/London'}}</div>\n <button mat-icon-button class=\"px-2 min-w-[80px]\" (click)=\"onDownload();$event.stopPropagation()\">\n <mat-icon>download</mat-icon>\n </button>\n </div>\n</div>\n", styles: [".scrollbar-hide::-webkit-scrollbar{display:none}.scrollbar-hide{-ms-overflow-style:none;scrollbar-width:none}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "pipe", type: i1.DatePipe, name: "date" }, { kind: "pipe", type: PricePipe, name: "price" }] }); }
|
|
80
101
|
}
|
|
81
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
102
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceItem, decorators: [{
|
|
82
103
|
type: Component,
|
|
83
104
|
args: [{ selector: 'rolatech-invoice-item', imports: [CommonModule, MatButtonModule, MatIcon, DatePipe, PricePipe], template: "<div class=\"flex w-full justify-between p-3 cursor-pointer hover:bg-[--rt-raised-background]\">\n <div class=\"flex items-center\">\n <div class=\"w-[180px] max-w-[180px]\">{{invoice().id}}</div>\n </div>\n <div class=\"flex items-center gap-3\">\n <div>{{invoice().total | price}}</div>\n </div>\n <div class=\"flex items-center gap-3\">\n <div\n class=\"inline-flex items-center rounded-full px-3 py-1 text-xs font-medium\"\n [ngClass]=\"statusBadgeClass(invoice().status)\"\n >\n {{status[invoice().status]}}\n </div>\n <div class=\"w-[100px]\">{{invoice().createdAt | date:'dd/MM/yyyy':'Europe/London'}}</div>\n <button mat-icon-button class=\"px-2 min-w-[80px]\" (click)=\"onDownload();$event.stopPropagation()\">\n <mat-icon>download</mat-icon>\n </button>\n </div>\n</div>\n", styles: [".scrollbar-hide::-webkit-scrollbar{display:none}.scrollbar-hide{-ms-overflow-style:none;scrollbar-width:none}\n"] }]
|
|
84
105
|
}], propDecorators: { invoice: [{ type: i0.Input, args: [{ isSignal: true, alias: "invoice", required: true }] }], download: [{ type: i0.Output, args: ["download"] }] } });
|
|
@@ -87,37 +108,37 @@ class InvoiceManageItem {
|
|
|
87
108
|
constructor() {
|
|
88
109
|
this.invoice = input.required(...(ngDevMode ? [{ debugName: "invoice" }] : []));
|
|
89
110
|
}
|
|
90
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.
|
|
91
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.
|
|
111
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceManageItem, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
112
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.6", type: InvoiceManageItem, isStandalone: true, selector: "rolatech-invoice-manage-item", inputs: { invoice: { classPropertyName: "invoice", publicName: "invoice", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<p>invoice-manage-item works!</p>\n<div>{{invoice().id}}</div>\n", styles: [""] }); }
|
|
92
113
|
}
|
|
93
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
114
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceManageItem, decorators: [{
|
|
94
115
|
type: Component,
|
|
95
116
|
args: [{ selector: 'rolatech-invoice-manage-item', imports: [], template: "<p>invoice-manage-item works!</p>\n<div>{{invoice().id}}</div>\n" }]
|
|
96
117
|
}], propDecorators: { invoice: [{ type: i0.Input, args: [{ isSignal: true, alias: "invoice", required: true }] }] } });
|
|
97
118
|
|
|
98
119
|
class InvoiceHeader {
|
|
99
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.
|
|
100
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.
|
|
120
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceHeader, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
121
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.6", type: InvoiceHeader, isStandalone: true, selector: "rolatech-invoice-header", ngImport: i0, template: "<div class=\"flex items-center justify-between font-bold h-11 px-3\">\n <div class=\"flex items-center\">\n <div class=\"font-bold w-[180px] max-w-[180px]\">ID</div>\n </div>\n <div class=\"flex items-center gap-3\">\n <div class=\"font-bold\">Total</div>\n </div>\n <div class=\"flex items-center gap-3\">\n <div class=\"font-bold\">Status</div>\n <div class=\"font-bold w-[100px]\">Date</div>\n <div class=\"font-bold m-w-[80px]\">Download</div>\n </div>\n</div>\n", styles: [""] }); }
|
|
101
122
|
}
|
|
102
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
123
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceHeader, decorators: [{
|
|
103
124
|
type: Component,
|
|
104
125
|
args: [{ selector: 'rolatech-invoice-header', imports: [], template: "<div class=\"flex items-center justify-between font-bold h-11 px-3\">\n <div class=\"flex items-center\">\n <div class=\"font-bold w-[180px] max-w-[180px]\">ID</div>\n </div>\n <div class=\"flex items-center gap-3\">\n <div class=\"font-bold\">Total</div>\n </div>\n <div class=\"flex items-center gap-3\">\n <div class=\"font-bold\">Status</div>\n <div class=\"font-bold w-[100px]\">Date</div>\n <div class=\"font-bold m-w-[80px]\">Download</div>\n </div>\n</div>\n" }]
|
|
105
126
|
}] });
|
|
106
127
|
|
|
107
128
|
class InvoiceHeaderSkeleton {
|
|
108
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.
|
|
109
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.
|
|
129
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceHeaderSkeleton, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
130
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.6", type: InvoiceHeaderSkeleton, isStandalone: true, selector: "rolatech-invoice-header-skeleton", ngImport: i0, template: "<div class=\"grid grid-cols-6 h-11 p-3 gap-3\">\n <rolatech-skeleton class=\"col-span-1\"></rolatech-skeleton>\n <rolatech-skeleton class=\"col-start-3 col-span-2\"></rolatech-skeleton>\n <rolatech-skeleton class=\"col-start-6 col-span-1\"></rolatech-skeleton>\n</div>\n", styles: [""], dependencies: [{ kind: "component", type: Skeleton, selector: "rolatech-skeleton" }], encapsulation: i0.ViewEncapsulation.None }); }
|
|
110
131
|
}
|
|
111
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
132
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceHeaderSkeleton, decorators: [{
|
|
112
133
|
type: Component,
|
|
113
134
|
args: [{ selector: 'rolatech-invoice-header-skeleton', imports: [Skeleton], encapsulation: ViewEncapsulation.None, template: "<div class=\"grid grid-cols-6 h-11 p-3 gap-3\">\n <rolatech-skeleton class=\"col-span-1\"></rolatech-skeleton>\n <rolatech-skeleton class=\"col-start-3 col-span-2\"></rolatech-skeleton>\n <rolatech-skeleton class=\"col-start-6 col-span-1\"></rolatech-skeleton>\n</div>\n" }]
|
|
114
135
|
}] });
|
|
115
136
|
|
|
116
137
|
class InvoiceItemSkeleton {
|
|
117
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.
|
|
118
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.
|
|
138
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceItemSkeleton, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
139
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.6", type: InvoiceItemSkeleton, isStandalone: true, selector: "rolatech-invoice-item-skeleton", ngImport: i0, template: "<div class=\"grid grid-cols-6 place-content-center h-16 p-3 gap-3\">\n <rolatech-skeleton class=\"col-span-1 h-4\"></rolatech-skeleton>\n <rolatech-skeleton class=\"col-start-3 col-span-2 h-4\"></rolatech-skeleton>\n <rolatech-skeleton class=\"col-start-6 col-span-1 h-4\"></rolatech-skeleton>\n</div>\n", styles: [""], dependencies: [{ kind: "component", type: Skeleton, selector: "rolatech-skeleton" }], encapsulation: i0.ViewEncapsulation.None }); }
|
|
119
140
|
}
|
|
120
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
141
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceItemSkeleton, decorators: [{
|
|
121
142
|
type: Component,
|
|
122
143
|
args: [{ selector: 'rolatech-invoice-item-skeleton', imports: [Skeleton], encapsulation: ViewEncapsulation.None, template: "<div class=\"grid grid-cols-6 place-content-center h-16 p-3 gap-3\">\n <rolatech-skeleton class=\"col-span-1 h-4\"></rolatech-skeleton>\n <rolatech-skeleton class=\"col-start-3 col-span-2 h-4\"></rolatech-skeleton>\n <rolatech-skeleton class=\"col-start-6 col-span-1 h-4\"></rolatech-skeleton>\n</div>\n" }]
|
|
123
144
|
}] });
|
|
@@ -240,10 +261,10 @@ class InvoiceIndexComponent extends BaseComponent {
|
|
|
240
261
|
replaceUrl: true, // optional: avoid stacking history on every page click
|
|
241
262
|
});
|
|
242
263
|
}
|
|
243
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.
|
|
244
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.
|
|
264
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceIndexComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
265
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: InvoiceIndexComponent, isStandalone: true, selector: "rolatech-invoice-index", usesInheritance: true, ngImport: i0, template: "<rolatech-container>\n <rolatech-toolbar title=\"Invoices\" large>\n <button mat-button (click)=\"filter = !filter\">\n <span>Filter</span>\n <mat-icon>tune</mat-icon>\n </button>\n </rolatech-toolbar>\n <rolatech-filter>\n <div class=\"collapsed\" [class.expanded]=\"filter\">\n <div\n class=\"min-w-[256px] md:min-w-[320px] px-3 h-full flex flex-row md:flex-col md:h-full items-center md:items-start shadow-inner shadow-light-400 md:shadow-none overflow-x-scroll overflow-y-hidden scrollbar-hide whitespace-pre\"\n >\n <div class=\"flex items-center gap-3 mt-2\">\n <mat-form-field appearance=\"fill\" subscriptSizing=\"dynamic\">\n <mat-select name=\"type\" placeholder=\"Type\" [(ngModel)]=\"filterOptions.type\">\n @for (type of invoiceType | keyvalue; track type) {\n <mat-option [value]=\"type.key\">\n {{ type.value }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field subscriptSizing=\"dynamic\">\n <mat-select [compareWith]=\"statusCompareFn\" placeholder=\"Status\" [(ngModel)]=\"filterOptions.status\">\n @for (status of invoiceStatus | keyvalue; track status) {\n <mat-option [value]=\"status.key\">\n {{ status.value }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <div>\n <button mat-flat-button (click)=\"find()\">Search</button>\n <button mat-stroked-button (click)=\"resetFilter()\" class=\"ml-3\">Reset</button>\n </div>\n </div>\n </div>\n </div>\n </rolatech-filter>\n <rolatech-tabs [select]=\"select\">\n @for (item of links; track item) {\n @if (item.status) {\n <rolatech-tab [label]=\"item.name\" routerLink=\"./\" [queryParams]=\"{ status: item.status }\"></rolatech-tab>\n } @else {\n <rolatech-tab [label]=\"item.name\" routerLink=\"./\"></rolatech-tab>\n }\n }\n </rolatech-tabs>\n <rolatech-list>\n @if (loading) {\n <rolatech-invoice-header-skeleton></rolatech-invoice-header-skeleton>\n @for (dummy of [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; track dummy) {\n <rolatech-invoice-item-skeleton></rolatech-invoice-item-skeleton>\n }\n } @else {\n @if (invoices()) {\n <rolatech-invoice-header></rolatech-invoice-header>\n @for (item of invoices(); track item) {\n <rolatech-invoice-item [routerLink]=\"['./', item.id]\" [invoice]=\"item\"></rolatech-invoice-item>\n }\n } @else {\n <rolatech-empty></rolatech-empty>\n }\n }\n </rolatech-list>\n @if (!loading) {\n <mat-paginator\n #paginator\n [length]=\"length\"\n [pageSize]=\"pageSize\"\n [pageIndex]=\"pageIndex()\"\n [pageSizeOptions]=\"pageSizeOptions\"\n (page)=\"onPage($event)\"\n hidePageSize\n showFirstLastButtons\n >\n </mat-paginator>\n }\n</rolatech-container>\n", styles: [".collapsed{max-height:0;overflow:hidden;transition:max-height .5s cubic-bezier(.4,0,.2,1)}.expanded{max-height:1000px}\n"], dependencies: [{ kind: "component", type: ContainerComponent, selector: "rolatech-container" }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: TabsComponent, selector: "rolatech-tabs", inputs: ["select", "loading", "block"], outputs: ["selectChange"] }, { kind: "component", type: TabComponent, selector: "rolatech-tab", inputs: ["label"] }, { kind: "component", type: ToolbarComponent, selector: "rolatech-toolbar", inputs: ["title", "subtitle", "back", "link", "large", "divider"] }, { kind: "component", type: ListComponent, selector: "rolatech-list" }, { kind: "component", type: InvoiceItem, selector: "rolatech-invoice-item", inputs: ["invoice"], outputs: ["download"] }, { kind: "component", type: EmptyComponent, selector: "rolatech-empty" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "ngmodule", type: MatOptionModule }, { kind: "component", type: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i7.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: FilterComponent, selector: "rolatech-filter" }, { kind: "component", type: InvoiceHeader, selector: "rolatech-invoice-header" }, { kind: "ngmodule", type: MatPaginatorModule }, { kind: "component", type: i8.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "component", type: InvoiceHeaderSkeleton, selector: "rolatech-invoice-header-skeleton" }, { kind: "component", type: InvoiceItemSkeleton, selector: "rolatech-invoice-item-skeleton" }, { kind: "pipe", type: KeyValuePipe, name: "keyvalue" }], encapsulation: i0.ViewEncapsulation.None }); }
|
|
245
266
|
}
|
|
246
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
267
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceIndexComponent, decorators: [{
|
|
247
268
|
type: Component,
|
|
248
269
|
args: [{ selector: 'rolatech-invoice-index', imports: [
|
|
249
270
|
ContainerComponent,
|
|
@@ -279,10 +300,10 @@ class InvoiceUser {
|
|
|
279
300
|
this.email = input.required(...(ngDevMode ? [{ debugName: "email" }] : []));
|
|
280
301
|
this.phone = input.required(...(ngDevMode ? [{ debugName: "phone" }] : []));
|
|
281
302
|
}
|
|
282
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.
|
|
283
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.
|
|
303
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceUser, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
304
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.6", type: InvoiceUser, isStandalone: true, selector: "rolatech-invoice-user", inputs: { firstName: { classPropertyName: "firstName", publicName: "firstName", isSignal: true, isRequired: true, transformFunction: null }, lastName: { classPropertyName: "lastName", publicName: "lastName", isSignal: true, isRequired: true, transformFunction: null }, email: { classPropertyName: "email", publicName: "email", isSignal: true, isRequired: true, transformFunction: null }, phone: { classPropertyName: "phone", publicName: "phone", isSignal: true, isRequired: true, transformFunction: null } }, host: { attributes: { "id": "rolatech-invoice-user" }, classAttribute: "rolatech-invoice-user" }, ngImport: i0, template: "<div class=\"rounded-xl border border-[--rt-border-color] p-4 grid space-y-3\">\n <h2 class=\"text-sm font-semibold\">User</h2>\n <div class=\"text-sm\">\n <div class=\"font-medium\">{{ firstName() }}, {{ lastName() }}</div>\n <div class=\"font-medium\">{{ phone() }}</div>\n <div>{{ email() }}</div>\n </div>\n</div>\n", styles: [""], encapsulation: i0.ViewEncapsulation.None }); }
|
|
284
305
|
}
|
|
285
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
306
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceUser, decorators: [{
|
|
286
307
|
type: Component,
|
|
287
308
|
args: [{ selector: 'rolatech-invoice-user', imports: [], encapsulation: ViewEncapsulation.None, host: {
|
|
288
309
|
id: 'rolatech-invoice-user',
|
|
@@ -294,15 +315,13 @@ class InvoiceLines {
|
|
|
294
315
|
constructor() {
|
|
295
316
|
this.lines = input([], ...(ngDevMode ? [{ debugName: "lines" }] : []));
|
|
296
317
|
}
|
|
297
|
-
|
|
298
|
-
static { this.ɵ
|
|
318
|
+
add() { }
|
|
319
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceLines, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
320
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: InvoiceLines, isStandalone: true, selector: "rolatech-invoice-lines", inputs: { lines: { classPropertyName: "lines", publicName: "lines", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"rounded-xl border border-[--rt-border-color] p-4 grid space-y-3\">\n <div class=\"flex justify-between\">\n <h2 class=\"text-sm font-semibold\">Invoice lines</h2>\n </div>\n\n <div class=\"overflow-x-auto\">\n <table class=\"min-w-full text-sm border border-[--rt-border-color] rounded-lg\">\n <thead class=\"bg-[--rt-raised-background]\">\n <tr>\n <th class=\"px-3 py-2 text-left font-medium border-b border-[--rt-border-color]\">Title</th>\n <th class=\"px-3 py-2 text-right font-medium border-b border-[--rt-border-color] w-28\">Unit (\u00A3)</th>\n <th class=\"px-3 py-2 text-right font-medium border-b border-[--rt-border-color] w-20\">VAT %</th>\n <th class=\"px-3 py-2 text-right font-medium border-b border-[--rt-border-color] w-28\">VAT (\u00A3)</th>\n <th class=\"px-3 py-2 text-right font-medium border-b border-[--rt-border-color] w-32\">Total (\u00A3)</th>\n </tr>\n </thead>\n\n <tbody class=\"[&>tr:nth-child(even)]:bg-[--rt-raised-background]\">\n @if (lines().length > 0) { @for (line of lines(); track line.id) {\n <tr class=\"hover:bg-[--rt-raised-background] cursor-pointer\">\n <!-- Title -->\n <td class=\"px-3 py-2 border-t border-[--rt-border-color]\">\n <div class=\"font-medium\">{{ line.title }}</div>\n </td>\n\n <!-- Unit price -->\n <td class=\"px-3 py-2 border-t border-[--rt-border-color] text-right text-[--rt-text-secondary]\">\n {{ line.unitPrice | price }}\n </td>\n\n <!-- VAT rate -->\n <td class=\"px-3 py-2 border-t border-[--rt-border-color] text-right text-[--rt-text-secondary]\">\n {{ line.vatRate }}%\n </td>\n\n <!-- VAT amount -->\n <td class=\"px-3 py-2 border-t border-[--rt-border-color] text-right text-[--rt-text-secondary]\">\n {{ line.vatAmount | price }}\n </td>\n\n <!-- Total -->\n <td class=\"px-3 py-2 border-t border-[--rt-border-color] text-right font-semibold text-[--rt-text-secondary]\">\n {{ line.lineTotal | price }}\n </td>\n </tr>\n } } @else {\n <tr>\n <td [attr.colspan]=\"6\" class=\"px-3 py-4 text-center text-[--rt-text-secondary] text-sm\">No invoice lines</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n</div>\n", styles: [".cdk-drag-preview{box-shadow:0 10px 20px #00000026;border-radius:12px}.cdk-drag-placeholder{opacity:.3}\n"], dependencies: [{ kind: "pipe", type: PricePipe, name: "price" }], encapsulation: i0.ViewEncapsulation.None }); }
|
|
299
321
|
}
|
|
300
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
322
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceLines, decorators: [{
|
|
301
323
|
type: Component,
|
|
302
|
-
args: [{ selector: 'rolatech-invoice-lines', imports: [PricePipe], encapsulation: ViewEncapsulation.None,
|
|
303
|
-
id: 'rolatech-invoice-lines',
|
|
304
|
-
class: 'rolatech-invoice-lines',
|
|
305
|
-
}, template: "<div class=\"rounded-xl border border-[--rt-border-color] p-4 grid space-y-3\">\n <h2 class=\"text-sm font-semibold\">Invoice lines</h2>\n\n <div class=\"overflow-x-auto\">\n <table class=\"min-w-full text-sm border border-[--rt-border-color] rounded-lg\">\n <thead class=\"bg-[--rt-raised-background]\">\n <tr>\n <th class=\"px-3 py-2 text-left font-medium border-b border-[--rt-border-color]\">Item</th>\n <th class=\"px-3 py-2 text-center font-medium border-b border-[--rt-border-color] w-20\">Qty</th>\n <th class=\"px-3 py-2 text-right font-medium border-b border-[--rt-border-color] w-28\">Unit (\u00A3)</th>\n <th class=\"px-3 py-2 text-right font-medium border-b border-[--rt-border-color] w-20\">VAT %</th>\n <th class=\"px-3 py-2 text-right font-medium border-b border-[--rt-border-color] w-28\">VAT (\u00A3)</th>\n <th class=\"px-3 py-2 text-right font-medium border-b border-[--rt-border-color] w-32\">Total (\u00A3)</th>\n </tr>\n </thead>\n\n <tbody class=\"[&>tr:nth-child(even)]:bg-[--rt-raised-background]\">\n @if (lines().length > 0) { @for (line of lines(); track line.id) {\n <tr class=\"hover:bg-[--rt-raised-background] cursor-pointer\">\n <!-- Title -->\n <td class=\"px-3 py-2 border-t border-[--rt-border-color]\">\n <div class=\"font-medium\">{{ line.title }}</div>\n\n @if (line.subtitle) {\n <div class=\"text-xs\">{{ line.subtitle }}</div>\n }\n </td>\n\n <!-- Qty -->\n <td class=\"px-3 py-2 border-t border-[--rt-border-color] text-center text-[--rt-text-secondary]\">\n {{ line.quantity }}\n </td>\n\n <!-- Unit price -->\n <td class=\"px-3 py-2 border-t border-[--rt-border-color] text-right text-[--rt-text-secondary]\">\n {{ line.unitPrice | price }}\n </td>\n\n <!-- VAT rate -->\n <td class=\"px-3 py-2 border-t border-[--rt-border-color] text-right text-[--rt-text-secondary]\">\n {{ line.vatRate }}%\n </td>\n\n <!-- VAT amount -->\n <td class=\"px-3 py-2 border-t border-[--rt-border-color] text-right text-[--rt-text-secondary]\">\n {{ line.vatAmount | price }}\n </td>\n\n <!-- Total -->\n <td class=\"px-3 py-2 border-t border-[--rt-border-color] text-right font-semibold text-[--rt-text-secondary]\">\n {{ line.lineTotal | price }}\n </td>\n </tr>\n } } @else {\n <tr>\n <td [attr.colspan]=\"6\" class=\"px-3 py-4 text-center text-[--rt-text-secondary] text-sm\">No invoice lines</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n</div>\n" }]
|
|
324
|
+
args: [{ selector: 'rolatech-invoice-lines', imports: [PricePipe], encapsulation: ViewEncapsulation.None, template: "<div class=\"rounded-xl border border-[--rt-border-color] p-4 grid space-y-3\">\n <div class=\"flex justify-between\">\n <h2 class=\"text-sm font-semibold\">Invoice lines</h2>\n </div>\n\n <div class=\"overflow-x-auto\">\n <table class=\"min-w-full text-sm border border-[--rt-border-color] rounded-lg\">\n <thead class=\"bg-[--rt-raised-background]\">\n <tr>\n <th class=\"px-3 py-2 text-left font-medium border-b border-[--rt-border-color]\">Title</th>\n <th class=\"px-3 py-2 text-right font-medium border-b border-[--rt-border-color] w-28\">Unit (\u00A3)</th>\n <th class=\"px-3 py-2 text-right font-medium border-b border-[--rt-border-color] w-20\">VAT %</th>\n <th class=\"px-3 py-2 text-right font-medium border-b border-[--rt-border-color] w-28\">VAT (\u00A3)</th>\n <th class=\"px-3 py-2 text-right font-medium border-b border-[--rt-border-color] w-32\">Total (\u00A3)</th>\n </tr>\n </thead>\n\n <tbody class=\"[&>tr:nth-child(even)]:bg-[--rt-raised-background]\">\n @if (lines().length > 0) { @for (line of lines(); track line.id) {\n <tr class=\"hover:bg-[--rt-raised-background] cursor-pointer\">\n <!-- Title -->\n <td class=\"px-3 py-2 border-t border-[--rt-border-color]\">\n <div class=\"font-medium\">{{ line.title }}</div>\n </td>\n\n <!-- Unit price -->\n <td class=\"px-3 py-2 border-t border-[--rt-border-color] text-right text-[--rt-text-secondary]\">\n {{ line.unitPrice | price }}\n </td>\n\n <!-- VAT rate -->\n <td class=\"px-3 py-2 border-t border-[--rt-border-color] text-right text-[--rt-text-secondary]\">\n {{ line.vatRate }}%\n </td>\n\n <!-- VAT amount -->\n <td class=\"px-3 py-2 border-t border-[--rt-border-color] text-right text-[--rt-text-secondary]\">\n {{ line.vatAmount | price }}\n </td>\n\n <!-- Total -->\n <td class=\"px-3 py-2 border-t border-[--rt-border-color] text-right font-semibold text-[--rt-text-secondary]\">\n {{ line.lineTotal | price }}\n </td>\n </tr>\n } } @else {\n <tr>\n <td [attr.colspan]=\"6\" class=\"px-3 py-4 text-center text-[--rt-text-secondary] text-sm\">No invoice lines</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n</div>\n", styles: [".cdk-drag-preview{box-shadow:0 10px 20px #00000026;border-radius:12px}.cdk-drag-placeholder{opacity:.3}\n"] }]
|
|
306
325
|
}], propDecorators: { lines: [{ type: i0.Input, args: [{ isSignal: true, alias: "lines", required: false }] }] } });
|
|
307
326
|
|
|
308
327
|
class InvoiceSummary {
|
|
@@ -312,10 +331,10 @@ class InvoiceSummary {
|
|
|
312
331
|
this.subtotal = input.required(...(ngDevMode ? [{ debugName: "subtotal" }] : []));
|
|
313
332
|
this.total = input.required(...(ngDevMode ? [{ debugName: "total" }] : []));
|
|
314
333
|
}
|
|
315
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.
|
|
316
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.
|
|
334
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceSummary, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
335
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: InvoiceSummary, isStandalone: true, selector: "rolatech-invoice-summary", inputs: { tax: { classPropertyName: "tax", publicName: "tax", isSignal: true, isRequired: true, transformFunction: null }, credit: { classPropertyName: "credit", publicName: "credit", isSignal: true, isRequired: false, transformFunction: null }, subtotal: { classPropertyName: "subtotal", publicName: "subtotal", isSignal: true, isRequired: true, transformFunction: null }, total: { classPropertyName: "total", publicName: "total", isSignal: true, isRequired: true, transformFunction: null } }, host: { attributes: { "id": "rolatech-invoice-summary" }, classAttribute: "rolatech-invoice-summary" }, ngImport: i0, template: "<div class=\"rounded-xl border border-[--rt-border-color] p-4 grid space-y-3\">\n <h2 class=\"text-sm font-semibold text-[--rt-text-secondary]\">Totals</h2>\n\n <dl class=\"space-y-2 text-sm\">\n <div class=\"flex justify-between\">\n <dt class=\"text-[--rt-text-secondary]\">Subtotal</dt>\n <dd class=\"font-medium\">{{ subtotal() | price }}</dd>\n </div>\n\n <div class=\"flex justify-between\">\n <dt class=\"text-[--rt-text-secondary]\">VAT</dt>\n <dd class=\"font-medium\">{{ tax() | price}}</dd>\n </div>\n\n @if (credit() > 0) {\n <div class=\"flex justify-between\">\n <dt class=\"text-[--rt-text-secondary]\">Less holding deposit</dt>\n <dd class=\"font-medium text-red-600\">\u2212{{ credit() | price }}</dd>\n </div>\n }\n\n <div class=\"border-t border-dashed border-[--rt-border-color] my-2\"></div>\n\n <div class=\"flex justify-between items-center\">\n <dt class=\"text-[--rt-text-secondary] font-semibold\">Total</dt>\n <dd class=\"text-lg font-semibold\">{{ total() | price }}</dd>\n </div>\n </dl>\n</div>\n", styles: [""], dependencies: [{ kind: "pipe", type: PricePipe, name: "price" }], encapsulation: i0.ViewEncapsulation.None }); }
|
|
317
336
|
}
|
|
318
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
337
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceSummary, decorators: [{
|
|
319
338
|
type: Component,
|
|
320
339
|
args: [{ selector: 'rolatech-invoice-summary', imports: [PricePipe], encapsulation: ViewEncapsulation.None, host: {
|
|
321
340
|
id: 'rolatech-invoice-summary',
|
|
@@ -324,28 +343,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImpor
|
|
|
324
343
|
}], propDecorators: { tax: [{ type: i0.Input, args: [{ isSignal: true, alias: "tax", required: true }] }], credit: [{ type: i0.Input, args: [{ isSignal: true, alias: "credit", required: false }] }], subtotal: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtotal", required: true }] }], total: [{ type: i0.Input, args: [{ isSignal: true, alias: "total", required: true }] }] } });
|
|
325
344
|
|
|
326
345
|
class InvoiceUserSkeleton {
|
|
327
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.
|
|
328
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.
|
|
346
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceUserSkeleton, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
347
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.6", type: InvoiceUserSkeleton, isStandalone: true, selector: "rolatech-invoice-user-skeleton", ngImport: i0, template: "<div class=\"rounded-xl border border-[--rt-border-color] p-4 grid space-y-3\">\n <rolatech-skeleton class=\"w-12 h-6\"></rolatech-skeleton>\n <div class=\"flex flex-col gap-2\">\n <rolatech-skeleton class=\"w-28 h-4\"></rolatech-skeleton>\n <rolatech-skeleton class=\"w-32 h-4\"></rolatech-skeleton>\n <rolatech-skeleton class=\"w-36 h-4\"></rolatech-skeleton>\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "component", type: Skeleton, selector: "rolatech-skeleton" }], encapsulation: i0.ViewEncapsulation.None }); }
|
|
329
348
|
}
|
|
330
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
349
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceUserSkeleton, decorators: [{
|
|
331
350
|
type: Component,
|
|
332
351
|
args: [{ selector: 'rolatech-invoice-user-skeleton', imports: [Skeleton], encapsulation: ViewEncapsulation.None, template: "<div class=\"rounded-xl border border-[--rt-border-color] p-4 grid space-y-3\">\n <rolatech-skeleton class=\"w-12 h-6\"></rolatech-skeleton>\n <div class=\"flex flex-col gap-2\">\n <rolatech-skeleton class=\"w-28 h-4\"></rolatech-skeleton>\n <rolatech-skeleton class=\"w-32 h-4\"></rolatech-skeleton>\n <rolatech-skeleton class=\"w-36 h-4\"></rolatech-skeleton>\n </div>\n</div>\n" }]
|
|
333
352
|
}] });
|
|
334
353
|
|
|
335
354
|
class InvoiceSummarySkeleton {
|
|
336
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.
|
|
337
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.
|
|
355
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceSummarySkeleton, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
356
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.6", type: InvoiceSummarySkeleton, isStandalone: true, selector: "rolatech-invoice-summary-skeleton", ngImport: i0, template: "<div class=\"rounded-xl border border-[--rt-border-color] p-4 grid space-y-3\">\n <rolatech-skeleton class=\"w-12 h-6\"></rolatech-skeleton>\n\n <dl class=\"space-y-2 text-sm\">\n <div class=\"flex justify-between\">\n <rolatech-skeleton class=\"w-28 h-4\"></rolatech-skeleton>\n <rolatech-skeleton class=\"w-11 h-4\"></rolatech-skeleton>\n </div>\n <div class=\"flex justify-between\">\n <rolatech-skeleton class=\"w-28 h-4\"></rolatech-skeleton>\n <rolatech-skeleton class=\"w-11 h-4\"></rolatech-skeleton>\n </div>\n <div class=\"flex justify-between\">\n <rolatech-skeleton class=\"w-32 h-4\"></rolatech-skeleton>\n <rolatech-skeleton class=\"w-11 h-4\"></rolatech-skeleton>\n </div>\n\n <div class=\"border-t border-dashed border-[--rt-border-color] my-2\"></div>\n\n <div class=\"flex justify-between\">\n <rolatech-skeleton class=\"w-11 h-4\"></rolatech-skeleton>\n <rolatech-skeleton class=\"w-28 h-4\"></rolatech-skeleton>\n </div>\n </dl>\n</div>\n", styles: [""], dependencies: [{ kind: "component", type: Skeleton, selector: "rolatech-skeleton" }], encapsulation: i0.ViewEncapsulation.None }); }
|
|
338
357
|
}
|
|
339
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
358
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceSummarySkeleton, decorators: [{
|
|
340
359
|
type: Component,
|
|
341
360
|
args: [{ selector: 'rolatech-invoice-summary-skeleton', imports: [Skeleton], encapsulation: ViewEncapsulation.None, template: "<div class=\"rounded-xl border border-[--rt-border-color] p-4 grid space-y-3\">\n <rolatech-skeleton class=\"w-12 h-6\"></rolatech-skeleton>\n\n <dl class=\"space-y-2 text-sm\">\n <div class=\"flex justify-between\">\n <rolatech-skeleton class=\"w-28 h-4\"></rolatech-skeleton>\n <rolatech-skeleton class=\"w-11 h-4\"></rolatech-skeleton>\n </div>\n <div class=\"flex justify-between\">\n <rolatech-skeleton class=\"w-28 h-4\"></rolatech-skeleton>\n <rolatech-skeleton class=\"w-11 h-4\"></rolatech-skeleton>\n </div>\n <div class=\"flex justify-between\">\n <rolatech-skeleton class=\"w-32 h-4\"></rolatech-skeleton>\n <rolatech-skeleton class=\"w-11 h-4\"></rolatech-skeleton>\n </div>\n\n <div class=\"border-t border-dashed border-[--rt-border-color] my-2\"></div>\n\n <div class=\"flex justify-between\">\n <rolatech-skeleton class=\"w-11 h-4\"></rolatech-skeleton>\n <rolatech-skeleton class=\"w-28 h-4\"></rolatech-skeleton>\n </div>\n </dl>\n</div>\n" }]
|
|
342
361
|
}] });
|
|
343
362
|
|
|
344
363
|
class InvoiceLinesSkeleton {
|
|
345
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.
|
|
346
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.
|
|
364
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceLinesSkeleton, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
365
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: InvoiceLinesSkeleton, isStandalone: true, selector: "rolatech-invoice-lines-skeleton", ngImport: i0, template: "<div class=\"rounded-xl border border-[--rt-border-color] p-4 grid space-y-3\">\n <rolatech-skeleton class=\"w-20 h-6\"></rolatech-skeleton>\n\n <div class=\"overflow-x-auto\">\n <table class=\"min-w-full text-sm border border-[--rt-border-color] rounded-lg\">\n <thead class=\"bg-[--rt-raised-background]\">\n <tr>\n <th class=\"px-3 py-2 text-left font-medium border-b border-[--rt-border-color]\">\n <rolatech-skeleton class=\"h-4\"></rolatech-skeleton>\n </th>\n <th class=\"px-3 py-2 text-center font-medium border-b border-[--rt-border-color] w-20\">\n <rolatech-skeleton class=\"h-4\"></rolatech-skeleton>\n </th>\n <th class=\"px-3 py-2 text-right font-medium border-b border-[--rt-border-color] w-28\">\n <rolatech-skeleton class=\"h-4\"></rolatech-skeleton>\n </th>\n <th class=\"px-3 py-2 text-right font-medium border-b border-[--rt-border-color] w-20\">\n <rolatech-skeleton class=\"h-4\"></rolatech-skeleton>\n </th>\n <th class=\"px-3 py-2 text-right font-medium border-b border-[--rt-border-color] w-28\">\n <rolatech-skeleton class=\"h-4\"></rolatech-skeleton>\n </th>\n <th class=\"px-3 py-2 text-right font-medium border-b border-[--rt-border-color] w-32\">\n <rolatech-skeleton class=\"h-4\"></rolatech-skeleton>\n </th>\n </tr>\n </thead>\n\n <tbody class=\"[&>tr:nth-child(even)]:bg-[--rt-raised-background]\">\n @for (dummy of [0, 1, 2, 3]; track dummy) {\n <tr>\n <!-- Title -->\n <td class=\"px-3 py-2 border-t border-[--rt-border-color]\">\n <rolatech-skeleton class=\"h-6\"></rolatech-skeleton>\n </td>\n\n <!-- Qty -->\n <td class=\"px-3 py-2 border-t border-[--rt-border-color] text-center text-[--rt-text-secondary]\">\n <rolatech-skeleton class=\"h-4\"></rolatech-skeleton>\n </td>\n\n <!-- Unit price -->\n <td class=\"px-3 py-2 border-t border-[--rt-border-color] text-right text-[--rt-text-secondary]\">\n <rolatech-skeleton class=\"h-4\"></rolatech-skeleton>\n </td>\n\n <!-- VAT rate -->\n <td class=\"px-3 py-2 border-t border-[--rt-border-color] text-right text-[--rt-text-secondary]\">\n <rolatech-skeleton class=\"h-4\"></rolatech-skeleton>\n </td>\n <!-- VAT amount -->\n <td class=\"px-3 py-2 border-t border-[--rt-border-color] text-right text-[--rt-text-secondary]\">\n <rolatech-skeleton class=\"h-4\"></rolatech-skeleton>\n </td>\n\n <!-- Total -->\n <td class=\"px-3 py-2 border-t border-[--rt-border-color] text-right font-semibold text-[--rt-text-secondary]\">\n <rolatech-skeleton class=\"h-4\"></rolatech-skeleton>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "component", type: Skeleton, selector: "rolatech-skeleton" }], encapsulation: i0.ViewEncapsulation.None }); }
|
|
347
366
|
}
|
|
348
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
367
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceLinesSkeleton, decorators: [{
|
|
349
368
|
type: Component,
|
|
350
369
|
args: [{ selector: 'rolatech-invoice-lines-skeleton', imports: [Skeleton], encapsulation: ViewEncapsulation.None, template: "<div class=\"rounded-xl border border-[--rt-border-color] p-4 grid space-y-3\">\n <rolatech-skeleton class=\"w-20 h-6\"></rolatech-skeleton>\n\n <div class=\"overflow-x-auto\">\n <table class=\"min-w-full text-sm border border-[--rt-border-color] rounded-lg\">\n <thead class=\"bg-[--rt-raised-background]\">\n <tr>\n <th class=\"px-3 py-2 text-left font-medium border-b border-[--rt-border-color]\">\n <rolatech-skeleton class=\"h-4\"></rolatech-skeleton>\n </th>\n <th class=\"px-3 py-2 text-center font-medium border-b border-[--rt-border-color] w-20\">\n <rolatech-skeleton class=\"h-4\"></rolatech-skeleton>\n </th>\n <th class=\"px-3 py-2 text-right font-medium border-b border-[--rt-border-color] w-28\">\n <rolatech-skeleton class=\"h-4\"></rolatech-skeleton>\n </th>\n <th class=\"px-3 py-2 text-right font-medium border-b border-[--rt-border-color] w-20\">\n <rolatech-skeleton class=\"h-4\"></rolatech-skeleton>\n </th>\n <th class=\"px-3 py-2 text-right font-medium border-b border-[--rt-border-color] w-28\">\n <rolatech-skeleton class=\"h-4\"></rolatech-skeleton>\n </th>\n <th class=\"px-3 py-2 text-right font-medium border-b border-[--rt-border-color] w-32\">\n <rolatech-skeleton class=\"h-4\"></rolatech-skeleton>\n </th>\n </tr>\n </thead>\n\n <tbody class=\"[&>tr:nth-child(even)]:bg-[--rt-raised-background]\">\n @for (dummy of [0, 1, 2, 3]; track dummy) {\n <tr>\n <!-- Title -->\n <td class=\"px-3 py-2 border-t border-[--rt-border-color]\">\n <rolatech-skeleton class=\"h-6\"></rolatech-skeleton>\n </td>\n\n <!-- Qty -->\n <td class=\"px-3 py-2 border-t border-[--rt-border-color] text-center text-[--rt-text-secondary]\">\n <rolatech-skeleton class=\"h-4\"></rolatech-skeleton>\n </td>\n\n <!-- Unit price -->\n <td class=\"px-3 py-2 border-t border-[--rt-border-color] text-right text-[--rt-text-secondary]\">\n <rolatech-skeleton class=\"h-4\"></rolatech-skeleton>\n </td>\n\n <!-- VAT rate -->\n <td class=\"px-3 py-2 border-t border-[--rt-border-color] text-right text-[--rt-text-secondary]\">\n <rolatech-skeleton class=\"h-4\"></rolatech-skeleton>\n </td>\n <!-- VAT amount -->\n <td class=\"px-3 py-2 border-t border-[--rt-border-color] text-right text-[--rt-text-secondary]\">\n <rolatech-skeleton class=\"h-4\"></rolatech-skeleton>\n </td>\n\n <!-- Total -->\n <td class=\"px-3 py-2 border-t border-[--rt-border-color] text-right font-semibold text-[--rt-text-secondary]\">\n <rolatech-skeleton class=\"h-4\"></rolatech-skeleton>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n</div>\n" }]
|
|
351
370
|
}] });
|
|
@@ -422,13 +441,14 @@ class InvoiceDetailComponent extends BaseComponent {
|
|
|
422
441
|
},
|
|
423
442
|
error: (error) => {
|
|
424
443
|
this.paying = false;
|
|
444
|
+
this.snackBarService.open(error.message);
|
|
425
445
|
},
|
|
426
446
|
});
|
|
427
447
|
}
|
|
428
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.
|
|
429
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.
|
|
448
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceDetailComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
449
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: InvoiceDetailComponent, isStandalone: true, selector: "rolatech-invoice-detail", usesInheritance: true, ngImport: i0, template: "<rolatech-container>\n <rolatech-toolbar [title]=\"invoice() ? status[invoice()!.status] : ''\" large link=\"../\"> </rolatech-toolbar>\n @if (loading()) {\n <div class=\"grid grid-cols-1 lg:grid-cols-3 gap-4\">\n <div class=\"grid lg:col-span-2 space-y-4\">\n <rolatech-invoice-user-skeleton></rolatech-invoice-user-skeleton>\n <rolatech-invoice-lines-skeleton></rolatech-invoice-lines-skeleton>\n </div>\n <div class=\"space-y-4\">\n <rolatech-invoice-summary-skeleton></rolatech-invoice-summary-skeleton>\n </div>\n </div>\n } @else {\n @if (invoice(); as invoice) {\n <div class=\"grid grid-cols-1 lg:grid-cols-3 gap-4\">\n <div class=\"grid lg:col-span-2 space-y-4\">\n <rolatech-invoice-user\n [firstName]=\"invoice.firstName\"\n [lastName]=\"invoice.lastName\"\n [email]=\"invoice.email\"\n [phone]=\"invoice.phone\"\n ></rolatech-invoice-user>\n @if (invoice?.lines; as lines) {\n <rolatech-invoice-lines [lines]=\"lines\"></rolatech-invoice-lines>\n }\n </div>\n <div class=\"space-y-4\">\n <rolatech-invoice-summary\n [tax]=\"invoice.vatTotal\"\n [credit]=\"invoice.holdingDepositApplied\"\n [subtotal]=\"invoice.subtotal\"\n [total]=\"invoice.total\"\n ></rolatech-invoice-summary>\n <div class=\"px-2\">\n <div class=\"flex items-baseline justify-between py-1\">\n <span class=\"font-medium min-w-20\" i18n>Note</span>\n <span class=\"text-sm\">{{ invoice.note || 'Null' }}</span>\n </div>\n @if (invoice.status.toString() === 'CREATED' || invoice.status.toString() === 'PAID') {\n <div class=\"flex items-center justify-between py-1\">\n <span class=\"font-medium\" i18n>Payment method</span>\n <span class=\"text-sm\"> Stripe</span>\n </div>\n }\n @if (invoice.status.toString() === 'ISSUED' || invoice.status.toString() === 'SENT') {\n <div class=\"py-3\">\n <div class=\"flex items-center justify-end\">\n <button mat-flat-button [disabled]=\"paying\" class=\"w-full min-h-11\" (click)=\"pay()\" i18n>\n {{ paying ? 'Processing' : 'Pay' }}\n </button>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n <div>\n <!-- safe area -->\n <div class=\"pb-16 sm:pb-3\"></div>\n </div>\n }\n }\n</rolatech-container>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: ContainerComponent, selector: "rolatech-container" }, { kind: "component", type: ToolbarComponent, selector: "rolatech-toolbar", inputs: ["title", "subtitle", "back", "link", "large", "divider"] }, { kind: "component", type: InvoiceUser, selector: "rolatech-invoice-user", inputs: ["firstName", "lastName", "email", "phone"] }, { kind: "component", type: InvoiceLines, selector: "rolatech-invoice-lines", inputs: ["lines"] }, { kind: "component", type: InvoiceSummary, selector: "rolatech-invoice-summary", inputs: ["tax", "credit", "subtotal", "total"] }, { kind: "component", type: InvoiceUserSkeleton, selector: "rolatech-invoice-user-skeleton" }, { kind: "component", type: InvoiceSummarySkeleton, selector: "rolatech-invoice-summary-skeleton" }, { kind: "component", type: InvoiceLinesSkeleton, selector: "rolatech-invoice-lines-skeleton" }] }); }
|
|
430
450
|
}
|
|
431
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
451
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceDetailComponent, decorators: [{
|
|
432
452
|
type: Component,
|
|
433
453
|
args: [{ selector: 'rolatech-invoice-detail', imports: [
|
|
434
454
|
MatButtonModule,
|
|
@@ -441,7 +461,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImpor
|
|
|
441
461
|
InvoiceUserSkeleton,
|
|
442
462
|
InvoiceSummarySkeleton,
|
|
443
463
|
InvoiceLinesSkeleton,
|
|
444
|
-
], template: "<rolatech-container>\n <rolatech-toolbar [title]=\"invoice() ? status[invoice()!.status] : ''\" large link=\"../\"> </rolatech-toolbar>\n @if (loading()) {\n <div class=\"grid grid-cols-1 lg:grid-cols-3 gap-4\">\n <div class=\"grid lg:col-span-2 space-y-4\">\n <rolatech-invoice-user-skeleton></rolatech-invoice-user-skeleton>\n <rolatech-invoice-lines-skeleton></rolatech-invoice-lines-skeleton>\n </div>\n <div class=\"space-y-4\">\n <rolatech-invoice-summary-skeleton></rolatech-invoice-summary-skeleton>\n </div>\n </div>\n } @else {\n @if (invoice(); as invoice) {\n <div class=\"grid grid-cols-1 lg:grid-cols-3 gap-4\">\n <div class=\"grid lg:col-span-2 space-y-4\">\n <rolatech-invoice-user\n [firstName]=\"invoice.firstName\"\n [lastName]=\"invoice.lastName\"\n [email]=\"invoice.email\"\n [phone]=\"invoice.phone\"\n ></rolatech-invoice-user>\n @if (invoice?.lines; as lines) {\n <rolatech-invoice-lines [lines]=\"lines\"></rolatech-invoice-lines>\n }\n </div>\n <div class=\"space-y-4\">\n <rolatech-invoice-summary\n [tax]=\"invoice.vatTotal\"\n [credit]=\"invoice.holdingDepositApplied\"\n [subtotal]=\"invoice.subtotal\"\n [total]=\"invoice.total\"\n ></rolatech-invoice-summary>\n <div class=\"px-2\">\n <div class=\"flex items-baseline justify-between py-1\">\n <span class=\"font-medium min-w-20\" i18n>Note</span>\n <span class=\"text-sm\">{{ invoice.note || 'Null' }}</span>\n </div>\n @if (invoice.status.toString() === 'CREATED' || invoice.status.toString() === 'PAID') {\n <div class=\"flex items-center justify-between py-1\">\n <span class=\"font-medium\" i18n>Payment method</span>\n <span class=\"text-sm\"> Stripe</span>\n </div>\n }\n @if (invoice.status.toString() === '
|
|
464
|
+
], template: "<rolatech-container>\n <rolatech-toolbar [title]=\"invoice() ? status[invoice()!.status] : ''\" large link=\"../\"> </rolatech-toolbar>\n @if (loading()) {\n <div class=\"grid grid-cols-1 lg:grid-cols-3 gap-4\">\n <div class=\"grid lg:col-span-2 space-y-4\">\n <rolatech-invoice-user-skeleton></rolatech-invoice-user-skeleton>\n <rolatech-invoice-lines-skeleton></rolatech-invoice-lines-skeleton>\n </div>\n <div class=\"space-y-4\">\n <rolatech-invoice-summary-skeleton></rolatech-invoice-summary-skeleton>\n </div>\n </div>\n } @else {\n @if (invoice(); as invoice) {\n <div class=\"grid grid-cols-1 lg:grid-cols-3 gap-4\">\n <div class=\"grid lg:col-span-2 space-y-4\">\n <rolatech-invoice-user\n [firstName]=\"invoice.firstName\"\n [lastName]=\"invoice.lastName\"\n [email]=\"invoice.email\"\n [phone]=\"invoice.phone\"\n ></rolatech-invoice-user>\n @if (invoice?.lines; as lines) {\n <rolatech-invoice-lines [lines]=\"lines\"></rolatech-invoice-lines>\n }\n </div>\n <div class=\"space-y-4\">\n <rolatech-invoice-summary\n [tax]=\"invoice.vatTotal\"\n [credit]=\"invoice.holdingDepositApplied\"\n [subtotal]=\"invoice.subtotal\"\n [total]=\"invoice.total\"\n ></rolatech-invoice-summary>\n <div class=\"px-2\">\n <div class=\"flex items-baseline justify-between py-1\">\n <span class=\"font-medium min-w-20\" i18n>Note</span>\n <span class=\"text-sm\">{{ invoice.note || 'Null' }}</span>\n </div>\n @if (invoice.status.toString() === 'CREATED' || invoice.status.toString() === 'PAID') {\n <div class=\"flex items-center justify-between py-1\">\n <span class=\"font-medium\" i18n>Payment method</span>\n <span class=\"text-sm\"> Stripe</span>\n </div>\n }\n @if (invoice.status.toString() === 'ISSUED' || invoice.status.toString() === 'SENT') {\n <div class=\"py-3\">\n <div class=\"flex items-center justify-end\">\n <button mat-flat-button [disabled]=\"paying\" class=\"w-full min-h-11\" (click)=\"pay()\" i18n>\n {{ paying ? 'Processing' : 'Pay' }}\n </button>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n <div>\n <!-- safe area -->\n <div class=\"pb-16 sm:pb-3\"></div>\n </div>\n }\n }\n</rolatech-container>\n" }]
|
|
445
465
|
}] });
|
|
446
466
|
|
|
447
467
|
const invoiceRoutes = [
|
|
@@ -459,6 +479,8 @@ class InvoiceManageIndex extends BaseComponent {
|
|
|
459
479
|
constructor() {
|
|
460
480
|
super(...arguments);
|
|
461
481
|
this.invoiceService = inject(InvoiceService);
|
|
482
|
+
this.enumApi = inject(EnumApiClient);
|
|
483
|
+
this.invoiceTypeMap = signal({}, ...(ngDevMode ? [{ debugName: "invoiceTypeMap" }] : []));
|
|
462
484
|
this.select = 0;
|
|
463
485
|
this.loading = false;
|
|
464
486
|
this.invoices = signal([], ...(ngDevMode ? [{ debugName: "invoices" }] : []));
|
|
@@ -489,6 +511,7 @@ class InvoiceManageIndex extends BaseComponent {
|
|
|
489
511
|
];
|
|
490
512
|
}
|
|
491
513
|
ngOnInit() {
|
|
514
|
+
this.getOptions('billing', 'InvoiceType');
|
|
492
515
|
const sub = this.route.queryParamMap
|
|
493
516
|
.pipe(map((p) => {
|
|
494
517
|
const page = p.get('page') ? Number(p.get('page')) : 1;
|
|
@@ -509,6 +532,7 @@ class InvoiceManageIndex extends BaseComponent {
|
|
|
509
532
|
// Cheap deep compare via JSON to avoid spam calls when nothing changed
|
|
510
533
|
map((o) => JSON.stringify(o)), distinctUntilChanged(), map((s) => JSON.parse(s)), switchMap((params) => {
|
|
511
534
|
this.loading = true;
|
|
535
|
+
params['sort'] = 'updatedAt desc';
|
|
512
536
|
return this.invoiceService.findByManager(params).pipe(finalize(() => (this.loading = false)));
|
|
513
537
|
}))
|
|
514
538
|
.subscribe({
|
|
@@ -525,6 +549,18 @@ class InvoiceManageIndex extends BaseComponent {
|
|
|
525
549
|
// // auto-unsubscribe on destroy
|
|
526
550
|
this.destroyRef.onDestroy(() => sub.unsubscribe());
|
|
527
551
|
}
|
|
552
|
+
getOptions(resource, enumName) {
|
|
553
|
+
this.enumApi.getOptions(resource, enumName).subscribe({
|
|
554
|
+
next: (res) => {
|
|
555
|
+
const map = res.reduce((acc, cur) => {
|
|
556
|
+
acc[cur.value] = cur.label;
|
|
557
|
+
return acc;
|
|
558
|
+
}, {});
|
|
559
|
+
this.invoiceTypeMap.set(map);
|
|
560
|
+
},
|
|
561
|
+
error: () => { },
|
|
562
|
+
});
|
|
563
|
+
}
|
|
528
564
|
onPage(e) {
|
|
529
565
|
this.router.navigate([], {
|
|
530
566
|
queryParams: { page: e.pageIndex + 1, limit: e.pageSize },
|
|
@@ -546,12 +582,12 @@ class InvoiceManageIndex extends BaseComponent {
|
|
|
546
582
|
return 'bg-gray-100 text-gray-700';
|
|
547
583
|
}
|
|
548
584
|
}
|
|
549
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.
|
|
550
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.
|
|
585
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceManageIndex, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
586
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: InvoiceManageIndex, isStandalone: true, selector: "rolatech-invoice-manage-index", usesInheritance: true, ngImport: i0, template: "<rolatech-toolbar title=\"Invoices\">\n <div class=\"flex items-center gap-2\"></div>\n</rolatech-toolbar>\n<rolatech-tabs [select]=\"select\">\n @for (item of links; track item) { @if (item.status) {\n <rolatech-tab [label]=\"item.name\" routerLink=\"./\" [queryParams]=\"{ status: item.status }\"></rolatech-tab>\n } @else {\n <rolatech-tab [label]=\"item.name\" routerLink=\"./\"></rolatech-tab>\n } }\n</rolatech-tabs>\n<div class=\"p-3 overflow-x-auto\">\n <table class=\"min-w-full\">\n <thead class=\"bg-[--rt-raised-background]\">\n <tr>\n <th class=\"p-3 border-b text-left\">#ID</th>\n <th class=\"p-3 border-b text-left\">Status</th>\n <th class=\"p-3 border-b text-left\">Email</th>\n <th class=\"p-3 border-b text-left\">Profile</th>\n <th class=\"p-3 border-b text-left\">Type</th>\n <th class=\"p-3 border-b text-left\">Total</th>\n </tr>\n </thead>\n <tbody>\n @for (item of invoices(); track $index) {\n <!-- <rolatech-invoice-manage-item [invoice]=\"item\"></rolatech-invoice-manage-item> -->\n <tr class=\"hover:bg-[--rt-raised-background] hover:cursor-pointer\" [routerLink]=\"['./', item.id]\">\n <td class=\"p-3 border-b\">{{item.id}}</td>\n <td class=\"p-3 border-b\">\n <span\n class=\"inline-flex items-center rounded-full px-3 py-1 text-xs font-medium\"\n [ngClass]=\"statusBadgeClass(item.status)\"\n >{{item.status}}</span\n >\n </td>\n <td class=\"p-3 border-b\">{{item.email}}</td>\n <td class=\"p-3 border-b\">{{item.firstName}},{{item.lastName}}</td>\n <td class=\"p-3 border-b\">{{invoiceTypeMap()[item.type] || item.type}}</td>\n <td class=\"p-3 border-b\">{{item.total | price}}</td>\n </tr>\n }\n </tbody>\n </table>\n</div>\n<!-- <div>\n <table class=\"min-w-full text-sm\">\n <thead class=\"border-b bg-[--rt-raised-background]\">\n <tr>\n <th class=\"py-2 text-left font-medium text-gray-600\">#ID</th>\n <th class=\"py-2 text-right font-medium text-gray-600\">Status</th>\n <th class=\"py-2 text-right font-medium text-gray-600\">Email</th>\n <th class=\"py-2 text-right font-medium text-gray-600\">Profile</th>\n <th class=\"py-2 text-right font-medium text-gray-600\">Total</th>\n </tr>\n </thead>\n\n <tbody>\n @for (item of invoices(); track $index) {\n <tr class=\"border-b hover:bg-[--rt-raised-background] hover:cursor-pointer\">\n <td class=\"py-2\">{{item.id}}</td>\n <td class=\"py-2 text-right\">{{item.status}}</td>\n <td class=\"py-2 text-right\">{{item.email}}</td>\n <td class=\"py-2 text-right\">{{item.firstName}}, {{item.lastName}}</td>\n <td class=\"py-2 text-right font-semibold\">{{item.total | price}}</td>\n </tr>\n }\n </tbody>\n </table>\n</div> -->\n<mat-paginator\n #paginator\n [length]=\"length\"\n [pageSize]=\"pageSize\"\n [pageIndex]=\"pageIndex()\"\n [pageSizeOptions]=\"pageSizeOptions\"\n (page)=\"onPage($event)\"\n hidePageSize\n showFirstLastButtons\n>\n</mat-paginator>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: ToolbarComponent, selector: "rolatech-toolbar", inputs: ["title", "subtitle", "back", "link", "large", "divider"] }, { kind: "component", type: TabsComponent, selector: "rolatech-tabs", inputs: ["select", "loading", "block"], outputs: ["selectChange"] }, { kind: "component", type: TabComponent, selector: "rolatech-tab", inputs: ["label"] }, { kind: "ngmodule", type: MatPaginatorModule }, { kind: "component", type: i8.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "pipe", type: PricePipe, name: "price" }] }); }
|
|
551
587
|
}
|
|
552
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
588
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceManageIndex, decorators: [{
|
|
553
589
|
type: Component,
|
|
554
|
-
args: [{ selector: 'rolatech-invoice-manage-index', imports: [CommonModule, RouterLink, ToolbarComponent, TabsComponent, TabComponent, MatPaginatorModule, PricePipe], template: "<rolatech-toolbar title=\"Invoices\">\n <div class=\"flex items-center gap-2\"></div>\n</rolatech-toolbar>\n<rolatech-tabs [select]=\"select\">\n @for (item of links; track item) { @if (item.status) {\n <rolatech-tab [label]=\"item.name\" routerLink=\"./\" [queryParams]=\"{ status: item.status }\"></rolatech-tab>\n } @else {\n <rolatech-tab [label]=\"item.name\" routerLink=\"./\"></rolatech-tab>\n } }\n</rolatech-tabs>\n<div class=\"p-3 overflow-x-auto\">\n <table class=\"min-w-full\">\n <thead class=\"bg-[--rt-raised-background]\">\n <tr>\n <th class=\"p-3 border-b text-left\">#ID</th>\n <th class=\"p-3 border-b text-left\">Status</th>\n <th class=\"p-3 border-b text-left\">Email</th>\n <th class=\"p-3 border-b text-left\">Profile</th>\n <th class=\"p-3 border-b text-left\">Total</th>\n </tr>\n </thead>\n <tbody>\n @for (item of invoices(); track $index) {\n <!-- <rolatech-invoice-manage-item [invoice]=\"item\"></rolatech-invoice-manage-item> -->\n <tr class=\"hover:bg-[--rt-raised-background] hover:cursor-pointer\" [routerLink]=\"['./', item.id]\">\n <td class=\"p-3 border-b\">{{item.id}}</td>\n <td class=\"p-3 border-b\">\n <span\n class=\"inline-flex items-center rounded-full px-3 py-1 text-xs font-medium\"\n [ngClass]=\"statusBadgeClass(item.status)\"\n >{{item.status}}</span\n >\n </td>\n <td class=\"p-3 border-b\">{{item.email}}</td>\n <td class=\"p-3 border-b\">{{item.firstName}},{{item.lastName}}</td>\n <td class=\"p-3 border-b\">{{item.total | price}}</td>\n </tr>\n }\n </tbody>\n </table>\n</div>\n<!-- <div>\n <table class=\"min-w-full text-sm\">\n <thead class=\"border-b bg-[--rt-raised-background]\">\n <tr>\n <th class=\"py-2 text-left font-medium text-gray-600\">#ID</th>\n <th class=\"py-2 text-right font-medium text-gray-600\">Status</th>\n <th class=\"py-2 text-right font-medium text-gray-600\">Email</th>\n <th class=\"py-2 text-right font-medium text-gray-600\">Profile</th>\n <th class=\"py-2 text-right font-medium text-gray-600\">Total</th>\n </tr>\n </thead>\n\n <tbody>\n @for (item of invoices(); track $index) {\n <tr class=\"border-b hover:bg-[--rt-raised-background] hover:cursor-pointer\">\n <td class=\"py-2\">{{item.id}}</td>\n <td class=\"py-2 text-right\">{{item.status}}</td>\n <td class=\"py-2 text-right\">{{item.email}}</td>\n <td class=\"py-2 text-right\">{{item.firstName}}, {{item.lastName}}</td>\n <td class=\"py-2 text-right font-semibold\">{{item.total | price}}</td>\n </tr>\n }\n </tbody>\n </table>\n</div> -->\n<mat-paginator\n #paginator\n [length]=\"length\"\n [pageSize]=\"pageSize\"\n [pageIndex]=\"pageIndex()\"\n [pageSizeOptions]=\"pageSizeOptions\"\n (page)=\"onPage($event)\"\n hidePageSize\n showFirstLastButtons\n>\n</mat-paginator>\n" }]
|
|
590
|
+
args: [{ selector: 'rolatech-invoice-manage-index', imports: [CommonModule, RouterLink, ToolbarComponent, TabsComponent, TabComponent, MatPaginatorModule, PricePipe], template: "<rolatech-toolbar title=\"Invoices\">\n <div class=\"flex items-center gap-2\"></div>\n</rolatech-toolbar>\n<rolatech-tabs [select]=\"select\">\n @for (item of links; track item) { @if (item.status) {\n <rolatech-tab [label]=\"item.name\" routerLink=\"./\" [queryParams]=\"{ status: item.status }\"></rolatech-tab>\n } @else {\n <rolatech-tab [label]=\"item.name\" routerLink=\"./\"></rolatech-tab>\n } }\n</rolatech-tabs>\n<div class=\"p-3 overflow-x-auto\">\n <table class=\"min-w-full\">\n <thead class=\"bg-[--rt-raised-background]\">\n <tr>\n <th class=\"p-3 border-b text-left\">#ID</th>\n <th class=\"p-3 border-b text-left\">Status</th>\n <th class=\"p-3 border-b text-left\">Email</th>\n <th class=\"p-3 border-b text-left\">Profile</th>\n <th class=\"p-3 border-b text-left\">Type</th>\n <th class=\"p-3 border-b text-left\">Total</th>\n </tr>\n </thead>\n <tbody>\n @for (item of invoices(); track $index) {\n <!-- <rolatech-invoice-manage-item [invoice]=\"item\"></rolatech-invoice-manage-item> -->\n <tr class=\"hover:bg-[--rt-raised-background] hover:cursor-pointer\" [routerLink]=\"['./', item.id]\">\n <td class=\"p-3 border-b\">{{item.id}}</td>\n <td class=\"p-3 border-b\">\n <span\n class=\"inline-flex items-center rounded-full px-3 py-1 text-xs font-medium\"\n [ngClass]=\"statusBadgeClass(item.status)\"\n >{{item.status}}</span\n >\n </td>\n <td class=\"p-3 border-b\">{{item.email}}</td>\n <td class=\"p-3 border-b\">{{item.firstName}},{{item.lastName}}</td>\n <td class=\"p-3 border-b\">{{invoiceTypeMap()[item.type] || item.type}}</td>\n <td class=\"p-3 border-b\">{{item.total | price}}</td>\n </tr>\n }\n </tbody>\n </table>\n</div>\n<!-- <div>\n <table class=\"min-w-full text-sm\">\n <thead class=\"border-b bg-[--rt-raised-background]\">\n <tr>\n <th class=\"py-2 text-left font-medium text-gray-600\">#ID</th>\n <th class=\"py-2 text-right font-medium text-gray-600\">Status</th>\n <th class=\"py-2 text-right font-medium text-gray-600\">Email</th>\n <th class=\"py-2 text-right font-medium text-gray-600\">Profile</th>\n <th class=\"py-2 text-right font-medium text-gray-600\">Total</th>\n </tr>\n </thead>\n\n <tbody>\n @for (item of invoices(); track $index) {\n <tr class=\"border-b hover:bg-[--rt-raised-background] hover:cursor-pointer\">\n <td class=\"py-2\">{{item.id}}</td>\n <td class=\"py-2 text-right\">{{item.status}}</td>\n <td class=\"py-2 text-right\">{{item.email}}</td>\n <td class=\"py-2 text-right\">{{item.firstName}}, {{item.lastName}}</td>\n <td class=\"py-2 text-right font-semibold\">{{item.total | price}}</td>\n </tr>\n }\n </tbody>\n </table>\n</div> -->\n<mat-paginator\n #paginator\n [length]=\"length\"\n [pageSize]=\"pageSize\"\n [pageIndex]=\"pageIndex()\"\n [pageSizeOptions]=\"pageSizeOptions\"\n (page)=\"onPage($event)\"\n hidePageSize\n showFirstLastButtons\n>\n</mat-paginator>\n" }]
|
|
555
591
|
}] });
|
|
556
592
|
|
|
557
593
|
class InvoiceManageCreate {
|
|
@@ -566,12 +602,12 @@ class InvoiceManageCreate {
|
|
|
566
602
|
issueDate: this.todayIso(),
|
|
567
603
|
dueDate: '',
|
|
568
604
|
notes: '',
|
|
569
|
-
lines: [{ description: '', quantity: 1, unitPrice: 0, taxRate: 0 }],
|
|
605
|
+
lines: [{ type: '', description: '', quantity: 1, unitPrice: 0, taxRate: 0 }],
|
|
570
606
|
};
|
|
571
607
|
this.lastResponse = null;
|
|
572
608
|
}
|
|
573
609
|
addLine() {
|
|
574
|
-
this.invoice.lines.push({ description: '', quantity: 1, unitPrice: 0, taxRate: 0 });
|
|
610
|
+
this.invoice.lines.push({ type: '', description: '', quantity: 1, unitPrice: 0, taxRate: 0 });
|
|
575
611
|
}
|
|
576
612
|
removeLine(i) {
|
|
577
613
|
this.invoice.lines.splice(i, 1);
|
|
@@ -634,7 +670,7 @@ class InvoiceManageCreate {
|
|
|
634
670
|
issueDate: this.todayIso(),
|
|
635
671
|
dueDate: '',
|
|
636
672
|
notes: '',
|
|
637
|
-
lines: [{ description: '', quantity: 1, unitPrice: 0, taxRate: 0 }],
|
|
673
|
+
lines: [{ type: '', description: '', quantity: 1, unitPrice: 0, taxRate: 0 }],
|
|
638
674
|
};
|
|
639
675
|
this.lastResponse = null;
|
|
640
676
|
}
|
|
@@ -644,10 +680,10 @@ class InvoiceManageCreate {
|
|
|
644
680
|
todayIso() {
|
|
645
681
|
return new Date().toISOString().slice(0, 10);
|
|
646
682
|
}
|
|
647
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.
|
|
648
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.0", type: InvoiceManageCreate, isStandalone: true, selector: "rolatech-invoice-manage-create", ngImport: i0, template: "<rolatech-toolbar title=\"Create Invoice\">\n <button mat-stroked-button (click)=\"addLine()\">\n <mat-icon>add</mat-icon>\n Add line\n </button>\n <button\n mat-flat-button\n color=\"primary\"\n (click)=\"save(invoiceForm)\"\n [disabled]=\"!invoiceForm.form.valid || invoice.lines.length === 0\"\n >\n Save\n </button>\n</rolatech-toolbar>\n<div class=\"p-4 md:p-8 max-w-6xl mx-auto space-y-6\">\n <form #invoiceForm=\"ngForm\" novalidate>\n <mat-card appearance=\"outlined\" class=\"p-4\">\n <div class=\"grid md:grid-cols-2 gap-4\">\n <mat-form-field appearance=\"fill\">\n <mat-label>Customer name</mat-label>\n <input matInput name=\"customerName\" [(ngModel)]=\"invoice.customerName\" required />\n @if (invoiceForm.submitted && !invoice.customerName) {<mat-error>Name is required</mat-error>}\n </mat-form-field>\n\n <mat-form-field appearance=\"fill\">\n <mat-label>Customer email</mat-label>\n <input matInput type=\"email\" name=\"customerEmail\" [(ngModel)]=\"invoice.customerEmail\" #email=\"ngModel\" email />\n @if (email.invalid && (email.dirty || email.touched)) {<mat-error>Invalid email</mat-error>}\n </mat-form-field>\n\n <mat-form-field appearance=\"fill\" class=\"md:col-span-2\">\n <mat-label>Customer address</mat-label>\n <textarea matInput rows=\"2\" name=\"customerAddress\" [(ngModel)]=\"invoice.customerAddress\"></textarea>\n </mat-form-field>\n\n <mat-form-field appearance=\"fill\">\n <mat-label>Currency</mat-label>\n <mat-select name=\"currency\" [(ngModel)]=\"invoice.currency\" required>\n <mat-option value=\"GBP\">GBP</mat-option>\n <mat-option value=\"USD\">USD</mat-option>\n <mat-option value=\"EUR\">EUR</mat-option>\n </mat-select>\n </mat-form-field>\n\n <mat-form-field appearance=\"fill\">\n <mat-label>Issue date</mat-label>\n <input matInput type=\"date\" name=\"issueDate\" [(ngModel)]=\"invoice.issueDate\" required />\n </mat-form-field>\n\n <mat-form-field appearance=\"fill\">\n <mat-label>Due date</mat-label>\n <input matInput type=\"date\" name=\"dueDate\" [(ngModel)]=\"invoice.dueDate\" />\n </mat-form-field>\n\n <mat-form-field appearance=\"fill\" class=\"md:col-span-2\">\n <mat-label>Notes</mat-label>\n <textarea\n matInput\n rows=\"2\"\n name=\"notes\"\n [(ngModel)]=\"invoice.notes\"\n placeholder=\"Optional notes shown on the invoice\"\n ></textarea>\n </mat-form-field>\n </div>\n </mat-card>\n\n <mat-card appearance=\"outlined\" class=\"p-4 space-y-4 mt-4\">\n <div class=\"flex items-center justify-between\">\n <h2 class=\"text-xl font-medium\">Line items</h2>\n <button mat-stroked-button type=\"button\" (click)=\"addLine()\">\n <mat-icon>playlist_add</mat-icon>\n Add line\n </button>\n </div>\n\n <div class=\"hidden md:grid grid-cols-12 gap-2 text-sm font-medium px-2\">\n <div class=\"col-span-5\">Description</div>\n <div class=\"col-span-2 text-right\">Qty</div>\n <div class=\"col-span-2 text-right\">Unit Price</div>\n <div class=\"col-span-2 text-right\">Tax %</div>\n <div class=\"col-span-1\"></div>\n </div>\n\n <div class=\"space-y-3\">\n @for ( line of invoice.lines; track line; let i = $index) {\n <div class=\"grid grid-cols-1 md:grid-cols-12 gap-2 items-start\">\n <mat-form-field class=\"md:col-span-5\" appearance=\"fill\">\n <mat-label>Description</mat-label>\n <input\n matInput\n [name]=\"'desc'+i\"\n [(ngModel)]=\"line.description\"\n required\n placeholder=\"e.g. Photo editing package\"\n />\n </mat-form-field>\n\n <mat-form-field class=\"md:col-span-2\" appearance=\"fill\">\n <mat-label>Quantity</mat-label>\n <input matInput type=\"number\" min=\"1\" step=\"1\" [name]=\"'qty'+i\" [(ngModel)]=\"line.quantity\" required />\n </mat-form-field>\n\n <mat-form-field class=\"md:col-span-2\" appearance=\"fill\">\n <mat-label>Unit Price (major unit)</mat-label>\n <input matInput type=\"number\" min=\"0\" step=\"0.01\" [name]=\"'unit'+i\" [(ngModel)]=\"line.unitPrice\" required />\n <mat-hint>e.g. 99.99</mat-hint>\n </mat-form-field>\n\n <mat-form-field class=\"md:col-span-2\" appearance=\"fill\">\n <mat-label>Tax %</mat-label>\n <input matInput type=\"number\" min=\"0\" step=\"0.01\" [name]=\"'tax'+i\" [(ngModel)]=\"line.taxRate\" />\n </mat-form-field>\n\n <div class=\"md:col-span-1 flex items-center justify-end\">\n <button mat-icon-button color=\"warn\" type=\"button\" (click)=\"removeLine(i)\" aria-label=\"Remove line\">\n <mat-icon>delete</mat-icon>\n </button>\n </div>\n </div>\n <mat-divider></mat-divider>\n }\n </div>\n\n <div class=\"flex flex-col items-end space-y-1\">\n <div class=\"text-sm\">\n Subtotal: <span class=\"font-medium\">{{ subtotalMajor() | number:'1.2-2' }} {{ invoice.currency }}</span>\n </div>\n <div class=\"text-sm\">\n Tax: <span class=\"font-medium\">{{ taxMajor() | number:'1.2-2' }} {{ invoice.currency }}</span>\n </div>\n <div class=\"text-lg font-semibold\">Total: {{ totalMajor() | number:'1.2-2' }} {{ invoice.currency }}</div>\n </div>\n </mat-card>\n </form>\n\n @if (lastResponse) {\n <mat-card appearance=\"outlined\" class=\"p-4 flex items-center justify-between\">\n <div>\n <div class=\"font-medium\">Created invoice #{{ lastResponse.data.id }}</div>\n <div class=\"text-sm opacity-70\">\n Total: {{ penceToMajor(lastResponse.data.total) | number:'1.2-2' }} {{ invoice.currency }}\n </div>\n </div>\n <div class=\"space-x-2\">\n <button mat-stroked-button (click)=\"resetForm()\">Create another</button>\n <button mat-flat-button color=\"primary\">Download PDF</button>\n </div>\n </mat-card>\n }\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$2.EmailValidator, selector: "[email][formControlName],[email][formControl],[email][ngModel]", inputs: ["email"] }, { kind: "directive", type: i1$2.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1$2.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i4.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i7.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatDividerModule }, { kind: "component", type: i8$1.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i9.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "component", type: ToolbarComponent, selector: "rolatech-toolbar", inputs: ["title", "subtitle", "back", "link", "large", "divider"] }, { kind: "pipe", type: i1.DecimalPipe, name: "number" }] }); }
|
|
683
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceManageCreate, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
684
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: InvoiceManageCreate, isStandalone: true, selector: "rolatech-invoice-manage-create", ngImport: i0, template: "<rolatech-toolbar title=\"Create Invoice\">\n <button\n mat-flat-button\n color=\"primary\"\n (click)=\"save(invoiceForm)\"\n [disabled]=\"!invoiceForm.form.valid || invoice.lines.length === 0\"\n >\n Save\n </button>\n</rolatech-toolbar>\n<div class=\"p-4 space-y-6\">\n <form #invoiceForm=\"ngForm\" novalidate>\n <mat-card appearance=\"outlined\" class=\"p-4\">\n <div class=\"grid lg:grid-cols-2 gap-4\">\n <mat-form-field appearance=\"fill\">\n <mat-label>Customer name</mat-label>\n <input matInput name=\"customerName\" [(ngModel)]=\"invoice.customerName\" required />\n @if (invoiceForm.submitted && !invoice.customerName) {<mat-error>Name is required</mat-error>}\n </mat-form-field>\n\n <mat-form-field appearance=\"fill\">\n <mat-label>Customer email</mat-label>\n <input matInput type=\"email\" name=\"customerEmail\" [(ngModel)]=\"invoice.customerEmail\" #email=\"ngModel\" email />\n @if (email.invalid && (email.dirty || email.touched)) {<mat-error>Invalid email</mat-error>}\n </mat-form-field>\n\n <mat-form-field appearance=\"fill\" class=\"lg:col-span-2\">\n <mat-label>Customer address</mat-label>\n <textarea matInput rows=\"2\" name=\"customerAddress\" [(ngModel)]=\"invoice.customerAddress\"></textarea>\n </mat-form-field>\n\n <mat-form-field appearance=\"fill\">\n <mat-label>Currency</mat-label>\n <mat-select name=\"currency\" [(ngModel)]=\"invoice.currency\" required>\n <mat-option value=\"GBP\">GBP</mat-option>\n <mat-option value=\"USD\">USD</mat-option>\n <mat-option value=\"EUR\">EUR</mat-option>\n </mat-select>\n </mat-form-field>\n\n <mat-form-field appearance=\"fill\">\n <mat-label>Issue date</mat-label>\n <input matInput type=\"date\" name=\"issueDate\" [(ngModel)]=\"invoice.issueDate\" required />\n </mat-form-field>\n\n <mat-form-field appearance=\"fill\">\n <mat-label>Due date</mat-label>\n <input matInput type=\"date\" name=\"dueDate\" [(ngModel)]=\"invoice.dueDate\" />\n </mat-form-field>\n\n <mat-form-field appearance=\"fill\" class=\"lg:col-span-2\">\n <mat-label>Notes</mat-label>\n <textarea\n matInput\n rows=\"2\"\n name=\"notes\"\n [(ngModel)]=\"invoice.notes\"\n placeholder=\"Optional notes shown on the invoice\"\n ></textarea>\n </mat-form-field>\n </div>\n </mat-card>\n\n <mat-card appearance=\"outlined\" class=\"p-4 space-y-4 mt-4\">\n <div class=\"flex items-center justify-between\">\n <h2 class=\"text-xl font-medium\">Line items</h2>\n <button mat-stroked-button type=\"button\" (click)=\"addLine()\">\n <mat-icon>add</mat-icon>\n Add line\n </button>\n </div>\n\n <div class=\"hidden lg:grid grid-cols-12 gap-2 text-sm font-medium px-2\">\n <div class=\"col-span-3\">Type</div>\n <div class=\"col-span-3\">Description</div>\n <div class=\"col-span-1 text-right\">Qty</div>\n <div class=\"col-span-2 text-right\">Unit Price</div>\n <div class=\"col-span-2 text-right\">Tax %</div>\n <div class=\"col-span-1\"></div>\n </div>\n\n <div class=\"space-y-3\">\n @for ( line of invoice.lines; track line; let i = $index) {\n <div class=\"grid grid-cols-1 lg:grid-cols-12 gap-2 items-start\">\n <rolatech-enum-select\n class=\"lg:col-span-3\"\n [resource]=\"'billing'\"\n [enumName]=\"'InvoiceLineType'\"\n [label]=\"'Line type'\"\n [placeholder]=\"'Select\u2026'\"\n [(value)]=\"line.type\"\n ></rolatech-enum-select>\n <mat-form-field class=\"lg:col-span-3\" appearance=\"fill\">\n <mat-label>Description</mat-label>\n <input\n matInput\n [name]=\"'desc'+i\"\n [(ngModel)]=\"line.description\"\n required\n placeholder=\"e.g. Photo editing package\"\n />\n </mat-form-field>\n\n <mat-form-field class=\"lg:col-span-1\" appearance=\"fill\">\n <mat-label>Quantity</mat-label>\n <input matInput type=\"number\" min=\"1\" step=\"1\" [name]=\"'qty'+i\" [(ngModel)]=\"line.quantity\" required />\n </mat-form-field>\n\n <mat-form-field class=\"lg:col-span-2\" appearance=\"fill\">\n <mat-label>Unit Price</mat-label>\n <input matInput type=\"number\" min=\"0\" step=\"0.01\" [name]=\"'unit'+i\" [(ngModel)]=\"line.unitPrice\" required />\n <mat-hint>e.g. 99.99</mat-hint>\n </mat-form-field>\n\n <mat-form-field class=\"lg:col-span-2\" appearance=\"fill\">\n <mat-label>Tax %</mat-label>\n <input matInput type=\"number\" min=\"0\" step=\"0.01\" [name]=\"'tax'+i\" [(ngModel)]=\"line.taxRate\" />\n </mat-form-field>\n\n <div class=\"lg:col-span-1 flex items-center justify-end\">\n <button mat-icon-button color=\"warn\" type=\"button\" (click)=\"removeLine(i)\" aria-label=\"Remove line\">\n <mat-icon>delete</mat-icon>\n </button>\n </div>\n </div>\n <mat-divider></mat-divider>\n }\n </div>\n\n <div class=\"flex flex-col items-end space-y-1\">\n <div class=\"text-sm\">\n Subtotal: <span class=\"font-medium\">{{ subtotalMajor() | number:'1.2-2' }} {{ invoice.currency }}</span>\n </div>\n <div class=\"text-sm\">\n Tax: <span class=\"font-medium\">{{ taxMajor() | number:'1.2-2' }} {{ invoice.currency }}</span>\n </div>\n <div class=\"text-lg font-semibold\">Total: {{ totalMajor() | number:'1.2-2' }} {{ invoice.currency }}</div>\n </div>\n </mat-card>\n </form>\n\n @if (lastResponse) {\n <mat-card appearance=\"outlined\" class=\"p-4 flex items-center justify-between\">\n <div>\n <div class=\"font-medium\">Created invoice #{{ lastResponse.data.id }}</div>\n <div class=\"text-sm opacity-70\">\n Total: {{ penceToMajor(lastResponse.data.total) | number:'1.2-2' }} {{ invoice.currency }}\n </div>\n </div>\n <div class=\"space-x-2\">\n <button mat-stroked-button (click)=\"resetForm()\">Create another</button>\n <button mat-flat-button color=\"primary\">Download PDF</button>\n </div>\n </mat-card>\n }\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$2.EmailValidator, selector: "[email][formControlName],[email][formControl],[email][ngModel]", inputs: ["email"] }, { kind: "directive", type: i1$2.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1$2.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i4.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i7.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatDividerModule }, { kind: "component", type: i8$1.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i9.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "component", type: ToolbarComponent, selector: "rolatech-toolbar", inputs: ["title", "subtitle", "back", "link", "large", "divider"] }, { kind: "component", type: EnumSelect, selector: "rolatech-enum-select", inputs: ["resource", "enumName", "label", "placeholder", "disabled", "value"], outputs: ["valueChange"] }, { kind: "pipe", type: i1.DecimalPipe, name: "number" }] }); }
|
|
649
685
|
}
|
|
650
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
686
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceManageCreate, decorators: [{
|
|
651
687
|
type: Component,
|
|
652
688
|
args: [{ selector: 'rolatech-invoice-manage-create', imports: [
|
|
653
689
|
CommonModule,
|
|
@@ -660,7 +696,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImpor
|
|
|
660
696
|
MatDividerModule,
|
|
661
697
|
MatCardModule,
|
|
662
698
|
ToolbarComponent,
|
|
663
|
-
|
|
699
|
+
EnumSelect,
|
|
700
|
+
], template: "<rolatech-toolbar title=\"Create Invoice\">\n <button\n mat-flat-button\n color=\"primary\"\n (click)=\"save(invoiceForm)\"\n [disabled]=\"!invoiceForm.form.valid || invoice.lines.length === 0\"\n >\n Save\n </button>\n</rolatech-toolbar>\n<div class=\"p-4 space-y-6\">\n <form #invoiceForm=\"ngForm\" novalidate>\n <mat-card appearance=\"outlined\" class=\"p-4\">\n <div class=\"grid lg:grid-cols-2 gap-4\">\n <mat-form-field appearance=\"fill\">\n <mat-label>Customer name</mat-label>\n <input matInput name=\"customerName\" [(ngModel)]=\"invoice.customerName\" required />\n @if (invoiceForm.submitted && !invoice.customerName) {<mat-error>Name is required</mat-error>}\n </mat-form-field>\n\n <mat-form-field appearance=\"fill\">\n <mat-label>Customer email</mat-label>\n <input matInput type=\"email\" name=\"customerEmail\" [(ngModel)]=\"invoice.customerEmail\" #email=\"ngModel\" email />\n @if (email.invalid && (email.dirty || email.touched)) {<mat-error>Invalid email</mat-error>}\n </mat-form-field>\n\n <mat-form-field appearance=\"fill\" class=\"lg:col-span-2\">\n <mat-label>Customer address</mat-label>\n <textarea matInput rows=\"2\" name=\"customerAddress\" [(ngModel)]=\"invoice.customerAddress\"></textarea>\n </mat-form-field>\n\n <mat-form-field appearance=\"fill\">\n <mat-label>Currency</mat-label>\n <mat-select name=\"currency\" [(ngModel)]=\"invoice.currency\" required>\n <mat-option value=\"GBP\">GBP</mat-option>\n <mat-option value=\"USD\">USD</mat-option>\n <mat-option value=\"EUR\">EUR</mat-option>\n </mat-select>\n </mat-form-field>\n\n <mat-form-field appearance=\"fill\">\n <mat-label>Issue date</mat-label>\n <input matInput type=\"date\" name=\"issueDate\" [(ngModel)]=\"invoice.issueDate\" required />\n </mat-form-field>\n\n <mat-form-field appearance=\"fill\">\n <mat-label>Due date</mat-label>\n <input matInput type=\"date\" name=\"dueDate\" [(ngModel)]=\"invoice.dueDate\" />\n </mat-form-field>\n\n <mat-form-field appearance=\"fill\" class=\"lg:col-span-2\">\n <mat-label>Notes</mat-label>\n <textarea\n matInput\n rows=\"2\"\n name=\"notes\"\n [(ngModel)]=\"invoice.notes\"\n placeholder=\"Optional notes shown on the invoice\"\n ></textarea>\n </mat-form-field>\n </div>\n </mat-card>\n\n <mat-card appearance=\"outlined\" class=\"p-4 space-y-4 mt-4\">\n <div class=\"flex items-center justify-between\">\n <h2 class=\"text-xl font-medium\">Line items</h2>\n <button mat-stroked-button type=\"button\" (click)=\"addLine()\">\n <mat-icon>add</mat-icon>\n Add line\n </button>\n </div>\n\n <div class=\"hidden lg:grid grid-cols-12 gap-2 text-sm font-medium px-2\">\n <div class=\"col-span-3\">Type</div>\n <div class=\"col-span-3\">Description</div>\n <div class=\"col-span-1 text-right\">Qty</div>\n <div class=\"col-span-2 text-right\">Unit Price</div>\n <div class=\"col-span-2 text-right\">Tax %</div>\n <div class=\"col-span-1\"></div>\n </div>\n\n <div class=\"space-y-3\">\n @for ( line of invoice.lines; track line; let i = $index) {\n <div class=\"grid grid-cols-1 lg:grid-cols-12 gap-2 items-start\">\n <rolatech-enum-select\n class=\"lg:col-span-3\"\n [resource]=\"'billing'\"\n [enumName]=\"'InvoiceLineType'\"\n [label]=\"'Line type'\"\n [placeholder]=\"'Select\u2026'\"\n [(value)]=\"line.type\"\n ></rolatech-enum-select>\n <mat-form-field class=\"lg:col-span-3\" appearance=\"fill\">\n <mat-label>Description</mat-label>\n <input\n matInput\n [name]=\"'desc'+i\"\n [(ngModel)]=\"line.description\"\n required\n placeholder=\"e.g. Photo editing package\"\n />\n </mat-form-field>\n\n <mat-form-field class=\"lg:col-span-1\" appearance=\"fill\">\n <mat-label>Quantity</mat-label>\n <input matInput type=\"number\" min=\"1\" step=\"1\" [name]=\"'qty'+i\" [(ngModel)]=\"line.quantity\" required />\n </mat-form-field>\n\n <mat-form-field class=\"lg:col-span-2\" appearance=\"fill\">\n <mat-label>Unit Price</mat-label>\n <input matInput type=\"number\" min=\"0\" step=\"0.01\" [name]=\"'unit'+i\" [(ngModel)]=\"line.unitPrice\" required />\n <mat-hint>e.g. 99.99</mat-hint>\n </mat-form-field>\n\n <mat-form-field class=\"lg:col-span-2\" appearance=\"fill\">\n <mat-label>Tax %</mat-label>\n <input matInput type=\"number\" min=\"0\" step=\"0.01\" [name]=\"'tax'+i\" [(ngModel)]=\"line.taxRate\" />\n </mat-form-field>\n\n <div class=\"lg:col-span-1 flex items-center justify-end\">\n <button mat-icon-button color=\"warn\" type=\"button\" (click)=\"removeLine(i)\" aria-label=\"Remove line\">\n <mat-icon>delete</mat-icon>\n </button>\n </div>\n </div>\n <mat-divider></mat-divider>\n }\n </div>\n\n <div class=\"flex flex-col items-end space-y-1\">\n <div class=\"text-sm\">\n Subtotal: <span class=\"font-medium\">{{ subtotalMajor() | number:'1.2-2' }} {{ invoice.currency }}</span>\n </div>\n <div class=\"text-sm\">\n Tax: <span class=\"font-medium\">{{ taxMajor() | number:'1.2-2' }} {{ invoice.currency }}</span>\n </div>\n <div class=\"text-lg font-semibold\">Total: {{ totalMajor() | number:'1.2-2' }} {{ invoice.currency }}</div>\n </div>\n </mat-card>\n </form>\n\n @if (lastResponse) {\n <mat-card appearance=\"outlined\" class=\"p-4 flex items-center justify-between\">\n <div>\n <div class=\"font-medium\">Created invoice #{{ lastResponse.data.id }}</div>\n <div class=\"text-sm opacity-70\">\n Total: {{ penceToMajor(lastResponse.data.total) | number:'1.2-2' }} {{ invoice.currency }}\n </div>\n </div>\n <div class=\"space-x-2\">\n <button mat-stroked-button (click)=\"resetForm()\">Create another</button>\n <button mat-flat-button color=\"primary\">Download PDF</button>\n </div>\n </mat-card>\n }\n</div>\n" }]
|
|
664
701
|
}] });
|
|
665
702
|
|
|
666
703
|
const MY_FORMATS = {
|
|
@@ -684,8 +721,8 @@ class InvoiceEdit extends BaseComponent {
|
|
|
684
721
|
ngDoCheck() {
|
|
685
722
|
this.output.emit(this.invoice());
|
|
686
723
|
}
|
|
687
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.
|
|
688
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.
|
|
724
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceEdit, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
725
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.6", type: InvoiceEdit, isStandalone: true, selector: "rolatech-invoice-edit", inputs: { invoice: { classPropertyName: "invoice", publicName: "invoice", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { invoice: "invoiceChange", output: "output" }, providers: [
|
|
689
726
|
{
|
|
690
727
|
provide: DateAdapter,
|
|
691
728
|
useClass: MomentDateAdapter,
|
|
@@ -694,7 +731,7 @@ class InvoiceEdit extends BaseComponent {
|
|
|
694
731
|
{ provide: MAT_DATE_FORMATS, useValue: MY_FORMATS },
|
|
695
732
|
], usesInheritance: true, ngImport: i0, template: "<div class=\"flex flex-col gap-2\">\n <div class=\"flex justify-between gap-3\">\n <mat-form-field appearance=\"fill\" subscriptSizing=\"dynamic\">\n <mat-label>Firstname</mat-label>\n <input matInput [(ngModel)]=\"invoice().firstName\" />\n </mat-form-field>\n <mat-form-field appearance=\"fill\" subscriptSizing=\"dynamic\">\n <mat-label>Lastname</mat-label>\n <input matInput [(ngModel)]=\"invoice().lastName\" />\n </mat-form-field>\n </div>\n <mat-form-field appearance=\"fill\" subscriptSizing=\"dynamic\">\n <mat-label>Email</mat-label>\n <input matInput [(ngModel)]=\"invoice().email\" />\n </mat-form-field>\n <mat-form-field appearance=\"fill\" subscriptSizing=\"dynamic\">\n <mat-label>Phone</mat-label>\n <input matInput [(ngModel)]=\"invoice().phone\" />\n </mat-form-field>\n <mat-form-field appearance=\"fill\" subscriptSizing=\"dynamic\">\n <mat-label>Discount total</mat-label>\n <input matInput [(ngModel)]=\"invoice().discountAmount\" />\n </mat-form-field>\n <mat-form-field appearance=\"fill\" subscriptSizing=\"dynamic\">\n <mat-label>Total</mat-label>\n <input matInput [(ngModel)]=\"invoice().total\" />\n </mat-form-field>\n <!-- <mat-form-field appearance=\"fill\" subscriptSizing=\"dynamic\">\n <mat-label i18n>Move-in date</mat-label>\n <input\n matInput\n placeholder=\"Move-in date\"\n [min]=\"minDate\"\n [matDatepicker]=\"startDatePicker\"\n (focus)=\"startDatePicker.open()\"\n [(ngModel)]=\"invoice().startDate\"\n (dateInput)=\"invoice().startDate = $event.value.format('YYYY-MM-DD')\"\n required\n readonly\n />\n <mat-datepicker-toggle matIconPrefix [for]=\"startDatePicker\"></mat-datepicker-toggle>\n <mat-datepicker #startDatePicker></mat-datepicker>\n </mat-form-field> -->\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "ngmodule", type: MatSelectModule }, { kind: "ngmodule", type: MatOptionModule }] }); }
|
|
696
733
|
}
|
|
697
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
734
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceEdit, decorators: [{
|
|
698
735
|
type: Component,
|
|
699
736
|
args: [{ selector: 'rolatech-invoice-edit', imports: [CommonModule, FormsModule, MatInputModule, MatDatepickerModule, MatSelectModule, MatOptionModule], providers: [
|
|
700
737
|
{
|
|
@@ -706,32 +743,395 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImpor
|
|
|
706
743
|
], template: "<div class=\"flex flex-col gap-2\">\n <div class=\"flex justify-between gap-3\">\n <mat-form-field appearance=\"fill\" subscriptSizing=\"dynamic\">\n <mat-label>Firstname</mat-label>\n <input matInput [(ngModel)]=\"invoice().firstName\" />\n </mat-form-field>\n <mat-form-field appearance=\"fill\" subscriptSizing=\"dynamic\">\n <mat-label>Lastname</mat-label>\n <input matInput [(ngModel)]=\"invoice().lastName\" />\n </mat-form-field>\n </div>\n <mat-form-field appearance=\"fill\" subscriptSizing=\"dynamic\">\n <mat-label>Email</mat-label>\n <input matInput [(ngModel)]=\"invoice().email\" />\n </mat-form-field>\n <mat-form-field appearance=\"fill\" subscriptSizing=\"dynamic\">\n <mat-label>Phone</mat-label>\n <input matInput [(ngModel)]=\"invoice().phone\" />\n </mat-form-field>\n <mat-form-field appearance=\"fill\" subscriptSizing=\"dynamic\">\n <mat-label>Discount total</mat-label>\n <input matInput [(ngModel)]=\"invoice().discountAmount\" />\n </mat-form-field>\n <mat-form-field appearance=\"fill\" subscriptSizing=\"dynamic\">\n <mat-label>Total</mat-label>\n <input matInput [(ngModel)]=\"invoice().total\" />\n </mat-form-field>\n <!-- <mat-form-field appearance=\"fill\" subscriptSizing=\"dynamic\">\n <mat-label i18n>Move-in date</mat-label>\n <input\n matInput\n placeholder=\"Move-in date\"\n [min]=\"minDate\"\n [matDatepicker]=\"startDatePicker\"\n (focus)=\"startDatePicker.open()\"\n [(ngModel)]=\"invoice().startDate\"\n (dateInput)=\"invoice().startDate = $event.value.format('YYYY-MM-DD')\"\n required\n readonly\n />\n <mat-datepicker-toggle matIconPrefix [for]=\"startDatePicker\"></mat-datepicker-toggle>\n <mat-datepicker #startDatePicker></mat-datepicker>\n </mat-form-field> -->\n</div>\n" }]
|
|
707
744
|
}], propDecorators: { invoice: [{ type: i0.Input, args: [{ isSignal: true, alias: "invoice", required: true }] }, { type: i0.Output, args: ["invoiceChange"] }], output: [{ type: i0.Output, args: ["output"] }] } });
|
|
708
745
|
|
|
746
|
+
class InvoiceStore {
|
|
747
|
+
constructor() {
|
|
748
|
+
this.api = inject(InvoiceService);
|
|
749
|
+
this._lines = signal([], ...(ngDevMode ? [{ debugName: "_lines" }] : []));
|
|
750
|
+
// --- state ---
|
|
751
|
+
this.invoiceId = signal(null, ...(ngDevMode ? [{ debugName: "invoiceId" }] : []));
|
|
752
|
+
this.invoice = signal(null, ...(ngDevMode ? [{ debugName: "invoice" }] : []));
|
|
753
|
+
/** last server-saved snapshot */
|
|
754
|
+
this.pristine = signal(null, ...(ngDevMode ? [{ debugName: "pristine" }] : []));
|
|
755
|
+
this.status = signal('idle', ...(ngDevMode ? [{ debugName: "status" }] : []));
|
|
756
|
+
this.error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : []));
|
|
757
|
+
// editor UI state
|
|
758
|
+
this.editorOpen = signal(false, ...(ngDevMode ? [{ debugName: "editorOpen" }] : []));
|
|
759
|
+
this.editingLineId = signal(null, ...(ngDevMode ? [{ debugName: "editingLineId" }] : []));
|
|
760
|
+
// --- derived ---
|
|
761
|
+
this.lines = computed(() => this.invoice()?.lines ?? [], ...(ngDevMode ? [{ debugName: "lines" }] : []));
|
|
762
|
+
this.currency = computed(() => this.invoice()?.currency ?? 'GBP', ...(ngDevMode ? [{ debugName: "currency" }] : []));
|
|
763
|
+
this.vatMode = computed(() => this.invoice()?.vatMode ?? InvoiceVatMode.EXCLUSIVE, ...(ngDevMode ? [{ debugName: "vatMode" }] : []));
|
|
764
|
+
this.totals = computed(() => {
|
|
765
|
+
const inv = this.invoice();
|
|
766
|
+
if (!inv) {
|
|
767
|
+
return { subtotal: 0, vatTotal: 0, total: 0 };
|
|
768
|
+
}
|
|
769
|
+
const lines = inv.lines ?? [];
|
|
770
|
+
const subtotal = lines.reduce((s, l) => s + (l.amount ?? 0), 0);
|
|
771
|
+
const vatTotal = lines.reduce((s, l) => s + (l.vatAmount ?? 0), 0);
|
|
772
|
+
const total = lines.reduce((s, l) => s + (l.lineTotal ?? 0), 0);
|
|
773
|
+
return { subtotal, vatTotal, total };
|
|
774
|
+
}, ...(ngDevMode ? [{ debugName: "totals" }] : []));
|
|
775
|
+
/** 🔴 DIRTY FLAG */
|
|
776
|
+
this.dirty = computed(() => {
|
|
777
|
+
const a = this.invoice();
|
|
778
|
+
const b = this.pristine();
|
|
779
|
+
if (!a || !b)
|
|
780
|
+
return false;
|
|
781
|
+
return JSON.stringify(a) !== JSON.stringify(b);
|
|
782
|
+
}, ...(ngDevMode ? [{ debugName: "dirty" }] : []));
|
|
783
|
+
}
|
|
784
|
+
// --- API: load / refresh ---
|
|
785
|
+
load(id) {
|
|
786
|
+
this.invoiceId.set(id);
|
|
787
|
+
this.status.set('loading');
|
|
788
|
+
this.error.set(null);
|
|
789
|
+
// ✅ Adapt to your actual API signature:
|
|
790
|
+
// e.g. this.api.get(id) or this.api.getInvoice(id)
|
|
791
|
+
this.api
|
|
792
|
+
.get(id)
|
|
793
|
+
.pipe(finalize$1(() => this.status.set('idle')))
|
|
794
|
+
.subscribe({
|
|
795
|
+
next: (inv) => {
|
|
796
|
+
console.log(inv);
|
|
797
|
+
const normalized = this.normalizeInvoice(inv.data);
|
|
798
|
+
this.invoice.set(normalized);
|
|
799
|
+
this.pristine.set(structuredClone(normalized));
|
|
800
|
+
},
|
|
801
|
+
error: (e) => {
|
|
802
|
+
this.status.set('error');
|
|
803
|
+
this.error.set(this.errMsg(e));
|
|
804
|
+
},
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
refresh() {
|
|
808
|
+
const id = this.invoiceId();
|
|
809
|
+
if (!id)
|
|
810
|
+
return;
|
|
811
|
+
this.load(id);
|
|
812
|
+
}
|
|
813
|
+
// --- API: save ---
|
|
814
|
+
saveInvoice() {
|
|
815
|
+
const id = this.invoiceId();
|
|
816
|
+
const inv = this.invoice();
|
|
817
|
+
if (!id || !inv)
|
|
818
|
+
return;
|
|
819
|
+
this.status.set('saving');
|
|
820
|
+
this.error.set(null);
|
|
821
|
+
// ✅ Adapt: this.api.update(id, dto)
|
|
822
|
+
this.api
|
|
823
|
+
.update(id, inv)
|
|
824
|
+
.pipe(finalize$1(() => this.status.set('idle')))
|
|
825
|
+
.subscribe({
|
|
826
|
+
next: (saved) => this.invoice.set(this.normalizeInvoice(saved)),
|
|
827
|
+
error: (e) => {
|
|
828
|
+
this.status.set('error');
|
|
829
|
+
this.error.set(this.errMsg(e));
|
|
830
|
+
},
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
saveLines() {
|
|
834
|
+
const id = this.invoiceId();
|
|
835
|
+
const inv = this.invoice();
|
|
836
|
+
if (!id || !inv)
|
|
837
|
+
return;
|
|
838
|
+
this.status.set('saving');
|
|
839
|
+
this.error.set(null);
|
|
840
|
+
const payload = (inv.lines ?? []).map((l) => this.toLinePayload(l));
|
|
841
|
+
// ✅ Adapt: this.api.updateLines(id, payload)
|
|
842
|
+
// If your backend updates invoice lines via PATCH /invoices/{id}/lines
|
|
843
|
+
this.api
|
|
844
|
+
.updateLines(id, payload)
|
|
845
|
+
.pipe(finalize$1(() => this.status.set('idle')))
|
|
846
|
+
.subscribe({
|
|
847
|
+
next: (saved) => {
|
|
848
|
+
const normalized = this.normalizeInvoice(saved.data);
|
|
849
|
+
this.invoice.set(normalized);
|
|
850
|
+
this.pristine.set(structuredClone(normalized));
|
|
851
|
+
},
|
|
852
|
+
error: (e) => {
|
|
853
|
+
this.status.set('error');
|
|
854
|
+
this.error.set(this.errMsg(e));
|
|
855
|
+
},
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
// ─────────────────────────────────────────────
|
|
859
|
+
// Editor lifecycle
|
|
860
|
+
// ─────────────────────────────────────────────
|
|
861
|
+
openLineEditor(lineId) {
|
|
862
|
+
this.editorOpen.set(true);
|
|
863
|
+
this.editingLineId.set(lineId ?? null);
|
|
864
|
+
}
|
|
865
|
+
closeLineEditor() {
|
|
866
|
+
this.editorOpen.set(false);
|
|
867
|
+
this.editingLineId.set(null);
|
|
868
|
+
}
|
|
869
|
+
getLineById(lineId) {
|
|
870
|
+
return this.lines().find((l) => l.id === lineId) ?? null;
|
|
871
|
+
}
|
|
872
|
+
// ─────────────────────────────────────────────
|
|
873
|
+
// Line operations (ALL mark dirty automatically)
|
|
874
|
+
// ─────────────────────────────────────────────
|
|
875
|
+
addLineAndEdit() {
|
|
876
|
+
this.invoice.update((inv) => {
|
|
877
|
+
if (!inv)
|
|
878
|
+
return inv;
|
|
879
|
+
const newLine = this.recalcLine(this.createEmptyLine(inv.currency), inv.vatMode);
|
|
880
|
+
queueMicrotask(() => this.openLineEditor(newLine.id));
|
|
881
|
+
return { ...inv, lines: [...inv.lines, newLine] };
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
updateLineById(lineId, patch) {
|
|
885
|
+
this.invoice.update((inv) => {
|
|
886
|
+
if (!inv)
|
|
887
|
+
return inv;
|
|
888
|
+
const mode = inv.vatMode;
|
|
889
|
+
const lines = (inv.lines ?? []).map((l) => {
|
|
890
|
+
if (l.id !== lineId)
|
|
891
|
+
return l;
|
|
892
|
+
const merged = { ...l, ...patch };
|
|
893
|
+
return this.recalcLine(merged, mode);
|
|
894
|
+
});
|
|
895
|
+
return { ...inv, lines };
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
removeLineById(lineId) {
|
|
899
|
+
this.invoice.update((inv) => {
|
|
900
|
+
if (!inv)
|
|
901
|
+
return inv;
|
|
902
|
+
if (this.editingLineId() === lineId)
|
|
903
|
+
this.closeLineEditor();
|
|
904
|
+
return { ...inv, lines: inv.lines.filter((l) => l.id !== lineId) };
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
// If vatMode changes, recalc all lines
|
|
908
|
+
setVatMode(mode) {
|
|
909
|
+
this.invoice.update((inv) => {
|
|
910
|
+
if (!inv)
|
|
911
|
+
return inv;
|
|
912
|
+
const lines = (inv.lines ?? []).map((l) => this.recalcLine(l, mode));
|
|
913
|
+
return { ...inv, vatMode: mode, lines };
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
reorderLines(fromIndex, toIndex) {
|
|
917
|
+
if (fromIndex === toIndex)
|
|
918
|
+
return;
|
|
919
|
+
this.invoice.update((inv) => {
|
|
920
|
+
if (!inv)
|
|
921
|
+
return inv;
|
|
922
|
+
const lines = [...(inv.lines ?? [])];
|
|
923
|
+
const [moved] = lines.splice(fromIndex, 1);
|
|
924
|
+
lines.splice(toIndex, 0, moved);
|
|
925
|
+
// re-index positions (1..n)
|
|
926
|
+
const rePos = lines.map((l, idx) => ({ ...l, position: idx + 1 }));
|
|
927
|
+
return { ...inv, lines: rePos };
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
setLines(lines) {
|
|
931
|
+
this._lines.set(lines);
|
|
932
|
+
}
|
|
933
|
+
updateLine(id, patch) {
|
|
934
|
+
this._lines.update((lines) => lines.map((l) => (l.id === id ? { ...l, ...patch } : l)));
|
|
935
|
+
}
|
|
936
|
+
// --- helpers: normalize / compute ---
|
|
937
|
+
normalizeInvoice(inv) {
|
|
938
|
+
const mode = inv.vatMode ?? InvoiceVatMode.EXCLUSIVE;
|
|
939
|
+
const sorted = [...(inv.lines ?? [])]
|
|
940
|
+
.sort((a, b) => (a.position ?? 0) - (b.position ?? 0))
|
|
941
|
+
.map((l, idx) => this.recalcLine({ ...l, position: idx + 1 }, mode));
|
|
942
|
+
return { ...inv, lines: sorted };
|
|
943
|
+
}
|
|
944
|
+
recalcLine(line, mode) {
|
|
945
|
+
const qty = this.num(line.quantity, 1);
|
|
946
|
+
const unit = this.num(line.unitPrice, 0);
|
|
947
|
+
const rate = this.num(line.vatRate, 0);
|
|
948
|
+
const grossUnit = unit;
|
|
949
|
+
const netUnit = mode === InvoiceVatMode.INCLUSIVE ? grossUnit / (1 + rate / 100) : grossUnit;
|
|
950
|
+
const net = netUnit * qty;
|
|
951
|
+
const vat = net * (rate / 100);
|
|
952
|
+
const total = mode === InvoiceVatMode.INCLUSIVE ? grossUnit * qty : net + vat;
|
|
953
|
+
return {
|
|
954
|
+
...line,
|
|
955
|
+
quantity: qty,
|
|
956
|
+
unitPrice: unit,
|
|
957
|
+
vatRate: rate,
|
|
958
|
+
amount: this.round2(net),
|
|
959
|
+
vatAmount: this.round2(mode === InvoiceVatMode.INCLUSIVE ? total - net : vat),
|
|
960
|
+
lineTotal: this.round2(total),
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
createEmptyLine(currency) {
|
|
964
|
+
return {
|
|
965
|
+
id: this.newId(),
|
|
966
|
+
code: '',
|
|
967
|
+
title: 'New item',
|
|
968
|
+
description: '',
|
|
969
|
+
imageUrl: '',
|
|
970
|
+
amount: 0,
|
|
971
|
+
unitPrice: 0,
|
|
972
|
+
vatRate: 0,
|
|
973
|
+
vatAmount: 0,
|
|
974
|
+
lineTotal: 0,
|
|
975
|
+
quantity: 1,
|
|
976
|
+
currency,
|
|
977
|
+
refId: '',
|
|
978
|
+
type: InvoiceLineType.CUSTOM,
|
|
979
|
+
position: 0,
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
toLinePayload(l) {
|
|
983
|
+
return {
|
|
984
|
+
id: l.id,
|
|
985
|
+
position: l.position, // ✅ persist order
|
|
986
|
+
type: l.type,
|
|
987
|
+
title: l.title,
|
|
988
|
+
description: l.description,
|
|
989
|
+
quantity: l.quantity,
|
|
990
|
+
unitPrice: l.unitPrice,
|
|
991
|
+
vatRate: l.vatRate,
|
|
992
|
+
currency: l.currency,
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
newId() {
|
|
996
|
+
return typeof crypto !== 'undefined' && 'randomUUID' in crypto
|
|
997
|
+
? crypto.randomUUID()
|
|
998
|
+
: `tmp_${Date.now()}_${Math.random().toString(16).slice(2)}`;
|
|
999
|
+
}
|
|
1000
|
+
num(v, def) {
|
|
1001
|
+
const n = Number(v);
|
|
1002
|
+
return Number.isFinite(n) ? n : def;
|
|
1003
|
+
}
|
|
1004
|
+
round2(n) {
|
|
1005
|
+
return Math.round((n + Number.EPSILON) * 100) / 100;
|
|
1006
|
+
}
|
|
1007
|
+
errMsg(e) {
|
|
1008
|
+
return e?.error?.message || e?.message || 'Request failed';
|
|
1009
|
+
}
|
|
1010
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1011
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceStore, providedIn: 'root' }); }
|
|
1012
|
+
}
|
|
1013
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceStore, decorators: [{
|
|
1014
|
+
type: Injectable,
|
|
1015
|
+
args: [{ providedIn: 'root' }]
|
|
1016
|
+
}] });
|
|
1017
|
+
|
|
1018
|
+
class InvoiceLinePropertySelector {
|
|
1019
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceLinePropertySelector, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1020
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.6", type: InvoiceLinePropertySelector, isStandalone: true, selector: "rolatech-invoice-line-property-selector", ngImport: i0, template: "<p>invoice-line-property-selector works!</p>\n", styles: [""] }); }
|
|
1021
|
+
}
|
|
1022
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceLinePropertySelector, decorators: [{
|
|
1023
|
+
type: Component,
|
|
1024
|
+
args: [{ selector: 'rolatech-invoice-line-property-selector', imports: [], template: "<p>invoice-line-property-selector works!</p>\n" }]
|
|
1025
|
+
}] });
|
|
1026
|
+
|
|
1027
|
+
class InvoiceManageLine {
|
|
1028
|
+
constructor() {
|
|
1029
|
+
this.store = inject(InvoiceStore);
|
|
1030
|
+
this.dialog = inject(MatDialog);
|
|
1031
|
+
this.lines = this.store.lines;
|
|
1032
|
+
this.vatMode = this.store.vatMode;
|
|
1033
|
+
this.editMode = signal(false, ...(ngDevMode ? [{ debugName: "editMode" }] : []));
|
|
1034
|
+
this.openingForLine = new Set();
|
|
1035
|
+
this.isVatExclusive = () => this.vatMode() === 'EXCLUSIVE';
|
|
1036
|
+
}
|
|
1037
|
+
toggleEdit() {
|
|
1038
|
+
this.editMode.update((v) => !v);
|
|
1039
|
+
}
|
|
1040
|
+
add() {
|
|
1041
|
+
this.store.addLineAndEdit(); // or addLine()
|
|
1042
|
+
}
|
|
1043
|
+
remove(id) {
|
|
1044
|
+
this.store.removeLineById(id);
|
|
1045
|
+
}
|
|
1046
|
+
update(id, patch) {
|
|
1047
|
+
this.store.updateLineById(id, patch);
|
|
1048
|
+
}
|
|
1049
|
+
drop(event) {
|
|
1050
|
+
if (!this.editMode())
|
|
1051
|
+
return; // ⛔ prevent reorder when not editing
|
|
1052
|
+
this.store.reorderLines(event.previousIndex, event.currentIndex);
|
|
1053
|
+
}
|
|
1054
|
+
isPropertyLine(type) {
|
|
1055
|
+
return type === 'PROPERTY_RENT' || type === 'PROPERTY_SALE';
|
|
1056
|
+
}
|
|
1057
|
+
onTitleFocus(line) {
|
|
1058
|
+
if (!this.isPropertyLine(line.type))
|
|
1059
|
+
return;
|
|
1060
|
+
// guard: focusing can fire repeatedly (click, focus, programmatic)
|
|
1061
|
+
if (this.openingForLine.has(line.id))
|
|
1062
|
+
return;
|
|
1063
|
+
this.openingForLine.add(line.id);
|
|
1064
|
+
this.openPropertyDialog(line).finally(() => {
|
|
1065
|
+
// release guard a tick later so re-focus works after dialog closes
|
|
1066
|
+
queueMicrotask(() => this.openingForLine.delete(line.id));
|
|
1067
|
+
});
|
|
1068
|
+
}
|
|
1069
|
+
onTitleKeydown(ev, line) {
|
|
1070
|
+
if (!this.isPropertyLine(line.type))
|
|
1071
|
+
return;
|
|
1072
|
+
// Block manual typing when it’s a property picker
|
|
1073
|
+
const allow = ['Tab', 'Shift', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Escape', 'Enter'];
|
|
1074
|
+
if (!allow.includes(ev.key)) {
|
|
1075
|
+
ev.preventDefault();
|
|
1076
|
+
}
|
|
1077
|
+
// Optional: Enter opens dialog too
|
|
1078
|
+
if (ev.key === 'Enter') {
|
|
1079
|
+
ev.preventDefault();
|
|
1080
|
+
this.onTitleFocus(line);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
async openPropertyDialog(line) {
|
|
1084
|
+
if (!this.isPropertyLine(line.type))
|
|
1085
|
+
return;
|
|
1086
|
+
const ref = this.dialog.open(InvoiceLinePropertySelector, {
|
|
1087
|
+
data: {
|
|
1088
|
+
title: 'Select property',
|
|
1089
|
+
initialQuery: line.title ?? '',
|
|
1090
|
+
},
|
|
1091
|
+
autoFocus: false,
|
|
1092
|
+
restoreFocus: false,
|
|
1093
|
+
});
|
|
1094
|
+
const selected = await ref.afterClosed().toPromise();
|
|
1095
|
+
if (!selected)
|
|
1096
|
+
return;
|
|
1097
|
+
// ✅ update with title + refId(propertyId)
|
|
1098
|
+
this.store.updateLine(line.id, {
|
|
1099
|
+
title: selected.title,
|
|
1100
|
+
refId: selected.id,
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
saveLines() { }
|
|
1104
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceManageLine, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1105
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: InvoiceManageLine, isStandalone: true, selector: "rolatech-invoice-manage-line", ngImport: i0, template: "<!-- invoice-lines.html -->\n<div class=\"rounded-xl border border-[--rt-border-color] p-4 grid space-y-3\">\n <!-- Header -->\n <div class=\"flex items-center justify-between\">\n <h2 class=\"text-sm font-semibold\">Invoice lines</h2>\n\n <div class=\"flex items-center gap-2\">\n <button mat-icon-button type=\"button\" aria-label=\"Toggle edit\" (click)=\"toggleEdit()\">\n <mat-icon> {{ editMode() ? 'done' : 'edit' }} </mat-icon>\n </button>\n <button mat-stroked-button type=\"button\" [disabled]=\"store.dirty() || store.status() === 'saving'\" (click)=\"saveLines()\">\n @if (store.status() === 'saving') { Saving\u2026 } @else { Save lines }\n </button>\n </div>\n </div>\n\n <!-- Column labels (desktop only) -->\n <div class=\"hidden lg:grid grid-cols-12 gap-2 text-sm font-medium px-2 text-[--rt-text-secondary]\">\n <!-- Drag column -->\n @if (editMode()) {\n <div class=\"col-span-1\"></div>\n }\n\n <!-- Type -->\n <div [class]=\"editMode() ? 'col-span-3' : 'col-span-3'\">Type</div>\n\n <!-- Description (take extra space when not editing) -->\n <div [class]=\"editMode() ? 'col-span-4' : 'col-span-5'\">Title</div>\n\n <!-- Unit Price -->\n <div class=\"col-span-2 text-right\">Unit Price</div>\n\n <!-- Tax % (optional) -->\n @if (!isVatExclusive()) {\n <div class=\"col-span-1 text-right\">Tax %</div>\n }\n\n <!-- Amount (take extra space when not editing) -->\n <div [class]=\"editMode() ? 'col-span-1' : 'col-span-2'\" class=\"text-right\">Amount</div>\n\n <!-- Delete column -->\n @if (editMode()) {\n <div class=\"col-span-1\"></div>\n }\n </div>\n\n <!-- invoice-lines.html (rows section) -->\n\n <div cdkDropList (cdkDropListDropped)=\"drop($event)\" [cdkDropListDisabled]=\"!editMode()\" class=\"space-y-2\">\n @if (lines().length > 0) { @for (line of lines(); track line.id; let i = $index) {\n\n <div\n cdkDrag\n class=\"grid grid-cols-12 gap-2 items-start rounded-xl border border-[--rt-border-color] bg-[--rt-background] p-2 hover:bg-[--rt-raised-background]\"\n [class.opacity-70]=\"!editMode()\"\n >\n <!-- Drag handle column (only in edit mode) -->\n @if (editMode()) {\n <div class=\"col-span-1 flex items-center justify-center\">\n <button mat-icon-button type=\"button\" cdkDragHandle aria-label=\"Reorder line\" (click)=\"$event.stopPropagation()\">\n <mat-icon>drag_indicator</mat-icon>\n </button>\n </div>\n }\n\n <!-- Type -->\n <div class=\"col-span-12 lg:col-span-3\">\n <rolatech-enum-select\n [resource]=\"'billing'\"\n [enumName]=\"'InvoiceLineType'\"\n [label]=\"'Line type'\"\n [placeholder]=\"'Select\u2026'\"\n [value]=\"line.type\"\n (valueChange)=\"editMode() && update(line.id!, { type: $event })\"\n ></rolatech-enum-select>\n </div>\n\n <!-- Title (expands when NOT editMode) -->\n <div [class]=\"editMode() ? 'col-span-12 lg:col-span-4' : 'col-span-12 lg:col-span-5'\">\n <mat-form-field appearance=\"fill\" class=\"w-full\" subscriptSizing=\"dynamic\">\n <mat-label>Title</mat-label>\n <input\n matInput\n placeholder=\"e.g. Photo editing package\"\n [readonly]=\"!editMode()\"\n [readonly]=\"isPropertyLine(line.type)\"\n (focus)=\"onTitleFocus(line)\"\n (click)=\"onTitleFocus(line)\"\n (keydown)=\"onTitleKeydown($event, line)\"\n [value]=\"line.title\"\n (input)=\"editMode() && update(line.id!, { title: $any($event.target).value })\"\n />\n </mat-form-field>\n </div>\n\n <!-- Unit Price -->\n <div class=\"col-span-6 lg:col-span-2\">\n <mat-form-field appearance=\"fill\" class=\"w-full\" subscriptSizing=\"dynamic\">\n <mat-label>Unit</mat-label>\n <input\n matInput\n type=\"number\"\n min=\"0\"\n step=\"0.01\"\n class=\"text-right\"\n [readonly]=\"!editMode()\"\n [value]=\"line.unitPrice\"\n (input)=\"editMode() && update(line.id!, { unitPrice: +$any($event.target).value })\"\n />\n </mat-form-field>\n </div>\n\n <!-- Tax % (optional, hidden if VAT exclusive) -->\n @if (!isVatExclusive()) {\n <div class=\"col-span-6 lg:col-span-1\">\n <mat-form-field appearance=\"fill\" class=\"w-full\" subscriptSizing=\"dynamic\">\n <mat-label>Tax %</mat-label>\n <input\n matInput\n type=\"number\"\n min=\"0\"\n step=\"0.01\"\n class=\"text-right\"\n [readonly]=\"!editMode()\"\n [value]=\"line.vatRate\"\n (input)=\"editMode() && update(line.id!, { vatRate: +$any($event.target).value })\"\n />\n </mat-form-field>\n </div>\n }\n\n <!-- Amount (expands when NOT editMode) -->\n <div\n [class]=\"editMode() ? 'col-span-6 lg:col-span-1' : 'col-span-6 lg:col-span-2'\"\n class=\"flex items-center justify-end px-1 lg:pt-3\"\n >\n <div class=\"text-right font-semibold text-[--rt-text-secondary]\">{{ line.lineTotal | price }}</div>\n </div>\n\n <!-- Delete column (only in edit mode) -->\n @if (editMode()) {\n <div class=\"col-span-6 lg:col-span-1 flex items-center justify-end lg:pt-1\">\n <button mat-icon-button color=\"warn\" type=\"button\" aria-label=\"Delete line\" (click)=\"remove(line.id!)\">\n <mat-icon>delete</mat-icon>\n </button>\n </div>\n }\n </div>\n } } @else {\n <div class=\"py-8 text-center text-sm text-[--rt-text-secondary]\">No invoice lines</div>\n }\n </div>\n\n <!-- Footer -->\n <div>\n <button mat-stroked-button (click)=\"add()\">Add item</button>\n </div>\n</div>\n", styles: [".cdk-drag-preview{box-shadow:0 10px 20px #00000026;border-radius:12px}.cdk-drag-placeholder{opacity:.3}\n"], dependencies: [{ kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i1$3.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i1$3.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i1$3.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: EnumSelect, selector: "rolatech-enum-select", inputs: ["resource", "enumName", "label", "placeholder", "disabled", "value"], outputs: ["valueChange"] }, { kind: "pipe", type: PricePipe, name: "price" }], encapsulation: i0.ViewEncapsulation.None }); }
|
|
1106
|
+
}
|
|
1107
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceManageLine, decorators: [{
|
|
1108
|
+
type: Component,
|
|
1109
|
+
args: [{ selector: 'rolatech-invoice-manage-line', imports: [DragDropModule, MatButtonModule, MatIcon, MatFormFieldModule, MatInputModule, PricePipe, EnumSelect], encapsulation: ViewEncapsulation.None, template: "<!-- invoice-lines.html -->\n<div class=\"rounded-xl border border-[--rt-border-color] p-4 grid space-y-3\">\n <!-- Header -->\n <div class=\"flex items-center justify-between\">\n <h2 class=\"text-sm font-semibold\">Invoice lines</h2>\n\n <div class=\"flex items-center gap-2\">\n <button mat-icon-button type=\"button\" aria-label=\"Toggle edit\" (click)=\"toggleEdit()\">\n <mat-icon> {{ editMode() ? 'done' : 'edit' }} </mat-icon>\n </button>\n <button mat-stroked-button type=\"button\" [disabled]=\"store.dirty() || store.status() === 'saving'\" (click)=\"saveLines()\">\n @if (store.status() === 'saving') { Saving\u2026 } @else { Save lines }\n </button>\n </div>\n </div>\n\n <!-- Column labels (desktop only) -->\n <div class=\"hidden lg:grid grid-cols-12 gap-2 text-sm font-medium px-2 text-[--rt-text-secondary]\">\n <!-- Drag column -->\n @if (editMode()) {\n <div class=\"col-span-1\"></div>\n }\n\n <!-- Type -->\n <div [class]=\"editMode() ? 'col-span-3' : 'col-span-3'\">Type</div>\n\n <!-- Description (take extra space when not editing) -->\n <div [class]=\"editMode() ? 'col-span-4' : 'col-span-5'\">Title</div>\n\n <!-- Unit Price -->\n <div class=\"col-span-2 text-right\">Unit Price</div>\n\n <!-- Tax % (optional) -->\n @if (!isVatExclusive()) {\n <div class=\"col-span-1 text-right\">Tax %</div>\n }\n\n <!-- Amount (take extra space when not editing) -->\n <div [class]=\"editMode() ? 'col-span-1' : 'col-span-2'\" class=\"text-right\">Amount</div>\n\n <!-- Delete column -->\n @if (editMode()) {\n <div class=\"col-span-1\"></div>\n }\n </div>\n\n <!-- invoice-lines.html (rows section) -->\n\n <div cdkDropList (cdkDropListDropped)=\"drop($event)\" [cdkDropListDisabled]=\"!editMode()\" class=\"space-y-2\">\n @if (lines().length > 0) { @for (line of lines(); track line.id; let i = $index) {\n\n <div\n cdkDrag\n class=\"grid grid-cols-12 gap-2 items-start rounded-xl border border-[--rt-border-color] bg-[--rt-background] p-2 hover:bg-[--rt-raised-background]\"\n [class.opacity-70]=\"!editMode()\"\n >\n <!-- Drag handle column (only in edit mode) -->\n @if (editMode()) {\n <div class=\"col-span-1 flex items-center justify-center\">\n <button mat-icon-button type=\"button\" cdkDragHandle aria-label=\"Reorder line\" (click)=\"$event.stopPropagation()\">\n <mat-icon>drag_indicator</mat-icon>\n </button>\n </div>\n }\n\n <!-- Type -->\n <div class=\"col-span-12 lg:col-span-3\">\n <rolatech-enum-select\n [resource]=\"'billing'\"\n [enumName]=\"'InvoiceLineType'\"\n [label]=\"'Line type'\"\n [placeholder]=\"'Select\u2026'\"\n [value]=\"line.type\"\n (valueChange)=\"editMode() && update(line.id!, { type: $event })\"\n ></rolatech-enum-select>\n </div>\n\n <!-- Title (expands when NOT editMode) -->\n <div [class]=\"editMode() ? 'col-span-12 lg:col-span-4' : 'col-span-12 lg:col-span-5'\">\n <mat-form-field appearance=\"fill\" class=\"w-full\" subscriptSizing=\"dynamic\">\n <mat-label>Title</mat-label>\n <input\n matInput\n placeholder=\"e.g. Photo editing package\"\n [readonly]=\"!editMode()\"\n [readonly]=\"isPropertyLine(line.type)\"\n (focus)=\"onTitleFocus(line)\"\n (click)=\"onTitleFocus(line)\"\n (keydown)=\"onTitleKeydown($event, line)\"\n [value]=\"line.title\"\n (input)=\"editMode() && update(line.id!, { title: $any($event.target).value })\"\n />\n </mat-form-field>\n </div>\n\n <!-- Unit Price -->\n <div class=\"col-span-6 lg:col-span-2\">\n <mat-form-field appearance=\"fill\" class=\"w-full\" subscriptSizing=\"dynamic\">\n <mat-label>Unit</mat-label>\n <input\n matInput\n type=\"number\"\n min=\"0\"\n step=\"0.01\"\n class=\"text-right\"\n [readonly]=\"!editMode()\"\n [value]=\"line.unitPrice\"\n (input)=\"editMode() && update(line.id!, { unitPrice: +$any($event.target).value })\"\n />\n </mat-form-field>\n </div>\n\n <!-- Tax % (optional, hidden if VAT exclusive) -->\n @if (!isVatExclusive()) {\n <div class=\"col-span-6 lg:col-span-1\">\n <mat-form-field appearance=\"fill\" class=\"w-full\" subscriptSizing=\"dynamic\">\n <mat-label>Tax %</mat-label>\n <input\n matInput\n type=\"number\"\n min=\"0\"\n step=\"0.01\"\n class=\"text-right\"\n [readonly]=\"!editMode()\"\n [value]=\"line.vatRate\"\n (input)=\"editMode() && update(line.id!, { vatRate: +$any($event.target).value })\"\n />\n </mat-form-field>\n </div>\n }\n\n <!-- Amount (expands when NOT editMode) -->\n <div\n [class]=\"editMode() ? 'col-span-6 lg:col-span-1' : 'col-span-6 lg:col-span-2'\"\n class=\"flex items-center justify-end px-1 lg:pt-3\"\n >\n <div class=\"text-right font-semibold text-[--rt-text-secondary]\">{{ line.lineTotal | price }}</div>\n </div>\n\n <!-- Delete column (only in edit mode) -->\n @if (editMode()) {\n <div class=\"col-span-6 lg:col-span-1 flex items-center justify-end lg:pt-1\">\n <button mat-icon-button color=\"warn\" type=\"button\" aria-label=\"Delete line\" (click)=\"remove(line.id!)\">\n <mat-icon>delete</mat-icon>\n </button>\n </div>\n }\n </div>\n } } @else {\n <div class=\"py-8 text-center text-sm text-[--rt-text-secondary]\">No invoice lines</div>\n }\n </div>\n\n <!-- Footer -->\n <div>\n <button mat-stroked-button (click)=\"add()\">Add item</button>\n </div>\n</div>\n", styles: [".cdk-drag-preview{box-shadow:0 10px 20px #00000026;border-radius:12px}.cdk-drag-placeholder{opacity:.3}\n"] }]
|
|
1110
|
+
}] });
|
|
1111
|
+
|
|
709
1112
|
class InvoiceManageDetail extends BaseComponent {
|
|
710
1113
|
constructor() {
|
|
711
1114
|
super(...arguments);
|
|
712
1115
|
this.invoiceService = inject(InvoiceService);
|
|
713
|
-
this.
|
|
714
|
-
|
|
715
|
-
this.invoice =
|
|
1116
|
+
this.store = inject(InvoiceStore);
|
|
1117
|
+
// expose store signals
|
|
1118
|
+
this.invoice = this.store.invoice;
|
|
716
1119
|
this.status = InvoiceStatus;
|
|
1120
|
+
// convenience derived values for template
|
|
1121
|
+
this.loading = computed(() => this.store.status() === 'loading', ...(ngDevMode ? [{ debugName: "loading" }] : []));
|
|
1122
|
+
this.pdfUrl = computed(() => this.store.invoice()?.pdfUrl ?? '', ...(ngDevMode ? [{ debugName: "pdfUrl" }] : []));
|
|
717
1123
|
}
|
|
718
1124
|
ngOnInit() {
|
|
719
|
-
this.
|
|
720
|
-
}
|
|
721
|
-
getInvoice() {
|
|
722
|
-
this.invoiceService.getInvoice(this.id).subscribe({
|
|
723
|
-
next: (res) => {
|
|
724
|
-
this.invoice.set(res.data);
|
|
725
|
-
this.loading.set(false);
|
|
726
|
-
this.pdfUrl = res.data.pdfUrl;
|
|
727
|
-
},
|
|
728
|
-
});
|
|
1125
|
+
this.store.load(this.id);
|
|
729
1126
|
}
|
|
730
1127
|
openPdf() {
|
|
731
|
-
|
|
1128
|
+
const url = this.pdfUrl();
|
|
1129
|
+
if (!url)
|
|
1130
|
+
return;
|
|
1131
|
+
window.open(url, '_blank', 'noopener,noreferrer');
|
|
732
1132
|
}
|
|
733
1133
|
statusBadgeClass(status) {
|
|
734
|
-
switch (status
|
|
1134
|
+
switch (String(status)) {
|
|
735
1135
|
case 'CREATED':
|
|
736
1136
|
return 'bg-yellow-100 text-yellow-800';
|
|
737
1137
|
case 'ISSUED':
|
|
@@ -745,61 +1145,92 @@ class InvoiceManageDetail extends BaseComponent {
|
|
|
745
1145
|
}
|
|
746
1146
|
}
|
|
747
1147
|
edit() {
|
|
748
|
-
const
|
|
1148
|
+
const inv = this.invoice();
|
|
1149
|
+
if (!inv)
|
|
1150
|
+
return;
|
|
1151
|
+
this.dialogService.open({
|
|
749
1152
|
title: 'Update invoice',
|
|
750
1153
|
cancelText: 'Cancel',
|
|
751
1154
|
confirmText: 'Confirm',
|
|
752
|
-
data: {
|
|
753
|
-
invoice: this.invoice(),
|
|
754
|
-
},
|
|
1155
|
+
data: { invoice: inv },
|
|
755
1156
|
component: InvoiceEdit,
|
|
756
|
-
};
|
|
757
|
-
|
|
758
|
-
this.dialogService.confirmed().subscribe({
|
|
1157
|
+
});
|
|
1158
|
+
// IMPORTANT: avoid stacking subscriptions if user opens dialog repeatedly
|
|
1159
|
+
const sub = this.dialogService.confirmed().subscribe({
|
|
759
1160
|
next: (res) => {
|
|
760
|
-
if (res)
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
1161
|
+
if (!res)
|
|
1162
|
+
return;
|
|
1163
|
+
const payload = {
|
|
1164
|
+
firstName: res.firstName,
|
|
1165
|
+
lastName: res.lastName,
|
|
1166
|
+
email: res.email,
|
|
1167
|
+
phone: res.phone,
|
|
1168
|
+
vatTotal: res.vatTotal,
|
|
1169
|
+
total: res.total,
|
|
1170
|
+
};
|
|
1171
|
+
this.invoiceService.updateInvoice(this.id, payload).subscribe({
|
|
1172
|
+
next: () => {
|
|
1173
|
+
this.snackBarService.open('Invoice updated');
|
|
1174
|
+
this.store.refresh(); // reload from API so UI stays consistent
|
|
1175
|
+
},
|
|
1176
|
+
error: (err) => this.snackBarService.open(err?.message ?? 'Update failed'),
|
|
1177
|
+
});
|
|
775
1178
|
},
|
|
776
|
-
error: (error) =>
|
|
777
|
-
|
|
1179
|
+
error: (error) => this.snackBarService.open(error?.message ?? 'Dialog error'),
|
|
1180
|
+
complete: () => sub.unsubscribe(),
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
addLine() {
|
|
1184
|
+
// add and open editor (recommended)
|
|
1185
|
+
this.store.addLineAndEdit();
|
|
1186
|
+
// OR if you just want to add without opening editor:
|
|
1187
|
+
// this.store.addLine();
|
|
1188
|
+
}
|
|
1189
|
+
// Optional: persist changes
|
|
1190
|
+
saveLines() {
|
|
1191
|
+
this.store.saveLines();
|
|
1192
|
+
}
|
|
1193
|
+
editCustomer() { }
|
|
1194
|
+
sendEmail() {
|
|
1195
|
+
const dialogRef = this.dialog.open(ConfirmationDialogComponent, {
|
|
1196
|
+
width: '400px',
|
|
1197
|
+
data: {
|
|
1198
|
+
title: 'Send invoice',
|
|
1199
|
+
message: 'Send this invoice to the customer’s email address?',
|
|
778
1200
|
},
|
|
779
1201
|
});
|
|
1202
|
+
dialogRef.afterClosed().subscribe((result) => {
|
|
1203
|
+
if (result) {
|
|
1204
|
+
this.invoiceService.sendInvoice(this.id).subscribe({
|
|
1205
|
+
next: (res) => {
|
|
1206
|
+
this.invoice.update((inv) => {
|
|
1207
|
+
if (!inv)
|
|
1208
|
+
return inv;
|
|
1209
|
+
inv.status = InvoiceStatus.SENT;
|
|
1210
|
+
return inv;
|
|
1211
|
+
});
|
|
1212
|
+
this.snackBarService.open('Invoice sent successfully.');
|
|
1213
|
+
},
|
|
1214
|
+
error: (e) => {
|
|
1215
|
+
this.snackBarService.open(e.message);
|
|
1216
|
+
},
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
1219
|
+
});
|
|
780
1220
|
}
|
|
781
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.
|
|
782
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.
|
|
1221
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceManageDetail, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
1222
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: InvoiceManageDetail, isStandalone: true, selector: "rolatech-invoice-manage-detail", usesInheritance: true, ngImport: i0, template: "@if (store.status() === 'loading') {\n<div class=\"flex justify-center py-16\">Loading invoice\u2026</div>\n} @else if (!store.invoice()) {\n<div class=\"flex justify-center py-16\">Invoice not found.</div>\n} @else { @if (store.invoice(); as inv) {\n\n<!-- Sticky header -->\n<div class=\"sticky top-0 z-10 border-b border-[--rt-border-color] bg-[--rt-background]\">\n <div class=\"px-4 py-3 flex items-start justify-between gap-4\">\n <div class=\"min-w-0\">\n <div class=\"flex items-center gap-2\">\n <h1 class=\"text-lg font-semibold truncate\">Invoice {{ inv.invoiceNumber || inv.id }}</h1>\n\n <span class=\"px-2 py-0.5 rounded-full text-xs font-semibold\" [class]=\"statusBadgeClass(inv.status)\">\n {{ inv.status }}\n </span>\n\n @if (store.dirty() ) {\n <span class=\"text-xs text-[--rt-text-secondary]\">\u2022 Unsaved changes</span>\n }\n </div>\n\n <div class=\"mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-sm text-[--rt-text-secondary]\">\n <span class=\"truncate\"> {{ inv.firstName }} {{ inv.lastName }} \u2022 {{ inv.email }} </span>\n <span>Created {{ inv.createdAt | date:'dd/MM/yyyy':'Europe/London' }}</span>\n @if (inv.dueAt) { <span>Due {{ inv.dueAt | date:'dd/MM/yyyy':'Europe/London' }}</span> }\n </div>\n </div>\n\n <!-- Desktop actions -->\n\n <div class=\"hidden lg:flex items-center gap-2 shrink-0\">\n @if (inv.pdfUrl) {\n <button mat-stroked-button type=\"button\" (click)=\"openPdf()\">Preview PDF</button>\n } @if (inv.status.toString() === 'ISSUED') {\n <button mat-flat-button type=\"button\" (click)=\"sendEmail()\">Send email</button>\n } @if (inv.status.toString() !== 'SENT' && inv.status.toString() !== 'PAID') {\n <button mat-flat-button (click)=\"edit()\">Edit</button>\n }\n </div>\n\n <!-- Mobile actions -->\n <div class=\"lg:hidden shrink-0\">\n <button mat-icon-button type=\"button\" [matMenuTriggerFor]=\"moreMenu\">\n <mat-icon>more_vert</mat-icon>\n </button>\n </div>\n </div>\n</div>\n\n<!-- Body -->\n<div class=\"p-4 space-y-4\">\n <div class=\"grid grid-cols-1 lg:grid-cols-3 gap-4\">\n <!-- Left column -->\n <div class=\"grid lg:col-span-2 space-y-4\">\n <!-- Customer -->\n <div class=\"rounded-xl border border-[--rt-border-color] bg-[--rt-background] p-4\">\n <div class=\"flex items-center justify-between\">\n <div>\n <div class=\"text-sm font-semibold\">Customer</div>\n <div class=\"text-sm text-[--rt-text-secondary]\">{{ inv.firstName }} {{ inv.lastName }} \u2022 {{ inv.email }}</div>\n </div>\n </div>\n\n <div class=\"mt-3 grid grid-cols-1 sm:grid-cols-2 gap-3 text-sm\">\n <div>\n <div class=\"text-xs text-[--rt-text-secondary]\">Phone</div>\n <div class=\"font-medium\">{{ inv.phone || '\u2014' }}</div>\n </div>\n <div>\n <div class=\"text-xs text-[--rt-text-secondary]\">Currency</div>\n <div class=\"font-medium\">{{ inv.currency }}</div>\n </div>\n </div>\n </div>\n\n <!-- Lines -->\n\n @if (inv.status.toString() !== 'SENT' && inv.status.toString() !== 'PAID') {\n <rolatech-invoice-manage-line></rolatech-invoice-manage-line>\n } @else {\n <rolatech-invoice-lines [lines]=\"store.lines()\"></rolatech-invoice-lines>\n }\n\n <!-- Notes (optional) -->\n @if (inv.note) {\n <div class=\"rounded-xl border border-[--rt-border-color] p-4\">\n <div class=\"text-sm font-semibold\">Notes</div>\n <div class=\"mt-2 text-sm text-[--rt-text-secondary] whitespace-pre-wrap\">{{ inv.note }}</div>\n </div>\n }\n </div>\n\n <!-- Right rail -->\n <div class=\"space-y-4\">\n <div class=\"rounded-xl border border-[--rt-border-color] bg-[--rt-background] p-4\">\n <div class=\"text-sm font-semibold\">Summary</div>\n\n @if (store.totals(); as t) {\n <div class=\"mt-3 space-y-2 text-sm\">\n <div class=\"flex justify-between\">\n <span class=\"text-[--rt-text-secondary]\">Subtotal</span>\n <span class=\"font-medium\">{{ t.subtotal | price }}</span>\n </div>\n\n <div class=\"flex justify-between\">\n <span class=\"text-[--rt-text-secondary]\">VAT</span>\n <span class=\"font-medium\">{{ t.vatTotal | price }}</span>\n </div>\n\n @if (inv.holdingDepositApplied) {\n <div class=\"flex justify-between\">\n <span class=\"text-[--rt-text-secondary]\">Credit</span>\n <span class=\"font-medium\">-{{ inv.holdingDepositApplied | price }}</span>\n </div>\n }\n\n <div class=\"pt-2 mt-2 border-t border-[--rt-border-color] flex justify-between text-base\">\n <span class=\"font-semibold\">Total</span>\n <span class=\"font-semibold\">{{ t.total | price }}</span>\n </div>\n </div>\n }\n </div>\n <div class=\"rounded-xl border border-[--rt-border-color] bg-[--rt-background] p-4\">\n <div class=\"text-sm font-semibold\">Payment</div>\n <div class=\"mt-2 text-sm text-[--rt-text-secondary]\">\n Status:\n <span class=\"font-medium text-[--rt-text-primary]\">{{ inv.status.toString() === 'PAID' ? 'Paid': 'Unpaid' }}</span>\n </div>\n @if (inv.paidAt) {\n <div class=\"mt-1 text-sm text-[--rt-text-secondary]\">\n Paid: <span class=\"font-medium text-[--rt-text-primary]\">{{ inv.paidAt | date:'dd/MM/yyyy':'Europe/London' }}</span>\n </div>\n }\n </div>\n </div>\n </div>\n</div>\n\n<mat-menu #moreMenu=\"matMenu\" xPosition=\"after\" class=\"divide-y divide-light-900\">\n @if (inv.pdfUrl) {\n <button mat-menu-item type=\"button\" (click)=\"openPdf()\">Preview PDF</button>\n } @if (inv.status.toString() === 'ISSUED') {\n <button mat-menu-item type=\"button\" (click)=\"sendEmail()\">Send email</button>\n } @if (inv.status.toString() !== 'SENT' && inv.status.toString() !== 'PAID') {\n <button mat-menu-item type=\"button\" (click)=\"edit()\">Edit</button>\n\n }\n</mat-menu>\n} }\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MaterialModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i3$1.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i3$1.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i3$1.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: InvoiceLines, selector: "rolatech-invoice-lines", inputs: ["lines"] }, { kind: "component", type: InvoiceManageLine, selector: "rolatech-invoice-manage-line" }, { kind: "pipe", type: i1.DatePipe, name: "date" }, { kind: "pipe", type: PricePipe, name: "price" }] }); }
|
|
783
1223
|
}
|
|
784
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
1224
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceManageDetail, decorators: [{
|
|
785
1225
|
type: Component,
|
|
786
|
-
args: [{ selector: 'rolatech-invoice-manage-detail', imports: [
|
|
787
|
-
CommonModule,
|
|
788
|
-
RouterLink,
|
|
789
|
-
MaterialModule,
|
|
790
|
-
MatMenuModule,
|
|
791
|
-
InvoiceSummary,
|
|
792
|
-
InvoiceLines,
|
|
793
|
-
InvoiceUser,
|
|
794
|
-
ToolbarComponent,
|
|
795
|
-
], template: "<!-- Content -->\n@if (loading()) {\n<div class=\"flex justify-center py-16\">Loading invoice\u2026</div>\n} @else if (!invoice()) {\n<div class=\"flex justify-center py-16\">Invoice not found.</div>\n} @else() { @if (invoice(); as invoice) {\n<rolatech-toolbar [title]=\"status[invoice.status]\" large link=\"../\">\n <div class=\"hidden items-center gap-2 lg:flex\">\n @if (invoice.pdfUrl) {\n <button mat-stroked-button (click)=\"openPdf()\">Preview PDF</button>\n }\n <button mat-flat-button>Send Email</button>\n <button mat-flat-button (click)=\"edit()\">Edit</button>\n </div>\n <div class=\"block lg:hidden\">\n <button mat-icon-button [matMenuTriggerFor]=\"moreMenu\">\n <mat-icon>more_vert</mat-icon>\n </button>\n </div>\n</rolatech-toolbar>\n<div class=\"px-4\">\n <!-- Breadcrumb / header -->\n <div class=\"flex items-start justify-between gap-4\">\n <div class=\"space-y-1\">\n <nav class=\"flex items-center gap-1\">\n <a routerLink=\"/invoices\" class=\"hover:underline hover:cursor-pointer\">Invoices</a>\n <span>/</span>\n <span>{{invoice?.invoiceNumber ?? invoice!.id}}</span>\n </nav>\n\n <div class=\"flex items-center gap-2 text-sm\">\n <span>Created: {{ invoice.createdAt | date:'dd/MM/yyyy':'Europe/London'}}</span>\n </div>\n </div>\n </div>\n</div>\n<div class=\"p-4 lg:p-4 space-y-6\">\n <div class=\"grid grid-cols-1 lg:grid-cols-3 gap-4\">\n <div class=\"grid lg:col-span-2 space-y-4\">\n <rolatech-invoice-user\n [firstName]=\"invoice.firstName\"\n [lastName]=\"invoice.lastName\"\n [email]=\"invoice.email\"\n [phone]=\"invoice.phone\"\n ></rolatech-invoice-user>\n @if (invoice?.lines; as lines) {\n <rolatech-invoice-lines [lines]=\"lines\"></rolatech-invoice-lines>\n }\n </div>\n <div class=\"space-y-4\">\n <rolatech-invoice-summary\n [tax]=\"invoice.vatTotal\"\n [credit]=\"invoice.holdingDepositApplied\"\n [subtotal]=\"invoice.subtotal\"\n [total]=\"invoice.total\"\n ></rolatech-invoice-summary>\n </div>\n </div>\n</div>\n<mat-menu #moreMenu=\"matMenu\" xPosition=\"after\" class=\"divide-y divide-light-900\">\n @if (invoice?.pdfUrl) {\n <button mat-menu-item (click)=\"openPdf()\">Preview PDF</button>\n }\n <button mat-menu-item>Send Email</button>\n <button mat-menu-item (click)=\"edit()\">Edit</button>\n</mat-menu>\n\n} }\n" }]
|
|
1226
|
+
args: [{ selector: 'rolatech-invoice-manage-detail', standalone: true, imports: [CommonModule, MaterialModule, MatMenuModule, InvoiceLines, InvoiceManageLine, PricePipe], template: "@if (store.status() === 'loading') {\n<div class=\"flex justify-center py-16\">Loading invoice\u2026</div>\n} @else if (!store.invoice()) {\n<div class=\"flex justify-center py-16\">Invoice not found.</div>\n} @else { @if (store.invoice(); as inv) {\n\n<!-- Sticky header -->\n<div class=\"sticky top-0 z-10 border-b border-[--rt-border-color] bg-[--rt-background]\">\n <div class=\"px-4 py-3 flex items-start justify-between gap-4\">\n <div class=\"min-w-0\">\n <div class=\"flex items-center gap-2\">\n <h1 class=\"text-lg font-semibold truncate\">Invoice {{ inv.invoiceNumber || inv.id }}</h1>\n\n <span class=\"px-2 py-0.5 rounded-full text-xs font-semibold\" [class]=\"statusBadgeClass(inv.status)\">\n {{ inv.status }}\n </span>\n\n @if (store.dirty() ) {\n <span class=\"text-xs text-[--rt-text-secondary]\">\u2022 Unsaved changes</span>\n }\n </div>\n\n <div class=\"mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-sm text-[--rt-text-secondary]\">\n <span class=\"truncate\"> {{ inv.firstName }} {{ inv.lastName }} \u2022 {{ inv.email }} </span>\n <span>Created {{ inv.createdAt | date:'dd/MM/yyyy':'Europe/London' }}</span>\n @if (inv.dueAt) { <span>Due {{ inv.dueAt | date:'dd/MM/yyyy':'Europe/London' }}</span> }\n </div>\n </div>\n\n <!-- Desktop actions -->\n\n <div class=\"hidden lg:flex items-center gap-2 shrink-0\">\n @if (inv.pdfUrl) {\n <button mat-stroked-button type=\"button\" (click)=\"openPdf()\">Preview PDF</button>\n } @if (inv.status.toString() === 'ISSUED') {\n <button mat-flat-button type=\"button\" (click)=\"sendEmail()\">Send email</button>\n } @if (inv.status.toString() !== 'SENT' && inv.status.toString() !== 'PAID') {\n <button mat-flat-button (click)=\"edit()\">Edit</button>\n }\n </div>\n\n <!-- Mobile actions -->\n <div class=\"lg:hidden shrink-0\">\n <button mat-icon-button type=\"button\" [matMenuTriggerFor]=\"moreMenu\">\n <mat-icon>more_vert</mat-icon>\n </button>\n </div>\n </div>\n</div>\n\n<!-- Body -->\n<div class=\"p-4 space-y-4\">\n <div class=\"grid grid-cols-1 lg:grid-cols-3 gap-4\">\n <!-- Left column -->\n <div class=\"grid lg:col-span-2 space-y-4\">\n <!-- Customer -->\n <div class=\"rounded-xl border border-[--rt-border-color] bg-[--rt-background] p-4\">\n <div class=\"flex items-center justify-between\">\n <div>\n <div class=\"text-sm font-semibold\">Customer</div>\n <div class=\"text-sm text-[--rt-text-secondary]\">{{ inv.firstName }} {{ inv.lastName }} \u2022 {{ inv.email }}</div>\n </div>\n </div>\n\n <div class=\"mt-3 grid grid-cols-1 sm:grid-cols-2 gap-3 text-sm\">\n <div>\n <div class=\"text-xs text-[--rt-text-secondary]\">Phone</div>\n <div class=\"font-medium\">{{ inv.phone || '\u2014' }}</div>\n </div>\n <div>\n <div class=\"text-xs text-[--rt-text-secondary]\">Currency</div>\n <div class=\"font-medium\">{{ inv.currency }}</div>\n </div>\n </div>\n </div>\n\n <!-- Lines -->\n\n @if (inv.status.toString() !== 'SENT' && inv.status.toString() !== 'PAID') {\n <rolatech-invoice-manage-line></rolatech-invoice-manage-line>\n } @else {\n <rolatech-invoice-lines [lines]=\"store.lines()\"></rolatech-invoice-lines>\n }\n\n <!-- Notes (optional) -->\n @if (inv.note) {\n <div class=\"rounded-xl border border-[--rt-border-color] p-4\">\n <div class=\"text-sm font-semibold\">Notes</div>\n <div class=\"mt-2 text-sm text-[--rt-text-secondary] whitespace-pre-wrap\">{{ inv.note }}</div>\n </div>\n }\n </div>\n\n <!-- Right rail -->\n <div class=\"space-y-4\">\n <div class=\"rounded-xl border border-[--rt-border-color] bg-[--rt-background] p-4\">\n <div class=\"text-sm font-semibold\">Summary</div>\n\n @if (store.totals(); as t) {\n <div class=\"mt-3 space-y-2 text-sm\">\n <div class=\"flex justify-between\">\n <span class=\"text-[--rt-text-secondary]\">Subtotal</span>\n <span class=\"font-medium\">{{ t.subtotal | price }}</span>\n </div>\n\n <div class=\"flex justify-between\">\n <span class=\"text-[--rt-text-secondary]\">VAT</span>\n <span class=\"font-medium\">{{ t.vatTotal | price }}</span>\n </div>\n\n @if (inv.holdingDepositApplied) {\n <div class=\"flex justify-between\">\n <span class=\"text-[--rt-text-secondary]\">Credit</span>\n <span class=\"font-medium\">-{{ inv.holdingDepositApplied | price }}</span>\n </div>\n }\n\n <div class=\"pt-2 mt-2 border-t border-[--rt-border-color] flex justify-between text-base\">\n <span class=\"font-semibold\">Total</span>\n <span class=\"font-semibold\">{{ t.total | price }}</span>\n </div>\n </div>\n }\n </div>\n <div class=\"rounded-xl border border-[--rt-border-color] bg-[--rt-background] p-4\">\n <div class=\"text-sm font-semibold\">Payment</div>\n <div class=\"mt-2 text-sm text-[--rt-text-secondary]\">\n Status:\n <span class=\"font-medium text-[--rt-text-primary]\">{{ inv.status.toString() === 'PAID' ? 'Paid': 'Unpaid' }}</span>\n </div>\n @if (inv.paidAt) {\n <div class=\"mt-1 text-sm text-[--rt-text-secondary]\">\n Paid: <span class=\"font-medium text-[--rt-text-primary]\">{{ inv.paidAt | date:'dd/MM/yyyy':'Europe/London' }}</span>\n </div>\n }\n </div>\n </div>\n </div>\n</div>\n\n<mat-menu #moreMenu=\"matMenu\" xPosition=\"after\" class=\"divide-y divide-light-900\">\n @if (inv.pdfUrl) {\n <button mat-menu-item type=\"button\" (click)=\"openPdf()\">Preview PDF</button>\n } @if (inv.status.toString() === 'ISSUED') {\n <button mat-menu-item type=\"button\" (click)=\"sendEmail()\">Send email</button>\n } @if (inv.status.toString() !== 'SENT' && inv.status.toString() !== 'PAID') {\n <button mat-menu-item type=\"button\" (click)=\"edit()\">Edit</button>\n\n }\n</mat-menu>\n} }\n" }]
|
|
796
1227
|
}] });
|
|
797
1228
|
|
|
798
1229
|
class InvoiceManageDownload {
|
|
799
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.
|
|
800
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.
|
|
1230
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceManageDownload, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1231
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.6", type: InvoiceManageDownload, isStandalone: true, selector: "rolatech-invoice-manage-download", ngImport: i0, template: "<p>invoice-manage-download works!</p>\n", styles: [""] }); }
|
|
801
1232
|
}
|
|
802
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
1233
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InvoiceManageDownload, decorators: [{
|
|
803
1234
|
type: Component,
|
|
804
1235
|
args: [{ selector: 'rolatech-invoice-manage-download', imports: [], template: "<p>invoice-manage-download works!</p>\n" }]
|
|
805
1236
|
}] });
|
|
@@ -955,10 +1386,10 @@ class AgentInvoiceIndex extends BaseComponent {
|
|
|
955
1386
|
return 'bg-gray-100 text-gray-700';
|
|
956
1387
|
}
|
|
957
1388
|
}
|
|
958
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.
|
|
959
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.
|
|
1389
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: AgentInvoiceIndex, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
1390
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: AgentInvoiceIndex, isStandalone: true, selector: "rolatech-agent-invoice-index", usesInheritance: true, ngImport: i0, template: "<rolatech-container>\n <rolatech-toolbar title=\"Invoices\" large>\n <button mat-button (click)=\"filter = !filter\">\n <span>Filter</span>\n <mat-icon>tune</mat-icon>\n </button>\n </rolatech-toolbar>\n <rolatech-filter>\n <div class=\"collapsed\" [class.expanded]=\"filter\">\n <div\n class=\"min-w-[256px] md:min-w-[320px] px-3 h-full flex flex-row md:flex-col md:h-full items-center md:items-start shadow-inner shadow-light-400 md:shadow-none overflow-x-scroll overflow-y-hidden scrollbar-hide whitespace-pre\"\n >\n <div class=\"flex items-center gap-3 mt-2\">\n <mat-form-field appearance=\"fill\" subscriptSizing=\"dynamic\">\n <mat-select name=\"type\" placeholder=\"Type\" [(ngModel)]=\"filterOptions.type\">\n @for (type of invoiceType | keyvalue; track type) {\n <mat-option [value]=\"type.key\"> {{ type.value }} </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field subscriptSizing=\"dynamic\">\n <mat-select [compareWith]=\"statusCompareFn\" placeholder=\"Status\" [(ngModel)]=\"filterOptions.status\">\n @for (status of invoiceStatus | keyvalue; track status) {\n <mat-option [value]=\"status.key\"> {{ status.value }} </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <div>\n <button mat-flat-button (click)=\"find()\">Search</button>\n <button mat-stroked-button (click)=\"resetFilter()\" class=\"ml-3\">Reset</button>\n </div>\n </div>\n </div>\n </div>\n </rolatech-filter>\n <rolatech-tabs [select]=\"select\">\n @for (item of links; track item) { @if (item.status) {\n <rolatech-tab [label]=\"item.name\" routerLink=\"./\" [queryParams]=\"{ status: item.status }\"></rolatech-tab>\n } @else {\n <rolatech-tab [label]=\"item.name\" routerLink=\"./\"></rolatech-tab>\n } }\n </rolatech-tabs>\n <rolatech-list>\n @if (invoices()) {\n <rolatech-invoice-header></rolatech-invoice-header>\n @for (item of invoices(); track item) {\n <rolatech-invoice-item [routerLink]=\"['./', item.id]\" [invoice]=\"item\"></rolatech-invoice-item>\n } } @else {\n <rolatech-empty></rolatech-empty>\n }\n </rolatech-list>\n <mat-paginator\n #paginator\n [length]=\"length\"\n [pageSize]=\"pageSize\"\n [pageIndex]=\"pageIndex()\"\n [pageSizeOptions]=\"pageSizeOptions\"\n (page)=\"onPage($event)\"\n hidePageSize\n showFirstLastButtons\n >\n </mat-paginator>\n</rolatech-container>\n", styles: [".collapsed{max-height:0;overflow:hidden;transition:max-height .5s cubic-bezier(.4,0,.2,1)}.expanded{max-height:1000px}\n"], dependencies: [{ kind: "component", type: ContainerComponent, selector: "rolatech-container" }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: TabsComponent, selector: "rolatech-tabs", inputs: ["select", "loading", "block"], outputs: ["selectChange"] }, { kind: "component", type: TabComponent, selector: "rolatech-tab", inputs: ["label"] }, { kind: "component", type: ToolbarComponent, selector: "rolatech-toolbar", inputs: ["title", "subtitle", "back", "link", "large", "divider"] }, { kind: "component", type: ListComponent, selector: "rolatech-list" }, { kind: "component", type: InvoiceItem, selector: "rolatech-invoice-item", inputs: ["invoice"], outputs: ["download"] }, { kind: "component", type: EmptyComponent, selector: "rolatech-empty" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "ngmodule", type: MatOptionModule }, { kind: "component", type: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i7.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: FilterComponent, selector: "rolatech-filter" }, { kind: "component", type: InvoiceHeader, selector: "rolatech-invoice-header" }, { kind: "ngmodule", type: MatPaginatorModule }, { kind: "component", type: i8.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "pipe", type: KeyValuePipe, name: "keyvalue" }] }); }
|
|
960
1391
|
}
|
|
961
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
1392
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: AgentInvoiceIndex, decorators: [{
|
|
962
1393
|
type: Component,
|
|
963
1394
|
args: [{ selector: 'rolatech-agent-invoice-index', imports: [
|
|
964
1395
|
ContainerComponent,
|
|
@@ -1044,10 +1475,10 @@ class AgentInvoiceDetail extends BaseComponent {
|
|
|
1044
1475
|
},
|
|
1045
1476
|
});
|
|
1046
1477
|
}
|
|
1047
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.
|
|
1048
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.
|
|
1478
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: AgentInvoiceDetail, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
1479
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: AgentInvoiceDetail, isStandalone: true, selector: "rolatech-agent-invoice-detail", usesInheritance: true, ngImport: i0, template: "<!-- Content -->\n@if (loading()) {\n<div class=\"flex justify-center py-16\">Loading invoice\u2026</div>\n} @else if (!invoice()) {\n<div class=\"flex justify-center py-16\">Invoice not found.</div>\n} @else() { @if (invoice(); as invoice) {\n<rolatech-toolbar [title]=\"status[invoice.status]\" large link=\"../\">\n <div class=\"hidden items-center gap-2 lg:flex\">\n @if (invoice.pdfUrl) {\n <button mat-stroked-button (click)=\"openPdf()\">Preview PDF</button>\n }\n <button mat-flat-button>Send Email</button>\n <button mat-flat-button (click)=\"edit()\">Edit</button>\n </div>\n <div class=\"block lg:hidden\">\n <button mat-icon-button [matMenuTriggerFor]=\"moreMenu\">\n <mat-icon>more_vert</mat-icon>\n </button>\n </div>\n</rolatech-toolbar>\n<div class=\"px-4\">\n <!-- Breadcrumb / header -->\n <div class=\"flex items-start justify-between gap-4\">\n <div class=\"space-y-1\">\n <nav class=\"flex items-center gap-1\">\n <a routerLink=\"/invoices\" class=\"hover:underline hover:cursor-pointer\">Invoices</a>\n <span>/</span>\n <span>{{invoice?.invoiceNumber ?? invoice!.id}}</span>\n </nav>\n\n <div class=\"flex items-center gap-2 text-sm\">\n <span>Created: {{ invoice.createdAt | date:'dd/MM/yyyy':'Europe/London'}}</span>\n </div>\n </div>\n </div>\n</div>\n<div class=\"p-4\">\n <div class=\"grid grid-cols-1 lg:grid-cols-3 gap-4\">\n <div class=\"grid lg:col-span-2 space-y-4\">\n @if (invoice.status.toString() === 'PAID') {\n <rolatech-invoice-user\n [firstName]=\"invoice.firstName\"\n [lastName]=\"invoice.lastName\"\n [email]=\"invoice.email\"\n [phone]=\"invoice.phone\"\n ></rolatech-invoice-user>\n <rolatech-invoice-lines></rolatech-invoice-lines>\n\n } @if (invoice?.lines; as lines) {\n <!-- <rolatech-invoice-lines [lines]=\"lines\"></rolatech-invoice-lines> -->\n }\n </div>\n <div class=\"space-y-4\">\n <rolatech-invoice-summary\n [tax]=\"invoice.vatTotal\"\n [credit]=\"invoice.holdingDepositApplied\"\n [subtotal]=\"invoice.subtotal\"\n [total]=\"invoice.total\"\n ></rolatech-invoice-summary>\n </div>\n </div>\n</div>\n<mat-menu #moreMenu=\"matMenu\" xPosition=\"after\" class=\"divide-y divide-light-900\">\n @if (invoice?.pdfUrl) {\n <button mat-menu-item (click)=\"openPdf()\">Preview PDF</button>\n }\n <button mat-menu-item>Send Email</button>\n <button mat-menu-item (click)=\"edit()\">Edit</button>\n</mat-menu>\n\n} }\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i3$1.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i3$1.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i3$1.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: InvoiceSummary, selector: "rolatech-invoice-summary", inputs: ["tax", "credit", "subtotal", "total"] }, { kind: "component", type: InvoiceLines, selector: "rolatech-invoice-lines", inputs: ["lines"] }, { kind: "component", type: InvoiceUser, selector: "rolatech-invoice-user", inputs: ["firstName", "lastName", "email", "phone"] }, { kind: "component", type: ToolbarComponent, selector: "rolatech-toolbar", inputs: ["title", "subtitle", "back", "link", "large", "divider"] }, { kind: "pipe", type: i1.DatePipe, name: "date" }] }); }
|
|
1049
1480
|
}
|
|
1050
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
1481
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: AgentInvoiceDetail, decorators: [{
|
|
1051
1482
|
type: Component,
|
|
1052
1483
|
args: [{ selector: 'rolatech-agent-invoice-detail', imports: [
|
|
1053
1484
|
CommonModule,
|
|
@@ -1059,7 +1490,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImpor
|
|
|
1059
1490
|
InvoiceLines,
|
|
1060
1491
|
InvoiceUser,
|
|
1061
1492
|
ToolbarComponent,
|
|
1062
|
-
], template: "<!-- Content -->\n@if (loading()) {\n<div class=\"flex justify-center py-16\">Loading invoice\u2026</div>\n} @else if (!invoice()) {\n<div class=\"flex justify-center py-16\">Invoice not found.</div>\n} @else() { @if (invoice(); as invoice) {\n<rolatech-toolbar [title]=\"status[invoice.status]\" large link=\"../\">\n <div class=\"hidden items-center gap-2 lg:flex\">\n @if (invoice.pdfUrl) {\n <button mat-stroked-button (click)=\"openPdf()\">Preview PDF</button>\n }\n <button mat-flat-button>Send Email</button>\n <button mat-flat-button (click)=\"edit()\">Edit</button>\n </div>\n <div class=\"block lg:hidden\">\n <button mat-icon-button [matMenuTriggerFor]=\"moreMenu\">\n <mat-icon>more_vert</mat-icon>\n </button>\n </div>\n</rolatech-toolbar>\n<div class=\"px-4\">\n <!-- Breadcrumb / header -->\n <div class=\"flex items-start justify-between gap-4\">\n <div class=\"space-y-1\">\n <nav class=\"flex items-center gap-1\">\n <a routerLink=\"/invoices\" class=\"hover:underline hover:cursor-pointer\">Invoices</a>\n <span>/</span>\n <span>{{invoice?.invoiceNumber ?? invoice!.id}}</span>\n </nav>\n\n <div class=\"flex items-center gap-2 text-sm\">\n <span>Created: {{ invoice.createdAt | date:'dd/MM/yyyy':'Europe/London'}}</span>\n </div>\n </div>\n </div>\n</div>\n<div class=\"p-4\">\n <div class=\"grid grid-cols-1 lg:grid-cols-3 gap-4\">\n <div class=\"grid lg:col-span-2 space-y-4\">\n @if (invoice.status.toString() === 'PAID') {\n <rolatech-invoice-user\n [firstName]=\"invoice.firstName\"\n [lastName]=\"invoice.lastName\"\n [email]=\"invoice.email\"\n [phone]=\"invoice.phone\"\n ></rolatech-invoice-user>\n } @if (invoice?.lines; as lines) {\n <rolatech-invoice-lines [lines]=\"lines\"></rolatech-invoice-lines
|
|
1493
|
+
], template: "<!-- Content -->\n@if (loading()) {\n<div class=\"flex justify-center py-16\">Loading invoice\u2026</div>\n} @else if (!invoice()) {\n<div class=\"flex justify-center py-16\">Invoice not found.</div>\n} @else() { @if (invoice(); as invoice) {\n<rolatech-toolbar [title]=\"status[invoice.status]\" large link=\"../\">\n <div class=\"hidden items-center gap-2 lg:flex\">\n @if (invoice.pdfUrl) {\n <button mat-stroked-button (click)=\"openPdf()\">Preview PDF</button>\n }\n <button mat-flat-button>Send Email</button>\n <button mat-flat-button (click)=\"edit()\">Edit</button>\n </div>\n <div class=\"block lg:hidden\">\n <button mat-icon-button [matMenuTriggerFor]=\"moreMenu\">\n <mat-icon>more_vert</mat-icon>\n </button>\n </div>\n</rolatech-toolbar>\n<div class=\"px-4\">\n <!-- Breadcrumb / header -->\n <div class=\"flex items-start justify-between gap-4\">\n <div class=\"space-y-1\">\n <nav class=\"flex items-center gap-1\">\n <a routerLink=\"/invoices\" class=\"hover:underline hover:cursor-pointer\">Invoices</a>\n <span>/</span>\n <span>{{invoice?.invoiceNumber ?? invoice!.id}}</span>\n </nav>\n\n <div class=\"flex items-center gap-2 text-sm\">\n <span>Created: {{ invoice.createdAt | date:'dd/MM/yyyy':'Europe/London'}}</span>\n </div>\n </div>\n </div>\n</div>\n<div class=\"p-4\">\n <div class=\"grid grid-cols-1 lg:grid-cols-3 gap-4\">\n <div class=\"grid lg:col-span-2 space-y-4\">\n @if (invoice.status.toString() === 'PAID') {\n <rolatech-invoice-user\n [firstName]=\"invoice.firstName\"\n [lastName]=\"invoice.lastName\"\n [email]=\"invoice.email\"\n [phone]=\"invoice.phone\"\n ></rolatech-invoice-user>\n <rolatech-invoice-lines></rolatech-invoice-lines>\n\n } @if (invoice?.lines; as lines) {\n <!-- <rolatech-invoice-lines [lines]=\"lines\"></rolatech-invoice-lines> -->\n }\n </div>\n <div class=\"space-y-4\">\n <rolatech-invoice-summary\n [tax]=\"invoice.vatTotal\"\n [credit]=\"invoice.holdingDepositApplied\"\n [subtotal]=\"invoice.subtotal\"\n [total]=\"invoice.total\"\n ></rolatech-invoice-summary>\n </div>\n </div>\n</div>\n<mat-menu #moreMenu=\"matMenu\" xPosition=\"after\" class=\"divide-y divide-light-900\">\n @if (invoice?.pdfUrl) {\n <button mat-menu-item (click)=\"openPdf()\">Preview PDF</button>\n }\n <button mat-menu-item>Send Email</button>\n <button mat-menu-item (click)=\"edit()\">Edit</button>\n</mat-menu>\n\n} }\n" }]
|
|
1063
1494
|
}] });
|
|
1064
1495
|
|
|
1065
1496
|
const agentInvoiceRoutes = [
|