Maarten van den Berg лет назад: 8
Родитель
Сommit
c9183e5828

+ 73 - 0
app/assets/javascripts/buttonhandlers.js

1
+// Provides handlers for the buttons!
2
+
3
+$(setup_handlers);
4
+
5
+function setup_handlers()
6
+{
7
+  $('.btn-present').on("click", set_present);
8
+  $('.btn-absent').on("click", set_absent);
9
+}
10
+
11
+// Update all references on the page to this activity:
12
+// 1. The present/absent buttons
13
+// 2. The activity's row-color in any tables
14
+function activity_changed(activity_id, new_state)
15
+{
16
+    // Set the present buttons and absent buttons to their appropriate state
17
+
18
+}
19
+
20
+function set_present(e)
21
+{
22
+  var group, person, activity;
23
+  group = this.dataset["groupId"];
24
+  person = this.dataset["personId"];
25
+  activity = this.dataset["activityId"];
26
+  $.ajax(`/groups/${group}/activities/${activity}/presence`,
27
+    {
28
+      method: 'PUT',
29
+      data: {person_id: person, attending: true}
30
+    }
31
+  );
32
+
33
+  // Set row state to success
34
+  $(`tr[data-person-id=${person}][data-activity-id=${activity}]`)
35
+    .removeClass('danger warning')
36
+    .addClass('success');
37
+
38
+  // Update present buttons
39
+  $(`.btn-present[data-person-id=${person}][data-activity-id=${activity}]`)
40
+    .html(check_selected);
41
+  $(`.btn-absent[data-person-id=${person}][data-activity-id=${activity}]`)
42
+    .html(times_unselected);
43
+}
44
+
45
+function set_absent()
46
+{
47
+  var group, person, activity;
48
+  group = this.dataset["groupId"];
49
+  person = this.dataset["personId"];
50
+  activity = this.dataset["activityId"];
51
+  $.ajax(`/groups/${group}/activities/${activity}/presence`,
52
+    {
53
+      method: 'PUT',
54
+      data: {person_id: person, attending: false}
55
+    }
56
+  );
57
+
58
+  // Set row state to danger
59
+  $(`tr[data-person-id=${person}][data-activity-id=${activity}]`)
60
+    .removeClass('success warning')
61
+    .addClass('danger');
62
+
63
+  // Update present buttons
64
+  $(`.btn-present[data-person-id=${person}][data-activity-id=${activity}]`)
65
+    .html(check_unselected);
66
+  $(`.btn-absent[data-person-id=${person}][data-activity-id=${activity}]`)
67
+    .html(times_selected);
68
+}
69
+
70
+var check_unselected = '<i class="fa fa-check"></i>';
71
+var check_selected = '<i class="fa fa-check-circle"></i>';
72
+var times_unselected = '<i class="fa fa-times"></i>';
73
+var times_selected = '<i class="fa fa-times-circle"></i>';

+ 15 - 1
app/controllers/activities_controller.rb

1
 class ActivitiesController < ApplicationController
1
 class ActivitiesController < ApplicationController
2
   include GroupsHelper
2
   include GroupsHelper
3
-  before_action :set_activity, only: [:show, :edit, :update, :destroy]
3
+  before_action :set_activity, only: [:show, :edit, :update, :destroy, :presence]
4
   before_action :set_group
4
   before_action :set_group
5
   before_action :require_membership!
5
   before_action :require_membership!
6
 
6
 
65
     end
65
     end
66
   end
66
   end
67
 
67
 
68
+  # PATCH/PUT /groups/:group_id/activities/:id/presence
69
+  # PATCH/PUT /groups/:group_id/activities/:id/presence.json
70
+  def presence
71
+    participant = Participant.find_by(
72
+      person_id: params[:person_id],
73
+      activity: @activity
74
+    )
75
+    if !@activity.may_change?(current_person)
76
+      render status: :forbidden
77
+    end
78
+
79
+    participant.update_attributes(params.permit(:notes, :attending))
80
+  end
81
+
68
   private
82
   private
69
     # Use callbacks to share common setup or constraints between actions.
83
     # Use callbacks to share common setup or constraints between actions.
70
     def set_activity
84
     def set_activity

+ 6 - 2
app/controllers/dashboard_controller.rb

2
   before_action :require_login!
2
   before_action :require_login!
3
 
3
 
4
   def home
4
   def home
5
-    @user_organized = current_person.activities.where(is_organizer: true)
6
-    @need_response = current_person.activities.includes(:participants)
5
+    @user_organized = current_person.participants.includes(:activity).where(is_organizer: true)
6
+    @need_response = current_person.participants.includes(:activity).where(attending: nil)
7
+    @upcoming = current_person.activities.where('start > ?', DateTime.now).includes(:participants)
8
+    @upcoming = current_person.participants
9
+      .includes(:activity).joins(:activity)
10
+      .where("activities.start >= ?", DateTime.now)
7
   end
11
   end
8
 end
12
 end

+ 7 - 0
app/models/activity.rb

76
     )
76
     )
77
   end
77
   end
78
 
78
 
79
+  # Determine whether the passed Person may change this activity.
80
+  def may_change?(person)
81
+    person.is_admin ||
82
+    self.is_organizer(person) ||
83
+    self.group.is_leader(person)
84
+  end
85
+
79
   private
86
   private
80
   # Assert that the deadline for participants to change the deadline, if any,
87
   # Assert that the deadline for participants to change the deadline, if any,
81
   # is set before the event starts.
88
   # is set before the event starts.

+ 9 - 0
app/models/group.rb

21
   def leaders
21
   def leaders
22
     self.members.includes(:person).where(is_leader: true)
22
     self.members.includes(:person).where(is_leader: true)
23
   end
23
   end
24
+
25
+  # Determine whether the passed person is a group leader.
26
+  def is_leader?(person)
27
+    Member.exists?(
28
+      person: person,
29
+      group: self,
30
+      is_leader: true
31
+    )
32
+  end
24
 end
33
 end

+ 13 - 0
app/models/participant.rb

23
       scope: :activity_id,
23
       scope: :activity_id,
24
       message: "person already participates in this activity"
24
       message: "person already participates in this activity"
25
     }
25
     }
26
+
27
+  # TODO: Move to a more appropriate place
28
+  # @return [String]
29
+  #   the class for a row containing this activity.
30
+  def row_class
31
+    if self.attending
32
+      "success"
33
+    elsif self.attending == false
34
+      "danger"
35
+    else
36
+      "warning"
37
+    end
38
+  end
26
 end
39
 end

+ 12 - 0
app/views/activities/_presence_buttons.haml

1
+.btn-group.btn-group-xs{role: "group"}
2
+  %button.btn.btn-success.btn-present{data: {person_id: person.id, activity_id: activity.id, group_id: activity.group.id}}
3
+    - if !state
4
+      %i.fa.fa-check
5
+    - else
6
+      %i.fa.fa-check-circle
7
+
8
+  %button.btn.btn-danger.btn-absent{data: {person_id: person.id, activity_id: activity.id, group_id: activity.group.id}}
9
+    - if state == false
10
+      %i.fa.fa-times-circle
11
+    - else
12
+      %i.fa.fa-times

+ 14 - 5
app/views/activities/show.html.erb

9
 </ul>
9
 </ul>
10
 
10
 
11
 <h2>Participants (<%= @activity.participants.count %>)</h2>
11
 <h2>Participants (<%= @activity.participants.count %>)</h2>
12
-<ul>
12
+<table class="table table-bordered">
13
   <% @activity.participants.each do |p| %>
13
   <% @activity.participants.each do |p| %>
14
-    <li>
15
-      <%= p.person.full_name %>, <%= p.is_organizer %>, <%= p.attending %>
16
-    </li>
14
+    <tr class="<%= p.row_class %>" data-person-id="<%= p.person.id %>" data-activity-id="<%= @activity.id %>">
15
+      <td>
16
+        <%= p.person.full_name %>
17
+      </td>
18
+      <td>
19
+        <%= p.is_organizer %>
20
+      </td>
21
+      <td>
22
+        <%= p.attending %>
23
+      </td>
24
+      <td>
25
+        <%= render partial: "activities/presence_buttons", locals: {activity: @activity, person: p.person, state: p.attending} %>
17
   <% end %>
26
   <% end %>
18
-</ul>
27
+</table>
19
 
28
 
20
 <%= link_to 'Edit', edit_group_activity_path(@group, @activity) %> |
29
 <%= link_to 'Edit', edit_group_activity_path(@group, @activity) %> |
21
 <%= link_to 'Back', group_activities_path(@group) %>
30
 <%= link_to 'Back', group_activities_path(@group) %>

+ 39 - 6
app/views/dashboard/home.html.haml

1
 .container
1
 .container
2
   .row
2
   .row
3
     - if @need_response.any?
3
     - if @need_response.any?
4
-      .col-md.12
4
+      .col-md-12
5
         .panel.panel-default
5
         .panel.panel-default
6
           .panel-heading
6
           .panel-heading
7
             Need response
7
             Need response
22
                     Actions
22
                     Actions
23
 
23
 
24
               %tbody
24
               %tbody
25
-                - @need_response.each do |e|
26
-                  %tr
25
+                - @need_response.each do |p|
26
+                  - e = p.activity
27
+                  %tr{class: p.row_class, data: {activity_id: e.id}}
27
                     %td
28
                     %td
28
-                      - if e.secret_name && e.participants.first.is_organizer
29
-                        = e.secret_name
30
                       = e.public_name
29
                       = e.public_name
30
+                      - if e.secret_name && e.participants.first.is_organizer
31
+                        %i
32
+                          = "(#{e.secret_name})"
31
                     %td
33
                     %td
32
                       = e.group.name
34
                       = e.group.name
33
                     %td
35
                     %td
35
                     %td
37
                     %td
36
                       = e.location
38
                       = e.location
37
                     %td
39
                     %td
40
+                      = render partial: "activities/presence_buttons", locals: {activity: e, person: current_person, state: p.attending}
41
+  .row
38
     .col-md-6
42
     .col-md-6
39
       .panel.panel-default
43
       .panel.panel-default
40
         .panel-heading
44
         .panel-heading
50
                       = group.name
54
                       = group.name
51
 
55
 
52
     .col-md-6
56
     .col-md-6
53
-      TODO
57
+      .panel.panel-default
58
+        .panel-heading
59
+          Organized by you
60
+
61
+        .panel-body
62
+          %table.table.table-striped.table-bordered
63
+            %tbody
64
+              - @user_organized.each do |p|
65
+                - a = p.activity
66
+                %tr
67
+                  %td
68
+                    = link_to group_activity_url(a.group, a) do
69
+                      = "#{a.public_name} (#{a.secret_name})"
70
+
71
+  .row
72
+    .col-md-12
73
+      .panel.panel-default
74
+        .panel-heading
75
+          Your activities
76
+
77
+        .panel-body
78
+          %table.table.table-striped
79
+            %tbody
80
+              - @upcoming.each do |p|
81
+                - e = p.activity
82
+                %tr{class: p.row_class, data: {activity_id: e.id}}
83
+                  %td
84
+                    = e.public_name
85
+                  %td
86
+                    = render partial: "activities/presence_buttons", locals: {activity: e, person: current_person, state: p.attending}

+ 4 - 1
config/routes.rb

19
 
19
 
20
   resources :groups do
20
   resources :groups do
21
     resources :members
21
     resources :members
22
-    resources :activities
22
+    resources :activities do
23
+      put 'presence', to: 'activities#presence', on: :member
24
+      patch 'presence', to: 'activities#presence', on: :member
25
+    end
23
   end
26
   end
24
   get 'my_groups', to: 'groups#user_groups', as: :user_groups
27
   get 'my_groups', to: 'groups#user_groups', as: :user_groups
25
 
28