Skip to content

## ISSUE #01 — [CRITICA] Dashboard.jsx e um God Component (1915 linhas) #161

Description

@darioreisjr

Arquivo: src/admin/Dashboard.jsx
Linhas: 1915
Limite recomendado: 150-250 linhas por componente

Problema

O Dashboard.jsx concentra toda a logica do painel admin em um unico arquivo: gestao de eventos, contribuintes, tags, usuarios, 3 modais diferentes, 30+ estados, 3 instancias de useForm, sidebar, topbar, notificacoes, tema e autenticacao.

Isso viola os principios de:

  • Single Responsibility Principle (SRP): um componente deve ter apenas uma responsabilidade
  • Separation of Concerns: logica de negocio misturada com UI
  • Composability: impossivel reutilizar partes isoladas

Refatoracao Proposta

Extrair em 12+ componentes e 4+ custom hooks:

src/admin/
├── Dashboard.jsx                    (~80 linhas - orquestrador)
├── components/
│   ├── AdminSidebar.jsx             (~80 linhas)
│   ├── AdminTopbar.jsx              (~30 linhas)
│   ├── StatsGrid.jsx                (~50 linhas)
│   ├── EventsTable.jsx              (~120 linhas)
│   ├── EventTableRow.jsx            (~70 linhas)
│   ├── EventFormModal.jsx           (~200 linhas)
│   ├── TagsSection.jsx              (~80 linhas)
│   ├── TagFormModal.jsx             (~80 linhas)
│   ├── ContributorsSection.jsx      (~90 linhas)
│   ├── ContributorCard.jsx          (~60 linhas)
│   ├── ContributorFormModal.jsx     (~150 linhas)
│   ├── UsersManagementSection.jsx   (~100 linhas)
│   ├── UserRoleRow.jsx              (~70 linhas)
│   ├── Notification.jsx             (~20 linhas)
│   └── ImageUpload.jsx              (~60 linhas)
├── hooks/
│   ├── useEvents.js                 (CRUD + estado de eventos)
│   ├── useContributors.js           (CRUD + estado de contribuintes)
│   ├── useTags.js                   (CRUD + estado de tags)
│   ├── useUsersManagement.js        (gestao de roles)
│   └── useNotification.js           (logica de notificacao)
└── constants/
    └── index.js                     (DAY_NAMES, PAGE_SIZES, ESTADOS_BR)

Exemplo de Implementacao — useEvents.js

import { useState, useCallback, useMemo } from 'react'
import { getEvents, createEvent, updateEvent, deleteEvent, getEventStats, uploadEventImage } from '../../services/eventService'
import { parseDateValue } from '../utils/dateUtils'

export function useEvents({ showNotification }) {
  const [eventos, setEventos] = useState([])
  const [loading, setLoading] = useState(true)
  const [stats, setStats] = useState({ total: 0, noturno: 0, diurno: 0 })

  const loadEvents = useCallback(async () => {
    try {
      setLoading(true)
      const [eventsData, statsData] = await Promise.all([getEvents(), getEventStats()])
      setEventos(eventsData)
      setStats(statsData)
    } catch (error) {
      showNotification('Erro ao carregar eventos', 'error')
    } finally {
      setLoading(false)
    }
  }, [showNotification])

  const sortedEvents = useMemo(() => {
    const today = new Date()
    today.setHours(0, 0, 0, 0)
    return [...eventos].sort((a, b) => {
      const dateA = parseDateValue(a.data_evento)
      const dateB = parseDateValue(b.data_evento)
      const isAFuture = dateA && dateA >= today
      const isBFuture = dateB && dateB >= today
      if (isAFuture && !isBFuture) return -1
      if (!isAFuture && isBFuture) return 1
      if (isAFuture) return dateA - dateB
      return dateB - dateA
    })
  }, [eventos])

  const handleDeleteEvent = useCallback(async (id) => {
    if (window.confirm('Tem certeza que deseja excluir este evento?')) {
      try {
        await deleteEvent(id)
        showNotification('Evento excluido com sucesso!')
        await loadEvents()
      } catch {
        showNotification('Erro ao excluir evento', 'error')
      }
    }
  }, [showNotification, loadEvents])

  return { eventos, loading, stats, sortedEvents, loadEvents, handleDeleteEvent }
}

Exemplo de Implementacao — EventTableRow.jsx

import { Eye, Edit2, Trash2 } from 'lucide-react'
import { parseDateValue } from '../utils/dateUtils'
import { stripRichText } from '../../utils/richText'
import BgEventos from '../../assets/eventos.png'

function EventTableRow({ evento, permissions, onEdit, onDelete }) {
  const eventDate = parseDateValue(evento.data_evento)
  const today = new Date()
  today.setHours(0, 0, 0, 0)
  const isPast = eventDate && eventDate < today

  return (
    <tr className={isPast ? 'event-row-past' : ''}>
      <td data-label="Imagem">
        <img src={evento.imagem || BgEventos} alt={evento.nome} className="event-thumbnail" />
      </td>
      <td data-label="Nome do Evento">
        <span className="event-name">{evento.nome}</span>
        {evento.descricao && (
          <span className="event-desc-preview">{stripRichText(evento.descricao)}</span>
        )}
        {isPast && <span className="badge badge-encerrado">Encerrado</span>}
      </td>
      <td data-label="Data">{evento.data_evento}</td>
      <td data-label="Horario">{evento.horario}</td>
      <td data-label="Periodo">
        <span className={`badge badge-${evento.periodo?.toLowerCase()}`}>{evento.periodo}</span>
      </td>
      <td data-label="Acoes">
        <div className="action-buttons">
          {!isPast && (
            <>
              <button className="btn-icon btn-view" onClick={() => window.open(evento.link, '_blank')} title="Ver evento">
                <Eye size={16} />
              </button>
              {permissions.canEditEvents && (
                <button className="btn-icon btn-edit" onClick={() => onEdit(evento)} title="Editar">
                  <Edit2 size={16} />
                </button>
              )}
            </>
          )}
          {permissions.canDeleteEvents && (
            <button className="btn-icon btn-delete" onClick={() => onDelete(evento.id)} title="Excluir">
              <Trash2 size={16} />
            </button>
          )}
        </div>
      </td>
    </tr>
  )
}

export default EventTableRow

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions