CitySelect.vue 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. <template>
  2. <q-select
  3. v-model="selectedCity"
  4. v-bind="$attrs"
  5. use-input
  6. hide-selected
  7. fill-input
  8. clearable
  9. :options="cityOptions"
  10. :label="label"
  11. :loading="loading"
  12. :placeholder="$t('common.actions.search') + ' ' + $t('ui.navigation.city')"
  13. :rules="rules"
  14. :disable="disable"
  15. label-color="primary"
  16. @filter="filterFn"
  17. >
  18. <template #no-option>
  19. <q-item>
  20. <q-item-section class="text-grey">
  21. {{ $t("http.errors.no_records_found") }}
  22. </q-item-section>
  23. </q-item>
  24. </template>
  25. </q-select>
  26. </template>
  27. <script setup>
  28. import { getCities } from "src/api/city";
  29. import { ref, onMounted, watch } from "vue";
  30. import { useI18n } from "vue-i18n";
  31. const emit = defineEmits(["selectedStateId"]);
  32. const { state, label, rules, initialId, disable } = defineProps({
  33. // This country prop is here for future use, maybe
  34. country: {
  35. type: Object,
  36. required: false,
  37. default: () => {
  38. return {
  39. label: "Brasil",
  40. value: 1,
  41. };
  42. },
  43. },
  44. state: {
  45. type: Object,
  46. required: false,
  47. default: null,
  48. },
  49. label: {
  50. type: String,
  51. default: () => useI18n().t("ui.navigation.city"),
  52. },
  53. rules: {
  54. type: Array,
  55. default: () => [],
  56. },
  57. initialId: {
  58. type: Number,
  59. required: false,
  60. default: null,
  61. },
  62. disable: {
  63. type: Boolean,
  64. default: false,
  65. },
  66. });
  67. const selectedCity = defineModel();
  68. const loading = ref(false);
  69. const filteredCities = ref([]);
  70. const baseCities = ref([]);
  71. const cityOptions = ref([]);
  72. const filterFn = async (val, update) => {
  73. if (!val) {
  74. cityOptions.value = filteredCities.value.map((city) => ({
  75. label: city.name,
  76. value: city.id,
  77. state_id: city.state_id,
  78. }));
  79. } else {
  80. const needle = val.toLowerCase();
  81. const cities = filteredCities.value.filter(
  82. (v) => v.name.toLowerCase().indexOf(needle) > -1,
  83. );
  84. cityOptions.value = cities.map((city) => ({
  85. label: city.name,
  86. value: city.id,
  87. state_id: city.state_id,
  88. }));
  89. }
  90. update();
  91. };
  92. const selectCityByName = (name) => {
  93. if (selectedCity.value?.label === name) {
  94. return;
  95. }
  96. selectedCity.value = cityOptions.value.find((city) => city.label === name);
  97. };
  98. const selectCityById = (id) => {
  99. if (selectedCity.value?.value === id) {
  100. return;
  101. }
  102. selectedCity.value = cityOptions.value.find((city) => city.value === id);
  103. };
  104. watch(
  105. () => state,
  106. (value, oldValue) => {
  107. if (
  108. value?.value != oldValue?.value &&
  109. value?.value != selectedCity.value?.state_id
  110. ) {
  111. selectedCity.value = null;
  112. }
  113. if (value) {
  114. filteredCities.value = baseCities.value.filter(
  115. (city) => city.state_id === value.value,
  116. );
  117. cityOptions.value = filteredCities.value.map((city) => ({
  118. label: city.name,
  119. value: city.id,
  120. state_id: city.state_id,
  121. }));
  122. } else {
  123. filteredCities.value = baseCities.value;
  124. cityOptions.value = baseCities.value.map((city) => ({
  125. label: city.name,
  126. value: city.id,
  127. state_id: city.state_id,
  128. }));
  129. }
  130. },
  131. { immediate: true },
  132. );
  133. watch(selectedCity, () => {
  134. if (selectedCity.value?.state_id) {
  135. emit("selectedStateId", selectedCity.value.state_id);
  136. }
  137. });
  138. onMounted(async () => {
  139. try {
  140. loading.value = true;
  141. baseCities.value = await getCities();
  142. filteredCities.value = baseCities.value;
  143. cityOptions.value = baseCities.value.map((city) => ({
  144. label: city.name,
  145. value: city.id,
  146. state_id: city.state_id,
  147. }));
  148. if (initialId) {
  149. selectCityById(initialId);
  150. }
  151. } catch (e) {
  152. console.error(e);
  153. } finally {
  154. loading.value = false;
  155. }
  156. });
  157. defineExpose({
  158. selectCityByName,
  159. selectCityById,
  160. });
  161. </script>