Browse Source

feat(EntityGrid): Split contacts into recent and alphabetical sections

When displaying contacts (not search results), show the 3 most recently
added contacts at the top with a "Recently Added" heading, followed by
the rest sorted alphabetically with an "Everyone Else" heading.

- Add recentContacts and alphabeticalContacts computed properties
- Hide "You" and "Unnamed" special entities during search
- Only show search spinner when actively searching with a term
- Style section headings with uppercase, improved spacing, and borders
pull/216/head
Jose Olarte III 13 hours ago
parent
commit
4004d9fe52
  1. 79
      src/components/EntityGrid.vue

79
src/components/EntityGrid.vue

@ -13,7 +13,7 @@ projects, and special entities with selection. * * @author Matthew Raymer */
@keydown.enter="performSearch"
/>
<div
v-show="isSearching"
v-show="isSearching && searchTerm"
class="border-y border-slate-400 ps-2 py-1.5 text-center text-slate-400"
>
<font-awesome
@ -47,7 +47,7 @@ projects, and special entities with selection. * * @author Matthew Raymer */
<template v-if="entityType === 'people'">
<!-- "You" entity -->
<SpecialEntityCard
v-if="showYouEntity"
v-if="showYouEntity && !searchTerm.trim()"
entity-type="you"
label="You"
icon="hand"
@ -61,7 +61,7 @@ projects, and special entities with selection. * * @author Matthew Raymer */
<!-- "Unnamed" entity -->
<SpecialEntityCard
v-if="showUnnamedEntity"
v-if="showUnnamedEntity && !searchTerm.trim()"
entity-type="unnamed"
:label="unnamedEntityName"
icon="circle-question"
@ -79,6 +79,49 @@ projects, and special entities with selection. * * @author Matthew Raymer */
<!-- Entity cards (people or projects) -->
<template v-if="entityType === 'people'">
<!-- When showing contacts without search: split into recent and alphabetical -->
<template v-if="!searchTerm.trim()">
<!-- Recently Added Section -->
<template v-if="recentContacts.length > 0">
<li
class="text-xs font-semibold text-slate-500 uppercase pt-5 pb-1.5 px-2 border-b border-slate-300"
>
Recently Added
</li>
<PersonCard
v-for="person in recentContacts"
:key="person.did"
:person="person"
:conflicted="isPersonConflicted(person.did)"
:show-time-icon="true"
:notify="notify"
:conflict-context="conflictContext"
@person-selected="handlePersonSelected"
/>
</template>
<!-- Alphabetical Section -->
<template v-if="alphabeticalContacts.length > 0">
<li
class="text-xs font-semibold text-slate-500 uppercase pt-5 pb-1.5 px-2 border-b border-slate-300"
>
Everyone Else
</li>
<PersonCard
v-for="person in alphabeticalContacts"
:key="person.did"
:person="person"
:conflicted="isPersonConflicted(person.did)"
:show-time-icon="true"
:notify="notify"
:conflict-context="conflictContext"
@person-selected="handlePersonSelected"
/>
</template>
</template>
<!-- When searching: show filtered results normally -->
<template v-else>
<PersonCard
v-for="person in displayedEntities as Contact[]"
:key="person.did"
@ -90,6 +133,7 @@ projects, and special entities with selection. * * @author Matthew Raymer */
@person-selected="handlePersonSelected"
/>
</template>
</template>
<template v-else-if="entityType === 'projects'">
<ProjectCard
@ -257,6 +301,35 @@ export default class EntityGrid extends Vue {
return this.entities.slice(0, maxDisplay);
}
/**
* Get the 3 most recently added contacts (when showing contacts and not searching)
*/
get recentContacts(): Contact[] {
if (this.entityType !== "people" || this.searchTerm.trim()) {
return [];
}
// Entities are already sorted by date added (newest first)
return (this.entities as Contact[]).slice(0, 3);
}
/**
* Get the remaining contacts sorted alphabetically (when showing contacts and not searching)
*/
get alphabeticalContacts(): Contact[] {
if (this.entityType !== "people" || this.searchTerm.trim()) {
return [];
}
// Skip the first 3 (recent contacts) and sort the rest alphabetically
// Create a copy to avoid mutating the original array
const remaining = (this.entities as Contact[]).slice(3);
return [...remaining].sort((a: Contact, b: Contact) => {
// Sort alphabetically by name, falling back to DID if name is missing
const nameA = (a.name || a.did).toLowerCase();
const nameB = (b.name || b.did).toLowerCase();
return nameA.localeCompare(nameB);
});
}
/**
* Computed empty state message based on entity type
*/

Loading…
Cancel
Save