From 5e20d3b4b8237a82f6413f5bb7fe803401342f03 Mon Sep 17 00:00:00 2001 From: Dafnik Date: Fri, 9 Feb 2024 13:35:54 +0100 Subject: [PATCH 01/13] feat: add stripe --- .eslintrc.json | 2 +- angular.json | 7 +- package.json | 1 + pnpm-lock.yaml | 12 +- src/app/_shared/services/error-interceptor.ts | 14 -- src/app/_shared/ui/footer/footer.component.ts | 10 +- .../ui/loading/app-progress-bar.component.ts | 7 +- src/app/_shared/waiterrobot-backend.d.ts | 54 +++++ .../dead-letters/dead-letters.component.ts | 2 +- .../system-notifications.component.ts | 2 +- .../tmp-notification-view.component.ts | 144 +++++++++--- .../tmp-notifications.component.ts | 23 +- .../tmp-notifications.routes.ts | 2 +- .../tmp-notifications.service.ts | 16 +- src/app/home/_admin/users/users.component.ts | 2 +- .../color/color-picker.component.ts | 23 +- .../scrollable-toolbar.component.ts | 2 +- .../_shared/services/user/my-user.service.ts | 3 +- src/app/home/_shared/stop-propagation.ts | 12 + .../bills/all-unpaid-reasons.component.ts | 2 +- src/app/home/bills/bill-info.component.ts | 52 +++-- src/app/home/bills/bills.component.ts | 2 +- src/app/home/bills/bills.layout.ts | 8 +- src/app/home/bills/bills.routes.ts | 5 +- src/app/home/events/events.component.ts | 2 +- src/app/home/home-theme.component.ts | 2 +- .../home/orders/orders/orders.component.html | 2 +- .../_services/organisations-stripe.service.ts | 100 +++++++++ .../_services/organisations-users.service.ts | 8 +- .../organisation-edit-settings.component.ts | 5 +- .../organisation-edit-stripe.component.ts | 206 ++++++++++++++++++ .../organisation-edit-users.component.ts | 135 ++++++------ .../organisation-edit.component.ts | 45 ++-- ...nisation-stripe-account-modal.component.ts | 78 +++++++ .../stripe-account-state-badge.component.ts | 57 +++++ .../organisations/organisations.component.ts | 37 +++- src/app/home/printers/printers.component.ts | 2 +- .../home/products/product-groups.component.ts | 2 +- .../products/products-by-group.component.ts | 2 +- src/app/home/products/products.component.ts | 2 +- src/app/home/products/products.routes.ts | 33 +-- src/app/home/tables/table-groups.component.ts | 2 +- src/app/home/tables/tables.component.ts | 2 +- src/app/home/tables/tables.layout.ts | 9 +- .../waiters/waiters-by-event.component.ts | 3 +- src/app/home/waiters/waiters.component.ts | 3 +- .../_shared/app-logo-with-text.component.ts | 2 +- src/assets/i18n/de.json | 6 + src/styles.scss | 33 ++- tsconfig.json | 13 +- 50 files changed, 938 insertions(+), 260 deletions(-) create mode 100644 src/app/home/_shared/stop-propagation.ts create mode 100644 src/app/home/organisations/_services/organisations-stripe.service.ts create mode 100644 src/app/home/organisations/organisation-edit/organisation-edit-stripe.component.ts create mode 100644 src/app/home/organisations/organisation-edit/organisation-stripe-account-modal.component.ts create mode 100644 src/app/home/organisations/organisation-edit/stripe-account-state-badge.component.ts diff --git a/.eslintrc.json b/.eslintrc.json index 6e4f1a4e..8118a607 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -29,7 +29,7 @@ ], "rules": { "@typescript-eslint/explicit-module-boundary-types": [ - "warn", + "off", { "allowArgumentsExplicitlyTypedAsAny": true } diff --git a/angular.json b/angular.json index 48337f61..81a1ce0e 100644 --- a/angular.json +++ b/angular.json @@ -25,7 +25,12 @@ "browser": "src/main.ts", "polyfills": ["zone.js", "@angular/localize/init"], "tsConfig": "tsconfig.app.json", - "assets": ["src/favicon.ico", "src/assets", "src/site.webmanifest"], + "assets": [ + "src/favicon.ico", + "src/assets", + "src/site.webmanifest", + {"glob": "**/*", "input": "node_modules/ng2-pdfjs-viewer/pdfjs", "output": "/assets/pdfjs"} + ], "styles": [ "src/styles.scss", "node_modules/dfx-helper/styles/helper.scss", diff --git a/package.json b/package.json index b9a59673..603d6935 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "dfx-translate": "4.0.0-beta.0", "html-to-image": "1.11.11", "jspdf": "2.5.1", + "ng2-pdfjs-viewer": "^17.0.3", "ngxtension": "2.0.0", "rxjs": "7.8.1", "tslib": "2.6.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5e23b719..de76ca64 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,7 +7,7 @@ settings: dependencies: '@angular-devkit/build-angular': specifier: ^17.0.10 - version: 17.0.10(@angular/compiler-cli@17.1.3)(@angular/localize@17.1.3)(@types/node@20.2.5)(karma@6.4.2)(typescript@5.3.3) + version: 17.0.10(@angular/compiler-cli@17.1.1)(@angular/localize@17.1.1)(@types/node@20.2.5)(karma@6.4.2)(typescript@5.3.3) '@angular/animations': specifier: 17.1.3 version: 17.1.3(@angular/core@17.1.3) @@ -222,7 +222,7 @@ packages: - chokidar dev: true - /@angular-devkit/build-angular@17.0.10(@angular/compiler-cli@17.1.3)(@angular/localize@17.1.3)(@types/node@20.2.5)(karma@6.4.2)(typescript@5.3.3): + /@angular-devkit/build-angular@17.0.10(@angular/compiler-cli@17.1.1)(@angular/localize@17.1.1)(@types/node@20.2.5)(karma@6.4.2)(typescript@5.3.3): resolution: {integrity: sha512-RWVu5Pdg6VdO3v1i0oI+HGr/NE4rhbNelM43w+9TqrzDtwmvckWsadSp0H88cPhQ4YGY5ldGKyQufO1UItR26w==} engines: {node: ^18.13.0 || >=20.9.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} peerDependencies: @@ -2681,8 +2681,8 @@ packages: resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} dev: false - /@schematics/angular@17.1.3: - resolution: {integrity: sha512-hmeasOvzmniy6urtzUKhEqGO67iPuLX/dVtkF4nWp2NTtcEKlvcJobNDMc+CTlX4+ZMPVOvmhDMQqrlfekZ+NQ==} + /@schematics/angular@17.1.1: + resolution: {integrity: sha512-1Wqefy1W9Y63g48Fp7BscL95V4U1seDGgZawH6DcJnytJVW89hazao7YREzLYfdoediuw7lU+OHJksWYe4VQww==} engines: {node: ^18.13.0 || >=20.9.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} dependencies: '@angular-devkit/core': 17.1.3 @@ -7525,8 +7525,8 @@ packages: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} dev: false - /ngxtension@2.0.0(@angular/common@17.1.3)(@angular/core@17.1.3)(@use-gesture/vanilla@10.3.0)(rxjs@7.8.1): - resolution: {integrity: sha512-S/MJ2rifemopwmq4rexfWmxnURGZeBxUwivG6ymbLusHsfHuQo821RzMl+fmJwMr4Yt5nLwUsralM7Vipd7/nQ==} + /ngxtension@1.12.0(@angular/common@17.1.1)(@angular/core@17.1.1)(@use-gesture/vanilla@10.3.0)(rxjs@7.8.1): + resolution: {integrity: sha512-zcipEnzFV7BRhqFOFR5cFL4RXaR1L32+F4fZJ+IK07LpqoIwk5A4oXV433r3XhDh1q8FykKzGnhlE5FdmGDsSA==} engines: {node: '>=18'} peerDependencies: '@angular/common': '>=16.0.0' diff --git a/src/app/_shared/services/error-interceptor.ts b/src/app/_shared/services/error-interceptor.ts index 28074bc5..79ef47da 100644 --- a/src/app/_shared/services/error-interceptor.ts +++ b/src/app/_shared/services/error-interceptor.ts @@ -13,21 +13,7 @@ export function errorInterceptor(req: HttpRequest, next: HttpHandlerFn) // Rate limit exceeded if (error?.status === 429) { notificationService.terror('ABOUT_SIGNIN_FAILED_RATE_LIMIT'); - return throwError(() => error); } - if ( - error?.status === 401 || - error?.status === 403 || - error?.status === 500 || - error?.status === 502 || - error?.status === 503 || - error?.status === 504 || - error?.status === 404 || - error?.status === 0 - ) { - return throwError(() => error); - } - notificationService.terror('REQUEST_ERROR'); return throwError(() => error); }), ); diff --git a/src/app/_shared/ui/footer/footer.component.ts b/src/app/_shared/ui/footer/footer.component.ts index bb3c0d88..fbb044c6 100644 --- a/src/app/_shared/ui/footer/footer.component.ts +++ b/src/app/_shared/ui/footer/footer.component.ts @@ -48,11 +48,8 @@ import {dfxTranslateSetLanguage, TranslateStore} from 'dfx-translate';
by
- @for (name of names; track name; let last = $last) { + @for (name of names; track name; let index = $index) {
- @if (last) { - & - }
{{ name }} Image
- @if (!last) { + @if (index < 1) { , } + @if (index === 1) { + & + }
} diff --git a/src/app/_shared/ui/loading/app-progress-bar.component.ts b/src/app/_shared/ui/loading/app-progress-bar.component.ts index 49e6fa5d..071b8825 100644 --- a/src/app/_shared/ui/loading/app-progress-bar.component.ts +++ b/src/app/_shared/ui/loading/app-progress-bar.component.ts @@ -1,9 +1,9 @@ -import {booleanAttribute, ChangeDetectionStrategy, Component, Input} from '@angular/core'; +import {booleanAttribute, ChangeDetectionStrategy, Component, input} from '@angular/core'; @Component({ template: `
- @if (!hidden) { + @if (show()) {
@@ -45,6 +45,5 @@ import {booleanAttribute, ChangeDetectionStrategy, Component, Input} from '@angu changeDetection: ChangeDetectionStrategy.OnPush, }) export class AppProgressBarComponent { - @Input({transform: booleanAttribute}) - hidden = false; + show = input(false, {transform: booleanAttribute}); } diff --git a/src/app/_shared/waiterrobot-backend.d.ts b/src/app/_shared/waiterrobot-backend.d.ts index 30e80ed5..5fd3b1ab 100644 --- a/src/app/_shared/waiterrobot-backend.d.ts +++ b/src/app/_shared/waiterrobot-backend.d.ts @@ -1,3 +1,4 @@ + /* tslint:disable */ /* * --------------------------------------------------------------- @@ -156,6 +157,21 @@ export interface UpdateTableGroupDto { color?: string; } +export interface UpdateStripeAccountDto { + id: string; + /** + * @minLength 4 + * @maxLength 40 + */ + name: string; + /** @format int64 */ + eventId?: number; +} + +export interface AlphabeticIdResponse { + id: string; +} + export interface UpdateProductDto { /** @format int64 */ id: number; @@ -689,6 +705,19 @@ export interface CreateTableGroupDto { eventId: number; } +export interface CreateStripeAccountDto { + /** + * @minLength 4 + * @maxLength 40 + */ + name: string; + /** @format int64 */ + organisationId: number; + /** @format int64 */ + eventId?: number; + businessType: 'COMPANY' | 'INDIVIDUAL' | 'NON_PROFIT'; +} + export interface CreateProductDto { /** * @minLength 1 @@ -1080,6 +1109,7 @@ export interface TempNotification { subject: string; body: string; bodyHTML?: string; + id: string; /** @format date-time */ createdAt: string; } @@ -1131,6 +1161,30 @@ export interface GetUserResponse { role: 'ADMIN' | 'USER'; } +export interface GetStripeAccountResponse { + id: string; + name: string; + /** @format int64 */ + organisationId: number; + event?: GetEventOrLocationMinResponse; + state: 'ONBOARDING' | 'ACTIVE'; +} + +export interface GetStripeAccountLinkResponse { + dashboardUrl?: string; + onboardingUrl?: string; +} + +export interface GetStripeAccountMaxResponse { + id: string; + name: string; + /** @format int64 */ + organisationId: number; + event?: GetEventOrLocationMinResponse; + state: 'ONBOARDING' | 'ACTIVE'; + link: GetStripeAccountLinkResponse; +} + export interface StatisticsTimelineDataEntryResponse { /** @format date-time */ name: string; diff --git a/src/app/home/_admin/dead-letters/dead-letters.component.ts b/src/app/home/_admin/dead-letters/dead-letters.component.ts index 19a3098c..6a6031cd 100644 --- a/src/app/home/_admin/dead-letters/dead-letters.component.ts +++ b/src/app/home/_admin/dead-letters/dead-letters.component.ts @@ -122,7 +122,7 @@ import {DeadLettersService} from './dead-letters.service';
- + `, selector: 'app-all-dead-letters', diff --git a/src/app/home/_admin/system-notifications/system-notifications.component.ts b/src/app/home/_admin/system-notifications/system-notifications.component.ts index 44f6fd92..6b83193d 100644 --- a/src/app/home/_admin/system-notifications/system-notifications.component.ts +++ b/src/app/home/_admin/system-notifications/system-notifications.component.ts @@ -132,7 +132,7 @@ import {SystemNotificationsService} from './_services/system-notifications.servi - + `, selector: 'app-all-system-notifications', diff --git a/src/app/home/_admin/tmp-notifications/tmp-notification-view.component.ts b/src/app/home/_admin/tmp-notifications/tmp-notification-view.component.ts index e3feb938..b3800d6a 100644 --- a/src/app/home/_admin/tmp-notifications/tmp-notification-view.component.ts +++ b/src/app/home/_admin/tmp-notifications/tmp-notification-view.component.ts @@ -1,14 +1,21 @@ import {DatePipe} from '@angular/common'; -import {Component, inject, OnInit, ViewEncapsulation} from '@angular/core'; +import {Component, computed, inject, ViewChild, ViewEncapsulation} from '@angular/core'; import {Router} from '@angular/router'; +import {filter, map, pipe, startWith, switchMap} from 'rxjs'; + import {AppBackButtonComponent} from '@home-shared/components/button/app-back-button.component'; import {ScrollableToolbarComponent} from '@home-shared/components/scrollable-toolbar.component'; -import {NgbNavModule} from '@ng-bootstrap/ng-bootstrap'; +import {NgbNavModule, NgbTooltip} from '@ng-bootstrap/ng-bootstrap'; import {AppProgressBarComponent} from '@shared/ui/loading/app-progress-bar.component'; +import {PdfJsViewerComponent, PdfJsViewerModule} from 'ng2-pdfjs-viewer'; +import {computedFrom} from 'ngxtension/computed-from'; +import {injectParams} from 'ngxtension/inject-params'; import {BiComponent} from 'dfx-bootstrap-icons'; import {DfxTr} from 'dfx-translate'; +import {cl_copy} from 'dfts-helper'; +import {DfxCutPipe} from 'dfx-helper'; import {TmpNotificationsService} from './tmp-notifications.service'; @@ -16,7 +23,7 @@ import {TmpNotificationsService} from './tmp-notifications.service'; template: ` @if (tmpNotification(); as it) {
-

TMP-Notification

+

TMP-Notification {{ it.id }}

@@ -33,33 +40,60 @@ import {TmpNotificationsService} from './tmp-notifications.service';
-

HTML

- - - -
+ @if (it.bodyHTML) { +

HTML

+ + + +
+ +
+ }

Text

-
-
{{ it.body }}
+
+
+
+ +
+
{{ it.body }}
+
+ + @if (tmpNotificationPdf(); as pdf) { +
+ +
+ }
} @@ -73,7 +107,10 @@ import {TmpNotificationsService} from './tmp-notifications.service'; } .json-data { margin-bottom: 0; + white-space: normal; /* Ensures normal text wrapping */ + word-wrap: break-word; /* Allows words to break and wrap */ } + /** Style email **/ .container { display: flex; @@ -104,17 +141,58 @@ import {TmpNotificationsService} from './tmp-notifications.service'; `, ], selector: 'app-tmp-notification-view', - imports: [DfxTr, BiComponent, ScrollableToolbarComponent, AppBackButtonComponent, DatePipe, AppProgressBarComponent, NgbNavModule], + imports: [ + DfxTr, + BiComponent, + ScrollableToolbarComponent, + AppBackButtonComponent, + DatePipe, + AppProgressBarComponent, + NgbNavModule, + PdfJsViewerModule, + NgbTooltip, + DfxCutPipe, + ], encapsulation: ViewEncapsulation.None, standalone: true, }) -export class TmpNotificationViewComponent implements OnInit { +export class TmpNotificationViewComponent { router = inject(Router); - tmpNotification = inject(TmpNotificationsService).single; + tmpNotificationService = inject(TmpNotificationsService); + + tmpNotification = computedFrom( + [injectParams('id')], + pipe( + map(([id]) => id), + filter((id): id is string => !!id), + switchMap((id) => this.tmpNotificationService.getSingle$(id)), + startWith(undefined), + ), + ); + + tmpNotificationPdf = computed(() => base64ToArrayBuffer(this.tmpNotification()!.body)); + + @ViewChild('pdfViewerAutoLoad') pdfViewerAutoLoad!: PdfJsViewerComponent; + + copy(it: string) { + cl_copy(it); + } +} + +function base64ToArrayBuffer(base64: string): Uint8Array | undefined { + try { + if (!/^(?:[A-Za-z0-9+/]{4})*?(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(base64)) { + return undefined; + } - ngOnInit(): void { - if (!this.tmpNotification()) { - void this.router.navigateByUrl('/tmp-notifications'); + const binary_string = window.atob(base64); + const len = binary_string.length; + const bytes = new Uint8Array(len); + for (let i = 0; i < len; i++) { + bytes[i] = binary_string.charCodeAt(i); } + return bytes; + } catch { + return undefined; } } diff --git a/src/app/home/_admin/tmp-notifications/tmp-notifications.component.ts b/src/app/home/_admin/tmp-notifications/tmp-notifications.component.ts index aac3c18d..13e40c8d 100644 --- a/src/app/home/_admin/tmp-notifications/tmp-notifications.component.ts +++ b/src/app/home/_admin/tmp-notifications/tmp-notifications.component.ts @@ -7,6 +7,7 @@ import {AbstractModelsListComponent} from '@home-shared/list/abstract-models-lis import {NgbTooltipModule} from '@ng-bootstrap/ng-bootstrap'; import {AppProgressBarComponent} from '@shared/ui/loading/app-progress-bar.component'; import {TempNotification} from '@shared/waiterrobot-backend'; +import {StopPropagationDirective} from '@home-shared/stop-propagation'; import {BiComponent} from 'dfx-bootstrap-icons'; import {DfxSortModule, DfxTableModule} from 'dfx-bootstrap-table'; @@ -39,6 +40,10 @@ import {TmpNotificationsService} from './tmp-notifications.service';
+ + + + @@ -56,15 +61,24 @@ import {TmpNotificationsService} from './tmp-notifications.service'; - + + + + + + - +
{{ 'Id' | tr }}{{ it.id | s_cut: 20 : '...' }} {{ 'To' | tr }} {{ it.to }}{{ 'HOME_ORDER_CREATED_AT' | tr }}{{ it.createdAt | date: 'dd.MM.YYYY HH:mm:ss:SSS' }}{{ it.createdAt | date: 'dd.MM.YY HH:mm:ss:SSS' }}{{ 'ACTIONS' | tr }} + + + +
- +
`, selector: 'app-all-dead-letters', @@ -82,11 +96,12 @@ import {TmpNotificationsService} from './tmp-notifications.service'; BiComponent, DfxCutPipe, AppProgressBarComponent, + StopPropagationDirective, ], }) export class TmpNotificationsComponent extends AbstractModelsListComponent { constructor(public deadLettersService: TmpNotificationsService) { super(deadLettersService); - this.columnsToDisplay = ['to', 'subject', 'body', 'createdAt']; + this.columnsToDisplay = ['id', 'to', 'subject', 'body', 'createdAt', 'actions']; } } diff --git a/src/app/home/_admin/tmp-notifications/tmp-notifications.routes.ts b/src/app/home/_admin/tmp-notifications/tmp-notifications.routes.ts index d8b75c63..0aa602b9 100644 --- a/src/app/home/_admin/tmp-notifications/tmp-notifications.routes.ts +++ b/src/app/home/_admin/tmp-notifications/tmp-notifications.routes.ts @@ -6,7 +6,7 @@ export const ROUTES: Routes = [ children: [ {path: 'all', loadComponent: () => import('./tmp-notifications.component').then((c) => c.TmpNotificationsComponent)}, { - path: 'view', + path: 'view/:id', loadComponent: () => import('./tmp-notification-view.component').then((c) => c.TmpNotificationViewComponent), }, {path: '', pathMatch: 'full', redirectTo: 'all'}, diff --git a/src/app/home/_admin/tmp-notifications/tmp-notifications.service.ts b/src/app/home/_admin/tmp-notifications/tmp-notifications.service.ts index 48cf4419..3310672e 100644 --- a/src/app/home/_admin/tmp-notifications/tmp-notifications.service.ts +++ b/src/app/home/_admin/tmp-notifications/tmp-notifications.service.ts @@ -1,20 +1,22 @@ import {HttpClient} from '@angular/common/http'; -import {inject, Injectable, signal} from '@angular/core'; +import {inject, Injectable} from '@angular/core'; -import {BehaviorSubject, Observable, switchMap} from 'rxjs'; +import {BehaviorSubject, map, Observable, switchMap} from 'rxjs'; import {TempNotification} from '../../../_shared/waiterrobot-backend'; @Injectable({providedIn: 'root'}) export class TmpNotificationsService { - private httpClient = inject(HttpClient); - url = '/public/temp-notifications'; + #httpClient = inject(HttpClient); + #url = '/public/temp-notifications'; - triggerGet$ = new BehaviorSubject(true); + #triggerGet$ = new BehaviorSubject(true); getAll$(): Observable { - return this.triggerGet$.pipe(switchMap(() => this.httpClient.get(`${this.url}`))); + return this.#triggerGet$.pipe(switchMap(() => this.#httpClient.get(`${this.#url}`))); } - single = signal(undefined); + getSingle$(id: string): Observable { + return this.getAll$().pipe(map((notifications) => notifications.find((it) => it.id === id))); + } } diff --git a/src/app/home/_admin/users/users.component.ts b/src/app/home/_admin/users/users.component.ts index 93409160..3f5278a4 100644 --- a/src/app/home/_admin/users/users.component.ts +++ b/src/app/home/_admin/users/users.component.ts @@ -100,7 +100,7 @@ import {UsersService} from './services/users.service'; - + `, selector: 'app-all-users', diff --git a/src/app/home/_shared/components/color/color-picker.component.ts b/src/app/home/_shared/components/color/color-picker.component.ts index 922998b5..8e3c6c21 100644 --- a/src/app/home/_shared/components/color/color-picker.component.ts +++ b/src/app/home/_shared/components/color/color-picker.component.ts @@ -10,10 +10,10 @@ import {AppIsLightColorPipe} from './app-is-light-color.pipe'; @Component({ template: ` -
+
+
+ +
diff --git a/src/app/home/_shared/components/scrollable-toolbar.component.ts b/src/app/home/_shared/components/scrollable-toolbar.component.ts index 817f99f8..71f55a60 100644 --- a/src/app/home/_shared/components/scrollable-toolbar.component.ts +++ b/src/app/home/_shared/components/scrollable-toolbar.component.ts @@ -5,7 +5,7 @@ import {AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, ViewChild
diff --git a/src/app/home/_shared/services/user/my-user.service.ts b/src/app/home/_shared/services/user/my-user.service.ts index e3b9278f..e23f4a69 100644 --- a/src/app/home/_shared/services/user/my-user.service.ts +++ b/src/app/home/_shared/services/user/my-user.service.ts @@ -6,7 +6,7 @@ import {catchError, EMPTY, filter, map, merge, Observable, of, Subject, switchMa import {connect} from 'ngxtension/connect'; -import {loggerOf, notNullAndUndefined} from 'dfts-helper'; +import {notNullAndUndefined} from 'dfts-helper'; import {AuthService} from '../../../../_shared/services/auth/auth.service'; import {GetMyselfResponse} from '../../../../_shared/waiterrobot-backend'; @@ -24,7 +24,6 @@ type MyUserState = { export class MyUserService { private httpClient = inject(HttpClient); private authStatus$ = inject(AuthService).status$; - private lumber = loggerOf('MyUserService'); private manualUserChange: Subject = new Subject(); diff --git a/src/app/home/_shared/stop-propagation.ts b/src/app/home/_shared/stop-propagation.ts new file mode 100644 index 00000000..482a0760 --- /dev/null +++ b/src/app/home/_shared/stop-propagation.ts @@ -0,0 +1,12 @@ +import {Directive, HostListener} from '@angular/core'; + +@Directive({ + selector: '[stopPropagation]', + standalone: true, +}) +export class StopPropagationDirective { + @HostListener('click', ['$event']) + onClick(event: MouseEvent): void { + event.stopPropagation(); + } +} diff --git a/src/app/home/bills/all-unpaid-reasons.component.ts b/src/app/home/bills/all-unpaid-reasons.component.ts index 2c39779a..d6e0c951 100644 --- a/src/app/home/bills/all-unpaid-reasons.component.ts +++ b/src/app/home/bills/all-unpaid-reasons.component.ts @@ -124,7 +124,7 @@ import {UnpaidReasonsService} from './_services/unpaid-reasons.service';
- + `, selector: 'app-all-unpaid-reasons', diff --git a/src/app/home/bills/bill-info.component.ts b/src/app/home/bills/bill-info.component.ts index e40b9dd0..21b694a9 100644 --- a/src/app/home/bills/bill-info.component.ts +++ b/src/app/home/bills/bill-info.component.ts @@ -26,34 +26,38 @@ import {BillsService} from './_services/bills.service'; - + - - - {{ vm.bill.createdAt | date: 'dd.MM.yy HH:mm:ss' }} - + - - - {{ vm.bill.table.group.name }} - {{ vm.bill.table.number }} - +
+ + + {{ vm.bill.createdAt | date: 'dd.MM.yy HH:mm:ss' }} + +
- - - {{ vm.bill.waiter.name }} - -
+ - - +
diff --git a/src/app/home/bills/bills.component.ts b/src/app/home/bills/bills.component.ts index 8090bc6a..c08e4d92 100644 --- a/src/app/home/bills/bills.component.ts +++ b/src/app/home/bills/bills.component.ts @@ -235,7 +235,7 @@ import {UnpaidReasonsService} from './_services/unpaid-reasons.service'; - + @if (!pagination.loading() && dataSource().length < 1) {
diff --git a/src/app/home/bills/bills.layout.ts b/src/app/home/bills/bills.layout.ts index da2d7082..c587d28a 100644 --- a/src/app/home/bills/bills.layout.ts +++ b/src/app/home/bills/bills.layout.ts @@ -8,18 +8,18 @@ import {EntitiesLayout} from '../_shared/layouts/entities.layout'; @Component({ template: ` - + `, selector: 'app-bills', standalone: true, diff --git a/src/app/home/bills/bills.routes.ts b/src/app/home/bills/bills.routes.ts index 3b8c3073..7f586169 100755 --- a/src/app/home/bills/bills.routes.ts +++ b/src/app/home/bills/bills.routes.ts @@ -6,8 +6,11 @@ export const ROUTES: Routes = [ loadComponent: () => import('./bills.layout').then((c) => c.BillsLayout), children: [ {path: 'all', loadComponent: () => import('./bills.component').then((c) => c.BillsComponent)}, - {path: 'unpaidReasons', loadComponent: () => import('./all-unpaid-reasons.component').then((c) => c.AllUnpaidReasonsComponent)}, {path: ':id', loadComponent: () => import('./bill-info.component').then((c) => c.BillInfoComponent)}, + { + path: 'reasons', + children: [{path: 'all', loadComponent: () => import('./all-unpaid-reasons.component').then((c) => c.AllUnpaidReasonsComponent)}], + }, {path: '', pathMatch: 'full', redirectTo: 'all'}, ], }, diff --git a/src/app/home/events/events.component.ts b/src/app/home/events/events.component.ts index 81130bde..d8ba5d2d 100644 --- a/src/app/home/events/events.component.ts +++ b/src/app/home/events/events.component.ts @@ -154,7 +154,7 @@ import {SelectedEventService} from './_services/selected-event.service';
- + `, selector: 'app-all-events', diff --git a/src/app/home/home-theme.component.ts b/src/app/home/home-theme.component.ts index 85c20eb1..8c46b7cc 100644 --- a/src/app/home/home-theme.component.ts +++ b/src/app/home/home-theme.component.ts @@ -8,7 +8,7 @@ import {BiComponent} from 'dfx-bootstrap-icons'; @Component({ template: ` `, diff --git a/src/app/home/orders/orders/orders.component.html b/src/app/home/orders/orders/orders.component.html index 04bbe98c..0c2fce05 100644 --- a/src/app/home/orders/orders/orders.component.html +++ b/src/app/home/orders/orders/orders.component.html @@ -232,7 +232,7 @@

{{ 'NAV_ORDERS' | tr }}

- + @if (!pagination.loading() && dataSource().length < 1) {
{{ 'HOME_STATISTICS_NO_DATA' | tr }}
diff --git a/src/app/home/organisations/_services/organisations-stripe.service.ts b/src/app/home/organisations/_services/organisations-stripe.service.ts new file mode 100644 index 00000000..aed3907b --- /dev/null +++ b/src/app/home/organisations/_services/organisations-stripe.service.ts @@ -0,0 +1,100 @@ +import {HttpClient} from '@angular/common/http'; +import {inject, Injectable} from '@angular/core'; + +import {catchError, concat, map, Observable, of, switchMap, tap} from 'rxjs'; + +import { + AlphabeticIdResponse, + CreateStripeAccountDto, + GetStripeAccountMaxResponse, + GetStripeAccountResponse, + UpdateStripeAccountDto, +} from '@shared/waiterrobot-backend'; +import {signalSlice} from 'ngxtension/signal-slice'; +import {NotificationService} from '@shared/notifications/notification.service'; + +import {injectWindow} from 'dfx-helper'; + +type OrganisationStripeState = { + loading: boolean; + organisationId: number | undefined; + data: GetStripeAccountResponse[] | undefined; +}; + +@Injectable({ + providedIn: 'root', +}) +export class OrganisationsStripeService { + #url = '/config/stripe/account'; + + #httpClient = inject(HttpClient); + #window = injectWindow(); + #notificationService = inject(NotificationService); + + #initialState: OrganisationStripeState = { + loading: true, + organisationId: undefined, + data: undefined, + }; + + #load$(organisationId: number) { + return this.#httpClient + .get(this.#url, {params: {organisationId}}) + .pipe(map((data) => ({organisationId, data, loading: false}))); + } + + #create$(dto: CreateStripeAccountDto) { + return this.#httpClient.post(this.#url, dto).pipe( + switchMap(({id}) => this.openLink$(id)), + map(() => ({})), + ); + } + + #update$(dto: UpdateStripeAccountDto) { + return this.#httpClient.put(this.#url, dto).pipe(map(() => ({}))); + } + + #delete$(id: string) { + return this.#httpClient.delete(`${this.#url}/${id}`).pipe( + map(() => ({})), + catchError(() => { + this.#notificationService.terror('STRIPE_ACCOUNT_BALANCE_NOT_EMPTY'); + return of({}); + }), + ); + } + + state = signalSlice({ + initialState: this.#initialState, + actionSources: { + load: (state, $: Observable) => + $.pipe(switchMap((organisationId) => concat(of({loading: true}), this.#load$(organisationId ?? state().organisationId!)))), + create: (state, action$: Observable) => + action$.pipe(switchMap((dto) => concat(of({loading: true}), this.#create$(dto), this.#load$(state().organisationId!)))), + update: (state, action$: Observable) => + action$.pipe(switchMap((dto) => concat(of({loading: true}), this.#update$(dto), this.#load$(state().organisationId!)))), + delete: (state, action$: Observable) => + action$.pipe( + switchMap((id) => + concat( + of({loading: true, data: state().data?.filter((it) => it.id !== id)}), + this.#delete$(id), + this.#load$(state().organisationId!), + ), + ), + ), + }, + }); + + openLink$(id: string) { + return this.#httpClient.get(`/config/stripe/account/${id}`).pipe( + tap((it) => { + if (it.state === 'ACTIVE') { + this.#window!.open(it.link.dashboardUrl, '_blank'); + return; + } + this.#window!.location.href = it.link.onboardingUrl!; + }), + ); + } +} diff --git a/src/app/home/organisations/_services/organisations-users.service.ts b/src/app/home/organisations/_services/organisations-users.service.ts index a4577d16..9dd838d6 100644 --- a/src/app/home/organisations/_services/organisations-users.service.ts +++ b/src/app/home/organisations/_services/organisations-users.service.ts @@ -3,8 +3,7 @@ import {Injectable} from '@angular/core'; import {BehaviorSubject, Observable, switchMap, tap} from 'rxjs'; -import {OrganisationUserDto, OrganisationUserResponse} from '../../../_shared/waiterrobot-backend'; -import {EventsService} from '../../events/_services/events.service'; +import {OrganisationUserDto, OrganisationUserResponse} from '@shared/waiterrobot-backend'; @Injectable({ providedIn: 'root', @@ -12,10 +11,7 @@ import {EventsService} from '../../events/_services/events.service'; export class OrganisationsUsersService { url = '/config/organisation/users'; - constructor( - private httpClient: HttpClient, - private eventsService: EventsService, - ) {} + constructor(private httpClient: HttpClient) {} triggerGet$ = new BehaviorSubject(true); diff --git a/src/app/home/organisations/organisation-edit/organisation-edit-settings.component.ts b/src/app/home/organisations/organisation-edit/organisation-edit-settings.component.ts index 8b7102a4..bbaa1664 100644 --- a/src/app/home/organisations/organisation-edit/organisation-edit-settings.component.ts +++ b/src/app/home/organisations/organisation-edit/organisation-edit-settings.component.ts @@ -32,11 +32,12 @@ import {OrganisationsSettingsService} from '../_services/organisations-settings.
- +
- @ + +
+
+ +
+
+
+ + @if ((filter.value?.length ?? 0) > 0) { + + } +
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + +
{{ 'NAME' | tr }} + {{ stripeAccount.name }} + @if (!stripeAccount.event) { + + } + {{ 'STATE' | tr }} + + {{ 'NAV_EVENTS' | tr }} + {{ stripeAccount.event?.name ?? '-' }} + {{ 'ACTIONS' | tr }} + + +
+
+ + @if (stripeState.data()?.length === 0) { +
+ {{ 'STRIPE_ACCOUNT_EMPTY' | tr }} +
+ } + + +
+ `, + selector: 'app-organisation-edit-stripe', + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ + BiComponent, + DfxTr, + NgbTooltip, + DfxTableModule, + DfxSortModule, + ReactiveFormsModule, + AppProgressBarComponent, + StripeAccountStateBadge, + ], +}) +export class OrganisationEditStripeComponent { + modal = inject(NgbModal); + confirmDialog = injectConfirmDialog(); + + stripeState = inject(OrganisationsStripeService).state; + + filter = new FormControl(''); + columnsToDisplay = ['name', 'state', 'event', 'actions']; + + @ViewChild(NgbSort, {static: true}) sort: NgbSort | undefined; + + idParam = injectParams('id'); + + dataSource = computedFrom( + [this.filter.valueChanges.pipe(startWith(''), filter(notNullAndUndefined)), this.stripeState.data], + pipe( + switchMap(([filterTerm, all]) => { + const dataSource = new NgbTableDataSource(all); + + if (this.sort) { + dataSource.sort = this.sort; + } + dataSource.filter = filterTerm ?? ''; + + return of(dataSource); + }), + startWith(new NgbTableDataSource()), + ), + ); + + constructor() { + effect( + () => { + void this.stripeState.load(Number(this.idParam())); + }, + {allowSignalWrites: true}, + ); + } + + onCreateStripeAccount(): void { + const modalRef = this.modal.open(OrganisationStripeAccountModal, { + ariaLabelledBy: 'modal-title-org-stripe-create', + size: 'lg', + }); + (modalRef.componentInstance as OrganisationStripeAccountModal).action = 'CREATE'; + (modalRef.componentInstance as OrganisationStripeAccountModal).organisationId = Number(this.idParam()); + (modalRef.componentInstance as OrganisationStripeAccountModal).existingStripeAccountCount = this.stripeState.data()?.length ?? 0; + modalRef.closed.subscribe((it?: CreateStripeAccountDto) => { + if (it) { + void this.stripeState.create(it); + } + }); + } + + onUpdateStripeAccount(stripeAccount: UpdateStripeAccountDto): void { + const modalRef = this.modal.open(OrganisationStripeAccountModal, { + ariaLabelledBy: 'modal-title-org-stripe-update', + size: 'lg', + }); + (modalRef.componentInstance as OrganisationStripeAccountModal).action = 'UPDATE'; + (modalRef.componentInstance as OrganisationStripeAccountModal).organisationId = Number(this.idParam()); + (modalRef.componentInstance as OrganisationStripeAccountModal).existingStripeAccountCount = this.stripeState.data()?.length ?? 0; + (modalRef.componentInstance as OrganisationStripeAccountModal).form.controls.name.patchValue(stripeAccount.name); + modalRef.closed.subscribe((it?: Omit) => { + if (it) { + void this.stripeState.update({ + ...it, + id: stripeAccount.id, + }); + } + }); + } + + onDeleteStripeAccount(id: string): void { + void this.confirmDialog('DELETE_CONFIRMATION').then((result) => { + if (result) { + void this.stripeState.delete(id); + } + }); + } +} diff --git a/src/app/home/organisations/organisation-edit/organisation-edit-users.component.ts b/src/app/home/organisations/organisation-edit/organisation-edit-users.component.ts index 98b25aac..606118cd 100644 --- a/src/app/home/organisations/organisation-edit/organisation-edit-users.component.ts +++ b/src/app/home/organisations/organisation-edit/organisation-edit-users.component.ts @@ -1,90 +1,92 @@ import {AsyncPipe} from '@angular/common'; import {ChangeDetectionStrategy, Component, inject, Input, ViewChild} from '@angular/core'; import {FormControl, ReactiveFormsModule} from '@angular/forms'; -import {ActivatedRoute} from '@angular/router'; import {combineLatest, filter, of, startWith, switchMap} from 'rxjs'; +import {injectConfirmDialog} from '@home-shared/components/question-dialog.component'; +import {injectIdParam$} from '@home-shared/services/injectActivatedRouteIdParam'; import {NgbModal, NgbTooltip} from '@ng-bootstrap/ng-bootstrap'; +import {GetOrganisationResponse, OrganisationUserResponse} from '@shared/waiterrobot-backend'; import {notNullAndUndefined} from 'dfts-helper'; import {BiComponent} from 'dfx-bootstrap-icons'; import {DfxSortModule, DfxTableModule, NgbSort, NgbTableDataSource} from 'dfx-bootstrap-table'; import {DfxTr} from 'dfx-translate'; -import {GetOrganisationResponse, OrganisationUserResponse} from '../../../_shared/waiterrobot-backend'; -import {injectConfirmDialog} from '../../_shared/components/question-dialog.component'; -import {injectIdParam$} from '../../_shared/services/injectActivatedRouteIdParam'; import {OrganisationsUsersService} from '../_services/organisations-users.service'; import {OrganisationUserAddModalComponent} from './organisation-user-add-modal.component'; @Component({ template: ` -
-
-
- - @if ((filter.value?.length ?? 0) > 0) { - - } +
+
+
+
- - - -
- -
- - - - - - - - - - - - - - - - - - - - - - - -
{{ 'NAME' | tr }}{{ organisationUser.firstname }} {{ organisationUser.surname }}{{ 'EMAIL' | tr }}{{ organisationUser.emailAddress }}{{ 'ROLE' | tr }} - {{ organisationUser.role }} - - - - {{ 'ACTIONS' | tr }} - @if (myUserEmailAddress !== organisationUser.email_address) { +
+
+ + @if ((filter.value?.length ?? 0) > 0) { } -
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + +
{{ 'NAME' | tr }}{{ organisationUser.firstname }} {{ organisationUser.surname }}{{ 'EMAIL' | tr }}{{ organisationUser.emailAddress }}{{ 'ROLE' | tr }} + {{ organisationUser.role }} + + + + {{ 'ACTIONS' | tr }} + @if (myUserEmailAddress !== organisationUser.email_address) { + + } +
+
`, selector: 'app-organisation-edit-users', @@ -95,15 +97,16 @@ import {OrganisationUserAddModalComponent} from './organisation-user-add-modal.c export class OrganisationEditUsersComponent { modal = inject(NgbModal); confirmDialog = injectConfirmDialog(); - route = inject(ActivatedRoute); organisationsUsersService = inject(OrganisationsUsersService); - entities$ = injectIdParam$().pipe(switchMap((id) => this.organisationsUsersService.getByOrganisationId$(id))); filter = new FormControl(''); columnsToDisplay = ['name', 'email', 'actions']; @ViewChild(NgbSort, {static: true}) sort: NgbSort | undefined; - dataSource$ = combineLatest([this.filter.valueChanges.pipe(startWith(''), filter(notNullAndUndefined)), this.entities$]).pipe( + dataSource$ = combineLatest([ + this.filter.valueChanges.pipe(startWith(''), filter(notNullAndUndefined)), + injectIdParam$().pipe(switchMap((id) => this.organisationsUsersService.getByOrganisationId$(id))), + ]).pipe( switchMap(([filterTerm, all]) => { const dataSource = new NgbTableDataSource(all); diff --git a/src/app/home/organisations/organisation-edit/organisation-edit.component.ts b/src/app/home/organisations/organisation-edit/organisation-edit.component.ts index f9ed4c5b..b15ed6fc 100644 --- a/src/app/home/organisations/organisation-edit/organisation-edit.component.ts +++ b/src/app/home/organisations/organisation-edit/organisation-edit.component.ts @@ -1,18 +1,19 @@ import {Component, computed, inject} from '@angular/core'; +import {AppSelectableBtnComponent} from '@home-shared/components/button/app-selectable-btn.component'; +import {AbstractModelEditComponent} from '@home-shared/form/abstract-model-edit.component'; +import {AppEntityEditModule} from '@home-shared/form/app-entity-edit.module'; +import {injectOnDelete, injectTabControls} from '@home-shared/form/edit'; +import {MyUserService} from '@home-shared/services/user/my-user.service'; import {NgbNavModule} from '@ng-bootstrap/ng-bootstrap'; +import {injectOnSubmit} from '@shared/form'; +import {GetOrganisationResponse} from '@shared/waiterrobot-backend'; -import {injectOnSubmit} from '../../../_shared/form'; -import {GetOrganisationResponse} from '../../../_shared/waiterrobot-backend'; -import {AppSelectableBtnComponent} from '../../_shared/components/button/app-selectable-btn.component'; -import {AbstractModelEditComponent} from '../../_shared/form/abstract-model-edit.component'; -import {AppEntityEditModule} from '../../_shared/form/app-entity-edit.module'; -import {injectOnDelete, injectTabControls} from '../../_shared/form/edit'; -import {MyUserService} from '../../_shared/services/user/my-user.service'; import {OrganisationsService} from '../_services/organisations.service'; import {SelectedOrganisationService} from '../_services/selected-organisation.service'; import {AppOrganisationEditFormComponent} from './organisation-edit-form.component'; import {OrganisationEditSettingsComponent} from './organisation-edit-settings.component'; +import {OrganisationEditStripeComponent} from './organisation-edit-stripe.component'; import {OrganisationEditUsersComponent} from './organisation-edit-users.component'; @Component({ @@ -44,7 +45,7 @@ import {OrganisationEditUsersComponent} from './organisation-edit-users.componen -
+
    - @if (myUser()?.isAdmin) { -
  • - {{ 'USER' | tr }} - - - - -
  • - } +
  • + {{ 'USER' | tr }} + + + + +
  • + +
  • + {{ 'STRIPE' | tr }} + + + +
  • {{ 'SETTINGS' | tr }} @@ -98,12 +104,13 @@ import {OrganisationEditUsersComponent} from './organisation-edit-users.componen AppOrganisationEditFormComponent, OrganisationEditUsersComponent, OrganisationEditSettingsComponent, + OrganisationEditStripeComponent, ], }) export class OrganisationEditComponent extends AbstractModelEditComponent { onSubmit = injectOnSubmit({entityService: this.organisationsService}); - tabControls = injectTabControls<'DATA' | 'USERS' | 'SETTINGS'>({ - onlyEditingTabs: ['USERS', 'SETTINGS'], + tabControls = injectTabControls<'DATA' | 'USERS' | 'SETTINGS' | 'STRIPE'>({ + onlyEditingTabs: ['USERS', 'SETTINGS', 'STRIPE'], defaultTab: 'DATA', isCreating: computed(() => this.entity() === 'CREATE'), }); diff --git a/src/app/home/organisations/organisation-edit/organisation-stripe-account-modal.component.ts b/src/app/home/organisations/organisation-edit/organisation-stripe-account-modal.component.ts new file mode 100644 index 00000000..d58526a3 --- /dev/null +++ b/src/app/home/organisations/organisation-edit/organisation-stripe-account-modal.component.ts @@ -0,0 +1,78 @@ +import {AsyncPipe, LowerCasePipe} from '@angular/common'; +import {ChangeDetectionStrategy, Component, inject} from '@angular/core'; +import {FormBuilder, ReactiveFormsModule, Validators} from '@angular/forms'; + +import {NgbActiveModal} from '@ng-bootstrap/ng-bootstrap'; +import {NgSelectModule} from '@ng-select/ng-select'; +import {allowedCharacterSet} from '@home-shared/regex'; +import {CreateStripeAccountDto} from '@shared/waiterrobot-backend'; + +import {BiComponent} from 'dfx-bootstrap-icons'; +import {DfxTr} from 'dfx-translate'; + +@Component({ + template: ` + + @if (form.valueChanges | async) {} +
    + + +
    + `, + selector: 'app-organisation-stripe-add-create', + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [DfxTr, LowerCasePipe, BiComponent, NgSelectModule, ReactiveFormsModule, AsyncPipe], +}) +export class OrganisationStripeAccountModal { + action!: 'CREATE' | 'UPDATE'; + organisationId!: number; + existingStripeAccountCount!: number; + + activeModal = inject(NgbActiveModal); + + form = inject(FormBuilder).nonNullable.group({ + name: [undefined as unknown as string, [Validators.minLength(4), Validators.maxLength(40), Validators.pattern(allowedCharacterSet)]], + businessType: ['NON_PROFIT' as CreateStripeAccountDto['businessType'], [Validators.required]], + eventId: [undefined as unknown as number], + }); + + submit(): void { + const name = + this.form.controls.name.value && this.form.controls.name.value !== '' + ? this.form.controls.name.value + : `Stripe Account #${this.existingStripeAccountCount + 1}`; + this.activeModal.close({ + ...this.form.getRawValue(), + name, + organisationId: this.organisationId, + }); + } +} diff --git a/src/app/home/organisations/organisation-edit/stripe-account-state-badge.component.ts b/src/app/home/organisations/organisation-edit/stripe-account-state-badge.component.ts new file mode 100644 index 00000000..61d2b7aa --- /dev/null +++ b/src/app/home/organisations/organisation-edit/stripe-account-state-badge.component.ts @@ -0,0 +1,57 @@ +import {Component, inject, input, signal} from '@angular/core'; + +import {GetStripeAccountResponse} from '@shared/waiterrobot-backend'; + +import {DfxTr} from 'dfx-translate'; +import {BiComponent} from 'dfx-bootstrap-icons'; + +import {OrganisationsStripeService} from '../_services/organisations-stripe.service'; + +@Component({ + template: ` + + `, + selector: 'app-stripe-account-state-badge', + imports: [DfxTr, BiComponent], + standalone: true, +}) +export class StripeAccountStateBadge { + stripeAccountId = input.required(); + state = input.required(); + + loading = signal(false); + + organisationStripeService = inject(OrganisationsStripeService); + + openLink() { + this.loading.set(true); + this.organisationStripeService.openLink$(this.stripeAccountId()).subscribe({ + next: (it) => { + if (it.state === 'ACTIVE') { + this.loading.set(false); + return; + } + }, + error: () => { + void this.organisationStripeService.state.load(undefined); + }, + }); + } +} diff --git a/src/app/home/organisations/organisations.component.ts b/src/app/home/organisations/organisations.component.ts index 3ad58476..2df760c2 100644 --- a/src/app/home/organisations/organisations.component.ts +++ b/src/app/home/organisations/organisations.component.ts @@ -4,13 +4,14 @@ import {ReactiveFormsModule} from '@angular/forms'; import {RouterLink} from '@angular/router'; import {NgbTooltip} from '@ng-bootstrap/ng-bootstrap'; +import {StopPropagationDirective} from '@home-shared/stop-propagation'; +import {AppProgressBarComponent} from '@shared/ui/loading/app-progress-bar.component'; +import {GetOrganisationResponse} from '@shared/waiterrobot-backend'; import {BiComponent} from 'dfx-bootstrap-icons'; import {DfxPaginationModule, DfxSortModule, DfxTableModule} from 'dfx-bootstrap-table'; import {DfxTr} from 'dfx-translate'; -import {AppProgressBarComponent} from '../../_shared/ui/loading/app-progress-bar.component'; -import {GetOrganisationResponse} from '../../_shared/waiterrobot-backend'; import {AppSelectableBtnComponent} from '../_shared/components/button/app-selectable-btn.component'; import {ScrollableToolbarComponent} from '../_shared/components/scrollable-toolbar.component'; import {AbstractModelsWithNameListWithDeleteComponent} from '../_shared/list/models-list-with-delete/abstract-models-with-name-list-with-delete.component'; @@ -119,8 +120,36 @@ import {SelectedOrganisationService} from './_services/selected-organisation.ser [selectedId]="selectedOrganisationService.selectedId()" (selectedChange)="setSelected($event)" /> + + + + + + + + + @@ -131,6 +160,7 @@ import {SelectedOrganisationService} from './_services/selected-organisation.ser type="button" class="btn btn-sm btn-outline-danger text-body-emphasis" ngbTooltip="{{ 'DELETE' | tr }}" + stopPropagation (click)="onDelete(organisation.id, $event)" > @@ -145,7 +175,7 @@ import {SelectedOrganisationService} from './_services/selected-organisation.ser
} - + `, selector: 'app-all-organisations', @@ -164,6 +194,7 @@ import {SelectedOrganisationService} from './_services/selected-organisation.ser AppSelectableBtnComponent, AsyncPipe, AppProgressBarComponent, + StopPropagationDirective, ], }) export class OrganisationsComponent extends AbstractModelsWithNameListWithDeleteComponent { diff --git a/src/app/home/printers/printers.component.ts b/src/app/home/printers/printers.component.ts index dfe584c9..7f61944c 100644 --- a/src/app/home/printers/printers.component.ts +++ b/src/app/home/printers/printers.component.ts @@ -147,7 +147,7 @@ import {PrinterBatchUpdateDto, PrintersBatchUpdateModal} from './printers-batch- - + `, selector: 'app-event-by-id-printers', diff --git a/src/app/home/products/product-groups.component.ts b/src/app/home/products/product-groups.component.ts index d5eb5f15..842f6249 100644 --- a/src/app/home/products/product-groups.component.ts +++ b/src/app/home/products/product-groups.component.ts @@ -171,7 +171,7 @@ import {ProductGroupsService} from './_services/product-groups.service'; - + `, styles: [AbstractModelsWithNameListWithDeleteAndOrderStyle], diff --git a/src/app/home/products/products-by-group.component.ts b/src/app/home/products/products-by-group.component.ts index 5fbbf2ef..dcfd3d81 100644 --- a/src/app/home/products/products-by-group.component.ts +++ b/src/app/home/products/products-by-group.component.ts @@ -216,7 +216,7 @@ import {ProductsService} from './_services/products.service'; } } - + `, styles: [AbstractModelsWithNameListWithDeleteAndOrderStyle], diff --git a/src/app/home/products/products.component.ts b/src/app/home/products/products.component.ts index 3c7d2d2a..10df15a4 100644 --- a/src/app/home/products/products.component.ts +++ b/src/app/home/products/products.component.ts @@ -174,7 +174,7 @@ import {ProductsService} from './_services/products.service'; - + `, selector: 'app-all-products', diff --git a/src/app/home/products/products.routes.ts b/src/app/home/products/products.routes.ts index a1854971..d5a1c30f 100644 --- a/src/app/home/products/products.routes.ts +++ b/src/app/home/products/products.routes.ts @@ -14,20 +14,25 @@ export const ROUTES: Routes = [ loadComponent: () => import('./product-edit/product-edit.component').then((c) => c.ProductEditComponent), }, { - path: 'groups/all', - loadComponent: () => import('./product-groups.component').then((c) => c.ProductGroupsComponent), - }, - { - path: 'groups/create', - loadComponent: () => import('./product-group-edit/product-group-edit.component').then((c) => c.ProductGroupEditComponent), - }, - { - path: 'groups/products/:id', - loadComponent: () => import('./products-by-group.component').then((c) => c.ProductsByGroupComponent), - }, - { - path: 'groups/:id', - loadComponent: () => import('./product-group-edit/product-group-edit.component').then((c) => c.ProductGroupEditComponent), + path: 'groups', + children: [ + { + path: 'all', + loadComponent: () => import('./product-groups.component').then((c) => c.ProductGroupsComponent), + }, + { + path: 'create', + loadComponent: () => import('./product-group-edit/product-group-edit.component').then((c) => c.ProductGroupEditComponent), + }, + { + path: 'products/:id', + loadComponent: () => import('./products-by-group.component').then((c) => c.ProductsByGroupComponent), + }, + { + path: ':id', + loadComponent: () => import('./product-group-edit/product-group-edit.component').then((c) => c.ProductGroupEditComponent), + }, + ], }, {path: '', pathMatch: 'full', redirectTo: 'all'}, ], diff --git a/src/app/home/tables/table-groups.component.ts b/src/app/home/tables/table-groups.component.ts index 1f8b629e..08646a0b 100644 --- a/src/app/home/tables/table-groups.component.ts +++ b/src/app/home/tables/table-groups.component.ts @@ -171,7 +171,7 @@ import {TableGroupsService} from './_services/table-groups.service'; } - + `, styles: [AbstractModelsWithNameListWithDeleteAndOrderStyle], diff --git a/src/app/home/tables/tables.component.ts b/src/app/home/tables/tables.component.ts index cf69144e..3b2366f1 100644 --- a/src/app/home/tables/tables.component.ts +++ b/src/app/home/tables/tables.component.ts @@ -175,7 +175,7 @@ import {TablesPrintQrCodesModal} from './tables-print-qr-codes.modal'; - + `, selector: 'app-all-tables', diff --git a/src/app/home/tables/tables.layout.ts b/src/app/home/tables/tables.layout.ts index d82f7478..a44d71ce 100644 --- a/src/app/home/tables/tables.layout.ts +++ b/src/app/home/tables/tables.layout.ts @@ -86,10 +86,9 @@ import {TableGroupsService} from './_services/table-groups.service'; export class TablesLayout { tableGroups = toSignal(inject(TableGroupsService).getAll$()); - allTablesAmount = computed( - () => - this.tableGroups() - ?.map((it) => it.tables.length) - ?.reduce((current, previous) => current + previous, 0), + allTablesAmount = computed(() => + this.tableGroups() + ?.map((it) => it.tables.length) + ?.reduce((current, previous) => current + previous, 0), ); } diff --git a/src/app/home/waiters/waiters-by-event.component.ts b/src/app/home/waiters/waiters-by-event.component.ts index 44e59af9..053ae202 100644 --- a/src/app/home/waiters/waiters-by-event.component.ts +++ b/src/app/home/waiters/waiters-by-event.component.ts @@ -138,6 +138,7 @@ import {BtnWaiterSignInQrCodeComponent} from './btn-waiter-sign-in-qr-code.compo class="btn btn-sm mx-1 btn-outline-success text-body-emphasis" routerLink="../../{{ waiter.id }}" ngbTooltip="{{ 'EDIT' | tr }}" + (click)="$event.stopPropagation()" > @@ -157,7 +158,7 @@ import {BtnWaiterSignInQrCodeComponent} from './btn-waiter-sign-in-qr-code.compo - + `, selector: 'app-event-by-id-waiters', diff --git a/src/app/home/waiters/waiters.component.ts b/src/app/home/waiters/waiters.component.ts index 8937d50d..43383e11 100644 --- a/src/app/home/waiters/waiters.component.ts +++ b/src/app/home/waiters/waiters.component.ts @@ -143,6 +143,7 @@ import {BtnWaiterSignInQrCodeComponent} from './btn-waiter-sign-in-qr-code.compo class="btn btn-sm mx-1 btn-outline-success text-body-emphasis" routerLink="../{{ waiter.id }}" ngbTooltip="{{ 'EDIT' | tr }}" + (click)="$event.stopPropagation()" > @@ -162,7 +163,7 @@ import {BtnWaiterSignInQrCodeComponent} from './btn-waiter-sign-in-qr-code.compo - + `, selector: 'app-organisation-waiters', diff --git a/src/app/outside/_shared/app-logo-with-text.component.ts b/src/app/outside/_shared/app-logo-with-text.component.ts index 65656ab0..c160a54d 100644 --- a/src/app/outside/_shared/app-logo-with-text.component.ts +++ b/src/app/outside/_shared/app-logo-with-text.component.ts @@ -6,7 +6,7 @@ import {ThemeService} from '../../_shared/services/theme.service'; @Component({ template: ` -
+
@if (!hideLogo) { kellner.team logo } diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json index 84e57325..a07722d6 100644 --- a/src/assets/i18n/de.json +++ b/src/assets/i18n/de.json @@ -64,6 +64,7 @@ "DELETED": "(gelöscht)", "PRICE": "Preis", "PRICE_PER_PIECE": "Preis / Stk.", + "SUCCESSFUL_ACTION": "Aktion erfolgreich.", "ORDER_MODE_SWITCH": "Anordnen", "COLOR_PICKER": "Farbe auswählen", @@ -77,6 +78,11 @@ "404": "Nicht gefunden", "404_TITLE": "Was du auch immer gesucht hast, wir haben es leider nicht gefunden.", + "STRIPE": "Stripe", + "STRIPE_ACCOUNT": "Stripe-Account", + "STRIPE_ACCOUNT_EMPTY": "Keine Stripe-Accounts vorhanden.", + "STRIPE_ACCOUNT_BALANCE_NOT_EMPTY": "Stripe-Account hat noch Einlagen.", + "ABOUT": "Über", "ABOUT_SIGNIN": "Anmelden", "ABOUT_SIGNIN_EMAIL_ADDRESS": "E-Mail Adresse", diff --git a/src/styles.scss b/src/styles.scss index b62adb9a..801f0f28 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -14,7 +14,7 @@ a:hover { } /* Fix vertical text alignment in buttons with icons */ -.btn > bi > svg { +bi > svg { vertical-align: -0.125em; } @@ -123,3 +123,34 @@ a:hover.badge { .ws-nowrap { white-space: nowrap; } + +@keyframes spin { + from { + transform: rotateY(0deg); + moz-transform: rotateY(0deg); //Firefox + ms-transform: rotateY(0deg); //Microsoft Browsers + } + to { + transform: rotateY(360deg); + moz-transform: rotateY(360deg); //Firefox + ms-transform: rotateY(360deg); //Microsoft Browsers + } +} +@-webkit-keyframes spin { + from { + -webkit-transform: rotateY(0deg); + } + to { + -webkit-transform: rotateY(360deg); + } +} +.spin { + animation-name: spin; + animation-timing-function: linear; + animation-iteration-count: infinite; + animation-duration: 3s; + -webkit-animation-name: spin; + -webkit-animation-timing-function: linear; + -webkit-animation-iteration-count: infinite; + -webkit-animation-duration: 3s; +} diff --git a/tsconfig.json b/tsconfig.json index 180fba8a..64033c4e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,7 @@ "paths": { "@shared/*": ["src/app/_shared/*"], "@home-shared/*": ["src/app/home/_shared/*"], - "@outside-shared/*": ["src/app/outside/_shared/*"] + "@outside-shared/*": ["src/app/outside/_shared/*"], }, "outDir": "./dist/out-tsc", "forceConsistentCasingInFileNames": true, @@ -21,17 +21,14 @@ "resolveJsonModule": true, "target": "ES2022", "module": "es2020", - "lib": [ - "es2018", - "dom" - ], + "lib": ["es2018", "dom"], "useDefineForClassFields": false, - "skipLibCheck": true + "skipLibCheck": true, }, "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": true, "strictInputAccessModifiers": true, - "strictTemplates": true - } + "strictTemplates": true, + }, } From e9e6d65f7aa7ed8c1bb27f2062bec5d30217dcb7 Mon Sep 17 00:00:00 2001 From: Dafnik Date: Thu, 15 Feb 2024 10:55:30 +0100 Subject: [PATCH 02/13] feat: application updates --- src/app/_shared/EnvironmentHelper.ts | 4 + .../_shared/services/system-info.service.ts | 2 + src/app/app.component.ts | 2 +- .../color/color-picker.component.ts | 18 +- src/app/home/_shared/services/filter.ts | 2 +- src/app/home/bills/bills.routes.ts | 2 +- src/app/home/home.layout.html | 4 +- .../printer-edit/printer-edit.component.ts | 71 +++-- .../product-edit-form.component.ts | 6 +- .../product-edit/product-edit.component.ts | 24 +- .../product-group-edit-form.component.ts | 8 +- .../product-group-edit.component.ts | 12 +- .../tables/table-edit/table-edit.component.ts | 9 +- .../table-group-edit.component.ts | 12 +- .../waiter-edit/waiter-edit.component.ts | 13 +- src/app/system-info.component.ts | 275 +++++++++--------- src/assets/i18n/de.json | 1 + 17 files changed, 246 insertions(+), 219 deletions(-) diff --git a/src/app/_shared/EnvironmentHelper.ts b/src/app/_shared/EnvironmentHelper.ts index 8a77c693..cd6fa9d7 100644 --- a/src/app/_shared/EnvironmentHelper.ts +++ b/src/app/_shared/EnvironmentHelper.ts @@ -1,6 +1,10 @@ import {environment} from '../../environments/environment'; export class EnvironmentHelper { + public static get() { + return environment; + } + public static getAPIUrl(): string { return environment.apiUrl; } diff --git a/src/app/_shared/services/system-info.service.ts b/src/app/_shared/services/system-info.service.ts index b7d47dd8..cfe95851 100644 --- a/src/app/_shared/services/system-info.service.ts +++ b/src/app/_shared/services/system-info.service.ts @@ -8,6 +8,8 @@ import {connect} from 'ngxtension/connect'; import {b_fromStorage, st_set} from 'dfts-helper'; + + import {AdminInfoResponse, JsonInfoResponse} from '../waiterrobot-backend'; import {AuthService} from './auth/auth.service'; diff --git a/src/app/app.component.ts b/src/app/app.component.ts index ccd0d7aa..c524938d 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -15,7 +15,7 @@ import {SystemInfoComponent} from './system-info.component';
- +
diff --git a/src/app/home/_shared/components/color/color-picker.component.ts b/src/app/home/_shared/components/color/color-picker.component.ts index 8e3c6c21..7c772c30 100644 --- a/src/app/home/_shared/components/color/color-picker.component.ts +++ b/src/app/home/_shared/components/color/color-picker.component.ts @@ -33,16 +33,14 @@ import {AppIsLightColorPipe} from './app-is-light-color.pipe'; {{ 'COLOR_PICKER' | tr }} -
- -
+ diff --git a/src/app/home/_shared/services/filter.ts b/src/app/home/_shared/services/filter.ts index 5b7c9adc..cc451437 100644 --- a/src/app/home/_shared/services/filter.ts +++ b/src/app/home/_shared/services/filter.ts @@ -56,7 +56,7 @@ export function injectFilter< ); // Subscribe to form to set params on change - valueChanges.pipe(takeUntilDestroyed(), distinctUntilChanged()).subscribe((queryParams) => { + form.valueChanges.pipe(takeUntilDestroyed(), distinctUntilChanged()).subscribe((queryParams) => { lumber.info('filterFormValueChanges', 'Set query params', queryParams); void router.navigate([], { relativeTo: route, diff --git a/src/app/home/bills/bills.routes.ts b/src/app/home/bills/bills.routes.ts index 7f586169..1b08c948 100755 --- a/src/app/home/bills/bills.routes.ts +++ b/src/app/home/bills/bills.routes.ts @@ -6,11 +6,11 @@ export const ROUTES: Routes = [ loadComponent: () => import('./bills.layout').then((c) => c.BillsLayout), children: [ {path: 'all', loadComponent: () => import('./bills.component').then((c) => c.BillsComponent)}, - {path: ':id', loadComponent: () => import('./bill-info.component').then((c) => c.BillInfoComponent)}, { path: 'reasons', children: [{path: 'all', loadComponent: () => import('./all-unpaid-reasons.component').then((c) => c.AllUnpaidReasonsComponent)}], }, + {path: ':id', loadComponent: () => import('./bill-info.component').then((c) => c.BillInfoComponent)}, {path: '', pathMatch: 'full', redirectTo: 'all'}, ], }, diff --git a/src/app/home/home.layout.html b/src/app/home/home.layout.html index 8cdaf270..38ace952 100644 --- a/src/app/home/home.layout.html +++ b/src/app/home/home.layout.html @@ -5,7 +5,7 @@ @if (isMobile()) { @if (showEnvironmentType && environmentType === 'dev') {
- DEV + LOCAL
} @if (myUserService.user()?.isAdmin) {
@@ -35,7 +35,7 @@
`, selector: 'app-all-organisations', diff --git a/src/app/home/printers/printers.layout.ts b/src/app/home/printers/printers.layout.ts index d22579ad..47dcfbfc 100644 --- a/src/app/home/printers/printers.layout.ts +++ b/src/app/home/printers/printers.layout.ts @@ -11,11 +11,11 @@ import {EntitiesLayout} from '../_shared/layouts/entities.layout';
- + {{ 'NAV_PRINTERS' | tr }} - + {{ 'HOME_PRINTER_NAV_MEDIATOR' | tr }} diff --git a/src/app/home/printers/printers.routes.ts b/src/app/home/printers/printers.routes.ts index 249ea061..e9d982ea 100755 --- a/src/app/home/printers/printers.routes.ts +++ b/src/app/home/printers/printers.routes.ts @@ -7,17 +7,22 @@ export const ROUTES: Routes = [ children: [ { path: 'mediators', - loadComponent: () => import('./mediators.component').then((c) => c.MediatorsComponent), + children: [ + { + path: 'all', + loadComponent: () => import('./mediators.component').then((c) => c.MediatorsComponent), + }, + ], }, { - path: 'printers', + path: 'all', loadComponent: () => import('./printers.component').then((c) => c.PrintersComponent), }, { path: ':id', loadComponent: () => import('./printer-edit/printer-edit.component').then((c) => c.PrinterEditComponent), }, - {path: '', pathMatch: 'full', redirectTo: 'printers'}, + {path: '', pathMatch: 'full', redirectTo: 'all'}, ], }, ]; diff --git a/src/app/home/products/product-edit/product-edit-form.component.ts b/src/app/home/products/product-edit/product-edit-form.component.ts index fddbd986..e8befa19 100644 --- a/src/app/home/products/product-edit/product-edit-form.component.ts +++ b/src/app/home/products/product-edit/product-edit-form.component.ts @@ -1,6 +1,8 @@ import {ChangeDetectionStrategy, Component, Input} from '@angular/core'; import {ReactiveFormsModule, Validators} from '@angular/forms'; +import {RouterLink} from '@angular/router'; +import {NgbTooltip} from '@ng-bootstrap/ng-bootstrap'; import {NgSelectModule} from '@ng-select/ng-select'; import {a_pluck, HasNumberIDAndName, n_from, s_from} from 'dfts-helper'; @@ -65,9 +67,21 @@ import {allowedCharacterSet} from '../../_shared/regex';
- - - + @if (isCreating()) { + + + + } @else { + + + + } @for (printer of this.printers; track printer.id) { @@ -151,7 +177,7 @@ import {allowedCharacterSet} from '../../_shared/regex'; `, selector: 'app-product-edit-form', - imports: [ReactiveFormsModule, DfxTr, BiComponent, NgSelectModule, AppModelEditSaveBtn], + imports: [ReactiveFormsModule, DfxTr, BiComponent, NgSelectModule, AppModelEditSaveBtn, RouterLink, NgbTooltip], standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, }) diff --git a/src/app/home/products/product-edit/product-edit.component.ts b/src/app/home/products/product-edit/product-edit.component.ts index 2797064d..a46fd598 100644 --- a/src/app/home/products/product-edit/product-edit.component.ts +++ b/src/app/home/products/product-edit/product-edit.component.ts @@ -4,16 +4,16 @@ import {RouterLink} from '@angular/router'; import {filter, map} from 'rxjs'; -import {n_from, n_isNumeric} from 'dfts-helper'; +import {AbstractModelEditComponent} from '@home-shared/form/abstract-model-edit.component'; +import {AppContinuesCreationSwitchComponent} from '@home-shared/form/app-continues-creation-switch.component'; +import {AppDeletedDirectives} from '@home-shared/form/app-entity-deleted.directives'; +import {AppEntityEditModule} from '@home-shared/form/app-entity-edit.module'; +import {injectContinuousCreation, injectOnDelete} from '@home-shared/form/edit'; +import {injectOnSubmit} from '@shared/form'; +import {GetProductMaxResponse} from '@shared/waiterrobot-backend'; +import {n_from, n_isNumeric} from 'dfts-helper'; -import {injectOnSubmit} from '../../../_shared/form'; -import {GetProductMaxResponse} from '../../../_shared/waiterrobot-backend'; -import {AbstractModelEditComponent} from '../../_shared/form/abstract-model-edit.component'; -import {AppContinuesCreationSwitchComponent} from '../../_shared/form/app-continues-creation-switch.component'; -import {AppDeletedDirectives} from '../../_shared/form/app-entity-deleted.directives'; -import {AppEntityEditModule} from '../../_shared/form/app-entity-edit.module'; -import {injectContinuousCreation, injectOnDelete} from '../../_shared/form/edit'; import {SelectedEventService} from '../../events/_services/selected-event.service'; import {PrintersService} from '../../printers/_services/printers.service'; import {AllergensService} from '../_services/allergens.service'; @@ -39,20 +39,6 @@ import {AppProductEditFormComponent} from './product-edit-form.component';
- - - - } @else { } diff --git a/src/app/home/statistics/statistics-overview.component.ts b/src/app/home/statistics/statistics.component.ts similarity index 98% rename from src/app/home/statistics/statistics-overview.component.ts rename to src/app/home/statistics/statistics.component.ts index 2d578d84..916cfdee 100644 --- a/src/app/home/statistics/statistics-overview.component.ts +++ b/src/app/home/statistics/statistics.component.ts @@ -83,6 +83,6 @@ import {StatisticsService} from './statistics.service'; `, selector: 'app-statistics-overview', }) -export class StatisticsOverviewComponent { +export class StatisticsComponent { countDto$ = inject(StatisticsService).counts; } diff --git a/src/app/home/statistics/statistics.module.ts b/src/app/home/statistics/statistics.module.ts index db3a7144..4499f573 100755 --- a/src/app/home/statistics/statistics.module.ts +++ b/src/app/home/statistics/statistics.module.ts @@ -22,15 +22,12 @@ import {SumProductsPerWaiterComponent} from './components/sum-products-per-waite import {SumProductsComponent} from './components/sum-products.component'; import {SumStatisticsComponent} from './components/sum/sum-statistics.component'; import {TimelineComponent} from './components/timeline.component'; -import {StatisticsOverviewComponent} from './statistics-overview.component'; +import {StatisticsComponent} from './statistics.component'; const routes: Routes = [ { path: '', - children: [ - {path: 'overview', component: StatisticsOverviewComponent}, - {path: '', pathMatch: 'full', redirectTo: 'overview'}, - ], + children: [{path: '', component: StatisticsComponent}], }, {path: 'products', component: SumProductsComponent}, ]; @@ -38,7 +35,7 @@ const routes: Routes = [ @NgModule({ declarations: [ CountCardComponent, - StatisticsOverviewComponent, + StatisticsComponent, SumProductGroupsComponent, SumProductsComponent, SumProductsPerWaiterComponent, diff --git a/src/app/home/tables/table-edit/table-edit-form.component.ts b/src/app/home/tables/table-edit/table-edit-form.component.ts index 4d345bba..639bc579 100644 --- a/src/app/home/tables/table-edit/table-edit-form.component.ts +++ b/src/app/home/tables/table-edit/table-edit-form.component.ts @@ -1,11 +1,13 @@ import {AsyncPipe} from '@angular/common'; import {ChangeDetectionStrategy, Component, inject, Input} from '@angular/core'; import {ReactiveFormsModule, Validators} from '@angular/forms'; +import {RouterLink} from '@angular/router'; import {debounceTime, filter, map, switchMap, tap} from 'rxjs'; import {AbstractModelEditFormComponent} from '@home-shared/form/abstract-model-edit-form.component'; import {AppModelEditSaveBtn} from '@home-shared/form/app-model-edit-save-btn.component'; +import {NgbTooltip} from '@ng-bootstrap/ng-bootstrap'; import {injectIsValid} from '@shared/form'; import {CreateTableDto, GetTableWithGroupResponse, UpdateTableDto} from '@shared/waiterrobot-backend'; @@ -51,9 +53,22 @@ import {TablesService} from '../_services/tables.service';
- - - + @if (isCreating()) { + + + + } @else { + + + + } + + @if ((filter.value?.length ?? 0) > 0) { - } @@ -49,7 +43,7 @@ import {DeadLettersService} from './dead-letters.service';
- +
@@ -68,8 +62,8 @@ import {DeadLettersService} from './dead-letters.service'; class="form-check-input" type="checkbox" name="checked" - (change)="$event ? selection.toggle(selectable) : null" [checked]="selection.isSelected(selectable)" + (change)="$event ? selection.toggle(selectable) : null" /> @@ -103,13 +97,13 @@ import {DeadLettersService} from './dead-letters.service'; - +
@@ -57,8 +51,8 @@ import {DeadLettersService} from './dead-letters.service'; class="form-check-input" type="checkbox" name="checked" - (change)="$event ? toggleAllRows() : null" [checked]="selection.hasValue() && isAllSelected()" + (change)="$event ? toggleAllRows() : null" />
{{ 'ACTIONS' | tr }} - +
diff --git a/src/app/home/_admin/dead-letters/dead-letters.service.ts b/src/app/home/_admin/dead-letters/dead-letters.service.ts index b6960827..f00f4d50 100644 --- a/src/app/home/_admin/dead-letters/dead-letters.service.ts +++ b/src/app/home/_admin/dead-letters/dead-letters.service.ts @@ -1,23 +1,22 @@ import {HttpClient} from '@angular/common/http'; -import {Injectable} from '@angular/core'; +import {inject, Injectable} from '@angular/core'; -import {BehaviorSubject, map, Observable, switchMap, tap} from 'rxjs'; +import {DeadLetterResponse} from '@shared/waiterrobot-backend'; import {s_from} from 'dfts-helper'; import {HasDelete, HasGetAll, HasGetSingle} from 'dfx-helper'; -import {DeadLetterResponse} from '../../../_shared/waiterrobot-backend'; +import {BehaviorSubject, map, Observable, switchMap, tap} from 'rxjs'; @Injectable({providedIn: 'root'}) export class DeadLettersService implements HasGetAll, HasGetSingle, HasDelete { url = '/config/dead-letter'; - - constructor(private httpClient: HttpClient) {} + #httpClient = inject(HttpClient); triggerGet$ = new BehaviorSubject(true); getAll$(): Observable { - return this.triggerGet$.pipe(switchMap(() => this.httpClient.get(`${this.url}`))); + return this.triggerGet$.pipe(switchMap(() => this.#httpClient.get(this.url))); } getSingle$(id: number): Observable { @@ -25,7 +24,7 @@ export class DeadLettersService implements HasGetAll, HasGet } delete$(id: number): Observable { - return this.httpClient.delete(`${this.url}/${s_from(id)}`).pipe( + return this.#httpClient.delete(`${this.url}/${s_from(id)}`).pipe( tap(() => { this.triggerGet$.next(true); }), diff --git a/src/app/home/_admin/system-notifications/_components/system-notification-alert.component.ts b/src/app/home/_admin/system-notifications/_components/system-notification-alert.component.ts index fea1c656..628b9f31 100644 --- a/src/app/home/_admin/system-notifications/_components/system-notification-alert.component.ts +++ b/src/app/home/_admin/system-notifications/_components/system-notification-alert.component.ts @@ -1,14 +1,15 @@ import {DatePipe, NgClass} from '@angular/common'; import {ChangeDetectionStrategy, Component, EventEmitter, Input, Output} from '@angular/core'; +import {GetSystemNotificationResponse} from '@shared/waiterrobot-backend'; + import {BiComponent} from 'dfx-bootstrap-icons'; import {DfxLowerCaseExceptFirstLettersPipe} from 'dfx-helper'; -import {GetSystemNotificationResponse} from '../../../../_shared/waiterrobot-backend'; - @Component({ template: `
@@ -86,5 +86,5 @@ export class AppSystemNotificationAlertComponent { } @Output() - ignore = new EventEmitter(); + readonly ignore = new EventEmitter(); } diff --git a/src/app/home/_admin/system-notifications/_components/system-notification-type-badge.component.ts b/src/app/home/_admin/system-notifications/_components/system-notification-type-badge.component.ts index 98922577..9c59f4e6 100644 --- a/src/app/home/_admin/system-notifications/_components/system-notification-type-badge.component.ts +++ b/src/app/home/_admin/system-notifications/_components/system-notification-type-badge.component.ts @@ -1,14 +1,16 @@ import {NgClass} from '@angular/common'; import {ChangeDetectionStrategy, Component, Input} from '@angular/core'; +import {GetSystemNotificationResponse} from '@shared/waiterrobot-backend'; + import {BiComponent} from 'dfx-bootstrap-icons'; import {DfxLowerCaseExceptFirstLettersPipe} from 'dfx-helper'; -import {GetSystemNotificationResponse} from '../../../../_shared/waiterrobot-backend'; - @Component({ template: `
@switch (type) { @case ('INFO') { diff --git a/src/app/home/_admin/system-notifications/_services/active-system-notifications.service.ts b/src/app/home/_admin/system-notifications/_services/active-system-notifications.service.ts index 9c69ec8e..f713a24b 100644 --- a/src/app/home/_admin/system-notifications/_services/active-system-notifications.service.ts +++ b/src/app/home/_admin/system-notifications/_services/active-system-notifications.service.ts @@ -1,30 +1,29 @@ import {HttpClient} from '@angular/common/http'; -import {computed, Injectable, signal} from '@angular/core'; +import {computed, inject, Injectable, signal} from '@angular/core'; import {toSignal} from '@angular/core/rxjs-interop'; -import {BehaviorSubject, catchError, EMPTY, switchMap} from 'rxjs'; +import {GetSystemNotificationResponse} from '@shared/waiterrobot-backend'; import {s_fromStorage, st_remove, st_set} from 'dfts-helper'; -import {GetSystemNotificationResponse} from '../../../../_shared/waiterrobot-backend'; +import {BehaviorSubject, catchError, EMPTY, switchMap} from 'rxjs'; @Injectable({providedIn: 'root'}) export class ActiveSystemNotificationsService { url = '/system-notification'; storageKey = 'ignoredSystemNotifications'; - - constructor(private httpClient: HttpClient) {} + #httpClient = inject(HttpClient); triggerGet$ = new BehaviorSubject(true); allSystemNotifications = toSignal( this.triggerGet$.pipe( - switchMap(() => this.httpClient.get(this.url)), + switchMap(() => this.#httpClient.get(this.url)), catchError(() => EMPTY), ), {initialValue: []}, ); - ignoredSystemNotifications = signal((JSON.parse(s_fromStorage(this.storageKey) ?? '[]') as number[]) ?? []); + ignoredSystemNotifications = signal(JSON.parse(s_fromStorage(this.storageKey) ?? '[]') as number[]); getFilteredSystemNotifications = computed(() => this.allSystemNotifications().filter((it) => !this.ignoredSystemNotifications().includes(it.id)), diff --git a/src/app/home/_admin/system-notifications/_services/system-notifications.service.ts b/src/app/home/_admin/system-notifications/_services/system-notifications.service.ts index 55b8aaf3..f874dc3c 100644 --- a/src/app/home/_admin/system-notifications/_services/system-notifications.service.ts +++ b/src/app/home/_admin/system-notifications/_services/system-notifications.service.ts @@ -1,18 +1,18 @@ import {HttpClient} from '@angular/common/http'; -import {Injectable} from '@angular/core'; +import {inject, Injectable} from '@angular/core'; -import {BehaviorSubject, map, Observable, switchMap, tap} from 'rxjs'; - -import {s_from} from 'dfts-helper'; -import {HasDelete, HasGetAll, HasGetSingle} from 'dfx-helper'; - -import {HasCreateWithIdResponse, HasUpdateWithIdResponse} from '../../../../_shared/services/services.interface'; +import {HasCreateWithIdResponse, HasUpdateWithIdResponse} from '@shared/services/services.interface'; import { CreateSystemNotificationDto, GetSystemNotificationResponse, IdResponse, UpdateSystemNotificationDto, -} from '../../../../_shared/waiterrobot-backend'; +} from '@shared/waiterrobot-backend'; + +import {s_from} from 'dfts-helper'; +import {HasDelete, HasGetAll, HasGetSingle} from 'dfx-helper'; + +import {BehaviorSubject, map, Observable, switchMap, tap} from 'rxjs'; import {ActiveSystemNotificationsService} from './active-system-notifications.service'; @Injectable({providedIn: 'root'}) @@ -25,16 +25,13 @@ export class SystemNotificationsService HasDelete { url = '/system-notification'; - - constructor( - private httpClient: HttpClient, - private activeSystemNotificationsService: ActiveSystemNotificationsService, - ) {} + #httpClient = inject(HttpClient); + #activeSystemNotificationsService = inject(ActiveSystemNotificationsService); triggerGet$ = new BehaviorSubject(true); getAll$(): Observable { - return this.triggerGet$.pipe(switchMap(() => this.httpClient.get(`${this.url}/all`))); + return this.triggerGet$.pipe(switchMap(() => this.#httpClient.get(`${this.url}/all`))); } getSingle$(id: number): Observable { @@ -42,28 +39,28 @@ export class SystemNotificationsService } create$(dto: CreateSystemNotificationDto): Observable { - return this.httpClient.post(this.url, dto).pipe( + return this.#httpClient.post(this.url, dto).pipe( tap(() => { this.triggerGet$.next(true); - this.activeSystemNotificationsService.triggerGet$.next(true); + this.#activeSystemNotificationsService.triggerGet$.next(true); }), ); } update$(dto: UpdateSystemNotificationDto): Observable { - return this.httpClient.put(this.url, dto).pipe( + return this.#httpClient.put(this.url, dto).pipe( tap(() => { this.triggerGet$.next(true); - this.activeSystemNotificationsService.triggerGet$.next(true); + this.#activeSystemNotificationsService.triggerGet$.next(true); }), ); } delete$(id: number): Observable { - return this.httpClient.delete(`${this.url}/${s_from(id)}`).pipe( + return this.#httpClient.delete(`${this.url}/${s_from(id)}`).pipe( tap(() => { this.triggerGet$.next(true); - this.activeSystemNotificationsService.triggerGet$.next(true); + this.#activeSystemNotificationsService.triggerGet$.next(true); }), ); } diff --git a/src/app/home/_admin/system-notifications/system-notification-edit/system-notification-edit-form.component.ts b/src/app/home/_admin/system-notifications/system-notification-edit/system-notification-edit-form.component.ts index f3c7b496..f6c97e4d 100644 --- a/src/app/home/_admin/system-notifications/system-notification-edit/system-notification-edit-form.component.ts +++ b/src/app/home/_admin/system-notifications/system-notification-edit/system-notification-edit-form.component.ts @@ -2,30 +2,26 @@ import {TextFieldModule} from '@angular/cdk/text-field'; import {ChangeDetectionStrategy, Component, Input} from '@angular/core'; import {FormControl, ReactiveFormsModule, Validators} from '@angular/forms'; +import {AppDatetimeInputComponent} from '@home-shared/components/datetime-picker/datetime-picker.component'; +import {AbstractModelEditFormComponent} from '@home-shared/form/abstract-model-edit-form.component'; +import {AppModelEditSaveBtn} from '@home-shared/form/app-model-edit-save-btn.component'; +import {injectIsValid} from '@shared/form'; +import {CreateSystemNotificationDto, GetSystemNotificationResponse, UpdateSystemNotificationDto} from '@shared/waiterrobot-backend'; + import {BiComponent} from 'dfx-bootstrap-icons'; import {DfxLowerCaseExceptFirstLettersPipe} from 'dfx-helper'; import {DfxTr} from 'dfx-translate'; - -import {AppDatetimeInputComponent} from '../../../_shared/components/datetime-picker/datetime-picker.component'; -import {AbstractModelEditFormComponent} from '../../../_shared/form/abstract-model-edit-form.component'; -import {AppModelEditSaveBtn} from '../../../_shared/form/app-model-edit-save-btn.component'; -import {injectIsValid} from '../../../../_shared/form'; -import { - CreateSystemNotificationDto, - GetSystemNotificationResponse, - UpdateSystemNotificationDto, -} from '../../../../_shared/waiterrobot-backend'; import {systemNotificationTypes} from '../_services/system-notifications.service'; @Component({ template: ` @if (isValid()) {} -
+
- + @if (form.controls.title.invalid) { @@ -41,7 +37,7 @@ import {systemNotificationTypes} from '../_services/system-notifications.service @if (form.controls.description.invalid) { diff --git a/src/app/home/_admin/system-notifications/system-notification-edit/system-notification-edit.component.ts b/src/app/home/_admin/system-notifications/system-notification-edit/system-notification-edit.component.ts index a9820639..24ba7a63 100644 --- a/src/app/home/_admin/system-notifications/system-notification-edit/system-notification-edit.component.ts +++ b/src/app/home/_admin/system-notifications/system-notification-edit/system-notification-edit.component.ts @@ -19,7 +19,7 @@ import {SystemNotificationEditFormComponent} from './system-notification-edit-fo
- @@ -30,9 +30,9 @@ import {SystemNotificationEditFormComponent} from './system-notification-edit-fo
} @else { diff --git a/src/app/home/_admin/system-notifications/system-notifications.component.ts b/src/app/home/_admin/system-notifications/system-notifications.component.ts index 6b83193d..829c314a 100644 --- a/src/app/home/_admin/system-notifications/system-notifications.component.ts +++ b/src/app/home/_admin/system-notifications/system-notifications.component.ts @@ -2,18 +2,18 @@ import {AsyncPipe, DatePipe} from '@angular/common'; import {ChangeDetectionStrategy, Component} from '@angular/core'; import {ReactiveFormsModule} from '@angular/forms'; import {RouterLink} from '@angular/router'; +import {ScrollableToolbarComponent} from '@home-shared/components/scrollable-toolbar.component'; +import {AbstractModelsListWithDeleteComponent} from '@home-shared/list/models-list-with-delete/abstract-models-list-with-delete.component'; +import {AppActivatedPipe} from '@home-shared/pipes/app-activated.pipe'; import {NgbTooltipModule} from '@ng-bootstrap/ng-bootstrap'; +import {AppProgressBarComponent} from '@shared/ui/loading/app-progress-bar.component'; +import {GetSystemNotificationResponse} from '@shared/waiterrobot-backend'; + import {BiComponent} from 'dfx-bootstrap-icons'; import {DfxSortModule, DfxTableModule} from 'dfx-bootstrap-table'; import {DfxTr} from 'dfx-translate'; - -import {AppProgressBarComponent} from '../../../_shared/ui/loading/app-progress-bar.component'; -import {GetSystemNotificationResponse} from '../../../_shared/waiterrobot-backend'; -import {ScrollableToolbarComponent} from '../../_shared/components/scrollable-toolbar.component'; -import {AbstractModelsListWithDeleteComponent} from '../../_shared/list/models-list-with-delete/abstract-models-list-with-delete.component'; -import {AppActivatedPipe} from '../../_shared/pipes/app-activated.pipe'; import {AppSystemNotificationTypeBadgeComponent} from './_components/system-notification-type-badge.component'; import {SystemNotificationsService} from './_services/system-notifications.service'; @@ -30,7 +30,7 @@ import {SystemNotificationsService} from './_services/system-notifications.servi >
- @@ -39,15 +39,9 @@ import {SystemNotificationsService} from './_services/system-notifications.servi
- + @if ((filter.value?.length ?? 0) > 0) { - } @@ -55,7 +49,7 @@ import {SystemNotificationsService} from './_services/system-notifications.servi
- +
@@ -74,8 +68,8 @@ import {SystemNotificationsService} from './_services/system-notifications.servi class="form-check-input" type="checkbox" name="checked" - (change)="$event ? selection.toggle(selectable) : null" [checked]="selection.isSelected(selectable)" + (change)="$event ? selection.toggle(selectable) : null" /> @@ -113,13 +107,13 @@ import {SystemNotificationsService} from './_services/system-notifications.servi - +
@@ -63,8 +57,8 @@ import {SystemNotificationsService} from './_services/system-notifications.servi class="form-check-input" type="checkbox" name="checked" - (change)="$event ? toggleAllRows() : null" [checked]="selection.hasValue() && isAllSelected()" + (change)="$event ? toggleAllRows() : null" />
{{ 'ACTIONS' | tr }} - +
diff --git a/src/app/home/_admin/tmp-notifications/tmp-notification-view.component.ts b/src/app/home/_admin/tmp-notifications/tmp-notification-view.component.ts index b3800d6a..0e943878 100644 --- a/src/app/home/_admin/tmp-notifications/tmp-notification-view.component.ts +++ b/src/app/home/_admin/tmp-notifications/tmp-notification-view.component.ts @@ -2,20 +2,20 @@ import {DatePipe} from '@angular/common'; import {Component, computed, inject, ViewChild, ViewEncapsulation} from '@angular/core'; import {Router} from '@angular/router'; -import {filter, map, pipe, startWith, switchMap} from 'rxjs'; - import {AppBackButtonComponent} from '@home-shared/components/button/app-back-button.component'; import {ScrollableToolbarComponent} from '@home-shared/components/scrollable-toolbar.component'; import {NgbNavModule, NgbTooltip} from '@ng-bootstrap/ng-bootstrap'; import {AppProgressBarComponent} from '@shared/ui/loading/app-progress-bar.component'; +import {cl_copy} from 'dfts-helper'; + +import {BiComponent} from 'dfx-bootstrap-icons'; +import {DfxCutPipe} from 'dfx-helper'; +import {DfxTr} from 'dfx-translate'; import {PdfJsViewerComponent, PdfJsViewerModule} from 'ng2-pdfjs-viewer'; import {computedFrom} from 'ngxtension/computed-from'; import {injectParams} from 'ngxtension/inject-params'; -import {BiComponent} from 'dfx-bootstrap-icons'; -import {DfxTr} from 'dfx-translate'; -import {cl_copy} from 'dfts-helper'; -import {DfxCutPipe} from 'dfx-helper'; +import {filter, map, pipe, startWith, switchMap} from 'rxjs'; import {TmpNotificationsService} from './tmp-notifications.service'; @@ -43,13 +43,13 @@ import {TmpNotificationsService} from './tmp-notifications.service'; @if (it.bodyHTML) {

HTML

-