(useCacheOnly)
| 1495 | } |
| 1496 | |
| 1497 | async function loadUserGroupsList(useCacheOnly) { |
| 1498 | const body = document.getElementById('userGroupsBody'); |
| 1499 | const status = document.getElementById('userGroupsStatus'); |
| 1500 | if (!body) return; |
| 1501 | |
| 1502 | body.textContent = `${t('loading')}…`; |
| 1503 | if (status) { |
| 1504 | status.textContent = ''; |
| 1505 | status.className = 'small text-muted'; |
| 1506 | } |
| 1507 | |
| 1508 | try { |
| 1509 | const users = await fetchAllUsers(); |
| 1510 | |
| 1511 | let groups; |
| 1512 | if (useCacheOnly && __groupsCache && Object.keys(__groupsCache).length) { |
| 1513 | groups = __groupsCache; |
| 1514 | } else { |
| 1515 | groups = await fetchAllGroups(); |
| 1516 | __groupsCache = groups || {}; |
| 1517 | } |
| 1518 | |
| 1519 | const usernames = users |
| 1520 | .map(u => String(u.username || '').trim()) |
| 1521 | .filter(Boolean) |
| 1522 | .sort((a, b) => a.localeCompare(b)); |
| 1523 | |
| 1524 | const groupNames = Object.keys(__groupsCache).sort((a, b) => a.localeCompare(b)); |
| 1525 | if (!groupNames.length) { |
| 1526 | body.innerHTML = `<p class="muted">${tf('no_groups_defined', 'No groups defined yet. Click “Add group” to create one.')}</p>`; |
| 1527 | return; |
| 1528 | } |
| 1529 | |
| 1530 | let html = ''; |
| 1531 | groupNames.forEach(name => { |
| 1532 | const g = __groupsCache[name] || {}; |
| 1533 | const label = g.label || name; |
| 1534 | const members = Array.isArray(g.members) ? g.members : []; |
| 1535 | |
| 1536 | const memberOptions = usernames.map(u => { |
| 1537 | const sel = members.includes(u) ? 'selected' : ''; |
| 1538 | return `<option value="${u}" ${sel}>${u}</option>`; |
| 1539 | }).join(''); |
| 1540 | |
| 1541 | const memberCountLabel = members.length |
| 1542 | ? `${members.length} member${members.length === 1 ? '' : 's'}` |
| 1543 | : 'No members yet'; |
| 1544 | |
| 1545 | html += ` |
| 1546 | <div class="card" data-group-name="${name}" style="margin-bottom:10px; border-radius:8px;"> |
| 1547 | <div class="group-card-header d-flex align-items-center" |
| 1548 | tabindex="0" |
| 1549 | role="button" |
| 1550 | aria-expanded="false" |
| 1551 | style="gap:6px; padding:6px 10px; cursor:pointer; border-radius:8px;"> |
| 1552 | <span class="group-caret" |
| 1553 | style="display:inline-block; transform:rotate(-90deg); transition:transform 120ms ease;">▸</span> |
| 1554 | <i class="material-icons" style="font-size:18px;">group</i> |
no test coverage detected