Skip to content

Commit 54031c8

Browse files
committed
feat(workplace): add parking request flow
1 parent 00fc936 commit 54031c8

12 files changed

Lines changed: 1691 additions & 1 deletion

apps/workplace/src/app/book/book.module.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { NewDeskFlowComponent } from './desk-flow.component';
99
import { BookLockerFlowComponent } from './locker-flow.component';
1010
import { BookMeetingFlowComponent } from './meeting-flow.component';
1111
import { NewParkingFlowComponent } from './parking-flow.component';
12+
import { ParkingRequestFlowComponent } from './parking-request-flow.component';
1213
import { VisitorFlowComponent } from './visitor-flow.component';
1314

1415
const ROUTES: Route[] = [
@@ -26,6 +27,11 @@ const ROUTES: Route[] = [
2627
{ path: 'meeting/:step', component: BookMeetingFlowComponent },
2728
{ path: 'parking', redirectTo: 'parking/form' },
2829
{ path: 'parking/:step', component: NewParkingFlowComponent },
30+
{ path: 'parking-request', redirectTo: 'parking-request/form' },
31+
{
32+
path: 'parking-request/:step',
33+
component: ParkingRequestFlowComponent,
34+
},
2935
{ path: 'visitor', redirectTo: 'visitor/form' },
3036
{ path: 'visitor/:step', component: VisitorFlowComponent },
3137
{ path: 'locker', redirectTo: 'locker/form' },
@@ -46,6 +52,7 @@ const STANDALONE_COMPONENTS = [
4652
NewDeskFlowComponent,
4753
VisitorFlowComponent,
4854
NewParkingFlowComponent,
55+
ParkingRequestFlowComponent,
4956
BookLockerFlowComponent,
5057
];
5158

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { Component, OnInit, inject } from '@angular/core';
2+
import { ActivatedRoute } from '@angular/router';
3+
import { BookingFormService } from '@placeos/bookings';
4+
import { AsyncHandler } from '@placeos/common';
5+
import { ParkingRequestFormComponent } from './parking-request-flow/parking-request-form.component';
6+
import { ParkingRequestSuccessComponent } from './parking-request-flow/parking-request-success.component';
7+
8+
@Component({
9+
selector: 'placeos-parking-request-flow',
10+
template: `
11+
<div class="bg-base-100 z-50 h-full w-full">
12+
@switch (view()) {
13+
@case ('success') {
14+
<parking-request-success></parking-request-success>
15+
}
16+
@default {
17+
<parking-request-form></parking-request-form>
18+
}
19+
}
20+
</div>
21+
`,
22+
styles: [
23+
`
24+
:host {
25+
height: 100%;
26+
width: 100%;
27+
}
28+
`,
29+
],
30+
imports: [
31+
ParkingRequestSuccessComponent,
32+
ParkingRequestFormComponent,
33+
],
34+
})
35+
export class ParkingRequestFlowComponent
36+
extends AsyncHandler
37+
implements OnInit
38+
{
39+
private _state = inject(BookingFormService);
40+
private _route = inject(ActivatedRoute);
41+
42+
public readonly view = this._state.view;
43+
44+
public ngOnInit() {
45+
this._state.loadForm();
46+
this._state.setOptions({ type: 'parking' });
47+
if (!this._state.form.value.id) this._state.newForm('parking');
48+
this._state.form.patchValue({ booking_type: 'parking' });
49+
this.subscription(
50+
'route.params',
51+
this._route.paramMap.subscribe((param) => {
52+
if (param.has('step'))
53+
this._state.setView(param.get('step') as any);
54+
}),
55+
);
56+
this.subscription(
57+
'route.query',
58+
this._route.queryParamMap.subscribe((param) => {
59+
if (param.has('success')) this._state.setView('success');
60+
}),
61+
);
62+
}
63+
}
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
import { CommonModule } from '@angular/common';
2+
import { Component, inject, model } from '@angular/core';
3+
import { MatBottomSheetRef } from '@angular/material/bottom-sheet';
4+
import { MatRippleModule } from '@angular/material/core';
5+
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
6+
import { BookingFormService } from '@placeos/bookings';
7+
import {
8+
AsyncHandler,
9+
OrganisationService,
10+
SettingsService,
11+
notifyError,
12+
} from '@placeos/common';
13+
import { IconComponent, TranslatePipe } from '@placeos/components';
14+
15+
@Component({
16+
selector: 'parking-request-confirm',
17+
template: `
18+
<header
19+
class="bg-base-200 sticky top-2 z-10 mx-auto mb-4 flex h-14 w-full max-w-[calc(100%-1rem)] items-center justify-between rounded-sm border-none px-4 py-2"
20+
>
21+
<h2 class="m-0 flex-1 text-xl font-medium capitalize">
22+
{{
23+
'APP.WORKPLACE.PARKING_REQUEST_CONFIRM_TITLE' | translate
24+
}}
25+
</h2>
26+
<div class="">
27+
@if (loading | async) {
28+
<mat-spinner diameter="32"></mat-spinner>
29+
}
30+
@if (show_close()) {
31+
<button
32+
icon
33+
name="close-parking-request-confirm"
34+
matRipple
35+
(click)="dismiss()"
36+
>
37+
<icon class="text-2xl">close</icon>
38+
</button>
39+
}
40+
</div>
41+
</header>
42+
<section period class="flex space-x-1 px-2 py-4 text-base">
43+
<icon class="text-success text-2xl">done</icon>
44+
<div details class="space-y-2 text-base">
45+
<h3 class="text-xl">
46+
{{ booking.title || '~Untitled~' }}
47+
</h3>
48+
<div class="flex items-center space-x-2">
49+
<icon>calendar_today</icon>
50+
<div date>{{ booking.date | date: 'fullDate' }}</div>
51+
</div>
52+
<div class="flex items-center space-x-2">
53+
<icon>schedule</icon>
54+
<div time>
55+
{{
56+
booking.all_day
57+
? ('COMMON.ALL_DAY' | translate)
58+
: (booking.date | date: time_format) +
59+
' - ' +
60+
(booking.date + booking.duration * 60 * 1000
61+
| date: time_format + ' (z)')
62+
}}
63+
</div>
64+
</div>
65+
</div>
66+
</section>
67+
<section
68+
vehicle
69+
class="flex space-x-1 border-t px-2 py-4 text-base"
70+
>
71+
<icon class="text-success text-2xl">done</icon>
72+
<div details class="space-y-2 text-base">
73+
<h3 class="text-xl">
74+
{{ 'BOOKINGS.PARKING_VEHICLE_TYPE' | translate }}
75+
</h3>
76+
<div class="flex items-center space-x-2">
77+
<icon>directions_car</icon>
78+
<span>
79+
{{
80+
'BOOKINGS.PARKING_VEHICLE_'
81+
+ (booking.vehicle_type | uppercase)
82+
| translate
83+
}}
84+
</span>
85+
</div>
86+
@if (booking.plate_number) {
87+
<div class="flex items-center space-x-2">
88+
<icon>confirmation_number</icon>
89+
<span>{{ booking.plate_number }}</span>
90+
</div>
91+
}
92+
<div class="flex items-center space-x-2">
93+
<icon>category</icon>
94+
<span>
95+
{{
96+
'BOOKINGS.PARKING_REQUEST_'
97+
+ (booking.request_type | uppercase)
98+
| translate
99+
}}
100+
</span>
101+
</div>
102+
@if (booking.space_restrictions) {
103+
<div class="flex items-center space-x-2">
104+
<icon>warning</icon>
105+
<span>
106+
{{
107+
'BOOKINGS.PARKING_SPACE_RESTRICTIONS'
108+
| translate
109+
}}
110+
</span>
111+
</div>
112+
}
113+
@if (booking.recurrence_type && booking.recurrence_type !== 'none') {
114+
<div class="flex items-center space-x-2">
115+
<icon>repeat</icon>
116+
<span class="capitalize">
117+
{{ booking.recurrence_type }}
118+
</span>
119+
</div>
120+
}
121+
@if (location) {
122+
<div class="flex items-center space-x-2">
123+
<icon>place</icon>
124+
<span>{{ location }}</span>
125+
</div>
126+
}
127+
</div>
128+
</section>
129+
<footer class="border-base-200 mt-4 w-full border-t p-2">
130+
@if (!(loading | async)) {
131+
<button
132+
confirm
133+
btn
134+
matRipple
135+
class="w-full"
136+
(click)="postForm()"
137+
>
138+
{{ 'COMMON.CONFIRM' | translate }}
139+
</button>
140+
}
141+
</footer>
142+
`,
143+
styles: [
144+
`
145+
section > icon {
146+
font-size: 1.5rem;
147+
margin-top: 0.3rem;
148+
}
149+
150+
h3 {
151+
font-size: 1.25rem;
152+
font-weight: medium;
153+
margin: 0.5rem 0;
154+
}
155+
`,
156+
],
157+
imports: [
158+
CommonModule,
159+
MatRippleModule,
160+
IconComponent,
161+
TranslatePipe,
162+
MatProgressSpinnerModule,
163+
],
164+
})
165+
export class ParkingRequestConfirmComponent extends AsyncHandler {
166+
private _state = inject(BookingFormService);
167+
private _org = inject(OrganisationService);
168+
private _sheet_ref = inject(MatBottomSheetRef, { optional: true });
169+
private _settings = inject(SettingsService);
170+
171+
public readonly show_close = model<boolean>(false);
172+
173+
public readonly loading = this._state.loading;
174+
175+
public readonly postForm = async () => {
176+
const r = await this._state.postForm().catch((_) => {
177+
notifyError(`Unable to submit parking request. ${_}`);
178+
});
179+
if (!r) return;
180+
this.dismiss(true);
181+
};
182+
public readonly dismiss = (e?) => this._sheet_ref?.dismiss(e);
183+
184+
public get time_format() {
185+
return this._settings.time_format;
186+
}
187+
188+
public get booking() {
189+
return this._state.form.getRawValue() as any;
190+
}
191+
192+
public get location() {
193+
const building = this._org.buildings.find((b) =>
194+
this.booking.zones?.includes(b.id),
195+
);
196+
return building?.display_name || building?.name || '';
197+
}
198+
}

0 commit comments

Comments
 (0)