@merkaly/nuxt 0.12.3 → 0.12.4
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/README.md
CHANGED
|
@@ -113,6 +113,39 @@ The path is `/phog` because ad blockers drop requests to paths such as
|
|
|
113
113
|
`/posthog`, `/analytics`, `/tracking` or `/telemetry`; a first-party, neutral
|
|
114
114
|
path keeps events flowing.
|
|
115
115
|
|
|
116
|
+
## Notifications and modals
|
|
117
|
+
|
|
118
|
+
`useNotify()` is the standard for feedback and confirmations in every app:
|
|
119
|
+
|
|
120
|
+
| Need | Use |
|
|
121
|
+
|---|---|
|
|
122
|
+
| Short feedback | `$notify.toast({ title, variant })` |
|
|
123
|
+
| Confirm an action (text and two buttons) | `$notify.confirm({ title, message, confirmText, cancelText, variant, onConfirm })` |
|
|
124
|
+
| Modal with its own content (form, list) | a component with `<BModal>`, opened with `useModal().create({ component: markRaw(component), ...props }).show()` |
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
const $notify = useNotify();
|
|
128
|
+
|
|
129
|
+
$notify.confirm({
|
|
130
|
+
cancelText: $t('actions.back'),
|
|
131
|
+
confirmText: $t('order.cancel.submit'),
|
|
132
|
+
message: $t('order.cancel.confirm'),
|
|
133
|
+
onConfirm: () => cancel({ id }).then(() => !error.value), // false keeps it open (the error is shown by the app)
|
|
134
|
+
title: $t('order.cancel.modalTitle', { number }),
|
|
135
|
+
variant: 'danger',
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
- `onConfirm` returning a promise keeps the modal open with both buttons disabled until it settles; it
|
|
140
|
+
closes afterwards unless the promise resolves `false`. Adapters' `execute` resolves on api errors too,
|
|
141
|
+
so check `error` before returning.
|
|
142
|
+
- Each call creates its own modal and destroys it when hidden.
|
|
143
|
+
- No inline "click again to confirm" and no `<BModal v-model>` in a page: open modals through `create`.
|
|
144
|
+
- A component opened by `create` is mounted outside the page tree: pass data as props and answer
|
|
145
|
+
through emits (`onSaved`), never `inject`. Do not use `modelValue` for data: the orchestrator shows the modal through it
|
|
146
|
+
(use a named model such as `v-model:page`). Destroy it on `onHidden` when created from a click.
|
|
147
|
+
- Props already set in the app `bootstrap.config.ts` are defaults: do not repeat them.
|
|
148
|
+
|
|
116
149
|
## Money formatting
|
|
117
150
|
|
|
118
151
|
Money in Merkaly is stored as **integer cents**. `MKFormatMoney`, `MKInputMoney`,
|
package/dist/module.json
CHANGED
|
@@ -106,8 +106,8 @@ function getRowAttrs(item, idx) {
|
|
|
106
106
|
</script>
|
|
107
107
|
|
|
108
108
|
<template>
|
|
109
|
-
<BCard no-body>
|
|
110
|
-
<BCardHeader v-if="!props.hideHeader" class="align-items-center position-relative">
|
|
109
|
+
<BCard class="card-flush" no-body>
|
|
110
|
+
<BCardHeader v-if="!props.hideHeader" class="align-items-center position-relative py-5 gap-2 gap-md-5">
|
|
111
111
|
<BCardTitle>
|
|
112
112
|
<div v-if="hasBulkSlot && !props.hideSelect && selectionState.selectedCount" class="w-35px">
|
|
113
113
|
<DropdownIcon icon="caret-down" toggle-class="p-2">
|
|
@@ -181,99 +181,100 @@ function getRowAttrs(item, idx) {
|
|
|
181
181
|
style="bottom: -6px; z-index: 10" />
|
|
182
182
|
</BCardHeader>
|
|
183
183
|
|
|
184
|
-
<
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
<BTh v-if="hasDetailsSlot" class="w-25px" style="padding: unset !important;" />
|
|
184
|
+
<BCardBody class="pt-0">
|
|
185
|
+
<BTableSimple
|
|
186
|
+
:class="{ 'h-100': !visibleItems.length || $datagrid.loading }"
|
|
187
|
+
class="mb-0 overflow-auto"
|
|
188
|
+
responsive="lg"
|
|
189
|
+
table-class="align-middle table-row-dashed fs-6 gy-5 h-100">
|
|
190
|
+
<BThead class="sticky-top z-index-1 align-middle">
|
|
191
|
+
<BTr class="text-start text-gray-500 fw-bold fs-7 text-uppercase gs-0">
|
|
192
|
+
<BTh v-if="hasDetailsSlot" class="w-25px" style="padding: unset !important;" />
|
|
194
193
|
|
|
195
|
-
|
|
196
|
-
<BFormCheckbox
|
|
197
|
-
:disabled="$datagrid.loading"
|
|
198
|
-
:indeterminate="selectionState.checkboxAllAttrs.indeterminate"
|
|
199
|
-
:model-value="selectionState.allChecked"
|
|
200
|
-
class="form-check-sm form-check-custom cell-checkbox"
|
|
201
|
-
@update:model-value="toggleCheckAll()" />
|
|
202
|
-
</BTh>
|
|
203
|
-
|
|
204
|
-
<template v-for="[key, column] in visibleColumns" :key="key">
|
|
205
|
-
<BTh :class="[column.class, column.thClass, 'text-nowrap']">
|
|
206
|
-
<slot :name="`head[${key}]`" v-bind="{ column, key }">
|
|
207
|
-
<span v-text="column.title ?? sentenceCase(key)" />
|
|
208
|
-
</slot>
|
|
209
|
-
</BTh>
|
|
210
|
-
</template>
|
|
211
|
-
|
|
212
|
-
<BTh v-if="hasActionsSlot" class="text-end px-3" />
|
|
213
|
-
</BTr>
|
|
214
|
-
</BThead>
|
|
215
|
-
|
|
216
|
-
<BTbody v-if="!visibleItems.length">
|
|
217
|
-
<BTd :colspan="tableColspan">
|
|
218
|
-
<slot name="empty">
|
|
219
|
-
<div class="text-center text-muted" v-text="props.emptyText ?? t('empty')" />
|
|
220
|
-
</slot>
|
|
221
|
-
</BTd>
|
|
222
|
-
</BTbody>
|
|
223
|
-
|
|
224
|
-
<BTbody v-else class="fw-semibold text-nowrap">
|
|
225
|
-
<template v-for="(item, idx) in visibleItems" :key="getRowKey(item, idx)">
|
|
226
|
-
<BTr v-bind="getRowAttrs(item, idx)">
|
|
227
|
-
<BTd v-if="hasDetailsSlot" class="w-25px" style="padding: unset !important;">
|
|
228
|
-
<BButton
|
|
229
|
-
class="w-25px h-100 rounded-0 p-0 border-end border-dashed"
|
|
230
|
-
size="sm"
|
|
231
|
-
variant="none"
|
|
232
|
-
@click="toggleDetails(item)">
|
|
233
|
-
<FormatIcon :name="item._showDetails ? 'chevron-down' : 'chevron-right'" class="p-0" size="sm"
|
|
234
|
-
variant="primary" />
|
|
235
|
-
</BButton>
|
|
236
|
-
</BTd>
|
|
237
|
-
|
|
238
|
-
<BTd v-if="!props.hideSelect" class="w-40px px-0">
|
|
194
|
+
<BTh v-if="!props.hideSelect" class="w-40px px-0">
|
|
239
195
|
<BFormCheckbox
|
|
240
|
-
v-model="item._checked"
|
|
241
196
|
:disabled="$datagrid.loading"
|
|
242
|
-
|
|
243
|
-
|
|
197
|
+
:indeterminate="selectionState.checkboxAllAttrs.indeterminate"
|
|
198
|
+
:model-value="selectionState.allChecked"
|
|
199
|
+
class="form-check-sm form-check-custom cell-checkbox"
|
|
200
|
+
@update:model-value="toggleCheckAll()" />
|
|
201
|
+
</BTh>
|
|
244
202
|
|
|
245
203
|
<template v-for="[key, column] in visibleColumns" :key="key">
|
|
246
|
-
<
|
|
247
|
-
<slot :name="`
|
|
248
|
-
<span v-text="column.
|
|
204
|
+
<BTh :class="[column.class, column.thClass, 'text-nowrap']">
|
|
205
|
+
<slot :name="`head[${key}]`" v-bind="{ column, key }">
|
|
206
|
+
<span v-text="column.title ?? sentenceCase(key)" />
|
|
249
207
|
</slot>
|
|
250
|
-
</
|
|
208
|
+
</BTh>
|
|
251
209
|
</template>
|
|
252
210
|
|
|
253
|
-
<
|
|
254
|
-
<DropdownIcon :text="t('actions')" icon="caret-down" toggle-class="py-1 px-3" variant="light-dark">
|
|
255
|
-
<slot :index="idx" :item="item" name="actions" />
|
|
256
|
-
</DropdownIcon>
|
|
257
|
-
</BTd>
|
|
211
|
+
<BTh v-if="hasActionsSlot" class="text-end px-3" />
|
|
258
212
|
</BTr>
|
|
213
|
+
</BThead>
|
|
259
214
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
215
|
+
<BTbody v-if="!visibleItems.length">
|
|
216
|
+
<BTd :colspan="tableColspan">
|
|
217
|
+
<slot name="empty">
|
|
218
|
+
<div class="text-center text-muted" v-text="props.emptyText ?? t('empty')" />
|
|
219
|
+
</slot>
|
|
220
|
+
</BTd>
|
|
221
|
+
</BTbody>
|
|
222
|
+
|
|
223
|
+
<BTbody v-else class="fw-semibold text-gray-600 text-nowrap">
|
|
224
|
+
<template v-for="(item, idx) in visibleItems" :key="getRowKey(item, idx)">
|
|
225
|
+
<BTr v-bind="getRowAttrs(item, idx)">
|
|
226
|
+
<BTd v-if="hasDetailsSlot" class="w-25px" style="padding: unset !important;">
|
|
227
|
+
<BButton
|
|
228
|
+
class="w-25px h-100 rounded-0 p-0 border-end border-dashed"
|
|
229
|
+
size="sm"
|
|
230
|
+
variant="none"
|
|
231
|
+
@click="toggleDetails(item)">
|
|
232
|
+
<FormatIcon
|
|
233
|
+
:name="item._showDetails ? 'chevron-down' : 'chevron-right'" class="p-0" size="sm"
|
|
234
|
+
variant="primary" />
|
|
235
|
+
</BButton>
|
|
236
|
+
</BTd>
|
|
237
|
+
|
|
238
|
+
<BTd v-if="!props.hideSelect" class="w-40px px-0">
|
|
239
|
+
<BFormCheckbox
|
|
240
|
+
v-model="item._checked"
|
|
241
|
+
:disabled="$datagrid.loading"
|
|
242
|
+
class="form-check-sm form-check-custom cell-checkbox" />
|
|
243
|
+
</BTd>
|
|
244
|
+
|
|
245
|
+
<template v-for="[key, column] in visibleColumns" :key="key">
|
|
246
|
+
<BTd :class="[column.class, column.tdClass]">
|
|
247
|
+
<slot :name="`row[${key}]`" v-bind="{ column, idx, item, key }">
|
|
248
|
+
<span v-text="column.getter?.(item) ?? getItemValue(item, key)" />
|
|
249
|
+
</slot>
|
|
250
|
+
</BTd>
|
|
251
|
+
</template>
|
|
252
|
+
|
|
253
|
+
<BTd v-if="hasActionsSlot" class="text-end px-3">
|
|
254
|
+
<DropdownIcon :text="t('actions')" icon="caret-down" toggle-class="py-1 px-3" variant="light-dark">
|
|
255
|
+
<slot :index="idx" :item="item" name="actions" />
|
|
256
|
+
</DropdownIcon>
|
|
257
|
+
</BTd>
|
|
258
|
+
</BTr>
|
|
259
|
+
|
|
260
|
+
<BTr v-if="hasDetailsSlot && item._showDetails">
|
|
261
|
+
<BTd :colspan="tableColspan" class="p-0">
|
|
262
|
+
<slot
|
|
263
|
+
:index="idx"
|
|
264
|
+
:item="item"
|
|
265
|
+
:toggle-details="() => toggleDetails(item)"
|
|
266
|
+
name="details" />
|
|
267
|
+
</BTd>
|
|
268
|
+
</BTr>
|
|
269
|
+
</template>
|
|
270
|
+
</BTbody>
|
|
271
271
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
272
|
+
<BTfoot>
|
|
273
|
+
<slot name="tfoot" />
|
|
274
|
+
</BTfoot>
|
|
275
275
|
|
|
276
|
-
|
|
276
|
+
</BTableSimple>
|
|
277
|
+
</BCardBody>
|
|
277
278
|
|
|
278
279
|
<BCardFooter v-if="!props.hideFooter">
|
|
279
280
|
<slot name="footer" />
|
|
@@ -317,24 +318,6 @@ function getRowAttrs(item, idx) {
|
|
|
317
318
|
.cell-checkbox input {
|
|
318
319
|
cursor: pointer;
|
|
319
320
|
}
|
|
320
|
-
|
|
321
|
-
table thead {
|
|
322
|
-
font-size: 0.8rem;
|
|
323
|
-
background: var(--bs-body-bg);
|
|
324
|
-
}
|
|
325
|
-
table thead:before {
|
|
326
|
-
content: "";
|
|
327
|
-
position: absolute;
|
|
328
|
-
inset: 0;
|
|
329
|
-
z-index: -1;
|
|
330
|
-
background: rgb(var(--bs-primary-rgb), 0.05);
|
|
331
|
-
}
|
|
332
|
-
table thead tr th:is(:first-child, :last-child) {
|
|
333
|
-
padding: 0 0.75rem !important;
|
|
334
|
-
}
|
|
335
|
-
table tbody tr td:is(:first-child, :last-child) {
|
|
336
|
-
padding: 0 0.75rem !important;
|
|
337
|
-
}
|
|
338
321
|
</style>
|
|
339
322
|
|
|
340
323
|
<i18n lang="json">
|
|
@@ -6,7 +6,7 @@ export interface NotifyOptions {
|
|
|
6
6
|
export interface NotifyConfirmOptions extends NotifyOptions {
|
|
7
7
|
cancelText?: string;
|
|
8
8
|
confirmText?: string;
|
|
9
|
-
onConfirm?: () =>
|
|
9
|
+
onConfirm?: () => Promise<unknown> | unknown;
|
|
10
10
|
}
|
|
11
11
|
export interface NotifyAlertOptions extends NotifyOptions {
|
|
12
12
|
okText?: string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { push } from "notivue";
|
|
2
|
+
import { computed, ref } from "vue";
|
|
2
3
|
import { useModal } from "bootstrap-vue-next";
|
|
3
4
|
export function useNotify() {
|
|
4
5
|
const $modal = useModal();
|
|
@@ -25,15 +26,27 @@ export function useNotify() {
|
|
|
25
26
|
instance.show();
|
|
26
27
|
}
|
|
27
28
|
function confirm(options) {
|
|
28
|
-
const
|
|
29
|
+
const busy = ref(false);
|
|
30
|
+
const instance = $modal.create(computed(() => ({
|
|
29
31
|
body: options.message,
|
|
32
|
+
busy: busy.value,
|
|
33
|
+
// disables both buttons
|
|
30
34
|
cancelTitle: options.cancelText || "Cancel",
|
|
31
35
|
cancelVariant: "secondary",
|
|
36
|
+
noCloseOnEsc: busy.value,
|
|
32
37
|
okTitle: options.confirmText || "OK",
|
|
33
38
|
okVariant: options.variant || "primary",
|
|
34
|
-
|
|
39
|
+
onHidden: () => void instance.destroy(),
|
|
40
|
+
// one modal per call: never pile up hidden ones
|
|
41
|
+
onOk: (event) => {
|
|
42
|
+
const result = options.onConfirm?.();
|
|
43
|
+
if (!(result instanceof Promise)) return;
|
|
44
|
+
event.preventDefault();
|
|
45
|
+
busy.value = true;
|
|
46
|
+
result.then((value) => value !== false && instance.hide("ok")).finally(() => void (busy.value = false));
|
|
47
|
+
},
|
|
35
48
|
title: options.title || "Confirm"
|
|
36
|
-
});
|
|
49
|
+
})));
|
|
37
50
|
instance.show();
|
|
38
51
|
}
|
|
39
52
|
return { toast, alert, confirm };
|