Skip to main content

Overview

Dashboards in VizBoard are collections of widgets organized in a customizable grid layout. Each project has a dashboard where you can add, arrange, and configure widgets to visualize your data. Dashboards support drag-and-drop reordering and responsive layouts.

Dashboard Structure

Every dashboard is associated with a project and contains:

Widgets

Data visualizations and content elements

Layout Order

Customizable widget arrangement

Project Context

Project title and description

Navigation

Sidebar for adding and managing widgets

Creating a Dashboard

Dashboards are automatically created when you create a project. The dashboard starts empty and you add widgets to build your visualization.
1

Navigate to Project Dashboard

Access your project’s dashboard:
// Dashboard URL format
/projects/{projectId}/dashboard
For public projects, a public URL is also available:
// Public dashboard URL format
/public/{idPublic}
2

Add Widgets

Click the sidebar to browse available widget types:
src/components/dashboard/navigation/addWidget.tsx
export function AddWidget({ onItemClick }) {
  return (
    <SidebarMenu>
      {widgets.map((widget, index) => {
        const Icon = widget.icon;
        return (
          <Collapsible key={index}>
            <CollapsibleTrigger>
              <SidebarMenuButton>
                <Icon className="mr-2 h-4 w-4" />
                <span>{t(widget.type)}</span>
              </SidebarMenuButton>
            </CollapsibleTrigger>
            <CollapsibleContent>
              {widget.subtypes.map((subtype) => (
                <SidebarMenuButton
                  onClick={() => {
                    const normalized = extractWidgetTypeInfo({
                      type: widget.type,
                      subtype,
                    });
                    onItemClick(normalized.type, normalized.subtype);
                  }}
                >
                  {t(subtype)}
                </SidebarMenuButton>
              ))}
            </CollapsibleContent>
          </Collapsible>
        );
      })}
    </SidebarMenu>
  );
}
3

Configure Widgets

Configure each widget with data sources, columns, and visualization options using the configuration dialog.
4

Arrange Layout

Reorder widgets using drag-and-drop or the widget order controls.

Dashboard Layout

Dashboards use a responsive grid layout that adapts to screen sizes:
src/components/dashboard/layouts/dashboardLayout.tsx
<div
  className="grid grid-cols-2 gap-4 md:gap-8 p-4 md:p-8 overflow-x-auto"
  style={{ WebkitOverflowScrolling: "touch" }}
>
  {orderedWidgets.map((widget) => {
    // Render widget based on type
    return renderWidget(widget);
  })}
</div>

Layout Features

Widgets are arranged in a 2-column grid:
grid-cols-2        /* 2 columns on all screens */
gap-4 md:gap-8     /* Responsive gap spacing */
p-4 md:p-8         /* Responsive padding */
The grid automatically adjusts for:
  • Desktop: 2 columns with larger gaps
  • Mobile: 2 columns with smaller gaps
  • Overflow: Horizontal scroll for wide content

Widget Ordering

Customize the display order of widgets on your dashboard.

Update Widget Order

Change the order by updating the orderedWidgetIds array:
import { updateWidgetOrder } from "@/app/actions/dashboard/updateWidgetOrder";

const newOrder = [
  "widget-id-3",  // First widget
  "widget-id-1",  // Second widget
  "widget-id-2",  // Third widget
];

const result = await updateWidgetOrder(projectId, newOrder);

if (result.success) {
  console.log("Widget order updated");
} else {
  console.error("Failed to update order:", result.error);
}

Server Action Implementation

The widget order is stored as an array in the project:
src/app/actions/dashboard/updateWidgetOrder.ts
export async function updateWidgetOrder(
  projectId: string,
  newOrder: string[]
) {
  try {
    await prisma.project.update({
      where: { id: projectId },
      data: {
        orderedWidgetIds: { set: newOrder },
      },
    });
    return { success: true };
  } catch (error) {
    console.error("Error updating widget order:", error);
    return { success: false, error: "Failed to update widget order" };
  }
}
The orderedWidgetIds array contains widget IDs in the order they should be displayed. Widgets not in the array are appended at the end in their creation order.

Dashboard Header

The dashboard header provides navigation and project context:
<header className="flex h-20 shrink-0 items-center gap-2 transition-[width,height] border-b">
  <div className="flex items-center justify-between w-full gap-2 px-4 md:px-8">
    <div className="flex items-center gap-4">
      <SidebarTrigger />  {/* Toggle widget sidebar */}
    </div>
    <UserMenu user={user} />  {/* User menu */}
  </div>
</header>

Project Title and Description

The dashboard displays the project title prominently:
<Accordion type="single" collapsible className="w-full">
  <AccordionItem value="description">
    <AccordionTrigger className="text-3xl px-4 md:px-8">
      <h1>{projectData.title}</h1>
    </AccordionTrigger>
    <AccordionContent className="text-muted-foreground px-4 md:px-8">
      <p>{projectData.description}</p>
    </AccordionContent>
  </AccordionItem>
</Accordion>
The description is collapsible to save space.

Widget Configuration Dialog

The configuration dialog allows you to edit widget settings:
<ConfigDialog projectId={projectData.id} />
The dialog provides:
  • Data source selection
  • Column/axis configuration
  • Visualization options
  • Filtering and aggregation settings
See Widgets for detailed widget configuration options.

Empty State

When a dashboard has no widgets, a helpful empty state is displayed:
{showNoWidgets ? (
  <div className="h-full col-span-full flex flex-col items-center justify-center p-8 text-center text-muted-foreground">
    <Grid2x2Plus className="h-12 w-12 mb-4" />
    <h3 className="text-xl font-semibold mb-2">
      {t("dashboard_no_widgets_title", { project: projectData.title })}
      <br />
      {t("dashboard_no_widgets_subtitle")}
    </h3>
  </div>
) : (
  // Render widgets
)}

Public Dashboards

Public dashboards allow sharing your visualizations without authentication.

Enable Public Access

import { updateProject } from "@/app/actions/project";

const result = await updateProject({
  id: projectId,
  isPublic: true,  // Enable public access
});

if (result.success) {
  // Project.idPublic is auto-generated
  console.log("Public URL: /public/" + project.idPublic);
}
Projects can only be made public if they have at least one valid database connection. Public access is automatically disabled if all connections become invalid.

Public vs Private Layout

Public dashboards use a simplified layout:
// Full functionality for project owner
- Widget editing and configuration
- Add/remove widgets
- Reorder widgets
- Project settings access
- Full sidebar navigation

Loading States

Dashboards display appropriate loading states:
if (!projectData) {
  return <Loading text={t("dashboard_loading_project_data")} />;
}

if (isLoading && !useWidgetsStore.getState().hasLoadedOnce) {
  return <Loading text={t("dashboard_loading_widgets")} />;
}

if (error) {
  return (
    <div className="h-full col-span-full flex flex-col items-center justify-center">
      <Grid2x2X className="h-12 w-12 mb-4" />
      <h3 className="text-xl font-semibold mb-2">
        {t("dashboard_error_loading_widgets")}
      </h3>
    </div>
  );
}

Widget State Management

Dashboards use Zustand for widget state management:
const { widgets, orderedWidgetIds, fetchWidgets, isLoading, error } =
  useWidgetsStore();

const { resetWidgets } = useWidgetsStore();

useEffect(() => {
  if (projectData?.id) {
    resetWidgets();  // Clear previous state
    fetchWidgets(projectData.id);  // Fetch widgets for current project
  }
}, [projectData?.id]);

State Structure

interface WidgetsStore {
  widgets: Widget[];           // All widgets for current project
  orderedWidgetIds: string[];  // Display order
  isLoading: boolean;          // Loading state
  error: string | null;        // Error message
  hasLoadedOnce: boolean;      // Has loaded at least once
  fetchWidgets: (projectId: string) => Promise<void>;
  resetWidgets: () => void;    // Clear state
}

Dashboard Organization

Best practices for organizing your dashboards:
1

Use Section Titles

Add section_title text widgets to divide your dashboard into logical sections:
await UpsertWidget({
  projectId: projectId,
  title: "Sales Metrics",
  type: "text_titles",
  subtype: "section_title",
  configs: {
    content: "Sales Metrics",
    fontSize: "large"
  }
});
2

Group Related Widgets

Place related visualizations near each other by managing widget order:
const order = [
  "section-title-sales",
  "widget-revenue-chart",
  "widget-sales-table",
  "section-title-expenses",
  "widget-expenses-chart",
  "widget-expenses-table"
];

await updateWidgetOrder(projectId, order);
3

Add Context

Use text_block widgets to provide descriptions and context:
await UpsertWidget({
  projectId: projectId,
  title: "About This Dashboard",
  type: "text_titles",
  subtype: "text_block",
  configs: {
    content: "This dashboard shows quarterly sales performance..."
  }
});

Performance Optimization

Optimize dashboard performance for large datasets:

Lazy Loading

Widgets are loaded progressively as the dashboard renders

Data Aggregation

Use aggregations in widget configs to reduce data transfer

Pagination

Enable pagination for data table widgets

Caching

Schemas are cached to avoid repeated introspection

Keyboard Shortcuts

Use keyboard shortcuts for faster dashboard navigation:
ShortcutAction
Ctrl/Cmd + KOpen command palette
Ctrl/Cmd + \Toggle sidebar
EscapeClose dialogs
TabNavigate between widgets

Best Practices

Logical Flow

Arrange widgets in a logical reading order (top to bottom, left to right)

Consistent Styling

Use consistent colors and chart types across related widgets

Clear Titles

Give widgets descriptive titles that explain what they show

Test Public View

Review public dashboards to ensure they work as expected

Troubleshooting

Symptoms: Dashboard shows loading state indefinitelySolutions:
  • Check browser console for errors
  • Verify project has valid database connections
  • Ensure widgets have valid configurations
  • Clear browser cache and reload
Symptoms: Widgets don’t reorder after calling updateWidgetOrderSolutions:
  • Verify orderedWidgetIds contains all widget IDs
  • Check that widget IDs are valid
  • Refresh the dashboard page
  • Verify no client-side caching issues
Symptoms: Public URL shows “not found” errorSolutions:
  • Verify project isPublic: true
  • Check that project has at least one valid connection
  • Confirm idPublic field is set
  • Verify no typos in the public URL

Next Steps

Widgets

Learn about widget types

Projects

Manage projects

Database Connections

Configure data sources

Build docs developers (and LLMs) love