edit
@@ -242,27 +170,21 @@ import {
styles: [``],
imports: [
SimpleTableComponent,
- AuthenticatedImageDirective,
MatMenuModule,
TranslatePipe,
IconComponent,
- LevelPipe,
+ AppSettingsMenuComponent,
],
})
export class BuildingListComponent {
private _manager = inject(BuildingManagementService);
private _clipboard = inject(Clipboard);
- private _dialog = inject(MatDialog);
public readonly buildings = this._manager.filtered_buildings;
- public settings = {};
public readonly editBuilding = (building) =>
this._manager.editBuilding(building);
- public readonly editBuildingMetadata = (building) =>
- this._manager.editBuildingMetadata(building);
-
public readonly removeBuilding = (building) =>
this._manager.removeBuilding(building);
@@ -279,36 +201,4 @@ export class BuildingListComponent {
const success = this._clipboard.copy(id);
if (success) notifySuccess(i18n('APP.CONCIERGE.BUILDINGS_COPIED_ID'));
};
-
- public async editWorkplaceSettings(zone) {
- const { WorkplaceSettingsFormModalComponent } =
- await import('../ui/app-settings/workplace-settings-form-modal.component');
- this._dialog.open(WorkplaceSettingsFormModalComponent, {
- data: { zone },
- });
- }
-
- public async editConciergeSettings(zone) {
- const { ConciergeSettingsFormModalComponent } =
- await import('../ui/app-settings/concierge-settings-form-modal.component');
- this._dialog.open(ConciergeSettingsFormModalComponent, {
- data: { zone },
- });
- }
-
- public async editBookingPanelSettings(zone) {
- const { BookingPanelSettingsModalComponent } =
- await import('../ui/app-settings/booking-panel-settings-modal.component');
- this._dialog.open(BookingPanelSettingsModalComponent, {
- data: { zone },
- });
- }
-
- public async editVisitorKioskSettings(zone) {
- const { VisitorKioskSettingsFormModalComponent } =
- await import('../ui/app-settings/visitor-kiosk-settings-form-modal.component');
- this._dialog.open(VisitorKioskSettingsFormModalComponent, {
- data: { zone },
- });
- }
}
diff --git a/apps/concierge/src/app/building-manager/building-management.service.ts b/apps/concierge/src/app/building-manager/building-management.service.ts
index d2f85b14f66..2ffd2809c7d 100644
--- a/apps/concierge/src/app/building-manager/building-management.service.ts
+++ b/apps/concierge/src/app/building-manager/building-management.service.ts
@@ -68,17 +68,6 @@ export class BuildingManagementService {
});
}
- public async editBuildingMetadata(zone: PlaceZone = new PlaceZone()) {
- const { AppSettingsModalComponent } =
- await import('../ui/app-settings-modal.component');
- const ref = this._dialog.open(AppSettingsModalComponent, {
- data: { zone },
- });
- ref.afterClosed().subscribe((data) => {
- if (data) setTimeout(() => location.reload(), 300);
- });
- }
-
public async setAutoRelease(building: PlaceZone) {
if (!building?.id) return;
// Loads on demand, so the zone screens do not download its YAML parser
diff --git a/apps/concierge/src/app/building-manager/induction-settings-modal.component.ts b/apps/concierge/src/app/building-manager/induction-settings-modal.component.ts
index a7a2f662060..b4839af8439 100644
--- a/apps/concierge/src/app/building-manager/induction-settings-modal.component.ts
+++ b/apps/concierge/src/app/building-manager/induction-settings-modal.component.ts
@@ -22,6 +22,7 @@ import {
TranslatePipe,
} from '@placeos/components';
import { showMetadata, updateMetadata } from '@placeos/ts-client';
+import { appMetadataKey } from '../ui/app-settings/app-settings.utilities';
import { errorText } from '../ui/modal-actions';
@Component({
@@ -114,10 +115,10 @@ export class InductionSettingsModalComponent implements OnInit {
public async loadSettings() {
this.loading.set(i18n('APP.CONCIERGE.INDUCTION_LOADING'));
- const visitor_kiosk_app =
- this._settings.get('app.visitor_kiosk_metadata_key') ||
- this._settings.get('app.visitor_kiosk_app') ||
- 'visitor-kiosk_app';
+ const visitor_kiosk_app = appMetadataKey(
+ this._settings,
+ 'visitor-kiosk',
+ );
try {
const [bld_metadata, org_metadata, org_settings] =
await Promise.all([
@@ -145,14 +146,11 @@ export class InductionSettingsModalComponent implements OnInit {
public async save() {
this.loading.set(i18n('APP.CONCIERGE.INDUCTION_SAVING'));
- const visitor_kiosk_app =
- this._settings.get('app.visitor_kiosk_metadata_key') ||
- this._settings.get('app.visitor_kiosk_app') ||
- 'visitor-kiosk_app';
- const concierge_app =
- this._settings.get('app.concierge_metadata_key') ||
- this._settings.get('app.concierge_app') ||
- 'concierge_app';
+ const visitor_kiosk_app = appMetadataKey(
+ this._settings,
+ 'visitor-kiosk',
+ );
+ const concierge_app = appMetadataKey(this._settings, 'concierge');
const induction = {
induction_details: this.induction_details(),
induction_enabled: this.is_enabled(),
diff --git a/apps/concierge/src/app/building-manager/item-list-modal.component.ts b/apps/concierge/src/app/building-manager/item-list-modal.component.ts
index efa8c40081e..a5037481a8c 100644
--- a/apps/concierge/src/app/building-manager/item-list-modal.component.ts
+++ b/apps/concierge/src/app/building-manager/item-list-modal.component.ts
@@ -10,6 +10,7 @@ import { MatTooltipModule } from '@angular/material/tooltip';
import { SettingsService, notifyError } from '@placeos/common';
import { IconComponent, TranslatePipe } from '@placeos/components';
import { showMetadata, updateMetadata } from '@placeos/ts-client';
+import { appMetadataKey } from '../ui/app-settings/app-settings.utilities';
@Component({
selector: 'item-list-modal',
@@ -98,12 +99,8 @@ export class ItemListModalComponent implements OnInit {
public identify = (index: number, _item: unknown) => index;
public async ngOnInit() {
- const metadata_key =
- this._settings.get('app.workplace_metadata_key') || 'workplace_app';
- const concierge_key =
- this._settings.get('app.concierge_metadata_key') ||
- this._settings.get('app.concierge_app') ||
- 'concierge_app';
+ const metadata_key = appMetadataKey(this._settings, 'workplace');
+ const concierge_key = appMetadataKey(this._settings, 'concierge');
// Save writes the list to both apps, so load from either. Otherwise
// a list that only exists for concierge is replaced on save.
const [workplace, concierge]: any[] = await Promise.all([
@@ -133,12 +130,8 @@ export class ItemListModalComponent implements OnInit {
}
public async save() {
- const metadata_key =
- this._settings.get('app.workplace_metadata_key') || 'workplace_app';
- const concierge_key =
- this._settings.get('app.concierge_metadata_key') ||
- this._settings.get('app.concierge_app') ||
- 'concierge_app';
+ const metadata_key = appMetadataKey(this._settings, 'workplace');
+ const concierge_key = appMetadataKey(this._settings, 'concierge');
this.loading.set(true);
const items = this.item_list().filter((_) => _);
try {
diff --git a/apps/concierge/src/app/catering/catering-topbar.component.ts b/apps/concierge/src/app/catering/catering-topbar.component.ts
index b49bb8bbe4b..ff6dec45369 100644
--- a/apps/concierge/src/app/catering/catering-topbar.component.ts
+++ b/apps/concierge/src/app/catering/catering-topbar.component.ts
@@ -3,32 +3,27 @@ import { ActivatedRoute, Router } from '@angular/router';
import { FormsModule } from '@angular/forms';
import { MatRippleModule } from '@angular/material/core';
-import { MatDialog } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatSelectModule } from '@angular/material/select';
import { MatTooltipModule } from '@angular/material/tooltip';
-import {
- CateringOrdersService,
- CateringStateService,
- ChargeCodeListModalComponent,
-} from '@placeos/catering';
+import { CateringOrdersService, CateringStateService } from '@placeos/catering';
import {
AsyncHandler,
- notifyError,
- notifySuccess,
OrganisationService,
SettingsService,
} from '@placeos/common';
import {
- AvailableRoomsStateModalComponent,
BuildingPipe,
IconComponent,
TranslatePipe,
} from '@placeos/components';
import { DateOptionsComponent } from '@placeos/form-fields';
-import { errorText } from '../ui/modal-actions';
import { SearchbarComponent } from '../ui/searchbar.component';
-import { loadPersistedZones, persistZones } from '../ui/zone-persistence';
+import {
+ loadPersistedZones,
+ persistZones,
+ zoneScopeId,
+} from '../ui/zone-persistence';
@Component({
selector: 'catering-topbar',
@@ -204,7 +199,6 @@ export class CateringTopbarComponent extends AsyncHandler implements OnInit {
private _org = inject(OrganisationService);
private _route = inject(ActivatedRoute);
private _router = inject(Router);
- private _dialog = inject(MatDialog);
private _settings = inject(SettingsService);
/** List of selected levels */
@@ -242,7 +236,7 @@ export class CateringTopbarComponent extends AsyncHandler implements OnInit {
this._catering.zone = zones[0];
persistZones(
this.page() === 'menu' ? 'catering-menu' : 'catering-orders',
- this._persistScopeId(),
+ zoneScopeId(this._org, this.use_region),
zones,
);
};
@@ -250,6 +244,9 @@ export class CateringTopbarComponent extends AsyncHandler implements OnInit {
public readonly addItem = () => this._catering.addItem();
public readonly editConfig = () => this._catering.editConfig();
public readonly importMenu = () => this._catering.importMenu();
+ public readonly setRoomAvailability = () =>
+ this._catering.setRoomAvailability();
+ public readonly setChargeCodes = () => this._catering.setChargeCodes();
public get building() {
return this._org.building;
@@ -298,7 +295,7 @@ export class CateringTopbarComponent extends AsyncHandler implements OnInit {
if (page_changed) {
const zones = loadPersistedZones(
page === 'menu' ? 'catering-menu' : 'catering-orders',
- this._persistScopeId(),
+ zoneScopeId(this._org, this.use_region),
).filter((zone) =>
this.levels().find((level) => level.id === zone),
);
@@ -311,38 +308,4 @@ export class CateringTopbarComponent extends AsyncHandler implements OnInit {
this._catering.zone =
(this.filters()?.zones || [])[0] || this._org.building?.id;
}
-
- private _persistScopeId() {
- return this.use_region
- ? this._org.region?.id || ''
- : this._org.building?.id || '';
- }
-
- public async setRoomAvailability() {
- const ref = this._dialog.open(AvailableRoomsStateModalComponent, {
- data: {
- type: 'Catering',
- disabled_rooms: this._catering.availability(),
- },
- });
- this.subscription(
- 'room-availability',
- ref.componentInstance.change.subscribe(async (list) => {
- try {
- await this._catering.saveSettings({ disabled_rooms: list });
- notifySuccess('Room availability settings saved');
- } catch (e) {
- notifyError(
- `Failed to save room availability. ${errorText(e)}`,
- );
- } finally {
- ref.componentInstance.loading.set(false);
- }
- }),
- );
- }
-
- public setChargeCodes() {
- this._dialog.open(ChargeCodeListModalComponent);
- }
}
diff --git a/apps/concierge/src/app/catering/catering.component.ts b/apps/concierge/src/app/catering/catering.component.ts
index d5f7b6150e2..98d73898ae5 100644
--- a/apps/concierge/src/app/catering/catering.component.ts
+++ b/apps/concierge/src/app/catering/catering.component.ts
@@ -53,7 +53,7 @@ import { CateringTopbarComponent } from './catering-topbar.component';
(null);
-
- constructor() {
- const sub = this._router.events.subscribe(() =>
- this._url.set(this._router.url),
- );
- this._destroy.onDestroy(() => sub.unsubscribe());
- }
-
- public readonly path = computed(() => {
- this._url();
- const parts = (this._router.url || '').split('/');
- return parts[parts.length - 1].split('?')[0];
- });
-}
+export class DayViewComponent {}
diff --git a/apps/concierge/src/app/day-view/dayview-topbar.component.ts b/apps/concierge/src/app/day-view/dayview-topbar.component.ts
deleted file mode 100644
index d8147db4daf..00000000000
--- a/apps/concierge/src/app/day-view/dayview-topbar.component.ts
+++ /dev/null
@@ -1,204 +0,0 @@
-import { Component, inject, OnInit, signal } from '@angular/core';
-import { FormsModule } from '@angular/forms';
-import { MatRippleModule } from '@angular/material/core';
-import { MatFormFieldModule } from '@angular/material/form-field';
-import { MatSelectModule } from '@angular/material/select';
-import { MatSlideToggleModule } from '@angular/material/slide-toggle';
-import { ActivatedRoute, Router } from '@angular/router';
-
-import {
- AsyncHandler,
- Identity,
- OrganisationService,
- SettingsService,
-} from '@placeos/common';
-import {
- IconComponent,
- SettingsToggleComponent,
- TranslatePipe,
-} from '@placeos/components';
-import { DateOptionsComponent } from '@placeos/form-fields';
-import { BookingUIOptions, EventsStateService } from './events-state.service';
-
-@Component({
- selector: 'dayview-topbar',
- template: `
-
-
-
-
-
-
- @for (level of levels(); track level) {
-
- {{ level.display_name || level.name }}
-
- }
-
-
-
-
- Legend
- @for (type of types; track type) {
-
- {{ type.name }}
-
- }
-
-
- @if (allow_setup_breakdown) {
-
Setup / Breakdown
- }
-
-
-
-
- `,
- styles: [
- `
- button {
- min-width: 0;
- padding: 0 0.85rem;
- }
-
- mat-form-field {
- height: 3.25em;
- width: 8em;
- margin-left: 1em;
- }
-
- mat-slide-toggle div {
- width: 5.5em;
- white-space: initial;
- line-height: 1.2em;
- }
- `,
- ],
- imports: [
- IconComponent,
- SettingsToggleComponent,
- DateOptionsComponent,
- TranslatePipe,
- MatFormFieldModule,
- MatSelectModule,
- MatSlideToggleModule,
- MatRippleModule,
- FormsModule,
- ],
-})
-export class DayviewTopbarComponent extends AsyncHandler implements OnInit {
- private _state = inject(EventsStateService);
- private _org = inject(OrganisationService);
- private _route = inject(ActivatedRoute);
- private _router = inject(Router);
- private _settings = inject(SettingsService);
-
- /** List of selected levels */
- public readonly zones = signal
([]);
-
- public readonly types: Identity[] = [
- { id: 'internal', name: 'Internal' },
- { id: 'external', name: 'External' },
- { id: 'cancelled', name: 'Cancelled' },
- ];
- /** List of selected types */
- public readonly type_list = signal(this.types.map((i) => `${i.id}`));
- /** List of levels for the active building */
- public readonly levels = this._state.levels;
- /** UI display options for bookings */
- public readonly ui_options = this._state.options;
- /** Set filtered date */
- public readonly setDate = (d) => this._state.setDate(d);
- /** */
- public readonly newBooking = (d?) => this._state.newBooking(d);
- /** List of levels for the active building */
- public readonly updateZones = (zones: string[]) => {
- const zone_ids = this._clean_zone_ids(zones);
- this.zones.set(zone_ids);
- this._state.setZones?.(zone_ids);
- this._router.navigate([], {
- relativeTo: this._route,
- queryParams: {
- zone_ids: zone_ids.length ? zone_ids.join(',') : null,
- },
- queryParamsHandling: 'merge',
- });
- };
- /** List of levels for the active building */
- public readonly updateTypes = (types: string[]) => {
- this.type_list.set(types);
- this._state.setFilters({
- hide_type: this.types.reduce((list, item) => {
- !types.includes(`${item.id}`) ? list.push(item) : '';
- return list;
- }, []),
- });
- };
-
- public updateUIOptions(options: BookingUIOptions) {
- this._state.setUIOptions(options);
- }
-
- public get allow_setup_breakdown() {
- return this._settings.get('app.events.allow_setup_breakdown');
- }
-
- public async ngOnInit() {
- await this._org.waitUntilInitialised();
- this.subscription(
- 'route.query',
- this._route.queryParamMap.subscribe((params) => {
- if (params.has('zone_ids')) {
- const zones = this._clean_zone_ids(
- params.get('zone_ids').split(','),
- );
- if (zones.length) {
- this.zones.set(zones);
- this._state.setZones?.(zones);
- const level = this._org.levelWithID(zones);
- if (!level) return;
- this._org.building = this._org.buildings.find(
- (bld) => bld.id === level.parent_id,
- );
- }
- }
- }),
- );
- this.updateTypes(this.type_list());
- }
-
- private _clean_zone_ids(zones: string[] = []) {
- return (zones || []).filter((zone_id) => !!zone_id);
- }
-}
diff --git a/apps/concierge/src/app/day-view/events-state.service.ts b/apps/concierge/src/app/day-view/events-state.service.ts
index 69e9da8d09e..79408df3e35 100644
--- a/apps/concierge/src/app/day-view/events-state.service.ts
+++ b/apps/concierge/src/app/day-view/events-state.service.ts
@@ -28,6 +28,8 @@ import {
} from '@placeos/common';
import {
CalendarEventList,
+ EventDetailsModalComponent,
+ SetupBreakdownModalComponent,
declineEvent,
queryEventListOrThrow,
requestSpacesForZone,
@@ -67,10 +69,6 @@ export interface BookingFilters {
export interface BookingUIOptions {
/** Whether to show setup and breakdown times for events */
show_overflow?: boolean;
- /** Whether to show cleaning status of the events */
- show_cleaning?: boolean;
- /** Whether weekends should be shown on week view */
- show_weekends?: boolean;
}
type DayOfWeek = 0 | 1 | 2 | 3 | 4 | 5 | 6;
@@ -119,8 +117,6 @@ export class EventsStateService extends AsyncHandler {
private _poll = signal(0);
/** Period to list bookings for */
private _period = signal<'month' | 'week' | 'day'>('day');
- /** Event to display */
- private _event = signal(null);
/** Events optimistically removed from the bookings list */
private _removed_events = signal([]);
/** Events optimistically added to the bookings list */
@@ -146,8 +142,6 @@ export class EventsStateService extends AsyncHandler {
public readonly zones = this._zones.asReadonly();
/** Signal for UI display options */
public readonly options = this._options.asReadonly();
- /** Signal for the viewed event */
- public readonly event = this._event.asReadonly();
/** Period of time to show events for */
public readonly period = this._period.asReadonly();
/** Pending room booking approval requests */
@@ -453,7 +447,6 @@ export class EventsStateService extends AsyncHandler {
this._period.set(period);
public readonly setZones = (zones: string[]) =>
this._zones.set(this._clean_zone_ids(zones));
- public readonly setEvent = (event: CalendarEvent) => this._event.set(event);
public setUIOptions(options: BookingUIOptions) {
this._options.update((old_options) => ({ ...old_options, ...options }));
@@ -504,6 +497,38 @@ export class EventsStateService extends AsyncHandler {
this.replace(details.metadata);
}
+ /**
+ * Open the details modal for a booking. `remove` runs when the user
+ * removes the booking from the modal. Ignores system events.
+ */
+ public viewEvent(
+ event: CalendarEvent,
+ remove: (event: CalendarEvent) => unknown,
+ ) {
+ if (event.is_system_event) return;
+ const ref = this._dialog.open(EventDetailsModalComponent, {
+ data: {
+ event,
+ edit_fn: (e: CalendarEvent) => this.newBooking(e),
+ remove_fn: (e: CalendarEvent) => remove(e),
+ },
+ });
+ ref.componentInstance.hide_edit.set(
+ !this._settings.get('app.events.allow_edit'),
+ );
+ this.subscription(
+ 'actions',
+ ref.componentInstance.action.subscribe(async (action) => {
+ if (!action.includes('breakdown')) return;
+ const ref = this._dialog.open(SetupBreakdownModalComponent, {
+ data: event,
+ });
+ const data = await nextValueFrom(ref.afterClosed());
+ if (data) this.replace(data);
+ }),
+ );
+ }
+
public async removeBooking(event: CalendarEvent, series = false) {
const time = `${format(event.date, 'dd MMM yyyy ' + this.time_format)}`;
const resource_name = event.space?.display_name || event.location;
diff --git a/apps/concierge/src/app/day-view/room-bookings.component.ts b/apps/concierge/src/app/day-view/room-bookings.component.ts
index bc513ac4b17..947874429f2 100644
--- a/apps/concierge/src/app/day-view/room-bookings.component.ts
+++ b/apps/concierge/src/app/day-view/room-bookings.component.ts
@@ -36,7 +36,11 @@ import {
import { requestSpacesForZone } from '@placeos/events';
import { UserPipe } from '@placeos/users';
import { format } from 'date-fns';
-import { loadPersistedZones, persistZones } from '../ui/zone-persistence';
+import {
+ loadPersistedZones,
+ persistZones,
+ zoneScopeId,
+} from '../ui/zone-persistence';
import { EventsStateService } from './events-state.service';
import { RoomBookingsApprovalsComponent } from './room-approvals.component';
import { RoomBookingsListComponent } from './room-bookings-list.component';
@@ -382,7 +386,7 @@ export class RoomBookingsComponent extends AsyncHandler implements OnInit {
// without an explicit URL filter. Empty means "all levels".
const persisted = loadPersistedZones(
'room-bookings',
- this._persistScopeId(),
+ zoneScopeId(this._org, !!this.use_region),
).filter((zone) => levels.find((lvl) => lvl.id === zone));
if (persisted.length) {
this.updateZones(persisted);
@@ -404,7 +408,11 @@ export class RoomBookingsComponent extends AsyncHandler implements OnInit {
queryParamsHandling: 'merge',
});
this._state.setZones(zone_ids);
- persistZones('room-bookings', this._persistScopeId(), zone_ids);
+ persistZones(
+ 'room-bookings',
+ zoneScopeId(this._org, !!this.use_region),
+ zone_ids,
+ );
};
public readonly updateUIOptions = (o) => this._state.setUIOptions(o);
public readonly setPeriod = (p) => {
@@ -480,12 +488,6 @@ export class RoomBookingsComponent extends AsyncHandler implements OnInit {
);
}
- private _persistScopeId() {
- return this.use_region
- ? this._org.region?.id || ''
- : this._org.building?.id || '';
- }
-
public setFilter(id: string, value: boolean) {
const filters = this._state.filters;
let hide_type = filters.hide_type || [];
diff --git a/apps/concierge/src/app/day-view/room-timeline-inverted.component.ts b/apps/concierge/src/app/day-view/room-timeline-inverted.component.ts
index 7fc46aab544..0857e33c25f 100644
--- a/apps/concierge/src/app/day-view/room-timeline-inverted.component.ts
+++ b/apps/concierge/src/app/day-view/room-timeline-inverted.component.ts
@@ -1,4 +1,4 @@
-import { CommonModule, DatePipe } from '@angular/common';
+import { CommonModule } from '@angular/common';
import {
AfterViewInit,
Component,
@@ -18,34 +18,25 @@ import {
CalendarEvent,
OrganisationService,
SettingsService,
- getTimezoneDifferenceInHours,
getTimezoneOffsetString,
HashMap,
- nextValueFrom,
notifyError,
notifySuccess,
} from '@placeos/common';
import { TranslatePipe, openConfirmModal } from '@placeos/components';
-import {
- EventDetailsModalComponent,
- SetupBreakdownModalComponent,
- declineEvent,
-} from '@placeos/events';
+import { declineEvent } from '@placeos/events';
import { DateOptionsComponent } from '@placeos/form-fields';
-import {
- addHours,
- differenceInMinutes,
- format,
- isSameDay,
- setHours,
- startOfDay,
- startOfMinute,
-} from 'date-fns';
+import { format, isSameDay, startOfMinute } from 'date-fns';
import { EventsStateService } from './events-state.service';
import { RoomBookingSearchComponent } from './room-booking-search.component';
import {
- groupRoomTimelineEvents,
- isActiveRoomTimelineEvent,
+ timelineEvents,
+ timelineHourLabel,
+ timelineHours,
+ timelineLength,
+ timelineOffset,
+ timelineTooltip,
+ timelineWindow,
} from './room-timeline.utilities';
@Component({
@@ -76,7 +67,7 @@ import {
}
@@ -320,58 +311,39 @@ export class RoomBookingsInvertedTimelineComponent
);
public readonly show_time = computed(() => {
this._building();
- const date = this.date();
- const today = isSameDay(date, Date.now());
- const offset = this.timezone
- ? getTimezoneDifferenceInHours(this.timezone)
- : 0;
- const start = addHours(
- setHours(startOfDay(Date.now()), this.block_start),
- -offset,
- ).valueOf();
- const end = addHours(
- setHours(startOfDay(Date.now()), this.block_end),
- -offset,
- ).valueOf();
- return today && Date.now() >= start && Date.now() <= end;
+ const now = Date.now();
+ const { start, end } = timelineWindow(
+ now,
+ this.block_start,
+ this.block_end,
+ this.timezone,
+ );
+ return isSameDay(this.date(), now) && now >= start && now <= end;
});
public readonly events = computed(() => {
- const spaces = this.spaces();
- const events = this._filtered().filter(isActiveRoomTimelineEvent);
- const date = this.date();
- const offset = this.timezone
- ? getTimezoneDifferenceInHours(this.timezone)
- : 0;
- const start = addHours(
- setHours(startOfDay(date), this.block_start),
- -offset,
- ).valueOf();
- const end = addHours(
- setHours(startOfDay(date), this.block_end),
- -offset,
- ).valueOf();
+ const window = timelineWindow(
+ this.date(),
+ this.block_start,
+ this.block_end,
+ this.timezone,
+ );
// HashMap keeps the loose template types of the old `{}` map
- const event_map: HashMap = groupRoomTimelineEvents(
- spaces,
- events.filter(
- (event) => event.date_end >= start && event.date <= end,
- ),
+ const event_map: HashMap = timelineEvents(
+ this.spaces(),
+ this._filtered(),
+ window,
);
return event_map;
});
- private _hour_list = Array.from({ length: 24 }, (_, i) => i);
public readonly hours = computed(() =>
- this._hour_list.filter(
- (hour) => hour >= this.block_start && hour < this.block_end,
- ),
+ timelineHours(this.block_start, this.block_end),
);
public get now() {
return startOfMinute(Date.now()).valueOf();
}
- public readonly edit = (e) => this._state.newBooking(e);
public readonly setDate = (d) => this._state.setDate(d);
private _local_tz = getTimezoneOffsetString(
@@ -408,19 +380,14 @@ export class RoomBookingsInvertedTimelineComponent
}
public formatHour(hour: number) {
- const date = setHours(Date.now(), hour);
- return this._settings.get('app.use_24_hour_time')
- ? format(date, 'HH:00')
- : format(date, 'h a');
+ return timelineHourLabel(
+ hour,
+ !!this._settings.get('app.use_24_hour_time'),
+ );
}
- private _date_pipe = new DatePipe('en');
-
public eventTooltip(event: CalendarEvent) {
- const tooltip = `Start: ${event.all_day ? 'All Day' : this._date_pipe.transform(event.date, this.time_format)}
-Title: ${event.title}
-Host: ${event.organiser?.name || event.host}`;
- return tooltip;
+ return timelineTooltip(event, this.time_format);
}
public ngOnInit() {
@@ -437,53 +404,22 @@ Host: ${event.organiser?.name || event.host}`;
}
public timeToOffset(date: number) {
- const current_tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
- const offset = !this.timezone
- ? 0
- : getTimezoneDifferenceInHours(this.timezone, current_tz);
- const start_time = setHours(
- startOfDay(this._state.getDate()),
- this.block_start - offset,
+ return timelineOffset(
+ date,
+ this._state.getDate(),
+ this.block_start,
+ this.block_range,
+ this.timezone,
);
- const diff = differenceInMinutes(date, start_time);
-
- return +((Math.max(0, diff / 60) / this.block_range) * 100).toFixed(2);
}
public endToOffset(duration: number) {
- return +(
- (Math.min(this.block_range, duration / 60) / this.block_range) *
- 100
- ).toFixed(2);
+ return timelineLength(duration, this.block_range);
}
- public viewEvent(
- event: CalendarEvent,
- space_id: string,
- _scroll_to = false,
- ) {
- if (event.is_system_event) return;
- const ref = this._dialog.open(EventDetailsModalComponent, {
- data: {
- event,
- edit_fn: (e) => this.edit(e),
- remove_fn: (e) => this.remove(e, space_id),
- },
- });
- ref.componentInstance.hide_edit.set(
- !this._settings.get('app.events.allow_edit'),
- );
- this.subscription(
- 'actions',
- ref.componentInstance.action.subscribe(async (action) => {
- if (!action.includes('breakdown')) return;
- const ref = this._dialog.open(SetupBreakdownModalComponent, {
- data: event,
- });
- const data = await nextValueFrom(ref.afterClosed());
- if (data) this._state.replace(data);
- }),
- );
+ /** Open the details of a booking shown in the column of `space_id` */
+ public viewEvent(event: CalendarEvent, space_id: string) {
+ this._state.viewEvent(event, (e) => this.remove(e, space_id));
}
public async remove(item: CalendarEvent, space_id: string) {
diff --git a/apps/concierge/src/app/day-view/room-timeline.component.ts b/apps/concierge/src/app/day-view/room-timeline.component.ts
index 76ee3e423fc..c685c0a96d0 100644
--- a/apps/concierge/src/app/day-view/room-timeline.component.ts
+++ b/apps/concierge/src/app/day-view/room-timeline.component.ts
@@ -1,4 +1,4 @@
-import { CommonModule, DatePipe } from '@angular/common';
+import { CommonModule } from '@angular/common';
import { Component, OnInit, computed, inject } from '@angular/core';
import { MatRippleModule } from '@angular/material/core';
import { MatDialog } from '@angular/material/dialog';
@@ -8,35 +8,26 @@ import {
CalendarEvent,
OrganisationService,
SettingsService,
- getTimezoneDifferenceInHours,
getTimezoneOffsetString,
HashMap,
- nextValueFrom,
notifyError,
notifyInfo,
notifySuccess,
} from '@placeos/common';
import { TranslatePipe, openConfirmModal } from '@placeos/components';
-import {
- EventDetailsModalComponent,
- SetupBreakdownModalComponent,
- declineEvent,
-} from '@placeos/events';
+import { declineEvent } from '@placeos/events';
import { DateOptionsComponent } from '@placeos/form-fields';
-import {
- addHours,
- differenceInMinutes,
- format,
- isSameDay,
- setHours,
- startOfDay,
- startOfMinute,
-} from 'date-fns';
+import { format, isSameDay, startOfMinute } from 'date-fns';
import { EventsStateService } from './events-state.service';
import { RoomBookingSearchComponent } from './room-booking-search.component';
import {
- groupRoomTimelineEvents,
- isActiveRoomTimelineEvent,
+ timelineEvents,
+ timelineHourLabel,
+ timelineHours,
+ timelineLength,
+ timelineOffset,
+ timelineTooltip,
+ timelineWindow,
} from './room-timeline.utilities';
@Component({
@@ -67,7 +58,7 @@ import {
}
@@ -273,58 +264,39 @@ export class RoomBookingsTimelineComponent
);
public readonly show_time = computed(() => {
this._building();
- const date = this.date();
- const today = isSameDay(date, Date.now());
- const offset = this.timezone
- ? getTimezoneDifferenceInHours(this.timezone)
- : 0;
- const start = addHours(
- setHours(startOfDay(Date.now()), this.block_start),
- -offset,
- ).valueOf();
- const end = addHours(
- setHours(startOfDay(Date.now()), this.block_end),
- -offset,
- ).valueOf();
- return today && Date.now() >= start && Date.now() <= end;
+ const now = Date.now();
+ const { start, end } = timelineWindow(
+ now,
+ this.block_start,
+ this.block_end,
+ this.timezone,
+ );
+ return isSameDay(this.date(), now) && now >= start && now <= end;
});
public readonly events = computed(() => {
- const spaces = this.spaces();
- const events = this._filtered().filter(isActiveRoomTimelineEvent);
- const date = this.date();
- const offset = this.timezone
- ? getTimezoneDifferenceInHours(this.timezone)
- : 0;
- const start = addHours(
- setHours(startOfDay(date), this.block_start),
- -offset,
- ).valueOf();
- const end = addHours(
- setHours(startOfDay(date), this.block_end),
- -offset,
- ).valueOf();
+ const window = timelineWindow(
+ this.date(),
+ this.block_start,
+ this.block_end,
+ this.timezone,
+ );
// HashMap keeps the loose template types of the old `{}` map
- const event_map: HashMap = groupRoomTimelineEvents(
- spaces,
- events.filter(
- (event) => event.date_end >= start && event.date <= end,
- ),
+ const event_map: HashMap = timelineEvents(
+ this.spaces(),
+ this._filtered(),
+ window,
);
return event_map;
});
- private _hour_list = Array.from({ length: 24 }, (_, i) => i);
public readonly hours = computed(() =>
- this._hour_list.filter(
- (hour) => hour >= this.block_start && hour < this.block_end,
- ),
+ timelineHours(this.block_start, this.block_end),
);
public get now() {
return startOfMinute(Date.now()).valueOf();
}
- public readonly edit = (e) => this._state.newBooking(e);
public readonly setDate = (d) => this._state.setDate(d);
private _local_tz = getTimezoneOffsetString(
@@ -365,19 +337,14 @@ export class RoomBookingsTimelineComponent
}
public formatHour(hour: number) {
- const date = setHours(Date.now(), hour);
- return this._settings.get('app.use_24_hour_time')
- ? format(date, 'HH:00')
- : format(date, 'h a');
+ return timelineHourLabel(
+ hour,
+ !!this._settings.get('app.use_24_hour_time'),
+ );
}
- private _date_pipe = new DatePipe('en');
-
public eventTooltip(event: CalendarEvent) {
- const tooltip = `Start: ${event.all_day ? 'All Day' : this._date_pipe.transform(event.date, this.time_format)}
-Title: ${event.title}
-Host: ${event.organiser?.name || event.host}`;
- return tooltip;
+ return timelineTooltip(event, this.time_format);
}
public ngOnInit() {
@@ -385,53 +352,22 @@ Host: ${event.organiser?.name || event.host}`;
}
public timeToOffset(date: number) {
- const current_tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
- const offset = !this.timezone
- ? 0
- : getTimezoneDifferenceInHours(this.timezone, current_tz);
- const start_time = setHours(
- startOfDay(this._state.getDate()),
- this.block_start - offset,
+ return timelineOffset(
+ date,
+ this._state.getDate(),
+ this.block_start,
+ this.block_range,
+ this.timezone,
);
- const diff = differenceInMinutes(date, start_time);
-
- return +((Math.max(0, diff / 60) / this.block_range) * 100).toFixed(2);
}
public endToOffset(duration: number) {
- return +(
- (Math.min(this.block_range, duration / 60) / this.block_range) *
- 100
- ).toFixed(2);
+ return timelineLength(duration, this.block_range);
}
- public viewEvent(
- event: CalendarEvent,
- space_id: string,
- _scroll_to = false,
- ) {
- if (event.is_system_event) return;
- const ref = this._dialog.open(EventDetailsModalComponent, {
- data: {
- event,
- edit_fn: (e) => this.edit(e),
- remove_fn: (e) => this.remove(e, space_id),
- },
- });
- ref.componentInstance.hide_edit.set(
- !this._settings.get('app.events.allow_edit'),
- );
- this.subscription(
- 'actions',
- ref.componentInstance.action.subscribe(async (action) => {
- if (!action.includes('breakdown')) return;
- const ref = this._dialog.open(SetupBreakdownModalComponent, {
- data: event,
- });
- const data = await nextValueFrom(ref.afterClosed());
- if (data) this._state.replace(data);
- }),
- );
+ /** Open the details of a booking shown in the column of `space_id` */
+ public viewEvent(event: CalendarEvent, space_id: string) {
+ this._state.viewEvent(event, (e) => this.remove(e, space_id));
}
public async remove(item: CalendarEvent, space_id: string) {
diff --git a/apps/concierge/src/app/day-view/room-timeline.utilities.ts b/apps/concierge/src/app/day-view/room-timeline.utilities.ts
index e4c5a544efb..ccfe1cff874 100644
--- a/apps/concierge/src/app/day-view/room-timeline.utilities.ts
+++ b/apps/concierge/src/app/day-view/room-timeline.utilities.ts
@@ -1,4 +1,12 @@
-import { CalendarEvent } from '@placeos/common';
+import { DatePipe } from '@angular/common';
+import { CalendarEvent, getTimezoneDifferenceInHours } from '@placeos/common';
+import {
+ addHours,
+ differenceInMinutes,
+ format,
+ setHours,
+ startOfDay,
+} from 'date-fns';
export function isActiveRoomTimelineEvent(event: CalendarEvent): boolean {
const state = (event as any).state;
@@ -59,3 +67,96 @@ export function groupRoomTimelineEvents(
}
return event_map;
}
+
+/** Time range of the shown hours, as Unix milliseconds */
+export interface TimelineWindow {
+ start: number;
+ end: number;
+}
+
+/**
+ * Start and end of the shown hours on the day of `date`. When `timezone` is
+ * set, the hours are in that timezone.
+ */
+export function timelineWindow(
+ date: number,
+ block_start: number,
+ block_end: number,
+ timezone: string,
+): TimelineWindow {
+ const offset = timezone ? getTimezoneDifferenceInHours(timezone) : 0;
+ const day = startOfDay(date);
+ return {
+ start: addHours(setHours(day, block_start), -offset).valueOf(),
+ end: addHours(setHours(day, block_end), -offset).valueOf(),
+ };
+}
+
+/** Group the active events that overlap `window` by the spaces they book. */
+export function timelineEvents(
+ spaces: TimelineSpace[],
+ events: CalendarEvent[],
+ window: TimelineWindow,
+): Record
{
+ return groupRoomTimelineEvents(
+ spaces,
+ events.filter(
+ (event) =>
+ isActiveRoomTimelineEvent(event) &&
+ event.date_end >= window.start &&
+ event.date <= window.end,
+ ),
+ );
+}
+
+/** Hours of the day from `block_start` up to, but not including, `block_end`. */
+export function timelineHours(block_start: number, block_end: number) {
+ return Array.from({ length: 24 }, (_, i) => i).filter(
+ (hour) => hour >= block_start && hour < block_end,
+ );
+}
+
+/**
+ * Position of `date` as a percent of the shown hours on the day of `day`.
+ * When `timezone` is set, the hours are in that timezone.
+ */
+export function timelineOffset(
+ date: number,
+ day: number,
+ block_start: number,
+ block_range: number,
+ timezone: string,
+): number {
+ const current_tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
+ const offset = timezone
+ ? getTimezoneDifferenceInHours(timezone, current_tz)
+ : 0;
+ const start_time = setHours(startOfDay(day), block_start - offset);
+ const diff = differenceInMinutes(date, start_time);
+ return +((Math.max(0, diff / 60) / block_range) * 100).toFixed(2);
+}
+
+/** Length of `duration` minutes as a percent of the shown hours. */
+export function timelineLength(duration: number, block_range: number): number {
+ return +(
+ (Math.min(block_range, duration / 60) / block_range) *
+ 100
+ ).toFixed(2);
+}
+
+/** Label for an hour on the timeline axis, such as "09:00" or "9 AM". */
+export function timelineHourLabel(hour: number, use_24_hour: boolean): string {
+ return format(setHours(Date.now(), hour), use_24_hour ? 'HH:00' : 'h a');
+}
+
+const DATE_PIPE = new DatePipe('en');
+
+/** Tooltip text for a booking on the timeline. */
+export function timelineTooltip(
+ event: CalendarEvent,
+ time_format: string,
+): string {
+ return `Start: ${event.all_day ? 'All Day' : DATE_PIPE.transform(event.date, time_format)}
+Title: ${event.title}
+Host: ${event.organiser?.name || event.host}`;
+}
diff --git a/apps/concierge/src/app/day-view/room-week-timeline.component.ts b/apps/concierge/src/app/day-view/room-week-timeline.component.ts
index a0be6a2e82f..a1a457db6e2 100644
--- a/apps/concierge/src/app/day-view/room-week-timeline.component.ts
+++ b/apps/concierge/src/app/day-view/room-week-timeline.component.ts
@@ -1,22 +1,16 @@
import { CommonModule, DatePipe } from '@angular/common';
import { Component, computed, inject, OnInit, signal } from '@angular/core';
import { MatRippleModule } from '@angular/material/core';
-import { MatDialog } from '@angular/material/dialog';
import {
AsyncHandler,
CalendarEvent,
getTimezoneOffsetInMinutes,
getTimezoneOffsetString,
i18n,
- nextValueFrom,
OrganisationService,
SettingsService,
} from '@placeos/common';
import { TranslatePipe } from '@placeos/components';
-import {
- EventDetailsModalComponent,
- SetupBreakdownModalComponent,
-} from '@placeos/events';
import { DateOptionsComponent } from '@placeos/form-fields';
import { UserPipe } from '@placeos/users';
import {
@@ -62,7 +56,7 @@ import { isActiveRoomTimelineEvent } from './room-timeline.utilities';
}
@@ -126,13 +120,7 @@ import { isActiveRoomTimelineEvent } from './room-timeline.utilities';
i);
- public readonly ui_options = this._state.options;
public readonly date = this._state.date;
public readonly remove = (event: CalendarEvent) =>
@@ -343,7 +328,6 @@ export class RoomWeekBookingsTimelineComponent
return isSameDay(date, Date.now());
}
- public readonly edit = (e) => this._state.newBooking(e);
public readonly setDate = (d) => this._state.setDate(d);
public get time_format() {
@@ -384,33 +368,8 @@ export class RoomWeekBookingsTimelineComponent
return this.types().find((_) => _.id === type)?.color || '#EEE';
}
- public viewEvent(
- event: CalendarEvent,
- space_id: string,
- _scroll_to = false,
- ) {
- if (event.is_system_event) return;
- const ref = this._dialog.open(EventDetailsModalComponent, {
- data: {
- event,
- edit_fn: (e) => this.edit(e),
- remove_fn: (e) => this.remove(e),
- },
- });
- ref.componentInstance.hide_edit.set(
- !this._settings.get('app.events.allow_edit'),
- );
- this.subscription(
- 'actions',
- ref.componentInstance.action.subscribe((action) => {
- if (!action.includes('breakdown')) return;
- const ref = this._dialog.open(SetupBreakdownModalComponent, {
- data: event,
- });
- nextValueFrom(ref.afterClosed()).then((data) => {
- if (data) this._state.replace(data);
- });
- }),
- );
+ /** Open the details of a booking */
+ public viewEvent(event: CalendarEvent) {
+ this._state.viewEvent(event, this.remove);
}
}
diff --git a/apps/concierge/src/app/deals/deals.component.ts b/apps/concierge/src/app/deals/deals.component.ts
index 35f74821ec1..7e03ed10d41 100644
--- a/apps/concierge/src/app/deals/deals.component.ts
+++ b/apps/concierge/src/app/deals/deals.component.ts
@@ -1,6 +1,5 @@
-import { Component, computed, inject, signal } from '@angular/core';
-import { Router, RouterModule } from '@angular/router';
-import { AsyncHandler } from '@placeos/common';
+import { Component } from '@angular/core';
+import { RouterModule } from '@angular/router';
import { ApplicationSidebarComponent } from '../ui/app-sidebar.component';
@Component({
@@ -26,22 +25,4 @@ import { ApplicationSidebarComponent } from '../ui/app-sidebar.component';
],
imports: [ApplicationSidebarComponent, RouterModule],
})
-export class DealsComponent extends AsyncHandler {
- private _router = inject(Router);
-
- private readonly _url = signal(null);
-
- public readonly path = computed(() => {
- this._url();
- const parts = this._router.url.split('/');
- return parts[parts.length - 1].split('?')[0];
- });
-
- constructor() {
- super();
- this.subscription(
- 'router',
- this._router.events.subscribe((event) => this._url.set(event)),
- );
- }
-}
+export class DealsComponent {}
diff --git a/apps/concierge/src/app/deals/deals.service.ts b/apps/concierge/src/app/deals/deals.service.ts
index 6402eaf6432..9c20cc85311 100644
--- a/apps/concierge/src/app/deals/deals.service.ts
+++ b/apps/concierge/src/app/deals/deals.service.ts
@@ -20,8 +20,6 @@ export class DealsService extends AsyncHandler {
private _dialog = inject(MatDialog);
private _changed = signal(0);
- public readonly loading = signal(false);
-
private readonly _deals = resource({
params: () => ({
building: this._org.active_building()?.id,
@@ -30,12 +28,10 @@ export class DealsService extends AsyncHandler {
defaultValue: [] as Deal[],
loader: async ({ params }) => {
if (!params.building) return [];
- this.loading.set(true);
const metadata = await showMetadata(
params.building,
'deals-n-offers',
).catch(() => ({ details: [] }));
- this.loading.set(false);
return metadata?.details instanceof Array ? metadata.details : [];
},
});
diff --git a/apps/concierge/src/app/desks/desk-book-modal.component.ts b/apps/concierge/src/app/desks/desk-book-modal.component.ts
index 7af4ed5158a..1e81b58ab9e 100644
--- a/apps/concierge/src/app/desks/desk-book-modal.component.ts
+++ b/apps/concierge/src/app/desks/desk-book-modal.component.ts
@@ -1,6 +1,9 @@
import { Component, inject, OnInit, output, signal } from '@angular/core';
import { MatDialogRef } from '@angular/material/dialog';
-import { BookingFormService } from '@placeos/bookings';
+import {
+ BookingFormService,
+ NewDeskFormDetailsComponent,
+} from '@placeos/bookings';
import {
DialogEvent,
i18n,
@@ -12,8 +15,6 @@ import {
TranslatePipe,
} from '@placeos/components';
-import { NewDeskFormDetailsComponent } from 'apps/workplace/src/app/book/desk-flow/desk-form-details.component';
-
@Component({
selector: 'desk-book-modal',
template: `
diff --git a/apps/concierge/src/app/desks/desk-bookings.component.ts b/apps/concierge/src/app/desks/desk-bookings.component.ts
index afbf859b991..2139eea3c3f 100644
--- a/apps/concierge/src/app/desks/desk-bookings.component.ts
+++ b/apps/concierge/src/app/desks/desk-bookings.component.ts
@@ -462,7 +462,6 @@ export class DeskBookingsComponent implements OnInit {
);
});
- public readonly rejectAll = () => this._state.rejectAllDesks();
public readonly cancel = (b) => this._state.cancelBooking(b);
public readonly cancelSeries = (b) => this._state.cancelBooking(b, true);
public readonly viewBookingHistory = (b) =>
diff --git a/apps/concierge/src/app/desks/desk-modal.component.ts b/apps/concierge/src/app/desks/desk-modal.component.ts
index de5a0358cca..0a2d8cb2e58 100644
--- a/apps/concierge/src/app/desks/desk-modal.component.ts
+++ b/apps/concierge/src/app/desks/desk-modal.component.ts
@@ -27,7 +27,6 @@ import {
Desk,
DialogEvent,
notifyInfo,
- OrganisationService,
randomString,
User,
} from '@placeos/common';
@@ -353,7 +352,6 @@ export class DeskModalComponent implements OnInit {
private _data = inject(MAT_DIALOG_DATA);
private _dialog_ref =
inject>(MatDialogRef);
- private _org = inject(OrganisationService);
private _dialog = inject(MatDialog);
public readonly event = output();
diff --git a/apps/concierge/src/app/desks/desks-manage.component.ts b/apps/concierge/src/app/desks/desks-manage.component.ts
index c6e9888fb4a..334cd9351a3 100644
--- a/apps/concierge/src/app/desks/desks-manage.component.ts
+++ b/apps/concierge/src/app/desks/desks-manage.component.ts
@@ -1,6 +1,6 @@
import { Clipboard } from '@angular/cdk/clipboard';
-import { Component, computed, ElementRef, inject, signal } from '@angular/core';
+import { Component, computed, inject, signal } from '@angular/core';
import { MatRippleModule } from '@angular/material/core';
import { MatDialog } from '@angular/material/dialog';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
@@ -287,7 +287,6 @@ export class DesksManageComponent extends AsyncHandler {
private _org = inject(OrganisationService);
private _dialog = inject(MatDialog);
private _settings = inject(SettingsService);
- private _element = inject(ElementRef);
private _clipboard = inject(Clipboard);
public readonly loading = signal('');
diff --git a/apps/concierge/src/app/desks/desks-state.service.ts b/apps/concierge/src/app/desks/desks-state.service.ts
index 825f08c1ae0..1a4d847821a 100644
--- a/apps/concierge/src/app/desks/desks-state.service.ts
+++ b/apps/concierge/src/app/desks/desks-state.service.ts
@@ -88,7 +88,6 @@ export type DeskView = 'manage' | 'events' | 'map';
export interface DeskFilters {
date?: number;
zones?: string[];
- show_map?: boolean;
search?: string;
view?: DeskView;
}
@@ -864,21 +863,6 @@ export class DesksStateService extends AsyncHandler {
});
}
- public async giveAccess(desk: Booking) {
- const status: any = await saveBooking(
- new Booking({ ...desk, access: true }).toJSON(),
- ).catch((_) => ({ failed: true, error: _ }));
- if (status.failed) {
- return notifyError(
- i18n('APP.CONCIERGE.DESKS_ACCESS_ERROR', {
- error: status.error,
- }),
- );
- }
- notifySuccess(i18n('APP.CONCIERGE.DESKS_ACCESS_SUCCESS'));
- this.setFilters({});
- }
-
public async rejectAllDesks() {
const search = this._filters().search;
const list = this.bookings().filter(
diff --git a/apps/concierge/src/app/desks/desks.component.ts b/apps/concierge/src/app/desks/desks.component.ts
index cf84661cd03..4e3de207891 100644
--- a/apps/concierge/src/app/desks/desks.component.ts
+++ b/apps/concierge/src/app/desks/desks.component.ts
@@ -3,7 +3,6 @@ import {
computed,
effect,
inject,
- OnDestroy,
OnInit,
resource,
signal,
@@ -49,7 +48,12 @@ import { showMetadata } from '@placeos/ts-client';
import { ApplicationSidebarComponent } from '../ui/app-sidebar.component';
import { BookingRulesModalComponent } from '../ui/booking-rules-modal.component';
import { SearchbarComponent } from '../ui/searchbar.component';
-import { loadPersistedZones, persistZones } from '../ui/zone-persistence';
+import {
+ loadPersistedZones,
+ persistZones,
+ sameZones,
+ zoneScopeId,
+} from '../ui/zone-persistence';
import { DeskBookModalComponent } from './desk-book-modal.component';
import { DeskQrCodeModalComponent } from './desk-qr-code-modal.component';
import { DesksStateService } from './desks-state.service';
@@ -308,7 +312,6 @@ import { DesksStateService } from './desks-state.service';
MatProgressBarModule,
MatProgressSpinnerModule,
RouterModule,
- MatRippleModule,
FormsModule,
IconComponent,
MatRippleModule,
@@ -323,7 +326,7 @@ import { DesksStateService } from './desks-state.service';
SafePipe,
],
})
-export class DesksComponent extends AsyncHandler implements OnInit, OnDestroy {
+export class DesksComponent extends AsyncHandler implements OnInit {
private _state = inject(DesksStateService);
private _router = inject(Router);
private _route = inject(ActivatedRoute);
@@ -425,7 +428,11 @@ export class DesksComponent extends AsyncHandler implements OnInit, OnDestroy {
? 'desks'
: '';
if (persistence_view) {
- persistZones(persistence_view, this._persistScopeId(), clean_zones);
+ persistZones(
+ persistence_view,
+ zoneScopeId(this._org, this.use_region),
+ clean_zones,
+ );
}
};
@@ -468,10 +475,6 @@ export class DesksComponent extends AsyncHandler implements OnInit, OnDestroy {
this._updateView();
}
- public ngOnDestroy() {
- super.ngOnDestroy();
- }
-
public viewQRCodes() {
this._dialog.open(DeskQrCodeModalComponent);
}
@@ -562,29 +565,13 @@ export class DesksComponent extends AsyncHandler implements OnInit, OnDestroy {
if (!next_zones.length && persistence_view) {
const persisted = loadPersistedZones(
persistence_view,
- this._persistScopeId(),
+ zoneScopeId(this._org, this.use_region),
).filter((zone) => levels.find((lvl) => lvl.id === zone));
if (persisted.length) {
next_zones = persisted;
}
}
- if (this._sameZones(current_zones, next_zones)) return;
+ if (sameZones(current_zones, next_zones)) return;
this.updateZones(next_zones);
}
-
- private _sameZones(
- current_zones: string[] = [],
- next_zones: string[] = [],
- ) {
- return (
- current_zones.length === next_zones.length &&
- current_zones.every((zone, index) => zone === next_zones[index])
- );
- }
-
- private _persistScopeId() {
- return this.use_region
- ? this._org.region?.id || ''
- : this._org.building?.id || '';
- }
}
diff --git a/apps/concierge/src/app/email-templates/email-templates-state.service.ts b/apps/concierge/src/app/email-templates/email-templates-state.service.ts
index 28f224bbf41..1456e0a6620 100644
--- a/apps/concierge/src/app/email-templates/email-templates-state.service.ts
+++ b/apps/concierge/src/app/email-templates/email-templates-state.service.ts
@@ -10,14 +10,13 @@ import {
SettingsService,
unique,
} from '@placeos/common';
-import { openConfirmModal } from '@placeos/components';
import {
PlaceMetadata,
showMetadata,
updateMetadata,
} from '@placeos/ts-client';
import { getUnixTime } from 'date-fns';
-import { errorText } from '../ui/modal-actions';
+import { confirmAction, errorText } from '../ui/modal-actions';
export interface EmailTemplate {
id: string;
@@ -218,7 +217,8 @@ export class EmailTemplatesStateService extends AsyncHandler {
}
public async removeTemplate(template: EmailTemplate) {
- const ref = await openConfirmModal(
+ const removed = await confirmAction(
+ this._dialog,
{
title: i18n('APP.CONCIERGE.EMAIL_TEMPLATES_REMOVE_TITLE'),
content: i18n('APP.CONCIERGE.EMAIL_TEMPLATES_REMOVE_MSG', {
@@ -227,24 +227,19 @@ export class EmailTemplatesStateService extends AsyncHandler {
icon: { content: 'delete_forever' },
confirm_text: i18n('COMMON.REMOVE'),
},
- this._dialog,
+ {
+ loading: i18n('APP.CONCIERGE.EMAIL_TEMPLATES_REMOVE_LOADING'),
+ action: () =>
+ this._writeZoneTemplates(template.zone_id, (list) =>
+ list.filter((_) => _.id !== template.id),
+ ),
+ error: (e) =>
+ i18n('APP.CONCIERGE.EMAIL_TEMPLATES_REMOVE_ERROR', {
+ error: errorText(e),
+ }),
+ },
);
- if (ref.reason !== 'done') return ref.close();
- ref.loading(i18n('APP.CONCIERGE.EMAIL_TEMPLATES_REMOVE_LOADING'));
- try {
- await this._writeZoneTemplates(template.zone_id, (list) =>
- list.filter((_) => _.id !== template.id),
- );
- } catch (e) {
- notifyError(
- i18n('APP.CONCIERGE.EMAIL_TEMPLATES_REMOVE_ERROR', {
- error: errorText(e),
- }),
- );
- throw e;
- } finally {
- ref.close();
- }
+ if (!removed) return;
notifySuccess(i18n('APP.CONCIERGE.EMAIL_TEMPLATES_REMOVE_SUCCESS'));
this.timeout('changed', () => this._change.set(Date.now()));
}
diff --git a/apps/concierge/src/app/email-templates/email-templates.component.ts b/apps/concierge/src/app/email-templates/email-templates.component.ts
index edad0fe39ab..71312697ae5 100644
--- a/apps/concierge/src/app/email-templates/email-templates.component.ts
+++ b/apps/concierge/src/app/email-templates/email-templates.component.ts
@@ -1,6 +1,5 @@
-import { Component, computed, inject, signal } from '@angular/core';
-import { Router, RouterModule } from '@angular/router';
-import { AsyncHandler } from '@placeos/common';
+import { Component } from '@angular/core';
+import { RouterModule } from '@angular/router';
import { ApplicationSidebarComponent } from '../ui/app-sidebar.component';
@Component({
@@ -26,22 +25,4 @@ import { ApplicationSidebarComponent } from '../ui/app-sidebar.component';
],
imports: [ApplicationSidebarComponent, RouterModule],
})
-export class EmailTemplatesComponent extends AsyncHandler {
- private _router = inject(Router);
-
- private readonly _url = signal(null);
-
- public readonly path = computed(() => {
- this._url();
- const parts = this._router.url.split('/');
- return parts[parts.length - 1].split('?')[0];
- });
-
- constructor() {
- super();
- this.subscription(
- 'router',
- this._router.events.subscribe((event) => this._url.set(event)),
- );
- }
-}
+export class EmailTemplatesComponent {}
diff --git a/apps/concierge/src/app/events/event-manage.component.ts b/apps/concierge/src/app/events/event-manage.component.ts
index c7bac95257f..d176120f963 100644
--- a/apps/concierge/src/app/events/event-manage.component.ts
+++ b/apps/concierge/src/app/events/event-manage.component.ts
@@ -3,7 +3,6 @@ import { COMMA, ENTER, SPACE } from '@angular/cdk/keycodes';
import { Component, OnInit, computed, inject, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { FormField } from '@angular/forms/signals';
-import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatChipInputEvent, MatChipsModule } from '@angular/material/chips';
import { MatRippleModule } from '@angular/material/core';
import { MatFormFieldModule } from '@angular/material/form-field';
@@ -28,7 +27,6 @@ import {
notifyError,
unique,
} from '@placeos/common';
-import { TIMEZONES_IANA } from '@placeos/common/timezones';
import {
IconComponent,
SettingsToggleComponent,
@@ -49,6 +47,7 @@ import {
} from '@placeos/form-fields';
import { differenceInMinutes, format, startOfDay } from 'date-fns';
import { errorText } from '../ui/modal-actions';
+import { TimezoneFieldComponent } from '../ui/timezone-field.component';
import { HasUnsavedChanges } from '../ui/unsaved-changes.guard';
import { EventStateService } from './event-state.service';
@@ -272,37 +271,10 @@ const EMPTY = [];
}
-