@omnitend/dashboard-for-laravel 0.7.1 → 0.9.0

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.
@@ -29,7 +29,7 @@
29
29
  <DTab
30
30
  v-for="(tab, index) in visibleTabs"
31
31
  :key="tab.key"
32
- :title="tab.label || tab.key"
32
+ :title="resolveTabLabel(tab)"
33
33
  :lazy="tab.lazy"
34
34
  :active="index === 0"
35
35
  >
@@ -207,6 +207,13 @@ function resolvePredicate(
207
207
  return typeof when === "function" ? when(model.value) : when;
208
208
  }
209
209
 
210
+ /** Resolve a tab's (possibly function-valued) label against the live model. */
211
+ function resolveTabLabel(tab: FormTab): string {
212
+ const label =
213
+ typeof tab.label === "function" ? tab.label(model.value) : tab.label;
214
+ return label || tab.key;
215
+ }
216
+
210
217
  function isFieldVisible(field: FieldDefinition): boolean {
211
218
  const whenOk = resolvePredicate(field.when, true);
212
219
  const showOk = field.show ? field.show() : true;
@@ -124,6 +124,7 @@ const rowKey = (index: number): string | number => {
124
124
  function defaultForType(type: FieldType): any {
125
125
  switch (type) {
126
126
  case "checkbox":
127
+ case "switch":
127
128
  return false;
128
129
  case "number":
129
130
  case "currency":
@@ -529,7 +529,7 @@
529
529
  :disabled="editForm?.processing"
530
530
  @click="handleDelete"
531
531
  >
532
- {{ editForm?.processing ? 'Deleting...' : 'Delete' }}
532
+ {{ pendingAction === 'delete' ? 'Deleting...' : 'Delete' }}
533
533
  </DButton>
534
534
  </div>
535
535
  <div class="d-flex gap-2">
@@ -542,10 +542,10 @@
542
542
  @click="handleEditSave"
543
543
  >
544
544
  <template v-if="isCreateMode">
545
- {{ editForm?.processing ? 'Creating...' : 'Create' }}
545
+ {{ pendingAction === 'save' ? 'Creating...' : 'Create' }}
546
546
  </template>
547
547
  <template v-else>
548
- {{ editForm?.processing ? 'Saving...' : 'Save Changes' }}
548
+ {{ pendingAction === 'save' ? 'Saving...' : 'Save Changes' }}
549
549
  </template>
550
550
  </DButton>
551
551
  </div>
@@ -621,8 +621,12 @@ export interface EditTab {
621
621
  /** Unique key for this tab */
622
622
  key: string;
623
623
 
624
- /** Display label (optional, auto-derived from key if omitted) */
625
- label?: string;
624
+ /**
625
+ * Display label (optional, auto-derived from key if omitted). May be a
626
+ * function of the model (the edited row merged with the live form data),
627
+ * e.g. `label: (item) => \`Products (${item.products_count ?? 0})\``.
628
+ */
629
+ label?: string | ((item: any) => string);
626
630
 
627
631
  /** Field keys to display in this tab (from editFields) */
628
632
  fieldKeys: string[];
@@ -1380,6 +1384,11 @@ const editForm = ref<any>(null);
1380
1384
  const activeTabIndex = ref(0);
1381
1385
  const isCreateMode = ref(false);
1382
1386
 
1387
+ // Which modal action is in flight, so the Save and Delete buttons show their
1388
+ // own loading label independently. `editForm.processing` is shared by every
1389
+ // request the form makes, so it can't tell Save from Delete on its own.
1390
+ const pendingAction = ref<'save' | 'delete' | null>(null);
1391
+
1383
1392
  // Toast (may not be available in test environment)
1384
1393
  let createToast: ((obj: any) => any) | undefined;
1385
1394
  try {
@@ -1521,6 +1530,15 @@ const handleCreateNew = () => {
1521
1530
  const handleEditSave = async () => {
1522
1531
  if (!editForm.value) return;
1523
1532
 
1533
+ pendingAction.value = 'save';
1534
+ try {
1535
+ await performSave();
1536
+ } finally {
1537
+ pendingAction.value = null;
1538
+ }
1539
+ };
1540
+
1541
+ const performSave = async () => {
1524
1542
  // Create mode: POST to createUrl
1525
1543
  if (isCreateMode.value && props.createUrl) {
1526
1544
  try {
@@ -1648,6 +1666,7 @@ const handleDelete = async () => {
1648
1666
 
1649
1667
  if (!confirmed) return;
1650
1668
 
1669
+ pendingAction.value = 'delete';
1651
1670
  try {
1652
1671
  const itemId = (selectedItem.value as any).id;
1653
1672
  const url = props.deleteUrl.replace(':id', itemId);
@@ -1687,6 +1706,8 @@ const handleDelete = async () => {
1687
1706
  });
1688
1707
  } catch (error) {
1689
1708
  emit('deleteError', selectedItem.value as T, error);
1709
+ } finally {
1710
+ pendingAction.value = null;
1690
1711
  }
1691
1712
  };
1692
1713
 
@@ -30,6 +30,7 @@ export interface DefineFormReturn<TData extends Record<string, any>> {
30
30
  function getDefaultValueForType(type: FieldType): any {
31
31
  switch (type) {
32
32
  case "checkbox":
33
+ case "switch":
33
34
  return false;
34
35
  case "number":
35
36
  case "currency":
@@ -8,6 +8,7 @@ export * from 'bootstrap-vue-next';
8
8
  export { default as DXDashboard } from "./components/extended/DXDashboard.vue";
9
9
  export { default as DXForm } from "./components/extended/DXForm.vue";
10
10
  export { default as DXField } from "./components/extended/DXField.vue";
11
+ export { default as DXFieldLabel } from "./components/extended/DXFieldLabel.vue";
11
12
  export { default as DXRepeater } from "./components/extended/DXRepeater.vue";
12
13
  /**
13
14
  * @deprecated Use `DXForm`. `DXBasicForm` is a thin wrapper around `DXForm`
@@ -11,6 +11,8 @@ import type { Component } from "vue";
11
11
  * - `image` / `file` — file input (`image` additionally shows a preview).
12
12
  * - `component` — escape hatch that renders `field.component`.
13
13
  * - `repeater` — nested, repeatable sub-form driven by `field.fields`.
14
+ * - `switch` — a toggle checkbox with contextual on/off text and an
15
+ * on-state (filled) style; see `textWhenTrue` / `textWhenFalse`.
14
16
  */
15
17
  export type FieldType =
16
18
  | "text"
@@ -28,6 +30,7 @@ export type FieldType =
28
30
  | "textarea"
29
31
  | "select"
30
32
  | "checkbox"
33
+ | "switch"
31
34
  | "radio"
32
35
  | "image"
33
36
  | "file"
@@ -105,6 +108,15 @@ export interface FieldDefinition {
105
108
  /** Symbol shown for `currency` fields (default: the locale's, "£"). */
106
109
  currencySymbol?: string;
107
110
 
111
+ /**
112
+ * For `percentage` fields: treat the underlying model value as a 0–1
113
+ * fraction while showing/editing it as a 0–100 percentage. The model keeps
114
+ * the fraction (e.g. `0.2`), the input shows `20`. Off by default (the value
115
+ * is taken as a whole percentage). Use for fields stored as ratios (VAT
116
+ * rates, discounts, …).
117
+ */
118
+ asFraction?: boolean;
119
+
108
120
  /** `accept` attribute for `image`/`file` inputs (e.g. "image/*"). */
109
121
  accept?: string;
110
122
 
@@ -117,6 +129,26 @@ export interface FieldDefinition {
117
129
  */
118
130
  hint?: MaybeFn<string>;
119
131
 
132
+ /**
133
+ * Longer help text revealed in a popover from a small info affordance
134
+ * on the field's label (on hover/focus). Complements `hint` (which is
135
+ * always-visible muted text below the control). May be a function of
136
+ * the model. For rich content, use the `#info` slot instead.
137
+ */
138
+ info?: MaybeFn<string>;
139
+
140
+ /**
141
+ * For `switch` fields: contextual label shown when the toggle is on.
142
+ * Falls back to `label` when omitted. May be a function of the model.
143
+ */
144
+ textWhenTrue?: MaybeFn<string>;
145
+
146
+ /**
147
+ * For `switch` fields: contextual label shown when the toggle is off.
148
+ * Falls back to `label` when omitted. May be a function of the model.
149
+ */
150
+ textWhenFalse?: MaybeFn<string>;
151
+
120
152
  /** CSS class for the form group */
121
153
  class?: string;
122
154
 
@@ -191,8 +223,13 @@ export interface FormTab {
191
223
  /** Unique key for this tab */
192
224
  key: string;
193
225
 
194
- /** Display label (optional, defaults to the key) */
195
- label?: string;
226
+ /**
227
+ * Display label (optional, defaults to the key). May be a function of the
228
+ * form model — the live form data merged with any `context` (e.g. the row
229
+ * DXTable is editing) — so a tab title can reflect the record, such as
230
+ * `label: (model) => \`Products (${model.products_count ?? 0})\``.
231
+ */
232
+ label?: MaybeFn<string>;
196
233
 
197
234
  /** Field keys (from the form's fields) to render in this tab */
198
235
  fieldKeys: string[];
@@ -11,7 +11,13 @@ export interface NavigationItem {
11
11
  export interface NavigationGroup {
12
12
  label?: string;
13
13
  items: NavigationItem[];
14
+ /**
15
+ * Per-group override for the sidebar's `collapsibleGroups` behaviour.
16
+ * Set to `false` to keep a group permanently expanded (no toggle header)
17
+ * even when the sidebar has collapsible groups enabled. Defaults to `true`.
18
+ */
14
19
  collapsible?: boolean;
20
+ /** Reserved for a future explicit initial-open hint; not currently wired. */
15
21
  collapsed?: boolean;
16
22
  visible?: boolean;
17
23
  }