Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions frontend/src/api/members.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type {
AllMembersFilter,
Member,
MemberWithContact,
MemberEventTeam,
} from "@/dto/members";

export const getAllMembers = (filters?: AllMembersFilter) =>
Expand All @@ -11,3 +12,9 @@ export const getAllMembers = (filters?: AllMembersFilter) =>
});

export const getMe = () => instance.get<MemberWithContact>("/me");

export const getMemberById = (id: string) =>
instance.get<Member>(`/members/${id}`);

export const getMemberParticipations = (id: string) =>
instance.get<MemberEventTeam[]>(`/members/${id}/participations`);
Original file line number Diff line number Diff line change
Expand Up @@ -151,17 +151,50 @@
</button>
</div>

<!-- Members section -->
<div v-if="filteredMembers.length > 0">
<div
class="px-3 py-2 text-xs font-semibold text-gray-500 bg-gray-50 border-b"
>
Members
</div>
<button
v-for="member in filteredMembers"
:key="`member-${member.id}`"
type="button"
:class="[
'w-full text-left px-3 py-2 border-b border-gray-100 last:border-b-0 flex items-center gap-3',
getItemIndex(member) === highlightedIndex
? 'bg-blue-50 border-blue-200'
: 'hover:bg-gray-50',
]"
@click="selectMember(member)"
>
<Image
:src="member.img"
:alt="member.name"
class="w-8 h-8 rounded object-cover border flex-shrink-0"
/>
<div class="flex-1 min-w-0">
<div class="font-medium text-gray-900 truncate">
{{ member.name }}
</div>
</div>
</button>
</div>

<!-- No results -->
<div
v-if="
!isLoading &&
filteredCompanies.length === 0 &&
filteredSpeakers.length === 0 &&
filteredMembers.length === 0 &&
searchTerm.trim()
"
class="p-3 text-gray-500 text-center"
>
No companies or speakers found
No companies, speakers or members found
</div>

<!-- Welcome message when no search term -->
Expand All @@ -170,11 +203,12 @@
!isLoading &&
!searchTerm.trim() &&
filteredCompanies.length === 0 &&
filteredSpeakers.length === 0
filteredSpeakers.length === 0 &&
filteredMembers.length === 0
"
class="p-3 text-gray-500 text-center"
>
Start typing to search companies and speakers...
Start typing to search companies, speakers and members...
</div>

<!-- Create options -->
Expand Down Expand Up @@ -255,6 +289,7 @@ import { ref, computed, watch, nextTick } from "vue";
import { useQuery } from "@pinia/colada";
import { getAllCompanies } from "@/api/companies";
import { getAllSpeakers } from "@/api/speakers";
import { getAllMembers } from "@/api/members";
import { useEventStore } from "@/stores/event";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
Expand All @@ -271,8 +306,9 @@ import {
} from "@/components/ui/alert-dialog";
import type { Company } from "@/dto/companies";
import type { Speaker } from "@/dto/speakers";
import type { Member } from "@/dto/members";

type SelectedItem = Company | Speaker;
type SelectedItem = Company | Speaker | Member;

interface Props {
modelValue?: string;
Expand All @@ -297,6 +333,7 @@ const props = withDefaults(defineProps<Props>(), {
const emit = defineEmits<{
companySelected: [value: Company];
speakerSelected: [value: Speaker];
memberSelected: [value: Member];
"update:modelValue": [value: string];
companySuccess: [companyId: string];
speakerSuccess: [speakerId: string];
Expand Down Expand Up @@ -335,8 +372,14 @@ const { data: speakersData, isLoading: speakersLoading } = useQuery({
enabled: () => !!eventStore.selectedEvent?.id,
});

const { data: membersData, isLoading: membersLoading } = useQuery({
key: () => ["members"],
query: () => getAllMembers({}),
enabled: () => !!eventStore.selectedEvent?.id,
});

const isLoading = computed(
() => companiesLoading.value || speakersLoading.value,
() => companiesLoading.value || speakersLoading.value || membersLoading.value,
);

const filteredCompanies = computed(() => {
Expand Down Expand Up @@ -377,12 +420,37 @@ const filteredSpeakers = computed(() => {
.slice(0, 5); // Limit to 5 results
});

const filteredMembers = computed(() => {
if (!membersData.value?.data) return [];

const term = searchTerm.value.toLowerCase();

if (!term) {
// Show recent members when no search term
return membersData.value.data.slice(0, 5);
}

return membersData.value.data
.filter((member: Member) => member.name.toLowerCase().includes(term))
.slice(0, 5);
});

const results = computed(() => [
...filteredCompanies.value,
...filteredSpeakers.value,
...filteredCompanies.value.map((company: Company) => ({
...company,
type: "company",
})),
...filteredSpeakers.value.map((speaker: Speaker) => ({
...speaker,
type: "speaker",
})),
...filteredMembers.value.map((member: Member) => ({
...member,
type: "member",
})),
]);

const getItemIndex = (item: Company | Speaker) => {
const getItemIndex = (item: SelectedItem) => {
return results.value.findIndex((result) => result.id === item.id);
};

Expand All @@ -396,6 +464,10 @@ const getItemImage = (item: SelectedItem) => {
return item.imgs.internal || item.imgs.speaker;
}
}

// Member may have an `img` property
if ((item as Member).img) return (item as Member).img;

return "";
};

Expand Down Expand Up @@ -440,11 +512,11 @@ const handleKeydown = (event: KeyboardEvent) => {
highlightedIndex.value < results.value.length
) {
const selectedResult = results.value[highlightedIndex.value];
if ("companyName" in selectedResult) {
// It's a speaker
if (selectedResult.type === "speaker") {
selectSpeaker(selectedResult as Speaker);
} else {
// It's a company
} else if (selectedResult.type === "member") {
selectMember(selectedResult as Member);
} else if (selectedResult.type === "company") {
selectCompany(selectedResult as Company);
}
}
Expand All @@ -469,6 +541,15 @@ const selectSpeaker = (speaker: Speaker) => {
emit("update:modelValue", speaker.name);
};

const selectMember = (member: Member) => {
selectedItem.value = member;
searchTerm.value = member.name;
showSuggestions.value = false;
highlightedIndex.value = -1;
emit("memberSelected", member);
emit("update:modelValue", member.name);
};

const clearSelection = () => {
selectedItem.value = null;
searchTerm.value = "";
Expand Down
8 changes: 7 additions & 1 deletion frontend/src/components/Navbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ import {
import { useEventStore } from "@/stores/event";
import { useAuthStore } from "@/stores/auth";
import { useRouter } from "vue-router";
import CompanyOrSpeakerAutocompleteWithDialog from "./CompanyOrSpeakerAutocompleteWithDialog.vue";
import CompanyOrSpeakerAutocompleteWithDialog from "./CompanyOrSpeakerOrMemberAutocompleteWithDialog.vue";
import type { Company } from "@/dto/companies";
import type { Speaker } from "@/dto/speakers";
import type { Member } from "@/dto/members";
import { useMagicKeys } from "@vueuse/core";
import Notification from "./navbar/Notification.vue";

Expand Down Expand Up @@ -73,6 +74,9 @@ const companySelected = (company: Company) =>
const speakerSelected = (speaker: Speaker) =>
router.push({ name: "speaker", params: { speakerId: speaker.id } });

const memberSelected = (member: Member) =>
router.push({ name: "member", params: { memberId: member.id } });

const keys = useMagicKeys();
const shortcutMac = keys["meta+k"];
const shortcutLinux = keys["ctrl+k"];
Expand Down Expand Up @@ -134,6 +138,7 @@ watch(shortcutLinux, () => {
show-create
@company-selected="companySelected"
@speaker-selected="speakerSelected"
@member-selected="memberSelected"
/>

<!-- Desktop Navigation -->
Expand Down Expand Up @@ -180,6 +185,7 @@ watch(shortcutLinux, () => {
placeholder="Search"
@company-selected="companySelected"
@speaker-selected="speakerSelected"
@member-selected="memberSelected"
/>

<div class="container mx-auto px-4">
Expand Down
7 changes: 6 additions & 1 deletion frontend/src/components/companies/MembersCompanies.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@
<template #default="{ item }">
<div class="w-full border-b border-muted-foreground/10 pb-4 mb-4">
<div class="flex items-center justify-between w-full py-2">
<MemberWithAvatar :member="item" with-separator />
<RouterLink
:to="{ name: 'member', params: { memberId: item.id } }"
class="flex items-center gap-3 no-underline"
>
<MemberWithAvatar :member="item" with-separator />
</RouterLink>
<button
type="button"
class="p-2 rounded-md hover:bg-slate-100"
Expand Down
7 changes: 6 additions & 1 deletion frontend/src/components/speakers/MembersSpeakers.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@
<template #default="{ item }">
<div class="w-full border-b border-muted-foreground/10 pb-4 mb-4">
<div class="flex items-center justify-between w-full py-2">
<MemberWithAvatar :member="item" with-separator />
<RouterLink
:to="{ name: 'member', params: { memberId: item.id } }"
class="flex items-center gap-3 no-underline"
>
<MemberWithAvatar :member="item" with-separator />
</RouterLink>
<button
type="button"
class="p-2 rounded-md hover:bg-slate-100"
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ const router = createRouter({
name: "speaker",
component: () => import("./views/Dashboard/Speakers/SpeakerView.vue"),
},
{
path: "member/:memberId",
name: "member",
component: () => import("./views/Dashboard/Members/MemberView.vue"),
},
{
path: "settings",
name: "settings",
Expand Down
Loading
Loading