Skip to content

Commit a68d659

Browse files
committed
Ad card user modal in dash board
1 parent 565e8ec commit a68d659

File tree

7 files changed

+201
-15
lines changed

7 files changed

+201
-15
lines changed

src/features/recruiters/components/DashBoardCard.jsx

Lines changed: 0 additions & 5 deletions
This file was deleted.
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import { useNavigate } from "react-router";
2+
import { capitalize } from "../../../utils/utils";
3+
import { CandidateSkills } from "./candidateSkills";
4+
import { PiChat, PiEnvelope, PiFileArrowDown, PiMapPinArea, PiReadCvLogo } from "react-icons/pi";
5+
import { NameUsers } from "../../../components/NameUsers";
6+
import { AvatarImage } from "../../../components/AvatarImage";
7+
import { GoChevronDown } from "react-icons/go";
8+
9+
10+
11+
export const DashBoardUserModal = ({ skillsOffer, colors, fadedColors, textColors, changeStatusCandidate, offerId, openChat, handleDownloadCV, handleDownloadCoverLetter, candidate, handleCloseApplicantsModal }) => {
12+
const navigate = useNavigate();
13+
14+
15+
const name = capitalize(candidate?.user?.name || '');
16+
const surname = capitalize(candidate?.user?.surname || '');
17+
const completeName = `${name} ${surname}`.trim() || 'Unknown Profile';
18+
const isResume = candidate?.user?.role?.developer?.resume;
19+
const isCoverLetter = (candidate?.coverLetter?.length ?? 0) >= 5;
20+
21+
22+
return (
23+
<div className=' modal modal-open fixed inset-0 flex justify-center items-center z-50'>
24+
<div className=' modal-box w-sm bg-neutral-80 border border-neutral-70 rounded-lg p-6 relative flex flex-col '>
25+
<button className="self-end cursor-pointer" onClick={handleCloseApplicantsModal} >X</button>
26+
<div className='flex flex-col items-center gap-4 w-full'>
27+
<div key={candidate._id} className=" ">
28+
<div
29+
30+
className='flex flex-col gap-6'
31+
>
32+
{/* Izquierda: avatar + info */}
33+
<div className='flex items-center gap-2 '>
34+
<div className='flex flex-col items-center gap-2 '>
35+
<div className='flex gap-4 self-start cursor-pointer' onClick={() => navigate(`/profile/${candidate?.user?._id}`)}>
36+
<div>
37+
<AvatarImage user={candidate?.user} width={20} />
38+
</div>
39+
<div>
40+
<NameUsers
41+
user={candidate?.user}
42+
align='items-start'
43+
classProps={"line-clamp-1 text-lg"}
44+
>
45+
{candidate?.user?.role?.developer?.location && (
46+
<>
47+
<p className="text-sm">
48+
{candidate?.user?.role?.developer?.professionalPosition}
49+
</p>
50+
<p className='flex items-center gap-1 text-sm text-neutral-20'>
51+
<PiMapPinArea className='size-4' />
52+
{candidate?.user?.role?.developer?.location}
53+
</p>
54+
55+
</>
56+
)}
57+
</NameUsers>
58+
</div>
59+
</div>
60+
61+
</div>
62+
{/* Tecnologías */}
63+
</div>
64+
<div className=" items-center mt-2 text-sm">
65+
<p className="text-md mb-1">Skills:</p>
66+
<CandidateSkills skills={candidate?.user?.role?.developer?.skills} skillsOffer={skillsOffer}/>
67+
</div>
68+
69+
<div className='flex self-center items-center w-full gap-4'>
70+
<div className='relative flex w-full flex-col gap-4'>
71+
<select
72+
value={candidate.status}
73+
onChange={(e) => changeStatusCandidate(e.target.value, candidate._id)}
74+
className={`px-3 py-1 pr-7 rounded-md text-md capitalize appearance-none
75+
${fadedColors[candidate.status] || "bg-black/20"}
76+
${textColors[candidate.status] || "text-white"}
77+
`}
78+
>
79+
{Object.keys(colors).map((status) => (
80+
<option key={status} value={status}>
81+
{status.charAt(0).toUpperCase() + status.slice(1)}
82+
</option>
83+
))}
84+
</select>
85+
<div className='pointer-events-none absolute right-2 top-1/5 transform -translate-y-1/2'>
86+
<GoChevronDown />
87+
</div>
88+
89+
<div className="flex items-center gap-4 justify-center self-center">
90+
91+
<button
92+
onClick={() =>
93+
handleDownloadCV(
94+
candidate.user.role.developer.resume,
95+
`${completeName}_CV.pdf`
96+
)
97+
}
98+
className={`btn bg-neutral-90 hover:bg-neutral-60 ${!isResume && 'btn-disabled'} `}
99+
>
100+
<PiReadCvLogo size={20} />
101+
</button>
102+
<button
103+
onClick={() => handleDownloadCoverLetter(offerId, candidate._id)}
104+
className={`btn bg-neutral-90 hover:bg-neutral-60 ${!isCoverLetter && 'btn-disabled'} `}
105+
>
106+
<PiFileArrowDown size={20} />
107+
</button>
108+
109+
110+
<a className="btn bg-neutral-90 hover:bg-neutral-60" href={`mailto: ${candidate?.user?.email}`}><PiEnvelope size={20} /></a>
111+
<button
112+
onClick={() => openChat(candidate.user)}
113+
className='btn bg-linear-135 from-[#37C848] from-10% to-[#0077ff80] to-90% '
114+
>
115+
<PiChat size={20} />
116+
</button>
117+
</div>
118+
119+
120+
</div>
121+
</div>
122+
</div>
123+
124+
</div>
125+
126+
127+
</div>
128+
</div>
129+
</div>
130+
);
131+
};

src/features/recruiters/components/ListDashBoard.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { TabsDashboard } from './TabsDashboard';
44
import { ListDashBoardCard } from './ListDashBoardCard';
55

66

7-
export const ListDashBoard = ({ classProps, offerId, lists, setLists, getCandidates,skillsOffer, openChat, handleDownloadCV, handleDownloadCoverLetter }) => {
7+
export const ListDashBoard = ({ offerId, lists, getCandidates,skillsOffer, openChat, handleDownloadCV, handleDownloadCoverLetter }) => {
88
const [activeTab, setActiveTab] = useState(Object.keys(lists)[0]);
99

1010
const changeStatusCandidate = async (status, idCandidato) => {

src/features/recruiters/components/ListDashBoardCard.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { useNavigate } from "react-router"
99

1010

1111

12-
export const ListDashBoardCard = ({lists, activeTab, skillsOffer, colors, fadedColors, textColors, changeStatusCandidate, offerId, openChat, handleDownloadCV, handleDownloadCoverLetter }) => {
12+
export const ListDashBoardCard = ({lists, activeTab, skillsOffer, colors, fadedColors, textColors, changeStatusCandidate, offerId, openChat, handleDownloadCV, handleDownloadCoverLetter }) => {
1313

1414
const navigate = useNavigate();
1515

@@ -20,6 +20,7 @@ export const ListDashBoardCard = ({lists, activeTab, skillsOffer, colors, fadedC
2020
<div className='flex flex-col gap-4'>
2121
{lists[activeTab]?.length > 0 ? (
2222
lists[activeTab].map((candidato) => {
23+
2324
const name = capitalize(candidato?.user?.name || '');
2425
const surname = capitalize(candidato?.user?.surname || '');
2526
const completeName = `${name} ${surname}`.trim() || 'Unknown Profile';

src/features/recruiters/components/candidateSkills.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export const CandidateSkills = ({skills,skillsOffer}) => {
1212
sortedSkills.map((tech) => (
1313
<span
1414
key={tech}
15-
className={`px-2 py-0.5 rounded-full text-neutral-0 ${
15+
className={`px-2 mx-0.5 py-0.5 rounded-full text-neutral-0 ${
1616
ofertaSkills.includes(tech) ? 'bg-green-600' : 'bg-primary-70'
1717
}`}
1818
>

src/features/recruiters/pages/DashBoarPage.jsx

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,21 @@ import { RecDashBoar } from "./RecDashBoar";
33
import { Link, useParams } from "react-router";
44
import { PiDotsNine, PiDotsThreeVertical, PiListBullets } from "react-icons/pi";
55
import { ListDashBoard } from "../components/ListDashBoard";
6-
import { getCandidatesByOfferId, getCoverLetter, getOffersById } from "../../../services/offersServices";
6+
import { getCandidatesByOfferId, getCoverLetter, getOffersById, updateCandidateStatus } from "../../../services/offersServices";
77
import { useContext } from "react";
88
import { ChatContext } from "../../../layout/chat/context/ChatContext";
9+
import { DashBoardUserModal } from "../components/DashBoardUserModal";
910

1011

1112
export const DashBoarPage = () => {
1213
const { offerId } = useParams();
1314
const [viewList, setViewList] = useState(false);
14-
const [nameOffer, setNameOffer] = useState('');
1515
const [skillsOffer, setSkillsOffer] = useState([]);
1616
const [offer, setOffer] = useState(null);
1717
const [isLoading, setIsLoading] = useState(true);
1818
const [error, setError] = useState(null);
19+
const [isOpenApplicantsModal, setIsOpenApplicantsModal] = useState(false);
20+
const [candidate, setCandidate] = useState();
1921

2022

2123
const handleDownloadCoverLetter = async (offerId, userId) => {
@@ -60,10 +62,10 @@ const handleDownloadCoverLetter = async (offerId, userId) => {
6062
accepted: [],
6163
rejected: [],
6264
});
65+
const [activeTab, setActiveTab] = useState(Object.keys(lists)[0]);
6366

6467
const getCandidates = async()=>{
6568
const data = await getCandidatesByOfferId(offerId, localStorage.getItem('token'));
66-
setNameOffer(data.nameOffer);
6769
setSkillsOffer(data.skills);
6870
const candidates = data.applicants;
6971
console.log('candidatos',candidates);
@@ -75,6 +77,15 @@ const handleDownloadCoverLetter = async (offerId, userId) => {
7577
console.log(grouped);
7678
setLists(grouped);
7779
}
80+
useEffect(() => {
81+
getCandidates(); // Aquí llamas a getCandidates
82+
}, [offerId]);
83+
84+
const changeStatusCandidate = async (status, idCandidato) => {
85+
await updateCandidateStatus(offerId, idCandidato, status, localStorage.getItem('token'));
86+
setActiveTab(status);
87+
getCandidates(); // Actualiza la lista de candidatos
88+
}
7889

7990
const fetchOfferByid = async () => {
8091
try {
@@ -87,6 +98,36 @@ const handleDownloadCoverLetter = async (offerId, userId) => {
8798
setIsLoading(false);
8899
}
89100
};
101+
const colors = {
102+
pending: 'bg-blue-500',
103+
reviewed: 'bg-purple-500',
104+
interviewed: 'bg-yellow-500',
105+
accepted: 'bg-green-500',
106+
rejected: 'bg-red-500',
107+
};
108+
const fadedColors = {
109+
pending: 'bg-blue-500/20',
110+
reviewed: 'bg-purple-500/20',
111+
interviewed: 'bg-yellow-500/20',
112+
accepted: 'bg-green-500/20',
113+
rejected: 'bg-red-500/20',
114+
};
115+
const textColors = {
116+
pending: 'text-blue-500',
117+
reviewed: 'text-purple-500',
118+
interviewed: 'text-yellow-500',
119+
accepted: 'text-green-500',
120+
rejected: 'text-red-500',
121+
};
122+
123+
const handleOpenApplicantsModal = (candidate) => {
124+
setCandidate(candidate);
125+
setIsOpenApplicantsModal(true);
126+
};
127+
128+
const handleCloseApplicantsModal = () => {
129+
setIsOpenApplicantsModal(false);
130+
};
90131

91132
// 1. Carga candidatos
92133
useEffect(() => {
@@ -189,9 +230,26 @@ const handleDownloadCoverLetter = async (offerId, userId) => {
189230
lists={lists}
190231
setLists={setLists}
191232
getCandidates={getCandidates} /> : <RecDashBoar offerId={offerId}
233+
setIsOpenApplicantsModal={handleOpenApplicantsModal}
234+
192235
lists={lists}
193236
setLists={setLists} />}
194237
</div>
238+
{isOpenApplicantsModal && <DashBoardUserModal offerId={offerId}
239+
key={candidate._id}
240+
candidate={candidate}
241+
handleCloseApplicantsModal={handleCloseApplicantsModal}
242+
openChat={openChat}
243+
handleDownloadCoverLetter={handleDownloadCoverLetter}
244+
handleDownloadCV={handleDownloadCV}
245+
skillsOffer={skillsOffer}
246+
getCandidates={getCandidates}
247+
setIsOpenApplicantsModal={setIsOpenApplicantsModal}
248+
colors={colors}
249+
fadedColors={fadedColors}
250+
textColors={textColors}
251+
changeStatusCandidate={changeStatusCandidate}
252+
/>}
195253
</>
196254
)
197255
}

src/features/recruiters/pages/RecDashBoar.jsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import { AvatarImage } from '../../../components/AvatarImage';
44
import { NameUsers } from '../../../components/NameUsers';
55
import { getDaysSince } from '../../../utils/utils';
66
import { PiInfo } from 'react-icons/pi';
7-
export const RecDashBoar = ({ offerId, lists, setLists }) => {
7+
8+
export const RecDashBoar = ({ offerId, lists, setLists, setIsOpenApplicantsModal }) => {
89
//guarda temporalmente de dónde (qué columna) y qué candidato estamos arrastrando.
910
const [dragInfo, setDragInfo] = useState({ fromList: null, candidate: null });
1011
// Esto es para iniciar el arrastre del drag-and-drop.
@@ -80,9 +81,9 @@ export const RecDashBoar = ({ offerId, lists, setLists }) => {
8081

8182
<div className='flex justify-between'>
8283
<AvatarImage user={c.user} width={6}/>
83-
<div>
84-
<PiInfo className='text-secondary-30 text-3xl z-48 size-6' />
85-
</div>
84+
<button className='cursor-pointer' onClick={() => setIsOpenApplicantsModal(c)}>
85+
<PiInfo className='text-secondary-30 hover:text-secondary-50 text-3xl z-48 size-6' />
86+
</button>
8687

8788
</div>
8889
<div className="flex flex-col">

0 commit comments

Comments
 (0)