improved assign clients page ux
Some checks failed
PHPUnit / test (push) Has been cancelled

This commit is contained in:
2026-06-12 20:43:18 +02:00
parent cceec525c1
commit 59ea2cd667
2 changed files with 42 additions and 91 deletions

View File

@ -1027,18 +1027,41 @@ table.data-table tbody tr.row-orphan-category td {
background: color-mix(in srgb, var(--surface-hover) 60%, transparent); background: color-mix(in srgb, var(--surface-hover) 60%, transparent);
} }
/* Assignment page — client table sized to rows (max 50); coach panel matches */ /* Assignment page — panels fill remaining viewport; lists scroll inside */
.main-content:has(.assign-page) {
display: flex;
flex-direction: column;
min-height: 100vh;
min-height: 100dvh;
}
.main-content:has(.assign-page) .page-header {
flex-shrink: 0;
}
.main-content:has(.assign-page) #assignContent {
flex: 1 1 auto;
min-height: 0;
display: flex;
flex-direction: column;
}
.assign-page { .assign-page {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex: 1 1 auto;
min-height: 0;
gap: 16px; gap: 16px;
} }
.assign-layout { .assign-layout {
display: grid; display: grid;
grid-template-columns: minmax(260px, min(32vw, 380px)) minmax(0, 1fr); grid-template-columns: minmax(260px, min(32vw, 380px)) minmax(0, 1fr);
grid-template-rows: minmax(0, 1fr);
gap: 16px; gap: 16px;
align-items: start; align-items: stretch;
flex: 1 1 auto;
min-height: 0;
} }
.assign-layout > .assign-coaches, .assign-layout > .assign-coaches,
@ -1047,6 +1070,16 @@ table.data-table tbody tr.row-orphan-category td {
flex-direction: column; flex-direction: column;
padding: 0; padding: 0;
overflow: hidden; overflow: hidden;
height: 100%;
max-height: 100%;
min-height: 0;
}
.assign-coaches .assign-panel-body {
flex: 1 1 0;
min-height: 0;
overflow-y: auto;
overflow-x: hidden;
} }
.assign-panel-header { .assign-panel-header {
@ -1066,20 +1099,13 @@ table.data-table tbody tr.row-orphan-category td {
} }
.assign-panel-body { .assign-panel-body {
flex: 0 0 auto; flex: 1 1 auto;
min-height: 0; min-height: 0;
overflow: auto; overflow: auto;
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
padding: 6px 8px 10px; padding: 6px 8px 10px;
} }
.assign-coaches .assign-panel-footer--align {
flex-shrink: 0;
margin-top: auto;
visibility: hidden;
pointer-events: none;
}
.assign-clients-toolbar { .assign-clients-toolbar {
flex-shrink: 0; flex-shrink: 0;
padding: 14px 16px; padding: 14px 16px;
@ -1129,16 +1155,18 @@ table.data-table tbody tr.row-orphan-category td {
} }
.assign-table-panel { .assign-table-panel {
flex: 0 0 auto; flex: 1 1 auto;
min-height: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.assign-table-panel .table-wrapper { .assign-table-panel .table-wrapper {
flex: none; flex: 1 1 auto;
min-height: 0;
max-height: none;
overflow: auto; overflow: auto;
box-sizing: border-box; box-sizing: border-box;
max-height: none;
} }
.assign-panel-footer, .assign-panel-footer,
@ -1195,6 +1223,7 @@ table.data-table tbody tr.row-orphan-category td {
@media (max-width: 900px) { @media (max-width: 900px) {
.assign-layout { .assign-layout {
grid-template-columns: 1fr; grid-template-columns: 1fr;
grid-template-rows: minmax(0, 38%) minmax(0, 1fr);
} }
} }

View File

@ -10,8 +10,6 @@ let filterCoachSearch = '';
let clientsByCoach = {}; let clientsByCoach = {};
let clientPage = 0; let clientPage = 0;
let selectedClientCodes = new Set(); let selectedClientCodes = new Set();
let assignResizeObserver = null;
let assignClientRowHeight = 0;
export async function assignmentsPage() { export async function assignmentsPage() {
selectedCoachID = null; selectedCoachID = null;
@ -76,7 +74,6 @@ function renderAssignments() {
<div class="assign-panel-body"> <div class="assign-panel-body">
<ul class="coach-list" id="coachList" role="listbox" aria-label="Filter by counselor"></ul> <ul class="coach-list" id="coachList" role="listbox" aria-label="Filter by counselor"></ul>
</div> </div>
<div class="assign-panel-footer assign-panel-footer--align" aria-hidden="true"></div>
</aside> </aside>
<section class="card assign-clients-card" aria-label="Clients"> <section class="card assign-clients-card" aria-label="Clients">
@ -199,78 +196,6 @@ function renderAssignments() {
renderCoachList(); renderCoachList();
renderClientRows(); renderClientRows();
setupAssignPanelHeightSync();
}
function setupAssignPanelHeightSync() {
assignResizeObserver?.disconnect();
assignResizeObserver = null;
const clientCard = document.querySelector('.assign-clients-card');
if (!clientCard || typeof ResizeObserver === 'undefined') {
syncAssignLayoutHeights();
return;
}
assignResizeObserver = new ResizeObserver(() => syncAssignLayoutHeights());
assignResizeObserver.observe(clientCard);
syncAssignLayoutHeights();
}
function syncAssignClientTableHeight() {
const wrapper = document.querySelector('.assign-table-panel .table-wrapper');
const table = wrapper?.querySelector('table.data-table');
if (!wrapper || !table) return;
const headerH = table.querySelector('thead')?.offsetHeight ?? 40;
const bodyRows = [...table.querySelectorAll('tbody tr')];
const dataRows = bodyRows.filter(tr => !tr.querySelector('td[colspan]'));
let rowH = assignClientRowHeight;
if (dataRows.length) {
rowH = dataRows[0].offsetHeight;
assignClientRowHeight = rowH;
} else if (bodyRows.length) {
rowH = bodyRows[0].offsetHeight;
} else if (!rowH) {
rowH = 44;
}
const rowCount = dataRows.length || 1;
const bodyH = rowH * Math.min(rowCount, CLIENT_PAGE_SIZE);
const maxH = rowH * CLIENT_PAGE_SIZE;
wrapper.style.height = `${headerH + bodyH}px`;
wrapper.style.maxHeight = `${headerH + maxH}px`;
wrapper.style.overflowY = 'auto';
}
function syncAssignCoachPanelHeight() {
const coachCard = document.querySelector('.assign-coaches');
const clientCard = document.querySelector('.assign-clients-card');
const coachBody = document.querySelector('.assign-coaches .assign-panel-body');
const clientTablePanel = document.querySelector('.assign-table-panel');
const coachFooter = document.querySelector('.assign-coaches .assign-panel-footer--align');
const clientFooter = document.querySelector('.assign-clients-footer');
if (clientFooter && coachFooter) {
coachFooter.style.height = `${clientFooter.offsetHeight}px`;
}
if (coachBody && clientTablePanel) {
const h = clientTablePanel.offsetHeight;
coachBody.style.height = `${h}px`;
coachBody.style.maxHeight = `${h}px`;
}
if (coachCard && clientCard) {
coachCard.style.minHeight = `${clientCard.offsetHeight}px`;
}
}
function syncAssignLayoutHeights() {
requestAnimationFrame(() => {
syncAssignClientTableHeight();
requestAnimationFrame(() => syncAssignCoachPanelHeight());
});
} }
function getFilteredCoaches() { function getFilteredCoaches() {
@ -424,7 +349,6 @@ function renderClientRows() {
}); });
syncSelectAllCb(); syncSelectAllCb();
updateSelectionInfo(); updateSelectionInfo();
syncAssignCoachPanelHeight();
return; return;
} }
@ -460,7 +384,6 @@ function renderClientRows() {
}); });
syncSelectAllCb(slice.map(c => c.clientCode)); syncSelectAllCb(slice.map(c => c.clientCode));
updateSelectionInfo(); updateSelectionInfo();
syncAssignLayoutHeights();
} }
function getSelectedCodes() { function getSelectedCodes() {
@ -488,7 +411,6 @@ function updateSelectionInfo() {
const sel = document.getElementById('targetCoachSelect'); const sel = document.getElementById('targetCoachSelect');
if (sel) sel.value = selectedCoachID; if (sel) sel.value = selectedCoachID;
} }
syncAssignLayoutHeights();
} }
async function doAssign() { async function doAssign() {