[feat] validation 추가 중

This commit is contained in:
Seula Kang
2022-09-20 20:36:09 +09:00
20 changed files with 641 additions and 124 deletions

View File

@@ -2,13 +2,29 @@
"dashboardId": "0001",
"title": "대시보드 1",
"templateId": null,
"layout": [
{
"x": 0,
"y": 0,
"w": 3,
"h": 1,
"i": "000001"
},
{
"x": 0,
"y": 10,
"w": 5,
"h": 2,
"i": "000002"
}
],
"widgets": [
{
"id": "000001",
"widgetId": "000001",
"title": "라인차트 위젯",
"date": "2022-04-11T18:27:45+09:00",
"dataSetId": "",
"type": "lineChart",
"dataSetId": "000001",
"type": "CHART_LINE",
"option": {
"xField": "name",
"series": [
@@ -31,6 +47,36 @@
],
"legendPosition": "left"
}
},
{
"widgetId": "000002",
"title": "파이차트 위젯",
"date": "2022-04-11T18:27:45+09:00",
"dataSetId": "00001",
"type": "CHART_PIE",
"option": {
"xField": "name",
"series": {
"field": "high",
"color": [
"#5470c6",
"#91cc75",
"#fac858",
"#ee6666",
"#73c0de",
"#3ba272",
"#fc8452",
"#9a60b4",
"#ea7ccc",
"#5470c6",
"#91cc75",
"#fac858"
],
"aggregation": "",
"label": "name"
},
"legendPosition": "right"
}
}
]
}

View File

@@ -4,7 +4,7 @@
"title": "위젯1",
"createDate": "2022-04-11T18:27:45+09:00",
"dataId": "",
"type": "LINECHART",
"type": "CHART_LINE",
"option": {
"xField": "title",
"series": [
@@ -33,7 +33,7 @@
"title": "위젯2",
"createDate": "2022-01-20T13:51:43+09:00",
"dataId": "",
"type": "PIECHART",
"type": "CHART_PIE",
"option": {}
}
]

View File

@@ -0,0 +1,98 @@
[
{
"id": 1,
"name": "january",
"color": "#00BFFF",
"high": 18,
"low": 2,
"avg": 8.9
},
{
"id": 2,
"name": "february",
"color": "#4682B4",
"high": 23,
"low": -2,
"avg": 9.9
},
{
"id": 3,
"name": "march",
"color": "#00CED1",
"high": 25,
"low": -1,
"avg": 11.3
},
{
"id": 4,
"name": "april",
"color": "#3CB371",
"high": 23,
"low": 1,
"avg": 13
},
{
"id": 5,
"name": "may",
"color": "#9ACD32",
"high": 26,
"low": 5,
"avg": 16.2
},
{
"id": 6,
"name": "june",
"color": "#BDB76B",
"high": 32,
"low": 7,
"avg": 19.9
},
{
"id": 7,
"name": "july",
"color": "#FFD700",
"high": 33,
"low": 12,
"avg": 23
},
{
"id": 8,
"name": "august",
"color": "#FF8C00",
"high": 33,
"low": 12,
"avg": 23.6
},
{
"id": 9,
"name": "september",
"color": "#FF7F50",
"high": 32,
"low": 7,
"avg": 21.1
},
{
"id": 10,
"name": "october",
"color": "#F4A460",
"high": 28,
"low": 2,
"avg": 17
},
{
"id": 11,
"name": "november",
"color": "#8B4513",
"high": 25,
"low": -2,
"avg": 12.5
},
{
"id": 12,
"name": "december",
"color": "#778899",
"high": 22,
"low": -2,
"avg": 10
}
]

View File

@@ -0,0 +1,142 @@
[
{
"year": 1970,
"country": "Saudi Arabia",
"oil": 192.2
},
{
"year": 1970,
"country": "USA",
"oil": 533.5
},
{
"year": 1970,
"country": "Iran",
"oil": 192.6
},
{
"year": 1970,
"country": "Mexico",
"oil": 24.2
},
{
"year": 1980,
"country": "Saudi Arabia",
"oil": 509.8
},
{
"year": 1980,
"country": "USA",
"oil": 480.2
},
{
"year": 1980,
"country": "Iran",
"oil": 74.3
},
{
"year": 1980,
"country": "Mexico",
"oil": 107.2
},
{
"year": 1990,
"country": "Saudi Arabia",
"oil": 342.6
},
{
"year": 1990,
"country": "USA",
"oil": 416.6
},
{
"year": 1990,
"country": "Iran",
"oil": 162.8
},
{
"year": 1990,
"country": "Mexico",
"oil": 146.3
},
{
"year": 1990,
"country": "Russia",
"oil": 515.9
},
{
"year": 2000,
"country": "Saudi Arabia",
"oil": 456.3
},
{
"year": 2000,
"country": "USA",
"oil": 352.6
},
{
"year": 2000,
"country": "Iran",
"oil": 191.3
},
{
"year": 2000,
"country": "Mexico",
"oil": 171.2
},
{
"year": 2000,
"country": "Russia",
"oil": 323.3
},
{
"year": 2008,
"country": "Saudi Arabia",
"oil": 515.3
},
{
"year": 2008,
"country": "USA",
"oil": 304.9
},
{
"year": 2008,
"country": "Iran",
"oil": 209.9
},
{
"year": 2008,
"country": "Mexico",
"oil": 157.7
},
{
"year": 2008,
"country": "Russia",
"oil": 488.5
},
{
"year": 2009,
"country": "Saudi Arabia",
"oil": 459.5
},
{
"year": 2009,
"country": "USA",
"oil": 325.3
},
{
"year": 2009,
"country": "Iran",
"oil": 202.4
},
{
"year": 2009,
"country": "Mexico",
"oil": 147.5
},
{
"year": 2009,
"country": "Russia",
"oil": 494.2
}
]

View File

@@ -0,0 +1,126 @@
[
{
"day": "1",
"value": 11
},
{
"day": "2",
"value": 7
},
{
"day": "3",
"value": 6
},
{
"day": "4",
"value": 8
},
{
"day": "5",
"value": 7
},
{
"day": "6",
"value": 7
},
{
"day": "7",
"value": 11
},
{
"day": "8",
"value": 9
},
{
"day": "9",
"value": 5
},
{
"day": "10",
"value": 8
},
{
"day": "11",
"value": 6
},
{
"day": "12",
"value": 9
},
{
"day": "13",
"value": 8
},
{
"day": "14",
"value": 6
},
{
"day": "15",
"value": 6
},
{
"day": "16",
"value": 6
},
{
"day": "17",
"value": 10
},
{
"day": "18",
"value": 9
},
{
"day": "19",
"value": 12
},
{
"day": "20",
"value": 9
},
{
"day": "21",
"value": 8
},
{
"day": "22",
"value": 13
},
{
"day": "23",
"value": 9
},
{
"day": "24",
"value": 7
},
{
"day": "25",
"value": 6
},
{
"day": "26",
"value": 11
},
{
"day": "27",
"value": 8
},
{
"day": "28",
"value": 7
},
{
"day": "29",
"value": 9
},
{
"day": "30",
"value": 7
},
{
"day": "31",
"value": 3
}
]

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React, { useEffect } from 'react';
import { CssBaseline } from '@mui/material';
import Layout from './layouts/Layout';
import Router from './router';

View File

@@ -1,33 +0,0 @@
import React from 'react';
import { Divider, Box, Stack, Typography } from '@mui/material';
function WidgetWrapper(props) {
const { title, width, button } = props;
return (
<Box
sx={{
width: width,
}}
>
<Stack direction="row" justifyContent="space-between" alignItems="center" sx={{ width: '100%', py: 1 }}>
<Typography variant="subtitle1" component="span" sx={{ fontWeight: 500 }}>
{title}
</Typography>
{button}
</Stack>
<Divider sx={{ marginBottom: 4 }} />
{props.children}
</Box>
);
}
WidgetWrapper.defaultProps = {
title: '',
width: '100%',
menuList: false,
naviUrl: false,
button: false,
};
export default WidgetWrapper;

View File

@@ -9,11 +9,9 @@ import './index.css';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<BrowserRouter>
<ThemeProvider theme={theme}>
<App />
</ThemeProvider>
</BrowserRouter>
</React.StrictMode>,
<BrowserRouter>
<ThemeProvider theme={theme}>
<App />
</ThemeProvider>
</BrowserRouter>,
);

View File

@@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react';
import ReactECharts from 'echarts-for-react';
import { Box } from '@mui/material';
const LineChart = props => {
const { option, dataSet, ...rest } = props;
@@ -25,8 +26,10 @@ const LineChart = props => {
};
useEffect(() => {
const newOption = createComponentOption();
setComponentOption(newOption);
if (option && dataSet) {
const newOption = createComponentOption();
setComponentOption(newOption);
}
}, [option, dataSet]);
/**
@@ -41,13 +44,12 @@ const LineChart = props => {
// series option에서 가져오기
const newSeries = [];
const newColors = [];
option.series.forEach((item, index) => {
// console.log(item, index);
option.series.forEach(item => {
if (item.field) {
const series = {
name: item.field,
data: dataSet.map(dataItem => dataItem[item.field]),
type: rest.componentType,
type: 'line',
smooth: true,
};
newSeries.push(series);
@@ -72,7 +74,14 @@ const LineChart = props => {
};
return (
<ReactECharts option={componentOption} style={{ height: '100%', width: '100%' }} lazyUpdate={true} notMerge={true} />
<Box
sx={{
width: '100%',
height: '100%',
}}
>
<ReactECharts option={componentOption} style={{ height: '100%', width: '100%' }} lazyUpdate={true} notMerge={true} />
</Box>
);
};

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import ReactECharts from 'echarts-for-react';
const PieChart = props => {
@@ -15,16 +15,7 @@ const PieChart = props => {
orient: 'vertical',
left: 'left',
},
series: [
{
type: 'pie',
smooth: true,
},
{
type: 'pie',
smooth: true,
},
],
series: [],
emphasis: {
itemStyle: {
shadowBlur: 10,
@@ -35,7 +26,7 @@ const PieChart = props => {
};
useEffect(() => {
// console.log('PieChart ', option, dataSet);
setComponentOption(defaultComponentOption);
setComponentOption(createComponentOption());
}, [option, dataSet]);
@@ -45,30 +36,28 @@ const PieChart = props => {
* 컴포넌트에 맞는 형태로 생성
*/
const createComponentOption = () => {
let newOption = defaultComponentOption;
let newOption = { ...defaultComponentOption };
const getData = () =>
dataSet.map(item => ({
value: item[option.series.field],
name: item[option.series.label],
}));
if (dataSet) {
const op = {
type: 'pie',
smooth: true,
color: [...option.series.color],
series: [
{
type: 'pie',
smooth: true,
data: getData(),
color: option.series.color, // 옵션값은 바뀌는데 색은 왜 제때 안 바뀔까요..
},
],
};
newOption = { ...defaultComponentOption, ...op };
}
return newOption;
};

View File

@@ -13,24 +13,51 @@ import '/node_modules/react-resizable/css/styles.css';
const ResponsiveGridLayout = WidthProvider(Responsive);
function DashboardModify(props) {
const layout = [
{ i: 'a', x: 0, y: 0, w: 1, h: 2, static: true },
{ i: 'b', x: 1, y: 0, w: 3, h: 2, minW: 2, maxW: 4 },
{ i: 'c', x: 4, y: 0, w: 1, h: 2 },
];
// const layout = [
// { i: 'a', x: 0, y: 0, w: 1, h: 2, static: true },
// { i: 'b', x: 1, y: 0, w: 3, h: 2, minW: 2, maxW: 4 },
// { i: 'c', x: 4, y: 0, w: 1, h: 2 },
// ];
// 현재 선택된 widget 목록
const [selectedWidgets, setSelectedWidgets] = useState([]);
// 화면에 그려질 widget 목록
const [widgets, setWidgets] = useState([]);
useEffect(() => {
// 여기서 처리
if (selectedWidgets.length > 0) {
console.log(selectedWidgets);
// console.log(selectedWidgets);
}
}, [selectedWidgets]);
useEffect(() => {
// 여기서 처리
if (widgets.length > 0) {
console.log(widgets);
}
}, [widgets]);
// 현재 위젯 선택창에서 선택된 위젯 목록 callback
const handleWidgetSelect = items => {
setSelectedWidgets(items);
};
const onAddItems = items => {
// console.log('adding', 'n' + this.state.newCounter);
// this.setState({
// // Add a new item. It must have a unique key!
// items: this.state.items.concat({
// i: "n" + this.state.newCounter,
// x: (this.state.items.length * 2) % (this.state.cols || 12),
// y: Infinity, // puts it at the bottom
// w: 2,
// h: 2
// }),
// // Increment the counter to ensure key is always unique.
// newCounter: this.state.newCounter + 1
// });
};
return (
<PageContainer>
<PageTitleBox
@@ -67,10 +94,10 @@ function DashboardModify(props) {
backgroundColor: '#eee',
}}
>
<ResponsiveGridLayout layout={layout} rowHeight={54} compactType={null} cols={{ lg: 20 }}>
<Card key="a">a</Card>
<Card key="b">b</Card>
<Card key="c">c</Card>
<ResponsiveGridLayout rowHeight={54} compactType={null} cols={{ lg: 20 }}>
{/*<Card key="a">a</Card>*/}
{/*<Card key="b">b</Card>*/}
{/*<Card key="c">c</Card>*/}
</ResponsiveGridLayout>
</Box>
</PageTitleBox>

View File

@@ -1,50 +1,110 @@
import React, { useEffect } from 'react';
import { Box, IconButton, Stack } from '@mui/material';
import { Link as RouterLink, useLocation, useSearchParams } from 'react-router-dom';
import React, { useEffect, useState } from 'react';
import { Box, Card, IconButton, Stack } from '@mui/material';
import { Link as RouterLink, useLocation, useMatch, useSearchParams } from 'react-router-dom';
import AutorenewIcon from '@mui/icons-material/Autorenew';
import EditIcon from '@mui/icons-material/Edit';
import DeleteIcon from '@mui/icons-material/Delete';
import PageTitleBox from '@/components/PageTitleBox';
import TitleBox from '@/components/TitleBox';
import { DialogAlertIconButton } from '@/components/button/DialogAlertButton';
import WidgetWrapper from '@/widget/wrapper/WidgetWrapper';
import { get } from '@/helpers/apiHelper';
import BoardListItem from '@/components/BoardListItem';
import RGL, { Responsive, WidthProvider } from 'react-grid-layout';
import '/node_modules/react-grid-layout/css/styles.css';
import '/node_modules/react-resizable/css/styles.css';
const ResponsiveGridLayout = WidthProvider(Responsive);
const DashboardView = props => {
const location = useLocation();
const dashboardId = '222';
const dashboardName = '111';
const match = useMatch('/dashboard/:dashboard_id');
const [dashboardInfo, setDashboardInfo] = useState({ title: '', widgets: [], layout: [] });
const [dashboardId, setDashboardId] = useState(null);
const ReactGridLayout = WidthProvider(RGL);
const layout = [
{
x: 0,
y: 0,
w: 6,
h: 2,
i: '0',
static: true,
},
{
x: 6,
y: 0,
w: 6,
h: 3,
i: '1',
static: true,
},
];
useEffect(() => {
console.log('location : ', location);
console.log('props.match : ', props);
setDashboardId(match.params.dashboard_id);
getDashboardInfo(match.params.dashboard_id);
}, []);
const handleRenewClick = () => {
console.log('renew');
const getDashboardInfo = id => {
get('/data/dummyDashboardInfo.json').then(response => {
setDashboardInfo(response.data);
});
};
const handleRefreshClick = () => {
getDashboardInfo(match.params.dashboard_id);
};
const generateWidget = () => {
console.log('generateWidget', dashboardInfo.widgets);
return dashboardInfo.widgets.map((item, index) => {
console.log('data', item);
return (
<Card key={index} sx={{ width: '100%', height: '100%', borderRadius: 1 }}>
<WidgetWrapper
widgetOption={item}
dataSetId={item.dataSetId}
sx={{ width: '100%', height: '100%', borderRadius: 1 }}
/>
</Card>
);
});
};
return (
<PageTitleBox title="대시보드 조회">
<TitleBox
title={dashboardName}
title={dashboardInfo.title}
button={
<Stack direction="row" spacing={1}>
<IconButton onClick={handleRenewClick} aria-label="새로고침" color="primary">
<IconButton onClick={handleRefreshClick} aria-label="새로고침" color="primary">
<AutorenewIcon />
</IconButton>
<IconButton
component={RouterLink}
to={`/dashboard/modify?id=${dashboardId}&name=${dashboardName}`}
to={`/dashboard/modify?id=${dashboardId}&name=${dashboardInfo.title}`}
aria-label="수정"
>
<EditIcon />
</IconButton>
<DialogAlertIconButton size="small" icon={<DeleteIcon />}>
{`<${dashboardName}>을 삭제하시겠습니까?`}
{`<${dashboardInfo.title}>을 삭제하시겠습니까?`}
</DialogAlertIconButton>
</Stack>
}
>
<Box sx={{ width: '100%', height: '50vw', borderRadius: 1, backgroundColor: '#eee' }} />
<Box
sx={{
width: '1280px',
minHeight: '1080px',
borderRadius: 1,
backgroundColor: '#eee',
}}
>
<ReactGridLayout layout={layout}>{generateWidget()}</ReactGridLayout>
</Box>
</TitleBox>
</PageTitleBox>
);

View File

@@ -4,8 +4,7 @@ import PageTitleBox from '@/components/PageTitleBox';
import BoardList from '@/components/BoardList';
import { Outlet, useParams } from 'react-router-dom';
import AddIconButton from '@/components/button/AddIconButton';
// import { get } from '@/helpers/apiHelper';
import axios from 'axios';
import { get } from '@/helpers/apiHelper';
const title = '대시보드';
@@ -17,8 +16,7 @@ function Dashboard(props) {
const [loadedCount, setLoadedCount] = useState(1);
useEffect(() => {
axios
.get('/data/dummyDashboardList.json')
get('/data/dummyDashboardList.json')
.then(response => response.data)
.then(data => setLoadedWidgetData(data.filter((list, idx) => idx <= 10 * loadedCount)));
setIsLoading(true);

View File

@@ -24,6 +24,8 @@ function WidgetAttributeSelect(props) {
const [switchChart, setSwitchChart] = useState(defaultChart);
useEffect(() => {
console.log('option changed', option);
const ChartProps = {
option: option,
dataSet: chartData,

View File

@@ -35,8 +35,7 @@ function WidgetCreate(props) {
const [isWidgetValueValid, setIsWidgetValueValid] = useState(false);
const [isNextButtonDisabled, setIsNextButtonDisabled] = useState(true);
// const [isSubmit, setIsSubmit] = useState(false);
const isSubmit = false;
const [isSubmit, setIsSubmit] = useState(false);
useEffect(() => {
if (activeStep === 0 && !!dataSet) {
@@ -65,20 +64,17 @@ function WidgetCreate(props) {
};
// 위젯 속성 저장
const handleSubmitClick = event => {
const handleSubmit = event => {
event.preventDefault();
// isSubmit = true;
console.log(event);
return false;
// if (!isWidgetValueValid) {
// return;
// }
// console.log('datesetId:', dataSet);
// console.log('widgetType:', widgetType);
// console.log('widgetTitle:', widgetTitle);
// console.log('widgetOption:', widgetOption);
setIsSubmit(true);
console.log('widgetOption:', widgetOption);
if (!isWidgetValueValid) {
return;
}
console.log('datesetId:', dataSet);
console.log('widgetType:', widgetType);
console.log('widgetTitle:', widgetTitle);
console.log('widgetOption:', widgetOption);
};
return (

View File

@@ -33,7 +33,7 @@ function WidgetModify(props) {
// 위젯 속성 저장
const handleSubmit = event => {
// datasetId , componentId, widgetTitle, option
// dataSetId , componentId, widgetTitle, option
event.preventDefault();
console.log('datesetId:', data.dataId);
console.log('widgetType:', data.type);

View File

@@ -4,8 +4,7 @@ import PageContainer from '@/components/PageContainer';
import PageTitleBox from '@/components/PageTitleBox';
import BoardList from '@/components/BoardList';
import AddIconButton from '@/components/button/AddIconButton';
// import { get } from '@/helpers/apiHelper';
import axios from 'axios';
import { get } from '@/helpers/apiHelper';
const title = '위젯';
@@ -17,8 +16,7 @@ function Widget() {
const [loadedCount, setLoadedCount] = useState(1);
useEffect(() => {
axios
.get('/data/dummyWidgetList.json')
get('/data/dummyWidgetList.json')
.then(response => response.data)
.then(data => setLoadedWidgetData(data.filter((list, idx) => idx <= 10 * loadedCount)));
setIsLoading(true);

View File

@@ -1,14 +1,72 @@
import React, { useEffect } from 'react';
import { Box } from '@mui/material';
import React, { useEffect, useState } from 'react';
import { Box, Divider, Stack, Typography } from '@mui/material';
import { WIDGET_TYPE } from '@/constant';
import LineChart from '@/modules/linechart/LineChart';
import { get } from '@/helpers/apiHelper';
import LineChartSetting from '@/widget/settings/LineChartSetting';
import PieChart from '@/modules/piechart/PieChart';
import PieChartSetting from '@/widget/settings/PieChartSetting';
const WidgetWrapper = props => {
const { widgetId } = props;
const { widgetOption, dataSetId } = props;
const [widget, setWidget] = useState(null);
useEffect(() => {
if (widgetId) {
console.log('widgetId : ', widgetId);
console.log('WidgetWrapper');
if (dataSetId) {
getData();
}
}, [widgetId]);
return <Box></Box>;
}, []);
const getData = () => {
get('/data/sample/chart.json').then(response => {
console.log('res', response.data);
if (widgetOption) {
console.log('widget widgetOption : ', widgetOption);
const widgetType = widgetOption.type;
let module = null;
switch (widgetType) {
case WIDGET_TYPE.CHART_LINE:
module = <LineChart option={widgetOption.option} dataSet={response.data} />;
break;
case WIDGET_TYPE.CHART_BAR:
break;
case WIDGET_TYPE.CHART_PIE:
module = <PieChart option={widgetOption.option} dataSet={response.data} />;
break;
default:
}
setWidget(module);
}
});
};
return (
<Stack
sx={{
width: '100%',
height: '100%',
border: '1px solid #DADDDD',
}}
>
<Stack direction="row" justifyContent="space-between" alignItems="center" sx={{ width: '100%', py: 1 }}>
<Typography variant="subtitle1" component="span" sx={{ fontWeight: 500 }}>
{widgetOption && widgetOption.title}
</Typography>
</Stack>
<Divider sx={{ marginBottom: 4 }} />
<Box
sx={{
width: '100%',
height: '100%',
}}
>
{widget}
</Box>
</Stack>
);
};
export default WidgetWrapper;

3
package-lock.json generated Normal file
View File

@@ -0,0 +1,3 @@
{
"lockfileVersion": 1
}