@feedmepos/mf-order-setting 0.0.56-dev.1 → 0.0.56-dev.2
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.
- package/.tsbuildinfo +1 -1
- package/dist/{KioskDevicesView-CccsAZqK.js → KioskDevicesView-Qv-xd_kZ.js} +1 -1
- package/dist/{KioskDevicesView.vue_vue_type_script_setup_true_lang-dF1jgi53.js → KioskDevicesView.vue_vue_type_script_setup_true_lang-CCF1mKni.js} +2 -2
- package/dist/KioskSettingView-CvvrK6Bv.js +643 -0
- package/dist/{KioskView-DmaCjLcw.js → KioskView-CppTVBv-.js} +117 -117
- package/dist/OrderSettingsView-C38N61dM.js +36564 -0
- package/dist/{app-EGmxrjDM.js → app-Bss1GkKY.js} +4 -4
- package/dist/app.js +1 -1
- package/dist/{dayjs.min-lCwCAXUZ.js → dayjs.min-DZfxGUk4.js} +1 -1
- package/dist/frontend/mf-order/src/stores/order-setting/index.d.ts +3 -0
- package/dist/frontend/mf-order/src/stores/restaurant/index.d.ts +1 -1
- package/dist/frontend/mf-order/src/views/kiosk/settings/KioskPaymentTypeSection.vue.d.ts +13 -3
- package/dist/frontend/mf-order/src/views/order-settings/delivery/integrated-delivery/ExternalSetting.vue.d.ts +12 -4
- package/dist/frontend/mf-order/src/views/order-settings/dine-in/OfflinePaymentTypeDialog.vue.d.ts +4 -4
- package/dist/frontend/mf-order/src/views/order-settings/dine-in/PaymentType.vue.d.ts +38 -4
- package/dist/frontend/mf-order/src/views/order-settings/pickup/PaymentSidesheet.vue.d.ts +1 -0
- package/dist/frontend/mf-order/src/views/order-settings/reservation/CustomTimePicker.vue.d.ts +1 -0
- package/dist/{index-CWrX79Jg.js → index-B6AGCsrw.js} +6 -6
- package/dist/index-BpKR-Cxd.js +19757 -0
- package/dist/{menu.dto-CgymySda.js → menu.dto-C_B3M2fs.js} +44222 -46755
- package/dist/package/entity/incoming-order/incoming-order.do.d.ts +22443 -3
- package/dist/package/entity/incoming-order/incoming-order.dto.d.ts +3 -3
- package/dist/package/entity/incoming-order/incoming-order.enum.d.ts +1 -1
- package/dist/package/entity/index.d.ts +1 -0
- package/dist/package/entity/marketing/marketing.dto.d.ts +1 -1
- package/dist/package/entity/order/dine-in/qr.dto.d.ts +38 -0
- package/dist/package/entity/order/order.do.d.ts +6358 -2
- package/dist/package/entity/order/order.dto.d.ts +22 -0
- package/dist/package/entity/order-platform/deliveroo/deliveroo-dto.d.ts +3 -0
- package/dist/package/entity/order-platform/deliveroo/deliveroo-setting.do.d.ts +3 -0
- package/dist/package/entity/order-platform/external/order/external-order.do.d.ts +12 -12
- package/dist/package/entity/order-platform/external/order/external-order.dto.d.ts +32 -32
- package/dist/package/entity/order-platform/external/setting/external-setting.do.d.ts +21 -3
- package/dist/package/entity/order-platform/external/setting/external-setting.dto.d.ts +12 -2
- package/dist/package/entity/order-platform/foodpanda/foodpanda-settings.do.d.ts +3 -0
- package/dist/package/entity/order-platform/foodpanda/foodpanda-settings.dto.d.ts +3 -0
- package/dist/package/entity/order-platform/grabfood/grabfood-edit-order.do.d.ts +9 -1
- package/dist/package/entity/order-platform/grabfood/grabfood-settings.do.d.ts +2 -2
- package/dist/package/entity/order-platform/grabfood/grabfood.dto.d.ts +3 -3
- package/dist/package/entity/order-platform/order-platform.dto.d.ts +2 -2
- package/dist/package/entity/order-platform/shopeefood/shopeefood-settings.do.d.ts +3 -0
- package/dist/package/entity/order-platform/shopeefood/shopeefood-settings.dto.d.ts +3 -0
- package/dist/package/entity/order-setting/order-setting.do.d.ts +3 -0
- package/dist/package/entity/order-setting/order-setting.dto.d.ts +6 -0
- package/dist/package/entity/queue/queue.do.d.ts +3 -8
- package/dist/package/entity/queue/queue.dto.d.ts +10 -0
- package/dist/package/entity/reservation/reservation.do.d.ts +4 -0
- package/dist/package/entity/reservation/reservation.dto.d.ts +10 -0
- package/dist/package/entity/reservation/reservation.utils.d.ts +2 -2
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/views/kiosk/KioskSummary.vue +3 -0
- package/src/views/kiosk/settings/KioskPaymentTypeSection.vue +99 -211
- package/src/views/kiosk/settings/KioskSettingView.vue +27 -11
- package/src/views/order-settings/dine-in/DineInSetting.vue +1 -0
- package/src/views/order-settings/dine-in/OfflinePaymentTypeDialog.vue +2 -3
- package/src/views/order-settings/dine-in/PaymentType.vue +151 -43
- package/src/views/order-settings/pickup/PaymentSidesheet.vue +33 -172
- package/src/views/order-settings/pickup/PickUpSettingDialogContent.vue +1 -0
- package/src/views/order-settings/reservation/CustomTimePicker.vue +129 -49
- package/src/views/order-settings/reservation/ReservationSetting.vue +547 -303
- package/dist/KioskSettingView-8GY7AT-N.js +0 -722
- package/dist/OrderSettingsView-BZcU4t9L.js +0 -56240
- package/dist/index-BXsnV_eO.js +0 -150
|
@@ -22,7 +22,6 @@ import moment from 'moment'
|
|
|
22
22
|
import CopySettingsSheet from './CopySettingsSheet.vue'
|
|
23
23
|
import notfound from '@/assets/images/not-found.png'
|
|
24
24
|
|
|
25
|
-
|
|
26
25
|
const { t } = useI18n()
|
|
27
26
|
const { showSuccess } = useSnackbarFunctions()
|
|
28
27
|
const { currentRestaurant, restaurants } = useCoreStore()
|
|
@@ -39,7 +38,8 @@ const generateRangeDefaults = (index = 0) => ({
|
|
|
39
38
|
})
|
|
40
39
|
|
|
41
40
|
// Helper function to generate unique capacity tier ID
|
|
42
|
-
const generateCapacityTierId = () =>
|
|
41
|
+
const generateCapacityTierId = () =>
|
|
42
|
+
new Date().toISOString() + '-' + Math.random().toString(36).substr(2, 9)
|
|
43
43
|
|
|
44
44
|
const DEFAULT_GUEST_MESSAGE = `Please take note of the following important details before making a reservation:
|
|
45
45
|
|
|
@@ -53,21 +53,144 @@ const DEFAULT_CANCELLATION_POLICY = `Cancellation Policy
|
|
|
53
53
|
|
|
54
54
|
Free cancellation up to 24 hours before your reservation. Please contact the outlet for any last-minute changes.`
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
function createDefaultCapacityTiers() {
|
|
57
|
+
return [
|
|
58
|
+
{
|
|
59
|
+
_id: generateCapacityTierId(),
|
|
60
|
+
minPax: 1,
|
|
61
|
+
maxPax: 2,
|
|
62
|
+
capacity: 8
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
_id: generateCapacityTierId(),
|
|
66
|
+
minPax: 3,
|
|
67
|
+
maxPax: 6,
|
|
68
|
+
capacity: 8
|
|
69
|
+
},
|
|
58
70
|
{
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
71
|
+
_id: generateCapacityTierId(),
|
|
72
|
+
minPax: 5,
|
|
73
|
+
maxPax: 7,
|
|
74
|
+
capacity: 8
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function createDefaultRange(index = 0): FdoReservationRange {
|
|
80
|
+
return {
|
|
81
|
+
...generateRangeDefaults(index),
|
|
82
|
+
enable: false,
|
|
83
|
+
bookingDuration: 90,
|
|
84
|
+
enablePreorder: true,
|
|
85
|
+
minLeadDuration: {
|
|
86
|
+
value: 0,
|
|
87
|
+
unit: 'day'
|
|
88
|
+
},
|
|
89
|
+
maxLeadDuration: {
|
|
90
|
+
value: 30,
|
|
91
|
+
unit: 'day'
|
|
92
|
+
},
|
|
93
|
+
operatingHours: {
|
|
94
|
+
0: {
|
|
95
|
+
enable: false,
|
|
96
|
+
hours: []
|
|
97
|
+
},
|
|
98
|
+
1: {
|
|
99
|
+
enable: true,
|
|
100
|
+
hours: [
|
|
101
|
+
{ start: '09:00', end: '14:00' },
|
|
102
|
+
{ start: '17:00', end: '22:00' }
|
|
103
|
+
]
|
|
104
|
+
},
|
|
105
|
+
2: {
|
|
106
|
+
enable: true,
|
|
107
|
+
hours: [
|
|
108
|
+
{ start: '09:00', end: '14:00' },
|
|
109
|
+
{ start: '17:00', end: '22:00' }
|
|
110
|
+
]
|
|
66
111
|
},
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
112
|
+
3: {
|
|
113
|
+
enable: true,
|
|
114
|
+
hours: [
|
|
115
|
+
{ start: '09:00', end: '14:00' },
|
|
116
|
+
{ start: '17:00', end: '22:00' }
|
|
117
|
+
]
|
|
70
118
|
},
|
|
119
|
+
4: {
|
|
120
|
+
enable: true,
|
|
121
|
+
hours: [
|
|
122
|
+
{ start: '09:00', end: '14:00' },
|
|
123
|
+
{ start: '17:00', end: '22:00' }
|
|
124
|
+
]
|
|
125
|
+
},
|
|
126
|
+
5: {
|
|
127
|
+
enable: true,
|
|
128
|
+
hours: [
|
|
129
|
+
{ start: '09:00', end: '14:00' },
|
|
130
|
+
{ start: '17:00', end: '22:00' }
|
|
131
|
+
]
|
|
132
|
+
},
|
|
133
|
+
6: {
|
|
134
|
+
enable: true,
|
|
135
|
+
hours: []
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
preferences: [],
|
|
139
|
+
capacityTiers: createDefaultCapacityTiers(),
|
|
140
|
+
slotInterval: 30,
|
|
141
|
+
guestMessage: DEFAULT_GUEST_MESSAGE,
|
|
142
|
+
cancellationPolicy: DEFAULT_CANCELLATION_POLICY
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function parseTimeToMinutes(time: string) {
|
|
147
|
+
const [hours, minutes] = time.split(':').map(Number)
|
|
148
|
+
return hours * 60 + minutes
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function isOvernightRange(range: { start: string; end: string }) {
|
|
152
|
+
return parseTimeToMinutes(range.end) <= parseTimeToMinutes(range.start)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function normalizeRange<T extends Partial<FdoReservationRange>>(range: T, index = 0): T {
|
|
156
|
+
const defaults = generateRangeDefaults(index)
|
|
157
|
+
return {
|
|
158
|
+
...range,
|
|
159
|
+
_id: range._id || defaults._id,
|
|
160
|
+
name: range.name || defaults.name,
|
|
161
|
+
capacityTiers: range.capacityTiers?.length ? range.capacityTiers : createDefaultCapacityTiers()
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function getPreviewHoursForDay(day: 0 | 1 | 2 | 3 | 4 | 5 | 6) {
|
|
166
|
+
const operatingHours = rangeSetting.value.operatingHours
|
|
167
|
+
const previousDay = ((day + 6) % 7) as 0 | 1 | 2 | 3 | 4 | 5 | 6
|
|
168
|
+
const currentDayHours = operatingHours[day]
|
|
169
|
+
const previousDayHours = operatingHours[previousDay]
|
|
170
|
+
|
|
171
|
+
const spilloverHours = previousDayHours?.enable
|
|
172
|
+
? previousDayHours.hours
|
|
173
|
+
.filter((hour) => isOvernightRange(hour))
|
|
174
|
+
.map((hour) => ({ start: '00:00', end: hour.end }))
|
|
175
|
+
: []
|
|
176
|
+
|
|
177
|
+
const currentHours = currentDayHours?.enable ? currentDayHours.hours : []
|
|
178
|
+
|
|
179
|
+
return [...spilloverHours, ...currentHours]
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function getPreviewOperatingHours(day: 0 | 1 | 2 | 3 | 4 | 5 | 6) {
|
|
183
|
+
const hours = getPreviewHoursForDay(day)
|
|
184
|
+
return {
|
|
185
|
+
enable: hours.length > 0,
|
|
186
|
+
hours
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const reservationSettings = ref<FdoOrderReservationSettingsV2>({
|
|
191
|
+
ranges: [
|
|
192
|
+
{
|
|
193
|
+
...createDefaultRange(0),
|
|
71
194
|
operatingHours: {
|
|
72
195
|
0: {
|
|
73
196
|
enable: false,
|
|
@@ -97,12 +220,7 @@ const reservationSettings = ref<FdoOrderReservationSettingsV2>({
|
|
|
97
220
|
enable: false,
|
|
98
221
|
hours: []
|
|
99
222
|
}
|
|
100
|
-
}
|
|
101
|
-
preferences: [],
|
|
102
|
-
capacityTiers: [],
|
|
103
|
-
slotInterval: 30,
|
|
104
|
-
guestMessage: DEFAULT_GUEST_MESSAGE,
|
|
105
|
-
cancellationPolicy: DEFAULT_CANCELLATION_POLICY
|
|
223
|
+
}
|
|
106
224
|
}
|
|
107
225
|
],
|
|
108
226
|
posCanOverbook: false,
|
|
@@ -113,95 +231,7 @@ const reservationSettings = ref<FdoOrderReservationSettingsV2>({
|
|
|
113
231
|
notifyOnCancel: true
|
|
114
232
|
})
|
|
115
233
|
|
|
116
|
-
const rangeSetting = ref<FdoReservationRange>(
|
|
117
|
-
...generateRangeDefaults(0),
|
|
118
|
-
enable: false,
|
|
119
|
-
bookingDuration: 90,
|
|
120
|
-
enablePreorder: true,
|
|
121
|
-
minLeadDuration: {
|
|
122
|
-
value: 0,
|
|
123
|
-
unit: 'day'
|
|
124
|
-
},
|
|
125
|
-
maxLeadDuration: {
|
|
126
|
-
value: 30,
|
|
127
|
-
unit: 'day'
|
|
128
|
-
},
|
|
129
|
-
operatingHours: {
|
|
130
|
-
0: {
|
|
131
|
-
enable: true,
|
|
132
|
-
hours: []
|
|
133
|
-
},
|
|
134
|
-
1: {
|
|
135
|
-
enable: true,
|
|
136
|
-
hours: [
|
|
137
|
-
{ start: '09:00', end: '14:00' },
|
|
138
|
-
{ start: '17:00', end: '22:00' }
|
|
139
|
-
]
|
|
140
|
-
},
|
|
141
|
-
2: {
|
|
142
|
-
enable: true,
|
|
143
|
-
hours: [
|
|
144
|
-
{ start: '09:00', end: '14:00' },
|
|
145
|
-
{ start: '17:00', end: '22:00' }
|
|
146
|
-
]
|
|
147
|
-
},
|
|
148
|
-
3: {
|
|
149
|
-
enable: true,
|
|
150
|
-
hours: [
|
|
151
|
-
{ start: '09:00', end: '14:00' },
|
|
152
|
-
{ start: '17:00', end: '22:00' }
|
|
153
|
-
]
|
|
154
|
-
},
|
|
155
|
-
4: {
|
|
156
|
-
enable: true,
|
|
157
|
-
hours: [
|
|
158
|
-
{ start: '09:00', end: '14:00' },
|
|
159
|
-
{ start: '17:00', end: '22:00' }
|
|
160
|
-
]
|
|
161
|
-
},
|
|
162
|
-
5: {
|
|
163
|
-
enable: true,
|
|
164
|
-
hours: [
|
|
165
|
-
{ start: '09:00', end: '14:00' },
|
|
166
|
-
{ start: '17:00', end: '22:00' }
|
|
167
|
-
]
|
|
168
|
-
},
|
|
169
|
-
6: {
|
|
170
|
-
enable: true,
|
|
171
|
-
hours: []
|
|
172
|
-
}
|
|
173
|
-
},
|
|
174
|
-
preferences: [],
|
|
175
|
-
capacityTiers: [
|
|
176
|
-
{
|
|
177
|
-
_id: generateCapacityTierId(),
|
|
178
|
-
minPax: 1,
|
|
179
|
-
maxPax: 2,
|
|
180
|
-
capacity: 10
|
|
181
|
-
},
|
|
182
|
-
{
|
|
183
|
-
_id: generateCapacityTierId(),
|
|
184
|
-
minPax: 3,
|
|
185
|
-
maxPax: 4,
|
|
186
|
-
capacity: 8
|
|
187
|
-
},
|
|
188
|
-
{
|
|
189
|
-
_id: generateCapacityTierId(),
|
|
190
|
-
minPax: 5,
|
|
191
|
-
maxPax: 6,
|
|
192
|
-
capacity: 5
|
|
193
|
-
},
|
|
194
|
-
{
|
|
195
|
-
_id: generateCapacityTierId(),
|
|
196
|
-
minPax: 7,
|
|
197
|
-
maxPax: null,
|
|
198
|
-
capacity: 3
|
|
199
|
-
}
|
|
200
|
-
],
|
|
201
|
-
slotInterval: 30,
|
|
202
|
-
guestMessage: DEFAULT_GUEST_MESSAGE,
|
|
203
|
-
cancellationPolicy: DEFAULT_CANCELLATION_POLICY
|
|
204
|
-
})
|
|
234
|
+
const rangeSetting = ref<FdoReservationRange>(createDefaultRange(0))
|
|
205
235
|
|
|
206
236
|
const isSaving = ref(false)
|
|
207
237
|
|
|
@@ -281,9 +311,10 @@ const selectedPreset = computed(() => {
|
|
|
281
311
|
// Now includes unavailable slots (between operating hour gaps)
|
|
282
312
|
const availableSlots = computed(() => {
|
|
283
313
|
const day = selectedPreviewDay.value as 0 | 1 | 2 | 3 | 4 | 5 | 6
|
|
284
|
-
const {
|
|
314
|
+
const { slotInterval, bookingDuration } = rangeSetting.value
|
|
315
|
+
const previewOperatingHours = getPreviewOperatingHours(day)
|
|
285
316
|
|
|
286
|
-
if (!
|
|
317
|
+
if (!previewOperatingHours.enable) {
|
|
287
318
|
return { morning: [], afternoon: [], evening: [] }
|
|
288
319
|
}
|
|
289
320
|
|
|
@@ -291,7 +322,7 @@ const availableSlots = computed(() => {
|
|
|
291
322
|
// This generates slots only from earliest to latest operating hour (not full 24h)
|
|
292
323
|
// Example: If hours are 10:00-12:00, 15:00-21:00, shows 10:00-21:00 range
|
|
293
324
|
const slotsWithStatus = generateDayTimeSlotsWithStatus(
|
|
294
|
-
|
|
325
|
+
previewOperatingHours,
|
|
295
326
|
slotInterval,
|
|
296
327
|
bookingDuration
|
|
297
328
|
)
|
|
@@ -333,15 +364,13 @@ const hasMoreItems = computed(() => {
|
|
|
333
364
|
|
|
334
365
|
const reservationPreview = computed(() => {
|
|
335
366
|
const today = moment()
|
|
336
|
-
const { minLeadDuration, maxLeadDuration
|
|
367
|
+
const { minLeadDuration, maxLeadDuration } = rangeSetting.value
|
|
337
368
|
const startDate = today.clone().add(minLeadDuration.value, minLeadDuration.unit).startOf('d')
|
|
338
369
|
const endDate = today.clone().add(maxLeadDuration.value, maxLeadDuration.unit).startOf('d')
|
|
339
370
|
|
|
340
|
-
type Day = keyof typeof operatingHours
|
|
341
|
-
|
|
342
371
|
// Get the hours for start and end days
|
|
343
|
-
const startDayHours =
|
|
344
|
-
const endDayHours =
|
|
372
|
+
const startDayHours = getPreviewHoursForDay(startDate.day() as 0 | 1 | 2 | 3 | 4 | 5 | 6)
|
|
373
|
+
const endDayHours = getPreviewHoursForDay(endDate.day() as 0 | 1 | 2 | 3 | 4 | 5 | 6)
|
|
345
374
|
|
|
346
375
|
// Check if hours exist for both days
|
|
347
376
|
if (!startDayHours || startDayHours.length === 0 || !endDayHours || endDayHours.length === 0) {
|
|
@@ -354,11 +383,14 @@ const reservationPreview = computed(() => {
|
|
|
354
383
|
}
|
|
355
384
|
|
|
356
385
|
// use operatingHours and get the earlier starting hour to return a start&end for the preview, return the formatted time
|
|
357
|
-
const startHour = [...startDayHours].sort(
|
|
358
|
-
|
|
386
|
+
const startHour = [...startDayHours].sort(
|
|
387
|
+
(a, b) => parseTimeToMinutes(a.start) - parseTimeToMinutes(b.start)
|
|
359
388
|
)[0]
|
|
360
|
-
const endHour = [...endDayHours].sort(
|
|
361
|
-
|
|
389
|
+
const endHour = [...endDayHours].sort(
|
|
390
|
+
(a, b) =>
|
|
391
|
+
parseTimeToMinutes(b.end) +
|
|
392
|
+
(isOvernightRange(b) ? 24 * 60 : 0) -
|
|
393
|
+
(parseTimeToMinutes(a.end) + (isOvernightRange(a) ? 24 * 60 : 0))
|
|
362
394
|
)[0]
|
|
363
395
|
|
|
364
396
|
return {
|
|
@@ -431,7 +463,7 @@ async function updateReservationSetting() {
|
|
|
431
463
|
|
|
432
464
|
// Validate with Zod schema
|
|
433
465
|
const validated = ReservationSettingsSchema.parse(reservationSettings.value)
|
|
434
|
-
console.log(validated)
|
|
466
|
+
console.log(validated)
|
|
435
467
|
await ReservationApi.updateReservationSetting(currentRestaurant.value._id, validated)
|
|
436
468
|
await init()
|
|
437
469
|
})
|
|
@@ -445,7 +477,7 @@ async function init() {
|
|
|
445
477
|
if (currentRestaurant.value) {
|
|
446
478
|
const settings = await readReservationSetting()
|
|
447
479
|
reservationSettings.value = {
|
|
448
|
-
ranges: settings.ranges || [],
|
|
480
|
+
ranges: (settings.ranges || []).map((range, index) => normalizeRange(range, index)),
|
|
449
481
|
posCanOverbook: settings.posCanOverbook || false,
|
|
450
482
|
draftHoldTimeMinutes: settings.draftHoldTimeMinutes || 15,
|
|
451
483
|
smsEnabled: settings.smsEnabled ?? true,
|
|
@@ -455,17 +487,11 @@ async function init() {
|
|
|
455
487
|
}
|
|
456
488
|
// Sync rangeSetting with the first range if it exists, otherwise reset to default
|
|
457
489
|
if (settings.ranges && settings.ranges.length > 0) {
|
|
458
|
-
rangeSetting.value = settings.ranges[0]
|
|
490
|
+
rangeSetting.value = normalizeRange(settings.ranges[0], 0)
|
|
459
491
|
} else {
|
|
460
492
|
// Reset to default state when no ranges exist (fresh restaurant)
|
|
461
493
|
rangeSetting.value = {
|
|
462
|
-
|
|
463
|
-
name: 'Dining Area 1',
|
|
464
|
-
enable: false,
|
|
465
|
-
bookingDuration: 90,
|
|
466
|
-
enablePreorder: true,
|
|
467
|
-
minLeadDuration: { value: 0, unit: 'day' },
|
|
468
|
-
maxLeadDuration: { value: 30, unit: 'day' },
|
|
494
|
+
...createDefaultRange(0),
|
|
469
495
|
operatingHours: {
|
|
470
496
|
0: { enable: false, hours: [] },
|
|
471
497
|
1: { enable: false, hours: [] },
|
|
@@ -474,12 +500,7 @@ async function init() {
|
|
|
474
500
|
4: { enable: false, hours: [] },
|
|
475
501
|
5: { enable: false, hours: [] },
|
|
476
502
|
6: { enable: false, hours: [] }
|
|
477
|
-
}
|
|
478
|
-
preferences: [],
|
|
479
|
-
capacityTiers: [],
|
|
480
|
-
slotInterval: 30,
|
|
481
|
-
guestMessage: DEFAULT_GUEST_MESSAGE,
|
|
482
|
-
cancellationPolicy: DEFAULT_CANCELLATION_POLICY
|
|
503
|
+
}
|
|
483
504
|
}
|
|
484
505
|
}
|
|
485
506
|
|
|
@@ -632,7 +653,7 @@ function validateTimeRange(day: 0 | 1 | 2 | 3 | 4 | 5 | 6, hourIndex: number): s
|
|
|
632
653
|
|
|
633
654
|
// Validate same day time range (end > start)
|
|
634
655
|
if (!validateSameDayTimeRange(range)) {
|
|
635
|
-
return '
|
|
656
|
+
return 'Start and end time cannot be the same'
|
|
636
657
|
}
|
|
637
658
|
|
|
638
659
|
// Check for overlaps with other time ranges
|
|
@@ -736,9 +757,13 @@ function handleCopySettings(copiedSettings: Partial<FdoOrderReservationSettingsV
|
|
|
736
757
|
<div class="flex-grow fm-typo-en-title-sm-600 mb-16">
|
|
737
758
|
{{ t('order.reservationStatus') }}
|
|
738
759
|
</div>
|
|
739
|
-
<FmSwitch
|
|
760
|
+
<FmSwitch
|
|
761
|
+
label-placement="right"
|
|
762
|
+
:label="'Enable reservation'"
|
|
740
763
|
:sublabel="'Enable this to make the outlet available for reservations. This setting does not impact walk-in dining.'"
|
|
741
|
-
:model-value="rangeSetting.enable ?? false"
|
|
764
|
+
:model-value="rangeSetting.enable ?? false"
|
|
765
|
+
@update:model-value="(v: boolean) => (rangeSetting.enable = v)"
|
|
766
|
+
/>
|
|
742
767
|
|
|
743
768
|
<div v-if="rangeSetting.enable && isMultiOutlet" class="ml-56 my-8">
|
|
744
769
|
<CopySettingsSheet :current-settings="reservationSettings" @apply="handleCopySettings" />
|
|
@@ -791,21 +816,31 @@ function handleCopySettings(copiedSettings: Partial<FdoOrderReservationSettingsV
|
|
|
791
816
|
<div class="mb-5">
|
|
792
817
|
<div class="mb-8 fm-typo-en-body-md-600 flex items-center gap-8">
|
|
793
818
|
{{ t('order.draftHoldTimeMinutes') }}
|
|
794
|
-
<FmTooltip
|
|
819
|
+
<FmTooltip
|
|
820
|
+
:content="'The amount of time a reservation is held before it is automatically released.'"
|
|
821
|
+
>
|
|
795
822
|
<FmIcon name="info" outline size="sm" class="cursor-pointer" />
|
|
796
823
|
</FmTooltip>
|
|
797
824
|
</div>
|
|
798
|
-
<FmTextField
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
825
|
+
<FmTextField
|
|
826
|
+
type="number"
|
|
827
|
+
:model-value="reservationSettings.draftHoldTimeMinutes"
|
|
828
|
+
@update:model-value="
|
|
829
|
+
(v: string | number) =>
|
|
830
|
+
updateSetting('draftHoldTimeMinutes', v === '' ? 15 : Number(v))
|
|
831
|
+
"
|
|
832
|
+
suffix="minutes"
|
|
833
|
+
class="max-w-md"
|
|
834
|
+
/>
|
|
803
835
|
</div>
|
|
804
836
|
<div class="mb-5">
|
|
805
|
-
<FmSwitch
|
|
837
|
+
<FmSwitch
|
|
838
|
+
:model-value="reservationSettings.posCanOverbook"
|
|
806
839
|
@update:model-value="(v: boolean) => updateSetting('posCanOverbook', v)"
|
|
807
|
-
:label="t('order.posCanOverbook')"
|
|
808
|
-
|
|
840
|
+
:label="t('order.posCanOverbook')"
|
|
841
|
+
label-placement="right"
|
|
842
|
+
:sublabel="t('order.posCanOverbookDescription')"
|
|
843
|
+
/>
|
|
809
844
|
</div>
|
|
810
845
|
</FmCard>
|
|
811
846
|
</div>
|
|
@@ -823,7 +858,8 @@ function handleCopySettings(copiedSettings: Partial<FdoOrderReservationSettingsV
|
|
|
823
858
|
<div class="mb-8">Preview</div>
|
|
824
859
|
<div class="flex items-center gap-4 mb-8">
|
|
825
860
|
<div
|
|
826
|
-
class="w-fit border-1 border-fm-color-neutral-gray-200 rounded-md py-4 px-8 bg-fm-color-neutral-gray-100 flex items-center gap-8 text-fm-color-typo-tertiary"
|
|
861
|
+
class="w-fit border-1 border-fm-color-neutral-gray-200 rounded-md py-4 px-8 bg-fm-color-neutral-gray-100 flex items-center gap-8 text-fm-color-typo-tertiary"
|
|
862
|
+
>
|
|
827
863
|
<FmIcon name="calendar_month" outline />
|
|
828
864
|
<div class="pr-6">
|
|
829
865
|
{{ reservationPreview.start + ' ' + reservationPreview.startHour }}
|
|
@@ -831,7 +867,8 @@ function handleCopySettings(copiedSettings: Partial<FdoOrderReservationSettingsV
|
|
|
831
867
|
</div>
|
|
832
868
|
<div>to</div>
|
|
833
869
|
<div
|
|
834
|
-
class="w-fit border-1 border-fm-color-neutral-gray-200 rounded-md py-4 px-8 bg-fm-color-neutral-gray-100 flex items-center gap-8 text-fm-color-typo-tertiary"
|
|
870
|
+
class="w-fit border-1 border-fm-color-neutral-gray-200 rounded-md py-4 px-8 bg-fm-color-neutral-gray-100 flex items-center gap-8 text-fm-color-typo-tertiary"
|
|
871
|
+
>
|
|
835
872
|
<FmIcon name="calendar_month" outline />
|
|
836
873
|
<div class="pr-6">
|
|
837
874
|
{{ reservationPreview.end + ' ' + reservationPreview.endHour }}
|
|
@@ -847,8 +884,10 @@ function handleCopySettings(copiedSettings: Partial<FdoOrderReservationSettingsV
|
|
|
847
884
|
|
|
848
885
|
<div class="mb-24">
|
|
849
886
|
<div class="mb-8">Presets</div>
|
|
850
|
-
<FmRadioGroup
|
|
851
|
-
|
|
887
|
+
<FmRadioGroup
|
|
888
|
+
:model-value="selectedPreset"
|
|
889
|
+
@update:model-value="(v: number | 'custom') => updateMaxLeadPreset(v)"
|
|
890
|
+
>
|
|
852
891
|
<FmRadio label="30 days" :value="30">
|
|
853
892
|
<template #label>
|
|
854
893
|
<div>30 days <span class="text-fm-color-typo-secondary">(default)</span></div>
|
|
@@ -860,24 +899,35 @@ function handleCopySettings(copiedSettings: Partial<FdoOrderReservationSettingsV
|
|
|
860
899
|
</FmRadioGroup>
|
|
861
900
|
|
|
862
901
|
<!-- Custom preset fields -->
|
|
863
|
-
<div
|
|
864
|
-
|
|
902
|
+
<div
|
|
903
|
+
v-if="selectedPreset === 'custom'"
|
|
904
|
+
class="ml-32 mt-12 p-16 border rounded-md bg-fm-color-neutral-gray-50"
|
|
905
|
+
>
|
|
865
906
|
<div class="mb-16">
|
|
866
907
|
<div class="mb-8 fm-typo-en-body-md-600 flex items-center gap-8">
|
|
867
908
|
Minimum Lead Time
|
|
868
909
|
<FmTooltip
|
|
869
|
-
:content="'The earliest time a guest can make a reservation. For example, if you set this to 1 day, guests can only make reservations from tomorrow onwards.'"
|
|
910
|
+
:content="'The earliest time a guest can make a reservation. For example, if you set this to 1 day, guests can only make reservations from tomorrow onwards.'"
|
|
911
|
+
>
|
|
870
912
|
<FmIcon name="info" outline size="sm" class="cursor-pointer" />
|
|
871
913
|
</FmTooltip>
|
|
872
914
|
</div>
|
|
873
915
|
<div class="flex items-center gap-8">
|
|
874
|
-
<FmStepperField
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
916
|
+
<FmStepperField
|
|
917
|
+
:model-value="rangeSetting.minLeadDuration.value"
|
|
918
|
+
@update:model-value="(v: number) => updateMinLeadValue(v)"
|
|
919
|
+
:min="0"
|
|
920
|
+
class=""
|
|
921
|
+
/>
|
|
922
|
+
<FmSelect
|
|
923
|
+
:model-value="rangeSetting.minLeadDuration.unit"
|
|
924
|
+
@update:model-value="(v: 'hour' | 'day') => updateMinLeadUnit(v)"
|
|
925
|
+
:items="[
|
|
878
926
|
{ label: 'Hours', value: 'hour' },
|
|
879
927
|
{ label: 'Days', value: 'day' }
|
|
880
|
-
]"
|
|
928
|
+
]"
|
|
929
|
+
class="w-120"
|
|
930
|
+
/>
|
|
881
931
|
</div>
|
|
882
932
|
</div>
|
|
883
933
|
|
|
@@ -885,18 +935,27 @@ function handleCopySettings(copiedSettings: Partial<FdoOrderReservationSettingsV
|
|
|
885
935
|
<div class="mb-8 fm-typo-en-body-md-600 flex items-center gap-8">
|
|
886
936
|
Maximum Lead Time
|
|
887
937
|
<FmTooltip
|
|
888
|
-
:content="'The furthest in advance a guest can make a reservation. For example, if you set this to 30 days, guests can only make reservations up to 30 days from today.'"
|
|
938
|
+
:content="'The furthest in advance a guest can make a reservation. For example, if you set this to 30 days, guests can only make reservations up to 30 days from today.'"
|
|
939
|
+
>
|
|
889
940
|
<FmIcon name="info" outline size="sm" class="cursor-pointer" />
|
|
890
941
|
</FmTooltip>
|
|
891
942
|
</div>
|
|
892
943
|
<div class="flex items-center gap-8">
|
|
893
|
-
<FmStepperField
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
944
|
+
<FmStepperField
|
|
945
|
+
:model-value="rangeSetting.maxLeadDuration.value"
|
|
946
|
+
@update:model-value="(v: number) => updateMaxLeadValue(v)"
|
|
947
|
+
:min="0"
|
|
948
|
+
class=""
|
|
949
|
+
/>
|
|
950
|
+
<FmSelect
|
|
951
|
+
:model-value="rangeSetting.maxLeadDuration.unit"
|
|
952
|
+
@update:model-value="(v: 'hour' | 'day') => updateMaxLeadUnit(v)"
|
|
953
|
+
:items="[
|
|
897
954
|
{ label: 'Hours', value: 'hour' },
|
|
898
955
|
{ label: 'Days', value: 'day' }
|
|
899
|
-
]"
|
|
956
|
+
]"
|
|
957
|
+
class="w-120"
|
|
958
|
+
/>
|
|
900
959
|
</div>
|
|
901
960
|
</div>
|
|
902
961
|
|
|
@@ -909,9 +968,7 @@ function handleCopySettings(copiedSettings: Partial<FdoOrderReservationSettingsV
|
|
|
909
968
|
</div>
|
|
910
969
|
|
|
911
970
|
<div class="flex flex-col">
|
|
912
|
-
<div class="flex-grow fm-typo-en-title-sm-600 mb-4">
|
|
913
|
-
Time Settings
|
|
914
|
-
</div>
|
|
971
|
+
<div class="flex-grow fm-typo-en-title-sm-600 mb-4">Time Settings</div>
|
|
915
972
|
<div class="fm-typo-en-body-lg-400 text-fm-color-typo-secondary mb-12">
|
|
916
973
|
Choose the start and end time for reservations, and the interval between each time slot.
|
|
917
974
|
Available time slots will be generated automatically.
|
|
@@ -920,13 +977,19 @@ function handleCopySettings(copiedSettings: Partial<FdoOrderReservationSettingsV
|
|
|
920
977
|
<!-- Operating hour setting -->
|
|
921
978
|
<div class="mb-32">
|
|
922
979
|
<div class="grid grid-cols-[1fr_1fr_3fr] items-start">
|
|
923
|
-
<div
|
|
980
|
+
<div
|
|
981
|
+
class="p-12 fm-typo-en-body-md-600 text-fm-color-typo-secondary bg-fm-color-neutral-gray-100"
|
|
982
|
+
>
|
|
924
983
|
Day
|
|
925
984
|
</div>
|
|
926
|
-
<div
|
|
985
|
+
<div
|
|
986
|
+
class="p-12 fm-typo-en-body-md-600 text-fm-color-typo-secondary bg-fm-color-neutral-gray-100"
|
|
987
|
+
>
|
|
927
988
|
Open / Closed
|
|
928
989
|
</div>
|
|
929
|
-
<div
|
|
990
|
+
<div
|
|
991
|
+
class="p-12 fm-typo-en-body-md-600 text-fm-color-typo-secondary bg-fm-color-neutral-gray-100"
|
|
992
|
+
>
|
|
930
993
|
Reservation Time Range
|
|
931
994
|
</div>
|
|
932
995
|
<template v-for="day in [1, 2, 3, 4, 5, 6, 0] as const" :key="day">
|
|
@@ -934,40 +997,63 @@ function handleCopySettings(copiedSettings: Partial<FdoOrderReservationSettingsV
|
|
|
934
997
|
{{ moment().day(day).format('dddd') }}
|
|
935
998
|
</div>
|
|
936
999
|
<template v-if="rangeSetting.operatingHours[day as 0 | 1 | 2 | 3 | 4 | 5 | 6]">
|
|
937
|
-
<template
|
|
938
|
-
|
|
1000
|
+
<template
|
|
1001
|
+
v-for="hours in [rangeSetting.operatingHours[day as 0 | 1 | 2 | 3 | 4 | 5 | 6]]"
|
|
1002
|
+
:key="`hours-${day}`"
|
|
1003
|
+
>
|
|
939
1004
|
<div class="px-12 py-8">
|
|
940
|
-
<FmSwitch
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
1005
|
+
<FmSwitch
|
|
1006
|
+
label-placement="right"
|
|
1007
|
+
:model-value="hours.enable"
|
|
1008
|
+
@update:model-value="
|
|
1009
|
+
(v) => {
|
|
1010
|
+
rangeSetting.operatingHours[day].enable = v
|
|
1011
|
+
rangeSetting.operatingHours[day].hours = v
|
|
1012
|
+
? [
|
|
1013
|
+
{
|
|
1014
|
+
start: '00:00',
|
|
1015
|
+
end: '23:59'
|
|
1016
|
+
}
|
|
1017
|
+
]
|
|
1018
|
+
: []
|
|
1019
|
+
}
|
|
1020
|
+
"
|
|
1021
|
+
:label="hours.enable ? 'Open' : 'Closed'"
|
|
1022
|
+
/>
|
|
953
1023
|
</div>
|
|
954
1024
|
<div class="px-12 self-center">
|
|
955
1025
|
<div v-if="!hours.enable" class="">-</div>
|
|
956
1026
|
<div v-else class="flex flex-col">
|
|
957
1027
|
<div v-for="(hour, hi) in hours.hours" :key="hi" class="flex flex-col gap-4">
|
|
958
1028
|
<div class="flex gap-4 items-center justify-between">
|
|
959
|
-
<div class="flex gap-12 items-center flex-1 justify-
|
|
960
|
-
<CustomTimePicker
|
|
961
|
-
|
|
1029
|
+
<div class="flex gap-12 items-center flex-1 justify-start py-8">
|
|
1030
|
+
<CustomTimePicker
|
|
1031
|
+
v-model="hour.start"
|
|
1032
|
+
:min-time="hours.hours[hi - 1]?.end ?? '00:00'"
|
|
1033
|
+
/>
|
|
962
1034
|
<div class="text-center w-16 flex-shrink-0">to</div>
|
|
963
|
-
<CustomTimePicker
|
|
1035
|
+
<CustomTimePicker
|
|
1036
|
+
v-model="hour.end"
|
|
1037
|
+
:min-time="hour.start"
|
|
1038
|
+
:restrict-min-time="false"
|
|
1039
|
+
/>
|
|
964
1040
|
</div>
|
|
965
1041
|
<div class="mr-8 w-32 flex-shrink">
|
|
966
|
-
<FmButton
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
"
|
|
1042
|
+
<FmButton
|
|
1043
|
+
variant="plain"
|
|
1044
|
+
icon="add"
|
|
1045
|
+
v-if="hi == 0"
|
|
1046
|
+
:disabled="hours.hours.length >= 2"
|
|
1047
|
+
@click="hours.hours.push({ start: hour.end, end: '23:59' })"
|
|
1048
|
+
/>
|
|
1049
|
+
<FmButton
|
|
1050
|
+
variant="plain"
|
|
1051
|
+
icon="delete"
|
|
1052
|
+
v-if="hi > 0"
|
|
1053
|
+
@click="
|
|
1054
|
+
deleteTimeRange(day as unknown as 0 | 1 | 2 | 3 | 4 | 5 | 6, hi)
|
|
1055
|
+
"
|
|
1056
|
+
/>
|
|
971
1057
|
</div>
|
|
972
1058
|
</div>
|
|
973
1059
|
<div v-if="getTimeRangeError(day, hi)" class="text-sm text-red-600 ml-4">
|
|
@@ -991,11 +1077,15 @@ function handleCopySettings(copiedSettings: Partial<FdoOrderReservationSettingsV
|
|
|
991
1077
|
<FmIcon name="info" outline size="sm" class="cursor-pointer" />
|
|
992
1078
|
</FmTooltip>
|
|
993
1079
|
</div>
|
|
994
|
-
<FmRadioGroup
|
|
995
|
-
|
|
1080
|
+
<FmRadioGroup
|
|
1081
|
+
:model-value="rangeSetting.slotInterval"
|
|
1082
|
+
@update:model-value="(v: number) => updateSlotInterval(v)"
|
|
1083
|
+
>
|
|
996
1084
|
<FmRadio label="15 min" :value="15" />
|
|
997
1085
|
<FmRadio label="30 min (default)" :value="30">
|
|
998
|
-
<template #label
|
|
1086
|
+
<template #label
|
|
1087
|
+
>30 min <span class="text-fm-color-typo-secondary">(default)</span></template
|
|
1088
|
+
>
|
|
999
1089
|
</FmRadio>
|
|
1000
1090
|
<FmRadio label="60 min" :value="60" />
|
|
1001
1091
|
</FmRadioGroup>
|
|
@@ -1008,11 +1098,15 @@ function handleCopySettings(copiedSettings: Partial<FdoOrderReservationSettingsV
|
|
|
1008
1098
|
<FmIcon name="info" outline size="sm" class="cursor-pointer" />
|
|
1009
1099
|
</FmTooltip>
|
|
1010
1100
|
</div>
|
|
1011
|
-
<FmRadioGroup
|
|
1012
|
-
|
|
1101
|
+
<FmRadioGroup
|
|
1102
|
+
:model-value="rangeSetting.bookingDuration"
|
|
1103
|
+
@update:model-value="(v: number) => updateBookingDuration(v)"
|
|
1104
|
+
>
|
|
1013
1105
|
<FmRadio label="60 min" :value="60" />
|
|
1014
1106
|
<FmRadio label="90 min" :value="90">
|
|
1015
|
-
<template #label
|
|
1107
|
+
<template #label
|
|
1108
|
+
>90 min <span class="text-fm-color-typo-secondary">(default)</span></template
|
|
1109
|
+
>
|
|
1016
1110
|
</FmRadio>
|
|
1017
1111
|
|
|
1018
1112
|
<FmRadio label="120 min" :value="120" />
|
|
@@ -1023,30 +1117,39 @@ function handleCopySettings(copiedSettings: Partial<FdoOrderReservationSettingsV
|
|
|
1023
1117
|
<div class="p-16 flex flex-col gap-16">
|
|
1024
1118
|
<div class="flex items-center justify-between w-full">
|
|
1025
1119
|
<div class="fm-typo-en-body-lg-600">Available reservation slots (preview)</div>
|
|
1026
|
-
<FmSelect
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1120
|
+
<FmSelect
|
|
1121
|
+
v-model="selectedPreviewDay"
|
|
1122
|
+
:items="[
|
|
1123
|
+
{ label: 'Monday', value: 1 },
|
|
1124
|
+
{ label: 'Tuesday', value: 2 },
|
|
1125
|
+
{ label: 'Wednesday', value: 3 },
|
|
1126
|
+
{ label: 'Thursday', value: 4 },
|
|
1127
|
+
{ label: 'Friday', value: 5 },
|
|
1128
|
+
{ label: 'Saturday', value: 6 },
|
|
1129
|
+
{ label: 'Sunday', value: 0 }
|
|
1130
|
+
]"
|
|
1131
|
+
/>
|
|
1035
1132
|
</div>
|
|
1036
1133
|
|
|
1037
1134
|
<template v-if="availableSlots.morning.length > 0">
|
|
1038
1135
|
<div class="fm-typo-en-body-md-600 text-[#4B4B4B]">Morning</div>
|
|
1039
1136
|
<div class="grid grid-cols-5 gap-8">
|
|
1040
|
-
<div
|
|
1041
|
-
|
|
1137
|
+
<div
|
|
1138
|
+
v-for="slot in displayedSlots.morning"
|
|
1139
|
+
:key="slot.time"
|
|
1140
|
+
class="border-1 rounded-md text-center p-8 transition-colors"
|
|
1141
|
+
:class="{
|
|
1042
1142
|
'bg-[#fafafa] text-fm-color-typo-primary': slot.available,
|
|
1043
1143
|
'bg-[#f0f0f0] text-fm-color-typo-disabled border-dashed': !slot.available
|
|
1044
|
-
}"
|
|
1144
|
+
}"
|
|
1145
|
+
>
|
|
1045
1146
|
{{ slot.time }}
|
|
1046
1147
|
</div>
|
|
1047
|
-
<div
|
|
1148
|
+
<div
|
|
1149
|
+
v-if="hasMoreItems.morning && !expandedSegments.morning"
|
|
1048
1150
|
class="border-2 rounded-md text-center p-8 transition-colors cursor-pointer border-fm-color-primary text-fm-color-primary"
|
|
1049
|
-
@click="toggleSegmentExpansion('morning')"
|
|
1151
|
+
@click="toggleSegmentExpansion('morning')"
|
|
1152
|
+
>
|
|
1050
1153
|
more
|
|
1051
1154
|
</div>
|
|
1052
1155
|
</div>
|
|
@@ -1055,16 +1158,22 @@ function handleCopySettings(copiedSettings: Partial<FdoOrderReservationSettingsV
|
|
|
1055
1158
|
<template v-if="availableSlots.afternoon.length > 0">
|
|
1056
1159
|
<div class="fm-typo-en-body-md-600 text-[#4B4B4B]">Afternoon</div>
|
|
1057
1160
|
<div class="grid grid-cols-5 gap-8">
|
|
1058
|
-
<div
|
|
1059
|
-
|
|
1161
|
+
<div
|
|
1162
|
+
v-for="slot in displayedSlots.afternoon"
|
|
1163
|
+
:key="slot.time"
|
|
1164
|
+
class="border-1 rounded-md text-center p-8 transition-colors"
|
|
1165
|
+
:class="{
|
|
1060
1166
|
'bg-[#fafafa] text-fm-color-typo-primary': slot.available,
|
|
1061
1167
|
'bg-[#f0f0f0] text-fm-color-typo-disabled border-dashed': !slot.available
|
|
1062
|
-
}"
|
|
1168
|
+
}"
|
|
1169
|
+
>
|
|
1063
1170
|
{{ slot.time }}
|
|
1064
1171
|
</div>
|
|
1065
|
-
<div
|
|
1172
|
+
<div
|
|
1173
|
+
v-if="hasMoreItems.afternoon && !expandedSegments.afternoon"
|
|
1066
1174
|
class="border-2 rounded-md text-center p-8 transition-colors cursor-pointer border-fm-color-primary text-fm-color-primary"
|
|
1067
|
-
@click="toggleSegmentExpansion('afternoon')"
|
|
1175
|
+
@click="toggleSegmentExpansion('afternoon')"
|
|
1176
|
+
>
|
|
1068
1177
|
more
|
|
1069
1178
|
</div>
|
|
1070
1179
|
</div>
|
|
@@ -1073,27 +1182,37 @@ function handleCopySettings(copiedSettings: Partial<FdoOrderReservationSettingsV
|
|
|
1073
1182
|
<template v-if="availableSlots.evening.length > 0">
|
|
1074
1183
|
<div class="fm-typo-en-body-md-600 text-[#4B4B4B]">Evening</div>
|
|
1075
1184
|
<div class="grid grid-cols-5 gap-8">
|
|
1076
|
-
<div
|
|
1077
|
-
|
|
1185
|
+
<div
|
|
1186
|
+
v-for="slot in displayedSlots.evening"
|
|
1187
|
+
:key="slot.time"
|
|
1188
|
+
class="border-1 rounded-md text-center p-8 transition-colors"
|
|
1189
|
+
:class="{
|
|
1078
1190
|
'bg-[#fafafa] text-fm-color-typo-primary': slot.available,
|
|
1079
1191
|
'bg-[#f0f0f0] text-fm-color-typo-disabled border-dashed': !slot.available
|
|
1080
|
-
}"
|
|
1192
|
+
}"
|
|
1193
|
+
>
|
|
1081
1194
|
{{ slot.time }}
|
|
1082
1195
|
</div>
|
|
1083
|
-
<div
|
|
1196
|
+
<div
|
|
1197
|
+
v-if="hasMoreItems.evening && !expandedSegments.evening"
|
|
1084
1198
|
class="border-2 rounded-md text-center p-8 transition-colors cursor-pointer border-fm-color-primary text-fm-color-primary"
|
|
1085
|
-
@click="toggleSegmentExpansion('evening')"
|
|
1199
|
+
@click="toggleSegmentExpansion('evening')"
|
|
1200
|
+
>
|
|
1086
1201
|
more
|
|
1087
1202
|
</div>
|
|
1088
1203
|
</div>
|
|
1089
1204
|
</template>
|
|
1090
1205
|
|
|
1091
|
-
<div
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1206
|
+
<div
|
|
1207
|
+
v-if="
|
|
1208
|
+
!availableSlots.morning.length &&
|
|
1209
|
+
!availableSlots.afternoon.length &&
|
|
1210
|
+
!availableSlots.evening.length
|
|
1211
|
+
"
|
|
1212
|
+
class="text-center text-fm-color-typo-secondary flex flex-col items-center gap-16 p-24"
|
|
1213
|
+
>
|
|
1095
1214
|
<img :src="notfound" class="aspect-square w-[150px]" />
|
|
1096
|
-
Uh-oh! This outlet is closed on the selected day. <br
|
|
1215
|
+
Uh-oh! This outlet is closed on the selected day. <br />
|
|
1097
1216
|
Please select another day to view available time slots.
|
|
1098
1217
|
</div>
|
|
1099
1218
|
</div>
|
|
@@ -1101,11 +1220,10 @@ function handleCopySettings(copiedSettings: Partial<FdoOrderReservationSettingsV
|
|
|
1101
1220
|
</div>
|
|
1102
1221
|
|
|
1103
1222
|
<div class="flex flex-col">
|
|
1104
|
-
<div class="flex-grow fm-typo-en-title-sm-600 mb-4">
|
|
1105
|
-
Table Capacity Settings
|
|
1106
|
-
</div>
|
|
1223
|
+
<div class="flex-grow fm-typo-en-title-sm-600 mb-4">Table Capacity Settings</div>
|
|
1107
1224
|
<div class="fm-typo-en-body-lg-400 text-fm-color-typo-secondary mb-24">
|
|
1108
|
-
Set how many tables are available for reservation per time slot. You can reduce
|
|
1225
|
+
Set how many tables are available for reservation per time slot. You can reduce
|
|
1226
|
+
availability to keep tables for walk-ins.
|
|
1109
1227
|
</div>
|
|
1110
1228
|
|
|
1111
1229
|
<!-- Table Capacity Settings -->
|
|
@@ -1121,44 +1239,74 @@ function handleCopySettings(copiedSettings: Partial<FdoOrderReservationSettingsV
|
|
|
1121
1239
|
</div>
|
|
1122
1240
|
<div class="bg-fm-color-neutral-gray-100 p-12"></div>
|
|
1123
1241
|
</div>
|
|
1124
|
-
<div
|
|
1125
|
-
|
|
1242
|
+
<div
|
|
1243
|
+
class="grid grid-cols-[4fr_4fr_4fr_1fr] items-center gap-8"
|
|
1244
|
+
v-for="tier in rangeSetting.capacityTiers"
|
|
1245
|
+
:key="tier._id"
|
|
1246
|
+
>
|
|
1126
1247
|
<div class="p-8">{{ tier.minPax }}{{ tier.maxPax ? `-${tier.maxPax}` : '+' }} pax</div>
|
|
1127
|
-
<div class="flex items-center gap-4">
|
|
1248
|
+
<div class="flex items-center gap-4 pr-24">
|
|
1128
1249
|
<FmStepperField v-model="tier.minPax" :min="1" />
|
|
1129
1250
|
<div>to</div>
|
|
1130
|
-
<FmStepperField
|
|
1251
|
+
<FmStepperField
|
|
1252
|
+
:model-value="tier.maxPax ?? null"
|
|
1253
|
+
@update:model-value="(v) => (tier.maxPax = v)"
|
|
1254
|
+
:min="tier.minPax"
|
|
1255
|
+
/>
|
|
1131
1256
|
</div>
|
|
1132
1257
|
<div class="flex items-center gap-8">
|
|
1133
|
-
<FmButton
|
|
1258
|
+
<FmButton
|
|
1259
|
+
variant="tertiary"
|
|
1260
|
+
icon="remove"
|
|
1261
|
+
@click="tier.capacity--"
|
|
1262
|
+
:disabled="tier.capacity <= 1"
|
|
1263
|
+
class="!rounded-full !w-10 !h-10 !bg-fm-color-neutral-gray-100"
|
|
1264
|
+
/>
|
|
1134
1265
|
<div class="w-32 text-center">{{ tier.capacity }}</div>
|
|
1135
|
-
<FmButton
|
|
1266
|
+
<FmButton
|
|
1267
|
+
variant="tertiary"
|
|
1268
|
+
icon="add"
|
|
1269
|
+
@click="tier.capacity++"
|
|
1270
|
+
class="!rounded-full !w-10 !h-10 !bg-fm-color-neutral-gray-100"
|
|
1271
|
+
/>
|
|
1136
1272
|
|
|
1137
1273
|
<!-- <FmStepperField :model-value="tier.capacity ?? null" @update:model-value="(v) => (tier.capacity = v)" /> -->
|
|
1138
1274
|
</div>
|
|
1139
|
-
<FmButton
|
|
1140
|
-
|
|
1141
|
-
"
|
|
1275
|
+
<FmButton
|
|
1276
|
+
icon="delete"
|
|
1277
|
+
variant="plain"
|
|
1278
|
+
@click="
|
|
1279
|
+
rangeSetting.capacityTiers.splice(rangeSetting.capacityTiers.indexOf(tier), 1)
|
|
1280
|
+
"
|
|
1281
|
+
/>
|
|
1142
1282
|
</div>
|
|
1143
1283
|
<div>
|
|
1144
|
-
<FmButton
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1284
|
+
<FmButton
|
|
1285
|
+
label="Add table type"
|
|
1286
|
+
icon="add"
|
|
1287
|
+
variant="plain"
|
|
1288
|
+
@click="
|
|
1289
|
+
rangeSetting.capacityTiers.push({
|
|
1290
|
+
_id: generateCapacityTierId(),
|
|
1291
|
+
minPax: Math.max(1, ([...rangeSetting.capacityTiers].pop()?.maxPax ?? 0) + 1),
|
|
1292
|
+
maxPax: null,
|
|
1293
|
+
capacity: 0
|
|
1294
|
+
})
|
|
1295
|
+
"
|
|
1296
|
+
/>
|
|
1152
1297
|
</div>
|
|
1153
1298
|
</div>
|
|
1154
1299
|
</div>
|
|
1155
1300
|
|
|
1156
1301
|
<div>
|
|
1157
1302
|
<div class="flex-grow fm-typo-en-title-sm-600 mb-16">Preorder</div>
|
|
1158
|
-
<FmSwitch
|
|
1303
|
+
<FmSwitch
|
|
1304
|
+
label-placement="right"
|
|
1305
|
+
:label="'Enable preorder'"
|
|
1159
1306
|
:sublabel="'Enable this to allow guests to place preorders when making a reservation. This allows them to order food in advance.'"
|
|
1160
1307
|
:model-value="rangeSetting.enablePreorder"
|
|
1161
|
-
@update:model-value="(v: boolean) => (rangeSetting.enablePreorder = v)"
|
|
1308
|
+
@update:model-value="(v: boolean) => (rangeSetting.enablePreorder = v)"
|
|
1309
|
+
/>
|
|
1162
1310
|
</div>
|
|
1163
1311
|
|
|
1164
1312
|
<div class="flex flex-col">
|
|
@@ -1168,8 +1316,12 @@ function handleCopySettings(copiedSettings: Partial<FdoOrderReservationSettingsV
|
|
|
1168
1316
|
allergies, or other notes.
|
|
1169
1317
|
</div>
|
|
1170
1318
|
|
|
1171
|
-
<FmSwitch
|
|
1172
|
-
|
|
1319
|
+
<FmSwitch
|
|
1320
|
+
class="mb-8"
|
|
1321
|
+
label="Enable preferences"
|
|
1322
|
+
label-placement="right"
|
|
1323
|
+
:model-value="rangeSetting.preferences.length > 0"
|
|
1324
|
+
@update:model-value="
|
|
1173
1325
|
(v) => {
|
|
1174
1326
|
if (v == true) {
|
|
1175
1327
|
rangeSetting.preferences = [
|
|
@@ -1183,10 +1335,15 @@ function handleCopySettings(copiedSettings: Partial<FdoOrderReservationSettingsV
|
|
|
1183
1335
|
rangeSetting.preferences = []
|
|
1184
1336
|
}
|
|
1185
1337
|
}
|
|
1186
|
-
"
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1338
|
+
"
|
|
1339
|
+
/>
|
|
1340
|
+
|
|
1341
|
+
<FmCard
|
|
1342
|
+
v-for="(preference, pIndex) in rangeSetting.preferences"
|
|
1343
|
+
class="p-16 mb-12"
|
|
1344
|
+
variant="outlined"
|
|
1345
|
+
:key="pIndex"
|
|
1346
|
+
>
|
|
1190
1347
|
<div class="fm-typo-en-body-lg-600 mb-8">Category title</div>
|
|
1191
1348
|
<div class="grid grid-cols-[6fr_3fr_1fr] items-start gap-24 mb-24">
|
|
1192
1349
|
<div>
|
|
@@ -1195,66 +1352,125 @@ function handleCopySettings(copiedSettings: Partial<FdoOrderReservationSettingsV
|
|
|
1195
1352
|
This title will be shown to guests during reservation.
|
|
1196
1353
|
</div>
|
|
1197
1354
|
</div>
|
|
1198
|
-
<CustomSelect
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1355
|
+
<CustomSelect
|
|
1356
|
+
v-model="preference.type"
|
|
1357
|
+
:items="[
|
|
1358
|
+
{ label: 'Single choice', value: 'radio', icon: 'radio' },
|
|
1359
|
+
{ label: 'Multiple choice', value: 'checkbox', icon: 'checkbox' }
|
|
1360
|
+
]"
|
|
1361
|
+
/>
|
|
1202
1362
|
<FmButton variant="tertiary" icon="delete" @click="removePreference(pIndex)" />
|
|
1203
1363
|
</div>
|
|
1204
1364
|
|
|
1205
1365
|
<div v-if="preference.type == 'checkbox' || preference.type == 'radio'">
|
|
1206
1366
|
<div class="mb-8 fm-typo-en-body-md-600">Options</div>
|
|
1207
|
-
<div
|
|
1208
|
-
|
|
1367
|
+
<div
|
|
1368
|
+
v-for="(option, oIndex) in preference.options"
|
|
1369
|
+
:key="oIndex"
|
|
1370
|
+
class="flex items-center gap-4 gap-y-8 mb-8"
|
|
1371
|
+
>
|
|
1209
1372
|
<div class="flex items-center w-full">
|
|
1210
|
-
<FmCheckbox
|
|
1211
|
-
|
|
1212
|
-
|
|
1373
|
+
<FmCheckbox
|
|
1374
|
+
v-if="preference.type == 'checkbox'"
|
|
1375
|
+
disabled
|
|
1376
|
+
:value="option"
|
|
1377
|
+
:model-value="false"
|
|
1378
|
+
readonly
|
|
1379
|
+
class="mr-8"
|
|
1380
|
+
/>
|
|
1381
|
+
<FmRadio
|
|
1382
|
+
v-if="preference.type == 'radio'"
|
|
1383
|
+
disabled
|
|
1384
|
+
:value="option"
|
|
1385
|
+
:model-value="false"
|
|
1386
|
+
readonly
|
|
1387
|
+
/>
|
|
1213
1388
|
|
|
1214
1389
|
<!-- Editable option label -->
|
|
1215
|
-
<input
|
|
1216
|
-
|
|
1390
|
+
<input
|
|
1391
|
+
v-if="isEditingOption(pIndex, oIndex)"
|
|
1392
|
+
type="text"
|
|
1393
|
+
class="flex-1 outline-none border-b-2 border-fm-color-primary px-4 py-2"
|
|
1394
|
+
:value="option"
|
|
1395
|
+
@blur="
|
|
1217
1396
|
(e) => saveOptionEdit(pIndex, oIndex, (e.target as HTMLInputElement).value)
|
|
1218
|
-
"
|
|
1397
|
+
"
|
|
1398
|
+
@keyup.enter="
|
|
1219
1399
|
(e) => saveOptionEdit(pIndex, oIndex, (e.target as HTMLInputElement).value)
|
|
1220
|
-
"
|
|
1221
|
-
|
|
1400
|
+
"
|
|
1401
|
+
ref="optionInput"
|
|
1402
|
+
/>
|
|
1403
|
+
<div
|
|
1404
|
+
v-else-if="option != 'Other'"
|
|
1222
1405
|
class="flex-1 cursor-pointer hover:bg-fm-color-neutral-gray-100 px-4 py-2 rounded transition-colors"
|
|
1223
|
-
@click="startEditingOption(pIndex, oIndex)"
|
|
1406
|
+
@click="startEditingOption(pIndex, oIndex)"
|
|
1407
|
+
title="Click to edit"
|
|
1408
|
+
>
|
|
1224
1409
|
{{ option }}
|
|
1225
1410
|
</div>
|
|
1226
|
-
<div
|
|
1411
|
+
<div
|
|
1412
|
+
v-else-if="option == 'Other'"
|
|
1413
|
+
class="flex-1 px-4 py-2 border-b border-fm-color-neutral-gray-200"
|
|
1414
|
+
>
|
|
1227
1415
|
Other:
|
|
1228
1416
|
</div>
|
|
1229
1417
|
</div>
|
|
1230
|
-
<FmButton
|
|
1231
|
-
|
|
1418
|
+
<FmButton
|
|
1419
|
+
variant="plain"
|
|
1420
|
+
icon="close"
|
|
1421
|
+
:class="{ 'opacity-0 pointer-events-nonex': oIndex == 0 }"
|
|
1422
|
+
@click="oIndex != 0 && removePreferenceOption(preference, option)"
|
|
1423
|
+
/>
|
|
1232
1424
|
</div>
|
|
1233
1425
|
<div class="flex items-center">
|
|
1234
|
-
<FmCheckbox
|
|
1426
|
+
<FmCheckbox
|
|
1427
|
+
v-if="preference.type == 'checkbox'"
|
|
1428
|
+
disabled
|
|
1429
|
+
:value="null"
|
|
1430
|
+
:model-value="false"
|
|
1431
|
+
readonly
|
|
1432
|
+
/>
|
|
1235
1433
|
<div v-if="preference.type == 'radio'" class="flex items-center">
|
|
1236
1434
|
<FmRadio disabled :value="null" :model-value="false" readonly />
|
|
1237
1435
|
</div>
|
|
1238
1436
|
<div class="flex items-center w-full">
|
|
1239
|
-
<FmButton
|
|
1240
|
-
|
|
1241
|
-
"
|
|
1437
|
+
<FmButton
|
|
1438
|
+
variant="tertiary"
|
|
1439
|
+
class="text-fm-color-typo-secondary"
|
|
1440
|
+
label="add option"
|
|
1441
|
+
@click="
|
|
1442
|
+
addPreferenceOption(preference, `Option ${preference.options.length + 1}`)
|
|
1443
|
+
"
|
|
1444
|
+
>
|
|
1242
1445
|
<template #default>
|
|
1243
1446
|
<div>Add option</div>
|
|
1244
1447
|
</template>
|
|
1245
1448
|
</FmButton>
|
|
1246
1449
|
<template v-if="!preference.options.includes('Other')">
|
|
1247
1450
|
<div>or</div>
|
|
1248
|
-
<FmButton
|
|
1451
|
+
<FmButton
|
|
1452
|
+
variant="plain"
|
|
1453
|
+
label='add "Other"'
|
|
1454
|
+
@click="addPreferenceOption(preference, 'Other')"
|
|
1455
|
+
/>
|
|
1249
1456
|
</template>
|
|
1250
1457
|
</div>
|
|
1251
1458
|
</div>
|
|
1252
1459
|
</div>
|
|
1253
1460
|
</FmCard>
|
|
1254
1461
|
|
|
1255
|
-
<FmCard
|
|
1256
|
-
|
|
1257
|
-
|
|
1462
|
+
<FmCard
|
|
1463
|
+
variant="outlined"
|
|
1464
|
+
class="border-dashed p-16"
|
|
1465
|
+
v-if="rangeSetting.preferences.length"
|
|
1466
|
+
>
|
|
1467
|
+
<FmButton
|
|
1468
|
+
label="Add another preference category"
|
|
1469
|
+
icon="add"
|
|
1470
|
+
variant="plain"
|
|
1471
|
+
class="border-1 border-fm-color-primary rounded-lg"
|
|
1472
|
+
@click="addPreference"
|
|
1473
|
+
/>
|
|
1258
1474
|
</FmCard>
|
|
1259
1475
|
</div>
|
|
1260
1476
|
|
|
@@ -1264,14 +1480,24 @@ function handleCopySettings(copiedSettings: Partial<FdoOrderReservationSettingsV
|
|
|
1264
1480
|
Add important information or notes guests should know before their visit.
|
|
1265
1481
|
</div>
|
|
1266
1482
|
|
|
1267
|
-
<FmSwitch
|
|
1268
|
-
"
|
|
1483
|
+
<FmSwitch
|
|
1484
|
+
class="mb-16"
|
|
1485
|
+
label="Enable guest message"
|
|
1486
|
+
label-placement="right"
|
|
1487
|
+
:model-value="
|
|
1488
|
+
rangeSetting.guestMessage !== null && rangeSetting.guestMessage !== undefined
|
|
1489
|
+
"
|
|
1490
|
+
@update:model-value="toggleGuestMessage"
|
|
1491
|
+
/>
|
|
1269
1492
|
|
|
1270
1493
|
<div v-if="rangeSetting.guestMessage !== null && rangeSetting.guestMessage !== undefined">
|
|
1271
1494
|
<div class="mb-8 fm-typo-en-body-md-600">Message</div>
|
|
1272
|
-
<FmTextarea
|
|
1495
|
+
<FmTextarea
|
|
1496
|
+
v-model="rangeSetting.guestMessage"
|
|
1497
|
+
:maxLength="600"
|
|
1273
1498
|
placeholder="Please take note of the following important details before making a reservation:"
|
|
1274
|
-
class="mb-4"
|
|
1499
|
+
class="mb-4"
|
|
1500
|
+
/>
|
|
1275
1501
|
<div class="text-right text-fm-color-typo-tertiary fm-typo-en-body-sm-400">
|
|
1276
1502
|
{{ rangeSetting.guestMessage?.length || 0 }} / 600 characters
|
|
1277
1503
|
</div>
|
|
@@ -1284,17 +1510,30 @@ function handleCopySettings(copiedSettings: Partial<FdoOrderReservationSettingsV
|
|
|
1284
1510
|
Set the rules guests should follow when cancelling a reservation.
|
|
1285
1511
|
</div>
|
|
1286
1512
|
|
|
1287
|
-
<FmSwitch
|
|
1288
|
-
|
|
1289
|
-
"
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1513
|
+
<FmSwitch
|
|
1514
|
+
class="mb-16"
|
|
1515
|
+
label="Enable cancellation policy"
|
|
1516
|
+
label-placement="right"
|
|
1517
|
+
:model-value="
|
|
1518
|
+
rangeSetting.cancellationPolicy !== null &&
|
|
1519
|
+
rangeSetting.cancellationPolicy !== undefined
|
|
1520
|
+
"
|
|
1521
|
+
@update:model-value="toggleCancellationPolicy"
|
|
1522
|
+
/>
|
|
1523
|
+
|
|
1524
|
+
<div
|
|
1525
|
+
v-if="
|
|
1526
|
+
rangeSetting.cancellationPolicy !== null &&
|
|
1527
|
+
rangeSetting.cancellationPolicy !== undefined
|
|
1528
|
+
"
|
|
1529
|
+
>
|
|
1295
1530
|
<div class="mb-8 fm-typo-en-body-md-600">Message</div>
|
|
1296
|
-
<FmTextarea
|
|
1297
|
-
|
|
1531
|
+
<FmTextarea
|
|
1532
|
+
v-model="rangeSetting.cancellationPolicy"
|
|
1533
|
+
:maxLength="200"
|
|
1534
|
+
placeholder="Cancellation Policy"
|
|
1535
|
+
class="mb-4"
|
|
1536
|
+
/>
|
|
1298
1537
|
<div class="text-right text-fm-color-typo-tertiary fm-typo-en-body-sm-400">
|
|
1299
1538
|
{{ rangeSetting.cancellationPolicy?.length || 0 }} / 200 characters
|
|
1300
1539
|
</div>
|
|
@@ -1304,8 +1543,13 @@ function handleCopySettings(copiedSettings: Partial<FdoOrderReservationSettingsV
|
|
|
1304
1543
|
|
|
1305
1544
|
<!-- Save Button -->
|
|
1306
1545
|
<div class="flex mt-5">
|
|
1307
|
-
<FmButton
|
|
1308
|
-
|
|
1546
|
+
<FmButton
|
|
1547
|
+
variant="primary"
|
|
1548
|
+
:label="t('order.saveAllChanges')"
|
|
1549
|
+
class="mr-auto"
|
|
1550
|
+
@click="updateReservationSetting"
|
|
1551
|
+
:loading="isSaving"
|
|
1552
|
+
/>
|
|
1309
1553
|
</div>
|
|
1310
1554
|
</div>
|
|
1311
1555
|
</template>
|