From 51a31aeade428cf3d0e2d4d97d5ff99c03d87fff Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 7 Dec 2025 14:19:42 +0000 Subject: [PATCH 1/3] Initial plan From 073e07888ea1bf7112d9c51e78595b17c4f345d2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 7 Dec 2025 14:23:56 +0000 Subject: [PATCH 2/3] Add keyboard and visual navigation to application modal Co-authored-by: Im-Fran <30329003+Im-Fran@users.noreply.github.com> --- src/components/ApplicationDetailsModal.tsx | 88 ++++++++++++++++++++-- src/pages/home/index.tsx | 50 +++++++++++- 2 files changed, 129 insertions(+), 9 deletions(-) diff --git a/src/components/ApplicationDetailsModal.tsx b/src/components/ApplicationDetailsModal.tsx index 15f3027..7a20455 100644 --- a/src/components/ApplicationDetailsModal.tsx +++ b/src/components/ApplicationDetailsModal.tsx @@ -11,18 +11,61 @@ import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { VotingPanel } from "@/components/VotingPanel"; import { ApplicationVoteCharts } from "@/components/ApplicationVoteCharts"; -import { ExternalLink, Mail, Calendar, Clock, BookOpen, Users, GraduationCap, User, FileText, BarChart3 } from "lucide-react"; +import { ExternalLink, Mail, Calendar, Clock, BookOpen, Users, GraduationCap, User, FileText, BarChart3, ChevronLeft, ChevronRight } from "lucide-react"; import { usePermissions } from "@/hooks/usePermissions"; +import { useEffect } from "react"; interface ApplicationDetailsModalProps { application: Application | null; open: boolean; onOpenChange: (open: boolean) => void; onVoteSubmitted?: () => void; + onNavigateNext?: () => void; + onNavigatePrevious?: () => void; + canNavigateNext?: boolean; + canNavigatePrevious?: boolean; + currentIndex?: number; + totalCount?: number; } -export const ApplicationDetailsModal = ({ application, open, onOpenChange, onVoteSubmitted }: ApplicationDetailsModalProps) => { +export const ApplicationDetailsModal = ({ + application, + open, + onOpenChange, + onVoteSubmitted, + onNavigateNext, + onNavigatePrevious, + canNavigateNext = false, + canNavigatePrevious = false, + currentIndex, + totalCount +}: ApplicationDetailsModalProps) => { const { canVote, canViewVoteDetails } = usePermissions(); + + // Handle keyboard navigation + useEffect(() => { + if (!open) return; + + const handleKeyDown = (event: KeyboardEvent) => { + // Prevent navigation if user is typing in an input or textarea + const target = event.target as HTMLElement; + if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') { + return; + } + + if (event.key === 'ArrowLeft' && canNavigatePrevious && onNavigatePrevious) { + event.preventDefault(); + onNavigatePrevious(); + } else if (event.key === 'ArrowRight' && canNavigateNext && onNavigateNext) { + event.preventDefault(); + onNavigateNext(); + } + }; + + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, [open, canNavigateNext, canNavigatePrevious, onNavigateNext, onNavigatePrevious]); + if (!application) return null; const formatDate = (dateString: string) => { @@ -42,6 +85,34 @@ export const ApplicationDetailsModal = ({ application, open, onOpenChange, onVot return ( + {/* Navigation Arrows */} + {(canNavigatePrevious || canNavigateNext) && ( + <> + {canNavigatePrevious && ( + + )} + {canNavigateNext && ( + + )} + + )} +
@@ -51,9 +122,16 @@ export const ApplicationDetailsModal = ({ application, open, onOpenChange, onVot {application.rut}
- - ID: {application.id} - +
+ {currentIndex !== undefined && totalCount !== undefined && ( + + {currentIndex + 1} / {totalCount} + + )} + + ID: {application.id} + +
diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx index 27bae8d..acfc4d3 100644 --- a/src/pages/home/index.tsx +++ b/src/pages/home/index.tsx @@ -18,6 +18,7 @@ export const Home = () => { const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [selectedApplication, setSelectedApplication] = useState(null); + const [selectedApplicationIndex, setSelectedApplicationIndex] = useState(-1); const [isModalOpen, setIsModalOpen] = useState(false); const [syncLoading, setSyncLoading] = useState(false); const [syncMessage, setSyncMessage] = useState(null); @@ -67,7 +68,9 @@ export const Home = () => { }, [user]); const handleCardClick = (application: Application) => { + const index = applications.findIndex(app => app.id === application.id); setSelectedApplication(application); + setSelectedApplicationIndex(index); setIsModalOpen(true); }; @@ -75,17 +78,50 @@ export const Home = () => { setIsModalOpen(open); if (!open) { setSelectedApplication(null); + setSelectedApplicationIndex(-1); + } + }; + + const handleNavigateNext = () => { + if (selectedApplicationIndex < applications.length - 1) { + const nextIndex = selectedApplicationIndex + 1; + setSelectedApplicationIndex(nextIndex); + setSelectedApplication(applications[nextIndex]); + } + }; + + const handleNavigatePrevious = () => { + if (selectedApplicationIndex > 0) { + const prevIndex = selectedApplicationIndex - 1; + setSelectedApplicationIndex(prevIndex); + setSelectedApplication(applications[prevIndex]); } }; const handleVoteSubmitted = () => { // Cuando se envía un voto, eliminar la aplicación de la lista if (selectedApplication) { - setApplications(prev => prev.filter(app => app.rut !== selectedApplication.rut)); + const newApplications = applications.filter(app => app.rut !== selectedApplication.rut); + setApplications(newApplications); setTotalApplications(prev => prev - 1); - // Cerrar el modal después de votar - setIsModalOpen(false); - setSelectedApplication(null); + + // Ajustar el índice después de eliminar + if (newApplications.length > 0) { + // Si hay aplicaciones después de la actual, mantener el mismo índice + if (selectedApplicationIndex < newApplications.length) { + setSelectedApplication(newApplications[selectedApplicationIndex]); + } else { + // Si no hay aplicaciones después, ir a la anterior + const newIndex = newApplications.length - 1; + setSelectedApplicationIndex(newIndex); + setSelectedApplication(newApplications[newIndex]); + } + } else { + // No hay más aplicaciones, cerrar el modal + setIsModalOpen(false); + setSelectedApplication(null); + setSelectedApplicationIndex(-1); + } } }; @@ -311,6 +347,12 @@ export const Home = () => { open={isModalOpen} onOpenChange={handleModalClose} onVoteSubmitted={handleVoteSubmitted} + onNavigateNext={handleNavigateNext} + onNavigatePrevious={handleNavigatePrevious} + canNavigateNext={selectedApplicationIndex < applications.length - 1} + canNavigatePrevious={selectedApplicationIndex > 0} + currentIndex={selectedApplicationIndex} + totalCount={applications.length} /> ); From a8b9836374a033a134931ed27709549f512d6621 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 7 Dec 2025 14:25:11 +0000 Subject: [PATCH 3/3] Improve keyboard navigation and vote index handling Co-authored-by: Im-Fran <30329003+Im-Fran@users.noreply.github.com> --- src/components/ApplicationDetailsModal.tsx | 8 ++++++-- src/pages/home/index.tsx | 16 ++++++++-------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/components/ApplicationDetailsModal.tsx b/src/components/ApplicationDetailsModal.tsx index 7a20455..65f63e7 100644 --- a/src/components/ApplicationDetailsModal.tsx +++ b/src/components/ApplicationDetailsModal.tsx @@ -47,9 +47,13 @@ export const ApplicationDetailsModal = ({ if (!open) return; const handleKeyDown = (event: KeyboardEvent) => { - // Prevent navigation if user is typing in an input or textarea + // Prevent navigation if user is typing in an input, textarea, or contenteditable element const target = event.target as HTMLElement; - if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') { + if ( + target instanceof HTMLInputElement || + target instanceof HTMLTextAreaElement || + target.contentEditable === 'true' + ) { return; } diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx index acfc4d3..ce0ca11 100644 --- a/src/pages/home/index.tsx +++ b/src/pages/home/index.tsx @@ -107,15 +107,15 @@ export const Home = () => { // Ajustar el índice después de eliminar if (newApplications.length > 0) { - // Si hay aplicaciones después de la actual, mantener el mismo índice - if (selectedApplicationIndex < newApplications.length) { - setSelectedApplication(newApplications[selectedApplicationIndex]); - } else { - // Si no hay aplicaciones después, ir a la anterior - const newIndex = newApplications.length - 1; - setSelectedApplicationIndex(newIndex); - setSelectedApplication(newApplications[newIndex]); + // El índice actual ahora apunta a la siguiente aplicación (porque removimos la actual) + // pero necesitamos validar que no exceda el límite + let newIndex = selectedApplicationIndex; + if (newIndex >= newApplications.length) { + // Si el índice está fuera de límites, ir a la última aplicación + newIndex = newApplications.length - 1; } + setSelectedApplicationIndex(newIndex); + setSelectedApplication(newApplications[newIndex]); } else { // No hay más aplicaciones, cerrar el modal setIsModalOpen(false);