@hed-hog/catalog 0.0.294 → 0.0.295

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.
Files changed (55) hide show
  1. package/README.md +409 -409
  2. package/dist/catalog.service.d.ts.map +1 -1
  3. package/dist/catalog.service.js +14 -6
  4. package/dist/catalog.service.js.map +1 -1
  5. package/hedhog/data/catalog_attribute.yaml +202 -202
  6. package/hedhog/data/catalog_attribute_option.yaml +109 -109
  7. package/hedhog/data/catalog_category.yaml +47 -47
  8. package/hedhog/data/catalog_category_attribute.yaml +209 -209
  9. package/hedhog/data/menu.yaml +133 -133
  10. package/hedhog/data/role.yaml +7 -7
  11. package/hedhog/data/route.yaml +72 -72
  12. package/hedhog/frontend/app/[resource]/page.tsx.ejs +391 -391
  13. package/hedhog/frontend/app/_components/catalog-ai-form-assist-dialog.tsx.ejs +340 -340
  14. package/hedhog/frontend/app/_components/catalog-resource-form-sheet.tsx.ejs +907 -907
  15. package/hedhog/frontend/app/_lib/catalog-resources.tsx.ejs +929 -929
  16. package/hedhog/frontend/app/dashboard/page.tsx.ejs +14 -83
  17. package/hedhog/frontend/messages/en.json +389 -389
  18. package/hedhog/frontend/messages/pt.json +389 -389
  19. package/hedhog/table/catalog_affiliate_program.yaml +41 -41
  20. package/hedhog/table/catalog_attribute.yaml +67 -67
  21. package/hedhog/table/catalog_attribute_group.yaml +18 -18
  22. package/hedhog/table/catalog_attribute_option.yaml +40 -40
  23. package/hedhog/table/catalog_brand.yaml +34 -34
  24. package/hedhog/table/catalog_category.yaml +40 -40
  25. package/hedhog/table/catalog_category_attribute.yaml +37 -37
  26. package/hedhog/table/catalog_click_event.yaml +50 -50
  27. package/hedhog/table/catalog_comparison.yaml +19 -19
  28. package/hedhog/table/catalog_comparison_highlight.yaml +39 -39
  29. package/hedhog/table/catalog_comparison_item.yaml +30 -30
  30. package/hedhog/table/catalog_content_relation.yaml +42 -42
  31. package/hedhog/table/catalog_import_run.yaml +33 -33
  32. package/hedhog/table/catalog_import_source.yaml +24 -24
  33. package/hedhog/table/catalog_merchant.yaml +29 -29
  34. package/hedhog/table/catalog_offer.yaml +83 -83
  35. package/hedhog/table/catalog_price_history.yaml +34 -34
  36. package/hedhog/table/catalog_product.yaml +30 -30
  37. package/hedhog/table/catalog_product_attribute_value.yaml +44 -44
  38. package/hedhog/table/catalog_product_category.yaml +13 -13
  39. package/hedhog/table/catalog_product_image.yaml +34 -34
  40. package/hedhog/table/catalog_product_score.yaml +38 -38
  41. package/hedhog/table/catalog_product_site.yaml +47 -47
  42. package/hedhog/table/catalog_product_tag.yaml +19 -19
  43. package/hedhog/table/catalog_score_criterion.yaml +42 -42
  44. package/hedhog/table/catalog_seo_page_rule.yaml +10 -10
  45. package/hedhog/table/catalog_similarity_rule.yaml +33 -33
  46. package/hedhog/table/catalog_site.yaml +21 -21
  47. package/hedhog/table/catalog_site_category.yaml +12 -12
  48. package/package.json +6 -6
  49. package/src/catalog-resource.config.ts +132 -132
  50. package/src/catalog.controller.ts +91 -91
  51. package/src/catalog.module.ts +16 -16
  52. package/src/catalog.service.ts +1585 -1573
  53. package/src/index.ts +1 -1
  54. package/src/language/en.json +4 -4
  55. package/src/language/pt.json +4 -4
@@ -1,929 +1,929 @@
1
- import type { LucideIcon } from 'lucide-react';
2
- import {
3
- BadgePercent,
4
- Boxes,
5
- Building2,
6
- Eye,
7
- Factory,
8
- FolderTree,
9
- Globe2,
10
- Layers3,
11
- List,
12
- ListFilter,
13
- PackagePlus,
14
- PackageSearch,
15
- Scale,
16
- SearchCode,
17
- ShoppingCart,
18
- Store,
19
- Tags,
20
- } from 'lucide-react';
21
-
22
- type CatalogListVariant = 'cards' | 'table';
23
- type CatalogFieldDisplayType = 'text' | 'boolean' | 'currency';
24
- type CatalogFormFieldType =
25
- | 'text'
26
- | 'url'
27
- | 'textarea'
28
- | 'richtext'
29
- | 'number'
30
- | 'currency'
31
- | 'switch'
32
- | 'select'
33
- | 'relation'
34
- | 'upload'
35
- | 'json'
36
- | 'date'
37
- | 'datetime';
38
-
39
- export type CatalogLocalizedText = { pt: string; en: string };
40
- export type CatalogFieldDefinition = { key: string; labelKey: string; type?: CatalogFieldDisplayType };
41
- export type CatalogFilterOptionDefinition = { value: string; labelKey: string };
42
- export type CatalogContextualKpiDefinition = {
43
- translationKey: string;
44
- icon: LucideIcon;
45
- count: (records: Record<string, unknown>[]) => number;
46
- };
47
- export type CatalogFormOptionDefinition = { value: string; label: CatalogLocalizedText };
48
- export type CatalogRelationDefinition = {
49
- endpoint: string;
50
- resource?: string;
51
- createResource?: string;
52
- allowCreate?: boolean;
53
- valueKey?: string;
54
- labelKeys: string[];
55
- searchParam?: string;
56
- };
57
- export type CatalogFormFieldDefinition = {
58
- key: string;
59
- label: CatalogLocalizedText;
60
- type: CatalogFormFieldType;
61
- required?: boolean;
62
- span?: 1 | 2;
63
- placeholder?: CatalogLocalizedText;
64
- options?: CatalogFormOptionDefinition[];
65
- relation?: CatalogRelationDefinition;
66
- uploadDestination?: string;
67
- accept?: string;
68
- uploadPreviewVariant?: 'default' | 'square';
69
- };
70
- export type CatalogFormSectionDefinition = {
71
- title: CatalogLocalizedText;
72
- description?: CatalogLocalizedText;
73
- fields: CatalogFormFieldDefinition[];
74
- };
75
- export type CatalogResourceDefinition = {
76
- resource: string;
77
- translationKey: string;
78
- singularLabel: CatalogLocalizedText;
79
- createActionLabel: CatalogLocalizedText;
80
- editActionLabel: CatalogLocalizedText;
81
- icon: LucideIcon;
82
- href: string;
83
- colorClass: string;
84
- glowClass: string;
85
- featured?: boolean;
86
- hasActiveStats?: boolean;
87
- template: Record<string, unknown>;
88
- listVariant: CatalogListVariant;
89
- primaryFilterField: string;
90
- primaryFilterOptions: CatalogFilterOptionDefinition[];
91
- titleFields: string[];
92
- descriptionFields: string[];
93
- badgeFields: string[];
94
- cardMetadata: CatalogFieldDefinition[];
95
- tableColumns?: CatalogFieldDefinition[];
96
- contextualKpi: CatalogContextualKpiDefinition;
97
- formSections: CatalogFormSectionDefinition[];
98
- };
99
-
100
- const ptEn = (pt: string, en: string): CatalogLocalizedText => ({ pt, en });
101
- const relation = (endpoint: string, labelKeys: string[], options?: Partial<CatalogRelationDefinition>): CatalogRelationDefinition => ({
102
- endpoint,
103
- labelKeys,
104
- searchParam: 'search',
105
- ...options,
106
- });
107
- const fileField = (key: string, label: CatalogLocalizedText): CatalogFormFieldDefinition => ({
108
- key,
109
- label,
110
- type: 'upload',
111
- uploadDestination: `catalog/${key.replace(/_id$/, '')}`,
112
- accept: 'image/*',
113
- span: 2,
114
- uploadPreviewVariant: 'square',
115
- });
116
-
117
- const activeInactiveStatusFilterOptions = [
118
- { value: 'active', labelKey: 'active' },
119
- { value: 'inactive', labelKey: 'inactive' },
120
- ];
121
- const publicationStatusFilterOptions = [
122
- { value: 'draft', labelKey: 'draft' },
123
- { value: 'published', labelKey: 'published' },
124
- { value: 'archived', labelKey: 'archived' },
125
- ];
126
- const availabilityFilterOptions = [
127
- { value: 'in_stock', labelKey: 'inStock' },
128
- { value: 'out_of_stock', labelKey: 'outOfStock' },
129
- { value: 'pre_order', labelKey: 'preOrder' },
130
- { value: 'unknown', labelKey: 'unknown' },
131
- ];
132
- const attributeTypeFilterOptions = [
133
- { value: 'text', labelKey: 'text' },
134
- { value: 'long_text', labelKey: 'longText' },
135
- { value: 'number', labelKey: 'number' },
136
- { value: 'boolean', labelKey: 'boolean' },
137
- { value: 'option', labelKey: 'option' },
138
- ];
139
- const facetModeFilterOptions = [
140
- { value: 'default', labelKey: 'default' },
141
- { value: 'range', labelKey: 'range' },
142
- { value: 'select', labelKey: 'select' },
143
- { value: 'hidden', labelKey: 'hidden' },
144
- ];
145
-
146
- const activeInactiveStatusOptions = [
147
- { value: 'active', label: ptEn('Ativo', 'Active') },
148
- { value: 'inactive', label: ptEn('Inativo', 'Inactive') },
149
- ];
150
- const publicationStatusOptions = [
151
- { value: 'draft', label: ptEn('Rascunho', 'Draft') },
152
- { value: 'published', label: ptEn('Publicado', 'Published') },
153
- { value: 'archived', label: ptEn('Arquivado', 'Archived') },
154
- ];
155
- const comparisonStatusOptions = [
156
- { value: 'draft', label: ptEn('Rascunho', 'Draft') },
157
- { value: 'ready', label: ptEn('Pronto', 'Ready') },
158
- { value: 'disabled', label: ptEn('Desabilitado', 'Disabled') },
159
- ];
160
- const siteTypeOptions = [
161
- { value: 'portal', label: ptEn('Portal', 'Portal') },
162
- { value: 'niche', label: ptEn('Nicho', 'Niche') },
163
- { value: 'tenant', label: ptEn('Tenant', 'Tenant') },
164
- ];
165
- const attributeTypeOptions = [
166
- { value: 'text', label: ptEn('Texto', 'Text') },
167
- { value: 'long_text', label: ptEn('Texto longo', 'Long text') },
168
- { value: 'number', label: ptEn('Número', 'Number') },
169
- { value: 'boolean', label: ptEn('Booleano', 'Boolean') },
170
- { value: 'option', label: ptEn('Opção', 'Option') },
171
- ];
172
- const comparisonModeOptions = [
173
- { value: 'neutral', label: ptEn('Neutro', 'Neutral') },
174
- { value: 'higher_better', label: ptEn('Maior é melhor', 'Higher is better') },
175
- { value: 'lower_better', label: ptEn('Menor é melhor', 'Lower is better') },
176
- { value: 'boolean_true_better', label: ptEn('Verdadeiro é melhor', 'True is better') },
177
- { value: 'exact_match', label: ptEn('Correspondência exata', 'Exact match') },
178
- ];
179
- const facetModeOptions = [
180
- { value: 'default', label: ptEn('Padrão', 'Default') },
181
- { value: 'range', label: ptEn('Faixa', 'Range') },
182
- { value: 'select', label: ptEn('Seleção', 'Select') },
183
- { value: 'hidden', label: ptEn('Oculto', 'Hidden') },
184
- ];
185
- const comparisonTypeOptions = [
186
- { value: 'manual', label: ptEn('Manual', 'Manual') },
187
- { value: 'automatic', label: ptEn('Automática', 'Automatic') },
188
- { value: 'similarity', label: ptEn('Similaridade', 'Similarity') },
189
- { value: 'compatibility', label: ptEn('Compatibilidade', 'Compatibility') },
190
- ];
191
- const generationModeOptions = [
192
- { value: 'manual', label: ptEn('Manual', 'Manual') },
193
- { value: 'automatic', label: ptEn('Automática', 'Automatic') },
194
- ];
195
- const availabilityOptions = [
196
- { value: 'in_stock', label: ptEn('Em estoque', 'In stock') },
197
- { value: 'out_of_stock', label: ptEn('Sem estoque', 'Out of stock') },
198
- { value: 'pre_order', label: ptEn('Pré-venda', 'Pre-order') },
199
- { value: 'unknown', label: ptEn('Desconhecido', 'Unknown') },
200
- ];
201
- const merchantTypeOptions = [
202
- { value: 'retailer', label: ptEn('Varejista', 'Retailer') },
203
- { value: 'marketplace', label: ptEn('Marketplace', 'Marketplace') },
204
- { value: 'saas', label: ptEn('SaaS', 'SaaS') },
205
- { value: 'direct', label: ptEn('Direto', 'Direct') },
206
- ];
207
- const networkTypeOptions = [
208
- { value: 'amazon', label: ptEn('Amazon', 'Amazon') },
209
- { value: 'mercado_livre', label: ptEn('Mercado Livre', 'Mercado Livre') },
210
- { value: 'aliexpress', label: ptEn('AliExpress', 'AliExpress') },
211
- { value: 'kabum', label: ptEn('KaBuM!', 'KaBuM!') },
212
- { value: 'direct', label: ptEn('Direto', 'Direct') },
213
- { value: 'network', label: ptEn('Rede', 'Network') },
214
- ];
215
- const commissionTypeOptions = [
216
- { value: 'percentage', label: ptEn('Percentual', 'Percentage') },
217
- { value: 'fixed', label: ptEn('Valor fixo', 'Fixed') },
218
- ];
219
- const pageTypeOptions = [
220
- { value: 'comparison', label: ptEn('Comparação', 'Comparison') },
221
- { value: 'best_of', label: ptEn('Melhores', 'Best of') },
222
- { value: 'cost_benefit', label: ptEn('Custo-benefício', 'Cost benefit') },
223
- { value: 'attribute', label: ptEn('Atributo', 'Attribute') },
224
- { value: 'price_range', label: ptEn('Faixa de preço', 'Price range') },
225
- { value: 'brand', label: ptEn('Marca', 'Brand') },
226
- { value: 'use_case', label: ptEn('Caso de uso', 'Use case') },
227
- ];
228
- const canonicalStrategyOptions = [
229
- { value: 'self', label: ptEn('Canônica própria', 'Self canonical') },
230
- { value: 'parent', label: ptEn('Canônica pai', 'Parent canonical') },
231
- { value: 'custom', label: ptEn('Canônica customizada', 'Custom canonical') },
232
- ];
233
- const sourceTypeOptions = [
234
- { value: 'api', label: ptEn('API', 'API') },
235
- { value: 'feed', label: ptEn('Feed', 'Feed') },
236
- { value: 'file', label: ptEn('Arquivo', 'File') },
237
- { value: 'crawler', label: ptEn('Crawler', 'Crawler') },
238
- ];
239
- const currencyOptions = [
240
- { value: 'BRL', label: ptEn('Real brasileiro (BRL)', 'Brazilian real (BRL)') },
241
- { value: 'USD', label: ptEn('Dólar americano (USD)', 'US dollar (USD)') },
242
- { value: 'EUR', label: ptEn('Euro (EUR)', 'Euro (EUR)') },
243
- ];
244
-
245
- export const catalogResources: CatalogResourceDefinition[] = [];
246
-
247
- catalogResources.push(
248
- {
249
- resource: 'categories',
250
- translationKey: 'categories',
251
- singularLabel: ptEn('Categoria', 'Category'),
252
- createActionLabel: ptEn('Nova Categoria', 'New Category'),
253
- editActionLabel: ptEn('Editar Categoria', 'Edit Category'),
254
- icon: FolderTree,
255
- href: '/catalog/categories',
256
- colorClass: 'from-amber-500/20 via-orange-500/10 to-transparent',
257
- glowClass: 'bg-amber-500/10 text-amber-700',
258
- featured: true,
259
- hasActiveStats: true,
260
- listVariant: 'table',
261
- primaryFilterField: 'status',
262
- primaryFilterOptions: activeInactiveStatusFilterOptions,
263
- titleFields: ['name', 'slug'],
264
- descriptionFields: ['normalized_name', 'description'],
265
- badgeFields: ['status'],
266
- cardMetadata: [{ key: 'parent_category_id', labelKey: 'parentCategoryId' }, { key: 'comparison_enabled', labelKey: 'comparisonEnabled', type: 'boolean' }],
267
- tableColumns: [
268
- { key: 'id', labelKey: 'id' },
269
- { key: 'name', labelKey: 'name' },
270
- { key: 'slug', labelKey: 'slug' },
271
- { key: 'parent_category_id', labelKey: 'parentCategoryId' },
272
- { key: 'comparison_enabled', labelKey: 'comparisonEnabled', type: 'boolean' },
273
- { key: 'sort_order', labelKey: 'sortOrder' },
274
- { key: 'status', labelKey: 'status' },
275
- ],
276
- contextualKpi: { translationKey: 'visible', icon: Eye, count: (records) => records.length },
277
- template: { parent_category_id: null, slug: '', name: '', normalized_name: '', description: '', comparison_enabled: true, status: 'active', sort_order: 0 },
278
- formSections: [{
279
- title: ptEn('Estrutura', 'Structure'),
280
- fields: [
281
- { key: 'name', label: ptEn('Nome', 'Name'), type: 'text', required: true },
282
- { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true },
283
- { key: 'normalized_name', label: ptEn('Nome normalizado', 'Normalized name'), type: 'text' },
284
- { key: 'parent_category_id', label: ptEn('Categoria pai', 'Parent category'), type: 'relation', relation: relation('/catalog/categories', ['name', 'slug'], { resource: 'categories' }) },
285
- { key: 'comparison_enabled', label: ptEn('Permite comparação', 'Comparison enabled'), type: 'switch' },
286
- { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: activeInactiveStatusOptions },
287
- { key: 'sort_order', label: ptEn('Ordem', 'Sort order'), type: 'number' },
288
- { key: 'description', label: ptEn('Descrição', 'Description'), type: 'textarea', span: 2 },
289
- ],
290
- }],
291
- },
292
- {
293
- resource: 'brands',
294
- translationKey: 'brands',
295
- singularLabel: ptEn('Marca', 'Brand'),
296
- createActionLabel: ptEn('Nova Marca', 'New Brand'),
297
- editActionLabel: ptEn('Editar Marca', 'Edit Brand'),
298
- icon: Factory,
299
- href: '/catalog/brands',
300
- colorClass: 'from-orange-500/20 via-amber-500/10 to-transparent',
301
- glowClass: 'bg-orange-500/10 text-orange-700',
302
- featured: true,
303
- hasActiveStats: true,
304
- listVariant: 'cards',
305
- primaryFilterField: 'status',
306
- primaryFilterOptions: activeInactiveStatusFilterOptions,
307
- titleFields: ['name', 'slug'],
308
- descriptionFields: ['normalized_name', 'website_url'],
309
- badgeFields: ['status'],
310
- cardMetadata: [
311
- { key: 'slug', labelKey: 'slug' },
312
- { key: 'normalized_name', labelKey: 'normalizedName' },
313
- { key: 'website_url', labelKey: 'websiteUrl' },
314
- ],
315
- contextualKpi: { translationKey: 'visible', icon: Eye, count: (records) => records.length },
316
- template: { slug: '', name: '', normalized_name: '', logo_file_id: null, status: 'active', website_url: '' },
317
- formSections: [{
318
- title: ptEn('Identificação', 'Identity'),
319
- fields: [
320
- { key: 'name', label: ptEn('Nome da marca', 'Brand name'), type: 'text', required: true },
321
- { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true },
322
- { key: 'normalized_name', label: ptEn('Nome normalizado', 'Normalized name'), type: 'text' },
323
- { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: activeInactiveStatusOptions },
324
- { key: 'website_url', label: ptEn('Site oficial', 'Official website'), type: 'url', span: 2 },
325
- fileField('logo_file_id', ptEn('Logo', 'Logo')),
326
- ],
327
- }],
328
- },
329
- {
330
- resource: 'sites',
331
- translationKey: 'sites',
332
- singularLabel: ptEn('Site', 'Site'),
333
- createActionLabel: ptEn('Novo Site', 'New Site'),
334
- editActionLabel: ptEn('Editar Site', 'Edit Site'),
335
- icon: Globe2,
336
- href: '/catalog/sites',
337
- colorClass: 'from-sky-500/20 via-cyan-500/10 to-transparent',
338
- glowClass: 'bg-sky-500/10 text-sky-700',
339
- featured: true,
340
- hasActiveStats: true,
341
- listVariant: 'cards',
342
- primaryFilterField: 'status',
343
- primaryFilterOptions: activeInactiveStatusFilterOptions,
344
- titleFields: ['name', 'domain', 'slug'],
345
- descriptionFields: ['domain', 'slug'],
346
- badgeFields: ['status', 'site_type'],
347
- cardMetadata: [
348
- { key: 'slug', labelKey: 'slug' },
349
- { key: 'domain', labelKey: 'domain' },
350
- { key: 'default_locale_id', labelKey: 'defaultLocaleId' },
351
- ],
352
- contextualKpi: { translationKey: 'visible', icon: Eye, count: (records) => records.length },
353
- template: { slug: '', name: '', domain: '', status: 'active', site_type: 'portal', default_locale_id: null, logo_file_id: null, theme_settings_json: {}, seo_defaults_json: {} },
354
- formSections: [
355
- {
356
- title: ptEn('Dados principais', 'Main details'),
357
- fields: [
358
- { key: 'name', label: ptEn('Nome do site', 'Site name'), type: 'text', required: true },
359
- { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true },
360
- { key: 'domain', label: ptEn('Domínio', 'Domain'), type: 'url', required: true },
361
- { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: activeInactiveStatusOptions },
362
- { key: 'site_type', label: ptEn('Tipo de site', 'Site type'), type: 'select', required: true, options: siteTypeOptions },
363
- { key: 'default_locale_id', label: ptEn('Locale padrão', 'Default locale'), type: 'relation', required: true, relation: relation('/locale', ['name', 'code']) },
364
- fileField('logo_file_id', ptEn('Logo', 'Logo')),
365
- ],
366
- },
367
- {
368
- title: ptEn('Configurações', 'Settings'),
369
- fields: [
370
- { key: 'theme_settings_json', label: ptEn('Tema e identidade', 'Theme and identity'), type: 'json', span: 2 },
371
- { key: 'seo_defaults_json', label: ptEn('Padrões de SEO', 'SEO defaults'), type: 'json', span: 2 },
372
- ],
373
- },
374
- ],
375
- },
376
- {
377
- resource: 'products',
378
- translationKey: 'products',
379
- singularLabel: ptEn('Produto', 'Product'),
380
- createActionLabel: ptEn('Novo Produto', 'New Product'),
381
- editActionLabel: ptEn('Editar Produto', 'Edit Product'),
382
- icon: PackageSearch,
383
- href: '/catalog/products',
384
- colorClass: 'from-emerald-500/20 via-green-500/10 to-transparent',
385
- glowClass: 'bg-emerald-500/10 text-emerald-700',
386
- featured: true,
387
- hasActiveStats: true,
388
- listVariant: 'table',
389
- primaryFilterField: 'status',
390
- primaryFilterOptions: publicationStatusFilterOptions,
391
- titleFields: ['name', 'model_name', 'slug'],
392
- descriptionFields: ['model_name', 'sku', 'slug'],
393
- badgeFields: ['status', 'comparison_status', 'is_active'],
394
- cardMetadata: [
395
- { key: 'slug', labelKey: 'slug' },
396
- { key: 'sku', labelKey: 'sku' },
397
- { key: 'brand_name', labelKey: 'brandId' },
398
- { key: 'category_name', labelKey: 'catalogCategoryId' },
399
- ],
400
- tableColumns: [
401
- { key: 'name', labelKey: 'name' },
402
- { key: 'brand_name', labelKey: 'brandId' },
403
- { key: 'category_name', labelKey: 'catalogCategoryId' },
404
- { key: 'status', labelKey: 'status' },
405
- { key: 'is_active', labelKey: 'isActive', type: 'boolean' },
406
- ],
407
- contextualKpi: { translationKey: 'activeInSlice', icon: PackageSearch, count: (records) => records.filter((record) => record.is_active === true).length },
408
- template: { brand_id: null, catalog_category_id: null, primary_content_id: null, slug: '', name: '', short_description: '', description: '', model_name: '', sku: '', gtin: '', status: 'draft', comparison_status: 'draft', release_date: '', spec_snapshot_json: {}, comparison_snapshot_json: {}, is_active: true },
409
- formSections: [
410
- {
411
- title: ptEn('Base do produto', 'Product basics'),
412
- fields: [
413
- { key: 'name', label: ptEn('Nome do produto', 'Product name'), type: 'text', required: true },
414
- { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true },
415
- { key: 'brand_id', label: ptEn('Marca', 'Brand'), type: 'relation', required: true, relation: relation('/catalog/brands', ['name', 'slug'], { resource: 'brands', createResource: 'brands', allowCreate: true }) },
416
- { key: 'catalog_category_id', label: ptEn('Categoria', 'Category'), type: 'relation', required: true, relation: relation('/catalog/categories', ['name', 'slug'], { resource: 'categories', createResource: 'categories', allowCreate: true }) },
417
- { key: 'primary_content_id', label: ptEn('Conteúdo principal', 'Primary content'), type: 'relation', relation: relation('/content', ['title', 'slug']) },
418
- { key: 'model_name', label: ptEn('Modelo', 'Model'), type: 'text' },
419
- { key: 'sku', label: ptEn('SKU', 'SKU'), type: 'text' },
420
- { key: 'gtin', label: ptEn('GTIN', 'GTIN'), type: 'text' },
421
- { key: 'release_date', label: ptEn('Data de lançamento', 'Release date'), type: 'date' },
422
- { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: publicationStatusOptions },
423
- { key: 'comparison_status', label: ptEn('Status de comparação', 'Comparison status'), type: 'select', required: true, options: comparisonStatusOptions },
424
- { key: 'is_active', label: ptEn('Ativo para comparação', 'Active for comparison'), type: 'switch' },
425
- ],
426
- },
427
- {
428
- title: ptEn('Conteúdo', 'Content'),
429
- fields: [
430
- { key: 'short_description', label: ptEn('Resumo curto', 'Short description'), type: 'richtext', span: 2 },
431
- { key: 'description', label: ptEn('Descrição completa', 'Full description'), type: 'richtext', span: 2 },
432
- ],
433
- },
434
- {
435
- title: ptEn('Snapshots', 'Snapshots'),
436
- description: ptEn('Campos secundários para cache, leitura legada e payloads desnormalizados.', 'Secondary fields for cache, legacy reads, and denormalized payloads.'),
437
- fields: [
438
- { key: 'spec_snapshot_json', label: ptEn('Snapshot de especificações', 'Specification snapshot'), type: 'json', span: 2 },
439
- { key: 'comparison_snapshot_json', label: ptEn('Snapshot de comparação', 'Comparison snapshot'), type: 'json', span: 2 },
440
- ],
441
- },
442
- ],
443
- }
444
- );
445
-
446
- catalogResources.push(
447
- {
448
- resource: 'comparisons',
449
- translationKey: 'comparisons',
450
- singularLabel: ptEn('Comparação', 'Comparison'),
451
- createActionLabel: ptEn('Nova Comparação', 'New Comparison'),
452
- editActionLabel: ptEn('Editar Comparação', 'Edit Comparison'),
453
- icon: Scale,
454
- href: '/catalog/comparisons',
455
- colorClass: 'from-amber-500/20 via-yellow-500/10 to-transparent',
456
- glowClass: 'bg-amber-500/10 text-amber-700',
457
- hasActiveStats: true,
458
- listVariant: 'cards',
459
- primaryFilterField: 'status',
460
- primaryFilterOptions: publicationStatusFilterOptions,
461
- titleFields: ['title', 'slug'],
462
- descriptionFields: ['summary', 'slug'],
463
- badgeFields: ['status', 'comparison_type', 'generation_mode'],
464
- cardMetadata: [{ key: 'slug', labelKey: 'slug' }, { key: 'site_id', labelKey: 'siteId' }, { key: 'catalog_category_id', labelKey: 'catalogCategoryId' }],
465
- contextualKpi: { translationKey: 'publishedInSlice', icon: Scale, count: (records) => records.filter((record) => String(record.status ?? '') === 'published').length },
466
- template: { catalog_category_id: null, site_id: null, primary_content_id: null, slug: '', comparison_type: 'manual', generation_mode: 'manual', status: 'draft', title: '', summary: '', intro: '', verdict: '', eligibility_hash: '', published_at: '' },
467
- formSections: [
468
- {
469
- title: ptEn('Base da comparação', 'Comparison basics'),
470
- fields: [
471
- { key: 'title', label: ptEn('Título', 'Title'), type: 'text', required: true },
472
- { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true },
473
- { key: 'site_id', label: ptEn('Site', 'Site'), type: 'relation', required: true, relation: relation('/catalog/sites', ['name', 'domain', 'slug'], { resource: 'sites', createResource: 'sites', allowCreate: true }) },
474
- { key: 'catalog_category_id', label: ptEn('Categoria', 'Category'), type: 'relation', required: true, relation: relation('/catalog/categories', ['name', 'slug'], { resource: 'categories' }) },
475
- { key: 'primary_content_id', label: ptEn('Conteúdo principal', 'Primary content'), type: 'relation', relation: relation('/content', ['title', 'slug']) },
476
- { key: 'comparison_type', label: ptEn('Tipo de comparação', 'Comparison type'), type: 'select', required: true, options: comparisonTypeOptions },
477
- { key: 'generation_mode', label: ptEn('Modo de geração', 'Generation mode'), type: 'select', required: true, options: generationModeOptions },
478
- { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: publicationStatusOptions },
479
- { key: 'published_at', label: ptEn('Publicado em', 'Published at'), type: 'datetime' },
480
- ],
481
- },
482
- {
483
- title: ptEn('Conteúdo editorial', 'Editorial content'),
484
- fields: [
485
- { key: 'summary', label: ptEn('Resumo', 'Summary'), type: 'richtext', span: 2 },
486
- { key: 'intro', label: ptEn('Introdução', 'Introduction'), type: 'richtext', span: 2 },
487
- { key: 'verdict', label: ptEn('Veredito', 'Verdict'), type: 'richtext', span: 2 },
488
- { key: 'eligibility_hash', label: ptEn('Hash de elegibilidade', 'Eligibility hash'), type: 'text', span: 2 },
489
- ],
490
- },
491
- ],
492
- },
493
- {
494
- resource: 'offers',
495
- translationKey: 'offers',
496
- singularLabel: ptEn('Oferta', 'Offer'),
497
- createActionLabel: ptEn('Nova Oferta', 'New Offer'),
498
- editActionLabel: ptEn('Editar Oferta', 'Edit Offer'),
499
- icon: ShoppingCart,
500
- href: '/catalog/offers',
501
- colorClass: 'from-red-500/20 via-orange-500/10 to-transparent',
502
- glowClass: 'bg-red-500/10 text-red-700',
503
- featured: true,
504
- listVariant: 'cards',
505
- primaryFilterField: 'availability_status',
506
- primaryFilterOptions: availabilityFilterOptions,
507
- titleFields: ['title', 'external_offer_id'],
508
- descriptionFields: ['external_offer_id', 'affiliate_url'],
509
- badgeFields: ['availability_status', 'is_featured'],
510
- cardMetadata: [{ key: 'price_amount', labelKey: 'priceAmount', type: 'currency' }, { key: 'merchant_id', labelKey: 'merchantId' }, { key: 'product_id', labelKey: 'productId' }],
511
- contextualKpi: { translationKey: 'inStockInSlice', icon: ShoppingCart, count: (records) => records.filter((record) => record.availability_status === 'in_stock').length },
512
- template: { product_id: null, merchant_id: null, affiliate_program_id: null, site_id: null, external_offer_id: '', title: '', price_amount: 0, price_currency: 'BRL', original_price_amount: null, installment_json: {}, availability_status: 'in_stock', affiliate_url: '', deep_link_url: '', priority_score: 0, is_featured: false, valid_from: '', valid_until: '', last_seen_at: '' },
513
- formSections: [
514
- {
515
- title: ptEn('Oferta comercial', 'Commercial offer'),
516
- fields: [
517
- { key: 'title', label: ptEn('Título da oferta', 'Offer title'), type: 'text', required: true, span: 2 },
518
- { key: 'product_id', label: ptEn('Produto', 'Product'), type: 'relation', required: true, relation: relation('/catalog/products', ['name', 'model_name', 'slug'], { resource: 'products' }) },
519
- { key: 'merchant_id', label: ptEn('Lojista', 'Merchant'), type: 'relation', required: true, relation: relation('/catalog/merchants', ['name', 'slug'], { resource: 'merchants', createResource: 'merchants', allowCreate: true }) },
520
- { key: 'affiliate_program_id', label: ptEn('Programa de afiliação', 'Affiliate program'), type: 'relation', relation: relation('/catalog/affiliate-programs', ['name', 'slug'], { resource: 'affiliate-programs', createResource: 'affiliate-programs', allowCreate: true }) },
521
- { key: 'site_id', label: ptEn('Site', 'Site'), type: 'relation', relation: relation('/catalog/sites', ['name', 'domain', 'slug'], { resource: 'sites' }) },
522
- { key: 'external_offer_id', label: ptEn('ID externo', 'External ID'), type: 'text' },
523
- { key: 'availability_status', label: ptEn('Disponibilidade', 'Availability'), type: 'select', required: true, options: availabilityOptions },
524
- { key: 'price_amount', label: ptEn('Preço', 'Price'), type: 'currency', required: true },
525
- { key: 'original_price_amount', label: ptEn('Preço original', 'Original price'), type: 'currency' },
526
- { key: 'price_currency', label: ptEn('Moeda', 'Currency'), type: 'select', required: true, options: currencyOptions },
527
- { key: 'priority_score', label: ptEn('Prioridade', 'Priority'), type: 'number' },
528
- { key: 'is_featured', label: ptEn('Oferta destacada', 'Featured offer'), type: 'switch' },
529
- ],
530
- },
531
- {
532
- title: ptEn('Links e vigência', 'Links and schedule'),
533
- fields: [
534
- { key: 'affiliate_url', label: ptEn('URL de afiliação', 'Affiliate URL'), type: 'url', span: 2 },
535
- { key: 'deep_link_url', label: ptEn('Deep link', 'Deep link'), type: 'url', span: 2 },
536
- { key: 'valid_from', label: ptEn('Válida a partir de', 'Valid from'), type: 'datetime' },
537
- { key: 'valid_until', label: ptEn('Válida até', 'Valid until'), type: 'datetime' },
538
- { key: 'last_seen_at', label: ptEn('Última captura', 'Last seen at'), type: 'datetime' },
539
- { key: 'installment_json', label: ptEn('Parcelamento', 'Installments'), type: 'json', span: 2 },
540
- ],
541
- },
542
- ],
543
- },
544
- {
545
- resource: 'merchants',
546
- translationKey: 'merchants',
547
- singularLabel: ptEn('Lojista', 'Merchant'),
548
- createActionLabel: ptEn('Novo Lojista', 'New Merchant'),
549
- editActionLabel: ptEn('Editar Lojista', 'Edit Merchant'),
550
- icon: Store,
551
- href: '/catalog/merchants',
552
- colorClass: 'from-lime-500/20 via-green-500/10 to-transparent',
553
- glowClass: 'bg-lime-500/10 text-lime-700',
554
- featured: true,
555
- hasActiveStats: true,
556
- listVariant: 'cards',
557
- primaryFilterField: 'status',
558
- primaryFilterOptions: activeInactiveStatusFilterOptions,
559
- titleFields: ['name', 'slug'],
560
- descriptionFields: ['slug'],
561
- badgeFields: ['status', 'merchant_type'],
562
- cardMetadata: [{ key: 'slug', labelKey: 'slug' }, { key: 'merchant_type', labelKey: 'merchantType' }, { key: 'logo_file_id', labelKey: 'logoFileId' }],
563
- contextualKpi: { translationKey: 'visible', icon: Eye, count: (records) => records.length },
564
- template: { slug: '', name: '', status: 'active', merchant_type: 'retailer', logo_file_id: null },
565
- formSections: [{
566
- title: ptEn('Perfil do lojista', 'Merchant profile'),
567
- fields: [
568
- { key: 'name', label: ptEn('Nome do lojista', 'Merchant name'), type: 'text', required: true },
569
- { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true },
570
- { key: 'merchant_type', label: ptEn('Tipo de lojista', 'Merchant type'), type: 'select', required: true, options: merchantTypeOptions },
571
- { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: activeInactiveStatusOptions },
572
- fileField('logo_file_id', ptEn('Logo', 'Logo')),
573
- ],
574
- }],
575
- },
576
- {
577
- resource: 'affiliate-programs',
578
- translationKey: 'affiliatePrograms',
579
- singularLabel: ptEn('Programa de afiliação', 'Affiliate program'),
580
- createActionLabel: ptEn('Novo Programa de Afiliação', 'New Affiliate Program'),
581
- editActionLabel: ptEn('Editar Programa de Afiliação', 'Edit Affiliate Program'),
582
- icon: Building2,
583
- href: '/catalog/affiliate-programs',
584
- colorClass: 'from-cyan-500/20 via-teal-500/10 to-transparent',
585
- glowClass: 'bg-cyan-500/10 text-cyan-700',
586
- hasActiveStats: true,
587
- listVariant: 'cards',
588
- primaryFilterField: 'status',
589
- primaryFilterOptions: activeInactiveStatusFilterOptions,
590
- titleFields: ['name', 'slug'],
591
- descriptionFields: ['slug', 'network_type'],
592
- badgeFields: ['status', 'network_type', 'commission_type'],
593
- cardMetadata: [{ key: 'merchant_id', labelKey: 'merchantId' }, { key: 'commission_type', labelKey: 'commissionType' }, { key: 'default_commission_value', labelKey: 'defaultCommissionValue', type: 'currency' }],
594
- contextualKpi: { translationKey: 'visible', icon: Eye, count: (records) => records.length },
595
- template: { merchant_id: null, slug: '', name: '', network_type: 'direct', tracking_template: '', commission_type: 'percentage', default_commission_value: 0, status: 'active' },
596
- formSections: [{
597
- title: ptEn('Programa', 'Program'),
598
- fields: [
599
- { key: 'name', label: ptEn('Nome do programa', 'Program name'), type: 'text', required: true },
600
- { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true },
601
- { key: 'merchant_id', label: ptEn('Lojista', 'Merchant'), type: 'relation', required: true, relation: relation('/catalog/merchants', ['name', 'slug'], { resource: 'merchants', createResource: 'merchants', allowCreate: true }) },
602
- { key: 'network_type', label: ptEn('Rede', 'Network'), type: 'select', required: true, options: networkTypeOptions },
603
- { key: 'commission_type', label: ptEn('Tipo de comissão', 'Commission type'), type: 'select', required: true, options: commissionTypeOptions },
604
- { key: 'default_commission_value', label: ptEn('Comissão padrão', 'Default commission'), type: 'currency' },
605
- { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: activeInactiveStatusOptions },
606
- { key: 'tracking_template', label: ptEn('Template de tracking', 'Tracking template'), type: 'textarea', span: 2 },
607
- ],
608
- }],
609
- },
610
- {
611
- resource: 'seo-rules',
612
- translationKey: 'seoRules',
613
- singularLabel: ptEn('Regra de SEO', 'SEO rule'),
614
- createActionLabel: ptEn('Nova Regra de SEO', 'New SEO Rule'),
615
- editActionLabel: ptEn('Editar Regra de SEO', 'Edit SEO Rule'),
616
- icon: SearchCode,
617
- href: '/catalog/seo-rules',
618
- colorClass: 'from-slate-500/20 via-zinc-500/10 to-transparent',
619
- glowClass: 'bg-slate-500/10 text-slate-700',
620
- featured: true,
621
- hasActiveStats: true,
622
- listVariant: 'cards',
623
- primaryFilterField: 'status',
624
- primaryFilterOptions: activeInactiveStatusFilterOptions,
625
- titleFields: ['rule_slug', 'page_type'],
626
- descriptionFields: ['page_type', 'canonical_strategy'],
627
- badgeFields: ['status', 'page_type', 'canonical_strategy'],
628
- cardMetadata: [{ key: 'site_id', labelKey: 'siteId' }, { key: 'catalog_category_id', labelKey: 'catalogCategoryId' }, { key: 'priority', labelKey: 'priority' }],
629
- contextualKpi: { translationKey: 'visible', icon: Eye, count: (records) => records.length },
630
- template: { site_id: null, catalog_category_id: null, page_type: 'comparison', rule_slug: '', status: 'active', generation_query_json: {}, min_product_count: 2, min_attribute_coverage: 1, canonical_strategy: 'self', priority: 0, template_json: {} },
631
- formSections: [
632
- {
633
- title: ptEn('Escopo da regra', 'Rule scope'),
634
- fields: [
635
- { key: 'site_id', label: ptEn('Site', 'Site'), type: 'relation', required: true, relation: relation('/catalog/sites', ['name', 'domain', 'slug'], { resource: 'sites' }) },
636
- { key: 'catalog_category_id', label: ptEn('Categoria', 'Category'), type: 'relation', relation: relation('/catalog/categories', ['name', 'slug'], { resource: 'categories' }) },
637
- { key: 'page_type', label: ptEn('Tipo de página', 'Page type'), type: 'select', required: true, options: pageTypeOptions },
638
- { key: 'rule_slug', label: ptEn('Slug da regra', 'Rule slug'), type: 'text', required: true },
639
- { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: activeInactiveStatusOptions },
640
- { key: 'canonical_strategy', label: ptEn('Estratégia canônica', 'Canonical strategy'), type: 'select', required: true, options: canonicalStrategyOptions },
641
- { key: 'priority', label: ptEn('Prioridade', 'Priority'), type: 'number' },
642
- { key: 'min_product_count', label: ptEn('Mínimo de produtos', 'Minimum products'), type: 'number' },
643
- { key: 'min_attribute_coverage', label: ptEn('Cobertura mínima de atributos', 'Minimum attribute coverage'), type: 'number' },
644
- ],
645
- },
646
- {
647
- title: ptEn('Regras dinâmicas', 'Dynamic rules'),
648
- fields: [
649
- { key: 'generation_query_json', label: ptEn('Consulta geradora', 'Generation query'), type: 'json', span: 2 },
650
- { key: 'template_json', label: ptEn('Template de SEO', 'SEO template'), type: 'json', span: 2 },
651
- ],
652
- },
653
- ],
654
- },
655
- {
656
- resource: 'import-sources',
657
- translationKey: 'importSources',
658
- singularLabel: ptEn('Fonte de importação', 'Import source'),
659
- createActionLabel: ptEn('Nova Fonte de Importação', 'New Import Source'),
660
- editActionLabel: ptEn('Editar Fonte de Importação', 'Edit Import Source'),
661
- icon: PackagePlus,
662
- href: '/catalog/import-sources',
663
- colorClass: 'from-stone-500/20 via-neutral-500/10 to-transparent',
664
- glowClass: 'bg-stone-500/10 text-stone-700',
665
- featured: true,
666
- hasActiveStats: true,
667
- listVariant: 'cards',
668
- primaryFilterField: 'status',
669
- primaryFilterOptions: activeInactiveStatusFilterOptions,
670
- titleFields: ['name', 'slug'],
671
- descriptionFields: ['slug', 'source_type'],
672
- badgeFields: ['status', 'source_type'],
673
- cardMetadata: [{ key: 'slug', labelKey: 'slug' }, { key: 'source_type', labelKey: 'sourceType' }, { key: 'status', labelKey: 'status' }],
674
- contextualKpi: { translationKey: 'visible', icon: Eye, count: (records) => records.length },
675
- template: { slug: '', name: '', source_type: 'api', config_json: {}, status: 'active' },
676
- formSections: [{
677
- title: ptEn('Origem dos dados', 'Data source'),
678
- fields: [
679
- { key: 'name', label: ptEn('Nome da fonte', 'Source name'), type: 'text', required: true },
680
- { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true },
681
- { key: 'source_type', label: ptEn('Tipo de fonte', 'Source type'), type: 'select', required: true, options: sourceTypeOptions },
682
- { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: activeInactiveStatusOptions },
683
- { key: 'config_json', label: ptEn('Configuração', 'Configuration'), type: 'json', span: 2 },
684
- ],
685
- }],
686
- }
687
- );
688
-
689
- catalogResources.push(
690
- {
691
- resource: 'attribute-groups',
692
- translationKey: 'attributeGroups',
693
- singularLabel: ptEn('Grupo de atributos', 'Attribute group'),
694
- createActionLabel: ptEn('Novo Grupo de Atributos', 'New Attribute Group'),
695
- editActionLabel: ptEn('Editar Grupo de Atributos', 'Edit Attribute Group'),
696
- icon: Layers3,
697
- href: '/catalog/attribute-groups',
698
- colorClass: 'from-violet-500/20 via-fuchsia-500/10 to-transparent',
699
- glowClass: 'bg-violet-500/10 text-violet-700',
700
- hasActiveStats: true,
701
- listVariant: 'table',
702
- primaryFilterField: 'status',
703
- primaryFilterOptions: activeInactiveStatusFilterOptions,
704
- titleFields: ['name', 'slug'],
705
- descriptionFields: ['slug'],
706
- badgeFields: ['status'],
707
- cardMetadata: [{ key: 'slug', labelKey: 'slug' }, { key: 'status', labelKey: 'status' }],
708
- tableColumns: [
709
- { key: 'id', labelKey: 'id' },
710
- { key: 'name', labelKey: 'name' },
711
- { key: 'slug', labelKey: 'slug' },
712
- { key: 'status', labelKey: 'status' },
713
- ],
714
- contextualKpi: { translationKey: 'visible', icon: Eye, count: (records) => records.length },
715
- template: { slug: '', name: '', status: 'active' },
716
- formSections: [{
717
- title: ptEn('Grupo', 'Group'),
718
- fields: [
719
- { key: 'name', label: ptEn('Nome do grupo', 'Group name'), type: 'text', required: true },
720
- { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true },
721
- { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: activeInactiveStatusOptions },
722
- ],
723
- }],
724
- },
725
- {
726
- resource: 'attributes',
727
- translationKey: 'attributes',
728
- singularLabel: ptEn('Atributo', 'Attribute'),
729
- createActionLabel: ptEn('Novo Atributo', 'New Attribute'),
730
- editActionLabel: ptEn('Editar Atributo', 'Edit Attribute'),
731
- icon: Tags,
732
- href: '/catalog/attributes',
733
- colorClass: 'from-indigo-500/20 via-blue-500/10 to-transparent',
734
- glowClass: 'bg-indigo-500/10 text-indigo-700',
735
- listVariant: 'table',
736
- primaryFilterField: 'data_type',
737
- primaryFilterOptions: attributeTypeFilterOptions,
738
- titleFields: ['name', 'code', 'slug'],
739
- descriptionFields: ['description', 'slug'],
740
- badgeFields: ['data_type', 'comparison_mode', 'status'],
741
- cardMetadata: [
742
- { key: 'group_id', labelKey: 'groupId' },
743
- { key: 'group_name', labelKey: 'groupName' },
744
- { key: 'is_filterable', labelKey: 'isFilterable', type: 'boolean' },
745
- { key: 'is_comparable', labelKey: 'isComparable', type: 'boolean' },
746
- ],
747
- tableColumns: [
748
- { key: 'id', labelKey: 'id' },
749
- { key: 'name', labelKey: 'name' },
750
- { key: 'code', labelKey: 'code' },
751
- { key: 'slug', labelKey: 'slug' },
752
- { key: 'data_type', labelKey: 'dataType' },
753
- { key: 'status', labelKey: 'status' },
754
- ],
755
- contextualKpi: { translationKey: 'filterableInSlice', icon: ListFilter, count: (records) => records.filter((record) => record.is_filterable === true).length },
756
- template: { code: '', group_id: null, group_name: '', slug: '', name: '', description: '', data_type: 'text', unit: '', comparison_mode: 'neutral', is_filterable: true, is_sortable: false, is_comparable: true, is_required_default: false, status: 'active', display_order: 0 },
757
- formSections: [
758
- {
759
- title: ptEn('Configuração do atributo', 'Attribute setup'),
760
- fields: [
761
- { key: 'name', label: ptEn('Nome', 'Name'), type: 'text', required: true },
762
- { key: 'code', label: ptEn('Código', 'Code'), type: 'text', required: true },
763
- { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true },
764
- { key: 'group_id', label: ptEn('Grupo', 'Group'), type: 'relation', relation: relation('/catalog/attribute-groups', ['name', 'slug'], { resource: 'attribute-groups', createResource: 'attribute-groups', allowCreate: true }) },
765
- { key: 'group_name', label: ptEn('Grupo textual', 'Group name'), type: 'text' },
766
- { key: 'data_type', label: ptEn('Tipo de dado', 'Data type'), type: 'select', required: true, options: attributeTypeOptions },
767
- { key: 'unit', label: ptEn('Unidade', 'Unit'), type: 'text' },
768
- { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: activeInactiveStatusOptions },
769
- { key: 'comparison_mode', label: ptEn('Modo de comparação', 'Comparison mode'), type: 'select', required: true, options: comparisonModeOptions },
770
- { key: 'display_order', label: ptEn('Ordem de exibição', 'Display order'), type: 'number' },
771
- { key: 'description', label: ptEn('Descrição', 'Description'), type: 'richtext', span: 2 },
772
- ],
773
- },
774
- {
775
- title: ptEn('Regras', 'Rules'),
776
- fields: [
777
- { key: 'is_filterable', label: ptEn('Filtrável', 'Filterable'), type: 'switch' },
778
- { key: 'is_sortable', label: ptEn('Ordenável', 'Sortable'), type: 'switch' },
779
- { key: 'is_comparable', label: ptEn('Comparável', 'Comparable'), type: 'switch' },
780
- { key: 'is_required_default', label: ptEn('Obrigatório por padrão', 'Required by default'), type: 'switch' },
781
- ],
782
- },
783
- ],
784
- },
785
- {
786
- resource: 'attribute-options',
787
- translationKey: 'attributeOptions',
788
- singularLabel: ptEn('Opção de atributo', 'Attribute option'),
789
- createActionLabel: ptEn('Nova Opção', 'New Option'),
790
- editActionLabel: ptEn('Editar Opção', 'Edit Option'),
791
- icon: List,
792
- href: '/catalog/attribute-options',
793
- colorClass: 'from-slate-500/20 via-zinc-500/10 to-transparent',
794
- glowClass: 'bg-slate-500/10 text-slate-700',
795
- hasActiveStats: true,
796
- listVariant: 'table',
797
- primaryFilterField: 'status',
798
- primaryFilterOptions: activeInactiveStatusFilterOptions,
799
- titleFields: ['label', 'option_value', 'slug'],
800
- descriptionFields: ['option_value', 'normalized_value'],
801
- badgeFields: ['status', 'is_default'],
802
- cardMetadata: [{ key: 'attribute_id', labelKey: 'attributeId' }, { key: 'sort_order', labelKey: 'sortOrder' }],
803
- tableColumns: [
804
- { key: 'id', labelKey: 'id' },
805
- { key: 'label', labelKey: 'label' },
806
- { key: 'slug', labelKey: 'slug' },
807
- { key: 'option_value', labelKey: 'optionValue' },
808
- { key: 'status', labelKey: 'status' },
809
- { key: 'is_default', labelKey: 'isDefault', type: 'boolean' },
810
- ],
811
- contextualKpi: { translationKey: 'visible', icon: Eye, count: (records) => records.length },
812
- template: { attribute_id: null, slug: '', label: '', option_value: '', normalized_value: '', sort_order: 0, status: 'active', is_default: false },
813
- formSections: [{
814
- title: ptEn('Opção', 'Option'),
815
- fields: [
816
- { key: 'attribute_id', label: ptEn('Atributo', 'Attribute'), type: 'relation', required: true, relation: relation('/catalog/attributes', ['name', 'code', 'slug'], { resource: 'attributes', createResource: 'attributes', allowCreate: true }) },
817
- { key: 'label', label: ptEn('Rótulo', 'Label'), type: 'text', required: true },
818
- { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true },
819
- { key: 'option_value', label: ptEn('Valor', 'Value'), type: 'text', required: true },
820
- { key: 'normalized_value', label: ptEn('Valor normalizado', 'Normalized value'), type: 'text' },
821
- { key: 'sort_order', label: ptEn('Ordem', 'Sort order'), type: 'number' },
822
- { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: activeInactiveStatusOptions },
823
- { key: 'is_default', label: ptEn('Padrão', 'Default'), type: 'switch' },
824
- ],
825
- }],
826
- },
827
- {
828
- resource: 'category-attributes',
829
- translationKey: 'categoryAttributes',
830
- singularLabel: ptEn('Atributo por categoria', 'Category attribute'),
831
- createActionLabel: ptEn('Novo Atributo por Categoria', 'New Category Attribute'),
832
- editActionLabel: ptEn('Editar Atributo por Categoria', 'Edit Category Attribute'),
833
- icon: BadgePercent,
834
- href: '/catalog/category-attributes',
835
- colorClass: 'from-pink-500/20 via-rose-500/10 to-transparent',
836
- glowClass: 'bg-pink-500/10 text-pink-700',
837
- listVariant: 'table',
838
- primaryFilterField: 'facet_mode',
839
- primaryFilterOptions: facetModeFilterOptions,
840
- titleFields: ['attribute_id', 'catalog_category_id'],
841
- descriptionFields: ['facet_mode'],
842
- badgeFields: ['facet_mode', 'is_required', 'is_highlight'],
843
- cardMetadata: [{ key: 'catalog_category_id', labelKey: 'catalogCategoryId' }, { key: 'attribute_id', labelKey: 'attributeId' }, { key: 'weight', labelKey: 'weight' }],
844
- tableColumns: [
845
- { key: 'id', labelKey: 'id' },
846
- { key: 'catalog_category_id', labelKey: 'catalogCategoryId' },
847
- { key: 'attribute_id', labelKey: 'attributeId' },
848
- { key: 'facet_mode', labelKey: 'facetMode' },
849
- { key: 'is_required', labelKey: 'isRequired', type: 'boolean' },
850
- { key: 'is_highlight', labelKey: 'isHighlight', type: 'boolean' },
851
- ],
852
- contextualKpi: { translationKey: 'requiredInSlice', icon: BadgePercent, count: (records) => records.filter((record) => record.is_required === true).length },
853
- template: { catalog_category_id: null, attribute_id: null, is_required: false, is_highlight: true, is_filter_visible: true, is_comparison_visible: true, sort_order: 0, weight: 1, facet_mode: 'default' },
854
- formSections: [{
855
- title: ptEn('Relacionamentos', 'Relationships'),
856
- fields: [
857
- { key: 'catalog_category_id', label: ptEn('Categoria', 'Category'), type: 'relation', required: true, relation: relation('/catalog/categories', ['name', 'slug'], { resource: 'categories', createResource: 'categories', allowCreate: true }) },
858
- { key: 'attribute_id', label: ptEn('Atributo', 'Attribute'), type: 'relation', required: true, relation: relation('/catalog/attributes', ['name', 'code', 'slug'], { resource: 'attributes', createResource: 'attributes', allowCreate: true }) },
859
- { key: 'facet_mode', label: ptEn('Modo de faceta', 'Facet mode'), type: 'select', required: true, options: facetModeOptions },
860
- { key: 'sort_order', label: ptEn('Ordem de exibição', 'Display order'), type: 'number' },
861
- { key: 'weight', label: ptEn('Peso', 'Weight'), type: 'number' },
862
- { key: 'is_required', label: ptEn('Obrigatório', 'Required'), type: 'switch' },
863
- { key: 'is_highlight', label: ptEn('Destacado', 'Highlighted'), type: 'switch' },
864
- { key: 'is_filter_visible', label: ptEn('Visível no filtro', 'Visible in filters'), type: 'switch' },
865
- { key: 'is_comparison_visible', label: ptEn('Visível na comparação', 'Visible in comparison'), type: 'switch' },
866
- ],
867
- }],
868
- },
869
- {
870
- resource: 'product-attributes',
871
- translationKey: 'productAttributes',
872
- singularLabel: ptEn('Atributo de produto', 'Product attribute'),
873
- createActionLabel: ptEn('Novo Valor de Atributo', 'New Attribute Value'),
874
- editActionLabel: ptEn('Editar Valor de Atributo', 'Edit Attribute Value'),
875
- icon: ListFilter,
876
- href: '/catalog/product-attributes',
877
- colorClass: 'from-teal-500/20 via-emerald-500/10 to-transparent',
878
- glowClass: 'bg-teal-500/10 text-teal-700',
879
- listVariant: 'table',
880
- primaryFilterField: 'is_verified',
881
- primaryFilterOptions: [{ value: 'true', labelKey: 'verified' }, { value: 'false', labelKey: 'unverified' }],
882
- titleFields: ['raw_value', 'value_text', 'attribute_id'],
883
- descriptionFields: ['normalized_value', 'source_type'],
884
- badgeFields: ['is_verified', 'source_type'],
885
- cardMetadata: [{ key: 'product_id', labelKey: 'productId' }, { key: 'attribute_id', labelKey: 'attributeId' }, { key: 'attribute_option_id', labelKey: 'attributeOptionId' }],
886
- tableColumns: [
887
- { key: 'id', labelKey: 'id' },
888
- { key: 'product_id', labelKey: 'productId' },
889
- { key: 'attribute_id', labelKey: 'attributeId' },
890
- { key: 'attribute_option_id', labelKey: 'attributeOptionId' },
891
- { key: 'raw_value', labelKey: 'rawValue' },
892
- { key: 'value_text', labelKey: 'valueText' },
893
- { key: 'is_verified', labelKey: 'isVerified', type: 'boolean' },
894
- ],
895
- contextualKpi: { translationKey: 'verifiedInSlice', icon: BadgePercent, count: (records) => records.filter((record) => record.is_verified === true).length },
896
- template: { product_id: null, attribute_id: null, attribute_option_id: null, value_text: '', value_number: null, value_boolean: null, raw_value: '', value_unit: '', normalized_value: '', normalized_text: '', normalized_number: null, source_type: 'manual', confidence_score: null, is_verified: false },
897
- formSections: [{
898
- title: ptEn('Valor', 'Value'),
899
- fields: [
900
- { key: 'product_id', label: ptEn('Produto', 'Product'), type: 'relation', required: true, relation: relation('/catalog/products', ['name', 'model_name', 'slug'], { resource: 'products' }) },
901
- { key: 'attribute_id', label: ptEn('Atributo', 'Attribute'), type: 'relation', required: true, relation: relation('/catalog/attributes', ['name', 'code', 'slug'], { resource: 'attributes' }) },
902
- { key: 'attribute_option_id', label: ptEn('Opção', 'Option'), type: 'relation', relation: relation('/catalog/attribute-options', ['label', 'option_value'], { resource: 'attribute-options' }) },
903
- { key: 'raw_value', label: ptEn('Valor bruto', 'Raw value'), type: 'textarea', span: 2 },
904
- { key: 'value_text', label: ptEn('Texto', 'Text'), type: 'text' },
905
- { key: 'value_number', label: ptEn('Número', 'Number'), type: 'number' },
906
- { key: 'value_unit', label: ptEn('Unidade', 'Unit'), type: 'text' },
907
- { key: 'normalized_value', label: ptEn('Valor normalizado', 'Normalized value'), type: 'text' },
908
- { key: 'source_type', label: ptEn('Origem', 'Source'), type: 'select', options: [{ value: 'manual', label: ptEn('Manual', 'Manual') }, { value: 'import', label: ptEn('Importação', 'Import') }, { value: 'computed', label: ptEn('Calculado', 'Computed') }] },
909
- { key: 'confidence_score', label: ptEn('Confiança', 'Confidence'), type: 'number' },
910
- { key: 'is_verified', label: ptEn('Verificado', 'Verified'), type: 'switch' },
911
- ],
912
- }],
913
- }
914
- );
915
-
916
- export const catalogResourceMap = new Map(catalogResources.map((resource) => [resource.resource, resource]));
917
- export const catalogDashboardHref = '/catalog/dashboard';
918
- export const catalogQuickActionResources = catalogResources.filter((resource) => resource.featured);
919
- export const catalogKpiResources = ['categories', 'products', 'brands', 'offers'];
920
-
921
- export function getCatalogRecordLabel(record: Record<string, unknown>) {
922
- return record.name || record.title || record.label || record.code || record.slug || record.rule_slug || record.option_value || record.external_offer_id || `#${record.id}`;
923
- }
924
-
925
- export function getCatalogLocalizedText(value: CatalogLocalizedText, localeCode?: string | null) {
926
- return localeCode?.toLowerCase().startsWith('pt') ? value.pt : value.en;
927
- }
928
-
929
- export const catalogModuleIcon = Boxes;
1
+ import type { LucideIcon } from 'lucide-react';
2
+ import {
3
+ BadgePercent,
4
+ Boxes,
5
+ Building2,
6
+ Eye,
7
+ Factory,
8
+ FolderTree,
9
+ Globe2,
10
+ Layers3,
11
+ List,
12
+ ListFilter,
13
+ PackagePlus,
14
+ PackageSearch,
15
+ Scale,
16
+ SearchCode,
17
+ ShoppingCart,
18
+ Store,
19
+ Tags,
20
+ } from 'lucide-react';
21
+
22
+ type CatalogListVariant = 'cards' | 'table';
23
+ type CatalogFieldDisplayType = 'text' | 'boolean' | 'currency';
24
+ type CatalogFormFieldType =
25
+ | 'text'
26
+ | 'url'
27
+ | 'textarea'
28
+ | 'richtext'
29
+ | 'number'
30
+ | 'currency'
31
+ | 'switch'
32
+ | 'select'
33
+ | 'relation'
34
+ | 'upload'
35
+ | 'json'
36
+ | 'date'
37
+ | 'datetime';
38
+
39
+ export type CatalogLocalizedText = { pt: string; en: string };
40
+ export type CatalogFieldDefinition = { key: string; labelKey: string; type?: CatalogFieldDisplayType };
41
+ export type CatalogFilterOptionDefinition = { value: string; labelKey: string };
42
+ export type CatalogContextualKpiDefinition = {
43
+ translationKey: string;
44
+ icon: LucideIcon;
45
+ count: (records: Record<string, unknown>[]) => number;
46
+ };
47
+ export type CatalogFormOptionDefinition = { value: string; label: CatalogLocalizedText };
48
+ export type CatalogRelationDefinition = {
49
+ endpoint: string;
50
+ resource?: string;
51
+ createResource?: string;
52
+ allowCreate?: boolean;
53
+ valueKey?: string;
54
+ labelKeys: string[];
55
+ searchParam?: string;
56
+ };
57
+ export type CatalogFormFieldDefinition = {
58
+ key: string;
59
+ label: CatalogLocalizedText;
60
+ type: CatalogFormFieldType;
61
+ required?: boolean;
62
+ span?: 1 | 2;
63
+ placeholder?: CatalogLocalizedText;
64
+ options?: CatalogFormOptionDefinition[];
65
+ relation?: CatalogRelationDefinition;
66
+ uploadDestination?: string;
67
+ accept?: string;
68
+ uploadPreviewVariant?: 'default' | 'square';
69
+ };
70
+ export type CatalogFormSectionDefinition = {
71
+ title: CatalogLocalizedText;
72
+ description?: CatalogLocalizedText;
73
+ fields: CatalogFormFieldDefinition[];
74
+ };
75
+ export type CatalogResourceDefinition = {
76
+ resource: string;
77
+ translationKey: string;
78
+ singularLabel: CatalogLocalizedText;
79
+ createActionLabel: CatalogLocalizedText;
80
+ editActionLabel: CatalogLocalizedText;
81
+ icon: LucideIcon;
82
+ href: string;
83
+ colorClass: string;
84
+ glowClass: string;
85
+ featured?: boolean;
86
+ hasActiveStats?: boolean;
87
+ template: Record<string, unknown>;
88
+ listVariant: CatalogListVariant;
89
+ primaryFilterField: string;
90
+ primaryFilterOptions: CatalogFilterOptionDefinition[];
91
+ titleFields: string[];
92
+ descriptionFields: string[];
93
+ badgeFields: string[];
94
+ cardMetadata: CatalogFieldDefinition[];
95
+ tableColumns?: CatalogFieldDefinition[];
96
+ contextualKpi: CatalogContextualKpiDefinition;
97
+ formSections: CatalogFormSectionDefinition[];
98
+ };
99
+
100
+ const ptEn = (pt: string, en: string): CatalogLocalizedText => ({ pt, en });
101
+ const relation = (endpoint: string, labelKeys: string[], options?: Partial<CatalogRelationDefinition>): CatalogRelationDefinition => ({
102
+ endpoint,
103
+ labelKeys,
104
+ searchParam: 'search',
105
+ ...options,
106
+ });
107
+ const fileField = (key: string, label: CatalogLocalizedText): CatalogFormFieldDefinition => ({
108
+ key,
109
+ label,
110
+ type: 'upload',
111
+ uploadDestination: `catalog/${key.replace(/_id$/, '')}`,
112
+ accept: 'image/*',
113
+ span: 2,
114
+ uploadPreviewVariant: 'square',
115
+ });
116
+
117
+ const activeInactiveStatusFilterOptions = [
118
+ { value: 'active', labelKey: 'active' },
119
+ { value: 'inactive', labelKey: 'inactive' },
120
+ ];
121
+ const publicationStatusFilterOptions = [
122
+ { value: 'draft', labelKey: 'draft' },
123
+ { value: 'published', labelKey: 'published' },
124
+ { value: 'archived', labelKey: 'archived' },
125
+ ];
126
+ const availabilityFilterOptions = [
127
+ { value: 'in_stock', labelKey: 'inStock' },
128
+ { value: 'out_of_stock', labelKey: 'outOfStock' },
129
+ { value: 'pre_order', labelKey: 'preOrder' },
130
+ { value: 'unknown', labelKey: 'unknown' },
131
+ ];
132
+ const attributeTypeFilterOptions = [
133
+ { value: 'text', labelKey: 'text' },
134
+ { value: 'long_text', labelKey: 'longText' },
135
+ { value: 'number', labelKey: 'number' },
136
+ { value: 'boolean', labelKey: 'boolean' },
137
+ { value: 'option', labelKey: 'option' },
138
+ ];
139
+ const facetModeFilterOptions = [
140
+ { value: 'default', labelKey: 'default' },
141
+ { value: 'range', labelKey: 'range' },
142
+ { value: 'select', labelKey: 'select' },
143
+ { value: 'hidden', labelKey: 'hidden' },
144
+ ];
145
+
146
+ const activeInactiveStatusOptions = [
147
+ { value: 'active', label: ptEn('Ativo', 'Active') },
148
+ { value: 'inactive', label: ptEn('Inativo', 'Inactive') },
149
+ ];
150
+ const publicationStatusOptions = [
151
+ { value: 'draft', label: ptEn('Rascunho', 'Draft') },
152
+ { value: 'published', label: ptEn('Publicado', 'Published') },
153
+ { value: 'archived', label: ptEn('Arquivado', 'Archived') },
154
+ ];
155
+ const comparisonStatusOptions = [
156
+ { value: 'draft', label: ptEn('Rascunho', 'Draft') },
157
+ { value: 'ready', label: ptEn('Pronto', 'Ready') },
158
+ { value: 'disabled', label: ptEn('Desabilitado', 'Disabled') },
159
+ ];
160
+ const siteTypeOptions = [
161
+ { value: 'portal', label: ptEn('Portal', 'Portal') },
162
+ { value: 'niche', label: ptEn('Nicho', 'Niche') },
163
+ { value: 'tenant', label: ptEn('Tenant', 'Tenant') },
164
+ ];
165
+ const attributeTypeOptions = [
166
+ { value: 'text', label: ptEn('Texto', 'Text') },
167
+ { value: 'long_text', label: ptEn('Texto longo', 'Long text') },
168
+ { value: 'number', label: ptEn('Número', 'Number') },
169
+ { value: 'boolean', label: ptEn('Booleano', 'Boolean') },
170
+ { value: 'option', label: ptEn('Opção', 'Option') },
171
+ ];
172
+ const comparisonModeOptions = [
173
+ { value: 'neutral', label: ptEn('Neutro', 'Neutral') },
174
+ { value: 'higher_better', label: ptEn('Maior é melhor', 'Higher is better') },
175
+ { value: 'lower_better', label: ptEn('Menor é melhor', 'Lower is better') },
176
+ { value: 'boolean_true_better', label: ptEn('Verdadeiro é melhor', 'True is better') },
177
+ { value: 'exact_match', label: ptEn('Correspondência exata', 'Exact match') },
178
+ ];
179
+ const facetModeOptions = [
180
+ { value: 'default', label: ptEn('Padrão', 'Default') },
181
+ { value: 'range', label: ptEn('Faixa', 'Range') },
182
+ { value: 'select', label: ptEn('Seleção', 'Select') },
183
+ { value: 'hidden', label: ptEn('Oculto', 'Hidden') },
184
+ ];
185
+ const comparisonTypeOptions = [
186
+ { value: 'manual', label: ptEn('Manual', 'Manual') },
187
+ { value: 'automatic', label: ptEn('Automática', 'Automatic') },
188
+ { value: 'similarity', label: ptEn('Similaridade', 'Similarity') },
189
+ { value: 'compatibility', label: ptEn('Compatibilidade', 'Compatibility') },
190
+ ];
191
+ const generationModeOptions = [
192
+ { value: 'manual', label: ptEn('Manual', 'Manual') },
193
+ { value: 'automatic', label: ptEn('Automática', 'Automatic') },
194
+ ];
195
+ const availabilityOptions = [
196
+ { value: 'in_stock', label: ptEn('Em estoque', 'In stock') },
197
+ { value: 'out_of_stock', label: ptEn('Sem estoque', 'Out of stock') },
198
+ { value: 'pre_order', label: ptEn('Pré-venda', 'Pre-order') },
199
+ { value: 'unknown', label: ptEn('Desconhecido', 'Unknown') },
200
+ ];
201
+ const merchantTypeOptions = [
202
+ { value: 'retailer', label: ptEn('Varejista', 'Retailer') },
203
+ { value: 'marketplace', label: ptEn('Marketplace', 'Marketplace') },
204
+ { value: 'saas', label: ptEn('SaaS', 'SaaS') },
205
+ { value: 'direct', label: ptEn('Direto', 'Direct') },
206
+ ];
207
+ const networkTypeOptions = [
208
+ { value: 'amazon', label: ptEn('Amazon', 'Amazon') },
209
+ { value: 'mercado_livre', label: ptEn('Mercado Livre', 'Mercado Livre') },
210
+ { value: 'aliexpress', label: ptEn('AliExpress', 'AliExpress') },
211
+ { value: 'kabum', label: ptEn('KaBuM!', 'KaBuM!') },
212
+ { value: 'direct', label: ptEn('Direto', 'Direct') },
213
+ { value: 'network', label: ptEn('Rede', 'Network') },
214
+ ];
215
+ const commissionTypeOptions = [
216
+ { value: 'percentage', label: ptEn('Percentual', 'Percentage') },
217
+ { value: 'fixed', label: ptEn('Valor fixo', 'Fixed') },
218
+ ];
219
+ const pageTypeOptions = [
220
+ { value: 'comparison', label: ptEn('Comparação', 'Comparison') },
221
+ { value: 'best_of', label: ptEn('Melhores', 'Best of') },
222
+ { value: 'cost_benefit', label: ptEn('Custo-benefício', 'Cost benefit') },
223
+ { value: 'attribute', label: ptEn('Atributo', 'Attribute') },
224
+ { value: 'price_range', label: ptEn('Faixa de preço', 'Price range') },
225
+ { value: 'brand', label: ptEn('Marca', 'Brand') },
226
+ { value: 'use_case', label: ptEn('Caso de uso', 'Use case') },
227
+ ];
228
+ const canonicalStrategyOptions = [
229
+ { value: 'self', label: ptEn('Canônica própria', 'Self canonical') },
230
+ { value: 'parent', label: ptEn('Canônica pai', 'Parent canonical') },
231
+ { value: 'custom', label: ptEn('Canônica customizada', 'Custom canonical') },
232
+ ];
233
+ const sourceTypeOptions = [
234
+ { value: 'api', label: ptEn('API', 'API') },
235
+ { value: 'feed', label: ptEn('Feed', 'Feed') },
236
+ { value: 'file', label: ptEn('Arquivo', 'File') },
237
+ { value: 'crawler', label: ptEn('Crawler', 'Crawler') },
238
+ ];
239
+ const currencyOptions = [
240
+ { value: 'BRL', label: ptEn('Real brasileiro (BRL)', 'Brazilian real (BRL)') },
241
+ { value: 'USD', label: ptEn('Dólar americano (USD)', 'US dollar (USD)') },
242
+ { value: 'EUR', label: ptEn('Euro (EUR)', 'Euro (EUR)') },
243
+ ];
244
+
245
+ export const catalogResources: CatalogResourceDefinition[] = [];
246
+
247
+ catalogResources.push(
248
+ {
249
+ resource: 'categories',
250
+ translationKey: 'categories',
251
+ singularLabel: ptEn('Categoria', 'Category'),
252
+ createActionLabel: ptEn('Nova Categoria', 'New Category'),
253
+ editActionLabel: ptEn('Editar Categoria', 'Edit Category'),
254
+ icon: FolderTree,
255
+ href: '/catalog/categories',
256
+ colorClass: 'from-amber-500/20 via-orange-500/10 to-transparent',
257
+ glowClass: 'bg-amber-500/10 text-amber-700',
258
+ featured: true,
259
+ hasActiveStats: true,
260
+ listVariant: 'table',
261
+ primaryFilterField: 'status',
262
+ primaryFilterOptions: activeInactiveStatusFilterOptions,
263
+ titleFields: ['name', 'slug'],
264
+ descriptionFields: ['normalized_name', 'description'],
265
+ badgeFields: ['status'],
266
+ cardMetadata: [{ key: 'parent_category_id', labelKey: 'parentCategoryId' }, { key: 'comparison_enabled', labelKey: 'comparisonEnabled', type: 'boolean' }],
267
+ tableColumns: [
268
+ { key: 'id', labelKey: 'id' },
269
+ { key: 'name', labelKey: 'name' },
270
+ { key: 'slug', labelKey: 'slug' },
271
+ { key: 'parent_category_id', labelKey: 'parentCategoryId' },
272
+ { key: 'comparison_enabled', labelKey: 'comparisonEnabled', type: 'boolean' },
273
+ { key: 'sort_order', labelKey: 'sortOrder' },
274
+ { key: 'status', labelKey: 'status' },
275
+ ],
276
+ contextualKpi: { translationKey: 'visible', icon: Eye, count: (records) => records.length },
277
+ template: { parent_category_id: null, slug: '', name: '', normalized_name: '', description: '', comparison_enabled: true, status: 'active', sort_order: 0 },
278
+ formSections: [{
279
+ title: ptEn('Estrutura', 'Structure'),
280
+ fields: [
281
+ { key: 'name', label: ptEn('Nome', 'Name'), type: 'text', required: true },
282
+ { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true },
283
+ { key: 'normalized_name', label: ptEn('Nome normalizado', 'Normalized name'), type: 'text' },
284
+ { key: 'parent_category_id', label: ptEn('Categoria pai', 'Parent category'), type: 'relation', relation: relation('/catalog/categories', ['name', 'slug'], { resource: 'categories' }) },
285
+ { key: 'comparison_enabled', label: ptEn('Permite comparação', 'Comparison enabled'), type: 'switch' },
286
+ { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: activeInactiveStatusOptions },
287
+ { key: 'sort_order', label: ptEn('Ordem', 'Sort order'), type: 'number' },
288
+ { key: 'description', label: ptEn('Descrição', 'Description'), type: 'textarea', span: 2 },
289
+ ],
290
+ }],
291
+ },
292
+ {
293
+ resource: 'brands',
294
+ translationKey: 'brands',
295
+ singularLabel: ptEn('Marca', 'Brand'),
296
+ createActionLabel: ptEn('Nova Marca', 'New Brand'),
297
+ editActionLabel: ptEn('Editar Marca', 'Edit Brand'),
298
+ icon: Factory,
299
+ href: '/catalog/brands',
300
+ colorClass: 'from-orange-500/20 via-amber-500/10 to-transparent',
301
+ glowClass: 'bg-orange-500/10 text-orange-700',
302
+ featured: true,
303
+ hasActiveStats: true,
304
+ listVariant: 'cards',
305
+ primaryFilterField: 'status',
306
+ primaryFilterOptions: activeInactiveStatusFilterOptions,
307
+ titleFields: ['name', 'slug'],
308
+ descriptionFields: ['normalized_name', 'website_url'],
309
+ badgeFields: ['status'],
310
+ cardMetadata: [
311
+ { key: 'slug', labelKey: 'slug' },
312
+ { key: 'normalized_name', labelKey: 'normalizedName' },
313
+ { key: 'website_url', labelKey: 'websiteUrl' },
314
+ ],
315
+ contextualKpi: { translationKey: 'visible', icon: Eye, count: (records) => records.length },
316
+ template: { slug: '', name: '', normalized_name: '', logo_file_id: null, status: 'active', website_url: '' },
317
+ formSections: [{
318
+ title: ptEn('Identificação', 'Identity'),
319
+ fields: [
320
+ { key: 'name', label: ptEn('Nome da marca', 'Brand name'), type: 'text', required: true },
321
+ { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true },
322
+ { key: 'normalized_name', label: ptEn('Nome normalizado', 'Normalized name'), type: 'text' },
323
+ { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: activeInactiveStatusOptions },
324
+ { key: 'website_url', label: ptEn('Site oficial', 'Official website'), type: 'url', span: 2 },
325
+ fileField('logo_file_id', ptEn('Logo', 'Logo')),
326
+ ],
327
+ }],
328
+ },
329
+ {
330
+ resource: 'sites',
331
+ translationKey: 'sites',
332
+ singularLabel: ptEn('Site', 'Site'),
333
+ createActionLabel: ptEn('Novo Site', 'New Site'),
334
+ editActionLabel: ptEn('Editar Site', 'Edit Site'),
335
+ icon: Globe2,
336
+ href: '/catalog/sites',
337
+ colorClass: 'from-sky-500/20 via-cyan-500/10 to-transparent',
338
+ glowClass: 'bg-sky-500/10 text-sky-700',
339
+ featured: true,
340
+ hasActiveStats: true,
341
+ listVariant: 'cards',
342
+ primaryFilterField: 'status',
343
+ primaryFilterOptions: activeInactiveStatusFilterOptions,
344
+ titleFields: ['name', 'domain', 'slug'],
345
+ descriptionFields: ['domain', 'slug'],
346
+ badgeFields: ['status', 'site_type'],
347
+ cardMetadata: [
348
+ { key: 'slug', labelKey: 'slug' },
349
+ { key: 'domain', labelKey: 'domain' },
350
+ { key: 'default_locale_id', labelKey: 'defaultLocaleId' },
351
+ ],
352
+ contextualKpi: { translationKey: 'visible', icon: Eye, count: (records) => records.length },
353
+ template: { slug: '', name: '', domain: '', status: 'active', site_type: 'portal', default_locale_id: null, logo_file_id: null, theme_settings_json: {}, seo_defaults_json: {} },
354
+ formSections: [
355
+ {
356
+ title: ptEn('Dados principais', 'Main details'),
357
+ fields: [
358
+ { key: 'name', label: ptEn('Nome do site', 'Site name'), type: 'text', required: true },
359
+ { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true },
360
+ { key: 'domain', label: ptEn('Domínio', 'Domain'), type: 'url', required: true },
361
+ { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: activeInactiveStatusOptions },
362
+ { key: 'site_type', label: ptEn('Tipo de site', 'Site type'), type: 'select', required: true, options: siteTypeOptions },
363
+ { key: 'default_locale_id', label: ptEn('Locale padrão', 'Default locale'), type: 'relation', required: true, relation: relation('/locale', ['name', 'code']) },
364
+ fileField('logo_file_id', ptEn('Logo', 'Logo')),
365
+ ],
366
+ },
367
+ {
368
+ title: ptEn('Configurações', 'Settings'),
369
+ fields: [
370
+ { key: 'theme_settings_json', label: ptEn('Tema e identidade', 'Theme and identity'), type: 'json', span: 2 },
371
+ { key: 'seo_defaults_json', label: ptEn('Padrões de SEO', 'SEO defaults'), type: 'json', span: 2 },
372
+ ],
373
+ },
374
+ ],
375
+ },
376
+ {
377
+ resource: 'products',
378
+ translationKey: 'products',
379
+ singularLabel: ptEn('Produto', 'Product'),
380
+ createActionLabel: ptEn('Novo Produto', 'New Product'),
381
+ editActionLabel: ptEn('Editar Produto', 'Edit Product'),
382
+ icon: PackageSearch,
383
+ href: '/catalog/products',
384
+ colorClass: 'from-emerald-500/20 via-green-500/10 to-transparent',
385
+ glowClass: 'bg-emerald-500/10 text-emerald-700',
386
+ featured: true,
387
+ hasActiveStats: true,
388
+ listVariant: 'table',
389
+ primaryFilterField: 'status',
390
+ primaryFilterOptions: publicationStatusFilterOptions,
391
+ titleFields: ['name', 'model_name', 'slug'],
392
+ descriptionFields: ['model_name', 'sku', 'slug'],
393
+ badgeFields: ['status', 'comparison_status', 'is_active'],
394
+ cardMetadata: [
395
+ { key: 'slug', labelKey: 'slug' },
396
+ { key: 'sku', labelKey: 'sku' },
397
+ { key: 'brand_name', labelKey: 'brandId' },
398
+ { key: 'category_name', labelKey: 'catalogCategoryId' },
399
+ ],
400
+ tableColumns: [
401
+ { key: 'name', labelKey: 'name' },
402
+ { key: 'brand_name', labelKey: 'brandId' },
403
+ { key: 'category_name', labelKey: 'catalogCategoryId' },
404
+ { key: 'status', labelKey: 'status' },
405
+ { key: 'is_active', labelKey: 'isActive', type: 'boolean' },
406
+ ],
407
+ contextualKpi: { translationKey: 'activeInSlice', icon: PackageSearch, count: (records) => records.filter((record) => record.is_active === true).length },
408
+ template: { brand_id: null, catalog_category_id: null, primary_content_id: null, slug: '', name: '', short_description: '', description: '', model_name: '', sku: '', gtin: '', status: 'draft', comparison_status: 'draft', release_date: '', spec_snapshot_json: {}, comparison_snapshot_json: {}, is_active: true },
409
+ formSections: [
410
+ {
411
+ title: ptEn('Base do produto', 'Product basics'),
412
+ fields: [
413
+ { key: 'name', label: ptEn('Nome do produto', 'Product name'), type: 'text', required: true },
414
+ { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true },
415
+ { key: 'brand_id', label: ptEn('Marca', 'Brand'), type: 'relation', required: true, relation: relation('/catalog/brands', ['name', 'slug'], { resource: 'brands', createResource: 'brands', allowCreate: true }) },
416
+ { key: 'catalog_category_id', label: ptEn('Categoria', 'Category'), type: 'relation', required: true, relation: relation('/catalog/categories', ['name', 'slug'], { resource: 'categories', createResource: 'categories', allowCreate: true }) },
417
+ { key: 'primary_content_id', label: ptEn('Conteúdo principal', 'Primary content'), type: 'relation', relation: relation('/content', ['title', 'slug']) },
418
+ { key: 'model_name', label: ptEn('Modelo', 'Model'), type: 'text' },
419
+ { key: 'sku', label: ptEn('SKU', 'SKU'), type: 'text' },
420
+ { key: 'gtin', label: ptEn('GTIN', 'GTIN'), type: 'text' },
421
+ { key: 'release_date', label: ptEn('Data de lançamento', 'Release date'), type: 'date' },
422
+ { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: publicationStatusOptions },
423
+ { key: 'comparison_status', label: ptEn('Status de comparação', 'Comparison status'), type: 'select', required: true, options: comparisonStatusOptions },
424
+ { key: 'is_active', label: ptEn('Ativo para comparação', 'Active for comparison'), type: 'switch' },
425
+ ],
426
+ },
427
+ {
428
+ title: ptEn('Conteúdo', 'Content'),
429
+ fields: [
430
+ { key: 'short_description', label: ptEn('Resumo curto', 'Short description'), type: 'richtext', span: 2 },
431
+ { key: 'description', label: ptEn('Descrição completa', 'Full description'), type: 'richtext', span: 2 },
432
+ ],
433
+ },
434
+ {
435
+ title: ptEn('Snapshots', 'Snapshots'),
436
+ description: ptEn('Campos secundários para cache, leitura legada e payloads desnormalizados.', 'Secondary fields for cache, legacy reads, and denormalized payloads.'),
437
+ fields: [
438
+ { key: 'spec_snapshot_json', label: ptEn('Snapshot de especificações', 'Specification snapshot'), type: 'json', span: 2 },
439
+ { key: 'comparison_snapshot_json', label: ptEn('Snapshot de comparação', 'Comparison snapshot'), type: 'json', span: 2 },
440
+ ],
441
+ },
442
+ ],
443
+ }
444
+ );
445
+
446
+ catalogResources.push(
447
+ {
448
+ resource: 'comparisons',
449
+ translationKey: 'comparisons',
450
+ singularLabel: ptEn('Comparação', 'Comparison'),
451
+ createActionLabel: ptEn('Nova Comparação', 'New Comparison'),
452
+ editActionLabel: ptEn('Editar Comparação', 'Edit Comparison'),
453
+ icon: Scale,
454
+ href: '/catalog/comparisons',
455
+ colorClass: 'from-amber-500/20 via-yellow-500/10 to-transparent',
456
+ glowClass: 'bg-amber-500/10 text-amber-700',
457
+ hasActiveStats: true,
458
+ listVariant: 'cards',
459
+ primaryFilterField: 'status',
460
+ primaryFilterOptions: publicationStatusFilterOptions,
461
+ titleFields: ['title', 'slug'],
462
+ descriptionFields: ['summary', 'slug'],
463
+ badgeFields: ['status', 'comparison_type', 'generation_mode'],
464
+ cardMetadata: [{ key: 'slug', labelKey: 'slug' }, { key: 'site_id', labelKey: 'siteId' }, { key: 'catalog_category_id', labelKey: 'catalogCategoryId' }],
465
+ contextualKpi: { translationKey: 'publishedInSlice', icon: Scale, count: (records) => records.filter((record) => String(record.status ?? '') === 'published').length },
466
+ template: { catalog_category_id: null, site_id: null, primary_content_id: null, slug: '', comparison_type: 'manual', generation_mode: 'manual', status: 'draft', title: '', summary: '', intro: '', verdict: '', eligibility_hash: '', published_at: '' },
467
+ formSections: [
468
+ {
469
+ title: ptEn('Base da comparação', 'Comparison basics'),
470
+ fields: [
471
+ { key: 'title', label: ptEn('Título', 'Title'), type: 'text', required: true },
472
+ { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true },
473
+ { key: 'site_id', label: ptEn('Site', 'Site'), type: 'relation', required: true, relation: relation('/catalog/sites', ['name', 'domain', 'slug'], { resource: 'sites', createResource: 'sites', allowCreate: true }) },
474
+ { key: 'catalog_category_id', label: ptEn('Categoria', 'Category'), type: 'relation', required: true, relation: relation('/catalog/categories', ['name', 'slug'], { resource: 'categories' }) },
475
+ { key: 'primary_content_id', label: ptEn('Conteúdo principal', 'Primary content'), type: 'relation', relation: relation('/content', ['title', 'slug']) },
476
+ { key: 'comparison_type', label: ptEn('Tipo de comparação', 'Comparison type'), type: 'select', required: true, options: comparisonTypeOptions },
477
+ { key: 'generation_mode', label: ptEn('Modo de geração', 'Generation mode'), type: 'select', required: true, options: generationModeOptions },
478
+ { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: publicationStatusOptions },
479
+ { key: 'published_at', label: ptEn('Publicado em', 'Published at'), type: 'datetime' },
480
+ ],
481
+ },
482
+ {
483
+ title: ptEn('Conteúdo editorial', 'Editorial content'),
484
+ fields: [
485
+ { key: 'summary', label: ptEn('Resumo', 'Summary'), type: 'richtext', span: 2 },
486
+ { key: 'intro', label: ptEn('Introdução', 'Introduction'), type: 'richtext', span: 2 },
487
+ { key: 'verdict', label: ptEn('Veredito', 'Verdict'), type: 'richtext', span: 2 },
488
+ { key: 'eligibility_hash', label: ptEn('Hash de elegibilidade', 'Eligibility hash'), type: 'text', span: 2 },
489
+ ],
490
+ },
491
+ ],
492
+ },
493
+ {
494
+ resource: 'offers',
495
+ translationKey: 'offers',
496
+ singularLabel: ptEn('Oferta', 'Offer'),
497
+ createActionLabel: ptEn('Nova Oferta', 'New Offer'),
498
+ editActionLabel: ptEn('Editar Oferta', 'Edit Offer'),
499
+ icon: ShoppingCart,
500
+ href: '/catalog/offers',
501
+ colorClass: 'from-red-500/20 via-orange-500/10 to-transparent',
502
+ glowClass: 'bg-red-500/10 text-red-700',
503
+ featured: true,
504
+ listVariant: 'cards',
505
+ primaryFilterField: 'availability_status',
506
+ primaryFilterOptions: availabilityFilterOptions,
507
+ titleFields: ['title', 'external_offer_id'],
508
+ descriptionFields: ['external_offer_id', 'affiliate_url'],
509
+ badgeFields: ['availability_status', 'is_featured'],
510
+ cardMetadata: [{ key: 'price_amount', labelKey: 'priceAmount', type: 'currency' }, { key: 'merchant_id', labelKey: 'merchantId' }, { key: 'product_id', labelKey: 'productId' }],
511
+ contextualKpi: { translationKey: 'inStockInSlice', icon: ShoppingCart, count: (records) => records.filter((record) => record.availability_status === 'in_stock').length },
512
+ template: { product_id: null, merchant_id: null, affiliate_program_id: null, site_id: null, external_offer_id: '', title: '', price_amount: 0, price_currency: 'BRL', original_price_amount: null, installment_json: {}, availability_status: 'in_stock', affiliate_url: '', deep_link_url: '', priority_score: 0, is_featured: false, valid_from: '', valid_until: '', last_seen_at: '' },
513
+ formSections: [
514
+ {
515
+ title: ptEn('Oferta comercial', 'Commercial offer'),
516
+ fields: [
517
+ { key: 'title', label: ptEn('Título da oferta', 'Offer title'), type: 'text', required: true, span: 2 },
518
+ { key: 'product_id', label: ptEn('Produto', 'Product'), type: 'relation', required: true, relation: relation('/catalog/products', ['name', 'model_name', 'slug'], { resource: 'products' }) },
519
+ { key: 'merchant_id', label: ptEn('Lojista', 'Merchant'), type: 'relation', required: true, relation: relation('/catalog/merchants', ['name', 'slug'], { resource: 'merchants', createResource: 'merchants', allowCreate: true }) },
520
+ { key: 'affiliate_program_id', label: ptEn('Programa de afiliação', 'Affiliate program'), type: 'relation', relation: relation('/catalog/affiliate-programs', ['name', 'slug'], { resource: 'affiliate-programs', createResource: 'affiliate-programs', allowCreate: true }) },
521
+ { key: 'site_id', label: ptEn('Site', 'Site'), type: 'relation', relation: relation('/catalog/sites', ['name', 'domain', 'slug'], { resource: 'sites' }) },
522
+ { key: 'external_offer_id', label: ptEn('ID externo', 'External ID'), type: 'text' },
523
+ { key: 'availability_status', label: ptEn('Disponibilidade', 'Availability'), type: 'select', required: true, options: availabilityOptions },
524
+ { key: 'price_amount', label: ptEn('Preço', 'Price'), type: 'currency', required: true },
525
+ { key: 'original_price_amount', label: ptEn('Preço original', 'Original price'), type: 'currency' },
526
+ { key: 'price_currency', label: ptEn('Moeda', 'Currency'), type: 'select', required: true, options: currencyOptions },
527
+ { key: 'priority_score', label: ptEn('Prioridade', 'Priority'), type: 'number' },
528
+ { key: 'is_featured', label: ptEn('Oferta destacada', 'Featured offer'), type: 'switch' },
529
+ ],
530
+ },
531
+ {
532
+ title: ptEn('Links e vigência', 'Links and schedule'),
533
+ fields: [
534
+ { key: 'affiliate_url', label: ptEn('URL de afiliação', 'Affiliate URL'), type: 'url', span: 2 },
535
+ { key: 'deep_link_url', label: ptEn('Deep link', 'Deep link'), type: 'url', span: 2 },
536
+ { key: 'valid_from', label: ptEn('Válida a partir de', 'Valid from'), type: 'datetime' },
537
+ { key: 'valid_until', label: ptEn('Válida até', 'Valid until'), type: 'datetime' },
538
+ { key: 'last_seen_at', label: ptEn('Última captura', 'Last seen at'), type: 'datetime' },
539
+ { key: 'installment_json', label: ptEn('Parcelamento', 'Installments'), type: 'json', span: 2 },
540
+ ],
541
+ },
542
+ ],
543
+ },
544
+ {
545
+ resource: 'merchants',
546
+ translationKey: 'merchants',
547
+ singularLabel: ptEn('Lojista', 'Merchant'),
548
+ createActionLabel: ptEn('Novo Lojista', 'New Merchant'),
549
+ editActionLabel: ptEn('Editar Lojista', 'Edit Merchant'),
550
+ icon: Store,
551
+ href: '/catalog/merchants',
552
+ colorClass: 'from-lime-500/20 via-green-500/10 to-transparent',
553
+ glowClass: 'bg-lime-500/10 text-lime-700',
554
+ featured: true,
555
+ hasActiveStats: true,
556
+ listVariant: 'cards',
557
+ primaryFilterField: 'status',
558
+ primaryFilterOptions: activeInactiveStatusFilterOptions,
559
+ titleFields: ['name', 'slug'],
560
+ descriptionFields: ['slug'],
561
+ badgeFields: ['status', 'merchant_type'],
562
+ cardMetadata: [{ key: 'slug', labelKey: 'slug' }, { key: 'merchant_type', labelKey: 'merchantType' }, { key: 'logo_file_id', labelKey: 'logoFileId' }],
563
+ contextualKpi: { translationKey: 'visible', icon: Eye, count: (records) => records.length },
564
+ template: { slug: '', name: '', status: 'active', merchant_type: 'retailer', logo_file_id: null },
565
+ formSections: [{
566
+ title: ptEn('Perfil do lojista', 'Merchant profile'),
567
+ fields: [
568
+ { key: 'name', label: ptEn('Nome do lojista', 'Merchant name'), type: 'text', required: true },
569
+ { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true },
570
+ { key: 'merchant_type', label: ptEn('Tipo de lojista', 'Merchant type'), type: 'select', required: true, options: merchantTypeOptions },
571
+ { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: activeInactiveStatusOptions },
572
+ fileField('logo_file_id', ptEn('Logo', 'Logo')),
573
+ ],
574
+ }],
575
+ },
576
+ {
577
+ resource: 'affiliate-programs',
578
+ translationKey: 'affiliatePrograms',
579
+ singularLabel: ptEn('Programa de afiliação', 'Affiliate program'),
580
+ createActionLabel: ptEn('Novo Programa de Afiliação', 'New Affiliate Program'),
581
+ editActionLabel: ptEn('Editar Programa de Afiliação', 'Edit Affiliate Program'),
582
+ icon: Building2,
583
+ href: '/catalog/affiliate-programs',
584
+ colorClass: 'from-cyan-500/20 via-teal-500/10 to-transparent',
585
+ glowClass: 'bg-cyan-500/10 text-cyan-700',
586
+ hasActiveStats: true,
587
+ listVariant: 'cards',
588
+ primaryFilterField: 'status',
589
+ primaryFilterOptions: activeInactiveStatusFilterOptions,
590
+ titleFields: ['name', 'slug'],
591
+ descriptionFields: ['slug', 'network_type'],
592
+ badgeFields: ['status', 'network_type', 'commission_type'],
593
+ cardMetadata: [{ key: 'merchant_id', labelKey: 'merchantId' }, { key: 'commission_type', labelKey: 'commissionType' }, { key: 'default_commission_value', labelKey: 'defaultCommissionValue', type: 'currency' }],
594
+ contextualKpi: { translationKey: 'visible', icon: Eye, count: (records) => records.length },
595
+ template: { merchant_id: null, slug: '', name: '', network_type: 'direct', tracking_template: '', commission_type: 'percentage', default_commission_value: 0, status: 'active' },
596
+ formSections: [{
597
+ title: ptEn('Programa', 'Program'),
598
+ fields: [
599
+ { key: 'name', label: ptEn('Nome do programa', 'Program name'), type: 'text', required: true },
600
+ { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true },
601
+ { key: 'merchant_id', label: ptEn('Lojista', 'Merchant'), type: 'relation', required: true, relation: relation('/catalog/merchants', ['name', 'slug'], { resource: 'merchants', createResource: 'merchants', allowCreate: true }) },
602
+ { key: 'network_type', label: ptEn('Rede', 'Network'), type: 'select', required: true, options: networkTypeOptions },
603
+ { key: 'commission_type', label: ptEn('Tipo de comissão', 'Commission type'), type: 'select', required: true, options: commissionTypeOptions },
604
+ { key: 'default_commission_value', label: ptEn('Comissão padrão', 'Default commission'), type: 'currency' },
605
+ { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: activeInactiveStatusOptions },
606
+ { key: 'tracking_template', label: ptEn('Template de tracking', 'Tracking template'), type: 'textarea', span: 2 },
607
+ ],
608
+ }],
609
+ },
610
+ {
611
+ resource: 'seo-rules',
612
+ translationKey: 'seoRules',
613
+ singularLabel: ptEn('Regra de SEO', 'SEO rule'),
614
+ createActionLabel: ptEn('Nova Regra de SEO', 'New SEO Rule'),
615
+ editActionLabel: ptEn('Editar Regra de SEO', 'Edit SEO Rule'),
616
+ icon: SearchCode,
617
+ href: '/catalog/seo-rules',
618
+ colorClass: 'from-slate-500/20 via-zinc-500/10 to-transparent',
619
+ glowClass: 'bg-slate-500/10 text-slate-700',
620
+ featured: true,
621
+ hasActiveStats: true,
622
+ listVariant: 'cards',
623
+ primaryFilterField: 'status',
624
+ primaryFilterOptions: activeInactiveStatusFilterOptions,
625
+ titleFields: ['rule_slug', 'page_type'],
626
+ descriptionFields: ['page_type', 'canonical_strategy'],
627
+ badgeFields: ['status', 'page_type', 'canonical_strategy'],
628
+ cardMetadata: [{ key: 'site_id', labelKey: 'siteId' }, { key: 'catalog_category_id', labelKey: 'catalogCategoryId' }, { key: 'priority', labelKey: 'priority' }],
629
+ contextualKpi: { translationKey: 'visible', icon: Eye, count: (records) => records.length },
630
+ template: { site_id: null, catalog_category_id: null, page_type: 'comparison', rule_slug: '', status: 'active', generation_query_json: {}, min_product_count: 2, min_attribute_coverage: 1, canonical_strategy: 'self', priority: 0, template_json: {} },
631
+ formSections: [
632
+ {
633
+ title: ptEn('Escopo da regra', 'Rule scope'),
634
+ fields: [
635
+ { key: 'site_id', label: ptEn('Site', 'Site'), type: 'relation', required: true, relation: relation('/catalog/sites', ['name', 'domain', 'slug'], { resource: 'sites' }) },
636
+ { key: 'catalog_category_id', label: ptEn('Categoria', 'Category'), type: 'relation', relation: relation('/catalog/categories', ['name', 'slug'], { resource: 'categories' }) },
637
+ { key: 'page_type', label: ptEn('Tipo de página', 'Page type'), type: 'select', required: true, options: pageTypeOptions },
638
+ { key: 'rule_slug', label: ptEn('Slug da regra', 'Rule slug'), type: 'text', required: true },
639
+ { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: activeInactiveStatusOptions },
640
+ { key: 'canonical_strategy', label: ptEn('Estratégia canônica', 'Canonical strategy'), type: 'select', required: true, options: canonicalStrategyOptions },
641
+ { key: 'priority', label: ptEn('Prioridade', 'Priority'), type: 'number' },
642
+ { key: 'min_product_count', label: ptEn('Mínimo de produtos', 'Minimum products'), type: 'number' },
643
+ { key: 'min_attribute_coverage', label: ptEn('Cobertura mínima de atributos', 'Minimum attribute coverage'), type: 'number' },
644
+ ],
645
+ },
646
+ {
647
+ title: ptEn('Regras dinâmicas', 'Dynamic rules'),
648
+ fields: [
649
+ { key: 'generation_query_json', label: ptEn('Consulta geradora', 'Generation query'), type: 'json', span: 2 },
650
+ { key: 'template_json', label: ptEn('Template de SEO', 'SEO template'), type: 'json', span: 2 },
651
+ ],
652
+ },
653
+ ],
654
+ },
655
+ {
656
+ resource: 'import-sources',
657
+ translationKey: 'importSources',
658
+ singularLabel: ptEn('Fonte de importação', 'Import source'),
659
+ createActionLabel: ptEn('Nova Fonte de Importação', 'New Import Source'),
660
+ editActionLabel: ptEn('Editar Fonte de Importação', 'Edit Import Source'),
661
+ icon: PackagePlus,
662
+ href: '/catalog/import-sources',
663
+ colorClass: 'from-stone-500/20 via-neutral-500/10 to-transparent',
664
+ glowClass: 'bg-stone-500/10 text-stone-700',
665
+ featured: true,
666
+ hasActiveStats: true,
667
+ listVariant: 'cards',
668
+ primaryFilterField: 'status',
669
+ primaryFilterOptions: activeInactiveStatusFilterOptions,
670
+ titleFields: ['name', 'slug'],
671
+ descriptionFields: ['slug', 'source_type'],
672
+ badgeFields: ['status', 'source_type'],
673
+ cardMetadata: [{ key: 'slug', labelKey: 'slug' }, { key: 'source_type', labelKey: 'sourceType' }, { key: 'status', labelKey: 'status' }],
674
+ contextualKpi: { translationKey: 'visible', icon: Eye, count: (records) => records.length },
675
+ template: { slug: '', name: '', source_type: 'api', config_json: {}, status: 'active' },
676
+ formSections: [{
677
+ title: ptEn('Origem dos dados', 'Data source'),
678
+ fields: [
679
+ { key: 'name', label: ptEn('Nome da fonte', 'Source name'), type: 'text', required: true },
680
+ { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true },
681
+ { key: 'source_type', label: ptEn('Tipo de fonte', 'Source type'), type: 'select', required: true, options: sourceTypeOptions },
682
+ { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: activeInactiveStatusOptions },
683
+ { key: 'config_json', label: ptEn('Configuração', 'Configuration'), type: 'json', span: 2 },
684
+ ],
685
+ }],
686
+ }
687
+ );
688
+
689
+ catalogResources.push(
690
+ {
691
+ resource: 'attribute-groups',
692
+ translationKey: 'attributeGroups',
693
+ singularLabel: ptEn('Grupo de atributos', 'Attribute group'),
694
+ createActionLabel: ptEn('Novo Grupo de Atributos', 'New Attribute Group'),
695
+ editActionLabel: ptEn('Editar Grupo de Atributos', 'Edit Attribute Group'),
696
+ icon: Layers3,
697
+ href: '/catalog/attribute-groups',
698
+ colorClass: 'from-violet-500/20 via-fuchsia-500/10 to-transparent',
699
+ glowClass: 'bg-violet-500/10 text-violet-700',
700
+ hasActiveStats: true,
701
+ listVariant: 'table',
702
+ primaryFilterField: 'status',
703
+ primaryFilterOptions: activeInactiveStatusFilterOptions,
704
+ titleFields: ['name', 'slug'],
705
+ descriptionFields: ['slug'],
706
+ badgeFields: ['status'],
707
+ cardMetadata: [{ key: 'slug', labelKey: 'slug' }, { key: 'status', labelKey: 'status' }],
708
+ tableColumns: [
709
+ { key: 'id', labelKey: 'id' },
710
+ { key: 'name', labelKey: 'name' },
711
+ { key: 'slug', labelKey: 'slug' },
712
+ { key: 'status', labelKey: 'status' },
713
+ ],
714
+ contextualKpi: { translationKey: 'visible', icon: Eye, count: (records) => records.length },
715
+ template: { slug: '', name: '', status: 'active' },
716
+ formSections: [{
717
+ title: ptEn('Grupo', 'Group'),
718
+ fields: [
719
+ { key: 'name', label: ptEn('Nome do grupo', 'Group name'), type: 'text', required: true },
720
+ { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true },
721
+ { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: activeInactiveStatusOptions },
722
+ ],
723
+ }],
724
+ },
725
+ {
726
+ resource: 'attributes',
727
+ translationKey: 'attributes',
728
+ singularLabel: ptEn('Atributo', 'Attribute'),
729
+ createActionLabel: ptEn('Novo Atributo', 'New Attribute'),
730
+ editActionLabel: ptEn('Editar Atributo', 'Edit Attribute'),
731
+ icon: Tags,
732
+ href: '/catalog/attributes',
733
+ colorClass: 'from-indigo-500/20 via-blue-500/10 to-transparent',
734
+ glowClass: 'bg-indigo-500/10 text-indigo-700',
735
+ listVariant: 'table',
736
+ primaryFilterField: 'data_type',
737
+ primaryFilterOptions: attributeTypeFilterOptions,
738
+ titleFields: ['name', 'code', 'slug'],
739
+ descriptionFields: ['description', 'slug'],
740
+ badgeFields: ['data_type', 'comparison_mode', 'status'],
741
+ cardMetadata: [
742
+ { key: 'group_id', labelKey: 'groupId' },
743
+ { key: 'group_name', labelKey: 'groupName' },
744
+ { key: 'is_filterable', labelKey: 'isFilterable', type: 'boolean' },
745
+ { key: 'is_comparable', labelKey: 'isComparable', type: 'boolean' },
746
+ ],
747
+ tableColumns: [
748
+ { key: 'id', labelKey: 'id' },
749
+ { key: 'name', labelKey: 'name' },
750
+ { key: 'code', labelKey: 'code' },
751
+ { key: 'slug', labelKey: 'slug' },
752
+ { key: 'data_type', labelKey: 'dataType' },
753
+ { key: 'status', labelKey: 'status' },
754
+ ],
755
+ contextualKpi: { translationKey: 'filterableInSlice', icon: ListFilter, count: (records) => records.filter((record) => record.is_filterable === true).length },
756
+ template: { code: '', group_id: null, group_name: '', slug: '', name: '', description: '', data_type: 'text', unit: '', comparison_mode: 'neutral', is_filterable: true, is_sortable: false, is_comparable: true, is_required_default: false, status: 'active', display_order: 0 },
757
+ formSections: [
758
+ {
759
+ title: ptEn('Configuração do atributo', 'Attribute setup'),
760
+ fields: [
761
+ { key: 'name', label: ptEn('Nome', 'Name'), type: 'text', required: true },
762
+ { key: 'code', label: ptEn('Código', 'Code'), type: 'text', required: true },
763
+ { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true },
764
+ { key: 'group_id', label: ptEn('Grupo', 'Group'), type: 'relation', relation: relation('/catalog/attribute-groups', ['name', 'slug'], { resource: 'attribute-groups', createResource: 'attribute-groups', allowCreate: true }) },
765
+ { key: 'group_name', label: ptEn('Grupo textual', 'Group name'), type: 'text' },
766
+ { key: 'data_type', label: ptEn('Tipo de dado', 'Data type'), type: 'select', required: true, options: attributeTypeOptions },
767
+ { key: 'unit', label: ptEn('Unidade', 'Unit'), type: 'text' },
768
+ { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: activeInactiveStatusOptions },
769
+ { key: 'comparison_mode', label: ptEn('Modo de comparação', 'Comparison mode'), type: 'select', required: true, options: comparisonModeOptions },
770
+ { key: 'display_order', label: ptEn('Ordem de exibição', 'Display order'), type: 'number' },
771
+ { key: 'description', label: ptEn('Descrição', 'Description'), type: 'richtext', span: 2 },
772
+ ],
773
+ },
774
+ {
775
+ title: ptEn('Regras', 'Rules'),
776
+ fields: [
777
+ { key: 'is_filterable', label: ptEn('Filtrável', 'Filterable'), type: 'switch' },
778
+ { key: 'is_sortable', label: ptEn('Ordenável', 'Sortable'), type: 'switch' },
779
+ { key: 'is_comparable', label: ptEn('Comparável', 'Comparable'), type: 'switch' },
780
+ { key: 'is_required_default', label: ptEn('Obrigatório por padrão', 'Required by default'), type: 'switch' },
781
+ ],
782
+ },
783
+ ],
784
+ },
785
+ {
786
+ resource: 'attribute-options',
787
+ translationKey: 'attributeOptions',
788
+ singularLabel: ptEn('Opção de atributo', 'Attribute option'),
789
+ createActionLabel: ptEn('Nova Opção', 'New Option'),
790
+ editActionLabel: ptEn('Editar Opção', 'Edit Option'),
791
+ icon: List,
792
+ href: '/catalog/attribute-options',
793
+ colorClass: 'from-slate-500/20 via-zinc-500/10 to-transparent',
794
+ glowClass: 'bg-slate-500/10 text-slate-700',
795
+ hasActiveStats: true,
796
+ listVariant: 'table',
797
+ primaryFilterField: 'status',
798
+ primaryFilterOptions: activeInactiveStatusFilterOptions,
799
+ titleFields: ['label', 'option_value', 'slug'],
800
+ descriptionFields: ['option_value', 'normalized_value'],
801
+ badgeFields: ['status', 'is_default'],
802
+ cardMetadata: [{ key: 'attribute_id', labelKey: 'attributeId' }, { key: 'sort_order', labelKey: 'sortOrder' }],
803
+ tableColumns: [
804
+ { key: 'id', labelKey: 'id' },
805
+ { key: 'label', labelKey: 'label' },
806
+ { key: 'slug', labelKey: 'slug' },
807
+ { key: 'option_value', labelKey: 'optionValue' },
808
+ { key: 'status', labelKey: 'status' },
809
+ { key: 'is_default', labelKey: 'isDefault', type: 'boolean' },
810
+ ],
811
+ contextualKpi: { translationKey: 'visible', icon: Eye, count: (records) => records.length },
812
+ template: { attribute_id: null, slug: '', label: '', option_value: '', normalized_value: '', sort_order: 0, status: 'active', is_default: false },
813
+ formSections: [{
814
+ title: ptEn('Opção', 'Option'),
815
+ fields: [
816
+ { key: 'attribute_id', label: ptEn('Atributo', 'Attribute'), type: 'relation', required: true, relation: relation('/catalog/attributes', ['name', 'code', 'slug'], { resource: 'attributes', createResource: 'attributes', allowCreate: true }) },
817
+ { key: 'label', label: ptEn('Rótulo', 'Label'), type: 'text', required: true },
818
+ { key: 'slug', label: ptEn('Slug', 'Slug'), type: 'text', required: true },
819
+ { key: 'option_value', label: ptEn('Valor', 'Value'), type: 'text', required: true },
820
+ { key: 'normalized_value', label: ptEn('Valor normalizado', 'Normalized value'), type: 'text' },
821
+ { key: 'sort_order', label: ptEn('Ordem', 'Sort order'), type: 'number' },
822
+ { key: 'status', label: ptEn('Status', 'Status'), type: 'select', required: true, options: activeInactiveStatusOptions },
823
+ { key: 'is_default', label: ptEn('Padrão', 'Default'), type: 'switch' },
824
+ ],
825
+ }],
826
+ },
827
+ {
828
+ resource: 'category-attributes',
829
+ translationKey: 'categoryAttributes',
830
+ singularLabel: ptEn('Atributo por categoria', 'Category attribute'),
831
+ createActionLabel: ptEn('Novo Atributo por Categoria', 'New Category Attribute'),
832
+ editActionLabel: ptEn('Editar Atributo por Categoria', 'Edit Category Attribute'),
833
+ icon: BadgePercent,
834
+ href: '/catalog/category-attributes',
835
+ colorClass: 'from-pink-500/20 via-rose-500/10 to-transparent',
836
+ glowClass: 'bg-pink-500/10 text-pink-700',
837
+ listVariant: 'table',
838
+ primaryFilterField: 'facet_mode',
839
+ primaryFilterOptions: facetModeFilterOptions,
840
+ titleFields: ['attribute_id', 'catalog_category_id'],
841
+ descriptionFields: ['facet_mode'],
842
+ badgeFields: ['facet_mode', 'is_required', 'is_highlight'],
843
+ cardMetadata: [{ key: 'catalog_category_id', labelKey: 'catalogCategoryId' }, { key: 'attribute_id', labelKey: 'attributeId' }, { key: 'weight', labelKey: 'weight' }],
844
+ tableColumns: [
845
+ { key: 'id', labelKey: 'id' },
846
+ { key: 'catalog_category_id', labelKey: 'catalogCategoryId' },
847
+ { key: 'attribute_id', labelKey: 'attributeId' },
848
+ { key: 'facet_mode', labelKey: 'facetMode' },
849
+ { key: 'is_required', labelKey: 'isRequired', type: 'boolean' },
850
+ { key: 'is_highlight', labelKey: 'isHighlight', type: 'boolean' },
851
+ ],
852
+ contextualKpi: { translationKey: 'requiredInSlice', icon: BadgePercent, count: (records) => records.filter((record) => record.is_required === true).length },
853
+ template: { catalog_category_id: null, attribute_id: null, is_required: false, is_highlight: true, is_filter_visible: true, is_comparison_visible: true, sort_order: 0, weight: 1, facet_mode: 'default' },
854
+ formSections: [{
855
+ title: ptEn('Relacionamentos', 'Relationships'),
856
+ fields: [
857
+ { key: 'catalog_category_id', label: ptEn('Categoria', 'Category'), type: 'relation', required: true, relation: relation('/catalog/categories', ['name', 'slug'], { resource: 'categories', createResource: 'categories', allowCreate: true }) },
858
+ { key: 'attribute_id', label: ptEn('Atributo', 'Attribute'), type: 'relation', required: true, relation: relation('/catalog/attributes', ['name', 'code', 'slug'], { resource: 'attributes', createResource: 'attributes', allowCreate: true }) },
859
+ { key: 'facet_mode', label: ptEn('Modo de faceta', 'Facet mode'), type: 'select', required: true, options: facetModeOptions },
860
+ { key: 'sort_order', label: ptEn('Ordem de exibição', 'Display order'), type: 'number' },
861
+ { key: 'weight', label: ptEn('Peso', 'Weight'), type: 'number' },
862
+ { key: 'is_required', label: ptEn('Obrigatório', 'Required'), type: 'switch' },
863
+ { key: 'is_highlight', label: ptEn('Destacado', 'Highlighted'), type: 'switch' },
864
+ { key: 'is_filter_visible', label: ptEn('Visível no filtro', 'Visible in filters'), type: 'switch' },
865
+ { key: 'is_comparison_visible', label: ptEn('Visível na comparação', 'Visible in comparison'), type: 'switch' },
866
+ ],
867
+ }],
868
+ },
869
+ {
870
+ resource: 'product-attributes',
871
+ translationKey: 'productAttributes',
872
+ singularLabel: ptEn('Atributo de produto', 'Product attribute'),
873
+ createActionLabel: ptEn('Novo Valor de Atributo', 'New Attribute Value'),
874
+ editActionLabel: ptEn('Editar Valor de Atributo', 'Edit Attribute Value'),
875
+ icon: ListFilter,
876
+ href: '/catalog/product-attributes',
877
+ colorClass: 'from-teal-500/20 via-emerald-500/10 to-transparent',
878
+ glowClass: 'bg-teal-500/10 text-teal-700',
879
+ listVariant: 'table',
880
+ primaryFilterField: 'is_verified',
881
+ primaryFilterOptions: [{ value: 'true', labelKey: 'verified' }, { value: 'false', labelKey: 'unverified' }],
882
+ titleFields: ['raw_value', 'value_text', 'attribute_id'],
883
+ descriptionFields: ['normalized_value', 'source_type'],
884
+ badgeFields: ['is_verified', 'source_type'],
885
+ cardMetadata: [{ key: 'product_id', labelKey: 'productId' }, { key: 'attribute_id', labelKey: 'attributeId' }, { key: 'attribute_option_id', labelKey: 'attributeOptionId' }],
886
+ tableColumns: [
887
+ { key: 'id', labelKey: 'id' },
888
+ { key: 'product_id', labelKey: 'productId' },
889
+ { key: 'attribute_id', labelKey: 'attributeId' },
890
+ { key: 'attribute_option_id', labelKey: 'attributeOptionId' },
891
+ { key: 'raw_value', labelKey: 'rawValue' },
892
+ { key: 'value_text', labelKey: 'valueText' },
893
+ { key: 'is_verified', labelKey: 'isVerified', type: 'boolean' },
894
+ ],
895
+ contextualKpi: { translationKey: 'verifiedInSlice', icon: BadgePercent, count: (records) => records.filter((record) => record.is_verified === true).length },
896
+ template: { product_id: null, attribute_id: null, attribute_option_id: null, value_text: '', value_number: null, value_boolean: null, raw_value: '', value_unit: '', normalized_value: '', normalized_text: '', normalized_number: null, source_type: 'manual', confidence_score: null, is_verified: false },
897
+ formSections: [{
898
+ title: ptEn('Valor', 'Value'),
899
+ fields: [
900
+ { key: 'product_id', label: ptEn('Produto', 'Product'), type: 'relation', required: true, relation: relation('/catalog/products', ['name', 'model_name', 'slug'], { resource: 'products' }) },
901
+ { key: 'attribute_id', label: ptEn('Atributo', 'Attribute'), type: 'relation', required: true, relation: relation('/catalog/attributes', ['name', 'code', 'slug'], { resource: 'attributes' }) },
902
+ { key: 'attribute_option_id', label: ptEn('Opção', 'Option'), type: 'relation', relation: relation('/catalog/attribute-options', ['label', 'option_value'], { resource: 'attribute-options' }) },
903
+ { key: 'raw_value', label: ptEn('Valor bruto', 'Raw value'), type: 'textarea', span: 2 },
904
+ { key: 'value_text', label: ptEn('Texto', 'Text'), type: 'text' },
905
+ { key: 'value_number', label: ptEn('Número', 'Number'), type: 'number' },
906
+ { key: 'value_unit', label: ptEn('Unidade', 'Unit'), type: 'text' },
907
+ { key: 'normalized_value', label: ptEn('Valor normalizado', 'Normalized value'), type: 'text' },
908
+ { key: 'source_type', label: ptEn('Origem', 'Source'), type: 'select', options: [{ value: 'manual', label: ptEn('Manual', 'Manual') }, { value: 'import', label: ptEn('Importação', 'Import') }, { value: 'computed', label: ptEn('Calculado', 'Computed') }] },
909
+ { key: 'confidence_score', label: ptEn('Confiança', 'Confidence'), type: 'number' },
910
+ { key: 'is_verified', label: ptEn('Verificado', 'Verified'), type: 'switch' },
911
+ ],
912
+ }],
913
+ }
914
+ );
915
+
916
+ export const catalogResourceMap = new Map(catalogResources.map((resource) => [resource.resource, resource]));
917
+ export const catalogDashboardHref = '/catalog/dashboard';
918
+ export const catalogQuickActionResources = catalogResources.filter((resource) => resource.featured);
919
+ export const catalogKpiResources = ['categories', 'products', 'brands', 'offers'];
920
+
921
+ export function getCatalogRecordLabel(record: Record<string, unknown>) {
922
+ return record.name || record.title || record.label || record.code || record.slug || record.rule_slug || record.option_value || record.external_offer_id || `#${record.id}`;
923
+ }
924
+
925
+ export function getCatalogLocalizedText(value: CatalogLocalizedText, localeCode?: string | null) {
926
+ return localeCode?.toLowerCase().startsWith('pt') ? value.pt : value.en;
927
+ }
928
+
929
+ export const catalogModuleIcon = Boxes;