shadimage
herocn
blockdashboard-01source: ok · hit

Dashboard 01

A dashboard with sidebar, charts and data table.

npx shadcn add "https://herocn.dev/r/dashboard-01.json"
free

Where it goes

sidebardashboard-shelldata-tablekanban

Use cases

dashboard

Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.

Dependencies

Bundle weight · light
formszod

Registry dependencies

@herocn/sidebar@herocn/breadcrumb@herocn/separator@herocn/label@herocn/chart@herocn/card@herocn/select@herocn/tabs@herocn/table@herocn/toggle-group@herocn/badge@herocn/button@herocn/checkbox@herocn/dropdown-menu@herocn/drawer@herocn/input@herocn/avatar@herocn/sheet@herocn/toast

Files11 · source cached 10/8/2026, 1:39:50 PM

page.tsx
import { AppSidebar } from "@/components/app-sidebar";
import { ChartAreaInteractive } from "@/components/chart-area-interactive";
import { DataTable } from "@/components/data-table";
import { SectionCards } from "@/components/section-cards";
import { SiteHeader } from "@/components/site-header";
import {
	SidebarInset,
	SidebarProvider,
} from "@/components/ui/sidebar";

import data from "./data.json";

export default function Page() {
	return (
		<SidebarProvider>
			<AppSidebar />
			<SidebarInset>
				<SiteHeader />
				<div className="flex flex-1 flex-col">
					<div className="@container/main flex flex-1 flex-col gap-2">
						<div className="flex flex-col gap-4 py-4 md:gap-6 md:py-6">
							<SectionCards />
							<div className="px-4 lg:px-6">
								<ChartAreaInteractive />
							</div>
							<DataTable data={data} />
						</div>
					</div>
				</div>
			</SidebarInset>
		</SidebarProvider>
	);
}
data.json
[
	{
		"id": 1,
		"header": "Cover page",
		"type": "Cover page",
		"status": "In Process",
		"target": "18",
		"limit": "5",
		"reviewer": "Eddie Lake"
	},
	{
		"id": 2,
		"header": "Table of contents",
		"type": "Table of contents",
		"status": "Done",
		"target": "29",
		"limit": "24",
		"reviewer": "Eddie Lake"
	},
	{
		"id": 3,
		"header": "Executive summary",
		"type": "Narrative",
		"status": "Done",
		"target": "10",
		"limit": "13",
		"reviewer": "Eddie Lake"
	},
	{
		"id": 4,
		"header": "Technical approach",
		"type": "Narrative",
		"status": "Done",
		"target": "27",
		"limit": "23",
		"reviewer": "Jamik Tashpulatov"
	},
	{
		"id": 5,
		"header": "Design",
		"type": "Narrative",
		"status": "In Process",
		"target": "2",
		"limit": "16",
		"reviewer": "Jamik Tashpulatov"
	},
	{
		"id": 6,
		"header": "Capabilities",
		"type": "Narrative",
		"status": "In Process",
		"target": "20",
		"limit": "8",
		"reviewer": "Jamik Tashpulatov"
	},
	{
		"id": 7,
		"header": "Integration with existing systems",
		"type": "Narrative",
		"status": "In Process",
		"target": "19",
		"limit": "21",
		"reviewer": "Jamik Tashpulatov"
	},
	{
		"id": 8,
		"header": "Innovation and Advantages",
		"type": "Narrative",
		"status": "Done",
		"target": "25",
		"limit": "26",
		"reviewer": "Assign reviewer"
	},
	{
		"id": 9,
		"header": "Overview of EMR's Innovative Solutions",
		"type": "Technical content",
		"status": "Done",
		"target": "7",
		"limit": "23",
		"reviewer": "Assign reviewer"
	},
	{
		"id": 10,
		"header": "Advanced Algorithms and Machine Learning",
		"type": "Narrative",
		"status": "Done",
		"target": "30",
		"limit": "28",
		"reviewer": "Assign reviewer"
	},
	{
		"id": 11,
		"header": "Adaptive Communication Protocols",
		"type": "Narrative",
		"status": "Done",
		"target": "9",
		"limit": "31",
		"reviewer": "Assign reviewer"
	},
	{
		"id": 12,
		"header": "Advantages Over Current Technologies",
		"type": "Narrative",
		"status": "Done",
		"target": "12",
		"limit": "0",
		"reviewer": "Assign reviewer"
	},
	{
		"id": 13,
		"header": "Past Performance",
		"type": "Narrative",
		"status": "Done",
		"target": "22",
		"limit": "33",
		"reviewer": "Assign reviewer"
	},
	{
		"id": 14,
		"header": "Customer Feedback and Satisfaction Levels",
		"type": "Narrative",
		"status": "Done",
		"target": "15",
		"limit": "34",
		"reviewer": "Assign reviewer"
	},
	{
		"id": 15,
		"header": "Implementation Challenges and Solutions",
		"type": "Narrative",
		"status": "Done",
		"target": "3",
		"limit": "35",
		"reviewer": "Assign reviewer"
	},
	{
		"id": 16,
		"header": "Security Measures and Data Protection Policies",
		"type": "Narrative",
		"status": "In Process",
		"target": "6",
		"limit": "36",
		"reviewer": "Assign reviewer"
	},
	{
		"id": 17,
		"header": "Scalability and Future Proofing",
		"type": "Narrative",
		"status": "Done",
		"target": "4",
		"limit": "37",
		"reviewer": "Assign reviewer"
	},
	{
		"id": 18,
		"header": "Cost-Benefit Analysis",
		"type": "Plain language",
		"status": "Done",
		"target": "14",
		"limit": "38",
		"reviewer": "Assign reviewer"
	},
	{
		"id": 19,
		"header": "User Training and Onboarding Experience",
		"type": "Narrative",
		"status": "Done",
		"target": "17",
		"limit": "39",
		"reviewer": "Assign reviewer"
	},
	{
		"id": 20,
		"header": "Future Development Roadmap",
		"type": "Narrative",
		"status": "Done",
		"target": "11",
		"limit": "40",
		"reviewer": "Assign reviewer"
	},
	{
		"id": 21,
		"header": "System Architecture Overview",
		"type": "Technical content",
		"status": "In Process",
		"target": "24",
		"limit": "18",
		"reviewer": "Maya Johnson"
	},
	{
		"id": 22,
		"header": "Risk Management Plan",
		"type": "Narrative",
		"status": "Done",
		"target": "15",
		"limit": "22",
		"reviewer": "Carlos Rodriguez"
	},
	{
		"id": 23,
		"header": "Compliance Documentation",
		"type": "Legal",
		"status": "In Process",
		"target": "31",
		"limit": "27",
		"reviewer": "Sarah Chen"
	},
	{
		"id": 24,
		"header": "API Documentation",
		"type": "Technical content",
		"status": "Done",
		"target": "8",
		"limit": "12",
		"reviewer": "Raj Patel"
	},
	{
		"id": 25,
		"header": "User Interface Mockups",
		"type": "Visual",
		"status": "In Process",
		"target": "19",
		"limit": "25",
		"reviewer": "Leila Ahmadi"
	},
	{
		"id": 26,
		"header": "Database Schema",
		"type": "Technical content",
		"status": "Done",
		"target": "22",
		"limit": "20",
		"reviewer": "Thomas Wilson"
	},
	{
		"id": 27,
		"header": "Testing Methodology",
		"type": "Technical content",
		"status": "In Process",
		"target": "17",
		"limit": "14",
		"reviewer": "Assign reviewer"
	},
	{
		"id": 28,
		"header": "Deployment Strategy",
		"type": "Narrative",
		"status": "Done",
		"target": "26",
		"limit": "30",
		"reviewer": "Eddie Lake"
	},
	{
		"id": 29,
		"header": "Budget Breakdown",
		"type": "Financial",
		"status": "In Process",
		"target": "13",
		"limit": "16",
		"reviewer": "Jamik Tashpulatov"
	},
	{
		"id": 30,
		"header": "Market Analysis",
		"type": "Research",
		"status": "Done",
		"target": "29",
		"limit": "32",
		"reviewer": "Sophia Martinez"
	},
	{
		"id": 31,
		"header": "Competitor Comparison",
		"type": "Research",
		"status": "In Process",
		"target": "21",
		"limit": "19",
		"reviewer": "Assign reviewer"
	},
	{
		"id": 32,
		"header": "Maintenance Plan",
		"type": "Technical content",
		"status": "Done",
		"target": "16",
		"limit": "23",
		"reviewer": "Alex Thompson"
	},
	{
		"id": 33,
		"header": "User Personas",
		"type": "Research",
		"status": "In Process",
		"target": "27",
		"limit": "24",
		"reviewer": "Nina Patel"
	},
	{
		"id": 34,
		"header": "Accessibility Compliance",
		"type": "Legal",
		"status": "Done",
		"target": "18",
		"limit": "21",
		"reviewer": "Assign reviewer"
	},
	{
		"id": 35,
		"header": "Performance Metrics",
		"type": "Technical content",
		"status": "In Process",
		"target": "23",
		"limit": "26",
		"reviewer": "David Kim"
	},
	{
		"id": 36,
		"header": "Disaster Recovery Plan",
		"type": "Technical content",
		"status": "Done",
		"target": "14",
		"limit": "17",
		"reviewer": "Jamik Tashpulatov"
	},
	{
		"id": 37,
		"header": "Third-party Integrations",
		"type": "Technical content",
		"status": "In Process",
		"target": "25",
		"limit": "28",
		"reviewer": "Eddie Lake"
	},
	{
		"id": 38,
		"header": "User Feedback Summary",
		"type": "Research",
		"status": "Done",
		"target": "20",
		"limit": "15",
		"reviewer": "Assign reviewer"
	},
	{
		"id": 39,
		"header": "Localization Strategy",
		"type": "Narrative",
		"status": "In Process",
		"target": "12",
		"limit": "19",
		"reviewer": "Maria Garcia"
	},
	{
		"id": 40,
		"header": "Mobile Compatibility",
		"type": "Technical content",
		"status": "Done",
		"target": "28",
		"limit": "31",
		"reviewer": "James Wilson"
	},
	{
		"id": 41,
		"header": "Data Migration Plan",
		"type": "Technical content",
		"status": "In Process",
		"target": "19",
		"limit": "22",
		"reviewer": "Assign reviewer"
	},
	{
		"id": 42,
		"header": "Quality Assurance Protocols",
		"type": "Technical content",
		"status": "Done",
		"target": "30",
		"limit": "33",
		"reviewer": "Priya Singh"
	},
	{
		"id": 43,
		"header": "Stakeholder Analysis",
		"type": "Research",
		"status": "In Process",
		"target": "11",
		"limit": "14",
		"reviewer": "Eddie Lake"
	},
	{
		"id": 44,
		"header": "Environmental Impact Assessment",
		"type": "Research",
		"status": "Done",
		"target": "24",
		"limit": "27",
		"reviewer": "Assign reviewer"
	},
	{
		"id": 45,
		"header": "Intellectual Property Rights",
		"type": "Legal",
		"status": "In Process",
		"target": "17",
		"limit": "20",
		"reviewer": "Sarah Johnson"
	},
	{
		"id": 46,
		"header": "Customer Support Framework",
		"type": "Narrative",
		"status": "Done",
		"target": "22",
		"limit": "25",
		"reviewer": "Jamik Tashpulatov"
	},
	{
		"id": 47,
		"header": "Version Control Strategy",
		"type": "Technical content",
		"status": "In Process",
		"target": "15",
		"limit": "18",
		"reviewer": "Assign reviewer"
	},
	{
		"id": 48,
		"header": "Continuous Integration Pipeline",
		"type": "Technical content",
		"status": "Done",
		"target": "26",
		"limit": "29",
		"reviewer": "Michael Chen"
	},
	{
		"id": 49,
		"header": "Regulatory Compliance",
		"type": "Legal",
		"status": "In Process",
		"target": "13",
		"limit": "16",
		"reviewer": "Assign reviewer"
	},
	{
		"id": 50,
		"header": "User Authentication System",
		"type": "Technical content",
		"status": "Done",
		"target": "28",
		"limit": "31",
		"reviewer": "Eddie Lake"
	},
	{
		"id": 51,
		"header": "Data Analytics Framework",
		"type": "Technical content",
		"status": "In Process",
		"target": "21",
		"limit": "24",
		"reviewer": "Jamik Tashpulatov"
	},
	{
		"id": 52,
		"header": "Cloud Infrastructure",
		"type": "Technical content",
		"status": "Done",
		"target": "16",
		"limit": "19",
		"reviewer": "Assign reviewer"
	},
	{
		"id": 53,
		"header": "Network Security Measures",
		"type": "Technical content",
		"status": "In Process",
		"target": "29",
		"limit": "32",
		"reviewer": "Lisa Wong"
	},
	{
		"id": 54,
		"header": "Project Timeline",
		"type": "Planning",
		"status": "Done",
		"target": "14",
		"limit": "17",
		"reviewer": "Eddie Lake"
	},
	{
		"id": 55,
		"header": "Resource Allocation",
		"type": "Planning",
		"status": "In Process",
		"target": "27",
		"limit": "30",
		"reviewer": "Assign reviewer"
	},
	{
		"id": 56,
		"header": "Team Structure and Roles",
		"type": "Planning",
		"status": "Done",
		"target": "20",
		"limit": "23",
		"reviewer": "Jamik Tashpulatov"
	},
	{
		"id": 57,
		"header": "Communication Protocols",
		"type": "Planning",
		"status": "In Process",
		"target": "15",
		"limit": "18",
		"reviewer": "Assign reviewer"
	},
	{
		"id": 58,
		"header": "Success Metrics",
		"type": "Planning",
		"status": "Done",
		"target": "30",
		"limit": "33",
		"reviewer": "Eddie Lake"
	},
	{
		"id": 59,
		"header": "Internationalization Support",
		"type": "Technical content",
		"status": "In Process",
		"target": "23",
		"limit": "26",
		"reviewer": "Jamik Tashpulatov"
	},
	{
		"id": 60,
		"header": "Backup and Recovery Procedures",
		"type": "Technical content",
		"status": "Done",
		"target": "18",
		"limit": "21",
		"reviewer": "Assign reviewer"
	},
	{
		"id": 61,
		"header": "Monitoring and Alerting System",
		"type": "Technical content",
		"status": "In Process",
		"target": "25",
		"limit": "28",
		"reviewer": "Daniel Park"
	},
	{
		"id": 62,
		"header": "Code Review Guidelines",
		"type": "Technical content",
		"status": "Done",
		"target": "12",
		"limit": "15",
		"reviewer": "Eddie Lake"
	},
	{
		"id": 63,
		"header": "Documentation Standards",
		"type": "Technical content",
		"status": "In Process",
		"target": "27",
		"limit": "30",
		"reviewer": "Jamik Tashpulatov"
	},
	{
		"id": 64,
		"header": "Release Management Process",
		"type": "Planning",
		"status": "Done",
		"target": "22",
		"limit": "25",
		"reviewer": "Assign reviewer"
	},
	{
		"id": 65,
		"header": "Feature Prioritization Matrix",
		"type": "Planning",
		"status": "In Process",
		"target": "19",
		"limit": "22",
		"reviewer": "Emma Davis"
	},
	{
		"id": 66,
		"header": "Technical Debt Assessment",
		"type": "Technical content",
		"status": "Done",
		"target": "24",
		"limit": "27",
		"reviewer": "Eddie Lake"
	},
	{
		"id": 67,
		"header": "Capacity Planning",
		"type": "Planning",
		"status": "In Process",
		"target": "21",
		"limit": "24",
		"reviewer": "Jamik Tashpulatov"
	},
	{
		"id": 68,
		"header": "Service Level Agreements",
		"type": "Legal",
		"status": "Done",
		"target": "26",
		"limit": "29",
		"reviewer": "Assign reviewer"
	}
]
components/app-sidebar.tsx
"use client";

import {
	CameraIcon,
	ChartBarIcon,
	CircleHelpIcon,
	CommandIcon,
	DatabaseIcon,
	FileChartColumnIcon,
	FileIcon,
	FileTextIcon,
	FolderIcon,
	LayoutDashboardIcon,
	ListIcon,
	SearchIcon,
	Settings2Icon,
	UsersIcon,
} from "lucide-react";

import type * as React from "react";

import { NavDocuments } from "@/components/nav-documents";
import { NavMain } from "@/components/nav-main";
import { NavSecondary } from "@/components/nav-secondary";
import { NavUser } from "@/components/nav-user";
import {
	Sidebar,
	SidebarContent,
	SidebarFooter,
	SidebarHeader,
	SidebarMenu,
	SidebarMenuButton,
	SidebarMenuItem,
} from "@/components/ui/sidebar";

const data = {
	user: {
		name: "Maqed",
		email: "maqed@example.com",
		avatar: "https://github.com/maqed.png",
	},
	navMain: [
		{
			title: "Dashboard",
			url: "#",
			icon: <LayoutDashboardIcon />,
		},
		{
			title: "Lifecycle",
			url: "#",
			icon: <ListIcon />,
		},
		{
			title: "Analytics",
			url: "#",
			icon: <ChartBarIcon />,
		},
		{
			title: "Projects",
			url: "#",
			icon: <FolderIcon />,
		},
		{
			title: "Team",
			url: "#",
			icon: <UsersIcon />,
		},
	],
	navClouds: [
		{
			title: "Capture",
			icon: <CameraIcon />,
			isActive: true,
			url: "#",
			items: [
				{
					title: "Active Proposals",
					url: "#",
				},
				{
					title: "Archived",
					url: "#",
				},
			],
		},
		{
			title: "Proposal",
			icon: <FileTextIcon />,
			url: "#",
			items: [
				{
					title: "Active Proposals",
					url: "#",
				},
				{
					title: "Archived",
					url: "#",
				},
			],
		},
		{
			title: "Prompts",
			icon: <FileTextIcon />,
			url: "#",
			items: [
				{
					title: "Active Proposals",
					url: "#",
				},
				{
					title: "Archived",
					url: "#",
				},
			],
		},
	],
	navSecondary: [
		{
			title: "Settings",
			url: "#",
			icon: <Settings2Icon />,
		},
		{
			title: "Get Help",
			url: "#",
			icon: <CircleHelpIcon />,
		},
		{
			title: "Search",
			url: "#",
			icon: <SearchIcon />,
		},
	],
	documents: [
		{
			name: "Data Library",
			url: "#",
			icon: <DatabaseIcon />,
		},
		{
			name: "Reports",
			url: "#",
			icon: <FileChartColumnIcon />,
		},
		{
			name: "Word Assistant",
			url: "#",
			icon: <FileIcon />,
		},
	],
};
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
	return (
		<Sidebar {...props}>
			<SidebarHeader>
				<SidebarMenu>
					<SidebarMenuItem>
						<SidebarMenuButton
							className="data-[slot=sidebar-menu-button]:p-1.5!"
							render={<a href="#" />}
						>
							<CommandIcon className="size-5!" />
							<span className="font-semibold text-base">Acme Inc.</span>
						</SidebarMenuButton>
					</SidebarMenuItem>
				</SidebarMenu>
			</SidebarHeader>
			<SidebarContent>
				<NavMain items={data.navMain} />
				<NavDocuments items={data.documents} />
				<NavSecondary items={data.navSecondary} className="mt-auto" />
			</SidebarContent>
			<SidebarFooter>
				<NavUser user={data.user} />
			</SidebarFooter>
		</Sidebar>
	);
}
components/chart-area-interactive.tsx
"use client";

import * as React from "react";
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts";

import { useIsMobile } from "@/hooks/use-mobile";
import {
	Card,
	CardAction,
	CardContent,
	CardDescription,
	CardHeader,
	CardTitle,
} from "@/components/ui/card";
import {
	type ChartConfig,
	ChartContainer,
	ChartTooltip,
	ChartTooltipContent,
} from "@/components/ui/chart";
import {
	Select,
	SelectContent,
	SelectItem,
	SelectTrigger,
	SelectValue,
} from "@/components/ui/select";
import {
	ToggleGroup,
	ToggleGroupItem,
} from "@/components/ui/toggle-group";

export const description = "An interactive area chart";

const chartData = [
	{ date: "2024-04-01", desktop: 222, mobile: 150 },
	{ date: "2024-04-02", desktop: 97, mobile: 180 },
	{ date: "2024-04-03", desktop: 167, mobile: 120 },
	{ date: "2024-04-04", desktop: 242, mobile: 260 },
	{ date: "2024-04-05", desktop: 373, mobile: 290 },
	{ date: "2024-04-06", desktop: 301, mobile: 340 },
	{ date: "2024-04-07", desktop: 245, mobile: 180 },
	{ date: "2024-04-08", desktop: 409, mobile: 320 },
	{ date: "2024-04-09", desktop: 59, mobile: 110 },
	{ date: "2024-04-10", desktop: 261, mobile: 190 },
	{ date: "2024-04-11", desktop: 327, mobile: 350 },
	{ date: "2024-04-12", desktop: 292, mobile: 210 },
	{ date: "2024-04-13", desktop: 342, mobile: 380 },
	{ date: "2024-04-14", desktop: 137, mobile: 220 },
	{ date: "2024-04-15", desktop: 120, mobile: 170 },
	{ date: "2024-04-16", desktop: 138, mobile: 190 },
	{ date: "2024-04-17", desktop: 446, mobile: 360 },
	{ date: "2024-04-18", desktop: 364, mobile: 410 },
	{ date: "2024-04-19", desktop: 243, mobile: 180 },
	{ date: "2024-04-20", desktop: 89, mobile: 150 },
	{ date: "2024-04-21", desktop: 137, mobile: 200 },
	{ date: "2024-04-22", desktop: 224, mobile: 170 },
	{ date: "2024-04-23", desktop: 138, mobile: 230 },
	{ date: "2024-04-24", desktop: 387, mobile: 290 },
	{ date: "2024-04-25", desktop: 215, mobile: 250 },
	{ date: "2024-04-26", desktop: 75, mobile: 130 },
	{ date: "2024-04-27", desktop: 383, mobile: 420 },
	{ date: "2024-04-28", desktop: 122, mobile: 180 },
	{ date: "2024-04-29", desktop: 315, mobile: 240 },
	{ date: "2024-04-30", desktop: 454, mobile: 380 },
	{ date: "2024-05-01", desktop: 165, mobile: 220 },
	{ date: "2024-05-02", desktop: 293, mobile: 310 },
	{ date: "2024-05-03", desktop: 247, mobile: 190 },
	{ date: "2024-05-04", desktop: 385, mobile: 420 },
	{ date: "2024-05-05", desktop: 481, mobile: 390 },
	{ date: "2024-05-06", desktop: 498, mobile: 520 },
	{ date: "2024-05-07", desktop: 388, mobile: 300 },
	{ date: "2024-05-08", desktop: 149, mobile: 210 },
	{ date: "2024-05-09", desktop: 227, mobile: 180 },
	{ date: "2024-05-10", desktop: 293, mobile: 330 },
	{ date: "2024-05-11", desktop: 335, mobile: 270 },
	{ date: "2024-05-12", desktop: 197, mobile: 240 },
	{ date: "2024-05-13", desktop: 197, mobile: 160 },
	{ date: "2024-05-14", desktop: 448, mobile: 490 },
	{ date: "2024-05-15", desktop: 473, mobile: 380 },
	{ date: "2024-05-16", desktop: 338, mobile: 400 },
	{ date: "2024-05-17", desktop: 499, mobile: 420 },
	{ date: "2024-05-18", desktop: 315, mobile: 350 },
	{ date: "2024-05-19", desktop: 235, mobile: 180 },
	{ date: "2024-05-20", desktop: 177, mobile: 230 },
	{ date: "2024-05-21", desktop: 82, mobile: 140 },
	{ date: "2024-05-22", desktop: 81, mobile: 120 },
	{ date: "2024-05-23", desktop: 252, mobile: 290 },
	{ date: "2024-05-24", desktop: 294, mobile: 220 },
	{ date: "2024-05-25", desktop: 201, mobile: 250 },
	{ date: "2024-05-26", desktop: 213, mobile: 170 },
	{ date: "2024-05-27", desktop: 420, mobile: 460 },
	{ date: "2024-05-28", desktop: 233, mobile: 190 },
	{ date: "2024-05-29", desktop: 78, mobile: 130 },
	{ date: "2024-05-30", desktop: 340, mobile: 280 },
	{ date: "2024-05-31", desktop: 178, mobile: 230 },
	{ date: "2024-06-01", desktop: 178, mobile: 200 },
	{ date: "2024-06-02", desktop: 470, mobile: 410 },
	{ date: "2024-06-03", desktop: 103, mobile: 160 },
	{ date: "2024-06-04", desktop: 439, mobile: 380 },
	{ date: "2024-06-05", desktop: 88, mobile: 140 },
	{ date: "2024-06-06", desktop: 294, mobile: 250 },
	{ date: "2024-06-07", desktop: 323, mobile: 370 },
	{ date: "2024-06-08", desktop: 385, mobile: 320 },
	{ date: "2024-06-09", desktop: 438, mobile: 480 },
	{ date: "2024-06-10", desktop: 155, mobile: 200 },
	{ date: "2024-06-11", desktop: 92, mobile: 150 },
	{ date: "2024-06-12", desktop: 492, mobile: 420 },
	{ date: "2024-06-13", desktop: 81, mobile: 130 },
	{ date: "2024-06-14", desktop: 426, mobile: 380 },
	{ date: "2024-06-15", desktop: 307, mobile: 350 },
	{ date: "2024-06-16", desktop: 371, mobile: 310 },
	{ date: "2024-06-17", desktop: 475, mobile: 520 },
	{ date: "2024-06-18", desktop: 107, mobile: 170 },
	{ date: "2024-06-19", desktop: 341, mobile: 290 },
	{ date: "2024-06-20", desktop: 408, mobile: 450 },
	{ date: "2024-06-21", desktop: 169, mobile: 210 },
	{ date: "2024-06-22", desktop: 317, mobile: 270 },
	{ date: "2024-06-23", desktop: 480, mobile: 530 },
	{ date: "2024-06-24", desktop: 132, mobile: 180 },
	{ date: "2024-06-25", desktop: 141, mobile: 190 },
	{ date: "2024-06-26", desktop: 434, mobile: 380 },
	{ date: "2024-06-27", desktop: 448, mobile: 490 },
	{ date: "2024-06-28", desktop: 149, mobile: 200 },
	{ date: "2024-06-29", desktop: 103, mobile: 160 },
	{ date: "2024-06-30", desktop: 446, mobile: 400 },
];

const chartConfig = {
	visitors: {
		label: "Visitors",
	},
	desktop: {
		label: "Desktop",
		color: "var(--primary)",
	},
	mobile: {
		label: "Mobile",
		color: "var(--primary)",
	},
} satisfies ChartConfig;

export function ChartAreaInteractive() {
	const isMobile = useIsMobile();
	const [timeRange, setTimeRange] = React.useState("90d");

	React.useEffect(() => {
		if (isMobile) {
			setTimeRange("7d");
		}
	}, [isMobile]);

	const filteredData = chartData.filter((item) => {
		const date = new Date(item.date);
		const referenceDate = new Date("2024-06-30");
		let daysToSubtract = 90;
		if (timeRange === "30d") {
			daysToSubtract = 30;
		} else if (timeRange === "7d") {
			daysToSubtract = 7;
		}
		const startDate = new Date(referenceDate);
		startDate.setDate(startDate.getDate() - daysToSubtract);
		return date >= startDate;
	});

	return (
		<Card className="@container/card">
			<CardHeader>
				<CardTitle>Total Visitors</CardTitle>
				<CardDescription>
					<span className="@[540px]/card:block hidden">
						Total for the last 3 months
					</span>
					<span className="@[540px]/card:hidden">Last 3 months</span>
				</CardDescription>
				<CardAction>
					<ToggleGroup
						multiple={false}
						value={timeRange ? [timeRange] : []}
						onValueChange={(value) => {
							setTimeRange(value[0] ?? "90d");
						}}
						className="@[767px]/card:flex hidden *:data-[slot=toggle-group-item]:px-4!"
					>
						<ToggleGroupItem value="90d">Last 3 months</ToggleGroupItem>
						<ToggleGroupItem value="30d">Last 30 days</ToggleGroupItem>
						<ToggleGroupItem value="7d">Last 7 days</ToggleGroupItem>
					</ToggleGroup>
					<Select
						value={timeRange}
						variant="secondary"
						onValueChange={(value) => {
							if (value !== null) {
								setTimeRange(value);
							}
						}}
					>
						<SelectTrigger
							className="flex @[767px]/card:hidden w-40 **:data-[slot=select-value]:block **:data-[slot=select-value]:truncate"
							aria-label="Select a value"
						>
							<SelectValue placeholder="Last 3 months" />
						</SelectTrigger>
						<SelectContent>
							<SelectItem value="90d">Last 3 months</SelectItem>
							<SelectItem value="30d">Last 30 days</SelectItem>
							<SelectItem value="7d">Last 7 days</SelectItem>
						</SelectContent>
					</Select>
				</CardAction>
			</CardHeader>
			<CardContent>
				<ChartContainer
					config={chartConfig}
					className="aspect-auto h-[250px] w-full"
				>
					<AreaChart data={filteredData}>
						<defs>
							<linearGradient id="fillDesktop" x1="0" y1="0" x2="0" y2="1">
								<stop
									offset="5%"
									stopColor="var(--color-desktop)"
									stopOpacity={1.0}
								/>
								<stop
									offset="95%"
									stopColor="var(--color-desktop)"
									stopOpacity={0.1}
								/>
							</linearGradient>
							<linearGradient id="fillMobile" x1="0" y1="0" x2="0" y2="1">
								<stop
									offset="5%"
									stopColor="var(--color-mobile)"
									stopOpacity={0.8}
								/>
								<stop
									offset="95%"
									stopColor="var(--color-mobile)"
									stopOpacity={0.1}
								/>
							</linearGradient>
						</defs>
						<CartesianGrid vertical={false} />
						<XAxis
							dataKey="date"
							tickLine={false}
							axisLine={false}
							tickMargin={8}
							minTickGap={32}
							tickFormatter={(value) => {
								const date = new Date(value);
								return date.toLocaleDateString("en-US", {
									month: "short",
									day: "numeric",
								});
							}}
						/>
						<ChartTooltip
							cursor={false}
							content={
								<ChartTooltipContent
									labelFormatter={(value) => {
										return new Date(value as string).toLocaleDateString(
											"en-US",
											{
												month: "short",
												day: "numeric",
											},
										);
									}}
									indicator="dot"
								/>
							}
						/>
						<Area
							dataKey="mobile"
							type="natural"
							fill="url(#fillMobile)"
							stroke="var(--color-mobile)"
							stackId="a"
						/>
						<Area
							dataKey="desktop"
							type="natural"
							fill="url(#fillDesktop)"
							stroke="var(--color-desktop)"
							stackId="a"
						/>
					</AreaChart>
				</ChartContainer>
			</CardContent>
		</Card>
	);
}
components/data-table.tsx
"use client";

import {
	closestCenter,
	DndContext,
	type DragEndEvent,
	KeyboardSensor,
	MouseSensor,
	TouchSensor,
	type UniqueIdentifier,
	useSensor,
	useSensors,
} from "@dnd-kit/core";
import { restrictToVerticalAxis } from "@dnd-kit/modifiers";
import {
	arrayMove,
	SortableContext,
	useSortable,
	verticalListSortingStrategy,
} from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import {
	type ColumnFiltersState,
	type ColumnVisibilityState,
	columnFilteringFeature,
	columnVisibilityFeature,
	createColumnHelper,
	createFilteredRowModel,
	createPaginatedRowModel,
	createSortedRowModel,
	FlexRender,
	type Row,
	rowPaginationFeature,
	rowSelectionFeature,
	rowSortingFeature,
	type SortingState,
	tableFeatures,
	useTable,
} from "@tanstack/react-table";
import {
	ChevronDownIcon,
	ChevronLeftIcon,
	ChevronRightIcon,
	ChevronsLeftIcon,
	ChevronsRightIcon,
	CircleCheckIcon,
	Columns3Icon,
	EllipsisVerticalIcon,
	GripVerticalIcon,
	LoaderIcon,
	PlusIcon,
	TrendingUpIcon,
} from "lucide-react";
import * as React from "react";
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts";
import { z } from "zod";
import { useIsMobile } from "@/hooks/use-mobile";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
	type ChartConfig,
	ChartContainer,
	ChartTooltip,
	ChartTooltipContent,
} from "@/components/ui/chart";
import { Checkbox } from "@/components/ui/checkbox";
import {
	Drawer,
	DrawerClose,
	DrawerContent,
	DrawerDescription,
	DrawerFooter,
	DrawerHeader,
	DrawerTitle,
	DrawerTrigger,
} from "@/components/ui/drawer";
import {
	DropdownMenu,
	DropdownMenuCheckboxItem,
	DropdownMenuContent,
	DropdownMenuItem,
	DropdownMenuSeparator,
	DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
	Select,
	SelectContent,
	SelectGroup,
	SelectItem,
	SelectTrigger,
	SelectValue,
} from "@/components/ui/select";
import { Separator } from "@/components/ui/separator";
import {
	Table,
	TableBody,
	TableCell,
	TableHead,
	TableHeader,
	TableRow,
} from "@/components/ui/table";
import {
	Tabs,
	TabsContent,
	TabsList,
	TabsTrigger,
} from "@/components/ui/tabs";
import { toast } from "@/components/ui/toast";

// New in v9: declare the features this table uses — anything you don't
// register is tree-shaken out of the bundle.
const features = tableFeatures({
	columnFilteringFeature,
	columnVisibilityFeature,
	rowPaginationFeature,
	rowSelectionFeature,
	rowSortingFeature,
	filteredRowModel: createFilteredRowModel(),
	paginatedRowModel: createPaginatedRowModel(),
	sortedRowModel: createSortedRowModel(),
});

const columnHelper = createColumnHelper<
	typeof features,
	z.infer<typeof schema>
>();

export const schema = z.object({
	id: z.number(),
	header: z.string(),
	type: z.string(),
	status: z.string(),
	target: z.string(),
	limit: z.string(),
	reviewer: z.string(),
});

// Create a separate component for the drag handle
function DragHandle({ id }: { id: number }) {
	const { attributes, listeners } = useSortable({
		id,
	});
	return (
		<Button
			{...attributes}
			{...listeners}
			variant="ghost"
			size="icon"
			className="size-7 text-muted-foreground hover:bg-transparent"
		>
			<GripVerticalIcon className="size-3 text-muted-foreground" />
			<span className="sr-only">Drag to reorder</span>
		</Button>
	);
}
const columns = columnHelper.columns([
	columnHelper.display({
		id: "drag",
		header: () => null,
		cell: ({ row }) => <DragHandle id={row.original.id} />,
	}),
	columnHelper.display({
		id: "select",
		header: ({ table }) => (
			<div className="flex items-center justify-center">
				<Checkbox
					checked={table.getIsAllPageRowsSelected()}
					indeterminate={
						table.getIsSomePageRowsSelected() &&
						!table.getIsAllPageRowsSelected()
					}
					onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
					aria-label="Select all"
				/>
			</div>
		),
		cell: ({ row }) => (
			<div className="flex items-center justify-center">
				<Checkbox
					variant="secondary"
					checked={row.getIsSelected()}
					onCheckedChange={(value) => row.toggleSelected(!!value)}
					aria-label="Select row"
				/>
			</div>
		),
		enableSorting: false,
		enableHiding: false,
	}),
	columnHelper.accessor("header", {
		header: "Header",
		cell: ({ row }) => {
			return <TableCellViewer item={row.original} />;
		},
		enableHiding: false,
	}),
	columnHelper.accessor("type", {
		header: "Section Type",
		cell: ({ row }) => (
			<div className="w-32">
				<Badge className="px-1.5 text-muted-foreground">
					{row.original.type}
				</Badge>
			</div>
		),
	}),
	columnHelper.accessor("status", {
		header: "Status",
		cell: ({ row }) => (
			<Badge
				variant={row.original.status === "Done" ? "success" : "default"}
				className="px-1.5 text-muted-foreground"
			>
				{row.original.status === "Done" ? (
					<CircleCheckIcon className="fill-green-500 dark:fill-green-400" />
				) : (
					<LoaderIcon />
				)}
				{row.original.status}
			</Badge>
		),
	}),
	columnHelper.accessor("target", {
		header: () => <div className="w-full text-right">Target</div>,
		cell: ({ row }) => (
			<form
				onSubmit={(e) => {
					e.preventDefault();
					toast.promise(new Promise((resolve) => setTimeout(resolve, 1000)), {
						loading: `Saving ${row.original.header}`,
						success: "Done",
						error: "Error",
					});
				}}
			>
				<Label htmlFor={`${row.original.id}-target`} className="sr-only">
					Target
				</Label>
				<Input
					className="h-8 w-16 border-transparent bg-transparent text-right shadow-none hover:bg-input/30 focus-visible:border focus-visible:bg-background dark:bg-transparent dark:focus-visible:bg-input/30 dark:hover:bg-input/30"
					defaultValue={row.original.target}
					id={`${row.original.id}-target`}
				/>
			</form>
		),
	}),
	columnHelper.accessor("limit", {
		header: () => <div className="w-full text-right">Limit</div>,
		cell: ({ row }) => (
			<form
				onSubmit={(e) => {
					e.preventDefault();
					toast.promise(new Promise((resolve) => setTimeout(resolve, 1000)), {
						loading: `Saving ${row.original.header}`,
						success: "Done",
						error: "Error",
					});
				}}
			>
				<Label htmlFor={`${row.original.id}-limit`} className="sr-only">
					Limit
				</Label>
				<Input
					className="h-8 w-16 border-transparent bg-transparent text-right shadow-none hover:bg-input/30 focus-visible:border focus-visible:bg-background dark:bg-transparent dark:focus-visible:bg-input/30 dark:hover:bg-input/30"
					defaultValue={row.original.limit}
					id={`${row.original.id}-limit`}
				/>
			</form>
		),
	}),
	columnHelper.accessor("reviewer", {
		header: "Reviewer",
		cell: ({ row }) => {
			const isAssigned = row.original.reviewer !== "Assign reviewer";
			if (isAssigned) {
				return row.original.reviewer;
			}
			return (
				<>
					<Label htmlFor={`${row.original.id}-reviewer`} className="sr-only">
						Reviewer
					</Label>
					<Select
						items={[
							{ label: "Eddie Lake", value: "Eddie Lake" },
							{ label: "Jamik Tashpulatov", value: "Jamik Tashpulatov" },
						]}
					>
						<SelectTrigger
							className="w-38 **:data-[slot=select-value]:block **:data-[slot=select-value]:truncate"
							id={`${row.original.id}-reviewer`}
						>
							<SelectValue placeholder="Assign reviewer" />
						</SelectTrigger>
						<SelectContent align="end">
							<SelectGroup>
								<SelectItem value="Eddie Lake">Eddie Lake</SelectItem>
								<SelectItem value="Jamik Tashpulatov">
									Jamik Tashpulatov
								</SelectItem>
							</SelectGroup>
						</SelectContent>
					</Select>
				</>
			);
		},
	}),
	columnHelper.display({
		id: "actions",
		cell: () => (
			<DropdownMenu>
				<DropdownMenuTrigger
					render={
						<Button
							variant="ghost"
							className="flex size-8 text-muted-foreground data-open:bg-muted"
							size="icon"
						/>
					}
				>
					<EllipsisVerticalIcon />
					<span className="sr-only">Open menu</span>
				</DropdownMenuTrigger>
				<DropdownMenuContent align="end" className="w-32">
					<DropdownMenuItem>Edit</DropdownMenuItem>
					<DropdownMenuItem>Make a copy</DropdownMenuItem>
					<DropdownMenuItem>Favorite</DropdownMenuItem>
					<DropdownMenuSeparator />
					<DropdownMenuItem variant="destructive">Delete</DropdownMenuItem>
				</DropdownMenuContent>
			</DropdownMenu>
		),
	}),
]);
function DraggableRow({
	row,
}: {
	row: Row<typeof features, z.infer<typeof schema>>;
}) {
	const { transform, transition, setNodeRef, isDragging } = useSortable({
		id: row.original.id,
	});
	return (
		<TableRow
			data-state={row.getIsSelected() && "selected"}
			data-dragging={isDragging}
			ref={setNodeRef}
			className="relative z-0 data-[dragging=true]:z-10 data-[dragging=true]:opacity-80"
			style={{
				transform: CSS.Transform.toString(transform),
				transition: transition,
			}}
		>
			{row.getVisibleCells().map((cell) => (
				<TableCell key={cell.id}>
					<FlexRender cell={cell} />
				</TableCell>
			))}
		</TableRow>
	);
}
export function DataTable({
	data: initialData,
}: {
	data: z.infer<typeof schema>[];
}) {
	const [data, setData] = React.useState(() => initialData);
	const [rowSelection, setRowSelection] = React.useState({});
	const [columnVisibility, setColumnVisibility] =
		React.useState<ColumnVisibilityState>({});
	const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
		[],
	);
	const [sorting, setSorting] = React.useState<SortingState>([]);
	const [pagination, setPagination] = React.useState({
		pageIndex: 0,
		pageSize: 10,
	});
	const sortableId = React.useId();
	const sensors = useSensors(
		useSensor(MouseSensor, {}),
		useSensor(TouchSensor, {}),
		useSensor(KeyboardSensor, {}),
	);
	const dataIds = React.useMemo<UniqueIdentifier[]>(
		() => data?.map(({ id }) => id) || [],
		[data],
	);
	const table = useTable({
		features,
		data,
		columns,
		state: {
			sorting,
			columnVisibility,
			rowSelection,
			columnFilters,
			pagination,
		},
		getRowId: (row) => row.id.toString(),
		enableRowSelection: true,
		onRowSelectionChange: setRowSelection,
		onSortingChange: setSorting,
		onColumnFiltersChange: setColumnFilters,
		onColumnVisibilityChange: setColumnVisibility,
		onPaginationChange: setPagination,
	});
	function handleDragEnd(event: DragEndEvent) {
		const { active, over } = event;
		if (active && over && active.id !== over.id) {
			setData((data) => {
				const oldIndex = dataIds.indexOf(active.id);
				const newIndex = dataIds.indexOf(over.id);
				return arrayMove(data, oldIndex, newIndex);
			});
		}
	}
	return (
		<Tabs
			defaultValue="outline"
			className="w-full flex-col justify-start gap-6"
		>
			<div className="flex items-center justify-between px-4 lg:px-6">
				<Label htmlFor="view-selector" className="sr-only">
					View
				</Label>
				<Select
					defaultValue="outline"
					items={[
						{ label: "Outline", value: "outline" },
						{ label: "Past Performance", value: "past-performance" },
						{ label: "Key Personnel", value: "key-personnel" },
						{ label: "Focus Documents", value: "focus-documents" },
					]}
				>
					<SelectTrigger
						className="flex @4xl/main:hidden w-fit"
						id="view-selector"
					>
						<SelectValue placeholder="Select a view" />
					</SelectTrigger>
					<SelectContent>
						<SelectGroup>
							<SelectItem value="outline">Outline</SelectItem>
							<SelectItem value="past-performance">Past Performance</SelectItem>
							<SelectItem value="key-personnel">Key Personnel</SelectItem>
							<SelectItem value="focus-documents">Focus Documents</SelectItem>
						</SelectGroup>
					</SelectContent>
				</Select>
				<TabsList className="@4xl/main:flex hidden **:data-[slot=badge]:size-5 **:data-[slot=badge]:rounded-full **:data-[slot=badge]:bg-muted-foreground/30 **:data-[slot=badge]:px-1">
					<TabsTrigger value="outline">Outline</TabsTrigger>
					<TabsTrigger value="past-performance">
						Past Performance <Badge>3</Badge>
					</TabsTrigger>
					<TabsTrigger value="key-personnel">
						Key Personnel <Badge>2</Badge>
					</TabsTrigger>
					<TabsTrigger value="focus-documents">Focus Documents</TabsTrigger>
				</TabsList>
				<div className="flex items-center gap-2">
					<DropdownMenu>
						<DropdownMenuTrigger
							render={<Button variant="outline" size="sm" />}
						>
							<Columns3Icon data-icon="inline-start" />
							Columns
							<ChevronDownIcon data-icon="inline-end" />
						</DropdownMenuTrigger>
						<DropdownMenuContent align="end" className="w-32">
							{table
								.getAllColumns()
								.filter(
									(column) =>
										typeof column.accessorFn !== "undefined" &&
										column.getCanHide(),
								)
								.map((column) => {
									return (
										<DropdownMenuCheckboxItem
											key={column.id}
											className="capitalize"
											checked={column.getIsVisible()}
											onCheckedChange={(value) =>
												column.toggleVisibility(!!value)
											}
										>
											{column.id}
										</DropdownMenuCheckboxItem>
									);
								})}
						</DropdownMenuContent>
					</DropdownMenu>
					<Button variant="outline" size="sm">
						<PlusIcon />
						<span className="hidden lg:inline">Add Section</span>
					</Button>
				</div>
			</div>
			<TabsContent
				value="outline"
				className="relative flex flex-col gap-4 overflow-auto px-4 lg:px-6"
			>
				<DndContext
					collisionDetection={closestCenter}
					modifiers={[restrictToVerticalAxis]}
					onDragEnd={handleDragEnd}
					sensors={sensors}
					id={sortableId}
				>
					<Table>
						<TableHeader className="sticky top-0 z-10 bg-muted">
							{table.getHeaderGroups().map((headerGroup) => (
								<TableRow key={headerGroup.id}>
									{headerGroup.headers.map((header) => {
										return (
											<TableHead key={header.id} colSpan={header.colSpan}>
												{header.isPlaceholder ? null : (
													<FlexRender header={header} />
												)}
											</TableHead>
										);
									})}
								</TableRow>
							))}
						</TableHeader>
						<TableBody className="**:data-[slot=table-cell]:first:w-8">
							{table.getRowModel().rows?.length ? (
								<SortableContext
									items={dataIds}
									strategy={verticalListSortingStrategy}
								>
									{table.getRowModel().rows.map((row) => (
										<DraggableRow key={row.id} row={row} />
									))}
								</SortableContext>
							) : (
								<TableRow>
									<TableCell
										colSpan={columns.length}
										className="h-24 text-center"
									>
										No results.
									</TableCell>
								</TableRow>
							)}
						</TableBody>
					</Table>
				</DndContext>
				<div className="flex items-center justify-between px-4">
					<div className="hidden flex-1 text-muted-foreground text-sm lg:flex">
						{table.getFilteredSelectedRowModel().rows.length} of{" "}
						{table.getFilteredRowModel().rows.length} row(s) selected.
					</div>
					<div className="flex w-full items-center gap-8 lg:w-fit">
						<div className="hidden items-center gap-2 lg:flex">
							<Label htmlFor="rows-per-page" className="font-medium text-sm">
								Rows per page
							</Label>
							<Select
								value={`${table.state.pagination.pageSize}`}
								onValueChange={(value) => {
									table.setPageSize(Number(value));
								}}
								items={[10, 20, 30, 40, 50].map((pageSize) => ({
									label: `${pageSize}`,
									value: `${pageSize}`,
								}))}
							>
								<SelectTrigger className="w-20" id="rows-per-page">
									<SelectValue placeholder={table.state.pagination.pageSize} />
								</SelectTrigger>
								<SelectContent side="top">
									<SelectGroup>
										{[10, 20, 30, 40, 50].map((pageSize) => (
											<SelectItem key={pageSize} value={`${pageSize}`}>
												{pageSize}
											</SelectItem>
										))}
									</SelectGroup>
								</SelectContent>
							</Select>
						</div>
						<div className="flex w-fit items-center justify-center font-medium text-sm">
							Page {table.state.pagination.pageIndex + 1} of{" "}
							{table.getPageCount()}
						</div>
						<div className="ml-auto flex items-center gap-2 lg:ml-0">
							<Button
								variant="outline"
								className="hidden h-8 w-8 p-0 lg:flex"
								onClick={() => table.setPageIndex(0)}
								disabled={!table.getCanPreviousPage()}
							>
								<span className="sr-only">Go to first page</span>
								<ChevronsLeftIcon />
							</Button>
							<Button
								variant="outline"
								className="size-8"
								size="icon"
								onClick={() => table.previousPage()}
								disabled={!table.getCanPreviousPage()}
							>
								<span className="sr-only">Go to previous page</span>
								<ChevronLeftIcon />
							</Button>
							<Button
								variant="outline"
								className="size-8"
								size="icon"
								onClick={() => table.nextPage()}
								disabled={!table.getCanNextPage()}
							>
								<span className="sr-only">Go to next page</span>
								<ChevronRightIcon />
							</Button>
							<Button
								variant="outline"
								className="hidden size-8 lg:flex"
								size="icon"
								onClick={() => table.setPageIndex(table.getPageCount() - 1)}
								disabled={!table.getCanNextPage()}
							>
								<span className="sr-only">Go to last page</span>
								<ChevronsRightIcon />
							</Button>
						</div>
					</div>
				</div>
			</TabsContent>
			<TabsContent
				value="past-performance"
				className="flex flex-col px-4 lg:px-6"
			>
				<div className="aspect-video w-full flex-1 rounded-lg border border-dashed" />
			</TabsContent>
			<TabsContent value="key-personnel" className="flex flex-col px-4 lg:px-6">
				<div className="aspect-video w-full flex-1 rounded-lg border border-dashed" />
			</TabsContent>
			<TabsContent
				value="focus-documents"
				className="flex flex-col px-4 lg:px-6"
			>
				<div className="aspect-video w-full flex-1 rounded-lg border border-dashed" />
			</TabsContent>
		</Tabs>
	);
}
const chartData = [
	{
		month: "January",
		desktop: 186,
		mobile: 80,
	},
	{
		month: "February",
		desktop: 305,
		mobile: 200,
	},
	{
		month: "March",
		desktop: 237,
		mobile: 120,
	},
	{
		month: "April",
		desktop: 73,
		mobile: 190,
	},
	{
		month: "May",
		desktop: 209,
		mobile: 130,
	},
	{
		month: "June",
		desktop: 214,
		mobile: 140,
	},
];
const chartConfig = {
	desktop: {
		label: "Desktop",
		color: "var(--primary)",
	},
	mobile: {
		label: "Mobile",
		color: "var(--primary)",
	},
} satisfies ChartConfig;
function TableCellViewer({ item }: { item: z.infer<typeof schema> }) {
	const isMobile = useIsMobile();
	return (
		<Drawer swipeDirection={isMobile ? "down" : "right"}>
			<DrawerTrigger
				render={
					<Button
						variant="link"
						className="w-fit px-0 text-left text-foreground"
					/>
				}
			>
				{item.header}
			</DrawerTrigger>
			<DrawerContent>
				<DrawerHeader className="gap-1">
					<DrawerTitle>{item.header}</DrawerTitle>
					<DrawerDescription>
						Showing total visitors for the last 6 months
					</DrawerDescription>
				</DrawerHeader>
				<div className="flex flex-col gap-4 overflow-y-auto px-4 text-sm">
					{!isMobile && (
						<>
							<ChartContainer config={chartConfig}>
								<AreaChart
									accessibilityLayer
									data={chartData}
									margin={{
										left: 0,
										right: 10,
									}}
								>
									<CartesianGrid vertical={false} />
									<XAxis
										dataKey="month"
										tickLine={false}
										axisLine={false}
										tickMargin={8}
										tickFormatter={(value) => value.slice(0, 3)}
										hide
									/>
									<ChartTooltip
										cursor={false}
										content={<ChartTooltipContent indicator="dot" />}
									/>
									<Area
										dataKey="mobile"
										type="natural"
										fill="var(--color-mobile)"
										fillOpacity={0.6}
										stroke="var(--color-mobile)"
										stackId="a"
									/>
									<Area
										dataKey="desktop"
										type="natural"
										fill="var(--color-desktop)"
										fillOpacity={0.4}
										stroke="var(--color-desktop)"
										stackId="a"
									/>
								</AreaChart>
							</ChartContainer>
							<Separator />
							<div className="grid gap-2">
								<div className="flex gap-2 font-medium leading-none">
									Trending up by 5.2% this month{" "}
									<TrendingUpIcon className="size-4" />
								</div>
								<div className="text-muted-foreground">
									Showing total visitors for the last 6 months. This is just
									some random text to test the layout. It spans multiple lines
									and should wrap around.
								</div>
							</div>
							<Separator />
						</>
					)}
					<form className="flex flex-col gap-4">
						<div className="flex flex-col gap-3">
							<Label htmlFor="header">Header</Label>
							<Input
								id="header"
								defaultValue={item.header}
								variant="secondary"
							/>
						</div>
						<div className="grid grid-cols-2 gap-4">
							<div className="flex flex-col gap-3">
								<Label htmlFor="type">Type</Label>
								<Select
									defaultValue={item.type}
									variant="secondary"
									items={[
										{ label: "Table of Contents", value: "Table of Contents" },
										{ label: "Executive Summary", value: "Executive Summary" },
										{
											label: "Technical Approach",
											value: "Technical Approach",
										},
										{ label: "Design", value: "Design" },
										{ label: "Capabilities", value: "Capabilities" },
										{ label: "Focus Documents", value: "Focus Documents" },
										{ label: "Narrative", value: "Narrative" },
										{ label: "Cover Page", value: "Cover Page" },
									]}
								>
									<SelectTrigger id="type" className="w-full">
										<SelectValue placeholder="Select a type" />
									</SelectTrigger>
									<SelectContent>
										<SelectGroup>
											<SelectItem value="Table of Contents">
												Table of Contents
											</SelectItem>
											<SelectItem value="Executive Summary">
												Executive Summary
											</SelectItem>
											<SelectItem value="Technical Approach">
												Technical Approach
											</SelectItem>
											<SelectItem value="Design">Design</SelectItem>
											<SelectItem value="Capabilities">Capabilities</SelectItem>
											<SelectItem value="Focus Documents">
												Focus Documents
											</SelectItem>
											<SelectItem value="Narrative">Narrative</SelectItem>
											<SelectItem value="Cover Page">Cover Page</SelectItem>
										</SelectGroup>
									</SelectContent>
								</Select>
							</div>
							<div className="flex flex-col gap-3">
								<Label htmlFor="status">Status</Label>
								<Select
									defaultValue={item.status}
									variant="secondary"
									items={[
										{ label: "Done", value: "Done" },
										{ label: "In Progress", value: "In Progress" },
										{ label: "Not Started", value: "Not Started" },
									]}
								>
									<SelectTrigger id="status" className="w-full">
										<SelectValue placeholder="Select a status" />
									</SelectTrigger>
									<SelectContent>
										<SelectGroup>
											<SelectItem value="Done">Done</SelectItem>
											<SelectItem value="In Progress">In Progress</SelectItem>
											<SelectItem value="Not Started">Not Started</SelectItem>
										</SelectGroup>
									</SelectContent>
								</Select>
							</div>
						</div>
						<div className="grid grid-cols-2 gap-4">
							<div className="flex flex-col gap-3">
								<Label htmlFor="target">Target</Label>
								<Input
									id="target"
									defaultValue={item.target}
									variant="secondary"
								/>
							</div>
							<div className="flex flex-col gap-3">
								<Label htmlFor="limit">Limit</Label>
								<Input
									id="limit"
									defaultValue={item.limit}
									variant="secondary"
								/>
							</div>
						</div>
						<div className="flex flex-col gap-3">
							<Label htmlFor="reviewer">Reviewer</Label>
							<Select
								defaultValue={item.reviewer}
								variant="secondary"
								items={[
									{ label: "Eddie Lake", value: "Eddie Lake" },
									{ label: "Jamik Tashpulatov", value: "Jamik Tashpulatov" },
									{ label: "Emily Whalen", value: "Emily Whalen" },
								]}
							>
								<SelectTrigger id="reviewer" className="w-full">
									<SelectValue placeholder="Select a reviewer" />
								</SelectTrigger>
								<SelectContent>
									<SelectGroup>
										<SelectItem value="Eddie Lake">Eddie Lake</SelectItem>
										<SelectItem value="Jamik Tashpulatov">
											Jamik Tashpulatov
										</SelectItem>
										<SelectItem value="Emily Whalen">Emily Whalen</SelectItem>
									</SelectGroup>
								</SelectContent>
							</Select>
						</div>
					</form>
				</div>
				<DrawerFooter>
					<Button>Submit</Button>
					<DrawerClose render={<Button variant="outline" />}>Done</DrawerClose>
				</DrawerFooter>
			</DrawerContent>
		</Drawer>
	);
}
components/nav-documents.tsx
"use client";

import {
	FolderIcon,
	MoreHorizontalIcon,
	ShareIcon,
	Trash2Icon,
} from "lucide-react";

import {
	DropdownMenu,
	DropdownMenuContent,
	DropdownMenuItem,
	DropdownMenuSeparator,
	DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
	SidebarGroup,
	SidebarGroupLabel,
	SidebarMenu,
	SidebarMenuAction,
	SidebarMenuButton,
	SidebarMenuItem,
	useSidebar,
} from "@/components/ui/sidebar";

export function NavDocuments({
	items,
}: {
	items: {
		name: string;
		url: string;
		icon: React.ReactNode;
	}[];
}) {
	const { isMobile } = useSidebar();
	return (
		<SidebarGroup className="group-data-[collapsible=icon]:hidden">
			<SidebarGroupLabel>Documents</SidebarGroupLabel>
			<SidebarMenu>
				{items.map((item) => (
					<SidebarMenuItem key={item.name}>
						<SidebarMenuButton render={<a href={item.url} />}>
							{item.icon}
							<span>{item.name}</span>
						</SidebarMenuButton>
						<DropdownMenu>
							<DropdownMenuTrigger
								render={
									<SidebarMenuAction
										showOnHover
										className="aria-expanded:bg-muted"
									/>
								}
							>
								<MoreHorizontalIcon />
								<span className="sr-only">More</span>
							</DropdownMenuTrigger>
							<DropdownMenuContent
								className="w-24"
								side={isMobile ? "bottom" : "right"}
								align={isMobile ? "end" : "start"}
							>
								<DropdownMenuItem>
									<FolderIcon />
									<span>Open</span>
								</DropdownMenuItem>
								<DropdownMenuItem>
									<ShareIcon />
									<span>Share</span>
								</DropdownMenuItem>
								<DropdownMenuSeparator />
								<DropdownMenuItem variant="destructive">
									<Trash2Icon />
									<span>Delete</span>
								</DropdownMenuItem>
							</DropdownMenuContent>
						</DropdownMenu>
					</SidebarMenuItem>
				))}
				<SidebarMenuItem>
					<SidebarMenuButton className="text-sidebar-foreground/70">
						<MoreHorizontalIcon className="text-sidebar-foreground/70" />
						<span>More</span>
					</SidebarMenuButton>
				</SidebarMenuItem>
			</SidebarMenu>
		</SidebarGroup>
	);
}
components/nav-main.tsx
"use client";

import { CirclePlusIcon } from "lucide-react";
import {
	SidebarGroup,
	SidebarGroupContent,
	SidebarMenu,
	SidebarMenuButton,
	SidebarMenuItem,
} from "@/components/ui/sidebar";

export function NavMain({
	items,
}: {
	items: {
		title: string;
		url: string;
		icon?: React.ReactNode;
	}[];
}) {
	return (
		<SidebarGroup>
			<SidebarGroupContent className="flex flex-col gap-2">
				<SidebarMenu>
					<SidebarMenuItem className="flex items-center gap-2">
						<SidebarMenuButton
							tooltip="Quick Create"
							className="min-w-8 bg-primary text-primary-foreground duration-200 ease-linear hover:bg-primary/90 hover:text-primary-foreground active:bg-primary/90 active:text-primary-foreground"
						>
							<CirclePlusIcon />
							<span>Quick Create</span>
						</SidebarMenuButton>
					</SidebarMenuItem>
				</SidebarMenu>
				<SidebarMenu>
					{items.map((item) => (
						<SidebarMenuItem key={item.title}>
							<SidebarMenuButton tooltip={item.title}>
								{item.icon}
								<span>{item.title}</span>
							</SidebarMenuButton>
						</SidebarMenuItem>
					))}
				</SidebarMenu>
			</SidebarGroupContent>
		</SidebarGroup>
	);
}
components/nav-secondary.tsx
"use client";

import type * as React from "react";

import {
	SidebarGroup,
	SidebarGroupContent,
	SidebarMenu,
	SidebarMenuButton,
	SidebarMenuItem,
} from "@/components/ui/sidebar";

export function NavSecondary({
	items,
	...props
}: {
	items: {
		title: string;
		url: string;
		icon: React.ReactNode;
	}[];
} & React.ComponentPropsWithoutRef<typeof SidebarGroup>) {
	return (
		<SidebarGroup {...props}>
			<SidebarGroupContent>
				<SidebarMenu>
					{items.map((item) => (
						<SidebarMenuItem key={item.title}>
							<SidebarMenuButton render={<a href={item.url} />}>
								{item.icon}
								<span>{item.title}</span>
							</SidebarMenuButton>
						</SidebarMenuItem>
					))}
				</SidebarMenu>
			</SidebarGroupContent>
		</SidebarGroup>
	);
}
components/nav-user.tsx
"use client";

import {
	BellIcon,
	CircleUserRoundIcon,
	CreditCardIcon,
	EllipsisVerticalIcon,
	LogOutIcon,
} from "lucide-react";

import {
	Avatar,
	AvatarFallback,
	AvatarImage,
} from "@/components/ui/avatar";
import {
	DropdownMenu,
	DropdownMenuContent,
	DropdownMenuGroup,
	DropdownMenuItem,
	DropdownMenuLabel,
	DropdownMenuSeparator,
	DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
	SidebarMenu,
	SidebarMenuButton,
	SidebarMenuItem,
	useSidebar,
} from "@/components/ui/sidebar";

export function NavUser({
	user,
}: {
	user: {
		name: string;
		email: string;
		avatar: string;
	};
}) {
	const { isMobile } = useSidebar();
	return (
		<SidebarMenu>
			<SidebarMenuItem>
				<DropdownMenu>
					<DropdownMenuTrigger
						render={
							<SidebarMenuButton size="lg" className="aria-expanded:bg-muted" />
						}
					>
						<Avatar className="size-8 rounded-lg grayscale">
							<AvatarImage src={user.avatar} alt={user.name} />
							<AvatarFallback className="rounded-lg">CN</AvatarFallback>
						</Avatar>
						<div className="grid flex-1 text-left text-sm leading-tight">
							<span className="truncate font-medium">{user.name}</span>
							<span className="truncate text-foreground/70 text-xs">
								{user.email}
							</span>
						</div>
						<EllipsisVerticalIcon className="ml-auto size-4" />
					</DropdownMenuTrigger>
					<DropdownMenuContent
						className="min-w-56"
						side={isMobile ? "bottom" : "right"}
						align="end"
						sideOffset={4}
					>
						<DropdownMenuGroup>
							<DropdownMenuLabel className="p-0 font-normal">
								<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
									<Avatar className="size-8">
										<AvatarImage src={user.avatar} alt={user.name} />
										<AvatarFallback className="rounded-lg">CN</AvatarFallback>
									</Avatar>
									<div className="grid flex-1 text-left text-sm leading-tight">
										<span className="truncate font-medium">{user.name}</span>
										<span className="truncate text-muted-foreground text-xs">
											{user.email}
										</span>
									</div>
								</div>
							</DropdownMenuLabel>
						</DropdownMenuGroup>
						<DropdownMenuSeparator />
						<DropdownMenuGroup>
							<DropdownMenuItem>
								<CircleUserRoundIcon />
								Account
							</DropdownMenuItem>
							<DropdownMenuItem>
								<CreditCardIcon />
								Billing
							</DropdownMenuItem>
							<DropdownMenuItem>
								<BellIcon />
								Notifications
							</DropdownMenuItem>
						</DropdownMenuGroup>
						<DropdownMenuSeparator />
						<DropdownMenuItem>
							<LogOutIcon />
							Log out
						</DropdownMenuItem>
					</DropdownMenuContent>
				</DropdownMenu>
			</SidebarMenuItem>
		</SidebarMenu>
	);
}
components/section-cards.tsx
"use client";

import { TrendingDownIcon, TrendingUpIcon } from "lucide-react";

import { Badge } from "@/components/ui/badge";
import {
	Card,
	CardAction,
	CardDescription,
	CardFooter,
	CardHeader,
	CardTitle,
} from "@/components/ui/card";

export function SectionCards() {
	return (
		<div className="grid @5xl/main:grid-cols-4 @xl/main:grid-cols-2 grid-cols-1 gap-4 px-4 lg:px-6">
			<Card className="@container/card">
				<CardHeader>
					<CardDescription>Total Revenue</CardDescription>
					<CardTitle className="font-semibold @[250px]/card:text-3xl text-2xl tabular-nums">
						$1,250.00
					</CardTitle>
					<CardAction>
						<Badge>
							<TrendingUpIcon />
							+12.5%
						</Badge>
					</CardAction>
				</CardHeader>
				<CardFooter className="flex-col items-start gap-1.5 text-sm">
					<div className="line-clamp-1 flex gap-2 font-medium">
						Trending up this month <TrendingUpIcon className="size-4" />
					</div>
					<div className="text-muted-foreground">
						Visitors for the last 6 months
					</div>
				</CardFooter>
			</Card>
			<Card className="@container/card">
				<CardHeader>
					<CardDescription>New Customers</CardDescription>
					<CardTitle className="font-semibold @[250px]/card:text-3xl text-2xl tabular-nums">
						1,234
					</CardTitle>
					<CardAction>
						<Badge>
							<TrendingDownIcon />
							-20%
						</Badge>
					</CardAction>
				</CardHeader>
				<CardFooter className="flex-col items-start gap-1.5 text-sm">
					<div className="line-clamp-1 flex gap-2 font-medium">
						Down 20% this period <TrendingDownIcon className="size-4" />
					</div>
					<div className="text-muted-foreground">
						Acquisition needs attention
					</div>
				</CardFooter>
			</Card>
			<Card className="@container/card">
				<CardHeader>
					<CardDescription>Active Accounts</CardDescription>
					<CardTitle className="font-semibold @[250px]/card:text-3xl text-2xl tabular-nums">
						45,678
					</CardTitle>
					<CardAction>
						<Badge>
							<TrendingUpIcon />
							+12.5%
						</Badge>
					</CardAction>
				</CardHeader>
				<CardFooter className="flex-col items-start gap-1.5 text-sm">
					<div className="line-clamp-1 flex gap-2 font-medium">
						Strong user retention <TrendingUpIcon className="size-4" />
					</div>
					<div className="text-muted-foreground">Engagement exceed targets</div>
				</CardFooter>
			</Card>
			<Card className="@container/card">
				<CardHeader>
					<CardDescription>Growth Rate</CardDescription>
					<CardTitle className="font-semibold @[250px]/card:text-3xl text-2xl tabular-nums">
						4.5%
					</CardTitle>
					<CardAction>
						<Badge>
							<TrendingUpIcon />
							+4.5%
						</Badge>
					</CardAction>
				</CardHeader>
				<CardFooter className="flex-col items-start gap-1.5 text-sm">
					<div className="line-clamp-1 flex gap-2 font-medium">
						Steady performance increase <TrendingUpIcon className="size-4" />
					</div>
					<div className="text-muted-foreground">Meets growth projections</div>
				</CardFooter>
			</Card>
		</div>
	);
}
components/site-header.tsx
import { Separator } from "@/components/ui/separator";
import { SidebarTrigger } from "@/components/ui/sidebar";

export function SiteHeader() {
	return (
		<header className="flex h-(--header-height) shrink-0 items-center gap-2 border-b transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-(--header-height)">
			<div className="flex w-full items-center gap-1 px-4 lg:gap-2 lg:px-6">
				<SidebarTrigger className="-ml-1" />
				<Separator
					orientation="vertical"
					className="mx-2 h-4 data-vertical:self-auto"
				/>
				<h1 className="font-medium text-base">Documents</h1>
			</div>
		</header>
	);
}

More from herocn

Browse all components in this library →