Continued creating the entity system

This commit is contained in:
Tiago Ribeiro
2024-10-01 17:39:43 +01:00
parent bae02e5192
commit 564e6438cb
37 changed files with 2522 additions and 130 deletions

View File

@@ -0,0 +1,32 @@
import {useListSearch} from "@/hooks/useListSearch";
import usePagination from "@/hooks/usePagination";
import {ReactNode} from "react";
import Checkbox from "../Low/Checkbox";
import Separator from "../Low/Separator";
interface Props<T> {
list: T[];
searchFields: string[][];
pageSize?: number;
firstCard?: () => ReactNode;
renderCard: (item: T) => ReactNode;
}
export default function CardList<T>({list, searchFields, renderCard, firstCard, pageSize = 20}: Props<T>) {
const {rows, renderSearch} = useListSearch(searchFields, list);
const {items, page, renderMinimal} = usePagination(rows, pageSize);
return (
<section className="flex flex-col gap-4 w-full">
<div className="w-full flex items-center gap-4">
{renderSearch()}
{renderMinimal()}
</div>
<div className="w-full h-full grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
{page === 0 && !!firstCard && firstCard()}
{items.map(renderCard)}
</div>
</section>
);
}

View File

@@ -0,0 +1,67 @@
import {Invite, InviteWithUsers} from "@/interfaces/invite";
import {User} from "@/interfaces/user";
import {getUserName} from "@/utils/users";
import axios from "axios";
import {useMemo, useState} from "react";
import {BsArrowRepeat} from "react-icons/bs";
import {toast} from "react-toastify";
interface Props {
invite: InviteWithUsers;
reload: () => void;
}
export default function InviteWithUserCard({invite, reload}: Props) {
const [isLoading, setIsLoading] = useState(false);
const name = useMemo(() => (!invite.from ? null : getUserName(invite.from)), [invite.from]);
const decide = (decision: "accept" | "decline") => {
if (!confirm(`Are you sure you want to ${decision} this invite?`)) return;
setIsLoading(true);
axios
.get(`/api/invites/${decision}/${invite.id}`)
.then(() => {
toast.success(`Successfully ${decision === "accept" ? "accepted" : "declined"} the invite!`, {toastId: "success"});
reload();
})
.catch((e) => {
toast.success(`Something went wrong, please try again later!`, {
toastId: "error",
});
reload();
})
.finally(() => setIsLoading(false));
};
return (
<div className="border-mti-gray-anti-flash flex min-w-[200px] flex-col gap-6 rounded-xl border p-4 text-black">
<span>Invited by {name}</span>
<div className="flex items-center gap-2">
<button
onClick={() => decide("accept")}
disabled={isLoading}
className="bg-mti-green-ultralight hover:bg-mti-green-light w-24 rounded-lg p-2 px-4 transition duration-300 ease-in-out hover:text-white disabled:cursor-not-allowed">
{!isLoading && "Accept"}
{isLoading && (
<div className="flex items-center justify-center">
<BsArrowRepeat className="animate-spin text-white" size={25} />
</div>
)}
</button>
<button
onClick={() => decide("decline")}
disabled={isLoading}
className="bg-mti-red-ultralight hover:bg-mti-red-light w-24 rounded-lg p-2 px-4 transition duration-300 ease-in-out hover:text-white disabled:cursor-not-allowed">
{!isLoading && "Decline"}
{isLoading && (
<div className="flex items-center justify-center">
<BsArrowRepeat className="animate-spin text-white" size={25} />
</div>
)}
</button>
</div>
</div>
);
}