Initial commit

This commit is contained in:
2026-06-17 13:51:22 +02:00
commit 4898bf7142
81 changed files with 12522 additions and 0 deletions
+52
View File
@@ -0,0 +1,52 @@
import { DashboardStats } from "@/types"
import { leads } from "./leads"
function getLeadsPerMonth() {
const months: { month: string; leads: number; closed: number }[] = []
const now = new Date()
for (let i = 5; i >= 0; i--) {
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() {
const statusCounts: Record<string, number> = { open: 0, contacted: 0, pending: 0, closed: 0, ignored: 0 }
leads.forEach((l) => {
statusCounts[l.status]++
})
return [
{ name: "Open", value: statusCounts.open, color: "#3b82f6" },
{ name: "Contacted", value: statusCounts.contacted, color: "#f59e0b" },
{ name: "Pending", value: statusCounts.pending, color: "#8b5cf6" },
{ name: "Closed", value: statusCounts.closed, color: "#10b981" },
{ name: "Ignored", value: statusCounts.ignored, color: "#71717a" },
]
}
export const dashboardStats: DashboardStats = {
totalLeads: leads.length,
openLeads: leads.filter((l) => l.status === "open").length,
contactedLeads: leads.filter((l) => l.status === "contacted").length,
pendingLeads: leads.filter((l) => l.status === "pending").length,
closedLeads: leads.filter((l) => l.status === "closed").length,
ignoredLeads: leads.filter((l) => l.status === "ignored").length,
conversionRate: Math.round(
(leads.filter((l) => l.status === "closed").length / leads.length) * 100
),
leadsPerMonth: getLeadsPerMonth(),
recentLeads: leads.slice(0, 10),
statusDistribution: getStatusDistribution(),
}
+88
View File
@@ -0,0 +1,88 @@
import { Lead, LeadStatus } from "@/types"
import { users } from "./users"
const companyNames = [
"Brightwave Studios", "Nexus Digital", "Pinnacle Web Solutions", "Vertex Media Group",
"Apex Interactive", "Meridian Design Co", "Titan Software", "Crafted Web Agency",
"Driftwood Creative", "Summit Digital Labs", "Horizon Interactive", "Pulse Marketing",
"Ironclad Systems", "Northstar Digital", "Vanguard Web Services", "Cascade Solutions",
"Atlas Creative Group", "Precision Web Co", "Momentum Digital", "Catalyst Agency",
"Ridgewood Tech", "Silverline Interactive", "Harbor Media Group", "Providence Web",
"Sentinel Systems", "Legacy Digital Agency", "Frontier Creative", "Summit Point Tech",
"Barrel House Digital", "Anchor Web Services", "Trinity Media", "Skyline Interactive",
"Cornerstone Digital", "Horizon Web Craft", "Noble Systems", "Crescent Creative",
"Blue Ridge Digital", "Pathfinder Web Co", "Ravenswood Agency", "Sterling Media Group",
"Cedar Creek Interactive", "Iron Gate Digital", "Fox Run Agency", "Oakwood Systems",
"Clover Creative", "Birchwood Digital", "Stone Ridge Media", "Riverbend Tech",
"Fairway Web Services", "Heritage Digital Group", "Maple Leaf Interactive", "Granite Systems",
"Portside Creative", "Westwind Digital", "Crestview Agency", "Sunburst Media",
"Broadwood Systems", "Highland Interactive", "Clearwater Web Co", "Emerald Digital",
]
const firstNames = ["James", "Mary", "Robert", "Patricia", "John", "Jennifer", "Michael", "Linda", "David", "Elizabeth", "William", "Barbara", "Richard", "Susan", "Joseph", "Jessica", "Thomas", "Sarah", "Christopher", "Karen", "Daniel", "Lisa", "Matthew", "Nancy", "Anthony", "Betty", "Mark", "Margaret", "Donald", "Sandra", "Steven", "Ashley", "Andrew", "Kimberly", "Paul", "Emily", "Joshua", "Donna", "Kenneth", "Michelle", "Kevin", "Carol", "Brian", "Amanda", "George", "Melissa", "Timothy", "Deborah", "Ronald", "Stephanie"]
const lastNames = ["Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis", "Rodriguez", "Martinez", "Hernandez", "Lopez", "Gonzalez", "Wilson", "Anderson", "Thomas", "Taylor", "Moore", "Jackson", "Martin", "Lee", "Perez", "Thompson", "White", "Harris", "Sanchez", "Clark", "Ramirez", "Lewis", "Robinson", "Walker", "Young", "Allen", "King", "Wright", "Scott", "Torres", "Nguyen", "Hill", "Flores", "Green", "Adams", "Nelson", "Baker", "Hall", "Rivera", "Campbell", "Mitchell", "Carter", "Roberts"]
const sources = ["Website", "Referral", "LinkedIn", "Cold Call", "Email", "Google", "Social Media", "Other"]
const statuses: LeadStatus[] = ["open", "contacted", "pending", "closed", "ignored"]
function randomItem<T>(arr: T[]): T {
return arr[Math.floor(Math.random() * arr.length)]
}
function randomDate(startMonthsAgo: number): string {
const now = new Date()
const start = new Date(now.getFullYear(), now.getMonth() - startMonthsAgo, 1)
const randomTime = start.getTime() + Math.random() * (now.getTime() - start.getTime())
return new Date(randomTime).toISOString()
}
const phonePrefixes = ["555", "123", "456", "789", "321", "654", "987", "234", "876", "432"]
function randomPhone(): string {
const prefix = randomItem(phonePrefixes)
const suffix = Math.floor(Math.random() * 10000000).toString().padStart(7, "0")
return `(${prefix}) ${suffix.slice(0, 3)}-${suffix.slice(3)}`
}
export const leads: Lead[] = Array.from({ length: 120 }, (_, i) => {
const firstName = randomItem(firstNames)
const lastName = randomItem(lastNames)
const status = randomItem(statuses)
const assignedUser = Math.random() > 0.15 ? randomItem(users.filter((u) => u.active)) : null
const createdAt = randomDate(6)
return {
id: `lead-${String(i + 1).padStart(3, "0")}`,
companyName: companyNames[i % companyNames.length],
contactName: `${firstName} ${lastName}`,
email: `${firstName.toLowerCase()}.${lastName.toLowerCase()}@${companyNames[i % companyNames.length].toLowerCase().replace(/\s+/g, "")}.com`,
phone: randomPhone(),
source: randomItem(sources),
description: `Looking for a complete website redesign and modern digital presence. Interested in responsive design, SEO optimization, and a custom CMS solution.`,
status,
assignedUserId: assignedUser?.id ?? null,
assignedUser,
createdAt,
updatedAt: createdAt,
}
}).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
export function getLeadById(id: string): Lead | undefined {
return leads.find((l) => l.id === id)
}
export function getLeadsByStatus(status: LeadStatus): Lead[] {
return leads.filter((l) => l.status === status)
}
export function searchLeads(query: string): Lead[] {
const q = query.toLowerCase()
return leads.filter(
(l) =>
l.companyName.toLowerCase().includes(q) ||
l.contactName.toLowerCase().includes(q) ||
l.email.toLowerCase().includes(q) ||
l.phone.includes(q)
)
}
+68
View File
@@ -0,0 +1,68 @@
import { Note } from "@/types"
import { leads } from "./leads"
import { users } from "./users"
const noteTemplates = [
"Called the prospect to discuss their requirements. They are looking for a complete website overhaul with e-commerce capabilities.",
"Sent over the proposal and pricing breakdown. Waiting for their feedback on the package options.",
"Followed up via email. They mentioned they're comparing a few agencies but liked our portfolio.",
"Had a discovery call. They need the project completed within 8 weeks. Budget seems flexible.",
"Sent a customized demo of our recent e-commerce work. They were particularly impressed with the checkout flow.",
"Received their requirements document. Scope includes 12 pages, blog integration, and a contact form.",
"Meeting scheduled for next Tuesday to discuss technical specifications in detail.",
"They requested references from similar projects. Sent over three case studies.",
"Discussed hosting options and ongoing maintenance packages. They're interested in a monthly retainer.",
"Sent over the contract for signature. Waiting for their legal team to review.",
"They asked about our experience with custom CRM integrations. Shared relevant project examples.",
"Follow-up call completed. They have some additional questions about the timeline.",
"Exchanged emails about the design direction. They prefer a minimalist approach.",
"Discussed SEO strategy and content creation. They want to handle content themselves.",
"They requested a revised quote for a smaller scope. Adjusted the proposal accordingly.",
"Had a productive call about their target audience and user personas.",
"Sent over wireframes for the homepage. Waiting for their feedback.",
"They mentioned a referral from one of our past clients - good rapport building.",
"Discussed payment terms. They prefer milestone-based payments.",
"They're ready to move forward! Sending the final contract today.",
"Checked in to see if they had any questions about the proposal. No response yet.",
"Left a voicemail. Will try again next week.",
"They mentioned their budget is slightly lower than our minimum. Discussed scope adjustments.",
"Great meeting! They love our approach and want to start as soon as possible.",
"They need to get approval from their partner before proceeding.",
]
function randomDateNear(leadDate: string, maxDaysAfter: number): string {
const base = new Date(leadDate)
const offset = Math.floor(Math.random() * maxDaysAfter * 24 * 60 * 60 * 1000)
return new Date(base.getTime() + offset).toISOString()
}
export const notes: Note[] = leads.flatMap((lead) => {
const leadNotes: Note[] = []
const numNotes = Math.floor(Math.random() * 4) + 1
const assignedUser = lead.assignedUser ?? users[0]
for (let i = 0; i < numNotes; i++) {
const author = Math.random() > 0.3 ? assignedUser : users[Math.floor(Math.random() * users.length)]
const createdAt = randomDateNear(lead.createdAt, 45)
leadNotes.push({
id: `note-${lead.id}-${i}`,
leadId: lead.id,
userId: author.id,
authorName: author.name,
authorAvatar: author.avatar,
authorRole: author.role,
note: noteTemplates[Math.floor(Math.random() * noteTemplates.length)],
createdAt,
updatedAt: createdAt,
})
}
return leadNotes
}).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
export function getNotesByLeadId(leadId: string): Note[] {
return notes.filter((n) => n.leadId === leadId).sort(
(a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
)
}
+90
View File
@@ -0,0 +1,90 @@
import { User } from "@/types"
export const users: User[] = [
{
id: "u1",
name: "Sarah Chen",
email: "sarah@coastalit.com",
role: "admin",
active: true,
avatar: "https://ui-avatars.com/api/?name=Sarah+Chen&background=1d4ed8&color=fff&size=128",
createdAt: "2025-01-15T08:00:00Z",
},
{
id: "u2",
name: "Marcus Johnson",
email: "marcus@coastalit.com",
role: "sales",
active: true,
avatar: "https://ui-avatars.com/api/?name=Marcus+Johnson&background=2563eb&color=fff&size=128",
createdAt: "2025-02-01T09:00:00Z",
},
{
id: "u3",
name: "Emily Rodriguez",
email: "emily@coastalit.com",
role: "sales",
active: true,
avatar: "https://ui-avatars.com/api/?name=Emily+Rodriguez&background=3b82f6&color=fff&size=128",
createdAt: "2025-02-15T10:00:00Z",
},
{
id: "u4",
name: "David Kim",
email: "david@coastalit.com",
role: "sales",
active: true,
avatar: "https://ui-avatars.com/api/?name=David+Kim&background=60a5fa&color=fff&size=128",
createdAt: "2025-03-01T08:00:00Z",
},
{
id: "u5",
name: "Jessica Patel",
email: "jessica@coastalit.com",
role: "sales",
active: false,
avatar: "https://ui-avatars.com/api/?name=Jessica+Patel&background=93c5fd&color=fff&size=128",
createdAt: "2025-03-10T09:00:00Z",
},
{
id: "u6",
name: "Alex Thompson",
email: "alex@coastalit.com",
role: "sales",
active: true,
avatar: "https://ui-avatars.com/api/?name=Alex+Thompson&background=1d4ed8&color=fff&size=128",
createdAt: "2025-04-01T08:00:00Z",
},
{
id: "u7",
name: "Rachel Williams",
email: "rachel@coastalit.com",
role: "sales",
active: true,
avatar: "https://ui-avatars.com/api/?name=Rachel+Williams&background=2563eb&color=fff&size=128",
createdAt: "2025-04-15T10:00:00Z",
},
{
id: "u8",
name: "Tom Nakamura",
email: "tom@coastalit.com",
role: "admin",
active: true,
avatar: "https://ui-avatars.com/api/?name=Tom+Nakamura&background=3b82f6&color=fff&size=128",
createdAt: "2025-05-01T09:00:00Z",
},
]
export const currentUser: User = users[0]
export function getUserById(id: string): User | undefined {
return users.find((u) => u.id === id)
}
export function getUsersByRole(role: "admin" | "sales"): User[] {
return users.filter((u) => u.role === role)
}
export function getActiveUsers(): User[] {
return users.filter((u) => u.active)
}