@feedmepos/mf-order-setting 0.0.51 → 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 (67) hide show
  1. package/.tsbuildinfo +1 -0
  2. package/dist/{KioskDevicesView-TIls1ag1.js → KioskDevicesView-Vy9FLX1n.js} +1 -1
  3. package/dist/{KioskDevicesView.vue_vue_type_script_setup_true_lang-B2gjPfOJ.js → KioskDevicesView.vue_vue_type_script_setup_true_lang-DhZPOEEQ.js} +2 -2
  4. package/dist/KioskSettingView-cE-JdCBB.js +551 -0
  5. package/dist/{KioskView-B0Jj2sOl.js → KioskView-BYs5bem0.js} +108 -111
  6. package/dist/OrderSettingsView-C4aEpC1j.js +56063 -0
  7. package/dist/{app-JqOEacuf.js → app-CwYXsqxX.js} +182 -26
  8. package/dist/app.js +1 -1
  9. package/dist/{dayjs.min-B42nUieJ.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 -8
  12. package/dist/frontend/mf-order/src/main.d.ts +164 -8
  13. package/dist/frontend/mf-order/src/modules/order-setting/kiosk/interface.d.ts +0 -2
  14. package/dist/frontend/mf-order/src/stores/order-setting/index.d.ts +0 -6
  15. package/dist/frontend/mf-order/src/stores/restaurant/index.d.ts +3 -3
  16. package/dist/frontend/mf-order/src/views/all-orders/ReflowOrder.vue.d.ts +2 -2
  17. package/dist/frontend/mf-order/src/views/order-settings/delivery/inhouse/InHouseDelivery.vue.d.ts +2 -2
  18. package/dist/frontend/mf-order/src/views/order-settings/reservation/CopySettingsSheet.vue.d.ts +186 -0
  19. package/dist/frontend/mf-order/src/views/order-settings/reservation/CustomSelect.vue.d.ts +15 -0
  20. package/dist/frontend/mf-order/src/views/order-settings/reservation/CustomTimePicker.vue.d.ts +10 -0
  21. package/dist/frontend/mf-order/src/views/order-settings/reservation/ReservationSetting.vue.d.ts +2 -0
  22. package/dist/{index-B0teNm7_.js → index-DZCjODMx.js} +2 -2
  23. package/dist/{menu.dto-DFTxveX1.js → menu.dto-D9CDVLiP.js} +22865 -20037
  24. package/dist/package/entity/food-court/order.do.d.ts +47 -2
  25. package/dist/package/entity/food-court/order.dto.d.ts +0 -3
  26. package/dist/package/entity/incoming-order/incoming-order-to-bill.dto.d.ts +12341 -1
  27. package/dist/package/entity/incoming-order/incoming-order.do.d.ts +3 -22266
  28. package/dist/package/entity/index.d.ts +5 -0
  29. package/dist/package/entity/kiosk/marketing/marketing.dto.d.ts +1 -19864
  30. package/dist/package/entity/order/order-item/order-item.dto.d.ts +1 -3721
  31. package/dist/package/entity/order-setting/kiosk/kiosk.do.d.ts +0 -3
  32. package/dist/package/entity/order-setting/kiosk/kiosk.dto.d.ts +0 -3
  33. package/dist/package/entity/order-setting/order-setting.do.d.ts +861 -5
  34. package/dist/package/entity/order-setting/order-setting.dto.d.ts +0 -10
  35. package/dist/package/entity/order-setting/reservationV2/reservation.do.d.ts +1269 -0
  36. package/dist/package/entity/queue/queue.do.d.ts +1 -11
  37. package/dist/package/entity/reservation/reservation.do.d.ts +101 -0
  38. package/dist/package/entity/reservation/reservation.dto.d.ts +325 -0
  39. package/dist/package/entity/reservation/reservation.enum.d.ts +3 -0
  40. package/dist/package/entity/reservation/reservation.utils.d.ts +152 -0
  41. package/dist/package/entity/restaurant/restaurant.dto.d.ts +0 -5
  42. package/dist/package/entity/websocket/websocket.dto.d.ts +0 -12
  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 +57 -3
  48. package/src/locales/ja-JP.json +9 -11
  49. package/src/locales/th-TH.json +55 -3
  50. package/src/locales/zh-CN.json +55 -3
  51. package/src/main.ts +7 -5
  52. package/src/modules/order-setting/kiosk/interface.ts +13 -15
  53. package/src/stores/order-setting/mapper.ts +41 -44
  54. package/src/views/kiosk/settings/KioskPaymentTypeSection.vue +1 -19
  55. package/src/views/kiosk/settings/KioskSettingView.vue +23 -43
  56. package/src/views/order-settings/OrderSettingsView.vue +7 -2
  57. package/src/views/order-settings/delivery/integrated-delivery/IntegratedDelivery.vue +3 -1
  58. package/src/views/order-settings/drive-thru/DriveThruSetting.vue +13 -28
  59. package/src/views/order-settings/reservation/CopySettingsSheet.vue +238 -0
  60. package/src/views/order-settings/reservation/CustomSelect.vue +99 -0
  61. package/src/views/order-settings/reservation/CustomTimePicker.vue +201 -0
  62. package/src/views/order-settings/reservation/ReservationSetting.vue +1246 -0
  63. package/src/views/order-settings/servicecharge/ServiceChargeRule.vue +5 -1
  64. package/tsconfig.app.json +8 -6
  65. package/dist/KioskSettingView-UPE-q-Zd.js +0 -573
  66. package/dist/OrderSettingsView-Ca4y2PNF.js +0 -51603
  67. package/dist/frontend/mf-order/tsconfig.app.tsbuildinfo +0 -1
@@ -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
 
@@ -1,23 +1,8 @@
1
- <template>
2
- <div v-if="!!kioskOrderSettingForm" class="flex flex-col gap-32">
3
- <!-- OTA Channel Section -->
4
- <div v-if="isAdmin" class="flex flex-col gap-2">
5
- <span class="fm-typo-en-title-sm-600">{{ t('order.otaChannel') }}</span>
6
- <div class="flex flex-col gap-1 w-full md:w-1/2">
7
- <FmTextField
8
- v-model="kioskOrderSettingForm.otaChannel"
9
- :label="t('order.otaChannel')"
10
- :placeholder="t('order.otaChannelHint')"
11
- />
12
- <span class="fm-typo-en-body-sm-400 text-fm-color-typo-secondary">
13
- {{ t('order.otaChannelHint') }}
14
- </span>
15
- </div>
16
- </div>
17
-
18
- <!-- Dine In Section -->
19
- <div class="flex flex-col gap-2">
20
- <span class="fm-typo-en-title-sm-600">{{ t('order.dineIn') }}</span>
1
+ <template>
2
+ <div v-if="!!kioskOrderSettingForm" class="flex flex-col gap-32">
3
+ <!-- Dine In Section -->
4
+ <div class="flex flex-col gap-2">
5
+ <span class="fm-typo-en-title-sm-600">{{ t('order.dineIn') }}</span>
21
6
  <div class="flex flex-col gap-5">
22
7
  <FmSwitch
23
8
  v-model="kioskOrderSettingForm.dineIn.enabled"
@@ -198,22 +183,19 @@
198
183
  </template>
199
184
  <script setup lang="ts">
200
185
  import { computed, ref } from 'vue'
201
- import KioskPaymentTypeSection from './KioskPaymentTypeSection.vue'
202
- import { FdoOfflinePaymentMethod } from '@feedmepos/core/entity'
203
- import { FdoEPaymentMethod, F_ORDER_PAYMENT_TYPE } from '@entity'
204
- import type {
205
- MfKioskOrderSetting,
186
+ import KioskPaymentTypeSection from './KioskPaymentTypeSection.vue'
187
+ import { FdoOfflinePaymentMethod } from '@feedmepos/core/entity'
188
+ import { FdoEPaymentMethod, F_ORDER_PAYMENT_TYPE } from '@entity'
189
+ import type {
190
+ MfKioskOrderSetting,
206
191
  MfKioskOrderSettingForm,
207
192
  KioskPaymentTypesForm
208
193
  } from '@/modules/order-setting/kiosk/interface'
209
- import { useLoading } from '@/composables/loading'
210
- import { useSnackbar } from '@feedmepos/ui-library'
211
- import { useI18n } from '@feedmepos/mf-common'
212
- import { useCoreStore } from '@feedmepos/mf-common'
213
-
214
- const { t } = useI18n()
215
- const CoreStore = useCoreStore()
216
- const isAdmin = computed(() => CoreStore.sessionUser.value?.role.isAdmin ?? false)
194
+ import { useLoading } from '@/composables/loading'
195
+ import { useSnackbar } from '@feedmepos/ui-library'
196
+ import { useI18n } from '@feedmepos/mf-common'
197
+
198
+ const { t } = useI18n()
217
199
 
218
200
  interface Props {
219
201
  restaurantId: string
@@ -249,9 +231,8 @@ const padDigitRules = computed(() => [nonNegativeRule])
249
231
  const minRules = computed(() => [nonNegativeRule])
250
232
  const maxRules = computed(() => [nonNegativeRule, greaterThanMinRule])
251
233
 
252
- const validKioskOrderSetting = computed<MfKioskOrderSetting | null>(() => {
253
- const f = kioskOrderSettingForm.value
254
- const otaChannel = f.otaChannel?.trim()
234
+ const validKioskOrderSetting = computed<MfKioskOrderSetting | null>(() => {
235
+ const f = kioskOrderSettingForm.value
255
236
 
256
237
  // Validate display stand settings if both dineIn and displayStand are enabled
257
238
  if (f.dineIn.enabled && f.dineIn.displayStand.enabled) {
@@ -266,13 +247,12 @@ const validKioskOrderSetting = computed<MfKioskOrderSetting | null>(() => {
266
247
  if (!isValid) return null
267
248
  }
268
249
 
269
- // Return the form as-is if valid, with display stand disabled when dineIn is disabled
270
- return {
271
- ...f,
272
- otaChannel: otaChannel || undefined,
273
- dineIn: {
274
- ...f.dineIn,
275
- displayStand: {
250
+ // Return the form as-is if valid, with display stand disabled when dineIn is disabled
251
+ return {
252
+ ...f,
253
+ dineIn: {
254
+ ...f.dineIn,
255
+ displayStand: {
276
256
  ...f.dineIn.displayStand,
277
257
  enabled: f.dineIn.enabled && f.dineIn.displayStand.enabled,
278
258
  padDigit: f.dineIn.displayStand.padDigit ?? 0,
@@ -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>
@@ -0,0 +1,99 @@
1
+ <script setup lang="ts">
2
+ import { ref, computed } from 'vue'
3
+ import { components } from '@feedmepos/ui-library';
4
+
5
+ interface SelectItem {
6
+ label: string
7
+ value: string
8
+ icon?: 'radio' | 'checkbox'
9
+ }
10
+
11
+ const props = defineProps<{
12
+ modelValue: string
13
+ items: SelectItem[]
14
+ }>()
15
+
16
+ const emit = defineEmits<{
17
+ (e: 'update:modelValue', value: string): void
18
+ }>()
19
+
20
+ const isOpen = ref(false)
21
+ const selectRef = ref<HTMLDivElement | null>(null)
22
+ const menuRef = ref<typeof components['FmMenu'] | null>(null);
23
+
24
+ const selectedItem = computed(() => {
25
+ const found = props.items.find((item) => item.value === props.modelValue)
26
+ return found || props.items[0] || { label: 'Select...', value: '', icon: undefined }
27
+ })
28
+
29
+ function selectItem(item: SelectItem) {
30
+ emit('update:modelValue', item.value)
31
+ menuRef.value?.toggleMenu();
32
+ }
33
+
34
+ // Close dropdown when clicking outside
35
+ function handleClickOutside(event: MouseEvent) {
36
+ if (selectRef.value && !selectRef.value.contains(event.target as Node)) {
37
+ menuRef.value?.toggleMenu();
38
+ }
39
+ }
40
+
41
+ // Setup click outside listener when component mounts/unmounts
42
+ import { onMounted, onUnmounted } from 'vue'
43
+
44
+ onMounted(() => {
45
+ document.addEventListener('click', handleClickOutside)
46
+ })
47
+
48
+ onUnmounted(() => {
49
+ document.removeEventListener('click', handleClickOutside)
50
+ })
51
+ </script>
52
+
53
+ <template>
54
+ <div class="relative w-full" ref="selectRef">
55
+ <!-- Use FmMenu for dropdown behavior -->
56
+ <FmMenu ref="menuRef" @menu-changed="(v) => isOpen = v">
57
+ <template #menu-button>
58
+ <FmField>
59
+ <div class="flex items-center gap-4 w-full">
60
+ <FmCheckbox v-if="selectedItem.icon == 'checkbox'" :value="true" :model-value="true"
61
+ @update:model-value="(v) => v" />
62
+ <div v-if="selectedItem.icon == 'radio'" class="flex items-center">
63
+ <FmRadio :value="true" :model-value="true" @update:model-value="(v) => v" />
64
+ </div>
65
+ <!-- Label -->
66
+ <span class="text-gray-900 flex-1">
67
+ {{ selectedItem.label }}
68
+ </span>
69
+ <FmIcon name="keyboard_arrow_down" class="text-gray-500 transition-transform duration-200 ml-2"
70
+ :class="{ 'rotate-180': isOpen }" />
71
+ </div>
72
+ </FmField>
73
+ </template>
74
+
75
+ <!-- Dropdown Menu Items -->
76
+ <template #default>
77
+ <div :style="{ width: `${selectRef?.clientWidth}px` }" class="rounded-md flex flex-col gap-4">
78
+ <div v-for="item in items" :key="item.value" @click="selectItem(item)"
79
+ class="cursor-pointer transition-colors duration-150 flex items-center p-8 rounded-sm hover:bg-orange-100">
80
+ <FmCheckbox v-if="item.icon == 'checkbox'" :value="true" :model-value="true" @update:model-value="(v) => v"
81
+ class="mr-8" />
82
+ <div v-if="item.icon == 'radio'" class="flex items-center">
83
+ <FmRadio :value="true" :model-value="true" @update:model-value="(v) => v" />
84
+ </div>
85
+ <span class="text-gray-900 flex-1"> {{ item.label }} </span>
86
+ </div>
87
+ </div>
88
+ </template>
89
+ </FmMenu>
90
+ </div>
91
+ </template>
92
+
93
+ <style scoped>
94
+ /* Match FmTextField styling */
95
+ .fm-text-field-input {
96
+ font-size: 1.125rem;
97
+ line-height: 1.75rem;
98
+ }
99
+ </style>