Skip to main content

Documentation Index

Fetch the complete documentation index at: https://quill.co/docs/llms.txt

Use this file to discover all available pages before exploring further.

Used alongside useDashboardReport for fully customizable dashboards. Below is an example with shadcn showing fully custom styling.
App.tsx
import {
  QuillProvider,
  useDashboard,
  useDashboardReport,
  StaticChart,
} from "@quillsql/react";
import { Card, CardHeader } from "@/components/ui/card";
import { Skeleton } from "@/components/ui/skeleton";

function App() {
  return (
    <QuillProvider
      tenants={[{ tenantField: "customer_id", tenantIds: [2] }]}
      publicKey="6579031b3e41c378aa8180ec"
    >
      <CustomDashboard />
    </QuillProvider>
  );
}

function CustomDashboard() {
  const { sections, isLoading } = useDashboard("quill demo dashboard");

  return (
    <>
      <ChartsSection reports={sections["charts"]} />
    </>
  );
}

function ChartsSection({ reports }: { reports: any[] }) {
  return (
    <div className="grid grid-cols-1 lg:grid-cols-6 gap-6">
      {reports.map((report: any) => (
        <ChartCard reportId={report.id} name={report.name} />
      ))}
    </div>
  );
}

function ChartCard({ reportId, name }) {
  const { report, loading } = useDashboardReport(reportId);
  if (loading) {
    return (
      <div className="lg:col-span-1">
        <Card
          className="h-full shadow-none bg-transparent border-none"
          title={name}
        >
          <Skeleton />
        </Card>
      </div>
    );
  }
  return (
    <div className="lg:col-span-1">
      <Card
        className="h-full shadow-none bg-transparent border-none"
        title={name}
      >
        <StaticChart reportId={report.id} />
      </Card>
    </div>
  );
}

Working with filters

useDashboard returns a filters array describing the dashboard’s available filters and an applyFilters function for updating them. Each filter has a type of "select", "multiselect", "date", or "tenant", plus a label, current value, and an options list. The example below renders each filter type with a native control and applies updates via applyFilters:
App.tsx
import {
  QuillProvider,
  useDashboard,
  StaticChart,
} from "@quillsql/react";

function App() {
  return (
    <QuillProvider
      tenants={[{ tenantField: "customer_id", tenantIds: [2] }]}
      publicKey="6579031b3e41c378aa8180ec"
    >
      <FilteredDashboard name="quill demo dashboard" />
    </QuillProvider>
  );
}

function FilteredDashboard({ name }: { name: string }) {
  const { sections, filters, applyFilters, isLoading } = useDashboard(name);

  return (
    <div>
      <div style={{ display: "flex", gap: 12, marginBottom: 16 }}>
        {filters.map((filter) => {
          if (filter.type === "select") {
            return (
              <select
                key={filter.label}
                value={filter.value ?? ""}
                onChange={(e) =>
                  applyFilters([{ label: filter.label, value: e.target.value }])
                }
              >
                <option value="">All</option>
                {filter.options.map((option) => (
                  <option key={option.value} value={option.value}>
                    {option.label}
                  </option>
                ))}
              </select>
            );
          }

          if (filter.type === "multiselect") {
            return (
              <select
                key={filter.label}
                multiple
                value={filter.value}
                onChange={(e) => {
                  const value = Array.from(e.target.selectedOptions).map(
                    (option) => option.value,
                  );
                  applyFilters([{ label: filter.label, value }]);
                }}
              >
                {filter.options.map((option) => (
                  <option key={option.value} value={option.value}>
                    {option.label}
                  </option>
                ))}
              </select>
            );
          }

          if (filter.type === "date") {
            return (
              <select
                key={filter.label}
                value={filter.value.presetValue ?? ""}
                onChange={(e) => {
                  const preset = filter.options.find(
                    (option) => option.value === e.target.value,
                  );
                  if (!preset) return;
                  applyFilters([
                    {
                      label: filter.label,
                      value: {
                        startDate: preset.startDate,
                        endDate: preset.endDate,
                      },
                    },
                  ]);
                }}
              >
                {filter.options.map((option) => (
                  <option key={option.value} value={option.value}>
                    {option.label}
                  </option>
                ))}
              </select>
            );
          }

          if (filter.type === "tenant") {
            // value is string | number | (string | number)[] | null
            const selected = Array.isArray(filter.value)
              ? filter.value.map(String)
              : filter.value === null
                ? ""
                : String(filter.value);
            return (
              <select
                key={filter.label}
                multiple={Array.isArray(selected)}
                value={selected}
                onChange={(e) => {
                  const value = Array.isArray(selected)
                    ? Array.from(e.target.selectedOptions).map((o) => o.value)
                    : e.target.value;
                  applyFilters([{ label: filter.label, value }]);
                }}
              >
                {!Array.isArray(selected) && <option value="">All</option>}
                {filter.options.map((option) => (
                  <option key={option.value} value={String(option.value)}>
                    {option.label}
                  </option>
                ))}
              </select>
            );
          }

          return null;
        })}
      </div>

      {!isLoading &&
        sections &&
        Object.values(sections)
          .flat()
          .map((report) => <StaticChart key={report.id} reportId={report.id} />)}
    </div>
  );
}

Filter shapes

typevalueoptions
"select"string{ label: string; value: string }[]
"multiselect"string[]{ label: string; value: string }[]
"date"{ presetValue?: string; startDate: Date | undefined; endDate: Date | undefined }{ label: string; value: string; startDate?: Date; endDate?: Date }[]
"tenant"string | number | (string | number)[] | null{ label: string; value: string | number }[]
The "tenant" filter only appears if you have multiple Tenants defined in the BI platform and the viewing tenant has a mapping to the dashboard’s owner tenant. For example, a parent organization can use a multiselect tenant filter to view any subset of the child organizations it maps to.

applyFilters payload

applyFilters accepts an array of { label, value } objects. label must match a filter’s label from the filters array, and value must match the shape expected for that filter:
  • "select"value: string
  • "multiselect"value: string[]
  • "date"value: { startDate?: Date; endDate?: Date; preset?: string }
  • "tenant"value: string | string[] (coerce numeric tenant IDs to strings; pass an array when the tenant filter is multi-select)

Parameters

dashboardName
string
required
The name of the dashboard to load
config
object
Configuration options for the dashboard

Returns

isLoading
boolean
Whether the dashboard data is currently loading
sections
Record<string, QuillReport[]> | null
The dashboard sections containing reports, organized by section name
filters
DashboardFilter[]
Available filters for the dashboard. These are typically rendered with html select or similar UI components.
applyFilters
(filters: Array<{ label: string; value: string | string[] | { startDate?: Date; endDate?: Date }; } | Filter>) => void
Function to apply filters to the dashboard