feat: add system monitor component and API for performance metrics

feat: implement conversations API with message retrieval and posting

feat: add avatar URL handling for users and update user role definitions

feat: create chat system tables in the database with initial seed data

fix: update user roles from "sales_user" to "sales" for consistency

chore: add middleware for system API route access

fixed fuckups on john's side, added a benchmark

Made Graphs actualy load from database and realtime data, graphs will update and percentages will be mathematically made,  and made realtime

added chatcart edits, made graphs glow.

added in some little small home feeling fuctions

added in a slide show, in login with qoutes from creators because i want to leave a print on it saying we made this shit, we built it brick for brick

login page added qoutes some random, and made them shuffle from left to right one dissapears other come in

adding shape to the textbox for the qoutes

Fixed my chat fuckups when it comes to chats
This commit is contained in:
Ace
2026-06-18 22:48:03 +02:00
parent f5d09298a2
commit adbcc4b9af
38 changed files with 2321 additions and 741 deletions
+107 -73
View File
@@ -1,5 +1,6 @@
"use client"
import { useState, useEffect } from "react"
import Link from "next/link"
import { motion } from "framer-motion"
import { use } from "react"
@@ -16,14 +17,39 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { getLeadById } from "@/data/leads"
import { getNotesByLeadId } from "@/data/notes"
import { ArrowLeft, Edit, ExternalLink } from "lucide-react"
import { Lead, Note } from "@/types"
export default function LeadDetailsPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = use(params)
const lead = getLeadById(id)
const leadNotes = lead ? getNotesByLeadId(lead.id) : []
const [lead, setLead] = useState<Lead | null>(null)
const [leadNotes, setLeadNotes] = useState<Note[]>([])
const [loading, setLoading] = useState(true)
useEffect(() => {
async function fetchData() {
try {
const [leadRes, notesRes] = await Promise.all([
fetch(`/api/leads/${id}`),
fetch(`/api/leads/${id}/notes`),
])
if (leadRes.ok) setLead(await leadRes.json())
if (notesRes.ok) setLeadNotes(await notesRes.json())
} catch {
// ignore
}
setLoading(false)
}
fetchData()
}, [id])
if (loading) {
return (
<div className="flex items-center justify-center h-[60vh]">
<p className="text-muted-foreground">Loading...</p>
</div>
)
}
if (!lead) {
return (
@@ -61,71 +87,45 @@ export default function LeadDetailsPage({ params }: { params: Promise<{ id: stri
}
description={lead.contactName}
>
<div className="flex items-center gap-3">
<Select defaultValue={lead.status}>
<SelectTrigger className="h-9 w-[140px]">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="open">Open</SelectItem>
<SelectItem value="contacted">Contacted</SelectItem>
<SelectItem value="pending">Pending</SelectItem>
<SelectItem value="closed">Closed</SelectItem>
<SelectItem value="ignored">Ignored</SelectItem>
</SelectContent>
</Select>
<Button className="gap-2">
<Edit className="h-4 w-4" />
Edit
</Button>
</div>
<Select
value={lead.status}
onValueChange={(v) => {
setLead((prev) => prev ? { ...prev, status: v as Lead["status"] } : prev)
}}
>
<SelectTrigger className="h-9 w-[140px]">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="open">Open</SelectItem>
<SelectItem value="contacted">Contacted</SelectItem>
<SelectItem value="pending">Pending</SelectItem>
<SelectItem value="closed">Closed</SelectItem>
<SelectItem value="ignored">Ignored</SelectItem>
</SelectContent>
</Select>
<Button variant="outline" size="sm">
<Edit className="mr-2 h-4 w-4" />
Edit
</Button>
</PageHeader>
<div className="grid gap-6 lg:grid-cols-3">
<div className="space-y-6 lg:col-span-2">
<LeadDetailsCard lead={lead} />
<div className="space-y-6">
<NoteForm leadId={lead.id} />
<NoteTimeline notes={leadNotes} />
</div>
</div>
<div className="lg:col-span-2 space-y-6">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
>
<LeadDetailsCard lead={lead} />
</motion.div>
<div className="space-y-6">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, delay: 0.1 }}
>
<div className="rounded-lg border bg-card">
<div className="border-b px-6 py-4">
<h3 className="font-semibold">Activity</h3>
</div>
<div className="divide-y">
{[
{ action: "Lead created", date: lead.createdAt, user: lead.assignedUser?.name || "System" },
...leadNotes.slice(0, 3).map((n) => ({
action: "Note added",
date: n.createdAt,
user: n.authorName,
})),
].map((activity, i) => (
<div key={i} className="px-6 py-3">
<div className="flex items-center gap-2">
<div className="h-2 w-2 rounded-full bg-primary/60 shrink-0" />
<p className="text-sm">{activity.action}</p>
</div>
<p className="mt-0.5 text-xs text-muted-foreground pl-4">
by {activity.user} &middot; {new Date(activity.date).toLocaleDateString(undefined, {
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
})}
</p>
</div>
))}
</div>
</div>
<NoteForm leadId={lead.id} />
</motion.div>
<motion.div
@@ -133,20 +133,54 @@ export default function LeadDetailsPage({ params }: { params: Promise<{ id: stri
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, delay: 0.2 }}
>
<div className="rounded-lg border bg-card">
<div className="border-b px-6 py-4">
<h3 className="font-semibold">Quick Actions</h3>
</div>
<div className="p-4 space-y-2">
<Button variant="outline" className="w-full justify-start" size="sm">
<ExternalLink className="mr-2 h-4 w-4" />
Send Email
</Button>
<Button variant="outline" className="w-full justify-start" size="sm">
<ExternalLink className="mr-2 h-4 w-4" />
Call Lead
</Button>
<NoteTimeline notes={leadNotes} />
</motion.div>
</div>
<div className="space-y-6">
<motion.div
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.3, delay: 0.15 }}
className="rounded-lg border bg-card p-6"
>
<h3 className="font-semibold mb-4">Activity</h3>
<div className="space-y-4">
<div className="flex items-center gap-3 text-sm">
<div className="h-2 w-2 rounded-full bg-blue-500" />
<div>
<p className="font-medium">Lead Created</p>
<p className="text-xs text-muted-foreground">{new Date(lead.createdAt).toLocaleDateString()}</p>
</div>
</div>
{leadNotes.slice(0, 3).map((note) => (
<div key={note.id} className="flex items-start gap-3 text-sm">
<div className="h-2 w-2 rounded-full bg-muted-foreground mt-1.5" />
<div>
<p className="font-medium">{note.authorName}</p>
<p className="text-xs text-muted-foreground">{note.note.slice(0, 60)}...</p>
</div>
</div>
))}
</div>
</motion.div>
<motion.div
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.3, delay: 0.25 }}
className="rounded-lg border bg-card p-6"
>
<h3 className="font-semibold mb-4">Quick Actions</h3>
<div className="space-y-2">
<Button variant="outline" className="w-full justify-start" size="sm">
<ExternalLink className="mr-2 h-4 w-4" />
Send Email
</Button>
<Button variant="outline" className="w-full justify-start" size="sm">
<ExternalLink className="mr-2 h-4 w-4" />
Call Lead
</Button>
</div>
</motion.div>
</div>
+18 -26
View File
@@ -1,42 +1,34 @@
"use client"
import { useState, useMemo } from "react"
import { useState, useEffect } from "react"
import { useRouter } from "next/navigation"
import { PageHeader } from "@/components/shared/page-header"
import { LeadsTable } from "@/components/leads/leads-table"
import { LeadsTableToolbar } from "@/components/leads/leads-table-toolbar"
import { leads } from "@/data/leads"
import { filterLeadsByPeriod } from "@/lib/date-utils"
import { Lead } from "@/types"
export default function LeadsPage() {
const router = useRouter()
const [search, setSearch] = useState("")
const [statusFilter, setStatusFilter] = useState("all")
const [periodFilter, setPeriodFilter] = useState("all")
const [leadsData, setLeadsData] = useState<Lead[]>([])
const [loading, setLoading] = useState(true)
const filteredLeads = useMemo(() => {
let result = leads
useEffect(() => {
const params = new URLSearchParams()
if (search) params.set("search", search)
if (statusFilter !== "all") params.set("status", statusFilter)
if (periodFilter !== "all") params.set("period", periodFilter)
if (periodFilter && periodFilter !== "all") {
result = filterLeadsByPeriod(result, periodFilter)
}
if (search) {
const q = search.toLowerCase()
result = result.filter(
(l) =>
l.companyName.toLowerCase().includes(q) ||
l.contactName.toLowerCase().includes(q) ||
l.email.toLowerCase().includes(q) ||
l.phone.includes(q)
)
}
if (statusFilter && statusFilter !== "all") {
result = result.filter((l) => l.status === statusFilter)
}
return result
setLoading(true)
fetch(`/api/leads?${params.toString()}`)
.then((r) => r.json())
.then((data) => {
setLeadsData(data)
setLoading(false)
})
.catch(() => setLoading(false))
}, [search, statusFilter, periodFilter])
return (
@@ -58,7 +50,7 @@ export default function LeadsPage() {
onCreateClick={() => router.push("/leads/new")}
/>
</div>
<LeadsTable data={filteredLeads} />
<LeadsTable data={leadsData} loading={loading} />
</div>
</div>
)