my $hold_handle_result;
$hold_handle_result = $self->handle_hold_update($action) if $action;
- my $holds_object = $self->fetch_user_holds($hold_id ? [$hold_id] : undef, 0, 1, $available, $limit, $offset);
+ my $holds_object;
+ if ($self->cgi->param('sort') ne "") {
+ $holds_object = $self->fetch_user_holds($hold_id ? [$hold_id] : undef, 0, 1, $available);
+ }
+ else {
+ $holds_object = $self->fetch_user_holds($hold_id ? [$hold_id] : undef, 0, 1, $available, $limit, $offset);
+ }
+
if($holds_object->{holds}) {
$ctx->{holds} = $holds_object->{holds};
}
$ctx->{circ_history_limit} = $limit;
$ctx->{circ_history_offset} = $offset;
- my $circ_ids = $e->json_query({
+ my $circ_ids;
+ if ($self->cgi->param('sort') ne "") { # Defer limitation to circ_history.tt2
+ $circ_ids = $e->json_query({
+ select => {
+ au => [{
+ column => 'id',
+ transform => 'action.usr_visible_circs',
+ result_field => 'id'
+ }]
+ },
+ from => 'au',
+ where => {id => $e->requestor->id}
+ });
+
+ } else {
+ $circ_ids = $e->json_query({
select => {
au => [{
column => 'id',
where => {id => $e->requestor->id},
limit => $limit,
offset => $offset
- });
+ });
+ }
$ctx->{circs} = $self->fetch_user_circs(1, [map { $_->{id} } @$circ_ids]);
return Apache2::Const::OK;
padding: 0;
vertical-align: middle;
}
+
+.sort_deemphasize {
+ font-weight: lighter;
+ font-size: 70%;
+}
+
[% PROCESS "opac/parts/header.tt2";
PROCESS "opac/parts/misc_util.tt2";
+ PROCESS "opac/parts/myopac/column_sort_support.tt2";
WRAPPER "opac/parts/myopac/base.tt2";
myopac_page = "circs"
limit = ctx.circ_history_limit;
<div id="acct_checked_tabs">
<div class="align">
- <a href='[% mkurl('circs') %]'>[% l("Current Items Checked Out") %]</a>
+ <a href='[% mkurl('circs',{},1) %]'>[% l("Current Items Checked Out") %]</a>
</div>
<div class="align selected">
<a href="#">[% l("Check Out History") %]</a>
</div>
</div>
+ [%
+ # In the sorting case, the size is the size of ALL the circ items. In the non-sorting case,
+ # the size is simply the size of the chunk passed in. See the TODO below for the still-lingering
+ # bug.
+ sort_field = CGI.param('sort');
+ IF (sort_field);
+ no_next = ctx.circs.size - offset <= limit;
+ ELSE;
+ no_next = ctx.circs.size < limit;
+ END;
+ %]
+
<div class="header_middle">
<span class="float-left">[% l('Previously Checked Out Items') %]</span>
<span class='float-left' style='padding-left: 10px;'>
[% IF offset == 0 %] class='invisible' [% END %]><span class="nav_arrow_fix">◄</span>[% l('Previous') %]</a>
[%# TODO: get total to prevent paging off then end of the list.. %]
<a href='[% mkurl('circ_history', {limit => limit, offset => (offset + limit)}) %]'
- [% IF ctx.circs.size < limit %] class='invisible' [% END %] >[% l('Next') %]<span class="nav_arrow_fix">►</span></a>
+ [% IF no_next %] class='invisible' [% END %] >[% l('Next') %]<span class="nav_arrow_fix">►</span></a>
</span>
<span class="float-left">
<form action="[% mkurl(ctx.opac_root _ '/myopac/circ_history/export') %]" method="POST">
<table id="acct_checked_main_header">
<thead>
<tr>
- <th>[% l('Title / Author') %]</th>
- <th>[% l('Checkout Date') %]</th>
- <th>[% l('Due Date') %]</th>
- <th>[% l('Date Returned') %]</th>
- <th>[% l('Barcode') %]</th>
- <th>[% l('Call Number') %]</th>
+ <th>[% sort_head("sort_title", "Title") %]</th>
+ <th>[% sort_head("author", "Author") %]</th>
+ <th>[% sort_head("checkout", "Checkout Date") %]</th>
+ <th>[% sort_head("due", "Due Date") %]</th>
+ <th>[% sort_head("returned", "Date Returned") %]</th>
+ <th>[% sort_head("barcode", "Barcode") %]</th>
+ <th>[% sort_head("callnum", "Call Number") %]</th>
</tr>
</thead>
<tbody>
- [% FOR circ IN ctx.circs;
- attrs = {marc_xml => circ.marc_xml};
- PROCESS get_marc_attrs args=attrs; %]
+ [%# Copy the ctx.circs into a local array, then add a SORT field
+ that contains the value to sort on. Since we need the item attrs,
+ invoke it and save the result in ATTRS.
+ %]
+ [%
+ circ_items = ctx.circs; # Array assignment
+
+ FOR circ IN circ_items;
+ circ.ATTRS = {marc_xml => circ.marc_xml};
+ PROCESS get_marc_attrs args=circ.ATTRS;
+
+ SWITCH sort_field;
+
+ CASE "sort_title";
+ circ.SORTING = circ.ATTRS.sort_title;
+
+ CASE "author";
+ circ.SORTING = circ.ATTRS.author;
+
+ CASE "checkout";
+ circ.SORTING = circ.circ.xact_start;
+
+ CASE "due";
+ circ.SORTING = circ.circ.due_date;
+
+ CASE "returned";
+ circ.SORTING = circ.circ.checkin_time;
+
+ CASE "barcode";
+ circ.SORTING = circ.circ.target_copy.barcode;
+
+ CASE "callnum";
+ circ.SORTING = circ.circ.target_copy.call_number.label;
+
+ CASE;
+ sort_field = "";
+ END; # SWITCH
+ END; #FOR circ
+
+ IF (sort_field != "sort_title");
+ deemphasize_class = "";
+ ELSE;
+ deemphasize_class = " class=\"sort_deemphasize\"";
+ END;
+
+ # Apply sorting to circ_items
+ IF (sort_field);
+ circ_items = circ_items.sort("SORTING");
+ IF (CGI.param("sort_type") == "desc");
+ circ_items = circ_items.reverse;
+ END;
+
+ # Shorten the circ_items list per offset/limit/cout
+ hi = offset + limit - 1;
+ hi = hi > circ_items.max ? circ_items.max : hi;
+
+ circ_items = circ_items.slice(offset, hi);
+ END;
+
+ # circ_items list is now sorted. Traverse and dump the information.
+
+ FOR circ IN circ_items; %]
<tr>
<td>
- <a href="[% mkurl(ctx.opac_root _ '/record/' _ circ.circ.target_copy.call_number.record.id) %]"
- name="[% l('Catalog record') %]">[% attrs.title | html %]</a>
- [% IF attrs.author %] /
+ <a href="[% mkurl(ctx.opac_root _ '/record/' _
+ circ.circ.target_copy.call_number.record.id, {}, 1) %]"
+ name="[% l('Catalog record') %]"><span[%- deemphasize_class -%]>
+ [%- circ.ATTRS.title.substr(0,circ.ATTRS.nonfiling_characters) | html %]</span>
+ [%- circ.ATTRS.title.substr(circ.ATTRS.nonfiling_characters) | html %]</a>
+ </td>
+ <td>
<a href="[% mkurl(ctx.opac_root _ '/results',
- {qtype => 'author', query => attrs.author.replace('[,\.:;]', '')}
- )%]">[% attrs.author | html %]</a>
- [% END %]
+ {qtype => 'author', query => circ.ATTRS.author.replace('[,\.:;]', '')},
+ 1
+ ) %]">[% circ.ATTRS.author | html %]</a>
</td>
<td>
[% date.format(ctx.parse_datetime(circ.circ.xact_start),DATE_FORMAT); %]
[% date.format(ctx.parse_datetime(circ.circ.due_date),DATE_FORMAT); %]
</td>
<td>
- [%
- IF circ.circ.checkin_time;
+ [% IF circ.circ.checkin_time;
date.format(ctx.parse_datetime(circ.circ.checkin_time),DATE_FORMAT);
ELSE; %]
<span style='color:blue;'>*</span><!-- meh -->
[% PROCESS "opac/parts/header.tt2";
PROCESS "opac/parts/misc_util.tt2";
+ PROCESS "opac/parts/myopac/column_sort_support.tt2";
WRAPPER "opac/parts/myopac/base.tt2";
myopac_page = "circs" %]
<div id='myopac_checked_div'>
<a href="#">[% l("Current Items Checked Out") %]</a>
</div>
<div class="align">
- <a href="[% mkurl('circ_history') %]">[% l("Check Out History") %]</a>
+ <a href="[% mkurl('circ_history',{},1) %]">[% l("Check Out History") %]</a>
</div>
</div>
onclick="var inputs=document.getElementsByTagName('input'); for (i = 0; i < inputs.length; i++) { if (inputs[i].name == 'circ' && !inputs[i].disabled) inputs[i].checked = this.checked;}"
/>
</th>
- <th>[% l('Title / Author') %]</th>
- <th>[% l('Renewals Left') %]</th>
- <th>[% l('Due Date') %]</th>
- <th>[% l('Barcode') %]</th>
- <th>[% l('Call number') %]</th>
+ <th>[% sort_head("sort_title", "Title") %]</th>
+ <th>[% sort_head("author", "Author") %]</th>
+ <th>[% sort_head("renews", "Renewals Left") %]</th>
+ <th>[% sort_head("due", "Due Date") %]</th>
+ <th>[% sort_head("barcode", "Barcode") %]</th>
+ <th>[% sort_head("callnum", "Call number") %]</th>
</tr>
</thead>
<tbody>
- [% FOR circ IN ctx.circs;
- attrs = {marc_xml => circ.marc_xml};
- PROCESS get_marc_attrs args=attrs; %]
+ [%# Copy the ctx.circs into a local array, then add a SORT field
+ that contains the value to sort on. Since we need the item attrs,
+ invoke it and save the result in ATTRS.
+ %]
+ [%
+ circ_items = ctx.circs; # Array assignment
+
+ sort_field = CGI.param('sort'); # unless changed below...
+
+ FOR circ IN circ_items;
+ circ.ATTRS = {marc_xml => circ.marc_xml};
+ PROCESS get_marc_attrs args=circ.ATTRS;
+
+ SWITCH sort_field;
+
+ CASE "sort_title";
+ circ.SORTING = circ.ATTRS.sort_title;
+
+ CASE "author";
+ circ.SORTING = circ.ATTRS.author;
+
+ CASE "renews";
+ circ.SORTING = circ.circ.renewal_remaining;
+
+ CASE "due";
+ circ.SORTING = circ.circ.due_date;
+
+ CASE "barcode";
+ circ.SORTING = circ.circ.target_copy.barcode;
+
+ CASE "callnum";
+ circ.SORTING = circ.circ.target_copy.call_number.label;
+
+ CASE;
+ sort_field = "";
+ END; # SWITCH
+ END; #FOR circ
+
+ IF (sort_field != "sort_title");
+ deemphasize_class = "";
+ ELSE;
+ deemphasize_class = " class=\"sort_deemphasize\"";
+ END;
+
+ # Apply sorting to circ_items
+ IF (sort_field);
+ circ_items = circ_items.sort("SORTING");
+ IF (CGI.param("sort_type") == "desc");
+ circ_items = circ_items.reverse;
+ END;
+ END;
+
+ # circ_items list is now sorted. Traverse and dump the information.
+
+ FOR circ IN circ_items; %]
<tr>
<td class="checkbox_column" valign="top">
<input type="checkbox" name="circ"
[% IF circ.circ.renewal_remaining < 1 %] disabled="disabled" [% END %]
value="[% circ.circ.id %]" />
</td>
- <td name="author">
+ <td name="title">
<a href="[% mkurl(ctx.opac_root _ '/record/' _
- circ.circ.target_copy.call_number.record.id) %]"
- name="[% l('Catalog record') %]">[% attrs.title | html %]</a>
- [% IF attrs.author %] /
+ circ.circ.target_copy.call_number.record.id, {}, 1) %]"
+ name="[% l('Catalog record') %]"><span[%- deemphasize_class -%]>
+ [%- circ.ATTRS.title.substr(0,circ.ATTRS.nonfiling_characters) | html %]</span>
+ [%- circ.ATTRS.title.substr(circ.ATTRS.nonfiling_characters) | html %]</a>
+ </td>
+ <td name="author">
<a href="[% mkurl(ctx.opac_root _ '/results',
- {qtype => 'author', query => attrs.author.replace('[,\.:;]', '')}
- ) %]">[% attrs.author | html %]</a>
- [% END %]
+ {qtype => 'author', query => circ.ATTRS.author.replace('[,\.:;]', '')},
+ 1
+ ) %]">[% circ.ATTRS.author | html %]</a>
</td>
<td name="renewals" align="center">
[% circ.circ.renewal_remaining %]
</span>
</td>
</tr>
- [% END;
+ [% END; # FOR
+
END %]
</table>
</div>
[% PROCESS "opac/parts/header.tt2";
PROCESS "opac/parts/misc_util.tt2";
PROCESS "opac/parts/hold_status.tt2";
+ PROCESS "opac/parts/myopac/column_sort_support.tt2";
WRAPPER "opac/parts/myopac/base.tt2";
myopac_page = "holds";
limit = ctx.holds_limit;
<a href='#'>[% l("Items on Hold") %]</a>
</div>
<div class="align">
- <a href='[% mkurl('hold_history', {}, ['limit','offset','available']) %]'>[% l("Holds History") %]</a>
+ <a href='[% mkurl('hold_history', {}, ['limit','offset','available','sort','sort_type']) %]'>[% l("Holds History") %]</a>
</div>
</div>
[% IF count <= limit + offset %] class='invisible' [% END %] >[% l('Next') %]<span class="nav_arrow_fix">►</span></a>
</span>
- <span style="float:right;">
- <a class="hide_me" href="#">[% l('Export List') %]</a>
- </span>
</div>
<div class="clear-both"></div>
<div id='holds_main'>
<a href="[% mkurl('holds',{available => 1},['limit','offset']) %]">[% l("Show only available") %]</a>
[% END %]
[% l("holds") %]
- <select class="hide_me" id="holds_sort">
- <option value="">[% l('-- Sort By --') %]</option>
- <option value="title">[% l('Title') %]</option>
- <option value="pickup">[% l('PickUp Location') %]</option>
- <option value="status">[% l('Status') %]</option>
- </select>
</td>
</tr>
</table>
[% IF ctx.holds.size < 1 %]
<div class="warning_box">[% l('No holds found.') %]</div>
[% ELSE %]
- <div class="hide_me">
- <select id="hold_pickup_lib_temp" name="hold_pickup_lib_sel" class="hide_me"></select>
- </div>
<table id="acct_holds_main_header" cellpadding='0' cellspacing='0' border='0'>
<thead>
<tr>
<th align="center">
<input type="checkbox" onclick="var inputs=document.getElementsByTagName('input'); for (i = 0; i < inputs.length; i++) { if (inputs[i].name == 'hold_id' && !inputs[i].disabled) inputs[i].checked = this.checked;}"/>
</th>
- <th>[% l('Title') %]</th>
- <th>[% l('Author') %]</th>
- <th>[% l('Format') %]</th>
+ <th>[% sort_head("sort_title", "Title") %]</th>
+ <th>[% sort_head("author", "Author") %]</th>
+ <th>[% sort_head("format", "Format") %]</th>
<th>[% l('Pickup Location') %]</th>
<th>[% l('Activate') %]</th>
<th>[% l('Cancel if not filled by') %]</th>
</tr>
</thead>
<tbody id="holds_temp_parent">
- [% FOR hold IN ctx.holds;
- attrs = {marc_xml => hold.marc_xml};
- PROCESS get_marc_attrs args=attrs;
+
+ [%# Copy the ctx.holds into a local array, then add a SORT field
+ that contains the value to sort on. Since we need the item attrs,
+ invoke it and save the result in ATTRS.
+ %]
+ [%
+ hold_items = ctx.holds;
+
+ sort_field = CGI.param('sort');
+
+ FOR hold IN hold_items;
+ hold.ATTRS = {marc_xml => hold.marc_xml};
+ PROCESS get_marc_attrs args=hold.ATTRS;
+
+ SWITCH sort_field;
+
+ CASE "sort_title";
+ hold.SORTING = hold.ATTRS.sort_title;
+
+ CASE "author";
+ hold.SORTING = hold.ATTRS.author;
+
+ CASE "format";
+ hold.SORTING = hold.ATTRS.format_label;
+
+ CASE;
+ sort_field = "";
+ END; # SWITCH
+ END; #FOR hold
+
+ IF (sort_field != "sort_title");
+ deemphasize_class = "";
+ ELSE;
+ deemphasize_class = " class=\"sort_deemphasize\"";
+ END;
+
+ # Apply sorting to hold_items
+ IF (sort_field != "");
+ hold_items = hold_items.sort("SORTING");
+ IF (CGI.param("sort_type") == "desc");
+ hold_items = hold_items.reverse;
+ END;
+
+ # Shorten the hold_items list per offset/limit/count
+ hi = offset + limit - 1;
+ hi = hi > hold_items.max ? hold_items.max : hi;
+
+ hold_items = hold_items.slice(offset, hi);
+ END;
+
+ # hold_items list is now sorted. Traverse and dump the information.
+
+ FOR hold IN hold_items;
ahr = hold.hold.hold %]
<tr id="acct_holds_temp" name="acct_holds_temp"
class="acct_holds_temp[% ahr.frozen == 't' ? ' inactive-hold' : '' %]">
</td>
<td>
<div>
- [%
- title = attrs.title;
- IF ahr.hold_type == 'P';
- title = l('[_1] ([_2])', title, hold.hold.part.label);
- END;
- %]
- <a href="[% mkurl(ctx.opac_root _ '/record/' _ hold.hold.bre_id) %]">[% title | html %]</a>
+ [% title = hold.ATTRS.title;
+ IF ahr.hold_type == 'P';
+ title = l('[_1] ([_2])', title, hold.hold.part.label);
+ END; %]
+
+ <a href="[% mkurl(ctx.opac_root _ '/record/' _
+ hold.hold.bre_id, {}, 1) %]"
+ name="[% l('Catalog record') %]"><span[%- deemphasize_class -%]>
+ [%- title.substr(0,hold.ATTRS.nonfiling_characters) | html %]</span>
+ [%- title.substr(hold.ATTRS.nonfiling_characters) | html %]</a>
</div>
</td>
<td>
<div>
<a href="[% mkurl(ctx.opac_root _ '/results',
- {qtype => 'author', query => attrs.author.replace('[,\.:;]', '')}
- ) %]">[% attrs.author | html %]</a>
+ {qtype => 'author', query => hold.ATTRS.author.replace('[,\.:;]', '')},
+ 1
+ ) %]">[% hold.ATTRS.author | html %]</a>
</div>
</td>
<td>
<div class="format_icon">
- [% IF attrs.format_icon %]
- <img title="[% attrs.format_label | html %]" alt="[% attrs.format_label | html %]" src="[% attrs.format_icon %]" />
+ [% IF hold.ATTRS.format_icon %]
+ <img title="[% hold.ATTRS.format_label | html %]" alt="[% hold.ATTRS.format_label | html %]" src="[% hold.ATTRS.format_icon %]" />
[% END %]
</div>
</td>
titresults = xml.findnodes('//*[@tag="245"]/*[@code="a" or @code="b" or @code="n" or @code="p"]');
titresults_content = [];
FOR sub IN titresults; titresults_content.push(sub.textContent); END;
+
args.title = titresults_content.join(" ");
# Avoid ugly trailing syntax on brief titles
args.title = args.title | replace('[:;/]$', '');
args.titles.push(title.primary.value);
END;
args.title_extended = (args.titles.size) ? args.titles.0 : '';
+ # Create a version of the title designed for sorted displays.
+ args.sort_title = args.title | upper;
+
+ # If the title has a "non-filing characters"
+ # (to logically remove leading "The " for example)
+ # chop the title. Otherwise, chop until the first alphanumeric.
+ # BTW: Template Toolkit folds 1-element arrays to scalars!
+ title_node = xml.findnodes('//*[@tag="245"]');
+
+ args.nonfiling_characters = title_node.findvalue('@ind2');
+
+ IF (args.nonfiling_characters > 0);
+ args.sort_title = args.sort_title.substr(args.nonfiling_characters);
+ ELSE;
+ args.sort_title = args.sort_title.replace('^[^A-Z0-9]*','');
+ END;
+
+ # Provide correct spacing between the subfields
+ titsubs = xml.findnodes('//*[@tag="245"]/*[@code]');
+ titsubs_content = [];
+ FOR sub IN titsubs; titsubs_content.push(sub.textContent); END;
+ args.title_extended = titsubs_content.join(" ");
args.pubplaces = [];
FOR sub IN xml.findnodes('//*[@tag="260"]/*[@code="a"]');
ELSE;
cls_which = "acct-tab-off";
END -%]
- <a href="[% mkurl(ctx.opac_root _ '/myopac/' _ page.url, {}, ['bbid', 'offset', 'limit']) %]"
+ <a href="[% mkurl(ctx.opac_root _ '/myopac/' _ page.url, {}, ['bbid', 'offset', 'limit','sort','sort_type']) %]"
class="[% cls_which %]">[% page.name; %]</a>
[% END %]
</div>
--- /dev/null
+[%# Produce a URL for a given field that cycles for sorting from
+ "nothing" to "ascending" to "descending" then back to "nothing".
+%]
+[% MACRO sort_url(field)
+ IF (CGI.param('sort') == field);
+ SWITCH CGI.param('sort_type');
+ CASE "asc";
+ mkurl('',{sort=>field, sort_type=>'desc'},1);
+ CASE "desc";
+ mkurl('',{},1);
+ CASE;
+ mkurl('',{sort=>field, sort_type=>'asc'}, 1);
+ END;
+ ELSE;
+ mkurl('',{sort=>field, sort_type=>'asc'}, 1);
+ END;
+%]
+[%# SET click_sort = l("click to sort") %]
+[%# SET click_sort = "title=\"$click_sort\"" %]
+
+[%# Produce arrows to indicate the sorting status of the column %]
+[% MACRO sort_indicator(field)
+ IF (CGI.param('sort') == field);
+ SWITCH CGI.param('sort_type');
+ CASE "asc";
+" <span class=\"column_sort_arrow\">▲</span>";
+ CASE "desc";
+" <span class=\"column_sort_arrow\">▼</span>";
+ END;
+ END;
+%]
+
+[%# Column headers for sortable columns %]
+[% MACRO sort_head(field, field_label)
+ BLOCK %]
+<a href="[% sort_url(field) %]" [% click_sort %]>[% l(field_label) %]</a>[%- sort_indicator(field) %]
+[% END
+%]
[% l('[_1] [_2]', ctx.user.first_given_name, ctx.user.family_name) | html %]
</span>
<span class="dash_divider">|</span>
- <a href="[% mkurl(ctx.opac_root _ '/myopac/main') %]"
+ <a href="[% mkurl(ctx.opac_root _ '/myopac/main', {}, ['sort','sort_type']) %]"
class="opac-button">[% l('My Account') %]</a>
<a href="[% mkurl(ctx.opac_root _ '/logout', {}, 1) %]"
</div>
<div id="dashboard">
<span class="dash-align">
- <a class="dash-link" href="[% mkurl(ctx.opac_root _ '/myopac/circs')
+ <a class="dash-link" href="[% mkurl(ctx.opac_root _ '/myopac/circs', {}, ['sort','sort_type'])
%]"><span id="dash_checked">[% ctx.user_stats.checkouts.total_out
%]</span> [% l("Checked Out") %]</a>
</span>
<span class="dash_divider">|</span>
<span class="dash-align">
- <a class="dash-link" href="[% mkurl(ctx.opac_root _ '/myopac/holds', {}, ['available'])
+ <a class="dash-link" href="[% mkurl(ctx.opac_root _ '/myopac/holds', {}, ['sort','sort_type'])
%]"><span id="dash_holds">[% ctx.user_stats.holds.total
%]</span> [% l("On Hold") %]</a>
</span>
<span class="dash_divider">|</span>
<span class="dash-align">
<a class="dash-link" href="[% mkurl(ctx.opac_root _ '/myopac/holds',
- {available => 1}) %]"><span id="dash_pickup">[%
+ {available => 1}, ['sort','sort_type']) %]"><span id="dash_pickup">[%
ctx.user_stats.holds.ready %]</span> [% l("Ready for Pickup") %]</a>
</span>
<span class="dash_divider">|</span>
<span class="dash-align">
- <a class="dash-link" href="[% mkurl(ctx.opac_root _ '/myopac/main')
+ <a class="dash-link" href="[% mkurl(ctx.opac_root _ '/myopac/main', {}, ['sort','sort_type'])
%]"><span id="dash_fines">[% money(ctx.user_stats.fines.balance_owed)
%]</span> [% l("Fines") %]</a>
</span>