@feedmepos/mf-order-setting 0.0.50 → 0.0.52-dev.0
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 -0
- package/dist/{KioskDevicesView-u14hzPbE.js → KioskDevicesView-Vy9FLX1n.js} +1 -1
- package/dist/{KioskDevicesView.vue_vue_type_script_setup_true_lang-DBgRDIoS.js → KioskDevicesView.vue_vue_type_script_setup_true_lang-DhZPOEEQ.js} +2 -2
- package/dist/{KioskSettingView-DmvtZcV1.js → KioskSettingView-cE-JdCBB.js} +206 -208
- package/dist/{KioskView-M8V91gD5.js → KioskView-BYs5bem0.js} +4 -4
- package/dist/OrderSettingsView-C4aEpC1j.js +56063 -0
- package/dist/{app-CLewMjcd.js → app-CwYXsqxX.js} +184 -20
- package/dist/app.js +1 -1
- package/dist/{dayjs.min-DCTYRWyD.js → dayjs.min-JEYIJz2D.js} +1 -1
- package/dist/frontend/mf-order/src/api/reservation/index.d.ts +8 -0
- package/dist/frontend/mf-order/src/app.d.ts +164 -0
- package/dist/frontend/mf-order/src/main.d.ts +164 -0
- package/dist/frontend/mf-order/src/stores/restaurant/index.d.ts +3 -3
- package/dist/frontend/mf-order/src/views/all-orders/ReflowOrder.vue.d.ts +2 -2
- package/dist/frontend/mf-order/src/views/order-settings/delivery/inhouse/InHouseDelivery.vue.d.ts +2 -2
- package/dist/frontend/mf-order/src/views/order-settings/reservation/CopySettingsSheet.vue.d.ts +186 -0
- package/dist/frontend/mf-order/src/views/order-settings/reservation/CustomSelect.vue.d.ts +15 -0
- package/dist/frontend/mf-order/src/views/order-settings/reservation/CustomTimePicker.vue.d.ts +10 -0
- package/dist/frontend/mf-order/src/views/order-settings/reservation/ReservationSetting.vue.d.ts +2 -0
- package/dist/{index-B7LtJeBJ.js → index-DZCjODMx.js} +2 -2
- package/dist/{menu.dto-Co7iXHNr.js → menu.dto-D9CDVLiP.js} +22865 -20028
- package/dist/package/entity/food-court/order.do.d.ts +47 -2
- package/dist/package/entity/food-court/order.dto.d.ts +0 -3
- package/dist/package/entity/incoming-order/incoming-order-to-bill.dto.d.ts +12356 -1
- package/dist/package/entity/incoming-order/incoming-order.do.d.ts +3 -22266
- package/dist/package/entity/incoming-order/incoming-order.dto.d.ts +18 -0
- package/dist/package/entity/index.d.ts +5 -0
- package/dist/package/entity/kiosk/marketing/marketing.dto.d.ts +1 -19864
- package/dist/package/entity/order/order-item/order-item.dto.d.ts +24 -3714
- package/dist/package/entity/order/order.do.d.ts +8 -0
- package/dist/package/entity/order/order.dto.d.ts +118 -0
- package/dist/package/entity/order-platform/external/menu/external-master-menu.do.d.ts +20 -0
- package/dist/package/entity/order-platform/external/menu/external-menu.do.d.ts +23 -0
- package/dist/package/entity/order-platform/menu.dto.d.ts +34 -0
- package/dist/package/entity/order-setting/order-setting.do.d.ts +861 -0
- package/dist/package/entity/order-setting/reservationV2/reservation.do.d.ts +1269 -0
- package/dist/package/entity/queue/queue.do.d.ts +1 -11
- package/dist/package/entity/queue/queue.dto.d.ts +25 -0
- package/dist/package/entity/reservation/reservation.do.d.ts +101 -0
- package/dist/package/entity/reservation/reservation.dto.d.ts +325 -0
- package/dist/package/entity/reservation/reservation.enum.d.ts +3 -0
- package/dist/package/entity/reservation/reservation.utils.d.ts +152 -0
- package/dist/style.css +1 -0
- package/package.json +3 -3
- package/src/api/reservation/index.ts +28 -0
- package/src/assets/images/not-found.png +0 -0
- package/src/locales/en-US.json +56 -0
- package/src/locales/th-TH.json +54 -0
- package/src/locales/zh-CN.json +54 -0
- package/src/main.ts +7 -5
- package/src/stores/order-setting/mapper.ts +50 -50
- package/src/views/kiosk/settings/KioskPaymentTypeSection.vue +1 -19
- package/src/views/order-settings/OrderSettingsView.vue +7 -2
- package/src/views/order-settings/delivery/integrated-delivery/IntegratedDelivery.vue +3 -1
- package/src/views/order-settings/drive-thru/DriveThruSetting.vue +13 -28
- package/src/views/order-settings/reservation/CopySettingsSheet.vue +238 -0
- package/src/views/order-settings/reservation/CustomSelect.vue +99 -0
- package/src/views/order-settings/reservation/CustomTimePicker.vue +201 -0
- package/src/views/order-settings/reservation/ReservationSetting.vue +1246 -0
- package/src/views/order-settings/servicecharge/ServiceChargeRule.vue +5 -1
- package/tsconfig.app.json +8 -6
- package/dist/OrderSettingsView-Bl3LshG3.js +0 -51603
- package/dist/frontend/mf-order/tsconfig.app.tsbuildinfo +0 -1
package/src/locales/zh-CN.json
CHANGED
|
@@ -232,6 +232,60 @@
|
|
|
232
232
|
"current": "当前",
|
|
233
233
|
"paxMin": "最少人数",
|
|
234
234
|
"paxMax": "最多人数",
|
|
235
|
+
"reservation": "预订",
|
|
236
|
+
"reservationSettings": "预订设置",
|
|
237
|
+
"reservationRanges": "预订范围",
|
|
238
|
+
"addRange": "添加范围",
|
|
239
|
+
"range": "范围",
|
|
240
|
+
"rangeName": "范围名称",
|
|
241
|
+
"priority": "优先级",
|
|
242
|
+
"priorityDescription": "数字越大,匹配时段时优先级越高",
|
|
243
|
+
"capacity": "容量",
|
|
244
|
+
"capacityDescription": "此范围的最大并发预订数",
|
|
245
|
+
"minPax": "最少人数",
|
|
246
|
+
"maxPax": "最多人数",
|
|
247
|
+
"slotInterval": "时段间隔",
|
|
248
|
+
"slotIntervalDescription": "每个可预订时段之间的时间(例如30分钟)",
|
|
249
|
+
"bookingDuration": "预订时长",
|
|
250
|
+
"bookingDurationDescription": "每次预订的持续时间(例如60分钟)",
|
|
251
|
+
"minLeadDays": "最少提前天数",
|
|
252
|
+
"minLeadDaysDescription": "顾客必须提前预订的最少天数(0 = 当天)",
|
|
253
|
+
"maxLeadDays": "最多提前天数",
|
|
254
|
+
"maxLeadDaysDescription": "顾客可以提前预订的最多天数",
|
|
255
|
+
"operatingWindows": "营业时段",
|
|
256
|
+
"addWindow": "添加时段",
|
|
257
|
+
"startTime": "开始时间",
|
|
258
|
+
"endTime": "结束时间",
|
|
259
|
+
"customPresetRemarks": "自定义预设备注",
|
|
260
|
+
"addRemark": "添加备注",
|
|
261
|
+
"noRangesConfigured": "未配置预订范围。点击\"添加范围\"开始",
|
|
262
|
+
"addFirstRange": "添加您的第一个预订范围以开始",
|
|
263
|
+
"globalSettings": "全局设置",
|
|
264
|
+
"paxRange": "人数范围",
|
|
265
|
+
"noOperatingHours": "无营业时间",
|
|
266
|
+
"saveAllChanges": "保存所有更改",
|
|
267
|
+
"confirmDeleteRange": "您确定要删除{name}吗?",
|
|
268
|
+
"settingUpdated": "设置已成功更新",
|
|
269
|
+
"newReservationRange": "新预订范围",
|
|
270
|
+
"basicInformation": "基本信息",
|
|
271
|
+
"guestRequirements": "客人要求",
|
|
272
|
+
"bookingConfiguration": "预订配置",
|
|
273
|
+
"rangeNamePlaceholder": "例如:VIP包厢、大厅",
|
|
274
|
+
"presetRemarkPlaceholder": "例如:靠窗座位、安静区域",
|
|
275
|
+
"saveRange": "保存范围",
|
|
276
|
+
"draftHoldTimeMinutes": "草稿保留时间(分钟)",
|
|
277
|
+
"draftHoldTimeDescription": "在草稿预订过期前保留的时间",
|
|
278
|
+
"posCanOverbook": "POS可以超额预订",
|
|
279
|
+
"posCanOverbookDescription": "允许POS在容量已满时创建预订",
|
|
280
|
+
"notificationSettings": "通知设置",
|
|
281
|
+
"smsEnabled": "启用短信通知",
|
|
282
|
+
"smsEnabledDescription": "向客户发送预订更新的短信通知",
|
|
283
|
+
"emailEnabled": "启用电子邮件通知",
|
|
284
|
+
"emailEnabledDescription": "向客户发送预订更新的电子邮件通知",
|
|
285
|
+
"notifyOnConfirm": "确认时通知",
|
|
286
|
+
"notifyOnConfirmDescription": "当预订被确认时发送通知",
|
|
287
|
+
"notifyOnCancel": "取消时通知",
|
|
288
|
+
"notifyOnCancelDescription": "当预订被取消时发送通知",
|
|
235
289
|
"qrPay": "二维码支付",
|
|
236
290
|
"terminalScanPay": "终端扫描支付",
|
|
237
291
|
"terminalQrPay": "终端二维码支付",
|
package/src/main.ts
CHANGED
|
@@ -16,6 +16,8 @@ import zhCN from "./locales/zh-CN.json"
|
|
|
16
16
|
import thTH from "./locales/th-TH.json"
|
|
17
17
|
import jaJP from "./locales/ja-JP.json"
|
|
18
18
|
|
|
19
|
+
import { setEnabledLocales } from "@feedmepos/mf-common"
|
|
20
|
+
|
|
19
21
|
export const i18nMessages = {
|
|
20
22
|
"en-US": enUS,
|
|
21
23
|
"zh-CN": zhCN,
|
|
@@ -34,15 +36,15 @@ function initI18n(): I18n {
|
|
|
34
36
|
|
|
35
37
|
const app = createApp(Entry)
|
|
36
38
|
|
|
39
|
+
app.use(initI18n())
|
|
40
|
+
app.use(createPinia())
|
|
41
|
+
app.use(router)
|
|
42
|
+
|
|
43
|
+
|
|
37
44
|
app.use(FeedMeUI, {
|
|
38
45
|
locale: detectLocale(),
|
|
39
46
|
})
|
|
40
47
|
|
|
41
|
-
app.use(createPinia() as any)
|
|
42
|
-
app.use(router)
|
|
43
|
-
app.use(initI18n() as any)
|
|
44
|
-
|
|
45
|
-
|
|
46
48
|
const CoreStore = useCoreStore();
|
|
47
49
|
const iframeMode = computed(() => {
|
|
48
50
|
return new URLSearchParams(window.location.search).get('iframe') === 'true';
|
|
@@ -107,10 +107,10 @@ const toKiosk = (
|
|
|
107
107
|
enabled: kioskOrderSettings?.canTakeaway ?? false,
|
|
108
108
|
submitOrderInstruction: kioskOrderSettings?.takeaway?.submitOrderInstruction
|
|
109
109
|
? {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
110
|
+
payAtCounter:
|
|
111
|
+
kioskOrderSettings?.takeaway?.submitOrderInstruction?.payAtCounter?.['en'] ?? null,
|
|
112
|
+
paid: kioskOrderSettings?.takeaway?.submitOrderInstruction?.paid?.['en'] ?? null
|
|
113
|
+
}
|
|
114
114
|
: defaultSubmitOrderInstruction
|
|
115
115
|
},
|
|
116
116
|
paymentSetting: {
|
|
@@ -144,35 +144,35 @@ const toKioskDineInSetting = (
|
|
|
144
144
|
sequenceNumber: kioskDineInSeq,
|
|
145
145
|
pickUp: kioskDineInSetting?.pickUp
|
|
146
146
|
? {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
147
|
+
...kioskDineInSetting.pickUp,
|
|
148
|
+
submitOrderInstruction: kioskDineInSetting.pickUp.submitOrderInstruction
|
|
149
|
+
? {
|
|
150
|
+
payAtCounter:
|
|
151
|
+
kioskDineInSetting.pickUp.submitOrderInstruction?.payAtCounter?.['en'] ?? null,
|
|
152
|
+
paid: kioskDineInSetting.pickUp.submitOrderInstruction?.paid?.['en'] ?? null
|
|
153
|
+
}
|
|
154
|
+
: defaultSubmitOrderInstruction,
|
|
155
|
+
enablePaxDialog: kioskDineInSetting.pickUp.enablePaxDialog
|
|
156
|
+
? kioskDineInSetting.pickUp.enablePaxDialog
|
|
157
|
+
: false
|
|
158
|
+
}
|
|
159
159
|
: defaultPickUp,
|
|
160
160
|
displayStand: kioskDineInSetting?.displayStand
|
|
161
161
|
? {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
162
|
+
enabled: kioskDineInSetting.displayStand.enabled,
|
|
163
|
+
standSlotRange: kioskDineInSetting.displayStand.standSlotRange,
|
|
164
|
+
prefix: kioskDineInSetting.displayStand.prefix,
|
|
165
|
+
padDigit: kioskDineInSetting.displayStand.padDigit,
|
|
166
|
+
enablePaxDialog: kioskDineInSetting.displayStand.enablePaxDialog ?? false,
|
|
167
|
+
submitOrderInstruction: kioskDineInSetting.displayStand.submitOrderInstruction
|
|
168
|
+
? {
|
|
169
|
+
payAtCounter:
|
|
170
|
+
kioskDineInSetting.displayStand.submitOrderInstruction?.payAtCounter?.['en'] ??
|
|
171
|
+
null,
|
|
172
|
+
paid: kioskDineInSetting.displayStand.submitOrderInstruction?.paid?.['en'] ?? null
|
|
173
|
+
}
|
|
174
|
+
: defaultSubmitOrderInstruction
|
|
175
|
+
}
|
|
176
176
|
: defaultDisplayStand
|
|
177
177
|
}
|
|
178
178
|
}
|
|
@@ -189,13 +189,13 @@ const toOrderKioskSettingsDto = (
|
|
|
189
189
|
submitOrderInstruction: {
|
|
190
190
|
payAtCounter: dineInSetting.displayStand.submitOrderInstruction?.payAtCounter
|
|
191
191
|
? {
|
|
192
|
-
|
|
193
|
-
|
|
192
|
+
en: dineInSetting.displayStand.submitOrderInstruction.payAtCounter
|
|
193
|
+
}
|
|
194
194
|
: null,
|
|
195
195
|
paid: dineInSetting.displayStand.submitOrderInstruction?.paid
|
|
196
196
|
? {
|
|
197
|
-
|
|
198
|
-
|
|
197
|
+
en: dineInSetting.displayStand.submitOrderInstruction.paid
|
|
198
|
+
}
|
|
199
199
|
: null
|
|
200
200
|
}
|
|
201
201
|
},
|
|
@@ -204,13 +204,13 @@ const toOrderKioskSettingsDto = (
|
|
|
204
204
|
submitOrderInstruction: {
|
|
205
205
|
payAtCounter: dineInSetting.pickUp.submitOrderInstruction?.payAtCounter
|
|
206
206
|
? {
|
|
207
|
-
|
|
208
|
-
|
|
207
|
+
en: dineInSetting.pickUp.submitOrderInstruction.payAtCounter
|
|
208
|
+
}
|
|
209
209
|
: null,
|
|
210
210
|
paid: dineInSetting.pickUp.submitOrderInstruction?.paid
|
|
211
211
|
? {
|
|
212
|
-
|
|
213
|
-
|
|
212
|
+
en: dineInSetting.pickUp.submitOrderInstruction.paid
|
|
213
|
+
}
|
|
214
214
|
: null
|
|
215
215
|
}
|
|
216
216
|
}
|
|
@@ -223,19 +223,19 @@ const toOrderKioskSettingsDto = (
|
|
|
223
223
|
paymentSetting: createPaymentSettings(kioskSetting.paymentSetting),
|
|
224
224
|
takeaway: kioskSetting.takeaway.submitOrderInstruction
|
|
225
225
|
? {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
226
|
+
submitOrderInstruction: {
|
|
227
|
+
payAtCounter: kioskSetting.takeaway.submitOrderInstruction.payAtCounter
|
|
228
|
+
? {
|
|
229
|
+
en: kioskSetting.takeaway.submitOrderInstruction.payAtCounter
|
|
230
|
+
}
|
|
231
|
+
: null,
|
|
232
|
+
paid: kioskSetting.takeaway.submitOrderInstruction.paid
|
|
233
|
+
? {
|
|
234
|
+
en: kioskSetting.takeaway.submitOrderInstruction.paid
|
|
235
|
+
}
|
|
236
|
+
: null
|
|
237
|
+
}
|
|
237
238
|
}
|
|
238
|
-
}
|
|
239
239
|
: undefined
|
|
240
240
|
})
|
|
241
241
|
|
|
@@ -92,16 +92,6 @@ const initializeCheckboxes = () => {
|
|
|
92
92
|
eWalletPaymentCheckboxVal.value.push(F_ORDER_E_PAYMENT_TYPE.enum.SCANPAY)
|
|
93
93
|
useEwallet.value = true
|
|
94
94
|
}
|
|
95
|
-
|
|
96
|
-
if (props.ePaymentTypes.eWallet.terminalScanPay) {
|
|
97
|
-
eWalletPaymentCheckboxVal.value.push(F_ORDER_E_PAYMENT_TYPE.enum.TERMINAL_SCAN_PAY)
|
|
98
|
-
useEwallet.value = true
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (props.ePaymentTypes.eWallet.terminalQrPay) {
|
|
102
|
-
eWalletPaymentCheckboxVal.value.push(F_ORDER_E_PAYMENT_TYPE.enum.TERMINAL_QR_PAY)
|
|
103
|
-
useEwallet.value = true
|
|
104
|
-
}
|
|
105
95
|
}
|
|
106
96
|
}
|
|
107
97
|
|
|
@@ -164,9 +154,7 @@ const handleEPaymentCardUpdate = (ev: string) => {
|
|
|
164
154
|
},
|
|
165
155
|
eWallet: {
|
|
166
156
|
qrPay: false,
|
|
167
|
-
scanPay: false
|
|
168
|
-
terminalScanPay: false,
|
|
169
|
-
terminalQrPay: false,
|
|
157
|
+
scanPay: false
|
|
170
158
|
}
|
|
171
159
|
} as FdoEPaymentMethod
|
|
172
160
|
|
|
@@ -187,12 +175,6 @@ const handleEPaymentCardUpdate = (ev: string) => {
|
|
|
187
175
|
if (eWalletPaymentCheckboxVal.value.includes(F_ORDER_E_PAYMENT_TYPE.enum.SCANPAY)) {
|
|
188
176
|
ePaymentObj.eWallet.scanPay = true
|
|
189
177
|
}
|
|
190
|
-
if (eWalletPaymentCheckboxVal.value.includes(F_ORDER_E_PAYMENT_TYPE.enum.TERMINAL_SCAN_PAY)) {
|
|
191
|
-
ePaymentObj.eWallet.terminalScanPay = true
|
|
192
|
-
}
|
|
193
|
-
if (eWalletPaymentCheckboxVal.value.includes(F_ORDER_E_PAYMENT_TYPE.enum.TERMINAL_QR_PAY)) {
|
|
194
|
-
ePaymentObj.eWallet.terminalQrPay = true
|
|
195
|
-
}
|
|
196
178
|
}
|
|
197
179
|
}
|
|
198
180
|
|
|
@@ -27,6 +27,7 @@ import SmsSetting from './sms/SmsSetting.vue'
|
|
|
27
27
|
|
|
28
28
|
import GeneralSetting from './general/GeneralSetting.vue'
|
|
29
29
|
import QueueSetting from './queue/QueueSetting.vue'
|
|
30
|
+
import ReservationSetting from './reservation/ReservationSetting.vue'
|
|
30
31
|
import { useI18n, useCoreStore } from '@feedmepos/mf-common'
|
|
31
32
|
import { useMenuStore } from '@/stores/menu/menu'
|
|
32
33
|
import { useSnackbarFunctions } from '@/components/snackbar'
|
|
@@ -45,6 +46,7 @@ type OrderSettingMenuItemValue =
|
|
|
45
46
|
// | 'discountRule'
|
|
46
47
|
| 'general'
|
|
47
48
|
| 'queue'
|
|
49
|
+
| 'reservation'
|
|
48
50
|
const selectedOrderSetting = ref<OrderSettingMenuItemValue>('delivery')
|
|
49
51
|
|
|
50
52
|
const settingItem = computed<FmTabProps[]>(() => [
|
|
@@ -56,7 +58,8 @@ const settingItem = computed<FmTabProps[]>(() => [
|
|
|
56
58
|
// { label: t('order.discountRule.title'), value: 'discountRule' },
|
|
57
59
|
{ label: t('order.sms'), value: 'sms' },
|
|
58
60
|
{ label: t('order.general'), value: 'general' },
|
|
59
|
-
{ label: t('order.queue'), value: 'queue' }
|
|
61
|
+
{ label: t('order.queue'), value: 'queue' },
|
|
62
|
+
{ label: t('order.reservation'), value: 'reservation' }
|
|
60
63
|
])
|
|
61
64
|
|
|
62
65
|
const currentComponent = computed(() => {
|
|
@@ -79,6 +82,8 @@ const currentComponent = computed(() => {
|
|
|
79
82
|
return GeneralSetting
|
|
80
83
|
case 'queue':
|
|
81
84
|
return QueueSetting
|
|
85
|
+
case 'reservation':
|
|
86
|
+
return ReservationSetting
|
|
82
87
|
}
|
|
83
88
|
})
|
|
84
89
|
|
|
@@ -88,7 +93,7 @@ onMounted(async () => {
|
|
|
88
93
|
}
|
|
89
94
|
})
|
|
90
95
|
|
|
91
|
-
watch(currentRestaurant, async (newRestaurant
|
|
96
|
+
watch(currentRestaurant, async (newRestaurant) => {
|
|
92
97
|
if (newRestaurant?._id) {
|
|
93
98
|
await menuStore.loadCatalogOptions(newRestaurant._id)
|
|
94
99
|
}
|
|
@@ -56,7 +56,9 @@ const emits = defineEmits<{
|
|
|
56
56
|
const menuStore = useMenuStore()
|
|
57
57
|
const CoreStore = useCoreStore()
|
|
58
58
|
|
|
59
|
-
const canEdit = computed(() => CoreStore.sessionUser.value?.role.isAdmin ?? false)
|
|
59
|
+
// const canEdit = computed(() => CoreStore.sessionUser.value?.role.isAdmin ?? false)
|
|
60
|
+
// For now, allow all users to edit, but added detail logging in backend
|
|
61
|
+
const canEdit = computed(() => true)
|
|
60
62
|
|
|
61
63
|
function updateManual(manual: FdoLinkedDelivery | null) {
|
|
62
64
|
emits('update:model-value', {
|
|
@@ -5,37 +5,21 @@
|
|
|
5
5
|
<div v-if="!isLoading">
|
|
6
6
|
<div class="p-[1.5rem] flex flex-col gap-5 m-5 w-2/3">
|
|
7
7
|
<div class="flex-grow fm-typo-en-title-sm-600">{{ t('order.generalSetting') }}</div>
|
|
8
|
-
<FmSwitch
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
:label="t('order.
|
|
12
|
-
:
|
|
13
|
-
label-placement="right"
|
|
14
|
-
/>
|
|
15
|
-
<FmSelect
|
|
16
|
-
v-if="driveThruSetting.enabled"
|
|
17
|
-
class="w-1/2"
|
|
18
|
-
:placeholder="t('order.selectItem')"
|
|
19
|
-
:model-value="driveThruSetting.catalogId"
|
|
20
|
-
:label="t('order.catalog')"
|
|
21
|
-
:items="menuStore.catalogOptions"
|
|
22
|
-
@update:model-value="updateCatalogId"
|
|
23
|
-
/>
|
|
8
|
+
<FmSwitch v-model="driveThruSetting.enabled" value="enabled" :label="t('order.enableDriveThru')"
|
|
9
|
+
:sublabel="t('order.enableDriveThruSublabel')" label-placement="right" />
|
|
10
|
+
<FmSelect v-if="driveThruSetting.enabled" class="w-1/2" :placeholder="t('order.selectItem')"
|
|
11
|
+
:model-value="driveThruSetting.catalogId" :label="t('order.catalog')" :items="menuStore.catalogOptions"
|
|
12
|
+
@update:model-value="updateCatalogId" />
|
|
24
13
|
</div>
|
|
25
14
|
<div class="fm-corner-radius-lg flex flex-col gap-5 m-5">
|
|
26
|
-
<FmButton
|
|
27
|
-
variant="primary"
|
|
28
|
-
:label="t('order.saveSetting')"
|
|
29
|
-
class="mr-auto"
|
|
30
|
-
@click="updateDriveThruSetting"
|
|
31
|
-
/>
|
|
15
|
+
<FmButton variant="primary" :label="t('order.saveSetting')" class="mr-auto" @click="updateDriveThruSetting" />
|
|
32
16
|
</div>
|
|
33
17
|
</div>
|
|
34
18
|
<FmCircularProgress size="xxl" v-else />
|
|
35
19
|
</template>
|
|
36
20
|
|
|
37
21
|
<script setup lang="ts">
|
|
38
|
-
import { type FdoRestaurantDriveThru } from '@feedmepos/core/entity'
|
|
22
|
+
import { FdoRestaurant, type FdoRestaurantDriveThru } from '@feedmepos/core/entity'
|
|
39
23
|
import RestaurantSelector from '../components/RestaurantSelector.vue'
|
|
40
24
|
import { onMounted, ref, watch } from 'vue'
|
|
41
25
|
import { useRestaurantStore } from '@/stores/restaurant'
|
|
@@ -73,7 +57,7 @@ async function updateDriveThruSetting() {
|
|
|
73
57
|
if (newDriveThruSetting) {
|
|
74
58
|
await restaurantStore.updateDriveThruSetting(newDriveThruSetting)
|
|
75
59
|
if (currentRestaurant.value) {
|
|
76
|
-
|
|
60
|
+
(currentRestaurant.value as unknown as FdoRestaurant).driveThru = newDriveThruSetting
|
|
77
61
|
}
|
|
78
62
|
}
|
|
79
63
|
})
|
|
@@ -82,9 +66,10 @@ async function updateDriveThruSetting() {
|
|
|
82
66
|
|
|
83
67
|
watch(
|
|
84
68
|
() => currentRestaurant.value,
|
|
85
|
-
async (
|
|
86
|
-
|
|
87
|
-
|
|
69
|
+
async (_newRestaurant) => {
|
|
70
|
+
const newRestaurant = _newRestaurant as unknown as FdoRestaurant;
|
|
71
|
+
if (newRestaurant?.driveThru) {
|
|
72
|
+
driveThruSetting.value = utils.clone(newRestaurant.driveThru)
|
|
88
73
|
} else {
|
|
89
74
|
driveThruSetting.value = initDriveThruSetting()
|
|
90
75
|
}
|
|
@@ -93,7 +78,7 @@ watch(
|
|
|
93
78
|
)
|
|
94
79
|
|
|
95
80
|
onMounted(async () => {
|
|
96
|
-
const newRestaurant = currentRestaurant.value as
|
|
81
|
+
const newRestaurant = currentRestaurant.value as unknown as FdoRestaurant;
|
|
97
82
|
if (newRestaurant?.driveThru) {
|
|
98
83
|
driveThruSetting.value = utils.clone(newRestaurant.driveThru)
|
|
99
84
|
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<FmSideSheet :max-width="560" v-model="showSheet">
|
|
3
|
+
<template #side-sheet-button>
|
|
4
|
+
<FmButton label="Copy settings from another restaurant" icon="edit" variant="plain"
|
|
5
|
+
class="border-1 rounded-lg border-fm-color-primary" />
|
|
6
|
+
</template>
|
|
7
|
+
|
|
8
|
+
<template #side-sheet-header> Copy settings from </template>
|
|
9
|
+
|
|
10
|
+
<template #default>
|
|
11
|
+
<div class="flex flex-col gap-24">
|
|
12
|
+
<!-- Restaurant Selector -->
|
|
13
|
+
<div>
|
|
14
|
+
<FmSelect :items="availableRestaurants" :model-value="selectedRestaurantId"
|
|
15
|
+
@update:model-value="(v: string) => (selectedRestaurantId = v)" placeholder="Select" :width="512" />
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<!-- Configuration Section -->
|
|
19
|
+
<div>
|
|
20
|
+
<div class="fm-typo-en-title-sm-600 mb-16">Configuration</div>
|
|
21
|
+
|
|
22
|
+
<div class="flex flex-col gap-12">
|
|
23
|
+
<!-- All Checkbox -->
|
|
24
|
+
<FmCheckbox label="All" :model-value="isAllSelected" value="all" @update:model-value="toggleAll" />
|
|
25
|
+
|
|
26
|
+
<!-- Individual Configuration Checkboxes -->
|
|
27
|
+
<FmCheckbox label="Reservation Availability" :model-value="configOptions.reservationAvailability"
|
|
28
|
+
value="reservationAvailability"
|
|
29
|
+
@update:model-value="(v: boolean) => updateConfigOption('reservationAvailability', v)" />
|
|
30
|
+
|
|
31
|
+
<FmCheckbox label="Time Settings" :model-value="configOptions.timeSettings" value="timeSettings"
|
|
32
|
+
@update:model-value="(v: boolean) => updateConfigOption('timeSettings', v)" />
|
|
33
|
+
|
|
34
|
+
<FmCheckbox label="Table Capacity Settings" :model-value="configOptions.tableCapacity" value="tableCapacity"
|
|
35
|
+
@update:model-value="(v: boolean) => updateConfigOption('tableCapacity', v)" />
|
|
36
|
+
|
|
37
|
+
<FmCheckbox label="Pre-order" :model-value="configOptions.preorder" value="preorder"
|
|
38
|
+
@update:model-value="(v: boolean) => updateConfigOption('preorder', v)" />
|
|
39
|
+
|
|
40
|
+
<FmCheckbox label="Guest Preferences" :model-value="configOptions.guestPreferences" value="guestPreferences"
|
|
41
|
+
@update:model-value="(v: boolean) => updateConfigOption('guestPreferences', v)" />
|
|
42
|
+
|
|
43
|
+
<FmCheckbox label="Guest Message" :model-value="configOptions.guestMessage" value="guestMessage"
|
|
44
|
+
@update:model-value="(v: boolean) => updateConfigOption('guestMessage', v)" />
|
|
45
|
+
|
|
46
|
+
<FmCheckbox label="Cancellation Policy" :model-value="configOptions.cancellationPolicy"
|
|
47
|
+
value="cancellationPolicy"
|
|
48
|
+
@update:model-value="(v: boolean) => updateConfigOption('cancellationPolicy', v)" />
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
</template>
|
|
53
|
+
|
|
54
|
+
<template #side-sheet-footer>
|
|
55
|
+
<div class="flex items-center justify-start gap-8">
|
|
56
|
+
<FmButton label="Apply" @click="handleApply" :disabled="!canApply" :loading="isLoading" />
|
|
57
|
+
<FmButton label="Cancel" variant="tertiary" @click="handleCancel" />
|
|
58
|
+
</div>
|
|
59
|
+
</template>
|
|
60
|
+
</FmSideSheet>
|
|
61
|
+
</template>
|
|
62
|
+
|
|
63
|
+
<script setup lang="ts">
|
|
64
|
+
import { useCoreStore, useI18n } from '@feedmepos/mf-common'
|
|
65
|
+
import { computed, ref } from 'vue'
|
|
66
|
+
import { ReservationApi } from '@/api/reservation'
|
|
67
|
+
import { useSnackbarFunctions } from '@/components/snackbar'
|
|
68
|
+
import type { FdoOrderReservationSettingsV2 } from '@entity'
|
|
69
|
+
|
|
70
|
+
const { t } = useI18n()
|
|
71
|
+
const { showSuccess, showError } = useSnackbarFunctions()
|
|
72
|
+
const Core = useCoreStore()
|
|
73
|
+
|
|
74
|
+
// Props
|
|
75
|
+
const props = defineProps<{
|
|
76
|
+
currentSettings: FdoOrderReservationSettingsV2
|
|
77
|
+
}>()
|
|
78
|
+
|
|
79
|
+
// Emits
|
|
80
|
+
const emit = defineEmits<{
|
|
81
|
+
apply: [settings: Partial<FdoOrderReservationSettingsV2>]
|
|
82
|
+
}>()
|
|
83
|
+
|
|
84
|
+
// State
|
|
85
|
+
const showSheet = ref(false)
|
|
86
|
+
const isLoading = ref(false)
|
|
87
|
+
|
|
88
|
+
const configOptions = ref({
|
|
89
|
+
reservationAvailability: false,
|
|
90
|
+
timeSettings: false,
|
|
91
|
+
tableCapacity: false,
|
|
92
|
+
preorder: false,
|
|
93
|
+
guestPreferences: false,
|
|
94
|
+
guestMessage: false,
|
|
95
|
+
cancellationPolicy: false
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
// Computed
|
|
99
|
+
const { currentRestaurant } = Core
|
|
100
|
+
|
|
101
|
+
const availableRestaurants = computed(() => {
|
|
102
|
+
// Filter out current restaurant from the list
|
|
103
|
+
return (Core.restaurants.value ?? [])
|
|
104
|
+
.filter((r) => r._id !== currentRestaurant.value?._id)
|
|
105
|
+
.map((r) => ({ label: r.profile.name, value: r._id }))
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
const selectedRestaurantId = ref<string>(availableRestaurants.value[0]?.value)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
const isAllSelected = computed(() => {
|
|
112
|
+
return Object.values(configOptions.value).every((v) => v === true)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
const canApply = computed(() => {
|
|
116
|
+
return (
|
|
117
|
+
selectedRestaurantId.value !== '' && Object.values(configOptions.value).some((v) => v === true)
|
|
118
|
+
)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
// Methods
|
|
122
|
+
function toggleAll(value: boolean) {
|
|
123
|
+
Object.keys(configOptions.value).forEach((key) => {
|
|
124
|
+
configOptions.value[key as keyof typeof configOptions.value] = value
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function updateConfigOption(key: keyof typeof configOptions.value, value: boolean) {
|
|
129
|
+
configOptions.value[key] = value
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function handleApply() {
|
|
133
|
+
if (!selectedRestaurantId.value) {
|
|
134
|
+
showError('Please select a restaurant')
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (!Object.values(configOptions.value).some((v) => v)) {
|
|
139
|
+
showError('Please select at least one configuration option')
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
isLoading.value = true
|
|
145
|
+
|
|
146
|
+
// Fetch settings from selected restaurant
|
|
147
|
+
const sourceSettings = await ReservationApi.getReservationSetting(selectedRestaurantId.value)
|
|
148
|
+
|
|
149
|
+
if (!sourceSettings || !sourceSettings.ranges || sourceSettings.ranges.length === 0) {
|
|
150
|
+
showError('No settings found for the selected restaurant')
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Build partial settings object based on selected options
|
|
155
|
+
const updatedSettings: Partial<FdoOrderReservationSettingsV2> = {}
|
|
156
|
+
const sourceRange = sourceSettings.ranges[0]
|
|
157
|
+
const currentRange = props.currentSettings.ranges[0]
|
|
158
|
+
|
|
159
|
+
// Create a copy of the current range to modify
|
|
160
|
+
const updatedRange = { ...currentRange }
|
|
161
|
+
|
|
162
|
+
// Copy selected configuration sections
|
|
163
|
+
if (configOptions.value.reservationAvailability) {
|
|
164
|
+
// Copy enable state, min/max lead duration
|
|
165
|
+
updatedRange.enable = sourceRange.enable
|
|
166
|
+
updatedRange.minLeadDuration = { ...sourceRange.minLeadDuration }
|
|
167
|
+
updatedRange.maxLeadDuration = { ...sourceRange.maxLeadDuration }
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (configOptions.value.timeSettings) {
|
|
171
|
+
// Copy operating hours, slot interval, booking duration
|
|
172
|
+
updatedRange.operatingHours = JSON.parse(JSON.stringify(sourceRange.operatingHours))
|
|
173
|
+
updatedRange.slotInterval = sourceRange.slotInterval
|
|
174
|
+
updatedRange.bookingDuration = sourceRange.bookingDuration
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (configOptions.value.tableCapacity) {
|
|
178
|
+
// Copy capacity tiers
|
|
179
|
+
updatedRange.capacityTiers = JSON.parse(JSON.stringify(sourceRange.capacityTiers))
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (configOptions.value.preorder) {
|
|
183
|
+
// Copy preorder setting
|
|
184
|
+
updatedRange.enablePreorder = sourceRange.enablePreorder
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (configOptions.value.guestPreferences) {
|
|
188
|
+
// Copy preferences
|
|
189
|
+
updatedRange.preferences = JSON.parse(JSON.stringify(sourceRange.preferences))
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (configOptions.value.guestMessage) {
|
|
193
|
+
// Copy guest message
|
|
194
|
+
updatedRange.guestMessage = sourceRange.guestMessage
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (configOptions.value.cancellationPolicy) {
|
|
198
|
+
// Copy cancellation policy
|
|
199
|
+
updatedRange.cancellationPolicy = sourceRange.cancellationPolicy
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Update the settings with the modified range
|
|
203
|
+
updatedSettings.ranges = [updatedRange]
|
|
204
|
+
|
|
205
|
+
// Emit the updated settings to parent
|
|
206
|
+
emit('apply', updatedSettings)
|
|
207
|
+
|
|
208
|
+
// Close the side sheet
|
|
209
|
+
showSheet.value = false
|
|
210
|
+
|
|
211
|
+
// Reset form
|
|
212
|
+
resetForm()
|
|
213
|
+
} catch (error) {
|
|
214
|
+
console.error('Error copying settings:', error)
|
|
215
|
+
showError('Failed to copy settings. Please try again.')
|
|
216
|
+
} finally {
|
|
217
|
+
isLoading.value = false
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function handleCancel() {
|
|
222
|
+
showSheet.value = false
|
|
223
|
+
resetForm()
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function resetForm() {
|
|
227
|
+
selectedRestaurantId.value = ''
|
|
228
|
+
configOptions.value = {
|
|
229
|
+
reservationAvailability: false,
|
|
230
|
+
timeSettings: false,
|
|
231
|
+
tableCapacity: false,
|
|
232
|
+
preorder: false,
|
|
233
|
+
guestPreferences: false,
|
|
234
|
+
guestMessage: false,
|
|
235
|
+
cancellationPolicy: false
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
</script>
|