Skip to content

Commit

Permalink
add top stats to edit page
Browse files Browse the repository at this point in the history
  • Loading branch information
olexh committed Mar 19, 2024
1 parent 4b9d762 commit 1cbeaab
Show file tree
Hide file tree
Showing 16 changed files with 1,261 additions and 258 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import * as React from 'react';
import MaterialSymbolsKidStar from '~icons/material-symbols/kid-star';

import Link from 'next/link';

import Small from '@/components/typography/small';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Label } from '@/components/ui/label';
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/ui/tooltip';
import { cn } from '@/utils';

interface Props {
user: API.User;
rank: number;
accepted: number;
closed: number;
denied: number;
}

const Component = ({ user, rank, accepted, denied, closed }: Props) => {
return (
<div
className={cn(
'relative flex min-w-0 flex-1 w-full items-center gap-4 rounded-md p-4',
'border border-secondary/60 bg-secondary/30',
)}
>
<MaterialSymbolsKidStar
className={cn(
'absolute text-lg',
rank === 1
? 'text-yellow-300'
: rank === 2
? 'text-slate-300'
: 'text-amber-700',
'right-2 top-2 lg:-right-2 lg:-top-2',
)}
/>
<Link href={`/u/${user.username}`}>
<Avatar className="w-10 rounded-md">
<AvatarImage src={user.avatar} />
<AvatarFallback className="w-10 rounded-md" />
</Avatar>
</Link>
<div className="flex min-w-0 flex-col gap-1">
<Label className="truncate">
<Link className="truncate" href={`/u/${user.username}`}>
{user.username}
</Link>
</Label>

<div className="flex gap-3">
<Tooltip delayDuration={0}>
<TooltipTrigger>
<div className="flex items-center gap-2 text-muted-foreground">
<div className="flex size-2 items-center justify-center rounded-full bg-success" />
<Small>{accepted}</Small>
</div>
</TooltipTrigger>
<TooltipContent>Прийнято</TooltipContent>
</Tooltip>
<Tooltip delayDuration={0}>
<TooltipTrigger>
<div className="flex items-center gap-2 text-muted-foreground">
<div className="flex size-2 items-center justify-center rounded-full bg-destructive" />
<Small>{denied}</Small>
</div>
</TooltipTrigger>
<TooltipContent>Відхилено</TooltipContent>
</Tooltip>

<Tooltip delayDuration={0}>
<TooltipTrigger>
<div className="flex items-center gap-2 text-muted-foreground">
<div className="flex size-2 items-center justify-center rounded-full bg-warning" />
<Small>{closed}</Small>
</div>
</TooltipTrigger>
<TooltipContent>Закрито</TooltipContent>
</Tooltip>
</div>
</div>
</div>
);
};

export default Component;
72 changes: 72 additions & 0 deletions app/(pages)/edit/_components/edit-top-stats/edit-top-stats.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
'use client';

import * as React from 'react';
import MaterialSymbolsMoreHoriz from '~icons/material-symbols/more-horiz';

import EditTopStatsModal from '@/components/modals/edit-top-stats-modal/edit-top-stats-modal';
import {
Carousel,
CarouselContent,
CarouselItem,
} from '@/components/ui/carousel';
import useEditTop from '@/services/hooks/stats/edit/useEditTop';
import { useModalContext } from '@/services/providers/modal-provider';

import EditTopItem from './components/ui/edit-top-item';

function Component() {
const { openModal } = useModalContext();
const { list } = useEditTop();

if (!list || list.length === 0) {
return null;
}

const handleOpenModal = () => {
openModal({
content: <EditTopStatsModal />,
title: 'Топ контрибуторів',
type: 'sheet',
});
};

return (
<Carousel
opts={{
breakpoints: {
'(min-width: 1024px)': {
active: false,
},
},
}}
className="-mx-4 lg:mx-0"
>
<CarouselContent containerClassName="lg:overflow-visible px-4 lg:px-0">
{list.slice(0, 3).map((stat, index) => (
<CarouselItem
className="basis-3/4 md:basis-1/3 lg:basis-[30%]"
key={stat.user.reference}
>
<EditTopItem
rank={index + 1}
user={stat.user}
accepted={stat.accepted}
closed={stat.closed}
denied={stat.denied}
/>
</CarouselItem>
))}
<CarouselItem className="md:basis-1/3 lg:basis-[10%]">
<div
onClick={handleOpenModal}
className="flex items-center justify-center rounded-md border border-secondary/60 bg-secondary/30 p-4 opacity-60 hover:opacity-100"
>
<MaterialSymbolsMoreHoriz className="text-4xl text-muted-foreground" />
</div>
</CarouselItem>
</CarouselContent>
</Carousel>
);
}

export default Component;
33 changes: 23 additions & 10 deletions app/(pages)/edit/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import { redirect } from 'next/navigation';
import { dehydrate } from '@tanstack/query-core';
import { HydrationBoundary } from '@tanstack/react-query';

import EditTopStats from '@/app/(pages)/edit/_components/edit-top-stats/edit-top-stats';
import EditFiltersModal from '@/components/modals/edit-filters-modal';
import { Button } from '@/components/ui/button';
import getEditList from '@/services/api/edit/getEditList';
import getEditTop from '@/services/api/stats/edit/getEditTop';
import getQueryClient from '@/utils/getQueryClient';

import Filters from '../../../components/filters/edit-filters';
Expand All @@ -27,23 +29,34 @@ const Component = async ({
const queryClient = getQueryClient();

await queryClient.prefetchQuery({
queryKey: ['editList', page],
queryKey: ['editList', { page }],
queryFn: () => getEditList({ page: Number(page) }),
});

await queryClient.prefetchInfiniteQuery({
queryKey: ['editTopStats'],
queryFn: ({ pageParam }) => getEditTop({ page: Number(pageParam) }),
initialPageParam: 1,
});

const dehydratedState = dehydrate(queryClient);

return (
<HydrationBoundary state={dehydratedState}>
<div className="grid grid-cols-1 justify-center gap-8 lg:grid-cols-[1fr_25%] lg:items-start lg:justify-between lg:gap-16">
<EditFiltersModal>
<Button variant="outline" className="flex lg:hidden">
<AntDesignFilterFilled /> Фільтри
</Button>
</EditFiltersModal>
<EditList page={page as string} />
<div className="sticky top-20 order-1 hidden w-full rounded-md border border-secondary/60 bg-secondary/30 opacity-60 transition-opacity hover:opacity-100 lg:order-2 lg:block">
<Filters className="px-4" />
<div className="flex flex-col gap-12 lg:gap-12">
<div className="grid grid-cols-1 justify-center gap-8 lg:grid-cols-[1fr_25%] lg:items-start lg:justify-between lg:gap-16">
<div className="flex flex-col gap-12">
<EditTopStats />
<EditFiltersModal>
<Button variant="outline" className="flex lg:hidden">
<AntDesignFilterFilled /> Фільтри
</Button>
</EditFiltersModal>
<EditList page={page as string} />
</div>
<div className="sticky top-20 order-1 hidden w-full rounded-md border border-secondary/60 bg-secondary/30 opacity-60 transition-opacity hover:opacity-100 lg:order-2 lg:block">
<Filters className="px-4" />
</div>
</div>
</div>
</HydrationBoundary>
Expand Down
4 changes: 2 additions & 2 deletions components/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
import { Label } from '@/components/ui/label';
import { useModalContext } from '@/services/providers/modal-provider';

import Rightholder from './rightholder.mdx';
import RightHolder from './rightholder';

const Component = () => {
const { openModal } = useModalContext();
Expand All @@ -32,7 +32,7 @@ const Component = () => {
size="sm"
onClick={() =>
openModal({
content: <Rightholder />,
content: <RightHolder />,
className: 'max-w-xl',
title: 'Правовласникам',
})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import * as React from 'react';
import MaterialSymbolsKidStar from '~icons/material-symbols/kid-star';

import Link from 'next/link';

import Small from '@/components/typography/small';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Label } from '@/components/ui/label';
import { cn } from '@/utils';

interface Props {
user: API.User;
rank: number;
accepted: number;
closed: number;
denied: number;
}

const Component = ({ user, rank, accepted, denied, closed }: Props) => {
return (
<div
className={cn(
'relative flex min-w-0 flex-1 items-center gap-4 rounded-md p-4',
)}
>
<Label className="text-muted-foreground">{rank}</Label>
<Link href={`/u/${user.username}`}>
<Avatar className="size-12 rounded-md">
<AvatarImage src={user.avatar} />
<AvatarFallback className="size-12 rounded-md" />
</Avatar>
</Link>
<div className="flex min-w-0 flex-1 flex-col gap-1">
<Link className="truncate font-bold" href={`/u/${user.username}`}>
{user.username}
</Link>

<div className="flex gap-3">
<div className="flex items-center gap-2 text-muted-foreground">
<div className="flex size-2 items-center justify-center rounded-full bg-success" />
<Small>{accepted}</Small>
</div>
<div className="flex items-center gap-2 text-muted-foreground">
<div className="flex size-2 items-center justify-center rounded-full bg-destructive" />
<Small>{denied}</Small>
</div>
<div className="flex items-center gap-2 text-muted-foreground">
<div className="flex size-2 items-center justify-center rounded-full bg-warning" />
<Small>{closed}</Small>
</div>
</div>
</div>
{rank <= 3 && (
<MaterialSymbolsKidStar
className={cn(
'text-lg',
rank === 1
? 'text-yellow-300'
: rank === 2
? 'text-slate-300'
: 'text-amber-700',
)}
/>
)}
</div>
);
};

export default Component;
56 changes: 56 additions & 0 deletions components/modals/edit-top-stats-modal/edit-top-stats-modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
'use client';

import * as React from 'react';

import { Button } from '@/components/ui/button';
import useEditTop from '@/services/hooks/stats/edit/useEditTop';

import EditTopItem from './components/ui/edit-top-item';


const Component = () => {
const { list, fetchNextPage, isFetchingNextPage, hasNextPage, ref } =
useEditTop();

if (!list) {
return null;
}

return (
<>
<hr className="-mx-6 mt-4 h-px w-auto bg-border" />
<div className="-mx-6 h-full w-auto flex-1 overflow-y-scroll">
{list.map((stat, index) => {
return (
<EditTopItem
key={stat.user.reference}
user={stat.user}
rank={index + 1}
accepted={stat.accepted}
closed={stat.closed}
denied={stat.denied}
/>
);
})}
{hasNextPage && (
<div className="px-4">
<Button
variant="secondary"
ref={ref}
disabled={isFetchingNextPage}
onClick={() => hasNextPage && fetchNextPage()}
className="w-full"
>
{isFetchingNextPage && (
<span className="loading loading-spinner"></span>
)}
Завантажити ще
</Button>
</div>
)}
</div>
</>
);
};

export default Component;
6 changes: 3 additions & 3 deletions components/nav-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ const Component = ({ routes, urlPrefix, showOnMobile, isEqualPath = true }: Prop
}

return (
<NavigationMenu>
<NavigationMenu className="min-w-0">
<NavigationMenuList>
<NavigationMenuItem>
<NavigationMenuTrigger>
<NavigationMenuTrigger className="min-w-0">
{current && (
<P className="text-sm">{current.title_ua}</P>
<P className="truncate text-sm">{current.title_ua}</P>
)}
</NavigationMenuTrigger>
<NavigationMenuContent>
Expand Down
Loading

0 comments on commit 1cbeaab

Please sign in to comment.