Skip to content

Commit

Permalink
fix: 공연을 추가할 때 출연진 정보 순서 변경이 올바르게 동작하도록 수정
Browse files Browse the repository at this point in the history
  • Loading branch information
Puterism committed Nov 16, 2024
1 parent f2226bd commit fd493f6
Show file tree
Hide file tree
Showing 6 changed files with 203 additions and 182 deletions.
207 changes: 100 additions & 107 deletions apps/admin/src/components/ShowCastInfo/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,13 @@ import { useRef, useState } from 'react';
import ShowCastInfoFormDialogContent, {
TempShowCastInfoFormInput,
} from '../ShowCastInfoFormDialogContent';
import { ShowCastTeamReadResponse } from '@boolti/api';

export interface CastTeamListDraft extends ShowCastTeamReadResponse {
index: number;
}

interface Props {
showCastInfo: CastTeamListDraft;
showCastInfo: TempShowCastInfoFormInput;
index: number;
onSave: (value: TempShowCastInfoFormInput) => Promise<void>;
onDropHover: (draggedItemId: number, hoverIndex: number) => void;
onDrop: () => void;
onDrop?: () => void;
onDelete?: () => Promise<void>;
}

Expand All @@ -29,41 +24,41 @@ interface DragItem {

const ShowCastInfo = ({ showCastInfo, index, onSave, onDropHover, onDrop, onDelete }: Props) => {
const ref = useRef<HTMLDivElement>(null)
const [{ isDragging }, drag, preview] = useDrag(() => ({
const [{ isDragging }, drag, preview] = useDrag<DragItem, unknown, { isDragging: boolean }>(() => ({
type: 'castTeam',
previewOptions: {
captureDraggingState: true,
},
item: { id: showCastInfo.id, index: showCastInfo.index },
item: { id: showCastInfo.id, index },
collect: (monitor) => ({
isDragging: monitor.isDragging()
}),
}))
const [, drop] = useDrop<DragItem>({
accept: 'castTeam',
hover(item: DragItem, monitor) {
if (!ref.current) return
if (!monitor.canDrop()) return
if (item.id === showCastInfo.id) return
if (!ref.current) return;
if (!monitor.canDrop()) return;
if (item.id === showCastInfo.id) return;

const dragIndex = item.index
const hoverIndex = index
const dragIndex = item.index;
const hoverIndex = index;

const hoverBoundingRect = ref.current.getBoundingClientRect()
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2
const clientOffset = monitor.getClientOffset()
if (!clientOffset) return
const hoverBoundingRect = ref.current.getBoundingClientRect();
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
const clientOffset = monitor.getClientOffset();
if (!clientOffset) return;

const hoverClientY = clientOffset.y - hoverBoundingRect.top
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) return
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) return
const hoverClientY = clientOffset.y - hoverBoundingRect.top;
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) return;
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) return;

item.index = hoverIndex
item.index = hoverIndex;

onDropHover(item.id, index)
onDropHover(item.id, index);
},
drop() {
onDrop()
onDrop?.()
}
})

Expand All @@ -74,90 +69,88 @@ const ShowCastInfo = ({ showCastInfo, index, onSave, onDropHover, onDrop, onDele

const toggle = () => setIsOpen((prev) => !prev);

preview(drop(ref))

return (
<Styled.Container ref={preview} style={{ opacity: isDragging ? 0.4 : 1 }}>
<div ref={ref}>
<div ref={drop}>
<Styled.Header>
<Styled.HeaderNameWrapper>
<Styled.Handle ref={drag}>
<MenuIcon />
</Styled.Handle>
<Styled.Name>
{showCastInfo.name}
</Styled.Name>
</Styled.HeaderNameWrapper>
<Styled.EditButton
colorTheme="line"
size="bold"
onClick={(e) => {
e.preventDefault();
dialog.open({
title: '출연진 정보 편집',
isAuto: true,
content: (
<ShowCastInfoFormDialogContent
onSave={async (castInfo) => {
try {
await onSave(castInfo);
dialog.close();
} catch {
return new Promise((_, reject) => reject('저장 중 오류가 발생하였습니다.'));
}
}}
prevShowCastInfo={showCastInfo}
onDelete={async () => {
try {
await onDelete?.();
dialog.close();
} catch {
return new Promise((_, reject) => reject('삭제 중 오류가 발생하였습니다.'));
}
}}
/>
),
});
}}
>
<EditIcon />
편집하기
</Styled.EditButton>
</Styled.Header>
{memberLength > 0 && (
<>
<Styled.Cast
animate={{ transition: { type: 'tween' }, height: isOpen ? 'auto' : 0 }}
transition={{ duration: 0.4 }}
initial={{ height: 0, opacity: 1 }}
exit={{ height: 0, opacity: 1 }}
>
{members.map((member) => (
<Styled.CastItem key={member.id}>
{member.userImgPath ? (
<Styled.UserImage
style={{ '--imgPath': `url(${member.userImgPath})` } as React.CSSProperties}
/>
) : (
<UserIcon size={32} />
)}
<Styled.Username>{member.userNickname}</Styled.Username>
<Styled.Rolename>({member.roleName})</Styled.Rolename>
</Styled.CastItem>
))}
</Styled.Cast>
<Styled.CollapseButton
onClick={(e) => {
e.preventDefault();
toggle();
}}
>
{isOpen ? '팀원 리스트 접기' : '팀원 리스트 펼쳐보기'}
{isOpen ? <ChevronUpIcon /> : <ChevronDownIcon />}
</Styled.CollapseButton>
</>
)}
</div>
</div>
<Styled.Container ref={ref} style={{ opacity: isDragging ? 0.4 : 1 }}>
<Styled.Header>
<Styled.HeaderNameWrapper>
<Styled.Handle ref={drag}>
<MenuIcon />
</Styled.Handle>
<Styled.Name>
{showCastInfo.name}
</Styled.Name>
</Styled.HeaderNameWrapper>
<Styled.EditButton
colorTheme="line"
size="bold"
onClick={(e) => {
e.preventDefault();
dialog.open({
title: '출연진 정보 편집',
isAuto: true,
content: (
<ShowCastInfoFormDialogContent
onSave={async (castInfo) => {
try {
await onSave(castInfo);
dialog.close();
} catch {
return new Promise((_, reject) => reject('저장 중 오류가 발생하였습니다.'));
}
}}
prevShowCastInfo={showCastInfo}
onDelete={async () => {
try {
await onDelete?.();
dialog.close();
} catch {
return new Promise((_, reject) => reject('삭제 중 오류가 발생하였습니다.'));
}
}}
/>
),
});
}}
>
<EditIcon />
편집하기
</Styled.EditButton>
</Styled.Header>
{memberLength > 0 && (
<>
<Styled.Cast
animate={{ transition: { type: 'tween' }, height: isOpen ? 'auto' : 0 }}
transition={{ duration: 0.4 }}
initial={{ height: 0, opacity: 1 }}
exit={{ height: 0, opacity: 1 }}
>
{members.map((member) => (
<Styled.CastItem key={member.id}>
{member.userImgPath ? (
<Styled.UserImage
style={{ '--imgPath': `url(${member.userImgPath})` } as React.CSSProperties}
/>
) : (
<UserIcon size={32} />
)}
<Styled.Username>{member.userNickname}</Styled.Username>
<Styled.Rolename>({member.roleName})</Styled.Rolename>
</Styled.CastItem>
))}
</Styled.Cast>
<Styled.CollapseButton
onClick={(e) => {
e.preventDefault();
toggle();
}}
>
{isOpen ? '팀원 리스트 접기' : '팀원 리스트 펼쳐보기'}
{isOpen ? <ChevronUpIcon /> : <ChevronDownIcon />}
</Styled.CollapseButton>
</>
)}
</Styled.Container >
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import { Member, queryKeys, useQueryClient } from '@boolti/api';
import { replaceUserCode } from '~/utils/replace';

export interface TempShowCastInfoFormInput {
id: number;
name: string;
members?: Array<Partial<Member>>;
order?: number;
}

interface Props {
Expand All @@ -19,7 +19,7 @@ interface Props {
onSave: (value: TempShowCastInfoFormInput) => Promise<void>;
}

const ShowCastInfoFormDialogContent = ({ onDelete, prevShowCastInfo, onSave }: Props) => {
const ShowCastInfoFormDialogContent = ({ prevShowCastInfo, onDelete, onSave }: Props) => {
const queryClient = useQueryClient();

const previousShowCastInfoMemberLength = prevShowCastInfo?.members?.length ?? 0;
Expand Down Expand Up @@ -279,13 +279,14 @@ const ShowCastInfoFormDialogContent = ({ onDelete, prevShowCastInfo, onSave }: P
onClick={async (e) => {
e.preventDefault();

const id = prevShowCastInfo?.id ?? -Math.floor(Math.random() * 1000000);
const name = getValues('name');
const members = (getValues('members') ?? []).filter(
(member) => member.userNickname && member.roleName && member.userCode,
);

try {
await onSave({ name, members });
await onSave({ id, name, members });
toast.success(
onDelete ? '출연진 정보를 수정했습니다.' : '출연진 정보를 생성했습니다.',
);
Expand Down
70 changes: 70 additions & 0 deletions apps/admin/src/hooks/useCastTeamListOrder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { useChangeCastTeamOrder } from "@boolti/api";
import { useCallback, useEffect, useState } from "react";
import { TempShowCastInfoFormInput } from "~/components/ShowCastInfoFormDialogContent";

interface UseCastTeamListOrderParams {
showId?: number;
castTeamList?: TempShowCastInfoFormInput[];
onChange?: () => void;
}

const useCastTeamListOrder = (params?: UseCastTeamListOrderParams) => {
const showId = params?.showId;
const castTeamList = params?.castTeamList;
const onChange = params?.onChange;

const [castTeamListDraft, setCastTeamListDraft] = useState<TempShowCastInfoFormInput[]>([]);

const changeCastTeamOrder = useChangeCastTeamOrder();

const changeCastTeamIndex = useCallback((draggedItemId: number, targetIndex: number) => {
setCastTeamListDraft((prevDraft) => {
if (!prevDraft) return prevDraft;

const draggedItemIndex = prevDraft.findIndex(({ id }) => id === draggedItemId);
if (draggedItemIndex === -1 || targetIndex < 0 || targetIndex >= prevDraft.length) {
return prevDraft;
}

const nextDraft = [...prevDraft];
const [draggedItem] = nextDraft.splice(draggedItemIndex, 1);
nextDraft.splice(targetIndex, 0, draggedItem);

return nextDraft;
})
}, [])

const castTeamDropHoverHandler = useCallback((draggedItemId: number, hoverIndex: number) => {
changeCastTeamIndex(draggedItemId, hoverIndex);
}, [changeCastTeamIndex]);

const castTeamDropHandler = useCallback(async () => {
if (!castTeamListDraft) return;

if (showId !== undefined) {
await changeCastTeamOrder.mutateAsync({
showId,
body: {
castTeamIds: castTeamListDraft.map(({ id }) => id),
},
});
}

onChange?.();
}, [castTeamListDraft, changeCastTeamOrder, onChange, showId])

useEffect(() => {
if (!castTeamList) return;

setCastTeamListDraft(castTeamList);
}, [castTeamList])

return {
castTeamListDraft,
setCastTeamListDraft,
castTeamDropHoverHandler,
castTeamDropHandler,
}
}

export default useCastTeamListOrder
Loading

0 comments on commit fd493f6

Please sign in to comment.