= turbo_stream_from @respondent_category, "tasks"
.container-fluid#transcriptSections v-cloak="" data-controller="sections-list"
.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: "Sections" } \
])
/ 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
| All Sections
/ Sticky List Header
.list-header.d-flex.align-items-center.fs-12.fw-bold.bg-light.p-3.rounded-top.position-sticky.top-0 style="z-index: 10;"
.col-primary.flex-grow-1 Name
.col-actions.text-center.px-3 colspan="2" Actions
/ Main Draggable List Container
.list-container.flex-grow-1.overflow-auto ref="scrollContainer" style="min-height: 0;"
.dragArea.rounded-bottom.border-top-0
draggable v-model="masterSections" :animation="200" @end="onDragEnd" handle=".drag-handle" item-key="id" tag="div" class="draggable-list" :scroll="scrollElement" :scrollSensitivity="100" :scrollSpeed="20" :forceFallback="true" :delay="1" scroll-container=".list-container"
template #item="{ element: section, index }"
.list-row.d-flex.align-items-center.p-3.border-bottom.fs-12 :key="section.id" :data-section-id="section.id"
.col-primary.flex-grow-1
.d-flex.align-items-center
.drag-handle.me-2.cursor-move
vue-feather type="menu" class="text-muted mt-1" size="14"
div v-if="currentEditingSection != section.id"
| {{ section.name }}
div v-if="currentEditingSection == section.id"
.d-flex.align-items-center
.flex-grow-1.me-2
input.form-control type="text" :id="'section_edit_' + section.id" :value="section.name" data-project-id="#{@respondent_category.project_id}" data-respondent-category-id="#{@respondent_category.id}" @keyup.enter="editSection(section.id, index)" @keyup.esc="stopSectionEdit()"
button.btn.btn-clear type="button" @click="stopSectionEdit()"
vue-feather type="x" class="text-primary" size="18"
small Press Enter to save
.col-actions.text-center.px-3
.d-flex.justify-content-center
div v-if="currentEditingSection != section.id"
button.btn.btn-clear.me-2 type="button" @click="startSectionEdit(section.id)"
vue-feather type="edit" class="text-primary" size="14"
div v-if="currentEditingSection == section.id"
.rounded.px-2.py-1.me-2 style="background-color: #F44174;" @click="editSection(section.id, index)"
vue-feather type="check" class="text-white mt-1" size="14"
button.btn.btn-clear type="button" @click="deleteSection(section.id)"
vue-feather type="trash-2" class="text-primary" 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 Section
.add-item-wrapper.flex-grow-1.me-4.position-relative
button.btn.btn-outline-primary.d-flex.align-items-center type="button" v-if="sectionInputEnabled == false" @click="toggleSectionInput()"
vue-feather type="plus" class="me-2" size="18"
span Add New Section
.new-item-input v-if="sectionInputEnabled == true" ref="newSectionInputContainer"
.d-flex.align-items-center
.flex-grow-1.me-2
input.form-control type="text" id="sectionAddInput" data-project-id="#{@respondent_category.project_id}" data-respondent-category-id="#{@respondent_category.id}" @keyup.enter="addNewSection()" @keyup.esc="toggleSectionInput()" placeholder="Enter section name" ref="newSectionInput"
button.btn.btn-primary.d-flex.align-items-center.me-2 type="button" @click="addNewSection()"
vue-feather type="check" class="text-white me-1" size="14"
span Save
button.btn.btn-secondary.d-flex.align-items-center type="button" @click="toggleSectionInput()"
vue-feather type="x" class="me-1" size="14"
span Cancel
/ Absolutely positioned tip to completely prevent layout shift
small.text-muted.position-absolute.start-0.fs-12 style="bottom: -22px;" Tip: Press Enter to save
/ Right Side: Bulk Actions
.action-buttons-wrapper.d-flex.align-items-center.gap-2.flex-shrink-0
= link_to download_sections_project_respondent_category_path(@project, @respondent_category), class: "btn btn-outline-primary btn-sm d-flex align-items-center" do
vue-feather type="download" class="me-2" size="14"
| Download
button.btn.btn-outline-primary.btn-sm.d-flex.align-items-center type="button" data-bs-toggle="modal" data-bs-target="#importModal"
vue-feather type="file-plus" class="me-2" size="14"
span Import CSV
= render "respondent_categories/dg_sections_import_button", respondent_category: @respondent_category, sections_generating: @sections_generating
.toast-container.position-fixed.top-0.end-0.p-3
.toast id="sectionSuccessToast" 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="sectionErrorToast" 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_master_sections_path, multipart: true, local: true do |f|
= f.hidden_field :respondent_category_id, value: @respondent_category.id
= f.hidden_field :project_id, value: @project.id
.modal-header
h5.modal-title#importModalLabel Import Sections
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 section names.
.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_master_sections_path, multipart: true, local: true do |f|
= f.hidden_field :respondent_category_id, value: @respondent_category.id
= f.hidden_field :project_id, value: @project.id
.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..." }