Kurs vibe coding z AI · Moduł 8: Slack Clone

Eksport danych - raporty CSV i PDF

3 min czytania
W tej lekcji2

Panel wygląda świetnie, ale kwatermistrz chce mieć liczby w arkuszu kalkulacyjnym, a armator raport w PDF. Zamiast przepisywać tabelę ręcznie, dodamy dwa przyciski: Export CSV i Export PDF. Oba pliki powstaną w przeglądarce, bez udziału serwera.

Eksport do CSV

CSV to zwykły tekst: pierwszy wiersz zawiera nagłówki, a każdy kolejny jeden rekord z wartościami oddzielonymi przecinkami. Funkcja exportToCSV składa taki tekst i każe przeglądarce go pobrać, a DataTable wywołuje ją przyciskiem:

function exportToCSV(data: any[], filename: string) {
  const headers = Object.keys(data[0]).join(',');
  const rows = data.map(row => Object.values(row).join(','));
  const csv = [headers, ...rows].join('\n');

  const blob = new Blob([csv], { type: 'text/csv' });
  const url = URL.createObjectURL(blob);
  const link = document.createElement('a');
  link.href = url;
  link.download = filename;
  link.click();
}

function DataTable({ data }: { data: any[] }) {
  return (
    <div>
      <button
        onClick={() => exportToCSV(data, 'report.csv')}
        className="bg-green-600 text-white px-4 py-2 rounded mb-4"
      >
        Export CSV
      </button>
      <table className="w-full border">
        {/* Table content */}
      </table>
    </div>
  );
}

Kroki są cztery. Object.keys(data[0]).join(',') tworzy wiersz nagłówków z kluczy pierwszego obiektu, Object.values(row).join(',') robi to samo z wartościami, a całość łączymy znakiem nowej linii (join('\n')). Potem powstaje Blob z typem text/csv, czyli plik trzymany w pamięci przeglądarki. URL.createObjectURL(blob) daje mu tymczasowy adres, a link.download = filename i link.click() uruchamiają pobieranie pod nazwą report.csv. Dane w tabeli zostają bez zmian. Ten sam wzorzec pobierze każdy plik zbudowany w przeglądarce, np. JSON.

Na produkcję przydadzą się trzy poprawki. Wartość z przecinkiem, cudzysłowem albo nową linią rozjedzie kolumny, więc otocz ją cudzysłowami, a cudzysłów w środku podwój, tak jak każe standard CSV:

const escapeCell = (value: unknown) => `"${String(value).replace(/"/g, '""')}"`;

Funkcja zwraca nowy tekst komórki i nie rusza oryginalnej wartości. Po pobraniu zwolnij adres przez URL.revokeObjectURL(url), a na samym początku sprawdź data.length, bo pusta tablica wysypie funkcję na data[0].

Eksport do PDF

PDF wygeneruje biblioteka jsPDF, a tabelę narysuje jej dodatek jspdf-autotable. Obie instalujesz razem, a samo polecenie nie zmienia jeszcze żadnego komponentu:

npm install jspdf jspdf-autotable

Po instalacji eksport mieści się w kilku linijkach: tworzysz dokument, wpisujesz tytuł, rysujesz tabelę i zapisujesz plik.

import jsPDF from 'jspdf';
import autoTable from 'jspdf-autotable';

function exportToPDF(data: any[], filename: string) {
  const doc = new jsPDF();

  doc.text('Analytics Report', 14, 15);

  autoTable(doc, {
    head: [Object.keys(data[0])],
    body: data.map(row => Object.values(row))
  });

  doc.save(filename);
}

new jsPDF() tworzy dokument, domyślnie A4 z wymiarami w milimetrach. doc.text('Analytics Report', 14, 15) wpisuje tytuł 14 mm od lewej i 15 mm od góry, a autoTable rysuje tabelę z nagłówkami (head) i wierszami (body). head to tablica wierszy nagłówka, stąd podwójne nawiasy wokół Object.keys(data[0]), a body zawiera po jednej tablicy wartości na rekord. doc.save(filename) pobiera gotowy plik, a tabela na stronie zostaje bez zmian. Standardowe czcionki PDF nie mają polskich znaków, więc dla ą czy ł dodaj własną czcionkę TTF. Eksport wywołuj w komponencie klienckim, np. w onClick, bo pobieranie dzieje się w przeglądarce.

Rada Kapitana Redbearda: arkusz może potraktować wartość zaczynającą się od =, +, - albo @ jak formułę. Gdy eksportujesz dane wpisane przez użytkowników, poprzedź takie wartości apostrofem, żeby nikt nie przemycił formuły w ładunku.

Zapamiętaj: CSV to tekst w Blobie, PDF to dokument z jsPDF, a oba trafiają do użytkownika przez pobranie w przeglądarce. W edytorze poniżej masz raport kanału z eksportem do CSV, a zadania sprawdzą kroki eksportu.

Kod do tej lekcji: App.jsx
// Eksport danych - pobieranie pliku CSV
import { useState } from 'react';

const reportData = [
  { channel: 'treasure-hunts', messages: 142, members: 8 },
  { channel: 'general', messages: 89, members: 12 },
  { channel: 'navigation', messages: 56, members: 5 }
];

function exportToCSV(data, filename) {
  const headers = Object.keys(data[0]).join(',');
  const rows = data.map(row => Object.values(row).join(','));
  const csv = [headers, ...rows].join('\n');

  const blob = new Blob([csv], { type: 'text/csv' });
  const url = URL.createObjectURL(blob);
  const link = document.createElement('a');
  link.href = url;
  link.download = filename;
  link.click();
}

export default function App() {
  return (
    <div className="min-h-screen bg-gray-100 p-8">
      <div className="max-w-3xl mx-auto bg-white border rounded p-6">
        <div className="flex items-center justify-between mb-4">
          <h1 className="text-xl font-bold">Raport kanałów</h1>
          <button
            onClick={() => exportToCSV(reportData, 'report.csv')}
            className="bg-green-600 text-white px-4 py-2 rounded hover:bg-green-700 text-sm font-semibold"
          >
            Eksportuj CSV
          </button>
        </div>

        <table className="w-full border text-left text-sm">
          <thead className="bg-gray-50">
            <tr>
              {Object.keys(reportData[0]).map(key => (
                <th key={key} className="border px-3 py-2 font-semibold">{key}</th>
              ))}
            </tr>
          </thead>
          <tbody>
            {reportData.map((row, idx) => (
              <tr key={idx}>
                {Object.values(row).map((val, i) => (
                  <td key={i} className="border px-3 py-2">{val}</td>
                ))}
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Jaki typ MIME ustawiamy w Blob podczas eksportu CSV?

  2. 2. Jak tworzymy wiersz nagłówków CSV z obiektu danych?

To 2 z 3 pytań do tej lekcji. Pozostałe rozwiążesz w grze.

Zadania praktyczne w grze

  • Edytor kodu

    Eksport danych do pliku CSV

  • Układanie w pionie

    Uporządkuj kroki pobierania pliku CSV w przeglądarce:

Przydatne artykuły