Add planned activities and follow-ups workflow
Activities with future dates are automatically tagged as Planned. Once the date passes they become Pending Follow-Ups requiring resolution. - Added Status column to activities (Planned/Completed/Did Not Occur) - Built Follow-Ups subview with expandable cards, outcome capture, and Create Next Step workflow for scheduling follow-on activities - Upcoming Planned table shows scheduled future activities - Executive Overview gains Meetings Scheduled and Follow-Ups Pending KPIs - Planned/Follow-Up badges appear in the activity detail table - Admin page includes Status field for activities Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -52,6 +52,12 @@ function migrateSchema(db: Database.Database) {
|
||||
if (!colNames.has('CPM')) {
|
||||
db.exec("ALTER TABLE accounts ADD COLUMN CPM TEXT DEFAULT NULL");
|
||||
}
|
||||
|
||||
const actCols = db.prepare("PRAGMA table_info(activities)").all() as { name: string }[];
|
||||
const actColNames = new Set(actCols.map(c => c.name));
|
||||
if (!actColNames.has('Status')) {
|
||||
db.exec("ALTER TABLE activities ADD COLUMN Status TEXT DEFAULT NULL");
|
||||
}
|
||||
}
|
||||
|
||||
function initSchema(db: Database.Database) {
|
||||
@@ -120,7 +126,8 @@ function initSchema(db: Database.Database) {
|
||||
Channel TEXT,
|
||||
Persona TEXT,
|
||||
Outcome TEXT,
|
||||
Logged_By TEXT
|
||||
Logged_By TEXT,
|
||||
Status TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS implementations (
|
||||
@@ -243,7 +250,7 @@ export function getDashboardData(): DashboardData {
|
||||
return {
|
||||
pipeline: db.prepare('SELECT * FROM pipeline').all() as PipelineRecord[],
|
||||
accounts: db.prepare('SELECT * FROM accounts').all() as AccountRecord[],
|
||||
activities: db.prepare('SELECT id, Activity_Date, Activity_Type, Account_Name, District_Name, Contact_Name, Notes, Play, Channel, Persona, Outcome, Logged_By FROM activities ORDER BY Activity_Date DESC').all() as (ActivityRecord & { id: number })[],
|
||||
activities: db.prepare('SELECT id, Activity_Date, Activity_Type, Account_Name, District_Name, Contact_Name, Notes, Play, Channel, Persona, Outcome, Logged_By, Status FROM activities ORDER BY Activity_Date DESC').all() as (ActivityRecord & { id: number })[],
|
||||
targets: buildTargetsFromImplementations(db),
|
||||
implementations: db.prepare('SELECT * FROM implementations WHERE Account_Name IS NOT NULL').all() as ImplementationRecord[],
|
||||
metricTargets: db.prepare('SELECT * FROM metric_targets').all() as MetricTarget[],
|
||||
@@ -346,11 +353,15 @@ export function addActivity(record: {
|
||||
Persona?: string | null;
|
||||
Outcome?: string | null;
|
||||
Logged_By?: string | null;
|
||||
Status?: string | null;
|
||||
}) {
|
||||
const db = getDb();
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
const status = record.Status || (record.Activity_Date > today ? 'Planned' : null);
|
||||
|
||||
db.prepare(`
|
||||
INSERT INTO activities (Activity_Date, Activity_Type, Account_Name, District_Name, Contact_Name, Notes, Play, Channel, Persona, Outcome, Logged_By)
|
||||
VALUES (@Activity_Date, @Activity_Type, @Account_Name, @District_Name, @Contact_Name, @Notes, @Play, @Channel, @Persona, @Outcome, @Logged_By)
|
||||
INSERT INTO activities (Activity_Date, Activity_Type, Account_Name, District_Name, Contact_Name, Notes, Play, Channel, Persona, Outcome, Logged_By, Status)
|
||||
VALUES (@Activity_Date, @Activity_Type, @Account_Name, @District_Name, @Contact_Name, @Notes, @Play, @Channel, @Persona, @Outcome, @Logged_By, @Status)
|
||||
`).run({
|
||||
Activity_Date: record.Activity_Date,
|
||||
Activity_Type: record.Activity_Type,
|
||||
@@ -363,9 +374,27 @@ export function addActivity(record: {
|
||||
Persona: record.Persona || null,
|
||||
Outcome: record.Outcome || null,
|
||||
Logged_By: record.Logged_By || null,
|
||||
Status: status,
|
||||
});
|
||||
|
||||
updateLastTouched(record.Account_Name, record.Activity_Date);
|
||||
if (!status || status !== 'Planned') {
|
||||
updateLastTouched(record.Account_Name, record.Activity_Date);
|
||||
}
|
||||
}
|
||||
|
||||
export function updateActivityStatus(id: number, updates: { Status: string; Outcome?: string | null; Notes?: string | null }) {
|
||||
const db = getDb();
|
||||
const params: Record<string, unknown> = { id, Status: updates.Status };
|
||||
const sets = ['Status = @Status'];
|
||||
if (updates.Outcome !== undefined) { sets.push('Outcome = @Outcome'); params.Outcome = updates.Outcome ?? null; }
|
||||
if (updates.Notes !== undefined) { sets.push('Notes = @Notes'); params.Notes = updates.Notes ?? null; }
|
||||
|
||||
db.prepare(`UPDATE activities SET ${sets.join(', ')} WHERE id = @id`).run(params);
|
||||
|
||||
if (updates.Status === 'Completed') {
|
||||
const act = db.prepare('SELECT Account_Name, Activity_Date FROM activities WHERE id = ?').get(id) as { Account_Name: string; Activity_Date: string } | undefined;
|
||||
if (act) updateLastTouched(act.Account_Name, act.Activity_Date);
|
||||
}
|
||||
}
|
||||
|
||||
export function importData(data: {
|
||||
|
||||
Reference in New Issue
Block a user