Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions app/controllers/questions_controller.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
class QuestionsController < ApplicationController
before_action :set_survey
before_action :set_roles, only: [:new, :edit]
before_action :set_question, only: [:edit, :update, :destroy]

def new
Expand Down Expand Up @@ -35,21 +36,25 @@ def update

def destroy
@question.destroy
redirect_to survey_path(@survey), notice: 'Question was successfully destroyed.'
redirect_to survey_path(@survey), notice: 'Question was successfully destroyed.', status: 303
end

private

def set_survey
@survey = Survey.find(params[:survey_id])
end


def set_roles
@roles = Role.all
end

def set_question
@question = @survey.questions.find(params[:id])
end

def question_params
params.require(:question).permit(:content, :question_type, :position, :required, options: [])
params.require(:question).permit(:content, :question_type, :position, :required, options: [], role_ids: [])
end

def process_options
Expand Down
50 changes: 50 additions & 0 deletions app/controllers/roles_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
class RolesController < ApplicationController
before_action :set_role, only: [:show, :edit, :update, :destroy]

def index
@roles = Role.all
end

def show
end

def new
@role = Role.new
end

def create
@role = Role.new(role_params)

if @role.save
redirect_to @role, notice: 'Role was successfully created.'
else
render :new, status: :unprocessable_entity
end
end

def edit
end

def update
if @role.update(role_params)
redirect_to @role, notice: 'Role was successfully updated.'
else
render :edit, status: :unprocessable_entity
end
end

def destroy
@role.destroy
redirect_to roles_url, notice: 'Role was successfully destroyed.'
end

private

def set_role
@role = Role.find(params[:id])
end

def role_params
params.require(:role).permit(:name)
end
end
50 changes: 41 additions & 9 deletions app/controllers/surveys_controller.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
class SurveysController < ApplicationController
before_action :set_survey, only: [:show, :edit, :update, :destroy, :take, :submit]
before_action :set_survey, only: [:show, :edit, :update, :destroy, :take, :submit, :analytics]

def index
@surveys = Survey.all
Expand Down Expand Up @@ -39,30 +39,62 @@ def destroy
end

def take
@questions = @survey.questions.order(:position)
if params[:role]
@questions = Role.find(params[:role]).questions.where(survey: @survey).order(:position)
if @questions.empty?
redirect_to take_survey_path(@survey), alert: 'No questions found, please select another role.'
end
else
@roles = Role.all
@questions = []
end
end

def submit
if params[:responses].present?
params[:responses].each do |response_params|
@survey.responses.create(
question_id: response_params[:question_id],
value: response_params[:value],
)
if response_params[:response].present?
if response_params[:response][:responses].present?
response_params[:response][:responses].each do |response|
@survey.responses.create(
question_id: response[:question_id],
value: response[:value],
role_ids: [response_params[:response][:role]]
)
end
end
redirect_to surveys_path, notice: 'Thank you for completing the survey!'
else
redirect_to take_survey_path(@survey), alert: 'Please answer at least one question.'
end
end

def analytics
@analytics = []
analytic_struct = Struct.new(:role, :total_questions, :total_responses)

@question_count = @survey.questions.count
@response_count = @survey.responses.count

Role.all.each do |role|
analytic = analytic_struct.new(
role.name,
role.questions.where(survey: @survey).count,
role.responses.where(survey: @survey).count
)
@analytics << analytic
end
end

private

def set_survey
@survey = Survey.find(params[:id])
end

def survey_params
params.require(:survey).permit(:title, :description)
end

def response_params
params.permit!
end
end
2 changes: 1 addition & 1 deletion app/javascript/components/QuestionList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,4 @@ document.addEventListener('DOMContentLoaded', initializeQuestionList);
// Additionally listen for turbo:load event if using Turbo
document.addEventListener('turbo:load', initializeQuestionList);

export default QuestionList;
export default QuestionList;
117 changes: 117 additions & 0 deletions app/javascript/components/RoleForm.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import React, { useState } from 'react';
import { createRoot } from 'react-dom/client';

const RoleForm = (props) => {
const [name, setName] = useState(props.role?.name || '');
const [errors, setErrors] = useState([]);

const handleSubmit = async (e) => {
e.preventDefault();

const csrfToken = document.querySelector('meta[name="csrf-token"]').content;
const url = props.role?.id ? `/roles/${props.role.id}` : '/roles';
const method = props.role?.id ? 'PATCH' : 'POST';

try {
const response = await fetch(url, {
method,
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({
role: { name }
})
});

if (response.ok) {
window.location.href = props.role?.id ? `/roles/${props.role.id}` : '/roles';
} else {
const data = await response.json();
setErrors(data.errors || ['An error occurred']);
}
} catch (error) {
setErrors(['An error occurred']);
}
};

return (
<div className="space-y-6">
{errors.length > 0 && (
<div className="bg-red-50 p-4 rounded-md">
<div className="flex">
<div className="flex-shrink-0">
<svg className="h-5 w-5 text-red-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
</svg>
</div>
<div className="ml-3">
<h3 className="text-sm font-medium text-red-800">
{errors.length} {errors.length === 1 ? 'error' : 'errors'} prohibited this role from being saved:
</h3>
<div className="mt-2 text-sm text-red-700">
<ul className="list-disc pl-5 space-y-1">
{errors.map((error, index) => (
<li key={index}>{error}</li>
))}
</ul>
</div>
</div>
</div>
</div>
)}

<form onSubmit={handleSubmit}>
<div>
<label htmlFor="name" className="block text-sm font-medium text-gray-700">Name</label>
<div className="mt-1">
<input
type="text"
id="name"
value={name}
onChange={(e) => setName(e.target.value)}
className="shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md"
/>
</div>
</div>

<div className="mt-6 flex justify-end">
<a href="/roles" className="bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Cancel
</a>
<button
type="submit"
className="ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
{props.role?.id ? 'Update Role' : 'Create Role'}
</button>
</div>
</form>
</div>
);
};

// Use a self-executing function to initialize the component
const initializeRoleForm = () => {
const container = document.getElementById('role-form-container');
if (container && !container.hasAttribute('data-react-initialized')) {
const roleData = JSON.parse(container.dataset.role || '{}');

// Mark as initialized to prevent double initialization
container.setAttribute('data-react-initialized', 'true');

const root = createRoot(container);
root.render(<RoleForm role={roleData} />);
}
};

// Try to initialize immediately
initializeRoleForm();

// Also listen for DOMContentLoaded
document.addEventListener('DOMContentLoaded', initializeRoleForm);

// Additionally listen for turbo:load event if using Turbo
document.addEventListener('turbo:load', initializeRoleForm);

export default RoleForm;
2 changes: 1 addition & 1 deletion app/javascript/components/SurveyForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,4 +134,4 @@ document.addEventListener('DOMContentLoaded', initializeSurveyForm);
// Additionally listen for turbo:load event if using Turbo
document.addEventListener('turbo:load', initializeSurveyForm);

export default SurveyForm;
export default SurveyForm;
16 changes: 8 additions & 8 deletions app/javascript/components/TakeSurvey.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ const RatingQuestion = ({ question, onChange }) => {
};

const TakeSurvey = (props) => {
const { survey, questions } = props;
const { survey, role, questions } = props;
const [responses, setResponses] = useState({});
const [submitting, setSubmitting] = useState(false);
const [errors, setErrors] = useState([]);
Expand Down Expand Up @@ -127,13 +127,13 @@ const TakeSurvey = (props) => {
// Format response data
const formattedResponses = Object.keys(responses).map(questionId => ({
question_id: questionId,
content: responses[questionId]
value: responses[questionId]
}));

const csrfToken = document.querySelector('meta[name="csrf-token"]').content;

try {
const response = await fetch(`/surveys/${survey.id}/responses`, {
const response = await fetch(`/surveys/${survey.id}/submit`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Expand All @@ -142,7 +142,8 @@ const TakeSurvey = (props) => {
body: JSON.stringify({
response: {
survey_id: survey.id,
question_responses_attributes: formattedResponses
role: role,
responses: formattedResponses
}
})
});
Expand Down Expand Up @@ -341,9 +342,6 @@ const TakeSurvey = (props) => {

return (
<div>
<h1 className="text-2xl font-bold mb-4">{survey.title}</h1>
<p className="mb-6">{survey.description}</p>

{errors.length > 0 && (
<div className="bg-red-50 border-l-4 border-red-400 p-4 mb-4">
<div className="flex">
Expand Down Expand Up @@ -390,6 +388,7 @@ const initializeTakeSurvey = () => {
if (container && !container.hasAttribute('data-react-initialized')) {
const surveyData = JSON.parse(container.dataset.survey || '{}');
const questionsData = JSON.parse(container.dataset.questions || '[]');
const roleId = new URLSearchParams(document.location.search).get("role");

// Mark as initialized to prevent double initialization
container.setAttribute('data-react-initialized', 'true');
Expand All @@ -398,6 +397,7 @@ const initializeTakeSurvey = () => {
root.render(
<TakeSurvey
survey={surveyData}
role={roleId}
questions={questionsData}
/>
);
Expand All @@ -413,4 +413,4 @@ document.addEventListener('DOMContentLoaded', initializeTakeSurvey);
// Additionally listen for turbo:load event if using Turbo
document.addEventListener('turbo:load', initializeTakeSurvey);

export default TakeSurvey;
export default TakeSurvey;
3 changes: 2 additions & 1 deletion app/javascript/components/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// Import all React components here
import SurveyForm from "./SurveyForm"
import RoleForm from "./RoleForm"
import QuestionList from "./QuestionList"
import TakeSurvey from "./TakeSurvey"

// Export components for use elsewhere if needed
export { SurveyForm, QuestionList, TakeSurvey }
export { SurveyForm, RoleForm, QuestionList, TakeSurvey }
4 changes: 4 additions & 0 deletions app/models/question.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
class Question < ApplicationRecord
belongs_to :survey
has_many :responses, dependent: :destroy
has_many :question_role, dependent: :destroy
has_many :roles, through: :question_role, dependent: :destroy

accepts_nested_attributes_for :roles

validates :content, presence: true
validates :question_type, presence: true
Expand Down
4 changes: 4 additions & 0 deletions app/models/question_role.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
class QuestionRole < ApplicationRecord
belongs_to :question
belongs_to :role
end
Loading