Skip to content

Commit

Permalink
[AUD-115] 코스 내 장소 순서 변경, 장소 이름 변경 기능 연동 (#70)
Browse files Browse the repository at this point in the history
* ✨ 코스 이름 수정 모달 추가, 순서 변경 로직 추가

* ✨ marker 관련 커스텀 이벤트 타입 추가

* ✨ 핀 장소 명 수정 모달 및 기능 연동

* 🐛 console.log 제거

* 🚑 유저 접속 시 접속자 목록에 표기되도록 수정

* 🐛 경로 그리기 기능 일부 수정

* ✨ 코스 순서 변경 기능 적용

* 🐛 Eslint 에서 지적한 에러 수정
  • Loading branch information
RookieAND authored Mar 2, 2024
1 parent dac975c commit 34a0495
Show file tree
Hide file tree
Showing 17 changed files with 428 additions and 172 deletions.
6 changes: 5 additions & 1 deletion global.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { CourseSocketPubType } from '@/apis/course/type';
import { UserSocketSubType } from '@/apis/user/type';
import type { MarkerType } from '@/types/map';

// T-MAP API NameSpace
Expand All @@ -12,11 +13,14 @@ declare global {
interface CustomEventMap {
'marker:create': CustomEvent<MarkerType>;
'marker:remove': CustomEvent<string>;
'marker:reorder': CustomEvent<Pick<MarkerType, 'pinId' | 'sequence'>>;
'marker:rename': CustomEvent<Pick<MarkerType, 'pinId' | 'pinName'>>;
'infoWindow:confirm': CustomEvent<
Omit<CourseSocketPubType['addition'], 'courseId'>
>;
'infoWindow:revert': CustomEvent<string>,
'infoWindow:revert': CustomEvent<string>;
'duration:update': CustomEvent<number>;
'user:list': CustomEvent<UserSocketSubType['getUserList']>;
}

interface WindowEventMap extends CustomEventMap {}
Expand Down
2 changes: 1 addition & 1 deletion src/apis/course/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export interface CourseSocketPubType {
};
modifySequence: {
pinId: string;
sequence: string;
order: number;
};
removal: {
pinId: string;
Expand Down
2 changes: 2 additions & 0 deletions src/apis/tmap/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export interface TmapResponseType {
index: number;
totalDistance: number;
totalTime: number;
pointType: 'S' | 'E' | 'B1' | 'B2' | 'B3' | 'N'
};
}[];
};
Expand All @@ -69,6 +70,7 @@ export interface TmapResponseType {
index: number;
totalDistance: number;
totalTime: number;
pointType: 'S' | 'E' | 'B1' | 'B2' | 'B3' | 'N'
};
}[];
};
Expand Down
7 changes: 7 additions & 0 deletions src/apis/user/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,10 @@ import { UserType } from '@/types';
export interface UserResponseType {
getInformation: UserType;
}

export interface UserSocketSubType {
getUserList: {
total: number;
users: Pick<UserType, 'userId'>[];
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ const MakeNewCourseModal = () => {
const navigate = useNavigate();

const handleSaveButtonClick = async () => {
const { data } = await makeNewCourse(courseName);
const { courseId } = await makeNewCourse(courseName);

closeModal();
setToast('새 코스가 만들어졌어요');
navigate(`/course/${data.courseId}`);
navigate(`/course/${courseId}`);
};

const handleNewCourseNameChange = ({
Expand Down
2 changes: 1 addition & 1 deletion src/features/path/path-item/PathItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ const PathItem = ({ marker, order }: PropsType) => {
name={marker.pinName}
address={marker.address}
/>
<ThreeDotButton pinId={marker.pinId} />
<ThreeDotButton pinId={marker.pinId} pinName={marker.pinName} />
</Reorder.Item>
);
};
Expand Down
20 changes: 17 additions & 3 deletions src/features/path/path-item/ThreeDotButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,23 @@ import EyeOpenedIcon from '@/assets/icons/eyeOpened.svg?react';
import ThreeDotIcon from '@/assets/icons/threeDot.svg?react';
import TrashCanIcon from '@/assets/icons/trashCan.svg?react';
import PopOver from '@/components/pop-over';
import PathNameEditModal from '@/features/path/path-name-edit-modal';
import { useDisclosure } from '@/hooks/useDisclosure';
import { useModal } from '@/hooks/useModal';
import { useSocket } from '@/hooks/useSocket';
import { useTmap } from '@/hooks/useTmap';

import * as S from './ThreeDotButton.css';

interface PropsType {
pinId: string;
pinName: string;
}

const ThreeDotButton = ({ pinId }: PropsType) => {
const ThreeDotButton = ({ pinId, pinName }: PropsType) => {
const { courseId } = useParams();
const { tmapModule } = useTmap();
const { openModal } = useModal();

const stompClient = useSocket(Number(courseId));

Expand All @@ -34,7 +38,17 @@ const ThreeDotButton = ({ pinId }: PropsType) => {
};

const handlePinRemove = () => {
stompClient.removePin({ pinId })
stompClient.removePin({ pinId });
};

const handleModifyPinName = () => {
openModal(
<PathNameEditModal
pinId={pinId}
pinName={pinName}
modifyPinName={stompClient.modifyPinName}
/>,
);
};

return (
Expand All @@ -55,7 +69,7 @@ const ThreeDotButton = ({ pinId }: PropsType) => {
</PopOver.Item>
)}

<PopOver.Item>
<PopOver.Item onClick={handleModifyPinName}>
<EditIcon />
<p className={S.text}>장소명 수정</p>
</PopOver.Item>
Expand Down
58 changes: 58 additions & 0 deletions src/features/path/path-name-edit-modal/PathNameEditModal.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { style } from '@vanilla-extract/css';
import { recipe } from '@vanilla-extract/recipes';

import { COLOR } from '@/styles/foundation';
import { sprinkles } from '@/styles/sprinkle.css';

export const modalHeader = style({
width: '100%',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
});

export const couseNameInput = style([
sprinkles({ typography: 'Regular15' }),
{
border: `1px solid ${COLOR.Gray200}`,
width: '100%',
flex: 1,
padding: '8px 12px',
borderRadius: '6px',
color: COLOR.Gray900,

'::placeholder': {
color: COLOR.Gray400,
},
},
]);

export const editButton = recipe({
base: [
sprinkles({ typography: 'Bold17' }),
{
height: '56px',
flex: 1,
textAlign: 'center',
borderRadius: '10px',
backgroundColor: COLOR.Gray900,
color: COLOR.MonoWhite,
},
],
variants: {
isButtonDisabled: {
true: {
backgroundColor: COLOR.Gray300,
cursor: 'default',
},
false: {
backgroundColor: COLOR.Gray900,
},
},
},
});

export const modalCloseButton = style({
padding: '6px',
color: COLOR.Gray400,
});
69 changes: 69 additions & 0 deletions src/features/path/path-name-edit-modal/PathNameEditModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { useState } from 'react';

import CloseIcon from '@/assets/icons/close.svg?react';
import Modal from '@/components/modal/Modal';
import { useModal } from '@/hooks/useModal';
import { useToast } from '@/hooks/useToast';
import type { MarkerType } from '@/types';

import * as S from './PathNameEditModal.css';

interface PropsType {
pinId: string;
pinName: string;
modifyPinName: (props: Pick<MarkerType, 'pinId' | 'pinName'>) => void;
}

const PathNameEditModal = ({ pinId, pinName, modifyPinName }: PropsType) => {
const { closeModal } = useModal();
const { setToast } = useToast();

const [isButtonDisabled, setIsButtonDisabled] = useState(false);
const [newPathName, setNewPathName] = useState(pinName);

const handleEditButtonClick = () => {
modifyPinName({ pinId, pinName: newPathName });
closeModal();
setToast('코스 이름이 변경되었어요');
};

const handleNewPathNameChange = ({
target,
}: React.ChangeEvent<HTMLInputElement>) => {
if (!target.value) setIsButtonDisabled(true);
else setIsButtonDisabled(false);

setNewPathName(target.value);
};

return (
<Modal>
<div className={S.modalHeader}>
<Modal.Title>코스명 수정</Modal.Title>
<button className={S.modalCloseButton} onClick={closeModal}>
<CloseIcon width={28} height={28} />
</button>
</div>

<Modal.Content>
<input
className={S.couseNameInput}
value={newPathName}
onChange={handleNewPathNameChange}
/>
</Modal.Content>

<Modal.Footer>
<button
className={S.editButton({ isButtonDisabled })}
onClick={handleEditButtonClick}
disabled={isButtonDisabled}
>
수정
</button>
</Modal.Footer>
</Modal>
);
};

export default PathNameEditModal;
3 changes: 3 additions & 0 deletions src/features/path/path-name-edit-modal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import PathNameEditModal from './PathNameEditModal';

export default PathNameEditModal;
67 changes: 56 additions & 11 deletions src/features/path/path-view/PathView.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useContext, useEffect, useState } from 'react';

import { Reorder } from 'framer-motion';
import { LexoRank } from 'lexorank';
import { useParams } from 'react-router-dom';

import { SearchContextValue } from '@/features/search/search-context';
Expand Down Expand Up @@ -29,7 +30,7 @@ const PathView = () => {
} = useGetCourseDetail({ courseId: Number(courseId) });

const [markers, setMarkers] = useState<MarkerType[]>([]);
const { isSearchMode } = useContext(SearchContextValue);
const { isSearchMode } = useContext(SearchContextValue);

useEventListeners('infoWindow:confirm', (event) => {
if (!courseId) return;
Expand All @@ -40,9 +41,7 @@ const PathView = () => {
});

useEventListeners('infoWindow:revert', (event) => {
console.log(event.detail);
const removedMarker = tmapModule?.getMarkerBySequence(event.detail);
console.log(removedMarker);
if (!removedMarker) return;
stompClient.removePin({ pinId: removedMarker.pinId });
});
Expand All @@ -59,26 +58,72 @@ const PathView = () => {
setMarkers(updatedMarkers);
});

useEventListeners('marker:reorder', (event) => {
const { pinId: updatedPinId, sequence } = event.detail;
const updatedMarkers = markers.map(({ pinId, ...rest }) => {
return pinId === updatedPinId
? { ...rest, pinId, sequence }
: { ...rest, pinId };
});
setMarkers(updatedMarkers);
});

useEventListeners('marker:rename', (event) => {
const { pinId: updatedPinId, pinName } = event.detail;
const updatedMarkers = markers.map(({ pinId, ...rest }) => {
return pinId === updatedPinId
? { ...rest, pinId, pinName }
: { ...rest, pinId };
});
setMarkers(updatedMarkers);
});

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

const initMarkerList = pinResList
.map((pin) => tmapModule.createMarker(pin))
.filter((marker): marker is MarkerType => !!marker);


initMarkerList.sort((a, b) => {
const aSequence = LexoRank.parse(a.sequence);
const bSequence = LexoRank.parse(b.sequence);
return aSequence.compareTo(bSequence);
});

setMarkers(initMarkerList);
}, [pinResList, tmapModule]);

const debouncedModifyMarker = debounce((newOrder: MarkerType[]) => {
if (!tmapModule) return;

const modifiedMarkers = newOrder.map((marker, index) => ({
...marker,
sequence: markers[index].sequence,
}));
let sortingType = 0;
newOrder.some((marker, index) => {
const originMarker = markers[index];
const currentSortingType = LexoRank.parse(
originMarker.sequence,
).compareTo(LexoRank.parse(marker.sequence));

if (!sortingType) {
sortingType = currentSortingType
return false;
}

if (sortingType !== currentSortingType) {
let modifiedIndex = 1;
if (currentSortingType === -1) modifiedIndex = index - 1;
if (currentSortingType === 1) modifiedIndex = index;

stompClient.modifyPinSequence({
pinId: newOrder[modifiedIndex].pinId,
order: modifiedIndex,
});
return true;
}
});



tmapModule.reorderMarkers(modifiedMarkers);
setMarkers(modifiedMarkers);
}, REORDER_DELAY);

const handleReorderMarker = (newOrder: MarkerType[]) => {
Expand All @@ -100,7 +145,7 @@ const PathView = () => {
>
{markers.map((marker, index) => (
<PathItem
key={marker.pinId}
key={`${marker.pinId}-${marker.pinName}`}
marker={marker}
order={index + 1}
/>
Expand Down
Loading

0 comments on commit 34a0495

Please sign in to comment.