diff --git a/stoptime.rb b/stoptime.rb index f38194d..9fe0fd0 100644 --- a/stoptime.rb +++ b/stoptime.rb @@ -1395,58 +1395,72 @@ module StopTime::Views # The main layout used by all views. def layout - html do + html(:lang => "en") do head do title "Stop… Camping Time!" + meta :name => "viewport", + :content => "width=device-width, initial-scale=1.0" + # Bootstrap CSS + link :rel => "stylesheet", :type => "text/css", + :media => "screen", + :href => (R(Static, "") + "stylesheets/bootstrap.min.css") # FIXME: improve static serving so that the hack below is not needed. link :rel => "stylesheet", :type => "text/css", :media => "screen", :href => (R(Static, "") + "stylesheets/style.css") end body do - div.wrapper! do - h1 "Stop… Camping Time!" - _menu - div.content! do - self << yield - end - end + _menu + self << yield + footer { br } + # JQuery and Bootstrap JavaScript + script :src => "http://code.jquery.com/jquery.js" + script :src => (R(Static, "") + "javascripts/bootstrap.min.js") end end end # The main overview showing accumulated time per task per customer. def overview - h2 "Overview" - - if @tasks.empty? - p do - "No customers, projects or tasks found! Set them up " + - "#{a "here", :href => R(CustomersNew)}." + header do + div.container do + h2 "Overview" end - else - @tasks.keys.sort_by { |c| c.name }.each do |customer| - h3 { a customer.name, :href => R(CustomersN, customer.id) } - if @tasks[customer].empty? - p do - text! "No projects/tasks found! Create one " + - "#{a "here", :href => R(CustomersNTasksNew, customer.id)}." + end + div.container do + div.row do + if @tasks.empty? + div.alert.alert_info do + text! "No customers, projects or tasks found! Set them up " + + "#{a "here", :href => R(CustomersNew)}." end else - table.overview do - col.task - col.hours - col.amount - @tasks[customer].each do |task| - tr do - summary = task.summary - td do - a task.name, - :href => R(CustomersNTasksN, customer.id, task.id) + div.span6 do + @tasks.keys.sort_by { |c| c.name }.each do |customer| + h3 { a customer.name, :href => R(CustomersN, customer.id) } + if @tasks[customer].empty? + p do + text! "No projects/tasks found! Create one " + + "#{a "here", :href => R(CustomersNTasksNew, customer.id)}." + end + else + table.table.table_condensed do + col.task + col.hours + col.amount + @tasks[customer].each do |task| + tr do + summary = task.summary + td do + a task.name, + :href => R(CustomersNTasksN, customer.id, task.id) + end + summary = task.summary + td.text_right { "%.2fh" % summary[0] } + td.text_right { "€ %.2f" % summary[2] } + end + end end - summary = task.summary - td.right { "%.2fh" % summary[0] } - td.right { "€ %.2f" % summary[2] } end end end @@ -1460,81 +1474,92 @@ module StopTime::Views # it will be assumed it is used as a partial view. # FIXME: This should be done in a nicer way. def time_entries(task_id=nil) - if task_id.present? - h2 "Registered #{@task.billed? ? "billed" : "unbilled"} time" - else - h2 "Timeline" + header do + div.container do + if task_id.present? + h2 "Registered #{@task.billed? ? "billed" : "unbilled"} time" + else + h2 "Timeline" + end + end end - table.timeline do - unless task_id.present? - col.customer - col.task - end - col.date - col.start_time - col.end_time - col.comment - col.hours - col.flag - tr do + div.container do + table.table.table_condensed.table_striped.table_hover do unless task_id.present? - th "Customer" - th "Project/Task" + col.customer_short + col.task end - th "Date" - th "Start time" - th "End time" - th "Comment" - th "Total time" - th "Bill?" - th {} - end - form :action => R(Timeline), :method => :post do - tr do - if task_id.present? - input :type => :hidden, :name => "task", :value => task_id - else - td { "–" } - td { _form_select_nested("task", @task_list) } - end - td { input :type => :text, :name => "date", - :value => DateTime.now.to_date.to_formatted_s } - td { input :type => :text, :name => "start", - :value => DateTime.now.to_time.to_formatted_s(:time_only) } - td { input :type => :text, :name => "end" } - td { input :type => :text, :name => "comment" } - td { "N/A" } - td { _form_input_checkbox("bill") } - td do - input :type => :submit, :name => "enter", :value => "Enter" - input :type => :reset, :name => "clear", :value => "Clear" + col.date + col.start_time + col.end_time + col.comment + col.hours + col.flag + thead do + tr do + unless task_id.present? + th "Customer" + th "Project/Task" + end + th "Date" + th "Start" + th "End" + th "Comment" + th "Total" + th "Bill?" + th {} end end - end - @time_entries.each do |entry| - tr(:class => entry.task.billed? ? "billed" : nil) do - unless task_id.present? - td do - a entry.customer.shortest_name, - :href => R(CustomersN, entry.customer.id) - end - td do - a entry.task.name, - :href => R(CustomersNTasksN, entry.customer.id, entry.task.id) + tbody do + form.form_inline :action => R(Timeline), :method => :post do + tr do + if task_id.present? + input :type => :hidden, :name => "task", :value => task_id + else + td { } + td { _form_select_nested("task", @task_list, :class => "task") } + end + td { input.date :type => :text, :name => "date", + :value => DateTime.now.to_date.to_formatted_s } + td { input.start_time :type => :text, :name => "start", + :value => DateTime.now.to_time.to_formatted_s(:time_only) } + td { input.end_time :type => :text, :name => "end" } + td { input.comment :type => :text, :name => "comment" } + td { "N/A" } + td { _form_input_checkbox("bill") } + td do + button.btn.btn_small.btn_primary "Enter", :type => :submit, :name => "enter", :value => "Enter" + end end end - td { a entry.date.to_date, - :href => R(TimelineN, entry.id) } - td { entry.start.to_formatted_s(:time_only) } - td { entry.end.to_formatted_s(:time_only)} - td { entry.comment } - td { "%.2fh" % entry.hours_total } - td do - _form_input_checkbox("bill_#{entry.id}", true, :disabled => true) - end - td do - form :action => R(TimelineN, entry.id), :method => :post do - input :type => :submit, :name => "delete", :value => "Delete" + @time_entries.each do |entry| + tr(:class => entry.task.billed? ? "billed" : nil) do + unless task_id.present? + td do + a entry.customer.shortest_name, + :title => entry.customer.shortest_name, + :href => R(CustomersN, entry.customer.id) + end + td do + a entry.task.name, + :title => entry.task.name, + :href => R(CustomersNTasksN, entry.customer.id, entry.task.id) + end + end + td { a entry.date.to_date, + :href => R(TimelineN, entry.id) } + td { entry.start.to_formatted_s(:time_only) } + td { entry.end.to_formatted_s(:time_only)} + td { span entry.comment, title: entry.comment } + td { "%.2fh" % entry.hours_total } + td do + _form_input_checkbox("bill_#{entry.id}", true, :disabled => true) + end + td do + form.form_inline :action => R(TimelineN, entry.id), :method => :post do + button.btn.btn_mini.btn_danger "Delete", :type => :submit, :name => "delete", :value => "Delete" + end + end end end end @@ -1544,108 +1569,135 @@ module StopTime::Views # Form for editing a time entry (Models::TimeEntry). def time_entry_form - h2 "Time Entry Information" - if @time_entry.present? and @time_entry.task.billed? - p.warn do - em "This time entry is already billed! Only make changes if you know " + - "what you are doing!" + header do + div.container do + h2 "Time Entry Information" end end - form :action => R(*@target), :method => :post do - ol do - li do - label "Customer", :for => "customer" - _form_select("customer", @customer_list) - end - li do - label "Task", :for => "task" - _form_select("task", @task_list) - end - if @time_entry.present? and @time_entry.task.billed? - li do - label "Billed in invoice" - a @time_entry.task.invoice.number, - :href => R(CustomersNInvoicesX, @time_entry.customer.id, - @time_entry.task.invoice.number) + div.container do + div.alert do + button.close(:type => "button", "data-dismiss" => "alert") { "×" } + strong "Warning!" + text! "This time entry is already billed! Only make changes if you know " + + "what you are doing!" + end if @time_entry.present? and @time_entry.task.billed? + form.form_horizontal.form_condensed :action => R(*@target), :method => :post do + div.control_group do + label.control_label "Customer", :for => "customer" + div.controls do + _form_select("customer", @customer_list) end end - li { _form_input_with_label("Date", "date", :text) } - li { _form_input_with_label("Start Time", "start", :text) } - li { _form_input_with_label("End Time", "end", :text) } - li { _form_input_with_label("Comment", "comment", :text) } - li do - label "Bill?", :for => "bill" - _form_input_checkbox("bill") + div.control_group do + label.control_label "Task", :for => "task" + div.controls do + _form_select("task", @task_list) + end + end + if @time_entry.present? and @time_entry.task.billed? + div.control_group do + label.control_label "Billed in invoice" + div.controls do + a @time_entry.task.invoice.number, + :href => R(CustomersNInvoicesX, @time_entry.customer.id, + @time_entry.task.invoice.number) + end + end + end + _form_input_with_label("Date", "date", :text, :class => "input-small") + _form_input_with_label("Start Time", "start", :text, :class => "input-mini") + _form_input_with_label("End Time", "end", :text, :class => "input-mini") + _form_input_with_label("Comment", "comment", :text, :class => "input-xxlarge") + div.control_group do + label.control_label "Bill?", :for => "bill" + div.controls do + _form_input_checkbox("bill") + end end # FIXME: link to invoice if any + div.form_actions do + button.btn.btn_primary @button.capitalize, :type => "submit", + :name => @button, :value => @button.capitalize + button.btn "Cancel", :type => "submit", + :name => "cancel", :value => "Cancel" + end end - input :type => "submit", :name => @button, :value => @button.capitalize - input :type => "submit", :name => "cancel", :value => "Cancel" end end # The main overview of the list of customers. def customers - h2 "Customers" - if @customers.empty? - p do - text! "None found! You can create one " + - "#{a "here", :href => R(CustomersNew)}." + header do + div.container do + h2 "Customers" end - else - table.customers do - col.name - col.short_name - col.address - col.email - col.phone - tr do - th "Name" - th "Short name" - th "Address" - th "Email" - th "Phone" - th {} - end - @customers.each do |customer| - tr do - td { a customer.name, :href => R(CustomersN, customer.id) } - td { customer.short_name || "–"} - td do - if customer.address_street.present? - text! customer.address_street - br - text! customer.address_postal_code + " " + - customer.address_city - else - "–" - end + end + div.container do + if @customers.empty? + p do + text! "None found! You can create one " + + "#{a "here", :href => R(CustomersNew)}." + end + else + table.table.table_striped.table_condensed do + col.name + col.short_name + col.address + col.email + col.phone + thead do + tr do + th "Name" + th "Short name" + th "Address" + th "Email" + th "Phone" + th {} end - td do - if customer.email.present? - a customer.email, :href => "mailto:#{customer.email}" - else - "–" - end - end - td do - if customer.phone.present? - # FIXME: hardcoded prefix! - "0#{customer.phone}" - else - "–" - end - end - td do - form :action => R(CustomersN, customer.id), :method => :post do - input :type => :submit, :name => "delete", :value => "Delete" + end + tbody do + @customers.each do |customer| + tr do + td { a customer.name, :href => R(CustomersN, customer.id) } + td { customer.short_name || "–"} + td do + if customer.address_street.present? + text! customer.address_street + br + text! customer.address_postal_code + " " + + customer.address_city + else + "–" + end + end + td do + if customer.email.present? + a customer.email, :href => "mailto:#{customer.email}" + else + "–" + end + end + td do + if customer.phone.present? + # FIXME: hardcoded prefix! + "0#{customer.phone}" + else + "–" + end + end + td do + form :action => R(CustomersN, customer.id), :method => :post do + button.btn.btn_mini.btn_danger "Delete", :type => :submit, + :name => "delete", :value => "Delete" + end + end end end end end - end - a "» Add a new customer", :href=> R(CustomersNew) + a.btn "» Add a new customer", :href=> R(CustomersNew) + end end end @@ -1653,114 +1705,147 @@ module StopTime::Views # for adding/editing/deleting tasks and showing a list of invoices for # the customer. def customer_form - form.float_left :action => R(*@target), :method => :post do - h2 "Customer Information" - ol do - li { _form_input_with_label("Name", "name", :text) } - li { _form_input_with_label("Short name", "short_name", :text) } - li { _form_input_with_label("Street address", "address_street", :text) } - li { _form_input_with_label("Postal code", "address_postal_code", :text) } - li { _form_input_with_label("City/town", "address_city", :text) } - li { _form_input_with_label("Email address", "email", :text) } - li { _form_input_with_label("Phone number", "phone", :text) } - li { _form_input_with_label("Financial contact", "financial_contact", :text) } - li { _form_input_with_label("Default hourly rate", "hourly_rate", :text) } - end - input :type => "submit", :name => @button, :value => @button.capitalize - input :type => "submit", :name => "cancel", :value => "Cancel" - end - if @edit_task - # FXIME: the following is not very RESTful! - form :action => R(CustomersNTasks, @customer.id), :method => :post do - h2 "Projects & Tasks" - select :name => "task_id", :size => 10 do - @tasks.each do |task| - if task.billed? - option(:value => task.id) { task.name + " (#{task.invoice.number})" } - else - option(:value => task.id) { task.name } + div.container do + div.row do + div.span6 do + h2 "Customer Information" + form.form_horizontal.form_condensed :action => R(*@target), :method => :post do + _form_input_with_label("Name", "name", :text) + _form_input_with_label("Short name", "short_name", :text) + _form_input_with_label("Street address", "address_street", :text) + _form_input_with_label("Postal code", "address_postal_code", :text) + _form_input_with_label("City/town", "address_city", :text) + _form_input_with_label("Email address", "email", :email) + _form_input_with_label("Phone number", "phone", :tel) + _form_input_with_label("Financial contact", "financial_contact", :text) + _form_input_with_label("Default hourly rate", "hourly_rate", :text) + div.form_actions do + button.btn.btn_primary @button.capitalize, :type => "submit", + :name => @button, :value => @button.capitalize + button.btn "Cancel", :type => "submit", + :name => "cancel", :value => "Cancel" end end end - div do - input :type => :submit, :name => "edit", :value => "Edit" - input :type => :submit, :name => "delete", :value => "Delete" - a "» Add a new project/task", :href => R(CustomersNTasksNew, @customer.id) + + div.span6 do + if @edit_task + h2 "Projects & Tasks" + # FXIME: the following is not very RESTful! + form :action => R(CustomersNTasks, @customer.id), :method => :post do + select.input_xlarge :name => "task_id", :size => 10 do + @tasks.each do |task| + if task.billed? + option(:value => task.id) { task.name + " (#{task.invoice.number})" } + else + option(:value => task.id) { task.name } + end + end + end + div.form_actions do + button.btn.btn_primary "Edit", :type => :submit, + :name => "edit", :value => "Edit" + button.btn.btn_danger "Delete", :type => :submit, + :name => "delete", :value => "Delete" + a.btn "» Add a new project/task", + :href => R(CustomersNTasksNew, @customer.id) + end + end + + h2 "Invoices" + _invoice_list(@invoices) + a.btn "» Create a new invoice", + :href => R(CustomersNInvoicesNew, @customer.id) + end end end - - div.clear do - h2 "Invoices" - _invoice_list(@invoices) - a "» Create a new invoice", :href => R(CustomersNInvoicesNew, @customer.id) - end end - div.clear {} end # Form for updating the properties of a task (Models::Task). def task_form - h2 "Task Information" - p.warn do - em "This task is already billed! Only make changes if you know " + - "what you are doing!" - end if @task.billed? - form :action => R(*@target), :method => :post do - ol do - li do - label "Customer", :for => "customer" - _form_select("customer", @customer_list) - a "» Go to customer", :href => R(CustomersN, @customer.id) + header do + div.container do + h2 "Task Information" + end + end + div.container do + div.alert do + button.close(:type => "button", "data-dismiss" => "alert") { "×" } + strong "Warning!" + text! "This task is already billed! Only make changes if you know " + + "what you are doing!" + end if @task.billed? + form.form_horizontal.form_condensed :action => R(*@target), :method => :post do + div.control_group do + label.control_label "Customer", :for => "customer" + div.controls do + _form_select("customer", @customer_list) + a.btn "» Go to customer", :href => R(CustomersN, @customer.id) + end end - li { _form_input_with_label("Name", "name", :text) } - li do - label "Project/Task type" - ol.radio do - li do + _form_input_with_label("Name", "name", :text) + div.control_group do + label.control_label "Project/Task type" + div.controls do + label.radio do _form_input_radio("type", "hourly_rate", default=true) - _form_input_with_label("Hourly rate", "hourly_rate", :text) + text!("Hourly rate: ") + _form_input("hourly_rate", :number, "Hourly rate", :class => "input-small") end - li do + label.radio do _form_input_radio("type", "fixed_cost") - _form_input_with_label("Fixed cost", "fixed_cost", :text) + text!("Fixed cost: ") + _form_input("fixed_cost", :number, "Fixed cost", :class => "input-small") end end end - li do - _form_input_with_label("VAT rate", "vat_rate", :text) - end + _form_input_with_label("VAT rate", "vat_rate", :number, :class => "input-small") if @task.billed? - li do - label "Billed in invoice" - a @task.invoice.number, - :href => R(CustomersNInvoicesX, @customer.id, @task.invoice.number) - end - li do - _form_input_with_label("Invoice comment", "invoice_comment", :text) + div.control_group do + label.control_label "Billed in invoice" + div.controls do + a @task.invoice.number, + :href => R(CustomersNInvoicesX, @customer.id, @task.invoice.number) + end end + _form_input_with_label("Invoice comment", "invoice_comment", :text) + end + div.form_actions do + button.btn.btn_primary @method.capitalize, :type => "submit", + :name => @method, :value => @method.capitalize + button.btn "Cancel", :type => "submit", + :name => "cancel", :value => "Cancel" end end - input :type => "submit", :name => @method, :value => @method.capitalize - input :type => "submit", :name => "cancel", :value => "Cancel" + # Show registered time (ab)using the time_entries view as partial view. + time_entries(@task.id) unless @method == "create" end - # Show registered time (ab)using the time_entries view as partial view. - time_entries(@task.id) unless @method == "create" end # The main overview of the existing invoices. def invoices - h2 "Invoices" - - if @invoices.values.flatten.empty? - p do - text! "Found none! You can create one by " - "#{a "selecting a customer", :href => R(Customers)}." + header do + div.container do + h2 "Invoices" end - else - @invoices.keys.sort.each do |key| - next if @invoices[key].empty? - h3 { key } - _invoice_list(@invoices[key]) + end + div.container do + div.row do + div.span7 do + if @invoices.values.flatten.empty? + p do + text! "Found none! You can create one by " + "#{a "selecting a customer", :href => R(Customers)}." + end + else + @invoices.keys.sort.each do |key| + next if @invoices[key].empty? + h3 { key } + _invoice_list(@invoices[key]) + end + end + end end end end @@ -1769,258 +1854,314 @@ module StopTime::Views # invoice (Models::Invoice) that also allows for updating the "+paid+" # property. def invoice_form - h2 do - span "Invoice for " - a @customer.name, :href => R(CustomersN, @customer.id) - end - - form :action => R(CustomersNInvoicesX, @customer.id, @invoice.number), - :method => :post do - table do - tr do - td.key { b "Number" } - td.val { @invoice.number } - end - tr do - td.key { b "Date" } - td.val { @invoice.created_at.to_formatted_s(:date_only) } - end - tr do - td.key { b "Period" } - td.val { _format_period(@invoice.period) } - end - tr do - td.key { b "Paid?" } - td.val do - _form_input_checkbox("paid") - input :type => :submit, :name => "update", :value => "Update" - input :type => :reset, :name => "reset", :value => "Reset" - end + header do + div.container do + h2 do + span "Invoice for " + a @customer.name, :href => R(CustomersN, @customer.id) end end end - - table.tasks do - col.task - col.reg_hours - col.hourly_rate - col.amount - tr do - th { "Project/Task" } - th.right { "Registered time" } - th.right { "Hourly rate" } - th.right { "Amount" } - end - subtotal = 0.0 - @tasks.each do |task, line| - tr do - td do - a task.comment_or_name, - :href => R(CustomersNTasksN, task.customer.id, task.id) - end - if line[1].blank? - # FIXME: information of time spent is available in the summary - # but show it? - td.right { "%.2fh" % line[0] } - td.right "–" - else - td.right { "%.2fh" % line[0] } - td.right { "€ %.2f" % line[1] } - end - td.right { "€ %.2f" % line[2] } - end - subtotal += line[2] - task.time_entries.each do |entry| - tr do - td.indent do - if entry.comment.present? - "• #{entry.comment}" - else - em.light "• no comment" + div.container do + div.row do + div.span6 do + form.form_horizontal.form_condensed :action => R(CustomersNInvoicesX, @customer.id, @invoice.number), + :method => :post do + _form_input_with_label("Number", "number", :text, :disabled => true, + :class => "input-small") + div.control_group do + label.control_label "Date" + div.controls do + input.input_medium :type => :text, :name => "created_at", + :id => "created_at", + :value => @invoice.created_at.to_formatted_s(:date_only), + :placeholder => "Date", :disabled => true end end - td.right { "%.2fh" % entry.hours_total } - td.right { "–" } - td.right { "–" } - end - end unless task.fixed_cost? - end - vattotal = 0.0 - if @company.vatno.present? - tr.total do - td { i "Sub-total" } - td "" - td "" - td.right { "€ %.2f" % subtotal } - end - @vat.keys.sort.each do |rate| - vattotal += @vat[rate] - tr do - td { i "VAT %d%%" % rate } - td "" - td "" - td.right { "€ %.2f" % @vat[rate] } + div.control_group do + label.control_label "Period" + div.controls do + input.input_large :type => :text, :name => "period", :id => "period", + :value => _format_period(@invoice.period), + :placeholder => "Period", :disabled => true + end + end + div.control_group do + label.control_label "Paid?" + div.controls do + _form_input_checkbox("paid") + end + end + div.form_actions do + button.btn.btn_primary "Update", :type => :submit, + :name => "update", :value => "Update" + button.btn "Reset", :type => :reset, + :name => "reset", :value => "Reset" + end + end + end + div.span6 do + table.table.table_condensed.table_striped do + col.task + col.reg_hours + col.hourly_rate + col.amount + thead do + tr do + th { "Project/Task" } + th.text_right { "Registered" } + th.text_right { "Hourly rt." } + th.text_right { "Amount" } + end + end + tbody do + subtotal = 0.0 + @tasks.each do |task, line| + tr do + td do + a task.comment_or_name, + :title => task.comment_or_name, + :href => R(CustomersNTasksN, task.customer.id, task.id) + end + if line[1].blank? + # FIXME: information of time spent is available in the summary + # but show it? + td.text_right { "%.2fh" % line[0] } + td.text_right "–" + else + td.text_right { "%.2fh" % line[0] } + td.text_right { "€ %.2f" % line[1] } + end + td.text_right { "€ %.2f" % line[2] } + end + subtotal += line[2] + task.time_entries.each do |entry| + tr do + td.indent do + if entry.comment.present? + "• #{entry.comment}" + else + em.light "• no comment" + end + end + td.text_right { "%.2fh" % entry.hours_total } + td.text_right { "–" } + td.text_right { "–" } + end + end unless task.fixed_cost? + end + vattotal = 0.0 + if @company.vatno.present? + tr.total do + td { i "Sub-total" } + td "" + td "" + td.text_right { "€ %.2f" % subtotal } + end + @vat.keys.sort.each do |rate| + vattotal += @vat[rate] + tr do + td { i "VAT %d%%" % rate } + td "" + td "" + td.text_right { "€ %.2f" % @vat[rate] } + end + end + end + tr.total do + td { b "Total" } + td "" + td "" + td.text_right { "€ %.2f" % (subtotal + vattotal) } + end + end + end + + div.btn_group do + a.btn.btn_primary "» Download PDF", + :href => R(CustomersNInvoicesX, @customer.id, "#{@invoice.number}.pdf") + a.btn "» Download LaTeX source", + :href => R(CustomersNInvoicesX, @customer.id, "#{@invoice.number}.tex") + a.btn "» View company info", + :href => R(Company, :revision => @company.revision) end end - end - tr.total do - td { b "Total" } - td "" - td "" - td.right { "€ %.2f" % (subtotal + vattotal) } end end - - a "» Download PDF", - :href => R(CustomersNInvoicesX, @customer.id, "#{@invoice.number}.pdf") - a "» Download LaTeX source", - :href => R(CustomersNInvoicesX, @customer.id, "#{@invoice.number}.tex") - a "» View related company info", - :href => R(Company, :revision => @company.revision) end # Form for selecting fixed cost tasks and registered time for tasks with # an hourly rate that need to be billed. def invoice_select_form - h2 "Registered Time" - form :action => R(CustomersNInvoices, @customer.id), :method => :post do - h3 "Projects/Tasks with an Hourly Rate" - unless @hourly_rate_tasks.empty? - table.invoice_select do - col.flag - col.date - col.start_time - col.end_time - col.comment - col.hours - col.amount - tr do - th "Bill?" - th "Date" - th "Start time" - th "End time" - th "Comment" - th.right "Total time" - th.right "Amount" - end - @hourly_rate_tasks.keys.each do |task| - tr.task do - td { _form_input_checkbox("tasks[]", task.id) } - td task.name, :colspan => 3 - td do - input :type => :text, :name => "task_#{task.id}_comment", - :id => "tasks_#{task.id}_comment", :value => task.name + header do + div.container do + h2 "Registered Time" + end + end + div.container do + div.row do + div.span10 do + form.form_horizontal :action => R(CustomersNInvoices, @customer.id), + :method => :post do + h3 "Projects/Tasks with an Hourly Rate" + unless @hourly_rate_tasks.empty? + table.table.table_striped.table_condensed do + col.flag + col.date + col.start_time + col.end_time + col.comment + col.hours + col.amount + thead do + tr do + th "Bill?" + th "Date" + th "Start" + th "End" + th "Comment" + th.text_right "Total" + th.text_right "Amount" + end + end + tbody do + @hourly_rate_tasks.keys.each do |task| + tr.task do + td { _form_input_checkbox("tasks[]", task.id) } + td task.name, :colspan => 3 + td do + input :type => :text, :name => "task_#{task.id}_comment", + :id => "tasks_#{task.id}_comment", :value => task.name + td {} + td {} + end + end + @hourly_rate_tasks[task].each do |entry| + tr do + td.indent { _form_input_checkbox("time_entries[]", entry.id) } + td { label entry.date.to_date, + :for => "time_entries[]_#{entry.id}" } + td { entry.start.to_formatted_s(:time_only) } + td { entry.end.to_formatted_s(:time_only) } + td { entry.comment } + td.text_right { "%.2fh" % entry.hours_total } + td.text_right { "€ %.2f" % (entry.hours_total * entry.task.hourly_rate) } + end + end + end + end end end - @hourly_rate_tasks[task].each do |entry| - tr do - td.indent { _form_input_checkbox("time_entries[]", entry.id) } - td { label entry.date.to_date, - :for => "time_entries[]_#{entry.id}" } - td { entry.start.to_formatted_s(:time_only) } - td { entry.end.to_formatted_s(:time_only) } - td { entry.comment } - td.right { "%.2fh" % entry.hours_total } - td.right { "€ %.2f" % (entry.hours_total * entry.task.hourly_rate) } + + unless @fixed_cost_tasks.empty? + h3 "Fixed Cost Projects/Tasks" + table.table.table_striped.table_condensed do + col.flag + col.task + col.comment + col.hours + col.amount + thead do + tr do + th "Bill?" + th "Project/Task" + th "Comment" + th.text_right "Registered time" + th.text_right "Amount" + end + end + tbody do + @fixed_cost_tasks.keys.each do |task| + tr do + td { _form_input_checkbox("tasks[]", task.id) } + td { label task.name, :for => "tasks[]_#{task.id}" } + td do + input :type => :text, :name => "task_#{task.id}_comment", + :id => "tasks_#{task.id}_comment", :value => task.name + end + td.text_right { "%.2fh" % @fixed_cost_tasks[task] } + td.text_right { task.fixed_cost } + end + end + end end end + + div.form_actions do + button.btn.btn_primary "Create invoice", :type => :submit, + :name => "create", :value => "Create invoice" + button.btn "Cancel", :type => :submit, + :name => "cancel", :value => "Cancel" + end end end end - - unless @fixed_cost_tasks.empty? - h3 "Fixed Cost Projects/Tasks" - table.tasks do - col.flag - col.task - col.comment - col.hours - col.amount - tr do - th "Bill?" - th "Project/Task" - th "Comment" - th.right "Registered time" - th.right "Amount" - end - @fixed_cost_tasks.keys.each do |task| - tr do - td { _form_input_checkbox("tasks[]", task.id) } - td { label task.name, :for => "tasks[]_#{task.id}" } - td do - input :type => :text, :name => "task_#{task.id}_comment", - :id => "tasks_#{task.id}_comment", :value => task.name - end - td.right { "%.2fh" % @fixed_cost_tasks[task] } - td.right { task.fixed_cost } - end - end - end - end - - input :type => :submit, :name => "create", :value => "Create invoice" - input :type => :submit, :name => "cancel", :value => "Cancel" end end # Form for editing the company information stored in Models::CompanyInfo. def company_form - h2 "Company Information" - - if @errors - div.form_errors do - h3 "There were #{@errors.count} errors in the form!" + header do + div.container do + h2 "Company Information" + end + end + div.container do + div.alert.alert_error.alert_block do + button.close(:type => "button", "data-dismiss" => "alert") { "×" } + h4 "There were #{@errors.count} errors in the form!" ul do @errors.each do |attrib, msg| li "#{attrib.to_s.capitalize} #{msg}" end end + end if @errors + div.alert.alert_info do + text! " Viewing revision #{@company.revision}, " + + " last update at #{@company.updated_at}." + if @company.original.present? + a.btn "» View previous revision", + :href => R(Company, :revision => @company.original.revision) + end end - end - p do - em " Viewing revision #{@company.revision}, " + - " last update at #{@company.updated_at}." - if @company.original.present? - a "» View previous revision", - :href => R(Company, :revision => @company.original.revision) - end - end - if @history_warn - p.warn do - em "This company information is already associated with some invoices! " + div.alert.alert_block do + button.close(:type => "button", "data-dismiss" => "alert") { "×" } + h4 "Warning!" + text! "This company information is already associated with some invoices! " br - em "Only make changes if you know what you are doing!" + text! "Only make changes if you know what you are doing!" + end if @history_warn + form.form_horizontal :action => R(Company, :revision => @company.revision), + :method => :post do + _form_input_with_label("Name", "name", :text) + _form_input_with_label("Contact name", "contact_name", :text) + _form_input_with_label("Street address", "address_street", :text) + _form_input_with_label("Postal code", "address_postal_code", :text) + _form_input_with_label("City/town", "address_city", :text) + _form_input_with_label("Phone number", "phone", :tel) + _form_input_with_label("Cellular number", "cell", :tel) + _form_input_with_label("Email address", "email", :email) + _form_input_with_label("Web address", "website", :url) + + h3 "Corporate information" + _form_input_with_label("Chamber number", "chamber", :text) + _form_input_with_label("VAT number", "vatno", :text) + + h3 "Bank information" + _form_input_with_label("Name", "bank_name", :text) + _form_input_with_label("Identification code", "bank_bic", :text) + _form_input_with_label("Account holder", "accountname", :text) + _form_input_with_label("Account number", "accountno", :text) + _form_input_with_label("Intl. account number", "accountiban", :text) + + div.form_actions do + button.btn.btn_primary "Update", :type => "submit", + :name => "update", :value => "Update" + button.tbn "Reset", :type => :reset, :name => "reset", + :value => "Reset" + end end end - form :action => R(Company, :revision => @company.revision), - :method => :post do - ol do - li { _form_input_with_label("Name", "name", :text) } - li { _form_input_with_label("Contact name", "contact_name", :text) } - li { _form_input_with_label("Street address", "address_street", :text) } - li { _form_input_with_label("Postal code", "address_postal_code", :text) } - li { _form_input_with_label("City/town", "address_city", :text) } - li { _form_input_with_label("Phone number", "phone", :text) } - li { _form_input_with_label("Cellular number", "cell", :text) } - li { _form_input_with_label("Email address", "email", :text) } - li { _form_input_with_label("Web address", "website", :text) } - end - h3 "Corporate information" - ol do - li { _form_input_with_label("Chamber number", "chamber", :text) } - li { _form_input_with_label("VAT number", "vatno", :text) } - end - h3 "Bank information" - ol do - li { _form_input_with_label("Name", "bank_name", :text) } - li { _form_input_with_label("Identification code", "bank_bic", :text) } - li { _form_input_with_label("Account holder", "accountname", :text) } - li { _form_input_with_label("Account number", "accountno", :text) } - li { _form_input_with_label("Intl. account number", "accountiban", :text) } - end - input :type => "submit", :name => "update", :value => "Update" - input :type => :reset, :name => "reset", :value => "Reset" - end end ############### @@ -2030,12 +2171,19 @@ module StopTime::Views # Partial view that generates the menu. def _menu - ol.menu! do - [["Overview", Index], - ["Timeline", Timeline], - ["Customers", Customers], - ["Invoices", Invoices], - ["Company", Company]].each { |label, ctrl| _menu_link(label, ctrl) } + nav.navbar.navbar_fixed_top do + div.navbar_inner do + div.container do + a.brand(:href => R(Index)) { "Stop… Camping Time!" } + ul.nav do + [["Overview", Index], + ["Timeline", Timeline], + ["Customers", Customers], + ["Invoices", Invoices], + ["Company", Company]].each { |label, ctrl| _menu_link(label, ctrl) } + end + end + end end end @@ -2044,7 +2192,7 @@ module StopTime::Views def _menu_link(label, ctrl) # FIXME: dirty hack? if self.class.to_s.match(/^#{ctrl.to_s}/) - li.selected { a label, :href => R(ctrl) } + li.active { a label, :href => R(ctrl) } else li { a label, :href => R(ctrl) } end @@ -2055,33 +2203,36 @@ module StopTime::Views if invoices.empty? p "None found!" else - table.invoices do + table.table.table_striped.table_condensed do col.number col.date col.period col.amount col.flag - tr do - th "Number" - th "Date" - th "Period" - th.right "Amount" - th "Paid?" - end - invoices.each do |invoice| + thead do tr do - td do - a invoice.number, - :href => R(CustomersNInvoicesX, - invoice.customer.id, invoice.number) - end - td { invoice.created_at.to_formatted_s(:date_only) } - td { _format_period(invoice.period) } - # FIXME: really retrieve the paid flag. - td.right { "€ %.2f" % invoice.total_amount } - td do - _form_input_checkbox("paid_#{invoice.number}", invoice.paid?, - :disabled => true) + th "Number" + th "Date" + th "Period" + th.text_right "Amount" + th "Paid?" + end + end + tbody do + invoices.each do |invoice| + tr do + td do + a invoice.number, + :href => R(CustomersNInvoicesX, + invoice.customer.id, invoice.number) + end + td { invoice.created_at.to_formatted_s(:date_only) } + td { _format_period(invoice.period) } + # FIXME: really retrieve the paid flag. + td.text_right { "€ %.2f" % invoice.total_amount } + td do + i(:class => "icon_ok") if invoice.paid? + end end end end @@ -2100,13 +2251,31 @@ module StopTime::Views end end + # Partial view that generates a form input with the given _label_name_, + # _type_ and _placeholder_ text. + # + # The _html_options_ should be a Hash of options that are usually passed + # on as arguments to a Markaby/Mab tag. + def _form_input(input_name, type, placeholder, html_options={}) + html_options.merge!(:type => type, :name => input_name, + :id => input_name, :value => @input[input_name], + :placeholder => placeholder) + input(html_options) + end + # Partial view that generates a form label with the given _label_name_ # and a form input with the given _input_name_ and _type_, such that the # label is linked to the input. - def _form_input_with_label(label_name, input_name, type) - label label_name, :for => input_name - input :type => type, :name => input_name, :id => input_name, - :value => @input[input_name] + # + # The _html_options_ should be a Hash of options that are usually passed + # on as arguments to a Markaby/Mab input tag (see #_form_input). + def _form_input_with_label(label_name, input_name, type, html_options={}) + div.control_group do + label.control_label label_name, :for => input_name + div.controls do + _form_input(input_name, type, label_name, html_options) + end + end end # Partial view that generates a form radio button with the given _name_ @@ -2142,13 +2311,18 @@ module StopTime::Views # # The option list is an Array of a 2-valued array containg a value label # and a human readable description for the value. - def _form_select(name, opts_list) + # + # The _html_options_ should be a Hash of options that are usually passed + # on as arguments to a Markaby/Mab tag. + def _form_select(name, opts_list, html_options={}) if opts_list.blank? - select :name => name, :id => name, :disabled => true do + html_options.merge!(:name => name, :id => name, :disabled => true) + select(html_options) do option "None found", :value => "none", :selected => true end else - select :name => name, :id => name do + html_options.merge!(:name => name, :id => name) + select(html_options) do opts_list.sort_by { |o| o.last }.each do |opt_val, opt_str| if @input[name] == opt_val option opt_str, :value => opt_val, :selected => true @@ -2169,13 +2343,18 @@ module StopTime::Views # The option list is an Hash of Strings mapping to an Array of a 2-valued # array containg a value label and a human readable description for the # value. - def _form_select_nested(name, opts) + # + # The _html_options_ should be a Hash of options that are usually passed + # on as arguments to a Markaby/Mab tag. + def _form_select_nested(name, opts, html_options={}) if opts.blank? - select :name => name, :id => name, :disabled => true do + html_options.merge!(:name => name, :id => name, :disabled => true) + select(html_options) do option "None found", :value => "none", :selected => true end else - select :name => name, :id => name do + html_options.merge!(:name => name, :id => name) + select(html_options) do opts.keys.sort.each do |key| option("— #{key} —", {:disabled => true}) opts[key].sort_by { |o| o.last }.each do |opt_val, opt_str|