Neurolingua commited on
Commit
943a750
·
verified ·
1 Parent(s): 2a7f874

Upload 17 files

Browse files
Dockerfile ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use an official Python runtime as a parent image
2
+ FROM python:3.9
3
+
4
+ # Set the working directory
5
+ WORKDIR /app
6
+
7
+ # Copy the requirements file
8
+ COPY requirements.txt .
9
+
10
+ # Install dependencies
11
+ RUN pip install --no-cache-dir -r requirements.txt
12
+
13
+ # Copy the rest of the application code
14
+ COPY . .
15
+
16
+ # Expose the Flask default port
17
+ EXPOSE 5000
18
+
19
+ # Set environment variables
20
+ ENV FLASK_APP=app.py
21
+ ENV FLASK_RUN_HOST=0.0.0.0
22
+ ENV FLASK_ENV=production
23
+
24
+ # Command to run the application
25
+ CMD ["flask", "run"]
__pycache__/email_agent.cpython-312.pyc ADDED
Binary file (8.23 kB). View file
 
__pycache__/email_service.cpython-312.pyc ADDED
Binary file (2.31 kB). View file
 
__pycache__/other_functions.cpython-312.pyc ADDED
Binary file (10.2 kB). View file
 
__pycache__/shortlist_mail.cpython-312.pyc ADDED
Binary file (8.04 kB). View file
 
ai_interview_agent.py ADDED
@@ -0,0 +1,296 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import requests
3
+ from pymongo import MongoClient
4
+ import speech_recognition as sr
5
+ from datetime import datetime
6
+ import certifi
7
+ import cv2
8
+ import threading
9
+ import time
10
+
11
+
12
+ class AIInterviewer:
13
+ def __init__(self):
14
+ # IBM Cloud API setup
15
+ self.url = "https://us-south.ml.cloud.ibm.com/ml/v1/text/generation?version=2023-05-29"
16
+ self.auth_url = "https://iam.cloud.ibm.com/identity/token"
17
+ self.apikey = "9FV7l0Jxqe7ceL09MeH_g9bYioIQuABXsr1j1VHbKOpr"
18
+
19
+ # MongoDB setup
20
+ MONGODB_URI = "mongodb+srv://roshauninfant:[email protected]/?retryWrites=true&w=majority&appName=slackbot"
21
+ self.client = MongoClient(MONGODB_URI, tlsCAFile=certifi.where())
22
+ self.db = self.client['Resume']
23
+
24
+ # Question categories
25
+ self.question_categories = [
26
+ "technical_expertise",
27
+ "problem_solving",
28
+ "project_experience",
29
+ "behavioral",
30
+ "role_specific"
31
+ ]
32
+
33
+ def get_response(self, question_type, candidate_info, focus_areas, depth_level, special_instructions, category):
34
+ token = self.get_auth_token()
35
+ headers = {
36
+ "Accept": "application/json",
37
+ "Content-Type": "application/json",
38
+ "Authorization": f"Bearer {token}"
39
+ }
40
+
41
+ prompt = f"""<|start_of_role|>system<|end_of_role|>
42
+ You are an experienced HR interviewer conducting a professional interview. You specialize in asking insightful questions that reveal a candidate's true capabilities and potential.
43
+
44
+ Candidate Profile:
45
+ - Experience Level: {candidate_info.get('experience')}
46
+ - Key Skills: {', '.join(candidate_info.get('matching_skills', []))}
47
+ - Focus Areas: {', '.join(focus_areas)}
48
+ - Seniority Level: {depth_level}
49
+
50
+ Category: {category}
51
+ Special Instructions: {special_instructions}
52
+
53
+ Guidelines for question generation:
54
+ 1. For Technical Expertise: Focus on practical application of skills
55
+ 2. For Problem Solving: Present real-world scenarios
56
+ 3. For Project Experience: Ask about specific achievements and challenges
57
+ 4. For Behavioral: Focus on past experiences and decision-making
58
+ 5. For Role Specific: Align with the target position requirements
59
+
60
+ Generate a single, well-structured interview question that:
61
+ - Is specific to the {category} category
62
+ - Matches the candidate's experience level
63
+ - Allows the candidate to showcase their expertise
64
+ - Encourages detailed responses
65
+ - Avoids yes/no answers
66
+ - Is different from standard interview questions
67
+
68
+ Question format: Start with a brief context (if needed) followed by the main question.
69
+ <|end_of_text|>
70
+ <|start_of_role|>assistant<|end_of_role|>"""
71
+
72
+ body = {
73
+ "input": prompt,
74
+ "parameters": {
75
+ "decoding_method": "sample",
76
+ "top_k": 50,
77
+ "temperature": 0.7,
78
+ "max_new_tokens": 200,
79
+ "repetition_penalty": 1.2
80
+ },
81
+ "model_id": "ibm/granite-3-8b-instruct",
82
+ "project_id": "4aa39c25-19d7-48c1-9cf6-e31b5c223a1f"
83
+ }
84
+
85
+ response = requests.post(self.url, headers=headers, json=body)
86
+ return response.json().get("results", [{}])[0].get("generated_text", "")
87
+
88
+ def get_auth_token(self):
89
+ auth_data = {
90
+ "grant_type": "urn:ibm:params:oauth:grant-type:apikey",
91
+ "apikey": self.apikey
92
+ }
93
+ auth_headers = {"Content-Type": "application/x-www-form-urlencoded"}
94
+ auth_response = requests.post(self.auth_url, data=auth_data, headers=auth_headers)
95
+ return auth_response.json().get("access_token")
96
+
97
+
98
+ def get_audio_input():
99
+ r = sr.Recognizer()
100
+ with sr.Microphone() as source:
101
+ st.write("🎤 Listening... Please speak your answer.")
102
+ try:
103
+ # Adjust for ambient noise
104
+ r.adjust_for_ambient_noise(source, duration=0.5)
105
+ audio = r.listen(source, timeout=30, phrase_time_limit=120)
106
+ text = r.recognize_google(audio)
107
+ return text, None
108
+ except sr.WaitTimeoutError:
109
+ return None, "No speech detected. Please try again."
110
+ except sr.UnknownValueError:
111
+ return None, "Could not understand audio. Please try again."
112
+ except sr.RequestError:
113
+ return None, "Could not process speech. Please try text input instead."
114
+
115
+
116
+ def video_feed():
117
+ cap = cv2.VideoCapture(0)
118
+ frame_placeholder = st.empty()
119
+
120
+ while cap.isOpened():
121
+ ret, frame = cap.read()
122
+ if not ret:
123
+ st.error("Failed to capture video")
124
+ break
125
+ frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
126
+ frame_placeholder.image(frame, channels="RGB")
127
+ time.sleep(0.03) # Reduce CPU usage
128
+
129
+ cap.release()
130
+
131
+
132
+ def main():
133
+ st.set_page_config(page_title="Professional AI Interview", page_icon="👔")
134
+
135
+ # Styling
136
+ st.markdown("""
137
+ <style>
138
+ .main {
139
+ padding: 2rem;
140
+ }
141
+ .stButton button {
142
+ width: 100%;
143
+ border-radius: 5px;
144
+ height: 3em;
145
+ }
146
+ </style>
147
+ """, unsafe_allow_html=True)
148
+
149
+ # Initialize interviewer
150
+ interviewer = AIInterviewer()
151
+
152
+ # Get interview ID and details
153
+ query_params = st.query_params
154
+ interview_id = query_params.get("interview_id", None)
155
+
156
+ if not interview_id:
157
+ st.error("⚠️ Invalid interview link")
158
+ return
159
+
160
+ # Get interview and candidate details
161
+ interview = interviewer.db.interviews.find_one({"interview_id": interview_id})
162
+ if not interview:
163
+ st.error("⚠️ Interview not found")
164
+ return
165
+
166
+ candidate = interviewer.db.ai_processed_candidates.find_one({"_id": interview["candidate_id"]})
167
+ if not candidate:
168
+ st.error("⚠️ Candidate information not found")
169
+ return
170
+
171
+ # Professional welcome message
172
+ st.title("🤝 Professional AI Interview")
173
+ st.write(
174
+ f"Welcome, {candidate['candidate_info']['name']}! We're excited to learn more about your experience and expertise.")
175
+
176
+ # Video feed in sidebar
177
+ st.sidebar.title("📹 Video Feed")
178
+ st.sidebar.info("Please ensure your camera is on and you're well-positioned in the frame.")
179
+ if 'video_thread' not in st.session_state:
180
+ st.session_state.video_thread = threading.Thread(target=video_feed, daemon=True)
181
+ st.session_state.video_thread.start()
182
+
183
+ # Initialize interview state
184
+ if 'current_question' not in st.session_state:
185
+ st.session_state.current_question = 0
186
+ st.session_state.questions_total = 5
187
+ st.session_state.answers = []
188
+ st.session_state.verbal_questions = [1, 3] # 2nd and 4th questions
189
+ st.session_state.current_category = None
190
+ st.session_state.verbal_response = None
191
+
192
+ # Display progress
193
+ progress = st.progress(st.session_state.current_question / st.session_state.questions_total)
194
+ st.write(f"📝 Question {st.session_state.current_question + 1} of {st.session_state.questions_total}")
195
+
196
+ # Generate question based on category
197
+ if st.session_state.current_category is None and st.session_state.current_question < st.session_state.questions_total:
198
+ st.session_state.current_category = interviewer.question_categories[st.session_state.current_question]
199
+
200
+ current_q = interviewer.get_response(
201
+ interview.get('interview_focus', 'technical'),
202
+ {
203
+ 'experience': candidate['candidate_info'].get('experience'),
204
+ 'matching_skills': candidate['ai_evaluation'].get('matching_skills', []),
205
+ 'interview_focus': interview.get('interview_focus')
206
+ },
207
+ focus_areas=interview.get('focus_areas', []),
208
+ depth_level=interview.get('depth_level', 'entry'),
209
+ special_instructions=interview.get('special_instructions', ''),
210
+ category=st.session_state.current_category
211
+ )
212
+
213
+ # Display question with styling
214
+ st.markdown(f"### Question {st.session_state.current_question + 1}:")
215
+ st.write(current_q)
216
+
217
+ # Handle answer input
218
+ if st.session_state.current_question in st.session_state.verbal_questions:
219
+ col1, col2 = st.columns(2)
220
+
221
+ with col1:
222
+ if st.session_state.verbal_response is None:
223
+ if st.button("🎤 Answer Verbally"):
224
+ answer, error = get_audio_input()
225
+ if error:
226
+ st.error(error)
227
+ else:
228
+ st.session_state.verbal_response = answer
229
+ st.rerun()
230
+
231
+ if st.session_state.verbal_response is not None:
232
+ st.write("Your answer (transcribed):", st.session_state.verbal_response)
233
+ if st.button("✅ Submit Answer"):
234
+ st.session_state.answers.append({
235
+ "question": current_q,
236
+ "answer": st.session_state.verbal_response,
237
+ "category": st.session_state.current_category
238
+ })
239
+ st.session_state.current_question += 1
240
+ st.session_state.current_category = None
241
+ st.session_state.verbal_response = None
242
+ st.rerun()
243
+ if st.button("🔄 Record Again"):
244
+ st.session_state.verbal_response = None
245
+ st.rerun()
246
+
247
+ with col2:
248
+ if st.button("⌨️ Switch to Text Input"):
249
+ st.session_state.verbal_questions.remove(st.session_state.current_question)
250
+ st.session_state.verbal_response = None
251
+ st.rerun()
252
+ else:
253
+ answer = st.text_area("Your answer:", height=150)
254
+ if st.button("Submit Answer"):
255
+ if answer.strip():
256
+ st.session_state.answers.append({
257
+ "question": current_q,
258
+ "answer": answer,
259
+ "category": st.session_state.current_category
260
+ })
261
+ st.session_state.current_question += 1
262
+ st.session_state.current_category = None
263
+ st.rerun()
264
+ else:
265
+ st.warning("Please provide an answer before continuing.")
266
+
267
+ # Interview completion
268
+ if st.session_state.current_question >= st.session_state.questions_total:
269
+ st.success("Interview completed! Thank you for your time.")
270
+
271
+ # Save interview results to MongoDB
272
+ interview_results = {
273
+ "interview_id": interview_id,
274
+ "candidate_id": candidate["_id"],
275
+ "completion_time": datetime.now(),
276
+ "answers": st.session_state.answers
277
+ }
278
+ interviewer.db.interview_results.insert_one(interview_results)
279
+
280
+ st.balloons()
281
+
282
+ # Display completion message
283
+ st.markdown("""
284
+ ### Next Steps
285
+ - Our team will carefully review your responses
286
+ - You will receive feedback within 2-3 business days
287
+ - If you have any questions, please contact your hiring manager
288
+
289
+ Thank you for participating in our AI-assisted interview process!
290
+ """)
291
+
292
+ return
293
+
294
+
295
+ if __name__ == "__main__":
296
+ main()
app.py ADDED
@@ -0,0 +1,535 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, request, jsonify, send_file
2
+ from pymongo import MongoClient
3
+ from bson import ObjectId
4
+ from datetime import datetime
5
+ import gridfs
6
+ from io import BytesIO
7
+ import certifi
8
+ import uuid
9
+
10
+ from email_agent import send_emails
11
+ from datetime import datetime, timedelta
12
+ import uuid
13
+ from email_service import EmailService
14
+ from other_functions import download_resume,generatecoding
15
+
16
+ email_service = EmailService(
17
+ gmail_user="[email protected]",
18
+ gmail_app_password="kpvo hgsg jfjs qoky"
19
+ )
20
+ app = Flask(__name__)
21
+
22
+ # MongoDB connection with SSL settings
23
+ MONGODB_URI = "mongodb+srv://roshauninfant:[email protected]/?retryWrites=true&w=majority&appName=slackbot&tlsAllowInvalidCertificates=true"
24
+ client = MongoClient(MONGODB_URI, tlsCAFile=certifi.where())
25
+ db = client['Resume']
26
+ fs = gridfs.GridFS(db)
27
+
28
+
29
+ @app.route('/')
30
+ def index():
31
+ return render_template('index.html')
32
+
33
+
34
+ @app.route('/post-jobs')
35
+ def post_jobs():
36
+ return render_template('job_posting.html')
37
+ @app.route('/get_coding_question')
38
+ def get_coding_question():
39
+ return jsonify({"question": generatecoding()})
40
+
41
+ @app.route('/technical.html')
42
+ def technical_page():
43
+ return render_template('technical.html')
44
+ @app.route('/dashboard')
45
+ def dashboard():
46
+ try:
47
+ # Get unique job IDs from ai_processed_candidates
48
+ unique_jobs = db.ai_processed_candidates.distinct('job_id')
49
+ jobs_data = []
50
+
51
+ # Get candidate counts for each job
52
+ for job_id in unique_jobs:
53
+ total_candidates = db.ai_processed_candidates.count_documents({"job_id": job_id})
54
+ shortlisted = db.ai_processed_candidates.count_documents({
55
+ "job_id": job_id,
56
+ "status": "shortlisted"
57
+ })
58
+
59
+ jobs_data.append({
60
+ "job_id": job_id,
61
+ "title": "Software Engineer", # You can customize this
62
+ "department": "Engineering", # You can customize this
63
+ "total_candidates": total_candidates,
64
+ "shortlisted_count": shortlisted,
65
+ "status": "active"
66
+ })
67
+
68
+ print("Jobs data:", jobs_data)
69
+ return render_template('dashboard.html', job_postings=jobs_data)
70
+ except Exception as e:
71
+ print(f"Dashboard Error: {e}")
72
+ return f"Error loading dashboard: {str(e)}", 500
73
+
74
+
75
+ @app.route('/candidate/<candidate_id>')
76
+ def get_candidate_details(candidate_id):
77
+ try:
78
+ # Convert string ID to ObjectId
79
+ candidate_id = candidate_id
80
+
81
+ # Find the candidate with proper null checks
82
+ candidate = db.ai_processed_candidates.find_one({"_id": candidate_id})
83
+ if not candidate:
84
+ return jsonify({"error": "Candidate not found"}), 404
85
+
86
+ # Get all interviews for the candidate
87
+ interviews = list(db.interviews.find({"candidate_id": candidate_id}))
88
+
89
+ # Ensure all ObjectId fields are converted to strings
90
+ candidate['_id'] = str(candidate['_id'])
91
+ if 'resume_id' in candidate:
92
+ candidate['resume_id'] = str(candidate['resume_id'])
93
+
94
+ # Ensure candidate_info exists with default values
95
+ if 'candidate_info' not in candidate:
96
+ candidate['candidate_info'] = {
97
+ 'name': 'Not Available',
98
+ 'email': 'Not Available',
99
+ 'phone': None,
100
+ 'experience': None,
101
+ 'current_company': None
102
+ }
103
+
104
+ # Ensure ai_evaluation exists with default values
105
+ if 'ai_evaluation' not in candidate:
106
+ candidate['ai_evaluation'] = {
107
+ 'confidence_score': None,
108
+ 'matching_skills': [],
109
+ 'missing_skills': []
110
+ }
111
+
112
+ # Process interviews
113
+ processed_interviews = []
114
+ for interview in interviews:
115
+ interview['_id'] = str(interview['_id'])
116
+ interview['candidate_id'] = str(interview['candidate_id'])
117
+
118
+ # Format date if exists
119
+ if 'scheduled_date' in interview and interview['scheduled_date']:
120
+ interview['scheduled_date'] = interview['scheduled_date'].strftime('%Y-%m-%d %H:%M')
121
+ else:
122
+ interview['scheduled_date'] = 'Not scheduled'
123
+
124
+ processed_interviews.append(interview)
125
+
126
+ return jsonify({
127
+ "candidate": candidate,
128
+ "interviews": processed_interviews
129
+ })
130
+ except Exception as e:
131
+ print(f"Error in get_candidate_details: {str(e)}")
132
+ return jsonify({"error": str(e)}), 500
133
+
134
+
135
+ @app.route('/job/<job_id>')
136
+ def view_job(job_id):
137
+ try:
138
+ print(f"Viewing job: {job_id}")
139
+
140
+ # Get and segregate candidates
141
+ shortlisted_candidates = list(db.ai_processed_candidates.find({
142
+ "job_id": job_id,
143
+ "status": {"$in": ["shortlisted", "interview_scheduled", "ai_interview", "tech_interview"]}
144
+ }))
145
+
146
+ rejected_candidates = list(db.ai_processed_candidates.find({
147
+ "job_id": job_id,
148
+ "status": "rejected"
149
+ }))
150
+
151
+ print(f"Found {len(shortlisted_candidates)} shortlisted and {len(rejected_candidates)} rejected candidates")
152
+
153
+ # Process each candidate
154
+ for candidate in shortlisted_candidates + rejected_candidates:
155
+ # Convert ObjectId to string
156
+ candidate_id = candidate['_id']
157
+ candidate['_id'] = str(candidate['_id'])
158
+ candidate['resume_id'] = str(candidate['resume_id'])
159
+
160
+ # Check for interview completion if candidate is in ai_interview status
161
+ if candidate['status'] == 'ai_interview':
162
+ interview_result = db.interview_results.find_one({
163
+ "candidate_id": candidate_id
164
+ })
165
+ candidate['interview_completed'] = bool(interview_result)
166
+ else:
167
+ candidate['interview_completed'] = False
168
+
169
+ # Get interview details if any (keeping this for reference)
170
+ interview = db.interviews.find_one({
171
+ "candidate_id": candidate_id
172
+ })
173
+
174
+ if interview:
175
+ interview['_id'] = str(interview['_id'])
176
+ interview['candidate_id'] = str(interview['candidate_id'])
177
+
178
+ # Safely format scheduled_date
179
+ scheduled_date = interview.get('scheduled_date')
180
+ if isinstance(scheduled_date, datetime):
181
+ formatted_date = scheduled_date.strftime('%Y-%m-%d %H:%M')
182
+ else:
183
+ formatted_date = str(scheduled_date)
184
+
185
+ candidate['has_interview'] = True
186
+ candidate['interview_data'] = {
187
+ 'type': interview.get('type', 'Not specified'),
188
+ 'scheduled_date': formatted_date,
189
+ 'meeting_link': interview.get('meeting_link', '#'),
190
+ 'streamlit_url': interview.get('streamlit_url', '#')
191
+ }
192
+ else:
193
+ candidate['has_interview'] = False
194
+
195
+ job = {
196
+ "job_id": job_id,
197
+ "title": "Software Engineer",
198
+ "department": "Engineering",
199
+ "status": "active",
200
+ "total_candidates": len(shortlisted_candidates) + len(rejected_candidates),
201
+ "shortlisted_count": len(shortlisted_candidates)
202
+ }
203
+
204
+ print("Rendering template...")
205
+ return render_template('job_details.html',
206
+ job=job,
207
+ shortlisted_candidates=shortlisted_candidates,
208
+ rejected_candidates=rejected_candidates)
209
+
210
+ except Exception as e:
211
+ print(f"View Job Error: {str(e)}")
212
+ return f"Error viewing job: {str(e)}", 500
213
+
214
+
215
+
216
+ @app.route('/send_confirmation_emails/<job_id>', methods=['POST'])
217
+ def send_shortlist_emails(job_id):
218
+ try:
219
+ # Your Gmail credentials
220
+ gmail_user = "[email protected]"
221
+ gmail_app_password = "kpvo hgsg jfjs qoky"
222
+
223
+ result = send_emails(job_id, "shortlisted", gmail_user, gmail_app_password)
224
+
225
+ if result['success']:
226
+ message = f"Successfully sent {result['emails_sent']} emails. "
227
+ if result['skipped_candidates'] > 0:
228
+ message += f"Skipped {result['skipped_candidates']} candidates who already have interviews scheduled."
229
+ if result['failed_emails']:
230
+ message += f"\nFailed to send to: {', '.join(result['failed_emails'])}"
231
+
232
+ return jsonify({
233
+ "success": True,
234
+ "message": message,
235
+ "details": result
236
+ })
237
+ else:
238
+ return jsonify({
239
+ "success": False,
240
+ "message": result.get('error', 'Unknown error occurred'),
241
+ "details": result
242
+ }), 400
243
+
244
+ except Exception as e:
245
+ return jsonify({
246
+ "success": False,
247
+ "message": str(e)
248
+ }), 400
249
+
250
+
251
+ @app.route('/send_rejection_emails/<job_id>', methods=['POST'])
252
+ def send_reject_emails(job_id):
253
+ try:
254
+ # Your Gmail credentials
255
+ gmail_user = "[email protected]"
256
+ gmail_app_password = "kpvo hgsg jfjs qoky"
257
+
258
+ result = send_emails(job_id, "rejected", gmail_user, gmail_app_password)
259
+
260
+ if result['success']:
261
+ message = f"Successfully sent {result['emails_sent']} emails. "
262
+ if result['skipped_candidates'] > 0:
263
+ message += f"Skipped {result['skipped_candidates']} candidates who already have interviews scheduled."
264
+ if result['failed_emails']:
265
+ message += f"\nFailed to send to: {', '.join(result['failed_emails'])}"
266
+
267
+ return jsonify({
268
+ "success": True,
269
+ "message": message,
270
+ "details": result
271
+ })
272
+ else:
273
+ return jsonify({
274
+ "success": False,
275
+ "message": result.get('error', 'Unknown error occurred'),
276
+ "details": result
277
+ }), 400
278
+
279
+ except Exception as e:
280
+ return jsonify({
281
+ "success": False,
282
+ "message": str(e)
283
+ }), 400
284
+
285
+
286
+ @app.route('/schedule_hr_interview', methods=['POST'])
287
+ def schedule_hr_interview():
288
+ try:
289
+ print("Received HR interview scheduling request")
290
+ data = request.json
291
+ candidate_id = data['candidate_id']
292
+
293
+ print(f"Scheduling for candidate: {candidate_id}")
294
+
295
+ # Get candidate details using ObjectId
296
+ candidate = db.ai_processed_candidates.find_one({"_id": candidate_id})
297
+ if not candidate:
298
+ return jsonify({"success": False, "message": "Candidate not found"}), 404
299
+
300
+ # Generate meet link
301
+ meet_id = f"meet-{str(candidate['_id'])[-6:]}"
302
+ meet_link = f"https://meet.google.com/{meet_id}"
303
+
304
+ # Create interview document
305
+ interview = {
306
+ "candidate_id": candidate_id, # Store as ObjectId
307
+ "round_type": "HR Interview",
308
+ "scheduled_date": datetime.now(),
309
+ "meeting_link": meet_link,
310
+ "status": "scheduled",
311
+ "created_at": datetime.now()
312
+ }
313
+
314
+ print("Creating interview:", interview)
315
+
316
+ # Insert interview
317
+ result = db.interviews.insert_one(interview)
318
+
319
+ # Update candidate status
320
+ db.ai_processed_candidates.update_one(
321
+ {"_id": candidate_id},
322
+ {
323
+ "$set": {
324
+ "status": "interview_scheduled",
325
+ "last_updated": datetime.now()
326
+ }
327
+ }
328
+ )
329
+
330
+ print("Interview scheduled successfully")
331
+
332
+ return jsonify({
333
+ "success": True,
334
+ "message": "HR Interview scheduled successfully",
335
+ "meeting_link": meet_link,
336
+ "interview_id": str(result.inserted_id)
337
+ })
338
+
339
+ except Exception as e:
340
+ print(f"Error scheduling HR interview: {str(e)}")
341
+ return jsonify({"success": False, "message": str(e)}), 400
342
+
343
+
344
+ @app.route('/schedule_ai_interview', methods=['POST'])
345
+ def schedule_ai_interview():
346
+ try:
347
+ data = request.json
348
+ candidate_id = data['candidate_id']
349
+
350
+ # Get candidate details
351
+ candidate = db.ai_processed_candidates.find_one({"_id": candidate_id})
352
+ if not candidate:
353
+ return jsonify({"success": False, "message": "Candidate not found"}), 404
354
+
355
+ # Generate unique interview ID
356
+ interview_id = str(uuid.uuid4())
357
+
358
+ # Create interview configuration
359
+ interview_config = {
360
+ "candidate_id": candidate_id,
361
+ "interview_id": interview_id,
362
+ "type": "ai_interview",
363
+ "interview_focus": data.get('interview_focus', 'technical'),
364
+ "depth_level": data.get('depth_level', 'mid'),
365
+ "focus_areas": data.get('focus_areas', []),
366
+ "duration": int(data.get('duration', 30)),
367
+ "special_instructions": data.get('special_instructions', ''),
368
+ "status": "scheduled",
369
+ "streamlit_url": f"http://localhost:8501?interview_id={interview_id}", # For local testing
370
+ "created_at": datetime.now(),
371
+ "expires_at": datetime.now() + timedelta(days=2)
372
+ }
373
+
374
+ # Store in interviews collection
375
+ db.interviews.insert_one(interview_config)
376
+
377
+ # Update candidate status
378
+ db.ai_processed_candidates.update_one(
379
+ {"_id": candidate_id},
380
+ {
381
+ "$set": {
382
+ "status": "ai_interview",
383
+ "last_updated": datetime.now()
384
+ }
385
+ }
386
+ )
387
+
388
+ # Send AI interview email
389
+ candidate_email = candidate['candidate_info']['email']
390
+ interview_url = interview_config['streamlit_url']
391
+ duration = interview_config['duration']
392
+
393
+ email_sent = email_service.send_ai_interview_email(candidate_email, interview_url, duration)
394
+
395
+ if email_sent:
396
+ return jsonify({
397
+ "success": True,
398
+ "message": "AI Interview scheduled successfully, email sent",
399
+ "interview_url": interview_url
400
+ })
401
+ else:
402
+ return jsonify({"success": False, "message": "AI Interview scheduled, but email sending failed"})
403
+
404
+ except Exception as e:
405
+ print(f"Error scheduling AI interview: {str(e)}")
406
+ return jsonify({"success": False, "message": str(e)}), 400
407
+
408
+
409
+ @app.route('/schedule_interview', methods=['POST'])
410
+ def schedule_interview():
411
+ try:
412
+ data = request.json
413
+ candidate_id = data['candidate_id']
414
+ scheduled_date = datetime.strptime(data['scheduled_date'], '%Y-%m-%dT%H:%M')
415
+
416
+ # Create interview document
417
+ interview = {
418
+ "candidate_id": candidate_id,
419
+ "round_type": data['round_type'],
420
+ "scheduled_date": scheduled_date,
421
+ "meeting_link": data['meeting_link'],
422
+ "status": "scheduled",
423
+ "created_at": datetime.now()
424
+ }
425
+
426
+ # Insert interview
427
+ db.interviews.insert_one(interview)
428
+
429
+ return jsonify({"message": "Interview scheduled successfully"})
430
+ except Exception as e:
431
+ return jsonify({"error": str(e)}), 400
432
+
433
+
434
+ @app.route('/view_resume/<resume_id>')
435
+ def view_resume(resume_id):
436
+ try:
437
+ # First get the resume document
438
+ resume_doc = db.raw_resumes.find_one({"_id": ObjectId(resume_id)})
439
+ if not resume_doc:
440
+ return "Resume not found", 404
441
+
442
+ # Get the file from GridFS
443
+ file_data = fs.get(resume_doc['resume']['file_id'])
444
+
445
+ return send_file(
446
+ BytesIO(file_data.read()),
447
+ mimetype='application/pdf',
448
+ as_attachment=True,
449
+ download_name=resume_doc['resume']['filename']
450
+ )
451
+ except Exception as e:
452
+ print(f"Resume Error: {e}")
453
+ return str(e), 400
454
+
455
+
456
+ @app.route('/favicon.ico')
457
+ def favicon():
458
+ return '', 204
459
+ mail_db = client['Resume']
460
+
461
+
462
+ @app.route('/mail-inbox')
463
+ def email_dashboard():
464
+ try:
465
+ # Get all emails from MongoDB
466
+ print(mail_db,"HI")
467
+ job_emails = list(mail_db.email_inbox.find({"mail_type": "Job application"}))
468
+ general_emails = list(mail_db.email_inbox.find({"mail_type": {"$ne": "Job application"}}))
469
+
470
+ # Convert ObjectId to string for JSON serialization
471
+ for email in job_emails + general_emails:
472
+ email['_id'] = str(email['_id'])
473
+ if 'resume_pdf' in email:
474
+ email['resume_pdf'] = str(email['resume_pdf'])
475
+
476
+ return render_template('email_dashboard.html',
477
+ job_emails=job_emails,
478
+ general_emails=general_emails)
479
+ except Exception as e:
480
+ print(f"Error loading email dashboard: {str(e)}")
481
+ return str(e), 500
482
+
483
+
484
+ @app.route('/api/get-emails')
485
+ def get_emails():
486
+ try:
487
+ job_emails = list(mail_db.emails.find({"mail_type": "Job application"}))
488
+ general_emails = list(mail_db.emails.find({"mail_type": {"$ne": "Job application"}}))
489
+
490
+ # Convert ObjectId to string
491
+ for email in job_emails + general_emails:
492
+ email['_id'] = str(email['_id'])
493
+ if 'resume_pdf' in email:
494
+ email['resume_pdf'] = str(email['resume_pdf'])
495
+
496
+ return jsonify({
497
+ "job_emails": job_emails,
498
+ "general_emails": general_emails
499
+ })
500
+ except Exception as e:
501
+ return jsonify({"error": str(e)}), 500
502
+
503
+
504
+ @app.route('/test_db')
505
+ def test_db():
506
+ try:
507
+ candidates = list(db.ai_processed_candidates.find())
508
+ return jsonify({
509
+ "status": "success",
510
+ "candidate_count": len(candidates),
511
+ "first_candidate_id": str(candidates[0]['_id']) if candidates else None
512
+ })
513
+ except Exception as e:
514
+ return jsonify({"error": str(e)}), 500
515
+
516
+
517
+ @app.route('/download_resumes', methods=['GET'])
518
+ def trigger_resume_download():
519
+ try:
520
+ downloaded_files = download_resume() # This should return a list of candidate names
521
+ return jsonify({
522
+ "status": "success",
523
+ "message": "Resumes downloaded successfully",
524
+ "candidates": downloaded_files # Ensure frontend uses 'candidates'
525
+ })
526
+ except Exception as e:
527
+ return jsonify({
528
+ "status": "error",
529
+ "message": str(e)
530
+ }), 500
531
+
532
+
533
+
534
+ if __name__ == '__main__':
535
+ app.run(debug=True, port=5000)
email_agent.py ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ from typing import Dict, Any
3
+ from datetime import datetime
4
+ import smtplib
5
+ from email.mime.text import MIMEText
6
+ from email.mime.multipart import MIMEMultipart
7
+ from pymongo import MongoClient
8
+ import certifi
9
+
10
+ # MongoDB connection
11
+ MONGODB_URI = "mongodb+srv://roshauninfant:[email protected]/?retryWrites=true&w=majority&appName=slackbot"
12
+ client = MongoClient(MONGODB_URI, tlsCAFile=certifi.where())
13
+ db = client['Resume']
14
+
15
+
16
+ class EmailAgent:
17
+ def __init__(self, gmail_user, gmail_app_password):
18
+ self.url = "https://us-south.ml.cloud.ibm.com/ml/v1/text/generation?version=2023-05-29"
19
+ self.gmail_user = gmail_user
20
+ self.gmail_app_password = gmail_app_password
21
+ self.auth_token = self.get_ibm_auth_token()
22
+
23
+ def get_ibm_auth_token(self):
24
+ """Fetch a valid IBM IAM access token."""
25
+ auth_url = "https://iam.cloud.ibm.com/identity/token"
26
+ auth_data = {
27
+ "grant_type": "urn:ibm:params:oauth:grant-type:apikey",
28
+ "apikey": "9FV7l0Jxqe7ceL09MeH_g9bYioIQuABXsr1j1VHbKOpr"
29
+ }
30
+ auth_headers = {"Content-Type": "application/x-www-form-urlencoded"}
31
+
32
+ auth_response = requests.post(auth_url, data=auth_data, headers=auth_headers)
33
+ auth_token = auth_response.json().get("access_token")
34
+
35
+ if not auth_token:
36
+ raise Exception("Failed to retrieve access token: " + str(auth_response.text))
37
+
38
+ return auth_token
39
+
40
+ def generate_email(self, candidate_info: Dict[str, Any], task: str) -> str:
41
+ email_prompt = f"""<|start_of_role|>system<|end_of_role|>
42
+ You are an HR Email Assistant responsible for drafting professional emails.
43
+ Your task is to generate a {task} email for the candidate.
44
+
45
+ Generate a professional, well-structured email that clearly communicates the {task} message.
46
+ Consider the specific context and requirements of a {task} email in the hiring process.
47
+ Maintain formal business email standards while keeping an appropriate tone for the {task}.
48
+
49
+ Use these candidate details:
50
+ - Name: {candidate_info['candidate_info']['name']}
51
+ - Position: Software Engineer
52
+ - Company: ABC Company Inc
53
+ - Task: {task}
54
+
55
+ Requirements for the email:
56
+ 1. Clear and direct communication about the {task}
57
+ 2. Professional and appropriate tone for the specific {task}
58
+ 3. Any necessary next steps or actions required from the candidate
59
+ 4. Professional closing with name Roshaun - HR - ABC Company Inc
60
+
61
+ Return only the email content without any explanations or meta text.
62
+ <|end_of_text|>
63
+ <|start_of_role|>assistant<|end_of_role|>"""
64
+
65
+ body = {
66
+ "input": email_prompt,
67
+ "parameters": {
68
+ "decoding_method": "greedy",
69
+ "max_new_tokens": 900,
70
+ "min_new_tokens": 0,
71
+ "repetition_penalty": 1
72
+ },
73
+ "model_id": "ibm/granite-3-8b-instruct",
74
+ "project_id": "4aa39c25-19d7-48c1-9cf6-e31b5c223a1f"
75
+ }
76
+
77
+ headers = {
78
+ "Accept": "application/json",
79
+ "Content-Type": "application/json",
80
+ "Authorization": f"Bearer {self.auth_token}"
81
+ }
82
+
83
+ try:
84
+ response = requests.post(self.url, headers=headers, json=body)
85
+
86
+ if response.status_code != 200:
87
+ raise Exception(f"API Error: {response.text}")
88
+
89
+ email_content = response.json()['results'][0]['generated_text']
90
+ return email_content
91
+
92
+ except Exception as e:
93
+ print(f"Error generating email: {str(e)}")
94
+ return None
95
+
96
+ def send_email(self, to_email: str, subject: str, body: str) -> bool:
97
+ try:
98
+ msg = MIMEMultipart()
99
+ msg['From'] = self.gmail_user
100
+ msg['To'] = to_email
101
+ msg['Subject'] = subject
102
+
103
+ msg.attach(MIMEText(body, 'plain'))
104
+
105
+ server = smtplib.SMTP_SSL('smtp.gmail.com', 465)
106
+ server.login(self.gmail_user, self.gmail_app_password)
107
+ server.send_message(msg)
108
+ server.quit()
109
+
110
+ return True
111
+ except Exception as e:
112
+ print(f"Error sending email: {str(e)}")
113
+ return False
114
+
115
+
116
+ def send_emails(job_id: str, task: str, gmail_user: str, gmail_app_password: str, candidate_list: list = None) -> Dict:
117
+ try:
118
+ # Get candidates based on parameter or status
119
+ if candidate_list:
120
+ # If candidate list is provided, get those specific candidates
121
+ candidates = list(db.ai_processed_candidates.find({
122
+ "job_id": job_id,
123
+ "_id": {"$in": candidate_list}
124
+ }))
125
+ else:
126
+ # If no candidate list, get all candidates with matching status
127
+ candidates = list(db.ai_processed_candidates.find({
128
+ "job_id": job_id,
129
+ "status": task
130
+ }))
131
+
132
+ print(f"Found {len(candidates)} candidates for {task}")
133
+
134
+ if not candidates:
135
+ return {
136
+ "success": False,
137
+ "error": f"No candidates found for {task} emails"
138
+ }
139
+
140
+ email_agent = EmailAgent(gmail_user, gmail_app_password)
141
+ emails_sent = 0
142
+ failed_emails = []
143
+ skipped_candidates = 0
144
+
145
+ for candidate in candidates:
146
+ # Skip if candidate already received this type of email
147
+ if db.email_communications.find_one({
148
+ "candidate_id": candidate['_id'],
149
+ "email_type": task,
150
+ "status": "sent"
151
+ }):
152
+ skipped_candidates += 1
153
+ continue
154
+
155
+ print(f"Processing candidate: {candidate['candidate_info']['email']}")
156
+
157
+ email_content = email_agent.generate_email(candidate, task)
158
+ if not email_content:
159
+ print(f"Email content generation failed for {candidate['candidate_info']['email']}")
160
+ continue
161
+
162
+ success = email_agent.send_email(
163
+ to_email=candidate['candidate_info']['email'],
164
+ subject=f"ABC Company Inc | {task.replace('_', ' ').title()} Update",
165
+ body=email_content
166
+ )
167
+
168
+ print(f"Email sent: {'Success' if success else 'Failed'} for {candidate['candidate_info']['email']}")
169
+
170
+ # Store email record in MongoDB
171
+ email_record = {
172
+ "candidate_id": candidate['_id'],
173
+ "job_id": job_id,
174
+ "email_type": task,
175
+ "email_content": email_content,
176
+ "sent_date": datetime.now(),
177
+ "status": "sent" if success else "failed"
178
+ }
179
+ db.email_communications.insert_one(email_record)
180
+
181
+ if success:
182
+ emails_sent += 1
183
+ else:
184
+ failed_emails.append(candidate['candidate_info']['email'])
185
+
186
+ return {
187
+ "success": True,
188
+ "emails_sent": emails_sent,
189
+ "failed_emails": failed_emails,
190
+ "skipped_candidates": skipped_candidates,
191
+ "total_candidates": len(candidates)
192
+ }
193
+
194
+ except Exception as e:
195
+ print(f"Error sending emails: {str(e)}")
196
+ return {"success": False, "error": str(e)}
197
+
198
+
email_service.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import smtplib
2
+ from email.mime.text import MIMEText
3
+ from email.mime.multipart import MIMEMultipart
4
+ from datetime import datetime
5
+
6
+
7
+ class EmailService:
8
+ def __init__(self, gmail_user, gmail_app_password):
9
+ self.gmail_user = gmail_user
10
+ self.gmail_app_password = gmail_app_password
11
+
12
+ def send_ai_interview_email(self, candidate_email, interview_url, duration):
13
+ subject = "Your AI Interview Link"
14
+
15
+ body = f"""
16
+ Dear Candidate,
17
+
18
+ Thank you for your application. You have been selected for an AI-based interview assessment.
19
+
20
+ Please click the link below to start your interview:
21
+ {interview_url}
22
+
23
+ Important Information:
24
+ - The interview will take approximately {duration} minutes
25
+ - Please ensure a stable internet connection
26
+ - Have your camera and microphone ready if required
27
+ - The link will be valid for 48 hours
28
+
29
+ Best of luck!
30
+
31
+ Best regards,
32
+ HR Team
33
+ """
34
+
35
+ try:
36
+ msg = MIMEMultipart()
37
+ msg['From'] = self.gmail_user
38
+ msg['To'] = candidate_email
39
+ msg['Subject'] = subject
40
+
41
+ msg.attach(MIMEText(body, 'plain'))
42
+
43
+ server = smtplib.SMTP_SSL('smtp.gmail.com', 465)
44
+ server.login(self.gmail_user, self.gmail_app_password)
45
+ server.send_message(msg)
46
+ server.quit()
47
+
48
+ return True
49
+ except Exception as e:
50
+ print(f"Error sending email: {str(e)}")
51
+ return False
other_functions.py ADDED
@@ -0,0 +1,252 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import os
3
+ import gridfs
4
+ from pymongo import MongoClient
5
+
6
+ def download_resume():
7
+ # MongoDB connection
8
+ uri = "mongodb+srv://roshauninfant:[email protected]/?retryWrites=true&w=majority&appName=slackbot&tlsAllowInvalidCertificates=true"
9
+ client = MongoClient(uri)
10
+ db = client["Resume"]
11
+ fs = gridfs.GridFS(db)
12
+
13
+ collection = db["raw_resumes"]
14
+
15
+ # Create a directory for saving resumes
16
+ download_dir = "downloaded_resumes"
17
+ os.makedirs(download_dir, exist_ok=True)
18
+
19
+ downloaded_files = [] # Store filenames for UI
20
+
21
+ # Fetch and download files
22
+ for doc in collection.find():
23
+ file_id = doc["resume"]["file_id"]
24
+ filename = doc["resume"]["filename"]
25
+
26
+ # Retrieve the file from GridFS
27
+ file_data = fs.get(file_id)
28
+
29
+ # Save the file locally
30
+ file_path = os.path.join(download_dir, filename)
31
+ with open(file_path, "wb") as f:
32
+ f.write(file_data.read())
33
+
34
+ downloaded_files.append(filename)
35
+
36
+ print("All resumes downloaded successfully!")
37
+ return downloaded_files # Return filenames for UI
38
+
39
+ def screenresume():
40
+ print
41
+ # IBM Granite AI Authentication
42
+ def get_ibm_auth_token(api_key):
43
+ auth_url = "https://iam.cloud.ibm.com/identity/token"
44
+ auth_data = {
45
+ "grant_type": "urn:ibm:params:oauth:grant-type:apikey",
46
+ "apikey": api_key
47
+ }
48
+ auth_headers = {"Content-Type": "application/x-www-form-urlencoded"}
49
+
50
+ response = requests.post(auth_url, data=auth_data, headers=auth_headers)
51
+ return response.json().get("access_token")
52
+
53
+ # Extract text from Word documents
54
+ def extract_text_from_docx(file_path):
55
+ doc = Document(file_path)
56
+ return "\n".join([para.text for para in doc.paragraphs])
57
+
58
+ # Construct a powerful AI prompt
59
+ def generate_prompt(resume_text, job_id, _id):
60
+ return f"""
61
+ <|start_of_role|>system<|end_of_role|>You are a highly intelligent AI model specialized in resume screening for hiring purposes. You analyze candidate resumes against job descriptions, extracting key information and evaluating how well they match.
62
+
63
+ JOB Description: Software engineer/developer
64
+
65
+ Below is a candidate's resume. Extract structured information such as name, email, phone, experience, current company, and 5 key skills. Then, evaluate the candidate based on the job description.
66
+
67
+ **Confidence Score Guidelines:**
68
+ - **90 - 100 (Highly Recommended):** Strong alignment with job requirements, possesses most required skills and relevant experience, ideal for shortlisting.
69
+ - **75 - 89 (Recommended):** Good match with the job role, some missing or secondary skills but still a strong candidate.
70
+ - **50 - 74 (Considered):** Partial match, lacks some key skills but has relevant experience.
71
+ - **Below 50 (Not Recommended):** Does not meet core job requirements, significant skill gaps.
72
+
73
+ Resume:
74
+ {resume_text}
75
+
76
+ Example output format:
77
+ {{
78
+ "_id": "{_id}",
79
+ "job_id": "{job_id}",
80
+ "resume_id": "{_id}",
81
+ "candidate_info": {{
82
+ "name": "John Doe",
83
+ "email": "[email protected]",
84
+ "phone": "+1234567890",
85
+ "experience": "5 years",
86
+ "current_company": "Tech Corp"
87
+ }},
88
+ "ai_evaluation": {{
89
+ "confidence_score": 85,
90
+ "matching_skills": ["Python", "MongoDB", "AWS"], # Return only top 8 skills
91
+ "missing_skills": ["GraphQL"],
92
+ "shortlist_reasons": [
93
+ "Strong technical match",
94
+ "Relevant experience"
95
+ ] # Return only top 4 reasons
96
+ }},
97
+ "status": "shortlisted", # 'shortlisted' if confidence_score >= 65 else 'rejected',no other words
98
+ "created_at": "{datetime.now().isoformat()}"
99
+ }}
100
+ <|end_of_role|>"""
101
+
102
+
103
+ # Call IBM Granite AI
104
+ def analyze_resume_with_ai(auth_token, prompt):
105
+ url = "https://us-south.ml.cloud.ibm.com/ml/v1/text/generation?version=2023-05-29"
106
+ headers = {
107
+ "Accept": "application/json",
108
+ "Content-Type": "application/json",
109
+ "Authorization": f"Bearer {auth_token}"
110
+ }
111
+ body = {
112
+ "input": prompt,
113
+ "parameters": {"decoding_method": "greedy", "max_new_tokens": 900, "repetition_penalty": 1},
114
+ "model_id": "ibm/granite-3-8b-instruct",
115
+ "project_id": "4aa39c25-19d7-48c1-9cf6-e31b5c223a1f"
116
+ }
117
+
118
+ response = requests.post(url, headers=headers, json=body)
119
+ return response.json().get("results", [{}])[0].get("generated_text", "{}")
120
+
121
+ # MongoDB Connection (for later use if needed)
122
+ client = MongoClient("mongodb+srv://roshauninfant:[email protected]/?retryWrites=true&w=majority")
123
+ db = client["Resume"]
124
+ collection = db["raw_resumes"] # Assuming you have the collection with raw resumes
125
+
126
+ # Process resumes
127
+ resume_folder =download_resume()
128
+ resume_folder='downloaded_resumes'
129
+ api_key = "9FV7l0Jxqe7ceL09MeH_g9bYioIQuABXsr1j1VHbKOpr" # Replace with your actual API key
130
+ auth_token = get_ibm_auth_token(api_key)
131
+
132
+ if not auth_token:
133
+ raise Exception("Failed to retrieve IBM auth token")
134
+
135
+ job_id = "SDE001"
136
+ output = []
137
+ print('hi',os.listdir(resume_folder))
138
+ for filename in os.listdir(resume_folder):
139
+ if filename.endswith(".docx"):
140
+ file_path = os.path.join(resume_folder, filename)
141
+
142
+ # Extract text from resume
143
+ resume_text = extract_text_from_docx(file_path)
144
+
145
+ # Find existing ObjectId based on the resume filename or other criteria
146
+ existing_document = collection.find_one({"resume.filename": filename})
147
+
148
+ if not existing_document:
149
+ print(f"Error: No document found for {filename}")
150
+ continue
151
+
152
+ _id = str(existing_document['_id']) # Use the existing _id from MongoDB
153
+
154
+ # Generate AI prompt with existing _id
155
+ prompt = generate_prompt(resume_text, job_id, _id)
156
+
157
+ # Get AI analysis
158
+ ai_response = analyze_resume_with_ai(auth_token, prompt)
159
+ print(ai_response)
160
+ try:
161
+ ai_data = eval(ai_response) # Convert AI output to dict (use eval safely or json.loads if needed)
162
+ output.append(ai_data)
163
+
164
+ # Update the existing document in MongoDB with the AI evaluation results
165
+ # print(_id,filename)
166
+ # print(f"Processed and saved: {filename}")
167
+ except Exception as e:
168
+ print(f"Error processing {filename}: {e}")
169
+
170
+ collection = db["ai_processed_candidates"] # New collection for storing JSON data
171
+
172
+ # Delete existing data in the collection
173
+ collection.delete_many({})
174
+
175
+
176
+ # Insert JSON data into MongoDB
177
+ if isinstance(output, list):
178
+ collection.insert_many(output)
179
+ else:
180
+ collection.insert_one(output)
181
+ collection.update_many({"status": "considered"}, {"$set": {"status": "rejected"}})
182
+
183
+
184
+ print("output.json successfully saved to MongoDB!")
185
+
186
+
187
+
188
+ import requests
189
+ def generatecoding():
190
+ # IBM Granite AI Authentication
191
+ def get_ibm_auth_token(api_key):
192
+ auth_url = "https://iam.cloud.ibm.com/identity/token"
193
+ auth_data = {
194
+ "grant_type": "urn:ibm:params:oauth:grant-type:apikey",
195
+ "apikey": api_key
196
+ }
197
+ auth_headers = {"Content-Type": "application/x-www-form-urlencoded"}
198
+
199
+ response = requests.post(auth_url, data=auth_data, headers=auth_headers)
200
+ return response.json().get("access_token")
201
+
202
+
203
+
204
+ # Construct a powerful AI prompt
205
+ def generate_prompt(job_title ):
206
+ return f"""
207
+ <|start_of_role|>system<|end_of_role|>You are an advanced AI specializing in technical hiring and coding assessments. Your task is to generate two high-quality coding question for evaluating candidates applying for the role of {job_title}.
208
+
209
+ The problem should be designed to assess proficiency in {job_title} and should include:
210
+
211
+ 1. **Problem Description**: A clear and concise problem statement that describes the coding task.
212
+ 2. **Input Format**: A detailed explanation of the expected input.
213
+ 3. **Output Format**: A description of the expected output.
214
+ 4. **Constraints**: Reasonable constraints on input values to ensure efficiency.
215
+ 5. **Test Cases**: At most 3 sample test cases with explanations.
216
+
217
+ Ensure that the problem is relevant to real-world scenarios particularly Leetcode Medium DSA problem for a {job_title} and effectively measures problem-solving skills.
218
+
219
+ <|end_of_role|>
220
+ """
221
+
222
+
223
+
224
+ # Call IBM Granite AI
225
+ def analyze_resume_with_ai(auth_token, prompt):
226
+ url = "https://us-south.ml.cloud.ibm.com/ml/v1/text/generation?version=2023-05-29"
227
+ headers = {
228
+ "Accept": "application/json",
229
+ "Content-Type": "application/json",
230
+ "Authorization": f"Bearer {auth_token}"
231
+ }
232
+ body = {
233
+ "input": prompt,
234
+ "parameters": {"decoding_method": "greedy", "max_new_tokens": 900, "repetition_penalty": 1},
235
+ "model_id": "ibm/granite-3-8b-instruct",
236
+ "project_id": "4aa39c25-19d7-48c1-9cf6-e31b5c223a1f"
237
+ }
238
+
239
+ response = requests.post(url, headers=headers, json=body)
240
+ return response.json().get("results", [{}])[0].get("generated_text", "{}")
241
+
242
+
243
+ api_key = "9FV7l0Jxqe7ceL09MeH_g9bYioIQuABXsr1j1VHbKOpr" # Replace with your actual API key
244
+ auth_token = get_ibm_auth_token(api_key)
245
+
246
+
247
+ prompt = generate_prompt("Software developer")
248
+
249
+ # Get AI analysis
250
+ ai_response = analyze_resume_with_ai(auth_token, prompt)
251
+ print(ai_response)
252
+ return ai_response
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ flask
2
+ pymongo
3
+ certifi
4
+ gridfs
5
+ requests
templates/dashboard.html ADDED
@@ -0,0 +1,377 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>HR Dashboard | Active Jobs</title>
7
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css" rel="stylesheet" />
8
+ </head>
9
+ <style>
10
+ .checkbox-checked {
11
+ background-color: #10B981;
12
+ border-color: #10B981;
13
+ transform: scale(1.1);
14
+ }
15
+
16
+ .checkbox-tick {
17
+ color: white;
18
+ display: none;
19
+ transform: scale(0);
20
+ transition: transform 0.2s ease-in-out;
21
+ }
22
+
23
+ .checkbox-checked .checkbox-tick {
24
+ display: block;
25
+ transform: scale(1);
26
+ }
27
+
28
+ .hover-trigger .hover-target {
29
+ display: none;
30
+ }
31
+
32
+ .hover-trigger:hover .hover-target {
33
+ display: flex;
34
+ }
35
+ </style>
36
+ <body class="bg-gradient-to-br from-gray-50 to-gray-100 min-h-screen">
37
+ <!-- Enhanced Header -->
38
+ <nav class="bg-white shadow-sm border-b border-gray-200">
39
+ <div class="max-w-7xl mx-auto px-6 py-4">
40
+ <div class="flex justify-between items-center">
41
+ <div class="flex items-center space-x-4">
42
+ <svg class="w-8 h-8 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
43
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"/>
44
+ </svg>
45
+ <div>
46
+ <h1 class="text-2xl font-bold text-gray-900">HR Dashboard</h1>
47
+ <p class="text-sm text-gray-500">Talent Acquisition Management</p>
48
+ </div>
49
+ </div>
50
+ <div class="flex items-center space-x-4">
51
+ <span id="currentDateTime" class="text-sm text-gray-500"></span>
52
+ </div>
53
+ </div>
54
+ </div>
55
+ </nav>
56
+
57
+ <main class="max-w-7xl mx-auto px-6 py-8">
58
+ <!-- Dashboard Overview Cards -->
59
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
60
+ <div class="bg-white rounded-lg shadow-sm p-6 border border-gray-200">
61
+ <div class="flex items-center">
62
+ <div class="bg-blue-50 rounded-full p-3">
63
+ <svg class="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
64
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 13.255A23.931 23.931 0 0112 15c-3.183 0-6.22-.62-9-1.745M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v2m4 6h.01M5 20h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
65
+ </svg>
66
+ </div>
67
+ <div class="ml-4">
68
+ <h3 class="text-lg font-semibold text-gray-900">Active Jobs</h3>
69
+ <p class="text-2xl font-bold text-blue-600">{{ job_postings|length }}</p>
70
+ </div>
71
+ </div>
72
+ </div>
73
+
74
+ <div class="bg-white rounded-lg shadow-sm p-6 border border-gray-200">
75
+ <div class="flex items-center">
76
+ <div class="bg-green-50 rounded-full p-3">
77
+ <svg class="w-6 h-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
78
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/>
79
+ </svg>
80
+ </div>
81
+ <div class="ml-4">
82
+ <h3 class="text-lg font-semibold text-gray-900">Total Candidates</h3>
83
+ <p class="text-2xl font-bold text-green-600">{{ job_postings|sum(attribute='total_candidates') }}</p>
84
+ </div>
85
+ </div>
86
+ </div>
87
+
88
+ <div class="bg-white rounded-lg shadow-sm p-6 border border-gray-200">
89
+ <div class="flex items-center">
90
+ <div class="bg-purple-50 rounded-full p-3">
91
+ <svg class="w-6 h-6 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
92
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z"/>
93
+ </svg>
94
+ </div>
95
+ <div class="ml-4">
96
+ <h3 class="text-lg font-semibold text-gray-900">Shortlisted</h3>
97
+ <p class="text-2xl font-bold text-purple-600">{{ job_postings|sum(attribute='shortlisted_count') }}</p>
98
+ </div>
99
+ </div>
100
+ </div>
101
+ </div>
102
+
103
+ <!-- Job Listings Table -->
104
+ <div class="bg-white shadow-sm rounded-lg border border-gray-200">
105
+ <div class="px-6 py-4 border-b border-gray-200">
106
+ <div class="flex justify-between items-center">
107
+ <h2 class="text-xl font-semibold text-gray-900">Active Job Postings</h2>
108
+ <div class="flex space-x-2">
109
+ <button class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors duration-200">
110
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
111
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z"/>
112
+ </svg>
113
+ </button>
114
+ <button class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors duration-200">
115
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
116
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
117
+ </svg>
118
+ </button>
119
+ </div>
120
+ </div>
121
+ </div>
122
+
123
+ <div class="overflow-x-auto">
124
+ <table class="min-w-full divide-y divide-gray-200">
125
+ <thead class="bg-gray-50">
126
+ <tr>
127
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Job ID</th>
128
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Position</th>
129
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Department</th>
130
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
131
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Applications</th>
132
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
133
+ </tr>
134
+ </thead>
135
+ <tbody class="bg-white divide-y divide-gray-200">
136
+ {% for job in job_postings %}
137
+ <tr class="hover:bg-gray-50 transition-colors duration-200">
138
+ <td class="px-6 py-4">
139
+ <div class="text-sm font-medium text-gray-900">{{ job.job_id }}</div>
140
+ <div class="text-xs text-gray-500">Created on {{ job.created_at|default('N/A') }}</div>
141
+ </td>
142
+ <td class="px-6 py-4">
143
+ <div class="text-sm text-gray-900 font-medium">{{ job.title }}</div>
144
+ </td>
145
+ <td class="px-6 py-4">
146
+ <div class="text-sm text-gray-900">{{ job.department }}</div>
147
+ </td>
148
+ <td class="px-6 py-4">
149
+ <span class="px-3 py-1 inline-flex text-xs leading-5 font-semibold rounded-full
150
+ {% if job.status == 'active' %}
151
+ bg-green-100 text-green-800
152
+ {% else %}
153
+ bg-red-100 text-red-800
154
+ {% endif %}">
155
+ {{ job.status|title }}
156
+ </span>
157
+ </td>
158
+ <td class="px-6 py-4">
159
+ <div class="flex flex-col space-y-1">
160
+ <div class="text-sm text-gray-900">
161
+ <span class="font-medium">{{ job.total_candidates }}</span> total applications
162
+ </div>
163
+ <div class="text-sm text-green-600">
164
+ <span class="font-medium">{{ job.shortlisted_count }}</span> shortlisted
165
+ </div>
166
+ <div class="w-full bg-gray-200 rounded-full h-1.5 mt-1">
167
+
168
+ </div>
169
+
170
+ </div>
171
+ </div>
172
+ </td>
173
+ <td class="px-6 py-4">
174
+ {% if job.status == 'active' %}
175
+ <div class="flex flex-col space-y-2">
176
+ <div class="flex space-x-2">
177
+ <a href="/job/{{ job.job_id }}"
178
+ class="inline-flex items-center px-3 py-1.5 bg-blue-50 text-blue-600 rounded-lg hover:bg-blue-100 transition-colors duration-200">
179
+ <svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
180
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
181
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7"/>
182
+ </svg>
183
+ View Details
184
+ </a>
185
+ <button onclick="closeJob('{{ job.job_id }}')"
186
+ class="inline-flex items-center px-3 py-1.5 bg-red-50 text-red-600 rounded-lg hover:bg-red-100 transition-colors duration-200">
187
+ <svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
188
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
189
+ </svg>
190
+ Close Job
191
+ </button>
192
+ </div>
193
+ <button onclick="download_resume('{{ job.job_id }}')"
194
+ class="inline-flex items-center px-3 py-1.5 bg-green-50 text-green-600 rounded-lg hover:bg-green-100 transition-colors duration-200">
195
+ <svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
196
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/>
197
+ </svg>
198
+ Screen Resumes
199
+ </button>
200
+
201
+ <!-- Status and Progress Section -->
202
+ <div class="mt-2 space-y-2">
203
+ <div id="download-status-{{ job.job_id }}"
204
+ class="text-sm font-medium"></div>
205
+ <div class="overflow-hidden">
206
+ <ul id="candidate-list-{{ job.job_id }}"
207
+ class="space-y-2 transition-all duration-200"></ul>
208
+ </div>
209
+ </div>
210
+ </div>
211
+ {% endif %}
212
+ </td>
213
+ </tr>
214
+ {% endfor %}
215
+ </tbody>
216
+ </table>
217
+ </div>
218
+ </div>
219
+ </main>
220
+
221
+ <script>
222
+ function closeJob(jobId) {
223
+ if (confirm("Are you sure you want to close this job posting? This will delete all associated resumes and candidate data.")) {
224
+ fetch("/close_job", {
225
+ method: "POST",
226
+ headers: { "Content-Type": "application/json" },
227
+ body: JSON.stringify({ job_id: jobId }),
228
+ })
229
+ .then(response => response.json())
230
+ .then(data => {
231
+ if (data.error) {
232
+ showNotification("Error: " + data.error, "error");
233
+ } else {
234
+ showNotification("Job closed successfully!", "success");
235
+ setTimeout(() => location.reload(), 1500);
236
+ }
237
+ })
238
+ .catch(error => {
239
+ showNotification("Error processing request.", "error");
240
+ console.error("Error:", error);
241
+ });
242
+ }
243
+ }
244
+
245
+ function download_resume(jobId) {
246
+ const statusDiv = document.getElementById("download-status-" + jobId);
247
+ const candidateList = document.getElementById("candidate-list-" + jobId);
248
+
249
+ statusDiv.innerHTML = `
250
+ <div class="flex items-center text-blue-600">
251
+ <svg class="animate-spin h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24">
252
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
253
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
254
+ </svg>
255
+ Downloading resumes...
256
+ </div>`;
257
+ candidateList.innerHTML = "";
258
+
259
+ fetch("/download_resumes", {
260
+ method: "GET",
261
+ })
262
+ .then(response => response.json())
263
+ .then(data => {
264
+ if (data.status === "error") {
265
+ statusDiv.innerHTML = `
266
+ <div class="flex items-center text-red-600">
267
+ <svg class="h-4 w-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
268
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
269
+ </svg>
270
+ Error downloading resumes.
271
+ </div>`;
272
+ } else {
273
+ statusDiv.innerHTML = `
274
+ <div class="flex items-center text-green-600">
275
+ <svg class="h-4 w-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
276
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
277
+ </svg>
278
+ Resumes downloaded successfully!
279
+ </div>`;
280
+
281
+ // Enhanced candidate list with animation
282
+ data.candidates.forEach((name, index) => {
283
+ const listItem = document.createElement("li");
284
+ listItem.className = "flex items-center justify-between p-2 bg-gray-50 rounded-lg opacity-0 transform translate-y-2";
285
+ listItem.style.transition = "all 0.3s ease";
286
+ listItem.style.transitionDelay = `${index * 0.1}s`;
287
+
288
+ const nameSpan = document.createElement("span");
289
+ nameSpan.className = "text-sm text-gray-700";
290
+ nameSpan.textContent = name;
291
+
292
+ const checkboxContainer = document.createElement("div");
293
+ checkboxContainer.className = "h-5 w-5 border rounded flex items-center justify-center transition-all duration-200";
294
+ checkboxContainer.id = `checkbox-${jobId}-${index}`;
295
+
296
+ const checkmark = document.createElement("span");
297
+ checkmark.innerHTML = "✓";
298
+ checkmark.className = "checkbox-tick transition-transform duration-200";
299
+
300
+ checkboxContainer.appendChild(checkmark);
301
+ listItem.appendChild(nameSpan);
302
+ listItem.appendChild(checkboxContainer);
303
+ candidateList.appendChild(listItem);
304
+
305
+ // Trigger animation after a brief delay
306
+ setTimeout(() => {
307
+ listItem.classList.remove("opacity-0", "translate-y-2");
308
+ }, 50);
309
+ });
310
+
311
+ animateCheckboxes(jobId, data.candidates.length);
312
+ }
313
+ })
314
+ .catch(error => {
315
+ statusDiv.innerHTML = `
316
+ <div class="flex items-center text-red-600">
317
+ <svg class="h-4 w-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
318
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
319
+ </svg>
320
+ Error processing request.
321
+ </div>`;
322
+ console.error("Error:", error);
323
+ });
324
+ }
325
+
326
+ function animateCheckboxes(jobId, totalBoxes) {
327
+ let currentBox = 0;
328
+
329
+ function checkNext() {
330
+ if (currentBox < totalBoxes) {
331
+ const checkbox = document.getElementById(`checkbox-${jobId}-${currentBox}`);
332
+ if (checkbox) {
333
+ checkbox.classList.add("checkbox-checked");
334
+ currentBox++;
335
+ setTimeout(checkNext, 2500);
336
+ }
337
+ }
338
+ }
339
+
340
+ setTimeout(checkNext, 500);
341
+ }
342
+
343
+ function showNotification(message, type) {
344
+ const notification = document.createElement("div");
345
+ notification.className = `fixed top-4 right-4 px-6 py-3 rounded-lg shadow-lg transform transition-all duration-300 translate-y-[-100%] ${
346
+ type === "success" ? "bg-green-500" : "bg-red-500"
347
+ } text-white`;
348
+ notification.textContent = message;
349
+
350
+ document.body.appendChild(notification);
351
+ setTimeout(() => notification.style.transform = "translateY(0)", 100);
352
+ setTimeout(() => {
353
+ notification.style.transform = "translateY(-100%)";
354
+ setTimeout(() => notification.remove(), 300);
355
+ }, 3000);
356
+ }
357
+ function updateDateTime() {
358
+ const now = new Date();
359
+ const options = {
360
+ year: 'numeric',
361
+ month: 'long',
362
+ day: 'numeric',
363
+ hour: '2-digit',
364
+ minute: '2-digit'
365
+ };
366
+ document.getElementById('currentDateTime').textContent =
367
+ 'Last updated: ' + now.toLocaleDateString('en-US', options);
368
+ }
369
+
370
+ // Update initially and then every minute
371
+ updateDateTime();
372
+ setInterval(updateDateTime, 60000);
373
+ </script>
374
+ </body>
375
+ </html>
376
+
377
+
templates/email_dashboard.html ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Email Dashboard</title>
7
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
8
+ </head>
9
+ <body class="bg-gray-100 p-6">
10
+ <div class="max-w-7xl mx-auto">
11
+ <h1 class="text-3xl font-bold mb-8 text-gray-800">Email Dashboard</h1>
12
+
13
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
14
+ <!-- Job Applications Container -->
15
+ <div class="bg-white rounded-lg shadow-lg p-6">
16
+ <div class="flex justify-between items-center mb-4">
17
+ <h2 class="text-xl font-semibold text-gray-800">
18
+ Job Applications
19
+ <span class="ml-2 text-sm text-gray-500">({{ job_emails|length }})</span>
20
+ </h2>
21
+ <button onclick="automateReplies('jobs')"
22
+ class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg transition duration-200">
23
+ Automate Replies
24
+ </button>
25
+ </div>
26
+ <div class="space-y-4">
27
+ {% for email in job_emails %}
28
+ <div class="border border-gray-200 rounded-lg p-4 hover:shadow-md transition duration-200">
29
+ <div class="flex justify-between items-start">
30
+ <div>
31
+ <h3 class="font-semibold text-gray-800">{{ email.name }}</h3>
32
+ <p class="text-sm text-gray-600">{{ email.from }}</p>
33
+ </div>
34
+ <span class="text-sm text-gray-500">{{ email.mail_type }}</span>
35
+ </div>
36
+ <h4 class="font-medium text-gray-700 mt-2">{{ email.subject }}</h4>
37
+ <p class="text-gray-600 mt-2 text-sm whitespace-pre-line">{{ email.message }}</p>
38
+ {% if email.resume_pdf %}
39
+ <div class="mt-3 flex items-center">
40
+ <svg class="w-4 h-4 text-gray-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
41
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
42
+ d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13">
43
+ </path>
44
+ </svg>
45
+ <span class="text-sm text-gray-500">Resume Attached</span>
46
+ </div>
47
+ {% endif %}
48
+ </div>
49
+ {% endfor %}
50
+ </div>
51
+ </div>
52
+
53
+ <!-- General Queries Container -->
54
+ <div class="bg-white rounded-lg shadow-lg p-6">
55
+ <div class="flex justify-between items-center mb-4">
56
+ <h2 class="text-xl font-semibold text-gray-800">
57
+ General Queries
58
+ <span class="ml-2 text-sm text-gray-500">({{ general_emails|length }})</span>
59
+ </h2>
60
+ <button onclick="automateReplies('general')"
61
+ class="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded-lg transition duration-200">
62
+ Automate Replies
63
+ </button>
64
+ </div>
65
+ <div class="space-y-4">
66
+ {% for email in general_emails %}
67
+ <div class="border border-gray-200 rounded-lg p-4 hover:shadow-md transition duration-200">
68
+ <div class="flex justify-between items-start">
69
+ <div>
70
+ <h3 class="font-semibold text-gray-800">{{ email.name }}</h3>
71
+ <p class="text-sm text-gray-600">{{ email.from }}</p>
72
+ </div>
73
+ <span class="text-sm text-gray-500">{{ email.mail_type }}</span>
74
+ </div>
75
+ <h4 class="font-medium text-gray-700 mt-2">{{ email.subject }}</h4>
76
+ <p class="text-gray-600 mt-2 text-sm whitespace-pre-line">{{ email.message }}</p>
77
+ </div>
78
+ {% endfor %}
79
+ </div>
80
+ </div>
81
+ </div>
82
+
83
+ <!-- Status Alert -->
84
+ <div id="status-alert" class="fixed bottom-4 right-4 hidden">
85
+ <div class="bg-green-100 border-l-4 border-green-500 text-green-700 p-4 rounded shadow-lg">
86
+ <p class="font-bold">Success!</p>
87
+ <p id="alert-message"></p>
88
+ </div>
89
+ </div>
90
+ </div>
91
+
92
+ <script>
93
+ function automateReplies(type) {
94
+ const alertDiv = document.getElementById('status-alert');
95
+ const alertMessage = document.getElementById('alert-message');
96
+
97
+ fetch('/api/automate-replies', {
98
+ method: 'POST',
99
+ headers: {
100
+ 'Content-Type': 'application/json'
101
+ },
102
+ body: JSON.stringify({ type: type })
103
+ })
104
+ .then(response => response.json())
105
+ .then(data => {
106
+ alertMessage.textContent = data.message;
107
+ alertDiv.classList.remove('hidden');
108
+ setTimeout(() => {
109
+ alertDiv.classList.add('hidden');
110
+ }, 3000);
111
+ })
112
+ .catch(error => {
113
+ console.error('Error:', error);
114
+ alertMessage.textContent = 'Error processing request';
115
+ alertDiv.classList.remove('hidden');
116
+ setTimeout(() => {
117
+ alertDiv.classList.add('hidden');
118
+ }, 3000);
119
+ });
120
+ }
121
+ </script>
122
+ </body>
123
+ </html>
templates/index.html ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Enterprise HR Portal | Dashboard</title>
7
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css" rel="stylesheet">
8
+ </head>
9
+ <body class="bg-gradient-to-br from-gray-50 to-gray-100 min-h-screen">
10
+ <!-- Header -->
11
+ <header class="fixed top-0 w-full bg-white shadow-sm z-10">
12
+ <div class="container mx-auto px-6 py-4">
13
+ <div class="flex items-center justify-between">
14
+ <div class="flex items-center">
15
+ <svg class="w-8 h-8 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
16
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"/>
17
+ </svg>
18
+ <h1 class="ml-3 text-xl font-semibold text-gray-800">Enterprise HR Portal</h1>
19
+ </div>
20
+ <div class="text-sm text-gray-500">Welcome, Administrator</div>
21
+ </div>
22
+ </div>
23
+ </header>
24
+
25
+ <!-- Main Content -->
26
+ <main class="container mx-auto px-6 pt-24 pb-12">
27
+ <div class="max-w-5xl mx-auto">
28
+ <!-- Welcome Section -->
29
+ <div class="text-center mb-12">
30
+ <h2 class="text-3xl font-bold text-gray-900 mb-4">Welcome to HR Management Suite</h2>
31
+ <p class="text-gray-600 max-w-2xl mx-auto">Access your comprehensive HR management tools and streamline your recruitment process with our integrated solutions.</p>
32
+ </div>
33
+
34
+ <!-- Cards Grid -->
35
+ <div class="grid md:grid-cols-3 gap-6">
36
+ <!-- Hiring Card -->
37
+ <div class="bg-white rounded-xl shadow-md overflow-hidden hover:shadow-lg transition-shadow duration-300">
38
+ <div class="p-6">
39
+ <div class="bg-blue-50 rounded-full w-12 h-12 flex items-center justify-center mb-4">
40
+ <svg class="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
41
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/>
42
+ </svg>
43
+ </div>
44
+ <h3 class="text-xl font-semibold text-gray-900 mb-2">Talent Acquisition</h3>
45
+ <p class="text-gray-600 mb-4">Manage your recruitment pipeline and candidate evaluations efficiently.</p>
46
+ <a href="/dashboard" class="inline-flex items-center justify-center w-full px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors duration-300">
47
+ <span>Access Dashboard</span>
48
+ <svg class="w-4 h-4 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
49
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
50
+ </svg>
51
+ </a>
52
+ </div>
53
+ </div>
54
+
55
+ <!-- Mail Inbox Card -->
56
+ <div class="bg-white rounded-xl shadow-md overflow-hidden hover:shadow-lg transition-shadow duration-300">
57
+ <div class="p-6">
58
+ <div class="bg-green-50 rounded-full w-12 h-12 flex items-center justify-center mb-4">
59
+ <svg class="w-6 h-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
60
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
61
+ </svg>
62
+ </div>
63
+ <h3 class="text-xl font-semibold text-gray-900 mb-2">Communication Hub</h3>
64
+ <p class="text-gray-600 mb-4">Centralized inbox for all recruitment-related communications.</p>
65
+ <a href="/mail-inbox" class="inline-flex items-center justify-center w-full px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors duration-300">
66
+ <span>Open Inbox</span>
67
+ <svg class="w-4 h-4 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
68
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
69
+ </svg>
70
+ </a>
71
+ </div>
72
+ </div>
73
+
74
+ <!-- Post Jobs Card -->
75
+ <div class="bg-white rounded-xl shadow-md overflow-hidden hover:shadow-lg transition-shadow duration-300">
76
+ <div class="p-6">
77
+ <div class="bg-purple-50 rounded-full w-12 h-12 flex items-center justify-center mb-4">
78
+ <svg class="w-6 h-6 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
79
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"/>
80
+ </svg>
81
+ </div>
82
+ <h3 class="text-xl font-semibold text-gray-900 mb-2">Job Management</h3>
83
+ <p class="text-gray-600 mb-4">Create and manage job postings across multiple platforms.</p>
84
+ <a href="/post-jobs" class="inline-flex items-center justify-center w-full px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors duration-300">
85
+ <span>Create Posting</span>
86
+ <svg class="w-4 h-4 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
87
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
88
+ </svg>
89
+ </a>
90
+ </div>
91
+ </div>
92
+ </div>
93
+
94
+ <!-- Quick Stats -->
95
+ <div class="mt-12 bg-white rounded-xl shadow-md p-6">
96
+ <h3 class="text-lg font-semibold text-gray-900 mb-4">Quick Overview</h3>
97
+ <div class="grid grid-cols-3 gap-6">
98
+ <div class="text-center">
99
+ <div class="text-2xl font-bold text-blue-600">1</div>
100
+ <div class="text-sm text-gray-600">Active Jobs</div>
101
+ </div>
102
+ <div class="text-center">
103
+ <div class="text-2xl font-bold text-green-600">12</div>
104
+ <div class="text-sm text-gray-600">New Applications</div>
105
+ </div>
106
+ <div class="text-center">
107
+ <div class="text-2xl font-bold text-purple-600">2</div>
108
+ <div class="text-sm text-gray-600">Interviews Today</div>
109
+ </div>
110
+ </div>
111
+ </div>
112
+ </div>
113
+ </main>
114
+
115
+ <!-- Footer -->
116
+ <footer class="bg-white border-t border-gray-200">
117
+ <div class="container mx-auto px-6 py-4">
118
+ <div class="text-center text-sm text-gray-500">
119
+ © 2024 Enterprise HR Portal. All rights reserved.
120
+ </div>
121
+ </div>
122
+ </footer>
123
+ </body>
124
+ </html>
templates/job_details.html ADDED
@@ -0,0 +1,575 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Job Details - HR Dashboard</title>
7
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css" rel="stylesheet">
8
+ </head>
9
+ <body class="bg-gray-50">
10
+ <!-- Enhanced Navigation -->
11
+ <nav class="bg-white shadow-lg border-b border-gray-200">
12
+ <div class="max-w-7xl mx-auto px-4 py-4">
13
+ <div class="flex justify-between items-center">
14
+ <div class="flex items-center">
15
+ <h1 class="text-2xl font-bold text-gray-900">HR Dashboard</h1>
16
+ <span class="mx-2 text-gray-300">|</span>
17
+ <h2 class="text-gray-600">Job Details</h2>
18
+ </div>
19
+ <a href="/" class="flex items-center text-indigo-600 hover:text-indigo-900">
20
+ <svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
21
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/>
22
+ </svg>
23
+ Back to Jobs
24
+ </a>
25
+ </div>
26
+ </div>
27
+ </nav>
28
+
29
+ <main class="max-w-7xl mx-auto px-4 py-8 space-y-8">
30
+ <!-- Enhanced Job Details Header -->
31
+ <div class="bg-white shadow rounded-lg p-6 border border-gray-100">
32
+ <div class="grid grid-cols-2 gap-6">
33
+ <div>
34
+ <h2 class="text-3xl font-bold text-gray-900">{{ job.title }}</h2>
35
+ <div class="mt-4 space-y-2">
36
+ <p class="text-gray-600 flex items-center">
37
+ <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
38
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"/>
39
+ </svg>
40
+ Department: {{ job.department }}
41
+ </p>
42
+ <p class="text-gray-600 flex items-center">
43
+ <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
44
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 20l4-16m2 16l4-16M6 9h14M4 15h14"/>
45
+ </svg>
46
+ Job ID: {{ job.job_id }}
47
+ </p>
48
+ </div>
49
+ </div>
50
+ <div class="text-right">
51
+ <span class="px-4 py-2 inline-flex text-base leading-5 font-semibold rounded-full
52
+ {% if job.status == 'active' %}
53
+ bg-green-100 text-green-800
54
+ {% else %}
55
+ bg-red-100 text-red-800
56
+ {% endif %}">
57
+ {{ job.status|default('Status Not Set')|title }}
58
+ </span>
59
+ </div>
60
+ </div>
61
+ </div>
62
+
63
+ <!-- Enhanced Shortlisted Candidates Section -->
64
+ <div class="bg-white shadow rounded-lg p-6 border border-gray-100">
65
+ <div class="flex justify-between items-center mb-6">
66
+ <div>
67
+ <h3 class="text-xl font-semibold text-gray-900">Shortlisted Candidates</h3>
68
+ <p class="text-sm text-gray-500 mt-1">{{ shortlisted_candidates|length }} candidates in pipeline</p>
69
+ </div>
70
+ {% if shortlisted_candidates %}
71
+ <button onclick="handleConfirmationEmails('{{ job.job_id }}')"
72
+ class="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition duration-150 ease-in-out flex items-center">
73
+ <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
74
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
75
+ </svg>
76
+ Send Confirmation Emails
77
+ </button>
78
+ {% endif %}
79
+ </div>
80
+
81
+ {% if shortlisted_candidates %}
82
+ <div class="overflow-x-auto rounded-lg border border-gray-200">
83
+ <table class="min-w-full divide-y divide-gray-200">
84
+ <thead class="bg-gray-50">
85
+ <tr>
86
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Candidate</th>
87
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
88
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Interview Progress</th>
89
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
90
+ </tr>
91
+ </thead>
92
+ <tbody class="bg-white divide-y divide-gray-200">
93
+ {% for candidate in shortlisted_candidates %}
94
+ <tr class="hover:bg-gray-50">
95
+ <td class="px-6 py-4">
96
+ <div>
97
+ <div class="text-sm font-medium text-gray-900">{{ candidate.candidate_info.name }}</div>
98
+ <div class="text-sm text-gray-500">{{ candidate.candidate_info.email }}</div>
99
+ </div>
100
+ </td>
101
+ <td class="px-6 py-4">
102
+ {% if candidate.status %}
103
+ <span class="px-3 py-1 text-sm font-medium rounded-full
104
+ {% if candidate.status == 'shortlisted' %}
105
+ bg-yellow-100 text-yellow-800
106
+ {% elif candidate.status == 'interview_scheduled' %}
107
+ bg-blue-100 text-blue-800
108
+ {% elif candidate.status == 'ai_interview' %}
109
+ bg-purple-100 text-purple-800
110
+ {% elif candidate.status == 'tech_interview' %}
111
+ bg-indigo-100 text-indigo-800
112
+ {% else %}
113
+ bg-gray-100 text-gray-800
114
+ {% endif %}">
115
+ {{ candidate.status|default('Not Set')|replace('_', ' ')|title }}
116
+ </span>
117
+ {% else %}
118
+ <span class="px-3 py-1 text-sm font-medium rounded-full bg-gray-100 text-gray-800">
119
+ Status Not Set
120
+ </span>
121
+ {% endif %}
122
+ </td>
123
+ <td class="px-6 py-4">
124
+ {% if candidate.status == 'ai_interview' %}
125
+ {% if candidate.interview_completed %}
126
+ <span class="px-2 py-1 text-xs font-medium bg-green-100 text-green-800 rounded-full">
127
+ Interview Completed
128
+ </span>
129
+ {% else %}
130
+ <span class="px-2 py-1 text-xs font-medium bg-yellow-100 text-yellow-800 rounded-full">
131
+ Interview Pending
132
+ </span>
133
+ {% endif %}
134
+ {% elif candidate.status == 'tech_interview' %}
135
+ <span class="px-2 py-1 text-xs font-medium bg-blue-100 text-blue-800 rounded-full">
136
+ Technical Interview Scheduled
137
+ </span>
138
+ {% else %}
139
+ <span class="px-2 py-1 text-xs font-medium bg-gray-100 text-gray-800 rounded-full">
140
+ No Interview / Pending
141
+ </span>
142
+ {% endif %}
143
+ </td>
144
+ <td class="px-6 py-4 text-sm font-medium space-x-3">
145
+ <button onclick="viewCandidate('{{ candidate._id }}')"
146
+ class="text-indigo-600 hover:text-indigo-900">
147
+ View Details
148
+ </button>
149
+ {% if candidate.resume_id %}
150
+ <a href="/view_resume/{{ candidate.resume_id }}"
151
+ class="text-green-600 hover:text-green-900">
152
+ View Resume
153
+ </a>
154
+ {% endif %}
155
+ </td>
156
+ </tr>
157
+ {% endfor %}
158
+ </tbody>
159
+ </table>
160
+ </div>
161
+ {% else %}
162
+ <div class="text-center py-8">
163
+ <svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
164
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"/>
165
+ </svg>
166
+ <h3 class="mt-2 text-sm font-medium text-gray-900">No shortlisted candidates</h3>
167
+ <p class="mt-1 text-sm text-gray-500">Start by reviewing applications to shortlist candidates.</p>
168
+ </div>
169
+ {% endif %}
170
+ </div>
171
+
172
+ <!-- Modal Content - Only show what's available -->
173
+ <div id="candidateModal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
174
+ <div class="relative top-20 mx-auto p-5 border w-11/12 max-w-4xl shadow-lg rounded-lg bg-white">
175
+ <div id="modalContent" class="mt-3"></div>
176
+ <div class="mt-6 flex justify-end">
177
+ <button onclick="closeModal()"
178
+ class="px-4 py-2 bg-gray-500 text-white rounded-lg hover:bg-gray-600 transition duration-150 ease-in-out">
179
+ Close
180
+ </button>
181
+ </div>
182
+ </div>
183
+ </div>
184
+ <!-- Rejected Candidates Section -->
185
+ <div class="bg-white shadow rounded-lg p-6 border border-gray-100">
186
+ <div class="flex justify-between items-center mb-6">
187
+ <div>
188
+ <h3 class="text-xl font-semibold text-gray-900">Rejected Candidates</h3>
189
+ <p class="text-sm text-gray-500 mt-1">{{ rejected_candidates|length }} candidates rejected</p>
190
+ </div>
191
+ {% if rejected_candidates %}
192
+ <button onclick="sendRejectionEmails('{{ job.job_id }}')"
193
+ class="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition duration-150 ease-in-out flex items-center">
194
+ <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
195
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
196
+ </svg>
197
+ Send Rejection Emails
198
+ </button>
199
+ {% endif %}
200
+ </div>
201
+
202
+ {% if rejected_candidates %}
203
+ <div class="overflow-x-auto rounded-lg border border-gray-200">
204
+ <table class="min-w-full divide-y divide-gray-200">
205
+ <thead class="bg-gray-50">
206
+ <tr>
207
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Candidate</th>
208
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
209
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Rejection Reason</th>
210
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
211
+ </tr>
212
+ </thead>
213
+ <tbody class="bg-white divide-y divide-gray-200">
214
+ {% for candidate in rejected_candidates %}
215
+ <tr class="hover:bg-gray-50">
216
+ <td class="px-6 py-4">
217
+ <div>
218
+ <div class="text-sm font-medium text-gray-900">{{ candidate.candidate_info.name|default('Name Not Available') }}</div>
219
+ <div class="text-sm text-gray-500">{{ candidate.candidate_info.email|default('Email Not Available') }}</div>
220
+ </div>
221
+ </td>
222
+ <td class="px-6 py-4">
223
+ <span class="px-3 py-1 text-sm font-medium rounded-full bg-red-100 text-red-800">
224
+ {{ candidate.status|default('Rejected')|replace('_', ' ')|title }}
225
+ </span>
226
+ </td>
227
+ <td class="px-6 py-4">
228
+ <span class="text-sm text-gray-500">
229
+ {{ candidate.rejection_reason|default('No reason specified') }}
230
+ </span>
231
+ </td>
232
+ <td class="px-6 py-4 text-sm font-medium space-x-3">
233
+ <button onclick="viewCandidate('{{ candidate._id }}')"
234
+ class="text-indigo-600 hover:text-indigo-900">
235
+ View Details
236
+ </button>
237
+ {% if candidate.resume_id %}
238
+ <a href="/view_resume/{{ candidate.resume_id }}"
239
+ class="text-green-600 hover:text-green-900">
240
+ View Resume
241
+ </a>
242
+ {% endif %}
243
+ </td>
244
+ </tr>
245
+ {% endfor %}
246
+ </tbody>
247
+ </table>
248
+ </div>
249
+ {% else %}
250
+ <div class="text-center py-8">
251
+ <svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
252
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"/>
253
+ </svg>
254
+ <h3 class="mt-2 text-sm font-medium text-gray-900">No rejected candidates</h3>
255
+ <p class="mt-1 text-sm text-gray-500">All candidates are still under consideration.</p>
256
+ </div>
257
+ {% endif %}
258
+ </div>
259
+ </main>
260
+
261
+ <script>
262
+ function viewCandidate(candidateId) {
263
+ console.log("Viewing candidate:", candidateId);
264
+
265
+ fetch(`/candidate/${candidateId}`)
266
+ .then(response => {
267
+ if (!response.ok) {
268
+ throw new Error('Network response was not ok');
269
+ }
270
+ return response.json();
271
+ })
272
+ .then(data => {
273
+ console.log("Received data:", data);
274
+ const candidate = data.candidate;
275
+ const interviews = data.interviews || [];
276
+
277
+ // Determine interview status and type
278
+ const currentStatus = candidate.status;
279
+ const hasInterview = interviews.length > 0;
280
+ const latestInterview = hasInterview ? interviews[interviews.length - 1] : null;
281
+
282
+ let content = `
283
+ <div class="space-y-6">
284
+ <div class="flex justify-between items-start">
285
+ <div>
286
+ <h2 class="text-2xl font-bold">${candidate.candidate_info.name || 'Name Not Available'}</h2>
287
+ <p class="text-gray-600">${candidate.candidate_info.email || 'Email Not Available'}</p>
288
+ ${candidate.candidate_info.phone ?
289
+ `<p class="text-gray-600">${candidate.candidate_info.phone}</p>` : ''}
290
+ ${candidate.candidate_info.experience ?
291
+ `<p class="text-gray-600">Experience: ${candidate.candidate_info.experience}</p>` : ''}
292
+ ${candidate.candidate_info.current_company ?
293
+ `<p class="text-gray-600">Current Company: ${candidate.candidate_info.current_company}</p>` : ''}
294
+ </div>
295
+ <div class="text-right">
296
+ ${candidate.ai_evaluation?.confidence_score ?
297
+ `<div class="text-lg font-semibold">AI Score: ${candidate.ai_evaluation.confidence_score}%</div>` : ''}
298
+ <div class="text-sm text-gray-500">Status: ${(currentStatus || 'Not Set').replace('_', ' ').toUpperCase()}</div>
299
+ </div>
300
+ </div>`;
301
+
302
+ // Skills Assessment Section (if available)
303
+ if (candidate.ai_evaluation?.matching_skills || candidate.ai_evaluation?.missing_skills) {
304
+ content += `
305
+ <div class="bg-gray-50 p-4 rounded-lg">
306
+ <h3 class="font-semibold mb-2">Skills Assessment</h3>
307
+ <div class="space-y-2">
308
+ ${candidate.ai_evaluation.matching_skills ? `
309
+ <div>
310
+ <span class="font-medium">Matching Skills:</span>
311
+ <div class="flex flex-wrap gap-1 mt-1">
312
+ ${candidate.ai_evaluation.matching_skills.map(skill =>
313
+ `<span class="px-2 py-1 bg-green-100 text-green-800 rounded-full text-sm">${skill}</span>`
314
+ ).join('')}
315
+ </div>
316
+ </div>` : ''}
317
+ ${candidate.ai_evaluation.missing_skills ? `
318
+ <div>
319
+ <span class="font-medium">Missing Skills:</span>
320
+ <div class="flex flex-wrap gap-1 mt-1">
321
+ ${candidate.ai_evaluation.missing_skills.map(skill =>
322
+ `<span class="px-2 py-1 bg-red-100 text-red-800 rounded-full text-sm">${skill}</span>`
323
+ ).join('')}
324
+ </div>
325
+ </div>` : ''}
326
+ </div>
327
+ </div>`;
328
+ }
329
+
330
+ // Interview Options Section
331
+ content += `
332
+ <div class="space-y-4">
333
+ <h3 class="font-semibold">Interview Options</h3>
334
+ <div class="grid grid-cols-3 gap-4">
335
+ <button
336
+ onclick="showAiInterviewModal('${candidate._id}')"
337
+ class="p-3 text-center rounded ${currentStatus === 'ai_interview' ? 'bg-gray-300 cursor-not-allowed' : 'bg-blue-600 hover:bg-blue-700 text-white'}"
338
+ ${currentStatus === 'ai_interview' ? 'disabled' : ''}>
339
+ <svg class="w-5 h-5 mx-auto mb-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
340
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
341
+ </svg>
342
+ AI Interview
343
+ </button>
344
+ <button
345
+ onclick="openCodingQuestionPage()"
346
+ class="p-3 text-center rounded bg-green-600 hover:bg-green-700 text-white">
347
+ <svg class="w-5 h-5 mx-auto mb-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
348
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"/>
349
+ </svg>
350
+ Technical Interview
351
+ </button>
352
+ <button
353
+ onclick="scheduleHRInterview('${candidate._id}')"
354
+ class="p-3 text-center rounded ${currentStatus === 'interview_scheduled' ? 'bg-gray-300 cursor-not-allowed' : 'bg-purple-600 hover:bg-purple-700 text-white'}"
355
+ ${currentStatus === 'interview_scheduled' ? 'disabled' : ''}>
356
+ <svg class="w-5 h-5 mx-auto mb-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
357
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/>
358
+ </svg>
359
+ HR Interview
360
+ </button>
361
+ </div>
362
+ </div>`;
363
+
364
+ // Scheduled Interviews Section
365
+ if (hasInterview) {
366
+ content += `
367
+ <div class="bg-blue-50 p-4 rounded-lg">
368
+ <h3 class="font-semibold mb-2">Scheduled Interview</h3>
369
+ ${interviews.map(interview => `
370
+ <div class="flex justify-between items-center p-2 bg-white rounded-lg mb-2">
371
+ <div>
372
+ <div class="font-medium">${interview.round_type || interview.type}</div>
373
+ <div class="text-sm text-gray-500">
374
+ Scheduled for: ${interview.scheduled_date || 'Date not set'}
375
+ </div>
376
+ </div>
377
+ ${(() => {
378
+ // Only show join button for HR interviews
379
+ if (currentStatus === 'interview_scheduled' && interview.meeting_link) {
380
+ return `
381
+ <a href="${interview.meeting_link}"
382
+ target="_blank"
383
+ class="px-4 py-2 bg-indigo-600 text-white rounded hover:bg-indigo-700 transition duration-150 ease-in-out">
384
+ Join Meeting
385
+ </a>
386
+ `;
387
+ }
388
+ // For AI interviews, show completion status
389
+ else if (currentStatus === 'ai_interview') {
390
+ return `
391
+ <div class="text-sm ${candidate.interview_completed ? 'text-green-600' : 'text-yellow-600'}">
392
+ ${candidate.interview_completed ? 'Interview Completed' : 'Interview Pending'}
393
+ </div>
394
+ `;
395
+ }
396
+ // For technical interviews, just show status
397
+ else if (currentStatus === 'tech_interview') {
398
+ return `
399
+ <div class="text-sm text-blue-600">
400
+ Technical Interview Scheduled
401
+ </div>
402
+ `;
403
+ }
404
+ return '';
405
+ })()}
406
+ </div>
407
+ `).join('')}
408
+ </div>`;
409
+ }
410
+
411
+ content += `</div>`;
412
+ document.getElementById('modalContent').innerHTML = content;
413
+ document.getElementById('candidateModal').classList.remove('hidden');
414
+ })
415
+ .catch(error => {
416
+ console.error('Error:', error);
417
+ alert('Error loading candidate details. Please try again.');
418
+ });
419
+ }
420
+
421
+ function closeModal() {
422
+ document.getElementById('candidateModal').classList.add('hidden');
423
+ }
424
+
425
+ // Enhanced email handling functions
426
+ function handleConfirmationEmails(jobId) {
427
+ if(!jobId) {
428
+ console.error('Job ID is required');
429
+ alert('Error: Job ID is missing');
430
+ return;
431
+ }
432
+
433
+ if(confirm('Send confirmation emails to all shortlisted candidates?')) {
434
+ fetch(`/send_confirmation_emails/${jobId}`, {
435
+ method: 'POST',
436
+ headers: {
437
+ 'Content-Type': 'application/json',
438
+ }
439
+ })
440
+ .then(response => {
441
+ if (!response.ok) {
442
+ throw new Error('Network response was not ok');
443
+ }
444
+ return response.json();
445
+ })
446
+ .then(data => {
447
+ if (data.success) {
448
+ alert('Success: ' + data.message);
449
+ location.reload();
450
+ } else {
451
+ alert('Error: ' + (data.message || 'Failed to send emails'));
452
+ }
453
+ })
454
+ .catch(error => {
455
+ console.error('Error:', error);
456
+ alert('Error sending emails. Please try again.');
457
+ });
458
+ }
459
+ }
460
+ function sendRejectionEmails(jobId) {
461
+ if(!jobId) {
462
+ console.error('Job ID is required');
463
+ alert('Error: Job ID is missing');
464
+ return;
465
+ }
466
+
467
+ if(confirm('Send rejection emails to all not-shortlisted candidates?')) {
468
+ fetch(`/send_rejection_emails/${jobId}`, {
469
+ method: 'POST',
470
+ headers: {
471
+ 'Content-Type': 'application/json',
472
+ }
473
+ })
474
+ .then(response => {
475
+ if (!response.ok) {
476
+ throw new Error('Network response was not ok');
477
+ }
478
+ return response.json();
479
+ })
480
+ .then(data => {
481
+ if (data.success) {
482
+ alert('Success: ' + data.message);
483
+ location.reload();
484
+ } else {
485
+ alert('Error: ' + (data.message || 'Failed to send emails'));
486
+ }
487
+ })
488
+ .catch(error => {
489
+ console.error('Error:', error);
490
+ alert('Error sending emails. Please try again.');
491
+ });
492
+ }
493
+ }
494
+ // Enhanced interview scheduling functions
495
+ function scheduleHRInterview(candidateId) {
496
+ if(!candidateId) {
497
+ console.error('Candidate ID is required');
498
+ alert('Error: Candidate ID is missing');
499
+ return;
500
+ }
501
+
502
+ if(confirm('Schedule HR interview for this candidate?')) {
503
+ fetch('/schedule_hr_interview', {
504
+ method: 'POST',
505
+ headers: {
506
+ 'Content-Type': 'application/json',
507
+ },
508
+ body: JSON.stringify({
509
+ candidate_id: candidateId
510
+ })
511
+ })
512
+ .then(response => {
513
+ if (!response.ok) {
514
+ throw new Error('Network response was not ok');
515
+ }
516
+ return response.json();
517
+ })
518
+ .then(data => {
519
+ if (data.success) {
520
+ alert('HR Interview scheduled successfully!\nMeeting Link: ' + data.meeting_link);
521
+ viewCandidate(candidateId); // Refresh the modal
522
+ } else {
523
+ alert('Error: ' + (data.message || 'Failed to schedule interview'));
524
+ }
525
+ })
526
+ .catch(error => {
527
+ console.error('Error:', error);
528
+ alert('Error scheduling interview. Please try again.');
529
+ });
530
+ }
531
+ }
532
+
533
+ function scheduleInterview(candidateId, type) {
534
+ if(!candidateId || !type) {
535
+ console.error('Candidate ID and interview type are required');
536
+ alert('Error: Missing required information');
537
+ return;
538
+ }
539
+
540
+ fetch('/schedule_interview', {
541
+ method: 'POST',
542
+ headers: {
543
+ 'Content-Type': 'application/json',
544
+ },
545
+ body: JSON.stringify({
546
+ candidate_id: candidateId,
547
+ interview_type: type
548
+ })
549
+ })
550
+ .then(response => {
551
+ if (!response.ok) {
552
+ throw new Error('Network response was not ok');
553
+ }
554
+ return response.json();
555
+ })
556
+ .then(data => {
557
+ if (data.success) {
558
+ alert(`${type.charAt(0).toUpperCase() + type.slice(1)} interview scheduled successfully!`);
559
+ viewCandidate(candidateId); // Refresh the modal
560
+ } else {
561
+ alert('Error: ' + (data.message || 'Failed to schedule interview'));
562
+ }
563
+ })
564
+ .catch(error => {
565
+ console.error('Error:', error);
566
+ alert('Error scheduling interview. Please try again.');
567
+ });
568
+ }
569
+ function openCodingQuestionPage() {
570
+ window.location.href = "/technical.html"; // Ensures the file loads from root directory
571
+ }
572
+
573
+ </script>
574
+ </body>
575
+ </html>
templates/job_posting.html ADDED
@@ -0,0 +1,353 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>HR Dashboard | Jobs Management</title>
7
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css" rel="stylesheet" />
8
+ </head>
9
+ <body class="bg-gradient-to-br from-gray-50 to-gray-100 min-h-screen">
10
+ <!-- Header -->
11
+ <nav class="bg-white shadow-sm border-b border-gray-200">
12
+ <div class="max-w-7xl mx-auto px-6 py-4">
13
+ <div class="flex justify-between items-center">
14
+ <div class="flex items-center space-x-4">
15
+ <svg class="w-8 h-8 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
16
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"/>
17
+ </svg>
18
+ <div>
19
+ <h1 class="text-2xl font-bold text-gray-900">HR Dashboard</h1>
20
+ <p class="text-sm text-gray-500">Jobs Management</p>
21
+ </div>
22
+ </div>
23
+ <div class="flex items-center space-x-4">
24
+ <span id="currentDateTime" class="text-sm text-gray-500"></span>
25
+ </div>
26
+ </div>
27
+ </div>
28
+ </nav>
29
+
30
+ <main class="max-w-7xl mx-auto px-6 py-8 space-y-8">
31
+ <!-- Job Posting Form -->
32
+ <div class="bg-white shadow-sm rounded-lg border border-gray-200">
33
+ <div class="px-6 py-4 border-b border-gray-200">
34
+ <div class="flex items-center justify-between">
35
+ <div class="flex items-center space-x-4">
36
+ <div class="bg-blue-50 rounded-full p-3">
37
+ <svg class="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
38
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
39
+ </svg>
40
+ </div>
41
+ <h2 class="text-xl font-semibold text-gray-900">Create New Job Posting</h2>
42
+ </div>
43
+ <button id="toggleForm" class="text-blue-600 hover:text-blue-800">
44
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
45
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
46
+ </svg>
47
+ </button>
48
+ </div>
49
+ </div>
50
+
51
+ <form id="jobPostingForm" class="p-6 space-y-6 hidden">
52
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
53
+ <div>
54
+ <label class="block text-sm font-medium text-gray-700 mb-2">Job Title</label>
55
+ <input type="text" name="jobTitle" required
56
+ class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
57
+ </div>
58
+
59
+ <div>
60
+ <label class="block text-sm font-medium text-gray-700 mb-2">Department</label>
61
+ <input type="text" name="department" required
62
+ class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
63
+ </div>
64
+
65
+ <div>
66
+ <label class="block text-sm font-medium text-gray-700 mb-2">Location</label>
67
+ <input type="text" name="location" required
68
+ class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
69
+ </div>
70
+
71
+ <div>
72
+ <label class="block text-sm font-medium text-gray-700 mb-2">Employment Type</label>
73
+ <select name="employmentType" required
74
+ class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
75
+ <option value="full-time">Full Time</option>
76
+ <option value="part-time">Part Time</option>
77
+ <option value="contract">Contract</option>
78
+ <option value="internship">Internship</option>
79
+ </select>
80
+ </div>
81
+ </div>
82
+
83
+ <div>
84
+ <label class="block text-sm font-medium text-gray-700 mb-2">Job Description</label>
85
+ <textarea name="jobDescription" rows="4" required
86
+ class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"></textarea>
87
+ </div>
88
+
89
+ <div>
90
+ <label class="block text-sm font-medium text-gray-700 mb-2">Roles and Responsibilities</label>
91
+ <textarea name="responsibilities" rows="4" required
92
+ class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"></textarea>
93
+ </div>
94
+
95
+ <div>
96
+ <label class="block text-sm font-medium text-gray-700 mb-2">Required Qualifications</label>
97
+ <textarea name="qualifications" rows="4" required
98
+ class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"></textarea>
99
+ </div>
100
+
101
+ <div class="border-t border-gray-200 pt-6">
102
+ <label class="block text-sm font-medium text-gray-700 mb-4">Select Platform for Posting</label>
103
+ <div class="flex space-x-4">
104
+ <label class="flex items-center space-x-2">
105
+ <input type="radio" name="platform" value="linkedin" checked
106
+ class="h-4 w-4 text-blue-600 focus:ring-blue-500">
107
+ <span class="text-sm text-gray-700">LinkedIn</span>
108
+ </label>
109
+ <label class="flex items-center space-x-2">
110
+ <input type="radio" name="platform" value="naukri"
111
+ class="h-4 w-4 text-blue-600 focus:ring-blue-500">
112
+ <span class="text-sm text-gray-700">Naukri</span>
113
+ </label>
114
+ </div>
115
+ </div>
116
+
117
+ <div class="flex justify-end space-x-4">
118
+ <button type="reset"
119
+ class="px-4 py-2 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50">
120
+ Reset
121
+ </button>
122
+ <button type="submit"
123
+ class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700">
124
+ Create Job Posting
125
+ </button>
126
+ </div>
127
+ </form>
128
+ </div>
129
+
130
+ <!-- Job Listings Table -->
131
+ <div class="bg-white shadow-sm rounded-lg border border-gray-200">
132
+ <div class="px-6 py-4 border-b border-gray-200">
133
+ <div class="flex justify-between items-center">
134
+ <h2 class="text-xl font-semibold text-gray-900">Job Postings History</h2>
135
+ <div class="flex space-x-2">
136
+ <button class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200">
137
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
138
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z"/>
139
+ </svg>
140
+ </button>
141
+ <button class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200">
142
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
143
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
144
+ </svg>
145
+ </button>
146
+ </div>
147
+ </div>
148
+ </div>
149
+
150
+ <div class="overflow-x-auto">
151
+ <table class="min-w-full divide-y divide-gray-200">
152
+ <thead class="bg-gray-50">
153
+ <tr>
154
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Job ID</th>
155
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Position</th>
156
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Department</th>
157
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Status</th>
158
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Applications</th>
159
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Actions</th>
160
+ </tr>
161
+ </thead>
162
+ <tbody class="bg-white divide-y divide-gray-200">
163
+ <!-- Sample Current Job -->
164
+ <tr class="hover:bg-gray-50">
165
+ <td class="px-6 py-4">
166
+ <div class="text-sm font-medium text-gray-900">SDE001</div>
167
+ <div class="text-xs text-gray-500">Created on Feb 20, 2024</div>
168
+ </td>
169
+ <td class="px-6 py-4">
170
+ <div class="text-sm text-gray-900">Software Engineer</div>
171
+ </td>
172
+ <td class="px-6 py-4">
173
+ <div class="text-sm text-gray-900">Engineering</div>
174
+ </td>
175
+ <td class="px-6 py-4">
176
+ <span class="px-3 py-1 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">
177
+ Active
178
+ </span>
179
+ </td>
180
+ <td class="px-6 py-4">
181
+ <div class="flex flex-col space-y-1">
182
+ <div class="text-sm text-gray-900">12 total applications</div>
183
+ <div class="text-sm text-green-600">4 shortlisted</div>
184
+ <div class="w-full bg-gray-200 rounded-full h-1.5">
185
+ <div class="bg-green-600 h-1.5 rounded-full" style="width: 33%"></div>
186
+ </div>
187
+ </div>
188
+ </td>
189
+ <td class="px-6 py-4">
190
+ <div class="flex space-x-2">
191
+ <button class="text-blue-600 hover:text-blue-800">View</button>
192
+ <button class="text-red-600 hover:text-red-800">Close</button>
193
+ </div>
194
+ </td>
195
+ </tr>
196
+
197
+ <!-- Sample Past Job -->
198
+ <tr class="hover:bg-gray-50">
199
+ <td class="px-6 py-4">
200
+ <div class="text-sm font-medium text-gray-900">UI001</div>
201
+ <div class="text-xs text-gray-500">Created on Jan 15, 2024</div>
202
+ </td>
203
+ <td class="px-6 py-4">
204
+ <div class="text-sm text-gray-900">UI/UX Designer</div>
205
+ </td>
206
+ <td class="px-6 py-4">
207
+ <div class="text-sm text-gray-900">Design</div>
208
+ </td>
209
+ <td class="px-6 py-4">
210
+ <span class="px-3 py-1 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">
211
+ Closed
212
+ </span>
213
+ </td>
214
+ <td class="px-6 py-4">
215
+ <div class="flex flex-col space-y-1">
216
+ <div class="text-sm text-gray-900">18 total applications</div>
217
+ <div class="text-sm text-green-600">6 shortlisted</div>
218
+ <div class="w-full bg-gray-200 rounded-full h-1.5">
219
+ <div class="bg-green-600 h-1.5 rounded-full" style="width: 33%"></div>
220
+ </div>
221
+ </div>
222
+ </td>
223
+ <td class="px-6 py-4">
224
+ <button class="text-blue-600 hover:text-blue-800">View</button>
225
+ </td>
226
+ </tr>
227
+ <!-- Sample Past Job -->
228
+ <tr class="hover:bg-gray-50">
229
+ <td class="px-6 py-4">
230
+ <div class="text-sm font-medium text-gray-900">PM001</div>
231
+ <div class="text-xs text-gray-500">Created on Jan 5, 2024</div>
232
+ </td>
233
+ <td class="px-6 py-4">
234
+ <div class="text-sm text-gray-900">Product Manager</div>
235
+ </td>
236
+ <td class="px-6 py-4">
237
+ <div class="text-sm text-gray-900">Product</div>
238
+ </td>
239
+ <td class="px-6 py-4">
240
+ <span class="px-3 py-1 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">
241
+ Closed
242
+ </span>
243
+ </td>
244
+ <td class="px-6 py-4">
245
+ <div class="flex flex-col space-y-1">
246
+ <div class="text-sm text-gray-900">15 total applications</div>
247
+ <div class="text-sm text-green-600">5 shortlisted</div>
248
+ <div class="w-full bg-gray-200 rounded-full h-1.5">
249
+ <div class="bg-green-600 h-1.5 rounded-full" style="width: 33%"></div>
250
+ </div>
251
+ </div>
252
+ </td>
253
+ <td class="px-6 py-4">
254
+ <button class="text-blue-600 hover:text-blue-800">View</button>
255
+ </td>
256
+ </tr>
257
+ </tbody>
258
+ </table>
259
+ </div>
260
+
261
+ <!-- Pagination -->
262
+ <div class="px-6 py-4 border-t border-gray-200">
263
+ <div class="flex items-center justify-between">
264
+ <div class="text-sm text-gray-700">
265
+ Showing <span class="font-medium">1</span> to <span class="font-medium">3</span> of <span class="font-medium">3</span> results
266
+ </div>
267
+ <div class="flex space-x-2">
268
+ <button class="px-3 py-1 border border-gray-300 rounded-md text-sm disabled:opacity-50">
269
+ Previous
270
+ </button>
271
+ <button class="px-3 py-1 border border-gray-300 rounded-md text-sm disabled:opacity-50">
272
+ Next
273
+ </button>
274
+ </div>
275
+ </div>
276
+ </div>
277
+ </div>
278
+ </main>
279
+
280
+ <script>
281
+ // Toggle Job Posting Form
282
+ document.getElementById('toggleForm').addEventListener('click', function() {
283
+ const form = document.getElementById('jobPostingForm');
284
+ const icon = this.querySelector('svg path');
285
+ if (form.classList.contains('hidden')) {
286
+ form.classList.remove('hidden');
287
+ icon.setAttribute('d', 'M5 15l7-7 7 7');
288
+ } else {
289
+ form.classList.add('hidden');
290
+ icon.setAttribute('d', 'M19 9l-7 7-7-7');
291
+ }
292
+ });
293
+
294
+ // Form Submission
295
+ document.getElementById('jobPostingForm').addEventListener('submit', function(e) {
296
+ e.preventDefault();
297
+
298
+ const formData = new FormData(e.target);
299
+ const data = Object.fromEntries(formData.entries());
300
+
301
+ // Show success notification
302
+ const notification = document.createElement('div');
303
+ notification.className = 'fixed top-4 right-4 bg-green-500 text-white px-6 py-3 rounded-lg shadow-lg transform transition-all duration-300';
304
+ notification.textContent = `Job posting created successfully for ${data.platform}!`;
305
+
306
+ document.body.appendChild(notification);
307
+ setTimeout(() => notification.remove(), 3000);
308
+
309
+ // Reset form and hide it
310
+ e.target.reset();
311
+ e.target.classList.add('hidden');
312
+ });
313
+
314
+ // Update DateTime
315
+ function updateDateTime() {
316
+ const now = new Date();
317
+ const options = {
318
+ year: 'numeric',
319
+ month: 'long',
320
+ day: 'numeric',
321
+ hour: '2-digit',
322
+ minute: '2-digit'
323
+ };
324
+ document.getElementById('currentDateTime').textContent =
325
+ 'Last updated: ' + now.toLocaleDateString('en-US', options);
326
+ }
327
+
328
+ // Initialize and update datetime
329
+ updateDateTime();
330
+ setInterval(updateDateTime, 60000);
331
+
332
+ // Close Job Function
333
+ function closeJob(jobId) {
334
+ if (confirm("Are you sure you want to close this job posting?")) {
335
+ // Add your close job logic here
336
+ showNotification("Job closed successfully!", "success");
337
+ }
338
+ }
339
+
340
+ // Show Notification Function
341
+ function showNotification(message, type) {
342
+ const notification = document.createElement('div');
343
+ notification.className = `fixed top-4 right-4 px-6 py-3 rounded-lg shadow-lg transform transition-all duration-300 ${
344
+ type === "success" ? "bg-green-500" : "bg-red-500"
345
+ } text-white`;
346
+ notification.textContent = message;
347
+
348
+ document.body.appendChild(notification);
349
+ setTimeout(() => notification.remove(), 3000);
350
+ }
351
+ </script>
352
+ </body>
353
+ </html>
templates/technical.html ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Technical Interview - Coding Challenge</title>
7
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css" rel="stylesheet">
8
+ </head>
9
+ <body class="bg-gray-100 flex justify-center items-center min-h-screen">
10
+
11
+ <div class="bg-white p-6 rounded-lg shadow-lg max-w-2xl w-full">
12
+ <h2 class="text-2xl font-bold mb-4">Technical Interview - Coding Challenge</h2>
13
+ <p id="codingProblem" class="text-gray-700 whitespace-pre-line">Loading...</p>
14
+
15
+ <div class="mt-4 flex justify-end">
16
+ <button onclick="postCode()" class="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700">
17
+ Post Code
18
+ </button>
19
+ <button onclick="closePage()" class="ml-2 px-4 py-2 bg-gray-500 text-white rounded-lg hover:bg-gray-600">
20
+ Close
21
+ </button>
22
+ </div>
23
+ </div>
24
+
25
+ <script>
26
+ // Fetch coding question from Flask API
27
+ function fetchCodingQuestion() {
28
+ fetch("/get_coding_question")
29
+ .then(response => response.json())
30
+ .then(data => {
31
+ document.getElementById("codingProblem").innerText = data.question;
32
+ })
33
+ .catch(error => {
34
+ console.error("Error fetching coding question:", error);
35
+ document.getElementById("codingProblem").innerText = "Error loading question.";
36
+ });
37
+ }
38
+
39
+ function postCode() {
40
+ alert("Posted Successfully!");
41
+ }
42
+
43
+ function closePage() {
44
+ window.history.back(); // Go back to the previous page
45
+ }
46
+
47
+ // Call function on page load
48
+ fetchCodingQuestion();
49
+ </script>
50
+
51
+ </body>
52
+ </html>