Merge pull request #6637 from lasniscinote/gl_SCI_9671

Implement the 'Add relationship' modal, only front end [SCI-9671]
This commit is contained in:
Martin Artnik 2023-11-28 12:46:11 +01:00 committed by GitHub
commit 3bc28df98c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 250 additions and 35 deletions

View file

@ -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');

View 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');

View file

@ -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);

View file

@ -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>

View file

@ -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;

View file

@ -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');

View file

@ -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);

View file

@ -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

View file

@ -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() {

View file

@ -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>

View file

@ -0,0 +1,5 @@
<div id="repositoryItemRelationshipsModalWrapper">
<repository-item-relationships-modal/>
</div>
<%= javascript_include_tag "vue_components_repository_item_relationships" %>

View file

@ -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"

View file

@ -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',