mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-07-10 15:00:15 +00:00
Add admin system logs UI and API support
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.
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* 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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user