@autobusal/utilities 1.6.0 → 1.7.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.
package/date.tsx CHANGED
@@ -102,4 +102,41 @@ export const futureDate = (days: number): string => {
102
102
  dayAfter.setDate(today.getDate() + days);
103
103
 
104
104
  return prepareDate(dayAfter);
105
- };
105
+ };
106
+ /**
107
+ * A prepared d/m/Y date as the API's own Y-m-d.
108
+ *
109
+ * Claude - 2026-11-03. The calendar fields post d/m/Y, which the endpoints
110
+ * built for them validate with date_format:d/m/Y; the ones that take a plain
111
+ * `date` (a recorded payout, a settlement) read "31/12/2026" the American
112
+ * way and refuse it. Convert at the edge; anything that is not a prepared
113
+ * date is passed through untouched.
114
+ */
115
+ export const isoDate = (prepared?: string | null): string => {
116
+ if (!isPreparedDate(prepared)) {
117
+ return prepared ?? '';
118
+ }
119
+
120
+ const [ day, month, year ] = String(prepared).split('/');
121
+
122
+ return `${ year }-${ month }-${ day }`;
123
+ };
124
+
125
+ /**
126
+ * The other direction: a date input's yyyy-mm-dd as the d/m/Y most obtapi
127
+ * list filters parse (Carbon::createFromFormat('d/m/Y')).
128
+ *
129
+ * Claude - 2026-11-04 (dashboard simplification, wave 7). The shared Filters
130
+ * toolbar writes native date inputs (yyyy-mm-dd) into the URL; the invoice
131
+ * and API-log endpoints still read d/m/Y. Anything that is not yyyy-mm-dd -
132
+ * an old d/m/Y bookmark, an empty value - passes through untouched.
133
+ */
134
+ export const dmyDate = (value?: string | null): string => {
135
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value ?? '');
136
+
137
+ if (match === null) {
138
+ return value ?? '';
139
+ }
140
+
141
+ return `${ match[3] }/${ match[2] }/${ match[1] }`;
142
+ };
package/form.tsx CHANGED
@@ -94,4 +94,4 @@ export const Display = (err: (FieldError | Merge<FieldError, FieldErrorsImpl<any
94
94
  { err.message as string }
95
95
  </Error>
96
96
  );
97
- }
97
+ }
package/index.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { createDate, prepareDate, isPreparedDate, getFormattedShort, getDayParts, differenceYears, futureDate } from './date';
1
+ import { createDate, prepareDate, isPreparedDate, isoDate, dmyDate, getFormattedShort, getDayParts, differenceYears, futureDate } from './date';
2
2
  import { Validate, Display } from './form';
3
+ import { serverErrors } from './serverErrors';
3
4
  import { error, notify, success } from './notify';
4
5
  import { getAllowed, getLanguages, getPayments } from './options';
5
6
  import { getSex, getOptionsSex } from './passengers';
@@ -12,12 +13,15 @@ export {
12
13
  sanitizeHtml,
13
14
  Validate,
14
15
  Display,
16
+ serverErrors,
15
17
  error,
16
18
  notify,
17
19
  success,
18
20
  createDate,
19
21
  prepareDate,
20
22
  isPreparedDate,
23
+ isoDate,
24
+ dmyDate,
21
25
  getFormattedShort,
22
26
  getDayParts,
23
27
  differenceYears,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autobusal/utilities",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "author": "Ferjolt Ozuni",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The field errors of a failed save, keyed by field.
3
+ *
4
+ * Claude - 2026-11-03 (dashboard simplification, wave 0). Laravel answers a
5
+ * rejected form with 422 and `{ message, errors: { field: [text] } }`; the
6
+ * API client toasts the message and the fields stayed silent, so the admin
7
+ * had to guess which of forty inputs the server meant. Pure: an Axios error
8
+ * in, a flat map out (the first message per field - one is enough under an
9
+ * input), an empty map for anything that is not a 422. Viewer takes it as
10
+ * `errors` and puts each message under its own field.
11
+ *
12
+ * A nested Laravel key (title.en) is passed through unchanged -
13
+ * react-hook-form resolves dotted names itself.
14
+ */
15
+ export const serverErrors = (error: unknown): Record<string, string> => {
16
+ const response = (error as { response?: { status?: number, data?: { errors?: Record<string, string[] | string> } } } | null | undefined)?.response;
17
+
18
+ if (response === undefined || response.status !== 422 || response.data?.errors === undefined || response.data.errors === null) {
19
+ return {};
20
+ }
21
+
22
+ return Object.fromEntries(
23
+ Object.entries(response.data.errors)
24
+ .map(([ field, messages ]) => [ field, Array.isArray(messages) ? String(messages[0] ?? '') : String(messages) ])
25
+ .filter(([ , message ]) => message !== '')
26
+ );
27
+ };