forlogic-core 1.4.15 → 1.5.1

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.
@@ -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