forlogic-core 1.5.2 → 1.5.4
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 +612 -20
- package/dist/README.md +550 -170
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/index.esm.js +2 -2
- package/dist/index.js +2 -2
- package/package.json +1 -1
- package/dist/docs/AI_RULES.md +0 -213
- package/dist/docs/README.md +0 -102
- package/dist/docs/TROUBLESHOOTING.md +0 -473
- package/dist/docs/architecture/TOKENS_ARCHITECTURE.md +0 -712
- package/dist/docs/templates/app-layout.tsx +0 -192
- package/dist/docs/templates/basic-crud-page.tsx +0 -97
- package/dist/docs/templates/basic-crud-working.tsx +0 -182
- package/dist/docs/templates/complete-crud-example.tsx +0 -307
- package/dist/docs/templates/custom-form.tsx +0 -99
- package/dist/docs/templates/custom-service.tsx +0 -194
- package/dist/docs/templates/permission-check-hook.tsx +0 -275
- package/dist/docs/templates/qualiex-config.ts +0 -137
- package/dist/docs/templates/qualiex-integration-example.tsx +0 -430
- package/dist/docs/templates/quick-start-example.tsx +0 -96
- package/dist/docs/templates/rls-policies.sql +0 -80
- package/dist/docs/templates/sidebar-config.tsx +0 -145
package/dist/README.md
CHANGED
|
@@ -1,248 +1,628 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 🧱 Projeto atual
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
**Schema padrão:** `central`
|
|
4
|
+
|
|
5
|
+
> **Nota sobre schemas:** O schema padrão é `central`, mas módulos podem usar schemas diferentes quando necessário para organização ou isolamento de dados. Por exemplo, o módulo de treinamentos usa o schema `trainings`. Você pode especificar o schema no service usando `schemaName: 'nome_do_schema'`.
|
|
5
6
|
|
|
6
7
|
---
|
|
7
8
|
|
|
8
|
-
##
|
|
9
|
+
## 🤖 REGRAS CRÍTICAS
|
|
10
|
+
|
|
11
|
+
### ⚠️ TOP 3 ERROS
|
|
12
|
+
|
|
13
|
+
1. **ESQUECER SCHEMA**
|
|
14
|
+
```typescript
|
|
15
|
+
// ❌ ERRADO
|
|
16
|
+
.from('table')
|
|
9
17
|
|
|
10
|
-
|
|
11
|
-
|
|
18
|
+
// ✅ CORRETO
|
|
19
|
+
.schema('schema').from('table')
|
|
12
20
|
```
|
|
13
21
|
|
|
14
|
-
|
|
22
|
+
2. **RLS COM `id` (sem alias)**
|
|
23
|
+
```sql
|
|
24
|
+
-- ❌ ERRADO
|
|
25
|
+
CREATE POLICY "Users view own" ON schema.table
|
|
26
|
+
FOR SELECT USING (id_user = auth.uid());
|
|
27
|
+
|
|
28
|
+
-- ✅ CORRETO
|
|
29
|
+
CREATE POLICY "Users view own" ON schema.table
|
|
30
|
+
FOR SELECT USING (alias = auth.uid());
|
|
31
|
+
```
|
|
15
32
|
|
|
16
|
-
|
|
33
|
+
3. **NÃO CRIAR ÍNDICES AUTOMATICAMENTE**
|
|
34
|
+
```sql
|
|
35
|
+
-- ❌ PROIBIDO criar índices sem aprovação
|
|
36
|
+
CREATE INDEX idx_table_user ON schema.table(id_user);
|
|
17
37
|
|
|
18
|
-
|
|
19
|
-
npm install react react-dom react-router-dom @tanstack/react-query
|
|
38
|
+
-- ✅ Apenas quando solicitado explicitamente
|
|
20
39
|
```
|
|
21
40
|
|
|
22
41
|
---
|
|
23
42
|
|
|
24
|
-
|
|
43
|
+
### ✅ CHECKLIST (antes de implementar)
|
|
25
44
|
|
|
26
|
-
|
|
45
|
+
- [ ] Schema `schema` especificado em queries e service?
|
|
46
|
+
- [ ] RLS usando `alias` (nunca `id_user`)?
|
|
47
|
+
- [ ] Preservar `item.id` no update?
|
|
48
|
+
- [ ] Config gerado com `useMemo()`?
|
|
49
|
+
- [ ] `<Outlet />` no componente pai?
|
|
50
|
+
- [ ] Qualiex header `un-alias` configurado?
|
|
27
51
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
import 'forlogic-core/index.css';
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## 🚀 QUICK START - Criar CRUD Completo
|
|
32
55
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
56
|
+
### **1️⃣ Type**
|
|
57
|
+
```typescript
|
|
58
|
+
// src/processes/process.ts
|
|
59
|
+
export interface Process {
|
|
60
|
+
id: string;
|
|
61
|
+
id_user: string | null;
|
|
62
|
+
title: string;
|
|
63
|
+
description?: string;
|
|
64
|
+
status: 'draft' | 'active' | 'completed';
|
|
65
|
+
created_at?: string;
|
|
66
|
+
updated_at?: string;
|
|
67
|
+
deleted?: boolean;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export type ProcessInsert = Omit<Process, 'id' | 'created_at' | 'updated_at'>;
|
|
71
|
+
export type ProcessUpdate = Partial<ProcessInsert>;
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### **2️⃣ Service**
|
|
75
|
+
```typescript
|
|
76
|
+
// src/processes/processService.ts
|
|
77
|
+
import { createSimpleService } from 'forlogic-core';
|
|
78
|
+
import { Process, ProcessInsert, ProcessUpdate } from './process';
|
|
79
|
+
|
|
80
|
+
export const { service: processService, useCrudHook: useProcesses } =
|
|
81
|
+
createSimpleService<Process, ProcessInsert, ProcessUpdate>({
|
|
82
|
+
tableName: 'processes',
|
|
83
|
+
entityName: 'processo',
|
|
84
|
+
schemaName: 'central', // ⚠️ OBRIGATÓRIO
|
|
85
|
+
searchFields: ['title', 'description'],
|
|
86
|
+
enableQualiexEnrichment: true
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### **3️⃣ Save Handler**
|
|
91
|
+
```typescript
|
|
92
|
+
// src/processes/ProcessesPage.tsx
|
|
93
|
+
import { createSimpleSaveHandler } from 'forlogic-core';
|
|
94
|
+
import { processService } from './processService';
|
|
95
|
+
|
|
96
|
+
const handleSave = createSimpleSaveHandler({
|
|
97
|
+
service: processService,
|
|
98
|
+
entityName: 'processo'
|
|
36
99
|
});
|
|
100
|
+
|
|
101
|
+
// ⚠️ CRÍTICO: Preservar ID no update
|
|
102
|
+
const handleUpdate = async (item: Process) => {
|
|
103
|
+
await handleSave({
|
|
104
|
+
...item,
|
|
105
|
+
id: item.id // ⚠️ OBRIGATÓRIO para update
|
|
106
|
+
});
|
|
107
|
+
};
|
|
37
108
|
```
|
|
38
109
|
|
|
39
|
-
###
|
|
110
|
+
### **4️⃣ Config (com useMemo)**
|
|
111
|
+
```typescript
|
|
112
|
+
// src/processes/ProcessesPage.tsx
|
|
113
|
+
import { useMemo } from 'react';
|
|
114
|
+
import { generateCrudConfig } from 'forlogic-core';
|
|
115
|
+
|
|
116
|
+
export default function ProcessesPage() {
|
|
117
|
+
const crud = useProcesses();
|
|
118
|
+
|
|
119
|
+
// ⚠️ OBRIGATÓRIO useMemo para evitar re-renders
|
|
120
|
+
const config = useMemo(() =>
|
|
121
|
+
generateCrudConfig<Process>({
|
|
122
|
+
entity: 'processo',
|
|
123
|
+
columns: [
|
|
124
|
+
{ key: 'title', label: 'Título', type: 'text', required: true },
|
|
125
|
+
{ key: 'status', label: 'Status', type: 'select',
|
|
126
|
+
options: [
|
|
127
|
+
{ value: 'draft', label: 'Rascunho' },
|
|
128
|
+
{ value: 'active', label: 'Ativo' },
|
|
129
|
+
{ value: 'completed', label: 'Concluído' }
|
|
130
|
+
]
|
|
131
|
+
},
|
|
132
|
+
{ key: 'description', label: 'Descrição', type: 'textarea' }
|
|
133
|
+
]
|
|
134
|
+
}),
|
|
135
|
+
[]);
|
|
136
|
+
|
|
137
|
+
return createCrudPage({
|
|
138
|
+
config,
|
|
139
|
+
crud,
|
|
140
|
+
saveHandler: handleSave
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
```
|
|
40
144
|
|
|
41
|
-
|
|
145
|
+
### **5️⃣ Page + Outlet (preservar estado)**
|
|
146
|
+
```typescript
|
|
42
147
|
// src/App.tsx
|
|
43
|
-
import {
|
|
44
|
-
import { BrowserRouter } from 'react-router-dom';
|
|
148
|
+
import { Outlet } from 'react-router-dom';
|
|
45
149
|
|
|
46
150
|
function App() {
|
|
47
151
|
return (
|
|
48
|
-
<
|
|
49
|
-
<
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
</QueryClientProvider>
|
|
56
|
-
</ErrorBoundary>
|
|
152
|
+
<Routes>
|
|
153
|
+
<Route path="/processes" element={<ProcessLayout />}>
|
|
154
|
+
<Route index element={<ProcessesPage />} />
|
|
155
|
+
<Route path="new" element={<ProcessesPage />} />
|
|
156
|
+
<Route path=":id/edit" element={<ProcessesPage />} />
|
|
157
|
+
</Route>
|
|
158
|
+
</Routes>
|
|
57
159
|
);
|
|
58
160
|
}
|
|
161
|
+
|
|
162
|
+
// ⚠️ OBRIGATÓRIO: Outlet preserva estado
|
|
163
|
+
function ProcessLayout() {
|
|
164
|
+
return <Outlet />; // Evita reload do componente
|
|
165
|
+
}
|
|
59
166
|
```
|
|
60
167
|
|
|
61
|
-
|
|
168
|
+
---
|
|
62
169
|
|
|
63
|
-
|
|
64
|
-
// src/employees/EmployeesPage.tsx
|
|
65
|
-
import { createCrudPage, createSimpleService, useCrud } from 'forlogic-core';
|
|
170
|
+
## 🎯 AÇÕES EM LOTE (Bulk Actions)
|
|
66
171
|
|
|
67
|
-
|
|
68
|
-
interface Employee {
|
|
69
|
-
id?: string;
|
|
70
|
-
name: string;
|
|
71
|
-
email: string;
|
|
72
|
-
role: string;
|
|
73
|
-
}
|
|
172
|
+
O sistema CRUD suporta seleção múltipla e ações em lote usando checkboxes.
|
|
74
173
|
|
|
75
|
-
|
|
76
|
-
const service = createSimpleService<Employee>('employees', {
|
|
77
|
-
schemaName: 'central' // Usar schema do seu projeto
|
|
78
|
-
});
|
|
174
|
+
### **Habilitar Bulk Actions**
|
|
79
175
|
|
|
80
|
-
|
|
81
|
-
const useEmployeesCrud = () => useCrud({
|
|
82
|
-
service,
|
|
83
|
-
queryKey: 'employees'
|
|
84
|
-
});
|
|
176
|
+
Adicione `enableBulkActions: true` na configuração:
|
|
85
177
|
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
]
|
|
178
|
+
```typescript
|
|
179
|
+
const config = useMemo((): CrudPageConfig<Process> => ({
|
|
180
|
+
entityName: "Processo",
|
|
181
|
+
entityNamePlural: "Processos",
|
|
182
|
+
enableBulkActions: true, // 🆕 Habilita checkboxes e ações em lote
|
|
183
|
+
columns: [...],
|
|
184
|
+
formSections: [...]
|
|
185
|
+
}), []);
|
|
186
|
+
```
|
|
92
187
|
|
|
93
|
-
|
|
94
|
-
{
|
|
95
|
-
title: 'Dados do Funcionário',
|
|
96
|
-
fields: [
|
|
97
|
-
{ name: 'name', label: 'Nome', type: 'text', required: true },
|
|
98
|
-
{ name: 'email', label: 'Email', type: 'email', required: true },
|
|
99
|
-
{ name: 'role', label: 'Cargo', type: 'text', required: true }
|
|
100
|
-
]
|
|
101
|
-
}
|
|
102
|
-
];
|
|
188
|
+
### **Ações Padrão Disponíveis**
|
|
103
189
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
190
|
+
Quando `enableBulkActions: true`, as seguintes ações ficam disponíveis automaticamente:
|
|
191
|
+
|
|
192
|
+
- ✅ **Deletar em lote**: Deleta múltiplos itens selecionados (usa a mesma função do delete individual)
|
|
193
|
+
- ✅ **Checkbox "Selecionar Todos"**: No header da tabela para selecionar/deselecionar todos os itens da página
|
|
194
|
+
- ✅ **Indicador Visual**: Linhas/cards selecionados têm background destacado
|
|
195
|
+
- ✅ **Barra de Ações**: Aparece no topo quando há itens selecionados
|
|
196
|
+
|
|
197
|
+
### **Ações Customizadas**
|
|
198
|
+
|
|
199
|
+
Você pode adicionar ações customizadas além do delete padrão:
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
import { Download, Archive } from 'lucide-react';
|
|
203
|
+
import type { BulkAction } from 'forlogic-core';
|
|
204
|
+
|
|
205
|
+
const config: CrudPageConfig<Process> = {
|
|
206
|
+
entityName: "Processo",
|
|
207
|
+
entityNamePlural: "Processos",
|
|
208
|
+
enableBulkActions: true,
|
|
209
|
+
bulkActions: [
|
|
210
|
+
{
|
|
211
|
+
label: "Exportar Selecionados",
|
|
212
|
+
icon: Download,
|
|
213
|
+
variant: "outline", // 'default' | 'destructive' | 'outline'
|
|
214
|
+
action: async (items) => {
|
|
215
|
+
// Sua lógica de exportação
|
|
216
|
+
const selectedItems = manager.entities.filter(
|
|
217
|
+
e => manager.selectedIds.includes(e.id)
|
|
218
|
+
);
|
|
219
|
+
await exportToExcel(selectedItems);
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
label: "Arquivar",
|
|
224
|
+
icon: Archive,
|
|
225
|
+
confirmMessage: "Tem certeza que deseja arquivar os itens selecionados?",
|
|
226
|
+
action: async (items) => {
|
|
227
|
+
const selectedItems = manager.entities.filter(
|
|
228
|
+
e => manager.selectedIds.includes(e.id)
|
|
229
|
+
);
|
|
230
|
+
await bulkArchive(selectedItems.map(i => i.id));
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
],
|
|
234
|
+
columns: [...]
|
|
235
|
+
};
|
|
111
236
|
```
|
|
112
237
|
|
|
113
|
-
|
|
238
|
+
### **Comportamento**
|
|
114
239
|
|
|
115
|
-
|
|
240
|
+
**Desktop (Tabela):**
|
|
241
|
+
- Checkbox na primeira coluna (50px de largura)
|
|
242
|
+
- Checkbox no header para "Selecionar Todos"
|
|
243
|
+
- Click na linha NÃO abre o form quando bulk actions está ativo (evita edição acidental)
|
|
116
244
|
|
|
117
|
-
|
|
245
|
+
**Mobile (Cards):**
|
|
246
|
+
- Checkbox no canto superior esquerdo de cada card
|
|
247
|
+
- Mesmo comportamento de seleção que desktop
|
|
118
248
|
|
|
119
|
-
|
|
120
|
-
-
|
|
121
|
-
-
|
|
122
|
-
-
|
|
249
|
+
**Barra de Ações:**
|
|
250
|
+
- Aparece automaticamente quando há itens selecionados
|
|
251
|
+
- Mostra quantidade de itens selecionados
|
|
252
|
+
- Botão "Limpar" para deselecionar todos
|
|
253
|
+
- Botões de ações (Delete + customizadas)
|
|
123
254
|
|
|
124
|
-
|
|
255
|
+
### **Propriedades do Manager**
|
|
256
|
+
|
|
257
|
+
Quando bulk actions está habilitado, o `manager` retornado pelo `useCrud` inclui:
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
manager.selectedIds // string[] - IDs dos itens selecionados
|
|
261
|
+
manager.selectItem(id) // Seleciona/desseleciona um item
|
|
262
|
+
manager.selectAll() // Seleciona/desseleciona todos
|
|
263
|
+
manager.clearSelection() // Limpa seleção
|
|
264
|
+
manager.isAllSelected // boolean - Todos selecionados?
|
|
265
|
+
manager.bulkDelete(ids) // Deleta múltiplos IDs
|
|
266
|
+
manager.isBulkDeleting // boolean - Loading do bulk delete
|
|
267
|
+
```
|
|
125
268
|
|
|
126
|
-
|
|
269
|
+
### **Exemplo Completo**
|
|
127
270
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
271
|
+
```typescript
|
|
272
|
+
// src/processes/ProcessesPage.tsx
|
|
273
|
+
import { useMemo } from 'react';
|
|
274
|
+
import { createCrudPage, createSimpleSaveHandler, type BulkAction } from 'forlogic-core';
|
|
275
|
+
import { Download, Archive } from 'lucide-react';
|
|
276
|
+
import { Process } from './process';
|
|
277
|
+
import { useProcessCrud } from './processService';
|
|
278
|
+
import { exportToExcel } from '@/utils/export';
|
|
133
279
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
- Proteção de rotas
|
|
137
|
-
- Integração com Supabase Auth
|
|
138
|
-
- Gerenciamento de tokens
|
|
280
|
+
export function ProcessesPage() {
|
|
281
|
+
const manager = useProcessCrud();
|
|
139
282
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
283
|
+
const bulkActions: BulkAction<Process>[] = useMemo(() => [
|
|
284
|
+
{
|
|
285
|
+
label: "Exportar",
|
|
286
|
+
icon: Download,
|
|
287
|
+
variant: "outline",
|
|
288
|
+
action: async () => {
|
|
289
|
+
const selected = manager.entities.filter(
|
|
290
|
+
e => manager.selectedIds.includes(e.id)
|
|
291
|
+
);
|
|
292
|
+
await exportToExcel(selected);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
], [manager.entities, manager.selectedIds]);
|
|
296
|
+
|
|
297
|
+
const config = useMemo(() => ({
|
|
298
|
+
entityName: "Processo",
|
|
299
|
+
entityNamePlural: "Processos",
|
|
300
|
+
enableBulkActions: true,
|
|
301
|
+
bulkActions,
|
|
302
|
+
columns: [
|
|
303
|
+
{ key: 'title', header: 'Título', sortable: true },
|
|
304
|
+
{ key: 'status', header: 'Status', className: 'w-24 text-center' },
|
|
305
|
+
],
|
|
306
|
+
formSections: [...]
|
|
307
|
+
}), [bulkActions]);
|
|
308
|
+
|
|
309
|
+
const handleSave = createSimpleSaveHandler(
|
|
310
|
+
manager,
|
|
311
|
+
(data) => ({ ...data }),
|
|
312
|
+
(data) => ({ ...data })
|
|
313
|
+
);
|
|
145
314
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
315
|
+
const CrudPage = createCrudPage({
|
|
316
|
+
manager,
|
|
317
|
+
config,
|
|
318
|
+
onSave: handleSave
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
return <CrudPage />;
|
|
322
|
+
}
|
|
323
|
+
```
|
|
150
324
|
|
|
151
325
|
---
|
|
152
326
|
|
|
153
|
-
|
|
327
|
+
### 🔗 Integração Qualiex (opcional)
|
|
154
328
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
} from 'forlogic-core';
|
|
329
|
+
**Auto-enrichment** (já configurado no BaseService):
|
|
330
|
+
```typescript
|
|
331
|
+
// ✅ Automático - dados enriquecidos com nome do usuário
|
|
332
|
+
const processes = await processService.getAll();
|
|
333
|
+
// processes[0].usuario_nome = "João Silva" (se enableQualiexEnrichment: true)
|
|
334
|
+
```
|
|
162
335
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
336
|
+
**Componentes prontos:**
|
|
337
|
+
```typescript
|
|
338
|
+
import { QualiexUserField, QualiexResponsibleSelectField } from 'forlogic-core';
|
|
339
|
+
|
|
340
|
+
// Select de usuários Qualiex
|
|
341
|
+
<QualiexResponsibleSelectField
|
|
342
|
+
value={form.watch('id_user')}
|
|
343
|
+
onChange={(userId) => form.setValue('id_user', userId)}
|
|
344
|
+
/>
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
**Componentes em formulários CRUD:**
|
|
348
|
+
```typescript
|
|
349
|
+
// Para seleção de usuário
|
|
350
|
+
{
|
|
351
|
+
name: 'responsible_id',
|
|
352
|
+
label: 'Responsável',
|
|
353
|
+
type: 'simple-qualiex-user-field' as const,
|
|
354
|
+
required: true
|
|
169
355
|
}
|
|
170
356
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
357
|
+
// Para seleção de responsável
|
|
358
|
+
{
|
|
359
|
+
name: 'responsible_id',
|
|
360
|
+
label: 'Responsável',
|
|
361
|
+
type: 'single-responsible-select' as const,
|
|
362
|
+
required: true
|
|
363
|
+
}
|
|
364
|
+
```
|
|
174
365
|
|
|
175
|
-
|
|
366
|
+
**Componentes customizados:**
|
|
176
367
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
{
|
|
196
|
-
name: 'status',
|
|
197
|
-
label: 'Status',
|
|
198
|
-
type: 'select',
|
|
199
|
-
options: [
|
|
200
|
-
{ value: 'pending', label: 'Pendente' },
|
|
201
|
-
{ value: 'in_progress', label: 'Em Progresso' },
|
|
202
|
-
{ value: 'done', label: 'Concluído' }
|
|
203
|
-
]
|
|
204
|
-
}
|
|
205
|
-
]
|
|
206
|
-
}
|
|
207
|
-
],
|
|
208
|
-
title: 'Tarefas'
|
|
209
|
-
});
|
|
368
|
+
Você pode criar e usar componentes customizados nos formulários para necessidades específicas:
|
|
369
|
+
|
|
370
|
+
```typescript
|
|
371
|
+
// Exemplo de campo customizado
|
|
372
|
+
{
|
|
373
|
+
name: 'custom_field',
|
|
374
|
+
label: 'Campo Customizado',
|
|
375
|
+
type: 'my-custom-component' as const,
|
|
376
|
+
required: true
|
|
377
|
+
}
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
> **Nota:** Componentes customizados devem ser registrados no `BaseForm.tsx` para funcionarem corretamente nos formulários CRUD.
|
|
381
|
+
|
|
382
|
+
**⚠️ CRÍTICO:** Requests Qualiex exigem header `un-alias`:
|
|
383
|
+
```typescript
|
|
384
|
+
// ✅ Já configurado no BaseService automaticamente
|
|
385
|
+
headers: { 'un-alias': 'true' }
|
|
210
386
|
```
|
|
211
387
|
|
|
212
388
|
---
|
|
213
389
|
|
|
214
|
-
##
|
|
390
|
+
## 🗃️ MIGRATIONS + RLS
|
|
391
|
+
|
|
392
|
+
### Template SQL Completo
|
|
393
|
+
```sql
|
|
394
|
+
-- 1️⃣ Criar tabela
|
|
395
|
+
CREATE TABLE central.processes (
|
|
396
|
+
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
|
397
|
+
id_user UUID, -- ⚠️ NUNCA foreign key para auth.users
|
|
398
|
+
title TEXT NOT NULL,
|
|
399
|
+
description TEXT,
|
|
400
|
+
status TEXT DEFAULT 'draft',
|
|
401
|
+
created_at TIMESTAMPTZ DEFAULT now(),
|
|
402
|
+
updated_at TIMESTAMPTZ DEFAULT now(),
|
|
403
|
+
deleted BOOLEAN DEFAULT false
|
|
404
|
+
);
|
|
405
|
+
|
|
406
|
+
-- 2️⃣ Habilitar RLS
|
|
407
|
+
ALTER TABLE central.processes ENABLE ROW LEVEL SECURITY;
|
|
408
|
+
|
|
409
|
+
-- 3️⃣ Políticas RLS (usar 'alias', nunca 'id_user')
|
|
410
|
+
CREATE POLICY "Users view own processes"
|
|
411
|
+
ON central.processes FOR SELECT
|
|
412
|
+
USING (alias = auth.uid()); -- ⚠️ 'alias', não 'id_user'
|
|
413
|
+
|
|
414
|
+
CREATE POLICY "Users insert own processes"
|
|
415
|
+
ON central.processes FOR INSERT
|
|
416
|
+
WITH CHECK (alias = auth.uid());
|
|
417
|
+
|
|
418
|
+
CREATE POLICY "Users update own processes"
|
|
419
|
+
ON central.processes FOR UPDATE
|
|
420
|
+
USING (alias = auth.uid());
|
|
421
|
+
|
|
422
|
+
-- 4️⃣ Trigger updated_at
|
|
423
|
+
CREATE TRIGGER set_updated_at
|
|
424
|
+
BEFORE UPDATE ON central.processes
|
|
425
|
+
FOR EACH ROW
|
|
426
|
+
EXECUTE FUNCTION public.set_updated_at();
|
|
427
|
+
|
|
428
|
+
-- 5️⃣ Índices (apenas se necessário)
|
|
429
|
+
-- ⚠️ NÃO criar automaticamente - solicitar aprovação
|
|
430
|
+
-- CREATE INDEX idx_processes_user ON central.processes(id_user);
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### ❌ Sintaxes Proibidas RLS
|
|
434
|
+
```sql
|
|
435
|
+
-- NUNCA usar 'id' ou 'id_user' direto
|
|
436
|
+
id_user = auth.uid() -- ❌ ERRADO
|
|
437
|
+
id = auth.uid() -- ❌ ERRADO
|
|
438
|
+
|
|
439
|
+
-- SEMPRE usar 'alias'
|
|
440
|
+
alias = auth.uid() -- ✅ CORRETO
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
---
|
|
444
|
+
|
|
445
|
+
## 🐛 TROUBLESHOOTING
|
|
446
|
+
|
|
447
|
+
### 1️⃣ "relation does not exist"
|
|
448
|
+
```typescript
|
|
449
|
+
// Causa: Schema ausente
|
|
450
|
+
.from('processes') // ❌
|
|
451
|
+
|
|
452
|
+
// Solução:
|
|
453
|
+
.from('processes', { schema: 'central' }) // ✅
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
### 2️⃣ RLS retorna vazio
|
|
457
|
+
```sql
|
|
458
|
+
-- Causa: Sintaxe incorreta
|
|
459
|
+
USING (id_user = auth.uid()) -- ❌
|
|
460
|
+
|
|
461
|
+
-- Solução:
|
|
462
|
+
USING (alias = auth.uid()) -- ✅
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
### 3️⃣ Duplicação de registros
|
|
466
|
+
```typescript
|
|
467
|
+
// Causa: ID ausente no update
|
|
468
|
+
await service.save({ title: 'Novo' }); // ❌ Cria duplicado
|
|
469
|
+
|
|
470
|
+
// Solução: Preservar ID
|
|
471
|
+
await service.save({ id: item.id, title: 'Novo' }); // ✅
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### 4️⃣ Página recarrega ao editar
|
|
475
|
+
```typescript
|
|
476
|
+
// Causa: Config sem useMemo
|
|
477
|
+
const config = generateCrudConfig(...); // ❌ Re-render infinito
|
|
478
|
+
|
|
479
|
+
// Solução:
|
|
480
|
+
const config = useMemo(() => generateCrudConfig(...), []); // ✅
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### 5️⃣ Estado reseta ao navegar
|
|
484
|
+
```typescript
|
|
485
|
+
// Causa: Outlet ausente
|
|
486
|
+
<Route path="/processes" element={<ProcessesPage />} /> // ❌
|
|
215
487
|
|
|
216
|
-
|
|
488
|
+
// Solução: Layout com Outlet
|
|
489
|
+
<Route path="/processes" element={<ProcessLayout />}>
|
|
490
|
+
<Route index element={<ProcessesPage />} />
|
|
491
|
+
</Route>
|
|
492
|
+
// ProcessLayout: return <Outlet />; // ✅
|
|
493
|
+
```
|
|
217
494
|
|
|
218
|
-
|
|
495
|
+
### 6️⃣ Qualiex retorna 401
|
|
496
|
+
```typescript
|
|
497
|
+
// Causa: Header ausente
|
|
498
|
+
fetch(url); // ❌
|
|
219
499
|
|
|
220
|
-
|
|
221
|
-
|
|
500
|
+
// Solução: Adicionar header
|
|
501
|
+
fetch(url, { headers: { 'un-alias': 'true' } }); // ✅
|
|
502
|
+
// (BaseService já faz automaticamente)
|
|
222
503
|
```
|
|
223
504
|
|
|
224
|
-
|
|
505
|
+
---
|
|
506
|
+
|
|
507
|
+
## 📐 CONTROLE DE LARGURA DAS COLUNAS
|
|
225
508
|
|
|
226
|
-
|
|
509
|
+
O `forlogic-core` oferece três formas de definir larguras de colunas nas tabelas CRUD:
|
|
510
|
+
|
|
511
|
+
### **1️⃣ Via `className` (Recomendado)**
|
|
512
|
+
Use classes do Tailwind para larguras fixas ou responsivas:
|
|
513
|
+
```typescript
|
|
514
|
+
const columns = [
|
|
515
|
+
{
|
|
516
|
+
key: 'status',
|
|
517
|
+
header: 'Status',
|
|
518
|
+
className: 'w-24 text-center', // Largura fixa: 96px
|
|
519
|
+
},
|
|
520
|
+
{
|
|
521
|
+
key: 'updated_at',
|
|
522
|
+
header: 'Atualizado em',
|
|
523
|
+
className: 'w-40 text-center whitespace-nowrap', // 160px, sem quebra
|
|
524
|
+
},
|
|
525
|
+
];
|
|
526
|
+
```
|
|
227
527
|
|
|
228
|
-
|
|
229
|
-
:
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
528
|
+
### **2️⃣ Via `width` (Fixo em pixels)**
|
|
529
|
+
Especifique largura fixa diretamente:
|
|
530
|
+
```typescript
|
|
531
|
+
{
|
|
532
|
+
key: 'order',
|
|
533
|
+
header: 'Ordem',
|
|
534
|
+
width: 60, // Largura fixa: 60px
|
|
535
|
+
className: 'text-center',
|
|
233
536
|
}
|
|
234
537
|
```
|
|
235
538
|
|
|
539
|
+
### **3️⃣ Via `minWidth` + `weight` (Flexível)**
|
|
540
|
+
Para colunas que crescem proporcionalmente:
|
|
541
|
+
```typescript
|
|
542
|
+
{
|
|
543
|
+
key: 'description',
|
|
544
|
+
header: 'Descrição',
|
|
545
|
+
minWidth: 200, // Mínimo: 200px
|
|
546
|
+
weight: 2, // 2x mais espaço que colunas padrão (weight: 1)
|
|
547
|
+
}
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
### **⚠️ Importante**
|
|
551
|
+
- A tabela usa `table-auto` para respeitar essas configurações
|
|
552
|
+
- Para truncar textos longos, use: `className: "max-w-[200px] truncate"`
|
|
553
|
+
- Combine `whitespace-nowrap` com largura fixa para evitar quebras
|
|
554
|
+
|
|
555
|
+
### **📋 Exemplo Completo**
|
|
556
|
+
```typescript
|
|
557
|
+
const columns: CrudColumn<MyEntity>[] = [
|
|
558
|
+
{
|
|
559
|
+
key: 'order',
|
|
560
|
+
header: 'Ordem',
|
|
561
|
+
width: 60, // Fixo: 60px
|
|
562
|
+
className: 'text-center',
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
key: 'title',
|
|
566
|
+
header: 'Título',
|
|
567
|
+
minWidth: 200, // Mínimo: 200px, cresce conforme espaço disponível
|
|
568
|
+
weight: 3, // 3x mais espaço que colunas padrão
|
|
569
|
+
},
|
|
570
|
+
{
|
|
571
|
+
key: 'status',
|
|
572
|
+
header: 'Status',
|
|
573
|
+
className: 'w-24 text-center', // Tailwind: 96px
|
|
574
|
+
},
|
|
575
|
+
{
|
|
576
|
+
key: 'progress',
|
|
577
|
+
header: 'Nível & Progresso',
|
|
578
|
+
className: 'w-40 text-center', // Tailwind: 160px
|
|
579
|
+
},
|
|
580
|
+
{
|
|
581
|
+
key: 'updated_at',
|
|
582
|
+
header: 'Atualizado em',
|
|
583
|
+
className: 'w-40 text-center whitespace-nowrap', // 160px, sem quebra
|
|
584
|
+
render: (item) => formatDate(item.updated_at)
|
|
585
|
+
},
|
|
586
|
+
];
|
|
587
|
+
```
|
|
588
|
+
|
|
236
589
|
---
|
|
237
590
|
|
|
238
|
-
##
|
|
591
|
+
## 📚 REFERÊNCIA RÁPIDA
|
|
592
|
+
|
|
593
|
+
### Imports Essenciais
|
|
594
|
+
```typescript
|
|
595
|
+
// CRUD
|
|
596
|
+
import {
|
|
597
|
+
createSimpleService,
|
|
598
|
+
createCrudPage,
|
|
599
|
+
generateCrudConfig,
|
|
600
|
+
createSimpleSaveHandler
|
|
601
|
+
} from 'forlogic-core';
|
|
602
|
+
|
|
603
|
+
// UI
|
|
604
|
+
import { Button, Input, Card, toast } from 'forlogic-core/ui';
|
|
605
|
+
|
|
606
|
+
// Auth
|
|
607
|
+
import { useAuth, ProtectedRoute } from 'forlogic-core';
|
|
608
|
+
|
|
609
|
+
// Qualiex
|
|
610
|
+
import { QualiexUserField, useQualiexUsers } from 'forlogic-core';
|
|
611
|
+
```
|
|
239
612
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
613
|
+
### Estrutura de Arquivos
|
|
614
|
+
```
|
|
615
|
+
src/
|
|
616
|
+
├── processes/
|
|
617
|
+
│ ├── process.ts # Types
|
|
618
|
+
│ ├── processService.ts # Service + Hook
|
|
619
|
+
│ └── ProcessesPage.tsx # Page + Config
|
|
620
|
+
```
|
|
243
621
|
|
|
244
622
|
---
|
|
245
623
|
|
|
246
624
|
## 📝 Licença
|
|
247
625
|
|
|
248
626
|
MIT License - ForLogic © 2025
|
|
627
|
+
|
|
628
|
+
**Última atualização:** 2025-10-05
|