@feedmepos/mf-order-setting 0.0.73-dev → 0.0.74-dev
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/{KioskDevicesView-Dztw-Q35.js → KioskDevicesView-CW7nDXi2.js} +1 -1
- package/dist/KioskDevicesView.vue_vue_type_script_setup_true_lang-CPDkls2i.js +762 -0
- package/dist/{KioskSettingView-BUHmoxyO.js → KioskSettingView-DErXeFIt.js} +3 -3
- package/dist/KioskView-d1YjttbD.js +362 -0
- package/dist/{OrderSettingsView-C5PK8CKZ.js → OrderSettingsView-asfw4cYT.js} +13 -10
- package/dist/app-DrWO2wkv.js +20750 -0
- package/dist/app.js +3 -3
- package/dist/{dayjs.min-B3nOmXcs.js → dayjs.min-Fu6ejRAr.js} +1 -1
- package/dist/frontend/mf-order/src/api/index.d.ts +3 -1
- package/dist/frontend/mf-order/src/app.d.ts +160 -152
- package/dist/frontend/mf-order/src/composables/useKioskPermission.d.ts +11 -0
- package/dist/frontend/mf-order/src/composables/useOrderSettingPermission.d.ts +13 -0
- package/dist/frontend/mf-order/src/main.d.ts +160 -152
- package/dist/frontend/mf-order/src/stores/order-setting/index.d.ts +3 -3
- package/dist/frontend/mf-order/src/views/kiosk/KioskStatePanel.vue.d.ts +19 -0
- package/dist/frontend/mf-order/src/views/kiosk/devices/KioskDevicesView.vue.d.ts +7 -1
- package/dist/frontend/mf-order/tsconfig.app.tsbuildinfo +1 -1
- package/dist/{index-B4XxBqiV.js → index-DEKeP24S.js} +7 -7
- package/dist/{index-6PQJ07gD.js → index-DJLCZHNZ.js} +2 -2
- package/dist/{lineman-settings.dto-DcYJFu0U.js → lineman-settings.dto-DS4tgxBJ.js} +18974 -18109
- package/dist/package/entity/delivery/gateway/pandago.dto.d.ts +6 -6
- package/dist/package/entity/order-setting/order-setting.do.d.ts +4 -4
- package/dist/package/entity/order-setting/order-setting.dto.d.ts +8 -8
- package/dist/package/entity/order-setting/sms/sms.do.d.ts +2 -2
- package/dist/package/entity/order-setting/sms/sms.dto.d.ts +2 -2
- package/package.json +3 -2
- package/src/api/index.ts +5 -2
- package/src/composables/useKioskPermission.ts +41 -0
- package/src/composables/useOrderSettingPermission.ts +34 -0
- package/src/locales/en-US.json +429 -421
- package/src/main.ts +9 -6
- package/src/router/routes.ts +34 -5
- package/src/views/kiosk/KioskStatePanel.vue +31 -0
- package/src/views/kiosk/KioskView.vue +100 -62
- package/src/views/kiosk/devices/KioskDeviceCard.vue +4 -0
- package/src/views/kiosk/devices/KioskDevicesView.vue +42 -9
- package/src/views/order-settings/OrderSettingsView.vue +8 -5
- package/src/views/order-settings/delivery/delivery.ts +1 -1
- package/vite.config.ts +5 -2
- package/dist/KioskDevicesView.vue_vue_type_script_setup_true_lang-Ol419NZh.js +0 -707
- package/dist/KioskView-B8j77zMA.js +0 -343
- package/dist/app-C2_s0fkK.js +0 -18792
package/src/main.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import './assets/main.css'
|
|
2
2
|
|
|
3
|
-
import { createApp } from 'vue'
|
|
3
|
+
import { createApp, type Plugin } from 'vue'
|
|
4
4
|
import { createPinia } from 'pinia'
|
|
5
5
|
import Entry from './Entry.vue'
|
|
6
6
|
import { router } from './router'
|
|
@@ -30,15 +30,18 @@ function initI18n(): I18n {
|
|
|
30
30
|
|
|
31
31
|
const app = createApp(Entry)
|
|
32
32
|
|
|
33
|
+
// mf-common's createFeedMeI18n returns vue-i18n's I18n instance, which is a valid
|
|
34
|
+
// Vue plugin at runtime but doesn't structurally match vue's tightened Plugin type
|
|
35
|
+
// after the mf-common bump — cast at the install site.
|
|
36
|
+
app.use(initI18n() as unknown as Plugin)
|
|
37
|
+
app.use(createPinia())
|
|
38
|
+
app.use(router)
|
|
39
|
+
|
|
40
|
+
|
|
33
41
|
app.use(FeedMeUI, {
|
|
34
42
|
locale: detectLocale(),
|
|
35
43
|
})
|
|
36
44
|
|
|
37
|
-
app.use(createPinia() as any)
|
|
38
|
-
app.use(router)
|
|
39
|
-
app.use(initI18n() as any)
|
|
40
|
-
|
|
41
|
-
|
|
42
45
|
const CoreStore = useCoreStore();
|
|
43
46
|
|
|
44
47
|
initialize(import.meta.env.VITE_AUTH_CLIENT_ID);
|
package/src/router/routes.ts
CHANGED
|
@@ -1,26 +1,55 @@
|
|
|
1
|
-
import { type RouteRecordRaw } from 'vue-router'
|
|
1
|
+
import { type RouteRecordRaw, type RouteMeta } from 'vue-router'
|
|
2
|
+
import { defineAsyncComponent } from 'vue'
|
|
3
|
+
import { Permission, type CaslPermission } from '@feedmepos/hrm-permission'
|
|
4
|
+
import { withPermission } from '@feedmepos/hrm-permission/components'
|
|
5
|
+
import { kioskPermission } from '@/composables/useKioskPermission'
|
|
6
|
+
import { orderSettingPermission } from '@/composables/useOrderSettingPermission'
|
|
2
7
|
import { PortalRoute } from '@/utils/constants/route';
|
|
3
8
|
|
|
9
|
+
/** Wrap CASL permissions into a route meta the withPermission HOC validates. */
|
|
10
|
+
const permissionMeta = (permissions: CaslPermission[]): RouteMeta =>
|
|
11
|
+
({
|
|
12
|
+
validationManifest: { requiredCaslPermissions: permissions }
|
|
13
|
+
}) as unknown as RouteMeta;
|
|
14
|
+
|
|
15
|
+
const manageDevices = kioskPermission(Permission.Action.manage, 'deviceManagement');
|
|
16
|
+
const bindDevices = kioskPermission(Permission.Action.manage, 'deviceBinding');
|
|
17
|
+
const manageOrderSetting = orderSettingPermission(Permission.Action.manage);
|
|
18
|
+
|
|
4
19
|
const routes: RouteRecordRaw[] = [
|
|
5
20
|
{
|
|
21
|
+
// Main Kiosk view hosts both the device-binding and management tabs, so
|
|
22
|
+
// allow access via either subject (coverSubject = OR fallback).
|
|
6
23
|
path: PortalRoute.KioskRoute,
|
|
7
24
|
name: 'Kiosk',
|
|
8
|
-
component: () => import('@/views/kiosk/KioskView.vue')
|
|
25
|
+
component: withPermission(defineAsyncComponent(() => import('@/views/kiosk/KioskView.vue'))),
|
|
26
|
+
meta: permissionMeta([{ ...manageDevices, coverSubject: bindDevices.subject }]),
|
|
9
27
|
},
|
|
10
28
|
{
|
|
11
29
|
path: PortalRoute.KioskDeviceRoute,
|
|
12
30
|
name: 'Kiosk Device',
|
|
13
|
-
component: (
|
|
31
|
+
component: withPermission(
|
|
32
|
+
defineAsyncComponent(() => import('@/views/kiosk/devices/KioskDevicesView.vue')),
|
|
33
|
+
),
|
|
34
|
+
meta: permissionMeta([manageDevices]),
|
|
14
35
|
},
|
|
15
36
|
{
|
|
37
|
+
// The Kiosk "Settings" tab is order settings, so it is governed by the
|
|
38
|
+
// order-setting subject (a kiosk permission is still required to reach /kiosk).
|
|
16
39
|
path: PortalRoute.KioskOrderSettingRoute,
|
|
17
40
|
name: 'Kiosk Order Setting',
|
|
18
|
-
component: (
|
|
41
|
+
component: withPermission(
|
|
42
|
+
defineAsyncComponent(() => import('@/views/kiosk/settings/KioskSettingView.vue')),
|
|
43
|
+
),
|
|
44
|
+
meta: permissionMeta([manageOrderSetting]),
|
|
19
45
|
},
|
|
20
46
|
{
|
|
21
47
|
path: PortalRoute.OrderSettingView,
|
|
22
48
|
name: 'Order Setting View',
|
|
23
|
-
component: (
|
|
49
|
+
component: withPermission(
|
|
50
|
+
defineAsyncComponent(() => import('@/views/order-settings/OrderSettingsView.vue')),
|
|
51
|
+
),
|
|
52
|
+
meta: permissionMeta([manageOrderSetting]),
|
|
24
53
|
},
|
|
25
54
|
];
|
|
26
55
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex flex-col items-center justify-center gap-3 py-16 text-center col-span-2">
|
|
3
|
+
<div
|
|
4
|
+
class="flex items-center justify-center w-16 h-16 rounded-full bg-fm-color-surface-secondary"
|
|
5
|
+
>
|
|
6
|
+
<FmIcon :name="icon" size="xl" class="text-fm-color-typo-tertiary" />
|
|
7
|
+
</div>
|
|
8
|
+
<span class="fm-typo-en-title-sm-600 block">{{ title }}</span>
|
|
9
|
+
<span class="fm-typo-en-body-md-400 block text-fm-color-typo-tertiary max-w-[360px]">
|
|
10
|
+
{{ message }}
|
|
11
|
+
</span>
|
|
12
|
+
<div v-if="$slots.default" class="pt-2">
|
|
13
|
+
<slot />
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
</template>
|
|
17
|
+
|
|
18
|
+
<script setup lang="ts">
|
|
19
|
+
// Lightweight, self-contained state panel for the kiosk surface (e.g. permission
|
|
20
|
+
// denied / load error). Kept local because @feedmepos/hrm-permission only exposes
|
|
21
|
+
// its NoPermission illustration via the route-level withPermission HOC, not as a
|
|
22
|
+
// standalone component we can render on an API 403.
|
|
23
|
+
withDefaults(
|
|
24
|
+
defineProps<{
|
|
25
|
+
title: string
|
|
26
|
+
message: string
|
|
27
|
+
icon?: string
|
|
28
|
+
}>(),
|
|
29
|
+
{ icon: 'info' }
|
|
30
|
+
)
|
|
31
|
+
</script>
|
|
@@ -1,58 +1,63 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="py-[1.5rem]">
|
|
3
|
-
<FmPageHead :title="t('order.kiosk')" />
|
|
4
|
-
<div class="px-[1.5rem]">
|
|
5
|
-
<FmTabs v-model:model-value="selectedMenuItem" :items="menuItems"></FmTabs>
|
|
6
|
-
<div class="pt-[1.5rem]">
|
|
7
|
-
<div class="flex flex-col gap-10">
|
|
8
|
-
<div class="flex flex-col gap-2">
|
|
9
|
-
<span class="fm-typo-en-title-sm-600"> {{ t('order.activateLocation') }} </span>
|
|
10
|
-
<span class="fm-typo-en-title-sm-400"> {{ t('order.selectRestaurant') }}: </span>
|
|
11
|
-
<FmSelect
|
|
12
|
-
class="w-1/2"
|
|
13
|
-
v-model="currentSelect"
|
|
14
|
-
:items="restaurantItems"
|
|
15
|
-
:placeholder="t('order.selectItem')"
|
|
16
|
-
@update:model-value="selectRestaurant"
|
|
17
|
-
/>
|
|
18
|
-
</div>
|
|
19
|
-
<KioskDevicesView
|
|
20
|
-
v-if="selectedMenuItem == 'device'"
|
|
21
|
-
:devices="devices"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
:
|
|
28
|
-
|
|
29
|
-
:
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
</
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
1
|
+
<template>
|
|
2
|
+
<div class="py-[1.5rem]">
|
|
3
|
+
<FmPageHead :title="t('order.kiosk')" />
|
|
4
|
+
<div class="px-[1.5rem]">
|
|
5
|
+
<FmTabs v-model:model-value="selectedMenuItem" :items="menuItems"></FmTabs>
|
|
6
|
+
<div class="pt-[1.5rem]">
|
|
7
|
+
<div class="flex flex-col gap-10">
|
|
8
|
+
<div class="flex flex-col gap-2">
|
|
9
|
+
<span class="fm-typo-en-title-sm-600"> {{ t('order.activateLocation') }} </span>
|
|
10
|
+
<span class="fm-typo-en-title-sm-400"> {{ t('order.selectRestaurant') }}: </span>
|
|
11
|
+
<FmSelect
|
|
12
|
+
class="w-1/2"
|
|
13
|
+
v-model="currentSelect"
|
|
14
|
+
:items="restaurantItems"
|
|
15
|
+
:placeholder="t('order.selectItem')"
|
|
16
|
+
@update:model-value="selectRestaurant"
|
|
17
|
+
/>
|
|
18
|
+
</div>
|
|
19
|
+
<KioskDevicesView
|
|
20
|
+
v-if="selectedMenuItem == 'device'"
|
|
21
|
+
:devices="devices"
|
|
22
|
+
:load-error="devicesLoadError"
|
|
23
|
+
@request-otp="requestOtp"
|
|
24
|
+
@retry="loadDevices"
|
|
25
|
+
/>
|
|
26
|
+
<KioskSettingView
|
|
27
|
+
:key="`KioskSettingView-${currentRestaurantId}`"
|
|
28
|
+
v-if="selectedMenuItem == 'setting' && canManageOrderSetting"
|
|
29
|
+
:restaurant-id="currentRestaurantId"
|
|
30
|
+
@update-kiosk-order-setting="updateKioskOrderSetting"
|
|
31
|
+
:form="OrderSettingStore.state.kiosk ?? defaultKioskOrderSetting"
|
|
32
|
+
/>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</template>
|
|
38
|
+
|
|
39
|
+
<script setup lang="ts">
|
|
40
|
+
import KioskDevicesView from '@/views/kiosk/devices/KioskDevicesView.vue'
|
|
41
|
+
import {
|
|
42
|
+
useDialog,
|
|
43
|
+
useSnackbar,
|
|
44
|
+
type FmTabProps,
|
|
45
|
+
type ISelectItem
|
|
46
|
+
} from '@feedmepos/ui-library'
|
|
47
|
+
import { ref, computed, defineAsyncComponent, watch, onMounted } from 'vue'
|
|
46
48
|
import { useKioskStore } from '@/stores/kiosk'
|
|
47
49
|
import type { MfKioskDevice } from '@/modules/kiosk/interface'
|
|
48
50
|
import KioskOtpDialog from '@/views/kiosk/devices/KioskOtpDialog.vue'
|
|
51
|
+
import { SvcCompanyApiError } from '@/api'
|
|
49
52
|
import { useCoreStore, useI18n } from '@feedmepos/mf-common'
|
|
50
53
|
import { useOrderSettings } from '@/stores/order-setting'
|
|
51
54
|
import type { MfKioskOrderSetting } from '@/modules/order-setting/kiosk/interface'
|
|
52
55
|
import { defaultKioskOrderSetting } from '@/stores/order-setting/mapper'
|
|
56
|
+
import { useOrderSettingPermission } from '@/composables/useOrderSettingPermission'
|
|
53
57
|
import { capitalCase } from 'change-case'
|
|
54
58
|
|
|
55
59
|
const { t } = useI18n()
|
|
60
|
+
const { canManageOrderSetting } = useOrderSettingPermission()
|
|
56
61
|
|
|
57
62
|
type KioskMenuItemValue = 'device' | 'setting'
|
|
58
63
|
|
|
@@ -63,12 +68,15 @@ const KioskSettingView = defineAsyncComponent(
|
|
|
63
68
|
const selectedMenuItem = ref<KioskMenuItemValue>('device')
|
|
64
69
|
const KioskStore = useKioskStore()
|
|
65
70
|
const OrderSettingStore = useOrderSettings()
|
|
66
|
-
const Dialog = useDialog()
|
|
67
|
-
const { currentRestaurant, restaurants, changeRestaurant } = useCoreStore()
|
|
68
|
-
const SnackBar = useSnackbar()
|
|
71
|
+
const Dialog = useDialog()
|
|
72
|
+
const { currentRestaurant, restaurants, changeRestaurant } = useCoreStore()
|
|
73
|
+
const SnackBar = useSnackbar()
|
|
69
74
|
|
|
70
75
|
const currentSelect = ref<string | undefined>(currentRestaurant.value?._id ?? undefined)
|
|
71
76
|
|
|
77
|
+
/** Drives the denied/error panel inside the device tab. 'forbidden' === HTTP 403. */
|
|
78
|
+
const devicesLoadError = ref<'forbidden' | 'error' | null>(null)
|
|
79
|
+
|
|
72
80
|
const restaurantItems = computed<ISelectItem[]>(() => {
|
|
73
81
|
return restaurants.value.map((r) => ({
|
|
74
82
|
label: r.profile.name,
|
|
@@ -78,7 +86,7 @@ const restaurantItems = computed<ISelectItem[]>(() => {
|
|
|
78
86
|
|
|
79
87
|
const currentRestaurantId = ref<string>(currentRestaurant.value?._id ?? '')
|
|
80
88
|
|
|
81
|
-
async function selectRestaurant(v: string) {
|
|
89
|
+
async function selectRestaurant(v: string) {
|
|
82
90
|
const restaurant = restaurants.value.find((r) => r._id == v)
|
|
83
91
|
await changeRestaurant(restaurant)
|
|
84
92
|
await loadRestaurantData()
|
|
@@ -87,27 +95,57 @@ async function selectRestaurant(v: string) {
|
|
|
87
95
|
}
|
|
88
96
|
|
|
89
97
|
async function loadRestaurantData() {
|
|
90
|
-
|
|
91
|
-
|
|
98
|
+
// Order settings are only needed for the (permission-gated) Settings tab. Skip the
|
|
99
|
+
// fetch when the user can't manage them, and never let its failure block the Device
|
|
100
|
+
// tab — the order-settings endpoint is now HRM-guarded and 403s without the subject.
|
|
101
|
+
if (canManageOrderSetting.value) {
|
|
102
|
+
try {
|
|
103
|
+
await OrderSettingStore.getSetting()
|
|
104
|
+
} catch {
|
|
105
|
+
// Settings tab surfaces its own state; don't block device loading.
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
await loadDevices()
|
|
92
109
|
}
|
|
93
110
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
value
|
|
111
|
+
// Loads the device list, mapping a 403 to the in-tab "access denied" panel and any
|
|
112
|
+
// other failure to a retryable error panel (instead of silently showing "no devices").
|
|
113
|
+
async function loadDevices() {
|
|
114
|
+
devicesLoadError.value = null
|
|
115
|
+
try {
|
|
116
|
+
await KioskStore.getDevices()
|
|
117
|
+
} catch (e) {
|
|
118
|
+
devicesLoadError.value =
|
|
119
|
+
e instanceof SvcCompanyApiError && e.status === 403 ? 'forbidden' : 'error'
|
|
102
120
|
}
|
|
103
|
-
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const menuItems = computed<FmTabProps[]>(() => {
|
|
124
|
+
const items: FmTabProps[] = [{ label: t('order.deviceBinding'), value: 'device' }]
|
|
125
|
+
// The Settings tab is order settings — only show it to users who can manage them.
|
|
126
|
+
if (canManageOrderSetting.value) {
|
|
127
|
+
items.push({ label: t('order.settings'), value: 'setting' })
|
|
128
|
+
}
|
|
129
|
+
return items
|
|
130
|
+
})
|
|
104
131
|
|
|
105
132
|
const devices = computed<MfKioskDevice[]>(() => {
|
|
106
133
|
return KioskStore.state.devices
|
|
107
134
|
})
|
|
108
135
|
|
|
109
136
|
async function requestOtp() {
|
|
110
|
-
|
|
137
|
+
try {
|
|
138
|
+
await KioskStore.requestOtp()
|
|
139
|
+
} catch (e) {
|
|
140
|
+
SnackBar.open({
|
|
141
|
+
title:
|
|
142
|
+
e instanceof SvcCompanyApiError && e.status === 403
|
|
143
|
+
? t('order.bindDevicePermissionDenied')
|
|
144
|
+
: t('order.bindDeviceFailed'),
|
|
145
|
+
type: 'error'
|
|
146
|
+
})
|
|
147
|
+
return
|
|
148
|
+
}
|
|
111
149
|
Dialog.open({
|
|
112
150
|
title: capitalCase(t('order.bindDevice')),
|
|
113
151
|
contentComponent: KioskOtpDialog,
|
|
@@ -116,7 +154,7 @@ async function requestOtp() {
|
|
|
116
154
|
}
|
|
117
155
|
}).onClose(async () => {
|
|
118
156
|
// Refresh devices after dialog closes (in case a device was bound)
|
|
119
|
-
await
|
|
157
|
+
await loadDevices()
|
|
120
158
|
})
|
|
121
159
|
}
|
|
122
160
|
|
|
@@ -80,6 +80,7 @@
|
|
|
80
80
|
</div>
|
|
81
81
|
|
|
82
82
|
<FmButton
|
|
83
|
+
v-if="canManageDevices"
|
|
83
84
|
class="w-full"
|
|
84
85
|
variant="primary"
|
|
85
86
|
:label="device?.pinInfo ? t('order.resetDevicePin') : t('order.setDevicePin')"
|
|
@@ -139,6 +140,7 @@
|
|
|
139
140
|
</div>
|
|
140
141
|
</FmSideSheet>
|
|
141
142
|
<FmButton
|
|
143
|
+
v-if="canBindDevice"
|
|
142
144
|
variant="plain"
|
|
143
145
|
icon="link_off"
|
|
144
146
|
class="text-fm-color-system-error-300"
|
|
@@ -213,9 +215,11 @@ import dayjs from 'dayjs'
|
|
|
213
215
|
import relativeTime from 'dayjs/plugin/relativeTime'
|
|
214
216
|
import { computed, ref, watch } from 'vue'
|
|
215
217
|
import KioskUnbindConfirm from './KioskUnbindConfirm.vue'
|
|
218
|
+
import { useKioskPermission } from '@/composables/useKioskPermission'
|
|
216
219
|
dayjs.extend(relativeTime)
|
|
217
220
|
|
|
218
221
|
const { t } = useI18n()
|
|
222
|
+
const { canManageDevices, canBindDevice } = useKioskPermission()
|
|
219
223
|
|
|
220
224
|
const Dialog = useDialog()
|
|
221
225
|
const Snackbar = useSnackbar()
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
<span class="fm-typo-en-body-md-400 block"> {{ t('order.kioskDisplayDescription') }} </span>
|
|
8
8
|
</div>
|
|
9
9
|
<FmButton
|
|
10
|
+
v-if="canBindDevice"
|
|
10
11
|
variant="primary"
|
|
11
12
|
:label="t('order.bindDevice')"
|
|
12
13
|
size="md"
|
|
@@ -15,6 +16,31 @@
|
|
|
15
16
|
/>
|
|
16
17
|
</div>
|
|
17
18
|
|
|
19
|
+
<!-- Permission denied (e.g. the user reached this tab via the bind-only -->
|
|
20
|
+
<!-- coverSubject, but GET /devices requires the device-management subject). -->
|
|
21
|
+
<KioskStatePanel
|
|
22
|
+
v-if="loadError === 'forbidden'"
|
|
23
|
+
icon="lock"
|
|
24
|
+
:title="t('order.deviceAccessDeniedTitle')"
|
|
25
|
+
:message="t('order.deviceAccessDeniedMessage')"
|
|
26
|
+
/>
|
|
27
|
+
|
|
28
|
+
<!-- Any other load failure -->
|
|
29
|
+
<KioskStatePanel
|
|
30
|
+
v-else-if="loadError === 'error'"
|
|
31
|
+
icon="error"
|
|
32
|
+
:title="t('order.deviceLoadErrorTitle')"
|
|
33
|
+
:message="t('order.deviceLoadErrorMessage')"
|
|
34
|
+
>
|
|
35
|
+
<FmButton
|
|
36
|
+
variant="secondary"
|
|
37
|
+
:label="t('order.retry')"
|
|
38
|
+
prepend-icon="refresh"
|
|
39
|
+
@click="$emit('retry')"
|
|
40
|
+
/>
|
|
41
|
+
</KioskStatePanel>
|
|
42
|
+
|
|
43
|
+
<template v-else>
|
|
18
44
|
<!-- Search and Filter Bar -->
|
|
19
45
|
<div class="flex items-center gap-3 flex-wrap">
|
|
20
46
|
<FmSearch
|
|
@@ -72,6 +98,7 @@
|
|
|
72
98
|
{{ t('order.noDevicesBound') }}
|
|
73
99
|
</div>
|
|
74
100
|
</div>
|
|
101
|
+
</template>
|
|
75
102
|
</div>
|
|
76
103
|
</div>
|
|
77
104
|
</template>
|
|
@@ -80,16 +107,22 @@ import type { MfKioskDevice } from '@/modules/kiosk/interface'
|
|
|
80
107
|
import { useI18n } from '@feedmepos/mf-common'
|
|
81
108
|
import { computed, ref } from 'vue'
|
|
82
109
|
import KioskDeviceCard from './KioskDeviceCard.vue'
|
|
110
|
+
import KioskStatePanel from '@/views/kiosk/KioskStatePanel.vue'
|
|
111
|
+
import { useKioskPermission } from '@/composables/useKioskPermission'
|
|
83
112
|
|
|
84
113
|
const { t } = useI18n()
|
|
114
|
+
const { canBindDevice } = useKioskPermission()
|
|
85
115
|
|
|
86
116
|
interface Props {
|
|
87
117
|
devices: MfKioskDevice[]
|
|
118
|
+
/** Why the device list failed to load, if at all. 'forbidden' === HTTP 403. */
|
|
119
|
+
loadError?: 'forbidden' | 'error' | null
|
|
88
120
|
}
|
|
89
121
|
|
|
90
|
-
const props = defineProps<Props>()
|
|
122
|
+
const props = withDefaults(defineProps<Props>(), { loadError: null })
|
|
91
123
|
defineEmits<{
|
|
92
124
|
(e: 'requestOtp'): void
|
|
125
|
+
(e: 'retry'): void
|
|
93
126
|
}>()
|
|
94
127
|
|
|
95
128
|
const searchQuery = ref('')
|
|
@@ -119,14 +152,14 @@ const filteredDevices = computed(() => {
|
|
|
119
152
|
// Apply search filter
|
|
120
153
|
const query = searchQuery.value.toLowerCase().trim()
|
|
121
154
|
if (query) {
|
|
122
|
-
result = result.filter((d) => {
|
|
123
|
-
const name = d.name?.toLowerCase() || ''
|
|
124
|
-
const machineId = d.machineId?.toLowerCase() || ''
|
|
125
|
-
const slot = d.slotInfo?.toLowerCase() || ''
|
|
126
|
-
const remark = d.remarkInfo?.toLowerCase() || ''
|
|
127
|
-
return name.includes(query) || machineId.includes(query) || slot.includes(query) || remark.includes(query)
|
|
128
|
-
})
|
|
129
|
-
}
|
|
155
|
+
result = result.filter((d) => {
|
|
156
|
+
const name = d.name?.toLowerCase() || ''
|
|
157
|
+
const machineId = d.machineId?.toLowerCase() || ''
|
|
158
|
+
const slot = d.slotInfo?.toLowerCase() || ''
|
|
159
|
+
const remark = d.remarkInfo?.toLowerCase() || ''
|
|
160
|
+
return name.includes(query) || machineId.includes(query) || slot.includes(query) || remark.includes(query)
|
|
161
|
+
})
|
|
162
|
+
}
|
|
130
163
|
|
|
131
164
|
// Sort tablets by slot, kiosks by name
|
|
132
165
|
return result.sort((a, b) => {
|
|
@@ -108,11 +108,14 @@ onMounted(async () => {
|
|
|
108
108
|
}
|
|
109
109
|
})
|
|
110
110
|
|
|
111
|
-
watch(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
111
|
+
watch(
|
|
112
|
+
() => currentRestaurant.value,
|
|
113
|
+
async (newRestaurant) => {
|
|
114
|
+
if (newRestaurant?._id) {
|
|
115
|
+
await menuStore.loadCatalogOptions(newRestaurant._id)
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
)
|
|
116
119
|
|
|
117
120
|
watch(
|
|
118
121
|
() => menuStore.catalogOptionsError,
|
|
@@ -66,7 +66,7 @@ export function useDelivery() {
|
|
|
66
66
|
const thSupported = [
|
|
67
67
|
AUTO_INTEGRATION_CLIENT.grabfood,
|
|
68
68
|
AUTO_INTEGRATION_CLIENT.external,
|
|
69
|
-
AUTO_INTEGRATION_CLIENT.lineman
|
|
69
|
+
// AUTO_INTEGRATION_CLIENT.lineman
|
|
70
70
|
]
|
|
71
71
|
const pkSupported = [AUTO_INTEGRATION_CLIENT.foodpanda, AUTO_INTEGRATION_CLIENT.external]
|
|
72
72
|
const idSupported = [
|
package/vite.config.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { fileURLToPath, URL } from 'node:url'
|
|
2
|
-
import { defineConfig, loadEnv } from 'vite'
|
|
2
|
+
import { defineConfig, loadEnv, type UserConfig } from 'vite'
|
|
3
3
|
import vue from '@vitejs/plugin-vue'
|
|
4
4
|
import { fmmfVitePresent } from '@feedmepos/mf-common/vite-preset'
|
|
5
5
|
|
|
@@ -58,5 +58,8 @@ export default defineConfig((env) => {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
// return config
|
|
61
|
-
|
|
61
|
+
// fmmfVitePresent / @vitejs/plugin-vue types drifted vs vite's UserConfig after
|
|
62
|
+
// the mf-common ecosystem bump; the runtime shape is correct — cast to satisfy
|
|
63
|
+
// defineConfig's overload.
|
|
64
|
+
return fmmfVitePresent(config, env) as unknown as UserConfig
|
|
62
65
|
})
|