김지은
10 months ago
10 changed files with 706 additions and 45 deletions
@ -0,0 +1,529 @@ |
|||||||
|
import { CustomMainLayout } from '../../components/layout/CustomMainLayout'; |
||||||
|
import { |
||||||
|
Button, |
||||||
|
Col, |
||||||
|
Row, |
||||||
|
Card, |
||||||
|
CardHeader, |
||||||
|
CardTitle, |
||||||
|
CardBody, |
||||||
|
CustomInput |
||||||
|
} from 'reactstrap'; |
||||||
|
import { Search } from 'react-feather'; |
||||||
|
import { FcAlarmClock, FcWorkflow, FcBarChart } from 'react-icons/fc'; |
||||||
|
import { Bar, Doughnut } from 'react-chartjs-2'; |
||||||
|
import { useCallback, useEffect, useRef, useState } from 'react'; |
||||||
|
import { useDispatch, useSelector } from 'react-redux'; |
||||||
|
import * as StcsActions from '../../modules/statistics/actions'; |
||||||
|
|
||||||
|
export default function ResultContainer({ |
||||||
|
tooltipShadow, |
||||||
|
gridLineColor, |
||||||
|
labelColor |
||||||
|
}) { |
||||||
|
const dispatch = useDispatch(); |
||||||
|
const { result, resultSearch } = useSelector(state => state.statisticsState); |
||||||
|
|
||||||
|
const [searchType, setSearchType] = useState({ |
||||||
|
category: 'FLT_RESULT', |
||||||
|
dateType: 'year', |
||||||
|
year: new Date().getFullYear(), |
||||||
|
month: new Date().getMonth() + 1, |
||||||
|
day: new Date().getDate() |
||||||
|
}); |
||||||
|
const [dateLists, setDateLists] = useState({ |
||||||
|
month: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], |
||||||
|
day: [] |
||||||
|
}); |
||||||
|
|
||||||
|
const titleName = '비행 실적 통계'; |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
dispatch(StcsActions.RESULT_STCS.request()); |
||||||
|
}, []); |
||||||
|
|
||||||
|
// 해당 월에 맞는 요일 표출
|
||||||
|
useEffect(() => { |
||||||
|
const { year, month } = searchType; |
||||||
|
const lastDay = new Date(year, Number(month), 0).getDate(); |
||||||
|
const dayList = Array.from({ length: lastDay }, (_, index) => index + 1); |
||||||
|
|
||||||
|
setDateLists({ ...dateLists, day: dayList }); |
||||||
|
}, [searchType.month]); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
const { category, dateType, year, month, day } = searchType; |
||||||
|
|
||||||
|
const dateMapping = { |
||||||
|
month: year, |
||||||
|
day: `${year}-${month}`, |
||||||
|
'one-day': `${year}-${month}-${day}` |
||||||
|
}; |
||||||
|
const date = dateMapping[dateType] || ''; |
||||||
|
|
||||||
|
dispatch( |
||||||
|
StcsActions.RESULT_STCS_SEARCH.request({ |
||||||
|
cate: category, |
||||||
|
date, |
||||||
|
type: dateType |
||||||
|
}) |
||||||
|
); |
||||||
|
}, [searchType]); |
||||||
|
|
||||||
|
// 검색조건 handler
|
||||||
|
const handleChangeSearchType = useCallback( |
||||||
|
(type, val) => { |
||||||
|
setSearchType({ |
||||||
|
...searchType, |
||||||
|
[type]: val |
||||||
|
}); |
||||||
|
}, |
||||||
|
[searchType] |
||||||
|
); |
||||||
|
|
||||||
|
// 그래프 타이틀 handler
|
||||||
|
const handlerTitleName = category => { |
||||||
|
const categoryMappings = { |
||||||
|
TIME: '비행 실적', |
||||||
|
DISTANCE: '비행 계획', |
||||||
|
FLT_COUNT: '비행 승인' |
||||||
|
}; |
||||||
|
return categoryMappings[category]; |
||||||
|
}; |
||||||
|
|
||||||
|
const handlerBarTicks = () => { |
||||||
|
const data = resultSearch.topData.map(i => i.value); |
||||||
|
const max = Math.max(...data); |
||||||
|
const min = Math.min(...data); |
||||||
|
const stepSize = handlerStepSize(max); |
||||||
|
|
||||||
|
return { max, min, stepSize }; |
||||||
|
}; |
||||||
|
|
||||||
|
const handlerStepSize = max => { |
||||||
|
if (max <= 1000) { |
||||||
|
return 100; |
||||||
|
} else if (max <= 5000) { |
||||||
|
return 500; |
||||||
|
} else { |
||||||
|
return 1000; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const options = { |
||||||
|
elements: { |
||||||
|
rectangle: { |
||||||
|
borderWidth: 2, |
||||||
|
borderSkipped: 'bottom' |
||||||
|
} |
||||||
|
}, |
||||||
|
responsive: true, |
||||||
|
maintainAspectRatio: false, |
||||||
|
responsiveAnimationDuration: 500, |
||||||
|
legend: { |
||||||
|
display: false |
||||||
|
}, |
||||||
|
tooltips: { |
||||||
|
// Updated default tooltip UI
|
||||||
|
shadowOffsetX: 1, |
||||||
|
shadowOffsetY: 1, |
||||||
|
shadowBlur: 8, |
||||||
|
shadowColor: tooltipShadow, |
||||||
|
backgroundColor: '#fff', |
||||||
|
titleFontColor: '#000', |
||||||
|
bodyFontColor: '#000' |
||||||
|
}, |
||||||
|
scales: { |
||||||
|
xAxes: [ |
||||||
|
{ |
||||||
|
display: true, |
||||||
|
gridLines: { |
||||||
|
display: true, |
||||||
|
color: gridLineColor, |
||||||
|
zeroLineColor: gridLineColor |
||||||
|
}, |
||||||
|
scaleLabel: { |
||||||
|
display: false |
||||||
|
}, |
||||||
|
ticks: { |
||||||
|
fontColor: labelColor |
||||||
|
} |
||||||
|
} |
||||||
|
], |
||||||
|
yAxes: [ |
||||||
|
{ |
||||||
|
display: true, |
||||||
|
gridLines: { |
||||||
|
color: gridLineColor, |
||||||
|
zeroLineColor: gridLineColor |
||||||
|
}, |
||||||
|
ticks: { |
||||||
|
...handlerBarTicks(), |
||||||
|
fontColor: labelColor |
||||||
|
} |
||||||
|
} |
||||||
|
] |
||||||
|
} |
||||||
|
}, |
||||||
|
data = { |
||||||
|
labels: resultSearch?.topData.map(i => i.name), |
||||||
|
datasets: [ |
||||||
|
{ |
||||||
|
data: resultSearch?.topData.map(i => i.value), |
||||||
|
backgroundColor: '#00bcd4', |
||||||
|
borderColor: '#00bcd4', |
||||||
|
barThickness: 15 |
||||||
|
} |
||||||
|
] |
||||||
|
}; |
||||||
|
|
||||||
|
const options2 = { |
||||||
|
responsive: true, |
||||||
|
maintainAspectRatio: true, |
||||||
|
responsiveAnimationDuration: 500, |
||||||
|
cutoutPercentage: 60, |
||||||
|
legend: { |
||||||
|
position: 'bottom', |
||||||
|
labels: { |
||||||
|
usePointStyle: true, |
||||||
|
padding: 18, |
||||||
|
boxWidth: 8, |
||||||
|
fontColor: labelColor, |
||||||
|
fontSize: 14, |
||||||
|
fontWeight: 500, |
||||||
|
fontFamily: ['Rubik', 'Montserrat', 'NotoSansKR'] |
||||||
|
} |
||||||
|
}, |
||||||
|
tooltips: { |
||||||
|
callbacks: { |
||||||
|
label(tooltipItem, data) { |
||||||
|
const label = data.datasets[0].labels[tooltipItem.index] || '', |
||||||
|
value = data.datasets[0].data[tooltipItem.index]; |
||||||
|
const output = ` ${label} : ${value} %`; |
||||||
|
return output; |
||||||
|
} |
||||||
|
}, |
||||||
|
shadowOffsetX: 1, |
||||||
|
shadowOffsetY: 1, |
||||||
|
shadowBlur: 8, |
||||||
|
shadowColor: tooltipShadow, |
||||||
|
backgroundColor: '#fff', |
||||||
|
titleFontColor: '#000', |
||||||
|
bodyFontColor: '#000' |
||||||
|
} |
||||||
|
}, |
||||||
|
data2 = { |
||||||
|
labels: resultSearch?.graphData.map(i => i.name), |
||||||
|
datasets: [ |
||||||
|
{ |
||||||
|
labels: resultSearch?.graphData.map(i => i.name), |
||||||
|
data: resultSearch?.graphData.map(i => i.value), |
||||||
|
// 레드버전
|
||||||
|
// backgroundColor: [
|
||||||
|
// '#ffe8d1',
|
||||||
|
// '#ffb59e',
|
||||||
|
// '#f0826b',
|
||||||
|
// '#Bd4f38',
|
||||||
|
// '#8a1c05'
|
||||||
|
// ],
|
||||||
|
|
||||||
|
backgroundColor: [ |
||||||
|
'#ccffff', |
||||||
|
'#99ffff', |
||||||
|
'#66ffff', |
||||||
|
'#33efff', |
||||||
|
'#00bcd4' |
||||||
|
], |
||||||
|
borderWidth: 0, |
||||||
|
pointStyle: 'rectRounded' |
||||||
|
} |
||||||
|
] |
||||||
|
}; |
||||||
|
|
||||||
|
return ( |
||||||
|
<CustomMainLayout title={titleName}> |
||||||
|
<div className='pal-card-box statistics'> |
||||||
|
<div> |
||||||
|
<Row> |
||||||
|
<Col md='4'> |
||||||
|
<div> |
||||||
|
<table className='statistics-table'> |
||||||
|
<tr> |
||||||
|
<th rowSpan='2'> |
||||||
|
<span> |
||||||
|
<FcAlarmClock /> |
||||||
|
</span> |
||||||
|
<span>비행 실적</span> |
||||||
|
<span>PA0001</span> |
||||||
|
</th> |
||||||
|
<td colSpan='3'> |
||||||
|
<span className='date'>년</span> |
||||||
|
<span className='date-data'>8일 10시간 35분 12초</span> |
||||||
|
</td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<td> |
||||||
|
<span className='date'>월</span> |
||||||
|
<span className='date-data'>1일 35분 12초</span> |
||||||
|
</td> |
||||||
|
<td> |
||||||
|
<span className='date'>일</span> |
||||||
|
<span className='date-data'>35분 12초</span> |
||||||
|
</td> |
||||||
|
</tr> |
||||||
|
</table> |
||||||
|
</div> |
||||||
|
</Col> |
||||||
|
<Col md='4'> |
||||||
|
<div> |
||||||
|
<table className='statistics-table'> |
||||||
|
<tr> |
||||||
|
<th rowSpan='2'> |
||||||
|
<span> |
||||||
|
<FcWorkflow /> |
||||||
|
</span> |
||||||
|
<span>비행 계획</span> |
||||||
|
<span>PA0002</span> |
||||||
|
</th> |
||||||
|
<td colSpan='3'> |
||||||
|
<span className='date'>년</span> |
||||||
|
<span className='date-data'>10,845m</span> |
||||||
|
</td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<td> |
||||||
|
<span className='date'>월</span> |
||||||
|
<span className='date-data'>1,201m</span> |
||||||
|
</td> |
||||||
|
<td> |
||||||
|
<span className='date'>일</span> |
||||||
|
<span className='date-data'>53m</span> |
||||||
|
</td> |
||||||
|
</tr> |
||||||
|
</table> |
||||||
|
</div> |
||||||
|
</Col> |
||||||
|
<Col md='4'> |
||||||
|
<div> |
||||||
|
<table className='statistics-table'> |
||||||
|
<tr> |
||||||
|
<th rowSpan='2'> |
||||||
|
<span> |
||||||
|
<FcBarChart /> |
||||||
|
</span> |
||||||
|
<span>비행 승인</span> |
||||||
|
<span>PA0002</span> |
||||||
|
</th> |
||||||
|
<td colSpan='3'> |
||||||
|
<span className='date'>년</span> |
||||||
|
<span className='date-data'>1,024,845건</span> |
||||||
|
</td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<td> |
||||||
|
<span className='date'>월</span> |
||||||
|
<span className='date-data'>111,201건</span> |
||||||
|
</td> |
||||||
|
<td> |
||||||
|
<span className='date'>일</span> |
||||||
|
<span className='date-data'>153건</span> |
||||||
|
</td> |
||||||
|
</tr> |
||||||
|
</table> |
||||||
|
</div> |
||||||
|
</Col> |
||||||
|
</Row> |
||||||
|
</div> |
||||||
|
<div> |
||||||
|
<Row> |
||||||
|
<Col> |
||||||
|
<div className='mt-2 cont-ti d-flex justify-content-between align-items-sm-center align-items-start flex-sm-row'> |
||||||
|
<div> |
||||||
|
<h4>검색조건</h4> |
||||||
|
</div> |
||||||
|
{/* <div className='d-flex align-items-center'> |
||||||
|
<Button.Ripple color='primary' size='sm'> |
||||||
|
<Search size={16} /> |
||||||
|
검색 |
||||||
|
</Button.Ripple> |
||||||
|
</div> */} |
||||||
|
</div> |
||||||
|
<Card> |
||||||
|
<CardBody className='pal-card-body'> |
||||||
|
<div className='search-cont'> |
||||||
|
<dl> |
||||||
|
<dt> |
||||||
|
<div className='search-box'> |
||||||
|
<div className='search-list-ti'>검색조건</div> |
||||||
|
<div className='search-list'> |
||||||
|
<div className='search-list-cont'> |
||||||
|
<Row> |
||||||
|
<Col className='list-input' md='4' sm='12'> |
||||||
|
<CustomInput |
||||||
|
inline |
||||||
|
type='select' |
||||||
|
id='' |
||||||
|
bsSize='sm' |
||||||
|
onChange={e => |
||||||
|
handleChangeSearchType( |
||||||
|
'category', |
||||||
|
e.target.value |
||||||
|
) |
||||||
|
} |
||||||
|
> |
||||||
|
<option value={'FLT_RESULT'}> |
||||||
|
비행 실적 |
||||||
|
</option> |
||||||
|
<option value={'FLT_PLAN'}> |
||||||
|
비행 계획 |
||||||
|
</option> |
||||||
|
<option value={'FLT_PLAN_APRVN'}> |
||||||
|
비행 승인 |
||||||
|
</option> |
||||||
|
</CustomInput> |
||||||
|
</Col> |
||||||
|
</Row> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</dt> |
||||||
|
</dl> |
||||||
|
</div> |
||||||
|
</CardBody> |
||||||
|
</Card> |
||||||
|
</Col> |
||||||
|
</Row> |
||||||
|
</div> |
||||||
|
<div> |
||||||
|
<Row className='mt-2'> |
||||||
|
<Col md='8' className='chart-wrap'> |
||||||
|
<Card> |
||||||
|
<CardHeader className='d-flex justify-content-between align-items-sm-center align-items-start flex-sm-row flex-column'> |
||||||
|
<CardTitle tag='h4'> |
||||||
|
{handlerTitleName(searchType.category)} 통계 |
||||||
|
</CardTitle> |
||||||
|
<div className='select-date-wrap'> |
||||||
|
<Row> |
||||||
|
<div className='select-date'> |
||||||
|
<CustomInput |
||||||
|
inline |
||||||
|
type='select' |
||||||
|
id='' |
||||||
|
bsSize='sm' |
||||||
|
value={searchType.type} |
||||||
|
onChange={e => |
||||||
|
handleChangeSearchType('dateType', e.target.value) |
||||||
|
} |
||||||
|
> |
||||||
|
<option value='year'>년</option> |
||||||
|
<option value='month'>월</option> |
||||||
|
<option value='day'>일</option> |
||||||
|
<option value='one-day'>시간</option> |
||||||
|
</CustomInput> |
||||||
|
</div> |
||||||
|
|
||||||
|
{searchType.dateType === 'month' || |
||||||
|
searchType.dateType === 'day' || |
||||||
|
searchType.dateType === 'one-day' ? ( |
||||||
|
<> |
||||||
|
<div className='select-date'> |
||||||
|
<CustomInput |
||||||
|
inline |
||||||
|
type='select' |
||||||
|
id='' |
||||||
|
bsSize='sm' |
||||||
|
value={searchType.year} |
||||||
|
onChange={e => |
||||||
|
handleChangeSearchType('year', e.target.value) |
||||||
|
} |
||||||
|
> |
||||||
|
<option>2023</option> |
||||||
|
</CustomInput> |
||||||
|
</div> |
||||||
|
{searchType.dateType === 'day' || |
||||||
|
searchType.dateType === 'one-day' ? ( |
||||||
|
<div className='select-date'> |
||||||
|
<CustomInput |
||||||
|
inline |
||||||
|
type='select' |
||||||
|
id='' |
||||||
|
bsSize='sm' |
||||||
|
value={searchType.month} |
||||||
|
onChange={e => |
||||||
|
handleChangeSearchType( |
||||||
|
'month', |
||||||
|
e.target.value |
||||||
|
) |
||||||
|
} |
||||||
|
> |
||||||
|
{dateLists.month.map(i => ( |
||||||
|
<option value={i} key={i}> |
||||||
|
{i} |
||||||
|
</option> |
||||||
|
))} |
||||||
|
</CustomInput> |
||||||
|
</div> |
||||||
|
) : null} |
||||||
|
{searchType.dateType === 'one-day' ? ( |
||||||
|
<div className='select-date'> |
||||||
|
<CustomInput |
||||||
|
inline |
||||||
|
type='select' |
||||||
|
id='' |
||||||
|
bsSize='sm' |
||||||
|
value={searchType.day} |
||||||
|
onChange={e => |
||||||
|
handleChangeSearchType('day', e.target.value) |
||||||
|
} |
||||||
|
> |
||||||
|
{dateLists.day.map(i => ( |
||||||
|
<option value={i} key={i}> |
||||||
|
{i} |
||||||
|
</option> |
||||||
|
))} |
||||||
|
</CustomInput> |
||||||
|
</div> |
||||||
|
) : null} |
||||||
|
</> |
||||||
|
) : null} |
||||||
|
</Row> |
||||||
|
{/* <Calendar size={14} /> |
||||||
|
<Flatpickr |
||||||
|
options={{ |
||||||
|
mode: 'range', |
||||||
|
defaultDate: ['2019-05-01', '2019-05-10'] |
||||||
|
}} |
||||||
|
className='form-control flat-picker bg-transparent border-0 shadow-none' |
||||||
|
/> */} |
||||||
|
</div> |
||||||
|
</CardHeader> |
||||||
|
<CardBody> |
||||||
|
<div style={{ height: '400px' }}> |
||||||
|
<Bar data={data} options={options} height={400} /> |
||||||
|
</div> |
||||||
|
</CardBody> |
||||||
|
</Card> |
||||||
|
</Col> |
||||||
|
<Col md='4' className='chart-wrap'> |
||||||
|
<Card> |
||||||
|
<CardHeader className='d-flex justify-content-between align-items-sm-center align-items-start flex-sm-row flex-column'> |
||||||
|
<CardTitle tag='h4'> |
||||||
|
{handlerTitleName(searchType.category)} TOP5 |
||||||
|
</CardTitle> |
||||||
|
</CardHeader> |
||||||
|
<CardBody> |
||||||
|
<div style={{ height: '275px' }}> |
||||||
|
<Doughnut data={data2} options={options2} height={275} /> |
||||||
|
</div> |
||||||
|
{/* <div className='d-flex justify-content-between mt-3 mb-1'> |
||||||
|
<div className='d-flex align-items-center'></div> |
||||||
|
</div> */} |
||||||
|
</CardBody> |
||||||
|
</Card> |
||||||
|
</Col> |
||||||
|
</Row> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</CustomMainLayout> |
||||||
|
); |
||||||
|
} |
@ -1,46 +1,41 @@ |
|||||||
export interface IStcsState { |
export interface IStatisticsState { |
||||||
flight: Flight; |
flight: IStcsRs[]; |
||||||
flightSearch: FlightSearch; |
flightSearch: IStcsSearchRs; |
||||||
|
result: IStcsRs[]; |
||||||
|
resultSearch: IStcsSearchRs; |
||||||
} |
} |
||||||
|
|
||||||
export interface Flight { |
export interface IStcsRs { |
||||||
count: number; |
name: string; |
||||||
data: { |
year: string; |
||||||
name: string; |
month: string; |
||||||
year: string; |
day: string; |
||||||
month: string; |
|
||||||
day: string; |
|
||||||
}[]; |
|
||||||
} |
} |
||||||
|
|
||||||
export interface FlightSearch { |
export interface IStcsSearchRs { |
||||||
count: number; |
graphData: IStcsSearchData[]; |
||||||
data: { |
topData: IStcsSearchData[]; |
||||||
graphData: SearchData[]; |
|
||||||
topData: SearchData[]; |
|
||||||
}; |
|
||||||
} |
} |
||||||
export interface SearchData { |
export interface IStcsSearchData { |
||||||
name: string; |
name: string; |
||||||
value: number; |
value: number; |
||||||
} |
} |
||||||
|
|
||||||
export interface FlightSearchRq { |
export interface IStcsSearchRq { |
||||||
cate: string; |
cate: string; |
||||||
date: string; |
date: string; |
||||||
type: string; |
type: string; |
||||||
} |
} |
||||||
|
|
||||||
export const initialState = { |
export const initialState = { |
||||||
flight: { |
flight: [], |
||||||
count: 0, |
|
||||||
data: [] |
|
||||||
}, |
|
||||||
flightSearch: { |
flightSearch: { |
||||||
count: 0, |
graphData: [], |
||||||
data: { |
topData: [] |
||||||
graphData: [], |
}, |
||||||
topData: [] |
result: [], |
||||||
} |
resultSearch: { |
||||||
|
graphData: [], |
||||||
|
topData: [] |
||||||
} |
} |
||||||
}; |
}; |
||||||
|
@ -0,0 +1,8 @@ |
|||||||
|
import '../../assets/css/custom.css'; |
||||||
|
import '@styles/react/libs/flatpickr/flatpickr.scss'; |
||||||
|
import '@styles/react/libs/tables/react-dataTable-component.scss'; |
||||||
|
import FlightResultContainer from '../../containers/statistics/FlightResultContainer'; |
||||||
|
|
||||||
|
export default function FlightResultView() { |
||||||
|
return <FlightResultContainer />; |
||||||
|
} |
Loading…
Reference in new issue