Skip to content
This repository was archived by the owner on Jan 2, 2026. It is now read-only.

Commit 72d2815

Browse files
authored
✨ 활동 보고 작성 페이지 구현 (#312)
1 parent 761eccc commit 72d2815

File tree

11 files changed

+187
-7
lines changed

11 files changed

+187
-7
lines changed

actions/council.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
import { revalidateTag } from 'next/cache';
44

55
import { putCouncilIntro } from '@/apis/v2/council/intro';
6-
import { FETCH_TAG_INTRO } from '@/constants/network';
7-
import { councilIntro } from '@/constants/segmentNode';
6+
import { postCouncilReport } from '@/apis/v2/council/report';
7+
import { FETCH_TAG_COUNCIL_INTRO, FETCH_TAG_COUNCIL_REPORT } from '@/constants/network';
8+
import { councilIntro, councilReport } from '@/constants/segmentNode';
89
import { redirectKo } from '@/i18n/routing';
910
import { getPath } from '@/utils/page';
1011

@@ -14,6 +15,14 @@ const introPath = getPath(councilIntro);
1415

1516
export const putIntroAction = withErrorHandler(async (formData: FormData) => {
1617
await putCouncilIntro(formData);
17-
revalidateTag(FETCH_TAG_INTRO);
18+
revalidateTag(FETCH_TAG_COUNCIL_INTRO);
1819
redirectKo(introPath);
1920
});
21+
22+
const councilReportPath = getPath(councilReport);
23+
24+
export const postCouncilReportAction = withErrorHandler(async (formData: FormData) => {
25+
await postCouncilReport(formData);
26+
revalidateTag(FETCH_TAG_COUNCIL_REPORT);
27+
redirectKo(councilReportPath);
28+
});

apis/v2/council/intro.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { putRequest } from '@/apis';
22
import { getRequest } from '@/apis';
3-
import { FETCH_TAG_INTRO } from '@/constants/network';
3+
import { FETCH_TAG_COUNCIL_INTRO } from '@/constants/network';
44

55
export const getCouncilIntro = () =>
66
getRequest<{ description: string; imageURL: string }>('/v2/council/intro', undefined, {
7-
next: { tags: [FETCH_TAG_INTRO] },
7+
next: { tags: [FETCH_TAG_COUNCIL_INTRO] },
88
});
99

1010
export const putCouncilIntro = (formData: FormData) =>

apis/v2/council/report/[id].ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { deleteRequest, getRequest, putRequest } from '@/apis';
2+
import { FETCH_TAG_COUNCIL_REPORT } from '@/constants/network';
3+
4+
interface GETReportByIDResponse {
5+
id: number;
6+
title: string;
7+
description: string;
8+
sequence: number;
9+
name: string;
10+
createdAt: string;
11+
prevId: number;
12+
prevTitle: string;
13+
nextId: number;
14+
nextTitle: string;
15+
}
16+
17+
export const getCouncilReport = (id: number) =>
18+
getRequest<GETReportByIDResponse>(`/v2/council/report/${id}`, undefined, {
19+
next: { tags: [FETCH_TAG_COUNCIL_REPORT] },
20+
});
21+
22+
export const putCouncilReport = (id: number, formData: FormData) =>
23+
putRequest<GETReportByIDResponse>(`/v2/council/report/${id}`, {
24+
body: formData,
25+
jsessionID: true,
26+
});
27+
28+
export const deleteCouncilReport = (id: number) => {
29+
return deleteRequest(`/v2/council/report/${id}`, { jsessionID: true });
30+
};

apis/v2/council/report/index.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { getRequest, postRequest } from '@/apis';
2+
import { FETCH_TAG_COUNCIL_REPORT } from '@/constants/network';
3+
4+
interface GETReportResponse {
5+
total: number;
6+
reports: {
7+
id: number;
8+
title: string;
9+
sequence: number;
10+
name: string;
11+
createdAt: string;
12+
imageURL: string;
13+
}[];
14+
}
15+
16+
export const getCouncilReportList = () =>
17+
getRequest<GETReportResponse>('/v2/council/report', undefined, {
18+
next: { tags: [FETCH_TAG_COUNCIL_REPORT] },
19+
});
20+
21+
export const postCouncilReport = (body: FormData) =>
22+
postRequest('/v2/council/report', { body, jsessionID: true });
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { FormProvider, useForm } from 'react-hook-form';
2+
3+
import Fieldset from '@/components/form/Fieldset';
4+
import Form from '@/components/form/Form';
5+
import { EditorImage } from '@/types/form';
6+
7+
export interface CouncilReportEditorContent {
8+
title: string;
9+
description: string;
10+
mainImage: EditorImage;
11+
sequence: number;
12+
name: string;
13+
}
14+
15+
interface Props {
16+
onCancel: () => void;
17+
onSubmit: (content: CouncilReportEditorContent) => Promise<void>;
18+
}
19+
20+
export default function CouncilReportEditor({ onCancel, onSubmit }: Props) {
21+
const methods = useForm<CouncilReportEditorContent>();
22+
const { handleSubmit } = methods;
23+
24+
return (
25+
<FormProvider {...methods}>
26+
<Form>
27+
<Fieldset.Title>
28+
<Form.Text
29+
name="title"
30+
placeholder="제목을 입력하세요."
31+
maxWidth="max-w-[40rem]"
32+
options={{ required: true }}
33+
/>
34+
</Fieldset.Title>
35+
<Fieldset.HTML>
36+
<Form.HTML name="description" options={{ required: true }} />
37+
</Fieldset.HTML>
38+
<Fieldset title="대표 이미지" mb="mb-8" titleMb="mb-[12px]" required>
39+
<p className="mb-[12px] text-sm font-medium text-neutral-900">
40+
그리드 목록에 표시됩니다.
41+
</p>
42+
<Form.Image name="mainImage" options={{ required: true }} />
43+
</Fieldset>
44+
<Fieldset title="게시자" mb="mb-8" titleMb="mb-[12px]" required>
45+
<div className="flex items-center gap-[8px]">
46+
{' '}
47+
<Form.Text
48+
name="sequence"
49+
maxWidth="w-[39px]"
50+
placeholder="39"
51+
options={{ required: true }}
52+
/>{' '}
53+
대 학생회{' '}
54+
<Form.Text
55+
name="name"
56+
maxWidth="w-[81px]"
57+
placeholder="학생회 이름"
58+
options={{ required: true }}
59+
/>
60+
</div>
61+
</Fieldset>
62+
<Form.Action onCancel={onCancel} onSubmit={handleSubmit(onSubmit)} submitLabel="게시하기" />
63+
</Form>
64+
</FormProvider>
65+
);
66+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
'use client';
2+
3+
import { postCouncilReportAction } from '@/actions/council';
4+
import CouncilReportEditor, {
5+
CouncilReportEditorContent,
6+
} from '@/app/[locale]/community/council/report/components/CouncilReportEditor';
7+
import PageLayout from '@/components/layout/pageLayout/PageLayout';
8+
import { councilReport } from '@/constants/segmentNode';
9+
import { useRouter } from '@/i18n/routing';
10+
import { contentToFormData } from '@/utils/formData';
11+
import { getPath } from '@/utils/page';
12+
13+
const councilReportListPath = getPath(councilReport);
14+
15+
export default function CouncilReportCreatePage() {
16+
const router = useRouter();
17+
18+
const onCancel = () => {
19+
router.push(councilReportListPath);
20+
};
21+
22+
const onSubmit = async ({ mainImage: image, ...requestObject }: CouncilReportEditorContent) => {
23+
const formData = contentToFormData('CREATE', { requestObject, image });
24+
await postCouncilReportAction(formData);
25+
};
26+
27+
return (
28+
<PageLayout title="활동 보고 작성" titleType="big" titleMargin="mb-[2.75rem]" hideNavbar>
29+
<CouncilReportEditor onCancel={onCancel} onSubmit={onSubmit} />
30+
</PageLayout>
31+
);
32+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { getCouncilReportList } from '@/apis/v2/council/report';
2+
import PageLayout from '@/components/layout/pageLayout/PageLayout';
3+
4+
export const dynamic = 'force-dynamic';
5+
6+
export default async function CouncilIntroPage() {
7+
const list = await getCouncilReportList();
8+
9+
return <PageLayout titleType="big">{JSON.stringify(list)}</PageLayout>;
10+
}

constants/network.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,5 @@ export const FETCH_TAG_INTERNATIONAL_SCHOLARSHIPS = 'international-scholarships'
5050
export const FETCH_TAG_INTERNATIONAL_GRADUATE = 'international-graduate';
5151
export const FETCH_TAG_INTERNATIONAL_UNDERGRADUATE = 'international-undergraduate';
5252

53-
export const FETCH_TAG_INTRO = 'intro';
53+
export const FETCH_TAG_COUNCIL_INTRO = 'council-intro';
54+
export const FETCH_TAG_COUNCIL_REPORT = 'council-report';

constants/segmentNode.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,14 @@ export const councilBylaws: SegmentNode = {
150150
children: [],
151151
};
152152

153+
export const councilReport: SegmentNode = {
154+
name: '활동 보고',
155+
segment: 'report',
156+
isPage: true,
157+
parent: council,
158+
children: [],
159+
};
160+
153161
export const people: SegmentNode = {
154162
name: '구성원',
155163
segment: 'people',
@@ -602,7 +610,7 @@ about.children = [
602610
directions,
603611
];
604612
community.children = [notice, news, seminar, facultyRecruitment, council];
605-
council.children = [councilIntro, councilBylaws];
613+
council.children = [councilIntro, councilBylaws, councilReport];
606614
people.children = [faculty, emeritusFaculty, staff];
607615
research.children = [researchGroups, researchCenters, researchLabs, topConferenceList];
608616
admissions.children = [undergraduateAdmission, graduateAdmission, internationalAdmission];

messages/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"학생회": "Student Council",
3131
"학생회 소개": "Introduction",
3232
"학생회칙 및 세칙": "Constitution & Bylaws",
33+
"활동 보고": "Report",
3334

3435
"교수진": "Faculty",
3536
"역대 교수진": "Emeritus Faculty",

0 commit comments

Comments
 (0)