@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.
Files changed (63) hide show
  1. package/.tsbuildinfo +1 -0
  2. package/dist/{KioskDevicesView-u14hzPbE.js → KioskDevicesView-Vy9FLX1n.js} +1 -1
  3. package/dist/{KioskDevicesView.vue_vue_type_script_setup_true_lang-DBgRDIoS.js → KioskDevicesView.vue_vue_type_script_setup_true_lang-DhZPOEEQ.js} +2 -2
  4. package/dist/{KioskSettingView-DmvtZcV1.js → KioskSettingView-cE-JdCBB.js} +206 -208
  5. package/dist/{KioskView-M8V91gD5.js → KioskView-BYs5bem0.js} +4 -4
  6. package/dist/OrderSettingsView-C4aEpC1j.js +56063 -0
  7. package/dist/{app-CLewMjcd.js → app-CwYXsqxX.js} +184 -20
  8. package/dist/app.js +1 -1
  9. package/dist/{dayjs.min-DCTYRWyD.js → dayjs.min-JEYIJz2D.js} +1 -1
  10. package/dist/frontend/mf-order/src/api/reservation/index.d.ts +8 -0
  11. package/dist/frontend/mf-order/src/app.d.ts +164 -0
  12. package/dist/frontend/mf-order/src/main.d.ts +164 -0
  13. package/dist/frontend/mf-order/src/stores/restaurant/index.d.ts +3 -3
  14. package/dist/frontend/mf-order/src/views/all-orders/ReflowOrder.vue.d.ts +2 -2
  15. package/dist/frontend/mf-order/src/views/order-settings/delivery/inhouse/InHouseDelivery.vue.d.ts +2 -2
  16. package/dist/frontend/mf-order/src/views/order-settings/reservation/CopySettingsSheet.vue.d.ts +186 -0
  17. package/dist/frontend/mf-order/src/views/order-settings/reservation/CustomSelect.vue.d.ts +15 -0
  18. package/dist/frontend/mf-order/src/views/order-settings/reservation/CustomTimePicker.vue.d.ts +10 -0
  19. package/dist/frontend/mf-order/src/views/order-settings/reservation/ReservationSetting.vue.d.ts +2 -0
  20. package/dist/{index-B7LtJeBJ.js → index-DZCjODMx.js} +2 -2
  21. package/dist/{menu.dto-Co7iXHNr.js → menu.dto-D9CDVLiP.js} +22865 -20028
  22. package/dist/package/entity/food-court/order.do.d.ts +47 -2
  23. package/dist/package/entity/food-court/order.dto.d.ts +0 -3
  24. package/dist/package/entity/incoming-order/incoming-order-to-bill.dto.d.ts +12356 -1
  25. package/dist/package/entity/incoming-order/incoming-order.do.d.ts +3 -22266
  26. package/dist/package/entity/incoming-order/incoming-order.dto.d.ts +18 -0
  27. package/dist/package/entity/index.d.ts +5 -0
  28. package/dist/package/entity/kiosk/marketing/marketing.dto.d.ts +1 -19864
  29. package/dist/package/entity/order/order-item/order-item.dto.d.ts +24 -3714
  30. package/dist/package/entity/order/order.do.d.ts +8 -0
  31. package/dist/package/entity/order/order.dto.d.ts +118 -0
  32. package/dist/package/entity/order-platform/external/menu/external-master-menu.do.d.ts +20 -0
  33. package/dist/package/entity/order-platform/external/menu/external-menu.do.d.ts +23 -0
  34. package/dist/package/entity/order-platform/menu.dto.d.ts +34 -0
  35. package/dist/package/entity/order-setting/order-setting.do.d.ts +861 -0
  36. package/dist/package/entity/order-setting/reservationV2/reservation.do.d.ts +1269 -0
  37. package/dist/package/entity/queue/queue.do.d.ts +1 -11
  38. package/dist/package/entity/queue/queue.dto.d.ts +25 -0
  39. package/dist/package/entity/reservation/reservation.do.d.ts +101 -0
  40. package/dist/package/entity/reservation/reservation.dto.d.ts +325 -0
  41. package/dist/package/entity/reservation/reservation.enum.d.ts +3 -0
  42. package/dist/package/entity/reservation/reservation.utils.d.ts +152 -0
  43. package/dist/style.css +1 -0
  44. package/package.json +3 -3
  45. package/src/api/reservation/index.ts +28 -0
  46. package/src/assets/images/not-found.png +0 -0
  47. package/src/locales/en-US.json +56 -0
  48. package/src/locales/th-TH.json +54 -0
  49. package/src/locales/zh-CN.json +54 -0
  50. package/src/main.ts +7 -5
  51. package/src/stores/order-setting/mapper.ts +50 -50
  52. package/src/views/kiosk/settings/KioskPaymentTypeSection.vue +1 -19
  53. package/src/views/order-settings/OrderSettingsView.vue +7 -2
  54. package/src/views/order-settings/delivery/integrated-delivery/IntegratedDelivery.vue +3 -1
  55. package/src/views/order-settings/drive-thru/DriveThruSetting.vue +13 -28
  56. package/src/views/order-settings/reservation/CopySettingsSheet.vue +238 -0
  57. package/src/views/order-settings/reservation/CustomSelect.vue +99 -0
  58. package/src/views/order-settings/reservation/CustomTimePicker.vue +201 -0
  59. package/src/views/order-settings/reservation/ReservationSetting.vue +1246 -0
  60. package/src/views/order-settings/servicecharge/ServiceChargeRule.vue +5 -1
  61. package/tsconfig.app.json +8 -6
  62. package/dist/OrderSettingsView-Bl3LshG3.js +0 -51603
  63. package/dist/frontend/mf-order/tsconfig.app.tsbuildinfo +0 -1
@@ -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
- payAtCounter:
111
- kioskOrderSettings?.takeaway?.submitOrderInstruction?.payAtCounter?.['en'] ?? null,
112
- paid: kioskOrderSettings?.takeaway?.submitOrderInstruction?.paid?.['en'] ?? null
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
- ...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
- }
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
- 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
- }
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
- en: dineInSetting.displayStand.submitOrderInstruction.payAtCounter
193
- }
192
+ en: dineInSetting.displayStand.submitOrderInstruction.payAtCounter
193
+ }
194
194
  : null,
195
195
  paid: dineInSetting.displayStand.submitOrderInstruction?.paid
196
196
  ? {
197
- en: dineInSetting.displayStand.submitOrderInstruction.paid
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
- en: dineInSetting.pickUp.submitOrderInstruction.payAtCounter
208
- }
207
+ en: dineInSetting.pickUp.submitOrderInstruction.payAtCounter
208
+ }
209
209
  : null,
210
210
  paid: dineInSetting.pickUp.submitOrderInstruction?.paid
211
211
  ? {
212
- en: dineInSetting.pickUp.submitOrderInstruction.paid
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
- 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
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: any) => {
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
- v-model="driveThruSetting.enabled"
10
- value="enabled"
11
- :label="t('order.enableDriveThru')"
12
- :sublabel="t('order.enableDriveThruSublabel')"
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
- ;(currentRestaurant.value as any).driveThru = newDriveThruSetting
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 (newRestaurant) => {
86
- if ((newRestaurant as any)?.driveThru) {
87
- driveThruSetting.value = utils.clone((newRestaurant as any).driveThru)
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 any
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>