init
This commit is contained in:
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;
|
||||
79
src/components/common/Date/DateRangePicker.js
Normal file
79
src/components/common/Date/DateRangePicker.js
Normal 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;
|
||||
171
src/components/common/Date/DateTimeRangerPickerComponent.js
Normal file
171
src/components/common/Date/DateTimeRangerPickerComponent.js
Normal 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;
|
||||
44
src/components/common/Date/SingleDatePicker.js
Normal file
44
src/components/common/Date/SingleDatePicker.js
Normal 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;
|
||||
83
src/components/common/Date/SingleTimePicker.js
Normal file
83
src/components/common/Date/SingleTimePicker.js
Normal 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;
|
||||
154
src/components/common/Date/TimeRangePicker.js
Normal file
154
src/components/common/Date/TimeRangePicker.js
Normal 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;
|
||||
16
src/components/common/Date/index.js
Normal file
16
src/components/common/Date/index.js
Normal 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,
|
||||
};
|
||||
97
src/components/common/DatePickerComponent.js
Normal file
97
src/components/common/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;
|
||||
171
src/components/common/DateTimeRangerPickerComponent.js
Normal file
171
src/components/common/DateTimeRangerPickerComponent.js
Normal 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;
|
||||
31
src/components/common/Header/Header.js
Normal file
31
src/components/common/Header/Header.js
Normal 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;
|
||||
244
src/components/common/Header/Navi.js
Normal file
244
src/components/common/Header/Navi.js
Normal 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;
|
||||
`;
|
||||
128
src/components/common/Header/Profile.js
Normal file
128
src/components/common/Header/Profile.js
Normal 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;
|
||||
`;
|
||||
4
src/components/common/Header/index.js
Normal file
4
src/components/common/Header/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
import Header from './Header';
|
||||
import Profile from './Profile';
|
||||
|
||||
export { Header, Profile };
|
||||
23
src/components/common/Layout/Layout.js
Normal file
23
src/components/common/Layout/Layout.js
Normal 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;
|
||||
17
src/components/common/Layout/LoginLayout.js
Normal file
17
src/components/common/Layout/LoginLayout.js
Normal 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;
|
||||
28
src/components/common/Layout/MainLayout.js
Normal file
28
src/components/common/Layout/MainLayout.js
Normal 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;
|
||||
`;
|
||||
5
src/components/common/Layout/index.js
Normal file
5
src/components/common/Layout/index.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import Layout from './Layout';
|
||||
import LoginLayout from './LoginLayout';
|
||||
import MainLayout from './MainLayout';
|
||||
|
||||
export { Layout, LoginLayout, MainLayout };
|
||||
28
src/components/common/Loading.js
Normal file
28
src/components/common/Loading.js
Normal 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;
|
||||
106
src/components/common/Pagination/Pagination.js
Normal file
106
src/components/common/Pagination/Pagination.js
Normal 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;
|
||||
}
|
||||
`;
|
||||
715
src/components/common/SearchBar/SearchBar.js
Normal file
715
src/components/common/SearchBar/SearchBar.js
Normal 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);
|
||||
57
src/components/common/SearchBar/SearchBarLayout.js
Normal file
57
src/components/common/SearchBar/SearchBarLayout.js
Normal 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;
|
||||
`;
|
||||
121
src/components/common/SearchBar/SearchPeriod.js
Normal file
121
src/components/common/SearchBar/SearchPeriod.js
Normal 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;
|
||||
}
|
||||
`;
|
||||
5
src/components/common/SearchBar/index.js
Normal file
5
src/components/common/SearchBar/index.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import SearchBar from './SearchBar';
|
||||
import SearchBarLayout from './SearchBarLayout';
|
||||
import SearchPeriod from './SearchPeriod';
|
||||
|
||||
export { SearchBar, SearchBarLayout, SearchPeriod };
|
||||
44
src/components/common/Table/ViewTableInfo.js
Normal file
44
src/components/common/Table/ViewTableInfo.js
Normal 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;
|
||||
151
src/components/common/button/Button.js
Normal file
151
src/components/common/button/Button.js
Normal 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;
|
||||
113
src/components/common/button/ExcelDownButton.js
Normal file
113
src/components/common/button/ExcelDownButton.js
Normal 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;
|
||||
42
src/components/common/index.js
Normal file
42
src/components/common/index.js
Normal 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
|
||||
};
|
||||
64
src/components/common/input/AuthCheckBox.js
Normal file
64
src/components/common/input/AuthCheckBox.js
Normal 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;
|
||||
}
|
||||
`;
|
||||
74
src/components/common/input/CheckBox.js
Normal file
74
src/components/common/input/CheckBox.js
Normal 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;
|
||||
}
|
||||
`;
|
||||
46
src/components/common/input/DateTimeInput.js
Normal file
46
src/components/common/input/DateTimeInput.js
Normal 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;
|
||||
50
src/components/common/input/Radio.js
Normal file
50
src/components/common/input/Radio.js
Normal 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;
|
||||
}
|
||||
}
|
||||
`;
|
||||
30
src/components/common/modal/AuthModal.js
Normal file
30
src/components/common/modal/AuthModal.js
Normal 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;
|
||||
19
src/components/common/modal/CompletedModal.js
Normal file
19
src/components/common/modal/CompletedModal.js
Normal 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;
|
||||
29
src/components/common/modal/ConfirmModal.js
Normal file
29
src/components/common/modal/ConfirmModal.js
Normal 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;
|
||||
29
src/components/common/modal/CustomConfirmModal.js
Normal file
29
src/components/common/modal/CustomConfirmModal.js
Normal 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;
|
||||
85
src/components/common/modal/DynamicModal.js
Normal file
85
src/components/common/modal/DynamicModal.js
Normal 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;
|
||||
41
src/components/common/modal/Modal.js
Normal file
41
src/components/common/modal/Modal.js
Normal 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;
|
||||
Reference in New Issue
Block a user