Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Entrega sesión 10 - ERR #11

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
20,802 changes: 12,290 additions & 8,512 deletions frontend/package-lock.json

Large diffs are not rendered by default.

10 changes: 7 additions & 3 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2",
"@types/node": "^16.18.97",
"@types/react": "^18.3.1",
"@types/react-dom": "^18.3.0",
"axios": "^1.7.2",
"bootstrap": "^5.3.3",
"dotenv": "^16.4.5",
"react": "^18.3.1",
Expand Down Expand Up @@ -45,5 +43,11 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@testing-library/jest-dom": "^6.4.5",
"@testing-library/react": "^16.0.0",
"@testing-library/user-event": "^14.5.2",
"jest": "^29.7.0"
}
}
3 changes: 3 additions & 0 deletions frontend/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { BrowserRouter, Routes, Route } from 'react-router-dom';
import RecruiterDashboard from './components/RecruiterDashboard';
import AddCandidate from './components/AddCandidateForm';
import Positions from './components/Positions';
import PositionDetails from './components/PositionDetails';


const App = () => {
return (
Expand All @@ -12,6 +14,7 @@ const App = () => {
<Route path="/" element={<RecruiterDashboard />} />
<Route path="/add-candidate" element={<AddCandidate />} /> {/* Agrega esta línea */}
<Route path="/positions" element={<Positions />} />
<Route path="/position/:id" element={<PositionDetails />} />
</Routes>
</BrowserRouter>
);
Expand Down
35 changes: 13 additions & 22 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,17 @@
import React from 'react';
import logo from './logo.svg';
import './App.css';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import Positions from './components/Positions';
import PositionDetails from './components/PositionDetails';

function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
const App: React.FC = () => {
return (
<Router>
<Routes>
<Route path="/" element={<Positions />} />
<Route path="/position/:id" element={<PositionDetails />} />
</Routes>
</Router>
);
};

export default App;
146 changes: 146 additions & 0 deletions frontend/src/components/PositionDetails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import { Container, Card, Row, Col, Spinner } from 'react-bootstrap';
import { DragDropContext, Droppable, Draggable, DropResult } from 'react-beautiful-dnd';
import { getInterviewFlow, getCandidates } from '../services/positionService';
import { updateCandidateStage } from '../services/candidateService';

interface InterviewStep {
id: number;
interviewFlowId: number;
interviewTypeId: number;
name: string;
orderIndex: number;
}

interface InterviewFlow {
positionName: string;
interviewFlow: {
id: number;
description: string;
interviewSteps: InterviewStep[];
};
}

interface Candidate {
id: number;
fullName: string;
currentInterviewStep: string;
averageScore: number;
applicationId: number;
}

const PositionDetails: React.FC = () => {
const { id } = useParams<{ id: string }>();
const [interviewFlow, setInterviewFlow] = useState<InterviewFlow | null>(null);
const [candidates, setCandidates] = useState<Candidate[]>([]);
const [loading, setLoading] = useState(true);

useEffect(() => {
const fetchData = async () => {
try {
const [flowData, candidatesData] = await Promise.all([
getInterviewFlow(id),
getCandidates(id)
]);

setInterviewFlow(flowData);
setCandidates(candidatesData);
setLoading(false);
} catch (error) {
console.error('Error fetching data:', error);
setLoading(false);
}
};

fetchData();
}, [id]);

const onDragEnd = async (result: DropResult) => {
const { source, destination, draggableId } = result;

if (!destination || !interviewFlow || !interviewFlow.interviewFlow.interviewSteps[destination.index]) {
console.error('Invalid destination or interview flow step');
return;
}

const candidateId = parseInt(draggableId);
const candidate = candidates.find(c => c.id === candidateId);
if (!candidate) {
console.error('Candidate not found');
return;
}

const newStepName = interviewFlow.interviewFlow.interviewSteps[destination.index].name;

try {
await updateCandidateStage(candidateId, candidate.applicationId, newStepName);
setCandidates(prevCandidates =>
prevCandidates.map(c =>
c.id === candidateId ? { ...c, currentInterviewStep: newStepName } : c
)
);
} catch (error) {
console.error('Error updating candidate stage:', error);
// Optionally, show an error message to the user
}
};

if (loading) {
return (
<Container className="mt-5 text-center">
<Spinner animation="border" />
</Container>
);
}

if (!interviewFlow) {
return <div>Posición no encontrada</div>;
}

return (
<Container className="mt-5">
<h2 className="text-center mb-4">{interviewFlow.positionName} Position</h2>
<DragDropContext onDragEnd={onDragEnd}>
<Row>
{interviewFlow.interviewFlow.interviewSteps.map((step, index) => (
<Col key={index} md={3}>
<Droppable droppableId={index.toString()}>
{(provided) => (
<Card className="mb-4" ref={provided.innerRef} {...provided.droppableProps}>
<Card.Body>
<Card.Title>{step.name}</Card.Title>
{candidates
.filter(candidate => candidate.currentInterviewStep === step.name)
.map((candidate, idx) => (
<Draggable key={candidate.id} draggableId={candidate.id.toString()} index={idx}>
{(provided) => (
<Card className="mb-2" ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
<Card.Body>
<Card.Text>
{candidate.fullName}
<div>
{Array(candidate.averageScore).fill(0).map((_, i) => (
<span key={i} role="img" aria-label="star">🟢</span>
))}
</div>
</Card.Text>
</Card.Body>
</Card>
)}
</Draggable>
))}
{provided.placeholder}
</Card.Body>
</Card>
)}
</Droppable>
</Col>
))}
</Row>
</DragDropContext>
</Container>
);
};

export default PositionDetails;
5 changes: 4 additions & 1 deletion frontend/src/components/Positions.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import { Card, Container, Row, Col, Form, Button } from 'react-bootstrap';
import { Link } from 'react-router-dom';

type Position = {
title: string;
Expand Down Expand Up @@ -57,7 +58,9 @@ const Positions: React.FC = () => {
{position.status}
</span>
<div className="d-flex justify-content-between mt-3">
<Button variant="primary">Ver proceso</Button>
<Link to={`/position/${index}`}>
<Button variant="primary">Ver proceso</Button>
</Link>
<Button variant="secondary">Editar</Button>
</div>
</Card.Body>
Expand Down
19 changes: 17 additions & 2 deletions frontend/src/services/candidateService.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import axios from 'axios';

const API_BASE_URL = 'http://localhost:3010';

export const uploadCV = async (file) => {
const formData = new FormData();
formData.append('file', file);

try {
const response = await axios.post('http://localhost:3010/upload', formData, {
const response = await axios.post(`${API_BASE_URL}/upload`, formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
Expand All @@ -18,9 +20,22 @@ export const uploadCV = async (file) => {

export const sendCandidateData = async (candidateData) => {
try {
const response = await axios.post('http://localhost:3010/candidates', candidateData);
const response = await axios.post(`${API_BASE_URL}/candidates`, candidateData);
return response.data;
} catch (error) {
throw new Error('Error al enviar datos del candidato:', error.response.data);
}
};

export const updateCandidateStage = async (candidateId, applicationId, currentInterviewStep) => {
try {
const response = await axios.put(`${API_BASE_URL}/candidate/${candidateId}`, {
applicationId,
currentInterviewStep
});
return response.data;
} catch (error) {
console.error('Error updating candidate stage:', error);
throw error;
}
};
23 changes: 23 additions & 0 deletions frontend/src/services/positionService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import axios from 'axios';

const API_BASE_URL = 'http://localhost:3010';

export const getInterviewFlow = async (positionId) => {
try {
const response = await axios.get(`${API_BASE_URL}/position/${positionId}/interviewFlow`);
return response.data.interviewFlow;
} catch (error) {
console.error('Error fetching interview flow:', error);
throw error;
}
};

export const getCandidates = async (positionId) => {
try {
const response = await axios.get(`${API_BASE_URL}/position/${positionId}/candidates`);
return response.data;
} catch (error) {
console.error('Error fetching candidates:', error);
throw error;
}
};
Loading