Skip to content

Commit

Permalink
feat: Activity 어드민 페이지 (without editor) (#44)
Browse files Browse the repository at this point in the history
* feat: Admin Project Page 추가

* feat: Admin Social Page 추가

* feat: Admin Study Category Page 추가
  • Loading branch information
son-daehyeon committed Sep 17, 2024
1 parent 9b555c6 commit 4434ba4
Show file tree
Hide file tree
Showing 12 changed files with 409 additions and 80 deletions.
20 changes: 7 additions & 13 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,11 @@ import prettierConfig from 'eslint-config-prettier';
import prettierRecommended from 'eslint-plugin-prettier/recommended';
import tseslint from 'typescript-eslint';

export default tseslint.config(
{
files: ['**/*.js', '**/*.mjs', '**/*.ts'],
extends: [eslint.configs.recommended, ...tseslint.configs.recommended],
rules: {
'no-console': 'warn',
},
export default tseslint.config({
files: ['**/*.js', '**/*.mjs', '**/*.jsx', '**/*.ts', '**/*.tsx'],
extends: [eslint.configs.recommended, ...tseslint.configs.recommended, prettierRecommended],
rules: {
...prettierConfig.rules,
'no-console': 'warn',
},
{
files: ['**/*.js', '**/*.mjs', '**/*.ts'],
extends: [prettierRecommended],
rules: prettierConfig.rules,
},
);
});
8 changes: 4 additions & 4 deletions src/api/domain/Member.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,8 @@ export interface UpdateMyPasswordRequestDto {

export interface EachGetMembersResponseDto {
_id: string;
createdAt: Date;
updatedAt: Date;
createdAt: string;
updatedAt: string;
name: string;
avatar: string;
description: string | null;
Expand Down Expand Up @@ -198,8 +198,8 @@ export const RoleKoreanToRole = (role: string): Role => {

export interface MemberType {
_id: string;
createdAt: Date;
updatedAt: Date;
createdAt: string;
updatedAt: string;
name: string;
studentId: string;
email: string;
Expand Down
4 changes: 2 additions & 2 deletions src/api/domain/activity/Project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ export interface GetProjectsPageResponseDto {

export interface ProjectType {
_id: string;
createdAt: Date;
updatedAt: Date;
createdAt: string;
updatedAt: string;
type: 'Project' | 'Study' | 'Social';
author: MemberType;
title: string;
Expand Down
12 changes: 10 additions & 2 deletions src/api/domain/activity/Social.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ export class Social {
return this.request.get('/activity/social/detail?socialId=' + data.socialId);
}

public async getSocialsPage(): Promise<GetSocialsPageResponseDto> {
return this.request.get('/activity/social/max');
}

public async getSocials(data: GetSocialsRequestDto): Promise<GetSocialsResponseDto> {
return this.request.get('/activity/social?page=' + data.page);
}
Expand Down Expand Up @@ -71,6 +75,10 @@ export interface GetSocialResponseDto {
social: SocialType;
}

export interface GetSocialsPageResponseDto {
page: number;
}

export interface GetSocialsResponseDto {
socials: SocialType[];
}
Expand All @@ -79,8 +87,8 @@ export interface GetSocialsResponseDto {

export interface SocialType {
_id: string;
createdAt: Date;
updatedAt: Date;
createdAt: string;
updatedAt: string;
type: 'Project' | 'Study' | 'Social';
title: string;
contents: Content[];
Expand Down
18 changes: 12 additions & 6 deletions src/api/domain/activity/Study.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,14 @@ export interface CreateStudyResponseDto {
study: StudyType;
}

export interface EachGetCategoriesResponseDto {
_id: string;
name: string;
dependencies: number;
}

export interface GetCategoriesResponseDto {
categories: Category[];
categories: EachGetCategoriesResponseDto[];
}

export interface GetStudiesResponse {
Expand All @@ -101,21 +107,21 @@ export interface GetStudiesPageResponse {

export interface StudyType {
_id: string;
createdAt: Date;
updatedAt: Date;
createdAt: string;
updatedAt: string;
type: 'Project' | 'Study' | 'Social';
title: string;
content: string;
author: string;
image: string;
link: string;
uploadedAt: Date;
uploadedAt: string;
category: Category;
}

export interface Category {
_id: string;
createdAt: Date;
updatedAt: Date;
createdAt: string;
updatedAt: string;
name: string;
}
14 changes: 5 additions & 9 deletions src/app/activity/study/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { FaAngleDown } from 'react-icons/fa';

import { StudyCard } from '@/component';

import { Category, StudyType, WinkApi } from '@/api';
import { EachGetCategoriesResponseDto, StudyType, WinkApi } from '@/api';

import { AnimatePresence, motion } from 'framer-motion';

Expand All @@ -14,14 +14,13 @@ const StudyPage = () => {
const [maxPage, setMaxPage] = useState<number>(1);

const [studies, setStudies] = useState<StudyType[]>([]);
const [categories, setCategories] = useState<Category[]>([]);
const [categories, setCategories] = useState<EachGetCategoriesResponseDto[]>([]);

const [isOpen, setIsOpen] = useState(false);
const [selectedCategory, setSelectedCategory] = useState<Category>({
createdAt: new Date(),
updatedAt: new Date(),
const [selectedCategory, setSelectedCategory] = useState<EachGetCategoriesResponseDto>({
_id: 'all',
name: 'All',
dependencies: 0,
});

const filteredStudies =
Expand All @@ -32,10 +31,7 @@ const StudyPage = () => {
useEffect(() => {
const fetchCategories = async () => {
const { categories } = await WinkApi.Activity.Study.getCategories();
setCategories([
{ createdAt: new Date(), updatedAt: new Date(), _id: 'all', name: 'All' },
...categories,
]);
setCategories([{ _id: 'all', name: 'All', dependencies: 0 }, ...categories]);
};

const fetchMaxPage = async () => {
Expand Down
87 changes: 69 additions & 18 deletions src/app/admin/activity/project/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
'use client';

import { useEffect, useState } from 'react';
import React, { useEffect, useState } from 'react';
import { FaEdit } from 'react-icons/fa';
import { FaTrashCan } from 'react-icons/fa6';
import { toast } from 'react-toastify';

import Link from 'next/link';

import { AdminIconButton, AdminSearchBar, AdminTablePaging, AdminTitle } from '@/component';
import { AdminIconButton, AdminSearchBar, AdminTablePaging, AdminTitle, Modal } from '@/component';

import { ProjectType, WinkApi } from '@/api';

Expand All @@ -20,33 +21,56 @@ const AdminActivityProjectPage = () => {

const [query, setQuery] = useState<string>('');

useEffect(() => {
async function fetchingStudies() {
const { studies } = await WinkApi.Activity.Study.getStudies({ page });
setProjects(studies);
}
const [deleteModal, setDeleteModal] = useState<ProjectType | null>(null);

useEffect(() => {
async function fetchMaxPage() {
const { page } = await WinkApi.Activity.Study.getStudiesPage();
const { page } = await WinkApi.Activity.Project.getProjectsPage();
setMaxPage(page);
}

(async () => {
await fetchingStudies();
await fetchMaxPage();
})();
}, [page]);
}, []);

useEffect(() => {
(async () => {
await fetchProjects();
})();
}, [page, query]);

async function fetchProjects() {
if (query) {
const { projects } = await WinkApi.Activity.Project.searchProjects({ query });
setProjects(projects);
} else {
const { projects } = await WinkApi.Activity.Project.getProjects({ page });
setProjects(projects);
}
}

const handleDelete = async () => {
if (!deleteModal) return;

await WinkApi.Activity.ProjectAdmin.deleteProject({ projectId: deleteModal._id });
await fetchProjects();

toast.warn(`"${deleteModal.title}" 프로젝트가 삭제되었습니다.`);

setDeleteModal(null);
};

return (
<div className="container mx-auto mt-4">
<AdminTitle title="Activity" subtitle="스터디" />
<AdminTitle title="Activity" subtitle="프로젝트" />

<div className="flex justify-end mb-4 space-x-4">
<AdminSearchBar value={query} placeholder="제목을 검색해주세요." onChange={setQuery} />

<AdminIconButton
icon={<FaEdit />}
text="스터디 추가"
text="프로젝트 추가"
className="bg-wink-500 hover:bg-wink-600 border-0 text-white"
onClick={() => {}}
/>
Expand All @@ -67,21 +91,48 @@ const AdminActivityProjectPage = () => {
</div>
</div>

{projects.map((study) => (
<div key={study._id} className="grid grid-cols-12 gap-2 border-b border-gray-200">
<Link href={study.link} className="py-4 px-4 col-span-7 text-sm truncate">
{study.title}
{projects.map((project) => (
<div key={project._id} className="grid grid-cols-12 gap-2 border-b border-gray-200">
<Link
href={`/activity/project/${project._id}`}
className="py-4 px-4 col-span-7 text-sm truncate"
>
{project.title}
</Link>
<div className="py-4 px-4 col-span-3 text-sm">{formatDate(study.uploadedAt)}</div>
<div className="py-4 px-4 col-span-3 text-sm">{formatDate(project.createdAt)}</div>
<div className="col-span-1 flex items-center justify-center space-x-8">
<FaEdit size={18} className="cursor-pointer" onClick={() => {}} />
</div>
<div className="col-span-1 flex items-center justify-center space-x-8">
<FaTrashCan size={18} className="cursor-pointer" onClick={() => {}} />
<FaTrashCan
size={18}
className="cursor-pointer"
onClick={() => setDeleteModal(project)}
/>
</div>
</div>
))}

<Modal isOpen={!!deleteModal} onClose={() => setDeleteModal(null)}>
<h2 className="text-xl font-bold mb-4">정말로 삭제하시겠습니까?</h2>
<p>{deleteModal?.title}</p>
<div className="flex justify-end mt-4">
<button
className="px-4 py-2 bg-red-500 hover:bg-red-600 text-white rounded-md"
onClick={handleDelete}
>
삭제
</button>

<button
className="px-4 py-2 bg-gray-300 hover:bg-gray-400 text-gray-600 rounded-md ml-2"
onClick={() => setDeleteModal(null)}
>
취소
</button>
</div>
</Modal>

{!query && <AdminTablePaging page={page} setPage={setPage} maxPage={maxPage} />}
</div>
);
Expand Down
Loading

0 comments on commit 4434ba4

Please sign in to comment.