Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 87 additions & 5 deletions src/components/ApplicationDetailsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,65 @@ 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, textarea, or contenteditable element
const target = event.target as HTMLElement;
if (
target instanceof HTMLInputElement ||
target instanceof HTMLTextAreaElement ||
target.contentEditable === 'true'
) {
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) => {
Expand All @@ -42,6 +89,34 @@ export const ApplicationDetailsModal = ({ application, open, onOpenChange, onVot
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-[95vw] max-h-[90vh] overflow-y-auto">
{/* Navigation Arrows */}
{(canNavigatePrevious || canNavigateNext) && (
<>
{canNavigatePrevious && (
<Button
variant="ghost"
size="icon"
className="absolute left-2 top-1/2 -translate-y-1/2 z-50 rounded-full bg-background/80 backdrop-blur-sm shadow-lg hover:bg-background"
onClick={onNavigatePrevious}
title="Anterior (←)"
>
<ChevronLeft className="w-6 h-6" />
</Button>
)}
{canNavigateNext && (
<Button
variant="ghost"
size="icon"
className="absolute right-2 top-1/2 -translate-y-1/2 z-50 rounded-full bg-background/80 backdrop-blur-sm shadow-lg hover:bg-background"
onClick={onNavigateNext}
title="Siguiente (→)"
>
<ChevronRight className="w-6 h-6" />
</Button>
)}
</>
)}

<DialogHeader>
<div className="flex items-start justify-between gap-4">
<div>
Expand All @@ -51,9 +126,16 @@ export const ApplicationDetailsModal = ({ application, open, onOpenChange, onVot
{application.rut}
</DialogDescription>
</div>
<Badge variant="secondary" className="text-sm flex-shrink-0">
ID: {application.id}
</Badge>
<div className="flex items-center gap-2 flex-shrink-0">
{currentIndex !== undefined && totalCount !== undefined && (
<Badge variant="outline" className="text-sm">
{currentIndex + 1} / {totalCount}
</Badge>
)}
<Badge variant="secondary" className="text-sm">
ID: {application.id}
</Badge>
</div>
</div>
</DialogHeader>

Expand Down
50 changes: 46 additions & 4 deletions src/pages/home/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const Home = () => {
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [selectedApplication, setSelectedApplication] = useState<Application | null>(null);
const [selectedApplicationIndex, setSelectedApplicationIndex] = useState<number>(-1);
const [isModalOpen, setIsModalOpen] = useState(false);
const [syncLoading, setSyncLoading] = useState(false);
const [syncMessage, setSyncMessage] = useState<string | null>(null);
Expand Down Expand Up @@ -67,25 +68,60 @@ export const Home = () => {
}, [user]);

const handleCardClick = (application: Application) => {
const index = applications.findIndex(app => app.id === application.id);
setSelectedApplication(application);
setSelectedApplicationIndex(index);
setIsModalOpen(true);
};

const handleModalClose = (open: boolean) => {
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) {
// 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);
setSelectedApplication(null);
setSelectedApplicationIndex(-1);
}
}
};

Expand Down Expand Up @@ -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}
/>
</div>
);
Expand Down