mirror of
https://github.com/lucasrcsantana/story-generator.git
synced 2025-12-18 14:27:51 +00:00
feat: adiciona filtro de período no gráfico de métricas
Implementa um sistema de filtragem temporal no gráfico de evolução das métricas, permitindo visualizar diferentes períodos de tempo: - Opções de 3, 6, 12 meses e todo período - Visualização padrão dos últimos 12 meses - Interface intuitiva com botões de período - Filtragem automática dos dados - Design consistente com o resto da aplicação Mudanças técnicas: - Adiciona sistema de filtragem temporal - Implementa conversão de datas semana/ano - Otimiza renderização do gráfico
This commit is contained in:
parent
c029aab50f
commit
7bb2a9a1b7
@ -229,9 +229,12 @@ e este projeto adere ao [Semantic Versioning](https://semver.org/lang/pt-BR/).
|
|||||||
- Tooltip personalizado com informações detalhadas
|
- Tooltip personalizado com informações detalhadas
|
||||||
- Agrupamento automático por semana
|
- Agrupamento automático por semana
|
||||||
- Layout responsivo e adaptável
|
- Layout responsivo e adaptável
|
||||||
|
- Filtro de período com opções de 3, 6, 12 meses e todo período
|
||||||
|
- Visualização padrão dos últimos 12 meses
|
||||||
|
|
||||||
### Técnico
|
### Técnico
|
||||||
- Implementação do Recharts para visualização de dados
|
- Implementação do Recharts para visualização de dados
|
||||||
- Novo sistema de processamento de métricas semanais
|
- Novo sistema de processamento de métricas semanais
|
||||||
- Otimização do carregamento de dados com agrupamento eficiente
|
- Otimização do carregamento de dados com agrupamento eficiente
|
||||||
- Integração com o tema existente do sistema
|
- Integração com o tema existente do sistema
|
||||||
|
- Sistema de filtragem temporal com conversão de datas
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Plus, BookOpen, Clock, TrendingUp, Award, Mic, Target, Brain, Gauge, Pause, XCircle, HelpCircle } from 'lucide-react';
|
import { Plus, BookOpen, Clock, TrendingUp, Award, Mic, Target, Brain, Gauge, Pause, XCircle, HelpCircle, Calendar } from 'lucide-react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { supabase } from '../../lib/supabase';
|
import { supabase } from '../../lib/supabase';
|
||||||
import type { Story, Student } from '../../types/database';
|
import type { Story, Student } from '../../types/database';
|
||||||
@ -59,6 +59,14 @@ interface MetricConfig {
|
|||||||
color: string;
|
color: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TimeFilter = '3m' | '6m' | '12m' | 'all';
|
||||||
|
|
||||||
|
interface TimeFilterOption {
|
||||||
|
value: TimeFilter;
|
||||||
|
label: string;
|
||||||
|
months: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
const METRICS_CONFIG: MetricConfig[] = [
|
const METRICS_CONFIG: MetricConfig[] = [
|
||||||
{ key: 'fluency', name: 'Fluência', color: '#6366f1' },
|
{ key: 'fluency', name: 'Fluência', color: '#6366f1' },
|
||||||
{ key: 'pronunciation', name: 'Pronúncia', color: '#f43f5e' },
|
{ key: 'pronunciation', name: 'Pronúncia', color: '#f43f5e' },
|
||||||
@ -67,6 +75,13 @@ const METRICS_CONFIG: MetricConfig[] = [
|
|||||||
{ key: 'wordsPerMinute', name: 'Palavras/Min', color: '#8b5cf6' }
|
{ key: 'wordsPerMinute', name: 'Palavras/Min', color: '#8b5cf6' }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const TIME_FILTERS: TimeFilterOption[] = [
|
||||||
|
{ value: '3m', label: '3 meses', months: 3 },
|
||||||
|
{ value: '6m', label: '6 meses', months: 6 },
|
||||||
|
{ value: '12m', label: '12 meses', months: 12 },
|
||||||
|
{ value: 'all', label: 'Todo período', months: null },
|
||||||
|
];
|
||||||
|
|
||||||
export function StudentDashboardPage() {
|
export function StudentDashboardPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [student, setStudent] = React.useState<Student | null>(null);
|
const [student, setStudent] = React.useState<Student | null>(null);
|
||||||
@ -89,6 +104,7 @@ export function StudentDashboardPage() {
|
|||||||
const [visibleMetrics, setVisibleMetrics] = React.useState<Set<string>>(
|
const [visibleMetrics, setVisibleMetrics] = React.useState<Set<string>>(
|
||||||
new Set(METRICS_CONFIG.map(metric => metric.key))
|
new Set(METRICS_CONFIG.map(metric => metric.key))
|
||||||
);
|
);
|
||||||
|
const [timeFilter, setTimeFilter] = React.useState<TimeFilter>('12m');
|
||||||
|
|
||||||
const processWeeklyMetrics = (recordings: Recording[]) => {
|
const processWeeklyMetrics = (recordings: Recording[]) => {
|
||||||
const weeklyData = recordings.reduce((acc: { [key: string]: WeeklyData }, recording) => {
|
const weeklyData = recordings.reduce((acc: { [key: string]: WeeklyData }, recording) => {
|
||||||
@ -149,6 +165,20 @@ export function StudentDashboardPage() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const filterDataByTime = (data: WeeklyMetrics[]): WeeklyMetrics[] => {
|
||||||
|
if (timeFilter === 'all') return data;
|
||||||
|
|
||||||
|
const months = TIME_FILTERS.find(f => f.value === timeFilter)?.months || 12;
|
||||||
|
const cutoffDate = new Date();
|
||||||
|
cutoffDate.setMonth(cutoffDate.getMonth() - months);
|
||||||
|
|
||||||
|
return data.filter(item => {
|
||||||
|
const [year, week] = item.week.split('-W').map(Number);
|
||||||
|
const itemDate = new Date(year, 0, 1 + (week - 1) * 7);
|
||||||
|
return itemDate >= cutoffDate;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const fetchDashboardData = async () => {
|
const fetchDashboardData = async () => {
|
||||||
try {
|
try {
|
||||||
@ -310,6 +340,8 @@ export function StudentDashboardPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const renderMetricsChart = () => {
|
const renderMetricsChart = () => {
|
||||||
|
const filteredData = filterDataByTime(weeklyMetrics);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-8 mb-8">
|
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-8 mb-8">
|
||||||
<div className="flex flex-col gap-6">
|
<div className="flex flex-col gap-6">
|
||||||
@ -318,7 +350,26 @@ export function StudentDashboardPage() {
|
|||||||
<h2 className="text-xl font-semibold text-gray-900">Evolução das Métricas por Semana</h2>
|
<h2 className="text-xl font-semibold text-gray-900">Evolução das Métricas por Semana</h2>
|
||||||
<p className="text-sm text-gray-500">Acompanhe seu progresso ao longo do tempo</p>
|
<p className="text-sm text-gray-500">Acompanhe seu progresso ao longo do tempo</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-4">
|
||||||
|
{/* Filtro de Período */}
|
||||||
|
<div className="flex items-center gap-2 bg-gray-50 p-1 rounded-lg">
|
||||||
|
<Calendar className="h-4 w-4 text-gray-500" />
|
||||||
|
{TIME_FILTERS.map(filter => (
|
||||||
|
<button
|
||||||
|
key={filter.value}
|
||||||
|
onClick={() => setTimeFilter(filter.value)}
|
||||||
|
className={`
|
||||||
|
px-3 py-1 rounded-md text-sm font-medium transition-all duration-200
|
||||||
|
${timeFilter === filter.value
|
||||||
|
? 'bg-white text-purple-600 shadow-sm'
|
||||||
|
: 'text-gray-600 hover:bg-gray-100'
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{filter.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
className="text-gray-400 hover:text-gray-600 cursor-help transition-colors"
|
className="text-gray-400 hover:text-gray-600 cursor-help transition-colors"
|
||||||
title="Gráfico mostrando a evolução das suas métricas de leitura ao longo das semanas"
|
title="Gráfico mostrando a evolução das suas métricas de leitura ao longo das semanas"
|
||||||
@ -358,7 +409,7 @@ export function StudentDashboardPage() {
|
|||||||
|
|
||||||
<div className="h-[400px] w-full">
|
<div className="h-[400px] w-full">
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
<ComposedChart data={weeklyMetrics} margin={{ top: 20, right: 30, left: 20, bottom: 20 }}>
|
<ComposedChart data={filteredData} margin={{ top: 20, right: 30, left: 20, bottom: 20 }}>
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="barGradient" x1="0" y1="0" x2="0" y2="1">
|
<linearGradient id="barGradient" x1="0" y1="0" x2="0" y2="1">
|
||||||
<stop offset="0%" stopColor="#6366f1" stopOpacity={0.3} />
|
<stop offset="0%" stopColor="#6366f1" stopOpacity={0.3} />
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user