feat: Add search support for drop downs in create linear issue (#9566)
Co-authored-by: iamsivin <iamsivin@gmail.com>
This commit is contained in:
@@ -13,7 +13,7 @@ defineProps({
|
|||||||
<template>
|
<template>
|
||||||
<button
|
<button
|
||||||
class="relative inline-flex items-center justify-start w-full p-3 border-0 rounded-none first:rounded-t-xl last:rounded-b-xl h-11 hover:bg-slate-50 dark:hover:bg-slate-700 active:bg-slate-75 dark:active:bg-slate-800"
|
class="relative inline-flex items-center justify-start w-full p-3 border-0 rounded-none first:rounded-t-xl last:rounded-b-xl h-11 hover:bg-slate-50 dark:hover:bg-slate-700 active:bg-slate-75 dark:active:bg-slate-800"
|
||||||
@click.stop="$emit('click')"
|
@click.stop.prevent="$emit('click')"
|
||||||
@mouseenter="$emit('mouseenter')"
|
@mouseenter="$emit('mouseenter')"
|
||||||
@mouseleave="$emit('mouseleave')"
|
@mouseleave="$emit('mouseleave')"
|
||||||
@focus="$emit('focus')"
|
@focus="$emit('focus')"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div @submit.prevent="onSubmit">
|
<div>
|
||||||
<woot-input
|
<woot-input
|
||||||
v-model="formState.title"
|
v-model="formState.title"
|
||||||
:class="{ error: v$.title.$error }"
|
:class="{ error: v$.title.$error }"
|
||||||
@@ -26,61 +26,19 @@
|
|||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<label :class="{ error: v$.teamId.$error }">
|
<div class="flex flex-col gap-4">
|
||||||
{{ $t('INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.FORM.TEAM.LABEL') }}
|
<searchable-dropdown
|
||||||
<select
|
v-for="dropdown in dropdowns"
|
||||||
v-model="formState.teamId"
|
:key="dropdown.type"
|
||||||
:style="inputStyles"
|
:type="dropdown.type"
|
||||||
@change="onChangeTeam"
|
:value="formState[dropdown.type]"
|
||||||
>
|
:label="$t(dropdown.label)"
|
||||||
<option v-for="item in teams" :key="item.name" :value="item.id">
|
:items="dropdown.items"
|
||||||
{{ item.name }}
|
:placeholder="$t(dropdown.placeholder)"
|
||||||
</option>
|
:error="dropdown.error"
|
||||||
</select>
|
@change="onChange"
|
||||||
<span v-if="v$.teamId.$error" class="message">
|
/>
|
||||||
{{ teamError }}
|
</div>
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
{{ $t('INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.FORM.ASSIGNEE.LABEL') }}
|
|
||||||
<select v-model="formState.assigneeId" :style="inputStyles">
|
|
||||||
<option v-for="item in assignees" :key="item.name" :value="item.id">
|
|
||||||
{{ item.name }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
{{ $t('INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.FORM.LABEL.LABEL') }}
|
|
||||||
<select v-model="formState.labelId" :style="inputStyles">
|
|
||||||
<option v-for="item in labels" :key="item.name" :value="item.id">
|
|
||||||
{{ item.name }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
{{ $t('INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.FORM.PRIORITY.LABEL') }}
|
|
||||||
<select v-model="formState.priority" :style="inputStyles">
|
|
||||||
<option v-for="item in priorities" :key="item.name" :value="item.id">
|
|
||||||
{{ item.name }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
{{ $t('INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.FORM.PROJECT.LABEL') }}
|
|
||||||
<select v-model="formState.projectId" :style="inputStyles">
|
|
||||||
<option v-for="item in projects" :key="item.name" :value="item.id">
|
|
||||||
{{ item.name }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
{{ $t('INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.FORM.STATUS.LABEL') }}
|
|
||||||
<select v-model="formState.stateId" :style="inputStyles">
|
|
||||||
<option v-for="item in statuses" :key="item.name" :value="item.id">
|
|
||||||
{{ item.name }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<div class="flex items-center justify-end w-full gap-2 mt-8">
|
<div class="flex items-center justify-end w-full gap-2 mt-8">
|
||||||
<woot-button
|
<woot-button
|
||||||
class="px-4 rounded-xl button clear outline-woot-200/50 outline"
|
class="px-4 rounded-xl button clear outline-woot-200/50 outline"
|
||||||
@@ -108,6 +66,7 @@ import { useAlert } from 'dashboard/composables';
|
|||||||
import LinearAPI from 'dashboard/api/integrations/linear';
|
import LinearAPI from 'dashboard/api/integrations/linear';
|
||||||
import validations from './validations';
|
import validations from './validations';
|
||||||
import { parseLinearAPIErrorResponse } from 'dashboard/store/utils/api';
|
import { parseLinearAPIErrorResponse } from 'dashboard/store/utils/api';
|
||||||
|
import SearchableDropdown from './SearchableDropdown.vue';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
accountId: {
|
accountId: {
|
||||||
@@ -162,7 +121,6 @@ const formState = reactive({
|
|||||||
priority: '',
|
priority: '',
|
||||||
projectId: '',
|
projectId: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const v$ = useVuelidate(validations, formState);
|
const v$ = useVuelidate(validations, formState);
|
||||||
|
|
||||||
const isSubmitDisabled = computed(
|
const isSubmitDisabled = computed(
|
||||||
@@ -179,6 +137,56 @@ const teamError = computed(() =>
|
|||||||
: ''
|
: ''
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const dropdowns = computed(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type: 'teamId',
|
||||||
|
label: 'INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.FORM.TEAM.LABEL',
|
||||||
|
items: teams.value,
|
||||||
|
placeholder: 'INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.FORM.TEAM.SEARCH',
|
||||||
|
error: teamError.value,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'assigneeId',
|
||||||
|
label: 'INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.FORM.ASSIGNEE.LABEL',
|
||||||
|
items: assignees.value,
|
||||||
|
placeholder:
|
||||||
|
'INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.FORM.ASSIGNEE.SEARCH',
|
||||||
|
error: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'labelId',
|
||||||
|
label: 'INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.FORM.LABEL.LABEL',
|
||||||
|
items: labels.value,
|
||||||
|
placeholder: 'INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.FORM.LABEL.SEARCH',
|
||||||
|
error: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'priority',
|
||||||
|
label: 'INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.FORM.PRIORITY.LABEL',
|
||||||
|
items: priorities,
|
||||||
|
placeholder:
|
||||||
|
'INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.FORM.PRIORITY.SEARCH',
|
||||||
|
error: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'projectId',
|
||||||
|
label: 'INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.FORM.PROJECT.LABEL',
|
||||||
|
items: projects.value,
|
||||||
|
placeholder:
|
||||||
|
'INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.FORM.PROJECT.SEARCH',
|
||||||
|
error: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'stateId',
|
||||||
|
label: 'INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.FORM.STATUS.LABEL',
|
||||||
|
items: statuses.value,
|
||||||
|
placeholder: 'INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.FORM.STATUS.SEARCH',
|
||||||
|
error: '',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
const onClose = () => emit('close');
|
const onClose = () => emit('close');
|
||||||
|
|
||||||
const getTeams = async () => {
|
const getTeams = async () => {
|
||||||
@@ -212,12 +220,15 @@ const getTeamEntities = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onChangeTeam = event => {
|
const onChange = (item, type) => {
|
||||||
formState.teamId = event.target.value;
|
formState[type] = item.id;
|
||||||
formState.assigneeId = '';
|
if (type === 'teamId') {
|
||||||
formState.stateId = '';
|
formState.assigneeId = '';
|
||||||
formState.labelId = '';
|
formState.stateId = '';
|
||||||
getTeamEntities();
|
formState.labelId = '';
|
||||||
|
formState.projectId = '';
|
||||||
|
getTeamEntities();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const createIssue = async () => {
|
const createIssue = async () => {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<filter-button
|
<filter-button
|
||||||
right-icon="chevron-down"
|
right-icon="chevron-down"
|
||||||
:button-text="linkIssueTitle"
|
:button-text="linkIssueTitle"
|
||||||
class="justify-between w-full bg-slate-50 dark:bg-slate-800 hover:bg-slate-75 dark:hover:bg-slate-800"
|
class="justify-between w-full h-[2.5rem] py-1.5 px-3 rounded-xl border border-slate-50 bg-slate-25 dark:border-slate-600 dark:bg-slate-900 hover:bg-slate-50 dark:hover:bg-slate-900/50"
|
||||||
@click="toggleDropdown"
|
@click="toggleDropdown"
|
||||||
>
|
>
|
||||||
<template v-if="shouldShowDropdown" #dropdown>
|
<template v-if="shouldShowDropdown" #dropdown>
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</filter-button>
|
</filter-button>
|
||||||
<div class="flex items-center justify-end w-full gap-2">
|
<div class="flex items-center justify-end w-full gap-2 mt-2">
|
||||||
<woot-button
|
<woot-button
|
||||||
class="px-4 rounded-xl button clear outline-woot-200/50 outline"
|
class="px-4 rounded-xl button clear outline-woot-200/50 outline"
|
||||||
@click.prevent="onClose"
|
@click.prevent="onClose"
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="flex w-full"
|
||||||
|
:class="type === 'stateId' && shouldShowDropdown ? 'h-[150px]' : 'gap-2'"
|
||||||
|
>
|
||||||
|
<label class="w-full" :class="{ error: hasError }">
|
||||||
|
{{ label }}
|
||||||
|
<filter-button
|
||||||
|
right-icon="chevron-down"
|
||||||
|
:button-text="selectedItemName"
|
||||||
|
class="justify-between w-full h-[2.5rem] py-1.5 px-3 rounded-xl border border-slate-50 bg-slate-25 dark:border-slate-600 dark:bg-slate-900 hover:bg-slate-50 dark:hover:bg-slate-900/50"
|
||||||
|
@click="toggleDropdown"
|
||||||
|
>
|
||||||
|
<template v-if="shouldShowDropdown" #dropdown>
|
||||||
|
<filter-list-dropdown
|
||||||
|
v-on-clickaway="toggleDropdown"
|
||||||
|
:show-clear-filter="false"
|
||||||
|
:list-items="items"
|
||||||
|
:active-filter-id="selectedItemId"
|
||||||
|
:input-placeholder="placeholder"
|
||||||
|
enable-search
|
||||||
|
class="left-0 flex flex-col w-full overflow-y-auto h-fit !max-h-[160px] md:left-auto md:right-0 top-10"
|
||||||
|
@click="onSelect"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</filter-button>
|
||||||
|
<span v-if="hasError" class="mt-1 message">{{ error }}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, defineComponent } from 'vue';
|
||||||
|
import FilterButton from 'dashboard/components/ui/Dropdown/DropdownButton.vue';
|
||||||
|
import FilterListDropdown from 'dashboard/components/ui/Dropdown/DropdownList.vue';
|
||||||
|
|
||||||
|
defineComponent({
|
||||||
|
name: 'SearchableDropdown',
|
||||||
|
});
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
type: { type: String, required: true },
|
||||||
|
label: { type: String, default: null },
|
||||||
|
items: { type: Array, required: true },
|
||||||
|
value: { type: [Number, String], default: null },
|
||||||
|
placeholder: { type: String, default: null },
|
||||||
|
error: { type: String, default: null },
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['change']);
|
||||||
|
const shouldShowDropdown = ref(false);
|
||||||
|
|
||||||
|
const toggleDropdown = () => {
|
||||||
|
shouldShowDropdown.value = !shouldShowDropdown.value;
|
||||||
|
};
|
||||||
|
const onSelect = item => {
|
||||||
|
emit('change', item, props.type);
|
||||||
|
toggleDropdown();
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasError = computed(() => !!props.error);
|
||||||
|
|
||||||
|
const selectedItem = computed(() => {
|
||||||
|
if (!props.value) return null;
|
||||||
|
return props.items.find(i => i.id === props.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedItemName = computed(
|
||||||
|
() => selectedItem.value?.name || props.placeholder
|
||||||
|
);
|
||||||
|
|
||||||
|
const selectedItemId = computed(() => selectedItem.value?.id || null);
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user