improve list view

This commit is contained in:
Sara
2023-12-06 17:12:10 +01:00
parent 6fe61cd5e3
commit 5ef7dc0a76
4 changed files with 180 additions and 71 deletions

View File

@@ -6,81 +6,176 @@ import { Title } from "../../lib/textComponents";
import Pagination from "./pagination";
import Link from "next/link";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faGear } from "@fortawesome/free-solid-svg-icons";
import {
faCheck,
faGear,
faMicrophone,
faStar,
faTrash,
faX,
} from "@fortawesome/free-solid-svg-icons";
import useTranscriptList from "../transcripts/useTranscriptList";
import { formatTime } from "../../lib/time";
import getApi from "../../api";
import { useError } from "../../(errors)/errorContext";
export default function TranscriptBrowser() {
const [page, setPage] = useState<number>(1);
const { loading, response } = useTranscriptList(page);
const { loading, response, refetch } = useTranscriptList(page);
const [transcriptToDeleteId, setTranscriptToDeleteId] = useState("");
const [deletionLoading, setDeletionLoading] = useState(false);
const [deletedItems, setDeletedItems] = useState<string[]>([]);
const api = getApi();
const { setError } = useError();
return (
<div className="grid grid-rows-layout-topbar gap-2 lg:gap-4 h-full max-h-full">
<div className="flex flex-row gap-2 items-center">
<Title className="mb-5 mt-5 flex-1">Past transcripts</Title>
<Pagination
page={page}
setPage={setPage}
total={response?.total || 0}
size={response?.size || 0}
if (loading && !response)
return (
<div className="h-full flex flex-col items-center justify-center">
<FontAwesomeIcon
icon={faGear}
className="animate-spin-slow h-14 w-14 md:h-20 md:w-20 text-gray-400"
/>
</div>
);
{loading && (
<div className="full-screen flex flex-col items-center justify-center">
<FontAwesomeIcon
icon={faGear}
className="animate-spin-slow h-14 w-14 md:h-20 md:w-20"
if (!loading && !response)
return (
<div className="text-gray-500">
No transcripts found, but you can&nbsp;
<Link href="/transcripts/new" className="underline">
record a meeting
</Link>
&nbsp;to get started.
</div>
);
const handleDelete =
(id: string) => (e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
e.preventDefault();
setTranscriptToDeleteId(id);
};
const deleteTranscript = () => (e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
e.preventDefault();
if (!deletionLoading) {
api
?.v1TranscriptDelete({ transcriptId: transcriptToDeleteId })
.then(() => {
setDeletionLoading(false);
setDeletedItems([...deletedItems, transcriptToDeleteId]);
setTranscriptToDeleteId("");
refetch();
})
.catch((err) => {
setDeletionLoading(false);
setError(err, "There was an error deleting the transcript");
});
}
};
const cancelDelete = (e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
e.preventDefault();
setTranscriptToDeleteId("");
};
return (
<div className="w-full flex flex-col items-center justify-center flex-grow">
<div className="max-w-5xl h-full">
<div className="flex flex-row gap-2 items-center">
<Title className="mb-5 mt-5 flex-1">Past transcripts</Title>
<Pagination
page={page}
setPage={setPage}
total={response?.total || 0}
size={response?.size || 0}
/>
</div>
)}
{!loading && !response && (
<div className="text-gray-500">
No transcripts found, but you can&nbsp;
<Link href="/transcripts/new" className="underline">
record a meeting
</Link>
&nbsp;to get started.
</div>
)}
<div /** center and max 900px wide */ className="overflow-y-scroll">
<div className="grid grid-cols-1 gap-2 lg:gap-4 h-full mx-auto max-w-[900px]">
{response?.items.map((item: GetTranscript) => (
<div
key={item.id}
className="flex flex-col bg-blue-400/20 rounded-lg md:rounded-xl p-2 md:px-4"
>
<div className="flex flex-col">
<div className="flex flex-row gap-2 items-start">
<Link
href={`/transcripts/${item.id}`}
className="text-1xl flex-1 pl-0 hover:underline focus-within:underline underline-offset-2 decoration-[.5px] font-light px-2"
>
{item.title || item.name}
</Link>
<div className="grid grid-cols-1 gap-2 lg:gap-4 h-full">
{response?.items
.filter((item) => !deletedItems.includes(item.id))
.map((item: GetTranscript) => (
<Link
key={item.id}
href={`/transcripts/${item.id}`}
className="flex flex-col bg-blue-400/20 rounded-lg md:rounded-xl p-2 md:px-4"
>
<div className="flex flex-col">
<div className="flex flex-row gap-2 items-start">
<h2 className="text-1xl font-semibold flex-1">
{item.title || item.name}
</h2>
{item.locked ? (
<div className="inline-block bg-red-500 text-white px-2 py-1 rounded-full text-xs font-semibold">
Locked
</div>
) : (
<></>
)}
{item.locked && (
<div className="inline-block bg-red-500 text-white px-2 py-1 rounded-full text-xs font-semibold">
Locked
</div>
)}
{item.source_language ? (
<div className="inline-block bg-blue-500 text-white px-2 py-1 rounded-full text-xs font-semibold">
{item.source_language}
{item.status == "ended" && (
<FontAwesomeIcon
icon={faCheck}
className="mt-1 text-green-500"
/>
)}
{item.status == "error" && (
<FontAwesomeIcon
icon={faX}
className="mt-1 text-red-500"
/>
)}
{item.status == "idle" && (
<FontAwesomeIcon
icon={faStar}
className="mt-1 text-yellow-500"
/>
)}
{item.status == "processing" && (
<FontAwesomeIcon
icon={faGear}
className="mt-1 text-grey-500"
/>
)}
{item.status == "recording" && (
<FontAwesomeIcon
icon={faMicrophone}
className="mt-1 text-blue-500"
/>
)}
{item.sourceLanguage && (
<div className="inline-block bg-blue-500 text-white px-2 py-1 rounded-full text-xs font-semibold">
{item.sourceLanguage}
</div>
)}
</div>
<div className="flex flex-row gap-2 items-start">
<div className="text-xs text-gray-700 flex-1">
{new Date(item.createdAt).toLocaleDateString("en-US")}
{"\u00A0"}-{"\u00A0"}
{formatTime(Math.floor(item.duration))}
<div className="text-sm">{item.shortSummary}</div>
</div>
) : (
<></>
)}
{item.status !== "ended" && (
<button
className="self-end p-2"
disabled={deletionLoading}
onClick={handleDelete(item.id)}
>
<FontAwesomeIcon icon={faTrash}></FontAwesomeIcon>
</button>
)}
<dialog open={transcriptToDeleteId == item.id}>
<p>Are you sure you want to delete {item.title} ?</p>
<p>This action is not reversible.</p>
<button onClick={cancelDelete}>Cancel</button>
<button onClick={deleteTranscript()}>Confirm</button>
</dialog>
</div>
</div>
<div className="text-xs text-gray-700">
{new Date(item.created_at).toLocaleDateString("en-US")}
</div>
<div className="text-sm">{item.short_summary}</div>
</div>
</div>
))}
</Link>
))}
</div>
</div>
</div>

View File

@@ -1,3 +1,6 @@
import { faArrowLeft, faArrowRight } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
type PaginationProps = {
page: number;
setPage: (page: number) => void;
@@ -40,13 +43,13 @@ export default function Pagination(props: PaginationProps) {
return (
<div className="flex justify-center space-x-4 my-4">
<button
className={`w-10 h-10 rounded-full p-2 border border-gray-300 disabled:bg-white ${
className={`w-10 h-10 rounded-full p-2 ${
canGoPrevious ? "text-gray-500" : "text-gray-300"
}`}
onClick={() => handlePageChange(page - 1)}
disabled={!canGoPrevious}
>
<i className="fa fa-chevron-left">&lt;</i>
<FontAwesomeIcon icon={faArrowLeft} className="h-5 w-auto" />
</button>
{pageNumbers.map((pageNumber) => (
@@ -62,13 +65,13 @@ export default function Pagination(props: PaginationProps) {
))}
<button
className={`w-10 h-10 rounded-full p-2 border border-gray-300 disabled:bg-white ${
className={`w-10 h-10 rounded-full p-2 ${
canGoNext ? "text-gray-500" : "text-gray-300"
}`}
onClick={() => handlePageChange(page + 1)}
disabled={!canGoNext}
>
<i className="fa fa-chevron-right">&gt;</i>
<FontAwesomeIcon icon={faArrowRight} className="h-5 w-auto" />
</button>
</div>
);

View File

@@ -1,16 +1,17 @@
import { useState } from "react";
import { useEffect, useState } from "react";
import { useError } from "../../(errors)/errorContext";
import { GetTranscript, CreateTranscript } from "../../api";
import { CreateTranscript } from "../../api";
import useApi from "../../lib/useApi";
type UseTranscript = {
type UseCreateTranscript = {
transcript: GetTranscript | null;
loading: boolean;
error: Error | null;
create: (transcriptCreationDetails: CreateTranscript) => void;
};
const useCreateTranscript = (): UseTranscript => {
const useCreateTranscript = (): UseCreateTranscript => {
const [transcript, setTranscript] = useState<GetTranscript | null>(null);
const [loading, setLoading] = useState<boolean>(false);
const [error, setErrorState] = useState<Error | null>(null);

View File

@@ -7,6 +7,7 @@ type TranscriptList = {
response: Page_GetTranscript_ | null;
loading: boolean;
error: Error | null;
refetch: () => void;
};
//always protected
@@ -16,6 +17,15 @@ const useTranscriptList = (page: number): TranscriptList => {
const [error, setErrorState] = useState<Error | null>(null);
const { setError } = useError();
const api = useApi();
const [refetchCount, setRefetchCount] = useState(0);
const refetch = () => {
setRefetchCount(refetchCount + 1);
};
useEffect(() => {
setResponse(null);
}, [page]);
useEffect(() => {
if (!api) return;
@@ -32,9 +42,9 @@ const useTranscriptList = (page: number): TranscriptList => {
setError(err);
setErrorState(err);
});
}, [!api, page]);
}, [!api, page, refetchCount]);
return { response, loading, error };
return { response, loading, error, refetch };
};
export default useTranscriptList;