// Widget genérico de vinculação em duas tabelas — não vinculados (esquerda) // e vinculados (direita), com checkbox de seleção múltipla, busca por coluna, // ordenação por nome e um botão de ação em lote por painel ("Vincular"/ // "Desvincular"). Mesmo espírito visual/de interação do painel "Usuários do // Escritório" de perfis-acesso.html (profiles.js), mas genérico o bastante // pra rodar em qualquer shell — hoje reaproveitado por "Usuários sob // liderança" em usuarios.html (users-admin.js) e no modal "Gerenciar // Usuário" (account.js, presente em todo shell). // // A vinculação aqui é só em memória — quem chama decide quando persistir // (chamando getVinculadosIds() no momento de salvar), não dispara nenhuma // requisição por conta própria. // // config = { // available: { selectAll, sortHeader, searchPrincipal, searchSecundaria, body, footer, acaoBtn }, // linked: { ...mesmo formato... }, // secundariaValor: (candidato) => string, // } // candidato = { id, nome, ...outros campos usados por secundariaValor } function pidCriarSeletorDuplo(config) { const paineis = { available: config.available, linked: config.linked }; const secundariaValor = config.secundariaValor || (() => ""); const estado = { available: { sortDir: 1, selected: new Set() }, linked: { sortDir: 1, selected: new Set() }, }; let candidatos = []; let vinculadosIds = new Set(); function secundariaTexto(candidato) { return secundariaValor(candidato) || "—"; } function listaPara(kind) { return kind === "linked" ? candidatos.filter((c) => vinculadosIds.has(c.id)) : candidatos.filter((c) => !vinculadosIds.has(c.id)); } function filtradosOrdenados(kind) { const el = paineis[kind]; const state = estado[kind]; const termoNome = el.searchPrincipal.value.trim().toLowerCase(); const termoSecundaria = el.searchSecundaria.value.trim().toLowerCase(); return listaPara(kind) .filter((c) => !termoNome || c.nome.toLowerCase().includes(termoNome)) .filter((c) => !termoSecundaria || secundariaTexto(c).toLowerCase().includes(termoSecundaria)) .slice() .sort((a, b) => a.nome.localeCompare(b.nome, "pt-BR", { sensitivity: "base" }) * state.sortDir); } function renderPainel(kind) { const el = paineis[kind]; const state = estado[kind]; // Poda a seleção de ids que saíram da lista (ex.: acabou de ser // vinculado/desvinculado e migrou pro outro painel). const idsAtuais = new Set(listaPara(kind).map((c) => c.id)); Array.from(state.selected).forEach((id) => { if (!idsAtuais.has(id)) state.selected.delete(id); }); const filtrados = filtradosOrdenados(kind); el.body.innerHTML = filtrados.length ? filtrados .map( (c) => `