EntitySelector.svelte 4.88 KB
<script>
  let {
    entidades = [],
    selectedEntity = $bindable(null),
    nEntidades = 0
  } = $props();

  let dropdownOpen = $state(false);
  let searchQuery = $state('');
  let entityLimit = $state(30);

  let filteredEntities = $derived.by(() => {
    if (!searchQuery) return entidades.slice(0, entityLimit);
    const q = searchQuery.toLowerCase();
    return entidades
      .filter(e =>
        e.entidad_desc?.toLowerCase().includes(q) ||
        e.entidad?.toString().includes(q)
      )
      .slice(0, entityLimit);
  });

  function selectEntity(entity) {
    selectedEntity = entity;
    dropdownOpen = false;
    searchQuery = '';
  }

  function clearEntity() {
    selectedEntity = null;
    dropdownOpen = false;
    searchQuery = '';
  }

  function handleScroll(e) {
    const { scrollTop, scrollHeight, clientHeight } = e.target;
    if (scrollTop + clientHeight >= scrollHeight - 50) {
      entityLimit += 20;
    }
  }
</script>

<div class="entity-selector">
  <button class="entity-btn" onclick={() => dropdownOpen = !dropdownOpen}>
    <span class="entity-label">
      {#if selectedEntity}
        {selectedEntity.entidad_desc}
      {:else}
        Todo el sector público
      {/if}
    </span>
    <svg class="chevron" class:open={dropdownOpen} width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
      <path d="M6 9l6 6 6-6"/>
    </svg>
  </button>

  {#if dropdownOpen}
    <div class="entity-dropdown">
      <div class="dropdown-search">
        <input
          type="text"
          bind:value={searchQuery}
          placeholder="Buscar entidad..."
        />
      </div>
      <button
        class="dropdown-item"
        class:active={!selectedEntity}
        onclick={clearEntity}
      >
        Todo el sector público
        <span class="item-count">({nEntidades})</span>
      </button>
      <div class="dropdown-list" onscroll={handleScroll}>
        {#each filteredEntities as entity}
          <button
            class="dropdown-item"
            class:active={selectedEntity?.entidad === entity.entidad}
            onclick={() => selectEntity(entity)}
          >
            <span class="item-code">{entity.entidad}</span>
            <span class="item-name">{entity.entidad_desc}</span>
          </button>
        {/each}
      </div>
    </div>
  {/if}
</div>

<style>
  .entity-selector {
    position: relative;
    flex: 1;
    min-width: 0;
  }

  .entity-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
    padding: 0.125rem 0;
    background: transparent;
    border: none;
    border-bottom: 1px dotted #666666;
    color: var(--theme-titulo);
    font-size: 1rem;
    font-weight: 500;
    cursor: pointer;
    transition: border-color 0.2s;
    max-width: 100%;
    min-width: 0;
  }

  .entity-btn:hover {
    border-bottom: 1px dotted #666666;
  }

  .entity-label {
    text-align: left;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .chevron {
    color: var(--theme-texto);
    opacity: 0.5;
    transition: transform 0.2s;
    flex-shrink: 0;
  }

  .chevron.open {
    transform: rotate(180deg);
  }

  .entity-dropdown {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    min-width: min(280px, calc(100vw - 2rem));
    background: var(--theme-surface);
    border: 1px solid var(--theme-borde);
    border-radius: 10px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
    z-index: 100;
    overflow: hidden;
  }

  .dropdown-search {
    padding: 0.5rem;
    border-bottom: 1px solid var(--theme-borde);
  }

  .dropdown-search input {
    width: 100%;
    padding: 0.5rem 0.75rem;
    border: 1px solid var(--theme-borde);
    border-radius: 6px;
    background: var(--theme-body);
    color: var(--theme-titulo);
    font-size: 0.8125rem;
    outline: none;
  }

  .dropdown-search input:focus {
    border-color: #6B9FD4;
  }

  .dropdown-item {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    width: 100%;
    padding: 0.625rem 0.75rem;
    border: none;
    background: transparent;
    color: var(--theme-titulo);
    font-size: 0.8125rem;
    text-align: left;
    cursor: pointer;
    transition: background 0.15s;
  }

  .dropdown-item:hover {
    background: var(--theme-surface-hover);
  }

  .dropdown-item.active {
    background: rgba(107, 159, 212, 0.1);
    color: #6B9FD4;
  }

  .item-count {
    margin-left: auto;
    font-size: 0.75rem;
    color: var(--theme-texto);
    opacity: 0.6;
  }

  .item-code {
    font-family: 'DM Mono', monospace;
    font-size: 0.6875rem;
    color: var(--theme-texto);
    min-width: 2rem;
  }

  .item-name {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .dropdown-list {
    max-height: 240px;
    overflow-y: auto;
  }

  :global(html.dark) .dropdown-item.active {
    background: rgba(201, 167, 81, 0.1);
    color: var(--theme-accent);
  }
</style>