Skip to content

Commit

Permalink
[🌎 Feature] Admin νšŒμ› 전체 보기 νŽ˜μ΄μ§€ #163 (#180)
Browse files Browse the repository at this point in the history
* feat(#163): μœ μ € 및 인생 지도 Admin 쑰회 API μž‘μ—…

* feat(#163): μœ μ € 및 인생 지도 Admin UI μž‘μ—…
  • Loading branch information
egg528 authored Feb 12, 2024
1 parent 9ebc8e1 commit ffb7ce9
Show file tree
Hide file tree
Showing 16 changed files with 575 additions and 3 deletions.
44 changes: 44 additions & 0 deletions admin-ui/src/client/life-map.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import useSWR from "swr";

export interface ILifeMap {
id: number;
isPublic: boolean;
goals: IGoal[];
goalCount: number;
viewCount: number;
cheeringCount: number;
}

export interface IGoal {
title: string;
description: string;
deadline: string;
stickerUrl: string
tagInfo: ITag;
tasks: ITask[]
}

export interface ITag {
tagId: number;
tagContent: string;
}


export interface ITask {
taskId: number;
isTaskDone: boolean;
taskDescription: string;
}
export interface ILifeMapResponse {
result: number;
body: ILifeMap
error: {
code: string;
message: string;
data: object;
};
}

export const useLifeMap = (id: string | number) => {
return useSWR<ILifeMapResponse>(`life-map?userId=${id}`);
};
39 changes: 39 additions & 0 deletions admin-ui/src/client/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import useSWR from "swr";

export interface IUser {
id: number;
email: string;
username: string;
nickname: string;
birth: string;
image: string;
provider: string;
authority: string;
}
export interface IUsersResponse {
result: number;
body: IUser[]
error: {
code: string;
message: string;
data: object;
};
}

export interface IUserResponse {
result: number;
body: IUser
error: {
code: string;
message: string;
data: object;
};
}

export const useUsers = () => {
return useSWR<IUsersResponse>(`user`);
};

export const useUser = (id: string | number) => {
return useSWR<IUserResponse>(`user/${id}`);
};
3 changes: 2 additions & 1 deletion admin-ui/src/components/layout/default-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@ export interface IPageHeader {
export type IDefaultLayoutPage<P = {}> = NextPage<P> & {
getLayout(page: NextComponentType, props: unknown): React.ReactNode;
pageHeader?: IPageHeader;
setSidebarClose?: boolean;
};

interface IDefaultLayoutProps {
Page: IDefaultLayoutPage;
}

const DefaultLayout = ({ Page, ...props }: IDefaultLayoutProps) => {
const [isShowSidebar, setIsShowSidebar] = useState(true);
const [isShowSidebar, setIsShowSidebar] = useState(Page.setSidebarClose === true ? false : true);
const [isShowPopupMenu, setIsShowPopupMenu] = useState(false);
const router = useRouter();

Expand Down
14 changes: 14 additions & 0 deletions admin-ui/src/components/layout/main-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,20 @@ const mainMenuData: IMenu[] = [
},
},
],
},
{
id: "user",
name: "μœ μ € 관리",
icon: <Package2 className="w-5 h-5" />,
submenu: [
{
id: "userList",
name: "μœ μ € λͺ©λ‘",
link: {
path: "/user/list",
},
},
],
}
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ interface IDashboardStaticsProps {
const renderTodayIncrease = (value: number, unit: string) => {
if (0 < value) {
return (
<span className="flex items-center px-2 py-1 text-sm text-white rounded-full bg-emerald">
<span className="flex items-center px-2 py-1 text-sm text-white rounded-full bg-blue-400">
<ArrowUp className="w-5 h-4" />
였늘 {value}{unit} μΆ”κ°€
</span>
Expand Down
150 changes: 150 additions & 0 deletions admin-ui/src/components/page/user/user-list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import DefaultTable from "@/components/shared/ui/default-table";
import {Alert, Button, Modal} from "antd";
import { ColumnsType } from "antd/es/table";
import { useRouter } from "next/router";
import React, {useCallback} from "react";
import {IUser, useUsers} from "@/client/user";

const UserList = () => {
const router = useRouter();

const { data, error, isLoading } = useUsers();

const handleChangePage = useCallback(
(pageNumber: number) => {
router.push({
pathname: router.pathname,
query: { ...router.query, page: pageNumber },
});
},
[router]
);

const convertProvider = (provider: string) => {
if(provider === 'KAKAO') {
return '카카였'
} else if (provider === 'GOOGLE') {
return 'ꡬ글'
} else if (provider === 'NAVER') {
return '넀이버'
}

return 'μ•Œ 수 μ—†λŠ” 경둜'
}

const openLifeMapPopup = (userId: number) => {
window.open(`/popup/life-map/${userId}`, 'LifeMap', 'width=1000, height=610');
};



const columns: ColumnsType<IUser> = [
{
title: "인생 지도",
key: "action",
width: 120,
align: "center",
render: (_value: unknown, record: IUser) => {
return (
<span className="flex justify-center gap-2">
<Button type="primary" onClick={() => openLifeMapPopup(record.id)}>
보기
</Button>
</span>
);
},
},
{
title: "μœ μ € ID",
dataIndex: "id",
width: 100,
render: (value: string, record: IUser) => {
return (
<span>
<span className="px-2 py-1 mr-1 bg-gray-100 rounded">{record.id}</span>
</span>
);
},
},
{
title: "μœ μ € 이름 (username)",
dataIndex: "username",
render: (value: string, record: IUser) => {
return (
<span>
<span className="px-2 py-1 mr-1 bg-gray-100 rounded">{record.username}</span>
</span>
);
},
},
{
title: "μœ μ € 별λͺ… (nickname)",
dataIndex: "nickname",
render: (value: string, record: IUser) => {
return (
<span>
<span className="px-2 py-1 mr-1 bg-gray-100 rounded">{record.nickname}</span>
</span>
);
},
},
{
title: "생년월",
dataIndex: "birth",
render: (value: string, record: IUser) => {
return (
<span>
<span className="px-2 py-1 mr-1 bg-gray-100 rounded">{record.birth}</span>
</span>
);
},
},
{
title: "μœ μ € 이미지",
dataIndex: "image",
render: (value: string, record: IUser) => {
return (
<span>
<img src={record.image} height={100} width={100} className="px-2 py-1 mr-1 bg-gray-100 rounded" />
</span>
);
},
},
{
title: "κ°€μž… 경둜",
dataIndex: "provider",
render: (value: string, record: IUser) => {
return (
<span>
<span className="px-2 py-1 mr-1 bg-gray-100 rounded">{convertProvider(record.provider)}</span>
</span>
);
},
},
];

if (error) {
return <Alert message="데이터 λ‘œλ”© 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€." type="warning" />;
}

return (
<>
<DefaultTable<IUser>
columns={columns}
dataSource={data?.body || []}
loading={isLoading}
pagination={{
current: Number(router.query.page || 1),
defaultPageSize: 10,
total: data?.body.length || 0,
showSizeChanger: false,
onChange: handleChangePage,
}}
className="mt-3"
countLabel={data?.body.length}
/>
</>
);
};

export default React.memo(UserList);
115 changes: 115 additions & 0 deletions admin-ui/src/pages/popup/life-map/[id].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { getDefaultLayout, IDefaultLayoutPage, IPageHeader } from "@/components/layout/default-layout";
import { useRouter } from "next/router";
import {IGoal, ITask, useLifeMap} from "@/client/life-map";
import DefaultTable from "@/components/shared/ui/default-table";
import React from "react";
import {ColumnsType} from "antd/es/table";

const pageHeader: IPageHeader = {
title: "인생 지도",
};

const LifeMapPopup: IDefaultLayoutPage = () => {
const router = useRouter();
const { data, error, isLoading, isValidating } = useLifeMap(router.query.id as string);

const formattingTask = (task: ITask): string => {
if(task.isTaskDone) {
return 'βœ… ' + task.taskDescription + ' \n \n'
}

return '❎ ' + task.taskDescription + ' \n \n';
}

const columns: ColumnsType<IGoal> = [
{
title: "λͺ©ν‘œ λͺ…",
dataIndex: "title",
width: 100,
render: (value: string, record: IGoal) => {
return (
<span>
<span className="px-2 py-1 mr-1 bg-gray-100 rounded">{record.title}</span>
</span>
);
},
},
{
title: "μ„€λͺ…",
dataIndex: "description",
width: 100,
render: (value: string, record: IGoal) => {
return (
<>
<span>
<span className="px-2 py-1 mr-1 bg-gray-100 rounded">{record.description}</span>
</span>
</>
);
},
},
{
title: "마감일",
dataIndex: "deadline",
width: 100,
render: (value: string, record: IGoal) => {
return (
<span>
<span className="px-2 py-1 mr-1 bg-gray-100 rounded">{record.deadline}</span>
</span>
);
},
},
{
title: "νƒœκ·Έ λͺ…",
dataIndex: "tagContent",
width: 100,
render: (value: string, record: IGoal) => {
return (
<span>
<span className="px-2 py-1 mr-1 bg-gray-100 rounded">{record.tagInfo.tagContent}</span>
</span>
);
},
},
{
title: "μ„ΈλΆ€ λͺ©ν‘œ",
dataIndex: "tasks",
width: 100,
render: (value: string, record: IGoal) => {
return (
<span>
{ record.tasks.map((task, index) => (
<>
<span className=" rounded border-b-2">{formattingTask(task)}</span>
<br/>
</>
))}
</span>
);
},
},
];

return (
<>
<div className="flex items-center text-2xl text-gray-900 mt-5">πŸšͺ 곡개 μ—¬λΆ€: {data?.body.isPublic === true ? '곡개' : 'λΉ„κ³΅κ°œ'}</div>
<div className="flex items-center text-2xl text-gray-900 mt-5">😘 응원 받은 수: {data?.body.cheeringCount}</div>
<div className="flex items-center text-2xl text-gray-900 mt-5">πŸ”₯ 방문자 수: {data?.body.viewCount}</div>
<div className="flex items-center text-2xl text-gray-900 mt-5">🚩 λͺ©ν‘œ 리슀트</div>
<DefaultTable<IGoal>
columns={columns}
dataSource={data?.body.goals || []}
loading={isLoading}
className="mt-3"
countLabel={data?.body.goals.length}
/>
</>
)
};

LifeMapPopup.getLayout = getDefaultLayout;
LifeMapPopup.pageHeader = pageHeader;
LifeMapPopup.setSidebarClose = true;

export default LifeMapPopup;
Loading

0 comments on commit ffb7ce9

Please sign in to comment.