feat(frontend): show transcription + keyword search + channel/date filters + speaker leaderboard

- cards now render transcription snippet (expandable); no-transcript placeholder
- toolbar: search box (transcription+username ILIKE), speaker filter (existing),
  channel filter, start/end date range, RESET
- new DECK/LEADERBOARD tabs: leaderboard = per-speaker clips, est duration,
  transcribed words (useRecordingsSummary → recordings.summary); click a row
  jumps the deck to that speaker's clips
- hooks: filter-driven SWR cache key (fields serialized), representative key
  matcher for delete/WS-sync across all filter views; useRecordingsSummary hook
- recordingsApi.list generalized to RecordingListParams (q/startDate/endDate)
This commit is contained in:
asepharyana committed 2026-08-30 13:35:33 +07:00
1 parent 22c09ecd28
commit e12b1302ff
5 files changed
+609 -273

No files matched your search

+25 -11
View File
@@ -1,20 +1,34 @@
import { orpc } from "@/lib/orpc/client";
import type { PaginatedRecordings } from "@/lib/types";
import type { PaginatedRecordings, SpeakerSummary } from "@/lib/types";
export interface RecordingListParams {
limit?: number;
channelId?: string;
userId?: string;
cursor?: string;
/** keyword search against transcription + username */
q?: string;
/** created_at lower bound (epoch ms) */
startDate?: number;
/** created_at upper bound (epoch ms) */
endDate?: number;
}
export const recordingsApi = {
list: (
limit?: number,
channelId?: string,
userId?: string,
cursor?: string,
) =>
list: (params: RecordingListParams = {}) =>
orpc.recordings.list({
limit,
channelId,
userId,
cursor,
limit: params.limit,
channelId: params.channelId,
userId: params.userId,
cursor: params.cursor,
q: params.q,
startDate: params.startDate,
endDate: params.endDate,
}) as unknown as Promise<PaginatedRecordings>,
delete: (id: string) =>
orpc.recordings.delete({ id }) as unknown as Promise<{ ok: boolean }>,
summary: () =>
orpc.recordings.summary({}) as unknown as Promise<SpeakerSummary[]>,
};
@@ -13,6 +13,7 @@ export interface VoiceRecording {
upload_error?: string | null;
created_at: number;
uploaded_at?: number | null;
transcription?: string | null;
}
export interface PaginatedRecordings {
@@ -20,3 +21,15 @@ export interface PaginatedRecordings {
nextCursor: string | null;
hasMore: boolean;
}
/** recordings.summary — per-speaker leaderboard. */
export interface SpeakerSummary {
user_id: string;
username: string;
avatar_url: string | null;
clips: number;
est_duration_s: number;
words: number;
transcribed: number;
last_at: number;
}