@rolatech/angular-offering 19.1.0-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.
@@ -0,0 +1,814 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, ElementRef, input, booleanAttribute, effect, HostBinding, ViewEncapsulation, Component, viewChild, model, output } from '@angular/core';
3
+ import { RouterLink, Router, ActivatedRoute, RouterLinkActive, RouterOutlet } from '@angular/router';
4
+ import * as i5 from '@angular/material/button';
5
+ import { MatButtonModule } from '@angular/material/button';
6
+ import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
7
+ import { MatSnackBar } from '@angular/material/snack-bar';
8
+ import { MatDialog } from '@angular/material/dialog';
9
+ import { OfferingService, CategoryService } from '@rolatech/angular-services';
10
+ import * as i1 from '@angular/common';
11
+ import { CommonModule } from '@angular/common';
12
+ import * as i7 from '@angular/material/menu';
13
+ import { MatMenuModule } from '@angular/material/menu';
14
+ import { FixedPipe, DecimalDirective } from '@rolatech/angular-common';
15
+ import { ThumbnailComponent, ImagePlaceholderComponent, ToolbarComponent, SpinnerComponent, BaseComponent, ImagePreviewDialogComponent, ConfirmationDialogComponent, MediaListComponent, MediaListItemComponent } from '@rolatech/angular-components';
16
+ import * as i2 from '@angular/material/icon';
17
+ import { MatIconModule } from '@angular/material/icon';
18
+ import { MatTableModule } from '@angular/material/table';
19
+ import * as i4 from '@angular/cdk/text-field';
20
+ import { TextFieldModule } from '@angular/cdk/text-field';
21
+ import * as i1$1 from '@angular/forms';
22
+ import { FormsModule } from '@angular/forms';
23
+ import { MatOptionModule, MAT_DATE_LOCALE, DateAdapter, MAT_DATE_FORMATS } from '@angular/material/core';
24
+ import * as i2$1 from '@angular/material/form-field';
25
+ import { MatFormFieldModule } from '@angular/material/form-field';
26
+ import * as i3 from '@angular/material/input';
27
+ import { MatInputModule } from '@angular/material/input';
28
+ import * as i5$1 from '@angular/material/select';
29
+ import { MatSelectModule } from '@angular/material/select';
30
+ import { MomentDateAdapter } from '@angular/material-moment-adapter';
31
+ import { MatDatepickerModule } from '@angular/material/datepicker';
32
+ import { findLastIndex, first, remove } from 'lodash';
33
+ import * as i9 from '@angular/material/divider';
34
+ import { MatDividerModule } from '@angular/material/divider';
35
+ import * as i8 from '@angular/material/progress-bar';
36
+ import { MatProgressBarModule } from '@angular/material/progress-bar';
37
+ import { trigger, state, transition, style, animate } from '@angular/animations';
38
+
39
+ var OfferingStatus;
40
+ (function (OfferingStatus) {
41
+ OfferingStatus[OfferingStatus["DRAFT"] = 'Draft'] = "DRAFT";
42
+ OfferingStatus[OfferingStatus["PENDING"] = 'Pending'] = "PENDING";
43
+ OfferingStatus[OfferingStatus["APPROVED"] = 'Approved'] = "APPROVED";
44
+ OfferingStatus[OfferingStatus["REJECTED"] = 'Rejected'] = "REJECTED";
45
+ OfferingStatus[OfferingStatus["AVAILABLE"] = 'Available'] = "AVAILABLE";
46
+ OfferingStatus[OfferingStatus["SOLD"] = 'Sold'] = "SOLD";
47
+ OfferingStatus[OfferingStatus["RENTED"] = 'Rented'] = "RENTED";
48
+ OfferingStatus[OfferingStatus["DELETED"] = 'Deleted'] = "DELETED";
49
+ OfferingStatus[OfferingStatus["ARCHIVED"] = 'Archived'] = "ARCHIVED";
50
+ })(OfferingStatus || (OfferingStatus = {}));
51
+ var OfferingType;
52
+ (function (OfferingType) {
53
+ OfferingType["VISIA"] = "Visa";
54
+ OfferingType["PROPERTY"] = "Property";
55
+ })(OfferingType || (OfferingType = {}));
56
+
57
+ class OfferingManageItemComponent {
58
+ hasClass = true;
59
+ el = inject(ElementRef);
60
+ offering = input.required();
61
+ thumbnail = input();
62
+ status = OfferingStatus;
63
+ list = input(false, { transform: booleanAttribute });
64
+ constructor() {
65
+ effect(() => {
66
+ if (this.list()) {
67
+ this.el.nativeElement.setAttribute('list', '');
68
+ }
69
+ else {
70
+ this.el.nativeElement.removeAttribute('list', '');
71
+ }
72
+ });
73
+ }
74
+ publish() { }
75
+ archived() { }
76
+ delete() { }
77
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: OfferingManageItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
78
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: OfferingManageItemComponent, isStandalone: true, selector: "rolatech-offering-manage-item", inputs: { offering: { classPropertyName: "offering", publicName: "offering", isSignal: true, isRequired: true, transformFunction: null }, thumbnail: { classPropertyName: "thumbnail", publicName: "thumbnail", isSignal: true, isRequired: false, transformFunction: null }, list: { classPropertyName: "list", publicName: "list", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.rolatech-offering-manage-item": "this.hasClass" } }, ngImport: i0, template: "<div\n class=\"flex p-3 bg-[--rt-raised-background] hover:bg-[--rt-raised-background] cursor-pointer rounded-md\"\n [ngClass]=\"list() ? 'w-full flex-row' : 'flex-col h-full'\"\n>\n <div>\n @if (offering().media) {\n <div class=\"object-cover aspect-video rounded-lg\" [ngClass]=\"list() ? 'w-32 mr-3' : ''\">\n @defer (on viewport()) {\n <rolatech-thumbnail [src]=\"offering().media ? offering().media[0].url : ''\" size=\"medium\" mode=\"full\">\n </rolatech-thumbnail>\n } @placeholder {\n <div class=\"bg-[--rt-raised-background] h-full w-full object-cover aspect-video rounded-lg\"></div>\n }\n </div>\n } @else {\n <div class=\"object-cover aspect-video rounded-lg\" [ngClass]=\"list() ? 'w-32 mr-3' : ''\">\n <rolatech-image-placeholder></rolatech-image-placeholder>\n </div>\n }\n </div>\n\n <div class=\"py-2\">\n <div class=\"text-lg font-bold\">\n {{ offering().title }}\n </div>\n </div>\n <div class=\"flex-1\"></div>\n <div class=\"\">\u00A5{{ offering().price | fixed }}</div>\n</div>\n", styles: ["rolatech-offering-manage-item{--rt-offering-view-item-margin: 16px;width:calc(100% / var(--rt-offering-view-items-per-row) - var(--rt-offering-view-item-margin) - .01px)}rolatech-offering-manage-item[list]{--rt-offering-view-items-per-row: 1}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: ImagePlaceholderComponent, selector: "rolatech-image-placeholder" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "ngmodule", type: MatMenuModule }, { kind: "pipe", type: FixedPipe, name: "fixed" }], encapsulation: i0.ViewEncapsulation.None, deferBlockDependencies: [() => [ThumbnailComponent]] });
79
+ }
80
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: OfferingManageItemComponent, decorators: [{
81
+ type: Component,
82
+ args: [{ selector: 'rolatech-offering-manage-item', imports: [CommonModule, ThumbnailComponent, ImagePlaceholderComponent, MatButtonModule, MatMenuModule, FixedPipe], encapsulation: ViewEncapsulation.None, template: "<div\n class=\"flex p-3 bg-[--rt-raised-background] hover:bg-[--rt-raised-background] cursor-pointer rounded-md\"\n [ngClass]=\"list() ? 'w-full flex-row' : 'flex-col h-full'\"\n>\n <div>\n @if (offering().media) {\n <div class=\"object-cover aspect-video rounded-lg\" [ngClass]=\"list() ? 'w-32 mr-3' : ''\">\n @defer (on viewport()) {\n <rolatech-thumbnail [src]=\"offering().media ? offering().media[0].url : ''\" size=\"medium\" mode=\"full\">\n </rolatech-thumbnail>\n } @placeholder {\n <div class=\"bg-[--rt-raised-background] h-full w-full object-cover aspect-video rounded-lg\"></div>\n }\n </div>\n } @else {\n <div class=\"object-cover aspect-video rounded-lg\" [ngClass]=\"list() ? 'w-32 mr-3' : ''\">\n <rolatech-image-placeholder></rolatech-image-placeholder>\n </div>\n }\n </div>\n\n <div class=\"py-2\">\n <div class=\"text-lg font-bold\">\n {{ offering().title }}\n </div>\n </div>\n <div class=\"flex-1\"></div>\n <div class=\"\">\u00A5{{ offering().price | fixed }}</div>\n</div>\n", styles: ["rolatech-offering-manage-item{--rt-offering-view-item-margin: 16px;width:calc(100% / var(--rt-offering-view-items-per-row) - var(--rt-offering-view-item-margin) - .01px)}rolatech-offering-manage-item[list]{--rt-offering-view-items-per-row: 1}\n"] }]
83
+ }], ctorParameters: () => [], propDecorators: { hasClass: [{
84
+ type: HostBinding,
85
+ args: ['class.rolatech-offering-manage-item']
86
+ }] } });
87
+
88
+ class OfferingManageFilterComponent {
89
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: OfferingManageFilterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
90
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.0.3", type: OfferingManageFilterComponent, isStandalone: true, selector: "rolatech-offering-manage-filter", ngImport: i0, template: "<p>offering-manage-filter works!</p>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
91
+ }
92
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: OfferingManageFilterComponent, decorators: [{
93
+ type: Component,
94
+ args: [{ selector: 'rolatech-offering-manage-filter', imports: [CommonModule], template: "<p>offering-manage-filter works!</p>\n" }]
95
+ }] });
96
+
97
+ class OfferingManageIndexComponent {
98
+ hasClass = true;
99
+ paginator = viewChild(MatPaginator);
100
+ dialog = inject(MatDialog);
101
+ snackBar = inject(MatSnackBar);
102
+ offeringService = inject(OfferingService);
103
+ isLoading = false;
104
+ isSearch = false;
105
+ offerings = [];
106
+ ngOnInit() {
107
+ this.findProperties();
108
+ }
109
+ findProperties() {
110
+ this.offeringService.find({}).subscribe({
111
+ next: (res) => {
112
+ this.offerings = res.data;
113
+ },
114
+ });
115
+ }
116
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: OfferingManageIndexComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
117
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: OfferingManageIndexComponent, isStandalone: true, selector: "rolatech-offering-manage-index", host: { properties: { "class.rolatech-offering-manage-index": "this.hasClass" } }, viewQueries: [{ propertyName: "paginator", first: true, predicate: MatPaginator, descendants: true, isSignal: true }], ngImport: i0, template: "<rolatech-toolbar title=\"Services\">\n <div class=\"flex items-center gap-2\">\n <button mat-flat-button routerLink=\"./create\">\n <mat-icon>add</mat-icon>\n <span i18n>Add service</span>\n </button>\n </div>\n</rolatech-toolbar>\n@if (isLoading) {\n <div class=\"flex justify-center items-center\">\n <rolatech-spinner></rolatech-spinner>\n </div>\n} @else {\n <div class=\"bg-[--rt-rasised-background] h-full\">\n <!-- <rolatech-offering-manage-filter></rolatech-offering-manage-filter> -->\n <div class=\"flex flex-wrap p-3 gap-3\">\n @for (item of offerings; track $index) {\n <rolatech-offering-manage-item\n list\n [offering]=\"item\"\n routerLink=\"./{{ item.id }}/manage/info\"\n ></rolatech-offering-manage-item>\n }\n </div>\n </div>\n\n <!-- <mat-paginator\n #paginator\n [length]=\"length\"\n [pageSize]=\"pageSize\"\n [pageSizeOptions]=\"pageSizeOptions\"\n (page)=\"pageEvent = find($event)\"\n hidePageSize\n showFirstLastButtons\n >\n </mat-paginator> -->\n}\n", styles: ["mat-form-field{width:100%}table{width:100%}td.mat-column-actions{text-align:right;max-width:64px;font-size:.8rem;padding:0 8px}.mat-mdc-header-cell.actions{text-align:right;max-width:64px;width:64px}.mat-mdc-cell:nth-last-child(2),.mat-mdc-header-cell:nth-last-child(2),.mat-mdc-footer-cell:nth-last-child(2){text-align:right;max-width:180px;width:180px}mat-cell:last-of-type,mat-header-cell:last-of-type,mat-footer-cell:last-of-type{text-align:right;padding-right:8px!important}rolatech-offering-manage-index{--rt-offering-view-items-per-row: 1}@media (min-width: 600px){rolatech-offering-manage-index{--rt-offering-view-items-per-row: 2}}@media (min-width: 768px){rolatech-offering-manage-index{--rt-offering-view-items-per-row: 3}}@media (min-width: 1280px){rolatech-offering-manage-index{--rt-offering-view-items-per-row: 3}}@media (min-width: 1536px){rolatech-offering-manage-index{--rt-offering-view-items-per-row: 4}}@media (min-width: 1920px){rolatech-offering-manage-index{--rt-offering-view-items-per-row: 5}}\n"], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i5.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: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTableModule }, { kind: "ngmodule", type: MatMenuModule }, { kind: "ngmodule", type: MatPaginatorModule }, { kind: "component", type: ToolbarComponent, selector: "rolatech-toolbar", inputs: ["title", "subtitle", "back", "link", "large", "divider"] }, { kind: "component", type: SpinnerComponent, selector: "rolatech-spinner", inputs: ["title"] }, { kind: "component", type: OfferingManageItemComponent, selector: "rolatech-offering-manage-item", inputs: ["offering", "thumbnail", "list"] }], encapsulation: i0.ViewEncapsulation.None });
118
+ }
119
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: OfferingManageIndexComponent, decorators: [{
120
+ type: Component,
121
+ args: [{ selector: 'rolatech-offering-manage-index', imports: [
122
+ MatButtonModule,
123
+ RouterLink,
124
+ MatIconModule,
125
+ MatTableModule,
126
+ MatMenuModule,
127
+ MatPaginatorModule,
128
+ ToolbarComponent,
129
+ SpinnerComponent,
130
+ OfferingManageItemComponent,
131
+ OfferingManageFilterComponent,
132
+ ], encapsulation: ViewEncapsulation.None, template: "<rolatech-toolbar title=\"Services\">\n <div class=\"flex items-center gap-2\">\n <button mat-flat-button routerLink=\"./create\">\n <mat-icon>add</mat-icon>\n <span i18n>Add service</span>\n </button>\n </div>\n</rolatech-toolbar>\n@if (isLoading) {\n <div class=\"flex justify-center items-center\">\n <rolatech-spinner></rolatech-spinner>\n </div>\n} @else {\n <div class=\"bg-[--rt-rasised-background] h-full\">\n <!-- <rolatech-offering-manage-filter></rolatech-offering-manage-filter> -->\n <div class=\"flex flex-wrap p-3 gap-3\">\n @for (item of offerings; track $index) {\n <rolatech-offering-manage-item\n list\n [offering]=\"item\"\n routerLink=\"./{{ item.id }}/manage/info\"\n ></rolatech-offering-manage-item>\n }\n </div>\n </div>\n\n <!-- <mat-paginator\n #paginator\n [length]=\"length\"\n [pageSize]=\"pageSize\"\n [pageSizeOptions]=\"pageSizeOptions\"\n (page)=\"pageEvent = find($event)\"\n hidePageSize\n showFirstLastButtons\n >\n </mat-paginator> -->\n}\n", styles: ["mat-form-field{width:100%}table{width:100%}td.mat-column-actions{text-align:right;max-width:64px;font-size:.8rem;padding:0 8px}.mat-mdc-header-cell.actions{text-align:right;max-width:64px;width:64px}.mat-mdc-cell:nth-last-child(2),.mat-mdc-header-cell:nth-last-child(2),.mat-mdc-footer-cell:nth-last-child(2){text-align:right;max-width:180px;width:180px}mat-cell:last-of-type,mat-header-cell:last-of-type,mat-footer-cell:last-of-type{text-align:right;padding-right:8px!important}rolatech-offering-manage-index{--rt-offering-view-items-per-row: 1}@media (min-width: 600px){rolatech-offering-manage-index{--rt-offering-view-items-per-row: 2}}@media (min-width: 768px){rolatech-offering-manage-index{--rt-offering-view-items-per-row: 3}}@media (min-width: 1280px){rolatech-offering-manage-index{--rt-offering-view-items-per-row: 3}}@media (min-width: 1536px){rolatech-offering-manage-index{--rt-offering-view-items-per-row: 4}}@media (min-width: 1920px){rolatech-offering-manage-index{--rt-offering-view-items-per-row: 5}}\n"] }]
133
+ }], propDecorators: { hasClass: [{
134
+ type: HostBinding,
135
+ args: ['class.rolatech-offering-manage-index']
136
+ }] } });
137
+
138
+ class OfferingManageCreateComponent {
139
+ offeringService = inject(OfferingService);
140
+ router = inject(Router);
141
+ route = inject(ActivatedRoute);
142
+ snackBar = inject(MatSnackBar);
143
+ offering = {};
144
+ create() {
145
+ const data = {
146
+ ...this.offering,
147
+ };
148
+ this.offeringService.create(data).subscribe({
149
+ next: (res) => {
150
+ this.router.navigate([`../${res.data.id}/manage/info`], {
151
+ relativeTo: this.route,
152
+ });
153
+ },
154
+ error: (e) => {
155
+ this.snackBar.open(e.message);
156
+ },
157
+ });
158
+ }
159
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: OfferingManageCreateComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
160
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.0.3", type: OfferingManageCreateComponent, isStandalone: true, selector: "rolatech-offering-manage-create", ngImport: i0, template: "<rolatech-toolbar title=\"Add offering\" link=\"../\"> </rolatech-toolbar>\n<div class=\"p-3\">\n <section>\n <form #productForm=\"ngForm\">\n <div class=\"flex flex-col\">\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Title </mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"offering.title\" name=\"title\" required />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Description </mat-label>\n <textarea\n matInput\n type=\"text\"\n [(ngModel)]=\"offering.description\"\n name=\"description\"\n required\n cdkTextareaAutosize\n cdkAutosizeMinRows=\"3\"\n ></textarea>\n </mat-form-field>\n </div>\n </form>\n </section>\n <div i18n>* items are required</div>\n <div class=\"mt-3\">\n <button mat-flat-button class=\"w-28\" (click)=\"create()\" i18n>Create</button>\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "component", type: ToolbarComponent, selector: "rolatech-toolbar", inputs: ["title", "subtitle", "back", "link", "large", "divider"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.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$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2$1.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3.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: "directive", type: i4.CdkTextareaAutosize, selector: "textarea[cdkTextareaAutosize]", inputs: ["cdkAutosizeMinRows", "cdkAutosizeMaxRows", "cdkTextareaAutosize", "placeholder"], exportAs: ["cdkTextareaAutosize"] }, { kind: "ngmodule", type: TextFieldModule }, { kind: "ngmodule", type: MatSelectModule }, { kind: "ngmodule", type: MatOptionModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i5.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"] }] });
161
+ }
162
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: OfferingManageCreateComponent, decorators: [{
163
+ type: Component,
164
+ args: [{ selector: 'rolatech-offering-manage-create', imports: [
165
+ ToolbarComponent,
166
+ FormsModule,
167
+ MatFormFieldModule,
168
+ MatInputModule,
169
+ TextFieldModule,
170
+ MatSelectModule,
171
+ MatOptionModule,
172
+ MatButtonModule,
173
+ ], template: "<rolatech-toolbar title=\"Add offering\" link=\"../\"> </rolatech-toolbar>\n<div class=\"p-3\">\n <section>\n <form #productForm=\"ngForm\">\n <div class=\"flex flex-col\">\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Title </mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"offering.title\" name=\"title\" required />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Description </mat-label>\n <textarea\n matInput\n type=\"text\"\n [(ngModel)]=\"offering.description\"\n name=\"description\"\n required\n cdkTextareaAutosize\n cdkAutosizeMinRows=\"3\"\n ></textarea>\n </mat-form-field>\n </div>\n </form>\n </section>\n <div i18n>* items are required</div>\n <div class=\"mt-3\">\n <button mat-flat-button class=\"w-28\" (click)=\"create()\" i18n>Create</button>\n </div>\n</div>\n" }]
174
+ }] });
175
+
176
+ class OfferingManageLayoutComponent extends BaseComponent {
177
+ offering;
178
+ submitted = false;
179
+ accepted = false;
180
+ isDraft = false;
181
+ offeringService = inject(OfferingService);
182
+ ngOnInit() {
183
+ this.id = this.route.snapshot.paramMap.get('id');
184
+ this.get();
185
+ }
186
+ get() {
187
+ this.offeringService.get(this.id).subscribe({
188
+ next: (res) => {
189
+ this.offering = res.data;
190
+ },
191
+ });
192
+ }
193
+ submit() {
194
+ this.offeringService.submitForReview(this.id).subscribe({
195
+ next: (res) => {
196
+ this.snackBarService.open('Submit successfully');
197
+ this.offering.status = OfferingStatus['Pending'];
198
+ this.updateStatus();
199
+ },
200
+ error: (error) => {
201
+ this.snackBarService.open(error.message);
202
+ },
203
+ });
204
+ }
205
+ updateStatus() {
206
+ this.isDraft = this.offering.status.toString() === 'DRAFT' || this.offering.status.toString() === 'PENDING';
207
+ this.accepted = this.offering.status.toString() === 'ACCEPTED';
208
+ }
209
+ publish() {
210
+ this.offeringService.publish(this.id).subscribe({
211
+ next: (res) => {
212
+ this.snackBarService.open('Publish successfully');
213
+ this.offering.status = OfferingStatus['Available'];
214
+ this.updateStatus();
215
+ },
216
+ error: (error) => {
217
+ this.snackBarService.open(error.message);
218
+ },
219
+ });
220
+ }
221
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: OfferingManageLayoutComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
222
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: OfferingManageLayoutComponent, isStandalone: true, selector: "rolatech-offering-manage-layout", usesInheritance: true, ngImport: i0, template: "<div class=\"flex flex-col md:flex-row m-auto\">\n <div\n class=\"min-w-[256px] px-3 md:px-0 flex flex-row md:flex-col md:h-full items-center md:items-start h-16 overflow-x-scroll overflow-y-hidden scrollbar-hide whitespace-pre\"\n >\n <div class=\"flex flex-row md:flex-col md:w-full\">\n <div class=\"hidden md:flex text-xl font-bold h-14 items-center px-1\" i18n>Offering</div>\n <a routerLink=\"./info\" routerLinkActive=\"manage-active\" class=\"p-2\" i18n>Basic info</a>\n <a routerLink=\"./media\" routerLinkActive=\"manage-active\" class=\"p-2\" i18n>Media</a>\n <a routerLink=\"./sections\" routerLinkActive=\"manage-active\" class=\"p-2\" i18n>Details</a>\n </div>\n @if (offering) {\n @if (offering.status.toString() === 'DRAFT') {\n <div class=\"md:mt-6 md:ml-2 flex items-center\">\n <button mat-flat-button (click)=\"submit()\" i18n>Submit for review</button>\n </div>\n }\n @if (offering.status.toString() === 'PENDING') {\n <div class=\"md:mt-6 md:ml-2 flex items-center\">\n <button mat-flat-button disabled i18n>Pending</button>\n </div>\n }\n @if (offering.status.toString() === 'APPROVED') {\n <div class=\"md:mt-6 md:ml-2 flex items-center\">\n <button mat-flat-button (click)=\"publish()\" i18n>Publish</button>\n </div>\n }\n }\n </div>\n <div class=\"w-full\">\n <router-outlet></router-outlet>\n </div>\n</div>\n", styles: [".manage-active{background-color:var(--rt-10-percent-layer, rgba(0, 0, 0, .05));box-shadow:4px 0 var(--rt-base-background-inverse, #000) inset;font-weight:600}@media (max-width: 768px){.manage-active{box-shadow:inset 0 -4px 0 0 var(--rt-base-background-inverse, #000)}}.scrollbar-hide::-webkit-scrollbar{display:none}.scrollbar-hide{-ms-overflow-style:none;scrollbar-width:none}\n"], dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i5.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: "directive", type: RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }] });
223
+ }
224
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: OfferingManageLayoutComponent, decorators: [{
225
+ type: Component,
226
+ args: [{ selector: 'rolatech-offering-manage-layout', imports: [RouterLink, RouterLinkActive, MatButtonModule, RouterOutlet], template: "<div class=\"flex flex-col md:flex-row m-auto\">\n <div\n class=\"min-w-[256px] px-3 md:px-0 flex flex-row md:flex-col md:h-full items-center md:items-start h-16 overflow-x-scroll overflow-y-hidden scrollbar-hide whitespace-pre\"\n >\n <div class=\"flex flex-row md:flex-col md:w-full\">\n <div class=\"hidden md:flex text-xl font-bold h-14 items-center px-1\" i18n>Offering</div>\n <a routerLink=\"./info\" routerLinkActive=\"manage-active\" class=\"p-2\" i18n>Basic info</a>\n <a routerLink=\"./media\" routerLinkActive=\"manage-active\" class=\"p-2\" i18n>Media</a>\n <a routerLink=\"./sections\" routerLinkActive=\"manage-active\" class=\"p-2\" i18n>Details</a>\n </div>\n @if (offering) {\n @if (offering.status.toString() === 'DRAFT') {\n <div class=\"md:mt-6 md:ml-2 flex items-center\">\n <button mat-flat-button (click)=\"submit()\" i18n>Submit for review</button>\n </div>\n }\n @if (offering.status.toString() === 'PENDING') {\n <div class=\"md:mt-6 md:ml-2 flex items-center\">\n <button mat-flat-button disabled i18n>Pending</button>\n </div>\n }\n @if (offering.status.toString() === 'APPROVED') {\n <div class=\"md:mt-6 md:ml-2 flex items-center\">\n <button mat-flat-button (click)=\"publish()\" i18n>Publish</button>\n </div>\n }\n }\n </div>\n <div class=\"w-full\">\n <router-outlet></router-outlet>\n </div>\n</div>\n", styles: [".manage-active{background-color:var(--rt-10-percent-layer, rgba(0, 0, 0, .05));box-shadow:4px 0 var(--rt-base-background-inverse, #000) inset;font-weight:600}@media (max-width: 768px){.manage-active{box-shadow:inset 0 -4px 0 0 var(--rt-base-background-inverse, #000)}}.scrollbar-hide::-webkit-scrollbar{display:none}.scrollbar-hide{-ms-overflow-style:none;scrollbar-width:none}\n"] }]
227
+ }] });
228
+
229
+ class OfferingManageContentComponent {
230
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: OfferingManageContentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
231
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.0.3", type: OfferingManageContentComponent, isStandalone: true, selector: "rolatech-offering-manage-content", ngImport: i0, template: "<ng-content select=\"rolatech-toolbar\"></ng-content>\n<div class=\"p-3\">\n <ng-content></ng-content>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
232
+ }
233
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: OfferingManageContentComponent, decorators: [{
234
+ type: Component,
235
+ args: [{ selector: 'rolatech-offering-manage-content', imports: [CommonModule], template: "<ng-content select=\"rolatech-toolbar\"></ng-content>\n<div class=\"p-3\">\n <ng-content></ng-content>\n</div>\n" }]
236
+ }] });
237
+
238
+ const MY_FORMATS = {
239
+ parse: {
240
+ dateInput: 'YYYY-MM-DD',
241
+ },
242
+ display: {
243
+ dateInput: 'YYYY-MM-DD',
244
+ monthYearLabel: 'MMM YYYY',
245
+ dateA11yLabel: 'YYYY-MM-DD',
246
+ monthYearA11yLabel: 'MMMM YYYY',
247
+ },
248
+ };
249
+ class OfferingManageInfoComponent extends BaseComponent {
250
+ offeringService = inject(OfferingService);
251
+ categoryService = inject(CategoryService);
252
+ offering;
253
+ status = OfferingStatus;
254
+ selectedCategoyIds;
255
+ categories;
256
+ parentCategory;
257
+ childrenCategories;
258
+ selectedChildrenCategories;
259
+ offeringType = OfferingType;
260
+ price = model('0.00');
261
+ originalPrice = model('0.00');
262
+ minDate = new Date();
263
+ ngOnInit() {
264
+ this.id = this.route.parent?.snapshot.paramMap.get('id');
265
+ this.find();
266
+ this.categoryService.find({ limit: 100, filter: 'level:1' }).subscribe({
267
+ next: (res) => {
268
+ this.categories = res.data;
269
+ },
270
+ });
271
+ }
272
+ find() {
273
+ this.offeringService.get(this.id).subscribe({
274
+ next: (res) => {
275
+ this.offering = res.data;
276
+ this.parentCategory = this.offering.categories?.filter((item) => item.level === 1)[0];
277
+ this.selectedChildrenCategories = this.offering.categories?.filter((item) => item.level === 2);
278
+ this.findChildrenCategoryByParentId(this.parentCategory.id);
279
+ const price = this.offering.price ? (this.offering.price / 100).toFixed(2) : '0.00';
280
+ const originalPrice = this.offering.originalPrice ? (this.offering.originalPrice / 100).toFixed(2) : '0.00';
281
+ this.price.set(price);
282
+ this.originalPrice.set(originalPrice);
283
+ },
284
+ });
285
+ }
286
+ formatPrice(value) {
287
+ // Convert to number first in case it's a string
288
+ this.offering.price = Number(Number(value).toFixed(2));
289
+ }
290
+ onParentCategoryChange(event) {
291
+ this.parentCategory.id = event.value;
292
+ this.childrenCategories = event.value.children;
293
+ }
294
+ onChildCategoryChange(event) {
295
+ this.selectedChildrenCategories = event.value;
296
+ }
297
+ findChildrenCategoryByParentId(parentId) {
298
+ this.categoryService.find({ limit: 100, filter: `parent:${parentId}` }).subscribe({
299
+ next: (res) => {
300
+ this.childrenCategories = res.data;
301
+ },
302
+ });
303
+ }
304
+ compareParentFn(o1, o2) {
305
+ return o1.id === o2?.id;
306
+ }
307
+ compareChildrenFn(o1, o2) {
308
+ return o1.id === o2?.id;
309
+ }
310
+ typeCompareFn(t1, t2) {
311
+ return t1 === t2;
312
+ }
313
+ priceTypeCompareFn(t1, t2) {
314
+ return t1 === t2;
315
+ }
316
+ onTypeChange(event) {
317
+ // this.offering.type = Object.keys(this.offeringType).find((key) => {
318
+ // return this.offeringType[key] === event.value;
319
+ // })!;
320
+ this.offering.type = event.value;
321
+ }
322
+ onCategoryChange(event) {
323
+ this.selectedCategoyIds = event.value;
324
+ }
325
+ update() {
326
+ const { title, description } = this.offering;
327
+ const data = {
328
+ title,
329
+ description,
330
+ categories: this.selectedChildrenCategories,
331
+ price: Number(this.price()) * 100,
332
+ originalPrice: Number(this.originalPrice()) * 100,
333
+ };
334
+ this.offeringService.update(this.id, data).subscribe({
335
+ next: (res) => {
336
+ this.snackBarService.open('Update successfully');
337
+ },
338
+ error: (e) => {
339
+ this.snackBarService.open(e.message);
340
+ },
341
+ });
342
+ }
343
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: OfferingManageInfoComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
344
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: OfferingManageInfoComponent, isStandalone: true, selector: "rolatech-offering-manage-info", inputs: { price: { classPropertyName: "price", publicName: "price", isSignal: true, isRequired: false, transformFunction: null }, originalPrice: { classPropertyName: "originalPrice", publicName: "originalPrice", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { price: "priceChange", originalPrice: "originalPriceChange" }, providers: [
345
+ {
346
+ provide: DateAdapter,
347
+ useClass: MomentDateAdapter,
348
+ deps: [MAT_DATE_LOCALE],
349
+ },
350
+ { provide: MAT_DATE_FORMATS, useValue: MY_FORMATS },
351
+ ], usesInheritance: true, ngImport: i0, template: "<rolatech-offering-manage-content>\n <rolatech-toolbar title=\"Basic info\" class=\"hidden md:block\" divider></rolatech-toolbar>\n @if (offering) {\n <div class=\"flex flex-col\">\n <form #offeringForm=\"ngForm\">\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Title </mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"offering.title\" name=\"title\" required />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Description </mat-label>\n <textarea\n matInput\n type=\"text\"\n [(ngModel)]=\"offering.description\"\n name=\"description\"\n required\n cdkTextareaAutosize\n cdkAutosizeMinRows=\"3\"\n ></textarea>\n </mat-form-field>\n <div class=\"flex gap-2\">\n <mat-form-field>\n <mat-label i18n>Level 1</mat-label>\n <mat-select\n name=\"id\"\n [compareWith]=\"compareParentFn\"\n [(ngModel)]=\"parentCategory\"\n (selectionChange)=\"onParentCategoryChange($event)\"\n >\n @for (category of categories; track category) {\n <mat-option [value]=\"category\">{{ category.name }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"fill\" ngModelGroup=\"categories\" required>\n <mat-label i18n>Level 2</mat-label>\n <mat-select\n multiple\n name=\"id\"\n [compareWith]=\"compareChildrenFn\"\n [(ngModel)]=\"selectedChildrenCategories\"\n #select=\"matSelect\"\n (selectionChange)=\"onChildCategoryChange($event)\"\n >\n @for (item of childrenCategories; track item) {\n <mat-option [value]=\"item\">\n {{ item.name }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Price </mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"price\" name=\"price\" required rolatechDecimal />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Original price </mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"originalPrice\" name=\"originalPrice\" required rolatechDecimal />\n </mat-form-field>\n </form>\n </div>\n <div>\n <button mat-flat-button (click)=\"update()\" i18n>Save</button>\n </div>\n }\n</rolatech-offering-manage-content>\n", styles: ["mat-form-field{width:100%}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.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$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1$1.NgModelGroup, selector: "[ngModelGroup]", inputs: ["ngModelGroup"], exportAs: ["ngModelGroup"] }, { kind: "directive", type: i1$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2$1.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3.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: "directive", type: i4.CdkTextareaAutosize, selector: "textarea[cdkTextareaAutosize]", inputs: ["cdkAutosizeMinRows", "cdkAutosizeMaxRows", "cdkTextareaAutosize", "placeholder"], exportAs: ["cdkTextareaAutosize"] }, { kind: "ngmodule", type: TextFieldModule }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i5$1.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$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatOptionModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i5.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: ToolbarComponent, selector: "rolatech-toolbar", inputs: ["title", "subtitle", "back", "link", "large", "divider"] }, { kind: "component", type: OfferingManageContentComponent, selector: "rolatech-offering-manage-content" }, { kind: "directive", type: DecimalDirective, selector: "[rolatechDecimal]" }, { kind: "ngmodule", type: MatDatepickerModule }] });
352
+ }
353
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: OfferingManageInfoComponent, decorators: [{
354
+ type: Component,
355
+ args: [{ selector: 'rolatech-offering-manage-info', imports: [
356
+ FormsModule,
357
+ MatFormFieldModule,
358
+ MatInputModule,
359
+ TextFieldModule,
360
+ MatSelectModule,
361
+ MatOptionModule,
362
+ MatButtonModule,
363
+ ToolbarComponent,
364
+ OfferingManageContentComponent,
365
+ DecimalDirective,
366
+ MatSelectModule,
367
+ MatDatepickerModule,
368
+ ], providers: [
369
+ {
370
+ provide: DateAdapter,
371
+ useClass: MomentDateAdapter,
372
+ deps: [MAT_DATE_LOCALE],
373
+ },
374
+ { provide: MAT_DATE_FORMATS, useValue: MY_FORMATS },
375
+ ], template: "<rolatech-offering-manage-content>\n <rolatech-toolbar title=\"Basic info\" class=\"hidden md:block\" divider></rolatech-toolbar>\n @if (offering) {\n <div class=\"flex flex-col\">\n <form #offeringForm=\"ngForm\">\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Title </mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"offering.title\" name=\"title\" required />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Description </mat-label>\n <textarea\n matInput\n type=\"text\"\n [(ngModel)]=\"offering.description\"\n name=\"description\"\n required\n cdkTextareaAutosize\n cdkAutosizeMinRows=\"3\"\n ></textarea>\n </mat-form-field>\n <div class=\"flex gap-2\">\n <mat-form-field>\n <mat-label i18n>Level 1</mat-label>\n <mat-select\n name=\"id\"\n [compareWith]=\"compareParentFn\"\n [(ngModel)]=\"parentCategory\"\n (selectionChange)=\"onParentCategoryChange($event)\"\n >\n @for (category of categories; track category) {\n <mat-option [value]=\"category\">{{ category.name }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"fill\" ngModelGroup=\"categories\" required>\n <mat-label i18n>Level 2</mat-label>\n <mat-select\n multiple\n name=\"id\"\n [compareWith]=\"compareChildrenFn\"\n [(ngModel)]=\"selectedChildrenCategories\"\n #select=\"matSelect\"\n (selectionChange)=\"onChildCategoryChange($event)\"\n >\n @for (item of childrenCategories; track item) {\n <mat-option [value]=\"item\">\n {{ item.name }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Price </mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"price\" name=\"price\" required rolatechDecimal />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Original price </mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"originalPrice\" name=\"originalPrice\" required rolatechDecimal />\n </mat-form-field>\n </form>\n </div>\n <div>\n <button mat-flat-button (click)=\"update()\" i18n>Save</button>\n </div>\n }\n</rolatech-offering-manage-content>\n", styles: ["mat-form-field{width:100%}\n"] }]
376
+ }] });
377
+
378
+ const SIZE = 10 * 1024 * 1024; // file slice size 10MB
379
+ class OfferingManageMediaComponent {
380
+ route = inject(ActivatedRoute);
381
+ offeringService = inject(OfferingService);
382
+ dialog = inject(MatDialog);
383
+ snackBar = inject(MatSnackBar);
384
+ id;
385
+ isLoading = false;
386
+ media = [];
387
+ status = OfferingStatus;
388
+ // offeringType: any = OfferingType;
389
+ constructor() {
390
+ this.id = this.route.parent?.snapshot.paramMap.get('id');
391
+ }
392
+ ngOnInit() {
393
+ this.find();
394
+ }
395
+ find() {
396
+ this.offeringService.get(this.id).subscribe({
397
+ next: (res) => {
398
+ this.media = res.data.media || [];
399
+ },
400
+ });
401
+ }
402
+ onImageClick(i) {
403
+ const dialogRef = this.dialog.open(ImagePreviewDialogComponent, {
404
+ maxWidth: '80vw',
405
+ maxHeight: '80vh',
406
+ height: '80%',
407
+ width: '80%',
408
+ panelClass: 'full-screen-modal',
409
+ data: {
410
+ media: this.media,
411
+ selected: i,
412
+ },
413
+ });
414
+ dialogRef.afterClosed().subscribe((result) => { });
415
+ }
416
+ createFileChunk(file, size = SIZE) {
417
+ const fileChunkList = [];
418
+ let cur = 0;
419
+ while (cur < file.size) {
420
+ fileChunkList.push({ file: file.slice(cur, cur + size) });
421
+ cur += size;
422
+ }
423
+ return fileChunkList;
424
+ }
425
+ onUploadMedia2(event) {
426
+ const file = event.target.files[0];
427
+ const reader = new FileReader();
428
+ reader.onload = (e) => { };
429
+ const fileChunkList = this.createFileChunk(file);
430
+ fileChunkList.forEach((item) => { });
431
+ }
432
+ onMultipleFilesUpload(event) {
433
+ // const files = event.target.files;
434
+ // if (files) {
435
+ // Object.values(files).forEach((file, index) => {
436
+ // this.uploadFile(file, index);
437
+ // });
438
+ // }
439
+ const input = event.target;
440
+ if (input.files && input.files.length > 0) {
441
+ const startIndex = this.media.length; // Offset to preserve existing items
442
+ Array.from(input.files).forEach((file, i) => {
443
+ this.uploadFile(file, startIndex + i);
444
+ });
445
+ }
446
+ }
447
+ uploadFile(file, index) {
448
+ const reader = new FileReader();
449
+ const formData = new FormData();
450
+ formData.append('file', file);
451
+ this.media.splice(index, 0, {
452
+ url: '',
453
+ alt: 'Uploading...',
454
+ width: 0,
455
+ height: 0,
456
+ uploading: true,
457
+ });
458
+ reader.readAsDataURL(file);
459
+ reader.onload = () => {
460
+ const img = new Image();
461
+ img.onload = () => {
462
+ Object.assign(this.media[index], {
463
+ url: img.src,
464
+ width: img.width,
465
+ height: img.height,
466
+ });
467
+ };
468
+ img.src = reader.result;
469
+ };
470
+ this.offeringService.uploadMedia(this.id, formData).subscribe({
471
+ next: (res) => {
472
+ // Replace item at index using native splice
473
+ // this.media.splice(index, 1, res.data);
474
+ this.media.splice(index, 1, {
475
+ ...res.data,
476
+ uploading: false,
477
+ });
478
+ },
479
+ error: (e) => {
480
+ this.snackBar.open('Upload failed: ' + e.message);
481
+ this.media.splice(index, 1); // remove failed placeholder
482
+ },
483
+ });
484
+ reader.onerror = (error) => { };
485
+ }
486
+ onUploadMedia(event) {
487
+ const file = event.target.files[0];
488
+ // 5MB * 1024 * 1024 = 5242880
489
+ // if (file?.size > 5242880) {
490
+ // this.snackBar.open('尺寸过大, 请修改后上传');
491
+ // return;
492
+ // }
493
+ if (file) {
494
+ const reader = new FileReader();
495
+ const formData = new FormData();
496
+ formData.append('file', file);
497
+ reader.readAsDataURL(file);
498
+ reader.onload = () => {
499
+ const img = new Image();
500
+ img.onload = () => {
501
+ this.media.push({
502
+ url: img.src,
503
+ alt: 'upload image',
504
+ width: img.width,
505
+ height: img.height,
506
+ });
507
+ };
508
+ img.src = reader.result; // This is the data URL
509
+ };
510
+ this.offeringService.uploadMedia(this.id, formData).subscribe({
511
+ next: (res) => {
512
+ const index = findLastIndex(this.media);
513
+ // Replace item at index using native splice
514
+ this.media.splice(index, 1, res.data);
515
+ },
516
+ error: (e) => { },
517
+ });
518
+ reader.onerror = (error) => { };
519
+ }
520
+ }
521
+ onMediaDelete(item) {
522
+ const dialogRef = this.dialog.open(ConfirmationDialogComponent, {
523
+ width: '400px',
524
+ data: {
525
+ title: '删除图片',
526
+ message: '确定删除这张商品图片吗?',
527
+ },
528
+ });
529
+ dialogRef.afterClosed().subscribe((result) => {
530
+ if (result) {
531
+ this.offeringService.deleteMedia(this.id, item.id).subscribe({
532
+ next: (res) => {
533
+ this.media = this.media.filter((m) => m.id !== item.id);
534
+ this.snackBar.open('Delete successfully');
535
+ },
536
+ error: (e) => {
537
+ this.snackBar.open(e.message);
538
+ },
539
+ });
540
+ }
541
+ });
542
+ }
543
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: OfferingManageMediaComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
544
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: OfferingManageMediaComponent, isStandalone: true, selector: "rolatech-offering-manage-media", ngImport: i0, template: "<rolatech-offering-manage-content>\n <rolatech-toolbar title=\"Media info\" class=\"hidden md:block\" divider></rolatech-toolbar>\n <div>\n <p i18n>*Limit 5MB per image*</p>\n <rolatech-media-list (upload)=\"onMultipleFilesUpload($event)\">\n @for (item of media; track item; let i = $index) {\n <rolatech-media-list-item\n [media]=\"item\"\n (mediaItemClick)=\"onImageClick(i)\"\n (deleteMedia)=\"onMediaDelete(item)\"\n [uploading]=\"item.uploading\"\n ></rolatech-media-list-item>\n }\n </rolatech-media-list>\n </div>\n</rolatech-offering-manage-content>\n", styles: [""], dependencies: [{ kind: "component", type: MediaListComponent, selector: "rolatech-media-list", inputs: ["isUploading", "media", "showAdd"], outputs: ["mediaItemClick", "upload"] }, { kind: "component", type: MediaListItemComponent, selector: "rolatech-media-list-item", inputs: ["media", "uploadProgress", "uploading"], outputs: ["mediaItemClick", "deleteMedia"] }, { kind: "component", type: ToolbarComponent, selector: "rolatech-toolbar", inputs: ["title", "subtitle", "back", "link", "large", "divider"] }, { kind: "component", type: OfferingManageContentComponent, selector: "rolatech-offering-manage-content" }] });
545
+ }
546
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: OfferingManageMediaComponent, decorators: [{
547
+ type: Component,
548
+ args: [{ selector: 'rolatech-offering-manage-media', imports: [MediaListComponent, MediaListItemComponent, ToolbarComponent, OfferingManageContentComponent], template: "<rolatech-offering-manage-content>\n <rolatech-toolbar title=\"Media info\" class=\"hidden md:block\" divider></rolatech-toolbar>\n <div>\n <p i18n>*Limit 5MB per image*</p>\n <rolatech-media-list (upload)=\"onMultipleFilesUpload($event)\">\n @for (item of media; track item; let i = $index) {\n <rolatech-media-list-item\n [media]=\"item\"\n (mediaItemClick)=\"onImageClick(i)\"\n (deleteMedia)=\"onMediaDelete(item)\"\n [uploading]=\"item.uploading\"\n ></rolatech-media-list-item>\n }\n </rolatech-media-list>\n </div>\n</rolatech-offering-manage-content>\n" }]
549
+ }], ctorParameters: () => [] });
550
+
551
+ class OfferingManageSectionItemComponent {
552
+ isUploading = input();
553
+ section = input.required();
554
+ actions = input(false);
555
+ selectMedia = input();
556
+ upload = output();
557
+ delete = output();
558
+ save = output();
559
+ deleteMedia = output();
560
+ selectedImg;
561
+ expanded = false;
562
+ constructor() { }
563
+ ngOnInit() {
564
+ this.selectedImg = this.section().media ? this.section().media[0] : null;
565
+ }
566
+ onUpload(data) {
567
+ this.upload.emit({ section: this.section(), data });
568
+ }
569
+ onMediaItemClick(image) {
570
+ this.selectedImg = image;
571
+ }
572
+ select(item) {
573
+ this.selectedImg = item;
574
+ }
575
+ deleteImage() { }
576
+ onSave(section) {
577
+ this.save.emit(section);
578
+ }
579
+ onDelete(section) {
580
+ this.delete.emit(section);
581
+ }
582
+ onDeleteMedia(media) {
583
+ this.deleteMedia.emit({ section: this.section(), media });
584
+ this.selectedImg = this.section().media ? first(this.section().media) : null;
585
+ }
586
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: OfferingManageSectionItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
587
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: OfferingManageSectionItemComponent, isStandalone: true, selector: "rolatech-offering-manage-section-item", inputs: { isUploading: { classPropertyName: "isUploading", publicName: "isUploading", isSignal: true, isRequired: false, transformFunction: null }, section: { classPropertyName: "section", publicName: "section", isSignal: true, isRequired: true, transformFunction: null }, actions: { classPropertyName: "actions", publicName: "actions", isSignal: true, isRequired: false, transformFunction: null }, selectMedia: { classPropertyName: "selectMedia", publicName: "selectMedia", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { upload: "upload", delete: "delete", save: "save", deleteMedia: "deleteMedia" }, ngImport: i0, template: "<div class=\"px-3\">\n <div\n class=\"h-14 py-3 flex items-center justify-between cursor-pointer\"\n (click)=\"expanded = !expanded; $event.stopPropagation()\"\n >\n <div class=\"flex\">\n <div class=\"w-32 line-clamp-1\">{{ section().title }}</div>\n <div class=\"line-clamp-1\">{{ section().description }}</div>\n </div>\n <div>\n <button mat-icon-button aria-label=\"expand row\" (click)=\"expanded = !expanded; $event.stopPropagation()\">\n @if (expanded) {\n <mat-icon>keyboard_arrow_up</mat-icon>\n } @else {\n <mat-icon>keyboard_arrow_down</mat-icon>\n }\n </button>\n </div>\n </div>\n <div class=\"flex flex-col gap-3 w-full overflow-hidden\" [@detailExpand]=\"expanded ? 'expanded' : 'collapsed'\">\n <div class=\"flex flex-col lg:flex-row\">\n <div class=\"flex flex-col grow\">\n <mat-form-field appearance=\"fill\">\n <mat-label i18n>Title</mat-label>\n <input matInput placeholder=\"Title\" i18n-placeholder [(ngModel)]=\"section().title\" />\n </mat-form-field>\n <mat-form-field appearance=\"fill\" [hideRequiredMarker]=\"true\">\n <mat-label i18n>Description</mat-label>\n <textarea\n matInput\n cdkTextareaAutosize\n cdkAutosizeMinRows=\"3\"\n placeholder=\"Description\"\n [(ngModel)]=\"section().description\"\n i18n-placeholder\n ></textarea>\n </mat-form-field>\n </div>\n <!-- media -->\n <div class=\"lg:basis-1/2 px-0 lg:px-3\">\n @if (selectedImg) {\n <div class=\"flex flex-row justify-center relative aspect-video\">\n <img class=\"object-contain w-full\" [src]=\"selectedImg.url\" [alt]=\"selectedImg.alt\" />\n <div class=\"absolute z-30 right-0\">\n <button\n mat-icon-button\n [matMenuTriggerFor]=\"beforeMenu\"\n class=\"ml-auto focus:outline-none hover:bg-[--rt-base-background] p-1\"\n >\n <mat-icon>more_vert</mat-icon>\n </button>\n <mat-menu #beforeMenu=\"matMenu\" xPosition=\"before\">\n <button mat-menu-item (click)=\"onDeleteMedia(selectedImg)\">\n <span i18n>Delete</span>\n </button>\n </mat-menu>\n </div>\n </div>\n }\n\n <!-- media -->\n <div>\n <div class=\"flex flex-row flex-wrap cursor-pointer relative box-border\" fxLayout=\"row\">\n <div class=\"progress-bar\">\n @if (section().isUploading) {\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n }\n </div>\n @for (media of section().media; track media) {\n <div class=\"media-list-item\">\n <img class=\"tile-media\" (click)=\"onMediaItemClick(media)\" [src]=\"media.url\" [alt]=\"media.alt\" />\n </div>\n }\n <input style=\"display: none\" type=\"file\" accept=\"image/*\" (change)=\"onUpload($event)\" #fileInput />\n <div class=\"add-button\">\n <div (click)=\"fileInput.click()\" class=\"tile-media flex justify-center items-center\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"48px\" viewBox=\"0 -960 960 960\" width=\"48px\" fill=\"#5f6368\">\n <path d=\"M444-444H240v-72h204v-204h72v204h204v72H516v204h-72v-204Z\" />\n </svg>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n @if (actions()) {\n <div class=\"flex flex-row justify-end p-3 gap-3\">\n <button mat-button class=\"max-h-8\" (click)=\"onDelete(section())\" i18n>Delete</button>\n <button mat-flat-button class=\"max-h-8\" (click)=\"onSave(section())\" i18n>Save</button>\n </div>\n }\n </div>\n</div>\n\n<mat-divider></mat-divider>\n", styles: [".media-list{flex-wrap:wrap;box-sizing:border-box}.progress-bar{display:block;min-height:6px;width:100%}.media-list-item{cursor:pointer;position:relative;box-sizing:border-box;padding:2px}.tile-media{height:64px;width:64px;object-fit:contain;cursor:pointer;border:1px solid grey;position:relative;box-sizing:border-box;border-radius:8px}.add-button{cursor:pointer;position:relative;box-sizing:border-box;padding:2px}\n"], dependencies: [{ kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2$1.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3.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: "directive", type: i4.CdkTextareaAutosize, selector: "textarea[cdkTextareaAutosize]", inputs: ["cdkAutosizeMinRows", "cdkAutosizeMaxRows", "cdkTextareaAutosize", "placeholder"], exportAs: ["cdkTextareaAutosize"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.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$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TextFieldModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i5.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: i5.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i7.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: i7.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i7.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: MatProgressBarModule }, { kind: "component", type: i8.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "ngmodule", type: MatDividerModule }, { kind: "component", type: i9.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }], animations: [
588
+ trigger('detailExpand', [
589
+ state('collapsed', style({ height: '0px' })),
590
+ state('expanded', style({ height: '*' })),
591
+ transition('expanded <=> collapsed', animate('400ms cubic-bezier(0.25, 0.1, 0.25, 1)')),
592
+ ]),
593
+ ] });
594
+ }
595
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: OfferingManageSectionItemComponent, decorators: [{
596
+ type: Component,
597
+ args: [{ selector: 'rolatech-offering-manage-section-item', imports: [
598
+ MatFormFieldModule,
599
+ MatInputModule,
600
+ FormsModule,
601
+ TextFieldModule,
602
+ MatButtonModule,
603
+ MatIconModule,
604
+ MatMenuModule,
605
+ MatProgressBarModule,
606
+ MatDividerModule,
607
+ ], animations: [
608
+ trigger('detailExpand', [
609
+ state('collapsed', style({ height: '0px' })),
610
+ state('expanded', style({ height: '*' })),
611
+ transition('expanded <=> collapsed', animate('400ms cubic-bezier(0.25, 0.1, 0.25, 1)')),
612
+ ]),
613
+ ], template: "<div class=\"px-3\">\n <div\n class=\"h-14 py-3 flex items-center justify-between cursor-pointer\"\n (click)=\"expanded = !expanded; $event.stopPropagation()\"\n >\n <div class=\"flex\">\n <div class=\"w-32 line-clamp-1\">{{ section().title }}</div>\n <div class=\"line-clamp-1\">{{ section().description }}</div>\n </div>\n <div>\n <button mat-icon-button aria-label=\"expand row\" (click)=\"expanded = !expanded; $event.stopPropagation()\">\n @if (expanded) {\n <mat-icon>keyboard_arrow_up</mat-icon>\n } @else {\n <mat-icon>keyboard_arrow_down</mat-icon>\n }\n </button>\n </div>\n </div>\n <div class=\"flex flex-col gap-3 w-full overflow-hidden\" [@detailExpand]=\"expanded ? 'expanded' : 'collapsed'\">\n <div class=\"flex flex-col lg:flex-row\">\n <div class=\"flex flex-col grow\">\n <mat-form-field appearance=\"fill\">\n <mat-label i18n>Title</mat-label>\n <input matInput placeholder=\"Title\" i18n-placeholder [(ngModel)]=\"section().title\" />\n </mat-form-field>\n <mat-form-field appearance=\"fill\" [hideRequiredMarker]=\"true\">\n <mat-label i18n>Description</mat-label>\n <textarea\n matInput\n cdkTextareaAutosize\n cdkAutosizeMinRows=\"3\"\n placeholder=\"Description\"\n [(ngModel)]=\"section().description\"\n i18n-placeholder\n ></textarea>\n </mat-form-field>\n </div>\n <!-- media -->\n <div class=\"lg:basis-1/2 px-0 lg:px-3\">\n @if (selectedImg) {\n <div class=\"flex flex-row justify-center relative aspect-video\">\n <img class=\"object-contain w-full\" [src]=\"selectedImg.url\" [alt]=\"selectedImg.alt\" />\n <div class=\"absolute z-30 right-0\">\n <button\n mat-icon-button\n [matMenuTriggerFor]=\"beforeMenu\"\n class=\"ml-auto focus:outline-none hover:bg-[--rt-base-background] p-1\"\n >\n <mat-icon>more_vert</mat-icon>\n </button>\n <mat-menu #beforeMenu=\"matMenu\" xPosition=\"before\">\n <button mat-menu-item (click)=\"onDeleteMedia(selectedImg)\">\n <span i18n>Delete</span>\n </button>\n </mat-menu>\n </div>\n </div>\n }\n\n <!-- media -->\n <div>\n <div class=\"flex flex-row flex-wrap cursor-pointer relative box-border\" fxLayout=\"row\">\n <div class=\"progress-bar\">\n @if (section().isUploading) {\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n }\n </div>\n @for (media of section().media; track media) {\n <div class=\"media-list-item\">\n <img class=\"tile-media\" (click)=\"onMediaItemClick(media)\" [src]=\"media.url\" [alt]=\"media.alt\" />\n </div>\n }\n <input style=\"display: none\" type=\"file\" accept=\"image/*\" (change)=\"onUpload($event)\" #fileInput />\n <div class=\"add-button\">\n <div (click)=\"fileInput.click()\" class=\"tile-media flex justify-center items-center\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"48px\" viewBox=\"0 -960 960 960\" width=\"48px\" fill=\"#5f6368\">\n <path d=\"M444-444H240v-72h204v-204h72v204h204v72H516v204h-72v-204Z\" />\n </svg>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n @if (actions()) {\n <div class=\"flex flex-row justify-end p-3 gap-3\">\n <button mat-button class=\"max-h-8\" (click)=\"onDelete(section())\" i18n>Delete</button>\n <button mat-flat-button class=\"max-h-8\" (click)=\"onSave(section())\" i18n>Save</button>\n </div>\n }\n </div>\n</div>\n\n<mat-divider></mat-divider>\n", styles: [".media-list{flex-wrap:wrap;box-sizing:border-box}.progress-bar{display:block;min-height:6px;width:100%}.media-list-item{cursor:pointer;position:relative;box-sizing:border-box;padding:2px}.tile-media{height:64px;width:64px;object-fit:contain;cursor:pointer;border:1px solid grey;position:relative;box-sizing:border-box;border-radius:8px}.add-button{cursor:pointer;position:relative;box-sizing:border-box;padding:2px}\n"] }]
614
+ }], ctorParameters: () => [] });
615
+
616
+ class OfferingManageSectionsComponent {
617
+ route = inject(ActivatedRoute);
618
+ offeringService = inject(OfferingService);
619
+ dialog = inject(MatDialog);
620
+ snackBar = inject(MatSnackBar);
621
+ id;
622
+ isLoading = false;
623
+ isUploading = false;
624
+ sections = [];
625
+ status = OfferingStatus;
626
+ offeringType = OfferingType;
627
+ constructor() {
628
+ this.id = this.route.parent?.snapshot.paramMap.get('id');
629
+ }
630
+ ngOnInit() {
631
+ this.find();
632
+ }
633
+ find() {
634
+ this.offeringService.findSections(this.id).subscribe({
635
+ next: (res) => {
636
+ if (res.data) {
637
+ this.sections = res.data;
638
+ }
639
+ },
640
+ });
641
+ }
642
+ addSection() {
643
+ if (!this.sections) {
644
+ this.sections = [];
645
+ }
646
+ this.offeringService
647
+ .addSection(this.id, {
648
+ title: '',
649
+ description: '',
650
+ content: '',
651
+ media: [],
652
+ })
653
+ .subscribe({
654
+ next: (res) => {
655
+ const section = res.data;
656
+ this.sections.push({
657
+ id: section.id,
658
+ title: 'Untitled',
659
+ description: '',
660
+ content: '',
661
+ media: [],
662
+ });
663
+ },
664
+ error: (e) => {
665
+ this.snackBar.open(e.message);
666
+ },
667
+ });
668
+ }
669
+ onUploadSectionMedia(event) {
670
+ const section = event.section;
671
+ const sectionId = section.id;
672
+ const file = event.data.target.files[0];
673
+ if (file) {
674
+ const reader = new FileReader();
675
+ reader.readAsDataURL(file);
676
+ reader.onload = () => {
677
+ if (!section.media) {
678
+ section.media = [];
679
+ }
680
+ section.media.push({
681
+ url: reader.result,
682
+ alt: 'upload image',
683
+ });
684
+ section.isUploading = true;
685
+ const formData = new FormData();
686
+ formData.append('file', file);
687
+ this.offeringService.uploadSectionMedia(sectionId, formData).subscribe({
688
+ next: (res) => {
689
+ this.isUploading = false;
690
+ section.isUploading = false;
691
+ delete res.data.offeringSection;
692
+ section.media[section.media.length - 1].id = res.data.id;
693
+ section.media[section.media.length - 1].url = res.data.url;
694
+ },
695
+ error: (e) => {
696
+ this.isUploading = false;
697
+ this.snackBar.open('Upload failed: ' + e.message);
698
+ },
699
+ });
700
+ };
701
+ reader.onerror = (error) => {
702
+ this.isUploading = false;
703
+ };
704
+ }
705
+ }
706
+ onDeleteSectionMedia(event) {
707
+ const dialogRef = this.dialog.open(ConfirmationDialogComponent, {
708
+ width: '400px',
709
+ data: {
710
+ title: '删除图片',
711
+ message: `确定删除吗?`,
712
+ },
713
+ });
714
+ dialogRef.afterClosed().subscribe((result) => {
715
+ if (result) {
716
+ const section = event.section;
717
+ const mediaId = event.media.id;
718
+ remove(section.media, {
719
+ id: mediaId,
720
+ });
721
+ this.offeringService.deleteSectionMedia(section.id, mediaId).subscribe({
722
+ next: (res) => {
723
+ // remove(section.media, {
724
+ // id: mediaId,
725
+ // });
726
+ },
727
+ });
728
+ }
729
+ });
730
+ }
731
+ onSectionSave(section) {
732
+ delete section.isUploading;
733
+ this.offeringService.updateSection(section.id, section).subscribe({
734
+ next: (res) => {
735
+ this.snackBar.open('Success');
736
+ },
737
+ error: (e) => {
738
+ this.snackBar.open(e.message);
739
+ },
740
+ });
741
+ }
742
+ onSectionDelete(section) {
743
+ const dialogRef = this.dialog.open(ConfirmationDialogComponent, {
744
+ width: '400px',
745
+ data: {
746
+ title: '删除详情',
747
+ message: '确定删除此详情吗?',
748
+ },
749
+ });
750
+ dialogRef.afterClosed().subscribe((result) => {
751
+ if (result) {
752
+ this.offeringService.deleteSection(section.id).subscribe({
753
+ next: (res) => {
754
+ this.snackBar.open(res.data);
755
+ remove(this.sections, {
756
+ id: section.id,
757
+ });
758
+ },
759
+ error: (e) => {
760
+ this.snackBar.open(e.message);
761
+ },
762
+ });
763
+ }
764
+ });
765
+ }
766
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: OfferingManageSectionsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
767
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: OfferingManageSectionsComponent, isStandalone: true, selector: "rolatech-offering-manage-sections", ngImport: i0, template: "<rolatech-offering-manage-content>\n <rolatech-toolbar title=\"Details\" class=\"hidden md:block\" divider> </rolatech-toolbar>\n <div>\n <div>\n @for (section of sections; track section) {\n <rolatech-offering-manage-section-item\n [section]=\"section\"\n (upload)=\"onUploadSectionMedia($event)\"\n (deleteMedia)=\"onDeleteSectionMedia($event)\"\n (save)=\"onSectionSave($event)\"\n (delete)=\"onSectionDelete($event)\"\n [actions]=\"true\"\n ></rolatech-offering-manage-section-item>\n }\n </div>\n <button mat-stroked-button (click)=\"addSection()\" class=\"mt-3\">\n <mat-icon>add</mat-icon>\n <span i18n>Add</span>\n </button>\n </div>\n</rolatech-offering-manage-content>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i5.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: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: ToolbarComponent, selector: "rolatech-toolbar", inputs: ["title", "subtitle", "back", "link", "large", "divider"] }, { kind: "component", type: OfferingManageContentComponent, selector: "rolatech-offering-manage-content" }, { kind: "component", type: OfferingManageSectionItemComponent, selector: "rolatech-offering-manage-section-item", inputs: ["isUploading", "section", "actions", "selectMedia"], outputs: ["upload", "delete", "save", "deleteMedia"] }] });
768
+ }
769
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: OfferingManageSectionsComponent, decorators: [{
770
+ type: Component,
771
+ args: [{ selector: 'rolatech-offering-manage-sections', imports: [
772
+ MatButtonModule,
773
+ MatIconModule,
774
+ ToolbarComponent,
775
+ OfferingManageContentComponent,
776
+ OfferingManageSectionItemComponent,
777
+ ], template: "<rolatech-offering-manage-content>\n <rolatech-toolbar title=\"Details\" class=\"hidden md:block\" divider> </rolatech-toolbar>\n <div>\n <div>\n @for (section of sections; track section) {\n <rolatech-offering-manage-section-item\n [section]=\"section\"\n (upload)=\"onUploadSectionMedia($event)\"\n (deleteMedia)=\"onDeleteSectionMedia($event)\"\n (save)=\"onSectionSave($event)\"\n (delete)=\"onSectionDelete($event)\"\n [actions]=\"true\"\n ></rolatech-offering-manage-section-item>\n }\n </div>\n <button mat-stroked-button (click)=\"addSection()\" class=\"mt-3\">\n <mat-icon>add</mat-icon>\n <span i18n>Add</span>\n </button>\n </div>\n</rolatech-offering-manage-content>\n" }]
778
+ }], ctorParameters: () => [] });
779
+
780
+ const offeringManageRoutes = [
781
+ {
782
+ path: '',
783
+ component: OfferingManageIndexComponent,
784
+ },
785
+ {
786
+ path: 'create',
787
+ component: OfferingManageCreateComponent,
788
+ },
789
+ {
790
+ path: ':id/manage',
791
+ component: OfferingManageLayoutComponent,
792
+ children: [
793
+ {
794
+ path: 'info',
795
+ component: OfferingManageInfoComponent,
796
+ },
797
+ {
798
+ path: 'media',
799
+ component: OfferingManageMediaComponent,
800
+ },
801
+ {
802
+ path: 'sections',
803
+ component: OfferingManageSectionsComponent,
804
+ },
805
+ ],
806
+ },
807
+ ];
808
+
809
+ /**
810
+ * Generated bundle index. Do not edit.
811
+ */
812
+
813
+ export { offeringManageRoutes };
814
+ //# sourceMappingURL=rolatech-angular-offering.mjs.map