From d996fb66b6bf5db287444f0986bc211d699ebf93 Mon Sep 17 00:00:00 2001 From: Chris Olson Date: Tue, 1 Sep 2026 08:50:57 -0400 Subject: [PATCH] Enhance renewal timeline and add inline create forms - Slider now filters $0-$500K in $50K increments (10 steps) - Timeline data points and account labels are clickable to open account detail - Add "Create Opp" button in Pipeline section with inline form (stage, amount, forecast, probability, close date, product, next step) - Add "Add Activity" button in Activity Timeline with inline form (type, date, contact, outcome, notes) - Both forms save via admin API and refresh dashboard data Co-Authored-By: Claude Opus 4.6 --- src/app/(dashboard)/accounts/page.tsx | 209 +++++++++++++++++++++++++- src/components/ui/RenewalTimeline.tsx | 23 ++- 2 files changed, 211 insertions(+), 21 deletions(-) diff --git a/src/app/(dashboard)/accounts/page.tsx b/src/app/(dashboard)/accounts/page.tsx index f0f1971..4418b7d 100644 --- a/src/app/(dashboard)/accounts/page.tsx +++ b/src/app/(dashboard)/accounts/page.tsx @@ -5,9 +5,9 @@ import { PageHeader } from '@/components/ui/PageHeader'; import { ChartCard } from '@/components/ui/ChartCard'; import { Scorecard } from '@/components/ui/Scorecard'; import { formatCurrency, CHART_COLORS, STATUS_COLORS, DISTRICT_SHORT, STAGE_COLORS, TIER_COLORS } from '@/lib/formatters'; -import { useMemo, useState } from 'react'; +import { useMemo, useState, useCallback } from 'react'; import { parseISO, format, differenceInDays, eachDayOfInterval, addMonths, addQuarters } from 'date-fns'; -import { AccountRecord } from '@/types/data'; +import { AccountRecord, STAGES, FORECAST_CATEGORIES, ACTIVITY_TYPES } from '@/types/data'; import { ExportButton } from '@/components/ui/ExportButton'; import { scoreAccountHealth, HEALTH_LEVEL_COLORS } from '@/lib/scoring'; import { AccountNotes } from '@/components/account/AccountNotes'; @@ -100,7 +100,7 @@ function getSuggestedPriority(account: AccountRecord): { level: string; reason: } export default function AccountExplorer() { - const { filtered } = useData(); + const { filtered, refresh } = useData(); const { accounts, pipeline, activities, targets } = filtered; const [search, setSearch] = useState(''); const [tierFilter, setTierFilter] = useState(null); @@ -112,6 +112,68 @@ export default function AccountExplorer() { const [priorityFilter, setPriorityFilter] = useState(null); const [competitorFilter, setCompetitorFilter] = useState(null); const [selectedAccount, setSelectedAccount] = useState(null); + const [showCreateOpp, setShowCreateOpp] = useState(false); + const [showAddActivity, setShowAddActivity] = useState(false); + const [saving, setSaving] = useState(false); + + const createOpp = useCallback(async (form: Record) => { + setSaving(true); + try { + const oppId = `OPP-${Date.now()}`; + await fetch('/api/admin', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + action: 'upsert', + table: 'pipeline', + record: { + Opportunity_ID: oppId, + Account_Name: selectedAccount?.Account_Name, + District_Name: selectedAccount?.District_Name, + Stage: form.stage || '01-Qualified', + Forecast_Category: form.forecast || 'Pipeline', + Amount_USD: Number(form.amount) || 0, + Probability_Pct: Number(form.probability) || 10, + Created_Date: new Date().toISOString().split('T')[0], + Expected_Close_Date: form.closeDate || '', + Next_Step: form.nextStep || null, + Product: form.product || '', + }, + }), + }); + await refresh(); + setShowCreateOpp(false); + } finally { + setSaving(false); + } + }, [selectedAccount, refresh]); + + const addNewActivity = useCallback(async (form: Record) => { + setSaving(true); + try { + await fetch('/api/admin', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + action: 'upsert', + table: 'activities', + record: { + Activity_Date: form.date || new Date().toISOString().split('T')[0], + Activity_Type: form.type || 'Call', + Account_Name: selectedAccount?.Account_Name, + District_Name: selectedAccount?.District_Name, + Contact_Name: form.contact || null, + Notes: form.notes || null, + Outcome: form.outcome || null, + }, + }), + }); + await refresh(); + setShowAddActivity(false); + } finally { + setSaving(false); + } + }, [selectedAccount, refresh]); const adValues = useMemo(() => Array.from(new Set(accounts.map(a => a.AD).filter(Boolean))).sort() as string[], [accounts]); const imsbaValues = useMemo(() => Array.from(new Set(accounts.map(a => a.IMS_BA).filter(Boolean))).sort() as string[], [accounts]); @@ -366,8 +428,72 @@ export default function AccountExplorer() {
{/* Pipeline Section */}
-

Pipeline ({accountPipeline.length} opportunities)

- {accountPipeline.length === 0 ? ( +
+

Pipeline ({accountPipeline.length} opportunities)

+ +
+ {showCreateOpp && ( +
{ + e.preventDefault(); + const fd = new FormData(e.currentTarget); + const form: Record = {}; + fd.forEach((v, k) => { form[k] = v.toString(); }); + createOpp(form); + }} + > +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+ + )} + {accountPipeline.length === 0 && !showCreateOpp ? (
No pipeline opportunities
) : (
@@ -397,8 +523,68 @@ export default function AccountExplorer() { {/* Activity Timeline */}
-

Activity Timeline ({accountActivities.length} activities)

- {accountActivities.length === 0 ? ( +
+

Activity Timeline ({accountActivities.length} activities)

+ +
+ {showAddActivity && ( +
{ + e.preventDefault(); + const fd = new FormData(e.currentTarget); + const form: Record = {}; + fd.forEach((v, k) => { form[k] = v.toString(); }); + addNewActivity(form); + }} + > +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +