@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.
- package/.tsbuildinfo +1 -0
- package/dist/{KioskDevicesView-TIls1ag1.js → KioskDevicesView-Vy9FLX1n.js} +1 -1
- package/dist/{KioskDevicesView.vue_vue_type_script_setup_true_lang-B2gjPfOJ.js → KioskDevicesView.vue_vue_type_script_setup_true_lang-DhZPOEEQ.js} +2 -2
- package/dist/KioskSettingView-cE-JdCBB.js +551 -0
- package/dist/{KioskView-B0Jj2sOl.js → KioskView-BYs5bem0.js} +108 -111
- package/dist/OrderSettingsView-C4aEpC1j.js +56063 -0
- package/dist/{app-JqOEacuf.js → app-CwYXsqxX.js} +182 -26
- package/dist/app.js +1 -1
- package/dist/{dayjs.min-B42nUieJ.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 -8
- package/dist/frontend/mf-order/src/main.d.ts +164 -8
- package/dist/frontend/mf-order/src/modules/order-setting/kiosk/interface.d.ts +0 -2
- package/dist/frontend/mf-order/src/stores/order-setting/index.d.ts +0 -6
- 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-B0teNm7_.js → index-DZCjODMx.js} +2 -2
- package/dist/{menu.dto-DFTxveX1.js → menu.dto-D9CDVLiP.js} +22865 -20037
- 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 +12341 -1
- package/dist/package/entity/incoming-order/incoming-order.do.d.ts +3 -22266
- 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 +1 -3721
- package/dist/package/entity/order-setting/kiosk/kiosk.do.d.ts +0 -3
- package/dist/package/entity/order-setting/kiosk/kiosk.dto.d.ts +0 -3
- package/dist/package/entity/order-setting/order-setting.do.d.ts +861 -5
- package/dist/package/entity/order-setting/order-setting.dto.d.ts +0 -10
- 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/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/package/entity/restaurant/restaurant.dto.d.ts +0 -5
- package/dist/package/entity/websocket/websocket.dto.d.ts +0 -12
- 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 +57 -3
- package/src/locales/ja-JP.json +9 -11
- package/src/locales/th-TH.json +55 -3
- package/src/locales/zh-CN.json +55 -3
- package/src/main.ts +7 -5
- package/src/modules/order-setting/kiosk/interface.ts +13 -15
- package/src/stores/order-setting/mapper.ts +41 -44
- package/src/views/kiosk/settings/KioskPaymentTypeSection.vue +1 -19
- package/src/views/kiosk/settings/KioskSettingView.vue +23 -43
- 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/KioskSettingView-UPE-q-Zd.js +0 -573
- package/dist/OrderSettingsView-Ca4y2PNF.js +0 -51603
- 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
|
-
<!--
|
|
4
|
-
<div
|
|
5
|
-
<span class="fm-typo-en-title-sm-600">{{ t('order.
|
|
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
|
-
|
|
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
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
|
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>
|
|
@@ -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>
|