Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions src/app/(after-login)/dashboard/[id]/edit/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { getErrorMessage } from '@/utils/errorMessage';
import DetailModify from '@/components/dashboard/DetailModify';
import DetailMembers from '@/components/dashboard/DetailMembers';
import DetailInvited from '@/components/dashboard/DetailInvited';
import Link from 'next/link';
import GoBackLink from '@/components/ui/Link/GoBackLink';

export default function DashboardEditPage() {
const router = useRouter();
Expand All @@ -29,9 +29,8 @@ export default function DashboardEditPage() {

return (
<div className='p-10'>
{/* TODO : 돌아가기 공용 컴포넌트로 교체 필요 */}
<div className='mb-8'>
<Link href={`/dashboard/${id}`}>돌아가기</Link>
<GoBackLink href={`/dashboard/${id}`} />
</div>
<div className='grid w-full max-w-[620px] gap-4'>
{/* 대시보드 정보 */}
Expand All @@ -44,7 +43,6 @@ export default function DashboardEditPage() {
<DetailInvited />

{/* 대시보드 삭제 */}
{/* TODO : 대시보드 공용 버튼 교체 필요 */}
<Button variant='outline' onClick={handleDelete}>
대시보드 삭제하기
</Button>
Expand Down
85 changes: 80 additions & 5 deletions src/components/dashboard/DetailInvited.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,98 @@
import { useRef } from 'react';
import { useRef, useState } from 'react';
import Button from '@/components/ui/Button/Button';
import { Card, CardTitle } from '@/components/ui/Card/Card';
import { ModalHandle } from '@/components/ui/Modal/Modal';
import InviteDashboard from './InviteDashboard';
import { useParams } from 'next/navigation';
import { useDashboardInvitationsQuery, useDashboardMutation } from '@/apis/dashboards/queries';
import { getErrorMessage } from '@/utils/errorMessage';
import useAlert from '@/hooks/useAlert';
import { Table, TableBody, TableCell, TableCol, TableColGroup, TableHead, TableHeadCell, TableRow } from '@/components/ui/Table/Table';
import PaginationWithCounter from '@/components/pagination/PaginationWithCounter';
import { isAxiosError } from 'axios';

const PAGE_SIZE = 5;

export default function DetailInvited() {
const { id } = useParams<{ id: string }>();
const [page, setPage] = useState(1);
const { data, error, isLoading } = useDashboardInvitationsQuery(Number(id), page, PAGE_SIZE);
const { cancel } = useDashboardMutation();
const alert = useAlert();
const inviteModalRef = useRef<ModalHandle | null>(null);

const cancelInvite = async (invitationId: number) => {
try {
await cancel({ dashboardId: Number(id), invitationId });
alert('초대를 취소했습니다.');
} catch (error) {
const message = getErrorMessage(error);
alert(message);
}
};
const notAllowed = isAxiosError(error) && error.status === 403;

return (
<Card>
<CardTitle>
초대내역
<div className='leading-none'>
<Button size='sm' onClick={() => inviteModalRef.current?.open()}>
초대하기
</Button>
<PaginationWithCounter //
totalCount={data?.totalCount || 0}
page={page}
setPage={setPage}
pageSize={PAGE_SIZE}
/>
</div>
</CardTitle>
<div>대시보드 초대내역(작업필요)</div>
<Table className='mb-4'>
<TableColGroup>
<TableCol />
<TableCol className='w-24' />
</TableColGroup>
<TableHead>
<TableRow>
<TableHeadCell>이메일</TableHeadCell>
<TableHeadCell></TableHeadCell>
</TableRow>
</TableHead>
<TableBody>
{isLoading && (
<TableRow>
<TableCell colSpan={2}>
<div className='p-4 text-center'>초대목록을 가져오는 중입니다.</div>
</TableCell>
</TableRow>
)}
{notAllowed && (
<TableRow>
<TableCell colSpan={2}>
<div className='p-4 text-center'>권한이 없습니다.</div>
</TableCell>
</TableRow>
)}
{data?.invitations.length === 0 && (
<TableRow>
<TableCell colSpan={2}>
<div className='p-4 text-center'>초대 내역이 없습니다.</div>
</TableCell>
</TableRow>
)}
{data?.invitations.map((item) => (
<TableRow key={item.id}>
<TableCell>{item.invitee.email}</TableCell>
<TableCell>
<Button variant='outline' size='sm' onClick={() => cancelInvite(item.id)}>
취소
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
<Button className='w-full' onClick={() => inviteModalRef.current?.open()}>
초대하기
</Button>

{/* 초대모달 */}
<InviteDashboard ref={inviteModalRef} />
Expand Down
28 changes: 7 additions & 21 deletions src/components/dashboard/MyDashboardList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

import { useState } from 'react';
import Link from 'next/link';
import PaginationControls from '@/components/pagination/PaginationControls';
import MyDashboardCard from '@/components/dashboard/MyDashboardCard';
import { useDashboardsQuery } from '@/apis/dashboards/queries';
import DashboardButton from '../ui/Button/DashboardButton';
import PaginationWithCounter from '../pagination/PaginationWithCounter';

interface MyDashboardListProps {
onAdd: () => void;
Expand All @@ -17,18 +17,6 @@ export default function MyDashboardList({ onAdd }: MyDashboardListProps) {
const [page, setPage] = useState(1);
const { data, isLoading } = useDashboardsQuery(page, ITEMS_PER_PAGE);

const totalCount = data?.totalCount || 0;
const totalPage = Math.ceil(totalCount / ITEMS_PER_PAGE);
const hasNext = page < totalPage;
const hasPrev = page > 1;

const handlePrev = () => {
setPage((prev) => prev - 1);
};
const handleNext = () => {
setPage((prev) => prev + 1);
};

return (
<div className='grid gap-3'>
<ul className='grid-row-6 md:grid-row-3 relative grid grid-cols-1 gap-3 md:grid-cols-2 lg:grid-cols-3 lg:grid-rows-2'>
Expand All @@ -47,14 +35,12 @@ export default function MyDashboardList({ onAdd }: MyDashboardListProps) {
))}
</ul>

{totalCount > 0 && (
<div className='flex items-center justify-end gap-4'>
<span className='text-md text-gray-70'>
{totalPage} 페이지중 {page}
</span>
<PaginationControls canGoPrev={hasPrev} canGoNext={hasNext} handlePrev={handlePrev} handleNext={handleNext} totalPages={totalCount} alwaysShow />
</div>
)}
<PaginationWithCounter //
totalCount={data?.totalCount || 0}
page={page}
setPage={setPage}
pageSize={ITEMS_PER_PAGE}
/>
</div>
);
}
Expand Down
55 changes: 31 additions & 24 deletions src/components/dashboard/MyInvitedDashboardList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import MyInvitedEmptyCard from './MyInvitedEmptyCard';
import { SearchInput } from '../ui/Field';
import useAlert from '@/hooks/useAlert';
import { getErrorMessage } from '@/utils/errorMessage';
import { Table, TableBody, TableCell, TableCol, TableColGroup, TableHead, TableHeadCell, TableRow } from '@/components/ui/Table/Table';

export default function MyInvitedDashboardList() {
const [searchKeyword, setSearchKeyword] = useState('');
Expand Down Expand Up @@ -47,42 +48,48 @@ export default function MyInvitedDashboardList() {
{hasNoInvitations ? (
<MyInvitedEmptyCard>아직 초대받은 대시보드가 없어요.</MyInvitedEmptyCard>
) : (
<div className='grid gap-10'>
<div className='grid gap-4 md:gap-10'>
<SearchInput value={searchKeyword} onChange={(e) => setSearchKeyword(e.target.value)} placeholder='검색' />
{hasNoSearchResults ? (
<MyInvitedEmptyCard>검색된 초대가 없습니다.</MyInvitedEmptyCard>
) : (
// TODO : 리스트 테이블 공용 컴포넌트화 필요
<div className='flex flex-col gap-6'>
<div className='px-8'>
<div className='hidden md:block'>
<div className='mb-4 grid grid-cols-[1fr_1fr_2fr] gap-5 text-lg text-gray-40 lg:pl-10'>
<span>이름</span>
<span>초대자</span>
<span>수락 여부</span>
</div>
<div className='flex flex-col gap-5'>
{invitations.map((item) => (
<div key={item.id} className='grid grid-cols-[1fr_1fr_2fr] items-center gap-5 border-b pb-5 text-lg text-gray-70 lg:pl-10'>
<span>{item.dashboard.title}</span>
<span>{item.inviter.nickname}</span>
<div className='flex gap-2.5'>
<>
<Table responsive>
<TableColGroup className='hidden md:table-column-group'>
<TableCol />
<TableCol className='w-[20%]' />
<TableCol className='w-48' />
</TableColGroup>
<TableHead>
<TableRow>
<TableHeadCell>이름</TableHeadCell>
<TableHeadCell>초대자</TableHeadCell>
<TableHeadCell>수락 여부</TableHeadCell>
</TableRow>
</TableHead>
<TableBody>
{invitations.map((item) => (
<TableRow key={item.id}>
<TableCell label='이름'>{item.dashboard.title}</TableCell>
<TableCell label='초대자'>{item.inviter.nickname}</TableCell>
<TableCell>
<div className='flex w-full gap-2'>
<Button size='sm' onClick={() => handleAccept({ id: item.id, flag: true })}>
수락
</Button>
<Button variant='outline' size='sm' onClick={() => handleAccept({ id: item.id, flag: false })}>
거절
</Button>
</div>
</div>
))}
</div>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>

{/* 공통 load more 트리거 요소 */}
<div ref={ref} className='h-8' />
</div>
</div>
{/* 공통 load more 트리거 요소 */}
<div ref={ref} className='h-2' />
</>
)}
</div>
)}
Expand Down
33 changes: 33 additions & 0 deletions src/components/pagination/PaginationWithCounter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Dispatch, SetStateAction } from 'react';
import PaginationControls from './PaginationControls';

interface PaginationWithCounterProps {
totalCount: number;
page: number;
setPage: Dispatch<SetStateAction<number>>;
pageSize: number;
}

export default function PaginationWithCounter({ totalCount, page, setPage, pageSize }: PaginationWithCounterProps) {
const totalPage = Math.ceil(totalCount / pageSize);
const hasNext = page < totalPage;
const hasPrev = page > 1;

const handlePrev = () => {
setPage((prev) => prev - 1);
};
const handleNext = () => {
setPage((prev) => prev + 1);
};

return (
<div className='flex items-center justify-end gap-4 font-normal'>
{totalCount > 0 && (
<span className='text-md text-gray-70'>
{totalPage} 페이지중 {page}
</span>
)}
<PaginationControls canGoPrev={hasPrev} canGoNext={hasNext} handlePrev={handlePrev} handleNext={handleNext} totalPages={totalPage} alwaysShow />
</div>
);
}
1 change: 1 addition & 0 deletions src/components/provider/QueryProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000, // 1분
retry: false,
},
},
});
Expand Down
Loading