From 4d64ac3ad8c0cf6de921bc67c9b23c54a6d79ab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B1=84=EC=A7=80=EC=9B=90?= Date: Wed, 14 May 2025 12:17:59 +0900 Subject: [PATCH 1/7] =?UTF-8?q?fix:=20=EC=B5=9C=EA=B7=BC=20=EC=9C=A0?= =?UTF-8?q?=EC=9E=85=20=EA=B3=A0=EA=B0=9D=20=EC=98=81=EC=97=AD=20=ED=81=B4?= =?UTF-8?q?=EB=A6=AD=20=EC=8B=9C=20=EB=AA=A8=EB=8B=AC=20=ED=91=9C=EC=B6=9C?= =?UTF-8?q?=EB=90=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/DashboardPage/DashboardPage.tsx | 145 +++++++++++++++++----- 1 file changed, 111 insertions(+), 34 deletions(-) diff --git a/src/pages/DashboardPage/DashboardPage.tsx b/src/pages/DashboardPage/DashboardPage.tsx index 1a7703c..e1d57b3 100644 --- a/src/pages/DashboardPage/DashboardPage.tsx +++ b/src/pages/DashboardPage/DashboardPage.tsx @@ -22,6 +22,7 @@ import { Alert, Chip, Tooltip, + CircularProgress, } from "@mui/material"; import "./DashboardPage.css"; import AssignmentIcon from "@mui/icons-material/Assignment"; @@ -156,6 +157,8 @@ const DashboardPage = () => { ); const [isSurveyDetailModalOpen, setIsSurveyDetailModalOpen] = useState(false); const [surveyDetailLoading, setSurveyDetailLoading] = useState(false); + const [isRecentCustomersModalOpen, setIsRecentCustomersModalOpen] = + useState(false); const fetchWeeklySchedules = async () => { try { @@ -510,12 +513,16 @@ const DashboardPage = () => { }} > + recentCustomers > 0 && setIsRecentCustomersModalOpen(true) + } sx={{ flex: 1, display: "flex", flexDirection: "column", justifyContent: "space-between", p: 2, + "&:hover": { cursor: "pointer" }, }} > @@ -531,42 +538,35 @@ const DashboardPage = () => { {isLoading ? ( - - - - + ) : ( - 0 && { - cursor: "pointer", - textDecoration: "underline", - "&:hover": { - color: "#0D3B7A", - }, - }), - }} - onClick={() => - recentCustomers > 0 && navigate("/customers") - } - > - {recentCustomers} - + <> + 0 && { + cursor: "pointer", + textDecoration: "underline", + "&:hover": { + color: "#0D3B7A", + }, + }), + }} + > + {recentCustomers} + + + 명 + + )} - - 명 - @@ -1600,6 +1600,83 @@ const DashboardPage = () => { surveyDetail={selectedSurvey} isLoading={surveyDetailLoading} /> + {/* 최근 유입 고객 모달 */} + setIsRecentCustomersModalOpen(false)} + maxWidth="md" + fullWidth + PaperProps={{ + sx: { + borderRadius: "12px", + }, + }} + > + + + 최근 유입 고객 + + + + + + + + 고객명 + 연락처 + 제출일 + + + + {Array.isArray(surveyResponses) && + surveyResponses.length === 0 ? ( + + + 신규 설문이 없습니다. + + + ) : ( + Array.isArray(surveyResponses) && + surveyResponses.map((res) => ( + handleSurveyClick(res.surveyResponseUid)} + sx={{ + cursor: "pointer", + "&:hover": { + backgroundColor: "rgba(22, 79, 158, 0.04)", + }, + }} + > + {res.name} + {res.phoneNumber} + {formatDate(res.submittedAt)} + + )) + )} + +
+
+
+ + + +
{/* Toast 메시지 */} Date: Wed, 14 May 2025 19:46:18 +0900 Subject: [PATCH 2/7] =?UTF-8?q?feat:=20=EC=B5=9C=EA=B7=BC=20=EA=B3=84?= =?UTF-8?q?=EC=95=BD=20=EA=B1=B4=EC=88=98=20=EC=98=81=EC=97=AD=20=ED=81=B4?= =?UTF-8?q?=EB=A6=AD=20=EC=8B=9C=20=EB=AA=A8=EB=8B=AC=20=ED=91=9C=EC=B6=9C?= =?UTF-8?q?=EB=90=98=EB=8F=84=EB=A1=9D=20=EC=BD=94=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/DashboardPage/DashboardPage.tsx | 341 +++++++++++++++++++--- 1 file changed, 299 insertions(+), 42 deletions(-) diff --git a/src/pages/DashboardPage/DashboardPage.tsx b/src/pages/DashboardPage/DashboardPage.tsx index e1d57b3..d633006 100644 --- a/src/pages/DashboardPage/DashboardPage.tsx +++ b/src/pages/DashboardPage/DashboardPage.tsx @@ -23,6 +23,8 @@ import { Chip, Tooltip, CircularProgress, + Modal, + TablePagination, } from "@mui/material"; import "./DashboardPage.css"; import AssignmentIcon from "@mui/icons-material/Assignment"; @@ -52,12 +54,12 @@ interface Contract { uid: number; lessorOrSellerNames: string[]; lesseeOrBuyerNames: string[]; - category: string; - contractDate: string; - contractStartDate: string; - contractEndDate: string; + category: "SALE" | "DEPOSIT" | "MONTHLY" | null; + contractDate: string | null; + contractStartDate: string | null; + contractEndDate: string | null; status: string; - address: string; + address?: string; } interface counsel { @@ -113,6 +115,19 @@ interface SurveyDetail { const SURVEY_PAGE_SIZE = 10; +const CONTRACT_STATUS_TYPES = [ + { value: "LISTED", name: "매물 등록", color: "default" }, + { value: "NEGOTIATING", name: "협상 중", color: "info" }, + { value: "INTENT_SIGNED", name: "가계약", color: "warning" }, + { value: "CANCELLED", name: "계약 취소", color: "error" }, + { value: "CONTRACTED", name: "계약 체결", color: "success" }, + { value: "IN_PROGRESS", name: "계약 진행 중", color: "primary" }, + { value: "PAID_COMPLETE", name: "잔금 지급 완료", color: "secondary" }, + { value: "REGISTERED", name: "등기 완료", color: "success" }, + { value: "MOVED_IN", name: "입주 완료", color: "success" }, + { value: "TERMINATED", name: "계약 해지", color: "error" }, +] as const; + const DashboardPage = () => { const [selectedDate, setSelectedDate] = useState(new Date()); const [counselTab, setcounselTab] = useState<"request" | "latest">("request"); @@ -121,7 +136,8 @@ const DashboardPage = () => { ); const navigate = useNavigate(); const [recentCustomers, setRecentCustomers] = useState(0); - const [recentContracts, setRecentContracts] = useState(0); + const [recentContractsCount, setRecentContractsCount] = useState(0); + const [recentContracts, setRecentContracts] = useState([]); const [recentContractsList, setRecentContractsList] = useState( [] ); @@ -159,6 +175,12 @@ const DashboardPage = () => { const [surveyDetailLoading, setSurveyDetailLoading] = useState(false); const [isRecentCustomersModalOpen, setIsRecentCustomersModalOpen] = useState(false); + const [recentContractsModalOpen, setRecentContractsModalOpen] = + useState(false); + const [recentContractsLoading, setRecentContractsLoading] = useState(false); + const [recentContractsPage, setRecentContractsPage] = useState(0); + const [recentContractsRowsPerPage, setRecentContractsRowsPerPage] = + useState(10); const fetchWeeklySchedules = async () => { try { @@ -207,6 +229,14 @@ const DashboardPage = () => { if (completedContractsRes.data.success) { setCompletedContracts(completedContractsRes.data.data); } + + // 최근 계약 건수는 contracts API의 totalElements로 세팅 + const recentContractsRes = await apiClient.get("/contracts", { + params: { recent: true, page: 0, size: 1 }, + }); + setRecentContractsCount( + recentContractsRes.data?.data?.totalElements ?? 0 + ); })(), ]); } catch (error) { @@ -331,11 +361,15 @@ const DashboardPage = () => { }, }), ]); - setExpiringContracts(expiringRes.data?.data?.contracts ?? []); - setRecentContractsList(recentRes.data?.data?.contracts ?? []); + const expiringContracts = expiringRes.data?.data?.contracts ?? []; + const recentContracts = recentRes.data?.data?.contracts ?? []; + setExpiringContracts(expiringContracts); + setRecentContractsList(recentContracts); + setRecentContractsCount(recentContracts.length); } catch { setExpiringContracts([]); setRecentContractsList([]); + setRecentContractsCount(0); } finally { setContractLoading(false); } @@ -343,6 +377,30 @@ const DashboardPage = () => { fetchAllContracts(); }, []); + // 최근 계약 목록을 가져오는 함수 + const fetchRecentContracts = async () => { + setRecentContractsLoading(true); + try { + const response = await apiClient.get("/contracts", { + params: { + recent: true, + page: recentContractsPage, + size: recentContractsRowsPerPage, + }, + }); + const contracts = response.data?.data?.contracts ?? []; + const totalElements = response.data?.data?.totalElements ?? 0; + setRecentContracts(contracts); + setRecentContractsCount(totalElements); + } catch (error) { + console.error("Failed to fetch recent contracts:", error); + setRecentContracts([]); + setRecentContractsCount(0); + } finally { + setRecentContractsLoading(false); + } + }; + // 더보기 클릭 핸들러 const handleMoreClick = (daySchedules: Schedule[], dayStr: string) => { setSelectedDaySchedules(daySchedules); @@ -465,6 +523,12 @@ const DashboardPage = () => { setSelectedSurvey(null); }; + // 최근 계약 건수 카드 클릭 핸들러 + const handleRecentContractsClick = () => { + fetchRecentContracts(); + setRecentContractsModalOpen(true); + }; + return ( { flexDirection: "column", justifyContent: "space-between", p: 2, + "&:hover": { + cursor: "pointer", + }, }} + onClick={handleRecentContractsClick} > @@ -619,42 +687,35 @@ const DashboardPage = () => { {isLoading ? ( - - - - + ) : ( - 0 && { - cursor: "pointer", - textDecoration: "underline", - "&:hover": { - color: "#0D3B7A", - }, - }), - }} - onClick={() => - recentContracts > 0 && navigate("/contracts") - } - > - {recentContracts} - + <> + 0 && { + cursor: "pointer", + textDecoration: "underline", + "&:hover": { + color: "#0D3B7A", + }, + }), + }} + > + {recentContractsCount} + + + 명 + + )} - - 건 - @@ -1677,6 +1738,202 @@ const DashboardPage = () => { + {/* 최근 계약 목록 모달 */} + setRecentContractsModalOpen(false)} + aria-labelledby="recent-contracts-modal" + > + + + 최근 계약 목록 + + + + + + 임대/매도인 + 임차/매수인 + 주소 + 계약 카테고리 + 계약일 + 계약 시작일 + 계약 종료일 + 상태 + + + + {recentContractsLoading ? ( + + + 불러오는 중... + + + ) : recentContracts.length === 0 ? ( + + + 계약 데이터가 없습니다 + + + ) : ( + recentContracts.map((contract) => ( + { + navigate(`/contracts/${contract.uid}`); + setRecentContractsModalOpen(false); + }} + > + + {Array.isArray(contract.lessorOrSellerNames) + ? contract.lessorOrSellerNames.length === 0 + ? "-" + : contract.lessorOrSellerNames.length === 1 + ? contract.lessorOrSellerNames[0] + : `${contract.lessorOrSellerNames[0]} 외 ${ + contract.lessorOrSellerNames.length - 1 + }명` + : "-"} + + + {Array.isArray(contract.lesseeOrBuyerNames) + ? contract.lesseeOrBuyerNames.length === 0 + ? "-" + : contract.lesseeOrBuyerNames.length === 1 + ? contract.lesseeOrBuyerNames[0] + : `${contract.lesseeOrBuyerNames[0]} 외 ${ + contract.lesseeOrBuyerNames.length - 1 + }명` + : "-"} + + + {contract.address ?? "-"} + + + {(() => { + const categoryKoreanMap: Record = { + SALE: "매매", + DEPOSIT: "전세", + MONTHLY: "월세", + }; + const colorMap: Record = { + SALE: "#4caf50", + DEPOSIT: "#2196f3", + MONTHLY: "#ff9800", + }; + if (!contract.category) return "-"; + return ( + + ); + })()} + + + {contract.contractDate ?? "-"} + + + {contract.contractStartDate ?? "-"} + + + {contract.contractEndDate ?? "-"} + + + {(() => { + const statusInfo = CONTRACT_STATUS_TYPES.find( + (item) => item.value === contract.status + ); + const getColor = (color: string) => { + switch (color) { + case "primary": + return "#1976d2"; + case "success": + return "#2e7d32"; + case "error": + return "#d32f2f"; + case "warning": + return "#ed6c02"; + case "info": + return "#0288d1"; + case "secondary": + return "#9c27b0"; + default: + return "#999"; + } + }; + return statusInfo ? ( + + ) : ( + contract.status + ); + })()} + + + )) + )} + +
+
+ { + setRecentContractsPage(newPage); + fetchRecentContracts(); + }} + onRowsPerPageChange={(e) => { + setRecentContractsRowsPerPage(parseInt(e.target.value, 10)); + setRecentContractsPage(0); + fetchRecentContracts(); + }} + rowsPerPageOptions={[10, 25, 50]} + labelRowsPerPage="페이지당 행 수" + labelDisplayedRows={({ from, to, count }) => + `${count}개 중 ${from}-${to}개` + } + /> +
+
{/* Toast 메시지 */} Date: Wed, 14 May 2025 20:08:50 +0900 Subject: [PATCH 3/7] =?UTF-8?q?chore:=20=EC=B5=9C=EA=B7=BC=20=EC=9C=A0?= =?UTF-8?q?=EC=9E=85=20=EA=B3=A0=EA=B0=9D=20=EB=AA=A8=EB=8B=AC=20=EB=B6=80?= =?UTF-8?q?=EB=B6=84=20=EC=BD=94=EB=93=9C=EB=A5=BC=20=EB=B3=84=EB=8F=84?= =?UTF-8?q?=EC=9D=98=20=ED=8C=8C=EC=9D=BC=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/DashboardPage/DashboardPage.tsx | 79 +----------- .../DashboardPage/RecentCustomersModal.tsx | 115 ++++++++++++++++++ 2 files changed, 120 insertions(+), 74 deletions(-) create mode 100644 src/pages/DashboardPage/RecentCustomersModal.tsx diff --git a/src/pages/DashboardPage/DashboardPage.tsx b/src/pages/DashboardPage/DashboardPage.tsx index d633006..4e5cb0d 100644 --- a/src/pages/DashboardPage/DashboardPage.tsx +++ b/src/pages/DashboardPage/DashboardPage.tsx @@ -46,6 +46,7 @@ import SurveyDetailModal from "@pages/DashboardPage/SurveyDetailModal"; import { Schedule } from "../../interfaces/schedule"; import useUserStore from "@stores/useUserStore"; import { formatDate } from "@utils/dateUtil"; +import RecentCustomersModal from "./RecentCustomersModal"; dayjs.extend(isSameOrAfter); dayjs.extend(isSameOrBefore); @@ -1662,82 +1663,12 @@ const DashboardPage = () => { isLoading={surveyDetailLoading} /> {/* 최근 유입 고객 모달 */} - setIsRecentCustomersModalOpen(false)} - maxWidth="md" - fullWidth - PaperProps={{ - sx: { - borderRadius: "12px", - }, - }} - > - - - 최근 유입 고객 - - - - - - - - 고객명 - 연락처 - 제출일 - - - - {Array.isArray(surveyResponses) && - surveyResponses.length === 0 ? ( - - - 신규 설문이 없습니다. - - - ) : ( - Array.isArray(surveyResponses) && - surveyResponses.map((res) => ( - handleSurveyClick(res.surveyResponseUid)} - sx={{ - cursor: "pointer", - "&:hover": { - backgroundColor: "rgba(22, 79, 158, 0.04)", - }, - }} - > - {res.name} - {res.phoneNumber} - {formatDate(res.submittedAt)} - - )) - )} - -
-
-
- - - -
+ surveyResponses={surveyResponses} + onSurveyClick={handleSurveyClick} + /> {/* 최근 계약 목록 모달 */} void; + surveyResponses: SurveyResponse[]; + onSurveyClick: (surveyResponseUid: number) => void; +} + +const RecentCustomersModal = ({ + open, + onClose, + surveyResponses, + onSurveyClick, +}: RecentCustomersModalProps) => { + return ( + + + + 최근 유입 고객 + + + + + + + + 고객명 + 연락처 + 제출일 + + + + {Array.isArray(surveyResponses) && + surveyResponses.length === 0 ? ( + + + 신규 설문이 없습니다. + + + ) : ( + Array.isArray(surveyResponses) && + surveyResponses.map((res) => ( + onSurveyClick(res.surveyResponseUid)} + sx={{ + cursor: "pointer", + "&:hover": { + backgroundColor: "rgba(22, 79, 158, 0.04)", + }, + }} + > + {res.name} + {res.phoneNumber} + {formatDate(res.submittedAt)} + + )) + )} + +
+
+
+ + + +
+ ); +}; + +export default RecentCustomersModal; From fd8cf2d1a3d1896b34dcd06536eb3e9f75ab8cd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B1=84=EC=A7=80=EC=9B=90?= Date: Wed, 14 May 2025 20:14:38 +0900 Subject: [PATCH 4/7] =?UTF-8?q?chore:=20=EC=B5=9C=EA=B7=BC=20=EA=B3=84?= =?UTF-8?q?=EC=95=BD=20=EB=AA=A8=EB=8B=AC=20=EB=B3=84=EB=8F=84=EC=9D=98=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/DashboardPage/DashboardPage.tsx | 240 +++------------- .../DashboardPage/RecentContractsModal.tsx | 262 ++++++++++++++++++ 2 files changed, 298 insertions(+), 204 deletions(-) create mode 100644 src/pages/DashboardPage/RecentContractsModal.tsx diff --git a/src/pages/DashboardPage/DashboardPage.tsx b/src/pages/DashboardPage/DashboardPage.tsx index 4e5cb0d..104fa8f 100644 --- a/src/pages/DashboardPage/DashboardPage.tsx +++ b/src/pages/DashboardPage/DashboardPage.tsx @@ -47,6 +47,7 @@ import { Schedule } from "../../interfaces/schedule"; import useUserStore from "@stores/useUserStore"; import { formatDate } from "@utils/dateUtil"; import RecentCustomersModal from "./RecentCustomersModal"; +import RecentContractsModal from "./RecentContractsModal"; dayjs.extend(isSameOrAfter); dayjs.extend(isSameOrBefore); @@ -208,7 +209,7 @@ const DashboardPage = () => { (async () => { const [ recentCustomersRes, - recentContractsRes, + recentContractsCountRes, ongoingContractsRes, completedContractsRes, ] = await Promise.all([ @@ -221,8 +222,8 @@ const DashboardPage = () => { if (recentCustomersRes.data.success) { setRecentCustomers(recentCustomersRes.data.data); } - if (recentContractsRes.data.success) { - setRecentContracts(recentContractsRes.data.data); + if (recentContractsCountRes.data.success) { + setRecentContractsCount(recentContractsCountRes.data.data); } if (ongoingContractsRes.data.success) { setOngoingContracts(ongoingContractsRes.data.data); @@ -231,12 +232,15 @@ const DashboardPage = () => { setCompletedContracts(completedContractsRes.data.data); } - // 최근 계약 건수는 contracts API의 totalElements로 세팅 - const recentContractsRes = await apiClient.get("/contracts", { - params: { recent: true, page: 0, size: 1 }, + // 최근 계약 목록 초기 데이터 로딩 + const recentContractsListRes = await apiClient.get("/contracts", { + params: { recent: true, page: 1, size: 10 }, }); + setRecentContracts( + recentContractsListRes.data?.data?.contracts ?? [] + ); setRecentContractsCount( - recentContractsRes.data?.data?.totalElements ?? 0 + recentContractsListRes.data?.data?.totalElements ?? 0 ); })(), ]); @@ -379,13 +383,13 @@ const DashboardPage = () => { }, []); // 최근 계약 목록을 가져오는 함수 - const fetchRecentContracts = async () => { + const fetchRecentContracts = async (page: number) => { setRecentContractsLoading(true); try { const response = await apiClient.get("/contracts", { params: { recent: true, - page: recentContractsPage, + page: page + 1, // 0-based를 1-based로 변환 size: recentContractsRowsPerPage, }, }); @@ -526,7 +530,8 @@ const DashboardPage = () => { // 최근 계약 건수 카드 클릭 핸들러 const handleRecentContractsClick = () => { - fetchRecentContracts(); + setRecentContractsPage(0); // 0-based로 초기화 + fetchRecentContracts(0); setRecentContractsModalOpen(true); }; @@ -1670,201 +1675,28 @@ const DashboardPage = () => { onSurveyClick={handleSurveyClick} /> {/* 최근 계약 목록 모달 */} - setRecentContractsModalOpen(false)} - aria-labelledby="recent-contracts-modal" - > - - - 최근 계약 목록 - - - - - - 임대/매도인 - 임차/매수인 - 주소 - 계약 카테고리 - 계약일 - 계약 시작일 - 계약 종료일 - 상태 - - - - {recentContractsLoading ? ( - - - 불러오는 중... - - - ) : recentContracts.length === 0 ? ( - - - 계약 데이터가 없습니다 - - - ) : ( - recentContracts.map((contract) => ( - { - navigate(`/contracts/${contract.uid}`); - setRecentContractsModalOpen(false); - }} - > - - {Array.isArray(contract.lessorOrSellerNames) - ? contract.lessorOrSellerNames.length === 0 - ? "-" - : contract.lessorOrSellerNames.length === 1 - ? contract.lessorOrSellerNames[0] - : `${contract.lessorOrSellerNames[0]} 외 ${ - contract.lessorOrSellerNames.length - 1 - }명` - : "-"} - - - {Array.isArray(contract.lesseeOrBuyerNames) - ? contract.lesseeOrBuyerNames.length === 0 - ? "-" - : contract.lesseeOrBuyerNames.length === 1 - ? contract.lesseeOrBuyerNames[0] - : `${contract.lesseeOrBuyerNames[0]} 외 ${ - contract.lesseeOrBuyerNames.length - 1 - }명` - : "-"} - - - {contract.address ?? "-"} - - - {(() => { - const categoryKoreanMap: Record = { - SALE: "매매", - DEPOSIT: "전세", - MONTHLY: "월세", - }; - const colorMap: Record = { - SALE: "#4caf50", - DEPOSIT: "#2196f3", - MONTHLY: "#ff9800", - }; - if (!contract.category) return "-"; - return ( - - ); - })()} - - - {contract.contractDate ?? "-"} - - - {contract.contractStartDate ?? "-"} - - - {contract.contractEndDate ?? "-"} - - - {(() => { - const statusInfo = CONTRACT_STATUS_TYPES.find( - (item) => item.value === contract.status - ); - const getColor = (color: string) => { - switch (color) { - case "primary": - return "#1976d2"; - case "success": - return "#2e7d32"; - case "error": - return "#d32f2f"; - case "warning": - return "#ed6c02"; - case "info": - return "#0288d1"; - case "secondary": - return "#9c27b0"; - default: - return "#999"; - } - }; - return statusInfo ? ( - - ) : ( - contract.status - ); - })()} - - - )) - )} - -
-
- { - setRecentContractsPage(newPage); - fetchRecentContracts(); - }} - onRowsPerPageChange={(e) => { - setRecentContractsRowsPerPage(parseInt(e.target.value, 10)); - setRecentContractsPage(0); - fetchRecentContracts(); - }} - rowsPerPageOptions={[10, 25, 50]} - labelRowsPerPage="페이지당 행 수" - labelDisplayedRows={({ from, to, count }) => - `${count}개 중 ${from}-${to}개` - } - /> -
-
+ onClose={() => { + setRecentContractsModalOpen(false); + setRecentContractsPage(0); // 0-based로 초기화 + setRecentContractsRowsPerPage(10); + }} + contracts={recentContracts} + loading={recentContractsLoading} + totalCount={recentContractsCount} + page={recentContractsPage} + rowsPerPage={recentContractsRowsPerPage} + onPageChange={(newPage) => { + setRecentContractsPage(newPage); + fetchRecentContracts(newPage); + }} + onRowsPerPageChange={(newRowsPerPage) => { + setRecentContractsRowsPerPage(newRowsPerPage); + setRecentContractsPage(0); + fetchRecentContracts(0); + }} + /> {/* Toast 메시지 */} void; + contracts: Contract[]; + loading: boolean; + totalCount: number; + page: number; + rowsPerPage: number; + onPageChange: (newPage: number) => void; + onRowsPerPageChange: (newRowsPerPage: number) => void; +} + +const CONTRACT_STATUS_TYPES = [ + { value: "LISTED", name: "매물 등록", color: "default" }, + { value: "NEGOTIATING", name: "협상 중", color: "info" }, + { value: "INTENT_SIGNED", name: "가계약", color: "warning" }, + { value: "CANCELLED", name: "계약 취소", color: "error" }, + { value: "CONTRACTED", name: "계약 체결", color: "success" }, + { value: "IN_PROGRESS", name: "계약 진행 중", color: "primary" }, + { value: "PAID_COMPLETE", name: "잔금 지급 완료", color: "secondary" }, + { value: "REGISTERED", name: "등기 완료", color: "success" }, + { value: "MOVED_IN", name: "입주 완료", color: "success" }, + { value: "TERMINATED", name: "계약 해지", color: "error" }, +] as const; + +const RecentContractsModal = ({ + open, + onClose, + contracts, + loading, + totalCount, + page, + rowsPerPage, + onPageChange, + onRowsPerPageChange, +}: RecentContractsModalProps) => { + const navigate = useNavigate(); + + const getColor = (color: string) => { + switch (color) { + case "primary": + return "#1976d2"; + case "success": + return "#2e7d32"; + case "error": + return "#d32f2f"; + case "warning": + return "#ed6c02"; + case "info": + return "#0288d1"; + case "secondary": + return "#9c27b0"; + default: + return "#999"; + } + }; + + return ( + + + + 최근 계약 목록 + + + + + + 임대/매도인 + 임차/매수인 + 주소 + 계약 카테고리 + 계약일 + 계약 시작일 + 계약 종료일 + 상태 + + + + {loading ? ( + + + 불러오는 중... + + + ) : contracts.length === 0 ? ( + + + 계약 데이터가 없습니다 + + + ) : ( + contracts.map((contract) => ( + { + navigate(`/contracts/${contract.uid}`); + onClose(); + }} + > + + {Array.isArray(contract.lessorOrSellerNames) + ? contract.lessorOrSellerNames.length === 0 + ? "-" + : contract.lessorOrSellerNames.length === 1 + ? contract.lessorOrSellerNames[0] + : `${contract.lessorOrSellerNames[0]} 외 ${ + contract.lessorOrSellerNames.length - 1 + }명` + : "-"} + + + {Array.isArray(contract.lesseeOrBuyerNames) + ? contract.lesseeOrBuyerNames.length === 0 + ? "-" + : contract.lesseeOrBuyerNames.length === 1 + ? contract.lesseeOrBuyerNames[0] + : `${contract.lesseeOrBuyerNames[0]} 외 ${ + contract.lesseeOrBuyerNames.length - 1 + }명` + : "-"} + + + {contract.address ?? "-"} + + + {(() => { + const categoryKoreanMap: Record = { + SALE: "매매", + DEPOSIT: "전세", + MONTHLY: "월세", + }; + const colorMap: Record = { + SALE: "#4caf50", + DEPOSIT: "#2196f3", + MONTHLY: "#ff9800", + }; + if (!contract.category) return "-"; + return ( + + ); + })()} + + + {contract.contractDate ?? "-"} + + + {contract.contractStartDate ?? "-"} + + + {contract.contractEndDate ?? "-"} + + + {(() => { + const statusInfo = CONTRACT_STATUS_TYPES.find( + (item) => item.value === contract.status + ); + return statusInfo ? ( + + ) : ( + contract.status + ); + })()} + + + )) + )} + +
+
+ onPageChange(newPage)} + onRowsPerPageChange={(e) => + onRowsPerPageChange(parseInt(e.target.value, 10)) + } + rowsPerPageOptions={[10, 25, 50]} + labelRowsPerPage="페이지당 행 수" + labelDisplayedRows={({ from, to, count }) => + `${count}개 중 ${from}-${to}개` + } + /> +
+
+ ); +}; + +export default RecentContractsModal; From 2a47d5995a2f1ede4e6de69f83e43d6511461d2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B1=84=EC=A7=80=EC=9B=90?= Date: Wed, 14 May 2025 20:35:46 +0900 Subject: [PATCH 5/7] =?UTF-8?q?chore:=20=EC=A7=84=ED=96=89=EC=A4=91=20?= =?UTF-8?q?=EA=B3=84=EC=95=BD=20=EB=AA=A8=EB=8B=AC=20=EB=B3=84=EB=8F=84?= =?UTF-8?q?=EC=9D=98=20=ED=8C=8C=EC=9D=BC=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/DashboardPage/DashboardPage.tsx | 94 +++++-- .../DashboardPage/OngoingContractsModal.tsx | 263 ++++++++++++++++++ 2 files changed, 337 insertions(+), 20 deletions(-) create mode 100644 src/pages/DashboardPage/OngoingContractsModal.tsx diff --git a/src/pages/DashboardPage/DashboardPage.tsx b/src/pages/DashboardPage/DashboardPage.tsx index 104fa8f..43b4d85 100644 --- a/src/pages/DashboardPage/DashboardPage.tsx +++ b/src/pages/DashboardPage/DashboardPage.tsx @@ -34,7 +34,7 @@ import PersonAddIcon from "@mui/icons-material/PersonAdd"; import ChevronLeftIcon from "@mui/icons-material/ChevronLeft"; import ChevronRightIcon from "@mui/icons-material/ChevronRight"; import HelpOutlineIcon from "@mui/icons-material/HelpOutline"; -import { useState, useEffect, useMemo } from "react"; +import { useState, useEffect, useMemo, useCallback } from "react"; import dayjs from "dayjs"; import isSameOrAfter from "dayjs/plugin/isSameOrAfter"; import isSameOrBefore from "dayjs/plugin/isSameOrBefore"; @@ -48,6 +48,7 @@ import useUserStore from "@stores/useUserStore"; import { formatDate } from "@utils/dateUtil"; import RecentCustomersModal from "./RecentCustomersModal"; import RecentContractsModal from "./RecentContractsModal"; +import OngoingContractsModal from "./OngoingContractsModal"; dayjs.extend(isSameOrAfter); dayjs.extend(isSameOrBefore); @@ -183,6 +184,16 @@ const DashboardPage = () => { const [recentContractsPage, setRecentContractsPage] = useState(0); const [recentContractsRowsPerPage, setRecentContractsRowsPerPage] = useState(10); + const [isOngoingContractsModalOpen, setIsOngoingContractsModalOpen] = + useState(false); + const [ongoingContractsList, setOngoingContractsList] = useState([]); + const [ongoingContractsPage, setOngoingContractsPage] = useState(0); + const [ongoingContractsRowsPerPage, setOngoingContractsRowsPerPage] = + useState(10); + const [isOngoingContractsLoading, setIsOngoingContractsLoading] = + useState(false); + const [ongoingContractsTotalCount, setOngoingContractsTotalCount] = + useState(0); const fetchWeeklySchedules = async () => { try { @@ -535,6 +546,37 @@ const DashboardPage = () => { setRecentContractsModalOpen(true); }; + const fetchOngoingContracts = useCallback(() => { + setIsOngoingContractsLoading(true); + apiClient + .get("/contracts", { + params: { + progress: true, + page: ongoingContractsPage + 1, + size: ongoingContractsRowsPerPage, + }, + }) + .then((res) => { + const contractData = res?.data?.data?.contracts; + const pageInfo = res?.data?.data; + setOngoingContractsList(contractData || []); + setOngoingContractsTotalCount(pageInfo?.totalElements || 0); + }) + .catch((error) => { + console.error("Failed to fetch ongoing contracts:", error); + setOngoingContractsList([]); + }) + .finally(() => { + setIsOngoingContractsLoading(false); + }); + }, [ongoingContractsPage, ongoingContractsRowsPerPage]); + + useEffect(() => { + if (isOngoingContractsModalOpen) { + fetchOngoingContracts(); + } + }, [isOngoingContractsModalOpen, fetchOngoingContracts]); + return ( { }} > - + - 진행중인 계약 건수 + 완료된 계약 건수 @@ -788,7 +830,7 @@ const DashboardPage = () => { sx={{ fontWeight: "bold", color: "#164F9E", - ...(ongoingContracts > 0 && { + ...(completedContracts > 0 && { cursor: "pointer", textDecoration: "underline", "&:hover": { @@ -797,10 +839,10 @@ const DashboardPage = () => { }), }} onClick={() => - ongoingContracts > 0 && navigate("/contracts") + completedContracts > 0 && navigate("/contracts") } > - {ongoingContracts} + {completedContracts} )} { flexDirection: "column", justifyContent: "space-between", p: 2, + "&:hover": { + cursor: "pointer", + }, }} + onClick={() => + ongoingContracts > 0 && setIsOngoingContractsModalOpen(true) + } > - + - 완료된 계약 건수 + 진행중인 계약 건수 {isLoading ? ( - - - - + ) : ( { sx={{ fontWeight: "bold", color: "#164F9E", - ...(completedContracts > 0 && { + ...(ongoingContracts > 0 && { cursor: "pointer", textDecoration: "underline", "&:hover": { @@ -884,11 +926,8 @@ const DashboardPage = () => { }, }), }} - onClick={() => - completedContracts > 0 && navigate("/contracts") - } > - {completedContracts} + {ongoingContracts} )} { fetchRecentContracts(0); }} /> + {/* Ongoing Contracts Modal */} + setIsOngoingContractsModalOpen(false)} + contracts={ongoingContractsList} + loading={isOngoingContractsLoading} + totalCount={ongoingContractsTotalCount} + page={ongoingContractsPage} + rowsPerPage={ongoingContractsRowsPerPage} + onPageChange={(newPage) => setOngoingContractsPage(newPage)} + onRowsPerPageChange={(newRowsPerPage) => { + setOngoingContractsRowsPerPage(newRowsPerPage); + setOngoingContractsPage(0); + }} + /> {/* Toast 메시지 */} void; + contracts: Contract[]; + loading: boolean; + totalCount: number; + page: number; + rowsPerPage: number; + onPageChange: (newPage: number) => void; + onRowsPerPageChange: (newRowsPerPage: number) => void; +} + +const CONTRACT_STATUS_TYPES = [ + { value: "LISTED", name: "매물 등록", color: "default" }, + { value: "NEGOTIATING", name: "협상 중", color: "info" }, + { value: "INTENT_SIGNED", name: "가계약", color: "warning" }, + { value: "CANCELLED", name: "계약 취소", color: "error" }, + { value: "CONTRACTED", name: "계약 체결", color: "success" }, + { value: "IN_PROGRESS", name: "계약 진행 중", color: "primary" }, + { value: "PAID_COMPLETE", name: "잔금 지급 완료", color: "secondary" }, + { value: "REGISTERED", name: "등기 완료", color: "success" }, + { value: "MOVED_IN", name: "입주 완료", color: "success" }, + { value: "TERMINATED", name: "계약 해지", color: "error" }, +] as const; + +const categoryKoreanMap: Record = { + SALE: "매매", + DEPOSIT: "전세", + MONTHLY: "월세", +}; + +const OngoingContractsModal = ({ + open, + onClose, + contracts, + loading, + totalCount, + page, + rowsPerPage, + onPageChange, + onRowsPerPageChange, +}: OngoingContractsModalProps) => { + const navigate = useNavigate(); + + const getColor = (color: string) => { + switch (color) { + case "primary": + return "#1976d2"; + case "success": + return "#2e7d32"; + case "error": + return "#d32f2f"; + case "warning": + return "#ed6c02"; + case "info": + return "#0288d1"; + case "secondary": + return "#9c27b0"; + default: + return "#999"; + } + }; + + const getStatusChip = (status: string) => { + const statusInfo = CONTRACT_STATUS_TYPES.find( + (item) => item.value === status + ); + if (!statusInfo) return status; + + return ( + + ); + }; + + const getCategoryChip = (category: string | null) => { + if (!category || category === "null") return "-"; + const label = categoryKoreanMap[category] ?? category; + const colorMap: Record = { + SALE: "#4caf50", + DEPOSIT: "#2196f3", + MONTHLY: "#ff9800", + }; + return ( + + ); + }; + + const formatDate = (date: string | null) => { + if (!date) return "-"; + return new Date(date).toLocaleDateString("ko-KR", { + year: "numeric", + month: "2-digit", + day: "2-digit", + }); + }; + + const handleRowClick = (contractUid: number) => { + navigate(`/contracts/${contractUid}`); + }; + + return ( + + + + 진행중인 계약 목록 + + + + + + + + + 임대/매도인 + 임차/매수인 + 주소 + 계약 카테고리 + 계약일 + 계약 시작일 + 계약 종료일 + 상태 + + + + {loading ? ( + + + + + + ) : contracts.length === 0 ? ( + + + 진행중인 계약이 없습니다. + + + ) : ( + contracts.map((contract) => ( + handleRowClick(contract.uid)} + > + + {contract.lessorOrSellerNames.join(", ") || "-"} + + + {contract.lesseeOrBuyerNames.join(", ") || "-"} + + {contract.address} + + {getCategoryChip(contract.category)} + + + {formatDate(contract.contractDate)} + + + {formatDate(contract.contractStartDate)} + + + {formatDate(contract.contractEndDate)} + + + {getStatusChip(contract.status)} + + + )) + )} + +
+
+ onPageChange(newPage)} + onRowsPerPageChange={(e) => + onRowsPerPageChange(parseInt(e.target.value, 10)) + } + rowsPerPageOptions={[10, 25, 50]} + labelRowsPerPage="페이지당 행 수" + labelDisplayedRows={({ from, to, count }) => + `${count}개 중 ${from}-${to}개` + } + /> +
+
+
+
+ ); +}; + +export default OngoingContractsModal; From cdfd9d9cadf0d2aba6899ee37a3e89ad8844f79c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B1=84=EC=A7=80=EC=9B=90?= Date: Wed, 14 May 2025 20:59:01 +0900 Subject: [PATCH 6/7] =?UTF-8?q?chore:=20=EC=99=84=EB=A3=8C=EB=90=9C=20?= =?UTF-8?q?=EA=B3=84=EC=95=BD=20=EB=AA=A8=EB=8B=AC=20=EB=B6=80=EB=B6=84=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=EB=A5=BC=20=EB=B3=84=EB=8F=84=EC=9D=98=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DashboardPage/CompletedContractsModal.tsx | 263 ++++++++++++++++++ src/pages/DashboardPage/DashboardPage.tsx | 142 +++++++--- 2 files changed, 362 insertions(+), 43 deletions(-) create mode 100644 src/pages/DashboardPage/CompletedContractsModal.tsx diff --git a/src/pages/DashboardPage/CompletedContractsModal.tsx b/src/pages/DashboardPage/CompletedContractsModal.tsx new file mode 100644 index 0000000..ec44f45 --- /dev/null +++ b/src/pages/DashboardPage/CompletedContractsModal.tsx @@ -0,0 +1,263 @@ +import { + Modal, + Box, + Typography, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Paper, + Chip, + TablePagination, + CircularProgress, +} from "@mui/material"; +import { useNavigate } from "react-router-dom"; + +interface Contract { + uid: number; + lessorOrSellerNames: string[]; + lesseeOrBuyerNames: string[]; + category: "SALE" | "DEPOSIT" | "MONTHLY" | null; + contractDate: string | null; + contractStartDate: string | null; + contractEndDate: string | null; + status: string; + address: string; +} + +interface CompletedContractsModalProps { + open: boolean; + onClose: () => void; + contracts: Contract[]; + loading: boolean; + totalCount: number; + page: number; + rowsPerPage: number; + onPageChange: (newPage: number) => void; + onRowsPerPageChange: (newRowsPerPage: number) => void; +} + +const CONTRACT_STATUS_TYPES = [ + { value: "LISTED", name: "매물 등록", color: "default" }, + { value: "NEGOTIATING", name: "협상 중", color: "info" }, + { value: "INTENT_SIGNED", name: "가계약", color: "warning" }, + { value: "CANCELLED", name: "계약 취소", color: "error" }, + { value: "CONTRACTED", name: "계약 체결", color: "success" }, + { value: "IN_PROGRESS", name: "계약 진행 중", color: "primary" }, + { value: "PAID_COMPLETE", name: "잔금 지급 완료", color: "secondary" }, + { value: "REGISTERED", name: "등기 완료", color: "success" }, + { value: "MOVED_IN", name: "입주 완료", color: "success" }, + { value: "TERMINATED", name: "계약 해지", color: "error" }, +] as const; + +const categoryKoreanMap: Record = { + SALE: "매매", + DEPOSIT: "전세", + MONTHLY: "월세", +}; + +const CompletedContractsModal = ({ + open, + onClose, + contracts = [], + loading, + totalCount, + page, + rowsPerPage, + onPageChange, + onRowsPerPageChange, +}: CompletedContractsModalProps) => { + const navigate = useNavigate(); + + const getColor = (color: string) => { + switch (color) { + case "primary": + return "#1976d2"; + case "success": + return "#2e7d32"; + case "error": + return "#d32f2f"; + case "warning": + return "#ed6c02"; + case "info": + return "#0288d1"; + case "secondary": + return "#9c27b0"; + default: + return "#999"; + } + }; + + const getStatusChip = (status: string) => { + const statusInfo = CONTRACT_STATUS_TYPES.find( + (item) => item.value === status + ); + if (!statusInfo) return status; + + return ( + + ); + }; + + const getCategoryChip = (category: string | null) => { + if (!category || category === "null") return "-"; + const label = categoryKoreanMap[category] ?? category; + const colorMap: Record = { + SALE: "#4caf50", + DEPOSIT: "#2196f3", + MONTHLY: "#ff9800", + }; + return ( + + ); + }; + + const formatDate = (date: string | null) => { + if (!date) return "-"; + return new Date(date).toLocaleDateString("ko-KR", { + year: "numeric", + month: "2-digit", + day: "2-digit", + }); + }; + + const handleRowClick = (contractUid: number) => { + navigate(`/contracts/${contractUid}`); + }; + + return ( + + + + 완료된 계약 목록 + + + + + + + + + 임대/매도인 + 임차/매수인 + 주소 + 계약 카테고리 + 계약일 + 계약 시작일 + 계약 종료일 + 상태 + + + + {loading ? ( + + + + + + ) : contracts.length === 0 ? ( + + + 완료된 계약이 없습니다. + + + ) : ( + contracts.map((contract) => ( + handleRowClick(contract.uid)} + > + + {contract.lessorOrSellerNames?.join(", ") || "-"} + + + {contract.lesseeOrBuyerNames?.join(", ") || "-"} + + {contract.address} + + {getCategoryChip(contract.category)} + + + {formatDate(contract.contractDate)} + + + {formatDate(contract.contractStartDate)} + + + {formatDate(contract.contractEndDate)} + + + {getStatusChip(contract.status)} + + + )) + )} + +
+
+ onPageChange(newPage)} + onRowsPerPageChange={(e) => + onRowsPerPageChange(parseInt(e.target.value, 10)) + } + rowsPerPageOptions={[10, 25, 50]} + labelRowsPerPage="페이지당 행 수" + labelDisplayedRows={({ from, to, count }) => + `${count}개 중 ${from}-${to}개` + } + /> +
+
+
+
+ ); +}; + +export default CompletedContractsModal; diff --git a/src/pages/DashboardPage/DashboardPage.tsx b/src/pages/DashboardPage/DashboardPage.tsx index 43b4d85..19a31be 100644 --- a/src/pages/DashboardPage/DashboardPage.tsx +++ b/src/pages/DashboardPage/DashboardPage.tsx @@ -49,6 +49,7 @@ import { formatDate } from "@utils/dateUtil"; import RecentCustomersModal from "./RecentCustomersModal"; import RecentContractsModal from "./RecentContractsModal"; import OngoingContractsModal from "./OngoingContractsModal"; +import CompletedContractsModal from "./CompletedContractsModal"; dayjs.extend(isSameOrAfter); dayjs.extend(isSameOrBefore); @@ -62,7 +63,7 @@ interface Contract { contractStartDate: string | null; contractEndDate: string | null; status: string; - address?: string; + address: string; } interface counsel { @@ -184,16 +185,27 @@ const DashboardPage = () => { const [recentContractsPage, setRecentContractsPage] = useState(0); const [recentContractsRowsPerPage, setRecentContractsRowsPerPage] = useState(10); - const [isOngoingContractsModalOpen, setIsOngoingContractsModalOpen] = - useState(false); - const [ongoingContractsList, setOngoingContractsList] = useState([]); + const [ongoingContractsOpen, setOngoingContractsOpen] = useState(false); + const [ongoingContractsList, setOngoingContractsList] = useState( + [] + ); const [ongoingContractsPage, setOngoingContractsPage] = useState(0); const [ongoingContractsRowsPerPage, setOngoingContractsRowsPerPage] = useState(10); - const [isOngoingContractsLoading, setIsOngoingContractsLoading] = - useState(false); + const [ongoingContractsLoading, setOngoingContractsLoading] = useState(false); const [ongoingContractsTotalCount, setOngoingContractsTotalCount] = useState(0); + const [completedContractsOpen, setCompletedContractsOpen] = useState(false); + const [completedContractsList, setCompletedContractsList] = useState< + Contract[] + >([]); + const [completedContractsPage, setCompletedContractsPage] = useState(0); + const [completedContractsRowsPerPage, setCompletedContractsRowsPerPage] = + useState(10); + const [completedContractsLoading, setCompletedContractsLoading] = + useState(false); + const [completedContractsTotalCount, setCompletedContractsTotalCount] = + useState(0); const fetchWeeklySchedules = async () => { try { @@ -546,36 +558,60 @@ const DashboardPage = () => { setRecentContractsModalOpen(true); }; - const fetchOngoingContracts = useCallback(() => { - setIsOngoingContractsLoading(true); - apiClient - .get("/contracts", { + const fetchOngoingContracts = useCallback(async () => { + try { + setOngoingContractsLoading(true); + const response = await apiClient.get("/contracts", { params: { progress: true, page: ongoingContractsPage + 1, size: ongoingContractsRowsPerPage, }, - }) - .then((res) => { - const contractData = res?.data?.data?.contracts; - const pageInfo = res?.data?.data; - setOngoingContractsList(contractData || []); - setOngoingContractsTotalCount(pageInfo?.totalElements || 0); - }) - .catch((error) => { - console.error("Failed to fetch ongoing contracts:", error); - setOngoingContractsList([]); - }) - .finally(() => { - setIsOngoingContractsLoading(false); }); + + if (response.data.success) { + setOngoingContractsList(response.data.data.contracts); + setOngoingContractsTotalCount(response.data.data.totalElements); + } + } catch (error) { + console.error("Failed to fetch ongoing contracts:", error); + } finally { + setOngoingContractsLoading(false); + } }, [ongoingContractsPage, ongoingContractsRowsPerPage]); useEffect(() => { - if (isOngoingContractsModalOpen) { + if (ongoingContractsOpen) { fetchOngoingContracts(); } - }, [isOngoingContractsModalOpen, fetchOngoingContracts]); + }, [ongoingContractsOpen, fetchOngoingContracts]); + + const fetchCompletedContracts = useCallback(async () => { + try { + setCompletedContractsLoading(true); + const response = await apiClient.get("/contracts", { + params: { + progress: false, + page: completedContractsPage + 1, + size: completedContractsRowsPerPage, + }, + }); + if (response.data.success) { + setCompletedContractsList(response.data.data.contracts); + setCompletedContractsTotalCount(response.data.data.totalElements); + } + } catch (error) { + console.error("Failed to fetch completed contracts:", error); + } finally { + setCompletedContractsLoading(false); + } + }, [completedContractsPage, completedContractsRowsPerPage]); + + useEffect(() => { + if (completedContractsOpen) { + fetchCompletedContracts(); + } + }, [completedContractsOpen]); return ( { }} > - recentCustomers > 0 && setIsRecentCustomersModalOpen(true) - } + onClick={() => setIsRecentCustomersModalOpen(true)} sx={{ flex: 1, display: "flex", @@ -801,7 +835,11 @@ const DashboardPage = () => { flexDirection: "column", justifyContent: "space-between", p: 2, + "&:hover": { + cursor: "pointer", + }, }} + onClick={() => setCompletedContractsOpen(true)} > @@ -816,13 +854,7 @@ const DashboardPage = () => { {isLoading ? ( - - - - + ) : ( { }, }), }} - onClick={() => - completedContracts > 0 && navigate("/contracts") - } > {completedContracts} @@ -894,7 +923,7 @@ const DashboardPage = () => { }, }} onClick={() => - ongoingContracts > 0 && setIsOngoingContractsModalOpen(true) + ongoingContracts > 0 && setOngoingContractsOpen(true) } > @@ -1738,19 +1767,46 @@ const DashboardPage = () => { /> {/* Ongoing Contracts Modal */} setIsOngoingContractsModalOpen(false)} + open={ongoingContractsOpen} + onClose={() => { + setOngoingContractsOpen(false); + setOngoingContractsPage(0); + setOngoingContractsRowsPerPage(10); + }} contracts={ongoingContractsList} - loading={isOngoingContractsLoading} + loading={ongoingContractsLoading} totalCount={ongoingContractsTotalCount} page={ongoingContractsPage} rowsPerPage={ongoingContractsRowsPerPage} - onPageChange={(newPage) => setOngoingContractsPage(newPage)} + onPageChange={(newPage) => { + setOngoingContractsPage(newPage); + }} onRowsPerPageChange={(newRowsPerPage) => { setOngoingContractsRowsPerPage(newRowsPerPage); setOngoingContractsPage(0); }} /> + {/* Completed Contracts Modal */} + { + setCompletedContractsOpen(false); + setCompletedContractsPage(0); + setCompletedContractsRowsPerPage(10); + }} + contracts={completedContractsList} + loading={completedContractsLoading} + totalCount={completedContractsTotalCount} + page={completedContractsPage} + rowsPerPage={completedContractsRowsPerPage} + onPageChange={(newPage) => { + setCompletedContractsPage(newPage); + }} + onRowsPerPageChange={(newRowsPerPage) => { + setCompletedContractsRowsPerPage(newRowsPerPage); + setCompletedContractsPage(0); + }} + /> {/* Toast 메시지 */} Date: Wed, 14 May 2025 21:03:49 +0900 Subject: [PATCH 7/7] =?UTF-8?q?chore:=20=EC=A7=84=ED=96=89=EC=A4=91?= =?UTF-8?q?=EC=9D=B8=20=EA=B3=84=EC=95=BD=20=EA=B1=B4=EC=88=98=20<->=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C=EB=90=9C=20=EA=B3=84=EC=95=BD=20=EA=B1=B4?= =?UTF-8?q?=EC=88=98=20=EC=B9=B4=EB=93=9C=20=EC=98=81=EC=97=AD=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/DashboardPage/DashboardPage.tsx | 25 +++++++++++------------ 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/pages/DashboardPage/DashboardPage.tsx b/src/pages/DashboardPage/DashboardPage.tsx index 19a31be..0a44edd 100644 --- a/src/pages/DashboardPage/DashboardPage.tsx +++ b/src/pages/DashboardPage/DashboardPage.tsx @@ -839,16 +839,18 @@ const DashboardPage = () => { cursor: "pointer", }, }} - onClick={() => setCompletedContractsOpen(true)} + onClick={() => + ongoingContracts > 0 && setOngoingContractsOpen(true) + } > - + - 완료된 계약 건수 + 진행중인 계약 건수 @@ -862,7 +864,7 @@ const DashboardPage = () => { sx={{ fontWeight: "bold", color: "#164F9E", - ...(completedContracts > 0 && { + ...(ongoingContracts > 0 && { cursor: "pointer", textDecoration: "underline", "&:hover": { @@ -871,7 +873,7 @@ const DashboardPage = () => { }), }} > - {completedContracts} + {ongoingContracts}
)} {
- { cursor: "pointer", }, }} - onClick={() => - ongoingContracts > 0 && setOngoingContractsOpen(true) - } + onClick={() => setCompletedContractsOpen(true)} > - + - 진행중인 계약 건수 + 완료된 계약 건수 @@ -947,7 +946,7 @@ const DashboardPage = () => { sx={{ fontWeight: "bold", color: "#164F9E", - ...(ongoingContracts > 0 && { + ...(completedContracts > 0 && { cursor: "pointer", textDecoration: "underline", "&:hover": { @@ -956,7 +955,7 @@ const DashboardPage = () => { }), }} > - {ongoingContracts} + {completedContracts}
)}