diff --git a/src/apis/passApi.ts b/src/apis/passApi.ts index 610ce9f..15dbe27 100644 --- a/src/apis/passApi.ts +++ b/src/apis/passApi.ts @@ -1,9 +1,14 @@ import axiosWithAuthorization from "../contexts/axiosWithAuthorization"; // 출입 내역 조회 -export const fetchEntryPassLog = async (page: number) => { +export const fetchEntryPassLog = async (page: number, keyword: string = "") => { try { - const res = await axiosWithAuthorization.get(`/pass-logs/enter?page=${page}`); + const res = await axiosWithAuthorization.get(`/pass-logs/enter?page=${page}`, { + params: { + page, + ...(keyword ? { keyword } : {}), + }, + }); console.log("출입 내역 조회:", res.data); return res.data.data; } catch (error) { @@ -13,9 +18,14 @@ export const fetchEntryPassLog = async (page: number) => { }; // 출입증 발급 내역 조회 -export const fetchIssuedPassLog = async (page: number) => { +export const fetchIssuedPassLog = async (page: number, keyword: string = "") => { try { - const res = await axiosWithAuthorization.get(`/pass-logs/issued?page=${page}`); + const res = await axiosWithAuthorization.get(`/pass-logs/issued?page=${page}`, { + params: { + page, + ...(keyword ? { keyword } : {}), + }, + }); console.log("출입증 발급 내역 조회:", res.data); return res.data.data; } catch (error) { diff --git a/src/apis/patientApi.ts b/src/apis/patientApi.ts index 2177a57..2340e2d 100644 --- a/src/apis/patientApi.ts +++ b/src/apis/patientApi.ts @@ -1,9 +1,14 @@ import axiosWithAuthorization from "../contexts/axiosWithAuthorization"; // 환자 목록 전체 조회 -export const fetchPatientList = async (page: number) => { +export const fetchPatientList = async (page: number, keyword: string = "") => { try { - const res = await axiosWithAuthorization.get(`/patients/paged?page=${page}`); + const res = await axiosWithAuthorization.get(`/patients/paged?page=${page}`, { + params: { + page, + ...(keyword ? { keyword } : {}), + }, + }); console.log("환자 목록 전체 조회:", res.data); return res.data.data; } catch (error) { diff --git a/src/components/searchbar/SearchBar.tsx b/src/components/searchbar/SearchBar.tsx index ac5d75b..0504a96 100644 --- a/src/components/searchbar/SearchBar.tsx +++ b/src/components/searchbar/SearchBar.tsx @@ -1,56 +1,35 @@ import { useState } from 'react'; import './css/SearchBar.css'; -interface SearchBarProps { - type: '구역관리' | '사원정보' | '보안그룹'; +interface SearchBarProps { + placeholder?: string; + onSearch?: (input: string) => void; } -const SearchBar = ({ type }: SearchBarProps) => { - const dropdownOptions = { - '구역관리': ['전체', 'A동', 'B동'], - '사원정보': ['사원이름', '이메일', '직책', '부서명'], - '보안그룹': ['보안그룹명', '구역 ID', '사원 ID'], - }; - - const placeholder = { - '구역관리': '검색어를 입력하세요', - '사원정보': '검색어를 입력하세요', - '보안그룹': '검색어를 입력하세요', - }; - - const options = dropdownOptions[type]; - const [selected, setSelected] = useState(options[0]); +const SearchBar = ({ placeholder, onSearch }: SearchBarProps) => { const [inputValue, setInputValue] = useState(''); - const [dropdownOpen, setDropdownOpen] = useState(false); const handleSearch = () => { - console.log(`검색: [${selected}] ${inputValue}`); + console.log(`검색: ${inputValue}`); + if (onSearch) onSearch(inputValue); + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + handleSearch(); + } }; return (
-
- - {dropdownOpen && ( -
    - {options.map((opt) => ( -
  • { setSelected(opt); setDropdownOpen(false); }}> - {opt} -
  • - ))} -
- )} -
setInputValue(e.target.value)} + onKeyDown={handleKeyDown} /> - diff --git a/src/components/searchbar/css/SearchBar.css b/src/components/searchbar/css/SearchBar.css index 73237b5..0655d17 100644 --- a/src/components/searchbar/css/SearchBar.css +++ b/src/components/searchbar/css/SearchBar.css @@ -1,131 +1,91 @@ -.search-bar { - display: flex; - align-items: center; - gap: 8px; - width: 600px; - background-color: white; - box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08); - padding: 0px; - border-radius: 4px; - -} - -.search-bar-dropdown { - position: relative; - width: 110px; - max-width: 160px; -} - -.search-bar-dropdown-toggle { - width: 100%; - padding: 8px; - border: none; - background-color: white; - border-right: 1px solid #ccc; - cursor: pointer; - display: flex; - justify-content: space-between; - align-items: center; - font-size: 14px; - border-radius: 4px 0 0 4px; -} - -.search-bar-arrow { - font-size: 10px; - margin-left: 4px; - border-radius: 0; - color: #1f441f; -} - -.search-bar-dropdown-menu { - list-style: none; - margin: 0; - padding: 0; - position: absolute; - top: 36px; - left: 0; - width: 100%; - background-color: white; - border: 1px solid #ccc; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); - z-index: 10; - overflow: hidden; -} - -.search-bar-dropdown-menu li { - padding: 8px; - font-size: 14px; - cursor: pointer; - white-space: nowrap; - text-align: left; -} - -.search-bar-dropdown-menu li:hover { - background-color: #e5eee7; -} - -.search-bar-input { - flex-grow: 1; - border: none; - outline: none; - padding: 8px; - font-size: 14px; - background-color: white; -} - -.search-bar-button { - background-color: #1f441f; - color: white; - padding: 8px 16px; - border: none; - cursor: pointer; - font-size: 14px; - border-radius: 4px; -} - -.search-bar-button:hover { - background-color: #24562B; -} - -body.dark-mode .search-bar { - background-color: #1f1f1f; - box-shadow: 0 2px 6px rgba(255, 255, 255, 0.08); -} - -body.dark-mode .search-bar-dropdown-toggle { - background-color: #2b2b2b; - border-right: 1px solid #555; - color: #f0f0f0; -} - -body.dark-mode .search-bar-dropdown-menu { - background-color: #2b2b2b; - border: 1px solid #555; - box-shadow: 0 2px 4px rgba(255, 255, 255, 0.1); -} - -body.dark-mode .search-bar-dropdown-menu li { - color: #f0f0f0; -} - -body.dark-mode .search-bar-dropdown-menu li:hover { - background-color: #3a4a3a; -} - -body.dark-mode .search-bar-input { - background-color: #2b2b2b; - color: #f0f0f0; -} - -body.dark-mode .search-bar-button { - background-color: #3a7541; - color: #fff; -} - -body.dark-mode .search-bar-button:hover { - background-color: #2e5d35; -} - -body.dark-mode .search-bar-arrow { - color: #b5d6b5; -} + .search-bar { + display: flex; + align-items: center; + gap: 8px; + width: 600px; + background-color: white; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); + padding: 0px; + border-radius: 4px; + } + + .search-bar-dropdown { + position: relative; + width: 110px; + max-width: 160px; + } + + .search-bar-dropdown-toggle { + width: 100%; + padding: 8px; + border: none; + background-color: white; + border-right: 1px solid #ccc; + cursor: pointer; + display: flex; + justify-content: space-between; + align-items: center; + font-size: 14px; + border-radius: 4px 0 0 4px; + } + + .search-bar-arrow { + font-size: 10px; + margin-left: 4px; + border-radius: 0; + color: #1f441f; + } + + .search-bar-dropdown-menu { + list-style: none; + margin: 0; + padding: 0; + position: absolute; + top: 36px; + left: 0; + width: 100%; + background-color: white; + border: 1px solid #ccc; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + z-index: 10; + overflow: hidden; + } + + .search-bar-dropdown-menu li { + padding: 8px; + font-size: 14px; + cursor: pointer; + white-space: nowrap; + text-align: left; + } + + .search-bar-dropdown-menu li:hover { + background-color: #e5eee7; + } + + .search-bar-input { + flex-grow: 1; + border: none; + outline: none; + padding: 8px; + font-size: 14px; + background-color: white; + + border-top-left-radius: 8px; + border-bottom-left-radius: 8px; + } + + .search-bar-button { + background-color: #1f441f; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); + color: white; + padding: 8px 16px; + border: none; + cursor: pointer; + font-size: 14px; + border-radius: 4px; + } + + .search-bar-button:hover { + background-color: #24562B; + } \ No newline at end of file diff --git a/src/pages/EntryHistoryPage.tsx b/src/pages/EntryHistoryPage.tsx index 4d57600..a5aa66b 100644 --- a/src/pages/EntryHistoryPage.tsx +++ b/src/pages/EntryHistoryPage.tsx @@ -1,9 +1,10 @@ -import { useEffect, useState } from "react"; +import { useEffect, useState, useRef } from "react"; import { usePassLogContext } from "../contexts/PassLogContext.tsx"; import Layout from '../components/layout/Layout.tsx'; import Background from '../components/background/Background.tsx'; import Breadcrumb from '../components/breadcrumb/Breadcrumb.tsx'; +import SearchBar from "../components/searchbar/SearchBar.tsx"; import DefaultTable from '../components/table/DefaultTable.tsx'; import Pagination from '../components/table/Pagination.tsx'; import Loading from "../components/loading/Loading.tsx"; @@ -32,15 +33,16 @@ const EntryHistoryPage = () => { const [entryHistory, setEntryHistory] = useState([]); const [totalPages, setTotalPages] = useState(1); const [currentPage, setCurrentPage] = useState(1); + const [searchKeyword, setSearchKeyword] = useState(''); const [isLoading, setIsLoading] = useState(false); + const prevSearchKeywordRef = useRef(''); const { isPassLogAvailable } = usePassLogContext(); - useEffect(() => { - const loadData = async () => { + const loadData = async (page: number, keyword: string) => { try { setIsLoading(true); - const data = await fetchEntryPassLog(currentPage - 1); + const data = await fetchEntryPassLog(page - 1, keyword); const transformed = data.content.map((item: any) => ({ ...item, createdDt: item.createdDt?.replace('T', ' ').split('.')[0], @@ -51,11 +53,27 @@ const EntryHistoryPage = () => { console.error("출입 내역 불러오기 실패:", err); } finally { setIsLoading(false); + } + }; + + const handleSearch = async (input: string) => { + const trimmed = input.trim(); + + if (trimmed !== prevSearchKeywordRef.current || currentPage !== 1) { + prevSearchKeywordRef.current = trimmed; + setSearchKeyword(trimmed); + + if (currentPage === 1) { + await loadData(1, trimmed); + } else { + setCurrentPage(1); } - }; + } + }; - loadData(); - }, [currentPage]); + useEffect(() => { + loadData(currentPage, searchKeyword); + }, [currentPage, searchKeyword]); if (!isPassLogAvailable) { return ( @@ -85,9 +103,14 @@ const EntryHistoryPage = () => { ) : ( <>
출입 내역 조회
+ +
{ const [issueHistory, setIssueHistory] = useState([]); const [totalPages, setTotalPages] = useState(1); const [currentPage, setCurrentPage] = useState(1); + const [searchKeyword, setSearchKeyword] = useState(''); const [isLoading, setIsLoading] = useState(false); + const prevSearchKeywordRef = useRef(''); const { isPassLogAvailable } = usePassLogContext(); - const navigate = useNavigate(); + const loadData = async (page: number, keyword: string) => { + try { + setIsLoading(true); + const data = await fetchIssuedPassLog(page - 1, keyword); + const transformed = data.content.map((item: any) => ({ + ...item, + startAt: item.startAt?.replace('T', ' ').split('.')[0], + expiredAt: item.expiredAt?.replace('T', ' ').split('.')[0], + visitCategory : item.visitCategory === "PATIENT" ? "환자" : item.visitCategory === "GUARDIAN" ? "보호자" : "-", + })); + setIssueHistory(transformed); + setTotalPages(data.totalPages); + } catch (err) { + console.error("출입 내역 불러오기 실패:", err); + } finally { + setIsLoading(false); + } + }; + + const handleSearch = async (input: string) => { + const trimmed = input.trim(); + + if (trimmed !== prevSearchKeywordRef.current || currentPage !== 1) { + prevSearchKeywordRef.current = trimmed; + setSearchKeyword(trimmed); + + if (currentPage === 1) { + await loadData(1, trimmed); + } else { + setCurrentPage(1); + } + } + }; + useEffect(() => { - const loadData = async () => { - try { - setIsLoading(true); - const data = await fetchIssuedPassLog(currentPage - 1); - const transformed = data.content.map((item: any) => ({ - ...item, - startAt: item.startAt?.replace('T', ' ').split('.')[0], - expiredAt: item.expiredAt?.replace('T', ' ').split('.')[0], - visitCategory : item.visitCategory === "PATIENT" ? "환자" : item.visitCategory === "GUARDIAN" ? "보호자" : "-", - })); - setIssueHistory(transformed); - setTotalPages(data.totalPages); - } catch (err) { - console.error("출입 내역 불러오기 실패:", err); - } finally { - setIsLoading(false); - } - }; - - loadData(); - }, [currentPage]); + loadData(currentPage, searchKeyword); + }, [currentPage, searchKeyword]); if (!isPassLogAvailable) { return ( @@ -90,6 +107,11 @@ const IssueHistoryPage = () => { ) : ( <>
출입증 발급 내역 조회
+ +
{ const [patientList, setPatientList] = useState([]); const [totalPages, setTotalPages] = useState(1); const [currentPage, setCurrentPage] = useState(1); + const [searchKeyword, setSearchKeyword] = useState(''); const [isLoading, setIsLoading] = useState(false); + const prevSearchKeywordRef = useRef(''); const navigate = useNavigate(); + const loadData = async (page: number, keyword: string) => { + try { + setIsLoading(true); + const data = await fetchPatientList(page - 1, keyword); + const transformed = data.content.map((item: any) => ({ + ...item, + sex: item.sex === "MALE" ? "남성" : item.sex === "FEMALE" ? "여성" : "-" + })); + setPatientList(transformed); + setTotalPages(data.totalPages); + } catch (err) { + console.error("출입 내역 불러오기 실패:", err); + } finally { + setIsLoading(false); + } + }; + + const handleSearch = async (input: string) => { + const trimmed = input.trim(); + + if (trimmed !== prevSearchKeywordRef.current || currentPage !== 1) { + prevSearchKeywordRef.current = trimmed; + setSearchKeyword(trimmed); + + if (currentPage === 1) { + await loadData(1, trimmed); + } else { + setCurrentPage(1); + } + } + }; + useEffect(() => { - const loadData = async () => { - try { - setIsLoading(true); - const data = await fetchPatientList(currentPage - 1); - const transformed = data.content.map((item: any) => ({ - ...item, - sex: item.sex === "MALE" ? "남성" : item.sex === "FEMALE" ? "여성" : "-" - })); - setPatientList(transformed); - setTotalPages(data.totalPages); - } catch (err) { - console.error("출입 내역 불러오기 실패:", err); - } finally { - setIsLoading(false); - } - }; - - loadData(); - }, [currentPage]); + loadData(currentPage, searchKeyword); + }, [currentPage, searchKeyword]); return ( <> @@ -72,6 +90,11 @@ const PatientListPage = () => { ) : ( <>
환자 전체 목록 조회
+ +