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.
- package/README.md +21 -1116
- package/dist/README.md +36 -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 +2 -2
- package/dist/index.js +2 -2
- package/package.json +4 -1
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TEMPLATE: Hook de Verificação de Permissão
|
|
3
|
+
* 📚 Detalhes: Consulte docs/FOR_AI.md
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { usePermissionQuery } from 'forlogic-core';
|
|
7
|
+
import { useAuth } from 'forlogic-core';
|
|
8
|
+
import { supabase } from '@/integrations/supabase/client';
|
|
9
|
+
|
|
10
|
+
// ============= EXEMPLO 1: Verificação Simples =============
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Hook para verificar se o usuário tem acesso a uma funcionalidade específica
|
|
14
|
+
*/
|
|
15
|
+
export const useFeatureAccess = () => {
|
|
16
|
+
const { user, selectedUnit } = useAuth();
|
|
17
|
+
|
|
18
|
+
return usePermissionQuery({
|
|
19
|
+
key: 'feature-access',
|
|
20
|
+
enabled: !!user && !!selectedUnit,
|
|
21
|
+
checkFn: async () => {
|
|
22
|
+
const { data } = await supabase
|
|
23
|
+
.from('feature_permissions')
|
|
24
|
+
.select('has_access')
|
|
25
|
+
.eq('unit_alias', selectedUnit!.alias)
|
|
26
|
+
.eq('feature_name', 'my-feature')
|
|
27
|
+
.single();
|
|
28
|
+
|
|
29
|
+
return data?.has_access || false;
|
|
30
|
+
},
|
|
31
|
+
staleTime: 30 * 60 * 1000, // 30 minutos
|
|
32
|
+
gcTime: 35 * 60 * 1000
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// ============= EXEMPLO 2: Verificação com Service =============
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Service para centralizar lógica de permissões
|
|
40
|
+
*/
|
|
41
|
+
class PermissionService {
|
|
42
|
+
async checkFeatureAccess(alias: string, feature: string): Promise<boolean> {
|
|
43
|
+
const { data, error } = await supabase
|
|
44
|
+
.rpc('check_feature_permission', {
|
|
45
|
+
p_alias: alias,
|
|
46
|
+
p_feature: feature
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (error) {
|
|
50
|
+
console.error('Permission check error:', error);
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return data || false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const permissionService = new PermissionService();
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Hook usando service
|
|
62
|
+
*/
|
|
63
|
+
export const useAdvancedFeatureAccess = (featureName: string) => {
|
|
64
|
+
const { user, selectedUnit } = useAuth();
|
|
65
|
+
|
|
66
|
+
return usePermissionQuery({
|
|
67
|
+
key: `feature-access-${featureName}`,
|
|
68
|
+
enabled: !!user && !!selectedUnit,
|
|
69
|
+
checkFn: () => permissionService.checkFeatureAccess(
|
|
70
|
+
selectedUnit!.alias,
|
|
71
|
+
featureName
|
|
72
|
+
),
|
|
73
|
+
staleTime: 30 * 60 * 1000,
|
|
74
|
+
gcTime: 35 * 60 * 1000
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// ============= EXEMPLO 3: Múltiplas Permissões =============
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Hook para verificar múltiplas permissões de uma vez
|
|
82
|
+
*/
|
|
83
|
+
export const useMultiplePermissions = (features: string[]) => {
|
|
84
|
+
const { user, selectedUnit } = useAuth();
|
|
85
|
+
|
|
86
|
+
return usePermissionQuery({
|
|
87
|
+
key: `multi-permissions-${features.join('-')}`,
|
|
88
|
+
enabled: !!user && !!selectedUnit && features.length > 0,
|
|
89
|
+
checkFn: async () => {
|
|
90
|
+
const { data } = await supabase
|
|
91
|
+
.from('feature_permissions')
|
|
92
|
+
.select('feature_name, has_access')
|
|
93
|
+
.eq('unit_alias', selectedUnit!.alias)
|
|
94
|
+
.in('feature_name', features);
|
|
95
|
+
|
|
96
|
+
// Retorna true se todas as features têm acesso
|
|
97
|
+
return data?.every(item => item.has_access) || false;
|
|
98
|
+
},
|
|
99
|
+
staleTime: 30 * 60 * 1000,
|
|
100
|
+
gcTime: 35 * 60 * 1000
|
|
101
|
+
});
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// ============= USO NOS COMPONENTES =============
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Exemplo 1: Proteger componente inteiro
|
|
108
|
+
*/
|
|
109
|
+
function ProtectedComponent() {
|
|
110
|
+
const { data: hasAccess, isLoading } = useFeatureAccess();
|
|
111
|
+
|
|
112
|
+
if (isLoading) {
|
|
113
|
+
return <div>Verificando permissões...</div>;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (!hasAccess) {
|
|
117
|
+
return <div>Acesso negado</div>;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return <div>Conteúdo protegido</div>;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Exemplo 2: Proteger ação específica
|
|
125
|
+
*/
|
|
126
|
+
function ComponentWithProtectedAction() {
|
|
127
|
+
const { data: hasAccess } = useFeatureAccess();
|
|
128
|
+
|
|
129
|
+
const handleSensitiveAction = () => {
|
|
130
|
+
if (!hasAccess) {
|
|
131
|
+
toast.error('Você não tem permissão para esta ação');
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Executar ação
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<button
|
|
140
|
+
onClick={handleSensitiveAction}
|
|
141
|
+
disabled={!hasAccess}
|
|
142
|
+
>
|
|
143
|
+
Ação Sensível
|
|
144
|
+
</button>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Exemplo 3: Condicional na UI
|
|
150
|
+
*/
|
|
151
|
+
function ConditionalUI() {
|
|
152
|
+
const { data: hasAccess } = useFeatureAccess();
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<div>
|
|
156
|
+
<h1>Dashboard</h1>
|
|
157
|
+
|
|
158
|
+
{/* Sempre visível */}
|
|
159
|
+
<div>Conteúdo público</div>
|
|
160
|
+
|
|
161
|
+
{/* Condicional */}
|
|
162
|
+
{hasAccess && (
|
|
163
|
+
<div>Conteúdo exclusivo</div>
|
|
164
|
+
)}
|
|
165
|
+
</div>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ============= INTEGRAÇÃO COM SIDEBAR =============
|
|
170
|
+
|
|
171
|
+
import type { SidebarConfig } from 'forlogic-core';
|
|
172
|
+
import { Settings, Package, Users } from 'lucide-react';
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Configuração da sidebar com itens condicionais
|
|
176
|
+
*/
|
|
177
|
+
export const sidebarConfig: SidebarConfig = {
|
|
178
|
+
appName: 'Minha Aplicação',
|
|
179
|
+
navigation: [
|
|
180
|
+
// Item sempre visível
|
|
181
|
+
{
|
|
182
|
+
label: 'Dashboard',
|
|
183
|
+
path: '/dashboard',
|
|
184
|
+
icon: Settings,
|
|
185
|
+
},
|
|
186
|
+
// Item condicional com hook
|
|
187
|
+
{
|
|
188
|
+
label: 'Gestão',
|
|
189
|
+
path: '/management',
|
|
190
|
+
icon: Package,
|
|
191
|
+
useAccessHook: useFeatureAccess // Hook de verificação
|
|
192
|
+
},
|
|
193
|
+
// Outro item condicional
|
|
194
|
+
{
|
|
195
|
+
label: 'Usuários',
|
|
196
|
+
path: '/users',
|
|
197
|
+
icon: Users,
|
|
198
|
+
useAccessHook: () => useAdvancedFeatureAccess('users-management')
|
|
199
|
+
}
|
|
200
|
+
]
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
// ============= CONFIGURAÇÃO DE CACHE =============
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Recomendações de tempo de cache por tipo de permissão
|
|
207
|
+
*/
|
|
208
|
+
|
|
209
|
+
// Permissões raramente alteradas (ex: roles, perfis)
|
|
210
|
+
export const useRoleCheck = () => {
|
|
211
|
+
return usePermissionQuery({
|
|
212
|
+
// ...
|
|
213
|
+
staleTime: 30 * 60 * 1000, // 30 minutos
|
|
214
|
+
gcTime: 60 * 60 * 1000 // 1 hora
|
|
215
|
+
});
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
// Permissões dinâmicas (ex: acesso temporário)
|
|
219
|
+
export const useDynamicPermission = () => {
|
|
220
|
+
return usePermissionQuery({
|
|
221
|
+
// ...
|
|
222
|
+
staleTime: 5 * 60 * 1000, // 5 minutos
|
|
223
|
+
gcTime: 10 * 60 * 1000 // 10 minutos
|
|
224
|
+
});
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
// Verificações críticas (ex: transações financeiras)
|
|
228
|
+
export const useCriticalPermission = () => {
|
|
229
|
+
return usePermissionQuery({
|
|
230
|
+
// ...
|
|
231
|
+
staleTime: 0, // Sem cache
|
|
232
|
+
gcTime: 0 // Sem garbage collection
|
|
233
|
+
});
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// ============= INVALIDAÇÃO MANUAL (RARAMENTE NECESSÁRIA) =============
|
|
237
|
+
|
|
238
|
+
import { useQueryClient } from '@tanstack/react-query';
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Exemplo de invalidação manual (use apenas quando necessário)
|
|
242
|
+
*/
|
|
243
|
+
function AdminPanel() {
|
|
244
|
+
const queryClient = useQueryClient();
|
|
245
|
+
|
|
246
|
+
const handlePermissionUpdate = async () => {
|
|
247
|
+
// Atualizar permissão no backend
|
|
248
|
+
await updatePermission();
|
|
249
|
+
|
|
250
|
+
// Invalidar cache específico
|
|
251
|
+
queryClient.invalidateQueries({
|
|
252
|
+
queryKey: ['permission', 'feature-access']
|
|
253
|
+
});
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
return <button onClick={handlePermissionUpdate}>Atualizar Permissão</button>;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// ============= BOAS PRÁTICAS =============
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* ✅ FAÇA:
|
|
263
|
+
* - Use staleTime apropriado para cada tipo de permissão
|
|
264
|
+
* - Sempre habilite apenas quando user e selectedUnit existirem
|
|
265
|
+
* - Trate loading states nos componentes
|
|
266
|
+
* - Use keys descritivas e únicas
|
|
267
|
+
* - Centralize lógica em services quando complexa
|
|
268
|
+
*
|
|
269
|
+
* ❌ NÃO FAÇA:
|
|
270
|
+
* - Não use staleTime muito longo para permissões críticas
|
|
271
|
+
* - Não esqueça de desabilitar quando dependências faltam
|
|
272
|
+
* - Não ignore estados de loading
|
|
273
|
+
* - Não invalidate manualmente sem necessidade (AuthContext já faz)
|
|
274
|
+
* - Não faça verificações síncronas de permissão
|
|
275
|
+
*/
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// ============= CONFIGURAÇÃO INICIAL QUALIEX =============
|
|
2
|
+
// Copie este código para o arquivo principal da aplicação (main.tsx ou App.tsx)
|
|
3
|
+
|
|
4
|
+
import { setQualiexConfig } from 'forlogic-core';
|
|
5
|
+
|
|
6
|
+
// ============= 1. VARIÁVEIS DE AMBIENTE =============
|
|
7
|
+
// Adicione no arquivo .env do projeto:
|
|
8
|
+
/*
|
|
9
|
+
VITE_QUALIEX_API_URL=https://common-v4-api.qualiex.com
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// Configuração da integração
|
|
13
|
+
// Execute no início da aplicação (antes de renderizar componentes)
|
|
14
|
+
|
|
15
|
+
export function setupQualiexIntegration() {
|
|
16
|
+
setQualiexConfig({
|
|
17
|
+
// Enriquecimento automático de entidades com responsible_name
|
|
18
|
+
enableUserEnrichment: true,
|
|
19
|
+
|
|
20
|
+
// Habilitar API de usuários Qualiex
|
|
21
|
+
enableUsersApi: true,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ============= 3. USO NO MAIN.TSX =============
|
|
26
|
+
/*
|
|
27
|
+
import { StrictMode } from 'react';
|
|
28
|
+
import { createRoot } from 'react-dom/client';
|
|
29
|
+
import { setupQualiexIntegration } from './config/qualiexConfig';
|
|
30
|
+
import App from './App';
|
|
31
|
+
|
|
32
|
+
// Configurar Qualiex ANTES de renderizar
|
|
33
|
+
setupQualiexIntegration();
|
|
34
|
+
|
|
35
|
+
createRoot(document.getElementById('root')!).render(
|
|
36
|
+
<StrictMode>
|
|
37
|
+
<App />
|
|
38
|
+
</StrictMode>
|
|
39
|
+
);
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
// ============= 4. EXPLICAÇÃO DAS OPÇÕES =============
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* enableUserEnrichment: boolean
|
|
46
|
+
*
|
|
47
|
+
* Quando true, o QualiexEnrichmentService adiciona automaticamente
|
|
48
|
+
* o campo `responsible_name` às entidades que possuem `id_user`.
|
|
49
|
+
*
|
|
50
|
+
* Exemplo:
|
|
51
|
+
* const tasks = await taskService.getAll();
|
|
52
|
+
* // Se enableUserEnrichment: true
|
|
53
|
+
* // tasks[0] = { id: '1', title: 'Tarefa', id_user: 'user-123', responsible_name: 'João Silva' }
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* enableUsersApi: boolean
|
|
58
|
+
*
|
|
59
|
+
* Quando true, habilita chamadas à API de usuários Qualiex.
|
|
60
|
+
* Necessário para usar useQualiexUsers e QualiexUserField.
|
|
61
|
+
*
|
|
62
|
+
* Se false, useQualiexUsers não fará requisições.
|
|
63
|
+
*/
|
|
64
|
+
|
|
65
|
+
// ============= 5. EXEMPLO DE CONFIGURAÇÃO CONDICIONAL =============
|
|
66
|
+
// Configure baseado no ambiente ou feature flags
|
|
67
|
+
|
|
68
|
+
export function setupQualiexIntegrationConditional() {
|
|
69
|
+
const isProduction = import.meta.env.PROD;
|
|
70
|
+
const hasQualiexUrl = !!import.meta.env.VITE_QUALIEX_API_URL;
|
|
71
|
+
|
|
72
|
+
setQualiexConfig({
|
|
73
|
+
// Habilitar enriquecimento apenas em produção
|
|
74
|
+
enableUserEnrichment: isProduction && hasQualiexUrl,
|
|
75
|
+
|
|
76
|
+
// Habilitar API sempre que URL estiver configurada
|
|
77
|
+
enableUsersApi: hasQualiexUrl,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ============= 6. VALIDAÇÃO DA CONFIGURAÇÃO =============
|
|
82
|
+
// Helper para validar se Qualiex está configurado corretamente
|
|
83
|
+
|
|
84
|
+
export function validateQualiexSetup() {
|
|
85
|
+
const qualiexUrl = import.meta.env.VITE_QUALIEX_API_URL;
|
|
86
|
+
|
|
87
|
+
if (!qualiexUrl) {
|
|
88
|
+
console.warn(
|
|
89
|
+
'⚠️ VITE_QUALIEX_API_URL não configurado. ' +
|
|
90
|
+
'Integração Qualiex não funcionará.'
|
|
91
|
+
);
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
console.log('✅ Integração Qualiex configurada:', qualiexUrl);
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ============= 7. DESABILITAR INTEGRAÇÃO =============
|
|
100
|
+
// Para desabilitar completamente a integração Qualiex
|
|
101
|
+
|
|
102
|
+
export function disableQualiexIntegration() {
|
|
103
|
+
setQualiexConfig({
|
|
104
|
+
enableUserEnrichment: false,
|
|
105
|
+
enableUsersApi: false,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ============= RESUMO =============
|
|
110
|
+
/*
|
|
111
|
+
PASSOS PARA CONFIGURAR:
|
|
112
|
+
|
|
113
|
+
1. Adicionar no .env:
|
|
114
|
+
VITE_QUALIEX_API_URL=https://common-v4-api.qualiex.com
|
|
115
|
+
|
|
116
|
+
2. No main.tsx, antes de renderizar:
|
|
117
|
+
import { setQualiexConfig } from 'forlogic-core';
|
|
118
|
+
|
|
119
|
+
setQualiexConfig({
|
|
120
|
+
enableUserEnrichment: true,
|
|
121
|
+
enableUsersApi: true,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
3. Usar nos componentes:
|
|
125
|
+
import { QualiexUserField, useQualiexUsers } from 'forlogic-core';
|
|
126
|
+
|
|
127
|
+
⚠️ IMPORTANTE:
|
|
128
|
+
- setQualiexConfig deve ser chamado ANTES de usar componentes Qualiex
|
|
129
|
+
- VITE_QUALIEX_API_URL deve existir no .env
|
|
130
|
+
- A autenticação é feita automaticamente via TokenManager
|
|
131
|
+
- O header un-alias é adicionado automaticamente
|
|
132
|
+
|
|
133
|
+
✅ VERIFICAR SE ESTÁ FUNCIONANDO:
|
|
134
|
+
- useQualiexUsers deve retornar lista de usuários
|
|
135
|
+
- QualiexUserField deve mostrar loading e depois lista
|
|
136
|
+
- Console não deve mostrar erros 401/403
|
|
137
|
+
*/
|