1
0
forked from baron/baron-sso

perf(admin): implement server-side search and virtualization for tenant list

- Backend: Added 'search' parameter to TenantRepository and TenantService.
- Backend: Updated all Tenant list calls to support searching.
- Backend: Enhanced UserRepository.List to support cursor-based pagination and search.
- Frontend: Switched TenantListPage to use useInfiniteQuery for lazy loading.
- Frontend: Implemented list virtualization in TenantHierarchyView using @tanstack/react-virtual.
- Frontend: Added server-side search with debouncing (useDeferredValue).
- Fixed various Go compilation errors caused by method signature changes.
This commit is contained in:
2026-06-04 14:08:55 +09:00
parent 8f2e351875
commit 6d3f128282
18 changed files with 223 additions and 108 deletions

View File

@@ -235,7 +235,7 @@ func (h *AdminHandler) countTenants(ctx context.Context) int64 {
if h == nil || h.TenantRepo == nil {
return 0
}
_, total, err := h.TenantRepo.List(ctx, 1, 0, "")
_, total, err := h.TenantRepo.List(ctx, 1, 0, "", "")
if err != nil {
return 0
}

View File

@@ -629,7 +629,7 @@ func (h *AuthHandler) GetActiveTenants(c *fiber.Ctx) error {
}
// 3. List and Filter Tenants
tenants, _, err := h.TenantService.ListTenants(c.Context(), 1000, 0, "")
tenants, _, err := h.TenantService.ListTenants(c.Context(), 1000, 0, "", "")
if err != nil {
return errorJSON(c, fiber.StatusInternalServerError, "Failed to fetch tenants")
}

View File

@@ -3571,7 +3571,7 @@ func (h *DevHandler) ListMyTenants(c *fiber.Ctx) error {
}
if role == domain.RoleSuperAdmin {
tenants, _, err := h.TenantSvc.ListTenants(c.Context(), 100, 0, "")
tenants, _, err := h.TenantSvc.ListTenants(c.Context(), 100, 0, "", "")
if err != nil {
return errorJSON(c, fiber.StatusInternalServerError, "failed to list tenants")
}

View File

@@ -113,7 +113,7 @@ func (h *UserHandler) resolveHanmacEmailScope(ctx context.Context) (*hanmacEmail
return nil, nil
}
tenants, _, err := h.TenantService.ListTenants(ctx, 10000, 0, "")
tenants, _, err := h.TenantService.ListTenants(ctx, 10000, 0, "", "")
if err != nil {
return nil, err
}

View File

@@ -269,7 +269,7 @@ func (h *TenantHandler) ListTenants(c *fiber.Ctx) error {
if role != domain.RoleSuperAdmin {
// Not a super admin: Only return the entire tree(s) of the tenants they belong to
allTenants, _, err := h.Service.ListTenants(c.Context(), 10000, 0, "")
allTenants, _, err := h.Service.ListTenants(c.Context(), 10000, 0, "", "")
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
@@ -343,13 +343,13 @@ func (h *TenantHandler) ListTenants(c *fiber.Ctx) error {
} else {
// Super Admin case
if cursorRaw != "" && h.DB != nil {
tenants, total, nextCursor, err = h.listTenantsByCursor(c.Context(), limit, parentId, cursorRaw)
tenants, total, nextCursor, err = h.listTenantsByCursor(c.Context(), limit, parentId, cursorRaw, "")
if err != nil {
return errorJSON(c, fiber.StatusBadRequest, "invalid cursor")
}
offset = 0
} else {
tenants, total, err = h.Service.ListTenants(c.Context(), limit, offset, parentId)
tenants, total, err = h.Service.ListTenants(c.Context(), limit, offset, parentId, "")
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
@@ -382,7 +382,7 @@ func (h *TenantHandler) ListTenants(c *fiber.Ctx) error {
})
}
func (h *TenantHandler) listTenantsByCursor(ctx context.Context, limit int, parentID string, cursorRaw string) ([]domain.Tenant, int64, string, error) {
func (h *TenantHandler) listTenantsByCursor(ctx context.Context, limit int, parentID string, cursorRaw string, search string) ([]domain.Tenant, int64, string, error) {
cursor, err := pagination.Decode(cursorRaw)
if err != nil {
return nil, 0, "", err
@@ -395,6 +395,12 @@ func (h *TenantHandler) listTenantsByCursor(ctx context.Context, limit int, pare
pageQuery = pageQuery.Where("parent_id = ?", parentID)
}
if search != "" {
searchTerm := "%" + strings.ToLower(search) + "%"
countQuery = countQuery.Where("LOWER(name) LIKE ? OR LOWER(slug) LIKE ? OR LOWER(description) LIKE ?", searchTerm, searchTerm, searchTerm)
pageQuery = pageQuery.Where("LOWER(name) LIKE ? OR LOWER(slug) LIKE ? OR LOWER(description) LIKE ?", searchTerm, searchTerm, searchTerm)
}
var total int64
if err := countQuery.Count(&total).Error; err != nil {
return nil, 0, "", err
@@ -422,7 +428,7 @@ func (h *TenantHandler) listTenantsByCursor(ctx context.Context, limit int, pare
func (h *TenantHandler) ExportTenantsCSV(c *fiber.Ctx) error {
parentID := strings.TrimSpace(c.Query("parentId"))
allTenants, _, err := h.Service.ListTenants(c.Context(), 10000, 0, "")
allTenants, _, err := h.Service.ListTenants(c.Context(), 10000, 0, "", "")
if err != nil {
return errorJSON(c, fiber.StatusInternalServerError, err.Error())
}
@@ -566,7 +572,7 @@ func (h *TenantHandler) ImportTenantsCSV(c *fiber.Ctx) error {
tenantIDBySlug := make(map[string]string)
if h.Service != nil {
if tenants, _, err := h.Service.ListTenants(c.Context(), 10000, 0, ""); err == nil {
if tenants, _, err := h.Service.ListTenants(c.Context(), 10000, 0, "", ""); err == nil {
for _, tenant := range tenants {
tenantIDBySlug[strings.ToLower(tenant.Slug)] = tenant.ID
}
@@ -2336,7 +2342,7 @@ func (h *TenantHandler) GetOrgContext(c *fiber.Ctx) error {
return errorJSON(c, fiber.StatusServiceUnavailable, "tenant service is not configured")
}
allTenants, _, err := h.Service.ListTenants(c.Context(), 10000, 0, "")
allTenants, _, err := h.Service.ListTenants(c.Context(), 10000, 0, "", "")
if err != nil {
return errorJSON(c, fiber.StatusInternalServerError, err.Error())
}
@@ -2410,7 +2416,7 @@ func (h *TenantHandler) loadOrgContextMembers(ctx context.Context, tenantIDs, te
if err != nil {
return nil, err
}
usersByAppointment, _, err := h.UserRepo.List(ctx, 0, 10000, "", "")
usersByAppointment, _, _, err := h.UserRepo.List(ctx, 0, 10000, "", "", "")
if err != nil {
return nil, err
}
@@ -2741,7 +2747,7 @@ func (h *TenantHandler) GetPublicOrgChart(c *fiber.Ctx) error {
return errorJSON(c, fiber.StatusUnauthorized, err.Error())
}
allTenants, _, err := h.Service.ListTenants(c.Context(), 10000, 0, "")
allTenants, _, err := h.Service.ListTenants(c.Context(), 10000, 0, "", "")
if err != nil {
return errorJSON(c, fiber.StatusInternalServerError, err.Error())
}

View File

@@ -486,7 +486,7 @@ func (h *UserHandler) ListUsers(c *fiber.Ctx) error {
// Expand manageableSlugs to the entire tenant tree (root + all descendants)
if h.TenantService != nil && len(baseTenantIDs) > 0 {
allTenants, _, err := h.TenantService.ListTenants(c.Context(), 10000, 0, "")
allTenants, _, err := h.TenantService.ListTenants(c.Context(), 10000, 0, "", "")
if err == nil {
parentMap := make(map[string]string)
for _, t := range allTenants {
@@ -1614,7 +1614,7 @@ func (h *UserHandler) ExportUsersCSV(c *fiber.Ctx) error {
}
// 1. Fetch Users using Repo for efficiency
users, _, err := h.UserRepo.List(c.Context(), 0, 10000, search, tenantSlug)
users, _, _, err := h.UserRepo.List(c.Context(), 0, 10000, search, tenantSlug, "")
if err != nil {
return errorJSON(c, fiber.StatusInternalServerError, "failed to fetch users for export")
}