FesaComponents
DataTableRecetas

Acciones masivas

Operar sobre la selección de la página o sobre todo el resultado filtrado.

Sobre la selección

const bulkActions = useCallback((rows: UserRow[]) => (
  <>
    <Button size="sm" variant="outline" onClick={() => blockMany(rows.map(r => r.id))}>
      Bloquear
    </Button>
    <Button size="sm" variant="destructive" onClick={() => deleteMany(rows.map(r => r.id))}>
      Eliminar
    </Button>
  </>
), [blockMany, deleteMany]);

<SmartDataTable
  selection={{ enabled: true, showCheckbox: true }}
  bulkActions={bulkActions}
  /* … */
/>

// En la mutación:
const blockMany = useCallback((ids: string[]) => {
  startTransition(async () => {
    const result = await bulkBlockUsersAction(ids);
    if (result.error) return toast.error(result.error);
    toast.success(result.success);
    tableRef.current?.clearSelection();
    void tableRef.current?.silentRefetch();
  });
}, []);

La barra integrada pone el contador y el botón de limpiar; tú solo aportas los botones.

Sobre TODO el filtro (estilo bandeja de correo)

Cuando el usuario marca la página completa y hay más resultados, la barra ofrece "Seleccionar los N que coinciden". Tu callback recibe el contexto:

const bulkActions = useCallback((rows: UserRow[], context: BulkActionsContext) => (
  <Button size="sm" variant="outline" onClick={() => {
    if (context.allMatchingSelected) {
      // Operar en servidor sobre el FILTRO, no sobre ids
      void blockAllMatchingAction({ q: searchTextRef.current, ...filterValuesRef.current });
    } else {
      void bulkBlockUsersAction(rows.map(r => r.id));
    }
  }}>
    Bloquear {context.allMatchingSelected ? `los ${context.totalRows}` : rows.length}
  </Button>
), []);

La parte importante

Con allMatchingSelected los ids de la página no bastan: la action del servidor debe reconstruir el where con los mismos filtros y operar ahí. Por eso una búsqueda nueva limpia la selección — evita operar sobre filas que ya no coinciden.

En esta pagina