michael-agent 1.0.2__py3-none-any.whl → 1.0.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- michael_agent/dashboard/static/styles.css +311 -0
- michael_agent/dashboard/templates/career_portal.html +482 -0
- michael_agent/dashboard/templates/dashboard.html +807 -0
- michael_agent/dashboard/templates/jd_creation.html +318 -0
- michael_agent/dashboard/templates/resume_scoring.html +1032 -0
- michael_agent/dashboard/templates/upload_resume.html +411 -0
- {michael_agent-1.0.2.dist-info → michael_agent-1.0.4.dist-info}/METADATA +1 -1
- {michael_agent-1.0.2.dist-info → michael_agent-1.0.4.dist-info}/RECORD +10 -4
- {michael_agent-1.0.2.dist-info → michael_agent-1.0.4.dist-info}/WHEEL +0 -0
- {michael_agent-1.0.2.dist-info → michael_agent-1.0.4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,482 @@
|
|
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>Career Portal - SmartRecruitAgent</title>
|
7
|
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
8
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
|
9
|
+
<link rel="stylesheet" href="/static/styles.css">
|
10
|
+
<style>
|
11
|
+
.job-card {
|
12
|
+
transition: transform 0.2s;
|
13
|
+
cursor: pointer;
|
14
|
+
}
|
15
|
+
.job-card:hover {
|
16
|
+
transform: translateY(-5px);
|
17
|
+
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
|
18
|
+
}
|
19
|
+
.job-tag {
|
20
|
+
font-size: 0.8rem;
|
21
|
+
padding: 0.25rem 0.5rem;
|
22
|
+
}
|
23
|
+
</style>
|
24
|
+
</head>
|
25
|
+
<body>
|
26
|
+
<header class="bg-dark text-white py-4">
|
27
|
+
<div class="container">
|
28
|
+
<div class="row align-items-center">
|
29
|
+
<div class="col-md-6">
|
30
|
+
<h1 class="mb-0">Career Opportunities</h1>
|
31
|
+
<p class="lead mb-0">Find your next opportunity with us</p>
|
32
|
+
</div>
|
33
|
+
<div class="col-md-6 d-flex justify-content-end">
|
34
|
+
<a href="/" class="btn btn-outline-light">Dashboard</a>
|
35
|
+
</div>
|
36
|
+
</div>
|
37
|
+
</div>
|
38
|
+
</header>
|
39
|
+
|
40
|
+
<div class="container my-5">
|
41
|
+
<!-- Search and Filters Section -->
|
42
|
+
<div class="card mb-4 shadow-sm">
|
43
|
+
<div class="card-body">
|
44
|
+
<form id="search-form">
|
45
|
+
<div class="row g-3">
|
46
|
+
<div class="col-md-6">
|
47
|
+
<div class="input-group">
|
48
|
+
<span class="input-group-text bg-white"><i class="bi bi-search"></i></span>
|
49
|
+
<input type="text" class="form-control" id="search-input" placeholder="Search for jobs...">
|
50
|
+
</div>
|
51
|
+
</div>
|
52
|
+
<div class="col-md-3">
|
53
|
+
<select class="form-select" id="location-filter">
|
54
|
+
<option value="">All Locations</option>
|
55
|
+
<!-- Locations will be populated dynamically -->
|
56
|
+
</select>
|
57
|
+
</div>
|
58
|
+
<div class="col-md-3">
|
59
|
+
<button type="submit" class="btn btn-primary w-100">Search Jobs</button>
|
60
|
+
</div>
|
61
|
+
</div>
|
62
|
+
</form>
|
63
|
+
</div>
|
64
|
+
</div>
|
65
|
+
|
66
|
+
<!-- Job Listings Container -->
|
67
|
+
<div class="row mb-4" id="job-listings-container">
|
68
|
+
<!-- Job listings will be loaded dynamically -->
|
69
|
+
<div class="col-12 text-center py-5">
|
70
|
+
<div class="spinner-border text-primary" role="status">
|
71
|
+
<span class="visually-hidden">Loading...</span>
|
72
|
+
</div>
|
73
|
+
<p class="mt-2">Loading job listings...</p>
|
74
|
+
</div>
|
75
|
+
</div>
|
76
|
+
|
77
|
+
<!-- No Results Message (hidden by default) -->
|
78
|
+
<div class="text-center py-5 d-none" id="no-results">
|
79
|
+
<i class="bi bi-search display-1 text-muted"></i>
|
80
|
+
<h3 class="mt-3">No job listings found</h3>
|
81
|
+
<p>Please try different search criteria.</p>
|
82
|
+
</div>
|
83
|
+
</div>
|
84
|
+
|
85
|
+
<!-- Job Details Modal -->
|
86
|
+
<div class="modal fade" id="job-details-modal" tabindex="-1" aria-hidden="true">
|
87
|
+
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
88
|
+
<div class="modal-content">
|
89
|
+
<div class="modal-header bg-primary text-white">
|
90
|
+
<h5 class="modal-title" id="modal-job-title">Job Title</h5>
|
91
|
+
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
92
|
+
</div>
|
93
|
+
<div class="modal-body">
|
94
|
+
<div class="mb-4">
|
95
|
+
<div class="d-flex justify-content-between align-items-start">
|
96
|
+
<div>
|
97
|
+
<p class="mb-1" id="modal-job-location"><i class="bi bi-geo-alt"></i> Location</p>
|
98
|
+
<div class="d-flex gap-2 mb-3">
|
99
|
+
<span class="badge bg-info" id="modal-job-type">Full-time</span>
|
100
|
+
<span class="badge bg-secondary" id="modal-job-level">Mid-level</span>
|
101
|
+
</div>
|
102
|
+
</div>
|
103
|
+
<div>
|
104
|
+
<span class="badge bg-success" id="modal-job-id">Job ID: JD123456</span>
|
105
|
+
</div>
|
106
|
+
</div>
|
107
|
+
|
108
|
+
<hr>
|
109
|
+
<div id="modal-job-description">
|
110
|
+
<!-- Job description will be loaded here -->
|
111
|
+
</div>
|
112
|
+
|
113
|
+
<div class="mt-4">
|
114
|
+
<h6>Required Skills:</h6>
|
115
|
+
<div class="d-flex flex-wrap gap-2 mb-3" id="modal-required-skills">
|
116
|
+
<!-- Skills will be loaded here -->
|
117
|
+
</div>
|
118
|
+
|
119
|
+
<h6>Preferred Skills:</h6>
|
120
|
+
<div class="d-flex flex-wrap gap-2" id="modal-preferred-skills">
|
121
|
+
<!-- Skills will be loaded here -->
|
122
|
+
</div>
|
123
|
+
</div>
|
124
|
+
</div>
|
125
|
+
</div>
|
126
|
+
<div class="modal-footer bg-light">
|
127
|
+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
128
|
+
<button type="button" class="btn btn-primary" id="apply-button">Apply Now</button>
|
129
|
+
</div>
|
130
|
+
</div>
|
131
|
+
</div>
|
132
|
+
</div>
|
133
|
+
|
134
|
+
<!-- Application Modal -->
|
135
|
+
<div class="modal fade" id="application-modal" tabindex="-1" aria-hidden="true">
|
136
|
+
<div class="modal-dialog">
|
137
|
+
<div class="modal-content">
|
138
|
+
<div class="modal-header bg-primary text-white">
|
139
|
+
<h5 class="modal-title">Apply for <span id="application-job-title"></span></h5>
|
140
|
+
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
141
|
+
</div>
|
142
|
+
<div class="modal-body">
|
143
|
+
<form id="application-form">
|
144
|
+
<input type="hidden" id="application-job-id" name="job_id">
|
145
|
+
|
146
|
+
<div class="mb-3">
|
147
|
+
<label for="applicant-name" class="form-label">Full Name*</label>
|
148
|
+
<input type="text" class="form-control" id="applicant-name" name="name" required>
|
149
|
+
</div>
|
150
|
+
|
151
|
+
<div class="mb-3">
|
152
|
+
<label for="applicant-email" class="form-label">Email Address*</label>
|
153
|
+
<input type="email" class="form-control" id="applicant-email" name="email" required>
|
154
|
+
</div>
|
155
|
+
|
156
|
+
<div class="mb-3">
|
157
|
+
<label for="applicant-phone" class="form-label">Phone Number</label>
|
158
|
+
<input type="tel" class="form-control" id="applicant-phone" name="phone">
|
159
|
+
</div>
|
160
|
+
|
161
|
+
<div class="mb-3">
|
162
|
+
<label for="resume-file" class="form-label">Upload Resume*</label>
|
163
|
+
<input type="file" class="form-control" id="resume-file" name="resume" required accept=".pdf,.doc,.docx">
|
164
|
+
<div class="form-text">Accepted formats: PDF, DOC, DOCX</div>
|
165
|
+
</div>
|
166
|
+
|
167
|
+
<div class="mb-3">
|
168
|
+
<label for="cover-letter" class="form-label">Cover Letter</label>
|
169
|
+
<textarea class="form-control" id="cover-letter" name="cover_letter" rows="4"></textarea>
|
170
|
+
</div>
|
171
|
+
</form>
|
172
|
+
</div>
|
173
|
+
<div class="modal-footer">
|
174
|
+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
175
|
+
<button type="button" class="btn btn-primary" id="submit-application">
|
176
|
+
<span class="spinner-border spinner-border-sm d-none" id="submit-spinner" role="status" aria-hidden="true"></span>
|
177
|
+
Submit Application
|
178
|
+
</button>
|
179
|
+
</div>
|
180
|
+
</div>
|
181
|
+
</div>
|
182
|
+
</div>
|
183
|
+
|
184
|
+
<!-- Success Modal -->
|
185
|
+
<div class="modal fade" id="success-modal" tabindex="-1" aria-hidden="true">
|
186
|
+
<div class="modal-dialog">
|
187
|
+
<div class="modal-content">
|
188
|
+
<div class="modal-header bg-success text-white">
|
189
|
+
<h5 class="modal-title">Application Submitted!</h5>
|
190
|
+
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
191
|
+
</div>
|
192
|
+
<div class="modal-body text-center py-4">
|
193
|
+
<i class="bi bi-check-circle-fill text-success display-1"></i>
|
194
|
+
<h4 class="mt-3">Thank You for Applying</h4>
|
195
|
+
<p>Your application has been successfully submitted. Our team will review your resume and get back to you soon.</p>
|
196
|
+
</div>
|
197
|
+
<div class="modal-footer">
|
198
|
+
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">Close</button>
|
199
|
+
</div>
|
200
|
+
</div>
|
201
|
+
</div>
|
202
|
+
</div>
|
203
|
+
|
204
|
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
205
|
+
<script>
|
206
|
+
document.addEventListener('DOMContentLoaded', function() {
|
207
|
+
// Initialize global variables
|
208
|
+
let allJobs = [];
|
209
|
+
let locations = new Set();
|
210
|
+
|
211
|
+
// Load job listings
|
212
|
+
fetchJobs();
|
213
|
+
|
214
|
+
// Event listeners
|
215
|
+
document.getElementById('search-form').addEventListener('submit', function(e) {
|
216
|
+
e.preventDefault();
|
217
|
+
filterJobs();
|
218
|
+
});
|
219
|
+
|
220
|
+
document.getElementById('location-filter').addEventListener('change', filterJobs);
|
221
|
+
document.getElementById('search-input').addEventListener('input', filterJobs);
|
222
|
+
|
223
|
+
// Apply button in job modal
|
224
|
+
document.getElementById('apply-button').addEventListener('click', function() {
|
225
|
+
// Get job details from the current modal
|
226
|
+
const jobId = this.getAttribute('data-job-id');
|
227
|
+
const jobTitle = document.getElementById('modal-job-title').textContent;
|
228
|
+
|
229
|
+
// Set values in application modal
|
230
|
+
document.getElementById('application-job-id').value = jobId;
|
231
|
+
document.getElementById('application-job-title').textContent = jobTitle;
|
232
|
+
|
233
|
+
// Hide job details modal and show application modal
|
234
|
+
const jobModal = bootstrap.Modal.getInstance(document.getElementById('job-details-modal'));
|
235
|
+
jobModal.hide();
|
236
|
+
|
237
|
+
const applicationModal = new bootstrap.Modal(document.getElementById('application-modal'));
|
238
|
+
applicationModal.show();
|
239
|
+
});
|
240
|
+
|
241
|
+
// Submit application
|
242
|
+
document.getElementById('submit-application').addEventListener('click', function() {
|
243
|
+
const form = document.getElementById('application-form');
|
244
|
+
|
245
|
+
// Check form validity
|
246
|
+
if (!form.checkValidity()) {
|
247
|
+
form.reportValidity();
|
248
|
+
return;
|
249
|
+
}
|
250
|
+
|
251
|
+
// Get form data
|
252
|
+
const formData = new FormData(form);
|
253
|
+
|
254
|
+
// Add resume file
|
255
|
+
const resumeFile = document.getElementById('resume-file').files[0];
|
256
|
+
if (!resumeFile) {
|
257
|
+
alert('Please select a resume file');
|
258
|
+
return;
|
259
|
+
}
|
260
|
+
formData.append('resume_file', resumeFile);
|
261
|
+
|
262
|
+
// Show spinner
|
263
|
+
this.disabled = true;
|
264
|
+
document.getElementById('submit-spinner').classList.remove('d-none');
|
265
|
+
|
266
|
+
// Submit application
|
267
|
+
fetch('/api/apply', {
|
268
|
+
method: 'POST',
|
269
|
+
body: formData
|
270
|
+
})
|
271
|
+
.then(response => response.json())
|
272
|
+
.then(data => {
|
273
|
+
if (data.success) {
|
274
|
+
// Hide application modal
|
275
|
+
const applicationModal = bootstrap.Modal.getInstance(document.getElementById('application-modal'));
|
276
|
+
applicationModal.hide();
|
277
|
+
|
278
|
+
// Show success modal
|
279
|
+
const successModal = new bootstrap.Modal(document.getElementById('success-modal'));
|
280
|
+
successModal.show();
|
281
|
+
|
282
|
+
// Reset form
|
283
|
+
form.reset();
|
284
|
+
} else {
|
285
|
+
alert('Error submitting application: ' + data.error);
|
286
|
+
}
|
287
|
+
})
|
288
|
+
.catch(error => {
|
289
|
+
console.error('Error:', error);
|
290
|
+
alert('Error submitting application. Please try again.');
|
291
|
+
})
|
292
|
+
.finally(() => {
|
293
|
+
// Hide spinner
|
294
|
+
this.disabled = false;
|
295
|
+
document.getElementById('submit-spinner').classList.add('d-none');
|
296
|
+
});
|
297
|
+
});
|
298
|
+
|
299
|
+
// Functions
|
300
|
+
function fetchJobs() {
|
301
|
+
fetch('/api/career-jobs')
|
302
|
+
.then(response => response.json())
|
303
|
+
.then(data => {
|
304
|
+
allJobs = data.jobs;
|
305
|
+
|
306
|
+
// Collect unique locations
|
307
|
+
allJobs.forEach(job => {
|
308
|
+
if (job.location) {
|
309
|
+
locations.add(job.location);
|
310
|
+
}
|
311
|
+
});
|
312
|
+
|
313
|
+
// Populate location filter
|
314
|
+
const locationFilter = document.getElementById('location-filter');
|
315
|
+
locations.forEach(location => {
|
316
|
+
const option = document.createElement('option');
|
317
|
+
option.value = location;
|
318
|
+
option.textContent = location;
|
319
|
+
locationFilter.appendChild(option);
|
320
|
+
});
|
321
|
+
|
322
|
+
// Display jobs
|
323
|
+
displayJobs(allJobs);
|
324
|
+
})
|
325
|
+
.catch(error => {
|
326
|
+
console.error('Error fetching jobs:', error);
|
327
|
+
document.getElementById('job-listings-container').innerHTML = `
|
328
|
+
<div class="col-12 text-center py-5">
|
329
|
+
<i class="bi bi-exclamation-triangle text-warning display-4"></i>
|
330
|
+
<h3 class="mt-3">Error Loading Jobs</h3>
|
331
|
+
<p>Please try refreshing the page.</p>
|
332
|
+
</div>
|
333
|
+
`;
|
334
|
+
});
|
335
|
+
}
|
336
|
+
|
337
|
+
function displayJobs(jobs) {
|
338
|
+
const container = document.getElementById('job-listings-container');
|
339
|
+
|
340
|
+
// Show/hide no results message
|
341
|
+
if (jobs.length === 0) {
|
342
|
+
container.innerHTML = '';
|
343
|
+
document.getElementById('no-results').classList.remove('d-none');
|
344
|
+
return;
|
345
|
+
}
|
346
|
+
|
347
|
+
document.getElementById('no-results').classList.add('d-none');
|
348
|
+
|
349
|
+
// Generate HTML for job cards
|
350
|
+
container.innerHTML = jobs.map(job => `
|
351
|
+
<div class="col-md-6 col-lg-4 mb-4">
|
352
|
+
<div class="card h-100 shadow-sm job-card" data-job-id="${job.id}">
|
353
|
+
<div class="card-body">
|
354
|
+
<div class="d-flex justify-content-between">
|
355
|
+
<h5 class="card-title">${job.title}</h5>
|
356
|
+
</div>
|
357
|
+
<p class="text-muted mb-2"><i class="bi bi-geo-alt"></i> ${job.location || 'Remote'}</p>
|
358
|
+
<div class="mb-3">
|
359
|
+
<span class="badge bg-info me-1">${job.employment_type || 'Full-time'}</span>
|
360
|
+
<span class="badge bg-secondary">${job.experience_level || 'Not specified'}</span>
|
361
|
+
</div>
|
362
|
+
<h6 class="card-subtitle mb-2">Required Skills:</h6>
|
363
|
+
<div class="d-flex flex-wrap gap-1 mb-3">
|
364
|
+
${job.required_skills && job.required_skills.length > 0 ?
|
365
|
+
job.required_skills.slice(0, 3).map(skill =>
|
366
|
+
`<span class="badge bg-light text-dark job-tag">${skill}</span>`
|
367
|
+
).join('') + (job.required_skills.length > 3 ?
|
368
|
+
`<span class="badge bg-light text-dark job-tag">+${job.required_skills.length - 3} more</span>` : '') :
|
369
|
+
'<span class="text-muted small">No specific skills required</span>'
|
370
|
+
}
|
371
|
+
</div>
|
372
|
+
<div class="d-grid gap-2 mt-3">
|
373
|
+
<button class="btn btn-outline-primary view-job-btn" data-job-id="${job.id}">View Details</button>
|
374
|
+
</div>
|
375
|
+
</div>
|
376
|
+
<div class="card-footer text-muted">
|
377
|
+
<small>Posted: ${formatDate(job.created_at)}</small>
|
378
|
+
</div>
|
379
|
+
</div>
|
380
|
+
</div>
|
381
|
+
`).join('');
|
382
|
+
|
383
|
+
// Add click event to each job card
|
384
|
+
document.querySelectorAll('.job-card').forEach(card => {
|
385
|
+
card.addEventListener('click', function() {
|
386
|
+
const jobId = this.getAttribute('data-job-id');
|
387
|
+
openJobModal(jobId);
|
388
|
+
});
|
389
|
+
});
|
390
|
+
|
391
|
+
// Add click event to view details buttons
|
392
|
+
document.querySelectorAll('.view-job-btn').forEach(btn => {
|
393
|
+
btn.addEventListener('click', function(e) {
|
394
|
+
e.stopPropagation(); // Prevent the card click event from firing
|
395
|
+
const jobId = this.getAttribute('data-job-id');
|
396
|
+
openJobModal(jobId);
|
397
|
+
});
|
398
|
+
});
|
399
|
+
}
|
400
|
+
|
401
|
+
function filterJobs() {
|
402
|
+
const searchTerm = document.getElementById('search-input').value.toLowerCase();
|
403
|
+
const selectedLocation = document.getElementById('location-filter').value.toLowerCase();
|
404
|
+
|
405
|
+
const filteredJobs = allJobs.filter(job => {
|
406
|
+
// Search in title and description
|
407
|
+
const matchesSearch = !searchTerm ||
|
408
|
+
job.title.toLowerCase().includes(searchTerm) ||
|
409
|
+
job.content.toLowerCase().includes(searchTerm) ||
|
410
|
+
job.required_skills.some(skill => skill.toLowerCase().includes(searchTerm));
|
411
|
+
|
412
|
+
// Filter by location
|
413
|
+
const matchesLocation = !selectedLocation ||
|
414
|
+
(job.location && job.location.toLowerCase() === selectedLocation);
|
415
|
+
|
416
|
+
return matchesSearch && matchesLocation;
|
417
|
+
});
|
418
|
+
|
419
|
+
displayJobs(filteredJobs);
|
420
|
+
}
|
421
|
+
|
422
|
+
function openJobModal(jobId) {
|
423
|
+
// Find the job
|
424
|
+
const job = allJobs.find(j => j.id === jobId);
|
425
|
+
if (!job) return;
|
426
|
+
|
427
|
+
// Set modal content
|
428
|
+
document.getElementById('modal-job-title').textContent = job.title;
|
429
|
+
document.getElementById('modal-job-location').innerHTML = `<i class="bi bi-geo-alt"></i> ${job.location || 'Remote'}`;
|
430
|
+
document.getElementById('modal-job-type').textContent = job.employment_type || 'Full-time';
|
431
|
+
document.getElementById('modal-job-level').textContent = job.experience_level || 'Not specified';
|
432
|
+
document.getElementById('modal-job-id').textContent = `Job ID: ${job.id}`;
|
433
|
+
|
434
|
+
// Format and set job description with line breaks
|
435
|
+
document.getElementById('modal-job-description').innerHTML = job.content.replace(/\n/g, '<br>');
|
436
|
+
|
437
|
+
// Set required skills
|
438
|
+
const requiredSkillsContainer = document.getElementById('modal-required-skills');
|
439
|
+
requiredSkillsContainer.innerHTML = '';
|
440
|
+
if (job.required_skills && job.required_skills.length > 0) {
|
441
|
+
job.required_skills.forEach(skill => {
|
442
|
+
const badge = document.createElement('span');
|
443
|
+
badge.className = 'badge bg-light text-dark';
|
444
|
+
badge.textContent = skill;
|
445
|
+
requiredSkillsContainer.appendChild(badge);
|
446
|
+
});
|
447
|
+
} else {
|
448
|
+
requiredSkillsContainer.innerHTML = '<span class="text-muted">No specific skills required</span>';
|
449
|
+
}
|
450
|
+
|
451
|
+
// Set preferred skills
|
452
|
+
const preferredSkillsContainer = document.getElementById('modal-preferred-skills');
|
453
|
+
preferredSkillsContainer.innerHTML = '';
|
454
|
+
if (job.preferred_skills && job.preferred_skills.length > 0) {
|
455
|
+
job.preferred_skills.forEach(skill => {
|
456
|
+
const badge = document.createElement('span');
|
457
|
+
badge.className = 'badge bg-light text-dark';
|
458
|
+
badge.textContent = skill;
|
459
|
+
preferredSkillsContainer.appendChild(badge);
|
460
|
+
});
|
461
|
+
} else {
|
462
|
+
preferredSkillsContainer.innerHTML = '<span class="text-muted">No preferred skills specified</span>';
|
463
|
+
}
|
464
|
+
|
465
|
+
// Set job ID for apply button
|
466
|
+
document.getElementById('apply-button').setAttribute('data-job-id', job.id);
|
467
|
+
|
468
|
+
// Show modal
|
469
|
+
const modal = new bootstrap.Modal(document.getElementById('job-details-modal'));
|
470
|
+
modal.show();
|
471
|
+
}
|
472
|
+
|
473
|
+
// Utility function to format dates
|
474
|
+
function formatDate(dateString) {
|
475
|
+
if (!dateString) return 'Unknown';
|
476
|
+
const date = new Date(dateString);
|
477
|
+
return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' });
|
478
|
+
}
|
479
|
+
});
|
480
|
+
</script>
|
481
|
+
</body>
|
482
|
+
</html>
|