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.
- checksums.yaml +7 -0
- data/LICENSE-AGPLv3.txt +661 -0
- data/README.md +91 -0
- data/Rakefile +42 -0
- data/app/cells/decidim/stratified_sortitions/stratified_sortition_cell.rb +21 -0
- data/app/cells/decidim/stratified_sortitions/stratified_sortition_l_cell.rb +22 -0
- data/app/cells/decidim/stratified_sortitions/stratified_sortition_metadata_cell.rb +63 -0
- data/app/commands/decidim/stratified_sortitions/admin/create_stratified_sortition.rb +84 -0
- data/app/commands/decidim/stratified_sortitions/admin/destroy_stratified_sortition.rb +49 -0
- data/app/commands/decidim/stratified_sortitions/admin/duplicate_stratified_sortition.rb +74 -0
- data/app/commands/decidim/stratified_sortitions/admin/import_sample.rb +40 -0
- data/app/commands/decidim/stratified_sortitions/admin/remove_uploaded_samples.rb +53 -0
- data/app/commands/decidim/stratified_sortitions/admin/update_stratified_sortition.rb +174 -0
- data/app/controllers/concerns/decidim/stratified_sortitions/orderable_stratified_sortitions.rb +37 -0
- data/app/controllers/concerns/decidim/stratified_sortitions/strata_charts_data.rb +91 -0
- data/app/controllers/decidim/stratified_sortitions/admin/application_controller.rb +19 -0
- data/app/controllers/decidim/stratified_sortitions/admin/samples_controller.rb +143 -0
- data/app/controllers/decidim/stratified_sortitions/admin/stratified_sortitions_controller.rb +235 -0
- data/app/controllers/decidim/stratified_sortitions/application_controller.rb +13 -0
- data/app/controllers/decidim/stratified_sortitions/charts_pdf_controller_helper.rb +17 -0
- data/app/controllers/decidim/stratified_sortitions/stratified_sortitions_controller.rb +61 -0
- data/app/forms/decidim/stratified_sortitions/admin/sample_upload_form.rb +15 -0
- data/app/forms/decidim/stratified_sortitions/admin/stratified_sortitions_form.rb +155 -0
- data/app/forms/decidim/stratified_sortitions/admin/stratum_form.rb +69 -0
- data/app/forms/decidim/stratified_sortitions/admin/substratum_form.rb +26 -0
- data/app/helpers/decidim/stratified_sortitions/admin/stratified_sortitions_helper.rb +18 -0
- data/app/helpers/decidim/stratified_sortitions/application_helper.rb +85 -0
- data/app/helpers/decidim/stratified_sortitions/charts_pdf_helper.rb +73 -0
- data/app/helpers/decidim/stratified_sortitions/stratified_sortition_cells_helper.rb +56 -0
- data/app/javascript/channels/sample_import_progress.js +12 -0
- data/app/jobs/decidim/stratified_sortitions/admin/execute_sortition_job.rb +21 -0
- data/app/jobs/decidim/stratified_sortitions/admin/import_sample_job.rb +170 -0
- data/app/jobs/decidim/stratified_sortitions/admin/remove_samples_job.rb +19 -0
- data/app/jobs/decidim/stratified_sortitions/admin/sortition_results_export_job.rb +28 -0
- data/app/mailers/decidim/stratified_sortitions/admin/import_mailer.rb +26 -0
- data/app/models/decidim/stratified_sortitions/application_record.rb +9 -0
- data/app/models/decidim/stratified_sortitions/panel_portfolio.rb +165 -0
- data/app/models/decidim/stratified_sortitions/sample_import.rb +14 -0
- data/app/models/decidim/stratified_sortitions/sample_participant.rb +12 -0
- data/app/models/decidim/stratified_sortitions/sample_participant_stratum.rb +11 -0
- data/app/models/decidim/stratified_sortitions/stratified_sortition.rb +74 -0
- data/app/models/decidim/stratified_sortitions/stratum.rb +22 -0
- data/app/models/decidim/stratified_sortitions/substratum.rb +19 -0
- data/app/packs/entrypoints/decidim_stratified_sortitions.js +10 -0
- data/app/packs/entrypoints/decidim_stratified_sortitions_admin.js +9 -0
- data/app/packs/entrypoints/decidim_stratified_sortitions_admin.scss +1 -0
- data/app/packs/images/decidim/stratified_sortitions/icon.svg +1 -0
- data/app/packs/src/decidim/stratified_sortitions/application.js +3 -0
- data/app/packs/src/decidim/stratified_sortitions/results_tabs.js +59 -0
- data/app/packs/src/decidim/stratified_sortitions/stratum_fields.js +302 -0
- data/app/packs/src/decidim/stratified_sortitions/substratum_fields.js +165 -0
- data/app/packs/src/decidim/stratified_sortitions/upload_sample.js +16 -0
- data/app/packs/stylesheets/decidim/stratified_sortitions/admin/stratified_sortitions.scss +210 -0
- data/app/packs/stylesheets/decidim/stratified_sortitions/charts_pdf.scss +148 -0
- data/app/packs/stylesheets/stratified_sortitions.scss +56 -0
- data/app/permissions/decidim/stratified_sortitions/admin/permissions.rb +74 -0
- data/app/permissions/decidim/stratified_sortitions/permissions.rb +23 -0
- data/app/presenters/decidim/stratified_sortitions/admin_log/stratified_sortition_presenter.rb +35 -0
- data/app/queries/decidim/stratified_sortitions/filtered_stratified_sortitions.rb +39 -0
- data/app/serializers/decidim/stratified_sortitions/sortition_result_serializer.rb +116 -0
- data/app/services/decidim/stratified_sortitions/charts_pdf_generator.rb +96 -0
- data/app/services/decidim/stratified_sortitions/fair_sortition_service.rb +247 -0
- data/app/services/decidim/stratified_sortitions/leximin/constraint_builder.rb +180 -0
- data/app/services/decidim/stratified_sortitions/leximin/distribution_solver.rb +217 -0
- data/app/services/decidim/stratified_sortitions/leximin/feasibility_checker.rb +222 -0
- data/app/services/decidim/stratified_sortitions/leximin/panel_generator.rb +149 -0
- data/app/services/decidim/stratified_sortitions/leximin/panel_sampler.rb +125 -0
- data/app/services/decidim/stratified_sortitions/leximin_selector.rb +136 -0
- data/app/services/decidim/stratified_sortitions/sortition_results_exporter.rb +161 -0
- data/app/views/decidim/admin/components/index.html.erb +54 -0
- data/app/views/decidim/stratified_sortitions/admin/import_mailer/import.html.erb +22 -0
- data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/_confirm_execute_modal.html.erb +19 -0
- data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/_confirm_participants_modal.html.erb +20 -0
- data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/_form.html.erb +55 -0
- data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/_navigation_menu.html.erb +10 -0
- data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/_strata.html.erb +22 -0
- data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/_stratum.html.erb +75 -0
- data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/_substrata.html.erb +34 -0
- data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/_substratum.html.erb +54 -0
- data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/edit.html.erb +21 -0
- data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/execute.html.erb +155 -0
- data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/export_charts_pdf.html.erb +38 -0
- data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/index.html.erb +75 -0
- data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/new.html.erb +21 -0
- data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/upload_sample.html.erb +117 -0
- data/app/views/decidim/stratified_sortitions/shared/_strata_charts.html.erb +21 -0
- data/app/views/decidim/stratified_sortitions/stratified_sortitions/_results_count.html.erb +15 -0
- data/app/views/decidim/stratified_sortitions/stratified_sortitions/_stratified_sortition.html.erb +1 -0
- data/app/views/decidim/stratified_sortitions/stratified_sortitions/_stratified_sortitions.html.erb +13 -0
- data/app/views/decidim/stratified_sortitions/stratified_sortitions/_tags.html.erb +9 -0
- data/app/views/decidim/stratified_sortitions/stratified_sortitions/index.html.erb +22 -0
- data/app/views/decidim/stratified_sortitions/stratified_sortitions/index.js.erb +5 -0
- data/app/views/decidim/stratified_sortitions/stratified_sortitions/show.html.erb +142 -0
- data/app/views/layouts/decidim/admin/_sidebar_menu.html.erb +45 -0
- data/app/views/layouts/decidim/stratified_sortitions/charts_pdf.html.erb +14 -0
- data/config/assets.rb +24 -0
- data/config/locales/ca.yml +361 -0
- data/config/locales/en.yml +360 -0
- data/config/locales/es.yml +360 -0
- data/config/locales/oc.yml +2 -0
- data/config/routes.rb +1 -0
- data/db/migrate/20251023103900_create_decidim_stratified_sortitions_stratified_sortitions.rb +18 -0
- data/db/migrate/20251104120000_create_stratified_sortitions_strata_and_substrata.decidim_stratified_sortitions.rb +27 -0
- data/db/migrate/20251219120000_create_sample_imports.rb +17 -0
- data/db/migrate/20251219120100_create_sample_participants.rb +19 -0
- data/db/migrate/20251219120200_create_sample_participant_strata.rb +16 -0
- data/db/migrate/20251230130000_change_value_to_text_in_substrata.rb +11 -0
- data/db/migrate/20260102114744_add_status_to_stratified_sortitions.rb +11 -0
- data/db/migrate/20260109105643_allow_null_value_in_substrata.rb +10 -0
- data/db/migrate/20260113121726_add_position_to_strata_and_substrata.rb +8 -0
- data/db/migrate/20260127100000_rename_weighing_to_max_quota_percentage.rb +12 -0
- data/db/migrate/20260127120000_create_panel_portfolios.rb +46 -0
- data/db/migrate/20260518000000_add_foreign_key_to_stratified_sortitions_component.rb +10 -0
- data/db/migrate/20260521000000_add_cascade_to_stratified_sortitions_foreign_keys.rb +64 -0
- data/db/migrate/20260616000000_add_execution_error_to_stratified_sortitions.rb +11 -0
- data/lib/decidim/stratified_sortitions/admin.rb +10 -0
- data/lib/decidim/stratified_sortitions/admin_engine.rb +49 -0
- data/lib/decidim/stratified_sortitions/component.rb +41 -0
- data/lib/decidim/stratified_sortitions/engine.rb +34 -0
- data/lib/decidim/stratified_sortitions/seeds.rb +24 -0
- data/lib/decidim/stratified_sortitions/test/factories.rb +80 -0
- data/lib/decidim/stratified_sortitions/version.rb +13 -0
- data/lib/decidim/stratified_sortitions.rb +29 -0
- metadata +222 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
module StratifiedSortitions
|
|
5
|
+
class SampleParticipant < ApplicationRecord
|
|
6
|
+
belongs_to :decidim_stratified_sortition, class_name: "Decidim::StratifiedSortitions::StratifiedSortition"
|
|
7
|
+
belongs_to :decidim_stratified_sortitions_sample_import, class_name: "Decidim::StratifiedSortitions::SampleImport", optional: true
|
|
8
|
+
|
|
9
|
+
has_many :sample_participant_strata, dependent: :destroy, foreign_key: :decidim_stratified_sortitions_sample_participant_id
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
module StratifiedSortitions
|
|
5
|
+
class SampleParticipantStratum < ApplicationRecord
|
|
6
|
+
belongs_to :decidim_stratified_sortitions_sample_participant, class_name: "Decidim::StratifiedSortitions::SampleParticipant"
|
|
7
|
+
belongs_to :decidim_stratified_sortitions_stratum, class_name: "Decidim::StratifiedSortitions::Stratum"
|
|
8
|
+
belongs_to :decidim_stratified_sortitions_substratum, class_name: "Decidim::StratifiedSortitions::Substratum"
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
module StratifiedSortitions
|
|
5
|
+
class StratifiedSortition < ApplicationRecord
|
|
6
|
+
include Decidim::HasComponent
|
|
7
|
+
include Decidim::FilterableResource
|
|
8
|
+
include Decidim::ScopableResource
|
|
9
|
+
include Decidim::Loggable
|
|
10
|
+
include Decidim::Resourceable
|
|
11
|
+
include Decidim::Searchable
|
|
12
|
+
include Decidim::Traceable
|
|
13
|
+
include Decidim::TranslatableAttributes
|
|
14
|
+
include Decidim::Randomable
|
|
15
|
+
include Decidim::HasUploadValidations
|
|
16
|
+
|
|
17
|
+
delegate :mounted_engine, :mounted_admin_engine, :mounted_params, to: :component
|
|
18
|
+
|
|
19
|
+
component_manifest_name "stratified_sortitions"
|
|
20
|
+
|
|
21
|
+
def self.log_presenter_class_for(_log)
|
|
22
|
+
Decidim::StratifiedSortitions::AdminLog::StratifiedSortitionPresenter
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
has_many :strata, class_name: "Decidim::StratifiedSortitions::Stratum", foreign_key: "decidim_stratified_sortition_id", dependent: :destroy
|
|
26
|
+
has_many :sample_imports, class_name: "Decidim::StratifiedSortitions::SampleImport", dependent: :destroy
|
|
27
|
+
has_many :sample_participants, class_name: "Decidim::StratifiedSortitions::SampleParticipant", foreign_key: "decidim_stratified_sortition_id", dependent: :destroy
|
|
28
|
+
has_one :panel_portfolio, class_name: "Decidim::StratifiedSortitions::PanelPortfolio", foreign_key: "decidim_stratified_sortitions_stratified_sortition_id",
|
|
29
|
+
dependent: :destroy
|
|
30
|
+
|
|
31
|
+
scope :search_text_cont, lambda { |search_text|
|
|
32
|
+
where("title ->> '#{I18n.locale}' ILIKE ?", "%#{search_text}%")
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
scope :with_any_state, lambda { |*values|
|
|
36
|
+
values = values.flatten.compact_blank
|
|
37
|
+
return all if values.empty? || values.include?("all")
|
|
38
|
+
|
|
39
|
+
where(status: values)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
def self.ransackable_scopes(_auth_object = nil)
|
|
43
|
+
[:search_text_cont, :with_any_state]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
searchable_fields({
|
|
47
|
+
participatory_space: :itself,
|
|
48
|
+
A: :title,
|
|
49
|
+
B: :description,
|
|
50
|
+
datetime: :created_at,
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
def strata_and_substrata_configured?
|
|
54
|
+
strata.any? && strata.all? { |stratum| stratum.substrata.any? }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def can_execute?
|
|
58
|
+
strata_and_substrata_configured? && !sample_participants.empty? && !executing?
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def executed?
|
|
62
|
+
status == "executed"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def executing?
|
|
66
|
+
status == "executing"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def failed?
|
|
70
|
+
status == "failed"
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
module StratifiedSortitions
|
|
5
|
+
class Stratum < ApplicationRecord
|
|
6
|
+
self.table_name = "decidim_stratified_sortitions_strata"
|
|
7
|
+
|
|
8
|
+
include Decidim::TranslatableResource
|
|
9
|
+
include Decidim::TranslatableAttributes
|
|
10
|
+
|
|
11
|
+
belongs_to :stratified_sortition, class_name: "Decidim::StratifiedSortitions::StratifiedSortition", foreign_key: "decidim_stratified_sortition_id"
|
|
12
|
+
has_many :substrata, class_name: "Decidim::StratifiedSortitions::Substratum", foreign_key: "decidim_stratified_sortitions_stratum_id", dependent: :destroy
|
|
13
|
+
has_many :sample_participant_strata, class_name: "Decidim::StratifiedSortitions::SampleParticipantStratum",
|
|
14
|
+
foreign_key: "decidim_stratified_sortitions_stratum_id",
|
|
15
|
+
dependent: :destroy
|
|
16
|
+
|
|
17
|
+
KINDS = %w(value numeric_range).freeze
|
|
18
|
+
|
|
19
|
+
translatable_fields :name
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
module StratifiedSortitions
|
|
5
|
+
class Substratum < ApplicationRecord
|
|
6
|
+
self.table_name = "decidim_stratified_sortitions_substrata"
|
|
7
|
+
|
|
8
|
+
include Decidim::TranslatableResource
|
|
9
|
+
include Decidim::TranslatableAttributes
|
|
10
|
+
|
|
11
|
+
belongs_to :stratum, class_name: "Decidim::StratifiedSortitions::Stratum", foreign_key: "decidim_stratified_sortitions_stratum_id"
|
|
12
|
+
has_many :sample_participant_strata, class_name: "Decidim::StratifiedSortitions::SampleParticipantStratum",
|
|
13
|
+
foreign_key: "decidim_stratified_sortitions_substratum_id",
|
|
14
|
+
dependent: :destroy
|
|
15
|
+
|
|
16
|
+
translatable_fields :name
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import "src/decidim/stratified_sortitions/stratum_fields"
|
|
2
|
+
import "src/decidim/stratified_sortitions/substratum_fields"
|
|
3
|
+
|
|
4
|
+
import "chartkick/chart.js"
|
|
5
|
+
|
|
6
|
+
// Images
|
|
7
|
+
require.context("../images", true)
|
|
8
|
+
|
|
9
|
+
// CSS
|
|
10
|
+
import "stylesheets/stratified_sortitions.scss"
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import "src/decidim/stratified_sortitions/stratum_fields"
|
|
2
|
+
import "src/decidim/stratified_sortitions/substratum_fields"
|
|
3
|
+
import "src/decidim/stratified_sortitions/results_tabs"
|
|
4
|
+
|
|
5
|
+
import "chartkick/chart.js"
|
|
6
|
+
|
|
7
|
+
import "entrypoints/decidim_stratified_sortitions_admin.scss"
|
|
8
|
+
|
|
9
|
+
import "src/decidim/stratified_sortitions/upload_sample"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import "stylesheets/decidim/stratified_sortitions/admin/stratified_sortitions.scss";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" data-icon="aperture" version="1.1" viewBox="0 0 34 34"><path d="M17 0c9.389 0 17 7.611 17 17s-7.611 17-17 17S0 26.389 0 17 7.611 0 17 0zm0 1.889C8.654 1.889 1.889 8.654 1.889 17S8.654 32.111 17 32.111 32.111 25.346 32.111 17 25.346 1.889 17 1.889zm11.865 10.74a3.15 3.15 0 1 1-6.3 0 3.15 3.15 0 0 1 6.3 0zm-1.89 0a1.26 1.26 0 1 0-2.521 0 1.26 1.26 0 0 0 2.522 0zM15.25 19.136a.944.944 0 0 1 1.89 0v1.792a5.71 5.71 0 1 1-11.423 0v-1.792a.944.944 0 0 1 1.89 0v1.792a3.822 3.822 0 1 0 7.643 0v-1.792zm5.841-6.974l.018.019a.941.941 0 0 1-.02 1.335l-2.514 2.44a.944.944 0 0 1-1.316-1.355l.843-.818a6.237 6.237 0 0 0-6.177 6.23.944.944 0 0 1-1.889 0c0-4.492 3.653-8.12 8.12-8.12h.041l-.958-.986a.944.944 0 0 1 1.355-1.316l2.497 2.571a.941.941 0 0 0-.66-.268h-2.234z"/></svg>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
2
|
+
const tabContainer = document.querySelector("[data-results-tabs]");
|
|
3
|
+
if (!tabContainer) return;
|
|
4
|
+
|
|
5
|
+
const tabs = tabContainer.querySelectorAll("[data-tab-target]");
|
|
6
|
+
const panels = document.querySelectorAll("[data-tab-panel]");
|
|
7
|
+
|
|
8
|
+
function activateTab(targetId) {
|
|
9
|
+
tabs.forEach((t) => t.classList.remove("active"));
|
|
10
|
+
panels.forEach((p) => (p.style.display = "none"));
|
|
11
|
+
|
|
12
|
+
const tab = tabContainer.querySelector(`[data-tab-target="${targetId}"]`);
|
|
13
|
+
if (tab) tab.classList.add("active");
|
|
14
|
+
|
|
15
|
+
const targetPanel = document.getElementById(targetId);
|
|
16
|
+
if (targetPanel) targetPanel.style.display = "";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
tabs.forEach((tab) => {
|
|
20
|
+
tab.addEventListener("click", () => {
|
|
21
|
+
const targetId = tab.dataset.tabTarget;
|
|
22
|
+
|
|
23
|
+
// If the tab opens a confirmation modal, don't switch yet
|
|
24
|
+
if (tab.dataset.dialogOpen) return;
|
|
25
|
+
|
|
26
|
+
activateTab(targetId);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// When the user confirms in the participants modal, switch to that tab
|
|
31
|
+
const confirmBtn = document.getElementById("confirm-show-participants");
|
|
32
|
+
if (confirmBtn) {
|
|
33
|
+
confirmBtn.addEventListener("click", () => {
|
|
34
|
+
activateTab("participants-section");
|
|
35
|
+
|
|
36
|
+
// Log the view_participants action
|
|
37
|
+
const logUrl = confirmBtn.dataset.logUrl;
|
|
38
|
+
const csrfToken = confirmBtn.dataset.authenticityToken;
|
|
39
|
+
if (logUrl && csrfToken) {
|
|
40
|
+
fetch(logUrl, {
|
|
41
|
+
method: "POST",
|
|
42
|
+
headers: {
|
|
43
|
+
"X-CSRF-Token": csrfToken,
|
|
44
|
+
"Content-Type": "application/json",
|
|
45
|
+
},
|
|
46
|
+
}).catch(() => {
|
|
47
|
+
// Silently ignore log errors
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Close the modal
|
|
52
|
+
const modal = document.getElementById("confirm-participants-modal");
|
|
53
|
+
if (modal) {
|
|
54
|
+
const closeBtn = modal.querySelector("[data-dialog-close]");
|
|
55
|
+
if (closeBtn) closeBtn.click();
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
});
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import AutoButtonsByPositionComponent from "src/decidim/admin/auto_buttons_by_position.component"
|
|
2
|
+
import AutoLabelByPositionComponent from "src/decidim/admin/auto_label_by_position.component"
|
|
3
|
+
import createLiveTextUpdateComponent from "src/decidim/forms/admin/live_text_update.component"
|
|
4
|
+
import createSortList from "src/decidim/admin/sort_list.component"
|
|
5
|
+
import { initializeSubstrataWrapper } from "src/decidim/stratified_sortitions/substratum_fields"
|
|
6
|
+
import { createAccordion } from "src/decidim/a11y"
|
|
7
|
+
|
|
8
|
+
$(() => {
|
|
9
|
+
const wrapperSelector = ".stratified-sortition-strata";
|
|
10
|
+
const fieldSelector = ".stratified-sortition-stratum";
|
|
11
|
+
|
|
12
|
+
const autoLabelByPosition = new AutoLabelByPositionComponent({
|
|
13
|
+
listSelector: ".stratified-sortition-stratum:not(.hidden)",
|
|
14
|
+
labelSelector: ".card-title span:first",
|
|
15
|
+
onPositionComputed: (el, idx) => {
|
|
16
|
+
$(el).find("input[name$=\\[position\\]]").not("[name*=substrata]").first().val(idx);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const autoButtonsByPosition = new AutoButtonsByPositionComponent({
|
|
21
|
+
listSelector: ".stratified-sortition-stratum:not(.hidden)",
|
|
22
|
+
hideOnFirstSelector: ".move-up-stratum",
|
|
23
|
+
hideOnLastSelector: ".move-down-stratum"
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const createSortableList = () => {
|
|
27
|
+
createSortList(".stratified-sortition-strata-list:not(.published)", {
|
|
28
|
+
handle: ".stratum-divider",
|
|
29
|
+
placeholder: '<div style="border-style: dashed; border-color: #000"></div>',
|
|
30
|
+
forcePlaceholderSize: true,
|
|
31
|
+
onSortUpdate: () => { autoLabelByPosition.run() }
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const hideDeletedStratum = ($target) => {
|
|
36
|
+
const inputDeleted = $target.find("input[name$=\\[deleted\\]]").val();
|
|
37
|
+
|
|
38
|
+
if (inputDeleted === "true") {
|
|
39
|
+
$target.addClass("hidden");
|
|
40
|
+
$target.hide();
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const runComponents = () => {
|
|
45
|
+
autoLabelByPosition.run();
|
|
46
|
+
autoButtonsByPosition.run();
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const expandStratum = ($stratum) => {
|
|
50
|
+
const $toggle = $stratum.find('.stratum--collapse');
|
|
51
|
+
if ($toggle.length === 0) return;
|
|
52
|
+
const ctrl = $toggle.attr('data-controls');
|
|
53
|
+
const panel = ctrl ? document.getElementById(ctrl) : null;
|
|
54
|
+
const isExpanded = $toggle.attr('aria-expanded') === 'true';
|
|
55
|
+
|
|
56
|
+
if (!isExpanded) {
|
|
57
|
+
$toggle.trigger('click');
|
|
58
|
+
|
|
59
|
+
setTimeout(() => {
|
|
60
|
+
const nowExpanded = $toggle.attr('aria-expanded') === 'true';
|
|
61
|
+
if (!nowExpanded && panel) {
|
|
62
|
+
$toggle.attr('aria-expanded', 'true');
|
|
63
|
+
panel.setAttribute('aria-hidden', 'false');
|
|
64
|
+
$(panel).show();
|
|
65
|
+
}
|
|
66
|
+
}, 0);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const createCollapsibleStratum = ($target) => {
|
|
71
|
+
const $collapsible = $target.find(".collapsible");
|
|
72
|
+
if ($collapsible.length > 0) {
|
|
73
|
+
const collapsibleId = $collapsible.attr("id").replace("-stratum-card", "");
|
|
74
|
+
const toggleAttr = `${collapsibleId}-stratum-card`;
|
|
75
|
+
|
|
76
|
+
$target.find(".stratum--collapse")
|
|
77
|
+
.attr("data-controls", toggleAttr)
|
|
78
|
+
.attr("aria-controls", toggleAttr)
|
|
79
|
+
.each((_i, btn) => {
|
|
80
|
+
if (!btn.id) {
|
|
81
|
+
btn.id = `${toggleAttr}-toggle`;
|
|
82
|
+
}
|
|
83
|
+
if (!btn.hasAttribute("aria-expanded")) {
|
|
84
|
+
btn.setAttribute("aria-expanded", "false");
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const createDynamicStratumTitle = (fieldId) => {
|
|
91
|
+
const targetSelector = `#${fieldId} .stratum-title-statement`;
|
|
92
|
+
const locale = $(targetSelector).data("locale");
|
|
93
|
+
const maxLength = $(targetSelector).data("max-length");
|
|
94
|
+
const omission = $(targetSelector).data("omission");
|
|
95
|
+
const placeholder = $(targetSelector).data("placeholder");
|
|
96
|
+
|
|
97
|
+
return createLiveTextUpdateComponent({
|
|
98
|
+
inputSelector: `#${fieldId} input[name$=\\[name_${locale}\\]]`,
|
|
99
|
+
targetSelector: targetSelector,
|
|
100
|
+
maxLength: maxLength,
|
|
101
|
+
omission: omission,
|
|
102
|
+
placeholder: placeholder
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
$(document).on("click", ".add-stratum", function(e) {
|
|
107
|
+
e.preventDefault();
|
|
108
|
+
e.stopPropagation();
|
|
109
|
+
|
|
110
|
+
const $button = $(this);
|
|
111
|
+
const $wrapper = $button.closest(wrapperSelector);
|
|
112
|
+
const $container = $wrapper.find(".stratified-sortition-strata-list");
|
|
113
|
+
const $template = $wrapper.find("template.decidim-template, script.decidim-template");
|
|
114
|
+
|
|
115
|
+
if ($template.length && $container.length) {
|
|
116
|
+
|
|
117
|
+
const templateContent = $template.html();
|
|
118
|
+
const uniqueId = new Date().getTime();
|
|
119
|
+
let newField = templateContent.replace(/(?<!SUB)STRATUM_ID/g, uniqueId);
|
|
120
|
+
newField = newField.replace(/stratified-sortition-stratum-id/g, `stratified-sortition-stratum-${uniqueId}`);
|
|
121
|
+
newField = newField.replace(/substrata-\d+/g, `substrata-${uniqueId}`);
|
|
122
|
+
$container.append(newField);
|
|
123
|
+
|
|
124
|
+
const $newField = $container.find(fieldSelector).last();
|
|
125
|
+
$newField.find(".stratified-sortition-substrata").each((idx, wrapperEl) => {
|
|
126
|
+
if (!wrapperEl.id) {
|
|
127
|
+
wrapperEl.id = `substrata-${uniqueId}`;
|
|
128
|
+
}
|
|
129
|
+
initializeSubstrataWrapper(wrapperEl);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
$newField.find('[data-tabs]').each(function() {
|
|
133
|
+
new Foundation.Tabs($(this));
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
updateSubstratumFieldsVisibility($newField);
|
|
137
|
+
createCollapsibleStratum($newField);
|
|
138
|
+
createDynamicStratumTitle($newField.attr("id"));
|
|
139
|
+
|
|
140
|
+
const $newPanel = $newField.find('.collapsible');
|
|
141
|
+
const $newToggle = $newField.find('.stratum--collapse');
|
|
142
|
+
if ($newPanel.length && $newToggle.length) {
|
|
143
|
+
$newToggle.attr('aria-expanded', 'true');
|
|
144
|
+
$newPanel.attr('aria-hidden', 'false').show();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
createSortableList();
|
|
148
|
+
runComponents();
|
|
149
|
+
|
|
150
|
+
const newAccordion = $newField[0].querySelector('[data-component="accordion"]');
|
|
151
|
+
if (newAccordion) {
|
|
152
|
+
try { createAccordion(newAccordion); } catch (_e) {}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (document && typeof document.dispatchEvent === "function") {
|
|
156
|
+
try {
|
|
157
|
+
document.dispatchEvent(new CustomEvent("ajax:loaded", { detail: $newField[0] }));
|
|
158
|
+
} catch (_e) {}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
$(document).on("click", ".remove-stratum", function(e) {
|
|
165
|
+
e.preventDefault();
|
|
166
|
+
e.stopPropagation();
|
|
167
|
+
|
|
168
|
+
const $field = $(this).closest(fieldSelector);
|
|
169
|
+
|
|
170
|
+
$field.find("input[name$=\\[deleted\\]]").val("true");
|
|
171
|
+
$field.addClass("hidden");
|
|
172
|
+
$field.hide();
|
|
173
|
+
|
|
174
|
+
runComponents();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
$(document).on("click", ".move-up-stratum", function(e) {
|
|
178
|
+
e.preventDefault();
|
|
179
|
+
e.stopPropagation();
|
|
180
|
+
|
|
181
|
+
const $field = $(this).closest(fieldSelector);
|
|
182
|
+
const $prev = $field.prevAll(fieldSelector + ":not(.hidden)").first();
|
|
183
|
+
|
|
184
|
+
if ($prev.length) {
|
|
185
|
+
$field.insertBefore($prev);
|
|
186
|
+
runComponents();
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
$(document).on("click", ".move-down-stratum", function(e) {
|
|
191
|
+
e.preventDefault();
|
|
192
|
+
e.stopPropagation();
|
|
193
|
+
|
|
194
|
+
const $field = $(this).closest(fieldSelector);
|
|
195
|
+
const $next = $field.nextAll(fieldSelector + ":not(.hidden)").first();
|
|
196
|
+
|
|
197
|
+
if ($next.length) {
|
|
198
|
+
$field.insertAfter($next);
|
|
199
|
+
runComponents();
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const updateSubstratumFieldsVisibility = ($stratum) => {
|
|
204
|
+
const kind = $stratum.find("select[id$='_kind']").val();
|
|
205
|
+
if (kind === "value") {
|
|
206
|
+
$stratum.find(".substratum-value-field").show();
|
|
207
|
+
$stratum.find(".substratum-range-field").hide();
|
|
208
|
+
} else {
|
|
209
|
+
$stratum.find(".substratum-value-field").hide();
|
|
210
|
+
$stratum.find(".substratum-range-field").show();
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
$(document).on("change", "select[id$='_kind']", function() {
|
|
215
|
+
const $stratum = $(this).closest(fieldSelector);
|
|
216
|
+
updateSubstratumFieldsVisibility($stratum);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
const makeRequiredCatalanFields = () => {
|
|
220
|
+
$(fieldSelector).find(".stratum-fields input[id$='_name_ca']").attr("required", true);
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
$("form").on("submit", function(e) {
|
|
224
|
+
let hasEmptyName = false;
|
|
225
|
+
let $firstInvalidStratum = null;
|
|
226
|
+
|
|
227
|
+
$(fieldSelector).each((idx, el) => {
|
|
228
|
+
const $stratum = $(el);
|
|
229
|
+
const isDeleted = $stratum.find("input[name$='[deleted]']").val() === "true";
|
|
230
|
+
const $nameField = $stratum.find(".stratum-fields input[id$='_name_ca']");
|
|
231
|
+
|
|
232
|
+
$nameField.removeClass("is-invalid-input");
|
|
233
|
+
|
|
234
|
+
if (!isDeleted) {
|
|
235
|
+
const nameValue = $nameField.val();
|
|
236
|
+
if (!nameValue || nameValue.trim() === "") {
|
|
237
|
+
hasEmptyName = true;
|
|
238
|
+
$nameField.addClass("is-invalid-input");
|
|
239
|
+
|
|
240
|
+
expandStratum($stratum);
|
|
241
|
+
|
|
242
|
+
if (!$firstInvalidStratum) {
|
|
243
|
+
$firstInvalidStratum = $stratum;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
if (hasEmptyName) {
|
|
250
|
+
e.preventDefault();
|
|
251
|
+
if ($firstInvalidStratum) {
|
|
252
|
+
$firstInvalidStratum[0].scrollIntoView({ behavior: "smooth", block: "center" });
|
|
253
|
+
}
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
createSortableList();
|
|
259
|
+
|
|
260
|
+
$(fieldSelector).each((idx, el) => {
|
|
261
|
+
const $target = $(el);
|
|
262
|
+
hideDeletedStratum($target);
|
|
263
|
+
updateSubstratumFieldsVisibility($target);
|
|
264
|
+
createCollapsibleStratum($target);
|
|
265
|
+
createDynamicStratumTitle($target.attr("id"));
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
makeRequiredCatalanFields();
|
|
269
|
+
|
|
270
|
+
runComponents();
|
|
271
|
+
|
|
272
|
+
if (document && typeof document.dispatchEvent === "function") {
|
|
273
|
+
try {
|
|
274
|
+
const $root = $(wrapperSelector).first();
|
|
275
|
+
if ($root.length) {
|
|
276
|
+
document.dispatchEvent(new CustomEvent("ajax:loaded", { detail: $root[0] }));
|
|
277
|
+
}
|
|
278
|
+
} catch (_e) {}
|
|
279
|
+
}
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
$(document).on("click", ".stratified-sortition-stratum .stratum--collapse", function(e) {
|
|
283
|
+
const btn = this;
|
|
284
|
+
|
|
285
|
+
if (btn.hasAttribute("role")) return;
|
|
286
|
+
|
|
287
|
+
const ctrl = btn.getAttribute("data-controls");
|
|
288
|
+
if (!ctrl) return;
|
|
289
|
+
const panel = document.getElementById(ctrl);
|
|
290
|
+
if (!panel) return;
|
|
291
|
+
|
|
292
|
+
const expanded = btn.getAttribute("aria-expanded") === "true";
|
|
293
|
+
const next = !expanded;
|
|
294
|
+
btn.setAttribute("aria-expanded", next ? "true" : "false");
|
|
295
|
+
panel.setAttribute("aria-hidden", next ? "false" : "true");
|
|
296
|
+
|
|
297
|
+
if (next) {
|
|
298
|
+
$(panel).show();
|
|
299
|
+
} else {
|
|
300
|
+
$(panel).hide();
|
|
301
|
+
}
|
|
302
|
+
});
|