feat: add pagination support for project lists in dialogs

Add server-side pagination to EntityGrid component for projects, enabling
infinite scrolling to load all available projects instead of stopping after
the initial batch.

Changes:
- EntityGrid: Add loadMoreCallback prop to trigger server-side loading when
  scroll reaches end of loaded projects
- OnboardMeetingSetupView: Update loadProjects() to support pagination with
  beforeId parameter and add handleLoadMoreProjects() callback
- MeetingProjectDialog: Accept and pass through loadMoreCallback to EntityGrid
- GiftedDialog: Add pagination support to loadProjects() and
  handleLoadMoreProjects() callback
- EntitySelectionStep: Accept and pass through loadMoreCallback prop to
  EntityGrid when showing projects

This ensures users can access all projects in MeetingProjectDialog and
GiftedDialog by automatically loading more as they scroll, matching the
behavior already present in DiscoverView.

All project uses of EntityGrid now use pagination by default.
This commit is contained in:
Jose Olarte III
2025-11-12 17:10:03 +08:00
parent bf7ee630d0
commit 6bf4055c2f
5 changed files with 165 additions and 12 deletions

View File

@@ -207,6 +207,7 @@ export default class EntityGrid extends Vue {
displayedCount = INITIAL_BATCH_SIZE;
infiniteScrollReset?: () => void;
scrollContainer?: HTMLElement;
isLoadingMore = false; // Prevent duplicate callback calls
/**
* Array of entities to display
@@ -286,6 +287,23 @@ export default class EntityGrid extends Vue {
entityType: "people" | "projects",
) => Contact[] | PlanData[];
/**
* Optional callback function to load more entities from server
* Called when infinite scroll reaches end and more data is available
* Required for projects when using server-side pagination
*
* @param entities - Current array of entities
* @returns Promise that resolves when more entities are loaded
*
* @example
* :load-more-callback="async (entities) => {
* const lastEntity = entities[entities.length - 1];
* await loadMoreFromServer(lastEntity.rowId);
* }"
*/
@Prop({ default: null })
loadMoreCallback?: (entities: Contact[] | PlanData[]) => Promise<void>;
/**
* CSS classes for the empty state message
*/
@@ -540,7 +558,15 @@ export default class EntityGrid extends Vue {
}
if (this.entityType === "projects") {
// Projects: check if more available
// Projects: if we've shown all loaded entities, callback handles server-side availability
// If callback exists and we've reached the end, assume more might be available
if (
this.displayedCount >= this.entities.length &&
this.loadMoreCallback
) {
return !this.isLoadingMore; // Only return true if not already loading
}
// Otherwise, check if more in memory
return this.displayedCount < this.entities.length;
}
@@ -560,9 +586,30 @@ export default class EntityGrid extends Vue {
if (container) {
const { reset } = useInfiniteScroll(
container,
() => {
// Load more: increment displayedCount
this.displayedCount += INCREMENT_SIZE;
async () => {
// For projects: if we've shown all entities and callback exists, call it
if (
this.entityType === "projects" &&
this.displayedCount >= this.entities.length &&
this.loadMoreCallback &&
!this.isLoadingMore
) {
this.isLoadingMore = true;
try {
await this.loadMoreCallback(this.entities);
// After callback, entities prop will update via Vue reactivity
// Reset scroll state to allow further loading
this.infiniteScrollReset?.();
} catch (error) {
// Error handling is up to the callback, but we should reset loading state
console.error("Error in loadMoreCallback:", error);
} finally {
this.isLoadingMore = false;
}
} else {
// Normal case: increment displayedCount to show more from memory
this.displayedCount += INCREMENT_SIZE;
}
},
{
distance: 50, // pixels from bottom