diff --git a/backend/src/main/java/net/beetechgroup/beetask/frameworks/config/PublicStatsUseCaseConfig.java b/backend/src/main/java/net/beetechgroup/beetask/frameworks/config/PublicStatsUseCaseConfig.java new file mode 100644 index 0000000..d501889 --- /dev/null +++ b/backend/src/main/java/net/beetechgroup/beetask/frameworks/config/PublicStatsUseCaseConfig.java @@ -0,0 +1,19 @@ +package net.beetechgroup.beetask.frameworks.config; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; +import net.beetechgroup.beetask.usecase.publicstats.PublicStatsUseCase; +import net.beetechgroup.beetask.usecase.repository.OrganizationRepository; +import net.beetechgroup.beetask.usecase.repository.TaskRepository; +import net.beetechgroup.beetask.usecase.repository.UserRepository; + +@ApplicationScoped +public class PublicStatsUseCaseConfig { + @Produces + public PublicStatsUseCase publicStatsUseCase( + UserRepository userRepository, + OrganizationRepository organizationRepository, + TaskRepository taskRepository) { + return new PublicStatsUseCase(userRepository, organizationRepository, taskRepository); + } +} diff --git a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/OrganizationRepositoryImpl.java b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/OrganizationRepositoryImpl.java index a4e9209..1505694 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/OrganizationRepositoryImpl.java +++ b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/OrganizationRepositoryImpl.java @@ -57,4 +57,9 @@ public class OrganizationRepositoryImpl implements OrganizationRepository, Panac } return organization; } + + @Override + public long countAll() { + return count(); + } } diff --git a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/TaskRepositoryImpl.java b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/TaskRepositoryImpl.java index 8fb7603..93cf234 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/TaskRepositoryImpl.java +++ b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/TaskRepositoryImpl.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.time.LocalDateTime; +import java.time.Duration; import io.quarkus.hibernate.orm.panache.PanacheRepository; import jakarta.enterprise.context.ApplicationScoped; @@ -172,4 +173,18 @@ public class TaskRepositoryImpl implements TaskRepository, PanacheRepository task.getHistory().stream()) + .filter(h -> Objects.nonNull(h.getEndAt())) + .mapToLong(h -> Duration.between(h.getStartAt(), h.getEndAt()).toMinutes()) + .sum(); + } } diff --git a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/UserRepositoryImpl.java b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/UserRepositoryImpl.java index cdfe325..699f744 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/UserRepositoryImpl.java +++ b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/UserRepositoryImpl.java @@ -64,4 +64,9 @@ public class UserRepositoryImpl implements UserRepository, PanacheRepository search(String query); Optional findOrganizationById(Long id); + long countAll(); } \ No newline at end of file diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/repository/TaskRepository.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/repository/TaskRepository.java index cc5a549..4478b5d 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/usecase/repository/TaskRepository.java +++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/repository/TaskRepository.java @@ -21,4 +21,6 @@ public interface TaskRepository { List findTasksFinishedByUserIdInPeriod(Long userId, LocalDateTime start, LocalDateTime end); List findTasksWorkedByUserEmailsInPeriod(List emails, LocalDateTime start, LocalDateTime end); List findTasksFinishedByUserEmailsInPeriod(List emails, LocalDateTime start, LocalDateTime end); + long countByStatus(TaskStatus status); + long sumAllCompletedTrackedMinutes(); } \ No newline at end of file diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/repository/UserRepository.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/repository/UserRepository.java index 858c4b8..8fe49be 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/usecase/repository/UserRepository.java +++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/repository/UserRepository.java @@ -10,4 +10,5 @@ public interface UserRepository { Optional findByEmail(String email); Optional findUserById(Long id); List findUserOrganizations(Long userId); + long countAll(); } diff --git a/frontend/src/pages/LandingPage.tsx b/frontend/src/pages/LandingPage.tsx new file mode 100644 index 0000000..6993ae9 --- /dev/null +++ b/frontend/src/pages/LandingPage.tsx @@ -0,0 +1,628 @@ +'use client'; + +import { useEffect, useState, useRef } from 'react'; +import { Link, useNavigate } from 'react-router-dom'; +import { motion, useInView } from 'framer-motion'; +import { + BarChart3, + TrendingUp, + Users, + Zap, + CheckCircle2, + Clock, + Trophy, + ArrowRight, + Menu, + X, + Layout, +} from 'lucide-react'; +import { cn } from '../lib/utils'; +import { publicStatsService } from '../services/publicStatsService'; +import type { PublicStats } from '../services/publicStatsService'; + +export const LandingPage = () => { + const navigate = useNavigate(); + const [isMenuOpen, setIsMenuOpen] = useState(false); + const [stats, setStats] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const fetchStats = async () => { + try { + const data = await publicStatsService.getStats(); + setStats(data); + } catch (err) { + console.error('Failed to fetch public stats:', err); + setStats(null); + } finally { + setLoading(false); + } + }; + + fetchStats(); + }, []); + + return ( +
+ {/* Subtle radial gradient overlay */} +
+
+
+
+ + {/* Navigation */} + + + {/* Hero Section */} +
+ +

+ Gestão de equipes com{' '} + + visibilidade real + +

+ +

+ Organize tarefas, rastreie tempo e acompanhe a produtividade da sua equipe em tempo real. + Tome decisões baseadas em dados. +

+ +
+ + +
+ + {/* Hero Decoration - Simple SVG Dashboard Mock */} + +
+
+
+
Dashboard
+
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ + {/* Floating cards */} + + + TOP + + + + + 8h 30m + + + +
+ + {/* Features Section */} +
+ +

+ Funcionalidades principais +

+

+ Tudo que sua equipe precisa para gerenciar tarefas e produtividade +

+
+ +
+ {[ + { + icon: BarChart3, + title: 'Dashboard da Organização', + description: 'Visualize a produtividade completa da sua equipe com gráficos intuitivos e análises em tempo real.', + }, + { + icon: Users, + title: 'Análise de Produtividade', + description: 'Acompanhe o desempenho individual de cada membro da equipe com rankings e métricas detalhadas.', + }, + { + icon: Clock, + title: 'Rastreamento de Tempo', + description: 'Registre automaticamente o tempo gasto em cada tarefa com nosso cronômetro integrado.', + }, + { + icon: Zap, + title: 'Gestão Visual', + description: 'Organize tarefas em um quadro Kanban intuitivo e acompanhe o progresso do projeto.', + }, + ].map((feature, idx) => ( + +
+
+ +
+

{feature.title}

+

{feature.description}

+ + ))} +
+
+ + {/* How It Works Section */} +
+ +

+ Como funciona +

+

+ Comece em 3 simples passos +

+
+ +
+ {[ + { + step: 1, + title: 'Crie sua organização', + description: 'Configure sua workspace em minutos e convide sua equipe.', + }, + { + step: 2, + title: 'Adicione tarefas e projetos', + description: 'Organize trabalho em projetos e categorize tarefas.', + }, + { + step: 3, + title: 'Acompanhe produtividade', + description: 'Visualize métricas em tempo real e tome melhores decisões.', + }, + ].map((item, idx) => ( + +
+ {item.step} +
+

{item.title}

+

{item.description}

+
+ ))} +
+
+ + {/* Showcase Section */} +
+ +

+ Dashboard em ação +

+
+ + +
+
+ {/* Left: Leaderboard */} +
+

+ Leaderboard de produtividade +

+ + {['João Silva', 'Maria Santos', 'Pedro Costa', 'Ana Lima'].map((name, idx) => ( +
+
+ {name[0]} +
+
+

{name}

+

32.5 horas esta semana

+
+ {idx === 0 && } +
+ ))} +
+ + {/* Right: Stats and Charts */} +
+
+ {[ + { label: 'Tarefas', value: '124' }, + { label: 'Concluídas', value: '89' }, + { label: 'Em andamento', value: '28' }, + { label: 'Tempo médio', value: '2h 30m' }, + ].map((stat, idx) => ( +
+

{stat.value}

+

{stat.label}

+
+ ))} +
+ +
+

+ Tempo por categoria +

+ {[ + { name: 'Desenvolvimento', percent: 45, color: 'bg-brand' }, + { name: 'Design', percent: 25, color: 'bg-accent' }, + { name: 'Reuniões', percent: 20, color: 'bg-warning' }, + { name: 'Outros', percent: 10, color: 'bg-success' }, + ].map((cat, idx) => ( +
+
+ {cat.name} + {cat.percent}% +
+
+
+
+
+ ))} +
+
+
+
+ +
+ + {/* Stats Section */} +
+ +

+ Usada por equipes em todo o Brasil +

+

+ Estatísticas da plataforma em tempo real +

+
+ +
+ {loading ? ( +
+

Carregando estatísticas...

+
+ ) : !stats ? ( +
+

Não foi possível carregar as estatísticas

+
+ ) : ( + <> + + + + + + )} +
+
+ + {/* Final CTA */} +
+ +
+ + +
+ +
+

+ Pronto para começar? +

+

+ Junte-se a centenas de equipes que já estão usando BeeTask para gerenciar suas tarefas + e aumentar a produtividade. +

+ +
+ + +
+
+
+
+ + {/* Footer */} +
+
+
+ +
+ +
+ BeeTask + + +
+ + +
+
+ +
+

© 2026 BeeTask. Todos os direitos reservados.

+
+
+
+
+ ); +}; + +interface StatsCardProps { + icon: React.ComponentType<{ className?: string }>; + label: string; + value: number; + delay: number; +} + +const StatsCard = ({ icon: Icon, label, value, delay }: StatsCardProps) => { + const ref = useRef(null); + const isInView = useInView(ref, { once: true }); + const [displayValue, setDisplayValue] = useState(0); + + useEffect(() => { + if (!isInView) return; + + const duration = 2; + const steps = 60; + const increment = value / steps; + let current = 0; + let step = 0; + + const interval = setInterval(() => { + current += increment; + step++; + if (step >= steps) { + setDisplayValue(value); + clearInterval(interval); + } else { + setDisplayValue(Math.floor(current)); + } + }, (duration * 1000) / steps); + + return () => clearInterval(interval); + }, [isInView, value]); + + return ( + +
+ +
+
+ {displayValue.toLocaleString('pt-BR')} + {label.includes('Horas') && 'h'} + {label.includes('Usuários') && '+'} +
+

{label}

+
+ ); +}; diff --git a/frontend/src/router.tsx b/frontend/src/router.tsx index 6f19efb..3c5f281 100644 --- a/frontend/src/router.tsx +++ b/frontend/src/router.tsx @@ -16,8 +16,13 @@ import { OrganizationsPage } from "./pages/OrganizationsPage"; import { DashboardPage } from "./pages/DashboardPage"; import { OrgDashboardPage } from "./pages/OrgDashboardPage"; import { RoleGate } from "./components/RoleGate"; +import { LandingPage } from "./pages/LandingPage"; export const router = createBrowserRouter([ + { + path: "/landing", + element: , + }, { path: "/login", element: , diff --git a/frontend/src/services/publicStatsService.ts b/frontend/src/services/publicStatsService.ts new file mode 100644 index 0000000..317c0ff --- /dev/null +++ b/frontend/src/services/publicStatsService.ts @@ -0,0 +1,17 @@ +import { apiFetch } from "../lib/api"; + +export interface PublicStats { + totalUsers: number; + totalOrganizations: number; + totalCompletedTasks: number; + totalTrackedHours: number; +} + +export const publicStatsService = { + getStats: async (): Promise => { + const response = await apiFetch("/public/stats", { + method: "GET", + }) as Response; + return response.json(); + }, +};