Portal Dev Full Stack com IA
Inicio / Trilha 3 / Modulo 3.8
Trilha 3 - Avancado Projeto Final

πŸ† Projeto Final: Dashboard Enterprise Completo

Integre todos os conhecimentos das 3 trilhas em um dashboard de producao real com autenticacao, integracao de dados, visualizacoes avancadas e deploy na nuvem.

6
Topicos
8h
Duracao
Expert
Nivel
Pratico
Tipo

🎯 Visao Geral do Projeto

Voce construira um Dashboard Analytics Enterprise completo que integra multiplas fontes de dados, oferece visualizacoes interativas, controle de acesso por roles, e pode ser deployado em producao.

πŸ“Š
Frontend
React + TailwindCSS + Recharts + TanStack Query
βš™οΈ
Backend
Node.js + TypeScript + Prisma + Redis
☁️
Infraestrutura
Docker + GitHub Actions + Vercel/Cloud Run
1

πŸ“‹ Especificacao do Projeto

Requisitos funcionais, nao-funcionais e escopo

🎯 Requisitos Funcionais

Autenticacao e Acesso
  • β˜‘οΈ Login com email/senha e OAuth (Google)
  • β˜‘οΈ Roles: Admin, Manager, Analyst, Viewer
  • β˜‘οΈ Convite de usuarios por email
  • β˜‘οΈ Audit log de acoes
Dashboard Core
  • β˜‘οΈ KPIs em tempo real
  • β˜‘οΈ Graficos interativos (linha, barra, pizza)
  • β˜‘οΈ Filtros por periodo, departamento, categoria
  • β˜‘οΈ Export para PDF e Excel
Dados e Integracoes
  • β˜‘οΈ Conexao com banco PostgreSQL
  • β˜‘οΈ API REST para dados
  • β˜‘οΈ Import de CSV/Excel
  • β˜‘οΈ Webhook para updates externos
UX e Configuracao
  • β˜‘οΈ Dark/Light mode
  • β˜‘οΈ Responsivo (desktop, tablet, mobile)
  • β˜‘οΈ Dashboards customizaveis
  • β˜‘οΈ Notificacoes de alertas

⚑ Requisitos Nao-Funcionais

πŸ“ˆ
Performance
LCP < 2.5s
πŸ”’
Seguranca
OWASP Top 10
πŸ“±
Responsivo
Mobile-first
β™Ώ
Acessibilidade
WCAG 2.1 AA

πŸ“ Estrutura de Pastas

dashboard-enterprise/
β”œβ”€β”€ apps/
β”‚   β”œβ”€β”€ web/                    # Frontend React
β”‚   β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”‚   β”œβ”€β”€ components/     # UI components
β”‚   β”‚   β”‚   β”œβ”€β”€ features/       # Feature modules
β”‚   β”‚   β”‚   β”œβ”€β”€ hooks/          # Custom hooks
β”‚   β”‚   β”‚   β”œβ”€β”€ lib/            # Utilities
β”‚   β”‚   β”‚   β”œβ”€β”€ pages/          # Route pages
β”‚   β”‚   β”‚   └── stores/         # State management
β”‚   β”‚   └── package.json
β”‚   β”‚
β”‚   └── api/                    # Backend Node.js
β”‚       β”œβ”€β”€ src/
β”‚       β”‚   β”œβ”€β”€ modules/        # Feature modules
β”‚       β”‚   β”œβ”€β”€ middleware/     # Express middlewares
β”‚       β”‚   β”œβ”€β”€ services/       # Business logic
β”‚       β”‚   └── utils/          # Helpers
β”‚       β”œβ”€β”€ prisma/
β”‚       β”‚   └── schema.prisma   # Database schema
β”‚       └── package.json
β”‚
β”œβ”€β”€ packages/
β”‚   β”œβ”€β”€ ui/                     # Shared UI components
β”‚   β”œβ”€β”€ config/                 # ESLint, TS configs
β”‚   └── types/                  # Shared TypeScript types
β”‚
β”œβ”€β”€ docker-compose.yml
β”œβ”€β”€ turbo.json                  # Monorepo config
└── package.json
2

πŸ—οΈ Arquitetura do Sistema

Design de alto nivel e decisoes tecnicas

πŸ“ Diagrama de Arquitetura

Usuarios
πŸ‘₯
Browsers
Edge
🌐
CDN
Vercel Edge
Frontend
βš›οΈ
React SPA
Static hosting
Backend
βš™οΈ
API
Cloud Run
Data
πŸ—„οΈ
PostgreSQL
+ Redis cache
← β†’ ← β†’ ← β†’ ← β†’

πŸ”§ Stack Tecnologico

Camada Tecnologia Justificativa
Frontend React 18 + Vite Ecosystem, performance, DX
Styling TailwindCSS + Radix UI Utility-first, acessibilidade
State TanStack Query + Zustand Server state vs client state
Charts Recharts React-native, customizavel
Backend Node.js + Express TypeScript, performance
ORM Prisma Type safety, migrations
Auth JWT + OAuth (Auth.js) Stateless, social login
Cache Redis Sessions, rate limiting

πŸ’» Schema do Banco de Dados

// prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id            String    @id @default(cuid())
  email         String    @unique
  name          String?
  passwordHash  String?
  role          Role      @default(VIEWER)
  department    String?
  createdAt     DateTime  @default(now())
  updatedAt     DateTime  @updatedAt

  dashboards    Dashboard[]
  auditLogs     AuditLog[]
  sessions      Session[]
}

model Dashboard {
  id          String   @id @default(cuid())
  name        String
  description String?
  config      Json     // Layout, widgets config
  isPublic    Boolean  @default(false)
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt

  owner       User     @relation(fields: [ownerId], references: [id])
  ownerId     String

  widgets     Widget[]
}

model Widget {
  id          String   @id @default(cuid())
  type        WidgetType
  title       String
  config      Json     // Chart config, data source
  position    Json     // x, y, width, height

  dashboard   Dashboard @relation(fields: [dashboardId], references: [id])
  dashboardId String
}

model DataSource {
  id          String   @id @default(cuid())
  name        String
  type        DataSourceType
  config      Json     // Connection details (encrypted)
  lastSync    DateTime?
  createdAt   DateTime @default(now())
}

model Metric {
  id          String   @id @default(cuid())
  name        String
  value       Float
  category    String
  department  String?
  timestamp   DateTime @default(now())

  @@index([category, timestamp])
  @@index([department, timestamp])
}

model AuditLog {
  id        String   @id @default(cuid())
  action    String
  resource  String
  details   Json?
  ip        String?
  timestamp DateTime @default(now())

  user      User     @relation(fields: [userId], references: [id])
  userId    String

  @@index([userId, timestamp])
}

enum Role {
  ADMIN
  MANAGER
  ANALYST
  VIEWER
}

enum WidgetType {
  LINE_CHART
  BAR_CHART
  PIE_CHART
  KPI_CARD
  TABLE
  MAP
}

enum DataSourceType {
  POSTGRESQL
  MYSQL
  API
  CSV
}
3

βš™οΈ Implementacao Backend

APIs, autenticacao e servicos de dados

πŸ“‘ Endpoints da API

POST /api/auth/login
Login com credenciais
GET /api/dashboards
Listar dashboards
GET /api/metrics?range=7d
Metricas com filtros
POST /api/metrics/aggregate
Agregacoes customizadas
PUT /api/dashboards/:id
Atualizar dashboard
POST /api/export/:format
Export PDF/Excel

πŸ’» Codigo: API de Metricas

// src/modules/metrics/metrics.controller.ts
import { Router } from 'express';
import { authenticate, authorize } from '../../middleware/auth';
import { validateRequest } from '../../middleware/validation';
import { metricsService } from './metrics.service';
import { z } from 'zod';

const router = Router();

const getMetricsSchema = z.object({
  query: z.object({
    range: z.enum(['24h', '7d', '30d', '90d', '1y']).default('7d'),
    department: z.string().optional(),
    category: z.string().optional(),
    granularity: z.enum(['hour', 'day', 'week', 'month']).default('day'),
  }),
});

router.get(
  '/',
  authenticate,
  validateRequest(getMetricsSchema),
  async (req, res) => {
    const { range, department, category, granularity } = req.query;

    // Cache key baseado nos parametros
    const cacheKey = `metrics:${range}:${department || 'all'}:${category || 'all'}`;

    // Tenta buscar do cache
    const cached = await redis.get(cacheKey);
    if (cached) {
      return res.json(JSON.parse(cached));
    }

    const metrics = await metricsService.getMetrics({
      range,
      department: req.user.role === 'ADMIN' ? department : req.user.department,
      category,
      granularity,
    });

    // Cache por 5 minutos
    await redis.setex(cacheKey, 300, JSON.stringify(metrics));

    res.json(metrics);
  }
);

// Agregacao customizada
const aggregateSchema = z.object({
  body: z.object({
    metrics: z.array(z.string()),
    groupBy: z.array(z.enum(['department', 'category', 'date'])),
    aggregation: z.enum(['sum', 'avg', 'count', 'min', 'max']),
    filters: z.record(z.any()).optional(),
    dateRange: z.object({
      start: z.string().datetime(),
      end: z.string().datetime(),
    }),
  }),
});

router.post(
  '/aggregate',
  authenticate,
  authorize(['ADMIN', 'MANAGER', 'ANALYST']),
  validateRequest(aggregateSchema),
  async (req, res) => {
    const result = await metricsService.aggregate(req.body);
    res.json(result);
  }
);

export default router;

πŸ’» Codigo: Middleware de Autenticacao

// src/middleware/auth.ts
import { Request, Response, NextFunction } from 'express';
import { verifyToken } from '../lib/jwt';
import { prisma } from '../lib/prisma';
import { auditService } from '../services/audit.service';

export interface AuthRequest extends Request {
  user: {
    id: string;
    email: string;
    role: 'ADMIN' | 'MANAGER' | 'ANALYST' | 'VIEWER';
    department?: string;
  };
}

export const authenticate = async (
  req: Request,
  res: Response,
  next: NextFunction
) => {
  const authHeader = req.headers.authorization;

  if (!authHeader?.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Authentication required' });
  }

  try {
    const token = authHeader.split(' ')[1];
    const payload = await verifyToken(token);

    const user = await prisma.user.findUnique({
      where: { id: payload.sub },
      select: { id: true, email: true, role: true, department: true },
    });

    if (!user) {
      return res.status(401).json({ error: 'User not found' });
    }

    (req as AuthRequest).user = user;
    next();
  } catch (error) {
    auditService.log({
      action: 'AUTH_FAILED',
      resource: 'api',
      details: { error: error.message },
      ip: req.ip,
    });
    return res.status(401).json({ error: 'Invalid token' });
  }
};

export const authorize = (allowedRoles: string[]) => {
  return (req: Request, res: Response, next: NextFunction) => {
    const user = (req as AuthRequest).user;

    if (!allowedRoles.includes(user.role)) {
      auditService.log({
        action: 'ACCESS_DENIED',
        resource: req.path,
        userId: user.id,
        details: { requiredRoles: allowedRoles, userRole: user.role },
        ip: req.ip,
      });
      return res.status(403).json({ error: 'Insufficient permissions' });
    }

    next();
  };
};
4

βš›οΈ Implementacao Frontend

Componentes, estado e visualizacoes

🧩 Componentes Principais

πŸ“Š
KPICard
Metricas em destaque
πŸ“ˆ
LineChart
Series temporais
πŸ“Š
BarChart
Comparacoes
πŸ₯§
PieChart
Distribuicoes
πŸ“‹
DataTable
Listas virtuais
πŸ”
FilterBar
Filtros globais
πŸ“…
DatePicker
Selecao de periodo
πŸ“€
ExportMenu
PDF, Excel, CSV

πŸ’» Codigo: Dashboard Page

// src/pages/Dashboard.tsx
import { useMetrics } from '@/hooks/useMetrics';
import { useDashboardFilters } from '@/stores/filters';
import { KPIGrid } from '@/components/KPIGrid';
import { ChartGrid } from '@/components/ChartGrid';
import { FilterBar } from '@/components/FilterBar';
import { DateRangePicker } from '@/components/DateRangePicker';
import { ExportMenu } from '@/components/ExportMenu';
import { Skeleton } from '@/components/ui/Skeleton';

export function DashboardPage() {
  const { dateRange, department, setDateRange } = useDashboardFilters();

  const { data: metrics, isLoading, error } = useMetrics({
    range: dateRange,
    department,
  });

  if (error) {
    return <ErrorBoundary error={error} />;
  }

  return (
    <div className="space-y-6">
      {/* Header com filtros */}
      <div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
        <div>
          <h1 className="text-2xl font-bold">Dashboard</h1>
          <p className="text-neutral-400">Visao geral das metricas</p>
        </div>
        <div className="flex items-center gap-3">
          <DateRangePicker value={dateRange} onChange={setDateRange} />
          <FilterBar />
          <ExportMenu data={metrics} />
        </div>
      </div>

      {/* KPIs */}
      {isLoading ? (
        <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
          {[...Array(4)].map((_, i) => (
            <Skeleton key={i} className="h-24" />
          ))}
        </div>
      ) : (
        <KPIGrid
          metrics={[
            {
              label: 'Receita Total',
              value: metrics.revenue,
              change: metrics.revenueChange,
              format: 'currency',
            },
            {
              label: 'Usuarios Ativos',
              value: metrics.activeUsers,
              change: metrics.usersChange,
              format: 'number',
            },
            {
              label: 'Conversao',
              value: metrics.conversionRate,
              change: metrics.conversionChange,
              format: 'percent',
            },
            {
              label: 'Ticket Medio',
              value: metrics.avgTicket,
              change: metrics.ticketChange,
              format: 'currency',
            },
          ]}
        />
      )}

      {/* Charts Grid */}
      <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
        <ChartCard title="Receita por Periodo">
          <LineChart
            data={metrics?.revenueTimeSeries}
            loading={isLoading}
          />
        </ChartCard>

        <ChartCard title="Vendas por Categoria">
          <BarChart
            data={metrics?.salesByCategory}
            loading={isLoading}
          />
        </ChartCard>

        <ChartCard title="Distribuicao por Regiao">
          <PieChart
            data={metrics?.salesByRegion}
            loading={isLoading}
          />
        </ChartCard>

        <ChartCard title="Top Produtos">
          <DataTable
            columns={productColumns}
            data={metrics?.topProducts}
            loading={isLoading}
          />
        </ChartCard>
      </div>
    </div>
  );
}

πŸ’» Codigo: Hook de Metricas

// src/hooks/useMetrics.ts
import { useQuery, UseQueryOptions } from '@tanstack/react-query';
import { api } from '@/lib/api';

interface MetricsParams {
  range: '24h' | '7d' | '30d' | '90d' | '1y';
  department?: string;
  category?: string;
}

interface MetricsData {
  revenue: number;
  revenueChange: number;
  activeUsers: number;
  usersChange: number;
  conversionRate: number;
  conversionChange: number;
  avgTicket: number;
  ticketChange: number;
  revenueTimeSeries: TimeSeriesPoint[];
  salesByCategory: CategoryData[];
  salesByRegion: RegionData[];
  topProducts: Product[];
}

export function useMetrics(
  params: MetricsParams,
  options?: UseQueryOptions<MetricsData>
) {
  return useQuery({
    queryKey: ['metrics', params],
    queryFn: async () => {
      const { data } = await api.get<MetricsData>('/metrics', { params });
      return data;
    },
    staleTime: 5 * 60 * 1000, // 5 minutos
    refetchInterval: 60 * 1000, // Refetch a cada minuto
    refetchOnWindowFocus: true,
    retry: 3,
    ...options,
  });
}

// Hook para metricas em tempo real via WebSocket
export function useRealtimeMetrics(dashboardId: string) {
  const queryClient = useQueryClient();

  useEffect(() => {
    const ws = new WebSocket(`${WS_URL}/metrics/${dashboardId}`);

    ws.onmessage = (event) => {
      const update = JSON.parse(event.data);

      // Atualiza cache do React Query
      queryClient.setQueryData(['metrics', { dashboardId }], (old: MetricsData) => ({
        ...old,
        ...update,
      }));
    };

    return () => ws.close();
  }, [dashboardId, queryClient]);
}
5

πŸ§ͺ Testes e Integracao

Garantia de qualidade em todas as camadas

🎯 Piramide de Testes

E2E (Playwright)
5% - Fluxos criticos
Integracao (Testing Library)
20% - APIs, componentes complexos
Unitarios (Vitest)
75% - Funcoes, hooks, utils

πŸ’» Codigo: Testes de Componente

// src/components/__tests__/KPICard.test.tsx
import { render, screen } from '@testing-library/react';
import { KPICard } from '../KPICard';

describe('KPICard', () => {
  it('renders metric value correctly', () => {
    render(
      <KPICard
        label="Receita"
        value={150000}
        format="currency"
        change={12.5}
      />
    );

    expect(screen.getByText('Receita')).toBeInTheDocument();
    expect(screen.getByText('R$ 150.000,00')).toBeInTheDocument();
    expect(screen.getByText('+12.5%')).toBeInTheDocument();
  });

  it('shows negative change with correct styling', () => {
    render(
      <KPICard
        label="Custos"
        value={50000}
        format="currency"
        change={-8.3}
      />
    );

    const changeElement = screen.getByText('-8.3%');
    expect(changeElement).toHaveClass('text-red-400');
  });

  it('renders skeleton when loading', () => {
    render(<KPICard label="Receita" loading />);

    expect(screen.getByTestId('kpi-skeleton')).toBeInTheDocument();
  });
});

// Teste de hook com mock
describe('useMetrics', () => {
  it('fetches and caches metrics', async () => {
    const { result } = renderHook(() =>
      useMetrics({ range: '7d' }),
      { wrapper: QueryWrapper }
    );

    await waitFor(() => expect(result.current.isSuccess).toBe(true));

    expect(result.current.data?.revenue).toBeDefined();
    expect(mockApi.get).toHaveBeenCalledWith('/metrics', {
      params: { range: '7d' },
    });
  });
});

πŸ’» Codigo: Teste E2E

// e2e/dashboard.spec.ts
import { test, expect } from '@playwright/test';

test.describe('Dashboard', () => {
  test.beforeEach(async ({ page }) => {
    // Login via API para speed
    await page.request.post('/api/auth/login', {
      data: { email: 'test@example.com', password: 'test123' },
    });

    await page.goto('/dashboard');
  });

  test('displays KPIs and charts', async ({ page }) => {
    // Aguarda carregamento
    await expect(page.getByText('Receita Total')).toBeVisible();
    await expect(page.getByText('Usuarios Ativos')).toBeVisible();

    // Verifica charts
    await expect(page.locator('[data-testid="line-chart"]')).toBeVisible();
    await expect(page.locator('[data-testid="bar-chart"]')).toBeVisible();
  });

  test('filters update charts', async ({ page }) => {
    // Muda filtro de periodo
    await page.getByRole('button', { name: '7 dias' }).click();
    await page.getByRole('option', { name: '30 dias' }).click();

    // Verifica que charts atualizaram (via network)
    await page.waitForResponse(resp =>
      resp.url().includes('/api/metrics') &&
      resp.url().includes('range=30d')
    );
  });

  test('export generates PDF', async ({ page }) => {
    // Clica em exportar
    await page.getByRole('button', { name: 'Exportar' }).click();
    await page.getByRole('menuitem', { name: 'PDF' }).click();

    // Verifica download
    const download = await page.waitForEvent('download');
    expect(download.suggestedFilename()).toContain('.pdf');
  });
});
6

πŸš€ Deploy e Producao

Levando o projeto para o ar

πŸ“‹ Checklist de Deploy

Pre-Deploy
βœ“ Testes passando (unit, integration, e2e)
βœ“ Build otimizado (bundle analyzer)
βœ“ Environment variables configuradas
βœ“ Migrations aplicadas em staging
βœ“ Security scan (npm audit, SAST)
Pos-Deploy
β†’ Smoke tests em producao
β†’ Monitorar metricas (LCP, error rate)
β†’ Verificar logs de erro
β†’ Rollback plan pronto
β†’ Notificar stakeholders

🌐 Infraestrutura de Producao

β–²
Vercel
Frontend
☁️
Cloud Run
API Backend
🐘
Supabase
PostgreSQL
πŸ“Š
Upstash
Redis
Custo estimado: $50-100/mes para MVPs

πŸ’» GitHub Actions Completo

# .github/workflows/deploy.yml
name: CI/CD Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

env:
  NODE_VERSION: '20'

jobs:
  lint-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v2
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'pnpm'

      - run: pnpm install --frozen-lockfile
      - run: pnpm lint
      - run: pnpm type-check
      - run: pnpm test:coverage

      - uses: codecov/codecov-action@v3

  e2e-tests:
    runs-on: ubuntu-latest
    needs: lint-and-test
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v2
      - uses: actions/setup-node@v4

      - run: pnpm install
      - run: pnpm exec playwright install --with-deps

      - name: Run E2E tests
        run: pnpm test:e2e
        env:
          DATABASE_URL: ${{ secrets.TEST_DATABASE_URL }}

  deploy-preview:
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-latest
    needs: [lint-and-test, e2e-tests]
    steps:
      - uses: actions/checkout@v4
      - uses: amondnet/vercel-action@v25
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
          vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}

  deploy-production:
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    needs: [lint-and-test, e2e-tests]
    environment: production
    steps:
      - uses: actions/checkout@v4

      # Deploy Frontend (Vercel)
      - uses: amondnet/vercel-action@v25
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-args: '--prod'
          vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
          vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}

      # Deploy Backend (Cloud Run)
      - uses: google-github-actions/auth@v2
        with:
          credentials_json: ${{ secrets.GCP_CREDENTIALS }}

      - uses: google-github-actions/deploy-cloudrun@v2
        with:
          service: dashboard-api
          region: us-central1
          source: ./apps/api

      # Notify
      - uses: slackapi/slack-github-action@v1
        with:
          payload: |
            {"text": "πŸš€ Dashboard deployed to production!"}

πŸ† Resultado Final

Ao completar este projeto, voce tera construido:

  • βœ… Dashboard completo com autenticacao multi-tenant
  • βœ… API RESTful com TypeScript e validacao
  • βœ… Visualizacoes interativas e responsivas
  • βœ… Pipeline CI/CD automatizado
  • βœ… Infraestrutura escalavel na nuvem
  • βœ… Suite de testes completa
  • βœ… Documentacao e codigo limpo
Repositorio do projeto:
github.com/seuuser/dashboard-enterprise
πŸŽ“

Parabens! Voce Completou o Dashboard Mastery

144 topicos, 3 trilhas, conhecimento completo

Trilha 1
Fundamentos
8 modulos, 48 topicos
Trilha 2
Tecnicas
8 modulos, 48 topicos
Trilha 3
Avancado
8 modulos, 48 topicos

Agora voce tem o conhecimento para construir dashboards profissionais de nivel enterprise.

Continue praticando, explorando novas tecnologias e compartilhando conhecimento!