This commit is contained in:
2025-02-12 18:29:27 +09:00
commit 513ea114cc
290 changed files with 84274 additions and 0 deletions

View 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;

View 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;

View File

@@ -0,0 +1,79 @@
import React from 'react';
import DatePickerComponent from './DatePickerComponent';
import { DatePickerWrapper } from '../../../styles/Components';
import {
FormRowGroup,
FormLabel,
DateContainer,
DateTimeWrapper,
DateTimeGroup,
} from '../../../styles/ModuleComponents';
import { useTranslation } from 'react-i18next';
const DateRangePicker = ({
label,
startDate,
endDate,
onStartDateChange,
onEndDateChange,
pastDate = new Date(),
disabled,
startLabel = '시작 일자',
endLabel = '종료 일자',
setAlert,
}) => {
const { t } = useTranslation();
const handleStartDate = (date) => {
const newDate = new Date(date);
onStartDateChange(newDate);
};
const handleEndDate = (date) => {
let newDate = new Date(date);
if (startDate && newDate < startDate) {
setAlert(t('DATE_START_DIFF_END'));
newDate = new Date(startDate);
}
onEndDateChange(newDate);
};
return (
<FormRowGroup>
<FormLabel>{label}</FormLabel>
<DateTimeWrapper>
<DateTimeGroup>
<DateContainer>
<DatePickerWrapper>
<DatePickerComponent
name={startLabel}
handleSelectedDate={handleStartDate}
selectedDate={startDate}
pastDate={pastDate}
disabled={disabled}
/>
</DatePickerWrapper>
</DateContainer>
</DateTimeGroup>
<DateTimeGroup>
<DateContainer>
<DatePickerWrapper>
<DatePickerComponent
name={endLabel}
handleSelectedDate={handleEndDate}
selectedDate={endDate}
pastDate={pastDate}
disabled={disabled}
/>
</DatePickerWrapper>
</DateContainer>
</DateTimeGroup>
</DateTimeWrapper>
</FormRowGroup>
);
};
export default DateRangePicker;

View File

@@ -0,0 +1,171 @@
import React, { useState, useEffect } from 'react';
import DatePickerComponent from './DatePickerComponent';
import { DatePickerWrapper } from '../../../styles/Components';
import {
FormRowGroup,
FormLabel,
DateTimeGroup, DateTimeWrapper, DateContainer, StyledSelectInput, TimeSeparator, TimeContainer,
} from '../../../styles/ModuleComponents';
import { HourList, MinuteList } from '../../../assets/data';
import { useTranslation } from 'react-i18next';
const DateTimeRangePicker = ({
label,
startDate,
endDate,
onStartDateChange,
onEndDateChange,
pastDate = new Date(),
disabled,
startLabel = '시작 일자',
endLabel = '종료 일자',
reset = false,
setAlert
}) => {
const { t } = useTranslation();
const [startHour, setStartHour] = useState('00');
const [startMin, setStartMin] = useState('00');
const [endHour, setEndHour] = useState('00');
const [endMin, setEndMin] = useState('00');
useEffect(() => {
if (startDate) {
const date = new Date(startDate);
setStartHour(String(date.getHours()).padStart(2, '0'));
setStartMin(String(date.getMinutes()).padStart(2, '0'));
}
}, [startDate]);
useEffect(() => {
if (endDate) {
const date = new Date(endDate);
setEndHour(String(date.getHours()).padStart(2, '0'));
setEndMin(String(date.getMinutes()).padStart(2, '0'));
}
}, [endDate]);
useEffect(() => {
if (reset) {
setStartHour('00');
setStartMin('00');
setEndHour('00');
setEndMin('00');
}
}, [reset]);
const handleStartDate = (date) => {
const newDate = new Date(date);
newDate.setHours(parseInt(startHour), parseInt(startMin));
onStartDateChange(newDate);
};
const handleEndDate = (date) => {
let newDate = new Date(date);
newDate.setHours(parseInt(endHour), parseInt(endMin));
if (startDate && newDate < startDate) {
setAlert(t('TIME_START_DIFF_END'));
newDate = new Date(startDate);
}
onEndDateChange(newDate);
};
const handleStartTime = (e) => {
const { id, value } = e.target;
const newDate = startDate ? new Date(startDate) : new Date();
if (id === 'hour') {
setStartHour(value);
newDate.setHours(parseInt(value), parseInt(startMin));
} else {
setStartMin(value);
newDate.setHours(parseInt(startHour), parseInt(value));
}
onStartDateChange(newDate);
};
const handleEndTime = (e) => {
const { id, value } = e.target;
let newDate = endDate ? new Date(endDate) : new Date();
if (id === 'hour') {
setEndHour(value);
newDate.setHours(parseInt(value), parseInt(endMin));
} else {
setEndMin(value);
newDate.setHours(parseInt(endHour), parseInt(value));
}
if (startDate && newDate < startDate) {
setAlert(t('TIME_START_DIFF_END'));
newDate = new Date(startDate)
}
onEndDateChange(newDate);
};
return (
<FormRowGroup>
<FormLabel>{label}</FormLabel>
<DateTimeWrapper>
<DateTimeGroup>
<DateContainer>
<DatePickerWrapper>
<DatePickerComponent
name={startLabel}
handleSelectedDate={handleStartDate}
selectedDate={startDate}
pastDate={pastDate}
disabled={disabled}
/>
</DatePickerWrapper>
</DateContainer>
<TimeContainer>
<StyledSelectInput onChange={handleStartTime} id="hour" value={startHour} disabled={disabled}>
{HourList.map(hour => (
<option value={hour} key={hour}>{hour}</option>
))}
</StyledSelectInput>
<TimeSeparator>:</TimeSeparator>
<StyledSelectInput onChange={handleStartTime} id="min" value={startMin} disabled={disabled}>
{MinuteList.map(min => (
<option value={min} key={min}>{min}</option>
))}
</StyledSelectInput>
</TimeContainer>
</DateTimeGroup>
<DateTimeGroup>
<DateContainer>
<DatePickerWrapper>
<DatePickerComponent
name={endLabel}
handleSelectedDate={handleEndDate}
selectedDate={endDate}
pastDate={pastDate}
disabled={disabled}
/>
</DatePickerWrapper>
</DateContainer>
<TimeContainer>
<StyledSelectInput onChange={handleEndTime} id="hour" value={endHour} disabled={disabled}>
{HourList.map(hour => (
<option value={hour} key={hour}>{hour}</option>
))}
</StyledSelectInput>
<TimeSeparator>:</TimeSeparator>
<StyledSelectInput onChange={handleEndTime} id="min" value={endMin} disabled={disabled}>
{MinuteList.map(min => (
<option value={min} key={min}>{min}</option>
))}
</StyledSelectInput>
</TimeContainer>
</DateTimeGroup>
</DateTimeWrapper>
</FormRowGroup>
);
};
export default DateTimeRangePicker;

View File

@@ -0,0 +1,44 @@
import React from 'react';
import DatePickerComponent from './DatePickerComponent';
import { DatePickerWrapper } from '../../../styles/Components';
import {
FormRowGroup,
FormLabel,
DateContainer,
} from '../../../styles/ModuleComponents';
import { useTranslation } from 'react-i18next';
const SingleDatePicker = ({
label,
selectedDate,
onDateChange,
pastDate = new Date(),
disabled,
dateLabel = '선택 일자',
}) => {
const { t } = useTranslation();
const handleDate = (date) => {
const newDate = new Date(date);
onDateChange(newDate);
};
return (
<>
<FormLabel>{label}</FormLabel>
<DateContainer>
<DatePickerWrapper>
<DatePickerComponent
name={dateLabel}
handleSelectedDate={handleDate}
selectedDate={selectedDate}
pastDate={pastDate}
disabled={disabled}
/>
</DatePickerWrapper>
</DateContainer>
</>
);
};
export default SingleDatePicker;

View File

@@ -0,0 +1,83 @@
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import {
FormRowGroup,
FormLabel,
TimeContainer,
StyledSelectInput,
TimeSeparator,
} from '../../../styles/ModuleComponents';
import { HourList, MinuteList } from '../../../assets/data';
const SingleTimePicker = ({
label,
selectedTime,
onTimeChange,
disabled,
reset = false,
}) => {
const { t } = useTranslation();
const [hour, setHour] = useState('00');
const [minute, setMinute] = useState('00');
useEffect(() => {
if (selectedTime) {
const time = new Date(selectedTime);
setHour(String(time.getHours()).padStart(2, '0'));
setMinute(String(time.getMinutes()).padStart(2, '0'));
}
}, [selectedTime]);
useEffect(() => {
if (reset) {
setHour('00');
setMinute('00');
}
}, [reset]);
const handleTimeChange = (e) => {
const { id, value } = e.target;
const time = selectedTime ? new Date(selectedTime) : new Date();
if (id === 'hour') {
setHour(value);
time.setHours(parseInt(value), parseInt(minute));
} else {
setMinute(value);
time.setHours(parseInt(hour), parseInt(value));
}
onTimeChange(time);
};
return (
<>
<FormLabel>{label}</FormLabel>
<TimeContainer>
<StyledSelectInput
onChange={handleTimeChange}
id="hour"
value={hour}
disabled={disabled}
>
{HourList.map(h => (
<option value={h} key={h}>{h}</option>
))}
</StyledSelectInput>
<TimeSeparator>:</TimeSeparator>
<StyledSelectInput
onChange={handleTimeChange}
id="min"
value={minute}
disabled={disabled}
>
{MinuteList.map(m => (
<option value={m} key={m}>{m}</option>
))}
</StyledSelectInput>
</TimeContainer>
</>
);
};
export default SingleTimePicker;

View File

@@ -0,0 +1,154 @@
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import {
FormRowGroup,
FormLabel,
TimeContainer,
StyledSelectInput,
TimeSeparator,
DateTimeWrapper,
DateTimeGroup,
} from '../../../styles/ModuleComponents';
import { HourList, MinuteList } from '../../../assets/data';
const TimeRangePicker = ({
label,
startTime,
endTime,
onStartTimeChange,
onEndTimeChange,
disabled,
reset = false,
setAlert,
}) => {
const { t } = useTranslation();
const [startHour, setStartHour] = useState('00');
const [startMin, setStartMin] = useState('00');
const [endHour, setEndHour] = useState('00');
const [endMin, setEndMin] = useState('00');
useEffect(() => {
if (startTime) {
const time = new Date(startTime);
setStartHour(String(time.getHours()).padStart(2, '0'));
setStartMin(String(time.getMinutes()).padStart(2, '0'));
}
}, [startTime]);
useEffect(() => {
if (endTime) {
const time = new Date(endTime);
setEndHour(String(time.getHours()).padStart(2, '0'));
setEndMin(String(time.getMinutes()).padStart(2, '0'));
}
}, [endTime]);
useEffect(() => {
if (reset) {
setStartHour('00');
setStartMin('00');
setEndHour('00');
setEndMin('00');
}
}, [reset]);
const handleStartTime = (e) => {
const { id, value } = e.target;
const time = startTime ? new Date(startTime) : new Date();
if (id === 'hour') {
setStartHour(value);
time.setHours(parseInt(value), parseInt(startMin));
} else {
setStartMin(value);
time.setHours(parseInt(startHour), parseInt(value));
}
onStartTimeChange(time);
};
const handleEndTime = (e) => {
const { id, value } = e.target;
let time = endTime ? new Date(endTime) : new Date();
if (id === 'hour') {
setEndHour(value);
time.setHours(parseInt(value), parseInt(endMin));
} else {
setEndMin(value);
time.setHours(parseInt(endHour), parseInt(value));
}
// 시작 시간보다 종료 시간이 이전인 경우 체크
if (startTime) {
const startDateTime = new Date(startTime);
if (time < startDateTime) {
setAlert(t('TIME_START_DIFF_END'));
time = new Date(startTime);
}
}
onEndTimeChange(time);
};
return (
<FormRowGroup>
<FormLabel>{label}</FormLabel>
<DateTimeWrapper>
<DateTimeGroup>
<TimeContainer>
<StyledSelectInput
onChange={handleStartTime}
id="hour"
value={startHour}
disabled={disabled}
>
{HourList.map(hour => (
<option value={hour} key={hour}>{hour}</option>
))}
</StyledSelectInput>
<TimeSeparator>:</TimeSeparator>
<StyledSelectInput
onChange={handleStartTime}
id="min"
value={startMin}
disabled={disabled}
>
{MinuteList.map(min => (
<option value={min} key={min}>{min}</option>
))}
</StyledSelectInput>
</TimeContainer>
</DateTimeGroup>
<DateTimeGroup>
<TimeContainer>
<StyledSelectInput
onChange={handleEndTime}
id="hour"
value={endHour}
disabled={disabled}
>
{HourList.map(hour => (
<option value={hour} key={hour}>{hour}</option>
))}
</StyledSelectInput>
<TimeSeparator>:</TimeSeparator>
<StyledSelectInput
onChange={handleEndTime}
id="min"
value={endMin}
disabled={disabled}
>
{MinuteList.map(min => (
<option value={min} key={min}>{min}</option>
))}
</StyledSelectInput>
</TimeContainer>
</DateTimeGroup>
</DateTimeWrapper>
</FormRowGroup>
);
};
export default TimeRangePicker;

View File

@@ -0,0 +1,16 @@
import { default as DateTimeRangePicker } from './DateTimeRangerPickerComponent';
import { default as DatePickerComponent } from './DatePickerComponent';
import { default as SingleDatePicker } from './SingleDatePicker';
import { default as SingleTimePicker } from './SingleTimePicker';
import { default as DateRangePicker } from './DateRangePicker';
import { default as TimeRangePicker } from './TimeRangePicker';
export {
DateTimeRangePicker,
DatePickerComponent,
SingleDatePicker,
SingleTimePicker,
DateRangePicker,
TimeRangePicker,
};

View 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;

View File

@@ -0,0 +1,171 @@
import React, { useState, useEffect } from 'react';
import DatePickerComponent from './DatePickerComponent';
import { DatePickerWrapper } from '../../styles/Components';
import {
FormRowGroup,
FormLabel,
DateTimeGroup, DateTimeWrapper, DateContainer, StyledSelectInput, TimeSeparator, TimeContainer,
} from '../../styles/ModuleComponents';
import { HourList, MinuteList } from '../../assets/data';
import { useTranslation } from 'react-i18next';
const DateTimeRangePicker = ({
label,
startDate,
endDate,
onStartDateChange,
onEndDateChange,
pastDate = new Date(),
disabled,
startLabel = '시작 일자',
endLabel = '종료 일자',
reset = false,
setAlert
}) => {
const { t } = useTranslation();
const [startHour, setStartHour] = useState('00');
const [startMin, setStartMin] = useState('00');
const [endHour, setEndHour] = useState('00');
const [endMin, setEndMin] = useState('00');
useEffect(() => {
if (startDate) {
const date = new Date(startDate);
setStartHour(String(date.getHours()).padStart(2, '0'));
setStartMin(String(date.getMinutes()).padStart(2, '0'));
}
}, [startDate]);
useEffect(() => {
if (endDate) {
const date = new Date(endDate);
setEndHour(String(date.getHours()).padStart(2, '0'));
setEndMin(String(date.getMinutes()).padStart(2, '0'));
}
}, [endDate]);
useEffect(() => {
if (reset) {
setStartHour('00');
setStartMin('00');
setEndHour('00');
setEndMin('00');
}
}, [reset]);
const handleStartDate = (date) => {
const newDate = new Date(date);
newDate.setHours(parseInt(startHour), parseInt(startMin));
onStartDateChange(newDate);
};
const handleEndDate = (date) => {
let newDate = new Date(date);
newDate.setHours(parseInt(endHour), parseInt(endMin));
if (startDate && newDate < startDate) {
setAlert(t('TIME_START_DIFF_END'));
newDate = new Date(startDate);
}
onEndDateChange(newDate);
};
const handleStartTime = (e) => {
const { id, value } = e.target;
const newDate = startDate ? new Date(startDate) : new Date();
if (id === 'hour') {
setStartHour(value);
newDate.setHours(parseInt(value), parseInt(startMin));
} else {
setStartMin(value);
newDate.setHours(parseInt(startHour), parseInt(value));
}
onStartDateChange(newDate);
};
const handleEndTime = (e) => {
const { id, value } = e.target;
let newDate = endDate ? new Date(endDate) : new Date();
if (id === 'hour') {
setEndHour(value);
newDate.setHours(parseInt(value), parseInt(endMin));
} else {
setEndMin(value);
newDate.setHours(parseInt(endHour), parseInt(value));
}
if (startDate && newDate < startDate) {
setAlert(t('TIME_START_DIFF_END'));
newDate = new Date(startDate)
}
onEndDateChange(newDate);
};
return (
<FormRowGroup>
<FormLabel>{label}</FormLabel>
<DateTimeWrapper>
<DateTimeGroup>
<DateContainer>
<DatePickerWrapper>
<DatePickerComponent
name={startLabel}
handleSelectedDate={handleStartDate}
selectedDate={startDate}
pastDate={pastDate}
disabled={disabled}
/>
</DatePickerWrapper>
</DateContainer>
<TimeContainer>
<StyledSelectInput onChange={handleStartTime} id="hour" value={startHour} disabled={disabled}>
{HourList.map(hour => (
<option value={hour} key={hour}>{hour}</option>
))}
</StyledSelectInput>
<TimeSeparator>:</TimeSeparator>
<StyledSelectInput onChange={handleStartTime} id="min" value={startMin} disabled={disabled}>
{MinuteList.map(min => (
<option value={min} key={min}>{min}</option>
))}
</StyledSelectInput>
</TimeContainer>
</DateTimeGroup>
<DateTimeGroup>
<DateContainer>
<DatePickerWrapper>
<DatePickerComponent
name={endLabel}
handleSelectedDate={handleEndDate}
selectedDate={endDate}
pastDate={pastDate}
disabled={disabled}
/>
</DatePickerWrapper>
</DateContainer>
<TimeContainer>
<StyledSelectInput onChange={handleEndTime} id="hour" value={endHour} disabled={disabled}>
{HourList.map(hour => (
<option value={hour} key={hour}>{hour}</option>
))}
</StyledSelectInput>
<TimeSeparator>:</TimeSeparator>
<StyledSelectInput onChange={handleEndTime} id="min" value={endMin} disabled={disabled}>
{MinuteList.map(min => (
<option value={min} key={min}>{min}</option>
))}
</StyledSelectInput>
</TimeContainer>
</DateTimeGroup>
</DateTimeWrapper>
</FormRowGroup>
);
};
export default DateTimeRangePicker;

View File

@@ -0,0 +1,31 @@
import { Link } from 'react-router-dom';
import LogoUrl from '../../../assets/img/logo-white.svg';
import Navi from './Navi';
import styled from 'styled-components';
const LogoWrapper = styled.h1`
display: flex;
justify-content: center;
align-items: center;
height: 150px;
background: #333;
`;
const Logo = styled.img`
display: block;
`;
const Header = () => {
return (
<header>
<LogoWrapper>
<Link to="/main">
<Logo src={LogoUrl} alt="" />
</Link>
</LogoWrapper>
<Navi />
</header>
);
};
export default Header;

View File

@@ -0,0 +1,244 @@
import { NavLink, useNavigate } from 'react-router-dom';
import arrowIcon from '../../../assets/img/icon/icon-tab.png';
import styled from 'styled-components';
import { useRecoilValue } from 'recoil';
import { authList } from '../../../store/authList';
import Modal from '../modal/Modal';
import { BtnWrapper, ButtonClose, ModalText } from '../../../styles/Components';
import { useEffect, useState } from 'react';
import Button from '../button/Button';
import { useLocation } from 'react-router-dom';
import { AuthInfo } from '../../../apis';
import { authType } from '../../../assets/data';
import { menuConfig } from '../../../assets/data/menuConfig';
import { getMenuConfig } from '../../../utils';
const Navi = () => {
const token = sessionStorage.getItem('token');
const userInfo = useRecoilValue(authList);
const menu = getMenuConfig(userInfo);
const [modalClose, setModalClose] = useState('hidden');
const [logoutModalClose, setLogoutModalClose] = useState('hidden');
const location = useLocation();
const navigate = useNavigate();
const handleToken = async () => {
const tokenStatus = await AuthInfo(token);
tokenStatus.message === '잘못된 타입의 토큰입니다.' && setLogoutModalClose('view');
};
useEffect(() => {
handleToken();
}, [token]);
const handleTopMenu = e => {
e.preventDefault();
e.target.classList.toggle('active');
};
const handleLink = e => {
let topActive = document.querySelectorAll('nav .active');
let currentTopMenu = e.target.closest('ul').previousSibling;
for (let i = 0; i < topActive.length; i++) {
if (topActive[i] !== currentTopMenu) {
topActive[i].classList.remove('active');
}
}
handleToken();
};
// 등록 완료 모달
const handleModalClose = () => {
if (modalClose === 'hidden') {
setModalClose('view');
} else {
setModalClose('hidden');
}
};
// 로그아웃 안내 모달
const handleConfirmClose = () => {
if (logoutModalClose === 'hidden') {
setLogoutModalClose('view');
} else {
setLogoutModalClose('hidden');
sessionStorage.removeItem('token');
navigate('/');
}
};
// const menu = [
// {
// title: '운영자 관리',
// link: '/usermanage',
// access: userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.adminSearchRead || auth.id === authType.adminLogSearchRead || auth.id === authType.authoritySettingRead || auth.id === authType.caliumRequestRead),
// submenu: [
// { title: '운영자 조회', link: '/usermanage/adminview', id: authType.adminSearchRead },
// { title: '사용 이력 조회', link: '/usermanage/logview', id: authType.adminLogSearchRead },
// { title: '권한 설정', link: '/usermanage/authsetting', id: authType.authoritySettingRead },
// { title: '칼리움 요청', link: '/usermanage/caliumrequest', id: authType.caliumRequestRead },
// ],
// },
// {
// title: '지표 관리',
// link: '/indexmanage',
// access: userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.userIndicatorsRead || auth.id === authType.economicIndicatorsRead),
// submenu: [
// { title: '유저 지표', link: '/indexmanage/userindex', id: authType.userIndicatorsRead },
// { title: '경제 지표', link: '/indexmanage/economicindex', id: authType.economicIndicatorsRead },
// ],
// },
// {
// title: '운영 정보 관리',
// link: '/datamanage',
// access: userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.userSearchRead || auth.id === authType.contentSearchRead || auth.id === authType.gameLogRead || auth.id === authType.cryptoRead),
// submenu: [
// { title: '유저 조회', link: '/datamanage/userview', id: authType.userIndicatorsRead },
// { title: '컨텐츠 조회', link: '/datamanage/contentsview', id: authType.contentSearchRead },
// { title: '게임 로그 조회', link: '/datamanage/gamelogview', id: authType.gameLogRead },
// { title: '크립토 조회', link: '/datamanage/cryptview', id: authType.cryptoRead },
// ],
// },
// {
// title: '운영 서비스 관리',
// link: '/servicemanage',
// access: userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.inGameRead || auth.id === authType.whiteListRead || auth.id === authType.mailRead
// || auth.id === authType.blackListRead || auth.id === authType.reportRead || auth.id === authType.itemRead || auth.id === authType.eventRead ),
// submenu: [
// { title: '인게임 메시지', link: '/servicemanage/board', id: authType.inGameRead },
// // { title: '화이트리스트', link: '/servicemanage/whitelist', id: authType.whiteListRead },
// { title: '우편', link: '/servicemanage/mail', id: authType.mailRead },
// { title: '이용자 제재', link: '/servicemanage/userblock', id: authType.blackListRead },
// { title: '신고내역', link: '/servicemanage/reportlist', id: authType.reportRead },
// // { title: '아이템 복구 및 삭제', link: '/servicemanage/items', id: authType.itemRead },
// { title: '보상 이벤트 관리', link: '/servicemanage/event', id: authType.eventRead },
// ],
// },
// ];
return (
<>
<nav>
<ul>
{menu.map((item, idx) => {
return (
<li key={idx}>
{item.access && (
<TopMenu to={item.link} onClick={handleTopMenu}>
{item.title}
</TopMenu>
)}
<SubMenu>
{item.submenu &&
item.submenu.map((submenu, idx) => {
return (
<SubMenuItem key={idx} $isclickable={userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === submenu.id) ? 'true' : 'false'}>
<NavLink
to={userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === submenu.id) ? submenu.link : location.pathname}
onClick={e => {
userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === submenu.id) ? handleLink(e) : handleModalClose();
}}>
{submenu.title}
</NavLink>
</SubMenuItem>
);
})}
</SubMenu>
</li>
);
})}
</ul>
</nav>
{/* 접근 불가 모달 */}
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={modalClose}>
<BtnWrapper $justify="flex-end">
<ButtonClose onClick={handleModalClose} />
</BtnWrapper>
<ModalText $align="center">
해당 메뉴에 대한 조회 권한이 없습니다.
<br />
권한 등급을 변경 다시 이용해주세요.
</ModalText>
<BtnWrapper $gap="10px">
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleModalClose} />
</BtnWrapper>
</Modal>
{/* 로그아웃 안내 모달 */}
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={logoutModalClose}>
<BtnWrapper $justify="flex-end">
<ButtonClose onClick={handleConfirmClose} />
</BtnWrapper>
<ModalText $align="center">로그아웃 되었습니다.</ModalText>
<BtnWrapper $gap="10px">
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleConfirmClose} />
</BtnWrapper>
</Modal>
</>
);
};
export default Navi;
const TopMenu = styled(NavLink)`
padding: 16px 30px;
width: 100%;
text-align: left;
border-bottom: 1px solid #888;
position: relative;
color: #fff;
&:before {
content: '';
display: block;
width: 12px;
height: 12px;
position: absolute;
right: 30px;
top: 50%;
transform: translate(0, -50%);
background: url('${arrowIcon}') -12px 0 no-repeat;
}
&:hover,
&.active {
background: #444;
}
&.active ~ ul {
display: block;
}
&.active:before {
background: url('${arrowIcon}') 0 0 no-repeat;
}
`;
const SubMenu = styled.ul`
display: none;
`;
const SubMenuItem = styled.li`
background: #eee;
border-bottom: 1px solid #ccc;
color: #2c2c2c;
a {
width: 100%;
padding: 16px 30px;
color: ${props => (props.$isclickable === 'false' ? '#818181' : '#2c2c2c')};
text-align: left;
&:hover,
&.active {
color: ${props => (props.$isclickable === 'false' ? '#818181' : '#2c2c2c')};
font-weight: ${props => (props.$isclickable === 'false' ? 400 : 600)};
}
}
`;
const BackGround = styled.div`
background: #eee2;
width: 100%;
height: 100%;
z-index: 100;
`;

View File

@@ -0,0 +1,128 @@
import { useState, useEffect } from 'react';
import UserIcon from '../../../assets/img/icon/icon-profile.png';
import styled from 'styled-components';
import Modal from '../modal/Modal';
import CloseIcon from '../../../assets/img/icon/icon-close.png';
import Button from '../../common/button/Button';
import { useRecoilState } from 'recoil';
import { Link, useNavigate } from 'react-router-dom';
import { AuthLogout, AuthInfo } from '../../../apis';
import { BtnWrapper, ModalText } from '../../../styles/Components';
import { authList } from '../../../store/authList';
const Profile = () => {
const [infoData, setInfoData] = useRecoilState(authList);
const [errorModal, setErrorModal] = useState('hidden');
const navigate = useNavigate();
const handleLogout = () => {
const token = sessionStorage.getItem('token');
AuthLogout(token);
sessionStorage.removeItem('token');
navigate('/');
};
const fetchData = async () => {
const token = sessionStorage.getItem('token');
setInfoData(await AuthInfo(token));
};
useEffect(() => {
fetchData();
}, []);
// 필수값 입력 모달창
const handleErrorModal = () => {
if (errorModal === 'hidden') {
setErrorModal('view');
} else {
setErrorModal('hidden');
}
};
return (
<>
<ProfileWrapper>
<UserWrapper>{infoData.name && <Username>{infoData.name.length > 20 ? infoData.name.slice(0, 20) + '...' : infoData.name}</Username>}</UserWrapper>
<Link>
<LogoutBtn onClick={handleErrorModal}>로그아웃</LogoutBtn>
</Link>
</ProfileWrapper>
{/* 로그아웃 확인 모달 */}
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={errorModal}>
<BtnWrapper $justify="flex-end">
<ButtonClose onClick={handleErrorModal} />
</BtnWrapper>
<ModalText $align="center">
로그아웃 하시겠습니까?
<br />
(로그아웃 저장되지 않은 값은 초기화 됩니다.)
</ModalText>
<BtnWrapper $gap="10px">
<Button text="취소" theme="line" size="large" width="100%" handleClick={handleErrorModal} />
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleLogout} />
</BtnWrapper>
</Modal>
</>
);
};
export default Profile;
const ProfileWrapper = styled.div`
background: #f6f6f6;
padding: 20px;
display: flex;
flex-wrap: wrap;
gap: 30px;
word-break: break-all;
justify-content: flex-end;
align-items: center;
`;
const LogoutBtn = styled.button`
color: #2c2c2c;
line-height: 1;
border-bottom: 0.5px solid #2c2c2c;
font-size: 13px;
font-weight: 300;
border-radius: 0;
letter-spacing: 0;
width: max-content;
height: max-content;
`;
const UserWrapper = styled.div`
padding-left: 35px;
position: relative;
font-size: 18px;
display: flex;
&:before {
background: url('${UserIcon}') 50% 50% no-repeat;
width: 24px;
height: 24px;
content: '';
display: block;
position: absolute;
left: 0;
top: 50%;
transform: translate(0, -50%);
}
`;
const Username = styled.div`
font-weight: 700;
padding-right: 3px;
`;
const ButtonClose = styled.button`
width: 16px;
height: 16px;
background: url(${CloseIcon}) 50% 50% no-repeat;
`;

View File

@@ -0,0 +1,4 @@
import Header from './Header';
import Profile from './Profile';
export { Header, Profile };

View File

@@ -0,0 +1,23 @@
import { Header, Profile } from '../Header';
import { Outlet } from 'react-router-dom';
import { Container, HeaderContainer, ContentContainer,FullContainer } from '../../../styles/Components';
const Layout = () => {
return (
<>
<Container type="flex">
<HeaderContainer>
<Header/>
</HeaderContainer>
<FullContainer>
<Profile />
<ContentContainer>
<Outlet />
</ContentContainer>
</FullContainer>
</Container>
</>
);
};
export default Layout;

View File

@@ -0,0 +1,17 @@
import { Outlet } from 'react-router-dom';
import { Container } from '../../../styles/Components';
const LoginLayout = ({ bgimg, ...props }) => {
return (
<>
<Container $bgimg={bgimg} type="flex" $align="center" $justify="center" height="100%" {...props}>
<Outlet />
</Container>
{/* <div className="bg-login">
<Outlet />
</div> */}
</>
);
};
export default LoginLayout;

View File

@@ -0,0 +1,28 @@
import { Header, Profile } from '../Header';
import { Outlet } from 'react-router-dom';
import { Container, HeaderContainer, ContentContainer, FullContainer } from '../../../styles/Components';
import { styled } from 'styled-components';
const Layout = () => {
return (
<>
<Container type="flex">
<HeaderContainer>
<Header />
</HeaderContainer>
<FullContainer>
<Profile />
<MainContainer>
<Outlet />
</MainContainer>
</FullContainer>
</Container>
</>
);
};
export default Layout;
const MainContainer = styled(ContentContainer)`
padding: 0;
`;

View File

@@ -0,0 +1,5 @@
import Layout from './Layout';
import LoginLayout from './LoginLayout';
import MainLayout from './MainLayout';
export { Layout, LoginLayout, MainLayout };

View File

@@ -0,0 +1,28 @@
import { useEffect, useState } from 'react';
import styled, { css } from 'styled-components';
import ResetIcon from '../../assets/img/icon/icon-reset.png';
const Loading = () => {
return (
<Wrapper >
<img src={"/loading.gif"} alt="loading..." />
</Wrapper>
);
};
const Wrapper = styled.div`
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
`;
export default Loading;

View File

@@ -0,0 +1,106 @@
import { Link } from 'react-router-dom';
import styled from 'styled-components';
import { useEffect, useState } from 'react';
import PaginationIcon from '../../../assets/img/icon/icon-pagination.png';
const Pagination = ({ postsPerPage, totalPosts, setCurrentPage, currentPage, pageLimit }) => {
const pageNumbers = [];
const maxPage = Math.ceil(totalPosts / postsPerPage);
const [blockNum, setBlockNum] = useState(0);
for (let i = 1; i <= maxPage; i++) {
pageNumbers.push(i);
}
const v = blockNum * pageLimit;
let pArr = pageNumbers.slice(v, pageLimit + v);
useEffect(() => {
setBlockNum(0);
}, [postsPerPage, totalPosts]);
const firstPage = () => {
setBlockNum(0);
setCurrentPage(1);
};
const lastPage = () => {
setBlockNum(Math.ceil(maxPage / pageLimit) - 1);
setCurrentPage(maxPage);
};
const prePage = () => {
if (currentPage <= 1) return;
else if (currentPage - 1 <= pageLimit * blockNum) {
setBlockNum(n => n - 1);
}
setCurrentPage(n => n - 1);
};
const nextPage = () => {
if (currentPage >= maxPage) return;
else if (pageLimit * (blockNum + 1) <= currentPage) {
setBlockNum(n => n + 1);
}
setCurrentPage(n => n + 1);
};
const clickPage = number => {
setCurrentPage(number);
};
return (
<>
<PaginationWrapper>
<Button $position="0" onClick={firstPage} />
<Button $position="-20px" onClick={prePage} />
{pArr.map(number => (
<PageNum
$state={currentPage === number ? 'on' : ''}
key={number}
onClick={() => {
clickPage(number);
}}>
{number}
</PageNum>
))}
<Button $position="-40px" onClick={nextPage} />
<Button $position="-60px" onClick={lastPage} />
</PaginationWrapper>
</>
);
};
export default Pagination;
const PaginationWrapper = styled.div`
display: flex;
justify-content: center;
align-items: center;
margin-top: 20px;
`;
const Button = styled.button`
background: url('${PaginationIcon}') no-repeat;
background-position: ${props => props.$position} 0;
width: 20px;
height: 20px;
&:hover {
background-position: ${props => props.$position} -20px;
}
`;
const PageNum = styled(Link)`
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
color: ${props => (props.$state === 'on' ? '#2c2c2c' : '#CECECE')};
&:hover {
color: #2c2c2c;
}
`;

View File

@@ -0,0 +1,715 @@
import { useState } from 'react';
import DatePicker, { registerLocale } from 'react-datepicker';
import { ko } from 'date-fns/esm/locale';
import 'react-datepicker/dist/react-datepicker.css';
import CheckBox from '../input/CheckBox';
import Button from '../button/Button';
import styled from 'styled-components';
import { TextInput, SelectInput, BtnWrapper, InputLabel } from '../../../styles/Components';
// 캘린더
import IconCalendar from '../../../assets/img/icon/icon-date.png';
import IconArrow from '../../../assets/img/icon/icon-arrow.png';
import { getMonth, getYear } from 'date-fns';
import range from 'lodash/range';
const SearchBar = props => {
const [startDateNew, setStartDateNew] = useState(new Date());
const [startDate, setStartDate] = useState(null);
const [endDate, setEndDate] = useState(null);
const years = range(1990, getYear(new Date()) + 1, 1);
const months = ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12'];
if (props.type === 'userview') {
return (
<SearchbarStyle>
<SearchItem>
<InputLabel>검색</InputLabel>
<InputGroup>
<SelectInput>
<option value="name">이름</option>
<option value="id">ID</option>
</SelectInput>
<TextInput type="text" />
</InputGroup>
</SearchItem>
<SearchItem>
<InputLabel>관리자 권한</InputLabel>
<SelectInput>
<option value="A">사용자 권한_A</option>
<option value="B">사용자 권한_B</option>
<option value="A">사용자 권한_C</option>
<option value="B">사용자 권한_D</option>
</SelectInput>
</SearchItem>
<SearchItem>
<CheckBox id="input-check" label="가입 신청" />
</SearchItem>
<BtnWrapper $gap="8px">
<Button theme="reset" />
<Button theme="gray" text="검색" />
</BtnWrapper>
</SearchbarStyle>
);
} else if (props.type === 'logview') {
return (
<SearchbarStyle2>
<SearchRow>
<SearchItem>
<InputLabel>검색</InputLabel>
<InputGroup>
<SelectInput>
<option value="name">이름</option>
<option value="id">ID</option>
</SelectInput>
<TextInput type="text" />
</InputGroup>
</SearchItem>
<SearchItem>
<InputLabel>로그</InputLabel>
<InputGroup>
<SelectInput name="" id="">
<option>구분 선택</option>
<option value="">회원가입 승인</option>
<option value="">회원가입 불가</option>
<option value="">사용자 정보 수정</option>
<option value="">사용자 정보 삭제</option>
<option value="">사용자 비밀번호 초기화</option>
<option value="">신규 그룹 생성</option>
<option value="">그룹 수정</option>
<option value="">그룹 삭제</option>
<option value="">공지사항 등록</option>
<option value="">공지사항 수정</option>
<option value="">공지사항 삭제</option>
<option value="">우편 발송</option>
<option value="">우편 회수</option>
<option value="">유저 정보 수정</option>
<option value="">화이트리스트 등록</option>
<option value="">화이트리스트 수정</option>
<option value="">유저 제재</option>
</SelectInput>
<SelectInput name="" id="">
<option>상세 이력 선택</option>
<option value="">회원가입 승인</option>
<option value="">회원가입 불가</option>
<option value="">사용자 정보 수정</option>
<option value="">사용자 정보 삭제</option>
<option value="">사용자 비밀번호 초기화</option>
<option value="">신규 그룹 생성</option>
<option value="">그룹 수정</option>
<option value="">그룹 삭제</option>
<option value="">공지사항 등록</option>
<option value="">공지사항 수정</option>
<option value="">공지사항 삭제</option>
<option value="">우편 발송</option>
<option value="">우편 회수</option>
<option value="">유저 정보 수정</option>
<option value="">화이트리스트 등록</option>
<option value="">화이트리스트 수정</option>
<option value="">유저 제재</option>
</SelectInput>
</InputGroup>
</SearchItem>
</SearchRow>
<SearchItem>
<InputLabel>기간</InputLabel>
<DatePickerWrapper>
<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>
)}
/>
<span>-</span>
<DatePicker
selected={endDate}
onChange={date => setEndDate(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>
)}
/>
</DatePickerWrapper>
<BtnWrapper $gap="8px">
<Button theme="reset" />
<Button theme="gray" text="검색" />
</BtnWrapper>
</SearchItem>
</SearchbarStyle2>
);
} else if (props.type === 'authSetting') {
return (
<SearchbarStyle>
<SearchItem>
<InputLabel>관리자 그룹명</InputLabel>
<TextInput type="text" placeholder="관리자 그룹명 입력" width="300px" />
</SearchItem>
<SearchItem>
<InputLabel>설명</InputLabel>
<TextInput type="text" placeholder="그룹 설명글을 입력하세요(최대 100자)" width="300px" />
</SearchItem>
<Button theme="gray" text="등록" />
</SearchbarStyle>
);
} else if (props.type === 'whitelist') {
return (
<SearchbarStyle>
<SearchItem>
<InputLabel>직접입력</InputLabel>
<TextInput type="text" placeholder="GUID 입력" width="300px" />
<Button theme="gray" text="등록" />
</SearchItem>
<SearchItem>
<InputLabel>일괄등록</InputLabel>
<TextInput type="text" width="300px" placeholder=".xlsx, .xls 확장자의 파일만 업로드 가능합니다." />
<Button theme="gray" text="엑셀 업로드" />
<Button theme="gray" text="등록" />
</SearchItem>
</SearchbarStyle>
);
} else if (props.type === 'mail') {
return (
<SearchbarStyle2>
<SearchRow>
<SearchItem>
<InputLabel>우편 제목</InputLabel>
<TextInput type="text" placeholder="우편 제목" />
</SearchItem>
<SearchItem>
<InputLabel>조회 일자</InputLabel>
<InputGroup>
<DatePickerWrapper>
<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>
)}
/>
<span>~</span>
<DatePicker
selected={endDate}
onChange={date => setEndDate(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>
)}
/>
</DatePickerWrapper>
</InputGroup>
</SearchItem>
</SearchRow>
<SearchRow>
<SearchItem>
<InputLabel>발송 방식</InputLabel>
<SelectInput>
<option value="">전체</option>
<option value="">전체</option>
<option value="">전체</option>
<option value="">전체</option>
<option value="">전체</option>
</SelectInput>
</SearchItem>
<SearchItem>
<InputLabel>발송 상태</InputLabel>
<SelectInput>
<option value="">전체</option>
<option value="">전체</option>
<option value="">전체</option>
<option value="">전체</option>
<option value="">전체</option>
</SelectInput>
</SearchItem>
<SearchItem>
<InputLabel>우편 타입</InputLabel>
<SelectInput>
<option value="">전체</option>
<option value="">전체</option>
<option value="">전체</option>
<option value="">전체</option>
<option value="">전체</option>
</SelectInput>
</SearchItem>
<SearchItem>
<InputLabel>수신 대상</InputLabel>
<SelectInput>
<option value="">전체</option>
<option value="">전체</option>
<option value="">전체</option>
<option value="">전체</option>
<option value="">전체</option>
</SelectInput>
</SearchItem>
<BtnWrapper $gap="8px">
<Button theme="reset" />
<Button theme="gray" text="검색" />
</BtnWrapper>
</SearchRow>
</SearchbarStyle2>
);
} else if (props.type === 'userblock') {
return (
<SearchbarStyle2>
<SearchRow>
<SearchItem>
<InputLabel>대상</InputLabel>
<InputGroup>
<SelectInput>
<option value="id">GUID</option>
<option value="name">닉네임</option>
</SelectInput>
<TextInput type="text" width="600px" placeholder="GUID 입력" />
</InputGroup>
</SearchItem>
<SearchItem>
<InputLabel>등록자</InputLabel>
<TextInput type="text" placeholder="이메일 입력" width="600px" />
</SearchItem>
</SearchRow>
<SearchRow>
<SearchItem>
<InputLabel>상태</InputLabel>
<SelectInput>
<option value="">전체</option>
<option value="">전체</option>
<option value="">전체</option>
<option value="">전체</option>
<option value="">전체</option>
</SelectInput>
</SearchItem>
<SearchItem>
<InputLabel>제재 사유</InputLabel>
<SelectInput>
<option value="">접속제한</option>
<option value="">전체</option>
<option value="">전체</option>
<option value="">전체</option>
<option value="">전체</option>
</SelectInput>
</SearchItem>
<SearchItem>
<InputLabel>제재 기간</InputLabel>
<SelectInput>
<option value="">전체</option>
<option value="">전체</option>
<option value="">전체</option>
<option value="">전체</option>
<option value="">전체</option>
</SelectInput>
</SearchItem>
<BtnWrapper $gap="8px">
<Button theme="reset" />
<Button theme="gray" text="검색" />
</BtnWrapper>
</SearchRow>
</SearchbarStyle2>
);
} else if (props.type === 'reportlist') {
return (
<SearchbarStyle2>
<SearchRow>
<SearchItem>
<InputLabel>신고 일자</InputLabel>
<DatePickerWrapper>
<DateGroup>
<DatePicker
selected={startDateNew}
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>
</DateGroup>
<span>~</span>
<DateGroup>
<DatePicker
selected={startDateNew}
onChange={date => setEndDate(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>
</DateGroup>
</DatePickerWrapper>
</SearchItem>
</SearchRow>
<SearchRow>
<SearchItem>
<InputLabel>신고 유형</InputLabel>
<SelectInput>
<option value="">전체</option>
<option value="">전체</option>
<option value="">전체</option>
<option value="">전체</option>
<option value="">전체</option>
</SelectInput>
</SearchItem>
<SearchItem>
<InputLabel>상태</InputLabel>
<SelectInput>
<option value="">전체</option>
<option value="">해결</option>
<option value="">미해결</option>
</SelectInput>
</SearchItem>
<SearchItem>
<InputLabel>신고자 / 담당자</InputLabel>
<InputGroup>
<SelectInput>
<option value="">전체</option>
<option value="">신고자</option>
<option value="">담당자</option>
</SelectInput>
<TextInput placeholder="입력" />
</InputGroup>
</SearchItem>
<BtnWrapper $gap="8px">
<Button theme="reset" />
<Button theme="gray" text="검색" />
</BtnWrapper>
</SearchRow>
</SearchbarStyle2>
);
}
};
export default SearchBar;
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 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 DatePickerWrapper = styled.div`
display: flex;
flex-wrap: wrap;
gap: 5px 0;
align-items: center;
.datepicker {
width: 160px;
border: 1px solid #e0e0e0;
padding: 5px 40px 5px 15px;
border-radius: 5px;
line-height: 18px;
font-size: 14px;
height: 35px;
background: url(${IconCalendar}) center right no-repeat;
&::placeholder {
color: #b8b8b8;
}
}
.react-datepicker-popper[data-placement^='bottom'] .react-datepicker__triangle::after {
border-bottom-color: #fff;
}
.calendar {
.react-datepicker__header {
background: #fff;
}
.btn-prev,
.btn-next {
width: 26px;
height: 26px;
overflow: hidden;
}
.btn-prev {
background: url(${IconArrow}) 0 50% no-repeat;
}
.btn-next {
background: url(${IconArrow}) -26px 50% no-repeat;
}
.calendar-top {
button {
margin: 0 10px;
}
select {
font-size: 16px;
padding: 10px 0;
}
}
.react-datepicker__day-names > div:first-child,
.react-datepicker__week > div:first-child {
color: #ff0000;
}
.react-datepicker__day-names > div:last-child,
.react-datepicker__week > div:last-child {
color: #256bfa;
}
}
span {
padding: 0 10px;
}
`;
const InputGroup = styled.div`
display: flex;
align-items: center;
gap: 5px;
`;
const DateGroup = styled.div`
display: flex;
align-items: center;
${SelectInput} {
margin-left: 5px;
}
`;
registerLocale('ko', ko);

View File

@@ -0,0 +1,57 @@
import { styled } from 'styled-components';
import { TextInput, SelectInput, SearchBarAlert } from '../../../styles/Components';
const SearchBarLayout = ({ firstColumnData, secondColumnData, direction }) => {
return (
<SearchbarStyle direction={direction}>
<SearchRow>
{firstColumnData.map((data, index) => (
<SearchItem key={index}>{data}</SearchItem>
))}
</SearchRow>
{secondColumnData && (
<SearchRow>
{secondColumnData.map((data, index) => (
<SearchItem key={index}>{data}</SearchItem>
))}
</SearchRow>
)}
</SearchbarStyle>
);
};
export default SearchBarLayout;
const SearchbarStyle = styled.div`
width: 100%;
display: flex;
flex-wrap: wrap;
font-size: 14px;
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')};
`;
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 SearchRow = styled.div`
display: flex;
flex-wrap: wrap;
gap: 20px 0;
`;

View File

@@ -0,0 +1,121 @@
import { styled } from 'styled-components';
import { SelectInput } from '../../../styles/Components';
import IconCalendar from '../../../assets/img/icon/icon-date.png';
import IconArrow from '../../../assets/img/icon/icon-arrow.png';
import DatePickerComponent from '../Date/DatePickerComponent';
const SearchPeriod = ({ isTime, startDate, handleStartDate, endDate, handleEndDate, onChange, maxDate }) => {
return (
<DatePickerWrapper>
<DateGroup>
<DatePickerComponent
maxDate={maxDate}
selectedDate={startDate}
handleSelectedDate={data => {
handleStartDate(data);
}}
/>
{isTime && (
<>
<SelectInput>
<option value="">00</option>
<option value="">01</option>
</SelectInput>
<SelectInput>
<option value="">00</option>
<option value="">01</option>
</SelectInput>
</>
)}
</DateGroup>
<span>-</span>
<DateGroup>
<DatePickerComponent maxDate={maxDate} selectedDate={endDate} handleSelectedDate={data => handleEndDate(data)} pastDate={startDate} />
{isTime && (
<>
<SelectInput>
<option value="">00</option>
<option value="">01</option>
</SelectInput>
<SelectInput>
<option value="">00</option>
<option value="">01</option>
</SelectInput>
</>
)}
</DateGroup>
</DatePickerWrapper>
);
};
export default SearchPeriod;
const DatePickerWrapper = styled.div`
display: flex;
align-items: center;
.datepicker {
width: 160px;
border: 1px solid #e0e0e0;
padding: 5px 40px 5px 15px;
border-radius: 5px;
line-height: 18px;
font-size: 14px;
height: 35px;
background: url(${IconCalendar}) center right no-repeat;
&::placeholder {
color: #b8b8b8;
}
}
.react-datepicker-popper[data-placement^='bottom'] .react-datepicker__triangle::after {
border-bottom-color: #fff;
}
.calendar {
.react-datepicker__header {
background: #fff;
}
.btn-prev,
.btn-next {
width: 26px;
height: 26px;
overflow: hidden;
}
.btn-prev {
background: url(${IconArrow}) 0 50% no-repeat;
}
.btn-next {
background: url(${IconArrow}) -26px 50% no-repeat;
}
.calendar-top {
button {
margin: 0 10px;
}
select {
font-size: 16px;
padding: 10px 0;
}
}
.react-datepicker__day-names > div:first-child,
.react-datepicker__week > div:first-child {
color: #ff0000;
}
.react-datepicker__day-names > div:last-child,
.react-datepicker__week > div:last-child {
color: #256bfa;
}
}
span {
padding: 0 10px;
}
`;
const DateGroup = styled.div`
display: flex;
align-items: center;
${SelectInput} {
margin-left: 5px;
}
`;

View File

@@ -0,0 +1,5 @@
import SearchBar from './SearchBar';
import SearchBarLayout from './SearchBarLayout';
import SearchPeriod from './SearchPeriod';
export { SearchBar, SearchBarLayout, SearchPeriod };

View File

@@ -0,0 +1,44 @@
import {
ListCount,
ListOption,
SelectInput,
TableInfo,
} from '../../../styles/Components';
import { ViewTitleCountType } from '../../../assets/data';
import { TitleItem, TitleItemLabel, TitleItemValue } from '../../../styles/ModuleComponents';
const ViewTableInfo = ({children, total, total_all, handleOrderBy, handlePageSize, countType = ViewTitleCountType.total}) => {
return (
<TableInfo>
{total !== undefined && total_all !== undefined &&
<ListCount>
{ countType === ViewTitleCountType.total && `총 : ${total ?? 0} 건 / ${total_all ?? 0}`}
{ countType === ViewTitleCountType.calium &&
<>
<TitleItem>
<TitleItemLabel>누적 충전</TitleItemLabel>
<TitleItemValue color='#b7e0c3' fontWeight='bold'>{total_all ?? 0}</TitleItemValue>
</TitleItem>
<TitleItem>
<TitleItemLabel>잔여 수량</TitleItemLabel>
<TitleItemValue color='#B39063' fontWeight='bold'>{total ?? 0}</TitleItemValue>
</TitleItem>
</>
}
</ListCount>}
<ListOption>
<SelectInput className="input-select" onChange={e => handleOrderBy(e)}>
<option value="DESC">내림차순</option>
<option value="ASC">오름차순</option>
</SelectInput>
<SelectInput name="" id="" className="input-select" onChange={e => handlePageSize(e)}>
<option value="50">50</option>
<option value="100">100</option>
</SelectInput>
{children}
</ListOption>
</TableInfo>
);
};
export default ViewTableInfo;

View File

@@ -0,0 +1,151 @@
import { useEffect, useState } from 'react';
import styled, { css } from 'styled-components';
import ResetIcon from '../../../assets/img/icon/icon-reset.png';
/**
* @param {string} text 버튼 내부 텍스트
* @param {string} type 버튼 타입 (button, submit)
* @param {string} errorMessage 표시할 에러 메시지
* @param {() => void} handleClick 버튼 클릭시 실행할 이벤트, Route 기능의 경우 history.push와 같은 함수를 이용합니다.
* @param {string} buttonColor 버튼 배경 색상
* @param {string} hoverColor 버튼 호버 배경 색상
* @param {object} props 폰트 관련 속성
*/
const Button = ({ text, type = 'button', errorMessage, handleClick, theme, size, width, height, bordercolor, disabled, name }) => {
const [isShowErrorMessage, setIsShowErrorMessage] = useState(false);
const handleButtonClick = () => {
if (errorMessage) {
setIsShowErrorMessage(true);
} else {
handleClick?.();
}
};
useEffect(() => {
errorMessage || setIsShowErrorMessage(false);
}, [errorMessage]);
return (
<Wrapper width={width}>
<ButtonStyle
onSubmit={e => e.preventDefault()}
type={type}
disabled={disabled}
onClick={handleClick}
theme={theme}
size={size}
bordercolor={bordercolor}
width={width}
height={height}
name={name}>
{text}
</ButtonStyle>
{isShowErrorMessage && <ErrorText>{errorMessage}</ErrorText>}
</Wrapper>
);
};
const Wrapper = styled.div`
display: ${props => props.display || 'inline-block'};
width: ${props => props.width};
text-align: center;
`;
const sizes = {
large: {
width: '100%',
fontSize: '17px',
padding: '16px 20px',
fontWeight: '600',
height: '50px',
},
};
const sizeStyles = css`
${({ size }) => css`
width: ${sizes[size].width};
font-size: ${sizes[size].fontSize};
border-radius: ${sizes[size].borderRadius};
padding: ${sizes[size].$padding};
font-weight: ${sizes[size].fontWeight};
height: ${sizes[size].height};
`}
`;
const ButtonStyle = styled.button`
border-radius: 5px;
display: inline-flex;
align-items: center;
justify-content: center;
width: ${props => props.width || '80px'};
height: ${props => props.height || '30px'};
min-width: fit-content;
color: #2c2c2c;
font-size: 14px;
${props => props.size && sizeStyles}
${props =>
props.theme === 'line' &&
css`
border: 1px solid #2c2c2c;
background: transparent;
`}
${props =>
props.theme === 'primary' &&
css`
background: #2c2c2c;
color: #fff;
`}
${props =>
props.theme === 'disable' &&
css`
background: #d9d9d9;
color: #fff;
`}
${props =>
props.theme === 'reset' &&
css`
border: 1px solid ${props.bordercolor};
background: url(${ResetIcon}) 50% 50% no-repeat;
border: 1px solid #d9d9d9;
width: 35px;
height: 35px;
`}
${props =>
props.theme === 'gray' &&
css`
background: #b8b8b8;
color: #fff;
width: 100px;
height: 35px;
`}
${props =>
props.theme === 'search' &&
css`
background: #2c2c2c;
color: #fff;
width: 100px;
height: 35px;
`}
${props =>
props.theme === 'find' &&
css`
background: #8c8c8c;
color: #fff;
width: 100px;
height: 35px;
`}
`;
const ErrorText = styled.p`
color: red;
text-align: center;
font-weight: 400;
font-size: 12px;
margin-top: 10px;
line-height: 15px;
`;
export default Button;

View File

@@ -0,0 +1,113 @@
import * as XLSX from 'xlsx-js-style';
import { ExcelDownButton } from '../../../styles/ModuleComponents';
const ExcelDownloadButton = ({ tableRef, fileName = 'download.xlsx', sheetName = 'Sheet1' }) => {
const isNumeric = (value) => {
// 숫자 또는 숫자 문자열인지 확인
return !isNaN(value) && !isNaN(parseFloat(value));
};
const downloadExcel = () => {
try {
if (!tableRef.current) return;
const tableElement = tableRef.current;
const headerRows = tableElement.getElementsByTagName('thead')[0].getElementsByTagName('tr');
const bodyRows = tableElement.getElementsByTagName('tbody')[0].getElementsByTagName('tr');
// 헤더 데이터 추출
const headers = Array.from(headerRows[0].cells).map(cell => cell.textContent);
// 바디 데이터 추출 및 숫자 타입 처리
const bodyData = Array.from(bodyRows).map(row =>
Array.from(row.cells).map(cell => {
const value = cell.textContent;
return isNumeric(value) ? parseFloat(value) : value;
})
);
// 워크북 생성
const wb = XLSX.utils.book_new();
// 테두리 스타일 정의
const borderStyle = {
style: "thin",
color: { rgb: "000000" }
};
// 스타일 정의
const centerStyle = {
font: {
name: "맑은 고딕",
sz: 11
},
alignment: {
horizontal: 'right',
vertical: 'right'
},
border: {
top: borderStyle,
bottom: borderStyle,
left: borderStyle,
right: borderStyle
}
};
const headerStyle = {
alignment: {
horizontal: 'center',
vertical: 'center'
},
fill: {
fgColor: { rgb: "d9e1f2" },
patternType: "solid"
}
};
// 데이터에 스타일 적용
const wsData = [
// 헤더 행
headers.map(h => ({
v: h,
s: headerStyle
})),
// 데이터 행들
...bodyData.map(row =>
row.map(cell => ({
v: cell,
s: centerStyle
}))
)
];
// 워크시트 생성
const ws = XLSX.utils.aoa_to_sheet(wsData);
// 열 너비 설정 (최소 8, 최대 50)
ws['!cols'] = headers.map((_, index) => {
const maxLength = Math.max(
headers[index].length * 2,
...bodyData.map(row => String(row[index] || '').length * 1.2)
);
return { wch: Math.max(8, Math.min(50, maxLength)) };
});
// 워크시트를 워크북에 추가
XLSX.utils.book_append_sheet(wb, ws, sheetName);
// 엑셀 파일 다운로드
XLSX.writeFile(wb, fileName);
} catch (error) {
console.error('Excel download failed:', error);
alert('엑셀 다운로드 중 오류가 발생했습니다.');
}
};
return (
<ExcelDownButton onClick={downloadExcel}>
엑셀 다운로드
</ExcelDownButton>
);
};
export default ExcelDownloadButton;

View File

@@ -0,0 +1,42 @@
import DateTimeInput from './input/DateTimeInput';
import AuthCheckBox from './input/AuthCheckBox';
import CheckBox from './input/CheckBox';
import Radio from './input/Radio';
import Button from './button/Button';
import ExcelDownButton from './button/ExcelDownButton';
import AuthModal from './modal/AuthModal';
import CompletedModal from './modal/CompletedModal';
import ConfirmModal from './modal/ConfirmModal';
import DynamicModal from './modal/DynamicModal';
import CustomConfirmModal from './modal/CustomConfirmModal';
import Modal from './modal/Modal';
import Pagination from './Pagination/Pagination';
import ViewTableInfo from './Table/ViewTableInfo';
import Loading from './Loading';
import CDivider from './CDivider';
export {
DatePickerComponent,
DateTimeRangePicker,
DateRangePicker,
SingleDatePicker,
SingleTimePicker,
TimeRangePicker
} from './Date';
export { DateTimeInput,
AuthCheckBox,
CheckBox,
Radio,
Button,
ExcelDownButton,
AuthModal,
CompletedModal,
ConfirmModal,
DynamicModal,
CustomConfirmModal,
Modal,
Pagination,
ViewTableInfo,
Loading,
CDivider
};

View File

@@ -0,0 +1,64 @@
import styled from 'styled-components';
import CheckIcon from '../../../assets/img/icon/icon-chk.png';
const AuthCheckBox = props => {
//console.log(props.defaultChecked);
return (
<>
<CheckBoxStyle>
<InputCheck
type="checkbox"
name={props.name}
className="input-check"
id={props.id}
disabled={props.disabled}
onChange={e => props.setData && props.setData(e)}
checked={props.checked !== undefined ? props.checked : false}
// defaultChecked={props.defaultChecked}
onClick={props.handleCheck && props.handleCheck}
/>
<Label htmlFor={props.id}>{props.label}</Label>
</CheckBoxStyle>
</>
);
};
export default AuthCheckBox;
const CheckBoxStyle = styled.div`
display: inline-flex;
align-items: center;
justify-content: center;
`;
const InputCheck = styled.input`
position: absolute;
opacity: 0;
&:checked + label:before, &:disabled + label:before {
background: url(${CheckIcon}) no-repeat;
background-position: -32px 0px;
}
`;
const Label = styled.label`
position: relative;
display: inline-block;
cursor: pointer;
padding-left: 21px;
font-weight: 600;
width: max-content;
&:before {
content: '';
display: inline-block;
position: absolute;
top: 50%;
transform: translate(0, -50%);
left: 0;
width: 16px;
height: 16px;
overflow: hidden;
vertical-align: middle;
background: url(${CheckIcon}) no-repeat;
background-position: 0px 0px;
}
`;

View File

@@ -0,0 +1,74 @@
import styled from 'styled-components';
import CheckIcon from '../../../assets/img/icon/icon-chk.png';
const CheckBox = props => {
//console.log(props.defaultChecked);
return (
<>
<CheckBoxStyle>
<InputCheck
type="checkbox"
name={props.name}
className="input-check"
id={props.id}
disabled={props.disabled}
onChange={e => props.setData && props.setData(e)}
checked={props.checked}
defaultChecked={props.defaultChecked}
onClick={props.handleCheck && props.handleCheck}
/>
<Label htmlFor={props.id}>{props.label}</Label>
</CheckBoxStyle>
</>
);
};
export default CheckBox;
const CheckBoxStyle = styled.div`
display: ${({inline}) => (inline === false ? 'block' : 'inline-flex')};
margin-top: ${({inline}) => (inline === false ? '10px' : '')};
align-items: center;
justify-content: center;
`;
// const CheckBoxStyle = styled.div`
// display: 'inline-flex';
// align-items: center;
// justify-content: center;
// `;
const InputCheck = styled.input`
position: absolute;
opacity: 0;
&:checked + label:before {
background: url(${CheckIcon}) no-repeat;
background-position: -32px 0px;
}
&:disabled + label:before {
background: url(${CheckIcon}) no-repeat;
background-position: -16px 0px;
}
`;
const Label = styled.label`
position: relative;
display: inline-block;
cursor: pointer;
padding-left: 21px;
font-weight: 600;
width: max-content;
&:before {
content: '';
display: inline-block;
position: absolute;
top: 50%;
transform: translate(0, -50%);
left: 0;
width: 16px;
height: 16px;
overflow: hidden;
vertical-align: middle;
background: url(${CheckIcon}) no-repeat;
background-position: 0px 0px;
}
`;

View File

@@ -0,0 +1,46 @@
import { DateGroup, DatePickerWrapper, InputGroup, InputLabel, SelectInput } from '../../../styles/Components';
import DatePickerComponent from '../Date/DatePickerComponent';
import { HourList, MinuteList } from '../../../assets/data';
import { RegistInputItem, SubText } from '../../../styles/ModuleComponents';
import { useTranslation } from 'react-i18next';
const DateTimeInput = ({title, dateName, selectedDate, handleSelectedDate, onChange}) => {
const { t } = useTranslation();
return(
<RegistInputItem>
{title && <InputLabel>{title}</InputLabel>}
<DateGroup>
<InputGroup>
<DatePickerWrapper>
<DatePickerComponent name={dateName} selectedDate={selectedDate} handleSelectedDate={handleSelectedDate} pastDate={new Date()} />
</DatePickerWrapper>
<SelectInput
onChange={onChange}
id="hour"
defaultValue={selectedDate && String(selectedDate.getHours()).padStart(2, '0')}
>
{HourList.map(hour => (
<option value={hour} key={hour}>
{hour}
</option>
))}
</SelectInput>
<SelectInput
onChange={onChange}
id="min"
defaultValue={selectedDate && String(selectedDate.getMinutes()).padStart(2, '0')}
>
{MinuteList.map(min => (
<option value={min} key={min}>
{min}
</option>
))}
</SelectInput>
</InputGroup>
<SubText>{t('DATE_KTC')}</SubText>
</DateGroup>
</RegistInputItem>
)
}
export default DateTimeInput;

View File

@@ -0,0 +1,50 @@
import styled from 'styled-components';
import RadioIcon from '../../../assets/img/icon/icon-radio.png';
const RadioInput = ({ label, id, name, value, fontSize, fontWeight, checked, handleChange, handleClick, disabled }) => {
return (
<>
<RadioWrapper fontSize={fontSize} fontWeight={fontWeight} onClick={handleClick}>
<input type="radio" name={name} id={id} value={value} checked={checked} onChange={handleChange} disabled={disabled} />
<label htmlFor={id}>{label}</label>
</RadioWrapper>
</>
);
};
export default RadioInput;
const RadioWrapper = styled.div`
display: inline-block;
vertical-align: middle;
margin-bottom: 5px;
label {
position: relative;
display: inline-block;
cursor: pointer;
padding-left: 20px;
width: max-content;
font-size: ${props => props.fontSize || '14px'};
font-weight: 600;
&:before {
content: '';
display: inline-block;
position: absolute;
top: 50%;
transform: translate(0, -50%);
left: 0;
width: 14px;
height: 14px;
vertical-align: middle;
background: url('${RadioIcon}') no-repeat;
}
}
input {
position: absolute;
opacity: 0;
&:checked + label:before {
background: url('${RadioIcon}') no-repeat;
background-position: -14px 0;
}
}
`;

View File

@@ -0,0 +1,30 @@
import { useNavigate } from 'react-router-dom';
import Modal from '../modal/Modal';
import Button from '../button/Button';
import { BtnWrapper, ButtonClose, ModalText } from '../../../styles/Components';
const AuthModal = () => {
const navigate = useNavigate();
return (
<>
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={'view'}>
<BtnWrapper $justify="flex-end">
<ButtonClose onClick={() => navigate(-1)} />
</BtnWrapper>
<ModalText $align="center">
해당 메뉴에 대한 조회 권한이 없습니다.
<br />
권한 등급을 변경 다시 이용해주세요.
</ModalText>
<BtnWrapper $gap="10px">
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={() => navigate(-1)} />
</BtnWrapper>
</Modal>
</>
);
};
export default AuthModal;

View File

@@ -0,0 +1,19 @@
import { BtnWrapper, ButtonClose, ModalText } from '../../../styles/Components';
import Button from '../button/Button';
import Modal from './Modal';
const CompletedModal = ({modalText, view, handleComplete}) => {
return (
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={view}>
<BtnWrapper $justify="flex-end">
<ButtonClose onClick={handleComplete} />
</BtnWrapper>
<ModalText $align="center">{modalText}</ModalText>
<BtnWrapper $gap="10px">
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleComplete} />
</BtnWrapper>
</Modal>
);
}
export default CompletedModal;

View File

@@ -0,0 +1,29 @@
import { BtnWrapper, ButtonClose, ModalText } from '../../../styles/Components';
import Button from '../button/Button';
import Modal from './Modal';
const ConfirmModal = ({modalText, view, handleClose, handleCancel, handleSubmit, ChildView}) => {
return (
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={view}>
<BtnWrapper $justify="flex-end">
<ButtonClose onClick={handleClose} />
</BtnWrapper>
<ModalText $align="center">
{modalText}
</ModalText>
<BtnWrapper $gap="10px">
<Button text="취소" theme="line" size="large" width="100%" handleClick={handleCancel} />
<Button
text="확인"
theme="primary"
type="submit"
size="large"
width="100%"
handleClick={handleSubmit}
/>
</BtnWrapper>
</Modal>
);
}
export default ConfirmModal;

View File

@@ -0,0 +1,29 @@
import { BtnWrapper, ButtonClose, ModalText } from '../../../styles/Components';
import Button from '../button/Button';
import Modal from './Modal';
const CustomConfirmModal = ({view, handleClose, handleCancel, handleSubmit, ChildView}) => {
return (
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={view}>
<BtnWrapper $justify="flex-end">
<ButtonClose onClick={handleClose} />
</BtnWrapper>
<ModalText $align="center">
{ChildView && <ChildView />}
</ModalText>
<BtnWrapper $gap="10px">
<Button text="취소" theme="line" size="large" width="100%" handleClick={handleCancel} />
<Button
text="확인"
theme="primary"
type="submit"
size="large"
width="100%"
handleClick={handleSubmit}
/>
</BtnWrapper>
</Modal>
);
}
export default CustomConfirmModal;

View File

@@ -0,0 +1,85 @@
import { BtnWrapper, ButtonClose, ModalText } from '../../../styles/Components';
import Button from '../button/Button';
import Modal from './Modal';
import { modalTypes } from '../../../assets/data';
const DynamicModal = ({modalType, view, handleSubmit, handleCancel, modalText, children}) => {
if (!view) return null;
const OkButton = ({handleClick}) => {
return <Button
text="확인"
theme="primary"
type="submit"
size="large"
width="100%"
handleClick={handleClick}
/>
}
const CancelButton = ({handleClick}) => {
return <Button text="취소" theme="line" size="large" width="100%" handleClick={handleClick} />
}
const ModalWrapper = ({children , modalText, handleCancel, view}) => {
return (
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={view}>
<BtnWrapper $justify="flex-end">
<ButtonClose onClick={handleCancel} />
</BtnWrapper>
<ModalText $align="center">
{modalText && modalText}
</ModalText>
<BtnWrapper $gap="10px">
{children}
</BtnWrapper>
</Modal>
)
}
switch (modalType) {
case modalTypes.confirmOkCancel:
return (
<ModalWrapper view={view} modalText={modalText} handleCancel={handleCancel} >
<CancelButton handleClick={handleCancel} />
<OkButton handleClick={handleSubmit} />
</ModalWrapper>
);
case modalTypes.completed:
return (
<ModalWrapper view={view} modalText={modalText} handleCancel={handleSubmit} >
<OkButton handleClick={handleSubmit} />
</ModalWrapper>
);
case modalTypes.childOkCancel:
return (
// <ModalWrapper view={view} modalText={modalText} handleCancel={handleCancel} children={children} >
// <CancelButton handleClick={handleCancel} />
// <OkButton handleClick={handleSubmit} />
// </ModalWrapper>
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={view}>
<BtnWrapper $justify="flex-end">
<ButtonClose onClick={handleCancel} />
</BtnWrapper>
<ModalText $align="center">
{children && children}
</ModalText>
<BtnWrapper $gap="10px">
<Button text="취소" theme="line" size="large" width="100%" handleClick={handleCancel} />
<Button
text="확인"
theme="primary"
type="submit"
size="large"
width="100%"
handleClick={handleSubmit}
/>
</BtnWrapper>
</Modal>
);
default:
return null;
}
}
export default DynamicModal;

View File

@@ -0,0 +1,41 @@
import styled from 'styled-components';
const ModalBg = styled.div`
position: fixed;
background: ${props => props.$bgcolor || 'rgba(0, 0, 0, 0.5)'};
width: 100%;
height: 100%;
top: 0;
left: 0;
min-width: 1080px;
display: ${props => (props.$view === 'hidden' ? 'none' : 'block')};
z-index: 20;
`;
const ModalWrapper = styled.div`
position: absolute;
background: #fff;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
min-width: ${props => props.min || 'auto'};
padding: ${props => props.$padding || '30px'};
border-radius: 30px;
max-height: 90%;
overflow: auto;
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.3);
`;
const Modal = ({ children, $padding, min, $view, $bgcolor }) => {
return (
<>
<ModalBg $view={$view} $bgcolor={$bgcolor}>
<ModalWrapper $padding={$padding} min={min}>
{children}
</ModalWrapper>
</ModalBg>
</>
);
};
export default Modal;