innoboxrr-vue-datatable 3.0.0 → 3.1.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.
package/README.md CHANGED
@@ -63,7 +63,7 @@ import 'innoboxrr-form-core/styles'
63
63
  | Prop | Por defecto | |
64
64
  |---|---|---|
65
65
  | `dataUrl`, `policyUrl` | — | Obligatorias. |
66
- | `dataMethod`, `policyMethod` | `'post'` | `get` manda `params`; `post`, `data`. |
66
+ | `dataMethod`, `policyMethod` | `'post'` | `get` manda `params`, sin el token CSRF; `post`, `data`, con `_token`. |
67
67
  | `model` | — | El contrato del modelo (abajo). |
68
68
  | `formFilters` | `{}` | Un cambio vuelve a la primera página. |
69
69
  | `externalFilters` | `{}` | Un cambio recarga sin cambiar de página. |
@@ -101,9 +101,11 @@ export const setFilters = (filters) => filters
101
101
  // Opcionales
102
102
  export const dataTableComponents = () => ({ CopyLink })
103
103
  export const bulkActions = () => [
104
- { id: 'delete', name: 'Borrar', icon: 'delete', callback: 'bulkDelete', danger: true },
104
+ { id: 'delete', name: 'Borrar', icon: 'delete', callback: 'bulkDelete', danger: true, success: 'Borrados' },
105
+ { id: 'publish', name: 'Publicar', callback: 'bulkUpdate', params: { status: 'published' } },
105
106
  ]
106
107
  export const bulkDelete = (ids, rows) => { /* … */ }
108
+ export const bulkUpdate = (ids, rows, data) => { /* … */ }
107
109
  ```
108
110
 
109
111
  - **Acciones.** `route: true` navega a `params.to` con vue-router;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "innoboxrr-vue-datatable",
3
- "version": "3.0.0",
3
+ "version": "3.1.1",
4
4
  "description": "Datatable para vue3",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/src/table.js CHANGED
@@ -130,9 +130,13 @@ export const sortIcon = (column, orderBy, sort = {}) => {
130
130
  /**
131
131
  * Lo que espera el backend. Con orden interno, el del usuario gana a lo que
132
132
  * traigan los filtros externos; sin él, es al revés.
133
+ *
134
+ * El token CSRF no va aquí: no es un filtro, y lo que sale de aquí también lo
135
+ * recibe el modelo con `setFilters()`. Lo pone `requestConfig()`, que sabe por
136
+ * dónde viaja la petición.
133
137
  */
134
138
  export const requestFilters = ({ formFilters = {}, externalFilters = {}, orderBy, sort = {}, page, internalSort }) => {
135
- const params = { _token: csrfToken(), managed: true, except_view_any: true }
139
+ const params = { managed: true, except_view_any: true }
136
140
  const order = { orderBy, orderMode: sort[orderBy] }
137
141
 
138
142
  return internalSort
@@ -140,12 +144,35 @@ export const requestFilters = ({ formFilters = {}, externalFilters = {}, orderBy
140
144
  : { ...params, ...formFilters, ...order, ...externalFilters, page }
141
145
  }
142
146
 
143
- export const requestConfig = (method, url, payload) => ({
144
- method,
145
- url,
146
- data: method === 'post' ? payload : null,
147
- params: method === 'get' ? payload : null,
148
- })
147
+ /**
148
+ * Los métodos cuyo payload viaja en la query. En el piloto de la aplicación
149
+ * base la tabla pedía GET /api/app/product/index?_token=...: el token CSRF
150
+ * acababa en los registros de acceso, en los proxies y en el historial del
151
+ * navegador. Laravel no comprueba CSRF en ellos y axios ya manda X-XSRF-TOKEN
152
+ * desde la cookie, así que no hace falta.
153
+ */
154
+ const QUERY_METHODS = ['get', 'head']
155
+
156
+ const withCsrfToken = (method, payload) => {
157
+ if (QUERY_METHODS.includes(String(method ?? '').toLowerCase())) {
158
+ const { _token: omitted, ...rest } = payload ?? {}
159
+
160
+ return rest
161
+ }
162
+
163
+ return { _token: csrfToken(), ...payload }
164
+ }
165
+
166
+ export const requestConfig = (method, url, payload) => {
167
+ const body = withCsrfToken(method, payload)
168
+
169
+ return {
170
+ method,
171
+ url,
172
+ data: method === 'post' ? body : null,
173
+ params: method === 'get' ? body : null,
174
+ }
175
+ }
149
176
 
150
177
  /** El usuario canceló una confirmación: no es un error que haya que contar. */
151
178
  export const isCancelled = (error) => ['RequestCancelledError', 'CanceledError'].includes(error?.name)
@@ -205,10 +205,8 @@ export default function useDataTable(props, { navigate = null, labels = core.DEF
205
205
  }
206
206
 
207
207
  try {
208
- const response = await axios(core.requestConfig(props.policyMethod, props.policyUrl, {
209
- _token: core.csrfToken(),
210
- id,
211
- }))
208
+ // El token CSRF lo pone requestConfig, solo si no viaja en la query.
209
+ const response = await axios(core.requestConfig(props.policyMethod, props.policyUrl, { id }))
212
210
 
213
211
  allowed.value = { ...allowed.value, [key]: response.data ?? {} }
214
212
  } catch {
@@ -271,7 +269,9 @@ export default function useDataTable(props, { navigate = null, labels = core.DEF
271
269
 
272
270
  /**
273
271
  * Una acción masiva recibe los ids seleccionados —también los de otras
274
- * páginas— y las filas cargadas que están entre ellos.
272
+ * páginas—, las filas cargadas que están entre ellos y sus `params`. Con
273
+ * los params, una misma función sirve para varias acciones: marcar como
274
+ * publicado y marcar como borrador son la misma llamada con otro valor.
275
275
  */
276
276
  const runBulk = async (action) => {
277
277
  if (typeof props.model[action.callback] !== 'function') {
@@ -284,7 +284,7 @@ export default function useDataTable(props, { navigate = null, labels = core.DEF
284
284
  const loaded = table.getSelectedRowModel().rows.map((row) => row.original)
285
285
 
286
286
  try {
287
- await props.model[action.callback](ids, loaded)
287
+ await props.model[action.callback](ids, loaded, action.params ?? {})
288
288
  } catch (failure) {
289
289
  if (! core.isCancelled(failure)) {
290
290
  notifyError(core.describeError(failure, text(), text().actionFailed).message)