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/README.md
CHANGED
|
@@ -1,36 +1,628 @@
|
|
|
1
|
-
# 🧱
|
|
1
|
+
# 🧱 Projeto atual
|
|
2
2
|
|
|
3
|
-
|
|
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'`.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 🤖 REGRAS CRÍTICAS
|
|
10
|
+
|
|
11
|
+
### ⚠️ TOP 3 ERROS
|
|
12
|
+
|
|
13
|
+
1. **ESQUECER SCHEMA**
|
|
14
|
+
```typescript
|
|
15
|
+
// ❌ ERRADO
|
|
16
|
+
.from('table')
|
|
17
|
+
|
|
18
|
+
// ✅ CORRETO
|
|
19
|
+
.schema('schema').from('table')
|
|
20
|
+
```
|
|
21
|
+
|
|
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
|
+
```
|
|
32
|
+
|
|
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);
|
|
37
|
+
|
|
38
|
+
-- ✅ Apenas quando solicitado explicitamente
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
### ✅ CHECKLIST (antes de implementar)
|
|
44
|
+
|
|
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?
|
|
4
51
|
|
|
5
52
|
---
|
|
6
53
|
|
|
7
|
-
##
|
|
54
|
+
## 🚀 QUICK START - Criar CRUD Completo
|
|
55
|
+
|
|
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'
|
|
99
|
+
});
|
|
8
100
|
|
|
9
|
-
|
|
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
|
+
};
|
|
108
|
+
```
|
|
10
109
|
|
|
11
|
-
|
|
110
|
+
### **4️⃣ Config (com useMemo)**
|
|
111
|
+
```typescript
|
|
112
|
+
// src/processes/ProcessesPage.tsx
|
|
113
|
+
import { useMemo } from 'react';
|
|
114
|
+
import { generateCrudConfig } from 'forlogic-core';
|
|
12
115
|
|
|
13
|
-
|
|
116
|
+
export default function ProcessesPage() {
|
|
117
|
+
const crud = useProcesses();
|
|
14
118
|
|
|
15
|
-
⚠️
|
|
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
|
+
[]);
|
|
16
136
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
137
|
+
return createCrudPage({
|
|
138
|
+
config,
|
|
139
|
+
crud,
|
|
140
|
+
saveHandler: handleSave
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
```
|
|
21
144
|
|
|
22
|
-
**
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
- ❌ Componentes base (importar de `forlogic-core`)
|
|
145
|
+
### **5️⃣ Page + Outlet (preservar estado)**
|
|
146
|
+
```typescript
|
|
147
|
+
// src/App.tsx
|
|
148
|
+
import { Outlet } from 'react-router-dom';
|
|
27
149
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
150
|
+
function App() {
|
|
151
|
+
return (
|
|
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>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ⚠️ OBRIGATÓRIO: Outlet preserva estado
|
|
163
|
+
function ProcessLayout() {
|
|
164
|
+
return <Outlet />; // Evita reload do componente
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## 🎯 AÇÕES EM LOTE (Bulk Actions)
|
|
171
|
+
|
|
172
|
+
O sistema CRUD suporta seleção múltipla e ações em lote usando checkboxes.
|
|
173
|
+
|
|
174
|
+
### **Habilitar Bulk Actions**
|
|
175
|
+
|
|
176
|
+
Adicione `enableBulkActions: true` na configuração:
|
|
177
|
+
|
|
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
|
+
```
|
|
187
|
+
|
|
188
|
+
### **Ações Padrão Disponíveis**
|
|
189
|
+
|
|
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
|
+
};
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### **Comportamento**
|
|
239
|
+
|
|
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)
|
|
244
|
+
|
|
245
|
+
**Mobile (Cards):**
|
|
246
|
+
- Checkbox no canto superior esquerdo de cada card
|
|
247
|
+
- Mesmo comportamento de seleção que desktop
|
|
248
|
+
|
|
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)
|
|
254
|
+
|
|
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
|
+
```
|
|
268
|
+
|
|
269
|
+
### **Exemplo Completo**
|
|
270
|
+
|
|
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';
|
|
279
|
+
|
|
280
|
+
export function ProcessesPage() {
|
|
281
|
+
const manager = useProcessCrud();
|
|
282
|
+
|
|
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
|
+
);
|
|
314
|
+
|
|
315
|
+
const CrudPage = createCrudPage({
|
|
316
|
+
manager,
|
|
317
|
+
config,
|
|
318
|
+
onSave: handleSave
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
return <CrudPage />;
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
### 🔗 Integração Qualiex (opcional)
|
|
328
|
+
|
|
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
|
+
```
|
|
335
|
+
|
|
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
|
|
355
|
+
}
|
|
356
|
+
|
|
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
|
+
```
|
|
365
|
+
|
|
366
|
+
**Componentes customizados:**
|
|
367
|
+
|
|
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' }
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
---
|
|
389
|
+
|
|
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 />} /> // ❌
|
|
487
|
+
|
|
488
|
+
// Solução: Layout com Outlet
|
|
489
|
+
<Route path="/processes" element={<ProcessLayout />}>
|
|
490
|
+
<Route index element={<ProcessesPage />} />
|
|
491
|
+
</Route>
|
|
492
|
+
// ProcessLayout: return <Outlet />; // ✅
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
### 6️⃣ Qualiex retorna 401
|
|
496
|
+
```typescript
|
|
497
|
+
// Causa: Header ausente
|
|
498
|
+
fetch(url); // ❌
|
|
499
|
+
|
|
500
|
+
// Solução: Adicionar header
|
|
501
|
+
fetch(url, { headers: { 'un-alias': 'true' } }); // ✅
|
|
502
|
+
// (BaseService já faz automaticamente)
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
---
|
|
506
|
+
|
|
507
|
+
## 📐 CONTROLE DE LARGURA DAS COLUNAS
|
|
508
|
+
|
|
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
|
+
```
|
|
527
|
+
|
|
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',
|
|
536
|
+
}
|
|
537
|
+
```
|
|
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
|
+
|
|
589
|
+
---
|
|
590
|
+
|
|
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
|
+
```
|
|
612
|
+
|
|
613
|
+
### Estrutura de Arquivos
|
|
614
|
+
```
|
|
615
|
+
src/
|
|
616
|
+
├── processes/
|
|
617
|
+
│ ├── process.ts # Types
|
|
618
|
+
│ ├── processService.ts # Service + Hook
|
|
619
|
+
│ └── ProcessesPage.tsx # Page + Config
|
|
620
|
+
```
|
|
31
621
|
|
|
32
622
|
---
|
|
33
623
|
|
|
34
624
|
## 📝 Licença
|
|
35
625
|
|
|
36
|
-
MIT License - ForLogic © 2025
|
|
626
|
+
MIT License - ForLogic © 2025
|
|
627
|
+
|
|
628
|
+
**Última atualização:** 2025-10-05
|