Midday / apps /api /src /trpc /routers /widgets.ts
Jules
Final deployment with all fixes and verified content
c09f67c
import {
getAccountBalancesSchema,
getBillableHoursSchema,
getCashFlowSchema,
getCategoryExpensesSchema,
getCustomerLifetimeValueSchema,
getGrowthRateSchema,
getInboxStatsSchema,
getMonthlySpendingSchema,
getNetPositionSchema,
getOutstandingInvoicesSchema,
getOverdueInvoicesAlertSchema,
getProfitMarginSchema,
getRecurringExpensesSchema,
getRevenueSummarySchema,
getRunwaySchema,
getTaxSummarySchema,
getTrackedTimeSchema,
getVaultActivitySchema,
updateWidgetPreferencesSchema,
} from "@api/schemas/widgets";
import { createTRPCRouter, protectedProcedure } from "@api/trpc/init";
import { widgetPreferencesCache } from "@midday/cache/widget-preferences-cache";
import {
getBillableHours,
getCashBalance,
getCashFlow,
getCustomerLifetimeValue,
getGrowthRate,
getInboxStats,
getNetPosition,
getOutstandingInvoices,
getOverdueInvoicesAlert,
getProfitMargin,
getRecentDocuments,
getRecurringExpenses,
getReports,
getRunway,
getSpending,
getSpendingForPeriod,
getTaxSummary,
getTopRevenueClient,
getTrackedTime,
} from "@midday/db/queries";
export const widgetsRouter = createTRPCRouter({
getRunway: protectedProcedure
.input(getRunwaySchema)
.query(async ({ ctx: { db, teamId }, input }) => {
const runway = await getRunway(db, {
teamId: teamId!,
currency: input.currency,
});
return {
result: runway,
toolCall: {
toolName: "getBurnRateAnalysis",
toolParams: {
currency: input.currency,
},
},
};
}),
getTopCustomer: protectedProcedure.query(async ({ ctx: { db, teamId } }) => {
const topCustomer = await getTopRevenueClient(db, {
teamId: teamId!,
});
return {
result: topCustomer,
};
}),
getRevenueSummary: protectedProcedure
.input(getRevenueSummarySchema)
.query(async ({ ctx: { db, teamId }, input }) => {
const result = await getReports(db, {
teamId: teamId!,
from: input.from,
to: input.to,
currency: input.currency,
type: "revenue",
revenueType: input.revenueType,
});
return {
result: {
totalRevenue: result.summary.currentTotal,
currency: result.summary.currency,
revenueType: input.revenueType,
monthCount: result.result.length,
},
};
}),
getGrowthRate: protectedProcedure
.input(getGrowthRateSchema)
.query(async ({ ctx: { db, teamId }, input }) => {
const growthData = await getGrowthRate(db, {
teamId: teamId!,
from: input.from,
to: input.to,
currency: input.currency,
type: input.type,
revenueType: input.revenueType,
period: input.period,
});
return {
result: {
currentTotal: growthData.summary.currentTotal,
prevTotal: growthData.summary.previousTotal,
growthRate: growthData.summary.growthRate,
quarterlyGrowthRate: growthData.summary.periodGrowthRate,
currency: growthData.summary.currency,
type: growthData.summary.type,
revenueType: growthData.summary.revenueType,
period: growthData.summary.period,
trend: growthData.summary.trend,
meta: growthData.meta,
},
};
}),
getProfitMargin: protectedProcedure
.input(getProfitMarginSchema)
.query(async ({ ctx: { db, teamId }, input }) => {
const profitMarginData = await getProfitMargin(db, {
teamId: teamId!,
from: input.from,
to: input.to,
currency: input.currency,
revenueType: input.revenueType,
});
return {
result: {
totalRevenue: profitMarginData.summary.totalRevenue,
totalProfit: profitMarginData.summary.totalProfit,
profitMargin: profitMarginData.summary.profitMargin,
averageMargin: profitMarginData.summary.averageMargin,
currency: profitMarginData.summary.currency,
revenueType: profitMarginData.summary.revenueType,
trend: profitMarginData.summary.trend,
monthCount: profitMarginData.summary.monthCount,
monthlyData: profitMarginData.result,
meta: profitMarginData.meta,
},
};
}),
getCashFlow: protectedProcedure
.input(getCashFlowSchema)
.query(async ({ ctx: { db, teamId }, input }) => {
const cashFlowData = await getCashFlow(db, {
teamId: teamId!,
from: input.from,
to: input.to,
currency: input.currency,
period: input.period,
});
return {
result: {
netCashFlow: cashFlowData.summary.netCashFlow,
currency: cashFlowData.summary.currency,
period: cashFlowData.summary.period,
meta: cashFlowData.meta,
},
};
}),
getOutstandingInvoices: protectedProcedure
.input(getOutstandingInvoicesSchema)
.query(async ({ ctx: { db, teamId }, input }) => {
const invoicesData = await getOutstandingInvoices(db, {
teamId: teamId!,
currency: input.currency,
status: input.status,
});
return {
result: {
count: invoicesData.summary.count,
totalAmount: invoicesData.summary.totalAmount,
currency: invoicesData.summary.currency,
status: invoicesData.summary.status,
meta: invoicesData.meta,
},
};
}),
getInboxStats: protectedProcedure
.input(getInboxStatsSchema)
.query(async ({ ctx: { db, teamId }, input }) => {
const inboxStats = await getInboxStats(db, {
teamId: teamId!,
from: input.from,
to: input.to,
currency: input.currency,
});
return {
result: inboxStats.result,
};
}),
getTrackedTime: protectedProcedure
.input(getTrackedTimeSchema)
.query(async ({ ctx: { db, teamId, session }, input }) => {
const trackedTime = await getTrackedTime(db, {
teamId: teamId!,
assignedId: input.assignedId ?? session.user.id,
from: input.from,
to: input.to,
});
return {
result: trackedTime,
};
}),
getVaultActivity: protectedProcedure
.input(getVaultActivitySchema)
.query(async ({ ctx: { db, teamId }, input }) => {
const vaultActivity = await getRecentDocuments(db, {
teamId: teamId!,
limit: input.limit,
});
return {
result: vaultActivity,
};
}),
getAccountBalances: protectedProcedure
.input(getAccountBalancesSchema)
.query(async ({ ctx: { db, teamId }, input }) => {
const accountBalances = await getCashBalance(db, {
teamId: teamId!,
currency: input.currency,
});
return {
result: accountBalances,
};
}),
getNetPosition: protectedProcedure
.input(getNetPositionSchema)
.query(async ({ ctx: { db, teamId }, input }) => {
const netPosition = await getNetPosition(db, {
teamId: teamId!,
currency: input.currency,
});
return {
result: netPosition,
};
}),
getMonthlySpending: protectedProcedure
.input(getMonthlySpendingSchema)
.query(async ({ ctx: { db, teamId }, input }) => {
const spending = await getSpendingForPeriod(db, {
teamId: teamId!,
from: input.from,
to: input.to,
currency: input.currency,
});
return {
result: spending,
toolCall: {
toolName: "getSpendingAnalysis",
toolParams: {
from: input.from,
to: input.to,
currency: input.currency,
},
},
};
}),
getRecurringExpenses: protectedProcedure
.input(getRecurringExpensesSchema)
.query(async ({ ctx: { db, teamId }, input }) => {
const recurringExpenses = await getRecurringExpenses(db, {
teamId: teamId!,
from: input.from,
to: input.to,
currency: input.currency,
});
return {
result: recurringExpenses,
};
}),
getTaxSummary: protectedProcedure
.input(getTaxSummarySchema)
.query(async ({ ctx: { db, teamId }, input }) => {
// Get both paid and collected taxes
const [paidTaxes, collectedTaxes] = await Promise.all([
getTaxSummary(db, {
teamId: teamId!,
type: "paid",
from: input.from,
to: input.to,
currency: input.currency,
}),
getTaxSummary(db, {
teamId: teamId!,
type: "collected",
from: input.from,
to: input.to,
currency: input.currency,
}),
]);
return {
result: {
paid: paidTaxes.summary,
collected: collectedTaxes.summary,
currency: paidTaxes.summary.currency || input.currency || "USD",
},
};
}),
getCategoryExpenses: protectedProcedure
.input(getCategoryExpensesSchema)
.query(async ({ ctx: { db, teamId }, input }) => {
const categoryExpenses = await getSpending(db, {
teamId: teamId!,
from: input.from,
to: input.to,
currency: input.currency,
});
// Get top N categories by amount
const topCategories = categoryExpenses
.sort((a, b) => b.amount - a.amount)
.slice(0, input.limit || 5);
const totalAmount = topCategories.reduce(
(sum, cat) => sum + cat.amount,
0,
);
return {
result: {
categories: topCategories,
totalAmount,
currency: topCategories[0]?.currency || input.currency || "USD",
totalCategories: categoryExpenses.length,
},
};
}),
getOverdueInvoicesAlert: protectedProcedure
.input(getOverdueInvoicesAlertSchema)
.query(async ({ ctx: { db, teamId }, input }) => {
const overdueData = await getOverdueInvoicesAlert(db, {
teamId: teamId!,
currency: input?.currency,
});
return {
result: overdueData.summary,
};
}),
getBillableHours: protectedProcedure
.input(getBillableHoursSchema)
.query(async ({ ctx: { db, teamId }, input }) => {
return getBillableHours(db, {
teamId: teamId!,
date: input.date,
view: input.view,
weekStartsOnMonday: input.weekStartsOnMonday,
});
}),
getCustomerLifetimeValue: protectedProcedure
.input(getCustomerLifetimeValueSchema)
.query(async ({ ctx: { db, teamId }, input }) => {
const result = await getCustomerLifetimeValue(db, {
teamId: teamId!,
currency: input.currency,
});
return {
result,
toolCall: {
toolName: "getCustomerLifetimeValue",
toolParams: {
currency: input.currency,
},
},
};
}),
getWidgetPreferences: protectedProcedure.query(
async ({ ctx: { teamId, session } }) => {
const preferences = await widgetPreferencesCache.getWidgetPreferences(
teamId!,
session.user.id,
);
return preferences;
},
),
updateWidgetPreferences: protectedProcedure
.input(updateWidgetPreferencesSchema)
.mutation(async ({ ctx: { teamId, session }, input }) => {
const preferences = await widgetPreferencesCache.updatePrimaryWidgets(
teamId!,
session.user.id,
input.primaryWidgets,
);
return preferences;
}),
});