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
1 change: 1 addition & 0 deletions eslint.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,6 @@ export default tseslint.config(sheriff(sheriffOptions), {
'@typescript-eslint/explicit-module-boundary-types': 'off',
'fsecond/prefer-destructured-optionals': 'off',
'@typescript-eslint/no-misused-spread': 'off',
'@typescript-eslint/prefer-nullish-coalescing': 'off',
},
});
18 changes: 13 additions & 5 deletions src/components/mydashboard/create-newboard-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { type ReactNode, useState } from 'react';
import CreateNewboardForm from '@/components/mydashboard/create-newboard-form';
import type { CreateNewboardFormData } from '@/components/mydashboard/type';
import ButtonModal from '@/components/ui/modal/modal-button';
import { useMutate } from '@/hooks/useAsync';
import { useModalKeyHandler } from '@/hooks/useModal';
import { createDashBoard } from '@/lib/dashboards/api';

interface CreateNewboardModalProps {
isOpen: boolean;
onClose: () => void;
onSubmit: (formData: CreateNewboardFormData) => void;
onSubmit?: (formData: CreateNewboardFormData) => void;
}

export default function CreateNewboardModal({
Expand All @@ -19,19 +21,25 @@ export default function CreateNewboardModal({
title: '',
color: 'green', // 기본 색상 설정
});

const { mutate } = useMutate({
asyncFunction: () => createDashBoard(formData),
});
const handleClose = () => {
setFormData({ title: '', color: 'green' });
onClose();
};

useModalKeyHandler(isOpen, handleClose);

// const handleSubmit = () => {
// onSubmit(formData);
// handleClose();
// };

const handleSubmit = () => {
onSubmit(formData);
handleClose();
mutate();
onClose();
};

const isSubmitDisabled = !formData.title.trim();

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
import { useRouter } from 'next/router';
import { type ReactNode, useCallback, useEffect } from 'react';
import ChipProfile, {
type ChipProfileProps,
} from '@/components/ui/chip/chip-profile';
import ChipProfile from '@/components/ui/chip/chip-profile';
import Dropdown from '@/components/ui/dropdown';

interface HeaderDropdownProps {
nickname: string;
profileLabel: string;
profileColor: ChipProfileProps['color'];
}
export default function HeaderDropdown({
nickname,
profileLabel,
profileColor,
}: HeaderDropdownProps): ReactNode {
export default function HeaderProfileDropdwon({
myNickname,
}: {
myNickname: string;
}): ReactNode {
const profileColor = 'yellow';
const profileLabel = myNickname.slice(0, 1);
const router = useRouter();

const handleMyPageButton = useCallback(() => {
Expand Down Expand Up @@ -46,11 +41,11 @@ export default function HeaderDropdown({
<Dropdown.Toggle>
<div className='border-l-gray-3 hover:bg-gray-4 active:bg-gray-3 mobile:pl-3 tablet:pr-8 mobile:pr-2 flex h-full cursor-pointer items-center gap-3 border-l-1 pr-20 pl-6'>
<ChipProfile label={profileLabel} size='lg' color={profileColor} />
<span className='mobile:hidden font-medium'>{nickname}</span>
<span className='mobile:hidden font-medium'>{myNickname}</span>
</div>
</Dropdown.Toggle>
<Dropdown.List
additionalClassName='w-32 mobile:w-28 -top-1'
additionalClassName='w-32 mobile:w-28 -top-1 mobile:-left-16'
ariaLabel='사용자 메뉴'
>
<Dropdown.Item
Expand Down
88 changes: 65 additions & 23 deletions src/components/ui/dashboard-header/index.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,39 @@
import Image from 'next/image';
import Link from 'next/link';
import { type ReactNode, useState } from 'react';
import HeaderDropdown from '@/components/ui/dashboard-header/header-dropdown';
import { useRouter } from 'next/router';
import { type ReactNode, useEffect, useState } from 'react';
import HeaderProfileDropdwon from '@/components/ui/dashboard-header/header-profile-dropdown';
import InviteMemberModal from '@/components/ui/dashboard-header/invite-member-modal';
import ProfileList from '@/components/ui/dashboard-header/profile-list';
import ModalPortal from '@/components/ui/modal/modal-portal';
import { useFetch } from '@/hooks/useAsync';
import { getDashBoard } from '@/lib/dashboards/api';
import { getMyInfo } from '@/lib/users/api';
import { getStringFromQuery } from '@/utils/getContextQuery';

const buttonClass =
'flex-center border-gray-3 text-md mobile:px-3 mobile:py-1.5 h-9 cursor-pointer gap-2 rounded-lg border-1 px-4 py-2.5 hover:bg-gray-4 active:bg-gray-3';

export default function DashboardHeader(): ReactNode {
const [isModalOpen, setIsModalOpen] = useState(false);
const dashboardId = 1;
const router = useRouter();
const dashboardId = getStringFromQuery(router.query, 'dashboardId');

const { data: myInfo } = useFetch({
asyncFunction: () => getMyInfo(),
});
const { data: dashboardData, refetch } = useFetch({
asyncFunction: () => getDashBoard(Number(dashboardId)),
deps: [dashboardId],
immediate: false,
});
const isMyDashboard = dashboardId && dashboardData?.createdByMe;

useEffect(() => {
if (dashboardId) {
refetch();
}
}, [dashboardId, refetch]);

const handleOpenModal = () => {
setIsModalOpen(true);
Expand All @@ -24,17 +47,21 @@ export default function DashboardHeader(): ReactNode {
handleCloseModal();
};

const title = pathnameToTitle(router.pathname);

return (
<header className='mobile:h-[3.75rem] border-gray-3 tablet:pl-48 mobile:pl-12 tablet:justify-end fixed top-0 right-0 left-0 z-50 flex h-[4.375rem] w-full items-center justify-between border-b-1 bg-white pl-96'>
<header className='mobile:h-[3.75rem] border-gray-3 tablet:pl-48 mobile:pl-12 tablet:justify-end fixed top-0 right-0 left-0 z-20 flex h-[4.375rem] w-full items-center justify-between border-b-1 bg-white pl-96'>
<div className='tablet:hidden flex gap-2 text-xl font-bold text-black'>
<h1>내 대시보드</h1>
<Image
className='h-4 w-5 self-center'
src={'/icon/mydashboard.svg'}
alt='왕관: 내 대시보드 아이콘'
width={20}
height={16}
/>
<h1>{title ?? dashboardData?.title}</h1>
{isMyDashboard && (
<Image
className='h-4 w-5 self-center'
src={'/icon/mydashboard.svg'}
alt='왕관: 내 대시보드 아이콘'
width={20}
height={16}
/>
)}
</div>
<nav className='mobile:gap-2 flex h-full items-center gap-8'>
<div className='mobile:gap-1.5 flex gap-3'>
Expand All @@ -56,18 +83,18 @@ export default function DashboardHeader(): ReactNode {
</button>
</div>
<div className='mobile:gap-3 flex h-full gap-6'>
<ProfileList />
<HeaderDropdown
nickname={'권수형'}
profileColor={'red'}
profileLabel={'K'}
/>
{dashboardId && myInfo && (
<ProfileList dashboardId={dashboardId} myId={myInfo.id} />
)}
{myInfo && <HeaderProfileDropdwon myNickname={myInfo.nickname} />}
</div>
<InviteMemberModal
isOpen={isModalOpen}
onClose={handleCloseModal}
onSubmit={handleSubmitInviteMember}
/>
<ModalPortal>
<InviteMemberModal
isOpen={isModalOpen}
onClose={handleCloseModal}
onSubmit={handleSubmitInviteMember}
/>
</ModalPortal>
</nav>
</header>
);
Expand Down Expand Up @@ -99,3 +126,18 @@ function AddBoxIcon() {
</svg>
);
}

const pathnameToTitle = (pathname: string) => {
switch (pathname) {
case '/mydashboard': {
return '나의 대시보드';
}
case '/mypage': {
return '계정관리';
}
default: {
return null;
break;
}
}
};
59 changes: 46 additions & 13 deletions src/components/ui/dashboard-header/profile-list.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,54 @@
import type { ReactNode } from 'react';
import ChipProfile from '@/components/ui/chip/chip-profile';
import { useFetch } from '@/hooks/useAsync';
import useIsBreakPoint from '@/hooks/useIsBreakPoint';
import { getMemberList } from '@/lib/members/api';

export default function ProfileList({
dashboardId,
myId,
}: {
dashboardId: string;
myId: number;
}): ReactNode {
const { data, loading, error } = useFetch({
asyncFunction: () => getMemberList({ dashboardId: Number(dashboardId) }),
deps: [dashboardId],
});
const isTablet = useIsBreakPoint(80);

if (!data || error) {
return null;
}
const maxDisplayLength = isTablet ? 2 : 4;
const excessNumber = data.totalCount - maxDisplayLength;

export default function ProfileList(): ReactNode {
return (
<ul className='flex items-center **:not-first:-ml-3'>
{Array(2)
.fill('0')
.map((num: number) => {
return (
<li key={`${crypto.randomUUID()}-${String(num)}`}>
<ChipProfile label={'Y'} size='lg' color='yellow' />
</li>
);
})}
<li>
<ChipProfile label={`+${String(2)}`} size='lg' color='red' />
</li>
{data.members.slice(0, maxDisplayLength).map((member) => {
if (member.userId === myId) {
return;
}

return (
<li key={member.id}>
<ChipProfile
label={member.nickname.slice(0, 1)}
size='lg'
color='yellow'
/>
</li>
);
})}
{excessNumber > 0 && (
<li>
<ChipProfile
label={`+${String(excessNumber)}`}
size='lg'
color='red'
/>
</li>
)}
</ul>
);
}
9 changes: 9 additions & 0 deletions src/components/ui/modal/modal-portal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { ReactNode } from 'react';
import { createPortal } from 'react-dom';

export default function Portal({ children }: { children: ReactNode }) {
const element =
typeof window !== 'undefined' && document.querySelector('#modal-portal');

return element && children ? createPortal(children, element) : null;
}
Loading