컴포넌트 관련 변경
이미지 업로드 한글명칭 불가 처리 유저 조회 부분 수정
This commit is contained in:
@@ -12,7 +12,7 @@ export const UserView = async (token, searchType, searchKey) => {
|
||||
{ headers: { Authorization: `Bearer ${token}` } },
|
||||
);
|
||||
|
||||
return res.data.data.result;
|
||||
return res.data;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new Error('UserView Error', e);
|
||||
|
||||
@@ -93,12 +93,6 @@ const UserInfoTable = styled.table`
|
||||
font-size: 13px;
|
||||
border-radius: 15px;
|
||||
overflow: hidden;
|
||||
tr:first-child {
|
||||
th,
|
||||
td {
|
||||
border-top: 0;
|
||||
}
|
||||
}
|
||||
th,
|
||||
td {
|
||||
height: 36px;
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useState, useEffect } from 'react';
|
||||
import { UserTattooView } from '../../apis/Users';
|
||||
import { TableSkeleton } from '../Skeleton/TableSkeleton';
|
||||
|
||||
const UserTatttooInfo = ({ userInfo }) => {
|
||||
const UserTattooInfo = ({ userInfo }) => {
|
||||
const [dataList, setDataList] = useState();
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
@@ -62,7 +62,7 @@ const UserTatttooInfo = ({ userInfo }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default UserTatttooInfo;
|
||||
export default UserTattooInfo;
|
||||
|
||||
const UserDefaultTable = styled.table`
|
||||
border: 1px solid #e8eaec;
|
||||
|
||||
@@ -32,6 +32,18 @@ const ImageUploadBtn = ({ disabled,
|
||||
|
||||
if (!file) return;
|
||||
|
||||
const koreanRegex = /[ㄱ-ㅎ|ㅏ-ㅣ|가-힣]/;
|
||||
if (koreanRegex.test(file.name)) {
|
||||
showToast('FILE_KOREAN_NAME_WARNING', {
|
||||
type: alertTypes.warning
|
||||
});
|
||||
if (document.querySelector('#fileinput')) {
|
||||
document.querySelector('#fileinput').value = '';
|
||||
}
|
||||
onFileDelete();
|
||||
return;
|
||||
}
|
||||
|
||||
// 이미지 파일 확장자 체크
|
||||
const fileExt = file.name.split('.').pop().toLowerCase();
|
||||
if (fileExt !== 'png' && fileExt !== 'jpg' && fileExt !== 'jpeg') {
|
||||
|
||||
@@ -1,79 +1,29 @@
|
||||
import React from 'react';
|
||||
import DatePickerComponent from './DatePickerComponent';
|
||||
import { DatePickerWrapper } from '../../../styles/Components';
|
||||
import {
|
||||
FormRowGroup,
|
||||
FormLabel,
|
||||
DateContainer,
|
||||
DateTimeWrapper,
|
||||
DateTimeGroup,
|
||||
} from '../../../styles/ModuleComponents';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { DatePicker } from 'antd';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const { RangePicker } = DatePicker;
|
||||
|
||||
const DateRangePicker = ({
|
||||
label,
|
||||
startDate,
|
||||
endDate,
|
||||
onStartDateChange,
|
||||
onEndDateChange,
|
||||
pastDate = new Date(),
|
||||
disabled,
|
||||
startLabel = '시작 일자',
|
||||
endLabel = '종료 일자',
|
||||
setAlert,
|
||||
value,
|
||||
onChange,
|
||||
format,
|
||||
showTime = true,
|
||||
size = 'middle',
|
||||
...props
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleStartDate = (date) => {
|
||||
const newDate = new Date(date);
|
||||
onStartDateChange(newDate);
|
||||
};
|
||||
|
||||
const handleEndDate = (date) => {
|
||||
let newDate = new Date(date);
|
||||
|
||||
if (startDate && newDate < startDate) {
|
||||
setAlert(t('DATE_START_DIFF_END'));
|
||||
newDate = new Date(startDate);
|
||||
}
|
||||
|
||||
onEndDateChange(newDate);
|
||||
};
|
||||
|
||||
return (
|
||||
<FormRowGroup>
|
||||
<FormLabel>{label}</FormLabel>
|
||||
<DateTimeWrapper>
|
||||
<DateTimeGroup>
|
||||
<DateContainer>
|
||||
<DatePickerWrapper>
|
||||
<DatePickerComponent
|
||||
name={startLabel}
|
||||
handleSelectedDate={handleStartDate}
|
||||
selectedDate={startDate}
|
||||
pastDate={pastDate}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</DatePickerWrapper>
|
||||
</DateContainer>
|
||||
</DateTimeGroup>
|
||||
|
||||
<DateTimeGroup>
|
||||
<DateContainer>
|
||||
<DatePickerWrapper>
|
||||
<DatePickerComponent
|
||||
name={endLabel}
|
||||
handleSelectedDate={handleEndDate}
|
||||
selectedDate={endDate}
|
||||
pastDate={pastDate}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</DatePickerWrapper>
|
||||
</DateContainer>
|
||||
</DateTimeGroup>
|
||||
</DateTimeWrapper>
|
||||
</FormRowGroup>
|
||||
<RangePicker
|
||||
showTime={showTime}
|
||||
value={value ? [dayjs(value[0]), dayjs(value[1])] : [null, null]}
|
||||
format={format || 'YYYY-MM-DD HH:mm:ss'}
|
||||
onChange={onChange}
|
||||
placeholder={['시작 일시', '종료 일시']}
|
||||
size={size}
|
||||
allowClear={false}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default DateRangePicker;
|
||||
205
src/components/common/Header/Navi_bak.js
Normal file
205
src/components/common/Header/Navi_bak.js
Normal file
@@ -0,0 +1,205 @@
|
||||
import { NavLink, useNavigate } from 'react-router-dom';
|
||||
import arrowIcon from '../../../assets/img/icon/icon-tab.png';
|
||||
import styled from 'styled-components';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { authList } from '../../../store/authList';
|
||||
import Modal from '../modal/Modal';
|
||||
import { BtnWrapper, ButtonClose, ModalText } from '../../../styles/Components';
|
||||
import { useEffect, useState } from 'react';
|
||||
import Button from '../button/Button';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { AuthInfo } from '../../../apis';
|
||||
import { getMenuConfig } from '../../../utils';
|
||||
import { adminAuthLevel } from '../../../assets/data/types';
|
||||
|
||||
const Navi = () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
const userInfo = useRecoilValue(authList);
|
||||
const menu = getMenuConfig(userInfo);
|
||||
|
||||
const [modalClose, setModalClose] = useState('hidden');
|
||||
const [logoutModalClose, setLogoutModalClose] = useState('hidden');
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleToken = async () => {
|
||||
const tokenStatus = await AuthInfo(token);
|
||||
|
||||
tokenStatus.message === '잘못된 타입의 토큰입니다.' && setLogoutModalClose('view');
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
handleToken();
|
||||
}, [token]);
|
||||
|
||||
const handleTopMenu = e => {
|
||||
e.preventDefault();
|
||||
e.target.classList.toggle('active');
|
||||
};
|
||||
|
||||
const handleLink = e => {
|
||||
let topActive = document.querySelectorAll('nav .active');
|
||||
let currentTopMenu = e.target.closest('ul').previousSibling;
|
||||
for (let i = 0; i < topActive.length; i++) {
|
||||
if (topActive[i] !== currentTopMenu) {
|
||||
topActive[i].classList.remove('active');
|
||||
}
|
||||
}
|
||||
|
||||
handleToken();
|
||||
};
|
||||
|
||||
// 등록 완료 모달
|
||||
const handleModalClose = () => {
|
||||
if (modalClose === 'hidden') {
|
||||
setModalClose('view');
|
||||
} else {
|
||||
setModalClose('hidden');
|
||||
}
|
||||
};
|
||||
|
||||
// 로그아웃 안내 모달
|
||||
const handleConfirmClose = () => {
|
||||
if (logoutModalClose === 'hidden') {
|
||||
setLogoutModalClose('view');
|
||||
} else {
|
||||
setLogoutModalClose('hidden');
|
||||
sessionStorage.removeItem('token');
|
||||
|
||||
navigate('/');
|
||||
}
|
||||
};
|
||||
|
||||
const isClickable = (submenu) => {
|
||||
switch (userInfo.auth_level_type) {
|
||||
case adminAuthLevel.DEVELOPER:
|
||||
case adminAuthLevel.READER:
|
||||
case adminAuthLevel.MASTER:
|
||||
return true;
|
||||
default:
|
||||
return submenu.authLevel === adminAuthLevel.NONE && userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === submenu.id);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<nav>
|
||||
<ul>
|
||||
{menu.map((item, idx) => {
|
||||
return (
|
||||
<li key={idx}>
|
||||
{item.access && (
|
||||
<TopMenu to={item.link} onClick={handleTopMenu}>
|
||||
{item.title}
|
||||
</TopMenu>
|
||||
)}
|
||||
<SubMenu>
|
||||
{item.submenu && userInfo &&
|
||||
item.submenu.map((submenu, idx) => {
|
||||
return (
|
||||
<SubMenuItem key={idx} $isclickable={isClickable(submenu) ? 'true' : 'false'}>
|
||||
<NavLink
|
||||
to={isClickable(submenu) ? submenu.link : location.pathname}
|
||||
onClick={e => {
|
||||
isClickable(submenu) ? handleLink(e) : handleModalClose();
|
||||
}}>
|
||||
{submenu.title}
|
||||
</NavLink>
|
||||
</SubMenuItem>
|
||||
);
|
||||
})}
|
||||
</SubMenu>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</nav>
|
||||
{/* 접근 불가 모달 */}
|
||||
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={modalClose}>
|
||||
<BtnWrapper $justify="flex-end">
|
||||
<ButtonClose onClick={handleModalClose} />
|
||||
</BtnWrapper>
|
||||
<ModalText $align="center">
|
||||
해당 메뉴에 대한 조회 권한이 없습니다.
|
||||
<br />
|
||||
권한 등급을 변경 후 다시 이용해주세요.
|
||||
</ModalText>
|
||||
<BtnWrapper $gap="10px">
|
||||
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleModalClose} />
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
|
||||
{/* 로그아웃 안내 모달 */}
|
||||
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={logoutModalClose}>
|
||||
<BtnWrapper $justify="flex-end">
|
||||
<ButtonClose onClick={handleConfirmClose} />
|
||||
</BtnWrapper>
|
||||
<ModalText $align="center">로그아웃 되었습니다.</ModalText>
|
||||
<BtnWrapper $gap="10px">
|
||||
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleConfirmClose} />
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Navi;
|
||||
|
||||
const TopMenu = styled(NavLink)`
|
||||
padding: 16px 30px;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #888;
|
||||
position: relative;
|
||||
color: #fff;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
position: absolute;
|
||||
right: 30px;
|
||||
top: 50%;
|
||||
transform: translate(0, -50%);
|
||||
background: url('${arrowIcon}') -12px 0 no-repeat;
|
||||
}
|
||||
&:hover,
|
||||
&.active {
|
||||
background: #444;
|
||||
}
|
||||
&.active ~ ul {
|
||||
display: block;
|
||||
}
|
||||
&.active:before {
|
||||
background: url('${arrowIcon}') 0 0 no-repeat;
|
||||
}
|
||||
`;
|
||||
|
||||
const SubMenu = styled.ul`
|
||||
display: none;
|
||||
`;
|
||||
|
||||
const SubMenuItem = styled.li`
|
||||
background: #eee;
|
||||
border-bottom: 1px solid #ccc;
|
||||
color: #2c2c2c;
|
||||
a {
|
||||
width: 100%;
|
||||
padding: 16px 30px;
|
||||
color: ${props => (props.$isclickable === 'false' ? '#818181' : '#2c2c2c')};
|
||||
text-align: left;
|
||||
&:hover,
|
||||
&.active {
|
||||
color: ${props => (props.$isclickable === 'false' ? '#818181' : '#2c2c2c')};
|
||||
font-weight: ${props => (props.$isclickable === 'false' ? 400 : 600)};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const BackGround = styled.div`
|
||||
background: #eee2;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 100;
|
||||
`;
|
||||
@@ -26,7 +26,7 @@ const SearchBarLayout = ({ firstColumnData, secondColumnData, filter, direction,
|
||||
</SearchRow>
|
||||
)}
|
||||
{isSearch &&
|
||||
<SearchRow>
|
||||
<SearchRow direction={direction}>
|
||||
<BtnWrapper $gap="8px">
|
||||
<Button theme="search" text="검색" handleClick={handleSubmit} type="button" />
|
||||
<Button theme="reset" handleClick={onReset} type="button" />
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const DotsButton = styled.button`
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
background-color: #f0f0f0;
|
||||
border: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
/* 점 스타일링 */
|
||||
.dot {
|
||||
width: 3px;
|
||||
height: 3px;
|
||||
border-radius: 50%;
|
||||
background-color: #333;
|
||||
margin: 2px 0;
|
||||
}
|
||||
`;
|
||||
|
||||
const VerticalDotsButton = ({ text, type = 'button', errorMessage, handleClick, size, width, height, borderColor, disabled, name }) => {
|
||||
|
||||
return (
|
||||
<DotsButton
|
||||
onSubmit={e => e.preventDefault()}
|
||||
type={type}
|
||||
disabled={disabled}
|
||||
onClick={handleClick}
|
||||
size={size}
|
||||
bordercolor={borderColor}
|
||||
width={width}
|
||||
height={height}
|
||||
name={name}
|
||||
>
|
||||
<div className="dot"></div>
|
||||
<div className="dot"></div>
|
||||
<div className="dot"></div>
|
||||
</DotsButton>
|
||||
);
|
||||
};
|
||||
|
||||
export default VerticalDotsButton;
|
||||
@@ -1,231 +0,0 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const AIMessageInput = ({ onSendMessage }) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [message, setMessage] = useState('');
|
||||
const [isSending, setIsSending] = useState(false);
|
||||
const textareaRef = useRef(null);
|
||||
const modalRef = useRef(null);
|
||||
|
||||
// 텍스트 영역 높이 자동 조절
|
||||
useEffect(() => {
|
||||
if (textareaRef.current && isOpen) {
|
||||
textareaRef.current.style.height = 'auto';
|
||||
textareaRef.current.style.height = `${Math.min(textareaRef.current.scrollHeight, 200)}px`;
|
||||
}
|
||||
}, [message, isOpen]);
|
||||
|
||||
// 모달 외부 클릭시 닫기
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event) => {
|
||||
if (modalRef.current && !modalRef.current.contains(event.target)) {
|
||||
closeModal();
|
||||
}
|
||||
};
|
||||
|
||||
if (isOpen) {
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, [isOpen]);
|
||||
|
||||
// 모달 열기
|
||||
const openModal = () => {
|
||||
setIsOpen(true);
|
||||
// 모달이 열린 후 텍스트 영역에 포커스
|
||||
setTimeout(() => {
|
||||
if (textareaRef.current) {
|
||||
textareaRef.current.focus();
|
||||
}
|
||||
}, 100);
|
||||
};
|
||||
|
||||
// 모달 닫기
|
||||
const closeModal = () => {
|
||||
setIsOpen(false);
|
||||
setMessage('');
|
||||
};
|
||||
|
||||
// 메시지 전송 처리
|
||||
const handleSendMessage = () => {
|
||||
if (message.trim() && !isSending) {
|
||||
setIsSending(true);
|
||||
|
||||
// 메시지 전송 처리
|
||||
if (onSendMessage) {
|
||||
onSendMessage(message);
|
||||
}
|
||||
|
||||
// 입력 초기화 및 상태 업데이트
|
||||
setMessage('');
|
||||
setIsSending(false);
|
||||
|
||||
// 모달 닫기
|
||||
closeModal();
|
||||
}
|
||||
};
|
||||
|
||||
// 엔터 키 처리 (Shift+Enter 줄바꿈, Enter 전송)
|
||||
const handleKeyDown = (e) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
handleSendMessage();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* 메뉴 버튼 */}
|
||||
<MenuButton onClick={openModal}>
|
||||
<div className="dot"></div>
|
||||
<div className="dot"></div>
|
||||
<div className="dot"></div>
|
||||
</MenuButton>
|
||||
|
||||
{/* 모달 오버레이 */}
|
||||
<ModalOverlay isOpen={isOpen}>
|
||||
<InputContainer ref={modalRef} isOpen={isOpen}>
|
||||
<MessageInput
|
||||
ref={textareaRef}
|
||||
value={message}
|
||||
onChange={(e) => setMessage(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="메시지를 입력하세요..."
|
||||
rows={1}
|
||||
/>
|
||||
|
||||
<SendButton
|
||||
onClick={handleSendMessage}
|
||||
disabled={!message.trim() || isSending}
|
||||
>
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" />
|
||||
</svg>
|
||||
</SendButton>
|
||||
</InputContainer>
|
||||
</ModalOverlay>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AIMessageInput;
|
||||
|
||||
const ModalOverlay = styled.div`
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: ${props => props.isOpen ? 'flex' : 'none'};
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
`;
|
||||
|
||||
// 메인 컨테이너
|
||||
const InputContainer = styled.div`
|
||||
width: 90%;
|
||||
max-width: 600px;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 12px;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
animation: ${props => props.isOpen ? 'slideUp 0.3s ease-out' : 'none'};
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// 메시지 입력 영역
|
||||
const MessageInput = styled.textarea`
|
||||
width: 100%;
|
||||
min-height: 60px;
|
||||
max-height: 200px;
|
||||
padding: 16px 60px 16px 16px;
|
||||
border: none;
|
||||
outline: none;
|
||||
resize: none;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
background: transparent;
|
||||
|
||||
&::placeholder {
|
||||
color: #9e9ea7;
|
||||
}
|
||||
`;
|
||||
|
||||
// 전송 버튼
|
||||
const SendButton = styled.button`
|
||||
position: absolute;
|
||||
bottom: 12px;
|
||||
right: 12px;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border-radius: 50%;
|
||||
background-color: #5436DA;
|
||||
color: white;
|
||||
border: none;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: #4527D0;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: #DADCE0;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
fill: white;
|
||||
}
|
||||
`;
|
||||
|
||||
// 메뉴 버튼 (세로 점 세개)
|
||||
const MenuButton = styled.button`
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background-color: #f0f0f0;
|
||||
border: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
border-radius: 50%;
|
||||
background-color: #666;
|
||||
margin: 2px 0;
|
||||
}
|
||||
`;
|
||||
@@ -1,57 +1,124 @@
|
||||
import { styled } from 'styled-components';
|
||||
import Button from '../common/button/Button';
|
||||
import { TextInput, SelectInput, InputGroup } from '../../styles/Components';
|
||||
import { SearchBarLayout } from '../common/SearchBar';
|
||||
import { useCallback, useState } from 'react';
|
||||
import {
|
||||
userSearchType2,
|
||||
} from '../../assets/data/options';
|
||||
import { useAlert } from '../../context/AlertProvider';
|
||||
import { alertTypes } from '../../assets/data/types';
|
||||
import { UserView } from '../../apis';
|
||||
|
||||
import { FormWrapper, InputLabel, TextInput, SelectInput, BtnWrapper } from '../../styles/Components';
|
||||
export const useUserSearch = (token) => {
|
||||
const {showToast} = useAlert();
|
||||
|
||||
const UserSearchBar = () => {
|
||||
return (
|
||||
<>
|
||||
<FormWrapper>
|
||||
<SearchbarStyle>
|
||||
<SearchItem>
|
||||
<InputLabel>유저 조회</InputLabel>
|
||||
<TextInput type="text" width="300px" placeholder="조회 대상 유저의 GUID를 입력하세요." />
|
||||
</SearchItem>
|
||||
<BtnWrapper $gap="8px">
|
||||
<Button theme="reset" />
|
||||
<Button
|
||||
theme="primary"
|
||||
text="검색"
|
||||
handleClick={e => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
/>
|
||||
</BtnWrapper>
|
||||
</SearchbarStyle>
|
||||
</FormWrapper>
|
||||
</>
|
||||
);
|
||||
const [searchParams, setSearchParams] = useState({
|
||||
search_type: 'GUID',
|
||||
search_data: ''
|
||||
});
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState(null);
|
||||
|
||||
const fetchData = useCallback(async (params) => {
|
||||
if (!token) return;
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
const result = await UserView(
|
||||
token,
|
||||
params.search_type,
|
||||
params.search_data
|
||||
);
|
||||
if(result.result === "NOT_USER"){
|
||||
showToast(result.result, {type: alertTypes.warning});
|
||||
return;
|
||||
}
|
||||
setData(result.data.result);
|
||||
return result.data.result;
|
||||
} catch (error) {
|
||||
showToast('error', {type: alertTypes.error});
|
||||
throw error;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [token]);
|
||||
|
||||
const updateSearchParams = useCallback((newParams) => {
|
||||
setSearchParams(prev => ({
|
||||
...prev,
|
||||
...newParams
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const handleSearch = useCallback(async (newParams = {}, executeSearch = true) => {
|
||||
const updatedParams = {
|
||||
...searchParams,
|
||||
...newParams
|
||||
};
|
||||
updateSearchParams(updatedParams);
|
||||
|
||||
if (executeSearch) {
|
||||
return await fetchData(updatedParams);
|
||||
}
|
||||
return null;
|
||||
}, [searchParams, fetchData]);
|
||||
|
||||
const handleReset = useCallback(async () => {
|
||||
const now = new Date();
|
||||
now.setDate(now.getDate() - 1);
|
||||
const resetParams = {
|
||||
search_type: 'GUID',
|
||||
search_data: ''
|
||||
};
|
||||
setSearchParams(resetParams);
|
||||
return await fetchData(resetParams);
|
||||
}, [fetchData]);
|
||||
|
||||
return {
|
||||
searchParams,
|
||||
loading,
|
||||
data,
|
||||
handleSearch,
|
||||
handleReset,
|
||||
updateSearchParams
|
||||
};
|
||||
};
|
||||
|
||||
export default UserSearchBar;
|
||||
const UserSearchBar = ({ searchParams, onSearch, onReset }) => {
|
||||
const handleSubmit = event => {
|
||||
event.preventDefault();
|
||||
|
||||
const SearchbarStyle = styled.div`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px 0;
|
||||
font-size: 14px;
|
||||
padding: 20px;
|
||||
border-top: 1px solid #000;
|
||||
border-bottom: 1px solid #000;
|
||||
margin-bottom: 40px;
|
||||
`;
|
||||
const SearchItem = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
margin-right: 50px;
|
||||
onSearch(searchParams, true);
|
||||
};
|
||||
|
||||
${TextInput}, ${SelectInput} {
|
||||
height: 35px;
|
||||
}
|
||||
${TextInput} {
|
||||
padding: 0 10px;
|
||||
max-width: 400px;
|
||||
}
|
||||
`;
|
||||
const searchList = [
|
||||
<>
|
||||
<InputGroup>
|
||||
<SelectInput value={searchParams.search_type} onChange={e => onSearch({search_type: e.target.value }, false)}>
|
||||
{userSearchType2.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<TextInput
|
||||
type="text"
|
||||
placeholder={searchParams.search_type === 'GUID' ? 'GUID 입력' : searchParams.search_type === 'ACCOUNT' ? 'ACCOUNT 입력' : '닉네임 입력'}
|
||||
value={searchParams.search_data}
|
||||
width="260px"
|
||||
onChange={e => onSearch({ search_data: e.target.value }, false)}
|
||||
onKeyDown={e => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
onSearch({ search_data: e.target.value }, true);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</InputGroup>
|
||||
</>
|
||||
];
|
||||
|
||||
return <SearchBarLayout firstColumnData={searchList} onReset={onReset} handleSubmit={handleSubmit} />;
|
||||
};
|
||||
|
||||
export default UserSearchBar;
|
||||
@@ -1,130 +1,149 @@
|
||||
import { useState, Fragment } from 'react';
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
|
||||
import { TabScroll, Title } from '../../styles/Components';
|
||||
import { TabItem, TabScroll, TabWrapper, Title } from '../../styles/Components';
|
||||
import { AnimatedPageWrapper } from '../../components/common/Layout'
|
||||
|
||||
import styled from 'styled-components';
|
||||
|
||||
import UserViewSearchBar from '../../components/searchBar/UserViewSearchBar';
|
||||
import UserDefaultInfo from '../../components/DataManage/UserDefaultInfo';
|
||||
import UserAvatarInfo from '../../components/DataManage/UserAvatarInfo';
|
||||
import UserDressInfo from '../../components/DataManage/UserDressInfo';
|
||||
import UserToolInfo from '../../components/DataManage/UserToolInfo';
|
||||
import UserInventoryInfo from '../../components/DataManage/UserInventoryInfo';
|
||||
import UserMailInfo from '../../components/DataManage/UserMailInfo';
|
||||
import UserMyHomeInfo from '../../components/DataManage/UserMyHomeInfo';
|
||||
import UserFriendInfo from '../../components/DataManage/UserFriendInfo';
|
||||
import UserTatttooInfo from '../../components/DataManage/UserTattooInfo';
|
||||
import UserQuestInfo from '../../components/DataManage/UserQuestInfo';
|
||||
import UserClaimInfo from '../../components/DataManage/UserClaimInfo';
|
||||
import {
|
||||
UserDefaultInfo,
|
||||
UserAvatarInfo,
|
||||
UserDressInfo,
|
||||
UserToolInfo,
|
||||
UserInventoryInfo,
|
||||
UserMailInfo,
|
||||
UserMyHomeInfo,
|
||||
UserFriendInfo,
|
||||
UserTattooInfo,
|
||||
UserQuestInfo,
|
||||
UserClaimInfo
|
||||
} from '../../components/DataManage';
|
||||
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { authList } from '../../store/authList';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import AuthModal from '../../components/common/modal/AuthModal';
|
||||
import { authType, TabUserList } from '../../assets/data';
|
||||
import { UserSearchBar, useUserSearch } from '../../components/searchBar';
|
||||
import { withAuth } from '../../hooks/hook';
|
||||
import { AnimatedTabs } from '../../components/common';
|
||||
|
||||
const UserView = () => {
|
||||
const userInfo = useRecoilValue(authList);
|
||||
const token = sessionStorage.getItem('token');
|
||||
|
||||
const [infoView, setInfoView] = useState('none');
|
||||
const [activeContent, setActiveContent] = useState('기본정보');
|
||||
const [resultData, setResultData] = useState([]);
|
||||
const [activeTab, setActiveTab] = useState('BASIC');
|
||||
const [resultData, setResultData] = useState();
|
||||
|
||||
const handleTab = title => {
|
||||
setActiveContent(title);
|
||||
const {
|
||||
searchParams,
|
||||
loading: dataLoading,
|
||||
data,
|
||||
handleSearch,
|
||||
handleReset,
|
||||
updateSearchParams
|
||||
} = useUserSearch(token);
|
||||
|
||||
useEffect(() => {
|
||||
if(data) {
|
||||
setResultData(data);
|
||||
setInfoView('flex');
|
||||
} else {
|
||||
setInfoView('none');
|
||||
}
|
||||
}, [data])
|
||||
|
||||
const tabItems = useMemo(() => {
|
||||
return TabUserList.map(el => ({
|
||||
key: el.value,
|
||||
label: el.name,
|
||||
children: (() => {
|
||||
switch(el.value) {
|
||||
case 'BASIC': return <UserDefaultInfo userInfo={resultData} />;
|
||||
case 'AVATAR': return <UserAvatarInfo userInfo={resultData} />;
|
||||
case 'CLOTH': return <UserDressInfo userInfo={resultData} />;
|
||||
case 'TOOL': return <UserToolInfo userInfo={resultData} />;
|
||||
case 'INVENTORY': return <UserInventoryInfo userInfo={resultData} />;
|
||||
case 'MAIL': return <UserMailInfo userInfo={resultData} />;
|
||||
case 'MYHOME': return <UserMyHomeInfo userInfo={resultData} />;
|
||||
case 'FRIEND': return <UserFriendInfo userInfo={resultData} />;
|
||||
case 'TATTOO': return <UserTattooInfo userInfo={resultData} />;
|
||||
case 'QUEST': return <UserQuestInfo userInfo={resultData} />;
|
||||
case 'CLAIM': return <UserClaimInfo userInfo={resultData} />;
|
||||
default: return null;
|
||||
}
|
||||
})()
|
||||
}));
|
||||
}, [resultData]);
|
||||
|
||||
const handleTabChange = (key) => {
|
||||
setActiveTab(key);
|
||||
};
|
||||
|
||||
// const handleTab = (e, content) => {
|
||||
// e.preventDefault();
|
||||
// setActiveTab(content);
|
||||
// };
|
||||
|
||||
return (
|
||||
<>
|
||||
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.userSearchRead) ? (
|
||||
<AuthModal />
|
||||
) : (
|
||||
<AnimatedPageWrapper>
|
||||
<Title>유저조회</Title>
|
||||
<UserViewSearchBar setInfoView={setInfoView} resultData={resultData} handleTab={handleTab} setResultData={setResultData} />
|
||||
<UserWrapper display={infoView}>
|
||||
<TabScroll>
|
||||
<UserTabWrapper>
|
||||
{TabUserList.map((el, idx) => {
|
||||
return (
|
||||
<UserTab key={idx} $state={el.title === activeContent ? 'active' : 'unactive'} onClick={() => handleTab(el.title)}>
|
||||
{el.title}
|
||||
</UserTab>
|
||||
);
|
||||
})}
|
||||
</UserTabWrapper>
|
||||
</TabScroll>
|
||||
<UserTabInfo>
|
||||
{activeContent === '기본정보' && <UserDefaultInfo userInfo={resultData && resultData} />}
|
||||
{activeContent === '아바타' && <UserAvatarInfo userInfo={resultData && resultData} />}
|
||||
{activeContent === '의상' && <UserDressInfo userInfo={resultData && resultData} />}
|
||||
{activeContent === '도구' && <UserToolInfo userInfo={resultData && resultData} />}
|
||||
{activeContent === '인벤토리' && <UserInventoryInfo userInfo={resultData && resultData} />}
|
||||
{activeContent === '우편' && <UserMailInfo userInfo={resultData && resultData} />}
|
||||
{activeContent === '마이홈' && <UserMyHomeInfo userInfo={resultData && resultData} />}
|
||||
{activeContent === '친구목록' && <UserFriendInfo userInfo={resultData && resultData} />}
|
||||
{activeContent === '타투' && <UserTatttooInfo userInfo={resultData && resultData} />}
|
||||
{activeContent === '퀘스트' && <UserQuestInfo userInfo={resultData && resultData} />}
|
||||
{activeContent === '클레임' && <UserClaimInfo userInfo={resultData && resultData} />}
|
||||
</UserTabInfo>
|
||||
</UserWrapper>
|
||||
</AnimatedPageWrapper>
|
||||
)}
|
||||
</>
|
||||
<AnimatedPageWrapper>
|
||||
<Title>유저조회</Title>
|
||||
<UserSearchBar
|
||||
searchParams={searchParams}
|
||||
onSearch={(newParams, executeSearch = true) => {
|
||||
if (executeSearch) {
|
||||
handleSearch(newParams);
|
||||
} else {
|
||||
updateSearchParams(newParams);
|
||||
setResultData();
|
||||
}
|
||||
}}
|
||||
onReset={handleReset}
|
||||
/>
|
||||
<UserWrapper display={infoView}>
|
||||
<AnimatedTabs
|
||||
items={tabItems}
|
||||
activeKey={activeTab}
|
||||
onChange={handleTabChange}
|
||||
tabPosition="center"
|
||||
/>
|
||||
|
||||
{/*<TabScroll>*/}
|
||||
{/* <TabWrapper>*/}
|
||||
{/* {TabUserList.map((el, idx) => {*/}
|
||||
{/* return (*/}
|
||||
{/* <li key={idx}>*/}
|
||||
{/* <TabItem $state={activeTab === el.value ? 'active' : 'none'} onClick={e => handleTab(e, el.value)}>*/}
|
||||
{/* {el.name}*/}
|
||||
{/* </TabItem>*/}
|
||||
{/* </li>*/}
|
||||
{/* )*/}
|
||||
{/* })}*/}
|
||||
{/* </TabWrapper>*/}
|
||||
{/*</TabScroll>*/}
|
||||
{/*<UserTabInfo>*/}
|
||||
{/* {activeTab === 'BASIC' && <UserDefaultInfo userInfo={resultData && resultData} />}*/}
|
||||
{/* {activeTab === 'AVATAR' && <UserAvatarInfo userInfo={resultData && resultData} />}*/}
|
||||
{/* {activeTab === 'CLOTH' && <UserDressInfo userInfo={resultData && resultData} />}*/}
|
||||
{/* {activeTab === 'TOOL' && <UserToolInfo userInfo={resultData && resultData} />}*/}
|
||||
{/* {activeTab === 'INVENTORY' && <UserInventoryInfo userInfo={resultData && resultData} />}*/}
|
||||
{/* {activeTab === 'MAIL' && <UserMailInfo userInfo={resultData && resultData} />}*/}
|
||||
{/* {activeTab === 'MYHOME' && <UserMyHomeInfo userInfo={resultData && resultData} />}*/}
|
||||
{/* {activeTab === 'FRIEND' && <UserFriendInfo userInfo={resultData && resultData} />}*/}
|
||||
{/* {activeTab === 'TATTOO' && <UserTattooInfo userInfo={resultData && resultData} />}*/}
|
||||
{/* {activeTab === 'QUEST' && <UserQuestInfo userInfo={resultData && resultData} />}*/}
|
||||
{/* {activeTab === 'CLAIM' && <UserClaimInfo userInfo={resultData && resultData} />}*/}
|
||||
{/*</UserTabInfo>*/}
|
||||
</UserWrapper>
|
||||
</AnimatedPageWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserView;
|
||||
export default withAuth(authType.userSearchRead)(UserView);
|
||||
|
||||
const UserTab = styled.li`
|
||||
display: flex;
|
||||
width: 100%;
|
||||
max-width: 120px;
|
||||
height: 35px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-top-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
color: #888;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
background: #888;
|
||||
}
|
||||
${props =>
|
||||
props.$state === 'active' &&
|
||||
`font-weight: 700;
|
||||
color: #fff;
|
||||
background: #888;`}
|
||||
`;
|
||||
|
||||
const UserTabWrapper = styled.ul`
|
||||
border-bottom: 1px solid #888888;
|
||||
display: flex;
|
||||
background: #fff;
|
||||
`;
|
||||
const UserWrapper = styled.div`
|
||||
display: ${props => props.display};
|
||||
${props => props.display && `flex-flow:column;`}
|
||||
min-height: calc(100vh - 345px);
|
||||
overflow: hidden;
|
||||
background: #f9f9f9;
|
||||
`;
|
||||
|
||||
const UserTabInfo = styled.div`
|
||||
padding: 50px;
|
||||
|
||||
overflow: auto;
|
||||
&::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #666666;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #d9d9d9;
|
||||
}
|
||||
`;
|
||||
padding-left: 40px;
|
||||
padding-right: 40px;
|
||||
//background: #f9f9f9;
|
||||
`;
|
||||
Reference in New Issue
Block a user