From f11cd099f6e55d036a574f13e1403d06deb2b31f Mon Sep 17 00:00:00 2001 From: kovakmedya Date: Thu, 30 Apr 2026 06:19:44 +0300 Subject: [PATCH] =?UTF-8?q?feat(dashboard):=20real=20data=20=E2=80=94=20me?= =?UTF-8?q?trics,=20charts,=20top=20customers,=20recent=20transactions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dashboard is no longer mock data. Single getDashboardData(tenantId) server query computes everything in one pass. New aggregator (lib/appwrite/dashboard-queries.ts): - Pulls customers, invoices, finance_entries, tasks, services in parallel. - Derives: * metrics: totalCustomers, activeCustomers, monthIncome, prevMonthIncome (for delta), outstanding (unpaid invoice total), overdueCount, openTasks, urgentTasks * monthlyIncome: 12-month income+expense series for area chart * topCustomers: 5 highest-grossing customers by paid invoice total * recentTransactions: 8 newest finance entries * topServices: 5 services by aggregate unit price (placeholder, will refine when we have invoice line analytics) * newCustomersMonthly: 6-month new customer count for bar chart Components (dashboard/components/): - Metrics: 4 cards with trend indicator on income (delta vs previous month), warning tone on overdue invoices and urgent tasks. - IncomeChart: Recharts Area chart, dual income/expense series with gradient fills, Turkish month labels. - TopCustomers: ranked list with progress bars relative to top earner. - RecentTransactions: list with type badge, signed amount, link to /finance for full list. - CustomerGrowth: BarChart of new customers per month (last 6). - QuickActions: 4 buttons linking to /customers, /invoices, /calendar, /tasks (replaced template's New User/Add Product/etc). Layout: 4 metric cards row, then income chart + top customers (2-col), then recent transactions + customer growth (2-col). Removed: - src/app/(dashboard)/dashboard-2/ (was the demo page; same components re-exported into the real /dashboard from there. Now /dashboard owns its components.) - 'Dashboard 2' entry from CommandSearch; replaced with our actual module list (Müşteriler / Hizmetler / Yazılımlarımız / Takvim / Görevler / Gelir-Gider / Faturalar). --- .../components/customer-insights.tsx | 248 ------------------ .../components/dashboard-header.tsx | 69 ----- .../components/metrics-overview.tsx | 90 ------- .../dashboard-2/components/quick-actions.tsx | 39 --- .../components/recent-transactions.tsx | 130 --------- .../components/revenue-breakdown.tsx | 204 -------------- .../dashboard-2/components/sales-chart.tsx | 115 -------- .../dashboard-2/components/top-products.tsx | 123 --------- .../dashboard-2/data/dashboard-data.json | 39 --- src/app/(dashboard)/dashboard-2/page.tsx | 47 ---- .../dashboard/components/customer-growth.tsx | 59 +++++ .../dashboard/components/income-chart.tsx | 90 +++++++ .../dashboard/components/metrics.tsx | 112 ++++++++ .../dashboard/components/quick-actions.tsx | 35 +++ .../components/recent-transactions.tsx | 82 ++++++ .../dashboard/components/top-customers.tsx | 63 +++++ src/app/(dashboard)/dashboard/page.tsx | 29 +- src/components/command-search.tsx | 22 +- src/lib/appwrite/dashboard-queries.ts | 225 ++++++++++++++++ 19 files changed, 698 insertions(+), 1123 deletions(-) delete mode 100644 src/app/(dashboard)/dashboard-2/components/customer-insights.tsx delete mode 100644 src/app/(dashboard)/dashboard-2/components/dashboard-header.tsx delete mode 100644 src/app/(dashboard)/dashboard-2/components/metrics-overview.tsx delete mode 100644 src/app/(dashboard)/dashboard-2/components/quick-actions.tsx delete mode 100644 src/app/(dashboard)/dashboard-2/components/recent-transactions.tsx delete mode 100644 src/app/(dashboard)/dashboard-2/components/revenue-breakdown.tsx delete mode 100644 src/app/(dashboard)/dashboard-2/components/sales-chart.tsx delete mode 100644 src/app/(dashboard)/dashboard-2/components/top-products.tsx delete mode 100644 src/app/(dashboard)/dashboard-2/data/dashboard-data.json delete mode 100644 src/app/(dashboard)/dashboard-2/page.tsx create mode 100644 src/app/(dashboard)/dashboard/components/customer-growth.tsx create mode 100644 src/app/(dashboard)/dashboard/components/income-chart.tsx create mode 100644 src/app/(dashboard)/dashboard/components/metrics.tsx create mode 100644 src/app/(dashboard)/dashboard/components/quick-actions.tsx create mode 100644 src/app/(dashboard)/dashboard/components/recent-transactions.tsx create mode 100644 src/app/(dashboard)/dashboard/components/top-customers.tsx create mode 100644 src/lib/appwrite/dashboard-queries.ts diff --git a/src/app/(dashboard)/dashboard-2/components/customer-insights.tsx b/src/app/(dashboard)/dashboard-2/components/customer-insights.tsx deleted file mode 100644 index 8356b5f..0000000 --- a/src/app/(dashboard)/dashboard-2/components/customer-insights.tsx +++ /dev/null @@ -1,248 +0,0 @@ -"use client" - -import { useState } from "react" -import { BarChart, Bar, XAxis, YAxis, CartesianGrid } from "recharts" -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" -import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart" -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" -import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" -import { Button } from "@/components/ui/button" -import { Users, MapPin, TrendingUp, Target, ArrowUpIcon, UserIcon } from "lucide-react" - -const customerGrowthData = [ - { month: "Jan", new: 245, returning: 890, churn: 45 }, - { month: "Feb", new: 312, returning: 934, churn: 52 }, - { month: "Mar", new: 289, returning: 1023, churn: 38 }, - { month: "Apr", new: 456, returning: 1156, churn: 61 }, - { month: "May", new: 523, returning: 1298, churn: 47 }, - { month: "Jun", new: 634, returning: 1445, churn: 55 }, -] - -const chartConfig = { - new: { - label: "New Customers", - color: "var(--chart-1)", - }, - returning: { - label: "Returning", - color: "var(--chart-2)", - }, - churn: { - label: "Churned", - color: "var(--chart-3)", - }, -} - -const demographicsData = [ - { ageGroup: "18-24", customers: 2847, percentage: "18.0%", growth: "+15.2%", growthColor: "text-green-600" }, - { ageGroup: "25-34", customers: 4521, percentage: "28.5%", growth: "+8.7%", growthColor: "text-green-600" }, - { ageGroup: "35-44", customers: 3982, percentage: "25.1%", growth: "+3.4%", growthColor: "text-blue-600" }, - { ageGroup: "45-54", customers: 2734, percentage: "17.2%", growth: "+1.2%", growthColor: "text-orange-600" }, - { ageGroup: "55+", customers: 1763, percentage: "11.2%", growth: "-2.1%", growthColor: "text-red-600" }, -] - -const regionsData = [ - { region: "North America", customers: 6847, revenue: "$847,523", growth: "+12.3%", growthColor: "text-green-600" }, - { region: "Europe", customers: 4521, revenue: "$563,891", growth: "+9.7%", growthColor: "text-green-600" }, - { region: "Asia Pacific", customers: 2892, revenue: "$321,456", growth: "+18.4%", growthColor: "text-blue-600" }, - { region: "Latin America", customers: 1123, revenue: "$187,234", growth: "+15.8%", growthColor: "text-green-600" }, - { region: "Others", customers: 464, revenue: "$67,891", growth: "+5.2%", growthColor: "text-orange-600" }, -] - -export function CustomerInsights() { - const [activeTab, setActiveTab] = useState("growth") - - return ( - - - Customer Insights - Growth trends and demographics - - - - - - - Growth - - - - Demographics - - - - Regions - - - - -
- {/* Chart and Key Metrics Side by Side */} -
- {/* Chart Area - 70% */} -
-

Customer Growth Trends

- - - - - - } /> - - - - - -
- - {/* Key Metrics - 30% */} -
-

Key Metrics

-
-
-
- - Total Customers -
-
15,847
-
- - +12.5% from last month -
-
- -
-
- - Retention Rate -
-
92.4%
-
- - +2.1% improvement -
-
- -
-
- - Avg. LTV -
-
$2,847
-
- - +8.3% growth -
-
-
-
-
-
-
- - -
- - - - Age Group - Customers - Percentage - Growth - - - - {demographicsData.map((row, index) => ( - - {row.ageGroup} - {row.customers.toLocaleString()} - {row.percentage} - - {row.growth} - - - ))} - -
-
-
-
- 0 of {demographicsData.length} row(s) selected. -
-
- - -
-
-
- - - -
- - - - Region - Customers - Revenue - Growth - - - - {regionsData.map((row, index) => ( - - {row.region} - {row.customers.toLocaleString()} - {row.revenue} - - {row.growth} - - - ))} - -
-
-
-
- 0 of {regionsData.length} row(s) selected. -
-
- - -
-
-
-
-
-
- ) -} diff --git a/src/app/(dashboard)/dashboard-2/components/dashboard-header.tsx b/src/app/(dashboard)/dashboard-2/components/dashboard-header.tsx deleted file mode 100644 index a25a4d7..0000000 --- a/src/app/(dashboard)/dashboard-2/components/dashboard-header.tsx +++ /dev/null @@ -1,69 +0,0 @@ -"use client" - -import { useState } from "react" -import { Calendar, Clock, RefreshCw, Filter } from "lucide-react" -import { Card, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" -import { Button } from "@/components/ui/button" -import { Badge } from "@/components/ui/badge" -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" -import { Separator } from "@/components/ui/separator" - -export function DashboardHeader() { - const [dateRange, setDateRange] = useState("30d") - const lastUpdated = new Date().toLocaleString() - - return ( - - -
-
- Business Dashboard - - Comprehensive overview of your business performance and key metrics - -
-
- - - Live Data - - -
-
- - - -
-
-
- - Date Range: - -
- -
- -
- Last updated: {lastUpdated} -
-
-
-
- ) -} diff --git a/src/app/(dashboard)/dashboard-2/components/metrics-overview.tsx b/src/app/(dashboard)/dashboard-2/components/metrics-overview.tsx deleted file mode 100644 index 6c7f595..0000000 --- a/src/app/(dashboard)/dashboard-2/components/metrics-overview.tsx +++ /dev/null @@ -1,90 +0,0 @@ -"use client" - -import { - TrendingUp, - TrendingDown, - DollarSign, - Users, - ShoppingCart, - BarChart3 -} from "lucide-react" -import { Card, CardAction, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card" -import { Badge } from "@/components/ui/badge" - -const metrics = [ - { - title: "Total Revenue", - value: "$54,230", - description: "Monthly revenue", - change: "+12%", - trend: "up", - icon: DollarSign, - footer: "Trending up this month", - subfooter: "Revenue for the last 6 months" - }, - { - title: "Active Customers", - value: "2,350", - description: "Total active users", - change: "+5.2%", - trend: "up", - icon: Users, - footer: "Strong user retention", - subfooter: "Engagement exceeds targets" - }, - { - title: "Total Orders", - value: "1,247", - description: "Orders this month", - change: "-2.1%", - trend: "down", - icon: ShoppingCart, - footer: "Down 2% this period", - subfooter: "Order volume needs attention" - }, - { - title: "Conversion Rate", - value: "3.24%", - description: "Average conversion", - change: "+8.3%", - trend: "up", - icon: BarChart3, - footer: "Steady performance increase", - subfooter: "Meets conversion projections" - }, -] - -export function MetricsOverview() { - return ( -
- {metrics.map((metric) => { - const TrendIcon = metric.trend === "up" ? TrendingUp : TrendingDown - - return ( - - - {metric.title} - - {metric.value} - - - - - {metric.change} - - - - -
- {metric.footer} -
-
- {metric.subfooter} -
-
-
- ) - })} -
- ) -} diff --git a/src/app/(dashboard)/dashboard-2/components/quick-actions.tsx b/src/app/(dashboard)/dashboard-2/components/quick-actions.tsx deleted file mode 100644 index 63aa623..0000000 --- a/src/app/(dashboard)/dashboard-2/components/quick-actions.tsx +++ /dev/null @@ -1,39 +0,0 @@ -"use client" - -import { Plus, Settings, FileText, Download } from "lucide-react" -import { Button } from "@/components/ui/button" -import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu" - -export function QuickActions() { - return ( -
- - - - - - - - - Generate Report - - - - Export Data - - - - - Dashboard Settings - - - -
- ) -} diff --git a/src/app/(dashboard)/dashboard-2/components/recent-transactions.tsx b/src/app/(dashboard)/dashboard-2/components/recent-transactions.tsx deleted file mode 100644 index f3dc595..0000000 --- a/src/app/(dashboard)/dashboard-2/components/recent-transactions.tsx +++ /dev/null @@ -1,130 +0,0 @@ -"use client" - -import { Eye, MoreHorizontal } from "lucide-react" -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" -import { Button } from "@/components/ui/button" -import { Badge } from "@/components/ui/badge" -import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" -import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu" - -const transactions = [ - { - id: "TXN-001", - customer: { - name: "Olivia Martin", - email: "olivia.martin@email.com", - avatar: "https://notion-avatars.netlify.app/api/avatar/?preset=female-7", - }, - amount: "$1,999.00", - status: "completed", - date: "2 hours ago", - }, - { - id: "TXN-002", - customer: { - name: "Jackson Lee", - email: "jackson.lee@email.com", - avatar: "https://notion-avatars.netlify.app/api/avatar/?preset=male-1", - }, - amount: "$2,999.00", - status: "pending", - date: "5 hours ago", - }, - { - id: "TXN-003", - customer: { - name: "Isabella Nguyen", - email: "isabella.nguyen@email.com", - avatar: "https://notion-avatars.netlify.app/api/avatar/?preset=female-2", - }, - amount: "$39.00", - status: "completed", - date: "1 day ago", - }, - { - id: "TXN-004", - customer: { - name: "William Kim", - email: "will@email.com", - avatar: "https://notion-avatars.netlify.app/api/avatar/?preset=male-5", - }, - amount: "$299.00", - status: "failed", - date: "2 days ago", - }, - { - id: "TXN-005", - customer: { - name: "Sofia Davis", - email: "sofia.davis@email.com", - avatar: "https://notion-avatars.netlify.app/api/avatar/?preset=female-4", - }, - amount: "$99.00", - status: "completed", - date: "3 days ago", - }, -] - -export function RecentTransactions() { - return ( - - -
- Recent Transactions - Latest customer transactions -
- -
- - {transactions.map((transaction) => ( -
-
- - - {transaction.customer.name.split(" ").map(n => n[0]).join("")} - -
-
-
-

{transaction.customer.name}

-

{transaction.customer.email}

-
-
-
- - {transaction.status} - -
-

{transaction.amount}

-

{transaction.date}

-
- - - - - - View Details - Download Receipt - Contact Customer - - -
-
-
-
- ))} -
-
- ) -} diff --git a/src/app/(dashboard)/dashboard-2/components/revenue-breakdown.tsx b/src/app/(dashboard)/dashboard-2/components/revenue-breakdown.tsx deleted file mode 100644 index dcd1765..0000000 --- a/src/app/(dashboard)/dashboard-2/components/revenue-breakdown.tsx +++ /dev/null @@ -1,204 +0,0 @@ -"use client" - -import * as React from "react" -import { Label, Pie, PieChart, Sector } from "recharts" -import type { PieSectorDataItem } from "recharts/types/polar/Pie" -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" -import { ChartContainer, ChartStyle, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart" -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" -import { Button } from "@/components/ui/button" - -const revenueData = [ - { category: "subscriptions", value: 45, amount: 24500, fill: "var(--color-subscriptions)" }, - { category: "sales", value: 30, amount: 16300, fill: "var(--color-sales)" }, - { category: "services", value: 15, amount: 8150, fill: "var(--color-services)" }, - { category: "partnerships", value: 10, amount: 5430, fill: "var(--color-partnerships)" }, -] - -const chartConfig = { - revenue: { - label: "Revenue", - }, - amount: { - label: "Amount", - }, - subscriptions: { - label: "Subscriptions", - color: "var(--chart-1)", - }, - sales: { - label: "One-time Sales", - color: "var(--chart-2)", - }, - services: { - label: "Services", - color: "var(--chart-3)", - }, - partnerships: { - label: "Partnerships", - color: "var(--chart-4)", - }, -} - -export function RevenueBreakdown() { - const id = "revenue-breakdown" - const [activeCategory, setActiveCategory] = React.useState("sales") - - const activeIndex = React.useMemo(() => { - const index = revenueData.findIndex((item) => item.category === activeCategory) - return index === -1 ? 0 : index - }, [activeCategory]) - - const categories = React.useMemo(() => revenueData.map((item) => item.category), []) - - return ( - - - -
- Revenue Breakdown - Revenue distribution by source -
-
- - -
-
- -
-
- - - } - /> - ( - - - - - )} - > - - - -
- -
- {revenueData.map((item, index) => { - const config = chartConfig[item.category as keyof typeof chartConfig] - const isActive = index === activeIndex - - return ( -
setActiveCategory(item.category)} - > -
- - {config?.label} -
-
-
${(item.amount / 1000).toFixed(1)}K
-
{item.value}%
-
-
- ) - })} -
-
-
-
- ) -} diff --git a/src/app/(dashboard)/dashboard-2/components/sales-chart.tsx b/src/app/(dashboard)/dashboard-2/components/sales-chart.tsx deleted file mode 100644 index bebe8d4..0000000 --- a/src/app/(dashboard)/dashboard-2/components/sales-chart.tsx +++ /dev/null @@ -1,115 +0,0 @@ -"use client" - -import { useState } from "react" -import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts" -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" -import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart" -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" -import { Button } from "@/components/ui/button" - -const salesData = [ - { month: "Jan", sales: 12500, target: 15000 }, - { month: "Feb", sales: 18200, target: 15000 }, - { month: "Mar", sales: 16800, target: 15000 }, - { month: "Apr", sales: 22400, target: 20000 }, - { month: "May", sales: 24600, target: 20000 }, - { month: "Jun", sales: 28200, target: 25000 }, - { month: "Jul", sales: 31500, target: 25000 }, - { month: "Aug", sales: 29800, target: 25000 }, - { month: "Sep", sales: 33200, target: 30000 }, - { month: "Oct", sales: 35100, target: 30000 }, - { month: "Nov", sales: 38900, target: 35000 }, - { month: "Dec", sales: 42300, target: 35000 }, -] - -const chartConfig = { - sales: { - label: "Sales", - color: "var(--primary)", - }, - target: { - label: "Target", - color: "var(--primary)", - }, -} - -export function SalesChart() { - const [timeRange, setTimeRange] = useState("12m") - - return ( - - -
- Sales Performance - Monthly sales vs targets -
-
- - -
-
- -
- - - - - - - - - - - - - - - `$${value.toLocaleString()}`} - /> - } /> - - - - -
-
-
- ) -} diff --git a/src/app/(dashboard)/dashboard-2/components/top-products.tsx b/src/app/(dashboard)/dashboard-2/components/top-products.tsx deleted file mode 100644 index a12140c..0000000 --- a/src/app/(dashboard)/dashboard-2/components/top-products.tsx +++ /dev/null @@ -1,123 +0,0 @@ -"use client" - -import { Eye, Star, TrendingUp } from "lucide-react" -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" -import { Button } from "@/components/ui/button" -import { Badge } from "@/components/ui/badge" -import { Progress } from "@/components/ui/progress" - -const products = [ - { - id: 1, - name: "Premium Dashboard", - sales: 2847, - revenue: "$142,350", - growth: "+23%", - rating: 4.8, - stock: 145, - category: "Software", - }, - { - id: 2, - name: "Analytics Pro", - sales: 1923, - revenue: "$96,150", - growth: "+18%", - rating: 4.6, - stock: 67, - category: "Tools", - }, - { - id: 3, - name: "Mobile App Suite", - sales: 1456, - revenue: "$72,800", - growth: "+12%", - rating: 4.9, - stock: 234, - category: "Mobile", - }, - { - id: 4, - name: "Enterprise License", - sales: 892, - revenue: "$178,400", - growth: "+8%", - rating: 4.7, - stock: 12, - category: "Enterprise", - }, - { - id: 5, - name: "Basic Subscription", - sales: 3421, - revenue: "$68,420", - growth: "+31%", - rating: 4.4, - stock: 999, - category: "Subscription", - }, -] - -export function TopProducts() { - return ( - - -
- Top Products - Best performing products this month -
- -
- - {products.map((product, index) => ( -
-
- #{index + 1} -
-
-
-
-

{product.name}

- - {product.category} - -
-
-
- - {product.rating} -
- - {product.sales} sales -
-
-
-
-

{product.revenue}

- - - {product.growth} - -
-
- Stock: {product.stock} - 100 ? 100 : (product.stock / 100) * 100} - className="w-12 h-1" - /> -
-
-
-
- ))} -
-
- ) -} diff --git a/src/app/(dashboard)/dashboard-2/data/dashboard-data.json b/src/app/(dashboard)/dashboard-2/data/dashboard-data.json deleted file mode 100644 index 10c9448..0000000 --- a/src/app/(dashboard)/dashboard-2/data/dashboard-data.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "totalRevenue": 54231.89, - "revenueChange": 12.5, - "activeCustomers": 2350, - "customerChange": 5.2, - "totalOrders": 1247, - "orderChange": -2.1, - "conversionRate": 3.24, - "conversionChange": 8.3, - "salesData": [ - { "month": "Jan", "sales": 12500, "target": 15000 }, - { "month": "Feb", "sales": 18200, "target": 15000 }, - { "month": "Mar", "sales": 16800, "target": 15000 }, - { "month": "Apr", "sales": 22400, "target": 20000 }, - { "month": "May", "sales": 24600, "target": 20000 }, - { "month": "Jun", "sales": 28200, "target": 25000 }, - { "month": "Jul", "sales": 31500, "target": 25000 }, - { "month": "Aug", "sales": 29800, "target": 25000 }, - { "month": "Sep", "sales": 33200, "target": 30000 }, - { "month": "Oct", "sales": 35100, "target": 30000 }, - { "month": "Nov", "sales": 38900, "target": 35000 }, - { "month": "Dec", "sales": 42300, "target": 35000 } - ], - "revenueBreakdown": [ - { "name": "Subscriptions", "value": 45, "amount": 24500, "color": "hsl(210, 100%, 50%)" }, - { "name": "One-time Sales", "value": 30, "amount": 16300, "color": "hsl(280, 100%, 70%)" }, - { "name": "Services", "value": 15, "amount": 8150, "color": "hsl(120, 100%, 40%)" }, - { "name": "Partnerships", "value": 10, "amount": 5430, "color": "hsl(30, 100%, 50%)" } - ], - "customerGrowth": [ - { "month": "Jan", "new": 245, "returning": 890, "churn": 45 }, - { "month": "Feb", "new": 312, "returning": 934, "churn": 52 }, - { "month": "Mar", "new": 289, "returning": 1023, "churn": 38 }, - { "month": "Apr", "new": 456, "returning": 1156, "churn": 61 }, - { "month": "May", "new": 523, "returning": 1298, "churn": 47 }, - { "month": "Jun", "new": 634, "returning": 1445, "churn": 55 } - ], - "lastUpdated": "2025-08-12T15:30:00Z" -} diff --git a/src/app/(dashboard)/dashboard-2/page.tsx b/src/app/(dashboard)/dashboard-2/page.tsx deleted file mode 100644 index 2d98a2f..0000000 --- a/src/app/(dashboard)/dashboard-2/page.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { MetricsOverview } from "./components/metrics-overview" -import { SalesChart } from "./components/sales-chart" -import { RecentTransactions } from "./components/recent-transactions" -import { TopProducts } from "./components/top-products" -import { CustomerInsights } from "./components/customer-insights" -import { QuickActions } from "./components/quick-actions" -import { RevenueBreakdown } from "./components/revenue-breakdown" - -export default function Dashboard2() { - return ( -
- {/* Enhanced Header */} - -
-
-

Business Dashboard

-

- Monitor your business performance and key metrics in real-time -

-
- -
- - {/* Main Dashboard Grid */} -
- {/* Top Row - Key Metrics */} - - - - {/* Second Row - Charts in 6-6 columns */} -
- - -
- - {/* Third Row - Two Column Layout */} -
- - -
- - {/* Fourth Row - Customer Insights and Team Performance */} - -
-
- ) -} diff --git a/src/app/(dashboard)/dashboard/components/customer-growth.tsx b/src/app/(dashboard)/dashboard/components/customer-growth.tsx new file mode 100644 index 0000000..c87cd56 --- /dev/null +++ b/src/app/(dashboard)/dashboard/components/customer-growth.tsx @@ -0,0 +1,59 @@ +"use client"; + +import { + Bar, + BarChart, + CartesianGrid, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from "recharts"; + +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; + +type Point = { month: string; count: number }; + +export function CustomerGrowth({ data }: { data: Point[] }) { + const total = data.reduce((s, p) => s + p.count, 0); + return ( + + + Yeni müşteriler + Son 6 ay — toplam {total} yeni müşteri + + + + + + + + [`${value} müşteri`, "Yeni"]} + /> + + + + + + ); +} diff --git a/src/app/(dashboard)/dashboard/components/income-chart.tsx b/src/app/(dashboard)/dashboard/components/income-chart.tsx new file mode 100644 index 0000000..26aac71 --- /dev/null +++ b/src/app/(dashboard)/dashboard/components/income-chart.tsx @@ -0,0 +1,90 @@ +"use client"; + +import { + Area, + AreaChart, + CartesianGrid, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from "recharts"; + +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { formatTRY } from "@/lib/format"; + +type Point = { month: string; income: number; expense: number }; + +export function IncomeChart({ data }: { data: Point[] }) { + const total = data.reduce((s, p) => s + p.income, 0); + + return ( + + + Gelir / Gider + + Son 12 ay — toplam gelir {formatTRY(total)} + + + + + + + + + + + + + + + + + + + v >= 1000 ? `${(v / 1000).toFixed(0)}k` : String(v) + } + /> + [ + formatTRY(value), + name === "income" ? "Gelir" : "Gider", + ]} + /> + + + + + + + ); +} diff --git a/src/app/(dashboard)/dashboard/components/metrics.tsx b/src/app/(dashboard)/dashboard/components/metrics.tsx new file mode 100644 index 0000000..2315a4e --- /dev/null +++ b/src/app/(dashboard)/dashboard/components/metrics.tsx @@ -0,0 +1,112 @@ +import { + AlertCircle, + ArrowDownRight, + ArrowUpRight, + CheckSquare, + Receipt, + Users, + Wallet, +} from "lucide-react"; + +import { Card, CardContent } from "@/components/ui/card"; +import { formatTRY } from "@/lib/format"; +import { cn } from "@/lib/utils"; + +import type { DashboardData } from "@/lib/appwrite/dashboard-queries"; + +function delta(current: number, previous: number): { pct: number; positive: boolean } | null { + if (previous === 0) { + if (current === 0) return null; + return { pct: 100, positive: true }; + } + const pct = ((current - previous) / previous) * 100; + return { pct: Math.abs(pct), positive: pct >= 0 }; +} + +export function Metrics({ data }: { data: DashboardData["metrics"] }) { + const incomeDelta = delta(data.monthIncome, data.prevMonthIncome); + + const cards = [ + { + label: "Müşteriler", + value: String(data.totalCustomers), + sub: `${data.activeCustomers} aktif`, + icon: Users, + tone: "default", + }, + { + label: "Bu ayki gelir", + value: formatTRY(data.monthIncome), + sub: incomeDelta + ? `${incomeDelta.positive ? "+" : "−"}${incomeDelta.pct.toFixed(1)}% önceki ay` + : "Geçen ay veri yok", + icon: Wallet, + tone: "income", + trend: incomeDelta, + }, + { + label: "Bekleyen tahsilat", + value: formatTRY(data.outstanding), + sub: + data.overdueCount > 0 + ? `${data.overdueCount} vadesi geçmiş` + : "Vadesi geçmiş yok", + icon: Receipt, + tone: data.overdueCount > 0 ? "warning" : "default", + }, + { + label: "Açık görevler", + value: String(data.openTasks), + sub: + data.urgentTasks > 0 ? `${data.urgentTasks} acil` : "Acil görev yok", + icon: CheckSquare, + tone: data.urgentTasks > 0 ? "warning" : "default", + }, + ]; + + const toneClass: Record = { + default: "text-muted-foreground", + income: "text-emerald-600 dark:text-emerald-400", + warning: "text-amber-600 dark:text-amber-400", + }; + + return ( +
+ {cards.map((c) => { + const Icon = c.icon; + return ( + + +
+

+ {c.label} +

+

{c.value}

+

0 + ? "text-amber-600 dark:text-amber-400" + : "text-muted-foreground", + )} + > + {c.trend && + (c.trend.positive ? ( + + ) : ( + + ))} + {c.tone === "warning" && data.overdueCount + data.urgentTasks > 0 && ( + + )} + {c.sub} +

+
+ +
+
+ ); + })} +
+ ); +} diff --git a/src/app/(dashboard)/dashboard/components/quick-actions.tsx b/src/app/(dashboard)/dashboard/components/quick-actions.tsx new file mode 100644 index 0000000..712556e --- /dev/null +++ b/src/app/(dashboard)/dashboard/components/quick-actions.tsx @@ -0,0 +1,35 @@ +import Link from "next/link"; +import { Calendar, FilePlus, Receipt, UserPlus } from "lucide-react"; + +import { Button } from "@/components/ui/button"; + +export function QuickActions() { + return ( +
+ + + + +
+ ); +} diff --git a/src/app/(dashboard)/dashboard/components/recent-transactions.tsx b/src/app/(dashboard)/dashboard/components/recent-transactions.tsx new file mode 100644 index 0000000..f8f7525 --- /dev/null +++ b/src/app/(dashboard)/dashboard/components/recent-transactions.tsx @@ -0,0 +1,82 @@ +import Link from "next/link"; +import { ArrowRight, Receipt } from "lucide-react"; + +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { formatDate, formatTRY } from "@/lib/format"; +import { cn } from "@/lib/utils"; + +import type { DashboardData } from "@/lib/appwrite/dashboard-queries"; + +const TYPE_LABEL: Record = { + income: "Gelir", + expense: "Gider", + debt: "Borç", + receivable: "Alacak", +}; + +const TYPE_COLOR: Record = { + income: "text-emerald-600 dark:text-emerald-400", + expense: "text-red-600 dark:text-red-400", + debt: "text-amber-600 dark:text-amber-400", + receivable: "text-blue-600 dark:text-blue-400", +}; + +export function RecentTransactions({ + data, +}: { + data: DashboardData["recentTransactions"]; +}) { + return ( + + +
+ Son işlemler + En son finans hareketleri +
+ +
+ + {data.length === 0 ? ( +
+ +

Henüz finans hareketi yok.

+
+ ) : ( +
    + {data.map((t) => { + const sign = + t.type === "income" || t.type === "receivable" ? "+" : "−"; + return ( +
  • +
    +
    + + {TYPE_LABEL[t.type]} + + + {formatDate(t.date)} + +
    +

    + {t.customerName ? `${t.customerName} — ` : ""} + {t.description || "—"} +

    +
    + + {sign} {formatTRY(t.amount)} + +
  • + ); + })} +
+ )} +
+
+ ); +} diff --git a/src/app/(dashboard)/dashboard/components/top-customers.tsx b/src/app/(dashboard)/dashboard/components/top-customers.tsx new file mode 100644 index 0000000..d7e0d31 --- /dev/null +++ b/src/app/(dashboard)/dashboard/components/top-customers.tsx @@ -0,0 +1,63 @@ +import { Crown, TrendingUp } from "lucide-react"; + +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { formatTRY } from "@/lib/format"; +import { cn } from "@/lib/utils"; + +type Item = { name: string; total: number }; + +export function TopCustomers({ data }: { data: Item[] }) { + const max = data[0]?.total ?? 1; + + return ( + + + + + En çok ciro yapan müşteriler + + Ödenmiş faturaların toplam tutarına göre + + + {data.length === 0 ? ( +
+ +

Henüz ödenmiş fatura yok.

+
+ ) : ( +
    + {data.map((c, i) => { + const width = (c.total / max) * 100; + return ( +
  • +
    + + + {String(i + 1).padStart(2, "0")} + + {c.name} + + {formatTRY(c.total)} +
    +
    +
    +
    +
  • + ); + })} +
+ )} +
+
+ ); +} diff --git a/src/app/(dashboard)/dashboard/page.tsx b/src/app/(dashboard)/dashboard/page.tsx index 62f5a5d..9195007 100644 --- a/src/app/(dashboard)/dashboard/page.tsx +++ b/src/app/(dashboard)/dashboard/page.tsx @@ -1,18 +1,21 @@ import { redirect } from "next/navigation"; import { getActiveContext } from "@/lib/appwrite/active-context"; -import { CustomerInsights } from "../dashboard-2/components/customer-insights"; -import { MetricsOverview } from "../dashboard-2/components/metrics-overview"; -import { QuickActions } from "../dashboard-2/components/quick-actions"; -import { RecentTransactions } from "../dashboard-2/components/recent-transactions"; -import { RevenueBreakdown } from "../dashboard-2/components/revenue-breakdown"; -import { SalesChart } from "../dashboard-2/components/sales-chart"; -import { TopProducts } from "../dashboard-2/components/top-products"; +import { getDashboardData } from "@/lib/appwrite/dashboard-queries"; + +import { CustomerGrowth } from "./components/customer-growth"; +import { IncomeChart } from "./components/income-chart"; +import { Metrics } from "./components/metrics"; +import { QuickActions } from "./components/quick-actions"; +import { RecentTransactions } from "./components/recent-transactions"; +import { TopCustomers } from "./components/top-customers"; export default async function DashboardPage() { const ctx = await getActiveContext(); if (!ctx) redirect("/onboarding"); + const data = await getDashboardData(ctx.tenantId); + const firstName = ctx.user.name?.split(" ")[0] ?? ""; const companyName = ctx.settings?.companyName ?? "Çalışma alanı"; @@ -32,19 +35,17 @@ export default async function DashboardPage() {
- +
- - + +
- - + +
- -
); diff --git a/src/components/command-search.tsx b/src/components/command-search.tsx index b920053..c4c7ceb 100644 --- a/src/components/command-search.tsx +++ b/src/components/command-search.tsx @@ -5,7 +5,6 @@ import { useRouter } from "next/navigation" import { Command as CommandPrimitive } from "cmdk" import { Search, - LayoutPanelLeft, LayoutDashboard, Mail, CheckSquare, @@ -17,6 +16,7 @@ import { HelpCircle, CreditCard, User, + Users, Bell, Link2, Palette, @@ -127,11 +127,23 @@ export function CommandSearch({ open, onOpenChange }: CommandSearchProps) { const commandRef = React.useRef(null) const searchItems: SearchItem[] = [ - // Dashboards - { title: "Dashboard 1", url: "/dashboard", group: "Dashboards", icon: LayoutDashboard }, - { title: "Dashboard 2", url: "/dashboard-2", group: "Dashboards", icon: LayoutPanelLeft }, + // Genel + { title: "Genel bakış", url: "/dashboard", group: "Genel", icon: LayoutDashboard }, - // Apps + // İşletme + { title: "Müşteriler", url: "/customers", group: "İşletme", icon: Users }, + { title: "Hizmetler", url: "/services", group: "İşletme", icon: Mail }, + { title: "Yazılımlarımız", url: "/software", group: "İşletme", icon: Mail }, + + // Operasyon + { title: "Takvim", url: "/calendar", group: "Operasyon", icon: Calendar }, + { title: "Görevler", url: "/tasks", group: "Operasyon", icon: CheckSquare }, + + // Finans + { title: "Gelir / Gider", url: "/finance", group: "Finans", icon: Mail }, + { title: "Faturalar", url: "/invoices", group: "Finans", icon: Mail }, + + // Apps (legacy template) { title: "Mail", url: "/mail", group: "Apps", icon: Mail }, { title: "Tasks", url: "/tasks", group: "Apps", icon: CheckSquare }, { title: "Chat", url: "/chat", group: "Apps", icon: MessageCircle }, diff --git a/src/lib/appwrite/dashboard-queries.ts b/src/lib/appwrite/dashboard-queries.ts new file mode 100644 index 0000000..832f70e --- /dev/null +++ b/src/lib/appwrite/dashboard-queries.ts @@ -0,0 +1,225 @@ +import "server-only"; + +import { Query } from "node-appwrite"; + +import { createAdminClient } from "./server"; +import { + DATABASE_ID, + TABLES, + type Customer, + type FinanceEntry, + type Invoice, + type Task, +} from "./schema"; + +export type DashboardData = { + metrics: { + totalCustomers: number; + activeCustomers: number; + monthIncome: number; // current month income + prevMonthIncome: number; // previous month income (for delta) + outstanding: number; // unpaid invoices total (draft+sent+overdue) + overdueCount: number; + openTasks: number; + urgentTasks: number; + }; + monthlyIncome: { month: string; income: number; expense: number }[]; // last 12 months + topCustomers: { name: string; total: number }[]; // top 5 by paid invoice total + recentTransactions: { + id: string; + type: FinanceEntry["type"]; + amount: number; + date: string; + customerName: string; + description: string; + }[]; + topServices: { name: string; total: number; count: number }[]; // top 5 services by revenue (qty*unitPrice) + newCustomersMonthly: { month: string; count: number }[]; // last 6 months +}; + +const MONTH_SHORT = ["Oca", "Şub", "Mar", "Nis", "May", "Haz", "Tem", "Ağu", "Eyl", "Eki", "Kas", "Ara"]; + +function monthKey(d: Date): string { + return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`; +} + +function monthLabel(d: Date): string { + return MONTH_SHORT[d.getMonth()]; +} + +export async function getDashboardData(tenantId: string): Promise { + const { tablesDB } = createAdminClient(); + + const [customers, invoices, financeEntries, tasks, services] = await Promise.all([ + tablesDB + .listRows({ + databaseId: DATABASE_ID, + tableId: TABLES.customers, + queries: [Query.equal("tenantId", tenantId), Query.limit(2000)], + }) + .catch(() => ({ rows: [] as unknown[] })), + tablesDB + .listRows({ + databaseId: DATABASE_ID, + tableId: TABLES.invoices, + queries: [Query.equal("tenantId", tenantId), Query.limit(2000)], + }) + .catch(() => ({ rows: [] as unknown[] })), + tablesDB + .listRows({ + databaseId: DATABASE_ID, + tableId: TABLES.financeEntries, + queries: [ + Query.equal("tenantId", tenantId), + Query.orderDesc("date"), + Query.limit(2000), + ], + }) + .catch(() => ({ rows: [] as unknown[] })), + tablesDB + .listRows({ + databaseId: DATABASE_ID, + tableId: TABLES.tasks, + queries: [Query.equal("tenantId", tenantId), Query.limit(2000)], + }) + .catch(() => ({ rows: [] as unknown[] })), + tablesDB + .listRows({ + databaseId: DATABASE_ID, + tableId: TABLES.services, + queries: [Query.equal("tenantId", tenantId), Query.limit(2000)], + }) + .catch(() => ({ rows: [] as unknown[] })), + ]); + + const customerList = customers.rows as unknown as Customer[]; + const invoiceList = invoices.rows as unknown as Invoice[]; + const entryList = financeEntries.rows as unknown as FinanceEntry[]; + const taskList = tasks.rows as unknown as Task[]; + + const customerMap = new Map(customerList.map((c) => [c.$id, c.name])); + + // ---------------- Metrics ---------------- + const now = new Date(); + const thisMonth = monthKey(now); + const prev = new Date(now.getFullYear(), now.getMonth() - 1, 1); + const prevMonth = monthKey(prev); + + let monthIncome = 0; + let prevMonthIncome = 0; + for (const e of entryList) { + if (e.type !== "income") continue; + const k = monthKey(new Date(e.date)); + if (k === thisMonth) monthIncome += e.amount; + else if (k === prevMonth) prevMonthIncome += e.amount; + } + + let outstanding = 0; + let overdueCount = 0; + const today = new Date(); + for (const inv of invoiceList) { + const status = inv.status ?? "draft"; + if (status === "paid" || status === "cancelled") continue; + outstanding += inv.total ?? 0; + if (status === "overdue" || (inv.dueDate && new Date(inv.dueDate) < today)) { + overdueCount += 1; + } + } + + let openTasks = 0; + let urgentTasks = 0; + for (const t of taskList) { + if ((t.status ?? "todo") !== "done") { + openTasks += 1; + if ((t.priority ?? "medium") === "urgent") urgentTasks += 1; + } + } + + const activeCustomers = customerList.filter((c) => (c.status ?? "active") === "active").length; + + // ---------------- Monthly income/expense (last 12 months) ---------------- + const monthSeries: DashboardData["monthlyIncome"] = []; + for (let i = 11; i >= 0; i--) { + const d = new Date(now.getFullYear(), now.getMonth() - i, 1); + monthSeries.push({ month: monthLabel(d), income: 0, expense: 0 }); + } + for (const e of entryList) { + const ed = new Date(e.date); + const monthsAgo = + (now.getFullYear() - ed.getFullYear()) * 12 + (now.getMonth() - ed.getMonth()); + if (monthsAgo < 0 || monthsAgo > 11) continue; + const idx = 11 - monthsAgo; + if (e.type === "income") monthSeries[idx].income += e.amount; + else if (e.type === "expense") monthSeries[idx].expense += e.amount; + } + + // ---------------- Top customers (by paid invoice total) ---------------- + const customerRevenue = new Map(); + for (const inv of invoiceList) { + if (inv.status !== "paid") continue; + customerRevenue.set( + inv.customerId, + (customerRevenue.get(inv.customerId) ?? 0) + (inv.total ?? 0), + ); + } + const topCustomers = Array.from(customerRevenue.entries()) + .map(([id, total]) => ({ name: customerMap.get(id) ?? "—", total })) + .sort((a, b) => b.total - a.total) + .slice(0, 5); + + // ---------------- Recent transactions (last 8) ---------------- + const recentTransactions = entryList.slice(0, 8).map((e) => ({ + id: e.$id, + type: e.type, + amount: e.amount, + date: e.date, + customerName: e.customerId ? customerMap.get(e.customerId) ?? "" : "", + description: e.description ?? "", + })); + + // ---------------- Top services (by current MRR estimate) ---------------- + const svcMap = new Map(); + for (const s of services.rows as unknown as { name: string; unitPrice?: number }[]) { + const key = s.name; + const entry = svcMap.get(key) ?? { name: s.name, total: 0, count: 0 }; + entry.total += s.unitPrice ?? 0; + entry.count += 1; + svcMap.set(key, entry); + } + const topServices = Array.from(svcMap.values()) + .sort((a, b) => b.total - a.total) + .slice(0, 5); + + // ---------------- New customers per month (last 6) ---------------- + const newCustomersMonthly: DashboardData["newCustomersMonthly"] = []; + for (let i = 5; i >= 0; i--) { + const d = new Date(now.getFullYear(), now.getMonth() - i, 1); + newCustomersMonthly.push({ month: monthLabel(d), count: 0 }); + } + for (const c of customerList) { + const cd = new Date(c.$createdAt); + const monthsAgo = + (now.getFullYear() - cd.getFullYear()) * 12 + (now.getMonth() - cd.getMonth()); + if (monthsAgo < 0 || monthsAgo > 5) continue; + const idx = 5 - monthsAgo; + newCustomersMonthly[idx].count += 1; + } + + return { + metrics: { + totalCustomers: customerList.length, + activeCustomers, + monthIncome, + prevMonthIncome, + outstanding, + overdueCount, + openTasks, + urgentTasks, + }, + monthlyIncome: monthSeries, + topCustomers, + recentTransactions, + topServices, + newCustomersMonthly, + }; +}