candidate history timeline

This commit is contained in:
Faheed 2025-10-14 15:55:49 +03:00
parent b9904b3ec8
commit 92013dd4f9
8 changed files with 665 additions and 268 deletions

View File

@ -1009,6 +1009,7 @@ def submit_form(request, template_id):
)
submission.applicant_email = email.display_value
submission.save()
time=timezone.now()
Candidate.objects.create(
first_name=first_name.display_value,
last_name=last_name.display_value,
@ -1017,6 +1018,7 @@ def submit_form(request, template_id):
address=address.display_value,
resume=resume.get_file if resume.is_file else None,
job=submission.template.job,
)
return redirect('application_success')

View File

@ -22,7 +22,18 @@
--stage-interview: #ffc107; /* Warning Yellow */
--stage-offer: #28a745; /* Success Green */
--stage-inactive: #6c757d; /* Secondary Gray */
}
--kaauh-teal: #00636e; /* Primary Theme / Active Stage */
--kaauh-teal-light: #4bb3be; /* For active glow */
--color-created: #6c757d; /* Muted Initial State */
--color-active: #00636e; /* Teal for Active Flow */
--color-posted: #17a2b8; /* Info Blue for External Posting */
--color-closed: #ffc107; /* Warning Yellow for Soft End/Review */
--color-archived: #343a40; /* Darkest for Final Storage */
--color-canceled: #dc3545; /* Red for Negative/Canceled */
--color-line-default: #e9ecef; /* Light Gray for all inactive markers */
}
/* Primary Color Overrides */
.text-primary { color: var(--kaauh-teal) !important; }
@ -160,6 +171,132 @@
border: 1px solid;
}
.job-timeline-container {
padding: 10px 0;
overflow-x: auto;
margin: 10px 0;
background-color: #f8f9fa;
border-radius: 0.5rem;
}
.job-timeline {
display: flex;
justify-content: space-between;
list-style: none;
padding: 0 10px;
margin: 0;
position: relative;
min-width: 1000px;
}
/* 🛑 NO CONNECTING LINE: Removed .job-timeline::before and .timeline-stage.is-complete + .timeline-stage::before */
/* ------------------ Stage & Marker Basics ------------------ */
.timeline-stage {
flex: 1 1 auto;
text-align: center;
position: relative;
z-index: 2;
padding-top: 50px;
}
.status-marker {
position: absolute;
top: 10px;
left: 50%;
transform: translateX(-50%);
width: 40px;
height: 40px;
border-radius: 50%;
/* DEFAULT (Inactive/Not Current Status) */
background-color: white;
border: 3px solid var(--color-line-default);
color: var(--color-line-default);
font-size: 18px;
line-height: 34px;
z-index: 3;
transition: all 0.3s ease, box-shadow 0.3s ease;
box-shadow: 0 0 0 2px white;
}
/* ------------------ ACTIVE/CURRENT Status Indicator ------------------ */
/* The 'is-active' class now completely defines the current status color and highlight */
.timeline-stage.is-active .status-marker {
transform: translateX(-50%) scale(1.15); /* Keep the pop effect */
color: white; /* Default text color for all active markers */
}
/* 1. CREATED (Active) */
.timeline-stage[data-stage="created"].is-active .status-marker {
background-color: var(--color-created);
border-color: var(--color-created);
box-shadow: 0 0 0 4px rgba(108, 117, 125, 0.5);
}
/* 2. MADE ACTIVE (Active) */
.timeline-stage[data-stage="active"].is-active .status-marker {
background-color: var(--color-active);
border-color: var(--color-active);
box-shadow: 0 0 0 4px var(--kaauh-teal-light);
}
/* 3. POSTED TO LINKEDIN (Active) */
.timeline-stage[data-stage="posted"].is-active .status-marker {
background-color: var(--color-posted);
border-color: var(--color-posted);
box-shadow: 0 0 0 4px rgba(23, 162, 184, 0.5);
}
/* 4. CANCELED (Active/Terminal) */
.timeline-stage[data-stage="canceled"].is-active .status-marker {
background-color: var(--color-canceled);
border-color: var(--color-canceled);
box-shadow: 0 0 0 4px rgba(220, 53, 69, 0.5);
}
/* 5. CLOSED (Active/Terminal) */
.timeline-stage[data-stage="closed"].is-active .status-marker {
background-color: var(--color-closed);
border-color: var(--color-closed);
color: #343a40; /* Dark icon for contrast on yellow */
box-shadow: 0 0 0 4px rgba(255, 193, 7, 0.5);
}
/* 6. ARCHIVED (Active/Terminal) */
.timeline-stage[data-stage="archived"].is-active .status-marker {
background-color: var(--color-archived);
border-color: var(--color-archived);
box-shadow: 0 0 0 4px rgba(52, 58, 64, 0.5);
}
/* ------------------ Text Labels ------------------ */
.status-label {
font-size: 0.85rem;
font-weight: 600;
color: #343a40;
margin-top: 5px;
}
/* Highlight text label for the active status */
.timeline-stage.is-active .status-label {
color: var(--kaauh-teal);
}
.status-date {
font-size: 0.75rem;
color: #6c757d;
}
</style>
{% endblock %}
@ -168,8 +305,50 @@
<div class="container-fluid py-4">
<div class="job-timeline-container shadow-sm">
<div class="card-body">
<ul class="job-timeline">
<li class="timeline-stage" data-stage="created">
<div class="status-marker"><i class="fas fa-hammer"></i></div>
<div class="status-label">Created</div>
<div class="status-date">Jan 1</div>
</li>
<li class="timeline-stage" data-stage="active">
<div class="status-marker"><i class="fas fa-play-circle"></i></div>
<div class="status-label">Made Active</div>
<div class="status-date">Jan 5</div>
</li>
<li class="timeline-stage" data-stage="posted">
<div class="status-marker"><i class="fab fa-linkedin"></i></div>
<div class="status-label">Posted to LinkedIn</div>
<div class="status-date">Jan 6</div>
</li>
<li class="timeline-stage is-active" data-stage="canceled">
<div class="status-marker"><i class="fas fa-times-circle"></i></div>
<div class="status-label">Canceled</div>
<div class="status-date">Jan 15</div>
</li>
<li class="timeline-stage" data-stage="closed">
<div class="status-marker"><i class="fas fa-lock"></i></div>
<div class="status-label">Closed</div>
<div class="status-date"></div>
</li>
<li class="timeline-stage" data-stage="archived">
<div class="status-marker"><i class="fas fa-archive"></i></div>
<div class="status-label">Archived</div>
<div class="status-date"></div>
</li>
</ul>
</div>
</div>
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'dashboard' %}" class="text-secondary">Home</a></li>

View File

@ -290,7 +290,7 @@
{% for job in jobs %}
<tr>
<td class="fw-medium text-primary-theme">
{{ job.title }}
<a href="{% url 'job_detail' job.slug %}" class="text-decoration-none">{{ job.title }}</a>
<br>
<small class="text-muted">{{ job.pk }} / </small>
<span class="badge bg-{{ job.status }} status-badge">{{ job.status }}</span>
@ -345,7 +345,7 @@
<div class="card shadow-sm">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-2">
<h5 class="card-title mb-0">{{ job.title }}</h5>
<h5 class="card-title mb-0"><a href="{% url 'job_detail' job.slug %}" class="text-decoration-none text-primary-theme">{{ job.title }}</a></h5>
<span class="badge bg-{{ job.status }} status-badge">{{ job.status }}</span>
</div>
<p class="text-muted small mb-3">ID: {{ job.pk }} | Source: {{ job.get_source }}</p>

View File

@ -1,306 +1,476 @@
{% extends "base.html" %}
{% extends 'base.html' %}
{% load static i18n %}
{% block title %}{% trans "Meeting Details" %} - {{ block.super }}{% endblock %}
{% block customCSS %}
<style>
/* UI Variables for the KAAT-S Theme (Consistent with list_meetings.html) */
:root {
--kaauh-teal: #00636e;
--kaauh-teal-dark: #004a53;
--kaauh-border: #eaeff3;
--kaauh-primary-text: #343a40;
--kaauh-gray-light: #f8f9fa;
}
/* -------------------------------------------------------------------------- */
/* KAAT-S Redesign CSS */
/* -------------------------------------------------------------------------- */
/* Main Container and Card Styling */
.container {
padding: 2rem 1rem;
}
.card {
border: 1px solid var(--kaauh-border);
border-radius: 0.75rem;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
background-color: white;
margin-bottom: 2rem;
}
.card:not(.no-hover):hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(0,0,0,0.1);
transition: transform 0.2s, box-shadow 0.2s;
}
.card.no-hover:hover {
transform: none;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
}
:root {
--kaauh-teal: #00636e; /* Primary Brand Teal */
--kaauh-teal-dark: #004a53; /* Darker Teal for Text/Hover */
--kaauh-teal-light: #e0f7f9; /* Lightest Teal for background accents */
--kaauh-border: #e9ecef; /* Soft Border Gray */
--kaauh-primary-text: #212529; /* Dark Text */
--kaauh-secondary-text: #6c757d;/* Muted Text */
--kaauh-gray-light: #f8f9fa; /* Card Header/Footer Background */
--kaauh-success: #198754; /* Success Green */
--kaauh-danger: #dc3545; /* Danger Red */
}
body {
background-color: #f0f2f5; /* Off-white page background */
font-family: 'Inter', sans-serif; /* Use a modern font stack */
}
/* Header Card Styling */
.card-header {
background-color: var(--kaauh-gray-light);
border-bottom: 1px solid var(--kaauh-border);
padding: 1.5rem;
border-radius: 0.75rem 0.75rem 0 0; /* Match top border radius of card */
}
.card-header h1 {
color: var(--kaauh-teal-dark);
font-weight: 700;
margin: 0 0 0.5rem 0; /* Space below title */
display: flex;
align-items: center;
gap: 0.75rem;
}
.card-header .heroicon {
width: 1.75rem;
height: 1.75rem;
color: var(--kaauh-teal);
}
.card-header .btn-secondary {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
font-weight: 600;
transition: all 0.2s ease;
}
.card-header .btn-secondary:hover {
background-color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal-dark);
color: white;
}
/* ------------------ General Layout & Card Styles ------------------ */
/* Status Badge Styling (from list_meetings.html) */
.status-badge {
font-size: 0.8rem;
padding: 0.4em 0.8em;
border-radius: 0.4rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.7px;
}
.bg-waiting { background-color: #ffc107 !important; color: var(--kaauh-primary-text) !important;}
.bg-started { background-color: var(--kaauh-teal) !important; color: white !important;}
.bg-ended { background-color: #dc3545 !important; color: white !important;}
.container {
width:auto;
padding: 3rem 1.5rem;
}
.card {
border: none; /* Remove default border */
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0,0,0,0.08), 0 4px 10px rgba(0,0,0,0.05); /* Deep, soft shadow */
background-color: white;
margin-bottom: 2.5rem;
transition: all 0.3s ease;
}
.card:not(.no-hover):hover {
transform: translateY(-3px);
box-shadow: 0 15px 40px rgba(0,0,0,0.1), 0 6px 15px rgba(0,0,0,0.08);
}
.card.no-hover:hover {
transform: none;
box-shadow: 0 10px 30px rgba(0,0,0,0.08), 0 4px 10px rgba(0,0,0,0.05);
}
.card-body {
padding: 2rem;
}
/* ------------------ Header & Title Styles ------------------ */
/* Detail Row Styling */
.detail-row {
display: flex;
justify-content: space-between;
padding: 0.75rem 1.5rem;
border-bottom: 1px solid var(--kaauh-border);
}
.detail-row:last-child {
border-bottom: none;
}
.detail-label {
font-weight: 600;
color: var(--kaauh-teal-dark);
min-width: 40%; /* Ensure labels align */
}
.detail-value {
text-align: right;
color: var(--kaauh-primary-text);
word-wrap: break-word; /* Long URLs or text wrap */
max-width: 60%; /* Ensure values align */
}
.card-header {
background-color: var(--kaauh-gray-light);
border-bottom: 1px solid var(--kaauh-border);
padding: 2rem;
border-radius: 12px 12px 0 0;
display: flex;
justify-content: space-between;
align-items: flex-start; /* Align title group to the top */
flex-wrap: wrap;
}
.card-header-title-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.card-header h1 {
color: var(--kaauh-teal-dark);
font-weight: 800; /* Extra bold for prominence */
margin: 0;
display: flex;
align-items: center;
gap: 1rem;
font-size: 2.5rem;
}
.card-header .heroicon {
width: 2.5rem;
height: 2.5rem;
color: var(--kaauh-teal);
}
.card-header .btn-secondary-back {
/* Subtle Back Button */
align-self: flex-start;
background-color: transparent;
border: none;
color: var(--kaauh-secondary-text);
font-weight: 600;
font-size: 1rem;
padding: 0.5rem 0.75rem;
transition: color 0.2s;
}
.card-header .btn-secondary-back:hover {
color: var(--kaauh-teal);
text-decoration: underline;
}
/* Card Title Styling within content cards */
.card h2 {
color: var(--kaauh-teal-dark);
font-weight: 600;
padding: 1.5rem 1.5rem 0.75rem;
margin: 0;
border-bottom: 1px solid var(--kaauh-border);
}
/* ------------------ Status Badge Styles ------------------ */
/* Join URL Styling */
.join-url-display {
background-color: #f8f9fa;
border: 1px solid var(--kaauh-border);
border-radius: 0.5rem;
padding: 0.75rem 1rem;
margin-top: 0.75rem;
word-break: break-all; /* Force long URLs to break */
}
.status-badge {
font-size: 0.85rem;
padding: 0.5em 1em;
border-radius: 20px; /* Pill shape */
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-top: 0.5rem;
}
.bg-waiting { background-color: #ffc107 !important; color: var(--kaauh-primary-text) !important;}
.bg-started { background-color: var(--kaauh-teal) !important; color: white !important;}
.bg-ended { background-color: var(--kaauh-danger) !important; color: white !important;}
/* Actions Styling */
.actions {
padding: 1.5rem;
display: flex;
flex-wrap: wrap;
gap: 1rem; /* Space between buttons */
justify-content: flex-start; /* Align buttons to the left */
}
.btn-primary {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
font-weight: 600;
transition: all 0.2s ease;
}
.btn-primary:hover {
background-color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal-dark);
color: white;
}
.btn-danger {
background-color: #dc3545;
border-color: #dc3545;
color: white;
font-weight: 600;
transition: all 0.2s ease;
}
.btn-danger:hover {
background-color: #c82333;
border-color: #bd2130;
color: white;
}
.btn-secondary {
background-color: #6c757d;
border-color: #6c757d;
color: white;
font-weight: 600;
transition: all 0.2s ease;
}
.btn-secondary:hover {
background-color: #5a6268;
border-color: #545b62;
color: white;
}
/* ------------------ Detail Row & Content Styles ------------------ */
/* API Response Styling */
#gateway-response {
margin-top: 2rem;
}
#gateway-response .card {
border-left: 4px solid var(--kaauh-teal); /* Indicate it's different content */
}
#gateway-response pre {
background-color: #f8f9fa;
border: 1px solid var(--kaauh-border);
border-radius: 0.5rem;
padding: 1rem;
margin-top: 0.75rem;
overflow-x: auto; /* For long JSON lines */
font-size: 0.9rem;
color: var(--kaauh-primary-text);
}
.card h2 {
color: var(--kaauh-teal-dark);
font-weight: 700;
padding: 1.5rem 2rem 1rem;
margin: 0;
font-size: 1.5rem;
border-bottom: 1px solid var(--kaauh-border);
}
.detail-row-group {
padding: 0;
}
.detail-row {
display: grid;
grid-template-columns: minmax(150px, 40%) 1fr;
padding: 1rem 2rem;
border-bottom: 1px solid var(--kaauh-border);
align-items: center;
}
.detail-row:last-child {
border-bottom: none;
}
.detail-label {
font-weight: 600;
color: var(--kaauh-teal-dark);
text-align: left;
font-size: 0.95rem;
}
.detail-value {
text-align: right;
color: var(--kaauh-primary-text);
word-wrap: break-word;
font-weight: 500;
}
/* ------------------ Join Info & Copy Button ------------------ */
.join-info-card .card-body {
padding-top: 2rem;
}
.btn-primary {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
font-weight: 600;
padding: 0.75rem 1.5rem;
border-radius: 8px;
transition: all 0.2s ease;
}
.btn-primary:hover {
background-color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal-dark);
box-shadow: 0 4px 8px rgba(0, 99, 110, 0.3);
}
.join-url-container {
display: flex;
gap: 1rem;
align-items: center;
margin-top: 1.5rem;
position: relative;
padding: 1rem 0; /* Add padding for clear space around the copy area */
}
.join-url-display {
flex-grow: 1;
background-color: var(--kaauh-gray-light);
border: 1px solid var(--kaauh-border);
border-radius: 8px;
padding: 0.75rem 1rem;
word-break: break-all;
font-size: 0.9rem;
color: var(--kaauh-secondary-text);
font-family: monospace; /* Monospace for links/code */
}
.join-url-display strong {
color: var(--kaauh-teal-dark);
font-family: 'Inter', sans-serif;
}
.btn-copy {
flex-shrink: 0;
background-color: var(--kaauh-teal-dark); /* Darker teal for a clean utility look */
border: none;
color: white;
padding: 0.75rem 1rem;
border-radius: 8px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
}
.btn-copy:hover {
background-color: var(--kaauh-teal);
}
.btn-copy i {
margin-right: 0.25rem;
}
/* 🎯 Copy Message Pill Style */
#copy-message {
position: absolute;
top: -5px;
right: 0;
background-color: var(--kaauh-success);
color: white;
padding: 0.2rem 0.6rem;
border-radius: 20px; /* Pill shape */
opacity: 0;
transition: opacity 0.4s ease-in-out;
z-index: 10;
font-size: 0.75rem;
font-weight: 600;
letter-spacing: 0.5px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
/* ------------------ Footer & Actions ------------------ */
.card-footer {
border-top: 1px solid var(--kaauh-border);
padding: 1.5rem 2rem;
display: flex;
flex-wrap: wrap;
gap: 1rem;
justify-content: flex-start;
background-color: var(--kaauh-gray-light);
border-radius: 0 0 12px 12px;
}
.btn-danger {
background-color: var(--kaauh-danger);
border-color: var(--kaauh-danger);
color: white;
font-weight: 600;
padding: 0.75rem 1.5rem;
border-radius: 8px;
transition: all 0.2s ease;
}
.btn-danger:hover {
background-color: #c82333;
border-color: #bd2130;
box-shadow: 0 4px 8px rgba(220, 53, 69, 0.3);
}
.btn-secondary {
background-color: #6c757d;
border-color: #6c757d;
color: white;
font-weight: 600;
padding: 0.75rem 1.5rem;
border-radius: 8px;
transition: all 0.2s ease;
}
.btn-secondary:hover {
background-color: #5a6268;
border-color: #545b62;
}
/* ------------------ API Response Styling ------------------ */
#gateway-response-card {
border-left: 5px solid var(--kaauh-teal); /* Prominent left border */
}
#gateway-response-card .card-body {
padding: 1.5rem;
}
#gateway-response-card h3 {
color: var(--kaauh-teal-dark);
font-weight: 700;
font-size: 1.35rem;
margin-bottom: 1rem;
}
#gateway-response-card pre {
background-color: #fff;
border: 1px solid var(--kaauh-border);
border-radius: 8px;
padding: 1rem;
font-size: 0.8rem;
color: var(--kaauh-primary-text);
white-space: pre-wrap;
word-wrap: break-word;
}
</style>
{% endblock %}
{% block content %}
<div class="container py-4">
<div class="container">
<div class="card no-hover">
<div class="card-header">
<h1>
<svg class="heroicon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
<path 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-7z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
{{ meeting.topic }}
</h1>
<span class="status-badge bg-{{ meeting.status }}">
{{ meeting.status|title }}
</span>
<a href="{% url 'list_meetings' %}" class="btn btn-secondary mt-3">{% trans "Back to Meetings" %}</a>
<div class="card-header-title-group">
<h1>
<svg class="heroicon" viewBox="0 0 24 24" fill="none" xmlns="[http://www.w3.org/2000/svg](http://www.w3.org/2000/svg)">
<path d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
<path 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-7z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
{{ meeting.topic }}
</h1>
<span class="status-badge bg-{{ meeting.status }}">
{{ meeting.status|title }}
</span>
</div>
<a href="{% url 'list_meetings' %}" class="btn btn-secondary-back">
<i class="fas fa-arrow-left"></i> {% trans "Back to Meetings" %}
</a>
</div>
</div>
<div class="card no-hover">
<h2>{% trans "Meeting Information" %}</h2>
<div class="detail-row">
<div class="detail-label">{% trans "Meeting ID" %}:</div>
<div class="detail-value">{{ meeting.meeting_id }}</div>
</div>
<div class="detail-row">
<div class="detail-label">{% trans "Topic" %}:</div>
<div class="detail-value">{{ meeting.topic }}</div>
</div>
<div class="detail-row">
<div class="detail-label">{% trans "Start Time" %}:</div>
<div class="detail-value">{{ meeting.start_time|date:"M d, Y H:i" }}</div>
</div>
<div class="detail-row">
<div class="detail-label">{% trans "Duration" %}:</div>
<div class="detail-value">{{ meeting.duration }} {% trans "minutes" %}</div>
</div>
<div class="detail-row">
<div class="detail-label">{% trans "Timezone" %}:</div>
<div class="detail-value">{{ meeting.timezone|default:"UTC" }}</div>
</div>
<div class="detail-row">
<div class="detail-label">{% trans "Host Email" %}:</div>
<div class="detail-value">{{ meeting.host_email|default:"N/A" }}</div>
<div class="card-body detail-row-group">
<div class="detail-row"><div class="detail-label">{% trans "Meeting ID" %}:</div><div class="detail-value">{{ meeting.meeting_id }}</div></div>
<div class="detail-row"><div class="detail-label">{% trans "Topic" %}:</div><div class="detail-value">{{ meeting.topic }}</div></div>
<div class="detail-row"><div class="detail-label">{% trans "Start Time" %}:</div><div class="detail-value">{{ meeting.start_time|date:"M d, Y H:i" }}</div></div>
<div class="detail-row"><div class="detail-label">{% trans "Duration" %}:</div><div class="detail-value">{{ meeting.duration }} {% trans "minutes" %}</div></div>
<div class="detail-row"><div class="detail-label">{% trans "Timezone" %}:</div><div class="detail-value">{{ meeting.timezone|default:"UTC" }}</div></div>
<div class="detail-row"><div class="detail-label">{% trans "Host Email" %}:</div><div class="detail-value">{{ meeting.host_email|default:"N/A" }}</div></div>
</div>
</div>
{% if meeting.join_url %}
<div class="card no-hover">
<div class="card no-hover join-info-card">
<h2>{% trans "Join Information" %}</h2>
<a href="{{ meeting.join_url }}" class="btn btn-primary" target="_blank">{% trans "Join Meeting" %}</a>
{% comment %} <div class="join-url-display">
<strong>{% trans "Join URL:" %}</strong> {{ meeting.join_url }}
</div> {% endcomment %}
{% if meeting.password %}
<div class="detail-row">
<div class="detail-label">{% trans "Password" %}: {{ meeting.password }}</div>
<div class="detail-value"></div>
<div class="card-body">
<a href="{{ meeting.join_url }}" class="btn btn-primary" target="_blank">
<i class="fas fa-video"></i> {% trans "Join Meeting Now" %}
</a>
<div class="join-url-container">
<div id="copy-message" style="opacity: 0;">{% trans "Copied!" %}</div>
<div class="join-url-display" id="join-url-display">
<strong>{% trans "Join URL" %}:</strong> <span id="meeting-join-url">{{ meeting.join_url }}</span>
</div>
<button class="btn-copy" onclick="copyLink()">
<i class="fas fa-copy"></i>
</button>
</div>
{% endif %}
{% if meeting.password %}
<div class="detail-row" style="border: none; padding: 1rem 0 0 0;">
<div class="detail-label" style="font-size: 1rem;">{% trans "Password" %}:</div>
<div class="detail-value" style="font-weight: 700; color: var(--kaauh-danger);">{{ meeting.password }}</div>
</div>
{% endif %}
</div>
</div>
{% endif %}
<div class="card no-hover">
<h2>{% trans "Settings" %}</h2>
<div class="detail-row">
<div class="detail-label">{% trans "Participant Video" %}:</div>
<div class="detail-value">{{ meeting.participant_video|yesno:"Yes,No" }}</div>
</div>
<div class="detail-row">
<div class="detail-label">{% trans "Join Before Host" %}:</div>
<div class="detail-value">{{ meeting.join_before_host|yesno:"Yes,No" }}</div>
</div>
<div class="detail-row">
<div class="detail-label">{% trans "Mute Upon Entry" %}:</div>
<div class="detail-value">{{ meeting.mute_upon_entry|yesno:"Yes,No" }}</div>
</div>
<div class="detail-row">
<div class="detail-label">{% trans "Waiting Room" %}:</div>
<div class="detail-value">{{ meeting.waiting_room|yesno:"Yes,No" }}</div>
<div class="card-body detail-row-group">
<div class="detail-row"><div class="detail-label">{% trans "Participant Video" %}:</div><div class="detail-value">{{ meeting.participant_video|yesno:"Yes,No" }}</div></div>
<div class="detail-row"><div class="detail-label">{% trans "Join Before Host" %}:</div><div class="detail-value">{{ meeting.join_before_host|yesno:"Yes,No" }}</div></div>
<div class="detail-row"><div class="detail-label">{% trans "Mute Upon Entry" %}:</div><div class="detail-value">{{ meeting.mute_upon_entry|yesno:"Yes,No" }}</div></div>
<div class="detail-row"><div class="detail-label">{% trans "Waiting Room" %}:</div><div class="detail-value">{{ meeting.waiting_room|yesno:"Yes,No" }}</div></div>
</div>
</div>
<div class="card no-hover">
<div class="actions">
<div class="card-footer">
<a href="{% url 'update_meeting' meeting.slug %}" class="btn btn-primary">
<i class="fas fa-edit"></i> {% trans "Update Meeting" %}
</a>
{% if meeting.zoom_gateway_response %}
<a href="#" class="btn btn-secondary" onclick="toggleGateway()">{% trans "View API Response" %}</a>
<button type="button" class="btn btn-secondary" onclick="toggleGateway()">
<i class="fas fa-code"></i> {% trans "View API Response" %}
</button>
{% endif %}
<a href="{% url 'update_meeting' meeting.slug %}" class="btn btn-primary">{% trans "Update Meeting" %}</a>
<form method="post" action="{% url 'delete_meeting' meeting.pk %}" style="display: inline;">
{% csrf_token %}
<button type="submit" class="btn btn-danger" onclick="return confirm('{% trans "Are you sure?" %}')">{% trans "Delete Meeting" %}</button>
<button type="submit" class="btn btn-danger" onclick="return confirm('{% trans "Are you sure you want to delete this meeting? This action is permanent." %}')">
<i class="fas fa-trash-alt"></i> {% trans "Delete Meeting" %}
</button>
</form>
</div>
</div>
{% if meeting.zoom_gateway_response %}
{% comment %} <div id="gateway-response">
<div class="card">
<h3>{% trans "Zoom API Response" %}</h3>
<div id="gateway-response-card" class="card" style="display: none;">
<div class="card-body">
<h3>{% trans "API Gateway Response" %}</h3>
<pre>{{ meeting.zoom_gateway_response|safe }}</pre>
</div>
</div> {% endcomment %}
<script>
function toggleGateway() {
const element = document.getElementById('gateway-response');
if (element.style.display === 'none' || !element.style.display) {
element.style.display = 'block';
} else {
element.style.display = 'none';
}
}
</script>
</div>
{% endif %}
</div>
{% endblock %}
{% block customJS %}
<script>
function toggleGateway() {
const element = document.getElementById('gateway-response-card');
if (element.style.display === 'none' || element.style.display === '') {
element.style.display = 'block';
} else {
element.style.display = 'none';
}
}
function copyLink() {
const urlElement = document.getElementById('meeting-join-url');
const messageElement = document.getElementById('copy-message');
const textToCopy = urlElement.textContent || urlElement.innerText;
// Clear any existing message
clearTimeout(window.copyMessageTimeout);
// Function to show the message
function showMessage(success) {
messageElement.textContent = success ? '{% trans "Copied!" %}' : '{% trans "Copy Failed." %}';
messageElement.style.backgroundColor = success ? 'var(--kaauh-success)' : 'var(--kaauh-danger)';
messageElement.style.opacity = '1';
// Hide the message after 2 seconds
window.copyMessageTimeout = setTimeout(() => {
messageElement.style.opacity = '0';
}, 2000);
}
// Use the modern clipboard API
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(textToCopy).then(() => {
showMessage(true); // Show success message
}).catch(err => {
console.error('Could not copy text: ', err);
fallbackCopyTextToClipboard(textToCopy, showMessage); // Try fallback on failure
});
} else {
// Fallback for older browsers
fallbackCopyTextToClipboard(textToCopy, showMessage);
}
}
// Fallback function for older browsers
function fallbackCopyTextToClipboard(text, callback) {
const textArea = document.createElement("textarea");
textArea.value = text;
textArea.style.top = "0";
textArea.style.left = "0";
textArea.style.position = "fixed";
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
let success = false;
try {
success = document.execCommand('copy');
} catch (err) {
console.error('Fallback: Oops, unable to copy', err);
}
document.body.removeChild(textArea);
callback(success); // Call the message function with the result
}
</script>
{% endblock %}

View File

@ -369,14 +369,53 @@
<i class="far fa-clock ms-2 me-1"></i> {{ candidate.created_at|date:"h:i A" }}
</small>
</div>
</div>
{% if candidate.exam_date %}
<div class="timeline-item">
<div class="timeline-icon timeline-bg-applied"><i class="fas fa-clipboard-check"></i></div>
<div class="timeline-content">
<p class="timeline-stage fw-bold mb-0 ms-2">{% trans "Exam" %}</p>
<small class="text-muted">
<i class="far fa-calendar-alt me-1"></i> {{ candidate.exam_date|date:"M d, Y" }}
<span class="ms-2">|</span>
<i class="far fa-clock ms-2 me-1"></i> {{ candidate.exam_date|date:"h:i A" }}
</small>
</div>
</div>
{# Fallback if no history is explicitly logged #}
{% if not candidate_stage_history %}
<p class="text-muted mt-3 mb-0 ps-3">
{% trans "Detailed stage history logs are currently unavailable." %}
</p>
{% endif %}
{% if candidate.interview_date %}
<div class="timeline-item">
<div class="timeline-icon timeline-bg-applied"><i class="fas fas fa-comments"></i></div>
<div class="timeline-content">
<p class="timeline-stage fw-bold mb-0 ms-2">{% trans "Interview" %}</p>
<small class="text-muted">
<i class="far fa-calendar-alt me-1"></i> {{ candidate.interview_date|date:"M d, Y" }}
<span class="ms-2">|</span>
<i class="far fa-clock ms-2 me-1"></i> {{ candidate.interview_date|date:"h:i A" }}
</small>
</div>
</div>
{% endif %}
{% if candidate.offer_date %}
<div class="timeline-item">
<div class="timeline-icon timeline-bg-applied"><i class="fas fas fa-handshake"></i></div>
<div class="timeline-content">
<p class="timeline-stage fw-bold mb-0 ms-2">{% trans "Offer" %}</p>
<small class="text-muted">
<i class="far fa-calendar-alt me-1"></i> {{ candidate.offer_date|date:"M d, Y" }}
<span class="ms-2">|</span>
<i class="far fa-clock ms-2 me-1"></i> {{ candidate.offer_date|date:"h:i A" }}
</small>
</div>
</div>
{% endif %}
</div>

View File

@ -14,6 +14,13 @@
--kaauh-gray-light: #f8f9fa; /* Added for hover/background consistency */
}
/* Primary Color Overrides */
.text-primary-theme { color: var(--kaauh-teal) !important; }
.bg-primary-theme { background-color: var(--kaauh-teal) !important; }
.text-success { color: var(--kaauh-success) !important; }
.text-danger { color: var(--kaauh-danger) !important; }
.text-info { color: #17a2b8 !important; }
/* Enhanced Card Styling (Consistent) */
.card {
border: 1px solid var(--kaauh-border);
@ -208,10 +215,10 @@
<tbody>
{% for candidate in candidates %}
<tr>
<td class="fw-medium">{{ candidate.name }}</td>
<td class="fw-medium"><a href="{% url 'candidate_detail' candidate.slug %}" class="text-decoration-none link-secondary">{{ candidate.name }}<a></td>
<td>{{ candidate.email }}</td>
<td>{{ candidate.phone }}</td>
<td> <span class="badge bg-primary">{{ candidate.job.title }}</span></td>
<td> <span class="badge bg-primary"><a href="{% url 'job_detail' candidate.job.slug %}" class="text-decoration-none text-white">{{ candidate.job.title }}</a></span></td>
<td>
<span class="badge bg-primary">
{{ candidate.stage }}
@ -250,14 +257,14 @@
<div class="card candidate-card h-100 shadow-sm">
<div class="card-body d-flex flex-column">
<div class="d-flex justify-content-between align-items-start mb-2">
<h5 class="card-title flex-grow-1 me-3">{{ candidate.name }}</h5>
<h5 class="card-title flex-grow-1 me-3"><a href="{% url 'candidate_detail' candidate.slug %}" class="text-decoration-none text-primary-theme ">{{ candidate.name }}</a></h5>
<span class="badge bg-primary">{{ candidate.stage }}</span>
</div>
<p class="card-text text-muted small">
<i class="fas fa-envelope"></i> {{ candidate.email }}<br>
<i class="fas fa-phone-alt"></i> {{ candidate.phone|default:"N/A" }}<br>
<i class="fas fa-briefcase"></i> <span class="badge bg-primary">{{ candidate.job.title }}</span>
<i class="fas fa-briefcase"></i> <span class="badge bg-primary"><a href="{% url 'job_detail' candidate.job.slug %}" class="text-decoration-none text-white">{{ candidate.job.title }}</a></span>
</p>
<div class="mt-auto pt-2 border-top">