DEV Community

Cover image for Build a Collaborative Inventory Dashboard with TanStack, Velt SDK and Kiro IDE⛵
Astrodevil
Astrodevil

Posted on

Build a Collaborative Inventory Dashboard with TanStack, Velt SDK and Kiro IDE⛵

Introduction

Inventory management is a crucial part of any business that sells or deals with physical goods. Managing inventory allows business owners or managers to have an accurate understanding of their business inventory, including sell-through rates, what they need to order, and what might not be selling.

However, traditional inventory management tools often lack collaborative features. This leads to misinformation or late notice of product shortages.

A Deloitte study showed that among employees who collaborate in the workplace, 73% do better work, and 60% are more innovative.

Now, imagine if those traditional inventory management tools had features such as live commenting and @mentions, features that enable the inventory management team to collaborate without needing to be online simultaneously. That would make life so much easier for the inventory management team.

In this article, you’ll learn how to build a collaborative inventory management dashboard with live commenting, presence and @mentions features.

You’ll be using the following technologies:

  • Next.js, TanStack
  • Velt SDK for comments, @mentions, and notifications
  • Kiro AI IDE for fast UI scaffolding

Our Final app will look like this:

Final App

Setting Up the Core Architecture

In this section, you’re going to set up your project infrastructure.

Creating a Next.js project with TypeScript

Open your terminal and create a new Next.js project using the following command:

npx create-next-app@latest
Enter fullscreen mode Exit fullscreen mode

Terminal

Follow the prompts to create your project.

Make sure you select TypeScript for type safety.

Adding TanStack Table for creating and managing tables

The inventory management dashboard will be a table for products. You’ll use TanStack Table to create and manage tables in the app.

TanStack Table is a headless UI library for building tables and data grids for JavaScript/TypeScript applications. Since it’s a headless UI library, you have control over the design and components.

In your project terminal, use the following command to install TanStack Table:

npm install @tanstack/react-table
Enter fullscreen mode Exit fullscreen mode

Adding Velt for real-time commenting and @mentions

You’ll use Velt SDK to add real-time commenting and @mentions features to the inventory dashboard.

You may be wondering: what’s Velt?

Velt is a developer platform that enables development teams to integrate AI-powered real-time collaboration features into their web applications. Velt SDK provides developers with ready-made full-stack components to add collaboration features to their apps.

Use the following commands to install the Velt packages in your project:

npm install @veltdev/react
Enter fullscreen mode Exit fullscreen mode
npm install --save-dev @veltdev/types
Enter fullscreen mode Exit fullscreen mode

You’ll need an API key to use Velt.
Go to Velt’s console and get your free API key.

Velt Dashboard

Add the API key in your .env file:

 NEXT_PUBLIC_VELT_API_KEY=your-api-key
Enter fullscreen mode Exit fullscreen mode

Note: You can only use the free API key in development. You have to upgrade to the paid version to use Velt in production.

User state management is needed as you’ll have multiple users who are going to collaborate in your dashboard.

Use the following command to install Zustand:

npm install zustand
Enter fullscreen mode Exit fullscreen mode

You’ve installed the necessary packages and have your Velt API key. In the next section, you’ll scaffold your app using AI.

Building the UI with Kiro AI IDE

In this section, you’ll build the UI of your app using Kiro AI IDE.

What is Kiro AI IDE?

Kiro AI IDE

Kiro is an experimental, agentic, AI-powered integrated development environment (IDE) built by AWS. With its agentic capabilities, Kiro AI IDE can take your idea and build a working app. Kiro AI IDE understands your entire codebase, enabling it to make changes based on this understanding.

Since it’s experimental, access is invite-only. You can apply or join the waitlist.

We’ll use Kiro AI IDE to build the UI of our inventory dashboard. This will save us time and allow us to focus on adding the collaboration features to the app.

Generating the inventory dashboard UI

Use the following prompt to generate the UI:

Create a collaborative inventory management dashboard with the following requirements:

SUMMARY: A simple dashboard showing inventory data and allowing internal discussion on stock issues and fulfillment problems.

KEY FEATURES:
- Data grid with inventory items, status, stock levels
- Detail view for each record
- Comments on specific items with @tag mentions
- Reactions and sidebar filter for discussions
- Focus on inventory management rather than shipment tracking

TECH STACK:
- TanStack Table for data grid
- Next.js with TypeScript
- Velt comments and notifications for collaboration
- Modern UI with responsive design

USE CASE: Shows how warehouse and logistics teams can comment on low stock items, supplier issues, and inventory problems collaboratively. Team members should be able to @mention each other (like @steve @elli) to coordinate restocking and resolve inventory issues.

Make it professional and production-ready with proper error handling and TypeScript compliance.
Remove Discussions and reactions section from side bar
make UI attractive
remove these blue colors looks overwhelming make rest components shorter and professional here
make stock threshold components little color full cant see entire close and edit buttons
Enter fullscreen mode Exit fullscreen mode

Open your development server, and you should have something similar to this in your browser, complete with the main inventory table and user selection in the header:

Inventory app table

Project structure

Your project structure should be similar to this:

├── app/                    # Next.js App Router
│   ├── page.tsx           # Main dashboard page
│   ├── layout.tsx         # Root layout
│   ├── globals.css        # Global styles
│   └── favicon.ico        # App icon
├── components/            # React components
│   ├── InventoryTable.tsx # Main data grid
│   ├── DetailView.tsx     # Item detail modal
│   ├── Sidebar.tsx        # Navigation sidebar
│   ├── header.tsx         # Top navigation header
│   ├── FilterDropdowns.tsx # Filter components
│   ├── Header.module.css  # Header styles
│   ├── Sidebar.module.css # Sidebar styles
│   └── ui/                # Reusable UI components
├── helper/               # Utilities
│   └── userdb.ts         # User management
├── lib/                  # Shared libraries
│   └── utils.ts          # Utility functions
├── types/                # TypeScript definitions
│   └── delivery.ts       # Data models
├── utils/                # Helper functions
│   ├── delivery-transform.ts # Data transformation
│   └── delivery-validation.ts # Data validation
├── images/               # Screenshots and assets
├── docs/                 # Documentation
│   └── kiro-prompts.md   # AI prompts used
└── public/               # Static assets
Enter fullscreen mode Exit fullscreen mode

In the next section, you’ll add the collaboration features using Velt.

Adding Collaboration Features with Velt

In this section, you’ll add collaboration features to your dashboard.

Before you start adding the collaboration features, let me explain a few concepts.

First, you'll need to wrap your app in the VeltProvider component. The Provider pattern is common in React applications. The VeltProvider component uses the Context API to make the Velt client accessible to any component in the tree without prop drilling.

Next, you need an authentication component which will allow Velt to identify users in your dashboard. In a real-world app, the auth component will be part of your authentication system. For this tutorial, we'll create a mock user database in helper/userdb.ts and sync it to a Zustand store to simulate a real authentication system.

Finally, you need a way to tell Velt to associate comments with your document (page). You’ll do that using the useSetDocument hook from the Velt SDK.

Wrapping your application in a VeltProvider component

In your layout.tsx file, wrap your app in the VeltProvider component:

<VeltProvider apiKey={process.env.NEXT_PUBLIC_VELT_API_KEY || ""}>
    <main>
        <Header />
        <div>
            {children}
        </div>
    </main>
</VeltProvider>
Enter fullscreen mode Exit fullscreen mode

Creating an Auth component

As I mentioned before, Velt needs to be able to identify users in our app. Without it, we cannot add comments or notifications.

Create a file, helper/userdb.ts, and add the following code:

import { create } from "zustand";
import { persist } from "zustand/middleware";

export type User = {
  uid: string;
  displayName: string;
  email: string;
  photoUrl?: string;
};

export interface UserStore {
  user: User | null;
  setUser: (user: User) => void;
}

export const userIds = ["user001", "user002"];
export const names = ["Steve", "Elli"];

export const useUserStore = create<UserStore>()(
  persist(
    (set) => ({
      user: null,
      setUser: (user) => set({ user }),
    }),
    {
      name: "user-storage",
    }
  )
);
Enter fullscreen mode Exit fullscreen mode

You created userIds and names arrays for two users: Steve and Elli. Of course, in your application, you’d get users from an auth fetch or similar.

The useUserStore, create, and persist are for creating and persisting a user in Zustand. You can substitute them for your preferred state management method.

  • create from zustand: creates a Zustand store. A Zustand store is a central location for your app’s state and functions that modify that state.
  • UserStore interface: defines the shape of our store, which includes a user object (which can be null if no one is logged in) and a setUser function to update the user's state.
  • The persist middleware saves the store’s state to the browser’s localStorage. This means that when the app reloads, the store is loaded from localStorage.
  • The "user-storage" option is used to give a unique key for the data in localStorage.
  • useUserStore hook: a custom hook that our components will use to access the user data (user) and the function to modify it (setUser).

Note: You can replace Zustand with your preferred method of state management.

Now that you have a way to manage the user's state globally, you need to let Velt know who the current user is. This is essential for all of Velt's collaborative features, like live cursors, comments, and notifications.

Create a file, VeltAuth.tsx, and add the following code:

"use client";

import { useVeltClient } from "@veltdev/react";
import { useEffect } from "react";
import { useUserStore } from "@/helper/userdb";

export default function VeltAuth({ children }: { children: React.ReactNode }) {
  const { client } = useVeltClient();
  const { user } = useUserStore();

  useEffect(() => {
    if (!client || !user) return;

    // Identify the user with Velt
    const veltUser = {
      userId: user.uid,
      name: user.displayName,
      email: user.email,
      photoUrl: user.photoUrl,
      organizationId: "inventory-org-001", // Single organization for this app
    };

    // Authenticate the user with Velt
    client.identify(veltUser);
  }, [client, user]);

  return <>{children}</>;
}
Enter fullscreen mode Exit fullscreen mode

Let’s walk through the code:

  • useVeltClient(): This hook from the Velt SDK gives you access to the Velt client instance.
  • useUserStore(): your custom hook to get the current user from your Zustand store.
  • useEffect: runs whenever the client or user object changes.
  • const veltUser = {...} : You’re creating a Velt user. The structure is what Velt expects to identify users correctly in your app.
  • client.identify(veltUser): This is the key part. You call the identify method on the Velt client, passing in a user object with the required information. This action authenticates the user with Velt's backend, effectively "logging them in" to the collaborative session.

The required properties in the veltUser are userId, name, email, organizationId, and photoUrl. You can include additional properties, such as color, used in the background color of a user’s avatar, and textColor used for the color of the user’s initials if photoUrl is not specified.

Now that you’ve created your auth component, let’s look at how you’ll use it in your UI.

Create a header.tsx component that will allow users to “log in” (by selecting a user from a dropdown in this demo).

"use client";

import { useUserStore } from "@/helper/userdb";
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";

const Header: React.FC = () => {
  const { user, setUser } = useUserStore();

  // ... (logic for predefined users)
  useEffect(() => {
    if (typeof window !== "undefined" && !user) {
      // Set default user if no user is set
      setUser(predefinedUsers[0]);
    }
  }, [user, setUser, predefinedUsers]);


  // UI rendering
  return (
    <>
    // ...
      {/* User Dropdown */}
      {user && (
                <div className={styles['user-dropdown']} ref={dropdownRef}>
                  <button
                    className={styles['user-btn']}
                    type="button"
                    onClick={() => setDropdownOpen((open) => !open)}
                  >
                    <Avatar>
                      <AvatarImage src={user.photoUrl} alt={user.displayName} />
                      <AvatarFallback>{user.displayName[0]}</AvatarFallback>
                    </Avatar>
                    <span className={styles['user-name']}>{user.displayName}</span>
                    <svg className={`ml-1 w-4 h-4 transition-transform ${dropdownOpen ? styles['rotate-180'] : styles['rotate-0']}`} fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" d="M19 9l-7 7-7-7" /></svg>
                  </button>
                  {dropdownOpen && (
                    <div className={styles['user-dropdown-menu']}>
                      {predefinedUsers.map((predefinedUser) => (
                        <div
                          key={predefinedUser.uid}
                          className={styles['user-dropdown-item'] + (user.uid === predefinedUser.uid ? ' ' + styles['user-dropdown-item-active'] : '')}
                          onClick={() => {
                            console.log('Switching user from', user?.displayName, 'to', predefinedUser.displayName);
                            setUser(predefinedUser);
                            setDropdownOpen(false);
                          }}
                        >
                          <Avatar>
                            <AvatarImage src={predefinedUser.photoUrl} alt={predefinedUser.displayName} />
                            <AvatarFallback>{predefinedUser.displayName[0]}</AvatarFallback>
                          </Avatar>
                          <span>{predefinedUser.displayName}</span>
                        </div>
                      ))}
                    </div>
                  )}
                </div>
              )}
      // rest of code
        </>

Enter fullscreen mode Exit fullscreen mode
  • const { user, setUser } = useUserStore(): The header component gets access to the global user state and the setUser function.
  • On the initial load, if no user is set in the store, it defaults to the first predefined user.
  • The UI displays the user's avatar and display name, which it gets from the user object.
  • The dropdown menu lets you switch between predefined users. Selecting a new user calls the setUser(predefinedUser) function, updating the global state in our Zustand store.

You’ve created your auth component and a simulation of users logging into your app. However, there’s one thing you need to do to make it work.

You need to add the VeltAuth.tsx to the layout.tsx file.

Update the layout.tsx file:

import VeltAuth from '../components/VeltAuth';

//...

<VeltProvider apiKey={process.env.NEXT_PUBLIC_VELT_API_KEY || ""}>
    <VeltAuth />
    <main>
        <Header />
        <div>
            {children}
        </div>
    </main>
</VeltProvider>

// ...
Enter fullscreen mode Exit fullscreen mode

Setting our document

The page.tsx is the main entry point to our app and the core of the Inventory dashboard application. This is where the primary UI is rendered and where the Velt collaborative features are configured for the main content area.

The main content area is the InventoryTable component. See the repo for the full code.

"use client";

import { useSetDocument, VeltComments, useVeltClient } from "@veltdev/react";
import React, { useEffect, useState } from "react";
import { userIds, names } from "@/helper/userdb";

export default function Page() {

    const { client } = useVeltClient();

  const users = userIds.map((id, index) => {
    const avatarUrls = [
      "https://api.dicebear.com/7.x/avataaars/svg?seed=Steve",
      "https://api.dicebear.com/7.x/avataaars/svg?seed=Elli",
    ];
    return {
      userId: id,
      name: names[index],
      email: `${names[index].toLowerCase()}@inventory.com`,
      photoUrl: avatarUrls[index],
    };
  });

  useSetDocument("inventory-dashboard-main", {
    documentName: "Inventory Dashboard Discussion",
  });

  useEffect(() => {
    if (!client)
       return;

    const commentElement = client.getCommentElement();
    const contactElement = client.getContactElement();

    commentElement.enableCustomAutocompleteSearch();

    contactElement.updateContactList(users);

    const subscription = commentElement
      .on("autocompleteSearch")
      .subscribe(async (inputData: { type: string; searchText: string }) => {
        if (inputData.type === "contact") {
          const searchText = inputData.searchText.toLowerCase();

          const filteredUsers = users.filter((user) =>
            user.name.toLowerCase().includes(searchText)
          );

          contactElement.updateContactList(filteredUsers, { merge: false });
        }
      });

    return () => {
      subscription.unsubscribe();
    };
  }, [client, users]);

  return (
    <div className="h-full w-full flex flex-col bg-white dark:bg-gray-900 m-0 p-0">
      <VeltComments
        popoverMode={true}
        customAutocompleteSearch={true}
        darkMode={isDarkMode}
      />

      // part of the code 

      {/* Main Content */}
      <div className="flex-1 p-4">
        <InventoryTable
          data={inventoryData}
          selectedMetric={selectedMetric}
          selectedCategory={selectedCategory}
        />
      </div>  
  )
}
Enter fullscreen mode Exit fullscreen mode

Let’s break down the code.

Setting the Collaboration Context with useSetDocument

The first and most important Velt-related action on this page is setting up a "document." In Velt's terminology, a document is a unique collaboration space. All comments, annotations, and other interactions on this page will be associated with the ID of this document.

useSetDocument("inventory-dashboard-main", {
documentName: "Inventory Dashboard Discussion",
});
Enter fullscreen mode Exit fullscreen mode
  • useSetDocument: This hook from @veltdev/react initializes the collaboration space for this page.
  • "inventory-dashboard-main": This is the unique ID for our document. By using a consistent ID, Velt ensures that every user who visits this page will see the same set of comments and annotations.
  • documentName: This provides a human-readable name for the collaboration space, which can be useful for organization and debugging.

Enabling Comments with the <VeltComments /> Component

To allow users to add comments anywhere on the page, we use the <VeltComments /> component.

<VeltComments
    popoverMode={true}
    customAutocompleteSearch={true}
    darkMode={isDarkMode}
/>
Enter fullscreen mode Exit fullscreen mode
  • <VeltComments />: This component allows you to add the commenting feature to your app. It handles the UI and logic for placing and viewing comments.

App in dark mode

  • popoverMode={true}: This prop configures the comments to appear in popovers attached to the elements they are placed on, which is a common and intuitive UI pattern.
  • customAutocompleteSearch={true}: This is an advanced feature that tells Velt you want to provide your logic for the @mention autocomplete, which you'll see next.
  • darkMode={isDarkMode}: This allows the comment UI to adapt to the application's current theme (light or dark), ensuring a consistent look and feel. This feature is not part of the VeltComment, it’s a feature added to improve the dashboard’s aesthetics.

It's important to know that by adding the VeltComments component, you enable a range of collaboration features by default. These features include threaded replies, @mentions, read receipts, and being able to resolve comment threads.

These built-in features allow you to add a rich, "multiplayer" experience to your app.

Customizing the @mention Experience

For a richer user experience, you implemented a custom search for the @mention functionality
within comments. This ensures that users can easily find and tag their colleagues.

useEffect(() => {
    if (!client) return;
    const commentElement = client.getCommentElement();
    const contactElement = client.getContactElement();
    commentElement.enableCustomAutocompleteSearch();
    contactElement.updateContactList(users);
    const subscription = commentElement
        .on("autocompleteSearch")
        .subscribe(async (inputData: { type: string; searchText: string }) => {
            if (inputData.type === "contact") {
            const searchText = inputData.searchText.toLowerCase();
            const filteredUsers = users.filter((user) =>
            user.name.toLowerCase().includes(searchText)
            );
            contactElement.updateContactList(filteredUsers, { merge: false });
            }
        });
    return () => {
    subscription.unsubscribe();
    };
}, [client, users]);
Enter fullscreen mode Exit fullscreen mode
  • client.getCommentElement() and client.getContactElement(): These give you control over Velt's comment and contact elements.
  • enableCustomAutocompleteSearch(): This allows you to enable the custom search feature.
  • updateContactList(users): You provide Velt with an initial list of all possible users to mention.
  • commentElement.on("autocompleteSearch").subscribe(...): You subscribe to the autocompleteSearch event. This event fires whenever a user types after an @ symbol.
  • Filtering Logic: Inside the subscription, you filter your users array based on the searchText, and then use contactElement.updateContactList(filteredUsers) to show the filtered results in the “mention” dropdown.

Rendering the Application UI

Finally, the page component is responsible for rendering the main UI of the dashboard.

export default function Page() {

    // rest of the code

   return (
    <div className="h-full w-full flex flex-col bg-white dark:bg-gray-900 m-0 p-0">

      {/* Filter Dropdowns */}
      <FilterDropdowns
        selectedMetric={selectedMetric}
        selectedCategory={selectedCategory}
        onMetricChange={setSelectedMetric}
        onCategoryChange={setSelectedCategory}
      />

      {/* Header Row */}
      // some part of the code

      {/* Main Content */}
      <div className="flex-1 p-4">
        <InventoryTable
          data={inventoryData}
          selectedMetric={selectedMetric}
          selectedCategory={selectedCategory}
        />
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode
  • <FilterDropdowns />: A component that allows users to filter the inventory data.
  • <InventoryTable />: The main component that displays all the inventory items in a table.

Building the collaborative data grid with TanStack Table

The <InventoryTable /> component, rendered by page.tsx, is at the core of our inventory dashboard. The table isn't just a static data display; it's an interactive and collaborative grid.

We use TanStack Table to achieve this level of custom functionality. Using TanStack Table allows full markup control and efficient handling of thousands of rows with virtualization.

Defining the Table Structure

The first step is to define columns for your table. TanStack Table uses a simple array of column objects. The real power comes from the cell renderer, which lets you define custom JSX for each cell.

Here’s a look at how we define the Status column:

// ../components/InventoryTable.tsx

const columns: ColumnDef<InventoryItem>[] = [
  // ... other column definitions
  {
    accessorKey: "Status",
    header: "Status",
    cell: ({ row }) => {
      const status = row.original.Status;
      const statusClass = getStatusClass(status); // Helper to get a CSS class
      return (
        <div className={`px-2 py-1 rounded-full text-xs font-medium text-center 
          ${statusClass}`}>
          {status}
        </div>
      );
    },
  },
  // ... other column definitions
];
Enter fullscreen mode Exit fullscreen mode

This approach gives you full control to render custom components, like the colored status
badges, instead of just plain text.

Powering the table with the useReactTable hook

The useReactTable hook, from tanstack/react-table, makes the component work. You provide the hook with your data, the columns you have defined, and indicate which features you want to enable (such as sorting).

const table = useReactTable({
    data: inventoryData,
    columns,
    state: {
        sorting,
    },
    onSortingChange: setSorting,
    getCoreRowModel: getCoreRowModel(),
    getSortedRowModel: getSortedRowModel(), 
});
Enter fullscreen mode Exit fullscreen mode

The table instance returned by the useReactTable hook contains everything you need to render the table and manage its state.

Integrating the Collaborative Layer

In this part, you add Velt's components directly into the table's rendered output to attach collaborative features to your data.

The rendering logic looks like this:

// In the component's return statement
<tbody>
  {table.getRowModel().rows.map((row) => (
    <VeltCommentsAnnotation
      key={row.id}
      id={row.original.id} // Unique ID for the comment thread
      location={{
        page: "inventory-dashboard",
        element: "table-row",
      }}
    >
      <tr className="dark:hover:bg-gray-700">
        {row.getVisibleCells().map((cell) => (
          <td key={cell.id} className="p-4 border-b ...">
            <VeltPresence
              id={`${row.original.id}-${cell.column.id}`} // Unique ID for the cell
              location={{
                page: "inventory-dashboard",
                element: "table-cell",
              }}
            >
              {flexRender(cell.column.columnDef.cell, cell.getContext())}
            </VeltPresence>
          </td>
        ))}
      </tr>
    </VeltCommentsAnnotation>
  ))}
</tbody>
Enter fullscreen mode Exit fullscreen mode

There are two key Velt components at play here:

<VeltCommentsAnnotation />: You wrap each table row (<tr>) with this component. By setting a unique id based on the inventory item's ID (row.original.id), you attach a unique comment thread to each row. This allows users to have discussions about a specific inventory item, and the comments will be anchored to that row.

<VeltPresence />: You wrap the content of every single cell (<td>) with this component. The id is a combination of the row and column IDs, making every cell a unique location. This enables Velt to show the live avatars of other users on the specific cells they are currently viewing or interacting with. It creates a "multiplayer" feel, as you can see exactly what your teammates are looking at in real-time.

That’s how you build a collaborative inventory dashboard using Vibe Coding Skills and Kiro IDE

Here’s the demo of the app: https://velt-inventory.vercel.app/

Conclusion

In this article, you learned how to build a collaborative inventory management dashboard. You used TanStack Table to create a flexible data grid, and Velt for real-time collaboration features. The whole project was initialized with Kiro IDE, and we have customized other parts using the same IDE. You can also vibe-code some cool apps using Kiro. If you’ve already tried Kiro, I’d love to hear about your experience with it.

This tutorial showed that you don't need a complex backend to add live comments and user presence to your apps. This means that you spend more time on what makes your app unique.

You now have a foundation to build upon for your projects.

Resources


Thankyou for reading! If you found this article useful, share it with your peers and community.

If You ❤️ My Content! Connect Me on Twitter

Check SaaS Tools I Use 👉🏼Access here!

I am open to collaborating on Blog Articles and Guest Posts🫱🏼‍🫲🏼 📅Contact Here

Top comments (2)

Collapse
 
arindam_1729 profile image
Arindam Majumder

Great Article!

Collapse
 
astrodevil profile image
Astrodevil

Thanks Arindam