Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat: Add dashboard view for freelance business insights
- Implemented DashboardView to render KPI cards, revenue charts, project budget progress, and financial goals.
- Created KPI card component for displaying key metrics.
- Developed vertical bar chart for revenue visualization.
- Added project budget row component to track budget utilization.
- Integrated financial goals tracking with progress indicators.
- Enhanced home view to include insights menu with dashboard navigation.
- Introduced revenue forecasting and KPI calculation modules.
- Added financial goal model and migration for database integration.
- Implemented tests for forecasting and KPI functionalities.
  • Loading branch information
clstaudt committed Mar 12, 2026
commit 34576ac3e90c081ce764fa54e05b79780e6636ca
2 changes: 2 additions & 0 deletions tuttle/app/dashboard/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .intent import DashboardIntent
from .view import DashboardView
114 changes: 114 additions & 0 deletions tuttle/app/dashboard/intent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
"""Business logic for the dashboard view."""


from ..core.abstractions import SQLModelDataSourceMixin, Intent
from ..core.intent_result import IntentResult

from ...model import Contract, Invoice, Project, FinancialGoal
from ...kpi import compute_kpis, monthly_revenue_breakdown, project_budget_status
from ...forecasting import revenue_curve


class DashboardIntent(SQLModelDataSourceMixin, Intent):
"""Gathers data for the freelance business dashboard."""

def __init__(self):
SQLModelDataSourceMixin.__init__(self)

def get_kpis(self) -> IntentResult:
"""Compute KPI summary from all invoices, contracts, projects."""
try:
invoices = self.query(Invoice)
contracts = self.query(Contract)
projects = self.query(Project)
kpis = compute_kpis(invoices, contracts, projects)
return IntentResult(was_intent_successful=True, data=kpis)
except Exception as e:
return IntentResult(
was_intent_successful=False,
error_msg="Failed to compute KPIs.",
log_message=f"DashboardIntent.get_kpis: {e}",
exception=e,
)

def get_monthly_revenue(self, n_months: int = 12) -> IntentResult:
"""Get monthly revenue breakdown for the last n months."""
try:
invoices = self.query(Invoice)
data = monthly_revenue_breakdown(invoices, n_months=n_months)
return IntentResult(was_intent_successful=True, data=data)
except Exception as e:
return IntentResult(
was_intent_successful=False,
error_msg="Failed to load monthly revenue.",
log_message=f"DashboardIntent.get_monthly_revenue: {e}",
exception=e,
)

def get_revenue_curve(self, forecast_months: int = 6) -> IntentResult:
"""Get combined historical + forecast revenue curve."""
try:
invoices = self.query(Invoice)
contracts = self.query(Contract)
data = revenue_curve(invoices, contracts, forecast_months=forecast_months)
return IntentResult(was_intent_successful=True, data=data)
except Exception as e:
return IntentResult(
was_intent_successful=False,
error_msg="Failed to generate revenue forecast.",
log_message=f"DashboardIntent.get_revenue_curve: {e}",
exception=e,
)

def get_project_budgets(self) -> IntentResult:
"""Get budget utilization for all projects."""
try:
projects = self.query(Project)
data = project_budget_status(projects)
return IntentResult(was_intent_successful=True, data=data)
except Exception as e:
return IntentResult(
was_intent_successful=False,
error_msg="Failed to load project budgets.",
log_message=f"DashboardIntent.get_project_budgets: {e}",
exception=e,
)

def get_financial_goals(self) -> IntentResult:
"""Load all financial goals."""
try:
goals = self.query(FinancialGoal)
return IntentResult(was_intent_successful=True, data=goals)
except Exception as e:
return IntentResult(
was_intent_successful=False,
error_msg="Failed to load financial goals.",
log_message=f"DashboardIntent.get_financial_goals: {e}",
exception=e,
)

def save_financial_goal(self, goal: FinancialGoal) -> IntentResult:
"""Save a financial goal."""
try:
self.store(goal)
return IntentResult(was_intent_successful=True, data=goal)
except Exception as e:
return IntentResult(
was_intent_successful=False,
error_msg="Failed to save financial goal.",
log_message=f"DashboardIntent.save_financial_goal: {e}",
exception=e,
)

def delete_financial_goal(self, goal_id: int) -> IntentResult:
"""Delete a financial goal by ID."""
try:
self.delete_by_id(FinancialGoal, goal_id)
return IntentResult(was_intent_successful=True, data=None)
except Exception as e:
return IntentResult(
was_intent_successful=False,
error_msg="Failed to delete financial goal.",
log_message=f"DashboardIntent.delete_financial_goal: {e}",
exception=e,
)
Loading
Loading