Skip to content

Commit 5ca4f51

Browse files
authored
feat:관리자 페이지 1차 기능 구현 (#43)
* feat:관리자 페이지 신고기능 구현완료(페이지네이션 제외) * feat:관리자 페이지 검열관리 필터링 기능 개발중(검열 단어 추가 구현중) * feat:필터링 단어 추가기능 목api연결 완료(단어 삭제는 목api가 안만들어짐) + 차단 편지목록 퍼블리싱 * chore:badwords, filteredLetter 라우팅 분리 + 사이드바 버튼에 라우팅 연결
1 parent 699fa09 commit 5ca4f51

24 files changed

+612
-201
lines changed

src/App.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import useViewport from './hooks/useViewport';
44
import Layout from './layouts/Layout';
55
import MobileLayout from './layouts/MobileLayout';
66
import AdminPage from './pages/Admin';
7+
import FilteredLetterManage from './pages/Admin/FilteredLetter';
8+
import FilteringManage from './pages/Admin/Filtering';
79
import ReportManage from './pages/Admin/Report';
810
import Home from './pages/Home';
911
import Landing from './pages/Landing';
@@ -57,6 +59,8 @@ const App = () => {
5759

5860
<Route path="admin" element={<AdminPage />}>
5961
<Route path="report" element={<ReportManage />} />
62+
<Route path="badwords" element={<FilteringManage />} />
63+
<Route path="filtered-letter" element={<FilteredLetterManage />} />
6064
</Route>
6165
</Routes>
6266
);

src/apis/admin.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import client from './client';
2+
3+
const getReports = async (
4+
setReports: React.Dispatch<React.SetStateAction<Report[]>>,
5+
queryString: string = '',
6+
callBack?: () => void,
7+
) => {
8+
try {
9+
const res = await client.get(`/api/reports${queryString}`);
10+
setReports(res.data.data);
11+
if (callBack) callBack();
12+
console.log(res.data.data);
13+
} catch (error) {
14+
console.error(error);
15+
}
16+
};
17+
18+
const patchReport = async (reportId: number, reportRequest: ReportRequest) => {
19+
try {
20+
console.log(`/api/reports/${reportId}`, reportRequest);
21+
const res = await client.patch(`/api/reports/${reportId}`, reportRequest);
22+
console.log(res);
23+
} catch (error) {
24+
console.error(error);
25+
}
26+
};
27+
28+
// badwords
29+
const getBadWords = async (setBadWords: React.Dispatch<React.SetStateAction<BadWords[]>>) => {
30+
try {
31+
const res = await client.get('/api/bad-words');
32+
setBadWords(res.data.data);
33+
console.log(res);
34+
} catch (error) {
35+
console.error(error);
36+
}
37+
};
38+
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/assets/icons/add.svg

Lines changed: 3 additions & 0 deletions
Loading

src/assets/icons/cancel.svg

Lines changed: 3 additions & 0 deletions
Loading

src/assets/icons/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import AddIcon from './add.svg?react';
12
import AlarmIcon from './alarm.svg?react';
23
import ArrowDownIcon from './arrow-down.svg?react';
34
import ArrowLeftIcon from './arrow-left.svg?react';
45
import BoardIcon from './board.svg?react';
6+
import CancelIcon from './cancel.svg?react';
57
import CheckIcon from './check.svg?react';
68
import CloudIcon from './cloud.svg?react';
79
import DeleteIcon from './delete.svg?react';
@@ -24,6 +26,7 @@ import ThermostatIcon from './thermostat.svg?react';
2426
import WarmIcon from './warm.svg?react';
2527

2628
export {
29+
AddIcon,
2730
NaverIcon,
2831
KakaoIcon,
2932
GoogleIcon,
@@ -48,4 +51,5 @@ export {
4851
LikeFilledIcon,
4952
LikeOutlinedIcon,
5053
DeleteIcon,
54+
CancelIcon,
5155
};

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 FilteredLetterManage() {
9+
const arr = new Array(10).fill(null);
10+
return (
11+
<WrapperFrame>
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 FilteringManage() {
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: 35 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,138 +1,58 @@
11
import { useEffect, useState } from 'react';
22

3-
import client from '@/apis/client';
3+
import { getReports } from '@/apis/admin';
44
import { AlarmIcon } from '@/assets/icons';
55

6-
import DetailFrame from './components/DetailFrame';
7-
import ListItem from './components/ListItem';
6+
import ListHeaderFrame from './components/ListHeaderFrame';
7+
import ReportDetailModal from './components/ReportDetailModal';
8+
import ReportHandlingModal from './components/ReportHandlingModal';
9+
import ReportListItem from './components/ReportListItem';
810
import WrapperFrame from './components/WrapperFrame';
11+
import WrapperTitle from './components/WrapperTitle';
912

1013
export default function ReportManage() {
1114
const [detailModalOpen, setDetailModalOpen] = useState<boolean>(false);
12-
// {
13-
// id: '001',
14-
// reporterEmail: '[email protected]',
15-
// targetEmail: '[email protected]',
16-
// reportedAt: new Date(2025, 1, 20),
17-
// letterId:2001,
18-
// sharePostId:null,
19-
// eventId:null,
20-
// reportType:'LETTER',
21-
// reason:"ABUSE",
22-
// reasonDetail:null,
23-
// status: 'PENDING',
24-
// },
25-
const DUMMY = [
26-
{
27-
id: '001',
28-
reporterEmail: '[email protected]',
29-
targetEmail: '[email protected]',
30-
reportedAt: new Date(2020, 12, 4),
31-
reason: '욕설',
32-
},
33-
{
34-
id: '002',
35-
reporterEmail: '[email protected]',
36-
targetEmail: '[email protected]',
37-
reportedAt: new Date(2020, 12, 4),
38-
reason: '욕설',
39-
},
40-
{
41-
id: '003',
42-
reporterEmail: '[email protected]',
43-
targetEmail: '[email protected]',
44-
reportedAt: new Date(2000, 6, 23),
45-
reason: '욕설',
46-
},
47-
{
48-
id: '004',
49-
reporterEmail: '[email protected]',
50-
targetEmail: '[email protected]',
51-
reportedAt: new Date(1080, 11, 5),
52-
reason: '욕설',
53-
},
54-
{
55-
id: '005',
56-
reporterEmail: '[email protected]',
57-
targetEmail: '[email protected]',
58-
reportedAt: new Date(2040, 1, 2),
59-
reason: '욕설',
60-
},
61-
{
62-
id: '006',
63-
reporterEmail: '[email protected]',
64-
targetEmail: '[email protected]',
65-
reportedAt: new Date(2025, 1, 23),
66-
reason: '욕설',
67-
},
68-
];
69-
const modalContents = [
70-
{
71-
title: '신고 목록 삭제',
72-
onClick: () => {
73-
console.log('삭제');
74-
},
75-
},
76-
{
77-
title: '작성자 활동 정지',
78-
onClick: () => {
79-
console.log('정지');
80-
},
81-
},
82-
];
15+
const [handleModalOpen, setHandleModalOpen] = useState<boolean>(false);
16+
const [reports, setReports] = useState<Report[]>([]);
17+
const [selectedReport, setSelectReport] = useState<Report | null>(null);
18+
const [selectedReportId, setSelectedReportId] = useState<number | null>(null);
19+
8320
// const [allReports, setAllReports] = useState();
8421
useEffect(() => {
85-
const getAllReports = async () => {
86-
const res = await client.get('/api/reports');
87-
console.log(res);
88-
};
89-
getAllReports();
90-
const getReportDetail = async () => {
91-
const res = await client.get('/api/reports/2');
92-
console.log(res);
93-
};
94-
getReportDetail();
95-
const postReport = async () => {
96-
const res = await client.post('/api/reports', {
97-
letterId: 2010,
98-
reportType: 'POST',
99-
reason: 'HARASSMENT',
100-
reasonDetail: '테스트용',
101-
});
102-
console.log(res);
103-
};
104-
postReport();
22+
getReports(setReports, '?status=PENDING');
10523
}, []);
10624
return (
10725
<WrapperFrame>
108-
<span className="h3-sb flex items-center gap-4.5">
109-
<AlarmIcon className="h-9 w-9"></AlarmIcon> 신고 편지 목록
110-
</span>
26+
<WrapperTitle title="신고 편지 목록" Icon={AlarmIcon} />
27+
11128
<section className="mt-5 flex flex-col">
112-
<div className="bg-primary-3 flex w-full border-b px-6 py-4">
113-
<div className="flex w-[80%] items-center">
114-
<span className="ml-4 flex basis-1/10 overflow-ellipsis">ID</span>
115-
<span className="ml-4 flex basis-2/10">제보자 이메일</span>
116-
<span className="ml-4 flex basis-2/10">작성자 이메일</span>
117-
<span className="ml-4 flex basis-2/10">제보 일자</span>
118-
<span className="ml-4 flex basis-3/10">제보 사유</span>
119-
</div>
120-
</div>
121-
{DUMMY.map((data, idx) => (
122-
<ListItem
123-
data={data}
124-
modalContents={modalContents}
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>
36+
{reports.map((data, idx) => (
37+
<ReportListItem
12538
key={idx}
39+
report={data}
12640
setDetailModalOpen={setDetailModalOpen}
41+
setSelectedReportId={setSelectedReportId}
42+
setHandleModalOpen={setHandleModalOpen}
43+
setSelectReport={setSelectReport}
12744
/>
12845
))}
12946
</section>
13047
{detailModalOpen && (
131-
<DetailFrame closeEvent={setDetailModalOpen}>
132-
<>
133-
<span className="h2-sb">제보 편지 상세 조회</span>
134-
</>
135-
</DetailFrame>
48+
<ReportDetailModal selectedReport={selectedReport} closeEvent={setDetailModalOpen} />
49+
)}
50+
{handleModalOpen && (
51+
<ReportHandlingModal
52+
setReports={setReports}
53+
setHandleModalOpen={setHandleModalOpen}
54+
selectedReportId={selectedReportId}
55+
/>
13656
)}
13757
</WrapperFrame>
13858
);

0 commit comments

Comments
 (0)