2026-05-15 22:45:38 -05:00
|
|
|
'use strict';
|
|
|
|
|
|
2026-07-06 11:00:01 -05:00
|
|
|
import type { Db } from '../types/db';
|
|
|
|
|
|
2026-05-15 22:45:38 -05:00
|
|
|
const { getDb, getSetting } = require('../db/database');
|
|
|
|
|
|
|
|
|
|
const USER_SETTING_KEYS = [
|
|
|
|
|
'currency',
|
|
|
|
|
'date_format',
|
2026-07-05 15:57:35 -05:00
|
|
|
'week_start',
|
2026-07-05 16:03:01 -05:00
|
|
|
'default_landing_page',
|
2026-05-15 22:45:38 -05:00
|
|
|
'grace_period_days',
|
2026-07-05 16:00:32 -05:00
|
|
|
'due_soon_days',
|
2026-05-15 22:45:38 -05:00
|
|
|
'notify_days_before',
|
2026-05-30 14:33:55 -05:00
|
|
|
'drift_threshold_pct',
|
2026-06-03 21:09:26 -05:00
|
|
|
'bank_tracking_enabled',
|
|
|
|
|
'bank_tracking_account_id',
|
|
|
|
|
'bank_tracking_pending_days',
|
2026-06-04 02:57:09 -05:00
|
|
|
'bank_late_attribution_days',
|
2026-06-07 01:28:35 -05:00
|
|
|
'search_bars_collapsed',
|
2026-06-07 17:23:14 -05:00
|
|
|
'tracker_show_bank_projection_banner',
|
|
|
|
|
'tracker_bank_projection_banner_snoozed_until',
|
|
|
|
|
'tracker_show_search_sort',
|
|
|
|
|
'tracker_show_summary_cards',
|
2026-06-12 01:52:48 -05:00
|
|
|
'tracker_show_safe_to_spend',
|
2026-06-07 17:23:14 -05:00
|
|
|
'tracker_show_overdue_command_center',
|
|
|
|
|
'tracker_show_drift_insights',
|
feat(tracker): persist Overdue/Drift panel collapse + inline hide
The Overdue Command Center and Price-Change (drift) panels reset to
expanded on every load — their collapse arrows used local useState. Now
the collapsed state is remembered per-user via two new settings
(tracker_overdue_collapsed / tracker_drift_collapsed), mirroring the
search panel's useSearchPanelPreference pattern. Each panel header also
gains an inline "Hide" control (EyeOff, like the search panel) that writes
the existing tracker_show_* setting and fires an Undo toast; hidden
sections come back from Settings → Tracker Layout.
- services/userSettings.js: allowlist + defaults for the two collapse keys
- TrackerPage: read collapse state, hide handlers with Undo, wire props
- OverdueCommandCenter/DriftInsightPanel: controlled collapse + header hide
Verified: typecheck 0, lint 0 errors, build, and a live settings round-trip
(collapse persists, hide writes, Undo restores, allowlist rejects junk keys).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 10:34:39 -05:00
|
|
|
'tracker_overdue_collapsed',
|
|
|
|
|
'tracker_drift_collapsed',
|
2026-06-07 17:23:14 -05:00
|
|
|
'tracker_table_columns',
|
2026-06-14 15:15:31 -05:00
|
|
|
'bank_auto_categorize_merchants',
|
2026-06-14 19:21:34 -05:00
|
|
|
'spending_show_ready_to_assign',
|
|
|
|
|
'spending_show_avg',
|
|
|
|
|
'spending_show_cover_overspend',
|
|
|
|
|
'spending_group_categories',
|
2026-05-15 22:45:38 -05:00
|
|
|
];
|
|
|
|
|
|
2026-07-06 11:00:01 -05:00
|
|
|
const USER_SETTING_DEFAULTS: Record<string, string> = {
|
2026-07-05 15:57:35 -05:00
|
|
|
week_start: '0',
|
2026-07-05 16:03:01 -05:00
|
|
|
default_landing_page: 'tracker',
|
2026-07-05 16:00:32 -05:00
|
|
|
due_soon_days: '3',
|
2026-06-07 01:28:35 -05:00
|
|
|
search_bars_collapsed: 'false',
|
2026-06-14 15:15:31 -05:00
|
|
|
bank_auto_categorize_merchants: 'true',
|
2026-06-07 17:23:14 -05:00
|
|
|
tracker_show_bank_projection_banner: 'true',
|
|
|
|
|
tracker_bank_projection_banner_snoozed_until: '',
|
|
|
|
|
tracker_show_search_sort: 'true',
|
|
|
|
|
tracker_show_summary_cards: 'true',
|
2026-06-12 01:52:48 -05:00
|
|
|
tracker_show_safe_to_spend: 'true',
|
2026-06-07 17:23:14 -05:00
|
|
|
tracker_show_overdue_command_center: 'true',
|
|
|
|
|
tracker_show_drift_insights: 'true',
|
feat(tracker): persist Overdue/Drift panel collapse + inline hide
The Overdue Command Center and Price-Change (drift) panels reset to
expanded on every load — their collapse arrows used local useState. Now
the collapsed state is remembered per-user via two new settings
(tracker_overdue_collapsed / tracker_drift_collapsed), mirroring the
search panel's useSearchPanelPreference pattern. Each panel header also
gains an inline "Hide" control (EyeOff, like the search panel) that writes
the existing tracker_show_* setting and fires an Undo toast; hidden
sections come back from Settings → Tracker Layout.
- services/userSettings.js: allowlist + defaults for the two collapse keys
- TrackerPage: read collapse state, hide handlers with Undo, wire props
- OverdueCommandCenter/DriftInsightPanel: controlled collapse + header hide
Verified: typecheck 0, lint 0 errors, build, and a live settings round-trip
(collapse persists, hide writes, Undo restores, allowlist rejects junk keys).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 10:34:39 -05:00
|
|
|
tracker_overdue_collapsed: 'false',
|
|
|
|
|
tracker_drift_collapsed: 'false',
|
2026-06-07 17:23:14 -05:00
|
|
|
tracker_table_columns: '["due","expected","previous","paid","paid_date","status","action","notes"]',
|
2026-06-14 19:21:34 -05:00
|
|
|
spending_show_ready_to_assign: 'true',
|
|
|
|
|
spending_show_avg: 'true',
|
|
|
|
|
spending_show_cover_overspend: 'true',
|
|
|
|
|
spending_group_categories: 'false',
|
2026-06-07 01:28:35 -05:00
|
|
|
};
|
|
|
|
|
|
2026-07-06 11:00:01 -05:00
|
|
|
function defaultUserSettings(): Record<string, any> {
|
|
|
|
|
const defaults: Record<string, any> = {};
|
2026-05-15 22:45:38 -05:00
|
|
|
for (const key of USER_SETTING_KEYS) {
|
2026-06-07 01:28:35 -05:00
|
|
|
defaults[key] = USER_SETTING_DEFAULTS[key] ?? getSetting(key);
|
2026-05-15 22:45:38 -05:00
|
|
|
}
|
|
|
|
|
return defaults;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-06 11:00:01 -05:00
|
|
|
function getUserSettings(userId: number): Record<string, any> {
|
|
|
|
|
const db: Db = getDb();
|
2026-05-15 22:45:38 -05:00
|
|
|
const settings = defaultUserSettings();
|
|
|
|
|
const rows = db.prepare(`
|
|
|
|
|
SELECT key, value
|
|
|
|
|
FROM user_settings
|
|
|
|
|
WHERE user_id = ?
|
|
|
|
|
`).all(userId);
|
|
|
|
|
|
|
|
|
|
for (const row of rows) {
|
|
|
|
|
if (USER_SETTING_KEYS.includes(row.key)) settings[row.key] = row.value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return settings;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-06 11:00:01 -05:00
|
|
|
function setUserSettings(userId: number, data: Record<string, any>): Record<string, any> {
|
|
|
|
|
const db: Db = getDb();
|
2026-05-15 22:45:38 -05:00
|
|
|
const upsert = db.prepare(`
|
|
|
|
|
INSERT INTO user_settings (user_id, key, value, updated_at)
|
|
|
|
|
VALUES (?, ?, ?, datetime('now'))
|
|
|
|
|
ON CONFLICT(user_id, key) DO UPDATE SET
|
|
|
|
|
value = excluded.value,
|
|
|
|
|
updated_at = datetime('now')
|
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
const save = db.transaction(() => {
|
|
|
|
|
for (const [key, value] of Object.entries(data || {})) {
|
|
|
|
|
if (USER_SETTING_KEYS.includes(key)) {
|
|
|
|
|
upsert.run(userId, key, String(value));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
save();
|
|
|
|
|
return getUserSettings(userId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
|
USER_SETTING_KEYS,
|
|
|
|
|
getUserSettings,
|
|
|
|
|
setUserSettings,
|
|
|
|
|
};
|