web: UI cleanup
This commit is contained in:
		
							parent
							
								
									c952ab881b
								
							
						
					
					
						commit
						5f6da96baa
					
				| @ -12,7 +12,7 @@ import Import | ||||
| import Hledger | ||||
| import Hledger.Cli.CliOptions | ||||
| import Hledger.Web.WebOptions | ||||
| import Widget.AddForm (addForm) | ||||
| import Widget.AddForm (addModal) | ||||
| import Widget.Common (accountQuery, mixedAmountAsHtml) | ||||
| 
 | ||||
| -- | The formatted journal view, with sidebar. | ||||
| @ -27,7 +27,6 @@ getJournalR = do | ||||
|       acctlink a = (RegisterR, [("q", accountQuery a)]) | ||||
|       (_, items) = journalTransactionsReport (reportopts_ $ cliopts_ opts) j m | ||||
| 
 | ||||
|   (addView, addEnctype) <- generateFormPost (addForm j today) | ||||
|   defaultLayout $ do | ||||
|     setTitle "journal - hledger-web" | ||||
|     $(widgetFile "journal") | ||||
|  | ||||
| @ -17,8 +17,8 @@ import Text.Hamlet (hamletFile) | ||||
| import Hledger | ||||
| import Hledger.Cli.CliOptions | ||||
| import Hledger.Web.WebOptions | ||||
| import Widget.AddForm (addForm) | ||||
| import Widget.Common (mixedAmountAsHtml, numberTransactionsReportItems) | ||||
| import Widget.AddForm (addModal) | ||||
| import Widget.Common (mixedAmountAsHtml) | ||||
| 
 | ||||
| -- | The main journal/account register view, with accounts sidebar. | ||||
| getRegisterR :: Handler Html | ||||
| @ -31,13 +31,6 @@ getRegisterR = do | ||||
| 
 | ||||
|   let r@(balancelabel,items) = accountTransactionsReport (reportopts_ $ cliopts_ opts) j m $ fromMaybe Any $ inAccountQuery qopts | ||||
|       balancelabel' = if isJust (inAccount qopts) then balancelabel else "Total" | ||||
|       evenodd x = if even x then "even" else "odd" :: Text | ||||
|       datetransition newm newd | ||||
|         | newm = "newmonth" | ||||
|         | newd = "newday" | ||||
|         | otherwise = "" :: Text | ||||
| 
 | ||||
|   (addView, addEnctype) <- generateFormPost (addForm j today) | ||||
|   defaultLayout $ do | ||||
|     setTitle "register - hledger-web" | ||||
|     $(widgetFile "register") | ||||
|  | ||||
| @ -2,10 +2,12 @@ | ||||
| {-# LANGUAGE GADTs #-} | ||||
| {-# LANGUAGE OverloadedStrings #-} | ||||
| {-# LANGUAGE QuasiQuotes #-} | ||||
| {-# LANGUAGE RankNTypes #-} | ||||
| {-# LANGUAGE TemplateHaskell #-} | ||||
| 
 | ||||
| module Widget.AddForm | ||||
|   ( addForm | ||||
|   , addModal | ||||
|   ) where | ||||
| 
 | ||||
| import Control.Monad.State.Strict (evalStateT) | ||||
| @ -26,6 +28,27 @@ import Settings (widgetFile) | ||||
| 
 | ||||
| -- XXX <select> which journal to add to | ||||
| 
 | ||||
| addModal :: | ||||
|      ( MonadWidget m | ||||
|      , r ~ Route (HandlerSite m) | ||||
|      , m ~ WidgetFor (HandlerSite m) | ||||
|      , RenderMessage (HandlerSite m) FormMessage | ||||
|      ) | ||||
|   => r -> Journal -> Day -> m () | ||||
| addModal addR j today = do | ||||
|   (addView, addEnctype) <- generateFormPost (addForm j today) | ||||
|   [whamlet| | ||||
| <div .modal.fade #addmodal tabindex="-1" role="dialog" aria-labelledby="addLabel" aria-hidden="true"> | ||||
|   <div .modal-dialog .modal-lg> | ||||
|     <div .modal-content> | ||||
|       <div .modal-header> | ||||
|         <button type="button" .close data-dismiss="modal" aria-hidden="true">× | ||||
|         <h3 .modal-title #addLabel>Add a transaction | ||||
|       <div .modal-body> | ||||
|         <form#addform.form action=@{addR} method=POST enctype=#{addEnctype}> | ||||
|           ^{addView} | ||||
| |] | ||||
| 
 | ||||
| addForm :: | ||||
|      (site ~ HandlerSite m, RenderMessage site FormMessage, MonadHandler m) | ||||
|   => Journal | ||||
|  | ||||
| @ -1,4 +1,3 @@ | ||||
| {-# LANGUAGE BangPatterns #-} | ||||
| {-# LANGUAGE LambdaCase #-} | ||||
| {-# LANGUAGE NamedFieldPuns #-} | ||||
| {-# LANGUAGE OverloadedStrings #-} | ||||
| @ -10,7 +9,6 @@ module Widget.Common | ||||
|   , balanceReportAsHtml | ||||
|   , helplink | ||||
|   , mixedAmountAsHtml | ||||
|   , numberTransactionsReportItems | ||||
|   , fromFormSuccess | ||||
|   , writeValidJournal | ||||
|   , journalFile404 | ||||
| @ -18,11 +16,9 @@ module Widget.Common | ||||
| 
 | ||||
| import Data.Default (def) | ||||
| import Data.Foldable (find, for_) | ||||
| import Data.List (mapAccumL) | ||||
| import Data.Semigroup ((<>)) | ||||
| import Data.Text (Text) | ||||
| import qualified Data.Text as T | ||||
| import Data.Time.Calendar (Day, toGregorian) | ||||
| import System.FilePath (takeFileName) | ||||
| import Text.Blaze ((!), textValue) | ||||
| import qualified Text.Blaze.Html5 as H | ||||
| @ -50,7 +46,6 @@ writeValidJournal f txt = | ||||
|   liftIO (readJournal def (Just f) txt) >>= \case | ||||
|     Left e -> return (Left e) | ||||
|     Right _ -> do | ||||
|       -- And write to the file | ||||
|       _ <- liftIO (writeFileWithBackupIfChanged f txt) | ||||
|       return (Right ()) | ||||
| 
 | ||||
| @ -102,19 +97,6 @@ accountQuery = ("inacct:" <>) .  quoteIfSpaced | ||||
| accountOnlyQuery :: AccountName -> Text | ||||
| accountOnlyQuery = ("inacctonly:" <>) . quoteIfSpaced | ||||
| 
 | ||||
| numberTransactionsReportItems :: [TransactionsReportItem] -> [(Int, Bool, Bool, TransactionsReportItem)] | ||||
| numberTransactionsReportItems = snd . mapAccumL number (0, nulldate) | ||||
|   where | ||||
|     number :: (Int, Day) -> TransactionsReportItem -> ((Int, Day), (Int, Bool, Bool, TransactionsReportItem)) | ||||
|     number (!n, !prevd) i@(t, _, _, _, _, _) = ((n', d), (n', newday, newmonth, i)) | ||||
|       where | ||||
|         n' = n + 1 | ||||
|         d = tdate t | ||||
|         newday = d /= prevd | ||||
|         newmonth = dm /= prevdm || dy /= prevdy | ||||
|         (dy, dm, _) = toGregorian d | ||||
|         (prevdy, prevdm, _) = toGregorian prevd | ||||
| 
 | ||||
| mixedAmountAsHtml :: MixedAmount -> HtmlUrl a | ||||
| mixedAmountAsHtml b _ = | ||||
|   for_ (lines (showMixedAmountWithoutPrice b)) $ \t -> do | ||||
|  | ||||
| @ -20,37 +20,6 @@ | ||||
| /*------------------------------------------------------------------------------------------*/ | ||||
| /* 4. typeahead styles */ | ||||
| 
 | ||||
| /* | ||||
| .typeahead, | ||||
| .tt-query, | ||||
| .tt-hint { | ||||
|   width: 396px; | ||||
|   height: 30px; | ||||
|   padding: 8px 12px; | ||||
|   font-size: 24px; | ||||
|   line-height: 30px; | ||||
|   border: 2px solid #ccc; | ||||
|   -webkit-border-radius: 8px; | ||||
|      -moz-border-radius: 8px; | ||||
|           border-radius: 8px; | ||||
|   outline: none; | ||||
| } | ||||
| 
 | ||||
| .typeahead { | ||||
|   background-color: #fff; | ||||
| } | ||||
| 
 | ||||
| .typeahead:focus { | ||||
|   border: 2px solid #0097cf; | ||||
| } | ||||
| 
 | ||||
| .tt-query { | ||||
|   -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); | ||||
|      -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); | ||||
|           box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); | ||||
| } | ||||
| 
 | ||||
| */ | ||||
| .tt-hint { | ||||
|   color: #bbb; | ||||
| } | ||||
| @ -100,7 +69,7 @@ code { | ||||
| 
 | ||||
| ul { | ||||
|     list-style-type: none; | ||||
| 	  padding: 0; | ||||
|     padding: 0; | ||||
| } | ||||
| 
 | ||||
| #main-content { | ||||
| @ -200,24 +169,6 @@ ul { | ||||
|     whitespace: nowrap; | ||||
| } | ||||
| 
 | ||||
| #main-content { | ||||
|    /* | ||||
|     -webkit-transition: width 0.3s ease, margin 0.3s ease; | ||||
|     -moz-transition: width 0.3s ease, margin 0.3s ease; | ||||
|     -o-transition: width 0.3s ease, margin 0.3s ease; | ||||
|     transition: width 0.3s ease, margin 0.3s ease; | ||||
| */ | ||||
| } | ||||
| 
 | ||||
| #sidebar-menu { | ||||
|     /* | ||||
|     -webkit-transition: width 0.3s ease, margin 0.3s ease,opacity 0.3s ease,height 1s ease 1s; | ||||
|     -moz-transition: width 0.3s ease, margin 0.3s ease,opacity 0.3s ease,height 1s ease 1s; | ||||
|     -o-transition: width 0.3s ease, margin 0.3s ease,opacity 0.3s ease,height 1s ease 1s; | ||||
|     transition: width 0.3s ease, margin 0.3s ease,opacity 0.3s ease,height 1s ease 1s; | ||||
| */ | ||||
| } | ||||
| 
 | ||||
| .col-any-0 { | ||||
|     width:0 !important; | ||||
|     height:0 !important; | ||||
| @ -229,10 +180,6 @@ ul { | ||||
|     font-size:large; | ||||
| } | ||||
| 
 | ||||
| #searchbar { | ||||
|     width: 100% !important; | ||||
| } | ||||
| 
 | ||||
| @media screen and (max-width: 768px) { | ||||
|     .row-offcanvas { | ||||
|         position: relative; | ||||
|  | ||||
| @ -4,9 +4,13 @@ | ||||
| // STARTUP
 | ||||
| 
 | ||||
| $(document).ready(function() { | ||||
|   // cache the input element as a variable
 | ||||
|   // for minor performance   benefits
 | ||||
|   var dateEl = $('#dateWrap'); | ||||
|   // date picker
 | ||||
|   // http://bootstrap-datepicker.readthedocs.io/en/latest/options.html
 | ||||
|   var dateEl = $('#dateWrap').datepicker({ | ||||
|     showOnFocus: false, | ||||
|     autoclose: true, | ||||
|     format: 'yyyy-mm-dd' | ||||
|   });; | ||||
| 
 | ||||
|   // ensure add form always focuses its first field
 | ||||
|   $('#addmodal') | ||||
| @ -18,21 +22,6 @@ $(document).ready(function() { | ||||
|       dateEl.datepicker('hide'); | ||||
|     }); | ||||
| 
 | ||||
|   // show add form if ?add=1
 | ||||
|   if ($.url.param('add')) { addformShow(true); } | ||||
| 
 | ||||
|   // date picker
 | ||||
|   // http://bootstrap-datepicker.readthedocs.io/en/latest/options.html
 | ||||
|   dateEl.datepicker({ | ||||
|     showOnFocus: false, | ||||
|     autoclose: true, | ||||
|     format: 'yyyy-mm-dd' | ||||
|   }); | ||||
| 
 | ||||
|   // sidebar account hover handlers
 | ||||
|   $('#sidebar td a').mouseenter(function(){ $(this).parent().addClass('mouseover'); }); | ||||
|   $('#sidebar td').mouseleave(function(){ $(this).removeClass('mouseover'); }); | ||||
| 
 | ||||
|   // keyboard shortcuts
 | ||||
|   // 'body' seems to hold focus better than document in FF
 | ||||
|   $('body').bind('keydown', 'h',       function(){ $('#helpmodal').modal('toggle'); return false; }); | ||||
| @ -42,12 +31,12 @@ $(document).ready(function() { | ||||
|   $('body').bind('keydown', 'a',       function(){ addformShow(); return false; }); | ||||
|   $('body').bind('keydown', 'n',       function(){ addformShow(); return false; }); | ||||
|   $('body').bind('keydown', 'f',       function(){ $('#searchform input').focus(); return false; }); | ||||
|   $('body, #addform input, #addform select').bind('keydown', 'ctrl++',       addformAddPosting); | ||||
|   $('body, #addform input, #addform select').bind('keydown', 'ctrl+shift+=', addformAddPosting); | ||||
|   $('body, #addform input, #addform select').bind('keydown', 'ctrl+=',       addformAddPosting); | ||||
|   $('body, #addform input, #addform select').bind('keydown', 'ctrl+-',       addformDeletePosting); | ||||
|   $('.amount-input:last').keypress(addformAddPosting); | ||||
| 
 | ||||
| 
 | ||||
|   // highlight the entry from the url hash
 | ||||
|   if (window.location.hash && $(window.location.hash)[0]) { | ||||
|     $(window.location.hash).addClass('highlighted'); | ||||
| @ -173,49 +162,38 @@ function focus($el) { | ||||
| 
 | ||||
| // Insert another posting row in the add form.
 | ||||
| function addformAddPosting() { | ||||
|   $('.amount-input:last').off('keypress'); | ||||
|   // do nothing if it's not currently visible
 | ||||
|   if (!$('#addform').is(':visible')) return; | ||||
|   // save a copy of last row
 | ||||
|   var lastrow = $('#addform .form-group:last').clone(); | ||||
|   if (!$('#addform').is(':visible')) { | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   // replace the submit button with an amount field, clear and renumber it, add the keybindings
 | ||||
|   var prevLastRow = $('.amount-input:last'); | ||||
|   prevLastRow.off('keypress'); | ||||
| 
 | ||||
|   // Clone the currently last row
 | ||||
|   $('#addform .account-postings').append(prevLastRow.clone()); | ||||
|   var num = $('#addform .account-group').length; | ||||
| 
 | ||||
|   // insert the new last row
 | ||||
|   $('#addform .account-postings').append(lastrow); | ||||
|   // TODO: Enable typehead on dynamically created inputs
 | ||||
| 
 | ||||
|   var $acctinput = $('.account-input:last'); | ||||
|   var $amntinput = $('.amount-input:last'); | ||||
|   // clear and renumber the field, add keybindings
 | ||||
|   $acctinput | ||||
|   // XXX Enable typehead on dynamically created inputs
 | ||||
|   $('.amount-input:last') | ||||
|     .val('') | ||||
|     .prop('name', 'account') | ||||
|     .prop('placeholder', 'Account ' + (num + 1)); | ||||
|   //lastrow.find('input') // not :last this time
 | ||||
|   $acctinput | ||||
|     .bind('keydown', 'ctrl+shift+=', addformAddPosting) | ||||
|     .bind('keydown', 'ctrl+=', addformAddPosting) | ||||
|     .bind('keydown', 'ctrl+-', addformDeletePosting); | ||||
| 
 | ||||
|   $amntinput | ||||
|     .val('') | ||||
|     .prop('name','amount') | ||||
|     .prop('placeholder','Amount ' + (num + 1)) | ||||
|     .prop('placeholder','Amount ' + num) | ||||
|     .keypress(addformAddPosting); | ||||
| 
 | ||||
|   $acctinput | ||||
|   $('.account-input:last') | ||||
|     .val('') | ||||
|     .prop('placeholder', 'Account ' + num) | ||||
|     .bind('keydown', 'ctrl++', addformAddPosting) | ||||
|     .bind('keydown', 'ctrl+shift+=', addformAddPosting) | ||||
|     .bind('keydown', 'ctrl+=', addformAddPosting) | ||||
|     .bind('keydown', 'ctrl+-', addformDeletePosting); | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| // Remove the add form's last posting row, if empty, keeping at least two.
 | ||||
| function addformDeletePosting() { | ||||
|   var num = $('#addform .account-group').length; | ||||
|   if (num <= 2) return; | ||||
|   if ($('#addform .account-group').length <= 2) { | ||||
|     return; | ||||
|   } | ||||
|   // remember if the last row's field or button had focus
 | ||||
|   var focuslost = | ||||
|     $('.account-input:last').is(':focus') | ||||
|  | ||||
| @ -7,29 +7,27 @@ | ||||
| <div#topbar .col-md-8 .col-sm-8 .col-xs-10> | ||||
|   <h1>#{takeFileName (journalFilePath j)} | ||||
| 
 | ||||
| <div #sidebar-menu .sidebar-offcanvas.#{sideShowmd}.#{sideShowsm}> | ||||
| <div#sidebar-menu .sidebar-offcanvas.#{sideShowmd}.#{sideShowsm}> | ||||
|   <table .main-menu .table> | ||||
|     ^{accounts} | ||||
| 
 | ||||
| <div .col-xs-12.#{mainShowmd}.#{mainShowsm}> | ||||
| <div#main-content .col-xs-12.#{mainShowmd}.#{mainShowsm}> | ||||
|   $maybe m <- msg | ||||
|     <div #message .alert.alert-info>#{m} | ||||
|   <div .row> | ||||
|     <form#searchform .form-inline method=GET> | ||||
|       <div .form-group .col-md-12 .col-sm-12 .col-xs-12> | ||||
|         <div #searchbar .input-group> | ||||
|           <input .form-control name=q value=#{q} placeholder="Search" | ||||
|             title="Enter hledger search patterns to filter the data below"> | ||||
|           <div .input-group-btn> | ||||
|             $if not (T.null q) | ||||
|               <a href=@{here} .btn .btn-default title="Clear search terms"> | ||||
|                 <span .glyphicon .glyphicon-remove-circle> | ||||
|             <button .btn .btn-default type=submit title="Apply search terms"> | ||||
|               <span .glyphicon .glyphicon-search> | ||||
|             <button .btn .btn-default type=button data-toggle="modal" data-target="#helpmodal" | ||||
|                title="Show search and general help">? | ||||
|             <a href="@{ManageR}" .btn.btn-default title="Manage journal files"> | ||||
|               <span .glyphicon .glyphicon-wrench> | ||||
|   <form#searchform.input-group method=GET> | ||||
|     <input .form-control name=q value=#{q} placeholder="Search" | ||||
|       title="Enter hledger search patterns to filter the data below"> | ||||
|     <div .input-group-btn> | ||||
|       $if not (T.null q) | ||||
|         <a href=@{here} .btn .btn-default title="Clear search terms"> | ||||
|           <span .glyphicon .glyphicon-remove-circle> | ||||
|       <button .btn .btn-default type=submit title="Apply search terms"> | ||||
|         <span .glyphicon .glyphicon-search> | ||||
|       <a href="@{ManageR}" .btn.btn-default title="Manage journal files"> | ||||
|         <span .glyphicon .glyphicon-wrench> | ||||
|       <button .btn .btn-default type=button data-toggle="modal" data-target="#helpmodal" | ||||
|          title="Show search and general help"> | ||||
|         <span .glyphicon .glyphicon-question-sign> | ||||
|   ^{widget} | ||||
| 
 | ||||
| <div .modal.fade #helpmodal tabindex="-1" role="dialog" aria-labelledby="helpLabel" aria-hidden="true"> | ||||
|  | ||||
| @ -16,7 +16,7 @@ | ||||
|         <td .date nowrap>#{show (tdate torig)} | ||||
|         <td .description colspan=2>#{textElideRight 60 (tdescription torig)} | ||||
|         <td .amount style="text-align:right;"> | ||||
|           $if not split || not (isZeroMixedAmount amt) | ||||
|           $if not split && not (isZeroMixedAmount amt) | ||||
|             \^{mixedAmountAsHtml amt} | ||||
| 
 | ||||
|       $forall Posting { paccount = acc, pamount = amt } <- tpostings torig | ||||
| @ -30,12 +30,4 @@ | ||||
|           <td .amount .nonhead style="text-align:right;"> | ||||
|             ^{mixedAmountAsHtml amt} | ||||
| 
 | ||||
| <div .modal #addmodal tabindex="-1" role="dialog" aria-labelledby="addLabel" aria-hidden="true"> | ||||
|   <div .modal-dialog .modal-lg> | ||||
|     <div .modal-content> | ||||
|       <div .modal-header> | ||||
|         <button type="button" .close data-dismiss="modal" aria-hidden="true">× | ||||
|         <h3 .modal-title #addLabel>Add a transaction | ||||
|       <div .modal-body> | ||||
|         <form#addform.form action=@{AddR} method=POST enctype=#{addEnctype}> | ||||
|           ^{addView} | ||||
| ^{addModal AddR j today} | ||||
|  | ||||
| @ -11,14 +11,12 @@ | ||||
|       <tbody> | ||||
|         $forall (path, _) <- jfiles j | ||||
|           <tr> | ||||
|             <td> | ||||
|             <td style="vertical-align:middle"> | ||||
|               #{path} | ||||
|             <td style="text-align:right"> | ||||
|               <a href=@{EditR path}> | ||||
|               <a.btn.btn-default href=@{EditR path}> | ||||
|                 Edit | ||||
|                  | ||||
|               <a href=@{UploadR path}> | ||||
|               <a.btn.btn-default href=@{UploadR path}> | ||||
|                 Upload | ||||
|                  | ||||
|               <a href=@{DownloadR path}> | ||||
|               <a.btn.btn-default href=@{DownloadR path}> | ||||
|                 Download | ||||
|  | ||||
| @ -4,13 +4,13 @@ | ||||
| <div .hidden-xs> | ||||
|   ^{registerChartHtml $ transactionsReportByCommodity r} | ||||
| 
 | ||||
| <div .table-responsive> | ||||
|   <table.registerreport .table .table-striped .table-condensed> | ||||
| <div.table-responsive> | ||||
|   <table .table.table-striped.table-condensed> | ||||
|     <thead> | ||||
|       <tr> | ||||
|         <th style="text-align:left;"> | ||||
|           Date | ||||
|           <span .glyphicon .glyphicon-chevron-up> | ||||
|           <span .glyphicon.glyphicon-chevron-up> | ||||
|         <th style="text-align:left;">Description | ||||
|         <th style="text-align:left;">To/From Account(s) | ||||
|         <th style="text-align:right; white-space:normal;">Amount Out/In | ||||
| @ -18,9 +18,8 @@ | ||||
|           #{balancelabel'} | ||||
| 
 | ||||
|     <tbody> | ||||
|       $forall (n, newd, newm, (torig, tacct, split, acct, amt, bal)) <- numberTransactionsReportItems items | ||||
|         <tr ##{tindex torig} .item.#{evenodd n}.#{datetransition newm newd} | ||||
|             title="#{show torig}" style="vertical-align:top;"> | ||||
|       $forall (torig, tacct, split, acct, amt, bal) <- items | ||||
|         <tr ##{tindex torig} .item title="#{show torig}" style="vertical-align:top;"> | ||||
|           <td .date> | ||||
|             <a href="@{JournalR}#transaction-#{tindex torig}"> | ||||
|               #{show (tdate tacct)} | ||||
| @ -28,15 +27,7 @@ | ||||
|           <td .account>#{elideRight 40 acct} | ||||
|           <td .amount style="text-align:right; white-space:nowrap;"> | ||||
|             $if not split || not (isZeroMixedAmount amt) | ||||
|               \^{mixedAmountAsHtml amt} | ||||
|               ^{mixedAmountAsHtml amt} | ||||
|           <td .balance style="text-align:right;">^{mixedAmountAsHtml bal} | ||||
| 
 | ||||
| <div .modal #addmodal tabindex="-1" role="dialog" aria-labelledby="addLabel" aria-hidden="true"> | ||||
|   <div .modal-dialog .modal-lg> | ||||
|     <div .modal-content> | ||||
|       <div .modal-header> | ||||
|         <button type="button" .close data-dismiss="modal" aria-hidden="true">× | ||||
|         <h3 .modal-title #addLabel>Add a transaction | ||||
|       <div .modal-body> | ||||
|         <form#addform.form action=@{AddR} method=POST enctype=#{addEnctype}> | ||||
|           ^{addView} | ||||
| ^{addModal AddR j today} | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user