refactor: create select muliple

This commit is contained in:
Thanaphon Frappet 2024-11-14 17:50:03 +07:00
parent e19a15eaeb
commit a5988bc3d4
2 changed files with 214 additions and 0 deletions

View file

@ -0,0 +1,104 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { createSelect, SelectProps } from './select-multiple';
import SelectInput from '../SelectInput.vue';
import useAddressStore, { Office } from 'stores/address';
type SelectOption = Office;
const value = defineModel<string[] | null | undefined>('value', {
required: true,
});
const valueOption = defineModel<SelectOption[]>('valueOption', {
required: false,
});
const selectOptions = ref<SelectOption[]>([]);
const { fetchOffice: getList, fetchOfficeById: getById } = useAddressStore();
defineEmits<{
(e: 'create'): void;
}>();
type ExclusiveProps = {};
const props = defineProps<SelectProps<typeof getList> & ExclusiveProps>();
const { getOptions, setFirstValue, getSelectedOption, filter } =
createSelect<SelectOption>(
{
value,
valueOption,
selectOptions,
getList: async (query) => {
const ret = await getList({
query,
...props.params,
});
if (ret) return ret;
},
getByValue: async (id) => {
const ret = await getById(id);
if (ret) return ret;
},
},
{ valueField: 'id' },
);
onMounted(async () => {
await getOptions();
if (props.autoSelectOnSingle && selectOptions.value.length === 1) {
setFirstValue();
}
await getSelectedOption();
});
</script>
<template>
<SelectInput
v-model="value"
option-value="id"
option-label="name"
incremental
multiple
:label
:placeholder
:readonly
:disable="disabled"
:option="selectOptions"
:hide-selected="false"
:fill-input="false"
:rules="[(v: string) => !!v || $t('form.error.required')]"
@filter="filter"
>
<template #before-options v-if="creatable">
<q-item clickable v-close-popup @click.stop="$emit('create')">
<q-item-section>
<b class="row items-center">
<q-icon
name="mdi-plus-circle-outline"
class="q-mr-sm"
style="color: hsl(var(--positive-bg))"
/>
{{ $t('general.add', { text: $t('quotation.newCustomer') }) }}
</b>
</q-item-section>
</q-item>
<q-separator class="q-mx-sm" />
</template>
<template #append v-if="clearable">
<q-icon
v-if="!readonly && value"
name="mdi-close-circle"
@click.stop="value = []"
class="cursor-pointer clear-btn"
/>
</template>
</SelectInput>
</template>

View file

@ -0,0 +1,110 @@
import { QSelect } from 'quasar';
import { ref, Ref, watch } from 'vue';
export type SelectProps<T extends (...args: any[]) => any> = {
params?: Parameters<T>[0];
creatable?: boolean;
label?: string;
placeholder?: string;
readonly?: boolean;
required?: boolean;
disabled?: boolean;
clearable?: boolean;
autoSelectOnSingle?: boolean;
};
export const createSelect = <T extends Record<string, any>>(
state: {
value: Ref<string[] | null | undefined>;
valueOption: Ref<T[] | undefined>;
selectOptions: Ref<T[]>;
getByValue: (id: string) => Promise<T | void> | T | void;
getList: (query?: string) => Promise<T[] | void> | T[] | void;
},
opts?: {
valueField?: keyof T;
},
) => {
const { value, valueOption, selectOptions, getList, getByValue } = state;
const valueField = opts?.valueField || 'value';
let cache: T[];
let previousSearch = '';
watch(value, (v) => {
console.log('UPDATED');
if (
!v ||
(cache && cache.find((opt) => v.find((val) => val === opt[valueField])))
) {
return;
}
getSelectedOption();
});
async function getOptions(query?: string) {
if (cache && selectOptions.value.length > 0 && previousSearch === query) {
selectOptions.value = JSON.parse(JSON.stringify(cache));
return;
}
const ret = await getList(query);
if (ret) {
cache = ret;
selectOptions.value = JSON.parse(JSON.stringify(cache));
previousSearch = query || previousSearch;
}
}
async function setFirstValue() {
if (value.value) return;
const first = selectOptions.value.at(0);
if (first) value.value = first[valueField];
}
async function getSelectedOption() {
const currentValue = value.value;
if (!currentValue) return;
if (selectOptions.value.find((v) => v[valueField] === currentValue)) return;
const newValueOptions = currentValue.map(async (a) => {
const findValue = valueOption.value?.find((b) => b[valueField] === a);
if (findValue) {
selectOptions.value.unshift(findValue);
return findValue;
}
const ret = await getByValue(a);
if (ret) {
selectOptions.value.unshift(ret);
return ret;
}
});
const retValueOptions = await Promise.all(newValueOptions);
valueOption.value = retValueOptions.flatMap((v) => (!!v ? v : []));
}
type QuasarSelectUpdate = (
callback: () => void,
afterFn?: ((ref: QSelect) => void) | undefined,
) => void;
function filter(value: string, update: QuasarSelectUpdate) {
update(
() => getOptions(value),
(ref) => {
if (!!value && ref.options && ref.options.length > 0) {
ref.setOptionIndex(-1);
ref.moveOptionSelection(1, true);
}
},
);
}
return { getOptions, setFirstValue, getSelectedOption, filter };
};