Skip to content

Commit

Permalink
Merge pull request #17 from Kernel360/13-feature-프로젝트-종합-대시보드-기능구현
Browse files Browse the repository at this point in the history
13 feature 프로젝트 종합 대시보드 기능구현
  • Loading branch information
MUNJEONGJUN authored Jan 8, 2025
2 parents c9795c5 + af120ab commit 9060c2b
Show file tree
Hide file tree
Showing 23 changed files with 2,508 additions and 2,298 deletions.
16 changes: 6 additions & 10 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ import { Provider } from "@/src/components/ui/provider";
import { Box, Container, Flex } from "@chakra-ui/react";
import Header from "@/src/components/layouts/Header";
import Sidebar from "@/src/components/layouts/Sidebar";
import { MSWComponent } from "@/src/components/common/MSWComponent";

export const metadata: Metadata = {
title: "FlowSync",
description: "Generated by create next app",
description: "The World Best PMS Service",
};

const RootLayout = (props: { children: React.ReactNode }) => {
const { children } = props;
const useMsw = process.env.USE_MSW === "true";
return (
<html suppressHydrationWarning>
<body>
Expand All @@ -24,24 +26,18 @@ const RootLayout = (props: { children: React.ReactNode }) => {
flexDirection="row"
margin={0}
padding={0}
bg="gray.50"
>
<Sidebar />
<Flex
width="100%"
height="100%"
align="center"
justify="center"
bg="gray.50"
p={6}
>
<Box
maxW="container.xl"
width="100%"
bg="white"
borderRadius="lg"
p={8}
>
{children}
<Box maxW="container.xl" width="100%" borderRadius="lg" p={8}>
{useMsw ? <MSWComponent>{children}</MSWComponent> : children}
</Box>
</Flex>
</Container>
Expand Down
10 changes: 5 additions & 5 deletions src/api/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import axiosInstance from "./axiosInstance";
import { BoardResponse, ProjectProps } from "@/src/types";

export const fetchProjects = async (
currentPage: number,
pageSize: number
currentPage: number,
pageSize: number,
query: string = "", // 검색어
filter: string = "" // 필터링 값
): Promise<BoardResponse<ProjectProps>> => {
console.log("Fetching projects with params:", { currentPage, pageSize });

const response = await axiosInstance.get("/projects", {
params: { currentPage, pageSize }
params: { currentPage, pageSize, query, filter },
});
return response.data;
};
Expand Down
135 changes: 106 additions & 29 deletions src/components/common/BasicTable.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
"use client";

import { useEffect, useState, useCallback } from "react";
import { Stack, Table } from "@chakra-ui/react";
import { useEffect, useState, useCallback, useRef } from "react";
import { Box, Heading, Stack, Table } from "@chakra-ui/react";
import Pagination from "./Pagination";
import { fetchProjects } from "@/src/api/projects";
import { ProjectProps, BoardResponse, PaginationMeta } from "@/src/types";
import { Loading } from "./Loading";
import { CustomBox } from "./CustomBox";
import { useRouter } from "next/navigation";
import SearchSection from "./SearchSection";
import { getTranslatedStatus } from "@/src/utils/getTranslatedStatus";

interface BasicTableProps {
headerTitle: React.ReactNode;
Expand All @@ -15,56 +20,128 @@ const BasicTable: React.FC<BasicTableProps> = ({ headerTitle }) => {
const [meta, setMeta] = useState<PaginationMeta | null>(null);
const [loading, setLoading] = useState(true);

const fetchData = useCallback(
async (page: number) => {
setLoading(true);
try {
const response: BoardResponse<ProjectProps> = await fetchProjects(
page - 1, // 서버에서 0-indexed 페이지를 사용
meta?.pageSize || 5
);
setData(response.data);
setMeta(response.meta);
} catch (error) {
console.error("Failed to fetch data:", error);
} finally {
setLoading(false);
}
},
[meta?.pageSize]
);
const [query, setQuery] = useState<string>(""); // 검색어 상태
const [filter, setFilter] = useState<string>(""); // 필터링 상태

// 최신 상태 값을 참조하기 위한 useRef
const queryRef = useRef(query);
const filterRef = useRef(filter);

// query와 filter가 변경될 때 useRef 업데이트
useEffect(() => {
queryRef.current = query;
}, [query]);

useEffect(() => {
filterRef.current = filter;
}, [filter]);

const router = useRouter();

const fetchData = useCallback(async (page: number, pageSize: number) => {
setLoading(true);
try {
const response: BoardResponse<ProjectProps> = await fetchProjects(
page - 1, // 서버에서 0-indexed 페이지를 사용
pageSize,
queryRef.current,
filterRef.current
);
setData(response.data);
setMeta(response.meta);
} catch (error) {
console.error("Failed to fetch data:", error);
} finally {
setLoading(false);
}
}, []);

useEffect(() => {
fetchData(meta?.currentPage ? meta.currentPage + 1 : 1); // 초기 로드 또는 페이지 변경 시 데이터 가져오기
}, [meta?.currentPage, fetchData]);
if (!meta) {
// 초기 로드 시 데이터 가져오기
fetchData(1, 5);
}
}, [meta, fetchData]);

const handlePageChange = (page: number) => {
if (meta) {
setMeta({ ...meta, currentPage: page - 1 }); // 페이지 변경
// 페이지 변경 시 새로운 데이터를 가져오기
fetchData(page, meta.pageSize || 5);
setMeta((prev) => ({ ...prev!, currentPage: page - 1 }));
}
};

const handleSearch = (newQuery: string, newFilter: string) => {
if (newQuery !== query) {
setQuery(newQuery);
}
if (newFilter !== filter) {
setFilter(newFilter);
}
fetchData(1, 5);
};

const handleRowClick = (id: number) => {
router.push(`/projects/${id}`);
};

return (
<Stack width="full" gap="5">
<Heading size="2xl" color="gray.700">
프로젝트 목록
</Heading>
<SearchSection
query={query}
filter={filter}
onQueryChange={setQuery}
onFilterChange={setFilter}
onSearch={handleSearch}
/>
<Table.Root size="sm" interactive>
<Table.Header>{headerTitle}</Table.Header>
<Table.Body>
{loading ? (
<Table.Row>
<Table.Cell colSpan={7} textAlign="center">
Loading...
<Loading />
</Table.Cell>
</Table.Row>
) : (
data.map((item) => (
<Table.Row key={item.id}>
<Table.Row
key={item.id}
onClick={() => handleRowClick(item.id)}
css={{
"& > td": {
textAlign: "center",
},
}}
>
<Table.Cell>{item.projectName}</Table.Cell>
<Table.Cell>{item.client}</Table.Cell>
<Table.Cell>{item.developer}</Table.Cell>
<Table.Cell>{item.contractStage}</Table.Cell>
<Table.Cell>{item.progressStage}</Table.Cell>
<Table.Cell>{item.startDate}</Table.Cell>
<Table.Cell textAlign="end">{item.endDate}</Table.Cell>
<Table.Cell>
<Box
display="flex"
justifyContent="center"
alignItems="center"
>
<CustomBox>
{getTranslatedStatus(item.projectStatus)}
</CustomBox>
</Box>
</Table.Cell>
<Table.Cell>
<Box
display="flex"
justifyContent="center"
alignItems="center"
>
<CustomBox>{item.progressStepName}</CustomBox>
</Box>
</Table.Cell>
<Table.Cell>{item.startAt}</Table.Cell>
<Table.Cell textAlign="end">{item.closeAt}</Table.Cell>
</Table.Row>
))
)}
Expand Down
27 changes: 27 additions & 0 deletions src/components/common/CustomBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Box, Text } from "@chakra-ui/react";

interface CustomBoxProps {
children: string;
}

export const CustomBox = ({ children }: CustomBoxProps) => {
const color = children === "진행중" ? "#21A366" : "#505050";
return (
<Box
display="flex"
backgroundColor="#F9F9F9"
paddingX="8px"
paddingY="4px"
justifyContent="center"
alignItems="center"
borderRadius="6px"
color={color}
fontSize="14px"
fontWeight="500"
letterSpacing="-0.28px"
width="100px"
>
{children}
</Box>
);
};
12 changes: 12 additions & 0 deletions src/components/common/Loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Spinner, Text, VStack } from "@chakra-ui/react";

export const Loading = () => {
return (
<VStack colorPalette="teal">
<Spinner size="xl" color="colorPalette.gray" />
<Text textStyle="xl" color="colorPalette.gray">
Loading...
</Text>
</VStack>
);
};
26 changes: 26 additions & 0 deletions src/components/common/MSWComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"use client";

import { useEffect, useState } from "react";

export const MSWComponent = ({ children }: { children: React.ReactNode }) => {
const [mswReady, setMswReady] = useState(false);
useEffect(() => {
const init = async () => {
const initMsw = await import("@/src/mocks/index").then(
(res) => res.initMSW
);
await initMsw();
setMswReady(true);
};

if (!mswReady) {
init();
}
}, [mswReady]);

if (!mswReady) {
return null;
}

return <>{children}</>;
};
3 changes: 1 addition & 2 deletions src/components/common/Pagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ interface PaginationProps {

const Pagination: React.FC<PaginationProps> = ({ meta, onPageChange }) => {
const { currentPage, totalPages, isFirstPage, isLastPage } = meta; // 필요한 데이터만 추출
const maxVisibleButtons = 10; // 한 번에 보여줄 페이지 번호 개수

const maxVisibleButtons = 5; // 한 번에 보여줄 페이지 번호 개수
// 현재 페이지 그룹 계산
const currentGroup = Math.ceil(currentPage / maxVisibleButtons);
const startPage = (currentGroup - 1) * maxVisibleButtons + 1;
Expand Down
2 changes: 1 addition & 1 deletion src/components/common/ProjectsStatusCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const StatusCard: React.FC<StatusCardProps> = ({ count, label, iconSrc }) => {
return (
<Box
background="white"
width={340}
width={300}
height={170}
border="1px solid #E2E8F0"
borderRadius="lg"
Expand Down
Loading

0 comments on commit 9060c2b

Please sign in to comment.