Skip to content

Datentabellen-Demo ​

Diese Demo zeigt, wie VOIX Benutzern bei der Interaktion mit Datentabellen helfen kann - Sortieren, Filtern und Exportieren von Daten mit natürlicher Sprache.

Wie es funktioniert ​

Die Datentabelle bietet Werkzeuge zur Bearbeitung und zum Export von tabellarischen Daten, wobei der Kontext den aktuellen Tabellenzustand anzeigt.

Code ​

html
<!-- Datentabellen-Werkzeuge -->
<tool name="sort_table" description="Die Tabelle nach einer Spalte sortieren">
  <prop name="column" type="string" description="Spaltenname: name, email, role, joined oder status"></prop>
  <prop name="order" type="string" description="asc oder desc"></prop>
</tool>

<tool name="filter_table" description="Tabellenzeilen filtern">
  <prop name="column" type="string" description="Zu filternde Spalte"></prop>
  <prop name="value" type="string" description="Filterwert"></prop>
</tool>

<tool name="select_rows" description="Zeilen nach Kriterien auswählen">
  <prop name="criteria" type="string" description="all, none oder spezifischer Status"></prop>
</tool>

<tool name="export_data" description="Tabellendaten exportieren">
  <prop name="format" type="string" description="csv, json oder Zwischenablage"></prop>
  <prop name="selected" type="boolean" description="Nur ausgewählte Zeilen exportieren"></prop>
</tool>

<tool name="reset_table" description="Alle Filter und Sortierungen zurücksetzen">
</tool>

<!-- Tabellenkontext -->
<context name="table_state" id="table-context">
  Gesamtzeilen: 8
  Sichtbare Zeilen: 8
  Ausgewählte Zeilen: 0
  Sortierung: keine
  Filter: keiner
  Verfügbare Spalten: name, email, role, joined, status
</context>

<!-- Tabellen-UI -->
<div id="table-container">
  <div id="table-controls">
    <span id="selection-info">0 Zeilen ausgewählt</span>
    <div id="active-filters"></div>
  </div>
  
  <table id="data-table">
    <thead>
      <tr>
        <th><input type="checkbox" id="select-all"></th>
        <th data-column="name">Name <span class="sort-icon"></span></th>
        <th data-column="email">E-Mail <span class="sort-icon"></span></th>
        <th data-column="role">Rolle <span class="sort-icon"></span></th>
        <th data-column="joined">Beigetreten <span class="sort-icon"></span></th>
        <th data-column="status">Status <span class="sort-icon"></span></th>
      </tr>
    </thead>
    <tbody id="table-body"></tbody>
  </table>
  
  <div id="export-message"></div>
</div>

<script>
// Beispieldaten
const tableData = [
  { id: 1, name: "Alice Johnson", email: "alice@example.com", role: "Admin", joined: "2023-01-15", status: "Active" },
  { id: 2, name: "Bob Smith", email: "bob@example.com", role: "User", joined: "2023-02-20", status: "Active" },
  { id: 3, name: "Carol Davis", email: "carol@example.com", role: "Editor", joined: "2023-03-10", status: "Active" },
  { id: 4, name: "David Wilson", email: "david@example.com", role: "User", joined: "2023-04-05", status: "Inactive" },
  { id: 5, name: "Eve Martinez", email: "eve@example.com", role: "Admin", joined: "2023-05-12", status: "Active" },
  { id: 6, name: "Frank Brown", email: "frank@example.com", role: "Editor", joined: "2023-06-08", status: "Active" },
  { id: 7, name: "Grace Lee", email: "grace@example.com", role: "User", joined: "2023-07-22", status: "Pending" },
  { id: 8, name: "Henry Taylor", email: "henry@example.com", role: "User", joined: "2023-08-30", status: "Active" }
];

// Zustand
let displayData = [...tableData];
let sortColumn = null;
let sortOrder = null;
let filters = {};
let selectedRows = new Set();

// Werkzeug-Handler
const handlers = {
  sort_table: ({ column, order = 'asc' }) => {
    sortColumn = column;
    sortOrder = order;
    applyFiltersAndSort();
  },
  
  filter_table: ({ column, value }) => {
    if (value) {
      filters[column] = value.toLowerCase();
    } else {
      delete filters[column];
    }
    applyFiltersAndSort();
  },
  
  select_rows: ({ criteria }) => {
    selectedRows.clear();
    
    if (criteria === 'all') {
      displayData.forEach(row => selectedRows.add(row.id));
    } else if (criteria === 'none') {
      // Bereits geleert
    } else {
      // Nach Status auswählen
      displayData.forEach(row => {
        if (row.status.toLowerCase() === criteria.toLowerCase()) {
          selectedRows.add(row.id);
        }
      });
    }
    
    updateUI();
  },
  
  export_data: ({ format, selected = false }) => {
    const dataToExport = selected 
      ? displayData.filter(row => selectedRows.has(row.id))
      : displayData;
    
    switch (format) {
      case 'csv':
        exportCSV(dataToExport);
        break;
      case 'json':
        exportJSON(dataToExport);
        break;
      case 'clipboard':
        copyToClipboard(dataToExport);
        break;
    }
  },
  
  reset_table: () => {
    sortColumn = null;
    sortOrder = null;
    filters = {};
    selectedRows.clear();
    applyFiltersAndSort();
  }
};

// Event-Listener anhängen
document.querySelectorAll('tool[name]').forEach(tool => {
  const name = tool.getAttribute('name');
  tool.addEventListener('call', (e) => {
    handlers[name]?.(e.detail);
  });
});

// Klicks auf Tabellenkopf
document.querySelectorAll('th[data-column]').forEach(th => {
  th.addEventListener('click', () => {
    const column = th.dataset.column;
    const newOrder = sortColumn === column && sortOrder === 'asc' ? 'desc' : 'asc';
    handlers.sort_table({ column, order: newOrder });
  });
});

// Alle auswählen Checkbox
document.getElementById('select-all').addEventListener('change', (e) => {
  if (e.target.checked) {
    handlers.select_rows({ criteria: 'all' });
  } else {
    handlers.select_rows({ criteria: 'none' });
  }
});

// Filter und Sortierung anwenden
function applyFiltersAndSort() {
  // Mit allen Daten beginnen
  displayData = [...tableData];
  
  // Filter anwenden
  Object.entries(filters).forEach(([column, value]) => {
    displayData = displayData.filter(row => 
      row[column].toLowerCase().includes(value)
    );
  });
  
  // Sortierung anwenden
  if (sortColumn) {
    displayData.sort((a, b) => {
      const aVal = a[sortColumn];
      const bVal = b[sortColumn];
      const comparison = aVal < bVal ? -1 : aVal > bVal ? 1 : 0;
      return sortOrder === 'asc' ? comparison : -comparison;
    });
  }
  
  updateUI();
}

// UI aktualisieren
function updateUI() {
  // Tabellenkörper rendern
  const tbody = document.getElementById('table-body');
  tbody.innerHTML = displayData.map(row => `
    <tr>
      <td><input type="checkbox" value="${row.id}" ${selectedRows.has(row.id) ? 'checked' : ''}></td>
      <td>${row.name}</td>
      <td>${row.email}</td>
      <td>${row.role}</td>
      <td>${row.joined}</td>
      <td><span class="status-badge status-${row.status.toLowerCase()}">${row.status}</span></td>
    </tr>
  `).join('');
  
  // Zeilenauswahl
  tbody.querySelectorAll('input[type="checkbox"]').forEach(cb => {
    cb.addEventListener('change', (e) => {
      const id = parseInt(e.target.value);
      if (e.target.checked) {
        selectedRows.add(id);
      } else {
        selectedRows.delete(id);
      }
      updateSelectionInfo();
    });
  });
  
  // Sortierungsindikatoren aktualisieren
  document.querySelectorAll('.sort-icon').forEach(icon => {
    icon.textContent = '';
  });
  if (sortColumn) {
    const icon = document.querySelector(`[data-column="${sortColumn}"] .sort-icon`);
    icon.textContent = sortOrder === 'asc' ? '▲' : '▼';
  }
  
  // Filteranzeige aktualisieren
  const filterDisplay = document.getElementById('active-filters');
  const filterTexts = Object.entries(filters).map(([col, val]) => `${col}: "${val}"`);
  filterDisplay.textContent = filterTexts.length ? `Filter: ${filterTexts.join(', ')}` : '';
  
  updateSelectionInfo();
  updateContext();
}

// Auswahl-Info aktualisieren
function updateSelectionInfo() {
  document.getElementById('selection-info').textContent = `${selectedRows.size} Zeilen ausgewählt`;
  document.getElementById('select-all').checked = selectedRows.size === displayData.length && displayData.length > 0;
}

// Kontext aktualisieren
function updateContext() {
  const sortText = sortColumn ? `${sortColumn} ${sortOrder}` : 'keine';
  const filterText = Object.keys(filters).length ? JSON.stringify(filters) : 'keiner';
  
  document.getElementById('table-context').textContent = `
Gesamtzeilen: ${tableData.length}
Sichtbare Zeilen: ${displayData.length}
Ausgewählte Zeilen: ${selectedRows.size}
Sortierung: ${sortText}
Filter: ${filterText}
Verfügbare Spalten: name, email, role, joined, status

Rollen: ${[...new Set(tableData.map(r => r.role))].join(', ')}
Status: ${[...new Set(tableData.map(r => r.status))].join(', ')}
  `;
}

// Exportfunktionen
function exportCSV(data) {
  const headers = ['name', 'email', 'role', 'joined', 'status'];
  const csv = [
    headers.join(','),
    ...data.map(row => headers.map(h => `"${row[h]}"`).join(','))
  ].join('\n');
  
  showMessage(`CSV exportiert (${data.length} Zeilen)`);
  console.log('CSV:', csv);
}

function exportJSON(data) {
  const json = JSON.stringify(data, null, 2);
  showMessage(`JSON exportiert (${data.length} Zeilen)`);
  console.log('JSON:', json);
}

function copyToClipboard(data) {
  const text = data.map(row => 
    `${row.name}\t${row.email}\t${row.role}\t${row.joined}\t${row.status}`
  ).join('\n');
  
  navigator.clipboard.writeText(text);
  showMessage(`${data.length} Zeilen in die Zwischenablage kopiert`);
}

function showMessage(text) {
  const msg = document.getElementById('export-message');
  msg.textContent = text;
  setTimeout(() => msg.textContent = '', 3000);
}

// Erstes Rendern
updateUI();
</script>

<style>
#table-container {
  max-width: 100%;
  overflow-x: auto;
}

#table-controls {
  display: flex;
  justify-content: space-between;
  margin: 1rem 0;
  font-size: 0.875rem;
}

#data-table {
  width: 100%;
  border-collapse: collapse;
  background: white;
}

#data-table th,
#data-table td {
  padding: 0.75rem;
  text-align: left;
  border-bottom: 1px solid #ddd;
}

#data-table th {
  background: #f5f5f5;
  font-weight: bold;
  cursor: pointer;
  user-select: none;
}

#data-table th:hover {
  background: #e9e9e9;
}

.sort-icon {
  margin-left: 0.5rem;
  font-size: 0.75rem;
}

.status-badge {
  padding: 0.25rem 0.5rem;
  border-radius: 4px;
  font-size: 0.875rem;
  font-weight: 500;
}

.status-active {
  background: #d4edda;
  color: #155724;
}

.status-inactive {
  background: #f8d7da;
  color: #721c24;
}

.status-pending {
  background: #fff3cd;
  color: #856404;
}

#export-message {
  margin: 1rem 0;
  padding: 0.75rem;
  background: #d1ecf1;
  color: #0c5460;
  border-radius: 4px;
  text-align: center;
  display: none;
}

#export-message:not(:empty) {
  display: block;
}
</style>

Probieren Sie es aus ​

Total rows: 8 Visible rows: 8 Selected rows: 0 Sort: none Filter: none Available columns: name, email, role, joined, status Roles: Admin, User, Editor Statuses: Active, Inactive, Pending
0 rows selected
Name Email Role Joined Status
Alice Johnsonalice@example.comAdmin2023-01-15Active
Bob Smithbob@example.comUser2023-02-20Active
Carol Daviscarol@example.comEditor2023-03-10Active
David Wilsondavid@example.comUser2023-04-05Inactive
Eve Martinezeve@example.comAdmin2023-05-12Active
Frank Brownfrank@example.comEditor2023-06-08Active
Grace Leegrace@example.comUser2023-07-22Pending
Henry Taylorhenry@example.comUser2023-08-30Active

Beispielinteraktionen ​

Versuchen Sie dies mit VOIX:

  • "Nach Name aufsteigend sortieren"
  • "Rolle filtern, um nur Admins anzuzeigen"
  • "Alle aktiven Benutzer auswählen"
  • "Ausgewählte Zeilen als CSV exportieren"
  • "Benutzer anzeigen, die nach dem 01.06.2023 beigetreten sind"
  • "Tabelle zurücksetzen"

Die KI liest den Tabellenkontext, um verfügbare Spalten, aktuelle Filter und Daten zu verstehen.

Released under the MIT License.