forlogic-core 3.0.0 → 3.0.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.
@@ -1,275 +1,275 @@
1
- # Golden Snippets — Exemplos Completos do Design System
2
-
3
- > Implementações de referência para a IA copiar e adaptar.
4
- > Cada snippet é funcional e segue 100% dos padrões do DS.
5
-
6
- ---
7
-
8
- ## 1. CRUD Completo com createCrudPage
9
-
10
- ```tsx
11
- // src/pages/ProcessesPage.tsx
12
- import { createSimpleService, createCrudPage, usePageMetadata } from 'forlogic-core';
13
-
14
- const processService = createSimpleService({
15
- tableName: 'processes',
16
- schema: 'quality',
17
- entityName: 'processo',
18
- searchFields: ['title', 'description'],
19
- });
20
-
21
- const ProcessesPage = createCrudPage({
22
- title: 'Processos',
23
- service: processService,
24
- columns: [
25
- { key: 'title', header: 'Título' },
26
- { key: 'status', header: 'Status' },
27
- { key: 'responsible', header: 'Responsável' },
28
- { key: 'updated_at', header: 'Atualizado em', format: 'datetime' },
29
- ],
30
- formSections: [{
31
- id: 'main',
32
- fields: [
33
- { name: 'title', label: 'Título', type: 'text', required: true },
34
- { name: 'description', label: 'Descrição', type: 'textarea' },
35
- { name: 'status', label: 'Status', type: 'combobox', options: [
36
- { value: 'active', label: 'Ativo' },
37
- { value: 'inactive', label: 'Inativo' },
38
- ]},
39
- { name: 'id_responsible', label: 'Responsável', type: 'combobox', entity: 'users' },
40
- ],
41
- }],
42
- actions: [
43
- { label: 'Editar', action: 'edit' },
44
- { label: 'Inativar', action: 'toggle-status', confirm: true },
45
- ],
46
- });
47
-
48
- export default ProcessesPage;
49
- ```
50
-
51
- ---
52
-
53
- ## 2. CrudTable Customizado com ActionButton
54
-
55
- ```tsx
56
- // src/pages/DocumentsPage.tsx
57
- import {
58
- CrudTable, CrudActionBar, ActionButton, Badge,
59
- usePageMetadata, useTranslation, getSupabaseClient,
60
- } from 'forlogic-core';
61
-
62
- export function DocumentsPage() {
63
- const { t } = useTranslation();
64
- usePageMetadata({ title: t('documents'), subtitle: t('documents_subtitle') });
65
-
66
- const columns = [
67
- { key: 'name', header: 'Nome' },
68
- {
69
- key: 'status',
70
- header: 'Status',
71
- cell: (row) => (
72
- <Badge variant={row.status === 'approved' ? 'default' : 'secondary'}>
73
- {row.status}
74
- </Badge>
75
- ),
76
- },
77
- { key: 'version', header: 'Versão' },
78
- {
79
- key: 'actions',
80
- header: '',
81
- cell: (row) => (
82
- <ActionButton
83
- actions={[
84
- { label: 'Editar', onClick: () => handleEdit(row) },
85
- { label: 'Duplicar', onClick: () => handleDuplicate(row) },
86
- { label: 'Inativar', onClick: () => handleDeactivate(row), variant: 'destructive' },
87
- ]}
88
- />
89
- ),
90
- },
91
- ];
92
-
93
- return (
94
- <div className="p-6 space-y-4">
95
- <CrudActionBar
96
- onAdd={() => setDialogOpen(true)}
97
- addLabel="Novo Documento"
98
- showSearch
99
- />
100
- <CrudTable columns={columns} data={documents} />
101
- </div>
102
- );
103
- }
104
- ```
105
-
106
- ---
107
-
108
- ## 3. Layout com AppLayout + Sidebar + Separador
109
-
110
- ```tsx
111
- // src/App.tsx
112
- import { BrowserRouter, Routes, Route } from 'react-router';
113
- import { CoreProviders, AppLayout } from 'forlogic-core';
114
- import { Home, FileText, Settings, BarChart3 } from 'lucide-react';
115
- import ptBR from './i18n/pt-BR.json';
116
-
117
- const sidebarConfig = {
118
- navigation: [
119
- { label: 'Início', path: '/', icon: Home },
120
- { label: 'Documentos', path: '/documents', icon: FileText },
121
- { label: 'Relatórios', path: '/reports', icon: BarChart3 },
122
- { type: 'separator' as const, label: '', path: '' },
123
- { label: 'Configurações', path: '/settings', icon: Settings },
124
- ],
125
- };
126
-
127
- export default function App() {
128
- return (
129
- <CoreProviders moduleAlias="quality" appTranslations={{ 'pt-BR': ptBR }}>
130
- <BrowserRouter>
131
- <AppLayout sidebarConfig={sidebarConfig}>
132
- <Routes>
133
- <Route path="/" element={<HomePage />} />
134
- <Route path="/documents" element={<DocumentsPage />} />
135
- </Routes>
136
- </AppLayout>
137
- </BrowserRouter>
138
- </CoreProviders>
139
- );
140
- }
141
- ```
142
-
143
- ---
144
-
145
- ## 4. Formulário com Dialog + BaseForm + Combobox
146
-
147
- ```tsx
148
- // src/components/ProcessForm.tsx
149
- import {
150
- Dialog, DialogContent, DialogHeader, DialogTitle,
151
- Input, Label, Combobox, Button,
152
- useTranslation,
153
- } from 'forlogic-core';
154
- import { useForm } from 'react-hook-form';
155
- import { zodResolver } from '@hookform/resolvers/zod';
156
- import { z } from 'zod';
157
-
158
- const schema = z.object({
159
- title: z.string().min(3, 'Mínimo 3 caracteres'),
160
- status: z.string(),
161
- id_responsible: z.string().uuid(),
162
- });
163
-
164
- type FormData = z.infer<typeof schema>;
165
-
166
- interface ProcessFormProps {
167
- open: boolean;
168
- onOpenChange: (open: boolean) => void;
169
- onSubmit: (data: FormData) => void;
170
- defaultValues?: Partial<FormData>;
171
- }
172
-
173
- export function ProcessForm({ open, onOpenChange, onSubmit, defaultValues }: ProcessFormProps) {
174
- const { t } = useTranslation();
175
- const form = useForm<FormData>({
176
- resolver: zodResolver(schema),
177
- defaultValues,
178
- });
179
-
180
- return (
181
- <Dialog open={open} onOpenChange={onOpenChange}>
182
- <DialogContent>
183
- <DialogHeader>
184
- <DialogTitle>{defaultValues ? t('edit') : t('create')}</DialogTitle>
185
- </DialogHeader>
186
- <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
187
- <div className="space-y-2">
188
- <Label htmlFor="title">{t('title')}</Label>
189
- <Input id="title" {...form.register('title')} />
190
- </div>
191
- <div className="space-y-2">
192
- <Label>{t('status')}</Label>
193
- <Combobox
194
- options={[
195
- { value: 'active', label: 'Ativo' },
196
- { value: 'inactive', label: 'Inativo' },
197
- ]}
198
- value={form.watch('status')}
199
- onValueChange={(v) => form.setValue('status', v)}
200
- />
201
- </div>
202
- <div className="flex justify-end gap-2">
203
- <Button variant="outline" onClick={() => onOpenChange(false)}>{t('cancel')}</Button>
204
- <Button type="submit">{t('save')}</Button>
205
- </div>
206
- </form>
207
- </DialogContent>
208
- </Dialog>
209
- );
210
- }
211
- ```
212
-
213
- ---
214
-
215
- ## 5. Supabase com Schema Correto
216
-
217
- ```tsx
218
- // src/services/processService.ts
219
- import { getSupabaseClient } from 'forlogic-core';
220
-
221
- const SCHEMA = 'quality';
222
-
223
- export async function fetchProcesses() {
224
- const supabase = getSupabaseClient();
225
- const { data, error } = await supabase
226
- .schema(SCHEMA)
227
- .from('processes')
228
- .select('*')
229
- .is('deleted_at', null)
230
- .order('created_at', { ascending: false });
231
-
232
- if (error) throw error;
233
- return data;
234
- }
235
-
236
- export async function softDeleteProcess(id: string) {
237
- const supabase = getSupabaseClient();
238
- const { error } = await supabase
239
- .schema(SCHEMA)
240
- .from('processes')
241
- .update({ deleted_at: new Date().toISOString() })
242
- .eq('id', id);
243
-
244
- if (error) throw error;
245
- }
246
- ```
247
-
248
- ---
249
-
250
- ## 6. Página com usePageMetadata + Breadcrumbs
251
-
252
- ```tsx
253
- // src/pages/ProcessDetailPage.tsx
254
- import { usePageMetadata } from 'forlogic-core';
255
- import { useParams } from 'react-router';
256
-
257
- export function ProcessDetailPage() {
258
- const { id } = useParams();
259
-
260
- usePageMetadata({
261
- title: 'Detalhes do Processo',
262
- subtitle: 'Visualize e edite os dados do processo',
263
- breadcrumbs: [
264
- { label: 'Processos', href: '/processes' },
265
- { label: 'Detalhes' },
266
- ],
267
- });
268
-
269
- return (
270
- <div className="p-6">
271
- {/* conteúdo da página */}
272
- </div>
273
- );
274
- }
275
- ```
1
+ # Golden Snippets — Exemplos Completos do Design System
2
+
3
+ > Implementações de referência para a IA copiar e adaptar.
4
+ > Cada snippet é funcional e segue 100% dos padrões do DS.
5
+
6
+ ---
7
+
8
+ ## 1. CRUD Completo com createCrudPage
9
+
10
+ ```tsx
11
+ // src/pages/ProcessesPage.tsx
12
+ import { createSimpleService, createCrudPage, usePageMetadata } from 'forlogic-core';
13
+
14
+ const processService = createSimpleService({
15
+ tableName: 'processes',
16
+ schema: 'quality',
17
+ entityName: 'processo',
18
+ searchFields: ['title', 'description'],
19
+ });
20
+
21
+ const ProcessesPage = createCrudPage({
22
+ title: 'Processos',
23
+ service: processService,
24
+ columns: [
25
+ { key: 'title', header: 'Título' },
26
+ { key: 'status', header: 'Status' },
27
+ { key: 'responsible', header: 'Responsável' },
28
+ { key: 'updated_at', header: 'Atualizado em', format: 'datetime' },
29
+ ],
30
+ formSections: [{
31
+ id: 'main',
32
+ fields: [
33
+ { name: 'title', label: 'Título', type: 'text', required: true },
34
+ { name: 'description', label: 'Descrição', type: 'textarea' },
35
+ { name: 'status', label: 'Status', type: 'combobox', options: [
36
+ { value: 'active', label: 'Ativo' },
37
+ { value: 'inactive', label: 'Inativo' },
38
+ ]},
39
+ { name: 'id_responsible', label: 'Responsável', type: 'combobox', entity: 'users' },
40
+ ],
41
+ }],
42
+ actions: [
43
+ { label: 'Editar', action: 'edit' },
44
+ { label: 'Inativar', action: 'toggle-status', confirm: true },
45
+ ],
46
+ });
47
+
48
+ export default ProcessesPage;
49
+ ```
50
+
51
+ ---
52
+
53
+ ## 2. CrudTable Customizado com ActionButton
54
+
55
+ ```tsx
56
+ // src/pages/DocumentsPage.tsx
57
+ import {
58
+ CrudTable, CrudActionBar, ActionButton, Badge,
59
+ usePageMetadata, useTranslation, getSupabaseClient,
60
+ } from 'forlogic-core';
61
+
62
+ export function DocumentsPage() {
63
+ const { t } = useTranslation();
64
+ usePageMetadata({ title: t('documents'), subtitle: t('documents_subtitle') });
65
+
66
+ const columns = [
67
+ { key: 'name', header: 'Nome' },
68
+ {
69
+ key: 'status',
70
+ header: 'Status',
71
+ cell: (row) => (
72
+ <Badge variant={row.status === 'approved' ? 'default' : 'secondary'}>
73
+ {row.status}
74
+ </Badge>
75
+ ),
76
+ },
77
+ { key: 'version', header: 'Versão' },
78
+ {
79
+ key: 'actions',
80
+ header: '',
81
+ cell: (row) => (
82
+ <ActionButton
83
+ actions={[
84
+ { label: 'Editar', onClick: () => handleEdit(row) },
85
+ { label: 'Duplicar', onClick: () => handleDuplicate(row) },
86
+ { label: 'Inativar', onClick: () => handleDeactivate(row), variant: 'destructive' },
87
+ ]}
88
+ />
89
+ ),
90
+ },
91
+ ];
92
+
93
+ return (
94
+ <div className="p-6 space-y-4">
95
+ <CrudActionBar
96
+ onAdd={() => setDialogOpen(true)}
97
+ addLabel="Novo Documento"
98
+ showSearch
99
+ />
100
+ <CrudTable columns={columns} data={documents} />
101
+ </div>
102
+ );
103
+ }
104
+ ```
105
+
106
+ ---
107
+
108
+ ## 3. Layout com AppLayout + Sidebar + Separador
109
+
110
+ ```tsx
111
+ // src/App.tsx
112
+ import { BrowserRouter, Routes, Route } from 'react-router';
113
+ import { CoreProviders, AppLayout } from 'forlogic-core';
114
+ import { Home, FileText, Settings, BarChart3 } from 'lucide-react';
115
+ import ptBR from './i18n/pt-BR.json';
116
+
117
+ const sidebarConfig = {
118
+ navigation: [
119
+ { label: 'Início', path: '/', icon: Home },
120
+ { label: 'Documentos', path: '/documents', icon: FileText },
121
+ { label: 'Relatórios', path: '/reports', icon: BarChart3 },
122
+ { type: 'separator' as const, label: '', path: '' },
123
+ { label: 'Configurações', path: '/settings', icon: Settings },
124
+ ],
125
+ };
126
+
127
+ export default function App() {
128
+ return (
129
+ <CoreProviders moduleAlias="quality" appTranslations={{ 'pt-BR': ptBR }}>
130
+ <BrowserRouter>
131
+ <AppLayout sidebarConfig={sidebarConfig}>
132
+ <Routes>
133
+ <Route path="/" element={<HomePage />} />
134
+ <Route path="/documents" element={<DocumentsPage />} />
135
+ </Routes>
136
+ </AppLayout>
137
+ </BrowserRouter>
138
+ </CoreProviders>
139
+ );
140
+ }
141
+ ```
142
+
143
+ ---
144
+
145
+ ## 4. Formulário com Dialog + BaseForm + Combobox
146
+
147
+ ```tsx
148
+ // src/components/ProcessForm.tsx
149
+ import {
150
+ Dialog, DialogContent, DialogHeader, DialogTitle,
151
+ Input, Label, Combobox, Button,
152
+ useTranslation,
153
+ } from 'forlogic-core';
154
+ import { useForm } from 'react-hook-form';
155
+ import { zodResolver } from '@hookform/resolvers/zod';
156
+ import { z } from 'zod';
157
+
158
+ const schema = z.object({
159
+ title: z.string().min(3, 'Mínimo 3 caracteres'),
160
+ status: z.string(),
161
+ id_responsible: z.string().uuid(),
162
+ });
163
+
164
+ type FormData = z.infer<typeof schema>;
165
+
166
+ interface ProcessFormProps {
167
+ open: boolean;
168
+ onOpenChange: (open: boolean) => void;
169
+ onSubmit: (data: FormData) => void;
170
+ defaultValues?: Partial<FormData>;
171
+ }
172
+
173
+ export function ProcessForm({ open, onOpenChange, onSubmit, defaultValues }: ProcessFormProps) {
174
+ const { t } = useTranslation();
175
+ const form = useForm<FormData>({
176
+ resolver: zodResolver(schema),
177
+ defaultValues,
178
+ });
179
+
180
+ return (
181
+ <Dialog open={open} onOpenChange={onOpenChange}>
182
+ <DialogContent>
183
+ <DialogHeader>
184
+ <DialogTitle>{defaultValues ? t('edit') : t('create')}</DialogTitle>
185
+ </DialogHeader>
186
+ <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
187
+ <div className="space-y-2">
188
+ <Label htmlFor="title">{t('title')}</Label>
189
+ <Input id="title" {...form.register('title')} />
190
+ </div>
191
+ <div className="space-y-2">
192
+ <Label>{t('status')}</Label>
193
+ <Combobox
194
+ options={[
195
+ { value: 'active', label: 'Ativo' },
196
+ { value: 'inactive', label: 'Inativo' },
197
+ ]}
198
+ value={form.watch('status')}
199
+ onValueChange={(v) => form.setValue('status', v)}
200
+ />
201
+ </div>
202
+ <div className="flex justify-end gap-2">
203
+ <Button variant="outline" onClick={() => onOpenChange(false)}>{t('cancel')}</Button>
204
+ <Button type="submit">{t('save')}</Button>
205
+ </div>
206
+ </form>
207
+ </DialogContent>
208
+ </Dialog>
209
+ );
210
+ }
211
+ ```
212
+
213
+ ---
214
+
215
+ ## 5. Supabase com Schema Correto
216
+
217
+ ```tsx
218
+ // src/services/processService.ts
219
+ import { getSupabaseClient } from 'forlogic-core';
220
+
221
+ const SCHEMA = 'quality';
222
+
223
+ export async function fetchProcesses() {
224
+ const supabase = getSupabaseClient();
225
+ const { data, error } = await supabase
226
+ .schema(SCHEMA)
227
+ .from('processes')
228
+ .select('*')
229
+ .is('deleted_at', null)
230
+ .order('created_at', { ascending: false });
231
+
232
+ if (error) throw error;
233
+ return data;
234
+ }
235
+
236
+ export async function softDeleteProcess(id: string) {
237
+ const supabase = getSupabaseClient();
238
+ const { error } = await supabase
239
+ .schema(SCHEMA)
240
+ .from('processes')
241
+ .update({ deleted_at: new Date().toISOString() })
242
+ .eq('id', id);
243
+
244
+ if (error) throw error;
245
+ }
246
+ ```
247
+
248
+ ---
249
+
250
+ ## 6. Página com usePageMetadata + Breadcrumbs
251
+
252
+ ```tsx
253
+ // src/pages/ProcessDetailPage.tsx
254
+ import { usePageMetadata } from 'forlogic-core';
255
+ import { useParams } from 'react-router';
256
+
257
+ export function ProcessDetailPage() {
258
+ const { id } = useParams();
259
+
260
+ usePageMetadata({
261
+ title: 'Detalhes do Processo',
262
+ subtitle: 'Visualize e edite os dados do processo',
263
+ breadcrumbs: [
264
+ { label: 'Processos', href: '/processes' },
265
+ { label: 'Detalhes' },
266
+ ],
267
+ });
268
+
269
+ return (
270
+ <div className="p-6">
271
+ {/* conteúdo da página */}
272
+ </div>
273
+ );
274
+ }
275
+ ```
@@ -101,7 +101,7 @@ import { UpdatesNotification } from 'forlogic-core';
101
101
  updates={[{ id: '1', title: 'Nova versão', text: 'Descrição' }]}
102
102
  badgeCount={1}
103
103
  onOpen={() => markAsViewed()}
104
- onViewAll={() => window.open('https://apps4.qualiex.com/common/alias/up/view', '_blank')}
104
+ onViewAll={() => navigate(\`/\${alias}/up/view\`)}
105
105
  />
106
106
  ```
107
107
 
@@ -115,7 +115,7 @@ import { UpdatesNotification } from
115
115
  - No modo automático, o componente usa useAuth() para obter o alias e useUpdatesNotification() para buscar dados da API.
116
116
  - No modo controlado, todas as props são fornecidas externamente — útil para testes e documentação.
117
117
  - O tipo canônico é UpdateNotificationItem (id, title, text), exportado do hook. UpdateNotification é um alias deprecated.
118
- - No modo automático, onOpen chama markAsVisualized (POST Updates/userVisualized/3/undefined) e onViewAll abre a rota legada.
118
+ - No modo automático, onOpen chama markAsVisualized (POST Updates/userVisualized/3/undefined) e onViewAll abre o app Common V5 em nova aba ({commonV5AppUrl}{alias}/up/view), único app que hospeda a página de atualizações. Apps que tenham a própria rota podem passar viewAllPath para navegar internamente.
119
119
  - O ícone utilizado é Coffee do Lucide React — não requer fontes externas nos projetos consumidores.
120
120
  - O hook useUpdatesNotification também é exportado separadamente para uso avançado.
121
121
  - Todas as chamadas à API incluem o header x-waf-rate (base64 do campo sub do JWT), exigido pelo WAF da API legada.