CitySelect.vue 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. <template>
  2. <DefaultSelect
  3. v-model="selectedCity"
  4. v-bind="$attrs"
  5. use-input
  6. hide-selected
  7. fill-input
  8. clearable
  9. :options="cityOptions"
  10. :label
  11. :loading
  12. :placeholder
  13. @filter="filterFn"
  14. >
  15. <template #no-option>
  16. <q-item>
  17. <q-item-section class="text-grey">
  18. {{ $t("http.errors.no_records_found") }}
  19. </q-item-section>
  20. </q-item>
  21. </template>
  22. </DefaultSelect>
  23. </template>
  24. <script setup>
  25. import { getCities } from "src/api/city";
  26. import { ref, watch } from "vue";
  27. import { normalizeString } from "src/helpers/utils";
  28. import { useI18n } from "vue-i18n";
  29. import DefaultSelect from "src/components/defaults/DefaultSelect.vue";
  30. const emit = defineEmits(["selectedStateId"]);
  31. const { state, label, placeholder } = defineProps({
  32. state: {
  33. type: Object,
  34. required: false,
  35. default: null,
  36. },
  37. label: {
  38. type: String,
  39. default: () => useI18n().t("ui.navigation.city"),
  40. },
  41. placeholder: {
  42. type: String,
  43. default: () =>
  44. useI18n().t("common.actions.search") +
  45. " " +
  46. useI18n().t("ui.navigation.city"),
  47. },
  48. });
  49. const selectedCity = defineModel({ type: Object });
  50. const loading = ref(false);
  51. const baseOptions = ref([]);
  52. const cityOptions = ref([]);
  53. const ensureOnlyPossibleOptions = (state_id) => {
  54. cityOptions.value = state_id
  55. ? baseOptions.value.filter((c) => c.state_id === state_id)
  56. : baseOptions.value;
  57. };
  58. const filterFn = async (val, update) => {
  59. if (baseOptions.value.length === 0) {
  60. loading.value = true;
  61. try {
  62. const data = await getCities();
  63. baseOptions.value = data.map((c) => ({
  64. label: c.name,
  65. value: c.id,
  66. state_id: c.state_id,
  67. }));
  68. } catch (e) {
  69. console.error(e);
  70. } finally {
  71. loading.value = false;
  72. }
  73. }
  74. ensureOnlyPossibleOptions(state?.value);
  75. const needle = normalizeString(val);
  76. if (needle) {
  77. cityOptions.value = cityOptions.value.filter((v) =>
  78. normalizeString(v.label).includes(needle),
  79. );
  80. }
  81. update();
  82. };
  83. const selectCityByName = (name) => {
  84. if (selectedCity.value?.label === name) return;
  85. selectedCity.value = baseOptions.value.find((c) => c.label === name);
  86. };
  87. const selectCityById = (id) => {
  88. if (selectedCity.value?.value === id) return;
  89. selectedCity.value = baseOptions.value.find((c) => c.value === id);
  90. };
  91. watch(
  92. () => state,
  93. (value, oldValue) => {
  94. if (!oldValue) return;
  95. if (value?.value != oldValue?.value && value?.value != selectedCity.value?.state_id) {
  96. selectedCity.value = null;
  97. }
  98. if (value && baseOptions.value.length > 0) {
  99. ensureOnlyPossibleOptions(value.value);
  100. }
  101. },
  102. );
  103. watch(selectedCity, () => {
  104. if (selectedCity.value?.state_id) {
  105. emit("selectedStateId", selectedCity.value.state_id);
  106. }
  107. });
  108. defineExpose({
  109. selectCityByName,
  110. selectCityById,
  111. });
  112. </script>