| import { RedisCache } from "./redis-client"; |
|
|
| export const WIDGET_TYPES = [ |
| |
| "runway", |
| "cash-flow", |
| "account-balances", |
| "profit-analysis", |
| "revenue-forecast", |
| "revenue-summary", |
| "growth-rate", |
|
|
| |
| "net-position", |
|
|
| |
| "customer-lifetime-value", |
| "top-customer", |
|
|
| |
| "outstanding-invoices", |
| "overdue-invoices-alert", |
| "invoice-payment-score", |
|
|
| |
| "monthly-spending", |
| "recurring-expenses", |
| "category-expenses", |
|
|
| |
| "profit-margin", |
|
|
| |
| "time-tracker", |
| "billable-hours", |
|
|
| |
| "inbox", |
| "vault", |
| "tax-summary", |
| ] as const; |
|
|
| export type WidgetType = (typeof WIDGET_TYPES)[number]; |
|
|
| export interface WidgetPreferences { |
| primaryWidgets: WidgetType[]; |
| availableWidgets: WidgetType[]; |
| } |
|
|
| export const DEFAULT_WIDGET_ORDER: WidgetType[] = [...WIDGET_TYPES]; |
|
|
| export const DEFAULT_WIDGET_PREFERENCES: WidgetPreferences = { |
| primaryWidgets: DEFAULT_WIDGET_ORDER.slice(0, 7), |
| availableWidgets: DEFAULT_WIDGET_ORDER.slice(7), |
| }; |
|
|
| class WidgetPreferencesCache extends RedisCache { |
| constructor() { |
| super("widget-preferences"); |
| } |
|
|
| private getWidgetPreferencesKey(teamId: string, userId: string): string { |
| return `${teamId}:${userId}`; |
| } |
|
|
| async getWidgetPreferences( |
| teamId: string, |
| userId: string, |
| ): Promise<WidgetPreferences> { |
| const key = this.getWidgetPreferencesKey(teamId, userId); |
| const preferences = await this.get<WidgetPreferences>(key); |
|
|
| if (!preferences) { |
| |
| return DEFAULT_WIDGET_PREFERENCES; |
| } |
|
|
| |
| const allWidgets = [ |
| ...preferences.primaryWidgets, |
| ...preferences.availableWidgets, |
| ]; |
| const missingWidgets = DEFAULT_WIDGET_ORDER.filter( |
| (widget) => !allWidgets.includes(widget), |
| ); |
| const extraWidgets = allWidgets.filter( |
| (widget) => !DEFAULT_WIDGET_ORDER.includes(widget), |
| ); |
|
|
| |
| if (missingWidgets.length > 0 || extraWidgets.length > 0) { |
| console.info( |
| `Migrating widget preferences for team ${teamId}, user ${userId}. Missing: ${missingWidgets.join(", ") || "none"}, Extra: ${extraWidgets.join(", ") || "none"}`, |
| ); |
|
|
| |
| const migratedPrimaryWidgets = preferences.primaryWidgets.filter( |
| (widget) => !extraWidgets.includes(widget), |
| ); |
|
|
| const migratedAvailableWidgets = preferences.availableWidgets.filter( |
| (widget) => !extraWidgets.includes(widget), |
| ); |
|
|
| |
| const updatedAvailableWidgets = [ |
| ...migratedAvailableWidgets, |
| ...missingWidgets, |
| ]; |
|
|
| const migratedPreferences: WidgetPreferences = { |
| primaryWidgets: migratedPrimaryWidgets, |
| availableWidgets: updatedAvailableWidgets, |
| }; |
|
|
| |
| await this.setWidgetPreferences(teamId, userId, migratedPreferences); |
|
|
| return migratedPreferences; |
| } |
|
|
| return preferences; |
| } |
|
|
| async setWidgetPreferences( |
| teamId: string, |
| userId: string, |
| preferences: WidgetPreferences, |
| ): Promise<void> { |
| |
| const allWidgets = [ |
| ...preferences.primaryWidgets, |
| ...preferences.availableWidgets, |
| ]; |
|
|
| |
| if (allWidgets.length !== DEFAULT_WIDGET_ORDER.length) { |
| throw new Error( |
| "Invalid widget preferences: incorrect number of widgets", |
| ); |
| } |
|
|
| |
| const missingWidgets = DEFAULT_WIDGET_ORDER.filter( |
| (widget) => !allWidgets.includes(widget), |
| ); |
| const extraWidgets = allWidgets.filter( |
| (widget) => !DEFAULT_WIDGET_ORDER.includes(widget), |
| ); |
|
|
| if (missingWidgets.length > 0) { |
| throw new Error( |
| `Invalid widget preferences: missing widgets ${missingWidgets.join(", ")}`, |
| ); |
| } |
|
|
| if (extraWidgets.length > 0) { |
| throw new Error( |
| `Invalid widget preferences: unknown widgets ${extraWidgets.join(", ")}`, |
| ); |
| } |
|
|
| |
| if (preferences.primaryWidgets.length > 7) { |
| throw new Error( |
| "Invalid widget preferences: primary widgets cannot exceed 7", |
| ); |
| } |
|
|
| |
| const duplicates = allWidgets.filter( |
| (widget, index) => allWidgets.indexOf(widget) !== index, |
| ); |
| if (duplicates.length > 0) { |
| throw new Error( |
| `Invalid widget preferences: duplicate widgets ${duplicates.join(", ")}`, |
| ); |
| } |
|
|
| const key = this.getWidgetPreferencesKey(teamId, userId); |
| await this.set(key, preferences); |
| } |
|
|
| async updatePrimaryWidgets( |
| teamId: string, |
| userId: string, |
| newPrimaryWidgets: WidgetType[], |
| ): Promise<WidgetPreferences> { |
| if (newPrimaryWidgets.length > 7) { |
| throw new Error("Primary widgets cannot exceed 7"); |
| } |
|
|
| const _currentPreferences = await this.getWidgetPreferences(teamId, userId); |
|
|
| |
| const availableWidgets = DEFAULT_WIDGET_ORDER.filter( |
| (widget) => !newPrimaryWidgets.includes(widget), |
| ); |
|
|
| const newPreferences: WidgetPreferences = { |
| primaryWidgets: newPrimaryWidgets, |
| availableWidgets, |
| }; |
|
|
| await this.setWidgetPreferences(teamId, userId, newPreferences); |
| return newPreferences; |
| } |
| } |
|
|
| export const widgetPreferencesCache = new WidgetPreferencesCache(); |
|
|