chore: Remove vue-multiselect and migrate to next components (#13506)
# Pull Request Template ## Description This PR includes: 1. Removes multiselect usage from the Merge Contact modal (Conversation sidebar) and replaces it with the existing component used on the Contact Details page. 2. Replaces legacy form and multiselect elements in Add and Edit automations flows with next components.**(Also check Macros)** 3. Replace multiselect with ComboBox in contact form country field. 4. Replace multiselect with TagInput in create/edit attribute form. 5. Replace multiselect with TagInput for agent selection in inbox creation. 6. Replace multiselect with ComboBox in Facebook channel page selection ## Type of change - [x] New feature (non-breaking change which adds functionality) ## How Has This Been Tested? **Screenshots** 1. **Merge modal** <img width="741" height="449" alt="image" src="https://github.com/user-attachments/assets/a05a96ec-0692-4d94-9e27-d3e85fd143e4" /> <img width="741" height="449" alt="image" src="https://github.com/user-attachments/assets/fc1dc977-689d-4440-869d-2124e4ca9083" /> 2. **Automations** <img width="849" height="1089" alt="image" src="https://github.com/user-attachments/assets/b0155f06-ab21-4f90-a2c8-5bfbd97b08f7" /> <img width="813" height="879" alt="image" src="https://github.com/user-attachments/assets/0921ac4a-88f5-49ac-a776-cc02941b479c" /> <img width="849" height="826" alt="image" src="https://github.com/user-attachments/assets/44358dae-a076-4e10-b7ba-a4e40ccd817f" /> 3. **Country field** <img width="462" height="483" alt="image" src="https://github.com/user-attachments/assets/d5db9aa1-b859-4327-9960-957d7091678f" /> 4. **Add/Edit attribute form** <img width="619" height="646" alt="image" src="https://github.com/user-attachments/assets/6ab2ea94-73e5-40b8-ac29-399c0543fa7b" /> <img width="619" height="646" alt="image" src="https://github.com/user-attachments/assets/b4c5bb0e-baa0-4ef7-a6a2-adb0f0203243" /> <img width="635" height="731" alt="image" src="https://github.com/user-attachments/assets/74890c80-b213-4567-bf5f-4789dda39d2d" /> 5. **Agent selection in inbox creation** <img width="635" height="534" alt="image" src="https://github.com/user-attachments/assets/0003bad1-1a75-4f20-b014-587e1c19a620" /> <img width="809" height="602" alt="image" src="https://github.com/user-attachments/assets/5e7ab635-7340-420a-a191-e6cd49c02704" /> 7. **Facebook channel page selection** <img width="597" height="444" alt="image" src="https://github.com/user-attachments/assets/f7ec8d84-0a7d-4bc6-92a1-a1365178e319" /> <img width="597" height="444" alt="image" src="https://github.com/user-attachments/assets/d0596c4d-94c1-4544-8b50-e7103ff207a6" /> <img width="597" height="444" alt="image" src="https://github.com/user-attachments/assets/be097921-011b-4dbe-b5f1-5d1306e25349" /> ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --------- Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
This commit is contained in:
@@ -3,6 +3,9 @@ import AutomationActionTeamMessageInput from './AutomationActionTeamMessageInput
|
||||
import AutomationActionFileInput from './AutomationFileInput.vue';
|
||||
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor.vue';
|
||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
||||
import SingleSelect from 'dashboard/components-next/filter/inputs/SingleSelect.vue';
|
||||
import MultiSelect from 'dashboard/components-next/filter/inputs/MultiSelect.vue';
|
||||
import NextInput from 'dashboard/components-next/input/Input.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -10,6 +13,9 @@ export default {
|
||||
AutomationActionFileInput,
|
||||
WootMessageEditor,
|
||||
NextButton,
|
||||
SingleSelect,
|
||||
MultiSelect,
|
||||
NextInput,
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
@@ -40,6 +46,10 @@ export default {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
dropdownMaxHeight: {
|
||||
type: String,
|
||||
default: 'max-h-80',
|
||||
},
|
||||
},
|
||||
emits: ['update:modelValue', 'input', 'removeAction', 'resetAction'],
|
||||
computed: {
|
||||
@@ -69,11 +79,21 @@ export default {
|
||||
return this.actionTypes.find(action => action.key === this.action_name)
|
||||
.inputType;
|
||||
},
|
||||
actionInputStyles() {
|
||||
return {
|
||||
'has-error': this.errorMessage,
|
||||
'is-a-macro': this.isMacro,
|
||||
};
|
||||
actionNameAsSelectModel: {
|
||||
get() {
|
||||
if (!this.action_name) return null;
|
||||
const found = this.actionTypes.find(a => a.key === this.action_name);
|
||||
return found ? { id: found.key, name: found.label } : null;
|
||||
},
|
||||
set(value) {
|
||||
this.action_name = value?.id || value;
|
||||
},
|
||||
},
|
||||
actionTypesAsOptions() {
|
||||
return this.actionTypes.map(a => ({ id: a.key, name: a.label }));
|
||||
},
|
||||
isVerticalLayout() {
|
||||
return ['team_message', 'textarea'].includes(this.inputType);
|
||||
},
|
||||
castMessageVmodel: {
|
||||
get() {
|
||||
@@ -94,203 +114,89 @@ export default {
|
||||
resetAction() {
|
||||
this.$emit('resetAction');
|
||||
},
|
||||
onActionNameChange(value) {
|
||||
this.actionNameAsSelectModel = value;
|
||||
this.resetAction();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="filter" :class="actionInputStyles">
|
||||
<div class="filter-inputs">
|
||||
<select
|
||||
v-model="action_name"
|
||||
class="action__question"
|
||||
:class="{ 'full-width': !showActionInput }"
|
||||
@change="resetAction()"
|
||||
>
|
||||
<option
|
||||
v-for="attribute in actionTypes"
|
||||
:key="attribute.key"
|
||||
:value="attribute.key"
|
||||
>
|
||||
{{ attribute.label }}
|
||||
</option>
|
||||
</select>
|
||||
<div v-if="showActionInput" class="filter__answer--wrap">
|
||||
<div v-if="inputType" class="w-full">
|
||||
<div
|
||||
<li class="list-none py-2 first:pt-0 last:pb-0">
|
||||
<div
|
||||
class="flex flex-col gap-2"
|
||||
:class="{ 'animate-wiggle': errorMessage }"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<SingleSelect
|
||||
:model-value="actionNameAsSelectModel"
|
||||
:options="actionTypesAsOptions"
|
||||
:dropdown-max-height="dropdownMaxHeight"
|
||||
disable-deselect
|
||||
class="flex-shrink-0"
|
||||
@update:model-value="onActionNameChange"
|
||||
/>
|
||||
<template v-if="showActionInput && !isVerticalLayout">
|
||||
<SingleSelect
|
||||
v-if="inputType === 'search_select'"
|
||||
class="multiselect-wrap--small"
|
||||
>
|
||||
<multiselect
|
||||
v-model="action_params"
|
||||
track-by="id"
|
||||
label="name"
|
||||
:placeholder="$t('FORMS.MULTISELECT.SELECT')"
|
||||
selected-label
|
||||
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
|
||||
deselect-label=""
|
||||
:max-height="160"
|
||||
:options="dropdownValues"
|
||||
:allow-empty="false"
|
||||
:option-height="104"
|
||||
>
|
||||
<template #noOptions>
|
||||
{{ $t('FORMS.MULTISELECT.NO_OPTIONS') }}
|
||||
</template>
|
||||
</multiselect>
|
||||
</div>
|
||||
<div
|
||||
v-model="action_params"
|
||||
:options="dropdownValues"
|
||||
:dropdown-max-height="dropdownMaxHeight"
|
||||
/>
|
||||
<MultiSelect
|
||||
v-else-if="inputType === 'multi_select'"
|
||||
class="multiselect-wrap--small"
|
||||
>
|
||||
<multiselect
|
||||
v-model="action_params"
|
||||
track-by="id"
|
||||
label="name"
|
||||
:placeholder="$t('FORMS.MULTISELECT.SELECT')"
|
||||
multiple
|
||||
selected-label
|
||||
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
|
||||
deselect-label=""
|
||||
:max-height="160"
|
||||
:options="dropdownValues"
|
||||
:allow-empty="false"
|
||||
:option-height="104"
|
||||
>
|
||||
<template #noOptions>
|
||||
{{ $t('FORMS.MULTISELECT.NO_OPTIONS') }}
|
||||
</template>
|
||||
</multiselect>
|
||||
</div>
|
||||
<input
|
||||
v-model="action_params"
|
||||
:options="dropdownValues"
|
||||
:dropdown-max-height="dropdownMaxHeight"
|
||||
/>
|
||||
<NextInput
|
||||
v-else-if="inputType === 'email'"
|
||||
v-model="action_params"
|
||||
type="email"
|
||||
class="answer--text-input"
|
||||
size="sm"
|
||||
:placeholder="$t('AUTOMATION.ACTION.EMAIL_INPUT_PLACEHOLDER')"
|
||||
/>
|
||||
<input
|
||||
<NextInput
|
||||
v-else-if="inputType === 'url'"
|
||||
v-model="action_params"
|
||||
type="url"
|
||||
class="answer--text-input"
|
||||
size="sm"
|
||||
:placeholder="$t('AUTOMATION.ACTION.URL_INPUT_PLACEHOLDER')"
|
||||
/>
|
||||
<AutomationActionFileInput
|
||||
v-if="inputType === 'attachment'"
|
||||
v-else-if="inputType === 'attachment'"
|
||||
v-model="action_params"
|
||||
:initial-file-name="initialFileName"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<NextButton
|
||||
v-if="!isMacro"
|
||||
sm
|
||||
solid
|
||||
slate
|
||||
icon="i-lucide-trash"
|
||||
class="flex-shrink-0"
|
||||
@click="removeAction"
|
||||
/>
|
||||
</div>
|
||||
<NextButton
|
||||
v-if="!isMacro"
|
||||
icon="i-lucide-x"
|
||||
slate
|
||||
ghost
|
||||
class="flex-shrink-0"
|
||||
@click="removeAction"
|
||||
<AutomationActionTeamMessageInput
|
||||
v-if="inputType === 'team_message'"
|
||||
v-model="action_params"
|
||||
:teams="dropdownValues"
|
||||
:dropdown-max-height="dropdownMaxHeight"
|
||||
/>
|
||||
<WootMessageEditor
|
||||
v-if="inputType === 'textarea'"
|
||||
v-model="castMessageVmodel"
|
||||
rows="4"
|
||||
enable-variables
|
||||
:placeholder="$t('AUTOMATION.ACTION.TEAM_MESSAGE_INPUT_PLACEHOLDER')"
|
||||
class="[&_.ProseMirror-menubar]:hidden px-3 py-1 bg-n-alpha-1 rounded-lg outline outline-1 outline-n-weak dark:outline-n-strong"
|
||||
/>
|
||||
</div>
|
||||
<AutomationActionTeamMessageInput
|
||||
v-if="inputType === 'team_message'"
|
||||
v-model="action_params"
|
||||
:teams="dropdownValues"
|
||||
/>
|
||||
<WootMessageEditor
|
||||
v-if="inputType === 'textarea'"
|
||||
v-model="castMessageVmodel"
|
||||
rows="4"
|
||||
enable-variables
|
||||
:placeholder="$t('AUTOMATION.ACTION.TEAM_MESSAGE_INPUT_PLACEHOLDER')"
|
||||
class="action-message"
|
||||
/>
|
||||
<p v-if="errorMessage" class="filter-error">
|
||||
<span v-if="errorMessage" class="text-sm text-n-ruby-11">
|
||||
{{ errorMessage }}
|
||||
</p>
|
||||
</div>
|
||||
</span>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.filter {
|
||||
@apply bg-n-background p-2 border border-solid border-n-strong dark:border-n-strong rounded-lg mb-2;
|
||||
|
||||
&.is-a-macro {
|
||||
@apply mb-0 bg-n-background dark:bg-n-solid-1 p-0 border-0 rounded-none;
|
||||
}
|
||||
}
|
||||
|
||||
.no-margin-bottom {
|
||||
@apply mb-0;
|
||||
}
|
||||
|
||||
.filter.has-error {
|
||||
@apply bg-n-ruby-8/20 border-n-ruby-5 dark:border-n-ruby-5;
|
||||
|
||||
&.is-a-macro {
|
||||
@apply bg-transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-inputs {
|
||||
@apply flex gap-1;
|
||||
}
|
||||
|
||||
.filter-error {
|
||||
@apply text-n-ruby-9 dark:text-n-ruby-9 block my-1 mx-0;
|
||||
}
|
||||
|
||||
.action__question,
|
||||
.filter__operator {
|
||||
@apply mb-0 mr-1;
|
||||
}
|
||||
|
||||
.action__question {
|
||||
@apply max-w-[50%];
|
||||
}
|
||||
|
||||
.action__question.full-width {
|
||||
@apply max-w-full;
|
||||
}
|
||||
|
||||
.filter__answer--wrap {
|
||||
@apply max-w-[50%] flex-grow mr-1 flex w-full items-center justify-start;
|
||||
|
||||
input {
|
||||
@apply mb-0;
|
||||
}
|
||||
}
|
||||
.filter__answer {
|
||||
&.answer--text-input {
|
||||
@apply mb-0;
|
||||
}
|
||||
}
|
||||
|
||||
.filter__join-operator-wrap {
|
||||
@apply relative z-20 m-0;
|
||||
}
|
||||
|
||||
.filter__join-operator {
|
||||
@apply flex items-center justify-center relative my-2.5 mx-0;
|
||||
|
||||
.operator__line {
|
||||
@apply absolute w-full border-b border-solid border-n-weak;
|
||||
}
|
||||
|
||||
.operator__select {
|
||||
margin-bottom: 0 !important;
|
||||
@apply relative w-auto;
|
||||
}
|
||||
}
|
||||
|
||||
.multiselect {
|
||||
@apply mb-0;
|
||||
}
|
||||
.action-message {
|
||||
@apply mt-2 mx-0 mb-0;
|
||||
}
|
||||
// Prosemirror does not have a native way of hiding the menu bar, hence
|
||||
::v-deep .ProseMirror-menubar {
|
||||
@apply hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
<script>
|
||||
import MultiSelect from 'dashboard/components-next/filter/inputs/MultiSelect.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MultiSelect,
|
||||
},
|
||||
props: {
|
||||
teams: { type: Array, required: true },
|
||||
modelValue: { type: Object, required: true },
|
||||
dropdownMaxHeight: { type: String, default: 'max-h-80' },
|
||||
},
|
||||
emits: ['update:modelValue'],
|
||||
data() {
|
||||
@@ -12,9 +18,9 @@ export default {
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
const { team_ids: teamIds } = this.modelValue;
|
||||
this.selectedTeams = teamIds;
|
||||
this.message = this.modelValue.message;
|
||||
const { team_ids: teamIds, message } = this.modelValue || {};
|
||||
this.selectedTeams = teamIds || [];
|
||||
this.message = message || '';
|
||||
},
|
||||
methods: {
|
||||
updateValue() {
|
||||
@@ -28,37 +34,19 @@ export default {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="multiselect-wrap--small flex flex-col gap-1 mt-1">
|
||||
<multiselect
|
||||
v-model="selectedTeams"
|
||||
track-by="id"
|
||||
label="name"
|
||||
:placeholder="$t('AUTOMATION.ACTION.TEAM_DROPDOWN_PLACEHOLDER')"
|
||||
multiple
|
||||
selected-label
|
||||
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
|
||||
deselect-label=""
|
||||
:max-height="160"
|
||||
:options="teams"
|
||||
:allow-empty="false"
|
||||
@update:model-value="updateValue"
|
||||
/>
|
||||
<textarea
|
||||
v-model="message"
|
||||
rows="4"
|
||||
:placeholder="$t('AUTOMATION.ACTION.TEAM_MESSAGE_INPUT_PLACEHOLDER')"
|
||||
@input="updateValue"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<MultiSelect
|
||||
v-model="selectedTeams"
|
||||
:options="teams"
|
||||
:dropdown-max-height="dropdownMaxHeight"
|
||||
@update:model-value="updateValue"
|
||||
/>
|
||||
<textarea
|
||||
v-model="message"
|
||||
class="mb-0 !text-sm"
|
||||
rows="4"
|
||||
:placeholder="$t('AUTOMATION.ACTION.TEAM_MESSAGE_INPUT_PLACEHOLDER')"
|
||||
@input="updateValue"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.multiselect {
|
||||
margin: 0.25rem 0;
|
||||
}
|
||||
textarea {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -79,7 +79,7 @@ input[type='file'] {
|
||||
@apply hidden;
|
||||
}
|
||||
.input-wrapper {
|
||||
@apply flex h-9 bg-n-background py-1 px-2 items-center text-xs cursor-pointer rounded-sm border border-dashed border-n-strong;
|
||||
@apply flex h-8 bg-n-background py-1 px-2 items-center text-xs cursor-pointer rounded-lg border border-dashed border-n-strong;
|
||||
}
|
||||
.success-icon {
|
||||
@apply text-n-teal-9 mr-2;
|
||||
|
||||
@@ -1,305 +0,0 @@
|
||||
<script>
|
||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
||||
|
||||
export default {
|
||||
name: 'FilterInput',
|
||||
components: {
|
||||
NextButton,
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
filterAttributes: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
inputType: {
|
||||
type: String,
|
||||
default: 'plain_text',
|
||||
},
|
||||
operators: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
dropdownValues: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
showQueryOperator: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showUserInput: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
groupedFilters: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
filterGroups: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
customAttributeType: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
errorMessage: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
emits: ['update:modelValue', 'removeFilter', 'resetFilter'],
|
||||
computed: {
|
||||
attributeKey: {
|
||||
get() {
|
||||
if (!this.modelValue) return null;
|
||||
return this.modelValue.attribute_key;
|
||||
},
|
||||
set(value) {
|
||||
const payload = this.modelValue || {};
|
||||
this.$emit('update:modelValue', { ...payload, attribute_key: value });
|
||||
},
|
||||
},
|
||||
filterOperator: {
|
||||
get() {
|
||||
if (!this.modelValue) return null;
|
||||
return this.modelValue.filter_operator;
|
||||
},
|
||||
set(value) {
|
||||
const payload = this.modelValue || {};
|
||||
this.$emit('update:modelValue', { ...payload, filter_operator: value });
|
||||
},
|
||||
},
|
||||
values: {
|
||||
get() {
|
||||
if (!this.modelValue) return null;
|
||||
return this.modelValue.values;
|
||||
},
|
||||
set(value) {
|
||||
const payload = this.modelValue || {};
|
||||
this.$emit('update:modelValue', { ...payload, values: value });
|
||||
},
|
||||
},
|
||||
query_operator: {
|
||||
get() {
|
||||
if (!this.modelValue) return null;
|
||||
return this.modelValue.query_operator;
|
||||
},
|
||||
set(value) {
|
||||
const payload = this.modelValue || {};
|
||||
this.$emit('update:modelValue', { ...payload, query_operator: value });
|
||||
},
|
||||
},
|
||||
custom_attribute_type: {
|
||||
get() {
|
||||
if (!this.customAttributeType) return '';
|
||||
return this.customAttributeType;
|
||||
},
|
||||
set() {
|
||||
const payload = this.modelValue || {};
|
||||
this.$emit('update:modelValue', {
|
||||
...payload,
|
||||
custom_attribute_type: this.customAttributeType,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
customAttributeType: {
|
||||
handler(value) {
|
||||
if (
|
||||
value === 'conversation_attribute' ||
|
||||
value === 'contact_attribute'
|
||||
) {
|
||||
// eslint-disable-next-line vue/no-mutating-props
|
||||
this.modelValue.custom_attribute_type = this.customAttributeType;
|
||||
// eslint-disable-next-line vue/no-mutating-props
|
||||
} else this.modelValue.custom_attribute_type = '';
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
removeFilter() {
|
||||
this.$emit('removeFilter');
|
||||
},
|
||||
resetFilter() {
|
||||
this.$emit('resetFilter');
|
||||
},
|
||||
getInputErrorClass(errorMessage) {
|
||||
return errorMessage
|
||||
? 'bg-n-ruby-8/20 border-n-ruby-5 dark:border-n-ruby-5'
|
||||
: 'bg-n-background border-n-weak dark:border-n-weak';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable vue/no-mutating-props -->
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
class="p-2 border border-solid rounded-lg"
|
||||
:class="getInputErrorClass(errorMessage)"
|
||||
>
|
||||
<div class="flex gap-1">
|
||||
<select
|
||||
v-if="groupedFilters"
|
||||
v-model="attributeKey"
|
||||
class="max-w-[30%] mb-0 mr-1"
|
||||
@change="resetFilter()"
|
||||
>
|
||||
<optgroup
|
||||
v-for="(group, i) in filterGroups"
|
||||
:key="i"
|
||||
:label="group.name"
|
||||
>
|
||||
<option
|
||||
v-for="attribute in group.attributes"
|
||||
:key="attribute.key"
|
||||
:value="attribute.key"
|
||||
:selected="true"
|
||||
>
|
||||
{{ attribute.name }}
|
||||
</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
<select
|
||||
v-else
|
||||
v-model="attributeKey"
|
||||
class="max-w-[30%] mb-0 mr-1"
|
||||
@change="resetFilter()"
|
||||
>
|
||||
<option
|
||||
v-for="attribute in filterAttributes"
|
||||
:key="attribute.key"
|
||||
:value="attribute.key"
|
||||
:disabled="attribute.disabled"
|
||||
>
|
||||
{{ attribute.name }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<select v-model="filterOperator" class="max-w-[20%] mb-0 mr-1">
|
||||
<option
|
||||
v-for="(operator, o) in operators"
|
||||
:key="o"
|
||||
:value="operator.value"
|
||||
>
|
||||
{{ $t(`FILTER.OPERATOR_LABELS.${operator.value}`) }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<div v-if="showUserInput" class="flex-grow mr-1 filter__answer--wrap">
|
||||
<div
|
||||
v-if="inputType === 'multi_select'"
|
||||
class="multiselect-wrap--small"
|
||||
>
|
||||
<multiselect
|
||||
v-model="values"
|
||||
track-by="id"
|
||||
label="name"
|
||||
:placeholder="$t('FORMS.MULTISELECT.SELECT')"
|
||||
multiple
|
||||
selected-label
|
||||
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
|
||||
deselect-label=""
|
||||
:max-height="160"
|
||||
:options="dropdownValues"
|
||||
:allow-empty="false"
|
||||
>
|
||||
<template #noOptions>
|
||||
{{ $t('FORMS.MULTISELECT.NO_OPTIONS') }}
|
||||
</template>
|
||||
</multiselect>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="inputType === 'search_select'"
|
||||
class="multiselect-wrap--small"
|
||||
>
|
||||
<multiselect
|
||||
v-model="values"
|
||||
track-by="id"
|
||||
label="name"
|
||||
:placeholder="$t('FORMS.MULTISELECT.SELECT')"
|
||||
selected-label
|
||||
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
|
||||
deselect-label=""
|
||||
:max-height="160"
|
||||
:options="dropdownValues"
|
||||
:allow-empty="false"
|
||||
:option-height="104"
|
||||
>
|
||||
<template #noOptions>
|
||||
{{ $t('FORMS.MULTISELECT.NO_OPTIONS') }}
|
||||
</template>
|
||||
</multiselect>
|
||||
</div>
|
||||
<div v-else-if="inputType === 'date'" class="multiselect-wrap--small">
|
||||
<input
|
||||
v-model="values"
|
||||
type="date"
|
||||
:editable="false"
|
||||
class="!mb-0 datepicker"
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
v-else
|
||||
v-model="values"
|
||||
type="text"
|
||||
class="!mb-0"
|
||||
:placeholder="$t('FILTER.INPUT_PLACEHOLDER')"
|
||||
/>
|
||||
</div>
|
||||
<NextButton
|
||||
icon="i-lucide-x"
|
||||
slate
|
||||
ghost
|
||||
class="flex-shrink-0"
|
||||
@click="removeFilter"
|
||||
/>
|
||||
</div>
|
||||
<p v-if="errorMessage" class="filter-error">
|
||||
{{ errorMessage }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="showQueryOperator"
|
||||
class="flex items-center justify-center relative my-2.5 mx-0"
|
||||
>
|
||||
<hr class="absolute w-full border-b border-solid border-n-weak" />
|
||||
<select
|
||||
v-model="query_operator"
|
||||
class="relative w-auto mb-0 bg-n-background text-n-slate-12 border-n-weak"
|
||||
>
|
||||
<option value="and">
|
||||
{{ $t('FILTER.QUERY_DROPDOWN_LABELS.AND') }}
|
||||
</option>
|
||||
<option value="or">
|
||||
{{ $t('FILTER.QUERY_DROPDOWN_LABELS.OR') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.filter__answer--wrap {
|
||||
input {
|
||||
@apply bg-n-background mb-0 text-n-slate-12 border-n-weak;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-error {
|
||||
@apply text-n-ruby-9 dark:text-n-ruby-9 block my-1 mx-0;
|
||||
}
|
||||
|
||||
.multiselect {
|
||||
@apply mb-0;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user