<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Confirm Meeting - Trusty Personal Assistant</title>
<link rel="icon" href="/booking/static/favicon.ico">
<style>
:root {
/* Tinexta InfoCert Official Colors */
--color-primary: #0078D4;
--color-primary-dark: #003D6F;
--color-primary-light: #1E88E5;
--color-success: #00c851;
--color-warning: #ff9800;
--color-error: #d32f2f;
--color-bg: #f5f5f5;
--color-card: #ffffff;
--color-text: #333333;
--color-text-light: #666666;
--color-border: #e0e0e0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Helvetica Neue', Arial, sans-serif;
background: var(--color-bg);
color: var(--color-text);
line-height: 1.6;
padding: 20px;
}
.container {
max-width: 700px;
margin: 0 auto;
}
.header {
background: linear-gradient(135deg, #0072ce 0%, #005a9e 100%);
color: white;
padding: 30px;
border-radius: 8px 8px 0 0;
}
.header-logos {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.header-logos img {
height: 35px;
}
.header-logos img:first-child {
height: 35px;
}
.header-logos img:last-child {
height: 40px;
}
.header-content {
text-align: center;
}
.header h1 {
font-size: 24px;
font-weight: 600;
margin-bottom: 5px;
}
.header p {
font-size: 14px;
opacity: 0.9;
}
.card {
background: var(--color-card);
padding: 30px;
border-radius: 0 0 8px 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.meeting-info {
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 2px solid var(--color-border);
}
.meeting-info h2 {
font-size: 20px;
color: var(--color-primary-dark);
margin-bottom: 15px;
}
.info-row {
display: flex;
margin-bottom: 10px;
font-size: 14px;
}
.info-label {
font-weight: 600;
width: 120px;
color: var(--color-text-light);
}
.info-value {
flex: 1;
color: var(--color-text);
}
.attendees-list {
list-style: none;
margin-top: 5px;
}
.attendees-list li {
padding: 3px 0;
}
.attendees-list li:before {
content: "• ";
color: var(--color-primary);
font-weight: bold;
}
.slots-section {
margin-bottom: 30px;
}
.slots-section h3 {
font-size: 18px;
color: var(--color-primary-dark);
margin-bottom: 15px;
}
.scenario-message {
padding: 15px;
border-radius: 6px;
margin-bottom: 20px;
font-size: 14px;
}
.scenario-message.info {
background: #e3f2fd;
color: #1565c0;
border-left: 4px solid var(--color-primary);
}
.scenario-message.warning {
background: #fff3e0;
color: #e65100;
border-left: 4px solid var(--color-warning);
}
.scenario-message.error {
background: #ffebee;
color: #c62828;
border-left: 4px solid var(--color-error);
}
.slot-option {
border: 2px solid var(--color-border);
border-radius: 8px;
padding: 20px;
margin-bottom: 15px;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
}
.slot-option:hover {
border-color: var(--color-primary-light);
box-shadow: 0 4px 12px rgba(0,120,212,0.15);
}
.slot-option.selected {
border-color: var(--color-primary);
background: #f0f8ff;
box-shadow: 0 4px 16px rgba(0,120,212,0.25);
}
.slot-option.unavailable {
opacity: 0.5;
cursor: not-allowed;
background: #fafafa;
}
.slot-option.unavailable:hover {
border-color: var(--color-border);
box-shadow: none;
}
.slot-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
}
.slot-date {
font-size: 18px;
font-weight: 600;
color: var(--color-primary-dark);
}
.slot-time {
font-size: 16px;
color: var(--color-text-light);
}
.slot-status {
padding: 4px 12px;
border-radius: 12px;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
}
.slot-status.available {
background: #e8f5e9;
color: #2e7d32;
}
.slot-status.unavailable {
background: #ffebee;
color: #c62828;
}
.actions {
display: flex;
gap: 15px;
margin-top: 30px;
}
.btn {
flex: 1;
padding: 14px 24px;
border: none;
border-radius: 6px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
text-align: center;
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn-primary {
background: var(--color-primary-light);
color: white;
}
.btn-primary:hover:not(:disabled) {
background: var(--color-primary);
box-shadow: 0 4px 12px rgba(0,120,212,0.3);
}
.btn-secondary {
background: white;
color: var(--color-primary);
border: 2px solid var(--color-primary);
}
.btn-secondary:hover:not(:disabled) {
background: var(--color-primary);
color: white;
}
.loading {
display: none;
text-align: center;
padding: 20px;
}
.loading.active {
display: block;
}
.spinner {
border: 4px solid var(--color-border);
border-top: 4px solid var(--color-primary);
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 0 auto 10px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.footer {
text-align: center;
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid var(--color-border);
color: var(--color-text-light);
font-size: 12px;
}
.footer strong {
color: var(--color-primary);
}
/* Mobile responsive */
@media (max-width: 600px) {
body {
padding: 10px;
}
.header, .card {
padding: 20px;
}
.header h1 {
font-size: 20px;
}
.actions {
flex-direction: column;
}
.btn {
width: 100%;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<div class="header-logos">
<img src="/booking/static/tinexta-logo-white.svg" alt="Tinexta InfoCert" />
<img src="/booking/static/trusty-logo.svg" alt="Trusty Personal Assistant" />
</div>
<div class="header-content">
<h1>Confirm Meeting</h1>
<p>Select your preferred time</p>
</div>
</div>
<div class="card">
<div class="meeting-info">
<h2>{{ session.meeting_subject }}</h2>
<div class="info-row">
<div class="info-label">Organizer:</div>
<div class="info-value">{{ session.organizer_name }}</div>
</div>
<div class="info-row">
<div class="info-label">Attendees:</div>
<div class="info-value">
<ul class="attendees-list">
{% for attendee in session.internal_attendees %}
<li>{{ attendee.name }}</li>
{% endfor %}
</ul>
</div>
</div>
<div class="info-row">
<div class="info-label">Duration:</div>
<div class="info-value">{{ session.meeting_duration }} minutes</div>
</div>
</div>
<div class="slots-section">
<h3>Available Times</h3>
{% if scenario == 'all_available' %}
<div class="scenario-message info">
✓ All times are available. Select your preferred one!
</div>
{% elif scenario == 'some_available' %}
<div class="scenario-message warning">
⚠ Some times are no longer available. Select from the available ones.
</div>
{% else %}
<div class="scenario-message error">
✗ All times have been booked. Click "Find New Times" to see alternatives.
</div>
{% endif %}
<div id="slots-container">
{% for slot in slots %}
<div class="slot-option {% if not slot.available %}unavailable{% endif %}"
data-slot-index="{{ slot.index }}"
{% if slot.available %}onclick="selectSlot({{ slot.index }})"{% endif %}>
<div class="slot-header">
<div>
<div class="slot-date" id="slot-date-{{ slot.index }}">Loading...</div>
<div class="slot-time" id="slot-time-{{ slot.index }}">Loading...</div>
</div>
<div class="slot-status {% if slot.available %}available{% else %}unavailable{% endif %}">
{% if slot.available %}Available{% else %}Unavailable{% endif %}
</div>
</div>
</div>
<script>
// Format slot {{ slot.index }}
(function() {
const start = new Date("{{ slot.start }}");
const end = new Date("{{ slot.end }}");
const options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };
const dateStr = start.toLocaleDateString('en-US', options);
const timeStr = start.toLocaleTimeString('en-US', {hour: '2-digit', minute: '2-digit', hour12: false}) +
' - ' +
end.toLocaleTimeString('en-US', {hour: '2-digit', minute: '2-digit', hour12: false});
document.getElementById('slot-date-{{ slot.index }}').textContent = dateStr;
document.getElementById('slot-time-{{ slot.index }}').textContent = timeStr;
})();
</script>
{% endfor %}
</div>
</div>
<div class="actions">
<button class="btn btn-primary" id="confirm-btn" onclick="confirmBooking()" disabled>
Confirm Booking
</button>
{% if scenario == 'none_available' %}
<button class="btn btn-secondary" onclick="findNewTimes()">
Find New Times
</button>
{% endif %}
</div>
<div class="loading" id="loading">
<div class="spinner"></div>
<p>Processing...</p>
</div>
</div>
<div class="footer">
Powered by <strong>Trusty Personal Assistant</strong><br>
Tinexta InfoCert - Digital Trust & Cybersecurity
</div>
</div>
<script>
const SESSION_ID = "{{ session.session_id }}";
let selectedSlotIndex = null;
function selectSlot(index) {
// Remove previous selection
document.querySelectorAll('.slot-option').forEach(el => {
el.classList.remove('selected');
});
// Select new slot
const slotEl = document.querySelector(`[data-slot-index="${index}"]`);
if (slotEl && !slotEl.classList.contains('unavailable')) {
slotEl.classList.add('selected');
selectedSlotIndex = index;
document.getElementById('confirm-btn').disabled = false;
}
}
async function confirmBooking() {
if (selectedSlotIndex === null) {
alert('Seleziona un orario prima di confermare.');
return;
}
// Show loading
document.getElementById('loading').classList.add('active');
document.getElementById('confirm-btn').disabled = true;
try {
const response = await fetch('/booking/api/confirm', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
session_id: SESSION_ID,
selected_slot_index: selectedSlotIndex
})
});
const result = await response.json();
if (result.success) {
// Redirect to confirmation page
window.location.href = `/booking/book/${SESSION_ID}`;
} else {
alert('Errore: ' + (result.message || 'Impossibile confermare la prenotazione'));
}
} catch (error) {
alert('Errore di connessione. Riprova più tardi.');
console.error(error);
} finally {
document.getElementById('loading').classList.remove('active');
document.getElementById('confirm-btn').disabled = false;
}
}
async function findNewTimes() {
// Show loading
document.getElementById('loading').classList.add('active');
try {
const response = await fetch('/booking/api/find_new_times', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
session_id: SESSION_ID
})
});
const result = await response.json();
if (result.success && result.slots) {
// Reload page to show new slots
window.location.reload();
} else {
alert('Impossibile trovare nuovi orari. Contatta l\'organizzatore.');
}
} catch (error) {
alert('Errore di connessione. Riprova più tardi.');
console.error(error);
} finally {
document.getElementById('loading').classList.remove('active');
}
}
</script>
</body>
</html>