@hed-hog/catalog 0.0.276

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 (65) hide show
  1. package/dist/catalog-resource.config.d.ts +12 -0
  2. package/dist/catalog-resource.config.d.ts.map +1 -0
  3. package/dist/catalog-resource.config.js +209 -0
  4. package/dist/catalog-resource.config.js.map +1 -0
  5. package/dist/catalog.controller.d.ts +29 -0
  6. package/dist/catalog.controller.d.ts.map +1 -0
  7. package/dist/catalog.controller.js +117 -0
  8. package/dist/catalog.controller.js.map +1 -0
  9. package/dist/catalog.module.d.ts +3 -0
  10. package/dist/catalog.module.d.ts.map +1 -0
  11. package/dist/catalog.module.js +25 -0
  12. package/dist/catalog.module.js.map +1 -0
  13. package/dist/catalog.service.d.ts +36 -0
  14. package/dist/catalog.service.d.ts.map +1 -0
  15. package/dist/catalog.service.js +142 -0
  16. package/dist/catalog.service.js.map +1 -0
  17. package/dist/index.d.ts +2 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +18 -0
  20. package/dist/index.js.map +1 -0
  21. package/hedhog/data/menu.yaml +233 -0
  22. package/hedhog/data/role.yaml +7 -0
  23. package/hedhog/data/route.yaml +56 -0
  24. package/hedhog/frontend/app/[resource]/page.tsx.ejs +684 -0
  25. package/hedhog/frontend/app/_components/catalog-nav.tsx.ejs +85 -0
  26. package/hedhog/frontend/app/_lib/catalog-resources.tsx.ejs +569 -0
  27. package/hedhog/frontend/app/dashboard/page.tsx.ejs +566 -0
  28. package/hedhog/frontend/app/page.tsx.ejs +5 -0
  29. package/hedhog/frontend/messages/en.json +293 -0
  30. package/hedhog/frontend/messages/pt.json +293 -0
  31. package/hedhog/table/catalog_affiliate_program.yaml +41 -0
  32. package/hedhog/table/catalog_attribute.yaml +53 -0
  33. package/hedhog/table/catalog_attribute_group.yaml +18 -0
  34. package/hedhog/table/catalog_brand.yaml +34 -0
  35. package/hedhog/table/catalog_category_attribute.yaml +36 -0
  36. package/hedhog/table/catalog_click_event.yaml +50 -0
  37. package/hedhog/table/catalog_comparison.yaml +65 -0
  38. package/hedhog/table/catalog_comparison_highlight.yaml +39 -0
  39. package/hedhog/table/catalog_comparison_item.yaml +30 -0
  40. package/hedhog/table/catalog_content_relation.yaml +42 -0
  41. package/hedhog/table/catalog_import_run.yaml +33 -0
  42. package/hedhog/table/catalog_import_source.yaml +24 -0
  43. package/hedhog/table/catalog_merchant.yaml +29 -0
  44. package/hedhog/table/catalog_offer.yaml +83 -0
  45. package/hedhog/table/catalog_price_history.yaml +34 -0
  46. package/hedhog/table/catalog_product.yaml +76 -0
  47. package/hedhog/table/catalog_product_attribute_value.yaml +60 -0
  48. package/hedhog/table/catalog_product_category.yaml +26 -0
  49. package/hedhog/table/catalog_product_image.yaml +34 -0
  50. package/hedhog/table/catalog_product_score.yaml +38 -0
  51. package/hedhog/table/catalog_product_site.yaml +47 -0
  52. package/hedhog/table/catalog_product_tag.yaml +19 -0
  53. package/hedhog/table/catalog_score_criterion.yaml +37 -0
  54. package/hedhog/table/catalog_seo_page_rule.yaml +51 -0
  55. package/hedhog/table/catalog_similarity_rule.yaml +28 -0
  56. package/hedhog/table/catalog_site.yaml +40 -0
  57. package/hedhog/table/catalog_site_category.yaml +26 -0
  58. package/package.json +40 -0
  59. package/src/catalog-resource.config.ts +218 -0
  60. package/src/catalog.controller.ts +82 -0
  61. package/src/catalog.module.ts +12 -0
  62. package/src/catalog.service.ts +167 -0
  63. package/src/index.ts +1 -0
  64. package/src/language/en.json +4 -0
  65. package/src/language/pt.json +4 -0
@@ -0,0 +1,85 @@
1
+ 'use client';
2
+
3
+ import { Badge } from '@/components/ui/badge';
4
+ import { cn } from '@/lib/utils';
5
+ import Link from 'next/link';
6
+ import { useTranslations } from 'next-intl';
7
+ import {
8
+ catalogDashboardHref,
9
+ catalogModuleIcon,
10
+ catalogResources,
11
+ } from '../_lib/catalog-resources';
12
+
13
+ type CatalogNavProps = {
14
+ currentHref: string;
15
+ };
16
+
17
+ export function CatalogNav({ currentHref }: CatalogNavProps) {
18
+ const t = useTranslations('catalog');
19
+ const ModuleIcon = catalogModuleIcon;
20
+
21
+ const items = [
22
+ {
23
+ href: catalogDashboardHref,
24
+ label: t('dashboard.title'),
25
+ description: t('dashboard.subtitle'),
26
+ icon: ModuleIcon,
27
+ },
28
+ ...catalogResources.map((resource) => ({
29
+ href: resource.href,
30
+ label: t(`resources.${resource.translationKey}.title`),
31
+ description: t(`resources.${resource.translationKey}.description`),
32
+ icon: resource.icon,
33
+ })),
34
+ ];
35
+
36
+ return (
37
+ <div className="min-w-0">
38
+ <div className="flex gap-2 overflow-x-auto pb-2 md:pb-1 lg:grid lg:grid-cols-2 lg:overflow-visible xl:grid-cols-3 2xl:grid-cols-4">
39
+ {items.map((item) => {
40
+ const active = currentHref === item.href;
41
+ const Icon = item.icon;
42
+
43
+ return (
44
+ <Link
45
+ key={item.href}
46
+ href={item.href}
47
+ className={cn(
48
+ 'group flex w-[280px] max-w-[85vw] shrink-0 snap-start items-start gap-3 rounded-2xl border px-4 py-3 transition-all lg:min-w-0 lg:max-w-none lg:w-full',
49
+ active
50
+ ? 'border-orange-300 bg-orange-50 text-orange-950 shadow-sm'
51
+ : 'border-border/70 bg-card hover:-translate-y-0.5 hover:border-orange-200 hover:bg-orange-50/40'
52
+ )}
53
+ >
54
+ <div
55
+ className={cn(
56
+ 'mt-0.5 rounded-xl p-2 transition-colors',
57
+ active
58
+ ? 'bg-orange-500 text-white'
59
+ : 'bg-muted text-muted-foreground group-hover:bg-orange-100 group-hover:text-orange-700'
60
+ )}
61
+ >
62
+ <Icon className="size-4" />
63
+ </div>
64
+ <div className="min-w-0 space-y-1">
65
+ <div className="flex min-w-0 items-center gap-2">
66
+ <span className="truncate text-sm font-semibold">
67
+ {item.label}
68
+ </span>
69
+ {active ? (
70
+ <Badge className="rounded-full bg-orange-500 px-2 py-0 text-[10px] text-white hover:bg-orange-500">
71
+ {t('navigation.current')}
72
+ </Badge>
73
+ ) : null}
74
+ </div>
75
+ <p className="line-clamp-2 text-xs text-muted-foreground">
76
+ {item.description}
77
+ </p>
78
+ </div>
79
+ </Link>
80
+ );
81
+ })}
82
+ </div>
83
+ </div>
84
+ );
85
+ }
@@ -0,0 +1,569 @@
1
+ import type { LucideIcon } from 'lucide-react';
2
+ import {
3
+ BadgePercent,
4
+ Boxes,
5
+ Building2,
6
+ Eye,
7
+ Factory,
8
+ Globe2,
9
+ Layers3,
10
+ ListFilter,
11
+ PackageSearch,
12
+ PackagePlus,
13
+ Scale,
14
+ SearchCode,
15
+ ShoppingCart,
16
+ Store,
17
+ Tags,
18
+ } from 'lucide-react';
19
+
20
+ type CatalogListVariant = 'cards' | 'table';
21
+
22
+ type CatalogFieldDisplayType = 'text' | 'boolean' | 'currency';
23
+
24
+ export type CatalogFieldDefinition = {
25
+ key: string;
26
+ labelKey: string;
27
+ type?: CatalogFieldDisplayType;
28
+ };
29
+
30
+ export type CatalogFilterOptionDefinition = {
31
+ value: string;
32
+ labelKey: string;
33
+ };
34
+
35
+ export type CatalogContextualKpiDefinition = {
36
+ translationKey: string;
37
+ icon: LucideIcon;
38
+ count: (records: Record<string, unknown>[]) => number;
39
+ };
40
+
41
+ export type CatalogResourceDefinition = {
42
+ resource: string;
43
+ translationKey: string;
44
+ icon: LucideIcon;
45
+ href: string;
46
+ colorClass: string;
47
+ glowClass: string;
48
+ featured?: boolean;
49
+ hasActiveStats?: boolean;
50
+ template: Record<string, unknown>;
51
+ listVariant: CatalogListVariant;
52
+ primaryFilterField: string;
53
+ primaryFilterOptions: CatalogFilterOptionDefinition[];
54
+ titleFields: string[];
55
+ descriptionFields: string[];
56
+ badgeFields: string[];
57
+ cardMetadata: CatalogFieldDefinition[];
58
+ tableColumns?: CatalogFieldDefinition[];
59
+ contextualKpi: CatalogContextualKpiDefinition;
60
+ };
61
+
62
+ const statusFilterOptions: CatalogFilterOptionDefinition[] = [
63
+ { value: 'active', labelKey: 'active' },
64
+ { value: 'inactive', labelKey: 'inactive' },
65
+ { value: 'draft', labelKey: 'draft' },
66
+ { value: 'published', labelKey: 'published' },
67
+ ];
68
+
69
+ const availabilityFilterOptions: CatalogFilterOptionDefinition[] = [
70
+ { value: 'in_stock', labelKey: 'inStock' },
71
+ { value: 'out_of_stock', labelKey: 'outOfStock' },
72
+ { value: 'preorder', labelKey: 'preorder' },
73
+ ];
74
+
75
+ const dataTypeFilterOptions: CatalogFilterOptionDefinition[] = [
76
+ { value: 'text', labelKey: 'text' },
77
+ { value: 'number', labelKey: 'number' },
78
+ { value: 'boolean', labelKey: 'boolean' },
79
+ { value: 'json', labelKey: 'json' },
80
+ ];
81
+
82
+ const facetModeFilterOptions: CatalogFilterOptionDefinition[] = [
83
+ { value: 'default', labelKey: 'default' },
84
+ { value: 'range', labelKey: 'range' },
85
+ { value: 'select', labelKey: 'select' },
86
+ ];
87
+
88
+ export const catalogResources: CatalogResourceDefinition[] = [
89
+ {
90
+ resource: 'brands',
91
+ translationKey: 'brands',
92
+ icon: Factory,
93
+ href: '/catalog/brands',
94
+ colorClass: 'from-orange-500/20 via-amber-500/10 to-transparent',
95
+ glowClass: 'bg-orange-500/10 text-orange-700',
96
+ featured: true,
97
+ hasActiveStats: true,
98
+ listVariant: 'cards',
99
+ primaryFilterField: 'status',
100
+ primaryFilterOptions: statusFilterOptions,
101
+ titleFields: ['name', 'slug'],
102
+ descriptionFields: ['normalized_name', 'website_url'],
103
+ badgeFields: ['status'],
104
+ cardMetadata: [
105
+ { key: 'slug', labelKey: 'slug' },
106
+ { key: 'normalized_name', labelKey: 'normalizedName' },
107
+ { key: 'website_url', labelKey: 'websiteUrl' },
108
+ ],
109
+ contextualKpi: {
110
+ translationKey: 'visible',
111
+ icon: Eye,
112
+ count: (records) => records.length,
113
+ },
114
+ template: {
115
+ slug: '',
116
+ name: '',
117
+ normalized_name: '',
118
+ logo_file_id: null,
119
+ status: 'active',
120
+ website_url: '',
121
+ },
122
+ },
123
+ {
124
+ resource: 'sites',
125
+ translationKey: 'sites',
126
+ icon: Globe2,
127
+ href: '/catalog/sites',
128
+ colorClass: 'from-sky-500/20 via-cyan-500/10 to-transparent',
129
+ glowClass: 'bg-sky-500/10 text-sky-700',
130
+ hasActiveStats: true,
131
+ listVariant: 'cards',
132
+ primaryFilterField: 'status',
133
+ primaryFilterOptions: statusFilterOptions,
134
+ titleFields: ['name', 'domain', 'slug'],
135
+ descriptionFields: ['domain', 'slug'],
136
+ badgeFields: ['status', 'site_type'],
137
+ cardMetadata: [
138
+ { key: 'slug', labelKey: 'slug' },
139
+ { key: 'domain', labelKey: 'domain' },
140
+ { key: 'default_locale_id', labelKey: 'defaultLocaleId' },
141
+ ],
142
+ contextualKpi: {
143
+ translationKey: 'visible',
144
+ icon: Eye,
145
+ count: (records) => records.length,
146
+ },
147
+ template: {
148
+ slug: '',
149
+ name: '',
150
+ domain: '',
151
+ status: 'active',
152
+ site_type: 'portal',
153
+ default_locale_id: 1,
154
+ theme_settings_json: {},
155
+ seo_defaults_json: {},
156
+ },
157
+ },
158
+ {
159
+ resource: 'products',
160
+ translationKey: 'products',
161
+ icon: PackageSearch,
162
+ href: '/catalog/products',
163
+ colorClass: 'from-emerald-500/20 via-green-500/10 to-transparent',
164
+ glowClass: 'bg-emerald-500/10 text-emerald-700',
165
+ featured: true,
166
+ hasActiveStats: true,
167
+ listVariant: 'cards',
168
+ primaryFilterField: 'status',
169
+ primaryFilterOptions: statusFilterOptions,
170
+ titleFields: ['name', 'model_name', 'slug'],
171
+ descriptionFields: ['model_name', 'sku', 'slug'],
172
+ badgeFields: ['status', 'comparison_status', 'is_active'],
173
+ cardMetadata: [
174
+ { key: 'slug', labelKey: 'slug' },
175
+ { key: 'sku', labelKey: 'sku' },
176
+ { key: 'brand_id', labelKey: 'brandId' },
177
+ { key: 'category_id', labelKey: 'categoryId' },
178
+ ],
179
+ contextualKpi: {
180
+ translationKey: 'activeInSlice',
181
+ icon: PackageSearch,
182
+ count: (records) =>
183
+ records.filter((record) => record.is_active === true).length,
184
+ },
185
+ template: {
186
+ brand_id: 1,
187
+ category_id: 1,
188
+ slug: '',
189
+ name: '',
190
+ status: 'draft',
191
+ comparison_status: 'draft',
192
+ is_active: true,
193
+ },
194
+ },
195
+ {
196
+ resource: 'attribute-groups',
197
+ translationKey: 'attributeGroups',
198
+ icon: Layers3,
199
+ href: '/catalog/attribute-groups',
200
+ colorClass: 'from-violet-500/20 via-fuchsia-500/10 to-transparent',
201
+ glowClass: 'bg-violet-500/10 text-violet-700',
202
+ hasActiveStats: true,
203
+ listVariant: 'table',
204
+ primaryFilterField: 'status',
205
+ primaryFilterOptions: statusFilterOptions,
206
+ titleFields: ['name', 'slug'],
207
+ descriptionFields: ['slug'],
208
+ badgeFields: ['status'],
209
+ cardMetadata: [
210
+ { key: 'slug', labelKey: 'slug' },
211
+ { key: 'status', labelKey: 'status' },
212
+ ],
213
+ tableColumns: [
214
+ { key: 'id', labelKey: 'id' },
215
+ { key: 'name', labelKey: 'name' },
216
+ { key: 'slug', labelKey: 'slug' },
217
+ { key: 'status', labelKey: 'status' },
218
+ ],
219
+ contextualKpi: {
220
+ translationKey: 'visible',
221
+ icon: Eye,
222
+ count: (records) => records.length,
223
+ },
224
+ template: {
225
+ slug: '',
226
+ name: '',
227
+ status: 'active',
228
+ },
229
+ },
230
+ {
231
+ resource: 'attributes',
232
+ translationKey: 'attributes',
233
+ icon: Tags,
234
+ href: '/catalog/attributes',
235
+ colorClass: 'from-indigo-500/20 via-blue-500/10 to-transparent',
236
+ glowClass: 'bg-indigo-500/10 text-indigo-700',
237
+ listVariant: 'table',
238
+ primaryFilterField: 'data_type',
239
+ primaryFilterOptions: dataTypeFilterOptions,
240
+ titleFields: ['label', 'slug'],
241
+ descriptionFields: ['description', 'slug'],
242
+ badgeFields: ['data_type', 'comparison_mode'],
243
+ cardMetadata: [
244
+ { key: 'group_id', labelKey: 'groupId' },
245
+ { key: 'is_filterable', labelKey: 'isFilterable', type: 'boolean' },
246
+ {
247
+ key: 'is_required_for_comparison',
248
+ labelKey: 'requiredForComparison',
249
+ type: 'boolean',
250
+ },
251
+ ],
252
+ tableColumns: [
253
+ { key: 'id', labelKey: 'id' },
254
+ { key: 'label', labelKey: 'label' },
255
+ { key: 'slug', labelKey: 'slug' },
256
+ { key: 'data_type', labelKey: 'dataType' },
257
+ { key: 'comparison_mode', labelKey: 'comparisonMode' },
258
+ ],
259
+ contextualKpi: {
260
+ translationKey: 'filterableInSlice',
261
+ icon: ListFilter,
262
+ count: (records) =>
263
+ records.filter((record) => record.is_filterable === true).length,
264
+ },
265
+ template: {
266
+ group_id: 1,
267
+ slug: '',
268
+ label: '',
269
+ data_type: 'text',
270
+ comparison_mode: 'neutral',
271
+ is_filterable: true,
272
+ is_sortable: false,
273
+ is_required_for_comparison: false,
274
+ normalization_rule_json: {},
275
+ display_order: 0,
276
+ },
277
+ },
278
+ {
279
+ resource: 'category-attributes',
280
+ translationKey: 'categoryAttributes',
281
+ icon: BadgePercent,
282
+ href: '/catalog/category-attributes',
283
+ colorClass: 'from-pink-500/20 via-rose-500/10 to-transparent',
284
+ glowClass: 'bg-pink-500/10 text-pink-700',
285
+ listVariant: 'table',
286
+ primaryFilterField: 'facet_mode',
287
+ primaryFilterOptions: facetModeFilterOptions,
288
+ titleFields: ['attribute_id', 'category_id'],
289
+ descriptionFields: ['facet_mode'],
290
+ badgeFields: ['facet_mode', 'is_required', 'is_highlighted'],
291
+ cardMetadata: [
292
+ { key: 'category_id', labelKey: 'categoryId' },
293
+ { key: 'attribute_id', labelKey: 'attributeId' },
294
+ { key: 'weight', labelKey: 'weight' },
295
+ ],
296
+ tableColumns: [
297
+ { key: 'id', labelKey: 'id' },
298
+ { key: 'category_id', labelKey: 'categoryId' },
299
+ { key: 'attribute_id', labelKey: 'attributeId' },
300
+ { key: 'facet_mode', labelKey: 'facetMode' },
301
+ { key: 'is_required', labelKey: 'isRequired', type: 'boolean' },
302
+ { key: 'is_highlighted', labelKey: 'isHighlighted', type: 'boolean' },
303
+ ],
304
+ contextualKpi: {
305
+ translationKey: 'requiredInSlice',
306
+ icon: BadgePercent,
307
+ count: (records) =>
308
+ records.filter((record) => record.is_required === true).length,
309
+ },
310
+ template: {
311
+ category_id: 1,
312
+ attribute_id: 1,
313
+ is_required: false,
314
+ is_highlighted: true,
315
+ display_order: 0,
316
+ weight: 1,
317
+ facet_mode: 'default',
318
+ },
319
+ },
320
+ {
321
+ resource: 'comparisons',
322
+ translationKey: 'comparisons',
323
+ icon: Scale,
324
+ href: '/catalog/comparisons',
325
+ colorClass: 'from-amber-500/20 via-yellow-500/10 to-transparent',
326
+ glowClass: 'bg-amber-500/10 text-amber-700',
327
+ hasActiveStats: true,
328
+ listVariant: 'cards',
329
+ primaryFilterField: 'status',
330
+ primaryFilterOptions: statusFilterOptions,
331
+ titleFields: ['title', 'slug'],
332
+ descriptionFields: ['summary', 'slug'],
333
+ badgeFields: ['status', 'comparison_type', 'generation_mode'],
334
+ cardMetadata: [
335
+ { key: 'slug', labelKey: 'slug' },
336
+ { key: 'site_id', labelKey: 'siteId' },
337
+ { key: 'category_id', labelKey: 'categoryId' },
338
+ ],
339
+ contextualKpi: {
340
+ translationKey: 'publishedInSlice',
341
+ icon: Scale,
342
+ count: (records) =>
343
+ records.filter((record) =>
344
+ ['active', 'published'].includes(String(record.status ?? ''))
345
+ ).length,
346
+ },
347
+ template: {
348
+ category_id: 1,
349
+ site_id: 1,
350
+ slug: '',
351
+ comparison_type: 'manual',
352
+ generation_mode: 'manual',
353
+ status: 'draft',
354
+ title: '',
355
+ summary: '',
356
+ intro: '',
357
+ verdict: '',
358
+ },
359
+ },
360
+ {
361
+ resource: 'offers',
362
+ translationKey: 'offers',
363
+ icon: ShoppingCart,
364
+ href: '/catalog/offers',
365
+ colorClass: 'from-red-500/20 via-orange-500/10 to-transparent',
366
+ glowClass: 'bg-red-500/10 text-red-700',
367
+ featured: true,
368
+ listVariant: 'cards',
369
+ primaryFilterField: 'availability_status',
370
+ primaryFilterOptions: availabilityFilterOptions,
371
+ titleFields: ['title', 'external_offer_id'],
372
+ descriptionFields: ['external_offer_id', 'affiliate_url'],
373
+ badgeFields: ['availability_status', 'is_featured'],
374
+ cardMetadata: [
375
+ { key: 'price_amount', labelKey: 'priceAmount', type: 'currency' },
376
+ { key: 'merchant_id', labelKey: 'merchantId' },
377
+ { key: 'product_id', labelKey: 'productId' },
378
+ ],
379
+ contextualKpi: {
380
+ translationKey: 'inStockInSlice',
381
+ icon: ShoppingCart,
382
+ count: (records) =>
383
+ records.filter((record) => record.availability_status === 'in_stock')
384
+ .length,
385
+ },
386
+ template: {
387
+ product_id: 1,
388
+ merchant_id: 1,
389
+ title: '',
390
+ price_amount: 0,
391
+ price_currency: 'BRL',
392
+ availability_status: 'in_stock',
393
+ priority_score: 0,
394
+ is_featured: false,
395
+ },
396
+ },
397
+ {
398
+ resource: 'merchants',
399
+ translationKey: 'merchants',
400
+ icon: Store,
401
+ href: '/catalog/merchants',
402
+ colorClass: 'from-lime-500/20 via-green-500/10 to-transparent',
403
+ glowClass: 'bg-lime-500/10 text-lime-700',
404
+ featured: true,
405
+ hasActiveStats: true,
406
+ listVariant: 'cards',
407
+ primaryFilterField: 'status',
408
+ primaryFilterOptions: statusFilterOptions,
409
+ titleFields: ['name', 'slug'],
410
+ descriptionFields: ['slug'],
411
+ badgeFields: ['status', 'merchant_type'],
412
+ cardMetadata: [
413
+ { key: 'slug', labelKey: 'slug' },
414
+ { key: 'merchant_type', labelKey: 'merchantType' },
415
+ { key: 'logo_file_id', labelKey: 'logoFileId' },
416
+ ],
417
+ contextualKpi: {
418
+ translationKey: 'visible',
419
+ icon: Eye,
420
+ count: (records) => records.length,
421
+ },
422
+ template: {
423
+ slug: '',
424
+ name: '',
425
+ status: 'active',
426
+ merchant_type: 'retailer',
427
+ },
428
+ },
429
+ {
430
+ resource: 'affiliate-programs',
431
+ translationKey: 'affiliatePrograms',
432
+ icon: Building2,
433
+ href: '/catalog/affiliate-programs',
434
+ colorClass: 'from-cyan-500/20 via-teal-500/10 to-transparent',
435
+ glowClass: 'bg-cyan-500/10 text-cyan-700',
436
+ hasActiveStats: true,
437
+ listVariant: 'cards',
438
+ primaryFilterField: 'status',
439
+ primaryFilterOptions: statusFilterOptions,
440
+ titleFields: ['name', 'slug'],
441
+ descriptionFields: ['slug', 'network_type'],
442
+ badgeFields: ['status', 'network_type', 'commission_type'],
443
+ cardMetadata: [
444
+ { key: 'merchant_id', labelKey: 'merchantId' },
445
+ { key: 'commission_type', labelKey: 'commissionType' },
446
+ {
447
+ key: 'default_commission_value',
448
+ labelKey: 'defaultCommissionValue',
449
+ type: 'currency',
450
+ },
451
+ ],
452
+ contextualKpi: {
453
+ translationKey: 'visible',
454
+ icon: Eye,
455
+ count: (records) => records.length,
456
+ },
457
+ template: {
458
+ merchant_id: 1,
459
+ slug: '',
460
+ name: '',
461
+ network_type: 'direct',
462
+ commission_type: 'percentage',
463
+ default_commission_value: 0,
464
+ status: 'active',
465
+ },
466
+ },
467
+ {
468
+ resource: 'seo-rules',
469
+ translationKey: 'seoRules',
470
+ icon: SearchCode,
471
+ href: '/catalog/seo-rules',
472
+ colorClass: 'from-slate-500/20 via-zinc-500/10 to-transparent',
473
+ glowClass: 'bg-slate-500/10 text-slate-700',
474
+ featured: true,
475
+ hasActiveStats: true,
476
+ listVariant: 'cards',
477
+ primaryFilterField: 'status',
478
+ primaryFilterOptions: statusFilterOptions,
479
+ titleFields: ['rule_slug', 'page_type'],
480
+ descriptionFields: ['page_type', 'canonical_strategy'],
481
+ badgeFields: ['status', 'page_type', 'canonical_strategy'],
482
+ cardMetadata: [
483
+ { key: 'site_id', labelKey: 'siteId' },
484
+ { key: 'category_id', labelKey: 'categoryId' },
485
+ { key: 'priority', labelKey: 'priority' },
486
+ ],
487
+ contextualKpi: {
488
+ translationKey: 'visible',
489
+ icon: Eye,
490
+ count: (records) => records.length,
491
+ },
492
+ template: {
493
+ site_id: 1,
494
+ page_type: 'comparison',
495
+ rule_slug: '',
496
+ status: 'active',
497
+ generation_query_json: {},
498
+ min_product_count: 2,
499
+ min_attribute_coverage: 1,
500
+ canonical_strategy: 'self',
501
+ priority: 0,
502
+ template_json: {},
503
+ },
504
+ },
505
+ {
506
+ resource: 'import-sources',
507
+ translationKey: 'importSources',
508
+ icon: PackagePlus,
509
+ href: '/catalog/import-sources',
510
+ colorClass: 'from-stone-500/20 via-neutral-500/10 to-transparent',
511
+ glowClass: 'bg-stone-500/10 text-stone-700',
512
+ featured: true,
513
+ hasActiveStats: true,
514
+ listVariant: 'cards',
515
+ primaryFilterField: 'status',
516
+ primaryFilterOptions: statusFilterOptions,
517
+ titleFields: ['name', 'slug'],
518
+ descriptionFields: ['slug', 'source_type'],
519
+ badgeFields: ['status', 'source_type'],
520
+ cardMetadata: [
521
+ { key: 'slug', labelKey: 'slug' },
522
+ { key: 'source_type', labelKey: 'sourceType' },
523
+ { key: 'status', labelKey: 'status' },
524
+ ],
525
+ contextualKpi: {
526
+ translationKey: 'visible',
527
+ icon: Eye,
528
+ count: (records) => records.length,
529
+ },
530
+ template: {
531
+ slug: '',
532
+ name: '',
533
+ source_type: 'api',
534
+ config_json: {},
535
+ status: 'active',
536
+ },
537
+ },
538
+ ];
539
+
540
+ export const catalogResourceMap = new Map(
541
+ catalogResources.map((resource) => [resource.resource, resource])
542
+ );
543
+
544
+ export const catalogDashboardHref = '/catalog/dashboard';
545
+
546
+ export const catalogQuickActionResources = catalogResources.filter(
547
+ (resource) => resource.featured
548
+ );
549
+
550
+ export const catalogKpiResources = [
551
+ 'products',
552
+ 'brands',
553
+ 'offers',
554
+ 'merchants',
555
+ ];
556
+
557
+ export function getCatalogRecordLabel(record: Record<string, unknown>) {
558
+ return (
559
+ record.name ||
560
+ record.title ||
561
+ record.label ||
562
+ record.slug ||
563
+ record.rule_slug ||
564
+ record.external_offer_id ||
565
+ `#${record.id}`
566
+ );
567
+ }
568
+
569
+ export const catalogModuleIcon = Boxes;