@rolatech/angular-billing 20.3.0-beta.4 → 20.3.1-beta.3

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,38 +1,36 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input, output, Component, ViewEncapsulation, inject, signal, model, computed, Injectable } from '@angular/core';
2
+ import { input, output, Component, ViewEncapsulation, inject, signal, computed, model, 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';
6
6
  import { FormsModule } from '@angular/forms';
7
7
  import * as i2 from '@angular/material/button';
8
8
  import { MatButtonModule } from '@angular/material/button';
9
- import * as i6 from '@angular/material/core';
10
- import { MatOptionModule, MAT_DATE_LOCALE, DateAdapter, MAT_DATE_FORMATS } from '@angular/material/core';
11
- import { MatDatepickerModule } from '@angular/material/datepicker';
12
9
  import * as i4 from '@angular/material/form-field';
13
10
  import { MatFormFieldModule } from '@angular/material/form-field';
14
11
  import * as i3 from '@angular/material/icon';
15
12
  import { MatIcon, MatIconModule } from '@angular/material/icon';
16
- import * as i5 from '@angular/material/input';
17
- import { MatInputModule } from '@angular/material/input';
18
- import * as i7 from '@angular/material/select';
13
+ import * as i5 from '@angular/material/select';
19
14
  import { MatSelectModule } from '@angular/material/select';
20
15
  import * as i1$1 from '@angular/router';
21
16
  import { RouterModule, RouterLink } from '@angular/router';
22
- import { Skeleton, BaseComponent, ContainerComponent, TabsComponent, TabComponent, ToolbarComponent, ListComponent, EmptyComponent, FilterComponent, EnumSelect, ConfirmationDialogComponent, MaterialModule } from '@rolatech/angular-components';
17
+ import { Skeleton, BaseComponent, PageCollectionShellComponent, ToolbarComponent, TabsComponent, TabComponent, EnumSelect, ConfirmationDialogComponent, MaterialModule } from '@rolatech/angular-components';
23
18
  import { PricePipe } from '@rolatech/angular-common';
24
19
  import { InvoiceService, PaymentService, EnumApiClient } from '@rolatech/angular-services';
25
- import { Title } from '@angular/platform-browser';
26
- import * as i8 from '@angular/material/paginator';
20
+ import * as i6 from '@angular/material/paginator';
27
21
  import { MatPaginatorModule } from '@angular/material/paginator';
28
22
  import { map, distinctUntilChanged, switchMap, finalize } from 'rxjs';
29
- import * as i9 from '@angular/material/card';
23
+ import * as i8 from '@angular/material/card';
30
24
  import { MatCardModule } from '@angular/material/card';
31
- import * as i8$1 from '@angular/material/divider';
25
+ import * as i7 from '@angular/material/divider';
32
26
  import { MatDividerModule } from '@angular/material/divider';
27
+ import * as i5$1 from '@angular/material/input';
28
+ import { MatInputModule } from '@angular/material/input';
33
29
  import * as i3$1 from '@angular/material/menu';
34
30
  import { MatMenuModule } from '@angular/material/menu';
35
31
  import { MomentDateAdapter } from '@angular/material-moment-adapter';
32
+ import { MatOptionModule, MAT_DATE_LOCALE, DateAdapter, MAT_DATE_FORMATS } from '@angular/material/core';
33
+ import { MatDatepickerModule } from '@angular/material/datepicker';
36
34
  import { finalize as finalize$1 } from 'rxjs/operators';
37
35
  import * as i1$3 from '@angular/cdk/drag-drop';
38
36
  import { DragDropModule } from '@angular/cdk/drag-drop';
@@ -79,29 +77,34 @@ class InvoiceItem {
79
77
  this.status = InvoiceStatus;
80
78
  this.download = output();
81
79
  }
80
+ customerName() {
81
+ return this.invoice().fullName || `${this.invoice().firstName} ${this.invoice().lastName}`.trim();
82
+ }
82
83
  onDownload() {
83
84
  this.download.emit();
84
85
  }
85
86
  statusBadgeClass(status) {
86
87
  switch (status.toString()) {
87
88
  case 'CREATED':
88
- return 'bg-yellow-100 text-yellow-800';
89
+ return 'invoice-item__status invoice-item__status--draft';
89
90
  case 'ISSUED':
90
- return 'bg-blue-100 text-blue-800';
91
+ case 'SENT':
92
+ case 'PARTIALLY_PAID':
93
+ return 'invoice-item__status invoice-item__status--issued';
91
94
  case 'PAID':
92
- return 'bg-emerald-100 text-emerald-800';
95
+ return 'invoice-item__status invoice-item__status--paid';
93
96
  case 'VOID':
94
- return 'bg-red-100 text-red-800';
97
+ return 'invoice-item__status invoice-item__status--void';
95
98
  default:
96
- return 'bg-gray-100 text-gray-700';
99
+ return 'invoice-item__status invoice-item__status--neutral';
97
100
  }
98
101
  }
99
102
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: InvoiceItem, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
100
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.1", 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" }] }); }
103
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", 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: "<article class=\"invoice-item\">\n <div class=\"invoice-item__top\">\n <div class=\"invoice-item__identity\">\n <span class=\"invoice-item__eyebrow\">Invoice</span>\n <h3 class=\"invoice-item__number\">{{ invoice().invoiceNumber || invoice().id }}</h3>\n <p class=\"invoice-item__customer\">{{ customerName() }}</p>\n </div>\n\n <div class=\"invoice-item__summary\">\n <span [ngClass]=\"statusBadgeClass(invoice().status)\">{{ status[invoice().status] }}</span>\n <strong class=\"invoice-item__total\">{{ invoice().total | price }}</strong>\n </div>\n </div>\n\n <div class=\"invoice-item__meta\">\n <div class=\"invoice-item__meta-card\">\n <span class=\"invoice-item__meta-label\">Created</span>\n <strong>{{ invoice().createdAt | date: 'dd/MM/yyyy':'Europe/London' }}</strong>\n </div>\n\n <div class=\"invoice-item__meta-card\">\n <span class=\"invoice-item__meta-label\">Due</span>\n <strong>\n @if (invoice().dueAt) {\n {{ invoice().dueAt | date: 'dd/MM/yyyy':'Europe/London' }}\n } @else {\n On receipt\n }\n </strong>\n </div>\n\n <div class=\"invoice-item__meta-card\">\n <span class=\"invoice-item__meta-label\">Type</span>\n <strong>{{ invoice().type }}</strong>\n </div>\n\n <button mat-stroked-button class=\"invoice-item__download\" (click)=\"onDownload(); $event.stopPropagation()\">\n <mat-icon>download</mat-icon>\n <span>Download</span>\n </button>\n </div>\n</article>\n", styles: [".scrollbar-hide::-webkit-scrollbar{display:none}.scrollbar-hide{-ms-overflow-style:none;scrollbar-width:none}:host{display:block;--rt-workspace-status-issued-surface: color-mix(in srgb, var(--rt-brand-color) 14%, transparent);--rt-workspace-status-issued-color: color-mix(in srgb, var(--rt-brand-color) 82%, var(--rt-text-primary) 18%);--rt-workspace-status-paid-surface: color-mix(in srgb, var(--rt-brand-color) 16%, var(--rt-base-background, #ffffff));--rt-workspace-status-paid-color: color-mix(in srgb, var(--rt-brand-color) 58%, var(--rt-text-primary) 42%)}.invoice-item{display:flex;flex-direction:column;gap:1rem;padding:1rem;border:1px solid var(--rt-border-color, rgba(15, 23, 42, .08));border-radius:1.35rem;background:color-mix(in srgb,var(--rt-raised-background, #ffffff) 96%,transparent);box-shadow:0 18px 40px -36px color-mix(in srgb,var(--rt-text-primary) 18%,transparent)}.invoice-item__top{display:flex;justify-content:space-between;gap:1rem;align-items:flex-start}.invoice-item__identity{display:flex;flex-direction:column;gap:.32rem}.invoice-item__eyebrow{display:inline-flex;align-self:flex-start;border-radius:9999px;padding:.28rem .62rem;background:color-mix(in srgb,var(--rt-brand-color) 12%,transparent);color:var(--rt-brand-color);font-size:.74rem;font-weight:700;letter-spacing:.05em;text-transform:uppercase}.invoice-item__number{margin:0;color:var(--rt-text-primary);font-size:1.08rem;font-weight:700}.invoice-item__customer{margin:0;color:var(--rt-text-secondary);line-height:1.5}.invoice-item__summary{display:flex;flex-direction:column;gap:.5rem;align-items:flex-end}.invoice-item__total{color:var(--rt-text-primary);font-size:1.08rem}.invoice-item__status{display:inline-flex;align-items:center;border-radius:9999px;padding:.42rem .78rem;font-size:.78rem;font-weight:700}.invoice-item__status--draft{background:color-mix(in srgb,var(--rt-brand-color) 10%,transparent);color:var(--rt-brand-color)}.invoice-item__status--issued{background:var(--rt-workspace-status-issued-surface);color:var(--rt-workspace-status-issued-color)}.invoice-item__status--paid{background:var(--rt-workspace-status-paid-surface);color:var(--rt-workspace-status-paid-color)}.invoice-item__status--void{background:color-mix(in srgb,var(--mat-sys-error, #b91c1c) 14%,transparent);color:var(--mat-sys-error, #b91c1c)}.invoice-item__status--neutral{background:color-mix(in srgb,var(--rt-10-percent-layer, rgba(15, 23, 42, .08)) 72%,transparent);color:var(--rt-text-secondary)}.invoice-item__meta{display:grid;gap:.85rem}.invoice-item__meta-card{display:flex;flex-direction:column;gap:.22rem;padding:.85rem .95rem;border-radius:1rem;background:color-mix(in srgb,var(--rt-10-percent-layer, rgba(15, 23, 42, .08)) 72%,transparent)}.invoice-item__meta-label{color:var(--rt-text-secondary);font-size:.82rem}.invoice-item__meta-card strong{color:var(--rt-text-primary);font-size:.96rem}.invoice-item__download{min-height:100%;border-radius:1rem}@media(min-width:900px){.invoice-item__meta{grid-template-columns:repeat(4,minmax(0,1fr));align-items:stretch}.invoice-item__download{justify-content:center}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { 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: 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" }] }); }
101
104
  }
102
105
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: InvoiceItem, decorators: [{
103
106
  type: Component,
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"] }]
107
+ args: [{ selector: 'rolatech-invoice-item', imports: [CommonModule, MatButtonModule, MatIcon, DatePipe, PricePipe], template: "<article class=\"invoice-item\">\n <div class=\"invoice-item__top\">\n <div class=\"invoice-item__identity\">\n <span class=\"invoice-item__eyebrow\">Invoice</span>\n <h3 class=\"invoice-item__number\">{{ invoice().invoiceNumber || invoice().id }}</h3>\n <p class=\"invoice-item__customer\">{{ customerName() }}</p>\n </div>\n\n <div class=\"invoice-item__summary\">\n <span [ngClass]=\"statusBadgeClass(invoice().status)\">{{ status[invoice().status] }}</span>\n <strong class=\"invoice-item__total\">{{ invoice().total | price }}</strong>\n </div>\n </div>\n\n <div class=\"invoice-item__meta\">\n <div class=\"invoice-item__meta-card\">\n <span class=\"invoice-item__meta-label\">Created</span>\n <strong>{{ invoice().createdAt | date: 'dd/MM/yyyy':'Europe/London' }}</strong>\n </div>\n\n <div class=\"invoice-item__meta-card\">\n <span class=\"invoice-item__meta-label\">Due</span>\n <strong>\n @if (invoice().dueAt) {\n {{ invoice().dueAt | date: 'dd/MM/yyyy':'Europe/London' }}\n } @else {\n On receipt\n }\n </strong>\n </div>\n\n <div class=\"invoice-item__meta-card\">\n <span class=\"invoice-item__meta-label\">Type</span>\n <strong>{{ invoice().type }}</strong>\n </div>\n\n <button mat-stroked-button class=\"invoice-item__download\" (click)=\"onDownload(); $event.stopPropagation()\">\n <mat-icon>download</mat-icon>\n <span>Download</span>\n </button>\n </div>\n</article>\n", styles: [".scrollbar-hide::-webkit-scrollbar{display:none}.scrollbar-hide{-ms-overflow-style:none;scrollbar-width:none}:host{display:block;--rt-workspace-status-issued-surface: color-mix(in srgb, var(--rt-brand-color) 14%, transparent);--rt-workspace-status-issued-color: color-mix(in srgb, var(--rt-brand-color) 82%, var(--rt-text-primary) 18%);--rt-workspace-status-paid-surface: color-mix(in srgb, var(--rt-brand-color) 16%, var(--rt-base-background, #ffffff));--rt-workspace-status-paid-color: color-mix(in srgb, var(--rt-brand-color) 58%, var(--rt-text-primary) 42%)}.invoice-item{display:flex;flex-direction:column;gap:1rem;padding:1rem;border:1px solid var(--rt-border-color, rgba(15, 23, 42, .08));border-radius:1.35rem;background:color-mix(in srgb,var(--rt-raised-background, #ffffff) 96%,transparent);box-shadow:0 18px 40px -36px color-mix(in srgb,var(--rt-text-primary) 18%,transparent)}.invoice-item__top{display:flex;justify-content:space-between;gap:1rem;align-items:flex-start}.invoice-item__identity{display:flex;flex-direction:column;gap:.32rem}.invoice-item__eyebrow{display:inline-flex;align-self:flex-start;border-radius:9999px;padding:.28rem .62rem;background:color-mix(in srgb,var(--rt-brand-color) 12%,transparent);color:var(--rt-brand-color);font-size:.74rem;font-weight:700;letter-spacing:.05em;text-transform:uppercase}.invoice-item__number{margin:0;color:var(--rt-text-primary);font-size:1.08rem;font-weight:700}.invoice-item__customer{margin:0;color:var(--rt-text-secondary);line-height:1.5}.invoice-item__summary{display:flex;flex-direction:column;gap:.5rem;align-items:flex-end}.invoice-item__total{color:var(--rt-text-primary);font-size:1.08rem}.invoice-item__status{display:inline-flex;align-items:center;border-radius:9999px;padding:.42rem .78rem;font-size:.78rem;font-weight:700}.invoice-item__status--draft{background:color-mix(in srgb,var(--rt-brand-color) 10%,transparent);color:var(--rt-brand-color)}.invoice-item__status--issued{background:var(--rt-workspace-status-issued-surface);color:var(--rt-workspace-status-issued-color)}.invoice-item__status--paid{background:var(--rt-workspace-status-paid-surface);color:var(--rt-workspace-status-paid-color)}.invoice-item__status--void{background:color-mix(in srgb,var(--mat-sys-error, #b91c1c) 14%,transparent);color:var(--mat-sys-error, #b91c1c)}.invoice-item__status--neutral{background:color-mix(in srgb,var(--rt-10-percent-layer, rgba(15, 23, 42, .08)) 72%,transparent);color:var(--rt-text-secondary)}.invoice-item__meta{display:grid;gap:.85rem}.invoice-item__meta-card{display:flex;flex-direction:column;gap:.22rem;padding:.85rem .95rem;border-radius:1rem;background:color-mix(in srgb,var(--rt-10-percent-layer, rgba(15, 23, 42, .08)) 72%,transparent)}.invoice-item__meta-label{color:var(--rt-text-secondary);font-size:.82rem}.invoice-item__meta-card strong{color:var(--rt-text-primary);font-size:.96rem}.invoice-item__download{min-height:100%;border-radius:1rem}@media(min-width:900px){.invoice-item__meta{grid-template-columns:repeat(4,minmax(0,1fr));align-items:stretch}.invoice-item__download{justify-content:center}}\n"] }]
105
108
  }], propDecorators: { invoice: [{ type: i0.Input, args: [{ isSignal: true, alias: "invoice", required: true }] }], download: [{ type: i0.Output, args: ["download"] }] } });
106
109
 
107
110
  class InvoiceManageItem {
@@ -116,24 +119,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImpor
116
119
  args: [{ selector: 'rolatech-invoice-manage-item', imports: [], template: "<p>invoice-manage-item works!</p>\n<div>{{invoice().id}}</div>\n" }]
117
120
  }], propDecorators: { invoice: [{ type: i0.Input, args: [{ isSignal: true, alias: "invoice", required: true }] }] } });
118
121
 
119
- class InvoiceHeader {
120
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: InvoiceHeader, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
121
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.1", 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: [""] }); }
122
- }
123
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: InvoiceHeader, decorators: [{
124
- type: Component,
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" }]
126
- }] });
127
-
128
- class InvoiceHeaderSkeleton {
129
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: InvoiceHeaderSkeleton, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
130
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.1", 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 }); }
131
- }
132
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: InvoiceHeaderSkeleton, decorators: [{
133
- type: Component,
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" }]
135
- }] });
136
-
137
122
  class InvoiceItemSkeleton {
138
123
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: InvoiceItemSkeleton, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
139
124
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.1", 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 }); }
@@ -147,7 +132,6 @@ class InvoiceIndexComponent extends BaseComponent {
147
132
  constructor() {
148
133
  super(...arguments);
149
134
  this.invoiceService = inject(InvoiceService);
150
- this.title = inject(Title);
151
135
  this.filterOptions = {
152
136
  type: '',
153
137
  status: '',
@@ -183,6 +167,21 @@ class InvoiceIndexComponent extends BaseComponent {
183
167
  this.pageSize = 15;
184
168
  this.pageSizeOptions = [5, 10, 25, 100];
185
169
  this.pageIndex = signal(0, ...(ngDevMode ? [{ debugName: "pageIndex" }] : []));
170
+ this.pageMeta = {
171
+ baseLink: this.readRouteData('invoiceBaseLink', '/invoices'),
172
+ eyebrow: this.readRouteData('invoiceEyebrow', 'Billing workspace'),
173
+ title: this.readRouteData('invoiceCollectionTitle', 'Invoices'),
174
+ description: this.readRouteData('invoiceCollectionDescription', 'Track invoice status, total value, and due dates in one calm workspace.'),
175
+ };
176
+ this.stats = computed(() => {
177
+ const invoices = this.invoices();
178
+ return [
179
+ { label: 'Invoices', value: this.length || invoices.length, hint: 'Every invoice in the current scope' },
180
+ { label: 'Created', value: invoices.filter((item) => item.status === InvoiceStatus.CREATED).length, hint: 'Drafted and waiting to move forward' },
181
+ { label: 'Issued', value: invoices.filter((item) => item.status === InvoiceStatus.ISSUED || item.status === InvoiceStatus.SENT).length, hint: 'Sent and awaiting payment' },
182
+ { label: 'Paid', value: invoices.filter((item) => item.status === InvoiceStatus.PAID).length, hint: 'Successfully settled' },
183
+ ];
184
+ }, ...(ngDevMode ? [{ debugName: "stats" }] : []));
186
185
  }
187
186
  ngOnInit() {
188
187
  const sub = this.route.queryParamMap
@@ -190,14 +189,25 @@ class InvoiceIndexComponent extends BaseComponent {
190
189
  const page = p.get('page') ? Number(p.get('page')) : 1;
191
190
  this.pageIndex.set(Math.max(page - 1, 0));
192
191
  const status = p.get('status') || undefined;
193
- let filter = '';
192
+ const type = p.get('type') || undefined;
193
+ this.filterOptions = {
194
+ type: type || '',
195
+ status: status || '',
196
+ };
197
+ const filters = [];
194
198
  if (status) {
195
199
  this.select = this.links.findIndex((item) => item.status === status);
196
- filter = `status:${status?.toUpperCase()}`;
200
+ filters.push(`status:${status.toUpperCase()}`);
201
+ }
202
+ else {
203
+ this.select = 0;
204
+ }
205
+ if (type) {
206
+ filters.push(`type:${type}`);
197
207
  }
198
208
  return {
199
209
  page,
200
- filter,
210
+ filter: filters.join(','),
201
211
  limit: p.get('limit') ? Number(p.get('limit')) : 15,
202
212
  sort: p.get('sort') || undefined,
203
213
  };
@@ -223,33 +233,30 @@ class InvoiceIndexComponent extends BaseComponent {
223
233
  this.destroyRef.onDestroy(() => sub.unsubscribe());
224
234
  }
225
235
  find() {
226
- const options = {
227
- sort: 'updatedAt desc',
228
- };
229
- const filterString = this.convertFilterOptions(this.filterOptions);
230
- if (filterString) {
231
- options['filter'] = filterString;
232
- }
233
- this.invoiceService.me(options).subscribe({
234
- next: (res) => {
235
- this.invoices.set(res.data);
236
+ this.router.navigate([], {
237
+ queryParams: {
238
+ type: this.filterOptions.type || null,
239
+ status: this.filterOptions.status || null,
240
+ page: 1,
236
241
  },
242
+ queryParamsHandling: 'merge',
243
+ replaceUrl: true,
237
244
  });
238
245
  }
239
246
  resetFilter() {
240
247
  this.filterOptions = {
241
248
  type: '',
249
+ status: '',
242
250
  };
243
- this.filter = false;
244
- this.find();
245
- }
246
- convertFilterOptions(jsonObj) {
247
- return Object.entries(jsonObj)
248
- .filter(([key, value]) => value !== '' && value !== undefined)
249
- .map(([key, value]) => {
250
- return `${key}:${value}`;
251
- })
252
- .join(',');
251
+ this.router.navigate([], {
252
+ queryParams: {
253
+ type: null,
254
+ status: null,
255
+ page: 1,
256
+ },
257
+ queryParamsHandling: 'merge',
258
+ replaceUrl: true,
259
+ });
253
260
  }
254
261
  statusCompareFn(t1, t2) {
255
262
  return t1 === t2;
@@ -261,36 +268,33 @@ class InvoiceIndexComponent extends BaseComponent {
261
268
  replaceUrl: true, // optional: avoid stacking history on every page click
262
269
  });
263
270
  }
271
+ readRouteData(key, fallback) {
272
+ for (const snapshot of [...this.route.snapshot.pathFromRoot].reverse()) {
273
+ const value = snapshot.data?.[key];
274
+ if (typeof value === 'string' && value.length > 0) {
275
+ return value;
276
+ }
277
+ }
278
+ return fallback;
279
+ }
264
280
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: InvoiceIndexComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
265
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", 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 class=\"cursor-pointer\" [label]=\"item.name\" routerLink=\"./\" [queryParams]=\"{ status: item.status }\"></rolatech-tab>\n } @else {\n <rolatech-tab class=\"cursor-pointer\" [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 class=\"cursor-pointer\" [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", "mode"], outputs: ["selectChange"] }, { kind: "component", type: TabComponent, selector: "rolatech-tab", inputs: ["label"], outputs: ["selectRequested"] }, { 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 }); }
281
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: InvoiceIndexComponent, isStandalone: true, selector: "rolatech-invoice-index", usesInheritance: true, ngImport: i0, template: "<rolatech-page-collection-shell [eyebrow]=\"pageMeta.eyebrow\" [title]=\"pageMeta.title\" [subtitle]=\"pageMeta.description\">\n <div page-shell-header-meta class=\"invoice-index-page__stats\">\n @for (stat of stats(); track stat.label) {\n <article class=\"invoice-index-page__stat\">\n <span class=\"invoice-index-page__stat-value\">{{ stat.value }}</span>\n <span class=\"invoice-index-page__stat-label\">{{ stat.label }}</span>\n <span class=\"invoice-index-page__stat-hint\">{{ stat.hint }}</span>\n </article>\n }\n </div>\n\n <section class=\"invoice-index-page__filters\">\n <nav class=\"invoice-index-page__status-nav\" aria-label=\"Invoice status filters\">\n @for (item of links; track item.name; let index = $index) {\n <a\n class=\"invoice-index-page__status-link\"\n [class.invoice-index-page__status-link--active]=\"select === index\"\n routerLink=\"./\"\n [queryParams]=\"item.status ? { status: item.status } : {}\"\n >\n {{ item.name }}\n </a>\n }\n </nav>\n\n <div class=\"invoice-index-page__filter-grid\">\n <mat-form-field appearance=\"fill\">\n <mat-label>Type</mat-label>\n <mat-select name=\"type\" [(ngModel)]=\"filterOptions.type\">\n @for (type of invoiceType | keyvalue; track type.key) {\n <mat-option [value]=\"type.key\">{{ type.value }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-form-field appearance=\"fill\">\n <mat-label>Status</mat-label>\n <mat-select [compareWith]=\"statusCompareFn\" [(ngModel)]=\"filterOptions.status\">\n @for (status of invoiceStatus | keyvalue; track status.key) {\n <mat-option [value]=\"status.key\">{{ status.value }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <div class=\"invoice-index-page__filter-actions\">\n <button mat-flat-button (click)=\"find()\">Apply filters</button>\n <button mat-stroked-button (click)=\"resetFilter()\">Reset</button>\n </div>\n </div>\n </section>\n\n <section class=\"invoice-index-page__list\">\n @if (loading) {\n @for (dummy of [0, 1, 2, 3]; track dummy) {\n <rolatech-invoice-item-skeleton></rolatech-invoice-item-skeleton>\n }\n } @else if (invoices().length > 0) {\n @for (item of invoices(); track item.id) {\n <rolatech-invoice-item class=\"cursor-pointer\" [routerLink]=\"['./', item.id]\" [invoice]=\"item\"></rolatech-invoice-item>\n }\n } @else {\n <div class=\"invoice-index-page__empty\">\n <h3>No invoices yet</h3>\n <p>The invoices for this workspace will appear here once billing is created.</p>\n </div>\n }\n\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 </section>\n</rolatech-page-collection-shell>\n", styles: [".collapsed{max-height:0;overflow:hidden;transition:max-height .5s cubic-bezier(.4,0,.2,1)}.expanded{max-height:1000px}:host{display:block}.invoice-index-page__filters,.invoice-index-page__list{border:1px solid var(--rt-border-color, rgba(15, 23, 42, .08));border-radius:1.5rem;background:color-mix(in srgb,var(--rt-raised-background, #ffffff) 96%,transparent);box-shadow:0 20px 48px -42px color-mix(in srgb,var(--rt-text-primary) 22%,transparent)}.invoice-index-page__stats{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:.85rem}.invoice-index-page__stat{display:flex;flex-direction:column;gap:.2rem;padding:.95rem 1rem;border:1px solid var(--rt-border-color, rgba(15, 23, 42, .08));border-radius:1.1rem;background:color-mix(in srgb,var(--rt-raised-background, #ffffff) 88%,var(--rt-brand-color) 12%)}.invoice-index-page__stat-value{color:var(--rt-text-primary);font-size:1.2rem;font-weight:700}.invoice-index-page__stat-label,.invoice-index-page__stat-hint{color:var(--rt-text-secondary);font-size:.84rem}.invoice-index-page__filters,.invoice-index-page__list{padding:1rem 1.1rem}.invoice-index-page__filters,.invoice-index-page__list{display:flex;flex-direction:column;gap:1rem}.invoice-index-page__status-nav{display:flex;flex-wrap:wrap;gap:.65rem}.invoice-index-page__status-link{display:inline-flex;align-items:center;justify-content:center;min-height:2.75rem;border:1px solid var(--rt-border-color, rgba(15, 23, 42, .08));border-radius:9999px;padding:.55rem .95rem;color:var(--rt-text-secondary);font-weight:600;text-decoration:none}.invoice-index-page__status-link:hover,.invoice-index-page__status-link--active{border-color:color-mix(in srgb,var(--rt-brand-color) 24%,var(--rt-border-color, rgba(15, 23, 42, .08)));background:color-mix(in srgb,var(--rt-brand-color) 12%,transparent);color:var(--rt-brand-color)}.invoice-index-page__filter-grid{display:grid;gap:.85rem}.invoice-index-page__filter-grid mat-form-field{width:100%}.invoice-index-page__filter-actions{display:flex;flex-wrap:wrap;gap:.75rem}.invoice-index-page__empty{display:flex;flex-direction:column;justify-content:center;min-height:10rem;gap:.45rem;padding:1.25rem;border:1px dashed var(--rt-border-color, rgba(15, 23, 42, .08));border-radius:1.35rem;background:var(--rt-base-background, #ffffff)}.invoice-index-page__empty h3{margin:0;color:var(--rt-text-primary);font-size:1.05rem;font-weight:700}.invoice-index-page__empty p{margin:0;color:var(--rt-text-secondary);line-height:1.65}@media(min-width:900px){.invoice-index-page__filter-grid{grid-template-columns:repeat(3,minmax(0,1fr));align-items:start}.invoice-index-page__stats{grid-template-columns:repeat(4,minmax(0,1fr))}}\n"], dependencies: [{ 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: InvoiceItem, selector: "rolatech-invoice-item", inputs: ["invoice"], outputs: ["download"] }, { 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: "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: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i5.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: i5.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatPaginatorModule }, { kind: "component", type: i6.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "component", type: InvoiceItemSkeleton, selector: "rolatech-invoice-item-skeleton" }, { kind: "component", type: PageCollectionShellComponent, selector: "rolatech-page-collection-shell", inputs: ["eyebrow", "title", "subtitle"] }, { kind: "pipe", type: KeyValuePipe, name: "keyvalue" }], encapsulation: i0.ViewEncapsulation.None }); }
266
282
  }
267
283
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: InvoiceIndexComponent, decorators: [{
268
284
  type: Component,
269
285
  args: [{ selector: 'rolatech-invoice-index', imports: [
270
- ContainerComponent,
271
286
  RouterModule,
272
- TabsComponent,
273
- TabComponent,
274
- ToolbarComponent,
275
- ListComponent,
276
287
  InvoiceItem,
277
- EmptyComponent,
278
288
  MatButtonModule,
279
289
  MatIconModule,
280
290
  FormsModule,
281
291
  MatFormFieldModule,
282
- MatDatepickerModule,
283
- MatOptionModule,
284
- MatInputModule,
285
292
  MatSelectModule,
286
- MatButtonModule,
287
- FilterComponent,
288
293
  KeyValuePipe,
289
- InvoiceHeader,
290
294
  MatPaginatorModule,
291
- InvoiceHeaderSkeleton,
292
295
  InvoiceItemSkeleton,
293
- ], encapsulation: ViewEncapsulation.None, 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 class=\"cursor-pointer\" [label]=\"item.name\" routerLink=\"./\" [queryParams]=\"{ status: item.status }\"></rolatech-tab>\n } @else {\n <rolatech-tab class=\"cursor-pointer\" [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 class=\"cursor-pointer\" [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"] }]
296
+ PageCollectionShellComponent,
297
+ ], encapsulation: ViewEncapsulation.None, template: "<rolatech-page-collection-shell [eyebrow]=\"pageMeta.eyebrow\" [title]=\"pageMeta.title\" [subtitle]=\"pageMeta.description\">\n <div page-shell-header-meta class=\"invoice-index-page__stats\">\n @for (stat of stats(); track stat.label) {\n <article class=\"invoice-index-page__stat\">\n <span class=\"invoice-index-page__stat-value\">{{ stat.value }}</span>\n <span class=\"invoice-index-page__stat-label\">{{ stat.label }}</span>\n <span class=\"invoice-index-page__stat-hint\">{{ stat.hint }}</span>\n </article>\n }\n </div>\n\n <section class=\"invoice-index-page__filters\">\n <nav class=\"invoice-index-page__status-nav\" aria-label=\"Invoice status filters\">\n @for (item of links; track item.name; let index = $index) {\n <a\n class=\"invoice-index-page__status-link\"\n [class.invoice-index-page__status-link--active]=\"select === index\"\n routerLink=\"./\"\n [queryParams]=\"item.status ? { status: item.status } : {}\"\n >\n {{ item.name }}\n </a>\n }\n </nav>\n\n <div class=\"invoice-index-page__filter-grid\">\n <mat-form-field appearance=\"fill\">\n <mat-label>Type</mat-label>\n <mat-select name=\"type\" [(ngModel)]=\"filterOptions.type\">\n @for (type of invoiceType | keyvalue; track type.key) {\n <mat-option [value]=\"type.key\">{{ type.value }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-form-field appearance=\"fill\">\n <mat-label>Status</mat-label>\n <mat-select [compareWith]=\"statusCompareFn\" [(ngModel)]=\"filterOptions.status\">\n @for (status of invoiceStatus | keyvalue; track status.key) {\n <mat-option [value]=\"status.key\">{{ status.value }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <div class=\"invoice-index-page__filter-actions\">\n <button mat-flat-button (click)=\"find()\">Apply filters</button>\n <button mat-stroked-button (click)=\"resetFilter()\">Reset</button>\n </div>\n </div>\n </section>\n\n <section class=\"invoice-index-page__list\">\n @if (loading) {\n @for (dummy of [0, 1, 2, 3]; track dummy) {\n <rolatech-invoice-item-skeleton></rolatech-invoice-item-skeleton>\n }\n } @else if (invoices().length > 0) {\n @for (item of invoices(); track item.id) {\n <rolatech-invoice-item class=\"cursor-pointer\" [routerLink]=\"['./', item.id]\" [invoice]=\"item\"></rolatech-invoice-item>\n }\n } @else {\n <div class=\"invoice-index-page__empty\">\n <h3>No invoices yet</h3>\n <p>The invoices for this workspace will appear here once billing is created.</p>\n </div>\n }\n\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 </section>\n</rolatech-page-collection-shell>\n", styles: [".collapsed{max-height:0;overflow:hidden;transition:max-height .5s cubic-bezier(.4,0,.2,1)}.expanded{max-height:1000px}:host{display:block}.invoice-index-page__filters,.invoice-index-page__list{border:1px solid var(--rt-border-color, rgba(15, 23, 42, .08));border-radius:1.5rem;background:color-mix(in srgb,var(--rt-raised-background, #ffffff) 96%,transparent);box-shadow:0 20px 48px -42px color-mix(in srgb,var(--rt-text-primary) 22%,transparent)}.invoice-index-page__stats{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:.85rem}.invoice-index-page__stat{display:flex;flex-direction:column;gap:.2rem;padding:.95rem 1rem;border:1px solid var(--rt-border-color, rgba(15, 23, 42, .08));border-radius:1.1rem;background:color-mix(in srgb,var(--rt-raised-background, #ffffff) 88%,var(--rt-brand-color) 12%)}.invoice-index-page__stat-value{color:var(--rt-text-primary);font-size:1.2rem;font-weight:700}.invoice-index-page__stat-label,.invoice-index-page__stat-hint{color:var(--rt-text-secondary);font-size:.84rem}.invoice-index-page__filters,.invoice-index-page__list{padding:1rem 1.1rem}.invoice-index-page__filters,.invoice-index-page__list{display:flex;flex-direction:column;gap:1rem}.invoice-index-page__status-nav{display:flex;flex-wrap:wrap;gap:.65rem}.invoice-index-page__status-link{display:inline-flex;align-items:center;justify-content:center;min-height:2.75rem;border:1px solid var(--rt-border-color, rgba(15, 23, 42, .08));border-radius:9999px;padding:.55rem .95rem;color:var(--rt-text-secondary);font-weight:600;text-decoration:none}.invoice-index-page__status-link:hover,.invoice-index-page__status-link--active{border-color:color-mix(in srgb,var(--rt-brand-color) 24%,var(--rt-border-color, rgba(15, 23, 42, .08)));background:color-mix(in srgb,var(--rt-brand-color) 12%,transparent);color:var(--rt-brand-color)}.invoice-index-page__filter-grid{display:grid;gap:.85rem}.invoice-index-page__filter-grid mat-form-field{width:100%}.invoice-index-page__filter-actions{display:flex;flex-wrap:wrap;gap:.75rem}.invoice-index-page__empty{display:flex;flex-direction:column;justify-content:center;min-height:10rem;gap:.45rem;padding:1.25rem;border:1px dashed var(--rt-border-color, rgba(15, 23, 42, .08));border-radius:1.35rem;background:var(--rt-base-background, #ffffff)}.invoice-index-page__empty h3{margin:0;color:var(--rt-text-primary);font-size:1.05rem;font-weight:700}.invoice-index-page__empty p{margin:0;color:var(--rt-text-secondary);line-height:1.65}@media(min-width:900px){.invoice-index-page__filter-grid{grid-template-columns:repeat(3,minmax(0,1fr));align-items:start}.invoice-index-page__stats{grid-template-columns:repeat(4,minmax(0,1fr))}}\n"] }]
294
298
  }] });
295
299
 
296
300
  class InvoiceUser {
@@ -378,6 +382,11 @@ class InvoiceDetailComponent extends BaseComponent {
378
382
  this.status = InvoiceStatus;
379
383
  this.paying = false;
380
384
  this.loading = signal(true, ...(ngDevMode ? [{ debugName: "loading" }] : []));
385
+ this.pageMeta = {
386
+ baseLink: this.readRouteData('invoiceBaseLink', '/invoices'),
387
+ collectionTitle: this.readRouteData('invoiceCollectionTitle', 'Invoices'),
388
+ eyebrow: this.readRouteData('invoiceEyebrow', 'Billing workspace'),
389
+ };
381
390
  }
382
391
  ngOnInit() {
383
392
  this.route.params.subscribe((params) => {
@@ -445,23 +454,47 @@ class InvoiceDetailComponent extends BaseComponent {
445
454
  },
446
455
  });
447
456
  }
457
+ statusToneClass(status) {
458
+ switch (status) {
459
+ case InvoiceStatus.PAID:
460
+ return 'invoice-detail-page__status invoice-detail-page__status--paid';
461
+ case InvoiceStatus.ISSUED:
462
+ case InvoiceStatus.SENT:
463
+ case InvoiceStatus.PARTIALLY_PAID:
464
+ return 'invoice-detail-page__status invoice-detail-page__status--issued';
465
+ case InvoiceStatus.VOID:
466
+ return 'invoice-detail-page__status invoice-detail-page__status--void';
467
+ default:
468
+ return 'invoice-detail-page__status invoice-detail-page__status--neutral';
469
+ }
470
+ }
471
+ readRouteData(key, fallback) {
472
+ for (const snapshot of [...this.route.snapshot.pathFromRoot].reverse()) {
473
+ const value = snapshot.data?.[key];
474
+ if (typeof value === 'string' && value.length > 0) {
475
+ return value;
476
+ }
477
+ }
478
+ return fallback;
479
+ }
448
480
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: InvoiceDetailComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
449
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", 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" }] }); }
481
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: InvoiceDetailComponent, isStandalone: true, selector: "rolatech-invoice-detail", usesInheritance: true, ngImport: i0, template: "<section class=\"invoice-detail-page\">\n @if (loading()) {\n <section class=\"invoice-detail-page__hero invoice-detail-page__hero--loading\">\n <div class=\"invoice-detail-page__hero-copy\">\n <span class=\"invoice-detail-page__eyebrow\">{{ pageMeta.eyebrow }}</span>\n <h1 class=\"invoice-detail-page__title\">Loading invoice...</h1>\n </div>\n </section>\n\n <section class=\"invoice-detail-page__content\">\n <div class=\"invoice-detail-page__main\">\n <rolatech-invoice-user-skeleton></rolatech-invoice-user-skeleton>\n <rolatech-invoice-lines-skeleton></rolatech-invoice-lines-skeleton>\n </div>\n <div class=\"invoice-detail-page__side\">\n <rolatech-invoice-summary-skeleton></rolatech-invoice-summary-skeleton>\n </div>\n </section>\n } @else if (invoice(); as invoice) {\n <section class=\"invoice-detail-page__hero\">\n <div class=\"invoice-detail-page__hero-copy\">\n <a class=\"invoice-detail-page__back\" [routerLink]=\"pageMeta.baseLink\">\u2190 {{ pageMeta.collectionTitle }}</a>\n <span class=\"invoice-detail-page__eyebrow\">{{ pageMeta.eyebrow }}</span>\n <h1 class=\"invoice-detail-page__title\">{{ invoice.invoiceNumber || invoice.id }}</h1>\n <p class=\"invoice-detail-page__description\">\n Invoice for {{ invoice.fullName || invoice.firstName + ' ' + invoice.lastName }} created\n {{ invoice.createdAt | date: 'mediumDate' }}.\n </p>\n </div>\n\n <div class=\"invoice-detail-page__hero-side\">\n <span [class]=\"statusToneClass(invoice.status)\">{{ status[invoice.status] }}</span>\n <div class=\"invoice-detail-page__total\">{{ invoice.total | price }}</div>\n <div class=\"invoice-detail-page__note\">\n @if (invoice.dueAt) {\n Due {{ invoice.dueAt | date: 'mediumDate' }}\n } @else {\n Payable on receipt\n }\n </div>\n\n @if (invoice.status.toString() === 'ISSUED' || invoice.status.toString() === 'SENT') {\n <button mat-flat-button [disabled]=\"paying\" class=\"invoice-detail-page__cta\" (click)=\"pay()\">\n {{ paying ? 'Processing payment...' : 'Pay invoice' }}\n </button>\n }\n </div>\n </section>\n\n <section class=\"invoice-detail-page__content\">\n <div class=\"invoice-detail-page__main\">\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\n @if (invoice.lines; as lines) {\n <rolatech-invoice-lines [lines]=\"lines\"></rolatech-invoice-lines>\n }\n </div>\n\n <div class=\"invoice-detail-page__side\">\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\n <article class=\"invoice-detail-page__panel\">\n <h2 class=\"invoice-detail-page__panel-title\">Invoice note</h2>\n <p class=\"invoice-detail-page__panel-copy\">{{ invoice.note || 'No note added for this invoice.' }}</p>\n\n @if (invoice.status.toString() === 'CREATED' || invoice.status.toString() === 'PAID') {\n <div class=\"invoice-detail-page__panel-row\">\n <span>Payment method</span>\n <strong>Stripe</strong>\n </div>\n }\n </article>\n </div>\n </section>\n }\n</section>\n", styles: [":host{display:block;padding:1rem;--rt-workspace-status-issued-surface: color-mix(in srgb, var(--rt-brand-color) 14%, transparent);--rt-workspace-status-issued-color: color-mix(in srgb, var(--rt-brand-color) 82%, var(--rt-text-primary) 18%);--rt-workspace-status-paid-surface: color-mix(in srgb, var(--rt-brand-color) 16%, var(--rt-base-background, #ffffff));--rt-workspace-status-paid-color: color-mix(in srgb, var(--rt-brand-color) 58%, var(--rt-text-primary) 42%)}.invoice-detail-page{display:flex;flex-direction:column;gap:1rem}.invoice-detail-page__hero,.invoice-detail-page__panel{border:1px solid var(--rt-border-color, rgba(15, 23, 42, .08));border-radius:1.5rem;background:color-mix(in srgb,var(--rt-raised-background, #ffffff) 96%,transparent);box-shadow:0 20px 48px -42px color-mix(in srgb,var(--rt-text-primary) 22%,transparent)}.invoice-detail-page__hero{display:grid;gap:1rem;padding:1.25rem;background:radial-gradient(circle at top left,color-mix(in srgb,var(--rt-brand-color) 14%,transparent),transparent 48%),linear-gradient(135deg,color-mix(in srgb,var(--rt-raised-background, #ffffff) 94%,transparent),color-mix(in srgb,var(--rt-base-background, #ffffff) 88%,var(--rt-brand-color) 12%)),var(--rt-base-background, #ffffff)}.invoice-detail-page__hero-copy,.invoice-detail-page__hero-side,.invoice-detail-page__main,.invoice-detail-page__side{display:flex;flex-direction:column;gap:1rem}.invoice-detail-page__back{color:var(--rt-text-secondary);font-weight:600;text-decoration:none}.invoice-detail-page__eyebrow{display:inline-flex;align-self:flex-start;border-radius:9999px;padding:.38rem .72rem;background:color-mix(in srgb,var(--rt-brand-color) 12%,transparent);color:var(--rt-brand-color);font-size:.74rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase}.invoice-detail-page__title{margin:0;color:var(--rt-text-primary);font-size:clamp(1.8rem,2.8vw,2.7rem);line-height:1.05;font-weight:800}.invoice-detail-page__description,.invoice-detail-page__note,.invoice-detail-page__panel-copy{margin:0;color:var(--rt-text-secondary);line-height:1.7}.invoice-detail-page__status{display:inline-flex;align-self:flex-start;border-radius:9999px;padding:.45rem .82rem;font-size:.8rem;font-weight:700}.invoice-detail-page__status--neutral{background:color-mix(in srgb,var(--rt-brand-color) 10%,transparent);color:var(--rt-brand-color)}.invoice-detail-page__status--issued{background:var(--rt-workspace-status-issued-surface);color:var(--rt-workspace-status-issued-color)}.invoice-detail-page__status--paid{background:var(--rt-workspace-status-paid-surface);color:var(--rt-workspace-status-paid-color)}.invoice-detail-page__status--void{background:color-mix(in srgb,var(--mat-sys-error, #b91c1c) 14%,transparent);color:var(--mat-sys-error, #b91c1c)}.invoice-detail-page__total{color:var(--rt-text-primary);font-size:clamp(1.8rem,2.4vw,2.3rem);font-weight:800}.invoice-detail-page__cta{min-height:3rem;border-radius:9999px}.invoice-detail-page__content{display:grid;gap:1rem}.invoice-detail-page__panel{padding:1rem 1.1rem}.invoice-detail-page__panel-title{margin:0 0 .6rem;color:var(--rt-text-primary);font-size:1.05rem;font-weight:700}.invoice-detail-page__panel-row{display:flex;justify-content:space-between;gap:1rem;margin-top:1rem;color:var(--rt-text-secondary)}.invoice-detail-page__panel-row strong{color:var(--rt-text-primary)}@media(min-width:1024px){.invoice-detail-page__hero{grid-template-columns:minmax(0,1.65fr) minmax(18rem,.85fr);align-items:start}.invoice-detail-page__content{grid-template-columns:minmax(0,1.65fr) minmax(20rem,.85fr);align-items:start}}@media(min-width:768px){:host{padding:1.25rem}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { 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: "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: 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" }, { kind: "pipe", type: i1.DatePipe, name: "date" }, { kind: "pipe", type: PricePipe, name: "price" }] }); }
450
482
  }
451
483
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: InvoiceDetailComponent, decorators: [{
452
484
  type: Component,
453
485
  args: [{ selector: 'rolatech-invoice-detail', imports: [
486
+ CommonModule,
487
+ RouterModule,
454
488
  MatButtonModule,
455
489
  MatIconModule,
456
- ContainerComponent,
457
- ToolbarComponent,
458
490
  InvoiceUser,
459
491
  InvoiceLines,
460
492
  InvoiceSummary,
461
493
  InvoiceUserSkeleton,
462
494
  InvoiceSummarySkeleton,
463
495
  InvoiceLinesSkeleton,
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" }]
496
+ PricePipe,
497
+ ], template: "<section class=\"invoice-detail-page\">\n @if (loading()) {\n <section class=\"invoice-detail-page__hero invoice-detail-page__hero--loading\">\n <div class=\"invoice-detail-page__hero-copy\">\n <span class=\"invoice-detail-page__eyebrow\">{{ pageMeta.eyebrow }}</span>\n <h1 class=\"invoice-detail-page__title\">Loading invoice...</h1>\n </div>\n </section>\n\n <section class=\"invoice-detail-page__content\">\n <div class=\"invoice-detail-page__main\">\n <rolatech-invoice-user-skeleton></rolatech-invoice-user-skeleton>\n <rolatech-invoice-lines-skeleton></rolatech-invoice-lines-skeleton>\n </div>\n <div class=\"invoice-detail-page__side\">\n <rolatech-invoice-summary-skeleton></rolatech-invoice-summary-skeleton>\n </div>\n </section>\n } @else if (invoice(); as invoice) {\n <section class=\"invoice-detail-page__hero\">\n <div class=\"invoice-detail-page__hero-copy\">\n <a class=\"invoice-detail-page__back\" [routerLink]=\"pageMeta.baseLink\">\u2190 {{ pageMeta.collectionTitle }}</a>\n <span class=\"invoice-detail-page__eyebrow\">{{ pageMeta.eyebrow }}</span>\n <h1 class=\"invoice-detail-page__title\">{{ invoice.invoiceNumber || invoice.id }}</h1>\n <p class=\"invoice-detail-page__description\">\n Invoice for {{ invoice.fullName || invoice.firstName + ' ' + invoice.lastName }} created\n {{ invoice.createdAt | date: 'mediumDate' }}.\n </p>\n </div>\n\n <div class=\"invoice-detail-page__hero-side\">\n <span [class]=\"statusToneClass(invoice.status)\">{{ status[invoice.status] }}</span>\n <div class=\"invoice-detail-page__total\">{{ invoice.total | price }}</div>\n <div class=\"invoice-detail-page__note\">\n @if (invoice.dueAt) {\n Due {{ invoice.dueAt | date: 'mediumDate' }}\n } @else {\n Payable on receipt\n }\n </div>\n\n @if (invoice.status.toString() === 'ISSUED' || invoice.status.toString() === 'SENT') {\n <button mat-flat-button [disabled]=\"paying\" class=\"invoice-detail-page__cta\" (click)=\"pay()\">\n {{ paying ? 'Processing payment...' : 'Pay invoice' }}\n </button>\n }\n </div>\n </section>\n\n <section class=\"invoice-detail-page__content\">\n <div class=\"invoice-detail-page__main\">\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\n @if (invoice.lines; as lines) {\n <rolatech-invoice-lines [lines]=\"lines\"></rolatech-invoice-lines>\n }\n </div>\n\n <div class=\"invoice-detail-page__side\">\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\n <article class=\"invoice-detail-page__panel\">\n <h2 class=\"invoice-detail-page__panel-title\">Invoice note</h2>\n <p class=\"invoice-detail-page__panel-copy\">{{ invoice.note || 'No note added for this invoice.' }}</p>\n\n @if (invoice.status.toString() === 'CREATED' || invoice.status.toString() === 'PAID') {\n <div class=\"invoice-detail-page__panel-row\">\n <span>Payment method</span>\n <strong>Stripe</strong>\n </div>\n }\n </article>\n </div>\n </section>\n }\n</section>\n", styles: [":host{display:block;padding:1rem;--rt-workspace-status-issued-surface: color-mix(in srgb, var(--rt-brand-color) 14%, transparent);--rt-workspace-status-issued-color: color-mix(in srgb, var(--rt-brand-color) 82%, var(--rt-text-primary) 18%);--rt-workspace-status-paid-surface: color-mix(in srgb, var(--rt-brand-color) 16%, var(--rt-base-background, #ffffff));--rt-workspace-status-paid-color: color-mix(in srgb, var(--rt-brand-color) 58%, var(--rt-text-primary) 42%)}.invoice-detail-page{display:flex;flex-direction:column;gap:1rem}.invoice-detail-page__hero,.invoice-detail-page__panel{border:1px solid var(--rt-border-color, rgba(15, 23, 42, .08));border-radius:1.5rem;background:color-mix(in srgb,var(--rt-raised-background, #ffffff) 96%,transparent);box-shadow:0 20px 48px -42px color-mix(in srgb,var(--rt-text-primary) 22%,transparent)}.invoice-detail-page__hero{display:grid;gap:1rem;padding:1.25rem;background:radial-gradient(circle at top left,color-mix(in srgb,var(--rt-brand-color) 14%,transparent),transparent 48%),linear-gradient(135deg,color-mix(in srgb,var(--rt-raised-background, #ffffff) 94%,transparent),color-mix(in srgb,var(--rt-base-background, #ffffff) 88%,var(--rt-brand-color) 12%)),var(--rt-base-background, #ffffff)}.invoice-detail-page__hero-copy,.invoice-detail-page__hero-side,.invoice-detail-page__main,.invoice-detail-page__side{display:flex;flex-direction:column;gap:1rem}.invoice-detail-page__back{color:var(--rt-text-secondary);font-weight:600;text-decoration:none}.invoice-detail-page__eyebrow{display:inline-flex;align-self:flex-start;border-radius:9999px;padding:.38rem .72rem;background:color-mix(in srgb,var(--rt-brand-color) 12%,transparent);color:var(--rt-brand-color);font-size:.74rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase}.invoice-detail-page__title{margin:0;color:var(--rt-text-primary);font-size:clamp(1.8rem,2.8vw,2.7rem);line-height:1.05;font-weight:800}.invoice-detail-page__description,.invoice-detail-page__note,.invoice-detail-page__panel-copy{margin:0;color:var(--rt-text-secondary);line-height:1.7}.invoice-detail-page__status{display:inline-flex;align-self:flex-start;border-radius:9999px;padding:.45rem .82rem;font-size:.8rem;font-weight:700}.invoice-detail-page__status--neutral{background:color-mix(in srgb,var(--rt-brand-color) 10%,transparent);color:var(--rt-brand-color)}.invoice-detail-page__status--issued{background:var(--rt-workspace-status-issued-surface);color:var(--rt-workspace-status-issued-color)}.invoice-detail-page__status--paid{background:var(--rt-workspace-status-paid-surface);color:var(--rt-workspace-status-paid-color)}.invoice-detail-page__status--void{background:color-mix(in srgb,var(--mat-sys-error, #b91c1c) 14%,transparent);color:var(--mat-sys-error, #b91c1c)}.invoice-detail-page__total{color:var(--rt-text-primary);font-size:clamp(1.8rem,2.4vw,2.3rem);font-weight:800}.invoice-detail-page__cta{min-height:3rem;border-radius:9999px}.invoice-detail-page__content{display:grid;gap:1rem}.invoice-detail-page__panel{padding:1rem 1.1rem}.invoice-detail-page__panel-title{margin:0 0 .6rem;color:var(--rt-text-primary);font-size:1.05rem;font-weight:700}.invoice-detail-page__panel-row{display:flex;justify-content:space-between;gap:1rem;margin-top:1rem;color:var(--rt-text-secondary)}.invoice-detail-page__panel-row strong{color:var(--rt-text-primary)}@media(min-width:1024px){.invoice-detail-page__hero{grid-template-columns:minmax(0,1.65fr) minmax(18rem,.85fr);align-items:start}.invoice-detail-page__content{grid-template-columns:minmax(0,1.65fr) minmax(20rem,.85fr);align-items:start}}@media(min-width:768px){:host{padding:1.25rem}}\n"] }]
465
498
  }] });
466
499
 
467
500
  const invoiceRoutes = [
@@ -584,7 +617,7 @@ class InvoiceManageIndex extends BaseComponent {
584
617
  }
585
618
  }
586
619
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: InvoiceManageIndex, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
587
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", 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 class=\"cursor-pointer\" [label]=\"item.name\" routerLink=\"./\" [queryParams]=\"{ status: item.status }\"></rolatech-tab>\n } @else {\n <rolatech-tab class=\"cursor-pointer\" [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 border-(--rt-border-color) text-left\">#ID</th>\n <th class=\"p-3 border-b border-(--rt-border-color) text-left\">Status</th>\n <th class=\"p-3 border-b border-(--rt-border-color) text-left\">Email</th>\n <th class=\"p-3 border-b border-(--rt-border-color) text-left\">Profile</th>\n <th class=\"p-3 border-b border-(--rt-border-color) text-left\">Type</th>\n <th class=\"p-3 border-b border-(--rt-border-color) 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 cursor-pointer\" [routerLink]=\"['./', item.id]\">\n <td class=\"p-3 border-b border-(--rt-border-color)\">{{item.id}}</td>\n <td class=\"p-3 border-b border-(--rt-border-color)\">\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 border-(--rt-border-color)\">{{item.email}}</td>\n <td class=\"p-3 border-b border-(--rt-border-color)\">{{item.fullName}}</td>\n <td class=\"p-3 border-b border-(--rt-border-color)\">{{invoiceTypeMap()[item.type] || item.type}}</td>\n <td class=\"p-3 border-b border-(--rt-border-color)\">{{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 border-(--rt-border-color) 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 border-(--rt-border-color) 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", "mode"], outputs: ["selectChange"] }, { kind: "component", type: TabComponent, selector: "rolatech-tab", inputs: ["label"], outputs: ["selectRequested"] }, { 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" }] }); }
620
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", 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 class=\"cursor-pointer\" [label]=\"item.name\" routerLink=\"./\" [queryParams]=\"{ status: item.status }\"></rolatech-tab>\n } @else {\n <rolatech-tab class=\"cursor-pointer\" [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 border-(--rt-border-color) text-left\">#ID</th>\n <th class=\"p-3 border-b border-(--rt-border-color) text-left\">Status</th>\n <th class=\"p-3 border-b border-(--rt-border-color) text-left\">Email</th>\n <th class=\"p-3 border-b border-(--rt-border-color) text-left\">Profile</th>\n <th class=\"p-3 border-b border-(--rt-border-color) text-left\">Type</th>\n <th class=\"p-3 border-b border-(--rt-border-color) 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 cursor-pointer\" [routerLink]=\"['./', item.id]\">\n <td class=\"p-3 border-b border-(--rt-border-color)\">{{item.id}}</td>\n <td class=\"p-3 border-b border-(--rt-border-color)\">\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 border-(--rt-border-color)\">{{item.email}}</td>\n <td class=\"p-3 border-b border-(--rt-border-color)\">{{item.fullName}}</td>\n <td class=\"p-3 border-b border-(--rt-border-color)\">{{invoiceTypeMap()[item.type] || item.type}}</td>\n <td class=\"p-3 border-b border-(--rt-border-color)\">{{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 border-(--rt-border-color) 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 border-(--rt-border-color) 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", "mode"], outputs: ["selectChange"] }, { kind: "component", type: TabComponent, selector: "rolatech-tab", inputs: ["label"], outputs: ["selectRequested"] }, { kind: "ngmodule", type: MatPaginatorModule }, { kind: "component", type: i6.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "pipe", type: PricePipe, name: "price" }] }); }
588
621
  }
589
622
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: InvoiceManageIndex, decorators: [{
590
623
  type: Component,
@@ -682,7 +715,7 @@ class InvoiceManageCreate {
682
715
  return new Date().toISOString().slice(0, 10);
683
716
  }
684
717
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: InvoiceManageCreate, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
685
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", 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" }] }); }
718
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", 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$1.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: i5.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: i5.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatDividerModule }, { kind: "component", type: i7.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i8.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" }] }); }
686
719
  }
687
720
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: InvoiceManageCreate, decorators: [{
688
721
  type: Component,
@@ -730,7 +763,7 @@ class InvoiceEdit extends BaseComponent {
730
763
  deps: [MAT_DATE_LOCALE],
731
764
  },
732
765
  { provide: MAT_DATE_FORMATS, useValue: MY_FORMATS },
733
- ], 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 }] }); }
766
+ ], 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$1.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 }] }); }
734
767
  }
735
768
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: InvoiceEdit, decorators: [{
736
769
  type: Component,
@@ -1103,7 +1136,7 @@ class InvoiceManageLine {
1103
1136
  }
1104
1137
  saveLines() { }
1105
1138
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: InvoiceManageLine, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1106
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", 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 }); }
1139
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", 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$1.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 }); }
1107
1140
  }
1108
1141
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: InvoiceManageLine, decorators: [{
1109
1142
  type: Component,
@@ -1259,7 +1292,6 @@ class AgentInvoiceIndex extends BaseComponent {
1259
1292
  constructor() {
1260
1293
  super(...arguments);
1261
1294
  this.invoiceService = inject(InvoiceService);
1262
- this.title = inject(Title);
1263
1295
  this.filterOptions = {
1264
1296
  type: '',
1265
1297
  status: '',
@@ -1276,39 +1308,46 @@ class AgentInvoiceIndex extends BaseComponent {
1276
1308
  this.links = [
1277
1309
  {
1278
1310
  name: 'All',
1279
- icon: 'dashboard',
1311
+ status: '',
1280
1312
  },
1281
1313
  {
1282
1314
  name: 'Created',
1283
- icon: 'category',
1284
1315
  status: 'created',
1285
1316
  },
1286
1317
  {
1287
1318
  name: 'Issued',
1288
- icon: 'category',
1289
1319
  status: 'issued',
1290
1320
  },
1291
1321
  {
1292
1322
  name: 'Paid',
1293
- icon: 'category',
1294
1323
  status: 'paid',
1295
1324
  },
1296
1325
  ];
1297
- this.filter = false;
1326
+ this.pageMeta = {
1327
+ baseLink: this.readRouteData('invoiceBaseLink', '/invoices'),
1328
+ eyebrow: this.readRouteData('invoiceEyebrow', 'Agent workspace'),
1329
+ title: this.readRouteData('invoiceCollectionTitle', 'Listing invoices'),
1330
+ description: this.readRouteData('invoiceCollectionDescription', 'Monitor invoices tied to your listings, with billing status and payment movement visible at a glance.'),
1331
+ };
1332
+ this.stats = computed(() => {
1333
+ const invoices = this.invoices();
1334
+ return [
1335
+ { label: 'Invoices', value: this.length || invoices.length, hint: 'Billing records in your agent workspace' },
1336
+ { label: 'Created', value: invoices.filter((item) => item.status === InvoiceStatus.CREATED).length, hint: 'Prepared but not yet settled' },
1337
+ { label: 'Issued', value: invoices.filter((item) => item.status === InvoiceStatus.ISSUED || item.status === InvoiceStatus.SENT).length, hint: 'Awaiting payment' },
1338
+ { label: 'Paid', value: invoices.filter((item) => item.status === InvoiceStatus.PAID).length, hint: 'Completed payments' },
1339
+ ];
1340
+ }, ...(ngDevMode ? [{ debugName: "stats" }] : []));
1298
1341
  }
1299
1342
  find() {
1300
- const options = {
1301
- sort: 'updatedAt desc',
1302
- };
1303
- const filterString = this.convertFilterOptions(this.filterOptions);
1304
- if (filterString) {
1305
- options['filter'] = filterString;
1306
- }
1307
- this.invoiceService.me(options).subscribe({
1308
- next: (res) => {
1309
- console.log(res);
1310
- this.invoices = res.data;
1343
+ this.router.navigate([], {
1344
+ queryParams: {
1345
+ type: this.filterOptions.type || null,
1346
+ status: this.filterOptions.status || null,
1347
+ page: 1,
1311
1348
  },
1349
+ queryParamsHandling: 'merge',
1350
+ replaceUrl: true,
1312
1351
  });
1313
1352
  }
1314
1353
  ngOnInit() {
@@ -1317,14 +1356,25 @@ class AgentInvoiceIndex extends BaseComponent {
1317
1356
  const page = p.get('page') ? Number(p.get('page')) : 1;
1318
1357
  this.pageIndex.set(Math.max(page - 1, 0));
1319
1358
  const status = p.get('status') || undefined;
1320
- let filter = '';
1359
+ const type = p.get('type') || undefined;
1360
+ this.filterOptions = {
1361
+ type: type || '',
1362
+ status: status || '',
1363
+ };
1364
+ const filters = [];
1321
1365
  if (status) {
1322
1366
  this.select = this.links.findIndex((item) => item.status === status);
1323
- filter = `status:${status?.toUpperCase()}`;
1367
+ filters.push(`status:${status.toUpperCase()}`);
1368
+ }
1369
+ else {
1370
+ this.select = 0;
1371
+ }
1372
+ if (type) {
1373
+ filters.push(`type:${type}`);
1324
1374
  }
1325
1375
  return {
1326
1376
  page,
1327
- filter,
1377
+ filter: filters.join(','),
1328
1378
  limit: p.get('limit') ? Number(p.get('limit')) : 15,
1329
1379
  sort: p.get('sort') || undefined,
1330
1380
  };
@@ -1358,63 +1408,46 @@ class AgentInvoiceIndex extends BaseComponent {
1358
1408
  resetFilter() {
1359
1409
  this.filterOptions = {
1360
1410
  type: '',
1411
+ status: '',
1361
1412
  };
1362
- this.filter = false;
1363
- this.find();
1364
- }
1365
- convertFilterOptions(jsonObj) {
1366
- return Object.entries(jsonObj)
1367
- .filter(([key, value]) => value !== '' && value !== undefined)
1368
- .map(([key, value]) => {
1369
- return `${key}:${value}`;
1370
- })
1371
- .join(',');
1413
+ this.router.navigate([], {
1414
+ queryParams: {
1415
+ type: null,
1416
+ status: null,
1417
+ page: 1,
1418
+ },
1419
+ queryParamsHandling: 'merge',
1420
+ replaceUrl: true,
1421
+ });
1372
1422
  }
1373
1423
  statusCompareFn(t1, t2) {
1374
1424
  return t1 === t2;
1375
1425
  }
1376
- statusBadgeClass(status) {
1377
- switch (status.toString()) {
1378
- case 'CREATED':
1379
- return 'bg-yellow-100 text-yellow-800';
1380
- case 'ISSUED':
1381
- return 'bg-blue-100 text-blue-800';
1382
- case 'PAID':
1383
- return 'bg-emerald-100 text-emerald-800';
1384
- case 'VOID':
1385
- return 'bg-red-100 text-red-800';
1386
- default:
1387
- return 'bg-gray-100 text-gray-700';
1426
+ readRouteData(key, fallback) {
1427
+ for (const snapshot of [...this.route.snapshot.pathFromRoot].reverse()) {
1428
+ const value = snapshot.data?.[key];
1429
+ if (typeof value === 'string' && value.length > 0) {
1430
+ return value;
1431
+ }
1388
1432
  }
1433
+ return fallback;
1389
1434
  }
1390
1435
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: AgentInvoiceIndex, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
1391
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", 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 class=\"cursor-pointer\" [label]=\"item.name\" routerLink=\"./\" [queryParams]=\"{ status: item.status }\"></rolatech-tab>\n } @else {\n <rolatech-tab class=\"cursor-pointer\" [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 class=\"cursor-pointer\" [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", "mode"], outputs: ["selectChange"] }, { kind: "component", type: TabComponent, selector: "rolatech-tab", inputs: ["label"], outputs: ["selectRequested"] }, { 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" }] }); }
1436
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: AgentInvoiceIndex, isStandalone: true, selector: "rolatech-agent-invoice-index", usesInheritance: true, ngImport: i0, template: "<section class=\"invoice-index-page\">\n <section class=\"invoice-index-page__hero\">\n <div class=\"invoice-index-page__copy\">\n <span class=\"invoice-index-page__eyebrow\">{{ pageMeta.eyebrow }}</span>\n <h1 class=\"invoice-index-page__title\">{{ pageMeta.title }}</h1>\n <p class=\"invoice-index-page__description\">{{ pageMeta.description }}</p>\n </div>\n\n <div class=\"invoice-index-page__stats\">\n @for (stat of stats(); track stat.label) {\n <article class=\"invoice-index-page__stat\">\n <span class=\"invoice-index-page__stat-value\">{{ stat.value }}</span>\n <span class=\"invoice-index-page__stat-label\">{{ stat.label }}</span>\n <span class=\"invoice-index-page__stat-hint\">{{ stat.hint }}</span>\n </article>\n }\n </div>\n </section>\n\n <section class=\"invoice-index-page__filters\">\n <nav class=\"invoice-index-page__status-nav\" aria-label=\"Invoice status filters\">\n @for (item of links; track item.name; let index = $index) {\n <a\n class=\"invoice-index-page__status-link\"\n [class.invoice-index-page__status-link--active]=\"select === index\"\n routerLink=\"./\"\n [queryParams]=\"item.status ? { status: item.status } : {}\"\n >\n {{ item.name }}\n </a>\n }\n </nav>\n\n <div class=\"invoice-index-page__filter-grid\">\n <mat-form-field appearance=\"fill\">\n <mat-label>Type</mat-label>\n <mat-select name=\"type\" [(ngModel)]=\"filterOptions.type\">\n @for (type of invoiceType | keyvalue; track type.key) {\n <mat-option [value]=\"type.key\">{{ type.value }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-form-field appearance=\"fill\">\n <mat-label>Status</mat-label>\n <mat-select [compareWith]=\"statusCompareFn\" [(ngModel)]=\"filterOptions.status\">\n @for (status of invoiceStatus | keyvalue; track status.key) {\n <mat-option [value]=\"status.key\">{{ status.value }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <div class=\"invoice-index-page__filter-actions\">\n <button mat-flat-button (click)=\"find()\">Apply filters</button>\n <button mat-stroked-button (click)=\"resetFilter()\">Reset</button>\n </div>\n </div>\n </section>\n\n <section class=\"invoice-index-page__list\">\n @if (loading) {\n @for (dummy of [0, 1, 2, 3]; track dummy) {\n <rolatech-invoice-item-skeleton></rolatech-invoice-item-skeleton>\n }\n } @else if (invoices().length > 0) {\n @for (item of invoices(); track item.id) {\n <rolatech-invoice-item class=\"cursor-pointer\" [routerLink]=\"['./', item.id]\" [invoice]=\"item\"></rolatech-invoice-item>\n }\n } @else {\n <div class=\"invoice-index-page__empty\">\n <h3>No invoices yet</h3>\n <p>The invoices for this workspace will appear here once billing is created.</p>\n </div>\n }\n\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 </section>\n</section>\n", styles: [".collapsed{max-height:0;overflow:hidden;transition:max-height .5s cubic-bezier(.4,0,.2,1)}.expanded{max-height:1000px}:host{display:block;padding:1rem}.invoice-index-page{display:flex;flex-direction:column;gap:1rem}.invoice-index-page__hero,.invoice-index-page__filters,.invoice-index-page__list{border:1px solid var(--rt-border-color, rgba(15, 23, 42, .08));border-radius:1.5rem;background:color-mix(in srgb,var(--rt-raised-background, #ffffff) 96%,transparent);box-shadow:0 20px 48px -42px color-mix(in srgb,var(--rt-text-primary) 22%,transparent)}.invoice-index-page__hero{display:grid;gap:1rem;padding:1.25rem;background:radial-gradient(circle at top left,color-mix(in srgb,var(--rt-brand-color) 14%,transparent),transparent 48%),linear-gradient(135deg,color-mix(in srgb,var(--rt-raised-background, #ffffff) 94%,transparent),color-mix(in srgb,var(--rt-base-background, #ffffff) 88%,var(--rt-brand-color) 12%)),var(--rt-base-background, #ffffff)}.invoice-index-page__copy{display:flex;flex-direction:column;gap:.75rem}.invoice-index-page__eyebrow{display:inline-flex;align-self:flex-start;border-radius:9999px;padding:.38rem .72rem;background:color-mix(in srgb,var(--rt-brand-color) 12%,transparent);color:var(--rt-brand-color);font-size:.74rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase}.invoice-index-page__title{margin:0;color:var(--rt-text-primary);font-size:clamp(1.8rem,2.8vw,2.7rem);line-height:1.05;font-weight:800}.invoice-index-page__description{margin:0;color:var(--rt-text-secondary);line-height:1.7}.invoice-index-page__stats{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:.85rem}.invoice-index-page__stat{display:flex;flex-direction:column;gap:.2rem;padding:.95rem 1rem;border:1px solid var(--rt-border-color, rgba(15, 23, 42, .08));border-radius:1.1rem;background:color-mix(in srgb,var(--rt-raised-background, #ffffff) 88%,var(--rt-brand-color) 12%)}.invoice-index-page__stat-value{color:var(--rt-text-primary);font-size:1.2rem;font-weight:700}.invoice-index-page__stat-label,.invoice-index-page__stat-hint{color:var(--rt-text-secondary);font-size:.84rem}.invoice-index-page__filters,.invoice-index-page__list{display:flex;flex-direction:column;gap:1rem;padding:1rem 1.1rem}.invoice-index-page__status-nav{display:flex;flex-wrap:wrap;gap:.65rem}.invoice-index-page__status-link{display:inline-flex;align-items:center;justify-content:center;min-height:2.75rem;border:1px solid var(--rt-border-color, rgba(15, 23, 42, .08));border-radius:9999px;padding:.55rem .95rem;color:var(--rt-text-secondary);font-weight:600;text-decoration:none}.invoice-index-page__status-link:hover,.invoice-index-page__status-link--active{border-color:color-mix(in srgb,var(--rt-brand-color) 24%,var(--rt-border-color, rgba(15, 23, 42, .08)));background:color-mix(in srgb,var(--rt-brand-color) 12%,transparent);color:var(--rt-brand-color)}.invoice-index-page__filter-grid{display:grid;gap:.85rem}.invoice-index-page__filter-grid mat-form-field{width:100%}.invoice-index-page__filter-actions{display:flex;flex-wrap:wrap;gap:.75rem}.invoice-index-page__empty{display:flex;flex-direction:column;justify-content:center;min-height:10rem;gap:.45rem;padding:1.25rem;border:1px dashed var(--rt-border-color, rgba(15, 23, 42, .08));border-radius:1.35rem;background:color-mix(in srgb,var(--rt-raised-background, #ffffff) 92%,var(--rt-brand-color) 8%)}.invoice-index-page__empty h3{margin:0;color:var(--rt-text-primary);font-size:1.05rem;font-weight:700}.invoice-index-page__empty p{margin:0;color:var(--rt-text-secondary);line-height:1.65}@media(min-width:900px){.invoice-index-page__hero{grid-template-columns:minmax(0,1.6fr) minmax(19rem,.95fr);align-items:start}.invoice-index-page__filter-grid{grid-template-columns:repeat(3,minmax(0,1fr));align-items:start}}@media(min-width:768px){:host{padding:1.25rem}}\n"], dependencies: [{ 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: InvoiceItem, selector: "rolatech-invoice-item", inputs: ["invoice"], outputs: ["download"] }, { 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: 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: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i5.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: i5.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatPaginatorModule }, { kind: "component", type: i6.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "component", type: InvoiceItemSkeleton, selector: "rolatech-invoice-item-skeleton" }, { kind: "pipe", type: KeyValuePipe, name: "keyvalue" }] }); }
1392
1437
  }
1393
1438
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: AgentInvoiceIndex, decorators: [{
1394
1439
  type: Component,
1395
1440
  args: [{ selector: 'rolatech-agent-invoice-index', imports: [
1396
- ContainerComponent,
1397
1441
  RouterModule,
1398
- TabsComponent,
1399
- TabComponent,
1400
- ToolbarComponent,
1401
- ListComponent,
1402
1442
  InvoiceItem,
1403
- EmptyComponent,
1404
1443
  MatButtonModule,
1405
- MatIconModule,
1406
1444
  FormsModule,
1407
1445
  MatFormFieldModule,
1408
- MatDatepickerModule,
1409
- MatOptionModule,
1410
- MatInputModule,
1411
1446
  MatSelectModule,
1412
- MatButtonModule,
1413
- FilterComponent,
1414
1447
  KeyValuePipe,
1415
- InvoiceHeader,
1416
1448
  MatPaginatorModule,
1417
- ], 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 class=\"cursor-pointer\" [label]=\"item.name\" routerLink=\"./\" [queryParams]=\"{ status: item.status }\"></rolatech-tab>\n } @else {\n <rolatech-tab class=\"cursor-pointer\" [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 class=\"cursor-pointer\" [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"] }]
1449
+ InvoiceItemSkeleton,
1450
+ ], template: "<section class=\"invoice-index-page\">\n <section class=\"invoice-index-page__hero\">\n <div class=\"invoice-index-page__copy\">\n <span class=\"invoice-index-page__eyebrow\">{{ pageMeta.eyebrow }}</span>\n <h1 class=\"invoice-index-page__title\">{{ pageMeta.title }}</h1>\n <p class=\"invoice-index-page__description\">{{ pageMeta.description }}</p>\n </div>\n\n <div class=\"invoice-index-page__stats\">\n @for (stat of stats(); track stat.label) {\n <article class=\"invoice-index-page__stat\">\n <span class=\"invoice-index-page__stat-value\">{{ stat.value }}</span>\n <span class=\"invoice-index-page__stat-label\">{{ stat.label }}</span>\n <span class=\"invoice-index-page__stat-hint\">{{ stat.hint }}</span>\n </article>\n }\n </div>\n </section>\n\n <section class=\"invoice-index-page__filters\">\n <nav class=\"invoice-index-page__status-nav\" aria-label=\"Invoice status filters\">\n @for (item of links; track item.name; let index = $index) {\n <a\n class=\"invoice-index-page__status-link\"\n [class.invoice-index-page__status-link--active]=\"select === index\"\n routerLink=\"./\"\n [queryParams]=\"item.status ? { status: item.status } : {}\"\n >\n {{ item.name }}\n </a>\n }\n </nav>\n\n <div class=\"invoice-index-page__filter-grid\">\n <mat-form-field appearance=\"fill\">\n <mat-label>Type</mat-label>\n <mat-select name=\"type\" [(ngModel)]=\"filterOptions.type\">\n @for (type of invoiceType | keyvalue; track type.key) {\n <mat-option [value]=\"type.key\">{{ type.value }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-form-field appearance=\"fill\">\n <mat-label>Status</mat-label>\n <mat-select [compareWith]=\"statusCompareFn\" [(ngModel)]=\"filterOptions.status\">\n @for (status of invoiceStatus | keyvalue; track status.key) {\n <mat-option [value]=\"status.key\">{{ status.value }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <div class=\"invoice-index-page__filter-actions\">\n <button mat-flat-button (click)=\"find()\">Apply filters</button>\n <button mat-stroked-button (click)=\"resetFilter()\">Reset</button>\n </div>\n </div>\n </section>\n\n <section class=\"invoice-index-page__list\">\n @if (loading) {\n @for (dummy of [0, 1, 2, 3]; track dummy) {\n <rolatech-invoice-item-skeleton></rolatech-invoice-item-skeleton>\n }\n } @else if (invoices().length > 0) {\n @for (item of invoices(); track item.id) {\n <rolatech-invoice-item class=\"cursor-pointer\" [routerLink]=\"['./', item.id]\" [invoice]=\"item\"></rolatech-invoice-item>\n }\n } @else {\n <div class=\"invoice-index-page__empty\">\n <h3>No invoices yet</h3>\n <p>The invoices for this workspace will appear here once billing is created.</p>\n </div>\n }\n\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 </section>\n</section>\n", styles: [".collapsed{max-height:0;overflow:hidden;transition:max-height .5s cubic-bezier(.4,0,.2,1)}.expanded{max-height:1000px}:host{display:block;padding:1rem}.invoice-index-page{display:flex;flex-direction:column;gap:1rem}.invoice-index-page__hero,.invoice-index-page__filters,.invoice-index-page__list{border:1px solid var(--rt-border-color, rgba(15, 23, 42, .08));border-radius:1.5rem;background:color-mix(in srgb,var(--rt-raised-background, #ffffff) 96%,transparent);box-shadow:0 20px 48px -42px color-mix(in srgb,var(--rt-text-primary) 22%,transparent)}.invoice-index-page__hero{display:grid;gap:1rem;padding:1.25rem;background:radial-gradient(circle at top left,color-mix(in srgb,var(--rt-brand-color) 14%,transparent),transparent 48%),linear-gradient(135deg,color-mix(in srgb,var(--rt-raised-background, #ffffff) 94%,transparent),color-mix(in srgb,var(--rt-base-background, #ffffff) 88%,var(--rt-brand-color) 12%)),var(--rt-base-background, #ffffff)}.invoice-index-page__copy{display:flex;flex-direction:column;gap:.75rem}.invoice-index-page__eyebrow{display:inline-flex;align-self:flex-start;border-radius:9999px;padding:.38rem .72rem;background:color-mix(in srgb,var(--rt-brand-color) 12%,transparent);color:var(--rt-brand-color);font-size:.74rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase}.invoice-index-page__title{margin:0;color:var(--rt-text-primary);font-size:clamp(1.8rem,2.8vw,2.7rem);line-height:1.05;font-weight:800}.invoice-index-page__description{margin:0;color:var(--rt-text-secondary);line-height:1.7}.invoice-index-page__stats{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:.85rem}.invoice-index-page__stat{display:flex;flex-direction:column;gap:.2rem;padding:.95rem 1rem;border:1px solid var(--rt-border-color, rgba(15, 23, 42, .08));border-radius:1.1rem;background:color-mix(in srgb,var(--rt-raised-background, #ffffff) 88%,var(--rt-brand-color) 12%)}.invoice-index-page__stat-value{color:var(--rt-text-primary);font-size:1.2rem;font-weight:700}.invoice-index-page__stat-label,.invoice-index-page__stat-hint{color:var(--rt-text-secondary);font-size:.84rem}.invoice-index-page__filters,.invoice-index-page__list{display:flex;flex-direction:column;gap:1rem;padding:1rem 1.1rem}.invoice-index-page__status-nav{display:flex;flex-wrap:wrap;gap:.65rem}.invoice-index-page__status-link{display:inline-flex;align-items:center;justify-content:center;min-height:2.75rem;border:1px solid var(--rt-border-color, rgba(15, 23, 42, .08));border-radius:9999px;padding:.55rem .95rem;color:var(--rt-text-secondary);font-weight:600;text-decoration:none}.invoice-index-page__status-link:hover,.invoice-index-page__status-link--active{border-color:color-mix(in srgb,var(--rt-brand-color) 24%,var(--rt-border-color, rgba(15, 23, 42, .08)));background:color-mix(in srgb,var(--rt-brand-color) 12%,transparent);color:var(--rt-brand-color)}.invoice-index-page__filter-grid{display:grid;gap:.85rem}.invoice-index-page__filter-grid mat-form-field{width:100%}.invoice-index-page__filter-actions{display:flex;flex-wrap:wrap;gap:.75rem}.invoice-index-page__empty{display:flex;flex-direction:column;justify-content:center;min-height:10rem;gap:.45rem;padding:1.25rem;border:1px dashed var(--rt-border-color, rgba(15, 23, 42, .08));border-radius:1.35rem;background:color-mix(in srgb,var(--rt-raised-background, #ffffff) 92%,var(--rt-brand-color) 8%)}.invoice-index-page__empty h3{margin:0;color:var(--rt-text-primary);font-size:1.05rem;font-weight:700}.invoice-index-page__empty p{margin:0;color:var(--rt-text-secondary);line-height:1.65}@media(min-width:900px){.invoice-index-page__hero{grid-template-columns:minmax(0,1.6fr) minmax(19rem,.95fr);align-items:start}.invoice-index-page__filter-grid{grid-template-columns:repeat(3,minmax(0,1fr));align-items:start}}@media(min-width:768px){:host{padding:1.25rem}}\n"] }]
1418
1451
  }] });
1419
1452
 
1420
1453
  class AgentInvoiceDetail extends BaseComponent {
@@ -1426,6 +1459,11 @@ class AgentInvoiceDetail extends BaseComponent {
1426
1459
  this.loading = signal(true, ...(ngDevMode ? [{ debugName: "loading" }] : []));
1427
1460
  this.invoice = signal(null, ...(ngDevMode ? [{ debugName: "invoice" }] : []));
1428
1461
  this.status = InvoiceStatus;
1462
+ this.pageMeta = {
1463
+ baseLink: this.readRouteData('invoiceBaseLink', '/invoices'),
1464
+ collectionTitle: this.readRouteData('invoiceCollectionTitle', 'Invoices'),
1465
+ eyebrow: this.readRouteData('invoiceEyebrow', 'Agent workspace'),
1466
+ };
1429
1467
  }
1430
1468
  ngOnInit() {
1431
1469
  this.getInvoice();
@@ -1476,8 +1514,31 @@ class AgentInvoiceDetail extends BaseComponent {
1476
1514
  },
1477
1515
  });
1478
1516
  }
1517
+ statusToneClass(status) {
1518
+ switch (status) {
1519
+ case InvoiceStatus.PAID:
1520
+ return 'invoice-detail-page__status invoice-detail-page__status--paid';
1521
+ case InvoiceStatus.ISSUED:
1522
+ case InvoiceStatus.SENT:
1523
+ case InvoiceStatus.PARTIALLY_PAID:
1524
+ return 'invoice-detail-page__status invoice-detail-page__status--issued';
1525
+ case InvoiceStatus.VOID:
1526
+ return 'invoice-detail-page__status invoice-detail-page__status--void';
1527
+ default:
1528
+ return 'invoice-detail-page__status invoice-detail-page__status--neutral';
1529
+ }
1530
+ }
1531
+ readRouteData(key, fallback) {
1532
+ for (const snapshot of [...this.route.snapshot.pathFromRoot].reverse()) {
1533
+ const value = snapshot.data?.[key];
1534
+ if (typeof value === 'string' && value.length > 0) {
1535
+ return value;
1536
+ }
1537
+ }
1538
+ return fallback;
1539
+ }
1479
1540
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: AgentInvoiceDetail, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
1480
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", 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 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" }] }); }
1541
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: AgentInvoiceDetail, isStandalone: true, selector: "rolatech-agent-invoice-detail", usesInheritance: true, ngImport: i0, template: "<section class=\"invoice-detail-page\">\n @if (loading()) {\n <section class=\"invoice-detail-page__hero invoice-detail-page__hero--loading\">\n <div class=\"invoice-detail-page__hero-copy\">\n <span class=\"invoice-detail-page__eyebrow\">{{ pageMeta.eyebrow }}</span>\n <h1 class=\"invoice-detail-page__title\">Loading invoice...</h1>\n </div>\n </section>\n } @else if (!invoice()) {\n <section class=\"invoice-detail-page__panel\">\n <h2 class=\"invoice-detail-page__panel-title\">Invoice not found</h2>\n <p class=\"invoice-detail-page__panel-copy\">The requested invoice could not be loaded for this workspace.</p>\n </section>\n } @else if (invoice(); as invoice) {\n <section class=\"invoice-detail-page__hero\">\n <div class=\"invoice-detail-page__hero-copy\">\n <a class=\"invoice-detail-page__back\" [routerLink]=\"pageMeta.baseLink\">\u2190 {{ pageMeta.collectionTitle }}</a>\n <span class=\"invoice-detail-page__eyebrow\">{{ pageMeta.eyebrow }}</span>\n <h1 class=\"invoice-detail-page__title\">{{ invoice.invoiceNumber || invoice.id }}</h1>\n <p class=\"invoice-detail-page__description\">\n Invoice for {{ invoice.fullName || invoice.firstName + ' ' + invoice.lastName }} created\n {{ invoice.createdAt | date: 'mediumDate' }}.\n </p>\n </div>\n\n <div class=\"invoice-detail-page__hero-side\">\n <span [class]=\"statusToneClass(invoice.status)\">{{ status[invoice.status] }}</span>\n <div class=\"invoice-detail-page__total\">{{ invoice.total | price }}</div>\n <div class=\"invoice-detail-page__note\">\n @if (invoice.dueAt) {\n Due {{ invoice.dueAt | date: 'mediumDate' }}\n } @else {\n Payable on receipt\n }\n </div>\n\n <div class=\"invoice-detail-page__hero-actions\">\n @if (invoice.pdfUrl) {\n <button mat-stroked-button (click)=\"openPdf()\">Preview PDF</button>\n }\n <button mat-flat-button (click)=\"edit()\">Edit invoice</button>\n </div>\n </div>\n </section>\n\n <section class=\"invoice-detail-page__content\">\n <div class=\"invoice-detail-page__main\">\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\n @if (invoice.lines; as lines) {\n <rolatech-invoice-lines [lines]=\"lines\"></rolatech-invoice-lines>\n }\n </div>\n\n <div class=\"invoice-detail-page__side\">\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\n <article class=\"invoice-detail-page__panel\">\n <h2 class=\"invoice-detail-page__panel-title\">Invoice note</h2>\n <p class=\"invoice-detail-page__panel-copy\">{{ invoice.note || 'No note added for this invoice.' }}</p>\n </article>\n </div>\n </section>\n }\n</section>\n", styles: [":host{display:block;padding:1rem;--rt-workspace-status-issued-surface: color-mix(in srgb, var(--rt-brand-color) 14%, transparent);--rt-workspace-status-issued-color: color-mix(in srgb, var(--rt-brand-color) 82%, var(--rt-text-primary) 18%);--rt-workspace-status-paid-surface: color-mix(in srgb, var(--rt-brand-color) 16%, var(--rt-base-background, #ffffff));--rt-workspace-status-paid-color: color-mix(in srgb, var(--rt-brand-color) 58%, var(--rt-text-primary) 42%)}.invoice-detail-page{display:flex;flex-direction:column;gap:1rem}.invoice-detail-page__hero,.invoice-detail-page__panel{border:1px solid var(--rt-border-color, rgba(15, 23, 42, .08));border-radius:1.5rem;background:color-mix(in srgb,var(--rt-raised-background, #ffffff) 96%,transparent);box-shadow:0 20px 48px -42px color-mix(in srgb,var(--rt-text-primary) 22%,transparent)}.invoice-detail-page__hero{display:grid;gap:1rem;padding:1.25rem;background:radial-gradient(circle at top left,color-mix(in srgb,var(--rt-brand-color) 14%,transparent),transparent 48%),linear-gradient(135deg,color-mix(in srgb,var(--rt-raised-background, #ffffff) 94%,transparent),color-mix(in srgb,var(--rt-base-background, #ffffff) 88%,var(--rt-brand-color) 12%)),var(--rt-base-background, #ffffff)}.invoice-detail-page__hero-copy,.invoice-detail-page__hero-side,.invoice-detail-page__hero-actions,.invoice-detail-page__main,.invoice-detail-page__side{display:flex;flex-direction:column;gap:1rem}.invoice-detail-page__back{color:var(--rt-text-secondary);font-weight:600;text-decoration:none}.invoice-detail-page__eyebrow{display:inline-flex;align-self:flex-start;border-radius:9999px;padding:.38rem .72rem;background:color-mix(in srgb,var(--rt-brand-color) 12%,transparent);color:var(--rt-brand-color);font-size:.74rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase}.invoice-detail-page__title{margin:0;color:var(--rt-text-primary);font-size:clamp(1.8rem,2.8vw,2.7rem);line-height:1.05;font-weight:800}.invoice-detail-page__description,.invoice-detail-page__note,.invoice-detail-page__panel-copy{margin:0;color:var(--rt-text-secondary);line-height:1.7}.invoice-detail-page__status{display:inline-flex;align-self:flex-start;border-radius:9999px;padding:.45rem .82rem;font-size:.8rem;font-weight:700}.invoice-detail-page__status--neutral{background:color-mix(in srgb,var(--rt-brand-color) 10%,transparent);color:var(--rt-brand-color)}.invoice-detail-page__status--issued{background:var(--rt-workspace-status-issued-surface);color:var(--rt-workspace-status-issued-color)}.invoice-detail-page__status--paid{background:var(--rt-workspace-status-paid-surface);color:var(--rt-workspace-status-paid-color)}.invoice-detail-page__status--void{background:color-mix(in srgb,var(--mat-sys-error, #b91c1c) 14%,transparent);color:var(--mat-sys-error, #b91c1c)}.invoice-detail-page__total{color:var(--rt-text-primary);font-size:clamp(1.8rem,2.4vw,2.3rem);font-weight:800}.invoice-detail-page__hero-actions{flex-wrap:wrap}.invoice-detail-page__content{display:grid;gap:1rem}.invoice-detail-page__panel{padding:1rem 1.1rem}.invoice-detail-page__panel-title{margin:0 0 .6rem;color:var(--rt-text-primary);font-size:1.05rem;font-weight:700}@media(min-width:1024px){.invoice-detail-page__hero{grid-template-columns:minmax(0,1.65fr) minmax(18rem,.85fr);align-items:start}.invoice-detail-page__content{grid-template-columns:minmax(0,1.65fr) minmax(20rem,.85fr);align-items:start}}@media(min-width:768px){:host{padding:1.25rem}}\n"], 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: "ngmodule", type: MatIconModule }, { kind: "ngmodule", type: MatMenuModule }, { 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: "pipe", type: i1.DatePipe, name: "date" }, { kind: "pipe", type: PricePipe, name: "price" }] }); }
1481
1542
  }
1482
1543
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: AgentInvoiceDetail, decorators: [{
1483
1544
  type: Component,
@@ -1490,8 +1551,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImpor
1490
1551
  InvoiceSummary,
1491
1552
  InvoiceLines,
1492
1553
  InvoiceUser,
1493
- ToolbarComponent,
1494
- ], 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 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" }]
1554
+ PricePipe,
1555
+ ], template: "<section class=\"invoice-detail-page\">\n @if (loading()) {\n <section class=\"invoice-detail-page__hero invoice-detail-page__hero--loading\">\n <div class=\"invoice-detail-page__hero-copy\">\n <span class=\"invoice-detail-page__eyebrow\">{{ pageMeta.eyebrow }}</span>\n <h1 class=\"invoice-detail-page__title\">Loading invoice...</h1>\n </div>\n </section>\n } @else if (!invoice()) {\n <section class=\"invoice-detail-page__panel\">\n <h2 class=\"invoice-detail-page__panel-title\">Invoice not found</h2>\n <p class=\"invoice-detail-page__panel-copy\">The requested invoice could not be loaded for this workspace.</p>\n </section>\n } @else if (invoice(); as invoice) {\n <section class=\"invoice-detail-page__hero\">\n <div class=\"invoice-detail-page__hero-copy\">\n <a class=\"invoice-detail-page__back\" [routerLink]=\"pageMeta.baseLink\">\u2190 {{ pageMeta.collectionTitle }}</a>\n <span class=\"invoice-detail-page__eyebrow\">{{ pageMeta.eyebrow }}</span>\n <h1 class=\"invoice-detail-page__title\">{{ invoice.invoiceNumber || invoice.id }}</h1>\n <p class=\"invoice-detail-page__description\">\n Invoice for {{ invoice.fullName || invoice.firstName + ' ' + invoice.lastName }} created\n {{ invoice.createdAt | date: 'mediumDate' }}.\n </p>\n </div>\n\n <div class=\"invoice-detail-page__hero-side\">\n <span [class]=\"statusToneClass(invoice.status)\">{{ status[invoice.status] }}</span>\n <div class=\"invoice-detail-page__total\">{{ invoice.total | price }}</div>\n <div class=\"invoice-detail-page__note\">\n @if (invoice.dueAt) {\n Due {{ invoice.dueAt | date: 'mediumDate' }}\n } @else {\n Payable on receipt\n }\n </div>\n\n <div class=\"invoice-detail-page__hero-actions\">\n @if (invoice.pdfUrl) {\n <button mat-stroked-button (click)=\"openPdf()\">Preview PDF</button>\n }\n <button mat-flat-button (click)=\"edit()\">Edit invoice</button>\n </div>\n </div>\n </section>\n\n <section class=\"invoice-detail-page__content\">\n <div class=\"invoice-detail-page__main\">\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\n @if (invoice.lines; as lines) {\n <rolatech-invoice-lines [lines]=\"lines\"></rolatech-invoice-lines>\n }\n </div>\n\n <div class=\"invoice-detail-page__side\">\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\n <article class=\"invoice-detail-page__panel\">\n <h2 class=\"invoice-detail-page__panel-title\">Invoice note</h2>\n <p class=\"invoice-detail-page__panel-copy\">{{ invoice.note || 'No note added for this invoice.' }}</p>\n </article>\n </div>\n </section>\n }\n</section>\n", styles: [":host{display:block;padding:1rem;--rt-workspace-status-issued-surface: color-mix(in srgb, var(--rt-brand-color) 14%, transparent);--rt-workspace-status-issued-color: color-mix(in srgb, var(--rt-brand-color) 82%, var(--rt-text-primary) 18%);--rt-workspace-status-paid-surface: color-mix(in srgb, var(--rt-brand-color) 16%, var(--rt-base-background, #ffffff));--rt-workspace-status-paid-color: color-mix(in srgb, var(--rt-brand-color) 58%, var(--rt-text-primary) 42%)}.invoice-detail-page{display:flex;flex-direction:column;gap:1rem}.invoice-detail-page__hero,.invoice-detail-page__panel{border:1px solid var(--rt-border-color, rgba(15, 23, 42, .08));border-radius:1.5rem;background:color-mix(in srgb,var(--rt-raised-background, #ffffff) 96%,transparent);box-shadow:0 20px 48px -42px color-mix(in srgb,var(--rt-text-primary) 22%,transparent)}.invoice-detail-page__hero{display:grid;gap:1rem;padding:1.25rem;background:radial-gradient(circle at top left,color-mix(in srgb,var(--rt-brand-color) 14%,transparent),transparent 48%),linear-gradient(135deg,color-mix(in srgb,var(--rt-raised-background, #ffffff) 94%,transparent),color-mix(in srgb,var(--rt-base-background, #ffffff) 88%,var(--rt-brand-color) 12%)),var(--rt-base-background, #ffffff)}.invoice-detail-page__hero-copy,.invoice-detail-page__hero-side,.invoice-detail-page__hero-actions,.invoice-detail-page__main,.invoice-detail-page__side{display:flex;flex-direction:column;gap:1rem}.invoice-detail-page__back{color:var(--rt-text-secondary);font-weight:600;text-decoration:none}.invoice-detail-page__eyebrow{display:inline-flex;align-self:flex-start;border-radius:9999px;padding:.38rem .72rem;background:color-mix(in srgb,var(--rt-brand-color) 12%,transparent);color:var(--rt-brand-color);font-size:.74rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase}.invoice-detail-page__title{margin:0;color:var(--rt-text-primary);font-size:clamp(1.8rem,2.8vw,2.7rem);line-height:1.05;font-weight:800}.invoice-detail-page__description,.invoice-detail-page__note,.invoice-detail-page__panel-copy{margin:0;color:var(--rt-text-secondary);line-height:1.7}.invoice-detail-page__status{display:inline-flex;align-self:flex-start;border-radius:9999px;padding:.45rem .82rem;font-size:.8rem;font-weight:700}.invoice-detail-page__status--neutral{background:color-mix(in srgb,var(--rt-brand-color) 10%,transparent);color:var(--rt-brand-color)}.invoice-detail-page__status--issued{background:var(--rt-workspace-status-issued-surface);color:var(--rt-workspace-status-issued-color)}.invoice-detail-page__status--paid{background:var(--rt-workspace-status-paid-surface);color:var(--rt-workspace-status-paid-color)}.invoice-detail-page__status--void{background:color-mix(in srgb,var(--mat-sys-error, #b91c1c) 14%,transparent);color:var(--mat-sys-error, #b91c1c)}.invoice-detail-page__total{color:var(--rt-text-primary);font-size:clamp(1.8rem,2.4vw,2.3rem);font-weight:800}.invoice-detail-page__hero-actions{flex-wrap:wrap}.invoice-detail-page__content{display:grid;gap:1rem}.invoice-detail-page__panel{padding:1rem 1.1rem}.invoice-detail-page__panel-title{margin:0 0 .6rem;color:var(--rt-text-primary);font-size:1.05rem;font-weight:700}@media(min-width:1024px){.invoice-detail-page__hero{grid-template-columns:minmax(0,1.65fr) minmax(18rem,.85fr);align-items:start}.invoice-detail-page__content{grid-template-columns:minmax(0,1.65fr) minmax(20rem,.85fr);align-items:start}}@media(min-width:768px){:host{padding:1.25rem}}\n"] }]
1495
1556
  }] });
1496
1557
 
1497
1558
  const agentInvoiceRoutes = [