@hed-hog/catalog 0.0.279 → 0.0.285

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,1154 +1,1154 @@
1
- import type { LucideIcon } from 'lucide-react';
2
- import {
3
- BadgePercent,
4
- Boxes,
5
- Building2,
6
- Eye,
7
- Factory,
8
- Globe2,
9
- Layers3,
10
- ListFilter,
11
- PackagePlus,
12
- PackageSearch,
13
- Scale,
14
- SearchCode,
15
- ShoppingCart,
16
- Store,
17
- Tags,
18
- } from 'lucide-react';
19
-
20
- type CatalogListVariant = 'cards' | 'table';
21
- type CatalogFieldDisplayType = 'text' | 'boolean' | 'currency';
22
- type CatalogFormFieldType =
23
- | 'text'
24
- | 'url'
25
- | 'textarea'
26
- | 'richtext'
27
- | 'number'
28
- | 'currency'
29
- | 'switch'
30
- | 'select'
31
- | 'relation'
32
- | 'upload'
33
- | 'json'
34
- | 'date'
35
- | 'datetime';
36
-
37
- export type CatalogLocalizedText = {
38
- pt: string;
39
- en: string;
40
- };
41
-
42
- export type CatalogFieldDefinition = {
43
- key: string;
44
- labelKey: string;
45
- type?: CatalogFieldDisplayType;
46
- };
47
-
48
- export type CatalogFilterOptionDefinition = {
49
- value: string;
50
- labelKey: string;
51
- };
52
-
53
- export type CatalogContextualKpiDefinition = {
54
- translationKey: string;
55
- icon: LucideIcon;
56
- count: (records: Record<string, unknown>[]) => number;
57
- };
58
-
59
- export type CatalogFormOptionDefinition = {
60
- value: string;
61
- label: CatalogLocalizedText;
62
- };
63
-
64
- export type CatalogRelationDefinition = {
65
- endpoint: string;
66
- resource?: string;
67
- createResource?: string;
68
- allowCreate?: boolean;
69
- valueKey?: string;
70
- labelKeys: string[];
71
- searchParam?: string;
72
- };
73
-
74
- export type CatalogFormFieldDefinition = {
75
- key: string;
76
- label: CatalogLocalizedText;
77
- type: CatalogFormFieldType;
78
- required?: boolean;
79
- span?: 1 | 2;
80
- placeholder?: CatalogLocalizedText;
81
- options?: CatalogFormOptionDefinition[];
82
- relation?: CatalogRelationDefinition;
83
- uploadDestination?: string;
84
- accept?: string;
85
- uploadPreviewVariant?: 'default' | 'square';
86
- };
87
-
88
- export type CatalogFormSectionDefinition = {
89
- title: CatalogLocalizedText;
90
- description?: CatalogLocalizedText;
91
- fields: CatalogFormFieldDefinition[];
92
- };
93
-
94
- export type CatalogResourceDefinition = {
95
- resource: string;
96
- translationKey: string;
97
- singularLabel: CatalogLocalizedText;
98
- createActionLabel: CatalogLocalizedText;
99
- editActionLabel: CatalogLocalizedText;
100
- icon: LucideIcon;
101
- href: string;
102
- colorClass: string;
103
- glowClass: string;
104
- featured?: boolean;
105
- hasActiveStats?: boolean;
106
- template: Record<string, unknown>;
107
- listVariant: CatalogListVariant;
108
- primaryFilterField: string;
109
- primaryFilterOptions: CatalogFilterOptionDefinition[];
110
- titleFields: string[];
111
- descriptionFields: string[];
112
- badgeFields: string[];
113
- cardMetadata: CatalogFieldDefinition[];
114
- tableColumns?: CatalogFieldDefinition[];
115
- contextualKpi: CatalogContextualKpiDefinition;
116
- formSections: CatalogFormSectionDefinition[];
117
- };
118
-
119
- const ptEn = (pt: string, en: string): CatalogLocalizedText => ({ pt, en });
120
-
121
- const statusFilterOptions: CatalogFilterOptionDefinition[] = [
122
- { value: 'active', labelKey: 'active' },
123
- { value: 'inactive', labelKey: 'inactive' },
124
- { value: 'draft', labelKey: 'draft' },
125
- { value: 'published', labelKey: 'published' },
126
- ];
127
-
128
- const availabilityFilterOptions: CatalogFilterOptionDefinition[] = [
129
- { value: 'in_stock', labelKey: 'inStock' },
130
- { value: 'out_of_stock', labelKey: 'outOfStock' },
131
- { value: 'preorder', labelKey: 'preorder' },
132
- ];
133
-
134
- const dataTypeFilterOptions: CatalogFilterOptionDefinition[] = [
135
- { value: 'text', labelKey: 'text' },
136
- { value: 'number', labelKey: 'number' },
137
- { value: 'boolean', labelKey: 'boolean' },
138
- { value: 'json', labelKey: 'json' },
139
- ];
140
-
141
- const facetModeFilterOptions: CatalogFilterOptionDefinition[] = [
142
- { value: 'default', labelKey: 'default' },
143
- { value: 'range', labelKey: 'range' },
144
- { value: 'select', labelKey: 'select' },
145
- ];
146
-
147
- const statusOptions = [
148
- { value: 'active', label: ptEn('Ativo', 'Active') },
149
- { value: 'inactive', label: ptEn('Inativo', 'Inactive') },
150
- { value: 'draft', label: ptEn('Rascunho', 'Draft') },
151
- { value: 'published', label: ptEn('Publicado', 'Published') },
152
- ];
153
-
154
- const comparisonStatusOptions = [
155
- { value: 'draft', label: ptEn('Rascunho', 'Draft') },
156
- { value: 'active', label: ptEn('Ativo', 'Active') },
157
- { value: 'published', label: ptEn('Publicado', 'Published') },
158
- { value: 'inactive', label: ptEn('Inativo', 'Inactive') },
159
- ];
160
-
161
- const siteTypeOptions = [
162
- { value: 'portal', label: ptEn('Portal', 'Portal') },
163
- { value: 'storefront', label: ptEn('Loja', 'Storefront') },
164
- { value: 'landing_page', label: ptEn('Landing page', 'Landing page') },
165
- ];
166
-
167
- const dataTypeOptions = [
168
- { value: 'text', label: ptEn('Texto', 'Text') },
169
- { value: 'number', label: ptEn('Número', 'Number') },
170
- { value: 'boolean', label: ptEn('Booleano', 'Boolean') },
171
- { value: 'json', label: ptEn('JSON', 'JSON') },
172
- ];
173
-
174
- const comparisonModeOptions = [
175
- { value: 'neutral', label: ptEn('Neutro', 'Neutral') },
176
- { value: 'higher_better', label: ptEn('Maior é melhor', 'Higher is better') },
177
- { value: 'lower_better', label: ptEn('Menor é melhor', 'Lower is better') },
178
- { value: 'exact_match', label: ptEn('Comparação exata', 'Exact match') },
179
- ];
180
-
181
- const facetModeOptions = [
182
- { value: 'default', label: ptEn('Padrão', 'Default') },
183
- { value: 'range', label: ptEn('Faixa', 'Range') },
184
- { value: 'select', label: ptEn('Seleção', 'Select') },
185
- ];
186
-
187
- const comparisonTypeOptions = [
188
- { value: 'manual', label: ptEn('Manual', 'Manual') },
189
- { value: 'automated', label: ptEn('Automática', 'Automated') },
190
- ];
191
-
192
- const generationModeOptions = [
193
- { value: 'manual', label: ptEn('Manual', 'Manual') },
194
- { value: 'automatic', label: ptEn('Automática', 'Automatic') },
195
- { value: 'ai_assisted', label: ptEn('Assistida por IA', 'AI assisted') },
196
- ];
197
-
198
- const availabilityOptions = [
199
- { value: 'in_stock', label: ptEn('Em estoque', 'In stock') },
200
- { value: 'out_of_stock', label: ptEn('Sem estoque', 'Out of stock') },
201
- { value: 'preorder', label: ptEn('Pré-venda', 'Pre-order') },
202
- ];
203
-
204
- const merchantTypeOptions = [
205
- { value: 'retailer', label: ptEn('Varejista', 'Retailer') },
206
- { value: 'marketplace', label: ptEn('Marketplace', 'Marketplace') },
207
- { value: 'brand_store', label: ptEn('Loja da marca', 'Brand store') },
208
- ];
209
-
210
- const networkTypeOptions = [
211
- { value: 'direct', label: ptEn('Direto', 'Direct') },
212
- { value: 'network', label: ptEn('Rede', 'Network') },
213
- { value: 'api', label: ptEn('API', 'API') },
214
- ];
215
-
216
- const commissionTypeOptions = [
217
- { value: 'percentage', label: ptEn('Percentual', 'Percentage') },
218
- { value: 'fixed', label: ptEn('Valor fixo', 'Fixed amount') },
219
- { value: 'cpa', label: ptEn('CPA', 'CPA') },
220
- { value: 'cpl', label: ptEn('CPL', 'CPL') },
221
- ];
222
-
223
- const pageTypeOptions = [
224
- { value: 'comparison', label: ptEn('Comparação', 'Comparison') },
225
- { value: 'category', label: ptEn('Categoria', 'Category') },
226
- { value: 'ranking', label: ptEn('Ranking', 'Ranking') },
227
- ];
228
-
229
- const canonicalStrategyOptions = [
230
- { value: 'self', label: ptEn('Self canonical', 'Self canonical') },
231
- { value: 'parent', label: ptEn('Canônica da categoria', 'Parent canonical') },
232
- { value: 'custom', label: ptEn('Canônica customizada', 'Custom canonical') },
233
- ];
234
-
235
- const sourceTypeOptions = [
236
- { value: 'api', label: ptEn('API', 'API') },
237
- { value: 'feed', label: ptEn('Feed', 'Feed') },
238
- { value: 'csv', label: ptEn('CSV', 'CSV') },
239
- { value: 'scraper', label: ptEn('Scraper', 'Scraper') },
240
- ];
241
-
242
- const currencyOptions = [
243
- { value: 'BRL', label: ptEn('Real brasileiro (BRL)', 'Brazilian real (BRL)') },
244
- { value: 'USD', label: ptEn('Dólar americano (USD)', 'US dollar (USD)') },
245
- { value: 'EUR', label: ptEn('Euro (EUR)', 'Euro (EUR)') },
246
- ];
247
-
248
- const relation = (
249
- endpoint: string,
250
- labelKeys: string[],
251
- options?: Partial<CatalogRelationDefinition>
252
- ): CatalogRelationDefinition => ({
253
- endpoint,
254
- labelKeys,
255
- searchParam: 'search',
256
- ...options,
257
- });
258
-
259
- const fileField = (
260
- key: string,
261
- label: CatalogLocalizedText
262
- ): CatalogFormFieldDefinition => ({
263
- key,
264
- label,
265
- type: 'upload',
266
- uploadDestination: `catalog/${key.replace(/_id$/, '')}`,
267
- accept: 'image/*',
268
- span: 2,
269
- uploadPreviewVariant: 'square',
270
- placeholder: ptEn('Envie uma imagem para representar este registro', 'Upload an image to represent this record'),
271
- });
272
-
273
- export const catalogResources: CatalogResourceDefinition[] = [
274
- {
275
- resource: 'brands',
276
- translationKey: 'brands',
277
- singularLabel: ptEn('Marca', 'Brand'),
278
- createActionLabel: ptEn('Nova Marca', 'New Brand'),
279
- editActionLabel: ptEn('Editar Marca', 'Edit Brand'),
280
- icon: Factory,
281
- href: '/catalog/brands',
282
- colorClass: 'from-orange-500/20 via-amber-500/10 to-transparent',
283
- glowClass: 'bg-orange-500/10 text-orange-700',
284
- featured: true,
285
- hasActiveStats: true,
286
- listVariant: 'cards',
287
- primaryFilterField: 'status',
288
- primaryFilterOptions: statusFilterOptions,
289
- titleFields: ['name', 'slug'],
290
- descriptionFields: ['normalized_name', 'website_url'],
291
- badgeFields: ['status'],
292
- cardMetadata: [
293
- { key: 'slug', labelKey: 'slug' },
294
- { key: 'normalized_name', labelKey: 'normalizedName' },
295
- { key: 'website_url', labelKey: 'websiteUrl' },
296
- ],
297
- contextualKpi: {
298
- translationKey: 'visible',
299
- icon: Eye,
300
- count: (records) => records.length,
301
- },
302
- template: {
303
- slug: '',
304
- name: '',
305
- normalized_name: '',
306
- logo_file_id: null,
307
- status: 'active',
308
- website_url: '',
309
- },
310
- formSections: [
311
- {
312
- title: ptEn('Identificação', 'Identity'),
313
- fields: [
314
- { key: 'name', label: ptEn('Nome da marca', 'Brand name'), type: 'text', required: true, placeholder: ptEn('Ex.: Samsung', 'Ex.: Samsung') },
315
- { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true, placeholder: ptEn('Ex.: samsung', 'Ex.: samsung') },
316
- { key: 'normalized_name', label: ptEn('Nome normalizado', 'Normalized name'), type: 'text', placeholder: ptEn('Versão limpa para buscas e matching', 'Normalized value for search and matching') },
317
- { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: statusOptions },
318
- { key: 'website_url', label: ptEn('Site oficial', 'Official website'), type: 'url', span: 2, placeholder: ptEn('https://www.sua-marca.com', 'https://www.your-brand.com') },
319
- fileField('logo_file_id', ptEn('Logo', 'Logo')),
320
- ],
321
- },
322
- ],
323
- },
324
- {
325
- resource: 'category-attributes',
326
- translationKey: 'categoryAttributes',
327
- singularLabel: ptEn('Atributo por categoria', 'Category attribute'),
328
- createActionLabel: ptEn('Novo Atributo por Categoria', 'New Category Attribute'),
329
- editActionLabel: ptEn('Editar Atributo por Categoria', 'Edit Category Attribute'),
330
- icon: BadgePercent,
331
- href: '/catalog/category-attributes',
332
- colorClass: 'from-pink-500/20 via-rose-500/10 to-transparent',
333
- glowClass: 'bg-pink-500/10 text-pink-700',
334
- listVariant: 'table',
335
- primaryFilterField: 'facet_mode',
336
- primaryFilterOptions: facetModeFilterOptions,
337
- titleFields: ['attribute_id', 'category_id'],
338
- descriptionFields: ['facet_mode'],
339
- badgeFields: ['facet_mode', 'is_required', 'is_highlighted'],
340
- cardMetadata: [
341
- { key: 'category_id', labelKey: 'categoryId' },
342
- { key: 'attribute_id', labelKey: 'attributeId' },
343
- { key: 'weight', labelKey: 'weight' },
344
- ],
345
- tableColumns: [
346
- { key: 'id', labelKey: 'id' },
347
- { key: 'category_id', labelKey: 'categoryId' },
348
- { key: 'attribute_id', labelKey: 'attributeId' },
349
- { key: 'facet_mode', labelKey: 'facetMode' },
350
- { key: 'is_required', labelKey: 'isRequired', type: 'boolean' },
351
- { key: 'is_highlighted', labelKey: 'isHighlighted', type: 'boolean' },
352
- ],
353
- contextualKpi: {
354
- translationKey: 'requiredInSlice',
355
- icon: BadgePercent,
356
- count: (records) => records.filter((record) => record.is_required === true).length,
357
- },
358
- template: {
359
- category_id: null,
360
- attribute_id: null,
361
- is_required: false,
362
- is_highlighted: true,
363
- display_order: 0,
364
- weight: 1,
365
- facet_mode: 'default',
366
- },
367
- formSections: [
368
- {
369
- title: ptEn('Relacionamentos', 'Relationships'),
370
- fields: [
371
- {
372
- key: 'category_id',
373
- label: ptEn('Categoria', 'Category'),
374
- type: 'relation',
375
- required: true,
376
- relation: relation('/category', ['name', 'slug'], { valueKey: 'category_id' }),
377
- },
378
- {
379
- key: 'attribute_id',
380
- label: ptEn('Atributo', 'Attribute'),
381
- type: 'relation',
382
- required: true,
383
- relation: relation('/catalog/attributes', ['label', 'slug'], {
384
- resource: 'attributes',
385
- createResource: 'attributes',
386
- allowCreate: true,
387
- }),
388
- },
389
- { key: 'facet_mode', label: ptEn('Modo de faceta', 'Facet mode'), type: 'select', required: true, options: facetModeOptions },
390
- { key: 'display_order', label: ptEn('Ordem de exibição', 'Display order'), type: 'number' },
391
- { key: 'weight', label: ptEn('Peso', 'Weight'), type: 'number' },
392
- { key: 'is_required', label: ptEn('Obrigatório', 'Required'), type: 'switch' },
393
- { key: 'is_highlighted', label: ptEn('Destacado', 'Highlighted'), type: 'switch' },
394
- ],
395
- },
396
- ],
397
- },
398
- {
399
- resource: 'comparisons',
400
- translationKey: 'comparisons',
401
- singularLabel: ptEn('Comparação', 'Comparison'),
402
- createActionLabel: ptEn('Nova Comparação', 'New Comparison'),
403
- editActionLabel: ptEn('Editar Comparação', 'Edit Comparison'),
404
- icon: Scale,
405
- href: '/catalog/comparisons',
406
- colorClass: 'from-amber-500/20 via-yellow-500/10 to-transparent',
407
- glowClass: 'bg-amber-500/10 text-amber-700',
408
- hasActiveStats: true,
409
- listVariant: 'cards',
410
- primaryFilterField: 'status',
411
- primaryFilterOptions: statusFilterOptions,
412
- titleFields: ['title', 'slug'],
413
- descriptionFields: ['summary', 'slug'],
414
- badgeFields: ['status', 'comparison_type', 'generation_mode'],
415
- cardMetadata: [
416
- { key: 'slug', labelKey: 'slug' },
417
- { key: 'site_id', labelKey: 'siteId' },
418
- { key: 'category_id', labelKey: 'categoryId' },
419
- ],
420
- contextualKpi: {
421
- translationKey: 'publishedInSlice',
422
- icon: Scale,
423
- count: (records) =>
424
- records.filter((record) =>
425
- ['active', 'published'].includes(String(record.status ?? ''))
426
- ).length,
427
- },
428
- template: {
429
- category_id: null,
430
- site_id: null,
431
- primary_content_id: null,
432
- slug: '',
433
- comparison_type: 'manual',
434
- generation_mode: 'manual',
435
- status: 'draft',
436
- title: '',
437
- summary: '',
438
- intro: '',
439
- verdict: '',
440
- spec_snapshot_json: {},
441
- eligibility_hash: '',
442
- published_at: '',
443
- },
444
- formSections: [
445
- {
446
- title: ptEn('Base da comparação', 'Comparison basics'),
447
- fields: [
448
- { key: 'title', label: ptEn('Título', 'Title'), type: 'text', required: true, placeholder: ptEn('Ex.: Melhor celular premium de 2026', 'Ex.: Best premium smartphone of 2026') },
449
- { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true, placeholder: ptEn('Ex.: melhor-celular-premium-2026', 'Ex.: best-premium-smartphone-2026') },
450
- {
451
- key: 'site_id',
452
- label: ptEn('Site', 'Site'),
453
- type: 'relation',
454
- required: true,
455
- relation: relation('/catalog/sites', ['name', 'domain', 'slug'], {
456
- resource: 'sites',
457
- createResource: 'sites',
458
- allowCreate: true,
459
- }),
460
- },
461
- { key: 'category_id', label: ptEn('Categoria', 'Category'), type: 'relation', relation: relation('/category', ['name', 'slug'], { valueKey: 'category_id' }) },
462
- { key: 'primary_content_id', label: ptEn('Conteúdo principal', 'Primary content'), type: 'relation', relation: relation('/content', ['title', 'slug'], { valueKey: 'content_id' }) },
463
- { key: 'comparison_type', label: ptEn('Tipo de comparação', 'Comparison type'), type: 'select', required: true, options: comparisonTypeOptions },
464
- { key: 'generation_mode', label: ptEn('Modo de geração', 'Generation mode'), type: 'select', required: true, options: generationModeOptions },
465
- { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: statusOptions },
466
- { key: 'published_at', label: ptEn('Publicado em', 'Published at'), type: 'datetime' },
467
- ],
468
- },
469
- {
470
- title: ptEn('Conteúdo editorial', 'Editorial content'),
471
- fields: [
472
- { key: 'summary', label: ptEn('Resumo', 'Summary'), type: 'richtext', span: 2, placeholder: ptEn('Resumo editorial da comparação', 'Editorial summary of the comparison') },
473
- { key: 'intro', label: ptEn('Introdução', 'Introduction'), type: 'richtext', span: 2, placeholder: ptEn('Contextualize o comparativo para o leitor', 'Add reader-facing context for the comparison') },
474
- { key: 'verdict', label: ptEn('Veredito', 'Verdict'), type: 'richtext', span: 2, placeholder: ptEn('Conclusão e recomendação final', 'Final conclusion and recommendation') },
475
- { key: 'eligibility_hash', label: ptEn('Hash de elegibilidade', 'Eligibility hash'), type: 'text', span: 2, placeholder: ptEn('Chave técnica para rastrear o conjunto elegível', 'Technical key to track the eligible set') },
476
- { key: 'spec_snapshot_json', label: ptEn('Snapshot de regras', 'Rule snapshot'), type: 'json', span: 2 },
477
- ],
478
- },
479
- ],
480
- },
481
- {
482
- resource: 'offers',
483
- translationKey: 'offers',
484
- singularLabel: ptEn('Oferta', 'Offer'),
485
- createActionLabel: ptEn('Nova Oferta', 'New Offer'),
486
- editActionLabel: ptEn('Editar Oferta', 'Edit Offer'),
487
- icon: ShoppingCart,
488
- href: '/catalog/offers',
489
- colorClass: 'from-red-500/20 via-orange-500/10 to-transparent',
490
- glowClass: 'bg-red-500/10 text-red-700',
491
- featured: true,
492
- listVariant: 'cards',
493
- primaryFilterField: 'availability_status',
494
- primaryFilterOptions: availabilityFilterOptions,
495
- titleFields: ['title', 'external_offer_id'],
496
- descriptionFields: ['external_offer_id', 'affiliate_url'],
497
- badgeFields: ['availability_status', 'is_featured'],
498
- cardMetadata: [
499
- { key: 'price_amount', labelKey: 'priceAmount', type: 'currency' },
500
- { key: 'merchant_id', labelKey: 'merchantId' },
501
- { key: 'product_id', labelKey: 'productId' },
502
- ],
503
- contextualKpi: {
504
- translationKey: 'inStockInSlice',
505
- icon: ShoppingCart,
506
- count: (records) => records.filter((record) => record.availability_status === 'in_stock').length,
507
- },
508
- template: {
509
- product_id: null,
510
- merchant_id: null,
511
- affiliate_program_id: null,
512
- site_id: null,
513
- external_offer_id: '',
514
- title: '',
515
- price_amount: 0,
516
- price_currency: 'BRL',
517
- original_price_amount: 0,
518
- installment_json: {},
519
- availability_status: 'in_stock',
520
- affiliate_url: '',
521
- deep_link_url: '',
522
- priority_score: 0,
523
- is_featured: false,
524
- valid_from: '',
525
- valid_until: '',
526
- last_seen_at: '',
527
- },
528
- formSections: [
529
- {
530
- title: ptEn('Oferta comercial', 'Commercial offer'),
531
- fields: [
532
- { key: 'title', label: ptEn('Título da oferta', 'Offer title'), type: 'text', required: true, span: 2, placeholder: ptEn('Ex.: Galaxy S25 Ultra 256GB por R$ 6.999', 'Ex.: Galaxy S25 Ultra 256GB for $1,299') },
533
- {
534
- key: 'product_id',
535
- label: ptEn('Produto', 'Product'),
536
- type: 'relation',
537
- required: true,
538
- relation: relation('/catalog/products', ['name', 'model_name', 'slug'], {
539
- resource: 'products',
540
- createResource: 'products',
541
- allowCreate: true,
542
- }),
543
- },
544
- {
545
- key: 'merchant_id',
546
- label: ptEn('Lojista', 'Merchant'),
547
- type: 'relation',
548
- required: true,
549
- relation: relation('/catalog/merchants', ['name', 'slug'], {
550
- resource: 'merchants',
551
- createResource: 'merchants',
552
- allowCreate: true,
553
- }),
554
- },
555
- {
556
- key: 'affiliate_program_id',
557
- label: ptEn('Programa de afiliação', 'Affiliate program'),
558
- type: 'relation',
559
- relation: relation('/catalog/affiliate-programs', ['name', 'slug'], {
560
- resource: 'affiliate-programs',
561
- createResource: 'affiliate-programs',
562
- allowCreate: true,
563
- }),
564
- },
565
- {
566
- key: 'site_id',
567
- label: ptEn('Site', 'Site'),
568
- type: 'relation',
569
- relation: relation('/catalog/sites', ['name', 'domain', 'slug'], {
570
- resource: 'sites',
571
- createResource: 'sites',
572
- allowCreate: true,
573
- }),
574
- },
575
- { key: 'external_offer_id', label: ptEn('ID externo', 'External ID'), type: 'text', placeholder: ptEn('ID da oferta no feed/API do parceiro', 'Offer id from partner feed/API') },
576
- { key: 'availability_status', label: ptEn('Disponibilidade', 'Availability'), type: 'select', required: true, options: availabilityOptions },
577
- { key: 'price_amount', label: ptEn('Preço', 'Price'), type: 'currency', required: true },
578
- { key: 'original_price_amount', label: ptEn('Preço original', 'Original price'), type: 'currency' },
579
- { key: 'price_currency', label: ptEn('Moeda', 'Currency'), type: 'select', required: true, options: currencyOptions },
580
- { key: 'priority_score', label: ptEn('Prioridade', 'Priority'), type: 'number' },
581
- { key: 'is_featured', label: ptEn('Oferta destacada', 'Featured offer'), type: 'switch' },
582
- ],
583
- },
584
- {
585
- title: ptEn('Links e vigência', 'Links and schedule'),
586
- fields: [
587
- { key: 'affiliate_url', label: ptEn('URL de afiliação', 'Affiliate URL'), type: 'url', span: 2, placeholder: ptEn('https://parceiro.com/r/abc123', 'https://partner.com/r/abc123') },
588
- { key: 'deep_link_url', label: ptEn('Deep link', 'Deep link'), type: 'url', span: 2, placeholder: ptEn('https://loja.com/produto/oferta', 'https://store.com/product/offer') },
589
- { key: 'valid_from', label: ptEn('Válida a partir de', 'Valid from'), type: 'datetime' },
590
- { key: 'valid_until', label: ptEn('Válida até', 'Valid until'), type: 'datetime' },
591
- { key: 'last_seen_at', label: ptEn('Última captura', 'Last seen at'), type: 'datetime' },
592
- { key: 'installment_json', label: ptEn('Parcelamento', 'Installments'), type: 'json', span: 2 },
593
- ],
594
- },
595
- ],
596
- },
597
- {
598
- resource: 'sites',
599
- translationKey: 'sites',
600
- singularLabel: ptEn('Site', 'Site'),
601
- createActionLabel: ptEn('Novo Site', 'New Site'),
602
- editActionLabel: ptEn('Editar Site', 'Edit Site'),
603
- icon: Globe2,
604
- href: '/catalog/sites',
605
- colorClass: 'from-sky-500/20 via-cyan-500/10 to-transparent',
606
- glowClass: 'bg-sky-500/10 text-sky-700',
607
- hasActiveStats: true,
608
- listVariant: 'cards',
609
- primaryFilterField: 'status',
610
- primaryFilterOptions: statusFilterOptions,
611
- titleFields: ['name', 'domain', 'slug'],
612
- descriptionFields: ['domain', 'slug'],
613
- badgeFields: ['status', 'site_type'],
614
- cardMetadata: [
615
- { key: 'slug', labelKey: 'slug' },
616
- { key: 'domain', labelKey: 'domain' },
617
- { key: 'default_locale_id', labelKey: 'defaultLocaleId' },
618
- ],
619
- contextualKpi: {
620
- translationKey: 'visible',
621
- icon: Eye,
622
- count: (records) => records.length,
623
- },
624
- template: {
625
- slug: '',
626
- name: '',
627
- domain: '',
628
- status: 'active',
629
- site_type: 'portal',
630
- default_locale_id: null,
631
- theme_settings_json: {},
632
- seo_defaults_json: {},
633
- },
634
- formSections: [
635
- {
636
- title: ptEn('Dados principais', 'Main details'),
637
- fields: [
638
- { key: 'name', label: ptEn('Nome do site', 'Site name'), type: 'text', required: true, placeholder: ptEn('Ex.: Tech Radar BR', 'Ex.: Tech Radar BR') },
639
- { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true, placeholder: ptEn('Ex.: tech-radar-br', 'Ex.: tech-radar-br') },
640
- { key: 'domain', label: ptEn('Domínio', 'Domain'), type: 'url', required: true, placeholder: ptEn('https://www.exemplo.com', 'https://www.example.com') },
641
- { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: statusOptions },
642
- { key: 'site_type', label: ptEn('Tipo de site', 'Site type'), type: 'select', required: true, options: siteTypeOptions },
643
- {
644
- key: 'default_locale_id',
645
- label: ptEn('Locale padrão', 'Default locale'),
646
- type: 'relation',
647
- relation: relation('/locale', ['name', 'code'], { valueKey: 'id' }),
648
- },
649
- ],
650
- },
651
- {
652
- title: ptEn('Configurações', 'Settings'),
653
- fields: [
654
- { key: 'theme_settings_json', label: ptEn('Tema e identidade', 'Theme and identity'), type: 'json', span: 2 },
655
- { key: 'seo_defaults_json', label: ptEn('Padrões de SEO', 'SEO defaults'), type: 'json', span: 2 },
656
- ],
657
- },
658
- ],
659
- },
660
- {
661
- resource: 'products',
662
- translationKey: 'products',
663
- singularLabel: ptEn('Produto', 'Product'),
664
- createActionLabel: ptEn('Novo Produto', 'New Product'),
665
- editActionLabel: ptEn('Editar Produto', 'Edit Product'),
666
- icon: PackageSearch,
667
- href: '/catalog/products',
668
- colorClass: 'from-emerald-500/20 via-green-500/10 to-transparent',
669
- glowClass: 'bg-emerald-500/10 text-emerald-700',
670
- featured: true,
671
- hasActiveStats: true,
672
- listVariant: 'cards',
673
- primaryFilterField: 'status',
674
- primaryFilterOptions: statusFilterOptions,
675
- titleFields: ['name', 'model_name', 'slug'],
676
- descriptionFields: ['model_name', 'sku', 'slug'],
677
- badgeFields: ['status', 'comparison_status', 'is_active'],
678
- cardMetadata: [
679
- { key: 'slug', labelKey: 'slug' },
680
- { key: 'sku', labelKey: 'sku' },
681
- { key: 'brand_id', labelKey: 'brandId' },
682
- { key: 'category_id', labelKey: 'categoryId' },
683
- ],
684
- contextualKpi: {
685
- translationKey: 'activeInSlice',
686
- icon: PackageSearch,
687
- count: (records) => records.filter((record) => record.is_active === true).length,
688
- },
689
- template: {
690
- brand_id: null,
691
- category_id: null,
692
- primary_content_id: null,
693
- slug: '',
694
- name: '',
695
- short_description: '',
696
- description: '',
697
- model_name: '',
698
- sku: '',
699
- gtin: '',
700
- status: 'draft',
701
- comparison_status: 'draft',
702
- release_date: '',
703
- spec_snapshot_json: {},
704
- comparison_snapshot_json: {},
705
- is_active: true,
706
- },
707
- formSections: [
708
- {
709
- title: ptEn('Base do produto', 'Product basics'),
710
- fields: [
711
- { key: 'name', label: ptEn('Nome do produto', 'Product name'), type: 'text', required: true, placeholder: ptEn('Ex.: Galaxy S25 Ultra', 'Ex.: Galaxy S25 Ultra') },
712
- { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true, placeholder: ptEn('Ex.: galaxy-s25-ultra', 'Ex.: galaxy-s25-ultra') },
713
- {
714
- key: 'brand_id',
715
- label: ptEn('Marca', 'Brand'),
716
- type: 'relation',
717
- required: true,
718
- relation: relation('/catalog/brands', ['name', 'slug'], {
719
- resource: 'brands',
720
- createResource: 'brands',
721
- allowCreate: true,
722
- }),
723
- },
724
- {
725
- key: 'category_id',
726
- label: ptEn('Categoria', 'Category'),
727
- type: 'relation',
728
- required: true,
729
- relation: relation('/category', ['name', 'slug'], {
730
- valueKey: 'category_id',
731
- }),
732
- },
733
- {
734
- key: 'primary_content_id',
735
- label: ptEn('Conteúdo principal', 'Primary content'),
736
- type: 'relation',
737
- relation: relation('/content', ['title', 'slug'], {
738
- valueKey: 'content_id',
739
- }),
740
- },
741
- { key: 'model_name', label: ptEn('Modelo', 'Model'), type: 'text', placeholder: ptEn('Nome técnico ou comercial do modelo', 'Technical or commercial model name') },
742
- { key: 'sku', label: ptEn('SKU', 'SKU'), type: 'text', placeholder: ptEn('Código interno de estoque', 'Internal stock code') },
743
- { key: 'gtin', label: ptEn('GTIN', 'GTIN'), type: 'text', placeholder: ptEn('Código de barras/GTIN', 'Barcode / GTIN') },
744
- { key: 'release_date', label: ptEn('Data de lançamento', 'Release date'), type: 'date' },
745
- { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: statusOptions },
746
- { key: 'comparison_status', label: ptEn('Status de comparação', 'Comparison status'), type: 'select', required: true, options: comparisonStatusOptions },
747
- { key: 'is_active', label: ptEn('Ativo para comparação', 'Active for comparison'), type: 'switch' },
748
- ],
749
- },
750
- {
751
- title: ptEn('Conteúdo', 'Content'),
752
- fields: [
753
- { key: 'short_description', label: ptEn('Resumo curto', 'Short description'), type: 'richtext', span: 2, placeholder: ptEn('Resumo rápido para cards e snippets', 'Short summary for cards and snippets') },
754
- { key: 'description', label: ptEn('Descrição completa', 'Full description'), type: 'richtext', span: 2, placeholder: ptEn('Detalhes completos, diferenciais e contexto do produto', 'Full product details, highlights and context') },
755
- { key: 'spec_snapshot_json', label: ptEn('Snapshot de especificações', 'Specification snapshot'), type: 'json', span: 2 },
756
- { key: 'comparison_snapshot_json', label: ptEn('Snapshot de comparação', 'Comparison snapshot'), type: 'json', span: 2 },
757
- ],
758
- },
759
- ],
760
- },
761
- {
762
- resource: 'attribute-groups',
763
- translationKey: 'attributeGroups',
764
- singularLabel: ptEn('Grupo de atributos', 'Attribute group'),
765
- createActionLabel: ptEn('Novo Grupo de Atributos', 'New Attribute Group'),
766
- editActionLabel: ptEn('Editar Grupo de Atributos', 'Edit Attribute Group'),
767
- icon: Layers3,
768
- href: '/catalog/attribute-groups',
769
- colorClass: 'from-violet-500/20 via-fuchsia-500/10 to-transparent',
770
- glowClass: 'bg-violet-500/10 text-violet-700',
771
- hasActiveStats: true,
772
- listVariant: 'table',
773
- primaryFilterField: 'status',
774
- primaryFilterOptions: statusFilterOptions,
775
- titleFields: ['name', 'slug'],
776
- descriptionFields: ['slug'],
777
- badgeFields: ['status'],
778
- cardMetadata: [
779
- { key: 'slug', labelKey: 'slug' },
780
- { key: 'status', labelKey: 'status' },
781
- ],
782
- tableColumns: [
783
- { key: 'id', labelKey: 'id' },
784
- { key: 'name', labelKey: 'name' },
785
- { key: 'slug', labelKey: 'slug' },
786
- { key: 'status', labelKey: 'status' },
787
- ],
788
- contextualKpi: {
789
- translationKey: 'visible',
790
- icon: Eye,
791
- count: (records) => records.length,
792
- },
793
- template: { slug: '', name: '', status: 'active' },
794
- formSections: [
795
- {
796
- title: ptEn('Grupo', 'Group'),
797
- fields: [
798
- { key: 'name', label: ptEn('Nome do grupo', 'Group name'), type: 'text', required: true, placeholder: ptEn('Ex.: Tela e Display', 'Ex.: Display and Screen') },
799
- { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true, placeholder: ptEn('Ex.: tela-e-display', 'Ex.: display-and-screen') },
800
- { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: statusOptions },
801
- ],
802
- },
803
- ],
804
- },
805
- {
806
- resource: 'attributes',
807
- translationKey: 'attributes',
808
- singularLabel: ptEn('Atributo', 'Attribute'),
809
- createActionLabel: ptEn('Novo Atributo', 'New Attribute'),
810
- editActionLabel: ptEn('Editar Atributo', 'Edit Attribute'),
811
- icon: Tags,
812
- href: '/catalog/attributes',
813
- colorClass: 'from-indigo-500/20 via-blue-500/10 to-transparent',
814
- glowClass: 'bg-indigo-500/10 text-indigo-700',
815
- listVariant: 'table',
816
- primaryFilterField: 'data_type',
817
- primaryFilterOptions: dataTypeFilterOptions,
818
- titleFields: ['label', 'slug'],
819
- descriptionFields: ['description', 'slug'],
820
- badgeFields: ['data_type', 'comparison_mode'],
821
- cardMetadata: [
822
- { key: 'group_id', labelKey: 'groupId' },
823
- { key: 'is_filterable', labelKey: 'isFilterable', type: 'boolean' },
824
- { key: 'is_required_for_comparison', labelKey: 'requiredForComparison', type: 'boolean' },
825
- ],
826
- tableColumns: [
827
- { key: 'id', labelKey: 'id' },
828
- { key: 'label', labelKey: 'label' },
829
- { key: 'slug', labelKey: 'slug' },
830
- { key: 'data_type', labelKey: 'dataType' },
831
- { key: 'comparison_mode', labelKey: 'comparisonMode' },
832
- ],
833
- contextualKpi: {
834
- translationKey: 'filterableInSlice',
835
- icon: ListFilter,
836
- count: (records) => records.filter((record) => record.is_filterable === true).length,
837
- },
838
- template: {
839
- group_id: null,
840
- slug: '',
841
- label: '',
842
- description: '',
843
- data_type: 'text',
844
- unit: '',
845
- comparison_mode: 'neutral',
846
- is_filterable: true,
847
- is_sortable: false,
848
- is_required_for_comparison: false,
849
- normalization_rule_json: {},
850
- display_order: 0,
851
- },
852
- formSections: [
853
- {
854
- title: ptEn('Configuração do atributo', 'Attribute setup'),
855
- fields: [
856
- { key: 'label', label: ptEn('Label', 'Label'), type: 'text', required: true, placeholder: ptEn('Ex.: Tamanho da tela', 'Ex.: Screen size') },
857
- { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true, placeholder: ptEn('Ex.: tamanho-da-tela', 'Ex.: screen-size') },
858
- {
859
- key: 'group_id',
860
- label: ptEn('Grupo', 'Group'),
861
- type: 'relation',
862
- required: true,
863
- relation: relation('/catalog/attribute-groups', ['name', 'slug'], {
864
- resource: 'attribute-groups',
865
- createResource: 'attribute-groups',
866
- allowCreate: true,
867
- }),
868
- },
869
- { key: 'data_type', label: ptEn('Tipo de dado', 'Data type'), type: 'select', required: true, options: dataTypeOptions },
870
- { key: 'unit', label: ptEn('Unidade', 'Unit'), type: 'text', placeholder: ptEn('Ex.: polegadas, GB, kg', 'Ex.: inches, GB, kg') },
871
- { key: 'comparison_mode', label: ptEn('Modo de comparação', 'Comparison mode'), type: 'select', required: true, options: comparisonModeOptions },
872
- { key: 'display_order', label: ptEn('Ordem de exibição', 'Display order'), type: 'number' },
873
- { key: 'description', label: ptEn('Descrição', 'Description'), type: 'richtext', span: 2, placeholder: ptEn('Explique como o atributo deve ser usado e exibido', 'Explain how the attribute should be used and displayed') },
874
- ],
875
- },
876
- {
877
- title: ptEn('Regras', 'Rules'),
878
- fields: [
879
- { key: 'is_filterable', label: ptEn('Filtrável', 'Filterable'), type: 'switch' },
880
- { key: 'is_sortable', label: ptEn('Ordenável', 'Sortable'), type: 'switch' },
881
- { key: 'is_required_for_comparison', label: ptEn('Obrigatório para comparação', 'Required for comparison'), type: 'switch' },
882
- { key: 'normalization_rule_json', label: ptEn('Regra de normalização', 'Normalization rule'), type: 'json', span: 2 },
883
- ],
884
- },
885
- ],
886
- },
887
- {
888
- resource: 'merchants',
889
- translationKey: 'merchants',
890
- singularLabel: ptEn('Lojista', 'Merchant'),
891
- createActionLabel: ptEn('Novo Lojista', 'New Merchant'),
892
- editActionLabel: ptEn('Editar Lojista', 'Edit Merchant'),
893
- icon: Store,
894
- href: '/catalog/merchants',
895
- colorClass: 'from-lime-500/20 via-green-500/10 to-transparent',
896
- glowClass: 'bg-lime-500/10 text-lime-700',
897
- featured: true,
898
- hasActiveStats: true,
899
- listVariant: 'cards',
900
- primaryFilterField: 'status',
901
- primaryFilterOptions: statusFilterOptions,
902
- titleFields: ['name', 'slug'],
903
- descriptionFields: ['slug'],
904
- badgeFields: ['status', 'merchant_type'],
905
- cardMetadata: [
906
- { key: 'slug', labelKey: 'slug' },
907
- { key: 'merchant_type', labelKey: 'merchantType' },
908
- { key: 'logo_file_id', labelKey: 'logoFileId' },
909
- ],
910
- contextualKpi: {
911
- translationKey: 'visible',
912
- icon: Eye,
913
- count: (records) => records.length,
914
- },
915
- template: {
916
- slug: '',
917
- name: '',
918
- status: 'active',
919
- merchant_type: 'retailer',
920
- logo_file_id: null,
921
- },
922
- formSections: [
923
- {
924
- title: ptEn('Perfil do lojista', 'Merchant profile'),
925
- fields: [
926
- { key: 'name', label: ptEn('Nome do lojista', 'Merchant name'), type: 'text', required: true, placeholder: ptEn('Ex.: Magazine Luiza', 'Ex.: Best Buy') },
927
- { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true, placeholder: ptEn('Ex.: magazine-luiza', 'Ex.: best-buy') },
928
- { key: 'merchant_type', label: ptEn('Tipo de lojista', 'Merchant type'), type: 'select', required: true, options: merchantTypeOptions },
929
- { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: statusOptions },
930
- fileField('logo_file_id', ptEn('Logo', 'Logo')),
931
- ],
932
- },
933
- ],
934
- },
935
- {
936
- resource: 'affiliate-programs',
937
- translationKey: 'affiliatePrograms',
938
- singularLabel: ptEn('Programa de afiliação', 'Affiliate program'),
939
- createActionLabel: ptEn('Novo Programa de Afiliação', 'New Affiliate Program'),
940
- editActionLabel: ptEn('Editar Programa de Afiliação', 'Edit Affiliate Program'),
941
- icon: Building2,
942
- href: '/catalog/affiliate-programs',
943
- colorClass: 'from-cyan-500/20 via-teal-500/10 to-transparent',
944
- glowClass: 'bg-cyan-500/10 text-cyan-700',
945
- hasActiveStats: true,
946
- listVariant: 'cards',
947
- primaryFilterField: 'status',
948
- primaryFilterOptions: statusFilterOptions,
949
- titleFields: ['name', 'slug'],
950
- descriptionFields: ['slug', 'network_type'],
951
- badgeFields: ['status', 'network_type', 'commission_type'],
952
- cardMetadata: [
953
- { key: 'merchant_id', labelKey: 'merchantId' },
954
- { key: 'commission_type', labelKey: 'commissionType' },
955
- { key: 'default_commission_value', labelKey: 'defaultCommissionValue', type: 'currency' },
956
- ],
957
- contextualKpi: {
958
- translationKey: 'visible',
959
- icon: Eye,
960
- count: (records) => records.length,
961
- },
962
- template: {
963
- merchant_id: null,
964
- slug: '',
965
- name: '',
966
- network_type: 'direct',
967
- tracking_template: '',
968
- commission_type: 'percentage',
969
- default_commission_value: 0,
970
- status: 'active',
971
- },
972
- formSections: [
973
- {
974
- title: ptEn('Programa', 'Program'),
975
- fields: [
976
- { key: 'name', label: ptEn('Nome do programa', 'Program name'), type: 'text', required: true, placeholder: ptEn('Ex.: Programa Magalu Ads', 'Ex.: Best Buy Affiliate Program') },
977
- { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true, placeholder: ptEn('Ex.: magalu-ads', 'Ex.: best-buy-affiliate') },
978
- {
979
- key: 'merchant_id',
980
- label: ptEn('Lojista', 'Merchant'),
981
- type: 'relation',
982
- required: true,
983
- relation: relation('/catalog/merchants', ['name', 'slug'], {
984
- resource: 'merchants',
985
- createResource: 'merchants',
986
- allowCreate: true,
987
- }),
988
- },
989
- { key: 'network_type', label: ptEn('Rede', 'Network'), type: 'select', required: true, options: networkTypeOptions },
990
- { key: 'commission_type', label: ptEn('Tipo de comissão', 'Commission type'), type: 'select', required: true, options: commissionTypeOptions },
991
- { key: 'default_commission_value', label: ptEn('Comissão padrão', 'Default commission'), type: 'currency' },
992
- { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: statusOptions },
993
- { key: 'tracking_template', label: ptEn('Template de tracking', 'Tracking template'), type: 'textarea', span: 2, placeholder: ptEn('Template com macros e parâmetros de tracking', 'Template with tracking macros and parameters') },
994
- ],
995
- },
996
- ],
997
- },
998
- {
999
- resource: 'seo-rules',
1000
- translationKey: 'seoRules',
1001
- singularLabel: ptEn('Regra de SEO', 'SEO rule'),
1002
- createActionLabel: ptEn('Nova Regra de SEO', 'New SEO Rule'),
1003
- editActionLabel: ptEn('Editar Regra de SEO', 'Edit SEO Rule'),
1004
- icon: SearchCode,
1005
- href: '/catalog/seo-rules',
1006
- colorClass: 'from-slate-500/20 via-zinc-500/10 to-transparent',
1007
- glowClass: 'bg-slate-500/10 text-slate-700',
1008
- featured: true,
1009
- hasActiveStats: true,
1010
- listVariant: 'cards',
1011
- primaryFilterField: 'status',
1012
- primaryFilterOptions: statusFilterOptions,
1013
- titleFields: ['rule_slug', 'page_type'],
1014
- descriptionFields: ['page_type', 'canonical_strategy'],
1015
- badgeFields: ['status', 'page_type', 'canonical_strategy'],
1016
- cardMetadata: [
1017
- { key: 'site_id', labelKey: 'siteId' },
1018
- { key: 'category_id', labelKey: 'categoryId' },
1019
- { key: 'priority', labelKey: 'priority' },
1020
- ],
1021
- contextualKpi: {
1022
- translationKey: 'visible',
1023
- icon: Eye,
1024
- count: (records) => records.length,
1025
- },
1026
- template: {
1027
- site_id: null,
1028
- category_id: null,
1029
- page_type: 'comparison',
1030
- rule_slug: '',
1031
- status: 'active',
1032
- generation_query_json: {},
1033
- min_product_count: 2,
1034
- min_attribute_coverage: 1,
1035
- canonical_strategy: 'self',
1036
- priority: 0,
1037
- template_json: {},
1038
- },
1039
- formSections: [
1040
- {
1041
- title: ptEn('Escopo da regra', 'Rule scope'),
1042
- fields: [
1043
- {
1044
- key: 'site_id',
1045
- label: ptEn('Site', 'Site'),
1046
- type: 'relation',
1047
- required: true,
1048
- relation: relation('/catalog/sites', ['name', 'domain', 'slug'], {
1049
- resource: 'sites',
1050
- createResource: 'sites',
1051
- allowCreate: true,
1052
- }),
1053
- },
1054
- { key: 'category_id', label: ptEn('Categoria', 'Category'), type: 'relation', relation: relation('/category', ['name', 'slug'], { valueKey: 'category_id' }) },
1055
- { key: 'page_type', label: ptEn('Tipo de página', 'Page type'), type: 'select', required: true, options: pageTypeOptions },
1056
- { key: 'rule_slug', label: ptEn('Slug da regra', 'Rule slug'), type: 'text', required: true, placeholder: ptEn('Ex.: comparativo-celulares-premium', 'Ex.: premium-smartphone-comparison') },
1057
- { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: statusOptions },
1058
- { key: 'canonical_strategy', label: ptEn('Estratégia canônica', 'Canonical strategy'), type: 'select', required: true, options: canonicalStrategyOptions },
1059
- { key: 'priority', label: ptEn('Prioridade', 'Priority'), type: 'number' },
1060
- { key: 'min_product_count', label: ptEn('Mínimo de produtos', 'Minimum products'), type: 'number' },
1061
- { key: 'min_attribute_coverage', label: ptEn('Cobertura mínima de atributos', 'Minimum attribute coverage'), type: 'number' },
1062
- ],
1063
- },
1064
- {
1065
- title: ptEn('Regras dinâmicas', 'Dynamic rules'),
1066
- fields: [
1067
- { key: 'generation_query_json', label: ptEn('Consulta geradora', 'Generation query'), type: 'json', span: 2 },
1068
- { key: 'template_json', label: ptEn('Template de SEO', 'SEO template'), type: 'json', span: 2 },
1069
- ],
1070
- },
1071
- ],
1072
- },
1073
- {
1074
- resource: 'import-sources',
1075
- translationKey: 'importSources',
1076
- singularLabel: ptEn('Fonte de importação', 'Import source'),
1077
- createActionLabel: ptEn('Nova Fonte de Importação', 'New Import Source'),
1078
- editActionLabel: ptEn('Editar Fonte de Importação', 'Edit Import Source'),
1079
- icon: PackagePlus,
1080
- href: '/catalog/import-sources',
1081
- colorClass: 'from-stone-500/20 via-neutral-500/10 to-transparent',
1082
- glowClass: 'bg-stone-500/10 text-stone-700',
1083
- featured: true,
1084
- hasActiveStats: true,
1085
- listVariant: 'cards',
1086
- primaryFilterField: 'status',
1087
- primaryFilterOptions: statusFilterOptions,
1088
- titleFields: ['name', 'slug'],
1089
- descriptionFields: ['slug', 'source_type'],
1090
- badgeFields: ['status', 'source_type'],
1091
- cardMetadata: [
1092
- { key: 'slug', labelKey: 'slug' },
1093
- { key: 'source_type', labelKey: 'sourceType' },
1094
- { key: 'status', labelKey: 'status' },
1095
- ],
1096
- contextualKpi: {
1097
- translationKey: 'visible',
1098
- icon: Eye,
1099
- count: (records) => records.length,
1100
- },
1101
- template: {
1102
- slug: '',
1103
- name: '',
1104
- source_type: 'api',
1105
- config_json: {},
1106
- status: 'active',
1107
- },
1108
- formSections: [
1109
- {
1110
- title: ptEn('Origem dos dados', 'Data source'),
1111
- fields: [
1112
- { key: 'name', label: ptEn('Nome da fonte', 'Source name'), type: 'text', required: true, placeholder: ptEn('Ex.: API principal do parceiro', 'Ex.: Primary partner API') },
1113
- { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true, placeholder: ptEn('Ex.: api-parceiro-principal', 'Ex.: primary-partner-api') },
1114
- { key: 'source_type', label: ptEn('Tipo de fonte', 'Source type'), type: 'select', required: true, options: sourceTypeOptions },
1115
- { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: statusOptions },
1116
- { key: 'config_json', label: ptEn('Configuração', 'Configuration'), type: 'json', span: 2 },
1117
- ],
1118
- },
1119
- ],
1120
- },
1121
- ];
1122
-
1123
- export const catalogResourceMap = new Map(
1124
- catalogResources.map((resource) => [resource.resource, resource])
1125
- );
1126
-
1127
- export const catalogDashboardHref = '/catalog/dashboard';
1128
-
1129
- export const catalogQuickActionResources = catalogResources.filter(
1130
- (resource) => resource.featured
1131
- );
1132
-
1133
- export const catalogKpiResources = ['products', 'brands', 'offers', 'merchants'];
1134
-
1135
- export function getCatalogRecordLabel(record: Record<string, unknown>) {
1136
- return (
1137
- record.name ||
1138
- record.title ||
1139
- record.label ||
1140
- record.slug ||
1141
- record.rule_slug ||
1142
- record.external_offer_id ||
1143
- `#${record.id}`
1144
- );
1145
- }
1146
-
1147
- export function getCatalogLocalizedText(
1148
- value: CatalogLocalizedText,
1149
- localeCode?: string | null
1150
- ) {
1151
- return localeCode?.toLowerCase().startsWith('pt') ? value.pt : value.en;
1152
- }
1153
-
1154
- export const catalogModuleIcon = Boxes;
1
+ import type { LucideIcon } from 'lucide-react';
2
+ import {
3
+ BadgePercent,
4
+ Boxes,
5
+ Building2,
6
+ Eye,
7
+ Factory,
8
+ Globe2,
9
+ Layers3,
10
+ ListFilter,
11
+ PackagePlus,
12
+ PackageSearch,
13
+ Scale,
14
+ SearchCode,
15
+ ShoppingCart,
16
+ Store,
17
+ Tags,
18
+ } from 'lucide-react';
19
+
20
+ type CatalogListVariant = 'cards' | 'table';
21
+ type CatalogFieldDisplayType = 'text' | 'boolean' | 'currency';
22
+ type CatalogFormFieldType =
23
+ | 'text'
24
+ | 'url'
25
+ | 'textarea'
26
+ | 'richtext'
27
+ | 'number'
28
+ | 'currency'
29
+ | 'switch'
30
+ | 'select'
31
+ | 'relation'
32
+ | 'upload'
33
+ | 'json'
34
+ | 'date'
35
+ | 'datetime';
36
+
37
+ export type CatalogLocalizedText = {
38
+ pt: string;
39
+ en: string;
40
+ };
41
+
42
+ export type CatalogFieldDefinition = {
43
+ key: string;
44
+ labelKey: string;
45
+ type?: CatalogFieldDisplayType;
46
+ };
47
+
48
+ export type CatalogFilterOptionDefinition = {
49
+ value: string;
50
+ labelKey: string;
51
+ };
52
+
53
+ export type CatalogContextualKpiDefinition = {
54
+ translationKey: string;
55
+ icon: LucideIcon;
56
+ count: (records: Record<string, unknown>[]) => number;
57
+ };
58
+
59
+ export type CatalogFormOptionDefinition = {
60
+ value: string;
61
+ label: CatalogLocalizedText;
62
+ };
63
+
64
+ export type CatalogRelationDefinition = {
65
+ endpoint: string;
66
+ resource?: string;
67
+ createResource?: string;
68
+ allowCreate?: boolean;
69
+ valueKey?: string;
70
+ labelKeys: string[];
71
+ searchParam?: string;
72
+ };
73
+
74
+ export type CatalogFormFieldDefinition = {
75
+ key: string;
76
+ label: CatalogLocalizedText;
77
+ type: CatalogFormFieldType;
78
+ required?: boolean;
79
+ span?: 1 | 2;
80
+ placeholder?: CatalogLocalizedText;
81
+ options?: CatalogFormOptionDefinition[];
82
+ relation?: CatalogRelationDefinition;
83
+ uploadDestination?: string;
84
+ accept?: string;
85
+ uploadPreviewVariant?: 'default' | 'square';
86
+ };
87
+
88
+ export type CatalogFormSectionDefinition = {
89
+ title: CatalogLocalizedText;
90
+ description?: CatalogLocalizedText;
91
+ fields: CatalogFormFieldDefinition[];
92
+ };
93
+
94
+ export type CatalogResourceDefinition = {
95
+ resource: string;
96
+ translationKey: string;
97
+ singularLabel: CatalogLocalizedText;
98
+ createActionLabel: CatalogLocalizedText;
99
+ editActionLabel: CatalogLocalizedText;
100
+ icon: LucideIcon;
101
+ href: string;
102
+ colorClass: string;
103
+ glowClass: string;
104
+ featured?: boolean;
105
+ hasActiveStats?: boolean;
106
+ template: Record<string, unknown>;
107
+ listVariant: CatalogListVariant;
108
+ primaryFilterField: string;
109
+ primaryFilterOptions: CatalogFilterOptionDefinition[];
110
+ titleFields: string[];
111
+ descriptionFields: string[];
112
+ badgeFields: string[];
113
+ cardMetadata: CatalogFieldDefinition[];
114
+ tableColumns?: CatalogFieldDefinition[];
115
+ contextualKpi: CatalogContextualKpiDefinition;
116
+ formSections: CatalogFormSectionDefinition[];
117
+ };
118
+
119
+ const ptEn = (pt: string, en: string): CatalogLocalizedText => ({ pt, en });
120
+
121
+ const statusFilterOptions: CatalogFilterOptionDefinition[] = [
122
+ { value: 'active', labelKey: 'active' },
123
+ { value: 'inactive', labelKey: 'inactive' },
124
+ { value: 'draft', labelKey: 'draft' },
125
+ { value: 'published', labelKey: 'published' },
126
+ ];
127
+
128
+ const availabilityFilterOptions: CatalogFilterOptionDefinition[] = [
129
+ { value: 'in_stock', labelKey: 'inStock' },
130
+ { value: 'out_of_stock', labelKey: 'outOfStock' },
131
+ { value: 'preorder', labelKey: 'preorder' },
132
+ ];
133
+
134
+ const dataTypeFilterOptions: CatalogFilterOptionDefinition[] = [
135
+ { value: 'text', labelKey: 'text' },
136
+ { value: 'number', labelKey: 'number' },
137
+ { value: 'boolean', labelKey: 'boolean' },
138
+ { value: 'json', labelKey: 'json' },
139
+ ];
140
+
141
+ const facetModeFilterOptions: CatalogFilterOptionDefinition[] = [
142
+ { value: 'default', labelKey: 'default' },
143
+ { value: 'range', labelKey: 'range' },
144
+ { value: 'select', labelKey: 'select' },
145
+ ];
146
+
147
+ const statusOptions = [
148
+ { value: 'active', label: ptEn('Ativo', 'Active') },
149
+ { value: 'inactive', label: ptEn('Inativo', 'Inactive') },
150
+ { value: 'draft', label: ptEn('Rascunho', 'Draft') },
151
+ { value: 'published', label: ptEn('Publicado', 'Published') },
152
+ ];
153
+
154
+ const comparisonStatusOptions = [
155
+ { value: 'draft', label: ptEn('Rascunho', 'Draft') },
156
+ { value: 'active', label: ptEn('Ativo', 'Active') },
157
+ { value: 'published', label: ptEn('Publicado', 'Published') },
158
+ { value: 'inactive', label: ptEn('Inativo', 'Inactive') },
159
+ ];
160
+
161
+ const siteTypeOptions = [
162
+ { value: 'portal', label: ptEn('Portal', 'Portal') },
163
+ { value: 'storefront', label: ptEn('Loja', 'Storefront') },
164
+ { value: 'landing_page', label: ptEn('Landing page', 'Landing page') },
165
+ ];
166
+
167
+ const dataTypeOptions = [
168
+ { value: 'text', label: ptEn('Texto', 'Text') },
169
+ { value: 'number', label: ptEn('Número', 'Number') },
170
+ { value: 'boolean', label: ptEn('Booleano', 'Boolean') },
171
+ { value: 'json', label: ptEn('JSON', 'JSON') },
172
+ ];
173
+
174
+ const comparisonModeOptions = [
175
+ { value: 'neutral', label: ptEn('Neutro', 'Neutral') },
176
+ { value: 'higher_better', label: ptEn('Maior é melhor', 'Higher is better') },
177
+ { value: 'lower_better', label: ptEn('Menor é melhor', 'Lower is better') },
178
+ { value: 'exact_match', label: ptEn('Comparação exata', 'Exact match') },
179
+ ];
180
+
181
+ const facetModeOptions = [
182
+ { value: 'default', label: ptEn('Padrão', 'Default') },
183
+ { value: 'range', label: ptEn('Faixa', 'Range') },
184
+ { value: 'select', label: ptEn('Seleção', 'Select') },
185
+ ];
186
+
187
+ const comparisonTypeOptions = [
188
+ { value: 'manual', label: ptEn('Manual', 'Manual') },
189
+ { value: 'automated', label: ptEn('Automática', 'Automated') },
190
+ ];
191
+
192
+ const generationModeOptions = [
193
+ { value: 'manual', label: ptEn('Manual', 'Manual') },
194
+ { value: 'automatic', label: ptEn('Automática', 'Automatic') },
195
+ { value: 'ai_assisted', label: ptEn('Assistida por IA', 'AI assisted') },
196
+ ];
197
+
198
+ const availabilityOptions = [
199
+ { value: 'in_stock', label: ptEn('Em estoque', 'In stock') },
200
+ { value: 'out_of_stock', label: ptEn('Sem estoque', 'Out of stock') },
201
+ { value: 'preorder', label: ptEn('Pré-venda', 'Pre-order') },
202
+ ];
203
+
204
+ const merchantTypeOptions = [
205
+ { value: 'retailer', label: ptEn('Varejista', 'Retailer') },
206
+ { value: 'marketplace', label: ptEn('Marketplace', 'Marketplace') },
207
+ { value: 'brand_store', label: ptEn('Loja da marca', 'Brand store') },
208
+ ];
209
+
210
+ const networkTypeOptions = [
211
+ { value: 'direct', label: ptEn('Direto', 'Direct') },
212
+ { value: 'network', label: ptEn('Rede', 'Network') },
213
+ { value: 'api', label: ptEn('API', 'API') },
214
+ ];
215
+
216
+ const commissionTypeOptions = [
217
+ { value: 'percentage', label: ptEn('Percentual', 'Percentage') },
218
+ { value: 'fixed', label: ptEn('Valor fixo', 'Fixed amount') },
219
+ { value: 'cpa', label: ptEn('CPA', 'CPA') },
220
+ { value: 'cpl', label: ptEn('CPL', 'CPL') },
221
+ ];
222
+
223
+ const pageTypeOptions = [
224
+ { value: 'comparison', label: ptEn('Comparação', 'Comparison') },
225
+ { value: 'category', label: ptEn('Categoria', 'Category') },
226
+ { value: 'ranking', label: ptEn('Ranking', 'Ranking') },
227
+ ];
228
+
229
+ const canonicalStrategyOptions = [
230
+ { value: 'self', label: ptEn('Self canonical', 'Self canonical') },
231
+ { value: 'parent', label: ptEn('Canônica da categoria', 'Parent canonical') },
232
+ { value: 'custom', label: ptEn('Canônica customizada', 'Custom canonical') },
233
+ ];
234
+
235
+ const sourceTypeOptions = [
236
+ { value: 'api', label: ptEn('API', 'API') },
237
+ { value: 'feed', label: ptEn('Feed', 'Feed') },
238
+ { value: 'csv', label: ptEn('CSV', 'CSV') },
239
+ { value: 'scraper', label: ptEn('Scraper', 'Scraper') },
240
+ ];
241
+
242
+ const currencyOptions = [
243
+ { value: 'BRL', label: ptEn('Real brasileiro (BRL)', 'Brazilian real (BRL)') },
244
+ { value: 'USD', label: ptEn('Dólar americano (USD)', 'US dollar (USD)') },
245
+ { value: 'EUR', label: ptEn('Euro (EUR)', 'Euro (EUR)') },
246
+ ];
247
+
248
+ const relation = (
249
+ endpoint: string,
250
+ labelKeys: string[],
251
+ options?: Partial<CatalogRelationDefinition>
252
+ ): CatalogRelationDefinition => ({
253
+ endpoint,
254
+ labelKeys,
255
+ searchParam: 'search',
256
+ ...options,
257
+ });
258
+
259
+ const fileField = (
260
+ key: string,
261
+ label: CatalogLocalizedText
262
+ ): CatalogFormFieldDefinition => ({
263
+ key,
264
+ label,
265
+ type: 'upload',
266
+ uploadDestination: `catalog/${key.replace(/_id$/, '')}`,
267
+ accept: 'image/*',
268
+ span: 2,
269
+ uploadPreviewVariant: 'square',
270
+ placeholder: ptEn('Envie uma imagem para representar este registro', 'Upload an image to represent this record'),
271
+ });
272
+
273
+ export const catalogResources: CatalogResourceDefinition[] = [
274
+ {
275
+ resource: 'brands',
276
+ translationKey: 'brands',
277
+ singularLabel: ptEn('Marca', 'Brand'),
278
+ createActionLabel: ptEn('Nova Marca', 'New Brand'),
279
+ editActionLabel: ptEn('Editar Marca', 'Edit Brand'),
280
+ icon: Factory,
281
+ href: '/catalog/brands',
282
+ colorClass: 'from-orange-500/20 via-amber-500/10 to-transparent',
283
+ glowClass: 'bg-orange-500/10 text-orange-700',
284
+ featured: true,
285
+ hasActiveStats: true,
286
+ listVariant: 'cards',
287
+ primaryFilterField: 'status',
288
+ primaryFilterOptions: statusFilterOptions,
289
+ titleFields: ['name', 'slug'],
290
+ descriptionFields: ['normalized_name', 'website_url'],
291
+ badgeFields: ['status'],
292
+ cardMetadata: [
293
+ { key: 'slug', labelKey: 'slug' },
294
+ { key: 'normalized_name', labelKey: 'normalizedName' },
295
+ { key: 'website_url', labelKey: 'websiteUrl' },
296
+ ],
297
+ contextualKpi: {
298
+ translationKey: 'visible',
299
+ icon: Eye,
300
+ count: (records) => records.length,
301
+ },
302
+ template: {
303
+ slug: '',
304
+ name: '',
305
+ normalized_name: '',
306
+ logo_file_id: null,
307
+ status: 'active',
308
+ website_url: '',
309
+ },
310
+ formSections: [
311
+ {
312
+ title: ptEn('Identificação', 'Identity'),
313
+ fields: [
314
+ { key: 'name', label: ptEn('Nome da marca', 'Brand name'), type: 'text', required: true, placeholder: ptEn('Ex.: Samsung', 'Ex.: Samsung') },
315
+ { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true, placeholder: ptEn('Ex.: samsung', 'Ex.: samsung') },
316
+ { key: 'normalized_name', label: ptEn('Nome normalizado', 'Normalized name'), type: 'text', placeholder: ptEn('Versão limpa para buscas e matching', 'Normalized value for search and matching') },
317
+ { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: statusOptions },
318
+ { key: 'website_url', label: ptEn('Site oficial', 'Official website'), type: 'url', span: 2, placeholder: ptEn('https://www.sua-marca.com', 'https://www.your-brand.com') },
319
+ fileField('logo_file_id', ptEn('Logo', 'Logo')),
320
+ ],
321
+ },
322
+ ],
323
+ },
324
+ {
325
+ resource: 'category-attributes',
326
+ translationKey: 'categoryAttributes',
327
+ singularLabel: ptEn('Atributo por categoria', 'Category attribute'),
328
+ createActionLabel: ptEn('Novo Atributo por Categoria', 'New Category Attribute'),
329
+ editActionLabel: ptEn('Editar Atributo por Categoria', 'Edit Category Attribute'),
330
+ icon: BadgePercent,
331
+ href: '/catalog/category-attributes',
332
+ colorClass: 'from-pink-500/20 via-rose-500/10 to-transparent',
333
+ glowClass: 'bg-pink-500/10 text-pink-700',
334
+ listVariant: 'table',
335
+ primaryFilterField: 'facet_mode',
336
+ primaryFilterOptions: facetModeFilterOptions,
337
+ titleFields: ['attribute_id', 'category_id'],
338
+ descriptionFields: ['facet_mode'],
339
+ badgeFields: ['facet_mode', 'is_required', 'is_highlighted'],
340
+ cardMetadata: [
341
+ { key: 'category_id', labelKey: 'categoryId' },
342
+ { key: 'attribute_id', labelKey: 'attributeId' },
343
+ { key: 'weight', labelKey: 'weight' },
344
+ ],
345
+ tableColumns: [
346
+ { key: 'id', labelKey: 'id' },
347
+ { key: 'category_id', labelKey: 'categoryId' },
348
+ { key: 'attribute_id', labelKey: 'attributeId' },
349
+ { key: 'facet_mode', labelKey: 'facetMode' },
350
+ { key: 'is_required', labelKey: 'isRequired', type: 'boolean' },
351
+ { key: 'is_highlighted', labelKey: 'isHighlighted', type: 'boolean' },
352
+ ],
353
+ contextualKpi: {
354
+ translationKey: 'requiredInSlice',
355
+ icon: BadgePercent,
356
+ count: (records) => records.filter((record) => record.is_required === true).length,
357
+ },
358
+ template: {
359
+ category_id: null,
360
+ attribute_id: null,
361
+ is_required: false,
362
+ is_highlighted: true,
363
+ display_order: 0,
364
+ weight: 1,
365
+ facet_mode: 'default',
366
+ },
367
+ formSections: [
368
+ {
369
+ title: ptEn('Relacionamentos', 'Relationships'),
370
+ fields: [
371
+ {
372
+ key: 'category_id',
373
+ label: ptEn('Categoria', 'Category'),
374
+ type: 'relation',
375
+ required: true,
376
+ relation: relation('/category', ['name', 'slug'], { valueKey: 'category_id' }),
377
+ },
378
+ {
379
+ key: 'attribute_id',
380
+ label: ptEn('Atributo', 'Attribute'),
381
+ type: 'relation',
382
+ required: true,
383
+ relation: relation('/catalog/attributes', ['label', 'slug'], {
384
+ resource: 'attributes',
385
+ createResource: 'attributes',
386
+ allowCreate: true,
387
+ }),
388
+ },
389
+ { key: 'facet_mode', label: ptEn('Modo de faceta', 'Facet mode'), type: 'select', required: true, options: facetModeOptions },
390
+ { key: 'display_order', label: ptEn('Ordem de exibição', 'Display order'), type: 'number' },
391
+ { key: 'weight', label: ptEn('Peso', 'Weight'), type: 'number' },
392
+ { key: 'is_required', label: ptEn('Obrigatório', 'Required'), type: 'switch' },
393
+ { key: 'is_highlighted', label: ptEn('Destacado', 'Highlighted'), type: 'switch' },
394
+ ],
395
+ },
396
+ ],
397
+ },
398
+ {
399
+ resource: 'comparisons',
400
+ translationKey: 'comparisons',
401
+ singularLabel: ptEn('Comparação', 'Comparison'),
402
+ createActionLabel: ptEn('Nova Comparação', 'New Comparison'),
403
+ editActionLabel: ptEn('Editar Comparação', 'Edit Comparison'),
404
+ icon: Scale,
405
+ href: '/catalog/comparisons',
406
+ colorClass: 'from-amber-500/20 via-yellow-500/10 to-transparent',
407
+ glowClass: 'bg-amber-500/10 text-amber-700',
408
+ hasActiveStats: true,
409
+ listVariant: 'cards',
410
+ primaryFilterField: 'status',
411
+ primaryFilterOptions: statusFilterOptions,
412
+ titleFields: ['title', 'slug'],
413
+ descriptionFields: ['summary', 'slug'],
414
+ badgeFields: ['status', 'comparison_type', 'generation_mode'],
415
+ cardMetadata: [
416
+ { key: 'slug', labelKey: 'slug' },
417
+ { key: 'site_id', labelKey: 'siteId' },
418
+ { key: 'category_id', labelKey: 'categoryId' },
419
+ ],
420
+ contextualKpi: {
421
+ translationKey: 'publishedInSlice',
422
+ icon: Scale,
423
+ count: (records) =>
424
+ records.filter((record) =>
425
+ ['active', 'published'].includes(String(record.status ?? ''))
426
+ ).length,
427
+ },
428
+ template: {
429
+ category_id: null,
430
+ site_id: null,
431
+ primary_content_id: null,
432
+ slug: '',
433
+ comparison_type: 'manual',
434
+ generation_mode: 'manual',
435
+ status: 'draft',
436
+ title: '',
437
+ summary: '',
438
+ intro: '',
439
+ verdict: '',
440
+ spec_snapshot_json: {},
441
+ eligibility_hash: '',
442
+ published_at: '',
443
+ },
444
+ formSections: [
445
+ {
446
+ title: ptEn('Base da comparação', 'Comparison basics'),
447
+ fields: [
448
+ { key: 'title', label: ptEn('Título', 'Title'), type: 'text', required: true, placeholder: ptEn('Ex.: Melhor celular premium de 2026', 'Ex.: Best premium smartphone of 2026') },
449
+ { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true, placeholder: ptEn('Ex.: melhor-celular-premium-2026', 'Ex.: best-premium-smartphone-2026') },
450
+ {
451
+ key: 'site_id',
452
+ label: ptEn('Site', 'Site'),
453
+ type: 'relation',
454
+ required: true,
455
+ relation: relation('/catalog/sites', ['name', 'domain', 'slug'], {
456
+ resource: 'sites',
457
+ createResource: 'sites',
458
+ allowCreate: true,
459
+ }),
460
+ },
461
+ { key: 'category_id', label: ptEn('Categoria', 'Category'), type: 'relation', relation: relation('/category', ['name', 'slug'], { valueKey: 'category_id' }) },
462
+ { key: 'primary_content_id', label: ptEn('Conteúdo principal', 'Primary content'), type: 'relation', relation: relation('/content', ['title', 'slug'], { valueKey: 'content_id' }) },
463
+ { key: 'comparison_type', label: ptEn('Tipo de comparação', 'Comparison type'), type: 'select', required: true, options: comparisonTypeOptions },
464
+ { key: 'generation_mode', label: ptEn('Modo de geração', 'Generation mode'), type: 'select', required: true, options: generationModeOptions },
465
+ { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: statusOptions },
466
+ { key: 'published_at', label: ptEn('Publicado em', 'Published at'), type: 'datetime' },
467
+ ],
468
+ },
469
+ {
470
+ title: ptEn('Conteúdo editorial', 'Editorial content'),
471
+ fields: [
472
+ { key: 'summary', label: ptEn('Resumo', 'Summary'), type: 'richtext', span: 2, placeholder: ptEn('Resumo editorial da comparação', 'Editorial summary of the comparison') },
473
+ { key: 'intro', label: ptEn('Introdução', 'Introduction'), type: 'richtext', span: 2, placeholder: ptEn('Contextualize o comparativo para o leitor', 'Add reader-facing context for the comparison') },
474
+ { key: 'verdict', label: ptEn('Veredito', 'Verdict'), type: 'richtext', span: 2, placeholder: ptEn('Conclusão e recomendação final', 'Final conclusion and recommendation') },
475
+ { key: 'eligibility_hash', label: ptEn('Hash de elegibilidade', 'Eligibility hash'), type: 'text', span: 2, placeholder: ptEn('Chave técnica para rastrear o conjunto elegível', 'Technical key to track the eligible set') },
476
+ { key: 'spec_snapshot_json', label: ptEn('Snapshot de regras', 'Rule snapshot'), type: 'json', span: 2 },
477
+ ],
478
+ },
479
+ ],
480
+ },
481
+ {
482
+ resource: 'offers',
483
+ translationKey: 'offers',
484
+ singularLabel: ptEn('Oferta', 'Offer'),
485
+ createActionLabel: ptEn('Nova Oferta', 'New Offer'),
486
+ editActionLabel: ptEn('Editar Oferta', 'Edit Offer'),
487
+ icon: ShoppingCart,
488
+ href: '/catalog/offers',
489
+ colorClass: 'from-red-500/20 via-orange-500/10 to-transparent',
490
+ glowClass: 'bg-red-500/10 text-red-700',
491
+ featured: true,
492
+ listVariant: 'cards',
493
+ primaryFilterField: 'availability_status',
494
+ primaryFilterOptions: availabilityFilterOptions,
495
+ titleFields: ['title', 'external_offer_id'],
496
+ descriptionFields: ['external_offer_id', 'affiliate_url'],
497
+ badgeFields: ['availability_status', 'is_featured'],
498
+ cardMetadata: [
499
+ { key: 'price_amount', labelKey: 'priceAmount', type: 'currency' },
500
+ { key: 'merchant_id', labelKey: 'merchantId' },
501
+ { key: 'product_id', labelKey: 'productId' },
502
+ ],
503
+ contextualKpi: {
504
+ translationKey: 'inStockInSlice',
505
+ icon: ShoppingCart,
506
+ count: (records) => records.filter((record) => record.availability_status === 'in_stock').length,
507
+ },
508
+ template: {
509
+ product_id: null,
510
+ merchant_id: null,
511
+ affiliate_program_id: null,
512
+ site_id: null,
513
+ external_offer_id: '',
514
+ title: '',
515
+ price_amount: 0,
516
+ price_currency: 'BRL',
517
+ original_price_amount: 0,
518
+ installment_json: {},
519
+ availability_status: 'in_stock',
520
+ affiliate_url: '',
521
+ deep_link_url: '',
522
+ priority_score: 0,
523
+ is_featured: false,
524
+ valid_from: '',
525
+ valid_until: '',
526
+ last_seen_at: '',
527
+ },
528
+ formSections: [
529
+ {
530
+ title: ptEn('Oferta comercial', 'Commercial offer'),
531
+ fields: [
532
+ { key: 'title', label: ptEn('Título da oferta', 'Offer title'), type: 'text', required: true, span: 2, placeholder: ptEn('Ex.: Galaxy S25 Ultra 256GB por R$ 6.999', 'Ex.: Galaxy S25 Ultra 256GB for $1,299') },
533
+ {
534
+ key: 'product_id',
535
+ label: ptEn('Produto', 'Product'),
536
+ type: 'relation',
537
+ required: true,
538
+ relation: relation('/catalog/products', ['name', 'model_name', 'slug'], {
539
+ resource: 'products',
540
+ createResource: 'products',
541
+ allowCreate: true,
542
+ }),
543
+ },
544
+ {
545
+ key: 'merchant_id',
546
+ label: ptEn('Lojista', 'Merchant'),
547
+ type: 'relation',
548
+ required: true,
549
+ relation: relation('/catalog/merchants', ['name', 'slug'], {
550
+ resource: 'merchants',
551
+ createResource: 'merchants',
552
+ allowCreate: true,
553
+ }),
554
+ },
555
+ {
556
+ key: 'affiliate_program_id',
557
+ label: ptEn('Programa de afiliação', 'Affiliate program'),
558
+ type: 'relation',
559
+ relation: relation('/catalog/affiliate-programs', ['name', 'slug'], {
560
+ resource: 'affiliate-programs',
561
+ createResource: 'affiliate-programs',
562
+ allowCreate: true,
563
+ }),
564
+ },
565
+ {
566
+ key: 'site_id',
567
+ label: ptEn('Site', 'Site'),
568
+ type: 'relation',
569
+ relation: relation('/catalog/sites', ['name', 'domain', 'slug'], {
570
+ resource: 'sites',
571
+ createResource: 'sites',
572
+ allowCreate: true,
573
+ }),
574
+ },
575
+ { key: 'external_offer_id', label: ptEn('ID externo', 'External ID'), type: 'text', placeholder: ptEn('ID da oferta no feed/API do parceiro', 'Offer id from partner feed/API') },
576
+ { key: 'availability_status', label: ptEn('Disponibilidade', 'Availability'), type: 'select', required: true, options: availabilityOptions },
577
+ { key: 'price_amount', label: ptEn('Preço', 'Price'), type: 'currency', required: true },
578
+ { key: 'original_price_amount', label: ptEn('Preço original', 'Original price'), type: 'currency' },
579
+ { key: 'price_currency', label: ptEn('Moeda', 'Currency'), type: 'select', required: true, options: currencyOptions },
580
+ { key: 'priority_score', label: ptEn('Prioridade', 'Priority'), type: 'number' },
581
+ { key: 'is_featured', label: ptEn('Oferta destacada', 'Featured offer'), type: 'switch' },
582
+ ],
583
+ },
584
+ {
585
+ title: ptEn('Links e vigência', 'Links and schedule'),
586
+ fields: [
587
+ { key: 'affiliate_url', label: ptEn('URL de afiliação', 'Affiliate URL'), type: 'url', span: 2, placeholder: ptEn('https://parceiro.com/r/abc123', 'https://partner.com/r/abc123') },
588
+ { key: 'deep_link_url', label: ptEn('Deep link', 'Deep link'), type: 'url', span: 2, placeholder: ptEn('https://loja.com/produto/oferta', 'https://store.com/product/offer') },
589
+ { key: 'valid_from', label: ptEn('Válida a partir de', 'Valid from'), type: 'datetime' },
590
+ { key: 'valid_until', label: ptEn('Válida até', 'Valid until'), type: 'datetime' },
591
+ { key: 'last_seen_at', label: ptEn('Última captura', 'Last seen at'), type: 'datetime' },
592
+ { key: 'installment_json', label: ptEn('Parcelamento', 'Installments'), type: 'json', span: 2 },
593
+ ],
594
+ },
595
+ ],
596
+ },
597
+ {
598
+ resource: 'sites',
599
+ translationKey: 'sites',
600
+ singularLabel: ptEn('Site', 'Site'),
601
+ createActionLabel: ptEn('Novo Site', 'New Site'),
602
+ editActionLabel: ptEn('Editar Site', 'Edit Site'),
603
+ icon: Globe2,
604
+ href: '/catalog/sites',
605
+ colorClass: 'from-sky-500/20 via-cyan-500/10 to-transparent',
606
+ glowClass: 'bg-sky-500/10 text-sky-700',
607
+ hasActiveStats: true,
608
+ listVariant: 'cards',
609
+ primaryFilterField: 'status',
610
+ primaryFilterOptions: statusFilterOptions,
611
+ titleFields: ['name', 'domain', 'slug'],
612
+ descriptionFields: ['domain', 'slug'],
613
+ badgeFields: ['status', 'site_type'],
614
+ cardMetadata: [
615
+ { key: 'slug', labelKey: 'slug' },
616
+ { key: 'domain', labelKey: 'domain' },
617
+ { key: 'default_locale_id', labelKey: 'defaultLocaleId' },
618
+ ],
619
+ contextualKpi: {
620
+ translationKey: 'visible',
621
+ icon: Eye,
622
+ count: (records) => records.length,
623
+ },
624
+ template: {
625
+ slug: '',
626
+ name: '',
627
+ domain: '',
628
+ status: 'active',
629
+ site_type: 'portal',
630
+ default_locale_id: null,
631
+ theme_settings_json: {},
632
+ seo_defaults_json: {},
633
+ },
634
+ formSections: [
635
+ {
636
+ title: ptEn('Dados principais', 'Main details'),
637
+ fields: [
638
+ { key: 'name', label: ptEn('Nome do site', 'Site name'), type: 'text', required: true, placeholder: ptEn('Ex.: Tech Radar BR', 'Ex.: Tech Radar BR') },
639
+ { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true, placeholder: ptEn('Ex.: tech-radar-br', 'Ex.: tech-radar-br') },
640
+ { key: 'domain', label: ptEn('Domínio', 'Domain'), type: 'url', required: true, placeholder: ptEn('https://www.exemplo.com', 'https://www.example.com') },
641
+ { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: statusOptions },
642
+ { key: 'site_type', label: ptEn('Tipo de site', 'Site type'), type: 'select', required: true, options: siteTypeOptions },
643
+ {
644
+ key: 'default_locale_id',
645
+ label: ptEn('Locale padrão', 'Default locale'),
646
+ type: 'relation',
647
+ relation: relation('/locale', ['name', 'code'], { valueKey: 'id' }),
648
+ },
649
+ ],
650
+ },
651
+ {
652
+ title: ptEn('Configurações', 'Settings'),
653
+ fields: [
654
+ { key: 'theme_settings_json', label: ptEn('Tema e identidade', 'Theme and identity'), type: 'json', span: 2 },
655
+ { key: 'seo_defaults_json', label: ptEn('Padrões de SEO', 'SEO defaults'), type: 'json', span: 2 },
656
+ ],
657
+ },
658
+ ],
659
+ },
660
+ {
661
+ resource: 'products',
662
+ translationKey: 'products',
663
+ singularLabel: ptEn('Produto', 'Product'),
664
+ createActionLabel: ptEn('Novo Produto', 'New Product'),
665
+ editActionLabel: ptEn('Editar Produto', 'Edit Product'),
666
+ icon: PackageSearch,
667
+ href: '/catalog/products',
668
+ colorClass: 'from-emerald-500/20 via-green-500/10 to-transparent',
669
+ glowClass: 'bg-emerald-500/10 text-emerald-700',
670
+ featured: true,
671
+ hasActiveStats: true,
672
+ listVariant: 'cards',
673
+ primaryFilterField: 'status',
674
+ primaryFilterOptions: statusFilterOptions,
675
+ titleFields: ['name', 'model_name', 'slug'],
676
+ descriptionFields: ['model_name', 'sku', 'slug'],
677
+ badgeFields: ['status', 'comparison_status', 'is_active'],
678
+ cardMetadata: [
679
+ { key: 'slug', labelKey: 'slug' },
680
+ { key: 'sku', labelKey: 'sku' },
681
+ { key: 'brand_id', labelKey: 'brandId' },
682
+ { key: 'category_id', labelKey: 'categoryId' },
683
+ ],
684
+ contextualKpi: {
685
+ translationKey: 'activeInSlice',
686
+ icon: PackageSearch,
687
+ count: (records) => records.filter((record) => record.is_active === true).length,
688
+ },
689
+ template: {
690
+ brand_id: null,
691
+ category_id: null,
692
+ primary_content_id: null,
693
+ slug: '',
694
+ name: '',
695
+ short_description: '',
696
+ description: '',
697
+ model_name: '',
698
+ sku: '',
699
+ gtin: '',
700
+ status: 'draft',
701
+ comparison_status: 'draft',
702
+ release_date: '',
703
+ spec_snapshot_json: {},
704
+ comparison_snapshot_json: {},
705
+ is_active: true,
706
+ },
707
+ formSections: [
708
+ {
709
+ title: ptEn('Base do produto', 'Product basics'),
710
+ fields: [
711
+ { key: 'name', label: ptEn('Nome do produto', 'Product name'), type: 'text', required: true, placeholder: ptEn('Ex.: Galaxy S25 Ultra', 'Ex.: Galaxy S25 Ultra') },
712
+ { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true, placeholder: ptEn('Ex.: galaxy-s25-ultra', 'Ex.: galaxy-s25-ultra') },
713
+ {
714
+ key: 'brand_id',
715
+ label: ptEn('Marca', 'Brand'),
716
+ type: 'relation',
717
+ required: true,
718
+ relation: relation('/catalog/brands', ['name', 'slug'], {
719
+ resource: 'brands',
720
+ createResource: 'brands',
721
+ allowCreate: true,
722
+ }),
723
+ },
724
+ {
725
+ key: 'category_id',
726
+ label: ptEn('Categoria', 'Category'),
727
+ type: 'relation',
728
+ required: true,
729
+ relation: relation('/category', ['name', 'slug'], {
730
+ valueKey: 'category_id',
731
+ }),
732
+ },
733
+ {
734
+ key: 'primary_content_id',
735
+ label: ptEn('Conteúdo principal', 'Primary content'),
736
+ type: 'relation',
737
+ relation: relation('/content', ['title', 'slug'], {
738
+ valueKey: 'content_id',
739
+ }),
740
+ },
741
+ { key: 'model_name', label: ptEn('Modelo', 'Model'), type: 'text', placeholder: ptEn('Nome técnico ou comercial do modelo', 'Technical or commercial model name') },
742
+ { key: 'sku', label: ptEn('SKU', 'SKU'), type: 'text', placeholder: ptEn('Código interno de estoque', 'Internal stock code') },
743
+ { key: 'gtin', label: ptEn('GTIN', 'GTIN'), type: 'text', placeholder: ptEn('Código de barras/GTIN', 'Barcode / GTIN') },
744
+ { key: 'release_date', label: ptEn('Data de lançamento', 'Release date'), type: 'date' },
745
+ { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: statusOptions },
746
+ { key: 'comparison_status', label: ptEn('Status de comparação', 'Comparison status'), type: 'select', required: true, options: comparisonStatusOptions },
747
+ { key: 'is_active', label: ptEn('Ativo para comparação', 'Active for comparison'), type: 'switch' },
748
+ ],
749
+ },
750
+ {
751
+ title: ptEn('Conteúdo', 'Content'),
752
+ fields: [
753
+ { key: 'short_description', label: ptEn('Resumo curto', 'Short description'), type: 'richtext', span: 2, placeholder: ptEn('Resumo rápido para cards e snippets', 'Short summary for cards and snippets') },
754
+ { key: 'description', label: ptEn('Descrição completa', 'Full description'), type: 'richtext', span: 2, placeholder: ptEn('Detalhes completos, diferenciais e contexto do produto', 'Full product details, highlights and context') },
755
+ { key: 'spec_snapshot_json', label: ptEn('Snapshot de especificações', 'Specification snapshot'), type: 'json', span: 2 },
756
+ { key: 'comparison_snapshot_json', label: ptEn('Snapshot de comparação', 'Comparison snapshot'), type: 'json', span: 2 },
757
+ ],
758
+ },
759
+ ],
760
+ },
761
+ {
762
+ resource: 'attribute-groups',
763
+ translationKey: 'attributeGroups',
764
+ singularLabel: ptEn('Grupo de atributos', 'Attribute group'),
765
+ createActionLabel: ptEn('Novo Grupo de Atributos', 'New Attribute Group'),
766
+ editActionLabel: ptEn('Editar Grupo de Atributos', 'Edit Attribute Group'),
767
+ icon: Layers3,
768
+ href: '/catalog/attribute-groups',
769
+ colorClass: 'from-violet-500/20 via-fuchsia-500/10 to-transparent',
770
+ glowClass: 'bg-violet-500/10 text-violet-700',
771
+ hasActiveStats: true,
772
+ listVariant: 'table',
773
+ primaryFilterField: 'status',
774
+ primaryFilterOptions: statusFilterOptions,
775
+ titleFields: ['name', 'slug'],
776
+ descriptionFields: ['slug'],
777
+ badgeFields: ['status'],
778
+ cardMetadata: [
779
+ { key: 'slug', labelKey: 'slug' },
780
+ { key: 'status', labelKey: 'status' },
781
+ ],
782
+ tableColumns: [
783
+ { key: 'id', labelKey: 'id' },
784
+ { key: 'name', labelKey: 'name' },
785
+ { key: 'slug', labelKey: 'slug' },
786
+ { key: 'status', labelKey: 'status' },
787
+ ],
788
+ contextualKpi: {
789
+ translationKey: 'visible',
790
+ icon: Eye,
791
+ count: (records) => records.length,
792
+ },
793
+ template: { slug: '', name: '', status: 'active' },
794
+ formSections: [
795
+ {
796
+ title: ptEn('Grupo', 'Group'),
797
+ fields: [
798
+ { key: 'name', label: ptEn('Nome do grupo', 'Group name'), type: 'text', required: true, placeholder: ptEn('Ex.: Tela e Display', 'Ex.: Display and Screen') },
799
+ { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true, placeholder: ptEn('Ex.: tela-e-display', 'Ex.: display-and-screen') },
800
+ { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: statusOptions },
801
+ ],
802
+ },
803
+ ],
804
+ },
805
+ {
806
+ resource: 'attributes',
807
+ translationKey: 'attributes',
808
+ singularLabel: ptEn('Atributo', 'Attribute'),
809
+ createActionLabel: ptEn('Novo Atributo', 'New Attribute'),
810
+ editActionLabel: ptEn('Editar Atributo', 'Edit Attribute'),
811
+ icon: Tags,
812
+ href: '/catalog/attributes',
813
+ colorClass: 'from-indigo-500/20 via-blue-500/10 to-transparent',
814
+ glowClass: 'bg-indigo-500/10 text-indigo-700',
815
+ listVariant: 'table',
816
+ primaryFilterField: 'data_type',
817
+ primaryFilterOptions: dataTypeFilterOptions,
818
+ titleFields: ['label', 'slug'],
819
+ descriptionFields: ['description', 'slug'],
820
+ badgeFields: ['data_type', 'comparison_mode'],
821
+ cardMetadata: [
822
+ { key: 'group_id', labelKey: 'groupId' },
823
+ { key: 'is_filterable', labelKey: 'isFilterable', type: 'boolean' },
824
+ { key: 'is_required_for_comparison', labelKey: 'requiredForComparison', type: 'boolean' },
825
+ ],
826
+ tableColumns: [
827
+ { key: 'id', labelKey: 'id' },
828
+ { key: 'label', labelKey: 'label' },
829
+ { key: 'slug', labelKey: 'slug' },
830
+ { key: 'data_type', labelKey: 'dataType' },
831
+ { key: 'comparison_mode', labelKey: 'comparisonMode' },
832
+ ],
833
+ contextualKpi: {
834
+ translationKey: 'filterableInSlice',
835
+ icon: ListFilter,
836
+ count: (records) => records.filter((record) => record.is_filterable === true).length,
837
+ },
838
+ template: {
839
+ group_id: null,
840
+ slug: '',
841
+ label: '',
842
+ description: '',
843
+ data_type: 'text',
844
+ unit: '',
845
+ comparison_mode: 'neutral',
846
+ is_filterable: true,
847
+ is_sortable: false,
848
+ is_required_for_comparison: false,
849
+ normalization_rule_json: {},
850
+ display_order: 0,
851
+ },
852
+ formSections: [
853
+ {
854
+ title: ptEn('Configuração do atributo', 'Attribute setup'),
855
+ fields: [
856
+ { key: 'label', label: ptEn('Label', 'Label'), type: 'text', required: true, placeholder: ptEn('Ex.: Tamanho da tela', 'Ex.: Screen size') },
857
+ { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true, placeholder: ptEn('Ex.: tamanho-da-tela', 'Ex.: screen-size') },
858
+ {
859
+ key: 'group_id',
860
+ label: ptEn('Grupo', 'Group'),
861
+ type: 'relation',
862
+ required: true,
863
+ relation: relation('/catalog/attribute-groups', ['name', 'slug'], {
864
+ resource: 'attribute-groups',
865
+ createResource: 'attribute-groups',
866
+ allowCreate: true,
867
+ }),
868
+ },
869
+ { key: 'data_type', label: ptEn('Tipo de dado', 'Data type'), type: 'select', required: true, options: dataTypeOptions },
870
+ { key: 'unit', label: ptEn('Unidade', 'Unit'), type: 'text', placeholder: ptEn('Ex.: polegadas, GB, kg', 'Ex.: inches, GB, kg') },
871
+ { key: 'comparison_mode', label: ptEn('Modo de comparação', 'Comparison mode'), type: 'select', required: true, options: comparisonModeOptions },
872
+ { key: 'display_order', label: ptEn('Ordem de exibição', 'Display order'), type: 'number' },
873
+ { key: 'description', label: ptEn('Descrição', 'Description'), type: 'richtext', span: 2, placeholder: ptEn('Explique como o atributo deve ser usado e exibido', 'Explain how the attribute should be used and displayed') },
874
+ ],
875
+ },
876
+ {
877
+ title: ptEn('Regras', 'Rules'),
878
+ fields: [
879
+ { key: 'is_filterable', label: ptEn('Filtrável', 'Filterable'), type: 'switch' },
880
+ { key: 'is_sortable', label: ptEn('Ordenável', 'Sortable'), type: 'switch' },
881
+ { key: 'is_required_for_comparison', label: ptEn('Obrigatório para comparação', 'Required for comparison'), type: 'switch' },
882
+ { key: 'normalization_rule_json', label: ptEn('Regra de normalização', 'Normalization rule'), type: 'json', span: 2 },
883
+ ],
884
+ },
885
+ ],
886
+ },
887
+ {
888
+ resource: 'merchants',
889
+ translationKey: 'merchants',
890
+ singularLabel: ptEn('Lojista', 'Merchant'),
891
+ createActionLabel: ptEn('Novo Lojista', 'New Merchant'),
892
+ editActionLabel: ptEn('Editar Lojista', 'Edit Merchant'),
893
+ icon: Store,
894
+ href: '/catalog/merchants',
895
+ colorClass: 'from-lime-500/20 via-green-500/10 to-transparent',
896
+ glowClass: 'bg-lime-500/10 text-lime-700',
897
+ featured: true,
898
+ hasActiveStats: true,
899
+ listVariant: 'cards',
900
+ primaryFilterField: 'status',
901
+ primaryFilterOptions: statusFilterOptions,
902
+ titleFields: ['name', 'slug'],
903
+ descriptionFields: ['slug'],
904
+ badgeFields: ['status', 'merchant_type'],
905
+ cardMetadata: [
906
+ { key: 'slug', labelKey: 'slug' },
907
+ { key: 'merchant_type', labelKey: 'merchantType' },
908
+ { key: 'logo_file_id', labelKey: 'logoFileId' },
909
+ ],
910
+ contextualKpi: {
911
+ translationKey: 'visible',
912
+ icon: Eye,
913
+ count: (records) => records.length,
914
+ },
915
+ template: {
916
+ slug: '',
917
+ name: '',
918
+ status: 'active',
919
+ merchant_type: 'retailer',
920
+ logo_file_id: null,
921
+ },
922
+ formSections: [
923
+ {
924
+ title: ptEn('Perfil do lojista', 'Merchant profile'),
925
+ fields: [
926
+ { key: 'name', label: ptEn('Nome do lojista', 'Merchant name'), type: 'text', required: true, placeholder: ptEn('Ex.: Magazine Luiza', 'Ex.: Best Buy') },
927
+ { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true, placeholder: ptEn('Ex.: magazine-luiza', 'Ex.: best-buy') },
928
+ { key: 'merchant_type', label: ptEn('Tipo de lojista', 'Merchant type'), type: 'select', required: true, options: merchantTypeOptions },
929
+ { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: statusOptions },
930
+ fileField('logo_file_id', ptEn('Logo', 'Logo')),
931
+ ],
932
+ },
933
+ ],
934
+ },
935
+ {
936
+ resource: 'affiliate-programs',
937
+ translationKey: 'affiliatePrograms',
938
+ singularLabel: ptEn('Programa de afiliação', 'Affiliate program'),
939
+ createActionLabel: ptEn('Novo Programa de Afiliação', 'New Affiliate Program'),
940
+ editActionLabel: ptEn('Editar Programa de Afiliação', 'Edit Affiliate Program'),
941
+ icon: Building2,
942
+ href: '/catalog/affiliate-programs',
943
+ colorClass: 'from-cyan-500/20 via-teal-500/10 to-transparent',
944
+ glowClass: 'bg-cyan-500/10 text-cyan-700',
945
+ hasActiveStats: true,
946
+ listVariant: 'cards',
947
+ primaryFilterField: 'status',
948
+ primaryFilterOptions: statusFilterOptions,
949
+ titleFields: ['name', 'slug'],
950
+ descriptionFields: ['slug', 'network_type'],
951
+ badgeFields: ['status', 'network_type', 'commission_type'],
952
+ cardMetadata: [
953
+ { key: 'merchant_id', labelKey: 'merchantId' },
954
+ { key: 'commission_type', labelKey: 'commissionType' },
955
+ { key: 'default_commission_value', labelKey: 'defaultCommissionValue', type: 'currency' },
956
+ ],
957
+ contextualKpi: {
958
+ translationKey: 'visible',
959
+ icon: Eye,
960
+ count: (records) => records.length,
961
+ },
962
+ template: {
963
+ merchant_id: null,
964
+ slug: '',
965
+ name: '',
966
+ network_type: 'direct',
967
+ tracking_template: '',
968
+ commission_type: 'percentage',
969
+ default_commission_value: 0,
970
+ status: 'active',
971
+ },
972
+ formSections: [
973
+ {
974
+ title: ptEn('Programa', 'Program'),
975
+ fields: [
976
+ { key: 'name', label: ptEn('Nome do programa', 'Program name'), type: 'text', required: true, placeholder: ptEn('Ex.: Programa Magalu Ads', 'Ex.: Best Buy Affiliate Program') },
977
+ { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true, placeholder: ptEn('Ex.: magalu-ads', 'Ex.: best-buy-affiliate') },
978
+ {
979
+ key: 'merchant_id',
980
+ label: ptEn('Lojista', 'Merchant'),
981
+ type: 'relation',
982
+ required: true,
983
+ relation: relation('/catalog/merchants', ['name', 'slug'], {
984
+ resource: 'merchants',
985
+ createResource: 'merchants',
986
+ allowCreate: true,
987
+ }),
988
+ },
989
+ { key: 'network_type', label: ptEn('Rede', 'Network'), type: 'select', required: true, options: networkTypeOptions },
990
+ { key: 'commission_type', label: ptEn('Tipo de comissão', 'Commission type'), type: 'select', required: true, options: commissionTypeOptions },
991
+ { key: 'default_commission_value', label: ptEn('Comissão padrão', 'Default commission'), type: 'currency' },
992
+ { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: statusOptions },
993
+ { key: 'tracking_template', label: ptEn('Template de tracking', 'Tracking template'), type: 'textarea', span: 2, placeholder: ptEn('Template com macros e parâmetros de tracking', 'Template with tracking macros and parameters') },
994
+ ],
995
+ },
996
+ ],
997
+ },
998
+ {
999
+ resource: 'seo-rules',
1000
+ translationKey: 'seoRules',
1001
+ singularLabel: ptEn('Regra de SEO', 'SEO rule'),
1002
+ createActionLabel: ptEn('Nova Regra de SEO', 'New SEO Rule'),
1003
+ editActionLabel: ptEn('Editar Regra de SEO', 'Edit SEO Rule'),
1004
+ icon: SearchCode,
1005
+ href: '/catalog/seo-rules',
1006
+ colorClass: 'from-slate-500/20 via-zinc-500/10 to-transparent',
1007
+ glowClass: 'bg-slate-500/10 text-slate-700',
1008
+ featured: true,
1009
+ hasActiveStats: true,
1010
+ listVariant: 'cards',
1011
+ primaryFilterField: 'status',
1012
+ primaryFilterOptions: statusFilterOptions,
1013
+ titleFields: ['rule_slug', 'page_type'],
1014
+ descriptionFields: ['page_type', 'canonical_strategy'],
1015
+ badgeFields: ['status', 'page_type', 'canonical_strategy'],
1016
+ cardMetadata: [
1017
+ { key: 'site_id', labelKey: 'siteId' },
1018
+ { key: 'category_id', labelKey: 'categoryId' },
1019
+ { key: 'priority', labelKey: 'priority' },
1020
+ ],
1021
+ contextualKpi: {
1022
+ translationKey: 'visible',
1023
+ icon: Eye,
1024
+ count: (records) => records.length,
1025
+ },
1026
+ template: {
1027
+ site_id: null,
1028
+ category_id: null,
1029
+ page_type: 'comparison',
1030
+ rule_slug: '',
1031
+ status: 'active',
1032
+ generation_query_json: {},
1033
+ min_product_count: 2,
1034
+ min_attribute_coverage: 1,
1035
+ canonical_strategy: 'self',
1036
+ priority: 0,
1037
+ template_json: {},
1038
+ },
1039
+ formSections: [
1040
+ {
1041
+ title: ptEn('Escopo da regra', 'Rule scope'),
1042
+ fields: [
1043
+ {
1044
+ key: 'site_id',
1045
+ label: ptEn('Site', 'Site'),
1046
+ type: 'relation',
1047
+ required: true,
1048
+ relation: relation('/catalog/sites', ['name', 'domain', 'slug'], {
1049
+ resource: 'sites',
1050
+ createResource: 'sites',
1051
+ allowCreate: true,
1052
+ }),
1053
+ },
1054
+ { key: 'category_id', label: ptEn('Categoria', 'Category'), type: 'relation', relation: relation('/category', ['name', 'slug'], { valueKey: 'category_id' }) },
1055
+ { key: 'page_type', label: ptEn('Tipo de página', 'Page type'), type: 'select', required: true, options: pageTypeOptions },
1056
+ { key: 'rule_slug', label: ptEn('Slug da regra', 'Rule slug'), type: 'text', required: true, placeholder: ptEn('Ex.: comparativo-celulares-premium', 'Ex.: premium-smartphone-comparison') },
1057
+ { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: statusOptions },
1058
+ { key: 'canonical_strategy', label: ptEn('Estratégia canônica', 'Canonical strategy'), type: 'select', required: true, options: canonicalStrategyOptions },
1059
+ { key: 'priority', label: ptEn('Prioridade', 'Priority'), type: 'number' },
1060
+ { key: 'min_product_count', label: ptEn('Mínimo de produtos', 'Minimum products'), type: 'number' },
1061
+ { key: 'min_attribute_coverage', label: ptEn('Cobertura mínima de atributos', 'Minimum attribute coverage'), type: 'number' },
1062
+ ],
1063
+ },
1064
+ {
1065
+ title: ptEn('Regras dinâmicas', 'Dynamic rules'),
1066
+ fields: [
1067
+ { key: 'generation_query_json', label: ptEn('Consulta geradora', 'Generation query'), type: 'json', span: 2 },
1068
+ { key: 'template_json', label: ptEn('Template de SEO', 'SEO template'), type: 'json', span: 2 },
1069
+ ],
1070
+ },
1071
+ ],
1072
+ },
1073
+ {
1074
+ resource: 'import-sources',
1075
+ translationKey: 'importSources',
1076
+ singularLabel: ptEn('Fonte de importação', 'Import source'),
1077
+ createActionLabel: ptEn('Nova Fonte de Importação', 'New Import Source'),
1078
+ editActionLabel: ptEn('Editar Fonte de Importação', 'Edit Import Source'),
1079
+ icon: PackagePlus,
1080
+ href: '/catalog/import-sources',
1081
+ colorClass: 'from-stone-500/20 via-neutral-500/10 to-transparent',
1082
+ glowClass: 'bg-stone-500/10 text-stone-700',
1083
+ featured: true,
1084
+ hasActiveStats: true,
1085
+ listVariant: 'cards',
1086
+ primaryFilterField: 'status',
1087
+ primaryFilterOptions: statusFilterOptions,
1088
+ titleFields: ['name', 'slug'],
1089
+ descriptionFields: ['slug', 'source_type'],
1090
+ badgeFields: ['status', 'source_type'],
1091
+ cardMetadata: [
1092
+ { key: 'slug', labelKey: 'slug' },
1093
+ { key: 'source_type', labelKey: 'sourceType' },
1094
+ { key: 'status', labelKey: 'status' },
1095
+ ],
1096
+ contextualKpi: {
1097
+ translationKey: 'visible',
1098
+ icon: Eye,
1099
+ count: (records) => records.length,
1100
+ },
1101
+ template: {
1102
+ slug: '',
1103
+ name: '',
1104
+ source_type: 'api',
1105
+ config_json: {},
1106
+ status: 'active',
1107
+ },
1108
+ formSections: [
1109
+ {
1110
+ title: ptEn('Origem dos dados', 'Data source'),
1111
+ fields: [
1112
+ { key: 'name', label: ptEn('Nome da fonte', 'Source name'), type: 'text', required: true, placeholder: ptEn('Ex.: API principal do parceiro', 'Ex.: Primary partner API') },
1113
+ { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true, placeholder: ptEn('Ex.: api-parceiro-principal', 'Ex.: primary-partner-api') },
1114
+ { key: 'source_type', label: ptEn('Tipo de fonte', 'Source type'), type: 'select', required: true, options: sourceTypeOptions },
1115
+ { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: statusOptions },
1116
+ { key: 'config_json', label: ptEn('Configuração', 'Configuration'), type: 'json', span: 2 },
1117
+ ],
1118
+ },
1119
+ ],
1120
+ },
1121
+ ];
1122
+
1123
+ export const catalogResourceMap = new Map(
1124
+ catalogResources.map((resource) => [resource.resource, resource])
1125
+ );
1126
+
1127
+ export const catalogDashboardHref = '/catalog/dashboard';
1128
+
1129
+ export const catalogQuickActionResources = catalogResources.filter(
1130
+ (resource) => resource.featured
1131
+ );
1132
+
1133
+ export const catalogKpiResources = ['products', 'brands', 'offers', 'merchants'];
1134
+
1135
+ export function getCatalogRecordLabel(record: Record<string, unknown>) {
1136
+ return (
1137
+ record.name ||
1138
+ record.title ||
1139
+ record.label ||
1140
+ record.slug ||
1141
+ record.rule_slug ||
1142
+ record.external_offer_id ||
1143
+ `#${record.id}`
1144
+ );
1145
+ }
1146
+
1147
+ export function getCatalogLocalizedText(
1148
+ value: CatalogLocalizedText,
1149
+ localeCode?: string | null
1150
+ ) {
1151
+ return localeCode?.toLowerCase().startsWith('pt') ? value.pt : value.en;
1152
+ }
1153
+
1154
+ export const catalogModuleIcon = Boxes;