Skip to content

Commit 44a0715

Browse files
committed
feat: Transcription Management page
Transcription Management page to handle the transcription process. The page features two tables: one for the transcription backlog and another for the transcription queue. Items from the backlog can be added to the queue for transcription. After transcription starts, it provides visibility on the state of active transcription jobs. Note: There is currently no visibility on past transcription jobs.
1 parent 0aff0e1 commit 44a0715

File tree

12 files changed

+581
-3
lines changed

12 files changed

+581
-3
lines changed

src/components/navbar/Menu.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { CgTranscript } from "react-icons/cg";
2424
import { FaGithub } from "react-icons/fa";
2525
import { FiUser, FiUsers } from "react-icons/fi";
2626
import { HiOutlineBookOpen, HiOutlineSwitchHorizontal } from "react-icons/hi";
27+
import { MdOutlineSource } from "react-icons/md";
2728
import MenuNav from "./MenuNav";
2829
import AdminMenu from "./AdminMenu";
2930
import { useHasPermission } from "@/hooks/useHasPermissions";
@@ -34,6 +35,7 @@ const Menu = () => {
3435
const canAccessAdminNav = useHasPermission("accessAdminNav");
3536
const canAccessTransactions = useHasPermission("accessTransactions");
3637
const canAccessUsers = useHasPermission("accessUsers");
38+
const canAccessTranscription = useHasPermission("accessTranscription");
3739
const router = useRouter();
3840
const currentRoute = router.asPath?.split("/")[1] ?? "";
3941
const fullCurrentRoute = router.asPath;
@@ -194,6 +196,15 @@ const Menu = () => {
194196
icon={FiUsers}
195197
/>
196198
)}
199+
{canAccessTranscription && (
200+
<MenuNav
201+
currentRoute={fullCurrentRoute}
202+
routeName={"Transcription"}
203+
routeLink={ROUTES_CONFIG.TRANSCRIPTION}
204+
handleClose={closeMenu}
205+
icon={MdOutlineSource}
206+
/>
207+
)}
197208
</Flex>
198209
</AdminMenu>
199210
) : null}

src/components/tables/TitleWithTags.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ type TitleWithTagsProps = {
2727
categories: string | string[];
2828
loc?: string;
2929
transcriptUrl?: string | null;
30+
url?: string;
3031
allTags: string[];
3132
length: number;
3233
shouldSlice?: boolean;
@@ -37,6 +38,7 @@ const TitleWithTags = ({
3738
categories,
3839
loc,
3940
transcriptUrl = null,
41+
url,
4042
id,
4143
length,
4244
shouldSlice = true,
@@ -50,13 +52,14 @@ const TitleWithTags = ({
5052
);
5153
const tags = shouldSlice ? allTags.slice(0, 1) : allTags;
5254
const transcript = resolveTranscriptUrl(transcriptUrl);
55+
const hyperlink = transcript?.url || url;
5356
return (
5457
<Td width="40%">
5558
<Flex gap={2} flexDir="column">
5659
<Box>
57-
{!transcript && <Text>{title}</Text>}
58-
{transcript && (
59-
<Link target="_blank" rel="noopener" href={transcript.url}>
60+
{!hyperlink && <Text>{title}</Text>}
61+
{hyperlink && (
62+
<Link target="_blank" rel="noopener" href={hyperlink}>
6063
<Text>{title}</Text>
6164
</Link>
6265
)}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import {
2+
Button,
3+
Flex,
4+
Menu,
5+
MenuButton,
6+
MenuItem,
7+
MenuList,
8+
Text,
9+
} from "@chakra-ui/react";
10+
import { MdArrowDropDown } from "react-icons/md";
11+
12+
const SelectSourceMenu = ({
13+
isLoading,
14+
sources,
15+
onSelection: selectSource,
16+
}: {
17+
isLoading?: boolean;
18+
sources?: string[];
19+
onSelection: (source: string) => void;
20+
}) => (
21+
<Menu>
22+
<MenuButton
23+
as={Button}
24+
isLoading={isLoading}
25+
aria-label="select source"
26+
colorScheme="orange"
27+
>
28+
<Flex gap={2} alignItems={"center"}>
29+
<Text> Select Source </Text>
30+
<MdArrowDropDown size={20} />
31+
</Flex>
32+
</MenuButton>
33+
<MenuList color="black">
34+
{sources &&
35+
sources.map((source) => (
36+
<MenuItem
37+
key={source}
38+
onClick={() => {
39+
selectSource(source);
40+
}}
41+
>
42+
{source}
43+
</MenuItem>
44+
))}
45+
</MenuList>
46+
</Menu>
47+
);
48+
49+
export default SelectSourceMenu;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as SelectSourceMenu } from "./SelectSourceMenu";

src/config/permissions.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"accessUsers": true,
55
"resetReviews": true,
66
"archiveTranscripts": true,
7+
"accessTranscription": true,
78
"accessTransactions": true,
89
"accessAdminNav": true,
910
"submitToOwnRepo": true
@@ -13,6 +14,7 @@
1314
"accessUsers": false,
1415
"resetReviews": false,
1516
"archiveTranscripts": true,
17+
"accessTranscription": true,
1618
"accessTransactions": false,
1719
"accessAdminNav": true,
1820
"submitToOwnRepo": false
@@ -21,6 +23,8 @@
2123
"accessReviews": false,
2224
"accessUsers": false,
2325
"resetReviews": false,
26+
"archiveTranscripts": false,
27+
"accessTranscription": false,
2428
"accessTransactions": false,
2529
"accessAdminNav": false,
2630
"submitToOwnRepo": false

src/config/ui-config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export const ROUTES_CONFIG = {
1010
ALL_REVIEWS: "admin/reviews?status=active",
1111
REVIEWS: "reviews",
1212
USERS: "admin/users",
13+
TRANSCRIPTION: "admin/transcription",
1314
};
1415

1516
export const UI_CONFIG = {

src/hooks/useHasPermissions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import permissions from "../config/permissions.json";
55
type Permissions = {
66
accessReviews: boolean;
77
accessUsers: boolean;
8+
accessTranscription: boolean;
89
resetReviews: boolean;
910
archiveTranscripts: boolean;
1011
accessTransactions: boolean;

src/pages/admin/transcription.tsx

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import { useState } from "react";
2+
import { Button, Flex, Heading, Text } from "@chakra-ui/react";
3+
4+
import { useHasPermission } from "@/hooks/useHasPermissions";
5+
import AuthStatus from "@/components/transcript/AuthStatus";
6+
import {
7+
useTranscriptionQueue,
8+
useTranscriptionBacklog,
9+
} from "@/services/api/admin";
10+
import { SelectSourceMenu } from "@/components/tables/components";
11+
12+
import BaseTable from "@/components/tables/BaseTable";
13+
import { convertStringToArray } from "@/utils";
14+
import TitleWithTags from "@/components/tables/TitleWithTags";
15+
import { TableStructure } from "@/components/tables/types";
16+
import { TranscriptMetadata, TranscriptionQueueItem } from "../../../types";
17+
18+
const Sources = () => {
19+
const [selectedSource, setSelectedSource] = useState<string>("all");
20+
const [removeFromQueueSelection, setRemoveFromQueueSelection] = useState<
21+
string[]
22+
>([]);
23+
const [addToQueueSelection, setAddToQueueSelection] = useState<string[]>([]);
24+
const canAccessTranscription = useHasPermission("accessTranscription");
25+
26+
const { transcriptionBacklog, sources } =
27+
useTranscriptionBacklog(selectedSource);
28+
const {
29+
transcriptionQueue,
30+
remainingBacklog,
31+
addToQueue,
32+
removeFromQueue,
33+
startTranscription,
34+
isTranscribing,
35+
refetch,
36+
} = useTranscriptionQueue(transcriptionBacklog.data);
37+
38+
const handleAddToQueue = async () => {
39+
await addToQueue.mutateAsync(addToQueueSelection);
40+
setAddToQueueSelection([]);
41+
};
42+
43+
const handleRemoveFromQueue = async () => {
44+
await removeFromQueue.mutateAsync(removeFromQueueSelection);
45+
setRemoveFromQueueSelection([]);
46+
};
47+
48+
const tableStructure = [
49+
{
50+
name: "Title",
51+
type: "default",
52+
modifier: () => null,
53+
component: (data) => {
54+
const allTags = convertStringToArray(data.tags);
55+
return (
56+
<TitleWithTags
57+
title={data.title}
58+
allTags={allTags}
59+
categories={[]}
60+
loc={data.loc}
61+
url={data.media}
62+
id={0}
63+
length={allTags.length}
64+
shouldSlice={false}
65+
/>
66+
);
67+
},
68+
},
69+
{
70+
name: "speakers",
71+
type: "text-long",
72+
modifier: (data) => data.speakers.join(", "),
73+
},
74+
{ name: "Publish Date", type: "text-short", modifier: (data) => data.date },
75+
] satisfies TableStructure<TranscriptMetadata>[];
76+
77+
const transcriptionQueueTableStructure = [
78+
...tableStructure,
79+
{
80+
name: "status",
81+
type: "text-short",
82+
modifier: (data) => data.status,
83+
},
84+
] satisfies TableStructure<TranscriptionQueueItem>[];
85+
86+
if (!canAccessTranscription) {
87+
return (
88+
<AuthStatus
89+
title="Unauthorized"
90+
message="You are not authorized to access this page"
91+
/>
92+
);
93+
}
94+
95+
return (
96+
<>
97+
<Flex flexDir="column">
98+
<Heading size={"md"} mb={10}>
99+
{`Transcription Management`}
100+
</Heading>
101+
<BaseTable
102+
data={transcriptionQueue.data}
103+
emptyView={
104+
<Flex w="full" justifyContent="center" alignItems="center" gap={2}>
105+
<Text>Transcription queue is empty</Text>
106+
</Flex>
107+
}
108+
isLoading={transcriptionQueue.isLoading}
109+
isError={transcriptionQueue.isError}
110+
tableStructure={transcriptionQueueTableStructure}
111+
tableHeaderComponent={
112+
<Heading size="sm" mb={1}>
113+
Transcription Queue
114+
</Heading>
115+
}
116+
enableCheckboxes={!isTranscribing}
117+
selectedRowIds={removeFromQueueSelection}
118+
onSelectedRowIdsChange={setRemoveFromQueueSelection}
119+
getRowId={(row) => row.media}
120+
refetch={refetch}
121+
actionItems={
122+
<>
123+
{!isTranscribing && (
124+
<Button
125+
isDisabled={removeFromQueueSelection.length == 0}
126+
isLoading={removeFromQueue.isLoading}
127+
onClick={handleRemoveFromQueue}
128+
>
129+
Remove from Queue
130+
</Button>
131+
)}
132+
<Button
133+
isDisabled={
134+
transcriptionBacklog.isLoading ||
135+
transcriptionQueue.data?.length == 0 ||
136+
isTranscribing
137+
}
138+
onClick={() => startTranscription.mutate()}
139+
>
140+
{`${
141+
isTranscribing
142+
? "Transcription in Progress..."
143+
: "Start Transcription"
144+
}`}
145+
</Button>
146+
</>
147+
}
148+
/>
149+
<BaseTable
150+
data={remainingBacklog}
151+
emptyView={
152+
<Flex w="full" justifyContent="center" alignItems="center" gap={2}>
153+
<Text>
154+
Transcription backlog is empty for the selected source
155+
</Text>
156+
</Flex>
157+
}
158+
isLoading={transcriptionBacklog.isLoading}
159+
isError={transcriptionBacklog.isError}
160+
tableStructure={tableStructure}
161+
tableHeaderComponent={
162+
<Heading size="sm" mb={1}>
163+
{`Transcription Backlog (${selectedSource})`}
164+
</Heading>
165+
}
166+
enableCheckboxes
167+
selectedRowIds={addToQueueSelection}
168+
onSelectedRowIdsChange={setAddToQueueSelection}
169+
getRowId={(row) => row.media}
170+
actionItems={
171+
<>
172+
<Button
173+
isLoading={addToQueue.isLoading}
174+
isDisabled={addToQueueSelection.length == 0}
175+
onClick={handleAddToQueue}
176+
>
177+
Add to Queue
178+
</Button>
179+
<SelectSourceMenu
180+
sources={sources.data}
181+
isLoading={sources.isLoading}
182+
onSelection={(source: string) => setSelectedSource(source)}
183+
/>
184+
</>
185+
}
186+
/>
187+
</Flex>
188+
</>
189+
);
190+
};
191+
192+
export default Sources;

src/services/api/admin/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
export * from "./useTransaction";
22
export * from "./useReviews";
33
export * from "./useUsers";
4+
export * from "./useTranscriptionQueue";
5+
export * from "./useTranscriptionBacklog";

0 commit comments

Comments
 (0)