@feedmepos/mf-order-setting 0.0.76 → 0.0.77
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-H7stMDFu.js → KioskDevicesView-Dgs5_UoK.js} +1 -1
- package/dist/{KioskDevicesView.vue_vue_type_script_setup_true_lang-DgpV4oKE.js → KioskDevicesView.vue_vue_type_script_setup_true_lang-B4DXPgAV.js} +3 -3
- package/dist/KioskSettingView-BUcPNVTc.js +681 -0
- package/dist/{KioskView-Cxcc1UL1.js → KioskView-DelVMMj4.js} +137 -127
- package/dist/{OrderSettingsView-Ds2L_rbu.js → OrderSettingsView-DE2U7vqW.js} +9232 -8262
- package/dist/{app-BDlPjU4y.js → app-BIvt4NBq.js} +1476 -1302
- package/dist/app.js +1 -1
- package/dist/{dayjs.min-OJf1trOq.js → dayjs.min-B58_jBg3.js} +1 -1
- package/dist/frontend/mf-order/src/api/index.d.ts +3 -1
- package/dist/frontend/mf-order/src/api/remoteOrder/index.d.ts +65 -2
- package/dist/frontend/mf-order/src/app.d.ts +174 -0
- package/dist/frontend/mf-order/src/composables/useInternalUser.d.ts +3 -0
- package/dist/frontend/mf-order/src/main.d.ts +174 -0
- package/dist/frontend/mf-order/src/modules/order-setting/kiosk/interface.d.ts +4 -0
- package/dist/frontend/mf-order/src/stores/order-setting/index.d.ts +15 -3
- package/dist/frontend/mf-order/src/views/order-settings/delivery/integrated-delivery/AdditionalStores.vue.d.ts +16 -0
- package/dist/frontend/mf-order/src/views/order-settings/delivery/integrated-delivery/ExternalSetting.vue.d.ts +2 -2
- package/dist/frontend/mf-order/src/views/order-settings/delivery/integrated-delivery/FoodpandaAdditionalStores.vue.d.ts +10 -0
- package/dist/frontend/mf-order/src/views/order-settings/delivery/integrated-delivery/IntegratedDelivery.vue.d.ts +15 -4
- package/dist/frontend/mf-order/src/views/order-settings/delivery/integrated-delivery/IntegrationIdentityDetails.vue.d.ts +10 -0
- package/dist/frontend/mf-order/src/views/order-settings/delivery/integrated-delivery/ShopeefoodAdditionalStores.vue.d.ts +10 -0
- package/dist/frontend/mf-order/tsconfig.app.tsbuildinfo +1 -1
- package/dist/index-CLS-FSH-.js +762 -0
- package/dist/{index-gGOpbnY3.js → index-CahsmFL1.js} +1 -1
- package/dist/{lineman-settings.dto-Cny_fF4n.js → lineman-settings.dto-DBDThIb0.js} +8999 -8874
- package/dist/package/entity/delivery/delivery.enum.d.ts +1 -1
- package/dist/package/entity/incoming-order/incoming-order.do.d.ts +4 -4
- package/dist/package/entity/kiosk/marketing/marketing.dto.d.ts +11 -0
- package/dist/package/entity/order-platform/base-integration.dto.d.ts +179 -0
- package/dist/package/entity/order-platform/deliveroo/deliveroo-dto.d.ts +2 -2
- package/dist/package/entity/order-platform/deliveroo/deliveroo-setting.do.d.ts +2 -2
- package/dist/package/entity/order-platform/external/order/external-order.dto.d.ts +2 -2
- package/dist/package/entity/order-platform/external/setting/external-setting.do.d.ts +4 -4
- package/dist/package/entity/order-platform/external/setting/external-setting.dto.d.ts +4 -4
- package/dist/package/entity/order-platform/foodpanda/foodpanda-menu.dto.d.ts +1 -1
- package/dist/package/entity/order-platform/foodpanda/foodpanda-settings.do.d.ts +105 -4
- package/dist/package/entity/order-platform/foodpanda/foodpanda-settings.dto.d.ts +30 -11
- package/dist/package/entity/order-platform/gofood/gofood-settings.do.d.ts +2 -2
- package/dist/package/entity/order-platform/gofood/gofood-settings.dto.d.ts +2 -2
- package/dist/package/entity/order-platform/grabfood/grabfood-order.do.d.ts +10 -10
- package/dist/package/entity/order-platform/grabfood/grabfood-settings.do.d.ts +134 -6
- package/dist/package/entity/order-platform/grabfood/grabfood.dto.d.ts +42 -20
- package/dist/package/entity/order-platform/lineman/lineman-settings.do.d.ts +2 -2
- package/dist/package/entity/order-platform/lineman/lineman-settings.dto.d.ts +2 -2
- package/dist/package/entity/order-platform/shopeefood/shopeefood-settings.do.d.ts +102 -4
- package/dist/package/entity/order-platform/shopeefood/shopeefood-settings.dto.d.ts +66 -5
- package/dist/package/entity/order-platform/tng/tng-settings.do.d.ts +2 -2
- package/dist/package/entity/order-platform/tng/tng-settings.dto.d.ts +4 -4
- package/dist/package/entity/order-setting/dine-in/dine-in.do.d.ts +2 -2
- package/dist/package/entity/order-setting/dine-in/dine-in.dto.d.ts +2 -2
- package/dist/package/entity/order-setting/kiosk/kiosk.do.d.ts +6 -0
- package/dist/package/entity/order-setting/kiosk/kiosk.dto.d.ts +6 -0
- package/dist/package/entity/order-setting/order-setting.do.d.ts +14 -4
- package/dist/package/entity/order-setting/order-setting.dto.d.ts +28 -8
- package/dist/package/entity/restaurant/restaurant.dto.d.ts +10 -0
- package/dist/package/entity/websocket/websocket.dto.d.ts +24 -0
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/api/index.ts +8 -2
- package/src/api/remoteOrder/index.ts +200 -4
- package/src/composables/useInternalUser.ts +14 -0
- package/src/locales/en-US.json +43 -0
- package/src/locales/ja-JP.json +43 -0
- package/src/locales/th-TH.json +90 -45
- package/src/locales/zh-CN.json +43 -0
- package/src/modules/order-setting/kiosk/interface.ts +29 -19
- package/src/stores/order-setting/mapper.ts +65 -55
- package/src/views/kiosk/settings/KioskSettingView.vue +33 -0
- package/src/views/order-settings/delivery/DeliverySetting.vue +0 -1
- package/src/views/order-settings/delivery/delivery.data.ts +3 -0
- package/src/views/order-settings/delivery/integrated-delivery/AdditionalStores.vue +472 -0
- package/src/views/order-settings/delivery/integrated-delivery/FoodpandaAdditionalStores.vue +314 -0
- package/src/views/order-settings/delivery/integrated-delivery/FoodpandaSetting.vue +27 -8
- package/src/views/order-settings/delivery/integrated-delivery/GrabfoodSetting.vue +31 -12
- package/src/views/order-settings/delivery/integrated-delivery/IntegratedDelivery.vue +23 -11
- package/src/views/order-settings/delivery/integrated-delivery/IntegrationIdentityDetails.vue +144 -0
- package/src/views/order-settings/delivery/integrated-delivery/ShopeefoodAdditionalStores.vue +335 -0
- package/src/views/order-settings/delivery/integrated-delivery/ShopeefoodSetting.vue +29 -8
- package/src/views/order-settings/queue/QueueSetting.vue +1 -1
- package/dist/KioskSettingView-fF6k9s_q.js +0 -658
- package/dist/index-Bid9lO2x.js +0 -665
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, reactive, computed, onMounted, watch } from 'vue'
|
|
3
|
+
import Day from 'dayjs'
|
|
4
|
+
import { useI18n } from '@feedmepos/mf-common'
|
|
5
|
+
import { useMenuStore } from '@/stores/menu/menu'
|
|
6
|
+
import { remoteOrderApi, type GrabfoodStoreView } from '@/api/remoteOrder'
|
|
7
|
+
import { useLoading } from '@/composables/loading'
|
|
8
|
+
import { useSnackbarFunctions } from '@/components/snackbar'
|
|
9
|
+
import { useDialog } from '@feedmepos/ui-library'
|
|
10
|
+
import IntegrationIdentityDetails from './IntegrationIdentityDetails.vue'
|
|
11
|
+
import { SvcCompanyApiError } from '@/api'
|
|
12
|
+
import { useInternalUser } from '@/composables/useInternalUser'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Multi-store: list of the ADDITIONAL GrabFood stores under this restaurant. The
|
|
16
|
+
* primary store is shown in the main GrabFood settings card above, not repeated
|
|
17
|
+
* here. Add / edit (catalog + enable) / remove all operate on additional stores;
|
|
18
|
+
* Add / Edit open a nested side sheet.
|
|
19
|
+
*/
|
|
20
|
+
const props = defineProps<{ restaurantId: string; primaryEnabled: boolean }>()
|
|
21
|
+
const emits = defineEmits<{ (event: 'syncMenu', merchantID?: string): void }>()
|
|
22
|
+
|
|
23
|
+
const { t } = useI18n()
|
|
24
|
+
const menuStore = useMenuStore()
|
|
25
|
+
const { startAsyncCall, startAsyncCallWithErr } = useLoading()
|
|
26
|
+
const { showSuccess, showError } = useSnackbarFunctions()
|
|
27
|
+
const Dialog = useDialog()
|
|
28
|
+
const REMOTE_CLOSE_FAILED_CODE = 'GRABFOOD_REMOTE_CLOSE_FAILED'
|
|
29
|
+
const { isInternal } = useInternalUser()
|
|
30
|
+
|
|
31
|
+
const stores = ref<GrabfoodStoreView[]>([])
|
|
32
|
+
const editing = ref<null | {
|
|
33
|
+
mode: 'new' | 'edit'
|
|
34
|
+
integrationId: string | null
|
|
35
|
+
routeId: string
|
|
36
|
+
merchantID: string
|
|
37
|
+
}>(null)
|
|
38
|
+
const isSaving = ref(false)
|
|
39
|
+
const form = reactive<{ merchantID: string; catalogId: string | null; enable: boolean }>({
|
|
40
|
+
merchantID: '',
|
|
41
|
+
catalogId: null,
|
|
42
|
+
enable: true
|
|
43
|
+
})
|
|
44
|
+
// Snapshot of the store's saved values, captured when the Edit sheet opens. Lets us
|
|
45
|
+
// detect unsaved edits so Activate (Self-Serve) requires a Save first.
|
|
46
|
+
const originalForm = reactive<{ merchantID: string; catalogId: string | null; enable: boolean }>({
|
|
47
|
+
merchantID: '',
|
|
48
|
+
catalogId: null,
|
|
49
|
+
enable: true
|
|
50
|
+
})
|
|
51
|
+
const isDirty = computed(
|
|
52
|
+
() =>
|
|
53
|
+
editing.value?.mode === 'edit' &&
|
|
54
|
+
(form.merchantID !== originalForm.merchantID ||
|
|
55
|
+
form.catalogId !== originalForm.catalogId ||
|
|
56
|
+
form.enable !== originalForm.enable)
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
const catalogOptions = computed(() => menuStore.catalogOptions ?? [])
|
|
60
|
+
function catalogLabel(catalogId?: string | null) {
|
|
61
|
+
if (!catalogId) return '—'
|
|
62
|
+
return catalogOptions.value.find((catalog) => catalog.value === catalogId)?.label ?? catalogId
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Dates arrive as ISO strings; render them the same way the main GrabFood card
|
|
66
|
+
// (IntegratedDelivery) does. Null → em dash.
|
|
67
|
+
function formatDate(d?: string | null) {
|
|
68
|
+
return d ? Day(d).format('DD/MM/YYYY hh:mm a') : '—'
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// The primary store is already shown in the main GrabFood settings card above, so
|
|
72
|
+
// this section lists only the ADDITIONAL stores. (save()'s uniqueness check still
|
|
73
|
+
// uses the full `stores` list so a new merchantID can't collide with the primary.)
|
|
74
|
+
const additionalStores = computed(() => stores.value.filter((s) => !s.isPrimary))
|
|
75
|
+
const showSyncAction = computed(
|
|
76
|
+
() => !props.primaryEnabled && additionalStores.value.some((store) => store.enable === true)
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
function syncMenu() {
|
|
80
|
+
const enabledStore = additionalStores.value.find((store) => store.enable === true)
|
|
81
|
+
emits('syncMenu', enabledStore?.merchantID)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// FmSideSheet emits update:model-value(false) when dismissed (backdrop / X) — treat
|
|
85
|
+
// that as Cancel so `editing` resets and the form closes.
|
|
86
|
+
function onSheetToggle(open: boolean) {
|
|
87
|
+
if (!open) cancelEdit()
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function load() {
|
|
91
|
+
if (!props.restaurantId) return
|
|
92
|
+
await startAsyncCallWithErr(async () => {
|
|
93
|
+
stores.value = await remoteOrderApi.integratedDelivery.listGrabfoodStores(props.restaurantId)
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
onMounted(load)
|
|
97
|
+
watch(
|
|
98
|
+
() => props.restaurantId,
|
|
99
|
+
() => load()
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
function openAdd() {
|
|
103
|
+
editing.value = { mode: 'new', integrationId: null, routeId: '', merchantID: '' }
|
|
104
|
+
form.merchantID = ''
|
|
105
|
+
form.catalogId = null
|
|
106
|
+
form.enable = true
|
|
107
|
+
}
|
|
108
|
+
function openEdit(s: GrabfoodStoreView) {
|
|
109
|
+
editing.value = {
|
|
110
|
+
mode: 'edit',
|
|
111
|
+
integrationId: s.integrationId,
|
|
112
|
+
routeId: s.routeId,
|
|
113
|
+
merchantID: s.merchantID
|
|
114
|
+
}
|
|
115
|
+
form.merchantID = s.merchantID
|
|
116
|
+
form.catalogId = s.catalogId ?? null
|
|
117
|
+
form.enable = s.enable ?? false
|
|
118
|
+
originalForm.merchantID = form.merchantID
|
|
119
|
+
originalForm.catalogId = form.catalogId
|
|
120
|
+
originalForm.enable = form.enable
|
|
121
|
+
}
|
|
122
|
+
function cancelEdit() {
|
|
123
|
+
editing.value = null
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function save() {
|
|
127
|
+
if (isSaving.value) return
|
|
128
|
+
|
|
129
|
+
const merchantID = form.merchantID.trim()
|
|
130
|
+
if (!merchantID) {
|
|
131
|
+
showError(t('order.merchantIdRequired'))
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
// Client-side uniqueness (backend is the source of truth): a collision is any
|
|
135
|
+
// OTHER store already using this merchantID. On edit, the store being edited is
|
|
136
|
+
// excluded — keeping its id (or self) is allowed, but renaming onto another
|
|
137
|
+
// store's id is blocked.
|
|
138
|
+
const editingId = editing.value?.mode === 'edit' ? editing.value.merchantID : null
|
|
139
|
+
if (stores.value.some((s) => s.merchantID === merchantID && s.merchantID !== editingId)) {
|
|
140
|
+
showError(t('order.merchantIdAlreadyExists'))
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
isSaving.value = true
|
|
144
|
+
try {
|
|
145
|
+
await startAsyncCallWithErr(async () => {
|
|
146
|
+
if (editing.value?.mode === 'new') {
|
|
147
|
+
await remoteOrderApi.integratedDelivery.addGrabfoodStore(props.restaurantId, {
|
|
148
|
+
merchantID,
|
|
149
|
+
catalogId: form.catalogId,
|
|
150
|
+
enable: form.enable
|
|
151
|
+
})
|
|
152
|
+
showSuccess(t('order.storeAdded'))
|
|
153
|
+
} else if (editing.value) {
|
|
154
|
+
// routeId is the client-side copy of this store's required integrationId.
|
|
155
|
+
const payload =
|
|
156
|
+
form.enable === false &&
|
|
157
|
+
merchantID === originalForm.merchantID &&
|
|
158
|
+
form.catalogId === originalForm.catalogId
|
|
159
|
+
? { merchantID, enable: false }
|
|
160
|
+
: { merchantID, catalogId: form.catalogId, enable: form.enable }
|
|
161
|
+
await remoteOrderApi.integratedDelivery.updateGrabfoodStore(
|
|
162
|
+
props.restaurantId,
|
|
163
|
+
editing.value.routeId,
|
|
164
|
+
payload
|
|
165
|
+
)
|
|
166
|
+
showSuccess(t('order.storeUpdated'))
|
|
167
|
+
}
|
|
168
|
+
editing.value = null
|
|
169
|
+
await load()
|
|
170
|
+
})
|
|
171
|
+
} finally {
|
|
172
|
+
isSaving.value = false
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Remove flow: a center confirm dialog (useDialog) gates the destructive call, so
|
|
177
|
+
// the row's buttons no longer shuffle in place.
|
|
178
|
+
function promptRemove(s: GrabfoodStoreView) {
|
|
179
|
+
if (s.enable) {
|
|
180
|
+
showError(t('order.disableBeforeRemove'))
|
|
181
|
+
return
|
|
182
|
+
}
|
|
183
|
+
Dialog.open({
|
|
184
|
+
title: t('order.removeStoreConfirm', { merchantID: s.merchantID }),
|
|
185
|
+
primaryActions: { text: t('order.removeStore'), variant: 'destructive', close: true },
|
|
186
|
+
secondaryActions: { text: t('order.cancel'), variant: 'tertiary', close: true }
|
|
187
|
+
}).onPrimary(() => {
|
|
188
|
+
void confirmRemove(s)
|
|
189
|
+
})
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function promptForceRemove(s: GrabfoodStoreView) {
|
|
193
|
+
Dialog.open({
|
|
194
|
+
title: t('order.forceRemoveStoreConfirm', { merchantID: s.merchantID }),
|
|
195
|
+
message: t('order.forceRemoveStoreWarning'),
|
|
196
|
+
dismissAway: false,
|
|
197
|
+
primaryActions: { text: t('order.forceRemoveStore'), variant: 'destructive', close: true },
|
|
198
|
+
secondaryActions: { text: t('order.cancel'), variant: 'tertiary', close: true }
|
|
199
|
+
}).onPrimary(() => {
|
|
200
|
+
void confirmRemove(s, true)
|
|
201
|
+
})
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function confirmRemove(s: GrabfoodStoreView, forceLocal = false) {
|
|
205
|
+
try {
|
|
206
|
+
await startAsyncCall(async () => {
|
|
207
|
+
await remoteOrderApi.integratedDelivery.removeGrabfoodStore(
|
|
208
|
+
props.restaurantId,
|
|
209
|
+
s.routeId,
|
|
210
|
+
forceLocal
|
|
211
|
+
)
|
|
212
|
+
showSuccess(t('order.storeRemoved'))
|
|
213
|
+
await load()
|
|
214
|
+
})
|
|
215
|
+
} catch (error) {
|
|
216
|
+
if (
|
|
217
|
+
!forceLocal &&
|
|
218
|
+
error instanceof SvcCompanyApiError &&
|
|
219
|
+
error.code === REMOTE_CLOSE_FAILED_CODE
|
|
220
|
+
) {
|
|
221
|
+
promptForceRemove(s)
|
|
222
|
+
return
|
|
223
|
+
}
|
|
224
|
+
showError(`${error}`)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Self-Serve Activation, opened from a store's Edit side sheet. Each additional
|
|
229
|
+
// store activates with its persisted FeedMe-generated integrationId; the backend
|
|
230
|
+
// sends that value in GrabFood's API-only partnerMerchantID field.
|
|
231
|
+
async function activate() {
|
|
232
|
+
const routeId = editing.value?.routeId
|
|
233
|
+
if (!routeId) return
|
|
234
|
+
await startAsyncCallWithErr(async () => {
|
|
235
|
+
const url = await remoteOrderApi.integratedDelivery.startSelfServeFlow(
|
|
236
|
+
props.restaurantId,
|
|
237
|
+
routeId
|
|
238
|
+
)
|
|
239
|
+
if (url) {
|
|
240
|
+
window.open(url, '_blank')
|
|
241
|
+
editing.value = null
|
|
242
|
+
await load()
|
|
243
|
+
} else showError('Failed to get GrabFood activation URL')
|
|
244
|
+
})
|
|
245
|
+
}
|
|
246
|
+
</script>
|
|
247
|
+
|
|
248
|
+
<template>
|
|
249
|
+
<div class="mt-10 store-section">
|
|
250
|
+
<div class="flex flex-row items-center mb-1">
|
|
251
|
+
<div class="flex-grow fm-typo-en-title-sm-600">{{ t('order.additionalStores') }}</div>
|
|
252
|
+
<div class="store-card__actions">
|
|
253
|
+
<FmButton
|
|
254
|
+
v-if="showSyncAction"
|
|
255
|
+
variant="primary"
|
|
256
|
+
:label="t('order.syncMenu')"
|
|
257
|
+
@click="syncMenu"
|
|
258
|
+
/>
|
|
259
|
+
<FmButton
|
|
260
|
+
v-if="isInternal"
|
|
261
|
+
variant="secondary"
|
|
262
|
+
:label="t('order.addStore')"
|
|
263
|
+
@click="openAdd"
|
|
264
|
+
/>
|
|
265
|
+
</div>
|
|
266
|
+
</div>
|
|
267
|
+
<div class="text-fm-color-neutral-gray-400 fm-typo-cn-body-md-400 mb-3">
|
|
268
|
+
{{ t('order.storesHint') }}
|
|
269
|
+
</div>
|
|
270
|
+
|
|
271
|
+
<div
|
|
272
|
+
v-if="additionalStores.length"
|
|
273
|
+
class="text-fm-color-neutral-gray-400 fm-typo-cn-body-md-400 mb-3"
|
|
274
|
+
>
|
|
275
|
+
{{ t('order.syncAllStoresHint') }}
|
|
276
|
+
</div>
|
|
277
|
+
|
|
278
|
+
<FmCard v-for="s in additionalStores" :key="s.routeId" variant="outlined" class="store-card">
|
|
279
|
+
<div class="store-card__top">
|
|
280
|
+
<div class="store-card__title fm-typo-en-title-sm-600">{{ s.merchantID }}</div>
|
|
281
|
+
<div class="store-card__actions">
|
|
282
|
+
<FmButton variant="secondary" :label="t('order.edit')" @click="openEdit(s)" />
|
|
283
|
+
<FmButton
|
|
284
|
+
v-if="isInternal"
|
|
285
|
+
variant="secondary"
|
|
286
|
+
:label="t('order.removeStore')"
|
|
287
|
+
@click="promptRemove(s)"
|
|
288
|
+
/>
|
|
289
|
+
</div>
|
|
290
|
+
</div>
|
|
291
|
+
<div
|
|
292
|
+
v-if="isInternal && s.enable"
|
|
293
|
+
class="text-fm-color-neutral-gray-400 fm-typo-cn-body-md-400 mb-3"
|
|
294
|
+
>
|
|
295
|
+
{{ t('order.disableBeforeRemove') }}
|
|
296
|
+
</div>
|
|
297
|
+
<div class="store-card__fields fm-typo-cn-body-md-400">
|
|
298
|
+
<div class="store-field">
|
|
299
|
+
<span class="store-field__label text-fm-color-neutral-gray-400">{{
|
|
300
|
+
t('order.enabled')
|
|
301
|
+
}}</span>
|
|
302
|
+
<span>{{ s.enable ? '✓' : '✕' }}</span>
|
|
303
|
+
</div>
|
|
304
|
+
<div class="store-field">
|
|
305
|
+
<span class="store-field__label text-fm-color-neutral-gray-400">{{
|
|
306
|
+
t('order.catalog')
|
|
307
|
+
}}</span>
|
|
308
|
+
<span>{{ catalogLabel(s.catalogId) }}</span>
|
|
309
|
+
</div>
|
|
310
|
+
<div class="store-field">
|
|
311
|
+
<span class="store-field__label text-fm-color-neutral-gray-400">{{
|
|
312
|
+
t('order.status')
|
|
313
|
+
}}</span>
|
|
314
|
+
<span>{{ s.status ?? '—' }}</span>
|
|
315
|
+
</div>
|
|
316
|
+
<div class="store-field">
|
|
317
|
+
<span class="store-field__label text-fm-color-neutral-gray-400">{{
|
|
318
|
+
t('order.menuSyncError')
|
|
319
|
+
}}</span>
|
|
320
|
+
<span :class="{ 'text-fm-color-typo-error': s.menuSyncError }">{{
|
|
321
|
+
s.menuSyncError ?? '—'
|
|
322
|
+
}}</span>
|
|
323
|
+
</div>
|
|
324
|
+
<div class="store-field">
|
|
325
|
+
<span class="store-field__label text-fm-color-neutral-gray-400">{{
|
|
326
|
+
t('order.lastSync')
|
|
327
|
+
}}</span>
|
|
328
|
+
<span>{{ formatDate(s.lastSynchronize) }}</span>
|
|
329
|
+
</div>
|
|
330
|
+
<div class="store-field">
|
|
331
|
+
<span class="store-field__label text-fm-color-neutral-gray-400">{{
|
|
332
|
+
t('order.updatedAt')
|
|
333
|
+
}}</span>
|
|
334
|
+
<span>{{ formatDate(s.updatedAt) }}</span>
|
|
335
|
+
</div>
|
|
336
|
+
</div>
|
|
337
|
+
<IntegrationIdentityDetails
|
|
338
|
+
:integration-id="s.integrationId"
|
|
339
|
+
:provider-id="s.providerId ?? s.merchantID"
|
|
340
|
+
:provider-label="t('order.merchantId')"
|
|
341
|
+
/>
|
|
342
|
+
</FmCard>
|
|
343
|
+
|
|
344
|
+
<FmSideSheet
|
|
345
|
+
:header="editing?.mode === 'new' ? t('order.addStore') : t('order.editStore')"
|
|
346
|
+
dismiss-away
|
|
347
|
+
class="w-full fm-typo-en-title-md-600"
|
|
348
|
+
:maxWidth="600"
|
|
349
|
+
:model-value="!!editing"
|
|
350
|
+
@update:model-value="onSheetToggle"
|
|
351
|
+
>
|
|
352
|
+
<div class="store-sheet-body">
|
|
353
|
+
<FmTextField
|
|
354
|
+
v-model="form.merchantID"
|
|
355
|
+
:label="t('order.merchantId')"
|
|
356
|
+
:disabled="!isInternal"
|
|
357
|
+
/>
|
|
358
|
+
<FmSelect
|
|
359
|
+
v-model="form.catalogId"
|
|
360
|
+
:label="t('order.catalog')"
|
|
361
|
+
:placeholder="t('order.selectCatalog')"
|
|
362
|
+
:items="catalogOptions"
|
|
363
|
+
/>
|
|
364
|
+
<FmSwitch v-model="form.enable" :label="t('order.enabled')" label-placement="right" />
|
|
365
|
+
<IntegrationIdentityDetails
|
|
366
|
+
v-if="editing?.mode === 'edit'"
|
|
367
|
+
:integration-id="editing.integrationId"
|
|
368
|
+
:provider-id="form.merchantID"
|
|
369
|
+
:provider-label="t('order.merchantId')"
|
|
370
|
+
/>
|
|
371
|
+
<div v-if="editing?.mode === 'edit'" class="store-sheet-activate">
|
|
372
|
+
<FmButton
|
|
373
|
+
variant="primary"
|
|
374
|
+
:label="t('order.activate')"
|
|
375
|
+
:disabled="isDirty"
|
|
376
|
+
@click="activate()"
|
|
377
|
+
/>
|
|
378
|
+
<div
|
|
379
|
+
v-if="isDirty"
|
|
380
|
+
class="store-sheet-activate__hint text-fm-color-neutral-gray-400 fm-typo-cn-body-md-400"
|
|
381
|
+
>
|
|
382
|
+
{{ t('order.saveBeforeActivate') }}
|
|
383
|
+
</div>
|
|
384
|
+
</div>
|
|
385
|
+
</div>
|
|
386
|
+
<template #side-sheet-footer>
|
|
387
|
+
<div class="flex gap-8 items-center justify-start">
|
|
388
|
+
<FmButton
|
|
389
|
+
variant="primary"
|
|
390
|
+
size="lg"
|
|
391
|
+
:label="editing?.mode === 'new' ? t('order.addStore') : t('order.save')"
|
|
392
|
+
:loading="isSaving"
|
|
393
|
+
:disabled="isSaving"
|
|
394
|
+
@click="save"
|
|
395
|
+
/>
|
|
396
|
+
<FmButton variant="secondary" size="lg" :label="t('order.cancel')" @click="cancelEdit" />
|
|
397
|
+
</div>
|
|
398
|
+
</template>
|
|
399
|
+
</FmSideSheet>
|
|
400
|
+
</div>
|
|
401
|
+
</template>
|
|
402
|
+
|
|
403
|
+
<style scoped>
|
|
404
|
+
/* Visual divider separating the primary store (shown in the main GrabFood card above)
|
|
405
|
+
from the additional stores listed in this section. */
|
|
406
|
+
.store-section {
|
|
407
|
+
border-top: 1px solid #e5e7eb;
|
|
408
|
+
padding-top: 1.5rem;
|
|
409
|
+
}
|
|
410
|
+
.store-card {
|
|
411
|
+
padding: 1.25rem;
|
|
412
|
+
margin-bottom: 1rem;
|
|
413
|
+
}
|
|
414
|
+
.store-card__top {
|
|
415
|
+
display: flex;
|
|
416
|
+
align-items: flex-start;
|
|
417
|
+
justify-content: space-between;
|
|
418
|
+
gap: 1rem;
|
|
419
|
+
margin-bottom: 1.25rem;
|
|
420
|
+
}
|
|
421
|
+
.store-card__title {
|
|
422
|
+
min-width: 0;
|
|
423
|
+
word-break: break-all;
|
|
424
|
+
}
|
|
425
|
+
.store-card__actions {
|
|
426
|
+
display: flex;
|
|
427
|
+
flex-wrap: wrap;
|
|
428
|
+
justify-content: flex-end;
|
|
429
|
+
gap: 0.5rem;
|
|
430
|
+
flex-shrink: 0;
|
|
431
|
+
}
|
|
432
|
+
.store-card__fields {
|
|
433
|
+
display: flex;
|
|
434
|
+
flex-direction: column;
|
|
435
|
+
gap: 0.625rem;
|
|
436
|
+
}
|
|
437
|
+
/* Two-column label/value grid. The grid container owns the column widths, so the
|
|
438
|
+
label never collapses regardless of utility-class width quirks. */
|
|
439
|
+
.store-field {
|
|
440
|
+
display: grid;
|
|
441
|
+
grid-template-columns: 9rem minmax(0, 1fr);
|
|
442
|
+
align-items: start;
|
|
443
|
+
column-gap: 1rem;
|
|
444
|
+
row-gap: 0.125rem;
|
|
445
|
+
}
|
|
446
|
+
.store-field > span:last-child {
|
|
447
|
+
word-break: break-word;
|
|
448
|
+
}
|
|
449
|
+
/* Edit/Add side-sheet body: even vertical rhythm between the form fields. */
|
|
450
|
+
.store-sheet-body {
|
|
451
|
+
display: flex;
|
|
452
|
+
flex-direction: column;
|
|
453
|
+
gap: 1rem;
|
|
454
|
+
padding-top: 0.5rem;
|
|
455
|
+
}
|
|
456
|
+
/* Activate (Self-Serve) sits below the form fields in the Edit side sheet,
|
|
457
|
+
separated from the inputs above it. */
|
|
458
|
+
.store-sheet-activate {
|
|
459
|
+
padding-top: 1.25rem;
|
|
460
|
+
border-top: 1px solid #e5e7eb;
|
|
461
|
+
}
|
|
462
|
+
/* Shown when there are unsaved edits — Activate is disabled until the user saves. */
|
|
463
|
+
.store-sheet-activate__hint {
|
|
464
|
+
margin-top: 0.5rem;
|
|
465
|
+
}
|
|
466
|
+
/* Narrow container (e.g. mobile drawer): stack value under label. */
|
|
467
|
+
@media (max-width: 460px) {
|
|
468
|
+
.store-field {
|
|
469
|
+
grid-template-columns: 1fr;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
</style>
|