feat: initialize Next.js project with database schema
- Set up Next.js with App Router, TypeScript, Tailwind CSS - Configure shadcn/ui with dark theme - Install dependencies: lightweight-charts, papaparse, lucide-react - Set up Drizzle ORM with better-sqlite3 - Create database schema for candles and annotations tables - Generate migration SQL
This commit is contained in:
parent
7d2fc42b73
commit
d04b673cfa
25 changed files with 903 additions and 0 deletions
38
src/app/globals.css
Normal file
38
src/app/globals.css
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
--card: 222.2 84% 4.9%;
|
||||
--card-foreground: 210 40% 98%;
|
||||
--popover: 222.2 84% 4.9%;
|
||||
--popover-foreground: 210 40% 98%;
|
||||
--primary: 217.2 91.2% 59.8%;
|
||||
--primary-foreground: 222.2 47.4% 11.2%;
|
||||
--secondary: 217.2 32.6% 17.5%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 65.1%;
|
||||
--accent: 217.2 32.6% 17.5%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
--ring: 224.3 76.3% 48%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
}
|
||||
19
src/app/layout.tsx
Normal file
19
src/app/layout.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import type { Metadata } from "next";
|
||||
import "./globals.css";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Candle Annotator",
|
||||
description: "Annotate candlestick charts for ML training",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className="antialiased">{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
10
src/app/page.tsx
Normal file
10
src/app/page.tsx
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
export default function Home() {
|
||||
return (
|
||||
<main className="min-h-screen p-8">
|
||||
<h1 className="text-2xl font-bold">Candle Annotator</h1>
|
||||
<p className="mt-4 text-slate-400">
|
||||
Upload CSV data and annotate candlestick charts
|
||||
</p>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
51
src/components/ui/button.tsx
Normal file
51
src/components/ui/button.tsx
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import * as React from "react"
|
||||
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",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
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> {}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, ...props }, ref) => {
|
||||
return (
|
||||
<button
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
Button.displayName = "Button"
|
||||
|
||||
export { Button, buttonVariants }
|
||||
53
src/components/ui/tooltip.tsx
Normal file
53
src/components/ui/tooltip.tsx
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const TooltipProvider = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ children, ...props }, ref) => (
|
||||
<div ref={ref} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
))
|
||||
TooltipProvider.displayName = "TooltipProvider"
|
||||
|
||||
const Tooltip = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ children, ...props }, ref) => (
|
||||
<div ref={ref} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
))
|
||||
Tooltip.displayName = "Tooltip"
|
||||
|
||||
const TooltipTrigger = React.forwardRef<
|
||||
HTMLButtonElement,
|
||||
React.ButtonHTMLAttributes<HTMLButtonElement>
|
||||
>(({ children, ...props }, ref) => (
|
||||
<button ref={ref} {...props}>
|
||||
{children}
|
||||
</button>
|
||||
))
|
||||
TooltipTrigger.displayName = "TooltipTrigger"
|
||||
|
||||
const TooltipContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
))
|
||||
TooltipContent.displayName = "TooltipContent"
|
||||
|
||||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
||||
15
src/lib/db/index.ts
Normal file
15
src/lib/db/index.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import Database from 'better-sqlite3';
|
||||
import { drizzle } from 'drizzle-orm/better-sqlite3';
|
||||
import * as schema from './schema';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
// Ensure data directory exists
|
||||
const dataDir = path.join(process.cwd(), 'data');
|
||||
if (!fs.existsSync(dataDir)) {
|
||||
fs.mkdirSync(dataDir, { recursive: true });
|
||||
}
|
||||
|
||||
const dbPath = path.join(dataDir, 'candles.db');
|
||||
const sqlite = new Database(dbPath);
|
||||
export const db = drizzle(sqlite, { schema });
|
||||
23
src/lib/db/migrate.ts
Normal file
23
src/lib/db/migrate.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import Database from 'better-sqlite3';
|
||||
import { drizzle } from 'drizzle-orm/better-sqlite3';
|
||||
import { migrate } from 'drizzle-orm/better-sqlite3/migrator';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
export function runMigrations() {
|
||||
// Ensure data directory exists
|
||||
const dataDir = path.join(process.cwd(), 'data');
|
||||
if (!fs.existsSync(dataDir)) {
|
||||
fs.mkdirSync(dataDir, { recursive: true });
|
||||
}
|
||||
|
||||
const dbPath = path.join(dataDir, 'candles.db');
|
||||
const sqlite = new Database(dbPath);
|
||||
const db = drizzle(sqlite);
|
||||
|
||||
// Run migrations
|
||||
migrate(db, { migrationsFolder: './drizzle' });
|
||||
|
||||
sqlite.close();
|
||||
console.log('✅ Migrations complete');
|
||||
}
|
||||
18
src/lib/db/schema.ts
Normal file
18
src/lib/db/schema.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { sqliteTable, integer, real, text } from 'drizzle-orm/sqlite-core';
|
||||
|
||||
export const candles = sqliteTable('candles', {
|
||||
id: integer('id').primaryKey({ autoIncrement: true }),
|
||||
time: integer('time').notNull().unique(),
|
||||
open: real('open').notNull(),
|
||||
high: real('high').notNull(),
|
||||
low: real('low').notNull(),
|
||||
close: real('close').notNull(),
|
||||
});
|
||||
|
||||
export const annotations = sqliteTable('annotations', {
|
||||
id: integer('id').primaryKey({ autoIncrement: true }),
|
||||
timestamp: integer('timestamp').notNull(),
|
||||
label_type: text('label_type').notNull(),
|
||||
geometry: text('geometry'), // JSON string for line coordinates
|
||||
created_at: integer('created_at').notNull(),
|
||||
});
|
||||
6
src/lib/utils.ts
Normal file
6
src/lib/utils.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { type ClassValue, clsx } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue