decidim-stratified_sortitions 0.0.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 (124) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE-AGPLv3.txt +661 -0
  3. data/README.md +91 -0
  4. data/Rakefile +42 -0
  5. data/app/cells/decidim/stratified_sortitions/stratified_sortition_cell.rb +21 -0
  6. data/app/cells/decidim/stratified_sortitions/stratified_sortition_l_cell.rb +22 -0
  7. data/app/cells/decidim/stratified_sortitions/stratified_sortition_metadata_cell.rb +63 -0
  8. data/app/commands/decidim/stratified_sortitions/admin/create_stratified_sortition.rb +84 -0
  9. data/app/commands/decidim/stratified_sortitions/admin/destroy_stratified_sortition.rb +49 -0
  10. data/app/commands/decidim/stratified_sortitions/admin/duplicate_stratified_sortition.rb +74 -0
  11. data/app/commands/decidim/stratified_sortitions/admin/import_sample.rb +40 -0
  12. data/app/commands/decidim/stratified_sortitions/admin/remove_uploaded_samples.rb +53 -0
  13. data/app/commands/decidim/stratified_sortitions/admin/update_stratified_sortition.rb +174 -0
  14. data/app/controllers/concerns/decidim/stratified_sortitions/orderable_stratified_sortitions.rb +37 -0
  15. data/app/controllers/concerns/decidim/stratified_sortitions/strata_charts_data.rb +91 -0
  16. data/app/controllers/decidim/stratified_sortitions/admin/application_controller.rb +19 -0
  17. data/app/controllers/decidim/stratified_sortitions/admin/samples_controller.rb +143 -0
  18. data/app/controllers/decidim/stratified_sortitions/admin/stratified_sortitions_controller.rb +235 -0
  19. data/app/controllers/decidim/stratified_sortitions/application_controller.rb +13 -0
  20. data/app/controllers/decidim/stratified_sortitions/charts_pdf_controller_helper.rb +17 -0
  21. data/app/controllers/decidim/stratified_sortitions/stratified_sortitions_controller.rb +61 -0
  22. data/app/forms/decidim/stratified_sortitions/admin/sample_upload_form.rb +15 -0
  23. data/app/forms/decidim/stratified_sortitions/admin/stratified_sortitions_form.rb +155 -0
  24. data/app/forms/decidim/stratified_sortitions/admin/stratum_form.rb +69 -0
  25. data/app/forms/decidim/stratified_sortitions/admin/substratum_form.rb +26 -0
  26. data/app/helpers/decidim/stratified_sortitions/admin/stratified_sortitions_helper.rb +18 -0
  27. data/app/helpers/decidim/stratified_sortitions/application_helper.rb +85 -0
  28. data/app/helpers/decidim/stratified_sortitions/charts_pdf_helper.rb +73 -0
  29. data/app/helpers/decidim/stratified_sortitions/stratified_sortition_cells_helper.rb +56 -0
  30. data/app/javascript/channels/sample_import_progress.js +12 -0
  31. data/app/jobs/decidim/stratified_sortitions/admin/execute_sortition_job.rb +21 -0
  32. data/app/jobs/decidim/stratified_sortitions/admin/import_sample_job.rb +170 -0
  33. data/app/jobs/decidim/stratified_sortitions/admin/remove_samples_job.rb +19 -0
  34. data/app/jobs/decidim/stratified_sortitions/admin/sortition_results_export_job.rb +28 -0
  35. data/app/mailers/decidim/stratified_sortitions/admin/import_mailer.rb +26 -0
  36. data/app/models/decidim/stratified_sortitions/application_record.rb +9 -0
  37. data/app/models/decidim/stratified_sortitions/panel_portfolio.rb +165 -0
  38. data/app/models/decidim/stratified_sortitions/sample_import.rb +14 -0
  39. data/app/models/decidim/stratified_sortitions/sample_participant.rb +12 -0
  40. data/app/models/decidim/stratified_sortitions/sample_participant_stratum.rb +11 -0
  41. data/app/models/decidim/stratified_sortitions/stratified_sortition.rb +74 -0
  42. data/app/models/decidim/stratified_sortitions/stratum.rb +22 -0
  43. data/app/models/decidim/stratified_sortitions/substratum.rb +19 -0
  44. data/app/packs/entrypoints/decidim_stratified_sortitions.js +10 -0
  45. data/app/packs/entrypoints/decidim_stratified_sortitions_admin.js +9 -0
  46. data/app/packs/entrypoints/decidim_stratified_sortitions_admin.scss +1 -0
  47. data/app/packs/images/decidim/stratified_sortitions/icon.svg +1 -0
  48. data/app/packs/src/decidim/stratified_sortitions/application.js +3 -0
  49. data/app/packs/src/decidim/stratified_sortitions/results_tabs.js +59 -0
  50. data/app/packs/src/decidim/stratified_sortitions/stratum_fields.js +302 -0
  51. data/app/packs/src/decidim/stratified_sortitions/substratum_fields.js +165 -0
  52. data/app/packs/src/decidim/stratified_sortitions/upload_sample.js +16 -0
  53. data/app/packs/stylesheets/decidim/stratified_sortitions/admin/stratified_sortitions.scss +210 -0
  54. data/app/packs/stylesheets/decidim/stratified_sortitions/charts_pdf.scss +148 -0
  55. data/app/packs/stylesheets/stratified_sortitions.scss +56 -0
  56. data/app/permissions/decidim/stratified_sortitions/admin/permissions.rb +74 -0
  57. data/app/permissions/decidim/stratified_sortitions/permissions.rb +23 -0
  58. data/app/presenters/decidim/stratified_sortitions/admin_log/stratified_sortition_presenter.rb +35 -0
  59. data/app/queries/decidim/stratified_sortitions/filtered_stratified_sortitions.rb +39 -0
  60. data/app/serializers/decidim/stratified_sortitions/sortition_result_serializer.rb +116 -0
  61. data/app/services/decidim/stratified_sortitions/charts_pdf_generator.rb +96 -0
  62. data/app/services/decidim/stratified_sortitions/fair_sortition_service.rb +247 -0
  63. data/app/services/decidim/stratified_sortitions/leximin/constraint_builder.rb +180 -0
  64. data/app/services/decidim/stratified_sortitions/leximin/distribution_solver.rb +217 -0
  65. data/app/services/decidim/stratified_sortitions/leximin/feasibility_checker.rb +222 -0
  66. data/app/services/decidim/stratified_sortitions/leximin/panel_generator.rb +149 -0
  67. data/app/services/decidim/stratified_sortitions/leximin/panel_sampler.rb +125 -0
  68. data/app/services/decidim/stratified_sortitions/leximin_selector.rb +136 -0
  69. data/app/services/decidim/stratified_sortitions/sortition_results_exporter.rb +161 -0
  70. data/app/views/decidim/admin/components/index.html.erb +54 -0
  71. data/app/views/decidim/stratified_sortitions/admin/import_mailer/import.html.erb +22 -0
  72. data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/_confirm_execute_modal.html.erb +19 -0
  73. data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/_confirm_participants_modal.html.erb +20 -0
  74. data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/_form.html.erb +55 -0
  75. data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/_navigation_menu.html.erb +10 -0
  76. data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/_strata.html.erb +22 -0
  77. data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/_stratum.html.erb +75 -0
  78. data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/_substrata.html.erb +34 -0
  79. data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/_substratum.html.erb +54 -0
  80. data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/edit.html.erb +21 -0
  81. data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/execute.html.erb +155 -0
  82. data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/export_charts_pdf.html.erb +38 -0
  83. data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/index.html.erb +75 -0
  84. data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/new.html.erb +21 -0
  85. data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/upload_sample.html.erb +117 -0
  86. data/app/views/decidim/stratified_sortitions/shared/_strata_charts.html.erb +21 -0
  87. data/app/views/decidim/stratified_sortitions/stratified_sortitions/_results_count.html.erb +15 -0
  88. data/app/views/decidim/stratified_sortitions/stratified_sortitions/_stratified_sortition.html.erb +1 -0
  89. data/app/views/decidim/stratified_sortitions/stratified_sortitions/_stratified_sortitions.html.erb +13 -0
  90. data/app/views/decidim/stratified_sortitions/stratified_sortitions/_tags.html.erb +9 -0
  91. data/app/views/decidim/stratified_sortitions/stratified_sortitions/index.html.erb +22 -0
  92. data/app/views/decidim/stratified_sortitions/stratified_sortitions/index.js.erb +5 -0
  93. data/app/views/decidim/stratified_sortitions/stratified_sortitions/show.html.erb +142 -0
  94. data/app/views/layouts/decidim/admin/_sidebar_menu.html.erb +45 -0
  95. data/app/views/layouts/decidim/stratified_sortitions/charts_pdf.html.erb +14 -0
  96. data/config/assets.rb +24 -0
  97. data/config/locales/ca.yml +361 -0
  98. data/config/locales/en.yml +360 -0
  99. data/config/locales/es.yml +360 -0
  100. data/config/locales/oc.yml +2 -0
  101. data/config/routes.rb +1 -0
  102. data/db/migrate/20251023103900_create_decidim_stratified_sortitions_stratified_sortitions.rb +18 -0
  103. data/db/migrate/20251104120000_create_stratified_sortitions_strata_and_substrata.decidim_stratified_sortitions.rb +27 -0
  104. data/db/migrate/20251219120000_create_sample_imports.rb +17 -0
  105. data/db/migrate/20251219120100_create_sample_participants.rb +19 -0
  106. data/db/migrate/20251219120200_create_sample_participant_strata.rb +16 -0
  107. data/db/migrate/20251230130000_change_value_to_text_in_substrata.rb +11 -0
  108. data/db/migrate/20260102114744_add_status_to_stratified_sortitions.rb +11 -0
  109. data/db/migrate/20260109105643_allow_null_value_in_substrata.rb +10 -0
  110. data/db/migrate/20260113121726_add_position_to_strata_and_substrata.rb +8 -0
  111. data/db/migrate/20260127100000_rename_weighing_to_max_quota_percentage.rb +12 -0
  112. data/db/migrate/20260127120000_create_panel_portfolios.rb +46 -0
  113. data/db/migrate/20260518000000_add_foreign_key_to_stratified_sortitions_component.rb +10 -0
  114. data/db/migrate/20260521000000_add_cascade_to_stratified_sortitions_foreign_keys.rb +64 -0
  115. data/db/migrate/20260616000000_add_execution_error_to_stratified_sortitions.rb +11 -0
  116. data/lib/decidim/stratified_sortitions/admin.rb +10 -0
  117. data/lib/decidim/stratified_sortitions/admin_engine.rb +49 -0
  118. data/lib/decidim/stratified_sortitions/component.rb +41 -0
  119. data/lib/decidim/stratified_sortitions/engine.rb +34 -0
  120. data/lib/decidim/stratified_sortitions/seeds.rb +24 -0
  121. data/lib/decidim/stratified_sortitions/test/factories.rb +80 -0
  122. data/lib/decidim/stratified_sortitions/version.rb +13 -0
  123. data/lib/decidim/stratified_sortitions.rb +29 -0
  124. metadata +222 -0
@@ -0,0 +1,55 @@
1
+ <div class="form__wrapper">
2
+ <div class="card" data-component="accordion" id="accordion-data">
3
+ <div class="card-divider">
4
+ <button class="card-divider-button" data-open="true" data-controls="panel-data" type="button">
5
+ <%= icon "arrow-right-s-line" %>
6
+ <h2 class="card-title" id="data">
7
+ <%= t("data", scope: "decidim.stratified_sortitions.admin.stratified_sortitions.form") %>
8
+ </h2>
9
+ </button>
10
+ </div>
11
+ <div id="panel-data" class="card-section">
12
+ <div class="row columns">
13
+ <%= form.translated :text_field, :title, autofocus: true, aria: { label: :title } %>
14
+ </div>
15
+
16
+ <div class="row column mt-4">
17
+ <%= form.number_field :num_candidates, readonly: stratified_sortition&.executed? %>
18
+ </div>
19
+
20
+ <div class="row column">
21
+ <%= form.translated :editor, :description %>
22
+ </div>
23
+
24
+ <div class="row column">
25
+ <%= form.translated :editor, :selection_criteria, help_text: t("selection_criteria_help", scope: "decidim.stratified_sortitions.admin.stratified_sortitions.form") %>
26
+ </div>
27
+
28
+ <div class="row column">
29
+ <%= form.translated :editor, :selected_profiles_description, help_text: t("selected_profiles_description_help", scope: "decidim.stratified_sortitions.admin.stratified_sortitions.form") %>
30
+ </div>
31
+ </div>
32
+ </div>
33
+
34
+ <div class="card" data-component="accordion" id="accordion-strata">
35
+ <div class="card-divider">
36
+ <button class="card-divider-button" data-open="true" data-controls="panel-strata" type="button">
37
+ <svg viewBox="0 0 24 24" id="ri-arrow-right-s-line">
38
+ <path fill="none" d="M0 0h24v24H0z" />
39
+ <path d="M13.172 12l-4.95-4.95 1.414-1.414L16 12l-6.364 6.364-1.414-1.414z" />
40
+ </svg>
41
+ <h2 class="card-title" id="strata">
42
+ <%= t("title", scope: "decidim.stratified_sortitions.admin.stratified_sortitions.strata") %>
43
+ </h2>
44
+ </button>
45
+ </div>
46
+ <div id="panel-strata" class="card-section">
47
+ <div class="row column">
48
+ <%= render "decidim/stratified_sortitions/admin/stratified_sortitions/strata", form: form %>
49
+ </div>
50
+ </div>
51
+ </div>
52
+ </div>
53
+
54
+ <%= append_javascript_pack_tag "decidim_stratified_sortitions_admin" %>
55
+ <%= append_stylesheet_pack_tag "decidim_stratified_sortitions_admin" %>
@@ -0,0 +1,10 @@
1
+ <div class="flex items-left stratified_sortition_nav">
2
+ <% navigation_menu_items(stratified_sortition).each do |item| %>
3
+ <%= button_to item[:path],
4
+ method: :get,
5
+ class: "import-sample-btn #{'active' if item[:active]}",
6
+ disabled: item[:disabled] do %>
7
+ <%= icon item[:icon] %> <%= item[:label] %>
8
+ <% end %>
9
+ <% end %>
10
+ </div>
@@ -0,0 +1,22 @@
1
+ <div id="panel-strata" class="card-section">
2
+ <%= field_errors_block(form, :strata) %>
3
+
4
+ <div class="stratified-sortition-strata row column">
5
+ <template class="decidim-template">
6
+ <%= fields_for "stratified_sortition[strata][STRATUM_ID]", blank_stratum do |stratum_form| %>
7
+ <%= render "stratum", form: stratum_form, id: tabs_id_for_stratum(blank_stratum) %>
8
+ <% end %>
9
+ </template>
10
+
11
+ <div class="stratified-sortition-strata-list">
12
+ <% form.object.strata.each do |stratum| %>
13
+ <%= fields_for "stratified_sortition[strata][]", stratum do |stratum_form| %>
14
+ <%= render "stratum", form: stratum_form, id: tabs_id_for_stratum(stratum) %>
15
+ <% end %>
16
+ <% end %>
17
+ </div>
18
+ <button class="button button__sm button__secondary add-stratum mt-2" type="button" <%= 'disabled' if @stratified_sortition&.sample_participants&.any? %>>
19
+ <%= t(".add_stratum") %>
20
+ </button>
21
+ </div>
22
+ </div>
@@ -0,0 +1,75 @@
1
+ <% stratum = form.object %>
2
+
3
+ <div class="card stratified-sortition-stratum" data-component="accordion" id="accordion-<%= id %>-field">
4
+ <div class="card-divider stratum-divider">
5
+ <h2 class="card-title flex items-center">
6
+ <span>
7
+ <%= icon("drag-move-2-fill") %>
8
+ <%= dynamic_title(translated_attribute(stratum.name), class: "stratum-title-statement", max_length: 50, omission: "...", placeholder: t(".title")) %>
9
+ </span>
10
+
11
+ <div class="ml-auto flex flex-row-reverse items-center gap-x-4">
12
+ <button type="button" class="stratum--collapse" data-controls="panel-<%= id %>-stratum-card">
13
+ <span class="icon-collapse">
14
+ <%== icon("arrow-up-s-line", role: "img") %>
15
+ </span>
16
+
17
+ <span class="icon-expand">
18
+ <%== icon("arrow-down-s-line", role: "img") %>
19
+ </span>
20
+ </button>
21
+ <button
22
+ type="button"
23
+ class="button button__sm button__transparent-secondary small alert move-up-stratum button--title"
24
+ <%= 'disabled' if @stratified_sortition&.sample_participants&.any? %>>
25
+ <%= icon "arrow-up-line" %> <%= t(".up") %>
26
+ </button>
27
+
28
+ <button
29
+ type="button"
30
+ class="button button__sm button__transparent-secondary small alert move-down-stratum button--title"
31
+ <%= 'disabled' if @stratified_sortition&.sample_participants&.any? %>>
32
+ <%= icon "arrow-down-line" %> <%= t(".down") %>
33
+ </button>
34
+
35
+ <button
36
+ type="button"
37
+ class="button button__sm button__transparent-secondary small alert remove-stratum button--title"
38
+ <%= 'disabled' if @stratified_sortition&.sample_participants&.any? %>>
39
+ <%= t(".remove") %>
40
+ </button>
41
+ </div>
42
+ </h2>
43
+ </div>
44
+
45
+ <div id="panel-<%= id %>-stratum-card" class="card-divider collapsible">
46
+ <div class="card-section stratum-fields">
47
+ <div class="translated-field">
48
+ <%= form.translated(
49
+ :text_field,
50
+ :name,
51
+ tabs_id: id,
52
+ label: t(".name"),
53
+ maxlength: 100,
54
+ readonly: @stratified_sortition&.sample_participants&.any?) %>
55
+ </div>
56
+ <div>
57
+ <%= form.select :kind,
58
+ [[t("decidim.stratified_sortitions.admin.stratified_sortitions.stratum.kind.value"), "value"],
59
+ [t("decidim.stratified_sortitions.admin.stratified_sortitions.stratum.kind.numeric_range"), "numeric_range"]],
60
+ { label: t("decidim.stratified_sortitions.admin.stratified_sortitions.stratum.kind.title") },
61
+ { disabled: @stratified_sortition&.sample_participants&.any? } %>
62
+ <%= form.hidden_field :kind if @stratified_sortition&.sample_participants&.any? %>
63
+ </div>
64
+ </div>
65
+ <%= form.hidden_field :id %>
66
+ <%= form.hidden_field :position, value: form.object.position || 0 %>
67
+ <%= form.hidden_field :deleted, value: false %>
68
+
69
+ <% blank_substratum = Decidim::StratifiedSortitions::Admin::SubstratumForm.new(stratum: stratum) %>
70
+ <%= render "substrata",
71
+ form: form,
72
+ stratum: stratum,
73
+ blank_substratum: blank_substratum %>
74
+ </div>
75
+ </div>
@@ -0,0 +1,34 @@
1
+ <div id="panel-substrata" class="card-section">
2
+ <%= field_errors_block(form, :substrata) %>
3
+
4
+ <div class="stratified-sortition-substrata row column" id="substrata-<%= form.object.object_id %>">
5
+
6
+ <script type="text/template" class="decidim-template">
7
+ <% new_substratum = Decidim::StratifiedSortitions::Admin::SubstratumForm.new(stratum: form.object) %>
8
+ <% stratum_id = form.object.id || "STRATUM_ID" %>
9
+ <% field_name_base = "stratified_sortition[strata][#{stratum_id}][substrata][SUBSTRATUM_ID]" %>
10
+ <%= fields_for field_name_base, new_substratum do |substratum_form| %>
11
+ <%= render "substratum",
12
+ form: substratum_form,
13
+ id: tabs_id_for_substratum("SUBSTRATUM_ID"),
14
+ stratum: form.object %>
15
+ <% end %>
16
+ </script>
17
+
18
+ <div class="stratified-sortition-substrata-list">
19
+ <% form.object.substrata.each_with_index do |substratum, index| %>
20
+ <%= form.fields_for "[substrata][#{index}]", substratum do |substratum_form| %>
21
+ <%= render "substratum",
22
+ form: substratum_form,
23
+ id: tabs_id_for_substratum(substratum),
24
+ stratum: form.object %>
25
+ <% end %>
26
+ <% end %>
27
+ </div>
28
+ <button
29
+ class="button button__sm button__secondary add-substratum mt-2"
30
+ type="button" <%= 'disabled' if @stratified_sortition&.sample_participants&.any? %>>
31
+ <%= t(".add_substratum") %>
32
+ </button>
33
+ </div>
34
+ </div>
@@ -0,0 +1,54 @@
1
+ <% substratum = form.object %>
2
+
3
+ <div class="card stratified-sortition-substratum" id="<%= id %>-field">
4
+ <div class="card-divider substratum-divider">
5
+ <h2 class="card-title mb-4">
6
+ <span><%= t(".title") %></span>
7
+
8
+ <div class="ml-auto flex gap-x-4">
9
+ <button
10
+ type="button"
11
+ class="button button__sm button__transparent-secondary small alert remove-substratum button--title"
12
+ <%= 'disabled' if @stratified_sortition&.sample_participants&.any? %>>
13
+ <%= t(".remove") %>
14
+ </button>
15
+ </div>
16
+ </h2>
17
+ </div>
18
+
19
+ <div class="card-section">
20
+ <div class="row column substratum-fields">
21
+ <div class="translated-field">
22
+ <%= form.translated(
23
+ :text_field,
24
+ :name,
25
+ tabs_id: id,
26
+ label: t(".name"),
27
+ maxlength: 100,
28
+ readonly: @stratified_sortition&.sample_participants&.any?) %>
29
+ </div>
30
+ <div class="substratum-value-field">
31
+ <%= form.text_field :value,
32
+ label: t(".value"),
33
+ placeholder: t(".value_placeholder",default: "Value"),
34
+ readonly: @stratified_sortition&.sample_participants&.any? %>
35
+ </div>
36
+ <div class="substratum-range-field">
37
+ <%= form.text_field :range,
38
+ label: t(".range"),
39
+ placeholder: t(".range_placeholder", default: "Range (e.g. 0-18)"),
40
+ readonly: @stratified_sortition&.sample_participants&.any? %>
41
+ </div>
42
+ <div>
43
+ <%= form.number_field :max_quota_percentage,
44
+ step: 0.1, label: t(".max_quota_percentage"),
45
+ placeholder: t(".max_quota_percentage_placeholder"),
46
+ readonly: @stratified_sortition&.executed? %>
47
+ </div>
48
+ </div>
49
+ </div>
50
+
51
+ <%= form.hidden_field :id %>
52
+ <%= form.hidden_field :deleted, value: false %>
53
+ <%= form.hidden_field :position, value: form.object.position || 0 %>
54
+ </div>
@@ -0,0 +1,21 @@
1
+ <% add_decidim_page_title(t(".title")) %>
2
+ <div class="item_show__header stratified_sortition_header">
3
+ <h1 class="item_show__header-title">
4
+ <%= t(".title") %>
5
+ </h1>
6
+ </div>
7
+
8
+ <%= render 'navigation_menu' %>
9
+
10
+ <div class="item__edit item__edit-1col">
11
+ <div class="item__edit-form">
12
+ <%= decidim_form_for(@form, html: { class: "form-defaults form edit_stratified_sortition stratified_sortition_form_admin" }) do |f| %>
13
+ <%= render partial: "form", object: f, locals: { title: t(".title") } %>
14
+ <div class="item__edit-sticky">
15
+ <div class="item__edit-sticky-container">
16
+ <%= f.submit t(".update"), class: "button button__sm button__secondary" %>
17
+ </div>
18
+ </div>
19
+ <% end %>
20
+ </div>
21
+ </div>
@@ -0,0 +1,155 @@
1
+ <% add_decidim_page_title(t(".title")) %>
2
+ <div class="item_show__header stratified_sortition_header">
3
+ <h1 class="item_show__header-title">
4
+ <%= t(".title") %>
5
+ </h1>
6
+ </div>
7
+ <%= render 'navigation_menu' %>
8
+ <% if stratified_sortition.executing? %>
9
+ <div class="callout warning">
10
+ <%= t('.executing_notice') %>
11
+ </div>
12
+ <% end %>
13
+ <% if stratified_sortition.failed? %>
14
+ <div class="callout alert">
15
+ <p><strong><%= t('.execution_failed') %></strong></p>
16
+ <p><%= stratified_sortition.execution_error %></p>
17
+ </div>
18
+ <% end %>
19
+ <div class="card">
20
+ <div class="item_show__header actions-container">
21
+ <button type="button"
22
+ class="button button__sm button__secondary"
23
+ data-dialog-open="confirm-execute-modal"
24
+ <%= 'disabled' if stratified_sortition.panel_portfolio&.sampled? || stratified_sortition.executing? || !stratified_sortition.can_execute? %>>
25
+ <%= t('.execute_stratified_sortition') %>
26
+ </button>
27
+ <% if stratified_sortition.panel_portfolio&.sampled? %>
28
+ <span class="exports button button__sm button__secondary" data-toggle="export-dropdown">
29
+ <%= t(".export") %>
30
+ <%= icon "arrow-down-s-line", class: "dropdown-filter-icon" %>
31
+ </span>
32
+ <div class="dropdown-pane" id="export-dropdown" data-dropdown data-position="bottom" data-alignment="right" data-auto-focus="true" data-close-on-click="true">
33
+ <ul class="vertical menu add-components">
34
+ <%= link_to export_results_stratified_sortition_path(stratified_sortition, format: "csv"), method: :post do %>
35
+ <li class="exports--format--csv"><%= t(".export_csv") %></li>
36
+ <% end %>
37
+ <%= link_to export_results_stratified_sortition_path(stratified_sortition, format: "excel"), method: :post do %>
38
+ <li class="exports--format--excel"><%= t(".export_excel") %></li>
39
+ <% end %>
40
+ <%= link_to export_results_stratified_sortition_path(stratified_sortition, format: "json"), method: :post do %>
41
+ <li class="exports--format--json"><%= t(".export_json") %></li>
42
+ <% end %>
43
+ <%= link_to export_charts_pdf_stratified_sortition_path(stratified_sortition), method: :post do %>
44
+ <li class="exports--format--pdf"><%= t(".export_charts_pdf") %></li>
45
+ <% end %>
46
+ </ul>
47
+ </div>
48
+ <% end %>
49
+ </div>
50
+ </div>
51
+ <% if stratified_sortition.panel_portfolio&.sampled? %>
52
+ <div class="card mt-12">
53
+ <div class="item_show__header">
54
+ <h1 class="item_show__header-title">
55
+ <%= t(".results") %>
56
+ </h1>
57
+ </div>
58
+ <div class="mt-4 mb-4">
59
+ <% if @portfolio&.sampled? %>
60
+ <div class="sortition-metadata mb-8">
61
+ <table class="table-list">
62
+ <thead>
63
+ <tr>
64
+ <th><%= t(".algorithm") %></th>
65
+ <th><%= t(".total_participants") %></th>
66
+ <th><%= t(".selected_participants_title") %></th>
67
+ <th><%= t(".generated_at") %></th>
68
+ <th><%= t(".generation_time") %></th>
69
+ <th><%= t(".num_panels") %></th>
70
+ <th><%= t(".selected_at") %></th>
71
+ <th><%= t(".random_value_used") %></th>
72
+ <th><%= t(".selected_panel_probability") %></th>
73
+ </tr>
74
+ </thead>
75
+ <tbody>
76
+ <tr>
77
+ <td><%= @portfolio.audit_log[:algorithm] %> v<%= @portfolio.audit_log[:version] %></td>
78
+ <td><%= stratified_sortition.sample_participants.count %></td>
79
+ <td><%= @portfolio.selected_panel.size %></td>
80
+ <td><%= l @portfolio.generated_at, format: :decidim_short %></td>
81
+ <td><%= number_with_precision(@portfolio.generation_time_seconds, precision: 2) %>s</td>
82
+ <td><%= @portfolio.num_panels %></td>
83
+ <td><%= l @portfolio.selected_at, format: :decidim_short %></td>
84
+ <td><code><%= @portfolio.random_value_used %></code></td>
85
+ <td><%= number_to_percentage(@portfolio.selected_panel_probability.to_f * 100, precision: 2) %></td>
86
+ </tr>
87
+ </tbody>
88
+ </table>
89
+ </div>
90
+
91
+ <div class="results-sub-nav flex items-left" data-results-tabs>
92
+ <button type="button" class="results-tab active" data-tab-target="charts-section">
93
+ <%= icon "bar-chart-box-line" %> <%= t(".tab_charts") %>
94
+ </button>
95
+ <button type="button" class="results-tab" data-tab-target="participants-section" data-dialog-open="confirm-participants-modal">
96
+ <%= icon "group-line" %> <%= t(".tab_participants") %>
97
+ </button>
98
+ </div>
99
+
100
+ <div id="charts-section" class="results-tab-panel" data-tab-panel>
101
+ <div class="chart-row">
102
+ <div class="charts-column">
103
+ <%= render partial: "decidim/stratified_sortitions/shared/strata_charts", locals: { strata_data: @strata_data, title: t(".target") } %>
104
+ </div>
105
+ <div class="charts-column target">
106
+ <%= render partial: "decidim/stratified_sortitions/shared/strata_charts", locals: { strata_data: @candidates_data, title: t(".candidates") } %>
107
+ </div>
108
+ <div class="charts-column target">
109
+ <%= render partial: "decidim/stratified_sortitions/shared/strata_charts", locals: { strata_data: @results_data, title: t(".results") } %>
110
+ </div>
111
+ </div>
112
+ </div>
113
+
114
+ <div id="participants-section" class="results-tab-panel" style="display: none;" data-tab-panel>
115
+ <h2 class="h4 mb-4"><%= t(".selected_participants", count: @selected_participants.size) %></h2>
116
+ <table class="table-list">
117
+ <thead>
118
+ <tr>
119
+ <th><%= t(".personal_data_1") %></th>
120
+ <th><%= t(".personal_data_2") %></th>
121
+ <th><%= t(".personal_data_3") %></th>
122
+ <th><%= t(".personal_data_4") %></th>
123
+ <% @strata.each do |stratum| %>
124
+ <th><%= translated_attribute(stratum.name) %></th>
125
+ <% end %>
126
+ </tr>
127
+ </thead>
128
+ <tbody>
129
+ <% @selected_participants.each do |participant| %>
130
+ <tr>
131
+ <td><%= participant.personal_data_1 %></td>
132
+ <td><%= participant.personal_data_2 %></td>
133
+ <td><%= participant.personal_data_3 %></td>
134
+ <td><%= participant.personal_data_4 %></td>
135
+ <% @strata.each do |stratum| %>
136
+ <% participant_stratum = participant.sample_participant_strata.find { |ps| ps.decidim_stratified_sortitions_stratum_id == stratum.id } %>
137
+ <td><%= participant_stratum ? translated_attribute(participant_stratum.decidim_stratified_sortitions_substratum&.name) : "-" %></td>
138
+ <% end %>
139
+ </tr>
140
+ <% end %>
141
+ </tbody>
142
+ </table>
143
+ </div>
144
+ <% else %>
145
+ <p class="text-muted"><%= t(".no_results_yet") %></p>
146
+ <% end %>
147
+ </div>
148
+ </div>
149
+ <% end %>
150
+
151
+ <%= render stratified_sortition.panel_portfolio&.sampled? ? "confirm_participants_modal" : "confirm_execute_modal" %>
152
+
153
+ <%= append_javascript_pack_tag "decidim_stratified_sortitions_admin" %>
154
+ <%= append_stylesheet_pack_tag "decidim_stratified_sortitions_admin" %>
155
+
@@ -0,0 +1,38 @@
1
+ <h1><%= @title %></h1>
2
+ <h2><%= @subtitle %></h2>
3
+ <hr>
4
+
5
+ <div class="info">
6
+ <p><%= @belongs_to %></p>
7
+ <p><%= @algorithm %></p>
8
+ <p><%= @executed_at %></p>
9
+ </div>
10
+
11
+ <table class="column-headers">
12
+ <tr>
13
+ <td class="col-header"><%= @col_target %></td>
14
+ <td class="col-header"><%= @col_candidates %></td>
15
+ <td class="col-header"><%= @col_results %></td>
16
+ </tr>
17
+ </table>
18
+
19
+ <% @strata_chart_data.each do |stratum_row| %>
20
+ <div class="stratum-section">
21
+ <div class="stratum-title"><%= stratum_row[:name] %></div>
22
+ <table class="charts-table">
23
+ <tr>
24
+ <% [:target, :candidates, :results].each do |col| %>
25
+ <td class="chart-cell">
26
+ <% chart_data = stratum_row[col] %>
27
+ <% if chart_data.present? %>
28
+ <%= svg_pie_chart(chart_data) %>
29
+ <%= legend_html(chart_data) %>
30
+ <% else %>
31
+ <div class="no-data"><%= @no_data_text %></div>
32
+ <% end %>
33
+ </td>
34
+ <% end %>
35
+ </tr>
36
+ </table>
37
+ </div>
38
+ <% end %>
@@ -0,0 +1,75 @@
1
+ <% add_decidim_page_title(t(".title")) %>
2
+ <div class="card">
3
+ <div class="item_show__header">
4
+ <h1 class="item_show__header-title">
5
+ <div>
6
+ <%= t(".title") %>
7
+ <span id="js-selected-stratified_sortitions-count" class="component-counter component-counter--inline" title="<%= t("decidim.stratified_sortitions.admin.stratified_sortitions.index.selected") %>"></span>
8
+ </div>
9
+ <div class="flex items-center gap-x-4">
10
+ <% if allowed_to? :create, :stratified_sortition %>
11
+ <%= link_to t("actions.new_stratified_sortition", scope: "decidim.stratified_sortitions.admin"),
12
+ new_stratified_sortition_path,
13
+ class: "button button__sm button__secondary" %>
14
+ <% end %>
15
+
16
+ <%= render partial: "decidim/admin/components/resource_action" %>
17
+ </div>
18
+ </h1>
19
+ </div>
20
+
21
+ <div class="table-scroll mt-16">
22
+ <table class="table-list">
23
+ <thead>
24
+ <tr>
25
+ <th>
26
+ <%= t("models.stratified_sortition.fields.title", scope: "decidim.stratified_sortitions.admin") %>
27
+ </th>
28
+ <th>
29
+ <%= t("models.stratified_sortition.fields.num_candidates", scope: "decidim.stratified_sortitions.admin") %>
30
+ </th>
31
+ <th>
32
+ <%= t("models.stratified_sortition.fields.status", scope: "decidim.stratified_sortitions.admin") %>
33
+ </th>
34
+ <th>
35
+ <%= t("models.stratified_sortition.fields.created_at", scope: "decidim.stratified_sortitions.admin") %>
36
+ </th>
37
+ <th class="actions"><%= t("actions.title", scope: "decidim.stratified_sortitions.admin") %></th>
38
+ </tr>
39
+ </thead>
40
+ <tbody>
41
+ <% @stratified_sortitions.each do |stratified_sortition| %>
42
+ <tr>
43
+ <td>
44
+ <%= link_to translated_attribute(stratified_sortition.title), edit_stratified_sortition_path(stratified_sortition) %>
45
+ </td>
46
+ <td>
47
+ <%= stratified_sortition.num_candidates %>
48
+ </td>
49
+ <td>
50
+ <strong class="label <%= stratified_sortition_state_css_class(stratified_sortition) %>">
51
+ <%= t(stratified_sortition.status, scope: "decidim.stratified_sortitions.stratified_sortitions.filters") %>
52
+ </strong>
53
+ </td>
54
+ <td>
55
+ <%= l(stratified_sortition.created_at, format: :decidim_short) if stratified_sortition.created_at %>
56
+ </td>
57
+ <td class="table-list__actions">
58
+ <% if allowed_to? :duplicate, :stratified_sortition %>
59
+ <%= icon_link_to "file-copy-line", duplicate_stratified_sortition_path(stratified_sortition), t("actions.duplicate", scope: "decidim.stratified_sortitions.admin"), class: "action-icon--new", method: :post %>
60
+ <% end %>
61
+ <% if allowed_to? :edit, :stratified_sortition %>
62
+ <%= icon_link_to "pencil-line", edit_stratified_sortition_path(stratified_sortition), t("actions.configure", scope: "decidim.stratified_sortitions.admin"), class: "action-icon--new" %>
63
+ <% end %>
64
+ <% if allowed_to? :destroy, :stratified_sortition, stratified_sortition: stratified_sortition %>
65
+ <%= icon_link_to "delete-bin-line", stratified_sortition, t("actions.destroy", scope: "decidim.stratified_sortitions.admin"), class: "action-icon--remove", method: :delete, data: { confirm: t("actions.confirm_destroy", scope: "decidim.stratified_sortitions.admin", name: translated_attribute(stratified_sortition.title)) } %>
66
+ <% end %>
67
+ </td>
68
+ </tr>
69
+ <% end %>
70
+ </tbody>
71
+ </table>
72
+ </div>
73
+ </div>
74
+
75
+ <%= decidim_paginate @stratified_sortitions %>
@@ -0,0 +1,21 @@
1
+ <% add_decidim_page_title(t(".title")) %>
2
+ <div class="item_show__header">
3
+ <h1 class="item_show__header-title">
4
+ <%= t(".title") %>
5
+ </h1>
6
+ </div>
7
+
8
+ <%= render 'navigation_menu' %>
9
+
10
+ <div class="item__edit item__edit-1col">
11
+ <div class="item__edit-form">
12
+ <%= decidim_form_for(@form, html: { class: "form-defaults form new_stratified_sortition stratified_sortition_form_admin" }) do |f| %>
13
+ <%= render partial: "form", object: f, locals: { title: t(".title") } %>
14
+ <div class="item__edit-sticky">
15
+ <div class="item__edit-sticky-container">
16
+ <%= f.submit t(".create"), class: "button button__sm button__secondary" %>
17
+ </div>
18
+ </div>
19
+ <% end %>
20
+ </div>
21
+ </div>