I fixed the filter for all the leads
This commit is contained in:
+1
-1
@@ -3,7 +3,7 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev -p 3006",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "eslint"
|
"lint": "eslint"
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useState } from "react"
|
import { useState, useEffect } from "react"
|
||||||
import { PageHeader } from "@/components/shared/page-header"
|
import { PageHeader } from "@/components/shared/page-header"
|
||||||
import { StatCard } from "@/components/dashboard/stat-card"
|
import { StatCard } from "@/components/dashboard/stat-card"
|
||||||
|
import { StatCardSkeleton } from "@/components/dashboard/stat-card-skeleton"
|
||||||
import { RecentLeadsTable } from "@/components/dashboard/recent-leads-table"
|
import { RecentLeadsTable } from "@/components/dashboard/recent-leads-table"
|
||||||
import { LeadStatusChart } from "@/components/dashboard/lead-status-chart"
|
import { LeadStatusChart } from "@/components/dashboard/lead-status-chart"
|
||||||
import { LeadsPerMonthChart } from "@/components/dashboard/leads-per-month-chart"
|
import { LeadsPerMonthChart } from "@/components/dashboard/leads-per-month-chart"
|
||||||
import { dashboardStats } from "@/data/dashboard"
|
import { getDashboardStats } from "@/data/dashboard"
|
||||||
import {
|
import {
|
||||||
Users,
|
Users,
|
||||||
UserPlus,
|
UserPlus,
|
||||||
@@ -23,19 +24,26 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select"
|
} from "@/components/ui/select"
|
||||||
|
import { DashboardStats } from "@/types"
|
||||||
|
|
||||||
export default function DashboardPage() {
|
export default function DashboardPage() {
|
||||||
const stats = dashboardStats
|
|
||||||
const [period, setPeriod] = useState("6months")
|
const [period, setPeriod] = useState("6months")
|
||||||
|
const [stats, setStats] = useState<DashboardStats | null>(null)
|
||||||
|
|
||||||
const statCards = [
|
useEffect(() => {
|
||||||
{ title: "Total Leads", value: stats.totalLeads, icon: Users, variant: "blue" as const, description: "All time" },
|
setStats(getDashboardStats(period))
|
||||||
|
}, [period])
|
||||||
|
|
||||||
|
const statCards = stats
|
||||||
|
? [
|
||||||
|
{ title: "Total Leads", value: stats.totalLeads, icon: Users, variant: "blue" as const, description: stats.periodLabel },
|
||||||
{ title: "Open Leads", value: stats.openLeads, icon: UserPlus, variant: "blue" as const, description: "Needs attention" },
|
{ title: "Open Leads", value: stats.openLeads, icon: UserPlus, variant: "blue" as const, description: "Needs attention" },
|
||||||
{ title: "Contacted", value: stats.contactedLeads, icon: PhoneCall, variant: "amber" as const, description: "In conversation" },
|
{ title: "Contacted", value: stats.contactedLeads, icon: PhoneCall, variant: "amber" as const, description: "In conversation" },
|
||||||
{ title: "Pending", value: stats.pendingLeads, icon: Clock, variant: "purple" as const, description: "Awaiting decision" },
|
{ title: "Pending", value: stats.pendingLeads, icon: Clock, variant: "purple" as const, description: "Awaiting decision" },
|
||||||
{ title: "Closed", value: stats.closedLeads, icon: CheckCircle2, variant: "emerald" as const, description: "Won" },
|
{ title: "Closed", value: stats.closedLeads, icon: CheckCircle2, variant: "emerald" as const, description: "Won" },
|
||||||
{ title: "Conversion Rate", value: `${stats.conversionRate}%`, icon: TrendingUp, variant: "emerald" as const, description: `${stats.closedLeads} of ${stats.totalLeads} leads` },
|
{ title: "Conversion Rate", value: `${stats.conversionRate}%`, icon: TrendingUp, variant: "emerald" as const, description: `${stats.closedLeads} of ${stats.totalLeads} leads` },
|
||||||
]
|
]
|
||||||
|
: []
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
@@ -55,17 +63,20 @@ export default function DashboardPage() {
|
|||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|
||||||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6">
|
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6">
|
||||||
{statCards.map((card, i) => (
|
{stats
|
||||||
|
? statCards.map((card, i) => (
|
||||||
<StatCard key={card.title} {...card} index={i} />
|
<StatCard key={card.title} {...card} index={i} />
|
||||||
))}
|
))
|
||||||
|
: Array.from({ length: 6 }).map((_, i) => <StatCardSkeleton key={i} />)
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid gap-6 lg:grid-cols-2">
|
<div className="grid gap-6 lg:grid-cols-2">
|
||||||
<LeadStatusChart data={stats.statusDistribution} />
|
<LeadStatusChart data={stats?.statusDistribution ?? []} />
|
||||||
<LeadsPerMonthChart data={stats.leadsPerMonth} />
|
<LeadsPerMonthChart data={stats?.leadsPerMonth ?? []} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<RecentLeadsTable leads={stats.recentLeads} />
|
<RecentLeadsTable leads={stats?.recentLeads ?? []} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,15 +6,21 @@ import { LeadsTable } from "@/components/leads/leads-table"
|
|||||||
import { LeadsTableToolbar } from "@/components/leads/leads-table-toolbar"
|
import { LeadsTableToolbar } from "@/components/leads/leads-table-toolbar"
|
||||||
import { LeadFormDialog } from "@/components/leads/lead-form-dialog"
|
import { LeadFormDialog } from "@/components/leads/lead-form-dialog"
|
||||||
import { leads } from "@/data/leads"
|
import { leads } from "@/data/leads"
|
||||||
|
import { filterLeadsByPeriod } from "@/lib/date-utils"
|
||||||
|
|
||||||
export default function LeadsPage() {
|
export default function LeadsPage() {
|
||||||
const [search, setSearch] = useState("")
|
const [search, setSearch] = useState("")
|
||||||
const [statusFilter, setStatusFilter] = useState("all")
|
const [statusFilter, setStatusFilter] = useState("all")
|
||||||
|
const [periodFilter, setPeriodFilter] = useState("all")
|
||||||
const [createOpen, setCreateOpen] = useState(false)
|
const [createOpen, setCreateOpen] = useState(false)
|
||||||
|
|
||||||
const filteredLeads = useMemo(() => {
|
const filteredLeads = useMemo(() => {
|
||||||
let result = leads
|
let result = leads
|
||||||
|
|
||||||
|
if (periodFilter && periodFilter !== "all") {
|
||||||
|
result = filterLeadsByPeriod(result, periodFilter)
|
||||||
|
}
|
||||||
|
|
||||||
if (search) {
|
if (search) {
|
||||||
const q = search.toLowerCase()
|
const q = search.toLowerCase()
|
||||||
result = result.filter(
|
result = result.filter(
|
||||||
@@ -31,7 +37,7 @@ export default function LeadsPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}, [search, statusFilter])
|
}, [search, statusFilter, periodFilter])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
@@ -47,6 +53,8 @@ export default function LeadsPage() {
|
|||||||
onSearchChange={setSearch}
|
onSearchChange={setSearch}
|
||||||
statusFilter={statusFilter}
|
statusFilter={statusFilter}
|
||||||
onStatusFilterChange={setStatusFilter}
|
onStatusFilterChange={setStatusFilter}
|
||||||
|
periodFilter={periodFilter}
|
||||||
|
onPeriodFilterChange={setPeriodFilter}
|
||||||
onCreateClick={() => setCreateOpen(true)}
|
onCreateClick={() => setCreateOpen(true)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,14 +4,14 @@ import { useState, useMemo } from "react"
|
|||||||
import { motion } from "framer-motion"
|
import { motion } from "framer-motion"
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
|
|
||||||
interface MonthlyData {
|
interface IntervalData {
|
||||||
month: string
|
label: string
|
||||||
leads: number
|
leads: number
|
||||||
closed: number
|
closed: number
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LeadsPerMonthChartProps {
|
interface LeadsPerMonthChartProps {
|
||||||
data: MonthlyData[]
|
data: IntervalData[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const BAR_GRADIENT_TOP = "#3B6AB8"
|
const BAR_GRADIENT_TOP = "#3B6AB8"
|
||||||
@@ -138,7 +138,7 @@ export function LeadsPerMonthChart({ data }: LeadsPerMonthChartProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<g
|
<g
|
||||||
key={d.month}
|
key={d.label}
|
||||||
onMouseEnter={() => setHovered(i)}
|
onMouseEnter={() => setHovered(i)}
|
||||||
onMouseLeave={() => setHovered(null)}
|
onMouseLeave={() => setHovered(null)}
|
||||||
style={{ cursor: "pointer" }}
|
style={{ cursor: "pointer" }}
|
||||||
@@ -188,7 +188,7 @@ export function LeadsPerMonthChart({ data }: LeadsPerMonthChartProps) {
|
|||||||
fontWeight={isActive ? 600 : 400}
|
fontWeight={isActive ? 600 : 400}
|
||||||
textAnchor="middle"
|
textAnchor="middle"
|
||||||
>
|
>
|
||||||
{d.month}
|
{d.label}
|
||||||
</text>
|
</text>
|
||||||
</g>
|
</g>
|
||||||
)
|
)
|
||||||
@@ -216,7 +216,7 @@ export function LeadsPerMonthChart({ data }: LeadsPerMonthChartProps) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="mb-1.5 text-xs font-semibold" style={{ color: "hsl(var(--foreground))" }}>
|
<div className="mb-1.5 text-xs font-semibold" style={{ color: "hsl(var(--foreground))" }}>
|
||||||
{activeDatum.month}
|
{activeDatum.label}
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-1 flex items-center justify-between gap-4 text-xs" style={{ color: "hsl(var(--muted-foreground))" }}>
|
<div className="mb-1 flex items-center justify-between gap-4 text-xs" style={{ color: "hsl(var(--muted-foreground))" }}>
|
||||||
<span className="flex items-center gap-1.5">
|
<span className="flex items-center gap-1.5">
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useState } from "react"
|
|
||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import {
|
import {
|
||||||
@@ -10,14 +9,15 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select"
|
} from "@/components/ui/select"
|
||||||
import { Search, Plus, SlidersHorizontal } from "lucide-react"
|
import { Search, Plus } from "lucide-react"
|
||||||
import { LeadStatus } from "@/types"
|
|
||||||
|
|
||||||
interface LeadsTableToolbarProps {
|
interface LeadsTableToolbarProps {
|
||||||
search: string
|
search: string
|
||||||
onSearchChange: (value: string) => void
|
onSearchChange: (value: string) => void
|
||||||
statusFilter: string
|
statusFilter: string
|
||||||
onStatusFilterChange: (value: string) => void
|
onStatusFilterChange: (value: string) => void
|
||||||
|
periodFilter: string
|
||||||
|
onPeriodFilterChange: (value: string) => void
|
||||||
onCreateClick: () => void
|
onCreateClick: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,6 +26,8 @@ export function LeadsTableToolbar({
|
|||||||
onSearchChange,
|
onSearchChange,
|
||||||
statusFilter,
|
statusFilter,
|
||||||
onStatusFilterChange,
|
onStatusFilterChange,
|
||||||
|
periodFilter,
|
||||||
|
onPeriodFilterChange,
|
||||||
onCreateClick,
|
onCreateClick,
|
||||||
}: LeadsTableToolbarProps) {
|
}: LeadsTableToolbarProps) {
|
||||||
return (
|
return (
|
||||||
@@ -40,6 +42,18 @@ export function LeadsTableToolbar({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
|
<Select value={periodFilter} onValueChange={onPeriodFilterChange}>
|
||||||
|
<SelectTrigger className="h-9 w-[150px]">
|
||||||
|
<SelectValue placeholder="All Time" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="all">All Time</SelectItem>
|
||||||
|
<SelectItem value="7days">Last 7 days</SelectItem>
|
||||||
|
<SelectItem value="30days">Last 30 days</SelectItem>
|
||||||
|
<SelectItem value="6months">Last 6 months</SelectItem>
|
||||||
|
<SelectItem value="12months">Last 12 months</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
<Select value={statusFilter} onValueChange={onStatusFilterChange}>
|
<Select value={statusFilter} onValueChange={onStatusFilterChange}>
|
||||||
<SelectTrigger className="h-9 w-[140px]">
|
<SelectTrigger className="h-9 w-[140px]">
|
||||||
<SelectValue placeholder="All Statuses" />
|
<SelectValue placeholder="All Statuses" />
|
||||||
@@ -53,10 +67,6 @@ export function LeadsTableToolbar({
|
|||||||
<SelectItem value="ignored">Ignored</SelectItem>
|
<SelectItem value="ignored">Ignored</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<Button size="sm" variant="outline" className="h-9 gap-2">
|
|
||||||
<SlidersHorizontal className="h-4 w-4" />
|
|
||||||
<span className="hidden sm:inline">Filters</span>
|
|
||||||
</Button>
|
|
||||||
<Button size="sm" className="h-9 gap-2" onClick={onCreateClick}>
|
<Button size="sm" className="h-9 gap-2" onClick={onCreateClick}>
|
||||||
<Plus className="h-4 w-4" />
|
<Plus className="h-4 w-4" />
|
||||||
<span className="hidden sm:inline">Create Lead</span>
|
<span className="hidden sm:inline">Create Lead</span>
|
||||||
|
|||||||
+35
-33
@@ -1,29 +1,31 @@
|
|||||||
import { DashboardStats } from "@/types"
|
import { DashboardStats } from "@/types"
|
||||||
import { leads } from "./leads"
|
import { leads } from "./leads"
|
||||||
|
import { filterLeadsByPeriod, groupLeadsByInterval } from "@/lib/date-utils"
|
||||||
|
|
||||||
function getLeadsPerMonth() {
|
const periodLabels: Record<string, string> = {
|
||||||
const months: { month: string; leads: number; closed: number }[] = []
|
"7days": "Last 7 days",
|
||||||
const now = new Date()
|
"30days": "Last 30 days",
|
||||||
|
"6months": "Last 6 months",
|
||||||
for (let i = 5; i >= 0; i--) {
|
"12months": "Last 12 months",
|
||||||
const d = new Date(now.getFullYear(), now.getMonth() - i, 1)
|
|
||||||
const monthStr = d.toLocaleDateString("en-US", { month: "short", year: "2-digit" })
|
|
||||||
const yearMonth = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`
|
|
||||||
|
|
||||||
const leadsCount = leads.filter((l) => l.createdAt.startsWith(yearMonth)).length
|
|
||||||
const closedCount = leads.filter(
|
|
||||||
(l) => l.status === "closed" && l.updatedAt.startsWith(yearMonth)
|
|
||||||
).length
|
|
||||||
|
|
||||||
months.push({ month: monthStr, leads: leadsCount, closed: closedCount })
|
|
||||||
}
|
|
||||||
|
|
||||||
return months
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStatusDistribution() {
|
export function getDashboardStats(period: string): DashboardStats {
|
||||||
|
const filtered = filterLeadsByPeriod(leads, period)
|
||||||
|
const label = periodLabels[period] ?? "Selected period"
|
||||||
|
|
||||||
|
const totalLeads = filtered.length
|
||||||
|
const openLeads = filtered.filter((l) => l.status === "open").length
|
||||||
|
const contactedLeads = filtered.filter((l) => l.status === "contacted").length
|
||||||
|
const pendingLeads = filtered.filter((l) => l.status === "pending").length
|
||||||
|
const closedLeads = filtered.filter((l) => l.status === "closed").length
|
||||||
|
const ignoredLeads = filtered.filter((l) => l.status === "ignored").length
|
||||||
|
const conversionRate = totalLeads > 0
|
||||||
|
? Math.round((closedLeads / totalLeads) * 100)
|
||||||
|
: 0
|
||||||
|
|
||||||
|
function getStatusDistribution() {
|
||||||
const statusCounts: Record<string, number> = { open: 0, contacted: 0, pending: 0, closed: 0, ignored: 0 }
|
const statusCounts: Record<string, number> = { open: 0, contacted: 0, pending: 0, closed: 0, ignored: 0 }
|
||||||
leads.forEach((l) => {
|
filtered.forEach((l) => {
|
||||||
statusCounts[l.status]++
|
statusCounts[l.status]++
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -34,19 +36,19 @@ function getStatusDistribution() {
|
|||||||
{ name: "Closed", value: statusCounts.closed, color: "#10b981" },
|
{ name: "Closed", value: statusCounts.closed, color: "#10b981" },
|
||||||
{ name: "Ignored", value: statusCounts.ignored, color: "#6B7280" },
|
{ name: "Ignored", value: statusCounts.ignored, color: "#6B7280" },
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const dashboardStats: DashboardStats = {
|
return {
|
||||||
totalLeads: leads.length,
|
totalLeads,
|
||||||
openLeads: leads.filter((l) => l.status === "open").length,
|
openLeads,
|
||||||
contactedLeads: leads.filter((l) => l.status === "contacted").length,
|
contactedLeads,
|
||||||
pendingLeads: leads.filter((l) => l.status === "pending").length,
|
pendingLeads,
|
||||||
closedLeads: leads.filter((l) => l.status === "closed").length,
|
closedLeads,
|
||||||
ignoredLeads: leads.filter((l) => l.status === "ignored").length,
|
ignoredLeads,
|
||||||
conversionRate: Math.round(
|
conversionRate,
|
||||||
(leads.filter((l) => l.status === "closed").length / leads.length) * 100
|
leadsPerMonth: groupLeadsByInterval(filtered, period),
|
||||||
),
|
recentLeads: filtered.slice(0, 10),
|
||||||
leadsPerMonth: getLeadsPerMonth(),
|
|
||||||
recentLeads: leads.slice(0, 10),
|
|
||||||
statusDistribution: getStatusDistribution(),
|
statusDistribution: getStatusDistribution(),
|
||||||
|
periodLabel: label,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import { Lead } from "@/types"
|
||||||
|
|
||||||
|
export function getPeriodDateRange(period: string): { start: Date; end: Date } {
|
||||||
|
const end = new Date()
|
||||||
|
const start = new Date()
|
||||||
|
|
||||||
|
switch (period) {
|
||||||
|
case "7days":
|
||||||
|
start.setDate(start.getDate() - 7)
|
||||||
|
break
|
||||||
|
case "30days":
|
||||||
|
start.setDate(start.getDate() - 30)
|
||||||
|
break
|
||||||
|
case "6months":
|
||||||
|
start.setMonth(start.getMonth() - 6)
|
||||||
|
break
|
||||||
|
case "12months":
|
||||||
|
start.setMonth(start.getMonth() - 12)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
start.setMonth(start.getMonth() - 6)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { start, end }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function filterLeadsByPeriod(leads: Lead[], period: string): Lead[] {
|
||||||
|
const { start, end } = getPeriodDateRange(period)
|
||||||
|
return leads.filter((l) => {
|
||||||
|
const d = new Date(l.createdAt)
|
||||||
|
return d >= start && d <= end
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function groupLeadsByInterval(
|
||||||
|
leads: Lead[],
|
||||||
|
period: string
|
||||||
|
): { label: string; leads: number; closed: number }[] {
|
||||||
|
const { start, end } = getPeriodDateRange(period)
|
||||||
|
|
||||||
|
if (period === "7days" || period === "30days") {
|
||||||
|
return groupByDay(leads, start, end)
|
||||||
|
}
|
||||||
|
return groupByMonth(leads, start, end)
|
||||||
|
}
|
||||||
|
|
||||||
|
function groupByDay(
|
||||||
|
leads: Lead[],
|
||||||
|
start: Date,
|
||||||
|
end: Date
|
||||||
|
): { label: string; leads: number; closed: number }[] {
|
||||||
|
const result: { label: string; leads: number; closed: number }[] = []
|
||||||
|
const current = new Date(start)
|
||||||
|
|
||||||
|
while (current <= end) {
|
||||||
|
const dateStr = current.toISOString().slice(0, 10)
|
||||||
|
const dayLeads = leads.filter((l) => l.createdAt.startsWith(dateStr))
|
||||||
|
const closedCount = dayLeads.filter((l) => l.status === "closed").length
|
||||||
|
|
||||||
|
result.push({
|
||||||
|
label: current.toLocaleDateString("en-US", { month: "short", day: "numeric" }),
|
||||||
|
leads: dayLeads.length,
|
||||||
|
closed: closedCount,
|
||||||
|
})
|
||||||
|
|
||||||
|
current.setDate(current.getDate() + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function groupByMonth(
|
||||||
|
leads: Lead[],
|
||||||
|
start: Date,
|
||||||
|
end: Date
|
||||||
|
): { label: string; leads: number; closed: number }[] {
|
||||||
|
const result: { label: string; leads: number; closed: number }[] = []
|
||||||
|
const current = new Date(start.getFullYear(), start.getMonth(), 1)
|
||||||
|
|
||||||
|
while (current <= end) {
|
||||||
|
const yearMonth = `${current.getFullYear()}-${String(current.getMonth() + 1).padStart(2, "0")}`
|
||||||
|
const monthLeads = leads.filter((l) => l.createdAt.startsWith(yearMonth))
|
||||||
|
const closedCount = monthLeads.filter((l) => l.status === "closed").length
|
||||||
|
|
||||||
|
result.push({
|
||||||
|
label: current.toLocaleDateString("en-US", { month: "short", year: "2-digit" }),
|
||||||
|
leads: monthLeads.length,
|
||||||
|
closed: closedCount,
|
||||||
|
})
|
||||||
|
|
||||||
|
current.setMonth(current.getMonth() + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
+2
-1
@@ -46,9 +46,10 @@ export interface DashboardStats {
|
|||||||
closedLeads: number
|
closedLeads: number
|
||||||
ignoredLeads: number
|
ignoredLeads: number
|
||||||
conversionRate: number
|
conversionRate: number
|
||||||
leadsPerMonth: { month: string; leads: number; closed: number }[]
|
leadsPerMonth: { label: string; leads: number; closed: number }[]
|
||||||
recentLeads: Lead[]
|
recentLeads: Lead[]
|
||||||
statusDistribution: { name: string; value: number; color: string }[]
|
statusDistribution: { name: string; value: number; color: string }[]
|
||||||
|
periodLabel: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ColumnFilter {
|
export interface ColumnFilter {
|
||||||
|
|||||||
Reference in New Issue
Block a user