416 lines
19 KiB
HTML
416 lines
19 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Create Activities Modal (Final)</title>
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
|
|
|
<style>
|
|
textarea.form-control {
|
|
resize: vertical;
|
|
}
|
|
.btn-add-row {
|
|
background-color: #6c757d;
|
|
border-color: #6c757d;
|
|
}
|
|
.btn-add-row:hover {
|
|
background-color: #5a6268;
|
|
border-color: #545b62;
|
|
}
|
|
.form-check-label-toggle {
|
|
font-weight: bold;
|
|
margin-left: 0.5rem;
|
|
display: flex;
|
|
align-items: center;
|
|
white-space: nowrap;
|
|
}
|
|
.form-check-label-toggle i {
|
|
margin-right: 0.5rem;
|
|
}
|
|
/* Custom CSS to make the Enable switch green (success) when checked */
|
|
#enableToggle:checked {
|
|
background-color: #198754;
|
|
border-color: #198754;
|
|
}
|
|
#enableToggle:checked:focus {
|
|
box-shadow: 0 0 0 0.25rem rgba(25, 135, 84, 0.25);
|
|
}
|
|
/* Style for schedule container when empty and invalid */
|
|
.invalid-schedule {
|
|
border: 1px solid var(--bs-danger);
|
|
border-radius: var(--bs-border-radius);
|
|
padding: 1rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div class="container py-5">
|
|
<h1>Bootstrap Modal Preview</h1>
|
|
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#createActivitiesModal">
|
|
Open 'Create Activities' Modal
|
|
</button>
|
|
</div>
|
|
|
|
<div class="toast-container position-fixed bottom-0 end-0 p-3">
|
|
<div id="validationToast" class="toast text-bg-danger" role="alert" aria-live="assertive" aria-atomic="true">
|
|
<div class="toast-header text-bg-danger">
|
|
<i class="bi bi-exclamation-triangle-fill me-2"></i>
|
|
<strong class="me-auto">Validation Error</strong>
|
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="toast" aria-label="Close"></button>
|
|
</div>
|
|
<div class="toast-body text-white" id="toastMessage">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal fade" id="createActivitiesModal" tabindex="-1" aria-labelledby="createActivitiesModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="createActivitiesModalLabel">
|
|
<i class="bi bi-hand-index-thumb me-2"></i> Create Sports Activities
|
|
</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
|
|
<div class="modal-body pb-3">
|
|
<form id="activityForm" onsubmit="return validateForm(event)">
|
|
|
|
<div class="row mb-4 d-flex align-items-center">
|
|
|
|
<div class="col-4">
|
|
<div class="form-check form-switch">
|
|
<input class="form-check-input" type="checkbox" role="switch" id="durationToggle" checked>
|
|
<label class="form-check-label form-check-label-toggle" for="durationToggle">
|
|
<i class="bi bi-calendar-event"></i> Duration
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-4">
|
|
<div class="form-check form-switch">
|
|
<input class="form-check-input" type="checkbox" role="switch" id="sessionsToggle">
|
|
<label class="form-check-label form-check-label-toggle" for="sessionsToggle">
|
|
<i class="bi bi-ticket"></i> <span id="sessionableLabelText">Session</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-4">
|
|
<div class="form-check form-switch">
|
|
<input class="form-check-input" type="checkbox" role="switch" id="enableToggle" checked>
|
|
<label class="form-check-label form-check-label-toggle" for="enableToggle">
|
|
Enable
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div id="sessionsInputContainer" class="d-none">
|
|
<div class="mb-3">
|
|
<label for="countSessions" class="form-label">Count Sessions</label>
|
|
<div class="input-group">
|
|
<input type="number" class="form-control" id="countSessions" placeholder="Count" min="1">
|
|
<button class="btn btn-outline-secondary dropdown-toggle dropdown-toggle-split" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
|
<span class="visually-hidden">Toggle Dropdown</span>
|
|
</button>
|
|
<ul class="dropdown-menu dropdown-menu-end">
|
|
<li><a class="dropdown-item" href="#">1</a></li>
|
|
<li><a class="dropdown-item" href="#">5</a></li>
|
|
<li><a class="dropdown-item" href="#">10</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mb-3">
|
|
<div class="col-md-6">
|
|
<label for="activityTitle" class="form-label">Title</label>
|
|
<input type="text" class="form-control" id="activityTitle" placeholder="Title">
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label for="facilitiesSelect" class="form-label">Facility</label>
|
|
<select class="form-select" id="facilitiesSelect">
|
|
<option value="" selected disabled>Select facility</option>
|
|
<option value="1">Gym</option>
|
|
<option value="2">Pool</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mb-3">
|
|
<div class="col-md-6">
|
|
<label for="maxCapacityInput" class="form-label">Max Capacity</label>
|
|
<input type="number" class="form-control" id="maxCapacityInput" placeholder="Capacity">
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label for="trainerSelect" class="form-label">Trainer</label>
|
|
<select class="form-select" id="trainerSelect">
|
|
<option selected>Select Trainer</option>
|
|
<option value="1">John Doe</option>
|
|
<option value="2">Jane Smith</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mb-3">
|
|
<div class="col-12">
|
|
<label for="activityNotes" class="form-label">Notes</label>
|
|
<textarea class="form-control" id="activityNotes" placeholder="Notes..." rows="3"></textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mb-4">
|
|
<div class="col-12">
|
|
<label for="packageImage" class="form-label">Package Image</label>
|
|
<div class="input-group">
|
|
<input type="file" class="form-control d-none" id="packageImage">
|
|
<button class="btn btn-outline-secondary rounded-end-0" type="button" onclick="document.getElementById('packageImage').click()">Browse...</button>
|
|
<input type="text" class="form-control rounded-start-0" value="No file selected." readonly id="fileNameDisplay">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<h6 class="mt-4 mb-3">Activity Schedule</h6>
|
|
|
|
<div id="scheduleHeaders" class="row fw-bold small text-secondary mb-1 d-none">
|
|
<div class="col-4">Day</div>
|
|
<div class="col-3">Start Time</div>
|
|
<div class="col-3">End Time</div>
|
|
<div class="col-2"></div>
|
|
</div>
|
|
|
|
<div id="scheduleContainer" class="pb-2">
|
|
</div>
|
|
|
|
</form>
|
|
</div>
|
|
|
|
<div class="modal-footer d-flex justify-content-between border-0">
|
|
<button type="button" class="btn btn-primary btn-add-row" onclick="addRow()">
|
|
<i class="bi bi-plus"></i> Add Schedule
|
|
</button>
|
|
|
|
<button type="submit" class="btn btn-primary" form="activityForm">Create</button>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
|
|
|
|
<script>
|
|
// Global element references
|
|
const scheduleContainer = document.getElementById('scheduleContainer');
|
|
const scheduleHeaders = document.getElementById('scheduleHeaders');
|
|
const durationToggle = document.getElementById('durationToggle');
|
|
const sessionsToggle = document.getElementById('sessionsToggle');
|
|
const sessionsInput = document.getElementById('sessionsInputContainer');
|
|
const countSessionsInput = document.getElementById('countSessions');
|
|
const facilitiesSelect = document.getElementById('facilitiesSelect');
|
|
const validationToast = new bootstrap.Toast(document.getElementById('validationToast'));
|
|
const toastMessageElement = document.getElementById('toastMessage');
|
|
|
|
// Helper function to apply/remove red border
|
|
function setInvalid(element, isInvalid) {
|
|
if (isInvalid) {
|
|
element.classList.add('is-invalid');
|
|
// Special case for schedule container, apply custom border class
|
|
if(element.id === 'scheduleContainer') {
|
|
element.classList.add('invalid-schedule');
|
|
}
|
|
} else {
|
|
element.classList.remove('is-invalid');
|
|
if(element.id === 'scheduleContainer') {
|
|
element.classList.remove('invalid-schedule');
|
|
}
|
|
}
|
|
}
|
|
|
|
// JS for file name display
|
|
document.getElementById('packageImage').addEventListener('change', function() {
|
|
var fileName = this.files.length > 0 ? this.files[0].name : 'No file selected.';
|
|
document.getElementById('fileNameDisplay').value = fileName;
|
|
});
|
|
|
|
function updateHeaderVisibility() {
|
|
if (scheduleContainer.children.length > 0) {
|
|
scheduleHeaders.classList.remove('d-none');
|
|
setInvalid(scheduleContainer, false); // Clear schedule error on add/remove
|
|
} else {
|
|
scheduleHeaders.classList.add('d-none');
|
|
}
|
|
}
|
|
|
|
function updateSessionsInputVisibility() {
|
|
if (sessionsToggle.checked) {
|
|
sessionsInput.classList.remove('d-none');
|
|
// Use the 'required' attribute for browser feedback, though JS handles the main logic
|
|
countSessionsInput.setAttribute('required', 'required');
|
|
} else {
|
|
sessionsInput.classList.add('d-none');
|
|
countSessionsInput.removeAttribute('required');
|
|
// Clear any lingering red border if switch is turned off
|
|
setInvalid(countSessionsInput, false);
|
|
}
|
|
}
|
|
|
|
// Enforce that at least one switch (Duration or Session) is always ON
|
|
function enforceMinOneSwitch(event) {
|
|
if (!durationToggle.checked && !sessionsToggle.checked) {
|
|
if (event.target.id === 'durationToggle') {
|
|
durationToggle.checked = true;
|
|
} else if (event.target.id === 'sessionsToggle') {
|
|
sessionsToggle.checked = true;
|
|
}
|
|
}
|
|
updateSessionsInputVisibility();
|
|
}
|
|
|
|
// ⭐ NEW: Form Validation Function with Highlighting and Toast
|
|
function validateForm(event) {
|
|
event.preventDefault(); // Prevent default submission first
|
|
let isValid = true;
|
|
let messages = [];
|
|
|
|
// Reset all error states
|
|
setInvalid(countSessionsInput, false);
|
|
setInvalid(facilitiesSelect, false);
|
|
setInvalid(scheduleContainer, false);
|
|
|
|
// 1. Validate Session Count if Session is ON
|
|
if (sessionsToggle.checked) {
|
|
const count = parseInt(countSessionsInput.value);
|
|
if (isNaN(count) || count <= 0) {
|
|
isValid = false;
|
|
messages.push("If 'Session' is enabled, 'Count Sessions' must be greater than 0.");
|
|
setInvalid(countSessionsInput, true);
|
|
}
|
|
}
|
|
|
|
// 2. Validate Facility Selection
|
|
if (facilitiesSelect.value === "") {
|
|
isValid = false;
|
|
messages.push("You must select a Facility.");
|
|
setInvalid(facilitiesSelect, true);
|
|
}
|
|
|
|
// 3. Validate Schedule (At least one row)
|
|
if (scheduleContainer.children.length === 0) {
|
|
isValid = false;
|
|
messages.push("You must add at least one schedule row for the activity.");
|
|
setInvalid(scheduleContainer, true);
|
|
}
|
|
|
|
if (!isValid) {
|
|
// Show toast message with list of errors
|
|
toastMessageElement.innerHTML = messages.map(msg => `• ${msg}`).join('<br>');
|
|
validationToast.show();
|
|
return false;
|
|
}
|
|
|
|
// If true, we would submit the form here (return true)
|
|
// For demo purposes, we will just show a success alert or log:
|
|
alert("Form is valid! Submitting...");
|
|
// return true; // Uncomment this line to allow actual form submission
|
|
|
|
return false; // Keep it false for the demo to prevent page reload
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
// Set initial states
|
|
document.getElementById('enableToggle').checked = true;
|
|
durationToggle.checked = true;
|
|
sessionsToggle.checked = false;
|
|
|
|
// Attach the switch constraint handler to both switches
|
|
durationToggle.addEventListener('change', enforceMinOneSwitch);
|
|
sessionsToggle.addEventListener('change', enforceMinOneSwitch);
|
|
|
|
// Attach listeners to clear red borders on interaction
|
|
countSessionsInput.addEventListener('input', () => {
|
|
const count = parseInt(countSessionsInput.value);
|
|
if (count > 0) setInvalid(countSessionsInput, false);
|
|
});
|
|
facilitiesSelect.addEventListener('change', () => {
|
|
if (facilitiesSelect.value !== "") setInvalid(facilitiesSelect, false);
|
|
});
|
|
|
|
// Call update functions on load
|
|
updateSessionsInputVisibility();
|
|
updateHeaderVisibility();
|
|
});
|
|
|
|
|
|
// JS for Dynamic Schedule Rows
|
|
let rowCount = 0;
|
|
const daysOfWeek = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
|
|
|
|
function addRow() {
|
|
rowCount++;
|
|
|
|
const newRow = document.createElement('div');
|
|
newRow.classList.add('row', 'mb-3', 'align-items-center');
|
|
newRow.id = 'scheduleRow_' + rowCount;
|
|
|
|
// Column 1: Day Dropdown (col-4)
|
|
const dayCol = document.createElement('div');
|
|
dayCol.classList.add('col-4');
|
|
dayCol.innerHTML = `
|
|
<select class="form-select" name="day_${rowCount}" required>
|
|
<option value="" selected disabled>Select Day</option>
|
|
${daysOfWeek.map(day => `<option value="${day}">${day}</option>`).join('')}
|
|
</select>
|
|
`;
|
|
|
|
// Column 2: Start Time Input (col-3)
|
|
const startTimeCol = document.createElement('div');
|
|
startTimeCol.classList.add('col-3');
|
|
startTimeCol.innerHTML = `
|
|
<input type="time" class="form-control" name="start_time_${rowCount}" required>
|
|
`;
|
|
|
|
// Column 3: End Time Input (col-3)
|
|
const endTimeCol = document.createElement('div');
|
|
endTimeCol.classList.add('col-3');
|
|
endTimeCol.innerHTML = `
|
|
<input type="time" class="form-control" name="end_time_${rowCount}" required>
|
|
`;
|
|
|
|
// Column 4: Remove Button (col-2)
|
|
const removeCol = document.createElement('div');
|
|
removeCol.classList.add('col-2');
|
|
removeCol.innerHTML = `
|
|
<button type="button" class="btn btn-danger btn-sm" onclick="removeRow('scheduleRow_${rowCount}')">
|
|
<i class="bi bi-x"></i>
|
|
</button>
|
|
`;
|
|
|
|
newRow.appendChild(dayCol);
|
|
newRow.appendChild(startTimeCol);
|
|
newRow.appendChild(endTimeCol);
|
|
newRow.appendChild(removeCol);
|
|
|
|
scheduleContainer.appendChild(newRow);
|
|
|
|
// Update visibility and clear schedule error
|
|
updateHeaderVisibility();
|
|
}
|
|
|
|
function removeRow(rowId) {
|
|
const rowToRemove = document.getElementById(rowId);
|
|
if (rowToRemove) {
|
|
rowToRemove.remove();
|
|
}
|
|
// Update visibility and check if schedule is now empty
|
|
updateHeaderVisibility();
|
|
}
|
|
</script>
|
|
|
|
</body>
|
|
</html> |