init
This commit is contained in:
198
src/components/DataManage/CreditLogSearchBar.js
Normal file
198
src/components/DataManage/CreditLogSearchBar.js
Normal file
@@ -0,0 +1,198 @@
|
||||
import { styled } from 'styled-components';
|
||||
import { useState } from 'react';
|
||||
|
||||
import Button from '../../components/common/button/Button';
|
||||
|
||||
import DatePicker, { registerLocale } from 'react-datepicker';
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
import { getMonth, getYear } from 'date-fns';
|
||||
import range from 'lodash/range';
|
||||
|
||||
import { TextInput, SelectInput, DatePickerWrapper, InputLabel, BtnWrapper } from '../../styles/Components';
|
||||
|
||||
const GoodsLogSearchBar = () => {
|
||||
const [startDate, setStartDate] = useState(new Date());
|
||||
const [endDate, setEndDate] = useState(new Date());
|
||||
const [selectData, setSelectData] = useState('default');
|
||||
const years = range(1990, getYear(new Date()) + 1, 1);
|
||||
const months = ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12'];
|
||||
|
||||
const handleChange = e => {
|
||||
setSelectData(e.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchbarStyle2>
|
||||
<SearchRow>
|
||||
<SearchItem>
|
||||
<InputLabel>조회 기간</InputLabel>
|
||||
<DatePickerWrapper>
|
||||
<InputGroup>
|
||||
<DatePicker
|
||||
selected={startDate}
|
||||
onChange={date => setStartDate(date)}
|
||||
className="datepicker"
|
||||
placeholderText="검색기간 선택"
|
||||
calendarClassName="calendar"
|
||||
dateFormat="yyyy - MM - dd"
|
||||
locale="ko"
|
||||
renderCustomHeader={({ date, changeYear, changeMonth, decreaseMonth, increaseMonth, prevMonthButtonDisabled, nextMonthButtonDisabled }) => (
|
||||
<div className="calendar-top">
|
||||
<button
|
||||
className="btn-prev"
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
decreaseMonth();
|
||||
}}
|
||||
disabled={prevMonthButtonDisabled}></button>
|
||||
<select value={getYear(date)} onChange={({ target: { value } }) => changeYear(value)}>
|
||||
{years.map(option => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
.
|
||||
<select value={months[getMonth(date)]} onChange={({ target: { value } }) => changeMonth(months.indexOf(value))}>
|
||||
{months.map(option => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<button
|
||||
className="btn-next"
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
increaseMonth();
|
||||
}}
|
||||
disabled={nextMonthButtonDisabled}></button>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<SelectInput>
|
||||
<option value="">00</option>
|
||||
<option value="">01</option>
|
||||
</SelectInput>
|
||||
<SelectInput>
|
||||
<option value="">00</option>
|
||||
<option value="">01</option>
|
||||
</SelectInput>
|
||||
</InputGroup>
|
||||
<span>~</span>
|
||||
<InputGroup>
|
||||
<DatePicker
|
||||
selected={endDate}
|
||||
onChange={date => setEndDate(date)}
|
||||
className="datepicker"
|
||||
placeholderText="검색기간 선택"
|
||||
calendarClassName="calendar"
|
||||
dateFormat="yyyy - MM - dd"
|
||||
minDate = {startDate}
|
||||
locale="ko"
|
||||
renderCustomHeader={({ date, changeYear, changeMonth, decreaseMonth, increaseMonth, prevMonthButtonDisabled, nextMonthButtonDisabled }) => (
|
||||
<div className="calendar-top">
|
||||
<button
|
||||
className="btn-prev"
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
decreaseMonth();
|
||||
}}
|
||||
disabled={prevMonthButtonDisabled}></button>
|
||||
<select value={getYear(date)} onChange={({ target: { value } }) => changeYear(value)}>
|
||||
{years.map(option => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
.
|
||||
<select value={months[getMonth(date)]} onChange={({ target: { value } }) => changeMonth(months.indexOf(value))}>
|
||||
{months.map(option => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<button
|
||||
className="btn-next"
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
increaseMonth();
|
||||
}}
|
||||
disabled={nextMonthButtonDisabled}></button>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<SelectInput>
|
||||
<option value="">00</option>
|
||||
<option value="">01</option>
|
||||
</SelectInput>
|
||||
<SelectInput>
|
||||
<option value="">00</option>
|
||||
<option value="">01</option>
|
||||
</SelectInput>
|
||||
</InputGroup>
|
||||
</DatePickerWrapper>
|
||||
</SearchItem>
|
||||
</SearchRow>
|
||||
<SearchRow>
|
||||
<SearchItem>
|
||||
<InputLabel>조회 대상</InputLabel>
|
||||
<TextInput type="text" placeholder="조회 대상 유저의 GUID를 입력하세요." width="600px" />
|
||||
</SearchItem>
|
||||
<BtnWrapper $gap="8px">
|
||||
<Button theme="reset" />
|
||||
<Button theme="gray" text="검색" />
|
||||
</BtnWrapper>
|
||||
</SearchRow>
|
||||
</SearchbarStyle2>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default GoodsLogSearchBar;
|
||||
|
||||
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 SearchbarStyle2 = styled(SearchbarStyle)`
|
||||
flex-flow: column;
|
||||
gap: 20px;
|
||||
`;
|
||||
|
||||
const SearchRow = styled.div`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px 0;
|
||||
`;
|
||||
|
||||
const InputGroup = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
`;
|
||||
|
||||
const SearchItem = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
margin-right: 50px;
|
||||
|
||||
${TextInput}, ${SelectInput} {
|
||||
height: 35px;
|
||||
}
|
||||
${TextInput} {
|
||||
padding: 0 10px;
|
||||
max-width: 400px;
|
||||
}
|
||||
`;
|
||||
219
src/components/DataManage/ItemLogSearchBar.js
Normal file
219
src/components/DataManage/ItemLogSearchBar.js
Normal file
@@ -0,0 +1,219 @@
|
||||
import { useState } from 'react';
|
||||
import { styled } from 'styled-components';
|
||||
|
||||
import RadioInput from '../common/input/Radio';
|
||||
import Button from '../common/button/Button';
|
||||
|
||||
import DatePicker, { registerLocale } from 'react-datepicker';
|
||||
import { ko } from 'date-fns/esm/locale';
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
import { getMonth, getYear } from 'date-fns';
|
||||
import range from 'lodash/range';
|
||||
|
||||
import { TextInput, SelectInput, DatePickerWrapper, InputLabel, BtnWrapper } from '../../styles/Components';
|
||||
|
||||
const ItemLogSearchBar = () => {
|
||||
const [startDate, setStartDate] = useState(new Date());
|
||||
const [endDate, setEndDate] = useState(new Date());
|
||||
const [selectData, setSelectData] = useState('default');
|
||||
const years = range(1990, getYear(new Date()) + 1, 1);
|
||||
const months = ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12'];
|
||||
|
||||
const handleChange = e => {
|
||||
setSelectData(e.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchbarStyle2>
|
||||
<SearchRow>
|
||||
<RadioGroup>
|
||||
<RadioInput label="기본 조회" id="single" name="receiver" value="default" fontWeight="600" checked={selectData === 'default'} handleChange={handleChange} />
|
||||
<RadioInput label="아이템 소유자 추적" id="multi" name="receiver" value="item" fontWeight="600" checked={selectData === 'item'} handleChange={handleChange} />
|
||||
</RadioGroup>
|
||||
</SearchRow>
|
||||
<SearchRow>
|
||||
<SearchItem>
|
||||
<InputLabel>조회 기간</InputLabel>
|
||||
<DatePickerWrapper>
|
||||
<InputGroup>
|
||||
<DatePicker
|
||||
selected={startDate}
|
||||
onChange={date => setStartDate(date)}
|
||||
className="datepicker"
|
||||
placeholderText="검색기간 선택"
|
||||
calendarClassName="calendar"
|
||||
dateFormat="yyyy - MM - dd"
|
||||
locale="ko"
|
||||
renderCustomHeader={({ date, changeYear, changeMonth, decreaseMonth, increaseMonth, prevMonthButtonDisabled, nextMonthButtonDisabled }) => (
|
||||
<div className="calendar-top">
|
||||
<button
|
||||
className="btn-prev"
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
decreaseMonth();
|
||||
}}
|
||||
disabled={prevMonthButtonDisabled}></button>
|
||||
<select value={getYear(date)} onChange={({ target: { value } }) => changeYear(value)}>
|
||||
{years.map(option => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
.
|
||||
<select value={months[getMonth(date)]} onChange={({ target: { value } }) => changeMonth(months.indexOf(value))}>
|
||||
{months.map(option => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<button
|
||||
className="btn-next"
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
increaseMonth();
|
||||
}}
|
||||
disabled={nextMonthButtonDisabled}></button>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<SelectInput>
|
||||
<option value="">00</option>
|
||||
<option value="">01</option>
|
||||
</SelectInput>
|
||||
<SelectInput>
|
||||
<option value="">00</option>
|
||||
<option value="">01</option>
|
||||
</SelectInput>
|
||||
</InputGroup>
|
||||
<span>~</span>
|
||||
<InputGroup>
|
||||
<DatePicker
|
||||
selected={endDate}
|
||||
onChange={date => setEndDate(date)}
|
||||
className="datepicker"
|
||||
placeholderText="검색기간 선택"
|
||||
calendarClassName="calendar"
|
||||
dateFormat="yyyy - MM - dd"
|
||||
minDate = {startDate}
|
||||
locale="ko"
|
||||
renderCustomHeader={({ date, changeYear, changeMonth, decreaseMonth, increaseMonth, prevMonthButtonDisabled, nextMonthButtonDisabled }) => (
|
||||
<div className="calendar-top">
|
||||
<button
|
||||
className="btn-prev"
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
decreaseMonth();
|
||||
}}
|
||||
disabled={prevMonthButtonDisabled}></button>
|
||||
<select value={getYear(date)} onChange={({ target: { value } }) => changeYear(value)}>
|
||||
{years.map(option => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
.
|
||||
<select value={months[getMonth(date)]} onChange={({ target: { value } }) => changeMonth(months.indexOf(value))}>
|
||||
{months.map(option => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<button
|
||||
className="btn-next"
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
increaseMonth();
|
||||
}}
|
||||
disabled={nextMonthButtonDisabled}></button>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<SelectInput>
|
||||
<option value="">00</option>
|
||||
<option value="">01</option>
|
||||
</SelectInput>
|
||||
<SelectInput>
|
||||
<option value="">00</option>
|
||||
<option value="">01</option>
|
||||
</SelectInput>
|
||||
</InputGroup>
|
||||
</DatePickerWrapper>
|
||||
</SearchItem>
|
||||
</SearchRow>
|
||||
<SearchRow>
|
||||
{selectData === 'default' ? (
|
||||
<SearchItem>
|
||||
<InputLabel>조회 대상</InputLabel>
|
||||
<TextInput type="text" placeholder="조회 대상 유저의 GUID를 입력하세요." width="600px" />
|
||||
</SearchItem>
|
||||
) : (
|
||||
<SearchItem>
|
||||
<InputLabel>아이템 ID</InputLabel>
|
||||
<TextInput type="text" placeholder="아이템의 GUID를 입력하세요." width="600px" />
|
||||
</SearchItem>
|
||||
)}
|
||||
|
||||
<BtnWrapper $gap="8px">
|
||||
<Button theme="reset" />
|
||||
<Button theme="gray" text="검색" />
|
||||
</BtnWrapper>
|
||||
</SearchRow>
|
||||
</SearchbarStyle2>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ItemLogSearchBar;
|
||||
|
||||
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 SearchbarStyle2 = styled(SearchbarStyle)`
|
||||
flex-flow: column;
|
||||
gap: 20px;
|
||||
`;
|
||||
|
||||
const SearchRow = styled.div`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px 0;
|
||||
`;
|
||||
|
||||
const InputGroup = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
`;
|
||||
|
||||
const RadioGroup = styled(InputGroup)`
|
||||
gap: 30px;
|
||||
height: 35px;
|
||||
`;
|
||||
|
||||
const SearchItem = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
margin-right: 50px;
|
||||
|
||||
${TextInput}, ${SelectInput} {
|
||||
height: 35px;
|
||||
}
|
||||
${TextInput} {
|
||||
padding: 0 10px;
|
||||
max-width: 400px;
|
||||
}
|
||||
`;
|
||||
79
src/components/DataManage/LandDetailModal.js
Normal file
79
src/components/DataManage/LandDetailModal.js
Normal file
@@ -0,0 +1,79 @@
|
||||
import { styled } from 'styled-components';
|
||||
|
||||
import { Title } from '../../styles/Components';
|
||||
import { BtnWrapper, TableStyle } from '../../styles/Components';
|
||||
import Button from '../../components/common/button/Button';
|
||||
import Modal from '../../components/common/modal/Modal';
|
||||
import { Fragment } from 'react';
|
||||
|
||||
const LandDetailModal = ({ detailPop, handleClick }) => {
|
||||
const landlist = [
|
||||
{ floor: '1', instanceId: 'ad31230001' },
|
||||
{ floor: '2', instanceId: 'ad31230001' },
|
||||
];
|
||||
return (
|
||||
<>
|
||||
<Modal $view={detailPop} min="480px">
|
||||
<Title $align="center">랜드 상세정보</Title>
|
||||
<TableWrapper>
|
||||
<TableStyle>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="100">층 정보</th>
|
||||
<th>인스턴스 연결 정보</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{landlist.map((el, index) => {
|
||||
return (
|
||||
<Fragment key={index}>
|
||||
<tr>
|
||||
<td>{el.floor}</td>
|
||||
<InstanceData>{el.instanceId}</InstanceData>
|
||||
</tr>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</TableStyle>
|
||||
</TableWrapper>
|
||||
<BtnWrapper2 $justify="center">
|
||||
<Button
|
||||
theme="line"
|
||||
text="확인"
|
||||
handleClick={e => {
|
||||
e.preventDefault();
|
||||
handleClick();
|
||||
}}
|
||||
/>
|
||||
</BtnWrapper2>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default LandDetailModal;
|
||||
|
||||
const InstanceData = styled.td``;
|
||||
|
||||
const TableWrapper = styled.div`
|
||||
max-height: 50vh;
|
||||
max-width: 600px;
|
||||
overflow: auto;
|
||||
${InstanceData} {
|
||||
text-align: left;
|
||||
}
|
||||
&::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #666666;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #d9d9d9;
|
||||
}
|
||||
`;
|
||||
|
||||
const BtnWrapper2 = styled(BtnWrapper)`
|
||||
margin-top: 30px;
|
||||
`;
|
||||
57
src/components/DataManage/LandSearchBar.js
Normal file
57
src/components/DataManage/LandSearchBar.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import { styled } from 'styled-components';
|
||||
import Button from '../../components/common/button/Button';
|
||||
|
||||
import { FormWrapper, InputLabel, TextInput, SelectInput, BtnWrapper } from '../../styles/Components';
|
||||
|
||||
const LandSearchBar = () => {
|
||||
return (
|
||||
<>
|
||||
<FormWrapper>
|
||||
<SearchbarStyle>
|
||||
<SearchItem>
|
||||
<InputLabel>랜드 조회</InputLabel>
|
||||
<TextInput type="text" width="300px" placeholder="랜드 ID를 입력하세요." />
|
||||
</SearchItem>
|
||||
<BtnWrapper $gap="8px">
|
||||
<Button theme="reset" />
|
||||
<Button
|
||||
theme="gray"
|
||||
text="검색"
|
||||
handleClick={e => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
/>
|
||||
</BtnWrapper>
|
||||
</SearchbarStyle>
|
||||
</FormWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default LandSearchBar;
|
||||
|
||||
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;
|
||||
|
||||
${TextInput}, ${SelectInput} {
|
||||
height: 35px;
|
||||
}
|
||||
${TextInput} {
|
||||
padding: 0 10px;
|
||||
max-width: 400px;
|
||||
}
|
||||
`;
|
||||
141
src/components/DataManage/MailDetailModal.js
Normal file
141
src/components/DataManage/MailDetailModal.js
Normal file
@@ -0,0 +1,141 @@
|
||||
import { styled } from 'styled-components';
|
||||
import Button from '../../components/common/button/Button';
|
||||
import Modal from '../common/modal/Modal';
|
||||
|
||||
import { TextInput, BtnWrapper} from '../../styles/Components';
|
||||
import { Textarea, Title } from '../../styles/Components';
|
||||
import CDivider from '../common/CDivider';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import IconDelete from '../../assets/img/icon/icon-delete.png';
|
||||
|
||||
const MailDetailModal = ({ mailModal, handleClick, setDetail, content, handleDelete, handleItemDelete, authDelete }) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<Modal $view={mailModal}>
|
||||
<Title $align="center">우편 상세 정보</Title>
|
||||
<MailDetailTable>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th width="120">제목</th>
|
||||
<td>
|
||||
<TextInput defaultValue={content.title}></TextInput>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>내용</th>
|
||||
<td>
|
||||
<Textarea defaultValue={content.content}></Textarea>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>아이템 첨부</th>
|
||||
<td>
|
||||
<div>
|
||||
{content.item_list && (
|
||||
<ItemList>
|
||||
{content &&
|
||||
content.item_list.map((data, index) => {
|
||||
return (
|
||||
<Item key={index}>
|
||||
<span>
|
||||
{data.item_name}({data.count})
|
||||
</span>
|
||||
<BtnDelete onClick={() => handleItemDelete(data)} disabled={!authDelete}></BtnDelete>
|
||||
{content && content.is_reserve === false}
|
||||
</Item>
|
||||
);
|
||||
})}
|
||||
</ItemList>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</MailDetailTable>
|
||||
<BtnWrapper $justify="center">
|
||||
<Button
|
||||
theme={authDelete ? "line" : "disable"}
|
||||
text="삭제"
|
||||
handleClick={handleDelete}
|
||||
disabled={!authDelete}
|
||||
/>
|
||||
<CDivider />
|
||||
<Button
|
||||
theme="line"
|
||||
text="확인"
|
||||
handleClick={e => {
|
||||
e.preventDefault();
|
||||
handleClick();
|
||||
setDetail({ title: '', content: '', item_list: [] });
|
||||
}}
|
||||
/>
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MailDetailModal;
|
||||
|
||||
const MailDetailTable = styled.table`
|
||||
max-width: 800px;
|
||||
margin-bottom: 30px;
|
||||
tr:first-child {
|
||||
th,
|
||||
td {
|
||||
border-top: 1px solid #000;
|
||||
}
|
||||
}
|
||||
th {
|
||||
font-weight: 700;
|
||||
vertical-align: top;
|
||||
line-height: 30px;
|
||||
}
|
||||
th,
|
||||
td {
|
||||
padding: 15px;
|
||||
border-bottom: 1px solid #d9d9d9;
|
||||
}
|
||||
tr:last-child {
|
||||
th,
|
||||
td {
|
||||
border-bottom: 1px solid #000;
|
||||
}
|
||||
}
|
||||
td {
|
||||
textarea {
|
||||
border: 1px solid #e0e0e0;
|
||||
width: 100%;
|
||||
border-radius: 5px;
|
||||
min-height: 200px;
|
||||
&::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #666666;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #d9d9d9;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const ItemList = styled.ul`
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
padding: 10px;
|
||||
flex-wrap: wrap;
|
||||
`;
|
||||
|
||||
const Item = styled.li`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const BtnDelete = styled.button`
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: url(${IconDelete}) 50% 50% no-repeat;
|
||||
`;
|
||||
129
src/components/DataManage/NicknameChangeModal.js
Normal file
129
src/components/DataManage/NicknameChangeModal.js
Normal file
@@ -0,0 +1,129 @@
|
||||
import { styled } from 'styled-components';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { Title } from '../../styles/Components';
|
||||
import { TextInput, BtnWrapper, ButtonClose, ModalText } from '../../styles/Components';
|
||||
import Button from '../../components/common/button/Button';
|
||||
import Modal from '../../components/common/modal/Modal';
|
||||
import { UserChangeNickName } from '../../apis';
|
||||
|
||||
const NicknameChangeModal = ({ pwPop, handleClick, dataList }) => {
|
||||
let nickName = dataList.char_info && dataList.char_info.character_name;
|
||||
const [modifyModal, setModifyModal] = useState('hidden');
|
||||
const [completeModal, setCompleteModal] = useState('hidden');
|
||||
const [completeText, setCompleteText] = useState('');
|
||||
|
||||
const [resultData, setResultData] = useState({
|
||||
guid: '',
|
||||
nickname: '',
|
||||
new_nickname: '',
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setResultData({ ...resultData, guid: dataList.user_info && dataList.user_info.aid, nickname: dataList.char_info && dataList.char_info.character_name });
|
||||
}, [dataList]);
|
||||
|
||||
// 수정 모달창
|
||||
const handleModifyModal = () => {
|
||||
if (modifyModal === 'hidden') {
|
||||
setModifyModal('view');
|
||||
} else {
|
||||
setModifyModal('hidden');
|
||||
}
|
||||
};
|
||||
|
||||
// 완료 모달창
|
||||
const handleCompleteModal = () => {
|
||||
if (completeModal === 'hidden') {
|
||||
setCompleteModal('view');
|
||||
} else {
|
||||
setCompleteModal('hidden');
|
||||
|
||||
handleClick();
|
||||
completeText === '변경이 완료되었습니다.' && window.location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
// 수정
|
||||
const handleModifyNotice = async () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
|
||||
const message = await UserChangeNickName(token, resultData);
|
||||
|
||||
// console.log(message);
|
||||
message.data.data.message !== '수정 하였습니다.' ? setCompleteText('변경 닉네임이 이미 존재합니다.\n다시 시도해주세요.') : setCompleteText('변경이 완료되었습니다.');
|
||||
|
||||
handleCompleteModal();
|
||||
handleModifyModal();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal $view={pwPop} min="480px">
|
||||
<Title $align="center">닉네임 변경</Title>
|
||||
<PwSetTable>
|
||||
<colgroup>
|
||||
<col width="120" />
|
||||
<col />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>기존 닉네임</th>
|
||||
<td>{nickName}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>변경 닉네임</th>
|
||||
<td>
|
||||
<TextInput placeholder="닉네임을 입력하세요." onChange={e => setResultData({ ...resultData, new_nickname: e.target.value })} maxLength={12} />
|
||||
<ChangeNotice>* 최대 12글자까지 가능합니다.</ChangeNotice>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</PwSetTable>
|
||||
<BtnWrapper $justify="center" $gap="10px">
|
||||
<Button theme="line" text="취소" handleClick={handleClick} />
|
||||
<Button theme="primary" text="변경하기" handleClick={handleModifyModal} />
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
|
||||
{/* 확인 모달 */}
|
||||
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={modifyModal}>
|
||||
<BtnWrapper $justify="flex-end">
|
||||
<ButtonClose onClick={handleModifyModal} />
|
||||
</BtnWrapper>
|
||||
<ModalText $align="center">닉네임을 변경하시겠습니까?</ModalText>
|
||||
<BtnWrapper $gap="10px">
|
||||
<Button text="취소" theme="line" size="large" width="100%" handleClick={handleModifyModal} />
|
||||
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleModifyNotice} />
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
{/* 완료 모달 */}
|
||||
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={completeModal}>
|
||||
<BtnWrapper $justify="flex-end">
|
||||
<ButtonClose onClick={handleCompleteModal} />
|
||||
</BtnWrapper>
|
||||
<ModalText $align="center">{completeText}</ModalText>
|
||||
<BtnWrapper $gap="10px">
|
||||
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleCompleteModal} />
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default NicknameChangeModal;
|
||||
const PwSetTable = styled.table`
|
||||
width: 400px;
|
||||
margin: 30px 0;
|
||||
th,
|
||||
td {
|
||||
padding: 10px 0;
|
||||
}
|
||||
`;
|
||||
const ChangeNotice = styled.span`
|
||||
font-size: 12px;
|
||||
font-weight: 300;
|
||||
color: ${props => props.$color || '#999'};
|
||||
margin-top: 10px;
|
||||
display: block;
|
||||
`;
|
||||
80
src/components/DataManage/QuestDetailModal.js
Normal file
80
src/components/DataManage/QuestDetailModal.js
Normal file
@@ -0,0 +1,80 @@
|
||||
import { styled } from 'styled-components';
|
||||
|
||||
import { Title } from '../../styles/Components';
|
||||
import { BtnWrapper, TableStyle } from '../../styles/Components';
|
||||
import Button from '../../components/common/button/Button';
|
||||
import Modal from '../../components/common/modal/Modal';
|
||||
import { useEffect, useState, Fragment } from 'react';
|
||||
|
||||
const QuestDetailModal = ({ detailPop, handleClick, detailQuest }) => {
|
||||
const [detailList, setDetailList] = useState([])
|
||||
|
||||
useEffect(() => {
|
||||
Array.isArray(detailQuest) && setDetailList(detailQuest)
|
||||
|
||||
}, [detailQuest])
|
||||
// const questlist = [{ taskNo: detailQuest.task_no, taskName: detailQuest.quest_name, counter: detailQuest.counter, state: detailQuest.status }];
|
||||
return (
|
||||
<>
|
||||
<Modal $view={detailPop} min="480px">
|
||||
<Title $align="center">퀘스트 상세정보</Title>
|
||||
<TableWrapper>
|
||||
<TableStyle>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="80">Task No</th>
|
||||
<th>Task Name</th>
|
||||
<th width="120">Counter</th>
|
||||
<th width="120">State</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{detailList && detailList.map((el, index) => {
|
||||
return (
|
||||
<Fragment key={index}>
|
||||
<tr>
|
||||
<td>{el.task_no}</td>
|
||||
<td>{el.quest_name}</td>
|
||||
<td>{el.counter}</td>
|
||||
<td>{el.status}</td>
|
||||
</tr>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</TableStyle>
|
||||
</TableWrapper>
|
||||
<BtnWrapper2 $justify="center">
|
||||
<Button
|
||||
theme="line"
|
||||
text="확인"
|
||||
handleClick={e => {
|
||||
e.preventDefault();
|
||||
handleClick();
|
||||
}}
|
||||
/>
|
||||
</BtnWrapper2>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default QuestDetailModal;
|
||||
|
||||
const TableWrapper = styled.div`
|
||||
max-height: 50vh;
|
||||
overflow: auto;
|
||||
&::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #666666;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #d9d9d9;
|
||||
}
|
||||
`;
|
||||
|
||||
const BtnWrapper2 = styled(BtnWrapper)`
|
||||
margin-top: 30px;
|
||||
`;
|
||||
198
src/components/DataManage/TradeLogSearchBar.js
Normal file
198
src/components/DataManage/TradeLogSearchBar.js
Normal file
@@ -0,0 +1,198 @@
|
||||
import { styled } from 'styled-components';
|
||||
import { useState } from 'react';
|
||||
|
||||
import Button from '../../components/common/button/Button';
|
||||
|
||||
import DatePicker, { registerLocale } from 'react-datepicker';
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
import { getMonth, getYear } from 'date-fns';
|
||||
import range from 'lodash/range';
|
||||
|
||||
import { TextInput, SelectInput, DatePickerWrapper, InputLabel, BtnWrapper } from '../../styles/Components';
|
||||
|
||||
const TradeLogSerchBar = () => {
|
||||
const [startDate, setStartDate] = useState(new Date());
|
||||
const [endDate, setEndDate] = useState(new Date());
|
||||
const [selectData, setSelectData] = useState('default');
|
||||
const years = range(1990, getYear(new Date()) + 1, 1);
|
||||
const months = ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12'];
|
||||
|
||||
const handleChange = e => {
|
||||
setSelectData(e.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchbarStyle2>
|
||||
<SearchRow>
|
||||
<SearchItem>
|
||||
<InputLabel>조회 기간</InputLabel>
|
||||
<DatePickerWrapper>
|
||||
<InputGroup>
|
||||
<DatePicker
|
||||
selected={startDate}
|
||||
onChange={date => setStartDate(date)}
|
||||
className="datepicker"
|
||||
placeholderText="검색기간 선택"
|
||||
calendarClassName="calendar"
|
||||
dateFormat="yyyy - MM - dd"
|
||||
locale="ko"
|
||||
renderCustomHeader={({ date, changeYear, changeMonth, decreaseMonth, increaseMonth, prevMonthButtonDisabled, nextMonthButtonDisabled }) => (
|
||||
<div className="calendar-top">
|
||||
<button
|
||||
className="btn-prev"
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
decreaseMonth();
|
||||
}}
|
||||
disabled={prevMonthButtonDisabled}></button>
|
||||
<select value={getYear(date)} onChange={({ target: { value } }) => changeYear(value)}>
|
||||
{years.map(option => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
.
|
||||
<select value={months[getMonth(date)]} onChange={({ target: { value } }) => changeMonth(months.indexOf(value))}>
|
||||
{months.map(option => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<button
|
||||
className="btn-next"
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
increaseMonth();
|
||||
}}
|
||||
disabled={nextMonthButtonDisabled}></button>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<SelectInput>
|
||||
<option value="">00</option>
|
||||
<option value="">01</option>
|
||||
</SelectInput>
|
||||
<SelectInput>
|
||||
<option value="">00</option>
|
||||
<option value="">01</option>
|
||||
</SelectInput>
|
||||
</InputGroup>
|
||||
<span>~</span>
|
||||
<InputGroup>
|
||||
<DatePicker
|
||||
selected={endDate}
|
||||
onChange={date => setEndDate(date)}
|
||||
className="datepicker"
|
||||
placeholderText="검색기간 선택"
|
||||
calendarClassName="calendar"
|
||||
dateFormat="yyyy - MM - dd"
|
||||
minDate = {startDate}
|
||||
locale="ko"
|
||||
renderCustomHeader={({ date, changeYear, changeMonth, decreaseMonth, increaseMonth, prevMonthButtonDisabled, nextMonthButtonDisabled }) => (
|
||||
<div className="calendar-top">
|
||||
<button
|
||||
className="btn-prev"
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
decreaseMonth();
|
||||
}}
|
||||
disabled={prevMonthButtonDisabled}></button>
|
||||
<select value={getYear(date)} onChange={({ target: { value } }) => changeYear(value)}>
|
||||
{years.map(option => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
.
|
||||
<select value={months[getMonth(date)]} onChange={({ target: { value } }) => changeMonth(months.indexOf(value))}>
|
||||
{months.map(option => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<button
|
||||
className="btn-next"
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
increaseMonth();
|
||||
}}
|
||||
disabled={nextMonthButtonDisabled}></button>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<SelectInput>
|
||||
<option value="">00</option>
|
||||
<option value="">01</option>
|
||||
</SelectInput>
|
||||
<SelectInput>
|
||||
<option value="">00</option>
|
||||
<option value="">01</option>
|
||||
</SelectInput>
|
||||
</InputGroup>
|
||||
</DatePickerWrapper>
|
||||
</SearchItem>
|
||||
</SearchRow>
|
||||
<SearchRow>
|
||||
<SearchItem>
|
||||
<InputLabel>조회 대상</InputLabel>
|
||||
<TextInput type="text" placeholder="조회 대상 유저의 GUID를 입력하세요." width="600px" />
|
||||
</SearchItem>
|
||||
<BtnWrapper $gap="8px">
|
||||
<Button theme="reset" />
|
||||
<Button theme="gray" text="검색" />
|
||||
</BtnWrapper>
|
||||
</SearchRow>
|
||||
</SearchbarStyle2>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TradeLogSerchBar;
|
||||
|
||||
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 SearchbarStyle2 = styled(SearchbarStyle)`
|
||||
flex-flow: column;
|
||||
gap: 20px;
|
||||
`;
|
||||
|
||||
const SearchRow = styled.div`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px 0;
|
||||
`;
|
||||
|
||||
const InputGroup = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
`;
|
||||
|
||||
const SearchItem = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
margin-right: 50px;
|
||||
|
||||
${TextInput}, ${SelectInput} {
|
||||
height: 35px;
|
||||
}
|
||||
${TextInput} {
|
||||
padding: 0 10px;
|
||||
max-width: 400px;
|
||||
}
|
||||
`;
|
||||
672
src/components/DataManage/UserAvatarInfo.js
Normal file
672
src/components/DataManage/UserAvatarInfo.js
Normal file
@@ -0,0 +1,672 @@
|
||||
import styled from 'styled-components';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { UserAvatarView } from '../../apis/Users';
|
||||
import { TableSkeleton } from '../Skeleton/TableSkeleton';
|
||||
|
||||
const UserAvatarInfo = ({ userInfo }) => {
|
||||
const [dataList, setDataList] = useState();
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
const fetchData = async () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
await UserAvatarView(token, userInfo.guid).then(data => {
|
||||
setDataList(data);
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
loading ? <TableSkeleton width='25%' count={20}/> :
|
||||
<>
|
||||
<Notice>* 아바타 항목의 모든 항목은 ID 및 코드로 노출됩니다.</Notice>
|
||||
<AvatarWrapper>
|
||||
<UserInfoTable $maxwidth="600px">
|
||||
<colgroup>
|
||||
<col width="120" />
|
||||
<col width="30%" />
|
||||
<col width="120" />
|
||||
<col width="30%" />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>아바타 ID</th>
|
||||
<td colSpan="3">{dataList && dataList.avatar_info.character_id}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>기본 외형</th>
|
||||
<td colSpan="3">{dataList && dataList.avatar_info.basicstyle}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>체형 타입</th>
|
||||
<td colSpan="3">{dataList && dataList.avatar_info.bodyshape}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>헤어스타일</th>
|
||||
<td>{dataList && dataList.avatar_info.hairstyle}</td>
|
||||
<th>헤어 컬러</th>
|
||||
<td>{dataList && dataList.avatar_info.haircolor}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>나이</th>
|
||||
<td colSpan="3">{dataList && dataList.avatar_info.age}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</UserInfoTable>
|
||||
|
||||
<UserFaceInfoTable $maxwidth="600px">
|
||||
<colgroup>
|
||||
<col width="120" />
|
||||
<col width="100%" />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>얼굴 윤곽</th>
|
||||
<td>
|
||||
<UserFaceDetailInfoTable>
|
||||
<colgroup>
|
||||
<col width="120" />
|
||||
<col width="100%" />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>얼굴 길이</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[0]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>이마 돌출</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[1]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>볼 라인</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[2]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>광대 좌우</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[3]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>광대 상하</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[4]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>광대 돌출</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[5]}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</UserFaceDetailInfoTable>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</UserFaceInfoTable>
|
||||
<UserFaceInfoTable $maxwidth="600px">
|
||||
<colgroup>
|
||||
<col width="120" />
|
||||
<col width="100%" />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>턱</th>
|
||||
<td>
|
||||
<UserFaceDetailInfoTable>
|
||||
<colgroup>
|
||||
<col width="120" />
|
||||
<col width="100%" />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>앞턱 넓이 좌우</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[6]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>앞턱 상하</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[7]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>앞턱 전후</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[8]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>뒷턱 돌출</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[9]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>뒷턱 상하</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[10]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>뒷턱 넓이 좌우</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[11]}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</UserFaceDetailInfoTable>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</UserFaceInfoTable>
|
||||
<UserFaceInfoTable $maxwidth="600px">
|
||||
<colgroup>
|
||||
<col width="120" />
|
||||
<col width="100%" />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>눈</th>
|
||||
<td>
|
||||
<UserFaceDetailInfoTable>
|
||||
<colgroup>
|
||||
<col width="120" />
|
||||
<col width="100%" />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>눈 사이 간격</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[12]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>눈 높이 상하</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[13]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>눈 각도</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[14]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>눈 크기</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[15]}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</UserFaceDetailInfoTable>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</UserFaceInfoTable>
|
||||
<UserFaceInfoTable $maxwidth="600px">
|
||||
<colgroup>
|
||||
<col width="120" />
|
||||
<col width="100%" />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>눈썹</th>
|
||||
<td>
|
||||
<UserFaceDetailInfoTable>
|
||||
<colgroup>
|
||||
<col width="120" />
|
||||
<col width="100%" />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>눈썹뼈 돌출</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[16]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>넓이</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[17]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>높이</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[18]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>각도</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[19]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>두께</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[20]}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</UserFaceDetailInfoTable>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</UserFaceInfoTable>
|
||||
<UserFaceInfoTable $maxwidth="600px">
|
||||
<colgroup>
|
||||
<col width="120" />
|
||||
<col width="100%" />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>코</th>
|
||||
<td>
|
||||
<UserFaceDetailInfoTable>
|
||||
<colgroup>
|
||||
<col width="120" />
|
||||
<col width="100%" />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>코 높이 상하</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[21]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>콧대 돌출</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[22]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>코볼</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[23]}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</UserFaceDetailInfoTable>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</UserFaceInfoTable>
|
||||
|
||||
<UserFaceInfoTable $maxwidth="600px">
|
||||
<colgroup>
|
||||
<col width="120" />
|
||||
<col width="100%" />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>입</th>
|
||||
<td>
|
||||
<UserFaceDetailInfoTable>
|
||||
<colgroup>
|
||||
<col width="120" />
|
||||
<col width="100%" />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>상하</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[24]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>넓이 좌우</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[25]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>윗입술 두께</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[26]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>아랫입술 두께</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[27]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>윗입술 넓이</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[28]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>아랫입술 넓이</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[29]}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</UserFaceDetailInfoTable>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</UserFaceInfoTable>
|
||||
<UserFaceInfoTable $maxwidth="600px">
|
||||
<colgroup>
|
||||
<col width="120" />
|
||||
<col width="100%" />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>귀</th>
|
||||
<td>
|
||||
<UserFaceDetailInfoTable>
|
||||
<colgroup>
|
||||
<col width="120" />
|
||||
<col width="100%" />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>뾰족귀(엘프귀)</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[30]}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>귓볼(부처귀)</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[31]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>귀 각도</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[32]}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</UserFaceDetailInfoTable>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</UserFaceInfoTable>
|
||||
<UserFaceInfoTable $maxwidth="600px">
|
||||
<colgroup>
|
||||
<col width="120" />
|
||||
<col width="100%" />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>헤어 루트컬러</th>
|
||||
<td>
|
||||
<UserFaceDetailInfoTable>
|
||||
<colgroup>
|
||||
<col width="120" />
|
||||
<col width="100%" />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>RED</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[33]}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>GREEN</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[34]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>BLUE</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[35]}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</UserFaceDetailInfoTable>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</UserFaceInfoTable>
|
||||
<UserFaceInfoTable $maxwidth="600px">
|
||||
<colgroup>
|
||||
<col width="120" />
|
||||
<col width="100%" />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>헤어 팁 컬러</th>
|
||||
<td>
|
||||
<UserFaceDetailInfoTable>
|
||||
<colgroup>
|
||||
<col width="120" />
|
||||
<col width="100%" />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>RED</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[36]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>GREEN</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[37]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>BLUE</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[38]}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</UserFaceDetailInfoTable>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</UserFaceInfoTable>
|
||||
<UserFaceInfoTable $maxwidth="600px">
|
||||
<colgroup>
|
||||
<col width="120" />
|
||||
<col width="100%" />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>헤어 컬러</th>
|
||||
<td>
|
||||
<UserFaceDetailInfoTable>
|
||||
<colgroup>
|
||||
<col width="120" />
|
||||
<col width="100%" />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>RED</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[39]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>GREEN</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[40]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>BLUE</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[41]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>CONTRAST</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[42]}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</UserFaceDetailInfoTable>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</UserFaceInfoTable>
|
||||
<UserFaceInfoTable $maxwidth="600px">
|
||||
<colgroup>
|
||||
<col width="120" />
|
||||
<col width="100%" />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>눈썹 컬러</th>
|
||||
<td>
|
||||
<UserFaceDetailInfoTable>
|
||||
<colgroup>
|
||||
<col width="120" />
|
||||
<col width="100%" />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>RED</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[43]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>GREEN</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[44]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>BLUE</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[45]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>ALPHA</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[46]}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</UserFaceDetailInfoTable>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</UserFaceInfoTable>
|
||||
<UserFaceInfoTable $maxwidth="600px">
|
||||
<colgroup>
|
||||
<col width="120" />
|
||||
<col width="100%" />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>홍채 컬러</th>
|
||||
<td>
|
||||
<UserFaceDetailInfoTable>
|
||||
<colgroup>
|
||||
<col width="120" />
|
||||
<col width="100%" />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>RED</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[47]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>GREEN</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[48]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>BLUE</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[49]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>ALPHA</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[50]}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</UserFaceDetailInfoTable>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
</tbody>
|
||||
</UserFaceInfoTable>
|
||||
{/*
|
||||
|
||||
|
||||
|
||||
<UserFaceInfoTable $maxwidth="600">
|
||||
<colgroup>
|
||||
<col width="120" />
|
||||
<col width="40%" />
|
||||
<col width="60%" />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>홍채 컬러</th>
|
||||
<tr>
|
||||
<th>RED</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[47]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>GREEN</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[48]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>BLUE</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[49]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>ALPHA</th>
|
||||
<td>{dataList && dataList.avatar_info.facesCustomizing[50]}</td>
|
||||
</tr>
|
||||
</tr>
|
||||
</tbody>
|
||||
</UserFaceInfoTable>
|
||||
*/}
|
||||
</AvatarWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserAvatarInfo;
|
||||
|
||||
const Notice = styled.span`
|
||||
font-size: 13px;
|
||||
font-weight: 300;
|
||||
margin-bottom: 10px;
|
||||
display: block;
|
||||
`;
|
||||
|
||||
const UserInfoTable = styled.table`
|
||||
width: 100%;
|
||||
max-width: ${props => props.$maxwidth || 'auto'};
|
||||
font-size: 13px;
|
||||
border-radius: 15px;
|
||||
overflow: hidden;
|
||||
tr:first-child {
|
||||
th,
|
||||
td {
|
||||
border-top: 0;
|
||||
}
|
||||
}
|
||||
th,
|
||||
td {
|
||||
height: 36px;
|
||||
vertical-align: middle;
|
||||
border-top: 1px solid #d9d9d9;
|
||||
}
|
||||
th {
|
||||
width: 120px;
|
||||
background: #888;
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
}
|
||||
td {
|
||||
background: #fff;
|
||||
padding: 0 20px;
|
||||
}
|
||||
`;
|
||||
|
||||
/*
|
||||
width: 100%;
|
||||
max-width: ${props => props.$maxwidth || 'auto'};
|
||||
*/
|
||||
|
||||
|
||||
const UserFaceInfoTable = styled.table`
|
||||
|
||||
max-width: ${props => props.$maxwidth || 'auto'};
|
||||
font-size: 13px;
|
||||
border-radius: 15px;
|
||||
overflow: hidden;
|
||||
tr:first-child {
|
||||
th,
|
||||
td {
|
||||
border-top: 0;
|
||||
}
|
||||
}
|
||||
th {
|
||||
height: 36px;
|
||||
vertical-align: middle;
|
||||
border-right: 1px solid #d9d9d9;
|
||||
border-bottom: 1px solid #d9d9d9;
|
||||
width: 120px;
|
||||
background: #888;
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
const UserFaceDetailInfoTable = styled.table`
|
||||
|
||||
max-width: ${props => props.$maxwidth || 'auto'};
|
||||
font-size: 13px;
|
||||
border-right-radius: 15px;
|
||||
overflow: hidden;
|
||||
tr:first-child {
|
||||
th,
|
||||
td {
|
||||
border-top: 0;
|
||||
}
|
||||
}
|
||||
tr:last-child {
|
||||
td {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
th {
|
||||
height: 36px;
|
||||
vertical-align: middle;
|
||||
border-right: 1px solid #d9d9d9;
|
||||
border-bottom: 1px solid #d9d9d9;
|
||||
width: 120px;
|
||||
background: #888;
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
}
|
||||
td {
|
||||
vertical-align: middle;
|
||||
border-bottom: 1px solid #d9d9d9;
|
||||
background: #fff;
|
||||
padding: 0 20px;
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
|
||||
const AvatarWrapper = styled.div`
|
||||
${UserInfoTable}:first-child {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
${UserFaceInfoTable} {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
`;
|
||||
130
src/components/DataManage/UserClaimInfo.js
Normal file
130
src/components/DataManage/UserClaimInfo.js
Normal file
@@ -0,0 +1,130 @@
|
||||
import { Fragment } from 'react';
|
||||
|
||||
import styled from 'styled-components';
|
||||
|
||||
const UserClaimInfo = () => {
|
||||
const ListData = [
|
||||
{ count: '1', state1: '수령', receipt1: '2023-08-11 09:00', state2: '수령', receipt2: '2023-08-11 09:00' },
|
||||
{ count: '2', state1: '수령가능', receipt1: '', state2: '수령가능', receipt2: '' },
|
||||
{ count: '3', state1: '수령불가', receipt1: '', state2: '수령불가', receipt2: '' },
|
||||
];
|
||||
return (
|
||||
<>
|
||||
<NoContent>진행중인 클레임이 없습니다.</NoContent>
|
||||
<UserTableWrapper>
|
||||
<ClaimTable>
|
||||
<colgroup>
|
||||
<col width="80" />
|
||||
<col width="170" />
|
||||
<col width="170" />
|
||||
<col width="170" />
|
||||
<col width="170" />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>분류</th>
|
||||
<th colSpan="2">everyone</th>
|
||||
<th colSpan="2">member</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>회차</th>
|
||||
<th>상태</th>
|
||||
<th>수령시간</th>
|
||||
<th>상태</th>
|
||||
<th>수령시간</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{ListData.map((el, idx) => {
|
||||
return (
|
||||
<Fragment key={idx}>
|
||||
<tr>
|
||||
<td>{el.count}</td>
|
||||
<td>{el.state1}</td>
|
||||
<td>{el.receipt1}</td>
|
||||
<td>{el.state2}</td>
|
||||
<td>{el.receipt2}</td>
|
||||
</tr>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</ClaimTable>
|
||||
</UserTableWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserClaimInfo;
|
||||
|
||||
const UserDefaultTable = styled.table`
|
||||
border: 1px solid #e8eaec;
|
||||
border-top: 1px solid #000;
|
||||
font-size: 14px;
|
||||
margin-bottom: 40px;
|
||||
th {
|
||||
background: #efefef;
|
||||
font-weight: 700;
|
||||
}
|
||||
th,
|
||||
td {
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
border-left: 1px solid #e8eaec;
|
||||
vertical-align: middle;
|
||||
}
|
||||
td {
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #e8eaec;
|
||||
word-break: break-all;
|
||||
}
|
||||
button {
|
||||
height: 24px;
|
||||
font-size: 13px;
|
||||
}
|
||||
`;
|
||||
|
||||
const QuestTable = styled(UserDefaultTable)`
|
||||
tbody {
|
||||
td {
|
||||
padding: 9px 12px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const UserTableWrapper = styled.div`
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
margin-bottom: 40px;
|
||||
${UserDefaultTable}, ${QuestTable} {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
&::-webkit-scrollbar {
|
||||
height: 4px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #666666;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #d9d9d9;
|
||||
}
|
||||
`;
|
||||
|
||||
const NoContent = styled.div`
|
||||
padding: 70px;
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
`;
|
||||
|
||||
const ClaimTable = styled(UserDefaultTable)`
|
||||
thead {
|
||||
th {
|
||||
padding: 9px 12px;
|
||||
}
|
||||
tr:first-child {
|
||||
th {
|
||||
background: #d9d9d9;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
186
src/components/DataManage/UserDefaultInfo.js
Normal file
186
src/components/DataManage/UserDefaultInfo.js
Normal file
@@ -0,0 +1,186 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import styled from 'styled-components';
|
||||
import Profile from '../../assets/img/datamanage/img-profile.png';
|
||||
import NicknameChangeModal from '../../components/DataManage/NicknameChangeModal';
|
||||
import EditIcon from '../../assets/img/icon/icon-edit.png';
|
||||
import { UserChangeAdminLevel, UserInfoView } from '../../apis/Users';
|
||||
import { SelectInput } from '../../styles/Components';
|
||||
import { adminLevelType, authType, modalTypes } from '../../assets/data';
|
||||
import DynamicModal from '../common/modal/DynamicModal';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { authList } from '../../store/authList';
|
||||
import { convertKTC } from '../../utils';
|
||||
import { EditButton, ProfileWrapper, UserDefault, UserInfoTable } from '../../styles/ModuleComponents';
|
||||
import { TableSkeleton } from '../Skeleton/TableSkeleton';
|
||||
import { UserInfoSkeleton } from '../Skeleton/UserInfoSkeleton';
|
||||
|
||||
const UserDefaultInfo = ({ userInfo }) => {
|
||||
const { t } = useTranslation();
|
||||
const authInfo = useRecoilValue(authList);
|
||||
|
||||
const [pwPop, setPwPop] = useState('hidden');
|
||||
const [gmModal, setGmModal] = useState('hidden');
|
||||
const [dataList, setDataList] = useState({});
|
||||
const [adminLevel, setAdminLevel] = useState('0');
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const handleClick = () => {
|
||||
if (pwPop === 'hidden') setPwPop('view');
|
||||
else setPwPop('hidden');
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, [userInfo]);
|
||||
|
||||
const fetchData = async () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
await UserInfoView(token, userInfo.guid).then(data => {
|
||||
setDataList(data);
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const handleGMChange = (e) =>{
|
||||
setAdminLevel(e.target.value);
|
||||
setGmModal('view');
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
let params = {};
|
||||
params.guid = userInfo.guid;
|
||||
params.admin_level = adminLevel;
|
||||
|
||||
await UserChangeAdminLevel(token, params);
|
||||
|
||||
handleCancel();
|
||||
await fetchData();
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
setGmModal('hidden');
|
||||
}
|
||||
|
||||
return (
|
||||
loading ? <UserInfoSkeleton /> :
|
||||
<>
|
||||
<div>
|
||||
<UserDefault>
|
||||
<ProfileWrapper>
|
||||
<img src={Profile} alt="" />
|
||||
</ProfileWrapper>
|
||||
<UserInfoTable $maxwidth="530px">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>AID(GUID)</th>
|
||||
<td>{dataList.user_info && dataList.user_info.aid}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>계정 ID</th>
|
||||
<td>{dataList.user_info && dataList.user_info.user_id}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>국가</th>
|
||||
<td>{dataList.user_info && dataList.user_info.nation}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>멤버십</th>
|
||||
<td>{dataList.user_info && dataList.user_info.membership}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>친구 추천코드</th>
|
||||
<td>{dataList.user_info && dataList.user_info.friend_code}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>계정 생성일</th>
|
||||
<td>
|
||||
{dataList.user_info && convertKTC(dataList.user_info.create_dt)}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>최근 접속일자</th>
|
||||
<td>
|
||||
{/*{dataList.user_info && String(new Date(new Date(dataList.user_info.access_dt).setHours(new Date(dataList.user_info.access_dt).getHours() + 9)).toLocaleString())}*/}
|
||||
{dataList.user_info && convertKTC(dataList.user_info.access_dt)}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>최근 종료일자</th>
|
||||
<td>{dataList.user_info && convertKTC(dataList.user_info.end_dt)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>전자지갑 URL</th>
|
||||
<td>
|
||||
<Link>{dataList.user_info && dataList.user_info.wallet_url}</Link>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>GM권한</th>
|
||||
<td>
|
||||
<SelectInput value={dataList.user_info && dataList.user_info.admin_level} onChange={(e) => handleGMChange(e)} disabled={authInfo.auth_list && !authInfo.auth_list.some(auth => auth.id === authType.userSearchUpdate)} >
|
||||
{adminLevelType.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</UserInfoTable>
|
||||
</UserDefault>
|
||||
<UserInfoTable $maxwidth="720px">
|
||||
<colgroup>
|
||||
<col />
|
||||
<col />
|
||||
<col width="120" />
|
||||
<col />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>캐릭터 아바타명</th>
|
||||
<td colSpan="3">
|
||||
{dataList.char_info && dataList.char_info.character_name}
|
||||
<EditButton
|
||||
hidden={true}
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
handleClick();
|
||||
}}></EditButton>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>시즌 패스 레벨</th>
|
||||
<td colSpan="3">{dataList.char_info && dataList.char_info.level}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>골드</th>
|
||||
<td>{dataList.char_info && dataList.char_info.gold_cali}</td>
|
||||
<th>사파이어</th>
|
||||
<td>{dataList.char_info && dataList.char_info.blue_cali}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>칼리움</th>
|
||||
<td>{dataList.char_info && dataList.char_info.red_cali}</td>
|
||||
<th>루비</th>
|
||||
<td>{dataList.char_info && dataList.char_info.black_cali}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</UserInfoTable>
|
||||
</div>
|
||||
<NicknameChangeModal pwPop={pwPop} handleClick={handleClick} dataList={dataList} />
|
||||
<DynamicModal
|
||||
modalType={modalTypes.childOkCancel}
|
||||
view={gmModal}
|
||||
modalText={t('USER_GM_CHANGE')}
|
||||
handleCancel={handleCancel}
|
||||
handleSubmit={handleSubmit}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default UserDefaultInfo;
|
||||
125
src/components/DataManage/UserDressInfo.js
Normal file
125
src/components/DataManage/UserDressInfo.js
Normal file
@@ -0,0 +1,125 @@
|
||||
import styled from 'styled-components';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { UserClothView } from '../../apis/Users';
|
||||
import { TableSkeleton } from '../Skeleton/TableSkeleton';
|
||||
|
||||
const UserDressInfo = ({ userInfo }) => {
|
||||
const [dataList, setDataList] = useState();
|
||||
const [rowData, setRowData] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setRowData([
|
||||
{ slotType : "상의", smallType: '셔츠', itemNo : dataList?.cloth_info?.cloth_shirt?.cloth || 0, itemName: dataList?.cloth_info?.cloth_shirt?.clothName || null},
|
||||
{ slotType : "상의", smallType: '드레스', itemNo : dataList?.cloth_info?.cloth_dress?.cloth || 0, itemName: dataList?.cloth_info?.cloth_dress?.clothName || null},
|
||||
{ slotType : "외투", smallType: '외투', itemNo : dataList?.cloth_info?.cloth_outer?.cloth || 0, itemName: dataList?.cloth_info?.cloth_outer?.clothName || null},
|
||||
{ slotType : "하의", smallType: '바지', itemNo : dataList?.cloth_info?.cloth_pants?.cloth || 0, itemName: dataList?.cloth_info?.cloth_pants?.clothName || null},
|
||||
{ slotType : "장갑", smallType: '장갑', itemNo : dataList?.cloth_info?.cloth_gloves?.cloth || 0, itemName: dataList?.cloth_info?.cloth_gloves?.clothName || null},
|
||||
{ slotType : "장갑", smallType: '반지', itemNo : dataList?.cloth_info?.cloth_ring?.cloth || 0, itemName: dataList?.cloth_info?.cloth_ring?.clothName || null},
|
||||
{ slotType : "장갑", smallType: '팔찌', itemNo : dataList?.cloth_info?.cloth_bracelet?.cloth || 0, itemName: dataList?.cloth_info?.cloth_bracelet?.clothName || null},
|
||||
{ slotType : "가방", smallType: '가방', itemNo : dataList?.cloth_info?.cloth_bag?.cloth || 0, itemName: dataList?.cloth_info?.cloth_bag?.clothName || null},
|
||||
{ slotType : "가방", smallType: '배낭', itemNo : dataList?.cloth_info?.cloth_backpack?.cloth || 0, itemName: dataList?.cloth_info?.cloth_backpack?.clothName || null},
|
||||
{ slotType : "머리 장식", smallType: '모자', itemNo : dataList?.cloth_info?.cloth_cap?.cloth || 0, itemName: dataList?.cloth_info?.cloth_cap?.clothName || null},
|
||||
{ slotType : "얼굴 장식", smallType: '얼굴 장식', itemNo : dataList?.cloth_info?.cloth_mask?.cloth || 0, itemName: dataList?.cloth_info?.cloth_mask?.clothName || null},
|
||||
{ slotType : "얼굴 장식", smallType: '안경', itemNo : dataList?.cloth_info?.cloth_glasses?.cloth || 0, itemName: dataList?.cloth_info?.cloth_glasses?.clothName || null},
|
||||
{ slotType : "귀걸이", smallType: '귀걸이', itemNo : dataList?.cloth_info?.cloth_earring?.cloth || 0, itemName: dataList?.cloth_info?.cloth_earring?.clothName || null},
|
||||
{ slotType : "목걸이", smallType: '목걸이', itemNo : dataList?.cloth_info?.cloth_necklace?.cloth || 0, itemName: dataList?.cloth_info?.cloth_necklace?.clothName || null},
|
||||
{ slotType : "신발", smallType: '신발', itemNo : dataList?.cloth_info?.cloth_shoes?.cloth || 0, itemName: dataList?.cloth_info?.cloth_shoes?.clothName || null},
|
||||
{ slotType : "양말", smallType: '양말', itemNo : dataList?.cloth_info?.cloth_socks?.cloth || 0, itemName: dataList?.cloth_info?.cloth_socks?.clothName || null},
|
||||
{ slotType : "양말", smallType: '발찌', itemNo : dataList?.cloth_info?.cloth_anklet?.cloth || 0, itemName: dataList?.cloth_info?.cloth_anklet?.clothName || null}
|
||||
])
|
||||
}, [dataList])
|
||||
|
||||
const fetchData = async () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
await UserClothView(token, userInfo.guid).then(data => {
|
||||
setDataList(data);
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const groupedData = rowData.reduce((acc, el) => {
|
||||
const key = el.slotType;
|
||||
if (!acc[key]) {
|
||||
acc[key] = [];
|
||||
}
|
||||
acc[key].push(el);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return (
|
||||
loading ? <TableSkeleton width='30%' count={15} /> :
|
||||
<>
|
||||
<DressWrapper>
|
||||
<UserInfoTable $maxwidth="670px">
|
||||
<colgroup>
|
||||
<col width="100" />
|
||||
<col width="120" />
|
||||
<col width="30%" />
|
||||
<col width="50%" />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
{Object.keys(groupedData).map((key) => {
|
||||
const rows = groupedData[key];
|
||||
return rows.map((el, idx) => (
|
||||
<tr key={`${key}-${idx}`}>
|
||||
{idx === 0 && (
|
||||
<th rowSpan={rows.length}>{el.slotType}</th>
|
||||
)}
|
||||
<th>{el.smallType}</th>
|
||||
<td>{el.itemNo}</td>
|
||||
<td>{el.itemName}</td>
|
||||
</tr>
|
||||
));
|
||||
})}
|
||||
</tbody>
|
||||
</UserInfoTable>
|
||||
</DressWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserDressInfo;
|
||||
|
||||
const UserInfoTable = styled.table`
|
||||
width: 100%;
|
||||
max-width: ${props => props.$maxwidth || 'auto'};
|
||||
font-size: 13px;
|
||||
border-radius: 15px;
|
||||
overflow: hidden;
|
||||
tr:first-child {
|
||||
th,
|
||||
td {
|
||||
border-top: 0;
|
||||
}
|
||||
}
|
||||
th,
|
||||
td {
|
||||
height: 36px;
|
||||
vertical-align: middle;
|
||||
border-top: 1px solid #d9d9d9;
|
||||
border-right: 1px solid #d9d9d9;
|
||||
}
|
||||
th {
|
||||
width: 120px;
|
||||
background: #888;
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
}
|
||||
td {
|
||||
background: #fff;
|
||||
padding: 0 20px;
|
||||
}
|
||||
`;
|
||||
|
||||
const DressWrapper = styled.div`
|
||||
${UserInfoTable} {
|
||||
td {
|
||||
border-left: 1px solid #d9d9d9;
|
||||
}
|
||||
}
|
||||
`;
|
||||
194
src/components/DataManage/UserFriendInfo.js
Normal file
194
src/components/DataManage/UserFriendInfo.js
Normal file
@@ -0,0 +1,194 @@
|
||||
import { useState, Fragment, useEffect } from 'react';
|
||||
import { SelectInput } from '../../styles/Components';
|
||||
import { UserTableWrapper, SelectWrapper, RequestTabWrapper, RequestTab, UserDefaultTable } from '../../styles/ModuleComponents';
|
||||
import { UserFriendListView } from '../../apis';
|
||||
import { convertKTC } from '../../utils';
|
||||
import { TableSkeleton } from '../Skeleton/TableSkeleton';
|
||||
|
||||
const UserFriendInfo = ({ userInfo }) => {
|
||||
const [activeSection, setActiveSection] = useState('list');
|
||||
const [activeRequest, setActiveRequest] = useState('received');
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const [dataList, setDataList] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
const fetchData = async () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
await UserFriendListView(token, userInfo.guid).then(data => {
|
||||
setDataList(data);
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
loading ? <TableSkeleton /> :
|
||||
<>
|
||||
<SelectWrapper>
|
||||
<SelectInput
|
||||
onChange={e => {
|
||||
setActiveSection(e.target.value);
|
||||
}}>
|
||||
<option value="list">친구 목록</option>
|
||||
<option value="request">친구 요청</option>
|
||||
<option value="block">차단 목록</option>
|
||||
</SelectInput>
|
||||
{activeSection === 'request' && (
|
||||
<RequestTabWrapper>
|
||||
<RequestTab
|
||||
$state={activeRequest === 'received' ? 'active' : 'inactive'}
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
setActiveRequest('received');
|
||||
}}>
|
||||
받은 요청
|
||||
</RequestTab>
|
||||
<RequestTab
|
||||
$state={activeRequest === 'sended' ? 'active' : 'inactive'}
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
setActiveRequest('sended');
|
||||
}}>
|
||||
보낸 요청
|
||||
</RequestTab>
|
||||
</RequestTabWrapper>
|
||||
)}
|
||||
</SelectWrapper>
|
||||
{activeSection === 'list' && (
|
||||
<>
|
||||
<UserTableWrapper>
|
||||
<UserDefaultTable>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="70">번호</th>
|
||||
<th width="200">친구 닉네임</th>
|
||||
<th width="300">친구 GUID</th>
|
||||
<th width="100">국가(언어)</th>
|
||||
<th width="160">친구 수락일</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{dataList && dataList.friend_list &&
|
||||
dataList.friend_list.map((data, idx) => (
|
||||
<Fragment key={idx}>
|
||||
<tr>
|
||||
<td>{data.row_num}</td>
|
||||
<td>{data.friend_name}</td>
|
||||
<td>{data.friend_guid}</td>
|
||||
<td>{data.language}</td>
|
||||
<td>{data.receive_dt && convertKTC(data.receive_dt)}</td>
|
||||
</tr>
|
||||
</Fragment>
|
||||
))}
|
||||
</tbody>
|
||||
</UserDefaultTable>
|
||||
</UserTableWrapper>
|
||||
</>
|
||||
)}
|
||||
{activeSection === 'request' && (
|
||||
<>
|
||||
{activeRequest === 'received' && (
|
||||
<>
|
||||
<UserDefaultTable>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="70">번호</th>
|
||||
<th width="200">요청자 닉네임</th>
|
||||
<th width="300">요청자 GUID</th>
|
||||
<th width="100">국가(언어)</th>
|
||||
<th width="160">받은 일자</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{dataList && dataList.friend_receive_list && dataList.friend_receive_list.map((data, idx) => {
|
||||
return (
|
||||
<Fragment key={idx}>
|
||||
<tr>
|
||||
<td>{data.row_num}</td>
|
||||
<td>{data.friend_name}</td>
|
||||
<td>{data.friend_guid}</td>
|
||||
<td>{data.language}</td>
|
||||
<td>{data.receive_dt && convertKTC(data.receive_dt)}</td>
|
||||
</tr>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</UserDefaultTable>
|
||||
</>
|
||||
)}
|
||||
{activeRequest === 'sended' && (
|
||||
<>
|
||||
<UserTableWrapper>
|
||||
<UserDefaultTable>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="70">번호</th>
|
||||
<th width="200">요청 대상자 닉네임</th>
|
||||
<th width="300">요청 대상자 GUID</th>
|
||||
<th width="100">국가(언어)</th>
|
||||
<th width="160">요청일</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{dataList && dataList.friend_send_list && dataList.friend_send_list.map((data, idx) => {
|
||||
return (
|
||||
<Fragment key={idx}>
|
||||
<tr>
|
||||
<td>{data.row_num}</td>
|
||||
<td>{data.friend_name}</td>
|
||||
<td>{data.friend_guid}</td>
|
||||
<td>{data.language}</td>
|
||||
<td>{data.receive_dt && convertKTC(data.receive_dt)}</td>
|
||||
</tr>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</UserDefaultTable>
|
||||
</UserTableWrapper>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{activeSection === 'block' && (
|
||||
<>
|
||||
<UserTableWrapper>
|
||||
<UserDefaultTable>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="70">번호</th>
|
||||
<th width="200">차단 닉네임</th>
|
||||
<th width="300">차단 대상 GUID</th>
|
||||
<th width="100">국가(언어)</th>
|
||||
<th width="160">차단 일자</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{dataList && dataList.friend_block_list && dataList.friend_block_list.map((data, idx) => {
|
||||
return (
|
||||
<Fragment key={idx}>
|
||||
<tr>
|
||||
<td>{data.row_num}</td>
|
||||
<td>{data.friend_name}</td>
|
||||
<td>{data.friend_guid}</td>
|
||||
<td>{data.language}</td>
|
||||
<td>{data.receive_dt && convertKTC(data.receive_dt)}</td>
|
||||
</tr>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</UserDefaultTable>
|
||||
</UserTableWrapper>
|
||||
</>
|
||||
)}
|
||||
{/*{loading && <Loading/>}*/}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserFriendInfo;
|
||||
363
src/components/DataManage/UserInventoryInfo.js
Normal file
363
src/components/DataManage/UserInventoryInfo.js
Normal file
@@ -0,0 +1,363 @@
|
||||
import styled from 'styled-components';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { UserInventoryItemDelete, UserInventoryView } from '../../apis/Users';
|
||||
import Button from '../common/button/Button';
|
||||
import ConfirmModal from '../common/modal/ConfirmModal';
|
||||
import CompletedModal from '../common/modal/CompletedModal';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { InputItem, SelectInput, TextInput } from '../../styles/Components';
|
||||
import CustomConfirmModal from '../common/modal/CustomConfirmModal';
|
||||
import { ivenTabType } from '../../assets/data';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { authList } from '../../store/authList';
|
||||
import { TableSkeleton } from '../Skeleton/TableSkeleton';
|
||||
import { InfoSubTitle, UserDefaultTable, UserTableWrapper } from '../../styles/ModuleComponents';
|
||||
|
||||
const UserInventoryInfo = ({ userInfo }) => {
|
||||
const { t } = useTranslation();
|
||||
const authInfo = useRecoilValue(authList);
|
||||
|
||||
const [dataList, setDataList] = useState();
|
||||
const [itemCount, setItemCount] = useState('');
|
||||
const [selected, setSelected] = useState({});
|
||||
const [itemUpdateCount, setItemUpdateCount] = useState('1');
|
||||
const [deleteConfirmModal, setDeleteConfirmModal] = useState('hidden');
|
||||
const [deleteSubmitModal, setDeleteSubmitModal] = useState('hidden');
|
||||
const [deleteCompleteModal, setDeleteCompleteModal] = useState('hidden');
|
||||
const [authDelete, setAuthDelete] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setAuthDelete(authInfo.auth_list.some(auth => auth.id === 35));
|
||||
}, [authInfo]);
|
||||
|
||||
const fetchData = async () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
await UserInventoryView(token, userInfo.guid).then(data => {
|
||||
setDataList(data);
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteConfirmModal = (data, type) =>{
|
||||
setItemCount(data.count);
|
||||
let row;
|
||||
switch(type){
|
||||
case ivenTabType.CLOTH:
|
||||
row = dataList.inventory_list.cloth.find(item => item.item_guid === data.item_guid);
|
||||
row.type = ivenTabType.CLOTH;
|
||||
break;
|
||||
case ivenTabType.PROP:
|
||||
row = dataList.inventory_list.prop.find(item => item.item_guid === data.item_guid);
|
||||
row.type = ivenTabType.PROP;
|
||||
break;
|
||||
case ivenTabType.BEAUTY:
|
||||
row = dataList.inventory_list.beauty.find(item => item.item_guid === data.item_guid);
|
||||
row.type = ivenTabType.BEAUTY;
|
||||
break;
|
||||
case ivenTabType.TATTOO:
|
||||
row = dataList.inventory_list.tattoo.find(item => item.item_guid === data.item_guid);
|
||||
row.type = ivenTabType.TATTOO;
|
||||
break;
|
||||
case ivenTabType.CURRENCY:
|
||||
row = dataList.inventory_list.currency.find(item => item.item_guid === data.item_guid);
|
||||
row.type = ivenTabType.CURRENCY;
|
||||
break;
|
||||
default:
|
||||
row = dataList.inventory_list.etc.find(item => item.item_guid === data.item_guid);
|
||||
row.type = ivenTabType.ETC;
|
||||
break;
|
||||
}
|
||||
setSelected(row);
|
||||
setDeleteConfirmModal('view');
|
||||
}
|
||||
|
||||
// 개수 입력 모달 hidden
|
||||
const handleDeleteConfirmClose = () => {
|
||||
setDeleteConfirmModal('hidden');
|
||||
setItemUpdateCount(1);
|
||||
setSelected({});
|
||||
}
|
||||
|
||||
// 삭제 모달 hidden
|
||||
const handleDeleteClose = () => {
|
||||
setDeleteSubmitModal('hidden');
|
||||
}
|
||||
|
||||
const handleDeleteConfirm = () => {
|
||||
setDeleteSubmitModal('view');
|
||||
}
|
||||
|
||||
// 삭제 처리
|
||||
const handleDeleteSubmit = async () => {
|
||||
let params = {}
|
||||
params.guid = userInfo.guid;
|
||||
params.item_guid = selected.item_guid;
|
||||
params.current_cnt = selected.count;
|
||||
params.cnt = itemUpdateCount;
|
||||
const token = sessionStorage.getItem('token');
|
||||
const result = await UserInventoryItemDelete(token, params);
|
||||
if(result.result === "SUCCESS"){
|
||||
//성공시 아이템 삭제 or 개수 차감
|
||||
if(selected.count <= itemUpdateCount){
|
||||
switch (selected.type) {
|
||||
case ivenTabType.CLOTH:
|
||||
dataList.inventory_list.cloth.splice(dataList.inventory_list.cloth.findIndex(item => item.item_guid === selected.item_guid), 1);
|
||||
break;
|
||||
case ivenTabType.PROP:
|
||||
dataList.inventory_list.prop.splice(dataList.inventory_list.prop.findIndex(item => item.item_guid === selected.item_guid), 1);
|
||||
break;
|
||||
case ivenTabType.BEAUTY:
|
||||
dataList.inventory_list.beauty.splice(dataList.inventory_list.beauty.findIndex(item => item.item_guid === selected.item_guid), 1);
|
||||
break;
|
||||
case ivenTabType.TATTOO:
|
||||
dataList.inventory_list.tattoo.splice(dataList.inventory_list.tattoo.findIndex(item => item.item_guid === selected.item_guid), 1);
|
||||
break;
|
||||
case ivenTabType.CURRENCY:
|
||||
dataList.inventory_list.currency.splice(dataList.inventory_list.currency.findIndex(item => item.item_guid === selected.item_guid), 1);
|
||||
break;
|
||||
default:
|
||||
dataList.inventory_list.etc.splice(dataList.inventory_list.etc.findIndex(item => item.item_guid === selected.item_guid), 1);
|
||||
break;
|
||||
}
|
||||
}else{
|
||||
selected.count = selected.count - itemUpdateCount;
|
||||
}
|
||||
}
|
||||
handleDeleteClose();
|
||||
handleDeleteConfirmClose();
|
||||
setDeleteCompleteModal('view')
|
||||
}
|
||||
|
||||
const handleDeleteComplete = () => {
|
||||
setDeleteCompleteModal('hidden');
|
||||
}
|
||||
|
||||
const handleItemCount = e => {
|
||||
if (e.target.value === '0' || e.target.value === '-0') {
|
||||
setItemUpdateCount('1');
|
||||
e.target.value = '1';
|
||||
} else if (e.target.value < 0) {
|
||||
let plusNum = Math.abs(e.target.value);
|
||||
setItemUpdateCount(plusNum);
|
||||
} else if(e.target.value > itemCount){
|
||||
alert(t('DEL_COUNT_CHECK'));
|
||||
setItemUpdateCount(itemCount);
|
||||
} else {
|
||||
setItemUpdateCount(e.target.value);
|
||||
}
|
||||
};
|
||||
|
||||
const ConfirmChild = () => {
|
||||
return(
|
||||
<InputItem>
|
||||
<p>{t('DEL_COUNT_CONFIRM', {count: itemCount})}</p>
|
||||
<TextInput placeholder="수량" type="number" value={itemUpdateCount} onChange={e => handleItemCount(e)} width="200px" />
|
||||
</InputItem>
|
||||
);
|
||||
}
|
||||
|
||||
const InvenTable = ({invenList, title, type}) => {
|
||||
return (
|
||||
<>
|
||||
<InfoSubTitle>{title}</InfoSubTitle>
|
||||
<UserTableWrapper>
|
||||
<UserDefaultTable>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="80">no.</th>
|
||||
<th width="120">ID</th>
|
||||
<th width="50%">아이템</th>
|
||||
<th width="100">보유개수</th>
|
||||
<th width="60">삭제</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{invenList.map((el, idx) => {
|
||||
return (
|
||||
<tr key={idx}>
|
||||
<td>{idx + 1}</td>
|
||||
<td>{el.item_id}</td>
|
||||
<td>{el.item_name}</td>
|
||||
<td>{el.count}</td>
|
||||
<td>
|
||||
<Button theme={authDelete ? "line" : "disable"} id={el.item_guid} name="single" text="삭제" handleClick={e => {handleDeleteConfirmModal(el, type)}} disabled={!authDelete}/>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</UserDefaultTable>
|
||||
</UserTableWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
loading ? <TableSkeleton count={15}/> :
|
||||
<>
|
||||
{dataList && dataList.inventory_list && <InvenTable title="의상 탭" type={ivenTabType.CLOTH} invenList={dataList.inventory_list.cloth} />}
|
||||
{dataList && dataList.inventory_list && <InvenTable title="소품 탭" type={ivenTabType.PROP} invenList={dataList.inventory_list.prop} />}
|
||||
{dataList && dataList.inventory_list && <InvenTable title="미용 탭" type={ivenTabType.BEAUTY} invenList={dataList.inventory_list.beauty} />}
|
||||
{dataList && dataList.inventory_list && <InvenTable title="타투 탭" type={ivenTabType.TATTOO} invenList={dataList.inventory_list.tattoo} />}
|
||||
{dataList && dataList.inventory_list && <InvenTable title="재화 탭" type={ivenTabType.CURRENCY} invenList={dataList.inventory_list.currency} />}
|
||||
{dataList && dataList.inventory_list && <InvenTable title="기타 탭" type={ivenTabType.ETC} invenList={dataList.inventory_list.etc} />}
|
||||
<CustomConfirmModal
|
||||
ChildView={ConfirmChild}
|
||||
view={deleteConfirmModal}
|
||||
handleSubmit={handleDeleteConfirm}
|
||||
handleCancel={handleDeleteConfirmClose}
|
||||
handleClose={handleDeleteConfirmClose}
|
||||
/>
|
||||
<ConfirmModal
|
||||
modalText={t('DEL_CONFIRM')}
|
||||
view={deleteSubmitModal}
|
||||
handleSubmit={handleDeleteSubmit}
|
||||
handleCancel={handleDeleteClose}
|
||||
handleClose={handleDeleteClose}
|
||||
/>
|
||||
<CompletedModal
|
||||
view={deleteCompleteModal}
|
||||
modalText={t('DEL_COMPLETE')}
|
||||
handleComplete={handleDeleteComplete}
|
||||
/>
|
||||
{/* <InfoSubTitle>의상 탭</InfoSubTitle>
|
||||
<UserTableWrapper>
|
||||
<UserDefaultTable>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="80">no.</th>
|
||||
<th width="120">ID</th>
|
||||
<th width="50%">아이템</th>
|
||||
<th width="100">보유개수</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{dataList &&
|
||||
dataList.inventory_list.cloth.map((el, idx) => {
|
||||
return (
|
||||
<tr key={idx}>
|
||||
<td>{idx + 1}</td>
|
||||
<td>{el.item_id}</td>
|
||||
<td>{el.item_name}</td>
|
||||
<td>{el.count}</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</UserDefaultTable>
|
||||
</UserTableWrapper>
|
||||
<InfoSubTitle>소품 탭</InfoSubTitle>
|
||||
<UserTableWrapper>
|
||||
<UserDefaultTable>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="80">no.</th>
|
||||
<th width="120">ID</th>
|
||||
<th width="50%">아이템</th>
|
||||
<th width="100">보유개수</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{dataList &&
|
||||
dataList.inventory_list.prop.map((el, idx) => {
|
||||
return (
|
||||
<tr key={idx}>
|
||||
<td>{idx + 1}</td>
|
||||
<td>{el.item_id}</td>
|
||||
<td>{el.item_name}</td>
|
||||
<td>{el.count}</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</UserDefaultTable>
|
||||
</UserTableWrapper>
|
||||
<InfoSubTitle>미용 탭</InfoSubTitle>
|
||||
<UserTableWrapper>
|
||||
<UserDefaultTable>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="80">no.</th>
|
||||
<th width="120">ID</th>
|
||||
<th width="50%">아이템</th>
|
||||
<th width="100">보유개수</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{dataList &&
|
||||
dataList.inventory_list.beauty.map((el, idx) => {
|
||||
return (
|
||||
<tr key={idx}>
|
||||
<td>{idx + 1}</td>
|
||||
<td>{el.item_id}</td>
|
||||
<td>{el.item_name}</td>
|
||||
<td>{el.count}</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</UserDefaultTable>
|
||||
</UserTableWrapper>
|
||||
<InfoSubTitle>타투 탭</InfoSubTitle>
|
||||
<UserTableWrapper>
|
||||
<UserDefaultTable>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="80">no.</th>
|
||||
<th width="120">ID</th>
|
||||
<th width="50%">아이템</th>
|
||||
<th width="100">보유개수</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{dataList &&
|
||||
dataList.inventory_list.tattoo.map((el, idx) => {
|
||||
return (
|
||||
<tr key={idx}>
|
||||
<td>{idx + 1}</td>
|
||||
<td>{el.item_id}</td>
|
||||
<td>{el.item_name}</td>
|
||||
<td>{el.count}</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</UserDefaultTable>
|
||||
</UserTableWrapper>
|
||||
<InfoSubTitle>기타 탭</InfoSubTitle>
|
||||
<UserTableWrapper>
|
||||
<UserDefaultTable>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="80">슬롯 no.</th>
|
||||
<th width="320">ID</th>
|
||||
<th width="50%">아이템</th>
|
||||
<th width="100">보유개수</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{dataList &&
|
||||
dataList.inventory_list.etc.map((el, idx) => {
|
||||
return (
|
||||
<tr key={idx}>
|
||||
<td>{idx + 1}</td>
|
||||
<td>{el.item_id}</td>
|
||||
<td>{el.item_name}</td>
|
||||
<td>{el.count}</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</UserDefaultTable>
|
||||
</UserTableWrapper> */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserInventoryInfo;
|
||||
|
||||
398
src/components/DataManage/UserMailInfo.js
Normal file
398
src/components/DataManage/UserMailInfo.js
Normal file
@@ -0,0 +1,398 @@
|
||||
import { useState, Fragment, useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import styled from 'styled-components';
|
||||
|
||||
import CheckBox from '../../components/common/input/CheckBox';
|
||||
import MailDetailModal from '../../components/DataManage/MailDetailModal';
|
||||
import { SelectInput, TextInput } from '../../styles/Components';
|
||||
import { UserMailDelete, UserMailItemDelete, UserMailView } from '../../apis';
|
||||
import ConfirmModal from '../common/modal/ConfirmModal';
|
||||
import CompletedModal from '../common/modal/CompletedModal';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import CustomConfirmModal from '../common/modal/CustomConfirmModal';
|
||||
import DynamicModal from '../common/modal/DynamicModal';
|
||||
import { authType, ivenTabType } from '../../assets/data';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { authList } from '../../store/authList';
|
||||
import { convertKTC } from '../../utils';
|
||||
import { TableSkeleton } from '../Skeleton/TableSkeleton';
|
||||
|
||||
const UserMailInfo = ({ userInfo }) => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
const { t } = useTranslation();
|
||||
const authInfo = useRecoilValue(authList);
|
||||
|
||||
//데이터 리스트
|
||||
const [dataList, setDataList] = useState([]);
|
||||
// 받은 우편, 보낸 우편
|
||||
const [option, setOption] = useState('RECEIVE');
|
||||
// 상세 정보
|
||||
const [detail, setDetail] = useState({ title: '', content: '', item_list: [], mail_guid: '' });
|
||||
const [deleteSelected, setDeleteSelected] = useState({});
|
||||
const [itemUpdateCount, setItemUpdateCount] = useState('1');
|
||||
const [authDelete, setAuthDelete] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const [modalState, setModalState] = useState({
|
||||
detailModal: 'hidden',
|
||||
deleteItemModal: 'hidden',
|
||||
deleteSubmitModal: 'hidden',
|
||||
deleteCompleteModal: 'hidden',
|
||||
deleteItemCompleteModal: 'hidden'
|
||||
});
|
||||
|
||||
// 받은 우편, 보낸 우편 option 에 따른 data fetch
|
||||
useEffect(() => {
|
||||
fetchData(option);
|
||||
}, [option]);
|
||||
|
||||
useEffect(() => {
|
||||
setAuthDelete(authInfo.auth_list.some(auth => auth.id === authType.userSearchDelete));
|
||||
}, [authInfo]);
|
||||
|
||||
const fetchData = async option => {
|
||||
await UserMailView(token, userInfo.guid, option).then(data =>{
|
||||
setDataList(data);
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
// 상세 모달 value 세팅
|
||||
const handleDetail = (title, content, itmeList, mail_guid) => {
|
||||
setDetail({ title: title, content: content, item_list: itmeList, mail_guid: mail_guid });
|
||||
};
|
||||
|
||||
// 우편 아이템 삭제 개수 처리
|
||||
const handleItemCount = e => {
|
||||
if (e.target.value === '0' || e.target.value === '-0') {
|
||||
setItemUpdateCount('1');
|
||||
e.target.value = '1';
|
||||
} else if (e.target.value < 0) {
|
||||
let plusNum = Math.abs(e.target.value);
|
||||
setItemUpdateCount(plusNum);
|
||||
} else if(e.target.value > deleteSelected.count){
|
||||
alert(t('DEL_COUNT_CHECK'));
|
||||
setItemUpdateCount(deleteSelected.count);
|
||||
} else {
|
||||
setItemUpdateCount(e.target.value);
|
||||
}
|
||||
};
|
||||
|
||||
const handleModalView = (type) => {
|
||||
setModalState((prevState) => ({
|
||||
...prevState,
|
||||
[`${type}Modal`]: 'view',
|
||||
}));
|
||||
}
|
||||
|
||||
const handleModalClose = (type) => {
|
||||
setModalState((prevState) => ({
|
||||
...prevState,
|
||||
[`${type}Modal`]: 'hidden',
|
||||
}));
|
||||
}
|
||||
|
||||
const handleModalSubmit = async (type, param = null) => {
|
||||
let params;
|
||||
let result;
|
||||
switch (type) {
|
||||
case "detail":
|
||||
handleModalView('deleteSubmit');
|
||||
break;
|
||||
case "deleteItem":
|
||||
setDeleteSelected(param);
|
||||
handleModalView('deleteItem');
|
||||
break;
|
||||
case "deleteSubmit":
|
||||
params = {}
|
||||
params.type = option;
|
||||
params.guid = userInfo.guid;
|
||||
params.mail_guid = detail.mail_guid;
|
||||
|
||||
result = await UserMailDelete(token, params);
|
||||
|
||||
handleModalClose('deleteSubmit');
|
||||
handleModalView('deleteComplete');
|
||||
break;
|
||||
case "deleteItemSubmit":
|
||||
params = {}
|
||||
params.type = option;
|
||||
params.guid = userInfo.guid;
|
||||
params.mail_guid = detail.mail_guid;
|
||||
params.item_id = deleteSelected.item_id;
|
||||
params.parrent_count = deleteSelected.count;
|
||||
params.count = itemUpdateCount;
|
||||
|
||||
result = await UserMailItemDelete(token, params);
|
||||
if(result.result === "SUCCESS"){
|
||||
if(deleteSelected.count <= itemUpdateCount){
|
||||
const item_idx = detail.item_list.findIndex(item => item.item_id === deleteSelected.item_id);
|
||||
if(item_idx >= 0) {
|
||||
detail.item_list.splice(item_idx, 1);
|
||||
}
|
||||
}else{
|
||||
deleteSelected.count = deleteSelected.count - itemUpdateCount;
|
||||
}
|
||||
}
|
||||
handleModalClose('deleteItem');
|
||||
handleModalView('deleteItemComplete');
|
||||
break;
|
||||
case "deleteComplete":
|
||||
handleModalClose('deleteComplete');
|
||||
handleModalClose('detail');
|
||||
// const idx = dataList.mail_list.findIndex(mail => mail.mail_guid === detail.mail_guid);
|
||||
// if(idx >= 0) {
|
||||
// dataList.mail_list.splice(idx, 1);
|
||||
// }
|
||||
fetchData(option);
|
||||
break;
|
||||
case "deleteItemComplete":
|
||||
handleModalClose('deleteItemComplete');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const ConfirmChild = () => {
|
||||
return(
|
||||
<InputItem>
|
||||
<p>{t('DEL_COUNT_CONFIRM', {count: deleteSelected.count})}</p>
|
||||
<TextInput placeholder="수량" type="number" value={itemUpdateCount} onChange={e => handleItemCount(e)} width="200px" />
|
||||
</InputItem>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
loading ? <TableSkeleton count={10}/> :
|
||||
<>
|
||||
<SelectWrapper>
|
||||
<SelectInput
|
||||
value={option}
|
||||
onChange={e => {
|
||||
setOption(e.target.value);
|
||||
}}>
|
||||
<option value="RECEIVE">받은 우편</option>
|
||||
<option value="SEND">보낸 우편</option>
|
||||
</SelectInput>
|
||||
</SelectWrapper>
|
||||
{option === 'RECEIVE' && (
|
||||
<>
|
||||
{/* <CheckWrapper>
|
||||
<CheckBox id="viewDelReceiveMail" label="삭제 우편 보기" />
|
||||
</CheckWrapper> */}
|
||||
<UserTableWrapper>
|
||||
<UserDefaultTable>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="170">수신일자</th>
|
||||
<th width="200">발송자</th>
|
||||
<th width="300">우편 제목</th>
|
||||
<th width="80">상태</th>
|
||||
<th width="100">첨부 아이템</th>
|
||||
<th width="80">수령</th>
|
||||
<th width="80">시스템 우편</th>
|
||||
{/* <th width="170">수령 일자</th> */}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{dataList.mail_list &&
|
||||
dataList.mail_list.map((mail, idx) => {
|
||||
return (
|
||||
<tr key={idx}>
|
||||
<td>{convertKTC(mail.create_time)}</td>
|
||||
<td>{mail.sender_nickname}</td>
|
||||
<td>
|
||||
<MailLink
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
handleModalView('detail');
|
||||
handleDetail(mail.title, mail.content, mail.item_list, mail.mail_guid);
|
||||
}}>
|
||||
{mail.title}
|
||||
</MailLink>
|
||||
</td>
|
||||
<td>{mail.status === true ? '확인' : '미확인'}</td>
|
||||
<td>{mail.item_list.length > 0 ? 'Y' : 'N'}</td>
|
||||
<td>{mail.is_get_item === true ? '수령함' : '미수령함'}</td>
|
||||
<td>{mail.is_system_mail === true ? 'Y' : 'N'}</td>
|
||||
{/* <td>
|
||||
{mail.is_get_item_dt && String(new Date(new Date(mail.is_get_item_dt).setHours(new Date(mail.is_get_item_dt).getHours() + 9)).toLocaleString())}
|
||||
</td> */}
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</UserDefaultTable>
|
||||
</UserTableWrapper>
|
||||
</>
|
||||
)}
|
||||
{option === 'SEND' && (
|
||||
<>
|
||||
{/* <CheckWrapper>
|
||||
<CheckBox id="viewDelSendMail" label="삭제 우편 보기" />
|
||||
</CheckWrapper> */}
|
||||
<UserTableWrapper>
|
||||
<UserDefaultTable>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="170">발송 일자</th>
|
||||
<th width="200">수신자</th>
|
||||
<th width="300">우편 제목</th>
|
||||
<th width="120">첨부 아이템</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{dataList.mail_list &&
|
||||
dataList.mail_list.map((mail, idx) => {
|
||||
return (
|
||||
<tr key={idx}>
|
||||
<td>{convertKTC(mail.create_time)}</td>
|
||||
<td>{mail.receiver_nickname}</td>
|
||||
<td>
|
||||
<MailLink
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
handleModalView('detail');
|
||||
handleDetail(mail.title, mail.content, mail.item_list, mail.mail_guid);
|
||||
}}>
|
||||
{mail.title}
|
||||
</MailLink>
|
||||
</td>
|
||||
<td>{mail.item_list.length > 0 ? 'Y' : 'N'}</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</UserDefaultTable>
|
||||
</UserTableWrapper>
|
||||
</>
|
||||
)}
|
||||
{/*상세*/}
|
||||
<MailDetailModal
|
||||
mailModal={modalState.detailModal}
|
||||
handleClick={() => handleModalClose('detail')}
|
||||
setDetail={setDetail}
|
||||
content={detail}
|
||||
handleDelete={() => handleModalSubmit('detail')}
|
||||
handleItemDelete={(param) => handleModalSubmit('deleteItem', param)}
|
||||
authDelete={authDelete}
|
||||
/>
|
||||
{/*메일 삭제 모달*/}
|
||||
<ConfirmModal
|
||||
modalText={t('USER_MAIL_DEL_CONFIRM')}
|
||||
view={modalState.deleteSubmitModal}
|
||||
handleSubmit={() => handleModalSubmit('deleteSubmit')}
|
||||
handleCancel={() => handleModalClose('deleteSubmit')}
|
||||
handleClose={() => handleModalClose('deleteSubmit')}
|
||||
/>
|
||||
<CompletedModal
|
||||
view={modalState.deleteCompleteModal}
|
||||
modalText={t('DEL_COMPLETE')}
|
||||
handleComplete={() => handleModalSubmit('deleteComplete')}
|
||||
/>
|
||||
{/*메일 아이템 삭제 모달*/}
|
||||
<CustomConfirmModal
|
||||
ChildView={ConfirmChild}
|
||||
view={modalState.deleteItemModal}
|
||||
handleSubmit={() => handleModalSubmit('deleteItemSubmit')}
|
||||
handleCancel={() => handleModalClose('deleteItem')}
|
||||
handleClose={() => handleModalClose('deleteItem')}
|
||||
/>
|
||||
<CompletedModal
|
||||
view={modalState.deleteItemCompleteModal}
|
||||
modalText={t('DEL_ITEM_COMPLETE')}
|
||||
handleComplete={() => handleModalSubmit('deleteItemComplete')}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserMailInfo;
|
||||
|
||||
const UserDefaultTable = styled.table`
|
||||
border: 1px solid #e8eaec;
|
||||
border-top: 1px solid #000;
|
||||
font-size: 14px;
|
||||
margin-bottom: 40px;
|
||||
th {
|
||||
background: #efefef;
|
||||
font-weight: 700;
|
||||
}
|
||||
th,
|
||||
td {
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
border-left: 1px solid #e8eaec;
|
||||
vertical-align: middle;
|
||||
}
|
||||
td {
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #e8eaec;
|
||||
word-break: break-all;
|
||||
}
|
||||
button {
|
||||
height: 24px;
|
||||
font-size: 13px;
|
||||
}
|
||||
`;
|
||||
|
||||
const QuestTable = styled(UserDefaultTable)`
|
||||
tbody {
|
||||
td {
|
||||
padding: 9px 12px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const UserTableWrapper = styled.div`
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
margin-bottom: 40px;
|
||||
${UserDefaultTable}, ${QuestTable} {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
&::-webkit-scrollbar {
|
||||
height: 4px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #666666;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #d9d9d9;
|
||||
}
|
||||
`;
|
||||
|
||||
const MailLink = styled.div`
|
||||
color: #61a2d0;
|
||||
text-decoration: underline;
|
||||
`;
|
||||
|
||||
const SelectWrapper = styled.div`
|
||||
select {
|
||||
height: 30px;
|
||||
}
|
||||
margin-bottom: 10px;
|
||||
`;
|
||||
const CheckWrapper = styled.div`
|
||||
text-align: right;
|
||||
padding: 10px;
|
||||
margin-top: -40px;
|
||||
height: 30px;
|
||||
margin-bottom: 10px;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
`;
|
||||
|
||||
const InputItem = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center; /* 수평 중앙 정렬 */
|
||||
gap: 10px;
|
||||
${TextInput} {
|
||||
height: 35px;
|
||||
font-size: 14px;
|
||||
width: 100px;
|
||||
padding: 10px 20px;
|
||||
}
|
||||
`;
|
||||
70
src/components/DataManage/UserMyHomeInfo.js
Normal file
70
src/components/DataManage/UserMyHomeInfo.js
Normal file
@@ -0,0 +1,70 @@
|
||||
import styled from 'styled-components';
|
||||
import Button from '../common/button/Button';
|
||||
import { InfoSubTitle, UserDefaultTable, UserInfoTable, UserTableWrapper } from '../../styles/ModuleComponents';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { authList } from '../../store/authList';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { TableSkeleton } from '../Skeleton/TableSkeleton';
|
||||
import { UserInventoryView, UserMyhomeView } from '../../apis';
|
||||
|
||||
const UserMyHomeInfo = ({ userInfo }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [dataList, setDataList] = useState();
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
const fetchData = async () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
await UserMyhomeView(token, userInfo.guid).then(data => {
|
||||
setDataList(data.myhome_info);
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
loading ? <TableSkeleton count={15}/> :
|
||||
dataList &&
|
||||
<>
|
||||
<UserInfoTable $maxwidth="700px">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>마이 홈명</th>
|
||||
<td>{dataList.myhome_name}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</UserInfoTable>
|
||||
<InfoSubTitle top='30px'>배치 소품</InfoSubTitle>
|
||||
<UserTableWrapper>
|
||||
<UserDefaultTable>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="80">no.</th>
|
||||
<th width="120">아이템ID</th>
|
||||
<th width="50%">아이템명</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{dataList.prop_list && dataList.prop_list.map((el, idx) => {
|
||||
return (
|
||||
<tr key={idx}>
|
||||
<td>{idx + 1}</td>
|
||||
<td>{el.item_id}</td>
|
||||
<td>{el.item_name}</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</UserDefaultTable>
|
||||
</UserTableWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserMyHomeInfo;
|
||||
|
||||
|
||||
130
src/components/DataManage/UserQuestInfo.js
Normal file
130
src/components/DataManage/UserQuestInfo.js
Normal file
@@ -0,0 +1,130 @@
|
||||
import { useState, useEffect, Fragment } from 'react';
|
||||
|
||||
import styled from 'styled-components';
|
||||
import Button from '../../components/common/button/Button';
|
||||
import QuestDetailModal from '../../components/DataManage/QuestDetailModal';
|
||||
import { UserQuestView } from '../../apis/Users';
|
||||
import { convertKTC } from '../../utils';
|
||||
import { TableSkeleton } from '../Skeleton/TableSkeleton';
|
||||
|
||||
const UserQuestInfo = ({ userInfo }) => {
|
||||
const [detailPop, setDetailPop] = useState('hidden');
|
||||
const [dataList, setDataList] = useState({});
|
||||
const [detailQuest, setDetailQuest] = useState({});
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
const fetchData = async () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
await UserQuestView(token, userInfo.guid).then(data => {
|
||||
setDataList(data);
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const handleClick = data => {
|
||||
if (detailPop === 'hidden') {
|
||||
setDetailPop('view');
|
||||
setDetailQuest(data.detailQuest);
|
||||
} else setDetailPop('hidden');
|
||||
};
|
||||
|
||||
return (
|
||||
loading ? <TableSkeleton /> :
|
||||
<>
|
||||
<UserTableWrapper>
|
||||
<QuestTable>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="80">퀘스트 ID</th>
|
||||
<th width="40%">퀘스트명</th>
|
||||
<th width="100">타입</th>
|
||||
<th width="80">상태</th>
|
||||
<th width="150">수령 시간</th>
|
||||
<th width="150">완료 시간</th>
|
||||
<th width="80">상세보기</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{dataList.quest_list &&
|
||||
dataList.quest_list.map((el, index) => {
|
||||
return (
|
||||
<Fragment key={index}>
|
||||
<tr>
|
||||
<td>{el.quest_id}</td>
|
||||
<td>{el.quest_name}</td>
|
||||
<td>{el.quest_type}</td>
|
||||
<td>{el.status}</td>
|
||||
<td>{convertKTC(el.quest_assign_time, false)}</td>
|
||||
<td>{convertKTC(el.quest_complete_time, false)}</td>
|
||||
<td>
|
||||
<Button text="상세보기" theme="line" handleClick={() => handleClick(el)} />
|
||||
</td>
|
||||
</tr>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</QuestTable>
|
||||
</UserTableWrapper>
|
||||
<QuestDetailModal detailPop={detailPop} handleClick={handleClick} detailQuest={detailQuest} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default UserQuestInfo;
|
||||
|
||||
const UserDefaultTable = styled.table`
|
||||
border: 1px solid #e8eaec;
|
||||
border-top: 1px solid #000;
|
||||
font-size: 14px;
|
||||
margin-bottom: 40px;
|
||||
th {
|
||||
background: #efefef;
|
||||
font-weight: 700;
|
||||
}
|
||||
th,
|
||||
td {
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
border-left: 1px solid #e8eaec;
|
||||
vertical-align: middle;
|
||||
}
|
||||
td {
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #e8eaec;
|
||||
word-break: break-all;
|
||||
}
|
||||
button {
|
||||
height: 24px;
|
||||
font-size: 13px;
|
||||
}
|
||||
`;
|
||||
|
||||
const QuestTable = styled(UserDefaultTable)`
|
||||
tbody {
|
||||
td {
|
||||
padding: 9px 12px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const UserTableWrapper = styled.div`
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
margin-bottom: 40px;
|
||||
${UserDefaultTable}, ${QuestTable} {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
&::-webkit-scrollbar {
|
||||
height: 4px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #666666;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #d9d9d9;
|
||||
}
|
||||
`;
|
||||
57
src/components/DataManage/UserSearchBar.js
Normal file
57
src/components/DataManage/UserSearchBar.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import { styled } from 'styled-components';
|
||||
import Button from '../../components/common/button/Button';
|
||||
|
||||
import { FormWrapper, InputLabel, TextInput, SelectInput, BtnWrapper } from '../../styles/Components';
|
||||
|
||||
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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserSearchBar;
|
||||
|
||||
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;
|
||||
|
||||
${TextInput}, ${SelectInput} {
|
||||
height: 35px;
|
||||
}
|
||||
${TextInput} {
|
||||
padding: 0 10px;
|
||||
max-width: 400px;
|
||||
}
|
||||
`;
|
||||
115
src/components/DataManage/UserTattooInfo.js
Normal file
115
src/components/DataManage/UserTattooInfo.js
Normal file
@@ -0,0 +1,115 @@
|
||||
import { Fragment } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { UserTattooView } from '../../apis/Users';
|
||||
import { TableSkeleton } from '../Skeleton/TableSkeleton';
|
||||
|
||||
const UserTatttooInfo = ({ userInfo }) => {
|
||||
const [dataList, setDataList] = useState();
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
const fetchData = async () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
await UserTattooView(token, userInfo.guid).then(data => {
|
||||
setDataList(data);
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
loading ? <TableSkeleton count={10}/> :
|
||||
<>
|
||||
<UserTableWrapper>
|
||||
<UserDefaultTable>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="100">장착슬롯</th>
|
||||
<th width="150">타투 ID</th>
|
||||
<th width="200">타투명</th>
|
||||
{/*<th width="100">등급</th>*/}
|
||||
<th width="100">레벨</th>
|
||||
{/*<th width="300">속성 및 수치</th>*/}
|
||||
{/*<th width="200">세트 효과</th>*/}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{dataList && dataList.tattoo_list &&
|
||||
dataList.tattoo_list.map((el, idx) => {
|
||||
return (
|
||||
<Fragment key={idx}>
|
||||
<tr>
|
||||
<td>{el.slot}</td>
|
||||
<td>{el.item_id}</td>
|
||||
<td>{el.item_name}</td>
|
||||
{/*<td>{el.grade}</td>*/}
|
||||
<td>{el.level}</td>
|
||||
{/*<td>{el.property}</td>*/}
|
||||
{/*<td>{el.setEffect}</td>*/}
|
||||
</tr>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</UserDefaultTable>
|
||||
</UserTableWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserTatttooInfo;
|
||||
|
||||
const UserDefaultTable = styled.table`
|
||||
border: 1px solid #e8eaec;
|
||||
border-top: 1px solid #000;
|
||||
font-size: 14px;
|
||||
margin-bottom: 40px;
|
||||
th {
|
||||
background: #efefef;
|
||||
font-weight: 700;
|
||||
}
|
||||
th,
|
||||
td {
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
border-left: 1px solid #e8eaec;
|
||||
vertical-align: middle;
|
||||
}
|
||||
td {
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #e8eaec;
|
||||
word-break: break-all;
|
||||
}
|
||||
button {
|
||||
height: 24px;
|
||||
font-size: 13px;
|
||||
}
|
||||
`;
|
||||
|
||||
const QuestTable = styled(UserDefaultTable)`
|
||||
tbody {
|
||||
td {
|
||||
padding: 9px 12px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
const UserTableWrapper = styled.div`
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
margin-bottom: 40px;
|
||||
${UserDefaultTable}, ${QuestTable} {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
&::-webkit-scrollbar {
|
||||
height: 4px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #666666;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #d9d9d9;
|
||||
}
|
||||
`;
|
||||
98
src/components/DataManage/UserToolInfo.js
Normal file
98
src/components/DataManage/UserToolInfo.js
Normal file
@@ -0,0 +1,98 @@
|
||||
import styled from 'styled-components';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { UserToolView } from '../../apis/Users';
|
||||
import { TableSkeleton } from '../Skeleton/TableSkeleton';
|
||||
|
||||
const UserToolInfo = ({ userInfo }) => {
|
||||
const [dataList, setDataList] = useState();
|
||||
const [rowData, setRowData] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if(dataList && dataList.slot_list)
|
||||
setRowData([
|
||||
{ title: '1번 슬롯', itemNo: dataList.slot_list.slot1?.tool_id, itemName: dataList?.slot_list.slot1?.tool_name },
|
||||
{ title: '2번 슬롯', itemNo: dataList.slot_list.slot2?.tool_id, itemName: dataList?.slot_list.slot2?.tool_name },
|
||||
{ title: '3번 슬롯', itemNo: dataList.slot_list.slot3?.tool_id, itemName: dataList?.slot_list.slot3?.tool_name },
|
||||
{ title: '4번 슬롯', itemNo: dataList.slot_list.slot4?.tool_id, itemName: dataList?.slot_list.slot4?.tool_name },
|
||||
])
|
||||
}, [dataList])
|
||||
|
||||
const fetchData = async () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
await UserToolView(token, userInfo.guid).then(data => {
|
||||
setDataList(data);
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
loading ? <TableSkeleton width='30%' count={4} /> :
|
||||
<>
|
||||
<ToolWrapper>
|
||||
<UserInfoTable $maxwidth="570px">
|
||||
<colgroup>
|
||||
<col width="120" />
|
||||
<col width="30%" />
|
||||
<col width="70%" />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
{rowData && rowData.map((el, idx) => {
|
||||
return (
|
||||
<tr key={idx}>
|
||||
<th>{el.title}</th>
|
||||
<td>{el.itemNo}</td>
|
||||
<td>{el.itemName}</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</UserInfoTable>
|
||||
</ToolWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserToolInfo;
|
||||
|
||||
const UserInfoTable = styled.table`
|
||||
width: 100%;
|
||||
max-width: ${props => props.$maxwidth || 'auto'};
|
||||
font-size: 13px;
|
||||
border-radius: 15px;
|
||||
overflow: hidden;
|
||||
tr:first-child {
|
||||
th,
|
||||
td {
|
||||
border-top: 0;
|
||||
}
|
||||
}
|
||||
th,
|
||||
td {
|
||||
height: 36px;
|
||||
vertical-align: middle;
|
||||
border-top: 1px solid #d9d9d9;
|
||||
}
|
||||
th {
|
||||
width: 120px;
|
||||
background: #888;
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
}
|
||||
td {
|
||||
background: #fff;
|
||||
padding: 0 20px;
|
||||
}
|
||||
`;
|
||||
|
||||
const ToolWrapper = styled.div`
|
||||
${UserInfoTable} {
|
||||
td {
|
||||
border-left: 1px solid #d9d9d9;
|
||||
}
|
||||
}
|
||||
`;
|
||||
216
src/components/DataManage/UserViewSearchBar.js
Normal file
216
src/components/DataManage/UserViewSearchBar.js
Normal file
@@ -0,0 +1,216 @@
|
||||
import { styled } from 'styled-components';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { TextInput, SelectInput, InputLabel, FormWrapper, BtnWrapper, ButtonClose } from '../../styles/Components';
|
||||
import Button from '../../components/common/button/Button';
|
||||
import Modal from '../../components/common/modal/Modal';
|
||||
import { UserView } from '../../apis';
|
||||
|
||||
const UserViewSearchBar = ({ setInfoView, handleTab, setResultData, resultData }) => {
|
||||
const [modalState, setModalState] = useState('hidden');
|
||||
const [form, setForm] = useState('');
|
||||
const [isNullValue, setIsNullValue] = useState(false);
|
||||
const [message, setMessage] = useState('필수값을 입력해주세요.');
|
||||
const [dataLength, setDataLength] = useState(false);
|
||||
const [dataList, setDataList] = useState([]);
|
||||
const [searchData, setSearchData] = useState({
|
||||
searchType: 'DEFAULT',
|
||||
searchKey: '',
|
||||
});
|
||||
|
||||
// console.log(dataList)
|
||||
|
||||
const searchType = [
|
||||
{ value: 'DEFAULT', name: '선택' },
|
||||
{ value: 'NAME', name: '아바타명' },
|
||||
{ value: 'GUID', name: 'GUID' },
|
||||
{ value: 'ACCOUNT', name: 'Account ID' },
|
||||
{ value: 'FRIEND_CODE', name: '친구코드' },
|
||||
];
|
||||
|
||||
const fetchData = async (searchType, searchKey) => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
setDataList(await UserView(token, searchType, searchKey));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
dataList && setDataLength(Object.values(dataList).length);
|
||||
}, [dataList]);
|
||||
|
||||
const handleSearch = (searchType, searchKey) => {
|
||||
fetchData(searchType, searchKey);
|
||||
};
|
||||
|
||||
const handleReset = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
setSearchData({
|
||||
searchType: 'DEFAULT',
|
||||
searchKey: '',
|
||||
});
|
||||
setInfoView('none');
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
const handleRegistModalClose = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (searchData.searchKey.length === 0 || searchData.searchType === 'DEFAULT') {
|
||||
setMessage(searchData.searchType === 'DEFAULT' ? '항목을 선택해주세요.' : '필수값을 입력해주세요.');
|
||||
setIsNullValue(true);
|
||||
} else if (searchData.searchKey && modalState === 'hidden') {
|
||||
setIsNullValue(false);
|
||||
|
||||
handleSearch(searchData.searchType, searchData.searchKey);
|
||||
setModalState('view');
|
||||
} else {
|
||||
setModalState('hidden');
|
||||
}
|
||||
};
|
||||
|
||||
const handleModal = () => {
|
||||
if (modalState === 'hidden') {
|
||||
setModalState('view');
|
||||
} else {
|
||||
setInfoView('flex');
|
||||
handleTab('기본정보');
|
||||
setModalState('hidden');
|
||||
}
|
||||
};
|
||||
|
||||
const handleModalClose = () => {
|
||||
if (modalState === 'hidden') {
|
||||
setModalState('view');
|
||||
} else {
|
||||
setModalState('hidden');
|
||||
}
|
||||
};
|
||||
|
||||
const handleGuid = data => {
|
||||
// setResultData(data.guid);
|
||||
setResultData(data);
|
||||
handleModal();
|
||||
};
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormWrapper onSubmit={e => handleRegistModalClose(e)}>
|
||||
<SearchbarStyle>
|
||||
<SearchItem>
|
||||
<InputLabel>유저 조회</InputLabel>
|
||||
<InputGroup>
|
||||
<SelectInput
|
||||
value={searchData.searchType}
|
||||
onChange={e => { setSearchData({ ...searchData, searchType: e.target.value }); setDataList([])}}
|
||||
>
|
||||
{searchType.map((data, index) => (
|
||||
<option key={index} value={data.value} disabled={data.value === 'FRIEND_CODE' ? true : false}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<TextInput
|
||||
type="text"
|
||||
width="300px"
|
||||
value={searchData.searchKey}
|
||||
onChange={e => setSearchData({ ...searchData, searchKey: e.target.value })}
|
||||
disabled={searchData.searchType === 'DEFAULT' ? true : false}
|
||||
/>
|
||||
</InputGroup>
|
||||
</SearchItem>
|
||||
<BtnWrapper $gap="8px">
|
||||
<Button type="button" theme="reset" handleClick={e => handleReset(e)} />
|
||||
<Button type="button"
|
||||
theme="search" text="검색"
|
||||
handleClick={e => handleRegistModalClose(e)}
|
||||
/>
|
||||
{isNullValue && <SearchBarAlert>{message}</SearchBarAlert>}
|
||||
</BtnWrapper>
|
||||
</SearchbarStyle>
|
||||
</FormWrapper>
|
||||
<Modal $view={modalState} $bgcolor="transparent" min="600px">
|
||||
<BtnWrapper $justify="space-between">
|
||||
<PopTitle className="title">검색 대상 선택</PopTitle>
|
||||
<ButtonClose onClick={handleModalClose} />
|
||||
</BtnWrapper>
|
||||
<PopCategory>{searchType.map(data => data.value === searchData.searchType && data.name)}</PopCategory>
|
||||
<PopResult>
|
||||
|
||||
{dataLength === 0 ? (
|
||||
<ResultItem>일치하는 조회 대상이 없습니다.</ResultItem>
|
||||
) : searchData.searchType === 'NAME' ? (
|
||||
<ResultItem onClick={() => handleGuid(dataList)}>{dataList.nickname && dataList.nickname}</ResultItem>
|
||||
) : (
|
||||
<ResultItem onClick={() => handleGuid(dataList)}>{dataList.guid && dataList.guid}</ResultItem>
|
||||
)}
|
||||
</PopResult>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserViewSearchBar;
|
||||
|
||||
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;
|
||||
|
||||
${TextInput}, ${SelectInput} {
|
||||
height: 35px;
|
||||
}
|
||||
${TextInput} {
|
||||
padding: 0 10px;
|
||||
max-width: 400px;
|
||||
}
|
||||
`;
|
||||
|
||||
const InputGroup = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
`;
|
||||
|
||||
const PopTitle = styled.div`
|
||||
font-weight: 700;
|
||||
margin-bottom: 10px;
|
||||
font-size: 18px;
|
||||
`;
|
||||
const PopCategory = styled.div`
|
||||
background: #818181;
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
padding: 5px 10px;
|
||||
`;
|
||||
const PopResult = styled.div`
|
||||
border-bottom: 1px solid #000;
|
||||
`;
|
||||
const ResultItem = styled.div`
|
||||
padding: 4px 10px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background: #f6f6f6;
|
||||
}
|
||||
`;
|
||||
|
||||
const SearchBarAlert = styled.div`
|
||||
width: 100%;
|
||||
color: #d60000;
|
||||
margin: auto;
|
||||
padding-left: 15px;
|
||||
`;
|
||||
312
src/components/IndexManage/CreditContent.js
Normal file
312
src/components/IndexManage/CreditContent.js
Normal file
@@ -0,0 +1,312 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { styled } from 'styled-components';
|
||||
import Button from '../../components/common/button/Button';
|
||||
|
||||
import { CurrencyIndexExport, CurrencyIndexView } from '../../apis';
|
||||
import { SelectInput, TableStyle, TableInfo, ListOption, InputLabel } from '../../styles/Components';
|
||||
|
||||
import CreditSeacrhBar from '../../components/IndexManage/CreditSearchBar';
|
||||
import { uniqBy } from 'lodash';
|
||||
import { sumBy } from 'lodash';
|
||||
|
||||
const CreditContent = () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
let d = new Date();
|
||||
const START_DATE = new Date(new Date(d.setDate(d.getDate() - 1)).setHours(0, 0, 0, 0));
|
||||
const END_DATE = new Date();
|
||||
const CURRENCY_LIST = [
|
||||
{ "key": "Gold", "name": "골드" },
|
||||
{ "key": "Sapphire", "name": "사파이어" },
|
||||
{ "key": "Calium", "name": "칼리움" },
|
||||
{ "key": "Onyxium", "name": "오닉시움" }
|
||||
];
|
||||
|
||||
const [sendDate, setSendDate] = useState(START_DATE);
|
||||
const [finishDate, setFinishDate] = useState(END_DATE);
|
||||
const [currencyType, setCurrencyType] = useState('Gold');
|
||||
const [currencyText, setCurrencyText] = useState('골드');
|
||||
|
||||
const [dataList, setDataList] = useState([]);
|
||||
const [routeData, setRouteData] = useState([]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
fetchData(sendDate, finishDate, currencyType);
|
||||
}, [currencyType]);
|
||||
|
||||
const fetchData = async (startDate, endDate) => {
|
||||
const newStartDate = new Date(startDate);
|
||||
const newEndDate = new Date(endDate);
|
||||
|
||||
const startDateToLocal =
|
||||
newStartDate.getFullYear() +
|
||||
'-' +
|
||||
(newStartDate.getMonth() + 1 < 9 ? '0' + (newStartDate.getMonth() + 1) : newStartDate.getMonth() + 1) +
|
||||
'-' +
|
||||
(newStartDate.getDate() < 9 ? '0' + newStartDate.getDate() : newStartDate.getDate());
|
||||
|
||||
const endDateToLocal =
|
||||
newEndDate.getFullYear() +
|
||||
'-' +
|
||||
(newEndDate.getMonth() + 1 < 9 ? '0' + (newEndDate.getMonth() + 1) : newEndDate.getMonth() + 1) +
|
||||
'-' +
|
||||
(newEndDate.getDate() < 9 ? '0' + newEndDate.getDate() : newEndDate.getDate());
|
||||
|
||||
setDataList(await CurrencyIndexView(token, startDateToLocal, endDateToLocal, currencyType));
|
||||
|
||||
|
||||
setSendDate(startDateToLocal);
|
||||
setFinishDate(endDateToLocal);
|
||||
setRoutArray(await CurrencyIndexView(token, startDateToLocal, endDateToLocal, currencyType));
|
||||
};
|
||||
|
||||
const handleCurrencyChange = e => {
|
||||
let value = e.target.value;
|
||||
setCurrencyType(value);
|
||||
CURRENCY_LIST.filter(data => data.key === value).map(data => {
|
||||
setCurrencyText(data.name);
|
||||
});
|
||||
};
|
||||
|
||||
//route data
|
||||
const setRoutArray = async (data) => {
|
||||
let routeArray = [];
|
||||
let routeAcqArr = [];
|
||||
let routeConArr = [];
|
||||
|
||||
//생산량 route
|
||||
data.list && data.list[0].daily_data.filter(routeData => routeData.delta_type === 'ACQUIRE').map(routeData => {
|
||||
routeData.data.map(routeData => {
|
||||
if(!routeAcqArr.includes(routeData.route) ){
|
||||
routeAcqArr.push(routeData.route);
|
||||
}
|
||||
})
|
||||
});
|
||||
routeArray.ACQUIRE = routeAcqArr;
|
||||
|
||||
//소진량 route
|
||||
data.list && data.list[0].daily_data.filter(routeData => routeData.delta_type === 'CONSUME').map(routeData => {
|
||||
routeData.data.map(routeData => {
|
||||
if(!routeConArr.includes(routeData.route) ){
|
||||
routeConArr.push(routeData.route);
|
||||
}
|
||||
})
|
||||
});
|
||||
routeArray.CONSUME = routeConArr;
|
||||
|
||||
setRouteData(routeArray);
|
||||
};
|
||||
|
||||
// 엑셀 다운로드
|
||||
const handleXlsxExport = () => {
|
||||
const fileName = 'Caliverse_Credit_Index.xlsx';
|
||||
CurrencyIndexExport(token, fileName, sendDate, finishDate, currencyType);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
<CreditSeacrhBar fetchData={fetchData} />
|
||||
<TableInfo2>
|
||||
<SelectInput onChange={handleCurrencyChange}>
|
||||
{CURRENCY_LIST.map((item, index) => (
|
||||
<option value={item.key} key={index}>
|
||||
{item.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<ListOption>
|
||||
<Button theme="line" text="엑셀 다운로드" handleClick={handleXlsxExport} />
|
||||
</ListOption>
|
||||
</TableInfo2>
|
||||
<TableWrapper>
|
||||
<EconomicTable>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colSpan="2" className="text-center" width="300">
|
||||
Product
|
||||
</th>
|
||||
{dataList.list && uniqBy(dataList.list, 'date').map(data =>
|
||||
<th width="160" key={data.date}>{data.date}</th>
|
||||
)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<TableTitle colSpan="2">(Total) {currencyText} 생산량</TableTitle>
|
||||
{dataList.list &&
|
||||
dataList.list.map((data) =>
|
||||
(data.total).filter(totalData => data.date === totalData.date && totalData.delta_type === 'ACQUIRE')
|
||||
.map((totalData, index) => (
|
||||
<TableData key={index}
|
||||
$state={totalData.dif !== "" && totalData.dif !== "Infinity" ? "danger" : ""}>
|
||||
{totalData.quantity}
|
||||
{
|
||||
totalData.dif !== "" && totalData.dif !== "Infinity"
|
||||
? (<span>({totalData.dif})</span>)
|
||||
: ("")
|
||||
}
|
||||
</TableData>
|
||||
)
|
||||
))
|
||||
}
|
||||
</tr>
|
||||
<tr>
|
||||
<TableTitle colSpan="2">(Total) {currencyText} 소진량</TableTitle>
|
||||
{dataList.list &&
|
||||
dataList.list.map((data) =>
|
||||
(data.total).filter(totalData => data.date === totalData.date && totalData.delta_type === 'CONSUME')
|
||||
.map((totalData, index) => (
|
||||
<TableData key={index}
|
||||
$state={totalData.dif !== "" && totalData.dif !== "Infinity" ? "danger" : ""}>
|
||||
{totalData.quantity}
|
||||
{
|
||||
totalData.dif !== "" && totalData.dif !== "Infinity"
|
||||
? (<span>({totalData.dif})</span>)
|
||||
: ("")
|
||||
}
|
||||
</TableData>
|
||||
)
|
||||
))
|
||||
}
|
||||
</tr>
|
||||
<tr>
|
||||
<TableTitle colSpan="2">(Total) {currencyText} 보유량</TableTitle>
|
||||
{dataList.list &&
|
||||
dataList.list.map((data, index) => (
|
||||
<TableData key={index}>
|
||||
{sumBy(
|
||||
data.total.filter(totalData => data.date === totalData.date && totalData.delta_type === 'ACQUIRE'),
|
||||
'quantity',
|
||||
) -
|
||||
sumBy(
|
||||
data.total.filter(totalData => data.date === totalData.date && totalData.delta_type === 'CONSUME'),
|
||||
'quantity',
|
||||
)}
|
||||
</TableData>
|
||||
))
|
||||
}
|
||||
</tr>
|
||||
{/* 획득 GET */}
|
||||
{
|
||||
routeData.ACQUIRE && routeData.ACQUIRE.map((route, index) => (
|
||||
<tr key={index}>
|
||||
<TableTitle>GET</TableTitle>
|
||||
<TableTitle>{route}</TableTitle>
|
||||
{dataList.list &&
|
||||
dataList.list.map((data) =>
|
||||
data.daily_data.filter(dailyData => data.date === dailyData.date && dailyData.delta_type === 'ACQUIRE')
|
||||
.map(dailyData => (dailyData.data).filter(routeData => data.date === routeData.date && routeData.route === route)
|
||||
.map((routeData, i) => (
|
||||
<TableData key={i} data={routeData.date}>{routeData.quantity}</TableData>
|
||||
)))
|
||||
)
|
||||
}
|
||||
</tr>
|
||||
))
|
||||
}
|
||||
{/* 소진 USE CONSUME */}
|
||||
{
|
||||
routeData.CONSUME && routeData.CONSUME.map((route, index) => (
|
||||
<tr key={index}>
|
||||
<TableTitle>USE</TableTitle>
|
||||
<TableTitle>{route}</TableTitle>
|
||||
{dataList.list &&
|
||||
dataList.list.map((data) =>
|
||||
data.daily_data.filter(dailyData => data.date === dailyData.date && dailyData.delta_type === 'CONSUME')
|
||||
.map(dailyData => (dailyData.data).filter(routeData => data.date === routeData.date && routeData.route === route)
|
||||
.map((routeData, i) => (
|
||||
<TableData key={i} data={routeData.date}>{routeData.quantity}</TableData>
|
||||
)))
|
||||
)
|
||||
}
|
||||
</tr>
|
||||
))
|
||||
}
|
||||
</tbody>
|
||||
</EconomicTable>
|
||||
</TableWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreditContent;
|
||||
|
||||
const TableWrapper = styled.div`
|
||||
width: 100%;
|
||||
min-width: 680px;
|
||||
overflow: auto;
|
||||
&::-webkit-scrollbar {
|
||||
height: 4px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #666666;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #d9d9d9;
|
||||
}
|
||||
${TableStyle} {
|
||||
width: 100%;
|
||||
min-width: 900px;
|
||||
th {
|
||||
&.cell-nru {
|
||||
background: #f0f0f0;
|
||||
border-left: 1px solid #aaa;
|
||||
border-right: 1px solid #aaa;
|
||||
}
|
||||
}
|
||||
td {
|
||||
&.blank {
|
||||
background: #f9f9f9;
|
||||
}
|
||||
&.cell-nru {
|
||||
background: #fafafa;
|
||||
border-left: 1px solid #aaa;
|
||||
border-right: 1px solid #aaa;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const TableInfo2 = styled(TableInfo)`
|
||||
justify-content: space-between;
|
||||
${InputLabel} {
|
||||
font-size: 12px;
|
||||
}
|
||||
${SelectInput} {
|
||||
width: auto;
|
||||
min-width: 100px;
|
||||
height: 24px;
|
||||
}
|
||||
`;
|
||||
|
||||
const TableDate = styled.th`
|
||||
color: ${props => (props.$state === 'danger' ? '#d60000' : '#2c2c2c')};
|
||||
`;
|
||||
|
||||
const TableData = styled.td`
|
||||
|
||||
background: ${props => (props.$state === 'danger' ? '#d60000' : props.$state === 'blank' ? '#F9F9F9' : 'transparent')};
|
||||
color: ${props => (props.$state === 'danger' ? '#fff' : '#2c2c2c')};
|
||||
`;
|
||||
|
||||
const perData = styled.span`
|
||||
display: ${props => (props.$view === 'hidden' ? 'none' : 'block')};
|
||||
`;
|
||||
|
||||
const TableTitle = styled.td`
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const EconomicTable = styled(TableStyle)`
|
||||
${TableData} {
|
||||
text-align: left;
|
||||
}
|
||||
tbody {
|
||||
tr:nth-child(1),
|
||||
tr:nth-child(2),
|
||||
tr:nth-child(3) {
|
||||
background: #f5fcff;
|
||||
}
|
||||
}
|
||||
`;
|
||||
113
src/components/IndexManage/CreditSearchBar.js
Normal file
113
src/components/IndexManage/CreditSearchBar.js
Normal file
@@ -0,0 +1,113 @@
|
||||
import { useState } from 'react';
|
||||
import { styled } from 'styled-components';
|
||||
import Button from '../../components/common/button/Button';
|
||||
|
||||
import DatePickerComponent from '../common/Date/DatePickerComponent';
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
|
||||
import { FormWrapper, InputLabel, TextInput, SelectInput, BtnWrapper, InputGroup, DatePickerWrapper } from '../../styles/Components';
|
||||
|
||||
const CreditSeacrhBar = ({ fetchData }) => {
|
||||
let d = new Date();
|
||||
const START_DATE = new Date(new Date(d.setDate(d.getDate() - 1)).setHours(0, 0, 0, 0));
|
||||
const END_DATE = new Date();
|
||||
|
||||
const [resultData, setResultData] = useState({
|
||||
send_dt: START_DATE,
|
||||
end_dt: END_DATE,
|
||||
});
|
||||
|
||||
// 발송 날짜 세팅 로직
|
||||
const handleSelectedDate = data => {
|
||||
const sendDate = new Date(data);
|
||||
const resultSendData = new Date(sendDate.getFullYear(), sendDate.getMonth(), sendDate.getDate());
|
||||
|
||||
setResultData({ ...resultData, send_dt: resultSendData });
|
||||
};
|
||||
|
||||
// 발송 날짜 세팅 로직
|
||||
const handleEndDate = data => {
|
||||
const endDate = new Date(data);
|
||||
const resultSendData = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate());
|
||||
|
||||
setResultData({ ...resultData, end_dt: resultSendData });
|
||||
};
|
||||
|
||||
const handleReset = e => {
|
||||
e.preventDefault();
|
||||
setResultData({ send_dt: START_DATE, end_dt: END_DATE });
|
||||
|
||||
fetchData(START_DATE, END_DATE);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormWrapper>
|
||||
<SearchbarStyle>
|
||||
<SearchItem>
|
||||
<InputLabel>집계 기간</InputLabel>
|
||||
<InputGroup>
|
||||
<DatePickerWrapper>
|
||||
<DatePickerComponent
|
||||
name="시작 일자" selectedDate={resultData.send_dt}
|
||||
handleSelectedDate={data => handleSelectedDate(data)}
|
||||
maxDate={new Date()} />
|
||||
<span>~</span>
|
||||
<DatePickerComponent
|
||||
name="종료 일자" selectedDate={resultData.end_dt}
|
||||
handleSelectedDate={data => handleEndDate(data)}
|
||||
pastDate = {resultData.send_dt}
|
||||
maxDate={new Date()} />
|
||||
</DatePickerWrapper>
|
||||
</InputGroup>
|
||||
</SearchItem>
|
||||
<BtnWrapper $gap="8px">
|
||||
<Button
|
||||
theme="reset"
|
||||
handleClick={e => {
|
||||
handleReset(e);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
theme="search"
|
||||
text="집계"
|
||||
handleClick={e => {
|
||||
e.preventDefault();
|
||||
fetchData(resultData.send_dt, resultData.end_dt);
|
||||
}}
|
||||
/>
|
||||
</BtnWrapper>
|
||||
</SearchbarStyle>
|
||||
</FormWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreditSeacrhBar;
|
||||
|
||||
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;
|
||||
|
||||
${TextInput}, ${SelectInput} {
|
||||
height: 35px;
|
||||
}
|
||||
${TextInput} {
|
||||
padding: 0 10px;
|
||||
max-width: 400px;
|
||||
}
|
||||
`;
|
||||
110
src/components/IndexManage/DailyActiveUserContent.js
Normal file
110
src/components/IndexManage/DailyActiveUserContent.js
Normal file
@@ -0,0 +1,110 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import Button from '../../components/common/button/Button';
|
||||
|
||||
import { TableStyle, TableInfo, ListOption, IndexTableWrap } from '../../styles/Components';
|
||||
import { DailySearchBar } from '../../components/IndexManage/index';
|
||||
import { DailyActiveUserExport, DailyActiveUserView } from '../../apis';
|
||||
|
||||
const PlayTimeContent = () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
let d = new Date();
|
||||
const START_DATE = new Date(new Date(d.setDate(d.getDate() - 1)).setHours(0, 0, 0, 0));
|
||||
const END_DATE = new Date();
|
||||
|
||||
const [dataList, setDataList] = useState([]);
|
||||
const [resultData, setResultData] = useState([]);
|
||||
|
||||
const [sendDate, setSendDate] = useState(START_DATE);
|
||||
const [finishDate, setFinishDate] = useState(END_DATE);
|
||||
|
||||
useEffect(() => {
|
||||
fetchData(START_DATE, END_DATE);
|
||||
}, []);
|
||||
|
||||
// DAU 데이터
|
||||
const fetchData = async (startDate, endDate) => {
|
||||
const startDateToLocal =
|
||||
startDate.getFullYear() +
|
||||
'-' +
|
||||
(startDate.getMonth() + 1 < 9 ? '0' + (startDate.getMonth() + 1) : startDate.getMonth() + 1) +
|
||||
'-' +
|
||||
(startDate.getDate() < 9 ? '0' + startDate.getDate() : startDate.getDate());
|
||||
|
||||
const endDateToLocal =
|
||||
endDate.getFullYear() +
|
||||
'-' +
|
||||
(endDate.getMonth() + 1 < 9 ? '0' + (endDate.getMonth() + 1) : endDate.getMonth() + 1) +
|
||||
'-' +
|
||||
(endDate.getDate() < 9 ? '0' + endDate.getDate() : endDate.getDate());
|
||||
|
||||
// await DailyActiveUserView(token, startDateToLocal, endDateToLocal).then(data => {
|
||||
// console.log(data);
|
||||
// setDataList(data);
|
||||
// });
|
||||
|
||||
setSendDate(startDateToLocal);
|
||||
setFinishDate(endDateToLocal);
|
||||
};
|
||||
|
||||
// 검색 함수
|
||||
const handleSearch = (send_dt, end_dt) => {
|
||||
fetchData(send_dt, end_dt);
|
||||
};
|
||||
|
||||
// 엑셀 다운로드
|
||||
const handleXlsxExport = () => {
|
||||
const fileName = 'Caliverse_Dau.xlsx';
|
||||
DailyActiveUserExport(token, fileName, sendDate, finishDate);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DailySearchBar setResultData={setResultData} resultData={resultData} handleSearch={handleSearch} fetchData={fetchData} />
|
||||
<TableInfo>
|
||||
<ListOption>
|
||||
<Button theme="line" text="엑셀 다운로드" handleClick={handleXlsxExport} />
|
||||
</ListOption>
|
||||
</TableInfo>
|
||||
<IndexTableWrap>
|
||||
<TableStyle>
|
||||
<caption></caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th rowSpan="1" width="45">
|
||||
일자
|
||||
</th>
|
||||
<th colSpan="1" width="30">
|
||||
DAU
|
||||
</th>
|
||||
{/*<th colSpan="1" width="30">*/}
|
||||
{/* DALC*/}
|
||||
{/*</th>*/}
|
||||
<th colSpan="1" width="30">
|
||||
DGLC
|
||||
</th>
|
||||
{/*<th colSpan="1" width="30">*/}
|
||||
{/* MaxAU*/}
|
||||
{/*</th>*/}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
{dataList && (dataList || []).map((data, index) => (
|
||||
<tr key={index}>
|
||||
<td>{data.date}</td>
|
||||
<td>{data.dau}</td>
|
||||
{/*<td>{data.dalc}</td>*/}
|
||||
<td>{data.dglc}</td>
|
||||
{/*<td>{data.maxAu}</td>*/}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</TableStyle>
|
||||
</IndexTableWrap>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PlayTimeContent;
|
||||
|
||||
170
src/components/IndexManage/DailyDashBoard.js
Normal file
170
src/components/IndexManage/DailyDashBoard.js
Normal file
@@ -0,0 +1,170 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { userTotalIndex } from '../../apis';
|
||||
|
||||
import { styled } from 'styled-components';
|
||||
import TitleArrow from '../../assets/img/icon/icon-title.png';
|
||||
import UpIcon from '../../assets/img/icon/icon-up.png';
|
||||
import DownIcon from '../../assets/img/icon/icon-down.png';
|
||||
|
||||
const DailyDashBoard = ({ content }) => {
|
||||
const [boardState, setBoardState] = useState('active');
|
||||
const [totalData, setTotalData] = useState([]);
|
||||
|
||||
const handleBoard = () => {
|
||||
if (boardState === 'active') {
|
||||
setBoardState('inactive');
|
||||
} else {
|
||||
setBoardState('active');
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// 이용자 지표 총계 불러오기
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
const fetchData = async () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
await userTotalIndex(token).then(data => {
|
||||
console.log(data);
|
||||
setTotalData(data.dashboard);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<DailyBoardWrapper>
|
||||
{totalData &&
|
||||
<DailyBoard>
|
||||
<BoardTitle onClick={handleBoard} $state={boardState}>
|
||||
Daily Dashboard
|
||||
</BoardTitle>
|
||||
<BoardInfo $state={boardState}>
|
||||
<BoxWrapper>
|
||||
<InfoItem>
|
||||
<InfoTitle>DAU</InfoTitle>
|
||||
<InfoValue>
|
||||
{totalData.dau && totalData.dau.count}
|
||||
<span>({totalData.dau && totalData.dau.dif}%)</span>
|
||||
<InfoArrow $state={totalData.dau && totalData.dau.updown === '=' ? null : totalData.dau && totalData.dau.updown}></InfoArrow>
|
||||
</InfoValue>
|
||||
</InfoItem>
|
||||
<InfoItem>
|
||||
<InfoTitle>NRU</InfoTitle>
|
||||
<InfoValue>
|
||||
{totalData.nru && totalData.nru.count}
|
||||
<span>({totalData.nru && totalData.nru.dif}%)</span>
|
||||
<InfoArrow $state={totalData.nru && totalData.nru.updown === '=' ? null : totalData.nru && totalData.nru.updown}></InfoArrow>
|
||||
</InfoValue>
|
||||
</InfoItem>
|
||||
<InfoItem>
|
||||
<InfoTitle>PU</InfoTitle>
|
||||
<InfoValue>
|
||||
{totalData.pu && totalData.pu.count}
|
||||
<span>({totalData.pu && totalData.pu.dif}%)</span>
|
||||
<InfoArrow $state={totalData.pu && totalData.pu.updown === '=' ? null : totalData.pu && totalData.pu.updown}></InfoArrow>
|
||||
</InfoValue>
|
||||
</InfoItem>
|
||||
<InfoItem>
|
||||
<InfoTitle>MCU</InfoTitle>
|
||||
<InfoValue>
|
||||
{totalData.mcu && totalData.mcu.count}
|
||||
<span>({totalData.mcu && totalData.mcu.dif}%)</span>
|
||||
<InfoArrow $state={totalData.mcu && totalData.mcu.updown === '=' ? null : totalData.mcu && totalData.mcu.updown}></InfoArrow>
|
||||
</InfoValue>
|
||||
</InfoItem>
|
||||
</BoxWrapper>
|
||||
<InfoNotice>집계 기간 : {totalData.date && totalData.date} 00시 ~ 24시 (집계는 당일 기준 하루 전 데이터를 사용합니다)</InfoNotice>
|
||||
</BoardInfo>
|
||||
</DailyBoard>
|
||||
}
|
||||
</DailyBoardWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default DailyDashBoard;
|
||||
|
||||
const DailyBoardWrapper = styled.div`
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #000;
|
||||
`;
|
||||
|
||||
const DailyBoard = styled.div`
|
||||
background: #f6f6f6;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 20px;
|
||||
`;
|
||||
|
||||
const BoardTitle = styled.div`
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
line-height: 52px;
|
||||
padding: 0 10px;
|
||||
cursor: pointer;
|
||||
&:after {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 11px;
|
||||
height: 52px;
|
||||
margin-left: 10px;
|
||||
background: url(${TitleArrow}) 50% 50% no-repeat;
|
||||
position: absolute;
|
||||
transform: ${props => (props.$state === 'active' ? 'rotate(0)' : 'rotate(180deg)')};
|
||||
}
|
||||
`;
|
||||
|
||||
const BoardInfo = styled.div`
|
||||
padding: 20px;
|
||||
border-top: 1px solid #d9d9d9;
|
||||
display: ${props => (props.$state === 'active' ? 'block' : 'none')};
|
||||
`;
|
||||
|
||||
const BoxWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
`;
|
||||
|
||||
const InfoItem = styled.div`
|
||||
width: 24%;
|
||||
background: #fff;
|
||||
padding: 15px 20px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-radius: 15px;
|
||||
`;
|
||||
|
||||
const InfoTitle = styled.div`
|
||||
width: 42px;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
`;
|
||||
|
||||
const InfoValue = styled.div`
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
margin: 5px 0;
|
||||
gap: 5px 0;
|
||||
align-items: center;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
span {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
display: inline-block;
|
||||
margin: 0 5px;
|
||||
}
|
||||
`;
|
||||
|
||||
const InfoArrow = styled.div`
|
||||
width: 12px;
|
||||
height: 6px;
|
||||
background: url(${props => (props.$state === null ? '' : props.$state === 'UP' ? UpIcon : DownIcon)}) 50% 50% no-repeat;
|
||||
`;
|
||||
const InfoNotice = styled.div`
|
||||
font-size: 12px;
|
||||
text-align: right;
|
||||
color: #999;
|
||||
margin-top: 10px;
|
||||
`;
|
||||
111
src/components/IndexManage/DailyMedalContent.js
Normal file
111
src/components/IndexManage/DailyMedalContent.js
Normal file
@@ -0,0 +1,111 @@
|
||||
import { Fragment, useEffect, useState } from 'react';
|
||||
|
||||
import Button from '../common/button/Button';
|
||||
|
||||
import { TableStyle, TableInfo, ListOption, IndexTableWrap } from '../../styles/Components';
|
||||
import { DailySearchBar } from './index';
|
||||
import { DailyMedalView } from '../../apis';
|
||||
|
||||
const DailyMedalContent = () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
let d = new Date();
|
||||
const START_DATE = new Date(new Date(d.setDate(d.getDate() - 1)).setHours(0, 0, 0, 0));
|
||||
const END_DATE = new Date();
|
||||
|
||||
const [dataList, setDataList] = useState([]);
|
||||
const [resultData, setResultData] = useState([]);
|
||||
|
||||
const [sendDate, setSendDate] = useState(START_DATE);
|
||||
const [finishDate, setFinishDate] = useState(END_DATE);
|
||||
|
||||
useEffect(() => {
|
||||
fetchData(START_DATE, END_DATE);
|
||||
}, []);
|
||||
|
||||
const fetchData = async (startDate, endDate) => {
|
||||
const startDateToLocal =
|
||||
startDate.getFullYear() +
|
||||
'-' +
|
||||
(startDate.getMonth() + 1 < 9 ? '0' + (startDate.getMonth() + 1) : startDate.getMonth() + 1) +
|
||||
'-' +
|
||||
(startDate.getDate() < 9 ? '0' + startDate.getDate() : startDate.getDate());
|
||||
|
||||
const endDateToLocal =
|
||||
endDate.getFullYear() +
|
||||
'-' +
|
||||
(endDate.getMonth() + 1 < 9 ? '0' + (endDate.getMonth() + 1) : endDate.getMonth() + 1) +
|
||||
'-' +
|
||||
(endDate.getDate() < 9 ? '0' + endDate.getDate() : endDate.getDate());
|
||||
|
||||
setDataList(await DailyMedalView(token, startDateToLocal, endDateToLocal));
|
||||
|
||||
setSendDate(startDateToLocal);
|
||||
setFinishDate(endDateToLocal);
|
||||
};
|
||||
|
||||
// 검색 함수
|
||||
const handleSearch = (send_dt, end_dt) => {
|
||||
fetchData(send_dt, end_dt);
|
||||
};
|
||||
|
||||
// 엑셀 다운로드
|
||||
const handleXlsxExport = () => {
|
||||
const fileName = 'Caliverse_Daily_Medal.xlsx';
|
||||
//DailyActiveUserExport(token, fileName, sendDate, finishDate);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DailySearchBar setResultData={setResultData} resultData={resultData} handleSearch={handleSearch} fetchData={fetchData} />
|
||||
<TableInfo>
|
||||
<ListOption>
|
||||
<Button theme="line" text="엑셀 다운로드" handleClick={handleXlsxExport} />
|
||||
</ListOption>
|
||||
</TableInfo>
|
||||
<IndexTableWrap>
|
||||
<TableStyle>
|
||||
<caption></caption>
|
||||
<thead >
|
||||
<tr>
|
||||
<th rowSpan="1" width="20">
|
||||
일자
|
||||
</th>
|
||||
<th colSpan="1" width="30">
|
||||
UserID
|
||||
</th>
|
||||
<th colSpan="1" width="30">
|
||||
닉네임
|
||||
</th>
|
||||
<th colSpan="1" width="30">
|
||||
Item ID
|
||||
</th>
|
||||
<th colSpan="1" width="30">
|
||||
획득량
|
||||
</th>
|
||||
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
{(dataList || []).map((data, index) => (
|
||||
<tr key={index}>
|
||||
<td>{data.date}</td>
|
||||
<td>{data.dau}</td>
|
||||
<td>{data.dalc}</td>
|
||||
<td>{data.dglc}</td>
|
||||
<td>{data.maxAu}</td>
|
||||
{Array.from({ length: 24 }, (_, i) => (
|
||||
<td key={i}>{data['h' + i]}</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
|
||||
</tbody>
|
||||
</TableStyle>
|
||||
</IndexTableWrap>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DailyMedalContent;
|
||||
|
||||
112
src/components/IndexManage/DailySearchBar.js
Normal file
112
src/components/IndexManage/DailySearchBar.js
Normal file
@@ -0,0 +1,112 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { styled } from 'styled-components';
|
||||
import Button from '../common/button/Button';
|
||||
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
import DatePickerComponent from '../common/Date/DatePickerComponent';
|
||||
|
||||
import { FormWrapper, InputLabel, TextInput, SelectInput, BtnWrapper, InputGroup, DatePickerWrapper } from '../../styles/Components';
|
||||
|
||||
const DailySearchBar = ({ resultData, setResultData, handleSearch, fetchData }) => {
|
||||
// 초기 날짜 세팅
|
||||
let d = new Date();
|
||||
const START_DATE = new Date(new Date(d.setDate(d.getDate() - 1)).setHours(0, 0, 0, 0));
|
||||
const END_DATE = new Date();
|
||||
|
||||
// resultData에 임의 날짜 넣어주기
|
||||
useEffect(() => {
|
||||
setResultData({
|
||||
send_dt: START_DATE,
|
||||
end_dt: END_DATE,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleSelectedDate = data => {
|
||||
const sendDate = new Date(data);
|
||||
const resultSendData = new Date(sendDate.getFullYear(), sendDate.getMonth(), sendDate.getDate());
|
||||
|
||||
setResultData({ ...resultData, send_dt: resultSendData });
|
||||
};
|
||||
|
||||
const handleEndDate = data => {
|
||||
const endDate = new Date(data);
|
||||
const resultSendData = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate());
|
||||
|
||||
setResultData({ ...resultData, end_dt: resultSendData });
|
||||
};
|
||||
|
||||
const handleReset = e => {
|
||||
e.preventDefault();
|
||||
setResultData({ send_dt: START_DATE, end_dt: END_DATE });
|
||||
|
||||
fetchData(START_DATE, END_DATE);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormWrapper>
|
||||
<SearchbarStyle>
|
||||
<SearchItem>
|
||||
<InputLabel>집계 기준일</InputLabel>
|
||||
<InputGroup>
|
||||
<DatePickerWrapper>
|
||||
<DatePickerComponent
|
||||
name="시작 일자"
|
||||
selectedDate={resultData.send_dt}
|
||||
handleSelectedDate={data => handleSelectedDate(data)}
|
||||
maxDate={new Date()} />
|
||||
<span>~</span>
|
||||
<DatePickerComponent
|
||||
name="종료 일자"
|
||||
selectedDate={resultData.end_dt}
|
||||
handleSelectedDate={data => handleEndDate(data)}
|
||||
pastDate = {resultData.send_dt}
|
||||
maxDate={new Date()} />
|
||||
</DatePickerWrapper>
|
||||
</InputGroup>
|
||||
</SearchItem>
|
||||
<BtnWrapper $gap="8px">
|
||||
<Button theme="reset" handleClick={handleReset} />
|
||||
<Button
|
||||
theme="search"
|
||||
text="검색"
|
||||
handleClick={e => {
|
||||
e.preventDefault();
|
||||
handleSearch(resultData.send_dt, resultData.end_dt);
|
||||
}}
|
||||
/>
|
||||
</BtnWrapper>
|
||||
</SearchbarStyle>
|
||||
</FormWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DailySearchBar;
|
||||
|
||||
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;
|
||||
|
||||
${TextInput}, ${SelectInput} {
|
||||
height: 35px;
|
||||
}
|
||||
${TextInput} {
|
||||
padding: 0 10px;
|
||||
max-width: 400px;
|
||||
}
|
||||
`;
|
||||
166
src/components/IndexManage/DecoContent.js
Normal file
166
src/components/IndexManage/DecoContent.js
Normal file
@@ -0,0 +1,166 @@
|
||||
import { styled } from 'styled-components';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import DecoSearchBar from '../../components/IndexManage/DecoSearchBar';
|
||||
import Button from '../../components/common/button/Button';
|
||||
|
||||
import { TableStyle, TableInfo, ListOption } from '../../styles/Components';
|
||||
import { ClothesIndexExport, ClothesIndexView } from '../../apis';
|
||||
|
||||
const DecoContent = () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
let d = new Date();
|
||||
const START_DATE = new Date(new Date(d.setDate(d.getDate() - 1)).setHours(0, 0, 0, 0));
|
||||
const END_DATE = new Date();
|
||||
|
||||
const [searchKey, setSearchKey] = useState('');
|
||||
const [sendDate, setSendDate] = useState(START_DATE);
|
||||
const [finishDate, setFinishDate] = useState(END_DATE);
|
||||
|
||||
const [dataList, setDataList] = useState([]);
|
||||
useEffect(() => {
|
||||
fetchData('', START_DATE, END_DATE);
|
||||
}, []);
|
||||
|
||||
// console.log(dataList);
|
||||
|
||||
const fetchData = async (data, startDate, endDate) => {
|
||||
const startDateToLocal =
|
||||
startDate.getFullYear() +
|
||||
'-' +
|
||||
(startDate.getMonth() + 1 < 9 ? '0' + (startDate.getMonth() + 1) : startDate.getMonth() + 1) +
|
||||
'-' +
|
||||
(startDate.getDate() < 9 ? '0' + startDate.getDate() : startDate.getDate());
|
||||
|
||||
const endDateToLocal =
|
||||
endDate.getFullYear() +
|
||||
'-' +
|
||||
(endDate.getMonth() + 1 < 9 ? '0' + (endDate.getMonth() + 1) : endDate.getMonth() + 1) +
|
||||
'-' +
|
||||
(endDate.getDate() < 9 ? '0' + endDate.getDate() : endDate.getDate());
|
||||
|
||||
setDataList(await ClothesIndexView(token, data, startDateToLocal, endDateToLocal));
|
||||
|
||||
setSearchKey(data);
|
||||
setSendDate(startDateToLocal);
|
||||
setFinishDate(endDateToLocal);
|
||||
};
|
||||
|
||||
// 엑셀 다운로드
|
||||
const handleXlsxExport = () => {
|
||||
const fileName = 'Caliverse_Deco_Index.xlsx';
|
||||
ClothesIndexExport(token, fileName, searchKey, sendDate, finishDate);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DecoSearchBar fetchData={fetchData} />
|
||||
<TableInfo>
|
||||
<ListOption>
|
||||
<Button theme="line" text="엑셀 다운로드" handleClick={handleXlsxExport} />
|
||||
</ListOption>
|
||||
</TableInfo>
|
||||
<TableWrapper>
|
||||
<EconomicTable2>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="240">일자</th>
|
||||
<th rowSpan="2" width="160">
|
||||
2023-08-07
|
||||
</th>
|
||||
<th rowSpan="2" width="160">
|
||||
2023-08-08
|
||||
</th>
|
||||
<th rowSpan="2" width="160">
|
||||
2023-08-09
|
||||
</th>
|
||||
<th rowSpan="2" width="160">
|
||||
2023-08-10
|
||||
</th>
|
||||
<th rowSpan="2" width="160"></th>
|
||||
<th rowSpan="2" width="160"></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>대상</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<TableTitle>(인스턴스 ID) 방문자 수</TableTitle>
|
||||
<TableData>95</TableData>
|
||||
<TableData>240</TableData>
|
||||
<TableData>240</TableData>
|
||||
<TableData>240</TableData>
|
||||
<TableData $state="blank"></TableData>
|
||||
<TableData $state="blank"></TableData>
|
||||
</tr>
|
||||
{/* {mokupData.map((data, index) => (
|
||||
<Fragment key={index}>
|
||||
<tr>
|
||||
<td>{data.date}</td>
|
||||
<td>{data.name}</td>
|
||||
<td>{data.trader}</td>
|
||||
<td>{data.id}</td>
|
||||
<td>{data.key}</td>
|
||||
</tr>
|
||||
</Fragment>
|
||||
))} */}
|
||||
</tbody>
|
||||
</EconomicTable2>
|
||||
</TableWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DecoContent;
|
||||
|
||||
const TableWrapper = styled.div`
|
||||
width: 100%;
|
||||
min-width: 680px;
|
||||
overflow: auto;
|
||||
&::-webkit-scrollbar {
|
||||
height: 4px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #666666;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #d9d9d9;
|
||||
}
|
||||
${TableStyle} {
|
||||
width: 100%;
|
||||
min-width: 900px;
|
||||
th {
|
||||
&.cell-nru {
|
||||
background: #f0f0f0;
|
||||
border-left: 1px solid #aaa;
|
||||
border-right: 1px solid #aaa;
|
||||
}
|
||||
}
|
||||
td {
|
||||
&.blank {
|
||||
background: #f9f9f9;
|
||||
}
|
||||
&.cell-nru {
|
||||
background: #fafafa;
|
||||
border-left: 1px solid #aaa;
|
||||
border-right: 1px solid #aaa;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const TableData = styled.td`
|
||||
background: ${props => (props.$state === 'danger' ? '#d60000' : props.$state === 'blank' ? '#F9F9F9' : 'transparent')};
|
||||
color: ${props => (props.$state === 'danger' ? '#fff' : '#2c2c2c')};
|
||||
`;
|
||||
|
||||
const TableTitle = styled.td`
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const EconomicTable2 = styled(TableStyle)`
|
||||
${TableData} {
|
||||
text-align: left;
|
||||
}
|
||||
`;
|
||||
116
src/components/IndexManage/DecoSearchBar.js
Normal file
116
src/components/IndexManage/DecoSearchBar.js
Normal file
@@ -0,0 +1,116 @@
|
||||
import { useState } from 'react';
|
||||
import { styled } from 'styled-components';
|
||||
import Button from '../../components/common/button/Button';
|
||||
|
||||
import DatePickerComponent from '../common/Date/DatePickerComponent';
|
||||
|
||||
import { FormWrapper, InputLabel, TextInput, SelectInput, BtnWrapper, InputGroup, DatePickerWrapper } from '../../styles/Components';
|
||||
|
||||
const DecoSearchBar = ({ fetchData }) => {
|
||||
let d = new Date();
|
||||
const START_DATE = new Date(new Date(d.setDate(d.getDate() - 1)).setHours(0, 0, 0, 0));
|
||||
const END_DATE = new Date();
|
||||
|
||||
const [resultData, setResultData] = useState({
|
||||
search_key: '',
|
||||
send_dt: START_DATE,
|
||||
end_dt: END_DATE,
|
||||
});
|
||||
|
||||
// 발송 날짜 세팅 로직
|
||||
const handleSelectedDate = data => {
|
||||
const sendDate = new Date(data);
|
||||
const resultSendData = new Date(sendDate.getFullYear(), sendDate.getMonth(), sendDate.getDate());
|
||||
|
||||
setResultData({ ...resultData, send_dt: resultSendData });
|
||||
};
|
||||
|
||||
// 발송 날짜 세팅 로직
|
||||
const handleEndDate = data => {
|
||||
const endDate = new Date(data);
|
||||
const resultSendData = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate());
|
||||
|
||||
setResultData({ ...resultData, end_dt: resultSendData });
|
||||
};
|
||||
|
||||
const handleReset = e => {
|
||||
e.preventDefault();
|
||||
setResultData({ search_key: '', send_dt: START_DATE, end_dt: END_DATE });
|
||||
|
||||
fetchData('', START_DATE, END_DATE);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<FormWrapper>
|
||||
<SearchbarStyle>
|
||||
<SearchItem>
|
||||
<InputLabel>아이템 ID</InputLabel>
|
||||
<TextInput type="text" placeholder="ID 입력" onChange={e => setResultData({ ...resultData, search_key: e.target.value })} />
|
||||
</SearchItem>
|
||||
<SearchItem>
|
||||
<InputLabel>집계 기간</InputLabel>
|
||||
<InputGroup>
|
||||
<DatePickerWrapper>
|
||||
<DatePickerComponent
|
||||
name="시작 일자" selectedDate={resultData.send_dt}
|
||||
handleSelectedDate={data => handleSelectedDate(data)}
|
||||
maxDate={new Date()} />
|
||||
<span>~</span>
|
||||
<DatePickerComponent
|
||||
name="종료 일자" selectedDate={resultData.end_dt}
|
||||
handleSelectedDate={data => handleEndDate(data)}
|
||||
pastDate = {resultData.send_dt}
|
||||
maxDate={new Date()} />
|
||||
</DatePickerWrapper>
|
||||
</InputGroup>
|
||||
</SearchItem>
|
||||
<BtnWrapper $gap="8px">
|
||||
<Button
|
||||
theme="reset"
|
||||
handleClick={e => {
|
||||
handleReset(e);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
theme="search"
|
||||
text="집계"
|
||||
handleClick={e => {
|
||||
e.preventDefault();
|
||||
fetchData(resultData.search_key, resultData.send_dt, resultData.end_dt);
|
||||
}}
|
||||
/>
|
||||
</BtnWrapper>
|
||||
</SearchbarStyle>
|
||||
</FormWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DecoSearchBar;
|
||||
|
||||
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;
|
||||
|
||||
${TextInput}, ${SelectInput} {
|
||||
height: 35px;
|
||||
}
|
||||
${TextInput} {
|
||||
padding: 0 10px;
|
||||
max-width: 400px;
|
||||
}
|
||||
`;
|
||||
166
src/components/IndexManage/InstanceContent.js
Normal file
166
src/components/IndexManage/InstanceContent.js
Normal file
@@ -0,0 +1,166 @@
|
||||
import { styled } from 'styled-components';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { TableStyle, TableInfo, ListOption } from '../../styles/Components';
|
||||
|
||||
import Button from '../../components/common/button/Button';
|
||||
import InstanceSearchBar from '../../components/IndexManage/InstanceSearchBar';
|
||||
import { InstanceIndexExport, InstanceIndexView } from '../../apis';
|
||||
|
||||
const InstanceContent = () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
let d = new Date();
|
||||
const START_DATE = new Date(new Date(d.setDate(d.getDate() - 1)).setHours(0, 0, 0, 0));
|
||||
const END_DATE = new Date();
|
||||
|
||||
const [searchKey, setSearchKey] = useState('');
|
||||
const [sendDate, setSendDate] = useState(START_DATE);
|
||||
const [finishDate, setFinishDate] = useState(END_DATE);
|
||||
|
||||
const [dataList, setDataList] = useState([]);
|
||||
useEffect(() => {
|
||||
fetchData('', START_DATE, END_DATE);
|
||||
}, []);
|
||||
|
||||
// console.log(dataList);
|
||||
|
||||
const fetchData = async (data, startDate, endDate) => {
|
||||
const startDateToLocal =
|
||||
startDate.getFullYear() +
|
||||
'-' +
|
||||
(startDate.getMonth() + 1 < 9 ? '0' + (startDate.getMonth() + 1) : startDate.getMonth() + 1) +
|
||||
'-' +
|
||||
(startDate.getDate() < 9 ? '0' + startDate.getDate() : startDate.getDate());
|
||||
|
||||
const endDateToLocal =
|
||||
endDate.getFullYear() +
|
||||
'-' +
|
||||
(endDate.getMonth() + 1 < 9 ? '0' + (endDate.getMonth() + 1) : endDate.getMonth() + 1) +
|
||||
'-' +
|
||||
(endDate.getDate() < 9 ? '0' + endDate.getDate() : endDate.getDate());
|
||||
|
||||
setDataList(await InstanceIndexView(token, data, startDateToLocal, endDateToLocal));
|
||||
|
||||
setSearchKey(data);
|
||||
setSendDate(startDateToLocal);
|
||||
setFinishDate(endDateToLocal);
|
||||
};
|
||||
|
||||
// 엑셀 다운로드
|
||||
const handleXlsxExport = () => {
|
||||
const fileName = 'Caliverse_Instance_Index.xlsx';
|
||||
InstanceIndexExport(token, fileName, searchKey, sendDate, finishDate);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<InstanceSearchBar fetchData={fetchData} />
|
||||
<TableInfo>
|
||||
<ListOption>
|
||||
<Button theme="line" text="엑셀 다운로드" handleClick={handleXlsxExport} />
|
||||
</ListOption>
|
||||
</TableInfo>
|
||||
<TableWrapper>
|
||||
<EconomicTable2>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="240">일자</th>
|
||||
<th rowSpan="2" width="160">
|
||||
2023-08-07
|
||||
</th>
|
||||
<th rowSpan="2" width="160">
|
||||
2023-08-08
|
||||
</th>
|
||||
<th rowSpan="2" width="160">
|
||||
2023-08-09
|
||||
</th>
|
||||
<th rowSpan="2" width="160">
|
||||
2023-08-10
|
||||
</th>
|
||||
<th rowSpan="2" width="160"></th>
|
||||
<th rowSpan="2" width="160"></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>대상</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<TableTitle>(인스턴스 ID) 방문자 수</TableTitle>
|
||||
<TableData>95</TableData>
|
||||
<TableData>240</TableData>
|
||||
<TableData>240</TableData>
|
||||
<TableData>240</TableData>
|
||||
<TableData $state="blank"></TableData>
|
||||
<TableData $state="blank"></TableData>
|
||||
</tr>
|
||||
{/* {mokupData.map((data, index) => (
|
||||
<Fragment key={index}>
|
||||
<tr>
|
||||
<td>{data.date}</td>
|
||||
<td>{data.name}</td>
|
||||
<td>{data.trader}</td>
|
||||
<td>{data.id}</td>
|
||||
<td>{data.key}</td>
|
||||
</tr>
|
||||
</Fragment>
|
||||
))} */}
|
||||
</tbody>
|
||||
</EconomicTable2>
|
||||
</TableWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default InstanceContent;
|
||||
|
||||
const TableWrapper = styled.div`
|
||||
width: 100%;
|
||||
min-width: 680px;
|
||||
overflow: auto;
|
||||
&::-webkit-scrollbar {
|
||||
height: 4px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #666666;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #d9d9d9;
|
||||
}
|
||||
${TableStyle} {
|
||||
width: 100%;
|
||||
min-width: 900px;
|
||||
th {
|
||||
&.cell-nru {
|
||||
background: #f0f0f0;
|
||||
border-left: 1px solid #aaa;
|
||||
border-right: 1px solid #aaa;
|
||||
}
|
||||
}
|
||||
td {
|
||||
&.blank {
|
||||
background: #f9f9f9;
|
||||
}
|
||||
&.cell-nru {
|
||||
background: #fafafa;
|
||||
border-left: 1px solid #aaa;
|
||||
border-right: 1px solid #aaa;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const TableData = styled.td`
|
||||
background: ${props => (props.$state === 'danger' ? '#d60000' : props.$state === 'blank' ? '#F9F9F9' : 'transparent')};
|
||||
color: ${props => (props.$state === 'danger' ? '#fff' : '#2c2c2c')};
|
||||
`;
|
||||
|
||||
const TableTitle = styled.td`
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const EconomicTable2 = styled(TableStyle)`
|
||||
${TableData} {
|
||||
text-align: left;
|
||||
}
|
||||
`;
|
||||
117
src/components/IndexManage/InstanceSearchBar.js
Normal file
117
src/components/IndexManage/InstanceSearchBar.js
Normal file
@@ -0,0 +1,117 @@
|
||||
import { useState } from 'react';
|
||||
import { styled } from 'styled-components';
|
||||
import Button from '../../components/common/button/Button';
|
||||
|
||||
import DatePickerComponent from '../common/Date/DatePickerComponent';
|
||||
|
||||
import { FormWrapper, InputLabel, TextInput, SelectInput, BtnWrapper, InputGroup, DatePickerWrapper } from '../../styles/Components';
|
||||
|
||||
const InstanceSearchBar = ({ fetchData }) => {
|
||||
let d = new Date();
|
||||
const START_DATE = new Date(new Date(d.setDate(d.getDate() - 1)).setHours(0, 0, 0, 0));
|
||||
const END_DATE = new Date();
|
||||
|
||||
const [resultData, setResultData] = useState({
|
||||
search_key: '',
|
||||
send_dt: START_DATE,
|
||||
end_dt: END_DATE,
|
||||
});
|
||||
|
||||
// 발송 날짜 세팅 로직
|
||||
const handleSelectedDate = data => {
|
||||
const sendDate = new Date(data);
|
||||
const resultSendData = new Date(sendDate.getFullYear(), sendDate.getMonth(), sendDate.getDate());
|
||||
|
||||
setResultData({ ...resultData, send_dt: resultSendData });
|
||||
};
|
||||
|
||||
// 발송 날짜 세팅 로직
|
||||
const handleEndDate = data => {
|
||||
const endDate = new Date(data);
|
||||
const resultSendData = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate());
|
||||
|
||||
setResultData({ ...resultData, end_dt: resultSendData });
|
||||
};
|
||||
|
||||
const handleReset = e => {
|
||||
e.preventDefault();
|
||||
setResultData({ search_key: '', send_dt: START_DATE, end_dt: END_DATE });
|
||||
|
||||
fetchData('', START_DATE, END_DATE);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormWrapper>
|
||||
<SearchbarStyle>
|
||||
<SearchItem>
|
||||
<InputLabel>인스턴스 ID</InputLabel>
|
||||
<TextInput type="text" placeholder="ID 입력" onChange={e => setResultData({ ...resultData, search_key: e.target.value })} />
|
||||
</SearchItem>
|
||||
<SearchItem>
|
||||
<InputLabel>집계 기간</InputLabel>
|
||||
<InputGroup>
|
||||
<DatePickerWrapper>
|
||||
<DatePickerComponent
|
||||
name="시작 일자" selectedDate={resultData.send_dt}
|
||||
handleSelectedDate={data => handleSelectedDate(data)}
|
||||
maxDate={new Date()} />
|
||||
<span>~</span>
|
||||
<DatePickerComponent
|
||||
name="종료 일자" selectedDate={resultData.end_dt}
|
||||
handleSelectedDate={data => handleEndDate(data)}
|
||||
pastDate = {resultData.send_dt}
|
||||
maxDate={new Date()} />
|
||||
</DatePickerWrapper>
|
||||
</InputGroup>
|
||||
</SearchItem>
|
||||
<BtnWrapper $gap="8px">
|
||||
<Button
|
||||
theme="reset"
|
||||
handleClick={e => {
|
||||
handleReset(e);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
theme="search"
|
||||
text="집계"
|
||||
handleClick={e => {
|
||||
e.preventDefault();
|
||||
fetchData(resultData.search_key, resultData.send_dt, resultData.end_dt);
|
||||
}}
|
||||
/>
|
||||
</BtnWrapper>
|
||||
</SearchbarStyle>
|
||||
</FormWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default InstanceSearchBar;
|
||||
|
||||
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;
|
||||
|
||||
${TextInput}, ${SelectInput} {
|
||||
height: 35px;
|
||||
}
|
||||
${TextInput} {
|
||||
padding: 0 10px;
|
||||
max-width: 400px;
|
||||
}
|
||||
`;
|
||||
241
src/components/IndexManage/ItemContent.js
Normal file
241
src/components/IndexManage/ItemContent.js
Normal file
@@ -0,0 +1,241 @@
|
||||
import { styled } from 'styled-components';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import Button from '../../components/common/button/Button';
|
||||
|
||||
import { SelectInput, TableStyle, TableInfo, ListOption, InputLabel, InputGroup } from '../../styles/Components';
|
||||
|
||||
import ItemSearchBar from '../../components/IndexManage/ItemSearchBar';
|
||||
import { ItemIndexExport, ItemIndexView } from '../../apis';
|
||||
|
||||
const ItemContent = () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
let d = new Date();
|
||||
const START_DATE = new Date(new Date(d.setDate(d.getDate() - 1)).setHours(0, 0, 0, 0));
|
||||
const END_DATE = new Date();
|
||||
|
||||
const [sendDate, setSendDate] = useState(START_DATE);
|
||||
const [finishDate, setFinishDate] = useState(END_DATE);
|
||||
|
||||
const [dataList, setDataList] = useState([]);
|
||||
useEffect(() => {
|
||||
fetchData(START_DATE, END_DATE);
|
||||
}, []);
|
||||
|
||||
// console.log(dataList);
|
||||
|
||||
const fetchData = async (startDate, endDate) => {
|
||||
const startDateToLocal =
|
||||
startDate.getFullYear() +
|
||||
'-' +
|
||||
(startDate.getMonth() + 1 < 9 ? '0' + (startDate.getMonth() + 1) : startDate.getMonth() + 1) +
|
||||
'-' +
|
||||
(startDate.getDate() < 9 ? '0' + startDate.getDate() : startDate.getDate());
|
||||
|
||||
const endDateToLocal =
|
||||
endDate.getFullYear() +
|
||||
'-' +
|
||||
(endDate.getMonth() + 1 < 9 ? '0' + (endDate.getMonth() + 1) : endDate.getMonth() + 1) +
|
||||
'-' +
|
||||
(endDate.getDate() < 9 ? '0' + endDate.getDate() : endDate.getDate());
|
||||
|
||||
setDataList(await ItemIndexView(token, startDateToLocal, endDateToLocal));
|
||||
|
||||
setSendDate(startDateToLocal);
|
||||
setFinishDate(endDateToLocal);
|
||||
};
|
||||
|
||||
// 엑셀 다운로드
|
||||
const handleXlsxExport = () => {
|
||||
const fileName = 'Caliverse_Item_Index.xlsx';
|
||||
ItemIndexExport(token, fileName, sendDate, finishDate);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ItemSearchBar fetchData={fetchData} />
|
||||
<TableInfo2>
|
||||
<InputGroup>
|
||||
<InputLabel>아이템 코드</InputLabel>
|
||||
<SelectInput>
|
||||
<option value="">15100001</option>
|
||||
<option value="">15100001</option>
|
||||
<option value="">15100001</option>
|
||||
<option value="">15100001</option>
|
||||
</SelectInput>
|
||||
</InputGroup>
|
||||
<ListOption>
|
||||
<Button theme="line" text="엑셀 다운로드" handleClick={handleXlsxExport} />
|
||||
</ListOption>
|
||||
</TableInfo2>
|
||||
<TableWrapper>
|
||||
<EconomicTable>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colSpan="2" className="text-center" width="300">
|
||||
Product
|
||||
</th>
|
||||
<th width="160">2023-08-07</th>
|
||||
<th width="160">2023-08-08</th>
|
||||
<th width="160">2023-08-09</th>
|
||||
<th width="160">2023-08-10</th>
|
||||
<th width="160">2023-08-11</th>
|
||||
<th width="160">2023-08-12</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<TableTitle colSpan="2">(Total) 15100001 생산량</TableTitle>
|
||||
<TableData>240</TableData>
|
||||
<TableData>240</TableData>
|
||||
<TableData>240</TableData>
|
||||
<TableData>240</TableData>
|
||||
<TableData $state="blank"></TableData>
|
||||
<TableData $state="blank"></TableData>
|
||||
</tr>
|
||||
<tr>
|
||||
<TableTitle colSpan="2">(Total) 15100001 소진량</TableTitle>
|
||||
<TableData>110</TableData>
|
||||
<TableData>110</TableData>
|
||||
<TableData>110</TableData>
|
||||
<TableData>110</TableData>
|
||||
<TableData $state="blank"></TableData>
|
||||
<TableData $state="blank"></TableData>
|
||||
</tr>
|
||||
<tr>
|
||||
<TableTitle colSpan="2">(Total) 15100001 보유량</TableTitle>
|
||||
<TableData>3750</TableData>
|
||||
<TableData>3750</TableData>
|
||||
<TableData>3750</TableData>
|
||||
<TableData>3750</TableData>
|
||||
<TableData $state="blank"></TableData>
|
||||
<TableData $state="blank"></TableData>
|
||||
</tr>
|
||||
<tr>
|
||||
<TableTitle rowSpan="2">GET</TableTitle>
|
||||
<TableTitle>퀘스트 보상</TableTitle>
|
||||
<TableData>70</TableData>
|
||||
<TableData>70</TableData>
|
||||
<TableData>70</TableData>
|
||||
<TableData>70</TableData>
|
||||
<TableData $state="blank"></TableData>
|
||||
<TableData $state="blank"></TableData>
|
||||
</tr>
|
||||
<tr>
|
||||
<TableTitle>시즌패스 보상</TableTitle>
|
||||
<TableData>70</TableData>
|
||||
<TableData>70</TableData>
|
||||
<TableData>70</TableData>
|
||||
<TableData>70</TableData>
|
||||
<TableData $state="blank"></TableData>
|
||||
<TableData $state="blank"></TableData>
|
||||
</tr>
|
||||
<tr>
|
||||
<TableTitle rowSpan="2">USE</TableTitle>
|
||||
<TableTitle>퀘스트 보상</TableTitle>
|
||||
<TableData>50</TableData>
|
||||
<TableData>50</TableData>
|
||||
<TableData>50</TableData>
|
||||
<TableData>50</TableData>
|
||||
<TableData $state="blank"></TableData>
|
||||
<TableData $state="blank"></TableData>
|
||||
</tr>
|
||||
<tr>
|
||||
<TableTitle>시즌패스 보상</TableTitle>
|
||||
<TableData>50</TableData>
|
||||
<TableData>50</TableData>
|
||||
<TableData>50</TableData>
|
||||
<TableData>50</TableData>
|
||||
<TableData $state="blank"></TableData>
|
||||
<TableData $state="blank"></TableData>
|
||||
</tr>
|
||||
|
||||
{/* {mokupData.map((data, index) => (
|
||||
<Fragment key={index}>
|
||||
<tr>
|
||||
<td>{data.date}</td>
|
||||
<td>{data.name}</td>
|
||||
<td>{data.trader}</td>
|
||||
<td>{data.id}</td>
|
||||
<td>{data.key}</td>
|
||||
</tr>
|
||||
</Fragment>
|
||||
))} */}
|
||||
</tbody>
|
||||
</EconomicTable>
|
||||
</TableWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ItemContent;
|
||||
|
||||
const TableWrapper = styled.div`
|
||||
width: 100%;
|
||||
min-width: 680px;
|
||||
overflow: auto;
|
||||
&::-webkit-scrollbar {
|
||||
height: 4px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #666666;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #d9d9d9;
|
||||
}
|
||||
${TableStyle} {
|
||||
width: 100%;
|
||||
min-width: 900px;
|
||||
th {
|
||||
&.cell-nru {
|
||||
background: #f0f0f0;
|
||||
border-left: 1px solid #aaa;
|
||||
border-right: 1px solid #aaa;
|
||||
}
|
||||
}
|
||||
td {
|
||||
&.blank {
|
||||
background: #f9f9f9;
|
||||
}
|
||||
&.cell-nru {
|
||||
background: #fafafa;
|
||||
border-left: 1px solid #aaa;
|
||||
border-right: 1px solid #aaa;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const TableInfo2 = styled(TableInfo)`
|
||||
justify-content: space-between;
|
||||
${InputLabel} {
|
||||
font-size: 12px;
|
||||
}
|
||||
${SelectInput} {
|
||||
width: auto;
|
||||
min-width: 100px;
|
||||
height: 24px;
|
||||
}
|
||||
`;
|
||||
|
||||
const TableData = styled.td`
|
||||
background: ${props => (props.$state === 'danger' ? '#d60000' : props.$state === 'blank' ? '#F9F9F9' : 'transparent')};
|
||||
color: ${props => (props.$state === 'danger' ? '#fff' : '#2c2c2c')};
|
||||
`;
|
||||
|
||||
const TableTitle = styled.td`
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const EconomicTable = styled(TableStyle)`
|
||||
${TableData} {
|
||||
text-align: left;
|
||||
}
|
||||
tbody {
|
||||
tr:nth-child(1),
|
||||
tr:nth-child(2),
|
||||
tr:nth-child(3) {
|
||||
background: #f5fcff;
|
||||
}
|
||||
}
|
||||
`;
|
||||
111
src/components/IndexManage/ItemSearchBar.js
Normal file
111
src/components/IndexManage/ItemSearchBar.js
Normal file
@@ -0,0 +1,111 @@
|
||||
import { useState } from 'react';
|
||||
import { styled } from 'styled-components';
|
||||
import Button from '../../components/common/button/Button';
|
||||
import DatePickerComponent from '../common/Date/DatePickerComponent';
|
||||
|
||||
import { FormWrapper, InputLabel, TextInput, SelectInput, BtnWrapper, InputGroup, DatePickerWrapper } from '../../styles/Components';
|
||||
|
||||
const ItemSearchBar = ({ fetchData }) => {
|
||||
let d = new Date();
|
||||
const START_DATE = new Date(new Date(d.setDate(d.getDate() - 1)).setHours(0, 0, 0, 0));
|
||||
const END_DATE = new Date();
|
||||
|
||||
const [resultData, setResultData] = useState({
|
||||
send_dt: START_DATE,
|
||||
end_dt: END_DATE,
|
||||
});
|
||||
|
||||
// 발송 날짜 세팅 로직
|
||||
const handleSelectedDate = data => {
|
||||
const sendDate = new Date(data);
|
||||
const resultSendData = new Date(sendDate.getFullYear(), sendDate.getMonth(), sendDate.getDate());
|
||||
|
||||
setResultData({ ...resultData, send_dt: resultSendData });
|
||||
};
|
||||
|
||||
// 발송 날짜 세팅 로직
|
||||
const handleEndDate = data => {
|
||||
const endDate = new Date(data);
|
||||
const resultSendData = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate());
|
||||
|
||||
setResultData({ ...resultData, end_dt: resultSendData });
|
||||
};
|
||||
|
||||
const handleReset = e => {
|
||||
e.preventDefault();
|
||||
setResultData({ send_dt: START_DATE, end_dt: END_DATE });
|
||||
|
||||
fetchData(START_DATE, END_DATE);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormWrapper>
|
||||
<SearchbarStyle>
|
||||
<SearchItem>
|
||||
<InputLabel>집계 기간</InputLabel>
|
||||
<InputGroup>
|
||||
<DatePickerWrapper>
|
||||
<DatePickerComponent
|
||||
name="시작 일자" selectedDate={resultData.send_dt}
|
||||
handleSelectedDate={data => handleSelectedDate(data)}
|
||||
maxDate={new Date()} />
|
||||
<span>~</span>
|
||||
<DatePickerComponent
|
||||
name="종료 일자" selectedDate={resultData.end_dt}
|
||||
handleSelectedDate={data => handleEndDate(data)}
|
||||
pastDate = {resultData.send_dt}
|
||||
maxDate={new Date()} />
|
||||
</DatePickerWrapper>
|
||||
</InputGroup>
|
||||
</SearchItem>
|
||||
<BtnWrapper $gap="8px">
|
||||
<Button
|
||||
theme="reset"
|
||||
handleClick={e => {
|
||||
handleReset(e);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
theme="search"
|
||||
text="집계"
|
||||
handleClick={e => {
|
||||
e.preventDefault();
|
||||
fetchData(resultData.send_dt, resultData.end_dt);
|
||||
}}
|
||||
/>
|
||||
</BtnWrapper>
|
||||
</SearchbarStyle>
|
||||
</FormWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ItemSearchBar;
|
||||
|
||||
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;
|
||||
|
||||
${TextInput}, ${SelectInput} {
|
||||
height: 35px;
|
||||
}
|
||||
${TextInput} {
|
||||
padding: 0 10px;
|
||||
max-width: 400px;
|
||||
}
|
||||
`;
|
||||
112
src/components/IndexManage/PlayTimeContent.js
Normal file
112
src/components/IndexManage/PlayTimeContent.js
Normal file
@@ -0,0 +1,112 @@
|
||||
import { Fragment, useEffect, useState } from 'react';
|
||||
|
||||
import Button from '../../components/common/button/Button';
|
||||
|
||||
import { TableStyle, TableInfo, ListOption, IndexTableWrap } from '../../styles/Components';
|
||||
import { PlayTimeSearchBar } from '../../components/IndexManage/index';
|
||||
import { PlaytimeIndexExport, PlaytimeIndexView } from '../../apis';
|
||||
|
||||
const PlayTimeContent = () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
let d = new Date();
|
||||
const START_DATE = new Date(new Date(d.setDate(d.getDate() - 1)).setHours(0, 0, 0, 0));
|
||||
const END_DATE = new Date();
|
||||
|
||||
const [dataList, setDataList] = useState([]);
|
||||
const [resultData, setResultData] = useState([]);
|
||||
|
||||
const [sendDate, setSendDate] = useState(START_DATE);
|
||||
const [finishDate, setFinishDate] = useState(END_DATE);
|
||||
|
||||
useEffect(() => {
|
||||
fetchData(START_DATE, END_DATE);
|
||||
}, []);
|
||||
|
||||
// 이용자 지표 데이터
|
||||
const fetchData = async (startDate, endDate) => {
|
||||
const startDateToLocal =
|
||||
startDate.getFullYear() +
|
||||
'-' +
|
||||
(startDate.getMonth() + 1 < 9 ? '0' + (startDate.getMonth() + 1) : startDate.getMonth() + 1) +
|
||||
'-' +
|
||||
(startDate.getDate() < 9 ? '0' + startDate.getDate() : startDate.getDate());
|
||||
|
||||
const endDateToLocal =
|
||||
endDate.getFullYear() +
|
||||
'-' +
|
||||
(endDate.getMonth() + 1 < 9 ? '0' + (endDate.getMonth() + 1) : endDate.getMonth() + 1) +
|
||||
'-' +
|
||||
(endDate.getDate() < 9 ? '0' + endDate.getDate() : endDate.getDate());
|
||||
|
||||
setDataList(await PlaytimeIndexView(token, startDateToLocal, endDateToLocal));
|
||||
|
||||
setSendDate(startDateToLocal);
|
||||
setFinishDate(endDateToLocal);
|
||||
};
|
||||
|
||||
// 검색 함수
|
||||
const handleSearch = (send_dt, end_dt) => {
|
||||
fetchData(send_dt, end_dt);
|
||||
};
|
||||
|
||||
// 엑셀 다운로드
|
||||
const handleXlsxExport = () => {
|
||||
const fileName = 'Caliverse_PlayTime_Index.xlsx';
|
||||
PlaytimeIndexExport(token, fileName, sendDate, finishDate);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<PlayTimeSearchBar setResultData={setResultData} resultData={resultData} handleSearch={handleSearch} fetchData={fetchData} />
|
||||
<TableInfo>
|
||||
<ListOption>
|
||||
<Button theme="line" text="엑셀 다운로드" handleClick={handleXlsxExport} />
|
||||
</ListOption>
|
||||
</TableInfo>
|
||||
<IndexTableWrap>
|
||||
<TableStyle>
|
||||
<caption></caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th rowSpan="2" width="140">
|
||||
일자
|
||||
</th>
|
||||
<th colSpan="4" width="520">
|
||||
유저수
|
||||
</th>
|
||||
<th rowSpan="2" width="160">
|
||||
총 누적 플레이타임(분)
|
||||
</th>
|
||||
<th rowSpan="2" width="160">
|
||||
1인당 평균 플레이타임(분)
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>30분 이내</th>
|
||||
<th>30분 ~ 1시간</th>
|
||||
<th>1시간 ~ 3시간</th>
|
||||
<th>3시간 이상</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{dataList.playtime &&
|
||||
dataList.playtime.map(time => (
|
||||
<tr key={time.date}>
|
||||
<td>{time.date}</td>
|
||||
{time.user_cnt.map((cnt, index) => (
|
||||
<td className="text-left" key={index}>
|
||||
{cnt}
|
||||
</td>
|
||||
))}
|
||||
<td className="text-left">{time.total_time}</td>
|
||||
<td className="text-left">{time.average_time}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</TableStyle>
|
||||
</IndexTableWrap>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PlayTimeContent;
|
||||
122
src/components/IndexManage/PlayTimeSearchBar.js
Normal file
122
src/components/IndexManage/PlayTimeSearchBar.js
Normal file
@@ -0,0 +1,122 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { styled } from 'styled-components';
|
||||
import Button from '../../components/common/button/Button';
|
||||
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
import DatePickerComponent from '../common/Date/DatePickerComponent';
|
||||
|
||||
import { FormWrapper, InputLabel, TextInput, SelectInput, BtnWrapper, InputGroup, DatePickerWrapper } from '../../styles/Components';
|
||||
|
||||
const PlayTimeSearchBar = ({ resultData, setResultData, handleSearch, fetchData }) => {
|
||||
// 초기 날짜 세팅
|
||||
let d = new Date();
|
||||
const START_DATE = new Date(new Date(d.setDate(d.getDate() - 1)).setHours(0, 0, 0, 0));
|
||||
const END_DATE = new Date();
|
||||
|
||||
// resultData에 임의 날짜 넣어주기
|
||||
useEffect(() => {
|
||||
setResultData({
|
||||
send_dt: START_DATE,
|
||||
end_dt: END_DATE,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const searchType = [
|
||||
{ name: '전체', value: 'ALL' },
|
||||
{ name: '한국어', value: 'KR' },
|
||||
{ name: '영어', value: 'EN' },
|
||||
{ name: '일본어', value: 'JP' },
|
||||
{ name: '태국어', value: 'TH' },
|
||||
];
|
||||
|
||||
// 발송 날짜 세팅 로직
|
||||
const handleSelectedDate = data => {
|
||||
const sendDate = new Date(data);
|
||||
const resultSendData = new Date(sendDate.getFullYear(), sendDate.getMonth(), sendDate.getDate());
|
||||
|
||||
setResultData({ ...resultData, send_dt: resultSendData });
|
||||
};
|
||||
|
||||
// 발송 날짜 세팅 로직
|
||||
const handleEndDate = data => {
|
||||
const endDate = new Date(data);
|
||||
const resultSendData = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate());
|
||||
|
||||
setResultData({ ...resultData, end_dt: resultSendData });
|
||||
};
|
||||
|
||||
const handleReset = e => {
|
||||
e.preventDefault();
|
||||
setResultData({ send_dt: START_DATE, end_dt: END_DATE });
|
||||
|
||||
fetchData(START_DATE, END_DATE);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormWrapper>
|
||||
<SearchbarStyle>
|
||||
<SearchItem>
|
||||
<InputLabel>집계 기준일</InputLabel>
|
||||
<InputGroup>
|
||||
<DatePickerWrapper>
|
||||
<DatePickerComponent
|
||||
name="시작 일자"
|
||||
selectedDate={resultData.send_dt}
|
||||
handleSelectedDate={data => handleSelectedDate(data)}
|
||||
maxDate={new Date()} />
|
||||
<span>~</span>
|
||||
<DatePickerComponent
|
||||
name="종료 일자"
|
||||
selectedDate={resultData.end_dt}
|
||||
handleSelectedDate={data => handleEndDate(data)}
|
||||
pastDate = {resultData.send_dt}
|
||||
maxDate={new Date()} />
|
||||
</DatePickerWrapper>
|
||||
</InputGroup>
|
||||
</SearchItem>
|
||||
<BtnWrapper $gap="8px">
|
||||
<Button theme="reset" handleClick={handleReset} />
|
||||
<Button
|
||||
theme="search"
|
||||
text="검색"
|
||||
handleClick={e => {
|
||||
e.preventDefault();
|
||||
handleSearch(resultData.send_dt, resultData.end_dt);
|
||||
}}
|
||||
/>
|
||||
</BtnWrapper>
|
||||
</SearchbarStyle>
|
||||
</FormWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PlayTimeSearchBar;
|
||||
|
||||
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;
|
||||
|
||||
${TextInput}, ${SelectInput} {
|
||||
height: 35px;
|
||||
}
|
||||
${TextInput} {
|
||||
padding: 0 10px;
|
||||
max-width: 400px;
|
||||
}
|
||||
`;
|
||||
109
src/components/IndexManage/RetentionContent.js
Normal file
109
src/components/IndexManage/RetentionContent.js
Normal file
@@ -0,0 +1,109 @@
|
||||
import { Fragment, useEffect, useState } from 'react';
|
||||
|
||||
import Button from '../../components/common/button/Button';
|
||||
|
||||
import { TableStyle, TableInfo, ListOption, IndexTableWrap } from '../../styles/Components';
|
||||
import { RetentionSearchBar } from '../../components/IndexManage/index';
|
||||
import { RetentionIndexExport, RetentionIndexView } from '../../apis';
|
||||
|
||||
const RetentionContent = () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
let d = new Date();
|
||||
const START_DATE = new Date(new Date(d.setDate(d.getDate() - 1)).setHours(0, 0, 0, 0));
|
||||
const END_DATE = new Date(new Date(d.setDate(d.getDate() - 1)).setHours(24, 0, 0, 0));
|
||||
|
||||
const [dataList, setDataList] = useState([]);
|
||||
const [resultData, setResultData] = useState([]);
|
||||
const [retentionData, setRetention] = useState(1);
|
||||
|
||||
const [sendDate, setSendDate] = useState(START_DATE);
|
||||
const [finishDate, setFinishDate] = useState(END_DATE);
|
||||
const [excelBtn, setExcelBtn] = useState(true); //true 시 비활성화
|
||||
|
||||
useEffect(() => {
|
||||
fetchData(START_DATE, END_DATE);
|
||||
}, []);
|
||||
|
||||
// Retention 지표 데이터
|
||||
const fetchData = async (startDate, endDate) => {
|
||||
const startDateToLocal =
|
||||
startDate.getFullYear() +
|
||||
'-' +
|
||||
(startDate.getMonth() + 1 < 9 ? '0' + (startDate.getMonth() + 1) : startDate.getMonth() + 1) +
|
||||
'-' +
|
||||
(startDate.getDate() < 9 ? '0' + startDate.getDate() : startDate.getDate());
|
||||
|
||||
const endDateToLocal =
|
||||
endDate.getFullYear() +
|
||||
'-' +
|
||||
(endDate.getMonth() + 1 < 9 ? '0' + (endDate.getMonth() + 1) : endDate.getMonth() + 1) +
|
||||
'-' +
|
||||
(endDate.getDate() < 9 ? '0' + endDate.getDate() : endDate.getDate());
|
||||
|
||||
setDataList(await RetentionIndexView(token, startDateToLocal, endDateToLocal));
|
||||
|
||||
console.log(dataList);
|
||||
|
||||
setSendDate(startDateToLocal);
|
||||
setFinishDate(endDateToLocal);
|
||||
};
|
||||
|
||||
// 검색 함수
|
||||
const handleSearch = (send_dt, end_dt) => {
|
||||
fetchData(send_dt, end_dt);
|
||||
setRetention(resultData.retention);
|
||||
};
|
||||
|
||||
// 엑셀 다운로드
|
||||
const handleXlsxExport = () => {
|
||||
const fileName = 'Caliverse_Retention_Index.xlsx';
|
||||
|
||||
if(!excelBtn){
|
||||
RetentionIndexExport(token, fileName, sendDate, finishDate);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<RetentionSearchBar setResultData={setResultData} resultData={resultData}
|
||||
handleSearch={handleSearch} fetchData={fetchData} setRetention={setRetention} setExcelBtn={setExcelBtn} />
|
||||
<TableInfo>
|
||||
<ListOption>
|
||||
<Button
|
||||
theme={excelBtn === true ? "disable" : "line"}
|
||||
text="엑셀 다운로드"
|
||||
disabled={handleXlsxExport}
|
||||
handleClick={handleXlsxExport} />
|
||||
</ListOption>
|
||||
</TableInfo>
|
||||
<IndexTableWrap>
|
||||
<TableStyle>
|
||||
<caption></caption>
|
||||
<thead>
|
||||
<tr>
|
||||
{/* <th width="100">국가</th> */}
|
||||
<th width="150">일자</th>
|
||||
<th className="cell-nru">NRU</th>
|
||||
{[...Array(Number(retentionData))].map((value, index) => {
|
||||
return <th key={index}>{`D+${index + 1}`}</th>;
|
||||
})}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{dataList.retention &&
|
||||
dataList.retention.map(data => (
|
||||
<tr className="cell-nru-th" key={data.date}>
|
||||
<td>{data.date}</td>
|
||||
{data['d-day'].map((day, index) => (
|
||||
<td key={index}>{day.dif}</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</TableStyle>
|
||||
</IndexTableWrap>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default RetentionContent;
|
||||
166
src/components/IndexManage/RetentionSearchBar.js
Normal file
166
src/components/IndexManage/RetentionSearchBar.js
Normal file
@@ -0,0 +1,166 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { styled } from 'styled-components';
|
||||
import Button from '../../components/common/button/Button';
|
||||
import DatePickerComponent from '../common/Date/DatePickerComponent';
|
||||
|
||||
import { FormWrapper, InputLabel, TextInput, SelectInput, BtnWrapper, InputGroup, DatePickerWrapper, AlertText } from '../../styles/Components';
|
||||
|
||||
const RetentionSearchBar = ({ resultData, setResultData, handleSearch, fetchData, setRetention, setExcelBtn }) => {
|
||||
// 초기 날짜 세팅
|
||||
let d = new Date();
|
||||
const START_DATE = new Date(new Date(d.setDate(d.getDate() - 1)).setHours(0, 0, 0, 0));
|
||||
const END_DATE = new Date(new Date(d.setDate(d.getDate() - 1)).setHours(24, 0, 0, 0));
|
||||
const [errorMessage, setErrorMessage] = useState('');
|
||||
const [period, setPeriod] = useState(0);
|
||||
|
||||
// resultData에 임의 날짜 넣어주기
|
||||
useEffect(() => {
|
||||
setResultData({
|
||||
send_dt: START_DATE,
|
||||
end_dt: '',
|
||||
retention: 0,
|
||||
});
|
||||
}, []);
|
||||
|
||||
// 발송 날짜 세팅 로직
|
||||
const handleSelectedDate = data => {
|
||||
const sendDate = new Date(data);
|
||||
const resultSendData = new Date(sendDate.getFullYear(), sendDate.getMonth(), sendDate.getDate());
|
||||
|
||||
const resultEndDate = new Date(resultSendData);
|
||||
resultEndDate.setDate(resultEndDate.getDate() + Number(resultData.retention));
|
||||
|
||||
setResultData({ ...resultData, send_dt: resultSendData, end_dt: resultEndDate });
|
||||
};
|
||||
|
||||
// // 발송 날짜 세팅 로직
|
||||
// const handleEndDate = data => {
|
||||
// const endDate = new Date(data);
|
||||
// const resultSendData = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate());
|
||||
|
||||
// setResultData({ ...resultData, end_dt: resultSendData });
|
||||
// };
|
||||
|
||||
// Retention 세팅 로직
|
||||
const handleRetention = e => {
|
||||
const value = e.target.value;
|
||||
|
||||
const resultEndDate = new Date(resultData.send_dt);
|
||||
resultEndDate.setDate(resultEndDate.getDate() + Number(value));
|
||||
|
||||
setResultData({ ...resultData, end_dt: resultEndDate, retention: value });
|
||||
setPeriod(value);
|
||||
};
|
||||
|
||||
//Retention 범위 선택 후 disable 처리 로직
|
||||
const handleSearchBtn = e => {
|
||||
e.preventDefault();
|
||||
|
||||
if(period == 0) {
|
||||
setErrorMessage("필수값을 선택하세요.");
|
||||
return false;
|
||||
} else {
|
||||
setErrorMessage("");
|
||||
setExcelBtn(false); //활성화
|
||||
handleSearch(resultData.send_dt, resultData.end_dt);
|
||||
}
|
||||
}
|
||||
|
||||
const handleReset = e => {
|
||||
e.preventDefault();
|
||||
setResultData({ send_dt: START_DATE, end_dt: '', retention: 0 });
|
||||
setRetention(1);
|
||||
setErrorMessage("");
|
||||
setPeriod(1);
|
||||
setExcelBtn(true); //비활성화
|
||||
|
||||
fetchData(START_DATE, END_DATE);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormWrapper>
|
||||
<SearchbarStyle>
|
||||
<SearchItem>
|
||||
<InputLabel>집계 기준일</InputLabel>
|
||||
<InputGroup>
|
||||
<DatePickerWrapper>
|
||||
<DatePickerComponent
|
||||
name="시작 일자" selectedDate={resultData.send_dt}
|
||||
handleSelectedDate={data => handleSelectedDate(data)}
|
||||
maxDate={new Date()} />
|
||||
<span>~</span>
|
||||
<DatePickerComponent
|
||||
name="종료 일자"
|
||||
selectedDate={resultData.end_dt}
|
||||
maxDate={new Date()}
|
||||
readOnly={true}
|
||||
disabled={true}
|
||||
type="retention" />
|
||||
</DatePickerWrapper>
|
||||
</InputGroup>
|
||||
</SearchItem>
|
||||
<SearchItem>
|
||||
<InputLabel>Retention 범위</InputLabel>
|
||||
<SelectInput
|
||||
onChange={e => handleRetention(e)} value={resultData.retention}>
|
||||
<option value={0}>선택</option>
|
||||
<option value={1}>D+1</option>
|
||||
<option value={7}>D+7</option>
|
||||
<option value={30}>D+30</option>
|
||||
</SelectInput>
|
||||
</SearchItem>
|
||||
{/* 기획 보류 */}
|
||||
{/* <SearchItem>
|
||||
<InputLabel>조회 국가</InputLabel>
|
||||
<SelectInput>
|
||||
<option value="">ALL</option>
|
||||
<option value="">KR</option>
|
||||
<option value="">EN</option>
|
||||
<option value="">JP</option>
|
||||
<option value="">TH</option>
|
||||
</SelectInput>
|
||||
</SearchItem> */}
|
||||
<BtnWrapper $gap="8px">
|
||||
<Button theme="reset" handleClick={handleReset} />
|
||||
<Button
|
||||
theme="search"
|
||||
text="검색"
|
||||
handleClick={handleSearchBtn}
|
||||
/>
|
||||
<AlertText>{errorMessage}</AlertText>
|
||||
</BtnWrapper>
|
||||
</SearchbarStyle>
|
||||
</FormWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default RetentionSearchBar;
|
||||
|
||||
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;
|
||||
|
||||
${TextInput}, ${SelectInput} {
|
||||
height: 35px;
|
||||
}
|
||||
${TextInput} {
|
||||
padding: 0 10px;
|
||||
max-width: 400px;
|
||||
}
|
||||
`;
|
||||
90
src/components/IndexManage/SegmentContent.js
Normal file
90
src/components/IndexManage/SegmentContent.js
Normal file
@@ -0,0 +1,90 @@
|
||||
import { Fragment, useEffect, useState } from 'react';
|
||||
|
||||
import Button from '../../components/common/button/Button';
|
||||
|
||||
import { TableStyle, TableInfo, ListOption, IndexTableWrap } from '../../styles/Components';
|
||||
import { SegmentSearchBar } from '../../components/IndexManage/index';
|
||||
import { SegmentIndexExport, SegmentIndexView } from '../../apis';
|
||||
|
||||
const SegmentContent = () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
const END_DATE = new Date();
|
||||
|
||||
const [dataList, setDataList] = useState([]);
|
||||
const [resultData, setResultData] = useState([]);
|
||||
|
||||
const [finishDate, setFinishDate] = useState(END_DATE);
|
||||
|
||||
useEffect(() => {
|
||||
fetchData(END_DATE);
|
||||
}, []);
|
||||
|
||||
// Retention 지표 데이터
|
||||
const fetchData = async endDate => {
|
||||
const endDateToLocal =
|
||||
endDate.getFullYear() +
|
||||
'-' +
|
||||
(endDate.getMonth() + 1 < 9 ? '0' + (endDate.getMonth() + 1) : endDate.getMonth() + 1) +
|
||||
'-' +
|
||||
(endDate.getDate() < 9 ? '0' + endDate.getDate() : endDate.getDate());
|
||||
|
||||
setDataList(await SegmentIndexView(token, endDateToLocal));
|
||||
setFinishDate(endDateToLocal);
|
||||
};
|
||||
|
||||
// 검색 함수
|
||||
const handleSearch = end_dt => {
|
||||
fetchData(end_dt);
|
||||
};
|
||||
|
||||
// 엑셀 다운로드
|
||||
const handleXlsxExport = () => {
|
||||
const fileName = 'Caliverse_Segment_Index.xlsx';
|
||||
SegmentIndexExport(token, fileName, finishDate);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<SegmentSearchBar setResultData={setResultData} resultData={resultData} handleSearch={handleSearch} fetchData={fetchData} />
|
||||
<TableInfo>
|
||||
<ListOption>
|
||||
<Button theme="line" text="엑셀 다운로드" handleClick={handleXlsxExport} />
|
||||
</ListOption>
|
||||
</TableInfo>
|
||||
<IndexTableWrap>
|
||||
<TableStyle>
|
||||
<caption></caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colSpan="1" width="200">
|
||||
{dataList && dataList.start_dt} ~ {dataList && dataList.end_dt}
|
||||
</th>
|
||||
<th colSpan="2" width="400">
|
||||
KIP
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
{/* <th>국가</th> */}
|
||||
<th>세그먼트 분류</th>
|
||||
<th>AU</th>
|
||||
<th>AU Percentage by User Segment (%)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{dataList && dataList.segment &&
|
||||
dataList.segment.map((segment, index) => (
|
||||
<tr key={index}>
|
||||
{/* <td rowSpan="6">TH</td> */}
|
||||
<td>{segment.type}</td>
|
||||
<td>{segment.au}</td>
|
||||
<td>{segment.dif}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</TableStyle>
|
||||
</IndexTableWrap>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SegmentContent;
|
||||
123
src/components/IndexManage/SegmentSearchBar.js
Normal file
123
src/components/IndexManage/SegmentSearchBar.js
Normal file
@@ -0,0 +1,123 @@
|
||||
import { useEffect } from 'react';
|
||||
import { styled } from 'styled-components';
|
||||
import Button from '../../components/common/button/Button';
|
||||
import DatePickerComponent from '../common/Date/DatePickerComponent';
|
||||
|
||||
import { FormWrapper, InputLabel, TextInput, SelectInput, BtnWrapper, InputGroup, DatePickerWrapper } from '../../styles/Components';
|
||||
|
||||
const SegmentSearchBar = ({ resultData, setResultData, handleSearch, fetchData }) => {
|
||||
// 초기 날짜 세팅
|
||||
const END_DATE = new Date();
|
||||
|
||||
// resultData에 임의 날짜 넣어주기
|
||||
useEffect(() => {
|
||||
setResultData({
|
||||
search_dt: END_DATE,
|
||||
});
|
||||
}, []);
|
||||
|
||||
// 발송 날짜 세팅 로직
|
||||
const handleSelectedDate = data => {
|
||||
const sendDate = new Date(data);
|
||||
const resultSendData = new Date(sendDate.getFullYear(), sendDate.getMonth(), sendDate.getDate());
|
||||
|
||||
setResultData({ search_dt: resultSendData });
|
||||
};
|
||||
|
||||
// 발송 날짜 세팅 로직
|
||||
|
||||
const handleReset = e => {
|
||||
e.preventDefault();
|
||||
setResultData({ search_dt: END_DATE });
|
||||
|
||||
fetchData(END_DATE);
|
||||
};
|
||||
|
||||
// console.log(resultData);
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormWrapper>
|
||||
<SearchbarStyle2>
|
||||
<SearchRow>
|
||||
<SearchItem>
|
||||
<InputLabel>집계 기준일</InputLabel>
|
||||
<InputGroup>
|
||||
<DatePickerWrapper>
|
||||
<DatePickerComponent name="집계 기준일" selectedDate={resultData.search_dt} handleSelectedDate={data => handleSelectedDate(data)} maxDate={new Date()} />
|
||||
</DatePickerWrapper>
|
||||
</InputGroup>
|
||||
</SearchItem>
|
||||
{/* 기획 보류 */}
|
||||
{/* <SearchItem>
|
||||
<InputLabel>조회 국가</InputLabel>
|
||||
<SelectInput>
|
||||
<option value="">ALL</option>
|
||||
<option value="">KR</option>
|
||||
<option value="">EN</option>
|
||||
<option value="">JP</option>
|
||||
<option value="">TH</option>
|
||||
</SelectInput>
|
||||
</SearchItem> */}
|
||||
<BtnWrapper $gap="8px">
|
||||
<Button theme="reset" handleClick={handleReset} />
|
||||
<Button
|
||||
theme="search"
|
||||
text="검색"
|
||||
handleClick={e => {
|
||||
e.preventDefault();
|
||||
handleSearch(resultData.search_dt);
|
||||
}}
|
||||
/>
|
||||
</BtnWrapper>
|
||||
</SearchRow>
|
||||
<SearchNoti>※ 집계 기준일 기준 -100일까지의 Segment 유저 분포를 집계합니다.</SearchNoti>
|
||||
</SearchbarStyle2>
|
||||
</FormWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SegmentSearchBar;
|
||||
|
||||
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 SearchbarStyle2 = styled(SearchbarStyle)`
|
||||
flex-flow: column;
|
||||
gap: 10px;
|
||||
`;
|
||||
|
||||
const SearchRow = styled.div`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px 0;
|
||||
`;
|
||||
const SearchItem = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
margin-right: 50px;
|
||||
|
||||
${TextInput}, ${SelectInput} {
|
||||
height: 35px;
|
||||
}
|
||||
${TextInput} {
|
||||
padding: 0 10px;
|
||||
max-width: 400px;
|
||||
}
|
||||
`;
|
||||
const SearchNoti = styled.div`
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
letter-spacing: 0.5px;
|
||||
`;
|
||||
133
src/components/IndexManage/UserContent.js
Normal file
133
src/components/IndexManage/UserContent.js
Normal file
@@ -0,0 +1,133 @@
|
||||
import { Fragment, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import Button from '../../components/common/button/Button';
|
||||
|
||||
import { Title, TableStyle, TableInfo, ListOption, IndexTableWrap } from '../../styles/Components';
|
||||
import { UserIndexSearchBar, DailyDashBoard } from '../../components/IndexManage/index';
|
||||
|
||||
import { userIndexView, userIndexExport } from '../../apis';
|
||||
import Loading from '../common/Loading';
|
||||
import { ExcelDownButton } from '../common';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { formatStringDate } from '../../utils';
|
||||
|
||||
const UserContent = () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
const { t } = useTranslation();
|
||||
let d = new Date();
|
||||
const START_DATE = new Date(new Date(d.setDate(d.getDate() - 1)).setHours(0, 0, 0, 0));
|
||||
const END_DATE = new Date();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const tableRef = useRef(null);
|
||||
|
||||
const [dataList, setDataList] = useState([]);
|
||||
const [resultData, setResultData] = useState([]);
|
||||
|
||||
// const [sendDate, setSendDate] = useState(START_DATE);
|
||||
// const [finishDate, setFinishDate] = useState(END_DATE);
|
||||
|
||||
const headers = [
|
||||
{key: 'date', label: '일자'},
|
||||
{key: 'nru', label: 'NRU'},
|
||||
{key: 'ugqCreate', label: '일자'},
|
||||
{key: 'dglc', label: '일자'},
|
||||
{key: 'dau', label: '일자'},
|
||||
{key: 'mcu', label: '일자'},
|
||||
{key: 'date', label: '일자'},
|
||||
{key: 'date', label: '일자'},
|
||||
]
|
||||
|
||||
useEffect(() => {
|
||||
fetchData(START_DATE, END_DATE);
|
||||
}, []);
|
||||
|
||||
// 이용자 지표 데이터
|
||||
const fetchData = async (startDate, endDate) => {
|
||||
setLoading(true);
|
||||
|
||||
const startDateToLocal = formatStringDate(startDate);
|
||||
const endDateToLocal = formatStringDate(endDate);
|
||||
|
||||
await userIndexView(token, startDateToLocal, endDateToLocal).then(data => {
|
||||
setDataList(data);
|
||||
setLoading(false);
|
||||
});
|
||||
|
||||
// setSendDate(startDateToLocal);
|
||||
// setFinishDate(endDateToLocal);
|
||||
};
|
||||
|
||||
// 검색 함수
|
||||
const handleSearch = (send_dt, end_dt) => {
|
||||
fetchData(send_dt, end_dt);
|
||||
};
|
||||
|
||||
// 엑셀 다운로드
|
||||
// const handleXlsxExport = () => {
|
||||
// const fileName = 'Caliverse_User_Index.xlsx';
|
||||
// userIndexExport(token, fileName, sendDate, finishDate);
|
||||
// };
|
||||
|
||||
return (
|
||||
<>
|
||||
<DailyDashBoard />
|
||||
<UserIndexSearchBar setResultData={setResultData} resultData={resultData} handleSearch={handleSearch} fetchData={fetchData} />
|
||||
<TableInfo>
|
||||
<ListOption>
|
||||
<ExcelDownButton tableRef={tableRef} fileName={t('FILE_INDEX_USER_CONTENT')} />
|
||||
</ListOption>
|
||||
</TableInfo>
|
||||
<IndexTableWrap>
|
||||
<TableStyle ref={tableRef}>
|
||||
<caption></caption>
|
||||
<thead>
|
||||
<tr>
|
||||
{/*<th width="100">국가</th>*/}
|
||||
<th width="120">일자</th>
|
||||
<th width="100">NRU</th>
|
||||
<th width="100">UGQ(생성)</th>
|
||||
<th width="100">DGLC</th>
|
||||
<th width="100">DAU</th>
|
||||
<th width="100">MCU</th>
|
||||
<th width="100">총 Playtime(HR)</th>
|
||||
<th width="100">평균 Playtime</th>
|
||||
<th width="100">WAU</th>
|
||||
<th width="100">MAU</th>
|
||||
<th width="100">ServerCount</th>
|
||||
<th width="100">DB Read Count</th>
|
||||
<th width="100">DB Write Count</th>
|
||||
<th width="100">DB Sum</th>
|
||||
{/*<th width="200">PU</th>*/}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{dataList &&
|
||||
dataList.map((data, index) => (
|
||||
<tr key={index}>
|
||||
{/*<td rowSpan={data.length}>ALL</td>*/}
|
||||
<td>{data.date}</td>
|
||||
<td>{data.nru}</td>
|
||||
<td>{data.ugqCreate}</td>
|
||||
<td>{data.dglc}</td>
|
||||
<td>{data.dau}</td>
|
||||
<td>{data.mcu}</td>
|
||||
<td>{Math.ceil(data.playtime / 3600) || 0}</td>
|
||||
<td>{Math.round(((data.playtime / 3600) / data.dau) * 100) / 100 || 0}</td>
|
||||
<td>{data.wau}</td>
|
||||
<td>{data.mau}</td>
|
||||
<td>{data.serverCount}</td>
|
||||
<td>{data.readCapacity}</td>
|
||||
<td>{data.writeCapacity}</td>
|
||||
<td>{data.readCapacity + data.writeCapacity}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</TableStyle>
|
||||
</IndexTableWrap>
|
||||
{loading && <Loading/>}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserContent;
|
||||
135
src/components/IndexManage/UserIndexSearchBar.js
Normal file
135
src/components/IndexManage/UserIndexSearchBar.js
Normal file
@@ -0,0 +1,135 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { styled } from 'styled-components';
|
||||
import Button from '../../components/common/button/Button';
|
||||
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
import DatePickerComponent from '../common/Date/DatePickerComponent';
|
||||
|
||||
import { FormWrapper, InputLabel, TextInput, SelectInput, BtnWrapper, InputGroup, DatePickerWrapper } from '../../styles/Components';
|
||||
|
||||
const UserIndexSearchBar = ({ resultData, setResultData, handleSearch, fetchData }) => {
|
||||
// 초기 날짜 세팅
|
||||
let d = new Date();
|
||||
const START_DATE = new Date(new Date(d.setDate(d.getDate() - 1)).setHours(0, 0, 0, 0));
|
||||
const END_DATE = new Date();
|
||||
|
||||
// resultData에 임의 날짜 넣어주기
|
||||
useEffect(() => {
|
||||
setResultData({
|
||||
send_dt: START_DATE,
|
||||
end_dt: END_DATE,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const searchType = [
|
||||
{ name: '전체', value: 'ALL' },
|
||||
{ name: '한국어', value: 'KR' },
|
||||
{ name: '영어', value: 'EN' },
|
||||
{ name: '일본어', value: 'JP' },
|
||||
{ name: '태국어', value: 'TH' },
|
||||
];
|
||||
|
||||
// 발송 날짜 세팅 로직
|
||||
const handleSelectedDate = data => {
|
||||
const sendDate = new Date(data);
|
||||
const resultSendData = new Date(sendDate.getFullYear(), sendDate.getMonth(), sendDate.getDate());
|
||||
|
||||
setResultData({ ...resultData, send_dt: resultSendData });
|
||||
};
|
||||
|
||||
// 발송 날짜 세팅 로직
|
||||
const handleEndDate = data => {
|
||||
const endDate = new Date(data);
|
||||
const resultSendData = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate());
|
||||
|
||||
setResultData({ ...resultData, end_dt: resultSendData });
|
||||
};
|
||||
|
||||
const handleReset = e => {
|
||||
e.preventDefault();
|
||||
setResultData({ send_dt: START_DATE, end_dt: END_DATE });
|
||||
|
||||
fetchData(START_DATE, END_DATE);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormWrapper>
|
||||
<SearchbarStyle>
|
||||
<SearchItem>
|
||||
<InputLabel>집계 기간</InputLabel>
|
||||
<InputGroup>
|
||||
<DatePickerWrapper>
|
||||
<DatePickerComponent
|
||||
name="시작 일자"
|
||||
selectedDate={resultData.send_dt}
|
||||
handleSelectedDate={data => handleSelectedDate(data)}
|
||||
maxDate={new Date()} />
|
||||
<span>~</span>
|
||||
<DatePickerComponent
|
||||
name="종료 일자"
|
||||
selectedDate={resultData.end_dt}
|
||||
handleSelectedDate={data => handleEndDate(data)}
|
||||
pastDate = {resultData.send_dt}
|
||||
maxDate={new Date()} />
|
||||
</DatePickerWrapper>
|
||||
</InputGroup>
|
||||
</SearchItem>
|
||||
{/* 기획 보류 */}
|
||||
{/* <SearchItem>
|
||||
<InputLabel>조회 국가</InputLabel>
|
||||
<SelectInput
|
||||
// onChange={e => setResultData({ ...resultData, searchType: e.target.value })}
|
||||
>
|
||||
{searchType.map((option, index) => (
|
||||
<option key={index} value={option.value}>
|
||||
{option.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</SearchItem> */}
|
||||
<BtnWrapper $gap="8px">
|
||||
<Button theme="reset" handleClick={handleReset} />
|
||||
<Button
|
||||
theme="search"
|
||||
text="검색"
|
||||
handleClick={e => {
|
||||
e.preventDefault();
|
||||
handleSearch(resultData.send_dt, resultData.end_dt);
|
||||
}}
|
||||
/>
|
||||
</BtnWrapper>
|
||||
</SearchbarStyle>
|
||||
</FormWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserIndexSearchBar;
|
||||
|
||||
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;
|
||||
|
||||
${TextInput}, ${SelectInput} {
|
||||
height: 35px;
|
||||
}
|
||||
${TextInput} {
|
||||
padding: 0 10px;
|
||||
max-width: 400px;
|
||||
}
|
||||
`;
|
||||
219
src/components/IndexManage/VBPContent.js
Normal file
219
src/components/IndexManage/VBPContent.js
Normal file
@@ -0,0 +1,219 @@
|
||||
import { styled } from 'styled-components';
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
import { TableStyle, TableInfo, ListOption } from '../../styles/Components';
|
||||
|
||||
import Button from '../../components/common/button/Button';
|
||||
import VBPSearchBar from '../../components/IndexManage/VBPSearchBar';
|
||||
import { VBPIndexExport, VbpIndexView } from '../../apis';
|
||||
|
||||
const VBPContent = () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
let d = new Date();
|
||||
const START_DATE = new Date(new Date(d.setDate(d.getDate() - 1)).setHours(0, 0, 0, 0));
|
||||
const END_DATE = new Date();
|
||||
|
||||
const [sendDate, setSendDate] = useState(START_DATE);
|
||||
const [finishDate, setFinishDate] = useState(END_DATE);
|
||||
|
||||
const [dataList, setDataList] = useState([]);
|
||||
useEffect(() => {
|
||||
fetchData(START_DATE, END_DATE);
|
||||
}, []);
|
||||
|
||||
// console.log(dataList);
|
||||
|
||||
const fetchData = async (startDate, endDate) => {
|
||||
const startDateToLocal =
|
||||
startDate.getFullYear() +
|
||||
'-' +
|
||||
(startDate.getMonth() + 1 < 9 ? '0' + (startDate.getMonth() + 1) : startDate.getMonth() + 1) +
|
||||
'-' +
|
||||
(startDate.getDate() < 9 ? '0' + startDate.getDate() : startDate.getDate());
|
||||
|
||||
const endDateToLocal =
|
||||
endDate.getFullYear() +
|
||||
'-' +
|
||||
(endDate.getMonth() + 1 < 9 ? '0' + (endDate.getMonth() + 1) : endDate.getMonth() + 1) +
|
||||
'-' +
|
||||
(endDate.getDate() < 9 ? '0' + endDate.getDate() : endDate.getDate());
|
||||
|
||||
setDataList(await VbpIndexView(token, startDateToLocal, endDateToLocal));
|
||||
|
||||
setSendDate(startDateToLocal);
|
||||
setFinishDate(endDateToLocal);
|
||||
};
|
||||
|
||||
// 엑셀 다운로드
|
||||
const handleXlsxExport = () => {
|
||||
const fileName = 'Caliverse_VBP_Index.xlsx';
|
||||
VBPIndexExport(token, fileName, sendDate, finishDate);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<VBPSearchBar fetchData={fetchData} />
|
||||
<TableInfo>
|
||||
<ListOption>
|
||||
<Button theme="line" text="엑셀 다운로드" handleClick={handleXlsxExport} />
|
||||
</ListOption>
|
||||
</TableInfo>
|
||||
<TableWrapper>
|
||||
<EconomicTable>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colSpan="2" className="text-center" width="300">
|
||||
Product
|
||||
</th>
|
||||
<th width="160">2023-08-07</th>
|
||||
<th width="160">2023-08-08</th>
|
||||
<th width="160">2023-08-09</th>
|
||||
<th width="160">2023-08-10</th>
|
||||
<th width="160">2023-08-11</th>
|
||||
<th width="160">2023-08-12</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<TableTitle colSpan="2">(Total) VBP 생산량</TableTitle>
|
||||
<TableData>500000</TableData>
|
||||
<TableData>500000</TableData>
|
||||
<TableData>500000</TableData>
|
||||
<TableData>500000</TableData>
|
||||
<TableData>500000</TableData>
|
||||
<TableData>500000</TableData>
|
||||
</tr>
|
||||
<tr>
|
||||
<TableTitle colSpan="2">(Total) VBP 소진량</TableTitle>
|
||||
<TableData>490000</TableData>
|
||||
<TableData>490000</TableData>
|
||||
<TableData>490000</TableData>
|
||||
<TableData>490000</TableData>
|
||||
<TableData>490000</TableData>
|
||||
<TableData>490000</TableData>
|
||||
</tr>
|
||||
<tr>
|
||||
<TableTitle colSpan="2">(Total) VBP 보유량</TableTitle>
|
||||
<TableData>3.2M</TableData>
|
||||
<TableData>3.3M</TableData>
|
||||
<TableData>3.3M</TableData>
|
||||
<TableData>3.4M</TableData>
|
||||
<TableData>3.5M</TableData>
|
||||
<TableData>3.5M</TableData>
|
||||
</tr>
|
||||
<tr>
|
||||
<TableTitle rowSpan="2">GET</TableTitle>
|
||||
<TableTitle>퀘스트 보상</TableTitle>
|
||||
<TableData>150000</TableData>
|
||||
<TableData>150000</TableData>
|
||||
<TableData>150000</TableData>
|
||||
<TableData>150000</TableData>
|
||||
<TableData>150000</TableData>
|
||||
<TableData>150000</TableData>
|
||||
</tr>
|
||||
<tr>
|
||||
<TableTitle>시즌패스 보상</TableTitle>
|
||||
<TableData>150000</TableData>
|
||||
<TableData>150000</TableData>
|
||||
<TableData>150000</TableData>
|
||||
<TableData>150000</TableData>
|
||||
<TableData>150000</TableData>
|
||||
<TableData>150000</TableData>
|
||||
</tr>
|
||||
<tr>
|
||||
<TableTitle rowSpan="2">USE</TableTitle>
|
||||
<TableTitle>퀘스트 보상</TableTitle>
|
||||
<TableData>150000</TableData>
|
||||
<TableData>150000</TableData>
|
||||
<TableData>150000</TableData>
|
||||
<TableData>150000</TableData>
|
||||
<TableData>150000</TableData>
|
||||
<TableData>150000</TableData>
|
||||
</tr>
|
||||
<tr>
|
||||
<TableTitle>시즌패스 보상</TableTitle>
|
||||
<TableData>150000</TableData>
|
||||
<TableData>150000</TableData>
|
||||
<TableData>150000</TableData>
|
||||
<TableData>150000</TableData>
|
||||
<TableData>150000</TableData>
|
||||
<TableData>150000</TableData>
|
||||
</tr>
|
||||
|
||||
{/* {mokupData.map((data, index) => (
|
||||
<Fragment key={index}>
|
||||
<tr>
|
||||
<td>{data.date}</td>
|
||||
<td>{data.name}</td>
|
||||
<td>{data.trader}</td>
|
||||
<td>{data.id}</td>
|
||||
<td>{data.key}</td>
|
||||
</tr>
|
||||
</Fragment>
|
||||
))} */}
|
||||
</tbody>
|
||||
</EconomicTable>
|
||||
</TableWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default VBPContent;
|
||||
|
||||
const TableWrapper = styled.div`
|
||||
width: 100%;
|
||||
min-width: 680px;
|
||||
overflow: auto;
|
||||
&::-webkit-scrollbar {
|
||||
height: 4px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #666666;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #d9d9d9;
|
||||
}
|
||||
${TableStyle} {
|
||||
width: 100%;
|
||||
min-width: 900px;
|
||||
th {
|
||||
&.cell-nru {
|
||||
background: #f0f0f0;
|
||||
border-left: 1px solid #aaa;
|
||||
border-right: 1px solid #aaa;
|
||||
}
|
||||
}
|
||||
td {
|
||||
&.blank {
|
||||
background: #f9f9f9;
|
||||
}
|
||||
&.cell-nru {
|
||||
background: #fafafa;
|
||||
border-left: 1px solid #aaa;
|
||||
border-right: 1px solid #aaa;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const TableData = styled.td`
|
||||
background: ${props => (props.$state === 'danger' ? '#d60000' : props.$state === 'blank' ? '#F9F9F9' : 'transparent')};
|
||||
color: ${props => (props.$state === 'danger' ? '#fff' : '#2c2c2c')};
|
||||
`;
|
||||
|
||||
const TableTitle = styled.td`
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const EconomicTable = styled(TableStyle)`
|
||||
${TableData} {
|
||||
text-align: left;
|
||||
}
|
||||
tbody {
|
||||
tr:nth-child(1),
|
||||
tr:nth-child(2),
|
||||
tr:nth-child(3) {
|
||||
background: #f5fcff;
|
||||
}
|
||||
}
|
||||
`;
|
||||
112
src/components/IndexManage/VBPSearchBar.js
Normal file
112
src/components/IndexManage/VBPSearchBar.js
Normal file
@@ -0,0 +1,112 @@
|
||||
import { useState } from 'react';
|
||||
import { styled } from 'styled-components';
|
||||
import Button from '../../components/common/button/Button';
|
||||
|
||||
import DatePickerComponent from '../common/Date/DatePickerComponent';
|
||||
|
||||
import { FormWrapper, InputLabel, TextInput, SelectInput, BtnWrapper, InputGroup, DatePickerWrapper } from '../../styles/Components';
|
||||
|
||||
const VBPSearchBar = ({ fetchData }) => {
|
||||
let d = new Date();
|
||||
const START_DATE = new Date(new Date(d.setDate(d.getDate() - 1)).setHours(0, 0, 0, 0));
|
||||
const END_DATE = new Date();
|
||||
|
||||
const [resultData, setResultData] = useState({
|
||||
send_dt: START_DATE,
|
||||
end_dt: END_DATE,
|
||||
});
|
||||
|
||||
// 발송 날짜 세팅 로직
|
||||
const handleSelectedDate = data => {
|
||||
const sendDate = new Date(data);
|
||||
const resultSendData = new Date(sendDate.getFullYear(), sendDate.getMonth(), sendDate.getDate());
|
||||
|
||||
setResultData({ ...resultData, send_dt: resultSendData });
|
||||
};
|
||||
|
||||
// 발송 날짜 세팅 로직
|
||||
const handleEndDate = data => {
|
||||
const endDate = new Date(data);
|
||||
const resultSendData = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate());
|
||||
|
||||
setResultData({ ...resultData, end_dt: resultSendData });
|
||||
};
|
||||
|
||||
const handleReset = e => {
|
||||
e.preventDefault();
|
||||
setResultData({ send_dt: START_DATE, end_dt: END_DATE });
|
||||
|
||||
fetchData(START_DATE, END_DATE);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormWrapper>
|
||||
<SearchbarStyle>
|
||||
<SearchItem>
|
||||
<InputLabel>집계 기간</InputLabel>
|
||||
<InputGroup>
|
||||
<DatePickerWrapper>
|
||||
<DatePickerComponent
|
||||
name="시작 일자" selectedDate={resultData.send_dt}
|
||||
handleSelectedDate={data => handleSelectedDate(data)}
|
||||
maxDate={new Date()} />
|
||||
<span>~</span>
|
||||
<DatePickerComponent
|
||||
name="종료 일자" selectedDate={resultData.end_dt}
|
||||
handleSelectedDate={data => handleEndDate(data)}
|
||||
pastDate = {resultData.send_dt}
|
||||
maxDate={new Date()} />
|
||||
</DatePickerWrapper>
|
||||
</InputGroup>
|
||||
</SearchItem>
|
||||
<BtnWrapper $gap="8px">
|
||||
<Button
|
||||
theme="reset"
|
||||
handleClick={e => {
|
||||
handleReset(e);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
theme="search"
|
||||
text="집계"
|
||||
handleClick={e => {
|
||||
e.preventDefault();
|
||||
fetchData(resultData.send_dt, resultData.end_dt);
|
||||
}}
|
||||
/>
|
||||
</BtnWrapper>
|
||||
</SearchbarStyle>
|
||||
</FormWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default VBPSearchBar;
|
||||
|
||||
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;
|
||||
|
||||
${TextInput}, ${SelectInput} {
|
||||
height: 35px;
|
||||
}
|
||||
${TextInput} {
|
||||
padding: 0 10px;
|
||||
max-width: 400px;
|
||||
}
|
||||
`;
|
||||
27
src/components/IndexManage/index.js
Normal file
27
src/components/IndexManage/index.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import UserIndexSearchBar from "./UserIndexSearchBar";
|
||||
import RetentionSearchBar from "./RetentionSearchBar";
|
||||
import SegmentSearchBar from "./SegmentSearchBar";
|
||||
import DailyDashBoard from "./DailyDashBoard";
|
||||
import PlayTimeSearchBar from "./PlayTimeSearchBar";
|
||||
import UserContent from "./UserContent";
|
||||
import PlayTimeContent from "./PlayTimeContent";
|
||||
import RetentionContent from "./RetentionContent";
|
||||
import SegmentContent from "./SegmentContent";
|
||||
import DailyActiveUserContent from "./DailyActiveUserContent";
|
||||
import DailyMedalContent from "./DailyMedalContent";
|
||||
import DailySearchBar from "./DailySearchBar";
|
||||
|
||||
export {
|
||||
UserIndexSearchBar,
|
||||
RetentionSearchBar,
|
||||
SegmentSearchBar,
|
||||
DailyDashBoard,
|
||||
PlayTimeSearchBar,
|
||||
UserContent,
|
||||
SegmentContent,
|
||||
RetentionContent,
|
||||
PlayTimeContent,
|
||||
DailySearchBar,
|
||||
DailyActiveUserContent,
|
||||
DailyMedalContent,
|
||||
};
|
||||
13
src/components/JsonTest.js
Normal file
13
src/components/JsonTest.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import ReactJson from 'react-json-view';
|
||||
|
||||
import json from '../assets/data/applicator.json';
|
||||
|
||||
const JsonTest = () => {
|
||||
return (
|
||||
<>
|
||||
<ReactJson src={json} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default JsonTest;
|
||||
1000
src/components/ServiceManage/BoardInfoModal.js
Normal file
1000
src/components/ServiceManage/BoardInfoModal.js
Normal file
File diff suppressed because it is too large
Load Diff
620
src/components/ServiceManage/BoardRegistModal.js
Normal file
620
src/components/ServiceManage/BoardRegistModal.js
Normal file
@@ -0,0 +1,620 @@
|
||||
import { styled } from 'styled-components';
|
||||
import { useState, useRef, Fragment, useEffect } from 'react';
|
||||
|
||||
import Button from '../common/button/Button';
|
||||
import CheckBox from '../common/input/CheckBox';
|
||||
import Modal from '../common/modal/Modal';
|
||||
|
||||
import { Title, BtnWrapper, SelectInput, TextInput, DatePickerWrapper, InputLabel, Textarea, ModalText, SearchBarAlert } from '../../styles/Components';
|
||||
import CloseIcon from '../../assets/img/icon/icon-close.png';
|
||||
import DatePickerComponent from '../common/Date/DatePickerComponent';
|
||||
import { HourList, MinuteList, modalTypes } from '../../assets/data';
|
||||
import { NoticeRegist } from '../../apis';
|
||||
import { convertKTC, convertKTCDate } from '../../utils';
|
||||
import DynamicModal from '../common/modal/DynamicModal';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
BoxWrapper, InputGroup2,
|
||||
MessageBox, MessageWrapper,
|
||||
NoticeInputGroup, NoticeInputItem, NoticeInputItem2,
|
||||
NoticeInputRow, NoticeInputRow2,
|
||||
NoticeRegistGroup,
|
||||
RegistInputItem, RepeatTime, SubText, SubTextRow, TitleLang,
|
||||
} from '../../styles/ModuleComponents';
|
||||
import { languageType } from '../../assets/data/options';
|
||||
|
||||
const BoardRegistModal = ({ registView, setRegistView, copyData, setIsCopyData }) => {
|
||||
const [doubleSubmitFlag, setDoubleSubmitFlag] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [sendHour, setSendHour] = useState('00');
|
||||
const [sendMin, setSendMin] = useState('00');
|
||||
const [endHour, setEndHour] = useState('00');
|
||||
const [endMin, setEndMin] = useState('00');
|
||||
const [repeatHour, setRepeatHour] = useState('00');
|
||||
const [repeatMin, setRepeatMin] = useState('00');
|
||||
|
||||
const [confirmModal, setConfirmModal] = useState('hidden');
|
||||
const [completeModal, setCompleteModal] = useState('hidden');
|
||||
const [errorModal, setErrorModal] = useState('hidden');
|
||||
|
||||
const [confirmText, setConfirmText] = useState('');
|
||||
const [completeText, setCompleteText] = useState('');
|
||||
const [isNullValue, setIsNullValue] = useState(false);
|
||||
const [btnValidation, setBtnValidation] = useState(false);
|
||||
|
||||
const [message_lang, setMessage_lang] = useState('KO');
|
||||
|
||||
const itemId = useRef(1);
|
||||
|
||||
const resetData = {
|
||||
send_dt: '',
|
||||
message_type: 'CHATTING',
|
||||
is_repeat: false,
|
||||
repeat_type: 'COUNT',
|
||||
repeat_dt: '00:00:00',
|
||||
repeat_cnt: '1',
|
||||
game_message: [
|
||||
{ language: 'KO', content: '' },
|
||||
{ language: 'EN', content: '' },
|
||||
{ language: 'JA', content: '' },
|
||||
],
|
||||
};
|
||||
|
||||
const [resultData, setResultData] = useState({
|
||||
send_dt: '',
|
||||
end_dt: '',
|
||||
message_type: 'CHATTING',
|
||||
is_repeat: false,
|
||||
repeat_type: 'COUNT',
|
||||
repeat_dt: '00:00:00',
|
||||
repeat_cnt: '1',
|
||||
game_message: [
|
||||
{ language: 'KO', content: '' },
|
||||
{ language: 'EN', content: '' },
|
||||
{ language: 'JA', content: '' },
|
||||
],
|
||||
});
|
||||
|
||||
const KOREAN_TIME = copyData && convertKTCDate(copyData.detail.send_dt);
|
||||
|
||||
// console.log(resultData);
|
||||
|
||||
useEffect(() => {
|
||||
setResultData({
|
||||
send_dt: copyData && '',
|
||||
end_dt: copyData && '',
|
||||
message_type: copyData ? copyData.detail.message_type : 'CHATTING',
|
||||
is_repeat: copyData ? copyData.detail.is_repeat : false,
|
||||
repeat_type: copyData ? copyData.detail.repeat_type : 'COUNT',
|
||||
repeat_dt: copyData ? copyData.detail.repeat_dt : '00:00:00',
|
||||
repeat_cnt: copyData ? copyData.detail.repeat_cnt : '1',
|
||||
game_message: copyData
|
||||
? copyData.game_message
|
||||
: [
|
||||
{ language: 'KO', content: '' },
|
||||
{ language: 'EN', content: '' },
|
||||
{ language: 'JA', content: '' },
|
||||
],
|
||||
});
|
||||
|
||||
if (copyData && copyData.game_message.length <= 1) {
|
||||
setBtnValidation(true);
|
||||
}
|
||||
}, [copyData]);
|
||||
|
||||
const handleRepeatTime = e => {
|
||||
if (e.target.id === 'hour') setRepeatHour(e.target.value);
|
||||
else if (e.target.id === 'min') setRepeatMin(e.target.value);
|
||||
|
||||
setResultData({ ...resultData, repeat_dt: (e.target.id === 'hour' ? e.target.value : repeatHour) + ':' + (e.target.id === 'min' ? e.target.value : repeatMin) + ':00' });
|
||||
};
|
||||
|
||||
// 날짜 설정
|
||||
const handleSelectedDate = data => {
|
||||
const sendDate = new Date(data);
|
||||
|
||||
if (resultData.send_dt.length === 0 || typeof resultData.send_dt.length != 'undefined') {
|
||||
setSendHour('00');
|
||||
setSendMin('00');
|
||||
}
|
||||
|
||||
const resultSendData = new Date(sendDate.getFullYear(), sendDate.getMonth(), sendDate.getDate(), sendHour, sendMin);
|
||||
|
||||
setResultData({ ...resultData, send_dt: resultSendData });
|
||||
};
|
||||
|
||||
// 시간 설정
|
||||
const handleSendTime = e => {
|
||||
let sendDate = '';
|
||||
|
||||
if (resultData.send_dt.length === 0 || typeof resultData.send_dt.length != 'undefined') {
|
||||
sendDate = new Date();
|
||||
} else {
|
||||
sendDate = new Date(resultData.send_dt);
|
||||
}
|
||||
|
||||
if (e.target.id === 'hour') setSendHour(e.target.value);
|
||||
else if (e.target.id === 'min') setSendMin(e.target.value);
|
||||
|
||||
// console.log(sendDate);
|
||||
|
||||
const result = new Date(sendDate.getFullYear(), sendDate.getMonth(), sendDate.getDate(), e.target.id === 'hour' ? e.target.value : sendHour, e.target.id === 'min' ? e.target.value : sendMin);
|
||||
|
||||
setResultData({ ...resultData, send_dt: result });
|
||||
};
|
||||
|
||||
// 종료 날짜 설정
|
||||
const handleSelectedEndDate = data => {
|
||||
const endDate = new Date(data);
|
||||
|
||||
if (resultData.end_dt.length === 0 || typeof resultData.end_dt.length != 'undefined') {
|
||||
setEndHour('00');
|
||||
setEndMin('00');
|
||||
}
|
||||
|
||||
let resultEndData = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate(), endHour, endMin);
|
||||
|
||||
if (resultData.send_dt > resultEndData){
|
||||
alert("송출 일자보다 작을 수 없습니다.");
|
||||
resultEndData = new Date(resultData.send_dt);
|
||||
}
|
||||
|
||||
setResultData({ ...resultData, end_dt: resultEndData });
|
||||
};
|
||||
|
||||
//종료 시간 설정
|
||||
const handleEndTime = e => {
|
||||
let endDate = '';
|
||||
|
||||
if (resultData.end_dt.length === 0 || typeof resultData.end_dt.length != 'undefined') {
|
||||
endDate = new Date();
|
||||
} else {
|
||||
endDate = new Date(resultData.end_dt);
|
||||
}
|
||||
|
||||
if (e.target.id === 'hour') setEndHour(e.target.value);
|
||||
else if (e.target.id === 'min') setEndMin(e.target.value);
|
||||
|
||||
// console.log(sendDate);
|
||||
|
||||
let result = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate(), e.target.id === 'hour' ? e.target.value : endHour, e.target.id === 'min' ? e.target.value : endMin);
|
||||
|
||||
if (resultData.send_dt > result){
|
||||
alert("송출 일자보다 작을 수 없습니다.");
|
||||
result = new Date(resultData.send_dt);
|
||||
}
|
||||
|
||||
setResultData({ ...resultData, end_dt: result });
|
||||
};
|
||||
|
||||
const handleDelete = language => {
|
||||
let filterList = resultData.game_message.filter(el => el.language !== language);
|
||||
|
||||
if (filterList.length === 1) {
|
||||
setBtnValidation(true);
|
||||
} else if (filterList.length > 1) {
|
||||
setBtnValidation(false);
|
||||
}
|
||||
|
||||
setResultData({ ...resultData, game_message: filterList });
|
||||
};
|
||||
|
||||
// 반복횟수 입력 데이터 폼 처리
|
||||
const handleRepeatValue = e => {
|
||||
// e.target.value.length === 0 ? setIsNullValue(true) : setIsNullValue(false);
|
||||
|
||||
if (e.target.value === '0' || e.target.value === '-0' || e.target.value.length === 0) {
|
||||
setResultData({ ...resultData, repeat_cnt: '1' });
|
||||
e.target.value = '1';
|
||||
} else if (e.target.value < 0) {
|
||||
let plusNum = Math.abs(e.target.value);
|
||||
setResultData({ ...resultData, repeat_cnt: plusNum });
|
||||
} else {
|
||||
setResultData({ ...resultData, repeat_cnt: e.target.value });
|
||||
}
|
||||
};
|
||||
|
||||
// 등록 버튼
|
||||
const handleRegistBtn = e => {
|
||||
e.preventDefault();
|
||||
|
||||
if (
|
||||
!resultData.game_message.some(data => data.content !== '') ||
|
||||
resultData.game_message.length === 0 ||
|
||||
resultData.send_dt.length === 0 ||
|
||||
typeof resultData.send_dt.length != 'undefined' ||
|
||||
(resultData.is_repeat === true && resultData.repeat_dt === '00:00:00') ||
|
||||
(resultData.is_repeat === true && resultData.message_type === "COUNT" && resultData.repeat_cnt.length === 0) ||
|
||||
(resultData.is_repeat === true && resultData.message_type === "COUNT" && resultData.end_dt.length === 0) ||
|
||||
(resultData.is_repeat === true && resultData.message_type === "COUNT" && typeof resultData.end_dt.length !== 'undefined')
|
||||
) {
|
||||
setIsNullValue(true);
|
||||
} else {
|
||||
setIsNullValue(false);
|
||||
setConfirmText('인게임 메시지를 등록하시겠습니까?');
|
||||
handleConfirmModal('regist');
|
||||
}
|
||||
};
|
||||
|
||||
// 취소 버튼
|
||||
const handleCancelBtn = e => {
|
||||
e.preventDefault();
|
||||
setConfirmText('인게임 메시지 등록을 취소하시겠습니까?');
|
||||
handleConfirmModal('cancel');
|
||||
};
|
||||
|
||||
// 입력창 글자 제한
|
||||
const handleInputData = e => {
|
||||
if (e.target.value.length > 250) {
|
||||
return;
|
||||
}
|
||||
let list = [...resultData.game_message];
|
||||
let findIndex = resultData.game_message.findIndex(item => item.language === e.target.id);
|
||||
list[findIndex].content = e.target.value.trimStart();
|
||||
|
||||
setResultData({ ...resultData, game_message: list });
|
||||
};
|
||||
|
||||
// 알림창 텍스트
|
||||
const handleSubmitNotice = async () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
|
||||
if (confirmText === '인게임 메시지를 등록하시겠습니까?') {
|
||||
const message = await NoticeRegist(token, resultData);
|
||||
|
||||
message.data.data.message !== '등록 하였습니다.' ? setCompleteText(message.data.data.message) : setCompleteText('등록이 완료되었습니다.');
|
||||
|
||||
handleConfirmModal();
|
||||
handleCompleteModal();
|
||||
setIsNullValue(false);
|
||||
|
||||
setRegistView('hidden');
|
||||
} else if (confirmText === '인게임 메시지 등록을 취소하시겠습니까?') {
|
||||
handleConfirmModal();
|
||||
handleCompleteModal();
|
||||
setCompleteText('등록이 취소되었습니다.');
|
||||
|
||||
setRegistView('hidden');
|
||||
}
|
||||
|
||||
itemId.current = 1;
|
||||
};
|
||||
|
||||
// 언어 선택
|
||||
const handleLanguage = e => {
|
||||
setMessage_lang(e.target.value);
|
||||
if(!resultData.game_message.some(({language}) => language === e.target.value))
|
||||
setResultData({ ...resultData, game_message: [...resultData.game_message, {language: e.target.value, content: ''}] })
|
||||
}
|
||||
|
||||
// 확인 모달
|
||||
const handleConfirmModal = step => {
|
||||
if (confirmModal === 'hidden') {
|
||||
setConfirmModal('view');
|
||||
} else {
|
||||
setConfirmModal('hidden');
|
||||
}
|
||||
};
|
||||
|
||||
// 완료 모달창
|
||||
const handleCompleteModal = () => {
|
||||
setIsCopyData(false);
|
||||
if (completeModal === 'hidden') {
|
||||
setCompleteModal('view');
|
||||
} else {
|
||||
setCompleteModal('hidden');
|
||||
setResultData(resetData);
|
||||
|
||||
window.location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
// 필수값 입력 모달창
|
||||
const handleErrorModal = () => {
|
||||
if (errorModal === 'hidden') {
|
||||
setErrorModal('view');
|
||||
} else {
|
||||
setErrorModal('hidden');
|
||||
}
|
||||
};
|
||||
|
||||
// 내용 복사일 때 이동 페이지
|
||||
return (
|
||||
<>
|
||||
<Modal min="960px" $view={registView}>
|
||||
{/* 등록 후 정보 확인시 타이틀 텍스트 변경 인게임 메시지 등록 -> 인게임 메시지 정보 */}
|
||||
<Title $align="center"> 인게임 메시지 등록</Title>
|
||||
<MessageWrapper>
|
||||
<InputLabel>[메세지 등록 설정]</InputLabel>
|
||||
<NoticeRegistGroup>
|
||||
<NoticeInputRow>
|
||||
<RegistInputItem>
|
||||
<InputLabel>송출 일자(예약)</InputLabel>
|
||||
<InputGroup2>
|
||||
<NoticeInputGroup>
|
||||
<DatePickerWrapper>
|
||||
<DatePickerComponent name="시작 일자" handleSelectedDate={data => handleSelectedDate(data)} selectedDate={resultData.send_dt} pastDate={new Date()} />
|
||||
</DatePickerWrapper>
|
||||
<SelectInput
|
||||
onChange={e => handleSendTime(e)}
|
||||
id="hour"
|
||||
value={
|
||||
resultData && String(new Date(resultData.send_dt).getHours()) < 10
|
||||
? '0' + String(new Date(resultData.send_dt).getHours())
|
||||
: (resultData && String(new Date(resultData.send_dt).getHours())) || ''
|
||||
}>
|
||||
{HourList.map(hour => (
|
||||
<option
|
||||
value={hour}
|
||||
key={hour}
|
||||
// selected={
|
||||
// resultData && String(new Date(resultData.send_dt).getHours()) < 10
|
||||
// ? '0' + String(new Date(resultData.send_dt).getHours()) === hour
|
||||
// : resultData && String(new Date(resultData.send_dt).getHours()) === hour
|
||||
// ? 'selected'
|
||||
// : ''
|
||||
// }
|
||||
>
|
||||
{hour}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<SelectInput
|
||||
onChange={e => handleSendTime(e)}
|
||||
id="min"
|
||||
value={
|
||||
resultData && String(new Date(resultData.send_dt).getMinutes()) < 10
|
||||
? '0' + String(new Date(resultData.send_dt).getMinutes())
|
||||
: (resultData && String(new Date(resultData.send_dt).getMinutes())) || ''
|
||||
}>
|
||||
{MinuteList.map(min => (
|
||||
<option
|
||||
value={min}
|
||||
key={min}
|
||||
// selected={
|
||||
// resultData && String(new Date(resultData.send_dt).getMinutes()) < 10
|
||||
// ? '0' + String(new Date(resultData.send_dt).getMinutes()) === min
|
||||
// : resultData && String(new Date(resultData.send_dt).getMinutes()) === min
|
||||
// ? 'selected'
|
||||
// : ''
|
||||
// }
|
||||
>
|
||||
{min}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</NoticeInputGroup>
|
||||
<SubText>* UTC+9 한국시간 기준으로 설정 (UTC+0 자동 반영처리)</SubText>
|
||||
</InputGroup2>
|
||||
</RegistInputItem>
|
||||
<RegistInputItem>
|
||||
<InputLabel>메시지 타입</InputLabel>
|
||||
<SelectInput onChange={e => setResultData({ ...resultData, message_type: e.target.value })} value={resultData.message_type || ''}>
|
||||
<option value="CHATTING">채팅</option>
|
||||
<option value="CHATTING_TOAST">채팅 + 토스트</option>
|
||||
</SelectInput>
|
||||
</RegistInputItem>
|
||||
</NoticeInputRow>
|
||||
<NoticeInputRow>
|
||||
<CheckBox label="반복 발송" id="input-regist" inline={false} checked={resultData.is_repeat} setData={e => setResultData({ ...resultData, is_repeat: e.target.checked })} style={{display: 'hidden'}}/>
|
||||
{resultData.is_repeat === true && (
|
||||
<>
|
||||
<RegistInputItem>
|
||||
<InputLabel>반복 타입</InputLabel>
|
||||
<SelectInput onChange={e => setResultData({ ...resultData, repeat_type: e.target.value })} value={resultData.repeat_type || ''}>
|
||||
<option value="COUNT">횟수</option>
|
||||
<option value="DATE">일자</option>
|
||||
<option value="TIME">특정시간</option>
|
||||
</SelectInput>
|
||||
</RegistInputItem>
|
||||
</>
|
||||
)}
|
||||
</NoticeInputRow>
|
||||
<NoticeInputRow>
|
||||
{resultData.is_repeat === true && (
|
||||
<>
|
||||
{resultData.repeat_type !== "COUNT" && (
|
||||
<>
|
||||
<NoticeInputItem>
|
||||
<InputLabel>종료 일자</InputLabel>
|
||||
<InputGroup2>
|
||||
<NoticeInputGroup>
|
||||
<DatePickerWrapper>
|
||||
<DatePickerComponent name="종료 일자" handleSelectedDate={data => handleSelectedEndDate(data)} selectedDate={resultData.end_dt} pastDate={new Date()} />
|
||||
</DatePickerWrapper>
|
||||
<SelectInput
|
||||
onChange={e => handleEndTime(e)}
|
||||
id="hour"
|
||||
value={
|
||||
resultData && String(new Date(resultData.end_dt).getHours()) < 10
|
||||
? '0' + String(new Date(resultData.end_dt).getHours())
|
||||
: (resultData && String(new Date(resultData.end_dt).getHours())) || ''
|
||||
}>
|
||||
{HourList.map(hour => (
|
||||
<option
|
||||
value={hour}
|
||||
key={hour}
|
||||
>
|
||||
{hour}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<SelectInput
|
||||
onChange={e => handleEndTime(e)}
|
||||
id="min"
|
||||
value={
|
||||
resultData && String(new Date(resultData.end_dt).getMinutes()) < 10
|
||||
? '0' + String(new Date(resultData.end_dt).getMinutes())
|
||||
: (resultData && String(new Date(resultData.end_dt).getMinutes())) || ''
|
||||
}>
|
||||
{MinuteList.map(min => (
|
||||
<option
|
||||
value={min}
|
||||
key={min}
|
||||
>
|
||||
{min}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</NoticeInputGroup>
|
||||
</InputGroup2>
|
||||
</NoticeInputItem>
|
||||
</>
|
||||
)}
|
||||
|
||||
<RegistInputItem>
|
||||
<RepeatTime>
|
||||
{resultData.repeat_type === "TIME" && (<span>발송시간</span>)}
|
||||
<SelectInput onChange={e => handleRepeatTime(e)} id="hour" value={resultData.repeat_dt.split(':')[0] || ''}>
|
||||
{HourList.map(hour => (
|
||||
<option
|
||||
value={hour}
|
||||
key={hour}
|
||||
>
|
||||
{hour}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<SelectInput onChange={e => handleRepeatTime(e)} id="min" value={resultData.repeat_dt.split(':')[1] || ''}>
|
||||
{MinuteList.map(min => (
|
||||
<option
|
||||
value={min}
|
||||
key={min}
|
||||
>
|
||||
{min}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
{resultData.repeat_type !== "TIME" ? (
|
||||
<>
|
||||
<span>마다</span>
|
||||
<SubText>* 최초 송출 이후 설정된 시간 단위마다 송출</SubText>
|
||||
</>
|
||||
) : (<SubText>* 설정된 시간에만 송출</SubText>)}
|
||||
</RepeatTime>
|
||||
</RegistInputItem>
|
||||
{resultData.repeat_type === "COUNT" && (
|
||||
<>
|
||||
<NoticeInputItem>
|
||||
<InputLabel>반복 횟수</InputLabel>
|
||||
<NoticeInputGroup>
|
||||
<TextInput
|
||||
type="number"
|
||||
value={resultData.repeat_cnt || ''}
|
||||
onChange={e => {
|
||||
handleRepeatValue(e);
|
||||
}}
|
||||
width="80px"
|
||||
/>
|
||||
<span>회</span>
|
||||
</NoticeInputGroup>
|
||||
</NoticeInputItem>
|
||||
</>
|
||||
)}
|
||||
|
||||
</>
|
||||
)}
|
||||
</NoticeInputRow>
|
||||
</NoticeRegistGroup>
|
||||
<NoticeInputRow2>
|
||||
<InputLabel>
|
||||
[메세지 작성]
|
||||
<SubTextRow>* 작성하지 않은 언어는 발송되지 않습니다.</SubTextRow>
|
||||
</InputLabel>
|
||||
<NoticeInputItem2>
|
||||
<InputLabel>언어</InputLabel>
|
||||
<SelectInput onChange={e => handleLanguage(e) } value={message_lang}>
|
||||
{languageType.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</NoticeInputItem2>
|
||||
</NoticeInputRow2>
|
||||
<BoxWrapper>
|
||||
{resultData.game_message.map(content => {
|
||||
return (
|
||||
<Fragment key={content.language}>
|
||||
{message_lang === content.language && (
|
||||
<MessageBox>
|
||||
{/* <BtnWrapper $justify="flex-end">
|
||||
{btnValidation === false ? (
|
||||
<ButtonClose
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
handleDelete(content.language);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<ButtonClose opacity="10%" />
|
||||
)}
|
||||
</BtnWrapper> */}
|
||||
<TitleLang>언어 : {content.language}</TitleLang>
|
||||
<div>
|
||||
<Textarea id={content.language} onChange={e => handleInputData(e)} defaultValue={content.content || ''} maxLength={250} />
|
||||
</div>
|
||||
</MessageBox>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</BoxWrapper>
|
||||
</MessageWrapper>
|
||||
{isNullValue && <SearchBarAlert $marginTop="25px">필수값을 입력해주세요.</SearchBarAlert>}
|
||||
|
||||
<BtnWrapper $gap="10px" $justify="center" $marginTop="20px">
|
||||
<Button text="취소" theme="line" handleClick={handleCancelBtn} />
|
||||
<Button
|
||||
type="submit"
|
||||
text="등록"
|
||||
name="등록버튼"
|
||||
theme={
|
||||
// resultData.game_message.map(data => data.content === '').includes(true) ||
|
||||
!resultData.game_message.some(data => data.content !== '') ||
|
||||
resultData.game_message.length === 0 ||
|
||||
resultData.send_dt.length === 0 ||
|
||||
typeof resultData.send_dt.length != 'undefined' ||
|
||||
(resultData.is_repeat === true && resultData.repeat_dt === '00:00:00') ||
|
||||
(resultData.is_repeat === true && resultData.message_type === "COUNT" && resultData.repeat_cnt.length === 0) ||
|
||||
(resultData.is_repeat === true && resultData.message_type === "COUNT" && resultData.end_dt.length === 0) ||
|
||||
(resultData.is_repeat === true && resultData.message_type === "COUNT" && typeof resultData.end_dt.length !== 'undefined')
|
||||
? 'disable'
|
||||
: 'primary'
|
||||
}
|
||||
handleClick={handleRegistBtn}
|
||||
/>
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
|
||||
{/* 확인 모달 */}
|
||||
<DynamicModal
|
||||
modalType={modalTypes.confirmOkCancel}
|
||||
view={confirmModal}
|
||||
modalText={confirmText}
|
||||
handleSubmit={() => {
|
||||
doubleSubmitFlag || handleSubmitNotice();
|
||||
setDoubleSubmitFlag(true);
|
||||
}}
|
||||
handleCancel={handleConfirmModal}
|
||||
/>
|
||||
{/* 완료 모달 */}
|
||||
<DynamicModal
|
||||
modalType={modalTypes.completed}
|
||||
view={completeModal}
|
||||
modalText={completeText}
|
||||
handleSubmit={handleCompleteModal}
|
||||
/>
|
||||
{/* 필수값 미입력 모달 */}
|
||||
<DynamicModal
|
||||
modalType={modalTypes.completed}
|
||||
view={errorModal}
|
||||
modalText={t('NULL_MSG')}
|
||||
handleSubmit={handleErrorModal}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default BoardRegistModal;
|
||||
86
src/components/ServiceManage/CaliumRequestSearchBar.js
Normal file
86
src/components/ServiceManage/CaliumRequestSearchBar.js
Normal file
@@ -0,0 +1,86 @@
|
||||
import { TextInput, BtnWrapper, InputLabel, SelectInput } from '../../styles/Components';
|
||||
import Button from '../common/button/Button';
|
||||
import { SearchBarLayout, SearchPeriod } from '../common/SearchBar';
|
||||
import { useState } from 'react';
|
||||
import { caliumStatus } from '../../assets/data/options';
|
||||
|
||||
const CaliumRequestSearchBar = ({ handleSearch, setResultData }) => {
|
||||
const [searchData, setSearchData] = useState({
|
||||
content: '',
|
||||
status: 'ALL',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
});
|
||||
|
||||
const handleSubmit = event => {
|
||||
event.preventDefault();
|
||||
|
||||
handleSearch(
|
||||
searchData.content,
|
||||
searchData.status ? searchData.status : 'ALL',
|
||||
searchData.startDate ? searchData.startDate : '',
|
||||
searchData.endDate ? searchData.endDate : new Date(),
|
||||
(searchData.startDate && searchData.endDate === '') && setSearchData({ startDate : searchData.startDate ,endDate : new Date()}),
|
||||
);
|
||||
|
||||
setResultData(searchData);
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setSearchData({
|
||||
content: '',
|
||||
status: 'ALL',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
order: 'DESC',
|
||||
});
|
||||
|
||||
handleSearch('', 'ALL', '', '');
|
||||
setResultData('', 'ALL', '', '');
|
||||
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
const searchList = [
|
||||
<>
|
||||
<InputLabel>등록 일자</InputLabel>
|
||||
<SearchPeriod
|
||||
startDate={searchData.startDate}
|
||||
handleStartDate={data => {
|
||||
setSearchData({ ...searchData, startDate: data });
|
||||
}}
|
||||
endDate={searchData.endDate}
|
||||
handleEndDate={data => setSearchData({ ...searchData, endDate: data })}
|
||||
/>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>요청 내용</InputLabel>
|
||||
<TextInput
|
||||
type="text"
|
||||
placeholder="요청 내용"
|
||||
value={searchData.content}
|
||||
onChange={e => setSearchData({ ...searchData, content: e.target.value })}
|
||||
/>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>요청 상태</InputLabel>
|
||||
<SelectInput value={searchData.status} onChange={e => setSearchData({ ...searchData, status: e.target.value })}>
|
||||
{caliumStatus.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<>
|
||||
<BtnWrapper $gap="8px">
|
||||
<Button theme="reset" handleClick={handleReset} type="button" />
|
||||
<Button theme="search" text="검색" handleClick={handleSubmit} type="submit" />
|
||||
</BtnWrapper>
|
||||
</>,
|
||||
];
|
||||
|
||||
return <SearchBarLayout firstColumnData={searchList} direction={'column'} />;
|
||||
};
|
||||
|
||||
export default CaliumRequestSearchBar;
|
||||
564
src/components/ServiceManage/EventDetailModal.js
Normal file
564
src/components/ServiceManage/EventDetailModal.js
Normal file
@@ -0,0 +1,564 @@
|
||||
import { useState, useEffect, Fragment } from 'react';
|
||||
|
||||
import { Title, SelectInput, BtnWrapper, TextInput, Label, InputLabel, Textarea, SearchBarAlert } from '../../styles/Components';
|
||||
import Button from '../../components/common/button/Button';
|
||||
import Modal from '../../components/common/modal/Modal';
|
||||
import { EventIsItem, EventModify, MailModify } from '../../apis';
|
||||
|
||||
import { authList } from '../../store/authList';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { authType, benItems, commonStatus, modalTypes, wellType } from '../../assets/data';
|
||||
import {
|
||||
AppendRegistBox, AppendRegistTable, AreaBtnClose,
|
||||
BtnDelete, DetailInputItem, DetailInputRow,
|
||||
DetailModalWrapper, RegistGroup, DetailRegistInfo, DetailState,
|
||||
Item, ItemList, LangArea
|
||||
} from '../../styles/ModuleComponents';
|
||||
import DynamicModal from '../common/modal/DynamicModal';
|
||||
import { convertKTC, combineDateTime, timeDiffMinute, convertKTCDate } from '../../utils';
|
||||
import DateTimeInput from '../common/input/DateTimeInput';
|
||||
|
||||
const EventDetailModal = ({ detailView, handleDetailView, content, setDetailData }) => {
|
||||
const userInfo = useRecoilValue(authList);
|
||||
const { t } = useTranslation();
|
||||
const token = sessionStorage.getItem('token');
|
||||
|
||||
const id = content && content.id;
|
||||
const updateAuth = userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.eventUpdate);
|
||||
|
||||
const [time, setTime] = useState({
|
||||
start_hour: '00',
|
||||
start_min: '00',
|
||||
end_hour: '00',
|
||||
end_min: '00',
|
||||
}); //시간 정보
|
||||
|
||||
const [item, setItem] = useState('');
|
||||
const [itemCount, setItemCount] = useState('');
|
||||
const [resource, setResource] = useState('19010001');
|
||||
const [resourceCount, setResourceCount] = useState('');
|
||||
|
||||
const [modifyModal, setModifyModal] = useState('hidden');
|
||||
const [completeModal, setCompleteModal] = useState('hidden');
|
||||
const [resultData, setResultData] = useState({});
|
||||
|
||||
const [modalState, setModalState] = useState({
|
||||
updateConfirmModal: 'hidden',
|
||||
updateCompleteModal: 'hidden',
|
||||
});
|
||||
|
||||
const [isNullValue, setIsNullValue] = useState(false);
|
||||
// 과거 판단
|
||||
const [isPast, setIsPast] = useState(false);
|
||||
const [isChanged, setIsChanged] = useState(false);
|
||||
|
||||
const [btnValidation, setBtnValidation] = useState(false);
|
||||
const [isReadOnly, setIsReadOnly] = useState(false);
|
||||
const [itemCheckMsg, setItemCheckMsg] = useState('');
|
||||
const [alertMsg, setAlertMsg] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if(content){
|
||||
const start_dt_KTC = convertKTCDate(content.start_dt)
|
||||
const end_dt_KTC = convertKTCDate(content.end_dt)
|
||||
|
||||
setResultData({
|
||||
start_dt: start_dt_KTC,
|
||||
end_dt: end_dt_KTC,
|
||||
event_type: content.event_type,
|
||||
mail_list: content.mail_list,
|
||||
item_list: content.item_list,
|
||||
});
|
||||
|
||||
setTime({ ...time,
|
||||
start_hour: String(start_dt_KTC.getHours()).padStart(2, '0'),
|
||||
start_min: String(start_dt_KTC.getMinutes()).padStart(2, '0'),
|
||||
end_hour: String(end_dt_KTC.getHours()).padStart(2, '0'),
|
||||
end_min: String(end_dt_KTC.getMinutes()).padStart(2, '0')
|
||||
});
|
||||
|
||||
start_dt_KTC < (new Date) ? setIsPast(true) : setIsPast(false);
|
||||
content.mail_list.length === 1 && setBtnValidation(true);
|
||||
}
|
||||
|
||||
setItem('');
|
||||
|
||||
}, [content]);
|
||||
|
||||
useEffect(() => {
|
||||
if(!updateAuth || isPast){
|
||||
setIsReadOnly(true);
|
||||
}else{
|
||||
setIsReadOnly(false);
|
||||
}
|
||||
}, [updateAuth, isPast]);
|
||||
|
||||
useEffect(() => {
|
||||
if (conditionCheck()) {
|
||||
setIsNullValue(false);
|
||||
} else {
|
||||
setIsNullValue(true);
|
||||
}
|
||||
}, [resultData]);
|
||||
|
||||
useEffect(() => {
|
||||
setItemCheckMsg('');
|
||||
}, [item]);
|
||||
|
||||
// 아이템 수량 숫자 체크
|
||||
const handleItemCount = e => {
|
||||
if (e.target.value === '0' || e.target.value === '-0') {
|
||||
setItemCount('1');
|
||||
e.target.value = '1';
|
||||
} else if (e.target.value < 0) {
|
||||
let plusNum = Math.abs(e.target.value);
|
||||
setItemCount(plusNum);
|
||||
} else {
|
||||
setItemCount(e.target.value);
|
||||
}
|
||||
};
|
||||
|
||||
// 아이템 추가
|
||||
const handleItemList = async () => {
|
||||
if(benItems.includes(item)){
|
||||
setAlertMsg(t('MAIL_ITEM_ADD_BEN'))
|
||||
return;
|
||||
}
|
||||
if(item.length === 0 || itemCount.length === 0) return;
|
||||
|
||||
const token = sessionStorage.getItem('token');
|
||||
const result = await EventIsItem(token, {item: item});
|
||||
|
||||
if(result.data.result === "ERROR"){
|
||||
setItemCheckMsg(t('NOT_ITEM'));
|
||||
return;
|
||||
}
|
||||
|
||||
const itemIndex = resultData.item_list.findIndex((data) => data.item === item);
|
||||
if (itemIndex !== -1) {
|
||||
setItemCheckMsg(t('MAIL_ITEM_ADD_DUPL'));
|
||||
return;
|
||||
}
|
||||
const newItem = { item: item, item_cnt: itemCount, item_name: result.data.data.item_info.item_name };
|
||||
|
||||
resultData.item_list.push(newItem);
|
||||
|
||||
setIsChanged(true);
|
||||
setItem('');
|
||||
setItemCount('');
|
||||
};
|
||||
|
||||
// 아이템 삭제
|
||||
const onItemRemove = id => {
|
||||
let filterList = resultData.item_list && resultData.item_list.filter(item => item !== resultData.item_list[id]);
|
||||
setIsChanged(true);
|
||||
|
||||
setResultData({ ...resultData, item_list: filterList });
|
||||
};
|
||||
|
||||
// 자원 수량 숫자 체크
|
||||
const handleResourceCount = e => {
|
||||
if (e.target.value === '0' || e.target.value === '-0') {
|
||||
setResourceCount('1');
|
||||
e.target.value = '1';
|
||||
} else if (e.target.value < 0) {
|
||||
let plusNum = Math.abs(e.target.value);
|
||||
setResourceCount(plusNum);
|
||||
} else {
|
||||
setResourceCount(e.target.value);
|
||||
}
|
||||
};
|
||||
|
||||
// 자원 추가
|
||||
const handleResourceList = (e) => {
|
||||
if(resource.length === 0 || resourceCount.length === 0) return;
|
||||
|
||||
const itemIndex = resultData.item_list.findIndex(
|
||||
(item) => item.item === resource
|
||||
);
|
||||
|
||||
if (itemIndex !== -1) {
|
||||
const item_cnt = resultData.item_list[itemIndex].item_cnt;
|
||||
resultData.item_list[itemIndex].item_cnt = Number(item_cnt) + Number(resourceCount);
|
||||
} else {
|
||||
const name = wellType.find(well => well.value === resource).name;
|
||||
const newItem = { item: resource, item_cnt: resourceCount, item_name: name };
|
||||
resultData.item_list.push(newItem);
|
||||
}
|
||||
setIsChanged(true);
|
||||
setResource('')
|
||||
setResourceCount('');
|
||||
};
|
||||
|
||||
// 입력창 삭제
|
||||
const onLangDelete = language => {
|
||||
let filterList = resultData.mail_list && resultData.mail_list.filter(el => el.language !== language);
|
||||
|
||||
if (filterList.length === 1) setBtnValidation(true);
|
||||
|
||||
setIsChanged(true);
|
||||
setResultData({ ...resultData, mail_list: filterList });
|
||||
};
|
||||
|
||||
// 날짜 처리
|
||||
const handleDateChange = (data, type) => {
|
||||
const date = new Date(data);
|
||||
setResultData({
|
||||
...resultData,
|
||||
[`${type}_dt`]: combineDateTime(date, time[`${type}_hour`], time[`${type}_min`]),
|
||||
});
|
||||
setIsChanged(true);
|
||||
};
|
||||
|
||||
// 시간 처리
|
||||
const handleTimeChange = (e, type) => {
|
||||
const { id, value } = e.target;
|
||||
const newTime = { ...time, [`${type}_${id}`]: value };
|
||||
setTime(newTime);
|
||||
|
||||
const date = resultData[`${type}_dt`] ? new Date(resultData[`${type}_dt`]) : new Date();
|
||||
|
||||
setResultData({
|
||||
...resultData,
|
||||
[`${type}_dt`]: combineDateTime(date, newTime[`${type}_hour`], newTime[`${type}_min`]),
|
||||
});
|
||||
setIsChanged(true);
|
||||
};
|
||||
|
||||
// 확인 버튼 후 다 초기화
|
||||
const handleReset = () => {
|
||||
setBtnValidation(false);
|
||||
setIsNullValue(false);
|
||||
setIsChanged(false);
|
||||
};
|
||||
|
||||
const conditionCheck = () => {
|
||||
return (
|
||||
content && content.mail_list.every(data => data.content !== '' && data.title !== '') &&
|
||||
isChanged
|
||||
);
|
||||
};
|
||||
|
||||
const handleModalView = (type) => {
|
||||
setModalState((prevState) => ({
|
||||
...prevState,
|
||||
[`${type}Modal`]: 'view',
|
||||
}));
|
||||
}
|
||||
|
||||
const handleModalClose = (type) => {
|
||||
setModalState((prevState) => ({
|
||||
...prevState,
|
||||
[`${type}Modal`]: 'hidden',
|
||||
}));
|
||||
}
|
||||
|
||||
const handleSubmit = async (type, param = null) => {
|
||||
switch (type) {
|
||||
case "submit":
|
||||
if (!conditionCheck()) return;
|
||||
|
||||
handleModalView('updateConfirm');
|
||||
break;
|
||||
case "updateConfirm":
|
||||
const timeDiff = timeDiffMinute(resultData.start_dt, (new Date))
|
||||
// 이벤트 시작 30분전이나 이미 SystemMail이 add된 상태에서는 수정할 수 없다.
|
||||
if(content.add_flag || timeDiff <= 30){
|
||||
setAlertMsg(t('EVENT_TIME_LIMIT_UPDATE'));
|
||||
handleModalClose('updateConfirm');
|
||||
return;
|
||||
}
|
||||
await EventModify(token, id, resultData);
|
||||
handleModalClose('updateConfirm');
|
||||
handleModalView('updateComplete');
|
||||
break;
|
||||
case "updateComplete":
|
||||
handleModalClose('updateComplete');
|
||||
window.location.reload();
|
||||
break;
|
||||
case "warning":
|
||||
setAlertMsg('');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const detailState = (status) => {
|
||||
switch (status) {
|
||||
case commonStatus.wait:
|
||||
return <DetailState>대기</DetailState>;
|
||||
case commonStatus.running:
|
||||
return <DetailState>진행중</DetailState>;
|
||||
case commonStatus.finish:
|
||||
return <DetailState result={commonStatus.finish}>완료</DetailState>;
|
||||
case commonStatus.fail:
|
||||
return <DetailState result={commonStatus.fail}>실패</DetailState>;
|
||||
case commonStatus.delete:
|
||||
return <DetailState result={commonStatus.delete}>삭제</DetailState>;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal min="960px" $view={detailView}>
|
||||
<Title $align="center">이벤트 상세 정보</Title>
|
||||
{content &&
|
||||
<DetailRegistInfo>
|
||||
<span>등록자 : {content.create_by}</span>
|
||||
<span>등록일 : {convertKTC(content.create_dt, false)}</span>
|
||||
{typeof content.update_by !== 'undefined' && (
|
||||
<>
|
||||
<span>수정자 : {content.update_by}</span>
|
||||
<span>수정일 : {convertKTC(content.update_dt, false)}</span>
|
||||
</>
|
||||
)}
|
||||
</DetailRegistInfo>
|
||||
}
|
||||
<DetailModalWrapper>
|
||||
{content &&
|
||||
<RegistGroup>
|
||||
<DetailInputRow>
|
||||
<DateTimeInput
|
||||
title="이벤트 기간"
|
||||
dateName="시작 일자"
|
||||
selectedDate={convertKTCDate(content.start_dt)}
|
||||
handleSelectedDate={data => handleDateChange(data, 'start')}
|
||||
onChange={e => handleTimeChange(e, 'start')}
|
||||
|
||||
/>
|
||||
<DateTimeInput
|
||||
dateName="종료 일자"
|
||||
selectedDate={convertKTCDate(content.end_dt)}
|
||||
handleSelectedDate={data => handleDateChange(data, 'end')}
|
||||
onChange={e => handleTimeChange(e, 'end')}
|
||||
/>
|
||||
</DetailInputRow>
|
||||
<DetailInputRow>
|
||||
<DetailInputItem>
|
||||
<InputLabel>이벤트 상태</InputLabel>
|
||||
<div>{detailState(content.status)}</div>
|
||||
</DetailInputItem>
|
||||
{content.status === commonStatus.delete &&
|
||||
<DetailInputItem>
|
||||
<InputLabel>삭제 사유</InputLabel>
|
||||
<div>{content.delete_desc}</div>
|
||||
</DetailInputItem>
|
||||
}
|
||||
</DetailInputRow>
|
||||
|
||||
</RegistGroup>
|
||||
}
|
||||
{resultData.mail_list &&
|
||||
resultData.mail_list.map(data => {
|
||||
return (
|
||||
<Fragment key={data.language}>
|
||||
<AppendRegistBox>
|
||||
<LangArea>
|
||||
언어 : {data.language}
|
||||
{btnValidation === false && !isReadOnly ? (
|
||||
<AreaBtnClose
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
onLangDelete(data.language);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<AreaBtnClose opacity="10%" />
|
||||
)}
|
||||
</LangArea>
|
||||
<AppendRegistTable>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th width="120">
|
||||
<Label>제목</Label>
|
||||
</th>
|
||||
<td>
|
||||
<DetailInputItem>
|
||||
<TextInput
|
||||
placeholder="우편 제목 입력"
|
||||
maxLength="30"
|
||||
id={data.language}
|
||||
value={data.title}
|
||||
readOnly={isReadOnly}
|
||||
onChange={e => {
|
||||
let list = [...resultData.mail_list];
|
||||
let findIndex = resultData.mail_list && resultData.mail_list.findIndex(item => item.language === e.target.id);
|
||||
list[findIndex].title = e.target.value.trimStart();
|
||||
|
||||
setResultData({ ...resultData, mail_list: list });
|
||||
setIsChanged(true);
|
||||
}}
|
||||
/>
|
||||
</DetailInputItem>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
<Label>내용</Label>
|
||||
</th>
|
||||
<td>
|
||||
<Textarea
|
||||
value={data.content}
|
||||
readOnly={isReadOnly}
|
||||
id={data.language}
|
||||
onChange={e => {
|
||||
if (e.target.value.length > 2000) {
|
||||
return;
|
||||
}
|
||||
let list = [...resultData.mail_list];
|
||||
let findIndex = resultData.mail_list && resultData.mail_list.findIndex(item => item.language === e.target.id);
|
||||
list[findIndex].content = e.target.value.trimStart();
|
||||
|
||||
setResultData({ ...resultData, mail_list: list });
|
||||
setIsChanged(true);
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</AppendRegistTable>
|
||||
</AppendRegistBox>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
<AppendRegistBox>
|
||||
<AppendRegistTable>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th width="120">
|
||||
<Label>아이템 첨부</Label>
|
||||
</th>
|
||||
<td>
|
||||
<DetailInputItem>
|
||||
<TextInput
|
||||
placeholder="Item Meta id 입력"
|
||||
value={item}
|
||||
onChange={e => {
|
||||
let list = [];
|
||||
list = e.target.value.trimStart();
|
||||
setItem(list);
|
||||
}}
|
||||
disabled={isReadOnly}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder="수량"
|
||||
value={itemCount}
|
||||
type="number"
|
||||
onChange={e => handleItemCount(e)}
|
||||
width="90px"
|
||||
disabled={isReadOnly}
|
||||
/>
|
||||
<Button
|
||||
text="추가"
|
||||
theme={itemCount.length === 0 || item.length === 0 ? 'disable' : 'search'}
|
||||
handleClick={handleItemList}
|
||||
/>
|
||||
{itemCheckMsg && <SearchBarAlert>{itemCheckMsg}</SearchBarAlert>}
|
||||
</DetailInputItem>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th width="120">
|
||||
<Label>자원 첨부</Label>
|
||||
</th>
|
||||
<td>
|
||||
<DetailInputItem>
|
||||
<SelectInput onChange={e => setResource(e.target.value)} value={resource} disabled={isReadOnly}>
|
||||
{wellType.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<TextInput
|
||||
placeholder="수량"
|
||||
type="number"
|
||||
value={resourceCount}
|
||||
disabled={isReadOnly}
|
||||
onChange={e => handleResourceCount(e)}
|
||||
width="200px"
|
||||
/>
|
||||
<Button
|
||||
text="추가"
|
||||
theme={resourceCount.length === 0 || resource.length === 0 ? 'disable' : 'search'}
|
||||
handleClick={handleResourceList}
|
||||
width="100px"
|
||||
height="35px"
|
||||
errorMessage={isReadOnly} />
|
||||
</DetailInputItem>
|
||||
|
||||
<div>
|
||||
{resultData.item_list && (
|
||||
<ItemList>
|
||||
{resultData.item_list.map((data, index) => {
|
||||
return (
|
||||
<Item key={index}>
|
||||
<span>
|
||||
{data.item_name}[{data.item}] ({data.item_cnt})
|
||||
</span>
|
||||
{!isReadOnly && <BtnDelete onClick={() => onItemRemove(index)}></BtnDelete>}
|
||||
</Item>
|
||||
);
|
||||
})}
|
||||
</ItemList>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</AppendRegistTable>
|
||||
</AppendRegistBox>
|
||||
</DetailModalWrapper>
|
||||
<BtnWrapper $justify="flex-end" $gap="10px" $paddingTop="20px">
|
||||
<Button
|
||||
text="확인"
|
||||
theme="line"
|
||||
name="확인버튼"
|
||||
handleClick={() => {
|
||||
handleDetailView();
|
||||
handleReset();
|
||||
setDetailData('');
|
||||
}}
|
||||
/>
|
||||
{!isReadOnly && (
|
||||
<Button
|
||||
type="submit"
|
||||
text="수정"
|
||||
id="수정버튼"
|
||||
theme={conditionCheck() ? 'primary' : 'disable'}
|
||||
handleClick={() => handleSubmit('submit')}
|
||||
/>
|
||||
)}
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
{/* 확인 모달 */}
|
||||
<DynamicModal
|
||||
modalType={modalTypes.confirmOkCancel}
|
||||
view={modalState.updateConfirmModal}
|
||||
modalText={t('MAIL_UPDATE_SAVE')}
|
||||
handleCancel={() => handleModalClose('updateConfirm')}
|
||||
handleSubmit={() => handleSubmit('updateConfirm')}
|
||||
/>
|
||||
{/* 완료 모달 */}
|
||||
<DynamicModal
|
||||
modalType={modalTypes.completed}
|
||||
view={modalState.updateCompleteModal}
|
||||
modalText={t('UPDATE_COMPLETED')}
|
||||
handleSubmit={() => handleSubmit('updateComplete')}
|
||||
/>
|
||||
{/* 경고 모달 */}
|
||||
<DynamicModal
|
||||
modalType={modalTypes.completed}
|
||||
view={alertMsg ? 'view' : 'hidden'}
|
||||
modalText={alertMsg}
|
||||
handleSubmit={() => handleSubmit('warning')}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default EventDetailModal;
|
||||
|
||||
|
||||
103
src/components/ServiceManage/EventListSearchBar.js
Normal file
103
src/components/ServiceManage/EventListSearchBar.js
Normal file
@@ -0,0 +1,103 @@
|
||||
import { TextInput, BtnWrapper, InputLabel, SelectInput } from '../../styles/Components';
|
||||
import Button from '../common/button/Button';
|
||||
import { SearchBarLayout, SearchPeriod } from '../common/SearchBar';
|
||||
import { useState } from 'react';
|
||||
import { eventStatus } from '../../assets/data';
|
||||
|
||||
const EventListSearchBar = ({ handleSearch, setResultData }) => {
|
||||
const [searchData, setSearchData] = useState({
|
||||
title: '',
|
||||
content: '',
|
||||
status: 'ALL',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
});
|
||||
|
||||
const handleSubmit = event => {
|
||||
event.preventDefault();
|
||||
|
||||
handleSearch(
|
||||
searchData.title,
|
||||
searchData.content,
|
||||
searchData.status ? searchData.status : 'ALL',
|
||||
searchData.startDate ? searchData.startDate : '',
|
||||
searchData.endDate ? searchData.endDate : new Date(),
|
||||
(searchData.startDate && searchData.endDate === '') && setSearchData({ startDate : searchData.startDate ,endDate : new Date()}),
|
||||
);
|
||||
|
||||
setResultData(searchData);
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setSearchData({
|
||||
title: '',
|
||||
content: '',
|
||||
status: 'ALL',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
order: 'DESC',
|
||||
});
|
||||
|
||||
handleSearch('', '', 'ALL', '', '');
|
||||
setResultData('', '', 'ALL', '', '');
|
||||
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
// console.log("searchData.endDate", searchData.endDate)
|
||||
|
||||
const searchList = [
|
||||
<>
|
||||
<InputLabel>우편 제목</InputLabel>
|
||||
<TextInput
|
||||
type="text"
|
||||
placeholder="우편 제목"
|
||||
value={searchData.title}
|
||||
onChange={e => setSearchData({ ...searchData, title: e.target.value })}
|
||||
/>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>조회 일자</InputLabel>
|
||||
<SearchPeriod
|
||||
startDate={searchData.startDate}
|
||||
handleStartDate={data => {
|
||||
setSearchData({ ...searchData, startDate: data });
|
||||
}}
|
||||
endDate={searchData.endDate}
|
||||
handleEndDate={data => setSearchData({ ...searchData, endDate: data })}
|
||||
/>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>우편 내용</InputLabel>
|
||||
<TextInput
|
||||
type="text"
|
||||
placeholder="우편 내용(공백으로 구분)"
|
||||
value={searchData.content}
|
||||
onChange={e => setSearchData({ ...searchData, content: e.target.value })}
|
||||
/>
|
||||
</>
|
||||
];
|
||||
|
||||
const optionList = [
|
||||
<>
|
||||
<InputLabel>이벤트 상태</InputLabel>
|
||||
<SelectInput value={searchData.status} onChange={e => setSearchData({ ...searchData, status: e.target.value })}>
|
||||
{eventStatus.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<></>,<></>,<></>,<></>,<></>,<></>,<></>,<></>,<></>,<></>,<></>,<></>,<></>,
|
||||
<>
|
||||
<BtnWrapper $gap="8px">
|
||||
<Button theme="reset" handleClick={handleReset} type="button" />
|
||||
<Button theme="search" text="검색" handleClick={handleSubmit} type="submit" />
|
||||
</BtnWrapper>
|
||||
</>,
|
||||
];
|
||||
return <SearchBarLayout firstColumnData={searchList} secondColumnData={optionList} direction={'column'} />;
|
||||
};
|
||||
|
||||
export default EventListSearchBar;
|
||||
127
src/components/ServiceManage/ItemsSearchBar.js
Normal file
127
src/components/ServiceManage/ItemsSearchBar.js
Normal file
@@ -0,0 +1,127 @@
|
||||
import { useState } from 'react';
|
||||
import { TextInput, BtnWrapper, InputLabel, SelectInput, InputGroup } from '../../styles/Components';
|
||||
import Button from '../common/button/Button';
|
||||
import { SearchBarLayout, SearchPeriod } from '../common/SearchBar';
|
||||
|
||||
const ItemsSearchBar = ({ handleSearch, setResultData }) => {
|
||||
const [searchData, setSearchData] = useState({
|
||||
searchType: 'GUID',
|
||||
data: '',
|
||||
status: 'ALL',
|
||||
restore: 'ALL',
|
||||
sendDate: '',
|
||||
endDate: '',
|
||||
});
|
||||
|
||||
const searchType = [
|
||||
{ value: 'GUID', name: 'GUID' },
|
||||
{ value: 'NAME', name: '닉네임' }
|
||||
];
|
||||
|
||||
const status = [
|
||||
{ value: 'ALL', name: '상태' },
|
||||
{ value: 'ACTIVE', name: '활성' },
|
||||
{ value: 'DEACTIVE', name: '비활성' },
|
||||
];
|
||||
|
||||
const restore = [
|
||||
{ value: 'ALL', name: '복구' },
|
||||
{ value: 'POSSIBLE', name: '가능' },
|
||||
{ value: 'IMPOSSIBLE', name: '불가능' },
|
||||
];
|
||||
|
||||
const handleSubmit = event => {
|
||||
event.preventDefault();
|
||||
|
||||
handleSearch(
|
||||
searchData.searchType ? searchData.searchType : 'GUID',
|
||||
searchData.data,
|
||||
searchData.status ? searchData.status : 'ALL',
|
||||
searchData.restore ? searchData.restore : 'ALL',
|
||||
searchData.sendDate ? searchData.sendDate : '',
|
||||
searchData.endDate ? searchData.endDate : new Date(),
|
||||
);
|
||||
|
||||
setResultData(searchData);
|
||||
};
|
||||
|
||||
// 초기화 버튼
|
||||
const handleReset = () => {
|
||||
setSearchData({
|
||||
searchType: 'GUID',
|
||||
data: '',
|
||||
status: 'ALL',
|
||||
restore: 'ALL',
|
||||
sendDate: '',
|
||||
endDate: '',
|
||||
});
|
||||
handleSearch('GUID', '', 'ALL', 'ALL', '', '');
|
||||
setResultData('GUID', '', 'ALL', 'ALL', '', '');
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
// console.log(searchData);
|
||||
|
||||
const searchList = [
|
||||
<>
|
||||
<InputGroup>
|
||||
<SelectInput value={searchData.searchType} onChange={e => setSearchData({ ...searchData, searchType: e.target.value })}>
|
||||
{searchType.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<TextInput
|
||||
type="text"
|
||||
placeholder={searchData.searchType === 'GUID' ? 'GUID 입력' : '닉네임 입력'}
|
||||
value={searchData.data}
|
||||
width="600px"
|
||||
onChange={e => setSearchData({ ...searchData, data: e.target.value })}
|
||||
/>
|
||||
</InputGroup>
|
||||
</>
|
||||
];
|
||||
|
||||
const optionList = [
|
||||
<>
|
||||
<SelectInput value={searchData.status} onChange={e => setSearchData({ ...searchData, status: e.target.value })}>
|
||||
{status.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<>
|
||||
<SelectInput value={searchData.restore} onChange={e => setSearchData({ ...searchData, restore: e.target.value })}>
|
||||
{restore.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>생성 날짜</InputLabel>
|
||||
<SearchPeriod
|
||||
startDate={searchData.sendDate}
|
||||
handleStartDate={data => {
|
||||
setSearchData({ ...searchData, sendDate: data });
|
||||
}}
|
||||
endDate={searchData.endDate}
|
||||
handleEndDate={data => setSearchData({ ...searchData, endDate: data })}
|
||||
maxDate={new Date()}
|
||||
/>
|
||||
</>,
|
||||
<>
|
||||
<BtnWrapper $gap="8px">
|
||||
<Button theme="reset" handleClick={handleReset} />
|
||||
<Button theme="search" text="검색" type="submit" handleClick={handleSubmit} />
|
||||
</BtnWrapper>
|
||||
</>,
|
||||
];
|
||||
return <SearchBarLayout firstColumnData={searchList} secondColumnData={optionList} direction={'column'} />;
|
||||
};
|
||||
|
||||
export default ItemsSearchBar;
|
||||
517
src/components/ServiceManage/LandAuctionModal.js
Normal file
517
src/components/ServiceManage/LandAuctionModal.js
Normal file
@@ -0,0 +1,517 @@
|
||||
import { useState, Fragment, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Button from '../common/button/Button';
|
||||
import Loading from '../common/Loading';
|
||||
|
||||
import {
|
||||
Title,
|
||||
BtnWrapper,
|
||||
SearchBarAlert, SelectInput, InputLabel,
|
||||
} from '../../styles/Components';
|
||||
|
||||
import {
|
||||
FormHelperText,
|
||||
FormInput,
|
||||
FormLabel,
|
||||
FormTextArea,
|
||||
FormTextAreaWrapper,
|
||||
MessageWrapper,
|
||||
FormRowGroup,
|
||||
NoticeInputRow2,
|
||||
NoticeInputItem2, BoxWrapper, FormStatusBar, FormStatusLabel, FormStatusWarning, FormButtonContainer,
|
||||
} from '../../styles/ModuleComponents';
|
||||
import { modalTypes } from '../../assets/data';
|
||||
import {DynamicModal, Modal, DateTimeRangePicker} from '../common';
|
||||
import { LandAuctionModify, LandAuctionSingleRegist } from '../../apis';
|
||||
import { TYPE_MODIFY, TYPE_REGISTRY } from '../../assets/data/adminConstants';
|
||||
import { landAuctionStatus, landAuctionStatusType, languageType, CurrencyType } from '../../assets/data';
|
||||
import { useModal } from '../../utils/hook';
|
||||
import { convertKTCDate } from '../../utils';
|
||||
|
||||
const LandAuctionModal = ({ modalType, detailView, handleDetailView, content, setDetailData, landData, buildingData }) => {
|
||||
const { t } = useTranslation();
|
||||
const token = sessionStorage.getItem('token');
|
||||
|
||||
const [loading, setLoading] = useState(false); // 로딩 창
|
||||
const {
|
||||
modalState,
|
||||
handleModalView,
|
||||
handleModalClose
|
||||
} = useModal({
|
||||
cancel: 'hidden',
|
||||
registConfirm: 'hidden',
|
||||
registComplete: 'hidden'
|
||||
});
|
||||
const [message_lang, setMessage_lang] = useState('KO');
|
||||
|
||||
const [isNullValue, setIsNullValue] = useState(false); // 데이터 값 체크
|
||||
const [alertMsg, setAlertMsg] = useState('');
|
||||
const [selectLand, setSelectLand] = useState(initLandData);
|
||||
|
||||
const [resultData, setResultData] = useState(initData); //데이터 정보
|
||||
const [resetDateTime, setResetDateTime] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if(modalType === TYPE_MODIFY && content && Object.keys(content).length > 0){
|
||||
setResultData({
|
||||
land_id: content.land_id,
|
||||
land_name: content.land_name,
|
||||
land_socket: content.land_socket,
|
||||
land_size: content.land_size,
|
||||
auction_seq: content.auction_seq,
|
||||
currency_type: content.currency_type,
|
||||
start_price: content.start_price,
|
||||
resv_start_dt: convertKTCDate(content.resv_start_dt),
|
||||
resv_end_dt: convertKTCDate(content.resv_end_dt),
|
||||
auction_start_dt: convertKTCDate(content.auction_start_dt),
|
||||
auction_end_dt: convertKTCDate(content.auction_end_dt),
|
||||
message_list: content.message_list,
|
||||
});
|
||||
const land = landData.find(land => land.id === parseInt(content.land_id));
|
||||
setSelectLand(land);
|
||||
}
|
||||
}, [modalType, content]);
|
||||
|
||||
useEffect(() => {
|
||||
if (checkCondition()) {
|
||||
setIsNullValue(false);
|
||||
} else {
|
||||
setIsNullValue(true);
|
||||
}
|
||||
}, [resultData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (resetDateTime) {
|
||||
setResetDateTime(false);
|
||||
}
|
||||
}, [resetDateTime]);
|
||||
|
||||
// 입력 수량 처리
|
||||
const handleCount = e => {
|
||||
const regex = /^\d*\.?\d{0,2}$/;
|
||||
if (!regex.test(e.target.value) && e.target.value !== '-') {
|
||||
return;
|
||||
}
|
||||
|
||||
let count = 0;
|
||||
if (e.target.value === '-0') {
|
||||
count = 1;
|
||||
} else if (e.target.value < 0) {
|
||||
let plusNum = Math.abs(e.target.value);
|
||||
count = plusNum;
|
||||
} else{
|
||||
count = e.target.value;
|
||||
}
|
||||
setResultData((prevState) => ({
|
||||
...prevState,
|
||||
start_price: count,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleReservationChange = {
|
||||
start: (date) => {
|
||||
setResultData(prev => ({ ...prev, resv_start_dt: date }));
|
||||
},
|
||||
end: (date) => {
|
||||
setResultData(prev => ({ ...prev, resv_end_dt: date }));
|
||||
}
|
||||
};
|
||||
|
||||
const handleAuctionChange = {
|
||||
start: (date) => {
|
||||
setResultData(prev => ({ ...prev, auction_start_dt: date }));
|
||||
},
|
||||
end: (date) => {
|
||||
setResultData(prev => ({ ...prev, auction_end_dt: date }));
|
||||
}
|
||||
};
|
||||
|
||||
// 입력 글자 제한
|
||||
const handleInputData = e => {
|
||||
if (e.target.value.length > 250) {
|
||||
return;
|
||||
}
|
||||
const updatedMessages = resultData.message_list.map(msg =>
|
||||
msg.language === message_lang
|
||||
? { ...msg, content: e.target.value.trimStart() }
|
||||
: msg
|
||||
);
|
||||
|
||||
setResultData(prev => ({
|
||||
...prev,
|
||||
message_list: updatedMessages
|
||||
}));
|
||||
};
|
||||
|
||||
// 언어 선택
|
||||
const handleLanguage = e => {
|
||||
setMessage_lang(e.target.value);
|
||||
if(!resultData.message_list.some(({language}) => language === e.target.value))
|
||||
setResultData({ ...resultData, message_list: [...resultData.message_list, {language: e.target.value, content: ''}] })
|
||||
}
|
||||
|
||||
const handleLand = e => {
|
||||
const land_id = e.target.value;
|
||||
const land = landData.find(land => land.id === parseInt(land_id));
|
||||
const instance = buildingData.find(building => building.id === parseInt(land.buildingId))?.socket;
|
||||
setSelectLand(land);
|
||||
setResultData({ ...resultData, land_id: land_id, land_name: land.name, land_size: land.size, land_socket: instance });
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
setMessage_lang('KO')
|
||||
setDetailData({});
|
||||
setSelectLand(initLandData);
|
||||
setResultData(initData);
|
||||
setResetDateTime(true);
|
||||
handleDetailView();
|
||||
}
|
||||
|
||||
const handleSubmit = async (type, param = null) => {
|
||||
switch (type) {
|
||||
case "submit":
|
||||
if (!checkCondition()) return;
|
||||
|
||||
const minAllowedTime = new Date(new Date().getTime() + 5 * 60000);
|
||||
if (isView('recv') && resultData.resv_start_dt < minAllowedTime) {
|
||||
setAlertMsg(t('LAND_AUCTION_MADEL_RESV_START_WARNING'));
|
||||
return;
|
||||
}
|
||||
if (resultData.auction_start_dt < minAllowedTime) {
|
||||
setAlertMsg(t('LAND_AUCTION_MADEL_AUCTION_START_WARNING'));
|
||||
return;
|
||||
}
|
||||
if(resultData.resv_start_dt >= resultData.auction_start_dt || resultData.resv_start_dt >= resultData.auction_end_dt) {
|
||||
setAlertMsg(t('LAND_AUCTION_MADEL_AUCTION_DIFF_RESERVATION'))
|
||||
return;
|
||||
}
|
||||
if(resultData.auction_start_dt >= resultData.auction_end_dt) {
|
||||
setAlertMsg(t('LAND_AUCTION_MADEL_AUCTION_DIFF_AUCTION'))
|
||||
return;
|
||||
}
|
||||
|
||||
//화면에 머물면서 상태는 안바꼈을 경우가 있기에 경매시작시간 지났을경우 차단
|
||||
if (modalType === TYPE_MODIFY && resultData.auction_start_dt < new Date()) {
|
||||
setAlertMsg(t('LAND_AUCTION_MADEL_MODIFY_START'));
|
||||
return;
|
||||
}
|
||||
|
||||
handleModalView('registConfirm');
|
||||
break;
|
||||
case "cancel":
|
||||
handleModalView('cancel');
|
||||
break;
|
||||
case "cancelConfirm":
|
||||
handleModalClose('cancel');
|
||||
handleReset();
|
||||
break;
|
||||
case "registConfirm":
|
||||
setLoading(true);
|
||||
|
||||
if(isView('modify')){
|
||||
await LandAuctionModify(token, content?.id, resultData).then(data => {
|
||||
setLoading(false);
|
||||
handleModalClose('registConfirm');
|
||||
if(data.result === "SUCCESS") {
|
||||
handleModalView('registComplete');
|
||||
}else if(data.result === "ERROR_AUCTION_STATUS_IMPOSSIBLE"){
|
||||
setAlertMsg(t('LAND_AUCTION_ERROR_MODIFY_STATUS'));
|
||||
}else{
|
||||
setAlertMsg(t('UPDATE_FAIL'));
|
||||
}
|
||||
}).catch(reason => {
|
||||
setAlertMsg(t('API_FAIL'));
|
||||
});
|
||||
}
|
||||
else{
|
||||
await LandAuctionSingleRegist(token, resultData).then(data => {
|
||||
setLoading(false);
|
||||
handleModalClose('registConfirm');
|
||||
if(data.result === "SUCCESS") {
|
||||
handleModalView('registComplete');
|
||||
}else if(data.result === "ERROR_LAND_AUCTION_IMPOSSIBLE"){
|
||||
setAlertMsg(t('LAND_AUCTION_ERROR_PROGRESS'));
|
||||
}else if(data.result === "ERROR_AUCTION_LAND_OWNER"){
|
||||
setAlertMsg(t('LAND_AUCTION_ERROR_OWNER'));
|
||||
}else{
|
||||
setAlertMsg(t('REGIST_FAIL'));
|
||||
}
|
||||
}).catch(reason => {
|
||||
setAlertMsg(t('API_FAIL'));
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "registComplete":
|
||||
handleModalClose('registComplete');
|
||||
handleReset();
|
||||
window.location.reload();
|
||||
break;
|
||||
case "warning":
|
||||
setAlertMsg('');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const checkCondition = () => {
|
||||
return (
|
||||
resultData.start_price > 0
|
||||
&& resultData.auction_start_dt !== ''
|
||||
&& resultData.auction_end_dt !== ''
|
||||
&& resultData.resv_start_dt !== ''
|
||||
&& resultData.resv_end_dt !== ''
|
||||
&& resultData.land_id !== ''
|
||||
// && resultData.message_list?.every(data => data.content !== '')
|
||||
);
|
||||
};
|
||||
|
||||
const isView = (label) => {
|
||||
switch (label) {
|
||||
case "recv":
|
||||
return modalType === TYPE_REGISTRY || (modalType === TYPE_MODIFY && content?.status === landAuctionStatusType.wait);
|
||||
case "auction":
|
||||
case "price":
|
||||
case "message":
|
||||
return modalType === TYPE_REGISTRY || (modalType === TYPE_MODIFY &&(content?.status === landAuctionStatusType.resv_start || content?.status === landAuctionStatusType.wait));
|
||||
case "modify":
|
||||
return modalType === TYPE_MODIFY && (content?.status === landAuctionStatusType.resv_start || content?.status === landAuctionStatusType.wait);
|
||||
case "registry":
|
||||
return modalType === TYPE_REGISTRY
|
||||
default:
|
||||
return modalType === TYPE_MODIFY && (content?.status === landAuctionStatusType.stl_end
|
||||
|| content?.status === landAuctionStatusType.auction_start
|
||||
|| content?.status === landAuctionStatusType.auction_end
|
||||
|| content?.status === landAuctionStatusType.fail
|
||||
|| content?.status === landAuctionStatusType.cancel
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal min="760px" $view={detailView}>
|
||||
<Title $align="center">{isView('registry') ? "랜드 경매 등록" : isView('modify') ? "랜드 경매 수정" : "랜드 경매 상세"}</Title>
|
||||
<MessageWrapper>
|
||||
<FormRowGroup>
|
||||
<FormLabel>랜드선택</FormLabel>
|
||||
<SelectInput value={resultData.land_id} onChange={e => handleLand(e)} disabled={!isView('registry')} width="400px">
|
||||
{landData && landData.map((data, index) => (
|
||||
<option key={index} value={data.id}>
|
||||
{data.name}({data.id})
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</FormRowGroup>
|
||||
<FormRowGroup>
|
||||
<FormLabel>랜드 이름</FormLabel>
|
||||
<FormInput
|
||||
type="text"
|
||||
disabled={true}
|
||||
width='400px'
|
||||
value={resultData?.land_name}
|
||||
/>
|
||||
</FormRowGroup>
|
||||
<FormRowGroup>
|
||||
<FormLabel>랜드 크기</FormLabel>
|
||||
<FormInput
|
||||
type="text"
|
||||
disabled={true}
|
||||
width='200px'
|
||||
value={resultData?.land_size}
|
||||
/>
|
||||
<FormLabel>인스턴스 수</FormLabel>
|
||||
<FormInput
|
||||
type="text"
|
||||
disabled={true}
|
||||
width='200px'
|
||||
value={resultData?.land_socket}
|
||||
/>
|
||||
</FormRowGroup>
|
||||
|
||||
<FormRowGroup>
|
||||
<FormLabel>입찰 재화</FormLabel>
|
||||
<SelectInput value={resultData.currency_type} width='200px' disabled={true} >
|
||||
{CurrencyType.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<FormLabel>입찰시작가</FormLabel>
|
||||
<FormInput
|
||||
type="number"
|
||||
name="price"
|
||||
value={resultData.start_price}
|
||||
step={"0.01"}
|
||||
min={0}
|
||||
width='200px'
|
||||
disabled={!isView('price')}
|
||||
onChange={e => handleCount(e)}
|
||||
/>
|
||||
</FormRowGroup>
|
||||
<DateTimeRangePicker
|
||||
label="예약기간"
|
||||
startDate={resultData.resv_start_dt}
|
||||
endDate={resultData.resv_end_dt}
|
||||
onStartDateChange={handleReservationChange.start}
|
||||
onEndDateChange={handleReservationChange.end}
|
||||
pastDate={new Date()}
|
||||
disabled={!isView('recv')}
|
||||
startLabel="시작 일자"
|
||||
endLabel="종료 일자"
|
||||
reset={resetDateTime}
|
||||
setAlert={setAlertMsg}
|
||||
/>
|
||||
<DateTimeRangePicker
|
||||
label="경매기간"
|
||||
startDate={resultData.auction_start_dt}
|
||||
endDate={resultData.auction_end_dt}
|
||||
onStartDateChange={handleAuctionChange.start}
|
||||
onEndDateChange={handleAuctionChange.end}
|
||||
pastDate={new Date()}
|
||||
disabled={!isView('auction')}
|
||||
startLabel="시작 일자"
|
||||
endLabel="종료 일자"
|
||||
reset={resetDateTime}
|
||||
setAlert={setAlertMsg}
|
||||
/>
|
||||
|
||||
{/*<NoticeInputRow2>*/}
|
||||
{/* <InputLabel>*/}
|
||||
{/* 메세지 작성[경매 시작 5분전 공지 - 미구현]*/}
|
||||
{/* </InputLabel>*/}
|
||||
{/* <NoticeInputItem2>*/}
|
||||
{/* <InputLabel>언어</InputLabel>*/}
|
||||
{/* <SelectInput onChange={e => handleLanguage(e) } value={message_lang}>*/}
|
||||
{/* {languageType.map((data, index) => (*/}
|
||||
{/* <option key={index} value={data.value}>*/}
|
||||
{/* {data.name}*/}
|
||||
{/* </option>*/}
|
||||
{/* ))}*/}
|
||||
{/* </SelectInput>*/}
|
||||
{/* </NoticeInputItem2>*/}
|
||||
{/*</NoticeInputRow2>*/}
|
||||
{/*<BoxWrapper>*/}
|
||||
{/* {resultData.message_list.map(content => {*/}
|
||||
{/* return (*/}
|
||||
{/* <Fragment key={content.language}>*/}
|
||||
{/* {message_lang === content.language && (*/}
|
||||
{/* <FormTextAreaWrapper>*/}
|
||||
{/* <FormTextArea*/}
|
||||
{/* name="content"*/}
|
||||
{/* id={content.language}*/}
|
||||
{/* value={content.content}*/}
|
||||
{/* onChange={e => handleInputData(e)}*/}
|
||||
{/* maxLength={250}*/}
|
||||
{/* disabled={!isView('message')}*/}
|
||||
{/* />*/}
|
||||
{/* </FormTextAreaWrapper>*/}
|
||||
{/* )}*/}
|
||||
{/* </Fragment>*/}
|
||||
{/* );*/}
|
||||
{/* })}*/}
|
||||
{/*</BoxWrapper>*/}
|
||||
{!isView() && isNullValue && <SearchBarAlert $marginTop="25px" $align="right">{t('REQUIRED_VALUE_CHECK')}</SearchBarAlert>}
|
||||
</MessageWrapper>
|
||||
|
||||
<BtnWrapper $gap="10px" $marginTop="10px">
|
||||
<FormStatusBar>
|
||||
<FormStatusLabel>
|
||||
현재상태: {landAuctionStatus.find(data => data.value === content?.status)?.name || "등록"}
|
||||
</FormStatusLabel>
|
||||
<FormStatusWarning>
|
||||
{isView('registry') ? '' : t('LAND_AUCTION_MODAL_STATUS_WARNING')}
|
||||
</FormStatusWarning>
|
||||
</FormStatusBar>
|
||||
<FormButtonContainer $gap="5px">
|
||||
{isView() ?
|
||||
<Button
|
||||
text="확인"
|
||||
name="확인버튼"
|
||||
theme="line"
|
||||
handleClick={() => handleReset()}
|
||||
/>
|
||||
:
|
||||
<>
|
||||
<Button text="취소" theme="line" handleClick={() => handleSubmit('cancel')} />
|
||||
<Button
|
||||
type="submit"
|
||||
text={isView('modify') ? "수정" : "등록"}
|
||||
name="등록버튼"
|
||||
theme={
|
||||
checkCondition()
|
||||
? 'primary'
|
||||
: 'disable'
|
||||
}
|
||||
handleClick={() => handleSubmit('submit')}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
</FormButtonContainer>
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
|
||||
{/* 확인 모달 */}
|
||||
<DynamicModal
|
||||
modalType={modalTypes.confirmOkCancel}
|
||||
view={modalState.registConfirmModal}
|
||||
modalText={isView('modify') ? t('LAND_UPDATE_CONFIRM') : t('LAND_REGIST_CONFIRM')}
|
||||
handleSubmit={() => handleSubmit('registConfirm')}
|
||||
handleCancel={() => handleModalClose('registConfirm')}
|
||||
/>
|
||||
{/* 완료 모달 */}
|
||||
<DynamicModal
|
||||
modalType={modalTypes.completed}
|
||||
view={modalState.registCompleteModal}
|
||||
modalText={isView('modify') ? t('UPDATE_COMPLETED') : t('REGIST_COMPLTE')}
|
||||
handleSubmit={() => handleSubmit('registComplete')}
|
||||
/>
|
||||
{/* 취소 모달 */}
|
||||
<DynamicModal
|
||||
modalType={modalTypes.confirmOkCancel}
|
||||
view={modalState.cancelModal}
|
||||
modalText={t('CANCEL_CONFIRM')}
|
||||
handleCancel={() => handleModalClose('cancel')}
|
||||
handleSubmit={() => handleSubmit('cancelConfirm')}
|
||||
/>
|
||||
{/* 경고 모달 */}
|
||||
<DynamicModal
|
||||
modalType={modalTypes.completed}
|
||||
view={alertMsg ? 'view' : 'hidden'}
|
||||
modalText={alertMsg}
|
||||
handleSubmit={() => handleSubmit('warning')}
|
||||
/>
|
||||
{loading && <Loading/>}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const initData = {
|
||||
land_id: '',
|
||||
land_name: '',
|
||||
land_size: '',
|
||||
land_socket: '',
|
||||
currency_type: 'Calium',
|
||||
start_price: 0,
|
||||
resv_start_dt: '',
|
||||
resv_end_dt: '',
|
||||
auction_start_dt: '',
|
||||
auction_end_dt: '',
|
||||
message_list: [
|
||||
{ language: 'KO', content: '' },
|
||||
{ language: 'EN', content: '' },
|
||||
{ language: 'JA', content: '' },
|
||||
],
|
||||
}
|
||||
|
||||
export const initLandData = {
|
||||
land_id: 0,
|
||||
name: '',
|
||||
size: '',
|
||||
socket: '',
|
||||
desc: '',
|
||||
open: false,
|
||||
owner: ''
|
||||
}
|
||||
|
||||
export default LandAuctionModal;
|
||||
|
||||
205
src/components/ServiceManage/LandAuctionSearchBar.js
Normal file
205
src/components/ServiceManage/LandAuctionSearchBar.js
Normal file
@@ -0,0 +1,205 @@
|
||||
import { TextInput, BtnWrapper, InputLabel, SelectInput, InputGroup } from '../../styles/Components';
|
||||
import Button from '../common/button/Button';
|
||||
import { SearchBarLayout, SearchPeriod } from '../common/SearchBar';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { LandAuctionView } from '../../apis';
|
||||
import { landAuctionStatus, landSearchType, landSize, userSearchType } from '../../assets/data';
|
||||
|
||||
export const useLandAuctionSearch = (token, initialPageSize) => {
|
||||
const [searchParams, setSearchParams] = useState({
|
||||
landType: 'ID',
|
||||
landData: '',
|
||||
userType: 'GUID',
|
||||
userData: '',
|
||||
landSize: 'ALL',
|
||||
status: 'ALL',
|
||||
auctionStartDate: '',
|
||||
auctionEndDate: '',
|
||||
orderBy: 'DESC',
|
||||
pageSize: initialPageSize,
|
||||
currentPage: 1
|
||||
});
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetchData(searchParams); // 컴포넌트 마운트 시 초기 데이터 로드
|
||||
}, [token]);
|
||||
|
||||
const fetchData = useCallback(async (params) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const result = await LandAuctionView(
|
||||
token,
|
||||
params.landType,
|
||||
params.landData,
|
||||
params.userType,
|
||||
params.userData,
|
||||
params.landSize,
|
||||
params.status,
|
||||
params.auctionStartDate && new Date(params.auctionStartDate).toISOString(),
|
||||
params.auctionEndDate && new Date(params.auctionEndDate).toISOString(),
|
||||
params.orderBy,
|
||||
params.pageSize,
|
||||
params.currentPage
|
||||
);
|
||||
setData(result);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Error fetching auction data:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [token]);
|
||||
|
||||
const updateSearchParams = useCallback((newParams) => {
|
||||
setSearchParams(prev => ({
|
||||
...prev,
|
||||
...newParams
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const handleSearch = useCallback(async (newParams = {}) => {
|
||||
const updatedParams = {
|
||||
...searchParams,
|
||||
...newParams,
|
||||
currentPage: newParams.currentPage || 1 // Reset to first page on new search
|
||||
};
|
||||
updateSearchParams(updatedParams);
|
||||
return await fetchData(updatedParams);
|
||||
}, [searchParams, fetchData]);
|
||||
|
||||
const handleReset = useCallback(async () => {
|
||||
const resetParams = {
|
||||
landType: 'ID',
|
||||
landData: '',
|
||||
userType: 'GUID',
|
||||
userData: '',
|
||||
landSize: 'ALL',
|
||||
status: 'ALL',
|
||||
auctionStartDate: '',
|
||||
auctionEndDate: '',
|
||||
orderBy: 'DESC',
|
||||
pageSize: initialPageSize,
|
||||
currentPage: 1
|
||||
};
|
||||
setSearchParams(resetParams);
|
||||
return await fetchData(resetParams);
|
||||
}, [initialPageSize, fetchData]);
|
||||
|
||||
const handlePageChange = useCallback(async (newPage) => {
|
||||
return await handleSearch({ currentPage: newPage });
|
||||
}, [handleSearch]);
|
||||
|
||||
const handlePageSizeChange = useCallback(async (newSize) => {
|
||||
return await handleSearch({ pageSize: newSize, currentPage: 1 });
|
||||
}, [handleSearch]);
|
||||
|
||||
const handleOrderByChange = useCallback(async (newOrder) => {
|
||||
return await handleSearch({ orderBy: newOrder });
|
||||
}, [handleSearch]);
|
||||
|
||||
return {
|
||||
searchParams,
|
||||
loading,
|
||||
data,
|
||||
handleSearch,
|
||||
handleReset,
|
||||
handlePageChange,
|
||||
handlePageSizeChange,
|
||||
handleOrderByChange,
|
||||
updateSearchParams
|
||||
};
|
||||
};
|
||||
|
||||
const LandAuctionSearchBar = ({ searchParams, onSearch, onReset }) => {
|
||||
const handleSubmit = event => {
|
||||
event.preventDefault();
|
||||
|
||||
onSearch(searchParams);
|
||||
};
|
||||
|
||||
const searchList = [
|
||||
<>
|
||||
<InputGroup>
|
||||
<SelectInput value={searchParams.landType} onChange={e => onSearch({landType: e.target.value })}>
|
||||
{landSearchType.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<TextInput
|
||||
type="text"
|
||||
placeholder={searchParams.landType === 'ID' ? '랜드 ID 입력' : '랜드명 입력'}
|
||||
value={searchParams.landData}
|
||||
width="300px"
|
||||
onChange={e => onSearch({ landData: e.target.value })}
|
||||
/>
|
||||
</InputGroup>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>낙찰자</InputLabel>
|
||||
<InputGroup>
|
||||
<SelectInput value={searchParams.userType} onChange={e => onSearch({userType: e.target.value })}>
|
||||
{userSearchType.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<TextInput
|
||||
type="text"
|
||||
placeholder={searchParams.userType === 'GUID' ? 'GUID 입력' : '닉네임 입력'}
|
||||
value={searchParams.userData}
|
||||
width="300px"
|
||||
onChange={e => onSearch({ userData: e.target.value })}
|
||||
/>
|
||||
</InputGroup>
|
||||
</>
|
||||
];
|
||||
|
||||
const optionList = [
|
||||
<>
|
||||
<InputLabel>랜드크기</InputLabel>
|
||||
<SelectInput value={searchParams.landSize} onChange={e => onSearch({ landSize: e.target.value }, false)} >
|
||||
{landSize.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>경매상태</InputLabel>
|
||||
<SelectInput value={searchParams.status} onChange={e => onSearch({ status: e.target.value }, false)}>
|
||||
{landAuctionStatus.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>경매 일자</InputLabel>
|
||||
<SearchPeriod
|
||||
startDate={searchParams.auctionStartDate}
|
||||
handleStartDate={date => onSearch({ auctionStartDate: date }, false)}
|
||||
endDate={searchParams.auctionEndDate}
|
||||
handleEndDate={date => onSearch({ auctionEndDate: date }, false)}
|
||||
/>
|
||||
</>,
|
||||
<></>,<></>,
|
||||
<>
|
||||
<BtnWrapper $gap="8px">
|
||||
<Button theme="reset" handleClick={onReset} type="button" />
|
||||
<Button theme="search" text="검색" handleClick={handleSubmit} type="submit" />
|
||||
</BtnWrapper>
|
||||
</>,
|
||||
];
|
||||
return <SearchBarLayout firstColumnData={searchList} secondColumnData={optionList} direction={'column'} />;
|
||||
};
|
||||
|
||||
export default LandAuctionSearchBar;
|
||||
979
src/components/ServiceManage/MailDetailModal.js
Normal file
979
src/components/ServiceManage/MailDetailModal.js
Normal file
@@ -0,0 +1,979 @@
|
||||
import { styled } from 'styled-components';
|
||||
import RadioInput from '../../components/common/input/Radio';
|
||||
import { useState, useEffect, Fragment } from 'react';
|
||||
import CheckBox from '../../components/common/input/CheckBox';
|
||||
|
||||
import { Title, SelectInput, BtnWrapper, TextInput, Label, InputLabel, DatePickerWrapper, Textarea, ModalText, ButtonClose, SearchBarAlert } from '../../styles/Components';
|
||||
import Button from '../../components/common/button/Button';
|
||||
import Modal from '../../components/common/modal/Modal';
|
||||
|
||||
import IconDelete from '../../assets/img/icon/icon-delete.png';
|
||||
import CloseIcon from '../../assets/img/icon/icon-close.png';
|
||||
import DatePickerComponent from '../common/Date/DatePickerComponent';
|
||||
import MailRegistUploadBtn from '../../components/ServiceManage/MailRegistUploadBtn';
|
||||
import { benItems, HourList, MinuteList, modalTypes, wellType } from '../../assets/data';
|
||||
import { EventModify, MailModify } from '../../apis';
|
||||
|
||||
import { authList } from '../../store/authList';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { convertKTC, convertKTCDate, timeDiffMinute } from '../../utils';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import DynamicModal from '../common/modal/DynamicModal';
|
||||
|
||||
const MailDetailModal = ({ detailView, handleDetailView, content }) => {
|
||||
const userInfo = useRecoilValue(authList);
|
||||
const { t } = useTranslation();
|
||||
const token = sessionStorage.getItem('token');
|
||||
|
||||
const id = content && content.id;
|
||||
const onlyView = userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === 23);
|
||||
const updateAuth = userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === 23);
|
||||
|
||||
const [sendHour, setSendHour] = useState('00');
|
||||
const [sendMin, setSendMin] = useState('00');
|
||||
|
||||
const [item, setItem] = useState('');
|
||||
const [itemCount, setItemCount] = useState('');
|
||||
const [resource, setResource] = useState('19010001');
|
||||
const [resourceCount, setResourceCount] = useState('');
|
||||
|
||||
const [modifyModal, setModifyModal] = useState('hidden');
|
||||
const [completeModal, setCompleteModal] = useState('hidden');
|
||||
const [resultData, setResultData] = useState({});
|
||||
|
||||
const [modalState, setModalState] = useState({
|
||||
updateConfirmModal: 'hidden',
|
||||
updateCompleteModal: 'hidden',
|
||||
});
|
||||
|
||||
const [isNullValue, setIsNullValue] = useState(false);
|
||||
// 과거 판단
|
||||
const [isPast, setIsPast] = useState(false);
|
||||
const [isChanged, setIsChanged] = useState(false);
|
||||
const [isItemNullValue, setIsItemNullValue] = useState(false);
|
||||
|
||||
const [excelFile, setExcelFile] = useState(content.target ? content.target : '');
|
||||
const [excelName, setExcelName] = useState(null);
|
||||
const [downloadData, setDownLoadData] = useState(null);
|
||||
|
||||
const [btnValidation, setBtnValidation] = useState(false);
|
||||
const [updateMessage, setUpdateMessage] = useState('수정이 완료되었습니다.');
|
||||
const [alertMessage, setAlertMessage] = useState('');
|
||||
const [undefinedFile, setUndefinedFile] = useState(false);
|
||||
const [disabledBtn, setDisabledBtn] = useState(false); // 예약 발송 확인용
|
||||
const [alertMsg, setAlertMsg] = useState('');
|
||||
|
||||
const KOREAN_TIME = content && convertKTCDate(content.send_dt);
|
||||
|
||||
const initialData = {
|
||||
send_hour: content && KOREAN_TIME.getHours() < 10 ? '0' + content && KOREAN_TIME.getHours() : content && KOREAN_TIME.getHours(),
|
||||
send_min: content && KOREAN_TIME.getMinutes() < 10 ? '0' + content && KOREAN_TIME.getMinutes() : content && KOREAN_TIME.getMinutes(),
|
||||
send_status: content && content.send_status,
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
document.querySelector('#fileinput').value = '';
|
||||
|
||||
setResultData({
|
||||
is_reserve: content && content.is_reserve,
|
||||
send_dt: content && KOREAN_TIME,
|
||||
mail_type: content && content.mail_type,
|
||||
receive_type: content && content.receive_type,
|
||||
user_type: content && content.user_type,
|
||||
mail_list: content && content.mail_list,
|
||||
item_list: content && content.item_list,
|
||||
guid: content && content.target,
|
||||
});
|
||||
|
||||
content && content.mail_list.length === 1 && setBtnValidation(true);
|
||||
content && content.is_reserve === false && setBtnValidation(true);
|
||||
|
||||
setItem('');
|
||||
KOREAN_TIME < new Date ? setIsPast(true) : setIsPast(false);
|
||||
setExcelFile(content && content.target);
|
||||
setDownLoadData(content && content.target);
|
||||
setDisabledBtn(content && content.is_reserve && false);
|
||||
|
||||
// 복수 or 단일일 때 content.target을 exel name 으로 지정
|
||||
(content && content.receive_type === 'MULTIPLE') ?
|
||||
setExcelName(content && content.target)
|
||||
: setExcelName('')
|
||||
|
||||
}, [content]);
|
||||
|
||||
// console.log('downloadData', downloadData);
|
||||
// console.log('isPast', isPast);
|
||||
// console.log("guid", resultData.guid)
|
||||
// console.log("메일 형식", content && content.receive_type," 엑셀네임", excelName)
|
||||
|
||||
// 아이템 수량 숫자 체크
|
||||
const handleItemCount = e => {
|
||||
if (e.target.value === '0' || e.target.value === '-0') {
|
||||
setItemCount('1');
|
||||
e.target.value = '1';
|
||||
} else if (e.target.value < 0) {
|
||||
let plusNum = Math.abs(e.target.value);
|
||||
setItemCount(plusNum);
|
||||
} else {
|
||||
setItemCount(e.target.value);
|
||||
}
|
||||
};
|
||||
|
||||
// 아이템 추가
|
||||
const handleItemList = () => {
|
||||
if(benItems.includes(item)){
|
||||
setAlertMsg(t('MAIL_ITEM_ADD_BEN'))
|
||||
return;
|
||||
}
|
||||
item.length === 0 || itemCount.length === 0 ? setIsItemNullValue(true) : setIsItemNullValue(false);
|
||||
|
||||
if (item.length === '' || itemCount.length === 0 || itemCount <= 0) {
|
||||
setIsItemNullValue(true);
|
||||
} else if (item.length !== 0) {
|
||||
setIsItemNullValue(false);
|
||||
setIsChanged(true);
|
||||
|
||||
const newItem = { item: item, item_cnt: itemCount };
|
||||
resultData.item_list.push(newItem);
|
||||
|
||||
setItem('');
|
||||
setItemCount('');
|
||||
}
|
||||
};
|
||||
|
||||
// 아이템 삭제
|
||||
const onItemRemove = id => {
|
||||
let filterList = resultData.item_list && resultData.item_list.filter(item => item !== resultData.item_list[id]);
|
||||
setIsChanged(true);
|
||||
|
||||
// console.log('filterList', filterList);
|
||||
|
||||
setResultData({ ...resultData, item_list: filterList });
|
||||
};
|
||||
|
||||
// 자원 수량 숫자 체크
|
||||
const handleResourceCount = e => {
|
||||
if (e.target.value === '0' || e.target.value === '-0') {
|
||||
setResourceCount('1');
|
||||
e.target.value = '1';
|
||||
} else if (e.target.value < 0) {
|
||||
let plusNum = Math.abs(e.target.value);
|
||||
setResourceCount(plusNum);
|
||||
} else {
|
||||
setResourceCount(e.target.value);
|
||||
}
|
||||
};
|
||||
|
||||
// 자원 추가
|
||||
const handleResourceList = () => {
|
||||
resourceCount.length === 0 ? setIsItemNullValue(true) : setIsItemNullValue(false);
|
||||
|
||||
if (resourceCount.length === 0 || resourceCount <= 0) {
|
||||
setIsItemNullValue(true);
|
||||
} else {
|
||||
setIsItemNullValue(false);
|
||||
const name = wellType.find(well => well.value === resource).name;
|
||||
const newItem = { item: resource, item_cnt: resourceCount, item_name: name };
|
||||
|
||||
resultData.item_list.push(newItem);
|
||||
|
||||
setResourceCount('');
|
||||
}
|
||||
};
|
||||
|
||||
// 입력창 삭제
|
||||
const onLangDelete = language => {
|
||||
let filterList = resultData.mail_list && resultData.mail_list.filter(el => el.language !== language);
|
||||
|
||||
if (filterList.length === 1) setBtnValidation(true);
|
||||
|
||||
setIsChanged(true);
|
||||
setResultData({ ...resultData, mail_list: filterList });
|
||||
};
|
||||
|
||||
// 발송 날짜 세팅 로직
|
||||
const handleSelectedDate = data => {
|
||||
const sendDate = new Date(data);
|
||||
|
||||
const resultSendData = new Date(sendDate.getFullYear(), sendDate.getMonth(), sendDate.getDate(), sendHour, sendMin);
|
||||
|
||||
setIsChanged(true);
|
||||
setResultData({ ...resultData, send_dt: resultSendData });
|
||||
};
|
||||
|
||||
// 발송 시간 세팅 로직
|
||||
const handleSendTime = e => {
|
||||
if (e.target.id === 'hour') setSendHour(e.target.value);
|
||||
else if (e.target.id === 'min') setSendMin(e.target.value);
|
||||
|
||||
const sendDate = new Date(resultData.send_dt);
|
||||
const result = new Date(sendDate.getFullYear(), sendDate.getMonth(), sendDate.getDate(), e.target.id === 'hour' ? e.target.value : sendHour, e.target.id === 'min' ? e.target.value : sendMin);
|
||||
|
||||
setIsChanged(true);
|
||||
setResultData({ ...resultData, send_dt: result });
|
||||
};
|
||||
|
||||
// 우편 상세 정보 수정
|
||||
const handleModifyModal = () => {
|
||||
if (
|
||||
resultData.mail_list.map(data => data.content === '' || data.title === '').includes(true) ||
|
||||
(resultData.receive_type === 'MULTIPLE' ? excelFile === null : resultData.guid === '') ||
|
||||
resultData.send_dt.length === 0 ||
|
||||
resultData.mail_type === 'SELECT' ||
|
||||
isChanged === false ||
|
||||
alertMessage
|
||||
) {
|
||||
isChanged === true && setIsNullValue(true);
|
||||
} else {
|
||||
// 복수로 수정하거나 복수로 등록할 때 항상 excel name을 넘겨줘야합니다.
|
||||
content && content.receive_type === 'MULTIPLE' ? setResultData({ ...resultData, file_name: excelName === null ? downloadData : excelName }) : setExcelName('');
|
||||
|
||||
// setExcelName(content && content.target)
|
||||
|
||||
setIsNullValue(false);
|
||||
if (modifyModal === 'hidden') {
|
||||
setModifyModal('view');
|
||||
} else {
|
||||
setModifyModal('hidden');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 상세 정보 단일 체크
|
||||
const handleSingleBtn = () => {
|
||||
if (content && content.is_reserve === true && !onlyView && !isPast) {
|
||||
setResultData({ ...resultData, guid: '' });
|
||||
delete resultData.file_name;
|
||||
|
||||
document.querySelector('#fileinput').value = '';
|
||||
|
||||
setExcelFile(null);
|
||||
setIsChanged(true);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// 상세 정보 복수 체크
|
||||
const handleMultiBtn = () => {
|
||||
if (content && content.is_reserve === true) {
|
||||
delete resultData.guid;
|
||||
}
|
||||
setIsChanged(true);
|
||||
};
|
||||
|
||||
// 완료 모달창
|
||||
const handleCompleteModal = () => {
|
||||
if (completeModal === 'hidden') {
|
||||
setCompleteModal('view');
|
||||
} else {
|
||||
setCompleteModal('hidden');
|
||||
|
||||
window.location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
// 메일 수정 버튼
|
||||
const handleModifyMail = () => {
|
||||
|
||||
if(resultData.receive_type === 'MULTIPLE') {
|
||||
delete resultData.guid
|
||||
|
||||
MailModify(token, id, resultData);
|
||||
} else {
|
||||
MailModify(token, id, resultData);
|
||||
}
|
||||
|
||||
handleCompleteModal();
|
||||
handleModifyModal();
|
||||
};
|
||||
|
||||
// 확인 버튼 후 다 초기화
|
||||
const handleReset = () => {
|
||||
setBtnValidation(false);
|
||||
setIsNullValue(false);
|
||||
setIsChanged(false);
|
||||
setUndefinedFile(false);
|
||||
setIsItemNullValue(false);
|
||||
setAlertMessage('');
|
||||
setExcelFile(null);
|
||||
setExcelName(null);
|
||||
};
|
||||
|
||||
// 상세 페이지에서 파일 삭제
|
||||
const handleDetailDelete = e => {
|
||||
e.preventDefault();
|
||||
|
||||
if (content && content.is_reserve === true) {
|
||||
setDownLoadData(undefined);
|
||||
setUndefinedFile(true);
|
||||
setResultData({ ...resultData, guid: '' });
|
||||
delete resultData.file_name;
|
||||
|
||||
document.querySelector('#fileinput').value = '';
|
||||
|
||||
setExcelFile(null);
|
||||
}
|
||||
|
||||
setIsChanged(true);
|
||||
};
|
||||
|
||||
const handleModalView = (type) => {
|
||||
setModalState((prevState) => ({
|
||||
...prevState,
|
||||
[`${type}Modal`]: 'view',
|
||||
}));
|
||||
}
|
||||
|
||||
const handleModalClose = (type) => {
|
||||
setModalState((prevState) => ({
|
||||
...prevState,
|
||||
[`${type}Modal`]: 'hidden',
|
||||
}));
|
||||
}
|
||||
|
||||
const handleSubmit = async (type, param = null) => {
|
||||
switch (type) {
|
||||
case "submit":
|
||||
// if (!conditionCheck()) return;
|
||||
|
||||
handleModalView('updateConfirm');
|
||||
break;
|
||||
case "updateConfirm":
|
||||
const timeDiff = timeDiffMinute(resultData.start_dt, (new Date))
|
||||
// 이벤트 시작 30분전이나 이미 SystemMail이 add된 상태에서는 수정할 수 없다.
|
||||
if(content.add_flag || timeDiff <= 30){
|
||||
setAlertMsg(t('EVENT_TIME_LIMIT_UPDATE'));
|
||||
handleModalClose('updateConfirm');
|
||||
return;
|
||||
}
|
||||
EventModify(token, id, resultData);
|
||||
handleModalClose('updateConfirm');
|
||||
handleModalView('updateComplete');
|
||||
break;
|
||||
case "updateComplete":
|
||||
handleModalClose('updateComplete');
|
||||
window.location.reload();
|
||||
break;
|
||||
case "warning":
|
||||
setAlertMsg('');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal min="960px" $view={detailView}>
|
||||
<Title $align="center">우편 상세 정보</Title>
|
||||
|
||||
<RegistInfo>
|
||||
<span>등록자 : {content && content.create_by}</span>
|
||||
<span>등록일 : {content && convertKTC(content.create_dt, false)}</span>
|
||||
{content && typeof content.update_by !== 'undefined' && (
|
||||
<>
|
||||
<span>수정자 : {content && content.update_by}</span>
|
||||
<span>수정일 : {content && convertKTC(content.update_dt, false)}</span>
|
||||
</>
|
||||
)}
|
||||
</RegistInfo>
|
||||
<ModalWrapper>
|
||||
<RegistGroup>
|
||||
<InputRow>
|
||||
<CheckBox
|
||||
label="예약 발송"
|
||||
id="reserve"
|
||||
checked={resultData && resultData.is_reserve}
|
||||
setData={e => {
|
||||
setResultData({ ...resultData, is_reserve: e.target.checked });
|
||||
setDisabledBtn(e.target.checked);
|
||||
setIsChanged(true);
|
||||
}}
|
||||
disabled={(content && content.is_reserve === false) || onlyView}
|
||||
/>
|
||||
{content && content.is_reserve === false ? (
|
||||
<></>
|
||||
) : (
|
||||
content &&
|
||||
content.is_reserve === true &&
|
||||
resultData.is_reserve === true && (
|
||||
<InputItem>
|
||||
<InputLabel>발송 시간</InputLabel>
|
||||
<InputGroup>
|
||||
<DatePickerWrapper>
|
||||
<DatePickerComponent
|
||||
readOnly={(content && content.is_reserve === false) || onlyView || isPast}
|
||||
name={initialData.send_dt}
|
||||
selectedDate={resultData ? resultData.send_dt : initialData.send_dt}
|
||||
handleSelectedDate={data => handleSelectedDate(data)}
|
||||
pastDate={new Date()}
|
||||
/>
|
||||
</DatePickerWrapper>
|
||||
<SelectInput
|
||||
onChange={e => handleSendTime(e)}
|
||||
id="hour"
|
||||
disabled={(content && content.is_reserve === false) || onlyView || isPast}
|
||||
value={
|
||||
resultData && String(new Date(resultData.send_dt).getHours()) < 10
|
||||
? '0' + String(new Date(resultData.send_dt).getHours())
|
||||
: resultData && String(new Date(resultData.send_dt).getHours())
|
||||
}>
|
||||
{HourList.map(hour => (
|
||||
<option
|
||||
value={hour}
|
||||
key={hour}
|
||||
// selected={
|
||||
// resultData && String(new Date(resultData.send_dt).getHours()) < 10
|
||||
// ? '0' + String(new Date(resultData.send_dt).getHours()) === hour
|
||||
// : resultData && String(new Date(resultData.send_dt).getHours()) === hour
|
||||
// ? 'selected'
|
||||
// : ''
|
||||
// }
|
||||
>
|
||||
{hour}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<SelectInput
|
||||
onChange={e => {
|
||||
handleSendTime(e);
|
||||
setIsChanged(true);
|
||||
}}
|
||||
id="min"
|
||||
disabled={(content && content.is_reserve === false) || onlyView || isPast}
|
||||
value={
|
||||
resultData && String(new Date(resultData.send_dt).getMinutes()) < 10
|
||||
? '0' + String(new Date(resultData.send_dt).getMinutes())
|
||||
: resultData && String(new Date(resultData.send_dt).getMinutes())
|
||||
}>
|
||||
{MinuteList.map(min => (
|
||||
<option
|
||||
value={min}
|
||||
key={min}
|
||||
// selected={
|
||||
// resultData && String(new Date(resultData.send_dt).getMinutes()) < 10
|
||||
// ? '0' + String(new Date(resultData.send_dt).getMinutes()) === min
|
||||
// : resultData && String(new Date(resultData.send_dt).getMinutes()) === min
|
||||
// ? 'selected'
|
||||
// : ''
|
||||
// }
|
||||
>
|
||||
{min}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</InputGroup>
|
||||
</InputItem>
|
||||
)
|
||||
)}
|
||||
<InputItem>
|
||||
<InputLabel>우편 타입</InputLabel>
|
||||
<SelectInput
|
||||
onChange={e => {
|
||||
setResultData({ ...resultData, mail_type: e.target.value });
|
||||
setIsChanged(true);
|
||||
}}
|
||||
value={content && resultData.mail_type}
|
||||
disabled={(content && content.is_reserve === false) || onlyView || isPast}>
|
||||
<option value="SELECT">타입 선택</option>
|
||||
<option value="SYSTEM_GUID">시스템 안내</option>
|
||||
<option value="INSPECTION_COMPENSATION">점검 보상</option>
|
||||
<option value="RECOVER_COMPENSATION">복구 보상</option>
|
||||
<option value="EVENT_COMPENSATION">이벤트 보상</option>
|
||||
</SelectInput>
|
||||
</InputItem>
|
||||
<InputItem>
|
||||
<InputLabel>발송상태</InputLabel>
|
||||
<div>
|
||||
{content && initialData.send_status === 'WAIT' && <MailState>대기</MailState>}
|
||||
{content && initialData.send_status === 'FINISH' && <MailState result="success">완료</MailState>}
|
||||
{content && initialData.send_status === 'FAIL' && <MailState result="fail">실패</MailState>}
|
||||
</div>
|
||||
</InputItem>
|
||||
</InputRow>
|
||||
<MailReceiver>
|
||||
<InputItem>
|
||||
<InputLabel>수신대상</InputLabel>
|
||||
<InputItem>
|
||||
<SelectInput onChange={e => setResultData({ ...resultData, user_type: e.target.value })} value={resultData.user_type} disabled={(content && content.is_reserve === false) || onlyView || isPast}>
|
||||
<option value="GUID">GUID</option>
|
||||
<option value="NICKNAME">아바타명</option>
|
||||
</SelectInput>
|
||||
</InputItem>
|
||||
<div>
|
||||
<InputGroup>
|
||||
<RadioInput
|
||||
label="단일"
|
||||
id="SINGLE"
|
||||
name="receiver"
|
||||
value="SINGLE"
|
||||
disabled={(content && content.is_reserve === false) || onlyView || isPast}
|
||||
fontWeight="600"
|
||||
checked={resultData.receive_type === 'SINGLE'}
|
||||
handleChange={e => {
|
||||
setResultData({ ...resultData, receive_type: e.target.id });
|
||||
setIsChanged(true);
|
||||
}}
|
||||
handleClick={handleSingleBtn}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder={resultData.user_type === "GUID" ? "GUID 입력" : resultData.user_type === "NICKNAME" ? "아바타명 입력" : "이메일 입력"}
|
||||
disabled={resultData.receive_type !== 'SINGLE' || (content && content.is_reserve === false) || onlyView || isPast}
|
||||
// defaultValue={resultData.receive_type === 'SINGLE' && resultData.guid !== '' ? content.target : ''}
|
||||
value={resultData.receive_type === 'SINGLE' && resultData.guid !== '' ? resultData.guid : ''}
|
||||
onChange={e => {
|
||||
let list = [...resultData.guid];
|
||||
list = e.target.value;
|
||||
setResultData({ ...resultData, guid: list });
|
||||
setIsChanged(true);
|
||||
}}
|
||||
/>
|
||||
</InputGroup>
|
||||
{content && resultData.receive_type === 'MULTIPLE' && typeof resultData.guid !== 'undefined' ? (
|
||||
<InputGroup>
|
||||
<RadioInput
|
||||
label="복수"
|
||||
id="MULTIPLE"
|
||||
name="receiver"
|
||||
value="MULTIPLE"
|
||||
fontWeight="600"
|
||||
checked={content && resultData.receive_type === 'MULTIPLE'}
|
||||
disabled
|
||||
/>
|
||||
<MailRegistUploadBtn
|
||||
disabled={resultData.receive_type !== 'MULTIPLE'}
|
||||
downloadData={downloadData}
|
||||
setResultData={setResultData}
|
||||
resultData={resultData}
|
||||
handleDetailDelete={handleDetailDelete}
|
||||
setExcelFile={setExcelFile}
|
||||
alertMessage={alertMessage}
|
||||
setAlertMessage={setAlertMessage}
|
||||
undefinedFile={undefinedFile}
|
||||
disabledBtn={disabledBtn}
|
||||
excelName={excelName}
|
||||
setExcelName={setExcelName}
|
||||
status={initialData.send_status}
|
||||
/>
|
||||
</InputGroup>
|
||||
) : (
|
||||
<InputGroup>
|
||||
<RadioInput
|
||||
label="복수"
|
||||
id="MULTIPLE"
|
||||
name="receiver"
|
||||
value="MULTIPLE"
|
||||
fontWeight="600"
|
||||
disabled={(content && content.is_reserve === false) || onlyView || isPast}
|
||||
handleChange={e => setResultData({ ...resultData, receive_type: e.target.id })}
|
||||
handleClick={handleMultiBtn}
|
||||
/>
|
||||
<MailRegistUploadBtn
|
||||
setUpdateMessage={setUpdateMessage}
|
||||
disabled={resultData.receive_type !== 'MULTIPLE'}
|
||||
setResultData={setResultData}
|
||||
resultData={resultData}
|
||||
excelFile={excelFile}
|
||||
setExcelFile={setExcelFile}
|
||||
alertMessage={alertMessage}
|
||||
setAlertMessage={setAlertMessage}
|
||||
undefinedFile={undefinedFile}
|
||||
handleDetailDelete={handleDetailDelete}
|
||||
disabledBtn={disabledBtn}
|
||||
excelName={excelName}
|
||||
setExcelName={setExcelName}
|
||||
status={initialData.send_status}
|
||||
/>
|
||||
</InputGroup>
|
||||
)}
|
||||
</div>
|
||||
</InputItem>
|
||||
</MailReceiver>
|
||||
</RegistGroup>
|
||||
{resultData.mail_list &&
|
||||
resultData.mail_list.map(data => {
|
||||
return (
|
||||
<Fragment key={data.language}>
|
||||
<MailRegistBox>
|
||||
<LangArea>
|
||||
언어 : {data.language}
|
||||
{btnValidation === false ? (
|
||||
<BtnClose
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
onLangDelete(data.language);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<BtnClose opacity="10%" />
|
||||
)}
|
||||
</LangArea>
|
||||
<MailRegistTable>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th width="120">
|
||||
<Label>제목</Label>
|
||||
</th>
|
||||
<td>
|
||||
<InputItem>
|
||||
<TextInput
|
||||
placeholder="우편 제목 입력"
|
||||
maxLength="30"
|
||||
id={data.language}
|
||||
value={data.title}
|
||||
readOnly={(content && content.is_reserve === false) || onlyView || isPast}
|
||||
onChange={e => {
|
||||
let list = [...resultData.mail_list];
|
||||
let findIndex = resultData.mail_list && resultData.mail_list.findIndex(item => item.language === e.target.id);
|
||||
list[findIndex].title = e.target.value.trimStart();
|
||||
|
||||
setResultData({ ...resultData, mail_list: list });
|
||||
setIsChanged(true);
|
||||
}}
|
||||
/>
|
||||
</InputItem>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
<Label>내용</Label>
|
||||
</th>
|
||||
<td>
|
||||
<Textarea
|
||||
value={data.content}
|
||||
readOnly={(content && content.is_reserve === false) || onlyView || isPast}
|
||||
id={data.language}
|
||||
onChange={e => {
|
||||
if (e.target.value.length > 2000) {
|
||||
return;
|
||||
}
|
||||
let list = [...resultData.mail_list];
|
||||
let findIndex = resultData.mail_list && resultData.mail_list.findIndex(item => item.language === e.target.id);
|
||||
list[findIndex].content = e.target.value.trimStart();
|
||||
|
||||
setResultData({ ...resultData, mail_list: list });
|
||||
setIsChanged(true);
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</MailRegistTable>
|
||||
</MailRegistBox>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
<MailRegistBox>
|
||||
<MailRegistTable>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th width="120">
|
||||
<Label>아이템 첨부</Label>
|
||||
</th>
|
||||
<td>
|
||||
<InputItem>
|
||||
<TextInput
|
||||
placeholder="Item Meta id 입력"
|
||||
value={item}
|
||||
onChange={e => {
|
||||
let list = [];
|
||||
list = e.target.value.trimStart();
|
||||
setItem(list);
|
||||
}}
|
||||
disabled={(content && content.is_reserve === false) || onlyView || isPast}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder="수량"
|
||||
value={itemCount}
|
||||
type="number"
|
||||
onChange={e => handleItemCount(e)}
|
||||
width="90px"
|
||||
disabled={(content && content.is_reserve === false) || onlyView || isPast}
|
||||
/>
|
||||
<Button
|
||||
text="추가"
|
||||
theme={itemCount.length === 0 || item.length === 0 ? 'disable' : 'search'}
|
||||
handleClick={handleItemList}
|
||||
errorMessage={(content && content.is_reserve === false) || onlyView || isPast}
|
||||
/>
|
||||
</InputItem>
|
||||
{/* {isItemNullValue && <SearchBarAlert $marginTop="15px">필수값을 입력해주세요.</SearchBarAlert>}
|
||||
<div>
|
||||
{resultData.item_list && (
|
||||
<ItemList>
|
||||
{content &&
|
||||
resultData.item_list.map((data, index) => {
|
||||
return (
|
||||
<Item key={index}>
|
||||
<span>
|
||||
{data.item}({data.item_cnt})
|
||||
</span>
|
||||
{(content && content.is_reserve === false) ||
|
||||
(updateAuth && <BtnDelete onClick={() => onItemRemove(index)}></BtnDelete>) ||
|
||||
isPast
|
||||
}
|
||||
</Item>
|
||||
);
|
||||
})}
|
||||
</ItemList>
|
||||
)}
|
||||
</div> */}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th width="120">
|
||||
<Label>자원 첨부</Label>
|
||||
</th>
|
||||
<td>
|
||||
<InputItem>
|
||||
<SelectInput onChange={e => setResource(e.target.value)} value={resource} disabled={(content && content.is_reserve === false) || onlyView || isPast}>
|
||||
{wellType.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<TextInput placeholder="수량" type="number" value={resourceCount} disabled={(content && content.is_reserve === false) || onlyView || isPast} onChange={e => handleResourceCount(e)} width="200px" />
|
||||
<Button text="추가" theme={resourceCount.length === 0 || resource.length === 0 ? 'disable' : 'search'} handleClick={handleResourceList} width="100px" height="35px" errorMessage={(content && content.is_reserve === false) || onlyView || isPast} />
|
||||
</InputItem>
|
||||
{isItemNullValue && <SearchBarAlert $marginTop="15px">필수값을 입력해주세요.</SearchBarAlert>}
|
||||
|
||||
<div>
|
||||
{resultData.item_list && (
|
||||
<ItemList>
|
||||
{resultData.item_list.map((data, index) => {
|
||||
return (
|
||||
<Item key={index}>
|
||||
<span>
|
||||
{data.item_name}[{data.item}] ({data.item_cnt})
|
||||
</span>
|
||||
{(content && content.is_reserve === true) || onlyView || isPast && <BtnDelete onClick={() => onItemRemove(index)}></BtnDelete>}
|
||||
</Item>
|
||||
);
|
||||
})}
|
||||
</ItemList>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</MailRegistTable>
|
||||
</MailRegistBox>
|
||||
</ModalWrapper>
|
||||
<BtnWrapper $justify="flex-end" $gap="10px" $paddingTop="20px">
|
||||
<Button
|
||||
text="확인"
|
||||
theme="line"
|
||||
name="확인버튼"
|
||||
handleClick={() => {
|
||||
handleDetailView();
|
||||
handleReset();
|
||||
}}
|
||||
/>
|
||||
{(updateAuth && content && content.is_reserve === true) && !isPast && (
|
||||
<Button
|
||||
type="submit"
|
||||
text="수정"
|
||||
id="수정버튼"
|
||||
theme={
|
||||
(content && content.mail_list.map(data => data.content === '' || data.title === '').includes(true)) ||
|
||||
(resultData.receive_type === 'MULTIPLE' ? excelFile === null : resultData.guid === '') ||
|
||||
resultData.mail_type === 'SELECT' ||
|
||||
isChanged === false ||
|
||||
alertMessage.length > 1
|
||||
? 'disable'
|
||||
: 'primary'
|
||||
}
|
||||
handleClick={() => {
|
||||
handleModifyModal();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
{/* 확인 모달 */}
|
||||
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={modifyModal}>
|
||||
<BtnWrapper $justify="flex-end">
|
||||
<ButtonClose onClick={handleModifyModal} />
|
||||
</BtnWrapper>
|
||||
<ModalText $align="center">
|
||||
우편 정보 수정사항을 <br />
|
||||
저장하시겠습니까?
|
||||
</ModalText>
|
||||
<BtnWrapper $gap="10px">
|
||||
<Button text="취소" theme="line" size="large" width="100%" handleClick={handleModifyModal} />
|
||||
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleModifyMail} />
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
{/* 완료 모달 */}
|
||||
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={completeModal}>
|
||||
<BtnWrapper $justify="flex-end">
|
||||
<ButtonClose onClick={handleCompleteModal} />
|
||||
</BtnWrapper>
|
||||
<ModalText $align="center">수정이 완료되었습니다. </ModalText>
|
||||
<BtnWrapper $gap="10px">
|
||||
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleCompleteModal} />
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
{/* 경고 모달 */}
|
||||
<DynamicModal
|
||||
modalType={modalTypes.completed}
|
||||
view={alertMsg ? 'view' : 'hidden'}
|
||||
modalText={alertMsg}
|
||||
handleSubmit={() => handleSubmit('warning')}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MailDetailModal;
|
||||
|
||||
const InputItem = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
${TextInput},${SelectInput} {
|
||||
height: 35px;
|
||||
font-size: 14px;
|
||||
}
|
||||
${TextInput} {
|
||||
padding: 0 15px;
|
||||
}
|
||||
${SelectInput} {
|
||||
width: max-content;
|
||||
}
|
||||
`;
|
||||
|
||||
const ModalWrapper = styled.div`
|
||||
max-height: 70vh;
|
||||
padding-bottom: 5px;
|
||||
overflow: auto;
|
||||
&::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #666666;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #d9d9d9;
|
||||
}
|
||||
`;
|
||||
|
||||
const RegistInfo = styled.div`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 50px;
|
||||
font-size: 14px;
|
||||
margin-bottom: 10px;
|
||||
`;
|
||||
|
||||
const BtnClose = styled.button`
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: url(${CloseIcon}) 50% 50% no-repeat;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translate(0, -50%);
|
||||
right: 20px;
|
||||
opacity: ${props => props.opacity};
|
||||
`;
|
||||
|
||||
const LangArea = styled.div`
|
||||
background: #f9f9f9;
|
||||
padding: 10px 20px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const MailRegistBox = styled.div`
|
||||
margin-bottom: 20px;
|
||||
border-top: 1px solid #999;
|
||||
border-bottom: 1px solid #999;
|
||||
`;
|
||||
|
||||
const MailRegistTable = styled.table`
|
||||
th,
|
||||
td {
|
||||
padding: 15px 0;
|
||||
}
|
||||
td {
|
||||
${TextInput} {
|
||||
max-width: 600px;
|
||||
}
|
||||
${Textarea} {
|
||||
width: 100%;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 5px;
|
||||
height: 150px;
|
||||
padding: 15px;
|
||||
&:focus {
|
||||
border: 1px solid #2c2c2c;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const RegistGroup = styled.div`
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-flow: column;
|
||||
gap: 20px;
|
||||
padding: 20px;
|
||||
border-top: 1px solid #000;
|
||||
border-bottom: 1px solid #000;
|
||||
font-size: 14px;
|
||||
margin-bottom: 40px;
|
||||
`;
|
||||
|
||||
const InputRow = styled.div`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px 50px;
|
||||
`;
|
||||
|
||||
const InputGroup = styled.div`
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const MailReceiver = styled.div`
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
${InputItem} {
|
||||
align-items: flex-start;
|
||||
}
|
||||
${InputLabel} {
|
||||
line-height: 35px;
|
||||
}
|
||||
${TextInput} {
|
||||
margin-left: 20px;
|
||||
width: 400px;
|
||||
}
|
||||
${InputGroup} {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
`;
|
||||
|
||||
const MailState = styled.span`
|
||||
font-weight: 600;
|
||||
color: ${props => (props.result === 'success' ? '#08994B' : props.result === 'fail' ? '#ff0000' : '#2c2c2c')};
|
||||
`;
|
||||
|
||||
const ItemList = styled.ul`
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
padding: 10px 20px;
|
||||
flex-wrap: wrap;
|
||||
`;
|
||||
|
||||
const Item = styled.li`
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const BtnDelete = styled.button`
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: url(${IconDelete}) 50% 50% no-repeat;
|
||||
`;
|
||||
143
src/components/ServiceManage/MailListSearchBar.js
Normal file
143
src/components/ServiceManage/MailListSearchBar.js
Normal file
@@ -0,0 +1,143 @@
|
||||
import styled from 'styled-components';
|
||||
import { TextInput, BtnWrapper, InputLabel, SelectInput } from '../../styles/Components';
|
||||
import Button from '../common/button/Button';
|
||||
import { SearchBarLayout, SearchPeriod } from '../common/SearchBar';
|
||||
import { useState } from 'react';
|
||||
import { mailReceiveType, mailSendStatus, mailSendType, mailType } from '../../assets/data';
|
||||
|
||||
const MailListSearchBar = ({ handleSearch, setResultData }) => {
|
||||
const [searchData, setSearchData] = useState({
|
||||
mailTitle: '',
|
||||
content: '',
|
||||
sendType: 'ALL',
|
||||
sendStatus: 'ALL',
|
||||
mailType: 'ALL',
|
||||
receiveType: 'ALL',
|
||||
sendDate: '',
|
||||
endDate: '',
|
||||
});
|
||||
|
||||
const handleSubmit = event => {
|
||||
event.preventDefault();
|
||||
|
||||
handleSearch(
|
||||
searchData.mailTitle,
|
||||
searchData.content,
|
||||
searchData.sendType ? searchData.sendType : 'ALL',
|
||||
searchData.sendStatus ? searchData.sendStatus : 'ALL',
|
||||
searchData.mailType ? searchData.mailType : 'ALL',
|
||||
searchData.receiveType ? searchData.receiveType : 'ALL',
|
||||
searchData.sendDate ? searchData.sendDate : '',
|
||||
searchData.endDate ? searchData.endDate : new Date(),
|
||||
(searchData.sendDate && searchData.endDate === '') && setSearchData({ sendDate : searchData.sendDate ,endDate : new Date()}),
|
||||
);
|
||||
|
||||
setResultData(searchData);
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setSearchData({
|
||||
mailTitle: '',
|
||||
content: '',
|
||||
sendType: 'ALL',
|
||||
sendStatus: 'ALL',
|
||||
mailType: 'ALL',
|
||||
receiveType: 'ALL',
|
||||
sendDate: '',
|
||||
endDate: '',
|
||||
order: 'DESC',
|
||||
});
|
||||
|
||||
handleSearch('', '', 'ALL', 'ALL', 'ALL', 'ALL', '', '');
|
||||
setResultData('', '', 'ALL', 'ALL', 'ALL', 'ALL', '', '');
|
||||
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
// console.log("searchData.endDate", searchData.endDate)
|
||||
|
||||
const searchList = [
|
||||
<>
|
||||
<InputLabel>우편 제목</InputLabel>
|
||||
<TextInput
|
||||
type="text"
|
||||
placeholder="우편 제목"
|
||||
value={searchData.mailTitle}
|
||||
onChange={e => setSearchData({ ...searchData, mailTitle: e.target.value })}
|
||||
/>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>조회 일자</InputLabel>
|
||||
<SearchPeriod
|
||||
startDate={searchData.sendDate}
|
||||
handleStartDate={data => {
|
||||
setSearchData({ ...searchData, sendDate: data });
|
||||
}}
|
||||
endDate={searchData.endDate}
|
||||
handleEndDate={data => setSearchData({ ...searchData, endDate: data })}
|
||||
maxDate={new Date()}
|
||||
/>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>우편 내용</InputLabel>
|
||||
<TextInput
|
||||
type="text"
|
||||
placeholder="우편 내용(공백으로 구분)"
|
||||
value={searchData.content}
|
||||
onChange={e => setSearchData({ ...searchData, content: e.target.value })}
|
||||
/>
|
||||
</>
|
||||
];
|
||||
|
||||
const optionList = [
|
||||
<>
|
||||
<InputLabel>발송 방식</InputLabel>
|
||||
<SelectInput value={searchData.sendType} onChange={e => setSearchData({ ...searchData, sendType: e.target.value })}>
|
||||
{mailSendType.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>발송 상태</InputLabel>
|
||||
<SelectInput value={searchData.sendStatus} onChange={e => setSearchData({ ...searchData, sendStatus: e.target.value })}>
|
||||
{mailSendStatus.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>우편 타입</InputLabel>
|
||||
<SelectInput value={searchData.mailType} onChange={e => setSearchData({ ...searchData, mailType: e.target.value })}>
|
||||
{mailType.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>수신 대상</InputLabel>
|
||||
<SelectInput value={searchData.receiveType} onChange={e => setSearchData({ ...searchData, receiveType: e.target.value })}>
|
||||
{mailReceiveType.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<>
|
||||
<BtnWrapper $gap="8px">
|
||||
<Button theme="reset" handleClick={handleReset} type="button" />
|
||||
<Button theme="search" text="검색" handleClick={handleSubmit} type="submit" />
|
||||
</BtnWrapper>
|
||||
</>,
|
||||
];
|
||||
return <SearchBarLayout firstColumnData={searchList} secondColumnData={optionList} direction={'column'} />;
|
||||
};
|
||||
|
||||
export default MailListSearchBar;
|
||||
206
src/components/ServiceManage/MailRegistUploadBtn.js
Normal file
206
src/components/ServiceManage/MailRegistUploadBtn.js
Normal file
@@ -0,0 +1,206 @@
|
||||
import styled, { css } from 'styled-components';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { MailExcelDown, MailMultiRegsit } from '../../apis';
|
||||
import { AlertText } from '../../styles/Components';
|
||||
|
||||
const MailRegistUploadBtn = ({ disabled, setResultData, resultData, downloadData, disabledBtn, setExcelFile, alertMessage, setAlertMessage, handleDetailDelete, excelName, setExcelName, status }) => {
|
||||
useEffect(() => {
|
||||
excelName && setExcelName(null);
|
||||
}, [downloadData]);
|
||||
|
||||
const handleFile = async e => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
|
||||
setExcelFile(e.target.files[0]);
|
||||
setExcelName(e.target.files[0].name);
|
||||
|
||||
let updateData = await MailMultiRegsit(token, e.target.files[0]);
|
||||
const message = updateData.data.data.message;
|
||||
|
||||
if (message === 'Excel 파일을 선택해주세요.'
|
||||
|| message === 'Maximum upload size exceeded'
|
||||
|| message === "guid(32자)를 확인해주세요.") {
|
||||
setAlertMessage('유효하지 않은 파일입니다.');
|
||||
} else if (message === '중복된 유저 정보가 있습니다.') {
|
||||
setAlertMessage('중복된 유저 정보가 있습니다.');
|
||||
} else {
|
||||
setAlertMessage('');
|
||||
}
|
||||
|
||||
handlePostGuid(updateData);
|
||||
};
|
||||
|
||||
// 저장된 엑셀 파일 -> API 로 전송
|
||||
const handlePostGuid = fileName => {
|
||||
// console.log("fileName.data.data.file_name", fileName.data.data.file_name)
|
||||
setResultData({ ...resultData, file_name: fileName.data.data.file_name });
|
||||
};
|
||||
|
||||
// 등록 페이지에서 파일 삭제
|
||||
const handleFileDelete = e => {
|
||||
document.querySelector('#fileinput').value = '';
|
||||
setResultData({ ...resultData, list: [], file_name: '' });
|
||||
|
||||
setExcelFile(null);
|
||||
};
|
||||
|
||||
// 상세페이지에서 파일 다운로드 버튼
|
||||
const handleDownloadExcel = async () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
await MailExcelDown(token, downloadData ? downloadData : 'mail_sample.xlsx');
|
||||
};
|
||||
|
||||
// console.log('undefinedFile 컴포넌트에서 ', undefinedFile);
|
||||
// console.log('excelFile ...', excelFile);
|
||||
|
||||
// console.log("excelName", excelName);
|
||||
// console.log(resultData);
|
||||
return (
|
||||
<>
|
||||
{/* form */}
|
||||
<div className="form-group custom-form">
|
||||
<FileWrapper>
|
||||
{/* 상세 페이지 : 파일업로드 버튼 */}
|
||||
<FileInput
|
||||
type="file"
|
||||
required onChange={handleFile}
|
||||
id="fileinput"
|
||||
disabled={disabledBtn || resultData.receive_type !== "MULTIPLE"}
|
||||
style={{ display: downloadData ? 'none' : 'block' }}
|
||||
/>
|
||||
|
||||
{/* 파일명 라벨 */}
|
||||
{downloadData && (
|
||||
<FileLabel htmlFor="fileinput" disabled={disabled}>
|
||||
{downloadData}
|
||||
</FileLabel>
|
||||
)}
|
||||
{/* 등록 페이지 : 파일이 있을 때 파일 삭제용 버튼 */}
|
||||
{excelName ? (
|
||||
<>
|
||||
<FileButton
|
||||
onClick={() => {
|
||||
handleFileDelete();
|
||||
setExcelName(null);
|
||||
}}>
|
||||
파일 삭제
|
||||
</FileButton>
|
||||
<AlertText>{alertMessage}</AlertText>
|
||||
</>
|
||||
) : // 상세 페이지 : 저장된 파일이 복수일 때
|
||||
resultData.guid && resultData.receive_type === 'MULTIPLE' ? (
|
||||
<>
|
||||
<FileButton
|
||||
disabled={resultData.is_reserve === false && disabledBtn || status === 'FINISH'}
|
||||
onClick={e => {
|
||||
handleDetailDelete(e);
|
||||
setExcelName(null);
|
||||
}}>
|
||||
파일 삭제
|
||||
</FileButton>
|
||||
</>
|
||||
) : (
|
||||
<FileButton htmlFor="fileinput" disabled={disabled}>
|
||||
엑셀 업로드
|
||||
</FileButton>
|
||||
)}
|
||||
|
||||
{downloadData && <FileButton onClick={handleDownloadExcel}>다운로드</FileButton>}
|
||||
{!downloadData && !disabled && <FileButton onClick={handleDownloadExcel}>샘플 다운로드</FileButton>}
|
||||
</FileWrapper>
|
||||
<FileNotice>* .xlsx, .xls 확장자의 파일만 업로드 가능합니다.</FileNotice>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MailRegistUploadBtn;
|
||||
|
||||
const FileWrapper = styled.div`
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin-right: 20px;
|
||||
margin-left: 20px;
|
||||
gap: 5px;
|
||||
`;
|
||||
|
||||
const FileButton = styled.label`
|
||||
border-radius: 5px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: fit-content;
|
||||
font-size: 14px;
|
||||
background: #2c2c2c;
|
||||
color: #fff;
|
||||
width: 100px;
|
||||
height: 35px;
|
||||
cursor: pointer;
|
||||
|
||||
${props =>
|
||||
props.disabled &&
|
||||
css`
|
||||
background: #b8b8b8;
|
||||
cursor: not-allowed;
|
||||
`}
|
||||
`;
|
||||
|
||||
const FileInput = styled.input`
|
||||
height: 35px;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 5px;
|
||||
width: 400px;
|
||||
padding: 0 15px;
|
||||
line-height: 35px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
&::file-selector-button {
|
||||
display: none;
|
||||
}
|
||||
&:disabled {
|
||||
color: #cccccc;
|
||||
background: #f6f6f6;
|
||||
}
|
||||
|
||||
${props =>
|
||||
props.disabled &&
|
||||
css`
|
||||
color: #cccccc;
|
||||
background: #f6f6f6;
|
||||
`}
|
||||
`;
|
||||
|
||||
const FileLabel = styled.label`
|
||||
height: 35px;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 5px;
|
||||
width: 400px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-all;
|
||||
white-space: nowrap;
|
||||
padding: 0 15px;
|
||||
line-height: 35px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
&::file-selector-button {
|
||||
display: none;
|
||||
}
|
||||
&:disabled {
|
||||
color: #cccccc;
|
||||
background: #f6f6f6;
|
||||
}
|
||||
|
||||
${props =>
|
||||
props.disabled &&
|
||||
css`
|
||||
color: #cccccc;
|
||||
background: #f6f6f6;
|
||||
`}
|
||||
`;
|
||||
|
||||
const FileNotice = styled.div`
|
||||
margin: 10px 25px 0;
|
||||
color: #cccccc;
|
||||
`;
|
||||
349
src/components/ServiceManage/ReportListAnswerModal.js
Normal file
349
src/components/ServiceManage/ReportListAnswerModal.js
Normal file
@@ -0,0 +1,349 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { styled } from 'styled-components';
|
||||
import { Title, BtnWrapper, TextInput, Label, Textarea, InputItem, ModalText, SearchBarAlert } from '../../styles/Components';
|
||||
|
||||
import { RepostReplyMessage } from '../../apis/Report';
|
||||
|
||||
import Modal from '../common/modal/Modal';
|
||||
import Button from '../common/button/Button';
|
||||
import CloseIcon from '../../assets/img/icon/icon-close.png';
|
||||
|
||||
const ReportListAnswerModal = ({ answerView, setAnswerView, detailData, replyData, pkId, skId }) => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
const [replied, setReplied] = useState(false);
|
||||
|
||||
// 버튼 validation -> true 일 때 disable 처리
|
||||
const [isNullValue, setIsNullValue] = useState(false);
|
||||
|
||||
// 발송 API 전달용 resultData 세팅
|
||||
const [resultData, setResultData] = useState({
|
||||
pk: '',
|
||||
sk: '',
|
||||
title: '',
|
||||
detail: '',
|
||||
reporter_nickname: '',
|
||||
});
|
||||
|
||||
// 모달 , 모달 메세지용 state
|
||||
const [modalStep, setModalStep] = useState(false);
|
||||
const [modalText, setModalText] = useState('');
|
||||
const [confirmModal, setConfirmModal] = useState('hidden');
|
||||
const [completeModal, setCompleteModal] = useState('hidden');
|
||||
|
||||
// 답변 조회 DATA 세팅용 resultData
|
||||
useEffect(() => {
|
||||
// 답변 조회 api에 호출할 pk, sk 세팅용
|
||||
const pk = encodeURI(pkId);
|
||||
const sk = encodeURI(skId);
|
||||
|
||||
setResultData({
|
||||
pk: pkId && pk,
|
||||
sk: skId && sk,
|
||||
title: replyData ? replyData.title : '',
|
||||
detail: replyData ? replyData.detail : '',
|
||||
reporter_nickname: detailData && detailData.reporter_nickname,
|
||||
});
|
||||
|
||||
// 답변 유무 확인용
|
||||
replyData ? setReplied(true) : setReplied(false);
|
||||
}, [detailData, replyData]);
|
||||
// console.log('신고답변작성 : ', resultData);
|
||||
|
||||
// Button Validation용 if 문
|
||||
const btnVali = resultData.title && resultData.title.length > 0 && resultData.detail && resultData.detail.length > 0;
|
||||
|
||||
const titleLeng = resultData.title && resultData.title.length;
|
||||
const detailLeng = resultData.detail && resultData.detail.length;
|
||||
|
||||
// Button Validation (2) - 에러 메세지
|
||||
const handleNullVali = e => {
|
||||
if (btnVali) {
|
||||
setIsNullValue(false);
|
||||
} else {
|
||||
setIsNullValue(true);
|
||||
}
|
||||
};
|
||||
|
||||
// 텍스트 변경 함수
|
||||
const handleNoticeText = e => {
|
||||
let btnName = e.target.name;
|
||||
|
||||
if (btnName === '취소버튼') {
|
||||
setModalText('신고 답변을 취소하시겠습니까? \n 취소 시 작성된 모든 정보가 사라집니다.');
|
||||
setModalStep(false);
|
||||
} else {
|
||||
setModalText('신고 답변 우편을 발송하시겠습니까?');
|
||||
setModalStep(true);
|
||||
}
|
||||
};
|
||||
|
||||
// 확인 모달
|
||||
const handleConfirmModal = e => {
|
||||
if (confirmModal === 'hidden') {
|
||||
setConfirmModal('view');
|
||||
} else {
|
||||
setConfirmModal('hidden');
|
||||
}
|
||||
};
|
||||
|
||||
// 완료 모달창
|
||||
const handleCompleteModal = () => {
|
||||
if (completeModal === 'hidden') {
|
||||
setCompleteModal('view');
|
||||
} else {
|
||||
setCompleteModal('hidden');
|
||||
window.location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
// 확인 모달창에서 취소, 확인 버튼 처리
|
||||
const handleModalStep = async e => {
|
||||
e.preventDefault();
|
||||
handleConfirmModal();
|
||||
|
||||
if (modalStep) {
|
||||
await RepostReplyMessage(token, resultData);
|
||||
setModalText('답변 발송이 완료되었습니다.');
|
||||
handleCompleteModal();
|
||||
} else {
|
||||
setModalText('답변이 취소되었습니다.');
|
||||
handleCompleteModal();
|
||||
}
|
||||
|
||||
handleReset();
|
||||
};
|
||||
|
||||
// 확인 버튼에서 불러올 초기화 처리
|
||||
const handleReset = () => {
|
||||
setAnswerView('hidden');
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal min="960px" $view={answerView}>
|
||||
<Title $align="center">{replied ? '신고 답변' : '신고 답변 작성'}</Title>
|
||||
<Subtitle>[답변 수신자 정보]</Subtitle>
|
||||
<ReportDetailState>
|
||||
<table>
|
||||
<caption></caption>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th width="100px">수신자 GUID</th>
|
||||
<td>
|
||||
<TextInput value={detailData.reporter_guid || ''} readOnly />
|
||||
</td>
|
||||
<th width="100px">닉네임</th>
|
||||
<td>
|
||||
<TextInput value={detailData.reporter_nickname || ''} readOnly />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>제목</th>
|
||||
<td colSpan="3">{detailData.title || ''}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th width="120">발송 경로</th>
|
||||
<td>개인 우편함</td>
|
||||
<th width="120">발송자</th>
|
||||
<td>GM</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ReportDetailState>
|
||||
|
||||
<Subtitle>[답변 내용]</Subtitle>
|
||||
<AnswerRegistTable>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th width="120">
|
||||
<Label>제목</Label>
|
||||
</th>
|
||||
<td>
|
||||
<InputItem>
|
||||
<TextInput
|
||||
maxLength="30"
|
||||
name="title"
|
||||
value={resultData.title || ''}
|
||||
onInput={e => {
|
||||
setResultData({ ...resultData, title: e.target.value.trimStart() });
|
||||
}}
|
||||
readOnly={replyData}
|
||||
/>
|
||||
</InputItem>
|
||||
{/* 답변 작성시에 글자 수 확인 */}
|
||||
<ReportNotice $color={titleLeng > 29 ? 'red' : '#666'}>* 최대 등록 가능 글자수 ({Number(titleLeng)}/30자)</ReportNotice>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
<Label>내용</Label>
|
||||
</th>
|
||||
<td>
|
||||
<Textarea
|
||||
value={resultData.detail || ''}
|
||||
name="detail"
|
||||
maxLength="2000"
|
||||
onInput={e => {
|
||||
setResultData({ ...resultData, detail: e.target.value.trimStart() });
|
||||
}}
|
||||
readOnly={replyData}
|
||||
/>
|
||||
{/* 답변 작성시에 글자 수 확인 */}
|
||||
<ReportNotice $color={detailLeng > 1999 ? 'red' : '#666'}>* 최대 등록 가능 글자수 ({Number(detailLeng)}/2000자)</ReportNotice>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</AnswerRegistTable>
|
||||
{isNullValue && (
|
||||
<SearchBarAlert $align="right" $padding="15px">
|
||||
필수값을 입력해주세요.
|
||||
</SearchBarAlert>
|
||||
)}
|
||||
<BtnWrapper $justify="flex-end" $gap="10px">
|
||||
{replyData ? (
|
||||
<Button text="확인" theme="line" handleClick={e => setAnswerView('hidden')} />
|
||||
) : (
|
||||
<>
|
||||
<Button
|
||||
text="취소"
|
||||
theme="line"
|
||||
name="취소버튼"
|
||||
handleClick={e => {
|
||||
handleConfirmModal(e);
|
||||
handleNoticeText(e);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
text="발송"
|
||||
name="발송버튼"
|
||||
// errorMessage={btnValidation}
|
||||
theme={btnVali ? 'primary' : 'disable'}
|
||||
handleClick={e => {
|
||||
{
|
||||
!btnVali ? handleNullVali(e) : handleConfirmModal(e);
|
||||
handleNoticeText(e);
|
||||
handleNullVali(e);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
|
||||
{/* 확인 모달 */}
|
||||
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={confirmModal}>
|
||||
<BtnWrapper $justify="flex-end">
|
||||
<ButtonClose onClick={handleConfirmModal} />
|
||||
</BtnWrapper>
|
||||
<ModalText $align="center">{modalText}</ModalText>
|
||||
<BtnWrapper $gap="10px">
|
||||
<Button text="취소" theme="line" size="large" width="100%" handleClick={handleConfirmModal} />
|
||||
<Button
|
||||
text="확인"
|
||||
theme="primary"
|
||||
type="submit"
|
||||
size="large"
|
||||
width="100%"
|
||||
handleClick={e => {
|
||||
handleModalStep(e);
|
||||
}}
|
||||
/>
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
{/* 완료 모달 */}
|
||||
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={completeModal}>
|
||||
<BtnWrapper $justify="flex-end">
|
||||
<ButtonClose onClick={handleCompleteModal} />
|
||||
</BtnWrapper>
|
||||
<ModalText $align="center">{modalText}</ModalText>
|
||||
<BtnWrapper $gap="10px">
|
||||
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleCompleteModal} />
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReportListAnswerModal;
|
||||
|
||||
const ReportDetailState = styled.div`
|
||||
border-top: 1px solid #000;
|
||||
border-bottom: 1px solid #000;
|
||||
padding: 15px 10px;
|
||||
font-size: 14px;
|
||||
margin-bottom: 20px;
|
||||
input {
|
||||
height: 35px;
|
||||
max-width: 330px;
|
||||
font-size: 14px;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
table {
|
||||
tr {
|
||||
th,
|
||||
td {
|
||||
padding: 5px;
|
||||
height: 45px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const Subtitle = styled.div`
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
line-height: 1.6;
|
||||
`;
|
||||
|
||||
const AnswerRegistTable = styled.table`
|
||||
border-top: 1px solid #999;
|
||||
margin-bottom: 20px;
|
||||
th,
|
||||
td {
|
||||
padding: 15px 0;
|
||||
border-bottom: 1px solid #f6f6f6;
|
||||
}
|
||||
tr:last-child {
|
||||
th,
|
||||
td {
|
||||
padding: 15px 0;
|
||||
border-bottom: 1px solid #999;
|
||||
}
|
||||
}
|
||||
th {
|
||||
vertical-align: top;
|
||||
line-height: 30px;
|
||||
}
|
||||
td {
|
||||
${TextInput} {
|
||||
max-width: 600px;
|
||||
}
|
||||
${Textarea} {
|
||||
width: 100%;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 5px;
|
||||
height: 150px;
|
||||
padding: 15px;
|
||||
&:focus {
|
||||
border: 1px solid #2c2c2c;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const ReportNotice = styled.span`
|
||||
font-size: 12px;
|
||||
font-weight: 300;
|
||||
color: ${props => props.$color || '#999'};
|
||||
margin-top: 10px;
|
||||
display: block;
|
||||
`;
|
||||
|
||||
const ButtonClose = styled.button`
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: url(${CloseIcon}) 50% 50% no-repeat;
|
||||
opacity: ${props => props.opacity};
|
||||
`;
|
||||
221
src/components/ServiceManage/ReportListDetailModal.js
Normal file
221
src/components/ServiceManage/ReportListDetailModal.js
Normal file
@@ -0,0 +1,221 @@
|
||||
import { styled } from 'styled-components';
|
||||
import { Title, BtnWrapper, TextInput, Label, Textarea, InputItem } from '../../styles/Components';
|
||||
|
||||
import Modal from '../common/modal/Modal';
|
||||
import Button from '../common/button/Button';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { convertKTC } from '../../utils';
|
||||
|
||||
const ReportListDetailModal = ({ detailView, handleDetailView, handleReply, detailData, replyData, replyAuth }) => {
|
||||
const [dataList, setDataList] = useState([]);
|
||||
|
||||
// UTC + 9 처리 해줄 변수
|
||||
const RESOLVE_TIME = detailData && new Date(detailData.resolution_time);
|
||||
const REPORT_TIME = detailData && new Date(detailData.create_time);
|
||||
|
||||
// 신고 유형 데이터 매핑
|
||||
const report_type = [
|
||||
{ value: 'ALL', name: '전체' },
|
||||
{ value: 'UNMANNERED_ACT', name: '비매너 행위' },
|
||||
{ value: 'USE_UNHEALTHY_NAMES', name: '불건전 이름 사용' },
|
||||
{ value: 'CASH_TRADING', name: '현금거래 행위' },
|
||||
{ value: 'INTERFERENCE_GAME', name: '게임 진행 방해' },
|
||||
{ value: 'INTERFERENCE_SERVICE', name: '운영서비스 방해' },
|
||||
{ value: 'ACCOUNT_EXPLOITATION', name: '계정도용' },
|
||||
{ value: 'BUG_ABUSING', name: '버그/어뷰징' },
|
||||
{ value: 'USE_HACK', name: '불법프로그램 사용' },
|
||||
{ value: 'LEAK_PERSONAL_INFO', name: '개인정보 유출' },
|
||||
{ value: 'PRETENDING_GM', name: '운영자 사칭' },
|
||||
];
|
||||
|
||||
// console.log(detailData);
|
||||
useEffect(() => {
|
||||
setDataList({
|
||||
create_time: detailData && convertKTC(REPORT_TIME, false),
|
||||
detail: detailData && detailData.detail,
|
||||
manager_email: replyData && replyData.manager_email,
|
||||
report_type: detailData && detailData.report_type,
|
||||
reporter_guid: detailData && detailData.reporter_guid,
|
||||
reporter_nickname: detailData && detailData.reporter_nickname,
|
||||
resolution_time: detailData && detailData.resolution_time,
|
||||
state: detailData && detailData.state,
|
||||
target_guid: detailData && detailData.target_guid,
|
||||
target_nickname: detailData && detailData.target_nickname,
|
||||
title: detailData && detailData.title,
|
||||
});
|
||||
}, [detailData]);
|
||||
|
||||
// console.log('모달창에서 리포트 상세 정보 : ', dataList);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal min="960px" $view={detailView}>
|
||||
<Title $align="center">신고내역 상세 정보</Title>
|
||||
{/* RegistInfo는 답변 완료시에만 보여집니다 */}
|
||||
{dataList && dataList.resolution_time && (
|
||||
<RegistInfo>
|
||||
<span>등록자(이메일주소) : {dataList && dataList.manager_email}</span>
|
||||
<span>등록일 : {dataList && convertKTC(RESOLVE_TIME)}</span>
|
||||
</RegistInfo>
|
||||
)}
|
||||
<Subtitle>[신고 대상 정보]</Subtitle>
|
||||
<ReportDetailState>
|
||||
<table>
|
||||
<caption></caption>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th width="120">신고 일자</th>
|
||||
<td>{dataList.create_time}</td>
|
||||
<th width="120">신고 유형</th>
|
||||
<td>{report_type.map(data => data.value === (dataList && dataList.report_type) && data.name)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>처리상태</th>
|
||||
<td colSpan="3">{dataList && dataList.state === 'RESOLVED' ? <ReportState results="solved">해결</ReportState> : <ReportState results="remain">미해결</ReportState>}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th width="100px">신고자 GUID</th>
|
||||
<td>
|
||||
<TextInput value={(dataList && dataList.reporter_guid) || ''} readOnly />
|
||||
</td>
|
||||
<th width="100px">닉네임</th>
|
||||
<td>
|
||||
<TextInput value={(dataList && dataList.reporter_nickname) || ''} readOnly />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th width="100px">신고대상 GUID</th>
|
||||
<td>
|
||||
<TextInput value={(dataList && dataList.target_guid) || ''} readOnly />
|
||||
</td>
|
||||
<th width="100px">닉네임</th>
|
||||
<td>
|
||||
<TextInput value={(dataList && dataList.target_nickname) || ''} readOnly />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ReportDetailState>
|
||||
|
||||
<Subtitle>[신고내용]</Subtitle>
|
||||
<AnswerRegistTable>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th width="120">
|
||||
<Label>제목</Label>
|
||||
</th>
|
||||
<td>
|
||||
<InputItem>
|
||||
<TextInput maxLength="30" value={(dataList && dataList.title) || ''} readOnly />
|
||||
</InputItem>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
<Label>내용</Label>
|
||||
</th>
|
||||
<td>
|
||||
<Textarea value={(dataList && dataList.detail) || ''} readOnly></Textarea>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</AnswerRegistTable>
|
||||
<BtnWrapper $justify="flex-end" $gap="10px">
|
||||
<Button text="확인" theme="line" handleClick={() => handleDetailView()} />
|
||||
{
|
||||
// 답변이 있을 때
|
||||
dataList && dataList.resolution_time ? (
|
||||
<Button text="답변보기" theme="line" handleClick={() => handleReply()} />
|
||||
) : // 답변이 없고, 답변 권한이 있을 때
|
||||
dataList && !dataList.resolution_time && replyAuth ? (
|
||||
<Button text="답변하기" theme="primary" handleClick={() => handleReply()} />
|
||||
) : (
|
||||
// 답변이 없고, 답변 권한도 없을 때
|
||||
<></>
|
||||
)
|
||||
}
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReportListDetailModal;
|
||||
|
||||
const ReportDetailState = styled.div`
|
||||
border-top: 1px solid #000;
|
||||
border-bottom: 1px solid #000;
|
||||
padding: 15px 10px;
|
||||
font-size: 14px;
|
||||
margin-bottom: 20px;
|
||||
input {
|
||||
height: 35px;
|
||||
max-width: 330px;
|
||||
font-size: 14px;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
table {
|
||||
tr {
|
||||
th,
|
||||
td {
|
||||
padding: 5px;
|
||||
height: 45px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
const ReportState = styled.span`
|
||||
font-weight: 600;
|
||||
color: ${props => (props.results === 'solved' ? '#08994B' : props.results === 'remain' ? '#ff0000' : '#2c2c2c')};
|
||||
`;
|
||||
const RegistInfo = styled.div`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 50px;
|
||||
font-size: 14px;
|
||||
margin-bottom: 10px;
|
||||
`;
|
||||
|
||||
const Subtitle = styled.div`
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
line-height: 1.6;
|
||||
`;
|
||||
|
||||
const AnswerRegistTable = styled.table`
|
||||
border-top: 1px solid #999;
|
||||
margin-bottom: 20px;
|
||||
th,
|
||||
td {
|
||||
padding: 15px 0;
|
||||
border-bottom: 1px solid #f6f6f6;
|
||||
}
|
||||
tr:last-child {
|
||||
th,
|
||||
td {
|
||||
padding: 15px 0;
|
||||
border-bottom: 1px solid #999;
|
||||
}
|
||||
}
|
||||
th {
|
||||
vertical-align: top;
|
||||
line-height: 30px;
|
||||
}
|
||||
td {
|
||||
${TextInput} {
|
||||
max-width: 600px;
|
||||
}
|
||||
${Textarea} {
|
||||
width: 100%;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 5px;
|
||||
height: 150px;
|
||||
padding: 15px;
|
||||
&:focus {
|
||||
border: 1px solid #2c2c2c;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
142
src/components/ServiceManage/ReportListSearchBar.js
Normal file
142
src/components/ServiceManage/ReportListSearchBar.js
Normal file
@@ -0,0 +1,142 @@
|
||||
import { styled } from 'styled-components';
|
||||
import { TextInput, BtnWrapper, InputLabel, SelectInput } from '../../styles/Components';
|
||||
import Button from '../common/button/Button';
|
||||
import { SearchBarLayout, SearchPeriod } from '../common/SearchBar';
|
||||
import { useState } from 'react';
|
||||
|
||||
const ReportListSearchBar = ({ handleSearch, setResultData }) => {
|
||||
let d = new Date();
|
||||
const START_DATE = new Date(new Date(d.setDate(d.getDate() - 1)).setHours(0, 0, 0, 0));
|
||||
const END_DATE = new Date();
|
||||
|
||||
const reportType = [
|
||||
{ value: 'ALL', name: '전체' },
|
||||
{ value: 'UNMANNERED_ACT', name: '비매너 행위' },
|
||||
{ value: 'USE_UNHEALTHY_NAMES', name: '불건전 이름 사용' },
|
||||
{ value: 'CASH_TRADING', name: '현금거래 행위' },
|
||||
{ value: 'INTERFERENCE_GAME', name: '게임 진행 방해' },
|
||||
{ value: 'INTERFERENCE_SERVICE', name: '운영서비스 방해' },
|
||||
{ value: 'ACCOUNT_EXPLOITATION', name: '계정도용' },
|
||||
{ value: 'BUG_ABUSING', name: '버그/어뷰징' },
|
||||
{ value: 'USE_HACK', name: '불법프로그램 사용' },
|
||||
{ value: 'LEAK_PERSONAL_INFO', name: '개인정보 유출' },
|
||||
{ value: 'PRETENDING_GM', name: '운영자 사칭' },
|
||||
];
|
||||
|
||||
const reportState = [
|
||||
{ value: 'ALL', name: '전체' },
|
||||
{ value: 'RESOLVED', name: '해결' },
|
||||
{ value: 'UNRESOLVED', name: '미해결' },
|
||||
];
|
||||
|
||||
const searchType = [
|
||||
{ value: 'ALL', name: '전체' },
|
||||
{ value: 'GUID', name: '신고자' },
|
||||
{ value: 'EMAIL', name: '담당자' },
|
||||
];
|
||||
|
||||
const [searchData, setSearchData] = useState({
|
||||
startDate: START_DATE,
|
||||
endDate: END_DATE,
|
||||
reportType: 'ALL',
|
||||
status: 'ALL',
|
||||
searchType: 'ALL',
|
||||
searchKey: '',
|
||||
});
|
||||
|
||||
// console.log(searchData);
|
||||
|
||||
const handleSubmit = event => {
|
||||
event.preventDefault();
|
||||
|
||||
handleSearch(
|
||||
searchData.startDate ? new Date(searchData.startDate).toISOString().split('.')[0] : new Date(START_DATE).toISOString().split('.')[0],
|
||||
searchData.endDate ? new Date(searchData.endDate).toISOString().split('.')[0] : new Date(END_DATE).toISOString().split('.')[0],
|
||||
searchData.reportType ? searchData.reportType : 'ALL',
|
||||
searchData.status ? searchData.status : 'ALL',
|
||||
searchData.searchType ? searchData.searchType : 'ALL',
|
||||
searchData.searchKey ? searchData.searchKey : '',
|
||||
);
|
||||
setResultData(searchData);
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setSearchData({
|
||||
startDate: START_DATE,
|
||||
endDate: END_DATE,
|
||||
reportType: 'ALL',
|
||||
status: 'ALL',
|
||||
searchKey: '',
|
||||
order: 'DESC',
|
||||
});
|
||||
handleSearch(START_DATE, END_DATE, 'ALL', 'ALL', 'ALL', '');
|
||||
setResultData(START_DATE, END_DATE, 'ALL', 'ALL', 'ALL', '');
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
const searchList = [
|
||||
<>
|
||||
<InputLabel>신고 일자</InputLabel>
|
||||
<SearchPeriod
|
||||
startDate={searchData.startDate}
|
||||
handleStartDate={data => {
|
||||
setSearchData({ ...searchData, startDate: data });
|
||||
}}
|
||||
endDate={searchData.endDate}
|
||||
handleEndDate={data => setSearchData({ ...searchData, endDate: data })}
|
||||
maxDate={new Date()}
|
||||
/>
|
||||
</>,
|
||||
];
|
||||
|
||||
const optionList = [
|
||||
<>
|
||||
<InputLabel>신고 유형</InputLabel>
|
||||
<SelectInput value={searchData.reportType || ''} onChange={e => setSearchData({ ...searchData, reportType: e.target.value })}>
|
||||
{reportType.map((data, index) => (
|
||||
<option key={index} value={data.value || ''}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>상태</InputLabel>
|
||||
<SelectInput value={searchData.status || ''} onChange={e => setSearchData({ ...searchData, status: e.target.value })}>
|
||||
{reportState.map((data, index) => (
|
||||
<option key={index} value={data.value || ''}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>신고자 / 담당자</InputLabel>
|
||||
<InputGroup>
|
||||
<SelectInput value={searchData.searchType || ''} onChange={e => setSearchData({ ...searchData, searchType: e.target.value })}>
|
||||
{searchType.map((data, index) => (
|
||||
<option key={index} value={data.value || ''}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<TextInput placeholder="입력" onChange={e => setSearchData({ ...searchData, searchKey: e.target.value })} />
|
||||
</InputGroup>
|
||||
</>,
|
||||
<>
|
||||
<BtnWrapper $gap="8px">
|
||||
<Button theme="reset" handleClick={handleReset} type="button" />
|
||||
<Button theme="search" text="검색" handleClick={handleSubmit} type="submit" />
|
||||
</BtnWrapper>
|
||||
</>,
|
||||
];
|
||||
return <SearchBarLayout firstColumnData={searchList} secondColumnData={optionList} direction={'column'} />;
|
||||
};
|
||||
|
||||
export default ReportListSearchBar;
|
||||
|
||||
const InputGroup = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
`;
|
||||
293
src/components/ServiceManage/ReportListSummary.js
Normal file
293
src/components/ServiceManage/ReportListSummary.js
Normal file
@@ -0,0 +1,293 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import DatePickerComponent from '../common/Date/DatePickerComponent';
|
||||
import styled from 'styled-components';
|
||||
import TitleArrow from '../../assets/img/icon/icon-title.png';
|
||||
import Button from '../../components/common/button/Button';
|
||||
|
||||
import { SelectInput, InputLabel, DatePickerWrapper, FormWrapper } from '../../styles/Components';
|
||||
import { ReportTotalView } from '../../apis/Report';
|
||||
import { HourList, MinuteList } from '../../assets/data';
|
||||
|
||||
const ReportListSummary = () => {
|
||||
let d = new Date();
|
||||
const START_DATE = new Date(new Date(d.setDate(d.getDate() - 1)).setHours(0, 0, 0, 0));
|
||||
const END_DATE = new Date();
|
||||
|
||||
const token = sessionStorage.getItem('token');
|
||||
|
||||
const [summaryState, setSummaryState] = useState('active');
|
||||
const [dataList, setDataList] = useState({});
|
||||
|
||||
const [sendHour, setSendHour] = useState('00');
|
||||
const [sendMin, setSendMin] = useState('00');
|
||||
|
||||
const [endHour, setEndHour] = useState('00');
|
||||
const [endMin, setEndMin] = useState('00');
|
||||
|
||||
const [resultData, setResultData] = useState({
|
||||
send_dt: START_DATE,
|
||||
end_dt: END_DATE,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
fetchData(START_DATE, END_DATE);
|
||||
}, []);
|
||||
|
||||
const fetchData = async (startDate, endDate) => {
|
||||
setDataList(await ReportTotalView(token, startDate && new Date(startDate).toISOString().split('.')[0], endDate && new Date(endDate).toISOString().split('.')[0]));
|
||||
};
|
||||
|
||||
const handleSummary = () => {
|
||||
if (summaryState === 'active') {
|
||||
setSummaryState('inactive');
|
||||
} else {
|
||||
setSummaryState('active');
|
||||
}
|
||||
};
|
||||
|
||||
// 발송 시간 세팅 로직
|
||||
const handleSendTime = e => {
|
||||
let sendDate = '';
|
||||
|
||||
if (resultData.send_dt.length === 0 || typeof resultData.send_dt.length != 'undefined') {
|
||||
sendDate = new Date();
|
||||
} else {
|
||||
sendDate = new Date(resultData.send_dt);
|
||||
}
|
||||
|
||||
if (e.target.id === 'hour') setSendHour(e.target.value);
|
||||
else if (e.target.id === 'min') setSendMin(e.target.value);
|
||||
|
||||
const result = new Date(sendDate.getFullYear(), sendDate.getMonth(), sendDate.getDate(), e.target.id === 'hour' ? e.target.value : sendHour, e.target.id === 'min' ? e.target.value : sendMin);
|
||||
|
||||
setResultData({ ...resultData, send_dt: result });
|
||||
};
|
||||
|
||||
// 발송 날짜 세팅 로직
|
||||
const handleSelectedDate = data => {
|
||||
const sendDate = new Date(data);
|
||||
|
||||
const resultSendData = new Date(sendDate.getFullYear(), sendDate.getMonth(), sendDate.getDate(), sendHour, sendMin);
|
||||
|
||||
setResultData({ ...resultData, send_dt: resultSendData });
|
||||
};
|
||||
|
||||
const handleEndTime = e => {
|
||||
let endDate = '';
|
||||
|
||||
if (resultData.end_dt.length === 0 || typeof resultData.end_dt.length != 'undefined') {
|
||||
endDate = new Date();
|
||||
} else {
|
||||
endDate = new Date(resultData.end_dt);
|
||||
}
|
||||
|
||||
if (e.target.id === 'hour') setEndHour(e.target.value);
|
||||
else if (e.target.id === 'min') setEndMin(e.target.value);
|
||||
|
||||
const result = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate(), e.target.id === 'hour' ? e.target.value : endHour, e.target.id === 'min' ? e.target.value : endMin);
|
||||
|
||||
setResultData({ ...resultData, end_dt: result });
|
||||
};
|
||||
|
||||
// 발송 날짜 세팅 로직
|
||||
const handleEndDate = data => {
|
||||
const endDate = new Date(data);
|
||||
|
||||
const resultSendData = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate(), endHour, endMin);
|
||||
|
||||
setResultData({ ...resultData, end_dt: resultSendData });
|
||||
};
|
||||
|
||||
return (
|
||||
<ReportSummaryContainer>
|
||||
<SummaryTitle onClick={handleSummary} $state={summaryState}>
|
||||
Summary
|
||||
</SummaryTitle>
|
||||
<SummaryInfo $state={summaryState}>
|
||||
<ReportBarWrapper>
|
||||
<StateBar $percent={dataList && dataList.rate} />
|
||||
</ReportBarWrapper>
|
||||
<ReportInfo>
|
||||
<ReportItem>
|
||||
<InfoTitle>총 신고건수</InfoTitle>
|
||||
<InfoCount>{dataList && dataList.totalCnt}</InfoCount>
|
||||
</ReportItem>
|
||||
<ReportItem>
|
||||
<InfoTitle>해결</InfoTitle>
|
||||
<InfoCount>{dataList && dataList.resolve}</InfoCount>
|
||||
</ReportItem>
|
||||
<ReportItem>
|
||||
<InfoTitle>미해결</InfoTitle>
|
||||
<InfoCount $color={dataList && dataList.unresolve > 0 && '#d60000'}>{dataList && dataList.unresolve}</InfoCount>
|
||||
</ReportItem>
|
||||
<ReportItem>
|
||||
<InfoTitle>완료율</InfoTitle>
|
||||
<InfoCount>{dataList && dataList.rate}</InfoCount>
|
||||
</ReportItem>
|
||||
</ReportInfo>
|
||||
|
||||
<FormWrapper>
|
||||
<ReportSetting>
|
||||
<InputLabel>
|
||||
집계 기간<span>(* 10분 단위 집계)</span>
|
||||
</InputLabel>
|
||||
<DatePickerWrapper>
|
||||
<InputGroup>
|
||||
<DatePickerWrapper>
|
||||
<DatePickerComponent
|
||||
name="시작 일자"
|
||||
selectedDate={resultData.send_dt}
|
||||
handleSelectedDate={data => handleSelectedDate(data)} />
|
||||
</DatePickerWrapper>
|
||||
<SelectInput onChange={e => handleSendTime(e)} id="hour">
|
||||
{HourList.map(hour => (
|
||||
<option value={hour} key={hour}>
|
||||
{hour}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<SelectInput onChange={e => handleSendTime(e)} id="min">
|
||||
{MinuteList.map(min => (
|
||||
<option value={min} key={min}>
|
||||
{min}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</InputGroup>
|
||||
<span>~</span>
|
||||
<InputGroup>
|
||||
<DatePickerWrapper>
|
||||
<DatePickerComponent
|
||||
name="종료 일자"
|
||||
selectedDate={resultData.end_dt}
|
||||
handleSelectedDate={data => handleEndDate(data)}
|
||||
pastDate = {resultData.send_dt}
|
||||
maxDate={new Date()} />
|
||||
</DatePickerWrapper>
|
||||
<SelectInput onChange={e => handleEndTime(e)} id="hour">
|
||||
{HourList.map(hour => (
|
||||
<option value={hour} key={hour}>
|
||||
{hour}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<SelectInput onChange={e => handleEndTime(e)} id="min">
|
||||
{MinuteList.map(min => (
|
||||
<option value={min} key={min}>
|
||||
{min}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</InputGroup>
|
||||
</DatePickerWrapper>
|
||||
<Button
|
||||
theme="search"
|
||||
text="집계"
|
||||
handleClick={e => {
|
||||
e.preventDefault();
|
||||
fetchData(resultData.send_dt, resultData.end_dt);
|
||||
}}
|
||||
/>
|
||||
</ReportSetting>
|
||||
</FormWrapper>
|
||||
</SummaryInfo>
|
||||
</ReportSummaryContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReportListSummary;
|
||||
|
||||
const InputGroup = styled.div`
|
||||
display: flex;
|
||||
${SelectInput} {
|
||||
margin-left: 5px;
|
||||
}
|
||||
`;
|
||||
|
||||
const ReportSummaryContainer = styled.div`
|
||||
background: #f6f6f6;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 20px;
|
||||
`;
|
||||
|
||||
const SummaryTitle = styled.div`
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
line-height: 52px;
|
||||
padding: 0 10px;
|
||||
cursor: pointer;
|
||||
&:after {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 11px;
|
||||
height: 52px;
|
||||
margin-left: 10px;
|
||||
background: url(${TitleArrow}) 50% 50% no-repeat;
|
||||
position: absolute;
|
||||
transform: ${props => (props.$state === 'active' ? 'rotate(0)' : 'rotate(180deg)')};
|
||||
}
|
||||
`;
|
||||
const SummaryInfo = styled.div`
|
||||
display: ${props => (props.$state === 'active' ? 'flex' : 'none')};
|
||||
border-top: 1px solid #d9d9d9;
|
||||
flex-flow: column;
|
||||
gap: 25px;
|
||||
padding: 30px 20px;
|
||||
|
||||
${SelectInput} {
|
||||
height: 35px;
|
||||
}
|
||||
${DatePickerWrapper} {
|
||||
justify-content: center;
|
||||
}
|
||||
`;
|
||||
const ReportBarWrapper = styled.div`
|
||||
width: 90%;
|
||||
display: block;
|
||||
margin: auto;
|
||||
height: 10px;
|
||||
overflow: hidden;
|
||||
background: #cccccc;
|
||||
border-radius: 10px;
|
||||
`;
|
||||
|
||||
const StateBar = styled.div`
|
||||
height: 10px;
|
||||
width: ${props => props.$percent};
|
||||
background: #666;
|
||||
border-radius: 10px;
|
||||
`;
|
||||
|
||||
const ReportInfo = styled.div`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 10px 40px;
|
||||
`;
|
||||
const ReportItem = styled.div`
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
font-size: 20px;
|
||||
`;
|
||||
|
||||
const InfoTitle = styled.div``;
|
||||
|
||||
const InfoCount = styled.div`
|
||||
font-weight: 600;
|
||||
color: ${props => props.$color};
|
||||
`;
|
||||
|
||||
const ReportSetting = styled.div`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
${InputLabel} {
|
||||
span {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
`;
|
||||
206
src/components/ServiceManage/UserBlockDetailModal.js
Normal file
206
src/components/ServiceManage/UserBlockDetailModal.js
Normal file
@@ -0,0 +1,206 @@
|
||||
import { Fragment, useState } from 'react';
|
||||
import { styled } from 'styled-components';
|
||||
|
||||
import { Title, TableStyle, BtnWrapper, TextInput } from '../../styles/Components';
|
||||
import Modal from '../../components/common/modal/Modal';
|
||||
import Button from '../common/button/Button';
|
||||
import { convertKTC } from '../../utils';
|
||||
|
||||
const UserBlockDetailModal = ({ stateModal, handleModal, data }) => {
|
||||
const history = data.history;
|
||||
|
||||
const type = [
|
||||
{ value: 'ACCESS_RESTRICTIONS', name: '접근 제한' },
|
||||
{ value: 'CHATTING_RESTRICTIONS', name: '채팅 제한' },
|
||||
];
|
||||
|
||||
const sanctions = [
|
||||
{ value: 'BAD_BEHAVIOR', name: '비매너 행위' },
|
||||
{ value: 'INAPPROPRIATE_NAME', name: '불건전 이름 사용' },
|
||||
{ value: 'CASH_TRANSACTION', name: '현금거래 행위' },
|
||||
{ value: 'GAME_INTERFERENCE', name: '게임 진행 방해' },
|
||||
{ value: 'SERVICE_INTERFERENCE', name: '운영서비스 방해' },
|
||||
{ value: 'ACCOUNT_IMPERSONATION', name: '계정도용' },
|
||||
{ value: 'BUG_ABUSE', name: '버그/어뷰징' },
|
||||
{ value: 'ILLEGAL_PROGRAM', name: '불법프로그램 사용' },
|
||||
{ value: 'PERSONAL_INFO_LEAK', name: '개인정보 유출' },
|
||||
{ value: 'ADMIN_IMPERSONATION', name: '운영자 사칭' },
|
||||
];
|
||||
|
||||
const period = [
|
||||
{ value: 'WARNING', name: '경고' },
|
||||
{ value: 'D1', name: '1일' },
|
||||
{ value: 'D3', name: '3일' },
|
||||
{ value: 'D7', name: '7일' },
|
||||
{ value: 'D15', name: '15일' },
|
||||
{ value: 'D30', name: '30일' },
|
||||
{ value: 'PERMANENT', name: '영구정지' },
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal min="960px" $view={stateModal}>
|
||||
<Title $align="center">이용자 제재 상세 정보</Title>
|
||||
<RegistInfo>
|
||||
<span>등록자 : {data.create_by}</span>
|
||||
<span>등록일 : {convertKTC(data.create_dt, false)}</span>
|
||||
</RegistInfo>
|
||||
<BlockDetailState>
|
||||
<table>
|
||||
<caption></caption>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th width="100px">유저 GUID</th>
|
||||
<td>
|
||||
<TextInput value={data && data.guid || ''} disabled />
|
||||
</td>
|
||||
<th width="100px">아바타명</th>
|
||||
<td>
|
||||
<TextInput value={data && data.nickname || ''} disabled />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>제재 방식</th>
|
||||
<td>
|
||||
<TextInput value={type.map(item => item.value === data.type && item.name).filter(data => data !== false) || ''} disabled />
|
||||
</td>
|
||||
<th></th>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>제재 기간</th>
|
||||
<td colSpan="3">
|
||||
<InputWrapper>
|
||||
<TextInput value={period.map(item => item.value === data.period && item.name).filter(data => data !== false) || ''} disabled />
|
||||
<span>시작일 : {convertKTC(data.start_dt, false)}</span>
|
||||
<span>종료일 : {convertKTC(data.end_dt, false)}</span>
|
||||
</InputWrapper>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>제재 사유</th>
|
||||
<td>
|
||||
<TextInput value={sanctions.map(item => item.value === data.sanctions && item.name).filter(data => data !== false) || ''} disabled />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</BlockDetailState>
|
||||
|
||||
<BlockCount>전체 제재 이력({history && history.length}건)</BlockCount>
|
||||
<BlockHistory>
|
||||
<TableStyle>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>일자</th>
|
||||
<th width="10%">제재 기간</th>
|
||||
<th width="10%">제재 방식</th>
|
||||
<th width="20%">제재 사유</th>
|
||||
<th width="20%">등록자</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{history &&
|
||||
history.map((content, index) => (
|
||||
<Fragment key={index}>
|
||||
<tr>
|
||||
<td>
|
||||
{convertKTC(content.start_dt, false)} ~
|
||||
{convertKTC(content.end_dt, false)}
|
||||
</td>
|
||||
<td>{period.map(item => item.value === data.period && item.name)}</td>
|
||||
<td>{type.map(item => item.value === data.type && item.name)}</td>
|
||||
<td>{sanctions.map(item => item.value === data.sanctions && item.name)}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</Fragment>
|
||||
))}
|
||||
</tbody>
|
||||
</TableStyle>
|
||||
</BlockHistory>
|
||||
<BlockNotice>※ 삭제된 제재 이력은 표시되지 않습니다.</BlockNotice>
|
||||
<BtnWrapper $justify="flex-end">
|
||||
<Button text="확인" theme="line" handleClick={handleModal} />
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserBlockDetailModal;
|
||||
|
||||
const BlockDetailState = styled.div`
|
||||
border-top: 1px solid #000;
|
||||
border-bottom: 1px solid #000;
|
||||
padding: 15px 10px;
|
||||
font-size: 14px;
|
||||
margin-bottom: 20px;
|
||||
input {
|
||||
height: 35px;
|
||||
max-width: 330px;
|
||||
font-size: 14px;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
table {
|
||||
tr {
|
||||
th,
|
||||
td {
|
||||
padding: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const RegistInfo = styled.div`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 50px;
|
||||
font-size: 14px;
|
||||
margin-bottom: 10px;
|
||||
`;
|
||||
|
||||
const BlockCount = styled.div`
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
line-height: 1.6;
|
||||
padding-bottom: 5px;
|
||||
`;
|
||||
|
||||
const BlockHistory = styled.div`
|
||||
max-height: 324px;
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
border-top: 1px solid #000;
|
||||
${TableStyle} {
|
||||
border-collapse: separate;
|
||||
&:before {
|
||||
display: none;
|
||||
}
|
||||
th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
tbody {
|
||||
tr:first-child {
|
||||
color: #d60000;
|
||||
}
|
||||
tr:last-child td {
|
||||
border-bottom: 1px solid #e8eaec;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const BlockNotice = styled.span`
|
||||
font-size: 12px;
|
||||
color: #686868;
|
||||
padding-left: 10px;
|
||||
font-weight: 300;
|
||||
line-height: 2;
|
||||
`;
|
||||
|
||||
const InputWrapper = styled.div`
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
align-items: center;
|
||||
`;
|
||||
117
src/components/ServiceManage/UserBlockSearchBar.js
Normal file
117
src/components/ServiceManage/UserBlockSearchBar.js
Normal file
@@ -0,0 +1,117 @@
|
||||
import { useState } from 'react';
|
||||
import { TextInput, BtnWrapper, InputLabel, SelectInput, InputGroup } from '../../styles/Components';
|
||||
import Button from '../common/button/Button';
|
||||
import { SearchBarLayout } from '../common/SearchBar';
|
||||
import { blockPeriod, blockSanctions, blockSearchType, blockStatus } from '../../assets/data';
|
||||
import { userSearchType } from '../../assets/data/options';
|
||||
|
||||
const UserBlockSearchBar = ({ handleSearch, setResultData }) => {
|
||||
const [searchData, setSearchData] = useState({
|
||||
searchType: 'GUID',
|
||||
data: '',
|
||||
email: '',
|
||||
status: 'ALL',
|
||||
sanctions: 'ALL',
|
||||
period: 'ALL',
|
||||
});
|
||||
|
||||
const handleSubmit = event => {
|
||||
event.preventDefault();
|
||||
|
||||
handleSearch(
|
||||
searchData.searchType ? searchData.searchType : 'GUID',
|
||||
searchData.data,
|
||||
searchData.email,
|
||||
searchData.status ? searchData.status : 'ALL',
|
||||
searchData.sanctions ? searchData.sanctions : 'ALL',
|
||||
searchData.period ? searchData.period : 'ALL',
|
||||
);
|
||||
|
||||
setResultData(searchData);
|
||||
};
|
||||
|
||||
// 초기화 버튼
|
||||
const handleReset = () => {
|
||||
setSearchData({
|
||||
searchType: 'GUID',
|
||||
data: '',
|
||||
email: '',
|
||||
status: 'ALL',
|
||||
sanctions: 'ALL',
|
||||
period: 'ALL',
|
||||
});
|
||||
handleSearch('GUID', '', '', 'ALL', 'ALL', 'ALL');
|
||||
setResultData('GUID', '', '', 'ALL', 'ALL', 'ALL');
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
// console.log(searchData);
|
||||
|
||||
const searchList = [
|
||||
<>
|
||||
<InputLabel>대상</InputLabel>
|
||||
<InputGroup>
|
||||
<SelectInput value={searchData.searchType} onChange={e => setSearchData({ ...searchData, searchType: e.target.value })}>
|
||||
{userSearchType.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<TextInput
|
||||
type="text"
|
||||
placeholder={searchData.searchType === 'GUID' ? 'GUID 입력' : '닉네임 입력'}
|
||||
value={searchData.data}
|
||||
width="600px"
|
||||
onChange={e => setSearchData({ ...searchData, data: e.target.value })}
|
||||
/>
|
||||
</InputGroup>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>등록자</InputLabel>
|
||||
<TextInput type="text" placeholder="이메일 입력" width="600px" value={searchData.email} onChange={e => setSearchData({ ...searchData, email: e.target.value })} />
|
||||
</>,
|
||||
];
|
||||
|
||||
const optionList = [
|
||||
<>
|
||||
<InputLabel>상태</InputLabel>
|
||||
<SelectInput value={searchData.status} onChange={e => setSearchData({ ...searchData, status: e.target.value })}>
|
||||
{blockStatus.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>제재 사유</InputLabel>
|
||||
<SelectInput value={searchData.sanctions} onChange={e => setSearchData({ ...searchData, sanctions: e.target.value })}>
|
||||
{blockSanctions.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>제재 기간</InputLabel>
|
||||
<SelectInput value={searchData.period} onChange={e => setSearchData({ ...searchData, period: e.target.value })}>
|
||||
{blockPeriod.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<>
|
||||
<BtnWrapper $gap="8px">
|
||||
<Button theme="reset" handleClick={handleReset} />
|
||||
<Button theme="search" text="검색" type="submit" handleClick={handleSubmit} />
|
||||
</BtnWrapper>
|
||||
</>,
|
||||
];
|
||||
return <SearchBarLayout firstColumnData={searchList} secondColumnData={optionList} direction={'column'} />;
|
||||
};
|
||||
|
||||
export default UserBlockSearchBar;
|
||||
191
src/components/ServiceManage/UserBlockUploadBtn.js
Normal file
191
src/components/ServiceManage/UserBlockUploadBtn.js
Normal file
@@ -0,0 +1,191 @@
|
||||
import styled, { css } from 'styled-components';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useState, useEffect, Fragment } from 'react';
|
||||
import Button from '../common/button/Button';
|
||||
|
||||
import Modal from '../common/modal/Modal';
|
||||
import { Title, BtnWrapper, InputGroup, TableStyle, AlertText } from '../../styles/Components';
|
||||
|
||||
import { BlackListExcelDown, BlackListMultipleUpload } from '../../apis';
|
||||
|
||||
const UserBlockUploadBtn = ({ disabled, setGuidList, guidList, typeError, setTypeError }) => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
|
||||
// onchange states
|
||||
const [file, setFile] = useState(null);
|
||||
const [previewModal, setPreviewModal] = useState('hidden');
|
||||
|
||||
const handlePreview = e => {
|
||||
e.preventDefault();
|
||||
|
||||
if (previewModal === 'hidden') {
|
||||
setPreviewModal('view');
|
||||
} else if (previewModal === 'view') {
|
||||
setPreviewModal('hidden');
|
||||
}
|
||||
};
|
||||
|
||||
// onchange event
|
||||
const handleFile = async e => {
|
||||
let fileTypes = ['application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'text/csv'];
|
||||
|
||||
setFile(e.target.files[0]);
|
||||
setGuidList(await BlackListMultipleUpload(token, e.target.files[0]));
|
||||
};
|
||||
|
||||
const handleFileDelete = e => {
|
||||
e.preventDefault();
|
||||
|
||||
document.querySelector('#fileinput').value = '';
|
||||
setGuidList([]);
|
||||
setTypeError(true);
|
||||
setFile(null);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof guidList === 'undefined') {
|
||||
setTypeError(true);
|
||||
} else if (typeof guidList !== 'undefined') {
|
||||
if (guidList.length > 0) {
|
||||
setTypeError(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const handleDownloadExcel = async () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
await BlackListExcelDown(token, 'block_sample.xlsx');
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* form */}
|
||||
<div className="form-group custom-form">
|
||||
<FileWrapper>
|
||||
<Label>제재 대상(복수)</Label>
|
||||
<InputGroup>
|
||||
<FileInput type="file" width="300px" required onChange={handleFile} id="fileinput" disabled={disabled} />
|
||||
{file ? <FileButton onClick={handleFileDelete}>파일 삭제</FileButton> : <FileButton htmlFor="fileinput" disabled={disabled}>엑셀 업로드</FileButton>}
|
||||
{typeError === false ? (
|
||||
<Button text="미리보기" theme="line" handleClick={e => handlePreview(e)} />
|
||||
) : (
|
||||
typeof guidList === 'undefined' && <AlertText>유효하지 않은 파일입니다.</AlertText>
|
||||
)}
|
||||
<FileButton onClick={handleDownloadExcel} disabled={disabled}>샘플 다운로드</FileButton>
|
||||
</InputGroup>
|
||||
</FileWrapper>
|
||||
</div>
|
||||
<Modal $bgcolor="transparent" min="300px" $view={previewModal}>
|
||||
<Title $align="center">업로드 미리보기</Title>
|
||||
<Count>총 제재 인원 : 45</Count>
|
||||
<TableWrapper>
|
||||
<TableStyle>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="300">GUID</th>
|
||||
<th width="200">닉네임</th>
|
||||
<th width="120">유효성 체크</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{guidList &&
|
||||
guidList.map((data, idx) => (
|
||||
<Fragment key={idx}>
|
||||
<tr>
|
||||
<td>{data.guid}</td>
|
||||
<td>{data.nickname}</td>
|
||||
{data.validate === true ? <State color="#007A00">true</State> : <State color="#d60000">false</State>}
|
||||
</tr>
|
||||
</Fragment>
|
||||
))}
|
||||
</tbody>
|
||||
</TableStyle>
|
||||
</TableWrapper>
|
||||
<BtnWrapper $justify="center">
|
||||
<Button theme="line" text="확인" handleClick={e => handlePreview(e)} />
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserBlockUploadBtn;
|
||||
|
||||
const Label = styled.div`
|
||||
display: block;
|
||||
min-width: 120px;
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const FileWrapper = styled.div`
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
margin-right: 20px;
|
||||
button {
|
||||
height: 35px;
|
||||
}
|
||||
`;
|
||||
|
||||
const FileButton = styled.label`
|
||||
border-radius: 5px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: fit-content;
|
||||
font-size: 14px;
|
||||
background: #2c2c2c;
|
||||
color: #fff;
|
||||
width: 100px;
|
||||
height: 35px;
|
||||
cursor: pointer;
|
||||
|
||||
${props =>
|
||||
props.disabled &&
|
||||
css`
|
||||
background: #b8b8b8;
|
||||
cursor: not-allowed;
|
||||
`}
|
||||
`;
|
||||
|
||||
const FileInput = styled.input`
|
||||
height: 35px;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 5px;
|
||||
width: 300px;
|
||||
padding: 0 15px;
|
||||
line-height: 35px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
&::file-selector-button {
|
||||
display: none;
|
||||
}
|
||||
&:disabled {
|
||||
color: #cccccc;
|
||||
background: #f6f6f6;
|
||||
}
|
||||
`;
|
||||
|
||||
const TableWrapper = styled.div`
|
||||
margin-top: 5px;
|
||||
margin-bottom: 30px;
|
||||
max-height: 324px;
|
||||
overflow: auto;
|
||||
&::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #666666;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #d9d9d9;
|
||||
}
|
||||
`;
|
||||
|
||||
const Count = styled.div`
|
||||
font-size: 14px;
|
||||
`;
|
||||
|
||||
const State = styled.td`
|
||||
color: ${props => props.color};
|
||||
`;
|
||||
46
src/components/ServiceManage/WhiteListRegistBar.js
Normal file
46
src/components/ServiceManage/WhiteListRegistBar.js
Normal file
@@ -0,0 +1,46 @@
|
||||
import { TextInput, InputLabel, InputGroup, SearchBarAlert } from '../../styles/Components';
|
||||
import Button from '../common/button/Button';
|
||||
import { SearchBarLayout } from '../common/SearchBar';
|
||||
|
||||
import WhiteListUploadBtn from './WhiteListUploadBtn';
|
||||
|
||||
const WhiteListRegistBar = ({ handleRegistModalClose, isNullValue, resultData, setResultData }) => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
|
||||
// console.log(isNullValue)
|
||||
|
||||
const searchList = [
|
||||
<>
|
||||
<InputLabel>직접입력</InputLabel>
|
||||
<InputGroup>
|
||||
<TextInput
|
||||
type="text"
|
||||
placeholder="GUID 입력"
|
||||
width="300px"
|
||||
id="guid"
|
||||
onChange={e => {
|
||||
setResultData({ ...resultData, guid: e.target.value });
|
||||
}}
|
||||
onKeyDown={event => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Button theme={resultData.guid ? 'search' : 'gray'} text="등록" handleClick={handleRegistModalClose} />
|
||||
</InputGroup>
|
||||
</>,
|
||||
<> {isNullValue && <SearchBarAlert>필수값을 입력해주세요</SearchBarAlert>}</>,
|
||||
<>
|
||||
<WhiteListUploadBtn />
|
||||
</>,
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchBarLayout firstColumnData={searchList} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default WhiteListRegistBar;
|
||||
195
src/components/ServiceManage/WhiteListUploadBtn.js
Normal file
195
src/components/ServiceManage/WhiteListUploadBtn.js
Normal file
@@ -0,0 +1,195 @@
|
||||
import styled from 'styled-components';
|
||||
import { useState } from 'react';
|
||||
import Button from '../common/button/Button';
|
||||
|
||||
import Modal from '../common/modal/Modal';
|
||||
import { BtnWrapper, ButtonClose, ModalText, InputLabel, InputGroup, AlertText } from '../../styles/Components';
|
||||
import { WhiteListMultiRegsit, WhiteListExelUpload } from '../../apis';
|
||||
|
||||
const WhiteListUploadBtn = () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
const [doubleSubmitFlag, setDoubleSubmitFlag] = useState(false);
|
||||
|
||||
// onchange states
|
||||
const [excelFile, setExcelFile] = useState(null);
|
||||
|
||||
const [registModalClose, setRegistModalClose] = useState('hidden');
|
||||
const [completeModalClose, setCompleteModalClose] = useState('hidden');
|
||||
const [fileResponse, setFileResponse] = useState('');
|
||||
|
||||
const [alertMessage, setAlertMessage] = useState("");
|
||||
const [updateMessage, setUpdateMessage] = useState('등록이 완료되었습니다.');
|
||||
|
||||
// 등록 확인 모달
|
||||
const handleRegistModalClose = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (registModalClose === 'hidden') {
|
||||
setRegistModalClose('view');
|
||||
} else {
|
||||
setRegistModalClose('hidden');
|
||||
}
|
||||
};
|
||||
|
||||
// 등록 완료 모달
|
||||
const handleCompleteModalClose = () => {
|
||||
if (completeModalClose === 'hidden') {
|
||||
setCompleteModalClose('view');
|
||||
} else {
|
||||
setCompleteModalClose('hidden');
|
||||
window.location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
// onchange event
|
||||
const handleFile = async e => {
|
||||
setExcelFile(e.target.files[0]);
|
||||
|
||||
const message = await WhiteListExelUpload(token, e.target.files[0]);
|
||||
const uploadMessage = message.data.data.message;
|
||||
|
||||
if (uploadMessage === '파일 업로드 하였습니다.') {
|
||||
setAlertMessage('')
|
||||
} else if (fileResponse === 'Maximum upload size exceeded' || fileResponse === 'guid를 확인해주세요.' || uploadMessage === 'Excel 파일을 선택해주세요.') {
|
||||
setAlertMessage('유효하지 않은 파일입니다.')
|
||||
} else {
|
||||
setAlertMessage('중복된 유저 정보가 있습니다.');
|
||||
}
|
||||
|
||||
setFileResponse(message.data.data.message);
|
||||
};
|
||||
|
||||
// 저장된 엑셀 파일 -> API 로 전송
|
||||
const handlePostGuid = async e => {
|
||||
e.preventDefault();
|
||||
|
||||
if (fileResponse === '파일 업로드 하였습니다.') {
|
||||
await WhiteListMultiRegsit(token, excelFile);
|
||||
setUpdateMessage('등록이 완료되었습니다.');
|
||||
} else if (fileResponse === 'Maximum upload size exceeded' || fileResponse === 'guid를 확인해주세요.' ) {
|
||||
setUpdateMessage('유효하지 않은 파일입니다');
|
||||
} else {
|
||||
setUpdateMessage('파일 내 중복된 유저 정보가 있습니다. \n 파일을 다시 확인 후 이용해주세요.');
|
||||
}
|
||||
|
||||
setRegistModalClose('hidden');
|
||||
handleCompleteModalClose();
|
||||
};
|
||||
|
||||
const handleFileDelete = e => {
|
||||
e.preventDefault();
|
||||
|
||||
document.querySelector('#fileinput').value = '';
|
||||
setAlertMessage('');
|
||||
|
||||
setExcelFile(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* form */}
|
||||
<div className="form-group custom-form">
|
||||
<FileWrapper>
|
||||
<InputLabel>일괄등록</InputLabel>
|
||||
<InputGroup>
|
||||
<FileInput type="file" width="300px" required onChange={handleFile} id="fileinput" />
|
||||
{excelFile ?
|
||||
<FileButton onClick={handleFileDelete}>파일 삭제</FileButton>
|
||||
: <FileButton htmlFor="fileinput">엑셀 업로드</FileButton>}
|
||||
{excelFile &&
|
||||
<Button
|
||||
theme={alertMessage.length > 0 ? 'gray' : 'search'}
|
||||
text="등록"
|
||||
errorMessage={alertMessage.length > 0}
|
||||
handleClick={e => {
|
||||
handleRegistModalClose(e);
|
||||
}}
|
||||
/>}
|
||||
<AlertText>{alertMessage}</AlertText>
|
||||
</InputGroup>
|
||||
</FileWrapper>
|
||||
</div>
|
||||
|
||||
{/* 일괄등록 모달창 */}
|
||||
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={registModalClose}>
|
||||
<BtnWrapper $justify="flex-end">
|
||||
<ButtonClose onClick={handleRegistModalClose} />
|
||||
</BtnWrapper>
|
||||
<ModalText $align="center">
|
||||
화이트리스트 명단에
|
||||
<br />
|
||||
일괄 등록하시겠습니까?
|
||||
</ModalText>
|
||||
<BtnWrapper $gap="10px">
|
||||
<Button
|
||||
text="취소"
|
||||
theme="line"
|
||||
size="large"
|
||||
width="100%"
|
||||
handleClick={handleRegistModalClose}
|
||||
/>
|
||||
<Button
|
||||
text="확인"
|
||||
theme="primary"
|
||||
type="submit"
|
||||
size="large"
|
||||
width="100%"
|
||||
handleClick={e => {
|
||||
e.preventDefault();
|
||||
doubleSubmitFlag || handlePostGuid(e);
|
||||
setDoubleSubmitFlag(true);
|
||||
}}
|
||||
/>
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
|
||||
{/* 등록 완료 모달 */}
|
||||
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={completeModalClose}>
|
||||
<BtnWrapper $justify="flex-end">
|
||||
<ButtonClose onClick={handleCompleteModalClose} />
|
||||
</BtnWrapper>
|
||||
<ModalText $align="center">{updateMessage}</ModalText>
|
||||
<BtnWrapper $gap="10px">
|
||||
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleCompleteModalClose} />
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default WhiteListUploadBtn;
|
||||
|
||||
const FileWrapper = styled.div`
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
margin-right: 20px;
|
||||
`;
|
||||
|
||||
const FileButton = styled.label`
|
||||
border-radius: 5px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: fit-content;
|
||||
font-size: 14px;
|
||||
background: #2c2c2c;
|
||||
color: #fff;
|
||||
width: 100px;
|
||||
height: 35px;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
const FileInput = styled.input`
|
||||
height: 35px;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 5px;
|
||||
width: 300px;
|
||||
padding: 0 15px;
|
||||
line-height: 35px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
&::file-selector-button {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
35
src/components/ServiceManage/index.js
Normal file
35
src/components/ServiceManage/index.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import BoardInfoModal from './modal/BoardInfoModal';
|
||||
import BoardRegistModal from './modal/BoardRegistModal';
|
||||
import MailDetailModal from './modal/MailDetailModal';
|
||||
import MailListSearchBar from './searchBar/MailListSearchBar';
|
||||
import ReportListAnswerModal from './modal/ReportListAnswerModal';
|
||||
import ReportListDetailModal from './modal/ReportListDetailModal';
|
||||
import ReportListSearchBar from './searchBar/ReportListSearchBar';
|
||||
import UserBlockDetailModal from './modal/UserBlockDetailModal';
|
||||
import UserBlockSearchBar from './searchBar/UserBlockSearchBar';
|
||||
import WhiteListSearchBar from './WhiteListRegistBar';
|
||||
import ReportListSummary from './ReportListSummary';
|
||||
import ItemsSearchBar from './searchBar/ItemsSearchBar';
|
||||
import EventListSearchBar from './searchBar/EventListSearchBar';
|
||||
import LandAuctionSearchBar from './searchBar/LandAuctionSearchBar'
|
||||
import LandAuctionModal from './modal/LandAuctionModal'
|
||||
import BattleEventModal from './modal/BattleEventModal'
|
||||
|
||||
export {
|
||||
BoardInfoModal,
|
||||
BoardRegistModal,
|
||||
MailDetailModal,
|
||||
MailListSearchBar,
|
||||
ReportListAnswerModal,
|
||||
ReportListDetailModal,
|
||||
ReportListSearchBar,
|
||||
ReportListSummary,
|
||||
UserBlockDetailModal,
|
||||
UserBlockSearchBar,
|
||||
WhiteListSearchBar,
|
||||
ItemsSearchBar,
|
||||
EventListSearchBar,
|
||||
LandAuctionSearchBar,
|
||||
LandAuctionModal,
|
||||
BattleEventModal
|
||||
};
|
||||
459
src/components/ServiceManage/modal/BattleEventModal.js
Normal file
459
src/components/ServiceManage/modal/BattleEventModal.js
Normal file
@@ -0,0 +1,459 @@
|
||||
import React, { useState, Fragment, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Button from '../../common/button/Button';
|
||||
import Loading from '../../common/Loading';
|
||||
|
||||
import {
|
||||
Title,
|
||||
BtnWrapper,
|
||||
SearchBarAlert, SelectInput, InputLabel, DatePickerWrapper,
|
||||
} from '../../../styles/Components';
|
||||
|
||||
import {
|
||||
FormHelperText,
|
||||
FormInput,
|
||||
FormLabel,
|
||||
FormTextArea,
|
||||
FormTextAreaWrapper,
|
||||
MessageWrapper,
|
||||
FormRowGroup,
|
||||
NoticeInputRow2,
|
||||
NoticeInputItem2,
|
||||
BoxWrapper,
|
||||
FormStatusBar,
|
||||
FormStatusLabel,
|
||||
FormStatusWarning,
|
||||
FormButtonContainer,
|
||||
StyledSelectInput, TimeSeparator, TimeContainer,
|
||||
} from '../../../styles/ModuleComponents';
|
||||
import { HourList, MinuteList, modalTypes } from '../../../assets/data';
|
||||
import { DynamicModal, Modal, DateTimeRangePicker, SingleDatePicker, SingleTimePicker } from '../../common';
|
||||
import { LandAuctionModify, LandAuctionSingleRegist } from '../../../apis';
|
||||
import { NONE, TYPE_MODIFY, TYPE_REGISTRY } from '../../../assets/data/adminConstants';
|
||||
import { landAuctionStatus, landAuctionStatusType, languageType, CurrencyType } from '../../../assets/data';
|
||||
import { useModal } from '../../../utils/hook';
|
||||
import { convertKTCDate } from '../../../utils';
|
||||
import { battleEventStatus, battleRepeatType } from '../../../assets/data/options';
|
||||
import DatePickerComponent from '../../common/Date/DatePickerComponent';
|
||||
import { BattleEventModify, BattleEventSingleRegist } from '../../../apis/Battle';
|
||||
import { battleEventStatusType } from '../../../assets/data/types';
|
||||
import { result } from 'lodash';
|
||||
|
||||
const BattleEventModal = ({ modalType, detailView, handleDetailView, content, setDetailData, configData, rewardData }) => {
|
||||
const { t } = useTranslation();
|
||||
const token = sessionStorage.getItem('token');
|
||||
|
||||
const [loading, setLoading] = useState(false); // 로딩 창
|
||||
const {
|
||||
modalState,
|
||||
handleModalView,
|
||||
handleModalClose
|
||||
} = useModal({
|
||||
cancel: 'hidden',
|
||||
registConfirm: 'hidden',
|
||||
registComplete: 'hidden'
|
||||
});
|
||||
const [message_lang, setMessage_lang] = useState('KO');
|
||||
|
||||
const [isNullValue, setIsNullValue] = useState(false); // 데이터 값 체크
|
||||
const [alertMsg, setAlertMsg] = useState('');
|
||||
|
||||
const [resultData, setResultData] = useState(initData); //데이터 정보
|
||||
const [resetDateTime, setResetDateTime] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if(modalType === TYPE_MODIFY && content && Object.keys(content).length > 0){
|
||||
setResultData({
|
||||
group_id: content.group_id,
|
||||
event_name: content.event_name,
|
||||
repeat_type: content.repeat_type,
|
||||
config_id: content.config_id,
|
||||
reward_group_id: content.reward_group_id,
|
||||
round_count: content.round_count,
|
||||
hot_time: content.hot_time,
|
||||
round_time: content.round_time,
|
||||
status: content.status,
|
||||
event_start_dt: convertKTCDate(content.event_start_dt),
|
||||
event_end_dt: convertKTCDate(content.event_end_dt)
|
||||
});
|
||||
}
|
||||
}, [modalType, content]);
|
||||
|
||||
useEffect(() => {
|
||||
if (checkCondition()) {
|
||||
setIsNullValue(false);
|
||||
} else {
|
||||
setIsNullValue(true);
|
||||
}
|
||||
}, [resultData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (resetDateTime) {
|
||||
setResetDateTime(false);
|
||||
}
|
||||
}, [resetDateTime]);
|
||||
|
||||
// 시작 날짜 변경 핸들러
|
||||
const handleStartDateChange = (date) => {
|
||||
if (!date) return;
|
||||
|
||||
const newDate = new Date(date);
|
||||
|
||||
setResultData(prev => ({
|
||||
...prev,
|
||||
event_start_dt: newDate
|
||||
}));
|
||||
};
|
||||
|
||||
// 시작 시간 변경 핸들러
|
||||
const handleStartTimeChange = (time) => {
|
||||
if (!time) return;
|
||||
|
||||
const newDateTime = resultData.event_start_dt
|
||||
? new Date(resultData.event_start_dt)
|
||||
: new Date();
|
||||
|
||||
newDateTime.setHours(
|
||||
time.getHours(),
|
||||
time.getMinutes(),
|
||||
0,
|
||||
0
|
||||
);
|
||||
|
||||
setResultData(prev => ({
|
||||
...prev,
|
||||
event_start_dt: newDateTime
|
||||
}));
|
||||
};
|
||||
|
||||
// 종료 날짜 변경 핸들러
|
||||
const handleEndDateChange = (date) => {
|
||||
if (!date || !resultData.start_dt) return;
|
||||
|
||||
const startDate = new Date(resultData.start_dt);
|
||||
const endDate = new Date(date);
|
||||
|
||||
// 일자만 비교하기 위해 년/월/일만 추출
|
||||
const startDay = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate());
|
||||
const endDay = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate());
|
||||
|
||||
if (endDay <= startDay) {
|
||||
setAlertMsg('종료일은 시작일보다 하루 이후여야 합니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
const newDate = new Date(date);
|
||||
newDate.setHours(23, 59, 59, 0);
|
||||
|
||||
setResultData(prev => ({
|
||||
...prev,
|
||||
event_end_dt: newDate
|
||||
}));
|
||||
};
|
||||
|
||||
const handleConfigChange = (e) => {
|
||||
const config = configData.find(data => data.id === e.target.value);
|
||||
setResultData({ ...resultData, config_id: config.id, round_time: config.round_time });
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
setMessage_lang('KO')
|
||||
setDetailData({});
|
||||
setResultData(initData);
|
||||
setResetDateTime(true);
|
||||
handleDetailView();
|
||||
}
|
||||
|
||||
const handleSubmit = async (type, param = null) => {
|
||||
switch (type) {
|
||||
case "submit":
|
||||
if (!checkCondition()) return;
|
||||
|
||||
const minAllowedTime = new Date(new Date().getTime() + 10 * 60000);
|
||||
if (resultData.event_start_dt < minAllowedTime) {
|
||||
setAlertMsg(t('BATTLE_EVENT_MADEL_START_DT_WARNING'));
|
||||
return;
|
||||
}
|
||||
if(resultData.repeat_type !== 'NONE' && resultData.event_start_dt >= resultData.event_end_dt) {
|
||||
setAlertMsg(t('LAND_AUCTION_MADEL_AUCTION_DIFF_AUCTION'))
|
||||
return;
|
||||
}
|
||||
|
||||
//화면에 머물면서 상태는 안바꼈을 경우가 있기에 시작시간 지났을경우 차단
|
||||
if (modalType === TYPE_MODIFY && resultData.event_start_dt < new Date()) {
|
||||
setAlertMsg(t('LAND_AUCTION_MADEL_MODIFY_START'));
|
||||
return;
|
||||
}
|
||||
|
||||
if(resultData.round_time === 0){
|
||||
const config = configData.find(data => data.id === resultData.config_id);
|
||||
setResultData({ ...resultData, round_time: config.round_time });
|
||||
}
|
||||
|
||||
handleModalView('registConfirm');
|
||||
break;
|
||||
case "cancel":
|
||||
handleModalView('cancel');
|
||||
break;
|
||||
case "cancelConfirm":
|
||||
handleModalClose('cancel');
|
||||
handleReset();
|
||||
break;
|
||||
case "registConfirm":
|
||||
setLoading(true);
|
||||
|
||||
if(isView('modify')){
|
||||
await BattleEventModify(token, content?.id, resultData).then(data => {
|
||||
setLoading(false);
|
||||
handleModalClose('registConfirm');
|
||||
if(data.result === "SUCCESS") {
|
||||
handleModalView('registComplete');
|
||||
}else if(data.result === "ERROR_AUCTION_STATUS_IMPOSSIBLE"){
|
||||
setAlertMsg(t('LAND_AUCTION_ERROR_MODIFY_STATUS'));
|
||||
}else{
|
||||
setAlertMsg(t('UPDATE_FAIL'));
|
||||
}
|
||||
}).catch(reason => {
|
||||
setAlertMsg(t('API_FAIL'));
|
||||
});
|
||||
}
|
||||
else{
|
||||
await BattleEventSingleRegist(token, resultData).then(data => {
|
||||
setLoading(false);
|
||||
handleModalClose('registConfirm');
|
||||
if(data.result === "SUCCESS") {
|
||||
handleModalView('registComplete');
|
||||
}else if(data.result === "ERROR_LAND_AUCTION_IMPOSSIBLE"){
|
||||
setAlertMsg(t('LAND_AUCTION_ERROR_PROGRESS'));
|
||||
}else{
|
||||
setAlertMsg(t('REGIST_FAIL'));
|
||||
}
|
||||
}).catch(reason => {
|
||||
setAlertMsg(t('API_FAIL'));
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "registComplete":
|
||||
handleModalClose('registComplete');
|
||||
handleReset();
|
||||
window.location.reload();
|
||||
break;
|
||||
case "warning":
|
||||
setAlertMsg('');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const checkCondition = () => {
|
||||
return (
|
||||
resultData.event_start_dt !== ''
|
||||
&& resultData.group_id !== ''
|
||||
&& resultData.event_name !== ''
|
||||
&& (resultData.repeat_type === 'NONE' || (resultData.repeat_type !== 'NONE' && resultData.event_end_dt !== ''))
|
||||
);
|
||||
};
|
||||
|
||||
const isView = (label) => {
|
||||
switch (label) {
|
||||
case "modify":
|
||||
return modalType === TYPE_MODIFY && (content?.status === battleEventStatusType.register);
|
||||
case "registry":
|
||||
case "repeat":
|
||||
case "group":
|
||||
case "name":
|
||||
case "config":
|
||||
case "reward":
|
||||
case "round":
|
||||
case "hot":
|
||||
case "start_dt":
|
||||
return modalType === TYPE_REGISTRY
|
||||
default:
|
||||
return modalType === TYPE_MODIFY && (content?.status === battleEventStatusType.register
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal min="760px" $view={detailView}>
|
||||
<Title $align="center">{isView('registry') ? "전투시스템 이벤트 등록" : isView('modify') ? "전투시스템 이벤트 수정" : "전투시스템 이벤트 상세"}</Title>
|
||||
<MessageWrapper>
|
||||
<FormRowGroup>
|
||||
<FormLabel>그룹 ID</FormLabel>
|
||||
<FormInput
|
||||
type="text"
|
||||
disabled={!isView('group')}
|
||||
width='150px'
|
||||
value={resultData?.group_id}
|
||||
onChange={e => setResultData({ ...resultData, group_id: e.target.value })}
|
||||
/>
|
||||
<FormLabel>이벤트명</FormLabel>
|
||||
<FormInput
|
||||
type="text"
|
||||
disabled={!isView('name')}
|
||||
width='300px'
|
||||
value={resultData?.event_name}
|
||||
onChange={e => setResultData({ ...resultData, event_name: e.target.value })}
|
||||
/>
|
||||
</FormRowGroup>
|
||||
<FormRowGroup>
|
||||
<SingleDatePicker
|
||||
label="시작일자"
|
||||
disabled={!isView('start_dt')}
|
||||
dateLabel="시작 일자"
|
||||
onDateChange={handleStartDateChange}
|
||||
selectedDate={resultData?.event_start_dt}
|
||||
/>
|
||||
<SingleTimePicker
|
||||
label="시작시간"
|
||||
disabled={!isView('start_dt')}
|
||||
selectedTime={resultData?.event_start_dt}
|
||||
onTimeChange={handleStartTimeChange}
|
||||
/>
|
||||
</FormRowGroup>
|
||||
<FormRowGroup>
|
||||
<FormLabel>반복</FormLabel>
|
||||
<SelectInput value={resultData?.repeat_type} onChange={e => setResultData({ ...resultData, repeat_type: e.target.value })} disabled={!isView('repeat')} width="150px">
|
||||
{battleRepeatType.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
{resultData?.repeat_type !== 'NONE' &&
|
||||
<SingleDatePicker
|
||||
label="종료일자"
|
||||
disabled={false}
|
||||
dateLabel="종료 일자"
|
||||
onDateChange={handleEndDateChange}
|
||||
selectedDate={resultData?.event_end_dt}
|
||||
/>
|
||||
}
|
||||
</FormRowGroup>
|
||||
<FormRowGroup>
|
||||
<FormLabel>라운드 시간</FormLabel>
|
||||
<SelectInput value={resultData.config_id} onChange={handleConfigChange} disabled={!isView('config')} width="200px">
|
||||
{configData && configData?.map((data, index) => (
|
||||
<option key={index} value={data.id}>
|
||||
{data.desc}({data.id})
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<FormLabel>라운드 수</FormLabel>
|
||||
<SelectInput value={resultData.round_count} onChange={e => setResultData({ ...resultData, round_count: e.target.value })} disabled={!isView('round')} width="100px">
|
||||
{[1,2,3,4,8,12,16].map((data, index) => (
|
||||
<option key={index} value={data}>
|
||||
{data}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</FormRowGroup>
|
||||
<FormRowGroup>
|
||||
<FormLabel>배정 포드</FormLabel>
|
||||
<SelectInput value={resultData.reward_group_id} onChange={e => setResultData({ ...resultData, reward_group_id: e.target.value })} disabled={!isView('reward')} width="200px">
|
||||
{rewardData && rewardData?.map((data, index) => (
|
||||
<option key={index} value={data.group_id}>
|
||||
{data.desc}({data.group_id})
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<FormLabel>핫타임</FormLabel>
|
||||
<SelectInput value={resultData.hot_time} onChange={e => setResultData({ ...resultData, hot_time: e.target.value })} disabled={!isView('hot')} width="100px">
|
||||
{[1,2,3,4,5,6,7,8].map((data, index) => (
|
||||
<option key={index} value={data}>
|
||||
{data}배
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</FormRowGroup>
|
||||
|
||||
{!isView() && isNullValue && <SearchBarAlert $marginTop="25px" $align="right">{t('REQUIRED_VALUE_CHECK')}</SearchBarAlert>}
|
||||
</MessageWrapper>
|
||||
|
||||
<BtnWrapper $gap="10px" $marginTop="10px">
|
||||
<FormStatusBar>
|
||||
<FormStatusLabel>
|
||||
현재상태: {battleEventStatus.find(data => data.value === content?.status)?.name || "등록"}
|
||||
</FormStatusLabel>
|
||||
<FormStatusWarning>
|
||||
{isView('registry') ? '' : t('LAND_AUCTION_MODAL_STATUS_WARNING')}
|
||||
</FormStatusWarning>
|
||||
</FormStatusBar>
|
||||
<FormButtonContainer $gap="5px">
|
||||
{isView() ?
|
||||
<Button
|
||||
text="확인"
|
||||
name="확인버튼"
|
||||
theme="line"
|
||||
handleClick={() => handleReset()}
|
||||
/>
|
||||
:
|
||||
<>
|
||||
<Button text="취소" theme="line" handleClick={() => handleSubmit('cancel')} />
|
||||
<Button
|
||||
type="submit"
|
||||
text={isView('modify') ? "수정" : "등록"}
|
||||
name="등록버튼"
|
||||
theme={
|
||||
checkCondition()
|
||||
? 'primary'
|
||||
: 'disable'
|
||||
}
|
||||
handleClick={() => handleSubmit('submit')}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
</FormButtonContainer>
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
|
||||
{/* 확인 모달 */}
|
||||
<DynamicModal
|
||||
modalType={modalTypes.confirmOkCancel}
|
||||
view={modalState.registConfirmModal}
|
||||
modalText={isView('modify') ? t('LAND_UPDATE_CONFIRM') : t('LAND_REGIST_CONFIRM')}
|
||||
handleSubmit={() => handleSubmit('registConfirm')}
|
||||
handleCancel={() => handleModalClose('registConfirm')}
|
||||
/>
|
||||
{/* 완료 모달 */}
|
||||
<DynamicModal
|
||||
modalType={modalTypes.completed}
|
||||
view={modalState.registCompleteModal}
|
||||
modalText={isView('modify') ? t('UPDATE_COMPLETED') : t('REGIST_COMPLTE')}
|
||||
handleSubmit={() => handleSubmit('registComplete')}
|
||||
/>
|
||||
{/* 취소 모달 */}
|
||||
<DynamicModal
|
||||
modalType={modalTypes.confirmOkCancel}
|
||||
view={modalState.cancelModal}
|
||||
modalText={t('CANCEL_CONFIRM')}
|
||||
handleCancel={() => handleModalClose('cancel')}
|
||||
handleSubmit={() => handleSubmit('cancelConfirm')}
|
||||
/>
|
||||
{/* 경고 모달 */}
|
||||
<DynamicModal
|
||||
modalType={modalTypes.completed}
|
||||
view={alertMsg ? 'view' : 'hidden'}
|
||||
modalText={alertMsg}
|
||||
handleSubmit={() => handleSubmit('warning')}
|
||||
/>
|
||||
{loading && <Loading/>}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const initData = {
|
||||
group_id: '',
|
||||
event_name: '',
|
||||
repeat_type: 'NONE',
|
||||
config_id: 1,
|
||||
round_time: 0,
|
||||
reward_group_id: 1,
|
||||
round_count: 1,
|
||||
hot_time: 1,
|
||||
event_start_dt: '',
|
||||
event_end_dt: ''
|
||||
}
|
||||
|
||||
export default BattleEventModal;
|
||||
|
||||
1000
src/components/ServiceManage/modal/BoardInfoModal.js
Normal file
1000
src/components/ServiceManage/modal/BoardInfoModal.js
Normal file
File diff suppressed because it is too large
Load Diff
620
src/components/ServiceManage/modal/BoardRegistModal.js
Normal file
620
src/components/ServiceManage/modal/BoardRegistModal.js
Normal file
@@ -0,0 +1,620 @@
|
||||
import { styled } from 'styled-components';
|
||||
import { useState, useRef, Fragment, useEffect } from 'react';
|
||||
|
||||
import Button from '../../common/button/Button';
|
||||
import CheckBox from '../../common/input/CheckBox';
|
||||
import Modal from '../../common/modal/Modal';
|
||||
|
||||
import { Title, BtnWrapper, SelectInput, TextInput, DatePickerWrapper, InputLabel, Textarea, ModalText, SearchBarAlert } from '../../../styles/Components';
|
||||
import CloseIcon from '../../../assets/img/icon/icon-close.png';
|
||||
import DatePickerComponent from '../../common/Date/DatePickerComponent';
|
||||
import { HourList, MinuteList, modalTypes } from '../../../assets/data';
|
||||
import { NoticeRegist } from '../../../apis';
|
||||
import { convertKTC, convertKTCDate } from '../../../utils';
|
||||
import DynamicModal from '../../common/modal/DynamicModal';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
BoxWrapper, InputGroup2,
|
||||
MessageBox, MessageWrapper,
|
||||
NoticeInputGroup, NoticeInputItem, NoticeInputItem2,
|
||||
NoticeInputRow, NoticeInputRow2,
|
||||
NoticeRegistGroup,
|
||||
RegistInputItem, RepeatTime, SubText, SubTextRow, TitleLang,
|
||||
} from '../../../styles/ModuleComponents';
|
||||
import { languageType } from '../../../assets/data/options';
|
||||
|
||||
const BoardRegistModal = ({ registView, setRegistView, copyData, setIsCopyData }) => {
|
||||
const [doubleSubmitFlag, setDoubleSubmitFlag] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [sendHour, setSendHour] = useState('00');
|
||||
const [sendMin, setSendMin] = useState('00');
|
||||
const [endHour, setEndHour] = useState('00');
|
||||
const [endMin, setEndMin] = useState('00');
|
||||
const [repeatHour, setRepeatHour] = useState('00');
|
||||
const [repeatMin, setRepeatMin] = useState('00');
|
||||
|
||||
const [confirmModal, setConfirmModal] = useState('hidden');
|
||||
const [completeModal, setCompleteModal] = useState('hidden');
|
||||
const [errorModal, setErrorModal] = useState('hidden');
|
||||
|
||||
const [confirmText, setConfirmText] = useState('');
|
||||
const [completeText, setCompleteText] = useState('');
|
||||
const [isNullValue, setIsNullValue] = useState(false);
|
||||
const [btnValidation, setBtnValidation] = useState(false);
|
||||
|
||||
const [message_lang, setMessage_lang] = useState('KO');
|
||||
|
||||
const itemId = useRef(1);
|
||||
|
||||
const resetData = {
|
||||
send_dt: '',
|
||||
message_type: 'CHATTING',
|
||||
is_repeat: false,
|
||||
repeat_type: 'COUNT',
|
||||
repeat_dt: '00:00:00',
|
||||
repeat_cnt: '1',
|
||||
game_message: [
|
||||
{ language: 'KO', content: '' },
|
||||
{ language: 'EN', content: '' },
|
||||
{ language: 'JA', content: '' },
|
||||
],
|
||||
};
|
||||
|
||||
const [resultData, setResultData] = useState({
|
||||
send_dt: '',
|
||||
end_dt: '',
|
||||
message_type: 'CHATTING',
|
||||
is_repeat: false,
|
||||
repeat_type: 'COUNT',
|
||||
repeat_dt: '00:00:00',
|
||||
repeat_cnt: '1',
|
||||
game_message: [
|
||||
{ language: 'KO', content: '' },
|
||||
{ language: 'EN', content: '' },
|
||||
{ language: 'JA', content: '' },
|
||||
],
|
||||
});
|
||||
|
||||
const KOREAN_TIME = copyData && convertKTCDate(copyData.detail.send_dt);
|
||||
|
||||
// console.log(resultData);
|
||||
|
||||
useEffect(() => {
|
||||
setResultData({
|
||||
send_dt: copyData && '',
|
||||
end_dt: copyData && '',
|
||||
message_type: copyData ? copyData.detail.message_type : 'CHATTING',
|
||||
is_repeat: copyData ? copyData.detail.is_repeat : false,
|
||||
repeat_type: copyData ? copyData.detail.repeat_type : 'COUNT',
|
||||
repeat_dt: copyData ? copyData.detail.repeat_dt : '00:00:00',
|
||||
repeat_cnt: copyData ? copyData.detail.repeat_cnt : '1',
|
||||
game_message: copyData
|
||||
? copyData.game_message
|
||||
: [
|
||||
{ language: 'KO', content: '' },
|
||||
{ language: 'EN', content: '' },
|
||||
{ language: 'JA', content: '' },
|
||||
],
|
||||
});
|
||||
|
||||
if (copyData && copyData.game_message.length <= 1) {
|
||||
setBtnValidation(true);
|
||||
}
|
||||
}, [copyData]);
|
||||
|
||||
const handleRepeatTime = e => {
|
||||
if (e.target.id === 'hour') setRepeatHour(e.target.value);
|
||||
else if (e.target.id === 'min') setRepeatMin(e.target.value);
|
||||
|
||||
setResultData({ ...resultData, repeat_dt: (e.target.id === 'hour' ? e.target.value : repeatHour) + ':' + (e.target.id === 'min' ? e.target.value : repeatMin) + ':00' });
|
||||
};
|
||||
|
||||
// 날짜 설정
|
||||
const handleSelectedDate = data => {
|
||||
const sendDate = new Date(data);
|
||||
|
||||
if (resultData.send_dt.length === 0 || typeof resultData.send_dt.length != 'undefined') {
|
||||
setSendHour('00');
|
||||
setSendMin('00');
|
||||
}
|
||||
|
||||
const resultSendData = new Date(sendDate.getFullYear(), sendDate.getMonth(), sendDate.getDate(), sendHour, sendMin);
|
||||
|
||||
setResultData({ ...resultData, send_dt: resultSendData });
|
||||
};
|
||||
|
||||
// 시간 설정
|
||||
const handleSendTime = e => {
|
||||
let sendDate = '';
|
||||
|
||||
if (resultData.send_dt.length === 0 || typeof resultData.send_dt.length != 'undefined') {
|
||||
sendDate = new Date();
|
||||
} else {
|
||||
sendDate = new Date(resultData.send_dt);
|
||||
}
|
||||
|
||||
if (e.target.id === 'hour') setSendHour(e.target.value);
|
||||
else if (e.target.id === 'min') setSendMin(e.target.value);
|
||||
|
||||
// console.log(sendDate);
|
||||
|
||||
const result = new Date(sendDate.getFullYear(), sendDate.getMonth(), sendDate.getDate(), e.target.id === 'hour' ? e.target.value : sendHour, e.target.id === 'min' ? e.target.value : sendMin);
|
||||
|
||||
setResultData({ ...resultData, send_dt: result });
|
||||
};
|
||||
|
||||
// 종료 날짜 설정
|
||||
const handleSelectedEndDate = data => {
|
||||
const endDate = new Date(data);
|
||||
|
||||
if (resultData.end_dt.length === 0 || typeof resultData.end_dt.length != 'undefined') {
|
||||
setEndHour('00');
|
||||
setEndMin('00');
|
||||
}
|
||||
|
||||
let resultEndData = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate(), endHour, endMin);
|
||||
|
||||
if (resultData.send_dt > resultEndData){
|
||||
alert("송출 일자보다 작을 수 없습니다.");
|
||||
resultEndData = new Date(resultData.send_dt);
|
||||
}
|
||||
|
||||
setResultData({ ...resultData, end_dt: resultEndData });
|
||||
};
|
||||
|
||||
//종료 시간 설정
|
||||
const handleEndTime = e => {
|
||||
let endDate = '';
|
||||
|
||||
if (resultData.end_dt.length === 0 || typeof resultData.end_dt.length != 'undefined') {
|
||||
endDate = new Date();
|
||||
} else {
|
||||
endDate = new Date(resultData.end_dt);
|
||||
}
|
||||
|
||||
if (e.target.id === 'hour') setEndHour(e.target.value);
|
||||
else if (e.target.id === 'min') setEndMin(e.target.value);
|
||||
|
||||
// console.log(sendDate);
|
||||
|
||||
let result = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate(), e.target.id === 'hour' ? e.target.value : endHour, e.target.id === 'min' ? e.target.value : endMin);
|
||||
|
||||
if (resultData.send_dt > result){
|
||||
alert("송출 일자보다 작을 수 없습니다.");
|
||||
result = new Date(resultData.send_dt);
|
||||
}
|
||||
|
||||
setResultData({ ...resultData, end_dt: result });
|
||||
};
|
||||
|
||||
const handleDelete = language => {
|
||||
let filterList = resultData.game_message.filter(el => el.language !== language);
|
||||
|
||||
if (filterList.length === 1) {
|
||||
setBtnValidation(true);
|
||||
} else if (filterList.length > 1) {
|
||||
setBtnValidation(false);
|
||||
}
|
||||
|
||||
setResultData({ ...resultData, game_message: filterList });
|
||||
};
|
||||
|
||||
// 반복횟수 입력 데이터 폼 처리
|
||||
const handleRepeatValue = e => {
|
||||
// e.target.value.length === 0 ? setIsNullValue(true) : setIsNullValue(false);
|
||||
|
||||
if (e.target.value === '0' || e.target.value === '-0' || e.target.value.length === 0) {
|
||||
setResultData({ ...resultData, repeat_cnt: '1' });
|
||||
e.target.value = '1';
|
||||
} else if (e.target.value < 0) {
|
||||
let plusNum = Math.abs(e.target.value);
|
||||
setResultData({ ...resultData, repeat_cnt: plusNum });
|
||||
} else {
|
||||
setResultData({ ...resultData, repeat_cnt: e.target.value });
|
||||
}
|
||||
};
|
||||
|
||||
// 등록 버튼
|
||||
const handleRegistBtn = e => {
|
||||
e.preventDefault();
|
||||
|
||||
if (
|
||||
!resultData.game_message.some(data => data.content !== '') ||
|
||||
resultData.game_message.length === 0 ||
|
||||
resultData.send_dt.length === 0 ||
|
||||
typeof resultData.send_dt.length != 'undefined' ||
|
||||
(resultData.is_repeat === true && resultData.repeat_dt === '00:00:00') ||
|
||||
(resultData.is_repeat === true && resultData.message_type === "COUNT" && resultData.repeat_cnt.length === 0) ||
|
||||
(resultData.is_repeat === true && resultData.message_type === "COUNT" && resultData.end_dt.length === 0) ||
|
||||
(resultData.is_repeat === true && resultData.message_type === "COUNT" && typeof resultData.end_dt.length !== 'undefined')
|
||||
) {
|
||||
setIsNullValue(true);
|
||||
} else {
|
||||
setIsNullValue(false);
|
||||
setConfirmText('인게임 메시지를 등록하시겠습니까?');
|
||||
handleConfirmModal('regist');
|
||||
}
|
||||
};
|
||||
|
||||
// 취소 버튼
|
||||
const handleCancelBtn = e => {
|
||||
e.preventDefault();
|
||||
setConfirmText('인게임 메시지 등록을 취소하시겠습니까?');
|
||||
handleConfirmModal('cancel');
|
||||
};
|
||||
|
||||
// 입력창 글자 제한
|
||||
const handleInputData = e => {
|
||||
if (e.target.value.length > 250) {
|
||||
return;
|
||||
}
|
||||
let list = [...resultData.game_message];
|
||||
let findIndex = resultData.game_message.findIndex(item => item.language === e.target.id);
|
||||
list[findIndex].content = e.target.value.trimStart();
|
||||
|
||||
setResultData({ ...resultData, game_message: list });
|
||||
};
|
||||
|
||||
// 알림창 텍스트
|
||||
const handleSubmitNotice = async () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
|
||||
if (confirmText === '인게임 메시지를 등록하시겠습니까?') {
|
||||
const message = await NoticeRegist(token, resultData);
|
||||
|
||||
message.data.data.message !== '등록 하였습니다.' ? setCompleteText(message.data.data.message) : setCompleteText('등록이 완료되었습니다.');
|
||||
|
||||
handleConfirmModal();
|
||||
handleCompleteModal();
|
||||
setIsNullValue(false);
|
||||
|
||||
setRegistView('hidden');
|
||||
} else if (confirmText === '인게임 메시지 등록을 취소하시겠습니까?') {
|
||||
handleConfirmModal();
|
||||
handleCompleteModal();
|
||||
setCompleteText('등록이 취소되었습니다.');
|
||||
|
||||
setRegistView('hidden');
|
||||
}
|
||||
|
||||
itemId.current = 1;
|
||||
};
|
||||
|
||||
// 언어 선택
|
||||
const handleLanguage = e => {
|
||||
setMessage_lang(e.target.value);
|
||||
if(!resultData.game_message.some(({language}) => language === e.target.value))
|
||||
setResultData({ ...resultData, game_message: [...resultData.game_message, {language: e.target.value, content: ''}] })
|
||||
}
|
||||
|
||||
// 확인 모달
|
||||
const handleConfirmModal = step => {
|
||||
if (confirmModal === 'hidden') {
|
||||
setConfirmModal('view');
|
||||
} else {
|
||||
setConfirmModal('hidden');
|
||||
}
|
||||
};
|
||||
|
||||
// 완료 모달창
|
||||
const handleCompleteModal = () => {
|
||||
setIsCopyData(false);
|
||||
if (completeModal === 'hidden') {
|
||||
setCompleteModal('view');
|
||||
} else {
|
||||
setCompleteModal('hidden');
|
||||
setResultData(resetData);
|
||||
|
||||
window.location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
// 필수값 입력 모달창
|
||||
const handleErrorModal = () => {
|
||||
if (errorModal === 'hidden') {
|
||||
setErrorModal('view');
|
||||
} else {
|
||||
setErrorModal('hidden');
|
||||
}
|
||||
};
|
||||
|
||||
// 내용 복사일 때 이동 페이지
|
||||
return (
|
||||
<>
|
||||
<Modal min="960px" $view={registView}>
|
||||
{/* 등록 후 정보 확인시 타이틀 텍스트 변경 인게임 메시지 등록 -> 인게임 메시지 정보 */}
|
||||
<Title $align="center"> 인게임 메시지 등록</Title>
|
||||
<MessageWrapper>
|
||||
<InputLabel>[메세지 등록 설정]</InputLabel>
|
||||
<NoticeRegistGroup>
|
||||
<NoticeInputRow>
|
||||
<RegistInputItem>
|
||||
<InputLabel>송출 일자(예약)</InputLabel>
|
||||
<InputGroup2>
|
||||
<NoticeInputGroup>
|
||||
<DatePickerWrapper>
|
||||
<DatePickerComponent name="시작 일자" handleSelectedDate={data => handleSelectedDate(data)} selectedDate={resultData.send_dt} pastDate={new Date()} />
|
||||
</DatePickerWrapper>
|
||||
<SelectInput
|
||||
onChange={e => handleSendTime(e)}
|
||||
id="hour"
|
||||
value={
|
||||
resultData && String(new Date(resultData.send_dt).getHours()) < 10
|
||||
? '0' + String(new Date(resultData.send_dt).getHours())
|
||||
: (resultData && String(new Date(resultData.send_dt).getHours())) || ''
|
||||
}>
|
||||
{HourList.map(hour => (
|
||||
<option
|
||||
value={hour}
|
||||
key={hour}
|
||||
// selected={
|
||||
// resultData && String(new Date(resultData.send_dt).getHours()) < 10
|
||||
// ? '0' + String(new Date(resultData.send_dt).getHours()) === hour
|
||||
// : resultData && String(new Date(resultData.send_dt).getHours()) === hour
|
||||
// ? 'selected'
|
||||
// : ''
|
||||
// }
|
||||
>
|
||||
{hour}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<SelectInput
|
||||
onChange={e => handleSendTime(e)}
|
||||
id="min"
|
||||
value={
|
||||
resultData && String(new Date(resultData.send_dt).getMinutes()) < 10
|
||||
? '0' + String(new Date(resultData.send_dt).getMinutes())
|
||||
: (resultData && String(new Date(resultData.send_dt).getMinutes())) || ''
|
||||
}>
|
||||
{MinuteList.map(min => (
|
||||
<option
|
||||
value={min}
|
||||
key={min}
|
||||
// selected={
|
||||
// resultData && String(new Date(resultData.send_dt).getMinutes()) < 10
|
||||
// ? '0' + String(new Date(resultData.send_dt).getMinutes()) === min
|
||||
// : resultData && String(new Date(resultData.send_dt).getMinutes()) === min
|
||||
// ? 'selected'
|
||||
// : ''
|
||||
// }
|
||||
>
|
||||
{min}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</NoticeInputGroup>
|
||||
<SubText>* UTC+9 한국시간 기준으로 설정 (UTC+0 자동 반영처리)</SubText>
|
||||
</InputGroup2>
|
||||
</RegistInputItem>
|
||||
<RegistInputItem>
|
||||
<InputLabel>메시지 타입</InputLabel>
|
||||
<SelectInput onChange={e => setResultData({ ...resultData, message_type: e.target.value })} value={resultData.message_type || ''}>
|
||||
<option value="CHATTING">채팅</option>
|
||||
<option value="CHATTING_TOAST">채팅 + 토스트</option>
|
||||
</SelectInput>
|
||||
</RegistInputItem>
|
||||
</NoticeInputRow>
|
||||
<NoticeInputRow>
|
||||
<CheckBox label="반복 발송" id="input-regist" inline={false} checked={resultData.is_repeat} setData={e => setResultData({ ...resultData, is_repeat: e.target.checked })} style={{display: 'hidden'}}/>
|
||||
{resultData.is_repeat === true && (
|
||||
<>
|
||||
<RegistInputItem>
|
||||
<InputLabel>반복 타입</InputLabel>
|
||||
<SelectInput onChange={e => setResultData({ ...resultData, repeat_type: e.target.value })} value={resultData.repeat_type || ''}>
|
||||
<option value="COUNT">횟수</option>
|
||||
<option value="DATE">일자</option>
|
||||
<option value="TIME">특정시간</option>
|
||||
</SelectInput>
|
||||
</RegistInputItem>
|
||||
</>
|
||||
)}
|
||||
</NoticeInputRow>
|
||||
<NoticeInputRow>
|
||||
{resultData.is_repeat === true && (
|
||||
<>
|
||||
{resultData.repeat_type !== "COUNT" && (
|
||||
<>
|
||||
<NoticeInputItem>
|
||||
<InputLabel>종료 일자</InputLabel>
|
||||
<InputGroup2>
|
||||
<NoticeInputGroup>
|
||||
<DatePickerWrapper>
|
||||
<DatePickerComponent name="종료 일자" handleSelectedDate={data => handleSelectedEndDate(data)} selectedDate={resultData.end_dt} pastDate={new Date()} />
|
||||
</DatePickerWrapper>
|
||||
<SelectInput
|
||||
onChange={e => handleEndTime(e)}
|
||||
id="hour"
|
||||
value={
|
||||
resultData && String(new Date(resultData.end_dt).getHours()) < 10
|
||||
? '0' + String(new Date(resultData.end_dt).getHours())
|
||||
: (resultData && String(new Date(resultData.end_dt).getHours())) || ''
|
||||
}>
|
||||
{HourList.map(hour => (
|
||||
<option
|
||||
value={hour}
|
||||
key={hour}
|
||||
>
|
||||
{hour}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<SelectInput
|
||||
onChange={e => handleEndTime(e)}
|
||||
id="min"
|
||||
value={
|
||||
resultData && String(new Date(resultData.end_dt).getMinutes()) < 10
|
||||
? '0' + String(new Date(resultData.end_dt).getMinutes())
|
||||
: (resultData && String(new Date(resultData.end_dt).getMinutes())) || ''
|
||||
}>
|
||||
{MinuteList.map(min => (
|
||||
<option
|
||||
value={min}
|
||||
key={min}
|
||||
>
|
||||
{min}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</NoticeInputGroup>
|
||||
</InputGroup2>
|
||||
</NoticeInputItem>
|
||||
</>
|
||||
)}
|
||||
|
||||
<RegistInputItem>
|
||||
<RepeatTime>
|
||||
{resultData.repeat_type === "TIME" && (<span>발송시간</span>)}
|
||||
<SelectInput onChange={e => handleRepeatTime(e)} id="hour" value={resultData.repeat_dt.split(':')[0] || ''}>
|
||||
{HourList.map(hour => (
|
||||
<option
|
||||
value={hour}
|
||||
key={hour}
|
||||
>
|
||||
{hour}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<SelectInput onChange={e => handleRepeatTime(e)} id="min" value={resultData.repeat_dt.split(':')[1] || ''}>
|
||||
{MinuteList.map(min => (
|
||||
<option
|
||||
value={min}
|
||||
key={min}
|
||||
>
|
||||
{min}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
{resultData.repeat_type !== "TIME" ? (
|
||||
<>
|
||||
<span>마다</span>
|
||||
<SubText>* 최초 송출 이후 설정된 시간 단위마다 송출</SubText>
|
||||
</>
|
||||
) : (<SubText>* 설정된 시간에만 송출</SubText>)}
|
||||
</RepeatTime>
|
||||
</RegistInputItem>
|
||||
{resultData.repeat_type === "COUNT" && (
|
||||
<>
|
||||
<NoticeInputItem>
|
||||
<InputLabel>반복 횟수</InputLabel>
|
||||
<NoticeInputGroup>
|
||||
<TextInput
|
||||
type="number"
|
||||
value={resultData.repeat_cnt || ''}
|
||||
onChange={e => {
|
||||
handleRepeatValue(e);
|
||||
}}
|
||||
width="80px"
|
||||
/>
|
||||
<span>회</span>
|
||||
</NoticeInputGroup>
|
||||
</NoticeInputItem>
|
||||
</>
|
||||
)}
|
||||
|
||||
</>
|
||||
)}
|
||||
</NoticeInputRow>
|
||||
</NoticeRegistGroup>
|
||||
<NoticeInputRow2>
|
||||
<InputLabel>
|
||||
[메세지 작성]
|
||||
<SubTextRow>* 작성하지 않은 언어는 발송되지 않습니다.</SubTextRow>
|
||||
</InputLabel>
|
||||
<NoticeInputItem2>
|
||||
<InputLabel>언어</InputLabel>
|
||||
<SelectInput onChange={e => handleLanguage(e) } value={message_lang}>
|
||||
{languageType.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</NoticeInputItem2>
|
||||
</NoticeInputRow2>
|
||||
<BoxWrapper>
|
||||
{resultData.game_message.map(content => {
|
||||
return (
|
||||
<Fragment key={content.language}>
|
||||
{message_lang === content.language && (
|
||||
<MessageBox>
|
||||
{/* <BtnWrapper $justify="flex-end">
|
||||
{btnValidation === false ? (
|
||||
<ButtonClose
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
handleDelete(content.language);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<ButtonClose opacity="10%" />
|
||||
)}
|
||||
</BtnWrapper> */}
|
||||
<TitleLang>언어 : {content.language}</TitleLang>
|
||||
<div>
|
||||
<Textarea id={content.language} onChange={e => handleInputData(e)} defaultValue={content.content || ''} maxLength={250} />
|
||||
</div>
|
||||
</MessageBox>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</BoxWrapper>
|
||||
</MessageWrapper>
|
||||
{isNullValue && <SearchBarAlert $marginTop="25px">필수값을 입력해주세요.</SearchBarAlert>}
|
||||
|
||||
<BtnWrapper $gap="10px" $justify="center" $marginTop="20px">
|
||||
<Button text="취소" theme="line" handleClick={handleCancelBtn} />
|
||||
<Button
|
||||
type="submit"
|
||||
text="등록"
|
||||
name="등록버튼"
|
||||
theme={
|
||||
// resultData.game_message.map(data => data.content === '').includes(true) ||
|
||||
!resultData.game_message.some(data => data.content !== '') ||
|
||||
resultData.game_message.length === 0 ||
|
||||
resultData.send_dt.length === 0 ||
|
||||
typeof resultData.send_dt.length != 'undefined' ||
|
||||
(resultData.is_repeat === true && resultData.repeat_dt === '00:00:00') ||
|
||||
(resultData.is_repeat === true && resultData.message_type === "COUNT" && resultData.repeat_cnt.length === 0) ||
|
||||
(resultData.is_repeat === true && resultData.message_type === "COUNT" && resultData.end_dt.length === 0) ||
|
||||
(resultData.is_repeat === true && resultData.message_type === "COUNT" && typeof resultData.end_dt.length !== 'undefined')
|
||||
? 'disable'
|
||||
: 'primary'
|
||||
}
|
||||
handleClick={handleRegistBtn}
|
||||
/>
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
|
||||
{/* 확인 모달 */}
|
||||
<DynamicModal
|
||||
modalType={modalTypes.confirmOkCancel}
|
||||
view={confirmModal}
|
||||
modalText={confirmText}
|
||||
handleSubmit={() => {
|
||||
doubleSubmitFlag || handleSubmitNotice();
|
||||
setDoubleSubmitFlag(true);
|
||||
}}
|
||||
handleCancel={handleConfirmModal}
|
||||
/>
|
||||
{/* 완료 모달 */}
|
||||
<DynamicModal
|
||||
modalType={modalTypes.completed}
|
||||
view={completeModal}
|
||||
modalText={completeText}
|
||||
handleSubmit={handleCompleteModal}
|
||||
/>
|
||||
{/* 필수값 미입력 모달 */}
|
||||
<DynamicModal
|
||||
modalType={modalTypes.completed}
|
||||
view={errorModal}
|
||||
modalText={t('NULL_MSG')}
|
||||
handleSubmit={handleErrorModal}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default BoardRegistModal;
|
||||
564
src/components/ServiceManage/modal/EventDetailModal.js
Normal file
564
src/components/ServiceManage/modal/EventDetailModal.js
Normal file
@@ -0,0 +1,564 @@
|
||||
import { useState, useEffect, Fragment } from 'react';
|
||||
|
||||
import { Title, SelectInput, BtnWrapper, TextInput, Label, InputLabel, Textarea, SearchBarAlert } from '../../../styles/Components';
|
||||
import Button from '../../common/button/Button';
|
||||
import Modal from '../../common/modal/Modal';
|
||||
import { EventIsItem, EventModify, MailModify } from '../../../apis';
|
||||
|
||||
import { authList } from '../../../store/authList';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { authType, benItems, commonStatus, modalTypes, wellType } from '../../../assets/data';
|
||||
import {
|
||||
AppendRegistBox, AppendRegistTable, AreaBtnClose,
|
||||
BtnDelete, DetailInputItem, DetailInputRow,
|
||||
DetailModalWrapper, RegistGroup, DetailRegistInfo, DetailState,
|
||||
Item, ItemList, LangArea
|
||||
} from '../../../styles/ModuleComponents';
|
||||
import DynamicModal from '../../common/modal/DynamicModal';
|
||||
import { convertKTC, combineDateTime, timeDiffMinute, convertKTCDate } from '../../../utils';
|
||||
import DateTimeInput from '../../common/input/DateTimeInput';
|
||||
|
||||
const EventDetailModal = ({ detailView, handleDetailView, content, setDetailData }) => {
|
||||
const userInfo = useRecoilValue(authList);
|
||||
const { t } = useTranslation();
|
||||
const token = sessionStorage.getItem('token');
|
||||
|
||||
const id = content && content.id;
|
||||
const updateAuth = userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.eventUpdate);
|
||||
|
||||
const [time, setTime] = useState({
|
||||
start_hour: '00',
|
||||
start_min: '00',
|
||||
end_hour: '00',
|
||||
end_min: '00',
|
||||
}); //시간 정보
|
||||
|
||||
const [item, setItem] = useState('');
|
||||
const [itemCount, setItemCount] = useState('');
|
||||
const [resource, setResource] = useState('19010001');
|
||||
const [resourceCount, setResourceCount] = useState('');
|
||||
|
||||
const [modifyModal, setModifyModal] = useState('hidden');
|
||||
const [completeModal, setCompleteModal] = useState('hidden');
|
||||
const [resultData, setResultData] = useState({});
|
||||
|
||||
const [modalState, setModalState] = useState({
|
||||
updateConfirmModal: 'hidden',
|
||||
updateCompleteModal: 'hidden',
|
||||
});
|
||||
|
||||
const [isNullValue, setIsNullValue] = useState(false);
|
||||
// 과거 판단
|
||||
const [isPast, setIsPast] = useState(false);
|
||||
const [isChanged, setIsChanged] = useState(false);
|
||||
|
||||
const [btnValidation, setBtnValidation] = useState(false);
|
||||
const [isReadOnly, setIsReadOnly] = useState(false);
|
||||
const [itemCheckMsg, setItemCheckMsg] = useState('');
|
||||
const [alertMsg, setAlertMsg] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if(content){
|
||||
const start_dt_KTC = convertKTCDate(content.start_dt)
|
||||
const end_dt_KTC = convertKTCDate(content.end_dt)
|
||||
|
||||
setResultData({
|
||||
start_dt: start_dt_KTC,
|
||||
end_dt: end_dt_KTC,
|
||||
event_type: content.event_type,
|
||||
mail_list: content.mail_list,
|
||||
item_list: content.item_list,
|
||||
});
|
||||
|
||||
setTime({ ...time,
|
||||
start_hour: String(start_dt_KTC.getHours()).padStart(2, '0'),
|
||||
start_min: String(start_dt_KTC.getMinutes()).padStart(2, '0'),
|
||||
end_hour: String(end_dt_KTC.getHours()).padStart(2, '0'),
|
||||
end_min: String(end_dt_KTC.getMinutes()).padStart(2, '0')
|
||||
});
|
||||
|
||||
start_dt_KTC < (new Date) ? setIsPast(true) : setIsPast(false);
|
||||
content.mail_list.length === 1 && setBtnValidation(true);
|
||||
}
|
||||
|
||||
setItem('');
|
||||
|
||||
}, [content]);
|
||||
|
||||
useEffect(() => {
|
||||
if(!updateAuth || isPast){
|
||||
setIsReadOnly(true);
|
||||
}else{
|
||||
setIsReadOnly(false);
|
||||
}
|
||||
}, [updateAuth, isPast]);
|
||||
|
||||
useEffect(() => {
|
||||
if (conditionCheck()) {
|
||||
setIsNullValue(false);
|
||||
} else {
|
||||
setIsNullValue(true);
|
||||
}
|
||||
}, [resultData]);
|
||||
|
||||
useEffect(() => {
|
||||
setItemCheckMsg('');
|
||||
}, [item]);
|
||||
|
||||
// 아이템 수량 숫자 체크
|
||||
const handleItemCount = e => {
|
||||
if (e.target.value === '0' || e.target.value === '-0') {
|
||||
setItemCount('1');
|
||||
e.target.value = '1';
|
||||
} else if (e.target.value < 0) {
|
||||
let plusNum = Math.abs(e.target.value);
|
||||
setItemCount(plusNum);
|
||||
} else {
|
||||
setItemCount(e.target.value);
|
||||
}
|
||||
};
|
||||
|
||||
// 아이템 추가
|
||||
const handleItemList = async () => {
|
||||
if(benItems.includes(item)){
|
||||
setAlertMsg(t('MAIL_ITEM_ADD_BEN'))
|
||||
return;
|
||||
}
|
||||
if(item.length === 0 || itemCount.length === 0) return;
|
||||
|
||||
const token = sessionStorage.getItem('token');
|
||||
const result = await EventIsItem(token, {item: item});
|
||||
|
||||
if(result.data.result === "ERROR"){
|
||||
setItemCheckMsg(t('NOT_ITEM'));
|
||||
return;
|
||||
}
|
||||
|
||||
const itemIndex = resultData.item_list.findIndex((data) => data.item === item);
|
||||
if (itemIndex !== -1) {
|
||||
setItemCheckMsg(t('MAIL_ITEM_ADD_DUPL'));
|
||||
return;
|
||||
}
|
||||
const newItem = { item: item, item_cnt: itemCount, item_name: result.data.data.item_info.item_name };
|
||||
|
||||
resultData.item_list.push(newItem);
|
||||
|
||||
setIsChanged(true);
|
||||
setItem('');
|
||||
setItemCount('');
|
||||
};
|
||||
|
||||
// 아이템 삭제
|
||||
const onItemRemove = id => {
|
||||
let filterList = resultData.item_list && resultData.item_list.filter(item => item !== resultData.item_list[id]);
|
||||
setIsChanged(true);
|
||||
|
||||
setResultData({ ...resultData, item_list: filterList });
|
||||
};
|
||||
|
||||
// 자원 수량 숫자 체크
|
||||
const handleResourceCount = e => {
|
||||
if (e.target.value === '0' || e.target.value === '-0') {
|
||||
setResourceCount('1');
|
||||
e.target.value = '1';
|
||||
} else if (e.target.value < 0) {
|
||||
let plusNum = Math.abs(e.target.value);
|
||||
setResourceCount(plusNum);
|
||||
} else {
|
||||
setResourceCount(e.target.value);
|
||||
}
|
||||
};
|
||||
|
||||
// 자원 추가
|
||||
const handleResourceList = (e) => {
|
||||
if(resource.length === 0 || resourceCount.length === 0) return;
|
||||
|
||||
const itemIndex = resultData.item_list.findIndex(
|
||||
(item) => item.item === resource
|
||||
);
|
||||
|
||||
if (itemIndex !== -1) {
|
||||
const item_cnt = resultData.item_list[itemIndex].item_cnt;
|
||||
resultData.item_list[itemIndex].item_cnt = Number(item_cnt) + Number(resourceCount);
|
||||
} else {
|
||||
const name = wellType.find(well => well.value === resource).name;
|
||||
const newItem = { item: resource, item_cnt: resourceCount, item_name: name };
|
||||
resultData.item_list.push(newItem);
|
||||
}
|
||||
setIsChanged(true);
|
||||
setResource('')
|
||||
setResourceCount('');
|
||||
};
|
||||
|
||||
// 입력창 삭제
|
||||
const onLangDelete = language => {
|
||||
let filterList = resultData.mail_list && resultData.mail_list.filter(el => el.language !== language);
|
||||
|
||||
if (filterList.length === 1) setBtnValidation(true);
|
||||
|
||||
setIsChanged(true);
|
||||
setResultData({ ...resultData, mail_list: filterList });
|
||||
};
|
||||
|
||||
// 날짜 처리
|
||||
const handleDateChange = (data, type) => {
|
||||
const date = new Date(data);
|
||||
setResultData({
|
||||
...resultData,
|
||||
[`${type}_dt`]: combineDateTime(date, time[`${type}_hour`], time[`${type}_min`]),
|
||||
});
|
||||
setIsChanged(true);
|
||||
};
|
||||
|
||||
// 시간 처리
|
||||
const handleTimeChange = (e, type) => {
|
||||
const { id, value } = e.target;
|
||||
const newTime = { ...time, [`${type}_${id}`]: value };
|
||||
setTime(newTime);
|
||||
|
||||
const date = resultData[`${type}_dt`] ? new Date(resultData[`${type}_dt`]) : new Date();
|
||||
|
||||
setResultData({
|
||||
...resultData,
|
||||
[`${type}_dt`]: combineDateTime(date, newTime[`${type}_hour`], newTime[`${type}_min`]),
|
||||
});
|
||||
setIsChanged(true);
|
||||
};
|
||||
|
||||
// 확인 버튼 후 다 초기화
|
||||
const handleReset = () => {
|
||||
setBtnValidation(false);
|
||||
setIsNullValue(false);
|
||||
setIsChanged(false);
|
||||
};
|
||||
|
||||
const conditionCheck = () => {
|
||||
return (
|
||||
content && content.mail_list.every(data => data.content !== '' && data.title !== '') &&
|
||||
isChanged
|
||||
);
|
||||
};
|
||||
|
||||
const handleModalView = (type) => {
|
||||
setModalState((prevState) => ({
|
||||
...prevState,
|
||||
[`${type}Modal`]: 'view',
|
||||
}));
|
||||
}
|
||||
|
||||
const handleModalClose = (type) => {
|
||||
setModalState((prevState) => ({
|
||||
...prevState,
|
||||
[`${type}Modal`]: 'hidden',
|
||||
}));
|
||||
}
|
||||
|
||||
const handleSubmit = async (type, param = null) => {
|
||||
switch (type) {
|
||||
case "submit":
|
||||
if (!conditionCheck()) return;
|
||||
|
||||
handleModalView('updateConfirm');
|
||||
break;
|
||||
case "updateConfirm":
|
||||
const timeDiff = timeDiffMinute(resultData.start_dt, (new Date))
|
||||
// 이벤트 시작 30분전이나 이미 SystemMail이 add된 상태에서는 수정할 수 없다.
|
||||
if(content.add_flag || timeDiff <= 30){
|
||||
setAlertMsg(t('EVENT_TIME_LIMIT_UPDATE'));
|
||||
handleModalClose('updateConfirm');
|
||||
return;
|
||||
}
|
||||
await EventModify(token, id, resultData);
|
||||
handleModalClose('updateConfirm');
|
||||
handleModalView('updateComplete');
|
||||
break;
|
||||
case "updateComplete":
|
||||
handleModalClose('updateComplete');
|
||||
window.location.reload();
|
||||
break;
|
||||
case "warning":
|
||||
setAlertMsg('');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const detailState = (status) => {
|
||||
switch (status) {
|
||||
case commonStatus.wait:
|
||||
return <DetailState>대기</DetailState>;
|
||||
case commonStatus.running:
|
||||
return <DetailState>진행중</DetailState>;
|
||||
case commonStatus.finish:
|
||||
return <DetailState result={commonStatus.finish}>완료</DetailState>;
|
||||
case commonStatus.fail:
|
||||
return <DetailState result={commonStatus.fail}>실패</DetailState>;
|
||||
case commonStatus.delete:
|
||||
return <DetailState result={commonStatus.delete}>삭제</DetailState>;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal min="960px" $view={detailView}>
|
||||
<Title $align="center">이벤트 상세 정보</Title>
|
||||
{content &&
|
||||
<DetailRegistInfo>
|
||||
<span>등록자 : {content.create_by}</span>
|
||||
<span>등록일 : {convertKTC(content.create_dt, false)}</span>
|
||||
{typeof content.update_by !== 'undefined' && (
|
||||
<>
|
||||
<span>수정자 : {content.update_by}</span>
|
||||
<span>수정일 : {convertKTC(content.update_dt, false)}</span>
|
||||
</>
|
||||
)}
|
||||
</DetailRegistInfo>
|
||||
}
|
||||
<DetailModalWrapper>
|
||||
{content &&
|
||||
<RegistGroup>
|
||||
<DetailInputRow>
|
||||
<DateTimeInput
|
||||
title="이벤트 기간"
|
||||
dateName="시작 일자"
|
||||
selectedDate={convertKTCDate(content.start_dt)}
|
||||
handleSelectedDate={data => handleDateChange(data, 'start')}
|
||||
onChange={e => handleTimeChange(e, 'start')}
|
||||
|
||||
/>
|
||||
<DateTimeInput
|
||||
dateName="종료 일자"
|
||||
selectedDate={convertKTCDate(content.end_dt)}
|
||||
handleSelectedDate={data => handleDateChange(data, 'end')}
|
||||
onChange={e => handleTimeChange(e, 'end')}
|
||||
/>
|
||||
</DetailInputRow>
|
||||
<DetailInputRow>
|
||||
<DetailInputItem>
|
||||
<InputLabel>이벤트 상태</InputLabel>
|
||||
<div>{detailState(content.status)}</div>
|
||||
</DetailInputItem>
|
||||
{content.status === commonStatus.delete &&
|
||||
<DetailInputItem>
|
||||
<InputLabel>삭제 사유</InputLabel>
|
||||
<div>{content.delete_desc}</div>
|
||||
</DetailInputItem>
|
||||
}
|
||||
</DetailInputRow>
|
||||
|
||||
</RegistGroup>
|
||||
}
|
||||
{resultData.mail_list &&
|
||||
resultData.mail_list.map(data => {
|
||||
return (
|
||||
<Fragment key={data.language}>
|
||||
<AppendRegistBox>
|
||||
<LangArea>
|
||||
언어 : {data.language}
|
||||
{btnValidation === false && !isReadOnly ? (
|
||||
<AreaBtnClose
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
onLangDelete(data.language);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<AreaBtnClose opacity="10%" />
|
||||
)}
|
||||
</LangArea>
|
||||
<AppendRegistTable>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th width="120">
|
||||
<Label>제목</Label>
|
||||
</th>
|
||||
<td>
|
||||
<DetailInputItem>
|
||||
<TextInput
|
||||
placeholder="우편 제목 입력"
|
||||
maxLength="30"
|
||||
id={data.language}
|
||||
value={data.title}
|
||||
readOnly={isReadOnly}
|
||||
onChange={e => {
|
||||
let list = [...resultData.mail_list];
|
||||
let findIndex = resultData.mail_list && resultData.mail_list.findIndex(item => item.language === e.target.id);
|
||||
list[findIndex].title = e.target.value.trimStart();
|
||||
|
||||
setResultData({ ...resultData, mail_list: list });
|
||||
setIsChanged(true);
|
||||
}}
|
||||
/>
|
||||
</DetailInputItem>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
<Label>내용</Label>
|
||||
</th>
|
||||
<td>
|
||||
<Textarea
|
||||
value={data.content}
|
||||
readOnly={isReadOnly}
|
||||
id={data.language}
|
||||
onChange={e => {
|
||||
if (e.target.value.length > 2000) {
|
||||
return;
|
||||
}
|
||||
let list = [...resultData.mail_list];
|
||||
let findIndex = resultData.mail_list && resultData.mail_list.findIndex(item => item.language === e.target.id);
|
||||
list[findIndex].content = e.target.value.trimStart();
|
||||
|
||||
setResultData({ ...resultData, mail_list: list });
|
||||
setIsChanged(true);
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</AppendRegistTable>
|
||||
</AppendRegistBox>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
<AppendRegistBox>
|
||||
<AppendRegistTable>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th width="120">
|
||||
<Label>아이템 첨부</Label>
|
||||
</th>
|
||||
<td>
|
||||
<DetailInputItem>
|
||||
<TextInput
|
||||
placeholder="Item Meta id 입력"
|
||||
value={item}
|
||||
onChange={e => {
|
||||
let list = [];
|
||||
list = e.target.value.trimStart();
|
||||
setItem(list);
|
||||
}}
|
||||
disabled={isReadOnly}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder="수량"
|
||||
value={itemCount}
|
||||
type="number"
|
||||
onChange={e => handleItemCount(e)}
|
||||
width="90px"
|
||||
disabled={isReadOnly}
|
||||
/>
|
||||
<Button
|
||||
text="추가"
|
||||
theme={itemCount.length === 0 || item.length === 0 ? 'disable' : 'search'}
|
||||
handleClick={handleItemList}
|
||||
/>
|
||||
{itemCheckMsg && <SearchBarAlert>{itemCheckMsg}</SearchBarAlert>}
|
||||
</DetailInputItem>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th width="120">
|
||||
<Label>자원 첨부</Label>
|
||||
</th>
|
||||
<td>
|
||||
<DetailInputItem>
|
||||
<SelectInput onChange={e => setResource(e.target.value)} value={resource} disabled={isReadOnly}>
|
||||
{wellType.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<TextInput
|
||||
placeholder="수량"
|
||||
type="number"
|
||||
value={resourceCount}
|
||||
disabled={isReadOnly}
|
||||
onChange={e => handleResourceCount(e)}
|
||||
width="200px"
|
||||
/>
|
||||
<Button
|
||||
text="추가"
|
||||
theme={resourceCount.length === 0 || resource.length === 0 ? 'disable' : 'search'}
|
||||
handleClick={handleResourceList}
|
||||
width="100px"
|
||||
height="35px"
|
||||
errorMessage={isReadOnly} />
|
||||
</DetailInputItem>
|
||||
|
||||
<div>
|
||||
{resultData.item_list && (
|
||||
<ItemList>
|
||||
{resultData.item_list.map((data, index) => {
|
||||
return (
|
||||
<Item key={index}>
|
||||
<span>
|
||||
{data.item_name}[{data.item}] ({data.item_cnt})
|
||||
</span>
|
||||
{!isReadOnly && <BtnDelete onClick={() => onItemRemove(index)}></BtnDelete>}
|
||||
</Item>
|
||||
);
|
||||
})}
|
||||
</ItemList>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</AppendRegistTable>
|
||||
</AppendRegistBox>
|
||||
</DetailModalWrapper>
|
||||
<BtnWrapper $justify="flex-end" $gap="10px" $paddingTop="20px">
|
||||
<Button
|
||||
text="확인"
|
||||
theme="line"
|
||||
name="확인버튼"
|
||||
handleClick={() => {
|
||||
handleDetailView();
|
||||
handleReset();
|
||||
setDetailData('');
|
||||
}}
|
||||
/>
|
||||
{!isReadOnly && (
|
||||
<Button
|
||||
type="submit"
|
||||
text="수정"
|
||||
id="수정버튼"
|
||||
theme={conditionCheck() ? 'primary' : 'disable'}
|
||||
handleClick={() => handleSubmit('submit')}
|
||||
/>
|
||||
)}
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
{/* 확인 모달 */}
|
||||
<DynamicModal
|
||||
modalType={modalTypes.confirmOkCancel}
|
||||
view={modalState.updateConfirmModal}
|
||||
modalText={t('MAIL_UPDATE_SAVE')}
|
||||
handleCancel={() => handleModalClose('updateConfirm')}
|
||||
handleSubmit={() => handleSubmit('updateConfirm')}
|
||||
/>
|
||||
{/* 완료 모달 */}
|
||||
<DynamicModal
|
||||
modalType={modalTypes.completed}
|
||||
view={modalState.updateCompleteModal}
|
||||
modalText={t('UPDATE_COMPLETED')}
|
||||
handleSubmit={() => handleSubmit('updateComplete')}
|
||||
/>
|
||||
{/* 경고 모달 */}
|
||||
<DynamicModal
|
||||
modalType={modalTypes.completed}
|
||||
view={alertMsg ? 'view' : 'hidden'}
|
||||
modalText={alertMsg}
|
||||
handleSubmit={() => handleSubmit('warning')}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default EventDetailModal;
|
||||
|
||||
|
||||
517
src/components/ServiceManage/modal/LandAuctionModal.js
Normal file
517
src/components/ServiceManage/modal/LandAuctionModal.js
Normal file
@@ -0,0 +1,517 @@
|
||||
import { useState, Fragment, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Button from '../../common/button/Button';
|
||||
import Loading from '../../common/Loading';
|
||||
|
||||
import {
|
||||
Title,
|
||||
BtnWrapper,
|
||||
SearchBarAlert, SelectInput, InputLabel,
|
||||
} from '../../../styles/Components';
|
||||
|
||||
import {
|
||||
FormHelperText,
|
||||
FormInput,
|
||||
FormLabel,
|
||||
FormTextArea,
|
||||
FormTextAreaWrapper,
|
||||
MessageWrapper,
|
||||
FormRowGroup,
|
||||
NoticeInputRow2,
|
||||
NoticeInputItem2, BoxWrapper, FormStatusBar, FormStatusLabel, FormStatusWarning, FormButtonContainer,
|
||||
} from '../../../styles/ModuleComponents';
|
||||
import { modalTypes } from '../../../assets/data';
|
||||
import {DynamicModal, Modal, DateTimeRangePicker} from '../../common';
|
||||
import { LandAuctionModify, LandAuctionSingleRegist } from '../../../apis';
|
||||
import { TYPE_MODIFY, TYPE_REGISTRY } from '../../../assets/data/adminConstants';
|
||||
import { landAuctionStatus, landAuctionStatusType, languageType, CurrencyType } from '../../../assets/data';
|
||||
import { useModal } from '../../../utils/hook';
|
||||
import { convertKTCDate } from '../../../utils';
|
||||
|
||||
const LandAuctionModal = ({ modalType, detailView, handleDetailView, content, setDetailData, landData, buildingData }) => {
|
||||
const { t } = useTranslation();
|
||||
const token = sessionStorage.getItem('token');
|
||||
|
||||
const [loading, setLoading] = useState(false); // 로딩 창
|
||||
const {
|
||||
modalState,
|
||||
handleModalView,
|
||||
handleModalClose
|
||||
} = useModal({
|
||||
cancel: 'hidden',
|
||||
registConfirm: 'hidden',
|
||||
registComplete: 'hidden'
|
||||
});
|
||||
const [message_lang, setMessage_lang] = useState('KO');
|
||||
|
||||
const [isNullValue, setIsNullValue] = useState(false); // 데이터 값 체크
|
||||
const [alertMsg, setAlertMsg] = useState('');
|
||||
const [selectLand, setSelectLand] = useState(initLandData);
|
||||
|
||||
const [resultData, setResultData] = useState(initData); //데이터 정보
|
||||
const [resetDateTime, setResetDateTime] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if(modalType === TYPE_MODIFY && content && Object.keys(content).length > 0){
|
||||
setResultData({
|
||||
land_id: content.land_id,
|
||||
land_name: content.land_name,
|
||||
land_socket: content.land_socket,
|
||||
land_size: content.land_size,
|
||||
auction_seq: content.auction_seq,
|
||||
currency_type: content.currency_type,
|
||||
start_price: content.start_price,
|
||||
resv_start_dt: convertKTCDate(content.resv_start_dt),
|
||||
resv_end_dt: convertKTCDate(content.resv_end_dt),
|
||||
auction_start_dt: convertKTCDate(content.auction_start_dt),
|
||||
auction_end_dt: convertKTCDate(content.auction_end_dt),
|
||||
message_list: content.message_list,
|
||||
});
|
||||
const land = landData.find(land => land.id === parseInt(content.land_id));
|
||||
setSelectLand(land);
|
||||
}
|
||||
}, [modalType, content]);
|
||||
|
||||
useEffect(() => {
|
||||
if (checkCondition()) {
|
||||
setIsNullValue(false);
|
||||
} else {
|
||||
setIsNullValue(true);
|
||||
}
|
||||
}, [resultData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (resetDateTime) {
|
||||
setResetDateTime(false);
|
||||
}
|
||||
}, [resetDateTime]);
|
||||
|
||||
// 입력 수량 처리
|
||||
const handleCount = e => {
|
||||
const regex = /^\d*\.?\d{0,2}$/;
|
||||
if (!regex.test(e.target.value) && e.target.value !== '-') {
|
||||
return;
|
||||
}
|
||||
|
||||
let count = 0;
|
||||
if (e.target.value === '-0') {
|
||||
count = 1;
|
||||
} else if (e.target.value < 0) {
|
||||
let plusNum = Math.abs(e.target.value);
|
||||
count = plusNum;
|
||||
} else{
|
||||
count = e.target.value;
|
||||
}
|
||||
setResultData((prevState) => ({
|
||||
...prevState,
|
||||
start_price: count,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleReservationChange = {
|
||||
start: (date) => {
|
||||
setResultData(prev => ({ ...prev, resv_start_dt: date }));
|
||||
},
|
||||
end: (date) => {
|
||||
setResultData(prev => ({ ...prev, resv_end_dt: date }));
|
||||
}
|
||||
};
|
||||
|
||||
const handleAuctionChange = {
|
||||
start: (date) => {
|
||||
setResultData(prev => ({ ...prev, auction_start_dt: date }));
|
||||
},
|
||||
end: (date) => {
|
||||
setResultData(prev => ({ ...prev, auction_end_dt: date }));
|
||||
}
|
||||
};
|
||||
|
||||
// 입력 글자 제한
|
||||
const handleInputData = e => {
|
||||
if (e.target.value.length > 250) {
|
||||
return;
|
||||
}
|
||||
const updatedMessages = resultData.message_list.map(msg =>
|
||||
msg.language === message_lang
|
||||
? { ...msg, content: e.target.value.trimStart() }
|
||||
: msg
|
||||
);
|
||||
|
||||
setResultData(prev => ({
|
||||
...prev,
|
||||
message_list: updatedMessages
|
||||
}));
|
||||
};
|
||||
|
||||
// 언어 선택
|
||||
const handleLanguage = e => {
|
||||
setMessage_lang(e.target.value);
|
||||
if(!resultData.message_list.some(({language}) => language === e.target.value))
|
||||
setResultData({ ...resultData, message_list: [...resultData.message_list, {language: e.target.value, content: ''}] })
|
||||
}
|
||||
|
||||
const handleLand = e => {
|
||||
const land_id = e.target.value;
|
||||
const land = landData.find(land => land.id === parseInt(land_id));
|
||||
const instance = buildingData.find(building => building.id === parseInt(land.buildingId))?.socket;
|
||||
setSelectLand(land);
|
||||
setResultData({ ...resultData, land_id: land_id, land_name: land.name, land_size: land.size, land_socket: instance });
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
setMessage_lang('KO')
|
||||
setDetailData({});
|
||||
setSelectLand(initLandData);
|
||||
setResultData(initData);
|
||||
setResetDateTime(true);
|
||||
handleDetailView();
|
||||
}
|
||||
|
||||
const handleSubmit = async (type, param = null) => {
|
||||
switch (type) {
|
||||
case "submit":
|
||||
if (!checkCondition()) return;
|
||||
|
||||
const minAllowedTime = new Date(new Date().getTime() + 5 * 60000);
|
||||
if (isView('recv') && resultData.resv_start_dt < minAllowedTime) {
|
||||
setAlertMsg(t('LAND_AUCTION_MADEL_RESV_START_WARNING'));
|
||||
return;
|
||||
}
|
||||
if (resultData.auction_start_dt < minAllowedTime) {
|
||||
setAlertMsg(t('LAND_AUCTION_MADEL_AUCTION_START_WARNING'));
|
||||
return;
|
||||
}
|
||||
if(resultData.resv_start_dt >= resultData.auction_start_dt || resultData.resv_start_dt >= resultData.auction_end_dt) {
|
||||
setAlertMsg(t('LAND_AUCTION_MADEL_AUCTION_DIFF_RESERVATION'))
|
||||
return;
|
||||
}
|
||||
if(resultData.auction_start_dt >= resultData.auction_end_dt) {
|
||||
setAlertMsg(t('LAND_AUCTION_MADEL_AUCTION_DIFF_AUCTION'))
|
||||
return;
|
||||
}
|
||||
|
||||
//화면에 머물면서 상태는 안바꼈을 경우가 있기에 경매시작시간 지났을경우 차단
|
||||
if (modalType === TYPE_MODIFY && resultData.auction_start_dt < new Date()) {
|
||||
setAlertMsg(t('LAND_AUCTION_MADEL_MODIFY_START'));
|
||||
return;
|
||||
}
|
||||
|
||||
handleModalView('registConfirm');
|
||||
break;
|
||||
case "cancel":
|
||||
handleModalView('cancel');
|
||||
break;
|
||||
case "cancelConfirm":
|
||||
handleModalClose('cancel');
|
||||
handleReset();
|
||||
break;
|
||||
case "registConfirm":
|
||||
setLoading(true);
|
||||
|
||||
if(isView('modify')){
|
||||
await LandAuctionModify(token, content?.id, resultData).then(data => {
|
||||
setLoading(false);
|
||||
handleModalClose('registConfirm');
|
||||
if(data.result === "SUCCESS") {
|
||||
handleModalView('registComplete');
|
||||
}else if(data.result === "ERROR_AUCTION_STATUS_IMPOSSIBLE"){
|
||||
setAlertMsg(t('LAND_AUCTION_ERROR_MODIFY_STATUS'));
|
||||
}else{
|
||||
setAlertMsg(t('UPDATE_FAIL'));
|
||||
}
|
||||
}).catch(reason => {
|
||||
setAlertMsg(t('API_FAIL'));
|
||||
});
|
||||
}
|
||||
else{
|
||||
await LandAuctionSingleRegist(token, resultData).then(data => {
|
||||
setLoading(false);
|
||||
handleModalClose('registConfirm');
|
||||
if(data.result === "SUCCESS") {
|
||||
handleModalView('registComplete');
|
||||
}else if(data.result === "ERROR_LAND_AUCTION_IMPOSSIBLE"){
|
||||
setAlertMsg(t('LAND_AUCTION_ERROR_PROGRESS'));
|
||||
}else if(data.result === "ERROR_AUCTION_LAND_OWNER"){
|
||||
setAlertMsg(t('LAND_AUCTION_ERROR_OWNER'));
|
||||
}else{
|
||||
setAlertMsg(t('REGIST_FAIL'));
|
||||
}
|
||||
}).catch(reason => {
|
||||
setAlertMsg(t('API_FAIL'));
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "registComplete":
|
||||
handleModalClose('registComplete');
|
||||
handleReset();
|
||||
window.location.reload();
|
||||
break;
|
||||
case "warning":
|
||||
setAlertMsg('');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const checkCondition = () => {
|
||||
return (
|
||||
resultData.start_price > 0
|
||||
&& resultData.auction_start_dt !== ''
|
||||
&& resultData.auction_end_dt !== ''
|
||||
&& resultData.resv_start_dt !== ''
|
||||
&& resultData.resv_end_dt !== ''
|
||||
&& resultData.land_id !== ''
|
||||
// && resultData.message_list?.every(data => data.content !== '')
|
||||
);
|
||||
};
|
||||
|
||||
const isView = (label) => {
|
||||
switch (label) {
|
||||
case "recv":
|
||||
return modalType === TYPE_REGISTRY || (modalType === TYPE_MODIFY && content?.status === landAuctionStatusType.wait);
|
||||
case "auction":
|
||||
case "price":
|
||||
case "message":
|
||||
return modalType === TYPE_REGISTRY || (modalType === TYPE_MODIFY &&(content?.status === landAuctionStatusType.resv_start || content?.status === landAuctionStatusType.wait));
|
||||
case "modify":
|
||||
return modalType === TYPE_MODIFY && (content?.status === landAuctionStatusType.resv_start || content?.status === landAuctionStatusType.wait);
|
||||
case "registry":
|
||||
return modalType === TYPE_REGISTRY
|
||||
default:
|
||||
return modalType === TYPE_MODIFY && (content?.status === landAuctionStatusType.stl_end
|
||||
|| content?.status === landAuctionStatusType.auction_start
|
||||
|| content?.status === landAuctionStatusType.auction_end
|
||||
|| content?.status === landAuctionStatusType.fail
|
||||
|| content?.status === landAuctionStatusType.cancel
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal min="760px" $view={detailView}>
|
||||
<Title $align="center">{isView('registry') ? "랜드 경매 등록" : isView('modify') ? "랜드 경매 수정" : "랜드 경매 상세"}</Title>
|
||||
<MessageWrapper>
|
||||
<FormRowGroup>
|
||||
<FormLabel>랜드선택</FormLabel>
|
||||
<SelectInput value={resultData.land_id} onChange={e => handleLand(e)} disabled={!isView('registry')} width="400px">
|
||||
{landData && landData.map((data, index) => (
|
||||
<option key={index} value={data.id}>
|
||||
{data.name}({data.id})
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</FormRowGroup>
|
||||
<FormRowGroup>
|
||||
<FormLabel>랜드 이름</FormLabel>
|
||||
<FormInput
|
||||
type="text"
|
||||
disabled={true}
|
||||
width='400px'
|
||||
value={resultData?.land_name}
|
||||
/>
|
||||
</FormRowGroup>
|
||||
<FormRowGroup>
|
||||
<FormLabel>랜드 크기</FormLabel>
|
||||
<FormInput
|
||||
type="text"
|
||||
disabled={true}
|
||||
width='200px'
|
||||
value={resultData?.land_size}
|
||||
/>
|
||||
<FormLabel>인스턴스 수</FormLabel>
|
||||
<FormInput
|
||||
type="text"
|
||||
disabled={true}
|
||||
width='200px'
|
||||
value={resultData?.land_socket}
|
||||
/>
|
||||
</FormRowGroup>
|
||||
|
||||
<FormRowGroup>
|
||||
<FormLabel>입찰 재화</FormLabel>
|
||||
<SelectInput value={resultData.currency_type} width='200px' disabled={true} >
|
||||
{CurrencyType.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<FormLabel>입찰시작가</FormLabel>
|
||||
<FormInput
|
||||
type="number"
|
||||
name="price"
|
||||
value={resultData.start_price}
|
||||
step={"0.01"}
|
||||
min={0}
|
||||
width='200px'
|
||||
disabled={!isView('price')}
|
||||
onChange={e => handleCount(e)}
|
||||
/>
|
||||
</FormRowGroup>
|
||||
<DateTimeRangePicker
|
||||
label="예약기간"
|
||||
startDate={resultData.resv_start_dt}
|
||||
endDate={resultData.resv_end_dt}
|
||||
onStartDateChange={handleReservationChange.start}
|
||||
onEndDateChange={handleReservationChange.end}
|
||||
pastDate={new Date()}
|
||||
disabled={!isView('recv')}
|
||||
startLabel="시작 일자"
|
||||
endLabel="종료 일자"
|
||||
reset={resetDateTime}
|
||||
setAlert={setAlertMsg}
|
||||
/>
|
||||
<DateTimeRangePicker
|
||||
label="경매기간"
|
||||
startDate={resultData.auction_start_dt}
|
||||
endDate={resultData.auction_end_dt}
|
||||
onStartDateChange={handleAuctionChange.start}
|
||||
onEndDateChange={handleAuctionChange.end}
|
||||
pastDate={new Date()}
|
||||
disabled={!isView('auction')}
|
||||
startLabel="시작 일자"
|
||||
endLabel="종료 일자"
|
||||
reset={resetDateTime}
|
||||
setAlert={setAlertMsg}
|
||||
/>
|
||||
|
||||
{/*<NoticeInputRow2>*/}
|
||||
{/* <InputLabel>*/}
|
||||
{/* 메세지 작성[경매 시작 5분전 공지 - 미구현]*/}
|
||||
{/* </InputLabel>*/}
|
||||
{/* <NoticeInputItem2>*/}
|
||||
{/* <InputLabel>언어</InputLabel>*/}
|
||||
{/* <SelectInput onChange={e => handleLanguage(e) } value={message_lang}>*/}
|
||||
{/* {languageType.map((data, index) => (*/}
|
||||
{/* <option key={index} value={data.value}>*/}
|
||||
{/* {data.name}*/}
|
||||
{/* </option>*/}
|
||||
{/* ))}*/}
|
||||
{/* </SelectInput>*/}
|
||||
{/* </NoticeInputItem2>*/}
|
||||
{/*</NoticeInputRow2>*/}
|
||||
{/*<BoxWrapper>*/}
|
||||
{/* {resultData.message_list.map(content => {*/}
|
||||
{/* return (*/}
|
||||
{/* <Fragment key={content.language}>*/}
|
||||
{/* {message_lang === content.language && (*/}
|
||||
{/* <FormTextAreaWrapper>*/}
|
||||
{/* <FormTextArea*/}
|
||||
{/* name="content"*/}
|
||||
{/* id={content.language}*/}
|
||||
{/* value={content.content}*/}
|
||||
{/* onChange={e => handleInputData(e)}*/}
|
||||
{/* maxLength={250}*/}
|
||||
{/* disabled={!isView('message')}*/}
|
||||
{/* />*/}
|
||||
{/* </FormTextAreaWrapper>*/}
|
||||
{/* )}*/}
|
||||
{/* </Fragment>*/}
|
||||
{/* );*/}
|
||||
{/* })}*/}
|
||||
{/*</BoxWrapper>*/}
|
||||
{!isView() && isNullValue && <SearchBarAlert $marginTop="25px" $align="right">{t('REQUIRED_VALUE_CHECK')}</SearchBarAlert>}
|
||||
</MessageWrapper>
|
||||
|
||||
<BtnWrapper $gap="10px" $marginTop="10px">
|
||||
<FormStatusBar>
|
||||
<FormStatusLabel>
|
||||
현재상태: {landAuctionStatus.find(data => data.value === content?.status)?.name || "등록"}
|
||||
</FormStatusLabel>
|
||||
<FormStatusWarning>
|
||||
{isView('registry') ? '' : t('LAND_AUCTION_MODAL_STATUS_WARNING')}
|
||||
</FormStatusWarning>
|
||||
</FormStatusBar>
|
||||
<FormButtonContainer $gap="5px">
|
||||
{isView() ?
|
||||
<Button
|
||||
text="확인"
|
||||
name="확인버튼"
|
||||
theme="line"
|
||||
handleClick={() => handleReset()}
|
||||
/>
|
||||
:
|
||||
<>
|
||||
<Button text="취소" theme="line" handleClick={() => handleSubmit('cancel')} />
|
||||
<Button
|
||||
type="submit"
|
||||
text={isView('modify') ? "수정" : "등록"}
|
||||
name="등록버튼"
|
||||
theme={
|
||||
checkCondition()
|
||||
? 'primary'
|
||||
: 'disable'
|
||||
}
|
||||
handleClick={() => handleSubmit('submit')}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
</FormButtonContainer>
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
|
||||
{/* 확인 모달 */}
|
||||
<DynamicModal
|
||||
modalType={modalTypes.confirmOkCancel}
|
||||
view={modalState.registConfirmModal}
|
||||
modalText={isView('modify') ? t('LAND_UPDATE_CONFIRM') : t('LAND_REGIST_CONFIRM')}
|
||||
handleSubmit={() => handleSubmit('registConfirm')}
|
||||
handleCancel={() => handleModalClose('registConfirm')}
|
||||
/>
|
||||
{/* 완료 모달 */}
|
||||
<DynamicModal
|
||||
modalType={modalTypes.completed}
|
||||
view={modalState.registCompleteModal}
|
||||
modalText={isView('modify') ? t('UPDATE_COMPLETED') : t('REGIST_COMPLTE')}
|
||||
handleSubmit={() => handleSubmit('registComplete')}
|
||||
/>
|
||||
{/* 취소 모달 */}
|
||||
<DynamicModal
|
||||
modalType={modalTypes.confirmOkCancel}
|
||||
view={modalState.cancelModal}
|
||||
modalText={t('CANCEL_CONFIRM')}
|
||||
handleCancel={() => handleModalClose('cancel')}
|
||||
handleSubmit={() => handleSubmit('cancelConfirm')}
|
||||
/>
|
||||
{/* 경고 모달 */}
|
||||
<DynamicModal
|
||||
modalType={modalTypes.completed}
|
||||
view={alertMsg ? 'view' : 'hidden'}
|
||||
modalText={alertMsg}
|
||||
handleSubmit={() => handleSubmit('warning')}
|
||||
/>
|
||||
{loading && <Loading/>}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const initData = {
|
||||
land_id: '',
|
||||
land_name: '',
|
||||
land_size: '',
|
||||
land_socket: '',
|
||||
currency_type: 'Calium',
|
||||
start_price: 0,
|
||||
resv_start_dt: '',
|
||||
resv_end_dt: '',
|
||||
auction_start_dt: '',
|
||||
auction_end_dt: '',
|
||||
message_list: [
|
||||
{ language: 'KO', content: '' },
|
||||
{ language: 'EN', content: '' },
|
||||
{ language: 'JA', content: '' },
|
||||
],
|
||||
}
|
||||
|
||||
export const initLandData = {
|
||||
land_id: 0,
|
||||
name: '',
|
||||
size: '',
|
||||
socket: '',
|
||||
desc: '',
|
||||
open: false,
|
||||
owner: ''
|
||||
}
|
||||
|
||||
export default LandAuctionModal;
|
||||
|
||||
979
src/components/ServiceManage/modal/MailDetailModal.js
Normal file
979
src/components/ServiceManage/modal/MailDetailModal.js
Normal file
@@ -0,0 +1,979 @@
|
||||
import { styled } from 'styled-components';
|
||||
import RadioInput from '../../common/input/Radio';
|
||||
import { useState, useEffect, Fragment } from 'react';
|
||||
import CheckBox from '../../common/input/CheckBox';
|
||||
|
||||
import { Title, SelectInput, BtnWrapper, TextInput, Label, InputLabel, DatePickerWrapper, Textarea, ModalText, ButtonClose, SearchBarAlert } from '../../../styles/Components';
|
||||
import Button from '../../common/button/Button';
|
||||
import Modal from '../../common/modal/Modal';
|
||||
|
||||
import IconDelete from '../../../assets/img/icon/icon-delete.png';
|
||||
import CloseIcon from '../../../assets/img/icon/icon-close.png';
|
||||
import DatePickerComponent from '../../common/Date/DatePickerComponent';
|
||||
import MailRegistUploadBtn from '../MailRegistUploadBtn';
|
||||
import { benItems, HourList, MinuteList, modalTypes, wellType } from '../../../assets/data';
|
||||
import { EventModify, MailModify } from '../../../apis';
|
||||
|
||||
import { authList } from '../../../store/authList';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { convertKTC, convertKTCDate, timeDiffMinute } from '../../../utils';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import DynamicModal from '../../common/modal/DynamicModal';
|
||||
|
||||
const MailDetailModal = ({ detailView, handleDetailView, content }) => {
|
||||
const userInfo = useRecoilValue(authList);
|
||||
const { t } = useTranslation();
|
||||
const token = sessionStorage.getItem('token');
|
||||
|
||||
const id = content && content.id;
|
||||
const onlyView = userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === 23);
|
||||
const updateAuth = userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === 23);
|
||||
|
||||
const [sendHour, setSendHour] = useState('00');
|
||||
const [sendMin, setSendMin] = useState('00');
|
||||
|
||||
const [item, setItem] = useState('');
|
||||
const [itemCount, setItemCount] = useState('');
|
||||
const [resource, setResource] = useState('19010001');
|
||||
const [resourceCount, setResourceCount] = useState('');
|
||||
|
||||
const [modifyModal, setModifyModal] = useState('hidden');
|
||||
const [completeModal, setCompleteModal] = useState('hidden');
|
||||
const [resultData, setResultData] = useState({});
|
||||
|
||||
const [modalState, setModalState] = useState({
|
||||
updateConfirmModal: 'hidden',
|
||||
updateCompleteModal: 'hidden',
|
||||
});
|
||||
|
||||
const [isNullValue, setIsNullValue] = useState(false);
|
||||
// 과거 판단
|
||||
const [isPast, setIsPast] = useState(false);
|
||||
const [isChanged, setIsChanged] = useState(false);
|
||||
const [isItemNullValue, setIsItemNullValue] = useState(false);
|
||||
|
||||
const [excelFile, setExcelFile] = useState(content.target ? content.target : '');
|
||||
const [excelName, setExcelName] = useState(null);
|
||||
const [downloadData, setDownLoadData] = useState(null);
|
||||
|
||||
const [btnValidation, setBtnValidation] = useState(false);
|
||||
const [updateMessage, setUpdateMessage] = useState('수정이 완료되었습니다.');
|
||||
const [alertMessage, setAlertMessage] = useState('');
|
||||
const [undefinedFile, setUndefinedFile] = useState(false);
|
||||
const [disabledBtn, setDisabledBtn] = useState(false); // 예약 발송 확인용
|
||||
const [alertMsg, setAlertMsg] = useState('');
|
||||
|
||||
const KOREAN_TIME = content && convertKTCDate(content.send_dt);
|
||||
|
||||
const initialData = {
|
||||
send_hour: content && KOREAN_TIME.getHours() < 10 ? '0' + content && KOREAN_TIME.getHours() : content && KOREAN_TIME.getHours(),
|
||||
send_min: content && KOREAN_TIME.getMinutes() < 10 ? '0' + content && KOREAN_TIME.getMinutes() : content && KOREAN_TIME.getMinutes(),
|
||||
send_status: content && content.send_status,
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
document.querySelector('#fileinput').value = '';
|
||||
|
||||
setResultData({
|
||||
is_reserve: content && content.is_reserve,
|
||||
send_dt: content && KOREAN_TIME,
|
||||
mail_type: content && content.mail_type,
|
||||
receive_type: content && content.receive_type,
|
||||
user_type: content && content.user_type,
|
||||
mail_list: content && content.mail_list,
|
||||
item_list: content && content.item_list,
|
||||
guid: content && content.target,
|
||||
});
|
||||
|
||||
content && content.mail_list.length === 1 && setBtnValidation(true);
|
||||
content && content.is_reserve === false && setBtnValidation(true);
|
||||
|
||||
setItem('');
|
||||
KOREAN_TIME < new Date ? setIsPast(true) : setIsPast(false);
|
||||
setExcelFile(content && content.target);
|
||||
setDownLoadData(content && content.target);
|
||||
setDisabledBtn(content && content.is_reserve && false);
|
||||
|
||||
// 복수 or 단일일 때 content.target을 exel name 으로 지정
|
||||
(content && content.receive_type === 'MULTIPLE') ?
|
||||
setExcelName(content && content.target)
|
||||
: setExcelName('')
|
||||
|
||||
}, [content]);
|
||||
|
||||
// console.log('downloadData', downloadData);
|
||||
// console.log('isPast', isPast);
|
||||
// console.log("guid", resultData.guid)
|
||||
// console.log("메일 형식", content && content.receive_type," 엑셀네임", excelName)
|
||||
|
||||
// 아이템 수량 숫자 체크
|
||||
const handleItemCount = e => {
|
||||
if (e.target.value === '0' || e.target.value === '-0') {
|
||||
setItemCount('1');
|
||||
e.target.value = '1';
|
||||
} else if (e.target.value < 0) {
|
||||
let plusNum = Math.abs(e.target.value);
|
||||
setItemCount(plusNum);
|
||||
} else {
|
||||
setItemCount(e.target.value);
|
||||
}
|
||||
};
|
||||
|
||||
// 아이템 추가
|
||||
const handleItemList = () => {
|
||||
if(benItems.includes(item)){
|
||||
setAlertMsg(t('MAIL_ITEM_ADD_BEN'))
|
||||
return;
|
||||
}
|
||||
item.length === 0 || itemCount.length === 0 ? setIsItemNullValue(true) : setIsItemNullValue(false);
|
||||
|
||||
if (item.length === '' || itemCount.length === 0 || itemCount <= 0) {
|
||||
setIsItemNullValue(true);
|
||||
} else if (item.length !== 0) {
|
||||
setIsItemNullValue(false);
|
||||
setIsChanged(true);
|
||||
|
||||
const newItem = { item: item, item_cnt: itemCount };
|
||||
resultData.item_list.push(newItem);
|
||||
|
||||
setItem('');
|
||||
setItemCount('');
|
||||
}
|
||||
};
|
||||
|
||||
// 아이템 삭제
|
||||
const onItemRemove = id => {
|
||||
let filterList = resultData.item_list && resultData.item_list.filter(item => item !== resultData.item_list[id]);
|
||||
setIsChanged(true);
|
||||
|
||||
// console.log('filterList', filterList);
|
||||
|
||||
setResultData({ ...resultData, item_list: filterList });
|
||||
};
|
||||
|
||||
// 자원 수량 숫자 체크
|
||||
const handleResourceCount = e => {
|
||||
if (e.target.value === '0' || e.target.value === '-0') {
|
||||
setResourceCount('1');
|
||||
e.target.value = '1';
|
||||
} else if (e.target.value < 0) {
|
||||
let plusNum = Math.abs(e.target.value);
|
||||
setResourceCount(plusNum);
|
||||
} else {
|
||||
setResourceCount(e.target.value);
|
||||
}
|
||||
};
|
||||
|
||||
// 자원 추가
|
||||
const handleResourceList = () => {
|
||||
resourceCount.length === 0 ? setIsItemNullValue(true) : setIsItemNullValue(false);
|
||||
|
||||
if (resourceCount.length === 0 || resourceCount <= 0) {
|
||||
setIsItemNullValue(true);
|
||||
} else {
|
||||
setIsItemNullValue(false);
|
||||
const name = wellType.find(well => well.value === resource).name;
|
||||
const newItem = { item: resource, item_cnt: resourceCount, item_name: name };
|
||||
|
||||
resultData.item_list.push(newItem);
|
||||
|
||||
setResourceCount('');
|
||||
}
|
||||
};
|
||||
|
||||
// 입력창 삭제
|
||||
const onLangDelete = language => {
|
||||
let filterList = resultData.mail_list && resultData.mail_list.filter(el => el.language !== language);
|
||||
|
||||
if (filterList.length === 1) setBtnValidation(true);
|
||||
|
||||
setIsChanged(true);
|
||||
setResultData({ ...resultData, mail_list: filterList });
|
||||
};
|
||||
|
||||
// 발송 날짜 세팅 로직
|
||||
const handleSelectedDate = data => {
|
||||
const sendDate = new Date(data);
|
||||
|
||||
const resultSendData = new Date(sendDate.getFullYear(), sendDate.getMonth(), sendDate.getDate(), sendHour, sendMin);
|
||||
|
||||
setIsChanged(true);
|
||||
setResultData({ ...resultData, send_dt: resultSendData });
|
||||
};
|
||||
|
||||
// 발송 시간 세팅 로직
|
||||
const handleSendTime = e => {
|
||||
if (e.target.id === 'hour') setSendHour(e.target.value);
|
||||
else if (e.target.id === 'min') setSendMin(e.target.value);
|
||||
|
||||
const sendDate = new Date(resultData.send_dt);
|
||||
const result = new Date(sendDate.getFullYear(), sendDate.getMonth(), sendDate.getDate(), e.target.id === 'hour' ? e.target.value : sendHour, e.target.id === 'min' ? e.target.value : sendMin);
|
||||
|
||||
setIsChanged(true);
|
||||
setResultData({ ...resultData, send_dt: result });
|
||||
};
|
||||
|
||||
// 우편 상세 정보 수정
|
||||
const handleModifyModal = () => {
|
||||
if (
|
||||
resultData.mail_list.map(data => data.content === '' || data.title === '').includes(true) ||
|
||||
(resultData.receive_type === 'MULTIPLE' ? excelFile === null : resultData.guid === '') ||
|
||||
resultData.send_dt.length === 0 ||
|
||||
resultData.mail_type === 'SELECT' ||
|
||||
isChanged === false ||
|
||||
alertMessage
|
||||
) {
|
||||
isChanged === true && setIsNullValue(true);
|
||||
} else {
|
||||
// 복수로 수정하거나 복수로 등록할 때 항상 excel name을 넘겨줘야합니다.
|
||||
content && content.receive_type === 'MULTIPLE' ? setResultData({ ...resultData, file_name: excelName === null ? downloadData : excelName }) : setExcelName('');
|
||||
|
||||
// setExcelName(content && content.target)
|
||||
|
||||
setIsNullValue(false);
|
||||
if (modifyModal === 'hidden') {
|
||||
setModifyModal('view');
|
||||
} else {
|
||||
setModifyModal('hidden');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 상세 정보 단일 체크
|
||||
const handleSingleBtn = () => {
|
||||
if (content && content.is_reserve === true && !onlyView && !isPast) {
|
||||
setResultData({ ...resultData, guid: '' });
|
||||
delete resultData.file_name;
|
||||
|
||||
document.querySelector('#fileinput').value = '';
|
||||
|
||||
setExcelFile(null);
|
||||
setIsChanged(true);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// 상세 정보 복수 체크
|
||||
const handleMultiBtn = () => {
|
||||
if (content && content.is_reserve === true) {
|
||||
delete resultData.guid;
|
||||
}
|
||||
setIsChanged(true);
|
||||
};
|
||||
|
||||
// 완료 모달창
|
||||
const handleCompleteModal = () => {
|
||||
if (completeModal === 'hidden') {
|
||||
setCompleteModal('view');
|
||||
} else {
|
||||
setCompleteModal('hidden');
|
||||
|
||||
window.location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
// 메일 수정 버튼
|
||||
const handleModifyMail = () => {
|
||||
|
||||
if(resultData.receive_type === 'MULTIPLE') {
|
||||
delete resultData.guid
|
||||
|
||||
MailModify(token, id, resultData);
|
||||
} else {
|
||||
MailModify(token, id, resultData);
|
||||
}
|
||||
|
||||
handleCompleteModal();
|
||||
handleModifyModal();
|
||||
};
|
||||
|
||||
// 확인 버튼 후 다 초기화
|
||||
const handleReset = () => {
|
||||
setBtnValidation(false);
|
||||
setIsNullValue(false);
|
||||
setIsChanged(false);
|
||||
setUndefinedFile(false);
|
||||
setIsItemNullValue(false);
|
||||
setAlertMessage('');
|
||||
setExcelFile(null);
|
||||
setExcelName(null);
|
||||
};
|
||||
|
||||
// 상세 페이지에서 파일 삭제
|
||||
const handleDetailDelete = e => {
|
||||
e.preventDefault();
|
||||
|
||||
if (content && content.is_reserve === true) {
|
||||
setDownLoadData(undefined);
|
||||
setUndefinedFile(true);
|
||||
setResultData({ ...resultData, guid: '' });
|
||||
delete resultData.file_name;
|
||||
|
||||
document.querySelector('#fileinput').value = '';
|
||||
|
||||
setExcelFile(null);
|
||||
}
|
||||
|
||||
setIsChanged(true);
|
||||
};
|
||||
|
||||
const handleModalView = (type) => {
|
||||
setModalState((prevState) => ({
|
||||
...prevState,
|
||||
[`${type}Modal`]: 'view',
|
||||
}));
|
||||
}
|
||||
|
||||
const handleModalClose = (type) => {
|
||||
setModalState((prevState) => ({
|
||||
...prevState,
|
||||
[`${type}Modal`]: 'hidden',
|
||||
}));
|
||||
}
|
||||
|
||||
const handleSubmit = async (type, param = null) => {
|
||||
switch (type) {
|
||||
case "submit":
|
||||
// if (!conditionCheck()) return;
|
||||
|
||||
handleModalView('updateConfirm');
|
||||
break;
|
||||
case "updateConfirm":
|
||||
const timeDiff = timeDiffMinute(resultData.start_dt, (new Date))
|
||||
// 이벤트 시작 30분전이나 이미 SystemMail이 add된 상태에서는 수정할 수 없다.
|
||||
if(content.add_flag || timeDiff <= 30){
|
||||
setAlertMsg(t('EVENT_TIME_LIMIT_UPDATE'));
|
||||
handleModalClose('updateConfirm');
|
||||
return;
|
||||
}
|
||||
EventModify(token, id, resultData);
|
||||
handleModalClose('updateConfirm');
|
||||
handleModalView('updateComplete');
|
||||
break;
|
||||
case "updateComplete":
|
||||
handleModalClose('updateComplete');
|
||||
window.location.reload();
|
||||
break;
|
||||
case "warning":
|
||||
setAlertMsg('');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal min="960px" $view={detailView}>
|
||||
<Title $align="center">우편 상세 정보</Title>
|
||||
|
||||
<RegistInfo>
|
||||
<span>등록자 : {content && content.create_by}</span>
|
||||
<span>등록일 : {content && convertKTC(content.create_dt, false)}</span>
|
||||
{content && typeof content.update_by !== 'undefined' && (
|
||||
<>
|
||||
<span>수정자 : {content && content.update_by}</span>
|
||||
<span>수정일 : {content && convertKTC(content.update_dt, false)}</span>
|
||||
</>
|
||||
)}
|
||||
</RegistInfo>
|
||||
<ModalWrapper>
|
||||
<RegistGroup>
|
||||
<InputRow>
|
||||
<CheckBox
|
||||
label="예약 발송"
|
||||
id="reserve"
|
||||
checked={resultData && resultData.is_reserve}
|
||||
setData={e => {
|
||||
setResultData({ ...resultData, is_reserve: e.target.checked });
|
||||
setDisabledBtn(e.target.checked);
|
||||
setIsChanged(true);
|
||||
}}
|
||||
disabled={(content && content.is_reserve === false) || onlyView}
|
||||
/>
|
||||
{content && content.is_reserve === false ? (
|
||||
<></>
|
||||
) : (
|
||||
content &&
|
||||
content.is_reserve === true &&
|
||||
resultData.is_reserve === true && (
|
||||
<InputItem>
|
||||
<InputLabel>발송 시간</InputLabel>
|
||||
<InputGroup>
|
||||
<DatePickerWrapper>
|
||||
<DatePickerComponent
|
||||
readOnly={(content && content.is_reserve === false) || onlyView || isPast}
|
||||
name={initialData.send_dt}
|
||||
selectedDate={resultData ? resultData.send_dt : initialData.send_dt}
|
||||
handleSelectedDate={data => handleSelectedDate(data)}
|
||||
pastDate={new Date()}
|
||||
/>
|
||||
</DatePickerWrapper>
|
||||
<SelectInput
|
||||
onChange={e => handleSendTime(e)}
|
||||
id="hour"
|
||||
disabled={(content && content.is_reserve === false) || onlyView || isPast}
|
||||
value={
|
||||
resultData && String(new Date(resultData.send_dt).getHours()) < 10
|
||||
? '0' + String(new Date(resultData.send_dt).getHours())
|
||||
: resultData && String(new Date(resultData.send_dt).getHours())
|
||||
}>
|
||||
{HourList.map(hour => (
|
||||
<option
|
||||
value={hour}
|
||||
key={hour}
|
||||
// selected={
|
||||
// resultData && String(new Date(resultData.send_dt).getHours()) < 10
|
||||
// ? '0' + String(new Date(resultData.send_dt).getHours()) === hour
|
||||
// : resultData && String(new Date(resultData.send_dt).getHours()) === hour
|
||||
// ? 'selected'
|
||||
// : ''
|
||||
// }
|
||||
>
|
||||
{hour}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<SelectInput
|
||||
onChange={e => {
|
||||
handleSendTime(e);
|
||||
setIsChanged(true);
|
||||
}}
|
||||
id="min"
|
||||
disabled={(content && content.is_reserve === false) || onlyView || isPast}
|
||||
value={
|
||||
resultData && String(new Date(resultData.send_dt).getMinutes()) < 10
|
||||
? '0' + String(new Date(resultData.send_dt).getMinutes())
|
||||
: resultData && String(new Date(resultData.send_dt).getMinutes())
|
||||
}>
|
||||
{MinuteList.map(min => (
|
||||
<option
|
||||
value={min}
|
||||
key={min}
|
||||
// selected={
|
||||
// resultData && String(new Date(resultData.send_dt).getMinutes()) < 10
|
||||
// ? '0' + String(new Date(resultData.send_dt).getMinutes()) === min
|
||||
// : resultData && String(new Date(resultData.send_dt).getMinutes()) === min
|
||||
// ? 'selected'
|
||||
// : ''
|
||||
// }
|
||||
>
|
||||
{min}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</InputGroup>
|
||||
</InputItem>
|
||||
)
|
||||
)}
|
||||
<InputItem>
|
||||
<InputLabel>우편 타입</InputLabel>
|
||||
<SelectInput
|
||||
onChange={e => {
|
||||
setResultData({ ...resultData, mail_type: e.target.value });
|
||||
setIsChanged(true);
|
||||
}}
|
||||
value={content && resultData.mail_type}
|
||||
disabled={(content && content.is_reserve === false) || onlyView || isPast}>
|
||||
<option value="SELECT">타입 선택</option>
|
||||
<option value="SYSTEM_GUID">시스템 안내</option>
|
||||
<option value="INSPECTION_COMPENSATION">점검 보상</option>
|
||||
<option value="RECOVER_COMPENSATION">복구 보상</option>
|
||||
<option value="EVENT_COMPENSATION">이벤트 보상</option>
|
||||
</SelectInput>
|
||||
</InputItem>
|
||||
<InputItem>
|
||||
<InputLabel>발송상태</InputLabel>
|
||||
<div>
|
||||
{content && initialData.send_status === 'WAIT' && <MailState>대기</MailState>}
|
||||
{content && initialData.send_status === 'FINISH' && <MailState result="success">완료</MailState>}
|
||||
{content && initialData.send_status === 'FAIL' && <MailState result="fail">실패</MailState>}
|
||||
</div>
|
||||
</InputItem>
|
||||
</InputRow>
|
||||
<MailReceiver>
|
||||
<InputItem>
|
||||
<InputLabel>수신대상</InputLabel>
|
||||
<InputItem>
|
||||
<SelectInput onChange={e => setResultData({ ...resultData, user_type: e.target.value })} value={resultData.user_type} disabled={(content && content.is_reserve === false) || onlyView || isPast}>
|
||||
<option value="GUID">GUID</option>
|
||||
<option value="NICKNAME">아바타명</option>
|
||||
</SelectInput>
|
||||
</InputItem>
|
||||
<div>
|
||||
<InputGroup>
|
||||
<RadioInput
|
||||
label="단일"
|
||||
id="SINGLE"
|
||||
name="receiver"
|
||||
value="SINGLE"
|
||||
disabled={(content && content.is_reserve === false) || onlyView || isPast}
|
||||
fontWeight="600"
|
||||
checked={resultData.receive_type === 'SINGLE'}
|
||||
handleChange={e => {
|
||||
setResultData({ ...resultData, receive_type: e.target.id });
|
||||
setIsChanged(true);
|
||||
}}
|
||||
handleClick={handleSingleBtn}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder={resultData.user_type === "GUID" ? "GUID 입력" : resultData.user_type === "NICKNAME" ? "아바타명 입력" : "이메일 입력"}
|
||||
disabled={resultData.receive_type !== 'SINGLE' || (content && content.is_reserve === false) || onlyView || isPast}
|
||||
// defaultValue={resultData.receive_type === 'SINGLE' && resultData.guid !== '' ? content.target : ''}
|
||||
value={resultData.receive_type === 'SINGLE' && resultData.guid !== '' ? resultData.guid : ''}
|
||||
onChange={e => {
|
||||
let list = [...resultData.guid];
|
||||
list = e.target.value;
|
||||
setResultData({ ...resultData, guid: list });
|
||||
setIsChanged(true);
|
||||
}}
|
||||
/>
|
||||
</InputGroup>
|
||||
{content && resultData.receive_type === 'MULTIPLE' && typeof resultData.guid !== 'undefined' ? (
|
||||
<InputGroup>
|
||||
<RadioInput
|
||||
label="복수"
|
||||
id="MULTIPLE"
|
||||
name="receiver"
|
||||
value="MULTIPLE"
|
||||
fontWeight="600"
|
||||
checked={content && resultData.receive_type === 'MULTIPLE'}
|
||||
disabled
|
||||
/>
|
||||
<MailRegistUploadBtn
|
||||
disabled={resultData.receive_type !== 'MULTIPLE'}
|
||||
downloadData={downloadData}
|
||||
setResultData={setResultData}
|
||||
resultData={resultData}
|
||||
handleDetailDelete={handleDetailDelete}
|
||||
setExcelFile={setExcelFile}
|
||||
alertMessage={alertMessage}
|
||||
setAlertMessage={setAlertMessage}
|
||||
undefinedFile={undefinedFile}
|
||||
disabledBtn={disabledBtn}
|
||||
excelName={excelName}
|
||||
setExcelName={setExcelName}
|
||||
status={initialData.send_status}
|
||||
/>
|
||||
</InputGroup>
|
||||
) : (
|
||||
<InputGroup>
|
||||
<RadioInput
|
||||
label="복수"
|
||||
id="MULTIPLE"
|
||||
name="receiver"
|
||||
value="MULTIPLE"
|
||||
fontWeight="600"
|
||||
disabled={(content && content.is_reserve === false) || onlyView || isPast}
|
||||
handleChange={e => setResultData({ ...resultData, receive_type: e.target.id })}
|
||||
handleClick={handleMultiBtn}
|
||||
/>
|
||||
<MailRegistUploadBtn
|
||||
setUpdateMessage={setUpdateMessage}
|
||||
disabled={resultData.receive_type !== 'MULTIPLE'}
|
||||
setResultData={setResultData}
|
||||
resultData={resultData}
|
||||
excelFile={excelFile}
|
||||
setExcelFile={setExcelFile}
|
||||
alertMessage={alertMessage}
|
||||
setAlertMessage={setAlertMessage}
|
||||
undefinedFile={undefinedFile}
|
||||
handleDetailDelete={handleDetailDelete}
|
||||
disabledBtn={disabledBtn}
|
||||
excelName={excelName}
|
||||
setExcelName={setExcelName}
|
||||
status={initialData.send_status}
|
||||
/>
|
||||
</InputGroup>
|
||||
)}
|
||||
</div>
|
||||
</InputItem>
|
||||
</MailReceiver>
|
||||
</RegistGroup>
|
||||
{resultData.mail_list &&
|
||||
resultData.mail_list.map(data => {
|
||||
return (
|
||||
<Fragment key={data.language}>
|
||||
<MailRegistBox>
|
||||
<LangArea>
|
||||
언어 : {data.language}
|
||||
{btnValidation === false ? (
|
||||
<BtnClose
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
onLangDelete(data.language);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<BtnClose opacity="10%" />
|
||||
)}
|
||||
</LangArea>
|
||||
<MailRegistTable>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th width="120">
|
||||
<Label>제목</Label>
|
||||
</th>
|
||||
<td>
|
||||
<InputItem>
|
||||
<TextInput
|
||||
placeholder="우편 제목 입력"
|
||||
maxLength="30"
|
||||
id={data.language}
|
||||
value={data.title}
|
||||
readOnly={(content && content.is_reserve === false) || onlyView || isPast}
|
||||
onChange={e => {
|
||||
let list = [...resultData.mail_list];
|
||||
let findIndex = resultData.mail_list && resultData.mail_list.findIndex(item => item.language === e.target.id);
|
||||
list[findIndex].title = e.target.value.trimStart();
|
||||
|
||||
setResultData({ ...resultData, mail_list: list });
|
||||
setIsChanged(true);
|
||||
}}
|
||||
/>
|
||||
</InputItem>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
<Label>내용</Label>
|
||||
</th>
|
||||
<td>
|
||||
<Textarea
|
||||
value={data.content}
|
||||
readOnly={(content && content.is_reserve === false) || onlyView || isPast}
|
||||
id={data.language}
|
||||
onChange={e => {
|
||||
if (e.target.value.length > 2000) {
|
||||
return;
|
||||
}
|
||||
let list = [...resultData.mail_list];
|
||||
let findIndex = resultData.mail_list && resultData.mail_list.findIndex(item => item.language === e.target.id);
|
||||
list[findIndex].content = e.target.value.trimStart();
|
||||
|
||||
setResultData({ ...resultData, mail_list: list });
|
||||
setIsChanged(true);
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</MailRegistTable>
|
||||
</MailRegistBox>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
<MailRegistBox>
|
||||
<MailRegistTable>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th width="120">
|
||||
<Label>아이템 첨부</Label>
|
||||
</th>
|
||||
<td>
|
||||
<InputItem>
|
||||
<TextInput
|
||||
placeholder="Item Meta id 입력"
|
||||
value={item}
|
||||
onChange={e => {
|
||||
let list = [];
|
||||
list = e.target.value.trimStart();
|
||||
setItem(list);
|
||||
}}
|
||||
disabled={(content && content.is_reserve === false) || onlyView || isPast}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder="수량"
|
||||
value={itemCount}
|
||||
type="number"
|
||||
onChange={e => handleItemCount(e)}
|
||||
width="90px"
|
||||
disabled={(content && content.is_reserve === false) || onlyView || isPast}
|
||||
/>
|
||||
<Button
|
||||
text="추가"
|
||||
theme={itemCount.length === 0 || item.length === 0 ? 'disable' : 'search'}
|
||||
handleClick={handleItemList}
|
||||
errorMessage={(content && content.is_reserve === false) || onlyView || isPast}
|
||||
/>
|
||||
</InputItem>
|
||||
{/* {isItemNullValue && <SearchBarAlert $marginTop="15px">필수값을 입력해주세요.</SearchBarAlert>}
|
||||
<div>
|
||||
{resultData.item_list && (
|
||||
<ItemList>
|
||||
{content &&
|
||||
resultData.item_list.map((data, index) => {
|
||||
return (
|
||||
<Item key={index}>
|
||||
<span>
|
||||
{data.item}({data.item_cnt})
|
||||
</span>
|
||||
{(content && content.is_reserve === false) ||
|
||||
(updateAuth && <BtnDelete onClick={() => onItemRemove(index)}></BtnDelete>) ||
|
||||
isPast
|
||||
}
|
||||
</Item>
|
||||
);
|
||||
})}
|
||||
</ItemList>
|
||||
)}
|
||||
</div> */}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th width="120">
|
||||
<Label>자원 첨부</Label>
|
||||
</th>
|
||||
<td>
|
||||
<InputItem>
|
||||
<SelectInput onChange={e => setResource(e.target.value)} value={resource} disabled={(content && content.is_reserve === false) || onlyView || isPast}>
|
||||
{wellType.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<TextInput placeholder="수량" type="number" value={resourceCount} disabled={(content && content.is_reserve === false) || onlyView || isPast} onChange={e => handleResourceCount(e)} width="200px" />
|
||||
<Button text="추가" theme={resourceCount.length === 0 || resource.length === 0 ? 'disable' : 'search'} handleClick={handleResourceList} width="100px" height="35px" errorMessage={(content && content.is_reserve === false) || onlyView || isPast} />
|
||||
</InputItem>
|
||||
{isItemNullValue && <SearchBarAlert $marginTop="15px">필수값을 입력해주세요.</SearchBarAlert>}
|
||||
|
||||
<div>
|
||||
{resultData.item_list && (
|
||||
<ItemList>
|
||||
{resultData.item_list.map((data, index) => {
|
||||
return (
|
||||
<Item key={index}>
|
||||
<span>
|
||||
{data.item_name}[{data.item}] ({data.item_cnt})
|
||||
</span>
|
||||
{(content && content.is_reserve === true) || onlyView || isPast && <BtnDelete onClick={() => onItemRemove(index)}></BtnDelete>}
|
||||
</Item>
|
||||
);
|
||||
})}
|
||||
</ItemList>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</MailRegistTable>
|
||||
</MailRegistBox>
|
||||
</ModalWrapper>
|
||||
<BtnWrapper $justify="flex-end" $gap="10px" $paddingTop="20px">
|
||||
<Button
|
||||
text="확인"
|
||||
theme="line"
|
||||
name="확인버튼"
|
||||
handleClick={() => {
|
||||
handleDetailView();
|
||||
handleReset();
|
||||
}}
|
||||
/>
|
||||
{(updateAuth && content && content.is_reserve === true) && !isPast && (
|
||||
<Button
|
||||
type="submit"
|
||||
text="수정"
|
||||
id="수정버튼"
|
||||
theme={
|
||||
(content && content.mail_list.map(data => data.content === '' || data.title === '').includes(true)) ||
|
||||
(resultData.receive_type === 'MULTIPLE' ? excelFile === null : resultData.guid === '') ||
|
||||
resultData.mail_type === 'SELECT' ||
|
||||
isChanged === false ||
|
||||
alertMessage.length > 1
|
||||
? 'disable'
|
||||
: 'primary'
|
||||
}
|
||||
handleClick={() => {
|
||||
handleModifyModal();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
{/* 확인 모달 */}
|
||||
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={modifyModal}>
|
||||
<BtnWrapper $justify="flex-end">
|
||||
<ButtonClose onClick={handleModifyModal} />
|
||||
</BtnWrapper>
|
||||
<ModalText $align="center">
|
||||
우편 정보 수정사항을 <br />
|
||||
저장하시겠습니까?
|
||||
</ModalText>
|
||||
<BtnWrapper $gap="10px">
|
||||
<Button text="취소" theme="line" size="large" width="100%" handleClick={handleModifyModal} />
|
||||
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleModifyMail} />
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
{/* 완료 모달 */}
|
||||
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={completeModal}>
|
||||
<BtnWrapper $justify="flex-end">
|
||||
<ButtonClose onClick={handleCompleteModal} />
|
||||
</BtnWrapper>
|
||||
<ModalText $align="center">수정이 완료되었습니다. </ModalText>
|
||||
<BtnWrapper $gap="10px">
|
||||
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleCompleteModal} />
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
{/* 경고 모달 */}
|
||||
<DynamicModal
|
||||
modalType={modalTypes.completed}
|
||||
view={alertMsg ? 'view' : 'hidden'}
|
||||
modalText={alertMsg}
|
||||
handleSubmit={() => handleSubmit('warning')}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MailDetailModal;
|
||||
|
||||
const InputItem = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
${TextInput},${SelectInput} {
|
||||
height: 35px;
|
||||
font-size: 14px;
|
||||
}
|
||||
${TextInput} {
|
||||
padding: 0 15px;
|
||||
}
|
||||
${SelectInput} {
|
||||
width: max-content;
|
||||
}
|
||||
`;
|
||||
|
||||
const ModalWrapper = styled.div`
|
||||
max-height: 70vh;
|
||||
padding-bottom: 5px;
|
||||
overflow: auto;
|
||||
&::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #666666;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #d9d9d9;
|
||||
}
|
||||
`;
|
||||
|
||||
const RegistInfo = styled.div`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 50px;
|
||||
font-size: 14px;
|
||||
margin-bottom: 10px;
|
||||
`;
|
||||
|
||||
const BtnClose = styled.button`
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: url(${CloseIcon}) 50% 50% no-repeat;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translate(0, -50%);
|
||||
right: 20px;
|
||||
opacity: ${props => props.opacity};
|
||||
`;
|
||||
|
||||
const LangArea = styled.div`
|
||||
background: #f9f9f9;
|
||||
padding: 10px 20px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const MailRegistBox = styled.div`
|
||||
margin-bottom: 20px;
|
||||
border-top: 1px solid #999;
|
||||
border-bottom: 1px solid #999;
|
||||
`;
|
||||
|
||||
const MailRegistTable = styled.table`
|
||||
th,
|
||||
td {
|
||||
padding: 15px 0;
|
||||
}
|
||||
td {
|
||||
${TextInput} {
|
||||
max-width: 600px;
|
||||
}
|
||||
${Textarea} {
|
||||
width: 100%;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 5px;
|
||||
height: 150px;
|
||||
padding: 15px;
|
||||
&:focus {
|
||||
border: 1px solid #2c2c2c;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const RegistGroup = styled.div`
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-flow: column;
|
||||
gap: 20px;
|
||||
padding: 20px;
|
||||
border-top: 1px solid #000;
|
||||
border-bottom: 1px solid #000;
|
||||
font-size: 14px;
|
||||
margin-bottom: 40px;
|
||||
`;
|
||||
|
||||
const InputRow = styled.div`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px 50px;
|
||||
`;
|
||||
|
||||
const InputGroup = styled.div`
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const MailReceiver = styled.div`
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
${InputItem} {
|
||||
align-items: flex-start;
|
||||
}
|
||||
${InputLabel} {
|
||||
line-height: 35px;
|
||||
}
|
||||
${TextInput} {
|
||||
margin-left: 20px;
|
||||
width: 400px;
|
||||
}
|
||||
${InputGroup} {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
`;
|
||||
|
||||
const MailState = styled.span`
|
||||
font-weight: 600;
|
||||
color: ${props => (props.result === 'success' ? '#08994B' : props.result === 'fail' ? '#ff0000' : '#2c2c2c')};
|
||||
`;
|
||||
|
||||
const ItemList = styled.ul`
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
padding: 10px 20px;
|
||||
flex-wrap: wrap;
|
||||
`;
|
||||
|
||||
const Item = styled.li`
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const BtnDelete = styled.button`
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: url(${IconDelete}) 50% 50% no-repeat;
|
||||
`;
|
||||
349
src/components/ServiceManage/modal/ReportListAnswerModal.js
Normal file
349
src/components/ServiceManage/modal/ReportListAnswerModal.js
Normal file
@@ -0,0 +1,349 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { styled } from 'styled-components';
|
||||
import { Title, BtnWrapper, TextInput, Label, Textarea, InputItem, ModalText, SearchBarAlert } from '../../../styles/Components';
|
||||
|
||||
import { RepostReplyMessage } from '../../../apis/Report';
|
||||
|
||||
import Modal from '../../common/modal/Modal';
|
||||
import Button from '../../common/button/Button';
|
||||
import CloseIcon from '../../../assets/img/icon/icon-close.png';
|
||||
|
||||
const ReportListAnswerModal = ({ answerView, setAnswerView, detailData, replyData, pkId, skId }) => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
const [replied, setReplied] = useState(false);
|
||||
|
||||
// 버튼 validation -> true 일 때 disable 처리
|
||||
const [isNullValue, setIsNullValue] = useState(false);
|
||||
|
||||
// 발송 API 전달용 resultData 세팅
|
||||
const [resultData, setResultData] = useState({
|
||||
pk: '',
|
||||
sk: '',
|
||||
title: '',
|
||||
detail: '',
|
||||
reporter_nickname: '',
|
||||
});
|
||||
|
||||
// 모달 , 모달 메세지용 state
|
||||
const [modalStep, setModalStep] = useState(false);
|
||||
const [modalText, setModalText] = useState('');
|
||||
const [confirmModal, setConfirmModal] = useState('hidden');
|
||||
const [completeModal, setCompleteModal] = useState('hidden');
|
||||
|
||||
// 답변 조회 DATA 세팅용 resultData
|
||||
useEffect(() => {
|
||||
// 답변 조회 api에 호출할 pk, sk 세팅용
|
||||
const pk = encodeURI(pkId);
|
||||
const sk = encodeURI(skId);
|
||||
|
||||
setResultData({
|
||||
pk: pkId && pk,
|
||||
sk: skId && sk,
|
||||
title: replyData ? replyData.title : '',
|
||||
detail: replyData ? replyData.detail : '',
|
||||
reporter_nickname: detailData && detailData.reporter_nickname,
|
||||
});
|
||||
|
||||
// 답변 유무 확인용
|
||||
replyData ? setReplied(true) : setReplied(false);
|
||||
}, [detailData, replyData]);
|
||||
// console.log('신고답변작성 : ', resultData);
|
||||
|
||||
// Button Validation용 if 문
|
||||
const btnVali = resultData.title && resultData.title.length > 0 && resultData.detail && resultData.detail.length > 0;
|
||||
|
||||
const titleLeng = resultData.title && resultData.title.length;
|
||||
const detailLeng = resultData.detail && resultData.detail.length;
|
||||
|
||||
// Button Validation (2) - 에러 메세지
|
||||
const handleNullVali = e => {
|
||||
if (btnVali) {
|
||||
setIsNullValue(false);
|
||||
} else {
|
||||
setIsNullValue(true);
|
||||
}
|
||||
};
|
||||
|
||||
// 텍스트 변경 함수
|
||||
const handleNoticeText = e => {
|
||||
let btnName = e.target.name;
|
||||
|
||||
if (btnName === '취소버튼') {
|
||||
setModalText('신고 답변을 취소하시겠습니까? \n 취소 시 작성된 모든 정보가 사라집니다.');
|
||||
setModalStep(false);
|
||||
} else {
|
||||
setModalText('신고 답변 우편을 발송하시겠습니까?');
|
||||
setModalStep(true);
|
||||
}
|
||||
};
|
||||
|
||||
// 확인 모달
|
||||
const handleConfirmModal = e => {
|
||||
if (confirmModal === 'hidden') {
|
||||
setConfirmModal('view');
|
||||
} else {
|
||||
setConfirmModal('hidden');
|
||||
}
|
||||
};
|
||||
|
||||
// 완료 모달창
|
||||
const handleCompleteModal = () => {
|
||||
if (completeModal === 'hidden') {
|
||||
setCompleteModal('view');
|
||||
} else {
|
||||
setCompleteModal('hidden');
|
||||
window.location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
// 확인 모달창에서 취소, 확인 버튼 처리
|
||||
const handleModalStep = async e => {
|
||||
e.preventDefault();
|
||||
handleConfirmModal();
|
||||
|
||||
if (modalStep) {
|
||||
await RepostReplyMessage(token, resultData);
|
||||
setModalText('답변 발송이 완료되었습니다.');
|
||||
handleCompleteModal();
|
||||
} else {
|
||||
setModalText('답변이 취소되었습니다.');
|
||||
handleCompleteModal();
|
||||
}
|
||||
|
||||
handleReset();
|
||||
};
|
||||
|
||||
// 확인 버튼에서 불러올 초기화 처리
|
||||
const handleReset = () => {
|
||||
setAnswerView('hidden');
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal min="960px" $view={answerView}>
|
||||
<Title $align="center">{replied ? '신고 답변' : '신고 답변 작성'}</Title>
|
||||
<Subtitle>[답변 수신자 정보]</Subtitle>
|
||||
<ReportDetailState>
|
||||
<table>
|
||||
<caption></caption>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th width="100px">수신자 GUID</th>
|
||||
<td>
|
||||
<TextInput value={detailData.reporter_guid || ''} readOnly />
|
||||
</td>
|
||||
<th width="100px">닉네임</th>
|
||||
<td>
|
||||
<TextInput value={detailData.reporter_nickname || ''} readOnly />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>제목</th>
|
||||
<td colSpan="3">{detailData.title || ''}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th width="120">발송 경로</th>
|
||||
<td>개인 우편함</td>
|
||||
<th width="120">발송자</th>
|
||||
<td>GM</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ReportDetailState>
|
||||
|
||||
<Subtitle>[답변 내용]</Subtitle>
|
||||
<AnswerRegistTable>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th width="120">
|
||||
<Label>제목</Label>
|
||||
</th>
|
||||
<td>
|
||||
<InputItem>
|
||||
<TextInput
|
||||
maxLength="30"
|
||||
name="title"
|
||||
value={resultData.title || ''}
|
||||
onInput={e => {
|
||||
setResultData({ ...resultData, title: e.target.value.trimStart() });
|
||||
}}
|
||||
readOnly={replyData}
|
||||
/>
|
||||
</InputItem>
|
||||
{/* 답변 작성시에 글자 수 확인 */}
|
||||
<ReportNotice $color={titleLeng > 29 ? 'red' : '#666'}>* 최대 등록 가능 글자수 ({Number(titleLeng)}/30자)</ReportNotice>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
<Label>내용</Label>
|
||||
</th>
|
||||
<td>
|
||||
<Textarea
|
||||
value={resultData.detail || ''}
|
||||
name="detail"
|
||||
maxLength="2000"
|
||||
onInput={e => {
|
||||
setResultData({ ...resultData, detail: e.target.value.trimStart() });
|
||||
}}
|
||||
readOnly={replyData}
|
||||
/>
|
||||
{/* 답변 작성시에 글자 수 확인 */}
|
||||
<ReportNotice $color={detailLeng > 1999 ? 'red' : '#666'}>* 최대 등록 가능 글자수 ({Number(detailLeng)}/2000자)</ReportNotice>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</AnswerRegistTable>
|
||||
{isNullValue && (
|
||||
<SearchBarAlert $align="right" $padding="15px">
|
||||
필수값을 입력해주세요.
|
||||
</SearchBarAlert>
|
||||
)}
|
||||
<BtnWrapper $justify="flex-end" $gap="10px">
|
||||
{replyData ? (
|
||||
<Button text="확인" theme="line" handleClick={e => setAnswerView('hidden')} />
|
||||
) : (
|
||||
<>
|
||||
<Button
|
||||
text="취소"
|
||||
theme="line"
|
||||
name="취소버튼"
|
||||
handleClick={e => {
|
||||
handleConfirmModal(e);
|
||||
handleNoticeText(e);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
text="발송"
|
||||
name="발송버튼"
|
||||
// errorMessage={btnValidation}
|
||||
theme={btnVali ? 'primary' : 'disable'}
|
||||
handleClick={e => {
|
||||
{
|
||||
!btnVali ? handleNullVali(e) : handleConfirmModal(e);
|
||||
handleNoticeText(e);
|
||||
handleNullVali(e);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
|
||||
{/* 확인 모달 */}
|
||||
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={confirmModal}>
|
||||
<BtnWrapper $justify="flex-end">
|
||||
<ButtonClose onClick={handleConfirmModal} />
|
||||
</BtnWrapper>
|
||||
<ModalText $align="center">{modalText}</ModalText>
|
||||
<BtnWrapper $gap="10px">
|
||||
<Button text="취소" theme="line" size="large" width="100%" handleClick={handleConfirmModal} />
|
||||
<Button
|
||||
text="확인"
|
||||
theme="primary"
|
||||
type="submit"
|
||||
size="large"
|
||||
width="100%"
|
||||
handleClick={e => {
|
||||
handleModalStep(e);
|
||||
}}
|
||||
/>
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
{/* 완료 모달 */}
|
||||
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={completeModal}>
|
||||
<BtnWrapper $justify="flex-end">
|
||||
<ButtonClose onClick={handleCompleteModal} />
|
||||
</BtnWrapper>
|
||||
<ModalText $align="center">{modalText}</ModalText>
|
||||
<BtnWrapper $gap="10px">
|
||||
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleCompleteModal} />
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReportListAnswerModal;
|
||||
|
||||
const ReportDetailState = styled.div`
|
||||
border-top: 1px solid #000;
|
||||
border-bottom: 1px solid #000;
|
||||
padding: 15px 10px;
|
||||
font-size: 14px;
|
||||
margin-bottom: 20px;
|
||||
input {
|
||||
height: 35px;
|
||||
max-width: 330px;
|
||||
font-size: 14px;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
table {
|
||||
tr {
|
||||
th,
|
||||
td {
|
||||
padding: 5px;
|
||||
height: 45px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const Subtitle = styled.div`
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
line-height: 1.6;
|
||||
`;
|
||||
|
||||
const AnswerRegistTable = styled.table`
|
||||
border-top: 1px solid #999;
|
||||
margin-bottom: 20px;
|
||||
th,
|
||||
td {
|
||||
padding: 15px 0;
|
||||
border-bottom: 1px solid #f6f6f6;
|
||||
}
|
||||
tr:last-child {
|
||||
th,
|
||||
td {
|
||||
padding: 15px 0;
|
||||
border-bottom: 1px solid #999;
|
||||
}
|
||||
}
|
||||
th {
|
||||
vertical-align: top;
|
||||
line-height: 30px;
|
||||
}
|
||||
td {
|
||||
${TextInput} {
|
||||
max-width: 600px;
|
||||
}
|
||||
${Textarea} {
|
||||
width: 100%;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 5px;
|
||||
height: 150px;
|
||||
padding: 15px;
|
||||
&:focus {
|
||||
border: 1px solid #2c2c2c;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const ReportNotice = styled.span`
|
||||
font-size: 12px;
|
||||
font-weight: 300;
|
||||
color: ${props => props.$color || '#999'};
|
||||
margin-top: 10px;
|
||||
display: block;
|
||||
`;
|
||||
|
||||
const ButtonClose = styled.button`
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: url(${CloseIcon}) 50% 50% no-repeat;
|
||||
opacity: ${props => props.opacity};
|
||||
`;
|
||||
221
src/components/ServiceManage/modal/ReportListDetailModal.js
Normal file
221
src/components/ServiceManage/modal/ReportListDetailModal.js
Normal file
@@ -0,0 +1,221 @@
|
||||
import { styled } from 'styled-components';
|
||||
import { Title, BtnWrapper, TextInput, Label, Textarea, InputItem } from '../../../styles/Components';
|
||||
|
||||
import Modal from '../../common/modal/Modal';
|
||||
import Button from '../../common/button/Button';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { convertKTC } from '../../../utils';
|
||||
|
||||
const ReportListDetailModal = ({ detailView, handleDetailView, handleReply, detailData, replyData, replyAuth }) => {
|
||||
const [dataList, setDataList] = useState([]);
|
||||
|
||||
// UTC + 9 처리 해줄 변수
|
||||
const RESOLVE_TIME = detailData && new Date(detailData.resolution_time);
|
||||
const REPORT_TIME = detailData && new Date(detailData.create_time);
|
||||
|
||||
// 신고 유형 데이터 매핑
|
||||
const report_type = [
|
||||
{ value: 'ALL', name: '전체' },
|
||||
{ value: 'UNMANNERED_ACT', name: '비매너 행위' },
|
||||
{ value: 'USE_UNHEALTHY_NAMES', name: '불건전 이름 사용' },
|
||||
{ value: 'CASH_TRADING', name: '현금거래 행위' },
|
||||
{ value: 'INTERFERENCE_GAME', name: '게임 진행 방해' },
|
||||
{ value: 'INTERFERENCE_SERVICE', name: '운영서비스 방해' },
|
||||
{ value: 'ACCOUNT_EXPLOITATION', name: '계정도용' },
|
||||
{ value: 'BUG_ABUSING', name: '버그/어뷰징' },
|
||||
{ value: 'USE_HACK', name: '불법프로그램 사용' },
|
||||
{ value: 'LEAK_PERSONAL_INFO', name: '개인정보 유출' },
|
||||
{ value: 'PRETENDING_GM', name: '운영자 사칭' },
|
||||
];
|
||||
|
||||
// console.log(detailData);
|
||||
useEffect(() => {
|
||||
setDataList({
|
||||
create_time: detailData && convertKTC(REPORT_TIME, false),
|
||||
detail: detailData && detailData.detail,
|
||||
manager_email: replyData && replyData.manager_email,
|
||||
report_type: detailData && detailData.report_type,
|
||||
reporter_guid: detailData && detailData.reporter_guid,
|
||||
reporter_nickname: detailData && detailData.reporter_nickname,
|
||||
resolution_time: detailData && detailData.resolution_time,
|
||||
state: detailData && detailData.state,
|
||||
target_guid: detailData && detailData.target_guid,
|
||||
target_nickname: detailData && detailData.target_nickname,
|
||||
title: detailData && detailData.title,
|
||||
});
|
||||
}, [detailData]);
|
||||
|
||||
// console.log('모달창에서 리포트 상세 정보 : ', dataList);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal min="960px" $view={detailView}>
|
||||
<Title $align="center">신고내역 상세 정보</Title>
|
||||
{/* RegistInfo는 답변 완료시에만 보여집니다 */}
|
||||
{dataList && dataList.resolution_time && (
|
||||
<RegistInfo>
|
||||
<span>등록자(이메일주소) : {dataList && dataList.manager_email}</span>
|
||||
<span>등록일 : {dataList && convertKTC(RESOLVE_TIME)}</span>
|
||||
</RegistInfo>
|
||||
)}
|
||||
<Subtitle>[신고 대상 정보]</Subtitle>
|
||||
<ReportDetailState>
|
||||
<table>
|
||||
<caption></caption>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th width="120">신고 일자</th>
|
||||
<td>{dataList.create_time}</td>
|
||||
<th width="120">신고 유형</th>
|
||||
<td>{report_type.map(data => data.value === (dataList && dataList.report_type) && data.name)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>처리상태</th>
|
||||
<td colSpan="3">{dataList && dataList.state === 'RESOLVED' ? <ReportState results="solved">해결</ReportState> : <ReportState results="remain">미해결</ReportState>}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th width="100px">신고자 GUID</th>
|
||||
<td>
|
||||
<TextInput value={(dataList && dataList.reporter_guid) || ''} readOnly />
|
||||
</td>
|
||||
<th width="100px">닉네임</th>
|
||||
<td>
|
||||
<TextInput value={(dataList && dataList.reporter_nickname) || ''} readOnly />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th width="100px">신고대상 GUID</th>
|
||||
<td>
|
||||
<TextInput value={(dataList && dataList.target_guid) || ''} readOnly />
|
||||
</td>
|
||||
<th width="100px">닉네임</th>
|
||||
<td>
|
||||
<TextInput value={(dataList && dataList.target_nickname) || ''} readOnly />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ReportDetailState>
|
||||
|
||||
<Subtitle>[신고내용]</Subtitle>
|
||||
<AnswerRegistTable>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th width="120">
|
||||
<Label>제목</Label>
|
||||
</th>
|
||||
<td>
|
||||
<InputItem>
|
||||
<TextInput maxLength="30" value={(dataList && dataList.title) || ''} readOnly />
|
||||
</InputItem>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
<Label>내용</Label>
|
||||
</th>
|
||||
<td>
|
||||
<Textarea value={(dataList && dataList.detail) || ''} readOnly></Textarea>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</AnswerRegistTable>
|
||||
<BtnWrapper $justify="flex-end" $gap="10px">
|
||||
<Button text="확인" theme="line" handleClick={() => handleDetailView()} />
|
||||
{
|
||||
// 답변이 있을 때
|
||||
dataList && dataList.resolution_time ? (
|
||||
<Button text="답변보기" theme="line" handleClick={() => handleReply()} />
|
||||
) : // 답변이 없고, 답변 권한이 있을 때
|
||||
dataList && !dataList.resolution_time && replyAuth ? (
|
||||
<Button text="답변하기" theme="primary" handleClick={() => handleReply()} />
|
||||
) : (
|
||||
// 답변이 없고, 답변 권한도 없을 때
|
||||
<></>
|
||||
)
|
||||
}
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReportListDetailModal;
|
||||
|
||||
const ReportDetailState = styled.div`
|
||||
border-top: 1px solid #000;
|
||||
border-bottom: 1px solid #000;
|
||||
padding: 15px 10px;
|
||||
font-size: 14px;
|
||||
margin-bottom: 20px;
|
||||
input {
|
||||
height: 35px;
|
||||
max-width: 330px;
|
||||
font-size: 14px;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
table {
|
||||
tr {
|
||||
th,
|
||||
td {
|
||||
padding: 5px;
|
||||
height: 45px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
const ReportState = styled.span`
|
||||
font-weight: 600;
|
||||
color: ${props => (props.results === 'solved' ? '#08994B' : props.results === 'remain' ? '#ff0000' : '#2c2c2c')};
|
||||
`;
|
||||
const RegistInfo = styled.div`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 50px;
|
||||
font-size: 14px;
|
||||
margin-bottom: 10px;
|
||||
`;
|
||||
|
||||
const Subtitle = styled.div`
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
line-height: 1.6;
|
||||
`;
|
||||
|
||||
const AnswerRegistTable = styled.table`
|
||||
border-top: 1px solid #999;
|
||||
margin-bottom: 20px;
|
||||
th,
|
||||
td {
|
||||
padding: 15px 0;
|
||||
border-bottom: 1px solid #f6f6f6;
|
||||
}
|
||||
tr:last-child {
|
||||
th,
|
||||
td {
|
||||
padding: 15px 0;
|
||||
border-bottom: 1px solid #999;
|
||||
}
|
||||
}
|
||||
th {
|
||||
vertical-align: top;
|
||||
line-height: 30px;
|
||||
}
|
||||
td {
|
||||
${TextInput} {
|
||||
max-width: 600px;
|
||||
}
|
||||
${Textarea} {
|
||||
width: 100%;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 5px;
|
||||
height: 150px;
|
||||
padding: 15px;
|
||||
&:focus {
|
||||
border: 1px solid #2c2c2c;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
206
src/components/ServiceManage/modal/UserBlockDetailModal.js
Normal file
206
src/components/ServiceManage/modal/UserBlockDetailModal.js
Normal file
@@ -0,0 +1,206 @@
|
||||
import { Fragment, useState } from 'react';
|
||||
import { styled } from 'styled-components';
|
||||
|
||||
import { Title, TableStyle, BtnWrapper, TextInput } from '../../../styles/Components';
|
||||
import Modal from '../../common/modal/Modal';
|
||||
import Button from '../../common/button/Button';
|
||||
import { convertKTC } from '../../../utils';
|
||||
|
||||
const UserBlockDetailModal = ({ stateModal, handleModal, data }) => {
|
||||
const history = data.history;
|
||||
|
||||
const type = [
|
||||
{ value: 'ACCESS_RESTRICTIONS', name: '접근 제한' },
|
||||
{ value: 'CHATTING_RESTRICTIONS', name: '채팅 제한' },
|
||||
];
|
||||
|
||||
const sanctions = [
|
||||
{ value: 'BAD_BEHAVIOR', name: '비매너 행위' },
|
||||
{ value: 'INAPPROPRIATE_NAME', name: '불건전 이름 사용' },
|
||||
{ value: 'CASH_TRANSACTION', name: '현금거래 행위' },
|
||||
{ value: 'GAME_INTERFERENCE', name: '게임 진행 방해' },
|
||||
{ value: 'SERVICE_INTERFERENCE', name: '운영서비스 방해' },
|
||||
{ value: 'ACCOUNT_IMPERSONATION', name: '계정도용' },
|
||||
{ value: 'BUG_ABUSE', name: '버그/어뷰징' },
|
||||
{ value: 'ILLEGAL_PROGRAM', name: '불법프로그램 사용' },
|
||||
{ value: 'PERSONAL_INFO_LEAK', name: '개인정보 유출' },
|
||||
{ value: 'ADMIN_IMPERSONATION', name: '운영자 사칭' },
|
||||
];
|
||||
|
||||
const period = [
|
||||
{ value: 'WARNING', name: '경고' },
|
||||
{ value: 'D1', name: '1일' },
|
||||
{ value: 'D3', name: '3일' },
|
||||
{ value: 'D7', name: '7일' },
|
||||
{ value: 'D15', name: '15일' },
|
||||
{ value: 'D30', name: '30일' },
|
||||
{ value: 'PERMANENT', name: '영구정지' },
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal min="960px" $view={stateModal}>
|
||||
<Title $align="center">이용자 제재 상세 정보</Title>
|
||||
<RegistInfo>
|
||||
<span>등록자 : {data.create_by}</span>
|
||||
<span>등록일 : {convertKTC(data.create_dt, false)}</span>
|
||||
</RegistInfo>
|
||||
<BlockDetailState>
|
||||
<table>
|
||||
<caption></caption>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th width="100px">유저 GUID</th>
|
||||
<td>
|
||||
<TextInput value={data && data.guid || ''} disabled />
|
||||
</td>
|
||||
<th width="100px">아바타명</th>
|
||||
<td>
|
||||
<TextInput value={data && data.nickname || ''} disabled />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>제재 방식</th>
|
||||
<td>
|
||||
<TextInput value={type.map(item => item.value === data.type && item.name).filter(data => data !== false) || ''} disabled />
|
||||
</td>
|
||||
<th></th>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>제재 기간</th>
|
||||
<td colSpan="3">
|
||||
<InputWrapper>
|
||||
<TextInput value={period.map(item => item.value === data.period && item.name).filter(data => data !== false) || ''} disabled />
|
||||
<span>시작일 : {convertKTC(data.start_dt, false)}</span>
|
||||
<span>종료일 : {convertKTC(data.end_dt, false)}</span>
|
||||
</InputWrapper>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>제재 사유</th>
|
||||
<td>
|
||||
<TextInput value={sanctions.map(item => item.value === data.sanctions && item.name).filter(data => data !== false) || ''} disabled />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</BlockDetailState>
|
||||
|
||||
<BlockCount>전체 제재 이력({history && history.length}건)</BlockCount>
|
||||
<BlockHistory>
|
||||
<TableStyle>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>일자</th>
|
||||
<th width="10%">제재 기간</th>
|
||||
<th width="10%">제재 방식</th>
|
||||
<th width="20%">제재 사유</th>
|
||||
<th width="20%">등록자</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{history &&
|
||||
history.map((content, index) => (
|
||||
<Fragment key={index}>
|
||||
<tr>
|
||||
<td>
|
||||
{convertKTC(content.start_dt, false)} ~
|
||||
{convertKTC(content.end_dt, false)}
|
||||
</td>
|
||||
<td>{period.map(item => item.value === data.period && item.name)}</td>
|
||||
<td>{type.map(item => item.value === data.type && item.name)}</td>
|
||||
<td>{sanctions.map(item => item.value === data.sanctions && item.name)}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</Fragment>
|
||||
))}
|
||||
</tbody>
|
||||
</TableStyle>
|
||||
</BlockHistory>
|
||||
<BlockNotice>※ 삭제된 제재 이력은 표시되지 않습니다.</BlockNotice>
|
||||
<BtnWrapper $justify="flex-end">
|
||||
<Button text="확인" theme="line" handleClick={handleModal} />
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserBlockDetailModal;
|
||||
|
||||
const BlockDetailState = styled.div`
|
||||
border-top: 1px solid #000;
|
||||
border-bottom: 1px solid #000;
|
||||
padding: 15px 10px;
|
||||
font-size: 14px;
|
||||
margin-bottom: 20px;
|
||||
input {
|
||||
height: 35px;
|
||||
max-width: 330px;
|
||||
font-size: 14px;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
table {
|
||||
tr {
|
||||
th,
|
||||
td {
|
||||
padding: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const RegistInfo = styled.div`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 50px;
|
||||
font-size: 14px;
|
||||
margin-bottom: 10px;
|
||||
`;
|
||||
|
||||
const BlockCount = styled.div`
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
line-height: 1.6;
|
||||
padding-bottom: 5px;
|
||||
`;
|
||||
|
||||
const BlockHistory = styled.div`
|
||||
max-height: 324px;
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
border-top: 1px solid #000;
|
||||
${TableStyle} {
|
||||
border-collapse: separate;
|
||||
&:before {
|
||||
display: none;
|
||||
}
|
||||
th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
tbody {
|
||||
tr:first-child {
|
||||
color: #d60000;
|
||||
}
|
||||
tr:last-child td {
|
||||
border-bottom: 1px solid #e8eaec;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const BlockNotice = styled.span`
|
||||
font-size: 12px;
|
||||
color: #686868;
|
||||
padding-left: 10px;
|
||||
font-weight: 300;
|
||||
line-height: 2;
|
||||
`;
|
||||
|
||||
const InputWrapper = styled.div`
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
align-items: center;
|
||||
`;
|
||||
205
src/components/ServiceManage/searchBar/BattleEventSearchBar.js
Normal file
205
src/components/ServiceManage/searchBar/BattleEventSearchBar.js
Normal file
@@ -0,0 +1,205 @@
|
||||
import { TextInput, BtnWrapper, InputLabel, SelectInput, InputGroup } from '../../../styles/Components';
|
||||
import Button from '../../common/button/Button';
|
||||
import { SearchBarLayout, SearchPeriod } from '../../common/SearchBar';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { landAuctionStatus, landSearchType, landSize, userSearchType } from '../../../assets/data';
|
||||
import { BattleEventView } from '../../../apis/Battle';
|
||||
|
||||
export const useBattleEventSearch = (token, initialPageSize) => {
|
||||
const [searchParams, setSearchParams] = useState({
|
||||
landType: 'ID',
|
||||
landData: '',
|
||||
userType: 'GUID',
|
||||
userData: '',
|
||||
landSize: 'ALL',
|
||||
status: 'ALL',
|
||||
auctionStartDate: '',
|
||||
auctionEndDate: '',
|
||||
orderBy: 'DESC',
|
||||
pageSize: initialPageSize,
|
||||
currentPage: 1
|
||||
});
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetchData(searchParams); // 컴포넌트 마운트 시 초기 데이터 로드
|
||||
}, [token]);
|
||||
|
||||
const fetchData = useCallback(async (params) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const result = await BattleEventView(
|
||||
token,
|
||||
params.landType,
|
||||
params.landData,
|
||||
params.userType,
|
||||
params.userData,
|
||||
params.landSize,
|
||||
params.status,
|
||||
params.auctionStartDate && new Date(params.auctionStartDate).toISOString(),
|
||||
params.auctionEndDate && new Date(params.auctionEndDate).toISOString(),
|
||||
params.orderBy,
|
||||
params.pageSize,
|
||||
params.currentPage
|
||||
);
|
||||
setData(result);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Error fetching auction data:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [token]);
|
||||
|
||||
const updateSearchParams = useCallback((newParams) => {
|
||||
setSearchParams(prev => ({
|
||||
...prev,
|
||||
...newParams
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const handleSearch = useCallback(async (newParams = {}) => {
|
||||
const updatedParams = {
|
||||
...searchParams,
|
||||
...newParams,
|
||||
currentPage: newParams.currentPage || 1 // Reset to first page on new search
|
||||
};
|
||||
updateSearchParams(updatedParams);
|
||||
return await fetchData(updatedParams);
|
||||
}, [searchParams, fetchData]);
|
||||
|
||||
const handleReset = useCallback(async () => {
|
||||
const resetParams = {
|
||||
landType: 'ID',
|
||||
landData: '',
|
||||
userType: 'GUID',
|
||||
userData: '',
|
||||
landSize: 'ALL',
|
||||
status: 'ALL',
|
||||
auctionStartDate: '',
|
||||
auctionEndDate: '',
|
||||
orderBy: 'DESC',
|
||||
pageSize: initialPageSize,
|
||||
currentPage: 1
|
||||
};
|
||||
setSearchParams(resetParams);
|
||||
return await fetchData(resetParams);
|
||||
}, [initialPageSize, fetchData]);
|
||||
|
||||
const handlePageChange = useCallback(async (newPage) => {
|
||||
return await handleSearch({ currentPage: newPage });
|
||||
}, [handleSearch]);
|
||||
|
||||
const handlePageSizeChange = useCallback(async (newSize) => {
|
||||
return await handleSearch({ pageSize: newSize, currentPage: 1 });
|
||||
}, [handleSearch]);
|
||||
|
||||
const handleOrderByChange = useCallback(async (newOrder) => {
|
||||
return await handleSearch({ orderBy: newOrder });
|
||||
}, [handleSearch]);
|
||||
|
||||
return {
|
||||
searchParams,
|
||||
loading,
|
||||
data,
|
||||
handleSearch,
|
||||
handleReset,
|
||||
handlePageChange,
|
||||
handlePageSizeChange,
|
||||
handleOrderByChange,
|
||||
updateSearchParams
|
||||
};
|
||||
};
|
||||
|
||||
const BattleEventSearchBar = ({ searchParams, onSearch, onReset }) => {
|
||||
const handleSubmit = event => {
|
||||
event.preventDefault();
|
||||
|
||||
onSearch(searchParams);
|
||||
};
|
||||
|
||||
const searchList = [
|
||||
<>
|
||||
<InputGroup>
|
||||
<SelectInput value={searchParams.landType} onChange={e => onSearch({landType: e.target.value })}>
|
||||
{landSearchType.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<TextInput
|
||||
type="text"
|
||||
placeholder={searchParams.landType === 'ID' ? '랜드 ID 입력' : '랜드명 입력'}
|
||||
value={searchParams.landData}
|
||||
width="300px"
|
||||
onChange={e => onSearch({ landData: e.target.value })}
|
||||
/>
|
||||
</InputGroup>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>낙찰자</InputLabel>
|
||||
<InputGroup>
|
||||
<SelectInput value={searchParams.userType} onChange={e => onSearch({userType: e.target.value })}>
|
||||
{userSearchType.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<TextInput
|
||||
type="text"
|
||||
placeholder={searchParams.userType === 'GUID' ? 'GUID 입력' : '닉네임 입력'}
|
||||
value={searchParams.userData}
|
||||
width="300px"
|
||||
onChange={e => onSearch({ userData: e.target.value })}
|
||||
/>
|
||||
</InputGroup>
|
||||
</>
|
||||
];
|
||||
|
||||
const optionList = [
|
||||
<>
|
||||
<InputLabel>랜드크기</InputLabel>
|
||||
<SelectInput value={searchParams.landSize} onChange={e => onSearch({ landSize: e.target.value }, false)} >
|
||||
{landSize.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>경매상태</InputLabel>
|
||||
<SelectInput value={searchParams.status} onChange={e => onSearch({ status: e.target.value }, false)}>
|
||||
{landAuctionStatus.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>경매 일자</InputLabel>
|
||||
<SearchPeriod
|
||||
startDate={searchParams.auctionStartDate}
|
||||
handleStartDate={date => onSearch({ auctionStartDate: date }, false)}
|
||||
endDate={searchParams.auctionEndDate}
|
||||
handleEndDate={date => onSearch({ auctionEndDate: date }, false)}
|
||||
/>
|
||||
</>,
|
||||
<></>,<></>,
|
||||
<>
|
||||
<BtnWrapper $gap="8px">
|
||||
<Button theme="reset" handleClick={onReset} type="button" />
|
||||
<Button theme="search" text="검색" handleClick={handleSubmit} type="submit" />
|
||||
</BtnWrapper>
|
||||
</>,
|
||||
];
|
||||
return <SearchBarLayout firstColumnData={searchList} secondColumnData={optionList} direction={'column'} />;
|
||||
};
|
||||
|
||||
export default BattleEventSearchBar;
|
||||
@@ -0,0 +1,86 @@
|
||||
import { TextInput, BtnWrapper, InputLabel, SelectInput } from '../../../styles/Components';
|
||||
import Button from '../../common/button/Button';
|
||||
import { SearchBarLayout, SearchPeriod } from '../../common/SearchBar';
|
||||
import { useState } from 'react';
|
||||
import { caliumStatus } from '../../../assets/data/options';
|
||||
|
||||
const CaliumRequestSearchBar = ({ handleSearch, setResultData }) => {
|
||||
const [searchData, setSearchData] = useState({
|
||||
content: '',
|
||||
status: 'ALL',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
});
|
||||
|
||||
const handleSubmit = event => {
|
||||
event.preventDefault();
|
||||
|
||||
handleSearch(
|
||||
searchData.content,
|
||||
searchData.status ? searchData.status : 'ALL',
|
||||
searchData.startDate ? searchData.startDate : '',
|
||||
searchData.endDate ? searchData.endDate : new Date(),
|
||||
(searchData.startDate && searchData.endDate === '') && setSearchData({ startDate : searchData.startDate ,endDate : new Date()}),
|
||||
);
|
||||
|
||||
setResultData(searchData);
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setSearchData({
|
||||
content: '',
|
||||
status: 'ALL',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
order: 'DESC',
|
||||
});
|
||||
|
||||
handleSearch('', 'ALL', '', '');
|
||||
setResultData('', 'ALL', '', '');
|
||||
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
const searchList = [
|
||||
<>
|
||||
<InputLabel>등록 일자</InputLabel>
|
||||
<SearchPeriod
|
||||
startDate={searchData.startDate}
|
||||
handleStartDate={data => {
|
||||
setSearchData({ ...searchData, startDate: data });
|
||||
}}
|
||||
endDate={searchData.endDate}
|
||||
handleEndDate={data => setSearchData({ ...searchData, endDate: data })}
|
||||
/>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>요청 내용</InputLabel>
|
||||
<TextInput
|
||||
type="text"
|
||||
placeholder="요청 내용"
|
||||
value={searchData.content}
|
||||
onChange={e => setSearchData({ ...searchData, content: e.target.value })}
|
||||
/>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>요청 상태</InputLabel>
|
||||
<SelectInput value={searchData.status} onChange={e => setSearchData({ ...searchData, status: e.target.value })}>
|
||||
{caliumStatus.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<>
|
||||
<BtnWrapper $gap="8px">
|
||||
<Button theme="reset" handleClick={handleReset} type="button" />
|
||||
<Button theme="search" text="검색" handleClick={handleSubmit} type="submit" />
|
||||
</BtnWrapper>
|
||||
</>,
|
||||
];
|
||||
|
||||
return <SearchBarLayout firstColumnData={searchList} direction={'column'} />;
|
||||
};
|
||||
|
||||
export default CaliumRequestSearchBar;
|
||||
103
src/components/ServiceManage/searchBar/EventListSearchBar.js
Normal file
103
src/components/ServiceManage/searchBar/EventListSearchBar.js
Normal file
@@ -0,0 +1,103 @@
|
||||
import { TextInput, BtnWrapper, InputLabel, SelectInput } from '../../../styles/Components';
|
||||
import Button from '../../common/button/Button';
|
||||
import { SearchBarLayout, SearchPeriod } from '../../common/SearchBar';
|
||||
import { useState } from 'react';
|
||||
import { eventStatus } from '../../../assets/data';
|
||||
|
||||
const EventListSearchBar = ({ handleSearch, setResultData }) => {
|
||||
const [searchData, setSearchData] = useState({
|
||||
title: '',
|
||||
content: '',
|
||||
status: 'ALL',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
});
|
||||
|
||||
const handleSubmit = event => {
|
||||
event.preventDefault();
|
||||
|
||||
handleSearch(
|
||||
searchData.title,
|
||||
searchData.content,
|
||||
searchData.status ? searchData.status : 'ALL',
|
||||
searchData.startDate ? searchData.startDate : '',
|
||||
searchData.endDate ? searchData.endDate : new Date(),
|
||||
(searchData.startDate && searchData.endDate === '') && setSearchData({ startDate : searchData.startDate ,endDate : new Date()}),
|
||||
);
|
||||
|
||||
setResultData(searchData);
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setSearchData({
|
||||
title: '',
|
||||
content: '',
|
||||
status: 'ALL',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
order: 'DESC',
|
||||
});
|
||||
|
||||
handleSearch('', '', 'ALL', '', '');
|
||||
setResultData('', '', 'ALL', '', '');
|
||||
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
// console.log("searchData.endDate", searchData.endDate)
|
||||
|
||||
const searchList = [
|
||||
<>
|
||||
<InputLabel>우편 제목</InputLabel>
|
||||
<TextInput
|
||||
type="text"
|
||||
placeholder="우편 제목"
|
||||
value={searchData.title}
|
||||
onChange={e => setSearchData({ ...searchData, title: e.target.value })}
|
||||
/>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>조회 일자</InputLabel>
|
||||
<SearchPeriod
|
||||
startDate={searchData.startDate}
|
||||
handleStartDate={data => {
|
||||
setSearchData({ ...searchData, startDate: data });
|
||||
}}
|
||||
endDate={searchData.endDate}
|
||||
handleEndDate={data => setSearchData({ ...searchData, endDate: data })}
|
||||
/>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>우편 내용</InputLabel>
|
||||
<TextInput
|
||||
type="text"
|
||||
placeholder="우편 내용(공백으로 구분)"
|
||||
value={searchData.content}
|
||||
onChange={e => setSearchData({ ...searchData, content: e.target.value })}
|
||||
/>
|
||||
</>
|
||||
];
|
||||
|
||||
const optionList = [
|
||||
<>
|
||||
<InputLabel>이벤트 상태</InputLabel>
|
||||
<SelectInput value={searchData.status} onChange={e => setSearchData({ ...searchData, status: e.target.value })}>
|
||||
{eventStatus.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<></>,<></>,<></>,<></>,<></>,<></>,<></>,<></>,<></>,<></>,<></>,<></>,<></>,
|
||||
<>
|
||||
<BtnWrapper $gap="8px">
|
||||
<Button theme="reset" handleClick={handleReset} type="button" />
|
||||
<Button theme="search" text="검색" handleClick={handleSubmit} type="submit" />
|
||||
</BtnWrapper>
|
||||
</>,
|
||||
];
|
||||
return <SearchBarLayout firstColumnData={searchList} secondColumnData={optionList} direction={'column'} />;
|
||||
};
|
||||
|
||||
export default EventListSearchBar;
|
||||
127
src/components/ServiceManage/searchBar/ItemsSearchBar.js
Normal file
127
src/components/ServiceManage/searchBar/ItemsSearchBar.js
Normal file
@@ -0,0 +1,127 @@
|
||||
import { useState } from 'react';
|
||||
import { TextInput, BtnWrapper, InputLabel, SelectInput, InputGroup } from '../../../styles/Components';
|
||||
import Button from '../../common/button/Button';
|
||||
import { SearchBarLayout, SearchPeriod } from '../../common/SearchBar';
|
||||
|
||||
const ItemsSearchBar = ({ handleSearch, setResultData }) => {
|
||||
const [searchData, setSearchData] = useState({
|
||||
searchType: 'GUID',
|
||||
data: '',
|
||||
status: 'ALL',
|
||||
restore: 'ALL',
|
||||
sendDate: '',
|
||||
endDate: '',
|
||||
});
|
||||
|
||||
const searchType = [
|
||||
{ value: 'GUID', name: 'GUID' },
|
||||
{ value: 'NAME', name: '닉네임' }
|
||||
];
|
||||
|
||||
const status = [
|
||||
{ value: 'ALL', name: '상태' },
|
||||
{ value: 'ACTIVE', name: '활성' },
|
||||
{ value: 'DEACTIVE', name: '비활성' },
|
||||
];
|
||||
|
||||
const restore = [
|
||||
{ value: 'ALL', name: '복구' },
|
||||
{ value: 'POSSIBLE', name: '가능' },
|
||||
{ value: 'IMPOSSIBLE', name: '불가능' },
|
||||
];
|
||||
|
||||
const handleSubmit = event => {
|
||||
event.preventDefault();
|
||||
|
||||
handleSearch(
|
||||
searchData.searchType ? searchData.searchType : 'GUID',
|
||||
searchData.data,
|
||||
searchData.status ? searchData.status : 'ALL',
|
||||
searchData.restore ? searchData.restore : 'ALL',
|
||||
searchData.sendDate ? searchData.sendDate : '',
|
||||
searchData.endDate ? searchData.endDate : new Date(),
|
||||
);
|
||||
|
||||
setResultData(searchData);
|
||||
};
|
||||
|
||||
// 초기화 버튼
|
||||
const handleReset = () => {
|
||||
setSearchData({
|
||||
searchType: 'GUID',
|
||||
data: '',
|
||||
status: 'ALL',
|
||||
restore: 'ALL',
|
||||
sendDate: '',
|
||||
endDate: '',
|
||||
});
|
||||
handleSearch('GUID', '', 'ALL', 'ALL', '', '');
|
||||
setResultData('GUID', '', 'ALL', 'ALL', '', '');
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
// console.log(searchData);
|
||||
|
||||
const searchList = [
|
||||
<>
|
||||
<InputGroup>
|
||||
<SelectInput value={searchData.searchType} onChange={e => setSearchData({ ...searchData, searchType: e.target.value })}>
|
||||
{searchType.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<TextInput
|
||||
type="text"
|
||||
placeholder={searchData.searchType === 'GUID' ? 'GUID 입력' : '닉네임 입력'}
|
||||
value={searchData.data}
|
||||
width="600px"
|
||||
onChange={e => setSearchData({ ...searchData, data: e.target.value })}
|
||||
/>
|
||||
</InputGroup>
|
||||
</>
|
||||
];
|
||||
|
||||
const optionList = [
|
||||
<>
|
||||
<SelectInput value={searchData.status} onChange={e => setSearchData({ ...searchData, status: e.target.value })}>
|
||||
{status.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<>
|
||||
<SelectInput value={searchData.restore} onChange={e => setSearchData({ ...searchData, restore: e.target.value })}>
|
||||
{restore.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>생성 날짜</InputLabel>
|
||||
<SearchPeriod
|
||||
startDate={searchData.sendDate}
|
||||
handleStartDate={data => {
|
||||
setSearchData({ ...searchData, sendDate: data });
|
||||
}}
|
||||
endDate={searchData.endDate}
|
||||
handleEndDate={data => setSearchData({ ...searchData, endDate: data })}
|
||||
maxDate={new Date()}
|
||||
/>
|
||||
</>,
|
||||
<>
|
||||
<BtnWrapper $gap="8px">
|
||||
<Button theme="reset" handleClick={handleReset} />
|
||||
<Button theme="search" text="검색" type="submit" handleClick={handleSubmit} />
|
||||
</BtnWrapper>
|
||||
</>,
|
||||
];
|
||||
return <SearchBarLayout firstColumnData={searchList} secondColumnData={optionList} direction={'column'} />;
|
||||
};
|
||||
|
||||
export default ItemsSearchBar;
|
||||
205
src/components/ServiceManage/searchBar/LandAuctionSearchBar.js
Normal file
205
src/components/ServiceManage/searchBar/LandAuctionSearchBar.js
Normal file
@@ -0,0 +1,205 @@
|
||||
import { TextInput, BtnWrapper, InputLabel, SelectInput, InputGroup } from '../../../styles/Components';
|
||||
import Button from '../../common/button/Button';
|
||||
import { SearchBarLayout, SearchPeriod } from '../../common/SearchBar';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { LandAuctionView } from '../../../apis';
|
||||
import { landAuctionStatus, landSearchType, landSize, userSearchType } from '../../../assets/data';
|
||||
|
||||
export const useLandAuctionSearch = (token, initialPageSize) => {
|
||||
const [searchParams, setSearchParams] = useState({
|
||||
landType: 'ID',
|
||||
landData: '',
|
||||
userType: 'GUID',
|
||||
userData: '',
|
||||
landSize: 'ALL',
|
||||
status: 'ALL',
|
||||
auctionStartDate: '',
|
||||
auctionEndDate: '',
|
||||
orderBy: 'DESC',
|
||||
pageSize: initialPageSize,
|
||||
currentPage: 1
|
||||
});
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetchData(searchParams); // 컴포넌트 마운트 시 초기 데이터 로드
|
||||
}, [token]);
|
||||
|
||||
const fetchData = useCallback(async (params) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const result = await LandAuctionView(
|
||||
token,
|
||||
params.landType,
|
||||
params.landData,
|
||||
params.userType,
|
||||
params.userData,
|
||||
params.landSize,
|
||||
params.status,
|
||||
params.auctionStartDate && new Date(params.auctionStartDate).toISOString(),
|
||||
params.auctionEndDate && new Date(params.auctionEndDate).toISOString(),
|
||||
params.orderBy,
|
||||
params.pageSize,
|
||||
params.currentPage
|
||||
);
|
||||
setData(result);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Error fetching auction data:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [token]);
|
||||
|
||||
const updateSearchParams = useCallback((newParams) => {
|
||||
setSearchParams(prev => ({
|
||||
...prev,
|
||||
...newParams
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const handleSearch = useCallback(async (newParams = {}) => {
|
||||
const updatedParams = {
|
||||
...searchParams,
|
||||
...newParams,
|
||||
currentPage: newParams.currentPage || 1 // Reset to first page on new search
|
||||
};
|
||||
updateSearchParams(updatedParams);
|
||||
return await fetchData(updatedParams);
|
||||
}, [searchParams, fetchData]);
|
||||
|
||||
const handleReset = useCallback(async () => {
|
||||
const resetParams = {
|
||||
landType: 'ID',
|
||||
landData: '',
|
||||
userType: 'GUID',
|
||||
userData: '',
|
||||
landSize: 'ALL',
|
||||
status: 'ALL',
|
||||
auctionStartDate: '',
|
||||
auctionEndDate: '',
|
||||
orderBy: 'DESC',
|
||||
pageSize: initialPageSize,
|
||||
currentPage: 1
|
||||
};
|
||||
setSearchParams(resetParams);
|
||||
return await fetchData(resetParams);
|
||||
}, [initialPageSize, fetchData]);
|
||||
|
||||
const handlePageChange = useCallback(async (newPage) => {
|
||||
return await handleSearch({ currentPage: newPage });
|
||||
}, [handleSearch]);
|
||||
|
||||
const handlePageSizeChange = useCallback(async (newSize) => {
|
||||
return await handleSearch({ pageSize: newSize, currentPage: 1 });
|
||||
}, [handleSearch]);
|
||||
|
||||
const handleOrderByChange = useCallback(async (newOrder) => {
|
||||
return await handleSearch({ orderBy: newOrder });
|
||||
}, [handleSearch]);
|
||||
|
||||
return {
|
||||
searchParams,
|
||||
loading,
|
||||
data,
|
||||
handleSearch,
|
||||
handleReset,
|
||||
handlePageChange,
|
||||
handlePageSizeChange,
|
||||
handleOrderByChange,
|
||||
updateSearchParams
|
||||
};
|
||||
};
|
||||
|
||||
const LandAuctionSearchBar = ({ searchParams, onSearch, onReset }) => {
|
||||
const handleSubmit = event => {
|
||||
event.preventDefault();
|
||||
|
||||
onSearch(searchParams);
|
||||
};
|
||||
|
||||
const searchList = [
|
||||
<>
|
||||
<InputGroup>
|
||||
<SelectInput value={searchParams.landType} onChange={e => onSearch({landType: e.target.value })}>
|
||||
{landSearchType.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<TextInput
|
||||
type="text"
|
||||
placeholder={searchParams.landType === 'ID' ? '랜드 ID 입력' : '랜드명 입력'}
|
||||
value={searchParams.landData}
|
||||
width="300px"
|
||||
onChange={e => onSearch({ landData: e.target.value })}
|
||||
/>
|
||||
</InputGroup>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>낙찰자</InputLabel>
|
||||
<InputGroup>
|
||||
<SelectInput value={searchParams.userType} onChange={e => onSearch({userType: e.target.value })}>
|
||||
{userSearchType.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<TextInput
|
||||
type="text"
|
||||
placeholder={searchParams.userType === 'GUID' ? 'GUID 입력' : '닉네임 입력'}
|
||||
value={searchParams.userData}
|
||||
width="300px"
|
||||
onChange={e => onSearch({ userData: e.target.value })}
|
||||
/>
|
||||
</InputGroup>
|
||||
</>
|
||||
];
|
||||
|
||||
const optionList = [
|
||||
<>
|
||||
<InputLabel>랜드크기</InputLabel>
|
||||
<SelectInput value={searchParams.landSize} onChange={e => onSearch({ landSize: e.target.value }, false)} >
|
||||
{landSize.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>경매상태</InputLabel>
|
||||
<SelectInput value={searchParams.status} onChange={e => onSearch({ status: e.target.value }, false)}>
|
||||
{landAuctionStatus.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>경매 일자</InputLabel>
|
||||
<SearchPeriod
|
||||
startDate={searchParams.auctionStartDate}
|
||||
handleStartDate={date => onSearch({ auctionStartDate: date }, false)}
|
||||
endDate={searchParams.auctionEndDate}
|
||||
handleEndDate={date => onSearch({ auctionEndDate: date }, false)}
|
||||
/>
|
||||
</>,
|
||||
<></>,<></>,
|
||||
<>
|
||||
<BtnWrapper $gap="8px">
|
||||
<Button theme="reset" handleClick={onReset} type="button" />
|
||||
<Button theme="search" text="검색" handleClick={handleSubmit} type="submit" />
|
||||
</BtnWrapper>
|
||||
</>,
|
||||
];
|
||||
return <SearchBarLayout firstColumnData={searchList} secondColumnData={optionList} direction={'column'} />;
|
||||
};
|
||||
|
||||
export default LandAuctionSearchBar;
|
||||
143
src/components/ServiceManage/searchBar/MailListSearchBar.js
Normal file
143
src/components/ServiceManage/searchBar/MailListSearchBar.js
Normal file
@@ -0,0 +1,143 @@
|
||||
import styled from 'styled-components';
|
||||
import { TextInput, BtnWrapper, InputLabel, SelectInput } from '../../../styles/Components';
|
||||
import Button from '../../common/button/Button';
|
||||
import { SearchBarLayout, SearchPeriod } from '../../common/SearchBar';
|
||||
import { useState } from 'react';
|
||||
import { mailReceiveType, mailSendStatus, mailSendType, mailType } from '../../../assets/data';
|
||||
|
||||
const MailListSearchBar = ({ handleSearch, setResultData }) => {
|
||||
const [searchData, setSearchData] = useState({
|
||||
mailTitle: '',
|
||||
content: '',
|
||||
sendType: 'ALL',
|
||||
sendStatus: 'ALL',
|
||||
mailType: 'ALL',
|
||||
receiveType: 'ALL',
|
||||
sendDate: '',
|
||||
endDate: '',
|
||||
});
|
||||
|
||||
const handleSubmit = event => {
|
||||
event.preventDefault();
|
||||
|
||||
handleSearch(
|
||||
searchData.mailTitle,
|
||||
searchData.content,
|
||||
searchData.sendType ? searchData.sendType : 'ALL',
|
||||
searchData.sendStatus ? searchData.sendStatus : 'ALL',
|
||||
searchData.mailType ? searchData.mailType : 'ALL',
|
||||
searchData.receiveType ? searchData.receiveType : 'ALL',
|
||||
searchData.sendDate ? searchData.sendDate : '',
|
||||
searchData.endDate ? searchData.endDate : new Date(),
|
||||
(searchData.sendDate && searchData.endDate === '') && setSearchData({ sendDate : searchData.sendDate ,endDate : new Date()}),
|
||||
);
|
||||
|
||||
setResultData(searchData);
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setSearchData({
|
||||
mailTitle: '',
|
||||
content: '',
|
||||
sendType: 'ALL',
|
||||
sendStatus: 'ALL',
|
||||
mailType: 'ALL',
|
||||
receiveType: 'ALL',
|
||||
sendDate: '',
|
||||
endDate: '',
|
||||
order: 'DESC',
|
||||
});
|
||||
|
||||
handleSearch('', '', 'ALL', 'ALL', 'ALL', 'ALL', '', '');
|
||||
setResultData('', '', 'ALL', 'ALL', 'ALL', 'ALL', '', '');
|
||||
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
// console.log("searchData.endDate", searchData.endDate)
|
||||
|
||||
const searchList = [
|
||||
<>
|
||||
<InputLabel>우편 제목</InputLabel>
|
||||
<TextInput
|
||||
type="text"
|
||||
placeholder="우편 제목"
|
||||
value={searchData.mailTitle}
|
||||
onChange={e => setSearchData({ ...searchData, mailTitle: e.target.value })}
|
||||
/>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>조회 일자</InputLabel>
|
||||
<SearchPeriod
|
||||
startDate={searchData.sendDate}
|
||||
handleStartDate={data => {
|
||||
setSearchData({ ...searchData, sendDate: data });
|
||||
}}
|
||||
endDate={searchData.endDate}
|
||||
handleEndDate={data => setSearchData({ ...searchData, endDate: data })}
|
||||
maxDate={new Date()}
|
||||
/>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>우편 내용</InputLabel>
|
||||
<TextInput
|
||||
type="text"
|
||||
placeholder="우편 내용(공백으로 구분)"
|
||||
value={searchData.content}
|
||||
onChange={e => setSearchData({ ...searchData, content: e.target.value })}
|
||||
/>
|
||||
</>
|
||||
];
|
||||
|
||||
const optionList = [
|
||||
<>
|
||||
<InputLabel>발송 방식</InputLabel>
|
||||
<SelectInput value={searchData.sendType} onChange={e => setSearchData({ ...searchData, sendType: e.target.value })}>
|
||||
{mailSendType.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>발송 상태</InputLabel>
|
||||
<SelectInput value={searchData.sendStatus} onChange={e => setSearchData({ ...searchData, sendStatus: e.target.value })}>
|
||||
{mailSendStatus.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>우편 타입</InputLabel>
|
||||
<SelectInput value={searchData.mailType} onChange={e => setSearchData({ ...searchData, mailType: e.target.value })}>
|
||||
{mailType.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>수신 대상</InputLabel>
|
||||
<SelectInput value={searchData.receiveType} onChange={e => setSearchData({ ...searchData, receiveType: e.target.value })}>
|
||||
{mailReceiveType.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<>
|
||||
<BtnWrapper $gap="8px">
|
||||
<Button theme="reset" handleClick={handleReset} type="button" />
|
||||
<Button theme="search" text="검색" handleClick={handleSubmit} type="submit" />
|
||||
</BtnWrapper>
|
||||
</>,
|
||||
];
|
||||
return <SearchBarLayout firstColumnData={searchList} secondColumnData={optionList} direction={'column'} />;
|
||||
};
|
||||
|
||||
export default MailListSearchBar;
|
||||
142
src/components/ServiceManage/searchBar/ReportListSearchBar.js
Normal file
142
src/components/ServiceManage/searchBar/ReportListSearchBar.js
Normal file
@@ -0,0 +1,142 @@
|
||||
import { styled } from 'styled-components';
|
||||
import { TextInput, BtnWrapper, InputLabel, SelectInput } from '../../../styles/Components';
|
||||
import Button from '../../common/button/Button';
|
||||
import { SearchBarLayout, SearchPeriod } from '../../common/SearchBar';
|
||||
import { useState } from 'react';
|
||||
|
||||
const ReportListSearchBar = ({ handleSearch, setResultData }) => {
|
||||
let d = new Date();
|
||||
const START_DATE = new Date(new Date(d.setDate(d.getDate() - 1)).setHours(0, 0, 0, 0));
|
||||
const END_DATE = new Date();
|
||||
|
||||
const reportType = [
|
||||
{ value: 'ALL', name: '전체' },
|
||||
{ value: 'UNMANNERED_ACT', name: '비매너 행위' },
|
||||
{ value: 'USE_UNHEALTHY_NAMES', name: '불건전 이름 사용' },
|
||||
{ value: 'CASH_TRADING', name: '현금거래 행위' },
|
||||
{ value: 'INTERFERENCE_GAME', name: '게임 진행 방해' },
|
||||
{ value: 'INTERFERENCE_SERVICE', name: '운영서비스 방해' },
|
||||
{ value: 'ACCOUNT_EXPLOITATION', name: '계정도용' },
|
||||
{ value: 'BUG_ABUSING', name: '버그/어뷰징' },
|
||||
{ value: 'USE_HACK', name: '불법프로그램 사용' },
|
||||
{ value: 'LEAK_PERSONAL_INFO', name: '개인정보 유출' },
|
||||
{ value: 'PRETENDING_GM', name: '운영자 사칭' },
|
||||
];
|
||||
|
||||
const reportState = [
|
||||
{ value: 'ALL', name: '전체' },
|
||||
{ value: 'RESOLVED', name: '해결' },
|
||||
{ value: 'UNRESOLVED', name: '미해결' },
|
||||
];
|
||||
|
||||
const searchType = [
|
||||
{ value: 'ALL', name: '전체' },
|
||||
{ value: 'GUID', name: '신고자' },
|
||||
{ value: 'EMAIL', name: '담당자' },
|
||||
];
|
||||
|
||||
const [searchData, setSearchData] = useState({
|
||||
startDate: START_DATE,
|
||||
endDate: END_DATE,
|
||||
reportType: 'ALL',
|
||||
status: 'ALL',
|
||||
searchType: 'ALL',
|
||||
searchKey: '',
|
||||
});
|
||||
|
||||
// console.log(searchData);
|
||||
|
||||
const handleSubmit = event => {
|
||||
event.preventDefault();
|
||||
|
||||
handleSearch(
|
||||
searchData.startDate ? new Date(searchData.startDate).toISOString().split('.')[0] : new Date(START_DATE).toISOString().split('.')[0],
|
||||
searchData.endDate ? new Date(searchData.endDate).toISOString().split('.')[0] : new Date(END_DATE).toISOString().split('.')[0],
|
||||
searchData.reportType ? searchData.reportType : 'ALL',
|
||||
searchData.status ? searchData.status : 'ALL',
|
||||
searchData.searchType ? searchData.searchType : 'ALL',
|
||||
searchData.searchKey ? searchData.searchKey : '',
|
||||
);
|
||||
setResultData(searchData);
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setSearchData({
|
||||
startDate: START_DATE,
|
||||
endDate: END_DATE,
|
||||
reportType: 'ALL',
|
||||
status: 'ALL',
|
||||
searchKey: '',
|
||||
order: 'DESC',
|
||||
});
|
||||
handleSearch(START_DATE, END_DATE, 'ALL', 'ALL', 'ALL', '');
|
||||
setResultData(START_DATE, END_DATE, 'ALL', 'ALL', 'ALL', '');
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
const searchList = [
|
||||
<>
|
||||
<InputLabel>신고 일자</InputLabel>
|
||||
<SearchPeriod
|
||||
startDate={searchData.startDate}
|
||||
handleStartDate={data => {
|
||||
setSearchData({ ...searchData, startDate: data });
|
||||
}}
|
||||
endDate={searchData.endDate}
|
||||
handleEndDate={data => setSearchData({ ...searchData, endDate: data })}
|
||||
maxDate={new Date()}
|
||||
/>
|
||||
</>,
|
||||
];
|
||||
|
||||
const optionList = [
|
||||
<>
|
||||
<InputLabel>신고 유형</InputLabel>
|
||||
<SelectInput value={searchData.reportType || ''} onChange={e => setSearchData({ ...searchData, reportType: e.target.value })}>
|
||||
{reportType.map((data, index) => (
|
||||
<option key={index} value={data.value || ''}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>상태</InputLabel>
|
||||
<SelectInput value={searchData.status || ''} onChange={e => setSearchData({ ...searchData, status: e.target.value })}>
|
||||
{reportState.map((data, index) => (
|
||||
<option key={index} value={data.value || ''}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>신고자 / 담당자</InputLabel>
|
||||
<InputGroup>
|
||||
<SelectInput value={searchData.searchType || ''} onChange={e => setSearchData({ ...searchData, searchType: e.target.value })}>
|
||||
{searchType.map((data, index) => (
|
||||
<option key={index} value={data.value || ''}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<TextInput placeholder="입력" onChange={e => setSearchData({ ...searchData, searchKey: e.target.value })} />
|
||||
</InputGroup>
|
||||
</>,
|
||||
<>
|
||||
<BtnWrapper $gap="8px">
|
||||
<Button theme="reset" handleClick={handleReset} type="button" />
|
||||
<Button theme="search" text="검색" handleClick={handleSubmit} type="submit" />
|
||||
</BtnWrapper>
|
||||
</>,
|
||||
];
|
||||
return <SearchBarLayout firstColumnData={searchList} secondColumnData={optionList} direction={'column'} />;
|
||||
};
|
||||
|
||||
export default ReportListSearchBar;
|
||||
|
||||
const InputGroup = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
`;
|
||||
117
src/components/ServiceManage/searchBar/UserBlockSearchBar.js
Normal file
117
src/components/ServiceManage/searchBar/UserBlockSearchBar.js
Normal file
@@ -0,0 +1,117 @@
|
||||
import { useState } from 'react';
|
||||
import { TextInput, BtnWrapper, InputLabel, SelectInput, InputGroup } from '../../../styles/Components';
|
||||
import Button from '../../common/button/Button';
|
||||
import { SearchBarLayout } from '../../common/SearchBar';
|
||||
import { blockPeriod, blockSanctions, blockSearchType, blockStatus } from '../../../assets/data';
|
||||
import { userSearchType } from '../../../assets/data/options';
|
||||
|
||||
const UserBlockSearchBar = ({ handleSearch, setResultData }) => {
|
||||
const [searchData, setSearchData] = useState({
|
||||
searchType: 'GUID',
|
||||
data: '',
|
||||
email: '',
|
||||
status: 'ALL',
|
||||
sanctions: 'ALL',
|
||||
period: 'ALL',
|
||||
});
|
||||
|
||||
const handleSubmit = event => {
|
||||
event.preventDefault();
|
||||
|
||||
handleSearch(
|
||||
searchData.searchType ? searchData.searchType : 'GUID',
|
||||
searchData.data,
|
||||
searchData.email,
|
||||
searchData.status ? searchData.status : 'ALL',
|
||||
searchData.sanctions ? searchData.sanctions : 'ALL',
|
||||
searchData.period ? searchData.period : 'ALL',
|
||||
);
|
||||
|
||||
setResultData(searchData);
|
||||
};
|
||||
|
||||
// 초기화 버튼
|
||||
const handleReset = () => {
|
||||
setSearchData({
|
||||
searchType: 'GUID',
|
||||
data: '',
|
||||
email: '',
|
||||
status: 'ALL',
|
||||
sanctions: 'ALL',
|
||||
period: 'ALL',
|
||||
});
|
||||
handleSearch('GUID', '', '', 'ALL', 'ALL', 'ALL');
|
||||
setResultData('GUID', '', '', 'ALL', 'ALL', 'ALL');
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
// console.log(searchData);
|
||||
|
||||
const searchList = [
|
||||
<>
|
||||
<InputLabel>대상</InputLabel>
|
||||
<InputGroup>
|
||||
<SelectInput value={searchData.searchType} onChange={e => setSearchData({ ...searchData, searchType: e.target.value })}>
|
||||
{userSearchType.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<TextInput
|
||||
type="text"
|
||||
placeholder={searchData.searchType === 'GUID' ? 'GUID 입력' : '닉네임 입력'}
|
||||
value={searchData.data}
|
||||
width="600px"
|
||||
onChange={e => setSearchData({ ...searchData, data: e.target.value })}
|
||||
/>
|
||||
</InputGroup>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>등록자</InputLabel>
|
||||
<TextInput type="text" placeholder="이메일 입력" width="600px" value={searchData.email} onChange={e => setSearchData({ ...searchData, email: e.target.value })} />
|
||||
</>,
|
||||
];
|
||||
|
||||
const optionList = [
|
||||
<>
|
||||
<InputLabel>상태</InputLabel>
|
||||
<SelectInput value={searchData.status} onChange={e => setSearchData({ ...searchData, status: e.target.value })}>
|
||||
{blockStatus.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>제재 사유</InputLabel>
|
||||
<SelectInput value={searchData.sanctions} onChange={e => setSearchData({ ...searchData, sanctions: e.target.value })}>
|
||||
{blockSanctions.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>제재 기간</InputLabel>
|
||||
<SelectInput value={searchData.period} onChange={e => setSearchData({ ...searchData, period: e.target.value })}>
|
||||
{blockPeriod.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<>
|
||||
<BtnWrapper $gap="8px">
|
||||
<Button theme="reset" handleClick={handleReset} />
|
||||
<Button theme="search" text="검색" type="submit" handleClick={handleSubmit} />
|
||||
</BtnWrapper>
|
||||
</>,
|
||||
];
|
||||
return <SearchBarLayout firstColumnData={searchList} secondColumnData={optionList} direction={'column'} />;
|
||||
};
|
||||
|
||||
export default UserBlockSearchBar;
|
||||
33
src/components/Skeleton/PageSkeleton.js
Normal file
33
src/components/Skeleton/PageSkeleton.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Skeleton, TableWrapper } from '../../styles/Components';
|
||||
import { Fragment } from 'react';
|
||||
import { styled } from 'styled-components';
|
||||
|
||||
export const PageSkeleton = ({width = '100%', count = 8}) => {
|
||||
return (
|
||||
<>
|
||||
<Skeleton width={width} height="40px" />
|
||||
<SearchbarStyle>
|
||||
<Skeleton width={width} height="80px" />
|
||||
</SearchbarStyle>
|
||||
<Skeleton width={width} height="40px" />
|
||||
<TableWrapper>
|
||||
{Array.from({ length: count }).map((_, index) => (
|
||||
<Skeleton key={index} width={width} height="40px" />
|
||||
))}
|
||||
</TableWrapper>
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const SearchbarStyle = styled.div`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 20px;
|
||||
border-top: 1px solid #000;
|
||||
border-bottom: 1px solid #000;
|
||||
margin: 0 0 40px;
|
||||
flex-flow: ${props => props.direction};
|
||||
gap: ${props => (props.direction === 'column' ? '20px' : '20px 0')};
|
||||
`;
|
||||
18
src/components/Skeleton/TableSkeleton.js
Normal file
18
src/components/Skeleton/TableSkeleton.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import { UserTableWrapper } from '../../styles/ModuleComponents';
|
||||
import { Skeleton } from '../../styles/Components';
|
||||
|
||||
export const TableSkeleton = ({width = '100%', count = 5}) => {
|
||||
return (
|
||||
<UserTableWrapper>
|
||||
{Array.from({ length: count }).map((_, index) => (
|
||||
<Skeleton key={index} width={width} height="40px" />
|
||||
))}
|
||||
{/*<Skeleton width={width} height="40px" />*/}
|
||||
{/*<Skeleton width={width} height="40px" />*/}
|
||||
{/*<Skeleton width={width} height="40px" />*/}
|
||||
{/*<Skeleton width={width} height="40px" />*/}
|
||||
{/*<Skeleton width={width} height="40px" />*/}
|
||||
</UserTableWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
58
src/components/Skeleton/UserInfoSkeleton.js
Normal file
58
src/components/Skeleton/UserInfoSkeleton.js
Normal file
@@ -0,0 +1,58 @@
|
||||
import { Skeleton, SkeletonImg } from '../../styles/Components';
|
||||
import { styled } from 'styled-components';
|
||||
|
||||
export const UserInfoSkeleton = () => {
|
||||
return (
|
||||
<>
|
||||
<UserDefault>
|
||||
<ProfileWrapper>
|
||||
<SkeletonImg width="200px" height="150px" />
|
||||
</ProfileWrapper>
|
||||
<UserInfoTable>
|
||||
<Skeleton width="530px" height="30px" />
|
||||
<Skeleton width="530px" height="30px" />
|
||||
<Skeleton width="530px" height="30px" />
|
||||
<Skeleton width="530px" height="30px" />
|
||||
<Skeleton width="530px" height="30px" />
|
||||
<Skeleton width="530px" height="30px" />
|
||||
<Skeleton width="530px" height="30px" />
|
||||
<Skeleton width="530px" height="30px" />
|
||||
<Skeleton width="530px" height="30px" />
|
||||
<Skeleton width="530px" height="30px" />
|
||||
</UserInfoTable>
|
||||
</UserDefault>
|
||||
<UserInfoTable>
|
||||
<Skeleton width="750px" height="30px" />
|
||||
<Skeleton width="750px" height="30px" />
|
||||
<Skeleton width="750px" height="30px" />
|
||||
<Skeleton width="750px" height="30px" />
|
||||
</UserInfoTable>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const UserDefault = styled.div`
|
||||
display: flex;
|
||||
gap: 40px;
|
||||
margin-bottom: 30px;
|
||||
justify-content: space-between;
|
||||
`;
|
||||
const ProfileWrapper = styled.div`
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
border-radius: 75px;
|
||||
overflow: hidden;
|
||||
display: inline-flex;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
background: #fff;
|
||||
`;
|
||||
|
||||
const UserInfoTable = styled.table`
|
||||
width: 100%;
|
||||
max-width: ${props => props.$maxwidth || 'auto'};
|
||||
font-size: 13px;
|
||||
border-radius: 15px;
|
||||
overflow: hidden;
|
||||
flex-grow: 1;
|
||||
`;
|
||||
96
src/components/UserManage/AdminViewSearchBar.js
Normal file
96
src/components/UserManage/AdminViewSearchBar.js
Normal file
@@ -0,0 +1,96 @@
|
||||
import { styled } from 'styled-components';
|
||||
|
||||
import { TextInput, BtnWrapper, InputLabel, SelectInput } from '../../styles/Components';
|
||||
import Button from '../common/button/Button';
|
||||
import CheckBox from '../common/input/CheckBox';
|
||||
import { SearchBarLayout } from '../common/SearchBar';
|
||||
import { useState } from 'react';
|
||||
|
||||
const AdminViewSearchBar = ({ handleSearch, groupList, setResultData, setCurrentPage }) => {
|
||||
const [searchData, setSearchData] = useState({
|
||||
searchOption: 'name',
|
||||
data: '',
|
||||
authorityOption: '',
|
||||
joinCheck: false,
|
||||
});
|
||||
|
||||
const handleSubmit = event => {
|
||||
event.preventDefault();
|
||||
|
||||
handleSearch(searchData.searchOption, searchData.data, searchData.authorityOption, searchData.joinCheck);
|
||||
setResultData(searchData);
|
||||
setCurrentPage(1);
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setSearchData({
|
||||
searchOption: 'name',
|
||||
data: '',
|
||||
authorityOption: '',
|
||||
joinCheck: false,
|
||||
});
|
||||
handleSearch('name', '', '', false);
|
||||
setResultData('name', '', '', false);
|
||||
setCurrentPage(1);
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
const searchOption = [
|
||||
{ value: 'name', name: '이름' },
|
||||
{ value: 'email', name: 'ID(이메일)' },
|
||||
];
|
||||
|
||||
const searchList = [
|
||||
<>
|
||||
<InputLabel>검색</InputLabel>
|
||||
<InputGroup>
|
||||
<SelectInput value={searchData.searchOption} onChange={e => setSearchData({ ...searchData, searchOption: e.target.value })}>
|
||||
{searchOption.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<TextInput
|
||||
value={searchData.data}
|
||||
type="text"
|
||||
onChange={e => setSearchData({ ...searchData, data: e.target.value })}
|
||||
placeholder={searchData.searchOption === 'name' ? '이름 입력' : '이메일 입력'}
|
||||
/>
|
||||
</InputGroup>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>관리자 권한</InputLabel>
|
||||
<SelectInput value={searchData.authorityOption} onChange={e => setSearchData({ ...searchData, authorityOption: e.target.value })}>
|
||||
<option key={1} value={''}>
|
||||
전체
|
||||
</option>
|
||||
{groupList &&
|
||||
groupList.map((data, index) => (
|
||||
<option key={index} value={data.group_id}>
|
||||
{data.group_nm}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<>
|
||||
{/*<CheckBox id="input-check" label="가입 신청" checked={searchData.joinCheck} setData={e => setSearchData({ ...searchData, joinCheck: e.target.checked })} />*/}
|
||||
</>,
|
||||
<>
|
||||
<BtnWrapper $gap="8px">
|
||||
<Button theme="reset" handleClick={handleReset} type="button" />
|
||||
<Button theme="search" text="검색" handleClick={handleSubmit} type="submit" />
|
||||
</BtnWrapper>
|
||||
</>,
|
||||
];
|
||||
|
||||
return <SearchBarLayout firstColumnData={searchList} />;
|
||||
};
|
||||
|
||||
export default AdminViewSearchBar;
|
||||
|
||||
const InputGroup = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
`;
|
||||
82
src/components/UserManage/AuthGroupRows.js
Normal file
82
src/components/UserManage/AuthGroupRows.js
Normal file
@@ -0,0 +1,82 @@
|
||||
import AuthCheckBox from '../common/input/AuthCheckBox';
|
||||
import CheckBox from '../common/input/CheckBox';
|
||||
import { isGroupFullySelected, isItemDisabled } from '../../utils';
|
||||
|
||||
const AuthGroupRows = ({ group, selectedPermissions, onPermissionChange }) => {
|
||||
const handleGroupSelect = () => {
|
||||
const allPermissions = group.items.flatMap(item =>
|
||||
Object.values(item.permissions)
|
||||
);
|
||||
|
||||
const isAllSelected = allPermissions.every(id =>
|
||||
selectedPermissions.includes(id)
|
||||
);
|
||||
|
||||
onPermissionChange(group.id, isAllSelected ? 'none' : 'all');
|
||||
};
|
||||
|
||||
return group.items.map((item, index) => (
|
||||
<tr key={item.id} className={index === 0 ? 'table-line' : ''}>
|
||||
{index === 0 && (
|
||||
<>
|
||||
<td rowSpan={group.items.length}>
|
||||
<AuthCheckBox
|
||||
id={group.id}
|
||||
handleCheck={handleGroupSelect}
|
||||
checked={isGroupFullySelected(group, selectedPermissions)}
|
||||
/>
|
||||
</td>
|
||||
<td rowSpan={group.items.length}>{group.title}</td>
|
||||
</>
|
||||
)}
|
||||
<td>{item.title}</td>
|
||||
<td>
|
||||
<AuthCheckBox
|
||||
id={item.permissions.read?.toString()}
|
||||
name={group.id}
|
||||
checked={selectedPermissions.includes(item.permissions.read)}
|
||||
setData={(e) => onPermissionChange(group.id, e.target.id)}
|
||||
disabled={isItemDisabled(item, selectedPermissions)}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
{item.permissions.confirm ? (
|
||||
<AuthCheckBox
|
||||
id={item.permissions.confirm?.toString()}
|
||||
name={group.id}
|
||||
checked={selectedPermissions.includes(item.permissions.confirm)}
|
||||
setData={(e) => onPermissionChange(group.id, e.target.id)}
|
||||
/>
|
||||
) : (
|
||||
<CheckBox disabled={true} />
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
{item.permissions.update ? (
|
||||
<AuthCheckBox
|
||||
id={item.permissions.update?.toString()}
|
||||
name={group.id}
|
||||
checked={selectedPermissions.includes(item.permissions.update)}
|
||||
setData={(e) => onPermissionChange(group.id, e.target.id)}
|
||||
/>
|
||||
) : (
|
||||
<CheckBox disabled={true} />
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
{item.permissions.delete ? (
|
||||
<AuthCheckBox
|
||||
id={item.permissions.delete?.toString()}
|
||||
name={group.id}
|
||||
checked={selectedPermissions.includes(item.permissions.delete)}
|
||||
setData={(e) => onPermissionChange(group.id, e.target.id)}
|
||||
/>
|
||||
) : (
|
||||
<CheckBox disabled={true} />
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
));
|
||||
};
|
||||
|
||||
export default AuthGroupRows;
|
||||
39
src/components/UserManage/AuthRegistBar.js
Normal file
39
src/components/UserManage/AuthRegistBar.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import { TextInput, InputLabel, SearchBarAlert } from '../../styles/Components';
|
||||
import Button from '../common/button/Button';
|
||||
import { SearchBarLayout } from '../common/SearchBar';
|
||||
|
||||
const AuthRegistBar = ({ handleRegistModalClose, $isNullValue, registData, setRegistData, nullMessage }) => {
|
||||
const searchList = [
|
||||
<>
|
||||
<InputLabel>관리자 그룹명*</InputLabel>
|
||||
<TextInput
|
||||
name="group_nm"
|
||||
type="text"
|
||||
placeholder="관리자 그룹명 입력"
|
||||
id="group_nm"
|
||||
width="300px"
|
||||
onChange={e => setRegistData({ ...registData, group_nm: e.target.value.trimStart() })}
|
||||
/>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>설명</InputLabel>
|
||||
<TextInput
|
||||
name="description"
|
||||
type="text"
|
||||
placeholder="그룹 설명글을 입력하세요(최대 100자)"
|
||||
id="description"
|
||||
width="300px"
|
||||
onChange={e => setRegistData({ ...registData, description: e.target.value.trimStart() })}
|
||||
maxLength={100}
|
||||
/>
|
||||
</>,
|
||||
<>
|
||||
<Button theme={registData.group_nm ? 'search' : 'disable'} text="등록" type="button" handleClick={handleRegistModalClose} width="100px" height="35px" />
|
||||
{$isNullValue && <SearchBarAlert>{nullMessage}</SearchBarAlert>}
|
||||
</>,
|
||||
];
|
||||
|
||||
return <SearchBarLayout firstColumnData={searchList} />;
|
||||
};
|
||||
|
||||
export default AuthRegistBar;
|
||||
278
src/components/UserManage/CaliumRequestRegistModal.js
Normal file
278
src/components/UserManage/CaliumRequestRegistModal.js
Normal file
@@ -0,0 +1,278 @@
|
||||
import { useState, Fragment, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Button from '../common/button/Button';
|
||||
import Loading from '../common/Loading';
|
||||
|
||||
import {
|
||||
Title,
|
||||
BtnWrapper,
|
||||
SearchBarAlert,
|
||||
} from '../../styles/Components';
|
||||
|
||||
import {
|
||||
FormGroup, FormHelperText, FormInput, FormLabel,
|
||||
FormTextArea, FormTextAreaWrapper, MessageWrapper, FormRowGroup, FormRowInput,
|
||||
} from '../../styles/ModuleComponents';
|
||||
import { modalTypes, caliumRequestInitData } from '../../assets/data';
|
||||
import {DynamicModal, Modal} from '../common';
|
||||
import { CaliumLimitCount, CaliumRequestRegist } from '../../apis/Calium';
|
||||
|
||||
const CaliumRequestRegistModal = ({ registView, setRegistView, userInfo }) => {
|
||||
const { t } = useTranslation();
|
||||
const token = sessionStorage.getItem('token');
|
||||
|
||||
const [loading, setLoading] = useState(false); // 로딩 창
|
||||
const [modalState, setModalState] = useState({
|
||||
cancelModal: 'hidden',
|
||||
registConfirmModal: 'hidden',
|
||||
registCompleteModal: 'hidden',
|
||||
}); // 모달 관리
|
||||
|
||||
const [isNullValue, setIsNullValue] = useState(false); // 데이터 값 체크
|
||||
const [alertMsg, setAlertMsg] = useState('');
|
||||
|
||||
const [resultData, setResultData] = useState(caliumRequestInitData); //데이터 정보
|
||||
const [maxCount, setMaxCount] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
if (checkCondition()) {
|
||||
setIsNullValue(false);
|
||||
} else {
|
||||
setIsNullValue(true);
|
||||
}
|
||||
}, [resultData]);
|
||||
|
||||
// 입력 수량 처리
|
||||
const handleCount = e => {
|
||||
const regex = /^\d*\.?\d{0,2}$/;
|
||||
if (!regex.test(e.target.value) && e.target.value !== '-') {
|
||||
return;
|
||||
}
|
||||
|
||||
let count = 0;
|
||||
if (e.target.value === '-0') {
|
||||
count = 1;
|
||||
} else if (e.target.value < 0) {
|
||||
let plusNum = Math.abs(e.target.value);
|
||||
count = plusNum;
|
||||
} else{
|
||||
count = e.target.value;
|
||||
}
|
||||
setResultData((prevState) => ({
|
||||
...prevState,
|
||||
count: count,
|
||||
}));
|
||||
};
|
||||
|
||||
// 입력 글자 제한
|
||||
const handleInputData = e => {
|
||||
if (e.target.value.length > 100) {
|
||||
return;
|
||||
}
|
||||
setResultData((prevState) => ({
|
||||
...prevState,
|
||||
content: e.target.value.trimStart(),
|
||||
}));
|
||||
};
|
||||
|
||||
const handleModalView = (type) => {
|
||||
setModalState((prevState) => ({
|
||||
...prevState,
|
||||
[`${type}Modal`]: 'view',
|
||||
}));
|
||||
}
|
||||
|
||||
const handleModalClose = (type) => {
|
||||
setModalState((prevState) => ({
|
||||
...prevState,
|
||||
[`${type}Modal`]: 'hidden',
|
||||
}));
|
||||
}
|
||||
|
||||
const initData = () =>{
|
||||
setMaxCount(0);
|
||||
setResultData(caliumRequestInitData);
|
||||
}
|
||||
|
||||
const handleSubmit = async (type, param = null) => {
|
||||
switch (type) {
|
||||
case "maxCount":
|
||||
setLoading(true);
|
||||
await CaliumLimitCount(token, resultData).then(data => {
|
||||
setMaxCount(data.reward_total_count);
|
||||
setLoading(false);
|
||||
}).catch(reason => {
|
||||
console.log(reason);
|
||||
setLoading(false);
|
||||
setAlertMsg(t('SEARCH_LIMIT_FAIL'));
|
||||
})
|
||||
break;
|
||||
case "submit":
|
||||
if (!checkCondition()) return;
|
||||
|
||||
handleModalView('registConfirm');
|
||||
break;
|
||||
case "cancel":
|
||||
handleModalView('cancel');
|
||||
break;
|
||||
case "cancelConfirm":
|
||||
initData();
|
||||
handleModalClose('cancel');
|
||||
setRegistView();
|
||||
break;
|
||||
case "registConfirm":
|
||||
setLoading(true);
|
||||
|
||||
await CaliumRequestRegist(token, resultData).then(data => {
|
||||
setLoading(false);
|
||||
});
|
||||
|
||||
handleModalClose('registConfirm');
|
||||
handleModalView('registComplete');
|
||||
break;
|
||||
case "registComplete":
|
||||
initData();
|
||||
handleModalClose('registComplete');
|
||||
setRegistView();
|
||||
window.location.reload();
|
||||
break;
|
||||
case "warning":
|
||||
setAlertMsg('');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const checkCondition = () => {
|
||||
return (
|
||||
resultData.content !== ''
|
||||
&& resultData.count > 0
|
||||
&& resultData.dept !== ''
|
||||
&& maxCount > 0
|
||||
&& resultData.count <= maxCount
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal min="760px" $view={registView}>
|
||||
<Title $align="center">칼리움 충전 요청 등록</Title>
|
||||
<MessageWrapper>
|
||||
<FormRowGroup>
|
||||
<FormLabel>인출 가능 수량</FormLabel>
|
||||
<FormRowInput
|
||||
type="text"
|
||||
name="maxCount"
|
||||
value={maxCount}
|
||||
readOnly={true}
|
||||
/>
|
||||
<Button theme="find" text="조회" handleClick={e => handleSubmit('maxCount')} />
|
||||
{maxCount <= 0 &&
|
||||
<FormHelperText>
|
||||
{t('SEARCH_LIMIT_TOTAL')}
|
||||
</FormHelperText>
|
||||
}
|
||||
</FormRowGroup>
|
||||
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>부서</FormLabel>
|
||||
<FormInput
|
||||
type="text"
|
||||
name="department"
|
||||
placeholder="ex) 사업실 라이브운영팀"
|
||||
value={resultData.dept}
|
||||
onChange={e => {
|
||||
setResultData((prevState) => ({
|
||||
...prevState,
|
||||
dept: e.target.value.trim(),
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>요청수량</FormLabel>
|
||||
<FormInput
|
||||
type="number"
|
||||
name="count"
|
||||
value={resultData.count}
|
||||
step={"0.01"}
|
||||
min={0}
|
||||
onChange={e => handleCount(e)}
|
||||
/>
|
||||
<FormHelperText>
|
||||
{t('LENGTH_NUMBER_POINT_2')}
|
||||
</FormHelperText>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>요청사유</FormLabel>
|
||||
<FormTextAreaWrapper>
|
||||
<FormTextArea
|
||||
name="content"
|
||||
value={resultData.content}
|
||||
placeholder="요청사유를 입력해주세요."
|
||||
onChange={e => handleInputData(e)}
|
||||
maxLength={100}
|
||||
/>
|
||||
<FormHelperText>
|
||||
{t('LENGTH_TEXT_LIMIT_100',{count:resultData.content.length})}
|
||||
</FormHelperText>
|
||||
</FormTextAreaWrapper>
|
||||
</FormGroup>
|
||||
{isNullValue && <SearchBarAlert $marginTop="25px">{t('REQUIRED_VALUE_CHECK')}</SearchBarAlert>}
|
||||
</MessageWrapper>
|
||||
|
||||
<BtnWrapper $gap="10px" $justify="center" $marginTop="20px">
|
||||
<Button text="취소" theme="line" handleClick={() => handleSubmit('cancel')} />
|
||||
<Button
|
||||
type="submit"
|
||||
text="등록"
|
||||
name="등록버튼"
|
||||
theme={
|
||||
maxCount > 0 && resultData.content !== '' && resultData.dept !== '' && resultData.count > 0
|
||||
? 'primary'
|
||||
: 'disable'
|
||||
}
|
||||
handleClick={() => handleSubmit('submit')}
|
||||
/>
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
|
||||
{/* 확인 모달 */}
|
||||
<DynamicModal
|
||||
modalType={modalTypes.confirmOkCancel}
|
||||
view={modalState.registConfirmModal}
|
||||
modalText={t('CALIUM_REGIST_CONFIRM')}
|
||||
handleSubmit={() => handleSubmit('registConfirm')}
|
||||
handleCancel={() => handleModalClose('registConfirm')}
|
||||
/>
|
||||
{/* 완료 모달 */}
|
||||
<DynamicModal
|
||||
modalType={modalTypes.completed}
|
||||
view={modalState.registCompleteModal}
|
||||
modalText={t('CALIUM_REGIST_COMPLTE')}
|
||||
handleSubmit={() => handleSubmit('registComplete')}
|
||||
/>
|
||||
{/* 취소 모달 */}
|
||||
<DynamicModal
|
||||
modalType={modalTypes.confirmOkCancel}
|
||||
view={modalState.cancelModal}
|
||||
modalText={t('CALIUM_REGIST_CANCEL')}
|
||||
handleCancel={() => handleModalClose('cancel')}
|
||||
handleSubmit={() => handleSubmit('cancelConfirm')}
|
||||
/>
|
||||
{/* 경고 모달 */}
|
||||
<DynamicModal
|
||||
modalType={modalTypes.completed}
|
||||
view={alertMsg ? 'view' : 'hidden'}
|
||||
modalText={alertMsg}
|
||||
handleSubmit={() => handleSubmit('warning')}
|
||||
/>
|
||||
{loading && <Loading/>}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CaliumRequestRegistModal;
|
||||
|
||||
86
src/components/UserManage/CaliumRequestSearchBar.js
Normal file
86
src/components/UserManage/CaliumRequestSearchBar.js
Normal file
@@ -0,0 +1,86 @@
|
||||
import { TextInput, BtnWrapper, InputLabel, SelectInput } from '../../styles/Components';
|
||||
import Button from '../common/button/Button';
|
||||
import { SearchBarLayout, SearchPeriod } from '../common/SearchBar';
|
||||
import { useState } from 'react';
|
||||
import { caliumStatus } from '../../assets/data/options';
|
||||
|
||||
const CaliumRequestSearchBar = ({ handleSearch, setResultData }) => {
|
||||
const [searchData, setSearchData] = useState({
|
||||
content: '',
|
||||
status: 'ALL',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
});
|
||||
|
||||
const handleSubmit = event => {
|
||||
event.preventDefault();
|
||||
|
||||
handleSearch(
|
||||
searchData.content,
|
||||
searchData.status ? searchData.status : 'ALL',
|
||||
searchData.startDate ? searchData.startDate : '',
|
||||
searchData.endDate ? searchData.endDate : new Date(),
|
||||
(searchData.startDate && searchData.endDate === '') && setSearchData({ startDate : searchData.startDate ,endDate : new Date()}),
|
||||
);
|
||||
|
||||
setResultData(searchData);
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setSearchData({
|
||||
content: '',
|
||||
status: 'ALL',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
order: 'DESC',
|
||||
});
|
||||
|
||||
handleSearch('', 'ALL', '', '');
|
||||
setResultData('', 'ALL', '', '');
|
||||
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
const searchList = [
|
||||
<>
|
||||
<InputLabel>등록 일자</InputLabel>
|
||||
<SearchPeriod
|
||||
startDate={searchData.startDate}
|
||||
handleStartDate={data => {
|
||||
setSearchData({ ...searchData, startDate: data });
|
||||
}}
|
||||
endDate={searchData.endDate}
|
||||
handleEndDate={data => setSearchData({ ...searchData, endDate: data })}
|
||||
/>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>요청 내용</InputLabel>
|
||||
<TextInput
|
||||
type="text"
|
||||
placeholder="요청 내용"
|
||||
value={searchData.content}
|
||||
onChange={e => setSearchData({ ...searchData, content: e.target.value })}
|
||||
/>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>요청 상태</InputLabel>
|
||||
<SelectInput value={searchData.status} onChange={e => setSearchData({ ...searchData, status: e.target.value })}>
|
||||
{caliumStatus.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<>
|
||||
<BtnWrapper $gap="8px">
|
||||
<Button theme="reset" handleClick={handleReset} type="button" />
|
||||
<Button theme="search" text="검색" handleClick={handleSubmit} type="submit" />
|
||||
</BtnWrapper>
|
||||
</>,
|
||||
];
|
||||
|
||||
return <SearchBarLayout firstColumnData={searchList} direction={'column'} />;
|
||||
};
|
||||
|
||||
export default CaliumRequestSearchBar;
|
||||
30
src/components/UserManage/LogViewModal.js
Normal file
30
src/components/UserManage/LogViewModal.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import styled from 'styled-components';
|
||||
import Modal from '../common/modal/Modal';
|
||||
import Button from '../common/button/Button';
|
||||
import { Title, BtnWrapper } from '../../styles/Components';
|
||||
|
||||
const LogViewModal = ({ stateModal, handleModal, data }) => {
|
||||
const obj = data !== '' && JSON.parse(data);
|
||||
const fullStr = JSON.stringify(obj, null, '\t');
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal min="960px" $view={stateModal}>
|
||||
<Title $align="center">JSON INFO</Title>
|
||||
<JsonContainer>{fullStr}</JsonContainer>
|
||||
<BtnWrapper $justify="center">
|
||||
<Button theme="line" text="닫기" handleClick={() => handleModal('hidden')} />
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default LogViewModal;
|
||||
|
||||
const JsonContainer = styled.pre`
|
||||
border-radius: 3px;
|
||||
background: #f6f6f6;
|
||||
min-height: 400px;
|
||||
margin-bottom: 20px;
|
||||
`;
|
||||
129
src/components/UserManage/LogViewSearchBar.js
Normal file
129
src/components/UserManage/LogViewSearchBar.js
Normal file
@@ -0,0 +1,129 @@
|
||||
import { styled } from 'styled-components';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { TextInput, InputLabel, SelectInput, BtnWrapper } from '../../styles/Components';
|
||||
import Button from '../common/button/Button';
|
||||
import { SearchBarLayout, SearchPeriod } from '../common/SearchBar';
|
||||
|
||||
const LogViewSearchBar = ({ handleSearch, resultData }) => {
|
||||
const [searchData, setSearchData] = useState({
|
||||
searchOption: 'name',
|
||||
data: '',
|
||||
logOption: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
});
|
||||
|
||||
const searchOption = [
|
||||
{ value: 'name', name: '이름' },
|
||||
{ value: 'id', name: 'ID(이메일)' },
|
||||
];
|
||||
|
||||
const logOption = [
|
||||
{ value: '', name: '이력 선택' },
|
||||
{ value: 'LOGIN_PERMITTED', name: '로그인 승인' },
|
||||
{ value: 'ADMIN_INFO_UPDATE', name: '운영자 정보 수정' },
|
||||
{ value: 'ADMIN_INFO_DELETE', name: '운영자 정보 삭제' },
|
||||
{ value: 'PASSWORD_INIT', name: '비밀번호 초기화' },
|
||||
{ value: 'USER_INFO_UPDATE', name: '유저 정보 변경' },
|
||||
{ value: 'GROUP_AUTH_UPDATE', name: '그룹 권한 수정' },
|
||||
{ value: 'GROUP_DELETE', name: '그룹 삭제' },
|
||||
{ value: 'NOTICE_DELETE', name: '인게임 메시지 삭제' },
|
||||
{ value: 'MAIL_DELETE', name: '우편 삭제' },
|
||||
{ value: 'WHITELIST_DELETE', name: '화이트리스트 삭제' },
|
||||
{ value: 'BLACKLIST_DELETE', name: '유저 제재 삭제' },
|
||||
{ value: 'REPORT_DELETE', name: '신고내역 삭제' },
|
||||
];
|
||||
|
||||
const handleReset = () => {
|
||||
setSearchData({
|
||||
searchOption: 'name',
|
||||
data: '',
|
||||
logOption: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
});
|
||||
handleSearch('name', '', '', '', '');
|
||||
resultData('name', '', '', '', '');
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
const handleSubmit = event => {
|
||||
event.preventDefault();
|
||||
handleSearch(
|
||||
searchData.searchOption,
|
||||
searchData.data,
|
||||
searchData.logOption,
|
||||
searchData.startDate !== '' ? new Date(searchData.startDate).toISOString() : '',
|
||||
searchData.endDate !== '' ? new Date(searchData.endDate).toISOString() : '',
|
||||
);
|
||||
resultData(searchData);
|
||||
};
|
||||
|
||||
// console.log(searchData.startDate.length)
|
||||
|
||||
const searchList = [
|
||||
<>
|
||||
<InputLabel>검색</InputLabel>
|
||||
<InputGroup>
|
||||
<SelectInput value={searchData.searchOption} onChange={e => setSearchData({ ...searchData, searchOption: e.target.value })}>
|
||||
{searchOption.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<TextInput
|
||||
value={searchData.data}
|
||||
type="text"
|
||||
onChange={e => setSearchData({ ...searchData, data: e.target.value })}
|
||||
placeholder={searchData.searchOption === 'name' ? '이름 입력' : 'ID 입력'}
|
||||
/>
|
||||
</InputGroup>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>사용 이력</InputLabel>
|
||||
<InputGroup>
|
||||
<SelectInput value={searchData.logOption} onChange={e => setSearchData({ ...searchData, logOption: e.target.value })}>
|
||||
{logOption.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</InputGroup>
|
||||
</>,
|
||||
];
|
||||
|
||||
const periodList = [
|
||||
<>
|
||||
<InputLabel>기간</InputLabel>
|
||||
<SearchPeriod
|
||||
startDate={searchData.startDate}
|
||||
handleStartDate={data => {
|
||||
setSearchData({ ...searchData, startDate: data });
|
||||
}}
|
||||
endDate={searchData.endDate}
|
||||
handleEndDate={data => setSearchData({ ...searchData, endDate: data })}
|
||||
onChange={searchData.startDate === null && setSearchData({ ...searchData, startDate: '', endDate: '' })}
|
||||
maxDate={new Date()}
|
||||
/>
|
||||
</>,
|
||||
<>
|
||||
<BtnWrapper $gap="8px">
|
||||
<Button theme="reset" handleClick={handleReset} type="button" />
|
||||
<Button theme="search" text="검색" handleClick={handleSubmit} type="submit" />
|
||||
</BtnWrapper>
|
||||
</>,
|
||||
];
|
||||
|
||||
return <SearchBarLayout firstColumnData={searchList} secondColumnData={periodList} direction={'column'} />;
|
||||
};
|
||||
|
||||
export default LogViewSearchBar;
|
||||
|
||||
const InputGroup = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
`;
|
||||
17
src/components/UserManage/index.js
Normal file
17
src/components/UserManage/index.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import CaliumRequestSearchBar from './CaliumRequestSearchBar';
|
||||
import CaliumRequestRegistModal from './CaliumRequestRegistModal'
|
||||
import AdminViewSearchBar from './AdminViewSearchBar'
|
||||
import AuthRegistBar from './AuthRegistBar'
|
||||
import LogViewModal from './LogViewModal'
|
||||
import LogViewSearchBar from './LogViewSearchBar'
|
||||
import AuthGroupRows from './AuthGroupRows'
|
||||
|
||||
export {
|
||||
CaliumRequestSearchBar,
|
||||
CaliumRequestRegistModal,
|
||||
AdminViewSearchBar,
|
||||
AuthRegistBar,
|
||||
LogViewSearchBar,
|
||||
LogViewModal,
|
||||
AuthGroupRows
|
||||
}
|
||||
13
src/components/account/AccountEditItem.js
Normal file
13
src/components/account/AccountEditItem.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { InputItem, Label, AlertText, TextInput } from '../../styles/Components';
|
||||
|
||||
const AccountEditItem = ({ title, name, placeholder, value, onChange, register, errors }) => {
|
||||
return (
|
||||
<InputItem>
|
||||
<Label require="true">{title}</Label>
|
||||
<TextInput fontSize="15px" $padding="16px 20px" type="password" name={name} placeholder={placeholder} required onChange={onChange} {...register(name)} />
|
||||
<AlertText>{errors[`${name}`] && errors[`${name}`].message}</AlertText>
|
||||
</InputItem>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccountEditItem;
|
||||
13
src/components/account/AccountInputItem.js
Normal file
13
src/components/account/AccountInputItem.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { InputItem, Label, AlertText, TextInput } from '../../styles/Components';
|
||||
|
||||
const AccountInputItem = ({ title, type, name, placeholder, value, onChange, ref, register, errors, length }) => {
|
||||
return (
|
||||
<InputItem>
|
||||
<Label require="true">{title}</Label>
|
||||
<TextInput fontSize="15px" $padding="16px 20px" type={type} name={name} placeholder={placeholder} onChange={onChange} ref={ref} required {...register(name)} maxlength={length} />
|
||||
<AlertText>{errors[`${name}`] && errors[`${name}`].message}</AlertText>
|
||||
</InputItem>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccountInputItem;
|
||||
114
src/components/account/PasswordAlertModal.js
Normal file
114
src/components/account/PasswordAlertModal.js
Normal file
@@ -0,0 +1,114 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import Modal from '../common/modal/Modal';
|
||||
import Button from '../common/button/Button';
|
||||
|
||||
import { Title, BtnWrapper, ButtonClose } from '../../styles/Components';
|
||||
import { AuthInfo } from '../../apis/Auth';
|
||||
|
||||
const PasswordAlertModal = ({ setStateModal, stateModal, handleModal }) => {
|
||||
const [infoData, setInfoData] = useState([]);
|
||||
|
||||
const fetchData = async () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
setInfoData(await AuthInfo(token));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
const handleModalDate = () => {
|
||||
const today = new Date();
|
||||
const delayDate = new Date(today);
|
||||
delayDate.setDate(today.getDate() + 1);
|
||||
|
||||
localStorage.setItem('homeVisited', delayDate);
|
||||
handleModal('hidden');
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal $padding="30px" $view={stateModal}>
|
||||
<BtnWrapper $justify="flex-end">
|
||||
<ButtonClose onClick={() => handleModal('hidden')} />
|
||||
</BtnWrapper>
|
||||
<Title $align="center">비밀번호 변경 안내</Title>
|
||||
<NoticeSummary $maxwidth="500px">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>대상 ID</th>
|
||||
<td>{infoData.email}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>대상 이름</th>
|
||||
<td>{infoData.name}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>비밀번호 만료일</th>
|
||||
<td>{infoData.expiredDt && infoData.expiredDt.split('T')[0]}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</NoticeSummary>
|
||||
<NoticeDetail>
|
||||
개인정보보호와 운영 보안을 위해 비밀번호 변경을 권장합니다.
|
||||
<br />
|
||||
비밀번호 변경 기간이 만료되면 로그인이 차단됩니다.
|
||||
<br />
|
||||
[지금 변경하기] 버튼을 누르면 비밀번호 재설정 화면으로 이동됩니다.
|
||||
</NoticeDetail>
|
||||
<BtnWrapper $gap="5px;">
|
||||
<Button
|
||||
text="지금 변경하기"
|
||||
theme="primary"
|
||||
size="large"
|
||||
width="100%"
|
||||
handleClick={e => {
|
||||
e.preventDefault();
|
||||
window.location.href = '/account/edit';
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
text="나중에 변경하기"
|
||||
theme="line"
|
||||
size="large"
|
||||
width="100%"
|
||||
handleClick={e => {
|
||||
e.preventDefault();
|
||||
handleModalDate();
|
||||
}}
|
||||
/>
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PasswordAlertModal;
|
||||
|
||||
const NoticeSummary = styled.div`
|
||||
border-top: 1px solid #000;
|
||||
border-bottom: 1px solid #000;
|
||||
padding: 20px 40px;
|
||||
margin-bottom: 20px;
|
||||
max-width: ${props => props.$maxwidth || 'auto'};
|
||||
table {
|
||||
th {
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
}
|
||||
th,
|
||||
td {
|
||||
padding: 8px 0;
|
||||
}
|
||||
}
|
||||
`;
|
||||
const NoticeDetail = styled.div`
|
||||
background: #f6f6f6;
|
||||
padding: 30px 20px;
|
||||
margin-bottom: 20px;
|
||||
line-height: 2;
|
||||
text-align: ${props => props.$align || 'left'};
|
||||
`;
|
||||
5
src/components/account/index.js
Normal file
5
src/components/account/index.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import AccountInputItem from './AccountInputItem';
|
||||
import AccountEditItem from './AccountEditItem';
|
||||
|
||||
export { AccountInputItem };
|
||||
export { AccountEditItem };
|
||||
16
src/components/common/CDivider.js
Normal file
16
src/components/common/CDivider.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
const CDivider = () => {
|
||||
|
||||
return (
|
||||
<Divider>|</Divider>
|
||||
);
|
||||
};
|
||||
|
||||
const Divider = styled.span`
|
||||
margin: 0 8px;
|
||||
color: RGB(217,217,217);
|
||||
padding: 4px 0;
|
||||
`;
|
||||
|
||||
export default CDivider;
|
||||
97
src/components/common/Date/DatePickerComponent.js
Normal file
97
src/components/common/Date/DatePickerComponent.js
Normal file
@@ -0,0 +1,97 @@
|
||||
import DatePicker, { registerLocale } from 'react-datepicker';
|
||||
import { ko } from 'date-fns/esm/locale';
|
||||
import { getMonth, getYear } from 'date-fns';
|
||||
import range from 'lodash/range';
|
||||
import { months } from '../../../assets/data';
|
||||
registerLocale('ko', ko);
|
||||
|
||||
const DatePickerComponent = ({ selectedDate, handleSelectedDate, pastDate, disabled, name, readOnly, maxDate, type }) => {
|
||||
const years = range(1990, getYear(new Date()) + 100, 1);
|
||||
|
||||
const parseDate = (date) => {
|
||||
if (!date) return null;
|
||||
|
||||
try {
|
||||
// 이미 Date 객체인 경우
|
||||
if (date instanceof Date) {
|
||||
return date;
|
||||
}
|
||||
|
||||
// 문자열인 경우
|
||||
if (typeof date === 'string') {
|
||||
// ISO 형식의 날짜 문자열인 경우 (예: "2025-01-03T15:00:00")
|
||||
if (date.includes('T')) {
|
||||
return new Date(date);
|
||||
}
|
||||
|
||||
// 일반 날짜 문자열인 경우 (예: "2025-01-03")
|
||||
const [year, month, day] = date.split('-').map(num => num.trim());
|
||||
return new Date(year, month - 1, day);
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('Date parsing error:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const parsedDate = parseDate(selectedDate);
|
||||
|
||||
return (
|
||||
<DatePicker
|
||||
onKeyDown={event => {
|
||||
if (event.keyCode === 8) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}}
|
||||
disabled={disabled}
|
||||
readOnly={readOnly}
|
||||
// selected={type !== 'retention' ? '' : parsedDate}
|
||||
selected={parsedDate}
|
||||
onChange={data => handleSelectedDate(data)}
|
||||
className="datepicker"
|
||||
placeholderText={name ? name : '검색기간 선택'}
|
||||
calendarClassName="calendar"
|
||||
dateFormat="yyyy - MM - dd"
|
||||
locale="ko"
|
||||
minDate={pastDate}
|
||||
maxDate={maxDate}
|
||||
renderCustomHeader={({ date, changeYear, changeMonth, decreaseMonth, increaseMonth, prevMonthButtonDisabled, nextMonthButtonDisabled }) => (
|
||||
<div className="calendar-top">
|
||||
<button
|
||||
className="btn-prev"
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
decreaseMonth();
|
||||
}}
|
||||
disabled={prevMonthButtonDisabled}></button>
|
||||
<select value={getYear(date)} onChange={({ target: { value } }) => changeYear(value)}>
|
||||
{years.map(option => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
.
|
||||
<select value={months[getMonth(date)]} onChange={({ target: { value } }) => changeMonth(months.indexOf(value))}>
|
||||
{months.map(option => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<button
|
||||
className="btn-next"
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
increaseMonth();
|
||||
}}
|
||||
disabled={nextMonthButtonDisabled}></button>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default DatePickerComponent;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user