Fixed Resources, Added Error Logging/Messages, Fixed Pumkin Spice Background
Build & Auto-Repair / build (push) Has been cancelled

This commit is contained in:
JCBSComputer
2026-07-06 09:13:45 +02:00
parent dbec6c0851
commit 4035f5fad4
11 changed files with 372 additions and 264 deletions
+33 -9
View File
@@ -73,9 +73,12 @@ Example format:
[{"companyName":"Acme Corp","contactName":"John Smith","email":"john@acme.com","phone":"555-0100","description":"Brief description"}]
Use realistic company names, names, and emails for the ${job.industry} industry.`
aiChatRef.current?.addAssistantMessage(`🔍 Finding leads for **${job.job_title}**...`)
const addMsg = (text: string) => aiChatRef.current?.addAssistantMessage(text)
try {
addMsg(`🔍 Step 1/3: Asking AI to generate leads for **${job.job_title}**...`)
const res = await fetch("/api/ai/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
@@ -84,11 +87,22 @@ Use realistic company names, names, and emails for the ${job.industry} industry.
if (!res.ok) {
const data = await res.json().catch(() => ({}))
throw new Error(data.error || `Error ${res.status}`)
const statusInfo = `HTTP ${res.status}: ${data.error || res.statusText}`
addMsg(`❌ Step 1/3 failed — AI chat returned ${statusInfo}. Make sure Ollama is running (port 11434) and the AI service on port 3001 is up.`)
setSearching(false)
return
}
const data = await res.json()
const raw = data.response || ""
if (!raw) {
addMsg("❌ Step 1/3 failed — AI returned an empty response.")
setSearching(false)
return
}
addMsg(`✅ Step 1/3 complete. Parsing AI response...`)
let leads: any[]
try {
let text = raw.trim()
@@ -97,11 +111,19 @@ Use realistic company names, names, and emails for the ${job.industry} industry.
leads = JSON.parse(text)
if (!Array.isArray(leads)) leads = [leads]
} catch (e) {
aiChatRef.current?.addAssistantMessage(`⚠️ AI response wasn't valid JSON. Showing raw output:\n\n${raw.slice(0, 1500)}`)
addMsg(`⚠️ Step 2/3 failed — AI response wasn't valid JSON. Raw output:\n\n${raw.slice(0, 1500)}`)
setSearching(false)
return
}
if (leads.length === 0) {
addMsg("⚠️ Step 2/3 — AI returned an empty array. No leads to create.")
setSearching(false)
return
}
addMsg(`✅ Step 2/3 — Parsed **${leads.length}** lead${leads.length > 1 ? "s" : ""} from AI response. Step 3/3: Saving to database...`)
let created = 0
let lastError = ""
for (const lead of leads.slice(0, 5)) {
@@ -119,18 +141,20 @@ Use realistic company names, names, and emails for the ${job.industry} industry.
}),
})
if (r.ok) created++
else { const e = await r.json().catch(() => ({})); lastError = e.error || r.statusText; }
} catch (e) { lastError = "Network error" }
else { const e = await r.json().catch(() => ({})); lastError = `API ${res.status} ${e.error || r.statusText}`; }
} catch (e) {
lastError = `Network error: ${e instanceof Error ? e.message : "Failed to reach server"}`
}
}
if (created > 0) {
aiChatRef.current?.addAssistantMessage(`✅ Created **${created}** lead${created > 1 ? "s" : ""} for **${job.job_title}**! Go to the Leads tab to see them.`)
addMsg(`✅ Created **${created}** lead${created > 1 ? "s" : ""} for **${job.job_title}**! Go to the **Leads** tab to view and manage them.${lastError ? `\n\n⚠️ ${leads.length - created} lead${leads.length - created > 1 ? "s" : ""} failed: ${lastError}` : ""}`)
} else {
aiChatRef.current?.addAssistantMessage(`Failed to create leads. Last error: ${lastError || "Unknown"}. Make sure you're logged in with an account that has permission to create leads.`)
addMsg(`Step 3/3 failed — Could not save any leads.\n\nLast error: ${lastError || "Unknown"}\n\nPossible causes:\n• Your session expired — try logging out and back in\n• You don't have permission to create leads\n• The database connection is down`)
}
} catch (err) {
const msg = err instanceof Error ? err.message : "Search failed"
aiChatRef.current?.addAssistantMessage(`Error: ${msg}. Make sure Ollama is running with the model loaded.`)
const msg = err instanceof Error ? err.message : "Unknown error"
addMsg(`Unexpected error: ${msg}\n\nThis might be a network issue or the AI service (port 3001) is not responding. Try again.`)
} finally {
setSearching(false)
}
+5 -5
View File
@@ -126,11 +126,11 @@ export default function CalendarPage() {
fetch("/api/event-users")
.then((r) => r.json())
.then((data) => setUsers(data.users || []))
.catch(() => {})
.catch((err) => console.error("Failed to fetch event users:", err))
fetch("/api/leads?limit=200")
.then((r) => r.json())
.then((data) => setLeads((Array.isArray(data) ? data : data?.leads || []).map((l: any) => ({ id: l.id, contactName: l.contactName, companyName: l.companyName }))))
.catch(() => {})
.catch((err) => console.error("Failed to fetch leads:", err))
}, [user, router])
useEffect(() => {
@@ -139,13 +139,13 @@ export default function CalendarPage() {
}, [fetchMonthEvents])
useEffect(() => {
fetchUpcomingEvents().catch(() => {})
fetchUpcomingEvents().catch((err) => console.error("Failed to fetch upcoming events:", err))
}, [fetchUpcomingEvents])
useEffect(() => {
const interval = setInterval(() => {
fetchMonthEvents().catch(() => {})
fetchUpcomingEvents().catch(() => {})
fetchMonthEvents().catch((err) => console.error("Failed to fetch month events:", err))
fetchUpcomingEvents().catch((err) => console.error("Failed to fetch upcoming events:", err))
}, 15000)
return () => clearInterval(interval)
}, [fetchMonthEvents, fetchUpcomingEvents])
+14 -8
View File
@@ -39,8 +39,11 @@ export default function CampaignDetailPage({ params }: { params: Promise<{ id: s
const searchLeads = async (q: string) => {
setLeadSearch(q)
if (q.length < 2) { setSearchResults([]); return }
const res = await fetch(`/api/leads?search=${encodeURIComponent(q)}&limit=10`)
if (res.ok) setSearchResults(await res.json())
try {
const res = await fetch(`/api/leads?search=${encodeURIComponent(q)}&limit=10`)
if (res.ok) setSearchResults(await res.json())
else console.error("Failed to search leads:", res.status, res.statusText)
} catch (err) { console.error("Failed to search leads:", err) }
}
const addStep = async () => {
@@ -84,12 +87,15 @@ export default function CampaignDetailPage({ params }: { params: Promise<{ id: s
}
const updateStatus = async (status: string) => {
await fetch(`/api/campaigns/${id}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ status }),
})
fetchCampaign()
try {
const res = await fetch(`/api/campaigns/${id}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ status }),
})
if (!res.ok) console.error("Failed to update campaign status:", res.status, res.statusText)
fetchCampaign()
} catch (err) { console.error("Failed to update campaign status:", err) }
}
if (loading) return <div className="p-8 text-center text-muted-foreground">Loading...</div>
+9 -9
View File
@@ -235,8 +235,8 @@ const formatPreviewContent = (content: string) => {
setUnreadMap(new Map(convs.map((c: any) => [c.id, c.unread])))
if (convs.length > 0) setActiveChat(convs[0].id)
}
} catch {
console.warn("Failed to fetch conversations in chats page")
} catch (err) {
console.error("Failed to fetch conversations:", err)
} finally {
setLoadingChats(false)
}
@@ -297,11 +297,11 @@ const formatPreviewContent = (content: string) => {
return next
})
}
} catch {
// ignore
} catch (err) {
console.error("Failed to fetch messages:", err)
}
// Mark conversation as read
fetch(`/api/conversations/${activeChat}/read`, { method: "POST" }).catch(() => {})
fetch(`/api/conversations/${activeChat}/read`, { method: "POST" }).catch((err) => console.error("Failed to mark read:", err))
setUnreadMap((prev) => { const m = new Map(prev); m.set(activeChat, 0); return m })
}
fetchMsgs()
@@ -320,7 +320,7 @@ const formatPreviewContent = (content: string) => {
const existingIds = new Set(conversations.map((c) => otherParticipant(c).id))
setSearchResults(data.users.filter((u: any) => !existingIds.has(u.id)))
}
} catch { console.warn("Failed to search users in chats page") }
} catch (err) { console.error("Failed to search users:", err) }
setSearchingUsers(false)
}, 300)
return () => clearTimeout(timer)
@@ -799,8 +799,8 @@ const formatPreviewContent = (content: string) => {
prev.map((c) => (c.id === convId ? { ...c, pinned: !pinned } : c))
)
}
} catch {
console.warn("Failed to toggle pin")
} catch (err) {
console.error("Failed to toggle pin:", err)
}
}
@@ -912,7 +912,7 @@ const formatPreviewContent = (content: string) => {
setSearchQuery("")
}
}
} catch { console.warn("Failed to create conversation in chats page") }
} catch (err) { console.error("Failed to create conversation:", err) }
}}
className="w-full flex items-center gap-3 p-4 text-left transition-colors hover:bg-muted/50"
>
+2 -2
View File
@@ -39,8 +39,8 @@ export default function DashboardPage() {
const data = await res.json()
setStats(data)
}
} catch {
console.warn("Failed to fetch dashboard stats")
} catch (err) {
console.error("Failed to fetch dashboard stats:", err)
}
}
+1 -1
View File
@@ -13,7 +13,7 @@ export default function EmailsPage() {
useEffect(() => {
if (!user) { router.push("/login"); return }
if (user.role !== "admin" && user.role !== "super_admin") { router.push("/dashboard"); return }
fetch("/api/emails").then((r) => r.ok ? r.json() : { emails: [] }).then((d) => setEmails(d.emails || []))
fetch("/api/emails").then((r) => r.ok ? r.json() : { emails: [] }).then((d) => setEmails(d.emails || [])).catch((err) => console.error("Failed to fetch emails:", err))
}, [user, router])
if (!user || (user.role !== "admin" && user.role !== "super_admin")) return null
+2 -2
View File
@@ -39,8 +39,8 @@ export default function LeadDetailsPage({ params }: { params: Promise<{ id: stri
fetchNotes(),
])
if (leadRes.ok) setLead(await leadRes.json())
} catch {
console.warn("Failed to fetch lead details")
} catch (err) {
console.error("Failed to fetch lead details:", err)
}
setLoading(false)
}
+19 -28
View File
@@ -42,33 +42,24 @@ interface Totals {
teamUtilization: number
}
function utilizationColor(pct: number): string {
if (pct > 100) return "bg-red-500"
if (pct > 90) return "bg-orange-500"
if (pct > 70) return "bg-yellow-500"
return "bg-emerald-500"
}
function utilizationTextColor(pct: number): string {
if (pct > 100) return "text-red-600"
if (pct > 90) return "text-orange-600"
if (pct > 70) return "text-yellow-600"
return "text-emerald-600"
}
function utilizationBgColor(pct: number): string {
if (pct > 100) return "bg-red-50 border-red-200"
if (pct > 90) return "bg-orange-50 border-orange-200"
if (pct > 70) return "bg-yellow-50 border-yellow-200"
return "bg-emerald-50 border-emerald-200"
function statusKey(pct: number): string {
if (pct > 100) return "overloaded"
if (pct > 90) return "near-capacity"
if (pct > 70) return "busy"
if (pct > 50) return "available"
return "underutilized"
}
function statusLabel(pct: number): { label: string; color: string } {
if (pct > 100) return { label: "Overloaded", color: "bg-red-100 text-red-700 border-red-300" }
if (pct > 90) return { label: "Near Capacity", color: "bg-orange-100 text-orange-700 border-orange-300" }
if (pct > 70) return { label: "Busy", color: "bg-yellow-100 text-yellow-700 border-yellow-300" }
if (pct > 50) return { label: "Available", color: "bg-emerald-100 text-emerald-700 border-emerald-300" }
return { label: "Underutilized", color: "bg-slate-100 text-slate-600 border-slate-300" }
const key = statusKey(pct)
const labels: Record<string, string> = {
overloaded: "Overloaded",
"near-capacity": "Near Capacity",
busy: "Busy",
available: "Available",
underutilized: "Underutilized",
}
return { label: labels[key], color: `util-badge-${key}` }
}
export default function ResourcePlanningPage() {
@@ -120,7 +111,7 @@ export default function ResourcePlanningPage() {
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<TrendingUp className="h-4 w-4" /> Utilisation
</div>
<p className={`text-2xl font-bold ${utilizationTextColor(totals?.teamUtilization ?? 0)}`}>
<p className={`text-2xl font-bold util-text-${statusKey(totals?.teamUtilization ?? 0)}`}>
{totals?.teamUtilization ?? 0}%
</p>
</Card>
@@ -157,7 +148,7 @@ export default function ResourcePlanningPage() {
{members.map((m) => {
const status = statusLabel(m.utilization)
return (
<Card key={m.id} className={`p-4 space-y-3 border ${utilizationBgColor(m.utilization)}`}>
<Card key={m.id} className={`p-4 space-y-3 border util-card-${statusKey(m.utilization)}`}>
<div className="flex items-start justify-between">
<div className="flex items-center gap-3">
<Avatar className="h-10 w-10">
@@ -179,11 +170,11 @@ export default function ResourcePlanningPage() {
<div className="flex justify-between text-xs text-muted-foreground">
<span>Capacity: {m.capacityHours}h</span>
<span>Logged: {m.totalHours}h</span>
<span className={utilizationTextColor(m.utilization)}>{m.utilization}%</span>
<span className={`util-text-${statusKey(m.utilization)}`}>{m.utilization}%</span>
</div>
<div className="h-2.5 w-full overflow-hidden rounded-full bg-muted">
<div
className={`h-full rounded-full transition-all ${utilizationColor(m.utilization)}`}
className={`h-full rounded-full transition-all util-bar-${statusKey(m.utilization)}`}
style={{ width: `${Math.min(m.utilization, 100)}%` }}
/>
</div>
+2 -2
View File
@@ -24,8 +24,8 @@ export default function UsersPage() {
const data = await res.json()
setUsers(data.users || [])
}
} catch {
console.warn("Failed to fetch users in users page")
} catch (err) {
console.error("Failed to fetch users:", err)
} finally {
setLoading(false)
}