Update global styles and enhance onboarding page. Refactored CSS variables for a neutral color palette and improved styling consistency. Added routing functionality to the onboarding page and updated component styles for better visual coherence.
This commit is contained in:
parent
152e994961
commit
0416e9191f
20
src/app/dashboard/layout.tsx
Normal file
20
src/app/dashboard/layout.tsx
Normal file
@ -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 (
|
||||
<div className="flex h-screen bg-neutral-50">
|
||||
<Sidebar />
|
||||
<div className="flex-1 flex flex-col min-w-0">
|
||||
<Header />
|
||||
<main className="flex-1 overflow-y-auto p-6">
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
222
src/app/dashboard/page.tsx
Normal file
222
src/app/dashboard/page.tsx
Normal file
@ -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<string, LucideIcon> = {
|
||||
DollarSign,
|
||||
ShoppingCart,
|
||||
Package,
|
||||
Users,
|
||||
};
|
||||
|
||||
export default function DashboardPage() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* KPI Cards */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-5">
|
||||
{dashboardData.kpiCards.map((card) => {
|
||||
const Icon = ICON_MAP[card.icon];
|
||||
return (
|
||||
<Card key={card.title} className="border-neutral-100 rounded-lg" style={{ boxShadow: '0 1px 2px rgba(0,0,0,.05), 0 4px 10px rgba(0,0,0,.04)' }}>
|
||||
<CardContent className="p-5">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-neutral-500 font-medium">{card.title}</p>
|
||||
<p className="text-2xl font-bold text-neutral-900 tracking-tight">{card.value}</p>
|
||||
<div className="flex items-center gap-1.5">
|
||||
{card.trend === "up" ? (
|
||||
<div className="flex items-center gap-1 px-2 py-0.5 rounded-full bg-green-50">
|
||||
<TrendingUp className="size-3 text-green-600" />
|
||||
<span className="text-xs font-medium text-green-600">{card.change}</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-1 px-2 py-0.5 rounded-full bg-red-50">
|
||||
<TrendingDown className="size-3 text-red-600" />
|
||||
<span className="text-xs font-medium text-red-600">{card.change}</span>
|
||||
</div>
|
||||
)}
|
||||
<span className="text-xs text-neutral-400">{card.period}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-10 h-10 rounded-md bg-neutral-100 flex items-center justify-center">
|
||||
{Icon && <Icon className="size-5 text-neutral-600" />}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Charts Row */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-5">
|
||||
{/* Sales Chart */}
|
||||
<Card className="lg:col-span-2 border-neutral-100 rounded-lg" style={{ boxShadow: '0 1px 2px rgba(0,0,0,.05), 0 4px 10px rgba(0,0,0,.04)' }}>
|
||||
<CardHeader className="flex flex-row items-center justify-between pb-4">
|
||||
<CardTitle className="text-base font-semibold text-neutral-900">{dashboardData.labels.salesChart}</CardTitle>
|
||||
<div className="flex items-center gap-4 text-xs">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<div className="w-3 h-3 rounded-sm bg-neutral-700" />
|
||||
<span className="text-neutral-500">{dashboardData.labels.currentYear}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<div className="w-3 h-3 rounded-sm bg-neutral-300" />
|
||||
<span className="text-neutral-500">{dashboardData.labels.previousYear}</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="h-[280px] flex items-end gap-2 px-2">
|
||||
{dashboardData.chart.months.map((month, i) => (
|
||||
<div key={month} className="flex-1 flex flex-col items-center gap-1">
|
||||
<div className="w-full flex gap-0.5 items-end justify-center" style={{ height: "240px" }}>
|
||||
<div
|
||||
className="flex-1 bg-neutral-300 rounded-t-sm"
|
||||
style={{ height: `${dashboardData.chart.previousYear[i]}%` }}
|
||||
/>
|
||||
<div
|
||||
className="flex-1 bg-neutral-700 rounded-t-sm"
|
||||
style={{ height: `${dashboardData.chart.currentYear[i]}%` }}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-[10px] text-neutral-400 mt-1">{month}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Donut Chart Widget */}
|
||||
<Card className="border-neutral-100 rounded-lg" style={{ boxShadow: '0 1px 2px rgba(0,0,0,.05), 0 4px 10px rgba(0,0,0,.04)' }}>
|
||||
<CardHeader className="flex flex-row items-center justify-between pb-4">
|
||||
<CardTitle className="text-base font-semibold text-neutral-900">{dashboardData.labels.productCategories}</CardTitle>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 text-neutral-400">
|
||||
<MoreHorizontal className="size-4" />
|
||||
</Button>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center gap-6">
|
||||
{/* Category List */}
|
||||
<div className="flex-1 space-y-3">
|
||||
{dashboardData.productCategories.map((cat) => (
|
||||
<div key={cat.name} className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className={`w-3 h-3 rounded-sm ${cat.color}`} />
|
||||
<span className="text-sm text-neutral-600">{cat.name}</span>
|
||||
</div>
|
||||
<span className="text-sm font-medium text-neutral-900">{cat.value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Donut Visual */}
|
||||
<div className="relative w-28 h-28 shrink-0">
|
||||
<svg viewBox="0 0 100 100" className="w-full h-full -rotate-90">
|
||||
<circle cx="50" cy="50" r="35" fill="none" stroke="#E5E5E5" strokeWidth="14" />
|
||||
<circle cx="50" cy="50" r="35" fill="none" stroke="#171717" strokeWidth="14" strokeDasharray="99 121" strokeDashoffset="0" />
|
||||
<circle cx="50" cy="50" r="35" fill="none" stroke="#525252" strokeWidth="14" strokeDasharray="66 154" strokeDashoffset="-99" />
|
||||
<circle cx="50" cy="50" r="35" fill="none" stroke="#A3A3A3" strokeWidth="14" strokeDasharray="33 187" strokeDashoffset="-165" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Tables Row */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-5">
|
||||
{/* Recent Transactions */}
|
||||
<Card className="lg:col-span-2 border-neutral-100 rounded-lg" style={{ boxShadow: '0 1px 2px rgba(0,0,0,.05), 0 4px 10px rgba(0,0,0,.04)' }}>
|
||||
<CardHeader className="flex flex-row items-center justify-between pb-4">
|
||||
<CardTitle className="text-base font-semibold text-neutral-900">{dashboardData.labels.recentTransactions}</CardTitle>
|
||||
<Button variant="ghost" size="sm" className="text-neutral-500 hover:text-neutral-700 h-8 gap-1">
|
||||
{dashboardData.labels.viewAll}
|
||||
<ArrowUpRight className="size-3.5" />
|
||||
</Button>
|
||||
</CardHeader>
|
||||
<CardContent className="p-0">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b border-neutral-100 bg-neutral-50">
|
||||
<th className="text-left text-xs font-semibold text-neutral-500 uppercase tracking-wider px-5 py-3">{dashboardData.tableHeaders.id}</th>
|
||||
<th className="text-left text-xs font-semibold text-neutral-500 uppercase tracking-wider px-5 py-3">{dashboardData.tableHeaders.customer}</th>
|
||||
<th className="text-left text-xs font-semibold text-neutral-500 uppercase tracking-wider px-5 py-3">{dashboardData.tableHeaders.items}</th>
|
||||
<th className="text-left text-xs font-semibold text-neutral-500 uppercase tracking-wider px-5 py-3">{dashboardData.tableHeaders.total}</th>
|
||||
<th className="text-left text-xs font-semibold text-neutral-500 uppercase tracking-wider px-5 py-3">{dashboardData.tableHeaders.status}</th>
|
||||
<th className="text-left text-xs font-semibold text-neutral-500 uppercase tracking-wider px-5 py-3">{dashboardData.tableHeaders.time}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{dashboardData.recentTransactions.map((trx) => (
|
||||
<tr key={trx.id} className="border-b border-neutral-100 last:border-0 hover:bg-neutral-50 transition-colors">
|
||||
<td className="px-5 py-3.5 text-sm font-medium text-neutral-900">{trx.id}</td>
|
||||
<td className="px-5 py-3.5 text-sm text-neutral-700">{trx.customer}</td>
|
||||
<td className="px-5 py-3.5 text-sm text-neutral-500">{trx.items} item</td>
|
||||
<td className="px-5 py-3.5 text-sm font-medium text-neutral-900">{trx.total}</td>
|
||||
<td className="px-5 py-3.5">
|
||||
<span className={`inline-flex px-2 py-1 rounded-full text-xs font-medium ${
|
||||
trx.status === "Selesai"
|
||||
? "bg-green-50 text-green-700"
|
||||
: trx.status === "Pending"
|
||||
? "bg-yellow-50 text-yellow-700"
|
||||
: "bg-red-50 text-red-700"
|
||||
}`}>
|
||||
{trx.status}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-5 py-3.5 text-sm text-neutral-500">{trx.time}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Top Products */}
|
||||
<Card className="border-neutral-100 rounded-lg" style={{ boxShadow: '0 1px 2px rgba(0,0,0,.05), 0 4px 10px rgba(0,0,0,.04)' }}>
|
||||
<CardHeader className="flex flex-row items-center justify-between pb-4">
|
||||
<CardTitle className="text-base font-semibold text-neutral-900">{dashboardData.labels.topProducts}</CardTitle>
|
||||
<Button variant="ghost" size="sm" className="text-neutral-500 hover:text-neutral-700 h-8 gap-1">
|
||||
{dashboardData.labels.viewAll}
|
||||
<ArrowUpRight className="size-3.5" />
|
||||
</Button>
|
||||
</CardHeader>
|
||||
<CardContent className="p-0">
|
||||
<div className="divide-y divide-neutral-100">
|
||||
{dashboardData.topProducts.map((product, i) => (
|
||||
<div key={product.name} className="flex items-center gap-4 px-5 py-3.5 hover:bg-neutral-50 transition-colors">
|
||||
<div className="w-8 h-8 rounded-md bg-neutral-100 flex items-center justify-center shrink-0">
|
||||
<span className="text-xs font-semibold text-neutral-500">{i + 1}</span>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-neutral-900 truncate">{product.name}</p>
|
||||
<p className="text-xs text-neutral-500">{product.sold} terjual</p>
|
||||
</div>
|
||||
<p className="text-sm font-medium text-neutral-900">{product.revenue}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<string | null>(null);
|
||||
const [selectedPlan, setSelectedPlan] = useState<string | null>(null);
|
||||
@ -47,19 +49,20 @@ export default function OnboardingPage() {
|
||||
|
||||
const handleSubmit = () => {
|
||||
console.log("Onboarding complete:", { selectedBusiness, form, selectedPlan });
|
||||
router.push("/dashboard");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen lg:h-screen bg-background flex flex-col lg:flex-row lg:overflow-hidden">
|
||||
<div className="min-h-screen lg:h-screen bg-neutral-50 flex flex-col lg:flex-row lg:overflow-hidden">
|
||||
{/* Left Panel */}
|
||||
<div className="w-full lg:w-[60%] flex flex-col px-5 py-6 sm:px-6 sm:py-10 lg:px-20 lg:py-14 lg:h-full lg:overflow-hidden">
|
||||
{/* Logo */}
|
||||
<div className="mb-6 lg:mb-10 shrink-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-8 h-8 rounded-lg bg-primary flex items-center justify-center">
|
||||
<span className="text-primary-foreground font-bold text-sm">P</span>
|
||||
<div className="w-8 h-8 rounded-md bg-neutral-900 flex items-center justify-center">
|
||||
<span className="text-white font-bold text-sm">P</span>
|
||||
</div>
|
||||
<span className="text-xl font-semibold tracking-tight text-foreground">
|
||||
<span className="text-xl font-semibold tracking-tight text-neutral-900">
|
||||
Profitra
|
||||
</span>
|
||||
</div>
|
||||
@ -71,11 +74,11 @@ export default function OnboardingPage() {
|
||||
{[1, 2, 3].map((s) => (
|
||||
<div key={s} className="flex items-center gap-1.5">
|
||||
<div
|
||||
className={`h-1.5 rounded-full transition-all ${step === s ? "w-6 bg-primary" : "w-2 bg-muted-foreground/30"
|
||||
className={`h-1.5 rounded-full transition-all ${step === s ? "w-6 bg-neutral-900" : "w-2 bg-neutral-300"
|
||||
}`}
|
||||
/>
|
||||
{step === s && (
|
||||
<span className="text-[11px] font-medium text-foreground whitespace-nowrap">
|
||||
<span className="text-[11px] font-medium text-neutral-900 whitespace-nowrap">
|
||||
{onboardingData.stepLabels[String(s) as keyof typeof onboardingData.stepLabels]}
|
||||
</span>
|
||||
)}
|
||||
@ -111,7 +114,7 @@ export default function OnboardingPage() {
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-3 mt-6 lg:mt-10 pt-6 border-t border-border shrink-0">
|
||||
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-3 mt-6 lg:mt-10 pt-6 border-t border-neutral-100 shrink-0">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="lg"
|
||||
@ -145,15 +148,15 @@ export default function OnboardingPage() {
|
||||
</div>
|
||||
|
||||
{step === 1 && !selectedBusiness && (
|
||||
<p className="text-sm text-muted-foreground mt-3 shrink-0">
|
||||
<p className="text-sm text-neutral-500 mt-3 shrink-0">
|
||||
Silakan pilih jenis bisnis terlebih dahulu.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right Panel */}
|
||||
<div className="hidden lg:flex w-[40%] relative border-l border-border overflow-hidden p-3">
|
||||
<div className="relative w-full h-full rounded-xl overflow-hidden">
|
||||
<div className="hidden lg:flex w-[40%] relative border-l border-neutral-100 overflow-hidden p-3">
|
||||
<div className="relative w-full h-full rounded-lg overflow-hidden">
|
||||
<Image
|
||||
src={onboardingData.rightImages[String(step) as keyof typeof onboardingData.rightImages]}
|
||||
alt="Onboarding illustration"
|
||||
@ -188,10 +191,10 @@ function StepOne({
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<h2 className="text-2xl font-semibold tracking-tight text-foreground mb-2">
|
||||
<h2 className="text-2xl font-semibold tracking-tight text-neutral-900 mb-2">
|
||||
Pilih Jenis Bisnis Anda
|
||||
</h2>
|
||||
<p className="text-muted-foreground text-sm mb-8 leading-relaxed">
|
||||
<p className="text-neutral-500 text-sm mb-8 leading-relaxed">
|
||||
Profitra akan menyesuaikan fitur dan pengaturan awal berdasarkan jenis
|
||||
usaha yang Anda jalankan.
|
||||
</p>
|
||||
@ -201,9 +204,9 @@ function StepOne({
|
||||
<button
|
||||
key={type.label}
|
||||
onClick={() => onSelect(type.label)}
|
||||
className={`group relative aspect-4/3 rounded-lg overflow-hidden text-left transition-all duration-150 ${selected === type.label
|
||||
? "ring-2 ring-primary ring-offset-2 shadow-md"
|
||||
: "ring-1 ring-border hover:ring-foreground/30 hover:shadow-sm"
|
||||
className={`group relative aspect-4/3 rounded-md overflow-hidden text-left transition-all duration-150 ${selected === type.label
|
||||
? "ring-2 ring-neutral-900 ring-offset-2 shadow-md"
|
||||
: "ring-1 ring-neutral-200 hover:ring-neutral-400 hover:shadow-sm"
|
||||
}`}
|
||||
>
|
||||
<Image
|
||||
@ -220,8 +223,8 @@ function StepOne({
|
||||
</span>
|
||||
</div>
|
||||
{selected === type.label && (
|
||||
<div className="absolute top-2 right-2 w-5 h-5 rounded-full bg-primary flex items-center justify-center">
|
||||
<CheckCircle className="size-3 text-primary-foreground" strokeWidth={3} />
|
||||
<div className="absolute top-2 right-2 w-5 h-5 rounded-full bg-neutral-900 flex items-center justify-center">
|
||||
<CheckCircle className="size-3 text-white" strokeWidth={3} />
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
@ -252,10 +255,10 @@ function StepTwo({
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<h2 className="text-2xl font-semibold tracking-tight text-foreground mb-2">
|
||||
<h2 className="text-2xl font-semibold tracking-tight text-neutral-900 mb-2">
|
||||
Lengkapi Informasi Bisnis
|
||||
</h2>
|
||||
<p className="text-muted-foreground text-sm mb-8 leading-relaxed">
|
||||
<p className="text-neutral-500 text-sm mb-8 leading-relaxed">
|
||||
Masukkan informasi dasar mengenai usaha Anda.
|
||||
</p>
|
||||
|
||||
@ -363,10 +366,10 @@ function StepThree({
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<h2 className="text-2xl font-semibold tracking-tight text-foreground mb-2">
|
||||
<h2 className="text-2xl font-semibold tracking-tight text-neutral-900 mb-2">
|
||||
Pilih Paket Profitra
|
||||
</h2>
|
||||
<p className="text-muted-foreground text-sm mb-8 leading-relaxed">
|
||||
<p className="text-neutral-500 text-sm mb-8 leading-relaxed">
|
||||
Mulai gratis dan upgrade kapan saja sesuai perkembangan bisnis Anda.
|
||||
</p>
|
||||
|
||||
@ -375,31 +378,32 @@ function StepThree({
|
||||
<Card
|
||||
key={plan.name}
|
||||
onClick={() => 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 && (
|
||||
<Badge className="absolute -top-2.5 left-4">
|
||||
<Badge className="absolute -top-2.5 left-4 bg-neutral-900 text-white">
|
||||
Paling Populer
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
<CardHeader>
|
||||
<CardTitle>{plan.name}</CardTitle>
|
||||
<CardTitle className="text-neutral-900">{plan.name}</CardTitle>
|
||||
<div className="flex items-baseline gap-1">
|
||||
<span className="text-xl font-bold text-foreground">
|
||||
<span className="text-xl font-bold text-neutral-900">
|
||||
{plan.price}
|
||||
</span>
|
||||
{plan.priceSub && (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
<span className="text-xs text-neutral-500">
|
||||
{plan.priceSub}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{plan.description && (
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
<p className="text-xs text-neutral-500 mt-1">
|
||||
{plan.description}
|
||||
</p>
|
||||
)}
|
||||
@ -410,10 +414,10 @@ function StepThree({
|
||||
{plan.features.map((feature) => (
|
||||
<li
|
||||
key={feature}
|
||||
className="flex items-center gap-2 text-sm text-muted-foreground"
|
||||
className="flex items-center gap-2 text-sm text-neutral-600"
|
||||
>
|
||||
<CheckCircle
|
||||
className="size-3.5 text-primary shrink-0"
|
||||
className="size-3.5 text-neutral-900 shrink-0"
|
||||
strokeWidth={2.5}
|
||||
/>
|
||||
<span>{feature}</span>
|
||||
|
||||
158
src/components/layout/header.tsx
Normal file
158
src/components/layout/header.tsx
Normal file
@ -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 (
|
||||
<header className="flex items-center justify-between h-[72px] px-6 border-b border-neutral-100 bg-white shrink-0">
|
||||
{/* Left: Mobile Menu + Page Title */}
|
||||
<div className="flex items-center gap-4">
|
||||
{/* Mobile Menu */}
|
||||
<Sheet open={open} onOpenChange={setOpen}>
|
||||
<SheetTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="xl:hidden text-neutral-600">
|
||||
<Menu className="size-5" />
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent side="left" className="w-[260px] p-0 bg-white">
|
||||
<div className="flex items-center gap-3 px-5 h-[72px] border-b border-neutral-100">
|
||||
<div className="w-9 h-9 rounded-md bg-neutral-900 flex items-center justify-center">
|
||||
<span className="text-white font-bold text-sm">P</span>
|
||||
</div>
|
||||
<SheetTitle className="text-lg font-semibold tracking-tight text-neutral-900">
|
||||
Profitra
|
||||
</SheetTitle>
|
||||
</div>
|
||||
<nav className="py-4 px-3 space-y-1">
|
||||
{NAV_ITEMS.map((item) => {
|
||||
const isActive = pathname === item.href;
|
||||
return (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
onClick={() => setOpen(false)}
|
||||
className={cn(
|
||||
"flex items-center gap-3 px-3 h-10 rounded-md text-sm transition-colors",
|
||||
isActive
|
||||
? "bg-neutral-100 text-neutral-900 font-semibold"
|
||||
: "text-neutral-500 hover:bg-neutral-50 hover:text-neutral-700"
|
||||
)}
|
||||
>
|
||||
<item.icon className="size-5 shrink-0" />
|
||||
<span>{item.label}</span>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
<div className="border-t border-neutral-100 p-3">
|
||||
<Link
|
||||
href="/login"
|
||||
className="flex items-center gap-3 px-3 h-10 rounded-md text-sm text-neutral-500 hover:bg-neutral-50 hover:text-neutral-700 transition-colors"
|
||||
>
|
||||
<LogOut className="size-5 shrink-0" />
|
||||
<span>Keluar</span>
|
||||
</Link>
|
||||
</div>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
|
||||
{/* Page Title */}
|
||||
<div>
|
||||
<h1 className="text-lg font-semibold text-neutral-900">
|
||||
{NAV_ITEMS.find((item) => item.href === pathname)?.label || "Dashboard"}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right: Search, Date, Notifications, Profile */}
|
||||
<div className="flex items-center gap-3">
|
||||
{/* Search - Hidden on mobile */}
|
||||
<div className="hidden md:flex items-center relative">
|
||||
<Search className="absolute left-3 size-4 text-neutral-400" />
|
||||
<Input
|
||||
placeholder="Cari..."
|
||||
className="w-56 h-10 pl-9 bg-neutral-50 border-neutral-100 rounded-md text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Date Filter */}
|
||||
<Button variant="outline" size="sm" className="hidden sm:flex h-10 gap-2 border-neutral-100 text-neutral-600 rounded-md">
|
||||
<Calendar className="size-4" />
|
||||
<span className="text-sm">Hari Ini</span>
|
||||
</Button>
|
||||
|
||||
{/* Notifications */}
|
||||
<Button variant="ghost" size="icon" className="relative text-neutral-500 hover:text-neutral-700 h-10 w-10">
|
||||
<Bell className="size-5" />
|
||||
<span className="absolute top-2 right-2 w-2 h-2 rounded-full bg-red-500" />
|
||||
</Button>
|
||||
|
||||
{/* User Profile */}
|
||||
<div className="flex items-center gap-3 pl-3 border-l border-neutral-100">
|
||||
<div className="w-9 h-9 rounded-full bg-neutral-200 flex items-center justify-center">
|
||||
<span className="text-xs font-semibold text-neutral-600">YP</span>
|
||||
</div>
|
||||
<div className="hidden xl:block">
|
||||
<p className="text-sm font-medium text-neutral-900">Yoga Pangestu</p>
|
||||
<p className="text-xs text-neutral-500">Admin</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
123
src/components/layout/sidebar.tsx
Normal file
123
src/components/layout/sidebar.tsx
Normal file
@ -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 (
|
||||
<aside
|
||||
className={cn(
|
||||
"hidden xl:flex flex-col h-screen bg-white border-r border-neutral-100 transition-all duration-300",
|
||||
collapsed ? "w-[72px]" : "w-[260px]"
|
||||
)}
|
||||
>
|
||||
{/* Logo */}
|
||||
<div className="flex items-center gap-3 px-5 h-[72px] border-b border-neutral-100 shrink-0">
|
||||
<div className="w-9 h-9 rounded-md bg-neutral-900 flex items-center justify-center shrink-0">
|
||||
<span className="text-white font-bold text-sm">P</span>
|
||||
</div>
|
||||
{!collapsed && (
|
||||
<span className="text-lg font-semibold tracking-tight text-neutral-900">
|
||||
Profitra
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Navigation */}
|
||||
<nav className="flex-1 py-4 px-3 space-y-1 overflow-y-auto">
|
||||
{NAV_ITEMS.map((item) => {
|
||||
const isActive = pathname === item.href;
|
||||
return (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className={cn(
|
||||
"flex items-center gap-3 px-3 h-10 rounded-md text-sm transition-colors",
|
||||
isActive
|
||||
? "bg-neutral-100 text-neutral-900 font-semibold"
|
||||
: "text-neutral-500 hover:bg-neutral-50 hover:text-neutral-700"
|
||||
)}
|
||||
>
|
||||
<item.icon className="size-5 shrink-0" />
|
||||
{!collapsed && <span>{item.label}</span>}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="border-t border-neutral-100 p-3 space-y-1 shrink-0">
|
||||
<Link
|
||||
href="/login"
|
||||
className="flex items-center gap-3 px-3 h-10 rounded-md text-sm text-neutral-500 hover:bg-neutral-50 hover:text-neutral-700 transition-colors"
|
||||
>
|
||||
<LogOut className="size-5 shrink-0" />
|
||||
{!collapsed && <span>Keluar</span>}
|
||||
</Link>
|
||||
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setCollapsed(!collapsed)}
|
||||
className="w-full justify-center text-neutral-400 hover:text-neutral-600 h-10"
|
||||
>
|
||||
{collapsed ? (
|
||||
<ChevronRight className="size-4" />
|
||||
) : (
|
||||
<ChevronLeft className="size-4" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
80
src/data/dashboard.json
Normal file
80
src/data/dashboard.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user