mirror of
https://github.com/lucasrcsantana/story-generator.git
synced 2025-12-16 21:37:51 +00:00
refactor: extrai componentes de métricas do dashboard
Some checks are pending
Docker Build and Push / build (push) Waiting to run
Some checks are pending
Docker Build and Push / build (push) Waiting to run
Modulariza os cards de métricas em componentes reutilizáveis: - Cria componente MetricCard para cards individuais - Cria componente DashboardMetrics para agrupamento - Move configurações de métricas para constantes - Adiciona suporte a tooltips e ícones personalizados - Mantém responsividade e acessibilidade - Simplifica o StudentDashboardPage Mudanças técnicas: - Extrai lógica de renderização para componentes dedicados - Centraliza configuração de métricas em constantes - Melhora tipagem com interfaces dedicadas - Adiciona suporte a tooltips informativos - Mantém consistência visual com o design system - Reduz duplicação de código
This commit is contained in:
parent
7a0bc3f8ca
commit
478ca2441d
@ -244,3 +244,9 @@ e este projeto adere ao [Semantic Versioning](https://semver.org/lang/pt-BR/).
|
||||
- Estado interno gerenciado
|
||||
- Props minimalistas e bem tipadas
|
||||
- Componente reutilizável em outros contextos
|
||||
- Componentes de métricas extraídos e modularizados
|
||||
- Novo componente MetricCard para cards individuais
|
||||
- Novo componente DashboardMetrics para agrupamento
|
||||
- Configuração centralizada de métricas
|
||||
- Suporte a tooltips e ícones personalizados
|
||||
- Responsividade e acessibilidade melhoradas
|
||||
|
||||
160
src/components/dashboard/DashboardMetrics.tsx
Normal file
160
src/components/dashboard/DashboardMetrics.tsx
Normal file
@ -0,0 +1,160 @@
|
||||
import React from 'react';
|
||||
import { BookOpen, Clock, TrendingUp, Award, Mic, Target, Brain, Gauge, Pause, XCircle, HelpCircle } from 'lucide-react';
|
||||
import { MetricCard } from './MetricCard';
|
||||
|
||||
interface DashboardMetricsData {
|
||||
totalStories: number;
|
||||
averageReadingFluency: number;
|
||||
totalReadingTime: number;
|
||||
currentLevel: number;
|
||||
averagePronunciation: number;
|
||||
averageAccuracy: number;
|
||||
averageComprehension: number;
|
||||
averageWordsPerMinute: number;
|
||||
averagePauses: number;
|
||||
averageErrors: number;
|
||||
}
|
||||
|
||||
interface DashboardMetricsProps {
|
||||
data: DashboardMetricsData;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const MAIN_METRICS = [
|
||||
{
|
||||
key: 'totalStories',
|
||||
title: 'Total de Histórias',
|
||||
getValue: (data: DashboardMetricsData) => data.totalStories,
|
||||
icon: BookOpen,
|
||||
iconColor: 'text-purple-600',
|
||||
iconBgColor: 'bg-purple-100'
|
||||
},
|
||||
{
|
||||
key: 'averageReadingFluency',
|
||||
title: 'Fluência Média',
|
||||
getValue: (data: DashboardMetricsData) => `${data.averageReadingFluency}%`,
|
||||
icon: TrendingUp,
|
||||
iconColor: 'text-green-600',
|
||||
iconBgColor: 'bg-green-100'
|
||||
},
|
||||
{
|
||||
key: 'totalReadingTime',
|
||||
title: 'Tempo de Leitura',
|
||||
getValue: (data: DashboardMetricsData) => `${data.totalReadingTime}min`,
|
||||
icon: Clock,
|
||||
iconColor: 'text-blue-600',
|
||||
iconBgColor: 'bg-blue-100'
|
||||
},
|
||||
{
|
||||
key: 'currentLevel',
|
||||
title: 'Nível Atual',
|
||||
getValue: (data: DashboardMetricsData) => data.currentLevel,
|
||||
icon: Award,
|
||||
iconColor: 'text-yellow-600',
|
||||
iconBgColor: 'bg-yellow-100'
|
||||
}
|
||||
];
|
||||
|
||||
const DETAILED_METRICS = [
|
||||
{
|
||||
key: 'averagePronunciation',
|
||||
title: 'Pronúncia Média',
|
||||
getValue: (data: DashboardMetricsData) => `${data.averagePronunciation}%`,
|
||||
icon: Mic,
|
||||
iconColor: 'text-indigo-600',
|
||||
iconBgColor: 'bg-indigo-100',
|
||||
tooltip: 'Avalia a qualidade da sua pronúncia durante a leitura, considerando a clareza e correção dos sons das palavras'
|
||||
},
|
||||
{
|
||||
key: 'averageAccuracy',
|
||||
title: 'Precisão na Leitura',
|
||||
getValue: (data: DashboardMetricsData) => `${data.averageAccuracy}%`,
|
||||
icon: Target,
|
||||
iconColor: 'text-pink-600',
|
||||
iconBgColor: 'bg-pink-100',
|
||||
tooltip: 'Indica o quão preciso você é ao ler as palavras, sem trocas ou omissões de letras e sílabas'
|
||||
},
|
||||
{
|
||||
key: 'averageComprehension',
|
||||
title: 'Compreensão do Texto',
|
||||
getValue: (data: DashboardMetricsData) => `${data.averageComprehension}%`,
|
||||
icon: Brain,
|
||||
iconColor: 'text-orange-600',
|
||||
iconBgColor: 'bg-orange-100',
|
||||
tooltip: 'Avalia seu nível de entendimento do texto durante a leitura, baseado no ritmo e entonação adequados'
|
||||
},
|
||||
{
|
||||
key: 'averageWordsPerMinute',
|
||||
title: 'Velocidade de Leitura',
|
||||
getValue: (data: DashboardMetricsData) => `${data.averageWordsPerMinute} WPM`,
|
||||
icon: Gauge,
|
||||
iconColor: 'text-cyan-600',
|
||||
iconBgColor: 'bg-cyan-100',
|
||||
tooltip: 'Média de palavras lidas por minuto (WPM), indicando a velocidade e fluidez da sua leitura'
|
||||
},
|
||||
{
|
||||
key: 'averagePauses',
|
||||
title: 'Pausas na Leitura',
|
||||
getValue: (data: DashboardMetricsData) => data.averagePauses,
|
||||
icon: Pause,
|
||||
iconColor: 'text-amber-600',
|
||||
iconBgColor: 'bg-amber-100',
|
||||
tooltip: 'Média de pausas não planejadas durante a leitura, indicando momentos de hesitação'
|
||||
},
|
||||
{
|
||||
key: 'averageErrors',
|
||||
title: 'Erros de Leitura',
|
||||
getValue: (data: DashboardMetricsData) => data.averageErrors,
|
||||
icon: XCircle,
|
||||
iconColor: 'text-red-600',
|
||||
iconBgColor: 'bg-red-100',
|
||||
tooltip: 'Média de erros cometidos durante a leitura, como trocas, omissões ou adições de palavras'
|
||||
}
|
||||
];
|
||||
|
||||
export function DashboardMetrics({ data, className = '' }: DashboardMetricsProps) {
|
||||
return (
|
||||
<div className={className}>
|
||||
{/* Métricas Principais */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
|
||||
{MAIN_METRICS.map(metric => (
|
||||
<MetricCard
|
||||
key={metric.key}
|
||||
title={metric.title}
|
||||
value={metric.getValue(data)}
|
||||
icon={metric.icon}
|
||||
iconColor={metric.iconColor}
|
||||
iconBgColor={metric.iconBgColor}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Métricas Detalhadas */}
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<h2 className="text-lg font-semibold text-gray-900">Métricas Detalhadas de Leitura</h2>
|
||||
<div
|
||||
className="text-gray-400 hover:text-gray-600 cursor-help transition-colors"
|
||||
title="Estas métricas são calculadas com base em todas as suas gravações de leitura, fornecendo uma visão detalhada do seu progresso"
|
||||
>
|
||||
<HelpCircle className="h-4 w-4" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{DETAILED_METRICS.map(metric => (
|
||||
<MetricCard
|
||||
key={metric.key}
|
||||
title={metric.title}
|
||||
value={metric.getValue(data)}
|
||||
icon={metric.icon}
|
||||
iconColor={metric.iconColor}
|
||||
iconBgColor={metric.iconBgColor}
|
||||
tooltip={metric.tooltip}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
45
src/components/dashboard/MetricCard.tsx
Normal file
45
src/components/dashboard/MetricCard.tsx
Normal file
@ -0,0 +1,45 @@
|
||||
import React from 'react';
|
||||
import { LucideIcon } from 'lucide-react';
|
||||
import { HelpCircle } from 'lucide-react';
|
||||
|
||||
interface MetricCardProps {
|
||||
title: string;
|
||||
value: string | number;
|
||||
icon: LucideIcon;
|
||||
iconColor: string;
|
||||
iconBgColor: string;
|
||||
tooltip?: string;
|
||||
}
|
||||
|
||||
export function MetricCard({
|
||||
title,
|
||||
value,
|
||||
icon: Icon,
|
||||
iconColor,
|
||||
iconBgColor,
|
||||
tooltip
|
||||
}: MetricCardProps) {
|
||||
return (
|
||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className={`p-3 ${iconBgColor} rounded-lg`}>
|
||||
<Icon className={`h-6 w-6 ${iconColor}`} />
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-1">
|
||||
<p className="text-sm text-gray-500">{title}</p>
|
||||
{tooltip && (
|
||||
<div
|
||||
className="text-gray-400 hover:text-gray-600 cursor-help transition-colors"
|
||||
title={tooltip}
|
||||
>
|
||||
<HelpCircle className="h-4 w-4" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-gray-900">{value}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,9 +1,10 @@
|
||||
import React from 'react';
|
||||
import { Plus, BookOpen, Clock, TrendingUp, Award, Mic, Target, Brain, Gauge, Pause, XCircle, HelpCircle } from 'lucide-react';
|
||||
import { Plus, BookOpen } from 'lucide-react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { supabase } from '../../lib/supabase';
|
||||
import type { Story, Student } from '../../types/database';
|
||||
import { MetricsChart } from '@/components/dashboard/MetricsChart';
|
||||
import { DashboardMetrics } from '@/components/dashboard/DashboardMetrics';
|
||||
|
||||
interface DashboardMetrics {
|
||||
totalStories: number;
|
||||
@ -316,197 +317,8 @@ export function StudentDashboardPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Métricas Principais */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
|
||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="p-3 bg-purple-100 rounded-lg">
|
||||
<BookOpen className="h-6 w-6 text-purple-600" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Total de Histórias</p>
|
||||
<p className="text-2xl font-bold text-gray-900">{metrics.totalStories}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="p-3 bg-green-100 rounded-lg">
|
||||
<TrendingUp className="h-6 w-6 text-green-600" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Fluência Média</p>
|
||||
<p className="text-2xl font-bold text-gray-900">{metrics.averageReadingFluency}%</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="p-3 bg-blue-100 rounded-lg">
|
||||
<Clock className="h-6 w-6 text-blue-600" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Tempo de Leitura</p>
|
||||
<p className="text-2xl font-bold text-gray-900">{metrics.totalReadingTime}min</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="p-3 bg-yellow-100 rounded-lg">
|
||||
<Award className="h-6 w-6 text-yellow-600" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Nível Atual</p>
|
||||
<p className="text-2xl font-bold text-gray-900">{metrics.currentLevel}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Métricas Detalhadas */}
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<h2 className="text-lg font-semibold text-gray-900">Métricas Detalhadas de Leitura</h2>
|
||||
<div
|
||||
className="text-gray-400 hover:text-gray-600 cursor-help transition-colors"
|
||||
title="Estas métricas são calculadas com base em todas as suas gravações de leitura, fornecendo uma visão detalhada do seu progresso"
|
||||
>
|
||||
<HelpCircle className="h-4 w-4" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{/* Pronúncia */}
|
||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="p-3 bg-indigo-100 rounded-lg">
|
||||
<Mic className="h-6 w-6 text-indigo-600" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-1">
|
||||
<p className="text-sm text-gray-500">Pronúncia Média</p>
|
||||
<div
|
||||
className="text-gray-400 hover:text-gray-600 cursor-help transition-colors"
|
||||
title="Avalia a qualidade da sua pronúncia durante a leitura, considerando a clareza e correção dos sons das palavras"
|
||||
>
|
||||
<HelpCircle className="h-4 w-4" />
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-gray-900">{metrics.averagePronunciation}%</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Precisão */}
|
||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="p-3 bg-pink-100 rounded-lg">
|
||||
<Target className="h-6 w-6 text-pink-600" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-1">
|
||||
<p className="text-sm text-gray-500">Precisão na Leitura</p>
|
||||
<div
|
||||
className="text-gray-400 hover:text-gray-600 cursor-help transition-colors"
|
||||
title="Indica o quão preciso você é ao ler as palavras, sem trocas ou omissões de letras e sílabas"
|
||||
>
|
||||
<HelpCircle className="h-4 w-4" />
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-gray-900">{metrics.averageAccuracy}%</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Compreensão */}
|
||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="p-3 bg-orange-100 rounded-lg">
|
||||
<Brain className="h-6 w-6 text-orange-600" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-1">
|
||||
<p className="text-sm text-gray-500">Compreensão do Texto</p>
|
||||
<div
|
||||
className="text-gray-400 hover:text-gray-600 cursor-help transition-colors"
|
||||
title="Avalia seu nível de entendimento do texto durante a leitura, baseado no ritmo e entonação adequados"
|
||||
>
|
||||
<HelpCircle className="h-4 w-4" />
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-gray-900">{metrics.averageComprehension}%</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Velocidade */}
|
||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="p-3 bg-cyan-100 rounded-lg">
|
||||
<Gauge className="h-6 w-6 text-cyan-600" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-1">
|
||||
<p className="text-sm text-gray-500">Velocidade de Leitura</p>
|
||||
<div
|
||||
className="text-gray-400 hover:text-gray-600 cursor-help transition-colors"
|
||||
title="Média de palavras lidas por minuto (WPM), indicando a velocidade e fluidez da sua leitura"
|
||||
>
|
||||
<HelpCircle className="h-4 w-4" />
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-gray-900">{metrics.averageWordsPerMinute} WPM</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Pausas */}
|
||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="p-3 bg-amber-100 rounded-lg">
|
||||
<Pause className="h-6 w-6 text-amber-600" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-1">
|
||||
<p className="text-sm text-gray-500">Pausas na Leitura</p>
|
||||
<div
|
||||
className="text-gray-400 hover:text-gray-600 cursor-help transition-colors"
|
||||
title="Média de pausas não planejadas durante a leitura, indicando momentos de hesitação"
|
||||
>
|
||||
<HelpCircle className="h-4 w-4" />
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-gray-900">{metrics.averagePauses}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Erros */}
|
||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="p-3 bg-red-100 rounded-lg">
|
||||
<XCircle className="h-6 w-6 text-red-600" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-1">
|
||||
<p className="text-sm text-gray-500">Erros de Leitura</p>
|
||||
<div
|
||||
className="text-gray-400 hover:text-gray-600 cursor-help transition-colors"
|
||||
title="Média de erros cometidos durante a leitura, como trocas, omissões ou adições de palavras"
|
||||
>
|
||||
<HelpCircle className="h-4 w-4" />
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-gray-900">{metrics.averageErrors}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Métricas */}
|
||||
<DashboardMetrics data={metrics} />
|
||||
|
||||
{/* Gráfico de Evolução */}
|
||||
<MetricsChart data={weeklyMetrics} className="mb-8" />
|
||||
|
||||
Loading…
Reference in New Issue
Block a user