decidim-meetings 0.13.1 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/config/admin/decidim_meetings_manifest.js +1 -0
  3. data/app/assets/javascripts/decidim/meetings/admin/registrations_invite_form.es6 +25 -0
  4. data/app/cells/decidim/meetings/join_meeting_button/show.erb +2 -0
  5. data/app/cells/decidim/meetings/meeting_cell.rb +1 -1
  6. data/app/commands/decidim/meetings/admin/invite_user_to_join_meeting.rb +57 -8
  7. data/app/commands/decidim/meetings/admin/validate_registration_code.rb +51 -0
  8. data/app/commands/decidim/meetings/decline_invitation.rb +45 -0
  9. data/app/commands/decidim/meetings/join_meeting.rb +8 -3
  10. data/app/controllers/decidim/meetings/admin/attachment_collections_controller.rb +0 -4
  11. data/app/controllers/decidim/meetings/admin/attachments_controller.rb +0 -4
  12. data/app/controllers/decidim/meetings/admin/invites_controller.rb +14 -5
  13. data/app/controllers/decidim/meetings/admin/registrations_controller.rb +20 -0
  14. data/app/controllers/decidim/meetings/registrations_controller.rb +16 -0
  15. data/app/events/decidim/meetings/registration_code_validated_event.rb +15 -0
  16. data/app/forms/decidim/meetings/admin/close_meeting_form.rb +1 -1
  17. data/app/forms/decidim/meetings/admin/meeting_registration_invite_form.rb +9 -2
  18. data/app/forms/decidim/meetings/admin/validate_registration_code_form.rb +33 -0
  19. data/app/mailers/decidim/meetings/registration_mailer.rb +2 -1
  20. data/app/models/decidim/meetings/invite.rb +38 -0
  21. data/app/models/decidim/meetings/meeting.rb +6 -0
  22. data/app/models/decidim/meetings/registration.rb +18 -0
  23. data/app/permissions/decidim/meetings/admin/permissions.rb +2 -2
  24. data/app/permissions/decidim/meetings/permissions.rb +8 -1
  25. data/app/presenters/decidim/meetings/admin_log/invite_presenter.rb +35 -0
  26. data/app/presenters/decidim/meetings/invite_presenter.rb +26 -0
  27. data/app/queries/decidim/meetings/admin/invites.rb +59 -0
  28. data/app/serializers/decidim/meetings/data_portability_invite_serializer.rb +35 -0
  29. data/app/serializers/decidim/meetings/data_portability_registration_serializer.rb +1 -0
  30. data/app/serializers/decidim/meetings/registration_serializer.rb +1 -0
  31. data/app/views/decidim/meetings/admin/invite_join_meeting_mailer/invite.html.erb +1 -0
  32. data/app/views/decidim/meetings/admin/invites/_form.html.erb +34 -5
  33. data/app/views/decidim/meetings/admin/invites/index.html.erb +99 -0
  34. data/app/views/decidim/meetings/admin/meeting_copies/new.html.erb +1 -1
  35. data/app/views/decidim/meetings/admin/meetings/_form.html.erb +2 -3
  36. data/app/views/decidim/meetings/admin/meetings/index.html.erb +2 -0
  37. data/app/views/decidim/meetings/admin/registrations/_form.html.erb +1 -1
  38. data/app/views/decidim/meetings/admin/registrations/edit.html.erb +24 -0
  39. data/app/views/decidim/meetings/meetings/_meetings.html.erb +1 -1
  40. data/app/views/decidim/meetings/meetings/index.js.erb +0 -1
  41. data/app/views/decidim/meetings/meetings/show.html.erb +9 -0
  42. data/app/views/decidim/meetings/registration_mailer/confirmation.html.erb +2 -0
  43. data/app/views/decidim/participatory_spaces/_meeting.html.erb +1 -1
  44. data/app/views/devise/mailer/join_meeting.html.erb +1 -0
  45. data/app/views/devise/mailer/join_meeting.text.erb +4 -0
  46. data/config/locales/ca.yml +53 -5
  47. data/config/locales/en.yml +53 -5
  48. data/config/locales/es-PY.yml +52 -4
  49. data/config/locales/es.yml +53 -5
  50. data/config/locales/eu.yml +52 -4
  51. data/config/locales/fi.yml +164 -116
  52. data/config/locales/fr.yml +53 -5
  53. data/config/locales/gl.yml +52 -4
  54. data/config/locales/hu.yml +391 -0
  55. data/config/locales/it.yml +52 -4
  56. data/config/locales/nl.yml +52 -4
  57. data/config/locales/pl.yml +52 -4
  58. data/config/locales/pt-BR.yml +56 -8
  59. data/config/locales/pt.yml +52 -4
  60. data/config/locales/ru.yml +57 -9
  61. data/config/locales/sv.yml +135 -87
  62. data/config/locales/uk.yml +56 -8
  63. data/db/migrate/20180607142020_create_decidim_meetings_invites.rb +15 -0
  64. data/db/migrate/20180615160839_add_code_to_decidim_meetings_registrations.rb +7 -0
  65. data/db/migrate/20180711111023_add_validated_at_to_decidim_meetings_registrations.rb +7 -0
  66. data/lib/decidim/meetings.rb +1 -0
  67. data/lib/decidim/meetings/admin_engine.rb +3 -1
  68. data/lib/decidim/meetings/component.rb +2 -0
  69. data/lib/decidim/meetings/engine.rb +1 -0
  70. data/lib/decidim/meetings/registrations.rb +14 -0
  71. data/lib/decidim/meetings/registrations/code_generator.rb +39 -0
  72. data/lib/decidim/meetings/test/factories.rb +20 -4
  73. data/lib/decidim/meetings/version.rb +1 -1
  74. metadata +30 -14
  75. data/app/views/decidim/meetings/admin/invites/new.html.erb +0 -21
@@ -8,9 +8,16 @@ module Decidim
8
8
  class MeetingRegistrationInviteForm < Form
9
9
  attribute :name, String
10
10
  attribute :email, String
11
+ attribute :user_id, Integer
12
+ attribute :existing_user, Boolean, default: false
11
13
 
12
- validates :name, presence: true
13
- validates :email, presence: true, 'valid_email_2/email': { disposable: true }
14
+ validates :name, presence: true, unless: proc { |object| object.existing_user }
15
+ validates :email, presence: true, 'valid_email_2/email': { disposable: true }, unless: proc { |object| object.existing_user }
16
+ validates :user, presence: true, if: proc { |object| object.existing_user }
17
+
18
+ def user
19
+ @user ||= current_organization.users.find_by(id: user_id)
20
+ end
14
21
  end
15
22
  end
16
23
  end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Meetings
5
+ module Admin
6
+ # This class holds a Form to validate registration codes from Decidim's admin panel.
7
+ class ValidateRegistrationCodeForm < Decidim::Form
8
+ attribute :code, String
9
+
10
+ validates :code, presence: true
11
+ validate :registration_exists
12
+
13
+ def registration
14
+ @registration ||= meeting.registrations.find_by(code: code, validated_at: nil)
15
+ end
16
+
17
+ private
18
+
19
+ def meeting
20
+ @meeting ||= context[:meeting]
21
+ end
22
+
23
+ def registration_exists
24
+ return unless registration.nil?
25
+ errors.add(
26
+ :code,
27
+ I18n.t("registrations.validate_registration_code.invalid", scope: "decidim.meetings.admin")
28
+ )
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -11,10 +11,11 @@ module Decidim
11
11
  helper Decidim::ResourceHelper
12
12
  helper Decidim::TranslationsHelper
13
13
 
14
- def confirmation(user, meeting)
14
+ def confirmation(user, meeting, registration)
15
15
  with_user(user) do
16
16
  @user = user
17
17
  @meeting = meeting
18
+ @registration = registration
18
19
  @organization = @meeting.organization
19
20
  @locator = Decidim::ResourceLocatorPresenter.new(@meeting)
20
21
 
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Meetings
5
+ # The data store for an Invite in the Decidim::Meetings component.
6
+ class Invite < Meetings::ApplicationRecord
7
+ include Decidim::Traceable
8
+ include Decidim::Loggable
9
+ include Decidim::DataPortability
10
+
11
+ belongs_to :meeting, foreign_key: "decidim_meeting_id", class_name: "Decidim::Meetings::Meeting"
12
+ belongs_to :user, foreign_key: "decidim_user_id", class_name: "Decidim::User"
13
+
14
+ validates :user, uniqueness: { scope: :meeting }
15
+
16
+ def self.export_serializer
17
+ Decidim::Meetings::DataPortabilityInviteSerializer
18
+ end
19
+
20
+ def self.log_presenter_class_for(_log)
21
+ Decidim::Meetings::AdminLog::InvitePresenter
22
+ end
23
+
24
+ def self.user_collection(user)
25
+ where(decidim_user_id: user.id)
26
+ end
27
+
28
+ def accept!
29
+ update!(accepted_at: Time.current, rejected_at: nil)
30
+ end
31
+
32
+ def reject!
33
+ update!(rejected_at: Time.current, accepted_at: nil)
34
+ end
35
+ alias decline! reject!
36
+ end
37
+ end
38
+ end
@@ -20,6 +20,7 @@ module Decidim
20
20
 
21
21
  belongs_to :organizer, foreign_key: "organizer_id", class_name: "Decidim::User", optional: true
22
22
  has_many :registrations, class_name: "Decidim::Meetings::Registration", foreign_key: "decidim_meeting_id", dependent: :destroy
23
+ has_many :invites, class_name: "Decidim::Meetings::Invite", foreign_key: "decidim_meeting_id", dependent: :destroy
23
24
  has_one :minutes, class_name: "Decidim::Meetings::Minutes", foreign_key: "decidim_meeting_id", dependent: :destroy
24
25
  has_one :agenda, class_name: "Decidim::Meetings::Agenda", foreign_key: "decidim_meeting_id", dependent: :destroy
25
26
 
@@ -85,6 +86,11 @@ module Decidim
85
86
  commentable? && !component.current_settings.comments_blocked
86
87
  end
87
88
 
89
+ # Public: Overrides the `allow_resource_permissions?` Resourceable concern method.
90
+ def allow_resource_permissions?
91
+ component.settings.resources_permissions_enabled
92
+ end
93
+
88
94
  # Public: Overrides the `comments_have_alignment?` Commentable concern method.
89
95
  def comments_have_alignment?
90
96
  true
@@ -10,6 +10,10 @@ module Decidim
10
10
  belongs_to :user, foreign_key: "decidim_user_id", class_name: "Decidim::User"
11
11
 
12
12
  validates :user, uniqueness: { scope: :meeting }
13
+ validates :code, uniqueness: { allow_blank: true, scope: :meeting }
14
+ validates :code, presence: true, on: :create
15
+
16
+ before_validation :generate_code, on: :create
13
17
 
14
18
  def self.user_collection(user)
15
19
  where(decidim_user_id: user.id)
@@ -18,6 +22,20 @@ module Decidim
18
22
  def self.export_serializer
19
23
  Decidim::Meetings::DataPortabilityRegistrationSerializer
20
24
  end
25
+
26
+ private
27
+
28
+ def generate_code
29
+ self[:code] ||= calculate_registration_code
30
+ end
31
+
32
+ # Calculates a unique code for the model using the class
33
+ # provided by the configuration and scoped to the meeting.
34
+ #
35
+ # Returns a String.
36
+ def calculate_registration_code
37
+ Decidim::Meetings::Registrations.code_generator.generate(self)
38
+ end
21
39
  end
22
40
  end
23
41
  end
@@ -33,9 +33,9 @@ module Decidim
33
33
  return unless permission_action.subject == :meeting
34
34
 
35
35
  case permission_action.action
36
- when :close, :copy, :destroy, :export_registrations, :update
36
+ when :close, :copy, :destroy, :export_registrations, :update, :read_invites
37
37
  toggle_allow(meeting.present?)
38
- when :invite_user
38
+ when :invite_attendee
39
39
  toggle_allow(meeting.present? && meeting.registrations_enabled?)
40
40
  when :create
41
41
  allow!
@@ -17,6 +17,8 @@ module Decidim
17
17
  toggle_allow(can_join_meeting?)
18
18
  when :leave
19
19
  toggle_allow(can_leave_meeting?)
20
+ when :decline_invitation
21
+ toggle_allow(can_decline_invitation?)
20
22
  end
21
23
 
22
24
  permission_action
@@ -30,12 +32,17 @@ module Decidim
30
32
 
31
33
  def can_join_meeting?
32
34
  meeting.can_be_joined_by?(user) &&
33
- authorized?(:join)
35
+ authorized?(:join, resource: meeting)
34
36
  end
35
37
 
36
38
  def can_leave_meeting?
37
39
  meeting.registrations_enabled?
38
40
  end
41
+
42
+ def can_decline_invitation?
43
+ meeting.registrations_enabled? &&
44
+ meeting.invites.where(user: user).exists?
45
+ end
39
46
  end
40
47
  end
41
48
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Meetings
5
+ module AdminLog
6
+ # This class holds the logic to present a `Decidim::Meetings::Invite`
7
+ # for the `AdminLog` log.
8
+ #
9
+ # Usage should be automatic and you shouldn't need to call this class
10
+ # directly, but here's an example:
11
+ #
12
+ # action_log = Decidim::ActionLog.last
13
+ # view_helpers # => this comes from the views
14
+ # InvitePresenter.new(action_log, view_helpers).present
15
+ class InvitePresenter < Decidim::Log::BasePresenter
16
+ private
17
+
18
+ def action_string
19
+ case action
20
+ when "create", "delete", "update"
21
+ "decidim.meetings.admin_log.invite.#{action}"
22
+ else
23
+ super
24
+ end
25
+ end
26
+
27
+ def i18n_params
28
+ super.merge(
29
+ attendee_name: action_log.resource.user.name
30
+ )
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Meetings
5
+ #
6
+ # Decorator for meeting invites
7
+ #
8
+ class InvitePresenter < SimpleDelegator
9
+ def status
10
+ return I18n.t("accepted", scope: "decidim.meetings.models.invite.status", at: I18n.l(accepted_at, format: :decidim_short)) if accepted_at.present?
11
+ return I18n.t("rejected", scope: "decidim.meetings.models.invite.status", at: I18n.l(rejected_at, format: :decidim_short)) if rejected_at.present?
12
+ return I18n.t("sent", scope: "decidim.meetings.models.invite.status") if sent_at.present?
13
+
14
+ "-"
15
+ end
16
+
17
+ def status_html_class
18
+ return "success" if accepted_at.present?
19
+ return "danger" if rejected_at.present?
20
+ return "warning" if sent_at.present?
21
+
22
+ ""
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Meetings
5
+ module Admin
6
+ # A class used to find the Invites by their status status.
7
+ class Invites < Rectify::Query
8
+ # Syntactic sugar to initialize the class and return the queried objects.
9
+ #
10
+ # invites - the initial Invites relation that needs to be filtered.
11
+ # query - query to filter invites
12
+ # status - invite status to be used as a filter
13
+ def self.for(invites, query = nil, status = nil)
14
+ new(invites, query, status).query
15
+ end
16
+
17
+ # Initializes the class.
18
+ #
19
+ # invites - the initial Invites relation that need to be filtered
20
+ # query - query to filter invites
21
+ # status - invite status to be used as a filter
22
+ def initialize(invites, query = nil, status = nil)
23
+ @invites = invites
24
+ @query = query
25
+ @status = status
26
+ end
27
+
28
+ # List the invites by the different filters.
29
+ def query
30
+ @invites = filter_by_search(@invites)
31
+ @invites = filter_by_status(@invites)
32
+ @invites
33
+ end
34
+
35
+ private
36
+
37
+ def filter_by_search(invites)
38
+ return invites if @query.blank?
39
+ invites.joins(:user).where(
40
+ User.arel_table[:name].lower.matches("%#{@query}%").or(User.arel_table[:email].lower.matches("%#{@query}%"))
41
+ )
42
+ end
43
+
44
+ def filter_by_status(invites)
45
+ case @status
46
+ when "accepted"
47
+ invites.where.not(accepted_at: nil)
48
+ when "rejected"
49
+ invites.where.not(rejected_at: nil)
50
+ when "sent"
51
+ invites.where.not(sent_at: nil).where(accepted_at: nil, rejected_at: nil)
52
+ else
53
+ invites
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Meetings
5
+ class DataPortabilityInviteSerializer < Decidim::Exporters::Serializer
6
+ # Serializes an invite for data portability
7
+ def serialize
8
+ {
9
+ id: resource.id,
10
+ sent_at: resource.sent_at,
11
+ accepted_at: resource.accepted_at,
12
+ rejected_at: resource.rejected_at,
13
+ user: {
14
+ name: resource.user.name,
15
+ email: resource.user.email
16
+ },
17
+ meeting: {
18
+ title: resource.meeting.title,
19
+ description: resource.meeting.description,
20
+ start_time: resource.meeting.start_time,
21
+ end_time: resource.meeting.end_time,
22
+ address: resource.meeting.address,
23
+ location: resource.meeting.location,
24
+ location_hints: resource.meeting.location_hints,
25
+ reference: resource.meeting.reference,
26
+ attendees_count: resource.meeting.attendees_count,
27
+ attending_organizations: resource.meeting.attending_organizations,
28
+ closed_at: resource.meeting.closed_at,
29
+ closing_report: resource.meeting.closing_report
30
+ }
31
+ }
32
+ end
33
+ end
34
+ end
35
+ end
@@ -7,6 +7,7 @@ module Decidim
7
7
  def serialize
8
8
  {
9
9
  id: resource.id,
10
+ code: resource.code,
10
11
  user: {
11
12
  name: resource.user.name,
12
13
  email: resource.user.email
@@ -7,6 +7,7 @@ module Decidim
7
7
  def serialize
8
8
  {
9
9
  id: resource.id,
10
+ code: resource.code,
10
11
  user: {
11
12
  name: resource.user.name,
12
13
  email: resource.user.email
@@ -4,4 +4,5 @@
4
4
  <%= t ".invited_you_to_join_a_meeting", invited_by: @invited_by.name, application: @user.organization.name %>
5
5
  </p>
6
6
 
7
+ <p><%= link_to t(".decline", meeting_title: translated_attribute(@meeting.title)),routes.decline_invitation_meeting_registration_path(meeting_id: @meeting, participatory_space_id: @meeting.component.participatory_space) %>
7
8
  <p><%= link_to t(".join", meeting_title: translated_attribute(@meeting.title)),routes.meeting_registration_url(meeting_id: @meeting, participatory_space_id: @meeting.component.participatory_space) %>
@@ -1,7 +1,36 @@
1
- <div class="row column">
2
- <%= form.text_field :name %>
3
- </div>
1
+ <div class="attendee-fields">
2
+ <div class="row column">
3
+ <fieldset class="check-radio-collection">
4
+ <legend><%= t(".attendee_type") %></legend>
5
+ <%= form.collection_radio_buttons(:existing_user, [[t(".non_user"), false], [t(".existing_user"), true]], :last, :first) %>
6
+ </fieldset>
7
+ </div>
8
+
9
+ <div class="text-warning attendee-fields--new-user">
10
+ <p><%= t(".invite_explanation") %></p>
11
+ </div>
12
+
13
+ <div class="grid-x grid-margin-x">
14
+ <div class="auto cell attendee-fields--new-user">
15
+ <%= form.text_field :name, disabled: disabled %>
16
+ </div>
17
+ <div class="auto cell attendee-fields--new-user">
18
+ <%= form.text_field :email, disabled: disabled %>
19
+ </div>
4
20
 
5
- <div class="row column">
6
- <%= form.text_field :email %>
21
+ <div class="auto cell attendee-fields--user-picker">
22
+ <% prompt_options = { url: decidim_admin.users_organization_url, placeholder: t(".select_user") } %>
23
+ <%= form.autocomplete_select(:user_id, form.object.user.presence, { multiple: false, class: "autocomplete-field--results-inline" }, prompt_options) do |user|
24
+ { value: user.id, label: "#{user.name} (@#{user.nickname})" }
25
+ end %>
26
+ </div>
27
+
28
+ <div class="shrink cell">
29
+ <div class="text-center mt-sm">
30
+ <%= form.submit t(".invite"), disabled: disabled %>
31
+ </div>
32
+ </div>
33
+ </div>
7
34
  </div>
35
+
36
+ <%= javascript_include_tag "decidim/meetings/admin/registrations_invite_form" %>
@@ -0,0 +1,99 @@
1
+ <div class="card">
2
+ <div class="card-divider">
3
+ <h2 class="card-title"><%= t(".invite_attendee") %></h2>
4
+ </div>
5
+ <div class="card-section">
6
+ <%= decidim_form_for(@form, url: meeting_registrations_invites_path, method: :post, html: { class: "form new_meeting_registration_invite" }) do |f| %>
7
+ <% disable_form = !allowed_to?(:invite_attendee, :meeting, meeting: meeting) %>
8
+
9
+ <%= render partial: "form", object: f, locals: { disabled: disable_form } %>
10
+
11
+ <% unless meeting.registrations_enabled? %>
12
+ <div class="text-alert">
13
+ <p><%= t(".registrations_disabled") %></p>
14
+ </div>
15
+ <% end %>
16
+ <% end %>
17
+ </div>
18
+ </div>
19
+
20
+ <div class="filters grid-x mt-m">
21
+ <div class="medium-7">
22
+ <span class="dropdown-menu-inverted_label"><%= t(".filter_by") %> :</span>
23
+ <ul class="dropdown menu dropdown-inverted" data-dropdown-menu data-click-open="true" data-close-on-click-inside="false">
24
+ <li class="is-dropdown-submenu-parent">
25
+ <a href="#">
26
+ <% if @status.present? %>
27
+ <%= t(".filter.#{@status}") %>
28
+ <% else %>
29
+ <%= t(".filter.all") %>
30
+ <% end %>
31
+ </a>
32
+ <ul class="menu is-dropdown-submenu">
33
+ <li><%= link_to t(".filter.sent"), url_for(status: "sent", q: @query) %></li>
34
+ <li><%= link_to t(".filter.accepted"), url_for(status: "accepted", q: @query) %></li>
35
+ <li><%= link_to t(".filter.rejected"), url_for(status: "rejected", q: @query) %></li>
36
+ <li><%= link_to t(".filter.all"), url_for(q: @query) %></li>
37
+ </ul>
38
+ </li>
39
+ </ul>
40
+ </div>
41
+ <div class="medium-5">
42
+ <%= form_tag "", method: :get do %>
43
+ <div class="filters__search">
44
+ <div class="input-group">
45
+ <%= search_field_tag :q, @query,label: false, class: "input-group-field", placeholder: t(".search") %>
46
+ <%= hidden_field_tag :state, @state %>
47
+ <div class="input-group-button">
48
+ <button type="submit" class="button button--muted">
49
+ <%= icon "magnifying-glass", aria_label: t(".search") %>
50
+ </button>
51
+ </div>
52
+ </div>
53
+ </div>
54
+ <% end %>
55
+ </div>
56
+ </div>
57
+
58
+ <div class="card" id="meeting-invites">
59
+ <div class="card-divider">
60
+ <h2 class="card-title"><%= title %> - <%= t(".invites") %></h2>
61
+ </div>
62
+
63
+ <div class="card-section">
64
+ <div class="table-scroll">
65
+ <table class="table-list">
66
+ <thead>
67
+ <tr>
68
+ <th><%= t("models.invite.fields.name", scope: "decidim.meetings") %></th>
69
+ <th><%= t("models.invite.fields.email", scope: "decidim.meetings") %></th>
70
+ <th><%= t("models.invite.fields.sent_at", scope: "decidim.meetings") %></th>
71
+ <th><%= t("models.invite.fields.status", scope: "decidim.meetings") %></th>
72
+ </tr>
73
+ </thead>
74
+ <tbody>
75
+ <% invites.each do |invite| %>
76
+ <% presenter = Decidim::Meetings::InvitePresenter.new(invite) %>
77
+ <tr data-id="<%= invite.id %>">
78
+ <td>
79
+ <%= invite.user.name %>
80
+ </td>
81
+ <td>
82
+ <%= invite.user.email %>
83
+ </td>
84
+ <td>
85
+ <% if invite.sent_at %>
86
+ <%= l invite.sent_at, format: :long %>
87
+ <% end %>
88
+ </td>
89
+ <td class="<%= presenter.status_html_class %>">
90
+ <%= presenter.status %>
91
+ </td>
92
+ </tr>
93
+ <% end %>
94
+ </tbody>
95
+ </table>
96
+ <%= paginate invites, theme: "decidim" %>
97
+ </div>
98
+ </div>
99
+ </div>