@omnitend/dashboard-for-laravel 0.32.0 → 0.33.1

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.
@@ -5,8 +5,14 @@ import type { FieldDefinition, FieldType } from "../types";
5
5
  * Extended field definition that includes default value
6
6
  */
7
7
  export interface FormFieldDefinition<TValue = any> extends FieldDefinition {
8
- /** Default/initial value for the field */
9
- default: TValue;
8
+ /**
9
+ * Default/initial value for the field. Optional: when omitted, the field
10
+ * seeds from its type (`false` for checkbox/switch, `[]` for
11
+ * checkbox-group/switch-list/repeater, `0` for numerics, `""` otherwise) —
12
+ * previously the type required this even though the runtime had a
13
+ * fallback, so typed consumers could never use it.
14
+ */
15
+ default?: TValue;
10
16
  }
11
17
 
12
18
  /**
@@ -37,6 +43,10 @@ function getDefaultValueForType(type: FieldType): any {
37
43
  case "percentage":
38
44
  return 0;
39
45
  case "repeater":
46
+ // Array-valued selections: checkbox-group previously fell through to
47
+ // the "" default, which is the wrong shape for an array model.
48
+ case "checkbox-group":
49
+ case "switch-list":
40
50
  return [];
41
51
  case "image":
42
52
  case "file":
@@ -1,5 +1,5 @@
1
1
  import { computed, ref, type Ref } from "vue";
2
- import axios from "axios";
2
+ import { api } from "../utils/api";
3
3
  import { useForm } from "./useForm";
4
4
  import { useToast } from "./useToast";
5
5
 
@@ -254,7 +254,7 @@ export function useResourceEditor<T = any>(
254
254
  editLoading.value = true;
255
255
  try {
256
256
  const url = props.showUrl!.replace(':id', String(itemId));
257
- const response = await axios.get(url);
257
+ const response = await api.get<any>(url);
258
258
  // Superseded by a newer row-open — discard.
259
259
  if (token !== editFetchToken) return;
260
260
 
@@ -271,9 +271,7 @@ export function useResourceEditor<T = any>(
271
271
  } catch (error: any) {
272
272
  if (token !== editFetchToken) return;
273
273
  const message =
274
- error?.response?.data?.message ??
275
- error?.message ??
276
- 'Failed to load the full record.';
274
+ error?.message ?? 'Failed to load the full record.';
277
275
  createToast?.({
278
276
  title: 'Error',
279
277
  body: message,
@@ -10,6 +10,7 @@ export { default as DXForm } from "./components/extended/DXForm.vue";
10
10
  export { default as DXField } from "./components/extended/DXField.vue";
11
11
  export { default as DXFieldLabel } from "./components/extended/DXFieldLabel.vue";
12
12
  export { default as DXSwitch } from "./components/extended/DXSwitch.vue";
13
+ export { default as DXCurrencyInput } from "./components/extended/DXCurrencyInput.vue";
13
14
  export { default as DXStatCard } from "./components/extended/DXStatCard.vue";
14
15
  // Chart components moved to the `@omnitend/dashboard-for-laravel/charts` entry
15
16
  // (#142) so chart.js / vue-chartjs stay genuinely optional peers — the main
@@ -33,6 +33,7 @@ export type FieldType =
33
33
  | "checkbox"
34
34
  | "checkbox-group"
35
35
  | "switch"
36
+ | "switch-list"
36
37
  | "radio"
37
38
  | "image"
38
39
  | "file"
@@ -103,9 +104,11 @@ export interface FieldDefinition {
103
104
  required?: boolean;
104
105
 
105
106
  /**
106
- * Options for `select`, `radio` and `autocomplete` fields. For
107
- * `autocomplete` these are the datalist suggestions; the field still
108
- * accepts free text not present in the list.
107
+ * Options for `select`, `radio`, `checkbox-group`, `switch-list` and
108
+ * `autocomplete` fields. For `autocomplete` these are the datalist
109
+ * suggestions; the field still accepts free text not present in the list.
110
+ * For `switch-list` each option renders a labelled toggle row
111
+ * (`description` becomes the row label's tooltip).
109
112
  */
110
113
  options?: FieldOption[];
111
114
 
@@ -152,6 +155,15 @@ export interface FieldDefinition {
152
155
  */
153
156
  decimals?: number;
154
157
 
158
+ /**
159
+ * For `currency` fields: the model stores integer MINOR units (pence) but
160
+ * the input shows/edits MAJOR units (pounds) — display = value / 10^decimals,
161
+ * submit = round(input × 10^decimals). `min`/`max`/`step` stay in major
162
+ * units. Off by default (the model is the major-unit amount). Use for
163
+ * legacy pence-integer columns (#116).
164
+ */
165
+ minorUnits?: boolean;
166
+
155
167
  /**
156
168
  * For `percentage` fields: treat the underlying model value as a 0–1
157
169
  * fraction while showing/editing it as a 0–100 percentage. The model keeps
@@ -197,11 +209,13 @@ export interface FieldDefinition {
197
209
  textWhenFalse?: MaybeFn<string>;
198
210
 
199
211
  /**
200
- * For `switch` fields: the on-state colour. `"success"` (default) is the
201
- * house green-on / red-off style for an active/enabled/good switch;
202
- * `"neutral"` (brand primary on / grey off) is for semantically-mixed
203
- * switches that shouldn't imply good/bad ("contains alcohol", an allergen
204
- * toggle, "hidden on web shop"). See DXSwitch `onVariant` (#158).
212
+ * For `switch` and `switch-list` fields: the on-state colour. `"success"`
213
+ * (default) is the house green-on / red-off style for an
214
+ * active/enabled/good switch; `"neutral"` (brand primary on / grey off) is
215
+ * for semantically-mixed switches that shouldn't imply good/bad ("contains
216
+ * alcohol", an allergen toggle, "hidden on web shop"). A `switch-list`
217
+ * usually wants `"neutral"` — a wall of red-off toggles over-signals when
218
+ * off simply means "not present". See DXSwitch `onVariant` (#158).
205
219
  */
206
220
  switchVariant?: "success" | "neutral";
207
221
 
@@ -53,8 +53,7 @@ class ApiClient {
53
53
  }
54
54
  };
55
55
  for (const [k, v] of Object.entries(params)) append(k, v);
56
- const qs = search.toString();
57
- return qs ? `?${qs}` : "";
56
+ return search.toString();
58
57
  }
59
58
 
60
59
  private handleError(response: Response, data: any): never {
@@ -92,18 +91,24 @@ class ApiClient {
92
91
  ): Promise<ApiResponse<T>> {
93
92
  const isFormData =
94
93
  typeof FormData !== "undefined" && options.body instanceof FormData;
94
+ // GET/HEAD carry no body (nothing for Content-Type to describe) and
95
+ // change no state (no CSRF exposure). Both headers are also non-simple
96
+ // for CORS, so sending them would force a preflight on cross-origin
97
+ // apiUrl/showUrl endpoints that axios's plain GETs never needed (#132).
98
+ const method = (options.method ?? "GET").toUpperCase();
99
+ const isBodyless = method === "GET" || method === "HEAD";
95
100
 
96
101
  const headers: Record<string, string> = {
97
102
  ...this.defaultHeaders,
98
103
  ...(isFormData
99
104
  ? {}
100
105
  : { "Content-Type": this.defaultHeaders["Content-Type"] }),
101
- "X-CSRF-TOKEN": this.getCsrfToken(),
106
+ ...(isBodyless ? {} : { "X-CSRF-TOKEN": this.getCsrfToken() }),
102
107
  ...options.headers,
103
108
  };
104
109
 
105
110
  // Let fetch set the multipart boundary if FormData
106
- if (isFormData) delete headers["Content-Type"];
111
+ if (isFormData || isBodyless) delete headers["Content-Type"];
107
112
 
108
113
  const config: RequestOptions = {
109
114
  credentials: "same-origin",
@@ -140,7 +145,10 @@ class ApiClient {
140
145
  params: Record<string, unknown> = {},
141
146
  options: Omit<RequestOptions, "method"> = {},
142
147
  ) {
143
- const full = `${url}${this.toQuery(params)}`;
148
+ // Merge into an existing query string (`/things?scope=x` + params) with
149
+ // `&` — a blind `?` append corrupts the URL and silently drops params.
150
+ const qs = this.toQuery(params);
151
+ const full = qs ? `${url}${url.includes("?") ? "&" : "?"}${qs}` : url;
144
152
  return this.request<T>(full, { ...options, method: "GET" });
145
153
  }
146
154