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:
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
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
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
npm install --save-dev @veltdev/types
You’ll need an API key to use Velt.
Go to Velt’s console and get your free API key.
Add the API key in your .env
file:
NEXT_PUBLIC_VELT_API_KEY=your-api-key
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
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 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
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:
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
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>
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",
}
)
);
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
fromzustand
: 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 asetUser
function to update the user's state. - The
persist
middleware saves the store’s state to the browser’slocalStorage
. This means that when the app reloads, the store is loaded fromlocalStorage
. - The
"user-storage"
option is used to give a unique key for the data inlocalStorage
. -
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}</>;
}
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 theidentify
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
</>
-
const { user, setUser } = useUserStore()
: The header component gets access to the global user state and thesetUser
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>
// ...
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>
)
}
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",
});
-
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}
/>
-
<VeltComments />
: This component allows you to add the commenting feature to your app. It handles the UI and logic for placing and viewing comments.
-
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 theVeltComment
, 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]);
-
client.getCommentElement()
andclient.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 theautocompleteSearch
event. This event fires whenever a user types after an @ symbol. - Filtering Logic: Inside the subscription, you filter your
users
array based on thesearchText
, and then usecontactElement.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>
);
}
-
<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
];
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(),
});
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>
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
- Velt Documentation
- TanStack Table Docs
- Next.js Docs
- Kiro AI IDE
- Tailwind CSS
- GitHub repo and live demo link
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)
Great Article!
Thanks Arindam