Fixed Resources, Added Error Logging/Messages, Fixed Pumkin Spice Background
Build & Auto-Repair / build (push) Has been cancelled
Build & Auto-Repair / build (push) Has been cancelled
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user