Skip to main content
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={process.env.QUILL_PUBLIC_KEY}
    >
      <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={process.env.QUILL_PUBLIC_KEY}
    >
      <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. See the QuillReport reference for the full schema.
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