diff --git a/presente/views.py b/presente/views.py index 1cb63a5..92f5355 100644 --- a/presente/views.py +++ b/presente/views.py @@ -270,7 +270,7 @@ class ActivityAttendanceListView(ActivityOwnerMixin, CoreFilterView): filterset_class = ActivityAttendanceFilter template_name = "presente/activity_attendance_list.html" table_pagination = {"per_page": 20} - context_object_name = "attendances" + # context_object_name = "attendances" actions = ["delete"] permission_required = [] diff --git a/static/css/presente.css b/static/css/presente.css index b15440b..b97c573 100644 --- a/static/css/presente.css +++ b/static/css/presente.css @@ -17,415 +17,3 @@ color: #6c757d; font-weight: 500; } - -/* Compact card headers - AdminLTE4 style */ -.card-header h5 { - font-size: 1rem; - font-weight: 500; - margin-bottom: 0; - line-height: 1.5; -} -.card-header { - padding: 0.5rem 1rem; - min-height: auto; -} -.card-header .btn-sm { - padding: 0.25rem 0.5rem; - font-size: 0.875rem; -} - -/* Detail field styling */ -.detail-field { - padding-bottom: 0.5rem; - border-bottom: 1px solid #e9ecef; -} - -.detail-field:last-child { - border-bottom: none; -} - -.detail-field label { - font-weight: 600; - letter-spacing: 0.05em; -} - -.detail-value { - font-size: 1rem; - padding-top: 0.25rem; -} - -/* Public Activity Page - Fullscreen QR Code Display */ -.public-activity-page body { - overflow: hidden; -} -.fullscreen-container { - min-height: 100vh; - display: flex; - flex-direction: column; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - padding: 1.5rem; -} -.activity-card { - background: white; - border-radius: 1.5rem; - box-shadow: 0 20px 60px rgba(0,0,0,0.3); - flex: 1; - display: flex; - flex-direction: column; - overflow: hidden; - max-width: 1200px; - margin: 0 auto; - width: 100%; -} - -/* Header with branding */ -.activity-header { - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - color: white; - padding: 1.5rem 2rem; - text-align: center; -} -.activity-header h1 { - margin: 0; - font-size: 2.5rem; - font-weight: 700; -} - -/* Body layout */ -.activity-body { - flex: 1; - display: flex; - flex-direction: column; - padding: 2rem; - justify-content: space-between; - gap: 1rem; -} - -/* Time info blocks */ -.time-info { - display: flex; - justify-content: center; - gap: 0.75rem; - flex-wrap: wrap; -} -.time-block { - text-align: center; - padding: 0.75rem 1rem; - background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); - border-radius: 0.75rem; - min-width: 160px; - flex: 1; - max-width: 220px; - box-shadow: 0 2px 8px rgba(0,0,0,0.08); -} -.time-label { - font-size: 0.75rem; - color: #666; - text-transform: uppercase; - letter-spacing: 0.5px; - margin-bottom: 0.25rem; - font-weight: 600; -} -.time-label i { - margin-right: 0.25rem; -} -.time-value { - font-size: 1.1rem; - font-weight: 700; - color: #333; -} - -/* QR Section */ -.qr-section { - text-align: center; - flex: 1; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -} -.qr-title { - font-size: 1.5rem; - font-weight: 600; - color: #333; - margin-bottom: 1rem; -} -#qr-code-container { - margin-bottom: 0.75rem; -} -.qr-wrapper { - display: flex; - flex-direction: column; - align-items: center; -} -.qr-code-link { - display: block; - padding: 1.5rem; - background: white; - border-radius: 1.2rem; - box-shadow: 0 10px 40px rgba(0,0,0,0.15); - transition: transform 0.2s, box-shadow 0.2s, opacity 0.3s ease-in-out; - cursor: pointer; - text-decoration: none; - opacity: 1; -} -.qr-code-link:hover { - transform: translateY(-2px); - box-shadow: 0 12px 50px rgba(0,0,0,0.2); -} -.qr-code-link.qr-fade-in { - animation: fadeIn 0.3s ease-in; -} -.qr-code-link.qr-fade-out { - opacity: 0.3; -} -@keyframes fadeIn { - from { - opacity: 0.3; - } - to { - opacity: 1; - } -} - -/* HTMX transition animations */ -#qr-code-container.htmx-swapping { - opacity: 0; - transition: opacity 0.3s ease-out; -} -#qr-code-container.htmx-settling { - opacity: 1; - transition: opacity 0.3s ease-in; -} -#qrcode img { - display: block; - border-radius: 0.5rem; - max-width: 100%; - height: auto; -} -/* Datetime badge - compact utility display */ -.qr-datetime-badge { - display: inline-flex; - align-items: center; - gap: 0.5rem; - padding: 0.4rem 0.75rem; - background: rgba(102, 126, 234, 0.08); - border-radius: 0.5rem; - font-size: 0.8rem; - color: #555; - font-weight: 500; - font-variant-numeric: tabular-nums; - margin-bottom: 1rem; -} -.qr-datetime-badge i { - color: #667eea; - font-size: 0.75rem; -} -.qr-datetime-separator { - color: #999; - margin: 0 0.15rem; -} - -/* Status container */ -.qr-status-container { - display: flex; - flex-direction: column; - align-items: center; -} -.qr-countdown { - margin-top: 1rem; - font-size: 0.95rem; - color: #666; - font-weight: 500; -} -.qr-countdown i { - animation: spin 2s linear infinite; -} -@keyframes spin { - from { transform: rotate(0deg); } - to { transform: rotate(360deg); } -} - -/* Footer branding */ -.page-footer { - margin-top: auto; - padding-top: 1rem; - border-top: 1px solid #e9ecef; - text-align: center; -} -.footer-content { - display: flex; - align-items: center; - justify-content: center; - gap: 0.5rem; -} -.footer-logo { - height: 24px; -} -.footer-text { - font-size: 1rem; - color: #333; - font-weight: 700; -} -.footer-separator { - color: #999; - font-size: 1rem; - margin: 0 0.25rem; -} -.footer-copyright { - font-size: 0.85rem; - color: #666; -} - -/* Responsive adjustments for public activity page */ -@media (max-width: 768px) { - .fullscreen-container { - padding: 1rem; - } - .activity-header { - padding: 1rem 1.5rem; - } - .activity-header h1 { - font-size: 1.8rem; - } - .activity-body { - padding: 1.5rem; - gap: 1rem; - } - .qr-title { - font-size: 1.2rem; - margin-bottom: 0.75rem; - } - .time-block { - min-width: 140px; - padding: 0.6rem 0.85rem; - } - .time-label { - font-size: 0.7rem; - } - .time-value { - font-size: 1rem; - } - .qr-code-link { - padding: 1rem; - } - .qr-datetime-badge { - font-size: 0.75rem; - padding: 0.35rem 0.65rem; - } -} - -@media (max-width: 576px) { - .activity-header h1 { - font-size: 1.4rem; - } - .qr-title { - font-size: 1.1rem; - margin-bottom: 0.5rem; - } - .time-info { - gap: 0.5rem; - } - .time-block { - min-width: 110px; - padding: 0.5rem 0.75rem; - } - .time-label { - font-size: 0.65rem; - letter-spacing: 0.25px; - } - .time-value { - font-size: 0.95rem; - } - .qr-code-link { - padding: 0.75rem; - } - .qr-datetime-badge { - font-size: 0.72rem; - padding: 0.3rem 0.6rem; - gap: 0.35rem; - } - .qr-countdown { - font-size: 0.85rem; - } - .footer-content { - gap: 0.35rem; - } - .footer-logo { - height: 20px; - } - .footer-text { - font-size: 0.85rem; - } - .footer-separator { - font-size: 0.85rem; - } - .footer-copyright { - font-size: 0.7rem; - } -} - -@media (max-height: 700px) { - .fullscreen-container { - padding: 0.75rem; - } - .activity-header { - padding: 0.75rem 1rem; - } - .activity-header h1 { - font-size: 1.5rem; - } - .activity-body { - padding: 1rem; - gap: 0.75rem; - } - .qr-title { - font-size: 1.1rem; - margin-bottom: 0.5rem; - } - .time-info { - gap: 0.5rem; - } - .time-block { - padding: 0.5rem 0.75rem; - min-width: 130px; - } - .time-label { - font-size: 0.65rem; - } - .time-value { - font-size: 0.95rem; - } - .qr-code-link { - padding: 0.75rem; - } - .qr-datetime-badge { - font-size: 0.7rem; - padding: 0.3rem 0.55rem; - gap: 0.3rem; - } - .qr-datetime-badge i { - font-size: 0.7rem; - } - .qr-countdown { - font-size: 0.85rem; - } - .page-footer { - padding-top: 0.5rem; - } - .footer-content { - gap: 0.35rem; - } - .footer-logo { - height: 18px; - } - .footer-text { - font-size: 0.8rem; - } - .footer-separator { - font-size: 0.8rem; - } - .footer-copyright { - font-size: 0.65rem; - } -} diff --git a/static/css/qrcode_page.css b/static/css/qrcode_page.css new file mode 100644 index 0000000..3a934a0 --- /dev/null +++ b/static/css/qrcode_page.css @@ -0,0 +1,374 @@ +/* Public Activity Page - Fullscreen QR Code Display */ +.public-activity-page body { + overflow: hidden; +} +.fullscreen-container { + min-height: 100vh; + display: flex; + flex-direction: column; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + padding: 1.5rem; +} +.activity-card { + background: white; + border-radius: 1.5rem; + box-shadow: 0 20px 60px rgba(0,0,0,0.3); + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; + max-width: 1200px; + margin: 0 auto; + width: 100%; +} + +/* Header with branding */ +.activity-header { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + padding: 1.5rem 2rem; + text-align: center; +} +.activity-header h1 { + margin: 0; + font-size: 2.5rem; + font-weight: 700; +} + +/* Body layout */ +.activity-body { + flex: 1; + display: flex; + flex-direction: column; + padding: 2rem; + justify-content: space-between; + gap: 1rem; +} + +/* Time info blocks */ +.time-info { + display: flex; + justify-content: center; + gap: 0.75rem; + flex-wrap: wrap; +} +.time-block { + text-align: center; + padding: 0.75rem 1rem; + background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); + border-radius: 0.75rem; + min-width: 160px; + flex: 1; + max-width: 220px; + box-shadow: 0 2px 8px rgba(0,0,0,0.08); +} +.time-label { + font-size: 0.75rem; + color: #666; + text-transform: uppercase; + letter-spacing: 0.5px; + margin-bottom: 0.25rem; + font-weight: 600; +} +.time-label i { + margin-right: 0.25rem; +} +.time-value { + font-size: 1.1rem; + font-weight: 700; + color: #333; +} + +/* QR Section */ +.qr-section { + text-align: center; + flex: 1; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} +.qr-title { + font-size: 1.5rem; + font-weight: 600; + color: #333; + margin-bottom: 1rem; +} +#qr-code-container { + margin-bottom: 0.75rem; +} +.qr-wrapper { + display: flex; + flex-direction: column; + align-items: center; +} +.qr-code-link { + display: block; + padding: 1.5rem; + background: white; + border-radius: 1.2rem; + box-shadow: 0 10px 40px rgba(0,0,0,0.15); + transition: transform 0.2s, box-shadow 0.2s, opacity 0.3s ease-in-out; + cursor: pointer; + text-decoration: none; + opacity: 1; +} +.qr-code-link:hover { + transform: translateY(-2px); + box-shadow: 0 12px 50px rgba(0,0,0,0.2); +} +.qr-code-link.qr-fade-in { + animation: fadeIn 0.3s ease-in; +} +.qr-code-link.qr-fade-out { + opacity: 0.3; +} +@keyframes fadeIn { + from { + opacity: 0.3; + } + to { + opacity: 1; + } +} + +/* HTMX transition animations */ +#qr-code-container.htmx-swapping { + opacity: 0; + transition: opacity 0.3s ease-out; +} +#qr-code-container.htmx-settling { + opacity: 1; + transition: opacity 0.3s ease-in; +} +#qrcode img { + display: block; + border-radius: 0.5rem; + max-width: 100%; + height: auto; +} +/* Datetime badge - compact utility display */ +.qr-datetime-badge { + display: inline-flex; + align-items: center; + gap: 0.5rem; + padding: 0.4rem 0.75rem; + background: rgba(102, 126, 234, 0.08); + border-radius: 0.5rem; + font-size: 0.8rem; + color: #555; + font-weight: 500; + font-variant-numeric: tabular-nums; +} +.qr-datetime-badge i { + color: #667eea; + font-size: 0.75rem; +} +.qr-datetime-separator { + color: #999; + margin: 0 0.15rem; +} + +/* Status container */ +.qr-status-container { + display: flex; + flex-direction: column; + align-items: center; +} +.qr-countdown { + margin-top: 1rem; + font-size: 0.95rem; + color: #666; + font-weight: 500; +} +.qr-countdown i { + animation: spin 2s linear infinite; +} +@keyframes spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +/* Footer branding */ +.page-footer { + margin-top: auto; + padding-top: 1rem; + border-top: 1px solid #e9ecef; + text-align: center; +} +.footer-content { + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; +} +.footer-logo { + height: 24px; +} +.footer-text { + font-size: 1rem; + color: #333; + font-weight: 700; +} +.footer-separator { + color: #999; + font-size: 1rem; + margin: 0 0.25rem; +} +.footer-copyright { + font-size: 0.85rem; + color: #666; +} + +/* Responsive adjustments for public activity page */ +@media (max-width: 768px) { + .fullscreen-container { + padding: 1rem; + } + .activity-header { + padding: 1rem 1.5rem; + } + .activity-header h1 { + font-size: 1.8rem; + } + .activity-body { + padding: 1.5rem; + gap: 1rem; + } + .qr-title { + font-size: 1.2rem; + margin-bottom: 0.75rem; + } + .time-block { + min-width: 140px; + padding: 0.6rem 0.85rem; + } + .time-label { + font-size: 0.7rem; + } + .time-value { + font-size: 1rem; + } + .qr-code-link { + padding: 1rem; + } + .qr-datetime-badge { + font-size: 0.75rem; + padding: 0.35rem 0.65rem; + } +} + +@media (max-width: 576px) { + .activity-header h1 { + font-size: 1.4rem; + } + .qr-title { + font-size: 1.1rem; + margin-bottom: 0.5rem; + } + .time-info { + gap: 0.5rem; + } + .time-block { + min-width: 110px; + padding: 0.5rem 0.75rem; + } + .time-label { + font-size: 0.65rem; + letter-spacing: 0.25px; + } + .time-value { + font-size: 0.95rem; + } + .qr-code-link { + padding: 0.75rem; + } + .qr-datetime-badge { + font-size: 0.72rem; + padding: 0.3rem 0.6rem; + gap: 0.35rem; + } + .qr-countdown { + font-size: 0.85rem; + } + .footer-content { + gap: 0.35rem; + } + .footer-logo { + height: 20px; + } + .footer-text { + font-size: 0.85rem; + } + .footer-separator { + font-size: 0.85rem; + } + .footer-copyright { + font-size: 0.7rem; + } +} + +@media (max-height: 700px) { + .fullscreen-container { + padding: 0.75rem; + } + .activity-header { + padding: 0.75rem 1rem; + } + .activity-header h1 { + font-size: 1.5rem; + } + .activity-body { + padding: 1rem; + gap: 0.75rem; + } + .qr-title { + font-size: 1.1rem; + margin-bottom: 0.5rem; + } + .time-info { + gap: 0.5rem; + } + .time-block { + padding: 0.5rem 0.75rem; + min-width: 130px; + } + .time-label { + font-size: 0.65rem; + } + .time-value { + font-size: 0.95rem; + } + .qr-code-link { + padding: 0.75rem; + } + .qr-datetime-badge { + font-size: 0.7rem; + padding: 0.3rem 0.55rem; + gap: 0.3rem; + } + .qr-datetime-badge i { + font-size: 0.7rem; + } + .qr-countdown { + font-size: 0.85rem; + } + .page-footer { + padding-top: 0.5rem; + } + .footer-content { + gap: 0.35rem; + } + .footer-logo { + height: 18px; + } + .footer-text { + font-size: 0.8rem; + } + .footer-separator { + font-size: 0.8rem; + } + .footer-copyright { + font-size: 0.65rem; + } +} diff --git a/static/js/presente.js b/static/js/presente.js new file mode 100644 index 0000000..995746b --- /dev/null +++ b/static/js/presente.js @@ -0,0 +1,159 @@ +"use strict"; + +function setupOverlayScrollbars() { + const sidebarWrapper = document.querySelector('.sidebar-wrapper'); + + if (sidebarWrapper && typeof OverlayScrollbarsGlobal?.OverlayScrollbars !== 'undefined') { + OverlayScrollbarsGlobal.OverlayScrollbars(sidebarWrapper, { + scrollbars: { + theme: 'os-theme-light', + autoHide: 'leave', + clickScroll: true, + }, + }); + } +} + +function setupTomSelect() { + // Multi-select for users + document.querySelectorAll('[data-tom-select="users"]').forEach(function(el) { + if (!el.tomselect) { + new TomSelect(el, { + plugins: ['remove_button'], + persist: false, + create: false, + maxItems: null, + preload: false, + loadThrottle: 300, + onInitialize: function() { + // Hide dropdown initially + this.clearOptions(); + }, + load: function(query, callback) { + // Only load if at least 2 characters typed + if (query.length < 2) { + return callback(); + } + // Reload all options and filter + const allOptions = Array.from(el.querySelectorAll('option')).map(opt => ({ + value: opt.value, + text: opt.text + })).filter(opt => opt.value && opt.text.toLowerCase().includes(query.toLowerCase())); + callback(allOptions); + }, + render: { + option: function(data, escape) { + return '