rl-core-front 0.1.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/README.md +129 -0
- package/package.json +113 -0
- package/src/_services/api/axios.factory.ts +97 -0
- package/src/_services/api/list-query.ts +26 -0
- package/src/_services/api/schema.d.ts +2527 -0
- package/src/_services/auth/index.ts +127 -0
- package/src/_utils/advanced-filter.ts +386 -0
- package/src/_utils/calendar.ts +103 -0
- package/src/_utils/crop-image.ts +65 -0
- package/src/_utils/filter.ts +313 -0
- package/src/_utils/format.ts +35 -0
- package/src/_utils/initials.ts +24 -0
- package/src/_utils/password.ts +7 -0
- package/src/components/app-shell.tsx +314 -0
- package/src/components/breadcrumbs.tsx +55 -0
- package/src/components/index.ts +7 -0
- package/src/components/language-selector.tsx +51 -0
- package/src/components/password-field.tsx +40 -0
- package/src/components/theme-toggle.tsx +27 -0
- package/src/components/ui/alert.tsx +27 -0
- package/src/components/ui/avatar.tsx +50 -0
- package/src/components/ui/badge.tsx +36 -0
- package/src/components/ui/button.tsx +62 -0
- package/src/components/ui/card.tsx +86 -0
- package/src/components/ui/checkbox.tsx +30 -0
- package/src/components/ui/column-visibility-modal.tsx +110 -0
- package/src/components/ui/confirm-dialog.tsx +69 -0
- package/src/components/ui/data-table.tsx +401 -0
- package/src/components/ui/date-range-picker.tsx +250 -0
- package/src/components/ui/dialog.tsx +138 -0
- package/src/components/ui/dropdown-menu.tsx +108 -0
- package/src/components/ui/field.tsx +20 -0
- package/src/components/ui/filter-chips.tsx +88 -0
- package/src/components/ui/filter-field.tsx +137 -0
- package/src/components/ui/filter-group.tsx +155 -0
- package/src/components/ui/filter-rule.tsx +221 -0
- package/src/components/ui/filter-sheet.tsx +201 -0
- package/src/components/ui/index.ts +30 -0
- package/src/components/ui/input.tsx +25 -0
- package/src/components/ui/label.tsx +23 -0
- package/src/components/ui/row-actions.tsx +32 -0
- package/src/components/ui/select.tsx +97 -0
- package/src/components/ui/separator.tsx +31 -0
- package/src/components/ui/sheet.tsx +119 -0
- package/src/components/ui/sonner.tsx +28 -0
- package/src/components/ui/spinner.tsx +12 -0
- package/src/components/ui/switch.tsx +29 -0
- package/src/components/ui/table.tsx +84 -0
- package/src/components/ui/tabs.tsx +52 -0
- package/src/components/user-avatar.tsx +54 -0
- package/src/contexts/auth-context.tsx +90 -0
- package/src/contexts/color-mode-context.tsx +105 -0
- package/src/contexts/i18n-context.tsx +76 -0
- package/src/contexts/index.ts +6 -0
- package/src/contexts/socket-context.tsx +153 -0
- package/src/contexts/toast-context.tsx +40 -0
- package/src/features/audit/audit-screen.tsx +299 -0
- package/src/features/audit/hooks/use-audit-trail.ts +68 -0
- package/src/features/audit/services/audit.service.ts +24 -0
- package/src/features/dashboard/dashboard-screen.tsx +73 -0
- package/src/features/login/components/backup-codes-dialog.tsx +66 -0
- package/src/features/login/components/index.ts +5 -0
- package/src/features/login/components/login-form.tsx +62 -0
- package/src/features/login/components/two-factor-setup.tsx +71 -0
- package/src/features/login/components/two-factor-verify.tsx +56 -0
- package/src/features/login/hooks/use-login-flow.ts +119 -0
- package/src/features/login/login-screen.tsx +83 -0
- package/src/features/login/validation/schemas.ts +30 -0
- package/src/features/logs/hooks/use-error-logs.ts +65 -0
- package/src/features/logs/hooks/use-request-logs.ts +65 -0
- package/src/features/logs/logs-screen.tsx +31 -0
- package/src/features/logs/panels/error-logs-panel.tsx +183 -0
- package/src/features/logs/panels/index.ts +3 -0
- package/src/features/logs/panels/request-logs-panel.tsx +124 -0
- package/src/features/logs/services/logs.service.ts +68 -0
- package/src/features/notifications/components/notification-item.tsx +67 -0
- package/src/features/notifications/hooks/use-notifications.ts +78 -0
- package/src/features/notifications/notifications-center.tsx +119 -0
- package/src/features/notifications/services/notifications.service.ts +38 -0
- package/src/features/profile/components/avatar-crop-dialog.tsx +133 -0
- package/src/features/profile/components/index.ts +2 -0
- package/src/features/profile/force-password-change.tsx +114 -0
- package/src/features/profile/profile-screen.tsx +385 -0
- package/src/features/rbac/panels/actions-panel.tsx +20 -0
- package/src/features/rbac/panels/catalog-panel.tsx +243 -0
- package/src/features/rbac/panels/groups-panel.tsx +269 -0
- package/src/features/rbac/panels/index.ts +8 -0
- package/src/features/rbac/panels/permissions-panel.tsx +204 -0
- package/src/features/rbac/panels/resources-panel.tsx +20 -0
- package/src/features/rbac/panels/roles-panel.tsx +224 -0
- package/src/features/rbac/panels/scopes-panel.tsx +20 -0
- package/src/features/rbac/rbac-screen.tsx +80 -0
- package/src/features/rbac/services/rbac.service.ts +110 -0
- package/src/features/recovery/forgot-password-screen.tsx +69 -0
- package/src/features/recovery/reset-password-screen.tsx +163 -0
- package/src/features/users/components/user-form-dialog.tsx +187 -0
- package/src/features/users/hooks/use-users.ts +181 -0
- package/src/features/users/services/users.service.ts +83 -0
- package/src/features/users/users-screen.tsx +331 -0
- package/src/hooks/use-column-visibility.ts +54 -0
- package/src/hooks/use-confirm.tsx +70 -0
- package/src/hooks/use-filter-schema.ts +85 -0
- package/src/hooks/use-filters.ts +148 -0
- package/src/hooks/use-list-query.ts +69 -0
- package/src/hooks/use-request.ts +114 -0
- package/src/i18n/messages/en.ts +296 -0
- package/src/i18n/messages/pt.ts +305 -0
- package/src/index.ts +60 -0
- package/src/lib/utils.ts +6 -0
- package/src/middleware.ts +42 -0
- package/src/styles/core.css +142 -0
- package/tailwind-preset.ts +103 -0
package/README.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# rl-core-front
|
|
2
|
+
|
|
3
|
+
As telas do core, em Next.js. Par do [`rl-core-api`](https://www.npmjs.com/package/rl-core-api).
|
|
4
|
+
|
|
5
|
+
Login com 2FA, usuários, RBAC, auditoria, logs e perfil — mais os primitivos de
|
|
6
|
+
UI e a `DataTable` com paginação, ordenação e filtro dinâmico.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install rl-core-front
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Instalação
|
|
13
|
+
|
|
14
|
+
O pacote é publicado em **código-fonte**, não compilado — são 72 arquivos com
|
|
15
|
+
`'use client'`, e compilar quebraria as diretivas. São três ajustes de
|
|
16
|
+
configuração, uma vez só.
|
|
17
|
+
|
|
18
|
+
**1. `next.config.ts`** — opta o pacote para dentro do compilador:
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
const nextConfig: NextConfig = {
|
|
22
|
+
transpilePackages: ["rl-core-front"],
|
|
23
|
+
};
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**2. `tailwind.config.ts`** — o preset traz o tema; o `content` precisa varrer
|
|
27
|
+
o pacote, senão as classes usadas por ele são descartadas e a interface sai sem
|
|
28
|
+
estilo:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import corePreset from "rl-core-front/tailwind-preset";
|
|
32
|
+
|
|
33
|
+
export default {
|
|
34
|
+
...corePreset,
|
|
35
|
+
content: [
|
|
36
|
+
"./src/**/*.{ts,tsx}",
|
|
37
|
+
"./node_modules/rl-core-front/src/**/*.{ts,tsx}",
|
|
38
|
+
],
|
|
39
|
+
} satisfies Config;
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**3. `globals.css`** — os tokens de cor:
|
|
43
|
+
|
|
44
|
+
```css
|
|
45
|
+
@import "tailwindcss";
|
|
46
|
+
@config "../../tailwind.config.ts";
|
|
47
|
+
@import "rl-core-front/styles.css";
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Uso
|
|
51
|
+
|
|
52
|
+
Cada rota fica com uma linha:
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
// src/app/(app)/usuarios/page.tsx
|
|
56
|
+
export { UsersScreen as default } from "rl-core-front";
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
O `layout.tsx` empilha os providers:
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
import {
|
|
63
|
+
AuthProvider,
|
|
64
|
+
ColorModeProvider,
|
|
65
|
+
I18nProvider,
|
|
66
|
+
SocketProvider,
|
|
67
|
+
ToastProvider,
|
|
68
|
+
} from "rl-core-front";
|
|
69
|
+
|
|
70
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
71
|
+
return (
|
|
72
|
+
<ColorModeProvider>
|
|
73
|
+
<I18nProvider>
|
|
74
|
+
<ToastProvider>
|
|
75
|
+
<AuthProvider>
|
|
76
|
+
<SocketProvider>{children}</SocketProvider>
|
|
77
|
+
</AuthProvider>
|
|
78
|
+
</ToastProvider>
|
|
79
|
+
</I18nProvider>
|
|
80
|
+
</ColorModeProvider>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
E o `middleware.ts` da raiz:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
export { config, middleware } from "rl-core-front/middleware";
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Nas suas telas
|
|
92
|
+
|
|
93
|
+
Os primitivos e hooks são os mesmos que as telas do core usam:
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
import { Button, DataTable, useListQuery } from "rl-core-front";
|
|
97
|
+
|
|
98
|
+
export function ContasScreen() {
|
|
99
|
+
const query = useListQuery("/contas/filter-schema");
|
|
100
|
+
return <DataTable columns={columns} {...query} />;
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`useListQuery` cuida de paginação, ordenação e filtro — inclusive o painel
|
|
105
|
+
lateral, os chips e a querystring, que sobrevive ao F5 e deixa o link
|
|
106
|
+
compartilhável. O catálogo de campos vem do backend; você não monta formulário
|
|
107
|
+
de filtro à mão.
|
|
108
|
+
|
|
109
|
+
## Traduções
|
|
110
|
+
|
|
111
|
+
As mensagens do core saem prontas e você mescla as suas:
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
import { en as coreEn, pt as corePt } from "rl-core-front";
|
|
115
|
+
|
|
116
|
+
export const messages = {
|
|
117
|
+
pt: { ...corePt, ...appPt },
|
|
118
|
+
en: { ...coreEn, ...appEn },
|
|
119
|
+
};
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Peer dependencies
|
|
123
|
+
|
|
124
|
+
`next`, `react`, `react-dom` e `tailwindcss` — precisam ser uma instância só.
|
|
125
|
+
O npm 7+ instala sozinho.
|
|
126
|
+
|
|
127
|
+
## Licença
|
|
128
|
+
|
|
129
|
+
MIT © Rodrigo Liberti
|
package/package.json
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rl-core-front",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Telas e componentes Next.js do core: login com 2FA, usu\u00e1rios, RBAC, auditoria, logs e listagens com filtro din\u00e2mico",
|
|
5
|
+
"author": "Rodrigo Liberti",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"nextjs",
|
|
9
|
+
"react",
|
|
10
|
+
"tailwind",
|
|
11
|
+
"auth",
|
|
12
|
+
"2fa",
|
|
13
|
+
"rbac",
|
|
14
|
+
"data-table",
|
|
15
|
+
"boilerplate"
|
|
16
|
+
],
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/liberti1991/aplication-core.git",
|
|
20
|
+
"directory": "packages/front"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://github.com/liberti1991/aplication-core#readme",
|
|
23
|
+
"bugs": "https://github.com/liberti1991/aplication-core/issues",
|
|
24
|
+
"imports": {
|
|
25
|
+
"#core/*": "./src/*"
|
|
26
|
+
},
|
|
27
|
+
"exports": {
|
|
28
|
+
".": "./src/index.ts",
|
|
29
|
+
"./middleware": "./src/middleware.ts",
|
|
30
|
+
"./styles.css": "./src/styles/core.css",
|
|
31
|
+
"./tailwind-preset": "./tailwind-preset.ts",
|
|
32
|
+
"./package.json": "./package.json"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"src",
|
|
36
|
+
"!src/app",
|
|
37
|
+
"!src/**/*.test.ts",
|
|
38
|
+
"!src/**/*.test.tsx",
|
|
39
|
+
"tailwind-preset.ts",
|
|
40
|
+
"README.md"
|
|
41
|
+
],
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"dev": "next dev -p 4000",
|
|
47
|
+
"build": "next build",
|
|
48
|
+
"prepublishOnly": "npm run lint && npx tsc --noEmit && npm test && npm run build",
|
|
49
|
+
"start": "next start -p 4000",
|
|
50
|
+
"lint": "eslint .",
|
|
51
|
+
"lint:fix": "eslint . --fix",
|
|
52
|
+
"openapi:types": "openapi-typescript ../../openapi.json -o src/_services/api/schema.d.ts",
|
|
53
|
+
"test": "vitest run",
|
|
54
|
+
"test:watch": "vitest"
|
|
55
|
+
},
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"next": "^16.3.1",
|
|
58
|
+
"react": "^19.2.8",
|
|
59
|
+
"react-dom": "^19.2.8",
|
|
60
|
+
"tailwindcss": "^4.3.3"
|
|
61
|
+
},
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"@hookform/resolvers": "^5.7.1",
|
|
64
|
+
"@radix-ui/react-avatar": "^1.2.6",
|
|
65
|
+
"@radix-ui/react-checkbox": "^1.3.11",
|
|
66
|
+
"@radix-ui/react-dialog": "^1.1.23",
|
|
67
|
+
"@radix-ui/react-dropdown-menu": "^2.1.24",
|
|
68
|
+
"@radix-ui/react-label": "^2.1.15",
|
|
69
|
+
"@radix-ui/react-select": "^2.3.7",
|
|
70
|
+
"@radix-ui/react-separator": "^1.1.15",
|
|
71
|
+
"@radix-ui/react-slot": "^1.3.3",
|
|
72
|
+
"@radix-ui/react-switch": "^1.3.7",
|
|
73
|
+
"@radix-ui/react-tabs": "^1.1.21",
|
|
74
|
+
"@tanstack/react-table": "^9.1.0",
|
|
75
|
+
"axios": "^1.19.0",
|
|
76
|
+
"class-variance-authority": "^0.7.1",
|
|
77
|
+
"clsx": "^2.1.1",
|
|
78
|
+
"lucide-react": "^1.30.0",
|
|
79
|
+
"react-easy-crop": "^6.2.3",
|
|
80
|
+
"react-hook-form": "^7.85.0",
|
|
81
|
+
"socket.io-client": "^4.8.3",
|
|
82
|
+
"sonner": "^2.0.7",
|
|
83
|
+
"tailwind-merge": "^3.6.0",
|
|
84
|
+
"tailwindcss-animate": "^1.0.7",
|
|
85
|
+
"yup": "^1.7.1"
|
|
86
|
+
},
|
|
87
|
+
"devDependencies": {
|
|
88
|
+
"@eslint/eslintrc": "^3.3.6",
|
|
89
|
+
"@tailwindcss/postcss": "^4.3.3",
|
|
90
|
+
"@testing-library/dom": "^10.4.1",
|
|
91
|
+
"@testing-library/react": "^16.3.2",
|
|
92
|
+
"@types/node": "^26.2.0",
|
|
93
|
+
"@types/react": "^19.2.18",
|
|
94
|
+
"@types/react-dom": "^19.2.4",
|
|
95
|
+
"@vitejs/plugin-react": "^6.0.5",
|
|
96
|
+
"autoprefixer": "^10.5.4",
|
|
97
|
+
"eslint": "^10.8.1",
|
|
98
|
+
"eslint-config-next": "^16.3.1",
|
|
99
|
+
"eslint-plugin-simple-import-sort": "^14.0.0",
|
|
100
|
+
"jsdom": "^30.0.1",
|
|
101
|
+
"next": "^16.3.1",
|
|
102
|
+
"openapi-typescript": "^7.13.0",
|
|
103
|
+
"postcss": "^8.5.26",
|
|
104
|
+
"react": "^19.2.8",
|
|
105
|
+
"react-dom": "^19.2.8",
|
|
106
|
+
"tailwindcss": "^4.3.3",
|
|
107
|
+
"typescript": "~6.0.3",
|
|
108
|
+
"vitest": "^4.1.10"
|
|
109
|
+
},
|
|
110
|
+
"engines": {
|
|
111
|
+
"node": ">=20"
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import axios, {
|
|
2
|
+
AxiosError,
|
|
3
|
+
AxiosInstance,
|
|
4
|
+
InternalAxiosRequestConfig,
|
|
5
|
+
} from "axios";
|
|
6
|
+
|
|
7
|
+
const BASE_URL =
|
|
8
|
+
process.env.NEXT_PUBLIC_API_BASE_URL ?? "http://localhost:3108/api/v1";
|
|
9
|
+
|
|
10
|
+
const PUBLIC_PATHS = ["/login", "/forgot-password", "/reset-password"];
|
|
11
|
+
|
|
12
|
+
function isPublicPath(pathname: string): boolean {
|
|
13
|
+
return PUBLIC_PATHS.some((p) => pathname.startsWith(p));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Cria uma instância axios com:
|
|
18
|
+
* - withCredentials (envia/recebe cookies httpOnly de auth);
|
|
19
|
+
* - refresh automático e único em caso de 401 (rotaciona o refresh token).
|
|
20
|
+
*/
|
|
21
|
+
export class AxiosFactory {
|
|
22
|
+
private isRefreshing = false;
|
|
23
|
+
private queue: Array<(ok: boolean) => void> = [];
|
|
24
|
+
|
|
25
|
+
make(): AxiosInstance {
|
|
26
|
+
const api = axios.create({
|
|
27
|
+
baseURL: BASE_URL,
|
|
28
|
+
withCredentials: true,
|
|
29
|
+
headers: { "Content-Type": "application/json" },
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
api.interceptors.response.use(
|
|
33
|
+
(res) => res,
|
|
34
|
+
async (error: AxiosError) => {
|
|
35
|
+
const original = error.config as InternalAxiosRequestConfig & {
|
|
36
|
+
_retry?: boolean;
|
|
37
|
+
};
|
|
38
|
+
const status = error.response?.status;
|
|
39
|
+
const url = original?.url ?? "";
|
|
40
|
+
|
|
41
|
+
const isAuthRoute =
|
|
42
|
+
url.includes("/auth/login") ||
|
|
43
|
+
url.includes("/auth/refresh") ||
|
|
44
|
+
url.includes("/auth/2fa");
|
|
45
|
+
|
|
46
|
+
if (status === 401 && !original?._retry && !isAuthRoute) {
|
|
47
|
+
original._retry = true;
|
|
48
|
+
const refreshed = await this.refresh(api);
|
|
49
|
+
if (refreshed) {
|
|
50
|
+
return api(original);
|
|
51
|
+
}
|
|
52
|
+
if (
|
|
53
|
+
typeof window !== "undefined" &&
|
|
54
|
+
!isPublicPath(window.location.pathname)
|
|
55
|
+
) {
|
|
56
|
+
// Limpa os cookies httpOnly no servidor antes de redirecionar — sem isso o
|
|
57
|
+
// middleware ainda vê os cookies (inválidos, mas presentes) e manda de volta
|
|
58
|
+
// pro dashboard, gerando loop entre /dashboard e /login?reason=expired.
|
|
59
|
+
await api.post("/auth/logout").catch(() => {});
|
|
60
|
+
window.location.href = "/login?reason=expired";
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return Promise.reject(error);
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
return api;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private refresh(api: AxiosInstance): Promise<boolean> {
|
|
71
|
+
if (this.isRefreshing) {
|
|
72
|
+
return new Promise((resolve) => this.queue.push(resolve));
|
|
73
|
+
}
|
|
74
|
+
this.isRefreshing = true;
|
|
75
|
+
return api
|
|
76
|
+
.post("/auth/refresh")
|
|
77
|
+
.then(() => {
|
|
78
|
+
this.flush(true);
|
|
79
|
+
return true;
|
|
80
|
+
})
|
|
81
|
+
.catch(() => {
|
|
82
|
+
this.flush(false);
|
|
83
|
+
return false;
|
|
84
|
+
})
|
|
85
|
+
.finally(() => {
|
|
86
|
+
this.isRefreshing = false;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private flush(ok: boolean): void {
|
|
91
|
+
this.queue.forEach((cb) => cb(ok));
|
|
92
|
+
this.queue = [];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Instância única reutilizável da API. */
|
|
97
|
+
export const api = new AxiosFactory().make();
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query de uma listagem paginada, como a tabela a enxerga.
|
|
3
|
+
*
|
|
4
|
+
* Existe para os `.service.ts` pararem de crescer em parâmetros posicionais —
|
|
5
|
+
* eram cinco e o filtro seria o sexto.
|
|
6
|
+
*/
|
|
7
|
+
export interface ListQuery {
|
|
8
|
+
/** Base 0, como o `PaginationState` da tabela. */
|
|
9
|
+
page: number;
|
|
10
|
+
limit: number;
|
|
11
|
+
search?: string;
|
|
12
|
+
sortBy?: string;
|
|
13
|
+
sortDir?: "ASC" | "DESC";
|
|
14
|
+
/** JSON do filtro dinâmico, montado pelo `useFilters`. */
|
|
15
|
+
filter?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** `ListQuery` → params do axios. A API pagina a partir de 1. */
|
|
19
|
+
export const toListParams = (query: ListQuery): Record<string, unknown> => ({
|
|
20
|
+
page: query.page + 1,
|
|
21
|
+
limit: query.limit,
|
|
22
|
+
search: query.search || undefined,
|
|
23
|
+
sortBy: query.sortBy,
|
|
24
|
+
sortDir: query.sortDir,
|
|
25
|
+
filter: query.filter,
|
|
26
|
+
});
|