Skip to content

Commit

Permalink
Merge pull request #219 from Nexters/refac/show-detail-layout
Browse files Browse the repository at this point in the history
refac: 공연 상세는 레이지 로딩 하지 않으며 레이웃은 라우터에서 주입
  • Loading branch information
alstn2468 authored Oct 15, 2024
2 parents 1b03a1f + 6930f63 commit faa09a9
Show file tree
Hide file tree
Showing 12 changed files with 225 additions and 197 deletions.
31 changes: 21 additions & 10 deletions apps/admin/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,6 @@ import {
OAuthKakaoPage,
HomePage,
ShowAddCompletePage,
ShowEnterancePage,
ShowInfoPage,
ShowReservationPage,
ShowSettlementPage,
ShowTicketPage,
SignUpCompletePage,
SitePolicyPage,
GiftRegisterPage,
Expand All @@ -39,6 +34,12 @@ import {
import ShowAddPage from './pages/ShowAddPage';
import { Suspense } from 'react';
import { domAnimation, LazyMotion } from 'framer-motion';
import ShowDetailLayout from './components/ShowDetailLayout';
import ShowInfoPage from './pages/ShowInfoPage';
import ShowTicketPage from './pages/ShowTicketPage';
import ShowReservationPage from './pages/ShowReservationPage';
import ShowSettlementPage from './pages/ShowSettlementPage';
import ShowEnterancePage from './pages/ShowEnterancePage';

setDefaultOptions({ locale: ko });

Expand Down Expand Up @@ -125,15 +126,25 @@ const privateRoutes = [
{ path: PATH.HOME, element: <HomePage /> },
{ path: PATH.SHOW_ADD, element: <ShowAddPage step="info" /> },
{ path: PATH.SHOW_ADD_TICKET, element: <ShowAddPage step="ticket" /> },
{ path: PATH.SHOW_INFO, element: <ShowInfoPage /> },
{ path: PATH.SHOW_TICKET, element: <ShowTicketPage /> },
{ path: PATH.SHOW_RESERVATION, element: <ShowReservationPage /> },
{ path: PATH.SHOW_ENTRANCE, element: <ShowEnterancePage /> },
{ path: PATH.SHOW_SETTLEMENT, element: <ShowSettlementPage /> },
{
path: PATH.SHOW_ADD_COMPLETE,
element: <ShowAddCompletePage />,
},
{
path: '/',
element: (
<ShowDetailLayout>
<Outlet />
</ShowDetailLayout>
),
children: [
{ path: PATH.SHOW_INFO, element: <ShowInfoPage /> },
{ path: PATH.SHOW_TICKET, element: <ShowTicketPage /> },
{ path: PATH.SHOW_RESERVATION, element: <ShowReservationPage /> },
{ path: PATH.SHOW_ENTRANCE, element: <ShowEnterancePage /> },
{ path: PATH.SHOW_SETTLEMENT, element: <ShowSettlementPage /> },
],
},
],
},
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ const ShowCastInfoFormDialogContent = ({ onDelete, prevShowCastInfo, onSave }: P
<Styled.Row key={field._id}>
<Controller
control={control}
defaultValue={field.userCode}
render={({ field: { onChange, onBlur } }) => {
const value = field.userCode;
const isError = Boolean(
Expand Down
257 changes: 128 additions & 129 deletions apps/admin/src/components/ShowDetailLayout/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { useMyHostInfo, useShowLastSettlementEvent, useShowSettlementInfo } from '@boolti/api';
import {
useMyHostInfo,
useShowDetail,
useShowLastSettlementEvent,
useShowSettlementInfo,
} from '@boolti/api';
import { ArrowLeftIcon } from '@boolti/icon';
import { Setting } from '@boolti/icon/src/components/Setting.tsx';
import { useDialog } from '@boolti/ui';
import { palette, useDialog } from '@boolti/ui';
import { useTheme } from '@emotion/react';
import { useInView } from 'react-intersection-observer';
import { useMatch, useNavigate, useParams } from 'react-router-dom';
Expand All @@ -14,7 +19,7 @@ import Layout from '../Layout/index.tsx';
import Styled from './ShowDetailLayout.styles.ts';
import AuthoritySettingDialogContent from '../AuthoritySettingDialogContent';
import { HostListItem, HostType } from '@boolti/api/src/types/host.ts';
import { atom, useAtom } from 'jotai';
import { atom, useAtom, useAtomValue } from 'jotai';
import { useEffect } from 'react';
import { useDeviceWidth } from '~/hooks/useDeviceWidth.ts';
import ProfileDropdown from '../ProfileDropdown/index.tsx';
Expand All @@ -27,51 +32,64 @@ const settlementTooltipText = {
};

interface ShowDetailLayoutProps {
showName: string;
children?: React.ReactNode;
onClickMiddleware?: () => Promise<boolean>;
}

export const myHostInfoAtom = atom<HostListItem | null>(null);

const ShowDetailLayout = ({ showName, children, onClickMiddleware }: ShowDetailLayoutProps) => {
const { ref: topObserverRef, inView: topInView } = useInView({
threshold: 1,
initialInView: true,
});
const { ref: headerObserverRef, inView: headerInView } = useInView({
threshold: 0.01,
initialInView: true,
});
const theme = useTheme();
const navigate = useNavigate();
export const middlewareAtom = atom<(() => Promise<boolean>) | undefined>(undefined);

interface TabItemProps {
type: 'INFO' | 'TICKET' | 'RESERVATION' | 'ENTRANCE' | 'SETTLEMENT';
}

const matchTargets: Record<TabItemProps['type'], string> = {
INFO: PATH.SHOW_INFO,
TICKET: PATH.SHOW_TICKET,
RESERVATION: PATH.SHOW_RESERVATION,
ENTRANCE: PATH.SHOW_ENTRANCE,
SETTLEMENT: PATH.SHOW_SETTLEMENT,
};

const toTargets = {
INFO: HREF.SHOW_INFO,
TICKET: HREF.SHOW_TICKET,
RESERVATION: HREF.SHOW_RESERVATION,
ENTRANCE: HREF.SHOW_ENTRANCE,
SETTLEMENT: HREF.SHOW_SETTLEMENT,
} as const;

const label = {
INFO: '공연 기본 정보',
TICKET: '티켓 관리',
RESERVATION: '방문자 관리',
ENTRANCE: '입장 관리',
SETTLEMENT: '정산 관리',
};

const tooltipStyle = {
color: palette.grey.w,
padding: '6px 8px',
backgroundColor: palette.grey.g90,
borderRadius: '4px',
boxShadow: `0px 4px 10px 0px ${palette.shadow}`,
fontWeight: '400',
fontStyle: 'normal',
lineHeight: '18px',
fontDisplay: 'auto',
fontSize: '12px',
};

const TabItem = ({ type }: TabItemProps) => {
const params = useParams<{ showId: string }>();
const matchInfoTab = useMatch(PATH.SHOW_INFO);
const matchTicketTab = useMatch(PATH.SHOW_TICKET);
const matchReservationTab = useMatch(PATH.SHOW_RESERVATION);
const matchEntryTab = useMatch(PATH.SHOW_ENTRANCE);
const matchSettlementTab = useMatch(PATH.SHOW_SETTLEMENT);
const authoritySettingDialog = useDialog();
const showId = Number(params!.showId);
const [, setMyHostInfo] = useAtom(myHostInfoAtom);
const deviceWidth = useDeviceWidth();
const isMobile = deviceWidth < parseInt(theme.breakpoint.mobile, 10);
const { data: myHostInfoData } = useMyHostInfo(showId);
const { data: lastSettlementEvent } = useShowLastSettlementEvent(showId);

const { data: settlementInfo } = useShowSettlementInfo(showId);
const { data: lastSettlementEvent } = useShowLastSettlementEvent(showId);

const tooltipStyle = {
color: theme.palette.grey.w,
padding: '6px 8px',
backgroundColor: theme.palette.grey.g90,
borderRadius: '4px',
boxShadow: `0px 4px 10px 0px ${theme.palette.shadow}`,
fontWeight: '400',
fontStyle: 'normal',
lineHeight: '18px',
fontDisplay: 'auto',
fontSize: '12px',
};
const match = useMatch(matchTargets[type]);
const navigate = useNavigate();
const middleware = useAtomValue(middlewareAtom);

const isSettlementInfoEmpty =
settlementInfo?.bankAccount === null ||
Expand All @@ -94,12 +112,74 @@ const ShowDetailLayout = ({ showName, children, onClickMiddleware }: ShowDetailL
return false;
})();

return (
<Styled.TabItem
active={match !== null}
id={type === 'SETTLEMENT' ? 'settlement-page-tooltip' : undefined}
onClick={async () => {
if (!params.showId) return;

if (middleware && !(await middleware())) {
return;
}

navigate(toTargets[type](params.showId));
}}
>
{label[type]}
{isTooltipVisible && (
<Tooltip
content={settlementTooltipText[lastSettlementEvent?.settlementEventType ?? 'DEFAULT']}
anchorSelect="#settlement-page-tooltip"
isOpen
style={tooltipStyle}
className="tooltip"
place="top"
positionStrategy="fixed"
offset={0}
opacity={0.85}
/>
)}
</Styled.TabItem>
);
};

const ShowDetailLayout = ({ children }: ShowDetailLayoutProps) => {
const { ref: topObserverRef, inView: topInView } = useInView({
threshold: 1,
initialInView: true,
});
const { ref: headerObserverRef, inView: headerInView } = useInView({
threshold: 0.01,
initialInView: true,
});
const theme = useTheme();
const navigate = useNavigate();
const params = useParams<{ showId: string }>();

const authoritySettingDialog = useDialog();
const showId = Number(params!.showId);

const [, setMyHostInfo] = useAtom(myHostInfoAtom);

const deviceWidth = useDeviceWidth();
const isMobile = deviceWidth < parseInt(theme.breakpoint.mobile, 10);

const { data: show } = useShowDetail(showId);
const { data: myHostInfoData } = useMyHostInfo(showId);

const middleware = useAtomValue(middlewareAtom);

useEffect(() => {
if (myHostInfoData) {
setMyHostInfo({ ...myHostInfoData });
}
}, [myHostInfoData, setMyHostInfo]);

if (!show || !myHostInfoData) {
return null;
}

return (
<>
<Styled.TopObserver ref={topObserverRef} />
Expand All @@ -112,7 +192,7 @@ const ShowDetailLayout = ({ showName, children, onClickMiddleware }: ShowDetailL
<Styled.BackButton
type="button"
onClick={async () => {
if (onClickMiddleware && !(await onClickMiddleware())) {
if (middleware && !(await middleware())) {
return;
}

Expand All @@ -128,7 +208,9 @@ const ShowDetailLayout = ({ showName, children, onClickMiddleware }: ShowDetailL
/>
<Styled.HeaderContent>
<Styled.ShowNameWrapper>
<Styled.ShowName size={headerInView ? 'big' : 'small'}>{showName}</Styled.ShowName>
<Styled.ShowName size={headerInView ? 'big' : 'small'}>
{show?.name}
</Styled.ShowName>
{myHostInfoData?.type !== HostType.SUPPORTER && (
<Styled.AuthorSettingButton
type="button"
Expand All @@ -154,94 +236,11 @@ const ShowDetailLayout = ({ showName, children, onClickMiddleware }: ShowDetailL
</Styled.ShowNameWrapper>
<Styled.TabContainer>
<Styled.Tab>
<Styled.TabItem
active={matchInfoTab !== null}
onClick={async () => {
if (!params.showId) return;

if (onClickMiddleware && !(await onClickMiddleware())) {
return;
}

navigate(HREF.SHOW_INFO(params.showId));
}}
>
공연 기본 정보
</Styled.TabItem>
<Styled.TabItem
active={matchTicketTab !== null}
onClick={async () => {
if (!params.showId) return;

if (onClickMiddleware && !(await onClickMiddleware())) {
return;
}

navigate(HREF.SHOW_TICKET(params.showId));
}}
>
티켓 관리
</Styled.TabItem>
<Styled.TabItem
active={matchReservationTab !== null}
onClick={async () => {
if (!params.showId) return;

if (onClickMiddleware && !(await onClickMiddleware())) {
return;
}

navigate(HREF.SHOW_RESERVATION(params.showId));
}}
>
방문자 관리
</Styled.TabItem>
<Styled.TabItem
active={matchEntryTab !== null}
onClick={async () => {
if (!params.showId) return;

if (onClickMiddleware && !(await onClickMiddleware())) {
return;
}

navigate(HREF.SHOW_ENTRANCE(params.showId));
}}
>
입장 관리
</Styled.TabItem>
<Styled.TabItem
active={matchSettlementTab !== null}
onClick={async () => {
if (!params.showId) return;

if (onClickMiddleware && !(await onClickMiddleware())) {
return;
}

navigate(HREF.SHOW_SETTLEMENT(params.showId));
}}
id="settlement-page-tooltip"
>
정산 관리
{isTooltipVisible && (
<Tooltip
content={
settlementTooltipText[
lastSettlementEvent?.settlementEventType ?? 'DEFAULT'
]
}
anchorSelect="#settlement-page-tooltip"
isOpen
style={tooltipStyle}
className="tooltip"
place="top"
positionStrategy="fixed"
offset={0}
opacity={0.85}
/>
)}
</Styled.TabItem>
<TabItem type="INFO" />
<TabItem type="TICKET" />
<TabItem type="RESERVATION" />
<TabItem type="ENTRANCE" />
<TabItem type="SETTLEMENT" />
</Styled.Tab>
</Styled.TabContainer>
</Styled.HeaderContent>
Expand Down
Loading

0 comments on commit faa09a9

Please sign in to comment.