mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2024-09-20 14:45:56 +08:00
Merge pull request #6637 from lasniscinote/gl_SCI_9671
Implement the 'Add relationship' modal, only front end [SCI-9671]
This commit is contained in:
commit
3bc28df98c
|
@ -1,19 +1,15 @@
|
|||
/* global notTurbolinksPreview */
|
||||
|
||||
import TurbolinksAdapter from 'vue-turbolinks';
|
||||
import Vue from 'vue/dist/vue.esm';
|
||||
import { createApp } from 'vue/dist/vue.esm-bundler.js';
|
||||
import PerfectScrollbar from 'vue3-perfect-scrollbar';
|
||||
import { vOnClickOutside } from '@vueuse/components';
|
||||
import { mountWithTurbolinks } from './helpers/turbolinks.js';
|
||||
import ItemRelationshipsModal from '../../vue/item_relationships/ItemRelationshipsModal.vue';
|
||||
|
||||
Vue.use(TurbolinksAdapter);
|
||||
Vue.prototype.i18n = window.I18n;
|
||||
|
||||
function initItemRelationshipsModal() {
|
||||
new Vue({
|
||||
el: '#itemRelationshipsModalWrapper',
|
||||
components: {
|
||||
ItemRelationshipsModal
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
initItemRelationshipsModal();
|
||||
const app = createApp({});
|
||||
app.component('ItemRelationshipsModal', ItemRelationshipsModal);
|
||||
app.use(ItemRelationshipsModal);
|
||||
app.use(PerfectScrollbar);
|
||||
app.directive('click-outside', vOnClickOutside);
|
||||
app.config.globalProperties.i18n = window.I18n;
|
||||
mountWithTurbolinks(app, '#itemRelationshipsModalWrapper');
|
||||
|
|
15
app/javascript/packs/vue/repository_item_relationships.js
Normal file
15
app/javascript/packs/vue/repository_item_relationships.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
/* global notTurbolinksPreview */
|
||||
|
||||
import { createApp } from 'vue/dist/vue.esm-bundler.js';
|
||||
import PerfectScrollbar from 'vue3-perfect-scrollbar';
|
||||
import { vOnClickOutside } from '@vueuse/components';
|
||||
import { mountWithTurbolinks } from './helpers/turbolinks.js';
|
||||
import RepositoryItemRelationshipsModal from '../../vue/item_relationships/RepositoryItemRelationshipsModal.vue';
|
||||
|
||||
const app = createApp({});
|
||||
app.component('RepositoryItemRelationshipsModal', RepositoryItemRelationshipsModal);
|
||||
app.use(RepositoryItemRelationshipsModal);
|
||||
app.use(PerfectScrollbar);
|
||||
app.directive('click-outside', vOnClickOutside);
|
||||
app.config.globalProperties.i18n = window.I18n;
|
||||
mountWithTurbolinks(app, '#repositoryItemRelationshipsModalWrapper');
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
import PerfectScrollbar from 'vue3-perfect-scrollbar';
|
||||
import { createApp } from 'vue/dist/vue.esm-bundler.js';
|
||||
import RepositoryItemSidebar from '../../vue/repository_item_sidebar/RepositoryItemSidebar.vue';
|
||||
import { vOnClickOutside } from '@vueuse/components';
|
||||
import { mountWithTurbolinks } from './helpers/turbolinks.js';
|
||||
import { vOnClickOutside } from '@vueuse/components'
|
||||
import RepositoryItemSidebar from '../../vue/repository_item_sidebar/RepositoryItemSidebar.vue';
|
||||
|
||||
const app = createApp({});
|
||||
app.component('RepositoryItemSidebar', RepositoryItemSidebar);
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
<template>
|
||||
<div ref="repositoryItemRelationshipsModal" @keydown.esc="cancel" id="repositoryItemRelationshipsModal" tabindex="-1" role="dialog"
|
||||
class="modal ">
|
||||
<div class="modal-dialog modal-sm" role="document">
|
||||
<div class="modal-content w-[400px] h-[498px] m-auto">
|
||||
|
||||
<!-- header -->
|
||||
<div class="modal-header h-[76px] flex !flex-col gap-[6px]">
|
||||
|
||||
<!-- header title with close icon -->
|
||||
<div class="h-[30px] w-full flex flex-row-reverse">
|
||||
<i id="close-icon" class="sn-icon sn-icon-close ml-auto cursor-pointer my-auto mx-0" data-dismiss="modal"
|
||||
aria-label="Close"></i>
|
||||
<h4 class="modal-title" id="modal-destroy-team-label">
|
||||
{{ i18n.t('repositories.item_card.repository_item_relationships_modal.header_title') }}
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
<!-- header subtitle -->
|
||||
<div class="h-10 overflow-hidden break-words text-sn-dark-grey text-sm font-normal leading-5"
|
||||
style="-webkit-box-orient: vertical; -webkit-line-clamp: 2; display: -webkit-box;">
|
||||
{{ i18n.t('repositories.item_card.repository_item_relationships_modal.header_subtitle') }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div v-if="isLoading" class="flex justify-center h-40 w-auto">
|
||||
<div class="m-auto h-fit w-fit">{{ i18n.t('general.loading') }}</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="modal-body flex flex-col gap-6">
|
||||
<!-- inventory -->
|
||||
<div class="flex flex-col gap-[7px]">
|
||||
<div class="h-5 whitespace-nowrap overflow-auto">
|
||||
{{ i18n.t('repositories.item_card.repository_item_relationships_modal.inventory_section_title') }}</div>
|
||||
<div class="h-11">
|
||||
<select-search ref="ChangeSelectedInventoryDropdownSelector" @change="changeSelectedInventory"
|
||||
:value="selectedInventoryValue" :withClearButton="false" :withEditCursor="false"
|
||||
:options="inventoryOptions" :isLoading="isLoading"
|
||||
:placeholder="i18n.t('repositories.item_card.repository_item_relationships_modal.select_inventory_placeholder')"
|
||||
:noOptionsPlaceholder="i18n.t('repositories.item_card.repository_item_relationships_modal.select_inventory_no_options_placeholder')"
|
||||
:searchPlaceholder="i18n.t('repositories.item_card.repository_item_relationships_modal.select_inventory_placeholder')"
|
||||
</select-search>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- item -->
|
||||
<div class="flex flex-col gap-[7px]">
|
||||
<div class="h-5 whitespace-nowrap overflow-auto">
|
||||
{{ i18n.t('repositories.item_card.repository_item_relationships_modal.item_section_title') }}</div>
|
||||
<div class="h-11">
|
||||
<checklist-select :shouldUpdateWithoutValues="true" :withButtons="false" :withEditCursor="false"
|
||||
ref="ChangeSelectedItemChecklistSelector" :options="itemOptions"
|
||||
:placeholder="i18n.t('repositories.item_card.repository_item_relationships_modal.select_item_placeholder')"
|
||||
:noOptionsPlaceholder="i18n.t('repositories.item_card.repository_item_relationships_modal.select_item_no_options_placeholder')"
|
||||
:initialSelectedValues="this.selectedItemValues.map((val) => val.id)" @update="handleUpdate"
|
||||
:shouldUpdateOnToggleClose="true"
|
||||
>
|
||||
</checklist-select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- relationship -->
|
||||
<div class="flex flex-col gap-[7px]">
|
||||
<div class="h-5 whitespace-nowrap overflow-auto">
|
||||
{{ i18n.t('repositories.item_card.repository_item_relationships_modal.relationship_section_title') }}
|
||||
</div>
|
||||
<div class="h-11">
|
||||
<select-search ref="ChangeSelectedRelationshipDropdownSelector" @change="changeSelectedRelationship"
|
||||
:value="selectedRelationshipValue" :withClearButton="false" :withEditCursor="false"
|
||||
:options="relationshipOptions" :isLoading="isLoading"
|
||||
:placeholder="i18n.t('repositories.item_card.repository_item_relationships_modal.select_relationship_placeholder')"
|
||||
:noOptionsPlaceholder="i18n.t('repositories.item_card.repository_item_relationships_modal.select_relationship_no_options_placeholder')"
|
||||
:searchPlaceholder="i18n.t('repositories.item_card.repository_item_relationships_modal.select_relationship_placeholder')"
|
||||
</select-search>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- footer -->
|
||||
<div class="modal-footer">
|
||||
<div class="flex justify-end gap-4">
|
||||
<button class="btn btn-secondary w-[78px] h-10 whitespace-nowrap overflow-auto" @click="cancel">
|
||||
{{ i18n.t('repositories.item_card.repository_item_relationships_modal.cancel_button') }}
|
||||
</button>
|
||||
<button class="btn btn-primary w-[59px] h-10 whitespace-nowrap overflow-auto"
|
||||
:class="{ 'disabled': !shouldEnableAddButton }" @click="add">
|
||||
{{ i18n.t('repositories.item_card.repository_item_relationships_modal.add_button') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SelectSearch from '../shared/select_search.vue';
|
||||
import ChecklistSelect from '../shared/checklist_select.vue';
|
||||
|
||||
export default {
|
||||
name: 'RepositoryItemRelationshipsModal',
|
||||
components: {
|
||||
'select-search': SelectSearch,
|
||||
'checklist-select': ChecklistSelect,
|
||||
},
|
||||
created() {
|
||||
window.repositoryItemRelationshipsModal = this;
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isLoading: false,
|
||||
selectedInventoryValue: null,
|
||||
selectedItemValues: [],
|
||||
selectedRelationshipValue: null,
|
||||
inventoryOptions: [{ 0: 1, 1: 'Participants database' }, { 0: 2, 1: 'Inventory Option 2' }, { 0: 3, 1: 'Inventory Option 3' }, { 0: 4, 1: 'Inventory Option 4' }],
|
||||
itemOptions: [{ id: 1, label: 'SEPA-STASE' }, { id: 2, label: 'GTC' }, { id: 3, label: 'DESTIL-MACRO' }, { id: 4, label: 'ARNESSLIM-L' }],
|
||||
relationshipOptions: [{ 0: 1, 1: 'Parent' }, { 0: 2, 1: 'Child' }],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
shouldEnableAddButton() {
|
||||
return ((this.selectedInventoryValue !== null) && (this.selectedRelationshipValue !== null) && (this.selectedItemValues.length > 0))
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
setParentOrChild(parentOrChild) {
|
||||
const lowerCaseParentOrChild = parentOrChild.toLowerCase();
|
||||
const foundRelationshipOption = this.relationshipOptions.find((option) => option[1].toLowerCase() === lowerCaseParentOrChild);
|
||||
this.selectedRelationshipValue = foundRelationshipOption[0];
|
||||
},
|
||||
handleUpdate(selectedValues) {
|
||||
const updatedItemOptions = this.itemOptions.filter((itemOption) => selectedValues.includes(itemOption.id));
|
||||
this.selectedItemValues = updatedItemOptions;
|
||||
},
|
||||
show(parentOrChild) {
|
||||
$(this.$refs.repositoryItemRelationshipsModal).modal('show');
|
||||
if (parentOrChild) {
|
||||
this.setParentOrChild(parentOrChild);
|
||||
}
|
||||
},
|
||||
changeSelectedInventory(value) {
|
||||
this.selectedInventoryValue = value;
|
||||
},
|
||||
changeSelectedRelationship(value) {
|
||||
this.selectedRelationshipValue = value;
|
||||
},
|
||||
cancel() {
|
||||
this.selectedInventoryValue = null;
|
||||
this.selectedItemValues = [];
|
||||
this.selectedRelationshipValue = null;
|
||||
$(this.$refs.repositoryItemRelationshipsModal).modal('hide');
|
||||
},
|
||||
add() {
|
||||
$(this.$refs.repositoryItemRelationshipsModal).modal('hide');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
|
@ -138,7 +138,12 @@
|
|||
</template>
|
||||
<template v-else>
|
||||
<div class="font-inter text-sm leading-5 w-full">
|
||||
<div class="mb-3 font-semibold">{{ i18n.t('repositories.item_card.relationships.parents.count', { count: parentsCount || 0 }) }}</div>
|
||||
<div class="flex flex-row justify-between">
|
||||
<div class="mb-3 font-semibold">{{ i18n.t('repositories.item_card.relationships.parents.count', { count: parentsCount || 0 }) }}</div>
|
||||
<a class="btn-text-link font-normal" @click="handleOpenAddRelationshipsModal($event, 'parent')">
|
||||
{{ i18n.t('repositories.item_card.add_relationship_button_text') }}
|
||||
</a>
|
||||
</div>
|
||||
<div v-if="parentsCount">
|
||||
<details v-for="(parent) in parents" @toggle="updateOpenState(parent.code, $event.target.open)" :key="parent.code" class="flex flex-col font-normal gap-5 group cursor-default">
|
||||
<summary class="flex flex-row gap-3 mb-3 items-center">
|
||||
|
@ -170,7 +175,12 @@
|
|||
<div class="sci-divider pb-4"></div>
|
||||
|
||||
<div class="font-inter text-sm leading-5 w-full">
|
||||
<div class="mb-3 font-semibold">{{ i18n.t('repositories.item_card.relationships.children.count', { count: childrenCount || 0 }) }}</div>
|
||||
<div class="flex flex-row justify-between">
|
||||
<div class="mb-3 font-semibold">{{ i18n.t('repositories.item_card.relationships.children.count', { count: childrenCount || 0 }) }}</div>
|
||||
<a class="btn-text-link font-normal" @click="handleOpenAddRelationshipsModal($event, 'child')">
|
||||
{{ i18n.t('repositories.item_card.add_relationship_button_text') }}
|
||||
</a>
|
||||
</div>
|
||||
<div v-if="childrenCount">
|
||||
<details v-for="(child) in children" :key="child.code" @toggle="updateOpenState(child.code, $event.target.open)" class="flex flex-col font-normal gap-5 group">
|
||||
<summary class="flex flex-row gap-3 mb-3 items-center">
|
||||
|
@ -347,12 +357,12 @@ export default {
|
|||
watch: {
|
||||
parents() {
|
||||
this.parents.forEach((parent) => {
|
||||
this.$set(this.relationshipDetailsState, parent.code, false);
|
||||
this.relationshipDetailsState[parent.code] = false;
|
||||
});
|
||||
},
|
||||
children() {
|
||||
this.children.forEach((child) => {
|
||||
this.$set(this.relationshipDetailsState, child.code, false);
|
||||
this.relationshipDetailsState[child.code] = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
|
@ -366,6 +376,11 @@ export default {
|
|||
document.removeEventListener('mousedown', this.handleOutsideClick);
|
||||
},
|
||||
methods: {
|
||||
handleOpenAddRelationshipsModal(event, parentOrChild) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
window.repositoryItemRelationshipsModal.show(parentOrChild);
|
||||
},
|
||||
handleOutsideClick(event) {
|
||||
if (!this.isShowing) return;
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div
|
||||
ref="modal"
|
||||
class="modal fade"
|
||||
class="modal fade"
|
||||
tabindex="-1"
|
||||
role="dialog"
|
||||
aria-labelledby="manage-stock-value"
|
||||
|
@ -246,7 +246,7 @@
|
|||
validateAndsaveStockValue() {
|
||||
let newErrors = {};
|
||||
this.errors = newErrors;
|
||||
if (!this.unit)
|
||||
if (!this.unit)
|
||||
newErrors['unit'] = I18n.t('repository_stock_values.manage_modal.unit_error');
|
||||
if (!this.amount)
|
||||
newErrors['amount'] = I18n.t('repository_stock_values.manage_modal.amount_error');
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<div class="w-full relative" ref="container" v-click-outside="closeDropdown">
|
||||
<button ref="focusElement"
|
||||
class="btn flex justify-between items-center w-full outline-none border-[1px] bg-white rounded p-2
|
||||
font-inter text-sm font-normal leading-5"
|
||||
font-inter text-sm font-normal leading-5"
|
||||
:class="{
|
||||
'sci-cursor-edit': !isOpen && withEditCursor,
|
||||
'border-sn-light-grey hover:border-sn-sleepy-grey': !isOpen,
|
||||
|
@ -64,12 +64,10 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import PerfectScrollbar from 'vue3-perfect-scrollbar';
|
||||
import { vOnClickOutside } from '@vueuse/components'
|
||||
import { vOnClickOutside } from '@vueuse/components';
|
||||
|
||||
export default {
|
||||
name: 'ChecklistSelect',
|
||||
comments: { PerfectScrollbar },
|
||||
props: {
|
||||
withButtons: { type: Boolean, default: false },
|
||||
withEditCursor: { type: Boolean, default: false },
|
||||
|
@ -79,7 +77,9 @@
|
|||
noOptionsPlaceholder: { type: String },
|
||||
disabled: { type: Boolean, default: false },
|
||||
className: { type: String, default: '' },
|
||||
optionsClassName: { type: String, default: '' }
|
||||
optionsClassName: { type: String, default: '' },
|
||||
shouldUpdateWithoutValues: { type: Boolean, default: false },
|
||||
shouldUpdateOnToggleClose: { type: Boolean, default: false }
|
||||
},
|
||||
directives: {
|
||||
'click-outside': vOnClickOutside
|
||||
|
@ -103,11 +103,11 @@
|
|||
},
|
||||
watch: {
|
||||
initialSelectedValues: {
|
||||
handler: function (newVal) {
|
||||
handler: function (newVal, oldVal) {
|
||||
this.selectedValues = newVal;
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggle() {
|
||||
|
@ -115,7 +115,10 @@
|
|||
if (this.isOpen) {
|
||||
this.updateOptionPosition();
|
||||
} else {
|
||||
this.closeDropdown();
|
||||
if (this.shouldUpdateOnToggleClose && this.shouldUpdateWithoutValues) {
|
||||
this.$emit('update', this.selectedValues);
|
||||
}
|
||||
this.close();
|
||||
}
|
||||
},
|
||||
updateOptionPosition() {
|
||||
|
@ -136,7 +139,13 @@
|
|||
if (!this.isOpen) return;
|
||||
|
||||
this.isOpen = false;
|
||||
this.$emit('update', this.selectedValues.length ? this.selectedValues : null);
|
||||
if (this.shouldUpdateWithoutValues) {
|
||||
this.$emit('update', this.selectedValues)
|
||||
|
||||
}
|
||||
if (this.selectedValues.length && !this.shouldUpdateWithoutValues) {
|
||||
this.$emit('update', this.selectedValues);
|
||||
}
|
||||
},
|
||||
isSelected(id) {
|
||||
return this.selectedValues.includes(id);
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
class="sn-select__options !relative !top-0 !left-[-1px] !shadow-none scroll-container px-2.5 pt-0 block"
|
||||
:class="{ [optionsClassName]: true }"
|
||||
>
|
||||
|
||||
<div v-if="options.length" class="flex flex-col gap-[1px]">
|
||||
<div
|
||||
v-for="option in options"
|
||||
|
@ -63,7 +62,7 @@
|
|||
noOptionsPlaceholder: { type: String },
|
||||
className: { type: String, default: '' },
|
||||
optionsClassName: { type: String, default: '' },
|
||||
disabled: { type: Boolean, default: false }
|
||||
disabled: { type: Boolean, default: false },
|
||||
},
|
||||
directives: {
|
||||
'click-outside': vOnClickOutside
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
disabled: { type: Boolean },
|
||||
isLoading: { type: Boolean, default: false },
|
||||
className: { type: String, default: '' },
|
||||
optionsClassName: { type: String, default: '' }
|
||||
optionsClassName: { type: String, default: '' },
|
||||
},
|
||||
components: { Select },
|
||||
data() {
|
||||
|
|
|
@ -114,6 +114,7 @@
|
|||
|
||||
<%= render "shared/comments/comments_sidebar" %>
|
||||
<%= render "shared/item_relationships_modal" %>
|
||||
<%= render "shared/repository_item_relationships_modal" %>
|
||||
<%= render "shared/repository_row_sidebar" %>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
|
||||
<div id="repositoryItemRelationshipsModalWrapper">
|
||||
<repository-item-relationships-modal/>
|
||||
</div>
|
||||
<%= javascript_include_tag "vue_components_repository_item_relationships" %>
|
|
@ -2224,6 +2224,21 @@ en:
|
|||
my_module_assigned_snapshot_service:
|
||||
invalid_arguments: "Can't find %{key}"
|
||||
item_card:
|
||||
add_relationship_button_text: "Add"
|
||||
repository_item_relationships_modal:
|
||||
header_title: "Add relationship"
|
||||
header_subtitle: "Select the fields below to create the desired relationship"
|
||||
add_button: "Add"
|
||||
cancel_button: "Cancel"
|
||||
select_inventory_placeholder: "Select inventory name"
|
||||
select_inventory_no_options_placeholder: "No inventory options"
|
||||
select_item_placeholder: "Select item name or ID"
|
||||
select_item_no_options_placeholder: "No items options"
|
||||
select_relationship_placeholder: "Select relationship"
|
||||
select_relationship_no_options_placeholder: "No relationship options"
|
||||
inventory_section_title: "Inventory"
|
||||
item_section_title: "Item"
|
||||
relationship_section_title: "Relationship"
|
||||
header_title: "Inventory item"
|
||||
section:
|
||||
information: "Information"
|
||||
|
|
|
@ -44,6 +44,7 @@ const entryList = {
|
|||
vue_navigation_breadcrumbs: './app/javascript/packs/vue/navigation/breadcrumbs.js',
|
||||
vue_protocol_file_import_modal: './app/javascript/packs/vue/protocol_file_import_modal.js',
|
||||
vue_components_item_relationships: './app/javascript/packs/vue/item_relationships.js',
|
||||
vue_components_repository_item_relationships: './app/javascript/packs/vue/repository_item_relationships.js',
|
||||
vue_components_export_stock_consumption_modal: './app/javascript/packs/vue/export_stock_consumption_modal.js',
|
||||
vue_components_manage_stock_value_modal: './app/javascript/packs/vue/manage_stock_value_modal.js',
|
||||
vue_legacy_datetime_picker: './app/javascript/packs/vue/legacy/datetime_picker.js',
|
||||
|
|
Loading…
Reference in a new issue