|  | 
| 1 | 1 | <template> | 
| 2 |  | -Member Search | 
|  | 2 | +  <div class="user-search"> | 
|  | 3 | +    <div class="member-search-field"> | 
|  | 4 | +      <div class="nested-input-container"> | 
|  | 5 | +        <a v-if="search" @click="clearSearch()" class="nested-clear-btn fa fa-times"></a> | 
|  | 6 | +        <a @click="searchUsers()" class="nested-btn">Search</a> | 
|  | 7 | +        <input class="input-text nested-input" type="search" v-model="searchStr" id="search-users" placeholder="Search users by username" @keydown="$event.which === 13 && searchUsers()" @keyup="$event.which === 27 && clearSearch()" /> | 
|  | 8 | +      </div> | 
|  | 9 | +    </div> | 
|  | 10 | + | 
|  | 11 | +    <div class="member-search-results" v-if="searchData && (searchData.count > 0 || search)"> | 
|  | 12 | +      <div v-if="search"> | 
|  | 13 | +      Displaying {{searchData.count}} search result(s) for "<strong>{{search}}</strong>":<br /><br /> | 
|  | 14 | +      </div> | 
|  | 15 | +      <table width="100%"> | 
|  | 16 | +        <thead class="members-header"> | 
|  | 17 | +          <tr> | 
|  | 18 | +            <th class="pointer" ><span :class="getSortClass('username')"></span> Username</th> | 
|  | 19 | +            <th class="pointer" ><span :class="getSortClass('role')"></span> Role</th> | 
|  | 20 | +            <th class="pointer"><span :class="getSortClass('post_count')"></span> Post Count</th> | 
|  | 21 | +            <th class="pointer hide-mobile" ><span :class="getSortClass('created_at')"></span> Registered Date</th> | 
|  | 22 | +          </tr> | 
|  | 23 | +        </thead> | 
|  | 24 | +        <tbody class="members"> | 
|  | 25 | +          <tr v-if="searchData?.count <= 0"> | 
|  | 26 | +            <td class="centered-text" colspan="4"><h5>No users to display</h5></td> | 
|  | 27 | +          </tr> | 
|  | 28 | +          <tr v-for="user in searchData?.users" :key="user.username"> | 
|  | 29 | +            <td class="username"> | 
|  | 30 | +              <a class="search-users user-avatar hide-mobile" href=""> | 
|  | 31 | +                <img :src="user.avatar || defaultAvatar"> | 
|  | 32 | +              </a> | 
|  | 33 | +              <a href="" v-html="user.username"></a> | 
|  | 34 | +            </td> | 
|  | 35 | +            <td v-html="user.role"></td> | 
|  | 36 | +            <td v-html="user.post_count"></td> | 
|  | 37 | +            <td class="hide-mobile">{{ user.created_at }}</td> | 
|  | 38 | +          </tr> | 
|  | 39 | +        </tbody> | 
|  | 40 | +      </table> | 
|  | 41 | +    </div> | 
|  | 42 | +  </div> | 
|  | 43 | +  <div class="sidebar"> | 
|  | 44 | +    <div class="sidebar-block" v-if="searchData?.page_count > 1"> | 
|  | 45 | +       <pagination :page="searchData.page" :limit="searchData.limit" :count="searchData.count"></pagination> | 
|  | 46 | +    </div> | 
|  | 47 | +  </div> | 
| 3 | 48 | </template> | 
| 4 | 49 | 
 | 
| 5 | 50 | <script> | 
| 6 |  | -import { reactive, toRefs } from 'vue' | 
|  | 51 | +import { reactive, toRefs, computed } from 'vue' | 
|  | 52 | +import { usersApi } from '@/api' | 
|  | 53 | +import Pagination from '@/components/layout/Pagination.vue' | 
|  | 54 | +import { useRoute, useRouter } from 'vue-router' | 
| 7 | 55 | 
 | 
| 8 | 56 | export default { | 
| 9 | 57 |   name: 'About', | 
|  | 58 | +  components: { Pagination }, | 
|  | 59 | +  beforeRouteEnter(to, from, next) { | 
|  | 60 | +    const params = { | 
|  | 61 | +      limit: 15, | 
|  | 62 | +      page: to.query.page || 1, | 
|  | 63 | +      field: to.query.field, | 
|  | 64 | +      desc: to.query.desc | 
|  | 65 | +    } | 
|  | 66 | +    next(vm => usersApi.memberSearch(params).then(d => vm.searchData = d).catch(() => {})) | 
|  | 67 | +  }, | 
|  | 68 | +  beforeRouteUpdate(to, from, next) { | 
|  | 69 | +    const params = { | 
|  | 70 | +      limit: 15, | 
|  | 71 | +      page: to.query.page || 1, | 
|  | 72 | +      field: to.query.field, | 
|  | 73 | +      desc: to.query.desc | 
|  | 74 | +    } | 
|  | 75 | +    usersApi.memberSearch(params).then(d => this.searchData = d).catch(() => {}) | 
|  | 76 | +    next() | 
|  | 77 | +  }, | 
| 10 | 78 |   setup() { | 
|  | 79 | +    const pageUsers = inc => { | 
|  | 80 | +      const params = { ...$route.params, saveScrollPos: true } | 
|  | 81 | +      // Handle different pagination controls (threads) prev/next vs (posts) actual pagination | 
|  | 82 | +      const newPage = v.threads && inc ? v.postData.page + inc : v.currentPage | 
|  | 83 | +      let query = { ...$route.query, page: newPage } | 
|  | 84 | +      if (query.page === 1 || !query.page) delete query.page | 
|  | 85 | +      if ($route.query.page !== v.currentPage) | 
|  | 86 | +        $router.replace({ name: $route.name, params: params, query: query }) | 
|  | 87 | +    } | 
|  | 88 | +
 | 
|  | 89 | +    const searchUsers = () => console.log('search users') | 
|  | 90 | +    const clearSearch = () => console.log('clear search') | 
|  | 91 | +
 | 
|  | 92 | +    const setSortField = () => { | 
|  | 93 | +      // // Get/Set new sort field | 
|  | 94 | +      // if (newField) v.sortField = newField | 
|  | 95 | +      // else newField = v.sortField | 
|  | 96 | +      // // Convert desc query param to boolean | 
|  | 97 | +      // let desc = $route.query.desc === 'false' ? false : true | 
|  | 98 | +      // // Sort Field hasn't changed just toggle desc | 
|  | 99 | +      // const defaultField = newField === 'updated_at' && !$route.query.field | 
|  | 100 | +      // if (defaultField || newField === $route.query.field) desc = !desc | 
|  | 101 | +      // else desc = true // Sort field changed, default to desc true | 
|  | 102 | +      // // Update router to have new query params, watch on query params will update data | 
|  | 103 | +      // let query = { field: newField, page: $route.query.page } | 
|  | 104 | +      // if (!query.page) delete query.page // don't include page if undefined | 
|  | 105 | +      // if (newField === 'updated_at') delete query.field // do not display default field in qs | 
|  | 106 | +      // if (!desc) query.desc = false  // only display desc in query string when false | 
|  | 107 | +      // const params = { ...$route.params, saveScrollPos: true } // save scroll pos when sorting table | 
|  | 108 | +      // $router.replace({ name: $route.name, params: params, query: query }) | 
|  | 109 | +    } | 
|  | 110 | +
 | 
|  | 111 | +    const getSortClass = field => { | 
|  | 112 | +      let sortClass = 'fa ' | 
|  | 113 | +      const desc = v.searchData.desc | 
|  | 114 | +      const curField = v.searchData.field | 
|  | 115 | +      const defaultField = field === 'updated_at' && !curField | 
|  | 116 | +      if ((defaultField || curField === field) && desc) sortClass += 'fa-sort-down' | 
|  | 117 | +      else if ((defaultField || curField === field) && !desc) sortClass += 'fa-sort-up' | 
|  | 118 | +      else sortClass += 'fa-sort' | 
|  | 119 | +      return sortClass | 
|  | 120 | +    } | 
|  | 121 | +
 | 
|  | 122 | +    const $route = useRoute() | 
|  | 123 | +    const $router = useRouter() | 
|  | 124 | +
 | 
| 11 | 125 |     const v = reactive({ | 
| 12 |  | -      title: window.title, | 
| 13 |  | -      description: window.description, | 
| 14 |  | -      currentYear: window.currentYear | 
|  | 126 | +      currentPage: Number($route.query.page) || 1, | 
|  | 127 | +      pages: computed(() => v.searchData?.page_count), | 
|  | 128 | +      search: computed(() => v.searchData?.search), | 
|  | 129 | +      searchData: null, | 
|  | 130 | +      searchStr: null, | 
|  | 131 | +      defaultAvatar: window.default_avatar, | 
|  | 132 | +      defaultAvatarShape: window.default_avatar_shape, | 
| 15 | 133 |     }) | 
| 16 |  | -    return { ...toRefs(v) } | 
|  | 134 | +    return { ...toRefs(v), searchUsers, clearSearch, setSortField, getSortClass, pageUsers } | 
| 17 | 135 |   } | 
| 18 | 136 | } | 
| 19 | 137 | </script> | 
| 20 | 138 | 
 | 
| 21 |  | -<style scoped lang="scss"> | 
|  | 139 | +<style lang="scss"> | 
|  | 140 | +.member-search main #public-content { grid-template-areas: 'header header' 'main sidebar' 'main sidebar'; } | 
|  | 141 | +.user-search { grid-area: main; } | 
|  | 142 | +.sidebar { | 
|  | 143 | +  grid-area: sidebar; | 
| 22 | 144 | 
 | 
|  | 145 | +  .sidebar-block { | 
|  | 146 | +    display: block; | 
|  | 147 | +    position: sticky; | 
|  | 148 | +    top: $header-offset; | 
|  | 149 | +  } | 
|  | 150 | +} | 
|  | 151 | +
 | 
|  | 152 | +.members td { | 
|  | 153 | +  height: 6.25rem; | 
|  | 154 | +  padding-left: 0; | 
|  | 155 | +  padding-right: 0; | 
|  | 156 | +} | 
|  | 157 | +
 | 
|  | 158 | +.user-avatar img { | 
|  | 159 | +  object-fit: cover; | 
|  | 160 | +  height: 6.25rem; | 
|  | 161 | +  width: 6.25rem; | 
|  | 162 | +  display: inline-block; | 
|  | 163 | +  vertical-align: middle; | 
|  | 164 | +  margin-right: .5rem | 
|  | 165 | +} | 
|  | 166 | +.members-header th { text-align: left; } | 
| 23 | 167 | </style> | 
0 commit comments