diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index eead3e61..eacedaac 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -3,6 +3,8 @@ //= require jquery_ujs //= require twitter/bootstrap //= require jquery.tokeninput +//= require bootstrap-datepicker +//= require bootstrap-datepicker.de //= require jquery.observe_field //= require jquery.fancybox-1.3.4.pack //= require rails.validations @@ -91,6 +93,9 @@ $(function() { $('form[data-remote]').bind('ajax:beforeSend', function() { $(this).children('input[type="submit"]').attr('disabled', 'disabled'); }); + + // Use bootstrap datepicker for dateinput + $('.datepicker').datepicker({format: 'yyyy-mm-dd', weekStart: 1, language: 'de'}); }); diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 242fb1ef..4c1ba2e6 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -1,6 +1,5 @@ /* *= require bootstrap_and_overrides -*= require foodsoft *= require token-input-bootstrappy *= require jquery.fancybox-1.3.4 */ \ No newline at end of file diff --git a/app/assets/stylesheets/bootstrap_and_overrides.css.less b/app/assets/stylesheets/bootstrap_and_overrides.css.less index 14bf301d..d122d886 100644 --- a/app/assets/stylesheets/bootstrap_and_overrides.css.less +++ b/app/assets/stylesheets/bootstrap_and_overrides.css.less @@ -30,3 +30,10 @@ body { // // Example: // @linkColor: #ff0000; + + +// Bootstrap datepicker +@import "datepicker"; + +// Custom styles +@import "foodsoft"; \ No newline at end of file diff --git a/app/assets/stylesheets/datepicker.less b/app/assets/stylesheets/datepicker.less new file mode 100644 index 00000000..5d39c7aa --- /dev/null +++ b/app/assets/stylesheets/datepicker.less @@ -0,0 +1,147 @@ +/*! + * Datepicker for Bootstrap + * + * Copyright 2012 Stefan Petre + * Improvements by Andrew Rowls + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + */ + +.datepicker { + top: 0; + left: 0; + padding: 4px; + margin-top: 1px; + .border-radius(4px); + &:before { + content: ''; + display: inline-block; + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-bottom: 7px solid #ccc; + border-bottom-color: rgba(0,0,0,.2); + position: absolute; + top: -7px; + left: 6px; + } + &:after { + content: ''; + display: inline-block; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid @white; + position: absolute; + top: -6px; + left: 7px; + } + >div { + display: none; + } + &.days div.datepicker-days { + display: block; + } + &.months div.datepicker-months { + display: block; + } + &.years div.datepicker-years { + display: block; + } + table{ + margin: 0; + } + td, + th{ + text-align: center; + width: 20px; + height: 20px; + .border-radius(4px); + } + td { + &.day:hover { + background: @grayLighter; + cursor: pointer; + } + &.old, + &.new { + color: @grayLight; + } + &.disabled, + &.disabled:hover { + background: none; + color: @grayLight; + cursor: default; + } + &.today, + &.today:hover, + &.today.disabled, + &.today.disabled:hover { + @todayBackground: lighten(@orange, 30%); + .buttonBackground(@todayBackground, spin(@todayBackground, 20)); + } + &.active, + &.active:hover, + &.active.disabled, + &.active.disabled:hover { + .buttonBackground(@btnPrimaryBackground, spin(@btnPrimaryBackground, 20)); + color: #fff; + text-shadow: 0 -1px 0 rgba(0,0,0,.25); + } + span { + display: block; + width: 23%; + height: 54px; + line-height: 54px; + float: left; + margin: 1%; + cursor: pointer; + .border-radius(4px); + &:hover { + background: @grayLighter; + } + &.disabled, + &.disabled:hover { + background:none; + color: @grayLight; + cursor: default; + } + &.active, + &.active:hover, + &.active.disabled, + &.active.disabled:hover { + .buttonBackground(@btnPrimaryBackground, spin(@btnPrimaryBackground, 20)); + color: #fff; + text-shadow: 0 -1px 0 rgba(0,0,0,.25); + } + &.old { + color: @grayLight; + } + } + } + + th.switch { + width: 145px; + } + + thead tr:first-child th, + tfoot tr:first-child th { + cursor: pointer; + &:hover{ + background: @grayLighter; + } + } + /*.dow { + border-top: 1px solid #ddd !important; + }*/ +} +.input-append, +.input-prepend { + &.date { + .add-on i { + display: block; + cursor: pointer; + width: 16px; + height: 16px; + } + } +} diff --git a/app/assets/stylesheets/foodsoft.css.less b/app/assets/stylesheets/foodsoft.less similarity index 57% rename from app/assets/stylesheets/foodsoft.css.less rename to app/assets/stylesheets/foodsoft.less index 4e1c0901..512a6403 100644 --- a/app/assets/stylesheets/foodsoft.css.less +++ b/app/assets/stylesheets/foodsoft.less @@ -2,4 +2,12 @@ section { padding-bottom: 30px; margin-bottom: 30px; border-bottom: 1px solid #d3d3d3; +} + +.accepted { + color: #468847; +} + +.unaccepted { + color: #B94A48; } \ No newline at end of file diff --git a/app/controllers/tasks_controller.rb b/app/controllers/tasks_controller.rb index 945aee47..ced65a23 100644 --- a/app/controllers/tasks_controller.rb +++ b/app/controllers/tasks_controller.rb @@ -72,14 +72,14 @@ class TasksController < ApplicationController redirect_to :action => "index" end - def update_status - Task.find(params[:id]).update_attribute("done", params[:task][:done]) + def set_done + Task.find(params[:id]).update_attribute :done, true redirect_to tasks_url, :notice => "Aufgabenstatus wurde aktualisiert" end # Shows all tasks, which are already done def archive - @tasks = Task.done.paginate :page => params[:page], :per_page => 30 + @tasks = Task.done.page(params[:page]).per(@per_page) end # shows workgroup (normal group) to edit weekly_tasks_template diff --git a/app/helpers/tasks_helper.rb b/app/helpers/tasks_helper.rb index 9ab3a17c..9d002c4d 100644 --- a/app/helpers/tasks_helper.rb +++ b/app/helpers/tasks_helper.rb @@ -10,7 +10,8 @@ module TasksHelper def highlighted_required_users(task) unless task.enough_users_assigned? still_required = task.required_users - task.assignments.select { |ass| ass.accepted }.size - "(#{still_required})".html_safe + content_tag :span, still_required, class: 'badge badge-important', + title: "Es fehlen #{still_required} Mitstreiterinnen!" end end end diff --git a/app/inputs/date_picker_input.rb b/app/inputs/date_picker_input.rb new file mode 100644 index 00000000..57ced21a --- /dev/null +++ b/app/inputs/date_picker_input.rb @@ -0,0 +1,5 @@ +class DatePickerInput < SimpleForm::Inputs::StringInput + def input + @builder.text_field(attribute_name, input_html_options.merge({class: 'datepicker'})) + end +end \ No newline at end of file diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml index 7143d9f9..fe654043 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -37,8 +37,7 @@ - unless Message.public.empty? %section %h2 Neuste Nachrichten - = render :partial => 'messages/messages', - :locals => {:messages => Message.public.order('created_at DESC').limit(5), :subject_length => 70} + = render 'messages/messages', messages: Message.public.order('created_at DESC').limit(5), pagination: false %p= link_to "Alle Nachrichten einsehen", messages_path - if current_user.ordergroup diff --git a/app/views/messages/_messages.html.haml b/app/views/messages/_messages.html.haml index 7db06dc4..555668ae 100644 --- a/app/views/messages/_messages.html.haml +++ b/app/views/messages/_messages.html.haml @@ -1,13 +1,14 @@ -- if Message.public.count > 20 - = items_per_page -= pagination_links_remote messages +- if pagination + - if Message.public.count > 20 + = items_per_page + = pagination_links_remote messages - unless messages.empty? %table.table.table-striped %tbody - for message in messages %tr - %td= format_subject(message, subject_length) + %td= format_subject(message, 130) %td= h(message.sender_name) %td= format_time(message.created_at) %td= link_to 'Antworten', new_message_path(:message => {:reply_to => message.id}), class: 'btn' \ No newline at end of file diff --git a/app/views/messages/index.html.haml b/app/views/messages/index.html.haml index af064bc5..be7a9230 100644 --- a/app/views/messages/index.html.haml +++ b/app/views/messages/index.html.haml @@ -2,4 +2,4 @@ = link_to 'Neue Nachricht', new_message_path, class: 'btn btn-primary' #messages - = render :partial => 'messages', :locals => { :messages => @messages, :subject_length => 130 } \ No newline at end of file + = render 'messages', messages: @messages, pagination: true \ No newline at end of file diff --git a/app/views/shared/_workgroup_members.haml b/app/views/shared/_workgroup_members.haml index f1470f5a..525133f8 100644 --- a/app/views/shared/_workgroup_members.haml +++ b/app/views/shared/_workgroup_members.haml @@ -1,6 +1,14 @@ -- for workgroup in Workgroup.all - %h4= link_to workgroup.name, "#", 'data-toggle-this' => "#workgroup_#{workgroup.id}" - %ul{:style => "display:none"}[workgroup] - - for user in workgroup.users.order("nick") - %li= "#{user.nick} (#{user.ordergroup.try(:name)})" - \ No newline at end of file +.well.well-small + %h4 Mitglieder der Gruppen + #groupMembers.accordion + - Workgroup.all.each do |workgroup| + .accordion-group + .accoridon-heading + = link_to workgroup.name, "#collapse#{workgroup.id}", data: {toggle: 'collapse', parent: '#groupMembers'} + %div{id: "collapse#{workgroup.id}", class: 'accordion-body collapse'} + .accordion-inner + %ul.unstyled + - workgroup.users.includes(:groups).order('nick').each do |user| + %li + = user.nick + %small (#{user.ordergroup.try(:name)}) \ No newline at end of file diff --git a/app/views/tasks/_archive_tasks.html.haml b/app/views/tasks/_archive_tasks.html.haml new file mode 100644 index 00000000..431667f7 --- /dev/null +++ b/app/views/tasks/_archive_tasks.html.haml @@ -0,0 +1,19 @@ +- if Task.done.count > 20 + = items_per_page += pagination_links_remote @tasks + +%table.table.table-striped + %thead + %tr + %th Fälligkeitsdatum + %th Betreff + %th Verantwortliche Menschen + %th + %tbody + - @tasks.each do |task| + %tr + %td= task.due_date unless task.due_date.nil? + %td= link_to "#{task.name} (#{task.duration}h)", :controller => "tasks", :action => "show", :id => task + %td + - unless task.users.empty? + = task.users.map(&:nick).join(", ") \ No newline at end of file diff --git a/app/views/tasks/_form.html.haml b/app/views/tasks/_form.html.haml index 7f16c69a..a6140075 100644 --- a/app/views/tasks/_form.html.haml +++ b/app/views/tasks/_form.html.haml @@ -1,4 +1,4 @@ -- content_for :head do +- content_for :javascript do :javascript $(function() { $("#task_user_list").tokenInput("#{users_path(:format => :json)}", { @@ -6,10 +6,14 @@ prePopulate: $("#task_user_list").data("pre"), hintText: 'Nach Nutzerin suchen', noResultText: 'Keine Nutzerin gefunden', - searchingText: 'Suche ...' + searchingText: 'Suche ...', + theme: 'facebook' }); }); +- content_for :sidebar do + = render "shared/workgroup_members" + = simple_form_for @task do |f| = f.input :name = f.input :description, as: :text, input_html: {rows: 10} @@ -17,5 +21,8 @@ = f.input :user_list, :as => :string, :input_html => { 'data-pre' => @task.users.map { |u| u.token_attributes }.to_json } = f.input :required_users = f.association :workgroup - = f.input :due_date, :include_blank => true - = f.submit \ No newline at end of file + = f.input :due_date, as: :date_picker + = f.input :done + .form-actions + = f.submit class: 'btn' + = link_to 'oder abbrechen', :back diff --git a/app/views/tasks/_list.haml b/app/views/tasks/_list.haml index f383e06f..3ff943b5 100644 --- a/app/views/tasks/_list.haml +++ b/app/views/tasks/_list.haml @@ -1,25 +1,29 @@ -%table - %tr - %th Fälligkeit - %th Betreff - %th{:colspan => '2'} - Wer machts? - %small (Wie viele werden noch benötigt?) - - for task in @tasks - - done = task.done ? " done" : "" - %tr{:class => cycle('even','odd', :name => "tasks") + done } - %td= format_date(task.due_date) unless task.due_date.nil? - %td= link_to "#{task.name} (#{task.duration}h)", task_path(task) - %td - = task_assignments task - = highlighted_required_users task - %td - - unless task.is_accepted?(@current_user) - %span{:style => "float:left"}= button_to "Aufgabe übernehmen", :controller => "tasks", :action => "accept", :id => task - = button_to "Aufgabe ablehnen", :controller => "tasks", :action => "reject", :id => task if task.is_assigned?(@current_user) - - else - - form_for :task, :url => {:action => "update_status", :id => task} do |f| - Erledigt? - = f.check_box :done, {:onchange => "submit()", :title => "Die Aufgabe wandert in das Archiv"} - = "Erledigt" if task.done - - reset_cycle("tasks") \ No newline at end of file +%table.table.table-striped + %thead + %tr + %th Fälligkeit + %th Betreff + %th{:colspan => '2'} + Wer machts? + %small (Wie viele werden noch benötigt?) + %tbody + - tasks.each do |task| + - done = task.done ? " done" : "" + %tr{:class => done } + %td= format_date(task.due_date) unless task.due_date.nil? + %td= link_to "#{task.name} (#{task.duration}h)", task_path(task) + %td + = task_assignments task + = highlighted_required_users task + %td + - if !task.is_accepted?(current_user) + = link_to "Aufgabe übernehmen", accept_task_path(task), method: :post, class: 'btn' + = link_to "Aufgabe ablehnen", reject_task_path(task), method: :post, class: 'btn' if task.is_assigned?(current_user) + - elsif !task.done + = link_to set_done_task_path(task), method: :post, class: 'btn', + title: 'Aufgabe als erledigt markieren' do + %i.icon-ok + Erledigt? + - else + %i.icon-ok + Erledigt \ No newline at end of file diff --git a/app/views/tasks/_nav.haml b/app/views/tasks/_nav.haml index 3f3e13d7..aa6e9516 100644 --- a/app/views/tasks/_nav.haml +++ b/app/views/tasks/_nav.haml @@ -1,19 +1,10 @@ -#start_nav{:style => "float:right"} - %h2 Aufgabenmenü - %ul - %li - Aktionen - %ul - %li= link_to "Neue Aufgabe erstellen", :action => "new" - %li - Seiten - %ul - %li= link_to "Meine Aufgaben", user_tasks_path - %li= link_to "Alle Aufgaben", tasks_path - %li= link_to "Erledigt Aufgaben (Archiv)", archive_tasks_path - - %li - Gruppenaufgaben - %ul - - for group in Workgroup.all - %li= link_to group.name, workgroup_tasks_path(workgroup_id: group.id) \ No newline at end of file +%p= link_to "Neue Aufgabe erstellen", new_task_path, class: 'btn btn-primary' +.well.well-small + %ul.nav.nav-list + %li.nav-header Seiten + %li= link_to "Meine Aufgaben", user_tasks_path + %li= link_to "Alle Aufgaben", tasks_path + %li= link_to "Erledigte Aufgaben (Archiv)", archive_tasks_path + %li.nav-header Gruppenaufgaben + - for group in Workgroup.all + %li= link_to group.name, workgroup_tasks_path(workgroup_id: group.id) \ No newline at end of file diff --git a/app/views/tasks/archive.haml b/app/views/tasks/archive.haml index 126cb879..32467e28 100644 --- a/app/views/tasks/archive.haml +++ b/app/views/tasks/archive.haml @@ -1,20 +1,5 @@ - title "Aufgabenarchiv" -= render :partial => "nav" +- content_for :sidebar do + = render 'nav' -= will_paginate @orders -%br/ -%table{:style => "width: 76%"} - %tr - %th Fälligkeitsdatum - %th Betreff - %th Verantwortliche Menschen - %th - - for task in @tasks - %tr{:class => cycle('even','odd')} - %td= task.due_date unless task.due_date.nil? - %td= link_to "#{task.name} (#{task.duration}h)", :controller => "tasks", :action => "show", :id => task - %td - - unless task.users.empty? - = task.users.map(&:nick).join(", ") -%p - = link_to_top \ No newline at end of file +#tasks= render 'archive_tasks' diff --git a/app/views/tasks/archive.js.erb b/app/views/tasks/archive.js.erb new file mode 100644 index 00000000..0ce64049 --- /dev/null +++ b/app/views/tasks/archive.js.erb @@ -0,0 +1 @@ +$('#tasks').html('<%= escape_javascript(render("archive_tasks")) %>'); \ No newline at end of file diff --git a/app/views/tasks/edit.haml b/app/views/tasks/edit.haml index 831e16a4..ef29e4ac 100644 --- a/app/views/tasks/edit.haml +++ b/app/views/tasks/edit.haml @@ -1,8 +1,3 @@ - title "Aufgabe bearbeiten" -#form{:style => "float:left; width:39em"} - = render('form') -#workgroup_members{:style => "padding-left:41em"} - %h3 Mitglieder der Gruppe - #list - = render :partial => "shared/workgroup_members" \ No newline at end of file += render 'form' \ No newline at end of file diff --git a/app/views/tasks/index.haml b/app/views/tasks/index.haml index a203fb39..71bf9c8c 100644 --- a/app/views/tasks/index.haml +++ b/app/views/tasks/index.haml @@ -1,21 +1,19 @@ -- title "Aufgabenübersicht" -= render :partial => "nav" +- title "Aufgaben" +- content_for :sidebar do + = render 'nav' - unless @non_group_tasks.empty? - .left_column{:style => "width:75%"} - - @tasks = @non_group_tasks - .box_title - %h2 Aufgaben für alle! - .column_content - = render :partial => "list" + %section + %h3 Aufgaben für alle! + = render 'list', tasks: @non_group_tasks -- for @group in @groups - - @tasks = @group.open_tasks - - unless @tasks.empty? - .left_column{:style => "width:75%"} - .box_title - %h2= link_to @group.name, :action => 'workgroup', :id => @group - .column_content - = render :partial => "list" +- for group in @groups + - tasks = group.open_tasks + - unless tasks.empty? + %section + %h3 + = group.name + %small= link_to "Gruppenaufgaben anzeigen", workgroup_tasks_path(group) + = render 'list', tasks: tasks = link_to_top \ No newline at end of file diff --git a/app/views/tasks/new.haml b/app/views/tasks/new.haml index 53b4545f..041728be 100644 --- a/app/views/tasks/new.haml +++ b/app/views/tasks/new.haml @@ -1,10 +1,3 @@ - title "Neue Aufgabe erstellen" -#form{:style => "float:left; width:39em"} - = render('form') - -#workgroup_members{:style => "padding-left:41em"} - %h3 Mitglieder der Arbeitsgruppen - %i Klicke auf den Gruppennamen um die Mitglieder zu sehen - #list - = render :partial => "shared/workgroup_members" += render 'form' diff --git a/app/views/tasks/show.haml b/app/views/tasks/show.haml index 64aac846..bab89b8e 100644 --- a/app/views/tasks/show.haml +++ b/app/views/tasks/show.haml @@ -1,38 +1,27 @@ - title "Aufgabe im Detail" -= render :partial => "nav" +- content_for :sidebar do + = render 'nav' + +%section + %dl.dl-horizontal + %dt Name + %dd= @task.name + %dt Beschreibung + %dd= simple_format(@task.description) + %dt Fälligkeitsdatum + %dd= format_date(@task.due_date) + %dt Dauer in Stunden + %dd= @task.duration + %dt Verantwortliche Menschen + %dd= task_assignments(@task) + %dt Arbeitsgruppe + %dd + - if @task.workgroup + = link_to @task.workgroup.name, workgroup_tasks_path(workgroup_id: @task.workgroup_id) -#task{:style => 'width:70%'} - %table - %tr - %td{:style => "width: 10em"} Name - %td - %b= @task.name - %tr - %td{:style => "vertical-align:top;"} Beschreibung - %td= simple_format(@task.description) - %tr - %td Fälligkeitsdatum - %td= format_date(@task.due_date) - %tr - %td Dauer in Stunden - %td= @task.duration - %tr - %td Verantwortliche Menschen - %td= task_assignments(@task) - %tr - %td Arbeitsgruppe - %td - - if @task.workgroup - = link_to @task.workgroup.name, workgroup_tasks_path(workgroup_id: @task.workgroup_id) - - %tr - %td{:colspan => "2"} - %div{:style => "float:left"} Als erledigt markieren - - form_for :task, :url => {:action => "update_status", :id => @task} do |f| - = f.check_box :done, {:onchange => "submit()", :title => "Die Aufgabe wandert in das Archiv"} - - %p - = link_to "Bearbeiten", edit_task_path(@task) - | - = link_to "Löschen", task_path(@task), :method => :delete, :confirm => "Die Aufgabe wirklich löschen?" + - unless @task.done? + = link_to 'Als erledigt markieren', set_done_task_path(@task), method: :post, class: 'btn' + = link_to "Bearbeiten", edit_task_path(@task), class: 'btn' + = link_to "Löschen", task_path(@task), :method => :delete, :confirm => "Die Aufgabe wirklich löschen?", + class: 'btn btn-danger' diff --git a/app/views/tasks/user.html.haml b/app/views/tasks/user.html.haml index 52e2e1d1..b03c8251 100644 --- a/app/views/tasks/user.html.haml +++ b/app/views/tasks/user.html.haml @@ -1,27 +1,20 @@ - title "Meine Aufgaben" -= render :partial => "nav" +- content_for :sidebar do + = render 'nav' + +- unless @unaccepted_tasks.empty? + %section + %h3 Offene Aufgaben + = render 'list', tasks: @unaccepted_tasks + +%section + %h3 Anstehende Aufgaben + - unless @accepted_tasks.empty? + = render 'list', tasks: @accepted_tasks + - else + Nichts zu tun? + = link_to "Hier", tasks_path + gibt es bestimmt Arbeit. + %br/ + = link_to_top -- @tasks = @unaccepted_tasks -- unless @tasks.empty? - .left_column{:style => "width:75%"} - .box_title - %h2 Offene Aufgaben - .column_content - = render :partial => "list" - - -.left_column{:style => "width:75%"} - .box_title - %h2 Anstehende Aufgaben - .column_content - - @tasks = @accepted_tasks - - unless @tasks.empty? - = render :partial => "list" - - else - Nichts zu tun? - = link_to "Hier", :action => "index" - gibt es bestimmt Arbeit. - %br/ - = link_to_top - - \ No newline at end of file diff --git a/app/views/tasks/workgroup.haml b/app/views/tasks/workgroup.haml index 5d74b50e..2bbb79c9 100644 --- a/app/views/tasks/workgroup.haml +++ b/app/views/tasks/workgroup.haml @@ -1,27 +1,22 @@ -%h1 - Aufgaben verwalten für - = @group.name -= render :partial => "nav" +- title "Aufgaben für #{@group.name}" +- content_for :sidebar do + = render 'nav' -.left_column{:style => "width:45em"} - .box_title - %h2 Wöchentliche Aufgaben verwalten - .column_content - - if @group.weekly_task - %p Jeden #{weekday(@group.weekday)} hat diese Arbeitsgruppe folgenden Job: #{@group.task_name} - %p Die Wochenaufgaben werden von der Foodsoft automatisch erstellt. Eintragen müsst Ihr Euch aber selber. - - else - Noch keine Wochenaufgaben angelegt. - - if @current_user.member_of?(@group) or @current_user.role_admin? - = link_to "Wochenaufgaben bearbeiten", edit_foodcoop_workgroup_path(@group) +%section.well + %h3 Wöchentliche Aufgaben + - if @group.weekly_task + %p Jeden #{weekday(@group.weekday)} hat diese Arbeitsgruppe folgenden Job: #{@group.task_name} + %p Die Wochenaufgaben werden von der Foodsoft automatisch erstellt. Eintragen müsst Ihr Euch aber selber. + - else + Noch keine Wochenaufgaben angelegt. -.left_column{:style => "width:75%"} - .box_title - %h2 Alle Aufgaben der Gruppe - .column_content - - @tasks = @group.open_tasks - = render :partial => "list" - %br/ - = link_to_top + - if @current_user.member_of?(@group) or @current_user.role_admin? + = link_to "Wöchentliche Aufgaben anpassen", edit_foodcoop_workgroup_path(@group), class: 'btn' + +%section + %h3 Alle Aufgaben der Gruppe + = render 'list', tasks: @group.open_tasks + %br/ + = link_to_top \ No newline at end of file diff --git a/config/locales/de.yml b/config/locales/de.yml index 36f8f7d1..5f955591 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -325,6 +325,7 @@ de: required_users: 'Anzahl' due_date: 'Wann erledigen?' workgroup: 'Arbeitsgruppe' + done: Erledigt? message: sent_to_all: 'An alle Mitglieder schicken' recipient_tokens: 'Empfänger_innen' diff --git a/config/navigation.rb b/config/navigation.rb index 87836bdb..0011b6dc 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -28,7 +28,7 @@ SimpleNavigation::Configuration.run do |navigation| if: Proc.new { current_user.role_article_meta? or current_user.role_suppliers? } do |subnav| subnav.item :suppliers, 'Lieferanten/Artikel', suppliers_path, id: nil subnav.item :stockit, 'Lager', stock_articles_path, id: nil - subnav.item :categories, 'Kategorien', id: nil + subnav.item :categories, 'Kategorien', article_categories_path, id: nil end primary.item :finance, 'Finanzen', '#', if: Proc.new { current_user.role_finance? } do |subnav| diff --git a/config/routes.rb b/config/routes.rb index c412eec6..c83241ff 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -62,6 +62,11 @@ Foodsoft::Application.routes.draw do get :archive get :workgroup end + member do + post :accept + post :reject + post :set_done + end end resources :messages, :only => [:index, :show, :new, :create] diff --git a/vendor/assets/javascripts/bootstrap-datepicker.de.js b/vendor/assets/javascripts/bootstrap-datepicker.de.js new file mode 100644 index 00000000..ca0c33f8 --- /dev/null +++ b/vendor/assets/javascripts/bootstrap-datepicker.de.js @@ -0,0 +1,14 @@ +/** + * German translation for bootstrap-datepicker + * Sam Zurcher + */ +;(function($){ + $.fn.datepicker.dates['de'] = { + days: ["Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag", "Sonntag"], + daysShort: ["Son", "Mon", "Die", "Mit", "Don", "Fre", "Sam", "Son"], + daysMin: ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"], + months: ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"], + monthsShort: ["Jan", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"], + today: "Heute" + }; +}(jQuery)); diff --git a/vendor/assets/javascripts/bootstrap-datepicker.js b/vendor/assets/javascripts/bootstrap-datepicker.js new file mode 100644 index 00000000..378642cd --- /dev/null +++ b/vendor/assets/javascripts/bootstrap-datepicker.js @@ -0,0 +1,818 @@ +/* ========================================================= + * bootstrap-datepicker.js + * http://www.eyecon.ro/bootstrap-datepicker + * ========================================================= + * Copyright 2012 Stefan Petre + * Improvements by Andrew Rowls + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================= */ + +!function( $ ) { + + function UTCDate(){ + return new Date(Date.UTC.apply(Date, arguments)); + } + function UTCToday(){ + var today = new Date(); + return UTCDate(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate()); + } + + // Picker object + + var Datepicker = function(element, options) { + var that = this; + + this.element = $(element); + this.language = options.language||this.element.data('date-language')||"en"; + this.language = this.language in dates ? this.language : "en"; + this.format = DPGlobal.parseFormat(options.format||this.element.data('date-format')||'mm/dd/yyyy'); + this.picker = $(DPGlobal.template) + .appendTo('body') + .on({ + click: $.proxy(this.click, this) + }); + this.isInput = this.element.is('input'); + this.component = this.element.is('.date') ? this.element.find('.add-on') : false; + this.hasInput = this.component && this.element.find('input').length; + if(this.component && this.component.length === 0) + this.component = false; + + if (this.isInput) { + this.element.on({ + focus: $.proxy(this.show, this), + keyup: $.proxy(this.update, this), + keydown: $.proxy(this.keydown, this) + }); + } else { + if (this.component && this.hasInput){ + // For components that are not readonly, allow keyboard nav + this.element.find('input').on({ + focus: $.proxy(this.show, this), + keyup: $.proxy(this.update, this), + keydown: $.proxy(this.keydown, this) + }); + + this.component.on('click', $.proxy(this.show, this)); + } else { + this.element.on('click', $.proxy(this.show, this)); + } + } + + $(document).on('mousedown', function (e) { + // Clicked outside the datepicker, hide it + if ($(e.target).closest('.datepicker').length == 0) { + that.hide(); + } + }); + + this.autoclose = false; + if ('autoclose' in options) { + this.autoclose = options.autoclose; + } else if ('dateAutoclose' in this.element.data()) { + this.autoclose = this.element.data('date-autoclose'); + } + + this.keyboardNavigation = true; + if ('keyboardNavigation' in options) { + this.keyboardNavigation = options.keyboardNavigation; + } else if ('dateKeyboardNavigation' in this.element.data()) { + this.keyboardNavigation = this.element.data('date-keyboard-navigation'); + } + + switch(options.startView || this.element.data('date-start-view')){ + case 2: + case 'decade': + this.viewMode = this.startViewMode = 2; + break; + case 1: + case 'year': + this.viewMode = this.startViewMode = 1; + break; + case 0: + case 'month': + default: + this.viewMode = this.startViewMode = 0; + break; + } + + this.todayBtn = (options.todayBtn||this.element.data('date-today-btn')||false); + this.todayHighlight = (options.todayHighlight||this.element.data('date-today-highlight')||false); + + this.weekStart = ((options.weekStart||this.element.data('date-weekstart')||dates[this.language].weekStart||0) % 7); + this.weekEnd = ((this.weekStart + 6) % 7); + this.startDate = -Infinity; + this.endDate = Infinity; + this.setStartDate(options.startDate||this.element.data('date-startdate')); + this.setEndDate(options.endDate||this.element.data('date-enddate')); + this.fillDow(); + this.fillMonths(); + this.update(); + this.showMode(); + }; + + Datepicker.prototype = { + constructor: Datepicker, + + show: function(e) { + this.picker.show(); + this.height = this.component ? this.component.outerHeight() : this.element.outerHeight(); + this.update(); + this.place(); + $(window).on('resize', $.proxy(this.place, this)); + if (e ) { + e.stopPropagation(); + e.preventDefault(); + } + this.element.trigger({ + type: 'show', + date: this.date + }); + }, + + hide: function(e){ + this.picker.hide(); + $(window).off('resize', this.place); + this.viewMode = this.startViewMode; + this.showMode(); + if (!this.isInput) { + $(document).off('mousedown', this.hide); + } + if (e && e.currentTarget.value) + this.setValue(); + this.element.trigger({ + type: 'hide', + date: this.date + }); + }, + + setValue: function() { + var formatted = DPGlobal.formatDate(this.date, this.format, this.language); + if (!this.isInput) { + if (this.component){ + this.element.find('input').prop('value', formatted); + } + this.element.data('date', formatted); + } else { + this.element.prop('value', formatted); + } + }, + + setStartDate: function(startDate){ + this.startDate = startDate||-Infinity; + if (this.startDate !== -Infinity) { + this.startDate = DPGlobal.parseDate(this.startDate, this.format, this.language); + } + this.update(); + this.updateNavArrows(); + }, + + setEndDate: function(endDate){ + this.endDate = endDate||Infinity; + if (this.endDate !== Infinity) { + this.endDate = DPGlobal.parseDate(this.endDate, this.format, this.language); + } + this.update(); + this.updateNavArrows(); + }, + + place: function(){ + var zIndex = parseInt(this.element.parents().filter(function() { + return $(this).css('z-index') != 'auto'; + }).first().css('z-index'))+10; + var offset = this.component ? this.component.offset() : this.element.offset(); + this.picker.css({ + top: offset.top + this.height, + left: offset.left, + zIndex: zIndex + }); + }, + + update: function(){ + this.date = DPGlobal.parseDate( + this.isInput ? this.element.prop('value') : this.element.data('date') || this.element.find('input').prop('value'), + this.format, this.language + ); + if (this.date < this.startDate) { + this.viewDate = new Date(this.startDate); + } else if (this.date > this.endDate) { + this.viewDate = new Date(this.endDate); + } else { + this.viewDate = new Date(this.date); + } + this.fill(); + }, + + fillDow: function(){ + var dowCnt = this.weekStart; + var html = ''; + while (dowCnt < this.weekStart + 7) { + html += ''+dates[this.language].daysMin[(dowCnt++)%7]+''; + } + html += ''; + this.picker.find('.datepicker-days thead').append(html); + }, + + fillMonths: function(){ + var html = ''; + var i = 0 + while (i < 12) { + html += ''+dates[this.language].monthsShort[i++]+''; + } + this.picker.find('.datepicker-months td').html(html); + }, + + fill: function() { + var d = new Date(this.viewDate), + year = d.getUTCFullYear(), + month = d.getUTCMonth(), + startYear = this.startDate !== -Infinity ? this.startDate.getUTCFullYear() : -Infinity, + startMonth = this.startDate !== -Infinity ? this.startDate.getUTCMonth() : -Infinity, + endYear = this.endDate !== Infinity ? this.endDate.getUTCFullYear() : Infinity, + endMonth = this.endDate !== Infinity ? this.endDate.getUTCMonth() : Infinity, + currentDate = this.date.valueOf(), + today = new Date(); + this.picker.find('.datepicker-days thead th:eq(1)') + .text(dates[this.language].months[month]+' '+year); + this.picker.find('tfoot th.today') + .text(dates[this.language].today) + .toggle(this.todayBtn); + this.updateNavArrows(); + this.fillMonths(); + var prevMonth = UTCDate(year, month-1, 28,0,0,0,0), + day = DPGlobal.getDaysInMonth(prevMonth.getUTCFullYear(), prevMonth.getUTCMonth()); + prevMonth.setUTCDate(day); + prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.weekStart + 7)%7); + var nextMonth = new Date(prevMonth); + nextMonth.setUTCDate(nextMonth.getUTCDate() + 42); + nextMonth = nextMonth.valueOf(); + var html = []; + var clsName; + while(prevMonth.valueOf() < nextMonth) { + if (prevMonth.getUTCDay() == this.weekStart) { + html.push(''); + } + clsName = ''; + if (prevMonth.getUTCFullYear() < year || (prevMonth.getUTCFullYear() == year && prevMonth.getUTCMonth() < month)) { + clsName += ' old'; + } else if (prevMonth.getUTCFullYear() > year || (prevMonth.getUTCFullYear() == year && prevMonth.getUTCMonth() > month)) { + clsName += ' new'; + } + // Compare internal UTC date with local today, not UTC today + if (this.todayHighlight && + prevMonth.getUTCFullYear() == today.getFullYear() && + prevMonth.getUTCMonth() == today.getMonth() && + prevMonth.getUTCDate() == today.getDate()) { + clsName += ' today'; + } + if (prevMonth.valueOf() == currentDate) { + clsName += ' active'; + } + if (prevMonth.valueOf() < this.startDate || prevMonth.valueOf() > this.endDate) { + clsName += ' disabled'; + } + html.push(''+prevMonth.getUTCDate() + ''); + if (prevMonth.getUTCDay() == this.weekEnd) { + html.push(''); + } + prevMonth.setUTCDate(prevMonth.getUTCDate()+1); + } + this.picker.find('.datepicker-days tbody').empty().append(html.join('')); + var currentYear = this.date.getUTCFullYear(); + + var months = this.picker.find('.datepicker-months') + .find('th:eq(1)') + .text(year) + .end() + .find('span').removeClass('active'); + if (currentYear == year) { + months.eq(this.date.getUTCMonth()).addClass('active'); + } + if (year < startYear || year > endYear) { + months.addClass('disabled'); + } + if (year == startYear) { + months.slice(0, startMonth).addClass('disabled'); + } + if (year == endYear) { + months.slice(endMonth+1).addClass('disabled'); + } + + html = ''; + year = parseInt(year/10, 10) * 10; + var yearCont = this.picker.find('.datepicker-years') + .find('th:eq(1)') + .text(year + '-' + (year + 9)) + .end() + .find('td'); + year -= 1; + for (var i = -1; i < 11; i++) { + html += ''+year+''; + year += 1; + } + yearCont.html(html); + }, + + updateNavArrows: function() { + var d = new Date(this.viewDate), + year = d.getUTCFullYear(), + month = d.getUTCMonth(); + switch (this.viewMode) { + case 0: + if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear() && month <= this.startDate.getUTCMonth()) { + this.picker.find('.prev').css({visibility: 'hidden'}); + } else { + this.picker.find('.prev').css({visibility: 'visible'}); + } + if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear() && month >= this.endDate.getUTCMonth()) { + this.picker.find('.next').css({visibility: 'hidden'}); + } else { + this.picker.find('.next').css({visibility: 'visible'}); + } + break; + case 1: + case 2: + if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear()) { + this.picker.find('.prev').css({visibility: 'hidden'}); + } else { + this.picker.find('.prev').css({visibility: 'visible'}); + } + if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear()) { + this.picker.find('.next').css({visibility: 'hidden'}); + } else { + this.picker.find('.next').css({visibility: 'visible'}); + } + break; + } + }, + + click: function(e) { + e.stopPropagation(); + e.preventDefault(); + var target = $(e.target).closest('span, td, th'); + if (target.length == 1) { + switch(target[0].nodeName.toLowerCase()) { + case 'th': + switch(target[0].className) { + case 'switch': + this.showMode(1); + break; + case 'prev': + case 'next': + var dir = DPGlobal.modes[this.viewMode].navStep * (target[0].className == 'prev' ? -1 : 1); + switch(this.viewMode){ + case 0: + this.viewDate = this.moveMonth(this.viewDate, dir); + break; + case 1: + case 2: + this.viewDate = this.moveYear(this.viewDate, dir); + break; + } + this.fill(); + break; + case 'today': + var date = new Date(); + date.setUTCHours(0); + date.setUTCMinutes(0); + date.setUTCSeconds(0); + date.setUTCMilliseconds(0); + + this.showMode(-2); + var which = this.todayBtn == 'linked' ? null : 'view'; + this._setDate(date, which); + break; + } + break; + case 'span': + if (!target.is('.disabled')) { + this.viewDate.setUTCDate(1); + if (target.is('.month')) { + var month = target.parent().find('span').index(target); + this.viewDate.setUTCMonth(month); + this.element.trigger({ + type: 'changeMonth', + date: this.viewDate + }); + } else { + var year = parseInt(target.text(), 10)||0; + this.viewDate.setUTCFullYear(year); + this.element.trigger({ + type: 'changeYear', + date: this.viewDate + }); + } + this.showMode(-1); + this.fill(); + } + break; + case 'td': + if (target.is('.day') && !target.is('.disabled')){ + var day = parseInt(target.text(), 10)||1; + var year = this.viewDate.getUTCFullYear(), + month = this.viewDate.getUTCMonth(); + if (target.is('.old')) { + if (month == 0) { + month = 11; + year -= 1; + } else { + month -= 1; + } + } else if (target.is('.new')) { + if (month == 11) { + month = 0; + year += 1; + } else { + month += 1; + } + } + this._setDate(UTCDate(year, month, day,0,0,0,0)); + } + break; + } + } + }, + + _setDate: function(date, which){ + if (!which || which == 'date') + this.date = date; + if (!which || which == 'view') + this.viewDate = date; + this.fill(); + this.setValue(); + this.element.trigger({ + type: 'changeDate', + date: this.date + }); + var element; + if (this.isInput) { + element = this.element; + } else if (this.component){ + element = this.element.find('input'); + } + if (element) { + element.change(); + if (this.autoclose) { + this.hide(); + } + } + }, + + moveMonth: function(date, dir){ + if (!dir) return date; + var new_date = new Date(date.valueOf()), + day = new_date.getUTCDate(), + month = new_date.getUTCMonth(), + mag = Math.abs(dir), + new_month, test; + dir = dir > 0 ? 1 : -1; + if (mag == 1){ + test = dir == -1 + // If going back one month, make sure month is not current month + // (eg, Mar 31 -> Feb 31 == Feb 28, not Mar 02) + ? function(){ return new_date.getUTCMonth() == month; } + // If going forward one month, make sure month is as expected + // (eg, Jan 31 -> Feb 31 == Feb 28, not Mar 02) + : function(){ return new_date.getUTCMonth() != new_month; }; + new_month = month + dir; + new_date.setUTCMonth(new_month); + // Dec -> Jan (12) or Jan -> Dec (-1) -- limit expected date to 0-11 + if (new_month < 0 || new_month > 11) + new_month = (new_month + 12) % 12; + } else { + // For magnitudes >1, move one month at a time... + for (var i=0; i= this.startDate && date <= this.endDate; + }, + + keydown: function(e){ + if (this.picker.is(':not(:visible)')){ + if (e.keyCode == 27) // allow escape to hide and re-show picker + this.show(); + return; + } + var dateChanged = false, + dir, day, month, + newDate, newViewDate; + switch(e.keyCode){ + case 27: // escape + this.hide(); + e.preventDefault(); + break; + case 37: // left + case 39: // right + if (!this.keyboardNavigation) break; + dir = e.keyCode == 37 ? -1 : 1; + if (e.ctrlKey){ + newDate = this.moveYear(this.date, dir); + newViewDate = this.moveYear(this.viewDate, dir); + } else if (e.shiftKey){ + newDate = this.moveMonth(this.date, dir); + newViewDate = this.moveMonth(this.viewDate, dir); + } else { + newDate = new Date(this.date); + newDate.setUTCDate(this.date.getUTCDate() + dir); + newViewDate = new Date(this.viewDate); + newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir); + } + if (this.dateWithinRange(newDate)){ + this.date = newDate; + this.viewDate = newViewDate; + this.setValue(); + this.update(); + e.preventDefault(); + dateChanged = true; + } + break; + case 38: // up + case 40: // down + if (!this.keyboardNavigation) break; + dir = e.keyCode == 38 ? -1 : 1; + if (e.ctrlKey){ + newDate = this.moveYear(this.date, dir); + newViewDate = this.moveYear(this.viewDate, dir); + } else if (e.shiftKey){ + newDate = this.moveMonth(this.date, dir); + newViewDate = this.moveMonth(this.viewDate, dir); + } else { + newDate = new Date(this.date); + newDate.setUTCDate(this.date.getUTCDate() + dir * 7); + newViewDate = new Date(this.viewDate); + newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir * 7); + } + if (this.dateWithinRange(newDate)){ + this.date = newDate; + this.viewDate = newViewDate; + this.setValue(); + this.update(); + e.preventDefault(); + dateChanged = true; + } + break; + case 13: // enter + this.hide(); + e.preventDefault(); + break; + case 9: // tab + this.hide(); + break; + } + if (dateChanged){ + this.element.trigger({ + type: 'changeDate', + date: this.date + }); + var element; + if (this.isInput) { + element = this.element; + } else if (this.component){ + element = this.element.find('input'); + } + if (element) { + element.change(); + } + } + }, + + showMode: function(dir) { + if (dir) { + this.viewMode = Math.max(0, Math.min(2, this.viewMode + dir)); + } + this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).show(); + this.updateNavArrows(); + } + }; + + $.fn.datepicker = function ( option ) { + var args = Array.apply(null, arguments); + args.shift(); + return this.each(function () { + var $this = $(this), + data = $this.data('datepicker'), + options = typeof option == 'object' && option; + if (!data) { + $this.data('datepicker', (data = new Datepicker(this, $.extend({}, $.fn.datepicker.defaults,options)))); + } + if (typeof option == 'string' && typeof data[option] == 'function') { + data[option].apply(data, args); + } + }); + }; + + $.fn.datepicker.defaults = { + }; + $.fn.datepicker.Constructor = Datepicker; + var dates = $.fn.datepicker.dates = { + en: { + days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"], + daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], + daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"], + months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], + monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], + today: "Today" + } + } + + var DPGlobal = { + modes: [ + { + clsName: 'days', + navFnc: 'Month', + navStep: 1 + }, + { + clsName: 'months', + navFnc: 'FullYear', + navStep: 1 + }, + { + clsName: 'years', + navFnc: 'FullYear', + navStep: 10 + }], + isLeapYear: function (year) { + return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0)) + }, + getDaysInMonth: function (year, month) { + return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month] + }, + validParts: /dd?|mm?|MM?|yy(?:yy)?/g, + nonpunctuation: /[^ -\/:-@\[-`{-~\t\n\r]+/g, + parseFormat: function(format){ + // IE treats \0 as a string end in inputs (truncating the value), + // so it's a bad format delimiter, anyway + var separators = format.replace(this.validParts, '\0').split('\0'), + parts = format.match(this.validParts); + if (!separators || !separators.length || !parts || parts.length == 0){ + throw new Error("Invalid date format."); + } + return {separators: separators, parts: parts}; + }, + parseDate: function(date, format, language) { + if (date instanceof Date) return date; + if (/^[-+]\d+[dmwy]([\s,]+[-+]\d+[dmwy])*$/.test(date)) { + var part_re = /([-+]\d+)([dmwy])/, + parts = date.match(/([-+]\d+)([dmwy])/g), + part, dir; + date = new Date(); + for (var i=0; i'+ + ''+ + ''+ + ''+ + ''+ + ''+ + '', + contTemplate: '', + footTemplate: '' + }; + DPGlobal.template = ''; +}( window.jQuery );