Skip to content

Commit 93a7f45

Browse files
committed
Merge branch 'develop' into custom/HIO687AS
2 parents 1ef8cf6 + 2b2e198 commit 93a7f45

12 files changed

Lines changed: 326 additions & 25 deletions

File tree

apps/concierge/src/app/desks/desk-qr-code-modal.component.ts

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Component, computed, inject } from '@angular/core';
1+
import { Component, ViewEncapsulation, computed, inject } from '@angular/core';
22
import { SettingsService } from '@placeos/common';
33
import { DesksStateService } from './desks-state.service';
44

@@ -22,15 +22,13 @@ import { IconComponent, SafePipe, TranslatePipe } from '@placeos/components';
2222
<icon>close</icon>
2323
</button>
2424
</div>
25-
<div
26-
class="flex h-[calc(100vh-5rem)] flex-wrap overflow-auto print:h-auto"
27-
>
25+
<div class="desk-qr-list flex h-[calc(100vh-5rem)] flex-wrap overflow-auto">
2826
@for (desk of desks(); track desk) {
2927
<a
3028
[href]="desk.qr_link | safe: 'url'"
3129
target="_blank"
3230
ref="noopener noreferrer"
33-
class="mx-auto flex w-[28%] flex-col items-center justify-center landscape:w-[21%] print:h-[25vh] print:landscape:h-[33.33vh]"
31+
class="desk-qr-item mx-auto flex w-[28%] flex-col items-center justify-center landscape:w-[21%]"
3432
>
3533
<div
3634
class="border-base-200 bg-base-100 mx-4 my-2 block rounded-lg border p-2"
@@ -47,7 +45,52 @@ import { IconComponent, SafePipe, TranslatePipe } from '@placeos/components';
4745
</div>
4846
</div>
4947
`,
50-
styles: [``],
48+
styles: [
49+
`
50+
.desk-qr-body-print {
51+
display: none;
52+
}
53+
54+
body.desk-qr-printing .desk-qr-body-print {
55+
display: block;
56+
}
57+
58+
@media print {
59+
@page {
60+
margin: 8mm;
61+
}
62+
63+
body.desk-qr-printing > * {
64+
display: none !important;
65+
}
66+
67+
body.desk-qr-printing .desk-qr-body-print {
68+
display: block !important;
69+
position: static;
70+
inset: auto;
71+
width: 100%;
72+
margin: 0;
73+
padding: 0;
74+
}
75+
76+
body.desk-qr-printing .desk-qr-body-print .desk-qr-list {
77+
display: grid !important;
78+
grid-template-columns: repeat(3, minmax(0, 1fr));
79+
gap: 0.25rem;
80+
height: auto !important;
81+
overflow: visible !important;
82+
}
83+
84+
body.desk-qr-printing .desk-qr-body-print .desk-qr-item {
85+
width: 100% !important;
86+
margin: 0 !important;
87+
break-inside: avoid;
88+
page-break-inside: avoid;
89+
}
90+
}
91+
`,
92+
],
93+
encapsulation: ViewEncapsulation.None,
5194
imports: [
5295
CommonModule,
5396
MatDialogModule,
@@ -61,7 +104,20 @@ export class DeskQrCodeModalComponent {
61104
private _settings = inject(SettingsService);
62105
private _state = inject(DesksStateService);
63106

64-
public readonly print = () => window.print();
107+
public print() {
108+
this.desks();
109+
const source = document.querySelector('.desk-qr-list') as HTMLElement;
110+
if (!source) return window.print();
111+
this._cleanupPrintView();
112+
const print_root = document.createElement('div');
113+
print_root.className = 'desk-qr-body-print';
114+
print_root.appendChild(source.cloneNode(true));
115+
document.body.appendChild(print_root);
116+
document.body.classList.add('desk-qr-printing');
117+
const on_after_print = () => this._cleanupPrintView();
118+
window.addEventListener('afterprint', on_after_print, { once: true });
119+
setTimeout(() => window.print(), 50);
120+
}
65121

66122
public readonly desks = computed(() =>
67123
this._state.desks().map((_) => {
@@ -84,4 +140,10 @@ export class DeskQrCodeModalComponent {
84140
item.qr_code = generateQRCode(link);
85141
return item.qr_code;
86142
}
143+
144+
private _cleanupPrintView() {
145+
document.body.classList.remove('desk-qr-printing');
146+
const print_root = document.querySelector('.desk-qr-body-print');
147+
if (print_root) print_root.remove();
148+
}
87149
}

apps/concierge/src/app/email-templates/email-template-manage.component.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
extractTextFromHTML,
2020
i18n,
2121
nextValueFrom,
22+
notifyError,
2223
notifySuccess,
2324
OrganisationService,
2425
} from '@placeos/common';
@@ -57,22 +58,22 @@ import {
5758
class="relative z-10 mx-auto w-full max-w-160 overflow-visible p-2"
5859
[formGroup]="form"
5960
>
60-
<div class="flex items-center space-x-4">
61-
<div class="w-1/4 flex-1 space-y-2">
61+
<div class="mb-2 flex items-center gap-2">
62+
<div class="w-1/4 flex-1 gap-2">
6263
<label for="zone">
6364
{{ 'RESOURCE.BUILDING' | translate }}
6465
</label>
65-
<mat-form-field appearance="outline" class="w-full">
66+
<mat-form-field
67+
appearance="outline"
68+
class="no-subscript w-full"
69+
>
6670
<mat-select
6771
name="zone"
6872
[placeholder]="
6973
'COMMON.BUILDING_SELECT' | translate
7074
"
7175
formControlName="zone_id"
7276
>
73-
<mat-option value="">{{
74-
'COMMON.BUILDING_EMPTY' | translate
75-
}}</mat-option>
7677
@for (bld of buildings | async; track bld) {
7778
<mat-option [value]="bld.id">
7879
{{ bld.display_name || bld.name }}
@@ -84,7 +85,7 @@ import {
8485
}}</mat-error>
8586
</mat-form-field>
8687
</div>
87-
<div class="w-1/4 flex-1 space-y-2 pb-6">
88+
<div class="w-1/4 flex-1 gap-2">
8889
<label for="trigger">
8990
{{ 'COMMON.TRIGGER' | translate }}
9091
</label>
@@ -167,7 +168,7 @@ import {
167168
<button
168169
btn
169170
matRipple
170-
class="mt-2 flex-1"
171+
class="mt-5.5 flex-1"
171172
matTooltip="Values that get replaced in the email template when sent"
172173
[disabled]="!form.value.trigger"
173174
[matMenuTriggerFor]="tracking_menu"
@@ -383,6 +384,7 @@ export class EmailTemplateManageComponent
383384
)
384385
.catch((e) => {
385386
this.loading = '';
387+
notifyError(i18n(e));
386388
throw e;
387389
});
388390
this.loading = '';

apps/concierge/src/app/email-templates/email-templates-state.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ export class EmailTemplatesStateService extends AsyncHandler {
178178
}
179179

180180
public async saveTemplate(template: EmailTemplate, old_zone = '') {
181-
if (!template.zone_id) return;
181+
if (!template.zone_id) throw 'A building is required';
182182
if (template.id && old_zone) {
183183
const old_metadata = await lastValueFrom(
184184
showMetadata(old_zone, 'email_templates'),

apps/concierge/src/app/ui/app-settings/concierge-settings-form-modal.component.ts

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Component, inject, OnInit, signal } from '@angular/core';
1+
import { Component, computed, inject, OnInit, signal } from '@angular/core';
22
import {
33
FormControl,
44
FormGroup,
@@ -11,6 +11,7 @@ import {
1111
MatDialogRef,
1212
} from '@angular/material/dialog';
1313
import {
14+
buildCurrencyOptions,
1415
currentUser,
1516
notifySuccess,
1617
OrganisationService,
@@ -314,6 +315,49 @@ import { UploadButtonComponent } from './upload-button.component';
314315
</mat-hint>
315316
</mat-form-field>
316317
</div>
318+
<div>
319+
<label for="currency">Currency</label>
320+
<mat-form-field appearance="outline" class="w-full">
321+
<mat-select
322+
name="currency"
323+
formControlName="currency"
324+
placeholder="Select currency code"
325+
(openedChange)="
326+
onCurrencySelectStateChange($event)
327+
"
328+
>
329+
<mat-option disabled class="!h-auto !py-2">
330+
<input
331+
matInput
332+
placeholder="Search currency code or name"
333+
[ngModel]="currency_filter()"
334+
(ngModelChange)="
335+
updateCurrencyFilter($event)
336+
"
337+
[ngModelOptions]="{ standalone: true }"
338+
(click)="$event.stopPropagation()"
339+
(keydown)="$event.stopPropagation()"
340+
/>
341+
</mat-option>
342+
@for (
343+
option of filtered_currency_options();
344+
track option.code
345+
) {
346+
<mat-option [value]="option.code">
347+
{{ option.display_name }}
348+
</mat-option>
349+
}
350+
@if (!filtered_currency_options().length) {
351+
<mat-option disabled>
352+
No currencies match your search
353+
</mat-option>
354+
}
355+
</mat-select>
356+
<mat-hint>
357+
ISO 4217 currency code for pricing
358+
</mat-hint>
359+
</mat-form-field>
360+
</div>
317361
<div class="-mx-2 flex flex-wrap items-center">
318362
<settings-toggle
319363
name="Use 24 hour time"
@@ -1301,6 +1345,15 @@ export class ConciergeSettingsFormModalComponent implements OnInit {
13011345
public readonly zone = this._data.zone;
13021346
public readonly loading = signal('');
13031347
public readonly shown_group = signal('');
1348+
public readonly currency_filter = signal('');
1349+
public readonly currency_options = buildCurrencyOptions();
1350+
public readonly filtered_currency_options = computed(() => {
1351+
const filter_text = this.currency_filter().trim().toLowerCase();
1352+
if (!filter_text) return this.currency_options;
1353+
return this.currency_options.filter((option) =>
1354+
option.search_text.includes(filter_text),
1355+
);
1356+
});
13041357
public readonly settings_key =
13051358
this._settings.get('app.concierge_metadata_key') || 'concierge_app';
13061359

@@ -1315,6 +1368,7 @@ export class ConciergeSettingsFormModalComponent implements OnInit {
13151368
force_upload_state: new FormControl(false),
13161369
private_uploads: new FormControl(false),
13171370
week_start: new FormControl(0),
1371+
currency: new FormControl('USD'),
13181372
use_region: new FormControl(false),
13191373
group_events_calendar: new FormControl(''),
13201374
kiosk_url_path: new FormControl(''),
@@ -1401,6 +1455,16 @@ export class ConciergeSettingsFormModalComponent implements OnInit {
14011455
this.shown_group.update((shown) => (group === shown ? '' : group));
14021456
}
14031457

1458+
public updateCurrencyFilter(value: string) {
1459+
this.currency_filter.set((value || '').trim());
1460+
}
1461+
1462+
public onCurrencySelectStateChange(is_open: boolean) {
1463+
if (!is_open) {
1464+
this.currency_filter.set('');
1465+
}
1466+
}
1467+
14041468
public async save() {
14051469
this.loading.set('Saving settings...');
14061470
const zone = this._data.zone;

apps/concierge/src/app/ui/app-settings/workplace-settings-form-modal.component.ts

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Component, inject, OnInit, signal } from '@angular/core';
1+
import { Component, computed, inject, OnInit, signal } from '@angular/core';
22
import {
33
FormControl,
44
FormGroup,
@@ -15,6 +15,7 @@ import { PlaceZone, showMetadata, updateMetadata } from '@placeos/ts-client';
1515
import { map } from 'rxjs/operators';
1616

1717
import {
18+
buildCurrencyOptions,
1819
currentUser,
1920
notifySuccess,
2021
OrganisationService,
@@ -325,6 +326,49 @@ import { UploadButtonComponent } from './upload-button.component';
325326
</mat-hint>
326327
</mat-form-field>
327328
</div>
329+
<div>
330+
<label for="currency">Currency</label>
331+
<mat-form-field appearance="outline" class="w-full">
332+
<mat-select
333+
name="currency"
334+
formControlName="currency"
335+
placeholder="Select currency code"
336+
(openedChange)="
337+
onCurrencySelectStateChange($event)
338+
"
339+
>
340+
<mat-option disabled class="!h-auto !py-2">
341+
<input
342+
matInput
343+
placeholder="Search currency code or name"
344+
[ngModel]="currency_filter()"
345+
(ngModelChange)="
346+
updateCurrencyFilter($event)
347+
"
348+
[ngModelOptions]="{ standalone: true }"
349+
(click)="$event.stopPropagation()"
350+
(keydown)="$event.stopPropagation()"
351+
/>
352+
</mat-option>
353+
@for (
354+
option of filtered_currency_options();
355+
track option.code
356+
) {
357+
<mat-option [value]="option.code">
358+
{{ option.display_name }}
359+
</mat-option>
360+
}
361+
@if (!filtered_currency_options().length) {
362+
<mat-option disabled>
363+
No currencies match your search
364+
</mat-option>
365+
}
366+
</mat-select>
367+
<mat-hint>
368+
ISO 4217 currency code for pricing
369+
</mat-hint>
370+
</mat-form-field>
371+
</div>
328372
<div>
329373
<label for="locales">Locales</label>
330374
<mat-form-field appearance="outline" class="w-full">
@@ -1766,6 +1810,15 @@ export class WorkplaceSettingsFormModalComponent implements OnInit {
17661810
public old_settings: Record<string, any> = {};
17671811
public readonly loading = signal('');
17681812
public readonly shown_group = signal<string>('');
1813+
public readonly currency_filter = signal('');
1814+
public readonly currency_options = buildCurrencyOptions();
1815+
public readonly filtered_currency_options = computed(() => {
1816+
const filter_text = this.currency_filter().trim().toLowerCase();
1817+
if (!filter_text) return this.currency_options;
1818+
return this.currency_options.filter((option) =>
1819+
option.search_text.includes(filter_text),
1820+
);
1821+
});
17691822
public readonly zone = this._data.zone;
17701823
public readonly settings_key =
17711824
this._settings.get('app.workplace_metadata_key') || 'workplace_app';
@@ -1796,6 +1849,7 @@ export class WorkplaceSettingsFormModalComponent implements OnInit {
17961849
external_support_url: new FormControl('', [validateURL]),
17971850
support_email: new FormControl('', [Validators.email]),
17981851
catering_provider: new FormControl(''),
1852+
currency: new FormControl('USD'),
17991853
departments: new FormGroup<Record<string, any>>({}),
18001854
week_start: new FormControl(0),
18011855
locales: new FormControl([]),
@@ -1934,6 +1988,16 @@ export class WorkplaceSettingsFormModalComponent implements OnInit {
19341988
this.shown_group.update((shown) => (group === shown ? '' : group));
19351989
}
19361990

1991+
public updateCurrencyFilter(value: string) {
1992+
this.currency_filter.set((value || '').trim());
1993+
}
1994+
1995+
public onCurrencySelectStateChange(is_open: boolean) {
1996+
if (!is_open) {
1997+
this.currency_filter.set('');
1998+
}
1999+
}
2000+
19372001
public async save() {
19382002
this.loading.set('Saving settings...');
19392003
const zone = this._data.zone;

0 commit comments

Comments
 (0)