@feedmepos/mf-order-setting 0.0.49 → 0.0.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/{KioskDevicesView-Ch_mWJz9.js → KioskDevicesView-u14hzPbE.js} +1 -1
  2. package/dist/KioskDevicesView.vue_vue_type_script_setup_true_lang-DBgRDIoS.js +501 -0
  3. package/dist/{KioskSettingView-CaBhf48e.js → KioskSettingView-DmvtZcV1.js} +1 -1
  4. package/dist/KioskView-M8V91gD5.js +474 -0
  5. package/dist/{OrderSettingsView-CRgoLOD2.js → OrderSettingsView-Bl3LshG3.js} +4 -4
  6. package/dist/{app-CDTAjXj9.js → app-CLewMjcd.js} +67 -19
  7. package/dist/app.js +1 -1
  8. package/dist/{dayjs.min-dI_j30pv.js → dayjs.min-DCTYRWyD.js} +1 -1
  9. package/dist/frontend/mf-order/src/app.d.ts +48 -0
  10. package/dist/frontend/mf-order/src/main.d.ts +48 -0
  11. package/dist/frontend/mf-order/src/modules/kiosk/interface.d.ts +1 -0
  12. package/dist/frontend/mf-order/src/stores/kiosk/index.d.ts +3 -0
  13. package/dist/frontend/mf-order/src/views/kiosk/devices/KioskDevicesView.vue.d.ts +6 -3
  14. package/dist/frontend/mf-order/src/views/order-settings/servicecharge/ServiceChargeRule.vue.d.ts +2 -2
  15. package/dist/frontend/mf-order/tsconfig.app.tsbuildinfo +1 -1
  16. package/dist/{index-B8U5Sawr.js → index-B7LtJeBJ.js} +2 -2
  17. package/dist/{menu.dto-qVeqpSdz.js → menu.dto-Co7iXHNr.js} +6345 -6286
  18. package/dist/package/entity/incoming-order/incoming-order-to-bill.dto.d.ts +104 -12
  19. package/dist/package/entity/incoming-order/incoming-order.dto.d.ts +106 -6
  20. package/dist/package/entity/kiosk/kiosk.do.d.ts +32 -0
  21. package/dist/package/entity/kiosk/kiosk.dto.d.ts +44 -0
  22. package/dist/package/entity/order/order.enum.d.ts +2 -0
  23. package/dist/package/entity/order-platform/external/order/external-order.do.d.ts +62 -0
  24. package/dist/package/entity/order-platform/external/order/external-order.dto.d.ts +178 -0
  25. package/dist/package/entity/order-platform/grabfood/grabfood-menu.do.d.ts +15 -0
  26. package/dist/package/entity/order-platform/grabfood/grabfood-omni.do.d.ts +1834 -15
  27. package/dist/package/entity/order-platform/grabfood/grabfood-order.do.d.ts +13 -13
  28. package/dist/package/entity/order-platform/grabfood/grabfood-settings.do.d.ts +3 -0
  29. package/dist/package/entity/order-platform/grabfood/grabfood-webhook.dto.d.ts +528 -805
  30. package/dist/package/entity/order-platform/grabfood/grabfood.dto.d.ts +8 -5
  31. package/dist/package/entity/order-platform/grabfood/grabfood.enum.d.ts +1 -1
  32. package/dist/package/entity/order-platform/order-platform.dto.d.ts +0 -20
  33. package/dist/package/entity/queue/queue.dto.d.ts +20 -0
  34. package/package.json +1 -1
  35. package/src/locales/en-US.json +12 -0
  36. package/src/locales/ja-JP.json +12 -0
  37. package/src/locales/th-TH.json +12 -0
  38. package/src/locales/zh-CN.json +12 -0
  39. package/src/modules/kiosk/interface.ts +1 -0
  40. package/src/stores/kiosk/mapper.ts +1 -0
  41. package/src/views/kiosk/KioskSummary.vue +37 -31
  42. package/src/views/kiosk/KioskView.vue +4 -1
  43. package/src/views/kiosk/devices/KioskDeviceCard.vue +205 -89
  44. package/src/views/kiosk/devices/KioskDevicesView.vue +133 -17
  45. package/dist/KioskDevicesView.vue_vue_type_script_setup_true_lang-DV2HYd8u.js +0 -306
  46. package/dist/KioskView-BGm-emCw.js +0 -452
@@ -1,104 +1,185 @@
1
1
  <template>
2
- <div class="border fm-corner-radius-lg flex justify-between pr-[1rem] w-11/12">
3
- <FmSideSheet
4
- :header="t('order.deviceDetails')"
5
- dismiss-away
6
- class="w-full"
7
- :maxWidth="700"
8
- @on:clickedAway="closeUpdateDevicePin"
9
- >
10
- <template #side-sheet-button>
11
- <FmListItem
12
- class="flex-1"
13
- :label="device.name"
14
- :sublabel="`${t('order.activatedDate')}: ${dayjs(device.activatedAt).format('DD MMM YYYY HH:mm')}`"
2
+ <div class="border fm-corner-radius-lg p-12 hover:bg-fm-color-neutral-gray-50 transition-colors">
3
+ <!-- Header Row: Type Badge + Slot + Actions -->
4
+ <div class="flex items-center justify-between mb-2">
5
+ <div class="flex items-center gap-2">
6
+ <span
7
+ class="px-2 py-1 fm-corner-radius-md fm-typo-en-body-sm-600 uppercase bg-fm-color-neutral-gray-200 text-fm-color-typo-secondary"
15
8
  >
16
- </FmListItem>
17
- </template>
18
- <div v-if="!showPinSetting" class="py-[1rem] flex flex-col gap-6">
19
- <span class="fm-typo-en-title-sm-600">{{ device.name }}</span>
20
- <div class="w-full border p-[1.5rem] fm-corner-radius-lg flex flex-col gap-2">
21
- <span class="fm-typo-en-title-sm-600 mb-3">App Info</span>
22
- <div class="flex flex-col gap-1 mb-2">
23
- <span class="fm-typo-en-body-lg-400 text-fm-color-typo-secondary">Type</span>
24
- <span class="fm-typo-en-body-lg-600 block">{{ appType }}</span>
25
- </div>
26
- <div class="flex flex-col gap-1 mb-2">
27
- <span class="fm-typo-en-body-lg-400 text-fm-color-typo-secondary">Version</span>
28
- <span class="fm-typo-en-body-lg-600 block">{{
29
- device.deviceAppVersion ?? 'Below 0.0.42'
30
- }}</span>
31
- </div>
32
- <div class="flex flex-col gap-1 mb-2">
33
- <span class="fm-typo-en-body-lg-400 text-fm-color-typo-secondary">OTA Version</span>
34
- <span class="fm-typo-en-body-lg-600 block">{{
35
- device.deviceAppOTAVersion ?? 'Not available'
36
- }}</span>
37
- </div>
38
- <div class="flex flex-col gap-1 mb-2">
39
- <span class="fm-typo-en-body-lg-400 text-fm-color-typo-secondary">PIN Code</span>
40
- <span class="fm-typo-en-body-lg-600 block">{{ isPinUpdate ? 'Yes' : 'No' }}</span>
41
- </div>
42
- <FmButton
43
- class="w-auto"
44
- variant="primary"
45
- :label="device?.pinInfo ? 'Reset Device PIN' : 'Set Device PIN'"
46
- @click="showUpdateDevicePin"
47
- />
48
- </div>
49
- <div
50
- v-for="config in configs.filter((c) => c[1])"
51
- class="w-full border p-[1.5rem] fm-corner-radius-lg flex flex-col gap-2"
9
+ {{ isTabletApp ? 'Tablet' : 'Kiosk' }}
10
+ </span>
11
+ <span
12
+ v-if="isTabletApp && device.slotInfo"
13
+ class="fm-typo-en-body-md-600 text-fm-color-typo-primary"
52
14
  >
53
- <span class="fm-typo-en-title-sm-600" v-if="config[1]">{{ config[0] }}</span>
54
- <div
55
- class="flex flex-col gap-1"
56
- v-if="config[1]"
57
- v-for="configEntry in Object.entries(config[1])"
58
- >
59
- <span class="fm-typo-en-body-lg-400 text-fm-color-typo-secondary">{{
60
- configEntry[0]
61
- }}</span>
62
- <span class="fm-typo-en-body-lg-600 block">{{
63
- configEntry[1] == '' ? '-' : configEntry[1]
64
- }}</span>
65
- </div>
66
- </div>
15
+ {{ device.slotInfo }}
16
+ </span>
67
17
  </div>
68
- <div v-else>
69
- <div class="py-[1rem] flex flex-col gap-6 text-center items-center">
70
- <div class="flex flex-col gap-1 mb-2">
71
- <span class="fm-typo-en-body-lg-400 text-fm-color-typo-secondary">{{ pinText }}</span>
72
- <span class="mt-5 block">
73
- <FmPinField v-if="!isPinEntered" :length="4" @complete="(val) => pinEntered(val)" />
74
- <FmPinField v-if="isPinEntered" :length="4" @complete="(val) => pinEntered(val)" />
75
- </span>
18
+ <div class="flex items-center gap-1">
19
+ <FmSideSheet
20
+ :header="t('order.deviceDetails')"
21
+ dismiss-away
22
+ :maxWidth="700"
23
+ @on:clickedAway="closeUpdateDevicePin"
24
+ >
25
+ <template #side-sheet-button>
26
+ <FmButton variant="tertiary" icon="more_vert" />
27
+ </template>
28
+ <div v-if="!showPinSetting" class="py-[1rem] flex flex-col gap-6">
29
+ <span class="fm-typo-en-title-sm-600">{{ device.name }}</span>
30
+ <div class="w-full border p-[1.5rem] fm-corner-radius-lg flex flex-col gap-2">
31
+ <span class="fm-typo-en-title-sm-600 mb-3">App Info</span>
32
+ <div class="flex flex-col gap-1 mb-2">
33
+ <span class="fm-typo-en-body-lg-400 text-fm-color-typo-secondary">Type</span>
34
+ <span class="fm-typo-en-body-lg-600 block">{{ appType }}</span>
35
+ </div>
36
+ <div class="flex flex-col gap-1 mb-2">
37
+ <span class="fm-typo-en-body-lg-400 text-fm-color-typo-secondary">Machine ID</span>
38
+ <span class="fm-typo-en-body-lg-600 block font-mono text-sm break-all">{{
39
+ device.machineId
40
+ }}</span>
41
+ </div>
42
+ <div class="flex flex-col gap-1 mb-2">
43
+ <span class="fm-typo-en-body-lg-400 text-fm-color-typo-secondary">Version</span>
44
+ <span class="fm-typo-en-body-lg-600 block">{{
45
+ device.deviceAppVersion ?? 'Below 0.0.42'
46
+ }}</span>
47
+ </div>
48
+ <div class="flex flex-col gap-1 mb-2">
49
+ <span class="fm-typo-en-body-lg-400 text-fm-color-typo-secondary">OTA Version</span>
50
+ <span class="fm-typo-en-body-lg-600 block">{{
51
+ device.deviceAppOTAVersion ?? 'Not available'
52
+ }}</span>
53
+ </div>
54
+ <div class="flex flex-col gap-1 mb-2">
55
+ <span class="fm-typo-en-body-lg-400 text-fm-color-typo-secondary">PIN Code</span>
56
+ <span class="fm-typo-en-body-lg-600 block">{{ isPinUpdate ? 'Yes' : 'No' }}</span>
57
+ </div>
58
+ <FmButton
59
+ class="w-auto"
60
+ variant="primary"
61
+ :label="device?.pinInfo ? 'Reset Device PIN' : 'Set Device PIN'"
62
+ @click="showUpdateDevicePin"
63
+ />
64
+ </div>
65
+ <div
66
+ v-for="config in configs.filter((c) => c[1])"
67
+ :key="config[0]"
68
+ class="w-full border p-[1.5rem] fm-corner-radius-lg flex flex-col gap-2"
69
+ >
70
+ <span class="fm-typo-en-title-sm-600" v-if="config[1]">{{ config[0] }}</span>
71
+ <div
72
+ class="flex flex-col gap-1"
73
+ v-if="config[1]"
74
+ v-for="configEntry in Object.entries(config[1])"
75
+ :key="configEntry[0]"
76
+ >
77
+ <span class="fm-typo-en-body-lg-400 text-fm-color-typo-secondary">{{
78
+ configEntry[0]
79
+ }}</span>
80
+ <span class="fm-typo-en-body-lg-600 block">{{
81
+ configEntry[1] == '' ? '-' : configEntry[1]
82
+ }}</span>
83
+ </div>
84
+ </div>
76
85
  </div>
77
- <FmButton
78
- class="w-1/3"
79
- variant="secondary"
80
- label="Cancel reset PIN"
81
- @click="closeUpdateDevicePin"
82
- />
83
- </div>
86
+ <div v-else>
87
+ <div class="py-[1rem] flex flex-col gap-6 text-center items-center">
88
+ <div class="flex flex-col gap-1 mb-2">
89
+ <span class="fm-typo-en-body-lg-400 text-fm-color-typo-secondary">{{
90
+ pinText
91
+ }}</span>
92
+ <span class="mt-5 block">
93
+ <FmPinField
94
+ v-if="!isPinEntered"
95
+ :length="4"
96
+ @complete="(val) => pinEntered(val)"
97
+ />
98
+ <FmPinField
99
+ v-if="isPinEntered"
100
+ :length="4"
101
+ @complete="(val) => pinEntered(val)"
102
+ />
103
+ </span>
104
+ </div>
105
+ <FmButton
106
+ class="w-1/3"
107
+ variant="secondary"
108
+ label="Cancel reset PIN"
109
+ @click="closeUpdateDevicePin"
110
+ />
111
+ </div>
112
+ </div>
113
+ </FmSideSheet>
114
+ <FmButton
115
+ variant="plain"
116
+ icon="link_off"
117
+ class="text-fm-color-system-error-300"
118
+ @click="openUnbindDialog"
119
+ />
84
120
  </div>
85
- </FmSideSheet>
86
- <FmButton
87
- variant="plain"
88
- append-icon="link_off"
89
- class="text-fm-color-system-error-300 align-middle my-auto"
90
- @click="openUnbindDialog"
91
- />
121
+ </div>
122
+
123
+ <!-- Device Name (truncated for long names) -->
124
+ <div
125
+ class="fm-typo-en-body-md-600 text-fm-color-typo-primary mb-1 overflow-hidden text-ellipsis whitespace-nowrap"
126
+ :title="device.name"
127
+ >
128
+ {{ device.name }}
129
+ </div>
130
+
131
+ <!-- Machine ID Row (clickable to copy) -->
132
+ <div
133
+ class="flex items-center gap-1 mb-2 cursor-pointer group"
134
+ @click="copyMachineId"
135
+ :title="t('order.clickToCopy')"
136
+ >
137
+ <span class="fm-typo-en-body-sm-400 text-fm-color-typo-tertiary font-mono">
138
+ {{ truncatedMachineId }}
139
+ </span>
140
+ <FmIcon
141
+ name="content_copy"
142
+ class="text-fm-color-typo-tertiary opacity-0 group-hover:opacity-100 transition-opacity"
143
+ />
144
+ </div>
145
+
146
+ <!-- Info Row: Version, OTA, PIN, Last Startup -->
147
+ <div
148
+ class="flex items-center gap-3 flex-wrap fm-typo-en-body-sm-400 text-fm-color-typo-secondary"
149
+ >
150
+ <span class="flex items-center gap-1">
151
+ <FmIcon name="smartphone" />
152
+ v{{ device.deviceAppVersion ?? '?' }}
153
+ </span>
154
+ <span v-if="device.deviceAppOTAVersion" class="flex items-center gap-1">
155
+ <FmIcon name="system_update" />
156
+ {{ device.deviceAppOTAVersion }}
157
+ </span>
158
+ <span class="flex items-center gap-1">
159
+ <FmIcon name="lock" />
160
+ {{ isPinUpdate ? t('order.pinSet') : t('order.noPin') }}
161
+ </span>
162
+ <span
163
+ v-if="device.lastStartup"
164
+ class="flex items-center gap-1"
165
+ :title="formatFullDate(device.lastStartup)"
166
+ >
167
+ <FmIcon name="schedule" />
168
+ {{ formatRelativeTime(device.lastStartup) }}
169
+ </span>
170
+ </div>
92
171
  </div>
93
172
  </template>
94
173
  <script setup lang="ts">
95
174
  import type { MfKioskDevice } from '@/modules/kiosk/interface'
96
- import { type SnackbarPosition, useDialog, useSnackbar } from '@feedmepos/ui-library'
97
- import KioskUnbindConfirm from './KioskUnbindConfirm.vue'
98
- import { computed, onUnmounted, ref } from 'vue'
99
175
  import { useKioskStore } from '@/stores/kiosk'
100
- import dayjs from 'dayjs'
101
176
  import { useI18n } from '@feedmepos/mf-common'
177
+ import { useDialog, useSnackbar, type SnackbarPosition } from '@feedmepos/ui-library'
178
+ import dayjs from 'dayjs'
179
+ import relativeTime from 'dayjs/plugin/relativeTime'
180
+ import { computed, ref } from 'vue'
181
+ import KioskUnbindConfirm from './KioskUnbindConfirm.vue'
182
+ dayjs.extend(relativeTime)
102
183
 
103
184
  const { t } = useI18n()
104
185
 
@@ -121,6 +202,41 @@ const appType = computed(() => {
121
202
  return props.device?.deviceAppType === 'TABLET_APP' ? 'Tablet App' : 'Kiosk App'
122
203
  })
123
204
 
205
+ const isTabletApp = computed(() => {
206
+ return props.device?.deviceAppType === 'TABLET_APP'
207
+ })
208
+
209
+ const truncatedMachineId = computed(() => {
210
+ const id = props.device.machineId
211
+ if (!id || id.length <= 16) return id
212
+ return `${id.slice(0, 8)}...${id.slice(-4)}`
213
+ })
214
+
215
+ async function copyMachineId() {
216
+ try {
217
+ await navigator.clipboard.writeText(props.device.machineId)
218
+ Snackbar.open({
219
+ type: 'success',
220
+ message: t('order.machineIdCopied'),
221
+ position: 'bottom' as SnackbarPosition
222
+ })
223
+ } catch {
224
+ Snackbar.open({
225
+ type: 'error',
226
+ message: t('order.copyFailed'),
227
+ position: 'bottom' as SnackbarPosition
228
+ })
229
+ }
230
+ }
231
+
232
+ function formatRelativeTime(date: Date): string {
233
+ return dayjs(date).fromNow()
234
+ }
235
+
236
+ function formatFullDate(date: Date): string {
237
+ return dayjs(date).format('DD MMM YYYY HH:mm:ss')
238
+ }
239
+
124
240
  // function showDeviceDetails() {
125
241
  // Dialog.open({
126
242
  // title: props.device.name,
@@ -1,29 +1,145 @@
1
1
  <template>
2
- <div>
2
+ <div>
3
+ <div class="flex flex-col gap-4">
4
+ <div class="flex items-center justify-between">
3
5
  <div class="flex flex-col gap-2">
4
- <div class="flex flex-col gap-2">
5
- <span class="fm-typo-en-title-sm-600 block"> {{ t('order.kioskDisplay') }} </span>
6
- <span class="fm-typo-en-body-md-400 block"> {{ t('order.kioskDisplayDescription') }} </span>
7
- </div>
8
- <div class="flex flex-col gap-6">
9
- <KioskDeviceCard v-for="device in devices" :key="device.machineId" :device="device"></KioskDeviceCard>
10
- <FmButton variant="plain" class="w-[10rem] border border-fm-color-primary fm-corner-radius-lg"
11
- :label="t('order.bindDevice')" size="md" prepend-icon="link" @click="requestOtp" />
12
- </div>
6
+ <span class="fm-typo-en-title-sm-600 block"> {{ t('order.kioskDisplay') }} </span>
7
+ <span class="fm-typo-en-body-md-400 block"> {{ t('order.kioskDisplayDescription') }} </span>
13
8
  </div>
9
+ <FmButton
10
+ variant="primary"
11
+ :label="t('order.bindDevice')"
12
+ size="md"
13
+ prepend-icon="link"
14
+ @click="$emit('requestOtp')"
15
+ />
16
+ </div>
17
+
18
+ <!-- Search and Filter Bar -->
19
+ <div class="flex items-center gap-3 flex-wrap">
20
+ <FmSearch
21
+ v-model:model-value="searchQuery"
22
+ class="flex-1 min-w-[200px] max-w-[400px]"
23
+ :placeholder="t('order.searchDevices')"
24
+ />
25
+ <FmMenu>
26
+ <template #menu-button>
27
+ <FmButton
28
+ variant="tertiary"
29
+ prepend-icon="filter_list"
30
+ :label="activeFilterLabel"
31
+ class="fm-typo-en-body-md-600"
32
+ />
33
+ </template>
34
+ <FmMenuItem
35
+ :model-value="filterType === 'all'"
36
+ :label="t('order.allTypes')"
37
+ @click="filterType = 'all'"
38
+ />
39
+ <FmMenuItem
40
+ :model-value="filterType === 'tablet'"
41
+ :label="t('order.tabletOnly')"
42
+ @click="filterType = 'tablet'"
43
+ />
44
+ <FmMenuItem
45
+ :model-value="filterType === 'kiosk'"
46
+ :label="t('order.kioskOnly')"
47
+ @click="filterType = 'kiosk'"
48
+ />
49
+ </FmMenu>
50
+ <span class="fm-typo-en-body-sm-400 text-fm-color-typo-tertiary">
51
+ {{ t('order.showingDevices', { count: filteredDevices.length, total: devices.length }) }}
52
+ </span>
53
+ </div>
54
+
55
+ <!-- Device Cards -->
56
+ <div class="grid grid-cols-2 gap-2 py-8">
57
+ <KioskDeviceCard
58
+ v-for="device in filteredDevices"
59
+ :key="device.machineId"
60
+ :device="device"
61
+ />
62
+ <div
63
+ v-if="filteredDevices.length === 0 && devices.length > 0"
64
+ class="text-center py-8 text-fm-color-typo-tertiary fm-typo-en-body-md-400 col-span-2"
65
+ >
66
+ {{ t('order.noDevicesMatch') }}
67
+ </div>
68
+ <div
69
+ v-if="devices.length === 0"
70
+ class="text-center py-8 text-fm-color-typo-tertiary fm-typo-en-body-md-400 col-span-2"
71
+ >
72
+ {{ t('order.noDevicesBound') }}
73
+ </div>
74
+ </div>
14
75
  </div>
76
+ </div>
15
77
  </template>
16
78
  <script setup lang="ts">
17
- import KioskDeviceCard from "./KioskDeviceCard.vue";
18
- import type { MfKioskDevice } from "@/modules/kiosk/interface";
79
+ import type { MfKioskDevice } from '@/modules/kiosk/interface'
19
80
  import { useI18n } from '@feedmepos/mf-common'
81
+ import { computed, ref } from 'vue'
82
+ import KioskDeviceCard from './KioskDeviceCard.vue'
20
83
 
21
- const { t } = useI18n();
84
+ const { t } = useI18n()
22
85
 
23
86
  interface Props {
24
- devices: MfKioskDevice[],
25
- requestOtp: () => Promise<void>;
87
+ devices: MfKioskDevice[]
26
88
  }
27
89
 
28
- const props = defineProps<Props>();
29
- </script>
90
+ const props = defineProps<Props>()
91
+ defineEmits<{
92
+ (e: 'requestOtp'): void
93
+ }>()
94
+
95
+ const searchQuery = ref('')
96
+ const filterType = ref<'all' | 'tablet' | 'kiosk'>('all')
97
+
98
+ const activeFilterLabel = computed(() => {
99
+ switch (filterType.value) {
100
+ case 'tablet':
101
+ return t('order.tabletOnly')
102
+ case 'kiosk':
103
+ return t('order.kioskOnly')
104
+ default:
105
+ return t('order.allTypes')
106
+ }
107
+ })
108
+
109
+ const filteredDevices = computed(() => {
110
+ let result = props.devices
111
+
112
+ // Apply type filter
113
+ if (filterType.value === 'tablet') {
114
+ result = result.filter((d) => d.deviceAppType === 'TABLET_APP')
115
+ } else if (filterType.value === 'kiosk') {
116
+ result = result.filter((d) => d.deviceAppType !== 'TABLET_APP')
117
+ }
118
+
119
+ // Apply search filter
120
+ const query = searchQuery.value.toLowerCase().trim()
121
+ 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
+ return name.includes(query) || machineId.includes(query) || slot.includes(query)
127
+ })
128
+ }
129
+
130
+ // Sort tablets by slot, kiosks by name
131
+ return result.sort((a, b) => {
132
+ // Tablets first
133
+ if (a.deviceAppType === 'TABLET_APP' && b.deviceAppType !== 'TABLET_APP') return -1
134
+ if (a.deviceAppType !== 'TABLET_APP' && b.deviceAppType === 'TABLET_APP') return 1
135
+
136
+ // For tablets, sort by slot
137
+ if (a.deviceAppType === 'TABLET_APP' && b.deviceAppType === 'TABLET_APP') {
138
+ return (a.slotInfo || '').localeCompare(b.slotInfo || '')
139
+ }
140
+
141
+ // For kiosks, sort by name
142
+ return (a.name || '').localeCompare(b.name || '')
143
+ })
144
+ })
145
+ </script>