mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2025-12-20 20:29:06 +00:00
Improvements based on feedback
This commit is contained in:
@@ -1,9 +1,9 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import React, { useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
import { GetTranscript } from "../../api";
|
import { GetTranscript } from "../../api";
|
||||||
import Pagination from "./pagination";
|
import Pagination from "./pagination";
|
||||||
import Link from "next/link";
|
import NextLink from "next/link";
|
||||||
import { FaGear } from "react-icons/fa6";
|
import { FaGear } from "react-icons/fa6";
|
||||||
import { FaCheck, FaTrash, FaStar, FaMicrophone } from "react-icons/fa";
|
import { FaCheck, FaTrash, FaStar, FaMicrophone } from "react-icons/fa";
|
||||||
import { MdError } from "react-icons/md";
|
import { MdError } from "react-icons/md";
|
||||||
@@ -11,12 +11,14 @@ import useTranscriptList from "../transcripts/useTranscriptList";
|
|||||||
import { formatTime } from "../../lib/time";
|
import { formatTime } from "../../lib/time";
|
||||||
import useApi from "../../lib/useApi";
|
import useApi from "../../lib/useApi";
|
||||||
import { useError } from "../../(errors)/errorContext";
|
import { useError } from "../../(errors)/errorContext";
|
||||||
|
import { FaEllipsisVertical } from "react-icons/fa6";
|
||||||
import {
|
import {
|
||||||
Flex,
|
Flex,
|
||||||
Spinner,
|
Spinner,
|
||||||
Heading,
|
Heading,
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
|
Link,
|
||||||
CardBody,
|
CardBody,
|
||||||
CardFooter,
|
CardFooter,
|
||||||
Stack,
|
Stack,
|
||||||
@@ -33,8 +35,22 @@ import {
|
|||||||
PopoverBody,
|
PopoverBody,
|
||||||
PopoverFooter,
|
PopoverFooter,
|
||||||
IconButton,
|
IconButton,
|
||||||
|
Spacer,
|
||||||
|
Menu,
|
||||||
|
MenuButton,
|
||||||
|
MenuItem,
|
||||||
|
MenuList,
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogOverlay,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogBody,
|
||||||
|
AlertDialogFooter,
|
||||||
|
keyframes,
|
||||||
|
Tooltip,
|
||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import { PlusSquareIcon } from "@chakra-ui/icons";
|
import { PlusSquareIcon } from "@chakra-ui/icons";
|
||||||
|
import { ExpandableText } from "../../lib/expandableText";
|
||||||
// import { useFiefUserinfo } from "@fief/fief/nextjs/react";
|
// import { useFiefUserinfo } from "@fief/fief/nextjs/react";
|
||||||
|
|
||||||
export default function TranscriptBrowser() {
|
export default function TranscriptBrowser() {
|
||||||
@@ -43,11 +59,19 @@ export default function TranscriptBrowser() {
|
|||||||
const [deletionLoading, setDeletionLoading] = useState(false);
|
const [deletionLoading, setDeletionLoading] = useState(false);
|
||||||
const api = useApi();
|
const api = useApi();
|
||||||
const { setError } = useError();
|
const { setError } = useError();
|
||||||
|
const cancelRef = React.useRef(null);
|
||||||
|
const [transcriptToDeleteId, setTranscriptToDeleteId] =
|
||||||
|
React.useState<string>();
|
||||||
|
const [deletedItemIds, setDeletedItemIds] = React.useState<string[]>();
|
||||||
|
|
||||||
// Todo: fief add name field to userinfo
|
// Todo: fief add name field to userinfo
|
||||||
// const user = useFiefUserinfo();
|
// const user = useFiefUserinfo();
|
||||||
// console.log(user);
|
// console.log(user);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setDeletedItemIds([]);
|
||||||
|
}, [page, response]);
|
||||||
|
|
||||||
if (loading && !response)
|
if (loading && !response)
|
||||||
return (
|
return (
|
||||||
<Flex flexDir="column" align="center" justify="center" h="100%">
|
<Flex flexDir="column" align="center" justify="center" h="100%">
|
||||||
@@ -67,6 +91,7 @@ export default function TranscriptBrowser() {
|
|||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
const onCloseDeletion = () => setTranscriptToDeleteId(undefined);
|
||||||
|
|
||||||
const handleDeleteTranscript = (transcriptToDeleteId) => (e) => {
|
const handleDeleteTranscript = (transcriptToDeleteId) => (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -77,6 +102,11 @@ export default function TranscriptBrowser() {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
setDeletionLoading(false);
|
setDeletionLoading(false);
|
||||||
refetch();
|
refetch();
|
||||||
|
onCloseDeletion();
|
||||||
|
setDeletedItemIds((deletedItemIds) => [
|
||||||
|
deletedItemIds,
|
||||||
|
...transcriptToDeleteId,
|
||||||
|
]);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
setDeletionLoading(false);
|
setDeletionLoading(false);
|
||||||
@@ -94,23 +124,27 @@ export default function TranscriptBrowser() {
|
|||||||
overflowY="scroll"
|
overflowY="scroll"
|
||||||
maxH="100%"
|
maxH="100%"
|
||||||
>
|
>
|
||||||
<Flex flexDir="row" justify="space-between" align="center">
|
<Flex
|
||||||
|
flexDir="row"
|
||||||
|
justify="flex-end"
|
||||||
|
align="center"
|
||||||
|
flexWrap={"wrap-reverse"}
|
||||||
|
>
|
||||||
{/* <Heading>{user?.fields?.name}'s Meetings</Heading> */}
|
{/* <Heading>{user?.fields?.name}'s Meetings</Heading> */}
|
||||||
<Heading>Your Meetings</Heading>
|
<Heading>Your Meetings</Heading>
|
||||||
<Flex flexDir="row" align="center">
|
{loading || (deletionLoading && <Spinner></Spinner>)}
|
||||||
{loading || (deletionLoading && <Spinner></Spinner>)}
|
|
||||||
|
|
||||||
<Pagination
|
<Spacer />
|
||||||
page={page}
|
<Pagination
|
||||||
setPage={setPage}
|
page={page}
|
||||||
total={response?.total || 0}
|
setPage={setPage}
|
||||||
size={response?.size || 0}
|
total={response?.total || 0}
|
||||||
/>
|
size={response?.size || 0}
|
||||||
|
/>
|
||||||
|
|
||||||
<Button colorScheme="blue" rightIcon={<PlusSquareIcon />}>
|
<Button colorScheme="blue" rightIcon={<PlusSquareIcon />}>
|
||||||
New Meeting
|
New Meeting
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Grid
|
<Grid
|
||||||
@@ -127,73 +161,127 @@ export default function TranscriptBrowser() {
|
|||||||
overflowY={"scroll"}
|
overflowY={"scroll"}
|
||||||
mb="4"
|
mb="4"
|
||||||
>
|
>
|
||||||
{response?.items.map((item: GetTranscript) => (
|
{response?.items
|
||||||
<Card key={item.id} border="gray.light" variant="outline">
|
.filter((i) => !deletedItemIds?.includes(i.id))
|
||||||
<CardBody as={Link} href={`/transcripts/${item.id}`}>
|
.map((item: GetTranscript) => (
|
||||||
<Heading size="md">
|
<Card key={item.id} border="gray.light" variant="outline">
|
||||||
{item.title || item.name || "Unamed Transcript"}
|
<CardBody>
|
||||||
|
<Flex align={"center"} ml="-6px">
|
||||||
|
{item.status == "ended" && (
|
||||||
|
<Tooltip label="Processing done">
|
||||||
|
<span>
|
||||||
|
<Icon color="green" as={FaCheck} mr="2" />
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
{item.status == "error" && (
|
||||||
|
<Tooltip label="Processing error">
|
||||||
|
<span>
|
||||||
|
<Icon color="red.primary" as={MdError} mr="2" />
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
{item.status == "idle" && (
|
||||||
|
<Tooltip label="New meeting, no recording">
|
||||||
|
<span>
|
||||||
|
<Icon color="yellow.500" as={FaStar} mr="2" />
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
{item.status == "processing" && (
|
||||||
|
<Tooltip label="Processing in progress">
|
||||||
|
<span>
|
||||||
|
<Icon
|
||||||
|
color="grey.primary"
|
||||||
|
as={FaGear}
|
||||||
|
mr="2"
|
||||||
|
transition={"all 2s ease"}
|
||||||
|
transform={"rotate(0deg)"}
|
||||||
|
_hover={{ transform: "rotate(360deg)" }}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
{item.status == "recording" && (
|
||||||
|
<Tooltip label="Recording in progress">
|
||||||
|
<span>
|
||||||
|
<Icon color="blue.primary" as={FaMicrophone} mr="2" />
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
<Heading size="md">
|
||||||
|
<Link
|
||||||
|
as={NextLink}
|
||||||
|
href={`/transcripts/${item.id}`}
|
||||||
|
noOfLines={2}
|
||||||
|
>
|
||||||
|
{item.title || item.name || "Unamed Transcript"}
|
||||||
|
</Link>
|
||||||
|
</Heading>
|
||||||
|
|
||||||
{item.status == "ended" && (
|
<Spacer />
|
||||||
<Icon color="green" as={FaCheck} ml="2" />
|
<Menu closeOnSelect={false}>
|
||||||
)}
|
<MenuButton
|
||||||
{item.status == "error" && (
|
as={IconButton}
|
||||||
<Icon color="red.primary" as={MdError} ml="2" />
|
icon={<FaEllipsisVertical />}
|
||||||
)}
|
aria-label="actions"
|
||||||
{item.status == "idle" && (
|
/>
|
||||||
<Icon color="yellow.500" as={FaStar} ml="2" />
|
<MenuList>
|
||||||
)}
|
<MenuItem
|
||||||
{item.status == "processing" && (
|
|
||||||
<Icon color="grey.primary" as={FaGear} ml="2" />
|
|
||||||
)}
|
|
||||||
{item.status == "recording" && (
|
|
||||||
<Icon color="blue.primary" as={FaMicrophone} ml="2" />
|
|
||||||
)}
|
|
||||||
</Heading>
|
|
||||||
<Stack mt="6" spacing="3">
|
|
||||||
<Text fontSize="small">
|
|
||||||
{new Date(item.created_at).toLocaleString("en-US")}
|
|
||||||
{"\u00A0"}-{"\u00A0"}
|
|
||||||
{formatTime(Math.floor(item.duration / 1000))}
|
|
||||||
</Text>
|
|
||||||
<Text>{item.short_summary}</Text>
|
|
||||||
</Stack>
|
|
||||||
</CardBody>
|
|
||||||
|
|
||||||
{item.status !== "ended" && (
|
|
||||||
<>
|
|
||||||
<Divider />
|
|
||||||
<CardFooter>
|
|
||||||
<Popover>
|
|
||||||
<PopoverTrigger>
|
|
||||||
<IconButton
|
|
||||||
colorScheme="red"
|
|
||||||
disabled={deletionLoading}
|
disabled={deletionLoading}
|
||||||
icon={<FaTrash />}
|
onClick={() => setTranscriptToDeleteId(item.id)}
|
||||||
aria-label="Delete"
|
icon={<FaTrash color={"red.500"} />}
|
||||||
/>
|
>
|
||||||
</PopoverTrigger>
|
Delete
|
||||||
<PopoverContent>
|
</MenuItem>
|
||||||
<PopoverArrow />
|
<AlertDialog
|
||||||
<PopoverCloseButton />
|
isOpen={transcriptToDeleteId === item.id}
|
||||||
<PopoverHeader>
|
leastDestructiveRef={cancelRef}
|
||||||
Are you sure you want to delete {item.title} ?
|
onClose={onCloseDeletion}
|
||||||
</PopoverHeader>
|
>
|
||||||
<PopoverBody>This action is not reversible.</PopoverBody>
|
<AlertDialogOverlay>
|
||||||
<PopoverFooter>
|
<AlertDialogContent>
|
||||||
<Button
|
<AlertDialogHeader fontSize="lg" fontWeight="bold">
|
||||||
colorScheme="red"
|
Delete{" "}
|
||||||
onClick={handleDeleteTranscript(item.id)}
|
{item.title || item.name || "Unamed Transcript"}
|
||||||
>
|
</AlertDialogHeader>
|
||||||
Confirm
|
|
||||||
</Button>
|
<AlertDialogBody>
|
||||||
</PopoverFooter>
|
Are you sure? You can't undo this action
|
||||||
</PopoverContent>
|
afterwards.
|
||||||
</Popover>
|
</AlertDialogBody>
|
||||||
</CardFooter>
|
|
||||||
</>
|
<AlertDialogFooter>
|
||||||
)}
|
<Button ref={cancelRef} onClick={onCloseDeletion}>
|
||||||
</Card>
|
Cancel
|
||||||
))}
|
</Button>
|
||||||
|
<Button
|
||||||
|
colorScheme="red"
|
||||||
|
onClick={handleDeleteTranscript(item.id)}
|
||||||
|
ml={3}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialogOverlay>
|
||||||
|
</AlertDialog>
|
||||||
|
</MenuList>
|
||||||
|
</Menu>
|
||||||
|
</Flex>
|
||||||
|
<Stack mt="6" spacing="3">
|
||||||
|
<Text fontSize="small">
|
||||||
|
{new Date(item.created_at).toLocaleString("en-US")}
|
||||||
|
{"\u00A0"}-{"\u00A0"}
|
||||||
|
{formatTime(Math.floor(item.duration / 1000))}
|
||||||
|
</Text>
|
||||||
|
<ExpandableText noOfLines={5}>
|
||||||
|
{item.short_summary}
|
||||||
|
</ExpandableText>
|
||||||
|
</Stack>
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
</Grid>
|
</Grid>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
|||||||
46
www/app/lib/expandableText.tsx
Normal file
46
www/app/lib/expandableText.tsx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import type { BoxProps } from "@chakra-ui/react";
|
||||||
|
import { Box, Button, Text } from "@chakra-ui/react";
|
||||||
|
import React, { forwardRef, useState } from "react";
|
||||||
|
|
||||||
|
interface Props extends BoxProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
noOfLines: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ExpandableText = forwardRef<HTMLDivElement, Props>(
|
||||||
|
({ children, noOfLines, ...rest }, ref) => {
|
||||||
|
const [expandedCount, setExpandedCount] = useState<number | undefined>(
|
||||||
|
noOfLines,
|
||||||
|
);
|
||||||
|
const [isClicked, setIsClicked] = useState(false);
|
||||||
|
const handleToggle = () => {
|
||||||
|
setIsClicked(true);
|
||||||
|
setExpandedCount(expandedCount ? undefined : noOfLines);
|
||||||
|
};
|
||||||
|
|
||||||
|
const inputRef = React.useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
const isTextClamped =
|
||||||
|
(inputRef.current?.scrollHeight as number) >
|
||||||
|
(inputRef.current?.clientHeight as number) || isClicked;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box ref={ref} {...rest}>
|
||||||
|
<Box ref={inputRef} noOfLines={expandedCount}>
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
<Button
|
||||||
|
display={isTextClamped ? "block" : "none"}
|
||||||
|
size="sm"
|
||||||
|
variant="link"
|
||||||
|
onClick={handleToggle}
|
||||||
|
mt={2}
|
||||||
|
>
|
||||||
|
<Text>{!expandedCount ? "Show less" : "Read more"}</Text>
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
ExpandableText.displayName = "ExpandableText";
|
||||||
Reference in New Issue
Block a user