@rolatech/angular-property 19.0.0-beta.18 → 20.0.0-beta.1

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,3467 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, ElementRef, input, booleanAttribute, effect, HostBinding, ViewEncapsulation, Component, viewChild, PLATFORM_ID, output, computed, model, signal, Pipe, ViewChild } from '@angular/core';
3
+ import * as i1$1 from '@angular/material/button';
4
+ import { MatButtonModule } from '@angular/material/button';
5
+ import * as i4$2 from '@angular/material/menu';
6
+ import { MatMenuModule } from '@angular/material/menu';
7
+ import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
8
+ import { MatSnackBar } from '@angular/material/snack-bar';
9
+ import { MatTableModule } from '@angular/material/table';
10
+ import * as i1$4 from '@angular/router';
11
+ import { RouterLink, RouterModule, Router, ActivatedRoute, RouterLinkActive, RouterOutlet } from '@angular/router';
12
+ import { ThumbnailComponent, ImagePlaceholderComponent, ToolbarComponent, SpinnerComponent, ImagePreviewDialogComponent, BaseComponent, AcceptDialogComponent, RejectDialogComponent, RichLabelComponent, AngularComponentsModule, ConfirmationDialogComponent, TabsComponent, TabComponent, EmptyComponent, ContainerComponent, ListComponent, MediaListComponent, MediaListItemComponent } from '@rolatech/angular-components';
13
+ import { PropertyService, FeatureService } from '@rolatech/angular-services';
14
+ import * as i1 from '@angular/common';
15
+ import { CommonModule, NgClass, isPlatformBrowser, ViewportScroller, KeyValuePipe, Location } from '@angular/common';
16
+ import { FixedPipe, AngularCommonModule, OptionsFormatPipe, APP_CONFIG, DecimalDirective } from '@rolatech/angular-common';
17
+ import * as i2 from '@angular/material/icon';
18
+ import { MatIconModule } from '@angular/material/icon';
19
+ import { MatDialog } from '@angular/material/dialog';
20
+ import { AuthUserService, AuthService, AuthGuard } from '@rolatech/angular-auth';
21
+ import { GoogleMapsModule } from '@angular/google-maps';
22
+ import * as i1$3 from '@angular/forms';
23
+ import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
24
+ import * as i2$2 from '@angular/material/form-field';
25
+ import { MatFormFieldModule } from '@angular/material/form-field';
26
+ import * as i1$2 from '@angular/material/chips';
27
+ import { MatChipsModule } from '@angular/material/chips';
28
+ import { startWith, map, defer, forkJoin } from 'rxjs';
29
+ import { ENTER, COMMA } from '@angular/cdk/keycodes';
30
+ import * as i4$1 from '@angular/material/autocomplete';
31
+ import { MatAutocompleteModule } from '@angular/material/autocomplete';
32
+ import { MatOptionModule, MAT_DATE_LOCALE, DateAdapter, MAT_DATE_FORMATS } from '@angular/material/core';
33
+ import _, { findIndex, findLastIndex, first, remove } from 'lodash';
34
+ import * as i2$1 from '@angular/material/divider';
35
+ import { MatDividerModule, MatDivider } from '@angular/material/divider';
36
+ import * as i4 from '@angular/material/input';
37
+ import { MatInputModule } from '@angular/material/input';
38
+ import * as i5 from '@angular/material/select';
39
+ import { MatSelectModule } from '@angular/material/select';
40
+ import * as i8 from '@angular/material/datepicker';
41
+ import { MatDatepickerModule } from '@angular/material/datepicker';
42
+ import { trigger, state, transition, style, animate } from '@angular/animations';
43
+ import { CommentsComponent } from '@rolatech/angular-comment';
44
+ import { DomSanitizer } from '@angular/platform-browser';
45
+ import * as i4$3 from '@angular/cdk/text-field';
46
+ import { TextFieldModule } from '@angular/cdk/text-field';
47
+ import * as i9 from '@angular/material/radio';
48
+ import { MatRadioModule } from '@angular/material/radio';
49
+ import { MomentDateAdapter } from '@angular/material-moment-adapter';
50
+ import * as i3 from '@angular/material/progress-spinner';
51
+ import { MatProgressSpinner, MatProgressSpinnerModule } from '@angular/material/progress-spinner';
52
+ import * as i8$1 from '@angular/material/progress-bar';
53
+ import { MatProgressBarModule } from '@angular/material/progress-bar';
54
+ import * as i1$5 from '@angular/material/checkbox';
55
+ import { MatCheckboxModule } from '@angular/material/checkbox';
56
+
57
+ var PropertyPriceType;
58
+ (function (PropertyPriceType) {
59
+ PropertyPriceType["FIXED"] = "\u56FA\u5B9A\u4EF7\u683C";
60
+ PropertyPriceType["PARTIAL"] = "\u652F\u4ED8\u5B9A\u91D1";
61
+ PropertyPriceType["VARIED"] = "\u591A\u53D8\u7684";
62
+ })(PropertyPriceType || (PropertyPriceType = {}));
63
+ var PropertyVideoTourType;
64
+ (function (PropertyVideoTourType) {
65
+ PropertyVideoTourType["YOUTUBE"] = "Youtube";
66
+ PropertyVideoTourType["S3"] = "AWS S3";
67
+ PropertyVideoTourType["LOCAL"] = "Local server";
68
+ })(PropertyVideoTourType || (PropertyVideoTourType = {}));
69
+ var PropertyStatus;
70
+ (function (PropertyStatus) {
71
+ PropertyStatus[PropertyStatus["DRAFT"] = 'Draft'] = "DRAFT";
72
+ PropertyStatus[PropertyStatus["PENDING"] = 'Pending'] = "PENDING";
73
+ PropertyStatus[PropertyStatus["APPROVED"] = 'Approved'] = "APPROVED";
74
+ PropertyStatus[PropertyStatus["REJECTED"] = 'Rejected'] = "REJECTED";
75
+ PropertyStatus[PropertyStatus["AVAILABLE"] = 'Available'] = "AVAILABLE";
76
+ PropertyStatus[PropertyStatus["SOLD"] = 'Sold'] = "SOLD";
77
+ PropertyStatus[PropertyStatus["RENTED"] = 'Rented'] = "RENTED";
78
+ PropertyStatus[PropertyStatus["DELETED"] = 'Deleted'] = "DELETED";
79
+ PropertyStatus[PropertyStatus["ARCHIVED"] = 'Archived'] = "ARCHIVED";
80
+ })(PropertyStatus || (PropertyStatus = {}));
81
+ var PropertyType;
82
+ (function (PropertyType) {
83
+ PropertyType["APARTMENT"] = "Apartment";
84
+ PropertyType["CONDO"] = "Condo";
85
+ PropertyType["HOUSE"] = "Hourse";
86
+ PropertyType["TOWNHOUSE"] = "Townhourse";
87
+ PropertyType["LAND"] = "Land";
88
+ PropertyType["COMMERCIAL"] = "Commercial";
89
+ PropertyType["INDUSTRIAL"] = "Industrial";
90
+ PropertyType["FLAT"] = "Flat";
91
+ PropertyType["OTHER"] = "Other";
92
+ })(PropertyType || (PropertyType = {}));
93
+ var PropertyInventoryStatus;
94
+ (function (PropertyInventoryStatus) {
95
+ PropertyInventoryStatus["IN_STOCK"] = "\u6709\u8D27";
96
+ PropertyInventoryStatus["LOW_STOCK"] = "\u5E93\u5B58\u4E0D\u8DB3";
97
+ PropertyInventoryStatus["OUT_OF_STOCK"] = "\u552E\u7F44";
98
+ PropertyInventoryStatus["DISCONTINUED"] = "\u5DF2\u505C\u4EA7";
99
+ })(PropertyInventoryStatus || (PropertyInventoryStatus = {}));
100
+ var PropertyScope;
101
+ (function (PropertyScope) {
102
+ PropertyScope[PropertyScope["WEB"] = '网站'] = "WEB";
103
+ PropertyScope[PropertyScope["GLOBAL"] = '全域'] = "GLOBAL";
104
+ })(PropertyScope || (PropertyScope = {}));
105
+ var PropertyViewingStatus;
106
+ (function (PropertyViewingStatus) {
107
+ PropertyViewingStatus["PENDING"] = "Pending";
108
+ PropertyViewingStatus["APPROVED"] = "Approved";
109
+ PropertyViewingStatus["REJECTED"] = "Rejected";
110
+ PropertyViewingStatus["CANCELLED"] = "Cancelled";
111
+ PropertyViewingStatus["COMPLETED"] = "Completed";
112
+ })(PropertyViewingStatus || (PropertyViewingStatus = {}));
113
+ var PropertyViewerCategory;
114
+ (function (PropertyViewerCategory) {
115
+ PropertyViewerCategory["TENANT"] = "Tenant";
116
+ PropertyViewerCategory["AGENT"] = "Agent";
117
+ })(PropertyViewerCategory || (PropertyViewerCategory = {}));
118
+ var PropertyOfferTimelineStatus;
119
+ (function (PropertyOfferTimelineStatus) {
120
+ PropertyOfferTimelineStatus["OFFER_CREATED"] = "Offer created";
121
+ PropertyOfferTimelineStatus["OFFER_ACCEPTED"] = "Offer accepted";
122
+ PropertyOfferTimelineStatus["OFFER_REJECTED"] = "Offer rejected";
123
+ PropertyOfferTimelineStatus["OFFER_CLOSED"] = "Offer closed";
124
+ PropertyOfferTimelineStatus["OFFER_CANCELLED"] = "Offer cancelled";
125
+ PropertyOfferTimelineStatus["OFFER_HOLDING_DEPOSIT_PAID"] = "Offer holding deposit paid";
126
+ PropertyOfferTimelineStatus["OFFER_SECURITY_DEPOSIT_PAID"] = "Offer security deposit paid";
127
+ PropertyOfferTimelineStatus["OFFER_REFUNDED"] = "Offer refunded";
128
+ PropertyOfferTimelineStatus["OFFER_COMPLETED"] = "Offer completed";
129
+ PropertyOfferTimelineStatus["OFFER_RETURN_REQUESTED"] = "Offer return requested";
130
+ PropertyOfferTimelineStatus["OFFER_RETURN_APPROVED"] = "Offer return approved";
131
+ PropertyOfferTimelineStatus["OFFER_RETURN_REJECTED"] = "Offer return rejected";
132
+ PropertyOfferTimelineStatus["OFFER_RETURN_PROCESSING"] = "Offer return processing";
133
+ PropertyOfferTimelineStatus["OFFER_RETURN_REFUNDED"] = "Offer return refunded";
134
+ })(PropertyOfferTimelineStatus || (PropertyOfferTimelineStatus = {}));
135
+ var PropertyOfferType;
136
+ (function (PropertyOfferType) {
137
+ PropertyOfferType["RENT"] = "Rent";
138
+ PropertyOfferType["BUY"] = "Buy";
139
+ })(PropertyOfferType || (PropertyOfferType = {}));
140
+ var PropertyOfferStatus;
141
+ (function (PropertyOfferStatus) {
142
+ PropertyOfferStatus["PENDING"] = "Pending";
143
+ PropertyOfferStatus["ACCEPTED"] = "Accepted";
144
+ PropertyOfferStatus["REJECTED"] = "Rejected";
145
+ PropertyOfferStatus["HOLDING_DEPOSIT_PAID"] = "Holding deposit paid";
146
+ PropertyOfferStatus["SECURITY_DEPOSIT_PAID"] = "Security deposit paid";
147
+ PropertyOfferStatus["CANCELLED"] = "Cancelled";
148
+ PropertyOfferStatus["TENANCY_FINALIZED"] = "Finalized";
149
+ })(PropertyOfferStatus || (PropertyOfferStatus = {}));
150
+ var EmploymentStatus;
151
+ (function (EmploymentStatus) {
152
+ EmploymentStatus["EMPLOYED"] = "Employed";
153
+ EmploymentStatus["RETRIED"] = "Retried";
154
+ EmploymentStatus["SELF_EMPLOYED"] = "Self employed";
155
+ EmploymentStatus["STUDENT"] = "Student";
156
+ EmploymentStatus["UNEMPLOYED"] = "UnEmployed";
157
+ })(EmploymentStatus || (EmploymentStatus = {}));
158
+ var ResidencyStatus;
159
+ (function (ResidencyStatus) {
160
+ ResidencyStatus["UK_CITIZEN"] = "I don't need a visa";
161
+ ResidencyStatus["VISA_HOLDER"] = "I have a visa";
162
+ ResidencyStatus["WAITING_FOR_VISA"] = "I am waiting for my visa";
163
+ ResidencyStatus["NOT_YET_APPLIED_FOR_VISA"] = "I have not yet applied for a visa";
164
+ })(ResidencyStatus || (ResidencyStatus = {}));
165
+ var PropertyApplicantType;
166
+ (function (PropertyApplicantType) {
167
+ PropertyApplicantType["INDIVIDUAL"] = "Individual";
168
+ PropertyApplicantType["CORPORATE"] = "Corporate";
169
+ PropertyApplicantType["STUDENT"] = "Student";
170
+ PropertyApplicantType["SUBLETTING_COMPANY"] = "Subletting Company";
171
+ })(PropertyApplicantType || (PropertyApplicantType = {}));
172
+ var AdverseCreditStatus;
173
+ (function (AdverseCreditStatus) {
174
+ AdverseCreditStatus["NONE"] = "None";
175
+ AdverseCreditStatus["ACTIVE_CCJ"] = "Active CCJ";
176
+ AdverseCreditStatus["SATISFIED_CCJ"] = "Satisfied CCJ";
177
+ AdverseCreditStatus["DEBT_MANAGEMENT_PLAN"] = "Debt managment plan";
178
+ AdverseCreditStatus["LOW_CREDIT_SCORE"] = "Low credit score";
179
+ AdverseCreditStatus["OTHER"] = "Other";
180
+ })(AdverseCreditStatus || (AdverseCreditStatus = {}));
181
+ const ViewingTime = [
182
+ '00:00',
183
+ '00:15',
184
+ '00:30',
185
+ '00:45',
186
+ '01:00',
187
+ '01:15',
188
+ '01:30',
189
+ '01:45',
190
+ '02:00',
191
+ '02:15',
192
+ '02:30',
193
+ '02:45',
194
+ '03:00',
195
+ '03:15',
196
+ '03:30',
197
+ '03:45',
198
+ '04:00',
199
+ '04:15',
200
+ '04:30',
201
+ '04:45',
202
+ '05:00',
203
+ '05:15',
204
+ '05:30',
205
+ '05:45',
206
+ '06:00',
207
+ '06:15',
208
+ '06:30',
209
+ '06:45',
210
+ '07:00',
211
+ '07:15',
212
+ '07:30',
213
+ '07:45',
214
+ '08:00',
215
+ '08:15',
216
+ '08:30',
217
+ '08:45',
218
+ '09:00',
219
+ '09:15',
220
+ '09:30',
221
+ '09:45',
222
+ '10:00',
223
+ '10:15',
224
+ '10:30',
225
+ '10:45',
226
+ '11:00',
227
+ '11:15',
228
+ '11:30',
229
+ '11:45',
230
+ '12:00',
231
+ '12:15',
232
+ '12:30',
233
+ '12:45',
234
+ '13:00',
235
+ '13:15',
236
+ '13:30',
237
+ '13:45',
238
+ '14:00',
239
+ '14:15',
240
+ '14:30',
241
+ '14:45',
242
+ '15:00',
243
+ '15:15',
244
+ '15:30',
245
+ '15:45',
246
+ '16:00',
247
+ '16:15',
248
+ '16:30',
249
+ '16:45',
250
+ '17:00',
251
+ '17:15',
252
+ '17:30',
253
+ '17:45',
254
+ '18:00',
255
+ '18:15',
256
+ '18:30',
257
+ '18:45',
258
+ '19:00',
259
+ '19:15',
260
+ '19:30',
261
+ '19:45',
262
+ '20:00',
263
+ '20:15',
264
+ '20:30',
265
+ '20:45',
266
+ '21:00',
267
+ '21:15',
268
+ '21:30',
269
+ '21:45',
270
+ '22:00',
271
+ '22:15',
272
+ '22:30',
273
+ '22:45',
274
+ '23:00',
275
+ '23:15',
276
+ '23:30',
277
+ '23:45',
278
+ ];
279
+
280
+ class PropertyManageItemComponent {
281
+ hasClass = true;
282
+ el = inject(ElementRef);
283
+ property = input.required();
284
+ thumbnail = input();
285
+ status = PropertyStatus;
286
+ list = input(false, { transform: booleanAttribute });
287
+ constructor() {
288
+ effect(() => {
289
+ if (this.list()) {
290
+ this.el.nativeElement.setAttribute('list', '');
291
+ }
292
+ else {
293
+ this.el.nativeElement.removeAttribute('list', '');
294
+ }
295
+ });
296
+ }
297
+ publish() { }
298
+ archived() { }
299
+ delete() { }
300
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyManageItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
301
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: PropertyManageItemComponent, isStandalone: true, selector: "rolatech-property-manage-item", inputs: { property: { classPropertyName: "property", publicName: "property", 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-property-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 (property().media) {\n <div class=\"object-cover aspect-video rounded-lg\" [ngClass]=\"list() ? 'w-32 mr-3' : ''\">\n @defer (on viewport()) {\n <rolatech-thumbnail [src]=\"property().media ? property().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 {{ property().title }}\n </div>\n </div>\n <div class=\"flex-1\"></div>\n <div class=\"\">\u00A3{{ property().price | fixed }}</div>\n</div>\n", styles: ["rolatech-property-manage-item{--rt-property-view-item-margin: 16px;width:calc(100% / var(--rt-property-view-items-per-row) - var(--rt-property-view-item-margin) - .01px)}rolatech-property-manage-item[list]{--rt-property-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]] });
302
+ }
303
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyManageItemComponent, decorators: [{
304
+ type: Component,
305
+ args: [{ selector: 'rolatech-property-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 (property().media) {\n <div class=\"object-cover aspect-video rounded-lg\" [ngClass]=\"list() ? 'w-32 mr-3' : ''\">\n @defer (on viewport()) {\n <rolatech-thumbnail [src]=\"property().media ? property().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 {{ property().title }}\n </div>\n </div>\n <div class=\"flex-1\"></div>\n <div class=\"\">\u00A3{{ property().price | fixed }}</div>\n</div>\n", styles: ["rolatech-property-manage-item{--rt-property-view-item-margin: 16px;width:calc(100% / var(--rt-property-view-items-per-row) - var(--rt-property-view-item-margin) - .01px)}rolatech-property-manage-item[list]{--rt-property-view-items-per-row: 1}\n"] }]
306
+ }], ctorParameters: () => [], propDecorators: { hasClass: [{
307
+ type: HostBinding,
308
+ args: ['class.rolatech-property-manage-item']
309
+ }] } });
310
+
311
+ class PropertyReviewIndexComponent {
312
+ hasClass = true;
313
+ paginator = viewChild(MatPaginator);
314
+ dialog = inject(MatDialog);
315
+ snackBar = inject(MatSnackBar);
316
+ propertyService = inject(PropertyService);
317
+ isLoading = false;
318
+ isSearch = false;
319
+ properties = [];
320
+ ngOnInit() {
321
+ this.findProperties();
322
+ }
323
+ findProperties() {
324
+ this.propertyService.find({}).subscribe({
325
+ next: (res) => {
326
+ this.properties = res.data;
327
+ },
328
+ });
329
+ }
330
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyReviewIndexComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
331
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: PropertyReviewIndexComponent, isStandalone: true, selector: "rolatech-property-review-index", host: { properties: { "class.rolatech-property-manage-index": "this.hasClass" } }, viewQueries: [{ propertyName: "paginator", first: true, predicate: MatPaginator, descendants: true, isSignal: true }], ngImport: i0, template: "<rolatech-toolbar title=\"Properties\"> </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 <div class=\"flex flex-wrap p-3 gap-3\">\n @for (item of properties; track $index) {\n <rolatech-property-manage-item\n list\n [property]=\"item\"\n routerLink=\"./properties/{{ item.id }}\"\n ></rolatech-property-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-property-manage-index{--rt-property-view-items-per-row: 1}@media (min-width: 600px){rolatech-property-manage-index{--rt-property-view-items-per-row: 2}}@media (min-width: 768px){rolatech-property-manage-index{--rt-property-view-items-per-row: 3}}@media (min-width: 1280px){rolatech-property-manage-index{--rt-property-view-items-per-row: 3}}@media (min-width: 1536px){rolatech-property-manage-index{--rt-property-view-items-per-row: 4}}@media (min-width: 1920px){rolatech-property-manage-index{--rt-property-view-items-per-row: 5}}\n"], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: MatIconModule }, { 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: PropertyManageItemComponent, selector: "rolatech-property-manage-item", inputs: ["property", "thumbnail", "list"] }], encapsulation: i0.ViewEncapsulation.None });
332
+ }
333
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyReviewIndexComponent, decorators: [{
334
+ type: Component,
335
+ args: [{ selector: 'rolatech-property-review-index', imports: [
336
+ MatButtonModule,
337
+ RouterLink,
338
+ MatIconModule,
339
+ MatTableModule,
340
+ MatMenuModule,
341
+ MatPaginatorModule,
342
+ ToolbarComponent,
343
+ SpinnerComponent,
344
+ PropertyManageItemComponent,
345
+ ], encapsulation: ViewEncapsulation.None, template: "<rolatech-toolbar title=\"Properties\"> </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 <div class=\"flex flex-wrap p-3 gap-3\">\n @for (item of properties; track $index) {\n <rolatech-property-manage-item\n list\n [property]=\"item\"\n routerLink=\"./properties/{{ item.id }}\"\n ></rolatech-property-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-property-manage-index{--rt-property-view-items-per-row: 1}@media (min-width: 600px){rolatech-property-manage-index{--rt-property-view-items-per-row: 2}}@media (min-width: 768px){rolatech-property-manage-index{--rt-property-view-items-per-row: 3}}@media (min-width: 1280px){rolatech-property-manage-index{--rt-property-view-items-per-row: 3}}@media (min-width: 1536px){rolatech-property-manage-index{--rt-property-view-items-per-row: 4}}@media (min-width: 1920px){rolatech-property-manage-index{--rt-property-view-items-per-row: 5}}\n"] }]
346
+ }], propDecorators: { hasClass: [{
347
+ type: HostBinding,
348
+ args: ['class.rolatech-property-manage-index']
349
+ }] } });
350
+
351
+ class PropertyMediaComponent {
352
+ dialog = inject(MatDialog);
353
+ media = input([]);
354
+ min = input(false);
355
+ mediaIndex = 0;
356
+ onImageClick(i) {
357
+ const dialogRef = this.dialog.open(ImagePreviewDialogComponent, {
358
+ maxWidth: '80vw',
359
+ maxHeight: '80vh',
360
+ height: '80%',
361
+ width: '80%',
362
+ panelClass: 'full-screen-modal',
363
+ data: {
364
+ media: this.media,
365
+ selected: i,
366
+ },
367
+ });
368
+ dialogRef.afterClosed().subscribe((result) => { });
369
+ }
370
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyMediaComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
371
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: PropertyMediaComponent, isStandalone: true, selector: "rolatech-property-media", inputs: { media: { classPropertyName: "media", publicName: "media", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div>\n <div class=\"object-cover aspect-video bg-[--rt-raised-background]\">\n <rolatech-thumbnail [src]=\"media() ? media()[mediaIndex].url : ''\" size=\"small\"></rolatech-thumbnail>\n </div>\n @for (media of media(); track media; let index = $index) {\n <div\n class=\"inline-flex flex-row mt-3 mr-3 cursor-pointer rounded-md w-32\"\n (click)=\"mediaIndex = index\"\n [ngClass]=\"mediaIndex === index ? '' : 'opacity-30'\"\n >\n <rolatech-thumbnail [src]=\"media.url\" size=\"small\"></rolatech-thumbnail>\n </div>\n }\n</div>\n", styles: [""], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: ThumbnailComponent, selector: "rolatech-thumbnail", inputs: ["src", "size", "mode", "ratio", "width", "height"] }], encapsulation: i0.ViewEncapsulation.None });
372
+ }
373
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyMediaComponent, decorators: [{
374
+ type: Component,
375
+ args: [{ selector: 'rolatech-property-media', imports: [NgClass, ThumbnailComponent], encapsulation: ViewEncapsulation.None, template: "<div>\n <div class=\"object-cover aspect-video bg-[--rt-raised-background]\">\n <rolatech-thumbnail [src]=\"media() ? media()[mediaIndex].url : ''\" size=\"small\"></rolatech-thumbnail>\n </div>\n @for (media of media(); track media; let index = $index) {\n <div\n class=\"inline-flex flex-row mt-3 mr-3 cursor-pointer rounded-md w-32\"\n (click)=\"mediaIndex = index\"\n [ngClass]=\"mediaIndex === index ? '' : 'opacity-30'\"\n >\n <rolatech-thumbnail [src]=\"media.url\" size=\"small\"></rolatech-thumbnail>\n </div>\n }\n</div>\n" }]
376
+ }] });
377
+
378
+ class PropertyFeaturesComponent {
379
+ features = input.required();
380
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyFeaturesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
381
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: PropertyFeaturesComponent, isStandalone: true, selector: "rolatech-property-features", inputs: { features: { classPropertyName: "features", publicName: "features", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div class=\"\">\n <div class=\"text-2xl font-bold py-3\" i18n>Features</div>\n <div class=\"flex flex-row flex-wrap items-center font-thin text-sm\">\n @for (feature of features(); track feature) {\n <div class=\"flex items-center mr-2\">\n <mat-icon>check</mat-icon>\n <span>{{ feature.value.name }}</span>\n </div>\n }\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }] });
382
+ }
383
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyFeaturesComponent, decorators: [{
384
+ type: Component,
385
+ args: [{ selector: 'rolatech-property-features', imports: [CommonModule, MatIconModule], template: "<div class=\"\">\n <div class=\"text-2xl font-bold py-3\" i18n>Features</div>\n <div class=\"flex flex-row flex-wrap items-center font-thin text-sm\">\n @for (feature of features(); track feature) {\n <div class=\"flex items-center mr-2\">\n <mat-icon>check</mat-icon>\n <span>{{ feature.value.name }}</span>\n </div>\n }\n </div>\n</div>\n" }]
386
+ }] });
387
+
388
+ class PropertyLocationComponent {
389
+ platformId = inject(PLATFORM_ID);
390
+ location = input.required();
391
+ mapElementRef = viewChild.required('mapElement');
392
+ map;
393
+ autocompleteService;
394
+ placesService;
395
+ sessionToken;
396
+ options = {
397
+ mapId: '6a8f7b7d56282c69',
398
+ center: { lat: 51.509865, lng: -0.118092 },
399
+ zoom: 15,
400
+ };
401
+ ngOnInit() {
402
+ if (isPlatformBrowser(this.platformId)) {
403
+ google.maps.importLibrary('places').then((res) => {
404
+ this.map = new google.maps.Map(this.mapElementRef().nativeElement, this.options);
405
+ this.autocompleteService = new google.maps.places.AutocompleteService();
406
+ this.placesService = new google.maps.places.PlacesService(document.createElement('div'));
407
+ this.sessionToken = new google.maps.places.AutocompleteSessionToken();
408
+ const request = {
409
+ input: this.location().postCode,
410
+ sessionToken: this.sessionToken,
411
+ types: ['geocode'], // Filter to addresses
412
+ componentRestrictions: { country: 'gb' },
413
+ };
414
+ this.autocompleteService.getPlacePredictions(request, (predictions, status) => {
415
+ if (status === google.maps.places.PlacesServiceStatus.OK && predictions && predictions.length > 0) {
416
+ const placeId = predictions[0].place_id;
417
+ this.getPlaceDetails(placeId);
418
+ }
419
+ });
420
+ });
421
+ }
422
+ }
423
+ getPlaceDetails(placeId) {
424
+ this.placesService.getDetails({ placeId }, (place, status) => {
425
+ if (status === google.maps.places.PlacesServiceStatus.OK) {
426
+ const location = place.geometry?.location;
427
+ if (location) {
428
+ this.map.setCenter(location);
429
+ new google.maps.Marker({
430
+ map: this.map,
431
+ position: location,
432
+ });
433
+ }
434
+ }
435
+ });
436
+ }
437
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyLocationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
438
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.0.3", type: PropertyLocationComponent, isStandalone: true, selector: "rolatech-property-location", inputs: { location: { classPropertyName: "location", publicName: "location", isSignal: true, isRequired: true, transformFunction: null } }, viewQueries: [{ propertyName: "mapElementRef", first: true, predicate: ["mapElement"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"\">\n <div class=\"text-2xl font-bold py-4\" i18n>Location</div>\n <div class=\"bg-[--rt-raised-background]\">\n <div class=\"map-container\">\n <div #mapElement id=\"map\"></div>\n </div>\n <!-- <google-map width=\"100%\" #mapRef [options]=\"options\">\n <map-advanced-marker #marker=\"mapAdvancedMarker\" />\n </google-map> -->\n </div>\n</div>\n", styles: [".aspect-ratio-16-9{position:relative;width:100%;padding-bottom:56.25%;height:0}.map-container{position:relative;width:100%;padding-bottom:56.25%;height:0;overflow:hidden}#map{position:absolute;top:0;left:0;width:100%;height:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: GoogleMapsModule }] });
439
+ }
440
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyLocationComponent, decorators: [{
441
+ type: Component,
442
+ args: [{ selector: 'rolatech-property-location', imports: [CommonModule, GoogleMapsModule], template: "<div class=\"\">\n <div class=\"text-2xl font-bold py-4\" i18n>Location</div>\n <div class=\"bg-[--rt-raised-background]\">\n <div class=\"map-container\">\n <div #mapElement id=\"map\"></div>\n </div>\n <!-- <google-map width=\"100%\" #mapRef [options]=\"options\">\n <map-advanced-marker #marker=\"mapAdvancedMarker\" />\n </google-map> -->\n </div>\n</div>\n", styles: [".aspect-ratio-16-9{position:relative;width:100%;padding-bottom:56.25%;height:0}.map-container{position:relative;width:100%;padding-bottom:56.25%;height:0;overflow:hidden}#map{position:absolute;top:0;left:0;width:100%;height:100%}\n"] }]
443
+ }] });
444
+
445
+ class PropertyReviewDetailComponent extends BaseComponent {
446
+ propertyService = inject(PropertyService);
447
+ authUserService = inject(AuthUserService);
448
+ property;
449
+ agent;
450
+ status = PropertyStatus;
451
+ ngOnInit() {
452
+ this.getProperty();
453
+ }
454
+ getProperty() {
455
+ this.propertyService.get(this.id).subscribe({
456
+ next: (res) => {
457
+ this.property = res.data;
458
+ this.getAgentPublicInfo(this.property.agentId);
459
+ },
460
+ });
461
+ }
462
+ getAgentPublicInfo(agentId) {
463
+ this.authUserService.getPublicUserInfo(agentId).subscribe({
464
+ next: (res) => {
465
+ this.agent = res;
466
+ console.log(this.agent);
467
+ },
468
+ });
469
+ }
470
+ approve() {
471
+ const dialogRef = this.dialog.open(AcceptDialogComponent, {
472
+ width: '400px',
473
+ data: {
474
+ title: 'Approve',
475
+ message: 'Approve this property?',
476
+ },
477
+ });
478
+ dialogRef.afterClosed().subscribe((result) => {
479
+ if (result) {
480
+ this.propertyService.reviewApprove(this.id).subscribe({
481
+ next: (res) => {
482
+ this.property.status = 'ACTIVE';
483
+ this.snackBarService.open('Approved');
484
+ },
485
+ error: (e) => {
486
+ this.snackBarService.open(e.message);
487
+ },
488
+ });
489
+ }
490
+ });
491
+ }
492
+ reject() {
493
+ const dialogRef = this.dialog.open(RejectDialogComponent, {
494
+ width: '400px',
495
+ data: {
496
+ content: '',
497
+ },
498
+ });
499
+ dialogRef.afterClosed().subscribe((result) => {
500
+ if (result) {
501
+ this.propertyService.reviewReject(this.id, result).subscribe({
502
+ next: (res) => {
503
+ this.snackBarService.open('Rejected');
504
+ },
505
+ error: (e) => {
506
+ this.snackBarService.open(e.message);
507
+ },
508
+ });
509
+ }
510
+ });
511
+ }
512
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyReviewDetailComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
513
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: PropertyReviewDetailComponent, isStandalone: true, selector: "rolatech-property-review-detail", usesInheritance: true, ngImport: i0, template: "@if (property) {\n <rolatech-toolbar [title]=\"status[property.status]\" large link=\"../../\">\n <button mat-flat-button (click)=\"approve()\" i18n>Approve</button>\n <button mat-button (click)=\"reject()\" i18n>Reject</button>\n </rolatech-toolbar>\n <div class=\"px-4\">\n <div>\n <div class=\"text-lg font-bold py-2\">Property details</div>\n <hr class=\"mb-2\" />\n <div class=\"flex items-center py-2\">\n <div class=\"min-w-16 w-24 object-cover aspect-video rounded-md mr-3\">\n @defer (on viewport()) {\n <rolatech-thumbnail [src]=\"property.media[0].url\" size=\"medium\" mode=\"full\"> </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 <div class=\"flex w-full justify-between\">\n <div class=\"flex justify-between w-full\">\n <div class=\"flex flex-col\">\n <div>{{ property.title }}</div>\n <div class=\"inline-flex gap-1 mt-2\">\n <div>\n <span class=\"mr-1\">{{ property.bedrooms }}</span>\n <span i18n>Bedrooms</span>\n </div>\n <div>\n <span class=\"mr-1\">{{ property.bathrooms }}</span>\n <span i18n>Bathrooms</span>\n </div>\n </div>\n </div>\n <div class=\"text-right\">\n <div class=\"text-sm\">\u00A3{{ property.price | fixed }}</div>\n </div>\n </div>\n </div>\n <div class=\"hidden md:flex flex-col px-3\"></div>\n </div>\n <rolatech-property-media [media]=\"property.media\"></rolatech-property-media>\n <rolatech-property-features [features]=\"property.features\"></rolatech-property-features>\n <rolatech-property-location [location]=\"property.location\"></rolatech-property-location>\n </div>\n <div class=\"mb-32\">\n <div class=\"text-lg font-bold py-2\" i18n>Agent</div>\n <hr class=\"mb-2\" />\n @if (agent) {\n <div class=\"flex flex-col md:flex-row gap-1 md:gap-3\">\n <rolatech-rich-label label=\"Name\" [title]=\"agent.name\"></rolatech-rich-label>\n <rolatech-rich-label label=\"Email\" [title]=\"agent.email\"></rolatech-rich-label>\n <rolatech-rich-label label=\"Phone\" [title]=\"agent.phone\"></rolatech-rich-label>\n </div>\n }\n </div>\n </div>\n}\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: ToolbarComponent, selector: "rolatech-toolbar", inputs: ["title", "subtitle", "back", "link", "large", "divider"] }, { kind: "component", type: RichLabelComponent, selector: "rolatech-rich-label", inputs: ["label", "title"] }, { kind: "pipe", type: FixedPipe, name: "fixed" }, { kind: "component", type: PropertyMediaComponent, selector: "rolatech-property-media", inputs: ["media", "min"] }, { kind: "component", type: PropertyFeaturesComponent, selector: "rolatech-property-features", inputs: ["features"] }, { kind: "component", type: PropertyLocationComponent, selector: "rolatech-property-location", inputs: ["location"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.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"] }], deferBlockDependencies: [() => [ThumbnailComponent]] });
514
+ }
515
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyReviewDetailComponent, decorators: [{
516
+ type: Component,
517
+ args: [{ selector: 'rolatech-property-review-detail', imports: [
518
+ CommonModule,
519
+ ToolbarComponent,
520
+ ThumbnailComponent,
521
+ RichLabelComponent,
522
+ FixedPipe,
523
+ PropertyMediaComponent,
524
+ PropertyFeaturesComponent,
525
+ PropertyLocationComponent,
526
+ MatButtonModule,
527
+ ], template: "@if (property) {\n <rolatech-toolbar [title]=\"status[property.status]\" large link=\"../../\">\n <button mat-flat-button (click)=\"approve()\" i18n>Approve</button>\n <button mat-button (click)=\"reject()\" i18n>Reject</button>\n </rolatech-toolbar>\n <div class=\"px-4\">\n <div>\n <div class=\"text-lg font-bold py-2\">Property details</div>\n <hr class=\"mb-2\" />\n <div class=\"flex items-center py-2\">\n <div class=\"min-w-16 w-24 object-cover aspect-video rounded-md mr-3\">\n @defer (on viewport()) {\n <rolatech-thumbnail [src]=\"property.media[0].url\" size=\"medium\" mode=\"full\"> </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 <div class=\"flex w-full justify-between\">\n <div class=\"flex justify-between w-full\">\n <div class=\"flex flex-col\">\n <div>{{ property.title }}</div>\n <div class=\"inline-flex gap-1 mt-2\">\n <div>\n <span class=\"mr-1\">{{ property.bedrooms }}</span>\n <span i18n>Bedrooms</span>\n </div>\n <div>\n <span class=\"mr-1\">{{ property.bathrooms }}</span>\n <span i18n>Bathrooms</span>\n </div>\n </div>\n </div>\n <div class=\"text-right\">\n <div class=\"text-sm\">\u00A3{{ property.price | fixed }}</div>\n </div>\n </div>\n </div>\n <div class=\"hidden md:flex flex-col px-3\"></div>\n </div>\n <rolatech-property-media [media]=\"property.media\"></rolatech-property-media>\n <rolatech-property-features [features]=\"property.features\"></rolatech-property-features>\n <rolatech-property-location [location]=\"property.location\"></rolatech-property-location>\n </div>\n <div class=\"mb-32\">\n <div class=\"text-lg font-bold py-2\" i18n>Agent</div>\n <hr class=\"mb-2\" />\n @if (agent) {\n <div class=\"flex flex-col md:flex-row gap-1 md:gap-3\">\n <rolatech-rich-label label=\"Name\" [title]=\"agent.name\"></rolatech-rich-label>\n <rolatech-rich-label label=\"Email\" [title]=\"agent.email\"></rolatech-rich-label>\n <rolatech-rich-label label=\"Phone\" [title]=\"agent.phone\"></rolatech-rich-label>\n </div>\n }\n </div>\n </div>\n}\n" }]
528
+ }] });
529
+
530
+ const propertyReviewRoutes = [
531
+ {
532
+ path: '',
533
+ component: PropertyReviewIndexComponent,
534
+ },
535
+ {
536
+ path: 'properties/:id',
537
+ component: PropertyReviewDetailComponent,
538
+ },
539
+ ];
540
+
541
+ class FeatureManageItemComponent {
542
+ feature = input.required();
543
+ addOnBlur = true;
544
+ delete = output();
545
+ save = output();
546
+ edit = output();
547
+ onDelete() {
548
+ this.delete.emit(this.feature());
549
+ }
550
+ onSave() {
551
+ this.save.emit(this.feature());
552
+ }
553
+ onEdit() {
554
+ this.edit.emit(this.feature());
555
+ }
556
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: FeatureManageItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
557
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.0.3", type: FeatureManageItemComponent, isStandalone: true, selector: "rolatech-feature-manage-item", inputs: { feature: { classPropertyName: "feature", publicName: "feature", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { delete: "delete", save: "save", edit: "edit" }, ngImport: i0, template: "<div class=\"group flex justify-between items-center h-14 hover:bg-[--rt-raised-background] cursor-pointer px-2\">\n <div class=\"flex\">\n <div class=\"min-w-[150px] mr-3\">{{ feature().name }}</div>\n <div class=\"overflow-hidden line-clamp-1\">\n {{ feature().values! | options }}\n </div>\n </div>\n <div class=\"flex justify-end max-w-24 w-24 invisible group-hover:visible\">\n <button mat-icon-button (click)=\"onEdit()\">\n <mat-icon>edit</mat-icon>\n </button>\n <button mat-icon-button (click)=\"onDelete()\">\n <mat-icon>delete</mat-icon>\n </button>\n </div>\n</div>\n<mat-divider></mat-divider>\n", styles: ["mat-icon{scale:.9}\n"], dependencies: [{ kind: "ngmodule", type: MatChipsModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: AngularCommonModule }, { kind: "ngmodule", type: AngularComponentsModule }, { kind: "component", type: i2$1.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "pipe", type: OptionsFormatPipe, name: "options" }] });
558
+ }
559
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: FeatureManageItemComponent, decorators: [{
560
+ type: Component,
561
+ args: [{ selector: 'rolatech-feature-manage-item', imports: [
562
+ MatChipsModule,
563
+ MatButtonModule,
564
+ MatFormFieldModule,
565
+ AngularCommonModule,
566
+ AngularComponentsModule,
567
+ MatIconModule,
568
+ OptionsFormatPipe,
569
+ ], template: "<div class=\"group flex justify-between items-center h-14 hover:bg-[--rt-raised-background] cursor-pointer px-2\">\n <div class=\"flex\">\n <div class=\"min-w-[150px] mr-3\">{{ feature().name }}</div>\n <div class=\"overflow-hidden line-clamp-1\">\n {{ feature().values! | options }}\n </div>\n </div>\n <div class=\"flex justify-end max-w-24 w-24 invisible group-hover:visible\">\n <button mat-icon-button (click)=\"onEdit()\">\n <mat-icon>edit</mat-icon>\n </button>\n <button mat-icon-button (click)=\"onDelete()\">\n <mat-icon>delete</mat-icon>\n </button>\n </div>\n</div>\n<mat-divider></mat-divider>\n", styles: ["mat-icon{scale:.9}\n"] }]
570
+ }] });
571
+
572
+ class FeatureManageItemAddComponent {
573
+ feature = {
574
+ id: '',
575
+ name: '',
576
+ values: [],
577
+ };
578
+ addOnBlur = true;
579
+ separatorKeysCodes = [ENTER, COMMA];
580
+ cancel = output();
581
+ save = output();
582
+ action = true;
583
+ output = output();
584
+ onCancel() {
585
+ this.cancel.emit();
586
+ }
587
+ onSave() {
588
+ this.save.emit(this.feature);
589
+ }
590
+ addFeatureValues(event) {
591
+ const value = (event.value || '').trim();
592
+ const exists = this.feature.values.some((item) => item.name.toLowerCase() === value.toLowerCase());
593
+ if (value && !exists) {
594
+ this.feature.values.push({ id: '', name: value });
595
+ }
596
+ event.chipInput.clear();
597
+ }
598
+ removeValue(value) {
599
+ if (value) {
600
+ const index = this.feature.values.findIndex((item) => item.name === value.name);
601
+ this.feature.values?.splice(index, 1);
602
+ }
603
+ }
604
+ ngDoCheck() {
605
+ this.output.emit(this.feature);
606
+ }
607
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: FeatureManageItemAddComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
608
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: FeatureManageItemAddComponent, isStandalone: true, selector: "rolatech-feature-manage-item-add", outputs: { cancel: "cancel", save: "save", output: "output" }, ngImport: i0, template: "<div class=\"flex flex-col\">\n <div class=\"p-1\" i18n>Feature</div>\n <form>\n <mat-form-field appearance=\"fill\">\n <input matInput placeholder=\"Name\" type=\"text\" [(ngModel)]=\"feature.name\" [ngModelOptions]=\"{ standalone: true }\" i18n />\n </mat-form-field>\n <mat-form-field>\n <mat-chip-grid #chipGrid aria-label=\"Enter values\">\n @for (value of feature.values; track value) {\n <mat-chip-row\n (removed)=\"removeValue(value)\"\n [editable]=\"true\"\n [aria-description]=\"'press enter to edit ' + value.name\"\n >\n {{ value.name }}\n <button matChipRemove [attr.aria-label]=\"'remove ' + value.name\">\n <mat-icon>cancel</mat-icon>\n </button>\n </mat-chip-row>\n }\n <input\n placeholder=\"Value\"\n [matChipInputFor]=\"chipGrid\"\n [matChipInputSeparatorKeyCodes]=\"separatorKeysCodes\"\n [matChipInputAddOnBlur]=\"addOnBlur\"\n (matChipInputTokenEnd)=\"addFeatureValues($event)\"\n i18n\n />\n </mat-chip-grid>\n </mat-form-field>\n </form>\n</div>\n@if (action) {\n <div class=\"flex items-center justify-end\">\n <button mat-button (click)=\"onCancel()\" i18n>Cancel</button>\n <button mat-flat-button (click)=\"onSave()\" i18n>Save</button>\n </div>\n}\n", styles: ["mat-form-field{width:100%}\n"], dependencies: [{ kind: "ngmodule", type: MatChipsModule }, { kind: "component", type: i1$2.MatChipGrid, selector: "mat-chip-grid", inputs: ["disabled", "placeholder", "required", "value", "errorStateMatcher"], outputs: ["change", "valueChange"] }, { kind: "directive", type: i1$2.MatChipInput, selector: "input[matChipInputFor]", inputs: ["matChipInputFor", "matChipInputAddOnBlur", "matChipInputSeparatorKeyCodes", "placeholder", "id", "disabled", "readonly", "matChipInputDisabledInteractive"], outputs: ["matChipInputTokenEnd"], exportAs: ["matChipInput", "matChipInputFor"] }, { kind: "directive", type: i1$2.MatChipRemove, selector: "[matChipRemove]" }, { kind: "component", type: i1$2.MatChipRow, selector: "mat-chip-row, [mat-chip-row], mat-basic-chip-row, [mat-basic-chip-row]", inputs: ["editable"], outputs: ["edited"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.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: MatFormFieldModule }, { kind: "component", type: i2$2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "ngmodule", type: AngularCommonModule }, { kind: "directive", type: i1$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$3.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$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1$3.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: AngularComponentsModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatIconModule }] });
609
+ }
610
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: FeatureManageItemAddComponent, decorators: [{
611
+ type: Component,
612
+ args: [{ selector: 'rolatech-feature-manage-item-add', imports: [MatChipsModule, MatButtonModule, MatFormFieldModule, AngularCommonModule, AngularComponentsModule, MatIconModule], template: "<div class=\"flex flex-col\">\n <div class=\"p-1\" i18n>Feature</div>\n <form>\n <mat-form-field appearance=\"fill\">\n <input matInput placeholder=\"Name\" type=\"text\" [(ngModel)]=\"feature.name\" [ngModelOptions]=\"{ standalone: true }\" i18n />\n </mat-form-field>\n <mat-form-field>\n <mat-chip-grid #chipGrid aria-label=\"Enter values\">\n @for (value of feature.values; track value) {\n <mat-chip-row\n (removed)=\"removeValue(value)\"\n [editable]=\"true\"\n [aria-description]=\"'press enter to edit ' + value.name\"\n >\n {{ value.name }}\n <button matChipRemove [attr.aria-label]=\"'remove ' + value.name\">\n <mat-icon>cancel</mat-icon>\n </button>\n </mat-chip-row>\n }\n <input\n placeholder=\"Value\"\n [matChipInputFor]=\"chipGrid\"\n [matChipInputSeparatorKeyCodes]=\"separatorKeysCodes\"\n [matChipInputAddOnBlur]=\"addOnBlur\"\n (matChipInputTokenEnd)=\"addFeatureValues($event)\"\n i18n\n />\n </mat-chip-grid>\n </mat-form-field>\n </form>\n</div>\n@if (action) {\n <div class=\"flex items-center justify-end\">\n <button mat-button (click)=\"onCancel()\" i18n>Cancel</button>\n <button mat-flat-button (click)=\"onSave()\" i18n>Save</button>\n </div>\n}\n", styles: ["mat-form-field{width:100%}\n"] }]
613
+ }] });
614
+
615
+ class FeatureManageIndexComponent extends BaseComponent {
616
+ featureService = inject(FeatureService);
617
+ features = [];
618
+ selectedFeatures = [];
619
+ filteredFeatures;
620
+ addOnBlur = true;
621
+ add = false;
622
+ allowFreeTextAddFeature = false;
623
+ featureControl = new FormControl();
624
+ separatorKeysCodes = [ENTER, COMMA];
625
+ featureInput = viewChild.required('featureInput');
626
+ matAutocomplete = viewChild('auto');
627
+ ngOnInit() {
628
+ this.findFeatures();
629
+ }
630
+ findFeatures() {
631
+ this.featureService.find({}).subscribe({
632
+ next: (res) => {
633
+ this.features = res.data || [];
634
+ this.filteredFeatures = this.featureControl.valueChanges.pipe(startWith(null), map((featureName) => {
635
+ return this.filterOnInputChange(featureName);
636
+ }));
637
+ },
638
+ });
639
+ }
640
+ saveFeature(feature) {
641
+ console.log(feature);
642
+ this.featureService.addFeature(feature).subscribe({
643
+ next: (res) => {
644
+ this.features.push(res.data);
645
+ this.add = false;
646
+ },
647
+ });
648
+ }
649
+ onUpdateFeature(feature) {
650
+ const features = {
651
+ title: 'Edit feature',
652
+ cancelText: 'Cancel',
653
+ confirmText: 'Confirm',
654
+ data: {
655
+ feature: feature,
656
+ action: false,
657
+ },
658
+ component: FeatureManageItemAddComponent,
659
+ };
660
+ this.dialogService.open(features);
661
+ this.dialogService.confirmed().subscribe({
662
+ next: (res) => {
663
+ if (res) {
664
+ this.featureService.update(feature.id, res).subscribe({
665
+ next: (res) => {
666
+ feature.values = res.data.values;
667
+ },
668
+ });
669
+ }
670
+ },
671
+ });
672
+ }
673
+ onDeleteFeature(feature) {
674
+ const dialogRef = this.dialog.open(ConfirmationDialogComponent, {
675
+ width: '400px',
676
+ data: {
677
+ title: 'Delete feature',
678
+ message: `Confirm delete ${feature.name} ?`,
679
+ },
680
+ });
681
+ dialogRef.afterClosed().subscribe((result) => {
682
+ if (result) {
683
+ this.featureService.delete(feature.id).subscribe({
684
+ next: (res) => {
685
+ const index = this.features.findIndex((item) => item.id === feature.id);
686
+ this.features.splice(index, 1);
687
+ this.snackBarService.open('Delete successfully');
688
+ },
689
+ });
690
+ }
691
+ });
692
+ }
693
+ // addFeature(event: MatChipInputEvent) {
694
+ // const value = (event.value || '').trim();
695
+ // if (value) {
696
+ // this.features.push({ id: '', name: value });
697
+ // }
698
+ // event.chipInput.clear();
699
+ // }
700
+ filterOnInputChange(featureName) {
701
+ const featuresLessSelected = this.features.filter((engineer) => findIndex(this.selectedFeatures, (item) => item.name === engineer.name) < 0);
702
+ return featureName
703
+ ? this.filterFeature(featuresLessSelected, featureName)
704
+ : featuresLessSelected.map((feature) => feature.name);
705
+ }
706
+ filterFeature(featureList, featureName) {
707
+ let filteredFeatureList = [];
708
+ const filterValue = featureName.toLowerCase();
709
+ const featuresMatchingFeatureName = featureList.filter((feature) => feature.name.toLowerCase().indexOf(filterValue) === 0);
710
+ if (featuresMatchingFeatureName.length || this.allowFreeTextAddFeature) {
711
+ filteredFeatureList = featuresMatchingFeatureName;
712
+ }
713
+ else {
714
+ filteredFeatureList = featureList;
715
+ }
716
+ return filteredFeatureList.map((feature) => feature.name);
717
+ }
718
+ remove(feature) {
719
+ const index = findIndex(this.selectedFeatures, (item) => item.name === feature.name);
720
+ if (index > -1) {
721
+ this.selectedFeatures.splice(index, 1);
722
+ this.resetInputs();
723
+ }
724
+ }
725
+ selected(event) {
726
+ this.selectFeatureByName(event.option.value);
727
+ this.resetInputs();
728
+ }
729
+ selectFeatureByName(featureName) {
730
+ const foundFeature = this.features.filter((item) => item.name == featureName);
731
+ this.selectedFeatures.push(foundFeature[0]);
732
+ }
733
+ resetInputs() {
734
+ this.featureInput().nativeElement.value = '';
735
+ this.featureControl.setValue(null);
736
+ }
737
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: FeatureManageIndexComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
738
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: FeatureManageIndexComponent, isStandalone: true, selector: "rolatech-feature-manage-index", viewQueries: [{ propertyName: "featureInput", first: true, predicate: ["featureInput"], descendants: true, isSignal: true }, { propertyName: "matAutocomplete", first: true, predicate: ["auto"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<rolatech-toolbar title=\"Features\">\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 Feature</span>\n </button>\n </div>\n</rolatech-toolbar>\n<div class=\"p-3\">\n @for (item of features; track $index) {\n <rolatech-feature-manage-item\n [feature]=\"item\"\n (delete)=\"onDeleteFeature($event)\"\n (edit)=\"onUpdateFeature($event)\"\n ></rolatech-feature-manage-item>\n }\n <div class=\"mt-3\">\n @if (add) {\n <rolatech-feature-manage-item-add (save)=\"saveFeature($event)\" (cancel)=\"add = false\"></rolatech-feature-manage-item-add>\n } @else {\n <button mat-stroked-button (click)=\"add = true\">\n <mat-icon>add</mat-icon>\n <span i18n>Add</span>\n </button>\n }\n </div>\n</div>\n", styles: [".features-chip-list{width:100%}\n"], dependencies: [{ kind: "ngmodule", type: MatDividerModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatChipsModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatAutocompleteModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.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: MatOptionModule }, { kind: "component", type: ToolbarComponent, selector: "rolatech-toolbar", inputs: ["title", "subtitle", "back", "link", "large", "divider"] }, { kind: "component", type: FeatureManageItemComponent, selector: "rolatech-feature-manage-item", inputs: ["feature"], outputs: ["delete", "save", "edit"] }, { kind: "component", type: FeatureManageItemAddComponent, selector: "rolatech-feature-manage-item-add", outputs: ["cancel", "save", "output"] }] });
739
+ }
740
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: FeatureManageIndexComponent, decorators: [{
741
+ type: Component,
742
+ args: [{ selector: 'rolatech-feature-manage-index', imports: [
743
+ MatDividerModule,
744
+ FormsModule,
745
+ MatFormFieldModule,
746
+ MatChipsModule,
747
+ MatIconModule,
748
+ MatAutocompleteModule,
749
+ ReactiveFormsModule,
750
+ MatButtonModule,
751
+ MatOptionModule,
752
+ ToolbarComponent,
753
+ FeatureManageItemComponent,
754
+ FeatureManageItemAddComponent,
755
+ ], template: "<rolatech-toolbar title=\"Features\">\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 Feature</span>\n </button>\n </div>\n</rolatech-toolbar>\n<div class=\"p-3\">\n @for (item of features; track $index) {\n <rolatech-feature-manage-item\n [feature]=\"item\"\n (delete)=\"onDeleteFeature($event)\"\n (edit)=\"onUpdateFeature($event)\"\n ></rolatech-feature-manage-item>\n }\n <div class=\"mt-3\">\n @if (add) {\n <rolatech-feature-manage-item-add (save)=\"saveFeature($event)\" (cancel)=\"add = false\"></rolatech-feature-manage-item-add>\n } @else {\n <button mat-stroked-button (click)=\"add = true\">\n <mat-icon>add</mat-icon>\n <span i18n>Add</span>\n </button>\n }\n </div>\n</div>\n", styles: [".features-chip-list{width:100%}\n"] }]
756
+ }] });
757
+
758
+ class FeatureManageDetailComponent {
759
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: FeatureManageDetailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
760
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.0.3", type: FeatureManageDetailComponent, isStandalone: true, selector: "rolatech-feature-manage-detail", ngImport: i0, template: "<p>feature-manage-detail works!</p>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
761
+ }
762
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: FeatureManageDetailComponent, decorators: [{
763
+ type: Component,
764
+ args: [{ selector: 'rolatech-feature-manage-detail', imports: [CommonModule], template: "<p>feature-manage-detail works!</p>\n" }]
765
+ }] });
766
+
767
+ const featureManageRoutes = [
768
+ {
769
+ path: '',
770
+ component: FeatureManageIndexComponent,
771
+ },
772
+ {
773
+ path: ':id',
774
+ component: FeatureManageDetailComponent,
775
+ },
776
+ ];
777
+
778
+ class PropertyOfferItemComponent {
779
+ offer = input.required();
780
+ status = PropertyOfferStatus;
781
+ total = computed(() => {
782
+ return (this.offer().amount / 100).toFixed(2);
783
+ });
784
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyOfferItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
785
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: PropertyOfferItemComponent, isStandalone: true, selector: "rolatech-property-offer-item", inputs: { offer: { classPropertyName: "offer", publicName: "offer", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div class=\"flex flex-col hover:bg-[--rt-raised-background] cursor-pointer p-3\">\n <div>\n <div class=\"flex justify-between w-full mb-2\">\n <a class=\"flex items-center gap-3\">\n <span>Offer ID: {{ offer().id }}</span>\n </a>\n <span class=\"font-medium text-sm\">{{ status[offer().status] }}</span>\n </div>\n <div class=\"flex flex-col overflow-x-scroll scrollbar-hide\">\n <div class=\"flex flex-row py-2\">\n @if (offer().property.media) {\n <div class=\"min-w-24 w-36 object-cover aspect-video rounded-lg mr-3\">\n @defer (on viewport()) {\n <rolatech-thumbnail [src]=\"offer().property.media[0].url\" size=\"medium\" mode=\"full\"> </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=\"min-w-24 w-36 object-cover aspect-video rounded-lg mr-3\">\n <rolatech-image-placeholder></rolatech-image-placeholder>\n </div>\n }\n <div class=\"flex w-full justify-between\">\n <div class=\"flex justify-between w-full\">\n <div class=\"flex flex-col\">\n <div>{{ offer().property.title }}</div>\n <div class=\"inline-flex gap-1 mt-2\">\n <div>\n <span class=\"mr-1\">{{ offer().property.bedrooms }}</span>\n <span i18n>Bedrooms</span>\n </div>\n <div>\n <span class=\"mr-1\">{{ offer().property.bathrooms }}</span>\n <span i18n>Bathrooms</span>\n </div>\n </div>\n </div>\n <div class=\"text-right\">\n <div class=\"text-sm\">\u00A3{{ offer().property.price | fixed }}</div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: MatIconModule }, { kind: "component", type: ImagePlaceholderComponent, selector: "rolatech-image-placeholder" }, { kind: "pipe", type: FixedPipe, name: "fixed" }], deferBlockDependencies: [() => [ThumbnailComponent]] });
786
+ }
787
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyOfferItemComponent, decorators: [{
788
+ type: Component,
789
+ args: [{ selector: 'rolatech-property-offer-item', imports: [MatIconModule, ThumbnailComponent, ImagePlaceholderComponent, FixedPipe], template: "<div class=\"flex flex-col hover:bg-[--rt-raised-background] cursor-pointer p-3\">\n <div>\n <div class=\"flex justify-between w-full mb-2\">\n <a class=\"flex items-center gap-3\">\n <span>Offer ID: {{ offer().id }}</span>\n </a>\n <span class=\"font-medium text-sm\">{{ status[offer().status] }}</span>\n </div>\n <div class=\"flex flex-col overflow-x-scroll scrollbar-hide\">\n <div class=\"flex flex-row py-2\">\n @if (offer().property.media) {\n <div class=\"min-w-24 w-36 object-cover aspect-video rounded-lg mr-3\">\n @defer (on viewport()) {\n <rolatech-thumbnail [src]=\"offer().property.media[0].url\" size=\"medium\" mode=\"full\"> </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=\"min-w-24 w-36 object-cover aspect-video rounded-lg mr-3\">\n <rolatech-image-placeholder></rolatech-image-placeholder>\n </div>\n }\n <div class=\"flex w-full justify-between\">\n <div class=\"flex justify-between w-full\">\n <div class=\"flex flex-col\">\n <div>{{ offer().property.title }}</div>\n <div class=\"inline-flex gap-1 mt-2\">\n <div>\n <span class=\"mr-1\">{{ offer().property.bedrooms }}</span>\n <span i18n>Bedrooms</span>\n </div>\n <div>\n <span class=\"mr-1\">{{ offer().property.bathrooms }}</span>\n <span i18n>Bathrooms</span>\n </div>\n </div>\n </div>\n <div class=\"text-right\">\n <div class=\"text-sm\">\u00A3{{ offer().property.price | fixed }}</div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n</div>\n" }]
790
+ }] });
791
+
792
+ class PropertyManageOfferIndexComponent extends BaseComponent {
793
+ propertyService = inject(PropertyService);
794
+ select = 0;
795
+ links = [
796
+ {
797
+ name: 'All',
798
+ icon: 'dashboard',
799
+ },
800
+ {
801
+ name: 'Pending',
802
+ icon: 'category',
803
+ status: 'pending',
804
+ },
805
+ {
806
+ name: 'Accepted',
807
+ icon: 'category',
808
+ status: 'accepted',
809
+ },
810
+ {
811
+ name: 'Rejected',
812
+ icon: 'category',
813
+ status: 'rejected',
814
+ },
815
+ {
816
+ name: 'Cancelled',
817
+ icon: 'category',
818
+ status: 'cancelled',
819
+ },
820
+ ];
821
+ filterOptions = {
822
+ type: '',
823
+ status: '',
824
+ };
825
+ offers = [];
826
+ ngOnInit() {
827
+ this.route.queryParams.subscribe(({ status }) => {
828
+ this.select = this.links.findIndex((item) => item.status === status);
829
+ if (status) {
830
+ this.filterOptions.status = status.toUpperCase();
831
+ }
832
+ else {
833
+ delete this.filterOptions.status;
834
+ }
835
+ this.findOffers();
836
+ });
837
+ }
838
+ findOffers() {
839
+ const options = {
840
+ sort: 'updatedAt desc',
841
+ };
842
+ const filterString = this.convertFilterOptions(this.filterOptions);
843
+ if (filterString) {
844
+ options['filter'] = filterString;
845
+ }
846
+ this.propertyService.findOffersByAgent(options).subscribe({
847
+ next: (res) => {
848
+ this.offers = res.data;
849
+ },
850
+ });
851
+ }
852
+ convertFilterOptions(jsonObj) {
853
+ return Object.entries(jsonObj)
854
+ .filter(([key, value]) => value !== '' && value !== undefined)
855
+ .map(([key, value]) => {
856
+ return `${key}:${value}`;
857
+ })
858
+ .join(',');
859
+ }
860
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyManageOfferIndexComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
861
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: PropertyManageOfferIndexComponent, isStandalone: true, selector: "rolatech-property-manage-offer-index", usesInheritance: true, ngImport: i0, template: "<rolatech-toolbar title=\"Offers\">\n <div class=\"flex items-center gap-2\"></div>\n</rolatech-toolbar>\n<rolatech-tabs [select]=\"select\">\n @for (item of links; track item) {\n @if (item.status) {\n <rolatech-tab [label]=\"item.name\" routerLink=\"./\" [queryParams]=\"{ status: item.status }\"></rolatech-tab>\n } @else {\n <rolatech-tab [label]=\"item.name\" routerLink=\"./\"></rolatech-tab>\n }\n }\n</rolatech-tabs>\n<div>\n @if (offers) {\n @for (item of offers; track $index) {\n <rolatech-property-offer-item [routerLink]=\"['./', item.id]\" [offer]=\"item\"></rolatech-property-offer-item>\n }\n } @else {\n <rolatech-empty></rolatech-empty>\n }\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: ToolbarComponent, selector: "rolatech-toolbar", inputs: ["title", "subtitle", "back", "link", "large", "divider"] }, { kind: "component", type: TabsComponent, selector: "rolatech-tabs", inputs: ["select", "loading"], outputs: ["selectChange"] }, { kind: "component", type: TabComponent, selector: "rolatech-tab", inputs: ["label"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$4.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: PropertyOfferItemComponent, selector: "rolatech-property-offer-item", inputs: ["offer"] }, { kind: "component", type: EmptyComponent, selector: "rolatech-empty" }] });
862
+ }
863
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyManageOfferIndexComponent, decorators: [{
864
+ type: Component,
865
+ args: [{ selector: 'rolatech-property-manage-offer-index', imports: [
866
+ CommonModule,
867
+ ToolbarComponent,
868
+ TabsComponent,
869
+ TabComponent,
870
+ RouterModule,
871
+ PropertyOfferItemComponent,
872
+ EmptyComponent,
873
+ ], template: "<rolatech-toolbar title=\"Offers\">\n <div class=\"flex items-center gap-2\"></div>\n</rolatech-toolbar>\n<rolatech-tabs [select]=\"select\">\n @for (item of links; track item) {\n @if (item.status) {\n <rolatech-tab [label]=\"item.name\" routerLink=\"./\" [queryParams]=\"{ status: item.status }\"></rolatech-tab>\n } @else {\n <rolatech-tab [label]=\"item.name\" routerLink=\"./\"></rolatech-tab>\n }\n }\n</rolatech-tabs>\n<div>\n @if (offers) {\n @for (item of offers; track $index) {\n <rolatech-property-offer-item [routerLink]=\"['./', item.id]\" [offer]=\"item\"></rolatech-property-offer-item>\n }\n } @else {\n <rolatech-empty></rolatech-empty>\n }\n</div>\n" }]
874
+ }] });
875
+
876
+ class PropertyManageOfferDetailComponent extends BaseComponent {
877
+ propertyService = inject(PropertyService);
878
+ authUserService = inject(AuthUserService);
879
+ offer;
880
+ name = '';
881
+ property;
882
+ status = PropertyOfferStatus;
883
+ employmentStatus = EmploymentStatus;
884
+ agent;
885
+ ngOnInit() {
886
+ this.getOffer();
887
+ }
888
+ getOffer() {
889
+ this.propertyService.getOffer(this.id).subscribe({
890
+ next: (res) => {
891
+ this.offer = res.data;
892
+ this.name = this.offer.firstName + ', ' + this.offer.lastName;
893
+ this.getProperty(this.offer.propertyId);
894
+ },
895
+ });
896
+ }
897
+ getProperty(propertyId) {
898
+ this.propertyService.get(propertyId).subscribe({
899
+ next: (res) => {
900
+ this.property = res.data;
901
+ this.getAgentPublicInfo(this.property.agentId);
902
+ },
903
+ });
904
+ }
905
+ getAgentPublicInfo(agentId) {
906
+ this.authUserService.getPublicUserInfo(agentId).subscribe({
907
+ next: (res) => {
908
+ this.agent = res;
909
+ console.log(this.agent);
910
+ },
911
+ });
912
+ }
913
+ accept() {
914
+ const dialogRef = this.dialog.open(AcceptDialogComponent, {
915
+ width: '400px',
916
+ data: {
917
+ title: 'Accept offer',
918
+ message: 'Accept this offer?',
919
+ },
920
+ });
921
+ dialogRef.afterClosed().subscribe((result) => {
922
+ if (result) {
923
+ this.propertyService.acceptOffer(this.id).subscribe({
924
+ next: (res) => {
925
+ this.offer.status = 'ACCEPTED';
926
+ this.snackBarService.open('Accepted');
927
+ },
928
+ error: (e) => {
929
+ this.snackBarService.open(e.message);
930
+ },
931
+ });
932
+ }
933
+ });
934
+ }
935
+ reject() {
936
+ const dialogRef = this.dialog.open(RejectDialogComponent, {
937
+ width: '400px',
938
+ data: {
939
+ content: '',
940
+ },
941
+ });
942
+ dialogRef.afterClosed().subscribe((result) => {
943
+ if (result) {
944
+ this.propertyService.rejectOffer(this.id, { reason: result.content }).subscribe({
945
+ next: (res) => {
946
+ this.offer.status = 'REJECTED';
947
+ this.snackBarService.open('Rejected');
948
+ },
949
+ error: (e) => {
950
+ this.snackBarService.open(e.message);
951
+ },
952
+ });
953
+ }
954
+ });
955
+ }
956
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyManageOfferDetailComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
957
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: PropertyManageOfferDetailComponent, isStandalone: true, selector: "rolatech-property-manage-offer-detail", usesInheritance: true, ngImport: i0, template: "@if (offer) {\n <rolatech-toolbar [title]=\"status[offer.status]\" large link=\"../\">\n @if (offer.status.toString() === 'PENDING') {\n <button mat-flat-button (click)=\"accept()\" i18n>Accept</button>\n <button mat-button (click)=\"reject()\" i18n>Reject</button>\n }\n </rolatech-toolbar>\n <div class=\"px-4\">\n <div>\n <div class=\"text-lg font-bold py-2\">Viewer</div>\n <hr class=\"mb-2\" />\n <div class=\"flex flex-col md:flex-row gap-1 md:gap-4\">\n <rolatech-rich-label label=\"Name\" [title]=\"name\"></rolatech-rich-label>\n <rolatech-rich-label label=\"Email\" [title]=\"offer.email\"></rolatech-rich-label>\n <rolatech-rich-label label=\"Phone\" [title]=\"offer.phone\"></rolatech-rich-label>\n </div>\n </div>\n <div>\n <div class=\"text-lg font-bold py-2\">Qualification</div>\n <hr class=\"mb-2\" />\n <div class=\"flex flex-col md:flex-row gap-1 md:gap-4\">\n <rolatech-rich-label label=\"Move-in date\" [title]=\"offer.startDate\"></rolatech-rich-label>\n <rolatech-rich-label label=\"Tenancy duration\" [title]=\"offer.tenancyDuration\"></rolatech-rich-label>\n <rolatech-rich-label label=\"Number of tenants\" [title]=\"offer.numberOfTenants\"></rolatech-rich-label>\n </div>\n <div class=\"flex flex-col md:flex-row gap-1 md:gap-4\">\n <rolatech-rich-label label=\"Employment status\" [title]=\"employmentStatus[offer.employmentStatus]\"></rolatech-rich-label>\n <rolatech-rich-label label=\"Employer\" [title]=\"offer.startDate\"></rolatech-rich-label>\n @if (offer.income) {\n <rolatech-rich-label label=\"Annual income\" [title]=\"offer.income\"></rolatech-rich-label>\n }\n </div>\n </div>\n <div>\n <div class=\"text-lg font-bold py-2\">Property details</div>\n <hr class=\"mb-2\" />\n <div class=\"flex items-center py-2\">\n <div class=\"min-w-16 w-24 object-cover aspect-video rounded-md mr-3\">\n @defer (on viewport()) {\n <rolatech-thumbnail [src]=\"offer.item.media[0].url\" size=\"medium\" mode=\"full\"> </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 <div class=\"flex w-full justify-between\">\n <div class=\"flex justify-between w-full\">\n <div class=\"flex flex-col\">\n <div>{{ offer.item.propertyTitle }}</div>\n <div class=\"inline-flex gap-1 mt-2\">\n <div>\n <span class=\"mr-1\">{{ offer.item.bedrooms }}</span>\n <span i18n>Bedrooms</span>\n </div>\n <div>\n <span class=\"mr-1\">{{ offer.item.bathrooms }}</span>\n <span i18n>Bathrooms</span>\n </div>\n </div>\n </div>\n <div class=\"text-right\">\n <div class=\"text-sm\">\u00A3{{ offer.item.price | fixed }}</div>\n </div>\n </div>\n </div>\n <div class=\"hidden md:flex flex-col px-3\"></div>\n </div>\n </div>\n\n <div>\n <div class=\"text-lg font-bold py-2\">Viewing agent</div>\n <hr class=\"mb-2\" />\n @if (agent) {\n <div>\n <rolatech-rich-label label=\"Move-in date\" [title]=\"offer.startDate\"></rolatech-rich-label>\n <rolatech-rich-label label=\"Tenancy duration\" [title]=\"offer.tenancyDuration\"></rolatech-rich-label>\n <rolatech-rich-label label=\"Phone\" [title]=\"agent.phone\"></rolatech-rich-label>\n </div>\n }\n </div>\n </div>\n}\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: ToolbarComponent, selector: "rolatech-toolbar", inputs: ["title", "subtitle", "back", "link", "large", "divider"] }, { kind: "component", type: RichLabelComponent, selector: "rolatech-rich-label", inputs: ["label", "title"] }, { kind: "pipe", type: FixedPipe, name: "fixed" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.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"] }], deferBlockDependencies: [() => [ThumbnailComponent]] });
958
+ }
959
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyManageOfferDetailComponent, decorators: [{
960
+ type: Component,
961
+ args: [{ selector: 'rolatech-property-manage-offer-detail', imports: [CommonModule, ToolbarComponent, RichLabelComponent, ThumbnailComponent, FixedPipe, MatButtonModule], template: "@if (offer) {\n <rolatech-toolbar [title]=\"status[offer.status]\" large link=\"../\">\n @if (offer.status.toString() === 'PENDING') {\n <button mat-flat-button (click)=\"accept()\" i18n>Accept</button>\n <button mat-button (click)=\"reject()\" i18n>Reject</button>\n }\n </rolatech-toolbar>\n <div class=\"px-4\">\n <div>\n <div class=\"text-lg font-bold py-2\">Viewer</div>\n <hr class=\"mb-2\" />\n <div class=\"flex flex-col md:flex-row gap-1 md:gap-4\">\n <rolatech-rich-label label=\"Name\" [title]=\"name\"></rolatech-rich-label>\n <rolatech-rich-label label=\"Email\" [title]=\"offer.email\"></rolatech-rich-label>\n <rolatech-rich-label label=\"Phone\" [title]=\"offer.phone\"></rolatech-rich-label>\n </div>\n </div>\n <div>\n <div class=\"text-lg font-bold py-2\">Qualification</div>\n <hr class=\"mb-2\" />\n <div class=\"flex flex-col md:flex-row gap-1 md:gap-4\">\n <rolatech-rich-label label=\"Move-in date\" [title]=\"offer.startDate\"></rolatech-rich-label>\n <rolatech-rich-label label=\"Tenancy duration\" [title]=\"offer.tenancyDuration\"></rolatech-rich-label>\n <rolatech-rich-label label=\"Number of tenants\" [title]=\"offer.numberOfTenants\"></rolatech-rich-label>\n </div>\n <div class=\"flex flex-col md:flex-row gap-1 md:gap-4\">\n <rolatech-rich-label label=\"Employment status\" [title]=\"employmentStatus[offer.employmentStatus]\"></rolatech-rich-label>\n <rolatech-rich-label label=\"Employer\" [title]=\"offer.startDate\"></rolatech-rich-label>\n @if (offer.income) {\n <rolatech-rich-label label=\"Annual income\" [title]=\"offer.income\"></rolatech-rich-label>\n }\n </div>\n </div>\n <div>\n <div class=\"text-lg font-bold py-2\">Property details</div>\n <hr class=\"mb-2\" />\n <div class=\"flex items-center py-2\">\n <div class=\"min-w-16 w-24 object-cover aspect-video rounded-md mr-3\">\n @defer (on viewport()) {\n <rolatech-thumbnail [src]=\"offer.item.media[0].url\" size=\"medium\" mode=\"full\"> </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 <div class=\"flex w-full justify-between\">\n <div class=\"flex justify-between w-full\">\n <div class=\"flex flex-col\">\n <div>{{ offer.item.propertyTitle }}</div>\n <div class=\"inline-flex gap-1 mt-2\">\n <div>\n <span class=\"mr-1\">{{ offer.item.bedrooms }}</span>\n <span i18n>Bedrooms</span>\n </div>\n <div>\n <span class=\"mr-1\">{{ offer.item.bathrooms }}</span>\n <span i18n>Bathrooms</span>\n </div>\n </div>\n </div>\n <div class=\"text-right\">\n <div class=\"text-sm\">\u00A3{{ offer.item.price | fixed }}</div>\n </div>\n </div>\n </div>\n <div class=\"hidden md:flex flex-col px-3\"></div>\n </div>\n </div>\n\n <div>\n <div class=\"text-lg font-bold py-2\">Viewing agent</div>\n <hr class=\"mb-2\" />\n @if (agent) {\n <div>\n <rolatech-rich-label label=\"Move-in date\" [title]=\"offer.startDate\"></rolatech-rich-label>\n <rolatech-rich-label label=\"Tenancy duration\" [title]=\"offer.tenancyDuration\"></rolatech-rich-label>\n <rolatech-rich-label label=\"Phone\" [title]=\"agent.phone\"></rolatech-rich-label>\n </div>\n }\n </div>\n </div>\n}\n" }]
962
+ }] });
963
+
964
+ const propertyManageOffersRoutes = [
965
+ {
966
+ path: '',
967
+ component: PropertyManageOfferIndexComponent,
968
+ },
969
+ {
970
+ path: ':id',
971
+ component: PropertyManageOfferDetailComponent,
972
+ },
973
+ ];
974
+
975
+ class PropertyFilterComponent {
976
+ towns = ['London'];
977
+ filter = model(false);
978
+ minDate = new Date();
979
+ propertyFilterIconComponent = defer(() => import('./rolatech-angular-property-property-filter-icon.component-DbzGJdxV.mjs').then((m) => m.PropertyFilterIconComponent));
980
+ searchOptions = {
981
+ town: '',
982
+ startDate: '',
983
+ endDate: '',
984
+ };
985
+ reset() { }
986
+ search() { }
987
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyFilterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
988
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: PropertyFilterComponent, isStandalone: true, selector: "rolatech-property-filter", inputs: { filter: { classPropertyName: "filter", publicName: "filter", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { filter: "filterChange" }, ngImport: i0, template: "<!-- <div class=\"p-3\">\n <div class=\"bg-white p-3 flex items-center justify-between rounded-md\">\n <div class=\"flex items-center\">\n <mat-form-field subscriptSizing=\"dynamic\">\n <mat-select #select=\"matSelect\" placeholder=\"Town\" i18n-placeholder [(ngModel)]=\"searchOptions.town\">\n @for (Town of towns; track Town) {\n <mat-option [value]=\"Town\">\n {{ Town }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n <div class=\"flex gap-3\">\n <a mat-stroked-button class=\"w-[128px]\" (click)=\"reset()\" i18n>Reset</a>\n <ng-container *ngComponentOutlet=\"propertyFilterIconComponent | async\"></ng-container>\n <a mat-flat-button class=\"w-full\" (click)=\"search()\" i18n>Search</a>\n </div>\n </div>\n</div> -->\n<div\n [ngClass]=\"filter() ? 'transition-height duration-500 ease-in-out' : 'md:h-14 h-0 transition-height duration-500 ease-in-out'\"\n>\n <div\n class=\"min-w-[256px] md:min-w-[320px] h-full m-3 flex flex-row md:flex-col md:h-full items-center md:items-start shadow-inner shadow-light-400 md:shadow-none overflow-x-scroll overflow-y-hidden scrollbar-hide whitespace-pre\"\n >\n <div class=\"w-full h-full\">\n <div class=\"flex flex-col md:flex-row h-in justify-center items-center gap-3\">\n <mat-form-field appearance=\"fill\" subscriptSizing=\"dynamic\">\n <mat-select #select=\"matSelect\" placeholder=\"Town\" [(ngModel)]=\"searchOptions.city\">\n @for (town of towns; track town) {\n <mat-option [value]=\"town\">\n {{ town }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"fill\" subscriptSizing=\"dynamic\">\n <input\n matInput\n placeholder=\"Available\"\n [matDatepicker]=\"startDatePicker\"\n [min]=\"minDate\"\n (focus)=\"startDatePicker.open()\"\n name=\"available\"\n [(ngModel)]=\"searchOptions.available\"\n (dateInput)=\"searchOptions.available = $event.value.format('YYYY-MM-DD')\"\n readonly\n />\n <mat-datepicker-toggle matIconPrefix [for]=\"startDatePicker\"></mat-datepicker-toggle>\n <mat-datepicker #startDatePicker></mat-datepicker>\n </mat-form-field>\n <div class=\"flex w-full gap-3\">\n <a mat-stroked-button class=\"w-[128px]\" (click)=\"reset()\">Reset</a>\n <ng-container *ngComponentOutlet=\"propertyFilterIconComponent | async\"></ng-container>\n <a mat-flat-button class=\"w-full\" (click)=\"search()\">Search</a>\n </div>\n </div>\n </div>\n </div>\n</div>\n", styles: ["mat-form-field{width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.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: MatFormFieldModule }, { kind: "component", type: i2$2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2$2.MatPrefix, selector: "[matPrefix], [matIconPrefix], [matTextPrefix]", inputs: ["matTextPrefix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i4$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatOptionModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.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$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i8.MatDatepicker, selector: "mat-datepicker", exportAs: ["matDatepicker"] }, { kind: "directive", type: i8.MatDatepickerInput, selector: "input[matDatepicker]", inputs: ["matDatepicker", "min", "max", "matDatepickerFilter"], exportAs: ["matDatepickerInput"] }, { kind: "component", type: i8.MatDatepickerToggle, selector: "mat-datepicker-toggle", inputs: ["for", "tabIndex", "aria-label", "disabled", "disableRipple"], exportAs: ["matDatepickerToggle"] }], animations: [
989
+ trigger('filter', [
990
+ state('collapsed', style({ height: '0px', minHeight: '0' })),
991
+ state('expanded', style({ height: '*' })),
992
+ transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')),
993
+ ]),
994
+ ] });
995
+ }
996
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyFilterComponent, decorators: [{
997
+ type: Component,
998
+ args: [{ selector: 'rolatech-property-filter', imports: [
999
+ CommonModule,
1000
+ MatButtonModule,
1001
+ MatFormFieldModule,
1002
+ MatInputModule,
1003
+ MatSelectModule,
1004
+ MatOptionModule,
1005
+ FormsModule,
1006
+ MatDatepickerModule,
1007
+ ], animations: [
1008
+ trigger('filter', [
1009
+ state('collapsed', style({ height: '0px', minHeight: '0' })),
1010
+ state('expanded', style({ height: '*' })),
1011
+ transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')),
1012
+ ]),
1013
+ ], template: "<!-- <div class=\"p-3\">\n <div class=\"bg-white p-3 flex items-center justify-between rounded-md\">\n <div class=\"flex items-center\">\n <mat-form-field subscriptSizing=\"dynamic\">\n <mat-select #select=\"matSelect\" placeholder=\"Town\" i18n-placeholder [(ngModel)]=\"searchOptions.town\">\n @for (Town of towns; track Town) {\n <mat-option [value]=\"Town\">\n {{ Town }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n <div class=\"flex gap-3\">\n <a mat-stroked-button class=\"w-[128px]\" (click)=\"reset()\" i18n>Reset</a>\n <ng-container *ngComponentOutlet=\"propertyFilterIconComponent | async\"></ng-container>\n <a mat-flat-button class=\"w-full\" (click)=\"search()\" i18n>Search</a>\n </div>\n </div>\n</div> -->\n<div\n [ngClass]=\"filter() ? 'transition-height duration-500 ease-in-out' : 'md:h-14 h-0 transition-height duration-500 ease-in-out'\"\n>\n <div\n class=\"min-w-[256px] md:min-w-[320px] h-full m-3 flex flex-row md:flex-col md:h-full items-center md:items-start shadow-inner shadow-light-400 md:shadow-none overflow-x-scroll overflow-y-hidden scrollbar-hide whitespace-pre\"\n >\n <div class=\"w-full h-full\">\n <div class=\"flex flex-col md:flex-row h-in justify-center items-center gap-3\">\n <mat-form-field appearance=\"fill\" subscriptSizing=\"dynamic\">\n <mat-select #select=\"matSelect\" placeholder=\"Town\" [(ngModel)]=\"searchOptions.city\">\n @for (town of towns; track town) {\n <mat-option [value]=\"town\">\n {{ town }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"fill\" subscriptSizing=\"dynamic\">\n <input\n matInput\n placeholder=\"Available\"\n [matDatepicker]=\"startDatePicker\"\n [min]=\"minDate\"\n (focus)=\"startDatePicker.open()\"\n name=\"available\"\n [(ngModel)]=\"searchOptions.available\"\n (dateInput)=\"searchOptions.available = $event.value.format('YYYY-MM-DD')\"\n readonly\n />\n <mat-datepicker-toggle matIconPrefix [for]=\"startDatePicker\"></mat-datepicker-toggle>\n <mat-datepicker #startDatePicker></mat-datepicker>\n </mat-form-field>\n <div class=\"flex w-full gap-3\">\n <a mat-stroked-button class=\"w-[128px]\" (click)=\"reset()\">Reset</a>\n <ng-container *ngComponentOutlet=\"propertyFilterIconComponent | async\"></ng-container>\n <a mat-flat-button class=\"w-full\" (click)=\"search()\">Search</a>\n </div>\n </div>\n </div>\n </div>\n</div>\n", styles: ["mat-form-field{width:100%}\n"] }]
1014
+ }] });
1015
+
1016
+ class PropertyLayoutComponent extends BaseComponent {
1017
+ propertyService = inject(PropertyService);
1018
+ selectIndex = 0;
1019
+ filter = false;
1020
+ ngOnInit() {
1021
+ this.findCategories();
1022
+ }
1023
+ findCategories() {
1024
+ // this.categoryService.find({}, false).subscribe({
1025
+ // next: (res: any) => {
1026
+ // this.categories = res.data;
1027
+ // },
1028
+ // });
1029
+ }
1030
+ loadPropertyByCategoryIndex(index) {
1031
+ // this.selectIndex = index;
1032
+ // const id = this.categories[index].id;
1033
+ // if (index === 0) {
1034
+ // this.router.navigate([`./`]);
1035
+ // // this.propertyService.find({}).subscribe({
1036
+ // // next: res => {
1037
+ // // }
1038
+ // // })
1039
+ // } else {
1040
+ // this.router.navigate([`../categories/${id}`], {
1041
+ // relativeTo: this.route,
1042
+ // });
1043
+ // }
1044
+ }
1045
+ nextCategory() { }
1046
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyLayoutComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
1047
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.0.3", type: PropertyLayoutComponent, isStandalone: true, selector: "rolatech-property-layout", usesInheritance: true, ngImport: i0, template: "<rolatech-container>\n <div class=\"flex flex-col min-w-[320px] max-w-[1280px] m-auto\">\n <rolatech-property-filter [(filter)]=\"filter\"></rolatech-property-filter>\n <div class=\"flex justify-between items-center\">\n <div class=\"p-3 text-2xl font-medium\" i18n>Properties</div>\n <button mat-button class=\"md:invisible\" (click)=\"filter = !filter\">\n <mat-icon>tune</mat-icon>\n <span>Filter</span>\n </button>\n </div>\n <div class=\"w-full\">\n <router-outlet></router-outlet>\n </div>\n </div>\n</rolatech-container>\n", styles: [".property-layout-active{border-radius:var(--rt-rounded-base, 8px);background-color:var(-rt-base-background, #fff);color:var(--rt-brand-color, #000)}@media (max-width: 768px){.property-layout-active{background-color:var(--rt-text-primary, #000);color:var(--rt-text-primary-inverse, #000)}}.scrollbar-hide::-webkit-scrollbar{display:none}.scrollbar-hide{-ms-overflow-style:none;scrollbar-width:none}\n"], dependencies: [{ kind: "ngmodule", type: AngularCommonModule }, { kind: "directive", type: i1$4.RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "ngmodule", type: AngularComponentsModule }, { kind: "component", type: i1$1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: ContainerComponent, selector: "rolatech-container" }, { kind: "component", type: PropertyFilterComponent, selector: "rolatech-property-filter", inputs: ["filter"], outputs: ["filterChange"] }] });
1048
+ }
1049
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyLayoutComponent, decorators: [{
1050
+ type: Component,
1051
+ args: [{ selector: 'rolatech-property-layout', imports: [AngularCommonModule, AngularComponentsModule, ContainerComponent, PropertyFilterComponent], template: "<rolatech-container>\n <div class=\"flex flex-col min-w-[320px] max-w-[1280px] m-auto\">\n <rolatech-property-filter [(filter)]=\"filter\"></rolatech-property-filter>\n <div class=\"flex justify-between items-center\">\n <div class=\"p-3 text-2xl font-medium\" i18n>Properties</div>\n <button mat-button class=\"md:invisible\" (click)=\"filter = !filter\">\n <mat-icon>tune</mat-icon>\n <span>Filter</span>\n </button>\n </div>\n <div class=\"w-full\">\n <router-outlet></router-outlet>\n </div>\n </div>\n</rolatech-container>\n", styles: [".property-layout-active{border-radius:var(--rt-rounded-base, 8px);background-color:var(-rt-base-background, #fff);color:var(--rt-brand-color, #000)}@media (max-width: 768px){.property-layout-active{background-color:var(--rt-text-primary, #000);color:var(--rt-text-primary-inverse, #000)}}.scrollbar-hide::-webkit-scrollbar{display:none}.scrollbar-hide{-ms-overflow-style:none;scrollbar-width:none}\n"] }]
1052
+ }] });
1053
+
1054
+ class PropertyActionsComponent {
1055
+ property = input.required();
1056
+ requestViewing = output();
1057
+ offer = output();
1058
+ deposit = output();
1059
+ displayDeposit = computed(() => {
1060
+ return (this.property().deposit / 100).toFixed(2);
1061
+ });
1062
+ onRequestViewing(property) {
1063
+ this.requestViewing.emit(property);
1064
+ }
1065
+ onOffer(property) {
1066
+ this.offer.emit(property);
1067
+ }
1068
+ onDeposit(property) {
1069
+ this.deposit.emit(property);
1070
+ }
1071
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyActionsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1072
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: PropertyActionsComponent, isStandalone: true, selector: "rolatech-property-actions", inputs: { property: { classPropertyName: "property", publicName: "property", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { requestViewing: "requestViewing", offer: "offer", deposit: "deposit" }, ngImport: i0, template: "<div class=\"flex flex-col gap-3\">\n <a mat-stroked-button class=\"\" (click)=\"onRequestViewing(property())\" i18n>Request viewing</a>\n @if (property().priceType && property().priceType.toString() === 'PARTIAL') {\n <a mat-flat-button class=\"\" (click)=\"onDeposit(property())\" i18n>Pay deposit \u00A3{{ displayDeposit() }}</a>\n } @else {\n <a mat-flat-button class=\"\" (click)=\"onOffer(property())\" i18n>Make an offer</a>\n }\n <ng-content select=\"rolatech-property-action-contact\"></ng-content>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "ngmodule", type: MatMenuModule }], encapsulation: i0.ViewEncapsulation.None });
1073
+ }
1074
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyActionsComponent, decorators: [{
1075
+ type: Component,
1076
+ args: [{ selector: 'rolatech-property-actions', imports: [CommonModule, MatButtonModule, MatIconModule, MatMenuModule], encapsulation: ViewEncapsulation.None, template: "<div class=\"flex flex-col gap-3\">\n <a mat-stroked-button class=\"\" (click)=\"onRequestViewing(property())\" i18n>Request viewing</a>\n @if (property().priceType && property().priceType.toString() === 'PARTIAL') {\n <a mat-flat-button class=\"\" (click)=\"onDeposit(property())\" i18n>Pay deposit \u00A3{{ displayDeposit() }}</a>\n } @else {\n <a mat-flat-button class=\"\" (click)=\"onOffer(property())\" i18n>Make an offer</a>\n }\n <ng-content select=\"rolatech-property-action-contact\"></ng-content>\n</div>\n" }]
1077
+ }] });
1078
+
1079
+ class PropertyItemComponent {
1080
+ property = input.required();
1081
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1082
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: PropertyItemComponent, isStandalone: true, selector: "rolatech-property-item", inputs: { property: { classPropertyName: "property", publicName: "property", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div class=\"flex flex-col hover:bg-[--rt-raised-background] cursor-pointer p-3\">\n <div>\n <div class=\"flex justify-between w-full mb-2\">\n <a class=\"flex items-center gap-3\">\n <span>ID: {{ property().id }}</span>\n </a>\n </div>\n <div class=\"flex flex-col overflow-x-scroll scrollbar-hide\">\n <div class=\"flex flex-row py-2\">\n @if (property().media) {\n <div class=\"min-w-24 w-36 object-cover aspect-video rounded-lg mr-3\">\n @defer (on viewport()) {\n <rolatech-thumbnail [src]=\"property().media[0].url\" size=\"medium\" mode=\"full\"> </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=\"min-w-24 w-36 object-cover aspect-video rounded-lg mr-3\">\n <rolatech-image-placeholder></rolatech-image-placeholder>\n </div>\n }\n <div class=\"flex w-full justify-between\">\n <div class=\"flex justify-between w-full\">\n <div class=\"flex flex-col\">\n <div>{{ property().title }}</div>\n <div class=\"inline-flex gap-1 mt-2\">\n <div>\n <span class=\"mr-1\">{{ property().bedrooms }}</span>\n <span i18n>Bedrooms</span>\n </div>\n <div>\n <span class=\"mr-1\">{{ property().bathrooms }}</span>\n <span i18n>Bathrooms</span>\n </div>\n </div>\n </div>\n <div class=\"text-right\">\n <div class=\"text-sm\">\u00A3{{ property().price | fixed }}</div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: ImagePlaceholderComponent, selector: "rolatech-image-placeholder" }, { kind: "pipe", type: FixedPipe, name: "fixed" }], deferBlockDependencies: [() => [ThumbnailComponent]] });
1083
+ }
1084
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyItemComponent, decorators: [{
1085
+ type: Component,
1086
+ args: [{ selector: 'rolatech-property-item', imports: [CommonModule, ThumbnailComponent, ImagePlaceholderComponent, FixedPipe], template: "<div class=\"flex flex-col hover:bg-[--rt-raised-background] cursor-pointer p-3\">\n <div>\n <div class=\"flex justify-between w-full mb-2\">\n <a class=\"flex items-center gap-3\">\n <span>ID: {{ property().id }}</span>\n </a>\n </div>\n <div class=\"flex flex-col overflow-x-scroll scrollbar-hide\">\n <div class=\"flex flex-row py-2\">\n @if (property().media) {\n <div class=\"min-w-24 w-36 object-cover aspect-video rounded-lg mr-3\">\n @defer (on viewport()) {\n <rolatech-thumbnail [src]=\"property().media[0].url\" size=\"medium\" mode=\"full\"> </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=\"min-w-24 w-36 object-cover aspect-video rounded-lg mr-3\">\n <rolatech-image-placeholder></rolatech-image-placeholder>\n </div>\n }\n <div class=\"flex w-full justify-between\">\n <div class=\"flex justify-between w-full\">\n <div class=\"flex flex-col\">\n <div>{{ property().title }}</div>\n <div class=\"inline-flex gap-1 mt-2\">\n <div>\n <span class=\"mr-1\">{{ property().bedrooms }}</span>\n <span i18n>Bedrooms</span>\n </div>\n <div>\n <span class=\"mr-1\">{{ property().bathrooms }}</span>\n <span i18n>Bathrooms</span>\n </div>\n </div>\n </div>\n <div class=\"text-right\">\n <div class=\"text-sm\">\u00A3{{ property().price | fixed }}</div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n</div>\n" }]
1087
+ }] });
1088
+
1089
+ class PropertyPricingComponent {
1090
+ ngOnInit() {
1091
+ this.calculateDeposit();
1092
+ }
1093
+ property = input.required();
1094
+ // price = computed(() => {
1095
+ // return (this.property().price / 100).toFixed(2);
1096
+ // });
1097
+ price = input();
1098
+ holdingDeposit;
1099
+ securityDeposit;
1100
+ displayPrice = computed(() => {
1101
+ return (this.property().price / 100).toFixed(2);
1102
+ });
1103
+ calculateDeposit() {
1104
+ const price = this.property().price;
1105
+ this.holdingDeposit = ((price * 12) / 52 / 100).toFixed(2);
1106
+ this.securityDeposit = (((price * 12) / 52 / 100) * 5).toFixed(2);
1107
+ }
1108
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyPricingComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1109
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.0.3", type: PropertyPricingComponent, isStandalone: true, selector: "rolatech-property-pricing", inputs: { property: { classPropertyName: "property", publicName: "property", isSignal: true, isRequired: true, transformFunction: null }, price: { classPropertyName: "price", publicName: "price", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"py-3\">\n <div class=\"flex justify-between items-center\">\n <div class=\"text-lg font-bold\" i18n>Rent per month</div>\n <div class=\"text-xl font-bold py-3\">\u00A3{{ displayPrice() }}</div>\n </div>\n <mat-divider></mat-divider>\n <div class=\"py-3\">\n <div class=\"flex justify-between items-center py-2\">\n <span i18n>Holding Deposit</span>\n <span>\u00A3{{ holdingDeposit }}</span>\n </div>\n <div class=\"flex justify-between items-center py-2 font-bold\">\n <span i18n>Security Deposit</span>\n <span>\u00A3{{ securityDeposit }}</span>\n </div>\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }], encapsulation: i0.ViewEncapsulation.None });
1110
+ }
1111
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyPricingComponent, decorators: [{
1112
+ type: Component,
1113
+ args: [{ selector: 'rolatech-property-pricing', imports: [CommonModule, MatButtonModule, MatDivider], encapsulation: ViewEncapsulation.None, template: "<div class=\"py-3\">\n <div class=\"flex justify-between items-center\">\n <div class=\"text-lg font-bold\" i18n>Rent per month</div>\n <div class=\"text-xl font-bold py-3\">\u00A3{{ displayPrice() }}</div>\n </div>\n <mat-divider></mat-divider>\n <div class=\"py-3\">\n <div class=\"flex justify-between items-center py-2\">\n <span i18n>Holding Deposit</span>\n <span>\u00A3{{ holdingDeposit }}</span>\n </div>\n <div class=\"flex justify-between items-center py-2 font-bold\">\n <span i18n>Security Deposit</span>\n <span>\u00A3{{ securityDeposit }}</span>\n </div>\n </div>\n</div>\n" }]
1114
+ }] });
1115
+
1116
+ class PropertyInfoComponent {
1117
+ property = input.required();
1118
+ wish = output();
1119
+ inWishList = input(false);
1120
+ onWish(property) {
1121
+ this.wish.emit(property);
1122
+ }
1123
+ onShare() { }
1124
+ onMore() { }
1125
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyInfoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1126
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: PropertyInfoComponent, isStandalone: true, selector: "rolatech-property-info", inputs: { property: { classPropertyName: "property", publicName: "property", isSignal: true, isRequired: true, transformFunction: null }, inWishList: { classPropertyName: "inWishList", publicName: "inWishList", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { wish: "wish" }, host: { attributes: { "id": "rolatech-property-info" } }, ngImport: i0, template: "<div>\n <div class=\"flex justify-between items-center\">\n <div class=\"text-2xl font-medium\">{{ property().title }}</div>\n <div class=\"flex items-center px-3\">\n <button mat-icon-button (click)=\"onWish(property())\">\n @if (inWishList()) {\n <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 -960 960 960\" width=\"24px\" fill=\"#5f6368\">\n <path\n d=\"m293-203.08 49.62-212.54-164.93-142.84 217.23-18.85L480-777.69l85.08 200.38 217.23 18.85-164.93 142.84L667-203.08 480-315.92 293-203.08Z\"\n />\n </svg>\n } @else {\n <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 -960 960 960\" width=\"24px\" fill=\"#5f6368\">\n <path\n d=\"m354-287 126-76 126 77-33-144 111-96-146-13-58-136-58 135-146 13 111 97-33 143Zm-61 83.92 49.62-212.54-164.93-142.84 217.23-18.85L480-777.69l85.08 200.38 217.23 18.85-164.93 142.84L667-203.08 480-315.92 293-203.08ZM480-470Z\"\n />\n </svg>\n }\n </button>\n <button mat-icon-button (click)=\"onShare()\">\n <mat-icon>share</mat-icon>\n </button>\n <button mat-icon-button (click)=\"onMore()\">\n <mat-icon>more_vert</mat-icon>\n </button>\n </div>\n </div>\n <div>\n {{ property().description }}\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.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: RouterModule }], encapsulation: i0.ViewEncapsulation.None });
1127
+ }
1128
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyInfoComponent, decorators: [{
1129
+ type: Component,
1130
+ args: [{ selector: 'rolatech-property-info', imports: [MatButtonModule, MatIconModule, RouterModule], encapsulation: ViewEncapsulation.None, host: {
1131
+ id: 'rolatech-property-info',
1132
+ }, template: "<div>\n <div class=\"flex justify-between items-center\">\n <div class=\"text-2xl font-medium\">{{ property().title }}</div>\n <div class=\"flex items-center px-3\">\n <button mat-icon-button (click)=\"onWish(property())\">\n @if (inWishList()) {\n <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 -960 960 960\" width=\"24px\" fill=\"#5f6368\">\n <path\n d=\"m293-203.08 49.62-212.54-164.93-142.84 217.23-18.85L480-777.69l85.08 200.38 217.23 18.85-164.93 142.84L667-203.08 480-315.92 293-203.08Z\"\n />\n </svg>\n } @else {\n <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 -960 960 960\" width=\"24px\" fill=\"#5f6368\">\n <path\n d=\"m354-287 126-76 126 77-33-144 111-96-146-13-58-136-58 135-146 13 111 97-33 143Zm-61 83.92 49.62-212.54-164.93-142.84 217.23-18.85L480-777.69l85.08 200.38 217.23 18.85-164.93 142.84L667-203.08 480-315.92 293-203.08ZM480-470Z\"\n />\n </svg>\n }\n </button>\n <button mat-icon-button (click)=\"onShare()\">\n <mat-icon>share</mat-icon>\n </button>\n <button mat-icon-button (click)=\"onMore()\">\n <mat-icon>more_vert</mat-icon>\n </button>\n </div>\n </div>\n <div>\n {{ property().description }}\n </div>\n</div>\n" }]
1133
+ }] });
1134
+
1135
+ class PropertySectionComponent {
1136
+ section = input.required();
1137
+ user = input();
1138
+ username = input();
1139
+ viewportScroller = inject(ViewportScroller);
1140
+ onClickScroller(id) {
1141
+ this.viewportScroller.scrollToAnchor(id);
1142
+ }
1143
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertySectionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1144
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: PropertySectionComponent, isStandalone: true, selector: "rolatech-property-section", inputs: { section: { classPropertyName: "section", publicName: "section", isSignal: true, isRequired: true, transformFunction: null }, user: { classPropertyName: "user", publicName: "user", isSignal: true, isRequired: false, transformFunction: null }, username: { classPropertyName: "username", publicName: "username", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (section(); as section) {\n <div>\n @if (section.title) {\n <div class=\"py-3 flex items-center gap-3\">\n <span class=\"h-4 w-1 bg-[--rt-brand-color] inline-block\"></span>\n <span class=\"text-lg font-medium py-1\"> {{ section.title }}</span>\n </div>\n }\n @if (section.description) {\n <div>\n {{ section.description }}\n </div>\n }\n @if (section.media) {\n <div class=\"w-80%\">\n @for (item of section.media; track item) {\n <div class=\"py-3\">\n <rolatech-thumbnail\n [src]=\"item.url\"\n size=\"small\"\n mode=\"full\"\n [width]=\"item.width\"\n [height]=\"item.height\"\n ></rolatech-thumbnail>\n </div>\n }\n </div>\n }\n </div>\n}\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: ThumbnailComponent, selector: "rolatech-thumbnail", inputs: ["src", "size", "mode", "ratio", "width", "height"] }], encapsulation: i0.ViewEncapsulation.None });
1145
+ }
1146
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertySectionComponent, decorators: [{
1147
+ type: Component,
1148
+ args: [{ selector: 'rolatech-property-section', imports: [CommonModule, ThumbnailComponent], encapsulation: ViewEncapsulation.None, template: "@if (section(); as section) {\n <div>\n @if (section.title) {\n <div class=\"py-3 flex items-center gap-3\">\n <span class=\"h-4 w-1 bg-[--rt-brand-color] inline-block\"></span>\n <span class=\"text-lg font-medium py-1\"> {{ section.title }}</span>\n </div>\n }\n @if (section.description) {\n <div>\n {{ section.description }}\n </div>\n }\n @if (section.media) {\n <div class=\"w-80%\">\n @for (item of section.media; track item) {\n <div class=\"py-3\">\n <rolatech-thumbnail\n [src]=\"item.url\"\n size=\"small\"\n mode=\"full\"\n [width]=\"item.width\"\n [height]=\"item.height\"\n ></rolatech-thumbnail>\n </div>\n }\n </div>\n }\n </div>\n}\n" }]
1149
+ }] });
1150
+
1151
+ class PropertyOwnerRendererComponent {
1152
+ name = input();
1153
+ avatar = input();
1154
+ username = input();
1155
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyOwnerRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1156
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: PropertyOwnerRendererComponent, isStandalone: true, selector: "rolatech-property-owner-renderer", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, avatar: { classPropertyName: "avatar", publicName: "avatar", isSignal: true, isRequired: false, transformFunction: null }, username: { classPropertyName: "username", publicName: "username", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"flex flex-row items-center py-3\">\n <div class=\"flex mr-3 gap-2 items-center\">\n @if (avatar()) {\n <div class=\"cursor-pointer\" [routerLink]=\"['/', '@' + username()]\">\n <img [src]=\"avatar()\" class=\"w-11 h-11 rounded-full\" alt />\n </div>\n } @else {\n <div class=\"w-11 h-11 rounded-full bg-[--rt-brand-color]\"></div>\n }\n <div class=\"flex items-center text-lg font-bold cursor-pointer\">\n <a [routerLink]=\"['/', '@' + username()]\">\n <span>{{ name() }}</span>\n </a>\n <mat-icon class=\"verified-icon\">verified</mat-icon>\n </div>\n </div>\n</div>\n", styles: ["mat-icon{transform:scale(.8);color:var(--rt-brand-color)}\n"], dependencies: [{ kind: "ngmodule", type: AngularCommonModule }, { kind: "directive", type: i1$4.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"] }] });
1157
+ }
1158
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyOwnerRendererComponent, decorators: [{
1159
+ type: Component,
1160
+ args: [{ selector: 'rolatech-property-owner-renderer', imports: [AngularCommonModule, MatIconModule], template: "<div class=\"flex flex-row items-center py-3\">\n <div class=\"flex mr-3 gap-2 items-center\">\n @if (avatar()) {\n <div class=\"cursor-pointer\" [routerLink]=\"['/', '@' + username()]\">\n <img [src]=\"avatar()\" class=\"w-11 h-11 rounded-full\" alt />\n </div>\n } @else {\n <div class=\"w-11 h-11 rounded-full bg-[--rt-brand-color]\"></div>\n }\n <div class=\"flex items-center text-lg font-bold cursor-pointer\">\n <a [routerLink]=\"['/', '@' + username()]\">\n <span>{{ name() }}</span>\n </a>\n <mat-icon class=\"verified-icon\">verified</mat-icon>\n </div>\n </div>\n</div>\n", styles: ["mat-icon{transform:scale(.8);color:var(--rt-brand-color)}\n"] }]
1161
+ }] });
1162
+
1163
+ class PropertyVideoTourComponent {
1164
+ sanitizer = inject(DomSanitizer);
1165
+ videoTour = input.required();
1166
+ videoId = computed(() => {
1167
+ return this.sanitizer.bypassSecurityTrustResourceUrl(`https://www.youtube.com/embed/${this.videoTour().videoId}`);
1168
+ });
1169
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyVideoTourComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1170
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.0.3", type: PropertyVideoTourComponent, isStandalone: true, selector: "rolatech-property-video-tour", inputs: { videoTour: { classPropertyName: "videoTour", publicName: "videoTour", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div class=\"\">\n <div class=\"text-2xl font-bold py-4\" i18n>Video tour</div>\n <iframe [src]=\"videoId()\" frameborder=\"0\" allowfullscreen class=\"w-full aspect-video\"></iframe>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
1171
+ }
1172
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyVideoTourComponent, decorators: [{
1173
+ type: Component,
1174
+ args: [{ selector: 'rolatech-property-video-tour', imports: [CommonModule], template: "<div class=\"\">\n <div class=\"text-2xl font-bold py-4\" i18n>Video tour</div>\n <iframe [src]=\"videoId()\" frameborder=\"0\" allowfullscreen class=\"w-full aspect-video\"></iframe>\n</div>\n" }]
1175
+ }] });
1176
+
1177
+ class PropertyActionContactComponent {
1178
+ btnRef = viewChild('btnEl', { read: ElementRef });
1179
+ email = input();
1180
+ phone = input();
1181
+ callAgent = output();
1182
+ emailAgent = output();
1183
+ onCallAgent() { }
1184
+ onEmailAgent(property) {
1185
+ this.emailAgent.emit(property);
1186
+ }
1187
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyActionContactComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1188
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.0.3", type: PropertyActionContactComponent, isStandalone: true, selector: "rolatech-property-action-contact", inputs: { email: { classPropertyName: "email", publicName: "email", isSignal: true, isRequired: false, transformFunction: null }, phone: { classPropertyName: "phone", publicName: "phone", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { callAgent: "callAgent", emailAgent: "emailAgent" }, viewQueries: [{ propertyName: "btnRef", first: true, predicate: ["btnEl"], descendants: true, read: ElementRef, isSignal: true }], ngImport: i0, template: "<a\n mat-stroked-button\n class=\"w-full\"\n #btnEl\n [matMenuTriggerFor]=\"callMenu\"\n [matMenuTriggerData]=\"{ width: btnRef()?.nativeElement.offsetWidth }\"\n (click)=\"onCallAgent()\"\n>\n <span i18n>Ask a question</span>\n</a>\n<mat-menu #callMenu=\"matMenu\" [overlapTrigger]=\"false\" class=\"custom-menu-panel\">\n <ng-template matMenuContent let-width=\"width\">\n <div class=\"menu-content\" [ngStyle]=\"{ width: width + 'px' }\">\n <a mat-menu-item href=\"mailto:hello@gmail.com\">\n <span class=\"flex items-center\">\n <span class=\"flex items-center\">\n <mat-icon class=\"mr-3\">email</mat-icon>\n </span>\n <span i18n>Email</span>\n </span>\n <span class=\"pl-9\" i18n>{{ email() }}</span>\n </a>\n <hr />\n <a mat-menu-item [href]=\"'https://wa.me/' + phone()\" target=\"_blank\">\n <span class=\"flex items-center\">\n <span class=\"flex items-center\">\n <mat-icon class=\"mr-3\">call</mat-icon>\n </span>\n <span i18n>WhatsApp</span>\n </span>\n <span class=\"pl-9\" i18n>{{ phone() }}</span>\n </a>\n </div>\n </ng-template>\n</mat-menu>\n", styles: [".mat-mdc-menu-panel,.custom-menu-panel{max-width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.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: "ngmodule", type: MatMenuModule }, { kind: "component", type: i4$2.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: i4$2.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i4$2.MatMenuContent, selector: "ng-template[matMenuContent]" }, { kind: "directive", type: i4$2.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }], encapsulation: i0.ViewEncapsulation.None });
1189
+ }
1190
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyActionContactComponent, decorators: [{
1191
+ type: Component,
1192
+ args: [{ selector: 'rolatech-property-action-contact', imports: [CommonModule, MatButtonModule, MatIconModule, MatMenuModule], encapsulation: ViewEncapsulation.None, template: "<a\n mat-stroked-button\n class=\"w-full\"\n #btnEl\n [matMenuTriggerFor]=\"callMenu\"\n [matMenuTriggerData]=\"{ width: btnRef()?.nativeElement.offsetWidth }\"\n (click)=\"onCallAgent()\"\n>\n <span i18n>Ask a question</span>\n</a>\n<mat-menu #callMenu=\"matMenu\" [overlapTrigger]=\"false\" class=\"custom-menu-panel\">\n <ng-template matMenuContent let-width=\"width\">\n <div class=\"menu-content\" [ngStyle]=\"{ width: width + 'px' }\">\n <a mat-menu-item href=\"mailto:hello@gmail.com\">\n <span class=\"flex items-center\">\n <span class=\"flex items-center\">\n <mat-icon class=\"mr-3\">email</mat-icon>\n </span>\n <span i18n>Email</span>\n </span>\n <span class=\"pl-9\" i18n>{{ email() }}</span>\n </a>\n <hr />\n <a mat-menu-item [href]=\"'https://wa.me/' + phone()\" target=\"_blank\">\n <span class=\"flex items-center\">\n <span class=\"flex items-center\">\n <mat-icon class=\"mr-3\">call</mat-icon>\n </span>\n <span i18n>WhatsApp</span>\n </span>\n <span class=\"pl-9\" i18n>{{ phone() }}</span>\n </a>\n </div>\n </ng-template>\n</mat-menu>\n", styles: [".mat-mdc-menu-panel,.custom-menu-panel{max-width:100%}\n"] }]
1193
+ }] });
1194
+
1195
+ class PropertyDetailsComponent extends BaseComponent {
1196
+ authService = inject(AuthService);
1197
+ authUserService = inject(AuthUserService);
1198
+ propertyService = inject(PropertyService);
1199
+ authenticated = this.authService.authenticated;
1200
+ property;
1201
+ fullname = '';
1202
+ username = '';
1203
+ user;
1204
+ inWishList = false;
1205
+ purchased = false;
1206
+ selectedOption;
1207
+ variants = [];
1208
+ selectedVariant;
1209
+ variantOption = signal(undefined);
1210
+ variantOptionChanged = computed(() => {
1211
+ return this.variantOption();
1212
+ });
1213
+ // results: any[] = [];
1214
+ constructor() {
1215
+ super();
1216
+ effect(() => {
1217
+ if (this.authenticated()) {
1218
+ this.wishListCheck();
1219
+ }
1220
+ });
1221
+ }
1222
+ ngOnInit() {
1223
+ this.getProperty();
1224
+ }
1225
+ getProperty() {
1226
+ this.propertyService.get(this.id).subscribe({
1227
+ next: (res) => {
1228
+ this.property = res.data;
1229
+ if (this.property.variants) {
1230
+ this.variants = this.property.variants;
1231
+ this.selectedVariant = this.variants[0];
1232
+ // this.selectedVariant.options.forEach((item) => {
1233
+ // this.results.push([item.option.id, item.value.id]);
1234
+ // });
1235
+ }
1236
+ this.findUserBaseInfo(this.property.agentId);
1237
+ this.titleService.setTitle(`${this.property.title}`);
1238
+ },
1239
+ });
1240
+ }
1241
+ findVariant(optionId, valueId) {
1242
+ return this.variants.find((item) => {
1243
+ const exist = item.options.some((i) => i.option.id === optionId && i.value.id === valueId);
1244
+ if (exist) {
1245
+ return item;
1246
+ }
1247
+ return null;
1248
+ });
1249
+ }
1250
+ findUserBaseInfo(userId) {
1251
+ this.authUserService.getPublicUserInfo(userId).subscribe({
1252
+ next: (res) => {
1253
+ this.fullname = res.name;
1254
+ this.username = res.username;
1255
+ this.user = res;
1256
+ },
1257
+ });
1258
+ }
1259
+ wishListCheck() {
1260
+ this.propertyService.wishListCheck(this.id).subscribe({
1261
+ next: (res) => {
1262
+ this.inWishList = res.data;
1263
+ },
1264
+ });
1265
+ }
1266
+ onWish(e) {
1267
+ if (this.authenticated()) {
1268
+ if (this.inWishList) {
1269
+ this.removeFromWishlist();
1270
+ }
1271
+ else {
1272
+ this.addToWishlist();
1273
+ }
1274
+ }
1275
+ else {
1276
+ this.snackBarService.open('Please sign in');
1277
+ }
1278
+ }
1279
+ onRequestViewing(e) {
1280
+ this.router.navigate([`/properties/${this.id}/viewing/application`]);
1281
+ }
1282
+ removeFromWishlist() {
1283
+ this.propertyService.removeFromWishlist(this.id).subscribe({
1284
+ next: (res) => {
1285
+ this.inWishList = false;
1286
+ this.snackBarService.open('Removed');
1287
+ },
1288
+ });
1289
+ }
1290
+ addToWishlist() {
1291
+ this.propertyService.addToWishlist(this.id).subscribe({
1292
+ next: (res) => {
1293
+ this.inWishList = true;
1294
+ this.snackBarService.open('Added');
1295
+ },
1296
+ });
1297
+ }
1298
+ onOffer(e) {
1299
+ if (this.authenticated()) {
1300
+ this.router.navigate([`/properties/${this.id}/offer`]);
1301
+ }
1302
+ else {
1303
+ this.snackBarService.open('Please sign in');
1304
+ }
1305
+ }
1306
+ onOptionChange(e) {
1307
+ const criteria = [];
1308
+ this.selectedVariant.options.map((item) => {
1309
+ criteria.push({
1310
+ optionId: item.option.id === e.option.id ? e.option.id : item.option.id,
1311
+ valueId: item.option.id === e.option.id ? e.value.id : item.value.id,
1312
+ });
1313
+ });
1314
+ // const criteria = [
1315
+ // { optionId: '249008855294545920', valueId: '249008855298740224' }, // color: red
1316
+ // { optionId: '248661715955355648', valueId: '249001130061860865' }, // size: 256GB
1317
+ // ];
1318
+ // Function to check if a variant matches all the criteria
1319
+ const matchesAllCriteria = (variant, criteria) => {
1320
+ return _.every(criteria, (criterion) => {
1321
+ return _.some(variant.options, (o) => o.option.id === criterion.optionId && o.value.id === criterion.valueId);
1322
+ });
1323
+ };
1324
+ // Function to update the criteria based on a matching variant
1325
+ const updateCriteriaFromVariant = (variant) => {
1326
+ return _.map(variant.options, (o) => ({
1327
+ optionId: o.option.id,
1328
+ valueId: o.value.id,
1329
+ }));
1330
+ };
1331
+ // Find the variant that matches all the criteria
1332
+ const matchingVariant = _.find(this.variants, (variant) => matchesAllCriteria(variant, criteria));
1333
+ this.selectedVariant = matchingVariant;
1334
+ // const updatedCriteria = matchingVariant ? updateCriteriaFromVariant(matchingVariant) : [];
1335
+ }
1336
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyDetailsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1337
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: PropertyDetailsComponent, isStandalone: true, selector: "rolatech-property-details", usesInheritance: true, ngImport: i0, template: "@if (property) {\n <rolatech-container>\n <div class=\"flex flex-col w-full\">\n <div class=\"py-3\">\n <rolatech-property-media [media]=\"property.media\"></rolatech-property-media>\n </div>\n <div class=\"flex flex-col md:flex-row gap-3 w-full\">\n <div class=\"w-full md:w-2/3\">\n <rolatech-property-info [property]=\"property\" (wish)=\"onWish($event)\" [inWishList]=\"inWishList\">\n </rolatech-property-info>\n <rolatech-property-owner-renderer\n [name]=\"fullname\"\n [avatar]=\"user?.avatar\"\n [username]=\"username\"\n ></rolatech-property-owner-renderer>\n <mat-divider></mat-divider>\n <rolatech-property-features [features]=\"property.features\"></rolatech-property-features>\n <rolatech-property-location [location]=\"property.location\"></rolatech-property-location>\n @if (property.videoTour) {\n <rolatech-property-video-tour [videoTour]=\"property.videoTour\"></rolatech-property-video-tour>\n }\n <div class=\"flex flex-col\">\n <div class=\"text-2xl font-bold pt-3\" i18n>Sections</div>\n @for (section of property.sections; track $index) {\n <rolatech-property-section [section]=\"section\"></rolatech-property-section>\n }\n <rolatech-comments [itemId]=\"property.id\"></rolatech-comments>\n </div>\n </div>\n <div class=\"w-full md:w-1/3\">\n <rolatech-property-pricing (wish)=\"onWish($event)\" [property]=\"property\"></rolatech-property-pricing>\n <rolatech-property-actions\n [property]=\"property\"\n (offer)=\"onOffer($event)\"\n (deposit)=\"onOffer($event)\"\n (requestViewing)=\"onRequestViewing($event)\"\n >\n @if (user) {\n <rolatech-property-action-contact [email]=\"user.email\" [phone]=\"user.phone\"></rolatech-property-action-contact>\n }\n </rolatech-property-actions>\n </div>\n </div>\n </div>\n </rolatech-container>\n}\n", styles: [""], dependencies: [{ kind: "ngmodule", type: AngularCommonModule }, { kind: "ngmodule", type: AngularComponentsModule }, { kind: "component", type: i2$1.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "component", type: ContainerComponent, selector: "rolatech-container" }, { kind: "component", type: PropertyInfoComponent, selector: "rolatech-property-info", inputs: ["property", "inWishList"], outputs: ["wish"] }, { kind: "component", type: PropertyMediaComponent, selector: "rolatech-property-media", inputs: ["media", "min"] }, { kind: "component", type: PropertyPricingComponent, selector: "rolatech-property-pricing", inputs: ["property", "price"] }, { kind: "component", type: PropertySectionComponent, selector: "rolatech-property-section", inputs: ["section", "user", "username"] }, { kind: "component", type: PropertyActionsComponent, selector: "rolatech-property-actions", inputs: ["property"], outputs: ["requestViewing", "offer", "deposit"] }, { kind: "component", type: CommentsComponent, selector: "rolatech-comments", inputs: ["itemId"] }, { kind: "component", type: PropertyOwnerRendererComponent, selector: "rolatech-property-owner-renderer", inputs: ["name", "avatar", "username"] }, { kind: "component", type: PropertyLocationComponent, selector: "rolatech-property-location", inputs: ["location"] }, { kind: "component", type: PropertyFeaturesComponent, selector: "rolatech-property-features", inputs: ["features"] }, { kind: "component", type: PropertyVideoTourComponent, selector: "rolatech-property-video-tour", inputs: ["videoTour"] }, { kind: "component", type: PropertyActionContactComponent, selector: "rolatech-property-action-contact", inputs: ["email", "phone"], outputs: ["callAgent", "emailAgent"] }] });
1338
+ }
1339
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyDetailsComponent, decorators: [{
1340
+ type: Component,
1341
+ args: [{ selector: 'rolatech-property-details', imports: [
1342
+ AngularCommonModule,
1343
+ AngularComponentsModule,
1344
+ ContainerComponent,
1345
+ PropertyInfoComponent,
1346
+ PropertyMediaComponent,
1347
+ PropertyPricingComponent,
1348
+ PropertySectionComponent,
1349
+ PropertyActionsComponent,
1350
+ CommentsComponent,
1351
+ PropertyOwnerRendererComponent,
1352
+ PropertyLocationComponent,
1353
+ PropertyFeaturesComponent,
1354
+ PropertyVideoTourComponent,
1355
+ PropertyActionContactComponent,
1356
+ ], template: "@if (property) {\n <rolatech-container>\n <div class=\"flex flex-col w-full\">\n <div class=\"py-3\">\n <rolatech-property-media [media]=\"property.media\"></rolatech-property-media>\n </div>\n <div class=\"flex flex-col md:flex-row gap-3 w-full\">\n <div class=\"w-full md:w-2/3\">\n <rolatech-property-info [property]=\"property\" (wish)=\"onWish($event)\" [inWishList]=\"inWishList\">\n </rolatech-property-info>\n <rolatech-property-owner-renderer\n [name]=\"fullname\"\n [avatar]=\"user?.avatar\"\n [username]=\"username\"\n ></rolatech-property-owner-renderer>\n <mat-divider></mat-divider>\n <rolatech-property-features [features]=\"property.features\"></rolatech-property-features>\n <rolatech-property-location [location]=\"property.location\"></rolatech-property-location>\n @if (property.videoTour) {\n <rolatech-property-video-tour [videoTour]=\"property.videoTour\"></rolatech-property-video-tour>\n }\n <div class=\"flex flex-col\">\n <div class=\"text-2xl font-bold pt-3\" i18n>Sections</div>\n @for (section of property.sections; track $index) {\n <rolatech-property-section [section]=\"section\"></rolatech-property-section>\n }\n <rolatech-comments [itemId]=\"property.id\"></rolatech-comments>\n </div>\n </div>\n <div class=\"w-full md:w-1/3\">\n <rolatech-property-pricing (wish)=\"onWish($event)\" [property]=\"property\"></rolatech-property-pricing>\n <rolatech-property-actions\n [property]=\"property\"\n (offer)=\"onOffer($event)\"\n (deposit)=\"onOffer($event)\"\n (requestViewing)=\"onRequestViewing($event)\"\n >\n @if (user) {\n <rolatech-property-action-contact [email]=\"user.email\" [phone]=\"user.phone\"></rolatech-property-action-contact>\n }\n </rolatech-property-actions>\n </div>\n </div>\n </div>\n </rolatech-container>\n}\n" }]
1357
+ }], ctorParameters: () => [] });
1358
+
1359
+ const MY_FORMATS$3 = {
1360
+ parse: {
1361
+ dateInput: 'YYYY-MM-DD',
1362
+ },
1363
+ display: {
1364
+ dateInput: 'YYYY-MM-DD',
1365
+ monthYearLabel: 'MMM YYYY',
1366
+ dateA11yLabel: 'YYYY-MM-DD',
1367
+ monthYearA11yLabel: 'MMMM YYYY',
1368
+ },
1369
+ };
1370
+ class PropertyViewingTimeComponent {
1371
+ minDate = new Date();
1372
+ maxDate = new Date();
1373
+ output = output();
1374
+ select = output();
1375
+ proposedTime = input({
1376
+ date: '',
1377
+ time: '',
1378
+ });
1379
+ time = ViewingTime;
1380
+ ngOnInit() {
1381
+ this.minDate.setDate(this.minDate.getDate() + 1);
1382
+ this.maxDate.setDate(this.maxDate.getDate() + 4);
1383
+ }
1384
+ ngDoCheck() {
1385
+ this.output.emit(this.proposedTime());
1386
+ }
1387
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyViewingTimeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1388
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: PropertyViewingTimeComponent, isStandalone: true, selector: "rolatech-property-viewing-time", inputs: { proposedTime: { classPropertyName: "proposedTime", publicName: "proposedTime", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { output: "output", select: "select" }, providers: [
1389
+ {
1390
+ provide: DateAdapter,
1391
+ useClass: MomentDateAdapter,
1392
+ deps: [MAT_DATE_LOCALE],
1393
+ },
1394
+ { provide: MAT_DATE_FORMATS, useValue: MY_FORMATS$3 },
1395
+ ], ngImport: i0, template: "<div class=\"flex flex-col md:flex-row gap-2\">\n <mat-form-field appearance=\"fill\">\n <mat-label i18n>Viewing Date</mat-label>\n <input\n matInput\n placeholder=\"Viewing date\"\n [matDatepicker]=\"startDatePicker\"\n [min]=\"minDate\"\n [max]=\"maxDate\"\n (focus)=\"startDatePicker.open()\"\n name=\"viewingDate\"\n [(ngModel)]=\"proposedTime()!.date\"\n (dateInput)=\"proposedTime()!.date = $event.value.format('YYYY-MM-DD')\"\n required\n readonly\n />\n <mat-datepicker-toggle matIconPrefix [for]=\"startDatePicker\"></mat-datepicker-toggle>\n <mat-datepicker #startDatePicker></mat-datepicker>\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n>Viewing time</mat-label>\n <mat-icon matIconPrefix>schedule</mat-icon>\n <mat-select [(ngModel)]=\"proposedTime()!.time\" required readonly>\n @for (d of time; track d) {\n <mat-option [value]=\"d\">\n {{ d }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n</div>\n", styles: ["mat-form-field{width:100%}\n"], dependencies: [{ kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2$2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2$2.MatLabel, selector: "mat-label" }, { kind: "directive", type: i2$2.MatPrefix, selector: "[matPrefix], [matIconPrefix], [matTextPrefix]", inputs: ["matTextPrefix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.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$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i8.MatDatepicker, selector: "mat-datepicker", exportAs: ["matDatepicker"] }, { kind: "directive", type: i8.MatDatepickerInput, selector: "input[matDatepicker]", inputs: ["matDatepicker", "min", "max", "matDatepickerFilter"], exportAs: ["matDatepickerInput"] }, { kind: "component", type: i8.MatDatepickerToggle, selector: "mat-datepicker-toggle", inputs: ["for", "tabIndex", "aria-label", "disabled", "disableRipple"], exportAs: ["matDatepickerToggle"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i4$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatOptionModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }] });
1396
+ }
1397
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyViewingTimeComponent, decorators: [{
1398
+ type: Component,
1399
+ args: [{ selector: 'rolatech-property-viewing-time', imports: [
1400
+ MatFormFieldModule,
1401
+ MatInputModule,
1402
+ FormsModule,
1403
+ MatDatepickerModule,
1404
+ MatSelectModule,
1405
+ MatOptionModule,
1406
+ MatIconModule,
1407
+ MatButtonModule,
1408
+ ], providers: [
1409
+ {
1410
+ provide: DateAdapter,
1411
+ useClass: MomentDateAdapter,
1412
+ deps: [MAT_DATE_LOCALE],
1413
+ },
1414
+ { provide: MAT_DATE_FORMATS, useValue: MY_FORMATS$3 },
1415
+ ], template: "<div class=\"flex flex-col md:flex-row gap-2\">\n <mat-form-field appearance=\"fill\">\n <mat-label i18n>Viewing Date</mat-label>\n <input\n matInput\n placeholder=\"Viewing date\"\n [matDatepicker]=\"startDatePicker\"\n [min]=\"minDate\"\n [max]=\"maxDate\"\n (focus)=\"startDatePicker.open()\"\n name=\"viewingDate\"\n [(ngModel)]=\"proposedTime()!.date\"\n (dateInput)=\"proposedTime()!.date = $event.value.format('YYYY-MM-DD')\"\n required\n readonly\n />\n <mat-datepicker-toggle matIconPrefix [for]=\"startDatePicker\"></mat-datepicker-toggle>\n <mat-datepicker #startDatePicker></mat-datepicker>\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n>Viewing time</mat-label>\n <mat-icon matIconPrefix>schedule</mat-icon>\n <mat-select [(ngModel)]=\"proposedTime()!.time\" required readonly>\n @for (d of time; track d) {\n <mat-option [value]=\"d\">\n {{ d }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n</div>\n", styles: ["mat-form-field{width:100%}\n"] }]
1416
+ }] });
1417
+
1418
+ const MY_FORMATS$2 = {
1419
+ parse: {
1420
+ dateInput: 'YYYY-MM-DD',
1421
+ },
1422
+ display: {
1423
+ dateInput: 'YYYY-MM-DD',
1424
+ monthYearLabel: 'MMM YYYY',
1425
+ dateA11yLabel: 'YYYY-MM-DD',
1426
+ monthYearA11yLabel: 'MMMM YYYY',
1427
+ },
1428
+ };
1429
+ class PropertyViewingRequestComponent extends BaseComponent {
1430
+ authService = inject(AuthService);
1431
+ authUserService = inject(AuthUserService);
1432
+ propertyService = inject(PropertyService);
1433
+ property;
1434
+ minDate = new Date();
1435
+ viewing = {
1436
+ firstName: '',
1437
+ lastName: '',
1438
+ email: '',
1439
+ phone: '',
1440
+ viewerCategory: PropertyViewerCategory.TENANT,
1441
+ // tenantCategory: PropertyApplicantType.INDIVIDUAL,
1442
+ proposedSlots: [
1443
+ { date: '', time: '' },
1444
+ { date: '', time: '' },
1445
+ { date: '', time: '' },
1446
+ ],
1447
+ };
1448
+ selectedViewerCategory = 'TENANT';
1449
+ viewerCategory = PropertyViewerCategory;
1450
+ applicantTypes = PropertyApplicantType;
1451
+ residencyStatus = ResidencyStatus;
1452
+ employmentStatus = EmploymentStatus;
1453
+ adverseCreditStatus = AdverseCreditStatus;
1454
+ countries = ['United Kingdom', 'China'];
1455
+ selectedCountry;
1456
+ displayPrice = computed(() => {
1457
+ return this.property ? (this.property.price / 100).toFixed(2) : 0;
1458
+ });
1459
+ ngOnInit() {
1460
+ this.getProperty();
1461
+ // this.viewing['country'] = this.countries[0];
1462
+ }
1463
+ getProperty() {
1464
+ this.propertyService.get(this.id).subscribe({
1465
+ next: (res) => {
1466
+ this.property = res.data;
1467
+ this.titleService.setTitle(`${this.property.title}`);
1468
+ },
1469
+ });
1470
+ }
1471
+ onCountrySelect(event) {
1472
+ this.viewing['country'] = event.value;
1473
+ }
1474
+ onViewerSelectionChange(event) {
1475
+ this.selectedViewerCategory = event.value;
1476
+ }
1477
+ sendRequest() {
1478
+ this.propertyService.requestViewing(this.id, this.viewing).subscribe({
1479
+ next: (res) => {
1480
+ this.snackBarService.open('Request viewing successfully');
1481
+ },
1482
+ error: (error) => {
1483
+ this.snackBarService.open(error.message);
1484
+ },
1485
+ });
1486
+ }
1487
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyViewingRequestComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
1488
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: PropertyViewingRequestComponent, isStandalone: true, selector: "rolatech-property-viewing-request", providers: [
1489
+ {
1490
+ provide: DateAdapter,
1491
+ useClass: MomentDateAdapter,
1492
+ deps: [MAT_DATE_LOCALE],
1493
+ },
1494
+ { provide: MAT_DATE_FORMATS, useValue: MY_FORMATS$2 },
1495
+ ], usesInheritance: true, ngImport: i0, template: "<rolatech-container>\n <div class=\"flex flex-col-reverse md:flex-row w-full py-2 mb-3 gap-3\">\n <div class=\"w-full md:w-2/3\">\n <div>\n <div class=\"text-xl font-bold py-2 mb-3\" i18n>Request a viewing</div>\n <div class=\"mb-3\">\n <mat-radio-group\n aria-label=\"Select an option\"\n [(ngModel)]=\"viewing.viewerCategory\"\n (change)=\"onViewerSelectionChange($event)\"\n >\n <mat-radio-button [value]=\"viewerCategory.TENANT\" i18n>I'm a tenant</mat-radio-button>\n <mat-radio-button [value]=\"viewerCategory.AGENT\" i18n>I'm an agent</mat-radio-button>\n </mat-radio-group>\n </div>\n @if (selectedViewerCategory === 'TENANT') {\n <div class=\"flex flex-col md:flex-row gap-2\">\n <mat-form-field appearance=\"fill\">\n <mat-label i18n>First name</mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"viewing.firstName\" />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n>Last name</mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"viewing.lastName\" />\n </mat-form-field>\n </div>\n <div class=\"flex flex-col md:flex-row gap-2\">\n <mat-form-field appearance=\"fill\">\n <mat-label i18n>Phone</mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"viewing.phone\" />\n </mat-form-field>\n </div>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n>Email</mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"viewing.email\" />\n </mat-form-field>\n <mat-form-field>\n <mat-label i18n>Tenant type</mat-label>\n <mat-select placeholder=\"Tenant type\" [(ngModel)]=\"viewing.applicantType\">\n @for (applicantType of applicantTypes | keyvalue; track applicantType) {\n <mat-option [value]=\"applicantType.key\">\n {{ applicantType.value }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n } @else {\n <div class=\"flex flex-col md:flex-row gap-2\">\n <mat-form-field appearance=\"fill\">\n <mat-label i18n>Your email</mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"viewing.email\" />\n </mat-form-field>\n </div>\n <div class=\"flex flex-col md:flex-row gap-2\">\n <mat-form-field appearance=\"fill\">\n <mat-label i18n>Number of tenants</mat-label>\n <input matInput type=\"number\" [(ngModel)]=\"viewing.numberOfTenants\" />\n </mat-form-field>\n </div>\n <mat-form-field>\n <mat-label i18n>Applicant type</mat-label>\n <mat-select placeholder=\"Applicant type\" [(ngModel)]=\"viewing.applicantType\">\n @for (applicantType of applicantTypes | keyvalue; track applicantType) {\n <mat-option [value]=\"applicantType.key\">\n {{ applicantType.value }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <input\n matInput\n placeholder=\"Start date\"\n [matDatepicker]=\"startDatePicker\"\n [min]=\"minDate\"\n (focus)=\"startDatePicker.open()\"\n name=\"startDate\"\n [(ngModel)]=\"viewing.startDate\"\n (dateInput)=\"viewing.startDate = $event.value.format('YYYY-MM-DD')\"\n readonly\n />\n <mat-datepicker-toggle matIconPrefix [for]=\"startDatePicker\"></mat-datepicker-toggle>\n <mat-datepicker #startDatePicker></mat-datepicker>\n </mat-form-field>\n <div class=\"flex flex-col md:flex-row gap-2\">\n <mat-form-field appearance=\"fill\">\n <mat-label i18n>Tenancy length</mat-label>\n <span matTextSuffix>months</span>\n <input matInput type=\"number\" [(ngModel)]=\"viewing.tenancyDuration\" />\n </mat-form-field>\n </div>\n <mat-form-field appearance=\"fill\" floatLabel=\"always\">\n <mat-label i18n>Annual income(All tenants combined)</mat-label>\n <span matTextPrefix>\u00A3&nbsp;</span>\n <input matInput type=\"text\" placeholder=\"00.00\" [(ngModel)]=\"viewing.income\" />\n </mat-form-field>\n <mat-form-field>\n <mat-label i18n>Employment Status</mat-label>\n <mat-select placeholder=\"Employment Status\" [(ngModel)]=\"viewing.employmentStatus\">\n @for (item of employmentStatus | keyvalue; track item) {\n <mat-option [value]=\"item.key\">\n {{ item.value }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field>\n <mat-label i18n>AdverseCredit Status</mat-label>\n <mat-select placeholder=\"AdverseCredit Status\" [(ngModel)]=\"viewing.adverseCreditStatus\">\n @for (item of adverseCreditStatus | keyvalue; track item) {\n <mat-option [value]=\"item.key\">\n {{ item.value }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n <div>\n <div class=\"mb-3\">\n <div class=\"text-lg font-bold\" i18n>Viewing date</div>\n <div class=\"opacity-70\" i18n>\n Please choose 3 different times on at least 2 different days that would work for you.\n </div>\n </div>\n @for (item of viewing.proposedSlots; track $index) {\n <rolatech-property-viewing-time [proposedTime]=\"item\"></rolatech-property-viewing-time>\n }\n </div>\n </div>\n <button mat-flat-button (click)=\"sendRequest()\" i18n>Send request</button>\n </div>\n <div class=\"w-full md:w-1/2 py-2 mb-3\">\n <div class=\"text-xl font-bold py-2 mb-3\" i18n>Property info</div>\n <div>\n <rolatech-thumbnail [src]=\"property ? property.media[0].url : ''\" size=\"small\"></rolatech-thumbnail>\n </div>\n <div class=\"py-3 text-xl font-bold\">\u00A3{{ displayPrice() }}</div>\n </div>\n </div>\n</rolatech-container>\n", styles: ["mat-form-field{width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i1.KeyValuePipe, name: "keyvalue" }, { kind: "component", type: ContainerComponent, selector: "rolatech-container" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2$2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2$2.MatLabel, selector: "mat-label" }, { kind: "directive", type: i2$2.MatPrefix, selector: "[matPrefix], [matIconPrefix], [matTextPrefix]", inputs: ["matTextPrefix"] }, { kind: "directive", type: i2$2.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.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$3.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: TextFieldModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.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: ThumbnailComponent, selector: "rolatech-thumbnail", inputs: ["src", "size", "mode", "ratio", "width", "height"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i4$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i8.MatDatepicker, selector: "mat-datepicker", exportAs: ["matDatepicker"] }, { kind: "directive", type: i8.MatDatepickerInput, selector: "input[matDatepicker]", inputs: ["matDatepicker", "min", "max", "matDatepickerFilter"], exportAs: ["matDatepickerInput"] }, { kind: "component", type: i8.MatDatepickerToggle, selector: "mat-datepicker-toggle", inputs: ["for", "tabIndex", "aria-label", "disabled", "disableRipple"], exportAs: ["matDatepickerToggle"] }, { kind: "ngmodule", type: MatRadioModule }, { kind: "directive", type: i9.MatRadioGroup, selector: "mat-radio-group", inputs: ["color", "name", "labelPosition", "value", "selected", "disabled", "required", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioGroup"] }, { kind: "component", type: i9.MatRadioButton, selector: "mat-radio-button", inputs: ["id", "name", "aria-label", "aria-labelledby", "aria-describedby", "disableRipple", "tabIndex", "checked", "value", "labelPosition", "disabled", "required", "color", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioButton"] }, { kind: "component", type: PropertyViewingTimeComponent, selector: "rolatech-property-viewing-time", inputs: ["proposedTime"], outputs: ["output", "select"] }] });
1496
+ }
1497
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyViewingRequestComponent, decorators: [{
1498
+ type: Component,
1499
+ args: [{ selector: 'rolatech-property-viewing-request', imports: [
1500
+ CommonModule,
1501
+ ContainerComponent,
1502
+ MatFormFieldModule,
1503
+ FormsModule,
1504
+ MatInputModule,
1505
+ TextFieldModule,
1506
+ MatButtonModule,
1507
+ ThumbnailComponent,
1508
+ MatSelectModule,
1509
+ MatDatepickerModule,
1510
+ MatRadioModule,
1511
+ PropertyViewingTimeComponent,
1512
+ ], providers: [
1513
+ {
1514
+ provide: DateAdapter,
1515
+ useClass: MomentDateAdapter,
1516
+ deps: [MAT_DATE_LOCALE],
1517
+ },
1518
+ { provide: MAT_DATE_FORMATS, useValue: MY_FORMATS$2 },
1519
+ ], template: "<rolatech-container>\n <div class=\"flex flex-col-reverse md:flex-row w-full py-2 mb-3 gap-3\">\n <div class=\"w-full md:w-2/3\">\n <div>\n <div class=\"text-xl font-bold py-2 mb-3\" i18n>Request a viewing</div>\n <div class=\"mb-3\">\n <mat-radio-group\n aria-label=\"Select an option\"\n [(ngModel)]=\"viewing.viewerCategory\"\n (change)=\"onViewerSelectionChange($event)\"\n >\n <mat-radio-button [value]=\"viewerCategory.TENANT\" i18n>I'm a tenant</mat-radio-button>\n <mat-radio-button [value]=\"viewerCategory.AGENT\" i18n>I'm an agent</mat-radio-button>\n </mat-radio-group>\n </div>\n @if (selectedViewerCategory === 'TENANT') {\n <div class=\"flex flex-col md:flex-row gap-2\">\n <mat-form-field appearance=\"fill\">\n <mat-label i18n>First name</mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"viewing.firstName\" />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n>Last name</mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"viewing.lastName\" />\n </mat-form-field>\n </div>\n <div class=\"flex flex-col md:flex-row gap-2\">\n <mat-form-field appearance=\"fill\">\n <mat-label i18n>Phone</mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"viewing.phone\" />\n </mat-form-field>\n </div>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n>Email</mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"viewing.email\" />\n </mat-form-field>\n <mat-form-field>\n <mat-label i18n>Tenant type</mat-label>\n <mat-select placeholder=\"Tenant type\" [(ngModel)]=\"viewing.applicantType\">\n @for (applicantType of applicantTypes | keyvalue; track applicantType) {\n <mat-option [value]=\"applicantType.key\">\n {{ applicantType.value }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n } @else {\n <div class=\"flex flex-col md:flex-row gap-2\">\n <mat-form-field appearance=\"fill\">\n <mat-label i18n>Your email</mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"viewing.email\" />\n </mat-form-field>\n </div>\n <div class=\"flex flex-col md:flex-row gap-2\">\n <mat-form-field appearance=\"fill\">\n <mat-label i18n>Number of tenants</mat-label>\n <input matInput type=\"number\" [(ngModel)]=\"viewing.numberOfTenants\" />\n </mat-form-field>\n </div>\n <mat-form-field>\n <mat-label i18n>Applicant type</mat-label>\n <mat-select placeholder=\"Applicant type\" [(ngModel)]=\"viewing.applicantType\">\n @for (applicantType of applicantTypes | keyvalue; track applicantType) {\n <mat-option [value]=\"applicantType.key\">\n {{ applicantType.value }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <input\n matInput\n placeholder=\"Start date\"\n [matDatepicker]=\"startDatePicker\"\n [min]=\"minDate\"\n (focus)=\"startDatePicker.open()\"\n name=\"startDate\"\n [(ngModel)]=\"viewing.startDate\"\n (dateInput)=\"viewing.startDate = $event.value.format('YYYY-MM-DD')\"\n readonly\n />\n <mat-datepicker-toggle matIconPrefix [for]=\"startDatePicker\"></mat-datepicker-toggle>\n <mat-datepicker #startDatePicker></mat-datepicker>\n </mat-form-field>\n <div class=\"flex flex-col md:flex-row gap-2\">\n <mat-form-field appearance=\"fill\">\n <mat-label i18n>Tenancy length</mat-label>\n <span matTextSuffix>months</span>\n <input matInput type=\"number\" [(ngModel)]=\"viewing.tenancyDuration\" />\n </mat-form-field>\n </div>\n <mat-form-field appearance=\"fill\" floatLabel=\"always\">\n <mat-label i18n>Annual income(All tenants combined)</mat-label>\n <span matTextPrefix>\u00A3&nbsp;</span>\n <input matInput type=\"text\" placeholder=\"00.00\" [(ngModel)]=\"viewing.income\" />\n </mat-form-field>\n <mat-form-field>\n <mat-label i18n>Employment Status</mat-label>\n <mat-select placeholder=\"Employment Status\" [(ngModel)]=\"viewing.employmentStatus\">\n @for (item of employmentStatus | keyvalue; track item) {\n <mat-option [value]=\"item.key\">\n {{ item.value }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field>\n <mat-label i18n>AdverseCredit Status</mat-label>\n <mat-select placeholder=\"AdverseCredit Status\" [(ngModel)]=\"viewing.adverseCreditStatus\">\n @for (item of adverseCreditStatus | keyvalue; track item) {\n <mat-option [value]=\"item.key\">\n {{ item.value }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n <div>\n <div class=\"mb-3\">\n <div class=\"text-lg font-bold\" i18n>Viewing date</div>\n <div class=\"opacity-70\" i18n>\n Please choose 3 different times on at least 2 different days that would work for you.\n </div>\n </div>\n @for (item of viewing.proposedSlots; track $index) {\n <rolatech-property-viewing-time [proposedTime]=\"item\"></rolatech-property-viewing-time>\n }\n </div>\n </div>\n <button mat-flat-button (click)=\"sendRequest()\" i18n>Send request</button>\n </div>\n <div class=\"w-full md:w-1/2 py-2 mb-3\">\n <div class=\"text-xl font-bold py-2 mb-3\" i18n>Property info</div>\n <div>\n <rolatech-thumbnail [src]=\"property ? property.media[0].url : ''\" size=\"small\"></rolatech-thumbnail>\n </div>\n <div class=\"py-3 text-xl font-bold\">\u00A3{{ displayPrice() }}</div>\n </div>\n </div>\n</rolatech-container>\n", styles: ["mat-form-field{width:100%}\n"] }]
1520
+ }] });
1521
+
1522
+ const MY_FORMATS$1 = {
1523
+ parse: {
1524
+ dateInput: 'YYYY-MM-DD',
1525
+ },
1526
+ display: {
1527
+ dateInput: 'YYYY-MM-DD',
1528
+ monthYearLabel: 'MMM YYYY',
1529
+ dateA11yLabel: 'YYYY-MM-DD',
1530
+ monthYearA11yLabel: 'MMMM YYYY',
1531
+ },
1532
+ };
1533
+ class PropertyOfferComponent extends BaseComponent {
1534
+ authService = inject(AuthService);
1535
+ authUserService = inject(AuthUserService);
1536
+ propertyService = inject(PropertyService);
1537
+ offer = {
1538
+ firstName: '',
1539
+ lastName: '',
1540
+ phone: '',
1541
+ email: '',
1542
+ applicantType: '',
1543
+ residencyStatus: '',
1544
+ employmentStatus: '',
1545
+ adverseCreditStatus: '',
1546
+ startDate: '',
1547
+ notes: '',
1548
+ amount: 0,
1549
+ };
1550
+ get formattedAmount() {
1551
+ return this.offer.amount.toFixed(2); // equivalent to | number:'1.2-2'
1552
+ } // applicantTypes = PropertyApplicantType;
1553
+ applicantTypes = Object.keys(PropertyApplicantType); // ['INDIVIDUAL', 'CORPORATE', ...]
1554
+ PropertyApplicantType = PropertyApplicantType;
1555
+ residencyStatus = ResidencyStatus;
1556
+ employmentStatus = EmploymentStatus;
1557
+ adverseCreditStatus = AdverseCreditStatus;
1558
+ selectedCountry;
1559
+ // property!: Property;
1560
+ // displayPrice = computed(() => {
1561
+ // return (this.property?.price / 100).toFixed(2);
1562
+ // });
1563
+ property = signal(null);
1564
+ displayPrice = computed(() => {
1565
+ const p = this.property();
1566
+ return p ? (p.price / 100).toFixed(2) : '0.00';
1567
+ });
1568
+ firstImageUrl = computed(() => {
1569
+ const p = this.property();
1570
+ return p?.media?.[0]?.url || '';
1571
+ });
1572
+ minDate = new Date();
1573
+ sending = false;
1574
+ constructor() {
1575
+ super();
1576
+ effect(() => {
1577
+ const p = this.property();
1578
+ if (p) {
1579
+ this.titleService.setTitle(p.title);
1580
+ }
1581
+ });
1582
+ }
1583
+ ngOnInit() {
1584
+ this.getProperty();
1585
+ }
1586
+ onAmountChange(event) {
1587
+ const inputValue = event.target.value;
1588
+ console.log(inputValue);
1589
+ const regex = /^\d*\.?\d{0,2}$/;
1590
+ if (regex.test(inputValue)) {
1591
+ this.offer.amount = inputValue;
1592
+ }
1593
+ }
1594
+ getProperty() {
1595
+ this.propertyService.get(this.id).subscribe({
1596
+ next: (res) => {
1597
+ this.property.set(res.data);
1598
+ // this.titleService.setTitle(`${this.property.title}`);
1599
+ },
1600
+ });
1601
+ }
1602
+ onCategorySelect(event) {
1603
+ this.offer['category'] = event.value;
1604
+ }
1605
+ onResidencyStatusSelect(event) { }
1606
+ sendRequest() {
1607
+ this.sending = true;
1608
+ this.propertyService.makeOffer(this.id, this.offer).subscribe({
1609
+ next: (res) => {
1610
+ this.sending = false;
1611
+ this.snackBarService.open('Sent successfully');
1612
+ this.router.navigate([`/properties/offers/${res.data.id}`]);
1613
+ },
1614
+ error: (error) => {
1615
+ this.sending = false;
1616
+ this.snackBarService.open(error.message);
1617
+ },
1618
+ });
1619
+ }
1620
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyOfferComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1621
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: PropertyOfferComponent, isStandalone: true, selector: "rolatech-property-offer", providers: [
1622
+ {
1623
+ provide: DateAdapter,
1624
+ useClass: MomentDateAdapter,
1625
+ deps: [MAT_DATE_LOCALE],
1626
+ },
1627
+ { provide: MAT_DATE_FORMATS, useValue: MY_FORMATS$1 },
1628
+ ], usesInheritance: true, ngImport: i0, template: "<rolatech-container>\n <div class=\"flex flex-col-reverse md:flex-row w-full py-2 mb-3 gap-3\">\n <div class=\"w-full md:w-2/3\">\n <div>\n <div class=\"text-xl font-bold py-2 mb-3\">\n <!-- <span i18n>Offer for </span><span>{{ property()?.title : '' }}</span> -->\n <span i18n=\"@@offerFor\">Offer for {{ property()?.title }}</span>\n </div>\n <div class=\"flex flex-col md:flex-row gap-2\">\n <mat-form-field appearance=\"fill\">\n <mat-label i18n>First name</mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"offer.firstName\" required />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n>Last name</mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"offer.lastName\" required />\n </mat-form-field>\n </div>\n <div class=\"flex flex-col md:flex-row gap-2\">\n <mat-form-field appearance=\"fill\">\n <mat-label i18n>Phone</mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"offer.phone\" required />\n </mat-form-field>\n </div>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n>Email</mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"offer.email\" required />\n </mat-form-field>\n\n <mat-form-field appearance=\"fill\" floatLabel=\"always\">\n <mat-label i18n>Asking price</mat-label>\n <span matTextPrefix>\u00A3&nbsp;</span>\n <input matInput type=\"text\" placeholder=\"0.00\" [(ngModel)]=\"offer.amount\" required />\n <!-- <input\n matInput\n type=\"number\"\n placeholder=\"0.00\"\n [value]=\"formattedAmount\"\n (input)=\"onAmountChange($event)\"\n required\n /> -->\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n>Tenancy Length</mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"offer.tenancyDuration\" required />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n>Number Of Tenants</mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"offer.numberOfTenants\" required />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label>Start date</mat-label>\n <input\n matInput\n placeholder=\"Start date\"\n [matDatepicker]=\"startDatePicker\"\n [min]=\"minDate\"\n (focus)=\"startDatePicker.open()\"\n name=\"startDate\"\n [(ngModel)]=\"offer.startDate\"\n (dateInput)=\"offer.startDate = $event.value.format('YYYY-MM-DD')\"\n readonly\n required\n />\n <mat-datepicker-toggle matIconPrefix [for]=\"startDatePicker\"></mat-datepicker-toggle>\n <mat-datepicker #startDatePicker></mat-datepicker>\n </mat-form-field>\n <mat-form-field>\n <mat-label i18n>Applicant type</mat-label>\n <mat-select placeholder=\"Applicant type\" [(ngModel)]=\"offer.applicantType\" required>\n @for (key of applicantTypes; track key) {\n <mat-option [value]=\"key\">\n {{ PropertyApplicantType[key] }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <!-- subscriptSizing=\"dynamic\" -->\n <mat-form-field>\n <mat-label i18n>Residency status</mat-label>\n <mat-select placeholder=\"Residency status\" [(ngModel)]=\"offer.residencyStatus\" required>\n @for (status of residencyStatus | keyvalue; track status) {\n <mat-option [value]=\"status.key\">\n {{ status.value }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field>\n <mat-label i18n>Employment Status</mat-label>\n <mat-select placeholder=\"Employment Status\" [(ngModel)]=\"offer.employmentStatus\" required>\n @for (item of employmentStatus | keyvalue; track item) {\n <mat-option [value]=\"item.key\">\n {{ item.value }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field>\n <mat-label i18n>AdverseCredit Status</mat-label>\n <mat-select placeholder=\"AdverseCredit Status\" [(ngModel)]=\"offer.adverseCreditStatus\" required>\n @for (item of adverseCreditStatus | keyvalue; track item) {\n <mat-option [value]=\"item.key\">\n {{ item.value }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n <div>\n <div class=\"text-md py-2\" i18n>Your message(Optional)</div>\n <mat-form-field appearance=\"fill\">\n <textarea matInput type=\"text\" [(ngModel)]=\"offer.notes\" cdkTextareaAutosize cdkAutosizeMinRows=\"3\"></textarea>\n </mat-form-field>\n </div>\n <!-- <button mat-flat-button (click)=\"sendRequest()\" i18n>Send request</button> -->\n <button mat-flat-button (click)=\"sendRequest()\" [disabled]=\"sending\">\n <span style=\"display: flex; align-items: center\">\n @if (sending) {\n <mat-progress-spinner diameter=\"20\" mode=\"indeterminate\" [style.marginRight.px]=\"8\"></mat-progress-spinner>\n }\n {{ sending ? 'Requesting...' : 'Send request' }}\n </span>\n </button>\n </div>\n <div class=\"w-full md:w-1/2 py-2 mb-3\">\n <div class=\"text-xl font-bold py-2 mb-3\" i18n>Property info</div>\n <div>\n <rolatech-thumbnail [src]=\"firstImageUrl()\" size=\"small\"></rolatech-thumbnail>\n </div>\n <div class=\"py-3 text-xl font-bold\">\u00A3{{ displayPrice() }}</div>\n </div>\n </div>\n</rolatech-container>\n", styles: ["mat-form-field{width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i1.KeyValuePipe, name: "keyvalue" }, { kind: "component", type: ContainerComponent, selector: "rolatech-container" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2$2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2$2.MatLabel, selector: "mat-label" }, { kind: "directive", type: i2$2.MatPrefix, selector: "[matPrefix], [matIconPrefix], [matTextPrefix]", inputs: ["matTextPrefix"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.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$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i4.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$3.CdkTextareaAutosize, selector: "textarea[cdkTextareaAutosize]", inputs: ["cdkAutosizeMinRows", "cdkAutosizeMaxRows", "cdkTextareaAutosize", "placeholder"], exportAs: ["cdkTextareaAutosize"] }, { kind: "ngmodule", type: TextFieldModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.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: ThumbnailComponent, selector: "rolatech-thumbnail", inputs: ["src", "size", "mode", "ratio", "width", "height"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i4$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i8.MatDatepicker, selector: "mat-datepicker", exportAs: ["matDatepicker"] }, { kind: "directive", type: i8.MatDatepickerInput, selector: "input[matDatepicker]", inputs: ["matDatepicker", "min", "max", "matDatepickerFilter"], exportAs: ["matDatepickerInput"] }, { kind: "component", type: i8.MatDatepickerToggle, selector: "mat-datepicker-toggle", inputs: ["for", "tabIndex", "aria-label", "disabled", "disableRipple"], exportAs: ["matDatepickerToggle"] }, { kind: "component", type: MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }] });
1629
+ }
1630
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyOfferComponent, decorators: [{
1631
+ type: Component,
1632
+ args: [{ selector: 'rolatech-property-offer', imports: [
1633
+ CommonModule,
1634
+ ContainerComponent,
1635
+ MatFormFieldModule,
1636
+ FormsModule,
1637
+ MatInputModule,
1638
+ TextFieldModule,
1639
+ MatButtonModule,
1640
+ ThumbnailComponent,
1641
+ MatSelectModule,
1642
+ KeyValuePipe,
1643
+ MatDatepickerModule,
1644
+ MatProgressSpinner,
1645
+ ], providers: [
1646
+ {
1647
+ provide: DateAdapter,
1648
+ useClass: MomentDateAdapter,
1649
+ deps: [MAT_DATE_LOCALE],
1650
+ },
1651
+ { provide: MAT_DATE_FORMATS, useValue: MY_FORMATS$1 },
1652
+ ], template: "<rolatech-container>\n <div class=\"flex flex-col-reverse md:flex-row w-full py-2 mb-3 gap-3\">\n <div class=\"w-full md:w-2/3\">\n <div>\n <div class=\"text-xl font-bold py-2 mb-3\">\n <!-- <span i18n>Offer for </span><span>{{ property()?.title : '' }}</span> -->\n <span i18n=\"@@offerFor\">Offer for {{ property()?.title }}</span>\n </div>\n <div class=\"flex flex-col md:flex-row gap-2\">\n <mat-form-field appearance=\"fill\">\n <mat-label i18n>First name</mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"offer.firstName\" required />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n>Last name</mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"offer.lastName\" required />\n </mat-form-field>\n </div>\n <div class=\"flex flex-col md:flex-row gap-2\">\n <mat-form-field appearance=\"fill\">\n <mat-label i18n>Phone</mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"offer.phone\" required />\n </mat-form-field>\n </div>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n>Email</mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"offer.email\" required />\n </mat-form-field>\n\n <mat-form-field appearance=\"fill\" floatLabel=\"always\">\n <mat-label i18n>Asking price</mat-label>\n <span matTextPrefix>\u00A3&nbsp;</span>\n <input matInput type=\"text\" placeholder=\"0.00\" [(ngModel)]=\"offer.amount\" required />\n <!-- <input\n matInput\n type=\"number\"\n placeholder=\"0.00\"\n [value]=\"formattedAmount\"\n (input)=\"onAmountChange($event)\"\n required\n /> -->\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n>Tenancy Length</mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"offer.tenancyDuration\" required />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n>Number Of Tenants</mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"offer.numberOfTenants\" required />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label>Start date</mat-label>\n <input\n matInput\n placeholder=\"Start date\"\n [matDatepicker]=\"startDatePicker\"\n [min]=\"minDate\"\n (focus)=\"startDatePicker.open()\"\n name=\"startDate\"\n [(ngModel)]=\"offer.startDate\"\n (dateInput)=\"offer.startDate = $event.value.format('YYYY-MM-DD')\"\n readonly\n required\n />\n <mat-datepicker-toggle matIconPrefix [for]=\"startDatePicker\"></mat-datepicker-toggle>\n <mat-datepicker #startDatePicker></mat-datepicker>\n </mat-form-field>\n <mat-form-field>\n <mat-label i18n>Applicant type</mat-label>\n <mat-select placeholder=\"Applicant type\" [(ngModel)]=\"offer.applicantType\" required>\n @for (key of applicantTypes; track key) {\n <mat-option [value]=\"key\">\n {{ PropertyApplicantType[key] }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <!-- subscriptSizing=\"dynamic\" -->\n <mat-form-field>\n <mat-label i18n>Residency status</mat-label>\n <mat-select placeholder=\"Residency status\" [(ngModel)]=\"offer.residencyStatus\" required>\n @for (status of residencyStatus | keyvalue; track status) {\n <mat-option [value]=\"status.key\">\n {{ status.value }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field>\n <mat-label i18n>Employment Status</mat-label>\n <mat-select placeholder=\"Employment Status\" [(ngModel)]=\"offer.employmentStatus\" required>\n @for (item of employmentStatus | keyvalue; track item) {\n <mat-option [value]=\"item.key\">\n {{ item.value }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field>\n <mat-label i18n>AdverseCredit Status</mat-label>\n <mat-select placeholder=\"AdverseCredit Status\" [(ngModel)]=\"offer.adverseCreditStatus\" required>\n @for (item of adverseCreditStatus | keyvalue; track item) {\n <mat-option [value]=\"item.key\">\n {{ item.value }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n <div>\n <div class=\"text-md py-2\" i18n>Your message(Optional)</div>\n <mat-form-field appearance=\"fill\">\n <textarea matInput type=\"text\" [(ngModel)]=\"offer.notes\" cdkTextareaAutosize cdkAutosizeMinRows=\"3\"></textarea>\n </mat-form-field>\n </div>\n <!-- <button mat-flat-button (click)=\"sendRequest()\" i18n>Send request</button> -->\n <button mat-flat-button (click)=\"sendRequest()\" [disabled]=\"sending\">\n <span style=\"display: flex; align-items: center\">\n @if (sending) {\n <mat-progress-spinner diameter=\"20\" mode=\"indeterminate\" [style.marginRight.px]=\"8\"></mat-progress-spinner>\n }\n {{ sending ? 'Requesting...' : 'Send request' }}\n </span>\n </button>\n </div>\n <div class=\"w-full md:w-1/2 py-2 mb-3\">\n <div class=\"text-xl font-bold py-2 mb-3\" i18n>Property info</div>\n <div>\n <rolatech-thumbnail [src]=\"firstImageUrl()\" size=\"small\"></rolatech-thumbnail>\n </div>\n <div class=\"py-3 text-xl font-bold\">\u00A3{{ displayPrice() }}</div>\n </div>\n </div>\n</rolatech-container>\n", styles: ["mat-form-field{width:100%}\n"] }]
1653
+ }], ctorParameters: () => [] });
1654
+
1655
+ class PropertyOfferIndexComponent extends BaseComponent {
1656
+ propertyService = inject(PropertyService);
1657
+ offers;
1658
+ offerType = PropertyOfferType;
1659
+ offerStatus = PropertyOfferStatus;
1660
+ filter = false;
1661
+ filterOptions = {
1662
+ type: '',
1663
+ status: '',
1664
+ };
1665
+ select = 0;
1666
+ links = [
1667
+ {
1668
+ name: 'All',
1669
+ icon: 'dashboard',
1670
+ },
1671
+ {
1672
+ name: 'Pending',
1673
+ icon: 'category',
1674
+ status: 'pending',
1675
+ },
1676
+ {
1677
+ name: 'Accepted',
1678
+ icon: 'category',
1679
+ status: 'accepted',
1680
+ },
1681
+ {
1682
+ name: 'Rejected',
1683
+ icon: 'category',
1684
+ status: 'rejected',
1685
+ },
1686
+ {
1687
+ name: 'Cancelled',
1688
+ icon: 'category',
1689
+ status: 'cancelled',
1690
+ },
1691
+ ];
1692
+ ngOnInit() {
1693
+ this.findOffers();
1694
+ this.route.queryParams.subscribe(({ status }) => {
1695
+ this.select = this.links.findIndex((item) => item.status === status);
1696
+ if (status) {
1697
+ this.filterOptions.status = status.toUpperCase();
1698
+ }
1699
+ else {
1700
+ delete this.filterOptions.status;
1701
+ }
1702
+ this.findOffers();
1703
+ });
1704
+ }
1705
+ findOffers() {
1706
+ const options = {
1707
+ sort: 'updatedAt desc',
1708
+ };
1709
+ const filterString = this.convertFilterOptions(this.filterOptions);
1710
+ if (filterString) {
1711
+ options['filter'] = filterString;
1712
+ }
1713
+ this.propertyService.findOffersByUser(options).subscribe({
1714
+ next: (res) => {
1715
+ this.offers = res.data;
1716
+ },
1717
+ });
1718
+ }
1719
+ findProperties(ids) { }
1720
+ resetFilter() {
1721
+ this.filterOptions = {
1722
+ type: '',
1723
+ };
1724
+ this.filter = false;
1725
+ this.findOffers();
1726
+ }
1727
+ statusCompareFn(t1, t2) {
1728
+ return t1 === t2;
1729
+ }
1730
+ convertFilterOptions(jsonObj) {
1731
+ return Object.entries(jsonObj)
1732
+ .filter(([key, value]) => value !== '' && value !== undefined)
1733
+ .map(([key, value]) => {
1734
+ return `${key}:${value}`;
1735
+ })
1736
+ .join(',');
1737
+ }
1738
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyOfferIndexComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
1739
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: PropertyOfferIndexComponent, isStandalone: true, selector: "rolatech-property-offer-index", usesInheritance: true, ngImport: i0, template: "<rolatech-container>\n <rolatech-toolbar title=\"Offers\" large> </rolatech-toolbar>\n <rolatech-tabs [select]=\"select\">\n @for (item of links; track item) {\n @if (item.status) {\n <rolatech-tab [label]=\"item.name\" routerLink=\"./\" [queryParams]=\"{ status: item.status }\"></rolatech-tab>\n } @else {\n <rolatech-tab [label]=\"item.name\" routerLink=\"./\"></rolatech-tab>\n }\n }\n </rolatech-tabs>\n <rolatech-list>\n @if (offers) {\n @for (item of offers; track item) {\n <rolatech-property-offer-item [routerLink]=\"['./', item.id]\" [offer]=\"item\"></rolatech-property-offer-item>\n }\n } @else {\n <rolatech-empty></rolatech-empty>\n }\n </rolatech-list>\n</rolatech-container>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: ContainerComponent, selector: "rolatech-container" }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$4.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: TabsComponent, selector: "rolatech-tabs", inputs: ["select", "loading"], outputs: ["selectChange"] }, { kind: "component", type: TabComponent, selector: "rolatech-tab", inputs: ["label"] }, { kind: "component", type: ToolbarComponent, selector: "rolatech-toolbar", inputs: ["title", "subtitle", "back", "link", "large", "divider"] }, { kind: "component", type: ListComponent, selector: "rolatech-list" }, { kind: "component", type: EmptyComponent, selector: "rolatech-empty" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "ngmodule", type: MatOptionModule }, { kind: "ngmodule", type: MatInputModule }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: PropertyOfferItemComponent, selector: "rolatech-property-offer-item", inputs: ["offer"] }], animations: [
1740
+ trigger('filter', [
1741
+ state('collapsed', style({ height: '0px', minHeight: '0' })),
1742
+ state('expanded', style({ height: '*' })),
1743
+ transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')),
1744
+ ]),
1745
+ ], encapsulation: i0.ViewEncapsulation.None });
1746
+ }
1747
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyOfferIndexComponent, decorators: [{
1748
+ type: Component,
1749
+ args: [{ selector: 'rolatech-property-offer-index', imports: [
1750
+ CommonModule,
1751
+ ContainerComponent,
1752
+ RouterModule,
1753
+ TabsComponent,
1754
+ TabComponent,
1755
+ ToolbarComponent,
1756
+ ListComponent,
1757
+ EmptyComponent,
1758
+ MatButtonModule,
1759
+ MatIconModule,
1760
+ FormsModule,
1761
+ MatFormFieldModule,
1762
+ MatDatepickerModule,
1763
+ MatOptionModule,
1764
+ MatInputModule,
1765
+ MatSelectModule,
1766
+ MatButtonModule,
1767
+ PropertyOfferItemComponent,
1768
+ ], animations: [
1769
+ trigger('filter', [
1770
+ state('collapsed', style({ height: '0px', minHeight: '0' })),
1771
+ state('expanded', style({ height: '*' })),
1772
+ transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')),
1773
+ ]),
1774
+ ], encapsulation: ViewEncapsulation.None, template: "<rolatech-container>\n <rolatech-toolbar title=\"Offers\" large> </rolatech-toolbar>\n <rolatech-tabs [select]=\"select\">\n @for (item of links; track item) {\n @if (item.status) {\n <rolatech-tab [label]=\"item.name\" routerLink=\"./\" [queryParams]=\"{ status: item.status }\"></rolatech-tab>\n } @else {\n <rolatech-tab [label]=\"item.name\" routerLink=\"./\"></rolatech-tab>\n }\n }\n </rolatech-tabs>\n <rolatech-list>\n @if (offers) {\n @for (item of offers; track item) {\n <rolatech-property-offer-item [routerLink]=\"['./', item.id]\" [offer]=\"item\"></rolatech-property-offer-item>\n }\n } @else {\n <rolatech-empty></rolatech-empty>\n }\n </rolatech-list>\n</rolatech-container>\n" }]
1775
+ }] });
1776
+
1777
+ class PropertyViewingItemComponent {
1778
+ viewing = input.required();
1779
+ status = PropertyViewingStatus;
1780
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyViewingItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1781
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: PropertyViewingItemComponent, isStandalone: true, selector: "rolatech-property-viewing-item", inputs: { viewing: { classPropertyName: "viewing", publicName: "viewing", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div class=\"flex flex-col hover:bg-[--rt-raised-background] cursor-pointer p-3\">\n <div>\n <div class=\"flex justify-between w-full mb-2\">\n <a class=\"flex items-center gap-3\">\n <span>Viewing ID: {{ viewing().id }}</span>\n </a>\n <span class=\"font-medium text-sm\">{{ status[viewing().status] }}</span>\n </div>\n <div class=\"flex flex-col overflow-x-scroll scrollbar-hide\">\n <div class=\"flex flex-row py-2\">\n @if (viewing().property.media) {\n <div class=\"min-w-24 w-36 object-cover aspect-video rounded-lg mr-3\">\n @defer (on viewport()) {\n <rolatech-thumbnail [src]=\"viewing().property.media[0].url\" size=\"medium\" mode=\"full\"> </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=\"min-w-24 w-36 object-cover aspect-video rounded-lg mr-3\">\n <rolatech-image-placeholder></rolatech-image-placeholder>\n </div>\n }\n <div class=\"flex w-full justify-between\">\n <div class=\"flex justify-between w-full\">\n <div class=\"flex flex-col\">\n <div>{{ viewing().property.title }}</div>\n <div class=\"inline-flex gap-1 mt-2\">\n <div>\n <span class=\"mr-1\">{{ viewing().property.bedrooms }}</span>\n <span i18n>Bedrooms</span>\n </div>\n <div>\n <span class=\"mr-1\">{{ viewing().property.bathrooms }}</span>\n <span i18n>Bathrooms</span>\n </div>\n </div>\n </div>\n <div class=\"text-right\">\n <div class=\"text-sm\">\u00A3{{ viewing().property.price | fixed }}</div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: MatIconModule }, { kind: "component", type: ImagePlaceholderComponent, selector: "rolatech-image-placeholder" }, { kind: "pipe", type: FixedPipe, name: "fixed" }], deferBlockDependencies: [() => [ThumbnailComponent]] });
1782
+ }
1783
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyViewingItemComponent, decorators: [{
1784
+ type: Component,
1785
+ args: [{ selector: 'rolatech-property-viewing-item', imports: [MatIconModule, ThumbnailComponent, ImagePlaceholderComponent, FixedPipe], template: "<div class=\"flex flex-col hover:bg-[--rt-raised-background] cursor-pointer p-3\">\n <div>\n <div class=\"flex justify-between w-full mb-2\">\n <a class=\"flex items-center gap-3\">\n <span>Viewing ID: {{ viewing().id }}</span>\n </a>\n <span class=\"font-medium text-sm\">{{ status[viewing().status] }}</span>\n </div>\n <div class=\"flex flex-col overflow-x-scroll scrollbar-hide\">\n <div class=\"flex flex-row py-2\">\n @if (viewing().property.media) {\n <div class=\"min-w-24 w-36 object-cover aspect-video rounded-lg mr-3\">\n @defer (on viewport()) {\n <rolatech-thumbnail [src]=\"viewing().property.media[0].url\" size=\"medium\" mode=\"full\"> </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=\"min-w-24 w-36 object-cover aspect-video rounded-lg mr-3\">\n <rolatech-image-placeholder></rolatech-image-placeholder>\n </div>\n }\n <div class=\"flex w-full justify-between\">\n <div class=\"flex justify-between w-full\">\n <div class=\"flex flex-col\">\n <div>{{ viewing().property.title }}</div>\n <div class=\"inline-flex gap-1 mt-2\">\n <div>\n <span class=\"mr-1\">{{ viewing().property.bedrooms }}</span>\n <span i18n>Bedrooms</span>\n </div>\n <div>\n <span class=\"mr-1\">{{ viewing().property.bathrooms }}</span>\n <span i18n>Bathrooms</span>\n </div>\n </div>\n </div>\n <div class=\"text-right\">\n <div class=\"text-sm\">\u00A3{{ viewing().property.price | fixed }}</div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n</div>\n" }]
1786
+ }] });
1787
+
1788
+ class PropertyViewingIndexComponent extends BaseComponent {
1789
+ propertyService = inject(PropertyService);
1790
+ viewings;
1791
+ viewerCategory = PropertyViewerCategory;
1792
+ tenantCategory = PropertyApplicantType;
1793
+ viewingStatus = PropertyViewingStatus;
1794
+ filter = false;
1795
+ filterOptions = {
1796
+ type: '',
1797
+ category: '',
1798
+ status: '',
1799
+ };
1800
+ select = 0;
1801
+ links = [
1802
+ {
1803
+ name: 'All',
1804
+ icon: 'dashboard',
1805
+ },
1806
+ {
1807
+ name: 'Completed',
1808
+ icon: 'category',
1809
+ status: 'completed',
1810
+ },
1811
+ {
1812
+ name: 'Cancelled',
1813
+ icon: 'category',
1814
+ status: 'cancelled',
1815
+ },
1816
+ ];
1817
+ ngOnInit() {
1818
+ this.route.queryParams.subscribe(({ status }) => {
1819
+ this.select = this.links.findIndex((item) => item.status === status);
1820
+ if (status) {
1821
+ this.filterOptions.status = status.toUpperCase();
1822
+ }
1823
+ else {
1824
+ delete this.filterOptions.status;
1825
+ }
1826
+ this.findViewings();
1827
+ });
1828
+ }
1829
+ findViewings() {
1830
+ const options = {
1831
+ sort: 'updatedAt desc',
1832
+ };
1833
+ const filterString = this.convertFilterOptions(this.filterOptions);
1834
+ if (filterString) {
1835
+ options['filter'] = filterString;
1836
+ }
1837
+ this.propertyService.findViewingsByUser(options).subscribe({
1838
+ next: (res) => {
1839
+ this.viewings = res.data;
1840
+ },
1841
+ });
1842
+ }
1843
+ findProperties(ids) { }
1844
+ resetFilter() {
1845
+ this.filterOptions = {
1846
+ type: '',
1847
+ };
1848
+ this.filter = false;
1849
+ this.findViewings();
1850
+ }
1851
+ statusCompareFn(t1, t2) {
1852
+ return t1 === t2;
1853
+ }
1854
+ convertFilterOptions(jsonObj) {
1855
+ return Object.entries(jsonObj)
1856
+ .filter(([key, value]) => value !== '' && value !== undefined)
1857
+ .map(([key, value]) => {
1858
+ return `${key}:${value}`;
1859
+ })
1860
+ .join(',');
1861
+ }
1862
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyViewingIndexComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
1863
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: PropertyViewingIndexComponent, isStandalone: true, selector: "rolatech-property-viewing-index", usesInheritance: true, ngImport: i0, template: "<rolatech-container>\n <rolatech-toolbar title=\"Viewings\" large> </rolatech-toolbar>\n <rolatech-tabs [select]=\"select\">\n @for (item of links; track item) {\n @if (item.status) {\n <rolatech-tab [label]=\"item.name\" routerLink=\"./\" [queryParams]=\"{ status: item.status }\"></rolatech-tab>\n } @else {\n <rolatech-tab [label]=\"item.name\" routerLink=\"./\"></rolatech-tab>\n }\n }\n </rolatech-tabs>\n <rolatech-list>\n @if (viewings) {\n @for (item of viewings; track item) {\n <rolatech-property-viewing-item [routerLink]=\"['./', item.id]\" [viewing]=\"item\"></rolatech-property-viewing-item>\n }\n } @else {\n <rolatech-empty></rolatech-empty>\n }\n </rolatech-list>\n</rolatech-container>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: ContainerComponent, selector: "rolatech-container" }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$4.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: TabsComponent, selector: "rolatech-tabs", inputs: ["select", "loading"], outputs: ["selectChange"] }, { kind: "component", type: TabComponent, selector: "rolatech-tab", inputs: ["label"] }, { kind: "component", type: ToolbarComponent, selector: "rolatech-toolbar", inputs: ["title", "subtitle", "back", "link", "large", "divider"] }, { kind: "component", type: ListComponent, selector: "rolatech-list" }, { kind: "component", type: EmptyComponent, selector: "rolatech-empty" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "ngmodule", type: MatOptionModule }, { kind: "ngmodule", type: MatInputModule }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: PropertyViewingItemComponent, selector: "rolatech-property-viewing-item", inputs: ["viewing"] }] });
1864
+ }
1865
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyViewingIndexComponent, decorators: [{
1866
+ type: Component,
1867
+ args: [{ selector: 'rolatech-property-viewing-index', imports: [
1868
+ CommonModule,
1869
+ ContainerComponent,
1870
+ RouterModule,
1871
+ TabsComponent,
1872
+ TabComponent,
1873
+ ToolbarComponent,
1874
+ ListComponent,
1875
+ EmptyComponent,
1876
+ MatButtonModule,
1877
+ MatIconModule,
1878
+ FormsModule,
1879
+ MatFormFieldModule,
1880
+ MatDatepickerModule,
1881
+ MatOptionModule,
1882
+ MatInputModule,
1883
+ MatSelectModule,
1884
+ MatButtonModule,
1885
+ PropertyViewingItemComponent,
1886
+ ], template: "<rolatech-container>\n <rolatech-toolbar title=\"Viewings\" large> </rolatech-toolbar>\n <rolatech-tabs [select]=\"select\">\n @for (item of links; track item) {\n @if (item.status) {\n <rolatech-tab [label]=\"item.name\" routerLink=\"./\" [queryParams]=\"{ status: item.status }\"></rolatech-tab>\n } @else {\n <rolatech-tab [label]=\"item.name\" routerLink=\"./\"></rolatech-tab>\n }\n }\n </rolatech-tabs>\n <rolatech-list>\n @if (viewings) {\n @for (item of viewings; track item) {\n <rolatech-property-viewing-item [routerLink]=\"['./', item.id]\" [viewing]=\"item\"></rolatech-property-viewing-item>\n }\n } @else {\n <rolatech-empty></rolatech-empty>\n }\n </rolatech-list>\n</rolatech-container>\n" }]
1887
+ }] });
1888
+
1889
+ class DuePipe {
1890
+ transform(value, ...args) {
1891
+ if (value) {
1892
+ const date = new Date(value);
1893
+ date.setDate(date.getDate() - 3);
1894
+ return date.toISOString().split('T')[0];
1895
+ }
1896
+ else {
1897
+ return '';
1898
+ }
1899
+ }
1900
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: DuePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
1901
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.0.3", ngImport: i0, type: DuePipe, isStandalone: true, name: "due" });
1902
+ }
1903
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: DuePipe, decorators: [{
1904
+ type: Pipe,
1905
+ args: [{
1906
+ name: 'due',
1907
+ }]
1908
+ }] });
1909
+
1910
+ class PropertyOfferDetailComponent extends BaseComponent {
1911
+ propertyService = inject(PropertyService);
1912
+ authUserService = inject(AuthUserService);
1913
+ offer;
1914
+ info = false;
1915
+ loadingTimeline = false;
1916
+ timelineData;
1917
+ status = PropertyOfferStatus;
1918
+ timelineStatus = PropertyOfferTimelineStatus;
1919
+ env = inject(APP_CONFIG);
1920
+ location = inject(Location);
1921
+ property;
1922
+ agent;
1923
+ checkouting = false;
1924
+ holdingDeposit;
1925
+ securityDeposit;
1926
+ async ngOnInit() {
1927
+ this.route.params.subscribe((params) => {
1928
+ const id = params['id'];
1929
+ this.get(id);
1930
+ });
1931
+ this.route.queryParams.subscribe(async (params) => {
1932
+ const sessionId = params['session_id'];
1933
+ if (sessionId) {
1934
+ this.propertyService.checkOfferPaymentStatus(this.id, sessionId).subscribe({
1935
+ next: (res) => {
1936
+ if (res.status === 'paid') {
1937
+ this.offer.status = PropertyOfferStatus.HOLDING_DEPOSIT_PAID;
1938
+ // Show success UI
1939
+ }
1940
+ },
1941
+ });
1942
+ }
1943
+ });
1944
+ }
1945
+ ngOnDestroy() {
1946
+ this.checkouting = false;
1947
+ }
1948
+ get(id) {
1949
+ this.propertyService.getOffer(id).subscribe({
1950
+ next: (res) => {
1951
+ this.offer = res.data;
1952
+ this.getProperty(this.offer.propertyId);
1953
+ this.calculateDeposit();
1954
+ // this.findAgentInfo();
1955
+ },
1956
+ });
1957
+ }
1958
+ timeline() {
1959
+ this.info = true;
1960
+ this.loadingTimeline = true;
1961
+ this.propertyService.offerTimeline(this.id).subscribe({
1962
+ next: (res) => {
1963
+ this.timelineData = res.data;
1964
+ this.loadingTimeline = false;
1965
+ },
1966
+ error: (error) => {
1967
+ this.loadingTimeline = false;
1968
+ },
1969
+ });
1970
+ }
1971
+ getProperty(propertyId) {
1972
+ this.propertyService.get(propertyId).subscribe({
1973
+ next: (res) => {
1974
+ this.property = res.data;
1975
+ this.getAgentPublicInfo(this.property.agentId);
1976
+ },
1977
+ });
1978
+ }
1979
+ getAgentPublicInfo(agentId) {
1980
+ this.authUserService.getPublicUserInfo(agentId).subscribe({
1981
+ next: (res) => {
1982
+ this.agent = res;
1983
+ },
1984
+ });
1985
+ }
1986
+ cancel() {
1987
+ this.propertyService.cancelOffer(this.id).subscribe({
1988
+ next: (res) => {
1989
+ this.offer.status = 'CANCELLED';
1990
+ this.snackBarService.open('Cancelled');
1991
+ },
1992
+ error: (error) => {
1993
+ this.snackBarService.open(error.message);
1994
+ },
1995
+ });
1996
+ }
1997
+ calculateDeposit() {
1998
+ const price = this.offer.item.price;
1999
+ this.holdingDeposit = ((price * 12) / 52 / 100).toFixed(2);
2000
+ this.securityDeposit = (((price * 12) / 52 / 100) * 5).toFixed(2);
2001
+ }
2002
+ holdingDepositCheckout() {
2003
+ this.checkouting = true;
2004
+ const data = {
2005
+ successUrl: window.location.href + '?session_id={CHECKOUT_SESSION_ID}',
2006
+ cancelUrl: window.location.href,
2007
+ };
2008
+ this.propertyService.createHoldingDepositCheckout(this.id, data).subscribe({
2009
+ next: (res) => {
2010
+ window.location.href = res.data.checkoutUrl;
2011
+ },
2012
+ error: (error) => {
2013
+ this.checkouting = false;
2014
+ },
2015
+ });
2016
+ }
2017
+ securityDepositCheckout() {
2018
+ this.checkouting = true;
2019
+ const data = {
2020
+ successUrl: window.location.href + '?session_id={CHECKOUT_SESSION_ID}',
2021
+ cancelUrl: window.location.href,
2022
+ };
2023
+ this.propertyService.createSecurityDepositCheckout(this.id, data).subscribe({
2024
+ next: (res) => {
2025
+ window.location.href = res.data.checkoutUrl;
2026
+ },
2027
+ error: (error) => {
2028
+ this.checkouting = false;
2029
+ },
2030
+ });
2031
+ }
2032
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyOfferDetailComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
2033
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: PropertyOfferDetailComponent, isStandalone: true, selector: "rolatech-property-offer-detail", usesInheritance: true, ngImport: i0, template: "@if (offer) {\n <rolatech-container>\n <rolatech-toolbar [title]=\"status[offer.status]\" large link=\"../\">\n <button mat-flat-button (click)=\"timeline()\">\n <mat-icon>history</mat-icon>\n <span i18n>Offer status</span>\n </button>\n </rolatech-toolbar>\n <div>\n <div class=\"flex justify-between items-center py-2\">\n <a class=\"text-xl font-bold\">Offer ID: {{ offer.id }}</a>\n </div>\n\n <div>\n <div class=\"text-lg font-bold py-2\" i18n>Property details</div>\n <hr class=\"mb-2\" />\n <div\n class=\"flex items-center py-2 cursor-pointer hover:bg-[--rt-raised-background]\"\n [routerLink]=\"['../../', offer.item.propertyId]\"\n >\n <div class=\"min-w-16 w-24 object-cover aspect-video rounded-md mr-3\">\n @defer (on viewport()) {\n <rolatech-thumbnail [src]=\"offer.item.media[0].url\" size=\"medium\" mode=\"full\"> </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 <div class=\"flex w-full justify-between\">\n <div class=\"flex justify-between w-full\">\n <div class=\"flex flex-col\">\n <div>{{ offer.item.propertyTitle }}</div>\n <div class=\"inline-flex gap-1 mt-2\">\n <div>\n <span class=\"mr-1\">{{ offer.item.bedrooms }}</span>\n <span i18n>Bedrooms</span>\n </div>\n <div>\n <span class=\"mr-1\">{{ offer.item.bathrooms }}</span>\n <span i18n>Bathrooms</span>\n </div>\n </div>\n </div>\n <div class=\"text-right\">\n <div class=\"text-sm\">\u00A3{{ offer.item.price | fixed }}</div>\n </div>\n </div>\n </div>\n <div class=\"hidden md:flex flex-col px-3\"></div>\n </div>\n </div>\n <div>\n <div class=\"mt-3\">\n <div class=\"text-lg py-2 font-bold\" i18n>Offer info</div>\n <div class=\"flex items-center justify-between py-1\">\n <span class=\"font-medium\" i18n>Created at</span>\n <span class=\"text-sm\"> {{ offer.createdAt }}</span>\n </div>\n <div class=\"flex items-center justify-between py-1\">\n <span class=\"font-medium\" i18n>Start date</span>\n <span class=\"text-sm\"> {{ offer.startDate }}</span>\n </div>\n <div class=\"flex items-center justify-between py-1\">\n <span class=\"font-medium\" i18n>Asked price</span>\n <span class=\"text-sm\" i18n>\u00A3{{ offer.amount | fixed }}</span>\n </div>\n <div class=\"flex items-center justify-between py-1\">\n <span class=\"font-medium\" i18n>Deposit due date</span>\n <span class=\"text-sm\"> {{ offer.startDate | due }}</span>\n </div>\n <div class=\"flex items-center justify-between py-1\">\n <span class=\"font-medium\" i18n>Holding deposit</span>\n <span class=\"text-sm\">\u00A3{{ holdingDeposit }}</span>\n </div>\n <div class=\"flex items-center justify-between py-1\">\n <span class=\"font-medium\" i18n>Security deposit</span>\n <span class=\"text-sm\">\u00A3{{ securityDeposit }}</span>\n </div>\n <div class=\"flex items-baseline justify-between py-1\">\n <span class=\"font-medium min-w-20\" i18n>Note</span>\n <!-- <span class=\"text-sm\">{{ offer.note || '' }}</span> -->\n </div>\n </div>\n <div id=\"payment-element\"></div>\n @if (offer.status.toString() === 'CREATED' || offer.status.toString() === 'PAID') {\n <div class=\"mt-6\">\n <div class=\"text-lg pb-3 font-medium\" i18n>Payment method</div>\n\n <div class=\"flex items-center\">\n <svg\n class=\"svg-icon\"\n style=\"width: 2rem; height: 2rem\"\n viewBox=\"0 0 1024 1024\"\n version=\"1.1\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M395.846 603.585c-3.921 1.98-7.936 2.925-12.81 2.925-10.9 0-19.791-5.85-24.764-14.625l-2.006-3.864-78.106-167.913c-0.956-1.98-0.956-3.865-0.956-5.845 0-7.83 5.928-13.68 13.863-13.68 2.965 0 5.928 0.944 8.893 2.924l91.965 64.43c6.884 3.864 14.82 6.79 23.708 6.79 4.972 0 9.85-0.945 14.822-2.926L861.71 282.479c-77.149-89.804-204.684-148.384-349.135-148.384-235.371 0-427.242 157.158-427.242 351.294 0 105.368 57.361 201.017 147.323 265.447 6.88 4.905 11.852 13.68 11.852 22.45 0 2.925-0.957 5.85-2.006 8.775-6.881 26.318-18.831 69.334-18.831 71.223-0.958 2.92-2.013 6.79-2.013 10.75 0 7.83 5.929 13.68 13.865 13.68 2.963 0 5.928-0.944 7.935-2.925l92.922-53.674c6.885-3.87 14.82-6.794 22.756-6.794 3.916 0 8.889 0.944 12.81 1.98 43.496 12.644 91.012 19.53 139.48 19.53 235.372 0 427.24-157.158 427.24-351.294 0-58.58-17.78-114.143-48.467-163.003l-491.39 280.07-2.963 1.98z\"\n fill=\"#09BB07\"\n />\n </svg>\n <span class=\"ml-1\" i18n>Stripe</span>\n </div>\n </div>\n }\n </div>\n <!-- safe area -->\n <div class=\"pb-16 sm:pb-3\"></div>\n <div class=\"\">\n <div class=\"flex items-center justify-end\">\n @if (offer.status.toString() === 'PENDING') {\n <span\n class=\"underline text-sm underline-offset-4 mr-6 cursor-pointer hover:text-[--rt-brand-color]\"\n (click)=\"cancel()\"\n i18n\n >Cancel</span\n >\n }\n @if (offer.status.toString() === 'PENDING') {\n <button mat-flat-button class=\"min-h-11\" (click)=\"holdingDepositCheckout()\" [disabled]=\"checkouting\">\n <span style=\"display: flex; align-items: center\">\n @if (checkouting) {\n <mat-progress-spinner diameter=\"20\" mode=\"indeterminate\" [style.marginRight.px]=\"8\"></mat-progress-spinner>\n }\n {{ checkouting ? 'Processing...' : 'Pay holding deposit' }}\n </span>\n </button>\n }\n @if (offer.status.toString() === 'ACCEPTED') {\n <button mat-flat-button class=\"min-h-11\" (click)=\"securityDepositCheckout()\" [disabled]=\"checkouting\">\n <span style=\"display: flex; align-items: center\">\n @if (checkouting) {\n <mat-progress-spinner diameter=\"20\" mode=\"indeterminate\" [style.marginRight.px]=\"8\"></mat-progress-spinner>\n }\n {{ checkouting ? 'Processing...' : 'Pay security deposit' }}\n </span>\n </button>\n }\n <!-- <button mat-flat-button class=\"w-32 min-h-11\" (click)=\"securityDepositCheckout()\" i18n>Pay security deposit</button> -->\n </div>\n </div>\n </div>\n </rolatech-container>\n}\n@if (info) {\n <div\n [ngClass]=\"loadingTimeline ? 'translate-x-full' : 'translate-none'\"\n class=\"fixed top-0 right-0 z-[1001] h-screen p-4 overflow-y-auto transition-transform bg-[--rt-raised-background] w-80 sm:w-[300px] shadow-xl\"\n >\n <div class=\"flex justify-between items-center sm:p-4\">\n <div class=\"text-xl font-bold\" i18n>Status</div>\n <button mat-icon-button (click)=\"info = !info\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n @if (loadingTimeline) {\n <div>\n <rolatech-spinner></rolatech-spinner>\n </div>\n } @else {\n <div class=\"mt-3 p-1 sm:p-4\">\n <ol class=\"relative boffer-l boffer-[--rt-boffer-color]\">\n @for (item of timelineData; track item) {\n <li class=\"mb-8 ml-4\">\n <div\n class=\"absolute w-3 h-3 rounded-full mt-1.5 -left-1.5 boffer boffer-[--rt-raised-background] bg-[--rt-text-primary]\"\n ></div>\n <div class=\"text-md font-bold mb-1\">{{ timelineStatus[item.status] }}</div>\n @if (item.status === 'OFFER_RETURN_REJECTED') {\n <div class=\"text-sm mb-1\">{{ item.return.note }}</div>\n }\n @if (item.status === 'OFFER_RETURN_REQUESTED') {\n <div class=\"text-sm mb-1\">{{ item.return.reason }}</div>\n }\n <div class=\"text-sm text-[--rt-text-secondary]\">{{ item.date }}</div>\n </li>\n }\n </ol>\n </div>\n }\n </div>\n}\n\n<div\n (click)=\"info = !info\"\n [ngClass]=\"info ? 'visible' : 'invisible'\"\n class=\"bg-[--rt-10-percent-layer] fixed inset-0 z-[1000]\"\n></div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.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: i1$1.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: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: SpinnerComponent, selector: "rolatech-spinner", inputs: ["title"] }, { kind: "component", type: ContainerComponent, selector: "rolatech-container" }, { kind: "component", type: ToolbarComponent, selector: "rolatech-toolbar", inputs: ["title", "subtitle", "back", "link", "large", "divider"] }, { kind: "pipe", type: FixedPipe, name: "fixed" }, { kind: "ngmodule", type: MatProgressSpinnerModule }, { kind: "component", type: i3.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "pipe", type: DuePipe, name: "due" }], deferBlockDependencies: [() => [ThumbnailComponent]] });
2034
+ }
2035
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyOfferDetailComponent, decorators: [{
2036
+ type: Component,
2037
+ args: [{ selector: 'rolatech-property-offer-detail', imports: [
2038
+ MatButtonModule,
2039
+ MatIconModule,
2040
+ NgClass,
2041
+ ThumbnailComponent,
2042
+ SpinnerComponent,
2043
+ ContainerComponent,
2044
+ ToolbarComponent,
2045
+ FixedPipe,
2046
+ MatProgressSpinnerModule,
2047
+ RouterLink,
2048
+ DuePipe,
2049
+ ], template: "@if (offer) {\n <rolatech-container>\n <rolatech-toolbar [title]=\"status[offer.status]\" large link=\"../\">\n <button mat-flat-button (click)=\"timeline()\">\n <mat-icon>history</mat-icon>\n <span i18n>Offer status</span>\n </button>\n </rolatech-toolbar>\n <div>\n <div class=\"flex justify-between items-center py-2\">\n <a class=\"text-xl font-bold\">Offer ID: {{ offer.id }}</a>\n </div>\n\n <div>\n <div class=\"text-lg font-bold py-2\" i18n>Property details</div>\n <hr class=\"mb-2\" />\n <div\n class=\"flex items-center py-2 cursor-pointer hover:bg-[--rt-raised-background]\"\n [routerLink]=\"['../../', offer.item.propertyId]\"\n >\n <div class=\"min-w-16 w-24 object-cover aspect-video rounded-md mr-3\">\n @defer (on viewport()) {\n <rolatech-thumbnail [src]=\"offer.item.media[0].url\" size=\"medium\" mode=\"full\"> </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 <div class=\"flex w-full justify-between\">\n <div class=\"flex justify-between w-full\">\n <div class=\"flex flex-col\">\n <div>{{ offer.item.propertyTitle }}</div>\n <div class=\"inline-flex gap-1 mt-2\">\n <div>\n <span class=\"mr-1\">{{ offer.item.bedrooms }}</span>\n <span i18n>Bedrooms</span>\n </div>\n <div>\n <span class=\"mr-1\">{{ offer.item.bathrooms }}</span>\n <span i18n>Bathrooms</span>\n </div>\n </div>\n </div>\n <div class=\"text-right\">\n <div class=\"text-sm\">\u00A3{{ offer.item.price | fixed }}</div>\n </div>\n </div>\n </div>\n <div class=\"hidden md:flex flex-col px-3\"></div>\n </div>\n </div>\n <div>\n <div class=\"mt-3\">\n <div class=\"text-lg py-2 font-bold\" i18n>Offer info</div>\n <div class=\"flex items-center justify-between py-1\">\n <span class=\"font-medium\" i18n>Created at</span>\n <span class=\"text-sm\"> {{ offer.createdAt }}</span>\n </div>\n <div class=\"flex items-center justify-between py-1\">\n <span class=\"font-medium\" i18n>Start date</span>\n <span class=\"text-sm\"> {{ offer.startDate }}</span>\n </div>\n <div class=\"flex items-center justify-between py-1\">\n <span class=\"font-medium\" i18n>Asked price</span>\n <span class=\"text-sm\" i18n>\u00A3{{ offer.amount | fixed }}</span>\n </div>\n <div class=\"flex items-center justify-between py-1\">\n <span class=\"font-medium\" i18n>Deposit due date</span>\n <span class=\"text-sm\"> {{ offer.startDate | due }}</span>\n </div>\n <div class=\"flex items-center justify-between py-1\">\n <span class=\"font-medium\" i18n>Holding deposit</span>\n <span class=\"text-sm\">\u00A3{{ holdingDeposit }}</span>\n </div>\n <div class=\"flex items-center justify-between py-1\">\n <span class=\"font-medium\" i18n>Security deposit</span>\n <span class=\"text-sm\">\u00A3{{ securityDeposit }}</span>\n </div>\n <div class=\"flex items-baseline justify-between py-1\">\n <span class=\"font-medium min-w-20\" i18n>Note</span>\n <!-- <span class=\"text-sm\">{{ offer.note || '' }}</span> -->\n </div>\n </div>\n <div id=\"payment-element\"></div>\n @if (offer.status.toString() === 'CREATED' || offer.status.toString() === 'PAID') {\n <div class=\"mt-6\">\n <div class=\"text-lg pb-3 font-medium\" i18n>Payment method</div>\n\n <div class=\"flex items-center\">\n <svg\n class=\"svg-icon\"\n style=\"width: 2rem; height: 2rem\"\n viewBox=\"0 0 1024 1024\"\n version=\"1.1\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M395.846 603.585c-3.921 1.98-7.936 2.925-12.81 2.925-10.9 0-19.791-5.85-24.764-14.625l-2.006-3.864-78.106-167.913c-0.956-1.98-0.956-3.865-0.956-5.845 0-7.83 5.928-13.68 13.863-13.68 2.965 0 5.928 0.944 8.893 2.924l91.965 64.43c6.884 3.864 14.82 6.79 23.708 6.79 4.972 0 9.85-0.945 14.822-2.926L861.71 282.479c-77.149-89.804-204.684-148.384-349.135-148.384-235.371 0-427.242 157.158-427.242 351.294 0 105.368 57.361 201.017 147.323 265.447 6.88 4.905 11.852 13.68 11.852 22.45 0 2.925-0.957 5.85-2.006 8.775-6.881 26.318-18.831 69.334-18.831 71.223-0.958 2.92-2.013 6.79-2.013 10.75 0 7.83 5.929 13.68 13.865 13.68 2.963 0 5.928-0.944 7.935-2.925l92.922-53.674c6.885-3.87 14.82-6.794 22.756-6.794 3.916 0 8.889 0.944 12.81 1.98 43.496 12.644 91.012 19.53 139.48 19.53 235.372 0 427.24-157.158 427.24-351.294 0-58.58-17.78-114.143-48.467-163.003l-491.39 280.07-2.963 1.98z\"\n fill=\"#09BB07\"\n />\n </svg>\n <span class=\"ml-1\" i18n>Stripe</span>\n </div>\n </div>\n }\n </div>\n <!-- safe area -->\n <div class=\"pb-16 sm:pb-3\"></div>\n <div class=\"\">\n <div class=\"flex items-center justify-end\">\n @if (offer.status.toString() === 'PENDING') {\n <span\n class=\"underline text-sm underline-offset-4 mr-6 cursor-pointer hover:text-[--rt-brand-color]\"\n (click)=\"cancel()\"\n i18n\n >Cancel</span\n >\n }\n @if (offer.status.toString() === 'PENDING') {\n <button mat-flat-button class=\"min-h-11\" (click)=\"holdingDepositCheckout()\" [disabled]=\"checkouting\">\n <span style=\"display: flex; align-items: center\">\n @if (checkouting) {\n <mat-progress-spinner diameter=\"20\" mode=\"indeterminate\" [style.marginRight.px]=\"8\"></mat-progress-spinner>\n }\n {{ checkouting ? 'Processing...' : 'Pay holding deposit' }}\n </span>\n </button>\n }\n @if (offer.status.toString() === 'ACCEPTED') {\n <button mat-flat-button class=\"min-h-11\" (click)=\"securityDepositCheckout()\" [disabled]=\"checkouting\">\n <span style=\"display: flex; align-items: center\">\n @if (checkouting) {\n <mat-progress-spinner diameter=\"20\" mode=\"indeterminate\" [style.marginRight.px]=\"8\"></mat-progress-spinner>\n }\n {{ checkouting ? 'Processing...' : 'Pay security deposit' }}\n </span>\n </button>\n }\n <!-- <button mat-flat-button class=\"w-32 min-h-11\" (click)=\"securityDepositCheckout()\" i18n>Pay security deposit</button> -->\n </div>\n </div>\n </div>\n </rolatech-container>\n}\n@if (info) {\n <div\n [ngClass]=\"loadingTimeline ? 'translate-x-full' : 'translate-none'\"\n class=\"fixed top-0 right-0 z-[1001] h-screen p-4 overflow-y-auto transition-transform bg-[--rt-raised-background] w-80 sm:w-[300px] shadow-xl\"\n >\n <div class=\"flex justify-between items-center sm:p-4\">\n <div class=\"text-xl font-bold\" i18n>Status</div>\n <button mat-icon-button (click)=\"info = !info\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n @if (loadingTimeline) {\n <div>\n <rolatech-spinner></rolatech-spinner>\n </div>\n } @else {\n <div class=\"mt-3 p-1 sm:p-4\">\n <ol class=\"relative boffer-l boffer-[--rt-boffer-color]\">\n @for (item of timelineData; track item) {\n <li class=\"mb-8 ml-4\">\n <div\n class=\"absolute w-3 h-3 rounded-full mt-1.5 -left-1.5 boffer boffer-[--rt-raised-background] bg-[--rt-text-primary]\"\n ></div>\n <div class=\"text-md font-bold mb-1\">{{ timelineStatus[item.status] }}</div>\n @if (item.status === 'OFFER_RETURN_REJECTED') {\n <div class=\"text-sm mb-1\">{{ item.return.note }}</div>\n }\n @if (item.status === 'OFFER_RETURN_REQUESTED') {\n <div class=\"text-sm mb-1\">{{ item.return.reason }}</div>\n }\n <div class=\"text-sm text-[--rt-text-secondary]\">{{ item.date }}</div>\n </li>\n }\n </ol>\n </div>\n }\n </div>\n}\n\n<div\n (click)=\"info = !info\"\n [ngClass]=\"info ? 'visible' : 'invisible'\"\n class=\"bg-[--rt-10-percent-layer] fixed inset-0 z-[1000]\"\n></div>\n" }]
2050
+ }] });
2051
+
2052
+ class PropertyViewingDetailComponent extends BaseComponent {
2053
+ propertyService = inject(PropertyService);
2054
+ authUserService = inject(AuthUserService);
2055
+ agent;
2056
+ viewing;
2057
+ info = false;
2058
+ loadingTimeline = false;
2059
+ status = PropertyViewingStatus;
2060
+ env = inject(APP_CONFIG);
2061
+ property;
2062
+ async ngOnInit() {
2063
+ this.route.params.subscribe((params) => {
2064
+ const id = params['id'];
2065
+ this.get(id);
2066
+ });
2067
+ }
2068
+ get(id) {
2069
+ this.propertyService.getViewing(id).subscribe({
2070
+ next: (res) => {
2071
+ this.viewing = res.data;
2072
+ this.getProperty(this.viewing.propertyId);
2073
+ // this.findAgentInfo();
2074
+ },
2075
+ });
2076
+ }
2077
+ getProperty(propertyId) {
2078
+ this.propertyService.get(propertyId).subscribe({
2079
+ next: (res) => {
2080
+ this.property = res.data;
2081
+ this.getAgentPublicInfo(this.property.agentId);
2082
+ },
2083
+ });
2084
+ }
2085
+ getAgentPublicInfo(agentId) {
2086
+ this.authUserService.getPublicUserInfo(agentId).subscribe({
2087
+ next: (res) => {
2088
+ this.agent = res;
2089
+ },
2090
+ });
2091
+ }
2092
+ cancel() {
2093
+ this.propertyService.cancelViewing(this.id).subscribe({
2094
+ next: (res) => {
2095
+ this.viewing.status = 'CANCELLED';
2096
+ this.snackBarService.open('Cancelled');
2097
+ },
2098
+ error: (error) => {
2099
+ this.snackBarService.open(error.message);
2100
+ },
2101
+ });
2102
+ }
2103
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyViewingDetailComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
2104
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: PropertyViewingDetailComponent, isStandalone: true, selector: "rolatech-property-viewing-detail", usesInheritance: true, ngImport: i0, template: "@if (viewing) {\n <rolatech-container>\n <rolatech-toolbar [title]=\"status[viewing.status]\" large link=\"../\"> </rolatech-toolbar>\n <div>\n <div>\n <div class=\"text-lg font-bold py-2\" i18n>Property details</div>\n <hr class=\"mb-2\" />\n @if (property) {\n <div\n class=\"flex items-center py-2 cursor-pointer hover:bg-[--rt-raised-background]\"\n [routerLink]=\"['../../', property.id]\"\n >\n <div class=\"min-w-16 w-24 object-cover aspect-video rounded-md mr-3\">\n @defer (on viewport()) {\n <rolatech-thumbnail [src]=\"property.media[0].url\" size=\"medium\" mode=\"full\"> </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 <div class=\"flex w-full justify-between\">\n <div class=\"flex justify-between w-full\">\n <div class=\"flex flex-col\">\n <div>{{ property.title }}</div>\n <div class=\"inline-flex gap-1 mt-2\">\n <div>\n <span class=\"mr-1\">{{ property.bedrooms }}</span>\n <span i18n>Bedrooms</span>\n </div>\n <div>\n <span class=\"mr-1\">{{ property.bathrooms }}</span>\n <span i18n>Bathrooms</span>\n </div>\n </div>\n </div>\n <div class=\"text-right\">\n <div class=\"text-sm\">\u00A3{{ property.price | fixed }}</div>\n </div>\n </div>\n </div>\n <div class=\"hidden md:flex flex-col px-3\"></div>\n </div>\n }\n </div>\n\n <div>\n <div class=\"text-lg font-bold py-2\" i18n>Proposed times</div>\n <hr class=\"mb-2\" />\n @for (item of viewing.proposedSlots; track $index) {\n <div class=\"flex flex-row items-center gap-3 py-3\">\n <rolatech-rich-label label=\"Date\" [title]=\"item.date\"></rolatech-rich-label>\n <rolatech-rich-label label=\"Time\" [title]=\"item.time\"></rolatech-rich-label>\n @if (viewing.viewingDate && viewing.viewingTime) {\n @if (item.date === viewing.viewingDate && item.time === viewing.viewingTime) {\n <div class=\"ml-3\"><button mat-flat-button i18n disabled=\"\">Confirmed</button></div>\n }\n }\n </div>\n }\n </div>\n <!-- safe area -->\n <div class=\"pb-16 sm:pb-3\"></div>\n <div class=\"\">\n <div class=\"flex items-center justify-end\">\n @if (viewing.status.toString() !== 'CANCELLED') {\n <button mat-flat-button class=\"min-h-11\" (click)=\"cancel()\" i18n>Cancel</button>\n }\n </div>\n </div>\n </div>\n </rolatech-container>\n}\n@if (info) {\n <div\n [ngClass]=\"loadingTimeline ? 'translate-x-full' : 'translate-none'\"\n class=\"fixed top-0 right-0 z-[1001] h-screen p-4 overflow-y-auto transition-transform bg-[--rt-raised-background] w-80 sm:w-[300px] shadow-xl\"\n >\n <div class=\"flex justify-between items-center sm:p-4\">\n <div class=\"text-xl font-bold\" i18n>Status</div>\n <button mat-icon-button (click)=\"info = !info\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n}\n\n<div\n (click)=\"info = !info\"\n [ngClass]=\"info ? 'visible' : 'invisible'\"\n class=\"bg-[--rt-10-percent-layer] fixed inset-0 z-[1000]\"\n></div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.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: i1$1.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: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: RichLabelComponent, selector: "rolatech-rich-label", inputs: ["label", "title"] }, { kind: "component", type: ContainerComponent, selector: "rolatech-container" }, { kind: "component", type: ToolbarComponent, selector: "rolatech-toolbar", inputs: ["title", "subtitle", "back", "link", "large", "divider"] }, { kind: "pipe", type: FixedPipe, name: "fixed" }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }], deferBlockDependencies: [() => [ThumbnailComponent]] });
2105
+ }
2106
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyViewingDetailComponent, decorators: [{
2107
+ type: Component,
2108
+ args: [{ selector: 'rolatech-property-viewing-detail', imports: [
2109
+ MatButtonModule,
2110
+ MatIconModule,
2111
+ NgClass,
2112
+ ThumbnailComponent,
2113
+ RichLabelComponent,
2114
+ ContainerComponent,
2115
+ ToolbarComponent,
2116
+ FixedPipe,
2117
+ RouterLink,
2118
+ ], template: "@if (viewing) {\n <rolatech-container>\n <rolatech-toolbar [title]=\"status[viewing.status]\" large link=\"../\"> </rolatech-toolbar>\n <div>\n <div>\n <div class=\"text-lg font-bold py-2\" i18n>Property details</div>\n <hr class=\"mb-2\" />\n @if (property) {\n <div\n class=\"flex items-center py-2 cursor-pointer hover:bg-[--rt-raised-background]\"\n [routerLink]=\"['../../', property.id]\"\n >\n <div class=\"min-w-16 w-24 object-cover aspect-video rounded-md mr-3\">\n @defer (on viewport()) {\n <rolatech-thumbnail [src]=\"property.media[0].url\" size=\"medium\" mode=\"full\"> </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 <div class=\"flex w-full justify-between\">\n <div class=\"flex justify-between w-full\">\n <div class=\"flex flex-col\">\n <div>{{ property.title }}</div>\n <div class=\"inline-flex gap-1 mt-2\">\n <div>\n <span class=\"mr-1\">{{ property.bedrooms }}</span>\n <span i18n>Bedrooms</span>\n </div>\n <div>\n <span class=\"mr-1\">{{ property.bathrooms }}</span>\n <span i18n>Bathrooms</span>\n </div>\n </div>\n </div>\n <div class=\"text-right\">\n <div class=\"text-sm\">\u00A3{{ property.price | fixed }}</div>\n </div>\n </div>\n </div>\n <div class=\"hidden md:flex flex-col px-3\"></div>\n </div>\n }\n </div>\n\n <div>\n <div class=\"text-lg font-bold py-2\" i18n>Proposed times</div>\n <hr class=\"mb-2\" />\n @for (item of viewing.proposedSlots; track $index) {\n <div class=\"flex flex-row items-center gap-3 py-3\">\n <rolatech-rich-label label=\"Date\" [title]=\"item.date\"></rolatech-rich-label>\n <rolatech-rich-label label=\"Time\" [title]=\"item.time\"></rolatech-rich-label>\n @if (viewing.viewingDate && viewing.viewingTime) {\n @if (item.date === viewing.viewingDate && item.time === viewing.viewingTime) {\n <div class=\"ml-3\"><button mat-flat-button i18n disabled=\"\">Confirmed</button></div>\n }\n }\n </div>\n }\n </div>\n <!-- safe area -->\n <div class=\"pb-16 sm:pb-3\"></div>\n <div class=\"\">\n <div class=\"flex items-center justify-end\">\n @if (viewing.status.toString() !== 'CANCELLED') {\n <button mat-flat-button class=\"min-h-11\" (click)=\"cancel()\" i18n>Cancel</button>\n }\n </div>\n </div>\n </div>\n </rolatech-container>\n}\n@if (info) {\n <div\n [ngClass]=\"loadingTimeline ? 'translate-x-full' : 'translate-none'\"\n class=\"fixed top-0 right-0 z-[1001] h-screen p-4 overflow-y-auto transition-transform bg-[--rt-raised-background] w-80 sm:w-[300px] shadow-xl\"\n >\n <div class=\"flex justify-between items-center sm:p-4\">\n <div class=\"text-xl font-bold\" i18n>Status</div>\n <button mat-icon-button (click)=\"info = !info\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n}\n\n<div\n (click)=\"info = !info\"\n [ngClass]=\"info ? 'visible' : 'invisible'\"\n class=\"bg-[--rt-10-percent-layer] fixed inset-0 z-[1000]\"\n></div>\n" }]
2119
+ }] });
2120
+
2121
+ class PropertyWishlistComponent extends BaseComponent {
2122
+ propertyService = inject(PropertyService);
2123
+ wishlist;
2124
+ ngOnInit() {
2125
+ this.findWishlist();
2126
+ }
2127
+ findWishlist() {
2128
+ this.propertyService.findWishlist({}).subscribe({
2129
+ next: (res) => {
2130
+ this.wishlist = res.data;
2131
+ },
2132
+ });
2133
+ }
2134
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyWishlistComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
2135
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: PropertyWishlistComponent, isStandalone: true, selector: "rolatech-property-wishlist", usesInheritance: true, ngImport: i0, template: "<rolatech-container>\n <rolatech-toolbar title=\"Wishlist\" large> </rolatech-toolbar>\n <rolatech-list>\n @if (wishlist) {\n @for (item of wishlist; track item) {\n <rolatech-property-item [routerLink]=\"['../', item.id]\" [property]=\"item\"></rolatech-property-item>\n }\n } @else {\n <rolatech-empty></rolatech-empty>\n }\n </rolatech-list>\n</rolatech-container>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: ContainerComponent, selector: "rolatech-container" }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$4.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: ToolbarComponent, selector: "rolatech-toolbar", inputs: ["title", "subtitle", "back", "link", "large", "divider"] }, { kind: "component", type: ListComponent, selector: "rolatech-list" }, { kind: "component", type: EmptyComponent, selector: "rolatech-empty" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "ngmodule", type: MatOptionModule }, { kind: "ngmodule", type: MatInputModule }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: PropertyItemComponent, selector: "rolatech-property-item", inputs: ["property"] }] });
2136
+ }
2137
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyWishlistComponent, decorators: [{
2138
+ type: Component,
2139
+ args: [{ selector: 'rolatech-property-wishlist', imports: [
2140
+ CommonModule,
2141
+ ContainerComponent,
2142
+ RouterModule,
2143
+ ToolbarComponent,
2144
+ ListComponent,
2145
+ EmptyComponent,
2146
+ MatButtonModule,
2147
+ MatIconModule,
2148
+ FormsModule,
2149
+ MatFormFieldModule,
2150
+ MatDatepickerModule,
2151
+ MatOptionModule,
2152
+ MatInputModule,
2153
+ MatSelectModule,
2154
+ MatButtonModule,
2155
+ PropertyItemComponent,
2156
+ ], template: "<rolatech-container>\n <rolatech-toolbar title=\"Wishlist\" large> </rolatech-toolbar>\n <rolatech-list>\n @if (wishlist) {\n @for (item of wishlist; track item) {\n <rolatech-property-item [routerLink]=\"['../', item.id]\" [property]=\"item\"></rolatech-property-item>\n }\n } @else {\n <rolatech-empty></rolatech-empty>\n }\n </rolatech-list>\n</rolatech-container>\n" }]
2157
+ }] });
2158
+
2159
+ const propertyRoutes = [
2160
+ {
2161
+ path: '',
2162
+ component: PropertyLayoutComponent,
2163
+ children: [
2164
+ {
2165
+ path: '',
2166
+ loadComponent: () => import('./rolatech-angular-property-property-index.component-C-eBtys3.mjs').then((x) => x.PropertyIndexComponent),
2167
+ },
2168
+ ],
2169
+ },
2170
+ {
2171
+ path: 'wishlist',
2172
+ canActivate: [AuthGuard],
2173
+ component: PropertyWishlistComponent,
2174
+ },
2175
+ {
2176
+ path: 'viewings',
2177
+ canActivate: [AuthGuard],
2178
+ component: PropertyViewingIndexComponent,
2179
+ },
2180
+ {
2181
+ path: 'offers',
2182
+ canActivate: [AuthGuard],
2183
+ component: PropertyOfferIndexComponent,
2184
+ },
2185
+ {
2186
+ path: 'viewings/:id',
2187
+ canActivate: [AuthGuard],
2188
+ component: PropertyViewingDetailComponent,
2189
+ },
2190
+ {
2191
+ path: 'offers/:id',
2192
+ canActivate: [AuthGuard],
2193
+ component: PropertyOfferDetailComponent,
2194
+ },
2195
+ {
2196
+ path: ':id/viewing/application',
2197
+ canActivate: [AuthGuard],
2198
+ component: PropertyViewingRequestComponent,
2199
+ },
2200
+ {
2201
+ path: ':id/offer',
2202
+ canActivate: [AuthGuard],
2203
+ component: PropertyOfferComponent,
2204
+ },
2205
+ {
2206
+ path: ':id',
2207
+ component: PropertyDetailsComponent,
2208
+ },
2209
+ ];
2210
+
2211
+ class PropertyManageFilterComponent {
2212
+ towns = ['London'];
2213
+ filter = false;
2214
+ searchOptions = {
2215
+ town: '',
2216
+ startDate: '',
2217
+ endDate: '',
2218
+ };
2219
+ reset() { }
2220
+ search() { }
2221
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyManageFilterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2222
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: PropertyManageFilterComponent, isStandalone: true, selector: "rolatech-property-manage-filter", ngImport: i0, template: "<div class=\"p-3\">\n <div class=\"bg-[--rt-raised-background] p-3 flex items-center justify-between rounded-md\">\n <div class=\"flex items-center\">\n <mat-form-field subscriptSizing=\"dynamic\">\n <mat-select #select=\"matSelect\" placeholder=\"Town\" i18n-placeholder [(ngModel)]=\"searchOptions.town\">\n @for (town of towns; track town) {\n <mat-option [value]=\"town\">\n {{ town }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <!-- <mat-form-field>\n <input />\n </mat-form-field> -->\n </div>\n <div class=\"flex gap-3\">\n <a mat-stroked-button class=\"w-[128px]\" (click)=\"reset()\" i18n>Reset</a>\n <a mat-flat-button class=\"w-full\" (click)=\"search()\" i18n>Search</a>\n </div>\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.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: MatFormFieldModule }, { kind: "component", type: i2$2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i4$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatOptionModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
2223
+ }
2224
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyManageFilterComponent, decorators: [{
2225
+ type: Component,
2226
+ args: [{ selector: 'rolatech-property-manage-filter', imports: [CommonModule, MatButtonModule, MatFormFieldModule, MatSelectModule, MatOptionModule, FormsModule], template: "<div class=\"p-3\">\n <div class=\"bg-[--rt-raised-background] p-3 flex items-center justify-between rounded-md\">\n <div class=\"flex items-center\">\n <mat-form-field subscriptSizing=\"dynamic\">\n <mat-select #select=\"matSelect\" placeholder=\"Town\" i18n-placeholder [(ngModel)]=\"searchOptions.town\">\n @for (town of towns; track town) {\n <mat-option [value]=\"town\">\n {{ town }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <!-- <mat-form-field>\n <input />\n </mat-form-field> -->\n </div>\n <div class=\"flex gap-3\">\n <a mat-stroked-button class=\"w-[128px]\" (click)=\"reset()\" i18n>Reset</a>\n <a mat-flat-button class=\"w-full\" (click)=\"search()\" i18n>Search</a>\n </div>\n </div>\n</div>\n" }]
2227
+ }] });
2228
+
2229
+ class PropertyManageIndexComponent {
2230
+ hasClass = true;
2231
+ paginator = viewChild(MatPaginator);
2232
+ dialog = inject(MatDialog);
2233
+ snackBar = inject(MatSnackBar);
2234
+ propertyService = inject(PropertyService);
2235
+ isLoading = false;
2236
+ isSearch = false;
2237
+ properties = [];
2238
+ ngOnInit() {
2239
+ this.findProperties();
2240
+ }
2241
+ findProperties() {
2242
+ this.propertyService.find({}).subscribe({
2243
+ next: (res) => {
2244
+ this.properties = res.data;
2245
+ },
2246
+ });
2247
+ }
2248
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyManageIndexComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2249
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: PropertyManageIndexComponent, isStandalone: true, selector: "rolatech-property-manage-index", host: { properties: { "class.rolatech-property-manage-index": "this.hasClass" } }, viewQueries: [{ propertyName: "paginator", first: true, predicate: MatPaginator, descendants: true, isSignal: true }], ngImport: i0, template: "<rolatech-toolbar title=\"Properties\">\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 property</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-property-manage-filter></rolatech-property-manage-filter>\n <div class=\"flex flex-wrap p-3 gap-3\">\n @for (item of properties; track $index) {\n <rolatech-property-manage-item\n [property]=\"item\"\n routerLink=\"./{{ item.id }}/manage/info\"\n ></rolatech-property-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-property-manage-index{--rt-property-view-items-per-row: 1}@media (min-width: 600px){rolatech-property-manage-index{--rt-property-view-items-per-row: 2}}@media (min-width: 768px){rolatech-property-manage-index{--rt-property-view-items-per-row: 3}}@media (min-width: 1280px){rolatech-property-manage-index{--rt-property-view-items-per-row: 3}}@media (min-width: 1536px){rolatech-property-manage-index{--rt-property-view-items-per-row: 4}}@media (min-width: 1920px){rolatech-property-manage-index{--rt-property-view-items-per-row: 5}}\n"], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.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: PropertyManageItemComponent, selector: "rolatech-property-manage-item", inputs: ["property", "thumbnail", "list"] }, { kind: "component", type: PropertyManageFilterComponent, selector: "rolatech-property-manage-filter" }], encapsulation: i0.ViewEncapsulation.None });
2250
+ }
2251
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyManageIndexComponent, decorators: [{
2252
+ type: Component,
2253
+ args: [{ selector: 'rolatech-property-manage-index', imports: [
2254
+ MatButtonModule,
2255
+ RouterLink,
2256
+ MatIconModule,
2257
+ MatTableModule,
2258
+ MatMenuModule,
2259
+ MatPaginatorModule,
2260
+ ToolbarComponent,
2261
+ SpinnerComponent,
2262
+ PropertyManageItemComponent,
2263
+ PropertyManageFilterComponent,
2264
+ ], encapsulation: ViewEncapsulation.None, template: "<rolatech-toolbar title=\"Properties\">\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 property</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-property-manage-filter></rolatech-property-manage-filter>\n <div class=\"flex flex-wrap p-3 gap-3\">\n @for (item of properties; track $index) {\n <rolatech-property-manage-item\n [property]=\"item\"\n routerLink=\"./{{ item.id }}/manage/info\"\n ></rolatech-property-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-property-manage-index{--rt-property-view-items-per-row: 1}@media (min-width: 600px){rolatech-property-manage-index{--rt-property-view-items-per-row: 2}}@media (min-width: 768px){rolatech-property-manage-index{--rt-property-view-items-per-row: 3}}@media (min-width: 1280px){rolatech-property-manage-index{--rt-property-view-items-per-row: 3}}@media (min-width: 1536px){rolatech-property-manage-index{--rt-property-view-items-per-row: 4}}@media (min-width: 1920px){rolatech-property-manage-index{--rt-property-view-items-per-row: 5}}\n"] }]
2265
+ }], propDecorators: { hasClass: [{
2266
+ type: HostBinding,
2267
+ args: ['class.rolatech-property-manage-index']
2268
+ }] } });
2269
+
2270
+ class PropertyManageCreateComponent {
2271
+ propertyService = inject(PropertyService);
2272
+ router = inject(Router);
2273
+ route = inject(ActivatedRoute);
2274
+ snackBar = inject(MatSnackBar);
2275
+ property = {};
2276
+ create() {
2277
+ const data = {
2278
+ ...this.property,
2279
+ };
2280
+ this.propertyService.create(data).subscribe({
2281
+ next: (res) => {
2282
+ this.router.navigate([`../${res.data.id}/manage/info`], {
2283
+ relativeTo: this.route,
2284
+ });
2285
+ },
2286
+ error: (e) => {
2287
+ this.snackBar.open(e.message);
2288
+ },
2289
+ });
2290
+ }
2291
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyManageCreateComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2292
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.0.3", type: PropertyManageCreateComponent, isStandalone: true, selector: "rolatech-property-manage-create", ngImport: i0, template: "<rolatech-toolbar title=\"Add property\" 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)]=\"property.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)]=\"property.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$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$3.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$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$3.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1$3.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2$2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2$2.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i4.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$3.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: i1$1.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"] }] });
2293
+ }
2294
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyManageCreateComponent, decorators: [{
2295
+ type: Component,
2296
+ args: [{ selector: 'rolatech-property-manage-create', imports: [
2297
+ ToolbarComponent,
2298
+ FormsModule,
2299
+ MatFormFieldModule,
2300
+ MatInputModule,
2301
+ TextFieldModule,
2302
+ MatSelectModule,
2303
+ MatOptionModule,
2304
+ MatButtonModule,
2305
+ ], template: "<rolatech-toolbar title=\"Add property\" 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)]=\"property.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)]=\"property.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" }]
2306
+ }] });
2307
+
2308
+ class PropertyManageLayoutComponent extends BaseComponent {
2309
+ property;
2310
+ submitted = false;
2311
+ accepted = false;
2312
+ isDraft = false;
2313
+ propertyService = inject(PropertyService);
2314
+ ngOnInit() {
2315
+ this.id = this.route.snapshot.paramMap.get('id');
2316
+ this.get();
2317
+ }
2318
+ get() {
2319
+ this.propertyService.get(this.id).subscribe({
2320
+ next: (res) => {
2321
+ this.property = res.data;
2322
+ },
2323
+ });
2324
+ }
2325
+ submit() {
2326
+ this.propertyService.submitForReview(this.id).subscribe({
2327
+ next: (res) => {
2328
+ this.snackBarService.open('Submit successfully');
2329
+ this.property.status = PropertyStatus['Pending'];
2330
+ this.updateStatus();
2331
+ },
2332
+ error: (error) => {
2333
+ this.snackBarService.open(error.message);
2334
+ },
2335
+ });
2336
+ }
2337
+ updateStatus() {
2338
+ this.isDraft = this.property.status.toString() === 'DRAFT' || this.property.status.toString() === 'PENDING';
2339
+ this.accepted = this.property.status.toString() === 'ACCEPTED';
2340
+ }
2341
+ publish() {
2342
+ this.propertyService.publish(this.id).subscribe({
2343
+ next: (res) => {
2344
+ this.snackBarService.open('Publish successfully');
2345
+ this.property.status = PropertyStatus['Available'];
2346
+ this.updateStatus();
2347
+ },
2348
+ error: (error) => {
2349
+ this.snackBarService.open(error.message);
2350
+ },
2351
+ });
2352
+ }
2353
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyManageLayoutComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
2354
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: PropertyManageLayoutComponent, isStandalone: true, selector: "rolatech-property-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>Property</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=\"./video-tour\" routerLinkActive=\"manage-active\" class=\"p-2\" i18n>Video tour</a>\n <a routerLink=\"./sections\" routerLinkActive=\"manage-active\" class=\"p-2\" i18n>Details</a>\n <a routerLink=\"./location\" routerLinkActive=\"manage-active\" class=\"p-2\" i18n>Location</a>\n <a routerLink=\"./features\" routerLinkActive=\"manage-active\" class=\"p-2\" i18n>Features</a>\n <!-- <a routerLink=\"./amenities\" routerLinkActive=\"manage-active\" class=\"p-2\" i18n>Amenities</a> -->\n <!-- <a routerLink=\"./tags\" routerLinkActive=\"manage-active\" class=\"p-2\" i18n>Tags</a> -->\n <!-- <a routerLink=\"./options\" routerLinkActive=\"manage-active\" class=\"p-2\" i18n>Options</a> -->\n </div>\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-2\" i18n>Settings</div>\n <!-- <a routerLink=\"./variants\" routerLinkActive=\"manage-active\" class=\"p-2\" i18n>Variants</a> -->\n <a routerLink=\"./schedule\" routerLinkActive=\"manage-active\" class=\"p-2\" i18n>Schedule</a>\n <a routerLink=\"./pricing\" routerLinkActive=\"manage-active\" class=\"p-2\" i18n>Pricing</a>\n </div>\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-2\" i18n>Others</div>\n </div> -->\n @if (property) {\n @if (property.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 (property.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 (property.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: i1$1.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"] }], encapsulation: i0.ViewEncapsulation.None });
2355
+ }
2356
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyManageLayoutComponent, decorators: [{
2357
+ type: Component,
2358
+ args: [{ selector: 'rolatech-property-manage-layout', imports: [RouterLink, RouterLinkActive, MatButtonModule, RouterOutlet], encapsulation: ViewEncapsulation.None, 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>Property</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=\"./video-tour\" routerLinkActive=\"manage-active\" class=\"p-2\" i18n>Video tour</a>\n <a routerLink=\"./sections\" routerLinkActive=\"manage-active\" class=\"p-2\" i18n>Details</a>\n <a routerLink=\"./location\" routerLinkActive=\"manage-active\" class=\"p-2\" i18n>Location</a>\n <a routerLink=\"./features\" routerLinkActive=\"manage-active\" class=\"p-2\" i18n>Features</a>\n <!-- <a routerLink=\"./amenities\" routerLinkActive=\"manage-active\" class=\"p-2\" i18n>Amenities</a> -->\n <!-- <a routerLink=\"./tags\" routerLinkActive=\"manage-active\" class=\"p-2\" i18n>Tags</a> -->\n <!-- <a routerLink=\"./options\" routerLinkActive=\"manage-active\" class=\"p-2\" i18n>Options</a> -->\n </div>\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-2\" i18n>Settings</div>\n <!-- <a routerLink=\"./variants\" routerLinkActive=\"manage-active\" class=\"p-2\" i18n>Variants</a> -->\n <a routerLink=\"./schedule\" routerLinkActive=\"manage-active\" class=\"p-2\" i18n>Schedule</a>\n <a routerLink=\"./pricing\" routerLinkActive=\"manage-active\" class=\"p-2\" i18n>Pricing</a>\n </div>\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-2\" i18n>Others</div>\n </div> -->\n @if (property) {\n @if (property.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 (property.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 (property.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"] }]
2359
+ }] });
2360
+
2361
+ class PropertyManageContentComponent {
2362
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyManageContentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2363
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.0.3", type: PropertyManageContentComponent, isStandalone: true, selector: "rolatech-property-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 }] });
2364
+ }
2365
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyManageContentComponent, decorators: [{
2366
+ type: Component,
2367
+ args: [{ selector: 'rolatech-property-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" }]
2368
+ }] });
2369
+
2370
+ const MY_FORMATS = {
2371
+ parse: {
2372
+ dateInput: 'YYYY-MM-DD',
2373
+ },
2374
+ display: {
2375
+ dateInput: 'YYYY-MM-DD',
2376
+ monthYearLabel: 'MMM YYYY',
2377
+ dateA11yLabel: 'YYYY-MM-DD',
2378
+ monthYearA11yLabel: 'MMMM YYYY',
2379
+ },
2380
+ };
2381
+ class PropertyManageInfoComponent extends BaseComponent {
2382
+ propertyService = inject(PropertyService);
2383
+ property;
2384
+ status = PropertyStatus;
2385
+ selectedCategoyIds;
2386
+ propertyType = PropertyType;
2387
+ priceType = PropertyPriceType;
2388
+ price = model('0.00');
2389
+ deposit = model('');
2390
+ minDate = new Date();
2391
+ ngOnInit() {
2392
+ this.id = this.route.parent?.snapshot.paramMap.get('id');
2393
+ this.find();
2394
+ }
2395
+ find() {
2396
+ this.propertyService.get(this.id).subscribe({
2397
+ next: (res) => {
2398
+ this.property = res.data;
2399
+ const price = this.property.price ? (this.property.price / 100).toFixed(2) : '0.00';
2400
+ this.price.set(price);
2401
+ if (this.property.priceType?.toString() === 'PARTIAL') {
2402
+ const deposit = (this.property.deposit / 100).toFixed(2);
2403
+ this.deposit.set(deposit);
2404
+ }
2405
+ },
2406
+ });
2407
+ }
2408
+ formatPrice(value) {
2409
+ // Convert to number first in case it's a string
2410
+ this.property.price = Number(Number(value).toFixed(2));
2411
+ }
2412
+ typeCompareFn(t1, t2) {
2413
+ return t1 === t2;
2414
+ }
2415
+ priceTypeCompareFn(t1, t2) {
2416
+ return t1 === t2;
2417
+ }
2418
+ onTypeChange(event) {
2419
+ // this.property.type = Object.keys(this.propertyType).find((key) => {
2420
+ // return this.propertyType[key] === event.value;
2421
+ // })!;
2422
+ this.property.type = event.value;
2423
+ }
2424
+ onCategoryChange(event) {
2425
+ this.selectedCategoyIds = event.value;
2426
+ }
2427
+ update() {
2428
+ const { title, description, type, priceType, availableDate, bedrooms, bathrooms, size, floor } = this.property;
2429
+ const data = {
2430
+ title,
2431
+ description,
2432
+ bedrooms,
2433
+ bathrooms,
2434
+ size,
2435
+ floor,
2436
+ type,
2437
+ price: Number(this.price()) * 100,
2438
+ priceType,
2439
+ deposit: Number(this.deposit()) * 100,
2440
+ availableDate,
2441
+ };
2442
+ this.propertyService.update(this.id, data).subscribe({
2443
+ next: (res) => {
2444
+ this.snackBarService.open('Update successfully');
2445
+ },
2446
+ error: (e) => {
2447
+ this.snackBarService.open(e.message);
2448
+ },
2449
+ });
2450
+ }
2451
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyManageInfoComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
2452
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: PropertyManageInfoComponent, isStandalone: true, selector: "rolatech-property-manage-info", inputs: { price: { classPropertyName: "price", publicName: "price", isSignal: true, isRequired: false, transformFunction: null }, deposit: { classPropertyName: "deposit", publicName: "deposit", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { price: "priceChange", deposit: "depositChange" }, providers: [
2453
+ {
2454
+ provide: DateAdapter,
2455
+ useClass: MomentDateAdapter,
2456
+ deps: [MAT_DATE_LOCALE],
2457
+ },
2458
+ { provide: MAT_DATE_FORMATS, useValue: MY_FORMATS },
2459
+ ], usesInheritance: true, ngImport: i0, template: "<rolatech-property-manage-content>\n <rolatech-toolbar title=\"Basic info\" class=\"hidden md:block\" divider></rolatech-toolbar>\n @if (property) {\n <div class=\"flex flex-col\">\n <form #propertyForm=\"ngForm\">\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Title </mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"property.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)]=\"property.description\"\n name=\"description\"\n required\n cdkTextareaAutosize\n cdkAutosizeMinRows=\"3\"\n ></textarea>\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Bedrooms </mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"property.bedrooms\" name=\"bedrooms\" required />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Bathrooms </mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"property.bathrooms\" name=\"bathrooms\" required />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Floor </mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"property.floor\" name=\"floor\" required />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Size(sqft) </mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"property.size\" name=\"size\" required />\n </mat-form-field>\n <mat-form-field appearance=\"fill\" required>\n <mat-label i18n>Type</mat-label>\n <mat-select\n name=\"type\"\n [compareWith]=\"typeCompareFn\"\n (selectionChange)=\"onTypeChange($event)\"\n [(ngModel)]=\"property.type\"\n >\n @for (type of propertyType | keyvalue; track type) {\n <mat-option [value]=\"type.key\">\n {{ type.value }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <!-- <mat-form-field appearance=\"fill\" required>\n <mat-label i18n>Price type</mat-label>\n <mat-select name=\"priceType\" [compareWith]=\"priceTypeCompareFn\" [(ngModel)]=\"property.priceType\">\n @for (type of priceType | keyvalue; track type) {\n <mat-option [value]=\"type.key\">\n {{ type.value }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field> -->\n <mat-form-field appearance=\"fill\">\n <mat-label i18n>Available date</mat-label>\n <input\n matInput\n placeholder=\"Available date\"\n [matDatepicker]=\"availableDatePicker\"\n [min]=\"minDate\"\n (focus)=\"availableDatePicker.open()\"\n name=\"availableDate\"\n [(ngModel)]=\"property.availableDate\"\n (dateInput)=\"property.availableDate = $event.value.format('YYYY-MM-DD')\"\n readonly\n />\n <mat-datepicker-toggle matIconPrefix [for]=\"availableDatePicker\"></mat-datepicker-toggle>\n <mat-datepicker #availableDatePicker></mat-datepicker>\n </mat-form-field>\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 @if (property.priceType && property.priceType.toString() === 'PARTIAL') {\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Deposit </mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"deposit\" name=\"deposit\" required rolatechDecimal />\n </mat-form-field>\n }\n </form>\n </div>\n <div>\n <button mat-flat-button (click)=\"update()\" i18n>Save</button>\n </div>\n }\n</rolatech-property-manage-content>\n", styles: ["mat-form-field{width:100%}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$3.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$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$3.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1$3.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2$2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2$2.MatLabel, selector: "mat-label" }, { kind: "directive", type: i2$2.MatPrefix, selector: "[matPrefix], [matIconPrefix], [matTextPrefix]", inputs: ["matTextPrefix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i4.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$3.CdkTextareaAutosize, selector: "textarea[cdkTextareaAutosize]", inputs: ["cdkAutosizeMinRows", "cdkAutosizeMaxRows", "cdkTextareaAutosize", "placeholder"], exportAs: ["cdkTextareaAutosize"] }, { kind: "ngmodule", type: TextFieldModule }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i4$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatOptionModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.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: PropertyManageContentComponent, selector: "rolatech-property-manage-content" }, { kind: "directive", type: DecimalDirective, selector: "[rolatechDecimal]" }, { kind: "pipe", type: KeyValuePipe, name: "keyvalue" }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i8.MatDatepicker, selector: "mat-datepicker", exportAs: ["matDatepicker"] }, { kind: "directive", type: i8.MatDatepickerInput, selector: "input[matDatepicker]", inputs: ["matDatepicker", "min", "max", "matDatepickerFilter"], exportAs: ["matDatepickerInput"] }, { kind: "component", type: i8.MatDatepickerToggle, selector: "mat-datepicker-toggle", inputs: ["for", "tabIndex", "aria-label", "disabled", "disableRipple"], exportAs: ["matDatepickerToggle"] }] });
2460
+ }
2461
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyManageInfoComponent, decorators: [{
2462
+ type: Component,
2463
+ args: [{ selector: 'rolatech-property-manage-info', imports: [
2464
+ FormsModule,
2465
+ MatFormFieldModule,
2466
+ MatInputModule,
2467
+ TextFieldModule,
2468
+ MatSelectModule,
2469
+ MatOptionModule,
2470
+ MatButtonModule,
2471
+ ToolbarComponent,
2472
+ PropertyManageContentComponent,
2473
+ DecimalDirective,
2474
+ MatSelectModule,
2475
+ KeyValuePipe,
2476
+ MatDatepickerModule,
2477
+ ], providers: [
2478
+ {
2479
+ provide: DateAdapter,
2480
+ useClass: MomentDateAdapter,
2481
+ deps: [MAT_DATE_LOCALE],
2482
+ },
2483
+ { provide: MAT_DATE_FORMATS, useValue: MY_FORMATS },
2484
+ ], template: "<rolatech-property-manage-content>\n <rolatech-toolbar title=\"Basic info\" class=\"hidden md:block\" divider></rolatech-toolbar>\n @if (property) {\n <div class=\"flex flex-col\">\n <form #propertyForm=\"ngForm\">\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Title </mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"property.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)]=\"property.description\"\n name=\"description\"\n required\n cdkTextareaAutosize\n cdkAutosizeMinRows=\"3\"\n ></textarea>\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Bedrooms </mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"property.bedrooms\" name=\"bedrooms\" required />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Bathrooms </mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"property.bathrooms\" name=\"bathrooms\" required />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Floor </mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"property.floor\" name=\"floor\" required />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Size(sqft) </mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"property.size\" name=\"size\" required />\n </mat-form-field>\n <mat-form-field appearance=\"fill\" required>\n <mat-label i18n>Type</mat-label>\n <mat-select\n name=\"type\"\n [compareWith]=\"typeCompareFn\"\n (selectionChange)=\"onTypeChange($event)\"\n [(ngModel)]=\"property.type\"\n >\n @for (type of propertyType | keyvalue; track type) {\n <mat-option [value]=\"type.key\">\n {{ type.value }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <!-- <mat-form-field appearance=\"fill\" required>\n <mat-label i18n>Price type</mat-label>\n <mat-select name=\"priceType\" [compareWith]=\"priceTypeCompareFn\" [(ngModel)]=\"property.priceType\">\n @for (type of priceType | keyvalue; track type) {\n <mat-option [value]=\"type.key\">\n {{ type.value }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field> -->\n <mat-form-field appearance=\"fill\">\n <mat-label i18n>Available date</mat-label>\n <input\n matInput\n placeholder=\"Available date\"\n [matDatepicker]=\"availableDatePicker\"\n [min]=\"minDate\"\n (focus)=\"availableDatePicker.open()\"\n name=\"availableDate\"\n [(ngModel)]=\"property.availableDate\"\n (dateInput)=\"property.availableDate = $event.value.format('YYYY-MM-DD')\"\n readonly\n />\n <mat-datepicker-toggle matIconPrefix [for]=\"availableDatePicker\"></mat-datepicker-toggle>\n <mat-datepicker #availableDatePicker></mat-datepicker>\n </mat-form-field>\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 @if (property.priceType && property.priceType.toString() === 'PARTIAL') {\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Deposit </mat-label>\n <input matInput type=\"text\" [(ngModel)]=\"deposit\" name=\"deposit\" required rolatechDecimal />\n </mat-form-field>\n }\n </form>\n </div>\n <div>\n <button mat-flat-button (click)=\"update()\" i18n>Save</button>\n </div>\n }\n</rolatech-property-manage-content>\n", styles: ["mat-form-field{width:100%}\n"] }]
2485
+ }] });
2486
+
2487
+ const SIZE = 10 * 1024 * 1024; // file slice size 10MB
2488
+ class PropertyManageMediaComponent {
2489
+ route = inject(ActivatedRoute);
2490
+ propertyService = inject(PropertyService);
2491
+ dialog = inject(MatDialog);
2492
+ snackBar = inject(MatSnackBar);
2493
+ id;
2494
+ isLoading = false;
2495
+ media = [];
2496
+ status = PropertyStatus;
2497
+ // propertyType: any = PropertyType;
2498
+ constructor() {
2499
+ this.id = this.route.parent?.snapshot.paramMap.get('id');
2500
+ }
2501
+ ngOnInit() {
2502
+ this.find();
2503
+ }
2504
+ find() {
2505
+ this.propertyService.get(this.id).subscribe({
2506
+ next: (res) => {
2507
+ this.media = res.data.media || [];
2508
+ },
2509
+ });
2510
+ }
2511
+ onImageClick(i) {
2512
+ const dialogRef = this.dialog.open(ImagePreviewDialogComponent, {
2513
+ maxWidth: '80vw',
2514
+ maxHeight: '80vh',
2515
+ height: '80%',
2516
+ width: '80%',
2517
+ panelClass: 'full-screen-modal',
2518
+ data: {
2519
+ media: this.media,
2520
+ selected: i,
2521
+ },
2522
+ });
2523
+ dialogRef.afterClosed().subscribe((result) => { });
2524
+ }
2525
+ createFileChunk(file, size = SIZE) {
2526
+ const fileChunkList = [];
2527
+ let cur = 0;
2528
+ while (cur < file.size) {
2529
+ fileChunkList.push({ file: file.slice(cur, cur + size) });
2530
+ cur += size;
2531
+ }
2532
+ return fileChunkList;
2533
+ }
2534
+ onUploadMedia2(event) {
2535
+ const file = event.target.files[0];
2536
+ const reader = new FileReader();
2537
+ reader.onload = (e) => { };
2538
+ const fileChunkList = this.createFileChunk(file);
2539
+ fileChunkList.forEach((item) => { });
2540
+ }
2541
+ onMultipleFilesUpload(event) {
2542
+ // const files = event.target.files;
2543
+ // if (files) {
2544
+ // Object.values(files).forEach((file, index) => {
2545
+ // this.uploadFile(file, index);
2546
+ // });
2547
+ // }
2548
+ const input = event.target;
2549
+ if (input.files && input.files.length > 0) {
2550
+ const startIndex = this.media.length; // Offset to preserve existing items
2551
+ Array.from(input.files).forEach((file, i) => {
2552
+ this.uploadFile(file, startIndex + i);
2553
+ });
2554
+ }
2555
+ }
2556
+ uploadFile(file, index) {
2557
+ const reader = new FileReader();
2558
+ const formData = new FormData();
2559
+ formData.append('file', file);
2560
+ this.media.splice(index, 0, {
2561
+ url: '',
2562
+ alt: 'Uploading...',
2563
+ width: 0,
2564
+ height: 0,
2565
+ uploading: true,
2566
+ });
2567
+ reader.readAsDataURL(file);
2568
+ reader.onload = () => {
2569
+ const img = new Image();
2570
+ img.onload = () => {
2571
+ Object.assign(this.media[index], {
2572
+ url: img.src,
2573
+ width: img.width,
2574
+ height: img.height,
2575
+ });
2576
+ };
2577
+ img.src = reader.result;
2578
+ };
2579
+ this.propertyService.uploadMedia(this.id, formData).subscribe({
2580
+ next: (res) => {
2581
+ // Replace item at index using native splice
2582
+ // this.media.splice(index, 1, res.data);
2583
+ this.media.splice(index, 1, {
2584
+ ...res.data,
2585
+ uploading: false,
2586
+ });
2587
+ },
2588
+ error: (e) => {
2589
+ this.snackBar.open('Upload failed: ' + e.message);
2590
+ this.media.splice(index, 1); // remove failed placeholder
2591
+ },
2592
+ });
2593
+ reader.onerror = (error) => { };
2594
+ }
2595
+ onUploadMedia(event) {
2596
+ const file = event.target.files[0];
2597
+ // 5MB * 1024 * 1024 = 5242880
2598
+ // if (file?.size > 5242880) {
2599
+ // this.snackBar.open('尺寸过大, 请修改后上传');
2600
+ // return;
2601
+ // }
2602
+ if (file) {
2603
+ const reader = new FileReader();
2604
+ const formData = new FormData();
2605
+ formData.append('file', file);
2606
+ reader.readAsDataURL(file);
2607
+ reader.onload = () => {
2608
+ const img = new Image();
2609
+ img.onload = () => {
2610
+ this.media.push({
2611
+ url: img.src,
2612
+ alt: 'upload image',
2613
+ width: img.width,
2614
+ height: img.height,
2615
+ });
2616
+ };
2617
+ img.src = reader.result; // This is the data URL
2618
+ };
2619
+ this.propertyService.uploadMedia(this.id, formData).subscribe({
2620
+ next: (res) => {
2621
+ const index = findLastIndex(this.media);
2622
+ // Replace item at index using native splice
2623
+ this.media.splice(index, 1, res.data);
2624
+ },
2625
+ error: (e) => { },
2626
+ });
2627
+ reader.onerror = (error) => { };
2628
+ }
2629
+ }
2630
+ onMediaDelete(item) {
2631
+ const dialogRef = this.dialog.open(ConfirmationDialogComponent, {
2632
+ width: '400px',
2633
+ data: {
2634
+ title: '删除图片',
2635
+ message: '确定删除这张商品图片吗?',
2636
+ },
2637
+ });
2638
+ dialogRef.afterClosed().subscribe((result) => {
2639
+ if (result) {
2640
+ this.propertyService.deleteMedia(this.id, item.id).subscribe({
2641
+ next: (res) => {
2642
+ this.media = this.media.filter((m) => m.id !== item.id);
2643
+ this.snackBar.open('Delete successfully');
2644
+ },
2645
+ error: (e) => {
2646
+ this.snackBar.open(e.message);
2647
+ },
2648
+ });
2649
+ }
2650
+ });
2651
+ }
2652
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyManageMediaComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2653
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: PropertyManageMediaComponent, isStandalone: true, selector: "rolatech-property-manage-media", ngImport: i0, template: "<rolatech-property-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-property-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: PropertyManageContentComponent, selector: "rolatech-property-manage-content" }] });
2654
+ }
2655
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyManageMediaComponent, decorators: [{
2656
+ type: Component,
2657
+ args: [{ selector: 'rolatech-property-manage-media', imports: [MediaListComponent, MediaListItemComponent, ToolbarComponent, PropertyManageContentComponent], template: "<rolatech-property-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-property-manage-content>\n" }]
2658
+ }], ctorParameters: () => [] });
2659
+
2660
+ class PropertyManageSectionItemComponent {
2661
+ isUploading = input();
2662
+ section = input.required();
2663
+ actions = input(false);
2664
+ selectMedia = input();
2665
+ upload = output();
2666
+ delete = output();
2667
+ save = output();
2668
+ deleteMedia = output();
2669
+ selectedImg;
2670
+ expanded = false;
2671
+ constructor() { }
2672
+ ngOnInit() {
2673
+ this.selectedImg = this.section().media ? this.section().media[0] : null;
2674
+ }
2675
+ onUpload(data) {
2676
+ this.upload.emit({ section: this.section(), data });
2677
+ }
2678
+ onMediaItemClick(image) {
2679
+ this.selectedImg = image;
2680
+ }
2681
+ select(item) {
2682
+ this.selectedImg = item;
2683
+ }
2684
+ deleteImage() { }
2685
+ onSave(section) {
2686
+ this.save.emit(section);
2687
+ }
2688
+ onDelete(section) {
2689
+ this.delete.emit(section);
2690
+ }
2691
+ onDeleteMedia(media) {
2692
+ this.deleteMedia.emit({ section: this.section(), media });
2693
+ this.selectedImg = this.section().media ? first(this.section().media) : null;
2694
+ }
2695
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyManageSectionItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2696
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: PropertyManageSectionItemComponent, isStandalone: true, selector: "rolatech-property-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$2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2$2.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i4.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$3.CdkTextareaAutosize, selector: "textarea[cdkTextareaAutosize]", inputs: ["cdkAutosizeMinRows", "cdkAutosizeMaxRows", "cdkTextareaAutosize", "placeholder"], exportAs: ["cdkTextareaAutosize"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.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$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.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: i1$1.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: i1$1.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: i4$2.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: i4$2.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i4$2.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$1.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "ngmodule", type: MatDividerModule }, { kind: "component", type: i2$1.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }], animations: [
2697
+ trigger('detailExpand', [
2698
+ state('collapsed', style({ height: '0px' })),
2699
+ state('expanded', style({ height: '*' })),
2700
+ transition('expanded <=> collapsed', animate('400ms cubic-bezier(0.25, 0.1, 0.25, 1)')),
2701
+ ]),
2702
+ ] });
2703
+ }
2704
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyManageSectionItemComponent, decorators: [{
2705
+ type: Component,
2706
+ args: [{ selector: 'rolatech-property-manage-section-item', imports: [
2707
+ MatFormFieldModule,
2708
+ MatInputModule,
2709
+ FormsModule,
2710
+ TextFieldModule,
2711
+ MatButtonModule,
2712
+ MatIconModule,
2713
+ MatMenuModule,
2714
+ MatProgressBarModule,
2715
+ MatDividerModule,
2716
+ ], animations: [
2717
+ trigger('detailExpand', [
2718
+ state('collapsed', style({ height: '0px' })),
2719
+ state('expanded', style({ height: '*' })),
2720
+ transition('expanded <=> collapsed', animate('400ms cubic-bezier(0.25, 0.1, 0.25, 1)')),
2721
+ ]),
2722
+ ], 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"] }]
2723
+ }], ctorParameters: () => [] });
2724
+
2725
+ class PropertyManageSectionsComponent {
2726
+ route = inject(ActivatedRoute);
2727
+ propertyService = inject(PropertyService);
2728
+ dialog = inject(MatDialog);
2729
+ snackBar = inject(MatSnackBar);
2730
+ id;
2731
+ isLoading = false;
2732
+ isUploading = false;
2733
+ sections = [];
2734
+ status = PropertyStatus;
2735
+ propertyType = PropertyType;
2736
+ constructor() {
2737
+ this.id = this.route.parent?.snapshot.paramMap.get('id');
2738
+ }
2739
+ ngOnInit() {
2740
+ this.find();
2741
+ }
2742
+ find() {
2743
+ this.propertyService.findSections(this.id).subscribe({
2744
+ next: (res) => {
2745
+ if (res.data) {
2746
+ this.sections = res.data;
2747
+ }
2748
+ },
2749
+ });
2750
+ }
2751
+ addSection() {
2752
+ if (!this.sections) {
2753
+ this.sections = [];
2754
+ }
2755
+ this.propertyService
2756
+ .addSection(this.id, {
2757
+ title: '',
2758
+ description: '',
2759
+ content: '',
2760
+ media: [],
2761
+ })
2762
+ .subscribe({
2763
+ next: (res) => {
2764
+ const section = res.data;
2765
+ this.sections.push({
2766
+ id: section.id,
2767
+ title: 'Untitled',
2768
+ description: '',
2769
+ content: '',
2770
+ media: [],
2771
+ });
2772
+ },
2773
+ error: (e) => {
2774
+ this.snackBar.open(e.message);
2775
+ },
2776
+ });
2777
+ }
2778
+ onUploadSectionMedia(event) {
2779
+ const section = event.section;
2780
+ const sectionId = section.id;
2781
+ const file = event.data.target.files[0];
2782
+ if (file) {
2783
+ const reader = new FileReader();
2784
+ reader.readAsDataURL(file);
2785
+ reader.onload = () => {
2786
+ if (!section.media) {
2787
+ section.media = [];
2788
+ }
2789
+ section.media.push({
2790
+ url: reader.result,
2791
+ alt: 'upload image',
2792
+ });
2793
+ section.isUploading = true;
2794
+ const formData = new FormData();
2795
+ formData.append('file', file);
2796
+ this.propertyService.uploadSectionMedia(sectionId, formData).subscribe({
2797
+ next: (res) => {
2798
+ this.isUploading = false;
2799
+ section.isUploading = false;
2800
+ delete res.data.propertySection;
2801
+ section.media[section.media.length - 1].id = res.data.id;
2802
+ section.media[section.media.length - 1].url = res.data.url;
2803
+ },
2804
+ error: (e) => {
2805
+ this.isUploading = false;
2806
+ this.snackBar.open('Upload failed: ' + e.message);
2807
+ },
2808
+ });
2809
+ };
2810
+ reader.onerror = (error) => {
2811
+ this.isUploading = false;
2812
+ };
2813
+ }
2814
+ }
2815
+ onDeleteSectionMedia(event) {
2816
+ const dialogRef = this.dialog.open(ConfirmationDialogComponent, {
2817
+ width: '400px',
2818
+ data: {
2819
+ title: '删除图片',
2820
+ message: `确定删除吗?`,
2821
+ },
2822
+ });
2823
+ dialogRef.afterClosed().subscribe((result) => {
2824
+ if (result) {
2825
+ const section = event.section;
2826
+ const mediaId = event.media.id;
2827
+ remove(section.media, {
2828
+ id: mediaId,
2829
+ });
2830
+ this.propertyService.deleteSectionMedia(section.id, mediaId).subscribe({
2831
+ next: (res) => {
2832
+ // remove(section.media, {
2833
+ // id: mediaId,
2834
+ // });
2835
+ },
2836
+ });
2837
+ }
2838
+ });
2839
+ }
2840
+ onSectionSave(section) {
2841
+ delete section.isUploading;
2842
+ this.propertyService.updateSection(section.id, section).subscribe({
2843
+ next: (res) => {
2844
+ this.snackBar.open('Success');
2845
+ },
2846
+ error: (e) => {
2847
+ this.snackBar.open(e.message);
2848
+ },
2849
+ });
2850
+ }
2851
+ onSectionDelete(section) {
2852
+ const dialogRef = this.dialog.open(ConfirmationDialogComponent, {
2853
+ width: '400px',
2854
+ data: {
2855
+ title: '删除详情',
2856
+ message: '确定删除此详情吗?',
2857
+ },
2858
+ });
2859
+ dialogRef.afterClosed().subscribe((result) => {
2860
+ if (result) {
2861
+ this.propertyService.deleteSection(section.id).subscribe({
2862
+ next: (res) => {
2863
+ this.snackBar.open(res.data);
2864
+ remove(this.sections, {
2865
+ id: section.id,
2866
+ });
2867
+ },
2868
+ error: (e) => {
2869
+ this.snackBar.open(e.message);
2870
+ },
2871
+ });
2872
+ }
2873
+ });
2874
+ }
2875
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyManageSectionsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2876
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: PropertyManageSectionsComponent, isStandalone: true, selector: "rolatech-property-manage-sections", ngImport: i0, template: "<rolatech-property-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-property-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-property-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-property-manage-content>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.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: PropertyManageContentComponent, selector: "rolatech-property-manage-content" }, { kind: "component", type: PropertyManageSectionItemComponent, selector: "rolatech-property-manage-section-item", inputs: ["isUploading", "section", "actions", "selectMedia"], outputs: ["upload", "delete", "save", "deleteMedia"] }] });
2877
+ }
2878
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyManageSectionsComponent, decorators: [{
2879
+ type: Component,
2880
+ args: [{ selector: 'rolatech-property-manage-sections', imports: [
2881
+ MatButtonModule,
2882
+ MatIconModule,
2883
+ ToolbarComponent,
2884
+ PropertyManageContentComponent,
2885
+ PropertyManageSectionItemComponent,
2886
+ ], template: "<rolatech-property-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-property-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-property-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-property-manage-content>\n" }]
2887
+ }], ctorParameters: () => [] });
2888
+
2889
+ class PropertyManagePricingComponent {
2890
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyManagePricingComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2891
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.0.3", type: PropertyManagePricingComponent, isStandalone: true, selector: "rolatech-property-manage-pricing", ngImport: i0, template: "<p>property-manage-pricing works!</p>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
2892
+ }
2893
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyManagePricingComponent, decorators: [{
2894
+ type: Component,
2895
+ args: [{ selector: 'rolatech-property-manage-pricing', imports: [CommonModule], template: "<p>property-manage-pricing works!</p>\n" }]
2896
+ }] });
2897
+
2898
+ class PropertyManageScheduleComponent {
2899
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyManageScheduleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2900
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.0.3", type: PropertyManageScheduleComponent, isStandalone: true, selector: "rolatech-property-manage-schedule", ngImport: i0, template: "<p>property-manage-schedule works!</p>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
2901
+ }
2902
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyManageScheduleComponent, decorators: [{
2903
+ type: Component,
2904
+ args: [{ selector: 'rolatech-property-manage-schedule', imports: [CommonModule], template: "<p>property-manage-schedule works!</p>\n" }]
2905
+ }] });
2906
+
2907
+ class PropertyManageFeaturesComponent extends BaseComponent {
2908
+ propertyService = inject(PropertyService);
2909
+ featureService = inject(FeatureService);
2910
+ features = [];
2911
+ selected = {};
2912
+ partiallyComplete = (feature) => computed(() => {
2913
+ if (!feature.values) {
2914
+ return false;
2915
+ }
2916
+ return feature.values.some((t) => t.selected) && !feature.values.every((t) => t.selected);
2917
+ });
2918
+ ngOnInit() {
2919
+ this.id = this.route.parent?.snapshot.paramMap.get('id');
2920
+ this.find();
2921
+ }
2922
+ isEmpty(obj) {
2923
+ return obj && Object.keys(obj).length === 0;
2924
+ }
2925
+ find() {
2926
+ forkJoin({
2927
+ features: this.featureService.find({}),
2928
+ options: this.propertyService.findFeatures(this.id),
2929
+ }).subscribe(({ features, options }) => {
2930
+ this.features = features.data;
2931
+ if (options.data) {
2932
+ this.selected = this.convertToBinding(options.data);
2933
+ // ensure all features have initialized value arrays
2934
+ for (const feature of this.features) {
2935
+ if (!this.selected[feature.id]) {
2936
+ this.selected[feature.id] = [];
2937
+ }
2938
+ }
2939
+ }
2940
+ });
2941
+ }
2942
+ sectionChange(feature, selected, index) {
2943
+ if (index === undefined) {
2944
+ feature.selected = selected;
2945
+ feature.values?.forEach((t) => (t.selected = selected));
2946
+ }
2947
+ else {
2948
+ feature.values[index].selected = selected;
2949
+ feature.selected = feature.values?.every((t) => t.selected) ?? true;
2950
+ }
2951
+ }
2952
+ toggleValue(featureId, valueId, checked) {
2953
+ const selectedValues = this.selected[featureId] || [];
2954
+ if (checked) {
2955
+ selectedValues.push(valueId);
2956
+ }
2957
+ else {
2958
+ const index = selectedValues.indexOf(valueId);
2959
+ if (index !== -1)
2960
+ selectedValues.splice(index, 1);
2961
+ }
2962
+ this.selected[featureId] = selectedValues;
2963
+ }
2964
+ save() {
2965
+ this.propertyService.updateFeatures(this.id, this.selected).subscribe({
2966
+ next: (res) => {
2967
+ this.snackBarService.open('Update successfully');
2968
+ },
2969
+ error: (error) => {
2970
+ this.snackBarService.open(error.message);
2971
+ },
2972
+ });
2973
+ }
2974
+ convertToBinding(options) {
2975
+ const binding = {};
2976
+ for (const option of options) {
2977
+ const featureId = option.feature.id;
2978
+ const valueId = option.value.id;
2979
+ if (!binding[featureId]) {
2980
+ binding[featureId] = [];
2981
+ }
2982
+ // avoid duplicates just in case
2983
+ if (!binding[featureId].includes(valueId)) {
2984
+ binding[featureId].push(valueId);
2985
+ }
2986
+ }
2987
+ return binding;
2988
+ }
2989
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyManageFeaturesComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
2990
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: PropertyManageFeaturesComponent, isStandalone: true, selector: "rolatech-property-manage-features", usesInheritance: true, ngImport: i0, template: "<rolatech-property-manage-content>\n <rolatech-toolbar title=\"Features\" class=\"hidden md:block\" divider></rolatech-toolbar>\n <div>\n @for (feature of features; track $index) {\n <div>\n <h4>{{ feature.name }}</h4>\n @for (value of feature.values; track $index) {\n <mat-checkbox\n [checked]=\"selected[feature.id] ? selected[feature.id].includes(value.id) : false\"\n (change)=\"toggleValue(feature.id, value.id, $event.checked)\"\n >\n {{ value.name }}\n </mat-checkbox>\n }\n </div>\n }\n </div>\n\n <!-- @for (item of features; track item) {\n <section class=\"example-section\">\n <span class=\"example-list-section\">\n <h4>{{ item.name }}</h4>\n <mat-checkbox\n class=\"example-margin\"\n [checked]=\"item.selected\"\n [indeterminate]=\"partiallyComplete(item)()\"\n (change)=\"sectionChange(item, $event.checked)\"\n >\n {{ item.name }}\n </mat-checkbox>\n </span>\n <div class=\"example-list-section\">\n <div>\n @for (value of item.values; track value; let i = $index) {\n <mat-checkbox [checked]=\"value.selected\" (change)=\"sectionChange(item, $event.checked, i)\">\n {{ value.name }}\n </mat-checkbox>\n }\n </div>\n </div>\n </section>\n } -->\n <!-- <div class=\"px-3 mb-3\">\n @for (item of features; track $index) {\n <div class=\"text-xl font-bold py-2\">{{ item.name }}</div>\n @for (value of item.values; track value; let i = $index) {\n <mat-checkbox\n class=\"text-lg font-medium\"\n (click)=\"$event.stopPropagation()\"\n [checked]=\"value.selected\"\n (change)=\"sectionChange($event.checked, i)\"\n >\n {{ value.name }}\n </mat-checkbox>\n }\n }\n </div> -->\n <button mat-flat-button (click)=\"save()\" i18n>Save</button>\n</rolatech-property-manage-content>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: PropertyManageContentComponent, selector: "rolatech-property-manage-content" }, { kind: "component", type: ToolbarComponent, selector: "rolatech-toolbar", inputs: ["title", "subtitle", "back", "link", "large", "divider"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i1$5.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.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"] }] });
2991
+ }
2992
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyManageFeaturesComponent, decorators: [{
2993
+ type: Component,
2994
+ args: [{ selector: 'rolatech-property-manage-features', imports: [CommonModule, PropertyManageContentComponent, ToolbarComponent, MatCheckboxModule, MatButtonModule], template: "<rolatech-property-manage-content>\n <rolatech-toolbar title=\"Features\" class=\"hidden md:block\" divider></rolatech-toolbar>\n <div>\n @for (feature of features; track $index) {\n <div>\n <h4>{{ feature.name }}</h4>\n @for (value of feature.values; track $index) {\n <mat-checkbox\n [checked]=\"selected[feature.id] ? selected[feature.id].includes(value.id) : false\"\n (change)=\"toggleValue(feature.id, value.id, $event.checked)\"\n >\n {{ value.name }}\n </mat-checkbox>\n }\n </div>\n }\n </div>\n\n <!-- @for (item of features; track item) {\n <section class=\"example-section\">\n <span class=\"example-list-section\">\n <h4>{{ item.name }}</h4>\n <mat-checkbox\n class=\"example-margin\"\n [checked]=\"item.selected\"\n [indeterminate]=\"partiallyComplete(item)()\"\n (change)=\"sectionChange(item, $event.checked)\"\n >\n {{ item.name }}\n </mat-checkbox>\n </span>\n <div class=\"example-list-section\">\n <div>\n @for (value of item.values; track value; let i = $index) {\n <mat-checkbox [checked]=\"value.selected\" (change)=\"sectionChange(item, $event.checked, i)\">\n {{ value.name }}\n </mat-checkbox>\n }\n </div>\n </div>\n </section>\n } -->\n <!-- <div class=\"px-3 mb-3\">\n @for (item of features; track $index) {\n <div class=\"text-xl font-bold py-2\">{{ item.name }}</div>\n @for (value of item.values; track value; let i = $index) {\n <mat-checkbox\n class=\"text-lg font-medium\"\n (click)=\"$event.stopPropagation()\"\n [checked]=\"value.selected\"\n (change)=\"sectionChange($event.checked, i)\"\n >\n {{ value.name }}\n </mat-checkbox>\n }\n }\n </div> -->\n <button mat-flat-button (click)=\"save()\" i18n>Save</button>\n</rolatech-property-manage-content>\n" }]
2995
+ }] });
2996
+
2997
+ class PropertyManageLocationComponent extends BaseComponent {
2998
+ propertyService = inject(PropertyService);
2999
+ predictions = [];
3000
+ autocompleteSuggestion;
3001
+ titleElement;
3002
+ resultsContainerElement;
3003
+ inputElement;
3004
+ newestRequestId = 0;
3005
+ expand = false;
3006
+ request = {
3007
+ input: '',
3008
+ includedPrimaryTypes: ['restaurant'],
3009
+ language: 'en-GB',
3010
+ region: 'uk',
3011
+ };
3012
+ map;
3013
+ placesService;
3014
+ sessionToken;
3015
+ autocompleteService;
3016
+ location = model({
3017
+ flatNumber: '',
3018
+ streetNumber: '',
3019
+ buildingName: '',
3020
+ street: '',
3021
+ town: '',
3022
+ postCode: '',
3023
+ country: '',
3024
+ longitude: '',
3025
+ latitude: '',
3026
+ });
3027
+ platformId = inject(PLATFORM_ID);
3028
+ placeAutocompleteElement;
3029
+ autocompleteInput;
3030
+ ngOnInit() {
3031
+ this.id = this.route.parent?.snapshot.paramMap.get('id');
3032
+ this.find();
3033
+ if (isPlatformBrowser(this.platformId)) {
3034
+ // this.autocompleteService = new google.maps.places.AutocompleteService();
3035
+ }
3036
+ }
3037
+ find() {
3038
+ this.propertyService.get(this.id).subscribe({
3039
+ next: (res) => {
3040
+ // this.location = res.data.location || {};
3041
+ this.location.update((state) => {
3042
+ return { ...(res.data.location || {}) };
3043
+ });
3044
+ if (res.data.location) {
3045
+ this.expand = true;
3046
+ }
3047
+ },
3048
+ });
3049
+ }
3050
+ async onPlaceSelected(place) {
3051
+ await place.fetchFields({
3052
+ fields: ['displayName', 'formattedAddress'],
3053
+ });
3054
+ const placeText = document.createTextNode(`${place.displayName}: ${place.formattedAddress}`);
3055
+ this.resultsContainerElement.replaceChildren(placeText);
3056
+ this.titleElement.innerText = 'Selected Place:';
3057
+ this.inputElement.value = '';
3058
+ this.refreshToken(this.request);
3059
+ }
3060
+ displayFn(option) {
3061
+ return option && option.description ? option.description : '';
3062
+ }
3063
+ onOptionSelected(event) {
3064
+ const prediction = event.option.value;
3065
+ const placeId = prediction.place_id;
3066
+ // Use Places Service to get detailed information (geometry) about the selected place
3067
+ this.placesService.getDetails({ placeId: placeId, fields: ['name', 'formatted_address', 'geometry', 'address_components'] }, (place, status) => {
3068
+ const components = place.address_components;
3069
+ const getComponent = (type) => components?.find((c) => c.types.includes(type))?.long_name || '';
3070
+ const streetNumber = getComponent('street_number');
3071
+ const route = getComponent('route');
3072
+ const town = getComponent('postal_town') || getComponent('locality');
3073
+ const postcode = getComponent('postal_code');
3074
+ if (streetNumber) {
3075
+ this.location.update((state) => {
3076
+ state.streetNumber = streetNumber;
3077
+ return { ...state };
3078
+ });
3079
+ }
3080
+ if (town) {
3081
+ this.location.update((state) => {
3082
+ state.town = town;
3083
+ return { ...state };
3084
+ });
3085
+ }
3086
+ if (postcode) {
3087
+ this.location.update((state) => {
3088
+ state.postCode = postcode;
3089
+ return { ...state };
3090
+ });
3091
+ }
3092
+ if (status === google.maps.places.PlacesServiceStatus.OK && place?.geometry) {
3093
+ const location = place.geometry.location;
3094
+ }
3095
+ });
3096
+ }
3097
+ refreshToken(request) {
3098
+ // Create a new session token and add it to the request.
3099
+ request.sessionToken = new google.maps.places.AutocompleteSessionToken();
3100
+ }
3101
+ ngAfterViewInit() {
3102
+ if (isPlatformBrowser(this.platformId)) {
3103
+ google.maps.importLibrary('places').then((res) => {
3104
+ const places = res;
3105
+ this.autocompleteService = new google.maps.places.AutocompleteService();
3106
+ this.placesService = new google.maps.places.PlacesService(document.createElement('div'));
3107
+ this.sessionToken = new google.maps.places.AutocompleteSessionToken();
3108
+ });
3109
+ }
3110
+ }
3111
+ onInputChange(e) {
3112
+ const query = e.target.value;
3113
+ if (query.length > 0) {
3114
+ const request = {
3115
+ input: e.target.value,
3116
+ sessionToken: this.sessionToken,
3117
+ includedRegionCodes: ['uk'],
3118
+ region: 'uk',
3119
+ includedPrimaryTypes: ['street_address', 'postal_code'],
3120
+ };
3121
+ this.autocompleteService.getPlacePredictions(request, (predictions, status) => {
3122
+ console.log(predictions);
3123
+ if (status === google.maps.places.PlacesServiceStatus.OK && predictions) {
3124
+ this.predictions = predictions;
3125
+ }
3126
+ else {
3127
+ this.predictions = [];
3128
+ }
3129
+ });
3130
+ }
3131
+ else {
3132
+ this.predictions = [];
3133
+ }
3134
+ }
3135
+ save() {
3136
+ this.propertyService.updateLocation(this.id, this.location()).subscribe({
3137
+ next: (res) => {
3138
+ this.snackBarService.open('Update successfully');
3139
+ },
3140
+ error: (error) => {
3141
+ this.snackBarService.open(error.message);
3142
+ },
3143
+ });
3144
+ }
3145
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyManageLocationComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
3146
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: PropertyManageLocationComponent, isStandalone: true, selector: "rolatech-property-manage-location", inputs: { location: { classPropertyName: "location", publicName: "location", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { location: "locationChange" }, viewQueries: [{ propertyName: "autocompleteInput", first: true, predicate: ["autocompleteInput"], descendants: true }], usesInheritance: true, ngImport: i0, template: "<rolatech-property-manage-content>\n <rolatech-toolbar title=\"Location\" class=\"hidden md:block\" divider></rolatech-toolbar>\n <mat-form-field position=\"left\">\n <input\n type=\"text\"\n placeholder=\"Search for a location\"\n #searchInput\n (input)=\"onInputChange($event)\"\n matInput\n [matAutocomplete]=\"auto\"\n />\n <mat-autocomplete\n class=\"single-autocomplete\"\n #auto=\"matAutocomplete\"\n (optionSelected)=\"onOptionSelected($event)\"\n [displayWith]=\"displayFn\"\n >\n @for (prediction of predictions; track $index) {\n <mat-option [value]=\"prediction\">\n {{ prediction.description }}\n </mat-option>\n }\n </mat-autocomplete>\n </mat-form-field>\n <div class=\"border px-3 rounded-md\">\n <div class=\"flex justify-between items-center py-3 cursor-pointer select-none\" i18n (click)=\"expand = !expand\">\n <span> Enter the address manually </span>\n <mat-icon>{{ expand ? 'keyboard_arrow_up' : 'keyboard_arrow_down' }}</mat-icon>\n </div>\n <div [@contentAnimation]=\"expand\" [class.hidden]=\"!expand\">\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Flat number </mat-label>\n <input matInput [(ngModel)]=\"location().flatNumber\" required />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Street number </mat-label>\n <input matInput [(ngModel)]=\"location().streetNumber\" required />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Building name </mat-label>\n <input matInput [(ngModel)]=\"location().buildingName\" required />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Street </mat-label>\n <input matInput [(ngModel)]=\"location().street\" required />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Town </mat-label>\n <input matInput [(ngModel)]=\"location().town\" required />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Postcode </mat-label>\n <input matInput [(ngModel)]=\"location().postCode\" required />\n </mat-form-field>\n </div>\n </div>\n <div class=\"flex justify-end items-center p-3 gap-3\">\n <button mat-flat-button i18n (click)=\"save()\">Save</button>\n </div>\n</rolatech-property-manage-content>\n", styles: ["mat-form-field{width:100%}\n"], dependencies: [{ kind: "component", type: PropertyManageContentComponent, selector: "rolatech-property-manage-content" }, { kind: "component", type: ToolbarComponent, selector: "rolatech-toolbar", inputs: ["title", "subtitle", "back", "link", "large", "divider"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.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$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2$2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2$2.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: TextFieldModule }, { kind: "ngmodule", type: MatAutocompleteModule }, { kind: "component", type: i4$1.MatAutocomplete, selector: "mat-autocomplete", inputs: ["aria-label", "aria-labelledby", "displayWith", "autoActiveFirstOption", "autoSelectActiveOption", "requireSelection", "panelWidth", "disableRipple", "class", "hideSingleSelectionIndicator"], outputs: ["optionSelected", "opened", "closed", "optionActivated"], exportAs: ["matAutocomplete"] }, { kind: "component", type: i4$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "directive", type: i4$1.MatAutocompleteTrigger, selector: "input[matAutocomplete], textarea[matAutocomplete]", inputs: ["matAutocomplete", "matAutocompletePosition", "matAutocompleteConnectedTo", "autocomplete", "matAutocompleteDisabled"], exportAs: ["matAutocompleteTrigger"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.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"] }], animations: [
3147
+ trigger('contentAnimation', [
3148
+ state('hidden', style({
3149
+ height: '0',
3150
+ opacity: 0,
3151
+ })),
3152
+ state('visible', style({
3153
+ height: '*',
3154
+ opacity: 1,
3155
+ })),
3156
+ transition('hidden <=> visible', animate('300ms ease-in-out')),
3157
+ ]),
3158
+ ] });
3159
+ }
3160
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyManageLocationComponent, decorators: [{
3161
+ type: Component,
3162
+ args: [{ selector: 'rolatech-property-manage-location', imports: [
3163
+ PropertyManageContentComponent,
3164
+ ToolbarComponent,
3165
+ FormsModule,
3166
+ MatFormFieldModule,
3167
+ ReactiveFormsModule,
3168
+ MatInputModule,
3169
+ FormsModule,
3170
+ TextFieldModule,
3171
+ MatAutocompleteModule,
3172
+ MatButtonModule,
3173
+ MatIconModule,
3174
+ ], animations: [
3175
+ trigger('contentAnimation', [
3176
+ state('hidden', style({
3177
+ height: '0',
3178
+ opacity: 0,
3179
+ })),
3180
+ state('visible', style({
3181
+ height: '*',
3182
+ opacity: 1,
3183
+ })),
3184
+ transition('hidden <=> visible', animate('300ms ease-in-out')),
3185
+ ]),
3186
+ ], template: "<rolatech-property-manage-content>\n <rolatech-toolbar title=\"Location\" class=\"hidden md:block\" divider></rolatech-toolbar>\n <mat-form-field position=\"left\">\n <input\n type=\"text\"\n placeholder=\"Search for a location\"\n #searchInput\n (input)=\"onInputChange($event)\"\n matInput\n [matAutocomplete]=\"auto\"\n />\n <mat-autocomplete\n class=\"single-autocomplete\"\n #auto=\"matAutocomplete\"\n (optionSelected)=\"onOptionSelected($event)\"\n [displayWith]=\"displayFn\"\n >\n @for (prediction of predictions; track $index) {\n <mat-option [value]=\"prediction\">\n {{ prediction.description }}\n </mat-option>\n }\n </mat-autocomplete>\n </mat-form-field>\n <div class=\"border px-3 rounded-md\">\n <div class=\"flex justify-between items-center py-3 cursor-pointer select-none\" i18n (click)=\"expand = !expand\">\n <span> Enter the address manually </span>\n <mat-icon>{{ expand ? 'keyboard_arrow_up' : 'keyboard_arrow_down' }}</mat-icon>\n </div>\n <div [@contentAnimation]=\"expand\" [class.hidden]=\"!expand\">\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Flat number </mat-label>\n <input matInput [(ngModel)]=\"location().flatNumber\" required />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Street number </mat-label>\n <input matInput [(ngModel)]=\"location().streetNumber\" required />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Building name </mat-label>\n <input matInput [(ngModel)]=\"location().buildingName\" required />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Street </mat-label>\n <input matInput [(ngModel)]=\"location().street\" required />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Town </mat-label>\n <input matInput [(ngModel)]=\"location().town\" required />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Postcode </mat-label>\n <input matInput [(ngModel)]=\"location().postCode\" required />\n </mat-form-field>\n </div>\n </div>\n <div class=\"flex justify-end items-center p-3 gap-3\">\n <button mat-flat-button i18n (click)=\"save()\">Save</button>\n </div>\n</rolatech-property-manage-content>\n", styles: ["mat-form-field{width:100%}\n"] }]
3187
+ }], propDecorators: { autocompleteInput: [{
3188
+ type: ViewChild,
3189
+ args: ['autocompleteInput', { static: false }]
3190
+ }] } });
3191
+
3192
+ class PropertyVideoTourAddComponent {
3193
+ videoUrl = '';
3194
+ videoId = '';
3195
+ output = output();
3196
+ getYouTubeVideoId(url) {
3197
+ const regex = /(?:youtube\.com\/(?:watch\?v=|embed\/|v\/|shorts\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})/;
3198
+ const match = url.match(regex);
3199
+ return match ? match[1] : null;
3200
+ }
3201
+ ngDoCheck() {
3202
+ this.videoId = this.getYouTubeVideoId(this.videoUrl);
3203
+ this.output.emit(this.videoId);
3204
+ }
3205
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyVideoTourAddComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3206
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.0.3", type: PropertyVideoTourAddComponent, isStandalone: true, selector: "rolatech-property-video-tour-add", outputs: { output: "output" }, ngImport: i0, template: "<mat-form-field appearance=\"fill\">\n <mat-label>Youtube Video URL</mat-label>\n <input matInput placeholder=\"Youtube Video URL\" type=\"text\" [(ngModel)]=\"videoUrl\" i18n />\n</mat-form-field>\n", styles: ["mat-form-field{width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2$2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2$2.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.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$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
3207
+ }
3208
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyVideoTourAddComponent, decorators: [{
3209
+ type: Component,
3210
+ args: [{ selector: 'rolatech-property-video-tour-add', imports: [CommonModule, MatFormFieldModule, MatInputModule, FormsModule], template: "<mat-form-field appearance=\"fill\">\n <mat-label>Youtube Video URL</mat-label>\n <input matInput placeholder=\"Youtube Video URL\" type=\"text\" [(ngModel)]=\"videoUrl\" i18n />\n</mat-form-field>\n", styles: ["mat-form-field{width:100%}\n"] }]
3211
+ }] });
3212
+
3213
+ class PropertyManageVideoTourComponent extends BaseComponent {
3214
+ propertyService = inject(PropertyService);
3215
+ sanitizer = inject(DomSanitizer);
3216
+ propertyVideoTour;
3217
+ data;
3218
+ iframeVisible = false;
3219
+ iframeLoaded = false;
3220
+ safeURL = this.sanitizer.bypassSecurityTrustResourceUrl('');
3221
+ thumbnailUrl;
3222
+ ngOnInit() {
3223
+ this.id = this.route.parent?.snapshot.paramMap.get('id');
3224
+ this.getVideoTour();
3225
+ }
3226
+ getVideoTour() {
3227
+ this.propertyService.getVideoTour(this.id).subscribe({
3228
+ next: (res) => {
3229
+ this.propertyVideoTour = res.data;
3230
+ this.safeURL = this.sanitizer.bypassSecurityTrustResourceUrl(`https://www.youtube.com/embed/${this.propertyVideoTour.videoId}`);
3231
+ this.thumbnailUrl = `https://i.ytimg.com/vi/${this.propertyVideoTour.videoId}/hqdefault.jpg`;
3232
+ },
3233
+ });
3234
+ }
3235
+ showIframe() {
3236
+ this.iframeVisible = true;
3237
+ }
3238
+ onIframeLoad() {
3239
+ this.iframeLoaded = true;
3240
+ }
3241
+ addVideoTour() {
3242
+ const features = {
3243
+ title: 'Add video tour',
3244
+ cancelText: 'Cancel',
3245
+ confirmText: 'Confirm',
3246
+ data: {
3247
+ videoUrl: '',
3248
+ },
3249
+ component: PropertyVideoTourAddComponent,
3250
+ };
3251
+ this.dialogService.open(features);
3252
+ this.dialogService.confirmed().subscribe({
3253
+ next: (res) => {
3254
+ if (res) {
3255
+ const data = {
3256
+ videoId: res,
3257
+ };
3258
+ this.propertyService.addVideoTour(this.id, data).subscribe({
3259
+ next: (res) => {
3260
+ this.propertyVideoTour = res.data;
3261
+ this.snackBarService.open('Video tour added');
3262
+ },
3263
+ });
3264
+ }
3265
+ },
3266
+ });
3267
+ }
3268
+ updateVideoTour() {
3269
+ const features = {
3270
+ title: 'Update video tour',
3271
+ cancelText: 'Cancel',
3272
+ confirmText: 'Confirm',
3273
+ data: {
3274
+ videoId: '',
3275
+ },
3276
+ component: PropertyVideoTourAddComponent,
3277
+ };
3278
+ this.dialogService.open(features);
3279
+ this.dialogService.confirmed().subscribe({
3280
+ next: (res) => {
3281
+ if (res) {
3282
+ const data = {
3283
+ videoId: res,
3284
+ };
3285
+ this.propertyService.addVideoTour(this.id, data).subscribe({
3286
+ next: (res) => {
3287
+ this.propertyVideoTour = res.data;
3288
+ this.snackBarService.open('Update successfully');
3289
+ },
3290
+ });
3291
+ }
3292
+ },
3293
+ });
3294
+ }
3295
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyManageVideoTourComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
3296
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: PropertyManageVideoTourComponent, isStandalone: true, selector: "rolatech-property-manage-video-tour", usesInheritance: true, ngImport: i0, template: "<rolatech-property-manage-content>\n <rolatech-toolbar title=\"Video tour\" class=\"hidden md:block\" divider> </rolatech-toolbar>\n @if (propertyVideoTour) {\n <!-- <div class=\"relative w-full aspect-video\">\n @if (!iframeVisible) {\n <ng-container>\n <img\n [src]=\"thumbnailUrl\"\n class=\"object-cover w-full h-full\"\n (click)=\"showIframe()\"\n alt=\"YouTube Video Thumbnail\"\n style=\"cursor: pointer\"\n />\n <button\n (click)=\"showIframe()\"\n class=\"absolute inset-0 flex items-center justify-center w-full h-full bg-black/40\"\n style=\"pointer-events: auto\"\n >\n <svg height=\"64\" width=\"64\" viewBox=\"0 0 68 48\">\n <path\n d=\"M66.52,7.13A8,8,0,0,0,60.56,1.19C55.34,0,34,0,34,0S12.66,0,7.44,1.19A8,8,0,0,0,1.48,7.13,84.19,84.19,0,0,0,0,24a84.19,84.19,0,0,0,1.48,16.87A8,8,0,0,0,7.44,46.81C12.66,48,34,48,34,48s21.34,0,26.56-1.19a8,8,0,0,0,6-6A84.19,84.19,0,0,0,68,24,84.19,84.19,0,0,0,66.52,7.13Z\"\n fill=\"#f00\"\n ></path>\n <polygon points=\"45 24 27 14 27 34 45 24\" fill=\"#fff\"></polygon>\n </svg>\n </button>\n </ng-container>\n } @else {\n <ng-container>\n <iframe [src]=\"safeURL\" frameborder=\"0\" allowfullscreen class=\"absolute top-0 left-0 w-full h-full\"></iframe>\n </ng-container>\n }\n </div> -->\n <div class=\"relative w-full aspect-video\">\n @if (!iframeLoaded) {\n <div class=\"absolute inset-0 flex items-center justify-center bg-black/10\">\n <span>Loading...</span>\n </div>\n }\n <iframe [src]=\"safeURL\" frameborder=\"0\" allowfullscreen class=\"w-full h-full\" (load)=\"iframeLoaded = true\"></iframe>\n </div>\n <button mat-flat-button (click)=\"updateVideoTour()\" class=\"mt-3\">Update video tour</button>\n } @else {\n <button mat-flat-button (click)=\"addVideoTour()\">Add video tour</button>\n }\n</rolatech-property-manage-content>\n", styles: [""], dependencies: [{ kind: "component", type: ToolbarComponent, selector: "rolatech-toolbar", inputs: ["title", "subtitle", "back", "link", "large", "divider"] }, { kind: "component", type: PropertyManageContentComponent, selector: "rolatech-property-manage-content" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.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"] }] });
3297
+ }
3298
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyManageVideoTourComponent, decorators: [{
3299
+ type: Component,
3300
+ args: [{ selector: 'rolatech-property-manage-video-tour', imports: [ToolbarComponent, PropertyManageContentComponent, MatButtonModule], template: "<rolatech-property-manage-content>\n <rolatech-toolbar title=\"Video tour\" class=\"hidden md:block\" divider> </rolatech-toolbar>\n @if (propertyVideoTour) {\n <!-- <div class=\"relative w-full aspect-video\">\n @if (!iframeVisible) {\n <ng-container>\n <img\n [src]=\"thumbnailUrl\"\n class=\"object-cover w-full h-full\"\n (click)=\"showIframe()\"\n alt=\"YouTube Video Thumbnail\"\n style=\"cursor: pointer\"\n />\n <button\n (click)=\"showIframe()\"\n class=\"absolute inset-0 flex items-center justify-center w-full h-full bg-black/40\"\n style=\"pointer-events: auto\"\n >\n <svg height=\"64\" width=\"64\" viewBox=\"0 0 68 48\">\n <path\n d=\"M66.52,7.13A8,8,0,0,0,60.56,1.19C55.34,0,34,0,34,0S12.66,0,7.44,1.19A8,8,0,0,0,1.48,7.13,84.19,84.19,0,0,0,0,24a84.19,84.19,0,0,0,1.48,16.87A8,8,0,0,0,7.44,46.81C12.66,48,34,48,34,48s21.34,0,26.56-1.19a8,8,0,0,0,6-6A84.19,84.19,0,0,0,68,24,84.19,84.19,0,0,0,66.52,7.13Z\"\n fill=\"#f00\"\n ></path>\n <polygon points=\"45 24 27 14 27 34 45 24\" fill=\"#fff\"></polygon>\n </svg>\n </button>\n </ng-container>\n } @else {\n <ng-container>\n <iframe [src]=\"safeURL\" frameborder=\"0\" allowfullscreen class=\"absolute top-0 left-0 w-full h-full\"></iframe>\n </ng-container>\n }\n </div> -->\n <div class=\"relative w-full aspect-video\">\n @if (!iframeLoaded) {\n <div class=\"absolute inset-0 flex items-center justify-center bg-black/10\">\n <span>Loading...</span>\n </div>\n }\n <iframe [src]=\"safeURL\" frameborder=\"0\" allowfullscreen class=\"w-full h-full\" (load)=\"iframeLoaded = true\"></iframe>\n </div>\n <button mat-flat-button (click)=\"updateVideoTour()\" class=\"mt-3\">Update video tour</button>\n } @else {\n <button mat-flat-button (click)=\"addVideoTour()\">Add video tour</button>\n }\n</rolatech-property-manage-content>\n" }]
3301
+ }] });
3302
+
3303
+ const propertyManageRoutes = [
3304
+ {
3305
+ path: '',
3306
+ component: PropertyManageIndexComponent,
3307
+ },
3308
+ {
3309
+ path: 'create',
3310
+ component: PropertyManageCreateComponent,
3311
+ },
3312
+ {
3313
+ path: ':id/manage',
3314
+ component: PropertyManageLayoutComponent,
3315
+ children: [
3316
+ {
3317
+ path: 'info',
3318
+ component: PropertyManageInfoComponent,
3319
+ },
3320
+ {
3321
+ path: 'media',
3322
+ component: PropertyManageMediaComponent,
3323
+ },
3324
+ {
3325
+ path: 'video-tour',
3326
+ component: PropertyManageVideoTourComponent,
3327
+ },
3328
+ {
3329
+ path: 'sections',
3330
+ component: PropertyManageSectionsComponent,
3331
+ },
3332
+ {
3333
+ path: 'location',
3334
+ component: PropertyManageLocationComponent,
3335
+ },
3336
+ {
3337
+ path: 'features',
3338
+ component: PropertyManageFeaturesComponent,
3339
+ },
3340
+ // {
3341
+ // path: 'amenities',
3342
+ // component: PropertyManageAmenitiesComponent,
3343
+ // },
3344
+ // {
3345
+ // path: 'options',
3346
+ // component: PropertyManageOptionsComponent,
3347
+ // },
3348
+ {
3349
+ path: 'pricing',
3350
+ component: PropertyManagePricingComponent,
3351
+ },
3352
+ {
3353
+ path: 'schedule',
3354
+ component: PropertyManageScheduleComponent,
3355
+ },
3356
+ ],
3357
+ },
3358
+ ];
3359
+
3360
+ class PropertyViewingConfirmationComponent {
3361
+ output = output();
3362
+ proposedTime = model();
3363
+ selectedTime = model();
3364
+ ngDoCheck() {
3365
+ this.output.emit(true);
3366
+ }
3367
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyViewingConfirmationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3368
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: PropertyViewingConfirmationComponent, isStandalone: true, selector: "rolatech-property-viewing-confirmation", inputs: { proposedTime: { classPropertyName: "proposedTime", publicName: "proposedTime", isSignal: true, isRequired: false, transformFunction: null }, selectedTime: { classPropertyName: "selectedTime", publicName: "selectedTime", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { output: "output", proposedTime: "proposedTimeChange", selectedTime: "selectedTimeChange" }, ngImport: i0, template: "<div>\n @if (proposedTime()) {\n <div>\n <span> {{ proposedTime().date }} </span><span>{{ proposedTime().time }}</span>\n </div>\n }\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
3369
+ }
3370
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyViewingConfirmationComponent, decorators: [{
3371
+ type: Component,
3372
+ args: [{ selector: 'rolatech-property-viewing-confirmation', imports: [CommonModule], template: "<div>\n @if (proposedTime()) {\n <div>\n <span> {{ proposedTime().date }} </span><span>{{ proposedTime().time }}</span>\n </div>\n }\n</div>\n" }]
3373
+ }] });
3374
+
3375
+ class PropertyManageViewingsDetailComponent extends BaseComponent {
3376
+ propertyService = inject(PropertyService);
3377
+ authUserService = inject(AuthUserService);
3378
+ viewing;
3379
+ name = '';
3380
+ property;
3381
+ status = PropertyViewingStatus;
3382
+ employmentStatus = EmploymentStatus;
3383
+ propertyViewerCategory = PropertyViewerCategory;
3384
+ agent;
3385
+ viewingTimeConfirmed = false;
3386
+ selectedSlotId = '';
3387
+ ngOnInit() {
3388
+ this.getViewing();
3389
+ }
3390
+ getViewing() {
3391
+ this.propertyService.getViewing(this.id).subscribe({
3392
+ next: (res) => {
3393
+ this.viewing = res.data;
3394
+ this.name = this.viewing.firstName + ', ' + this.viewing.lastName;
3395
+ this.getProperty(this.viewing.propertyId);
3396
+ this.viewingTimeConfirmed = this.viewing.viewingDate ? true : false;
3397
+ },
3398
+ });
3399
+ }
3400
+ getProperty(propertyId) {
3401
+ this.propertyService.get(propertyId).subscribe({
3402
+ next: (res) => {
3403
+ this.property = res.data;
3404
+ this.getAgentPublicInfo(this.property.agentId);
3405
+ },
3406
+ });
3407
+ }
3408
+ getAgentPublicInfo(agentId) {
3409
+ this.authUserService.getPublicUserInfo(agentId).subscribe({
3410
+ next: (res) => {
3411
+ this.agent = res;
3412
+ },
3413
+ });
3414
+ }
3415
+ findConfirmedViewingTime() {
3416
+ const viewingProposedSlot = this.viewing.proposedSlots.find((item) => {
3417
+ return item.date === this.viewing.viewingDate && item.time === this.viewing.viewingTime;
3418
+ });
3419
+ this.selectedSlotId = viewingProposedSlot.id;
3420
+ }
3421
+ confirmViewingTime(item) {
3422
+ const options = {
3423
+ title: 'Confirm viewing time',
3424
+ component: PropertyViewingConfirmationComponent,
3425
+ data: {
3426
+ proposedTime: item,
3427
+ },
3428
+ };
3429
+ this.dialogService.open(options);
3430
+ this.dialogService.confirmed().subscribe({
3431
+ next: (result) => {
3432
+ if (result) {
3433
+ console.log(result);
3434
+ this.propertyService.confirmViewing(this.id, item.id).subscribe({
3435
+ next: (res) => {
3436
+ this.viewingTimeConfirmed = true;
3437
+ },
3438
+ });
3439
+ }
3440
+ },
3441
+ });
3442
+ }
3443
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyManageViewingsDetailComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
3444
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: PropertyManageViewingsDetailComponent, isStandalone: true, selector: "rolatech-property-manage-viewings-detail", usesInheritance: true, ngImport: i0, template: "@if (viewing) {\n <rolatech-toolbar [title]=\"status[viewing.status]\" large link=\"../\"></rolatech-toolbar>\n <div class=\"px-4\">\n <div>\n <div class=\"text-lg font-bold py-2\" i18n>Viewer</div>\n <hr class=\"mb-2\" />\n <div class=\"flex flex-col md:flex-row gap-1 md:gap-3\">\n <rolatech-rich-label label=\"Name\" [title]=\"name\"></rolatech-rich-label>\n <rolatech-rich-label label=\"Email\" [title]=\"viewing.email\"></rolatech-rich-label>\n <rolatech-rich-label label=\"Phone\" [title]=\"viewing.phone\"></rolatech-rich-label>\n </div>\n </div>\n <div>\n <div class=\"text-lg font-bold py-2\" i18n>Proposed times</div>\n <hr class=\"mb-2\" />\n @for (item of viewing.proposedSlots; track $index) {\n <div class=\"flex flex-row items-center gap-3 py-3\">\n <rolatech-rich-label label=\"Date\" [title]=\"item.date\"></rolatech-rich-label>\n <rolatech-rich-label label=\"Time\" [title]=\"item.time\"></rolatech-rich-label>\n @if (viewing.viewingDate && viewing.viewingTime) {\n @if (item.date === viewing.viewingDate && item.time === viewing.viewingTime) {\n <div class=\"ml-3\"><button mat-flat-button i18n disabled=\"\">Confirmed</button></div>\n }\n }\n @if (!viewingTimeConfirmed) {\n <div class=\"ml-3\"><button mat-flat-button (click)=\"confirmViewingTime(item)\" i18n>Confirm</button></div>\n }\n </div>\n }\n </div>\n <!-- Introducing agent -->\n @if (viewing.viewerCategory.toString() === 'AGENT') {\n <div>\n <div class=\"text-lg font-bold py-2\" i18n>Qualification</div>\n <hr class=\"mb-2\" />\n <div class=\"flex flex-col md:flex-row gap-1 md:gap-3\">\n <!-- <rolatech-rich-label label=\"Move-in date\" [title]=\"viewing.startDate\"></rolatech-rich-label> -->\n <rolatech-rich-label label=\"Tenancy duration\" [title]=\"viewing.tenancyDuration\"></rolatech-rich-label>\n <rolatech-rich-label label=\"Number of tenants\" [title]=\"viewing.numberOfTenants\"></rolatech-rich-label>\n </div>\n <div class=\"flex flex-col md:flex-row gap-1 md:gap-3\">\n <rolatech-rich-label\n label=\"Employment status\"\n [title]=\"employmentStatus[viewing.employmentStatus]\"\n ></rolatech-rich-label>\n <!-- <rolatech-rich-label label=\"Employer\" [title]=\"viewing.startDate\"></rolatech-rich-label> -->\n <rolatech-rich-label label=\"Annual income\" [title]=\"viewing.income\"></rolatech-rich-label>\n </div>\n </div>\n }\n\n <div>\n <div class=\"text-lg font-bold py-2\" i18n>Property details</div>\n <hr class=\"mb-2\" />\n @if (property) {\n <div class=\"flex items-center py-2\">\n <div class=\"min-w-16 w-24 object-cover aspect-video rounded-md mr-3\">\n @defer (on viewport()) {\n <rolatech-thumbnail [src]=\"property.media[0].url\" size=\"medium\" mode=\"full\"> </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 <div class=\"flex w-full justify-between\">\n <div class=\"flex justify-between w-full\">\n <div class=\"flex flex-col\">\n <div>{{ property.title }}</div>\n <div class=\"inline-flex gap-1 mt-2\">\n <div>\n <span class=\"mr-1\">{{ property.bedrooms }}</span>\n <span i18n>Bedrooms</span>\n </div>\n <div>\n <span class=\"mr-1\">{{ property.bathrooms }}</span>\n <span i18n>Bathrooms</span>\n </div>\n </div>\n </div>\n <div class=\"text-right\">\n <div class=\"text-sm\">\u00A3{{ property.price | fixed }}</div>\n </div>\n </div>\n </div>\n <div class=\"hidden md:flex flex-col px-3\"></div>\n </div>\n }\n </div>\n\n <div>\n <div class=\"text-lg font-bold py-2\" i18n>Viewing agent</div>\n <hr class=\"mb-2\" />\n @if (agent) {\n <div class=\"flex flex-col md:flex-row gap-1 md:gap-3\">\n <rolatech-rich-label label=\"Name\" [title]=\"agent.name\"></rolatech-rich-label>\n <rolatech-rich-label label=\"Email\" [title]=\"agent.email\"></rolatech-rich-label>\n <rolatech-rich-label label=\"Phone\" [title]=\"agent.phone\"></rolatech-rich-label>\n </div>\n }\n </div>\n </div>\n}\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: ToolbarComponent, selector: "rolatech-toolbar", inputs: ["title", "subtitle", "back", "link", "large", "divider"] }, { kind: "component", type: RichLabelComponent, selector: "rolatech-rich-label", inputs: ["label", "title"] }, { kind: "pipe", type: FixedPipe, name: "fixed" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.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"] }], deferBlockDependencies: [() => [ThumbnailComponent]] });
3445
+ }
3446
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: PropertyManageViewingsDetailComponent, decorators: [{
3447
+ type: Component,
3448
+ args: [{ selector: 'rolatech-property-manage-viewings-detail', imports: [CommonModule, ToolbarComponent, RichLabelComponent, ThumbnailComponent, FixedPipe, MatButtonModule], template: "@if (viewing) {\n <rolatech-toolbar [title]=\"status[viewing.status]\" large link=\"../\"></rolatech-toolbar>\n <div class=\"px-4\">\n <div>\n <div class=\"text-lg font-bold py-2\" i18n>Viewer</div>\n <hr class=\"mb-2\" />\n <div class=\"flex flex-col md:flex-row gap-1 md:gap-3\">\n <rolatech-rich-label label=\"Name\" [title]=\"name\"></rolatech-rich-label>\n <rolatech-rich-label label=\"Email\" [title]=\"viewing.email\"></rolatech-rich-label>\n <rolatech-rich-label label=\"Phone\" [title]=\"viewing.phone\"></rolatech-rich-label>\n </div>\n </div>\n <div>\n <div class=\"text-lg font-bold py-2\" i18n>Proposed times</div>\n <hr class=\"mb-2\" />\n @for (item of viewing.proposedSlots; track $index) {\n <div class=\"flex flex-row items-center gap-3 py-3\">\n <rolatech-rich-label label=\"Date\" [title]=\"item.date\"></rolatech-rich-label>\n <rolatech-rich-label label=\"Time\" [title]=\"item.time\"></rolatech-rich-label>\n @if (viewing.viewingDate && viewing.viewingTime) {\n @if (item.date === viewing.viewingDate && item.time === viewing.viewingTime) {\n <div class=\"ml-3\"><button mat-flat-button i18n disabled=\"\">Confirmed</button></div>\n }\n }\n @if (!viewingTimeConfirmed) {\n <div class=\"ml-3\"><button mat-flat-button (click)=\"confirmViewingTime(item)\" i18n>Confirm</button></div>\n }\n </div>\n }\n </div>\n <!-- Introducing agent -->\n @if (viewing.viewerCategory.toString() === 'AGENT') {\n <div>\n <div class=\"text-lg font-bold py-2\" i18n>Qualification</div>\n <hr class=\"mb-2\" />\n <div class=\"flex flex-col md:flex-row gap-1 md:gap-3\">\n <!-- <rolatech-rich-label label=\"Move-in date\" [title]=\"viewing.startDate\"></rolatech-rich-label> -->\n <rolatech-rich-label label=\"Tenancy duration\" [title]=\"viewing.tenancyDuration\"></rolatech-rich-label>\n <rolatech-rich-label label=\"Number of tenants\" [title]=\"viewing.numberOfTenants\"></rolatech-rich-label>\n </div>\n <div class=\"flex flex-col md:flex-row gap-1 md:gap-3\">\n <rolatech-rich-label\n label=\"Employment status\"\n [title]=\"employmentStatus[viewing.employmentStatus]\"\n ></rolatech-rich-label>\n <!-- <rolatech-rich-label label=\"Employer\" [title]=\"viewing.startDate\"></rolatech-rich-label> -->\n <rolatech-rich-label label=\"Annual income\" [title]=\"viewing.income\"></rolatech-rich-label>\n </div>\n </div>\n }\n\n <div>\n <div class=\"text-lg font-bold py-2\" i18n>Property details</div>\n <hr class=\"mb-2\" />\n @if (property) {\n <div class=\"flex items-center py-2\">\n <div class=\"min-w-16 w-24 object-cover aspect-video rounded-md mr-3\">\n @defer (on viewport()) {\n <rolatech-thumbnail [src]=\"property.media[0].url\" size=\"medium\" mode=\"full\"> </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 <div class=\"flex w-full justify-between\">\n <div class=\"flex justify-between w-full\">\n <div class=\"flex flex-col\">\n <div>{{ property.title }}</div>\n <div class=\"inline-flex gap-1 mt-2\">\n <div>\n <span class=\"mr-1\">{{ property.bedrooms }}</span>\n <span i18n>Bedrooms</span>\n </div>\n <div>\n <span class=\"mr-1\">{{ property.bathrooms }}</span>\n <span i18n>Bathrooms</span>\n </div>\n </div>\n </div>\n <div class=\"text-right\">\n <div class=\"text-sm\">\u00A3{{ property.price | fixed }}</div>\n </div>\n </div>\n </div>\n <div class=\"hidden md:flex flex-col px-3\"></div>\n </div>\n }\n </div>\n\n <div>\n <div class=\"text-lg font-bold py-2\" i18n>Viewing agent</div>\n <hr class=\"mb-2\" />\n @if (agent) {\n <div class=\"flex flex-col md:flex-row gap-1 md:gap-3\">\n <rolatech-rich-label label=\"Name\" [title]=\"agent.name\"></rolatech-rich-label>\n <rolatech-rich-label label=\"Email\" [title]=\"agent.email\"></rolatech-rich-label>\n <rolatech-rich-label label=\"Phone\" [title]=\"agent.phone\"></rolatech-rich-label>\n </div>\n }\n </div>\n </div>\n}\n" }]
3449
+ }] });
3450
+
3451
+ const propertyManageViewingsRoutes = [
3452
+ {
3453
+ path: '',
3454
+ loadComponent: () => import('./rolatech-angular-property-property-manage-viewings-index.component-Clazb8j0.mjs').then((x) => x.PropertyManageViewingsIndexComponent),
3455
+ },
3456
+ {
3457
+ path: ':id',
3458
+ component: PropertyManageViewingsDetailComponent,
3459
+ },
3460
+ ];
3461
+
3462
+ /**
3463
+ * Generated bundle index. Do not edit.
3464
+ */
3465
+
3466
+ export { AdverseCreditStatus as A, EmploymentStatus as E, PropertyViewingItemComponent as P, ResidencyStatus as R, ViewingTime as V, propertyManageOffersRoutes as a, propertyRoutes as b, propertyManageRoutes as c, propertyManageViewingsRoutes as d, PropertyActionsComponent as e, featureManageRoutes as f, PropertyItemComponent as g, PropertyPricingComponent as h, PropertyPriceType as i, PropertyVideoTourType as j, PropertyStatus as k, PropertyType as l, PropertyInventoryStatus as m, PropertyScope as n, PropertyViewingStatus as o, propertyReviewRoutes as p, PropertyViewerCategory as q, PropertyOfferTimelineStatus as r, PropertyOfferType as s, PropertyOfferStatus as t, PropertyApplicantType as u };
3467
+ //# sourceMappingURL=rolatech-angular-property-rolatech-angular-property-CO7GI4x7.mjs.map