Initial state for GitNexus analysis
This commit is contained in:
33
apps/web/components/cms-page-shell.tsx
Normal file
33
apps/web/components/cms-page-shell.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
import { PageBody } from '@kit/ui/page';
|
||||
import { AppBreadcrumbs } from '@kit/ui/app-breadcrumbs';
|
||||
|
||||
import { TeamAccountLayoutPageHeader } from '~/home/[account]/_components/team-account-layout-page-header';
|
||||
|
||||
interface CmsPageShellProps {
|
||||
account: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared CMS page shell — wraps PageBody + header + breadcrumbs.
|
||||
* Use in every CMS feature page to maintain consistent layout.
|
||||
*/
|
||||
export function CmsPageShell({ account, title, description, children }: CmsPageShellProps) {
|
||||
return (
|
||||
<>
|
||||
<TeamAccountLayoutPageHeader
|
||||
account={account}
|
||||
title={title}
|
||||
description={description ?? <AppBreadcrumbs />}
|
||||
/>
|
||||
|
||||
<PageBody>
|
||||
{children}
|
||||
</PageBody>
|
||||
</>
|
||||
);
|
||||
}
|
||||
39
apps/web/components/empty-state.tsx
Normal file
39
apps/web/components/empty-state.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import { Button } from '@kit/ui/button';
|
||||
|
||||
interface EmptyStateProps {
|
||||
icon?: React.ReactNode;
|
||||
title: string;
|
||||
description: string;
|
||||
actionLabel?: string;
|
||||
actionHref?: string;
|
||||
onAction?: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reusable empty state with icon + CTA.
|
||||
* Used when DataTables have 0 rows.
|
||||
*/
|
||||
export function EmptyState({ icon, title, description, actionLabel, actionHref, onAction }: EmptyStateProps) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center rounded-lg border border-dashed p-12 text-center">
|
||||
{icon && (
|
||||
<div className="mb-4 rounded-full bg-muted p-4 text-muted-foreground">
|
||||
{icon}
|
||||
</div>
|
||||
)}
|
||||
<h3 className="text-lg font-semibold">{title}</h3>
|
||||
<p className="mt-1 max-w-sm text-sm text-muted-foreground">{description}</p>
|
||||
{actionLabel && (
|
||||
<div className="mt-6">
|
||||
{actionHref ? (
|
||||
<a href={actionHref}>
|
||||
<Button>{actionLabel}</Button>
|
||||
</a>
|
||||
) : (
|
||||
<Button onClick={onAction}>{actionLabel}</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
44
apps/web/components/stats-card.tsx
Normal file
44
apps/web/components/stats-card.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import { Card, CardContent } from '@kit/ui/card';
|
||||
|
||||
interface StatsCardProps {
|
||||
title: string;
|
||||
value: string | number;
|
||||
icon?: React.ReactNode;
|
||||
description?: string;
|
||||
trend?: { value: number; label: string };
|
||||
}
|
||||
|
||||
/**
|
||||
* Reusable stat card with icon + value + label + optional trend.
|
||||
* Used on dashboard and list pages.
|
||||
*/
|
||||
export function StatsCard({ title, value, icon, description, trend }: StatsCardProps) {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-medium text-muted-foreground">{title}</p>
|
||||
<p className="text-2xl font-bold">{value}</p>
|
||||
{description && (
|
||||
<p className="text-xs text-muted-foreground">{description}</p>
|
||||
)}
|
||||
</div>
|
||||
{icon && (
|
||||
<div className="rounded-full bg-primary/10 p-3 text-primary">
|
||||
{icon}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{trend && (
|
||||
<div className="mt-2 flex items-center text-xs">
|
||||
<span className={trend.value >= 0 ? 'text-green-600' : 'text-red-600'}>
|
||||
{trend.value >= 0 ? '↑' : '↓'} {Math.abs(trend.value)}%
|
||||
</span>
|
||||
<span className="ml-1 text-muted-foreground">{trend.label}</span>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user