Skip to content

Commit 61b58f3

Browse files
committed
feat:필터링 단어 추가기능 목api연결 완료(단어 삭제는 목api가 안만들어짐) + 차단 편지목록 퍼블리싱
1 parent ed6b383 commit 61b58f3

File tree

9 files changed

+188
-70
lines changed

9 files changed

+188
-70
lines changed

src/apis/admin.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const patchReport = async (reportId: number, reportRequest: ReportRequest) => {
2525
}
2626
};
2727

28-
//
28+
// badwords
2929
const getBadWords = async (setBadWords: React.Dispatch<React.SetStateAction<BadWords[]>>) => {
3030
try {
3131
const res = await client.get('/api/bad-words');
@@ -36,4 +36,29 @@ const getBadWords = async (setBadWords: React.Dispatch<React.SetStateAction<BadW
3636
}
3737
};
3838

39-
export { getReports, patchReport, getBadWords };
39+
const postBadWords = async (badWordsRequest: BadWords, callBack?: () => void) => {
40+
try {
41+
const res = await client.post('/api/bad-words', badWordsRequest);
42+
if (callBack) callBack();
43+
console.log(res);
44+
} catch (error) {
45+
console.error(error);
46+
}
47+
};
48+
49+
// 내 상상대로 만든 필터링 단어 취소 버튼
50+
const patchBadWords = async (
51+
badWordId: number,
52+
badWordsRequest: BadWords,
53+
callBack?: () => void,
54+
) => {
55+
try {
56+
const res = await client.patch(`/api/bad-words/${badWordId}/status`, badWordsRequest);
57+
if (callBack) callBack();
58+
console.log(res);
59+
} catch (error) {
60+
console.error(error);
61+
}
62+
};
63+
64+
export { getReports, patchReport, getBadWords, postBadWords, patchBadWords };

src/pages/Admin/BadWords.tsx

Lines changed: 6 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,12 @@
1-
import { useEffect, useState } from 'react';
2-
3-
import { getBadWords } from '@/apis/admin';
4-
import { AddIcon, AlarmIcon, CancelIcon } from '@/assets/icons';
5-
6-
import AddInputButton from './components/AddInputButton';
7-
import WrapperFrame from './components/WrapperFrame';
8-
import WrapperTitle from './components/WrapperTitle';
1+
import FilteredLetter from './FilteredLetter';
2+
import Filtering from './Filtering';
93

104
const BadWordsManage = () => {
11-
const [badWords, setBadWords] = useState<BadWords[]>([]);
12-
const [addInputShow, setAddInputShow] = useState<boolean>(false);
13-
14-
useEffect(() => {
15-
getBadWords(setBadWords);
16-
}, []);
175
return (
18-
<WrapperFrame>
19-
<WrapperTitle title="필터링 단어 설정" Icon={AlarmIcon} />
20-
<div className="mt-5 flex w-full flex-wrap gap-4">
21-
{badWords.map((badWord, idx) => {
22-
return (
23-
<span
24-
key={idx}
25-
className="flex items-center gap-1.5 rounded-2xl bg-[#C1C1C1] px-4 py-1.5"
26-
>
27-
{badWord.word}
28-
<button>
29-
<CancelIcon className="h-4 w-4" />
30-
</button>
31-
</span>
32-
);
33-
})}
34-
{addInputShow ? (
35-
<AddInputButton setAddInputShow={setAddInputShow} />
36-
) : (
37-
<span className="flex items-center gap-1.5 rounded-2xl bg-[#C1C1C1] px-4 py-1.5">
38-
추가하기
39-
<button
40-
onClick={() => {
41-
setAddInputShow(true);
42-
}}
43-
>
44-
<AddIcon className="h-4 w-4" />
45-
</button>
46-
</span>
47-
)}
48-
</div>
49-
</WrapperFrame>
6+
<>
7+
<Filtering />
8+
<FilteredLetter />
9+
</>
5010
);
5111
};
5212

src/pages/Admin/FilteredLetter.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { AlarmIcon } from '@/assets/icons';
2+
3+
import FilteredLetterListItem from './components/FilteredLetterListItem';
4+
import ListHeaderFrame from './components/ListHeaderFrame';
5+
import WrapperFrame from './components/WrapperFrame';
6+
import WrapperTitle from './components/WrapperTitle';
7+
8+
export default function FilteredLetter() {
9+
const arr = new Array(10).fill(null);
10+
return (
11+
<WrapperFrame className="mt-7">
12+
<WrapperTitle title="필터링 단어로 차단된 편지 목록" Icon={AlarmIcon} />
13+
<section className="mt-5 flex flex-col">
14+
<ListHeaderFrame>
15+
<span className="admin-list-set basis-1/10">ID</span>
16+
<span className="admin-list-set basis-2/10">제보자 이메일</span>
17+
<span className="admin-list-set basis-2/10">작성자 이메일</span>
18+
<span className="admin-list-set basis-2/10">차단 일자</span>
19+
<span className="admin-list-set basis-2/10">포함된 단어</span>
20+
</ListHeaderFrame>
21+
{arr.map((_, idx) => {
22+
return <FilteredLetterListItem key={idx} />;
23+
})}
24+
</section>
25+
</WrapperFrame>
26+
);
27+
}

src/pages/Admin/Filtering.tsx

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { useEffect, useState } from 'react';
2+
3+
import { getBadWords } from '@/apis/admin';
4+
import { AddIcon, AlarmIcon, CancelIcon } from '@/assets/icons';
5+
6+
import AddInputButton from './components/AddInputButton';
7+
import WrapperFrame from './components/WrapperFrame';
8+
import WrapperTitle from './components/WrapperTitle';
9+
10+
export default function Filtering() {
11+
const [badWords, setBadWords] = useState<BadWords[]>([]);
12+
const [addInputShow, setAddInputShow] = useState<boolean>(false);
13+
14+
useEffect(() => {
15+
getBadWords(setBadWords);
16+
}, []);
17+
return (
18+
<WrapperFrame>
19+
<WrapperTitle title="필터링 단어 설정" Icon={AlarmIcon} />
20+
<div className="mt-5 flex w-full flex-wrap gap-4">
21+
{badWords.map((badWord, idx) => {
22+
return (
23+
<span
24+
key={idx}
25+
className="flex items-center gap-1.5 rounded-2xl bg-[#C1C1C1] px-4 py-1.5"
26+
>
27+
{badWord.word}
28+
<button>
29+
<CancelIcon className="h-4 w-4" />
30+
</button>
31+
</span>
32+
);
33+
})}
34+
{addInputShow ? (
35+
<AddInputButton setAddInputShow={setAddInputShow} setBadWords={setBadWords} />
36+
) : (
37+
<span className="flex items-center gap-1.5 rounded-2xl bg-[#C1C1C1] px-4 py-1.5">
38+
추가하기
39+
<button
40+
onClick={() => {
41+
setAddInputShow(true);
42+
}}
43+
>
44+
<AddIcon className="h-4 w-4" />
45+
</button>
46+
</span>
47+
)}
48+
</div>
49+
</WrapperFrame>
50+
);
51+
}

src/pages/Admin/Report.tsx

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useEffect, useState } from 'react';
33
import { getReports } from '@/apis/admin';
44
import { AlarmIcon } from '@/assets/icons';
55

6+
import ListHeaderFrame from './components/ListHeaderFrame';
67
import ReportDetailModal from './components/ReportDetailModal';
78
import ReportHandlingModal from './components/ReportHandlingModal';
89
import ReportListItem from './components/ReportListItem';
@@ -25,15 +26,13 @@ export default function ReportManage() {
2526
<WrapperTitle title="신고 편지 목록" Icon={AlarmIcon} />
2627

2728
<section className="mt-5 flex flex-col">
28-
<div className="bg-primary-3 flex w-full border-b px-6 py-4">
29-
<div className="flex w-[80%] items-center">
30-
<span className="admin-list-set basis-1/10 overflow-ellipsis">ID</span>
31-
<span className="admin-list-set basis-2/10">제보자 이메일</span>
32-
<span className="admin-list-set basis-2/10">작성자 이메일</span>
33-
<span className="admin-list-set basis-2/10">제보 일자</span>
34-
<span className="admin-list-set basis-3/10">제보 사유</span>
35-
</div>
36-
</div>
29+
<ListHeaderFrame>
30+
<span className="admin-list-set basis-1/10 overflow-ellipsis">ID</span>
31+
<span className="admin-list-set basis-2/10">제보자 이메일</span>
32+
<span className="admin-list-set basis-2/10">작성자 이메일</span>
33+
<span className="admin-list-set basis-2/10">제보 일자</span>
34+
<span className="admin-list-set basis-3/10">제보 사유</span>
35+
</ListHeaderFrame>
3736
{reports.map((data, idx) => (
3837
<ReportListItem
3938
key={idx}

src/pages/Admin/components/AddInputButton.tsx

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,37 @@
1-
import { useEffect, useRef } from 'react';
1+
import { useEffect, useRef, useState } from 'react';
22

3+
import { postBadWords } from '@/apis/admin';
34
import { AddIcon } from '@/assets/icons';
45

56
export default function AddInputButton({
67
setAddInputShow,
8+
setBadWords,
79
}: {
810
setAddInputShow: React.Dispatch<React.SetStateAction<boolean>>;
11+
setBadWords: React.Dispatch<React.SetStateAction<BadWords[]>>;
912
}) {
13+
const [inputText, setInputText] = useState<BadWords>({ word: '' });
1014
const inputRef = useRef<HTMLInputElement>(null);
11-
const handleInputWidth = (event: Event) => {
15+
16+
const handleInputWidth = (event: React.FormEvent<HTMLInputElement>) => {
1217
const target = event.target as HTMLInputElement;
1318
target.style.width = '50px';
1419
target.style.width = `${target.scrollWidth}px`;
1520
};
1621

22+
const handlePostBadWords = () => {
23+
if (inputText.word === '') return setAddInputShow(false);
24+
postBadWords(inputText, () => {
25+
setBadWords((cur) => [...cur, inputText]);
26+
setAddInputShow(false);
27+
});
28+
};
29+
1730
useEffect(() => {
1831
const inputElement = inputRef.current;
1932
if (inputElement) {
20-
inputElement.addEventListener('input', handleInputWidth);
2133
inputElement.focus();
2234
}
23-
24-
return () => {
25-
if (inputElement) {
26-
inputElement.removeEventListener('input', handleInputWidth);
27-
}
28-
};
2935
}, []);
3036

3137
return (
@@ -34,15 +40,21 @@ export default function AddInputButton({
3440
ref={inputRef}
3541
type="text"
3642
className="w-10 min-w-10"
43+
onInput={(e) => {
44+
handleInputWidth(e);
45+
}}
46+
onChange={(e) => {
47+
setInputText(() => ({ word: e.target.value }));
48+
}}
3749
onKeyDown={(e) => {
3850
if (e.key === 'Enter') {
39-
setAddInputShow(false);
51+
handlePostBadWords();
4052
}
4153
}}
4254
/>
4355
<button
4456
onClick={() => {
45-
setAddInputShow(false);
57+
handlePostBadWords();
4658
}}
4759
>
4860
<AddIcon className="h-4 w-4" />
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { KebobMenuIcon } from '@/assets/icons';
2+
// import MenuModal from "./MenuModal";
3+
4+
export default function FilteredLetterListItem() {
5+
return (
6+
<div
7+
className="hover:bg-primary-4 relative flex cursor-pointer justify-between border-b bg-white px-6 py-4"
8+
onClick={() => {}}
9+
>
10+
<div className="flex h-full w-[80%] items-center">
11+
<span className="admin-list-set basis-1/10">{'더미'}</span>
12+
<span className="admin-list-set basis-2/10">{'더미'}</span>
13+
<span className="admin-list-set basis-2/10">{'더미'}</span>
14+
<span className="admin-list-set basis-2/10">{'더미'}</span>
15+
<span className="admin-list-set basis-2/10">{'더미'}</span>
16+
</div>
17+
<button onClick={() => {}}>
18+
<KebobMenuIcon className="h-5 w-5" />
19+
</button>
20+
{/* {modalOpen && <MenuModal modalContents={modalContents} setModalOpen={setModalOpen} />} */}
21+
</div>
22+
);
23+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { ReactNode } from 'react';
2+
3+
export default function ListHeaderFrame({ children }: { children: ReactNode }) {
4+
return (
5+
<div className="bg-primary-3 flex w-full border-b px-6 py-4">
6+
<div className="flex w-[80%] items-center">{children}</div>
7+
</div>
8+
);
9+
}

src/pages/Admin/components/WrapperFrame.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,20 @@
11
import { ReactNode } from 'react';
2+
import { twMerge } from 'tailwind-merge';
23

3-
export default function WrapperFrame({ children }: { children: ReactNode }) {
4+
export default function WrapperFrame({
5+
children,
6+
className,
7+
}: {
8+
children: ReactNode;
9+
className?: string;
10+
}) {
411
return (
5-
<section className="wrapper-box-shadow flex w-full flex-col rounded-lg bg-white p-6">
12+
<section
13+
className={twMerge(
14+
'wrapper-box-shadow flex w-full flex-col rounded-lg bg-white p-6',
15+
className,
16+
)}
17+
>
618
{children}
719
</section>
820
);

0 commit comments

Comments
 (0)