Add politicians question type

This commit is contained in:
Charlotte Van Petegem 2025-05-17 11:58:07 +02:00
parent 4fe218166f
commit 2893f2bc53
Signed by: chvp
SSH key fingerprint: SHA256:s9rb8jBVfdahqWHuBAcHCBP1wmj4eYQXZfqgz4H3E9E
16 changed files with 101 additions and 33 deletions

View file

@ -1 +1,5 @@
/* Entry point for your PostCSS build */
.politicians-listing {
display: flex;
flex-direction: row;
}

View file

@ -20,4 +20,8 @@ class QuestionsController < ApplicationController
)
@answer.save!
end
def handle_politicians_answer
@answer.update!(data: params[:order])
end
end

View file

@ -1,2 +1,5 @@
module QuestionsHelper
def reorder_with_indices(array, indices)
array.sort {|a1, a2| indices.index(a1[1]) <=> indices.index(a2[1])}
end
end

View file

@ -1,8 +1,10 @@
// Entry point for the build script in your package.json
import initSimpleQuestions from './simple_input.js'
import initImageQuestions from './image_input.js'
import initPoliticianQuestions from './politicians_answer.js'
addEventListener("DOMContentLoaded", () => {
initSimpleQuestions()
initImageQuestions()
initPoliticianQuestions()
})

View file

@ -1,4 +1,4 @@
const csrfToken = document.querySelector("[name='csrf-token']").content
import { submitValue } from './submit_value.js'
export default function initImageQuestions() {
document.querySelectorAll('[data-behaviour="question_image_input"]').forEach((input) => {
@ -8,17 +8,10 @@ export default function initImageQuestions() {
const file = input.files[0];
const fileReader = new FileReader();
fileReader.onload = (ev) => {
fetch(submitUrl, {
method: 'PUT',
headers: {
"x-csrf-token": csrfToken,
"content-type": "application/json",
},
body: JSON.stringify({
filename: file.name,
mimetype: file.type,
data: ev.target.result.replace(/^data:[a-zA-Z0-9!#$%^&\\*_\-+{}|'.`~]+\/[a-zA-Z0-9!#$%^&\\*_\-+{}|'.`~]+;base64,/, ""),
})
submitValue(submitUrl, {
filename: file.name,
mimetype: file.type,
data: ev.target.result.replace(/^data:[a-zA-Z0-9!#$%^&\\*_\-+{}|'.`~]+\/[a-zA-Z0-9!#$%^&\\*_\-+{}|'.`~]+;base64,/, ""),
})
}
fileReader.readAsDataURL(file);

View file

@ -0,0 +1,20 @@
import { Sortable, Swap } from 'sortablejs'
import { submitValue } from './submit_value.js'
export default function initPoliticianQuestions() {
Sortable.mount(new Swap())
document.querySelectorAll('[data-behaviour="politicians_answer').forEach((list) => {
const submitUrl = list.dataset.submitUrl
Sortable.create(list, {
swap: true,
onUpdate: (event) => {
const newOrder = Array.from(event.to.children).map((el) => Number.parseInt(el.dataset.id))
submitValue(submitUrl, { order: newOrder })
}
})
})
}

View file

@ -1,23 +1,11 @@
import debounce from 'debounce'
const csrfToken = document.querySelector("[name='csrf-token']").content
import { submitValueDebounced } from './submit_value.js'
export default function initSimpleQuestions() {
document.querySelectorAll('[data-behaviour="question_simple_input"]').forEach((input) => {
const submitUrl = input.dataset.submitUrl
const submit = submitValueDebounced()
const handleInput = debounce(() => {
fetch(submitUrl, {
method: 'PUT',
headers: {
"x-csrf-token": csrfToken,
"content-type": "application/json",
},
body: JSON.stringify({ value: input.value }),
})
}, 300)
input.addEventListener('change', handleInput)
input.addEventListener('input', handleInput)
input.addEventListener('change', () => submit(submitUrl, {value: input.value}))
input.addEventListener('input', () => submit(submitUrl, {value: input.value}))
})
}

View file

@ -0,0 +1,18 @@
import debounce from 'debounce'
const csrfToken = document.querySelector("[name='csrf-token']").content
export function submitValue(url, value) {
fetch(url, {
method: 'PUT',
headers: {
"x-csrf-token": csrfToken,
"content-type": "application/json",
},
body: JSON.stringify(value),
})
}
export function submitValueDebounced() {
return debounce(submitValue, 300)
}

View file

@ -4,6 +4,7 @@
#
# id :integer not null, primary key
# answer_kind :integer not null
# data :text
# public_asset_path :string
# question_kind :integer default("simple"), not null
# text :text not null
@ -25,4 +26,6 @@ class Question < ApplicationRecord
belongs_to :section
has_one :answer
serialize :data, coder: JSON
end

View file

@ -1,3 +1,16 @@
<div>
This one's going to be hard...
<div class="politicians-listing">
<div>
<% question.data["left"].each do |l| %>
<p><%= l %></p>
<% end %>
</div>
<div data-behaviour="politicians_answer"
data-submit-url="<%= answer_question_url(question.id) %>"
>
<% answers = question.data["right"].each.with_index.to_a %>
<% answers = reorder_with_indices(answers, question.answer.data) if question.answer.present? %>
<% answers.each do |a, i| %>
<p data-id="<%= i %>"><%= a %></p>
<% end %>
</div>
</div>

View file

@ -0,0 +1,5 @@
class QuestionAddDataField < ActiveRecord::Migration[8.0]
def change
add_column :questions, :data, :text
end
end

3
db/schema.rb generated
View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.0].define(version: 2025_04_26_202909) do
ActiveRecord::Schema[8.0].define(version: 2025_05_15_124656) do
create_table "active_storage_attachments", force: :cascade do |t|
t.string "name", null: false
t.string "record_type", null: false
@ -55,6 +55,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_26_202909) do
t.datetime "updated_at", null: false
t.integer "question_kind", default: 0, null: false
t.string "public_asset_path"
t.text "data"
t.index ["section_id"], name: "index_questions_on_section_id"
end

View file

@ -11,7 +11,8 @@
"postcss": "^8.5.3",
"postcss-cli": "^11.0.1",
"postcss-import": "^16.1.0",
"postcss-nesting": "^13.0.1"
"postcss-nesting": "^13.0.1",
"sortablejs": "^1.15.6"
},
"devDependencies": {
"esbuild": "^0.25.3"

View file

@ -6,6 +6,7 @@
#
# id :integer not null, primary key
# answer_kind :integer not null
# data :text
# public_asset_path :string
# question_kind :integer default("simple"), not null
# text :text not null
@ -115,7 +116,13 @@ politics_regenpijpen:
politics_schepenen:
section: politics
text: Verbind de schepenen en hun bevoegdheden.
text: Verplaats de bevoegdheden zodat ze bij de juiste schepen staan.
data: '<%=
JSON.dump({
left: ["Mathias De Clercq", "Hafsa El-Bazioui", "Astrid De Bruycker", "Sofie Bracke", "Evita Willaert", "Joris Vandenbroucke", "Bram Van Braeckevelt", "Burak Nalli", "Filip Watteeuw", "Cristophe Peeters"].shuffle,
right: ["Burgemeester", "Participatie, Buurtwerk, Mondiale Solidariteit, Facilitair Management en Digitalisering", "Sociale Vooruitgang, Gezondheid, Ouderenbeleid en Cultuur", "Economie, Haven, Handel, Toerisme en Openbare netheid", "Onderwijs, Opvoeding, Jeugd, Gezin en Outreachend Werk", "Mobiliteit, Ruimte, Stadsontwikkeling en Plezier", "Natuur en Groen, Werk en Sociale Economie, Gelijke kansen en Sport", "Personeelsbeleid, Burgerzaken en Dienstverlening", "Wonen, Milieu, Klimaat en Energie", "Financiën, Stedenbouw, Erfgoed en Administratieve vereenvoudiging"].shuffle
})
%>'
answer_kind: <%= Question.answer_kinds[:politicians] %>
question_kind: <%= Question.question_kinds[:simple] %>

View file

@ -4,6 +4,7 @@
#
# id :integer not null, primary key
# answer_kind :integer not null
# data :text
# public_asset_path :string
# question_kind :integer default("simple"), not null
# text :text not null

View file

@ -559,6 +559,11 @@ slash@^5.0.0:
resolved "https://registry.yarnpkg.com/slash/-/slash-5.1.0.tgz#be3adddcdf09ac38eebe8dcdc7b1a57a75b095ce"
integrity sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==
sortablejs@^1.15.6:
version "1.15.6"
resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.15.6.tgz#ff93699493f5b8ab8d828f933227b4988df1d393"
integrity sha512-aNfiuwMEpfBM/CN6LY0ibyhxPfPbyFeBTYJKCvzkJ2GkUpazIt3H+QIPAMHwqQ7tMKaHz1Qj+rJJCqljnf4p3A==
source-map-js@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"