diff --git a/nodejs-project/nodejs-project/src/client/assets/styles/mobile.css b/nodejs-project/nodejs-project/src/client/assets/styles/mobile.css index afa56605..0a485bc1 100644 --- a/nodejs-project/nodejs-project/src/client/assets/styles/mobile.css +++ b/nodejs-project/nodejs-project/src/client/assets/styles/mobile.css @@ -399,15 +399,19 @@ textarea, input, select { font-size: 16px !important; } -/* ---- Filtros: wrap multilinea, todos los botones visibles ---- */ +/* En móvil ocultamos el grid antiguo; usa el paginador (.actpager) */ +.activity-filter-grid { + display: none !important; +} + +/* ---- Filtros (mode-buttons): wrap multilinea ---- */ .mode-buttons, .mode-buttons-cols, .mode-buttons-row, .filter-group, .filters, .inhabitant-action, -.tribe-mode-buttons, -.activity-filter-grid { +.tribe-mode-buttons { display: flex !important; flex-direction: row !important; flex-wrap: wrap !important; @@ -416,25 +420,18 @@ textarea, input, select { overflow: visible !important; } -/* En el activity-filter-grid las columnas (.activity-filter-col) se aplanan */ -.activity-filter-grid .activity-filter-col { - display: contents !important; -} - .mode-buttons form, .mode-buttons-cols form, .mode-buttons-row form, .filter-group form, -.filters form, -.activity-filter-grid form { +.filters form { flex: 0 0 auto !important; margin: 0 !important; } .mode-buttons .filter-btn, .mode-buttons button, -.filter-group .filter-btn, -.activity-filter-grid .filter-btn { +.filter-group .filter-btn { width: auto !important; white-space: nowrap !important; padding: 8px 14px !important; @@ -732,3 +729,282 @@ pre, code { overflow-x: hidden !important; } + +/* ============================================================ + ACTPAGER — paginador móvil CSS-only para activity_view + ============================================================ */ +.actpager { + display: block !important; + width: 100% !important; + margin-top: 12px !important; + position: relative !important; +} +.actpager-radio { + position: absolute !important; + opacity: 0 !important; + pointer-events: none !important; + width: 0 !important; + height: 0 !important; +} +.actpager-frame { + display: flex !important; + flex-direction: column !important; + gap: 10px !important; +} +.actpager-clip { + overflow: hidden !important; + width: 100% !important; +} +.actpager-row { + display: flex !important; + flex-direction: row !important; + flex-wrap: nowrap !important; + width: 100% !important; + transition: transform 0.3s ease !important; + transform: translateX(0) !important; +} +.actpager-cell { + flex: 0 0 100% !important; + display: flex !important; + flex-direction: row !important; + gap: 8px !important; + width: 100% !important; + box-sizing: border-box !important; +} +.actpager-form { + flex: 1 1 0 !important; + margin: 0 !important; + min-width: 0 !important; +} +.actpager-cell .filter-btn { + width: 100% !important; + padding: 12px 10px !important; + font-size: 0.9rem !important; + font-weight: 700 !important; + white-space: nowrap !important; + overflow: hidden !important; + text-overflow: ellipsis !important; + border-radius: 8px !important; +} +.actpager-controls { + display: flex !important; + flex-direction: row !important; + justify-content: center !important; + align-items: center !important; + width: 100% !important; +} +.actpager-arrows { + display: none !important; + flex-direction: row !important; + align-items: center !important; + justify-content: center !important; + gap: 18px !important; + width: 100% !important; +} +.actpager-arrow { + width: 44px !important; + height: 44px !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + font-size: 1.7rem !important; + font-weight: 700 !important; + border-radius: 50% !important; + cursor: pointer !important; + user-select: none !important; + line-height: 1 !important; +} +.actpager-arrow.disabled { + opacity: 0.25 !important; + cursor: default !important; + pointer-events: none !important; +} +.actpager-counter { + font-size: 0.9rem !important; + font-weight: 700 !important; + min-width: 60px !important; + text-align: center !important; + letter-spacing: 0.05em !important; +} + +/* Reglas por página: traslación + arrows visibles */ +.actpager-r0:checked ~ .actpager-frame .actpager-row { + transform: translateX(-0%) !important; +} +.actpager-r0:checked ~ .actpager-frame .actpager-a0 { + display: flex !important; +} +.actpager-r1:checked ~ .actpager-frame .actpager-row { + transform: translateX(-100%) !important; +} +.actpager-r1:checked ~ .actpager-frame .actpager-a1 { + display: flex !important; +} +.actpager-r2:checked ~ .actpager-frame .actpager-row { + transform: translateX(-200%) !important; +} +.actpager-r2:checked ~ .actpager-frame .actpager-a2 { + display: flex !important; +} +.actpager-r3:checked ~ .actpager-frame .actpager-row { + transform: translateX(-300%) !important; +} +.actpager-r3:checked ~ .actpager-frame .actpager-a3 { + display: flex !important; +} +.actpager-r4:checked ~ .actpager-frame .actpager-row { + transform: translateX(-400%) !important; +} +.actpager-r4:checked ~ .actpager-frame .actpager-a4 { + display: flex !important; +} +.actpager-r5:checked ~ .actpager-frame .actpager-row { + transform: translateX(-500%) !important; +} +.actpager-r5:checked ~ .actpager-frame .actpager-a5 { + display: flex !important; +} +.actpager-r6:checked ~ .actpager-frame .actpager-row { + transform: translateX(-600%) !important; +} +.actpager-r6:checked ~ .actpager-frame .actpager-a6 { + display: flex !important; +} +.actpager-r7:checked ~ .actpager-frame .actpager-row { + transform: translateX(-700%) !important; +} +.actpager-r7:checked ~ .actpager-frame .actpager-a7 { + display: flex !important; +} +.actpager-r8:checked ~ .actpager-frame .actpager-row { + transform: translateX(-800%) !important; +} +.actpager-r8:checked ~ .actpager-frame .actpager-a8 { + display: flex !important; +} +.actpager-r9:checked ~ .actpager-frame .actpager-row { + transform: translateX(-900%) !important; +} +.actpager-r9:checked ~ .actpager-frame .actpager-a9 { + display: flex !important; +} +.actpager-r10:checked ~ .actpager-frame .actpager-row { + transform: translateX(-1000%) !important; +} +.actpager-r10:checked ~ .actpager-frame .actpager-a10 { + display: flex !important; +} +.actpager-r11:checked ~ .actpager-frame .actpager-row { + transform: translateX(-1100%) !important; +} +.actpager-r11:checked ~ .actpager-frame .actpager-a11 { + display: flex !important; +} +.actpager-r12:checked ~ .actpager-frame .actpager-row { + transform: translateX(-1200%) !important; +} +.actpager-r12:checked ~ .actpager-frame .actpager-a12 { + display: flex !important; +} +.actpager-r13:checked ~ .actpager-frame .actpager-row { + transform: translateX(-1300%) !important; +} +.actpager-r13:checked ~ .actpager-frame .actpager-a13 { + display: flex !important; +} +.actpager-r14:checked ~ .actpager-frame .actpager-row { + transform: translateX(-1400%) !important; +} +.actpager-r14:checked ~ .actpager-frame .actpager-a14 { + display: flex !important; +} +.actpager-r15:checked ~ .actpager-frame .actpager-row { + transform: translateX(-1500%) !important; +} +.actpager-r15:checked ~ .actpager-frame .actpager-a15 { + display: flex !important; +} +.actpager-r16:checked ~ .actpager-frame .actpager-row { + transform: translateX(-1600%) !important; +} +.actpager-r16:checked ~ .actpager-frame .actpager-a16 { + display: flex !important; +} +.actpager-r17:checked ~ .actpager-frame .actpager-row { + transform: translateX(-1700%) !important; +} +.actpager-r17:checked ~ .actpager-frame .actpager-a17 { + display: flex !important; +} +.actpager-r18:checked ~ .actpager-frame .actpager-row { + transform: translateX(-1800%) !important; +} +.actpager-r18:checked ~ .actpager-frame .actpager-a18 { + display: flex !important; +} +.actpager-r19:checked ~ .actpager-frame .actpager-row { + transform: translateX(-1900%) !important; +} +.actpager-r19:checked ~ .actpager-frame .actpager-a19 { + display: flex !important; +} +.actpager-r20:checked ~ .actpager-frame .actpager-row { + transform: translateX(-2000%) !important; +} +.actpager-r20:checked ~ .actpager-frame .actpager-a20 { + display: flex !important; +} +.actpager-r21:checked ~ .actpager-frame .actpager-row { + transform: translateX(-2100%) !important; +} +.actpager-r21:checked ~ .actpager-frame .actpager-a21 { + display: flex !important; +} +.actpager-r22:checked ~ .actpager-frame .actpager-row { + transform: translateX(-2200%) !important; +} +.actpager-r22:checked ~ .actpager-frame .actpager-a22 { + display: flex !important; +} +.actpager-r23:checked ~ .actpager-frame .actpager-row { + transform: translateX(-2300%) !important; +} +.actpager-r23:checked ~ .actpager-frame .actpager-a23 { + display: flex !important; +} +.actpager-r24:checked ~ .actpager-frame .actpager-row { + transform: translateX(-2400%) !important; +} +.actpager-r24:checked ~ .actpager-frame .actpager-a24 { + display: flex !important; +} +.actpager-r25:checked ~ .actpager-frame .actpager-row { + transform: translateX(-2500%) !important; +} +.actpager-r25:checked ~ .actpager-frame .actpager-a25 { + display: flex !important; +} +.actpager-r26:checked ~ .actpager-frame .actpager-row { + transform: translateX(-2600%) !important; +} +.actpager-r26:checked ~ .actpager-frame .actpager-a26 { + display: flex !important; +} +.actpager-r27:checked ~ .actpager-frame .actpager-row { + transform: translateX(-2700%) !important; +} +.actpager-r27:checked ~ .actpager-frame .actpager-a27 { + display: flex !important; +} +.actpager-r28:checked ~ .actpager-frame .actpager-row { + transform: translateX(-2800%) !important; +} +.actpager-r28:checked ~ .actpager-frame .actpager-a28 { + display: flex !important; +} +.actpager-r29:checked ~ .actpager-frame .actpager-row { + transform: translateX(-2900%) !important; +} +.actpager-r29:checked ~ .actpager-frame .actpager-a29 { + display: flex !important; +} diff --git a/nodejs-project/nodejs-project/src/views/activity_view.js b/nodejs-project/nodejs-project/src/views/activity_view.js index 42627c7a..78b36d85 100644 --- a/nodejs-project/nodejs-project/src/views/activity_view.js +++ b/nodejs-project/nodejs-project/src/views/activity_view.js @@ -1701,6 +1701,47 @@ exports.activityView = (actions, filter, userId, q = '') => { }); } + // Mobile paginator (CSS-only, no JS): 2 buttons per page with arrow labels + const buildBtn = (cls, t, lab) => form({ method: 'GET', action: '/activity', class: cls }, + input({ type: 'hidden', name: 'filter', value: t }), + button({ type: 'submit', class: filter === t ? 'filter-btn active' : 'filter-btn' }, lab) + ); + const PAGE_SIZE = 2; + const totalPages = Math.ceil(activityTypes.length / PAGE_SIZE); + const pageCells = []; + for (let i = 0; i < totalPages; i++) { + const slice = activityTypes.slice(i * PAGE_SIZE, (i + 1) * PAGE_SIZE); + pageCells.push(div({ class: 'actpager-cell' }, + slice.map(({ type, label }) => buildBtn('actpager-form', type, label)) + )); + } + const radios = []; + for (let i = 0; i < totalPages; i++) { + const attrs = { type: 'radio', name: 'actp', id: `actp${i}`, class: `actpager-radio actpager-r${i}` }; + if (i === 0) attrs.checked = 'checked'; + radios.push(input(attrs)); + } + const arrowSets = []; + for (let i = 0; i < totalPages; i++) { + const prev = i > 0 + ? require('../server/node_modules/hyperaxe').label({ for: `actp${i - 1}`, class: 'actpager-arrow' }, '‹') + : require('../server/node_modules/hyperaxe').span({ class: 'actpager-arrow disabled' }, '‹'); + const next = i < totalPages - 1 + ? require('../server/node_modules/hyperaxe').label({ for: `actp${i + 1}`, class: 'actpager-arrow' }, '›') + : require('../server/node_modules/hyperaxe').span({ class: 'actpager-arrow disabled' }, '›'); + const counter = require('../server/node_modules/hyperaxe').span({ class: 'actpager-counter' }, `${i + 1} / ${totalPages}`); + arrowSets.push(div({ class: `actpager-arrows actpager-a${i}` }, prev, counter, next)); + } + const mobilePager = section({ class: 'actpager', style: 'display:none' }, + ...radios, + div({ class: 'actpager-frame' }, + div({ class: 'actpager-clip' }, + div({ class: 'actpager-row' }, ...pageCells) + ), + div({ class: 'actpager-controls' }, ...arrowSets) + ) + ); + let html = template( title, section( @@ -1708,6 +1749,7 @@ exports.activityView = (actions, filter, userId, q = '') => { h2(i18n.activityList), p(desc) ), + mobilePager, div({ class: 'activity-filter-grid' }, ...[ activityTypes.slice(0, 4),