Skip to content

Commit ba924ff

Browse files
committed
feat: Add user search functionality
- Remove search input from Team view - Add search input to Users view - Implement debounced search in Users view - Update API to support user search by full name
1 parent ee67cc8 commit ba924ff

3 files changed

Lines changed: 47 additions & 44 deletions

File tree

client/src/views/TeamView.vue

Lines changed: 4 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,7 @@
2525
</v-card-title>
2626
</v-card>
2727
</div>
28-
29-
<div class="d-flex align-center justify-space-between mt-6 mb-4">
30-
<h3>Team Members</h3>
31-
<v-text-field v-model="searchQuery" label="Search by name" prepend-inner-icon="mdi-magnify" variant="outlined"
32-
density="compact" hide-details clearable class="search-field" @update:model-value="onSearchChange"
33-
style="max-width: 300px;">
34-
</v-text-field>
35-
</div>
36-
28+
<h3>Team Members</h3>
3729
<v-card class="" v-if="teamUsers.length">
3830
<v-row>
3931
<v-col class="d-flex flex-wrap justify-start">
@@ -50,8 +42,7 @@
5042
rounded="circle"></v-pagination>
5143
</v-card>
5244
<v-card v-else class="pa-15 mt-15 d-flex justify-center text-center" variant="plain">
53-
<span v-if="searchQuery">No users found matching "{{ searchQuery }}". Try a different search.</span>
54-
<span v-else>Seems like there are no users. Please try changing the filters and attempting again.</span>
45+
Seems like there are no users. Please try changing the filters and attempting again.
5546
</v-card>
5647
</v-container>
5748
</template>
@@ -62,7 +53,6 @@ import { watch, ref, onMounted } from 'vue'
6253
import profileImage from '../components/profileImage.vue'
6354
import UserCard from '@/components/userCard.vue'
6455
import type { Api } from '@/types';
65-
import { debounce } from 'lodash';
6656
6757
export default {
6858
name: 'TeamView',
@@ -85,19 +75,14 @@ export default {
8575
const teamPage = ref(1)
8676
const teamCount = ref<number>(0)
8777
const teamUsers = ref<any[]>([])
88-
const searchQuery = ref('')
8978
9079
function displayValue(value: any) {
9180
return value ?? '-'
9281
}
9382
94-
// Function to list team based on page and search query
83+
// Function to list team based on page and itemsPerPage
9584
async function listTeam(): Promise<Api.User[]> {
96-
const query: any = { page: teamPage.value }
97-
if (searchQuery.value.trim()) {
98-
query.search = searchQuery.value.trim()
99-
}
100-
const res = await $api.users.team.list(query)
85+
const res = await $api.users.team.list({ page: teamPage.value })
10186
if (res && res.count) {
10287
teamCount.value = Math.ceil(res.count / 12)
10388
} else {
@@ -106,18 +91,6 @@ export default {
10691
return res!.results
10792
}
10893
109-
// Debounced search handler
110-
const performSearch = debounce(async () => {
111-
loading.value = true
112-
teamPage.value = 1 // Reset to first page when searching
113-
teamUsers.value = await listTeam()
114-
loading.value = false
115-
}, 300)
116-
117-
function onSearchChange() {
118-
performSearch()
119-
}
120-
12194
watch(teamPage,
12295
async () => {
12396
loading.value = true
@@ -145,8 +118,6 @@ export default {
145118
teamPage,
146119
teamLeads,
147120
displayValue,
148-
searchQuery,
149-
onSearchChange,
150121
}
151122
}
152123
}

client/src/views/UsersView.vue

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,18 @@
1212
You can change the selected office to discover the team in other offices.
1313
</v-alert>
1414
</v-col>
15-
<v-col cols="6" sm="6" md="6" class="pa-1">
15+
<v-col cols="12" sm="4" md="4" class="pa-1">
1616
<v-autocomplete clearable v-model="selectedOffice" :items="offices" label="Office" return-object
1717
item-title="country" @click:clear="clearFilter('office')" @update:model-value="applyFilters" />
1818
</v-col>
19-
<v-col cols="6" sm="6" md="6" class="pa-1">
19+
<v-col cols="12" sm="4" md="4" class="pa-1">
2020
<v-autocomplete clearable v-model="selectedTeam" :items="teams" label="Team" return-object item-title="name"
2121
item-value="id" @click:clear="clearFilter('team')" @update:model-value="applyFilters" />
2222
</v-col>
23+
<v-col cols="12" sm="4" md="4" class="pa-1">
24+
<v-text-field v-model="searchQuery" label="Search by name" prepend-inner-icon="mdi-magnify" clearable
25+
@click:clear="clearFilter('search')" @update:model-value="onSearchChange" />
26+
</v-col>
2327
</v-row>
2428

2529
<template v-if="isLoading">
@@ -51,6 +55,7 @@ import { useRoute, useRouter } from "vue-router";
5155
import { $api } from "@/clients";
5256
import UserCard from "@/components/userCard.vue";
5357
import type { Api } from "@/types";
58+
import { debounce } from "lodash";
5459
5560
export default {
5661
name: "UsersView",
@@ -63,6 +68,7 @@ export default {
6368
const users = ref<Api.User[]>([]);
6469
const selectedOffice = ref<Api.LocationType | null>(null);
6570
const selectedTeam = ref<{ name: string, id: number } | null>(null);
71+
const searchQuery = ref("");
6672
const officePage = ref(1);
6773
const usersPage = ref(1);
6874
const officeCount = ref(0);
@@ -89,16 +95,18 @@ export default {
8995
const res = await $api.users.list({
9096
location_id: selectedOffice?.value?.id || "",
9197
team_name: selectedTeam?.value?.name || "",
98+
search: searchQuery.value.trim() || "",
9299
page: usersPage.value,
93100
});
94101
usersCount.value = Math.ceil(res!.count / 12);
95102
users.value = res!.results;
96103
isLoading.value = false;
97104
};
98105
99-
const clearFilter = (filter: "office" | "team") => {
106+
const clearFilter = (filter: "office" | "team" | "search") => {
100107
if (filter === "office") selectedOffice.value = null;
101108
if (filter === "team") selectedTeam.value = null;
109+
if (filter === "search") searchQuery.value = "";
102110
applyFilters();
103111
};
104112
@@ -108,10 +116,21 @@ export default {
108116
query: {
109117
location_id: selectedOffice.value?.id || "",
110118
team_name: selectedTeam.value?.name || "",
119+
search: searchQuery.value.trim() || "",
111120
},
112121
});
113122
};
114123
124+
// Debounced search to avoid too many API calls
125+
const performSearch = debounce(() => {
126+
usersPage.value = 1;
127+
applyFilters();
128+
}, 300);
129+
130+
const onSearchChange = () => {
131+
performSearch();
132+
};
133+
115134
const initialize = async () => {
116135
isLoading.value = true;
117136
await Promise.all([fetchOffices(), fetchUsers()]);
@@ -123,6 +142,9 @@ export default {
123142
if (route.query.team_name) {
124143
selectedTeam.value = teams.find((team) => team.name === route.query.team_name) as { name: string, id: number };
125144
}
145+
if (route.query.search) {
146+
searchQuery.value = route.query.search as string;
147+
}
126148
isLoading.value = false;
127149
};
128150
@@ -139,17 +161,24 @@ export default {
139161
await fetchUsers();
140162
});
141163
164+
// Watch for route query changes (e.g., when search is applied)
165+
watch(() => route.query, () => {
166+
fetchUsers();
167+
}, { deep: true });
168+
142169
return {
143170
offices,
144171
users,
145172
teams,
146173
selectedOffice,
147174
selectedTeam,
175+
searchQuery,
148176
usersPage,
149177
usersCount,
150178
isLoading,
151179
clearFilter,
152180
applyFilters,
181+
onSearchChange,
153182
};
154183
},
155184
};

server/cshr/views/users.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,17 +49,27 @@ def get_queryset(self) -> Response:
4949
"""get all users in the system for a normal user"""
5050
location_id = ""
5151
team_name = ""
52+
user_full_name = ""
53+
5254
if self.request.query_params.get("location_id"):
5355
location_id = self.request.query_params.get("location_id")
5456

5557
if self.request.query_params.get("team_name"):
5658
team_name = self.request.query_params.get("team_name")
59+
60+
if self.request.query_params.get("user_full_name"):
61+
user_full_name = self.request.query_params.get("user_full_name").strip()
5762

5863
if len(location_id) > 0 or len(team_name) > 0:
5964
options = {"location": {"id": location_id}, "team": {"name": team_name}}
6065
query_set = get_all_of_users(options)
6166
else:
6267
query_set = get_all_of_users()
68+
69+
# Filter by search query (full name)
70+
if user_full_name:
71+
query_set = query_set.filter(full_name__icontains=user_full_name)
72+
6373
return query_set
6474

6575

@@ -70,17 +80,10 @@ class TeamAPIView(ListAPIView):
7080

7181
def get_queryset(self) -> Response:
7282
"""
73-
Get all team information, Team leaders and team members.
74-
Supports filtering by 'search' query parameter for full_name.
83+
Get all team information, Team leaders and team members
7584
"""
7685
user: User = self.request.user
7786
query_set: List[User] = get_user_team_members(user)
78-
79-
# Filter by search query (full name)
80-
search = self.request.query_params.get("search", "").strip()
81-
if search:
82-
query_set = query_set.filter(full_name__icontains=search)
83-
8487
return query_set
8588

8689

0 commit comments

Comments
 (0)