wagtail-enap-designsystem 1.2.1.144__py3-none-any.whl → 1.2.1.146__py3-none-any.whl

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.

Potentially problematic release.


This version of wagtail-enap-designsystem might be problematic. Click here for more details.

Files changed (41) hide show
  1. enap_designsystem/blocks/__init__.py +2 -0
  2. enap_designsystem/blocks/content_blocks.py +2 -1
  3. enap_designsystem/blocks/html_blocks.py +154 -77
  4. enap_designsystem/blocks/semana_blocks.py +1 -1
  5. enap_designsystem/migrations/0415_showcasecomponentesdireto_debug_mode_and_more.py +77346 -0
  6. enap_designsystem/migrations/0416_alter_showcasecomponentesdireto_debug_mode_and_more.py +33 -0
  7. enap_designsystem/migrations/0417_alter_showcasecomponentesdireto_options_and_more.py +53 -0
  8. enap_designsystem/models.py +798 -147
  9. enap_designsystem/static/enap_designsystem/blocks/accordions.css +1 -0
  10. enap_designsystem/static/enap_designsystem/blocks/apresentacao_block.css +174 -0
  11. enap_designsystem/static/enap_designsystem/blocks/cards_section.css +112 -0
  12. enap_designsystem/static/enap_designsystem/blocks/cards_titles.css +229 -0
  13. enap_designsystem/static/enap_designsystem/blocks/carousel_images.css +323 -0
  14. enap_designsystem/static/enap_designsystem/blocks/carousel_responsive.css +522 -0
  15. enap_designsystem/static/enap_designsystem/blocks/carousel_responsivo_snippet.css +294 -0
  16. enap_designsystem/static/enap_designsystem/blocks/clientes_block.css +146 -0
  17. enap_designsystem/static/enap_designsystem/blocks/semana.css +6 -5
  18. enap_designsystem/templates/enap_designsystem/base.html +8 -1
  19. enap_designsystem/templates/enap_designsystem/blocks/apresentacao_block.html +25 -29
  20. enap_designsystem/templates/enap_designsystem/blocks/apresentacao_simple_block.html +14 -15
  21. enap_designsystem/templates/enap_designsystem/blocks/banner.html +14 -1
  22. enap_designsystem/templates/enap_designsystem/blocks/banner_logo.html +15 -1
  23. enap_designsystem/templates/enap_designsystem/blocks/card_block.html +2 -2
  24. enap_designsystem/templates/enap_designsystem/blocks/cards_section.html +2 -109
  25. enap_designsystem/templates/enap_designsystem/blocks/cards_titles.html +31 -30
  26. enap_designsystem/templates/enap_designsystem/blocks/carousel.html +1 -187
  27. enap_designsystem/templates/enap_designsystem/blocks/carousel_images.html +17 -314
  28. enap_designsystem/templates/enap_designsystem/blocks/carousel_responsive.html +0 -328
  29. enap_designsystem/templates/enap_designsystem/blocks/carousel_responsivo_snippet.html +3 -197
  30. enap_designsystem/templates/enap_designsystem/blocks/clientes_block.html +9 -152
  31. enap_designsystem/templates/enap_designsystem/blocks/footer_block.html +122 -0
  32. enap_designsystem/templates/enap_designsystem/blocks/logos_simple_block.html +1 -22
  33. enap_designsystem/templates/enap_designsystem/blocks/section_block.html +1 -1
  34. enap_designsystem/templates/enap_designsystem/blocks/video_hero_banner.html +4 -10
  35. enap_designsystem/templates/enap_designsystem/pages/showcase_components.html +1168 -444
  36. enap_designsystem/templates/enap_designsystem/semana_inovacao/blocks_menu_navigation.html +2 -2
  37. {wagtail_enap_designsystem-1.2.1.144.dist-info → wagtail_enap_designsystem-1.2.1.146.dist-info}/METADATA +1 -1
  38. {wagtail_enap_designsystem-1.2.1.144.dist-info → wagtail_enap_designsystem-1.2.1.146.dist-info}/RECORD +41 -30
  39. {wagtail_enap_designsystem-1.2.1.144.dist-info → wagtail_enap_designsystem-1.2.1.146.dist-info}/WHEEL +0 -0
  40. {wagtail_enap_designsystem-1.2.1.144.dist-info → wagtail_enap_designsystem-1.2.1.146.dist-info}/licenses/LICENSE +0 -0
  41. {wagtail_enap_designsystem-1.2.1.144.dist-info → wagtail_enap_designsystem-1.2.1.146.dist-info}/top_level.txt +0 -0
@@ -5028,231 +5028,882 @@ class SistemaVotacaoPage(Page):
5028
5028
 
5029
5029
 
5030
5030
 
5031
- from wagtail.models import Page
5032
- from wagtail.fields import RichTextField, StreamField
5033
- from wagtail.admin.panels import FieldPanel
5034
5031
 
5035
- from .blocks import LAYOUT_STREAMBLOCKS
5032
+
5033
+
5034
+
5035
+
5036
+ import logging
5037
+ import traceback
5038
+ from wagtail.blocks import StreamBlock, StructBlock, ListBlock
5039
+
5040
+ logger = logging.getLogger(__name__)
5036
5041
 
5037
5042
  class ShowcaseComponentesDireto(Page):
5038
5043
  """
5039
- Showcase que renderiza componentes diretamente no template
5040
- usando valores padrão dos componentes
5044
+ Showcase que renderiza componentes COMPLETOS visualmente
5041
5045
  """
5042
5046
 
5043
5047
  description = RichTextField(
5044
5048
  verbose_name="Descrição",
5045
5049
  help_text="Descrição da biblioteca de componentes",
5046
5050
  blank=True,
5047
- default="Showcase com renderização direta dos componentes usando valores padrão"
5051
+ default="Showcase visual dos componentes completos"
5052
+ )
5053
+
5054
+ debug_mode = models.BooleanField(
5055
+ default=False, # False por padrão para foco na estética
5056
+ verbose_name="Modo Debug",
5057
+ help_text="Ativar para ver informações técnicas"
5058
+ )
5059
+
5060
+ show_errors_only = models.BooleanField(
5061
+ default=False,
5062
+ verbose_name="Mostrar Apenas Erros",
5063
+ help_text="Mostrar apenas componentes que falharam na renderização"
5064
+ )
5065
+
5066
+ filter_category = models.CharField(
5067
+ max_length=100,
5068
+ blank=True,
5069
+ verbose_name="Filtrar Categoria",
5070
+ help_text="banners, galerias, carousels, dashboards, formularios, cursos, eventos, navegacao, botoes, conteudo, secoes, cards, interativos, midia, especialidades"
5048
5071
  )
5049
5072
 
5050
5073
  content_panels = Page.content_panels + [
5051
5074
  FieldPanel('description'),
5075
+ FieldPanel('filter_category'),
5076
+ FieldPanel('show_errors_only'),
5077
+ FieldPanel('debug_mode'),
5052
5078
  ]
5053
5079
 
5054
5080
  template = 'enap_designsystem/pages/showcase_components.html'
5055
5081
 
5056
5082
  class Meta:
5057
- verbose_name = "Showcase Direto de Componentes"
5058
- verbose_name_plural = "Showcases Diretos de Componentes"
5083
+ verbose_name = "Showcase Visual de Componentes"
5084
+ verbose_name_plural = "Showcases Visuais de Componentes"
5059
5085
 
5060
5086
  def get_context(self, request):
5061
5087
  context = super().get_context(request)
5088
+ debug_log = []
5062
5089
 
5063
5090
  try:
5064
- # Organizar componentes por categoria
5065
- categories_with_components = self.get_rendered_categories()
5091
+ # Importar LAYOUT_STREAMBLOCKS
5092
+ debug_log.append("Importando LAYOUT_STREAMBLOCKS...")
5093
+
5094
+ try:
5095
+ from .blocks import LAYOUT_STREAMBLOCKS
5096
+ debug_log.append(f"✅ Importado: {len(LAYOUT_STREAMBLOCKS)} categorias")
5097
+ except Exception as e:
5098
+ debug_log.append(f"❌ Erro de import: {str(e)}")
5099
+ raise Exception(f"Erro ao importar LAYOUT_STREAMBLOCKS: {str(e)}")
5100
+
5101
+ # Descobrir e renderizar componentes COMPLETOS
5102
+ categories_with_components = self.discover_and_render_complete_components(LAYOUT_STREAMBLOCKS, debug_log)
5103
+
5104
+ # Aplicar filtros
5105
+ if self.filter_category:
5106
+ categories_with_components = {
5107
+ k: v for k, v in categories_with_components.items()
5108
+ if k == self.filter_category
5109
+ }
5110
+ debug_log.append(f"Filtro aplicado: {self.filter_category}")
5111
+
5112
+ if self.show_errors_only:
5113
+ filtered_categories = {}
5114
+ for cat_name, cat_data in categories_with_components.items():
5115
+ error_components = [c for c in cat_data['rendered_components'] if c.get('has_error', False)]
5116
+ if error_components:
5117
+ cat_data_copy = cat_data.copy()
5118
+ cat_data_copy['rendered_components'] = error_components
5119
+ filtered_categories[cat_name] = cat_data_copy
5120
+ categories_with_components = filtered_categories
5121
+ debug_log.append("Filtro de erros aplicado")
5122
+
5123
+ # Calcular estatísticas
5124
+ total_components = sum(len(cat['rendered_components']) for cat in categories_with_components.values())
5125
+ components_ok = sum(
5126
+ len([c for c in cat['rendered_components'] if not c.get('has_error', False)])
5127
+ for cat in categories_with_components.values()
5128
+ )
5129
+ components_error = total_components - components_ok
5066
5130
 
5067
5131
  context.update({
5068
5132
  'categories_with_components': categories_with_components,
5069
- 'total_components': sum(len(cat['rendered_components']) for cat in categories_with_components.values()),
5133
+ 'total_components': total_components,
5134
+ 'components_ok': components_ok,
5135
+ 'components_error': components_error,
5070
5136
  'total_categories': len(categories_with_components),
5071
- 'page_title': 'Showcase Direto - Componentes ENAP',
5072
- 'page_description': 'Visualização direta dos componentes com valores padrão'
5137
+ 'page_title': 'Showcase Visual - Componentes ENAP',
5138
+ 'page_description': 'Visualização estética dos componentes do Design System',
5139
+ 'debug_log': debug_log if self.debug_mode else [],
5140
+ 'debug_mode': self.debug_mode,
5141
+ 'available_categories': [name for name, _ in LAYOUT_STREAMBLOCKS],
5073
5142
  })
5143
+
5074
5144
  except Exception as e:
5145
+ debug_log.append(f"❌ ERRO GERAL: {str(e)}")
5146
+ debug_log.append(f"Traceback: {traceback.format_exc()}")
5147
+ logger.error(f"Erro no showcase: {e}", exc_info=True)
5148
+
5075
5149
  context.update({
5076
5150
  'error_message': f'Erro ao carregar componentes: {str(e)}',
5077
5151
  'categories_with_components': {},
5078
5152
  'total_components': 0,
5153
+ 'components_ok': 0,
5154
+ 'components_error': 0,
5079
5155
  'total_categories': 0,
5156
+ 'debug_log': debug_log,
5157
+ 'debug_mode': self.debug_mode,
5158
+ 'available_categories': [],
5080
5159
  })
5081
5160
 
5082
5161
  return context
5083
5162
 
5084
- def get_rendered_categories(self):
5085
- """Retorna categorias com componentes renderizados"""
5163
+ def discover_and_render_complete_components(self, layout_streamblocks, debug_log):
5164
+ """Descobre e renderiza componentes COMPLETOS, não os campos individuais"""
5086
5165
  categories = {}
5087
- layout_streamblocks = LAYOUT_STREAMBLOCKS
5088
-
5089
- # Mapeamento de ícones
5090
- category_icons = {
5091
- 'banners': 'image',
5092
- 'galerias': 'images',
5093
- 'carousels': 'arrows-up-down',
5094
- 'dashboards': 'bar-chart',
5095
- 'formularios': 'form',
5096
- 'cursos': 'graduation-cap',
5097
- 'eventos': 'calendar',
5098
- 'navegacao': 'bars',
5099
- 'menus': 'list-ul',
5100
- 'botoes': 'mouse-pointer',
5101
- 'conteudo': 'file-text',
5102
- 'secoes': 'th-large',
5103
- 'cards': 'clone',
5104
- 'interativos': 'cogs',
5105
- 'midia': 'play-circle',
5106
- 'especialidades': 'puzzle-piece',
5107
- }
5108
-
5109
- # Descrições das categorias
5110
- category_descriptions = {
5111
- 'banners': 'Componentes para banners, heroes e seções de destaque visual',
5112
- 'galerias': 'Componentes para exibição de imagens, galerias e portfolios',
5113
- 'carousels': 'Componentes de carrosséis, sliders e apresentações rotativas',
5114
- 'dashboards': 'Componentes para dashboards, KPIs, métricas e visualização de dados',
5115
- 'formularios': 'Componentes de formulários, campos de entrada e interação',
5116
- 'cursos': 'Componentes específicos para cursos, educação e capacitação',
5117
- 'eventos': 'Componentes para eventos, cronogramas, timelines e agenda',
5118
- 'navegacao': 'Componentes de navegação, menus e estrutura de site',
5119
- 'menus': 'Componentes de menu e navegação',
5120
- 'botoes': 'Componentes de botões, call-to-actions e elementos clicáveis',
5121
- 'conteudo': 'Componentes de conteúdo, texto rico, citações e mídia',
5122
- 'secoes': 'Componentes de seções, containers e estruturas de layout',
5123
- 'cards': 'Componentes de cards, grids e elementos organizacionais',
5124
- 'interativos': 'Componentes interativos, acordeões, modais e elementos dinâmicos',
5125
- 'midia': 'Componentes de vídeo, áudio, podcasts e conteúdo multimídia',
5126
- 'especialidades': 'Componentes especializados e funcionalidades específicas da ENAP',
5127
- }
5166
+ debug_log.append("\n=== DESCOBRINDO COMPONENTES COMPLETOS ===")
5128
5167
 
5129
5168
  for stream_name, stream_block in layout_streamblocks:
5130
- # Pular categorias complexas
5131
- if stream_name in ['enap_section', 'recaptcha']:
5132
- continue
5169
+ debug_log.append(f"\nProcessando categoria: {stream_name}")
5133
5170
 
5134
- if not hasattr(stream_block, 'child_blocks'):
5171
+ # Pular categorias que são muito complexas ou não visuais
5172
+ if self.should_skip_category(stream_name):
5173
+ debug_log.append(f" ⏭️ Pulando categoria complexa: {stream_name}")
5135
5174
  continue
5136
-
5175
+
5176
+ # Criar categoria
5137
5177
  category_info = {
5138
5178
  'name': stream_name,
5139
- 'display_name': self.get_category_display_name(stream_block, stream_name),
5140
- 'icon': category_icons.get(stream_name, 'cube'),
5141
- 'description': category_descriptions.get(stream_name, f'Componentes da categoria {stream_name}'),
5179
+ 'display_name': self.format_category_name(stream_name),
5180
+ 'icon': self.get_category_icon(stream_name),
5181
+ 'description': self.get_category_description(stream_name),
5142
5182
  'rendered_components': []
5143
5183
  }
5144
5184
 
5145
- # Renderizar cada componente da categoria
5146
- for component_name, component_block in stream_block.child_blocks.items():
5147
- rendered_component = self.render_component_with_defaults(
5148
- component_name, component_block, stream_name
5185
+ # Extrair componentes da categoria
5186
+ if hasattr(stream_block, 'child_blocks') and stream_block.child_blocks:
5187
+ debug_log.append(f" 📦 Categoria com {len(stream_block.child_blocks)} componentes")
5188
+
5189
+ for comp_name, comp_block in stream_block.child_blocks.items():
5190
+ debug_log.append(f" 🔧 Renderizando: {comp_name}")
5191
+
5192
+ # Renderizar o componente COMPLETO
5193
+ rendered_comp = self.render_complete_component(
5194
+ comp_name, comp_block, stream_name, debug_log
5195
+ )
5196
+
5197
+ if rendered_comp:
5198
+ category_info['rendered_components'].append(rendered_comp)
5199
+ status = "❌" if rendered_comp.get('has_error') else "✅"
5200
+ debug_log.append(f" {status} Adicionado")
5201
+ else:
5202
+ debug_log.append(f" ❌ Falhou completamente")
5203
+
5204
+ elif self.is_single_renderable_component(stream_block):
5205
+ # Se a categoria é um componente único
5206
+ debug_log.append(f" 🎯 Categoria é componente único")
5207
+ rendered_comp = self.render_complete_component(
5208
+ stream_name, stream_block, stream_name, debug_log
5149
5209
  )
5150
- if rendered_component:
5151
- category_info['rendered_components'].append(rendered_component)
5210
+ if rendered_comp:
5211
+ category_info['rendered_components'].append(rendered_comp)
5212
+
5213
+ else:
5214
+ debug_log.append(f" ⚠️ Categoria não processável: {stream_block.__class__.__name__}")
5152
5215
 
5153
- # adicionar categoria se tiver componentes renderizados
5216
+ # Adicionar categoria se tiver componentes
5154
5217
  if category_info['rendered_components']:
5155
5218
  categories[stream_name] = category_info
5219
+ debug_log.append(f" ✅ Categoria adicionada com {len(category_info['rendered_components'])} componentes")
5220
+ else:
5221
+ debug_log.append(f" ❌ Categoria ignorada (sem componentes)")
5156
5222
 
5223
+ debug_log.append(f"\n📊 TOTAL: {len(categories)} categorias processadas")
5157
5224
  return categories
5158
5225
 
5159
- def get_category_display_name(self, stream_block, stream_name):
5160
- """Obtém nome de exibição da categoria"""
5161
- try:
5162
- category_meta = getattr(stream_block.__class__, 'Meta', None)
5163
- if category_meta and hasattr(category_meta, 'label'):
5164
- return category_meta.label
5165
- except:
5166
- pass
5226
+ def should_skip_category(self, stream_name):
5227
+ """Determina se deve pular uma categoria"""
5228
+ skip_list = [
5229
+ 'enap_section', # Muito complexa
5230
+ 'recaptcha', # Não visual
5231
+ ]
5232
+ return stream_name in skip_list
5233
+
5234
+ def is_single_renderable_component(self, block):
5235
+ """Verifica se o bloco é um componente único renderizável"""
5236
+ class_name = block.__class__.__name__
5167
5237
 
5168
- return stream_name.replace('_', ' ').title()
5238
+ # Se termina com Block e tem método render, é renderizável
5239
+ if class_name.endswith('Block') and hasattr(block, 'render'):
5240
+ return True
5241
+
5242
+ return False
5169
5243
 
5170
- def render_component_with_defaults(self, component_name, component_block, category_name):
5171
- """Renderiza um componente usando valores padrão"""
5244
+ def render_complete_component(self, comp_name, comp_block, category_name, debug_log):
5245
+ """Renderiza um componente COMPLETO com dados realistas"""
5172
5246
  try:
5173
- # Gerar dados padrão inteligentes
5174
- default_data = self.get_smart_defaults(component_name, component_block)
5247
+ debug_log.append(f" 🎨 Tipo: {comp_block.__class__.__name__}")
5248
+
5249
+ component_data = self.generate_realistic_component_data(comp_name, comp_block, debug_log)
5175
5250
 
5176
- # Tentar renderizar
5177
5251
  try:
5178
- rendered_html = component_block.render(default_data)
5179
- except Exception as render_error:
5180
- # Se falhar com dados padrão, tentar sem dados
5252
+ if hasattr(comp_block, 'child_blocks'):
5253
+ for field_name, field_block in comp_block.child_blocks.items():
5254
+ if field_name in component_data:
5255
+ field_type = field_block.__class__.__name__
5256
+ value = component_data[field_name]
5257
+
5258
+ if 'Stream' in field_type and not isinstance(value, list):
5259
+ component_data[field_name] = []
5260
+ debug_log.append(f" 🔧 Corrigido {field_name}: StreamBlock precisa de lista")
5261
+
5262
+ elif 'List' in field_type and not isinstance(value, list):
5263
+ component_data[field_name] = []
5264
+ debug_log.append(f" 🔧 Corrigido {field_name}: ListBlock precisa de lista")
5265
+
5266
+ elif 'Choice' in field_type and not isinstance(value, str):
5267
+ component_data[field_name] = 'opcao1'
5268
+ debug_log.append(f" 🔧 Corrigido {field_name}: ChoiceBlock precisa de string")
5269
+
5270
+ elif 'RichText' in field_type and not isinstance(value, str):
5271
+ component_data[field_name] = '<p>Texto exemplo</p>'
5272
+ debug_log.append(f" 🔧 Corrigido {field_name}: RichText precisa de string")
5273
+
5274
+ except Exception as validation_error:
5275
+ debug_log.append(f" ⚠️ Erro na validação: {str(validation_error)}")
5276
+ component_data = {}
5277
+
5278
+ rendered_html = None
5279
+ render_error = None
5280
+
5281
+ try:
5282
+ debug_log.append(f" 🎯 Tentando renderizar componente completo...")
5283
+ rendered_html = comp_block.render(component_data)
5284
+ debug_log.append(f" ✅ Renderizado com sucesso ({len(rendered_html)} chars)")
5285
+ has_error = False
5286
+ except Exception as e:
5287
+ debug_log.append(f" ❌ Erro na renderização: {str(e)}")
5288
+ render_error = str(e)
5289
+
5181
5290
  try:
5182
- rendered_html = component_block.render({})
5183
- except:
5184
- # Se ainda falhar, criar um placeholder
5185
- rendered_html = f'<div style="padding: 1rem; background: #f3f4f6; border-radius: 8px; text-align: center; color: #6b7280;"><p>Componente {component_name}</p><small>Erro na renderização: {str(render_error)}</small></div>'
5291
+ debug_log.append(f" 🔄 Tentando com dados mínimos...")
5292
+ minimal_data = self.generate_minimal_component_data(comp_block)
5293
+ rendered_html = comp_block.render(minimal_data)
5294
+ debug_log.append(f" Renderizado com dados mínimos")
5295
+ has_error = False
5296
+ except Exception as e2:
5297
+ debug_log.append(f" ❌ Falhou também com dados mínimos: {str(e2)}")
5298
+ rendered_html = self.create_visual_placeholder(comp_name, category_name, str(e))
5299
+ has_error = True
5186
5300
 
5187
5301
  return {
5188
- 'name': component_name,
5189
- 'display_name': self.get_component_display_name(component_block),
5190
- 'class_name': component_block.__class__.__name__,
5302
+ 'name': comp_name,
5303
+ 'display_name': self.get_component_display_name(comp_block, comp_name),
5304
+ 'class_name': comp_block.__class__.__name__,
5191
5305
  'rendered_html': rendered_html,
5192
5306
  'category_name': category_name,
5193
- 'has_error': 'Erro na renderização' in rendered_html,
5194
- 'field_count': len(getattr(component_block, 'child_blocks', {}))
5307
+ 'has_error': has_error,
5308
+ 'error_message': render_error if has_error else None,
5309
+ 'data_used': component_data if self.debug_mode else {},
5195
5310
  }
5196
5311
 
5197
5312
  except Exception as e:
5198
- print(f"Erro ao processar componente {component_name}: {e}")
5313
+ debug_log.append(f" 💥 ERRO GERAL: {str(e)}")
5199
5314
  return None
5200
5315
 
5201
- def get_component_display_name(self, component_block):
5202
- """Obtém nome de exibição do componente"""
5203
- if hasattr(component_block, 'label') and component_block.label:
5204
- label = component_block.label
5205
- # Remove emojis do início
5206
- if any(label.startswith(emoji) for emoji in ['🎯', '🎨', '🏢', '🚀', '🔍', '📑', '🖼️', '⭐', '🎬']):
5207
- parts = label.split(' ', 1)
5208
- if len(parts) > 1:
5209
- return parts[1]
5210
- return label
5316
+ def generate_realistic_component_data(self, comp_name, comp_block, debug_log):
5317
+ """Gera dados realistas para renderização visual completa"""
5318
+ if not hasattr(comp_block, 'child_blocks'):
5319
+ return {}
5320
+
5321
+ data = {}
5322
+ debug_log.append(f" 📝 Gerando dados para {len(comp_block.child_blocks)} campos")
5323
+
5324
+ for field_name, field_block in comp_block.child_blocks.items():
5325
+ value = self.generate_realistic_field_value(field_name, field_block, comp_name)
5326
+ data[field_name] = value
5327
+
5328
+ if self.debug_mode:
5329
+ debug_log.append(f" {field_name}: {str(value)[:50]}...")
5211
5330
 
5212
- return component_block.__class__.__name__.replace('Block', '').replace('_', ' ')
5331
+ return data
5213
5332
 
5214
- def get_smart_defaults(self, component_name, component_block):
5215
- """Gera valores padrão inteligentes para o componente"""
5216
- defaults = {}
5333
+ def generate_realistic_field_value(self, field_name, field_block, comp_name):
5334
+ """Gera valores realistas baseados no contexto ENAP com defaults específicos por tipo"""
5335
+ field_type = field_block.__class__.__name__
5336
+ field_lower = field_name.lower()
5337
+
5338
+ # Usar default se existir
5339
+ if hasattr(field_block, 'default') and field_block.default is not None:
5340
+ return field_block.default
5341
+
5342
+ # DEFAULTS ESPECÍFICOS POR TIPO DE CAMPO
5343
+
5344
+ # === IMAGENS ===
5345
+ if field_type in ['ImageChooserBlock', 'ImageBlock']:
5346
+ return self.get_default_image_value(field_name)
5347
+
5348
+ # === DOCUMENTOS ===
5349
+ elif field_type in ['DocumentChooserBlock', 'DocumentBlock']:
5350
+ return self.get_default_document_value(field_name)
5351
+
5352
+ # === PÁGINAS ===
5353
+ elif field_type in ['PageChooserBlock', 'PageBlock']:
5354
+ return self.get_default_page_value(field_name)
5355
+
5356
+ # === SNIPPETS ===
5357
+ elif field_type in ['SnippetChooserBlock']:
5358
+ return None # Snippets são mais complexos, deixar vazio
5217
5359
 
5218
- if not hasattr(component_block, 'child_blocks'):
5219
- return defaults
5360
+ # === BLOCOS ESTRUTURADOS ===
5361
+ elif field_type == 'StructBlock':
5362
+ return {} # Será processado recursivamente
5363
+
5364
+ elif field_type == 'ListBlock':
5365
+ return self.generate_listblock_default_data(field_block, field_name)
5366
+
5367
+ elif field_type == 'StreamBlock':
5368
+ return self.generate_streamblock_default_data(field_block, field_name)
5369
+
5370
+ # === CAMPOS DE ESCOLHA ===
5371
+ elif field_type == 'ChoiceBlock':
5372
+ return self.get_choice_default_value(field_block, field_name)
5373
+
5374
+ # === CAMPOS BÁSICOS ===
5375
+ elif field_type == 'BooleanBlock':
5376
+ return self.get_boolean_default_value(field_name)
5377
+
5378
+ elif field_type in ['IntegerBlock', 'FloatBlock', 'DecimalBlock']:
5379
+ return self.get_numeric_default_value(field_name, field_type)
5380
+
5381
+ elif field_type in ['DateBlock', 'TimeBlock', 'DateTimeBlock']:
5382
+ return self.get_datetime_default_value(field_type)
5383
+
5384
+ elif field_type == 'RichTextBlock':
5385
+ return self.get_richtext_default_value(field_name)
5386
+
5387
+ elif field_type in ['URLBlock']:
5388
+ return self.get_url_default_value(field_name)
5389
+
5390
+ elif field_type == 'EmailBlock':
5391
+ return self.get_email_default_value(field_name)
5392
+
5393
+ elif field_type in ['TextBlock', 'CharBlock']:
5394
+ return self.get_text_default_value(field_name, field_lower)
5395
+
5396
+ elif field_type == 'RegexBlock':
5397
+ return 'ABC123' # Valor que geralmente passa em regex básicas
5398
+
5399
+ elif field_type == 'RawHTMLBlock':
5400
+ return '<div class="exemplo">Conteúdo HTML de exemplo</div>'
5401
+
5402
+ # === BLOCOS CUSTOMIZADOS ===
5403
+ elif 'Color' in field_type:
5404
+ return '#3B82F6' # Azul padrão
5405
+
5406
+ elif 'Icon' in field_type:
5407
+ return 'fas fa-star' # Ícone padrão
5408
+
5409
+ # Valor padrão genérico
5410
+ return f'Exemplo ENAP - {field_name}'
5411
+
5412
+ def get_default_image_value(self, field_name):
5413
+ """Gera valor padrão para campos de imagem"""
5414
+ from wagtail.images.models import Image
5415
+
5416
+ # Tentar encontrar uma imagem existente no sistema
5417
+ try:
5418
+ # Buscar por imagens que possam ser usadas como placeholder
5419
+ placeholder_images = Image.objects.filter(
5420
+ title__icontains='placeholder'
5421
+ ).first()
5220
5422
 
5221
- for field_name, field_block in component_block.child_blocks.items():
5222
- field_type = field_block.__class__.__name__
5423
+ if placeholder_images:
5424
+ return placeholder_images
5223
5425
 
5224
- # Usar valor padrão do campo se existir
5225
- if hasattr(field_block, 'default') and field_block.default is not None:
5226
- defaults[field_name] = field_block.default
5227
- continue
5426
+ # Buscar por imagens com nomes genéricos
5427
+ generic_names = ['exemplo', 'sample', 'test', 'demo', 'enap', 'logo']
5428
+ for name in generic_names:
5429
+ image = Image.objects.filter(title__icontains=name).first()
5430
+ if image:
5431
+ return image
5228
5432
 
5229
- # Gerar valores contextuais baseados no nome do campo
5230
- field_lower = field_name.lower()
5433
+ # Se não encontrar, usar qualquer imagem
5434
+ any_image = Image.objects.first()
5435
+ if any_image:
5436
+ return any_image
5437
+
5438
+ except Exception:
5439
+ pass
5440
+
5441
+ # Se não houver imagens, criar uma imagem placeholder via código
5442
+ return self.create_placeholder_image_object(field_name)
5443
+
5444
+ def create_placeholder_image_object(self, field_name):
5445
+ """Cria um objeto placeholder para representar uma imagem"""
5446
+ # Como não podemos criar imagens reais sem arquivos,
5447
+ # retornamos None e deixamos o template lidar com isso
5448
+ # O template deve mostrar um placeholder visual
5449
+ return None
5450
+
5451
+ def get_default_document_value(self, field_name):
5452
+ """Gera valor padrão para campos de documento"""
5453
+ from wagtail.documents.models import Document
5454
+
5455
+ try:
5456
+ # Buscar documento de exemplo
5457
+ example_doc = Document.objects.filter(
5458
+ title__icontains='exemplo'
5459
+ ).first()
5460
+
5461
+ if example_doc:
5462
+ return example_doc
5463
+
5464
+ # Qualquer documento
5465
+ any_doc = Document.objects.first()
5466
+ if any_doc:
5467
+ return any_doc
5468
+
5469
+ except Exception:
5470
+ pass
5471
+
5472
+ return None
5473
+
5474
+ def get_default_page_value(self, field_name):
5475
+ """Gera valor padrão para campos de página"""
5476
+ from wagtail.models import Page
5477
+
5478
+ try:
5479
+ # Buscar página home ou root
5480
+ home_page = Page.objects.filter(
5481
+ slug__in=['home', 'inicio', 'root']
5482
+ ).first()
5483
+
5484
+ if home_page:
5485
+ return home_page
5486
+
5487
+ # Qualquer página que não seja root
5488
+ any_page = Page.objects.filter(depth__gt=1).first()
5489
+ if any_page:
5490
+ return any_page
5491
+
5492
+ except Exception:
5493
+ pass
5494
+
5495
+ return None
5496
+
5497
+ def get_choice_default_value(self, field_block, field_name):
5498
+ """Gera valor padrão para campos de escolha"""
5499
+ try:
5500
+ if hasattr(field_block, 'choices') and field_block.choices:
5501
+ # Retornar a primeira opção
5502
+ return field_block.choices[0][0]
5503
+ except Exception:
5504
+ pass
5505
+
5506
+ return 'opcao1'
5507
+
5508
+ def get_boolean_default_value(self, field_name):
5509
+ """Gera valor padrão para campos boolean"""
5510
+ field_lower = field_name.lower()
5511
+
5512
+ # Campos que geralmente são False por padrão
5513
+ false_defaults = [
5514
+ 'disable', 'hidden', 'private', 'draft', 'inactive',
5515
+ 'closed', 'locked', 'disabled', 'hide'
5516
+ ]
5517
+
5518
+ for false_word in false_defaults:
5519
+ if false_word in field_lower:
5520
+ return False
5521
+
5522
+ # Maioria dos booleans são True para melhor visualização
5523
+ return True
5524
+
5525
+ def get_numeric_default_value(self, field_name, field_type):
5526
+ """Gera valor padrão para campos numéricos"""
5527
+ field_lower = field_name.lower()
5528
+
5529
+ # Valores específicos baseados no nome
5530
+ if 'price' in field_lower or 'preco' in field_lower:
5531
+ return 299.90 if field_type == 'FloatBlock' else 299
5532
+ elif 'year' in field_lower or 'ano' in field_lower:
5533
+ return 2024
5534
+ elif 'month' in field_lower or 'mes' in field_lower:
5535
+ return 6
5536
+ elif 'day' in field_lower or 'dia' in field_lower:
5537
+ return 15
5538
+ elif 'hour' in field_lower or 'hora' in field_lower:
5539
+ return 14
5540
+ elif 'minute' in field_lower or 'minuto' in field_lower:
5541
+ return 30
5542
+ elif 'percent' in field_lower or 'porcentagem' in field_lower:
5543
+ return 85.5 if field_type == 'FloatBlock' else 85
5544
+ elif 'count' in field_lower or 'total' in field_lower:
5545
+ return 42
5546
+ elif 'order' in field_lower or 'ordem' in field_lower:
5547
+ return 1
5548
+
5549
+ # Valor padrão genérico
5550
+ return 100.0 if field_type == 'FloatBlock' else 100
5551
+
5552
+ def get_datetime_default_value(self, field_type):
5553
+ """Gera valor padrão para campos de data/hora"""
5554
+ if field_type == 'DateBlock':
5555
+ return '2024-06-15'
5556
+ elif field_type == 'TimeBlock':
5557
+ return '14:30:00'
5558
+ elif field_type == 'DateTimeBlock':
5559
+ return '2024-06-15T14:30:00'
5560
+
5561
+ return '2024-06-15'
5562
+
5563
+ def generate_streamblock_default_data(self, stream_block, field_name):
5564
+ """Gera dados padrão para StreamBlocks"""
5565
+ if not hasattr(stream_block, 'child_blocks'):
5566
+ return []
5567
+
5568
+ stream_data = []
5569
+ available_blocks = list(stream_block.child_blocks.items())[:2]
5570
+
5571
+ for block_name, block_instance in available_blocks:
5572
+ if hasattr(block_instance, 'child_blocks'):
5573
+ block_data = {}
5574
+ for child_field, child_block in block_instance.child_blocks.items():
5575
+ block_data[child_field] = self.generate_realistic_field_value(
5576
+ child_field, child_block, f"{field_name}_{block_name}"
5577
+ )
5578
+ else:
5579
+ block_data = self.generate_realistic_field_value(
5580
+ block_name, block_instance, field_name
5581
+ )
5582
+
5583
+ stream_data.append({
5584
+ 'type': block_name,
5585
+ 'value': block_data
5586
+ })
5587
+
5588
+ return stream_data
5589
+
5590
+ def generate_listblock_default_data(self, field_block, field_name):
5591
+ """Gera dados padrão para ListBlocks"""
5592
+ if hasattr(field_block, 'child_block'):
5593
+ child_block = field_block.child_block
5594
+ items = []
5595
+
5596
+ for i in range(2):
5597
+ if hasattr(child_block, 'child_blocks'):
5598
+ item_data = {}
5599
+ for sub_field, sub_block in child_block.child_blocks.items():
5600
+ item_data[sub_field] = self.generate_realistic_field_value(
5601
+ sub_field, sub_block, f"{field_name}_item_{i}"
5602
+ )
5603
+ else:
5604
+ item_data = self.generate_realistic_field_value(
5605
+ f"{field_name}_item_{i}", child_block, f"{field_name}_list"
5606
+ )
5607
+ items.append(item_data)
5231
5608
 
5232
- if 'title' in field_lower or 'titulo' in field_lower:
5233
- defaults[field_name] = 'Escola Nacional de Administração Pública'
5234
- elif 'subtitle' in field_lower or 'subtitulo' in field_lower:
5235
- defaults[field_name] = 'Excelência em capacitação para o serviço público'
5236
- elif 'text' in field_lower or 'texto' in field_lower:
5237
- defaults[field_name] = 'A ENAP é responsável pela capacitação de servidores públicos federais, contribuindo para a modernização da administração pública.'
5238
- elif 'description' in field_lower or 'descricao' in field_lower:
5239
- defaults[field_name] = 'Componente do Design System ENAP para construção de interfaces modernas.'
5240
- elif 'button' in field_lower or 'botao' in field_lower:
5241
- defaults[field_name] = 'Saiba Mais'
5242
- elif 'url' in field_lower or 'link' in field_lower:
5243
- defaults[field_name] = 'https://www.enap.gov.br'
5244
- elif 'email' in field_lower:
5245
- defaults[field_name] = 'contato@enap.gov.br'
5609
+ return items
5610
+
5611
+ return []
5612
+
5613
+ def get_richtext_default_value(self, field_name):
5614
+ """Gera valor padrão para campos de texto rico"""
5615
+ field_lower = field_name.lower()
5616
+
5617
+ # Conteúdos específicos baseados no nome do campo
5618
+ if 'about' in field_lower or 'sobre' in field_lower:
5619
+ return '''
5620
+ <h3>Sobre a ENAP</h3>
5621
+ <p>A <strong>Escola Nacional de Administração Pública</strong> é responsável pela capacitação e desenvolvimento de servidores públicos federais.</p>
5622
+ <ul>
5623
+ <li>Cursos presenciais e à distância</li>
5624
+ <li>Eventos e seminários</li>
5625
+ <li>Pesquisas em gestão pública</li>
5626
+ </ul>
5627
+ '''
5628
+ elif 'content' in field_lower or 'conteudo' in field_lower:
5629
+ return '''
5630
+ <h2>Modernização da Gestão Pública</h2>
5631
+ <p>A ENAP promove a <em>excelência na administração pública</em> através de:</p>
5632
+ <ol>
5633
+ <li><strong>Capacitação continuada</strong> de servidores</li>
5634
+ <li><strong>Pesquisa aplicada</strong> em gestão pública</li>
5635
+ <li><strong>Inovação</strong> em processos governamentais</li>
5636
+ </ol>
5637
+ <blockquote>
5638
+ <p>"Transformando a administração pública através do conhecimento"</p>
5639
+ </blockquote>
5640
+ '''
5641
+ elif 'description' in field_lower or 'descricao' in field_lower:
5642
+ return '''
5643
+ <p>Desenvolvemos <strong>competências e habilidades</strong> dos servidores para uma gestão pública moderna, eficiente e orientada ao cidadão.</p>
5644
+ <p>Nossa missão é contribuir para a modernização do Estado através da capacitação de seus servidores.</p>
5645
+ '''
5646
+
5647
+ # Conteúdo padrão genérico
5648
+ return '''
5649
+ <h3>Título de Exemplo</h3>
5650
+ <p>Este é um conteúdo de <strong>exemplo</strong> em texto rico para demonstrar a funcionalidade do componente.</p>
5651
+ <p>Inclui <em>formatação básica</em> e <a href="https://www.enap.gov.br">links externos</a>.</p>
5652
+ '''
5653
+
5654
+ def get_url_default_value(self, field_name):
5655
+ """Gera valor padrão para campos de URL"""
5656
+ field_lower = field_name.lower()
5657
+
5658
+ # URLs específicas baseadas no nome do campo
5659
+ url_mapping = {
5660
+ 'site': 'https://www.enap.gov.br',
5661
+ 'home': 'https://www.enap.gov.br',
5662
+ 'curso': 'https://www.enap.gov.br/cursos',
5663
+ 'course': 'https://www.enap.gov.br/cursos',
5664
+ 'event': 'https://www.enap.gov.br/eventos',
5665
+ 'evento': 'https://www.enap.gov.br/eventos',
5666
+ 'news': 'https://www.enap.gov.br/noticias',
5667
+ 'noticia': 'https://www.enap.gov.br/noticias',
5668
+ 'contact': 'https://www.enap.gov.br/contato',
5669
+ 'contato': 'https://www.enap.gov.br/contato',
5670
+ 'blog': 'https://www.enap.gov.br/blog',
5671
+ 'video': 'https://www.youtube.com/watch?v=example',
5672
+ 'youtube': 'https://www.youtube.com/watch?v=example',
5673
+ 'social': 'https://www.linkedin.com/company/enap',
5674
+ 'linkedin': 'https://www.linkedin.com/company/enap',
5675
+ 'facebook': 'https://www.facebook.com/enap.oficial',
5676
+ 'twitter': 'https://twitter.com/enap_oficial',
5677
+ 'instagram': 'https://instagram.com/enap_oficial',
5678
+ }
5679
+
5680
+ for key, url in url_mapping.items():
5681
+ if key in field_lower:
5682
+ return url
5683
+
5684
+ # URL padrão
5685
+ return 'https://www.enap.gov.br'
5686
+
5687
+ def get_email_default_value(self, field_name):
5688
+ """Gera valor padrão para campos de email"""
5689
+ field_lower = field_name.lower()
5690
+
5691
+ # Emails específicos baseados no nome do campo
5692
+ email_mapping = {
5693
+ 'contact': 'contato@enap.gov.br',
5694
+ 'contato': 'contato@enap.gov.br',
5695
+ 'support': 'suporte@enap.gov.br',
5696
+ 'suporte': 'suporte@enap.gov.br',
5697
+ 'admin': 'admin@enap.gov.br',
5698
+ 'curso': 'cursos@enap.gov.br',
5699
+ 'course': 'cursos@enap.gov.br',
5700
+ 'event': 'eventos@enap.gov.br',
5701
+ 'evento': 'eventos@enap.gov.br',
5702
+ 'news': 'noticias@enap.gov.br',
5703
+ 'noticia': 'noticias@enap.gov.br',
5704
+ 'inscricao': 'inscricoes@enap.gov.br',
5705
+ 'registration': 'inscricoes@enap.gov.br',
5706
+ }
5707
+
5708
+ for key, email in email_mapping.items():
5709
+ if key in field_lower:
5710
+ return email
5711
+
5712
+ # Email padrão
5713
+ return 'contato@enap.gov.br'
5714
+
5715
+ def get_text_default_value(self, field_name, field_lower):
5716
+ """Gera valor padrão para campos de texto"""
5717
+ # Conteúdos específicos da ENAP baseados no nome do campo
5718
+
5719
+ if 'title' in field_lower or 'titulo' in field_lower:
5720
+ titles = [
5721
+ 'Escola Nacional de Administração Pública',
5722
+ 'Capacitação para o Serviço Público',
5723
+ ]
5724
+ import random
5725
+ return random.choice(titles)
5726
+
5727
+ elif 'subtitle' in field_lower or 'subtitulo' in field_lower:
5728
+ return 'Excelência em capacitação para o serviço público'
5729
+
5730
+ elif 'description' in field_lower or 'descricao' in field_lower:
5731
+ descriptions = [
5732
+ 'A ENAP é a escola de governo do Poder Executivo federal, responsável pela formação e capacitação de servidores públicos.'
5733
+ ]
5734
+ import random
5735
+ return random.choice(descriptions)
5736
+
5737
+ elif 'button' in field_lower or 'botao' in field_lower or 'cta' in field_lower:
5738
+ buttons = ['Saiba Mais', 'Inscreva-se', 'Conheça', 'Participe', 'Acesse', 'Baixar', 'Ver Mais']
5739
+ import random
5740
+ return random.choice(buttons)
5741
+
5742
+ elif 'name' in field_lower or 'nome' in field_lower:
5743
+ return 'ENAP - Escola Nacional de Administração Pública'
5744
+
5745
+ elif 'author' in field_lower or 'autor' in field_lower:
5746
+ return 'Equipe ENAP'
5747
+
5748
+ elif 'phone' in field_lower or 'telefone' in field_lower:
5749
+ return '(61) 2020-3000'
5750
+
5751
+ elif 'address' in field_lower or 'endereco' in field_lower:
5752
+ return 'SAIS - Área 2-A - Brasília/DF - CEP: 70610-900'
5753
+
5754
+ elif 'city' in field_lower or 'cidade' in field_lower:
5755
+ return 'Brasília'
5756
+
5757
+ elif 'state' in field_lower or 'estado' in field_lower:
5758
+ return 'Distrito Federal'
5759
+
5760
+ elif 'country' in field_lower or 'pais' in field_lower:
5761
+ return 'Brasil'
5762
+
5763
+ elif 'price' in field_lower or 'preco' in field_lower:
5764
+ return 'Gratuito'
5765
+
5766
+ elif 'duration' in field_lower or 'duracao' in field_lower:
5767
+ return '40 horas'
5768
+
5769
+ elif 'level' in field_lower or 'nivel' in field_lower:
5770
+ return 'Intermediário'
5771
+
5772
+ elif 'category' in field_lower or 'categoria' in field_lower:
5773
+ return 'Gestão Pública'
5774
+
5775
+ elif 'tag' in field_lower:
5776
+ return 'capacitacao'
5777
+
5778
+ elif 'slug' in field_lower:
5779
+ return 'exemplo-componente-enap'
5780
+
5781
+ # Valor padrão baseado no nome do campo
5782
+ return f'Exemplo ENAP - {field_name.replace("_", " ").title()}'
5783
+
5784
+ def generate_minimal_component_data(self, comp_block):
5785
+ """Gera dados mínimos para fallback"""
5786
+ if not hasattr(comp_block, 'child_blocks'):
5787
+ return {}
5788
+
5789
+ minimal = {}
5790
+ for field_name, field_block in comp_block.child_blocks.items():
5791
+ field_type = field_block.__class__.__name__
5792
+
5793
+ if 'title' in field_name.lower():
5794
+ minimal[field_name] = 'ENAP'
5246
5795
  elif field_type == 'BooleanBlock':
5247
- defaults[field_name] = True
5796
+ minimal[field_name] = True
5248
5797
  elif field_type in ['IntegerBlock', 'FloatBlock']:
5249
- defaults[field_name] = 42
5250
- elif field_type == 'DateBlock':
5251
- defaults[field_name] = '2024-03-15'
5252
- elif field_type == 'RichTextBlock':
5253
- defaults[field_name] = '<p>Conteúdo de <strong>exemplo</strong> para demonstração do componente.</p>'
5254
- else:
5255
- # Valor genérico baseado no nome do campo
5256
- defaults[field_name] = f'Exemplo {field_name}'
5798
+ minimal[field_name] = 1
5799
+
5800
+ return minimal
5801
+
5802
+ def create_visual_placeholder(self, comp_name, category_name, error_msg):
5803
+ """Cria um placeholder visual para componentes com erro"""
5804
+ return f'''
5805
+ <div style="
5806
+ padding: 2rem;
5807
+ background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%);
5808
+ border: 2px dashed #dc2626;
5809
+ border-radius: 12px;
5810
+ text-align: center;
5811
+ color: #7f1d1d;
5812
+ margin: 1rem 0;
5813
+ box-shadow: 0 4px 8px rgba(220, 38, 38, 0.1);
5814
+ ">
5815
+ <div style="font-size: 2rem; margin-bottom: 0.5rem;">⚠️</div>
5816
+ <h3 style="margin: 0 0 0.5rem 0; color: #dc2626; font-size: 1.25rem;">{comp_name}</h3>
5817
+ <p style="margin: 0 0 0.5rem 0; font-size: 0.875rem;">Componente não pôde ser renderizado</p>
5818
+ <small style="color: #991b1b; display: block;">Categoria: {category_name}</small>
5819
+ {f'<details style="margin-top: 1rem;"><summary style="cursor: pointer;">Ver erro técnico</summary><div style="margin-top: 0.5rem; font-family: monospace; font-size: 0.75rem; text-align: left; background: rgba(0,0,0,0.1); padding: 0.5rem; border-radius: 4px;">{error_msg}</div></details>' if self.debug_mode else ''}
5820
+ </div>
5821
+ '''
5822
+
5823
+ def get_component_display_name(self, block, comp_name):
5824
+ """Nome de exibição limpo do componente"""
5825
+ if hasattr(block, 'label') and block.label:
5826
+ label = block.label
5827
+ # Remove emojis do início
5828
+ emoji_prefixes = ['🎯', '🎨', '🏢', '🚀', '🔍', '📑', '🖼️', '⭐', '🎬', '📋', '⚡', '📞', '👂']
5829
+ for emoji in emoji_prefixes:
5830
+ if label.startswith(emoji):
5831
+ parts = label.split(' ', 1)
5832
+ if len(parts) > 1:
5833
+ return parts[1]
5834
+ break
5835
+ return label
5257
5836
 
5258
- return defaults
5837
+ # Formatar nome da classe
5838
+ class_name = block.__class__.__name__
5839
+ if class_name.endswith('Block'):
5840
+ class_name = class_name[:-5]
5841
+
5842
+ return class_name.replace('_', ' ').title()
5843
+
5844
+ def format_category_name(self, category_name):
5845
+ """Formata nome da categoria"""
5846
+ category_names = {
5847
+ 'banners': 'Banners e Heroes',
5848
+ 'galerias': 'Galerias e Imagens',
5849
+ 'carousels': 'Carrosséis',
5850
+ 'dashboards': 'Dashboards e Métricas',
5851
+ 'formularios': 'Formulários',
5852
+ 'cursos': 'Cursos e Educação',
5853
+ 'eventos': 'Eventos',
5854
+ 'navegacao': 'Navegação',
5855
+ 'menus': 'Menus',
5856
+ 'botoes': 'Botões e CTAs',
5857
+ 'conteudo': 'Conteúdo e Texto',
5858
+ 'secoes': 'Seções e Containers',
5859
+ 'cards': 'Cards',
5860
+ 'interativos': 'Componentes Interativos',
5861
+ 'midia': 'Mídia e Vídeos',
5862
+ 'especialidades': 'Componentes Especializados',
5863
+ }
5864
+
5865
+ return category_names.get(category_name, category_name.replace('_', ' ').title())
5866
+
5867
+ def get_category_icon(self, category_name):
5868
+ """Ícone da categoria"""
5869
+ icons = {
5870
+ 'banners': 'image',
5871
+ 'galerias': 'images',
5872
+ 'carousels': 'arrows-up-down',
5873
+ 'dashboards': 'chart-bar',
5874
+ 'formularios': 'edit',
5875
+ 'cursos': 'graduation-cap',
5876
+ 'eventos': 'calendar',
5877
+ 'navegacao': 'bars',
5878
+ 'menus': 'list',
5879
+ 'botoes': 'mouse-pointer',
5880
+ 'conteudo': 'file-text',
5881
+ 'secoes': 'th-large',
5882
+ 'cards': 'clone',
5883
+ 'interativos': 'cogs',
5884
+ 'midia': 'play-circle',
5885
+ 'especialidades': 'puzzle-piece',
5886
+ }
5887
+ return icons.get(category_name, 'cube')
5888
+
5889
+ def get_category_description(self, category_name):
5890
+ """Descrição da categoria"""
5891
+ descriptions = {
5892
+ 'banners': 'Componentes para seções de destaque, heroes e banners promocionais',
5893
+ 'galerias': 'Componentes para exibição de imagens, galerias e portfolios visuais',
5894
+ 'carousels': 'Componentes de carrosséis, sliders e apresentações rotativas',
5895
+ 'dashboards': 'Componentes para dashboards, KPIs, gráficos e visualização de dados',
5896
+ 'formularios': 'Componentes de formulários, campos de entrada e validação',
5897
+ 'cursos': 'Componentes específicos para cursos, educação e capacitação online',
5898
+ 'eventos': 'Componentes para eventos, cronogramas e calendários',
5899
+ 'navegacao': 'Componentes de navegação, menus e estruturas de site',
5900
+ 'menus': 'Componentes específicos de menu e navegação secundária',
5901
+ 'botoes': 'Componentes de botões, call-to-actions e elementos clicáveis',
5902
+ 'conteudo': 'Componentes de conteúdo, texto rico, citações e artigos',
5903
+ 'secoes': 'Componentes de seções, containers e estruturas de layout',
5904
+ 'cards': 'Componentes de cards, grids e elementos organizacionais',
5905
+ 'interativos': 'Componentes interativos, acordeões, modais e elementos dinâmicos',
5906
+ 'midia': 'Componentes de vídeo, áudio, podcasts e conteúdo multimídia',
5907
+ 'especialidades': 'Componentes especializados e funcionalidades específicas da ENAP',
5908
+ }
5909
+ return descriptions.get(category_name, f'Componentes da categoria {category_name}')