mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-07-10 06:50:13 +00:00
eef6ae3462
Introduce a complete admin System Logs feature: adds frontend components (filters, date picker, active filter chips, rows, detail panel, skeletons, pagination, toolbar, user typeahead, and styles) under src/app/admin/logs/components, plus hooks (useAutoRefreshControl, useLogsUrlState, useUserSearch) and types. Add constants for job labels and log filters, wire URL-driven filters/search/date-range/hasError/user/audiobookQuery with pause-on-interact behavior and page-size options. Update API route (/api/admin/logs) to support the expanded query params and exported where-builder. Update documentation (TABLEOFCONTENTS and admin-dashboard) and add/adjust tests for the new admin logs UI and API behavior.
89 lines
2.4 KiB
TypeScript
89 lines
2.4 KiB
TypeScript
/**
|
|
* Component: useUserSearch Hook (admin logs user typeahead)
|
|
* Documentation: documentation/admin-dashboard.md
|
|
*
|
|
* Fetch-once-and-cache user directory from /api/admin/users for the user
|
|
* typeahead in LogsFilters. SWR caches the response for the session so every
|
|
* keystroke filters in-memory — no per-keystroke network round-trip.
|
|
*
|
|
* Assumes installs have <500 users (Zach Resolution #3 — fine for self-hosted).
|
|
*/
|
|
|
|
'use client';
|
|
|
|
import { useCallback, useMemo } from 'react';
|
|
import useSWR from 'swr';
|
|
import { authenticatedFetcher } from '@/lib/utils/api';
|
|
|
|
const USERS_URL = '/api/admin/users';
|
|
const MAX_SUGGESTIONS = 10;
|
|
// One-time-per-session cache: dedupe identical fetches for an hour.
|
|
const DEDUPING_INTERVAL_MS = 60 * 60 * 1000;
|
|
|
|
export interface UserSearchUser {
|
|
id: string;
|
|
plexUsername: string;
|
|
role: string;
|
|
}
|
|
|
|
interface UsersApiResponse {
|
|
users: UserSearchUser[];
|
|
}
|
|
|
|
export interface UseUserSearchResult {
|
|
users: UserSearchUser[];
|
|
filterByQuery: (q: string) => UserSearchUser[];
|
|
/** Resolve a user by id — handy for chip label rendering. */
|
|
findUserById: (id: string | null | undefined) => UserSearchUser | undefined;
|
|
isLoading: boolean;
|
|
error: Error | null;
|
|
}
|
|
|
|
export function useUserSearch(): UseUserSearchResult {
|
|
const { data, error, isLoading } = useSWR<UsersApiResponse>(
|
|
USERS_URL,
|
|
authenticatedFetcher,
|
|
{
|
|
revalidateOnFocus: false,
|
|
revalidateIfStale: false,
|
|
revalidateOnReconnect: false,
|
|
dedupingInterval: DEDUPING_INTERVAL_MS,
|
|
}
|
|
);
|
|
|
|
const users = useMemo<UserSearchUser[]>(() => data?.users ?? [], [data]);
|
|
|
|
const filterByQuery = useCallback(
|
|
(q: string): UserSearchUser[] => {
|
|
if (users.length === 0) return [];
|
|
const trimmed = q.trim().toLowerCase();
|
|
if (!trimmed) return users.slice(0, MAX_SUGGESTIONS);
|
|
const out: UserSearchUser[] = [];
|
|
for (const u of users) {
|
|
if (u.plexUsername.toLowerCase().includes(trimmed)) {
|
|
out.push(u);
|
|
if (out.length >= MAX_SUGGESTIONS) break;
|
|
}
|
|
}
|
|
return out;
|
|
},
|
|
[users]
|
|
);
|
|
|
|
const findUserById = useCallback(
|
|
(id: string | null | undefined): UserSearchUser | undefined => {
|
|
if (!id) return undefined;
|
|
return users.find((u) => u.id === id);
|
|
},
|
|
[users]
|
|
);
|
|
|
|
return {
|
|
users,
|
|
filterByQuery,
|
|
findUserById,
|
|
isLoading,
|
|
error: (error as Error | null) ?? null,
|
|
};
|
|
}
|