diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index f7fa87e..c23771a 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,34 +1,67 @@
-import type { Metadata } from "next";
-import { Geist, Geist_Mono } from "next/font/google";
-import "./globals.css";
+import type { Metadata } from 'next'
+import { Inter, Montserrat } from 'next/font/google'
+import './globals.css'
+import { cn } from '@/lib/utils'
-const geistSans = Geist({
- variable: "--font-geist-sans",
- subsets: ["latin"],
-});
+const inter = Inter({
+ subsets: ['latin'],
+ variable: '--font-sans',
+ display: 'swap',
+})
-const geistMono = Geist_Mono({
- variable: "--font-geist-mono",
- subsets: ["latin"],
-});
+const montserrat = Montserrat({
+ subsets: ['latin'],
+ variable: '--font-display',
+ display: 'swap',
+})
export const metadata: Metadata = {
- title: "Create Next App",
- description: "Generated by create next app",
-};
+ title: 'BI Descomplicar® | Painéis de Métricas',
+ description: 'Dashboards de performance digital para clientes Descomplicar - Agência de Aceleração Digital',
+ icons: {
+ icon: '/favicon.ico',
+ },
+}
export default function RootLayout({
children,
}: Readonly<{
- children: React.ReactNode;
+ children: React.ReactNode
}>) {
return (
-
-
- {children}
+
+
+
+
+
+
+
+
+ BI Descomplicar®
+
+
+ Painéis de Performance Digital
+
+
+
+
+
+
+
+ {children}
+
+
+
+
- );
+ )
}
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 295f8fd..f5b5c7f 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,65 +1,5 @@
-import Image from "next/image";
+import { DashboardView } from '@/components/dashboard/dashboard-view'
export default function Home() {
- return (
-
-
-
-
-
- To get started, edit the page.tsx file.
-
-
- Looking for a starting point or more instructions? Head over to{" "}
-
- Templates
- {" "}
- or the{" "}
-
- Learning
- {" "}
- center.
-
-
-
-
-
- );
+ return
}
diff --git a/src/components/dashboard/dashboard-view.tsx b/src/components/dashboard/dashboard-view.tsx
new file mode 100644
index 0000000..cae9463
--- /dev/null
+++ b/src/components/dashboard/dashboard-view.tsx
@@ -0,0 +1,279 @@
+'use client'
+
+import { useEffect, useState } from 'react'
+import { MetricCard } from './metric-card'
+import { ChartCard } from './chart-card'
+import { SiteSelector } from './site-selector'
+import { Users, MousePointerClick, TrendingUp, Eye } from 'lucide-react'
+import { formatNumber, formatPercent } from '@/lib/utils'
+
+interface Site {
+ id: number
+ siteName: string
+}
+
+interface Metrics {
+ visitors: number
+ visitorsChange: number
+ sessions: number
+ sessionsChange: number
+ pageViews: number
+ pageViewsChange: number
+ newUsers: number
+ newUsersChange: number
+ engagement: number
+}
+
+interface Charts {
+ dailyTraffic: Array<{
+ date: string
+ visitors: number
+ sessions: number
+ }>
+ trafficSources: Array<{
+ source: string
+ medium: string
+ sessions: number
+ }>
+ topQueries: Array<{
+ query: string
+ clicks: number
+ impressions: number
+ ctr: number
+ position: number
+ }>
+}
+
+interface MetricsData {
+ success: boolean
+ site: {
+ id: number
+ name: string
+ }
+ metrics: Metrics
+ charts: Charts
+}
+
+export function DashboardView() {
+ const [sites, setSites] = useState([])
+ const [selectedSite, setSelectedSite] = useState(null)
+ const [metricsData, setMetricsData] = useState(null)
+ const [loading, setLoading] = useState(true)
+ const [error, setError] = useState(null)
+
+ // Fetch sites on mount
+ useEffect(() => {
+ const fetchSites = async () => {
+ try {
+ const response = await fetch('/api/sites')
+ const data = await response.json()
+
+ if (data.success && data.sites.length > 0) {
+ setSites(data.sites)
+ setSelectedSite(data.sites[0].id)
+ } else {
+ setError('Nenhum site disponível')
+ }
+ } catch (err) {
+ setError('Erro ao carregar sites')
+ console.error('Error fetching sites:', err)
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ fetchSites()
+ }, [])
+
+ // Fetch metrics when site changes
+ useEffect(() => {
+ if (!selectedSite) return
+
+ const fetchMetrics = async () => {
+ setLoading(true)
+ try {
+ const response = await fetch(`/api/metrics/${selectedSite}?period=30d`)
+ const data = await response.json()
+
+ if (data.success) {
+ setMetricsData(data)
+ setError(null)
+ } else {
+ setError(data.error || 'Erro ao carregar métricas')
+ }
+ } catch (err) {
+ setError('Erro ao carregar métricas')
+ console.error('Error fetching metrics:', err)
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ fetchMetrics()
+ }, [selectedSite])
+
+ if (error && !selectedSite) {
+ return (
+
+ )
+ }
+
+ return (
+
+
+ {/* Header with Site Selector */}
+
+
+
+ Desempenho Digital
+
+
+ Últimos 30 dias
+
+
+
+
+
+ {error && selectedSite && (
+
+ )}
+
+ {/* KPIs Grid */}
+
+
+
+
+
+
+
+ {/* Charts Grid */}
+
+
+ ({
+ name: new Date(d.date).toLocaleDateString('pt-PT', {
+ day: '2-digit',
+ month: 'short'
+ }),
+ Visitantes: d.visitors,
+ Sessões: d.sessions,
+ })) || []}
+ type="area"
+ dataKey="Visitantes"
+ xAxisKey="name"
+ loading={loading}
+ />
+
+
+
+ ({
+ name: `${s.source} / ${s.medium}`,
+ value: s.sessions,
+ })) || []}
+ type="pie"
+ dataKey="value"
+ loading={loading}
+ />
+
+
+
+ {/* Top Queries Table */}
+ {metricsData?.charts.topQueries && metricsData.charts.topQueries.length > 0 && (
+
+
+
+ Top Queries de Pesquisa
+
+
+ Dados Google Search Console
+
+
+
+
+
+
+ |
+ Query
+ |
+
+ Clicks
+ |
+
+ Impressões
+ |
+
+ CTR
+ |
+
+ Posição
+ |
+
+
+
+ {metricsData.charts.topQueries.slice(0, 10).map((query, index) => (
+
+ |
+ {query.query}
+ |
+
+ {formatNumber(query.clicks)}
+ |
+
+ {formatNumber(query.impressions)}
+ |
+
+ {formatPercent(query.ctr)}
+ |
+
+ {query.position.toFixed(1)}
+ |
+
+ ))}
+
+
+
+
+ )}
+
+
+ )
+}