vccs
Vue 3용 차트 컴포넌트 모음, Recharts에서 포팅
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Milton is a web application that helps small and medium businesses understand their performance through automated data analysis and KPI dashboards. Users upload their existing business data files (Excel/CSV), and the app uses AI to classify and map that data, calculate relevant KPIs, and generate insights — without requiring any technical knowledge.
Target users: Startup founders, SMB owners/operators (no technical background required)
Core value: Upload your business data (bookings, orders, transactions, memberships) → get automatic KPI dashboards and AI-generated insights.
Supported business types:
Key capabilities:
| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router, TypeScript) |
| UI | React 19, Tailwind CSS 4, Radix UI, Shadcn/ui, Lucide icons |
| Database & Auth | Supabase (PostgreSQL + Row Level Security) |
| AI | Vercel AI SDK + OpenAI API (gpt-4o-mini) |
| Charts | Recharts |
| Data Model Visualizer | React Flow |
| File Parsing | xlsx (Excel), PapaParse (CSV) |
| PDF Generation | jsPDF + html2canvas |
| Form Handling | React Hook Form + Zod |
| Testing | Vitest (unit), Playwright (E2E) |
git clone <repo-url>
cd milton
npm install
Create a .env.local file:
# OpenAI
OPENAI_API_KEY=your-openai-api-key
# Supabase (public — safe in browser)
NEXT_PUBLIC_SUPABASE_URL=your-supabase-url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
# Supabase (secret — server-side only, bypasses RLS)
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
Where to find Supabase keys: Dashboard → Settings → API
npm run dev # development server at http://localhost:3000
npm run build # production build
npm run test # run unit tests
┌─────────────────────────────────────────────────────────────────┐
│ Next.js App Router │
│ │
│ Pages (app/) API Routes (app/api/) │
│ ├── /auth ├── /auth/signup-complete │
│ ├── /onboarding ├── /ai/* (classify, analyze, insights) │
│ ├── /dashboard ├── /data/* (parse, upload, status) │
│ ├── /management ├── /kpis/* (calculate, select) │
│ └── / └── /analytics/* (per business type) │
└───────────────────────────────┬─────────────────────────────────┘
│
┌───────────────────────┼───────────────────────┐
│ │ │
Supabase OpenAI API Client Browser
(Auth + DB) (gpt-4o-mini) (React components)
User upload (CSV/Excel)
│
▼
/api/data/parse — Extract sheets, headers, sample rows
│
▼
/api/ai/dataset-classifier — Identify which business table this file maps to
│
▼
Data Mapping UI — User confirms/adjusts column → field mappings
│
▼
Value Normalization — Case-correct categories, normalize dates
│
▼
/api/data/upload-model-table — Insert rows into model_data table (JSONB)
│
▼
/api/kpis/calculate — Query model_data, aggregate, return KPI values
│
▼
Dashboard — Display KPI cards, charts, AI insights
auth.users, app creates profiles and companies records/onboarding/chat) — Describe your business in natural language; AI identifies business type (fitness_studio, restaurant, ecommerce, etc.)/onboarding/data-sources) — Select which data sources you have (bookings, transactions, orders, etc.)/onboarding/upload) — Upload Excel or CSV files; AI classifies each sheet to a canonical table/onboarding/kpi-selection) — Choose and rank which KPIs to track/dashboard with KPIs populatedAccessible at /management/ (requires admin role):
milton/
├── app/ # Next.js App Router
│ ├── (root)/page.tsx # Landing page
│ ├── auth/ # Login, signup, OAuth callback
│ ├── onboarding/ # 6-step onboarding flow
│ │ ├── chat/ # AI business model chat
│ │ ├── data-sources/ # Source selection
│ │ ├── upload/ # File upload step
│ │ ├── model/ # Data model viewer
│ │ └── kpi-selection/ # KPI selection step
│ ├── dashboard/ # Main app
│ │ ├── page.tsx # KPI dashboard home
│ │ ├── analytics/ # Analytics & drill-downs
│ │ ├── data/ # Data management
│ │ ├── upload/ # Additional uploads
│ │ ├── reporting/ # PDF report generation
│ │ ├── account/ # User account
│ │ └── settings/ # Dashboard settings
│ ├── management/ # Admin panel
│ │ ├── dashboard/ # Admin overview
│ │ ├── kpis/ # KPI CRUD
│ │ ├── templates/ # Template CRUD
│ │ └── data-tables/ # Data table CRUD
│ └── api/ # API routes (see API Reference)
│
├── components/
│ ├── dashboard/ # Dashboard & upload UI components
│ ├── onboarding/ # Onboarding wizard components
│ ├── management/ # Admin panel components
│ ├── layout/ # Navigation, providers, theme
│ ├── auth/ # Login/signup forms
│ ├── ai-elements/ # Chat UI components
│ └── ui/ # Shadcn/Radix primitives
│
├── lib/
│ ├── model/
│ │ ├── transform.ts # Core model types (TableDef, ModelProposal, etc.) + graph logic
│ │ └── default-models.ts # Hardcoded fallback models (fitness, restaurant, ecom)
│ ├── kpi-calculations/ # One file per KPI calculator function
│ │ ├── fitness/ # Fitness studio KPIs
│ │ ├── restaurant/ # Restaurant KPIs
│ │ ├── ecom/ # E-commerce KPIs
│ │ ├── financial/ # Cross-sector financial KPIs
│ │ ├── fieldAccessors.ts # Flexible field reading from JSONB rows
│ │ └── index.ts # Exports all calculators
│ ├── ai/ # AI helper modules
│ │ ├── dashboard-context.ts # Build AI context from KPI data
│ │ ├── normalize-values.ts # AI value normalization
│ │ ├── generate-ids.ts # AI-generated IDs for table rows
│ │ ├── insights.ts # Generic insight framework
│ │ ├── fitness-studio-insights.ts
│ │ └── restaurant-insights.ts
│ ├── types/
│ │ ├── data.ts # Shared data types (DataTable, TransactionData, etc.)
│ │ ├── kpi.ts # KPI types and selectors
│ │ ├── pipeline.ts # CRM/pipeline types
│ │ └── report.ts # Report data structures
│ ├── supabase/
│ │ ├── client.ts # Browser Supabase client
│ │ └── server.ts # Server-side Supabase client (service role)
│ ├── data-table-service.ts # Data table CRUD (Supabase)
│ ├── business-models.ts # BusinessModel record CRUD
│ ├── dashboard-kpis.ts # KPI routing by business type
│ ├── kpi-recipe-service.ts # Map KPI IDs → calculator functions
│ ├── kpi-availability.ts # Which KPIs are available
│ ├── report-data-service.ts # Gather data for PDF reports
│ └── utils.ts # cn(), normalizeDateValue(), etc.
│
├── types/
│ └── schema.ts # ColumnMapping and other shared types
│
├── supabase/ # DB migrations
├── test-data/ # Sample data files for testing
└── tests/ # Test suites
├── unit/ # Vitest unit tests
└── smoke/ # Playwright E2E tests
| Table | Purpose |
|---|---|
auth.users | Supabase-managed authentication |
profiles | User profile (id, user_id, company_id, full_name, email); see milton_admins for admin flag |
companies | Tenant record; linked to creator user |
business_models | Per-company business type, KPI selections, canonical data model, onboarding answers |
data_tables | Admin-defined canonical table schemas (fields as JSONB array) |
model_data | All uploaded business data — rows stored as JSONB, scoped to company_id + model_table_id |
kpis | KPI definitions (name, formula reference, business_types, is_published) |
business_model_templates | Templates per business type (required tables, relationships, KPI lists) |
custom_datasets | Metadata about uploaded files (schema, source info) |
dashboard_insights | Cached AI-generated insight text |
auth.users
└── profiles (one-to-one)
└── companies (one-to-many via created_by)
└── business_models (one-to-one)
└── model_data (one-to-many) ← all uploaded rows live here
└── data_tables (many-to-one via model_table_id)
This is the central storage table for all uploaded business data:
model_data (
id uuid PRIMARY KEY,
company_id uuid REFERENCES companies(id),
model_table_id uuid REFERENCES data_tables(id),
data jsonb, -- the actual row data
indexed_fields jsonb, -- extracted fields for faster querying
created_at timestamptz
)
All KPI calculations query this table, filtering by company_id and model_table_id, then reading the flexible data JSONB column.
company_id matches their companyThe "business model" represents a company's canonical data structure — which tables they use, what relationships exist, and what KPIs are relevant.
lib/model/default-models.ts) — fallback definitions used during onboarding when no Supabase template is availablebusiness_models.canonical_model as JSONB) — the actual model applied to a company, derived from the template + any user customizationsThe core types in lib/model/transform.ts:
interface FieldDef {
name: string;
type: "string" | "number" | "integer" | "date" | "currency" | "boolean";
required: boolean;
primaryKey?: boolean;
references?: { table: string; field?: string } | null;
allowedValues?: string[]; // For enum/categorical fields
displayName?: string; // Human-readable label (e.g., "Order Items" vs "ecom_order_items")
}
interface TableDef {
name: string; // Internal name used in DB (e.g., "ecom_orders")
displayName?: string; // User-facing label (e.g., "Orders")
fields: FieldDef[];
fileMapping?: { fileName?: string; columnMap?: Record<string, string> };
}
interface ModelProposal {
businessType: string;
recommendedTables: TableDef[];
relationships: RelationshipDef[];
}
| Key | Display Name | Tables |
|---|---|---|
fitness_studio | Fitness Studio | Customers, Classes, Instructors, Bookings, Payments |
restaurant | Restaurant | Tables, Menu Items, Orders, Order Items, Reservations |
ecommerce | E-commerce | Orders, Order Items, Products, Customers, Marketing Spend |
KPIs are stored in the kpis Supabase table. Each KPI has:
name — display namedefinition — descriptionformula — optional formula string (documentation)required_data — array of data_tables.id UUIDs the KPI needsbusiness_types — which business types this KPI applies tois_published — whether visible to users (drafts hidden)Each KPI has a dedicated TypeScript function in lib/kpi-calculations/:
// Common signature
async function calculateXxx(
supabase: SupabaseClient,
userId: string,
fromDate: string,
toDate: string
): Promise<{
currentValue: number;
historicalData: Array<{ period: string; value: number }>;
}>;
Fitness Studio KPIs:
Financial KPIs (cross-sector):
Restaurant KPIs:
E-commerce KPIs:
When a user opens the KPI selector, the app:
GET /api/data/status → gets which data_tables have uploaded data (by UUID)required_data UUIDs have dataSelected KPIs appear on the dashboard as:
The app uses two OpenAI integration patterns:
Used for conversational interfaces:
/app/onboarding/chat)Used for one-shot classification/analysis tasks:
/api/ai/dataset-classifier) — which business table does a file map to?/api/ai/business-model-analyzer) — what type of business is this?/api/data/normalize-values) — correct inconsistent categorical valueslib/ai/generate-ids.ts) — human-readable IDs for uploaded rowslib/ai/)All AI calls use gpt-4o-mini for cost efficiency. The model is not configurable per-call; it is set globally in each AI helper.
/auth/signupauth.usersPOST /api/auth/signup-complete creates profiles (basic info only) and companies records| Role | Access |
|---|---|
user | Own company data only; dashboard, uploads, KPI selection |
admin | All management routes; KPI/template/table CRUD |
Admin role is stored in public.milton_admins. Checked server-side via isUserAdminServer() in lib/profile-service-server.ts, which calls the is_milton_admin() RPC (migration 020).
lib/supabase/client.ts — browser client (anon key, subject to RLS)lib/supabase/server.ts — server client (service role key, bypasses RLS)| Method | Endpoint | Description |
|---|---|---|
| POST | /api/data/parse | Parse Excel/CSV; returns sheets, headers, sample rows, all rows |
| POST | /api/data/upload-model-table | Insert mapped rows into model_data table |
| DELETE | /api/data/delete-model-table | Remove data for a specific table |
| GET | /api/data/status | Which data tables have uploaded data (with row counts) |
| GET | /api/data/table-names | Resolve table UUIDs to names |
| GET | /api/data/table-preview | Preview first N rows of a table |
| POST | /api/data/normalize-values | AI-normalize categorical field values |
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/kpis/calculate | Calculate all selected KPIs for a date range |
| POST | /api/kpis/series | Get historical time series for a KPI |
| GET | /api/kpis/selected | Get user's selected KPI IDs and display modes |
| POST | /api/kpis/select | Save KPI selection |
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/ai/business-model-analyzer | Classify business type from description |
| POST | /api/ai/dataset-classifier | Classify uploaded dataset to a canonical table |
| POST | /api/ai/onboarding | Stream onboarding chat responses |
| POST | /api/ai/insight-generator | Generate AI insights from KPI data |
| POST | /api/milton/ask | Dashboard chat assistant |
| Endpoint | Business Type | Returns |
|---|---|---|
GET /api/analytics/fitness-studio/kpis | Fitness | All fitness KPI values |
GET /api/analytics/ecommerce/kpis | E-commerce | All ecom KPI values |
GET /api/analytics/restaurant/overview | Restaurant | Revenue, covers, etc. |
Various /api/analytics/fitness-studio/* | Fitness | Members, instructors, classes, cash flow, charts |
| Method | Endpoint | Description |
|---|---|---|
| GET/POST | /api/admin/kpis | List / create KPIs |
| PUT/DELETE | /api/admin/kpis/[id] | Update / delete KPI |
| GET | /api/admin/kpis/impact/[id] | Impact analysis before delete |
| GET/POST | /api/admin/templates | List / create templates |
| GET/PUT/DELETE | /api/admin/templates/[key] | Manage specific template |
| GET/POST | /api/admin/data-tables | List / create data table definitions |
| PUT/DELETE | /api/admin/data-tables/[id] | Update / delete data table |
| GET | /api/admin/data-tables/impact/[id] | Impact analysis before delete |
npm run test:unit # run once
npm run test:unit:watch # watch mode
Located in tests/unit/. Cover:
npx playwright test # run all E2E tests
Located in tests/smoke/. Cover:
Sample data files for manual testing are in test-data/ organized by business type (e.g., test-data/restaurant/).
| File | Purpose |
|---|---|
lib/model/transform.ts | Core data model types and transformation logic |
lib/model/default-models.ts | Hardcoded fallback business models |
lib/kpi-calculations/fieldAccessors.ts | Flexible field reading from JSONB rows |
lib/kpi-calculations/index.ts | Exports all KPI calculator functions |
lib/utils.ts | normalizeDateValue(), cn(), shared utilities |
lib/data-table-service.ts | Read data table definitions from Supabase |
lib/dashboard-kpis.ts | Routes KPI calculation requests by business type |
lib/kpi-recipe-service.ts | Maps KPI IDs to calculator functions |
app/api/data/upload-model-table/route.ts | Central upload handler with ID generation, FK resolution, normalization |
app/api/data/parse/route.ts | File parsing endpoint (Excel/CSV → rows) |
components/dashboard/ModelTableDetailView.tsx | Single-table upload + mapping UI |
components/dashboard/MultiFileUploadView.tsx | Multi-file drag-drop upload flow |
components/dashboard/data-mapping-confirmation.tsx | Column mapping modal |
components/dashboard/kpi-selector.tsx | KPI selection dialog |