CitySelect.vue 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  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
  11. :rules
  12. :loading
  13. :placeholder="$t('common.actions.search') + ' ' + $t('ui.navigation.city')"
  14. :error
  15. :error-message
  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 } = 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. error: {
  63. type: Boolean,
  64. default: false,
  65. },
  66. errorMessage: {
  67. type: String,
  68. default: "",
  69. },
  70. });
  71. const selectedCity = defineModel();
  72. const loading = ref(false);
  73. const filteredCities = ref([]);
  74. const baseCities = ref([]);
  75. const cityOptions = ref([]);
  76. const filterFn = async (val, update) => {
  77. if (!val) {
  78. cityOptions.value = filteredCities.value.map((city) => ({
  79. label: city.name,
  80. value: city.id,
  81. state_id: city.state_id,
  82. }));
  83. } else {
  84. const needle = val.toLowerCase();
  85. const cities = filteredCities.value.filter(
  86. (v) => v.name.toLowerCase().indexOf(needle) > -1,
  87. );
  88. cityOptions.value = cities.map((city) => ({
  89. label: city.name,
  90. value: city.id,
  91. state_id: city.state_id,
  92. }));
  93. }
  94. update();
  95. };
  96. const selectCityByName = (name) => {
  97. if (selectedCity.value?.label === name) {
  98. return;
  99. }
  100. selectedCity.value = cityOptions.value.find((city) => city.label === name);
  101. };
  102. const selectCityById = (id) => {
  103. if (selectedCity.value?.value === id) {
  104. return;
  105. }
  106. selectedCity.value = cityOptions.value.find((city) => city.value === id);
  107. };
  108. watch(
  109. () => state,
  110. (value, oldValue) => {
  111. if (
  112. value?.value != oldValue?.value &&
  113. value?.value != selectedCity.value?.state_id
  114. ) {
  115. selectedCity.value = null;
  116. }
  117. if (value) {
  118. filteredCities.value = baseCities.value.filter(
  119. (city) => city.state_id === value.value,
  120. );
  121. cityOptions.value = filteredCities.value.map((city) => ({
  122. label: city.name,
  123. value: city.id,
  124. state_id: city.state_id,
  125. }));
  126. } else {
  127. filteredCities.value = baseCities.value;
  128. cityOptions.value = baseCities.value.map((city) => ({
  129. label: city.name,
  130. value: city.id,
  131. state_id: city.state_id,
  132. }));
  133. }
  134. },
  135. { immediate: true },
  136. );
  137. watch(selectedCity, () => {
  138. if (selectedCity.value?.state_id) {
  139. emit("selectedStateId", selectedCity.value.state_id);
  140. }
  141. });
  142. onMounted(async () => {
  143. try {
  144. loading.value = true;
  145. baseCities.value = await getCities();
  146. filteredCities.value = baseCities.value;
  147. cityOptions.value = baseCities.value.map((city) => ({
  148. label: city.name,
  149. value: city.id,
  150. state_id: city.state_id,
  151. }));
  152. if (initialId) {
  153. selectCityById(initialId);
  154. }
  155. } catch (e) {
  156. console.log(e);
  157. } finally {
  158. loading.value = false;
  159. }
  160. });
  161. defineExpose({
  162. selectCityByName,
  163. selectCityById,
  164. });
  165. </script>