forlogic-core 1.5.0 → 1.5.2
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 +21 -1116
- package/dist/README.md +248 -0
- package/dist/docs/AI_RULES.md +213 -0
- package/dist/docs/README.md +102 -0
- package/dist/docs/TROUBLESHOOTING.md +473 -0
- package/dist/docs/architecture/TOKENS_ARCHITECTURE.md +712 -0
- package/dist/docs/templates/app-layout.tsx +192 -0
- package/dist/docs/templates/basic-crud-page.tsx +97 -0
- package/dist/docs/templates/basic-crud-working.tsx +182 -0
- package/dist/docs/templates/complete-crud-example.tsx +307 -0
- package/dist/docs/templates/custom-form.tsx +99 -0
- package/dist/docs/templates/custom-service.tsx +194 -0
- package/dist/docs/templates/permission-check-hook.tsx +275 -0
- package/dist/docs/templates/qualiex-config.ts +137 -0
- package/dist/docs/templates/qualiex-integration-example.tsx +430 -0
- package/dist/docs/templates/quick-start-example.tsx +96 -0
- package/dist/docs/templates/rls-policies.sql +80 -0
- package/dist/docs/templates/sidebar-config.tsx +145 -0
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +2 -1
- package/dist/assets/index-C_ZLBeXY.css +0 -1
- package/dist/assets/index-wEAMQwsw.js +0 -7898
- package/dist/index.html +0 -19
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TEMPLATE: Integração Qualiex
|
|
3
|
+
* ⚠️ REGRAS: Consulte docs/AI_REFERENCE.md
|
|
4
|
+
* 📚 Detalhes: Consulte docs/QUALIEX_INTEGRATION.md
|
|
5
|
+
* ❌ NÃO criar implementação própria - usar da lib!
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// 1. Configuração inicial (main.tsx ou App.tsx)
|
|
9
|
+
import { setQualiexConfig } from 'forlogic-core';
|
|
10
|
+
|
|
11
|
+
// Habilitar integração Qualiex
|
|
12
|
+
setQualiexConfig({
|
|
13
|
+
enableUserEnrichment: true, // Adicionar responsible_name às entidades
|
|
14
|
+
enableUsersApi: true, // Habilitar API de usuários
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// 2. Variável de ambiente (.env)
|
|
18
|
+
// VITE_QUALIEX_API_URL=https://common-v4-api.qualiex.com
|
|
19
|
+
|
|
20
|
+
// 3. Interface da entidade (src/tasks/task.ts)
|
|
21
|
+
|
|
22
|
+
import { BaseEntity, ContentEntity, UserRelatedEntity } from 'forlogic-core';
|
|
23
|
+
|
|
24
|
+
export interface Task extends ContentEntity, UserRelatedEntity {
|
|
25
|
+
// ContentEntity: title, description
|
|
26
|
+
// UserRelatedEntity: id_user, responsible_name
|
|
27
|
+
|
|
28
|
+
status: 'pending' | 'in_progress' | 'completed';
|
|
29
|
+
due_date?: string | null;
|
|
30
|
+
priority?: 'low' | 'medium' | 'high';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface CreateTaskPayload {
|
|
34
|
+
title: string;
|
|
35
|
+
description?: string;
|
|
36
|
+
id_user: string; // ID do usuário Qualiex
|
|
37
|
+
status: 'pending' | 'in_progress' | 'completed';
|
|
38
|
+
due_date?: string;
|
|
39
|
+
priority?: 'low' | 'medium' | 'high';
|
|
40
|
+
alias: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface UpdateTaskPayload {
|
|
44
|
+
title?: string;
|
|
45
|
+
description?: string;
|
|
46
|
+
id_user?: string;
|
|
47
|
+
status?: 'pending' | 'in_progress' | 'completed';
|
|
48
|
+
due_date?: string;
|
|
49
|
+
priority?: 'low' | 'medium' | 'high';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ============= 4. SERVICE =============
|
|
53
|
+
// src/tasks/taskService.ts
|
|
54
|
+
|
|
55
|
+
import { createSimpleService } from 'forlogic-core';
|
|
56
|
+
import { Task, CreateTaskPayload, UpdateTaskPayload } from './task';
|
|
57
|
+
|
|
58
|
+
export const { service: taskService, useCrudHook: useTaskCrud } =
|
|
59
|
+
createSimpleService<Task, CreateTaskPayload, UpdateTaskPayload>({
|
|
60
|
+
tableName: 'tasks',
|
|
61
|
+
entityName: 'Task',
|
|
62
|
+
searchFields: ['title', 'description'],
|
|
63
|
+
schemaName: 'YOUR_SCHEMA' // Use o schema do seu projeto (ex: 'central', 'trainings', 'public')
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// ============= 5. PÁGINA CRUD COM QUALIEX USER FIELD =============
|
|
67
|
+
// src/tasks/TasksPage.tsx
|
|
68
|
+
|
|
69
|
+
import {
|
|
70
|
+
createCrudPage,
|
|
71
|
+
createSimpleSaveHandler,
|
|
72
|
+
QualiexUserField,
|
|
73
|
+
useAuth,
|
|
74
|
+
Badge,
|
|
75
|
+
type FormSection
|
|
76
|
+
} from 'forlogic-core';
|
|
77
|
+
import { Calendar, User, CheckCircle } from 'lucide-react';
|
|
78
|
+
import { Task } from './task';
|
|
79
|
+
import { useTaskCrud } from './taskService';
|
|
80
|
+
|
|
81
|
+
// Configuração das colunas
|
|
82
|
+
const taskColumns = [
|
|
83
|
+
{
|
|
84
|
+
key: 'title',
|
|
85
|
+
header: 'Título',
|
|
86
|
+
sortable: true,
|
|
87
|
+
render: (item: Task) => (
|
|
88
|
+
<span className="font-medium">{item.title}</span>
|
|
89
|
+
)
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
key: 'responsible_name',
|
|
93
|
+
header: 'Responsável',
|
|
94
|
+
sortable: true,
|
|
95
|
+
render: (item: Task) => (
|
|
96
|
+
<div className="flex items-center gap-2">
|
|
97
|
+
<User className="h-4 w-4 text-muted-foreground" />
|
|
98
|
+
<span>{item.responsible_name || 'Sem responsável'}</span>
|
|
99
|
+
</div>
|
|
100
|
+
)
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
key: 'status',
|
|
104
|
+
header: 'Status',
|
|
105
|
+
sortable: true,
|
|
106
|
+
render: (item: Task) => {
|
|
107
|
+
const statusConfig = {
|
|
108
|
+
pending: { label: 'Pendente', color: 'bg-yellow-100 text-yellow-800' },
|
|
109
|
+
in_progress: { label: 'Em Progresso', color: 'bg-blue-100 text-blue-800' },
|
|
110
|
+
completed: { label: 'Concluída', color: 'bg-green-100 text-green-800' }
|
|
111
|
+
};
|
|
112
|
+
const config = statusConfig[item.status];
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<Badge className={config.color}>
|
|
116
|
+
{config.label}
|
|
117
|
+
</Badge>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
key: 'due_date',
|
|
123
|
+
header: 'Prazo',
|
|
124
|
+
sortable: true,
|
|
125
|
+
render: (item: Task) => (
|
|
126
|
+
item.due_date ? (
|
|
127
|
+
<div className="flex items-center gap-2">
|
|
128
|
+
<Calendar className="h-4 w-4 text-muted-foreground" />
|
|
129
|
+
{new Date(item.due_date).toLocaleDateString('pt-BR')}
|
|
130
|
+
</div>
|
|
131
|
+
) : (
|
|
132
|
+
<span className="text-muted-foreground">-</span>
|
|
133
|
+
)
|
|
134
|
+
)
|
|
135
|
+
},
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
// ✅ Seções do formulário com QualiexUserField
|
|
139
|
+
const formSections: FormSection[] = [
|
|
140
|
+
{
|
|
141
|
+
id: 'main-info',
|
|
142
|
+
title: 'Informações Principais',
|
|
143
|
+
fields: [
|
|
144
|
+
{
|
|
145
|
+
name: 'title',
|
|
146
|
+
label: 'Título',
|
|
147
|
+
type: 'text',
|
|
148
|
+
required: true,
|
|
149
|
+
placeholder: 'Digite o título da tarefa',
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
name: 'description',
|
|
153
|
+
label: 'Descrição',
|
|
154
|
+
type: 'textarea',
|
|
155
|
+
placeholder: 'Descreva a tarefa...',
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: 'status',
|
|
159
|
+
label: 'Status',
|
|
160
|
+
type: 'select',
|
|
161
|
+
required: true,
|
|
162
|
+
options: [
|
|
163
|
+
{ value: 'pending', label: 'Pendente' },
|
|
164
|
+
{ value: 'in_progress', label: 'Em Progresso' },
|
|
165
|
+
{ value: 'completed', label: 'Concluída' },
|
|
166
|
+
],
|
|
167
|
+
defaultValue: 'pending',
|
|
168
|
+
},
|
|
169
|
+
],
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
id: 'assignment',
|
|
173
|
+
title: 'Atribuição e Prazo',
|
|
174
|
+
fields: [
|
|
175
|
+
{
|
|
176
|
+
name: 'id_user',
|
|
177
|
+
label: 'Responsável',
|
|
178
|
+
type: 'custom',
|
|
179
|
+
required: true,
|
|
180
|
+
// ✅ USAR QualiexUserField para carregar usuários da API Qualiex
|
|
181
|
+
renderField: ({ value, onChange, error }) => (
|
|
182
|
+
<div className="space-y-1">
|
|
183
|
+
<QualiexUserField
|
|
184
|
+
value={value}
|
|
185
|
+
onChange={onChange}
|
|
186
|
+
placeholder="Selecione o responsável"
|
|
187
|
+
/>
|
|
188
|
+
{error && (
|
|
189
|
+
<p className="text-sm text-destructive">{error}</p>
|
|
190
|
+
)}
|
|
191
|
+
</div>
|
|
192
|
+
)
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
name: 'due_date',
|
|
196
|
+
label: 'Prazo',
|
|
197
|
+
type: 'date',
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
name: 'priority',
|
|
201
|
+
label: 'Prioridade',
|
|
202
|
+
type: 'select',
|
|
203
|
+
options: [
|
|
204
|
+
{ value: 'low', label: 'Baixa' },
|
|
205
|
+
{ value: 'medium', label: 'Média' },
|
|
206
|
+
{ value: 'high', label: 'Alta' },
|
|
207
|
+
],
|
|
208
|
+
defaultValue: 'medium',
|
|
209
|
+
},
|
|
210
|
+
],
|
|
211
|
+
},
|
|
212
|
+
];
|
|
213
|
+
|
|
214
|
+
// Componente da página
|
|
215
|
+
export function TasksPage() {
|
|
216
|
+
const manager = useTaskCrud();
|
|
217
|
+
const { alias } = useAuth();
|
|
218
|
+
|
|
219
|
+
// Handler com transformações
|
|
220
|
+
const handleSave = createSimpleSaveHandler(
|
|
221
|
+
manager,
|
|
222
|
+
// CREATE transform
|
|
223
|
+
(data: any) => ({
|
|
224
|
+
...data,
|
|
225
|
+
alias: alias || '',
|
|
226
|
+
status: data.status || 'pending',
|
|
227
|
+
priority: data.priority || 'medium',
|
|
228
|
+
}),
|
|
229
|
+
// UPDATE transform
|
|
230
|
+
(data: any) => {
|
|
231
|
+
const { alias: _, ...updateData } = data;
|
|
232
|
+
return updateData;
|
|
233
|
+
}
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
const CrudPage = createCrudPage({
|
|
237
|
+
manager,
|
|
238
|
+
config: {
|
|
239
|
+
entityName: 'Tarefa',
|
|
240
|
+
entityNamePlural: 'Tarefas',
|
|
241
|
+
columns: taskColumns,
|
|
242
|
+
formSections,
|
|
243
|
+
cardFields: [
|
|
244
|
+
{
|
|
245
|
+
key: 'title',
|
|
246
|
+
label: 'Título',
|
|
247
|
+
render: (item: Task) => (
|
|
248
|
+
<span className="font-semibold text-lg">{item.title}</span>
|
|
249
|
+
)
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
key: 'responsible_name',
|
|
253
|
+
label: 'Responsável',
|
|
254
|
+
render: (item: Task) => (
|
|
255
|
+
<div className="flex items-center gap-2">
|
|
256
|
+
<User className="h-4 w-4" />
|
|
257
|
+
<span>{item.responsible_name || 'Sem responsável'}</span>
|
|
258
|
+
</div>
|
|
259
|
+
)
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
key: 'status',
|
|
263
|
+
label: 'Status',
|
|
264
|
+
render: (item: Task) => {
|
|
265
|
+
const config = {
|
|
266
|
+
pending: { label: 'Pendente', color: 'bg-yellow-100' },
|
|
267
|
+
in_progress: { label: 'Em Progresso', color: 'bg-blue-100' },
|
|
268
|
+
completed: { label: 'Concluída', color: 'bg-green-100' }
|
|
269
|
+
};
|
|
270
|
+
return (
|
|
271
|
+
<Badge className={config[item.status].color}>
|
|
272
|
+
{config[item.status].label}
|
|
273
|
+
</Badge>
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
]
|
|
278
|
+
},
|
|
279
|
+
onSave: handleSave
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
return <CrudPage />;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// ============= 6. USO DIRETO DO HOOK (OPCIONAL) =============
|
|
286
|
+
// Exemplo de componente que usa useQualiexUsers diretamente
|
|
287
|
+
|
|
288
|
+
import { useQualiexUsers } from 'forlogic-core';
|
|
289
|
+
|
|
290
|
+
export function QualiexUsersDebug() {
|
|
291
|
+
const { data: users, isLoading, error } = useQualiexUsers();
|
|
292
|
+
|
|
293
|
+
if (isLoading) {
|
|
294
|
+
return <div>Carregando usuários...</div>;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (error) {
|
|
298
|
+
return <div>Erro ao carregar: {error.message}</div>;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return (
|
|
302
|
+
<div>
|
|
303
|
+
<h3>Usuários Qualiex ({users?.length || 0})</h3>
|
|
304
|
+
<ul>
|
|
305
|
+
{users?.map(user => (
|
|
306
|
+
<li key={user.userId}>
|
|
307
|
+
{user.userName} - {user.userEmail}
|
|
308
|
+
</li>
|
|
309
|
+
))}
|
|
310
|
+
</ul>
|
|
311
|
+
</div>
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// ============= 7. USO DO SERVIÇO DE API (AVANÇADO) =============
|
|
316
|
+
// Uso direto do qualiexApi para casos específicos
|
|
317
|
+
|
|
318
|
+
import { qualiexApi } from 'forlogic-core';
|
|
319
|
+
|
|
320
|
+
export async function fetchQualiexUserById(userId: string) {
|
|
321
|
+
try {
|
|
322
|
+
const user = await qualiexApi.fetchUserById(
|
|
323
|
+
userId,
|
|
324
|
+
'company-alias',
|
|
325
|
+
'company-id'
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
if (user) {
|
|
329
|
+
console.log('Usuário encontrado:', user.userName);
|
|
330
|
+
return user;
|
|
331
|
+
}
|
|
332
|
+
} catch (error) {
|
|
333
|
+
console.error('Erro ao buscar usuário:', error);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// ============= 8. ENRIQUECIMENTO DE ENTIDADES (OPCIONAL) =============
|
|
338
|
+
// Adicionar responsible_name automaticamente às entidades
|
|
339
|
+
|
|
340
|
+
import { QualiexEnrichmentService } from 'forlogic-core';
|
|
341
|
+
|
|
342
|
+
export async function enrichTasksWithResponsibles(tasks: Task[]) {
|
|
343
|
+
return QualiexEnrichmentService.enrichWithResponsibleNames(
|
|
344
|
+
tasks,
|
|
345
|
+
{
|
|
346
|
+
entityName: 'Task',
|
|
347
|
+
fallbackResponsibleText: 'Sem responsável'
|
|
348
|
+
}
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// ============= 9. FORMULÁRIO CUSTOMIZADO (ALTERNATIVA) =============
|
|
353
|
+
// Se precisar de controle total do formulário
|
|
354
|
+
|
|
355
|
+
import { useForm, Controller } from 'react-hook-form';
|
|
356
|
+
import { QualiexUserField, Button, Input, Textarea } from 'forlogic-core';
|
|
357
|
+
|
|
358
|
+
interface CustomTaskFormData {
|
|
359
|
+
title: string;
|
|
360
|
+
description?: string;
|
|
361
|
+
id_user: string;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export function CustomTaskForm() {
|
|
365
|
+
const { control, register, handleSubmit } = useForm<CustomTaskFormData>();
|
|
366
|
+
|
|
367
|
+
const onSubmit = (data: CustomTaskFormData) => {
|
|
368
|
+
console.log('Dados do formulário:', data);
|
|
369
|
+
// Lógica de salvar
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
return (
|
|
373
|
+
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
|
374
|
+
<div>
|
|
375
|
+
<label>Título</label>
|
|
376
|
+
<Input {...register('title', { required: true })} />
|
|
377
|
+
</div>
|
|
378
|
+
|
|
379
|
+
<div>
|
|
380
|
+
<label>Descrição</label>
|
|
381
|
+
<Textarea {...register('description')} />
|
|
382
|
+
</div>
|
|
383
|
+
|
|
384
|
+
<div>
|
|
385
|
+
<label>Responsável</label>
|
|
386
|
+
<Controller
|
|
387
|
+
control={control}
|
|
388
|
+
name="id_user"
|
|
389
|
+
rules={{ required: 'Responsável é obrigatório' }}
|
|
390
|
+
render={({ field, fieldState }) => (
|
|
391
|
+
<div className="space-y-1">
|
|
392
|
+
<QualiexUserField
|
|
393
|
+
value={field.value}
|
|
394
|
+
onChange={field.onChange}
|
|
395
|
+
placeholder="Selecione o responsável"
|
|
396
|
+
/>
|
|
397
|
+
{fieldState.error && (
|
|
398
|
+
<p className="text-sm text-destructive">
|
|
399
|
+
{fieldState.error.message}
|
|
400
|
+
</p>
|
|
401
|
+
)}
|
|
402
|
+
</div>
|
|
403
|
+
)}
|
|
404
|
+
/>
|
|
405
|
+
</div>
|
|
406
|
+
|
|
407
|
+
<Button type="submit">Salvar Tarefa</Button>
|
|
408
|
+
</form>
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// ============= RESUMO =============
|
|
413
|
+
/*
|
|
414
|
+
✅ O QUE A LIB FORNECE:
|
|
415
|
+
- qualiexApi: Serviço de API com autenticação automática
|
|
416
|
+
- useQualiexUsers: Hook React Query para buscar usuários
|
|
417
|
+
- QualiexUserField: Componente UI pronto (loading, error, lista)
|
|
418
|
+
- QualiexEnrichmentService: Enriquecimento de entidades com responsible_name
|
|
419
|
+
|
|
420
|
+
❌ O QUE NÃO FAZER:
|
|
421
|
+
- NÃO criar seu próprio serviço de API Qualiex
|
|
422
|
+
- NÃO criar hook customizado useQualiexUsers
|
|
423
|
+
- NÃO criar componente customizado de select de usuário
|
|
424
|
+
|
|
425
|
+
✅ CHECKLIST:
|
|
426
|
+
1. Adicionar VITE_QUALIEX_API_URL no .env
|
|
427
|
+
2. Configurar setQualiexConfig({ enableUsersApi: true })
|
|
428
|
+
3. Usar QualiexUserField nos formulários
|
|
429
|
+
4. Importar de 'forlogic-core'
|
|
430
|
+
*/
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QUICK START EXAMPLE - Complete CRUD in 5 minutes
|
|
3
|
+
*
|
|
4
|
+
* This example shows how to create a complete CRUD interface
|
|
5
|
+
* with minimal code using the new simplified API.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from 'react';
|
|
9
|
+
import { createSimpleService, createCrudPage, generateCrudConfig } from 'forlogic-core';
|
|
10
|
+
|
|
11
|
+
// 1. Define your entity type
|
|
12
|
+
interface Product {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
description: string;
|
|
16
|
+
price: number;
|
|
17
|
+
category: string;
|
|
18
|
+
active: boolean;
|
|
19
|
+
created_at: Date;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// 2. Create the service (one line!)
|
|
23
|
+
const productService = createSimpleService<Product>({
|
|
24
|
+
tableName: 'products'
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// 3. Generate CRUD configuration with smart defaults
|
|
28
|
+
const productConfig = generateCrudConfig<Product>(
|
|
29
|
+
'Produtos',
|
|
30
|
+
{
|
|
31
|
+
name: '',
|
|
32
|
+
description: '',
|
|
33
|
+
price: 0,
|
|
34
|
+
category: '',
|
|
35
|
+
active: true,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
// Override defaults as needed
|
|
39
|
+
columns: [
|
|
40
|
+
{ key: 'name', label: 'Nome', type: 'text', required: true, searchable: true },
|
|
41
|
+
{ key: 'description', label: 'Descrição', type: 'textarea' },
|
|
42
|
+
{ key: 'price', label: 'Preço', type: 'number', required: true },
|
|
43
|
+
{
|
|
44
|
+
key: 'category',
|
|
45
|
+
label: 'Categoria',
|
|
46
|
+
type: 'select',
|
|
47
|
+
options: [
|
|
48
|
+
{ label: 'Eletrônicos', value: 'electronics' },
|
|
49
|
+
{ label: 'Roupas', value: 'clothing' },
|
|
50
|
+
{ label: 'Casa', value: 'home' }
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
{ key: 'active', label: 'Ativo', type: 'boolean' },
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
// 4. Create the complete CRUD page (one line!)
|
|
59
|
+
export const ProductsPage = createCrudPage({
|
|
60
|
+
service: productService,
|
|
61
|
+
config: productConfig,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// That's it! You now have:
|
|
65
|
+
// ✅ Complete CRUD interface (list, create, edit, delete)
|
|
66
|
+
// ✅ Search functionality
|
|
67
|
+
// ✅ Form validation
|
|
68
|
+
// ✅ Loading states
|
|
69
|
+
// ✅ Error handling
|
|
70
|
+
// ✅ Responsive design
|
|
71
|
+
// ✅ Type safety
|
|
72
|
+
|
|
73
|
+
// ALTERNATIVE: Even simpler with auto-generation
|
|
74
|
+
// export const ProductsPageAuto = createCrudPage({
|
|
75
|
+
// service: createSimpleService<Product>({ tableName: 'products' }),
|
|
76
|
+
// config: generateCrudConfig('Produtos', { name: '', price: 0, active: true })
|
|
77
|
+
// });
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* COMPARISON: Old vs New
|
|
81
|
+
*
|
|
82
|
+
* OLD WAY (100+ lines):
|
|
83
|
+
* - Create service class extending BaseService
|
|
84
|
+
* - Create form component with validation
|
|
85
|
+
* - Create table component with actions
|
|
86
|
+
* - Create page component tying everything together
|
|
87
|
+
* - Handle loading states manually
|
|
88
|
+
* - Handle error states manually
|
|
89
|
+
*
|
|
90
|
+
* NEW WAY (20 lines):
|
|
91
|
+
* - Define entity type
|
|
92
|
+
* - Create service (1 line)
|
|
93
|
+
* - Generate config (1 function call)
|
|
94
|
+
* - Create page (1 line)
|
|
95
|
+
* - Everything else is automatic!
|
|
96
|
+
*/
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
-- =========================================
|
|
2
|
+
-- TEMPLATES RLS POLICIES
|
|
3
|
+
-- =========================================
|
|
4
|
+
-- Templates prontos para usar em novas tabelas
|
|
5
|
+
-- Adapte conforme necessário para cada caso
|
|
6
|
+
|
|
7
|
+
-- =========================================
|
|
8
|
+
-- 1. TEMPLATE PADRÃO: Tabela com Soft Delete
|
|
9
|
+
-- =========================================
|
|
10
|
+
-- Use este template para a maioria das tabelas
|
|
11
|
+
/*
|
|
12
|
+
-- Substituir:
|
|
13
|
+
-- - schema_name: nome do schema (ex: central, public)
|
|
14
|
+
-- - table_name: nome da tabela
|
|
15
|
+
|
|
16
|
+
-- Habilitar RLS
|
|
17
|
+
ALTER TABLE schema_name.table_name ENABLE ROW LEVEL SECURITY;
|
|
18
|
+
|
|
19
|
+
-- SELECT: Ver apenas dados da empresa
|
|
20
|
+
CREATE POLICY "Users can view company data" ON schema_name.table_name
|
|
21
|
+
FOR SELECT USING (
|
|
22
|
+
((SELECT auth.jwt()) ->> 'alias') = alias
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
-- INSERT: Inserir apenas com alias da empresa do usuário
|
|
26
|
+
CREATE POLICY "Users can insert company data" ON schema_name.table_name
|
|
27
|
+
FOR INSERT WITH CHECK (
|
|
28
|
+
((SELECT auth.jwt()) ->> 'alias') = alias
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
-- UPDATE: Atualizar apenas dados da empresa
|
|
32
|
+
CREATE POLICY "Users can update company data" ON schema_name.table_name
|
|
33
|
+
FOR UPDATE USING (
|
|
34
|
+
((SELECT auth.jwt()) ->> 'alias') = alias
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
-- IMPORTANTE: NÃO criar política DELETE (soft delete usado)
|
|
38
|
+
|
|
39
|
+
-- ⚠️ IMPORTANTE: NÃO criar índices automaticamente
|
|
40
|
+
-- Índices devem ser criados APENAS quando explicitamente solicitado
|
|
41
|
+
-- Criar índices desnecessários prejudica performance de INSERT/UPDATE/DELETE
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
-- =========================================
|
|
45
|
+
-- 2. EXEMPLO COMPLETO: Tabela de Produtos
|
|
46
|
+
-- =========================================
|
|
47
|
+
|
|
48
|
+
-- Criar tabela de exemplo
|
|
49
|
+
CREATE TABLE central.products (
|
|
50
|
+
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
|
51
|
+
alias TEXT NOT NULL,
|
|
52
|
+
name TEXT NOT NULL,
|
|
53
|
+
description TEXT,
|
|
54
|
+
price DECIMAL(10,2),
|
|
55
|
+
is_removed BOOLEAN DEFAULT false,
|
|
56
|
+
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
|
|
57
|
+
updated_at TIMESTAMP WITH TIME ZONE DEFAULT now()
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
-- Habilitar RLS
|
|
61
|
+
ALTER TABLE central.products ENABLE ROW LEVEL SECURITY;
|
|
62
|
+
|
|
63
|
+
-- Políticas
|
|
64
|
+
CREATE POLICY "Users can view company products" ON central.products
|
|
65
|
+
FOR SELECT USING (
|
|
66
|
+
((SELECT auth.jwt()) ->> 'alias') = alias
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
CREATE POLICY "Users can insert company products" ON central.products
|
|
70
|
+
FOR INSERT WITH CHECK (
|
|
71
|
+
((SELECT auth.jwt()) ->> 'alias') = alias
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
CREATE POLICY "Users can update company products" ON central.products
|
|
75
|
+
FOR UPDATE USING (
|
|
76
|
+
((SELECT auth.jwt()) ->> 'alias') = alias
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
-- ⚠️ Nota: Índices NÃO foram criados (seguindo a regra de não criar automaticamente)
|
|
80
|
+
-- Crie índices apenas quando necessário para otimizar queries específicas
|