wagtail-enap-designsystem 1.2.1.143__py3-none-any.whl → 1.2.1.145__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 (26) hide show
  1. enap_designsystem/blocks/content_blocks.py +2 -1
  2. enap_designsystem/blocks/form.py +1 -6
  3. enap_designsystem/blocks/html_blocks.py +82 -77
  4. enap_designsystem/blocks/layout_blocks.py +26 -0
  5. enap_designsystem/blocks/semana_blocks.py +30 -19
  6. enap_designsystem/migrations/0415_showcasecomponentesdireto_debug_mode_and_more.py +77346 -0
  7. enap_designsystem/migrations/0416_alter_showcasecomponentesdireto_debug_mode_and_more.py +33 -0
  8. enap_designsystem/migrations/0417_alter_showcasecomponentesdireto_options_and_more.py +53 -0
  9. enap_designsystem/models.py +729 -146
  10. enap_designsystem/static/enap_designsystem/blocks/accordions.css +1 -0
  11. enap_designsystem/static/enap_designsystem/blocks/semana.css +6 -5
  12. enap_designsystem/templates/enap_designsystem/blocks/apresentacao_block.html +8 -16
  13. enap_designsystem/templates/enap_designsystem/blocks/apresentacao_simple_block.html +11 -12
  14. enap_designsystem/templates/enap_designsystem/blocks/cards_titles.html +23 -31
  15. enap_designsystem/templates/enap_designsystem/blocks/formulario_dinamico.html +4 -4
  16. enap_designsystem/templates/enap_designsystem/blocks/logos_simple_block.html +1 -22
  17. enap_designsystem/templates/enap_designsystem/blocks/numeros_block.html +1 -1
  18. enap_designsystem/templates/enap_designsystem/blocks/section_block.html +2 -2
  19. enap_designsystem/templates/enap_designsystem/blocks/video_hero_banner.html +3 -8
  20. enap_designsystem/templates/enap_designsystem/pages/showcase_components.html +867 -464
  21. enap_designsystem/templates/enap_designsystem/semana_inovacao/blocks_menu_navigation.html +10 -0
  22. {wagtail_enap_designsystem-1.2.1.143.dist-info → wagtail_enap_designsystem-1.2.1.145.dist-info}/METADATA +1 -1
  23. {wagtail_enap_designsystem-1.2.1.143.dist-info → wagtail_enap_designsystem-1.2.1.145.dist-info}/RECORD +26 -23
  24. {wagtail_enap_designsystem-1.2.1.143.dist-info → wagtail_enap_designsystem-1.2.1.145.dist-info}/WHEEL +0 -0
  25. {wagtail_enap_designsystem-1.2.1.143.dist-info → wagtail_enap_designsystem-1.2.1.145.dist-info}/licenses/LICENSE +0 -0
  26. {wagtail_enap_designsystem-1.2.1.143.dist-info → wagtail_enap_designsystem-1.2.1.145.dist-info}/top_level.txt +0 -0
@@ -5028,231 +5028,814 @@ 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)
5152
5212
 
5153
- # Só adicionar categoria se tiver componentes renderizados
5213
+ else:
5214
+ debug_log.append(f" ⚠️ Categoria não processável: {stream_block.__class__.__name__}")
5215
+
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
+ # Gerar dados COMPLETOS para o componente
5250
+ component_data = self.generate_realistic_component_data(comp_name, comp_block, debug_log)
5175
5251
 
5176
5252
  # Tentar renderizar
5253
+ rendered_html = None
5254
+ render_error = None
5255
+
5177
5256
  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
5257
+ debug_log.append(f" 🎯 Tentando renderizar componente completo...")
5258
+ rendered_html = comp_block.render(component_data)
5259
+ debug_log.append(f" ✅ Renderizado com sucesso ({len(rendered_html)} chars)")
5260
+ has_error = False
5261
+ except Exception as e:
5262
+ debug_log.append(f" ❌ Erro na renderização: {str(e)}")
5263
+ render_error = str(e)
5264
+
5265
+ # Tentar com dados mínimos
5181
5266
  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>'
5267
+ debug_log.append(f" 🔄 Tentando com dados mínimos...")
5268
+ minimal_data = self.generate_minimal_component_data(comp_block)
5269
+ rendered_html = comp_block.render(minimal_data)
5270
+ debug_log.append(f" Renderizado com dados mínimos")
5271
+ has_error = False
5272
+ except Exception as e2:
5273
+ debug_log.append(f" ❌ Falhou também com dados mínimos: {str(e2)}")
5274
+ rendered_html = self.create_visual_placeholder(comp_name, category_name, str(e))
5275
+ has_error = True
5186
5276
 
5187
5277
  return {
5188
- 'name': component_name,
5189
- 'display_name': self.get_component_display_name(component_block),
5190
- 'class_name': component_block.__class__.__name__,
5278
+ 'name': comp_name,
5279
+ 'display_name': self.get_component_display_name(comp_block, comp_name),
5280
+ 'class_name': comp_block.__class__.__name__,
5191
5281
  'rendered_html': rendered_html,
5192
5282
  'category_name': category_name,
5193
- 'has_error': 'Erro na renderização' in rendered_html,
5194
- 'field_count': len(getattr(component_block, 'child_blocks', {}))
5283
+ 'has_error': has_error,
5284
+ 'error_message': render_error if has_error else None,
5285
+ 'data_used': component_data if self.debug_mode else {},
5195
5286
  }
5196
5287
 
5197
5288
  except Exception as e:
5198
- print(f"Erro ao processar componente {component_name}: {e}")
5289
+ debug_log.append(f" 💥 ERRO GERAL: {str(e)}")
5199
5290
  return None
5200
5291
 
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
5292
+ def generate_realistic_component_data(self, comp_name, comp_block, debug_log):
5293
+ """Gera dados realistas para renderização visual completa"""
5294
+ if not hasattr(comp_block, 'child_blocks'):
5295
+ return {}
5296
+
5297
+ data = {}
5298
+ debug_log.append(f" 📝 Gerando dados para {len(comp_block.child_blocks)} campos")
5299
+
5300
+ for field_name, field_block in comp_block.child_blocks.items():
5301
+ value = self.generate_realistic_field_value(field_name, field_block, comp_name)
5302
+ data[field_name] = value
5303
+
5304
+ if self.debug_mode:
5305
+ debug_log.append(f" {field_name}: {str(value)[:50]}...")
5306
+
5307
+ return data
5308
+
5309
+ def generate_realistic_field_value(self, field_name, field_block, comp_name):
5310
+ """Gera valores realistas baseados no contexto ENAP com defaults específicos por tipo"""
5311
+ field_type = field_block.__class__.__name__
5312
+ field_lower = field_name.lower()
5313
+
5314
+ # Usar default se existir
5315
+ if hasattr(field_block, 'default') and field_block.default is not None:
5316
+ return field_block.default
5211
5317
 
5212
- return component_block.__class__.__name__.replace('Block', '').replace('_', ' ')
5318
+ # DEFAULTS ESPECÍFICOS POR TIPO DE CAMPO
5319
+
5320
+ # === IMAGENS ===
5321
+ if field_type in ['ImageChooserBlock', 'ImageBlock']:
5322
+ return self.get_default_image_value(field_name)
5323
+
5324
+ # === DOCUMENTOS ===
5325
+ elif field_type in ['DocumentChooserBlock', 'DocumentBlock']:
5326
+ return self.get_default_document_value(field_name)
5327
+
5328
+ # === PÁGINAS ===
5329
+ elif field_type in ['PageChooserBlock', 'PageBlock']:
5330
+ return self.get_default_page_value(field_name)
5331
+
5332
+ # === SNIPPETS ===
5333
+ elif field_type in ['SnippetChooserBlock']:
5334
+ return None # Snippets são mais complexos, deixar vazio
5335
+
5336
+ # === BLOCOS ESTRUTURADOS ===
5337
+ elif field_type == 'StructBlock':
5338
+ return {} # Será processado recursivamente
5339
+
5340
+ elif field_type == 'ListBlock':
5341
+ return [] # Lista vazia por padrão
5342
+
5343
+ elif field_type == 'StreamBlock':
5344
+ return [] # Stream vazio por padrão
5345
+
5346
+ # === CAMPOS DE ESCOLHA ===
5347
+ elif field_type == 'ChoiceBlock':
5348
+ return self.get_choice_default_value(field_block, field_name)
5349
+
5350
+ # === CAMPOS BÁSICOS ===
5351
+ elif field_type == 'BooleanBlock':
5352
+ return self.get_boolean_default_value(field_name)
5353
+
5354
+ elif field_type in ['IntegerBlock', 'FloatBlock', 'DecimalBlock']:
5355
+ return self.get_numeric_default_value(field_name, field_type)
5356
+
5357
+ elif field_type in ['DateBlock', 'TimeBlock', 'DateTimeBlock']:
5358
+ return self.get_datetime_default_value(field_type)
5359
+
5360
+ elif field_type == 'RichTextBlock':
5361
+ return self.get_richtext_default_value(field_name)
5362
+
5363
+ elif field_type in ['URLBlock']:
5364
+ return self.get_url_default_value(field_name)
5365
+
5366
+ elif field_type == 'EmailBlock':
5367
+ return self.get_email_default_value(field_name)
5368
+
5369
+ elif field_type in ['TextBlock', 'CharBlock']:
5370
+ return self.get_text_default_value(field_name, field_lower)
5371
+
5372
+ elif field_type == 'RegexBlock':
5373
+ return 'ABC123' # Valor que geralmente passa em regex básicas
5374
+
5375
+ elif field_type == 'RawHTMLBlock':
5376
+ return '<div class="exemplo">Conteúdo HTML de exemplo</div>'
5377
+
5378
+ # === BLOCOS CUSTOMIZADOS ===
5379
+ elif 'Color' in field_type:
5380
+ return '#3B82F6' # Azul padrão
5381
+
5382
+ elif 'Icon' in field_type:
5383
+ return 'fas fa-star' # Ícone padrão
5384
+
5385
+ # Valor padrão genérico
5386
+ return f'Exemplo ENAP - {field_name}'
5213
5387
 
5214
- def get_smart_defaults(self, component_name, component_block):
5215
- """Gera valores padrão inteligentes para o componente"""
5216
- defaults = {}
5388
+ def get_default_image_value(self, field_name):
5389
+ """Gera valor padrão para campos de imagem"""
5390
+ from wagtail.images.models import Image
5217
5391
 
5218
- if not hasattr(component_block, 'child_blocks'):
5219
- return defaults
5392
+ # Tentar encontrar uma imagem existente no sistema
5393
+ try:
5394
+ # Buscar por imagens que possam ser usadas como placeholder
5395
+ placeholder_images = Image.objects.filter(
5396
+ title__icontains='placeholder'
5397
+ ).first()
5220
5398
 
5221
- for field_name, field_block in component_block.child_blocks.items():
5222
- field_type = field_block.__class__.__name__
5399
+ if placeholder_images:
5400
+ return placeholder_images
5223
5401
 
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
5402
+ # Buscar por imagens com nomes genéricos
5403
+ generic_names = ['exemplo', 'sample', 'test', 'demo', 'enap', 'logo']
5404
+ for name in generic_names:
5405
+ image = Image.objects.filter(title__icontains=name).first()
5406
+ if image:
5407
+ return image
5228
5408
 
5229
- # Gerar valores contextuais baseados no nome do campo
5230
- field_lower = field_name.lower()
5409
+ # Se não encontrar, usar qualquer imagem
5410
+ any_image = Image.objects.first()
5411
+ if any_image:
5412
+ return any_image
5413
+
5414
+ except Exception:
5415
+ pass
5416
+
5417
+ # Se não houver imagens, criar uma imagem placeholder via código
5418
+ return self.create_placeholder_image_object(field_name)
5419
+
5420
+ def create_placeholder_image_object(self, field_name):
5421
+ """Cria um objeto placeholder para representar uma imagem"""
5422
+ # Como não podemos criar imagens reais sem arquivos,
5423
+ # retornamos None e deixamos o template lidar com isso
5424
+ # O template deve mostrar um placeholder visual
5425
+ return None
5426
+
5427
+ def get_default_document_value(self, field_name):
5428
+ """Gera valor padrão para campos de documento"""
5429
+ from wagtail.documents.models import Document
5430
+
5431
+ try:
5432
+ # Buscar documento de exemplo
5433
+ example_doc = Document.objects.filter(
5434
+ title__icontains='exemplo'
5435
+ ).first()
5436
+
5437
+ if example_doc:
5438
+ return example_doc
5439
+
5440
+ # Qualquer documento
5441
+ any_doc = Document.objects.first()
5442
+ if any_doc:
5443
+ return any_doc
5444
+
5445
+ except Exception:
5446
+ pass
5447
+
5448
+ return None
5449
+
5450
+ def get_default_page_value(self, field_name):
5451
+ """Gera valor padrão para campos de página"""
5452
+ from wagtail.models import Page
5453
+
5454
+ try:
5455
+ # Buscar página home ou root
5456
+ home_page = Page.objects.filter(
5457
+ slug__in=['home', 'inicio', 'root']
5458
+ ).first()
5459
+
5460
+ if home_page:
5461
+ return home_page
5462
+
5463
+ # Qualquer página que não seja root
5464
+ any_page = Page.objects.filter(depth__gt=1).first()
5465
+ if any_page:
5466
+ return any_page
5467
+
5468
+ except Exception:
5469
+ pass
5470
+
5471
+ return None
5472
+
5473
+ def get_choice_default_value(self, field_block, field_name):
5474
+ """Gera valor padrão para campos de escolha"""
5475
+ try:
5476
+ if hasattr(field_block, 'choices') and field_block.choices:
5477
+ # Retornar a primeira opção
5478
+ return field_block.choices[0][0]
5479
+ except Exception:
5480
+ pass
5481
+
5482
+ return 'opcao1'
5483
+
5484
+ def get_boolean_default_value(self, field_name):
5485
+ """Gera valor padrão para campos boolean"""
5486
+ field_lower = field_name.lower()
5487
+
5488
+ # Campos que geralmente são False por padrão
5489
+ false_defaults = [
5490
+ 'disable', 'hidden', 'private', 'draft', 'inactive',
5491
+ 'closed', 'locked', 'disabled', 'hide'
5492
+ ]
5493
+
5494
+ for false_word in false_defaults:
5495
+ if false_word in field_lower:
5496
+ return False
5497
+
5498
+ # Maioria dos booleans são True para melhor visualização
5499
+ return True
5500
+
5501
+ def get_numeric_default_value(self, field_name, field_type):
5502
+ """Gera valor padrão para campos numéricos"""
5503
+ field_lower = field_name.lower()
5504
+
5505
+ # Valores específicos baseados no nome
5506
+ if 'price' in field_lower or 'preco' in field_lower:
5507
+ return 299.90 if field_type == 'FloatBlock' else 299
5508
+ elif 'year' in field_lower or 'ano' in field_lower:
5509
+ return 2024
5510
+ elif 'month' in field_lower or 'mes' in field_lower:
5511
+ return 6
5512
+ elif 'day' in field_lower or 'dia' in field_lower:
5513
+ return 15
5514
+ elif 'hour' in field_lower or 'hora' in field_lower:
5515
+ return 14
5516
+ elif 'minute' in field_lower or 'minuto' in field_lower:
5517
+ return 30
5518
+ elif 'percent' in field_lower or 'porcentagem' in field_lower:
5519
+ return 85.5 if field_type == 'FloatBlock' else 85
5520
+ elif 'count' in field_lower or 'total' in field_lower:
5521
+ return 42
5522
+ elif 'order' in field_lower or 'ordem' in field_lower:
5523
+ return 1
5524
+
5525
+ # Valor padrão genérico
5526
+ return 100.0 if field_type == 'FloatBlock' else 100
5527
+
5528
+ def get_datetime_default_value(self, field_type):
5529
+ """Gera valor padrão para campos de data/hora"""
5530
+ if field_type == 'DateBlock':
5531
+ return '2024-06-15'
5532
+ elif field_type == 'TimeBlock':
5533
+ return '14:30:00'
5534
+ elif field_type == 'DateTimeBlock':
5535
+ return '2024-06-15T14:30:00'
5536
+
5537
+ return '2024-06-15'
5538
+
5539
+ def get_richtext_default_value(self, field_name):
5540
+ """Gera valor padrão para campos de texto rico"""
5541
+ field_lower = field_name.lower()
5542
+
5543
+ # Conteúdos específicos baseados no nome do campo
5544
+ if 'about' in field_lower or 'sobre' in field_lower:
5545
+ return '''
5546
+ <h3>Sobre a ENAP</h3>
5547
+ <p>A <strong>Escola Nacional de Administração Pública</strong> é responsável pela capacitação e desenvolvimento de servidores públicos federais.</p>
5548
+ <ul>
5549
+ <li>Cursos presenciais e à distância</li>
5550
+ <li>Eventos e seminários</li>
5551
+ <li>Pesquisas em gestão pública</li>
5552
+ </ul>
5553
+ '''
5554
+ elif 'content' in field_lower or 'conteudo' in field_lower:
5555
+ return '''
5556
+ <h2>Modernização da Gestão Pública</h2>
5557
+ <p>A ENAP promove a <em>excelência na administração pública</em> através de:</p>
5558
+ <ol>
5559
+ <li><strong>Capacitação continuada</strong> de servidores</li>
5560
+ <li><strong>Pesquisa aplicada</strong> em gestão pública</li>
5561
+ <li><strong>Inovação</strong> em processos governamentais</li>
5562
+ </ol>
5563
+ <blockquote>
5564
+ <p>"Transformando a administração pública através do conhecimento"</p>
5565
+ </blockquote>
5566
+ '''
5567
+ elif 'description' in field_lower or 'descricao' in field_lower:
5568
+ return '''
5569
+ <p>Desenvolvemos <strong>competências e habilidades</strong> dos servidores para uma gestão pública moderna, eficiente e orientada ao cidadão.</p>
5570
+ <p>Nossa missão é contribuir para a modernização do Estado através da capacitação de seus servidores.</p>
5571
+ '''
5572
+
5573
+ # Conteúdo padrão genérico
5574
+ return '''
5575
+ <h3>Título de Exemplo</h3>
5576
+ <p>Este é um conteúdo de <strong>exemplo</strong> em texto rico para demonstrar a funcionalidade do componente.</p>
5577
+ <p>Inclui <em>formatação básica</em> e <a href="https://www.enap.gov.br">links externos</a>.</p>
5578
+ '''
5579
+
5580
+ def get_url_default_value(self, field_name):
5581
+ """Gera valor padrão para campos de URL"""
5582
+ field_lower = field_name.lower()
5583
+
5584
+ # URLs específicas baseadas no nome do campo
5585
+ url_mapping = {
5586
+ 'site': 'https://www.enap.gov.br',
5587
+ 'home': 'https://www.enap.gov.br',
5588
+ 'curso': 'https://www.enap.gov.br/cursos',
5589
+ 'course': 'https://www.enap.gov.br/cursos',
5590
+ 'event': 'https://www.enap.gov.br/eventos',
5591
+ 'evento': 'https://www.enap.gov.br/eventos',
5592
+ 'news': 'https://www.enap.gov.br/noticias',
5593
+ 'noticia': 'https://www.enap.gov.br/noticias',
5594
+ 'contact': 'https://www.enap.gov.br/contato',
5595
+ 'contato': 'https://www.enap.gov.br/contato',
5596
+ 'blog': 'https://www.enap.gov.br/blog',
5597
+ 'video': 'https://www.youtube.com/watch?v=example',
5598
+ 'youtube': 'https://www.youtube.com/watch?v=example',
5599
+ 'social': 'https://www.linkedin.com/company/enap',
5600
+ 'linkedin': 'https://www.linkedin.com/company/enap',
5601
+ 'facebook': 'https://www.facebook.com/enap.oficial',
5602
+ 'twitter': 'https://twitter.com/enap_oficial',
5603
+ 'instagram': 'https://instagram.com/enap_oficial',
5604
+ }
5605
+
5606
+ for key, url in url_mapping.items():
5607
+ if key in field_lower:
5608
+ return url
5609
+
5610
+ # URL padrão
5611
+ return 'https://www.enap.gov.br'
5612
+
5613
+ def get_email_default_value(self, field_name):
5614
+ """Gera valor padrão para campos de email"""
5615
+ field_lower = field_name.lower()
5616
+
5617
+ # Emails específicos baseados no nome do campo
5618
+ email_mapping = {
5619
+ 'contact': 'contato@enap.gov.br',
5620
+ 'contato': 'contato@enap.gov.br',
5621
+ 'support': 'suporte@enap.gov.br',
5622
+ 'suporte': 'suporte@enap.gov.br',
5623
+ 'admin': 'admin@enap.gov.br',
5624
+ 'curso': 'cursos@enap.gov.br',
5625
+ 'course': 'cursos@enap.gov.br',
5626
+ 'event': 'eventos@enap.gov.br',
5627
+ 'evento': 'eventos@enap.gov.br',
5628
+ 'news': 'noticias@enap.gov.br',
5629
+ 'noticia': 'noticias@enap.gov.br',
5630
+ 'inscricao': 'inscricoes@enap.gov.br',
5631
+ 'registration': 'inscricoes@enap.gov.br',
5632
+ }
5633
+
5634
+ for key, email in email_mapping.items():
5635
+ if key in field_lower:
5636
+ return email
5637
+
5638
+ # Email padrão
5639
+ return 'contato@enap.gov.br'
5640
+
5641
+ def get_text_default_value(self, field_name, field_lower):
5642
+ """Gera valor padrão para campos de texto"""
5643
+ # Conteúdos específicos da ENAP baseados no nome do campo
5644
+
5645
+ if 'title' in field_lower or 'titulo' in field_lower:
5646
+ titles = [
5647
+ 'Escola Nacional de Administração Pública',
5648
+ 'Capacitação para o Serviço Público',
5649
+ 'Inovação na Gestão Pública',
5650
+ 'Transformação Digital do Governo',
5651
+ 'Excelência em Administração Pública'
5652
+ ]
5653
+ import random
5654
+ return random.choice(titles)
5655
+
5656
+ elif 'subtitle' in field_lower or 'subtitulo' in field_lower:
5657
+ return 'Excelência em capacitação para o serviço público'
5231
5658
 
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'
5659
+ elif 'description' in field_lower or 'descricao' in field_lower:
5660
+ descriptions = [
5661
+ 'A ENAP é a escola de governo do Poder Executivo federal, responsável pela formação e capacitação de servidores públicos.',
5662
+ 'Desenvolvemos competências e habilidades dos servidores para uma gestão pública moderna, eficiente e orientada ao cidadão.',
5663
+ 'Nossa missão é contribuir para a modernização do Estado através da capacitação de seus servidores.',
5664
+ 'Promovemos a inovação e a transformação digital na administração pública federal.'
5665
+ ]
5666
+ import random
5667
+ return random.choice(descriptions)
5668
+
5669
+ elif 'button' in field_lower or 'botao' in field_lower or 'cta' in field_lower:
5670
+ buttons = ['Saiba Mais', 'Inscreva-se', 'Conheça', 'Participe', 'Acesse', 'Baixar', 'Ver Mais']
5671
+ import random
5672
+ return random.choice(buttons)
5673
+
5674
+ elif 'name' in field_lower or 'nome' in field_lower:
5675
+ return 'ENAP - Escola Nacional de Administração Pública'
5676
+
5677
+ elif 'author' in field_lower or 'autor' in field_lower:
5678
+ return 'Equipe ENAP'
5679
+
5680
+ elif 'phone' in field_lower or 'telefone' in field_lower:
5681
+ return '(61) 2020-3000'
5682
+
5683
+ elif 'address' in field_lower or 'endereco' in field_lower:
5684
+ return 'SAIS - Área 2-A - Brasília/DF - CEP: 70610-900'
5685
+
5686
+ elif 'city' in field_lower or 'cidade' in field_lower:
5687
+ return 'Brasília'
5688
+
5689
+ elif 'state' in field_lower or 'estado' in field_lower:
5690
+ return 'Distrito Federal'
5691
+
5692
+ elif 'country' in field_lower or 'pais' in field_lower:
5693
+ return 'Brasil'
5694
+
5695
+ elif 'price' in field_lower or 'preco' in field_lower:
5696
+ return 'Gratuito'
5697
+
5698
+ elif 'duration' in field_lower or 'duracao' in field_lower:
5699
+ return '40 horas'
5700
+
5701
+ elif 'level' in field_lower or 'nivel' in field_lower:
5702
+ return 'Intermediário'
5703
+
5704
+ elif 'category' in field_lower or 'categoria' in field_lower:
5705
+ return 'Gestão Pública'
5706
+
5707
+ elif 'tag' in field_lower:
5708
+ return 'capacitacao'
5709
+
5710
+ elif 'slug' in field_lower:
5711
+ return 'exemplo-componente-enap'
5712
+
5713
+ # Valor padrão baseado no nome do campo
5714
+ return f'Exemplo ENAP - {field_name.replace("_", " ").title()}'
5715
+
5716
+ def generate_minimal_component_data(self, comp_block):
5717
+ """Gera dados mínimos para fallback"""
5718
+ if not hasattr(comp_block, 'child_blocks'):
5719
+ return {}
5720
+
5721
+ minimal = {}
5722
+ for field_name, field_block in comp_block.child_blocks.items():
5723
+ field_type = field_block.__class__.__name__
5724
+
5725
+ if 'title' in field_name.lower():
5726
+ minimal[field_name] = 'ENAP'
5246
5727
  elif field_type == 'BooleanBlock':
5247
- defaults[field_name] = True
5728
+ minimal[field_name] = True
5248
5729
  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}'
5730
+ minimal[field_name] = 1
5731
+
5732
+ return minimal
5733
+
5734
+ def create_visual_placeholder(self, comp_name, category_name, error_msg):
5735
+ """Cria um placeholder visual para componentes com erro"""
5736
+ return f'''
5737
+ <div style="
5738
+ padding: 2rem;
5739
+ background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%);
5740
+ border: 2px dashed #dc2626;
5741
+ border-radius: 12px;
5742
+ text-align: center;
5743
+ color: #7f1d1d;
5744
+ margin: 1rem 0;
5745
+ box-shadow: 0 4px 8px rgba(220, 38, 38, 0.1);
5746
+ ">
5747
+ <div style="font-size: 2rem; margin-bottom: 0.5rem;">⚠️</div>
5748
+ <h3 style="margin: 0 0 0.5rem 0; color: #dc2626; font-size: 1.25rem;">{comp_name}</h3>
5749
+ <p style="margin: 0 0 0.5rem 0; font-size: 0.875rem;">Componente não pôde ser renderizado</p>
5750
+ <small style="color: #991b1b; display: block;">Categoria: {category_name}</small>
5751
+ {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 ''}
5752
+ </div>
5753
+ '''
5754
+
5755
+ def get_component_display_name(self, block, comp_name):
5756
+ """Nome de exibição limpo do componente"""
5757
+ if hasattr(block, 'label') and block.label:
5758
+ label = block.label
5759
+ # Remove emojis do início
5760
+ emoji_prefixes = ['🎯', '🎨', '🏢', '🚀', '🔍', '📑', '🖼️', '⭐', '🎬', '📋', '⚡', '📞', '👂']
5761
+ for emoji in emoji_prefixes:
5762
+ if label.startswith(emoji):
5763
+ parts = label.split(' ', 1)
5764
+ if len(parts) > 1:
5765
+ return parts[1]
5766
+ break
5767
+ return label
5768
+
5769
+ # Formatar nome da classe
5770
+ class_name = block.__class__.__name__
5771
+ if class_name.endswith('Block'):
5772
+ class_name = class_name[:-5]
5257
5773
 
5258
- return defaults
5774
+ return class_name.replace('_', ' ').title()
5775
+
5776
+ def format_category_name(self, category_name):
5777
+ """Formata nome da categoria"""
5778
+ category_names = {
5779
+ 'banners': 'Banners e Heroes',
5780
+ 'galerias': 'Galerias e Imagens',
5781
+ 'carousels': 'Carrosséis',
5782
+ 'dashboards': 'Dashboards e Métricas',
5783
+ 'formularios': 'Formulários',
5784
+ 'cursos': 'Cursos e Educação',
5785
+ 'eventos': 'Eventos',
5786
+ 'navegacao': 'Navegação',
5787
+ 'menus': 'Menus',
5788
+ 'botoes': 'Botões e CTAs',
5789
+ 'conteudo': 'Conteúdo e Texto',
5790
+ 'secoes': 'Seções e Containers',
5791
+ 'cards': 'Cards',
5792
+ 'interativos': 'Componentes Interativos',
5793
+ 'midia': 'Mídia e Vídeos',
5794
+ 'especialidades': 'Componentes Especializados',
5795
+ }
5796
+
5797
+ return category_names.get(category_name, category_name.replace('_', ' ').title())
5798
+
5799
+ def get_category_icon(self, category_name):
5800
+ """Ícone da categoria"""
5801
+ icons = {
5802
+ 'banners': 'image',
5803
+ 'galerias': 'images',
5804
+ 'carousels': 'arrows-up-down',
5805
+ 'dashboards': 'chart-bar',
5806
+ 'formularios': 'edit',
5807
+ 'cursos': 'graduation-cap',
5808
+ 'eventos': 'calendar',
5809
+ 'navegacao': 'bars',
5810
+ 'menus': 'list',
5811
+ 'botoes': 'mouse-pointer',
5812
+ 'conteudo': 'file-text',
5813
+ 'secoes': 'th-large',
5814
+ 'cards': 'clone',
5815
+ 'interativos': 'cogs',
5816
+ 'midia': 'play-circle',
5817
+ 'especialidades': 'puzzle-piece',
5818
+ }
5819
+ return icons.get(category_name, 'cube')
5820
+
5821
+ def get_category_description(self, category_name):
5822
+ """Descrição da categoria"""
5823
+ descriptions = {
5824
+ 'banners': 'Componentes para seções de destaque, heroes e banners promocionais',
5825
+ 'galerias': 'Componentes para exibição de imagens, galerias e portfolios visuais',
5826
+ 'carousels': 'Componentes de carrosséis, sliders e apresentações rotativas',
5827
+ 'dashboards': 'Componentes para dashboards, KPIs, gráficos e visualização de dados',
5828
+ 'formularios': 'Componentes de formulários, campos de entrada e validação',
5829
+ 'cursos': 'Componentes específicos para cursos, educação e capacitação online',
5830
+ 'eventos': 'Componentes para eventos, cronogramas e calendários',
5831
+ 'navegacao': 'Componentes de navegação, menus e estruturas de site',
5832
+ 'menus': 'Componentes específicos de menu e navegação secundária',
5833
+ 'botoes': 'Componentes de botões, call-to-actions e elementos clicáveis',
5834
+ 'conteudo': 'Componentes de conteúdo, texto rico, citações e artigos',
5835
+ 'secoes': 'Componentes de seções, containers e estruturas de layout',
5836
+ 'cards': 'Componentes de cards, grids e elementos organizacionais',
5837
+ 'interativos': 'Componentes interativos, acordeões, modais e elementos dinâmicos',
5838
+ 'midia': 'Componentes de vídeo, áudio, podcasts e conteúdo multimídia',
5839
+ 'especialidades': 'Componentes especializados e funcionalidades específicas da ENAP',
5840
+ }
5841
+ return descriptions.get(category_name, f'Componentes da categoria {category_name}')