diff --git a/src/apis/apiClient.ts b/src/apis/apiClient.ts index 658909e..4932675 100644 --- a/src/apis/apiClient.ts +++ b/src/apis/apiClient.ts @@ -45,17 +45,21 @@ apiClient.interceptors.response.use( const originalRequest = error.config; const isLoginRequest = originalRequest?.url?.includes("/users/login"); - if (error.response.status === 401 && !originalRequest.__isRetryRequest && !isLoginRequest) { + if ( + error.response.status === 401 && + !originalRequest.__isRetryRequest && + !isLoginRequest + ) { try { originalRequest.__isRetryRequest = true; const deviceId = localStorage.getItem("deviceId"); -const refreshResponse = await reissueTokenClient.get("/users/reissue", { - headers: { - "X-Device-Id": deviceId ?? "", - }, -});; + const refreshResponse = await reissueTokenClient.get("/users/reissue", { + headers: { + "X-Device-Id": deviceId ?? "", + }, + }); const newAccessToken = refreshResponse?.data?.data?.accessToken ?? null; if (!newAccessToken) { @@ -71,7 +75,7 @@ const refreshResponse = await reissueTokenClient.get("/users/reissue", { return axios(originalRequest); } catch (refreshError) { sessionStorage.removeItem("_ZA"); - toast.error("인증이 만료되었습니다. 다시 로그인하세요."); + toast.error("인증이 만료되었습니다. 다시 로그인하세요."); window.location.replace("/sign-in"); return Promise.reject(refreshError); } diff --git a/src/components/DeleteConfirm/DeleteConfirmModal.tsx b/src/components/DeleteConfirm/DeleteConfirmModal.tsx index 7f3f8a6..6d05bc8 100644 --- a/src/components/DeleteConfirm/DeleteConfirmModal.tsx +++ b/src/components/DeleteConfirm/DeleteConfirmModal.tsx @@ -1,66 +1,79 @@ import { - Dialog, - DialogTitle, - DialogContent, - DialogActions, - Button, - IconButton, - Typography, - } from "@mui/material"; - import CloseIcon from "@mui/icons-material/Close"; - - interface DeleteConfirmModalProps { - open: boolean; - onConfirm: () => void; - onCancel: () => void; - } - - const DeleteConfirmModal = ({ open, onConfirm, onCancel }: DeleteConfirmModalProps) => { - return ( - void; + onCancel: () => void; + category: string; +} + +const DeleteConfirmModal = ({ + open, + onConfirm, + onCancel, + category = "", +}: DeleteConfirmModalProps) => { + return ( + + - - 경고 - - - - - - - - 정말 삭제하시겠습니까? - - - - - - - - - ); - }; - - export default DeleteConfirmModal; - \ No newline at end of file + + {category} 삭제 + + + + + + + + + 이 {category}을 정말 삭제할까요? + + + + + + + + + ); +}; + +export default DeleteConfirmModal; diff --git a/src/components/PageHeader/PageHeader.tsx b/src/components/PageHeader/PageHeader.tsx index 77312c2..0857f07 100644 --- a/src/components/PageHeader/PageHeader.tsx +++ b/src/components/PageHeader/PageHeader.tsx @@ -11,7 +11,6 @@ import { Link, useNavigate } from "react-router-dom"; import useUserStore from "@stores/useUserStore"; import AccountCircleIcon from "@mui/icons-material/AccountCircle"; import apiClient from "@apis/apiClient"; -import ArrowBackIcon from "@mui/icons-material/ArrowBackIos"; interface PageHeaderProps { title: string; @@ -36,11 +35,11 @@ const PageHeader = ({ title, userName, action }: PageHeaderProps) => { try { const accessToken = sessionStorage.getItem("_ZA"); const deviceId = localStorage.getItem("deviceId"); - + if (!accessToken || !deviceId) { throw new Error("로그아웃 정보가 부족합니다."); } - + await apiClient.post( "/users/logout", {}, @@ -52,12 +51,11 @@ const PageHeader = ({ title, userName, action }: PageHeaderProps) => { withCredentials: true, } ); - + sessionStorage.removeItem("_ZA"); clearUser(); navigate("/sign-in"); } catch (error) { - console.error("로그아웃 실패", error); sessionStorage.removeItem("_ZA"); clearUser(); navigate("/sign-in"); @@ -83,16 +81,6 @@ const PageHeader = ({ title, userName, action }: PageHeaderProps) => { }} > - { - navigate(-1)} - sx={{ - color: "#222222", - }} - > - - - } { + toast[type](message, { + autoClose: 2000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + style: { + marginTop: "6px", + top: "40px", + }, + }); +}; + +export const ToastProvider = () => { + return ( + + ); +}; diff --git a/src/pages/ContractDetailPage/ContractDetailContent.tsx b/src/pages/ContractDetailPage/ContractDetailContent.tsx index 0370470..f1ea774 100644 --- a/src/pages/ContractDetailPage/ContractDetailContent.tsx +++ b/src/pages/ContractDetailPage/ContractDetailContent.tsx @@ -86,7 +86,7 @@ const ContractDetailContent = ({ contract, histories }: Props) => { <> {/* 계약 기본정보, 당사자 정보 */} - + 계약 기본 정보 @@ -143,8 +143,14 @@ const ContractDetailContent = ({ contract, histories }: Props) => { ); })()} /> - - + + {contract.status === "CANCELLED" && ( { - + 계약 당사자 정보 - + { {/* 첨부문서, 히스토리 */} - + 첨부 문서 @@ -245,12 +254,21 @@ const ContractDetailContent = ({ contract, histories }: Props) => { )) ) : ( - 첨부 문서 없음 + + 첨부 문서 없음 + )} - + 상태 변경 이력 @@ -268,10 +286,40 @@ const ContractDetailContent = ({ contract, histories }: Props) => { {histories.map((h, idx) => ( - {statusKoreanMap[h.prevStatus] ?? h.prevStatus} + item.value === h.prevStatus + )?.color, + borderColor: CONTRACT_STATUS_TYPES.find( + (item) => item.value === h.prevStatus + )?.color, + fontWeight: 500, + fontSize: 13, + height: 28, + }} + /> - {statusKoreanMap[h.currentStatus] ?? h.currentStatus} + item.value === h.currentStatus + )?.color, + borderColor: CONTRACT_STATUS_TYPES.find( + (item) => item.value === h.currentStatus + )?.color, + fontWeight: 500, + fontSize: 13, + height: 28, + }} + /> {h.changedAt} @@ -279,9 +327,16 @@ const ContractDetailContent = ({ contract, histories }: Props) => { ) : ( - - 히스토리 없음 - + + 히스토리 없음 + )} diff --git a/src/pages/ContractDetailPage/ContractDetailPage.tsx b/src/pages/ContractDetailPage/ContractDetailPage.tsx index 1b7f889..188667d 100644 --- a/src/pages/ContractDetailPage/ContractDetailPage.tsx +++ b/src/pages/ContractDetailPage/ContractDetailPage.tsx @@ -10,7 +10,8 @@ import { toast } from "react-toastify"; import DeleteConfirmModal from "@components/DeleteConfirm/DeleteConfirmModal"; import useUserStore from "@stores/useUserStore"; import EditIcon from "@mui/icons-material/Edit"; -import DeleteIcon from "@mui/icons-material/DeleteOutline"; +import DeleteIcon from "@mui/icons-material/Delete"; +import { showToast } from "@components/Toast/Toast"; const ContractDetailPage = () => { const { contractUid } = useParams<{ contractUid: string }>(); @@ -33,12 +34,18 @@ const ContractDetailPage = () => { apiClient .delete(`/contracts/${contractUid}`) .then(() => { - toast.success("계약 삭제 성공"); + showToast({ + message: "계약을 삭제했습니다.", + type: "success", + }); navigate("/contracts"); }) .catch((err) => { console.error("계약 삭제 실패", err); - toast.error("계약 삭제 중 오류가 발생했습니다."); + showToast({ + message: "계약 삭제 중 오류가 발생했습니다.", + type: "error", + }); }) .finally(() => { setDeleteModalOpen(false); @@ -77,9 +84,7 @@ const ContractDetailPage = () => { return (
-
- -
+
{editModalOpen && ( @@ -104,10 +109,11 @@ const ContractDetailPage = () => { open={deleteModalOpen} onConfirm={confirmDelete} onCancel={() => setDeleteModalOpen(false)} + category="계약" />
- +
@@ -523,12 +636,16 @@ function CounselDetailPage() {
주소 - {data.property.address} + + {data.property.address} +
매물 유형 - {PROPERTY_CATEGORIES[data.property.type as PropertyCategory] || data.property.type} + {PROPERTY_CATEGORIES[ + data.property.type as PropertyCategory + ] || data.property.type}
{data.property.price !== null && data.property.price !== 0 && ( @@ -539,22 +656,24 @@ function CounselDetailPage() {
)} - {data.property.deposit !== null && data.property.deposit !== 0 && ( -
- 보증금 - - {data.property.deposit.toLocaleString()}원 - -
- )} - {data.property.monthlyRent !== null && data.property.monthlyRent !== 0 && ( -
- 월세 - - {data.property.monthlyRent.toLocaleString()}원 - -
- )} + {data.property.deposit !== null && + data.property.deposit !== 0 && ( +
+ 보증금 + + {data.property.deposit.toLocaleString()}원 + +
+ )} + {data.property.monthlyRent !== null && + data.property.monthlyRent !== 0 && ( +
+ 월세 + + {data.property.monthlyRent.toLocaleString()}원 + +
+ )}
전용면적 @@ -569,11 +688,17 @@ function CounselDetailPage() {
층수 - {data.property.floor}층 + + {data.property.floor}층 +
준공년도 - {data.property.constructionYear}년 + + {data.property.constructionYear + ? `${data.property.constructionYear}년` + : "-"} +
특징 @@ -582,7 +707,9 @@ function CounselDetailPage() { 엘리베이터 )} {data.property.parkingCapacity > 0 && ( - 주차 {data.property.parkingCapacity}대 + + 주차 {data.property.parkingCapacity}대 + )} {data.property.hasPet && ( 반려동물 @@ -592,7 +719,9 @@ function CounselDetailPage() { {data.property.description && (
상세 설명 - {data.property.description} + + {data.property.description} +
)}
@@ -675,9 +804,7 @@ function CounselDetailPage() { 상담 삭제 - - 정말로 상담을 삭제하시겠습니까? - + 정말로 상담을 삭제하시겠습니까? diff --git a/src/pages/CounselDetailPage/styles/CounselDetailPage.module.css b/src/pages/CounselDetailPage/styles/CounselDetailPage.module.css index c61ba05..fa94396 100644 --- a/src/pages/CounselDetailPage/styles/CounselDetailPage.module.css +++ b/src/pages/CounselDetailPage/styles/CounselDetailPage.module.css @@ -38,10 +38,13 @@ } .cardTitle { - font-size: 18px; - font-weight: 600; - margin-bottom: 16px; - color: #333; + margin: 0; + font-size: 1.2rem !important; + font-weight: 700 !important; + padding-bottom: 8px !important; + line-height: 1.6; + letter-spacing: 0.0075em; + margin-bottom: 0.35em !important; } .infoGrid { @@ -179,4 +182,4 @@ border-radius: 4px; cursor: pointer; font-weight: 500; -} \ No newline at end of file +} diff --git a/src/pages/CounselListPage/CounselListPage.tsx b/src/pages/CounselListPage/CounselListPage.tsx index d7825c6..de202f0 100644 --- a/src/pages/CounselListPage/CounselListPage.tsx +++ b/src/pages/CounselListPage/CounselListPage.tsx @@ -12,6 +12,9 @@ import { CircularProgress, Button, } from "@mui/material"; +import { DatePicker } from "@mui/x-date-pickers/DatePicker"; +import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; +import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; import PageHeader from "@components/PageHeader/PageHeader"; import useUserStore from "@stores/useUserStore"; import { useEffect, useState } from "react"; @@ -59,7 +62,9 @@ function CounselListPage() { const [startDate, setStartDate] = useState(null); const [endDate, setEndDate] = useState(null); const [selectedType, setSelectedType] = useState(null); - const [selectedCompleted, setSelectedCompleted] = useState(null); + const [selectedCompleted, setSelectedCompleted] = useState( + null + ); const COUNSEL_TYPES = [ { value: "PURCHASE", label: "매수" }, @@ -159,19 +164,92 @@ function CounselListPage() {
- setStartDate(e.target.value)} - /> - ~ - setEndDate(e.target.value)} - /> + + + + 상담일 + + + { + setStartDate( + newValue ? newValue.format("YYYY-MM-DD") : null + ); + }} + format="YYYY/MM/DD" + slotProps={{ + textField: { + size: "small", + sx: { + backgroundColor: "white", + width: "170px", + "& .MuiOutlinedInput-root": { + "& fieldset": { + borderColor: "#E0E0E0", + borderRadius: "20px", + }, + "&:hover fieldset": { + borderColor: "#164F9E", + }, + "&.Mui-focused fieldset": { + borderColor: "#164F9E", + }, + }, + }, + }, + }} + /> + + ~ + + { + setEndDate( + newValue ? newValue.format("YYYY-MM-DD") : null + ); + }} + format="YYYY/MM/DD" + slotProps={{ + textField: { + size: "small", + sx: { + backgroundColor: "white", + width: "170px", + "& .MuiOutlinedInput-root": { + "& fieldset": { + borderColor: "#E0E0E0", + borderRadius: "20px", + }, + "&:hover fieldset": { + borderColor: "#164F9E", + }, + "&.Mui-focused fieldset": { + borderColor: "#164F9E", + }, + }, + }, + }, + }} + /> + + +
@@ -226,7 +304,8 @@ function CounselListPage() { onClick={() => setIsModalOpen(true)} sx={{ backgroundColor: "#164F9E", - "&:hover": { backgroundColor: "#0D3B7A" }, + boxShadow: "none", + "&:hover": { backgroundColor: "#0D3B7A", boxShadow: "none" }, height: "36px", fontSize: "13px", padding: "0 16px", @@ -291,15 +370,23 @@ function CounselListPage() { - {(!counsel.dueDate || counsel.completed) ? "의뢰 마감" : "의뢰 진행중"} + {!counsel.dueDate || counsel.completed + ? "의뢰 마감" + : "의뢰 진행중"} @@ -333,4 +420,4 @@ function CounselListPage() { ); } -export default CounselListPage; \ No newline at end of file +export default CounselListPage; diff --git a/src/pages/CustomerDetailPage/CustomerDetailPage.tsx b/src/pages/CustomerDetailPage/CustomerDetailPage.tsx index ef2ffda..994e904 100644 --- a/src/pages/CustomerDetailPage/CustomerDetailPage.tsx +++ b/src/pages/CustomerDetailPage/CustomerDetailPage.tsx @@ -24,6 +24,7 @@ import { toast } from "react-toastify"; import DeleteConfirmModal from "@components/DeleteConfirm/DeleteConfirmModal"; import RegionSelect from "@components/RegionSelect/RegionSelect"; import { formatPhoneNumber } from "@utils/numberUtil"; +import { showToast } from "@components/Toast/Toast"; interface CustomerData { uid: number; @@ -177,15 +178,24 @@ function CustomerDetailPage() { try { if (!editedCustomer.name) { - toast.error("이름을 입력해주세요."); + showToast({ + message: "이름을 입력해주세요.", + type: "error", + }); return; } if (!editedCustomer.phoneNo) { - toast.error("전화번호를 입력해주세요."); + showToast({ + message: "전화번호를 입력해주세요.", + type: "error", + }); return; } if (editedCustomer.birthDay && !/^\d{8}$/.test(editedCustomer.birthDay)) { - toast.error("생년월일을 올바르게 입력해주세요. ex)19910501"); + showToast({ + message: "생년월일을 올바르게 입력해주세요. ex)19910501", + type: "error", + }); return; } @@ -215,13 +225,20 @@ function CustomerDetailPage() { ); if (response.status === 200) { - toast.success("고객 정보를 수정했습니다."); + showToast({ + message: "고객 정보를 수정했습니다.", + type: "success", + }); setIsEditing(false); fetchCustomerData(); } } catch (error: any) { console.error("Failed to update customer:", error); - toast.error(error.response?.data?.message || "고객 정보 수정에 실패했습니다."); + showToast({ + message: + error.response?.data?.message || "고객 정보 수정에 실패했습니다.", + type: "error", + }); } }; @@ -237,7 +254,10 @@ function CustomerDetailPage() { try { const response = await apiClient.delete(`/customers/${customerId}`); if (response.status === 200) { - toast.success("고객이 삭제되었습니다."); + showToast({ + message: "고객을 삭제했습니다.", + type: "success", + }); navigate("/customers"); } } catch (error) { @@ -328,18 +348,16 @@ function CustomerDetailPage() { }} >