mirror of
https://github.com/StanGirard/quivr.git
synced 2024-09-11 13:15:41 +03:00
Brain management 1 (#752)
* feat: add brain management button * feat: add brains list * feat: add brain search bar * feat: sort brain list by name * refactor: update brains management page structure * feat(BrainManagement): add new brain button * feat: update import links
This commit is contained in:
parent
914689957d
commit
cf376fb59f
@ -0,0 +1,44 @@
|
||||
import Link from "next/link";
|
||||
import { FaBrain } from "react-icons/fa";
|
||||
|
||||
import { MinimalBrainForUser } from "@/lib/context/BrainProvider/types";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { useBrainListItem } from "./hooks/useBrainListItem";
|
||||
|
||||
interface BrainsListItemProps {
|
||||
brain: MinimalBrainForUser;
|
||||
}
|
||||
|
||||
export const BrainListItem = ({ brain }: BrainsListItemProps): JSX.Element => {
|
||||
const { selected } = useBrainListItem(brain);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"w-full min-w-48 border-b border-black/10 dark:border-white/25 last:border-none relative group flex overflow-x-hidden hover:bg-gray-100 dark:hover:bg-gray-800",
|
||||
selected
|
||||
? "bg-gray-100 dark:bg-gray-800 text-primary dark:text-white"
|
||||
: ""
|
||||
)}
|
||||
data-testid="brains
|
||||
-list-item"
|
||||
>
|
||||
<Link
|
||||
className="flex flex-col flex-1 min-w-0 p-4"
|
||||
href={`/brains-management/${brain.id}`}
|
||||
key={brain.id}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<FaBrain className="text-xl" />
|
||||
<p>{brain.name}</p>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<div
|
||||
aria-hidden
|
||||
className="not-sr-only absolute left-1/2 top-0 bottom-0 right-0 bg-gradient-to-r from-transparent to-white dark:to-black pointer-events-none"
|
||||
></div>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -0,0 +1,13 @@
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
import { MinimalBrainForUser } from "@/lib/context/BrainProvider/types";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export const useBrainListItem = (brain: MinimalBrainForUser) => {
|
||||
const pathname = usePathname()?.split("/").at(-1);
|
||||
const selected = brain.id === pathname;
|
||||
|
||||
return {
|
||||
selected,
|
||||
};
|
||||
};
|
@ -0,0 +1 @@
|
||||
export * from './BrainListItem'
|
@ -0,0 +1,24 @@
|
||||
import Field from "@/lib/components/ui/Field";
|
||||
|
||||
type BrainSearchBarProps = {
|
||||
searchQuery: string;
|
||||
setSearchQuery: (searchQuery: string) => void;
|
||||
};
|
||||
|
||||
export const BrainSearchBar = ({
|
||||
searchQuery,
|
||||
setSearchQuery,
|
||||
}: BrainSearchBarProps): JSX.Element => {
|
||||
return (
|
||||
<div className="m-2">
|
||||
<Field
|
||||
name="brainsearch"
|
||||
placeholder="Search for a brain"
|
||||
autoFocus
|
||||
autoComplete="off"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -0,0 +1 @@
|
||||
export * from './BrainSearchBar';
|
@ -0,0 +1,77 @@
|
||||
"use client";
|
||||
import { motion, MotionConfig } from "framer-motion";
|
||||
import { MdChevronRight } from "react-icons/md";
|
||||
|
||||
import { AddBrainModal } from "@/lib/components/AddBrainModal";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { BrainListItem } from "./BrainListItem";
|
||||
import { BrainSearchBar } from "./BrainSearchBar";
|
||||
import { useBrainsList } from "../hooks/useBrainsList";
|
||||
|
||||
export const BrainsList = (): JSX.Element => {
|
||||
const { open, setOpen, searchQuery, setSearchQuery, brains } =
|
||||
useBrainsList();
|
||||
|
||||
return (
|
||||
<MotionConfig transition={{ massq: 1, damping: 10 }}>
|
||||
<motion.div
|
||||
drag="x"
|
||||
dragConstraints={{ right: 0, left: 0 }}
|
||||
dragElastic={0.15}
|
||||
onDragEnd={(event, info) => {
|
||||
if (info.offset.x > 100 && !open) {
|
||||
setOpen(true);
|
||||
} else if (info.offset.x < -100 && open) {
|
||||
setOpen(false);
|
||||
}
|
||||
}}
|
||||
className="flex flex-col lg:sticky fixed top-16 left-0 bottom-0 lg:h-[90vh] overflow-visible z-30 border-r border-black/10 dark:border-white/25 bg-white dark:bg-black"
|
||||
>
|
||||
<motion.div
|
||||
animate={{
|
||||
width: open ? "fit-content" : "0px",
|
||||
opacity: open ? 1 : 0.5,
|
||||
boxShadow: open
|
||||
? "10px 10px 16px rgba(0, 0, 0, 0)"
|
||||
: "10px 10px 16px rgba(0, 0, 0, 0.5)",
|
||||
}}
|
||||
className={cn("overflow-hidden flex flex-col flex-1")}
|
||||
data-testid="brains-list"
|
||||
>
|
||||
<div className="flex flex-col flex-1">
|
||||
<BrainSearchBar
|
||||
searchQuery={searchQuery}
|
||||
setSearchQuery={setSearchQuery}
|
||||
/>
|
||||
<div
|
||||
data-testid="brains-list-items"
|
||||
className="flex-1 overflow-auto scrollbar h-full"
|
||||
>
|
||||
{brains.map((brain) => (
|
||||
<BrainListItem brain={brain} key={brain.id} />
|
||||
))}
|
||||
</div>
|
||||
<div className="m-2 mb flex flex-col">
|
||||
<AddBrainModal />
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
<button
|
||||
onClick={() => {
|
||||
setOpen(!open);
|
||||
}}
|
||||
className="absolute left-full top-16 text-3xl bg-black dark:bg-white text-white dark:text-black rounded-r-full p-3 pl-1"
|
||||
data-testid="brains-list-toggle"
|
||||
>
|
||||
<motion.div
|
||||
whileTap={{ scale: 0.9 }}
|
||||
animate={{ scaleX: open ? -1 : 1 }}
|
||||
>
|
||||
<MdChevronRight />
|
||||
</motion.div>
|
||||
</button>
|
||||
</motion.div>
|
||||
</MotionConfig>
|
||||
);
|
||||
};
|
@ -0,0 +1,3 @@
|
||||
export * from "./BrainListItem";
|
||||
export * from "./BrainSearchBar";
|
||||
export * from "./BrainsList";
|
@ -0,0 +1,36 @@
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
|
||||
import { useDevice } from "@/lib/hooks/useDevice";
|
||||
|
||||
import { sortBrainsByName } from "../utils/sortByName";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export const useBrainsList = () => {
|
||||
const { isMobile } = useDevice();
|
||||
const [open, setOpen] = useState(!isMobile);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const { allBrains } = useBrainContext();
|
||||
|
||||
const pathname = usePathname();
|
||||
|
||||
useEffect(() => {
|
||||
setOpen(!isMobile);
|
||||
}, [isMobile, pathname]);
|
||||
|
||||
const brains = allBrains.filter((brain) => {
|
||||
const query = searchQuery.toLowerCase();
|
||||
const name = brain.name.toLowerCase();
|
||||
|
||||
return name.includes(query);
|
||||
});
|
||||
|
||||
return {
|
||||
open,
|
||||
setOpen,
|
||||
searchQuery,
|
||||
setSearchQuery,
|
||||
brains: sortBrainsByName(brains),
|
||||
};
|
||||
};
|
11
frontend/app/brains-management/[brainId]/page.tsx
Normal file
11
frontend/app/brains-management/[brainId]/page.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
"use client";
|
||||
|
||||
const BrainsManagement = (): JSX.Element => {
|
||||
return (
|
||||
<main className="flex flex-col w-full pt-10" data-testid="brain-page">
|
||||
<p>Coming soon</p>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
export default BrainsManagement;
|
12
frontend/app/brains-management/[brainId]/utils/sortByName.ts
Normal file
12
frontend/app/brains-management/[brainId]/utils/sortByName.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { MinimalBrainForUser } from "@/lib/context/BrainProvider/types";
|
||||
|
||||
export const sortBrainsByName = (
|
||||
minimalBrains: MinimalBrainForUser[]
|
||||
): MinimalBrainForUser[] => {
|
||||
// Use the sort method to sort the array by the 'name' property
|
||||
const sortedMinimalBrains = minimalBrains
|
||||
.slice()
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
return sortedMinimalBrains;
|
||||
};
|
27
frontend/app/brains-management/layout.tsx
Normal file
27
frontend/app/brains-management/layout.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
"use client";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
import { useSupabase } from "@/lib/context/SupabaseProvider";
|
||||
import { redirectToLogin } from "@/lib/router/redirectToLogin";
|
||||
|
||||
import { BrainsList } from "./[brainId]/components";
|
||||
|
||||
interface LayoutProps {
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
const Layout = ({ children }: LayoutProps): JSX.Element => {
|
||||
const { session } = useSupabase();
|
||||
if (session === null) {
|
||||
redirectToLogin();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative h-full w-full flex justify-stretch items-stretch">
|
||||
<BrainsList />
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Layout;
|
4
frontend/app/brains-management/page.tsx
Normal file
4
frontend/app/brains-management/page.tsx
Normal file
@ -0,0 +1,4 @@
|
||||
"use client";
|
||||
import BrainsManagementPage from "./[brainId]/page";
|
||||
|
||||
export default BrainsManagementPage;
|
@ -0,0 +1,14 @@
|
||||
import Link from "next/link";
|
||||
import { FaBrain } from "react-icons/fa";
|
||||
import { MdSettings } from "react-icons/md";
|
||||
|
||||
export const BrainManagementButton = (): JSX.Element => {
|
||||
return (
|
||||
<Link href={"/brains-management"}>
|
||||
<button type="button" className="flex items-center focus:outline-none">
|
||||
<MdSettings className="w-6 h-6" color="gray" />
|
||||
<FaBrain className="w-3 h-3" color="gray" />
|
||||
</button>
|
||||
</Link>
|
||||
);
|
||||
};
|
@ -6,8 +6,8 @@ import Field from "@/lib/components/ui/Field";
|
||||
import Popover from "@/lib/components/ui/Popover";
|
||||
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
|
||||
|
||||
import { AddBrainModal } from "./components/AddBrainModal";
|
||||
import { BrainActions } from "./components/BrainActions/BrainActions";
|
||||
import { AddBrainModal } from "../../../../../AddBrainModal";
|
||||
|
||||
export const BrainsDropDown = (): JSX.Element => {
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
|
@ -1,2 +1,2 @@
|
||||
export * from "./AddBrainModal";
|
||||
export * from "@/lib/components/AddBrainModal";
|
||||
export * from "./BrainActions";
|
||||
|
@ -8,6 +8,7 @@ import { useSupabase } from "@/lib/context/SupabaseProvider";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { AuthButtons } from "./components/AuthButtons";
|
||||
import { BrainManagementButton } from "./components/BrainManagementButton";
|
||||
import { BrainsDropDown } from "./components/BrainsDropDown";
|
||||
import { DarkModeToggle } from "./components/DarkModeToggle";
|
||||
import { NavLink } from "./components/NavLink";
|
||||
@ -58,6 +59,7 @@ export const NavItems = ({
|
||||
{isUserLoggedIn && (
|
||||
<>
|
||||
<BrainsDropDown />
|
||||
<BrainManagementButton />
|
||||
<Link aria-label="account" className="" href={"/user"}>
|
||||
<MdPerson className="text-2xl" />
|
||||
</Link>
|
||||
|
Loading…
Reference in New Issue
Block a user