Refactor checklist [SCI-9959]

This commit is contained in:
Anton 2024-01-22 13:48:37 +01:00
parent cfb64a125c
commit 1fff6da45d
5 changed files with 66 additions and 62 deletions

View file

@ -18,7 +18,7 @@ module StepElements
checklist_item = @checklist.checklist_items.new(checklist_item_params.merge!(created_by: current_user))
new_items = []
ActiveRecord::Base.transaction do
new_items = checklist_item.save_multiline!
new_items = checklist_item.save_multiline!(after_id: params[:after_id])
new_items.each do |item|
log_activity(
"#{@step.protocol.in_module? ? :task : :protocol}_step_checklist_item_added",
@ -102,9 +102,10 @@ module StepElements
end
def reorder
checklist_item = @checklist.checklist_items.find(checklist_item_params[:id])
checklist_item = @checklist.checklist_items.find(params[:id])
ActiveRecord::Base.transaction do
checklist_item.insert_at(checklist_item_params[:position])
insert_at = (@checklist.checklist_items.find_by(id: params[:after_id])&.position || 0)
checklist_item.insert_at(insert_at)
end
render json: params[:checklist_item_positions], status: :ok
rescue ActiveRecord::RecordInvalid

View file

@ -30,7 +30,7 @@
@delete="showDeleteModal"
></MenuDropdown>
</div>
<div v-if="element.attributes.orderable.urls.create_item_url || orderedChecklistItems.length > 0" :class="{ 'pointer-events-none': locked }">
<div v-if="element.attributes.orderable.urls.create_item_url || checklistItems.length > 0" :class="{ 'pointer-events-none': locked }">
<Draggable
v-model="checklistItems"
:ghostClass="'checklist-item-ghost'"
@ -63,8 +63,8 @@
<div v-if="element.attributes.orderable.urls.create_item_url && !addingNewItem"
class="flex items-center gap-1 text-sn-blue cursor-pointer mb-2 mt-1 "
tabindex="0"
@keyup.enter="addItem(orderedChecklistItems.length + 1)"
@click="addItem(orderedChecklistItems.length + 1)">
@keyup.enter="addItem(checklistItems[checklistItems.length - 1]?.id)"
@click="addItem(checklistItems[checklistItems.length - 1]?.id)">
<i class="sn-icon sn-icon-new-task w-6 text-center inline-block"></i>
{{ i18n.t('protocols.steps.insert.checklist_item') }}
</div>
@ -81,6 +81,9 @@
</template>
<script>
/* global HelperModule I18n */
import Draggable from 'vuedraggable';
import DeleteMixin from './mixins/delete.js';
import MoveMixin from './mixins/move.js';
@ -90,6 +93,7 @@ import InlineEdit from '../inline_edit.vue';
import ChecklistItem from './checklistItem.vue';
import moveElementModal from './modal/move.vue';
import MenuDropdown from '../menu_dropdown.vue';
import axios from '../../../packs/custom_axios.js';
export default {
name: 'Checklist',
@ -128,7 +132,7 @@ export default {
},
created() {
if (this.isNew) {
this.addItem(1);
this.addItem();
} else {
this.loadChecklistItems();
}
@ -139,13 +143,6 @@ export default {
}
},
computed: {
orderedChecklistItems() {
return this.checklistItems.sort((a, b) => a.attributes.position - b.attributes.position || b.id - a.id)
.map((item, index) => {
item.attributes.position = index + 1;
return item;
});
},
locked() {
return this.editingName || !this.element.attributes.orderable.urls.update_url;
},
@ -199,20 +196,23 @@ export default {
this.update();
},
postItem(item) {
item.attributes.position = item.attributes.position - 1;
$.post(this.element.attributes.orderable.urls.create_item_url, item).done((result) => {
this.loadChecklistItems(result.data[result.data.length - 1].attributes.position);
}).fail((e) => {
const position = this.checklistItems.findIndex((i) => i.id === item.id);
let afterId = null;
if (position > 0) {
afterId = this.checklistItems[position - 1].id;
}
axios.post(this.element.attributes.orderable.urls.create_item_url, {
attributes: item.attributes,
after_id: afterId
}).then((result) => {
this.loadChecklistItems(result.data.data[result.data.data.length - 1].id);
}).catch(() => {
HelperModule.flashAlertMsg(this.i18n.t('errors.general'), 'danger');
});
// Fake element during loading
item.id = `new${Math.floor(Math.random() * 1000000000)}`;
this.checklistItems.push(item);
},
saveItem(item, key) {
if (item.id > 0) {
const insertAfter = key === 'Enter' ? item.attributes.position : null;
const insertAfter = key === 'Enter' ? item.id : null;
$.ajax({
url: item.attributes.urls.update_url,
type: 'PATCH',
@ -220,7 +220,7 @@ export default {
success: () => {
this.loadChecklistItems(insertAfter);
},
error: (xhr) => setFlashErrors(xhr.responseJSON.errors)
error: (xhr) => this.setFlashErrors(xhr.responseJSON.errors)
});
} else {
this.postItem(item, key);
@ -240,20 +240,23 @@ export default {
});
},
addItem(insertAfter) {
this.checklistItems.push(
const afterIndex = this.checklistItems.findIndex((i) => i.id === insertAfter);
this.checklistItems.splice(
afterIndex + 1,
0,
{
id: `new${Math.floor(Math.random() * 1000000000)}`,
attributes: {
text: '',
checked: false,
position: insertAfter,
isNew: true
isNew: true,
with_paragraphs: false
}
}
);
this.checklistItems = this.orderedChecklistItems;
},
removeItem(position) {
this.checklistItems = this.orderedChecklistItems.filter((item) => item.attributes.position !== position);
this.checklistItems = this.checklistItems.filter((item) => item.attributes.position !== position);
},
startReorder() {
this.reordering = true;
@ -265,21 +268,26 @@ export default {
&& Number.isInteger(event.oldIndex)
&& event.newIndex !== event.oldIndex
) {
const position = this.orderedChecklistItems[event.newIndex]?.attributes.position;
const id = this.checklistItems[event.oldIndex]?.id;
this.checklistItems[event.oldIndex].attributes.position = position + (event.newIndex > event.oldIndex ? 1 : -1);
this.saveItemOrder(id, position);
let afterId = null;
if (event.newIndex > 0) {
if (event.newIndex > event.oldIndex) {
afterId = this.checklistItems[event.newIndex - 1].id;
} else {
afterId = this.checklistItems[event.newIndex + 1].id;
}
}
const id = this.checklistItems[event.newIndex]?.id;
this.saveItemOrder(id, afterId);
}
},
saveItemOrder(id, position) {
$.ajax({
type: 'POST',
url: this.element.attributes.orderable.urls.reorder_url,
data: JSON.stringify({ attributes: { id, position } }),
contentType: 'application/json',
dataType: 'json',
error: (xhr) => this.setFlashErrors(xhr.responseJSON.errors),
success: () => this.loadChecklistItems()
saveItemOrder(id, afterId) {
axios.post(this.element.attributes.orderable.urls.reorder_url, {
id,
after_id: afterId
}).then(() => {
this.loadChecklistItems();
}).catch((e) => {
this.setFlashErrors(e.response.errors);
});
},
setFlashErrors(errors) {

View file

@ -1,6 +1,6 @@
<template>
<div class="content__checklist-item pl-10 ml-[-2.325rem]">
<div class="checklist-item-header flex rounded items-center relative w-full group/checklist-item-header" :class="{ 'locked': locked || editingText, 'editing-name': editingText }">
<div class="content__checklist-item pl-10 ml-[-2.325rem] group/checklist-item-header">
<div class="checklist-item-header flex rounded items-center relative w-full" :class="{ 'locked': locked || editingText, 'editing-name': editingText }">
<div v-if="reorderChecklistItemUrl"
class="absolute h-6 cursor-grab justify-center left-[-2.325rem] top-0.5 px-2 tw-hidden text-sn-grey element-grip step-element-grip--draggable"
:class="{ 'group-hover/checklist-item-header:flex': (!locked && !editingText && draggable) }"
@ -8,7 +8,8 @@
<i class="sn-icon sn-icon-drag"></i>
</div>
<div class="flex items-start gap-2 grow" :class="{ 'done': checklistItem.attributes.checked }">
<div v-if="!inRepository" class="sci-checkbox-container my-1.5 border-0 border-y border-transparent border-solid" :class="{ 'disabled': !toggleUrl }" :style="toggleUrl && 'pointer-events: initial'">
<div v-if="!inRepository" class="sci-checkbox-container my-1.5 border-0 border-y border-transparent border-solid"
:class="{ 'disabled': !toggleUrl }" :style="toggleUrl && 'pointer-events: initial'">
<input ref="checkbox"
type="checkbox"
class="sci-checkbox"
@ -42,9 +43,10 @@
@delete="removeItem()"
@keypress="keyPressHandler"
@blur="onBlurHandler"
@paste="pasteHandler"
/>
<span v-if="!editingText && (!checklistItem.attributes.urls || deleteUrl)" class="absolute right-0 top-0.5 leading-6 tw-hidden group-hover/checklist-item-header:inline-block !text-sn-blue cursor-pointer" @click="showDeleteModal" tabindex="0">
<span v-if="!editingText && (!checklistItem.attributes.urls || deleteUrl)"
class="absolute right-0 top-0.5 leading-6 tw-hidden group-hover/checklist-item-header:inline-block !text-sn-blue cursor-pointer"
@click="showDeleteModal" tabindex="0">
<i class="sn-icon sn-icon-delete"></i>
</span>
</div>
@ -127,8 +129,7 @@ export default {
disableTextEdit() {
if (this.checklistItem.attributes.isNew) {
if (this.deleting) return;
this.removeItem();
if (this.checklistItem.attributes.text.length === 0) this.removeItem();
this.$emit('editEnd');
}
},
@ -162,15 +163,9 @@ export default {
this.$emit('update', this.checklistItem, withKey);
},
keyPressHandler(e) {
if (
((e.shiftKey || e.metaKey) && e.key === 'Enter')
|| ((e.ctrlKey || e.metaKey) && e.key === 'v')
) {
if ((e.shiftKey || e.metaKey) && e.key === 'Enter') {
this.checklistItem.attributes.with_paragraphs = true;
}
},
pasteHandler() {
this.checklistItem.attributes.with_paragraphs = true;
}
}
};

View file

@ -270,8 +270,8 @@ export default {
this.newValue = this.$refs.input.value.trim(); // Fix for smart annotation
this.editing = false;
this.$emit('editingDisabled');
this.$emit('update', this.newValue, withKey);
this.$emit('editingDisabled');
},
refreshTexareaHeight() {
if (this.editing && !this.singleLine) {

View file

@ -27,13 +27,13 @@ class ChecklistItem < ApplicationRecord
after_save :touch_checklist
after_touch :touch_checklist
def save_multiline!
def save_multiline!(after_id: nil)
at_position = checklist.checklist_items.find_by(id: after_id).position if after_id
if with_paragraphs
if new_record?
original_position = position
self.position = nil
save!
insert_at(original_position + 1)
insert_at(at_position + 1) || 0
else
save!
end
@ -42,7 +42,7 @@ class ChecklistItem < ApplicationRecord
items = []
if new_record?
start_position = position
start_position = at_position || 0
text.split("\n").compact.each do |line|
new_item = checklist.checklist_items.create!(text: line)
new_item.insert_at(start_position + 1)