diff --git a/src/app/dashboard/layout.tsx b/src/app/dashboard/layout.tsx new file mode 100644 index 0000000..b7a02c0 --- /dev/null +++ b/src/app/dashboard/layout.tsx @@ -0,0 +1,20 @@ +import { Sidebar } from "@/components/layout/sidebar"; +import { Header } from "@/components/layout/header"; + +export default function DashboardLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
+ +
+
+
+ {children} +
+
+
+ ); +} diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx new file mode 100644 index 0000000..2a1eb7d --- /dev/null +++ b/src/app/dashboard/page.tsx @@ -0,0 +1,222 @@ +import { + Card, + CardContent, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { + TrendingUp, + TrendingDown, + ShoppingCart, + Package, + Users, + DollarSign, + MoreHorizontal, + ArrowUpRight, + type LucideIcon, +} from "lucide-react"; +import { Button } from "@/components/ui/button"; +import dashboardData from "@/data/dashboard.json"; + +const ICON_MAP: Record = { + DollarSign, + ShoppingCart, + Package, + Users, +}; + +export default function DashboardPage() { + return ( +
+ {/* KPI Cards */} +
+ {dashboardData.kpiCards.map((card) => { + const Icon = ICON_MAP[card.icon]; + return ( + + +
+
+

{card.title}

+

{card.value}

+
+ {card.trend === "up" ? ( +
+ + {card.change} +
+ ) : ( +
+ + {card.change} +
+ )} + {card.period} +
+
+
+ {Icon && } +
+
+
+
+ ); + })} +
+ + {/* Charts Row */} +
+ {/* Sales Chart */} + + + {dashboardData.labels.salesChart} +
+
+
+ {dashboardData.labels.currentYear} +
+
+
+ {dashboardData.labels.previousYear} +
+
+ + +
+ {dashboardData.chart.months.map((month, i) => ( +
+
+
+
+
+ {month} +
+ ))} +
+ + + + {/* Donut Chart Widget */} + + + {dashboardData.labels.productCategories} + + + +
+ {/* Category List */} +
+ {dashboardData.productCategories.map((cat) => ( +
+
+
+ {cat.name} +
+ {cat.value} +
+ ))} +
+ + {/* Donut Visual */} +
+ + + + + + +
+
+ + +
+ + {/* Tables Row */} +
+ {/* Recent Transactions */} + + + {dashboardData.labels.recentTransactions} + + + +
+ + + + + + + + + + + + + {dashboardData.recentTransactions.map((trx) => ( + + + + + + + + + ))} + +
{dashboardData.tableHeaders.id}{dashboardData.tableHeaders.customer}{dashboardData.tableHeaders.items}{dashboardData.tableHeaders.total}{dashboardData.tableHeaders.status}{dashboardData.tableHeaders.time}
{trx.id}{trx.customer}{trx.items} item{trx.total} + + {trx.status} + + {trx.time}
+
+
+
+ + {/* Top Products */} + + + {dashboardData.labels.topProducts} + + + +
+ {dashboardData.topProducts.map((product, i) => ( +
+
+ {i + 1} +
+
+

{product.name}

+

{product.sold} terjual

+
+

{product.revenue}

+
+ ))} +
+
+
+
+
+ ); +} diff --git a/src/app/globals.css b/src/app/globals.css index c24a7a8..fe37aa8 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -5,39 +5,62 @@ @custom-variant dark (&:is(.dark *)); :root { - --card: oklch(1 0 0); - --card-foreground: oklch(0.145 0 0); - --popover: oklch(1 0 0); - --popover-foreground: oklch(0.145 0 0); - --primary: oklch(0.205 0 0); - --primary-foreground: oklch(0.985 0 0); - --secondary: oklch(0.97 0 0); - --secondary-foreground: oklch(0.205 0 0); - --muted: oklch(0.97 0 0); - --muted-foreground: oklch(0.556 0 0); - --accent: oklch(0.97 0 0); - --accent-foreground: oklch(0.205 0 0); - --destructive: oklch(0.577 0.245 27.325); - --destructive-foreground: #fafafa; - --border: oklch(0.922 0 0); - --input: oklch(0.922 0 0); - --ring: oklch(0.708 0 0); - --radius: 0.625rem; - --background: oklch(1 0 0); - --foreground: oklch(0.145 0 0); - --chart-1: oklch(0.87 0 0); - --chart-2: oklch(0.556 0 0); - --chart-3: oklch(0.439 0 0); - --chart-4: oklch(0.371 0 0); - --chart-5: oklch(0.269 0 0); - --sidebar: oklch(0.985 0 0); - --sidebar-foreground: oklch(0.145 0 0); - --sidebar-primary: oklch(0.205 0 0); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.97 0 0); - --sidebar-accent-foreground: oklch(0.205 0 0); - --sidebar-border: oklch(0.922 0 0); - --sidebar-ring: oklch(0.708 0 0); + /* Neutral Palette */ + --neutral-50: #FAFAFA; + --neutral-100: #F5F5F5; + --neutral-200: #E5E5E5; + --neutral-300: #D4D4D4; + --neutral-400: #A3A3A3; + --neutral-500: #737373; + --neutral-600: #525252; + --neutral-700: #404040; + --neutral-800: #262626; + --neutral-900: #171717; + + /* Semantic Colors */ + --success: #22C55E; + --warning: #F59E0B; + --error: #EF4444; + --info: #3B82F6; + + /* shadcn Tokens */ + --card: #FFFFFF; + --card-foreground: #171717; + --popover: #FFFFFF; + --popover-foreground: #171717; + --primary: #171717; + --primary-foreground: #FAFAFA; + --secondary: #F5F5F5; + --secondary-foreground: #171717; + --muted: #F5F5F5; + --muted-foreground: #737373; + --accent: #F5F5F5; + --accent-foreground: #171717; + --destructive: #EF4444; + --destructive-foreground: #FAFAFA; + --border: #E5E5E5; + --input: #D4D4D4; + --ring: #525252; + --radius: 12px; + --background: #FAFAFA; + --foreground: #171717; + + /* Chart Colors */ + --chart-1: #404040; + --chart-2: #A3A3A3; + --chart-3: #D4D4D4; + --chart-4: #525252; + --chart-5: #737373; + + /* Sidebar */ + --sidebar: #FFFFFF; + --sidebar-foreground: #171717; + --sidebar-primary: #171717; + --sidebar-primary-foreground: #FAFAFA; + --sidebar-accent: #F5F5F5; + --sidebar-accent-foreground: #171717; + --sidebar-border: #E5E5E5; + --sidebar-ring: #525252; } @theme inline { @@ -77,17 +100,17 @@ --color-chart-1: var(--chart-1); --color-popover-foreground: var(--popover-foreground); --color-popover: var(--popover); - --radius-sm: calc(var(--radius) * 0.6); - --radius-md: calc(var(--radius) * 0.8); - --radius-lg: var(--radius); - --radius-xl: calc(var(--radius) * 1.4); - --radius-2xl: calc(var(--radius) * 1.8); - --radius-3xl: calc(var(--radius) * 2.2); - --radius-4xl: calc(var(--radius) * 2.6); + --radius-sm: 8px; + --radius-md: 12px; + --radius-lg: 16px; + --radius-xl: 20px; + --radius-2xl: 24px; + --radius-3xl: 28px; + --radius-4xl: 32px; } body { - font-family: var(--font-poppins), var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif; + font-family: var(--font-inter), var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } @@ -103,51 +126,51 @@ body { } ::-webkit-scrollbar-thumb { - background: var(--border); + background: var(--neutral-300); border-radius: 9999px; } ::-webkit-scrollbar-thumb:hover { - background: var(--muted-foreground); + background: var(--neutral-400); } * { scrollbar-width: thin; - scrollbar-color: var(--border) transparent; + scrollbar-color: var(--neutral-300) transparent; } .dark { - --background: oklch(0.145 0 0); - --foreground: oklch(0.985 0 0); - --card: oklch(0.205 0 0); - --card-foreground: oklch(0.985 0 0); - --popover: oklch(0.205 0 0); - --popover-foreground: oklch(0.985 0 0); - --primary: oklch(0.922 0 0); - --primary-foreground: oklch(0.205 0 0); - --secondary: oklch(0.269 0 0); - --secondary-foreground: oklch(0.985 0 0); - --muted: oklch(0.269 0 0); - --muted-foreground: oklch(0.708 0 0); - --accent: oklch(0.269 0 0); - --accent-foreground: oklch(0.985 0 0); - --destructive: oklch(0.704 0.191 22.216); - --border: oklch(1 0 0 / 10%); - --input: oklch(1 0 0 / 15%); - --ring: oklch(0.556 0 0); - --chart-1: oklch(0.87 0 0); - --chart-2: oklch(0.556 0 0); - --chart-3: oklch(0.439 0 0); - --chart-4: oklch(0.371 0 0); - --chart-5: oklch(0.269 0 0); - --sidebar: oklch(0.205 0 0); - --sidebar-foreground: oklch(0.985 0 0); - --sidebar-primary: oklch(0.488 0.243 264.376); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.269 0 0); - --sidebar-accent-foreground: oklch(0.985 0 0); - --sidebar-border: oklch(1 0 0 / 10%); - --sidebar-ring: oklch(0.556 0 0); + --background: #171717; + --foreground: #FAFAFA; + --card: #2D2D2D; + --card-foreground: #FAFAFA; + --popover: #262626; + --popover-foreground: #FAFAFA; + --primary: #FAFAFA; + --primary-foreground: #171717; + --secondary: #262626; + --secondary-foreground: #FAFAFA; + --muted: #262626; + --muted-foreground: #A3A3A3; + --accent: #262626; + --accent-foreground: #FAFAFA; + --destructive: #EF4444; + --border: #404040; + --input: #404040; + --ring: #737373; + --chart-1: #D4D4D4; + --chart-2: #737373; + --chart-3: #525252; + --chart-4: #A3A3A3; + --chart-5: #404040; + --sidebar: #262626; + --sidebar-foreground: #FAFAFA; + --sidebar-primary: #FAFAFA; + --sidebar-primary-foreground: #171717; + --sidebar-accent: #404040; + --sidebar-accent-foreground: #FAFAFA; + --sidebar-border: #404040; + --sidebar-ring: #737373; } @layer base { @@ -160,4 +183,4 @@ body { html { @apply font-sans; } -} \ No newline at end of file +} diff --git a/src/app/onboarding/page.tsx b/src/app/onboarding/page.tsx index cbac17d..e9169f5 100644 --- a/src/app/onboarding/page.tsx +++ b/src/app/onboarding/page.tsx @@ -1,6 +1,7 @@ "use client"; import { useState } from "react"; +import { useRouter } from "next/navigation"; import Image from "next/image"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; @@ -20,6 +21,7 @@ import { ArrowRightIcon, CheckCircle, ChevronRightIcon } from "lucide-react"; import onboardingData from "@/data/onboarding.json"; export default function OnboardingPage() { + const router = useRouter(); const [step, setStep] = useState(1); const [selectedBusiness, setSelectedBusiness] = useState(null); const [selectedPlan, setSelectedPlan] = useState(null); @@ -47,19 +49,20 @@ export default function OnboardingPage() { const handleSubmit = () => { console.log("Onboarding complete:", { selectedBusiness, form, selectedPlan }); + router.push("/dashboard"); }; return ( -
+
{/* Left Panel */}
{/* Logo */}
-
- P +
+ P
- + Profitra
@@ -71,11 +74,11 @@ export default function OnboardingPage() { {[1, 2, 3].map((s) => (
{step === s && ( - + {onboardingData.stepLabels[String(s) as keyof typeof onboardingData.stepLabels]} )} @@ -111,7 +114,7 @@ export default function OnboardingPage() {
{/* Actions */} -
+
{/* Right Panel */} -
-
+
+
Onboarding illustration -

+

Pilih Jenis Bisnis Anda

-

+

Profitra akan menyesuaikan fitur dan pengaturan awal berdasarkan jenis usaha yang Anda jalankan.

@@ -201,9 +204,9 @@ function StepOne({
{selected === type.label && ( -
- +
+
)} @@ -252,10 +255,10 @@ function StepTwo({ }) { return (
-

+

Lengkapi Informasi Bisnis

-

+

Masukkan informasi dasar mengenai usaha Anda.

@@ -363,10 +366,10 @@ function StepThree({ }) { return (
-

+

Pilih Paket Profitra

-

+

Mulai gratis dan upgrade kapan saja sesuai perkembangan bisnis Anda.

@@ -375,31 +378,32 @@ function StepThree({ onSelect(plan.name)} - className={`relative cursor-pointer transition-all duration-150 ${selected === plan.name - ? "border-primary bg-primary/5 shadow-sm ring-1 ring-primary" - : "hover:border-foreground/20 hover:bg-accent/50" + className={`relative cursor-pointer transition-all duration-150 rounded-lg ${selected === plan.name + ? "border-neutral-900 bg-neutral-50 shadow-sm ring-1 ring-neutral-900" + : "border-neutral-100 hover:border-neutral-300 hover:bg-neutral-50" }`} + style={{ boxShadow: selected === plan.name ? '0 1px 2px rgba(0,0,0,.05), 0 4px 10px rgba(0,0,0,.04)' : undefined }} > {plan.popular && ( - + Paling Populer )} - {plan.name} + {plan.name}
- + {plan.price} {plan.priceSub && ( - + {plan.priceSub} )}
{plan.description && ( -

+

{plan.description}

)} @@ -410,10 +414,10 @@ function StepThree({ {plan.features.map((feature) => (
  • {feature} diff --git a/src/components/layout/header.tsx b/src/components/layout/header.tsx new file mode 100644 index 0000000..6236415 --- /dev/null +++ b/src/components/layout/header.tsx @@ -0,0 +1,158 @@ +"use client"; + +import { useState } from "react"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { + Menu, + LayoutDashboard, + ShoppingCart, + Package, + Users, + BarChart3, + Settings, + LogOut, + Bell, + Calendar, + Search, +} from "lucide-react"; +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; +import { Sheet, SheetContent, SheetTrigger, SheetTitle } from "@/components/ui/sheet"; +import { Input } from "@/components/ui/input"; + +const NAV_ITEMS = [ + { + label: "Dashboard", + href: "/dashboard", + icon: LayoutDashboard, + }, + { + label: "POS", + href: "/dashboard/pos", + icon: ShoppingCart, + }, + { + label: "Produk", + href: "/dashboard/products", + icon: Package, + }, + { + label: "Pelanggan", + href: "/dashboard/customers", + icon: Users, + }, + { + label: "Laporan", + href: "/dashboard/reports", + icon: BarChart3, + }, + { + label: "Pengaturan", + href: "/dashboard/settings", + icon: Settings, + }, +]; + +export function Header() { + const [open, setOpen] = useState(false); + const pathname = usePathname(); + + return ( +
    + {/* Left: Mobile Menu + Page Title */} +
    + {/* Mobile Menu */} + + + + + +
    +
    + P +
    + + Profitra + +
    + +
    + + + Keluar + +
    +
    +
    + + {/* Page Title */} +
    +

    + {NAV_ITEMS.find((item) => item.href === pathname)?.label || "Dashboard"} +

    +
    +
    + + {/* Right: Search, Date, Notifications, Profile */} +
    + {/* Search - Hidden on mobile */} +
    + + +
    + + {/* Date Filter */} + + + {/* Notifications */} + + + {/* User Profile */} +
    +
    + YP +
    +
    +

    Yoga Pangestu

    +

    Admin

    +
    +
    +
    +
    + ); +} diff --git a/src/components/layout/sidebar.tsx b/src/components/layout/sidebar.tsx new file mode 100644 index 0000000..5b850d2 --- /dev/null +++ b/src/components/layout/sidebar.tsx @@ -0,0 +1,123 @@ +"use client"; + +import { useState } from "react"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { + LayoutDashboard, + ShoppingCart, + Package, + Users, + BarChart3, + Settings, + ChevronLeft, + ChevronRight, + LogOut, +} from "lucide-react"; +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; + +const NAV_ITEMS = [ + { + label: "Dashboard", + href: "/dashboard", + icon: LayoutDashboard, + }, + { + label: "POS", + href: "/dashboard/pos", + icon: ShoppingCart, + }, + { + label: "Produk", + href: "/dashboard/products", + icon: Package, + }, + { + label: "Pelanggan", + href: "/dashboard/customers", + icon: Users, + }, + { + label: "Laporan", + href: "/dashboard/reports", + icon: BarChart3, + }, + { + label: "Pengaturan", + href: "/dashboard/settings", + icon: Settings, + }, +]; + +export function Sidebar() { + const [collapsed, setCollapsed] = useState(false); + const pathname = usePathname(); + + return ( + + ); +} diff --git a/src/data/dashboard.json b/src/data/dashboard.json new file mode 100644 index 0000000..c238268 --- /dev/null +++ b/src/data/dashboard.json @@ -0,0 +1,80 @@ +{ + "kpiCards": [ + { + "title": "Total Penjualan", + "value": "Rp 12.450.000", + "change": "+12.5%", + "trend": "up", + "icon": "DollarSign", + "period": "vs bulan lalu" + }, + { + "title": "Total Transaksi", + "value": "1.248", + "change": "+8.2%", + "trend": "up", + "icon": "ShoppingCart", + "period": "vs bulan lalu" + }, + { + "title": "Produk Terjual", + "value": "3.456", + "change": "-2.4%", + "trend": "down", + "icon": "Package", + "period": "vs bulan lalu" + }, + { + "title": "Total Pelanggan", + "value": "567", + "change": "+24.3%", + "trend": "up", + "icon": "Users", + "period": "vs bulan lalu" + } + ], + "recentTransactions": [ + { "id": "TRX-001", "customer": "Budi Santoso", "items": 3, "total": "Rp 125.000", "status": "Selesai", "time": "10:30" }, + { "id": "TRX-002", "customer": "Siti Rahayu", "items": 1, "total": "Rp 45.000", "status": "Selesai", "time": "10:15" }, + { "id": "TRX-003", "customer": "Ahmad Hidayat", "items": 5, "total": "Rp 287.500", "status": "Pending", "time": "09:45" }, + { "id": "TRX-004", "customer": "Dewi Lestari", "items": 2, "total": "Rp 98.000", "status": "Selesai", "time": "09:30" }, + { "id": "TRX-005", "customer": "Rudi Hermawan", "items": 4, "total": "Rp 175.000", "status": "Selesai", "time": "09:00" }, + { "id": "TRX-006", "customer": "Maya Putri", "items": 2, "total": "Rp 67.000", "status": "Dibatalkan", "time": "08:45" } + ], + "topProducts": [ + { "name": "Kopi Arabica 250gr", "sold": 145, "revenue": "Rp 2.175.000" }, + { "name": "Roti Tawar Gandum", "sold": 132, "revenue": "Rp 792.000" }, + { "name": "Susu Fresh 1L", "sold": 98, "revenue": "Rp 686.000" }, + { "name": "Mie Instan Pack", "sold": 87, "revenue": "Rp 435.000" }, + { "name": "Air Mineral 600ml", "sold": 76, "revenue": "Rp 152.000" } + ], + "productCategories": [ + { "name": "Makanan", "value": "45%", "color": "bg-neutral-900" }, + { "name": "Minuman", "value": "30%", "color": "bg-neutral-600" }, + { "name": "Snack", "value": "15%", "color": "bg-neutral-400" }, + { "name": "Lainnya", "value": "10%", "color": "bg-neutral-200" } + ], + "chart": { + "months": ["Jan", "Feb", "Mar", "Apr", "Mei", "Jun", "Jul", "Agu", "Sep", "Okt", "Nov", "Des"], + "currentYear": [45, 52, 48, 65, 58, 72, 68, 75, 82, 78, 85, 90], + "previousYear": [35, 42, 38, 55, 48, 62, 58, 65, 72, 68, 75, 80] + }, + "labels": { + "salesChart": "Grafik Penjualan", + "currentYear": "Tahun Ini", + "previousYear": "Tahun Lalu", + "productCategories": "Kategori Produk", + "recentTransactions": "Transaksi Terakhir", + "topProducts": "Produk Terlaris", + "viewAll": "Lihat Semua", + "vsLastMonth": "vs bulan lalu" + }, + "tableHeaders": { + "id": "ID", + "customer": "Pelanggan", + "items": "Item", + "total": "Total", + "status": "Status", + "time": "Waktu" + } +}