data layer

This commit is contained in:
2026-05-25 08:38:26 +07:00
parent 4e8c11d545
commit a428170fef
81 changed files with 3941 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
import { NavLink, Outlet } from 'react-router-dom';
import {
Activity, Code2, LineChart, Search, Settings, Tags, Users,
} from 'lucide-react';
import { cn } from '@/lib/utils';
const nav = [
{ to: '/', label: 'Explore', icon: Search },
{ to: '/sql', label: 'Custom SQL', icon: Code2 },
{ to: '/profiles', label: 'Profiles', icon: Users },
{ to: '/funnels', label: 'Funnels', icon: LineChart },
{ to: '/retention', label: 'Retention', icon: Activity },
{ to: '/segments', label: 'Segments', icon: Tags },
{ to: '/traits', label: 'Traits', icon: Settings },
];
export function AppShell() {
return (
<div className="flex h-full">
<aside className="w-60 shrink-0 border-r bg-muted/30 p-4">
<div className="mb-6 px-2">
<div className="text-lg font-semibold">CDP Analytics</div>
<div className="text-xs text-muted-foreground">Data Layer</div>
</div>
<nav className="space-y-1">
{nav.map(({ to, label, icon: Icon }) => (
<NavLink
key={to}
to={to}
end={to === '/'}
className={({ isActive }) =>
cn(
'flex items-center gap-2 rounded-md px-3 py-2 text-sm font-medium',
isActive
? 'bg-primary text-primary-foreground'
: 'text-foreground hover:bg-accent hover:text-accent-foreground',
)
}
>
<Icon className="h-4 w-4" />
{label}
</NavLink>
))}
</nav>
</aside>
<main className="flex-1 overflow-auto p-8">
<Outlet />
</main>
</div>
);
}

View File

@@ -0,0 +1,25 @@
import * as React from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const badgeVariants = cva(
'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors',
{
variants: {
variant: {
default: 'border-transparent bg-primary text-primary-foreground',
secondary: 'border-transparent bg-muted text-foreground',
destructive: 'border-transparent bg-destructive text-destructive-foreground',
outline: 'text-foreground',
success: 'border-transparent bg-emerald-500 text-white',
},
},
defaultVariants: { variant: 'default' },
},
);
export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {}
export function Badge({ className, variant, ...props }: BadgeProps) {
return <div className={cn(badgeVariants({ variant }), className)} {...props} />;
}

View File

@@ -0,0 +1,48 @@
import * as React from 'react';
import { Slot } from '@radix-ui/react-slot';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const buttonVariants = cva(
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
default: 'h-10 px-4 py-2',
sm: 'h-9 rounded-md px-3',
lg: 'h-11 rounded-md px-8',
icon: 'h-10 w-10',
},
},
defaultVariants: { variant: 'default', size: 'default' },
},
);
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : 'button';
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
);
},
);
Button.displayName = 'Button';
export { buttonVariants };

View File

@@ -0,0 +1,44 @@
import * as React from 'react';
import { cn } from '@/lib/utils';
export const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn('rounded-lg border bg-card text-card-foreground shadow-sm', className)} {...props} />
),
);
Card.displayName = 'Card';
export const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn('flex flex-col space-y-1.5 p-6', className)} {...props} />
),
);
CardHeader.displayName = 'CardHeader';
export const CardTitle = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn('text-lg font-semibold leading-none tracking-tight', className)} {...props} />
),
);
CardTitle.displayName = 'CardTitle';
export const CardDescription = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn('text-sm text-muted-foreground', className)} {...props} />
),
);
CardDescription.displayName = 'CardDescription';
export const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
),
);
CardContent.displayName = 'CardContent';
export const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn('flex items-center p-6 pt-0', className)} {...props} />
),
);
CardFooter.displayName = 'CardFooter';

View File

@@ -0,0 +1,19 @@
import * as React from 'react';
import { cn } from '@/lib/utils';
export type InputProps = React.InputHTMLAttributes<HTMLInputElement>;
export const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => (
<input
ref={ref}
type={type}
className={cn(
'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
{...props}
/>
),
);
Input.displayName = 'Input';