feat: add board group models and update related interfaces

This commit is contained in:
Abhimanyu Saharan
2026-02-07 20:29:50 +05:30
parent 7b5ee230f5
commit 88a5075684
170 changed files with 12372 additions and 3697 deletions

View File

@@ -2,7 +2,14 @@
import Link from "next/link";
import { usePathname } from "next/navigation";
import { Activity, BarChart3, Bot, LayoutGrid, Network } from "lucide-react";
import {
Activity,
BarChart3,
Bot,
Folder,
LayoutGrid,
Network,
} from "lucide-react";
import { ApiError } from "@/api/mutator";
import {
@@ -13,14 +20,16 @@ import { cn } from "@/lib/utils";
export function DashboardSidebar() {
const pathname = usePathname();
const healthQuery = useHealthzHealthzGet<healthzHealthzGetResponse, ApiError>({
query: {
refetchInterval: 30_000,
refetchOnMount: "always",
retry: false,
const healthQuery = useHealthzHealthzGet<healthzHealthzGetResponse, ApiError>(
{
query: {
refetchInterval: 30_000,
refetchOnMount: "always",
retry: false,
},
request: { cache: "no-store" },
},
request: { cache: "no-store" },
});
);
const okValue = healthQuery.data?.data?.ok;
const systemStatus: "unknown" | "operational" | "degraded" =
@@ -51,7 +60,7 @@ export function DashboardSidebar() {
"flex items-center gap-3 rounded-lg px-3 py-2.5 text-slate-700 transition",
pathname === "/dashboard"
? "bg-blue-100 text-blue-800 font-medium"
: "hover:bg-slate-100"
: "hover:bg-slate-100",
)}
>
<BarChart3 className="h-4 w-4" />
@@ -63,19 +72,31 @@ export function DashboardSidebar() {
"flex items-center gap-3 rounded-lg px-3 py-2.5 text-slate-700 transition",
pathname.startsWith("/gateways")
? "bg-blue-100 text-blue-800 font-medium"
: "hover:bg-slate-100"
: "hover:bg-slate-100",
)}
>
<Network className="h-4 w-4" />
Gateways
</Link>
<Link
href="/board-groups"
className={cn(
"flex items-center gap-3 rounded-lg px-3 py-2.5 text-slate-700 transition",
pathname.startsWith("/board-groups")
? "bg-blue-100 text-blue-800 font-medium"
: "hover:bg-slate-100",
)}
>
<Folder className="h-4 w-4" />
Board groups
</Link>
<Link
href="/boards"
className={cn(
"flex items-center gap-3 rounded-lg px-3 py-2.5 text-slate-700 transition",
pathname.startsWith("/boards")
? "bg-blue-100 text-blue-800 font-medium"
: "hover:bg-slate-100"
: "hover:bg-slate-100",
)}
>
<LayoutGrid className="h-4 w-4" />
@@ -87,7 +108,7 @@ export function DashboardSidebar() {
"flex items-center gap-3 rounded-lg px-3 py-2.5 text-slate-700 transition",
pathname.startsWith("/activity")
? "bg-blue-100 text-blue-800 font-medium"
: "hover:bg-slate-100"
: "hover:bg-slate-100",
)}
>
<Activity className="h-4 w-4" />
@@ -99,7 +120,7 @@ export function DashboardSidebar() {
"flex items-center gap-3 rounded-lg px-3 py-2.5 text-slate-700 transition",
pathname.startsWith("/agents")
? "bg-blue-100 text-blue-800 font-medium"
: "hover:bg-slate-100"
: "hover:bg-slate-100",
)}
>
<Bot className="h-4 w-4" />
@@ -114,7 +135,7 @@ export function DashboardSidebar() {
"h-2 w-2 rounded-full",
systemStatus === "operational" && "bg-emerald-500",
systemStatus === "degraded" && "bg-rose-500",
systemStatus === "unknown" && "bg-slate-300"
systemStatus === "unknown" && "bg-slate-300",
)}
/>
{statusLabel}

View File

@@ -2,10 +2,21 @@
import Link from "next/link";
import { SignInButton, SignedIn, SignedOut, isClerkEnabled } from "@/auth/clerk";
import {
SignInButton,
SignedIn,
SignedOut,
isClerkEnabled,
} from "@/auth/clerk";
const ArrowIcon = () => (
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
aria-hidden="true"
>
<path
d="M6 12L10 8L6 4"
stroke="currentColor"
@@ -81,16 +92,14 @@ export function LandingHero() {
</div>
<div className="hero-features">
{[
"Agent-First Operations",
"Approval Queues",
"Live Signals",
].map((label) => (
<div key={label} className="hero-feature">
<div className="feature-icon"></div>
<span>{label}</span>
</div>
))}
{["Agent-First Operations", "Approval Queues", "Live Signals"].map(
(label) => (
<div key={label} className="hero-feature">
<div className="feature-icon"></div>
<span>{label}</span>
</div>
),
)}
</div>
</div>
@@ -104,7 +113,9 @@ export function LandingHero() {
</div>
<div className="surface-subtitle">
<h3>Ship work without losing the thread.</h3>
<p>Tasks, approvals, and agent status stay synced across the board.</p>
<p>
Tasks, approvals, and agent status stay synced across the board.
</p>
</div>
<div className="metrics-row">
{[
@@ -266,4 +277,3 @@ export function LandingHero() {
</>
);
}

View File

@@ -1,6 +1,13 @@
"use client";
import { memo, useCallback, useLayoutEffect, useMemo, useRef, useState } from "react";
import {
memo,
useCallback,
useLayoutEffect,
useMemo,
useRef,
useState,
} from "react";
import { TaskCard } from "@/components/molecules/TaskCard";
import { parseApiDatetime } from "@/lib/datetime";
@@ -140,6 +147,7 @@ export const TaskBoard = memo(function TaskBoard({
}, []);
useLayoutEffect(() => {
const cardRefsSnapshot = cardRefs.current;
if (animationRafRef.current !== null) {
window.cancelAnimationFrame(animationRafRef.current);
animationRafRef.current = null;
@@ -149,7 +157,7 @@ export const TaskBoard = memo(function TaskBoard({
cleanupTimeoutRef.current = null;
}
for (const taskId of animatedTaskIdsRef.current) {
const element = cardRefs.current.get(taskId);
const element = cardRefsSnapshot.get(taskId);
if (!element) continue;
element.style.transform = "";
element.style.transition = "";
@@ -182,7 +190,7 @@ export const TaskBoard = memo(function TaskBoard({
const dx = prev.left - next.left;
const dy = prev.top - next.top;
if (Math.abs(dx) < 1 && Math.abs(dy) < 1) continue;
const element = cardRefs.current.get(taskId);
const element = cardRefsSnapshot.get(taskId);
if (!element) continue;
moved.push({ taskId, element, dx, dy });
}
@@ -229,7 +237,7 @@ export const TaskBoard = memo(function TaskBoard({
cleanupTimeoutRef.current = null;
}
for (const taskId of animatedTaskIdsRef.current) {
const element = cardRefs.current.get(taskId);
const element = cardRefsSnapshot.get(taskId);
if (!element) continue;
element.style.transform = "";
element.style.transition = "";
@@ -302,10 +310,10 @@ export const TaskBoard = memo(function TaskBoard({
};
const handleDragLeave = (status: TaskStatus) => () => {
if (activeColumn === status) {
setActiveColumn(null);
}
};
if (activeColumn === status) {
setActiveColumn(null);
}
};
return (
<div
@@ -343,9 +351,14 @@ export const TaskBoard = memo(function TaskBoard({
? columnTasks.filter((task) => {
if (reviewBucket === "blocked") return Boolean(task.is_blocked);
if (reviewBucket === "approval_needed")
return (task.approvals_pending_count ?? 0) > 0 && !task.is_blocked;
return (
(task.approvals_pending_count ?? 0) > 0 && !task.is_blocked
);
if (reviewBucket === "waiting_lead")
return !task.is_blocked && (task.approvals_pending_count ?? 0) === 0;
return (
!task.is_blocked &&
(task.approvals_pending_count ?? 0) === 0
);
return true;
})
: columnTasks;
@@ -393,7 +406,11 @@ export const TaskBoard = memo(function TaskBoard({
label: "Lead review",
count: reviewCounts.waiting_lead,
},
{ key: "blocked", label: "Blocked", count: reviewCounts.blocked },
{
key: "blocked",
label: "Blocked",
count: reviewCounts.blocked,
},
] as const
).map((option) => (
<button

View File

@@ -15,7 +15,11 @@ import {
Trello,
} from "lucide-react";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { cn } from "@/lib/utils";
export function UserMenu({ className }: { className?: string }) {