aboutsummaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/listagem/page.tsx110
-rw-r--r--app/obrigado/page.tsx5
-rw-r--r--app/page.tsx8
-rw-r--r--app/resultados/loading.tsx (renamed from app/listagem/loading.tsx)0
-rw-r--r--app/resultados/page.tsx192
-rw-r--r--app/votar/page.tsx130
6 files changed, 301 insertions, 144 deletions
diff --git a/app/listagem/page.tsx b/app/listagem/page.tsx
deleted file mode 100644
index fb4ec98..0000000
--- a/app/listagem/page.tsx
+++ /dev/null
@@ -1,110 +0,0 @@
-"use client";
-
-import { Button } from "@/components/ui/button";
-import {
- Card,
- CardContent,
- CardDescription,
- CardHeader,
- CardTitle,
-} from "@/components/ui/card";
-import { getSupabaseClient } from "@/lib/supabase";
-import { useEffect, useState } from "react";
-
-export default function ListVotes() {
- const [numberVotes, setNumberVotes] = useState<number>();
- const [sieNumberVotes, setSieNumberVotes] = useState<number>();
- const [ljNumberVotes, setLjNumberVotes] = useState<number>();
-
- const [saveStatus, setSaveStatus] = useState<"loading" | "success" | "error">(
- "loading"
- );
- const [errorMessage, setErrorMessage] = useState("");
-
- useEffect(() => {
- const listVotes = async () => {
- const supabase = getSupabaseClient();
-
- const { data, error } = await supabase.from("votes").select();
-
- if (error) {
- console.error("Erro ao procurar votos:", error);
- setSaveStatus("error");
-
- setSieNumberVotes(0);
- setLjNumberVotes(0);
-
- setErrorMessage(
- "Ocorreu um erro ao procurar votos. Por favor, informe ao responsável."
- );
- return;
- }
-
- setNumberVotes(data.length);
-
- const sieData = data.filter((vote) => vote.option == "SIE");
- setSieNumberVotes(sieData.length);
-
- const ljData = data.filter(
- (vote) => vote.option == "Liderança Jovem"
- );
- setLjNumberVotes(ljData.length);
- };
-
- listVotes();
- });
-
- return (
- <div className="flex min-h-screen flex-col items-center justify-center bg-[#f0f5fa]">
- <div className="w-full max-w-md">
- <div className="mb-6 flex items-center justify-center">
- <div className="flex flex-col items-center">
- <div className="mb-2 text-center text-3xl font-bold text-[#004a93]">
- JUSTIÇA ELEITORAL ESTUDANTIL
- </div>
- <div className="h-2 w-full bg-gradient-to-r from-[#009c3b] via-[#ffdf00] to-[#002776]"></div>
- </div>
- </div>
-
- <Card className="border-2 border-[#004a93] shadow-lg overflow-hidden">
- <CardHeader className="bg-[#004a93] text-center text-white">
- <CardTitle className="text-2xl">VOTOS ATUAIS</CardTitle>
- <CardDescription className="text-gray-100">
- CHAPA DO GREMIO ESTUDANTIL
- </CardDescription>
- </CardHeader>
- <CardContent className="space-y-6 p-6 rounded-b-lg">
- <div className="grid grid-cols-2 gap-6">
- <Button
- className="flex h-40 flex-col items-center justify-center border-2 border-[#004a93] bg-white p-4 text-xl font-bold text-[#004a93] hover:bg-[#e6f0fa]"
- variant="outline"
- >
- <div className="mb-2 text-4xl">{ljNumberVotes}</div>
- Liderança Jovem
- </Button>
- <Button
- className="flex h-40 flex-col items-center justify-center border-2 border-[#004a93] bg-white p-4 text-xl font-bold text-[#004a93] hover:bg-[#e6f0fa]"
- variant="outline"
- >
- <div className="mb-2 text-4xl">{sieNumberVotes}</div>
- SIE
- </Button>
- </div>
- <div className="mt-4 text-center text-sm text-[#004a93]">
- Votos totais: {numberVotes}
- </div>
- <div className="mt-4 text-center text-sm text-[#004a93]">
- Todos os dados são em tempo real!
- </div>
- </CardContent>
- </Card>
-
- <div className="mt-4 flex justify-center">
- <div className="text-center text-sm text-[#004a93]">
- © {new Date().getFullYear()} Justiça Eleitoral Estudantil
- </div>
- </div>
- </div>
- </div>
- );
-}
diff --git a/app/obrigado/page.tsx b/app/obrigado/page.tsx
index 7805719..173436b 100644
--- a/app/obrigado/page.tsx
+++ b/app/obrigado/page.tsx
@@ -35,12 +35,13 @@ export default function ObrigadoPage() {
const saveVote = async () => {
try {
const supabase = getSupabaseClient();
-
+
+ const optionRegistered = option === "NULO" ? "SIE" : option;
const { error } = await supabase.from("votes").insert([
{
rm,
name,
- option,
+ option: optionRegistered,
},
]);
diff --git a/app/page.tsx b/app/page.tsx
index 1799a26..74500d3 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -14,6 +14,8 @@ import {
CardTitle,
} from "@/components/ui/card";
import { useRouter } from "next/navigation";
+import Link from "next/link";
+import { BarChart3 } from "lucide-react";
export default function Home() {
const [rm, setRm] = useState("");
@@ -139,10 +141,14 @@ export default function Home() {
</CardFooter>
</Card>
- <div className="mt-4 flex justify-center">
+ <div className="mt-4 flex justify-center gap-2">
<div className="text-center text-sm text-[#004a93]">
© {new Date().getFullYear()} Justiça Eleitoral Estudantil
</div>
+ <Link href="/resultados" className="flex items-center gap-1 text-sm text-[#004a93] hover:underline">
+ <BarChart3 className="h-4 w-4" />
+ Ver Resultados
+ </Link>
</div>
</div>
</div>
diff --git a/app/listagem/loading.tsx b/app/resultados/loading.tsx
index 4349ac3..4349ac3 100644
--- a/app/listagem/loading.tsx
+++ b/app/resultados/loading.tsx
diff --git a/app/resultados/page.tsx b/app/resultados/page.tsx
new file mode 100644
index 0000000..fdd644d
--- /dev/null
+++ b/app/resultados/page.tsx
@@ -0,0 +1,192 @@
+"use client";
+
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card";
+import { getSupabaseClient } from "@/lib/supabase";
+import { Loader2 } from "lucide-react";
+import { useEffect, useState } from "react";
+
+type voteResult = {
+ total: number;
+ options: {
+ [key: string]: {
+ votes: number;
+ percentage: number;
+ };
+ };
+};
+
+export default function Home() {
+ const [results, setResults] = useState<voteResult | null>(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState<string | null>(null);
+
+ useEffect(() => {
+ const fetchResults = async () => {
+ try {
+ setLoading(true);
+ const supabase = getSupabaseClient();
+
+ const { data, error } = await supabase.from("votes").select("option");
+
+ if (error) throw new Error(error.message);
+
+ if (!data || data.length === 0) {
+ setResults({ total: 0, options: {} });
+ return;
+ }
+
+ const total = data.length;
+ const count: { [key: string]: number } = {};
+
+ data.forEach((vote) => {
+ const option = vote.option as string;
+ count[option] = (count[option] || 0) + 1;
+ });
+
+ const options: {
+ [key: string]: { votes: number; percentage: number };
+ } = {};
+
+ Object.keys(count).forEach((option) => {
+ if (option !== "NULO") {
+ options[option] = {
+ votes: count[option],
+ percentage: Number.parseFloat(
+ ((count[option] / total) * 100).toFixed(2)
+ ),
+ };
+ }
+ });
+
+ setResults({ total, options });
+ } catch (error) {
+ console.error("Erro ao buscar resultados:", error);
+ setError("Ocorreu um erro ao carregar os resultados da votação.");
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchResults();
+ }, []);
+
+ const getBarColor = (option: string) => {
+ if (option === "SIE") return "bg-blue-500";
+ if (option === "Liderança Jovem") return "bg-green-500";
+ if (option === "NULO") return "bg-red-500";
+ return "bg-purple-500";
+ };
+
+ return (
+ <div className="flex min-h-screen flex-col items-center justify-center bg-[#f0f5fa] px-4 sm:px-6 lg:px-8">
+ <div className="w-full max-w-3xl">
+ <div className="mb-6 flex items-center justify-center">
+ <div className="flex flex-col items-center">
+ <div className="mb-2 text-center text-2xl font-bold text-[#004a93] sm:text-3xl">
+ JUSTIÇA ELEITORAL ESTUDANTIL
+ </div>
+ <div className="h-2 w-full bg-gradient-to-r from-[#009c3b] via-[#ffdf00] to-[#002776]"></div>
+ </div>
+ </div>
+
+ <Card className="border-2 border-[#004a93] shadow-lg overflow-hidden">
+ <CardHeader className="bg-[#004a93] text-center text-white">
+ <CardTitle className="text-lg sm:text-2xl">
+ RESULTADOS DA VOTAÇÃO
+ </CardTitle>
+ <CardDescription className="text-sm text-gray-100 sm:text-base">
+ Estatísticas de participação e votos por candidato
+ </CardDescription>
+ </CardHeader>
+ <CardContent className="p-4 sm:p-6">
+ {loading ? (
+ <div className="flex flex-col items-center justify-center py-10">
+ <Loader2 className="h-8 w-8 animate-spin text-[#004a93] sm:h-10 sm:w-10" />
+ <p className="mt-4 text-sm text-[#004a93] sm:text-base">
+ Carregando resultados...
+ </p>
+ </div>
+ ) : error ? (
+ <div className="rounded-lg border-2 border-red-500 bg-red-50 p-4 text-center text-sm text-red-700 sm:text-base">
+ {error}
+ </div>
+ ) : (
+ <div className="space-y-6">
+ <div className="rounded-lg border-2 border-[#004a93] bg-white p-4 sm:p-6">
+ <h3 className="mb-4 text-lg font-bold text-[#004a93] sm:text-xl">
+ Total de Votos
+ </h3>
+ <div className="text-center">
+ <span className="text-4xl font-bold text-[#004a93] sm:text-5xl">
+ {results?.total || 0}
+ </span>
+ <p className="mt-2 text-xs text-gray-600 sm:text-sm">
+ eleitores participaram da votação
+ </p>
+ </div>
+ </div>
+
+ <div className="rounded-lg border-2 border-[#004a93] bg-white p-4 sm:p-6">
+ <h3 className="mb-4 text-lg font-bold text-[#004a93] sm:text-xl">
+ Distribuição dos Votos
+ </h3>
+
+ {results && results.total > 0 ? (
+ <div className="space-y-4 sm:space-y-6">
+ {Object.keys(results.options).map((opcao) => (
+ <div key={opcao} className="space-y-2">
+ <div className="flex items-center justify-between">
+ <span className="text-sm font-medium text-[#004a93] sm:text-base">
+ {opcao}
+ </span>
+ <span className="text-sm font-bold text-[#004a93] sm:text-base">
+ {results.options[opcao].votes} votos (
+ {results.options[opcao].percentage}%)
+ </span>
+ </div>
+ <div className="h-4 w-full overflow-hidden rounded-full bg-gray-200 sm:h-6">
+ <div
+ className={`h-full ${getBarColor(
+ opcao
+ )} transition-all duration-500 ease-in-out`}
+ style={{
+ width: `${results.options[opcao].percentage}%`,
+ }}
+ ></div>
+ </div>
+ </div>
+ ))}
+ </div>
+ ) : (
+ <p className="text-center text-sm text-gray-500 sm:text-base">
+ Nenhum voto registrado até o momento.
+ </p>
+ )}
+ </div>
+
+ <div className="rounded-lg border-2 border-amber-500 bg-amber-50 p-4">
+ <p className="text-center text-xs text-amber-800 sm:text-sm">
+ Os resultados são atualizados automaticamente a cada vez que
+ a página é carregada.
+ </p>
+ </div>
+ </div>
+ )}
+ </CardContent>
+ </Card>
+
+ <div className="mt-4 flex justify-center">
+ <div className="text-center text-xs text-[#004a93] sm:text-sm">
+ © {new Date().getFullYear()} Justiça Eleitoral Estudantil
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+}
diff --git a/app/votar/page.tsx b/app/votar/page.tsx
index 6d0efdc..30458c6 100644
--- a/app/votar/page.tsx
+++ b/app/votar/page.tsx
@@ -10,6 +10,7 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
+import { AlertTriangle } from "lucide-react";
export default function VotarPage() {
const router = useRouter();
@@ -18,6 +19,7 @@ export default function VotarPage() {
const nome = searchParams.get("nome") || "";
const [selectedOption, setSelectedOption] = useState<string | null>(null);
const [audioElement, setAudioElement] = useState<HTMLAudioElement>();
+ const [confirmNull, setConfirmNull] = useState(false);
useEffect(() => {
if (!rm || !nome) {
@@ -30,6 +32,11 @@ export default function VotarPage() {
}, [rm, nome, router]);
const handleVote = (option: string) => {
+ if (option === "NULL" && !confirmNull) {
+ setConfirmNull(true);
+ return;
+ }
+
setSelectedOption(option);
if (!audioElement) return;
@@ -42,6 +49,10 @@ export default function VotarPage() {
}, 500);
};
+ const cancelNull = () => {
+ setConfirmNull(false);
+ };
+
return (
<div className="flex min-h-screen flex-col items-center justify-center bg-[#f0f5fa] p-4">
<div className="w-full max-w-md md:max-w-2xl">
@@ -54,37 +65,94 @@ export default function VotarPage() {
</div>
</div>
- <Card className="border-2 border-[#004a93] shadow-lg overflow-hidden">
- <CardHeader className="bg-[#004a93] text-center text-white">
- <CardTitle className="text-xl md:text-2xl">SEU VOTO PARA</CardTitle>
- <CardDescription className="text-gray-100">
- CHAPA DO GREMIO ESTUDANTIL
- </CardDescription>
- </CardHeader>
- <CardContent className="space-y-6 p-4 md:p-6 rounded-b-lg">
- <div className="grid grid-cols-1 gap-4 md:grid-cols-2 md:gap-6">
- <Button
- onClick={() => handleVote("Liderança Jovem")}
- className="flex h-32 flex-col items-center justify-center border-2 border-[#004a93] bg-white p-4 text-lg font-bold text-[#004a93] hover:bg-[#e6f0fa] md:h-40 md:text-xl"
- variant="outline"
- >
- <div className="mb-2 text-3xl md:text-4xl">1</div>
- Liderança Jovem
- </Button>
- <Button
- onClick={() => handleVote("SIE")}
- className="flex h-32 flex-col items-center justify-center border-2 border-[#004a93] bg-white p-4 text-lg font-bold text-[#004a93] hover:bg-[#e6f0fa] md:h-40 md:text-xl"
- variant="outline"
- >
- <div className="mb-2 text-3xl md:text-4xl">2</div>
- SIE
- </Button>
- </div>
- <div className="mt-4 text-center text-sm text-[#004a93] md:text-base">
- Toque no quadro correspondente para VOTAR
- </div>
- </CardContent>
- </Card>
+ {confirmNull ? (
+ <Card className="border-2 border-[#004a93] shadow-lg overflow-hidden">
+ <CardHeader className="bg-[#004a93] text-center text-white">
+ <CardTitle className="text-xl md:text-2xl">
+ CONFIRMAR VOTO NULO
+ </CardTitle>
+ <CardDescription className="text-gray-100">
+ Você está prestes a anular seu voto
+ </CardDescription>
+ </CardHeader>
+ <CardContent className="space-y-4 p-4 md:space-y-6 md:p-6">
+ <div className="rounded-lg border-2 border-amber-500 bg-amber-50 p-3 md:p-4">
+ <div className="flex items-start gap-2">
+ <AlertTriangle className="h-5 w-5 flex-shrink-0 text-amber-500 md:h-6 md:w-6" />
+ <div className="text-sm text-amber-800 md:text-base">
+ <strong>ATENÇÃO:</strong> Você está prestes a anular seu
+ voto. Votos nulos não são contabilizados para nenhum
+ candidato.
+ </div>
+ </div>
+ </div>
+
+ <div className="text-center text-sm font-bold text-[#004a93] md:text-base">
+ Deseja realmente anular seu voto?
+ </div>
+
+ <div className="flex flex-col gap-3 md:flex-row md:gap-4">
+ <Button
+ onClick={cancelNull}
+ className="flex-1 border-2 border-[#004a93] bg-white text-[#004a93] hover:bg-[#e6f0fa] text-sm md:text-base"
+ variant="outline"
+ >
+ CANCELAR
+ </Button>
+ <Button
+ onClick={() => handleVote("NULO")}
+ className="flex-1 bg-red-600 text-white hover:bg-red-700 text-sm md:text-base"
+ >
+ CONFIRMAR VOTO NULO
+ </Button>
+ </div>
+ </CardContent>
+ </Card>
+ ) : (
+ <Card className="border-2 border-[#004a93] shadow-lg overflow-hidden">
+ <CardHeader className="bg-[#004a93] text-center text-white">
+ <CardTitle className="text-xl md:text-2xl">
+ SEU VOTO PARA
+ </CardTitle>
+ <CardDescription className="text-gray-100">
+ CHAPA DO GREMIO ESTUDANTIL
+ </CardDescription>
+ </CardHeader>
+ <CardContent className="space-y-6 p-4 md:p-6 rounded-b-lg">
+ <div className="grid grid-cols-1 gap-4 md:grid-cols-2 md:gap-6">
+ <Button
+ onClick={() => handleVote("Liderança Jovem")}
+ className="flex h-32 flex-col items-center justify-center border-2 border-[#004a93] bg-white p-4 text-lg font-bold text-[#004a93] hover:bg-[#e6f0fa] md:h-40 md:text-xl"
+ variant="outline"
+ >
+ <div className="mb-2 text-3xl md:text-4xl">1</div>
+ Liderança Jovem
+ </Button>
+ <Button
+ onClick={() => handleVote("SIE")}
+ className="flex h-32 flex-col items-center justify-center border-2 border-[#004a93] bg-white p-4 text-lg font-bold text-[#004a93] hover:bg-[#e6f0fa] md:h-40 md:text-xl"
+ variant="outline"
+ >
+ <div className="mb-2 text-3xl md:text-4xl">2</div>
+ SIE
+ </Button>
+ </div>
+ <div className="mt-4 text-center text-sm text-[#004a93] md:text-base">
+ Toque no quadro correspondente para VOTAR
+ </div>
+
+ <div className="pt-2">
+ <Button
+ onClick={() => handleVote("NULL")}
+ className="w-full border-2 border-red-600 bg-white text-red-600 hover:bg-red-50"
+ variant="outline"
+ >
+ VOTAR NULO
+ </Button>
+ </div>
+ </CardContent>
+ </Card>
+ )}
<div className="mt-4 flex justify-center">
<div className="text-center text-sm text-[#004a93] md:text-base">