Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

신청자 리스트 불러오기 기능 구현 #43

Merged
merged 7 commits into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ApplicantListResponse } from "./types";
import { fetchInstance } from "@/shared";

export const getApplicantListPath = () => `/admin/people`;

export const getApplicantList = async (page: number, per_page: number): Promise<ApplicantListResponse> => {
const response = await fetchInstance.get(getApplicantListPath(), {
params: {
page,
per_page,
},
});
return response.data;
};
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { useGetApplicantList } from "./useGetApplicantList";
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { getApplicantList, getApplicantListPath } from "../applicant-list.api";
import { ApplicantListResponse } from "../types";
import { useInfiniteQuery } from "@tanstack/react-query";

export const useGetApplicantList = (perPage: number) => {
return useInfiniteQuery<ApplicantListResponse, Error>({
queryKey: [getApplicantListPath(), perPage],
queryFn: ({ pageParam = 1 }) => getApplicantList(Number(pageParam), perPage),
initialPageParam: 1,
getNextPageParam: (lastPage) => {
const { currentPage, totalPages } = lastPage.pageInfo;
return currentPage < totalPages ? currentPage + 1 : undefined;
},
});
};
3 changes: 2 additions & 1 deletion packages/admin/src/features/people-management/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
// export * from "./hook";
export * from "./hook";
export * from "./types";
export * from "./mock";
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export type ApplyPeople = {
id: number;
studentId: number;
email: string;
githubId: string;
major: string;
name: string;
phoneNumber: string;
position: string;
teamName: string;
accepted: boolean;
checkined: boolean;
};

export type PageInfo = {
currentPage: number;
perPage: number;
totalItems: number;
totalPages: number;
};

export type ApplicantListResponse = {
status: number;
applyPeople: ApplyPeople[];
pageInfo: PageInfo;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type { ApplyPeople, PageInfo, ApplicantListResponse } from "./applicant-list.response";
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
import { useState } from "react";
import { useState, useEffect } from "react";

import {
InputField,
PeopleTable,
useDeletePerson,
useUpdatePersonStatus,
useFilteredPeople,
useGetPeople,
useGetApplicantList,
ApplyPeople,
} from "@/features";

export const MainContents = () => {
const [searchTerm, setSearchTerm] = useState("");
const [people, setPeople] = useState<ApplyPeople[]>([]);
const { data, isLoading, fetchNextPage, hasNextPage } = useGetApplicantList(10);

const { data } = useGetPeople();

const { people, setPeople, deletePerson } = useDeletePerson(data);
useEffect(() => {
if (data) {
const fetchedPeople = data.pages.flatMap((page) => page.applyPeople);
setPeople(fetchedPeople);
}
}, [data]);

const updatePersonStatus = useUpdatePersonStatus(people, setPeople);

Expand All @@ -24,7 +29,13 @@ export const MainContents = () => {
<main className="flex-1 p-4 lg:p-8">
<h1 className="mb-6 text-2xl font-bold">인원 관리</h1>
<InputField searchTerm={searchTerm} setSearchTerm={setSearchTerm} />
<PeopleTable people={filteredPeople} updatePersonStatus={updatePersonStatus} deletePerson={deletePerson} />
<PeopleTable
people={filteredPeople}
updatePersonStatus={updatePersonStatus}
isLoading={isLoading}
hasNextPage={hasNextPage}
fetchNextPage={fetchNextPage}
/>
</main>
);
};
Original file line number Diff line number Diff line change
@@ -1,14 +1,41 @@
import { ChevronDown, X } from "lucide-react";
import { useState, useRef, useEffect, useCallback } from "react";

import { PeopleResponseData } from "../../api/mock/useGetPeople";
import { ChevronDown } from "lucide-react";

import { ApplyPeople } from "@/features";

type Props = {
people: PeopleResponseData[];
people?: ApplyPeople[];
isLoading: boolean;
hasNextPage: boolean;
fetchNextPage: () => void;
updatePersonStatus: (id: number, status: "accepted" | "rejected" | "checkedIn" | "checkedOut") => void;
deletePerson: (id: number) => void;
};

export const PeopleTable = ({ people, updatePersonStatus, deletePerson }: Props) => {
export const PeopleTable = ({ people, updatePersonStatus, isLoading, hasNextPage, fetchNextPage }: Props) => {
const [applyPeople, setApplyPeople] = useState<ApplyPeople[]>([]);
const observer = useRef<IntersectionObserver | null>(null);

useEffect(() => {
if (people) {
setApplyPeople(people);
}
}, [people]);

const lastPersonRef = useCallback(
(node: HTMLTableRowElement) => {
if (isLoading) return;
if (observer.current) observer.current.disconnect();
observer.current = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && hasNextPage) {
fetchNextPage();
}
});
if (node) observer.current.observe(node);
},
[isLoading, hasNextPage, fetchNextPage],
);

return (
<div className="overflow-x-auto bg-white rounded-lg shadow">
<table className="w-full">
Expand All @@ -23,6 +50,9 @@ export const PeopleTable = ({ people, updatePersonStatus, deletePerson }: Props)
<th className="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">
전화번호
</th>
<th className="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">
팀 이름
</th>
<th className="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">
전공
</th>
Expand All @@ -32,57 +62,81 @@ export const PeopleTable = ({ people, updatePersonStatus, deletePerson }: Props)
<th className="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">
체크인/아웃
</th>
<th className="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">
삭제
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{people.map((person) => (
<tr key={person.id}>
<td className="px-6 py-4 whitespace-nowrap">{person.name}</td>
<td className="px-6 py-4 whitespace-nowrap">{person.studentId}</td>
<td className="px-6 py-4 whitespace-nowrap">{person.phoneNumber}</td>
<td className="px-6 py-4 whitespace-nowrap">{person.major}</td>
<td className="px-6 py-4 whitespace-nowrap">
<button
className={`inline-flex items-center px-3 py-1 rounded-full text-sm font-medium ${
person.accepted
? "bg-green-100 text-green-800"
: "bg-yellow-100 text-yellow-800"
}`}
onClick={() =>
updatePersonStatus(person.id, person.accepted ? "rejected" : "accepted")
}
>
{person.accepted ? "승인됨" : "미승인"}
<ChevronDown className="w-4 h-4 ml-1" />
</button>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<button
className={`inline-flex items-center px-3 py-1 rounded-full text-sm font-medium ${
person.checkedIn ? "bg-blue-100 text-blue-800" : "bg-gray-100 text-gray-800"
}`}
onClick={() =>
updatePersonStatus(person.id, person.checkedIn ? "checkedOut" : "checkedIn")
}
disabled={!person.accepted}
>
{person.checkedIn ? "체크인됨" : "미체크인"}
<ChevronDown className="w-4 h-4 ml-1" />
</button>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<button
className="text-red-600 hover:text-red-900"
onClick={() => deletePerson(person.id)}
>
<X className="w-5 h-5" />
</button>
</td>
</tr>
))}
{isLoading
? Array.from({ length: 10 }).map((_, idx) => (
<tr key={idx} className="animate-pulse">
<td className="px-6 py-4 whitespace-nowrap">
<div className="w-3/4 h-4 rounded bg-slate-200" />
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="w-1/2 h-4 rounded bg-slate-200" />
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="w-1/2 h-4 rounded bg-slate-200" />
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="w-1/2 h-4 rounded bg-slate-200" />
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="w-16 h-6 rounded bg-slate-200" />
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="w-16 h-6 rounded bg-slate-200" />
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="w-16 h-6 rounded bg-slate-200" />
</td>
</tr>
))
: applyPeople.map((person, index) => (
<tr
key={person.studentId}
ref={index === (people ? people.length - 1 : -1) ? lastPersonRef : null}
>
<td className="px-6 py-4 whitespace-nowrap">{person.name}</td>
<td className="px-6 py-4 whitespace-nowrap">{person.studentId}</td>
<td className="px-6 py-4 whitespace-nowrap">{person.phoneNumber}</td>
<td className="px-6 py-4 whitespace-nowrap">{person.teamName}</td>
<td className="px-6 py-4 whitespace-nowrap">{person.major}</td>
<td className="px-6 py-4 whitespace-nowrap">
<button
className={`inline-flex items-center px-3 py-1 rounded-full text-sm font-medium ${
person.accepted
? "bg-green-100 text-green-800"
: "bg-yellow-100 text-yellow-800"
}`}
onClick={() =>
updatePersonStatus(person.id, person.accepted ? "rejected" : "accepted")
}
>
{person.accepted ? "승인됨" : "미승인"}
<ChevronDown className="w-4 h-4 ml-1" />
</button>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<button
className={`inline-flex items-center px-3 py-1 rounded-full text-sm font-medium ${
person.checkined
? "bg-blue-100 text-blue-800"
: "bg-gray-100 text-gray-800"
}`}
onClick={() =>
updatePersonStatus(
person.id,
person.checkined ? "checkedOut" : "checkedIn",
)
}
disabled={!person.accepted}
>
{person.checkined ? "체크인됨" : "미체크인"}
<ChevronDown className="w-4 h-4 ml-1" />
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export { useDeletePerson } from "./useDeletePerson";
export { useFilteredPeople } from "./useFilteredPeople";
export { useUpdatePersonStatus } from "./useUpdatePersonStatus";

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { useMemo } from "react";

import { PeopleResponseData } from "../api/mock/useGetPeople";
import { ApplyPeople } from "../api";

export const useFilteredPeople = (people: PeopleResponseData[], searchTerm: string) => {
export const useFilteredPeople = (people: ApplyPeople[], searchTerm: string) => {
const filteredPeople = useMemo(() => {
return people.filter(
(person) =>
person.name.includes(searchTerm) ||
person.studentId.includes(searchTerm) ||
person.phoneNumber.includes(searchTerm) ||
person.studentId.toString().includes(searchTerm) ||
person.phoneNumber.toString().includes(searchTerm) ||
person.major.includes(searchTerm),
);
}, [people, searchTerm]);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { PeopleResponseData } from "../api/mock/useGetPeople";
import { ApplyPeople } from "../api";

export const useUpdatePersonStatus = (
people: PeopleResponseData[],
setPeople: (people: PeopleResponseData[]) => void,
) => {
export const useUpdatePersonStatus = (people: ApplyPeople[], setPeople: (people: ApplyPeople[]) => void) => {
const updatePersonStatus = (id: number, status: "accepted" | "rejected" | "checkedIn" | "checkedOut") => {
setPeople(
people.map((person) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/admin/src/shared/api/instance/Instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const initInstance = (config: AxiosRequestConfig): AxiosInstance => {
};

export const fetchInstance = initInstance({
baseURL: "https://api.example.com",
baseURL: `https://k8q4ci7a8j.execute-api.us-east-1.amazonaws.com`,
});

export const queryClient = new QueryClient({
Expand Down