Reports

NAME

App::Moneymoor::Screen::Reports - the reports tab: the period's cash-flow strip and the bar chart of what it went on.

SYNOPSIS


use App::Moneymoor::Screen::Reports;

# Built by Screen::Main's content-host seam, never directly:
my $tab = App::Moneymoor::Screen::Reports.new(main => $main);
$tab.build($content-host);       # mounts the strip and the chart
$tab.install-subscriptions;      # digest / period / by-group β†’ repaint
$tab.install-keybinds;           # b

$tab.chart.data.head<label>;     # 'Β£412.50  Groceries'
$tab.by-group;                   # False β€” b flips it
$tab.summary.parent.sizing.value; # 3 β€” the strip fits on one line

DESCRIPTION

VBox (the content host)
     β”œ Border(fixed 3 or 4, 'March 2026 summary') β†’ RichText strip
     β”” Border(flex, 'Spending by category',
              bottom '+3 more…')                  β†’ BarChart (horizontal)

Every number on screen comes out of View::ReportRow; this file is the wiring. The tab is period-scoped and shares the budget tab's [ / ] navigation β€” the period lives in app/period, so switching tabs keeps the user where they were.

One pane takes focus, and it is the chart

mount-pane registers a pane's hint context and makes the first one mounted the tab's focus target. The summary strip is a caption for the chart, not a thing to drive, so it is added to the host directly (with its store pushed down by hand β€” a fixed-size child does not get set-store cascaded) and only the chart is mounted. That makes the chart the tab's focus target, the reports hint context's pane, and the widget b is bound on.

The chart is constructed focusable for exactly that reason. A BarChart has nothing to do with a keystroke of its own, but a pane that cannot take focus leaves the hint footer resolving to generic and the tab's one keybind unreachable.

The cut is a function of the pane's height

Horizontal bars do not scroll and do not compress below one row each, so a chart with more categories than rows draws the overflow on top of its own axis. The tab therefore asks View::ReportRow::top-spending for as many bars as it has rows for (less the one the value axis takes), and puts '+3 more…' in the pane's bottom title β€” where it costs no row, which is the whole problem being solved.

The height is read at every repaint rather than cached, and handle-resize re-runs the cut when a resize changes how many bars fit. Screen::Main owns the app's single App.on-resize callback and fans it out here (resize callbacks accumulate with no way to remove them, so a per-tab registration would leak one per visit).

The strip's box is as deep as the strip

The summary pane is three rows on any terminal wide enough for Β§4.6's five figures β€” frame, one line, frame β€” and four only when the strip has to wrap. A fourth row held in reserve for a wrap that never happens is a blank line inside a box, which reads as a bug rather than as spacing, and it is a row taken off the chart.

View::ReportRow::summary-rows-needed owns the rule and !fit-summary-height applies it, from both of the places the answer can change: the strip's own repaint (a period whose totals gained a digit) and a resize (the same strip in a narrower box). Applying it is Selkie::Widget::MultiLineInput's re-measure idiom β€” set-sizing followed by a dirty mark, because the parent layout re-reads a child's sizing on its next render and never on its own β€” with Widget.mark-screen-dirty as the mark, since the row that moves here is a row the chart below gains or loses.

The height is only touched when it actually changes; $!summary-rows-painted is to the strip what $!limit-painted is to the chart.

Why the digest is enough here

The register keys on ws/rev because neither change token can see a memo or a cleared state. Reports has the opposite problem and the opposite answer: every figure on this tab β€” spending, inflow, outflow, assigned β€” moves the derivation, so budget/digest covers all of them, and a payee rename that leaves the derivation identical genuinely changes nothing here.

METHODS

  • build($host) β€” construct and mount.

  • install-subscriptions() / install-keybinds() β€” the two seams Screen::Main::Subscriptions::install-content and Screen::Main::Keybinds::install-content call.

  • refresh() β€” repaint both panes; the subscription callback.

  • refresh-summary() / refresh-chart() β€” one pane each, and the two subscription callbacks. refresh-summary also fits the strip's box to the strip.

  • handle-resize(Int $rows, Int $cols) β€” re-fit the strip's box and re-cut the bars.

  • chart, summary, by-group, rows, hidden-count.

SEE ALSO

App::Moneymoor v0.4.2

YNAB-style envelope budgeting: a derivation engine

Authors

  • Matt Doughty

License

Artistic-2.0

Dependencies

DBIish:ver<0.6.7>:auth<zef:raku-community-modules>Notcurses::Native:ver<0.6.5+>:auth<zef:apogee>Selkie:ver<0.16.0+>:auth<zef:apogee>JSON::Fast:ver<0.19>:auth<cpan:TIMOTIMO>MacOS::NativeLib:ver<0.0.6>:auth<zef:lizmat>

Test Dependencies

Provides

  • App::Moneymoor
  • App::Moneymoor::Config
  • App::Moneymoor::DB
  • App::Moneymoor::Gateway::Account
  • App::Moneymoor::Gateway::Assignment
  • App::Moneymoor::Gateway::Category
  • App::Moneymoor::Gateway::Payee
  • App::Moneymoor::Gateway::Transaction
  • App::Moneymoor::Handlers::Boot
  • App::Moneymoor::Model::Account
  • App::Moneymoor::Model::Assignment
  • App::Moneymoor::Model::Category
  • App::Moneymoor::Model::CategoryGroup
  • App::Moneymoor::Model::Payee
  • App::Moneymoor::Model::Split
  • App::Moneymoor::Model::Transaction
  • App::Moneymoor::Screen::Accounts
  • App::Moneymoor::Screen::Budget
  • App::Moneymoor::Screen::Login
  • App::Moneymoor::Screen::Main
  • App::Moneymoor::Screen::Main::Keybinds
  • App::Moneymoor::Screen::Main::Modals
  • App::Moneymoor::Screen::Main::Subscriptions
  • App::Moneymoor::Screen::Reports
  • App::Moneymoor::Service::Budget
  • App::Moneymoor::Service::Icons
  • App::Moneymoor::Service::Target
  • App::Moneymoor::Service::Workspace
  • App::Moneymoor::StoreHandlers
  • App::Moneymoor::Theme
  • App::Moneymoor::Theme::Catppuccin
  • App::Moneymoor::Theme::Dracula
  • App::Moneymoor::Theme::Everforest
  • App::Moneymoor::Theme::Gruvbox
  • App::Moneymoor::Theme::Kanagawa
  • App::Moneymoor::Theme::Monokai
  • App::Moneymoor::Theme::Nord
  • App::Moneymoor::Theme::OneDark
  • App::Moneymoor::Theme::RosePine
  • App::Moneymoor::Theme::Solarized
  • App::Moneymoor::Theme::TokyoNight
  • App::Moneymoor::Themes
  • App::Moneymoor::UI
  • App::Moneymoor::Util::Money
  • App::Moneymoor::Util::Period
  • App::Moneymoor::View::BudgetRow
  • App::Moneymoor::View::EmptyState
  • App::Moneymoor::View::HintBar
  • App::Moneymoor::View::InspectorPane
  • App::Moneymoor::View::ModalChrome
  • App::Moneymoor::View::RegisterRow
  • App::Moneymoor::View::ReportRow
  • App::Moneymoor::Widget::BannerBar
  • App::Moneymoor::Widget::BootProgressModal

The Camelia image is copyright 2009 by Larry Wall. "Raku" is a trademark of the Yet Another Society. All rights reserved.

Built with Podlite β€” the markup and publishing tools behind this site.