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. 99
      src/components/EntityGrid.vue

99
src/components/EntityGrid.vue

@ -13,7 +13,7 @@ projects, and special entities with selection. * * @author Matthew Raymer */
@keydown.enter="performSearch" @keydown.enter="performSearch"
/> />
<div <div
v-show="isSearching" v-show="isSearching && searchTerm"
class="border-y border-slate-400 ps-2 py-1.5 text-center text-slate-400" class="border-y border-slate-400 ps-2 py-1.5 text-center text-slate-400"
> >
<font-awesome <font-awesome
@ -47,7 +47,7 @@ projects, and special entities with selection. * * @author Matthew Raymer */
<template v-if="entityType === 'people'"> <template v-if="entityType === 'people'">
<!-- "You" entity --> <!-- "You" entity -->
<SpecialEntityCard <SpecialEntityCard
v-if="showYouEntity" v-if="showYouEntity && !searchTerm.trim()"
entity-type="you" entity-type="you"
label="You" label="You"
icon="hand" icon="hand"
@ -61,7 +61,7 @@ projects, and special entities with selection. * * @author Matthew Raymer */
<!-- "Unnamed" entity --> <!-- "Unnamed" entity -->
<SpecialEntityCard <SpecialEntityCard
v-if="showUnnamedEntity" v-if="showUnnamedEntity && !searchTerm.trim()"
entity-type="unnamed" entity-type="unnamed"
:label="unnamedEntityName" :label="unnamedEntityName"
icon="circle-question" icon="circle-question"
@ -79,16 +79,60 @@ projects, and special entities with selection. * * @author Matthew Raymer */
<!-- Entity cards (people or projects) --> <!-- Entity cards (people or projects) -->
<template v-if="entityType === 'people'"> <template v-if="entityType === 'people'">
<PersonCard <!-- When showing contacts without search: split into recent and alphabetical -->
v-for="person in displayedEntities as Contact[]" <template v-if="!searchTerm.trim()">
:key="person.did" <!-- Recently Added Section -->
:person="person" <template v-if="recentContacts.length > 0">
:conflicted="isPersonConflicted(person.did)" <li
:show-time-icon="true" class="text-xs font-semibold text-slate-500 uppercase pt-5 pb-1.5 px-2 border-b border-slate-300"
:notify="notify" >
:conflict-context="conflictContext" Recently Added
@person-selected="handlePersonSelected" </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"
:person="person"
:conflicted="isPersonConflicted(person.did)"
:show-time-icon="true"
:notify="notify"
:conflict-context="conflictContext"
@person-selected="handlePersonSelected"
/>
</template>
</template> </template>
<template v-else-if="entityType === 'projects'"> <template v-else-if="entityType === 'projects'">
@ -257,6 +301,35 @@ export default class EntityGrid extends Vue {
return this.entities.slice(0, maxDisplay); 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 * Computed empty state message based on entity type
*/ */

Loading…
Cancel
Save