- content_for :head do
meta name="turbo-cache-control" content="no-cache"
= turbo_stream_from @respondent_category, "tasks"
.container-fluid
.row
.col-md-3
= render partial: "respondent_categories/sidebar"
.col-md-9.d-flex.flex-column.overflow-hidden style="height: calc(100vh - 100px);"
= render_breadcrumbs([ \
{ name: 'Home', path: projects_path }, \
{ name: @project.title, path: project_path(@project) }, \
{ name: @respondent_category.name, path: [@project, @respondent_category] }, \
{ name: "Queries" } \
])
/ Top Header Area
.d-flex.align-items-center.justify-content-between.mb-4
.fs-30.semi-bold
= @respondent_category.name
.fs-20.medium.text-muted
= t(:all_queries)
/ Vue App Container (Now a flex column itself to manage the table and footer)
div#interviewQuestions.d-flex.flex-column.flex-grow-1.overflow-hidden v-cloak="" data-controller="interview-questions"
/ Scrollable Table Container
.table-container.flex-grow-1.overflow-auto ref="scrollContainer" style="min-height: 0;"
table.table.table-borderless-head.mb-0
/ Sticky Table Header
thead.table-light.fs-12.position-sticky.top-0 style="z-index: 5;"
tr
th Name
th.text-center Sections
th.text-center Order
th
tbody.fs-12
tr v-for="(question, index) in interviewQuestions" :key="question.id" :data-question-id="question.id"
/ Question Name & Edit
td.align-middle style="max-width: 40vw;"
.d-flex.align-items-center v-if="currentEditingQuestion != question.id"
| {{ question.question }}
a href="javascript:;" @click="startQuestionEdit(question.id)"
vue-feather type="edit-2" class="text-primary mt-1 ms-2" size="12"
div v-if="currentEditingQuestion == question.id"
.d-flex.align-items-center
.w-75
input.input-sm.form-control type="text" :id="'question_edit_' + question.id" :value="question.question" @keyup.enter="editQuestion(question.id, index, question.respondent_category_id)" @keyup.esc="stopQuestionEdit()" style="font-size: 0.75rem;"
a.ms-2 href="javascript:;" @click="editQuestion(question.id, index, question.respondent_category_id)"
vue-feather type="check" class="text-primary mt-1" size="18"
a.ms-2 href="javascript:;" @click="stopQuestionEdit()"
vue-feather type="x" class="text-primary mt-1" size="18"
small Press Enter to save
/ Section Assignment
td.align-middle
.d-flex.align-items-center.justify-content-between
div v-if="question.master_section_ids && question.master_section_ids.length > 0"
| {{ displaySectionNames(question.master_section_ids) }}
.text-primary v-if="!question.master_section_ids || question.master_section_ids.length == 0"
| No Sections added
.ms-2
div
button.btn.btn-sm.fs-10 v-bind:class="{'btn-secondary': question.master_section_ids && question.master_section_ids.length > 0, 'btn-primary': !question.master_section_ids || question.master_section_ids.length == 0}" style="font-weight: 600 ; white-space: nowrap; padding: 5px; padding-top: 0px; padding-bottom: 0px;" @click="openAssignSectionModal(question.id)"
| {{ question.master_section_ids && question.master_section_ids.length > 0 ? 'Edit Sections' : 'Add Sections' }}
/ Re-order Question
td.align-middle.text-center
button.btn.btn-sm.bg-secondary.me-1 @click="reorderQuestion(question.id, 'up')"
vue-feather type="arrow-up" class="text-primary mt-1" size="12"
button.btn.btn-sm.bg-secondary @click="reorderQuestion(question.id, 'down')"
vue-feather type="arrow-down" class="text-primary mt-1" size="12"
/ Delete Question
td.align-middle.text-center
a href='javascript:;' @click="deleteQuestion(question.id, question.respondent_category_id)"
vue-feather type="trash-2" class="text-primary mt-1" size="14"
/ STICKY FOOTER ACTION AREA
.mt-auto.bg-white.pt-3.pb-4.border-top style="box-shadow: 0 -10px 15px -10px rgba(0,0,0,0.05);"
.d-flex.align-items-center.justify-content-between.flex-nowrap
/ Left Side: Add Query
.add-item-wrapper.flex-grow-1.me-4
button.btn.btn-outline-primary.d-flex.align-items-center type="button" @click="openAddQuestionModal()"
vue-feather type="plus" class="me-2" size="18"
span Add New Query
/ Right Side: Bulk Actions
.action-buttons-wrapper.d-flex.align-items-center.gap-2.flex-shrink-0
button.btn.btn-outline-primary.btn-sm.d-flex.align-items-center type="button" data-bs-toggle="modal" data-bs-target="#importModal"
i.bi.bi-file-earmark-plus.me-1
span Import Queries CSV
= render "interview_questions/dg_queries_import_button", respondent_category: @respondent_category, queries_generating: @queries_generating
/ Modals & Toasts
div class="modal fade" id="assignSectionDialog"
div class="modal-dialog modal-dialog-centered"
div class="modal-content"
div class="modal-body p-4"
.d-flex.align-items-center.justify-content-between
.fs-24.medium Select Sections
.d-flex.align-items-center
button class="btn btn-light fs-12 px-3 py-2" style="font-weight: 600;" v-on:click="closeAssignSectionModal()" Cancel
button class="btn btn-primary ms-2 fs-12 px-3 py-2" style="font-weight: 600;" v-on:click="assignSection()" Submit
.fs-12.text-primary.mt-1
| {{ isModalOpen ? sectionAssignmentQuestion.question : "Question" }}
.my-3
select class="form-select" id="sectionSelect" multiple="multiple" style="width: 100%"
option v-for="sectionId in sectionOrder" v-bind:value="sectionId"
| {{ masterSections[sectionId].name }}
div class="modal fade" id="addQuestionModal"
div class="modal-dialog modal-dialog-centered"
div class="modal-content"
div class="modal-body p-4"
.d-flex.align-items-center.justify-content-between.mb-3
.fs-24.medium Add New Query
.mb-3
label.form-label.fw-bold.fs-14 Query Text
input.form-control type="text" id="newQuestionInput" v-model="newQuestionText" @keyup.enter="addNewQuestion()" placeholder="Enter your query here..."
.mb-4
label.form-label.fw-bold.fs-14 Assign Sections (Optional)
select class="form-select" id="newQuestionSectionSelect" multiple="multiple" style="width: 100%"
option v-for="sectionId in sectionOrder" v-bind:value="sectionId"
| {{ masterSections[sectionId].name }}
.d-flex.justify-content-end
button.btn.btn-light.px-4.me-2 @click="closeAddQuestionModal()" Cancel
button.btn.btn-primary.px-4 @click="addNewQuestion()" Save
.toast-container.position-fixed.top-0.end-0.p-3
.toast id="questionSuccessToast" role="alert" aria-live="assertive" aria-atomic="true"
.toast-header
i.feather-18.text-success data-feather="check-circle"
strong.ms-2.me-auto Success!
button.btn-close.btn-sm data-bs-dismiss="toast" aria-label="Close"
.toast-body
| {{ successMessage }}
.toast-container.position-fixed.top-0.end-0.p-3
.toast id="questionErrorToast" role="alert" aria-live="assertive" aria-atomic="true"
.toast-header
i.feather-18.text-danger data-feather="x-octagon"
strong.ms-2.me-auto Error!
button.btn-close.btn-sm data-bs-dismiss="toast" aria-label="Close"
.toast-body
| {{ errorMessage }}
.modal.fade#importModal tabindex="-1" aria-labelledby="importModalLabel" aria-hidden="true"
.modal-dialog
.modal-content
= form_with url: import_project_respondent_category_interview_questions_path(@project, @respondent_category), multipart: true, local: true do |f|
.modal-header
h5.modal-title#importModalLabel Import Questions
button.btn-close type="button" data-bs-dismiss="modal" aria-label="Close"
.modal-body
.alert.alert-info.fs-14
| Upload a CSV file. The first column must contain the questions.
.mb-3
= f.label :file, "Select .csv File", class: "form-label fw-bold fs-14"
= f.file_field :file, class: "form-control", accept: ".csv, text/csv", required: true
.modal-footer
button.btn.btn-light type="button" data-bs-dismiss="modal" Cancel
= f.submit "Upload & Import", class: "btn btn-primary"
.modal.fade#importDGModal tabindex="-1" aria-labelledby="importDGModalLabel" aria-hidden="true"
.modal-dialog
.modal-content
= form_with url: import_with_dg_project_respondent_category_interview_questions_path(@project, @respondent_category), multipart: true, local: true do |f|
.modal-header
h5.modal-title#importDGModalLabel Import Discussion Guide (DG)
button.btn-close type="button" data-bs-dismiss="modal" aria-label="Close"
.modal-body
.alert.alert-info.fs-14
| Upload a .docx file containing your Discussion Guide.
.mb-3
= f.label :file, "Select .docx File", class: "form-label fw-bold fs-14"
= f.file_field :file, class: "form-control", accept: ".docx, application/vnd.openxmlformats-officedocument.wordprocessingml.document", required: true
.modal-footer
button.btn.btn-light type="button" data-bs-dismiss="modal" Cancel
= f.submit "Upload & Generate", class: "btn btn-primary", data: { turbo_submits_with: "Uploading..." }