567 lines
21 KiB
HTML
567 lines
21 KiB
HTML
|
|
{% extends 'base.html' %}
|
|
{% load static i18n %}
|
|
{% block customCSS %}
|
|
<style>
|
|
/* -------------------------------------------------------------------------- */
|
|
/* KAAT-S Redesign CSS */
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
: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 */
|
|
}
|
|
|
|
/* ------------------ General Layout & Card Styles ------------------ */
|
|
|
|
.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 ------------------ */
|
|
|
|
.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;
|
|
}
|
|
|
|
/* ------------------ Status Badge Styles ------------------ */
|
|
|
|
.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;}
|
|
|
|
/* ------------------ Detail Row & Content Styles ------------------ */
|
|
|
|
.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">
|
|
|
|
<div class="card no-hover">
|
|
<div class="card-header">
|
|
<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>
|
|
<div class="col-auto">
|
|
<span class="status-badge bg-{{ meeting.status }}">
|
|
{{ meeting.status|title }}
|
|
</span>
|
|
</div>
|
|
{% if meeting.interview %}
|
|
<div class="col-auto">
|
|
<span class="status-badge">
|
|
Candidate Name : <a class="text-primary-theme" href="{% url 'candidate_detail' meeting.interview.candidate.slug %}">{{ meeting.interview.candidate.name }} </a>
|
|
</span>
|
|
</div>
|
|
{% endif %}
|
|
</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="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 join-info-card">
|
|
<h2>{% trans "Join Information" %}</h2>
|
|
<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>
|
|
|
|
{% 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="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="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 %}
|
|
<button type="button" class="btn btn-secondary" onclick="toggleGateway()">
|
|
<i class="fas fa-code"></i> {% trans "View API Response" %}
|
|
</button>
|
|
{% endif %}
|
|
<button type="button" class="btn btn-danger" title="{% trans 'Delete' %}"
|
|
data-bs-toggle="modal" data-bs-target="#deleteModal"
|
|
hx-post="{% url 'delete_meeting' meeting.slug %}"
|
|
hx-target="#deleteModalBody"
|
|
hx-swap="outerHTML"
|
|
data-item-name="{{ meeting.topic }}">
|
|
<i class="fas fa-trash-alt"></i>
|
|
Delete Meeting
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{% if meeting.zoom_gateway_response %}
|
|
<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>
|
|
{% endif %}
|
|
|
|
<!-- Comments Section -->
|
|
<div class="card no-hover" id="comments-card">
|
|
<div class="card-header text-primary-theme d-flex justify-content-between align-items-center">
|
|
<h5 class="card-title mb-0">
|
|
<i class="fas fa-comments me-2"></i>
|
|
Comments ({{ meeting.comments.count }})
|
|
</h5>
|
|
{% if user.is_authenticated %}
|
|
<button type="button" class="btn btn-primary btn-sm"
|
|
hx-get="{% url 'add_meeting_comment' meeting.slug %}"
|
|
hx-target="#comment-section"
|
|
>
|
|
<i class="fas fa-plus"></i> Add Comment
|
|
</button>
|
|
{% endif %}
|
|
</div>
|
|
<div class="card-body">
|
|
<div id="comment-section">
|
|
{% if meeting.comments.all %}
|
|
<div class="row">
|
|
{% for comment in meeting.comments.all|dictsortreversed:"created_at" %}
|
|
<div class="col-12 mb-3">
|
|
<div class="card ">
|
|
<div class="card-header d-flex justify-content-between align-items-start">
|
|
<div>
|
|
<strong>{{ comment.author.get_full_name|default:comment.author.username }}</strong>
|
|
{% if comment.author != user %}
|
|
<span class="badge bg-secondary ms-2">Comment</span>
|
|
{% endif %}
|
|
</div>
|
|
<small class="text-muted">{{ comment.created_at|date:"M d, Y P" }}</small>
|
|
</div>
|
|
<div class="card-body">
|
|
<p class="card-text">{{ comment.content|safe }}</p>
|
|
</div>
|
|
<div class="card-footer">
|
|
{% if comment.author == user or user.is_staff %}
|
|
<div class="btn-group btn-group-sm">
|
|
<button type="button" class="btn btn-outline-primary"
|
|
hx-get="{% url 'edit_meeting_comment' meeting.slug comment.id %}"
|
|
hx-target="#comment-section"
|
|
title="Edit Comment">
|
|
<i class="fas fa-edit"></i>
|
|
</button>
|
|
<button type="button" class="btn btn-outline-danger"
|
|
hx-get="{% url 'delete_meeting_comment' meeting.slug comment.id %}"
|
|
hx-target="#comment-section"
|
|
title="Delete Comment">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
<p class="text-muted">No comments yet. Be the first to comment!</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Comment Modal (for Add/Edit) -->
|
|
<div class="modal fade" id="commentModal" tabindex="-1" aria-labelledby="commentModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="commentModalLabel">Add Comment</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body" id="commentModalBody">
|
|
<!-- HTMX will load the form here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</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 %}
|