feat: Enhance provider components with detailed documentation and comments

- Added comprehensive comments and JSDoc-style documentation to NotificationProvider, ThemeProvider, UserProvider, and WebsiteThemeProvider for better clarity and maintainability.
- Improved type definitions in index.ts for better code understanding and usage.
- Introduced Docker support with Dockerfiles for various services including AI server, signaling server, and browser-use service.
- Created a docker-compose.yml file to orchestrate multiple services including PostgreSQL, AI, scraper, and frontend.
- Added a startup guide (startup.txt) for setting up the CRM environment on Ubuntu with Docker.
- Included a .dockerignore file to exclude unnecessary files from Docker builds.
This commit is contained in:
Ace
2026-07-13 13:05:30 +02:00
parent dba4c84cd5
commit d35c806d5b
167 changed files with 3474 additions and 102 deletions
+32 -4
View File
@@ -1,8 +1,21 @@
// ── Dashboard ────────────────────────────────────────────────────────────────
// GET /api/dashboard?period=6months&year=2025
// — Returns aggregated stats, trends, monthly breakdown, and recent leads.
//
// Auth: authenticated; non-admin users scoped to their own leads
//
// Periods: 7days, 30days, 6months (default), 12months, or a specific year
import { NextRequest, NextResponse } from "next/server"
import { getSessionUser } from "@/lib/auth"
import { query } from "@/lib/db"
import { avatarSvgUrl } from "@/lib/avatar"
// ── Helpers ──────────────────────────────────────────────────────────────────
/**
* Returns the { start, end } Date range for a named period.
*/
function getPeriodDateRange(period: string): { start: Date; end: Date } {
const end = new Date()
let start: Date
@@ -20,6 +33,9 @@ function getPeriodDateRange(period: string): { start: Date; end: Date } {
return { start, end }
}
/**
* Returns the range for the *previous* period of the same length.
*/
function getPreviousPeriodRange(period: string, currentStart: Date): { start: Date; end: Date } {
const end = new Date(currentStart)
const diff = end.getTime() - currentStart.getTime()
@@ -34,6 +50,9 @@ const periodLabels: Record<string, string> = {
"12months": "Last 12 months",
}
/**
* Maps DB stage name to client-facing status string.
*/
function stageToStatus(name: string): string {
switch (name) {
case "New": return "open"
@@ -48,12 +67,16 @@ function stageToStatus(name: string): string {
}
}
/**
* Computes a trend object from current vs previous count.
*/
function computeTrend(current: number, previous: number): { pct: number; up: boolean } {
if (previous === 0) return { pct: current > 0 ? 100 : 0, up: current > 0 }
const pct = Math.round(((current - previous) / previous) * 100)
return { pct: Math.abs(pct), up: pct >= 0 }
}
// Reusable SQL snippet that maps stage names to status strings
const stageStatusSql = `
CASE
WHEN ls.name = 'New' THEN 'open'
@@ -64,6 +87,8 @@ const stageStatusSql = `
ELSE 'open'
END`
// ── GET ──────────────────────────────────────────────────────────────────────
export async function GET(request: NextRequest) {
try {
const user = await getSessionUser()
@@ -76,6 +101,7 @@ export async function GET(request: NextRequest) {
const yearParam = searchParams.get("year")
let start: Date, end: Date, prevRange: { start: Date; end: Date }
if (yearParam) {
// Year mode: compare full year to previous full year
const y = parseInt(yearParam)
start = new Date(y, 0, 1)
end = new Date(y, 11, 31, 23, 59, 59)
@@ -86,9 +112,10 @@ export async function GET(request: NextRequest) {
prevRange = getPreviousPeriodRange(period, start)
}
// Non-admin users only see their own leads
const ownerFilter = isAdmin ? "" : "AND l.assigned_to = $3"
// Status counts for current period (SQL aggregation)
// ── Status counts for current period (SQL aggregation) ──────────
const countSql = `
SELECT ${stageStatusSql} AS status, COUNT(*) AS count
FROM leads l
@@ -129,7 +156,7 @@ export async function GET(request: NextRequest) {
const closedLeads = currentCounts.closed
const conversionRate = totalLeads > 0 ? Math.round((closedLeads / totalLeads) * 100) : 0
// Monthly/weekly breakdown via date_trunc
// ── Monthly/weekly breakdown via date_trunc ─────────────────────
const isMonthly = period === "6months" || period === "12months"
const truncUnit = isMonthly ? "month" : "day"
const breakdownSql = `
@@ -148,7 +175,7 @@ export async function GET(request: NextRequest) {
: [truncUnit, start.toISOString(), end.toISOString(), user.id]
const breakdownResult = await query(breakdownSql, breakdownParams)
// Build monthly breakdown from aggregated data
// Aggregate the grouped rows into a label-keyed map
const breakdownMap: Record<string, { label: string; total: number; open: number; contacted: number; pending: number; closed: number; ignored: number }> = {}
for (const r of breakdownResult.rows) {
const d = new Date(r.period_start)
@@ -165,7 +192,7 @@ export async function GET(request: NextRequest) {
}
const monthlyBreakdown = Object.values(breakdownMap)
// Recent 10 leads
// ── Recent 10 leads ─────────────────────────────────────────────
const recentSql = `
SELECT l.id, l.created_at, l.company_name, l.contact_name, l.email, l.phone,
l.notes, l.assigned_to, l.score,
@@ -206,6 +233,7 @@ export async function GET(request: NextRequest) {
updatedAt: r.updated_at,
}))
// ── Trends (current vs previous period) ─────────────────────────
const trends = {
totalLeads: computeTrend(totalLeads, prevTotal),
openLeads: computeTrend(currentCounts.open, prevCounts.open),