Skip to content

Commit d8ec6fc

Browse files
authored
Merge pull request #331 from boostcampwm2023/fix/backlog-page
fix: 백로그 페이지 기능 수정
2 parents 4aa3861 + a7c0b4e commit d8ec6fc

File tree

9 files changed

+288
-126
lines changed

9 files changed

+288
-126
lines changed

frontend/src/components/backlog/EpicBlock.tsx

+5
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,20 @@ interface EpicBlockProps {
1010
epic: EpicCategoryDTO;
1111
showStoryList: boolean;
1212
onShowStoryList: () => void;
13+
lastRankValue: string;
1314
}
1415

1516
const EpicBlock = ({
1617
storyExist,
1718
epic,
1819
showStoryList,
1920
onShowStoryList,
21+
lastRankValue,
2022
}: EpicBlockProps) => {
2123
const {
2224
open: epicUpdating,
2325
handleOpen: handleEpicUpdateOpen,
26+
handleClose: handleEpicUpdateClose,
2427
dropdownRef: epicRef,
2528
} = useDropdownState();
2629

@@ -66,6 +69,8 @@ const EpicBlock = ({
6669
selectedEpic={epic}
6770
epicList={[epic]}
6871
onEpicChange={() => {}}
72+
lastRankValue={lastRankValue}
73+
onCloseDropdown={handleEpicUpdateClose}
6974
/>
7075
)}
7176
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import Plus from "../../assets/icons/plus.svg?react";
2+
3+
interface EpicCreateButtonProps {
4+
onClick: () => void;
5+
}
6+
7+
const EpicCreateButton = ({ onClick }: EpicCreateButtonProps) => (
8+
<button
9+
type="button"
10+
className="flex items-center justify-center w-full gap-2 py-1 rounded-md bg-middle-green"
11+
onClick={onClick}
12+
>
13+
<Plus width={24} height={24} stroke="white" />
14+
<span className="text-white">Epic 생성하기</span>
15+
</button>
16+
);
17+
18+
export default EpicCreateButton;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { ChangeEvent, useRef, useState } from "react";
2+
import { useOutletContext } from "react-router-dom";
3+
import { Socket } from "socket.io-client";
4+
import { LexoRank } from "lexorank";
5+
import useEpicEmitEvent from "../../hooks/pages/backlog/useEpicEmitEvent";
6+
import { CATEGORY_COLOR } from "../../constants/backlog";
7+
import getNewColor from "../../utils/getNewColor";
8+
import Check from "../../assets/icons/check.svg?react";
9+
import Closed from "../../assets/icons/closed.svg?react";
10+
11+
interface EpicCreateFormProps {
12+
onCloseClick: () => void;
13+
}
14+
15+
const EpicCreateForm = ({ onCloseClick }: EpicCreateFormProps) => {
16+
const [value, setValue] = useState("");
17+
const [epicColor] = useState(getNewColor(Object.keys(CATEGORY_COLOR)));
18+
const { socket }: { socket: Socket } = useOutletContext();
19+
const { emitEpicCreateEvent } = useEpicEmitEvent(socket);
20+
21+
const inputElementRef = useRef<HTMLInputElement | null>(null);
22+
23+
const handleInputChange = ({ target }: ChangeEvent<HTMLInputElement>) => {
24+
const { value } = target;
25+
26+
setValue(value);
27+
};
28+
29+
const handleSubmit = () => {
30+
if (value.length > 10) {
31+
alert("에픽 이름은 10자 이하여야 합니다.");
32+
return;
33+
}
34+
35+
emitEpicCreateEvent({
36+
name: value,
37+
color: epicColor,
38+
rankValue: LexoRank.middle().toString(),
39+
});
40+
setValue("");
41+
onCloseClick();
42+
};
43+
44+
return (
45+
<form className="flex items-center w-full py-1" onSubmit={handleSubmit}>
46+
<input
47+
className="w-full mr-3 bg-gray-200 rounded-sm focus:outline-none hover:cursor-pointer"
48+
type="text"
49+
ref={inputElementRef}
50+
onChange={handleInputChange}
51+
/>
52+
<div className="flex items-center gap-2">
53+
<button
54+
className="flex items-center justify-center w-6 h-6 rounded-md bg-confirm-green"
55+
type="button"
56+
onClick={handleSubmit}
57+
>
58+
<Check width={20} height={20} stroke="white" />
59+
</button>
60+
<button
61+
className="flex items-center justify-center w-6 h-6 rounded-md bg-error-red"
62+
type="button"
63+
onClick={onCloseClick}
64+
>
65+
<Closed stroke="white" />
66+
</button>
67+
</div>
68+
</form>
69+
);
70+
};
71+
72+
export default EpicCreateForm;

frontend/src/components/backlog/EpicDropdown.tsx

+34-16
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,16 @@ interface EpicDropdownProps {
1818
selectedEpic?: EpicCategoryDTO;
1919
epicList: EpicCategoryDTO[];
2020
onEpicChange: (epicId: number | undefined) => void;
21+
lastRankValue?: string;
22+
onCloseDropdown: () => void;
2123
}
2224

2325
const EpicDropdown = ({
2426
selectedEpic,
2527
epicList,
2628
onEpicChange,
29+
lastRankValue,
30+
onCloseDropdown,
2731
}: EpicDropdownProps) => {
2832
const { socket }: { socket: Socket } = useOutletContext();
2933
const { emitEpicCreateEvent } = useEpicEmitEvent(socket);
@@ -39,26 +43,29 @@ const EpicDropdown = ({
3943
setValue(value);
4044
};
4145

46+
const handleCreateButtonClick = () => {
47+
if (value.length > 10) {
48+
alert("에픽 이름은 10자 이하여야 합니다.");
49+
return;
50+
}
51+
52+
const rankValue =
53+
lastRankValue !== undefined
54+
? LexoRank.parse(lastRankValue).genNext().toString()
55+
: LexoRank.middle().toString();
56+
57+
emitEpicCreateEvent({ name: value, color: epicColor, rankValue });
58+
setValue("");
59+
setEpicColor(getNewColor(Object.keys(CATEGORY_COLOR)));
60+
};
61+
4262
const handleEnterKeydown = (event: React.KeyboardEvent) => {
4363
if (event.nativeEvent.isComposing) {
4464
return;
4565
}
4666

4767
if (event.key === "Enter" && value) {
48-
if (value.length > 10) {
49-
alert("에픽 이름은 10자 이하여야 합니다.");
50-
return;
51-
}
52-
53-
const rankValue = epicList.length
54-
? LexoRank.parse(epicList[epicList.length - 1].rankValue)
55-
.genNext()
56-
.toString()
57-
: LexoRank.middle().toString();
58-
59-
emitEpicCreateEvent({ name: value, color: epicColor, rankValue });
60-
setValue("");
61-
setEpicColor(getNewColor(Object.keys(CATEGORY_COLOR)));
68+
handleCreateButtonClick();
6269
}
6370
};
6471

@@ -76,12 +83,20 @@ const EpicDropdown = ({
7683
}
7784
};
7885

86+
const handleESCClick = (event: KeyboardEvent) => {
87+
if (event.key === "Escape") {
88+
onCloseDropdown();
89+
}
90+
};
91+
7992
useEffect(() => {
8093
socket.on("backlog", handleEpicEvent);
8194
inputElementRef.current?.focus();
95+
window.addEventListener("keydown", handleESCClick);
8296

8397
return () => {
8498
socket.off("backlog", handleEpicEvent);
99+
window.removeEventListener("keydown", handleESCClick);
85100
};
86101
}, []);
87102

@@ -107,10 +122,13 @@ const EpicDropdown = ({
107122
/>
108123
</div>
109124
{value ? (
110-
<div className="flex items-center gap-2 p-1">
125+
<button
126+
className="flex items-center gap-2 p-1"
127+
onClick={handleCreateButtonClick}
128+
>
111129
<span>생성</span>
112130
<CategoryChip content={value} bgColor={epicColor} />
113-
</div>
131+
</button>
114132
) : (
115133
<ul className="max-h-[16rem] overflow-y-auto scrollbar-thin pt-1">
116134
{...epicList.map((epic) => (

frontend/src/components/backlog/StoryBlock.tsx

+8-3
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ const StoryBlock = ({
7272
const {
7373
open: deleteMenuOpen,
7474
handleOpen: handleDeleteMenuOpen,
75-
7675
dropdownRef: blockRef,
7776
} = useDropdownState();
7877
const { emitStoryUpdateEvent, emitStoryDeleteEvent } =
@@ -180,6 +179,12 @@ const StoryBlock = ({
180179
selectedEpic={epic}
181180
epicList={epicList}
182181
onEpicChange={updateEpic}
182+
lastRankValue={
183+
epicList.length
184+
? epicList[epicList.length - 1].rankValue
185+
: undefined
186+
}
187+
onCloseDropdown={handleEpicUpdateClose}
183188
/>
184189
)}
185190
</div>
@@ -220,7 +225,7 @@ const StoryBlock = ({
220225
/>
221226
</div>
222227
<div
223-
className="flex items-center gap-1 w-[4rem] mr-[2.76rem] text-right hover:cursor-pointer"
228+
className="flex items-center gap-1 w-[4.5rem] mr-[2.76rem] text-right hover:cursor-pointer"
224229
onClick={() => handlePointUpdatingOpen(true)}
225230
ref={pointRef}
226231
>
@@ -248,7 +253,7 @@ const StoryBlock = ({
248253
</div>
249254
</div>
250255
{deleteMenuOpen && (
251-
<div className="absolute px-2 py-1 bg-white rounded-md shadow-box">
256+
<div className="absolute z-10 px-2 py-1 bg-white rounded-md shadow-box">
252257
<button
253258
className="flex items-center w-full gap-3"
254259
type="button"

frontend/src/components/backlog/StoryCreateForm.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,9 @@ const StoryCreateForm = ({
121121
>
122122
{!epic ? (
123123
<div
124-
className="w-[5rem] min-h-[1.75rem] bg-light-gray rounded-md mr-7 hover:cursor-pointer relative"
124+
className={`w-[5rem] min-h-[1.75rem] rounded-md mr-7 hover:cursor-pointer relative ${
125+
epicId ? "" : "bg-light-gray"
126+
}`}
125127
onClick={handleEpicColumnClick}
126128
ref={dropdownRef}
127129
>
@@ -136,6 +138,7 @@ const StoryCreateForm = ({
136138
selectedEpic={selectedEpic}
137139
epicList={epicList}
138140
onEpicChange={handleEpicChange}
141+
onCloseDropdown={handleClose}
139142
/>
140143
)}
141144
</div>

frontend/src/components/backlog/TaskBlock.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ const TaskBlock = ({
228228
)}
229229
</div>
230230
<div
231-
className="w-16 min-h-[1.5rem] hover:cursor-pointer"
231+
className="w-16 min-h-[1.5rem] hover:cursor-pointer text-right"
232232
ref={expectedTimeRef}
233233
onClick={() => handleExpectedTimeUpdating(true)}
234234
>
@@ -240,7 +240,7 @@ const TaskBlock = ({
240240
/>
241241
</div>
242242
<div
243-
className="w-16 min-h-[1.5rem] hover:cursor-pointer"
243+
className="w-16 min-h-[1.5rem] hover:cursor-pointer text-right"
244244
ref={actualTimeRef}
245245
onClick={() => handleActualTimeUpdating(true)}
246246
>
@@ -264,7 +264,7 @@ const TaskBlock = ({
264264
</div>
265265
</div>
266266
{deleteMenuOpen && (
267-
<div className="absolute px-2 py-1 bg-white rounded-md shadow-box">
267+
<div className="absolute z-10 px-2 py-1 bg-white rounded-md shadow-box">
268268
<button
269269
className="flex items-center w-full gap-3"
270270
type="button"

0 commit comments

Comments
 (0)