@feedmepos/mf-order-setting 0.0.91 → 0.0.93
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/dist/DisplaySystemView-BMRCs7o8.js +8891 -0
- package/dist/{KioskDevicesView-Bz6seroM.js → KioskDevicesView-BCZOqou3.js} +1 -1
- package/dist/{KioskDevicesView.vue_vue_type_script_setup_true_lang-I39sE8Pe.js → KioskDevicesView.vue_vue_type_script_setup_true_lang-4wFO9co5.js} +3 -3
- package/dist/{KioskSettingView-Cajjh4Zm.js → KioskSettingView-DIvpRwFV.js} +3 -3
- package/dist/{KioskView-BPSbsMi5.js → KioskView-w4txC9m2.js} +5 -5
- package/dist/{OrderSettingsView-jPxZ3JDM.js → OrderSettingsView-BPuBA2QC.js} +28615 -27747
- package/dist/{app-DIhv5ssA.js → app-xZAaURng.js} +4594 -3733
- package/dist/app.js +1 -1
- package/dist/common/order-platform/index.d.ts +20 -0
- package/dist/featureFlag-Cbj0uMaM.js +846 -0
- package/dist/frontend/mf-order/src/api/brandkit/index.d.ts +3 -0
- package/dist/frontend/mf-order/src/api/reservation/index.d.ts +8 -0
- package/dist/frontend/mf-order/src/app.d.ts +752 -0
- package/dist/frontend/mf-order/src/composables/featureFlag.d.ts +7 -0
- package/dist/frontend/mf-order/src/composables/useBusinessFeatureFlag.d.ts +4 -0
- package/dist/frontend/mf-order/src/composables/useLegacyOdsAccess.d.ts +4 -0
- package/dist/frontend/mf-order/src/composables/useMiniProgramPermission.d.ts +5 -0
- package/dist/frontend/mf-order/src/main.d.ts +752 -0
- package/dist/frontend/mf-order/src/modules/display-system/legacy-ods/interface.d.ts +80 -0
- package/dist/frontend/mf-order/src/stores/order-setting/index.d.ts +3 -3
- package/dist/frontend/mf-order/src/stores/restaurant/index.d.ts +3 -0
- package/dist/frontend/mf-order/src/views/display-system/legacy-ods/ColorOverrideDialog.vue.d.ts +26 -0
- package/dist/frontend/mf-order/src/views/display-system/legacy-ods/ColorSelection.vue.d.ts +15 -0
- package/dist/frontend/mf-order/src/views/display-system/legacy-ods/LegacyOdsEditorDialog.vue.d.ts +24 -0
- package/dist/frontend/mf-order/src/views/display-system/legacy-ods/LegacyOdsTemplateList.vue.d.ts +4 -0
- package/dist/frontend/mf-order/src/views/display-system/view-selection.d.ts +18 -0
- 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/CustomTimePicker.vue.d.ts +11 -0
- package/dist/frontend/mf-order/src/views/order-settings/reservation/ReservationSetting.vue.d.ts +2 -0
- package/dist/frontend/mf-order/tsconfig.app.tsbuildinfo +1 -1
- package/dist/{index-vaSLX6r3.js → index-BAFwfvqv.js} +2 -2
- package/dist/{index-Clp_YRJ2.js → index-BxQKdXyN.js} +1 -1
- package/dist/{index-CmqNrkNl.js → index-Cipbdi6i.js} +1 -1
- package/dist/order.dto-yLICIGDQ.js +41528 -0
- package/dist/package/entity/delivery/delivery.dto.d.ts +7 -0
- package/dist/package/entity/food-court/food-court.dto.d.ts +315 -264
- package/dist/package/entity/food-court/order-v2.do.d.ts +231 -0
- package/dist/package/entity/food-court/order-v2.do.spec.d.ts +1 -0
- package/dist/package/entity/incoming-order/incoming-order-to-bill.dto.d.ts +15 -6
- package/dist/package/entity/index.d.ts +7 -0
- package/dist/package/entity/index.frontend.d.ts +2 -0
- package/dist/package/entity/member/member.dto.d.ts +2 -2
- package/dist/package/entity/order/dine-in/dine-in.do.d.ts +44 -9
- package/dist/package/entity/order/dine-in/dine-in.dto.d.ts +17 -3
- package/dist/package/entity/order/menu/menu.dto.d.ts +33 -3
- package/dist/package/entity/order/order-item/order-item.dto.d.ts +18 -18
- package/dist/package/entity/order/order.do.d.ts +87 -12
- package/dist/package/entity/order/order.dto.d.ts +762 -358
- package/dist/package/entity/order/order.enum.d.ts +1 -1
- package/dist/package/entity/order-platform/foodpanda/foodpanda-order.dto.d.ts +7 -0
- package/dist/package/entity/order-platform/grabfood/grabfood-order.do.d.ts +7 -0
- package/dist/package/entity/order-platform/menu.dto.d.ts +10 -10
- package/dist/package/entity/order-platform/order-platform.dto.d.ts +92 -0
- package/dist/package/entity/order-platform/shopeefood/shopeefood-order.do.d.ts +7 -0
- package/dist/package/entity/order-platform/tng/tng-menu.do.d.ts +6 -6
- package/dist/package/entity/order-setting/order-setting.do.d.ts +866 -5
- package/dist/package/entity/order-setting/order-setting.dto.d.ts +10 -10
- package/dist/package/entity/order-setting/reservationV2/reservation.do.d.ts +1269 -0
- package/dist/package/entity/order-setting/sms/sms.do.d.ts +3 -3
- package/dist/package/entity/order-setting/sms/sms.dto.d.ts +3 -3
- package/dist/package/entity/queue/queue.dto.d.ts +175 -121
- package/dist/package/entity/reservation/reservation-pos.do.d.ts +222 -0
- package/dist/package/entity/reservation/reservation.do.d.ts +205 -0
- package/dist/package/entity/reservation/reservation.dto.d.ts +428 -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 +39 -24
- package/dist/package/entity/user/user.do.d.ts +2 -2
- package/dist/package/entity/websocket/websocket.dto.d.ts +161 -21
- package/dist/package/entity/websocket/websocket.enum.d.ts +1 -1
- package/dist/style.css +1 -1
- package/env.d.ts +2 -0
- package/package.json +3 -2
- package/src/api/brandkit/index.ts +24 -0
- package/src/api/reservation/index.ts +25 -0
- package/src/assets/images/not-found.png +0 -0
- package/src/composables/featureFlag.ts +22 -0
- package/src/composables/useBusinessFeatureFlag.ts +44 -0
- package/src/composables/useLegacyOdsAccess.ts +35 -0
- package/src/composables/useMiniProgramPermission.ts +19 -0
- package/src/composables/useQrOrderingEnabled.ts +1 -21
- package/src/locales/en-US.json +188 -0
- package/src/locales/ja-JP.json +188 -0
- package/src/locales/th-TH.json +188 -0
- package/src/locales/zh-CN.json +188 -0
- package/src/modules/display-system/legacy-ods/interface.ts +108 -0
- package/src/router/routes.ts +1 -1
- package/src/views/display-system/DisplaySystemView.vue +82 -11
- package/src/views/display-system/legacy-ods/ColorOverrideDialog.vue +223 -0
- package/src/views/display-system/legacy-ods/ColorSelection.vue +67 -0
- package/src/views/display-system/legacy-ods/LegacyOdsEditorDialog.spec.ts +185 -0
- package/src/views/display-system/legacy-ods/LegacyOdsEditorDialog.vue +1677 -0
- package/src/views/display-system/legacy-ods/LegacyOdsTemplateList.spec.ts +124 -0
- package/src/views/display-system/legacy-ods/LegacyOdsTemplateList.vue +297 -0
- package/src/views/display-system/legacy-ods/brandkit.spec.ts +67 -0
- package/src/views/display-system/view-selection.spec.ts +93 -0
- package/src/views/display-system/view-selection.ts +54 -0
- package/src/views/order-settings/OrderSettingsView.vue +9 -2
- package/src/views/order-settings/delivery/delivery.spec.ts +21 -0
- package/src/views/order-settings/delivery/delivery.ts +2 -51
- package/src/views/order-settings/dine-in/DineInSetting.vue +11 -1
- package/src/views/order-settings/reservation/CopySettingsSheet.vue +256 -0
- package/src/views/order-settings/reservation/CustomTimePicker.vue +311 -0
- package/src/views/order-settings/reservation/ReservationSetting.vue +1549 -0
- package/vitest.config.ts +2 -1
- package/dist/DisplaySystemView-n7Kqdw-h.js +0 -6564
- package/dist/_plugin-vue_export-helper-CHgC5LLL.js +0 -9
- package/dist/order.dto-DV1qL3Q_.js +0 -40301
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
3
|
+
import { flushPromises, mount, type VueWrapper } from '@vue/test-utils'
|
|
4
|
+
import { ref } from 'vue'
|
|
5
|
+
import { getBusinessBrandkit, saveBusinessOds } from '@/api/brandkit'
|
|
6
|
+
|
|
7
|
+
const dialog = vi.hoisted(() => ({ open: vi.fn(), close: vi.fn() }))
|
|
8
|
+
vi.mock('@feedmepos/ui-library', () => ({ useDialog: () => dialog }))
|
|
9
|
+
vi.mock('@/api/brandkit', () => ({ getBusinessBrandkit: vi.fn(), saveBusinessOds: vi.fn() }))
|
|
10
|
+
vi.mock('@/components/snackbar', () => ({
|
|
11
|
+
useSnackbarFunctions: () => ({ showError: vi.fn(), showSuccess: vi.fn() })
|
|
12
|
+
}))
|
|
13
|
+
vi.mock('@/composables/useLegacyOdsAccess', () => ({
|
|
14
|
+
useLegacyOdsAccess: () => ({ canAccessOdsTemplateSettings: ref(false) })
|
|
15
|
+
}))
|
|
16
|
+
vi.mock('@feedmepos/mf-common', () => ({
|
|
17
|
+
useCoreStore: () => ({
|
|
18
|
+
currentBusiness: ref({ _id: 'business' }),
|
|
19
|
+
namespace: ref('dev'),
|
|
20
|
+
restaurants: ref([])
|
|
21
|
+
}),
|
|
22
|
+
useI18n: () => ({ t: (key: string) => key })
|
|
23
|
+
}))
|
|
24
|
+
vi.mock('./LegacyOdsEditorDialog.vue', () => ({
|
|
25
|
+
default: {
|
|
26
|
+
name: 'LegacyOdsEditorDialog',
|
|
27
|
+
props: ['modelValue', 'entry', 'allEntries'],
|
|
28
|
+
template: '<div />'
|
|
29
|
+
}
|
|
30
|
+
}))
|
|
31
|
+
import List from './LegacyOdsTemplateList.vue'
|
|
32
|
+
|
|
33
|
+
const wrappers: VueWrapper[] = []
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
wrappers.forEach((wrapper) => wrapper.unmount())
|
|
36
|
+
wrappers.length = 0
|
|
37
|
+
vi.resetAllMocks()
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
async function list() {
|
|
41
|
+
vi.mocked(getBusinessBrandkit).mockResolvedValue({
|
|
42
|
+
ods: [
|
|
43
|
+
{ label: 'Default', isDefault: true, restaurantIds: ['outlet'] },
|
|
44
|
+
{ label: 'Other', isDefault: false, restaurantIds: ['other'] }
|
|
45
|
+
]
|
|
46
|
+
})
|
|
47
|
+
const wrapper = mount(List, {
|
|
48
|
+
global: {
|
|
49
|
+
stubs: {
|
|
50
|
+
FmButton: {
|
|
51
|
+
props: ['icon', 'disabled'],
|
|
52
|
+
template: '<button :data-icon="icon" :disabled="disabled" />'
|
|
53
|
+
},
|
|
54
|
+
FmSnackbar: true,
|
|
55
|
+
FmCircularProgress: true
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
wrappers.push(wrapper)
|
|
60
|
+
await flushPromises()
|
|
61
|
+
return wrapper
|
|
62
|
+
}
|
|
63
|
+
function buttons(wrapper: VueWrapper, icon: string) {
|
|
64
|
+
return wrapper.findAll(`button[data-icon="${icon}"]`)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function editor(wrapper: VueWrapper) {
|
|
68
|
+
return wrapper.findComponent({ name: 'LegacyOdsEditorDialog' })
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
describe('legacy ODS list without the template flag', () => {
|
|
72
|
+
it('saves a new default right away and clears the old default', async () => {
|
|
73
|
+
const wrapper = await list()
|
|
74
|
+
;(wrapper.vm as unknown as { addOdsEntry: () => void }).addOdsEntry()
|
|
75
|
+
await wrapper.vm.$nextTick()
|
|
76
|
+
expect(editor(wrapper).props('modelValue')).toBe(true)
|
|
77
|
+
expect(editor(wrapper).props('entry')).toMatchObject({ isDefault: false })
|
|
78
|
+
editor(wrapper).vm.$emit('save', { label: 'New default', isDefault: true })
|
|
79
|
+
await flushPromises()
|
|
80
|
+
expect(saveBusinessOds).toHaveBeenCalledWith(
|
|
81
|
+
'business',
|
|
82
|
+
[
|
|
83
|
+
{ label: 'Default', isDefault: false, restaurantIds: ['outlet'] },
|
|
84
|
+
{ label: 'Other', isDefault: false, restaurantIds: ['other'] },
|
|
85
|
+
{ label: 'New default', isDefault: true }
|
|
86
|
+
],
|
|
87
|
+
'dev'
|
|
88
|
+
)
|
|
89
|
+
expect(editor(wrapper).props('modelValue')).toBe(false)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('keeps the editor open when saving fails', async () => {
|
|
93
|
+
const wrapper = await list()
|
|
94
|
+
vi.mocked(saveBusinessOds).mockRejectedValue(new Error('offline'))
|
|
95
|
+
await buttons(wrapper, 'edit')[1].trigger('click')
|
|
96
|
+
editor(wrapper).vm.$emit('save', { label: 'Renamed', isDefault: false })
|
|
97
|
+
await flushPromises()
|
|
98
|
+
expect(editor(wrapper).props('modelValue')).toBe(true)
|
|
99
|
+
expect(editor(wrapper).props('allEntries')[1].label).toBe('Other')
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('duplicates and deletes after confirmation, never offering to delete the default', async () => {
|
|
103
|
+
const wrapper = await list()
|
|
104
|
+
expect(buttons(wrapper, 'delete')).toHaveLength(1)
|
|
105
|
+
dialog.open.mockReturnValue({ onPrimary: (callback: () => Promise<void>) => callback() })
|
|
106
|
+
await buttons(wrapper, 'content_copy')[0].trigger('click')
|
|
107
|
+
await flushPromises()
|
|
108
|
+
expect(saveBusinessOds).toHaveBeenLastCalledWith(
|
|
109
|
+
'business',
|
|
110
|
+
[
|
|
111
|
+
{ label: 'Default', isDefault: true, restaurantIds: ['outlet'] },
|
|
112
|
+
{ label: 'Other', isDefault: false, restaurantIds: ['other'] },
|
|
113
|
+
{ label: 'order.displaySystem.legacyOds.copyLabel', isDefault: false, restaurantIds: [] }
|
|
114
|
+
],
|
|
115
|
+
'dev'
|
|
116
|
+
)
|
|
117
|
+
expect(buttons(wrapper, 'delete')).toHaveLength(2)
|
|
118
|
+
await buttons(wrapper, 'delete')[1].trigger('click')
|
|
119
|
+
await flushPromises()
|
|
120
|
+
expect(dialog.open).toHaveBeenCalledTimes(2)
|
|
121
|
+
expect(editor(wrapper).props('allEntries')).toHaveLength(2)
|
|
122
|
+
expect(editor(wrapper).props('allEntries')[0].isDefault).toBe(true)
|
|
123
|
+
})
|
|
124
|
+
})
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, ref, watch } from 'vue'
|
|
3
|
+
import { useCoreStore, useI18n } from '@feedmepos/mf-common'
|
|
4
|
+
import { useDialog } from '@feedmepos/ui-library'
|
|
5
|
+
import { getBusinessBrandkit, saveBusinessOds } from '@/api/brandkit'
|
|
6
|
+
import { useSnackbarFunctions } from '@/components/snackbar'
|
|
7
|
+
import {
|
|
8
|
+
migrateOdsToArray,
|
|
9
|
+
type BrandKit,
|
|
10
|
+
type OdsConfigEntry
|
|
11
|
+
} from '@/modules/display-system/legacy-ods/interface'
|
|
12
|
+
import LegacyOdsEditorDialog from './LegacyOdsEditorDialog.vue'
|
|
13
|
+
|
|
14
|
+
const Core = useCoreStore()
|
|
15
|
+
const { t } = useI18n()
|
|
16
|
+
const dialog = useDialog()
|
|
17
|
+
const { showError, showSuccess } = useSnackbarFunctions()
|
|
18
|
+
const brandkit = ref<BrandKit>({})
|
|
19
|
+
const loading = ref(true)
|
|
20
|
+
const saving = ref(false)
|
|
21
|
+
const loaded = ref(false)
|
|
22
|
+
const odsConfigs = computed(() =>
|
|
23
|
+
migrateOdsToArray(brandkit.value.ods, t('order.displaySystem.legacyOds.default'))
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
const colorOverrideFields = computed(() => [
|
|
27
|
+
{
|
|
28
|
+
key: 'colors.primary',
|
|
29
|
+
title: t('order.displaySystem.legacyOds.color.primary.title'),
|
|
30
|
+
description: t('order.displaySystem.legacyOds.color.primary.description')
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
key: 'colors.secondary',
|
|
34
|
+
title: t('order.displaySystem.legacyOds.color.secondary.title'),
|
|
35
|
+
description: t('order.displaySystem.legacyOds.color.secondary.description')
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
key: 'colors.accent',
|
|
39
|
+
title: t('order.displaySystem.legacyOds.color.accent.title'),
|
|
40
|
+
description: t('order.displaySystem.legacyOds.color.accent.description')
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
key: 'colors.text.onPrimary',
|
|
44
|
+
title: t('order.displaySystem.legacyOds.typography.onPrimary.title'),
|
|
45
|
+
description: t('order.displaySystem.legacyOds.typography.onPrimary.description')
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
key: 'colors.text.primary',
|
|
49
|
+
title: t('order.displaySystem.legacyOds.typography.primary.title'),
|
|
50
|
+
description: t('order.displaySystem.legacyOds.typography.primary.description')
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
key: 'colors.text.secondary',
|
|
54
|
+
title: t('order.displaySystem.legacyOds.typography.secondary.title'),
|
|
55
|
+
description: t('order.displaySystem.legacyOds.typography.secondary.description')
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
key: 'colors.text.tertiary',
|
|
59
|
+
title: t('order.displaySystem.legacyOds.typography.tertiary.title'),
|
|
60
|
+
description: t('order.displaySystem.legacyOds.typography.tertiary.description')
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
key: 'colors.neutral.light',
|
|
64
|
+
title: t('order.displaySystem.legacyOds.neutralColors.light.title'),
|
|
65
|
+
description: t('order.displaySystem.legacyOds.neutralColors.light.description')
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
key: 'colors.neutral.mid',
|
|
69
|
+
title: t('order.displaySystem.legacyOds.neutralColors.mid.title'),
|
|
70
|
+
description: t('order.displaySystem.legacyOds.neutralColors.mid.description')
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
key: 'colors.neutral.dark',
|
|
74
|
+
title: t('order.displaySystem.legacyOds.neutralColors.dark.title'),
|
|
75
|
+
description: t('order.displaySystem.legacyOds.neutralColors.dark.description')
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
key: 'colors.background.base',
|
|
79
|
+
title: t('order.displaySystem.legacyOds.backgroundColors.base.title'),
|
|
80
|
+
description: t('order.displaySystem.legacyOds.backgroundColors.base.description')
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
key: 'colors.background.elevated',
|
|
84
|
+
title: t('order.displaySystem.legacyOds.backgroundColors.elevated.title'),
|
|
85
|
+
description: t('order.displaySystem.legacyOds.backgroundColors.elevated.description')
|
|
86
|
+
}
|
|
87
|
+
])
|
|
88
|
+
|
|
89
|
+
const odsEditorOpen = ref(false)
|
|
90
|
+
const odsEditingIndex = ref(-1)
|
|
91
|
+
const odsEditingEntry = ref<OdsConfigEntry>({ isDefault: false })
|
|
92
|
+
|
|
93
|
+
const cloneOds = () => JSON.parse(JSON.stringify(odsConfigs.value)) as OdsConfigEntry[]
|
|
94
|
+
|
|
95
|
+
const entryLabel = (entry: OdsConfigEntry, index: number) =>
|
|
96
|
+
entry.label ||
|
|
97
|
+
(entry.isDefault
|
|
98
|
+
? t('order.displaySystem.legacyOds.default')
|
|
99
|
+
: t('order.displaySystem.legacyOds.configNumber', { index: index + 1 }))
|
|
100
|
+
|
|
101
|
+
async function persist(ods: OdsConfigEntry[]) {
|
|
102
|
+
const businessId = Core.currentBusiness.value?._id
|
|
103
|
+
const namespace = Core.namespace.value
|
|
104
|
+
if (!businessId || !loaded.value || saving.value) return false
|
|
105
|
+
saving.value = true
|
|
106
|
+
try {
|
|
107
|
+
await saveBusinessOds(businessId, ods, namespace)
|
|
108
|
+
if (Core.currentBusiness.value?._id !== businessId || Core.namespace.value !== namespace) return false
|
|
109
|
+
brandkit.value = { ...brandkit.value, ods }
|
|
110
|
+
showSuccess(t('order.displaySystem.legacyOds.saved'))
|
|
111
|
+
return true
|
|
112
|
+
} catch (error) {
|
|
113
|
+
showError(error instanceof Error ? error.message : t('order.displaySystem.saveFailed'))
|
|
114
|
+
return false
|
|
115
|
+
} finally {
|
|
116
|
+
saving.value = false
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const addOdsEntry = () => {
|
|
121
|
+
const hasDefault = odsConfigs.value.some((e) => e.isDefault)
|
|
122
|
+
odsEditingIndex.value = -1
|
|
123
|
+
odsEditingEntry.value = {
|
|
124
|
+
label: hasDefault ? '' : t('order.displaySystem.legacyOds.default'),
|
|
125
|
+
isDefault: !hasDefault
|
|
126
|
+
}
|
|
127
|
+
odsEditorOpen.value = true
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const editOdsEntry = (index: number) => {
|
|
131
|
+
const entry = odsConfigs.value[index]
|
|
132
|
+
if (!entry) return
|
|
133
|
+
odsEditingIndex.value = index
|
|
134
|
+
odsEditingEntry.value = JSON.parse(JSON.stringify(entry))
|
|
135
|
+
odsEditorOpen.value = true
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const duplicateOdsEntry = (index: number) => {
|
|
139
|
+
const entry = odsConfigs.value[index]
|
|
140
|
+
if (!entry) return
|
|
141
|
+
const label = entryLabel(entry, index)
|
|
142
|
+
|
|
143
|
+
dialog
|
|
144
|
+
.open({
|
|
145
|
+
title: t('order.displaySystem.legacyOds.duplicateConfiguration'),
|
|
146
|
+
message: t('order.displaySystem.legacyOds.duplicateConfirmation', { label }),
|
|
147
|
+
primaryActions: {
|
|
148
|
+
text: t('order.displaySystem.legacyOds.duplicate'),
|
|
149
|
+
close: false
|
|
150
|
+
},
|
|
151
|
+
secondaryActions: {
|
|
152
|
+
text: t('order.displaySystem.legacyOds.cancel'),
|
|
153
|
+
close: true
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
.onPrimary(async () => {
|
|
157
|
+
const duplicate = JSON.parse(JSON.stringify(entry)) as OdsConfigEntry
|
|
158
|
+
duplicate.label = t('order.displaySystem.legacyOds.copyLabel', { label })
|
|
159
|
+
duplicate.isDefault = false
|
|
160
|
+
if (entry.isDefault) {
|
|
161
|
+
duplicate.restaurantIds = []
|
|
162
|
+
}
|
|
163
|
+
if (await persist([...cloneOds(), duplicate])) dialog.close()
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const deleteOdsEntry = (index: number) => {
|
|
168
|
+
const entry = odsConfigs.value[index]
|
|
169
|
+
if (!entry) return
|
|
170
|
+
|
|
171
|
+
dialog
|
|
172
|
+
.open({
|
|
173
|
+
title: t('order.displaySystem.legacyOds.deleteConfiguration'),
|
|
174
|
+
message: t('order.displaySystem.legacyOds.deleteConfirmation', {
|
|
175
|
+
label: entryLabel(entry, index)
|
|
176
|
+
}),
|
|
177
|
+
primaryActions: {
|
|
178
|
+
text: t('order.displaySystem.legacyOds.delete'),
|
|
179
|
+
close: false
|
|
180
|
+
},
|
|
181
|
+
secondaryActions: {
|
|
182
|
+
text: t('order.displaySystem.legacyOds.cancel'),
|
|
183
|
+
close: true
|
|
184
|
+
}
|
|
185
|
+
})
|
|
186
|
+
.onPrimary(async () => {
|
|
187
|
+
const ods = cloneOds()
|
|
188
|
+
ods.splice(index, 1)
|
|
189
|
+
if (await persist(ods)) dialog.close()
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const onSaveOdsEntry = async (entry: OdsConfigEntry) => {
|
|
194
|
+
const ods = cloneOds()
|
|
195
|
+
if (entry.isDefault) {
|
|
196
|
+
for (const e of ods) {
|
|
197
|
+
e.isDefault = false
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (odsEditingIndex.value >= 0) {
|
|
201
|
+
ods[odsEditingIndex.value] = entry
|
|
202
|
+
} else {
|
|
203
|
+
ods.push(entry)
|
|
204
|
+
}
|
|
205
|
+
if (await persist(ods)) odsEditorOpen.value = false
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const restaurantNameMap = computed(() => {
|
|
209
|
+
const map = new Map<string, string>()
|
|
210
|
+
for (const r of Core.restaurants?.value ?? []) {
|
|
211
|
+
map.set(r._id, r.profile.name)
|
|
212
|
+
}
|
|
213
|
+
return map
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
const restaurantNamesForEntry = (entry: OdsConfigEntry) => {
|
|
217
|
+
if (entry.isDefault) return t('order.displaySystem.legacyOds.allRestaurantsDefault')
|
|
218
|
+
if (!entry.restaurantIds?.length) return t('order.displaySystem.legacyOds.noRestaurantsSelected')
|
|
219
|
+
return entry.restaurantIds.map((id) => restaurantNameMap.value.get(id) ?? id).join(', ')
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
watch(
|
|
223
|
+
[() => Core.currentBusiness.value?._id, () => Core.namespace.value],
|
|
224
|
+
async ([businessId, namespace], _, onCleanup) => {
|
|
225
|
+
let cancelled = false
|
|
226
|
+
onCleanup(() => {
|
|
227
|
+
cancelled = true
|
|
228
|
+
})
|
|
229
|
+
odsEditorOpen.value = false
|
|
230
|
+
loaded.value = false
|
|
231
|
+
loading.value = true
|
|
232
|
+
if (!businessId) {
|
|
233
|
+
loading.value = false
|
|
234
|
+
return
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
const result = await getBusinessBrandkit(businessId, namespace)
|
|
238
|
+
if (cancelled) return
|
|
239
|
+
brandkit.value = result
|
|
240
|
+
loaded.value = true
|
|
241
|
+
} catch (error) {
|
|
242
|
+
if (!cancelled)
|
|
243
|
+
showError(error instanceof Error ? error.message : t('order.displaySystem.loadFailed'))
|
|
244
|
+
} finally {
|
|
245
|
+
if (!cancelled) loading.value = false
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
{ immediate: true }
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
defineExpose({ addOdsEntry })
|
|
252
|
+
</script>
|
|
253
|
+
|
|
254
|
+
<template>
|
|
255
|
+
<section class="mt-12">
|
|
256
|
+
<div v-if="loading" class="flex justify-center py-40"><FmCircularProgress size="lg" /></div>
|
|
257
|
+
<template v-else-if="loaded">
|
|
258
|
+
<div v-if="!odsConfigs.length" class="empty-state">
|
|
259
|
+
<strong>{{ t('order.displaySystem.legacyOds.noConfigsYet') }}</strong>
|
|
260
|
+
<span>{{ t('order.displaySystem.legacyOds.noConfigsDescription') }}</span>
|
|
261
|
+
</div>
|
|
262
|
+
<div v-else class="grid grid-cols-1 gap-16 xl:grid-cols-2">
|
|
263
|
+
<article v-for="(entry, index) in odsConfigs" :key="index" class="resource-card">
|
|
264
|
+
<div class="flex justify-between gap-12">
|
|
265
|
+
<div class="min-w-0">
|
|
266
|
+
<div class="flex items-center gap-8">
|
|
267
|
+
<h3 class="truncate text-lg font-semibold">{{ entryLabel(entry, index) }}</h3>
|
|
268
|
+
<span v-if="entry.isDefault" class="default-badge">{{ t('order.displaySystem.legacyOds.default') }}</span>
|
|
269
|
+
</div>
|
|
270
|
+
<p class="mt-4 truncate text-sm text-gray-500">{{ restaurantNamesForEntry(entry) }}</p>
|
|
271
|
+
</div>
|
|
272
|
+
<div class="flex gap-4">
|
|
273
|
+
<FmButton icon="edit" variant="plain" :disabled="saving" @click="editOdsEntry(index)" />
|
|
274
|
+
<FmButton icon="content_copy" variant="plain" :disabled="saving" @click="duplicateOdsEntry(index)" />
|
|
275
|
+
<FmButton v-if="!entry.isDefault" icon="delete" variant="plain" :disabled="saving" @click="deleteOdsEntry(index)" />
|
|
276
|
+
</div>
|
|
277
|
+
</div>
|
|
278
|
+
</article>
|
|
279
|
+
</div>
|
|
280
|
+
</template>
|
|
281
|
+
<LegacyOdsEditorDialog
|
|
282
|
+
v-model="odsEditorOpen"
|
|
283
|
+
:entry="odsEditingEntry"
|
|
284
|
+
:editing-index="odsEditingIndex"
|
|
285
|
+
:all-entries="odsConfigs"
|
|
286
|
+
:brandkit="brandkit"
|
|
287
|
+
:color-fields="colorOverrideFields"
|
|
288
|
+
:saving="saving"
|
|
289
|
+
@save="onSaveOdsEntry"
|
|
290
|
+
@cancel="odsEditorOpen = false"
|
|
291
|
+
/>
|
|
292
|
+
</section>
|
|
293
|
+
</template>
|
|
294
|
+
|
|
295
|
+
<style scoped>
|
|
296
|
+
.empty-state{display:grid;justify-items:center;gap:6px;border:1px dashed #d1d5db;border-radius:12px;padding:56px 20px;color:#6b7280;text-align:center}.empty-state strong{color:#111827}.resource-card{border:1px solid #e5e7eb;border-radius:12px;padding:20px;background:white;box-shadow:0 2px 8px #0000000a}.default-badge{border-radius:999px;padding:3px 8px;background:#f3f4f6;font-size:11px;font-weight:700}
|
|
297
|
+
</style>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import axios from 'axios'
|
|
3
|
+
import { getBusinessBrandkit, saveBusinessOds } from '@/api/brandkit'
|
|
4
|
+
import { migrateOdsToArray } from '@/modules/display-system/legacy-ods/interface'
|
|
5
|
+
|
|
6
|
+
vi.mock('axios', () => ({ default: { get: vi.fn(), post: vi.fn() } }))
|
|
7
|
+
beforeEach(() => vi.resetAllMocks())
|
|
8
|
+
|
|
9
|
+
describe('business ODS persistence', () => {
|
|
10
|
+
it('reads the latest brandkit on every save and only replaces ods', async () => {
|
|
11
|
+
const latest = {
|
|
12
|
+
colors: { primary: '#123456' },
|
|
13
|
+
fonts: { brand: 'Inter' },
|
|
14
|
+
layout: { app: { template: { mainView: { id: 'new-app' } } }, kiosk: { currentVersion: 3 } },
|
|
15
|
+
ordering: { app: { singleCategory: true } },
|
|
16
|
+
unknownField: { preserved: true },
|
|
17
|
+
ods: [{ isDefault: true, label: 'Old' }]
|
|
18
|
+
}
|
|
19
|
+
const ods = [{ isDefault: true, label: 'Updated' }]
|
|
20
|
+
vi.mocked(axios.get).mockResolvedValue({ data: latest })
|
|
21
|
+
vi.mocked(axios.post).mockResolvedValue({})
|
|
22
|
+
await saveBusinessOds('business/id', ods, 'dev')
|
|
23
|
+
expect(axios.get).toHaveBeenCalledWith(
|
|
24
|
+
'https://api.feedmedev.vip/metadata/business%2Fid/brandkit'
|
|
25
|
+
)
|
|
26
|
+
expect(axios.post).toHaveBeenCalledWith(
|
|
27
|
+
'https://api.feedmedev.vip/metadata/business%2Fid/brandkit',
|
|
28
|
+
{ ...latest, ods }
|
|
29
|
+
)
|
|
30
|
+
expect(latest.ods[0].label).toBe('Old')
|
|
31
|
+
const newer = { ...latest, colors: { primary: '#abcdef' } }
|
|
32
|
+
vi.mocked(axios.get).mockResolvedValue({ data: newer })
|
|
33
|
+
await saveBusinessOds('business/id', ods, 'prod')
|
|
34
|
+
expect(axios.post).toHaveBeenLastCalledWith(
|
|
35
|
+
'https://api.feedme.vip/metadata/business%2Fid/brandkit',
|
|
36
|
+
{ ...newer, ods }
|
|
37
|
+
)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('does not overwrite metadata when reading the latest document fails', async () => {
|
|
41
|
+
vi.mocked(axios.get).mockRejectedValue(new Error('offline'))
|
|
42
|
+
await expect(saveBusinessOds('business', [], 'dev')).rejects.toThrow('offline')
|
|
43
|
+
expect(axios.post).not.toHaveBeenCalled()
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('returns business metadata without altering it', async () => {
|
|
47
|
+
const brandkit = { ods: [] }
|
|
48
|
+
vi.mocked(axios.get).mockResolvedValue({ data: brandkit })
|
|
49
|
+
expect(await getBusinessBrandkit('business', 'prod')).toBe(brandkit)
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
describe('legacy ODS shape', () => {
|
|
54
|
+
it('wraps an object in a default entry and preserves its settings', () => {
|
|
55
|
+
const legacy = {
|
|
56
|
+
layout: { boardWidthPercent: 80 },
|
|
57
|
+
display: { hideCompletedOrders: true },
|
|
58
|
+
brandkitOverride: { colors: { primary: '#fff' } }
|
|
59
|
+
}
|
|
60
|
+
expect(migrateOdsToArray(legacy, 'Default')).toEqual([
|
|
61
|
+
{ label: 'Default', isDefault: true, ...legacy }
|
|
62
|
+
])
|
|
63
|
+
expect(migrateOdsToArray(undefined, 'Default')).toEqual([])
|
|
64
|
+
const entries = [{ isDefault: false }]
|
|
65
|
+
expect(migrateOdsToArray(entries, 'Default')).toBe(entries)
|
|
66
|
+
})
|
|
67
|
+
})
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
availableDisplaySystemViews,
|
|
4
|
+
defaultDisplaySystemView,
|
|
5
|
+
isPosV8,
|
|
6
|
+
requestedDisplaySystemView
|
|
7
|
+
} from './view-selection'
|
|
8
|
+
|
|
9
|
+
const v7a = { _id: 'v7a', posVersion: '7.9.0' }
|
|
10
|
+
const v7b = { _id: 'v7b', posVersion: '7.12.31' }
|
|
11
|
+
const v8a = { _id: 'v8a', posVersion: '8.0.0-beta.1' }
|
|
12
|
+
const v8b = { _id: 'v8b', posVersion: '9.0.0' }
|
|
13
|
+
const fresh = { _id: 'fresh', posVersion: null }
|
|
14
|
+
|
|
15
|
+
const both = { canManageOrderSetting: true, canManageBrandkit: true, hasLegacyOdsConfigs: false }
|
|
16
|
+
|
|
17
|
+
describe('available Display System views', () => {
|
|
18
|
+
it('offers each view only to users with its permission', () => {
|
|
19
|
+
expect(availableDisplaySystemViews({ ...both, outlets: [v7a, v8a] })).toEqual(['v8', 'v7'])
|
|
20
|
+
expect(
|
|
21
|
+
availableDisplaySystemViews({ ...both, canManageBrandkit: false, outlets: [v7a] })
|
|
22
|
+
).toEqual(['v8'])
|
|
23
|
+
expect(
|
|
24
|
+
availableDisplaySystemViews({ ...both, canManageOrderSetting: false, outlets: [v7a] })
|
|
25
|
+
).toEqual(['v7'])
|
|
26
|
+
expect(
|
|
27
|
+
availableDisplaySystemViews({
|
|
28
|
+
...both,
|
|
29
|
+
canManageOrderSetting: false,
|
|
30
|
+
canManageBrandkit: false,
|
|
31
|
+
outlets: [v7a]
|
|
32
|
+
})
|
|
33
|
+
).toEqual([])
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('offers the v7 view to an all-v8 business only while it still has v7 ODS configs', () => {
|
|
37
|
+
expect(availableDisplaySystemViews({ ...both, outlets: [v8a, v8b] })).toEqual(['v8'])
|
|
38
|
+
expect(
|
|
39
|
+
availableDisplaySystemViews({ ...both, outlets: [v8a, v8b], hasLegacyOdsConfigs: true })
|
|
40
|
+
).toEqual(['v8', 'v7'])
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('does not count outlets without a POS version as v7', () => {
|
|
44
|
+
expect(availableDisplaySystemViews({ ...both, outlets: [fresh] })).toEqual(['v8'])
|
|
45
|
+
expect(
|
|
46
|
+
availableDisplaySystemViews({ ...both, canManageOrderSetting: false, outlets: [fresh, v8a] })
|
|
47
|
+
).toEqual([])
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
describe('default Display System view', () => {
|
|
52
|
+
it('follows the business when every outlet runs the same POS', () => {
|
|
53
|
+
expect(defaultDisplaySystemView(['v8', 'v7'], [v7a, v7b])).toBe('v7')
|
|
54
|
+
expect(defaultDisplaySystemView(['v8', 'v7'], [v8a, v8b])).toBe('v8')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('ignores outlets without a POS version, and defaults to v8 when none has one', () => {
|
|
58
|
+
expect(defaultDisplaySystemView(['v8', 'v7'], [v7a, fresh])).toBe('v7')
|
|
59
|
+
expect(defaultDisplaySystemView(['v8', 'v7'], [fresh])).toBe('v8')
|
|
60
|
+
expect(defaultDisplaySystemView(['v8', 'v7'], [])).toBe('v8')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('follows the selected outlet in a mixed business', () => {
|
|
64
|
+
expect(defaultDisplaySystemView(['v8', 'v7'], [v7a, v8a], 'v7a')).toBe('v7')
|
|
65
|
+
expect(defaultDisplaySystemView(['v8', 'v7'], [v7a, v8a], 'v8a')).toBe('v8')
|
|
66
|
+
expect(defaultDisplaySystemView(['v8', 'v7'], [v7a, v8a, fresh], 'fresh')).toBe('v8')
|
|
67
|
+
expect(defaultDisplaySystemView(['v8', 'v7'], [v7a, v8a])).toBe('v8')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('uses the only view a user can open', () => {
|
|
71
|
+
expect(defaultDisplaySystemView(['v7'], [v8a])).toBe('v7')
|
|
72
|
+
expect(defaultDisplaySystemView(['v8'], [v7a])).toBe('v8')
|
|
73
|
+
expect(defaultDisplaySystemView([], [v7a])).toBeUndefined()
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
describe('requested Display System view', () => {
|
|
78
|
+
it('reads ?view and the brand-app ?tab=legacy-ods link', () => {
|
|
79
|
+
expect(requestedDisplaySystemView({ view: 'v7' })).toBe('v7')
|
|
80
|
+
expect(requestedDisplaySystemView({ view: 'v8' })).toBe('v8')
|
|
81
|
+
expect(requestedDisplaySystemView({ tab: 'legacy-ods' })).toBe('v7')
|
|
82
|
+
expect(requestedDisplaySystemView({ view: 'other' })).toBeUndefined()
|
|
83
|
+
expect(requestedDisplaySystemView({})).toBeUndefined()
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('parses the major version like the backend', () => {
|
|
88
|
+
for (const posVersion of [undefined, null, '', '7.99', 'invalid']) {
|
|
89
|
+
expect(isPosV8(posVersion)).toBe(false)
|
|
90
|
+
}
|
|
91
|
+
expect(isPosV8('8.0.0-beta.1')).toBe(true)
|
|
92
|
+
expect(isPosV8('10')).toBe(true)
|
|
93
|
+
})
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export type DisplaySystemView = 'v8' | 'v7'
|
|
2
|
+
|
|
3
|
+
interface Outlet {
|
|
4
|
+
_id: string
|
|
5
|
+
posVersion?: string | null
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function isPosV8(posVersion: string | null | undefined): boolean {
|
|
9
|
+
if (!posVersion) return false
|
|
10
|
+
return parseInt(posVersion.split('.')[0]) >= 8
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// An outlet that has never reported a POS version says nothing about which
|
|
14
|
+
// system the merchant runs, so it is left out of every choice below.
|
|
15
|
+
const isKnownV7 = (outlet: Outlet) => Boolean(outlet.posVersion) && !isPosV8(outlet.posVersion)
|
|
16
|
+
|
|
17
|
+
export function availableDisplaySystemViews(options: {
|
|
18
|
+
canManageOrderSetting: boolean
|
|
19
|
+
canManageBrandkit: boolean
|
|
20
|
+
outlets: Outlet[]
|
|
21
|
+
hasLegacyOdsConfigs: boolean
|
|
22
|
+
}): DisplaySystemView[] {
|
|
23
|
+
const views: DisplaySystemView[] = options.canManageOrderSetting ? ['v8'] : []
|
|
24
|
+
if (
|
|
25
|
+
options.canManageBrandkit &&
|
|
26
|
+
(options.outlets.some(isKnownV7) || options.hasLegacyOdsConfigs)
|
|
27
|
+
) {
|
|
28
|
+
views.push('v7')
|
|
29
|
+
}
|
|
30
|
+
return views
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function defaultDisplaySystemView(
|
|
34
|
+
available: DisplaySystemView[],
|
|
35
|
+
outlets: Outlet[],
|
|
36
|
+
currentOutletId?: string
|
|
37
|
+
): DisplaySystemView | undefined {
|
|
38
|
+
if (available.length < 2) return available[0]
|
|
39
|
+
const known = outlets.filter((outlet) => outlet.posVersion)
|
|
40
|
+
if (known.length && known.every(isKnownV7)) return 'v7'
|
|
41
|
+
if (known.every((outlet) => isPosV8(outlet.posVersion))) return 'v8'
|
|
42
|
+
const current = outlets.find((outlet) => outlet._id === currentOutletId)
|
|
43
|
+
return current && isKnownV7(current) ? 'v7' : 'v8'
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// `?tab=legacy-ods` is what the brand-app Brandkit link sends.
|
|
47
|
+
export function requestedDisplaySystemView(query: {
|
|
48
|
+
view?: unknown
|
|
49
|
+
tab?: unknown
|
|
50
|
+
}): DisplaySystemView | undefined {
|
|
51
|
+
if (query.view === 'v7' || query.tab === 'legacy-ods') return 'v7'
|
|
52
|
+
if (query.view === 'v8') return 'v8'
|
|
53
|
+
return undefined
|
|
54
|
+
}
|
|
@@ -27,13 +27,16 @@ 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'
|
|
33
34
|
import { useQrOrderingEnabled } from '@/composables/useQrOrderingEnabled'
|
|
34
35
|
|
|
35
36
|
const { t } = useI18n()
|
|
36
|
-
const { currentRestaurant } = useCoreStore()
|
|
37
|
+
const { currentRestaurant, sessionUser } = useCoreStore()
|
|
38
|
+
// Reservations are FeedMe-admin only during the pilot (as the Dine-in switch).
|
|
39
|
+
const isAdmin = computed(() => sessionUser.value?.role?.isAdmin ?? false)
|
|
37
40
|
const menuStore = useMenuStore()
|
|
38
41
|
const { showError } = useSnackbarFunctions()
|
|
39
42
|
const qrEnabled = useQrOrderingEnabled()
|
|
@@ -47,6 +50,7 @@ type OrderSettingMenuItemValue =
|
|
|
47
50
|
// | 'discountRule'
|
|
48
51
|
| 'general'
|
|
49
52
|
| 'queue'
|
|
53
|
+
| 'reservation'
|
|
50
54
|
const selectedOrderSetting = ref<OrderSettingMenuItemValue>('delivery')
|
|
51
55
|
|
|
52
56
|
const settingItem = computed<FmTabProps[]>(() => {
|
|
@@ -63,7 +67,8 @@ const settingItem = computed<FmTabProps[]>(() => {
|
|
|
63
67
|
// { label: t('order.discountRule.title'), value: 'discountRule' },
|
|
64
68
|
{ label: t('order.notifications'), value: 'sms' },
|
|
65
69
|
{ label: t('order.general'), value: 'general' },
|
|
66
|
-
{ label: t('order.queue'), value: 'queue' }
|
|
70
|
+
{ label: t('order.queue'), value: 'queue' },
|
|
71
|
+
...(isAdmin.value ? [{ label: t('order.reservation'), value: 'reservation' }] : [])
|
|
67
72
|
]
|
|
68
73
|
})
|
|
69
74
|
|
|
@@ -97,6 +102,8 @@ const currentComponent = computed(() => {
|
|
|
97
102
|
return GeneralSetting
|
|
98
103
|
case 'queue':
|
|
99
104
|
return QueueSetting
|
|
105
|
+
case 'reservation':
|
|
106
|
+
return ReservationSetting
|
|
100
107
|
default:
|
|
101
108
|
return DeliverySetting
|
|
102
109
|
}
|